mirror of
https://github.com/estkme-group/lpac
synced 2025-08-18 10:03:02 +02:00
252 lines
9.1 KiB
C
252 lines
9.1 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright (c) 2024, Robert Marko <robert.marko@sartura.hr>
|
|
*/
|
|
#include "qmi.h"
|
|
#include "qmi_common.h"
|
|
|
|
#include <lpac/utils.h>
|
|
|
|
#include <libqmi-glib.h>
|
|
#include <stdio.h>
|
|
|
|
static gboolean is_sim_available(struct qmi_data *qmi_priv) {
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(QmiMessageUimGetCardStatusOutput) card_status_output = NULL;
|
|
GArray *cards = NULL;
|
|
guint i, j;
|
|
|
|
// Get card status
|
|
card_status_output = qmi_client_uim_get_card_status_sync(qmi_priv->uimClient, qmi_priv->context, &error);
|
|
if (!card_status_output) {
|
|
fprintf(stderr, "error: get card status failed: %s\n", error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
// Check if the operation was successful
|
|
if (!qmi_message_uim_get_card_status_output_get_result(card_status_output, &error)) {
|
|
fprintf(stderr, "error: get card status operation failed: %s\n", error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
// Get card status details
|
|
if (!qmi_message_uim_get_card_status_output_get_card_status(card_status_output,
|
|
NULL, // index_gw_primary
|
|
NULL, // index_1x_primary
|
|
NULL, // index_gw_secondary
|
|
NULL, // index_1x_secondary
|
|
&cards, &error)) {
|
|
fprintf(stderr, "error: get card status details failed: %s\n", error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
// Check if any card is present and has a USIM application that is ready
|
|
for (i = 0; i < cards->len; i++) {
|
|
QmiMessageUimGetCardStatusOutputCardStatusCardsElement *card_element;
|
|
card_element = &g_array_index(cards, QmiMessageUimGetCardStatusOutputCardStatusCardsElement, i);
|
|
|
|
// Check applications on this card
|
|
for (j = 0; j < card_element->applications->len; j++) {
|
|
QmiMessageUimGetCardStatusOutputCardStatusCardsElementApplicationsElementV2 *app_element;
|
|
app_element =
|
|
&g_array_index(card_element->applications,
|
|
QmiMessageUimGetCardStatusOutputCardStatusCardsElementApplicationsElementV2, j);
|
|
|
|
// Check if this is a USIM application and if it's ready
|
|
if (app_element->type == QMI_UIM_CARD_APPLICATION_TYPE_USIM
|
|
&& app_element->state == QMI_UIM_CARD_APPLICATION_STATE_READY) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean select_sim_slot(struct qmi_data *qmi_priv) {
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(QmiMessageUimGetSlotStatusOutput) slot_status_output = NULL;
|
|
g_autoptr(QmiMessageUimSwitchSlotInput) switch_slot_input = NULL;
|
|
g_autoptr(QmiMessageUimSwitchSlotOutput) switch_slot_output = NULL;
|
|
g_autoptr(QmiMessageUimGetCardStatusOutput) card_status_output = NULL;
|
|
GArray *physical_slot_status = NULL;
|
|
guint8 active_slot = 0;
|
|
guint8 target_slot;
|
|
guint i;
|
|
int retries;
|
|
|
|
// Get the target slot (1-based indexing)
|
|
target_slot = qmi_priv->uimSlot;
|
|
|
|
// Get current slot status
|
|
slot_status_output = qmi_client_uim_get_slot_status_sync(qmi_priv->uimClient, qmi_priv->context, &error);
|
|
if (!slot_status_output) {
|
|
fprintf(stderr, "error: get slot status failed: %s\n", error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
// Check if the operation was successful
|
|
if (!qmi_message_uim_get_slot_status_output_get_result(slot_status_output, &error)) {
|
|
// Some older devices do not support the GetSlotStatusRequest QMI command
|
|
if (error->code == QMI_PROTOCOL_ERROR_NOT_SUPPORTED) {
|
|
return TRUE;
|
|
}
|
|
fprintf(stderr, "error: get slot status operation failed: %s\n", error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
// Get physical slot status
|
|
if (!qmi_message_uim_get_slot_status_output_get_physical_slot_status(slot_status_output, &physical_slot_status,
|
|
&error)) {
|
|
fprintf(stderr, "error: get physical slot status failed: %s\n", error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
// Find the active slot
|
|
active_slot = 0;
|
|
for (i = 0; i < physical_slot_status->len; i++) {
|
|
QmiPhysicalSlotStatusSlot *element;
|
|
element = &g_array_index(physical_slot_status, QmiPhysicalSlotStatusSlot, i);
|
|
if (element->physical_slot_status == QMI_UIM_SLOT_STATE_ACTIVE) {
|
|
active_slot = i + 1; // 1-based indexing
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the active slot is not the target slot, switch to the target slot
|
|
if (active_slot != target_slot) {
|
|
// Create switch slot input
|
|
switch_slot_input = qmi_message_uim_switch_slot_input_new();
|
|
|
|
if (!qmi_message_uim_switch_slot_input_set_logical_slot(switch_slot_input, 1, &error)) {
|
|
fprintf(stderr, "error: set logical slot failed: %s\n", error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
// Set the physical slot (use the logical slot number as physical slot)
|
|
if (!qmi_message_uim_switch_slot_input_set_physical_slot(switch_slot_input, target_slot, &error)) {
|
|
fprintf(stderr, "error: set physical slot failed: %s\n", error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
// Switch to the target slot
|
|
switch_slot_output =
|
|
qmi_client_uim_switch_slot_sync(qmi_priv->uimClient, switch_slot_input, qmi_priv->context, &error);
|
|
if (!switch_slot_output) {
|
|
fprintf(stderr, "error: switch slot failed: %s\n", error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
// Check if the operation was successful
|
|
if (!qmi_message_uim_switch_slot_output_get_result(switch_slot_output, &error)) {
|
|
fprintf(stderr, "error: switch slot operation failed: %s\n", error->message);
|
|
return FALSE;
|
|
}
|
|
|
|
// Wait for SIM to be available
|
|
for (retries = 0; retries < 20; retries++) {
|
|
if (is_sim_available(qmi_priv)) {
|
|
return TRUE;
|
|
}
|
|
// Wait a bit and retry
|
|
g_usleep(500000); // 0.5 seconds
|
|
}
|
|
|
|
fprintf(stderr, "error: SIM not available after switching slot\n");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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 = getenv(ENV_DEVICE);
|
|
GFile *file;
|
|
|
|
if (device_path == NULL) {
|
|
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_DEVICE_OPEN_FLAGS_PROXY, 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);
|
|
if (select_sim_slot(qmi_priv) < 0) {
|
|
fprintf(stderr, "error: select SIM slot failed\n");
|
|
return -1;
|
|
}
|
|
|
|
// In QMI mode, we need to keep the SIM slot set to 1, because once the
|
|
// configured slot becomes active, it will be assigned as slot 1.
|
|
qmi_priv->uimSlot = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int libapduinterface_init(struct euicc_apdu_interface *ifstruct) {
|
|
set_deprecated_env_name(ENV_UIM_SLOT, "UIM_SLOT");
|
|
set_deprecated_env_name(ENV_DEVICE, "QMI_DEVICE");
|
|
|
|
struct qmi_data *qmi_priv;
|
|
|
|
qmi_priv = calloc(1, 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.
|
|
*/
|
|
qmi_priv->uimSlot = getenv_or_default(ENV_UIM_SLOT, (int)1);
|
|
|
|
ifstruct->userdata = qmi_priv;
|
|
|
|
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 = NULL,
|
|
.fini = (void (*)(void *))libapduinterface_fini,
|
|
};
|