From cb292908fdf9acb415a27469817d27da6c8d2466 Mon Sep 17 00:00:00 2001 From: Fabrice Bellet Date: Wed, 8 Apr 2015 16:48:29 +0200 Subject: [PATCH] mbm: add GPS location gathering support The udev rules file is updated according to the list of devices supporting gps features provided by the udev rules file from the mbm-gpsd project. https://bugs.freedesktop.org/show_bug.cgi?id=85008 --- plugins/mbm/77-mm-ericsson-mbm.rules | 16 ++ plugins/mbm/mm-broadband-modem-mbm.c | 346 ++++++++++++++++++++++++++++++++++- plugins/mbm/mm-plugin-mbm.c | 31 ++++ 3 files changed, 392 insertions(+), 1 deletion(-) diff --git a/plugins/mbm/77-mm-ericsson-mbm.rules b/plugins/mbm/77-mm-ericsson-mbm.rules index 64efd7c..f9899f5 100644 --- a/plugins/mbm/77-mm-ericsson-mbm.rules +++ b/plugins/mbm/77-mm-ericsson-mbm.rules @@ -5,14 +5,20 @@ SUBSYSTEMS=="usb", GOTO="mm_mbm_check" GOTO="mm_mbm_end" LABEL="mm_mbm_check" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" # Ericsson F3507g +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1900", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1900", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1902", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1902", ENV{ID_MM_ERICSSON_MBM}="1" # Ericsson F3607gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1904", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1904", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1905", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1905", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1906", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1906", ENV{ID_MM_ERICSSON_MBM}="1" # Ericsson F3307 @@ -29,7 +35,9 @@ ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1049", ENV{ID_MM_ERICSSON_MBM}="1" ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190b", ENV{ID_MM_ERICSSON_MBM}="1" # Ericsson F5521gw +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190d", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190d", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1911", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1911", ENV{ID_MM_ERICSSON_MBM}="1" # Ericsson H5321gw @@ -76,14 +84,19 @@ ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818e", ENV{ID_MM_ERICSSON_MBM}="1" ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818d", ENV{ID_MM_ERICSSON_MBM}="1" # Dell 5530 HSDPA +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8147", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8147", ENV{ID_MM_ERICSSON_MBM}="1" # Dell F3607gw +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8183", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8183", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8184", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8184", ENV{ID_MM_ERICSSON_MBM}="1" # Dell F3307 +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818b", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818b", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818c", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818c", ENV{ID_MM_ERICSSON_MBM}="1" # HP hs2330 Mobile Broadband Module @@ -105,10 +118,13 @@ ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="301d", ENV{ID_MM_ERICSSON_MBM}="1" ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="2f1d", ENV{ID_MM_ERICSSON_MBM}="1" # Toshiba +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130b", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130b", ENV{ID_MM_ERICSSON_MBM}="1" # Toshiba F3607gw +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130c", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130c", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1311", ENV{.MM_USBIFNUM}=="09", ENV{ID_MM_ERICSSON_MBM_GPS_PORT}="1" ATTRS{idVendor}=="0930", ATTRS{idProduct}=="1311", ENV{ID_MM_ERICSSON_MBM}="1" # Toshiba F3307 diff --git a/plugins/mbm/mm-broadband-modem-mbm.c b/plugins/mbm/mm-broadband-modem-mbm.c index 0eaa65a..5627dee 100644 --- a/plugins/mbm/mm-broadband-modem-mbm.c +++ b/plugins/mbm/mm-broadband-modem-mbm.c @@ -41,16 +41,23 @@ #include "mm-base-modem-at.h" #include "mm-iface-modem.h" #include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-location.h" + +/* sets the interval in seconds on how often the card emits the NMEA sentences */ +#define MBM_GPS_NMEA_INTERVAL "5" static void iface_modem_init (MMIfaceModem *iface); static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); static MMIfaceModem *iface_modem_parent; static MMIfaceModem3gpp *iface_modem_3gpp_parent; +static MMIfaceModemLocation *iface_modem_location_parent; G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbm, mm_broadband_modem_mbm, MM_TYPE_BROADBAND_MODEM, 0, G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) - G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)) #define MBM_NETWORK_MODE_OFFLINE 0 #define MBM_NETWORK_MODE_ANY 1 @@ -74,6 +81,8 @@ struct _MMBroadbandModemMbmPrivate { GRegex *emwi_regex; GRegex *erinfo_regex; + MMModemLocationSource enabled_sources; + guint mbm_mode; }; @@ -1176,6 +1185,305 @@ modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, } /*****************************************************************************/ +/* Location capabilities loading (Location interface) */ + +static MMModemLocationSource +location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return MM_MODEM_LOCATION_SOURCE_NONE; + + return (MMModemLocationSource) GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + MMModemLocationSource sources; + GError *error = NULL; + + sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_simple_async_result_take_error (simple, error); + g_simple_async_result_complete (simple); + g_object_unref (simple); + return; + } + + /* not sure how to check if GPS is supported, just allow it */ + if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) + sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED); + + /* So we're done, complete */ + g_simple_async_result_set_op_res_gpointer (simple, + GUINT_TO_POINTER (sources), + NULL); + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + location_load_capabilities); + + /* Chain up parent's setup */ + iface_modem_location_parent->load_capabilities (self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + result); +} + +/*****************************************************************************/ +/* Enable/Disable location gathering (Location interface) */ + +typedef struct { + MMBroadbandModemMbm *self; + GSimpleAsyncResult *result; + MMModemLocationSource source; +} LocationGatheringContext; + +static void +location_gathering_context_complete_and_free (LocationGatheringContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->self); + g_slice_free (LocationGatheringContext, ctx); +} + +/******************************/ +/* Disable location gathering */ + +static gboolean +disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +gps_disabled_ready (MMBaseModem *self, + GAsyncResult *res, + LocationGatheringContext *ctx) +{ + MMPortSerialGps *gps_port; + GError *error = NULL; + + if (!mm_base_modem_at_command_full_finish (self, res, &error)) + g_simple_async_result_take_error (ctx->result, error); + else + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + + /* Only use the GPS port in NMEA/RAW setups */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + /* Even if we get an error here, we try to close the GPS port */ + gps_port = mm_base_modem_peek_port_gps (self); + if (gps_port) + mm_port_serial_close (MM_PORT_SERIAL (gps_port)); + } + + location_gathering_context_complete_and_free (ctx); +} + +static void +disable_location_gathering (MMIfaceModemLocation *_self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); + gboolean stop_gps = FALSE; + LocationGatheringContext *ctx; + + ctx = g_slice_new (LocationGatheringContext); + ctx->self = g_object_ref (self); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + disable_location_gathering); + ctx->source = source; + + /* Only stop GPS engine if no GPS-related sources enabled */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + self->priv->enabled_sources &= ~source; + + if (!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) + stop_gps = TRUE; + } + + if (stop_gps) { + mm_base_modem_at_command_full (MM_BASE_MODEM (_self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (_self)), + "AT*E2GPSCTL=0", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_disabled_ready, + ctx); + return; + } + + /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */ + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + location_gathering_context_complete_and_free (ctx); +} + +/*****************************************************************************/ +/* Enable location gathering (Location interface) */ + +static gboolean +enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +gps_enabled_ready (MMBaseModem *self, + GAsyncResult *res, + LocationGatheringContext *ctx) +{ + GError *error = NULL; + MMPortSerialGps *gps_port; + + if (!mm_base_modem_at_command_full_finish (self, res, &error)) { + g_simple_async_result_take_error (ctx->result, error); + location_gathering_context_complete_and_free (ctx); + return; + } + + /* Only use the GPS port in NMEA/RAW setups */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + gps_port = mm_base_modem_peek_port_gps (self); + if (!gps_port || + !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) { + if (error) + g_simple_async_result_take_error (ctx->result, error); + else + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't open raw GPS serial port"); + } else { + GByteArray *buf; + const gchar *command = "AT*E2GPSNPD\r\n"; + + /* We need to send an AT command to the GPS data port to + * toggle it into this data mode. This is a particularity of + * mbm cards where the GPS data port is not hard wired. So + * we need to use the MMPortSerial API here. + */ + buf = g_byte_array_new (); + g_byte_array_append (buf, (const guint8 *) command, strlen (command)); + mm_port_serial_command (MM_PORT_SERIAL (gps_port), + buf, + 3, + FALSE, + NULL, + NULL, + NULL); + g_byte_array_unref (buf); + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + } + + } else + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + + location_gathering_context_complete_and_free (ctx); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + LocationGatheringContext *ctx) +{ + gboolean start_gps = FALSE; + GError *error = NULL; + + if (!iface_modem_location_parent->enable_location_gathering_finish (self, res, &error)) { + g_simple_async_result_take_error (ctx->result, error); + location_gathering_context_complete_and_free (ctx); + return; + } + + /* Now our own enabling */ + + /* 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 | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { + /* Only start GPS engine if not done already */ + if (!(ctx->self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) + start_gps = TRUE; + ctx->self->priv->enabled_sources |= ctx->source; + } + + if (start_gps) { + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + "AT*E2GPSCTL=1," MBM_GPS_NMEA_INTERVAL ",0", + 3, + FALSE, + FALSE, /* raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_enabled_ready, + ctx); + return; + } + + /* For any other location (e.g. 3GPP), or if GPS already running just return */ + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + location_gathering_context_complete_and_free (ctx); +} + +static void +enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LocationGatheringContext *ctx; + + ctx = g_slice_new (LocationGatheringContext); + ctx->self = g_object_ref (self); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + enable_location_gathering); + ctx->source = source; + + /* Chain up parent's gathering enable */ + iface_modem_location_parent->enable_location_gathering (self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + ctx); +} + +/*****************************************************************************/ /* Setup ports (Broadband modem class) */ static void @@ -1187,10 +1495,19 @@ emrdy_received (MMPortSerialAt *port, } static void +gps_trace_received (MMPortSerialGps *port, + const gchar *trace, + MMIfaceModemLocation *self) +{ + mm_iface_modem_location_gps_update (self, trace); +} + +static void setup_ports (MMBroadbandModem *_self) { MMBroadbandModemMbm *self = MM_BROADBAND_MODEM_MBM (_self); MMPortSerialAt *ports[2]; + MMPortSerialGps *gps_data_port; guint i; /* Call parent's setup ports first always */ @@ -1244,6 +1561,20 @@ setup_ports (MMBroadbandModem *_self) /* Now reset the unsolicited messages we'll handle when enabled */ set_unsolicited_events_handlers (MM_BROADBAND_MODEM_MBM (self), FALSE); + + /* NMEA GPS monitoring */ + gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); + if (gps_data_port) { + /* make sure GPS is stopped incase it was left enabled */ + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), + "AT*E2GPSCTL=0", + 3, FALSE, FALSE, NULL, NULL, NULL); + /* Add handler for the NMEA traces */ + mm_port_serial_gps_add_trace_handler (gps_data_port, + (MMPortSerialGpsTraceFn)gps_trace_received, + self, NULL); + } } /*****************************************************************************/ @@ -1358,6 +1689,19 @@ iface_modem_3gpp_init (MMIfaceModem3gpp *iface) } static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = location_load_capabilities; + iface->load_capabilities_finish = location_load_capabilities_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; +} + +static void mm_broadband_modem_mbm_class_init (MMBroadbandModemMbmClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); diff --git a/plugins/mbm/mm-plugin-mbm.c b/plugins/mbm/mm-plugin-mbm.c index bef3ab2..e71a0ee 100644 --- a/plugins/mbm/mm-plugin-mbm.c +++ b/plugins/mbm/mm-plugin-mbm.c @@ -65,6 +65,35 @@ create_modem (MMPlugin *self, product)); } +static gboolean +grab_port (MMPlugin *self, + MMBaseModem *modem, + MMPortProbe *probe, + GError **error) +{ + MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE; + GUdevDevice *port; + MMPortType port_type; + + port_type = mm_port_probe_get_port_type (probe); + port = mm_port_probe_peek_port (probe); + + if (g_udev_device_get_property_as_boolean (port, "ID_MM_ERICSSON_MBM_GPS_PORT")) { + mm_dbg ("(%s/%s) Port flagged as GPS", + mm_port_probe_get_port_subsys (probe), + mm_port_probe_get_port_name (probe)); + port_type = MM_PORT_TYPE_GPS; + } + + return mm_base_modem_grab_port (modem, + mm_port_probe_get_port_subsys (probe), + mm_port_probe_get_port_name (probe), + mm_port_probe_get_parent_path (probe), + port_type, + pflags, + error); +} + /*****************************************************************************/ G_MODULE_EXPORT MMPlugin * @@ -97,4 +126,6 @@ mm_plugin_mbm_class_init (MMPluginMbmClass *klass) MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass); plugin_class->create_modem = create_modem; + plugin_class->grab_port = grab_port; + } -- 2.1.0