driver: apdu: add MBIM backend (#166)

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>
This commit is contained in:
Frans Klaver 2024-11-16 15:43:29 +01:00 committed by GitHub
parent 63f29b4c9f
commit 5987dfa815
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 543 additions and 1 deletions

View file

@ -8,6 +8,7 @@
- `stdio`: use standard input/output
- `qmi`: use QMI
- `qmi_qrtr`: use QMI over QRTR
- `mbim`: use MBIM
- GBinder-based backends for `libhybris` (Halium) distributions:
- `gbinder_hidl`: use HIDL IRadio (SoC launched before Android 13)
* `LPAC_HTTP`: specify which HTTP backend will be used.
@ -15,8 +16,10 @@
- `stdio`: use standard input/ouput
* `AT_DEVICE`: specify which serial port device will be used by AT APDU backend.
* `QMI_DEVICE`: specify which QMI device will be used by QMI APDU backend.
* `UIM_SLOT`: specify which UIM slot will be used by QMI APDU backends. (default: 1)
* `UIM_SLOT`: specify which UIM slot will be used by QMI and MBIM APDU backends. (default: 1, slot number starts from 1)
* `DRIVER_IFID`: specify which PC/SC interface will be used by PC/SC APDU backend.
* `MBIM_DEVICE`: specify which MBIM device will be used by MBIM APDU backend. (default: "/dev/cdc-wdm0")
* `MBIM_USE_PROXY`: tell the MBIM APDU backend to use the mbim-proxy. (default: 0, anything other than 0 means true)
## Debug

View file

@ -10,6 +10,7 @@ endif()
option(LPAC_WITH_APDU_GBINDER "Build APDU Gbinder backend for libhybris devices (requires gbinder headers)" OFF)
option(LPAC_WITH_APDU_QMI "Build QMI backend for Qualcomm devices (requires libqmi)" OFF)
option(LPAC_WITH_APDU_QMI_QRTR "Build QMI-over-QRTR backend for Qualcomm devices (requires libqrtr and libqmi headers)" OFF)
option(LPAC_WITH_APDU_MBIM "Build MBIM backend for MBIM devices (requires libmbim)" OFF)
option(LPAC_WITH_HTTP_CURL "Build HTTP Curl interface" ON)
@ -94,6 +95,19 @@ if(LPAC_WITH_APDU_QMI_QRTR)
endif()
endif()
if(LPAC_WITH_APDU_MBIM)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DLPAC_WITH_APDU_MBIM")
target_sources(euicc-drivers PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/apdu/mbim.c
${CMAKE_CURRENT_SOURCE_DIR}/apdu/mbim_helpers.c)
find_package(PkgConfig REQUIRED)
pkg_check_modules(MBIM_GLIB REQUIRED IMPORTED_TARGET mbim-glib)
target_link_libraries(euicc-drivers PkgConfig::MBIM_GLIB PkgConfig::MBIM_GLIB)
if(LPAC_DYNAMIC_DRIVERS)
list(APPEND LIBEUICC_DRIVERS_REQUIRES "mbim-glib")
endif()
endif()
if(LPAC_WITH_HTTP_CURL)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DLPAC_WITH_HTTP_CURL")
target_sources(euicc-drivers PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/http/curl.c)

367
driver/apdu/mbim.c Normal file
View file

@ -0,0 +1,367 @@
// 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, &current_slot_count, &current_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,
};

8
driver/apdu/mbim.h Normal file
View file

@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2024, Frans Klaver <frans.klaver@vislink.com>
*/
#pragma once
#include <driver.private.h>
extern const struct euicc_driver driver_apdu_mbim;

111
driver/apdu/mbim_helpers.c Normal file
View file

@ -0,0 +1,111 @@
// SPDX-License-Identifier: MIT
/*
Copyright (c) 2024, Frans Klaver <frans.klaver@vislink.com>
*/
#include "mbim_helpers.h"
#include <libmbim-glib.h>
static void
async_result_ready(GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GAsyncResult **result_out = user_data;
g_assert(*result_out == NULL);
*result_out = g_object_ref(res);
}
MbimDevice *
mbim_device_new_from_path(GFile *file,
GMainContext *context,
GError **error)
{
g_autoptr(GMainContextPusher) pusher = NULL;
g_autoptr(GAsyncResult) result = NULL;
g_autofree gchar *id = NULL;
pusher = g_main_context_pusher_new(context);
id = g_file_get_path (file);
if (id)
mbim_device_new(file,
NULL,
async_result_ready,
&result);
while (!result)
g_main_context_iteration(context, TRUE);
return mbim_device_new_finish(result, error);
}
gboolean
mbim_device_open_sync(MbimDevice *device,
MbimDeviceOpenFlags open_flags,
GMainContext *context,
GError **error)
{
g_autoptr(GMainContextPusher) pusher = NULL;
g_autoptr(GAsyncResult) result = NULL;
pusher = g_main_context_pusher_new(context);
mbim_device_open_full(device,
open_flags,
15,
NULL,
async_result_ready,
&result);
while (!result)
g_main_context_iteration(context, TRUE);
return mbim_device_open_finish(device, result, error);
}
MbimMessage *
mbim_device_command_sync(MbimDevice *device, GMainContext *context, MbimMessage *request, GError **error)
{
g_autoptr(GMainContextPusher) pusher = NULL;
g_autoptr(GAsyncResult) result = NULL;
pusher = g_main_context_pusher_new(context);
mbim_device_command(device, request, 10, NULL, async_result_ready, &result);
mbim_message_unref(request);
while (result == NULL)
g_main_context_iteration(context, TRUE);
MbimMessage *response = mbim_device_command_finish(device, result, error);
if (!response) {
return NULL;
}
if (!mbim_message_response_get_result(response, MBIM_MESSAGE_TYPE_COMMAND_DONE, error)) {
return NULL;
}
return response;
}
gboolean
mbim_device_close_sync(
MbimDevice *device,
GMainContext *context,
GError **error)
{
g_autoptr(GMainContextPusher) pusher = NULL;
g_autoptr(GAsyncResult) result = NULL;
pusher = g_main_context_pusher_new(context);
mbim_device_close(device, 20, NULL, async_result_ready, &result);
while (result == NULL)
g_main_context_iteration(context, TRUE);
return mbim_device_close_finish(device, result, error);
}

View file

@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2024, Frans Klaver <frans.klaver@vislink.com>
*/
#include <libmbim-glib.h>
MbimDevice *
mbim_device_new_from_path(
GFile *file,
GMainContext *context,
GError **error);
gboolean
mbim_device_open_sync(
MbimDevice *device,
MbimDeviceOpenFlags open_flags,
GMainContext *context,
GError **error);
gboolean
mbim_device_close_sync(
MbimDevice *device,
GMainContext *context,
GError **error);
MbimMessage *
mbim_device_command_sync(
MbimDevice *device,
GMainContext *context,
MbimMessage *request,
GError **error);

View file

@ -8,6 +8,10 @@
#include "driver/apdu/gbinder_hidl.h"
#endif
#ifdef LPAC_WITH_APDU_MBIM
#include "driver/apdu/mbim.h"
#endif
#ifdef LPAC_WITH_APDU_QMI
#include "driver/apdu/qmi.h"
#endif
@ -32,6 +36,9 @@ static const struct euicc_driver *drivers[] = {
#ifdef LPAC_WITH_APDU_GBINDER
&driver_apdu_gbinder_hidl,
#endif
#ifdef LPAC_WITH_APDU_MBIM
&driver_apdu_mbim,
#endif
#ifdef LPAC_WITH_APDU_QMI
&driver_apdu_qmi,
#endif