aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@aleksander.es>2018-06-30 17:26:41 +0200
committerDan Williams <dcbw@redhat.com>2018-08-21 13:26:08 +0000
commit0798f32362f0a9664e65de13c66d3767fc7ddf6d (patch)
treea976d3dec388ebbd901181fd0d61d3bf59362e2d /src
parent8bc51d3ed14bba4e1ccc6f365a2ebf4a11ac8658 (diff)
shared-qmi: implement LOC-based GPS/A-GPS support
Diffstat (limited to 'src')
-rw-r--r--src/mm-broadband-modem-mbim.c3
-rw-r--r--src/mm-broadband-modem-qmi.c3
-rw-r--r--src/mm-shared-qmi.c928
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);