diff options
-rw-r--r-- | src/mm-broadband-modem-mbim.c | 3 | ||||
-rw-r--r-- | src/mm-broadband-modem-qmi.c | 3 | ||||
-rw-r--r-- | src/mm-shared-qmi.c | 928 |
3 files changed, 864 insertions, 70 deletions
diff --git a/src/mm-broadband-modem-mbim.c b/src/mm-broadband-modem-mbim.c index 34ab7f86..45f19558 100644 --- a/src/mm-broadband-modem-mbim.c +++ b/src/mm-broadband-modem-mbim.c @@ -1884,7 +1884,8 @@ initialization_started (MMBroadbandModem *self, /* Setup services to open */ ctx->qmi_services[0] = QMI_SERVICE_DMS; ctx->qmi_services[1] = QMI_SERVICE_PDS; - ctx->qmi_services[2] = QMI_SERVICE_UNKNOWN; + ctx->qmi_services[2] = QMI_SERVICE_LOC; + ctx->qmi_services[3] = QMI_SERVICE_UNKNOWN; #endif /* Now open our MBIM port */ diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c index 805947cf..05d9ddbc 100644 --- a/src/mm-broadband-modem-qmi.c +++ b/src/mm-broadband-modem-qmi.c @@ -10295,7 +10295,8 @@ initialization_started (MMBroadbandModem *self, ctx->services[3] = QMI_SERVICE_PDS; ctx->services[4] = QMI_SERVICE_OMA; ctx->services[5] = QMI_SERVICE_UIM; - ctx->services[6] = QMI_SERVICE_UNKNOWN; + ctx->services[6] = QMI_SERVICE_LOC; + ctx->services[7] = QMI_SERVICE_UNKNOWN; /* Now open our QMI port */ mm_port_qmi_open (ctx->qmi, diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c index 62ca26c6..87f7fdce 100644 --- a/src/mm-shared-qmi.c +++ b/src/mm-shared-qmi.c @@ -28,6 +28,10 @@ #include "mm-iface-modem.h" #include "mm-iface-modem-location.h" #include "mm-shared-qmi.h" +#include "mm-modem-helpers-qmi.h" + +/* Default session id to use in LOC operations */ +#define DEFAULT_LOC_SESSION_ID 0x10 /*****************************************************************************/ /* Private data context */ @@ -41,6 +45,8 @@ typedef struct { MMModemLocationSource enabled_sources; QmiClient *pds_client; gulong pds_location_event_report_indication_id; + QmiClient *loc_client; + gulong loc_location_nmea_indication_id; } Private; static void @@ -50,6 +56,10 @@ private_free (Private *priv) g_signal_handler_disconnect (priv->pds_client, priv->pds_location_event_report_indication_id); if (priv->pds_client) g_object_unref (priv->pds_client); + if (priv->loc_location_nmea_indication_id) + g_signal_handler_disconnect (priv->loc_client, priv->loc_location_nmea_indication_id); + if (priv->loc_client) + g_object_unref (priv->loc_client); g_slice_free (Private, priv); } @@ -78,37 +88,24 @@ get_private (MMSharedQmi *self) /*****************************************************************************/ /* 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); -} +typedef struct { + QmiClient *client; + gchar *supl; + glong indication_id; + guint timeout_id; +} SetSuplServerContext; static void -set_agps_config_ready (QmiClientPds *client, - GAsyncResult *res, - GTask *task) +set_supl_server_context_free (SetSuplServerContext *ctx) { - 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 (ctx->client) { + if (ctx->timeout_id) + g_source_remove (ctx->timeout_id); + if (ctx->indication_id) + g_signal_handler_disconnect (ctx->client, ctx->indication_id); + g_object_unref (ctx->client); } - - 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); + g_slice_free (SetSuplServerContext, ctx); } static gboolean @@ -142,36 +139,65 @@ out: } static gboolean -parse_as_url (const gchar *supl, - GArray **out_url) +parse_as_utf16_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); + 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) + +gboolean +mm_shared_qmi_location_set_supl_server_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) { - QmiClient *client = NULL; + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +pds_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 void +pds_set_supl_server (GTask *task) +{ + MMSharedQmi *self; + SetSuplServerContext *ctx; 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; + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); input = qmi_message_pds_set_agps_config_input_new (); @@ -181,27 +207,200 @@ mm_shared_qmi_location_set_supl_server (MMIfaceModemLocation *self, 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)) + if (parse_as_ip_port (ctx->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)) { + else if (parse_as_utf16_url (ctx->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), + QMI_CLIENT_PDS (ctx->client), input, 10, NULL, /* cancellable */ - (GAsyncReadyCallback)set_agps_config_ready, - g_task_new (self, NULL, callback, user_data)); + (GAsyncReadyCallback)pds_set_agps_config_ready, + task); qmi_message_pds_set_agps_config_input_unref (input); } +static gboolean +loc_location_set_server_indication_timed_out (GTask *task) +{ + SetSuplServerContext *ctx; + + ctx = g_task_get_task_data (task); + ctx->timeout_id = 0; + + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, + "Failed to receive indication with the server update result"); + g_object_unref (task); + return G_SOURCE_REMOVE; +} + +static void +loc_location_set_server_indication_cb (QmiClientLoc *client, + QmiIndicationLocSetServerOutput *output, + GTask *task) +{ + QmiLocIndicationStatus status; + GError *error = NULL; + + if (!qmi_indication_loc_set_server_output_get_indication_status (output, &status, &error)) { + g_prefix_error (&error, "QMI operation failed: "); + goto out; + } + + mm_error_from_qmi_loc_indication_status (status, &error); + +out: + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +loc_set_server_ready (QmiClientLoc *client, + GAsyncResult *res, + GTask *task) +{ + SetSuplServerContext *ctx; + QmiMessageLocSetServerOutput *output; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + output = qmi_client_loc_set_server_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_loc_set_server_output_get_result (output, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_loc_set_server_output_unref (output); + return; + } + + /* The task ownership is shared between signal and timeout; the one which is + * scheduled first will cancel the other. */ + ctx->indication_id = g_signal_connect (ctx->client, + "set-server", + G_CALLBACK (loc_location_set_server_indication_cb), + task); + ctx->timeout_id = g_timeout_add_seconds (10, + (GSourceFunc)loc_location_set_server_indication_timed_out, + task); + + qmi_message_loc_set_server_output_unref (output); +} + +static void +loc_set_supl_server (GTask *task) +{ + MMSharedQmi *self; + SetSuplServerContext *ctx; + QmiMessageLocSetServerInput *input; + guint32 ip; + guint32 port; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + input = qmi_message_loc_set_server_input_new (); + + /* For multimode devices, prefer UMTS by default */ + if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) + qmi_message_loc_set_server_input_set_server_type (input, QMI_LOC_SERVER_TYPE_UMTS_SLP, NULL); + else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) + qmi_message_loc_set_server_input_set_server_type (input, QMI_LOC_SERVER_TYPE_CDMA_PDE, NULL); + + if (parse_as_ip_port (ctx->supl, &ip, &port)) + qmi_message_loc_set_server_input_set_ipv4 (input, ip, port, NULL); + else + qmi_message_loc_set_server_input_set_url (input, ctx->supl, NULL); + + qmi_client_loc_set_server ( + QMI_CLIENT_LOC (ctx->client), + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)loc_set_server_ready, + task); + qmi_message_loc_set_server_input_unref (input); +} + +void +mm_shared_qmi_location_set_supl_server (MMIfaceModemLocation *self, + const gchar *supl, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + SetSuplServerContext *ctx; + QmiClient *client = NULL; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_slice_new0 (SetSuplServerContext); + ctx->supl = g_strdup (supl); + g_task_set_task_data (task, ctx, (GDestroyNotify)set_supl_server_context_free); + + /* Prefer PDS */ + client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), + QMI_SERVICE_PDS, + MM_PORT_QMI_FLAG_DEFAULT, + NULL); + if (client) { + ctx->client = g_object_ref (client); + pds_set_supl_server (task); + return; + } + + /* Otherwise LOC */ + client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), + QMI_SERVICE_LOC, + MM_PORT_QMI_FLAG_DEFAULT, + NULL); + if (client) { + ctx->client = g_object_ref (client); + loc_set_supl_server (task); + return; + } + + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't find any PDS/LOC client"); + g_object_unref (task); +} + /*****************************************************************************/ /* Location: Load SUPL server */ +typedef struct { + QmiClient *client; + glong indication_id; + guint timeout_id; +} LoadSuplServerContext; + +static void +load_supl_server_context_free (LoadSuplServerContext *ctx) +{ + if (ctx->client) { + if (ctx->timeout_id) + g_source_remove (ctx->timeout_id); + if (ctx->indication_id) + g_signal_handler_disconnect (ctx->client, ctx->indication_id); + g_object_unref (ctx->client); + } + g_slice_free (LoadSuplServerContext, ctx); +} + gchar * mm_shared_qmi_location_load_supl_server_finish (MMIfaceModemLocation *self, GAsyncResult *res, @@ -211,11 +410,11 @@ mm_shared_qmi_location_load_supl_server_finish (MMIfaceModemLocation *self, } static void -get_agps_config_ready (QmiClientPds *client, - GAsyncResult *res, - GTask *task) +pds_get_agps_config_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) { - QmiMessagePdsGetAgpsConfigOutput *output = NULL; + QmiMessagePdsGetAgpsConfigOutput *output; GError *error = NULL; guint32 ip = 0; guint32 port = 0; @@ -279,18 +478,15 @@ out: qmi_message_pds_get_agps_config_output_unref (output); } -void -mm_shared_qmi_location_load_supl_server (MMIfaceModemLocation *self, - GAsyncReadyCallback callback, - gpointer user_data) +static void +pds_load_supl_server (GTask *task) { - QmiClient *client = NULL; + MMSharedQmi *self; + LoadSuplServerContext *ctx; QmiMessagePdsGetAgpsConfigInput *input; - if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), - QMI_SERVICE_PDS, &client, - callback, user_data)) - return; + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); input = qmi_message_pds_get_agps_config_input_new (); @@ -301,15 +497,208 @@ mm_shared_qmi_location_load_supl_server (MMIfaceModemLocation *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), + QMI_CLIENT_PDS (ctx->client), input, 10, NULL, /* cancellable */ - (GAsyncReadyCallback)get_agps_config_ready, - g_task_new (self, NULL, callback, user_data)); + (GAsyncReadyCallback)pds_get_agps_config_ready, + task); qmi_message_pds_get_agps_config_input_unref (input); } +static gboolean +loc_location_get_server_indication_timed_out (GTask *task) +{ + LoadSuplServerContext *ctx; + + ctx = g_task_get_task_data (task); + ctx->timeout_id = 0; + + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, + "Failed to receive indication with the current server settings"); + g_object_unref (task); + return G_SOURCE_REMOVE; +} + +static void +loc_location_get_server_indication_cb (QmiClientLoc *client, + QmiIndicationLocGetServerOutput *output, + GTask *task) +{ + QmiLocIndicationStatus status; + const gchar *url = NULL; + guint32 ipv4_address = 0; + guint16 ipv4_port = 0; + GError *error = NULL; + gchar *str = NULL; + + if (!qmi_indication_loc_get_server_output_get_indication_status (output, &status, &error)) { + g_prefix_error (&error, "QMI operation failed: "); + goto out; + } + + if (!mm_error_from_qmi_loc_indication_status (status, &error)) + goto out; + + /* Prefer IP/PORT to URL */ + + if (qmi_indication_loc_get_server_output_get_ipv4 ( + output, + &ipv4_address, + &ipv4_port, + NULL) && + ipv4_address != 0 && ipv4_port != 0) { + struct in_addr a = { .s_addr = ipv4_address }; + 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", ipv4_address); + goto out; + } + + str = g_strdup_printf ("%s:%u", buf, ipv4_port); + goto out; + } + + if (qmi_indication_loc_get_server_output_get_url ( + output, + &url, + NULL) && + url && url [0]) { + str = g_strdup (url); + } + + 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); + +} + +static void +loc_get_server_ready (QmiClientLoc *client, + GAsyncResult *res, + GTask *task) +{ + LoadSuplServerContext *ctx; + QmiMessageLocGetServerOutput *output; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + output = qmi_client_loc_get_server_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_loc_get_server_output_get_result (output, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_loc_get_server_output_unref (output); + return; + } + + /* The task ownership is shared between signal and timeout; the one which is + * scheduled first will cancel the other. */ + ctx->indication_id = g_signal_connect (ctx->client, + "get-server", + G_CALLBACK (loc_location_get_server_indication_cb), + task); + ctx->timeout_id = g_timeout_add_seconds (10, + (GSourceFunc)loc_location_get_server_indication_timed_out, + task); + + qmi_message_loc_get_server_output_unref (output); +} + +static void +loc_load_supl_server (GTask *task) +{ + MMSharedQmi *self; + LoadSuplServerContext *ctx; + QmiMessageLocGetServerInput *input; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + input = qmi_message_loc_get_server_input_new (); + + /* For multimode devices, prefer UMTS by default */ + if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) + qmi_message_loc_get_server_input_set_server_type (input, QMI_LOC_SERVER_TYPE_UMTS_SLP, NULL); + else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) + qmi_message_loc_get_server_input_set_server_type (input, QMI_LOC_SERVER_TYPE_CDMA_PDE, NULL); + + qmi_message_loc_get_server_input_set_server_address_type ( + input, + (QMI_LOC_SERVER_ADDRESS_TYPE_IPV4 | QMI_LOC_SERVER_ADDRESS_TYPE_URL), + NULL); + + qmi_client_loc_get_server ( + QMI_CLIENT_LOC (ctx->client), + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)loc_get_server_ready, + task); + qmi_message_loc_get_server_input_unref (input); +} + +void +mm_shared_qmi_location_load_supl_server (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + QmiClient *client; + GTask *task; + LoadSuplServerContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_slice_new0 (LoadSuplServerContext); + g_task_set_task_data (task, ctx, (GDestroyNotify)load_supl_server_context_free); + + /* Prefer PDS */ + client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), + QMI_SERVICE_PDS, + MM_PORT_QMI_FLAG_DEFAULT, + NULL); + if (client) { + ctx->client = g_object_ref (client); + pds_load_supl_server (task); + return; + } + + /* Otherwise LOC */ + client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), + QMI_SERVICE_LOC, + MM_PORT_QMI_FLAG_DEFAULT, + NULL); + if (client) { + ctx->client = g_object_ref (client); + loc_load_supl_server (task); + return; + } + + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't find any PDS/LOC client"); + g_object_unref (task); +} + /*****************************************************************************/ /* Location: internal helper: stop gps engine */ @@ -369,6 +758,49 @@ pds_gps_service_state_stop_ready (QmiClientPds *client, } static void +loc_stop_ready (QmiClientLoc *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + QmiMessageLocStopOutput *output; + GError *error = NULL; + + output = qmi_client_loc_stop_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_loc_stop_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't stop GPS engine: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_loc_stop_output_unref (output); + return; + } + + qmi_message_loc_stop_output_unref (output); + + self = g_task_get_source_object (task); + priv = get_private (self); + + if (priv->loc_client) { + if (priv->loc_location_nmea_indication_id != 0) { + g_signal_handler_disconnect (priv->loc_client, priv->loc_location_nmea_indication_id); + priv->loc_location_nmea_indication_id = 0; + } + g_clear_object (&priv->loc_client); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void stop_gps_engine (MMSharedQmi *self, GAsyncReadyCallback callback, gpointer user_data) @@ -396,13 +828,28 @@ stop_gps_engine (MMSharedQmi *self, return; } + if (priv->loc_client) { + QmiMessageLocStopInput *input; + + input = qmi_message_loc_stop_input_new (); + qmi_message_loc_stop_input_set_session_id (input, DEFAULT_LOC_SESSION_ID, NULL); + qmi_client_loc_stop (QMI_CLIENT_LOC (priv->loc_client), + input, + 10, + NULL, + (GAsyncReadyCallback) loc_stop_ready, + task); + qmi_message_loc_stop_input_unref (input); + return; + } + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, - "Couldn't find any PDS client"); + "Couldn't find any PDS/LOC client"); g_object_unref (task); } /*****************************************************************************/ -/* Location: internal helper: NMEA indication callback */ +/* Location: internal helpers: NMEA indication callbacks */ static void pds_location_event_report_indication_cb (QmiClientPds *client, @@ -429,6 +876,21 @@ pds_location_event_report_indication_cb (QmiClientPds *clie } } +static void +loc_location_nmea_indication_cb (QmiClientLoc *client, + QmiIndicationLocNmeaOutput *output, + MMSharedQmi *self) +{ + const gchar *nmea = NULL; + + qmi_indication_loc_nmea_output_get_nmea_string (output, &nmea, NULL); + if (!nmea) + return; + + mm_dbg ("[NMEA] %s", nmea); + mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), nmea); +} + /*****************************************************************************/ /* Location: internal helper: start gps engine */ @@ -571,6 +1033,89 @@ pds_gps_service_state_start_ready (QmiClientPds *client, } static void +loc_register_events_ready (QmiClientLoc *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + QmiMessageLocRegisterEventsOutput *output; + GError *error = NULL; + + output = qmi_client_loc_register_events_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_loc_register_events_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't not register tracking events: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_loc_register_events_output_unref (output); + return; + } + + qmi_message_loc_register_events_output_unref (output); + + self = g_task_get_source_object (task); + priv = get_private (self); + + g_assert (!priv->loc_client); + g_assert (!priv->loc_location_nmea_indication_id); + priv->loc_client = g_object_ref (client); + priv->loc_location_nmea_indication_id = + g_signal_connect (client, + "nmea", + G_CALLBACK (loc_location_nmea_indication_cb), + self); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +loc_start_ready (QmiClientLoc *client, + GAsyncResult *res, + GTask *task) +{ + QmiMessageLocRegisterEventsInput *input; + QmiMessageLocStartOutput *output; + GError *error = NULL; + + output = qmi_client_loc_start_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_loc_start_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't start GPS engine: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_loc_start_output_unref (output); + return; + } + + qmi_message_loc_start_output_unref (output); + + input = qmi_message_loc_register_events_input_new (); + qmi_message_loc_register_events_input_set_event_registration_mask ( + input, QMI_LOC_EVENT_REGISTRATION_FLAG_NMEA, NULL); + qmi_client_loc_register_events (client, + input, + 10, + NULL, + (GAsyncReadyCallback) loc_register_events_ready, + task); + qmi_message_loc_register_events_input_unref (input); +} + +static void start_gps_engine (MMSharedQmi *self, GAsyncReadyCallback callback, gpointer user_data) @@ -580,6 +1125,7 @@ start_gps_engine (MMSharedQmi *self, task = g_task_new (self, NULL, callback, user_data); + /* Prefer PDS */ client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_PDS, MM_PORT_QMI_FLAG_DEFAULT, @@ -600,8 +1146,31 @@ start_gps_engine (MMSharedQmi *self, return; } + /* Otherwise LOC */ + client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), + QMI_SERVICE_LOC, + MM_PORT_QMI_FLAG_DEFAULT, + NULL); + if (client) { + QmiMessageLocStartInput *input; + + input = qmi_message_loc_start_input_new (); + qmi_message_loc_start_input_set_session_id (input, DEFAULT_LOC_SESSION_ID, NULL); + qmi_message_loc_start_input_set_intermediate_report_state (input, QMI_LOC_INTERMEDIATE_REPORT_STATE_DISABLE, NULL); + qmi_message_loc_start_input_set_minimum_interval_between_position_reports (input, 5000, NULL); + qmi_message_loc_start_input_set_fix_recurrence_type (input, QMI_LOC_FIX_RECURRENCE_TYPE_REQUEST_PERIODIC_FIXES, NULL); + qmi_client_loc_start (QMI_CLIENT_LOC (client), + input, + 10, + NULL, + (GAsyncReadyCallback) loc_start_ready, + task); + qmi_message_loc_start_input_unref (input); + return; + } + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, - "Couldn't find any PDS client"); + "Couldn't find any PDS/LOC client"); g_object_unref (task); } @@ -617,13 +1186,20 @@ typedef enum { typedef struct { QmiClient *client; GpsOperationMode mode; + glong indication_id; + guint timeout_id; } SetGpsOperationModeContext; static void set_gps_operation_mode_context_free (SetGpsOperationModeContext *ctx) { - if (ctx->client) + if (ctx->client) { + if (ctx->timeout_id) + g_source_remove (ctx->timeout_id); + if (ctx->indication_id) + g_signal_handler_disconnect (ctx->client, ctx->indication_id); g_object_unref (ctx->client); + } g_slice_free (SetGpsOperationModeContext, ctx); } @@ -752,6 +1328,198 @@ pds_get_default_tracking_session_ready (QmiClientPds *client, qmi_message_pds_set_default_tracking_session_input_unref (input); } +static gboolean +loc_location_operation_mode_indication_timed_out (GTask *task) +{ + SetSuplServerContext *ctx; + + ctx = g_task_get_task_data (task); + ctx->timeout_id = 0; + + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, + "Failed to receive operation mode indication"); + g_object_unref (task); + return G_SOURCE_REMOVE; +} + +static void +loc_location_set_operation_mode_indication_cb (QmiClientLoc *client, + QmiIndicationLocSetOperationModeOutput *output, + GTask *task) +{ + SetGpsOperationModeContext *ctx; + QmiLocIndicationStatus status; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!qmi_indication_loc_set_operation_mode_output_get_indication_status (output, &status, &error)) { + g_prefix_error (&error, "QMI operation failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!mm_error_from_qmi_loc_indication_status (status, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_dbg ("A-GPS %s", ctx->mode == GPS_OPERATION_MODE_ASSISTED ? "enabled" : "disabled"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +loc_set_operation_mode_ready (QmiClientLoc *client, + GAsyncResult *res, + GTask *task) +{ + SetGpsOperationModeContext *ctx; + QmiMessageLocSetOperationModeOutput *output; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + output = qmi_client_loc_set_operation_mode_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_loc_set_operation_mode_output_get_result (output, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_loc_set_operation_mode_output_unref (output); + return; + } + + /* The task ownership is shared between signal and timeout; the one which is + * scheduled first will cancel the other. */ + ctx->indication_id = g_signal_connect (ctx->client, + "set-operation-mode", + G_CALLBACK (loc_location_set_operation_mode_indication_cb), + task); + ctx->timeout_id = g_timeout_add_seconds (10, + (GSourceFunc)loc_location_operation_mode_indication_timed_out, + task); + + qmi_message_loc_set_operation_mode_output_unref (output); +} + +static void +loc_location_get_operation_mode_indication_cb (QmiClientLoc *client, + QmiIndicationLocGetOperationModeOutput *output, + GTask *task) +{ + SetGpsOperationModeContext *ctx; + QmiLocIndicationStatus status; + GError *error = NULL; + QmiLocOperationMode mode = QMI_LOC_OPERATION_MODE_DEFAULT; + QmiMessageLocSetOperationModeInput *input; + + ctx = g_task_get_task_data (task); + + if (!qmi_indication_loc_get_operation_mode_output_get_indication_status (output, &status, &error)) { + g_prefix_error (&error, "QMI operation failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!mm_error_from_qmi_loc_indication_status (status, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + qmi_indication_loc_get_operation_mode_output_get_operation_mode (output, &mode, NULL); + + if (ctx->mode == GPS_OPERATION_MODE_ASSISTED) { + if (mode == QMI_LOC_OPERATION_MODE_MSA) { + mm_dbg ("A-GPS already enabled"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + mm_dbg ("Need to enable A-GPS"); + mode = QMI_LOC_OPERATION_MODE_MSA; + } else if (ctx->mode == GPS_OPERATION_MODE_STANDALONE) { + if (mode == QMI_LOC_OPERATION_MODE_STANDALONE) { + mm_dbg ("A-GPS already disabled"); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + mm_dbg ("Need to disable A-GPS"); + mode = QMI_LOC_OPERATION_MODE_STANDALONE; + } else + g_assert_not_reached (); + + if (ctx->timeout_id) { + g_source_remove (ctx->timeout_id); + ctx->timeout_id = 0; + } + + if (ctx->indication_id) { + g_signal_handler_disconnect (ctx->client, ctx->indication_id); + ctx->indication_id = 0; + } + + input = qmi_message_loc_set_operation_mode_input_new (); + qmi_message_loc_set_operation_mode_input_set_operation_mode (input, mode, NULL); + qmi_client_loc_set_operation_mode ( + QMI_CLIENT_LOC (ctx->client), + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)loc_set_operation_mode_ready, + task); + qmi_message_loc_set_operation_mode_input_unref (input); +} + +static void +loc_get_operation_mode_ready (QmiClientLoc *client, + GAsyncResult *res, + GTask *task) +{ + SetGpsOperationModeContext *ctx; + QmiMessageLocGetOperationModeOutput *output; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + output = qmi_client_loc_get_operation_mode_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_loc_get_operation_mode_output_get_result (output, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_loc_get_operation_mode_output_unref (output); + return; + } + + /* The task ownership is shared between signal and timeout; the one which is + * scheduled first will cancel the other. */ + ctx->indication_id = g_signal_connect (ctx->client, + "get-operation-mode", + G_CALLBACK (loc_location_get_operation_mode_indication_cb), + task); + ctx->timeout_id = g_timeout_add_seconds (10, + (GSourceFunc)loc_location_operation_mode_indication_timed_out, + task); + + qmi_message_loc_get_operation_mode_output_unref (output); +} + static void set_gps_operation_mode (MMSharedQmi *self, GpsOperationMode mode, @@ -768,6 +1536,7 @@ set_gps_operation_mode (MMSharedQmi *self, ctx->mode = mode; g_task_set_task_data (task, ctx, (GDestroyNotify)set_gps_operation_mode_context_free); + /* Prefer PDS */ client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_PDS, MM_PORT_QMI_FLAG_DEFAULT, @@ -784,8 +1553,25 @@ set_gps_operation_mode (MMSharedQmi *self, return; } + /* Otherwise LOC */ + client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), + QMI_SERVICE_LOC, + MM_PORT_QMI_FLAG_DEFAULT, + NULL); + if (client) { + ctx->client = g_object_ref (client); + qmi_client_loc_get_operation_mode ( + QMI_CLIENT_LOC (ctx->client), + NULL, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)loc_get_operation_mode_ready, + task); + return; + } + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, - "Couldn't find any PDS client"); + "Couldn't find any PDS/LOC client"); g_object_unref (task); } @@ -873,7 +1659,7 @@ mm_shared_qmi_disable_location_gathering (MMIfaceModemLocation *_self, return; } - g_assert (!priv->pds_client); + g_assert (!(priv->pds_client && priv->loc_client)); /* Disable A-GPS? */ if (source == MM_MODEM_LOCATION_SOURCE_AGPS) { @@ -1080,6 +1866,12 @@ parent_load_capabilities_ready (MMIfaceModemLocation *self, MM_MODEM_LOCATION_SOURCE_GPS_RAW | MM_MODEM_LOCATION_SOURCE_AGPS); + /* If we have support for the LOC client, GPS location is supported */ + if (mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_LOC, 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); |