diff options
author | Pavan Holla <pholla@chromium.org> | 2021-02-24 20:03:24 +0000 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2021-02-27 15:26:43 +0100 |
commit | e1372a716e1e87737427f5cc39a7e2b15c982862 (patch) | |
tree | 31234fdcce74ac606eefb272242c1d2fffd82272 | |
parent | fbc1636001878f5beb9a8a551fe4c4029ff6a95b (diff) |
iface-modem: detect hotswap on all slots
Prior to this change, hotswap was detected on the primary sim slot only.
After this change, hotswap on any slot triggers reenumeration of the
modem. SIM hotswap events happening on the non-active slot would also
trigger a full modem reprobe. The modem may already have been
connected while the SIM event happens on the secondary slot, and we force
disconnection to reload the inactive SIM info. However, a user would
expect feedback for their SIM action, so we need to reprobe the modem.
-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 |