forked from mirrors/lpac
Add an APDU backend for MBIM devices. The MBIM device path can be passed via the MBIM_DEVICE environment variable. We'll default to /dev/cdc-wdm0. By default we will not operate through the mbim-proxy. Set MBIM_USE_PROXY to 1 to enable this. Like QMI devices, use UIM_PORT to select the active SIM slot. Tested on Semtech EM9191 and EM7590, and Quectel RM520N-GL. resolve #94 Signed-off-by: Frans Klaver <frans.klaver@vislink.com> Co-authored-by: Frans Klaver <frans.klaver@vislink.com>
367 lines
11 KiB
C
367 lines
11 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright (c) 2024, Frans Klaver <frans.klaver@vislink.com>
|
|
*/
|
|
#include "mbim.h"
|
|
|
|
#include <libmbim-glib.h>
|
|
#include <stdio.h>
|
|
#include <euicc/interface.h>
|
|
#include <euicc/euicc.h>
|
|
#include "mbim_helpers.h"
|
|
|
|
struct mbim_data {
|
|
const char *device_path;
|
|
int last_channel_id;
|
|
gboolean use_proxy;
|
|
guint32 uim_slot;
|
|
GMainContext *context;
|
|
MbimDevice *device;
|
|
};
|
|
|
|
static gboolean is_sim_available(struct mbim_data *mbim_priv)
|
|
{
|
|
MbimMessage *request = mbim_message_subscriber_ready_status_query_new(NULL);
|
|
g_autoptr(MbimMessage) response = mbim_device_command_sync(
|
|
mbim_priv->device, mbim_priv->context, request, NULL
|
|
);
|
|
if (!response)
|
|
return FALSE;
|
|
|
|
MbimSubscriberReadyState ready_state;
|
|
if (!mbim_message_subscriber_ready_status_response_parse(
|
|
response, &ready_state, NULL, NULL, NULL, NULL, NULL, NULL
|
|
)) {
|
|
return FALSE;
|
|
}
|
|
|
|
switch (ready_state) {
|
|
case MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE:
|
|
case MBIM_SUBSCRIBER_READY_STATE_INITIALIZED:
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static int select_sim_slot(struct mbim_data *mbim_priv)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
MbimMessage *current_slot_request =
|
|
mbim_message_ms_basic_connect_extensions_device_slot_mappings_query_new(NULL);
|
|
|
|
g_autoptr(MbimMessage) current_slot_response = mbim_device_command_sync(
|
|
mbim_priv->device, mbim_priv->context, current_slot_request, &error
|
|
);
|
|
if (!current_slot_response) {
|
|
fprintf(stderr, "error: device didn't respond: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
guint32 current_slot_count;
|
|
g_autoptr(MbimSlotArray) current_slots = NULL;
|
|
if (!mbim_message_ms_basic_connect_extensions_device_slot_mappings_response_parse(
|
|
current_slot_response, ¤t_slot_count, ¤t_slots, &error
|
|
)) {
|
|
fprintf(stderr, "error: sim select response could not be parsed: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
if (current_slot_count && current_slots[0]->slot == mbim_priv->uim_slot) {
|
|
return 0;
|
|
}
|
|
|
|
g_autoptr(GPtrArray) new_slot_array = g_ptr_array_new_with_free_func(g_free);
|
|
MbimSlot *new_slot = g_new(MbimSlot, 1);
|
|
new_slot->slot = mbim_priv->uim_slot;
|
|
g_ptr_array_add(new_slot_array, new_slot);
|
|
|
|
MbimMessage *update_slot_request = mbim_message_ms_basic_connect_extensions_device_slot_mappings_set_new(
|
|
new_slot_array->len, (const MbimSlot **)new_slot_array->pdata, &error
|
|
);
|
|
if (!update_slot_request) {
|
|
fprintf(stderr, "error: unable to select sim slot: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
g_autoptr(MbimMessage) update_slot_response = mbim_device_command_sync(
|
|
mbim_priv->device, mbim_priv->context, update_slot_request, &error
|
|
);
|
|
if (!update_slot_response) {
|
|
fprintf(stderr, "error: device didn't respond: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
guint32 slot_count;
|
|
g_autoptr(MbimSlotArray) updated_slots = NULL;
|
|
if (!mbim_message_ms_basic_connect_extensions_device_slot_mappings_response_parse(
|
|
update_slot_response, &slot_count, &updated_slots, &error
|
|
)) {
|
|
fprintf(stderr, "error: sim select response could not be parsed: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
int retries = 20;
|
|
while (retries--) {
|
|
if (is_sim_available(mbim_priv)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "sim did not become available\n");
|
|
return -1;
|
|
}
|
|
|
|
static int apdu_interface_connect(struct euicc_ctx *ctx)
|
|
{
|
|
struct mbim_data *mbim_priv = ctx->apdu.interface->userdata;
|
|
g_autoptr(GError) error = NULL;
|
|
GFile *file;
|
|
|
|
file = g_file_new_for_path(mbim_priv->device_path);
|
|
|
|
mbim_priv->context = g_main_context_new();
|
|
|
|
mbim_priv->device = mbim_device_new_from_path(file, mbim_priv->context, &error);
|
|
if (!mbim_priv->device) {
|
|
fprintf(stderr, "error: create mbim device from path failed: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
MbimDeviceOpenFlags open_flags = MBIM_DEVICE_OPEN_FLAGS_NONE;
|
|
if (mbim_priv->use_proxy)
|
|
open_flags |= MBIM_DEVICE_OPEN_FLAGS_NONE;
|
|
|
|
mbim_device_open_sync(mbim_priv->device, open_flags, mbim_priv->context, &error);
|
|
if (error) {
|
|
fprintf(stderr, "error: open mbim device failed: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
return select_sim_slot(mbim_priv);
|
|
}
|
|
|
|
/*
|
|
* Allocate storage in rx and copy the contents of response_data there. Also
|
|
* tack the status at the end, as the MBIM protocol separates the status from
|
|
* the rest of the response.
|
|
*/
|
|
static int copy_data_with_status(
|
|
uint8_t **rx, uint32_t *rx_len,
|
|
const guint8 *response_data, guint32 response_size,
|
|
guint32 status)
|
|
{
|
|
*rx_len = response_size + 2;
|
|
*rx = malloc(*rx_len);
|
|
if (!*rx)
|
|
return -1;
|
|
|
|
memcpy(*rx, response_data, response_size);
|
|
(*rx)[*rx_len - 2] = status & 0xff;
|
|
(*rx)[*rx_len - 1] = (status >> 8) & 0xff;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mbim_apdu_interface_transmit(
|
|
struct euicc_ctx *ctx,
|
|
uint8_t **rx, uint32_t *rx_len,
|
|
const uint8_t *tx, uint32_t tx_len)
|
|
{
|
|
struct mbim_data *mbim_priv = ctx->apdu.interface->userdata;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
MbimMessage *request = mbim_message_ms_uicc_low_level_access_apdu_set_new(
|
|
mbim_priv->last_channel_id,
|
|
MBIM_UICC_SECURE_MESSAGING_NONE,
|
|
MBIM_UICC_CLASS_BYTE_TYPE_INTER_INDUSTRY,
|
|
tx_len,
|
|
tx,
|
|
&error
|
|
);
|
|
if (!request) {
|
|
fprintf(stderr, "error: creating apdu message failed: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
g_autoptr(MbimMessage) response = mbim_device_command_sync(
|
|
mbim_priv->device, mbim_priv->context, request, &error
|
|
);
|
|
if (!response) {
|
|
fprintf(stderr, "error: no apdu response received: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
guint32 status = 0;
|
|
guint32 response_size = 0;
|
|
const guint8 *response_data = NULL;
|
|
|
|
if (!mbim_message_ms_uicc_low_level_access_apdu_response_parse(
|
|
response, &status, &response_size, &response_data, &error
|
|
)) {
|
|
fprintf(stderr, "error: unable to parse apdu response: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
return copy_data_with_status(rx, rx_len, response_data, response_size, status);
|
|
}
|
|
|
|
static int mbim_apdu_interface_logic_channel_open(
|
|
struct euicc_ctx *ctx,
|
|
const uint8_t *aid,
|
|
uint8_t aid_len)
|
|
{
|
|
struct mbim_data *mbim_priv = ctx->apdu.interface->userdata;
|
|
g_autoptr(GError) error = NULL;
|
|
guint8 channel_id;
|
|
|
|
MbimMessage *request = mbim_message_ms_uicc_low_level_access_open_channel_set_new(
|
|
aid_len, aid, 0, 1, &error
|
|
);
|
|
if (!request) {
|
|
fprintf(stderr, "error: creating channel message failed: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
g_autoptr(MbimMessage) response = mbim_device_command_sync(
|
|
mbim_priv->device, mbim_priv->context, request, &error
|
|
);
|
|
if (!response) {
|
|
fprintf(stderr, "error: no channel response received: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
guint32 status = 0;
|
|
guint32 channel = -1;
|
|
guint32 response_size = 0;
|
|
const guint8 *response_data = NULL;
|
|
|
|
if (!mbim_message_ms_uicc_low_level_access_open_channel_response_parse(
|
|
response, &status, &channel, &response_size, &response_data, &error
|
|
)) {
|
|
fprintf(stderr, "error: unable to parse channel response: %s\n", error->message);
|
|
return -1;
|
|
}
|
|
|
|
mbim_priv->last_channel_id = channel;
|
|
return channel;
|
|
}
|
|
|
|
static void mbim_apdu_interface_logic_channel_close(struct euicc_ctx *ctx, uint8_t channel)
|
|
{
|
|
struct mbim_data *mbim_priv = ctx->apdu.interface->userdata;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
MbimMessage *request = mbim_message_ms_uicc_low_level_access_close_channel_set_new(
|
|
channel, 1, &error
|
|
);
|
|
if (!request) {
|
|
fprintf(stderr, "error: creating channel message failed: %s\n", error->message);
|
|
return;
|
|
}
|
|
|
|
g_autoptr(MbimMessage) response = mbim_device_command_sync(
|
|
mbim_priv->device, mbim_priv->context, request, &error
|
|
);
|
|
if (!response) {
|
|
fprintf(stderr, "error: no channel response received: %s\n", error->message);
|
|
return;
|
|
}
|
|
|
|
guint32 status = 0;
|
|
|
|
if (!mbim_message_ms_uicc_low_level_access_close_channel_response_parse(
|
|
response, &status, &error
|
|
)) {
|
|
fprintf(stderr, "error: unable to parse channel response: %s\n", error->message);
|
|
return;
|
|
}
|
|
|
|
if (channel == mbim_priv->last_channel_id)
|
|
mbim_priv->last_channel_id = -1;
|
|
}
|
|
|
|
static void mbim_apdu_interface_disconnect(struct euicc_ctx *ctx)
|
|
{
|
|
struct mbim_data *mbim_priv = ctx->apdu.interface->userdata;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
if (mbim_priv->last_channel_id > 0)
|
|
{
|
|
fprintf(stderr, "Cleaning up leaked APDU channel %d\n", mbim_priv->last_channel_id);
|
|
mbim_apdu_interface_logic_channel_close(NULL, mbim_priv->last_channel_id);
|
|
mbim_priv->last_channel_id = -1;
|
|
}
|
|
|
|
mbim_device_close_sync(mbim_priv->device, mbim_priv->context, &error);
|
|
|
|
g_main_context_unref(mbim_priv->context);
|
|
mbim_priv->context = NULL;
|
|
}
|
|
|
|
static int libapduinterface_init(struct euicc_apdu_interface *ifstruct)
|
|
{
|
|
struct mbim_data *mbim_priv;
|
|
|
|
guint32 uim_slot = 0;
|
|
/*
|
|
* We're using the same UIM_SLOT environment variable as the QMI backends.
|
|
* QMI uses 1-based indexing for the sim slots. MBIM uses 0-based indexing,
|
|
* so account for that.
|
|
*/
|
|
const char *env_uim_slot = getenv("UIM_SLOT");
|
|
if (env_uim_slot) {
|
|
uim_slot = atoi(env_uim_slot);
|
|
if (uim_slot == 0) {
|
|
fprintf(stderr, "error: Invalid UIM_SLOT: '%s'\n", env_uim_slot);
|
|
return -1;
|
|
}
|
|
uim_slot--;
|
|
}
|
|
|
|
mbim_priv = malloc(sizeof(struct mbim_data));
|
|
if (!mbim_priv) {
|
|
fprintf(stderr, "Failed allocating memory\n");
|
|
return -1;
|
|
}
|
|
|
|
mbim_priv->uim_slot = uim_slot;
|
|
|
|
const char *use_proxy = getenv("MBIM_USE_PROXY");
|
|
if (use_proxy && strcmp(use_proxy, "0") != 0) {
|
|
mbim_priv->use_proxy = TRUE;
|
|
}
|
|
|
|
if (!(mbim_priv->device_path = getenv("MBIM_DEVICE"))) {
|
|
mbim_priv->device_path = "/dev/cdc-wdm0";
|
|
}
|
|
|
|
memset(ifstruct, 0, sizeof(struct euicc_apdu_interface));
|
|
ifstruct->connect = apdu_interface_connect;
|
|
ifstruct->disconnect = mbim_apdu_interface_disconnect;
|
|
ifstruct->logic_channel_open = mbim_apdu_interface_logic_channel_open;
|
|
ifstruct->logic_channel_close = mbim_apdu_interface_logic_channel_close;
|
|
ifstruct->transmit = mbim_apdu_interface_transmit;
|
|
ifstruct->userdata = mbim_priv;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int libapduinterface_main(int argc, char **argv)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void libapduinterface_fini(struct euicc_apdu_interface *ifstruct)
|
|
{
|
|
g_free(ifstruct->userdata);
|
|
}
|
|
|
|
const struct euicc_driver driver_apdu_mbim = {
|
|
.type = DRIVER_APDU,
|
|
.name = "mbim",
|
|
.init = (int (*)(void *))libapduinterface_init,
|
|
.main = libapduinterface_main,
|
|
.fini = (void (*)(void *))libapduinterface_fini,
|
|
};
|