diff options
author | Pavan Holla <pholla@chromium.org> | 2021-05-22 19:33:19 +0000 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2021-07-12 14:47:52 +0200 |
commit | 503e886c02e074b4cd334476569f42ee5c27e255 (patch) | |
tree | 201459154033c89fe0ecf9b10c875b73e1e61ccf /src | |
parent | 4102cbc700a0489e5d398bb93f47c151783dbea9 (diff) |
shared-qmi,iface-modem : Do not reprobe on hotswap in inactive slot
Prior to this change, a hotswap on the inactive slot would result
in reprobe of the modem. With this change, the sim object would be
updated, but no reprobe will be performed. The advantage is
1) we do not drop connections on the active slot
2) Slot status on the inactive slot is susceptible to firmware bugs.
We have seen scenarios where the inactive slot is reported as
absent/ iccid={0}, followed by a correction. We should not
reprobe the modem if our active slot hasn't changed.
Diffstat (limited to 'src')
-rw-r--r-- | src/mm-iface-modem.c | 72 | ||||
-rw-r--r-- | src/mm-iface-modem.h | 4 | ||||
-rw-r--r-- | src/mm-shared-qmi.c | 160 |
3 files changed, 225 insertions, 11 deletions
diff --git a/src/mm-iface-modem.c b/src/mm-iface-modem.c index c3c4106d..b3a301f7 100644 --- a/src/mm-iface-modem.c +++ b/src/mm-iface-modem.c @@ -134,6 +134,78 @@ mm_iface_modem_check_for_sim_swap (MMIfaceModem *self, g_object_unref (task); } +static void +sim_slot_free (MMBaseSim *sim) +{ + if (sim) + g_object_unref (sim); +} + +void +mm_iface_modem_modify_sim (MMIfaceModem *self, + guint slot_index, + MMBaseSim *new_sim) +{ + MmGdbusModem *skeleton; + g_autoptr (GPtrArray) sim_slots_old = NULL; + g_autoptr (GPtrArray) sim_slots_new = NULL; + guint i; + GPtrArray *sim_slot_paths_array; + g_auto (GStrv) sim_slot_paths = NULL; + + g_object_get (self, + MM_IFACE_MODEM_SIM_SLOTS, + &sim_slots_old, + NULL); + if (!sim_slots_old) { + mm_obj_warn (self, "Failed to process SIM hot swap: couldn't load current list of SIM slots"); + return; + } + + sim_slot_paths_array = g_ptr_array_new (); + sim_slots_new = g_ptr_array_new_with_free_func ((GDestroyNotify) sim_slot_free); + for (i = 0; i < sim_slots_old->len; i++) { + MMBaseSim *sim; + const gchar *sim_path; + + sim = MM_BASE_SIM (g_ptr_array_index (sim_slots_old, i)); + if (i == slot_index) { + mm_obj_dbg (self, "Updating sim at slot %d", i + 1); + g_ptr_array_add (sim_slots_new, new_sim ? g_object_ref (new_sim) : NULL); + sim = new_sim; + } else { + g_ptr_array_add (sim_slots_new, g_object_ref (sim)); + } + + if (!sim) { + g_ptr_array_add (sim_slot_paths_array, g_strdup ("/")); + continue; + } + + sim_path = mm_base_sim_get_path (sim); + g_ptr_array_add (sim_slot_paths_array, g_strdup (sim_path)); + } + + g_object_set (self, + MM_IFACE_MODEM_SIM_SLOTS, + sim_slots_new, + NULL); + + g_ptr_array_add (sim_slot_paths_array, NULL); + sim_slot_paths = (GStrv) g_ptr_array_free (sim_slot_paths_array, FALSE); + + g_object_get (self, + MM_IFACE_MODEM_DBUS_SKELETON, + &skeleton, + NULL); + if (!skeleton) { + mm_obj_warn (self, "Failed to get dbus skeleton in mm_iface_modem_modify_sim, will not update sim_slots"); + return; + } + mm_gdbus_modem_set_sim_slots (skeleton, (const gchar *const *) sim_slot_paths); + g_object_unref (skeleton); +} + /*****************************************************************************/ void diff --git a/src/mm-iface-modem.h b/src/mm-iface-modem.h index fa2b3dc8..34f3297b 100644 --- a/src/mm-iface-modem.h +++ b/src/mm-iface-modem.h @@ -609,4 +609,8 @@ gboolean mm_iface_modem_check_for_sim_swap_finish (MMIfaceModem *self, GAsyncResult *res, GError **error); +void mm_iface_modem_modify_sim (MMIfaceModem *self, + guint slot_index, + MMBaseSim *new_sim); + #endif /* MM_IFACE_MODEM_H */ diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c index 9edc595b..f82606e6 100644 --- a/src/mm-shared-qmi.c +++ b/src/mm-shared-qmi.c @@ -98,6 +98,7 @@ typedef struct { /* Slot status monitoring */ QmiClient *uim_client; gulong uim_slot_status_indication_id; + GArray *slots_status; gulong uim_refresh_indication_id; guint uim_refresh_start_timeout_id; } Private; @@ -121,6 +122,8 @@ private_free (Private *priv) g_object_unref (priv->loc_client); if (priv->uim_slot_status_indication_id) g_signal_handler_disconnect (priv->uim_client, priv->uim_slot_status_indication_id); + if (priv->slots_status) + g_array_unref (priv->slots_status); if (priv->uim_refresh_indication_id) g_signal_handler_disconnect (priv->uim_client, priv->uim_refresh_indication_id); if (priv->uim_client) @@ -3326,15 +3329,17 @@ uim_get_slot_status_ready (QmiClientUim *client, { g_autoptr(QmiMessageUimGetSlotStatusOutput) output = NULL; LoadSimSlotsContext *ctx; - MMIfaceModem *self; - GError *error = NULL; - GArray *physical_slots = NULL; - GArray *ext_information = NULL; - GArray *slot_eids = NULL; - guint i; + MMIfaceModem *self; + Private *priv; + GError *error = NULL; + GArray *physical_slots = NULL; + GArray *ext_information = NULL; + GArray *slot_eids = NULL; + guint i; 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 || @@ -3345,6 +3350,12 @@ uim_get_slot_status_ready (QmiClientUim *client, return; } + /* Store the slot status before loading all sim slots. + * We will use this to check if a hotswap requires a reprobe. */ + if (priv->slots_status) + g_array_unref (priv->slots_status); + priv->slots_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) && @@ -3710,18 +3721,120 @@ uim_refresh_indication_cb (QmiClientUim *client, } } +/* Modifies the sim at slot == index+1, based on the content of slot_status. + * Primarily used when a hotswap occurs on the inactive slot */ +static void +update_sim_from_slot_status (MMSharedQmi *self, + QmiPhysicalSlotStatusSlot *slot_status, + guint slot_index) +{ + g_autoptr(MMBaseSim) sim = NULL; + g_autofree gchar *raw_iccid = NULL; + g_autofree gchar *iccid = NULL; + g_autoptr(GError) inner_error = NULL; + + mm_obj_dbg (self, "Updating sim at slot %d", slot_index + 1); + /* If sim is not present, ask mm_iface_modem to clear the sim object */ + if (slot_status->physical_card_status != QMI_UIM_PHYSICAL_CARD_STATE_PRESENT) { + mm_iface_modem_modify_sim (MM_IFACE_MODEM (self), slot_index, NULL); + return; + } + + raw_iccid = mm_utils_bin2hexstr ((const guint8 *)slot_status->iccid->data, slot_status->iccid->len); + if (!raw_iccid) { + mm_obj_warn (self, "not creating SIM object: failed to convert ICCID from BCD"); + return; + } + iccid = mm_3gpp_parse_iccid (raw_iccid, &inner_error); + if (!iccid) { + mm_obj_warn (self, "not creating SIM object: couldn't parse SIM iccid: %s", inner_error->message); + return; + } + sim = mm_sim_qmi_new_initialized (MM_BASE_MODEM (self), + TRUE, /* consider DMS UIM deprecated if we're creating SIM slots */ + slot_index + 1, /* slot number is the array index starting at 1 */ + slot_status->physical_slot_status, /* is_active */ + iccid, + NULL, /* imsi unknown */ + NULL, /* eid unknown */ + NULL, /* operator id unknown */ + NULL, /* operator name unknown */ + NULL); /* emergency numbers unknown */ + + mm_iface_modem_modify_sim (MM_IFACE_MODEM (self), slot_index, sim); +} + +/* Checks for equality of two slots. */ +static gboolean +slot_status_equal (QmiPhysicalSlotStatusSlot *slot_a, + QmiPhysicalSlotStatusSlot *slot_b) +{ + guint j; + + if (slot_a->physical_slot_status != slot_b->physical_slot_status) + return FALSE; + if (slot_a->physical_card_status != slot_b->physical_card_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; +} + +/* Checks for equality of QmiPhysicalSlotStatusSlot arrays. + * The number of elements in each array is expected to be the number of sim slots in the modem.*/ +static gboolean +slot_array_status_equal (GArray *slots_status1, + GArray *slots_status2, + gboolean check_active_slots_only) +{ + guint i; + + if (!slots_status1 && !slots_status2) + return TRUE; + if (!slots_status1 || !slots_status2 || slots_status1->len != slots_status2->len) + return FALSE; + for (i = 0; i < slots_status1->len; i++) { + /* Compare slot at index i from slots_status1 and slots_status2 */ + QmiPhysicalSlotStatusSlot *slot_a; + QmiPhysicalSlotStatusSlot *slot_b; + + slot_a = &g_array_index (slots_status1, QmiPhysicalSlotStatusSlot, i); + slot_b = &g_array_index (slots_status2, QmiPhysicalSlotStatusSlot, i); + + /* Check that slot_a and slot_b have the same slot status (i.e. active or inactive) */ + if (slot_a->physical_slot_status != slot_b->physical_slot_status) + return FALSE; + /* Once slot_a and slot_b are confirmed to have the same physical slot status, + * we will ignore inactive slots if check_active_slots_only is set. */ + if (check_active_slots_only && slot_a->physical_slot_status != QMI_UIM_SLOT_STATE_ACTIVE) { + g_assert (slot_a->physical_slot_status == slot_b->physical_slot_status); + continue; + } + if (!slot_status_equal (slot_a, slot_b)) + return FALSE; + } + return TRUE; +} + static void uim_slot_status_indication_cb (QmiClientUim *client, QmiIndicationUimSlotStatusOutput *output, MMSharedQmi *self) { - GArray *physical_slots = NULL; + GArray *new_slots_status = NULL; + Private *priv; + guint i; g_autoptr(GError) error = NULL; + priv = get_private (self); mm_obj_dbg (self, "received slot status indication"); if (!qmi_indication_uim_slot_status_output_get_physical_slot_status (output, - &physical_slots, + &new_slots_status, &error)) { mm_obj_warn (self, "could not process slot status indication: %s", error->message); return; @@ -3731,9 +3844,34 @@ uim_slot_status_indication_cb (QmiClientUim *client, * 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)); + * 3) A physical slot is powered up or down. */ + + /* Reprobe if the active slot changed or the information in an + * active slot's status changed */ + if (!slot_array_status_equal (priv->slots_status, new_slots_status, TRUE)) { + mm_obj_dbg (self, "An active slot had a status change, will reprobe the modem"); + mm_base_modem_process_sim_event (MM_BASE_MODEM (self)); + return; + } + + /* An inactive slot changed, we won't be reprobing the modem. + * Instead, we will ask mm_iface_modem to update the sim object. + * Iterate over each slot to identify the slots that changed state.*/ + for (i = 0; i < priv->slots_status->len; i++) { + QmiPhysicalSlotStatusSlot *old_slot; + QmiPhysicalSlotStatusSlot *new_slot; + + old_slot = &g_array_index (priv->slots_status, QmiPhysicalSlotStatusSlot, i); + new_slot = &g_array_index (new_slots_status, QmiPhysicalSlotStatusSlot, i); + + if (!slot_status_equal (old_slot, new_slot)) { + mm_obj_dbg (self, "Slot %d (inactive) had a status change. Will update sims, but not reprobe", i + 1); + update_sim_from_slot_status (self, new_slot, i); + } + } + + g_clear_pointer (&priv->slots_status, g_array_unref); + priv->slots_status = g_array_ref (new_slots_status); } static void |