diff options
-rw-r--r-- | src/mm-shared-qmi.c | 213 |
1 files changed, 186 insertions, 27 deletions
diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c index c36dbfe4..cd969c33 100644 --- a/src/mm-shared-qmi.c +++ b/src/mm-shared-qmi.c @@ -3188,11 +3188,45 @@ typedef struct { guint current_slot_number; guint active_slot_number; guint active_logical_id; + GArray *initial_slot_status; + GArray *final_slot_status; + gulong final_slot_status_timeout_id; + gulong load_sim_slots_indication_id; } LoadSimSlotsContext; static void +clear_load_sim_slot_callbacks (GTask *task) +{ + MMIfaceModem *self; + LoadSimSlotsContext *ctx; + Private *priv; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + priv = get_private (MM_SHARED_QMI (self)); + + if (ctx->client_uim && ctx->load_sim_slots_indication_id) { + g_signal_handler_disconnect(ctx->client_uim, ctx->load_sim_slots_indication_id); + ctx->load_sim_slots_indication_id = 0; + } + if (ctx->final_slot_status_timeout_id) { + g_source_remove (ctx->final_slot_status_timeout_id); + ctx->final_slot_status_timeout_id = 0; + } + /* Restore hot swap detection because we aren't loading slots anymore */ + if (priv->uim_slot_status_indication_id) + g_signal_handler_unblock (ctx->client_uim, priv->uim_slot_status_indication_id); +} + +static void load_sim_slots_context_free (LoadSimSlotsContext *ctx) { + if (ctx->initial_slot_status) + g_array_unref (ctx->initial_slot_status); + if (ctx->final_slot_status) + g_array_unref (ctx->final_slot_status); + g_assert (ctx->load_sim_slots_indication_id == 0); + g_assert (ctx->final_slot_status_timeout_id == 0); g_clear_object (&ctx->current_sim); g_list_free_full (ctx->sorted_sims, (GDestroyNotify)g_object_unref); g_clear_pointer (&ctx->sim_slots, g_ptr_array_unref); @@ -3220,6 +3254,7 @@ mm_shared_qmi_load_sim_slots_finish (MMIfaceModem *self, return FALSE; ctx = g_task_get_task_data (G_TASK (res)); + if (sim_slots) *sim_slots = g_steal_pointer (&ctx->sim_slots); if (primary_sim_slot) @@ -3227,6 +3262,93 @@ mm_shared_qmi_load_sim_slots_finish (MMIfaceModem *self, return TRUE; } +/* Compares arrays of QmiPhysicalSlotStatusSlot */ +static gboolean +compare_slot_status (GArray *slot_status1, + GArray *slot_status2) +{ + guint i; + guint j; + + if (!slot_status1 && !slot_status2) + return TRUE; + if (!slot_status1 || !slot_status2 || slot_status1->len != slot_status2->len) + return FALSE; + for (i = 0; i < slot_status1->len; i++) { + /* Compare slot at index i from slot_status1 and slot_status2 */ + QmiPhysicalSlotStatusSlot *slot_a; + QmiPhysicalSlotStatusSlot *slot_b; + + slot_a = &g_array_index (slot_status1, QmiPhysicalSlotStatusSlot, i); + slot_b = &g_array_index (slot_status2, QmiPhysicalSlotStatusSlot, i); + if (slot_a->physical_card_status != slot_b->physical_card_status) + return FALSE; + if (slot_a->physical_slot_status != slot_b->physical_slot_status) + return FALSE; + if (slot_a->iccid->len != slot_b->iccid->len) + return FALSE; + + for (j = 0; j < slot_a->iccid->len; j++) { + if (g_array_index (slot_a->iccid, guint8, j) != g_array_index (slot_b->iccid, guint8, j)) + return FALSE; + } + } + return TRUE; +} + +static gboolean +hotswap_while_loading_slots (GTask *task) +{ + MMIfaceModem *self; + + self = g_task_get_source_object (task); + + mm_obj_dbg (self, + "Slot status before loading sim slots is different from slot status after loading sim slots, " + "assuming hotswap"); + clear_load_sim_slot_callbacks (task); + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_ABORTED, + "Timed out waiting for final slot status to match initial slot status"); + g_object_unref (task); + + mm_base_modem_process_sim_event (MM_BASE_MODEM (self)); + return G_SOURCE_REMOVE; +} + +#define FINAL_SLOT_STATUS_TIMEOUT 3 + +static void +check_final_slot_status (GTask *task) +{ + LoadSimSlotsContext *ctx; + MMIfaceModem *self; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* We may never receive a slot status indication, which means that + * no slot switch was performed while loading sim slots */ + if (!ctx->final_slot_status || compare_slot_status (ctx->initial_slot_status, ctx->final_slot_status)) { + mm_obj_dbg (self, "Final slot status matches initial slot status"); + clear_load_sim_slot_callbacks (task); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + if (!ctx->final_slot_status_timeout_id) { + /* Setup a timeout before which final and initial slot status should match */ + mm_obj_dbg (self, + "Final slot status does not match initial slot status. " + "Waiting for final slot status indication..."); + ctx->final_slot_status_timeout_id = g_timeout_add_seconds (FINAL_SLOT_STATUS_TIMEOUT, + (GSourceFunc) hotswap_while_loading_slots, + self); + } +} + static void active_slot_switch_ready (QmiClientUim *client, GAsyncResult *res, @@ -3244,10 +3366,13 @@ active_slot_switch_ready (QmiClientUim *client, if ((!output || !qmi_message_uim_switch_slot_output_get_result (output, &error)) && !g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) { mm_obj_err (self, "couldn't switch to original slot %u", ctx->active_slot_number); + clear_load_sim_slot_callbacks (task); g_task_return_error (task, g_steal_pointer (&error)); - } else - g_task_return_boolean (task, TRUE); - g_object_unref (task); + g_object_unref (task); + return; + } + + check_final_slot_status (task); } static void @@ -3262,8 +3387,10 @@ reload_active_slot (GTask *task) /* If we're already in the original active SIM slot, nothing else to do */ if (ctx->current_slot_number == ctx->active_slot_number) { - g_task_return_boolean (task, TRUE); - g_object_unref (task); + mm_obj_dbg (self, + "Already on the original active SIM at slot %u. " + "No more slot switches necessary", ctx->active_slot_number); + check_final_slot_status (task); return; } @@ -3379,6 +3506,35 @@ load_next_sim_info (GTask *task) } static void +load_sim_slots_indication_cb (QmiClientUim *client, + QmiIndicationUimSlotStatusOutput *output, + GTask *task) +{ + g_autoptr(GError) error = NULL; + LoadSimSlotsContext *ctx; + MMIfaceModem *self; + GArray *physical_slots = NULL; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + mm_obj_dbg (self, "received slot status indication while loading slots"); + if (!qmi_indication_uim_slot_status_output_get_physical_slot_status (output, + &physical_slots, + &error)) { + mm_obj_warn (self, "could not process slot status indication: %s", error->message); + return; + } + + g_clear_object (&ctx->final_slot_status); + ctx->final_slot_status = g_array_ref (physical_slots); + + /* We are awaiting the final slot status before we finish the load_sim_slots task */ + if (ctx->final_slot_status_timeout_id) + check_final_slot_status (task); +} + +static void uim_get_slot_status_ready (QmiClientUim *client, GAsyncResult *res, GTask *task) @@ -3386,6 +3542,7 @@ uim_get_slot_status_ready (QmiClientUim *client, g_autoptr(QmiMessageUimGetSlotStatusOutput) output = NULL; LoadSimSlotsContext *ctx; MMIfaceModem *self; + Private *priv; GError *error = NULL; GArray *physical_slots = NULL; GArray *ext_information = NULL; @@ -3394,6 +3551,7 @@ uim_get_slot_status_ready (QmiClientUim *client, self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); + priv = get_private (MM_SHARED_QMI (self)); output = qmi_client_uim_get_slot_status_finish (client, res, &error); if (!output || @@ -3404,6 +3562,11 @@ uim_get_slot_status_ready (QmiClientUim *client, return; } + /* Store the slot status before loading all sim slots. + * We know we are done loading sim slots when the final slot status + * indication is the same as initial slot status */ + ctx->initial_slot_status = g_array_ref (physical_slots); + /* It's fine if we don't have EID information, but it should be well-formed if present. If it's malformed, * there is probably a modem firmware bug. */ if (qmi_message_uim_get_slot_status_output_get_physical_slot_information (output, &ext_information, NULL) && @@ -3491,6 +3654,17 @@ uim_get_slot_status_ready (QmiClientUim *client, } g_assert_cmpuint (ctx->sim_slots->len, ==, physical_slots->len); + /* Block hotswap on slot_status_indications since + * we will be switching slots while loading them. */ + if (priv->uim_slot_status_indication_id) + g_signal_handler_block (client, priv->uim_slot_status_indication_id); + + /* Monitor slot status indications while we are loading slots. Once we are done loading slots, + * check that no hotswap occurred. */ + ctx->load_sim_slots_indication_id = g_signal_connect (priv->uim_client, + "slot-status", + G_CALLBACK (load_sim_slots_indication_cb), + task); /* Now, iterate over all the SIMs, we'll attempt to load info from them by * quickly switching over to them, leaving the active SIM to the end */ load_next_sim_info (task); @@ -3780,7 +3954,6 @@ uim_slot_status_indication_cb (QmiClientUim *client, MMSharedQmi *self) { GArray *physical_slots = NULL; - guint i; g_autoptr(GError) error = NULL; mm_obj_dbg (self, "received slot status indication"); @@ -3792,27 +3965,13 @@ uim_slot_status_indication_cb (QmiClientUim *client, return; } - for (i = 0; i < physical_slots->len; i++) { - QmiPhysicalSlotStatusSlot *slot_status; - - slot_status = &g_array_index (physical_slots, QmiPhysicalSlotStatusSlot, i); - - /* We only care about active slot changes */ - if (slot_status->physical_slot_status == QMI_UIM_SLOT_STATE_ACTIVE) { - g_autofree gchar *iccid = NULL; - - if (slot_status->iccid && slot_status->iccid->len > 0) { - iccid = mm_bcd_to_string ((const guint8 *) slot_status->iccid->data, slot_status->iccid->len, - TRUE /* low_nybble_first */); - } - - mm_iface_modem_check_for_sim_swap (MM_IFACE_MODEM (self), - i + 1, /* Slot index */ - iccid, - NULL, - NULL); - } - } + /* A slot status indication means that + * 1) The physical slot to logical slot mapping has changed as a + * result of switching the slot. or, + * 2) A card has been removed from, or inserted to, the physical slot. or, + * 3) A physical slot is powered up or down. or, + * In all these cases, we must reprobe the modem to keep SIM objects updated */ + mm_base_modem_process_sim_event (MM_BASE_MODEM (self)); } static void |