// SPDX-License-Identifier: MIT /* * Copyright (c) 2024, Robert Marko */ #include "qmi.h" #include "qmi_common.h" #include #include #include 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, };