blob: 7356a5e8bd823e4b75b078c457d1134ed04e4199 [file] [log] [blame]
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2012 Google Inc.
* Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include "mm-broadband-modem-qmi.h"
#include "ModemManager.h"
#include "mm-log.h"
#include "mm-errors-types.h"
#include "mm-modem-helpers.h"
#include "mm-modem-helpers-qmi.h"
#include "mm-iface-modem.h"
#include "mm-iface-modem-3gpp.h"
#include "mm-iface-modem-3gpp-ussd.h"
#include "mm-iface-modem-cdma.h"
#include "mm-iface-modem-messaging.h"
#include "mm-iface-modem-location.h"
#include "mm-iface-modem-firmware.h"
#include "mm-iface-modem-signal.h"
#include "mm-iface-modem-oma.h"
#include "mm-shared-qmi.h"
#include "mm-sim-qmi.h"
#include "mm-bearer-qmi.h"
#include "mm-sms-qmi.h"
#include "mm-sms-part-3gpp.h"
#include "mm-sms-part-cdma.h"
static void iface_modem_init (MMIfaceModem *iface);
static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
static void iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface);
static void iface_modem_cdma_init (MMIfaceModemCdma *iface);
static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
static void iface_modem_location_init (MMIfaceModemLocation *iface);
static void iface_modem_oma_init (MMIfaceModemOma *iface);
static void iface_modem_firmware_init (MMIfaceModemFirmware *iface);
static void iface_modem_signal_init (MMIfaceModemSignal *iface);
static void shared_qmi_init (MMSharedQmi *iface);
static MMIfaceModemLocation *iface_modem_location_parent;
static MMIfaceModemMessaging *iface_modem_messaging_parent;
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmi, mm_broadband_modem_qmi, MM_TYPE_BROADBAND_MODEM, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_USSD, iface_modem_3gpp_ussd_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_OMA, iface_modem_oma_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_QMI, shared_qmi_init))
struct _MMBroadbandModemQmiPrivate {
/* Cached device IDs, retrieved by the modem interface when loading device
* IDs, and used afterwards in the 3GPP and CDMA interfaces. */
gchar *imei;
gchar *meid;
gchar *esn;
/* Cached supported frequency bands; in order to handle ANY */
GArray *supported_bands;
/* 3GPP and CDMA share unsolicited events setup/enable/disable/cleanup */
gboolean unsolicited_events_enabled;
gboolean unsolicited_events_setup;
guint event_report_indication_id;
#if defined WITH_NEWEST_QMI_COMMANDS
guint signal_info_indication_id;
#endif /* WITH_NEWEST_QMI_COMMANDS */
/* New devices may not support the legacy DMS UIM commands */
gboolean dms_uim_deprecated;
/* 3GPP/CDMA registration helpers */
gchar *current_operator_id;
gchar *current_operator_description;
gboolean unsolicited_registration_events_enabled;
gboolean unsolicited_registration_events_setup;
guint serving_system_indication_id;
#if defined WITH_NEWEST_QMI_COMMANDS
guint system_info_indication_id;
#endif /* WITH_NEWEST_QMI_COMMANDS */
/* CDMA activation helpers */
MMModemCdmaActivationState activation_state;
guint activation_event_report_indication_id;
GTask *activation_task;
/* Messaging helpers */
gboolean messaging_fallback_at;
gboolean messaging_unsolicited_events_enabled;
gboolean messaging_unsolicited_events_setup;
guint messaging_event_report_indication_id;
/* Location helpers */
MMModemLocationSource enabled_sources;
/* Oma helpers */
gboolean oma_unsolicited_events_enabled;
gboolean oma_unsolicited_events_setup;
guint oma_event_report_indication_id;
/* Firmware helpers */
gboolean firmware_list_preloaded;
GList *firmware_list;
MMFirmwareProperties *current_firmware;
/* For notifying when the qmi-proxy connection is dead */
guint qmi_device_removed_id;
};
/*****************************************************************************/
static QmiClient *
shared_qmi_peek_client (MMSharedQmi *self,
QmiService service,
MMPortQmiFlag flag,
GError **error)
{
MMPortQmi *port;
QmiClient *client;
port = mm_base_modem_peek_port_qmi (MM_BASE_MODEM (self));
if (!port) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't peek QMI port");
return NULL;
}
client = mm_port_qmi_peek_client (port, service, flag);
if (!client)
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't peek client for service '%s'",
qmi_service_get_string (service));
return client;
}
/*****************************************************************************/
/* Create Bearer (Modem interface) */
static MMBaseBearer *
modem_create_bearer_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
modem_create_bearer (MMIfaceModem *self,
MMBearerProperties *properties,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBaseBearer *bearer;
GTask *task;
/* We just create a MMBearerQmi */
bearer = mm_bearer_qmi_new (MM_BROADBAND_MODEM_QMI (self), properties);
task = g_task_new (self, NULL, callback, user_data);
g_task_return_pointer (task, bearer, g_object_unref);
g_object_unref (task);
}
/*****************************************************************************/
/* Manufacturer loading (Modem interface) */
static gchar *
modem_load_manufacturer_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
dms_get_manufacturer_ready (QmiClientDms *client,
GAsyncResult *res,
GTask *task)
{
QmiMessageDmsGetManufacturerOutput *output = NULL;
GError *error = NULL;
output = qmi_client_dms_get_manufacturer_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
} else if (!qmi_message_dms_get_manufacturer_output_get_result (output, &error)) {
g_prefix_error (&error, "Couldn't get Manufacturer: ");
g_task_return_error (task, error);
} else {
const gchar *str;
qmi_message_dms_get_manufacturer_output_get_manufacturer (output, &str, NULL);
g_task_return_pointer (task, g_strdup (str), g_free);
}
if (output)
qmi_message_dms_get_manufacturer_output_unref (output);
g_object_unref (task);
}
static void
modem_load_manufacturer (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
QmiClient *client = NULL;
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
QMI_SERVICE_DMS, &client,
callback, user_data))
return;
mm_dbg ("loading manufacturer...");
qmi_client_dms_get_manufacturer (QMI_CLIENT_DMS (client),
NULL,
5,
NULL,
(GAsyncReadyCallback)dms_get_manufacturer_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Model loading (Modem interface) */
static gchar *
modem_load_model_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
dms_get_model_ready (QmiClientDms *client,
GAsyncResult *res,
GTask *task)
{
QmiMessageDmsGetModelOutput *output = NULL;
GError *error = NULL;
output = qmi_client_dms_get_model_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
} else if (!qmi_message_dms_get_model_output_get_result (output, &error)) {
g_prefix_error (&error, "Couldn't get Model: ");
g_task_return_error (task, error);
} else {
const gchar *str;
qmi_message_dms_get_model_output_get_model (output, &str, NULL);
g_task_return_pointer (task, g_strdup (str), g_free);
}
if (output)
qmi_message_dms_get_model_output_unref (output);
g_object_unref (task);
}
static void
modem_load_model (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
QmiClient *client = NULL;
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
QMI_SERVICE_DMS, &client,
callback, user_data))
return;
mm_dbg ("loading model...");
qmi_client_dms_get_model (QMI_CLIENT_DMS (client),
NULL,
5,
NULL,
(GAsyncReadyCallback)dms_get_model_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Revision loading (Modem interface) */
static gchar *
modem_load_revision_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
dms_get_revision_ready (QmiClientDms *client,
GAsyncResult *res,
GTask *task)
{
QmiMessageDmsGetRevisionOutput *output = NULL;
GError *error = NULL;
output = qmi_client_dms_get_revision_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
} else if (!qmi_message_dms_get_revision_output_get_result (output, &error)) {
g_prefix_error (&error, "Couldn't get Revision: ");
g_task_return_error (task, error);
} else {
const gchar *str;
qmi_message_dms_get_revision_output_get_revision (output, &str, NULL);
g_task_return_pointer (task, g_strdup (str), g_free);
}
if (output)
qmi_message_dms_get_revision_output_unref (output);
g_object_unref (task);
}
static void
modem_load_revision (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
QmiClient *client = NULL;
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
QMI_SERVICE_DMS, &client,
callback, user_data))
return;
mm_dbg ("loading revision...");
qmi_client_dms_get_revision (QMI_CLIENT_DMS (client),
NULL,
5,
NULL,
(GAsyncReadyCallback)dms_get_revision_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Hardware Revision loading (Modem interface) */
static gchar *
modem_load_hardware_revision_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
dms_get_hardware_revision_ready (QmiClientDms *client,
GAsyncResult *res,
GTask *task)
{
QmiMessageDmsGetHardwareRevisionOutput *output = NULL;
GError *error = NULL;
output = qmi_client_dms_get_hardware_revision_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
} else if (!qmi_message_dms_get_hardware_revision_output_get_result (output, &error)) {
g_prefix_error (&error, "Couldn't get Hardware Revision: ");
g_task_return_error (task, error);
} else {
const gchar *str;
qmi_message_dms_get_hardware_revision_output_get_revision (output, &str, NULL);
g_task_return_pointer (task, g_strdup (str), g_free);
}
if (output)
qmi_message_dms_get_hardware_revision_output_unref (output);
g_object_unref (task);
}
static void
modem_load_hardware_revision (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
QmiClient *client = NULL;
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
QMI_SERVICE_DMS, &client,
callback, user_data))
return;
mm_dbg ("loading hardware revision...");
qmi_client_dms_get_hardware_revision (QMI_CLIENT_DMS (client),
NULL,
5,
NULL,
(GAsyncReadyCallback)dms_get_hardware_revision_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Equipment Identifier loading (Modem interface) */
static gchar *
modem_load_equipment_identifier_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
dms_get_ids_ready (QmiClientDms *client,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModemQmi *self;
QmiMessageDmsGetIdsOutput *output = NULL;
GError *error = NULL;
const gchar *str;
guint len;
output = qmi_client_dms_get_ids_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (!qmi_message_dms_get_ids_output_get_result (output, &error)) {
g_prefix_error (&error, "Couldn't get IDs: ");
g_task_return_error (task, error);
g_object_unref (task);
qmi_message_dms_get_ids_output_unref (output);
return;
}
self = g_task_get_source_object (task);
/* In order:
* If we have a IMEI, use it...
* Otherwise, if we have a ESN, use it...
* Otherwise, if we have a MEID, use it...
* Otherwise, 'unknown'
*/
if (qmi_message_dms_get_ids_output_get_imei (output, &str, NULL) &&
str[0] != '\0') {
g_free (self->priv->imei);
self->priv->imei = g_strdup (str);
}
if (qmi_message_dms_get_ids_output_get_esn (output, &str, NULL) &&
str[0] != '\0') {
g_clear_pointer (&self->priv->esn, g_free);
len = strlen (str);
if (len == 7)
self->priv->esn = g_strdup_printf ("0%s", str); /* zero-pad to 8 chars */
else if (len == 8)
self->priv->esn = g_strdup (str);
else
mm_dbg ("Invalid ESN reported: '%s' (unexpected length)", str);
}
if (qmi_message_dms_get_ids_output_get_meid (output, &str, NULL) &&
str[0] != '\0') {
g_clear_pointer (&self->priv->meid, g_free);
len = strlen (str);
if (len == 14)
self->priv->meid = g_strdup (str);
else
mm_dbg ("Invalid MEID reported: '%s' (unexpected length)", str);
}
if (self->priv->imei)
str = self->priv->imei;
else if (self->priv->esn)
str = self->priv->esn;
else if (self->priv->meid)
str = self->priv->meid;
else
str = "unknown";
g_task_return_pointer (task, g_strdup (str), g_free);
g_object_unref (task);
qmi_message_dms_get_ids_output_unref (output);
}
static void
modem_load_equipment_identifier (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
QmiClient *client = NULL;
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
QMI_SERVICE_DMS, &client,
callback, user_data))
return;
mm_dbg ("loading equipment identifier...");
qmi_client_dms_get_ids (QMI_CLIENT_DMS (client),
NULL,
5,
NULL,
(GAsyncReadyCallback)dms_get_ids_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Device identifier loading (Modem interface) */
static gchar *
modem_load_device_identifier_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
modem_load_device_identifier (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
gchar *device_identifier;
GTask *task;
mm_dbg ("loading device identifier...");
/* Just use dummy ATI/ATI1 replies, all the other internal info should be
* enough for uniqueness */
device_identifier = mm_broadband_modem_create_device_identifier (MM_BROADBAND_MODEM (self), "", "");
task = g_task_new (self, NULL, callback, user_data);
g_task_return_pointer (task, device_identifier, g_free);
g_object_unref (task);
}
/*****************************************************************************/
/* Own Numbers loading (Modem interface) */
static GStrv
modem_load_own_numbers_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
dms_get_msisdn_ready (QmiClientDms *client,
GAsyncResult *res,
GTask *task)
{
QmiMessageDmsGetMsisdnOutput *output = NULL;
GError *error = NULL;
output = qmi_client_dms_get_msisdn_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
} else if (!qmi_message_dms_get_msisdn_output_get_result (output, &error)) {
g_prefix_error (&error, "Couldn't get MSISDN: ");
g_task_return_error (task, error);
} else {
const gchar *str = NULL;
GStrv numbers;
qmi_message_dms_get_msisdn_output_get_msisdn (output, &str, NULL);
numbers = g_new0 (gchar *, 2);
numbers[0] = g_strdup (str);
g_task_return_pointer (task, numbers, (GDestroyNotify)g_strfreev);
}
if (output)
qmi_message_dms_get_msisdn_output_unref (output);
g_object_unref (task);
}
static void
modem_load_own_numbers (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
QmiClient *client = NULL;
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
QMI_SERVICE_DMS, &client,
callback, user_data))
return;
mm_dbg ("loading own numbers...");
qmi_client_dms_get_msisdn (QMI_CLIENT_DMS (client),
NULL,
5,
NULL,
(GAsyncReadyCallback)dms_get_msisdn_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Check if unlock required (Modem interface) */
typedef enum {
LOAD_UNLOCK_REQUIRED_STEP_FIRST,
LOAD_UNLOCK_REQUIRED_STEP_CDMA,
LOAD_UNLOCK_REQUIRED_STEP_DMS,
LOAD_UNLOCK_REQUIRED_STEP_UIM,
} LoadUnlockRequiredStep;
typedef struct {
LoadUnlockRequiredStep step;
QmiClient *dms;
QmiClient *uim;
} LoadUnlockRequiredContext;
static MMModemLock
modem_load_unlock_required_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GError *inner_error = NULL;
gssize value;
value = g_task_propagate_int (G_TASK (res), &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return MM_MODEM_LOCK_UNKNOWN;
}
return (MMModemLock)value;
}
static void load_unlock_required_context_step (GTask *task);
/* Used also when loading unlock retries left */
static gboolean
uim_get_card_status_output_parse (QmiMessageUimGetCardStatusOutput *output,
MMModemLock *o_lock,
guint *o_pin1_retries,
guint *o_puk1_retries,
guint *o_pin2_retries,
guint *o_puk2_retries,
GError **error)
{
GArray *cards;
QmiMessageUimGetCardStatusOutputCardStatusCardsElement *card;
QmiMessageUimGetCardStatusOutputCardStatusCardsElementApplicationsElement *app;
MMModemLock lock = MM_MODEM_LOCK_UNKNOWN;
guint i;
gint card_i = -1;
gint application_j = -1;
guint n_absent = 0;
guint n_error = 0;
guint n_invalid = 0;
/* This command supports MULTIPLE cards with MULTIPLE applications each. For our
* purposes, we're going to consider as the SIM to use the first card present
* with a SIM/USIM application. */
if (!qmi_message_uim_get_card_status_output_get_result (output, error)) {
g_prefix_error (error, "QMI operation failed: ");
return FALSE;
}
qmi_message_uim_get_card_status_output_get_card_status (
output,
NULL, /* index_gw_primary */
NULL, /* index_1x_primary */
NULL, /* index_gw_secondary */
NULL, /* index_1x_secondary */
&cards,
NULL);
if (cards->len == 0) {
g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_FAILED,
"No cards reported");
return FALSE;
}
if (cards->len > 1)
mm_dbg ("Multiple cards reported: %u", cards->len);
/* All KNOWN applications in all cards will need to be in READY state for us
* to consider UNLOCKED */
for (i = 0; i < cards->len; i++) {
card = &g_array_index (cards, QmiMessageUimGetCardStatusOutputCardStatusCardsElement, i);
switch (card->card_state) {
case QMI_UIM_CARD_STATE_PRESENT: {
guint j;
gboolean sim_usim_found = FALSE;
if (card->applications->len == 0) {
mm_dbg ("No applications reported in card [%u]", i);
n_invalid++;
break;
}
if (card->applications->len > 1)
mm_dbg ("Multiple applications reported in card [%u]: %u", i, card->applications->len);
for (j = 0; j < card->applications->len; j++) {
app = &g_array_index (card->applications, QmiMessageUimGetCardStatusOutputCardStatusCardsElementApplicationsElement, j);
if (app->type == QMI_UIM_CARD_APPLICATION_TYPE_UNKNOWN) {
mm_dbg ("Unknown application [%u] found in card [%u]: %s. Ignored.",
j, i, qmi_uim_card_application_state_get_string (app->state));
continue;
}
mm_dbg ("Application '%s' [%u] in card [%u]: %s",
qmi_uim_card_application_type_get_string (app->type), j, i, qmi_uim_card_application_state_get_string (app->state));
if (app->type == QMI_UIM_CARD_APPLICATION_TYPE_SIM || app->type == QMI_UIM_CARD_APPLICATION_TYPE_USIM) {
/* We found the card/app pair to use! Only keep the first found,
* but still, keep on looping to log about the remaining ones */
if (card_i < 0 && application_j < 0) {
card_i = i;
application_j = j;
}
sim_usim_found = TRUE;
}
}
if (!sim_usim_found) {
mm_dbg ("No SIM/USIM application found in card [%u]", i);
n_invalid++;
}
break;
}
case QMI_UIM_CARD_STATE_ABSENT:
mm_dbg ("Card '%u' is absent", i);
n_absent++;
break;
case QMI_UIM_CARD_STATE_ERROR:
default:
n_error++;
if (qmi_uim_card_error_get_string (card->error_code) != NULL)
mm_warn ("Card '%u' is unusable: %s", i, qmi_uim_card_error_get_string (card->error_code));
else
mm_warn ("Card '%u' is unusable: unknown error", i);
break;
}
/* go on to next card */
}
/* If we found no card/app to use, we need to report an error */
if (card_i < 0 || application_j < 0) {
/* If not a single card found, report SIM not inserted */
if (n_absent > 0 && !n_error && !n_invalid)
g_set_error (error,
MM_MOBILE_EQUIPMENT_ERROR,
MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED,
"No card found");
else if (n_error > 0)
g_set_error (error,
MM_MOBILE_EQUIPMENT_ERROR,
MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG,
"Card error");
else
g_set_error (error,
MM_MOBILE_EQUIPMENT_ERROR,
MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE,
"Card failure: %u absent, %u errors, %u invalid",
n_absent, n_error, n_invalid);
return FALSE;
}
/* Get card/app to use */
card = &g_array_index (cards, QmiMessageUimGetCardStatusOutputCardStatusCardsElement, card_i);
app = &g_array_index (card->applications, QmiMessageUimGetCardStatusOutputCardStatusCardsElementApplicationsElement, application_j);
/* If card not ready yet, return RETRY error.
* If the application state reports needing PIN/PUk, consider that ready as
* well, and let the logic fall down to check PIN1/PIN2. */
if (app->state != QMI_UIM_CARD_APPLICATION_STATE_READY &&
app->state != QMI_UIM_CARD_APPLICATION_STATE_PIN1_OR_UPIN_PIN_REQUIRED &&
app->state != QMI_UIM_CARD_APPLICATION_STATE_PUK1_OR_UPIN_PUK_REQUIRED &&
app->state != QMI_UIM_CARD_APPLICATION_STATE_PIN1_BLOCKED) {
mm_dbg ("Neither SIM nor USIM are ready");
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_RETRY,
"SIM not ready yet (retry)");
return FALSE;
}
/* Report retries if requested to do so */
if (o_pin1_retries)
*o_pin1_retries = app->pin1_retries;
if (o_puk1_retries)
*o_puk1_retries = app->puk1_retries;
if (o_pin2_retries)
*o_pin2_retries = app->pin2_retries;
if (o_puk2_retries)
*o_puk2_retries = app->puk2_retries;
/* Early bail out if lock status isn't wanted at this point, so that we
* don't fail with an error the unlock retries check */
if (!o_lock)
return TRUE;
/* Card is ready, what's the lock status? */
/* PIN1 */
switch (app->pin1_state) {
case QMI_UIM_PIN_STATE_PERMANENTLY_BLOCKED:
g_set_error (error,
MM_MOBILE_EQUIPMENT_ERROR,
MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG,
"SIM PIN/PUK permanently blocked");
return FALSE;
case QMI_UIM_PIN_STATE_ENABLED_NOT_VERIFIED:
lock = MM_MODEM_LOCK_SIM_PIN;
break;
case QMI_UIM_PIN_STATE_BLOCKED:
lock = MM_MODEM_LOCK_SIM_PUK;
break;
case QMI_UIM_PIN_STATE_DISABLED:
case QMI_UIM_PIN_STATE_ENABLED_VERIFIED:
lock = MM_MODEM_LOCK_NONE;
break;
default:
g_set_error (error,
MM_MOBILE_EQUIPMENT_ERROR,
MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG,
"Unknown SIM PIN/PUK status");
return FALSE;
}
/* PIN2 */
if (lock == MM_MODEM_LOCK_NONE) {
switch (app->pin2_state) {
case QMI_UIM_PIN_STATE_ENABLED_NOT_VERIFIED:
lock = MM_MODEM_LOCK_SIM_PIN2;
break;
case QMI_UIM_PIN_STATE_PERMANENTLY_BLOCKED:
mm_warn ("PUK2 permanently blocked");
case QMI_UIM_PIN_STATE_BLOCKED:
lock = MM_MODEM_LOCK_SIM_PUK2;
break;
case QMI_UIM_PIN_STATE_DISABLED:
case QMI_UIM_PIN_STATE_ENABLED_VERIFIED:
break;
default:
mm_warn ("Unknown SIM PIN2/PUK2 status");
break;
}
}
*o_lock = lock;
return TRUE;
}
static void
unlock_required_uim_get_card_status_ready (QmiClientUim *client,
GAsyncResult *res,
GTask *task)
{
QmiMessageUimGetCardStatusOutput *output;
GError *error = NULL;
MMModemLock lock = MM_MODEM_LOCK_UNKNOWN;
output = qmi_client_uim_get_card_status_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (!uim_get_card_status_output_parse (output,
&lock,
NULL, NULL, NULL, NULL,
&error)) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
} else
g_task_return_int (task, lock);
g_object_unref (task);
qmi_message_uim_get_card_status_output_unref (output);
}
static void
dms_uim_get_pin_status_ready (QmiClientDms *client,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModemQmi *self;
LoadUnlockRequiredContext *ctx;
QmiMessageDmsUimGetPinStatusOutput *output;
GError *error = NULL;
MMModemLock lock = MM_MODEM_LOCK_UNKNOWN;
QmiDmsUimPinStatus current_status;
output = qmi_client_dms_uim_get_pin_status_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
}
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
if (!qmi_message_dms_uim_get_pin_status_output_get_result (output, &error)) {
/* We get InvalidQmiCommand on newer devices which don't like the legacy way */
if (g_error_matches (error,
QMI_PROTOCOL_ERROR,
QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND) ||
g_error_matches (error,
QMI_PROTOCOL_ERROR,
QMI_PROTOCOL_ERROR_NOT_SUPPORTED)) {
g_error_free (error);
qmi_message_dms_uim_get_pin_status_output_unref (output);
/* Flag that the command is unsupported, and try with the new way */
self->priv->dms_uim_deprecated = TRUE;
ctx->step++;
load_unlock_required_context_step (task);
return;
}
/* Internal and uim-uninitialized errors are retry-able before being fatal */
if (g_error_matches (error,
QMI_PROTOCOL_ERROR,
QMI_PROTOCOL_ERROR_INTERNAL) ||
g_error_matches (error,
QMI_PROTOCOL_ERROR,
QMI_PROTOCOL_ERROR_UIM_UNINITIALIZED)) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_RETRY,
"Couldn't get PIN status (retry): %s",
error->message);
g_object_unref (task);
g_error_free (error);
qmi_message_dms_uim_get_pin_status_output_unref (output);
return;
}
/* Other errors, just propagate them */
g_prefix_error (&error, "Couldn't get PIN status: ");
g_task_return_error (task, error);
g_object_unref (task);
qmi_message_dms_uim_get_pin_status_output_unref (output);
return;
}
/* Command succeeded, process results */
if (qmi_message_dms_uim_get_pin_status_output_get_pin1_status (
output,
&current_status,
NULL, /* verify_retries_left */
NULL, /* unblock_retries_left */
NULL))
lock = mm_modem_lock_from_qmi_uim_pin_status (current_status, TRUE);
if (lock == MM_MODEM_LOCK_NONE &&
qmi_message_dms_uim_get_pin_status_output_get_pin2_status (
output,
&current_status,
NULL, /* verify_retries_left */
NULL, /* unblock_retries_left */
NULL)) {
MMModemLock lock2;
/* We only use the PIN2 status if it isn't unknown */
lock2 = mm_modem_lock_from_qmi_uim_pin_status (current_status, FALSE);
if (lock2 != MM_MODEM_LOCK_UNKNOWN)
lock = lock2;
}
/* We're done! */
g_task_return_int (task, lock);
g_object_unref (task);
}
static void
load_unlock_required_context_step (GTask *task)
{
MMBroadbandModemQmi *self;
LoadUnlockRequiredContext *ctx;
GError *error = NULL;
QmiClient *client;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
switch (ctx->step) {
case LOAD_UNLOCK_REQUIRED_STEP_FIRST:
ctx->step++;
/* Go on to next step */
case LOAD_UNLOCK_REQUIRED_STEP_CDMA:
/* CDMA-only modems don't need this */
if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self))) {
mm_dbg ("Skipping unlock check in CDMA-only modem...");
g_task_return_int (task, MM_MODEM_LOCK_NONE);
g_object_unref (task);
return;
}
ctx->step++;
/* Go on to next step */
case LOAD_UNLOCK_REQUIRED_STEP_DMS:
if (!self->priv->dms_uim_deprecated) {
/* Failure to get DMS client is hard really */
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
QMI_SERVICE_DMS,
MM_PORT_QMI_FLAG_DEFAULT,
&error);
if (!client) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
mm_dbg ("loading unlock required (DMS)...");
qmi_client_dms_uim_get_pin_status (QMI_CLIENT_DMS (client),
NULL,
5,
NULL,
(GAsyncReadyCallback) dms_uim_get_pin_status_ready,
task);
return;
}
ctx->step++;
/* Go on to next step */
case LOAD_UNLOCK_REQUIRED_STEP_UIM:
/* Failure to get UIM client at this point is hard as well */
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
QMI_SERVICE_UIM,
MM_PORT_QMI_FLAG_DEFAULT,
&error);
if (!client) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
mm_dbg ("loading unlock required (UIM)...");
qmi_client_uim_get_card_status (QMI_CLIENT_UIM (client),
NULL,
5,
NULL,
(GAsyncReadyCallback) unlock_required_uim_get_card_status_ready,
task);
return;
}
}
static void
modem_load_unlock_required (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
LoadUnlockRequiredContext *ctx;
GTask *task;
ctx = g_new0 (LoadUnlockRequiredContext, 1);
ctx->step = LOAD_UNLOCK_REQUIRED_STEP_FIRST;
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, ctx, g_free);
load_unlock_required_context_step (task);
}
/*****************************************************************************/
/* Check if unlock retries (Modem interface) */
static MMUnlockRetries *
modem_load_unlock_retries_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return MM_UNLOCK_RETRIES (g_task_propagate_pointer (G_TASK (res), error));
}
static void
unlock_retries_uim_get_card_status_ready (QmiClientUim *client,
GAsyncResult *res,
GTask *task)
{
QmiMessageUimGetCardStatusOutput *output;
GError *error = NULL;
guint pin1_retries = 0;
guint puk1_retries = 0;
guint pin2_retries = 0;
guint puk2_retries = 0;
MMUnlockRetries *retries;
output = qmi_client_uim_get_card_status_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (!uim_get_card_status_output_parse (output,
NULL,
&pin1_retries, &puk1_retries,
&pin2_retries, &puk2_retries,
&error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
retries = mm_unlock_retries_new ();
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, pin1_retries);
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, puk1_retries);
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, pin2_retries);
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, puk2_retries);
qmi_message_uim_get_card_status_output_unref (output);
g_task_return_pointer (task, retries, g_object_unref);
g_object_unref (task);
}
static void
uim_load_unlock_retries (MMBroadbandModemQmi *self,
GTask *task)
{
QmiClient *client;
GError *error = NULL;
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
QMI_SERVICE_UIM,
MM_PORT_QMI_FLAG_DEFAULT,
&error);
if (!client) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
qmi_client_uim_get_card_status (QMI_CLIENT_UIM (client),
NULL,
5,
NULL,
(GAsyncReadyCallback) unlock_retries_uim_get_card_status_ready,
task);
}
static void
unlock_retries_dms_uim_get_pin_status_ready (QmiClientDms *client,
GAsyncResult *res,
GTask *task)
{
QmiMessageDmsUimGetPinStatusOutput *output;
GError *error = NULL;
MMBroadbandModemQmi *self;
MMUnlockRetries *retries;
guint8 verify_retries_left;
guint8 unblock_retries_left;
self = g_task_get_source_object (task);
output = qmi_client_dms_uim_get_pin_status_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (!qmi_message_dms_uim_get_pin_status_output_get_result (output, &error)) {
qmi_message_dms_uim_get_pin_status_output_unref (output);
/* We get InvalidQmiCommand on newer devices which don't like the legacy way */
if (g_error_matches (error,
QMI_PROTOCOL_ERROR,
QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND)) {
g_error_free (error);
/* Flag that the command is unsupported, and try with the new way */
self->priv->dms_uim_deprecated = TRUE;
uim_load_unlock_retries (self, task);
return;
}
g_prefix_error (&error, "Couldn't get unlock retries: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
}
retries = mm_unlock_retries_new ();
if (qmi_message_dms_uim_get_pin_status_output_get_pin1_status (
output,
NULL, /* current_status */
&verify_retries_left,
&unblock_retries_left,
NULL)) {
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, verify_retries_left);
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, unblock_retries_left);
}
if (qmi_message_dms_uim_get_pin_status_output_get_pin2_status (
output,
NULL, /* current_status */
&verify_retries_left,
&unblock_retries_left,
NULL)) {
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, verify_retries_left);
mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, unblock_retries_left);
}
qmi_message_dms_uim_get_pin_status_output_unref (output);
g_task_return_pointer (task, retries, g_object_unref);
g_object_unref (task);
}
static void
dms_uim_load_unlock_retries (MMBroadbandModemQmi *self,
GTask *task)
{
QmiClient *client;
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
QMI_SERVICE_DMS,
MM_PORT_QMI_FLAG_DEFAULT,
NULL);
if (!client) {
/* Very unlikely that this will ever happen, but anyway, try with
* UIM service instead */
uim_load_unlock_retries (self, task);
return;
}
qmi_client_dms_uim_get_pin_status (QMI_CLIENT_DMS (client),
NULL,
5,
NULL,
(GAsyncReadyCallback) unlock_retries_dms_uim_get_pin_status_ready,
task);
}
static void
modem_load_unlock_retries (MMIfaceModem *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModemQmi *self;
GTask *task;
self = MM_BROADBAND_MODEM_QMI (_self);
task = g_task_new (self, NULL, callback, user_data);
mm_dbg ("loading unlock retries...");
if (!self->priv->dms_uim_deprecated)
dms_uim_load_unlock_retries (MM_BROADBAND_MODEM_QMI (self), task);
else
uim_load_unlock_retries (MM_BROADBAND_MODEM_QMI (self), task);
}
/*****************************************************************************/
/* Load supported IP families (Modem interface) */
static MMBearerIpFamily
modem_load_supported_ip_families_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GError *inner_error = NULL;
gssize value;
value = g_task_propagate_int (G_TASK (res), &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return MM_BEARER_IP_FAMILY_NONE;
}
return (MMBearerIpFamily)value;
}
static void
modem_load_supported_ip_families (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self)))
/* CDMA-only: IPv4 */
g_task_return_int (task, MM_BEARER_IP_FAMILY_IPV4);
else
/* Assume IPv4 + IPv6 supported */
g_task_return_int (task,
MM_BEARER_IP_FAMILY_IPV4 |
MM_BEARER_IP_FAMILY_IPV6 |
MM_BEARER_IP_FAMILY_IPV4V6);
g_object_unref (task);
}
/*****************************************************************************/
/* Load signal quality (Modem interface) */
/* Limit the value betweeen [-113,-51] and scale it to a percentage */
#define STRENGTH_TO_QUALITY(strength) \
(guint8)(100 - ((CLAMP (strength, -113, -51) + 51) * 100 / (-113 + 51)))
static gboolean
qmi_dbm_valid (gint8 dbm, QmiNasRadioInterface radio_interface)
{
/* Different radio interfaces have different signal quality bounds */
switch (radio_interface) {
case QMI_NAS_RADIO_INTERFACE_CDMA_1X:
case QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO:
return (dbm > -125 && dbm < -30);
case QMI_NAS_RADIO_INTERFACE_UMTS:
return (dbm > -125 && dbm < -30);
default:
break;
}
return TRUE;
}
static guint
load_signal_quality_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GError *inner_error = NULL;
gssize value;
value = g_task_propagate_int (G_TASK (res), &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return 0;
}
return value;
}
#if defined WITH_NEWEST_QMI_COMMANDS
static gboolean
common_signal_info_get_quality (gint8 cdma1x_rssi,
gint8 evdo_rssi,
gint8 gsm_rssi,
gint8 wcdma_rssi,
gint8 lte_rssi,
guint8 *out_quality,
MMModemAccessTechnology *out_act)
{
gint8 rssi_max = -125;
QmiNasRadioInterface signal_info_radio_interface = QMI_NAS_RADIO_INTERFACE_UNKNOWN;
g_assert (out_quality != NULL);
g_assert (out_act != NULL);
/* We do not report per-technology signal quality, so just get the highest
* one of the ones reported. TODO: When several technologies are in use, if
* the indication only contains the data of the one which passed a threshold
* value, we'll need to have an internal cache of per-technology values, in
* order to report always the one with the maximum value. */
if (cdma1x_rssi < 0) {
mm_dbg ("RSSI (CDMA): %d dBm", cdma1x_rssi);
if (qmi_dbm_valid (cdma1x_rssi, QMI_NAS_RADIO_INTERFACE_CDMA_1X)) {
rssi_max = MAX (cdma1x_rssi, rssi_max);
signal_info_radio_interface = QMI_NAS_RADIO_INTERFACE_CDMA_1X;
}
}
if (evdo_rssi < 0) {
mm_dbg ("RSSI (HDR): %d dBm", evdo_rssi);
if (qmi_dbm_valid (evdo_rssi, QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO)) {
rssi_max = MAX (evdo_rssi, rssi_max);
signal_info_radio_interface = QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO;
}
}
if (gsm_rssi < 0) {
mm_dbg ("RSSI (GSM): %d dBm", gsm_rssi);
if (qmi_dbm_valid (gsm_rssi, QMI_NAS_RADIO_INTERFACE_GSM)) {
rssi_max = MAX (gsm_rssi, rssi_max);
signal_info_radio_interface = QMI_NAS_RADIO_INTERFACE_GSM;
}
}
if (wcdma_rssi < 0) {
mm_dbg ("RSSI (WCDMA): %d dBm", wcdma_rssi);
if (qmi_dbm_valid (wcdma_rssi, QMI_NAS_RADIO_INTERFACE_UMTS)) {
rssi_max = MAX (wcdma_rssi, rssi_max);
signal_info_radio_interface = QMI_NAS_RADIO_INTERFACE_UMTS;
}
}
if (lte_rssi < 0) {
mm_dbg ("RSSI (LTE): %d dBm", lte_rssi);
if (qmi_dbm_valid (lte_rssi, QMI_NAS_RADIO_INTERFACE_LTE)) {
rssi_max = MAX (lte_rssi, rssi_max);
signal_info_radio_interface = QMI_NAS_RADIO_INTERFACE_LTE;
}
}
if (rssi_max < 0 && rssi_max > -125) {
/* This RSSI comes as negative dBms */
*out_quality = STRENGTH_TO_QUALITY (rssi_max);
*out_act = mm_modem_access_technology_from_qmi_radio_interface (signal_info_radio_interface);
mm_dbg ("RSSI: %d dBm --> %u%%", rssi_max, *out_quality);
return TRUE;
}
return FALSE;
}
static gboolean
signal_info_get_quality (MMBroadbandModemQmi *self,
QmiMessageNasGetSignalInfoOutput *output,
guint8 *out_quality,
MMModemAccessTechnology *out_act)
{
gint8 cdma1x_rssi = 0;
gint8 evdo_rssi = 0;
gint8 gsm_rssi = 0;
gint8 wcdma_rssi = 0;
gint8 lte_rssi = 0;
qmi_message_nas_get_signal_info_output_get_cdma_signal_strength (output, &cdma1x_rssi, NULL, NULL);
qmi_message_nas_get_signal_info_output_get_hdr_signal_strength (output, &evdo_rssi, NULL, NULL, NULL, NULL);
qmi_message_nas_get_signal_info_output_get_gsm_signal_strength (output, &gsm_rssi, NULL);
qmi_message_nas_get_signal_info_output_get_wcdma_signal_strength (output, &wcdma_rssi, NULL, NULL);
qmi_message_nas_get_signal_info_output_get_lte_signal_strength (output, &lte_rssi, NULL, NULL, NULL, NULL);
return common_signal_info_get_quality (cdma1x_rssi, evdo_rssi, gsm_rssi, wcdma_rssi, lte_rssi, out_quality, out_act);
}
static void
get_signal_info_ready (QmiClientNas *client,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModemQmi *self;
QmiMessageNasGetSignalInfoOutput *output;
GError *error = NULL;
guint8 quality = 0;
MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
output = qmi_client_nas_get_signal_info_finish (client, res, &error);
if (!output) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (!qmi_message_nas_get_signal_info_output_get_result (output, &error)) {
qmi_message_nas_get_signal_info_output_unref (output);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
self = g_task_get_source_object (task);
if (!signal_info_get_quality (self, output, &quality, &act)) {
qmi_message_nas_get_signal_info_output_unref (output);
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Signal info reported invalid signal strength.");
g_object_unref (task);
return;
}
/* We update the access technologies directly here when loading signal
* quality. It goes a bit out of context, but we can do it nicely */
mm_iface_modem_update_access_technologies (
MM_IFACE_MODEM (self),
act,
(MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK | MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK));
g_task_return_int (task, quality);
g_object_unref (task);
qmi_message_nas_get_signal_info_output_unref (output);
}
#endif /* WITH_NEWEST_QMI_COMMANDS */
static gboolean
signal_strength_get_quality_and_access_tech (MMBroadbandModemQmi *self,
QmiMessageNasGetSignalStrengthOutput *output,
guint8 *o_quality,
MMModemAccessTechnology *o_act)
{
GArray *array = NULL;
gint8 signal_max = 0;
QmiNasRadioInterface main_interface;
MMModemAccessTechnology act;
/* We do not report per-technology signal quality, so just get the highest
* one of the ones reported. */
/* The mandatory one is always present */
qmi_message_nas_get_signal_strength_output_get_signal_strength (output, &signal_max, &main_interface, NULL);
mm_dbg ("Signal strength (%s): %d dBm",
qmi_nas_radio_interface_get_string (main_interface),
signal_max);
/* Treat results as invalid if main signal strength is invalid */
if (!qmi_dbm_valid (signal_max, main_interface))
return FALSE;
act = mm_modem_access_technology_from_qmi_radio_interface (main_interface);
/* On multimode devices we may get more */
if (qmi_message_nas_get_signal_strength_output_get_strength_list (output, &array, NULL)) {
guint i;
for (i = 0; i < array->len; i++) {
QmiMessageNasGetSignalStrengthOutputStrengthListElement *element;
element = &g_array_index (array, QmiMessageNasGetSignalStrengthOutputStrengthListElement, i);
mm_dbg ("Signal strength (%s): %d dBm",
qmi_nas_radio_interface_get_string (element->radio_interface),
element->strength);
if (qmi_dbm_valid (element->strength, element->radio_interface)) {
signal_max = MAX (element->strength, signal_max);
act |= mm_modem_access_technology_from_qmi_radio_interface (element->radio_interface);
}
}
}
if (signal_max < 0) {
/* This signal strength comes as negative dBms */
*o_quality = STRENGTH_TO_QUALITY (signal_max);
*o_act = act;
mm_dbg ("Signal strength: %d dBm --> %u%%", signal_max, *o_quality);
}
return (signal_max < 0);
}
static void
get_signal_strength_ready (QmiClientNas *client,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModemQmi *self;
QmiMessageNasGetSignalStrengthOutput *output;
GError *error = NULL;
guint8 quality = 0;
MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
output = qmi_client_nas_get_signal_strength_finish (client, res, &error);
if (!output) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (!qmi_message_nas_get_signal_strength_output_get_result (output, &error)) {
qmi_message_nas_get_signal_strength_output_unref (output);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
self = g_task_get_source_object (task);
if (!signal_strength_get_quality_and_access_tech (self, output, &quality, &act)) {
qmi_message_nas_get_signal_strength_output_unref (output);
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"GetSignalStrength signal strength invalid.");
g_object_unref (task);
return;
}
/* We update the access technologies directly here when loading signal
* quality. It goes a bit out of context, but we can do it nicely */
mm_iface_modem_update_access_technologies (
MM_IFACE_MODEM (self),
act,
(MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK | MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK));
g_task_return_int (task, quality);
g_object_unref (task);
qmi_message_nas_get_signal_strength_output_unref (output);
}
static void
load_signal_quality (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
QmiClient *client = NULL;
GTask *task;
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
QMI_SERVICE_NAS, &client,
callback, user_data))
return;
task = g_task_new (self, NULL, callback, user_data);
mm_dbg ("loading signal quality...");
#if defined WITH_NEWEST_QMI_COMMANDS
/* Signal info introduced in NAS 1.8 */
if (qmi_client_check_version (client, 1, 8)) {
qmi_client_nas_get_signal_info (QMI_CLIENT_NAS (client),
NULL,
10,
NULL,
(GAsyncReadyCallback)get_signal_info_ready,
task);
return;
}
#endif /* WITH_NEWEST_QMI_COMMANDS */
qmi_client_nas_get_signal_strength (QMI_CLIENT_NAS (client),
NULL,
10,
NULL,
(GAsyncReadyCallback)get_signal_strength_ready,
task);
}
/*****************************************************************************/
/* Powering up the modem (Modem interface) */
typedef enum {
SET_OPERATING_MODE_STEP_FIRST,
SET_OPERATING_MODE_STEP_FCC_AUTH,
SET_OPERATING_MODE_STEP_RETRY,
SET_OPERATING_MODE_STEP_LAST
} SetOperatingModeStep;
typedef struct {
QmiClientDms *client;
QmiMessageDmsSetOperatingModeInput *input;
SetOperatingModeStep step;
} SetOperatingModeContext;
static void
set_operating_mode_context_free (SetOperatingModeContext *ctx)
{
g_object_unref (ctx->client);
qmi_message_dms_set_operating_mode_input_unref (ctx->input);
g_slice_free (SetOperatingModeContext, ctx);
}
static gboolean
modem_power_up_down_off_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void set_operating_mode_context_step (GTask *task);
static void
dms_set_fcc_authentication_ready (QmiClientDms *client,
GAsyncResult *res,
GTask *task)
{
SetOperatingModeContext *ctx;
QmiMessageDmsSetFccAuthenticationOutput *output = NULL;
GError *error = NULL;
ctx = g_task_get_task_data (task);
output = qmi_client_dms_set_fcc_authentication_finish (client, res, &error);
if (!output || !qmi_message_dms_set_fcc_authentication_output_get_result (output, &error)) {
/* No hard errors */
mm_dbg ("Couldn't set FCC authentication: %s", error->message);
g_error_free (error);
}
if (output)
qmi_message_dms_set_fcc_authentication_output_unref (output);
/* Retry Set Operating Mode */
ctx->step++;
set_operating_mode_context_step (task);
}
static void
dms_set_operating_mode_ready (QmiClientDms *client,
GAsyncResult *res,
GTask *task)
{
SetOperatingModeContext *ctx;
QmiMessageDmsSetOperatingModeOutput *output = NULL;
GError *error = NULL;
ctx = g_task_get_task_data (task);
output = qmi_client_dms_set_operating_mode_finish (client, res, &error);
if (!output) {
/* If unsupported, just go out without errors */
if (g_error_matches (error, QMI_CORE_ERROR, QMI_CORE_ERROR_UNSUPPORTED)) {
mm_dbg ("Device doesn't support operating mode setting. Ignoring power update.");
g_error_free (error);
ctx->step = SET_OPERATING_MODE_STEP_LAST;
set_operating_mode_context_step (task);
return;
}
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (!qmi_message_dms_set_operating_mode_output_get_result (output, &error)) {
QmiDmsOperatingMode mode;
/*
* Some new devices, like the Dell DW5770, will return an internal error when
* trying to bring the power mode to online.
*
* Other devices, like some rebranded EM7455 modules, will return an "invalid
* transition" instead when trying to bring the power mode to online.
*
* We can avoid this by sending the magic "DMS Set FCC Auth" message before
* retrying.
*/
if (ctx->step == SET_OPERATING_MODE_STEP_FIRST &&
qmi_message_dms_set_operating_mode_input_get_mode (ctx->input, &mode, NULL) &&
mode == QMI_DMS_OPERATING_MODE_ONLINE &&
(g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INTERNAL) ||
g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_TRANSITION))) {
g_error_free (error);
/* Go on to FCC auth */
ctx->step++;
set_operating_mode_context_step (task);
qmi_message_dms_set_operating_mode_output_unref (output);
return;
}
g_prefix_error (&error, "Couldn't set operating mode: ");
g_task_return_error (task, error);
g_object_unref (task);
qmi_message_dms_set_operating_mode_output_unref (output);
return;
}
qmi_message_dms_set_operating_mode_output_unref (output);
/* Good! we're done, go to last step */
ctx->step = SET_OPERATING_MODE_STEP_LAST;
set_operating_mode_context_step (task);
}
static void
set_operating_mode_context_step (GTask *task)
{
SetOperatingModeContext *ctx;
ctx = g_task_get_task_data (task);
switch (ctx->step) {
case SET_OPERATING_MODE_STEP_FIRST:
mm_dbg ("Setting device operating mode...");
qmi_client_dms_set_operating_mode (QMI_CLIENT_DMS (ctx->client),
ctx->input,
20,
NULL,
(GAsyncReadyCallback)dms_set_operating_mode_ready,
task);
return;
case SET_OPERATING_MODE_STEP_FCC_AUTH:
mm_dbg ("Setting FCC auth...");
qmi_client_dms_set_fcc_authentication (QMI_CLIENT_DMS (ctx->client),
NULL,
5,
NULL,
(GAsyncReadyCallback)dms_set_fcc_authentication_ready,
task);
return;
case SET_OPERATING_MODE_STEP_RETRY:
mm_dbg ("Setting device operating mode (retry)...");
qmi_client_dms_set_operating_mode (QMI_CLIENT_DMS (ctx->client),
ctx->input,
20,
NULL,
(GAsyncReadyCallback)dms_set_operating_mode_ready,
task);
return;
case SET_OPERATING_MODE_STEP_LAST:
/* Good! */
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
default:
g_assert_not_reached ();
}
}
static void
common_power_up_down_off (MMIfaceModem *self,
QmiDmsOperatingMode mode,
GAsyncReadyCallback callback,
gpointer user_data)
{
SetOperatingModeContext *ctx;
GTask *task;
QmiClient *client = NULL;
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
QMI_SERVICE_DMS, &client,
callback, user_data))
return;
/* Setup context */
ctx = g_slice_new0 (SetOperatingModeContext);
ctx->client = g_object_ref (client);
ctx->input = qmi_message_dms_set_operating_mode_input_new ();
qmi_message_dms_set_operating_mode_input_set_mode (ctx->input, mode, NULL);
ctx->step = SET_OPERATING_MODE_STEP_FIRST;
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task,
ctx,
(GDestroyNotify)set_operating_mode_context_free);
set_operating_mode_context_step (task);
}
static void
modem_power_off (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
common_power_up_down_off (self,
QMI_DMS_OPERATING_MODE_OFFLINE,
callback,
user_data);
}
static void
modem_power_down (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
common_power_up_down_off (self,
QMI_DMS_OPERATING_MODE_LOW_POWER,
callback,
user_data);
}
static void
modem_power_up (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
common_power_up_down_off (self,
QMI_DMS_OPERATING_MODE_ONLINE,
callback,
user_data);
}
/*****************************************************************************/
/* Power state loading (Modem interface) */
static MMModemPowerState
load_power_state_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GError *inner_error = NULL;
gssize value;
value = g_task_propagate_int (G_TASK (res), &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return MM_MODEM_POWER_STATE_UNKNOWN;
}
return (MMModemPowerState)value;
}
static void
dms_get_operating_mode_ready (QmiClientDms *client,
GAsyncResult *res,
GTask *task)
{
QmiMessageDmsGetOperatingModeOutput *output = NULL;
GError *error = NULL;
output = qmi_client_dms_get_operating_mode_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
} else if (!qmi_message_dms_get_operating_mode_output_get_result (output, &error)) {
g_prefix_error (&error, "Couldn't get operating mode: ");
g_task_return_error (task, error);
} else {
QmiDmsOperatingMode mode = QMI_DMS_OPERATING_MODE_UNKNOWN;
qmi_message_dms_get_operating_mode_output_get_mode (output, &mode, NULL);
switch (mode) {
case QMI_DMS_OPERATING_MODE_ONLINE:
g_task_return_int (task, MM_MODEM_POWER_STATE_ON);
break;
case QMI_DMS_OPERATING_MODE_LOW_POWER:
case QMI_DMS_OPERATING_MODE_PERSISTENT_LOW_POWER:
case QMI_DMS_OPERATING_MODE_MODE_ONLY_LOW_POWER:
g_task_return_int (task, MM_MODEM_POWER_STATE_LOW);
break;
case QMI_DMS_OPERATING_MODE_OFFLINE:
g_task_return_int (task, MM_MODEM_POWER_STATE_OFF);
break;
default:
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Unhandled power state: '%s' (%u)",
qmi_dms_operating_mode_get_string (mode),
mode);
break;
}
}
if (output)
qmi_message_dms_get_operating_mode_output_unref (output);
g_object_unref (task);
}
static void
load_power_state (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
QmiClient *client = NULL;
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
QMI_SERVICE_DMS, &client,
callback, user_data))
return;
mm_dbg ("Getting device operating mode...");
qmi_client_dms_get_operating_mode (QMI_CLIENT_DMS (client),
NULL,
5,
NULL,
(GAsyncReadyCallback)dms_get_operating_mode_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Create SIM (Modem interface) */
static MMBaseSim *
create_sim_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return mm_sim_qmi_new_finish (res, error);
}
static void
create_sim (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
/* New QMI SIM */
mm_sim_qmi_new (MM_BASE_MODEM (self),
MM_BROADBAND_MODEM_QMI (self)->priv->dms_uim_deprecated,
NULL, /* cancellable */
callback,
user_data);
}
/*****************************************************************************/
/* IMEI loading (3GPP interface) */
static gchar *
modem_3gpp_load_imei_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
modem_3gpp_load_imei (MMIfaceModem3gpp *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self);
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
if (self->priv->imei)
g_task_return_pointer (task, g_strdup (self->priv->imei), g_free);
else
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Device doesn't report a valid IMEI");
g_object_unref (task);
}
/*****************************************************************************/
/* Facility locks status loading (3GPP interface) */
typedef struct {
QmiClient *client;
guint current;
MMModem3gppFacility facilities;
MMModem3gppFacility locks;
} LoadEnabledFacilityLocksContext;
static void get_next_facility_lock_status (GTask *task);
static void
load_enabled_facility_locks_context_free (LoadEnabledFacilityLocksContext *ctx)
{
g_object_unref (ctx->client);
g_free (ctx);
}
static MMModem3gppFacility
modem_3gpp_load_enabled_facility_locks_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
GError *inner_error = NULL;
gssize value;
value = g_task_propagate_int (G_TASK (res), &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return MM_MODEM_3GPP_FACILITY_NONE;
}
return (MMModem3gppFacility)value;
}
static void
get_sim_lock_status_via_pin_status_ready (QmiClientDms *client,
GAsyncResult *res,
GTask *task)
{
LoadEnabledFacilityLocksContext *ctx;
QmiMessageDmsUimGetPinStatusOutput *output;
gboolean enabled;
ctx = g_task_get_task_data (task);
output = qmi_client_dms_uim_get_pin_status_finish (client, res, NULL);
if (!output ||
!qmi_message_dms_uim_get_pin_status_output_get_result (output, NULL)) {
mm_dbg ("Couldn't query PIN status, assuming SIM PIN is disabled");
enabled = FALSE;
} else {
QmiDmsUimPinStatus current_status;
if (qmi_message_dms_uim_get_pin_status_output_get_pin1_status (
output,
&current_status,
NULL, /* verify_retries_left */
NULL, /* unblock_retries_left */
NULL)) {
enabled = mm_pin_enabled_from_qmi_uim_pin_status (current_status);
mm_dbg ("PIN is reported %s", (enabled ? "enabled" : "disabled"));
} else {
mm_dbg ("Couldn't find PIN1 status in the result, assuming SIM PIN is disabled");
enabled = FALSE;
}
}
if (output)
qmi_message_dms_uim_get_pin_status_output_unref (output);
if (enabled) {
ctx->locks |= (MM_MODEM_3GPP_FACILITY_SIM);
} else {
ctx->locks &= ~(MM_MODEM_3GPP_FACILITY_SIM);
}
/* No more facilities to query, all done */
g_task_return_int (task, ctx->locks);
g_object_unref (task);
}
/* the SIM lock cannot be queried with the qmi_get_ck_status function,
* therefore using the PIN status */
static void
get_sim_lock_status_via_pin_status (GTask *task)
{
LoadEnabledFacilityLocksContext *ctx;
ctx = g_task_get_task_data (task);
mm_dbg ("Retrieving PIN status to check for enabled PIN");
/* if the SIM is locked or not can only be queried by locking at
* the PIN status */
qmi_client_dms_uim_get_pin_status (QMI_CLIENT_DMS (ctx->client),
NULL,
5,
NULL,
(GAsyncReadyCallback)get_sim_lock_status_via_pin_status_ready,
task);
}
static void
dms_uim_get_ck_status_ready (QmiClientDms *client,
GAsyncResult *res,
GTask *task)
{
LoadEnabledFacilityLocksContext *ctx;
gchar *facility_str;
QmiMessageDmsUimGetCkStatusOutput *output;
ctx = g_task_get_task_data (task);
facility_str = mm_modem_3gpp_facility_build_string_from_mask (1 << ctx->current);
output = qmi_client_dms_uim_get_ck_status_finish (client, res, NULL);
if (!output ||
!qmi_message_dms_uim_get_ck_status_output_get_result (output, NULL)) {
/* On errors, we'll just assume disabled */
mm_dbg ("Couldn't query facility '%s' status, assuming disabled", facility_str);
ctx->locks &= ~(1 << ctx->current);
} else {
QmiDmsUimFacilityState state;
guint8 verify_retries_left;
guint8 unblock_retries_left;
qmi_message_dms_uim_get_ck_status_output_get_ck_status (
output,
&state,
&verify_retries_left,
&unblock_retries_left,
NULL);
mm_dbg ("Facility '%s' is: '%s'",
facility_str,
qmi_dms_uim_facility_state_get_string (state));
if (state == QMI_DMS_UIM_FACILITY_STATE_ACTIVATED ||
state == QMI_DMS_UIM_FACILITY_STATE_BLOCKED) {
ctx->locks |= (1 << ctx->current);
}
}
if (output)
qmi_message_dms_uim_get_ck_status_output_unref (output);
g_free (facility_str);
/* And go on with the next one */
ctx->current++;
get_next_facility_lock_status (task);
}
static void
get_next_facility_lock_status (GTask *task)
{
LoadEnabledFacilityLocksContext *ctx;
guint i;
ctx = g_task_get_task_data (task);
for (i = ctx->current; i < sizeof (MMModem3gppFacility) * 8; i++) {
guint32 facility = 1 << i;
/* Found the next one to query! */
if (ctx->facilities & facility) {
QmiMessageDmsUimGetCkStatusInput *input;
/* Keep the current one */
ctx->current = i;
/* Query current */
input = qmi_message_dms_uim_get_ck_status_input_new ();
qmi_message_dms_uim_get_ck_status_input_set_facility (
input,
mm_3gpp_facility_to_qmi_uim_facility (facility),
NULL);
qmi_client_dms_uim_get_ck_status (QMI_CLIENT_DMS (ctx->client),
input,
5,
NULL,
(GAsyncReadyCallback)dms_uim_get_ck_status_ready,
task);
qmi_message_dms_uim_get_ck_status_input_unref (input);
return;
}
}
get_sim_lock_status_via_pin_status (task);
}
static void
modem_3gpp_load_enabled_facility_locks (MMIfaceModem3gpp *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
LoadEnabledFacilityLocksContext *ctx;
GTask *task;
QmiClient *client = NULL;
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
QMI_SERVICE_DMS, &client,
callback, user_data))
return;
ctx = g_new (LoadEnabledFacilityLocksContext, 1);
ctx->client = g_object_ref (client);
/* Set initial list of facilities to query */
ctx->facilities = (MM_MODEM_3GPP_FACILITY_PH_SIM |
MM_MODEM_3GPP_FACILITY_NET_PERS |
MM_MODEM_3GPP_FACILITY_NET_SUB_PERS |
MM_MODEM_3GPP_FACILITY_PROVIDER_PERS |
MM_MODEM_3GPP_FACILITY_CORP_PERS);
ctx->locks = MM_MODEM_3GPP_FACILITY_NONE;
ctx->current = 0;
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, ctx, (GDestroyNotify)load_enabled_facility_locks_context_free);
get_next_facility_lock_status (task);
}
/*****************************************************************************/
/* Scan networks (3GPP interface) */
static GList *
modem_3gpp_scan_networks_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static MMModem3gppNetworkAvailability
network_availability_from_qmi_nas_network_status (QmiNasNetworkStatus qmi)
{
if (qmi & QMI_NAS_NETWORK_STATUS_CURRENT_SERVING)
return MM_MODEM_3GPP_NETWORK_AVAILABILITY_CURRENT;
if (qmi & QMI_NAS_NETWORK_STATUS_AVAILABLE) {
if (qmi & QMI_NAS_NETWORK_STATUS_FORBIDDEN)
return MM_MODEM_3GPP_NETWORK_AVAILABILITY_FORBIDDEN;
return MM_MODEM_3GPP_NETWORK_AVAILABILITY_AVAILABLE;
}
return MM_MODEM_3GPP_NETWORK_AVAILABILITY_UNKNOWN;
}
static MM3gppNetworkInfo *
get_3gpp_network_info (QmiMessageNasNetworkScanOutputNetworkInformationElement *element)
{
GString *aux;
MM3gppNetworkInfo *info;
info = g_new (MM3gppNetworkInfo, 1);
info->status = network_availability_from_qmi_nas_network_status (element->network_status);
aux = g_string_new ("");
/* MCC always 3 digits */
g_string_append_printf (aux, "%.3"G_GUINT16_FORMAT, element->mcc);
/* Guess about MNC, if < 100 assume it's 2 digits, no PCS info here */
if (element->mnc >= 100)
g_string_append_printf (aux, "%.3"G_GUINT16_FORMAT, element->mnc);
else
g_string_append_printf (aux, "%.2"G_GUINT16_FORMAT, element->mnc);
info->operator_code = g_string_free (aux, FALSE);
info->operator_short = NULL;
info->operator_long = g_strdup (element->description);
info->access_tech = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
return info;
}
static MMModemAccessTechnology
get_3gpp_access_technology (GArray *array,
gboolean *array_used_flags,
guint16 mcc,
guint16 mnc)
{
guint i;
for (i = 0; i < array->len; i++) {
QmiMessageNasNetworkScanOutputRadioAccessTechnologyElement *element;
if (array_used_flags[i])
continue;
element = &g_array_index (array, QmiMessageNasNetworkScanOutputRadioAccessTechnologyElement, i);
if (element->mcc == mcc &&
element->mnc == mnc) {
array_used_flags[i] = TRUE;
return mm_modem_access_technology_from_qmi_radio_interface (element->radio_interface);
}
}
return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
}
static void
nas_network_scan_ready (QmiClientNas *client,
GAsyncResult *res,
GTask *task)
{
QmiMessageNasNetworkScanOutput *output = NULL;
GError *error = NULL;
output = qmi_client_nas_network_scan_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
} else if (!qmi_message_nas_network_scan_output_get_result (output, &error)) {
g_prefix_error (&error, "Couldn't scan networks: ");
g_task_return_error (task, error);
} else {
GList *scan_result = NULL;
GArray *info_array = NULL;
if (qmi_message_nas_network_scan_output_get_network_information (output, &info_array, NULL)) {
GArray *rat_array = NULL;
gboolean *rat_array_used_flags = NULL;
guint i;
/* Get optional RAT array */
qmi_message_nas_network_scan_output_get_radio_access_technology (output, &rat_array, NULL);
if (rat_array)
rat_array_used_flags = g_new0 (gboolean, rat_array->len);
for (i = 0; i < info_array->len; i++) {
QmiMessageNasNetworkScanOutputNetworkInformationElement *info_element;
MM3gppNetworkInfo *info;
info_element = &g_array_index (info_array, QmiMessageNasNetworkScanOutputNetworkInformationElement, i);
info = get_3gpp_network_info (info_element);
if (rat_array)
info->access_tech = get_3gpp_access_technology (rat_array,
rat_array_used_flags,
info_element->mcc,
info_element->mnc);
scan_result = g_list_append (scan_result, info);
}
g_free (rat_array_used_flags);
}
g_task_return_pointer (task, scan_result, (GDestroyNotify)mm_3gpp_network_info_list_free);
}
if (output)
qmi_message_nas_network_scan_output_unref (output);
g_object_unref (task);
}
static void
modem_3gpp_scan_networks (MMIfaceModem3gpp *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
QmiClient *client = NULL;
/* We will pass the GList in the GSimpleAsyncResult, so we must
* ensure that there is a callback so that we get it properly
* passed to the caller and deallocated afterwards */
g_assert (callback != NULL);
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
QMI_SERVICE_NAS, &client,
callback, user_data))
return;
mm_dbg ("Scanning networks...");
qmi_client_nas_network_scan (QMI_CLIENT_NAS (client),
NULL,
300,
NULL,
(GAsyncReadyCallback)nas_network_scan_ready,
g_task_new (self, NULL, callback, user_data));
}
/*****************************************************************************/
/* Load operator name (3GPP interface) */
static gchar *
modem_3gpp_load_operator_name_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
modem_3gpp_load_operator_name (MMIfaceModem3gpp *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self);
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
if (self->priv->current_operator_description)
g_task_return_pointer (task,
g_strdup (self->priv->current_operator_description),
g_free);
else
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Current operator description is still unknown");
g_object_unref (task);
}
/*****************************************************************************/
/* Load operator code (3GPP interface) */
static gchar *
modem_3gpp_load_operator_code_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
modem_3gpp_load_operator_code (MMIfaceModem3gpp *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self);
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
if (self->priv->current_operator_id)
g_task_return_pointer (task,
g_strdup (self->priv->current_operator_id),
g_free);
else
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Current operator MCC/MNC is still unknown");
g_object_unref (task);
}
/*****************************************************************************/
/* Registration checks (3GPP interface) */
static gboolean
modem_3gpp_run_registration_checks_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
common_process_serving_system_3gpp (MMBroadbandModemQmi *self,
QmiMessageNasGetServingSystemOutput *response_output,
QmiIndicationNasServingSystemOutput *indication_output)
{
QmiNasRegistrationState registration_state;
QmiNasAttachState cs_attach_state;
QmiNasAttachState ps_attach_state;
QmiNasNetworkType selected_network;
GArray *radio_interfaces;
GArray *data_service_capabilities;
QmiNasRoamingIndicatorStatus roaming;
guint16 mcc;
guint16 mnc;
const gchar *description;
gboolean has_pcs_digit;
guint16 lac;
guint16 tac;
guint32 cid;
MMModemAccessTechnology mm_access_technologies;
MMModem3gppRegistrationState mm_cs_registration_state;
MMModem3gppRegistrationState mm_ps_registration_state;
if (response_output)
qmi_message_nas_get_serving_system_output_get_serving_system (
response_output,
&registration_state,
&cs_attach_state,
&ps_attach_state,
&selected_network,
&radio_interfaces,
NULL);
else
qmi_indication_nas_serving_system_output_get_serving_system (
indication_output,
&registration_state,
&cs_attach_state,
&ps_attach_state,
&selected_network,
&radio_interfaces,
NULL);
/* Build access technologies mask */
data_service_capabilities = NULL;
if (response_output)
qmi_message_nas_get_serving_system_output_get_data_service_capability (response_output, &data_service_capabilities, NULL);
else
qmi_indication_nas_serving_system_output_get_data_service_capability (indication_output, &data_service_capabilities, NULL);
if (data_service_capabilities)
mm_access_technologies =
mm_modem_access_technologies_from_qmi_data_capability_array (data_service_capabilities);
else
mm_access_technologies =
mm_modem_access_technologies_from_qmi_radio_interface_array (radio_interfaces);
/* Only process 3GPP info.
* Seen the case already where 'selected_network' gives UNKNOWN but we still
* have valid LTE info around. */
if (selected_network == QMI_NAS_NETWORK_TYPE_3GPP ||
(selected_network == QMI_NAS_NETWORK_TYPE_UNKNOWN &&
(mm_access_technologies & MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK))) {
mm_dbg ("Processing 3GPP info...");
} else {
MMModem3gppRegistrationState reg_state_3gpp;
mm_dbg ("No 3GPP info given...");
g_free (self->priv->current_operator_id);
self->priv->current_operator_id = NULL;
g_free (self->priv->current_operator_description);
self->priv->current_operator_description = NULL;
if (registration_state == QMI_NAS_REGISTRATION_STATE_NOT_REGISTERED_SEARCHING)
reg_state_3gpp = MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING;
else
reg_state_3gpp = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
mm_iface_modem_3gpp_update_cs_registration_state (MM_IFACE_MODEM_3GPP (self), reg_state_3gpp);
mm_iface_modem_3gpp_update_ps_registration_state (MM_IFACE_MODEM_3GPP (self), reg_state_3gpp);
mm_iface_modem_3gpp_update_access_technologies (MM_IFACE_MODEM_3GPP (self), MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN);
mm_iface_modem_3gpp_update_location (MM_IFACE_MODEM_3GPP (self), 0, 0, 0);
return;
}
/* Get roaming status.
* TODO: QMI may report per-access-technology roaming indicators, for when
* the modem is connected to more than one network. How to handle those? */
roaming = QMI_NAS_ROAMING_INDICATOR_STATUS_OFF;
if (response_output)
qmi_message_nas_get_serving_system_output_get_roaming_indicator (response_output, &roaming, NULL);
else
qmi_indication_nas_serving_system_output_get_roaming_indicator (indication_output, &roaming, NULL);
/* Build MM registration states */
mm_cs_registration_state =
mm_modem_3gpp_registration_state_from_qmi_registration_state (
cs_attach_state,
registration_state,
(roaming == QMI_NAS_ROAMING_INDICATOR_STATUS_ON));
mm_ps_registration_state =
mm_modem_3gpp_registration_state_from_qmi_registration_state (
ps_attach_state,
registration_state,
(roaming == QMI_NAS_ROAMING_INDICATOR_STATUS_ON));
/* Get and cache operator ID/name */
if ((response_output &&
qmi_message_nas_get_serving_system_output_get_current_plmn (
response_output,
&mcc,
&mnc,
&description,
NULL)) ||
(indication_output &&
qmi_indication_nas_serving_system_output_get_current_plmn (
indication_output,
&mcc,
&mnc,
&description,
NULL))) {
/* When we don't have information about leading PCS digit, guess best */
g_free (self->priv->current_operator_id);
if (mnc >= 100)
self->priv->current_operator_id =
g_strdup_printf ("%.3" G_GUINT16_FORMAT "%.3" G_GUINT16_FORMAT,
mcc,
mnc);
else
self->priv->current_operator_id =
g_strdup_printf ("%.3" G_GUINT16_FORMAT "%.2" G_GUINT16_FORMAT,
mcc,
mnc);
g_clear_pointer (&self->priv->current_operator_description, g_free);
/* Some Telit modems apparently sometimes report non-UTF8 characters */
if (g_utf8_validate (description, -1, NULL))
self->priv->current_operator_description = g_strdup (description);
}
/* If MNC comes with PCS digit, we must make sure the additional
* leading '0' is added */
if (((response_output &&
qmi_message_nas_get_serving_system_output_get_mnc_pcs_digit_include_status (
response_output,
&mcc,
&mnc,
&has_pcs_digit,
NULL)) ||
(indication_output &&
qmi_indication_nas_serving_system_output_get_mnc_pcs_digit_include_status (
indication_output,
&mcc,
&mnc,
&has_pcs_digit,
NULL))) &&
has_pcs_digit) {
g_free (self->priv->current_operator_id);
self->priv->current_operator_id =
g_strdup_printf ("%.3" G_GUINT16_FORMAT "%.3" G_GUINT16_FORMAT,
mcc,
mnc);
}
/* Report new registration states */
mm_iface_modem_3gpp_update_cs_registration_state (MM_IFACE_MODEM_3GPP (self), mm_cs_registration_state);
mm_iface_modem_3gpp_update_ps_registration_state (MM_IFACE_MODEM_3GPP (self), mm_ps_registration_state);
if (mm_access_technologies & MM_MODEM_ACCESS_TECHNOLOGY_LTE)
mm_iface_modem_3gpp_update_eps_registration_state (MM_IFACE_MODEM_3GPP (self), mm_ps_registration_state);
/* Get 3GPP location LAC/TAC and CI */
lac = 0;
tac = 0;
cid = 0;
if (response_output) {
qmi_message_nas_get_serving_system_output_get_lac_3gpp (response_output, &lac, NULL);
qmi_message_nas_get_serving_system_output_get_lte_tac (response_output, &tac, NULL);
qmi_message_nas_get_serving_system_output_get_cid_3gpp (response_output, &cid, NULL);
} else if (indication_output) {
qmi_indication_nas_serving_system_output_get_lac_3gpp (indication_output, &lac, NULL);
qmi_indication_nas_serving_system_output_get_lte_tac (indication_output, &tac, NULL);
qmi_indication_nas_serving_system_output_get_cid_3gpp (indication_output, &cid, NULL);
}
/* Only update info in the interface if we get something */
if (cid && (lac || tac))
mm_iface_modem_3gpp_update_location (MM_IFACE_MODEM_3GPP (self), lac, tac, cid);
/* Note: don't update access technologies with the ones retrieved here; they
* are not really the 'current' access technologies */
}
static void
get_serving_system_3gpp_ready (QmiClientNas *client,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModemQmi *self;
QmiMessageNasGetServingSystemOutput *output;
GError *error = NULL;
output = qmi_client_nas_get_serving_system_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (!qmi_message_nas_get_serving_system_output_get_result (output, &error)) {
g_prefix_error (&error, "Couldn't get serving system: ");
g_task_return_error (task, error);
g_object_unref (task);
qmi_message_nas_get_serving_system_output_unref (output);
return;
}
self = g_task_get_source_object (task);
common_process_serving_system_3gpp (self, output, NULL);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
qmi_message_nas_get_serving_system_output_unref (output);
}
#if defined WITH_NEWEST_QMI_COMMANDS
static gboolean
process_common_info (QmiNasServiceStatus service_status,
gboolean domain_valid,
QmiNasNetworkServiceDomain domain,
gboolean roaming_status_valid,
QmiNasRoamingStatus roaming_status,
gboolean forbidden_valid,
gboolean forbidden,
gboolean lac_valid,
guint16 lac,
gboolean tac_valid,
guint16 tac,
gboolean cid_valid,
guint32 cid,
gboolean network_id_valid,
const gchar *mcc,
const gchar *mnc,
MMModem3gppRegistrationState *mm_cs_registration_state,
MMModem3gppRegistrationState *mm_ps_registration_state,
guint16 *mm_lac,
guint16 *mm_tac,
guint32 *mm_cid,
gchar **mm_operator_id)
{
MMModem3gppRegistrationState tmp_registration_state;
gboolean apply_cs = TRUE;
gboolean apply_ps = TRUE;
if (service_status != QMI_NAS_SERVICE_STATUS_LIMITED &&
service_status != QMI_NAS_SERVICE_STATUS_AVAILABLE &&
service_status != QMI_NAS_SERVICE_STATUS_LIMITED_REGIONAL)
return FALSE;
/* If we don't have domain, unknown */
if (!domain_valid)
tmp_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
else if (domain == QMI_NAS_NETWORK_SERVICE_DOMAIN_NONE)
tmp_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING;
else if (domain == QMI_NAS_NETWORK_SERVICE_DOMAIN_UNKNOWN)
tmp_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
else {
/* If we have CS or PS service domain, assume registered for now */
if (domain == QMI_NAS_NETWORK_SERVICE_DOMAIN_CS)
apply_ps = FALSE;
else if (domain == QMI_NAS_NETWORK_SERVICE_DOMAIN_PS)
apply_cs = FALSE;
else if (domain == QMI_NAS_NETWORK_SERVICE_DOMAIN_CS_PS)
/* both apply */ ;
/* Check if we really are roaming or forbidden */
if (forbidden_valid && forbidden)
tmp_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_DENIED;
else {
if (roaming_status_valid && roaming_status == QMI_NAS_ROAMING_STATUS_ON)
tmp_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING;
else
tmp_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_HOME;
/* If we're registered either at home or roaming, try to get LAC/CID */
if (lac_valid)
*mm_lac = lac;
if (tac_valid)
*mm_tac = tac;
if (cid_valid)
*mm_cid = cid;
}
}
if (apply_cs)
*mm_cs_registration_state = tmp_registration_state;
if (apply_ps)
*mm_ps_registration_state = tmp_registration_state;
if (network_id_valid) {
*mm_operator_id = g_malloc (7);
memcpy (*mm_operator_id, mcc, 3);
if (mnc[2] == 0xFF) {
memcpy (&((*mm_operator_id)[3]), mnc, 2);
(*mm_operator_id)[5] = '\0';
} else {
memcpy (&((*mm_operator_id)[3]), mnc, 3);
(*mm_operator_id)[6] = '\0';
}
}
return TRUE;
}
static gboolean
process_gsm_info (QmiMessageNasGetSystemInfoOutput *response_output,
QmiIndicationNasSystemInfoOutput *indication_output,
MMModem3gppRegistrationState *mm_cs_registration_state,
MMModem3gppRegistrationState *mm_ps_registration_state,
guint16 *mm_lac,
guint32 *mm_cid,
gchar **mm_operator_id)
{
QmiNasServiceStatus service_status;
gboolean domain_valid;
QmiNasNetworkServiceDomain domain;
gboolean roaming_status_valid;
QmiNasRoamingStatus roaming_status;
gboolean forbidden_valid;
gboolean forbidden;
gboolean lac_valid;
guint16 lac;
gboolean cid_valid;
guint32 cid;
gboolean network_id_valid;
const gchar *mcc;
const gchar *mnc;