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.
This commit is contained in:
Pavan Holla
2021-02-24 20:03:24 +00:00
committed by Aleksander Morgado
parent fbc1636001
commit e1372a716e

View File

@@ -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);
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;
}
@@ -3378,6 +3505,35 @@ load_next_sim_info (GTask *task)
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,
@@ -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