diff options
Diffstat (limited to 'src/mm-shared-qmi.c')
-rw-r--r-- | src/mm-shared-qmi.c | 455 |
1 files changed, 455 insertions, 0 deletions
diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c index 5ae54349..bea47a39 100644 --- a/src/mm-shared-qmi.c +++ b/src/mm-shared-qmi.c @@ -92,6 +92,12 @@ typedef struct { gboolean config_active_default; GArray *config_list; gint config_active_i; + + /* Slot status monitoring */ + QmiClient *uim_client; + gulong uim_slot_status_indication_id; + gulong uim_refresh_indication_id; + guint uim_refresh_start_timeout_id; } Private; static void @@ -111,6 +117,14 @@ private_free (Private *priv) g_signal_handler_disconnect (priv->loc_client, priv->loc_location_nmea_indication_id); if (priv->loc_client) 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->uim_refresh_indication_id) + g_signal_handler_disconnect (priv->uim_client, priv->uim_refresh_indication_id); + if (priv->uim_client) + g_object_unref (priv->uim_client); + if (priv->uim_refresh_start_timeout_id) + g_source_remove (priv->uim_refresh_start_timeout_id); g_strfreev (priv->loc_assistance_data_servers); g_slice_free (Private, priv); } @@ -3602,6 +3616,447 @@ mm_shared_qmi_set_primary_sim_slot (MMIfaceModem *self, } /*****************************************************************************/ +/* SIM hot swap detection */ + +#define REFRESH_START_TIMEOUT_SECS 3 + +gboolean +mm_shared_qmi_setup_sim_hot_swap_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +uim_refresh_complete (QmiClientUim *client, + QmiUimSessionType session_type) +{ + g_autoptr(QmiMessageUimRefreshCompleteInput) refresh_complete_input; + GArray *dummy_aid; + + dummy_aid = g_array_new (FALSE, FALSE, sizeof (guint8)); + + refresh_complete_input = qmi_message_uim_refresh_complete_input_new (); + qmi_message_uim_refresh_complete_input_set_session ( + refresh_complete_input, + session_type, + dummy_aid, /* ignored */ + NULL); + qmi_message_uim_refresh_complete_input_set_info ( + refresh_complete_input, + TRUE, + NULL); + + qmi_client_uim_refresh_complete ( + client, + refresh_complete_input, + 10, + NULL, + NULL, + NULL); + g_array_unref (dummy_aid); +} + +static gboolean +uim_start_refresh_timeout (MMSharedQmi *self) +{ + Private *priv; + + priv = get_private (self); + priv->uim_refresh_start_timeout_id = 0; + + mm_obj_dbg (self, "refresh start timed out; trigger SIM change check"); + + mm_iface_modem_check_for_sim_swap (MM_IFACE_MODEM (self), 0, NULL, NULL, NULL); + + return G_SOURCE_REMOVE; +} + +static void +uim_refresh_indication_cb (QmiClientUim *client, + QmiIndicationUimRefreshOutput *output, + MMSharedQmi *self) +{ + QmiUimRefreshStage stage; + QmiUimRefreshMode mode; + QmiUimSessionType session_type; + Private *priv; + g_autoptr(GError) error = NULL; + + priv = get_private (self); + + if (!qmi_indication_uim_refresh_output_get_event (output, + &stage, + &mode, + &session_type, + NULL, + NULL, + &error)) { + mm_obj_warn (self, "couldn't process UIM refresh indication: %s", error->message); + return; + } + + mm_obj_dbg (self, "refresh indication received: session type '%s', stage '%s', mode '%s'", + qmi_uim_session_type_get_string (session_type), + qmi_uim_refresh_stage_get_string (stage), + qmi_uim_refresh_mode_get_string (mode)); + + /* Support only the first slot for now. Primary GW provisioning is used in old modems. */ + if (session_type != QMI_UIM_SESSION_TYPE_CARD_SLOT_1 && + session_type != QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING) { + mm_obj_warn (self, "refresh session type not supported: %s", qmi_uim_session_type_get_string (session_type)); + return; + } + + /* Currently we handle only UICC Reset type refresh, which can be used + * in profile switch scenarios. In other cases we just trigger 'refresh + * complete' during start phase. Signal to notify about potential SIM + * profile switch is triggered when the refresh is ending. If it were + * triggered in start phase, reading SIM files seems to fail with + * an internal error. + * + * It's possible that 'end-with-success' stage never appears. For that, + * we start a timer at 'start' stage and if it expires, the SIM change + * check is triggered anyway. */ + if (stage == QMI_UIM_REFRESH_STAGE_START) { + if (mode == QMI_UIM_REFRESH_MODE_RESET) { + if (!priv->uim_refresh_start_timeout_id) + priv->uim_refresh_start_timeout_id = g_timeout_add_seconds (REFRESH_START_TIMEOUT_SECS, + (GSourceFunc)uim_start_refresh_timeout, + self); + } else + uim_refresh_complete (client, session_type); + } else if (stage == QMI_UIM_REFRESH_STAGE_END_WITH_SUCCESS) { + if (mode == QMI_UIM_REFRESH_MODE_RESET) { + if (priv->uim_refresh_start_timeout_id) { + g_source_remove (priv->uim_refresh_start_timeout_id); + priv->uim_refresh_start_timeout_id = 0; + } + mm_iface_modem_check_for_sim_swap (MM_IFACE_MODEM (self), 0, NULL, NULL, NULL); + } + } +} + +static void +uim_slot_status_indication_cb (QmiClientUim *client, + QmiIndicationUimSlotStatusOutput *output, + MMSharedQmi *self) +{ + GArray *physical_slots = NULL; + guint i; + g_autoptr(GError) error = NULL; + + mm_obj_dbg (self, "received slot status indication"); + + 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; + } + + 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); + + mm_iface_modem_check_for_sim_swap (MM_IFACE_MODEM (self), + i + 1, /* Slot index */ + iccid, + NULL, + NULL); + } + } +} + +static void +uim_refresh_register_iccid_change_ready (QmiClientUim *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + g_autoptr(QmiMessageUimRefreshRegisterOutput) output = NULL; + g_autoptr(GError) error = NULL; + + self = g_task_get_source_object (task); + priv = get_private (self); + + output = qmi_client_uim_refresh_register_finish (client, res, &error); + if (!output || !qmi_message_uim_refresh_register_output_get_result (output, &error)) { + mm_obj_dbg (self, "refresh registration using 'refresh register' failed: %s", error->message); + g_clear_object (&priv->uim_client); + g_task_return_new_error (task, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED, + "SIM hot swap detection not supported by modem"); + } else { + mm_obj_dbg (self, "registered for SIM refresh events using 'refresh register'"); + priv->uim_refresh_indication_id = + g_signal_connect (client, + "refresh", + G_CALLBACK (uim_refresh_indication_cb), + self); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +/* This is the last resort if 'refresh register all' does not work. It works + * on some older modems. Those modems may not also support QMI_UIM_SESSION_TYPE_CARD_SLOT_1 + * so we'll use QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING */ +static void +uim_refresh_register_iccid_change (GTask *task) +{ + MMSharedQmi *self; + Private *priv; + QmiMessageUimRefreshRegisterInputInfoFilesElement file_element; + guint8 val; + g_autoptr(QmiMessageUimRefreshRegisterInput) refresh_register_input = NULL; + g_autoptr(GArray) dummy_aid = NULL; + g_autoptr(GArray) file = NULL; + g_autoptr(GArray) file_element_path = NULL; + + self = g_task_get_source_object (task); + priv = get_private (MM_SHARED_QMI (self)); + + mm_obj_dbg (self, "register for refresh file indication"); + + dummy_aid = g_array_new (FALSE, FALSE, sizeof (guint8)); + + file = g_array_sized_new (FALSE, FALSE, sizeof (QmiMessageUimRefreshRegisterInputInfoFilesElement), 1); + + file_element_path = g_array_sized_new (FALSE, FALSE, sizeof (guint8), 2); + val = 0x00; + g_array_append_val (file_element_path, val); + val = 0x3F; + g_array_append_val (file_element_path, val); + + + memset (&file_element, 0, sizeof (file_element)); + file_element.file_id = 0x2FE2; /* ICCID */ + file_element.path = file_element_path; + g_array_append_val (file, file_element); + + refresh_register_input = qmi_message_uim_refresh_register_input_new (); + qmi_message_uim_refresh_register_input_set_info (refresh_register_input, + TRUE, + FALSE, + file, + NULL); + qmi_message_uim_refresh_register_input_set_session (refresh_register_input, + QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING, + dummy_aid, + NULL); + + qmi_client_uim_refresh_register (QMI_CLIENT_UIM (priv->uim_client), + refresh_register_input, + 10, + NULL, + (GAsyncReadyCallback) uim_refresh_register_iccid_change_ready, + task); +} + +/* Refresh registration and event handling. + * This is used only as fallback in case slot status indications do not work + * in the particular modem (determined by UIM Get Slot Status failing) for + * detecting ICCID changing due to a profile switch. + * + * We assume that devices not supporting UIM Get Slot Status only have a + * single slot, for which we register refresh events. + */ + +static void +uim_refresh_register_all_ready (QmiClientUim *client, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(QmiMessageUimRefreshRegisterAllOutput) output = NULL; + g_autoptr(GError) error = NULL; + MMIfaceModem *self; + Private *priv; + + self = g_task_get_source_object (task); + priv = get_private (MM_SHARED_QMI (self)); + + output = qmi_client_uim_refresh_register_all_finish (client, res, &error); + if (!output || !qmi_message_uim_refresh_register_all_output_get_result (output, &error)) { + if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NOT_SUPPORTED) || + g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND)) { + /* As last resort, if 'refresh register all' fails, try a plain 'refresh register'. + * Some older modems may not support 'refresh register all'. */ + uim_refresh_register_iccid_change (task); + return; + } + + mm_obj_dbg (self, "refresh register all operation failed: %s", error->message); + g_clear_object (&priv->uim_client); + g_task_return_error (task, g_steal_pointer (&error)); + } else { + mm_obj_dbg (self, "registered for all SIM refresh events"); + priv->uim_refresh_indication_id = + g_signal_connect (client, + "refresh", + G_CALLBACK (uim_refresh_indication_cb), + self); + g_task_return_boolean (task, TRUE); + } + g_object_unref (task); +} + +static void +uim_slot_status_not_supported (GTask *task) +{ + MMIfaceModem *self; + Private *priv; + g_autoptr(QmiMessageUimRefreshRegisterAllInput) refresh_register_all_input = NULL; + g_autoptr(GArray) dummy_aid = NULL; + + self = g_task_get_source_object (task); + priv = get_private (MM_SHARED_QMI (self)); + + g_assert (!priv->uim_refresh_indication_id); + + mm_obj_dbg (self, "slot status not supported by modem: register for refresh indications"); + + dummy_aid = g_array_new (FALSE, FALSE, sizeof (guint8)); + refresh_register_all_input = qmi_message_uim_refresh_register_all_input_new (); + + qmi_message_uim_refresh_register_all_input_set_info (refresh_register_all_input, + TRUE, + NULL); + qmi_message_uim_refresh_register_all_input_set_session (refresh_register_all_input, + QMI_UIM_SESSION_TYPE_CARD_SLOT_1, + dummy_aid, + NULL); + + qmi_client_uim_refresh_register_all (QMI_CLIENT_UIM (priv->uim_client), + refresh_register_all_input, + 10, + NULL, + (GAsyncReadyCallback) uim_refresh_register_all_ready, + task); +} + +static void +uim_check_get_slot_status_ready (QmiClientUim *client, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(QmiMessageUimGetSlotStatusOutput) output = NULL; + g_autoptr(GError) error = NULL; + MMIfaceModem *self; + Private *priv; + + self = g_task_get_source_object (task); + priv = get_private (MM_SHARED_QMI (self)); + + output = qmi_client_uim_get_slot_status_finish (client, res, &error); + if (!output || !qmi_message_uim_get_slot_status_output_get_result (output, &error)) { + if (priv->uim_slot_status_indication_id) { + g_signal_handler_disconnect (client, priv->uim_slot_status_indication_id); + priv->uim_slot_status_indication_id = 0; + } + + if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NOT_SUPPORTED) || + g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND)) { + uim_slot_status_not_supported (task); + return; + } + + mm_obj_dbg (self, "slot status retrieval failed: %s", error->message); + g_clear_object (&priv->uim_client); + g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "slot status retrieval succeeded: monitoring slot status indications"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +uim_register_events_ready (QmiClientUim *client, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(QmiMessageUimRegisterEventsOutput) output = NULL; + g_autoptr(GError) error = NULL; + MMIfaceModem *self; + Private *priv; + + self = g_task_get_source_object (task); + priv = get_private (MM_SHARED_QMI (self)); + + /* If event registration fails, go on with initialization. In that case + * we cannot use slot status indications to detect eUICC profile switches. */ + output = qmi_client_uim_register_events_finish (client, res, &error); + if (output && qmi_message_uim_register_events_output_get_result (output, &error)) { + g_assert (!priv->uim_slot_status_indication_id); + priv->uim_slot_status_indication_id = g_signal_connect (priv->uim_client, + "slot-status", + G_CALLBACK (uim_slot_status_indication_cb), + self); + mm_obj_dbg (self, "registered for slot status indications"); + + /* Successful registration does not mean that the modem actually sends + * physical slot status indications; invoke Get Slot Status to find out if + * the modem really supports slot status. */ + qmi_client_uim_get_slot_status (client, + NULL, + 10, + NULL, + (GAsyncReadyCallback) uim_check_get_slot_status_ready, + task); + return; + } + + mm_obj_dbg (self, "not registered for slot status indications: %s", error->message); + uim_slot_status_not_supported (task); +} + +void +mm_shared_qmi_setup_sim_hot_swap (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(QmiMessageUimRegisterEventsInput) register_events_input = NULL; + GTask *task; + QmiClient *client = NULL; + Private *priv; + + if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), + QMI_SERVICE_UIM, &client, + callback, user_data)) + return; + + task = g_task_new (self, NULL, callback, user_data); + priv = get_private (MM_SHARED_QMI (self)); + + g_assert (!priv->uim_slot_status_indication_id); + g_assert (!priv->uim_client); + priv->uim_client = g_object_ref (client); + + register_events_input = qmi_message_uim_register_events_input_new (); + qmi_message_uim_register_events_input_set_event_registration_mask (register_events_input, + QMI_UIM_EVENT_REGISTRATION_FLAG_PHYSICAL_SLOT_STATUS, + NULL); + qmi_client_uim_register_events (QMI_CLIENT_UIM (priv->uim_client), + register_events_input, + 10, + NULL, + (GAsyncReadyCallback) uim_register_events_ready, + task); +} + +/*****************************************************************************/ /* Location: Set SUPL server */ typedef struct { |