diff options
author | Aleksander Morgado <aleksander@aleksander.es> | 2015-12-29 01:25:59 +0100 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2015-12-29 17:48:19 +0100 |
commit | 39c4b6b8edd4b824c342ca8b7288056773593d1a (patch) | |
tree | e04339ebfcb47dcfdfdeb4435c7dd5fd19af70fc | |
parent | 07384cdf0b66701904bc2b750565ca06573616a3 (diff) | |
download | ModemManager-aleksander/mc7455.tar.gz |
broadband-modem-qmi: use "UIM Get Card Status" if "DMS UIM" command unavailablealeksander/mc7455
The newer modules like the MC7455 which support multiple SIM cards won't
implement the legacy "DMS UIM" commands.
-rw-r--r-- | src/mm-broadband-modem-qmi.c | 452 |
1 files changed, 390 insertions, 62 deletions
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c index f738ba1cb..999ead1c1 100644 --- a/src/mm-broadband-modem-qmi.c +++ b/src/mm-broadband-modem-qmi.c @@ -90,6 +90,9 @@ struct _MMBroadbandModemQmiPrivate { guint signal_info_indication_id; #endif /* WITH_NEWEST_QMI_COMMANDS */ + /* New devices may not support the legacy DMS UIM Get PIN status */ + gboolean dms_uim_get_pin_status_supported; + /* 3GPP/CDMA registration helpers */ gchar *current_operator_id; gchar *current_operator_description; @@ -1426,6 +1429,30 @@ modem_load_own_numbers (MMIfaceModem *self, /*****************************************************************************/ /* Check if unlock required (Modem interface) */ +typedef enum { + LOAD_UNLOCK_REQUIRED_STEP_FIRST, + LOAD_UNLOCK_REQUIRED_STEP_CDMA, + LOAD_UNLOCK_REQUIRED_STEP_DMS, + LOAD_UNLOCK_REQUIRED_STEP_UIM, +} LoadUnlockRequiredStep; + +typedef struct { + MMBroadbandModemQmi *self; + GSimpleAsyncResult *result; + LoadUnlockRequiredStep step; + QmiClient *dms; + QmiClient *uim; +} LoadUnlockRequiredContext; + +static void +load_unlock_required_context_complete_and_free (LoadUnlockRequiredContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->self); + g_slice_free (LoadUnlockRequiredContext, ctx); +} + static MMModemLock modem_load_unlock_required_finish (MMIfaceModem *self, GAsyncResult *res, @@ -1438,19 +1465,271 @@ modem_load_unlock_required_finish (MMIfaceModem *self, G_SIMPLE_ASYNC_RESULT (res))); } +static void load_unlock_required_context_step (LoadUnlockRequiredContext *ctx); + +static void +uim_get_card_status_ready (QmiClientUim *client, + GAsyncResult *res, + LoadUnlockRequiredContext *ctx) +{ + QmiMessageUimGetCardStatusOutput *output; + GError *error = NULL; + GArray *cards; + QmiMessageUimGetCardStatusOutputCardStatusCardsElement *card; + QmiMessageUimGetCardStatusOutputCardStatusCardsElementApplicationsElement *app; + MMModemLock lock = MM_MODEM_LOCK_UNKNOWN; + guint i; + gint card_i = -1; + gint application_j = -1; + guint n_absent = 0; + guint n_error = 0; + guint n_invalid = 0; + + /* This command supports MULTIPLE cards with MULTIPLE applications each. For our + * purposes, we're going to consider as the SIM to use the first card present + * with a SIM/USIM application. */ + + output = qmi_client_uim_get_card_status_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + load_unlock_required_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_uim_get_card_status_output_get_result (output, &error)) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + qmi_message_uim_get_card_status_output_unref (output); + load_unlock_required_context_complete_and_free (ctx); + return; + } + + qmi_message_uim_get_card_status_output_get_card_status ( + output, + NULL, /* index_gw_primary */ + NULL, /* index_1x_primary */ + NULL, /* index_gw_secondary */ + NULL, /* index_1x_secondary */ + &cards, + NULL); + + if (cards->len == 0) { + g_simple_async_result_set_error (ctx->result, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED, + "No cards reported"); + qmi_message_uim_get_card_status_output_unref (output); + load_unlock_required_context_complete_and_free (ctx); + return; + } + + if (cards->len > 1) + g_debug ("Multiple cards reported: %u", cards->len); + + /* All KNOWN applications in all cards will need to be in READY state for us + * to consider UNLOCKED */ + for (i = 0; i < cards->len; i++) { + card = &g_array_index (cards, QmiMessageUimGetCardStatusOutputCardStatusCardsElement, i); + + switch (card->card_state) { + case QMI_UIM_CARD_STATE_PRESENT: { + guint j; + gboolean sim_usim_found = FALSE; + + if (card->applications->len == 0) { + g_debug ("No applications reported in card [%u]", i); + n_invalid++; + break; + } + + if (card->applications->len > 1) + g_debug ("Multiple applications reported in card [%u]: %u", i, card->applications->len); + + for (j = 0; j < card->applications->len; j++) { + app = &g_array_index (card->applications, QmiMessageUimGetCardStatusOutputCardStatusCardsElementApplicationsElement, j); + + if (app->type == QMI_UIM_CARD_APPLICATION_TYPE_UNKNOWN) { + g_debug ("Unknown application [%u] found in card [%u]: %s. Ignored.", + j, i, qmi_uim_card_application_state_get_string (app->state)); + continue; + } + + g_debug ("Application '%s' [%u] in card [%u]: %s", + qmi_uim_card_application_type_get_string (app->type), j, i, qmi_uim_card_application_state_get_string (app->state)); + + if (app->type == QMI_UIM_CARD_APPLICATION_TYPE_SIM || app->type == QMI_UIM_CARD_APPLICATION_TYPE_USIM) { + /* We found the card/app pair to use! Only keep the first found, + * but still, keep on looping to log about the remaining ones */ + if (card_i < 0 && application_j < 0) { + card_i = i; + application_j = j; + } + + sim_usim_found = TRUE; + } + } + + if (!sim_usim_found) { + g_debug ("No SIM/USIM application found in card [%u]", i); + n_invalid++; + } + + break; + } + + case QMI_UIM_CARD_STATE_ABSENT: + g_debug ("Card '%u' is absent", i); + n_absent++; + break; + + case QMI_UIM_CARD_STATE_ERROR: + default: + n_error++; + if (qmi_uim_card_error_get_string (card->error_code) != NULL) + g_warning ("Card '%u' is unusable: %s", i, qmi_uim_card_error_get_string (card->error_code)); + else + g_warning ("Card '%u' is unusable: unknown error", i); + break; + } + + /* go on to next card */ + } + + /* If we found no card/app to use, we need to report an error */ + if (card_i < 0 || application_j < 0) { + /* If not a single card found, report SIM not inserted */ + if (n_absent > 0 && !n_error && !n_invalid) + g_simple_async_result_set_error (ctx->result, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED, + "No card found"); + else if (n_error > 0) + g_simple_async_result_set_error (ctx->result, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG, + "Card error"); + else + g_simple_async_result_set_error (ctx->result, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE, + "Card failure: %u absent, %u errors, %u invalid", + n_absent, n_error, n_invalid); + qmi_message_uim_get_card_status_output_unref (output); + load_unlock_required_context_complete_and_free (ctx); + return; + } + + /* Get card/app to use */ + card = &g_array_index (cards, QmiMessageUimGetCardStatusOutputCardStatusCardsElement, card_i); + app = &g_array_index (card->applications, QmiMessageUimGetCardStatusOutputCardStatusCardsElementApplicationsElement, application_j); + + /* If card not ready yet, return RETRY error */ + if (app->state != QMI_UIM_CARD_APPLICATION_STATE_READY) { + g_debug ("Neither SIM nor USIM are ready"); + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_RETRY, + "SIM not ready yet (retry)"); + qmi_message_uim_get_card_status_output_unref (output); + load_unlock_required_context_complete_and_free (ctx); + return; + } + + /* Card is ready, what's the lock status? */ + + /* PIN1 */ + switch (app->pin1_state) { + case QMI_UIM_PIN_STATE_PERMANENTLY_BLOCKED: + g_simple_async_result_set_error (ctx->result, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG, + "SIM PIN/PUK permanently blocked"); + qmi_message_uim_get_card_status_output_unref (output); + load_unlock_required_context_complete_and_free (ctx); + return; + + case QMI_UIM_PIN_STATE_ENABLED_NOT_VERIFIED: + lock = MM_MODEM_LOCK_SIM_PIN; + break; + + case QMI_UIM_PIN_STATE_BLOCKED: + lock = MM_MODEM_LOCK_SIM_PUK; + break; + + case QMI_UIM_PIN_STATE_DISABLED: + case QMI_UIM_PIN_STATE_ENABLED_VERIFIED: + lock = MM_MODEM_LOCK_NONE; + break; + + default: + g_simple_async_result_set_error (ctx->result, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG, + "Unknown SIM PIN/PUK status"); + qmi_message_uim_get_card_status_output_unref (output); + load_unlock_required_context_complete_and_free (ctx); + return; + } + + /* PIN2 */ + if (lock == MM_MODEM_LOCK_NONE) { + switch (app->pin2_state) { + case QMI_UIM_PIN_STATE_ENABLED_NOT_VERIFIED: + lock = MM_MODEM_LOCK_SIM_PIN2; + break; + + case QMI_UIM_PIN_STATE_PERMANENTLY_BLOCKED: + g_warning ("PUK2 permanently blocked"); + case QMI_UIM_PIN_STATE_BLOCKED: + lock = MM_MODEM_LOCK_SIM_PUK2; + break; + + case QMI_UIM_PIN_STATE_DISABLED: + case QMI_UIM_PIN_STATE_ENABLED_VERIFIED: + break; + + default: + g_warning ("Unknown SIM PIN2/PUK2 status"); + break; + } + } + + g_simple_async_result_set_op_res_gpointer (ctx->result, GUINT_TO_POINTER (lock), NULL); + qmi_message_uim_get_card_status_output_unref (output); + load_unlock_required_context_complete_and_free (ctx); +} + static void dms_uim_get_pin_status_ready (QmiClientDms *client, GAsyncResult *res, - GSimpleAsyncResult *simple) + LoadUnlockRequiredContext *ctx) { QmiMessageDmsUimGetPinStatusOutput *output; GError *error = NULL; + MMModemLock lock = MM_MODEM_LOCK_UNKNOWN; + QmiDmsUimPinStatus current_status; output = qmi_client_dms_uim_get_pin_status_finish (client, res, &error); if (!output) { g_prefix_error (&error, "QMI operation failed: "); - g_simple_async_result_take_error (simple, error); - } else if (!qmi_message_dms_uim_get_pin_status_output_get_result (output, &error)) { + g_simple_async_result_take_error (ctx->result, error); + load_unlock_required_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_dms_uim_get_pin_status_output_get_result (output, &error)) { + /* We get InvalidQmiCommand on newer devices which don't like the legacy way */ + if (g_error_matches (error, + QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND)) { + g_error_free (error); + qmi_message_dms_uim_get_pin_status_output_unref (output); + /* Flag that the command is unsupported, and try with the new way */ + ctx->self->priv->dms_uim_get_pin_status_supported = FALSE; + ctx->step++; + load_unlock_required_context_step (ctx); + return; + } + /* Internal and uim-uninitialized errors are retry-able before being fatal */ if (g_error_matches (error, QMI_PROTOCOL_ERROR, @@ -1458,85 +1737,130 @@ dms_uim_get_pin_status_ready (QmiClientDms *client, g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_UIM_UNINITIALIZED)) { - g_simple_async_result_set_error (simple, + g_simple_async_result_set_error (ctx->result, MM_CORE_ERROR, MM_CORE_ERROR_RETRY, "Couldn't get PIN status (retry): %s", error->message); g_error_free (error); + qmi_message_dms_uim_get_pin_status_output_unref (output); + load_unlock_required_context_complete_and_free (ctx); + return; } - /* Other errors, just propagate them */ - else { - g_prefix_error (&error, "Couldn't get PIN status: "); - g_simple_async_result_take_error (simple, error); - } - } else { - MMModemLock lock = MM_MODEM_LOCK_UNKNOWN; - QmiDmsUimPinStatus current_status; - if (qmi_message_dms_uim_get_pin_status_output_get_pin1_status ( - output, - ¤t_status, - NULL, /* verify_retries_left */ - NULL, /* unblock_retries_left */ - NULL)) - lock = mm_modem_lock_from_qmi_uim_pin_status (current_status, TRUE); + /* Other errors, just propagate them */ + g_prefix_error (&error, "Couldn't get PIN status: "); + g_simple_async_result_take_error (ctx->result, error); + qmi_message_dms_uim_get_pin_status_output_unref (output); + load_unlock_required_context_complete_and_free (ctx); + return; + } - if (lock == MM_MODEM_LOCK_NONE && - qmi_message_dms_uim_get_pin_status_output_get_pin2_status ( - output, - ¤t_status, - NULL, /* verify_retries_left */ - NULL, /* unblock_retries_left */ - NULL)) - lock = mm_modem_lock_from_qmi_uim_pin_status (current_status, FALSE); + /* Command succeeded, process results */ - g_simple_async_result_set_op_res_gpointer (simple, GUINT_TO_POINTER (lock), NULL); - } + if (qmi_message_dms_uim_get_pin_status_output_get_pin1_status ( + output, + ¤t_status, + NULL, /* verify_retries_left */ + NULL, /* unblock_retries_left */ + NULL)) + lock = mm_modem_lock_from_qmi_uim_pin_status (current_status, TRUE); - if (output) - qmi_message_dms_uim_get_pin_status_output_unref (output); + if (lock == MM_MODEM_LOCK_NONE && + qmi_message_dms_uim_get_pin_status_output_get_pin2_status ( + output, + ¤t_status, + NULL, /* verify_retries_left */ + NULL, /* unblock_retries_left */ + NULL)) + lock = mm_modem_lock_from_qmi_uim_pin_status (current_status, FALSE); - g_simple_async_result_complete (simple); - g_object_unref (simple); + /* We're done! */ + g_simple_async_result_set_op_res_gpointer (ctx->result, GUINT_TO_POINTER (lock), NULL); + qmi_message_dms_uim_get_pin_status_output_unref (output); + load_unlock_required_context_complete_and_free (ctx); } static void -modem_load_unlock_required (MMIfaceModem *self, - GAsyncReadyCallback callback, - gpointer user_data) +load_unlock_required_context_step (LoadUnlockRequiredContext *ctx) { - GSimpleAsyncResult *result; - QmiClient *client = NULL; + GError *error = NULL; + QmiClient *client; - if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), - QMI_SERVICE_DMS, &client, - callback, user_data)) - return; + switch (ctx->step) { + case LOAD_UNLOCK_REQUIRED_STEP_FIRST: + ctx->step++; + /* Go on to next step */ + + case LOAD_UNLOCK_REQUIRED_STEP_CDMA: + /* CDMA-only modems don't need this */ + if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (ctx->self))) { + mm_dbg ("Skipping unlock check in CDMA-only modem..."); + g_simple_async_result_set_op_res_gpointer (ctx->result, GUINT_TO_POINTER (MM_MODEM_LOCK_NONE), NULL); + load_unlock_required_context_complete_and_free (ctx); + return; + } + ctx->step++; + /* Go on to next step */ + + case LOAD_UNLOCK_REQUIRED_STEP_DMS: + if (ctx->self->priv->dms_uim_get_pin_status_supported) { + /* Failure to get DMS client is hard really */ + client = peek_qmi_client (ctx->self, QMI_SERVICE_DMS, &error); + if (!client) { + g_simple_async_result_take_error (ctx->result, error); + load_unlock_required_context_complete_and_free (ctx); + return; + } - result = g_simple_async_result_new (G_OBJECT (self), - callback, - user_data, - modem_load_unlock_required); + mm_dbg ("loading unlock required (DMS)..."); + qmi_client_dms_uim_get_pin_status (QMI_CLIENT_DMS (client), + NULL, + 5, + NULL, + (GAsyncReadyCallback) dms_uim_get_pin_status_ready, + ctx); + return; + } + ctx->step++; + /* Go on to next step */ - /* CDMA-only modems don't need this */ - if (mm_iface_modem_is_cdma_only (self)) { - mm_dbg ("Skipping unlock check in CDMA-only modem..."); - g_simple_async_result_set_op_res_gpointer (result, - GUINT_TO_POINTER (MM_MODEM_LOCK_NONE), - NULL); - g_simple_async_result_complete_in_idle (result); - g_object_unref (result); + case LOAD_UNLOCK_REQUIRED_STEP_UIM: + /* Failure to get UIM client at this point is hard as well */ + client = peek_qmi_client (ctx->self, QMI_SERVICE_UIM, &error); + if (!client) { + g_simple_async_result_take_error (ctx->result, error); + load_unlock_required_context_complete_and_free (ctx); + return; + } + + mm_dbg ("loading unlock required (UIM)..."); + qmi_client_uim_get_card_status (QMI_CLIENT_UIM (client), + NULL, + 5, + NULL, + (GAsyncReadyCallback) uim_get_card_status_ready, + ctx); return; } +} - mm_dbg ("loading unlock required..."); - qmi_client_dms_uim_get_pin_status (QMI_CLIENT_DMS (client), - NULL, - 5, - NULL, - (GAsyncReadyCallback)dms_uim_get_pin_status_ready, - result); +static void +modem_load_unlock_required (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadUnlockRequiredContext *ctx; + + ctx = g_slice_new0 (LoadUnlockRequiredContext); + ctx->self = g_object_ref (self); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_unlock_required); + ctx->step = LOAD_UNLOCK_REQUIRED_STEP_FIRST; + + load_unlock_required_context_step (ctx); } /*****************************************************************************/ @@ -10841,7 +11165,8 @@ initialization_started (MMBroadbandModem *self, ctx->services[2] = QMI_SERVICE_WMS; ctx->services[3] = QMI_SERVICE_PDS; ctx->services[4] = QMI_SERVICE_OMA; - ctx->services[5] = QMI_SERVICE_UNKNOWN; + ctx->services[5] = QMI_SERVICE_UIM; + ctx->services[6] = QMI_SERVICE_UNKNOWN; /* Now open our QMI port */ mm_port_qmi_open (ctx->qmi, @@ -10876,6 +11201,9 @@ mm_broadband_modem_qmi_init (MMBroadbandModemQmi *self) self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_BROADBAND_MODEM_QMI, MMBroadbandModemQmiPrivate); + + /* Initially we always try to use the legacy command */ + self->priv->dms_uim_get_pin_status_supported = TRUE; } static void |