aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mm-shared-qmi.c213
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