Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

driver: apdu: add QMI backend #131

Merged
merged 8 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/ENVVARS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
- `at`: use AT commands interface used by LTE module
- `pcsc`: use PC/SC Smart Card API
- `stdio`: use standard input/output
- `qmi`: use QMI
- `qmi_qrtr`: use QMI over QRTR
- 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.
- `curl`: use libcurl
- `stdio`: use standard input/ouput
* `AT_DEVICE`: specify which serial port device will be used by AT APDU backend.
* `UIM_SLOT`: specify which UIM slot will be used by QMI QRTR APDU backend. (default: 1)
* `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)
* `DRIVER_IFID`: specify which PC/SC interface will be used by PC/SC APDU backend.

## Debug
Expand Down
20 changes: 19 additions & 1 deletion driver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ cmake_dependent_option(LPAC_DYNAMIC_DRIVERS "Build lpac/libeuicc driver backends
option(LPAC_WITH_APDU_PCSC "Build APDU PCSC Backend (requires PCSC libraries)" ON)
option(LPAC_WITH_APDU_AT "Build APDU AT Backend" ON)
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_HTTP_CURL "Build HTTP Curl interface" ON)
Expand Down Expand Up @@ -59,9 +60,26 @@ if(LPAC_WITH_APDU_GBINDER)
endif()
endif()

if(LPAC_WITH_APDU_QMI)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DLPAC_WITH_APDU_QMI")
target_sources(euicc-drivers PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/apdu/qmi.c
${CMAKE_CURRENT_SOURCE_DIR}/apdu/qmi_helpers.c
${CMAKE_CURRENT_SOURCE_DIR}/apdu/qmi_common.c)
find_package(PkgConfig REQUIRED)
pkg_check_modules(QMI_GLIB REQUIRED IMPORTED_TARGET qmi-glib)
target_link_libraries(euicc-drivers PkgConfig::QMI_GLIB)
if(LPAC_DYNAMIC_DRIVERS)
list(APPEND LIBEUICC_DRIVERS_REQUIRES "qmi-glib")
endif()
endif()

if(LPAC_WITH_APDU_QMI_QRTR)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DLPAC_WITH_APDU_QMI_QRTR")
target_sources(euicc-drivers PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/apdu/qmi_qrtr.c ${CMAKE_CURRENT_SOURCE_DIR}/apdu/qmi_qrtr_helpers.c)
target_sources(euicc-drivers PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/apdu/qmi_qrtr.c
${CMAKE_CURRENT_SOURCE_DIR}/apdu/qmi_helpers.c
${CMAKE_CURRENT_SOURCE_DIR}/apdu/qmi_common.c)
find_package(PkgConfig REQUIRED)
pkg_check_modules(QRTR_GLIB REQUIRED IMPORTED_TARGET qrtr-glib)
pkg_check_modules(QMI_GLIB REQUIRED IMPORTED_TARGET qmi-glib)
Expand Down
4 changes: 2 additions & 2 deletions driver/apdu/at.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ static int libapduinterface_main(int argc, char **argv)
return 0;
}

static void libapduinterface_fini(void)
static void libapduinterface_fini(struct euicc_apdu_interface *ifstruct)
{
}

Expand All @@ -227,5 +227,5 @@ const struct euicc_driver driver_apdu_at = {
.name = "at",
.init = (int (*)(void *))libapduinterface_init,
.main = libapduinterface_main,
.fini = libapduinterface_fini,
.fini = (void (*)(void *))libapduinterface_fini,
};
2 changes: 1 addition & 1 deletion driver/apdu/gbinder_hidl.c
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ static int libapduinterface_main(int argc, char **argv)
return 0;
}

static void libapduinterface_fini(void)
static void libapduinterface_fini(struct euicc_apdu_interface *ifstruct)
{
}

Expand Down
4 changes: 2 additions & 2 deletions driver/apdu/pcsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ static int libapduinterface_main(int argc, char **argv)
return 0;
}

static void libapduinterface_fini(void)
static void libapduinterface_fini(struct euicc_apdu_interface *ifstruct)
{
}

Expand All @@ -462,5 +462,5 @@ const struct euicc_driver driver_apdu_pcsc = {
.name = "pcsc",
.init = (int (*)(void *))libapduinterface_init,
.main = libapduinterface_main,
.fini = libapduinterface_fini,
.fini = (void (*)(void *))libapduinterface_fini,
};
101 changes: 101 additions & 0 deletions driver/apdu/qmi.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2024, Robert Marko <[email protected]>
*/
#include "qmi.h"

#include <stdio.h>
#include "qmi_common.h"

static int apdu_interface_connect(struct euicc_ctx *ctx)
{
struct qmi_data *qmi_priv = ctx->apdu.interface->userdata;
g_autoptr(GError) error = NULL;
QmiDevice *device = NULL;
QmiClient *client = NULL;
const char *device_path;
GFile *file;

if (!(device_path = getenv("QMI_DEVICE"))) {
fprintf(stderr, "No QMI device path specified!\n");
return -1;
}
file = g_file_new_for_path(device_path);

qmi_priv->context = g_main_context_new();

device = qmi_device_new_from_path(file, qmi_priv->context, &error);
if (!device) {
fprintf(stderr, "error: create QMI device from path failed: %s\n", error->message);
return -1;
}

qmi_device_open_sync(device, qmi_priv->context, &error);
if (error) {
fprintf(stderr, "error: open QMI device failed: %s\n", error->message);
return -1;
}

client = qmi_device_allocate_client_sync(device, qmi_priv->context, &error);
if (!client) {
fprintf(stderr, "error: allocate QMI client failed: %s\n", error->message);
return -1;
}

qmi_priv->uimClient = QMI_CLIENT_UIM(client);

return 0;
}

static int libapduinterface_init(struct euicc_apdu_interface *ifstruct)
{
struct qmi_data *qmi_priv;

qmi_priv = malloc(sizeof(struct qmi_data));
if(!qmi_priv) {
fprintf(stderr, "Failed allocating memory\n");
return -1;
}

memset(ifstruct, 0, sizeof(struct euicc_apdu_interface));
ifstruct->connect = apdu_interface_connect;
ifstruct->disconnect = qmi_apdu_interface_disconnect;
ifstruct->logic_channel_open = qmi_apdu_interface_logic_channel_open;
ifstruct->logic_channel_close = qmi_apdu_interface_logic_channel_close;
ifstruct->transmit = qmi_apdu_interface_transmit;

/*
* Allow the user to select the SIM card slot via environment variable.
* Use the primary SIM slot if not set.
*/
if (getenv("UIM_SLOT"))
qmi_priv->uimSlot = atoi(getenv("UIM_SLOT"));
else
qmi_priv->uimSlot = 1;

ifstruct->userdata = qmi_priv;

return 0;
}

static int libapduinterface_main(int argc, char **argv)
{
return 0;
}

static void libapduinterface_fini(struct euicc_apdu_interface *ifstruct)
{
struct qmi_data *qmi_priv = ifstruct->userdata;

qmi_cleanup(qmi_priv);

free(qmi_priv);
}

const struct euicc_driver driver_apdu_qmi = {
.type = DRIVER_APDU,
.name = "qmi",
.init = (int (*)(void *))libapduinterface_init,
.main = libapduinterface_main,
.fini = (void (*)(void *))libapduinterface_fini,
};
8 changes: 8 additions & 0 deletions driver/apdu/qmi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2024, Robert Marko <[email protected]>
*/
#pragma once
#include <driver.private.h>

extern const struct euicc_driver driver_apdu_qmi;
163 changes: 163 additions & 0 deletions driver/apdu/qmi_common.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2024, Luca Weiss <[email protected]>
*/

#include <stdio.h>

#include "qmi_common.h"

int qmi_apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, uint32_t tx_len)
{
struct qmi_data *qmi_priv = ctx->apdu.interface->userdata;
g_autoptr(GError) error = NULL;
g_autoptr(GArray) apdu_data = NULL;

/* Convert tx into request GArray */
apdu_data = g_array_new(FALSE, FALSE, sizeof(guint8));
for (uint32_t i = 0; i < tx_len; i++)
g_array_append_val(apdu_data, tx[i]);

QmiMessageUimSendApduInput *input;
input = qmi_message_uim_send_apdu_input_new();
qmi_message_uim_send_apdu_input_set_slot(input, qmi_priv->uimSlot, NULL);
qmi_message_uim_send_apdu_input_set_channel_id(input, qmi_priv->lastChannelId, NULL);
qmi_message_uim_send_apdu_input_set_apdu(input, apdu_data, NULL);

QmiMessageUimSendApduOutput *output;
output = qmi_client_uim_send_apdu_sync(qmi_priv->uimClient, input, qmi_priv->context, &error);

qmi_message_uim_send_apdu_input_unref(input);

if (!qmi_message_uim_send_apdu_output_get_result(output, &error))
{
fprintf(stderr, "error: send apdu operation failed: %s\n", error->message);
return -1;
}

GArray *apdu_res = NULL;
if (!qmi_message_uim_send_apdu_output_get_apdu_response(output, &apdu_res, &error))
{
fprintf(stderr, "error: get apdu response operation failed: %s\n", error->message);
return -1;
}

/* Convert response GArray into rx */
*rx_len = apdu_res->len;
*rx = malloc(*rx_len);
if (!*rx)
return -1;
for (guint i = 0; i < apdu_res->len; i++)
(*rx)[i] = apdu_res->data[i];

qmi_message_uim_send_apdu_output_unref(output);

return 0;
}

int qmi_apdu_interface_logic_channel_open(struct euicc_ctx *ctx, const uint8_t *aid, uint8_t aid_len)
{
struct qmi_data *qmi_priv = ctx->apdu.interface->userdata;
g_autoptr(GError) error = NULL;
guint8 channel_id;

GArray *aid_data = g_array_new(FALSE, FALSE, sizeof(guint8));
for (int i = 0; i < aid_len; i++)
g_array_append_val(aid_data, aid[i]);

QmiMessageUimOpenLogicalChannelInput *input;
input = qmi_message_uim_open_logical_channel_input_new();
qmi_message_uim_open_logical_channel_input_set_slot(input, qmi_priv->uimSlot, NULL);
qmi_message_uim_open_logical_channel_input_set_aid(input, aid_data, NULL);

QmiMessageUimOpenLogicalChannelOutput *output;
output = qmi_client_uim_open_logical_channel_sync(qmi_priv->uimClient, input, qmi_priv->context, &error);

qmi_message_uim_open_logical_channel_input_unref(input);
g_array_unref(aid_data);

if (!output)
{
fprintf(stderr, "error: send Open Logical Channel command failed: %s\n", error->message);
return -1;
}

if (!qmi_message_uim_open_logical_channel_output_get_result(output, &error))
{
fprintf(stderr, "error: open logical channel operation failed: %s\n", error->message);
return -1;
}

if (!qmi_message_uim_open_logical_channel_output_get_channel_id(output, &channel_id, &error))
{
fprintf(stderr, "error: get channel id operation failed: %s\n", error->message);
return -1;
}
qmi_priv->lastChannelId = channel_id;

g_debug("Opened logical channel with id %d", channel_id);

qmi_message_uim_open_logical_channel_output_unref(output);

return channel_id;
}

void qmi_apdu_interface_logic_channel_close(struct euicc_ctx *ctx, uint8_t channel)
{
struct qmi_data *qmi_priv = ctx->apdu.interface->userdata;
g_autoptr(GError) error = NULL;

QmiMessageUimLogicalChannelInput *input;
input = qmi_message_uim_logical_channel_input_new();
qmi_message_uim_logical_channel_input_set_slot(input, qmi_priv->uimSlot, NULL);
qmi_message_uim_logical_channel_input_set_channel_id(input, channel, NULL);

QmiMessageUimLogicalChannelOutput *output;
output = qmi_client_uim_logical_channel_sync(qmi_priv->uimClient, input, qmi_priv->context, &error);

qmi_message_uim_logical_channel_input_unref(input);

if (error)
{
fprintf(stderr, "error: send Close Logical Channel command failed: %s\n", error->message);
return;
}

if (!qmi_message_uim_logical_channel_output_get_result(output, &error))
{
fprintf(stderr, "error: logical channel operation failed: %s\n", error->message);
return;
}

/* Mark channel as having been cleaned up */
if (channel == qmi_priv->lastChannelId)
qmi_priv->lastChannelId = -1;

g_debug("Closed logical channel with id %d", channel);

qmi_message_uim_logical_channel_output_unref(output);
}

void qmi_apdu_interface_disconnect(struct euicc_ctx *ctx)
{
struct qmi_data *qmi_priv = ctx->apdu.interface->userdata;
g_autoptr(GError) error = NULL;
QmiClient *client = QMI_CLIENT(qmi_priv->uimClient);
QmiDevice *device = QMI_DEVICE(qmi_client_get_device(client));

qmi_device_release_client_sync(device, client, qmi_priv->context, &error);
qmi_priv->uimClient = NULL;

g_main_context_unref(qmi_priv->context);
qmi_priv->context = NULL;
}

void qmi_cleanup(struct qmi_data *qmi_priv)
{
if (qmi_priv->lastChannelId != -1)
{
fprintf(stderr, "Cleaning up leaked APDU channel %d\n", qmi_priv->lastChannelId);
qmi_apdu_interface_logic_channel_close(NULL, qmi_priv->lastChannelId);
qmi_priv->lastChannelId = -1;
}
}
21 changes: 21 additions & 0 deletions driver/apdu/qmi_common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2024, Luca Weiss <[email protected]>
*/

#include <euicc/interface.h>
#include <euicc/euicc.h>
#include "qmi_helpers.h"

struct qmi_data {
int lastChannelId;
int uimSlot;
GMainContext *context;
QmiClientUim *uimClient;
};

int qmi_apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, uint32_t tx_len);
int qmi_apdu_interface_logic_channel_open(struct euicc_ctx *ctx, const uint8_t *aid, uint8_t aid_len);
void qmi_apdu_interface_logic_channel_close(struct euicc_ctx *ctx, uint8_t channel);
void qmi_apdu_interface_disconnect(struct euicc_ctx *ctx);
void qmi_cleanup(struct qmi_data *qmi_priv);
Loading