diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mm-broadband-modem-qmi.c | 952 | ||||
-rw-r--r-- | src/mm-shared-qmi.c | 1045 | ||||
-rw-r--r-- | src/mm-shared-qmi.h | 39 |
3 files changed, 1156 insertions, 880 deletions
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c index abf846fa..805947cf 100644 --- a/src/mm-broadband-modem-qmi.c +++ b/src/mm-broadband-modem-qmi.c @@ -57,8 +57,8 @@ 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; -static MMIfaceModemLocation *iface_modem_location_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) @@ -119,7 +119,6 @@ struct _MMBroadbandModemQmiPrivate { /* Location helpers */ MMModemLocationSource enabled_sources; - guint location_event_report_indication_id; /* Oma helpers */ gboolean oma_unsolicited_events_enabled; @@ -8081,9 +8080,9 @@ messaging_create_sms (MMIfaceModemMessaging *_self) /* Location capabilities loading (Location interface) */ static MMModemLocationSource -location_load_capabilities_finish (MMIfaceModemLocation *self, - GAsyncResult *res, - GError **error) +location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) { GError *inner_error = NULL; gssize value; @@ -8097,35 +8096,28 @@ location_load_capabilities_finish (MMIfaceModemLocation *self, } static void -parent_load_capabilities_ready (MMIfaceModemLocation *self, - GAsyncResult *res, - GTask *task) +shared_qmi_location_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) { MMModemLocationSource sources; GError *error = NULL; - sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error); + sources = mm_shared_qmi_location_load_capabilities_finish (self, res, &error); if (error) { g_task_return_error (task, error); g_object_unref (task); return; } - /* Now our own checks */ - - /* If we have support for the PDS client, GPS and A-GPS location is supported */ - if (mm_shared_qmi_peek_client (MM_SHARED_QMI (self), - QMI_SERVICE_PDS, - MM_PORT_QMI_FLAG_DEFAULT, - NULL)) - sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | - MM_MODEM_LOCATION_SOURCE_GPS_RAW | - MM_MODEM_LOCATION_SOURCE_AGPS); - /* If the modem is CDMA, we have support for CDMA BS location */ if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) sources |= MM_MODEM_LOCATION_SOURCE_CDMA_BS; + /* If the modem is 3GPP, we have support for 3GPP LAC/CI location */ + if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) + sources |= MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI; + /* So we're done, complete */ g_task_return_int (task, sources); g_object_unref (task); @@ -8133,574 +8125,71 @@ parent_load_capabilities_ready (MMIfaceModemLocation *self, static void location_load_capabilities (MMIfaceModemLocation *self, - GAsyncReadyCallback callback, - gpointer user_data) + GAsyncReadyCallback callback, + gpointer user_data) { GTask *task; task = g_task_new (self, NULL, callback, user_data); - /* Chain up parent's setup */ - iface_modem_location_parent->load_capabilities ( + /* Chain up shared QMI setup, which takes care of running the PARENT + * setup as well as processing GPS-related checks. */ + mm_shared_qmi_location_load_capabilities ( self, - (GAsyncReadyCallback)parent_load_capabilities_ready, + (GAsyncReadyCallback)shared_qmi_location_load_capabilities_ready, task); } /*****************************************************************************/ -/* Load SUPL server */ - -static gchar * -location_load_supl_server_finish (MMIfaceModemLocation *self, - GAsyncResult *res, - GError **error) -{ - return g_task_propagate_pointer (G_TASK (res), error); -} - -static void -get_agps_config_ready (QmiClientPds *client, - GAsyncResult *res, - GTask *task) -{ - QmiMessagePdsGetAgpsConfigOutput *output = NULL; - GError *error = NULL; - guint32 ip; - guint32 port; - GArray *url; - gchar *str; - - output = qmi_client_pds_get_agps_config_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_pds_get_agps_config_output_get_result (output, &error)) { - g_task_return_error (task, error); - g_object_unref (task); - return; - } - - str = NULL; - - /* Prefer IP/PORT to URL */ - if (qmi_message_pds_get_agps_config_output_get_location_server_address ( - output, - &ip, - &port, - NULL) && - ip != 0 && - port != 0) { - struct in_addr a = { .s_addr = ip }; - gchar buf[INET_ADDRSTRLEN + 1]; - - memset (buf, 0, sizeof (buf)); - - if (!inet_ntop (AF_INET, &a, buf, sizeof (buf) - 1)) { - g_task_return_new_error (task, - MM_CORE_ERROR, - MM_CORE_ERROR_FAILED, - "Cannot convert numeric IP address to string"); - g_object_unref (task); - return; - } - - str = g_strdup_printf ("%s:%u", buf, port); - } - - if (!str && - qmi_message_pds_get_agps_config_output_get_location_server_url ( - output, - &url, - NULL) && - url->len > 0) { - str = g_convert (url->data, url->len, "UTF-8", "UTF-16BE", NULL, NULL, NULL); - } - - if (!str) - str = g_strdup (""); - - qmi_message_pds_get_agps_config_output_unref (output); - - g_task_return_pointer (task, str, g_free); - g_object_unref (task); -} - -static void -location_load_supl_server (MMIfaceModemLocation *self, - GAsyncReadyCallback callback, - gpointer user_data) -{ - QmiClient *client = NULL; - QmiMessagePdsGetAgpsConfigInput *input; - - if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), - QMI_SERVICE_PDS, &client, - callback, user_data)) { - return; - } - - input = qmi_message_pds_get_agps_config_input_new (); - - /* For multimode devices, prefer UMTS by default */ - if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) - qmi_message_pds_get_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_UMTS, NULL); - else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) - qmi_message_pds_get_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_CDMA, NULL); - - qmi_client_pds_get_agps_config ( - QMI_CLIENT_PDS (client), - input, - 10, - NULL, /* cancellable */ - (GAsyncReadyCallback)get_agps_config_ready, - g_task_new (self, NULL, callback, user_data)); - qmi_message_pds_get_agps_config_input_unref (input); -} - -/*****************************************************************************/ -/* Set SUPL server */ - -static gboolean -location_set_supl_server_finish (MMIfaceModemLocation *self, - GAsyncResult *res, - GError **error) -{ - return g_task_propagate_boolean (G_TASK (res), error); -} - -static void -set_agps_config_ready (QmiClientPds *client, - GAsyncResult *res, - GTask *task) -{ - QmiMessagePdsSetAgpsConfigOutput *output = NULL; - GError *error = NULL; - - output = qmi_client_pds_set_agps_config_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_pds_set_agps_config_output_get_result (output, &error)) { - g_task_return_error (task, error); - g_object_unref (task); - return; - } - - qmi_message_pds_set_agps_config_output_unref (output); - - g_task_return_boolean (task, TRUE); - g_object_unref (task); -} - -static gboolean -parse_as_ip_port (const gchar *supl, - guint32 *out_ip, - guint32 *out_port) -{ - gboolean valid = FALSE; - gchar **split; - guint port; - guint32 ip; - - split = g_strsplit (supl, ":", -1); - if (g_strv_length (split) != 2) - goto out; - - if (!mm_get_uint_from_str (split[1], &port)) - goto out; - if (port == 0 || port > G_MAXUINT16) - goto out; - if (inet_pton (AF_INET, split[0], &ip) <= 0) - goto out; - - *out_ip = ip; - *out_port = port; - valid = TRUE; - -out: - g_strfreev (split); - return valid; -} - -static gboolean -parse_as_url (const gchar *supl, - GArray **out_url) -{ - gchar *utf16; - gsize utf16_len; - - utf16 = g_convert (supl, -1, "UTF-16BE", "UTF-8", NULL, &utf16_len, NULL); - *out_url = g_array_append_vals (g_array_sized_new (FALSE, FALSE, sizeof (guint8), utf16_len), - utf16, - utf16_len); - g_free (utf16); - return TRUE; -} - -static void -location_set_supl_server (MMIfaceModemLocation *self, - const gchar *supl, - GAsyncReadyCallback callback, - gpointer user_data) -{ - QmiClient *client = NULL; - QmiMessagePdsSetAgpsConfigInput *input; - guint32 ip; - guint32 port; - GArray *url; - - if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), - QMI_SERVICE_PDS, &client, - callback, user_data)) { - return; - } - - input = qmi_message_pds_set_agps_config_input_new (); - - /* For multimode devices, prefer UMTS by default */ - if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) - qmi_message_pds_set_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_UMTS, NULL); - else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) - qmi_message_pds_set_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_CDMA, NULL); - - if (parse_as_ip_port (supl, &ip, &port)) - qmi_message_pds_set_agps_config_input_set_location_server_address (input, ip, port, NULL); - else if (parse_as_url (supl, &url)) { - qmi_message_pds_set_agps_config_input_set_location_server_url (input, url, NULL); - g_array_unref (url); - } else - g_assert_not_reached (); - - qmi_client_pds_set_agps_config ( - QMI_CLIENT_PDS (client), - input, - 10, - NULL, /* cancellable */ - (GAsyncReadyCallback)set_agps_config_ready, - g_task_new (self, NULL, callback, user_data)); - qmi_message_pds_set_agps_config_input_unref (input); -} - -/*****************************************************************************/ /* Disable location gathering (Location interface) */ -typedef struct { - QmiClientPds *client; - MMModemLocationSource source; - /* Default tracking session (for A-GPS disabling) */ - QmiPdsOperatingMode session_operation; - guint8 data_timeout; - guint32 interval; - guint32 accuracy_threshold; -} DisableLocationGatheringContext; - -static void -disable_location_gathering_context_free (DisableLocationGatheringContext *ctx) -{ - if (ctx->client) - g_object_unref (ctx->client); - g_slice_free (DisableLocationGatheringContext, ctx); -} - static gboolean -disable_location_gathering_finish (MMIfaceModemLocation *self, - GAsyncResult *res, - GError **error) +disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void -gps_service_state_stop_ready (QmiClientPds *client, - GAsyncResult *res, - GTask *task) +shared_qmi_disable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) { - MMBroadbandModemQmi *self; - DisableLocationGatheringContext *ctx; - QmiMessagePdsSetGpsServiceStateOutput *output = NULL; GError *error = NULL; - output = qmi_client_pds_set_gps_service_state_finish (client, res, &error); - if (!output) { - g_prefix_error (&error, "QMI operation failed: "); + if (!mm_shared_qmi_disable_location_gathering_finish (self, res, &error)) g_task_return_error (task, error); - g_object_unref (task); - return; - } - - if (!qmi_message_pds_set_gps_service_state_output_get_result (output, &error)) { - if (!g_error_matches (error, - QMI_PROTOCOL_ERROR, - QMI_PROTOCOL_ERROR_NO_EFFECT)) { - g_prefix_error (&error, "Couldn't set GPS service state: "); - g_task_return_error (task, error); - g_object_unref (task); - qmi_message_pds_set_gps_service_state_output_unref (output); - return; - } - - g_error_free (error); - } - - self = g_task_get_source_object (task); - ctx = g_task_get_task_data (task); - - qmi_message_pds_set_gps_service_state_output_unref (output); - - g_assert (self->priv->location_event_report_indication_id != 0); - g_signal_handler_disconnect (client, self->priv->location_event_report_indication_id); - self->priv->location_event_report_indication_id = 0; - - mm_dbg ("GPS stopped"); - self->priv->enabled_sources &= ~ctx->source; - g_task_return_boolean (task, TRUE); - g_object_unref (task); -} - -static void -set_default_tracking_session_stop_ready (QmiClientPds *client, - GAsyncResult *res, - GTask *task) -{ - MMBroadbandModemQmi *self; - DisableLocationGatheringContext *ctx; - QmiMessagePdsSetDefaultTrackingSessionOutput *output = NULL; - GError *error = NULL; - - output = qmi_client_pds_set_default_tracking_session_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_pds_set_default_tracking_session_output_get_result (output, &error)) { - g_prefix_error (&error, "Couldn't set default tracking session: "); - g_task_return_error (task, error); - g_object_unref (task); - qmi_message_pds_set_default_tracking_session_output_unref (output); - return; - } - - self = g_task_get_source_object (task); - ctx = g_task_get_task_data (task); - - qmi_message_pds_set_default_tracking_session_output_unref (output); - - /* Done */ - mm_dbg ("A-GPS disabled"); - self->priv->enabled_sources &= ~ctx->source; - g_task_return_boolean (task, TRUE); - g_object_unref (task); -} - -static void -get_default_tracking_session_stop_ready (QmiClientPds *client, - GAsyncResult *res, - GTask *task) -{ - MMBroadbandModemQmi *self; - DisableLocationGatheringContext *ctx; - QmiMessagePdsSetDefaultTrackingSessionInput *input; - QmiMessagePdsGetDefaultTrackingSessionOutput *output = NULL; - GError *error = NULL; - - output = qmi_client_pds_get_default_tracking_session_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_pds_get_default_tracking_session_output_get_result (output, &error)) { - g_prefix_error (&error, "Couldn't get default tracking session: "); - g_task_return_error (task, error); - g_object_unref (task); - qmi_message_pds_get_default_tracking_session_output_unref (output); - return; - } - - self = g_task_get_source_object (task); - ctx = g_task_get_task_data (task); - - qmi_message_pds_get_default_tracking_session_output_get_info ( - output, - &ctx->session_operation, - &ctx->data_timeout, - &ctx->interval, - &ctx->accuracy_threshold, - NULL); - - qmi_message_pds_get_default_tracking_session_output_unref (output); - - if (ctx->session_operation == QMI_PDS_OPERATING_MODE_STANDALONE) { - /* Done */ - mm_dbg ("A-GPS already disabled"); - self->priv->enabled_sources &= ~ctx->source; + else g_task_return_boolean (task, TRUE); - g_object_unref (task); - return; - } - - input = qmi_message_pds_set_default_tracking_session_input_new (); - qmi_message_pds_set_default_tracking_session_input_set_info ( - input, - QMI_PDS_OPERATING_MODE_STANDALONE, - ctx->data_timeout, - ctx->interval, - ctx->accuracy_threshold, - NULL); - qmi_client_pds_set_default_tracking_session ( - ctx->client, - input, - 10, - NULL, /* cancellable */ - (GAsyncReadyCallback)set_default_tracking_session_stop_ready, - task); - qmi_message_pds_set_default_tracking_session_input_unref (input); + g_object_unref (task); } static void disable_location_gathering (MMIfaceModemLocation *_self, MMModemLocationSource source, - GAsyncReadyCallback callback, - gpointer user_data) + GAsyncReadyCallback callback, + gpointer user_data) { MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); - DisableLocationGatheringContext *ctx; - GTask *task; - QmiClient *client = NULL; + GTask *task; task = g_task_new (self, NULL, callback, user_data); /* Nothing to be done to disable 3GPP or CDMA locations */ - if (source == MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI || - source == MM_MODEM_LOCATION_SOURCE_CDMA_BS) { - /* Just mark it as disabled */ - self->priv->enabled_sources &= ~source; - g_task_return_boolean (task, TRUE); - g_object_unref (task); - return; - } - - /* Setup context and client */ - if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), - QMI_SERVICE_PDS, &client, - callback, user_data)) { - g_object_unref (task); - return; - } - ctx = g_slice_new0 (DisableLocationGatheringContext); - ctx->client = g_object_ref (client); - ctx->source = source; - - g_task_set_task_data (task, ctx, (GDestroyNotify)disable_location_gathering_context_free); - - /* Disable A-GPS? */ - if (source == MM_MODEM_LOCATION_SOURCE_AGPS) { - qmi_client_pds_get_default_tracking_session ( - ctx->client, - NULL, - 10, - NULL, /* cancellable */ - (GAsyncReadyCallback)get_default_tracking_session_stop_ready, - task); - return; - } - - /* Only stop GPS engine if no GPS-related sources enabled */ - if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { - MMModemLocationSource tmp; - - /* If no more GPS sources enabled, stop GPS */ - tmp = self->priv->enabled_sources; - tmp &= ~source; - if (!(tmp & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW))) { - QmiMessagePdsSetGpsServiceStateInput *input; - - input = qmi_message_pds_set_gps_service_state_input_new (); - qmi_message_pds_set_gps_service_state_input_set_state (input, FALSE, NULL); - qmi_client_pds_set_gps_service_state ( - ctx->client, - input, - 10, - NULL, /* cancellable */ - (GAsyncReadyCallback)gps_service_state_stop_ready, - task); - qmi_message_pds_set_gps_service_state_input_unref (input); - return; - } - - /* Otherwise, we have more GPS sources enabled, we shouldn't stop GPS, just - * return */ + if (source == MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI || source == MM_MODEM_LOCATION_SOURCE_CDMA_BS) self->priv->enabled_sources &= ~source; - g_task_return_boolean (task, TRUE); - g_object_unref (task); - return; - } - /* The QMI implementation has a fixed set of capabilities supported. Arriving - * here means we tried to disable one which wasn't set as supported, which should - * not happen */ - g_assert_not_reached (); + mm_shared_qmi_disable_location_gathering ( + _self, + source, + (GAsyncReadyCallback) shared_qmi_disable_location_gathering_ready, + task); } /*****************************************************************************/ /* Enable location gathering (Location interface) */ -static void -location_event_report_indication_cb (QmiClientPds *client, - QmiIndicationPdsEventReportOutput *output, - MMBroadbandModemQmi *self) -{ - QmiPdsPositionSessionStatus session_status; - const gchar *nmea; - - if (qmi_indication_pds_event_report_output_get_position_session_status ( - output, - &session_status, - NULL)) { - mm_dbg ("[GPS] session status changed: '%s'", - qmi_pds_position_session_status_get_string (session_status)); - } - - if (qmi_indication_pds_event_report_output_get_nmea_position ( - output, - &nmea, - NULL)) { - mm_dbg ("[NMEA] %s", nmea); - mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), nmea); - } -} - -typedef struct { - QmiClientPds *client; - MMModemLocationSource source; - /* Default tracking session (for A-GPS enabling) */ - QmiPdsOperatingMode session_operation; - guint8 data_timeout; - guint32 interval; - guint32 accuracy_threshold; -} EnableLocationGatheringContext; - -static void -enable_location_gathering_context_free (EnableLocationGatheringContext *ctx) -{ - if (ctx->client) - g_object_unref (ctx->client); - g_slice_free (EnableLocationGatheringContext, ctx); -} - static gboolean enable_location_gathering_finish (MMIfaceModemLocation *self, GAsyncResult *res, @@ -8710,273 +8199,26 @@ enable_location_gathering_finish (MMIfaceModemLocation *self, } static void -ser_location_ready (QmiClientPds *client, - GAsyncResult *res, - GTask *task) -{ - MMBroadbandModemQmi *self; - EnableLocationGatheringContext *ctx; - QmiMessagePdsSetEventReportOutput *output = NULL; - GError *error = NULL; - - output = qmi_client_pds_set_event_report_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_pds_set_event_report_output_get_result (output, &error)) { - g_prefix_error (&error, "Couldn't set event report: "); - g_task_return_error (task, error); - g_object_unref (task); - qmi_message_pds_set_event_report_output_unref (output); - return; - } - - qmi_message_pds_set_event_report_output_unref (output); - - self = g_task_get_source_object (task); - ctx = g_task_get_task_data (task); - - mm_dbg ("Adding location event report indication handling"); - g_assert (self->priv->location_event_report_indication_id == 0); - self->priv->location_event_report_indication_id = - g_signal_connect (client, - "event-report", - G_CALLBACK (location_event_report_indication_cb), - self); - - /* Done */ - mm_dbg ("GPS started"); - self->priv->enabled_sources |= ctx->source; - g_task_return_boolean (task, TRUE); - g_object_unref (task); -} - -static void -auto_tracking_state_start_ready (QmiClientPds *client, - GAsyncResult *res, - GTask *task) +shared_qmi_enable_location_gathering_ready (MMIfaceModemLocation *_self, + GAsyncResult *res, + GTask *task) { - EnableLocationGatheringContext *ctx; - QmiMessagePdsSetEventReportInput *input; - QmiMessagePdsSetAutoTrackingStateOutput *output = NULL; - GError *error = NULL; + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); + MMModemLocationSource source; + GError *error = NULL; - output = qmi_client_pds_set_auto_tracking_state_finish (client, res, &error); - if (!output) { - g_prefix_error (&error, "QMI operation failed: "); + if (!mm_shared_qmi_enable_location_gathering_finish (_self, res, &error)) { g_task_return_error (task, error); g_object_unref (task); return; } - if (!qmi_message_pds_set_auto_tracking_state_output_get_result (output, &error)) { - if (!g_error_matches (error, - QMI_PROTOCOL_ERROR, - QMI_PROTOCOL_ERROR_NO_EFFECT)) { - g_prefix_error (&error, "Couldn't set auto-tracking state: "); - g_task_return_error (task, error); - g_object_unref (task); - qmi_message_pds_set_auto_tracking_state_output_unref (output); - return; - } - g_error_free (error); - } - - qmi_message_pds_set_auto_tracking_state_output_unref (output); - - ctx = g_task_get_task_data (task); - - /* Only gather standard NMEA traces */ - input = qmi_message_pds_set_event_report_input_new (); - qmi_message_pds_set_event_report_input_set_nmea_position_reporting (input, TRUE, NULL); - qmi_client_pds_set_event_report ( - ctx->client, - input, - 5, - NULL, - (GAsyncReadyCallback)ser_location_ready, - task); - qmi_message_pds_set_event_report_input_unref (input); -} - -static void -gps_service_state_start_ready (QmiClientPds *client, - GAsyncResult *res, - GTask *task) -{ - EnableLocationGatheringContext *ctx; - QmiMessagePdsSetAutoTrackingStateInput *input; - QmiMessagePdsSetGpsServiceStateOutput *output = NULL; - GError *error = NULL; - - output = qmi_client_pds_set_gps_service_state_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_pds_set_gps_service_state_output_get_result (output, &error)) { - if (!g_error_matches (error, - QMI_PROTOCOL_ERROR, - QMI_PROTOCOL_ERROR_NO_EFFECT)) { - g_prefix_error (&error, "Couldn't set GPS service state: "); - g_task_return_error (task, error); - g_object_unref (task); - qmi_message_pds_set_gps_service_state_output_unref (output); - return; - } - g_error_free (error); - } - - qmi_message_pds_set_gps_service_state_output_unref (output); - - ctx = g_task_get_task_data (task); - - /* Enable auto-tracking for a continuous fix */ - input = qmi_message_pds_set_auto_tracking_state_input_new (); - qmi_message_pds_set_auto_tracking_state_input_set_state (input, TRUE, NULL); - qmi_client_pds_set_auto_tracking_state ( - ctx->client, - input, - 10, - NULL, /* cancellable */ - (GAsyncReadyCallback)auto_tracking_state_start_ready, - task); - qmi_message_pds_set_auto_tracking_state_input_unref (input); -} - -static void -set_default_tracking_session_start_ready (QmiClientPds *client, - GAsyncResult *res, - GTask *task) -{ - MMBroadbandModemQmi *self; - EnableLocationGatheringContext *ctx; - QmiMessagePdsSetDefaultTrackingSessionOutput *output = NULL; - GError *error = NULL; - - output = qmi_client_pds_set_default_tracking_session_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_pds_set_default_tracking_session_output_get_result (output, &error)) { - g_prefix_error (&error, "Couldn't set default tracking session: "); - g_task_return_error (task, error); - g_object_unref (task); - qmi_message_pds_set_default_tracking_session_output_unref (output); - return; - } - - qmi_message_pds_set_default_tracking_session_output_unref (output); - - self = g_task_get_source_object (task); - ctx = g_task_get_task_data (task); - - /* Done */ - mm_dbg ("A-GPS enabled"); - self->priv->enabled_sources |= ctx->source; - g_task_return_boolean (task, TRUE); - g_object_unref (task); -} - -static void -get_default_tracking_session_start_ready (QmiClientPds *client, - GAsyncResult *res, - GTask *task) -{ - MMBroadbandModemQmi *self; - EnableLocationGatheringContext *ctx; - QmiMessagePdsSetDefaultTrackingSessionInput *input; - QmiMessagePdsGetDefaultTrackingSessionOutput *output = NULL; - GError *error = NULL; - - output = qmi_client_pds_get_default_tracking_session_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_pds_get_default_tracking_session_output_get_result (output, &error)) { - g_prefix_error (&error, "Couldn't get default tracking session: "); - g_task_return_error (task, error); - g_object_unref (task); - qmi_message_pds_get_default_tracking_session_output_unref (output); - return; - } - - self = g_task_get_source_object (task); - ctx = g_task_get_task_data (task); - - qmi_message_pds_get_default_tracking_session_output_get_info ( - output, - &ctx->session_operation, - &ctx->data_timeout, - &ctx->interval, - &ctx->accuracy_threshold, - NULL); - - qmi_message_pds_get_default_tracking_session_output_unref (output); - - if (ctx->session_operation == QMI_PDS_OPERATING_MODE_MS_ASSISTED) { - /* Done */ - mm_dbg ("A-GPS already enabled"); - self->priv->enabled_sources |= ctx->source; - g_task_return_boolean (task, TRUE); - g_object_unref (task); - return; - } - - input = qmi_message_pds_set_default_tracking_session_input_new (); - qmi_message_pds_set_default_tracking_session_input_set_info ( - input, - QMI_PDS_OPERATING_MODE_MS_ASSISTED, - ctx->data_timeout, - ctx->interval, - ctx->accuracy_threshold, - NULL); - qmi_client_pds_set_default_tracking_session ( - ctx->client, - input, - 10, - NULL, /* cancellable */ - (GAsyncReadyCallback)set_default_tracking_session_start_ready, - task); - qmi_message_pds_set_default_tracking_session_input_unref (input); -} - -static void -parent_enable_location_gathering_ready (MMIfaceModemLocation *_self, - GAsyncResult *res, - GTask *task) -{ - MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); - EnableLocationGatheringContext *ctx; - GError *error = NULL; - QmiClient *client; - - if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) { - g_task_return_error (task, error); - g_object_unref (task); - return; - } - - ctx = g_task_get_task_data (task); + source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task)); /* Nothing else needed in the QMI side for LAC/CI */ - if (ctx->source == MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI) { - self->priv->enabled_sources |= ctx->source; + if (source == MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI && + mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) { + self->priv->enabled_sources |= source; g_task_return_boolean (task, TRUE); g_object_unref (task); return; @@ -8986,95 +8228,37 @@ parent_enable_location_gathering_ready (MMIfaceModemLocation *_self, * location source, so that we get up to date BS location information. * Note that we don't care for when the registration checks get finished. */ - if (ctx->source == MM_MODEM_LOCATION_SOURCE_CDMA_BS && + if (source == MM_MODEM_LOCATION_SOURCE_CDMA_BS && mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) { /* Reload registration to get LAC/CI */ mm_iface_modem_cdma_run_registration_checks (MM_IFACE_MODEM_CDMA (self), NULL, NULL); /* Just mark it as enabled */ - self->priv->enabled_sources |= ctx->source; - g_task_return_boolean (task, TRUE); - g_object_unref (task); - return; - } - - /* Setup context and client */ - client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), - QMI_SERVICE_PDS, - MM_PORT_QMI_FLAG_DEFAULT, - &error); - if (!client) { - g_task_return_error (task, error); - g_object_unref (task); - return; - } - ctx->client = g_object_ref (client); - - /* Enabling A-GPS? */ - if (ctx->source == MM_MODEM_LOCATION_SOURCE_AGPS) { - qmi_client_pds_get_default_tracking_session ( - ctx->client, - NULL, - 10, - NULL, /* cancellable */ - (GAsyncReadyCallback)get_default_tracking_session_start_ready, - task); - return; - } - - /* NMEA and RAW are both enabled in the same way */ - if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { - /* Only start GPS engine if not done already */ - if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | - MM_MODEM_LOCATION_SOURCE_GPS_RAW))) { - QmiMessagePdsSetGpsServiceStateInput *input; - - input = qmi_message_pds_set_gps_service_state_input_new (); - qmi_message_pds_set_gps_service_state_input_set_state (input, TRUE, NULL); - qmi_client_pds_set_gps_service_state ( - ctx->client, - input, - 10, - NULL, /* cancellable */ - (GAsyncReadyCallback)gps_service_state_start_ready, - task); - qmi_message_pds_set_gps_service_state_input_unref (input); - return; - } - - /* GPS already started, we're done */ - self->priv->enabled_sources |= ctx->source; + self->priv->enabled_sources |= source; g_task_return_boolean (task, TRUE); g_object_unref (task); return; } - /* The QMI implementation has a fixed set of capabilities supported. Arriving - * here means we tried to enable one which wasn't set as supported, which should - * not happen */ - g_assert_not_reached (); + /* Otherwise, we're done */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); } static void -enable_location_gathering (MMIfaceModemLocation *self, - MMModemLocationSource source, - GAsyncReadyCallback callback, - gpointer user_data) +enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) { - EnableLocationGatheringContext *ctx; GTask *task; - ctx = g_slice_new0 (EnableLocationGatheringContext); - /* Store source to enable, there will be only one! */ - ctx->source = source; - task = g_task_new (self, NULL, callback, user_data); - g_task_set_task_data (task, ctx, (GDestroyNotify)enable_location_gathering_context_free); + g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL); - /* Chain up parent's gathering enable */ - iface_modem_location_parent->enable_location_gathering ( + mm_shared_qmi_enable_location_gathering ( self, - ctx->source, - (GAsyncReadyCallback)parent_enable_location_gathering_ready, + source, + (GAsyncReadyCallback)shared_qmi_enable_location_gathering_ready, task); } @@ -11383,14 +10567,15 @@ iface_modem_location_init (MMIfaceModemLocation *iface) iface->load_capabilities = location_load_capabilities; iface->load_capabilities_finish = location_load_capabilities_finish; - iface->load_supl_server = location_load_supl_server; - iface->load_supl_server_finish = location_load_supl_server_finish; - iface->set_supl_server = location_set_supl_server; - iface->set_supl_server_finish = location_set_supl_server_finish; iface->enable_location_gathering = enable_location_gathering; iface->enable_location_gathering_finish = enable_location_gathering_finish; iface->disable_location_gathering = disable_location_gathering; iface->disable_location_gathering_finish = disable_location_gathering_finish; + + iface->load_supl_server = mm_shared_qmi_location_load_supl_server; + iface->load_supl_server_finish = mm_shared_qmi_location_load_supl_server_finish; + iface->set_supl_server = mm_shared_qmi_location_set_supl_server; + iface->set_supl_server_finish = mm_shared_qmi_location_set_supl_server_finish; } static void @@ -11440,10 +10625,17 @@ iface_modem_firmware_init (MMIfaceModemFirmware *iface) iface->change_current_finish = firmware_change_current_finish; } +static MMIfaceModemLocation * +peek_parent_location_interface (MMSharedQmi *self) +{ + return iface_modem_location_parent; +} + static void shared_qmi_init (MMSharedQmi *iface) { iface->peek_client = shared_qmi_peek_client; + iface->peek_parent_location_interface = peek_parent_location_interface; } static void diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c index 84ec75d4..8bcfe55f 100644 --- a/src/mm-shared-qmi.c +++ b/src/mm-shared-qmi.c @@ -24,10 +24,1054 @@ #include <libqmi-glib.h> +#include "mm-log.h" #include "mm-iface-modem.h" +#include "mm-iface-modem-location.h" #include "mm-shared-qmi.h" /*****************************************************************************/ +/* Private data context */ + +#define PRIVATE_TAG "shared-qmi-private-tag" +static GQuark private_quark; + +typedef struct { + /* Location helpers */ + MMIfaceModemLocation *iface_modem_location_parent; + MMModemLocationSource enabled_sources; + QmiClient *pds_client; + gulong location_event_report_indication_id; +} Private; + +static void +private_free (Private *priv) +{ + if (priv->location_event_report_indication_id) + g_signal_handler_disconnect (priv->pds_client, priv->location_event_report_indication_id); + if (priv->pds_client) + g_object_unref (priv->pds_client); + g_slice_free (Private, priv); +} + +static Private * +get_private (MMSharedQmi *self) +{ + Private *priv; + + if (G_UNLIKELY (!private_quark)) + private_quark = g_quark_from_static_string (PRIVATE_TAG); + + priv = g_object_get_qdata (G_OBJECT (self), private_quark); + if (!priv) { + priv = g_slice_new0 (Private); + + /* Setup parent class' MMIfaceModemLocation */ + g_assert (MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface); + priv->iface_modem_location_parent = MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface (self); + + g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); + } + + return priv; +} + +/*****************************************************************************/ +/* Location: Set SUPL server */ + +gboolean +mm_shared_qmi_location_set_supl_server_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +set_agps_config_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + QmiMessagePdsSetAgpsConfigOutput *output; + GError *error = NULL; + + output = qmi_client_pds_set_agps_config_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_pds_set_agps_config_output_get_result (output, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); + + qmi_message_pds_set_agps_config_output_unref (output); +} + +static gboolean +parse_as_ip_port (const gchar *supl, + guint32 *out_ip, + guint32 *out_port) +{ + gboolean valid = FALSE; + gchar **split; + guint port; + guint32 ip; + + split = g_strsplit (supl, ":", -1); + if (g_strv_length (split) != 2) + goto out; + + if (!mm_get_uint_from_str (split[1], &port)) + goto out; + if (port == 0 || port > G_MAXUINT16) + goto out; + if (inet_pton (AF_INET, split[0], &ip) <= 0) + goto out; + + *out_ip = ip; + *out_port = port; + valid = TRUE; + +out: + g_strfreev (split); + return valid; +} + +static gboolean +parse_as_url (const gchar *supl, + GArray **out_url) +{ + gchar *utf16; + gsize utf16_len; + + utf16 = g_convert (supl, -1, "UTF-16BE", "UTF-8", NULL, &utf16_len, NULL); + *out_url = g_array_append_vals (g_array_sized_new (FALSE, FALSE, sizeof (guint8), utf16_len), + utf16, + utf16_len); + g_free (utf16); + return TRUE; +} + +void +mm_shared_qmi_location_set_supl_server (MMIfaceModemLocation *self, + const gchar *supl, + GAsyncReadyCallback callback, + gpointer user_data) +{ + QmiClient *client = NULL; + QmiMessagePdsSetAgpsConfigInput *input; + guint32 ip; + guint32 port; + GArray *url; + + if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), + QMI_SERVICE_PDS, &client, + callback, user_data)) + return; + + input = qmi_message_pds_set_agps_config_input_new (); + + /* For multimode devices, prefer UMTS by default */ + if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) + qmi_message_pds_set_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_UMTS, NULL); + else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) + qmi_message_pds_set_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_CDMA, NULL); + + if (parse_as_ip_port (supl, &ip, &port)) + qmi_message_pds_set_agps_config_input_set_location_server_address (input, ip, port, NULL); + else if (parse_as_url (supl, &url)) { + qmi_message_pds_set_agps_config_input_set_location_server_url (input, url, NULL); + g_array_unref (url); + } else + g_assert_not_reached (); + + qmi_client_pds_set_agps_config ( + QMI_CLIENT_PDS (client), + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)set_agps_config_ready, + g_task_new (self, NULL, callback, user_data)); + qmi_message_pds_set_agps_config_input_unref (input); +} + +/*****************************************************************************/ +/* Location: Load SUPL server */ + +gchar * +mm_shared_qmi_location_load_supl_server_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +get_agps_config_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + QmiMessagePdsGetAgpsConfigOutput *output = NULL; + GError *error = NULL; + guint32 ip = 0; + guint32 port = 0; + GArray *url = NULL; + gchar *str = NULL; + + output = qmi_client_pds_get_agps_config_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + goto out; + } + + if (!qmi_message_pds_get_agps_config_output_get_result (output, &error)) + goto out; + + /* Prefer IP/PORT to URL */ + if (qmi_message_pds_get_agps_config_output_get_location_server_address ( + output, + &ip, + &port, + NULL) && + ip != 0 && + port != 0) { + struct in_addr a = { .s_addr = ip }; + gchar buf[INET_ADDRSTRLEN + 1]; + + memset (buf, 0, sizeof (buf)); + + if (!inet_ntop (AF_INET, &a, buf, sizeof (buf) - 1)) { + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Cannot convert numeric IP address (%u) to string", ip); + goto out; + } + + str = g_strdup_printf ("%s:%u", buf, port); + goto out; + } + + if (qmi_message_pds_get_agps_config_output_get_location_server_url ( + output, + &url, + NULL) && + url->len > 0) { + str = g_convert (url->data, url->len, "UTF-8", "UTF-16BE", NULL, NULL, NULL); + } + + if (!str) + str = g_strdup (""); + +out: + if (error) + g_task_return_error (task, error); + else { + g_assert (str); + g_task_return_pointer (task, str, g_free); + } + g_object_unref (task); + + if (output) + qmi_message_pds_get_agps_config_output_unref (output); +} + +void +mm_shared_qmi_location_load_supl_server (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + QmiClient *client = NULL; + QmiMessagePdsGetAgpsConfigInput *input; + + if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), + QMI_SERVICE_PDS, &client, + callback, user_data)) + return; + + input = qmi_message_pds_get_agps_config_input_new (); + + /* For multimode devices, prefer UMTS by default */ + if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) + qmi_message_pds_get_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_UMTS, NULL); + else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) + qmi_message_pds_get_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_CDMA, NULL); + + qmi_client_pds_get_agps_config ( + QMI_CLIENT_PDS (client), + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)get_agps_config_ready, + g_task_new (self, NULL, callback, user_data)); + qmi_message_pds_get_agps_config_input_unref (input); +} + +/*****************************************************************************/ +/* Location: disable */ + +typedef struct { + QmiClientPds *client; + MMModemLocationSource source; +} DisableLocationGatheringContext; + +static void +disable_location_gathering_context_free (DisableLocationGatheringContext *ctx) +{ + if (ctx->client) + g_object_unref (ctx->client); + g_slice_free (DisableLocationGatheringContext, ctx); +} + +gboolean +mm_shared_qmi_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_service_state_stop_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + DisableLocationGatheringContext *ctx; + QmiMessagePdsSetGpsServiceStateOutput *output; + GError *error = NULL; + + output = qmi_client_pds_set_gps_service_state_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_pds_set_gps_service_state_output_get_result (output, &error)) { + if (!g_error_matches (error, + QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_NO_EFFECT)) { + g_prefix_error (&error, "Couldn't set GPS service state: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_set_gps_service_state_output_unref (output); + return; + } + + g_error_free (error); + } + + qmi_message_pds_set_gps_service_state_output_unref (output); + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + g_assert (priv->location_event_report_indication_id != 0); + g_assert (priv->pds_client); + + g_signal_handler_disconnect (priv->pds_client, priv->location_event_report_indication_id); + priv->location_event_report_indication_id = 0; + g_clear_object (&priv->pds_client); + + mm_dbg ("GPS stopped"); + priv->enabled_sources &= ~ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +set_default_tracking_session_stop_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + DisableLocationGatheringContext *ctx; + QmiMessagePdsSetDefaultTrackingSessionOutput *output; + GError *error = NULL; + + output = qmi_client_pds_set_default_tracking_session_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_pds_set_default_tracking_session_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't set default tracking session: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_set_default_tracking_session_output_unref (output); + return; + } + + qmi_message_pds_set_default_tracking_session_output_unref (output); + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + /* Done */ + mm_dbg ("A-GPS disabled"); + priv->enabled_sources &= ~ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +get_default_tracking_session_stop_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + DisableLocationGatheringContext *ctx; + QmiMessagePdsSetDefaultTrackingSessionInput *input; + QmiMessagePdsGetDefaultTrackingSessionOutput *output; + GError *error = NULL; + QmiPdsOperatingMode session_operation; + guint8 data_timeout; + guint32 interval; + guint32 accuracy_threshold; + + output = qmi_client_pds_get_default_tracking_session_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_pds_get_default_tracking_session_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get default tracking session: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_get_default_tracking_session_output_unref (output); + return; + } + + qmi_message_pds_get_default_tracking_session_output_get_info ( + output, + &session_operation, + &data_timeout, + &interval, + &accuracy_threshold, + NULL); + + qmi_message_pds_get_default_tracking_session_output_unref (output); + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + if (session_operation == QMI_PDS_OPERATING_MODE_STANDALONE) { + /* Done */ + mm_dbg ("A-GPS already disabled"); + priv->enabled_sources &= ~ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + input = qmi_message_pds_set_default_tracking_session_input_new (); + qmi_message_pds_set_default_tracking_session_input_set_info ( + input, + QMI_PDS_OPERATING_MODE_STANDALONE, + data_timeout, + interval, + accuracy_threshold, + NULL); + qmi_client_pds_set_default_tracking_session ( + ctx->client, + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)set_default_tracking_session_stop_ready, + task); + qmi_message_pds_set_default_tracking_session_input_unref (input); +} + +void +mm_shared_qmi_disable_location_gathering (MMIfaceModemLocation *_self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMSharedQmi *self; + Private *priv; + GTask *task; + DisableLocationGatheringContext *ctx; + + self = MM_SHARED_QMI (_self); + priv = get_private (self); + + task = g_task_new (self, NULL, callback, user_data); + + /* NOTE: no parent disable_location_gathering() implementation */ + + if (!(source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_AGPS))) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + if (!priv->pds_client) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "missing QMI PDS client"); + g_object_unref (task); + return; + } + + ctx = g_slice_new0 (DisableLocationGatheringContext); + ctx->source = source; + ctx->client = g_object_ref (priv->pds_client); + g_task_set_task_data (task, ctx, (GDestroyNotify)disable_location_gathering_context_free); + + /* Disable A-GPS? */ + if (source == MM_MODEM_LOCATION_SOURCE_AGPS) { + qmi_client_pds_get_default_tracking_session ( + ctx->client, + NULL, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)get_default_tracking_session_stop_ready, + task); + return; + } + + /* Only stop GPS engine if no GPS-related sources enabled */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + MMModemLocationSource tmp; + + /* If no more GPS sources enabled, stop GPS */ + tmp = priv->enabled_sources; + tmp &= ~source; + if (!(tmp & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW))) { + QmiMessagePdsSetGpsServiceStateInput *input; + + input = qmi_message_pds_set_gps_service_state_input_new (); + qmi_message_pds_set_gps_service_state_input_set_state (input, FALSE, NULL); + qmi_client_pds_set_gps_service_state ( + ctx->client, + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_service_state_stop_ready, + task); + qmi_message_pds_set_gps_service_state_input_unref (input); + return; + } + + /* Otherwise, we have more GPS sources enabled, we shouldn't stop GPS, just + * return */ + priv->enabled_sources &= ~source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* The QMI implementation has a fixed set of capabilities supported. Arriving + * here means we tried to disable one which wasn't set as supported, which should + * not happen */ + g_assert_not_reached (); +} + +/*****************************************************************************/ +/* Location: enable */ + +static void +location_event_report_indication_cb (QmiClientPds *client, + QmiIndicationPdsEventReportOutput *output, + MMSharedQmi *self) +{ + QmiPdsPositionSessionStatus session_status; + const gchar *nmea; + + if (qmi_indication_pds_event_report_output_get_position_session_status ( + output, + &session_status, + NULL)) { + mm_dbg ("[GPS] session status changed: '%s'", + qmi_pds_position_session_status_get_string (session_status)); + } + + if (qmi_indication_pds_event_report_output_get_nmea_position ( + output, + &nmea, + NULL)) { + mm_dbg ("[NMEA] %s", nmea); + mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), nmea); + } +} + +typedef struct { + QmiClientPds *client; + MMModemLocationSource source; +} EnableLocationGatheringContext; + +static void +enable_location_gathering_context_free (EnableLocationGatheringContext *ctx) +{ + if (ctx->client) + g_object_unref (ctx->client); + g_slice_free (EnableLocationGatheringContext, ctx); +} + +gboolean +mm_shared_qmi_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +ser_location_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + EnableLocationGatheringContext *ctx; + QmiMessagePdsSetEventReportOutput *output; + GError *error = NULL; + + output = qmi_client_pds_set_event_report_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_pds_set_event_report_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't set event report: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_set_event_report_output_unref (output); + return; + } + + qmi_message_pds_set_event_report_output_unref (output); + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + mm_dbg ("Adding location event report indication handling"); + g_assert (!priv->pds_client); + g_assert (priv->location_event_report_indication_id == 0); + priv->pds_client = g_object_ref (client); + priv->location_event_report_indication_id = + g_signal_connect (priv->pds_client, + "event-report", + G_CALLBACK (location_event_report_indication_cb), + self); + + mm_dbg ("GPS started"); + priv->enabled_sources |= ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +auto_tracking_state_start_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + QmiMessagePdsSetEventReportInput *input; + QmiMessagePdsSetAutoTrackingStateOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_pds_set_auto_tracking_state_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_pds_set_auto_tracking_state_output_get_result (output, &error)) { + if (!g_error_matches (error, + QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_NO_EFFECT)) { + g_prefix_error (&error, "Couldn't set auto-tracking state: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_set_auto_tracking_state_output_unref (output); + return; + } + g_error_free (error); + } + + qmi_message_pds_set_auto_tracking_state_output_unref (output); + + /* Only gather standard NMEA traces */ + input = qmi_message_pds_set_event_report_input_new (); + qmi_message_pds_set_event_report_input_set_nmea_position_reporting (input, TRUE, NULL); + qmi_client_pds_set_event_report ( + client, + input, + 5, + NULL, + (GAsyncReadyCallback)ser_location_ready, + task); + qmi_message_pds_set_event_report_input_unref (input); +} + +static void +gps_service_state_start_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + QmiMessagePdsSetAutoTrackingStateInput *input; + QmiMessagePdsSetGpsServiceStateOutput *output; + GError *error = NULL; + + output = qmi_client_pds_set_gps_service_state_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_pds_set_gps_service_state_output_get_result (output, &error)) { + if (!g_error_matches (error, + QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_NO_EFFECT)) { + g_prefix_error (&error, "Couldn't set GPS service state: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_set_gps_service_state_output_unref (output); + return; + } + g_error_free (error); + } + + qmi_message_pds_set_gps_service_state_output_unref (output); + + /* Enable auto-tracking for a continuous fix */ + input = qmi_message_pds_set_auto_tracking_state_input_new (); + qmi_message_pds_set_auto_tracking_state_input_set_state (input, TRUE, NULL); + qmi_client_pds_set_auto_tracking_state ( + client, + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)auto_tracking_state_start_ready, + task); + qmi_message_pds_set_auto_tracking_state_input_unref (input); +} + +static void +set_default_tracking_session_start_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + EnableLocationGatheringContext *ctx; + QmiMessagePdsSetDefaultTrackingSessionOutput *output; + GError *error = NULL; + + output = qmi_client_pds_set_default_tracking_session_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_pds_set_default_tracking_session_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't set default tracking session: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_set_default_tracking_session_output_unref (output); + return; + } + + qmi_message_pds_set_default_tracking_session_output_unref (output); + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + /* Done */ + mm_dbg ("A-GPS enabled"); + priv->enabled_sources |= ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +get_default_tracking_session_start_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + EnableLocationGatheringContext *ctx; + QmiMessagePdsSetDefaultTrackingSessionInput *input; + QmiMessagePdsGetDefaultTrackingSessionOutput *output; + GError *error = NULL; + QmiPdsOperatingMode session_operation; + guint8 data_timeout; + guint32 interval; + guint32 accuracy_threshold; + + output = qmi_client_pds_get_default_tracking_session_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_pds_get_default_tracking_session_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get default tracking session: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_get_default_tracking_session_output_unref (output); + return; + } + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + qmi_message_pds_get_default_tracking_session_output_get_info ( + output, + &session_operation, + &data_timeout, + &interval, + &accuracy_threshold, + NULL); + + qmi_message_pds_get_default_tracking_session_output_unref (output); + + if (session_operation == QMI_PDS_OPERATING_MODE_MS_ASSISTED) { + mm_dbg ("A-GPS already enabled"); + priv->enabled_sources |= ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + input = qmi_message_pds_set_default_tracking_session_input_new (); + qmi_message_pds_set_default_tracking_session_input_set_info ( + input, + QMI_PDS_OPERATING_MODE_MS_ASSISTED, + data_timeout, + interval, + accuracy_threshold, + NULL); + qmi_client_pds_set_default_tracking_session ( + ctx->client, + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)set_default_tracking_session_start_ready, + task); + qmi_message_pds_set_default_tracking_session_input_unref (input); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *_self, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self = MM_SHARED_QMI (_self); + Private *priv; + EnableLocationGatheringContext *ctx; + GError *error = NULL; + QmiClient *client; + + priv = get_private (self); + + if (!priv->iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + + /* We only consider GPS related sources in this shared QMI implementation */ + if (!(ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_AGPS))) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Setup context and client */ + client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), + QMI_SERVICE_PDS, + MM_PORT_QMI_FLAG_DEFAULT, + &error); + if (!client) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + ctx->client = g_object_ref (client); + + /* Enabling A-GPS? */ + if (ctx->source == MM_MODEM_LOCATION_SOURCE_AGPS) { + qmi_client_pds_get_default_tracking_session ( + ctx->client, + NULL, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)get_default_tracking_session_start_ready, + task); + return; + } + + /* NMEA and RAW are both enabled in the same way */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + /* Only start GPS engine if not done already */ + if (!(priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW))) { + QmiMessagePdsSetGpsServiceStateInput *input; + + input = qmi_message_pds_set_gps_service_state_input_new (); + qmi_message_pds_set_gps_service_state_input_set_state (input, TRUE, NULL); + qmi_client_pds_set_gps_service_state ( + ctx->client, + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_service_state_start_ready, + task); + qmi_message_pds_set_gps_service_state_input_unref (input); + return; + } + + /* GPS already started, we're done */ + priv->enabled_sources |= ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* The shared QMI implementation has a fixed set of capabilities supported. Arriving + * here means we tried to enable one which wasn't set as supported, which should + * not happen */ + g_assert_not_reached (); +} + +void +mm_shared_qmi_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EnableLocationGatheringContext *ctx; + GTask *task; + Private *priv; + + ctx = g_slice_new0 (EnableLocationGatheringContext); + ctx->source = source; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)enable_location_gathering_context_free); + + priv = get_private (MM_SHARED_QMI (self)); + g_assert (priv->iface_modem_location_parent); + g_assert (priv->iface_modem_location_parent->enable_location_gathering); + g_assert (priv->iface_modem_location_parent->enable_location_gathering_finish); + + /* Chain up parent's gathering enable */ + priv->iface_modem_location_parent->enable_location_gathering ( + self, + ctx->source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); +} + +/*****************************************************************************/ +/* Location: load capabilities */ + +MMModemLocationSource +mm_shared_qmi_location_load_capabilities_finish (MMIfaceModemLocation *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_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource)value; +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_QMI (self)); + + sources = priv->iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Now our own checks */ + + /* If we have support for the PDS client, GPS and A-GPS location is supported */ + if (mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_PDS, MM_PORT_QMI_FLAG_DEFAULT, NULL)) + sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_AGPS); + + /* So we're done, complete */ + g_task_return_int (task, sources); + g_object_unref (task); +} + +void +mm_shared_qmi_location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_QMI (self)); + g_assert (priv->iface_modem_location_parent); + g_assert (priv->iface_modem_location_parent->load_capabilities); + g_assert (priv->iface_modem_location_parent->load_capabilities_finish); + + priv->iface_modem_location_parent->load_capabilities (self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + task); +} + +/*****************************************************************************/ QmiClient * mm_shared_qmi_peek_client (MMSharedQmi *self, @@ -78,6 +1122,7 @@ mm_shared_qmi_get_type (void) shared_qmi_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedQmi", &info, 0); g_type_interface_add_prerequisite (shared_qmi_type, MM_TYPE_IFACE_MODEM); + g_type_interface_add_prerequisite (shared_qmi_type, MM_TYPE_IFACE_MODEM_LOCATION); } return shared_qmi_type; diff --git a/src/mm-shared-qmi.h b/src/mm-shared-qmi.h index b4e83e63..13b95850 100644 --- a/src/mm-shared-qmi.h +++ b/src/mm-shared-qmi.h @@ -25,6 +25,7 @@ #include <libqmi-glib.h> #include "mm-iface-modem.h" +#include "mm-iface-modem-location.h" #include "mm-port-qmi.h" #define MM_TYPE_SHARED_QMI (mm_shared_qmi_get_type ()) @@ -41,6 +42,9 @@ struct _MMSharedQmi { QmiService service, MMPortQmiFlag flag, GError **error); + + /* Peek location interface of the parent class of the object */ + MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedQmi *self); }; GType mm_shared_qmi_get_type (void); @@ -56,5 +60,40 @@ gboolean mm_shared_qmi_ensure_client (MMSharedQmi *self, GAsyncReadyCallback callback, gpointer user_data); +/* Shared QMI location support */ + +void mm_shared_qmi_location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data); +MMModemLocationSource mm_shared_qmi_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); +void mm_shared_qmi_enable_location_gathering (MMIfaceModemLocation *_self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_qmi_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); +void mm_shared_qmi_disable_location_gathering (MMIfaceModemLocation *_self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_qmi_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); +void mm_shared_qmi_location_load_supl_server (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data); +gchar *mm_shared_qmi_location_load_supl_server_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); +void mm_shared_qmi_location_set_supl_server (MMIfaceModemLocation *self, + const gchar *supl, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_qmi_location_set_supl_server_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error); #endif /* MM_SHARED_QMI_H */ |