shared-qmi: implement SIM/profile change detection

Implement eUICC change detection for QMI based modems using one of the
following mechanisms (in order of preference):

1. If the modem supports "get slot status" operation, we monitor
   physical slot status indications from the modem for the active
   slot to detect when ICCID changes.
2. Use "refresh register all" to subscribe refresh indications when
   the eUICC triggers REFRESH operation following the enablement of
   a new profile.
3. Use "refresh register" to subscribe refresh indications (file
   path of EF_ICCID is used) in a similar way. This is used with
   older modems that do not support "refresh register all".

If ICCID change is detected, the already existing SIM hot swap
mechanism in MM is triggered.
This commit is contained in:
Teijo Kinnunen
2020-09-28 09:29:09 +00:00
committed by Aleksander Morgado
parent 318b2b01e3
commit e91f2ef315
8 changed files with 626 additions and 56 deletions

View File

@@ -27,6 +27,7 @@ static void iface_modem_location_init (MMIfaceModemLocation *iface);
static void iface_modem_time_init (MMIfaceModemTime *iface); static void iface_modem_time_init (MMIfaceModemTime *iface);
static void shared_quectel_init (MMSharedQuectel *iface); static void shared_quectel_init (MMSharedQuectel *iface);
static MMIfaceModem *iface_modem_parent;
static MMIfaceModemLocation *iface_modem_location_parent; static MMIfaceModemLocation *iface_modem_location_parent;
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmiQuectel, mm_broadband_modem_qmi_quectel, MM_TYPE_BROADBAND_MODEM_QMI, 0, G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmiQuectel, mm_broadband_modem_qmi_quectel, MM_TYPE_BROADBAND_MODEM_QMI, 0,
@@ -62,10 +63,18 @@ mm_broadband_modem_qmi_quectel_init (MMBroadbandModemQmiQuectel *self)
static void static void
iface_modem_init (MMIfaceModem *iface) iface_modem_init (MMIfaceModem *iface)
{ {
iface_modem_parent = g_type_interface_peek_parent (iface);
iface->setup_sim_hot_swap = mm_shared_quectel_setup_sim_hot_swap; iface->setup_sim_hot_swap = mm_shared_quectel_setup_sim_hot_swap;
iface->setup_sim_hot_swap_finish = mm_shared_quectel_setup_sim_hot_swap_finish; iface->setup_sim_hot_swap_finish = mm_shared_quectel_setup_sim_hot_swap_finish;
} }
static MMIfaceModem *
peek_parent_modem_interface (MMSharedQuectel *self)
{
return iface_modem_parent;
}
static void static void
iface_modem_firmware_init (MMIfaceModemFirmware *iface) iface_modem_firmware_init (MMIfaceModemFirmware *iface)
{ {
@@ -87,7 +96,7 @@ iface_modem_location_init (MMIfaceModemLocation *iface)
} }
static MMIfaceModemLocation * static MMIfaceModemLocation *
peek_parent_location_interface (MMSharedQuectel *self) peek_parent_modem_location_interface (MMSharedQuectel *self)
{ {
return iface_modem_location_parent; return iface_modem_location_parent;
} }
@@ -102,7 +111,8 @@ iface_modem_time_init (MMIfaceModemTime *iface)
static void static void
shared_quectel_init (MMSharedQuectel *iface) shared_quectel_init (MMSharedQuectel *iface)
{ {
iface->peek_parent_location_interface = peek_parent_location_interface; iface->peek_parent_modem_interface = peek_parent_modem_interface;
iface->peek_parent_modem_location_interface = peek_parent_modem_location_interface;
} }
static void static void

View File

@@ -27,6 +27,7 @@ static void iface_modem_location_init (MMIfaceModemLocation *iface);
static void iface_modem_time_init (MMIfaceModemTime *iface); static void iface_modem_time_init (MMIfaceModemTime *iface);
static void shared_quectel_init (MMSharedQuectel *iface); static void shared_quectel_init (MMSharedQuectel *iface);
static MMIfaceModem *iface_modem_parent;
static MMIfaceModemLocation *iface_modem_location_parent; static MMIfaceModemLocation *iface_modem_location_parent;
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQuectel, mm_broadband_modem_quectel, MM_TYPE_BROADBAND_MODEM, 0, G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQuectel, mm_broadband_modem_quectel, MM_TYPE_BROADBAND_MODEM, 0,
@@ -62,10 +63,18 @@ mm_broadband_modem_quectel_init (MMBroadbandModemQuectel *self)
static void static void
iface_modem_init (MMIfaceModem *iface) iface_modem_init (MMIfaceModem *iface)
{ {
iface_modem_parent = g_type_interface_peek_parent (iface);
iface->setup_sim_hot_swap = mm_shared_quectel_setup_sim_hot_swap; iface->setup_sim_hot_swap = mm_shared_quectel_setup_sim_hot_swap;
iface->setup_sim_hot_swap_finish = mm_shared_quectel_setup_sim_hot_swap_finish; iface->setup_sim_hot_swap_finish = mm_shared_quectel_setup_sim_hot_swap_finish;
} }
static MMIfaceModem *
peek_parent_modem_interface (MMSharedQuectel *self)
{
return iface_modem_parent;
}
static void static void
iface_modem_firmware_init (MMIfaceModemFirmware *iface) iface_modem_firmware_init (MMIfaceModemFirmware *iface)
{ {
@@ -87,7 +96,7 @@ iface_modem_location_init (MMIfaceModemLocation *iface)
} }
static MMIfaceModemLocation * static MMIfaceModemLocation *
peek_parent_location_interface (MMSharedQuectel *self) peek_parent_modem_location_interface (MMSharedQuectel *self)
{ {
return iface_modem_location_parent; return iface_modem_location_parent;
} }
@@ -102,7 +111,8 @@ iface_modem_time_init (MMIfaceModemTime *iface)
static void static void
shared_quectel_init (MMSharedQuectel *iface) shared_quectel_init (MMSharedQuectel *iface)
{ {
iface->peek_parent_location_interface = peek_parent_location_interface; iface->peek_parent_modem_interface = peek_parent_modem_interface;
iface->peek_parent_modem_location_interface = peek_parent_modem_location_interface;
} }
static void static void

View File

@@ -43,6 +43,7 @@ typedef enum {
} FeatureSupport; } FeatureSupport;
typedef struct { typedef struct {
MMIfaceModem *iface_modem_parent;
MMIfaceModemLocation *iface_modem_location_parent; MMIfaceModemLocation *iface_modem_location_parent;
MMModemLocationSource provided_sources; MMModemLocationSource provided_sources;
MMModemLocationSource enabled_sources; MMModemLocationSource enabled_sources;
@@ -65,8 +66,11 @@ get_private (MMSharedQuectel *self)
priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE; priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE;
priv->qgps_supported = FEATURE_SUPPORT_UNKNOWN; priv->qgps_supported = FEATURE_SUPPORT_UNKNOWN;
g_assert (MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_location_interface); g_assert (MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_location_interface);
priv->iface_modem_location_parent = MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_location_interface (self); priv->iface_modem_location_parent = MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_location_interface (self);
g_assert (MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_interface);
priv->iface_modem_parent = MM_SHARED_QUECTEL_GET_INTERFACE (self)->peek_parent_modem_interface (self);
g_object_set_qdata (G_OBJECT (self), private_quark, priv); g_object_set_qdata (G_OBJECT (self), private_quark, priv);
} }
@@ -170,16 +174,37 @@ mm_shared_quectel_setup_sim_hot_swap_finish (MMIfaceModem *self,
return g_task_propagate_boolean (G_TASK (res), error); return g_task_propagate_boolean (G_TASK (res), error);
} }
static void
parent_setup_sim_hot_swap_ready (MMIfaceModem *self,
GAsyncResult *res,
GTask *task)
{
Private *priv;
g_autoptr(GError) error = NULL;
priv = get_private (MM_SHARED_QUECTEL (self));
if (!priv->iface_modem_parent->setup_sim_hot_swap_finish (self, res, &error))
mm_obj_dbg (self, "additional SIM hot swap detection setup failed: %s", error->message);
/* The +QUSIM based setup never fails, so we can safely return success here */
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
void void
mm_shared_quectel_setup_sim_hot_swap (MMIfaceModem *self, mm_shared_quectel_setup_sim_hot_swap (MMIfaceModem *self,
GAsyncReadyCallback callback, GAsyncReadyCallback callback,
gpointer user_data) gpointer user_data)
{ {
Private *priv;
MMPortSerialAt *ports[2]; MMPortSerialAt *ports[2];
GTask *task; GTask *task;
GRegex *pattern; GRegex *pattern;
guint i; guint i;
priv = get_private (MM_SHARED_QUECTEL (self));
task = g_task_new (self, NULL, callback, user_data); task = g_task_new (self, NULL, callback, user_data);
ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
@@ -200,6 +225,17 @@ mm_shared_quectel_setup_sim_hot_swap (MMIfaceModem *self,
g_regex_unref (pattern); g_regex_unref (pattern);
mm_obj_dbg (self, "+QUSIM detection set up"); mm_obj_dbg (self, "+QUSIM detection set up");
/* Now, if available, setup parent logic */
if (priv->iface_modem_parent->setup_sim_hot_swap &&
priv->iface_modem_parent->setup_sim_hot_swap_finish) {
priv->iface_modem_parent->setup_sim_hot_swap (self,
(GAsyncReadyCallback) parent_setup_sim_hot_swap_ready,
task);
return;
}
/* Otherwise, we're done */
g_task_return_boolean (task, TRUE); g_task_return_boolean (task, TRUE);
g_object_unref (task); g_object_unref (task);
} }

View File

@@ -37,7 +37,8 @@ typedef struct _MMSharedQuectel MMSharedQuectel;
struct _MMSharedQuectel { struct _MMSharedQuectel {
GTypeInterface g_iface; GTypeInterface g_iface;
MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedQuectel *self); MMIfaceModem * (* peek_parent_modem_interface) (MMSharedQuectel *self);
MMIfaceModemLocation * (* peek_parent_modem_location_interface) (MMSharedQuectel *self);
}; };
GType mm_shared_quectel_get_type (void); GType mm_shared_quectel_get_type (void);

View File

@@ -3554,16 +3554,9 @@ cleanup_unsolicited_events_3gpp (MMIfaceModem3gpp *_self,
gpointer user_data) gpointer user_data)
{ {
MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self); MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
gboolean is_sim_hot_swap_configured = FALSE;
g_object_get (self,
MM_IFACE_MODEM_SIM_HOT_SWAP_CONFIGURED, &is_sim_hot_swap_configured,
NULL);
self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY; self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY;
self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_CONNECT; self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_CONNECT;
if (is_sim_hot_swap_configured)
self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE; self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE;
if (self->priv->is_pco_supported) if (self->priv->is_pco_supported)
self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_PCO; self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_PCO;
@@ -3791,69 +3784,126 @@ modem_3gpp_enable_unsolicited_registration_events (MMIfaceModem3gpp *_self,
/*****************************************************************************/ /*****************************************************************************/
/* Setup SIM hot swap */ /* Setup SIM hot swap */
typedef struct {
MbimDevice *device;
GError *subscriber_info_error;
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
GError *qmi_error;
#endif
} SetupSimHotSwapContext;
static void
setup_sim_hot_swap_context_free (SetupSimHotSwapContext *ctx)
{
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
g_clear_error (&ctx->qmi_error);
#endif
g_clear_error (&ctx->subscriber_info_error);
g_clear_object (&ctx->device);
g_slice_free (SetupSimHotSwapContext, ctx);
}
static gboolean static gboolean
modem_setup_sim_hot_swap_finish (MMIfaceModem *self, modem_setup_sim_hot_swap_finish (MMIfaceModem *self,
GAsyncResult *res, GAsyncResult *res,
GError **error) GError **error)
{ {
return g_task_propagate_boolean (G_TASK (res), error); return g_task_propagate_boolean (G_TASK (res), error);
} }
static void static void
enable_subscriber_info_unsolicited_events_ready (MMBroadbandModemMbim *self, sim_hot_swap_complete (GTask *task)
GAsyncResult *res,
GTask *task)
{ {
GError *error = NULL; SetupSimHotSwapContext *ctx;
if (!common_enable_disable_unsolicited_events_finish (self, res, &error)) { ctx = g_task_get_task_data (task);
mm_obj_dbg (self, "failed to enable subscriber info events: %s", error->message);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_task_return_boolean (task, TRUE); /* If MBIM based logic worked, success */
if (!ctx->subscriber_info_error)
g_task_return_boolean (task, TRUE);
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
/* Otherwise, If QMI-over-MBIM based logic worked, success */
else if (!ctx->qmi_error)
g_task_return_boolean (task, TRUE);
#endif
/* Otherwise, prefer MBIM specific error */
else
g_task_return_error (task, g_steal_pointer (&ctx->subscriber_info_error));
g_object_unref (task); g_object_unref (task);
} }
static void #if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
setup_subscriber_info_unsolicited_events_ready (MMBroadbandModemMbim *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!common_setup_cleanup_unsolicited_events_finish (self, res, &error)) { static void
mm_obj_dbg (self, "failed to set up subscriber info events: %s", error->message); qmi_setup_sim_hot_swap_ready (MMIfaceModem *self,
g_task_return_error (task, error); GAsyncResult *res,
g_object_unref (task); GTask *task)
return; {
SetupSimHotSwapContext *ctx;
ctx = g_task_get_task_data (task);
if (!mm_shared_qmi_setup_sim_hot_swap_finish (self, res, &ctx->qmi_error))
mm_obj_dbg (self, "couldn't setup SIM hot swap using QMI over MBIM: %s", ctx->qmi_error->message);
sim_hot_swap_complete (task);
}
#endif
static void
enable_subscriber_info_unsolicited_events_ready (MMBroadbandModemMbim *self,
GAsyncResult *res,
GTask *task)
{
SetupSimHotSwapContext *ctx;
ctx = g_task_get_task_data (task);
if (!common_enable_disable_unsolicited_events_finish (self, res, &ctx->subscriber_info_error)) {
mm_obj_dbg (self, "failed to enable subscriber info events: %s", ctx->subscriber_info_error->message);
/* reset setup flags if enabling failed */
self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
common_setup_cleanup_unsolicited_events_sync (self, ctx->device, FALSE);
} }
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
mm_shared_qmi_setup_sim_hot_swap (MM_IFACE_MODEM (self),
(GAsyncReadyCallback)qmi_setup_sim_hot_swap_ready,
task);
#else
sim_hot_swap_complete (task);
#endif
}
static void
modem_setup_sim_hot_swap (MMIfaceModem *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
MbimDevice *device;
GTask *task;
SetupSimHotSwapContext *ctx;
if (!peek_device (self, &device, callback, user_data))
return;
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (SetupSimHotSwapContext);
ctx->device = g_object_ref (device);
g_task_set_task_data (task, ctx, (GDestroyNotify)setup_sim_hot_swap_context_free);
/* Setup flags synchronously, which never fails */
self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
common_setup_cleanup_unsolicited_events_sync (self, ctx->device, TRUE);
/* Enable flags asynchronously, which may fail */
self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO; self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
common_enable_disable_unsolicited_events (self, common_enable_disable_unsolicited_events (self,
(GAsyncReadyCallback)enable_subscriber_info_unsolicited_events_ready, (GAsyncReadyCallback)enable_subscriber_info_unsolicited_events_ready,
task); task);
} }
static void
modem_setup_sim_hot_swap (MMIfaceModem *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
common_setup_cleanup_unsolicited_events (self,
TRUE,
(GAsyncReadyCallback)setup_subscriber_info_unsolicited_events_ready,
task);
}
/*****************************************************************************/ /*****************************************************************************/
/* Enable/Disable unsolicited events (3GPP interface) */ /* Enable/Disable unsolicited events (3GPP interface) */

View File

@@ -9277,6 +9277,8 @@ iface_modem_init (MMIfaceModem *iface)
iface->load_sim_slots_finish = mm_shared_qmi_load_sim_slots_finish; iface->load_sim_slots_finish = mm_shared_qmi_load_sim_slots_finish;
iface->set_primary_sim_slot = mm_shared_qmi_set_primary_sim_slot; iface->set_primary_sim_slot = mm_shared_qmi_set_primary_sim_slot;
iface->set_primary_sim_slot_finish = mm_shared_qmi_set_primary_sim_slot_finish; iface->set_primary_sim_slot_finish = mm_shared_qmi_set_primary_sim_slot_finish;
iface->setup_sim_hot_swap = mm_shared_qmi_setup_sim_hot_swap;
iface->setup_sim_hot_swap_finish = mm_shared_qmi_setup_sim_hot_swap_finish;
/* Create QMI-specific bearer */ /* Create QMI-specific bearer */
iface->create_bearer = modem_create_bearer; iface->create_bearer = modem_create_bearer;

View File

@@ -92,6 +92,12 @@ typedef struct {
gboolean config_active_default; gboolean config_active_default;
GArray *config_list; GArray *config_list;
gint config_active_i; 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; } Private;
static void static void
@@ -111,6 +117,14 @@ private_free (Private *priv)
g_signal_handler_disconnect (priv->loc_client, priv->loc_location_nmea_indication_id); g_signal_handler_disconnect (priv->loc_client, priv->loc_location_nmea_indication_id);
if (priv->loc_client) if (priv->loc_client)
g_object_unref (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_strfreev (priv->loc_assistance_data_servers);
g_slice_free (Private, priv); g_slice_free (Private, priv);
} }
@@ -3601,6 +3615,447 @@ mm_shared_qmi_set_primary_sim_slot (MMIfaceModem *self,
task); task);
} }
/*****************************************************************************/
/* 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 */ /* Location: Set SUPL server */

View File

@@ -179,6 +179,12 @@ void mm_shared_qmi_set_primary_sim_slot (MMIfaceMode
gboolean mm_shared_qmi_set_primary_sim_slot_finish (MMIfaceModem *self, gboolean mm_shared_qmi_set_primary_sim_slot_finish (MMIfaceModem *self,
GAsyncResult *res, GAsyncResult *res,
GError **error); GError **error);
void mm_shared_qmi_setup_sim_hot_swap (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean mm_shared_qmi_setup_sim_hot_swap_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error);
/* Shared QMI location support */ /* Shared QMI location support */