diff options
author | Guido Günther <agx@sigxcpu.org> | 2014-02-05 08:38:23 +0100 |
---|---|---|
committer | Guido Günther <agx@sigxcpu.org> | 2014-02-05 08:38:23 +0100 |
commit | dc645b92b9a7db3076ae34986ac219d01677d124 (patch) | |
tree | 963a5d6ad150a88a2a8ab6d994d79d539e19383a /src | |
parent | 87bd9deec22af69bb27226254803ac5c63b18d78 (diff) |
Imported Upstream version 0.4+git.20100624t180933.6e79d15upstream/0.4+git.20100624t180933.6e79d15
Diffstat (limited to 'src')
57 files changed, 9963 insertions, 1652 deletions
diff --git a/src/77-mm-pcmcia-device-blacklist.rules b/src/77-mm-pcmcia-device-blacklist.rules new file mode 100644 index 0000000..76259a2 --- /dev/null +++ b/src/77-mm-pcmcia-device-blacklist.rules @@ -0,0 +1,10 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change", GOTO="mm_pcmcia_device_blacklist_end" +SUBSYSTEM!="pcmcia", GOTO="mm_pcmcia_device_blacklist_end" + +# Gemplus Serial Port smartcard adapter +ATTRS{prod_id1}=="Gemplus", ATTRS{prod_id2}=="SerialPort", ATTRS{prod_id3}=="GemPC Card", ENV{ID_MM_DEVICE_IGNORE}="1" + +LABEL="mm_pcmcia_device_blacklist_end" + diff --git a/src/77-mm-platform-serial-whitelist.rules b/src/77-mm-platform-serial-whitelist.rules new file mode 100644 index 0000000..b62d0a6 --- /dev/null +++ b/src/77-mm-platform-serial-whitelist.rules @@ -0,0 +1,14 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change", GOTO="mm_platform_device_whitelist_end" +SUBSYSTEM!="platform", GOTO="mm_platform_device_whitelist_end" + +# Be careful here since many devices connected to platform drivers on PCs +# are legacy devices that won't like probing. But often on embedded +# systems serial ports are provided by platform devices. + +# Allow atmel_usart +DRIVERS=="atmel_usart", ENV{ID_MM_PLATFORM_DRIVER_PROBE}="1" + +LABEL="mm_platform_device_whitelist_end" + diff --git a/src/77-mm-usb-device-blacklist.rules b/src/77-mm-usb-device-blacklist.rules new file mode 100644 index 0000000..78a6770 --- /dev/null +++ b/src/77-mm-usb-device-blacklist.rules @@ -0,0 +1,66 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change", GOTO="mm_usb_device_blacklist_end" +SUBSYSTEM!="usb", GOTO="mm_usb_device_blacklist_end" +ENV{DEVTYPE}!="usb_device", GOTO="mm_usb_device_blacklist_end" + +# APC UPS devices +ATTRS{idVendor}=="051d", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Sweex 1000VA +ATTRS{idVendor}=="0925", ATTRS{idProduct}=="1234", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Agiler UPS +ATTRS{idVendor}=="05b8", ATTRS{idProduct}=="0000", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Krauler UP-M500VA +ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0000", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Ablerex 625L USB +ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="0000", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Belkin F6C1200-UNV +ATTRS{idVendor}=="0665", ATTRS{idProduct}=="5161", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Various Liebert and Phoenixtec Power devices +ATTRS{idVendor}=="06da", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Unitek Alpha 1200Sx +ATTRS{idVendor}=="0f03", ATTRS{idProduct}=="0001", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Various Tripplite devices +ATTRS{idVendor}=="09ae", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Various MGE Office Protection Systems devices +ATTRS{idVendor}=="0463", ATTRS{idProduct}=="0001", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="0463", ATTRS{idProduct}=="ffff", ENV{ID_MM_DEVICE_IGNORE}="1" + +# CyberPower 900AVR/BC900D +ATTRS{idVendor}=="0764", ATTRS{idProduct}=="0005", ENV{ID_MM_DEVICE_IGNORE}="1" +# CyberPower CP1200AVR/BC1200D +ATTRS{idVendor}=="0764", ATTRS{idProduct}=="0501", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Various Belkin devices +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0980", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0900", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0910", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0912", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0551", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0751", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0375", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="1100", ENV{ID_MM_DEVICE_IGNORE}="1" + +# HP R/T 2200 INTL (like SMART2200RMXL2U) +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="1f0a", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Powerware devices +ATTRS{idVendor}=="0592", ATTRS{idProduct}=="0002", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Palm Treo 700/900/etc +# Shouldn't be probed themselves, but you can install programs like +# "MobileStream USB Modem" which changes the USB PID of the device to something +# that isn't blacklisted. +ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0061", ENV{ID_MM_DEVICE_IGNORE}="1" + +LABEL="mm_usb_device_blacklist_end" + diff --git a/src/Makefile.am b/src/Makefile.am index 9209b55..2061ae8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,14 @@ SUBDIRS=. tests +udevrulesdir = $(UDEV_BASE_DIR)/rules.d +udevrules_DATA = \ + 77-mm-usb-device-blacklist.rules \ + 77-mm-pcmcia-device-blacklist.rules \ + 77-mm-platform-serial-whitelist.rules + +EXTRA_DIST = \ + $(udevrules_DATA) + noinst_LTLIBRARIES = libmodem-helpers.la libmodem_helpers_la_CPPFLAGS = \ @@ -9,38 +18,76 @@ libmodem_helpers_la_SOURCES = \ mm-errors.c \ mm-errors.h \ mm-modem-helpers.c \ - mm-modem-helpers.h + mm-modem-helpers.h \ + mm-charsets.c \ + mm-charsets.h \ + mm-utils.c \ + mm-utils.h sbin_PROGRAMS = modem-manager modem_manager_CPPFLAGS = \ $(MM_CFLAGS) \ $(GUDEV_CFLAGS) \ + -I$(top_srcdir) \ -I${top_builddir}/marshallers \ -DPLUGINDIR=\"$(pkglibdir)\" +if WITH_POLKIT +modem_manager_CPPFLAGS += $(POLKIT_CFLAGS) +endif + modem_manager_LDADD = \ $(MM_LIBS) \ $(GUDEV_LIBS) \ $(top_builddir)/marshallers/libmarshallers.la \ + $(top_builddir)/libqcdm/src/libqcdm.la \ $(builddir)/libmodem-helpers.la +if WITH_POLKIT +modem_manager_LDADD += $(POLKIT_LIBS) +endif + +auth_sources = \ + mm-auth-request.c \ + mm-auth-request.h \ + mm-auth-provider.h \ + mm-auth-provider.c \ + mm-auth-provider-factory.c + +if WITH_POLKIT +auth_sources += \ + mm-auth-request-polkit.c \ + mm-auth-request-polkit.h \ + mm-auth-provider-polkit.c \ + mm-auth-provider-polkit.h +endif + +loc_sources = \ + mm-modem-location.c \ + mm-modem-location.h + modem_manager_SOURCES = \ main.c \ mm-callback-info.c \ mm-callback-info.h \ + $(auth_sources) \ mm-manager.c \ mm-manager.h \ mm-modem.c \ mm-modem.h \ mm-port.c \ mm-port.h \ - mm-modem-base.c \ - mm-modem-base.h \ mm-serial-port.c \ mm-serial-port.h \ + mm-at-serial-port.c \ + mm-at-serial-port.h \ + mm-qcdm-serial-port.c \ + mm-qcdm-serial-port.h \ mm-serial-parsers.c \ mm-serial-parsers.h \ + mm-modem-base.c \ + mm-modem-base.h \ mm-generic-cdma.c \ mm-generic-cdma.h \ mm-generic-gsm.c \ @@ -86,7 +133,6 @@ mm-modem-gsm-network-glue.h: $(top_srcdir)/introspection/mm-modem-gsm-network.xm mm-modem-gsm-sms-glue.h: $(top_srcdir)/introspection/mm-modem-gsm-sms.xml dbus-binding-tool --prefix=mm_modem_gsm_sms --mode=glib-server --output=$@ $< - BUILT_SOURCES = \ mm-manager-glue.h \ mm-modem-glue.h \ @@ -96,4 +142,15 @@ BUILT_SOURCES = \ mm-modem-gsm-network-glue.h \ mm-modem-gsm-sms-glue.h +if WITH_LOCATION_API +mm-modem-location-glue.h: $(top_srcdir)/introspection/mm-modem-location.xml + dbus-binding-tool --prefix=mm_modem_location --mode=glib-server --output=$@ $< + +modem_manager_SOURCES += $(loc_sources) + +BUILT_SOURCES += mm-modem-location-glue.h +else +EXTRA_DIST += $(loc_sources) +endif + CLEANFILES = $(BUILT_SOURCES) @@ -11,18 +11,22 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ +#include <config.h> #include <signal.h> #include <syslog.h> #include <string.h> +#include <unistd.h> #include <dbus/dbus-glib.h> #include <dbus/dbus-glib-lowlevel.h> #include "mm-manager.h" #include "mm-options.h" -#define HAL_DBUS_SERVICE "org.freedesktop.Hal" +#if !defined(MM_DIST_VERSION) +# define MM_DIST_VERSION VERSION +#endif static GMainLoop *loop = NULL; @@ -33,8 +37,11 @@ mm_signal_handler (int signo) mm_options_set_debug (!mm_options_debug ()); else if (signo == SIGINT || signo == SIGTERM) { g_message ("Caught signal %d, shutting down...", signo); - g_main_loop_quit (loop); - } + if (loop) + g_main_loop_quit (loop); + else + _exit (0); + } } static void @@ -178,6 +185,8 @@ main (int argc, char *argv[]) if (!mm_options_debug ()) logging_setup (); + g_message ("ModemManager (version " MM_DIST_VERSION ") starting..."); + bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err); if (!bus) { g_warning ("Could not get the system bus. Make sure " @@ -201,6 +210,16 @@ main (int argc, char *argv[]) g_signal_handler_disconnect (proxy, id); + mm_manager_shutdown (manager); + + /* Wait for all modems to be removed */ + while (mm_manager_num_modems (manager)) { + GMainContext *ctx = g_main_loop_get_context (loop); + + g_main_context_iteration (ctx, FALSE); + g_usleep (50); + } + g_object_unref (manager); g_object_unref (proxy); dbus_g_connection_unref (bus); diff --git a/src/mm-at-serial-port.c b/src/mm-at-serial-port.c new file mode 100644 index 0000000..068450d --- /dev/null +++ b/src/mm-at-serial-port.c @@ -0,0 +1,364 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#define _GNU_SOURCE /* for strcasestr() */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include "mm-at-serial-port.h" +#include "mm-errors.h" +#include "mm-options.h" + +G_DEFINE_TYPE (MMAtSerialPort, mm_at_serial_port, MM_TYPE_SERIAL_PORT) + +#define MM_AT_SERIAL_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_AT_SERIAL_PORT, MMAtSerialPortPrivate)) + +typedef struct { + /* Response parser data */ + MMAtSerialResponseParserFn response_parser_fn; + gpointer response_parser_user_data; + GDestroyNotify response_parser_notify; + GSList *unsolicited_msg_handlers; +} MMAtSerialPortPrivate; + + +/*****************************************************************************/ + +void +mm_at_serial_port_set_response_parser (MMAtSerialPort *self, + MMAtSerialResponseParserFn fn, + gpointer user_data, + GDestroyNotify notify) +{ + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + + g_return_if_fail (MM_IS_AT_SERIAL_PORT (self)); + + if (priv->response_parser_notify) + priv->response_parser_notify (priv->response_parser_user_data); + + priv->response_parser_fn = fn; + priv->response_parser_user_data = user_data; + priv->response_parser_notify = notify; +} + +static gboolean +parse_response (MMSerialPort *port, GByteArray *response, GError **error) +{ + MMAtSerialPort *self = MM_AT_SERIAL_PORT (port); + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + gboolean found; + GString *string; + + g_return_val_if_fail (priv->response_parser_fn != NULL, FALSE); + + /* Construct the string that AT-parsing functions expect */ + string = g_string_sized_new (response->len + 1); + g_string_append_len (string, (const char *) response->data, response->len); + + /* Parse it */ + found = priv->response_parser_fn (priv->response_parser_user_data, string, error); + + /* And copy it back into the response array after the parser has removed + * matches and cleaned it up. + */ + if (response->len) + g_byte_array_remove_range (response, 0, response->len); + g_byte_array_append (response, (const guint8 *) string->str, string->len); + g_string_free (string, TRUE); + return found; +} + +static gsize +handle_response (MMSerialPort *port, + GByteArray *response, + GError *error, + GCallback callback, + gpointer callback_data) +{ + MMAtSerialPort *self = MM_AT_SERIAL_PORT (port); + MMAtSerialResponseFn response_callback = (MMAtSerialResponseFn) callback; + GString *string; + + /* Convert to a string and call the callback */ + string = g_string_sized_new (response->len + 1); + g_string_append_len (string, (const char *) response->data, response->len); + response_callback (self, string, error, callback_data); + g_string_free (string, TRUE); + + return response->len; +} + +/*****************************************************************************/ + +typedef struct { + GRegex *regex; + MMAtSerialUnsolicitedMsgFn callback; + gpointer user_data; + GDestroyNotify notify; +} MMAtUnsolicitedMsgHandler; + +void +mm_at_serial_port_add_unsolicited_msg_handler (MMAtSerialPort *self, + GRegex *regex, + MMAtSerialUnsolicitedMsgFn callback, + gpointer user_data, + GDestroyNotify notify) +{ + MMAtUnsolicitedMsgHandler *handler; + MMAtSerialPortPrivate *priv; + + g_return_if_fail (MM_IS_AT_SERIAL_PORT (self)); + g_return_if_fail (regex != NULL); + + handler = g_slice_new (MMAtUnsolicitedMsgHandler); + handler->regex = g_regex_ref (regex); + handler->callback = callback; + handler->user_data = user_data; + handler->notify = notify; + + priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + priv->unsolicited_msg_handlers = g_slist_append (priv->unsolicited_msg_handlers, handler); +} + +static gboolean +remove_eval_cb (const GMatchInfo *match_info, + GString *result, + gpointer user_data) +{ + int *result_len = (int *) user_data; + int start; + int end; + + if (g_match_info_fetch_pos (match_info, 0, &start, &end)) + *result_len -= (end - start); + + return FALSE; +} + +static void +parse_unsolicited (MMSerialPort *port, GByteArray *response) +{ + MMAtSerialPort *self = MM_AT_SERIAL_PORT (port); + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + GSList *iter; + + for (iter = priv->unsolicited_msg_handlers; iter; iter = iter->next) { + MMAtUnsolicitedMsgHandler *handler = (MMAtUnsolicitedMsgHandler *) iter->data; + GMatchInfo *match_info; + gboolean matches; + + matches = g_regex_match_full (handler->regex, + (const char *) response->data, + response->len, + 0, 0, &match_info, NULL); + if (handler->callback) { + while (g_match_info_matches (match_info)) { + handler->callback (self, match_info, handler->user_data); + g_match_info_next (match_info, NULL); + } + } + + g_match_info_free (match_info); + + if (matches) { + /* Remove matches */ + char *str; + int result_len = response->len; + + str = g_regex_replace_eval (handler->regex, + (const char *) response->data, + response->len, + 0, 0, + remove_eval_cb, &result_len, NULL); + + g_byte_array_remove_range (response, 0, response->len); + g_byte_array_append (response, (const guint8 *) str, result_len); + g_free (str); + } + } +} + +/*****************************************************************************/ + +static GByteArray * +at_command_to_byte_array (const char *command) +{ + GByteArray *buf; + int cmdlen; + + g_return_val_if_fail (command != NULL, NULL); + + cmdlen = strlen (command); + buf = g_byte_array_sized_new (cmdlen + 3); + + /* Make sure there's an AT in the front */ + if (!g_str_has_prefix (command, "AT")) + g_byte_array_append (buf, (const guint8 *) "AT", 2); + g_byte_array_append (buf, (const guint8 *) command, cmdlen); + + /* Make sure there's a trailing carriage return */ + if (command[cmdlen] != '\r') + g_byte_array_append (buf, (const guint8 *) "\r", 1); + + return buf; +} + +void +mm_at_serial_port_queue_command (MMAtSerialPort *self, + const char *command, + guint32 timeout_seconds, + MMAtSerialResponseFn callback, + gpointer user_data) +{ + GByteArray *buf; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AT_SERIAL_PORT (self)); + g_return_if_fail (command != NULL); + + buf = at_command_to_byte_array (command); + g_return_if_fail (buf != NULL); + + mm_serial_port_queue_command (MM_SERIAL_PORT (self), + buf, + TRUE, + timeout_seconds, + (MMSerialResponseFn) callback, + user_data); +} + +void +mm_at_serial_port_queue_command_cached (MMAtSerialPort *self, + const char *command, + guint32 timeout_seconds, + MMAtSerialResponseFn callback, + gpointer user_data) +{ + GByteArray *buf; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AT_SERIAL_PORT (self)); + g_return_if_fail (command != NULL); + + buf = at_command_to_byte_array (command); + g_return_if_fail (buf != NULL); + + mm_serial_port_queue_command_cached (MM_SERIAL_PORT (self), + buf, + TRUE, + timeout_seconds, + (MMSerialResponseFn) callback, + user_data); +} + +static void +debug_log (MMSerialPort *port, const char *prefix, const char *buf, gsize len) +{ + static GString *debug = NULL; + const char *s; + GTimeVal tv; + + if (!debug) + debug = g_string_sized_new (256); + + g_string_append (debug, prefix); + g_string_append (debug, " '"); + + s = buf; + while (len--) { + if (g_ascii_isprint (*s)) + g_string_append_c (debug, *s); + else if (*s == '\r') + g_string_append (debug, "<CR>"); + else if (*s == '\n') + g_string_append (debug, "<LF>"); + else + g_string_append_printf (debug, "\\%d", *s); + + s++; + } + + g_string_append_c (debug, '\''); + g_get_current_time (&tv); + g_debug ("<%ld.%ld> (%s): %s", + tv.tv_sec, + tv.tv_usec, + mm_port_get_device (MM_PORT (port)), + debug->str); + g_string_truncate (debug, 0); +} + +/*****************************************************************************/ + +MMAtSerialPort * +mm_at_serial_port_new (const char *name, MMPortType ptype) +{ + return MM_AT_SERIAL_PORT (g_object_new (MM_TYPE_AT_SERIAL_PORT, + MM_PORT_DEVICE, name, + MM_PORT_SUBSYS, MM_PORT_SUBSYS_TTY, + MM_PORT_TYPE, ptype, + NULL)); +} + +static void +mm_at_serial_port_init (MMAtSerialPort *self) +{ +} + +static void +finalize (GObject *object) +{ + MMAtSerialPort *self = MM_AT_SERIAL_PORT (object); + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + + while (priv->unsolicited_msg_handlers) { + MMAtUnsolicitedMsgHandler *handler = (MMAtUnsolicitedMsgHandler *) priv->unsolicited_msg_handlers->data; + + if (handler->notify) + handler->notify (handler->user_data); + + g_regex_unref (handler->regex); + g_slice_free (MMAtUnsolicitedMsgHandler, handler); + priv->unsolicited_msg_handlers = g_slist_delete_link (priv->unsolicited_msg_handlers, + priv->unsolicited_msg_handlers); + } + + if (priv->response_parser_notify) + priv->response_parser_notify (priv->response_parser_user_data); + + G_OBJECT_CLASS (mm_at_serial_port_parent_class)->finalize (object); +} + +static void +mm_at_serial_port_class_init (MMAtSerialPortClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMSerialPortClass *port_class = MM_SERIAL_PORT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMAtSerialPortPrivate)); + + /* Virtual methods */ + object_class->finalize = finalize; + + port_class->parse_unsolicited = parse_unsolicited; + port_class->parse_response = parse_response; + port_class->handle_response = handle_response; + port_class->debug_log = debug_log; +} diff --git a/src/mm-at-serial-port.h b/src/mm-at-serial-port.h new file mode 100644 index 0000000..5d5f13f --- /dev/null +++ b/src/mm-at-serial-port.h @@ -0,0 +1,85 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#ifndef MM_AT_SERIAL_PORT_H +#define MM_AT_SERIAL_PORT_H + +#include <glib.h> +#include <glib/gtypes.h> +#include <glib-object.h> + +#include "mm-serial-port.h" + +#define MM_TYPE_AT_SERIAL_PORT (mm_at_serial_port_get_type ()) +#define MM_AT_SERIAL_PORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_AT_SERIAL_PORT, MMAtSerialPort)) +#define MM_AT_SERIAL_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_AT_SERIAL_PORT, MMAtSerialPortClass)) +#define MM_IS_AT_SERIAL_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_AT_SERIAL_PORT)) +#define MM_IS_AT_SERIAL_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_AT_SERIAL_PORT)) +#define MM_AT_SERIAL_PORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_AT_SERIAL_PORT, MMAtSerialPortClass)) + +typedef struct _MMAtSerialPort MMAtSerialPort; +typedef struct _MMAtSerialPortClass MMAtSerialPortClass; + +typedef gboolean (*MMAtSerialResponseParserFn) (gpointer user_data, + GString *response, + GError **error); + +typedef void (*MMAtSerialUnsolicitedMsgFn) (MMAtSerialPort *port, + GMatchInfo *match_info, + gpointer user_data); + +typedef void (*MMAtSerialResponseFn) (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data); + +struct _MMAtSerialPort { + MMSerialPort parent; +}; + +struct _MMAtSerialPortClass { + MMSerialPortClass parent; +}; + +GType mm_at_serial_port_get_type (void); + +MMAtSerialPort *mm_at_serial_port_new (const char *name, MMPortType ptype); + +void mm_at_serial_port_add_unsolicited_msg_handler (MMAtSerialPort *self, + GRegex *regex, + MMAtSerialUnsolicitedMsgFn callback, + gpointer user_data, + GDestroyNotify notify); + +void mm_at_serial_port_set_response_parser (MMAtSerialPort *self, + MMAtSerialResponseParserFn fn, + gpointer user_data, + GDestroyNotify notify); + +void mm_at_serial_port_queue_command (MMAtSerialPort *self, + const char *command, + guint32 timeout_seconds, + MMAtSerialResponseFn callback, + gpointer user_data); + +void mm_at_serial_port_queue_command_cached (MMAtSerialPort *self, + const char *command, + guint32 timeout_seconds, + MMAtSerialResponseFn callback, + gpointer user_data); + +#endif /* MM_AT_SERIAL_PORT_H */ + diff --git a/src/mm-auth-provider-factory.c b/src/mm-auth-provider-factory.c new file mode 100644 index 0000000..356dcf2 --- /dev/null +++ b/src/mm-auth-provider-factory.c @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include <string.h> + +#include "config.h" +#include "mm-auth-provider.h" + +GObject *mm_auth_provider_new (void); + +#ifdef WITH_POLKIT +#define IN_AUTH_PROVIDER_FACTORY_C +#include "mm-auth-provider-polkit.h" +#undef IN_AUTH_PROVIDER_FACTORY_C +#endif + +MMAuthProvider * +mm_auth_provider_get (void) +{ + static MMAuthProvider *singleton; + + if (!singleton) { +#if WITH_POLKIT + singleton = (MMAuthProvider *) mm_auth_provider_polkit_new (); +#else + singleton = (MMAuthProvider *) mm_auth_provider_new (); +#endif + } + + g_assert (singleton); + return singleton; +} + diff --git a/src/mm-auth-provider-polkit.c b/src/mm-auth-provider-polkit.c new file mode 100644 index 0000000..c457eaf --- /dev/null +++ b/src/mm-auth-provider-polkit.c @@ -0,0 +1,153 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include <polkit/polkit.h> + +#include "mm-auth-request-polkit.h" +#include "mm-auth-provider-polkit.h" + +G_DEFINE_TYPE (MMAuthProviderPolkit, mm_auth_provider_polkit, MM_TYPE_AUTH_PROVIDER) + +#define MM_AUTH_PROVIDER_POLKIT_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_AUTH_PROVIDER_POLKIT, MMAuthProviderPolkitPrivate)) + +typedef struct { + PolkitAuthority *authority; + guint auth_changed_id; +} MMAuthProviderPolkitPrivate; + +enum { + PROP_NAME = 1000, +}; + +/*****************************************************************************/ + +GObject * +mm_auth_provider_polkit_new (void) +{ + return g_object_new (MM_TYPE_AUTH_PROVIDER_POLKIT, NULL); +} + +/*****************************************************************************/ + +static void +pk_authority_changed_cb (GObject *object, gpointer user_data) +{ + /* Let clients know they should re-check their authorization */ +} + +/*****************************************************************************/ + +static MMAuthRequest * +real_create_request (MMAuthProvider *provider, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify) +{ + MMAuthProviderPolkitPrivate *priv = MM_AUTH_PROVIDER_POLKIT_GET_PRIVATE (provider); + + return (MMAuthRequest *) mm_auth_request_polkit_new (priv->authority, + authorization, + owner, + context, + callback, + callback_data, + notify); +} + +/*****************************************************************************/ + +static void +mm_auth_provider_polkit_init (MMAuthProviderPolkit *self) +{ + MMAuthProviderPolkitPrivate *priv = MM_AUTH_PROVIDER_POLKIT_GET_PRIVATE (self); + + priv->authority = polkit_authority_get (); + if (priv->authority) { + priv->auth_changed_id = g_signal_connect (priv->authority, + "changed", + G_CALLBACK (pk_authority_changed_cb), + self); + } else + g_warning ("%s: failed to create PolicyKit authority.", __func__); +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_NAME: + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, "polkit"); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + MMAuthProviderPolkit *self = MM_AUTH_PROVIDER_POLKIT (object); + MMAuthProviderPolkitPrivate *priv = MM_AUTH_PROVIDER_POLKIT_GET_PRIVATE (self); + + if (priv->auth_changed_id) { + g_signal_handler_disconnect (priv->authority, priv->auth_changed_id); + priv->auth_changed_id = 0; + } + + G_OBJECT_CLASS (mm_auth_provider_polkit_parent_class)->dispose (object); +} + +static void +mm_auth_provider_polkit_class_init (MMAuthProviderPolkitClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + MMAuthProviderClass *ap_class = MM_AUTH_PROVIDER_CLASS (class); + + mm_auth_provider_polkit_parent_class = g_type_class_peek_parent (class); + g_type_class_add_private (class, sizeof (MMAuthProviderPolkitPrivate)); + + /* Virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + ap_class->create_request = real_create_request; + + /* Properties */ + g_object_class_override_property (object_class, PROP_NAME, MM_AUTH_PROVIDER_NAME); +} + diff --git a/src/mm-auth-provider-polkit.h b/src/mm-auth-provider-polkit.h new file mode 100644 index 0000000..a52c56d --- /dev/null +++ b/src/mm-auth-provider-polkit.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_AUTH_PROVIDER_POLKIT_H +#define MM_AUTH_PROVIDER_POLKIT_H + +#include <glib-object.h> + +#include "mm-auth-provider.h" + +#define MM_TYPE_AUTH_PROVIDER_POLKIT (mm_auth_provider_polkit_get_type ()) +#define MM_AUTH_PROVIDER_POLKIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_AUTH_PROVIDER_POLKIT, MMAuthProviderPolkit)) +#define MM_AUTH_PROVIDER_POLKIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_AUTH_PROVIDER_POLKIT, MMAuthProviderPolkitClass)) +#define MM_IS_AUTH_PROVIDER_POLKIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_AUTH_PROVIDER_POLKIT)) +#define MM_IS_AUTH_PROVIDER_POLKIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_AUTH_PROVIDER_POLKIT)) +#define MM_AUTH_PROVIDER_POLKIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_AUTH_PROVIDER_POLKIT, MMAuthProviderPolkitClass)) + +typedef struct { + MMAuthProvider parent; +} MMAuthProviderPolkit; + +typedef struct { + MMAuthProviderClass parent; +} MMAuthProviderPolkitClass; + +GType mm_auth_provider_polkit_get_type (void); + +GObject *mm_auth_provider_polkit_new (void); + +#endif /* MM_AUTH_PROVIDER_POLKIT_H */ + diff --git a/src/mm-auth-provider.c b/src/mm-auth-provider.c new file mode 100644 index 0000000..ceff9ad --- /dev/null +++ b/src/mm-auth-provider.c @@ -0,0 +1,300 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include <string.h> + +#include "mm-marshal.h" +#include "mm-auth-provider.h" + +GObject *mm_auth_provider_new (void); + +G_DEFINE_TYPE (MMAuthProvider, mm_auth_provider, G_TYPE_OBJECT) + +#define MM_AUTH_PROVIDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_AUTH_PROVIDER, MMAuthProviderPrivate)) + +typedef struct { + GHashTable *requests; + guint process_id; +} MMAuthProviderPrivate; + +enum { + PROP_0, + PROP_NAME, + LAST_PROP +}; + +/*****************************************************************************/ + +GObject * +mm_auth_provider_new (void) +{ + return g_object_new (MM_TYPE_AUTH_PROVIDER, NULL); +} + +/*****************************************************************************/ + +static void +remove_requests (MMAuthProvider *self, GSList *remove) +{ + MMAuthProviderPrivate *priv = MM_AUTH_PROVIDER_GET_PRIVATE (self); + MMAuthRequest *req; + + while (remove) { + req = MM_AUTH_REQUEST (remove->data); + g_hash_table_remove (priv->requests, req); + remove = g_slist_remove (remove, req); + } +} + +void +mm_auth_provider_cancel_request (MMAuthProvider *provider, MMAuthRequest *req) +{ + MMAuthProviderPrivate *priv; + + g_return_if_fail (provider != NULL); + g_return_if_fail (MM_IS_AUTH_PROVIDER (provider)); + g_return_if_fail (req != NULL); + + priv = MM_AUTH_PROVIDER_GET_PRIVATE (provider); + + g_return_if_fail (g_hash_table_lookup (priv->requests, req) != NULL); + g_hash_table_remove (priv->requests, req); +} + +void +mm_auth_provider_cancel_for_owner (MMAuthProvider *self, GObject *owner) +{ + MMAuthProviderPrivate *priv; + GHashTableIter iter; + MMAuthRequest *req; + gpointer value; + GSList *remove = NULL; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AUTH_PROVIDER (self)); + + /* Find all requests from this owner */ + priv = MM_AUTH_PROVIDER_GET_PRIVATE (self); + g_hash_table_iter_init (&iter, priv->requests); + while (g_hash_table_iter_next (&iter, NULL, &value)) { + req = MM_AUTH_REQUEST (value); + if (mm_auth_request_get_owner (req) == owner) + remove = g_slist_prepend (remove, req); + } + + /* And cancel/remove them */ + remove_requests (self, remove); +} + +/*****************************************************************************/ + + +static MMAuthRequest * +real_create_request (MMAuthProvider *provider, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify) +{ + return (MMAuthRequest *) mm_auth_request_new (0, + authorization, + owner, + context, + callback, + callback_data, + notify); +} + +static gboolean +process_complete_requests (gpointer user_data) +{ + MMAuthProvider *self = MM_AUTH_PROVIDER (user_data); + MMAuthProviderPrivate *priv = MM_AUTH_PROVIDER_GET_PRIVATE (self); + GHashTableIter iter; + gpointer value; + GSList *remove = NULL; + MMAuthRequest *req; + + priv->process_id = 0; + + /* Call finished request's callbacks */ + g_hash_table_iter_init (&iter, priv->requests); + while (g_hash_table_iter_next (&iter, NULL, &value)) { + req = MM_AUTH_REQUEST (value); + + if (mm_auth_request_get_authorization (req) != MM_AUTH_RESULT_UNKNOWN) { + mm_auth_request_callback (req); + remove = g_slist_prepend (remove, req); + } + } + + /* And remove those requests from our pending request list */ + remove_requests (self, remove); + + return FALSE; +} + +static void +auth_result_cb (MMAuthRequest *req, gpointer user_data) +{ + MMAuthProvider *self = MM_AUTH_PROVIDER (user_data); + MMAuthProviderPrivate *priv = MM_AUTH_PROVIDER_GET_PRIVATE (self); + + /* Process results from an idle handler */ + if (priv->process_id == 0) + priv->process_id = g_idle_add (process_complete_requests, self); +} + +#define RESULT_SIGID_TAG "result-sigid" + +MMAuthRequest * +mm_auth_provider_request_auth (MMAuthProvider *self, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify, + GError **error) +{ + MMAuthProviderPrivate *priv; + MMAuthRequest *req; + guint32 sigid; + + g_return_val_if_fail (self != NULL, 0); + g_return_val_if_fail (MM_IS_AUTH_PROVIDER (self), 0); + g_return_val_if_fail (authorization != NULL, 0); + g_return_val_if_fail (callback != NULL, 0); + + priv = MM_AUTH_PROVIDER_GET_PRIVATE (self); + + req = MM_AUTH_PROVIDER_GET_CLASS (self)->create_request (self, + authorization, + owner, + context, + callback, + callback_data, + notify); + g_assert (req); + + sigid = g_signal_connect (req, "result", G_CALLBACK (auth_result_cb), self); + g_object_set_data (G_OBJECT (req), RESULT_SIGID_TAG, GUINT_TO_POINTER (sigid)); + + g_hash_table_insert (priv->requests, req, req); + if (!mm_auth_request_authenticate (req, error)) { + /* Error */ + g_hash_table_remove (priv->requests, req); + return NULL; + } + + return req; +} + +/*****************************************************************************/ + +static void +dispose_auth_request (gpointer data) +{ + MMAuthRequest *req = MM_AUTH_REQUEST (data); + guint sigid; + + sigid = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (req), RESULT_SIGID_TAG)); + if (sigid) + g_signal_handler_disconnect (req, sigid); + mm_auth_request_dispose (req); + g_object_unref (req); +} + +static void +mm_auth_provider_init (MMAuthProvider *self) +{ + MMAuthProviderPrivate *priv = MM_AUTH_PROVIDER_GET_PRIVATE (self); + + priv->requests = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + dispose_auth_request); +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_NAME: + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +#define NULL_PROVIDER "open" + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, NULL_PROVIDER); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + MMAuthProviderPrivate *priv = MM_AUTH_PROVIDER_GET_PRIVATE (object); + + if (priv->process_id) + g_source_remove (priv->process_id); + g_hash_table_destroy (priv->requests); + + G_OBJECT_CLASS (mm_auth_provider_parent_class)->dispose (object); +} + +static void +mm_auth_provider_class_init (MMAuthProviderClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + mm_auth_provider_parent_class = g_type_class_peek_parent (class); + g_type_class_add_private (class, sizeof (MMAuthProviderPrivate)); + + /* Virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + class->create_request = real_create_request; + + /* Properties */ + g_object_class_install_property (object_class, PROP_NAME, + g_param_spec_string (MM_AUTH_PROVIDER_NAME, + "Name", + "Provider name", + NULL_PROVIDER, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + diff --git a/src/mm-auth-provider.h b/src/mm-auth-provider.h new file mode 100644 index 0000000..26ff340 --- /dev/null +++ b/src/mm-auth-provider.h @@ -0,0 +1,86 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_AUTH_PROVIDER_H +#define MM_AUTH_PROVIDER_H + +#include <glib-object.h> +#include <dbus/dbus-glib-lowlevel.h> + +#include "mm-auth-request.h" + +/* Authorizations */ +#define MM_AUTHORIZATION_DEVICE_INFO "org.freedesktop.ModemManager.Device.Info" +#define MM_AUTHORIZATION_DEVICE_CONTROL "org.freedesktop.ModemManager.Device.Control" +#define MM_AUTHORIZATION_CONTACTS "org.freedesktop.ModemManager.Contacts" +#define MM_AUTHORIZATION_SMS "org.freedesktop.ModemManager.SMS" +#define MM_AUTHORIZATION_LOCATION "org.freedesktop.ModemManager.Location" +/******************/ + + +#define MM_TYPE_AUTH_PROVIDER (mm_auth_provider_get_type ()) +#define MM_AUTH_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_AUTH_PROVIDER, MMAuthProvider)) +#define MM_AUTH_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_AUTH_PROVIDER, MMAuthProviderClass)) +#define MM_IS_AUTH_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_AUTH_PROVIDER)) +#define MM_IS_AUTH_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_AUTH_PROVIDER)) +#define MM_AUTH_PROVIDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_AUTH_PROVIDER, MMAuthProviderClass)) + +#define MM_AUTH_PROVIDER_NAME "name" + +typedef struct { + GObject parent; +} MMAuthProvider; + +typedef struct { + GObjectClass parent; + + MMAuthRequest * (*create_request) (MMAuthProvider *provider, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify); +} MMAuthProviderClass; + +GType mm_auth_provider_get_type (void); + +/* Don't do anything clever from the notify callback... */ +MMAuthRequest *mm_auth_provider_request_auth (MMAuthProvider *provider, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify, + GError **error); + +void mm_auth_provider_cancel_for_owner (MMAuthProvider *provider, + GObject *owner); + +/* Subclass API */ + +/* To get an auth provider instance, implemented in mm-auth-provider-factory.c */ +MMAuthProvider *mm_auth_provider_get (void); + +/* schedules the request's completion */ +void mm_auth_provider_finish_request (MMAuthProvider *provider, + MMAuthRequest *req, + MMAuthResult result); + +void mm_auth_provider_cancel_request (MMAuthProvider *provider, MMAuthRequest *req); + +#endif /* MM_AUTH_PROVIDER_H */ + diff --git a/src/mm-auth-request-polkit.c b/src/mm-auth-request-polkit.c new file mode 100644 index 0000000..2a96bfe --- /dev/null +++ b/src/mm-auth-request-polkit.c @@ -0,0 +1,175 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include <glib.h> +#include <gio/gio.h> + +#include "mm-auth-request-polkit.h" + +G_DEFINE_TYPE (MMAuthRequestPolkit, mm_auth_request_polkit, MM_TYPE_AUTH_REQUEST) + +#define MM_AUTH_REQUEST_POLKIT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_AUTH_REQUEST_POLKIT, MMAuthRequestPolkitPrivate)) + +typedef struct { + PolkitAuthority *authority; + GCancellable *cancellable; + PolkitSubject *subject; +} MMAuthRequestPolkitPrivate; + +/*****************************************************************************/ + +GObject * +mm_auth_request_polkit_new (PolkitAuthority *authority, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify) +{ + GObject *obj; + MMAuthRequestPolkitPrivate *priv; + char *sender; + + g_return_val_if_fail (authorization != NULL, NULL); + g_return_val_if_fail (owner != NULL, NULL); + g_return_val_if_fail (callback != NULL, NULL); + g_return_val_if_fail (context != NULL, NULL); + + obj = mm_auth_request_new (MM_TYPE_AUTH_REQUEST_POLKIT, + authorization, + owner, + context, + callback, + callback_data, + notify); + if (obj) { + priv = MM_AUTH_REQUEST_POLKIT_GET_PRIVATE (obj); + priv->authority = authority; + priv->cancellable = g_cancellable_new (); + + sender = dbus_g_method_get_sender (context); + priv->subject = polkit_system_bus_name_new (sender); + g_free (sender); + } + + return obj; +} + +/*****************************************************************************/ + +static void +pk_auth_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + MMAuthRequestPolkit *self = user_data; + MMAuthRequestPolkitPrivate *priv; + PolkitAuthorizationResult *pk_result; + GError *error = NULL; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AUTH_REQUEST_POLKIT (self)); + + priv = MM_AUTH_REQUEST_POLKIT_GET_PRIVATE (self); + if (!g_cancellable_is_cancelled (priv->cancellable)) { + pk_result = polkit_authority_check_authorization_finish (priv->authority, + result, + &error); + if (error) { + mm_auth_request_set_result (MM_AUTH_REQUEST (self), MM_AUTH_RESULT_INTERNAL_FAILURE); + g_warning ("%s: PolicyKit authentication error: (%d) %s", + __func__, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + } else if (polkit_authorization_result_get_is_authorized (pk_result)) + mm_auth_request_set_result (MM_AUTH_REQUEST (self), MM_AUTH_RESULT_AUTHORIZED); + else if (polkit_authorization_result_get_is_challenge (pk_result)) + mm_auth_request_set_result (MM_AUTH_REQUEST (self), MM_AUTH_RESULT_CHALLENGE); + else + mm_auth_request_set_result (MM_AUTH_REQUEST (self), MM_AUTH_RESULT_NOT_AUTHORIZED); + + g_signal_emit_by_name (self, "result"); + } + + g_object_unref (self); +} + +static gboolean +real_authenticate (MMAuthRequest *self, GError **error) +{ + MMAuthRequestPolkitPrivate *priv; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (MM_IS_AUTH_REQUEST_POLKIT (self), FALSE); + + /* We ref ourselves across the polkit call, because we can't get + * disposed of while the call is still in-progress, and even if we + * cancel ourselves we'll still get the callback. + */ + g_object_ref (self); + + priv = MM_AUTH_REQUEST_POLKIT_GET_PRIVATE (self); + polkit_authority_check_authorization (priv->authority, + priv->subject, + mm_auth_request_get_authorization (MM_AUTH_REQUEST (self)), + NULL, + POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, + priv->cancellable, + pk_auth_cb, + self); + return TRUE; +} + +static void +real_dispose (MMAuthRequest *req) +{ + g_return_if_fail (req != NULL); + g_return_if_fail (MM_IS_AUTH_REQUEST_POLKIT (req)); + + g_cancellable_cancel (MM_AUTH_REQUEST_POLKIT_GET_PRIVATE (req)->cancellable); +} + +/*****************************************************************************/ + +static void +mm_auth_request_polkit_init (MMAuthRequestPolkit *self) +{ +} + +static void +dispose (GObject *object) +{ + MMAuthRequestPolkitPrivate *priv = MM_AUTH_REQUEST_POLKIT_GET_PRIVATE (object); + + g_object_unref (priv->cancellable); + g_object_unref (priv->subject); + + G_OBJECT_CLASS (mm_auth_request_polkit_parent_class)->dispose (object); +} + +static void +mm_auth_request_polkit_class_init (MMAuthRequestPolkitClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + MMAuthRequestClass *ar_class = MM_AUTH_REQUEST_CLASS (class); + + mm_auth_request_polkit_parent_class = g_type_class_peek_parent (class); + g_type_class_add_private (class, sizeof (MMAuthRequestPolkitPrivate)); + + /* Virtual methods */ + object_class->dispose = dispose; + ar_class->authenticate = real_authenticate; + ar_class->dispose = real_dispose; +} + diff --git a/src/mm-auth-request-polkit.h b/src/mm-auth-request-polkit.h new file mode 100644 index 0000000..384ace8 --- /dev/null +++ b/src/mm-auth-request-polkit.h @@ -0,0 +1,53 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_AUTH_REQUEST_POLKIT_H +#define MM_AUTH_REQUEST_POLKIT_H + +#include <glib-object.h> +#include <polkit/polkit.h> +#include <dbus/dbus-glib-lowlevel.h> + +#include "mm-auth-request.h" + +#define MM_TYPE_AUTH_REQUEST_POLKIT (mm_auth_request_polkit_get_type ()) +#define MM_AUTH_REQUEST_POLKIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_AUTH_REQUEST_POLKIT, MMAuthRequestPolkit)) +#define MM_AUTH_REQUEST_POLKIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_AUTH_REQUEST_POLKIT, MMAuthRequestPolkitClass)) +#define MM_IS_AUTH_REQUEST_POLKIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_AUTH_REQUEST_POLKIT)) +#define MM_IS_AUTH_REQUEST_POLKIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_AUTH_REQUEST_POLKIT)) +#define MM_AUTH_REQUEST_POLKIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_AUTH_REQUEST_POLKIT, MMAuthRequestPolkitClass)) + +typedef struct { + MMAuthRequest parent; +} MMAuthRequestPolkit; + +typedef struct { + MMAuthRequestClass parent; +} MMAuthRequestPolkitClass; + +GType mm_auth_request_polkit_get_type (void); + +GObject *mm_auth_request_polkit_new (PolkitAuthority *authority, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify); + +void mm_auth_request_polkit_cancel (MMAuthRequestPolkit *self); + +#endif /* MM_AUTH_REQUEST_POLKIT_H */ + diff --git a/src/mm-auth-request.c b/src/mm-auth-request.c new file mode 100644 index 0000000..79f0d42 --- /dev/null +++ b/src/mm-auth-request.c @@ -0,0 +1,182 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include "mm-auth-request.h" + +G_DEFINE_TYPE (MMAuthRequest, mm_auth_request, G_TYPE_OBJECT) + +#define MM_AUTH_REQUEST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_AUTH_REQUEST, MMAuthRequestPrivate)) + +typedef struct { + GObject *owner; + char *auth; + DBusGMethodInvocation *context; + MMAuthRequestCb callback; + gpointer callback_data; + + MMAuthResult result; +} MMAuthRequestPrivate; + +/*****************************************************************************/ + +GObject * +mm_auth_request_new (GType atype, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify) +{ + GObject *obj; + MMAuthRequestPrivate *priv; + + g_return_val_if_fail (authorization != NULL, NULL); + g_return_val_if_fail (owner != NULL, NULL); + g_return_val_if_fail (callback != NULL, NULL); + + obj = g_object_new (atype ? atype : MM_TYPE_AUTH_REQUEST, NULL); + if (obj) { + priv = MM_AUTH_REQUEST_GET_PRIVATE (obj); + priv->owner = owner; /* not reffed */ + priv->context = context; + priv->auth = g_strdup (authorization); + priv->callback = callback; + priv->callback_data = callback_data; + + g_object_set_data_full (obj, "caller-data", callback_data, notify); + } + + return obj; +} + +/*****************************************************************************/ + +const char * +mm_auth_request_get_authorization (MMAuthRequest *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_AUTH_REQUEST (self), NULL); + + return MM_AUTH_REQUEST_GET_PRIVATE (self)->auth; +} + +GObject * +mm_auth_request_get_owner (MMAuthRequest *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_AUTH_REQUEST (self), NULL); + + return MM_AUTH_REQUEST_GET_PRIVATE (self)->owner; +} + +MMAuthResult +mm_auth_request_get_result (MMAuthRequest *self) +{ + g_return_val_if_fail (self != NULL, MM_AUTH_RESULT_UNKNOWN); + g_return_val_if_fail (MM_IS_AUTH_REQUEST (self), MM_AUTH_RESULT_UNKNOWN); + + return MM_AUTH_REQUEST_GET_PRIVATE (self)->result; +} + +void +mm_auth_request_set_result (MMAuthRequest *self, MMAuthResult result) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AUTH_REQUEST (self)); + g_return_if_fail (result != MM_AUTH_RESULT_UNKNOWN); + + MM_AUTH_REQUEST_GET_PRIVATE (self)->result = result; +} + +gboolean +mm_auth_request_authenticate (MMAuthRequest *self, GError **error) +{ + return MM_AUTH_REQUEST_GET_CLASS (self)->authenticate (self, error); +} + +void +mm_auth_request_callback (MMAuthRequest *self) +{ + MMAuthRequestPrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AUTH_REQUEST (self)); + + priv = MM_AUTH_REQUEST_GET_PRIVATE (self); + g_warn_if_fail (priv->result != MM_AUTH_RESULT_UNKNOWN); + + if (priv->callback) + priv->callback (self, priv->owner, priv->context, priv->callback_data); +} + +void +mm_auth_request_dispose (MMAuthRequest *self) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AUTH_REQUEST (self)); + + if (MM_AUTH_REQUEST_GET_CLASS (self)->dispose) + MM_AUTH_REQUEST_GET_CLASS (self)->dispose (self); +} + +/*****************************************************************************/ + +static gboolean +real_authenticate (MMAuthRequest *self, GError **error) +{ + /* Null auth; everything passes */ + mm_auth_request_set_result (self, MM_AUTH_RESULT_AUTHORIZED); + g_signal_emit_by_name (self, "result"); + return TRUE; +} + +/*****************************************************************************/ + +static void +mm_auth_request_init (MMAuthRequest *self) +{ +} + +static void +dispose (GObject *object) +{ + MMAuthRequestPrivate *priv = MM_AUTH_REQUEST_GET_PRIVATE (object); + + g_free (priv->auth); + + G_OBJECT_CLASS (mm_auth_request_parent_class)->dispose (object); +} + +static void +mm_auth_request_class_init (MMAuthRequestClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + mm_auth_request_parent_class = g_type_class_peek_parent (class); + g_type_class_add_private (class, sizeof (MMAuthRequestPrivate)); + + /* Virtual methods */ + object_class->dispose = dispose; + class->authenticate = real_authenticate; + + g_signal_new ("result", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0, G_TYPE_NONE); +} + diff --git a/src/mm-auth-request.h b/src/mm-auth-request.h new file mode 100644 index 0000000..e22f0a2 --- /dev/null +++ b/src/mm-auth-request.h @@ -0,0 +1,72 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_AUTH_REQUEST_H +#define MM_AUTH_REQUEST_H + +#include <glib-object.h> +#include <dbus/dbus-glib-lowlevel.h> + +#define MM_TYPE_AUTH_REQUEST (mm_auth_request_get_type ()) +#define MM_AUTH_REQUEST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_AUTH_REQUEST, MMAuthRequest)) +#define MM_AUTH_REQUEST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_AUTH_REQUEST, MMAuthRequestClass)) +#define MM_IS_AUTH_REQUEST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_AUTH_REQUEST)) +#define MM_IS_AUTH_REQUEST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_AUTH_REQUEST)) +#define MM_AUTH_REQUEST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_AUTH_REQUEST, MMAuthRequestClass)) + +typedef enum MMAuthResult { + MM_AUTH_RESULT_UNKNOWN = 0, + MM_AUTH_RESULT_INTERNAL_FAILURE, + MM_AUTH_RESULT_NOT_AUTHORIZED, + MM_AUTH_RESULT_CHALLENGE, + MM_AUTH_RESULT_AUTHORIZED +} MMAuthResult; + +typedef struct { + GObject parent; +} MMAuthRequest; + +typedef struct { + GObjectClass parent; + + gboolean (*authenticate) (MMAuthRequest *self, GError **error); + void (*dispose) (MMAuthRequest *self); +} MMAuthRequestClass; + +GType mm_auth_request_get_type (void); + +typedef void (*MMAuthRequestCb) (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data); + +GObject *mm_auth_request_new (GType atype, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify); + +const char * mm_auth_request_get_authorization (MMAuthRequest *req); +GObject * mm_auth_request_get_owner (MMAuthRequest *req); +MMAuthResult mm_auth_request_get_result (MMAuthRequest *req); +void mm_auth_request_set_result (MMAuthRequest *req, MMAuthResult result); +gboolean mm_auth_request_authenticate (MMAuthRequest *req, GError **error); +void mm_auth_request_callback (MMAuthRequest *req); +void mm_auth_request_dispose (MMAuthRequest *req); + +#endif /* MM_AUTH_REQUEST_H */ + diff --git a/src/mm-callback-info.c b/src/mm-callback-info.c index 1882898..1986bb5 100644 --- a/src/mm-callback-info.c +++ b/src/mm-callback-info.c @@ -91,6 +91,8 @@ mm_callback_info_schedule (MMCallbackInfo *info) g_return_if_fail (info->pending_id == 0); g_return_if_fail (info->called == FALSE); + g_warn_if_fail (info->chain_left == 0); + info->pending_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, callback_info_do, info, callback_info_done); } @@ -208,3 +210,24 @@ mm_callback_info_unref (MMCallbackInfo *info) } } +void +mm_callback_info_chain_start (MMCallbackInfo *info, guint num) +{ + g_return_if_fail (info != NULL); + g_return_if_fail (num > 0); + g_return_if_fail (info->chain_left == 0); + + info->chain_left = num; +} + +void +mm_callback_info_chain_complete_one (MMCallbackInfo *info) +{ + g_return_if_fail (info != NULL); + g_return_if_fail (info->chain_left > 0); + + info->chain_left--; + if (info->chain_left == 0) + mm_callback_info_schedule (info); +} + diff --git a/src/mm-callback-info.h b/src/mm-callback-info.h index 66c2048..ef4d65f 100644 --- a/src/mm-callback-info.h +++ b/src/mm-callback-info.h @@ -25,6 +25,9 @@ typedef void (*MMCallbackInfoInvokeFn) (MMCallbackInfo *info); struct _MMCallbackInfo { guint32 refcount; + /* # of ops left in this callback chain */ + guint32 chain_left; + GData *qdata; MMModem *modem; @@ -70,5 +73,8 @@ gpointer mm_callback_info_get_data (MMCallbackInfo *info, MMCallbackInfo *mm_callback_info_ref (MMCallbackInfo *info); void mm_callback_info_unref (MMCallbackInfo *info); +void mm_callback_info_chain_start (MMCallbackInfo *info, guint num); +void mm_callback_info_chain_complete_one (MMCallbackInfo *info); + #endif /* MM_CALLBACK_INFO_H */ diff --git a/src/mm-charsets.c b/src/mm-charsets.c new file mode 100644 index 0000000..c75c3a9 --- /dev/null +++ b/src/mm-charsets.c @@ -0,0 +1,175 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include "mm-charsets.h" +#include "mm-utils.h" + +typedef struct { + const char *gsm_name; + const char *other_name; + const char *iconv_from_name; + const char *iconv_to_name; + MMModemCharset charset; +} CharsetEntry; + +static CharsetEntry charset_map[] = { + { "UTF-8", "UTF8", "UTF-8", "UTF-8//TRANSLIT", MM_MODEM_CHARSET_UTF8 }, + { "UCS2", NULL, "UCS-2BE", "UCS-2BE//TRANSLIT", MM_MODEM_CHARSET_UCS2 }, + { "IRA", "ASCII", "ASCII", "ASCII//TRANSLIT", MM_MODEM_CHARSET_IRA }, + { "GSM", NULL, NULL, NULL, MM_MODEM_CHARSET_GSM }, + { "8859-1", NULL, "ISO8859-1", "ISO8859-1//TRANSLIT", MM_MODEM_CHARSET_8859_1 }, + { "PCCP437", NULL, NULL, NULL, MM_MODEM_CHARSET_PCCP437 }, + { "PCDN", NULL, NULL, NULL, MM_MODEM_CHARSET_PCDN }, + { "HEX", NULL, NULL, NULL, MM_MODEM_CHARSET_HEX }, + { NULL, NULL, NULL, NULL, MM_MODEM_CHARSET_UNKNOWN } +}; + +const char * +mm_modem_charset_to_string (MMModemCharset charset) +{ + CharsetEntry *iter = &charset_map[0]; + + g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL); + + while (iter->gsm_name) { + if (iter->charset == charset) + return iter->gsm_name; + iter++; + } + g_warn_if_reached (); + return NULL; +} + +MMModemCharset +mm_modem_charset_from_string (const char *string) +{ + CharsetEntry *iter = &charset_map[0]; + + g_return_val_if_fail (string != NULL, MM_MODEM_CHARSET_UNKNOWN); + + while (iter->gsm_name) { + if (strcasestr (string, iter->gsm_name)) + return iter->charset; + if (iter->other_name && strcasestr (string, iter->other_name)) + return iter->charset; + iter++; + } + return MM_MODEM_CHARSET_UNKNOWN; +} + +static const char * +charset_iconv_to (MMModemCharset charset) +{ + CharsetEntry *iter = &charset_map[0]; + + g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL); + + while (iter->gsm_name) { + if (iter->charset == charset) + return iter->iconv_to_name; + iter++; + } + g_warn_if_reached (); + return NULL; +} + +static const char * +charset_iconv_from (MMModemCharset charset) +{ + CharsetEntry *iter = &charset_map[0]; + + g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL); + + while (iter->gsm_name) { + if (iter->charset == charset) + return iter->iconv_from_name; + iter++; + } + g_warn_if_reached (); + return NULL; +} + +gboolean +mm_modem_charset_byte_array_append (GByteArray *array, + const char *string, + gboolean quoted, + MMModemCharset charset) +{ + const char *iconv_to; + char *converted; + GError *error = NULL; + gsize written = 0; + + g_return_val_if_fail (array != NULL, FALSE); + g_return_val_if_fail (string != NULL, FALSE); + + iconv_to = charset_iconv_to (charset); + g_return_val_if_fail (iconv_to != NULL, FALSE); + + converted = g_convert (string, + g_utf8_strlen (string, -1), + iconv_to, + "UTF-8", + NULL, + &written, + &error); + if (!converted) { + if (error) { + g_warning ("%s: failed to convert '%s' to %s character set: (%d) %s", + __func__, string, iconv_to, + error->code, error->message); + g_error_free (error); + } + return FALSE; + } + + if (quoted) + g_byte_array_append (array, (const guint8 *) "\"", 1); + g_byte_array_append (array, (const guint8 *) converted, written); + if (quoted) + g_byte_array_append (array, (const guint8 *) "\"", 1); + + g_free (converted); + return TRUE; +} + +char * +mm_modem_charset_hex_to_utf8 (const char *src, MMModemCharset charset) +{ + char *unconverted; + const char *iconv_from; + gsize unconverted_len = 0; + + g_return_val_if_fail (src != NULL, NULL); + g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL); + + iconv_from = charset_iconv_from (charset); + g_return_val_if_fail (iconv_from != NULL, FALSE); + + unconverted = utils_hexstr2bin (src, &unconverted_len); + g_return_val_if_fail (unconverted != NULL, NULL); + + if (charset == MM_MODEM_CHARSET_UTF8 || charset == MM_MODEM_CHARSET_IRA) + return unconverted; + + return g_convert (unconverted, unconverted_len, "UTF-8//TRANSLIT", iconv_from, NULL, NULL, NULL); +} + diff --git a/src/mm-charsets.h b/src/mm-charsets.h new file mode 100644 index 0000000..5fa3406 --- /dev/null +++ b/src/mm-charsets.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_CHARSETS_H +#define MM_CHARSETS_H + +#include <glib.h> + +typedef enum { + MM_MODEM_CHARSET_UNKNOWN = 0x00000000, + MM_MODEM_CHARSET_GSM = 0x00000001, + MM_MODEM_CHARSET_IRA = 0x00000002, + MM_MODEM_CHARSET_8859_1 = 0x00000004, + MM_MODEM_CHARSET_UTF8 = 0x00000008, + MM_MODEM_CHARSET_UCS2 = 0x00000010, + MM_MODEM_CHARSET_PCCP437 = 0x00000020, + MM_MODEM_CHARSET_PCDN = 0x00000040, + MM_MODEM_CHARSET_HEX = 0x00000080 +} MMModemCharset; + +const char *mm_modem_charset_to_string (MMModemCharset charset); + +MMModemCharset mm_modem_charset_from_string (const char *string); + +/* Append the given string to the given byte array but re-encode it + * into the given charset first. The original string is assumed to be + * UTF-8 encoded. + */ +gboolean mm_modem_charset_byte_array_append (GByteArray *array, + const char *string, + gboolean quoted, + MMModemCharset charset); + +/* Take a string in hex representation ("00430052" or "A4BE11" for example) + * and convert it from the given character set to UTF-8. + */ +char *mm_modem_charset_hex_to_utf8 (const char *src, MMModemCharset charset); + +#endif /* MM_CHARSETS_H */ + diff --git a/src/mm-errors.c b/src/mm-errors.c index 34f56c1..e4fdda7 100644 --- a/src/mm-errors.c +++ b/src/mm-errors.c @@ -16,6 +16,9 @@ #include "mm-errors.h" +#include <string.h> +#include <ctype.h> + #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } GQuark @@ -36,10 +39,12 @@ mm_serial_error_get_type (void) if (etype == 0) { static const GEnumValue values[] = { - ENUM_ENTRY (MM_SERIAL_OPEN_FAILED, "SerialOpenFailed"), - ENUM_ENTRY (MM_SERIAL_SEND_FAILED, "SerialSendfailed"), - ENUM_ENTRY (MM_SERIAL_RESPONSE_TIMEOUT, "SerialResponseTimeout"), - ENUM_ENTRY (MM_SERIAL_OPEN_FAILED_NO_DEVICE, "SerialOpenFailedNoDevice"), + ENUM_ENTRY (MM_SERIAL_ERROR_OPEN_FAILED, "SerialOpenFailed"), + ENUM_ENTRY (MM_SERIAL_ERROR_SEND_FAILED, "SerialSendfailed"), + ENUM_ENTRY (MM_SERIAL_ERROR_RESPONSE_TIMEOUT, "SerialResponseTimeout"), + ENUM_ENTRY (MM_SERIAL_ERROR_OPEN_FAILED_NO_DEVICE, "SerialOpenFailedNoDevice"), + ENUM_ENTRY (MM_SERIAL_ERROR_FLASH_FAILED, "SerialFlashFailed"), + ENUM_ENTRY (MM_SERIAL_ERROR_NOT_OPEN, "SerialNotOpen"), { 0, 0, 0 } }; @@ -73,6 +78,8 @@ mm_modem_error_get_type (void) ENUM_ENTRY (MM_MODEM_ERROR_DISCONNECTED, "Disconnected"), ENUM_ENTRY (MM_MODEM_ERROR_OPERATION_IN_PROGRESS, "OperationInProgress"), ENUM_ENTRY (MM_MODEM_ERROR_REMOVED, "Removed"), + ENUM_ENTRY (MM_MODEM_ERROR_AUTHORIZATION_REQUIRED, "AuthorizationRequired"), + ENUM_ENTRY (MM_MODEM_ERROR_UNSUPPORTED_CHARSET, "UnsupportedCharset"), { 0, 0, 0 } }; @@ -222,69 +229,127 @@ mm_mobile_error_get_type (void) return etype; } +typedef struct { + int code; + const char *error; /* lowercase, and stripped of special chars and whitespace */ + const char *message; +} ErrorTable; + +static ErrorTable errors[] = { + { MM_MOBILE_ERROR_PHONE_FAILURE, "phonefailure", "Phone failure" }, + { MM_MOBILE_ERROR_NO_CONNECTION, "noconnectiontophone", "No connection to phone" }, + { MM_MOBILE_ERROR_LINK_RESERVED, "phoneadapterlinkreserved", "Phone-adaptor link reserved" }, + { MM_MOBILE_ERROR_NOT_ALLOWED, "operationnotallowed", "Operation not allowed" }, + { MM_MOBILE_ERROR_NOT_SUPPORTED, "operationnotsupported", "Operation not supported" }, + { MM_MOBILE_ERROR_PH_SIM_PIN, "phsimpinrequired", "PH-SIM PIN required" }, + { MM_MOBILE_ERROR_PH_FSIM_PIN, "phfsimpinrequired", "PH-FSIM PIN required" }, + { MM_MOBILE_ERROR_PH_FSIM_PUK, "phfsimpukrequired", "PH-FSIM PUK required" }, + { MM_MOBILE_ERROR_SIM_NOT_INSERTED, "simnotinserted", "SIM not inserted" }, + { MM_MOBILE_ERROR_SIM_PIN, "simpinrequired", "SIM PIN required" }, + { MM_MOBILE_ERROR_SIM_PUK, "simpukrequired", "SIM PUK required" }, + { MM_MOBILE_ERROR_SIM_FAILURE, "simfailure", "SIM failure" }, + { MM_MOBILE_ERROR_SIM_BUSY, "simbusy", "SIM busy" }, + { MM_MOBILE_ERROR_SIM_WRONG, "simwrong", "SIM wrong" }, + { MM_MOBILE_ERROR_WRONG_PASSWORD, "incorrectpassword", "Incorrect password" }, + { MM_MOBILE_ERROR_SIM_PIN2, "simpin2required", "SIM PIN2 required" }, + { MM_MOBILE_ERROR_SIM_PUK2, "simpuk2required", "SIM PUK2 required" }, + { MM_MOBILE_ERROR_MEMORY_FULL, "memoryfull", "Memory full" }, + { MM_MOBILE_ERROR_INVALID_INDEX, "invalidindex", "Invalid index" }, + { MM_MOBILE_ERROR_NOT_FOUND, "notfound", "Not found" }, + { MM_MOBILE_ERROR_MEMORY_FAILURE, "memoryfailure", "Memory failure" }, + { MM_MOBILE_ERROR_TEXT_TOO_LONG, "textstringtoolong", "Text string too long" }, + { MM_MOBILE_ERROR_INVALID_CHARS, "invalidcharactersintextstring", "Invalid characters in text string" }, + { MM_MOBILE_ERROR_DIAL_STRING_TOO_LONG, "dialstringtoolong", "Dial string too long" }, + { MM_MOBILE_ERROR_DIAL_STRING_INVALID, "invalidcharactersindialstring", "Invalid characters in dial string" }, + { MM_MOBILE_ERROR_NO_NETWORK, "nonetworkservice", "No network service" }, + { MM_MOBILE_ERROR_NETWORK_TIMEOUT, "networktimeout", "Network timeout" }, + { MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED, "networknotallowedemergencycallsonly", "Network not allowed - emergency calls only" }, + { MM_MOBILE_ERROR_NETWORK_PIN, "networkpersonalizationpinrequired", "Network personalization PIN required" }, + { MM_MOBILE_ERROR_NETWORK_PUK, "networkpersonalizationpukrequired", "Network personalization PUK required" }, + { MM_MOBILE_ERROR_NETWORK_SUBSET_PIN, "networksubsetpersonalizationpinrequired", "Network subset personalization PIN required" }, + { MM_MOBILE_ERROR_NETWORK_SUBSET_PUK, "networksubsetpersonalizationpukrequired", "Network subset personalization PUK required" }, + { MM_MOBILE_ERROR_SERVICE_PIN, "serviceproviderpersonalizationpinrequired", "Service provider personalization PIN required" }, + { MM_MOBILE_ERROR_SERVICE_PUK, "serviceproviderpersonalizationpukrequired", "Service provider personalization PUK required" }, + { MM_MOBILE_ERROR_CORP_PIN, "corporatepersonalizationpinrequired", "Corporate personalization PIN required" }, + { MM_MOBILE_ERROR_CORP_PUK, "corporatepersonalizationpukrequired", "Corporate personalization PUK required" }, + { MM_MOBILE_ERROR_HIDDEN_KEY, "phsimpukrequired", "Hidden key required" }, + { MM_MOBILE_ERROR_EAP_NOT_SUPPORTED, "eapmethodnotsupported", "EAP method not supported" }, + { MM_MOBILE_ERROR_INCORRECT_PARAMS, "incorrectparameters", "Incorrect parameters" }, + { MM_MOBILE_ERROR_UNKNOWN, "unknownerror", "Unknown error" }, + { MM_MOBILE_ERROR_GPRS_ILLEGAL_MS, "illegalms", "Illegal MS" }, + { MM_MOBILE_ERROR_GPRS_ILLEGAL_ME, "illegalme", "Illegal ME" }, + { MM_MOBILE_ERROR_GPRS_SERVICE_NOT_ALLOWED, "gprsservicesnotallowed", "GPRS services not allowed" }, + { MM_MOBILE_ERROR_GPRS_PLMN_NOT_ALLOWED, "plmnnotallowed", "PLMN not allowed" }, + { MM_MOBILE_ERROR_GPRS_LOCATION_NOT_ALLOWED, "locationareanotallowed", "Location area not allowed" }, + { MM_MOBILE_ERROR_GPRS_ROAMING_NOT_ALLOWED, "roamingnotallowedinthislocationarea", "Roaming not allowed in this location area" }, + { MM_MOBILE_ERROR_GPRS_OPTION_NOT_SUPPORTED, "serviceoperationnotsupported", "Service option not supported" }, + { MM_MOBILE_ERROR_GPRS_NOT_SUBSCRIBED, "requestedserviceoptionnotsubscribed", "Requested service option not subscribed" }, + { MM_MOBILE_ERROR_GPRS_OUT_OF_ORDER, "serviceoptiontemporarilyoutoforder", "Service option temporarily out of order" }, + { MM_MOBILE_ERROR_GPRS_UNKNOWN, "unspecifiedgprserror", "Unspecified GPRS error" }, + { MM_MOBILE_ERROR_GPRS_PDP_AUTH_FAILURE, "pdpauthenticationfailure", "PDP authentication failure" }, + { MM_MOBILE_ERROR_GPRS_INVALID_CLASS, "invalidmobileclass", "Invalid mobile class" }, + { -1, NULL, NULL } +}; + GError * mm_mobile_error_for_code (int error_code) { - const char *msg; + const char *msg = NULL; + const ErrorTable *ptr = &errors[0]; + + while (ptr->code >= 0) { + if (ptr->code == error_code) { + msg = ptr->message; + break; + } + ptr++; + } - switch (error_code) { - case MM_MOBILE_ERROR_PHONE_FAILURE: msg = "Phone failure"; break; - case MM_MOBILE_ERROR_NO_CONNECTION: msg = "No connection to phone"; break; - case MM_MOBILE_ERROR_LINK_RESERVED: msg = "Phone-adaptor link reserved"; break; - case MM_MOBILE_ERROR_NOT_ALLOWED: msg = "Operation not allowed"; break; - case MM_MOBILE_ERROR_NOT_SUPPORTED: msg = "Operation not supported"; break; - case MM_MOBILE_ERROR_PH_SIM_PIN: msg = "PH-SIM PIN required"; break; - case MM_MOBILE_ERROR_PH_FSIM_PIN: msg = "PH-FSIM PIN required"; break; - case MM_MOBILE_ERROR_PH_FSIM_PUK: msg = "PH-FSIM PUK required"; break; - case MM_MOBILE_ERROR_SIM_NOT_INSERTED: msg = "SIM not inserted"; break; - case MM_MOBILE_ERROR_SIM_PIN: msg = "SIM PIN required"; break; - case MM_MOBILE_ERROR_SIM_PUK: msg = "SIM PUK required"; break; - case MM_MOBILE_ERROR_SIM_FAILURE: msg = "SIM failure"; break; - case MM_MOBILE_ERROR_SIM_BUSY: msg = "SIM busy"; break; - case MM_MOBILE_ERROR_SIM_WRONG: msg = "SIM wrong"; break; - case MM_MOBILE_ERROR_WRONG_PASSWORD: msg = "Incorrect password"; break; - case MM_MOBILE_ERROR_SIM_PIN2: msg = "SIM PIN2 required"; break; - case MM_MOBILE_ERROR_SIM_PUK2: msg = "SIM PUK2 required"; break; - case MM_MOBILE_ERROR_MEMORY_FULL: msg = "Memory full"; break; - case MM_MOBILE_ERROR_INVALID_INDEX: msg = "Invalid index"; break; - case MM_MOBILE_ERROR_NOT_FOUND: msg = "Not found"; break; - case MM_MOBILE_ERROR_MEMORY_FAILURE: msg = "Memory failure"; break; - case MM_MOBILE_ERROR_TEXT_TOO_LONG: msg = "Text string too long"; break; - case MM_MOBILE_ERROR_INVALID_CHARS: msg = "Invalid characters in text string"; break; - case MM_MOBILE_ERROR_DIAL_STRING_TOO_LONG: msg = "Dial string too long"; break; - case MM_MOBILE_ERROR_DIAL_STRING_INVALID: msg = "Invalid characters in dial string"; break; - case MM_MOBILE_ERROR_NO_NETWORK: msg = "No network service"; break; - case MM_MOBILE_ERROR_NETWORK_TIMEOUT: msg = "Network timeout"; break; - case MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED: msg = "Network not allowed - emergency calls only"; break; - case MM_MOBILE_ERROR_NETWORK_PIN: msg = "Network personalization PIN required"; break; - case MM_MOBILE_ERROR_NETWORK_PUK: msg = "Network personalization PUK required"; break; - case MM_MOBILE_ERROR_NETWORK_SUBSET_PIN: msg = "Network subset personalization PIN required"; break; - case MM_MOBILE_ERROR_NETWORK_SUBSET_PUK: msg = "Network subset personalization PUK required"; break; - case MM_MOBILE_ERROR_SERVICE_PIN: msg = "Service provider personalization PIN required"; break; - case MM_MOBILE_ERROR_SERVICE_PUK: msg = "Service provider personalization PUK required"; break; - case MM_MOBILE_ERROR_CORP_PIN: msg = "Corporate personalization PIN required"; break; - case MM_MOBILE_ERROR_CORP_PUK: msg = "Corporate personalization PUK required"; break; - case MM_MOBILE_ERROR_HIDDEN_KEY: msg = "Hidden key required"; break; - case MM_MOBILE_ERROR_EAP_NOT_SUPPORTED: msg = "EAP method not supported"; break; - case MM_MOBILE_ERROR_INCORRECT_PARAMS: msg = "Incorrect parameters"; break; - case MM_MOBILE_ERROR_UNKNOWN: msg = "Unknown error"; break; - case MM_MOBILE_ERROR_GPRS_ILLEGAL_MS: msg = "Illegal MS"; break; - case MM_MOBILE_ERROR_GPRS_ILLEGAL_ME: msg = "Illegal ME"; break; - case MM_MOBILE_ERROR_GPRS_SERVICE_NOT_ALLOWED: msg = "GPRS services not allowed"; break; - case MM_MOBILE_ERROR_GPRS_PLMN_NOT_ALLOWED: msg = "PLMN not allowed"; break; - case MM_MOBILE_ERROR_GPRS_LOCATION_NOT_ALLOWED: msg = "Location area not allowed"; break; - case MM_MOBILE_ERROR_GPRS_ROAMING_NOT_ALLOWED: msg = "Roaming not allowed in this location area"; break; - case MM_MOBILE_ERROR_GPRS_OPTION_NOT_SUPPORTED: msg = "Service option not supported"; break; - case MM_MOBILE_ERROR_GPRS_NOT_SUBSCRIBED: msg = "Requested service option not subscribed"; break; - case MM_MOBILE_ERROR_GPRS_OUT_OF_ORDER: msg = "Service option temporarily out of order"; break; - case MM_MOBILE_ERROR_GPRS_PDP_AUTH_FAILURE: msg = "PDP authentication failure"; break; - case MM_MOBILE_ERROR_GPRS_UNKNOWN: msg = "Unspecified GPRS error"; break; - case MM_MOBILE_ERROR_GPRS_INVALID_CLASS: msg = "Invalid mobile class"; break; - default: - g_warning ("Invalid error code"); + if (!msg) { + g_warning ("Invalid error code: %d", error_code); error_code = MM_MOBILE_ERROR_UNKNOWN; msg = "Unknown error"; } return g_error_new_literal (MM_MOBILE_ERROR, error_code, msg); } + +#define BUF_SIZE 100 + +GError * +mm_mobile_error_for_string (const char *str) +{ + int error_code = -1; + const ErrorTable *ptr = &errors[0]; + char buf[BUF_SIZE + 1]; + const char *msg = NULL, *p = str; + int i = 0; + + g_return_val_if_fail (str != NULL, NULL); + + /* Normalize the error code by stripping whitespace and odd characters */ + while (*p && i < BUF_SIZE) { + if (isalnum (*p)) + buf[i++] = tolower (*p); + p++; + } + buf[i] = '\0'; + + while (ptr->code >= 0) { + if (!strcmp (buf, ptr->error)) { + error_code = ptr->code; + msg = ptr->message; + break; + } + ptr++; + } + + if (!msg) { + g_warning ("Invalid error code: %d", error_code); + error_code = MM_MOBILE_ERROR_UNKNOWN; + msg = "Unknown error"; + } + + return g_error_new_literal (MM_MOBILE_ERROR, error_code, msg); +} + diff --git a/src/mm-errors.h b/src/mm-errors.h index c02a351..13a531d 100644 --- a/src/mm-errors.h +++ b/src/mm-errors.h @@ -20,10 +20,12 @@ #include <glib-object.h> enum { - MM_SERIAL_OPEN_FAILED = 0, - MM_SERIAL_SEND_FAILED = 1, - MM_SERIAL_RESPONSE_TIMEOUT = 2, - MM_SERIAL_OPEN_FAILED_NO_DEVICE = 3 + MM_SERIAL_ERROR_OPEN_FAILED = 0, + MM_SERIAL_ERROR_SEND_FAILED = 1, + MM_SERIAL_ERROR_RESPONSE_TIMEOUT = 2, + MM_SERIAL_ERROR_OPEN_FAILED_NO_DEVICE = 3, + MM_SERIAL_ERROR_FLASH_FAILED = 4, + MM_SERIAL_ERROR_NOT_OPEN = 5, }; #define MM_SERIAL_ERROR (mm_serial_error_quark ()) @@ -39,7 +41,9 @@ enum { MM_MODEM_ERROR_CONNECTED = 2, MM_MODEM_ERROR_DISCONNECTED = 3, MM_MODEM_ERROR_OPERATION_IN_PROGRESS = 4, - MM_MODEM_ERROR_REMOVED = 5 + MM_MODEM_ERROR_REMOVED = 5, + MM_MODEM_ERROR_AUTHORIZATION_REQUIRED = 6, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET = 7 }; #define MM_MODEM_ERROR (mm_modem_error_quark ()) @@ -64,6 +68,7 @@ GType mm_modem_connect_error_get_type (void); GError *mm_modem_connect_error_for_code (int error_code); +/* 3GPP TS 07.07 version 7.8.0 Release 1998 (page 90) ETSI TS 100 916 V7.8.0 (2003-03) */ enum { MM_MOBILE_ERROR_PHONE_FAILURE = 0, MM_MOBILE_ERROR_NO_CONNECTION = 1, @@ -115,8 +120,8 @@ enum { MM_MOBILE_ERROR_GPRS_OPTION_NOT_SUPPORTED = 132, MM_MOBILE_ERROR_GPRS_NOT_SUBSCRIBED = 133, MM_MOBILE_ERROR_GPRS_OUT_OF_ORDER = 134, - MM_MOBILE_ERROR_GPRS_PDP_AUTH_FAILURE = 149, MM_MOBILE_ERROR_GPRS_UNKNOWN = 148, + MM_MOBILE_ERROR_GPRS_PDP_AUTH_FAILURE = 149, MM_MOBILE_ERROR_GPRS_INVALID_CLASS = 150 }; @@ -127,5 +132,6 @@ enum { GQuark mm_mobile_error_quark (void); GType mm_mobile_error_get_type (void); GError *mm_mobile_error_for_code (int error_code); +GError *mm_mobile_error_for_string (const char *str); #endif /* MM_MODEM_ERROR_H */ diff --git a/src/mm-generic-cdma.c b/src/mm-generic-cdma.c index 50cd86c..9fe897f 100644 --- a/src/mm-generic-cdma.c +++ b/src/mm-generic-cdma.c @@ -23,10 +23,15 @@ #include "mm-generic-cdma.h" #include "mm-modem-cdma.h" #include "mm-modem-simple.h" -#include "mm-serial-port.h" +#include "mm-at-serial-port.h" +#include "mm-qcdm-serial-port.h" #include "mm-errors.h" #include "mm-callback-info.h" #include "mm-serial-parsers.h" +#include "mm-modem-helpers.h" +#include "libqcdm/src/commands.h" + +#define MM_GENERIC_CDMA_PREV_STATE_TAG "prev-state" static void simple_reg_callback (MMModemCdma *modem, MMModemCdmaRegistrationState cdma_1x_reg_state, @@ -58,6 +63,10 @@ typedef struct { gboolean evdo_rev0; gboolean evdo_revA; gboolean reg_try_css; + gboolean has_spservice; + gboolean has_speri; + + guint poll_id; MMModemCdmaRegistrationState cdma_1x_reg_state; MMModemCdmaRegistrationState evdo_reg_state; @@ -67,8 +76,9 @@ typedef struct { guint reg_state_changed_id; MMCallbackInfo *simple_connect_info; - MMSerialPort *primary; - MMSerialPort *secondary; + MMAtSerialPort *primary; + MMAtSerialPort *secondary; + MMQcdmSerialPort *qcdm; MMPort *data; } MMGenericCdmaPrivate; @@ -114,6 +124,44 @@ check_valid (MMGenericCdma *self) mm_modem_base_set_valid (MM_MODEM_BASE (self), new_valid); } +static void +get_esn_cb (MMModem *modem, + const char *result, + GError *error, + gpointer user_data) +{ + if (modem) { + mm_modem_base_set_equipment_identifier (MM_MODEM_BASE (modem), error ? "" : result); + mm_serial_port_close (MM_SERIAL_PORT (MM_GENERIC_CDMA_GET_PRIVATE (modem)->primary)); + check_valid (MM_GENERIC_CDMA (modem)); + } +} + +static void +initial_esn_check (MMGenericCdma *self) +{ + GError *error = NULL; + MMGenericCdmaPrivate *priv; + + g_return_if_fail (MM_IS_GENERIC_CDMA (self)); + priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + + g_return_if_fail (priv->primary != NULL); + + if (mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &error)) { + /* Make sure echoing is off */ + mm_at_serial_port_queue_command (priv->primary, "E0", 3, NULL, NULL); + mm_modem_cdma_get_esn (MM_MODEM_CDMA (self), get_esn_cb, NULL); + } else { + g_warning ("%s: failed to open serial port: (%d) %s", + __func__, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + check_valid (self); + } +} + static gboolean owns_port (MMModem *modem, const char *subsys, const char *name) { @@ -148,25 +196,36 @@ mm_generic_cdma_grab_port (MMGenericCdma *self, } port = mm_modem_base_add_port (MM_MODEM_BASE (self), subsys, name, ptype); - if (port && MM_IS_SERIAL_PORT (port)) { + if (!port) { + g_warn_if_fail (port != NULL); + return NULL; + } + + if (MM_IS_AT_SERIAL_PORT (port)) { g_object_set (G_OBJECT (port), MM_PORT_CARRIER_DETECT, FALSE, NULL); - mm_serial_port_set_response_parser (MM_SERIAL_PORT (port), - mm_serial_parser_v1_parse, - mm_serial_parser_v1_new (), - mm_serial_parser_v1_destroy); + mm_at_serial_port_set_response_parser (MM_AT_SERIAL_PORT (port), + mm_serial_parser_v1_e1_parse, + mm_serial_parser_v1_e1_new (), + mm_serial_parser_v1_e1_destroy); if (ptype == MM_PORT_TYPE_PRIMARY) { - priv->primary = MM_SERIAL_PORT (port); + priv->primary = MM_AT_SERIAL_PORT (port); if (!priv->data) { priv->data = port; g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); } - check_valid (self); + + /* Get modem's ESN number */ + initial_esn_check (self); + } else if (ptype == MM_PORT_TYPE_SECONDARY) - priv->secondary = MM_SERIAL_PORT (port); - } else { + priv->secondary = MM_AT_SERIAL_PORT (port); + } else if (MM_IS_QCDM_SERIAL_PORT (port)) { + if (!priv->qcdm) + priv->qcdm = MM_QCDM_SERIAL_PORT (port); + } else if (!strcmp (subsys, "net")) { /* Net device (if any) is the preferred data port */ - if (!priv->data || MM_IS_SERIAL_PORT (priv->data)) { + if (!priv->data || MM_IS_AT_SERIAL_PORT (priv->data)) { priv->data = port; g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); check_valid (self); @@ -197,7 +256,7 @@ release_port (MMModem *modem, const char *subsys, const char *name) if (!port) return; - if (port == MM_PORT (priv->primary)) { + if (port == (MMPort *) priv->primary) { mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); priv->primary = NULL; } @@ -207,17 +266,22 @@ release_port (MMModem *modem, const char *subsys, const char *name) g_object_notify (G_OBJECT (modem), MM_MODEM_DATA_DEVICE); } - if (port == MM_PORT (priv->secondary)) { + if (port == (MMPort *) priv->secondary) { mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); priv->secondary = NULL; } + if (port == (MMPort *) priv->qcdm) { + mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); + priv->qcdm = NULL; + } + check_valid (MM_GENERIC_CDMA (modem)); } -MMSerialPort * -mm_generic_cdma_get_port (MMGenericCdma *modem, - MMPortType ptype) +MMAtSerialPort * +mm_generic_cdma_get_at_port (MMGenericCdma *modem, + MMPortType ptype) { g_return_val_if_fail (MM_IS_GENERIC_CDMA (modem), NULL); g_return_val_if_fail (ptype != MM_PORT_TYPE_UNKNOWN, NULL); @@ -230,6 +294,27 @@ mm_generic_cdma_get_port (MMGenericCdma *modem, return NULL; } +MMAtSerialPort * +mm_generic_cdma_get_best_at_port (MMGenericCdma *self, GError **error) +{ + MMGenericCdmaPrivate *priv; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), NULL); + + priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + + if (!mm_port_get_connected (MM_PORT (priv->primary))) + return priv->primary; + + if (!priv->secondary) { + g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, + "Cannot perform this operation while connected"); + } + + return priv->secondary; +} + /*****************************************************************************/ void @@ -301,6 +386,38 @@ mm_generic_cdma_evdo_get_registration_state_sync (MMGenericCdma *self) /*****************************************************************************/ static void +periodic_poll_reg_cb (MMModemCdma *modem, + MMModemCdmaRegistrationState cdma_1x_reg_state, + MMModemCdmaRegistrationState evdo_reg_state, + GError *error, + gpointer user_data) +{ + /* cached reg state already updated */ +} + +static void +periodic_poll_signal_quality_cb (MMModem *modem, + guint32 result, + GError *error, + gpointer user_data) +{ + /* cached signal quality already updated */ +} + +static gboolean +periodic_poll_cb (gpointer user_data) +{ + MMGenericCdma *self = MM_GENERIC_CDMA (user_data); + + mm_modem_cdma_get_registration_state (MM_MODEM_CDMA (self), periodic_poll_reg_cb, NULL); + mm_modem_cdma_get_signal_quality (MM_MODEM_CDMA (self), periodic_poll_signal_quality_cb, NULL); + + return TRUE; +} + +/*****************************************************************************/ + +static void update_enabled_state (MMGenericCdma *self, gboolean stay_connected, MMModemStateReason reason) @@ -349,6 +466,43 @@ registration_cleanup (MMGenericCdma *self, GQuark error_class, guint32 error_num } static void +get_enable_info_done (MMModem *modem, + const char *manufacturer, + const char *model, + const char *version, + GError *error, + gpointer user_data) +{ + /* Modem base class handles the response for us */ +} + +static void +spservice_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + if (!error) { + MM_GENERIC_CDMA_GET_PRIVATE (user_data)->has_spservice = TRUE; + + /* +SPSERVICE provides a better indicator of registration status than + * +CSS, which some devices implement inconsistently. + */ + MM_GENERIC_CDMA_GET_PRIVATE (user_data)->reg_try_css = FALSE; + } +} + +static void +speri_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + if (!error) + MM_GENERIC_CDMA_GET_PRIVATE (user_data)->has_speri = TRUE; +} + +static void enable_all_done (MMModem *modem, GError *error, gpointer user_data) { MMCallbackInfo *info = user_data; @@ -358,15 +512,33 @@ enable_all_done (MMModem *modem, GError *error, gpointer user_data) if (error) info->error = g_error_copy (error); else { + /* Try to enable XON/XOFF flow control */ + mm_at_serial_port_queue_command (priv->primary, "+IFC=1,1", 3, NULL, NULL); + /* Open up the second port, if one exists */ if (priv->secondary) { - if (!mm_serial_port_open (priv->secondary, &info->error)) { + if (!mm_serial_port_open (MM_SERIAL_PORT (priv->secondary), &info->error)) { + g_assert (info->error); + goto out; + } + } + + /* Open up the second port, if one exists */ + if (priv->qcdm) { + if (!mm_serial_port_open (MM_SERIAL_PORT (priv->qcdm), &info->error)) { g_assert (info->error); goto out; } } update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE); + + /* Grab device info right away */ + mm_modem_get_info (modem, get_enable_info_done, NULL); + + /* Check for support of Sprint-specific phone commands */ + mm_at_serial_port_queue_command (priv->primary, "+SPSERVICE?", 3, spservice_done, self); + mm_at_serial_port_queue_command (priv->primary, "$SPERI?", 3, speri_done, self); } out: @@ -380,7 +552,7 @@ out: } static void -enable_error_reporting_done (MMSerialPort *port, +enable_error_reporting_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -399,7 +571,7 @@ enable_error_reporting_done (MMSerialPort *port, } static void -init_done (MMSerialPort *port, +init_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -419,7 +591,7 @@ init_done (MMSerialPort *port, FIXME: It's mandatory by spec, so it really shouldn't be optional. Figure out which CDMA modems have problems with it and implement plugin for them. */ - mm_serial_port_queue_command (port, "+CMEE=1", 3, enable_error_reporting_done, user_data); + mm_at_serial_port_queue_command (port, "+CMEE=1", 3, enable_error_reporting_done, user_data); } } @@ -439,7 +611,7 @@ flash_done (MMSerialPort *port, GError *error, gpointer user_data) return; } - mm_serial_port_queue_command (port, "Z E0 V1 X4 &C1", 3, init_done, user_data); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "Z E0 V1 X4 &C1", 3, init_done, user_data); } static void @@ -453,7 +625,7 @@ enable (MMModem *modem, info = mm_callback_info_new (modem, callback, user_data); - if (!mm_serial_port_open (priv->primary, &info->error)) { + if (!mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &info->error)) { g_assert (info->error); mm_callback_info_schedule (info); return; @@ -463,7 +635,7 @@ enable (MMModem *modem, MM_MODEM_STATE_ENABLING, MM_MODEM_STATE_REASON_NONE); - mm_serial_port_flash (priv->primary, 100, flash_done, info); + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 100, FALSE, flash_done, info); } static void @@ -489,7 +661,7 @@ disable_all_done (MMModem *modem, GError *error, gpointer user_data) MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); - mm_serial_port_close (priv->primary); + mm_serial_port_close_force (MM_SERIAL_PORT (priv->primary)); mm_modem_set_state (modem, MM_MODEM_STATE_DISABLED, MM_MODEM_STATE_REASON_NONE); priv->cdma_1x_reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; @@ -545,21 +717,24 @@ disable (MMModem *modem, GUINT_TO_POINTER (state), NULL); + /* Close auxiliary serial ports */ if (priv->secondary) - mm_serial_port_close (priv->secondary); + mm_serial_port_close_force (MM_SERIAL_PORT (priv->secondary)); + if (priv->qcdm) + mm_serial_port_close_force (MM_SERIAL_PORT (priv->qcdm)); mm_modem_set_state (MM_MODEM (info->modem), MM_MODEM_STATE_DISABLING, MM_MODEM_STATE_REASON_NONE); if (mm_port_get_connected (MM_PORT (priv->primary))) - mm_serial_port_flash (priv->primary, 1000, disable_flash_done, info); + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disable_flash_done, info); else - disable_flash_done (priv->primary, NULL, info); + disable_flash_done (MM_SERIAL_PORT (priv->primary), NULL, info); } static void -dial_done (MMSerialPort *port, +dial_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -598,7 +773,7 @@ connect (MMModem *modem, info = mm_callback_info_new (modem, callback, user_data); command = g_strconcat ("DT", number, NULL); - mm_serial_port_queue_command (priv->primary, command, 90, dial_done, info); + mm_at_serial_port_queue_command (priv->primary, command, 90, dial_done, info); g_free (command); } @@ -648,83 +823,7 @@ disconnect (MMModem *modem, NULL); mm_modem_set_state (modem, MM_MODEM_STATE_DISCONNECTING, MM_MODEM_STATE_REASON_NONE); - mm_serial_port_flash (priv->primary, 1000, disconnect_flash_done, info); -} - -static void -card_info_invoke (MMCallbackInfo *info) -{ - MMModemInfoFn callback = (MMModemInfoFn) info->callback; - - callback (info->modem, - (char *) mm_callback_info_get_data (info, "card-info-manufacturer"), - (char *) mm_callback_info_get_data (info, "card-info-model"), - (char *) mm_callback_info_get_data (info, "card-info-version"), - info->error, info->user_data); -} - -static const char * -strip_response (const char *resp, const char *cmd) -{ - const char *p = resp; - - if (p) { - if (!strncmp (p, cmd, strlen (cmd))) - p += strlen (cmd); - while (*p == ' ') - p++; - } - return p; -} - -static void -get_version_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) -{ - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *p; - - if (!error) { - p = strip_response (response->str, "+GMR:"); - mm_callback_info_set_data (info, "card-info-version", g_strdup (p), g_free); - } else if (!info->error) - info->error = g_error_copy (error); - - mm_callback_info_schedule (info); -} - -static void -get_model_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) -{ - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *p; - - if (!error) { - p = strip_response (response->str, "+GMM:"); - mm_callback_info_set_data (info, "card-info-model", g_strdup (p), g_free); - } else if (!info->error) - info->error = g_error_copy (error); -} - -static void -get_manufacturer_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) -{ - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *p; - - if (!error) { - p = strip_response (response->str, "+GMI:"); - mm_callback_info_set_data (info, "card-info-manufacturer", g_strdup (p), g_free); - } else - info->error = g_error_copy (error); + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disconnect_flash_done, info); } static void @@ -732,30 +831,12 @@ get_card_info (MMModem *modem, MMModemInfoFn callback, gpointer user_data) { - MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); - MMCallbackInfo *info; - MMSerialPort *port = priv->primary; - - info = mm_callback_info_new_full (MM_MODEM (modem), - card_info_invoke, - G_CALLBACK (callback), - user_data); - - if (mm_port_get_connected (MM_PORT (priv->primary))) { - if (!priv->secondary) { - info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, - "Cannot modem info while connected"); - mm_callback_info_schedule (info); - return; - } - - /* Use secondary port if primary is connected */ - port = priv->secondary; - } + MMAtSerialPort *port; + GError *error = NULL; - mm_serial_port_queue_command_cached (port, "+GMI", 3, get_manufacturer_done, info); - mm_serial_port_queue_command_cached (port, "+GMM", 3, get_model_done, info); - mm_serial_port_queue_command_cached (port, "+GMR", 3, get_version_done, info); + port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &error); + mm_modem_base_get_card_info (MM_MODEM_BASE (modem), port, error, callback, user_data); + g_clear_error (&error); } /*****************************************************************************/ @@ -793,7 +874,7 @@ mm_generic_cdma_update_evdo_quality (MMGenericCdma *self, guint32 quality) #define CSQ2_TRIED "csq?-tried" static void -get_signal_quality_done (MMSerialPort *port, +get_signal_quality_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -811,7 +892,7 @@ get_signal_quality_done (MMSerialPort *port, * try the other command if the first one fails. */ mm_callback_info_set_data (info, CSQ2_TRIED, GUINT_TO_POINTER (1), NULL); - mm_serial_port_queue_command (port, "+CSQ?", 3, get_signal_quality_done, info); + mm_at_serial_port_queue_command (port, "+CSQ?", 3, get_signal_quality_done, info); return; } } else { @@ -847,31 +928,102 @@ get_signal_quality_done (MMSerialPort *port, } static void +qcdm_pilot_sets_cb (MMQcdmSerialPort *port, + GByteArray *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMGenericCdmaPrivate *priv; + QCDMResult *result; + guint32 num = 0, quality = 0, i; + float best_db = -28; + + if (error) { + info->error = g_error_copy (error); + goto done; + } + + priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); + + /* Parse the response */ + result = qcdm_cmd_pilot_sets_result ((const char *) response->data, response->len, &info->error); + if (!result) + goto done; + + qcdm_cmd_pilot_sets_result_get_num (result, QCDM_CMD_PILOT_SETS_TYPE_ACTIVE, &num); + for (i = 0; i < num; i++) { + guint32 pn_offset = 0, ecio = 0; + float db = 0; + + qcdm_cmd_pilot_sets_result_get_pilot (result, + QCDM_CMD_PILOT_SETS_TYPE_ACTIVE, + i, + &pn_offset, + &ecio, + &db); + best_db = MAX (db, best_db); + } + qcdm_result_unref (result); + + if (num > 0) { + #define BEST_ECIO 3 + #define WORST_ECIO 25 + + /* EC/IO dB ranges from roughly 0 to -31 dB. Lower == worse. We + * really only care about -3 to -25 dB though, since that's about what + * you'll see in real-world usage. + */ + best_db = CLAMP (ABS (best_db), BEST_ECIO, WORST_ECIO) - BEST_ECIO; + quality = (guint32) (100 - (best_db * 100 / (WORST_ECIO - BEST_ECIO))); + } + + mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL); + + if (priv->cdma1x_quality != quality) { + priv->cdma1x_quality = quality; + mm_modem_cdma_emit_signal_quality_changed (MM_MODEM_CDMA (info->modem), quality); + } + +done: + mm_callback_info_schedule (info); +} + +static void get_signal_quality (MMModemCdma *modem, MMModemUIntFn callback, gpointer user_data) { MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); MMCallbackInfo *info; - MMSerialPort *port = priv->primary; + MMAtSerialPort *at_port; - if (mm_port_get_connected (MM_PORT (priv->primary))) { - if (!priv->secondary) { - g_message ("Returning saved signal quality %d", priv->cdma1x_quality); - callback (MM_MODEM (modem), priv->cdma1x_quality, NULL, user_data); - return; - } + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - /* Use secondary port if primary is connected */ - port = priv->secondary; + at_port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &info->error); + if (!at_port && !priv->qcdm) { + g_message ("Returning saved signal quality %d", priv->cdma1x_quality); + mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->cdma1x_quality), NULL); + mm_callback_info_schedule (info); + return; + } + g_clear_error (&info->error); + + if (at_port) + mm_at_serial_port_queue_command (at_port, "+CSQ", 3, get_signal_quality_done, info); + else if (priv->qcdm) { + GByteArray *pilot_sets; + + /* Use CDMA1x pilot EC/IO if we can */ + pilot_sets = g_byte_array_sized_new (25); + pilot_sets->len = qcdm_cmd_pilot_sets_new ((char *) pilot_sets->data, 25, NULL); + g_assert (pilot_sets->len); + mm_qcdm_serial_port_queue_command (priv->qcdm, pilot_sets, 3, qcdm_pilot_sets_cb, info); } - - info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_queue_command (port, "+CSQ", 3, get_signal_quality_done, info); } static void -get_string_done (MMSerialPort *port, +get_string_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -882,7 +1034,7 @@ get_string_done (MMSerialPort *port, if (error) info->error = g_error_copy (error); else { - p = strip_response (response->str, "+GSN:"); + p = mm_strip_tag (response->str, "+GSN:"); mm_callback_info_set_result (info, g_strdup (p), g_free); } @@ -894,26 +1046,18 @@ get_esn (MMModemCdma *modem, MMModemStringFn callback, gpointer user_data) { - MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); MMCallbackInfo *info; - GError *error; - MMSerialPort *port = priv->primary; - - if (mm_port_get_connected (MM_PORT (priv->primary))) { - if (!priv->secondary) { - error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, - "Cannot get ESN while connected"); - callback (MM_MODEM (modem), NULL, error, user_data); - g_error_free (error); - return; - } + MMAtSerialPort *port; + + info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); - /* Use secondary port if primary is connected */ - port = priv->secondary; + port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; } - info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_queue_command_cached (port, "+GSN", 3, get_string_done, info); + mm_at_serial_port_queue_command_cached (port, "+GSN", 3, get_string_done, info); } static void @@ -996,8 +1140,17 @@ convert_sid (const char *sid) return (int) tmp_sid; } +static GError * +new_css_no_service_error (void) +{ + /* NOTE: update reg_state_css_response() if this error changes */ + return g_error_new_literal (MM_MOBILE_ERROR, + MM_MOBILE_ERROR_NO_NETWORK, + "No service"); +} + static void -serving_system_done (MMSerialPort *port, +serving_system_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1089,12 +1242,9 @@ serving_system_done (MMSerialPort *port, sid = 99999; /* 99999 means unknown/no service */ - if (sid == 99999) { - /* NOTE: update reg_state_css_response() if this error changes */ - info->error = g_error_new_literal (MM_MOBILE_ERROR, - MM_MOBILE_ERROR_NO_NETWORK, - "No service"); - } else { + if (sid == 99999) + info->error = new_css_no_service_error (); + else { mm_callback_info_set_data (info, "class", GUINT_TO_POINTER (class), NULL); mm_callback_info_set_data (info, "band", GUINT_TO_POINTER ((guint32) band), NULL); mm_callback_info_set_data (info, "sid", GUINT_TO_POINTER (sid), NULL); @@ -1109,39 +1259,97 @@ serving_system_done (MMSerialPort *port, } static void +legacy_get_serving_system (MMGenericCdma *self, MMCallbackInfo *info) +{ + MMAtSerialPort *port; + + port = mm_generic_cdma_get_best_at_port (self, &info->error); + if (port) + mm_at_serial_port_queue_command (port, "+CSS?", 3, serving_system_done, info); + else + mm_callback_info_schedule (info); +} + +static void +cdma_status_cb (MMQcdmSerialPort *port, + GByteArray *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + QCDMResult *result; + guint32 sid, rxstate; + + if (error) + goto error; + + /* Parse the response */ + result = qcdm_cmd_cdma_status_result ((const char *) response->data, response->len, &info->error); + if (!result) + goto error; + + qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_RX_STATE, &rxstate); + qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_SID, &sid); + qcdm_result_unref (result); + + if (rxstate == QCDM_CMD_CDMA_STATUS_RX_STATE_ENTERING_CDMA) + info->error = new_css_no_service_error (); + else { + mm_callback_info_set_data (info, "class", GUINT_TO_POINTER (0), NULL); + mm_callback_info_set_data (info, "band", GUINT_TO_POINTER ((guint32) 'Z'), NULL); + mm_callback_info_set_data (info, "sid", GUINT_TO_POINTER (sid), NULL); + } + + mm_callback_info_schedule (info); + return; + +error: + /* If there was some error, fall back to use +CSS like we did before QCDM */ + legacy_get_serving_system (MM_GENERIC_CDMA (info->modem), info); +} + +static void get_serving_system (MMModemCdma *modem, MMModemCdmaServingSystemFn callback, gpointer user_data) { - MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); + MMGenericCdma *self = MM_GENERIC_CDMA (modem); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); MMCallbackInfo *info; - GError *error; - MMSerialPort *port = priv->primary; - - if (mm_port_get_connected (MM_PORT (priv->primary))) { - if (!priv->secondary) { - error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, - "Cannot get serving system while connected"); - callback (modem, 0, 0, 0, error, user_data); - g_error_free (error); - return; - } - - /* Use secondary port if primary is connected */ - port = priv->secondary; - } info = mm_callback_info_new_full (MM_MODEM (modem), serving_system_invoke, G_CALLBACK (callback), user_data); - mm_serial_port_queue_command (port, "+CSS?", 3, serving_system_done, info); + if (priv->qcdm) { + GByteArray *cdma_status; + + cdma_status = g_byte_array_sized_new (25); + cdma_status->len = qcdm_cmd_cdma_status_new ((char *) cdma_status->data, 25, NULL); + g_assert (cdma_status->len); + mm_qcdm_serial_port_queue_command (priv->qcdm, cdma_status, 3, cdma_status_cb, info); + } else + legacy_get_serving_system (self, info); } +/*****************************************************************************/ + +/* Registration state stuff */ + #define CDMA_1X_STATE_TAG "cdma-1x-reg-state" #define EVDO_STATE_TAG "evdo-reg-state" +MMModemCdmaRegistrationState +mm_generic_cdma_query_reg_state_get_callback_1x_state (MMCallbackInfo *info) +{ + g_return_val_if_fail (info != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + g_return_val_if_fail (info->modem != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + g_return_val_if_fail (MM_IS_GENERIC_CDMA (info->modem), MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + + return GPOINTER_TO_UINT (mm_callback_info_get_data (info, CDMA_1X_STATE_TAG)); +} + void mm_generic_cdma_query_reg_state_set_callback_1x_state (MMCallbackInfo *info, MMModemCdmaRegistrationState new_state) @@ -1153,6 +1361,16 @@ mm_generic_cdma_query_reg_state_set_callback_1x_state (MMCallbackInfo *info, mm_callback_info_set_data (info, CDMA_1X_STATE_TAG, GUINT_TO_POINTER (new_state), NULL); } +MMModemCdmaRegistrationState +mm_generic_cdma_query_reg_state_get_callback_evdo_state (MMCallbackInfo *info) +{ + g_return_val_if_fail (info != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + g_return_val_if_fail (info->modem != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + g_return_val_if_fail (MM_IS_GENERIC_CDMA (info->modem), MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + + return GPOINTER_TO_UINT (mm_callback_info_get_data (info, EVDO_STATE_TAG)); +} + void mm_generic_cdma_query_reg_state_set_callback_evdo_state (MMCallbackInfo *info, MMModemCdmaRegistrationState new_state) @@ -1171,18 +1389,19 @@ registration_state_invoke (MMCallbackInfo *info) /* note: This is the MMModemCdma interface callback */ callback (MM_MODEM_CDMA (info->modem), - GPOINTER_TO_UINT (mm_callback_info_get_data (info, CDMA_1X_STATE_TAG)), - GPOINTER_TO_UINT (mm_callback_info_get_data (info, EVDO_STATE_TAG)), + mm_generic_cdma_query_reg_state_get_callback_1x_state (info), + mm_generic_cdma_query_reg_state_get_callback_evdo_state (info), info->error, info->user_data); } MMCallbackInfo * mm_generic_cdma_query_reg_state_callback_info_new (MMGenericCdma *self, + MMModemCdmaRegistrationState cur_cdma_state, + MMModemCdmaRegistrationState cur_evdo_state, MMModemCdmaRegistrationStateFn callback, gpointer user_data) { - MMGenericCdmaPrivate *priv; MMCallbackInfo *info; g_return_val_if_fail (self != NULL, NULL); @@ -1195,15 +1414,8 @@ mm_generic_cdma_query_reg_state_callback_info_new (MMGenericCdma *self, user_data); /* Fill with current state */ - priv = MM_GENERIC_CDMA_GET_PRIVATE (self); - mm_callback_info_set_data (info, - CDMA_1X_STATE_TAG, - GUINT_TO_POINTER (priv->cdma_1x_reg_state), - NULL); - mm_callback_info_set_data (info, - EVDO_STATE_TAG, - GUINT_TO_POINTER (priv->evdo_reg_state), - NULL); + mm_generic_cdma_query_reg_state_set_callback_1x_state (info, cur_cdma_state); + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, cur_evdo_state); return info; } @@ -1211,60 +1423,138 @@ static void set_callback_1x_state_helper (MMCallbackInfo *info, MMModemCdmaRegistrationState new_state) { - MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); - MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); + if (info->modem) { + MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); - mm_generic_cdma_set_1x_registration_state (self, new_state); - mm_generic_cdma_query_reg_state_set_callback_1x_state (info, priv->cdma_1x_reg_state); + mm_generic_cdma_set_1x_registration_state (self, new_state); + mm_generic_cdma_query_reg_state_set_callback_1x_state (info, priv->cdma_1x_reg_state); + } } static void set_callback_evdo_state_helper (MMCallbackInfo *info, MMModemCdmaRegistrationState new_state) { - MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); - MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); + if (info->modem) { + MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); - mm_generic_cdma_set_evdo_registration_state (self, new_state); - mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, priv->evdo_reg_state); + mm_generic_cdma_set_evdo_registration_state (self, new_state); + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, priv->evdo_reg_state); + } } static void -reg_state_query_done (MMModemCdma *cdma, - MMModemCdmaRegistrationState cdma_1x_reg_state, - MMModemCdmaRegistrationState evdo_reg_state, +subclass_reg_query_done (MMModemCdma *cdma, + MMModemCdmaRegistrationState cdma_reg_state, + MMModemCdmaRegistrationState evdo_reg_state, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + info->error = mm_modem_check_removed (info->modem, error); + if (!info->error) { + /* Set final registration state */ + set_callback_1x_state_helper (info, cdma_reg_state); + set_callback_evdo_state_helper (info, evdo_reg_state); + } + + mm_callback_info_schedule (info); +} + +static void +reg_query_speri_done (MMAtSerialPort *port, + GString *response, GError *error, gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMCallbackInfo *info = user_data; + gboolean roam = FALSE; + const char *p; + + if (error) + goto done; + + p = mm_strip_tag (response->str, "$SPERI:"); + if (!p || !mm_cdma_parse_eri (p, &roam, NULL, NULL)) + goto done; + + /* Change the 1x and EVDO registration states to roaming if they were + * anything other than UNKNOWN. + */ + if (roam) { + if (mm_generic_cdma_query_reg_state_get_callback_1x_state (info)) + mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING); + + if (mm_generic_cdma_query_reg_state_get_callback_evdo_state (info)) + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING); + } + +done: + mm_callback_info_schedule (info); +} + +static void +reg_query_spservice_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMModemCdmaRegistrationState cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; if (error) info->error = g_error_copy (error); - else { - set_callback_1x_state_helper (info, cdma_1x_reg_state); - set_callback_evdo_state_helper (info, evdo_reg_state); + else if (mm_cdma_parse_spservice_response (response->str, &cdma_state, &evdo_state)) { + mm_generic_cdma_query_reg_state_set_callback_1x_state (info, cdma_state); + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, evdo_state); + + if (MM_GENERIC_CDMA_GET_PRIVATE (info->modem)->has_speri) { + /* Get roaming status to override generic registration state */ + mm_at_serial_port_queue_command (port, "$SPERI?", 3, reg_query_speri_done, info); + return; + } } mm_callback_info_schedule (info); } static void -query_subclass_registration_state (MMGenericCdma *self, MMCallbackInfo *info) +real_query_registration_state (MMGenericCdma *self, + MMModemCdmaRegistrationState cur_cdma_state, + MMModemCdmaRegistrationState cur_evdo_state, + MMModemCdmaRegistrationStateFn callback, + gpointer user_data) { - /* Let subclasses figure out roaming and detailed registration state */ - if (MM_GENERIC_CDMA_GET_CLASS (self)->query_registration_state) { - MM_GENERIC_CDMA_GET_CLASS (self)->query_registration_state (self, - reg_state_query_done, - info); + MMCallbackInfo *info; + MMAtSerialPort *port; + + /* Seed this CallbackInfo with any previously determined registration state */ + info = mm_generic_cdma_query_reg_state_callback_info_new (self, + cur_cdma_state, + cur_evdo_state, + callback, + user_data); + + port = mm_generic_cdma_get_best_at_port (self, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + if (MM_GENERIC_CDMA_GET_PRIVATE (self)->has_spservice) { + /* Try Sprint-specific commands */ + mm_at_serial_port_queue_command (port, "+SPSERVICE?", 3, reg_query_spservice_done, info); } else { - /* Or if the subclass doesn't implement more specific checking, - * assume we're registered. + /* Assume we're registered on the 1x network if we passed +CAD, +CSS, + * and QCDM Call Manager checking. */ - reg_state_query_done (MM_MODEM_CDMA (self), - MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED, - MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED, - NULL, - info); + mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED); + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + mm_callback_info_schedule (info); } } @@ -1282,8 +1572,7 @@ reg_state_css_response (MMModemCdma *cdma, * report unknown registration state. */ if (error) { - if ( (error->domain == MM_MOBILE_ERROR) - && (error->code == MM_MOBILE_ERROR_NO_NETWORK)) { + if (g_error_matches (error, MM_MOBILE_ERROR, MM_MOBILE_ERROR_NO_NETWORK)) { set_callback_1x_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); set_callback_evdo_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); } else { @@ -1291,14 +1580,20 @@ reg_state_css_response (MMModemCdma *cdma, info->error = g_error_copy (error); } mm_callback_info_schedule (info); - return; + } else { + /* We're registered on the CDMA 1x network at least, but let subclasses + * do more specific registration checking. + */ + MM_GENERIC_CDMA_GET_CLASS (cdma)->query_registration_state (MM_GENERIC_CDMA (info->modem), + MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED, + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN, + subclass_reg_query_done, + info); } - - query_subclass_registration_state (MM_GENERIC_CDMA (info->modem), info); } static void -get_analog_digital_done (MMSerialPort *port, +get_analog_digital_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1313,7 +1608,7 @@ get_analog_digital_done (MMSerialPort *port, } /* Strip any leading command tag and spaces */ - reply = strip_response (response->str, "+CAD:"); + reply = mm_strip_tag (response->str, "+CAD:"); errno = 0; int_cad = strtol (reply, NULL, 10); @@ -1345,7 +1640,11 @@ get_analog_digital_done (MMSerialPort *port, /* Subclass knows that AT+CSS? will respond incorrectly to EVDO * state, so skip AT+CSS? query. */ - query_subclass_registration_state (MM_GENERIC_CDMA (info->modem), info); + MM_GENERIC_CDMA_GET_CLASS (info->modem)->query_registration_state (MM_GENERIC_CDMA (info->modem), + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN, + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN, + subclass_reg_query_done, + info); } return; } else { @@ -1359,28 +1658,113 @@ error: } static void +reg_cmstate_cb (MMQcdmSerialPort *port, + GByteArray *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMAtSerialPort *at_port; + QCDMResult *result; + guint32 opmode = 0, sysmode = 0; + MMModemCdmaRegistrationState cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + + if (error) + goto error; + + /* Parse the response */ + result = qcdm_cmd_cm_subsys_state_info_result ((const char *) response->data, response->len, &info->error); + if (!result) + goto error; + + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE, &opmode); + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE, &sysmode); + qcdm_result_unref (result); + + if (opmode == QCDM_CMD_CM_SUBSYS_STATE_INFO_OPERATING_MODE_ONLINE) { + switch (sysmode) { + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_CDMA: + cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + break; + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_HDR: + evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + break; + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_AMPS: + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_NO_SERVICE: + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_WCDMA: + default: + break; + } + + if (cdma_state || evdo_state) { + /* Device is registered to something; see if the subclass has a + * better idea of whether we're roaming or not and what the + * access technology is. + */ + if (MM_GENERIC_CDMA_GET_CLASS (info->modem)->query_registration_state) { + MM_GENERIC_CDMA_GET_CLASS (info->modem)->query_registration_state (MM_GENERIC_CDMA (info->modem), + cdma_state, + evdo_state, + subclass_reg_query_done, + info); + return; + } + } + } + + set_callback_1x_state_helper (info, cdma_state); + set_callback_evdo_state_helper (info, evdo_state); + mm_callback_info_schedule (info); + return; + +error: + /* If there was some error, fall back to use +CAD like we did before QCDM */ + at_port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (info->modem), &info->error); + if (at_port) + mm_at_serial_port_queue_command (at_port, "+CAD?", 3, get_analog_digital_done, info); + else + mm_callback_info_schedule (info); +} + +static void get_registration_state (MMModemCdma *modem, MMModemCdmaRegistrationStateFn callback, gpointer user_data) { MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); MMCallbackInfo *info; - MMSerialPort *port = priv->primary; - - if (mm_port_get_connected (MM_PORT (priv->primary))) { - if (!priv->secondary) { - g_message ("Returning saved registration states: 1x: %d EVDO: %d", - priv->cdma_1x_reg_state, priv->evdo_reg_state); - callback (MM_MODEM_CDMA (modem), priv->cdma_1x_reg_state, priv->evdo_reg_state, NULL, user_data); - return; - } - - /* Use secondary port if primary is connected */ - port = priv->secondary; + MMAtSerialPort *port; + + info = mm_generic_cdma_query_reg_state_callback_info_new (MM_GENERIC_CDMA (modem), + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN, + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN, + callback, + user_data); + + port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &info->error); + if (!port && !priv->qcdm) { + g_message ("Returning saved registration states: 1x: %d EVDO: %d", + priv->cdma_1x_reg_state, priv->evdo_reg_state); + mm_generic_cdma_query_reg_state_set_callback_1x_state (info, priv->cdma_1x_reg_state); + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, priv->evdo_reg_state); + mm_callback_info_schedule (info); + return; } - info = mm_generic_cdma_query_reg_state_callback_info_new (MM_GENERIC_CDMA (modem), callback, user_data); - mm_serial_port_queue_command (port, "+CAD?", 3, get_analog_digital_done, info); + /* Use QCDM for Call Manager state or HDR state before trying CAD, since + * CAD doesn't always reflect the state of the HDR radio's registration + * status. + */ + if (priv->qcdm) { + GByteArray *cmstate; + + cmstate = g_byte_array_sized_new (25); + cmstate->len = qcdm_cmd_cm_subsys_state_info_new ((char *) cmstate->data, 25, NULL); + g_assert (cmstate->len); + mm_qcdm_serial_port_queue_command (priv->qcdm, cmstate, 3, reg_cmstate_cb, info); + } else + mm_at_serial_port_queue_command (port, "+CAD?", 3, get_analog_digital_done, info); } /*****************************************************************************/ @@ -1512,10 +1896,9 @@ simple_state_machine (MMModem *modem, GError *error, gpointer user_data) const char *str; guint id; - if (error) { - info->error = g_error_copy (error); + info->error = mm_modem_check_removed (modem, error); + if (info->error) goto out; - } switch (state) { case SIMPLE_STATE_BEGIN: @@ -1550,7 +1933,8 @@ simple_state_machine (MMModem *modem, GError *error, gpointer user_data) out: if (info->error || state == SIMPLE_STATE_DONE) { - registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0); + if (modem) + registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0); mm_callback_info_schedule (info); } } @@ -1609,16 +1993,26 @@ simple_uint_value (guint32 i) return val; } +#define SS_HASH_TAG "simple-get-status" + static void simple_status_got_signal_quality (MMModem *modem, guint32 result, GError *error, gpointer user_data) { - if (error) + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GHashTable *properties; + + if (error) { + info->error = g_error_copy (error); g_warning ("Error getting signal quality: %s", error->message); - else - g_hash_table_insert ((GHashTable *) user_data, "signal_quality", simple_uint_value (result)); + } else { + properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG); + g_hash_table_insert (properties, "signal_quality", simple_uint_value (result)); + } + + mm_callback_info_schedule (info); } static void @@ -1627,7 +2021,7 @@ simple_get_status_invoke (MMCallbackInfo *info) MMModemSimpleGetStatusFn callback = (MMModemSimpleGetStatusFn) info->callback; callback (MM_MODEM_SIMPLE (info->modem), - (GHashTable *) mm_callback_info_get_data (info, "simple-get-status"), + (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG), info->error, info->user_data); } @@ -1644,9 +2038,9 @@ simple_get_status (MMModemSimple *simple, G_CALLBACK (callback), user_data); - properties = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, simple_free_gvalue); - mm_callback_info_set_data (info, "simple-get-status", properties, (GDestroyNotify) g_hash_table_unref); - mm_modem_cdma_get_signal_quality (MM_MODEM_CDMA (simple), simple_status_got_signal_quality, properties); + properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, simple_free_gvalue); + mm_callback_info_set_data (info, SS_HASH_TAG, properties, (GDestroyNotify) g_hash_table_unref); + mm_modem_cdma_get_signal_quality (MM_MODEM_CDMA (simple), simple_status_got_signal_quality, info); } /*****************************************************************************/ @@ -1659,6 +2053,28 @@ modem_valid_changed (MMGenericCdma *self, GParamSpec *pspec, gpointer user_data) registration_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL); } +static void +modem_state_changed (MMGenericCdma *self, GParamSpec *pspec, gpointer user_data) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + MMModemState state; + + /* Start polling registration status and signal quality when enabled */ + + state = mm_modem_get_state (MM_MODEM (self)); + if (state >= MM_MODEM_STATE_ENABLED) { + if (!priv->poll_id) { + priv->poll_id = g_timeout_add_seconds (30, periodic_poll_cb, self); + /* Kick one off immediately */ + periodic_poll_cb (self); + } + } else { + if (priv->poll_id) + g_source_remove (priv->poll_id); + priv->poll_id = 0; + } +} + /*****************************************************************************/ static void @@ -1690,27 +2106,13 @@ modem_simple_init (MMModemSimple *class) class->get_status = simple_get_status; } -static GObject* -constructor (GType type, - guint n_construct_params, - GObjectConstructParam *construct_params) -{ - GObject *object; - - object = G_OBJECT_CLASS (mm_generic_cdma_parent_class)->constructor (type, - n_construct_params, - construct_params); - if (object) { - g_signal_connect (object, "notify::" MM_MODEM_VALID, - G_CALLBACK (modem_valid_changed), NULL); - } - - return object; -} - static void mm_generic_cdma_init (MMGenericCdma *self) { + g_signal_connect (self, "notify::" MM_MODEM_VALID, + G_CALLBACK (modem_valid_changed), NULL); + g_signal_connect (self, "notify::" MM_MODEM_STATE, + G_CALLBACK (modem_state_changed), NULL); } static void @@ -1771,15 +2173,15 @@ get_property (GObject *object, guint prop_id, static void dispose (GObject *object) { - registration_cleanup (MM_GENERIC_CDMA (object), MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL); + MMGenericCdma *self = MM_GENERIC_CDMA (object); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); - G_OBJECT_CLASS (mm_generic_cdma_parent_class)->dispose (object); -} + registration_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL); -static void -finalize (GObject *object) -{ - G_OBJECT_CLASS (mm_generic_cdma_parent_class)->finalize (object); + if (priv->poll_id) + g_source_remove (priv->poll_id); + + G_OBJECT_CLASS (mm_generic_cdma_parent_class)->dispose (object); } static void @@ -1794,8 +2196,7 @@ mm_generic_cdma_class_init (MMGenericCdmaClass *klass) object_class->set_property = set_property; object_class->get_property = get_property; object_class->dispose = dispose; - object_class->finalize = finalize; - object_class->constructor = constructor; + klass->query_registration_state = real_query_registration_state; /* Properties */ g_object_class_override_property (object_class, diff --git a/src/mm-generic-cdma.h b/src/mm-generic-cdma.h index 5b4a0b6..e4f9e57 100644 --- a/src/mm-generic-cdma.h +++ b/src/mm-generic-cdma.h @@ -20,7 +20,7 @@ #include "mm-modem.h" #include "mm-modem-base.h" #include "mm-modem-cdma.h" -#include "mm-serial-port.h" +#include "mm-at-serial-port.h" #include "mm-callback-info.h" #define MM_TYPE_GENERIC_CDMA (mm_generic_cdma_get_type ()) @@ -42,7 +42,27 @@ typedef struct { typedef struct { MMModemBaseClass parent; + /* Subclasses should implement this function if they can more accurately + * determine the registration state and/or roaming status than the base + * class can (by using manufacturer custom AT commands or whatever). + * The base class passes its detected registration state in the + * cur_cdma_state and cur_evdo_state arguments, which the subclass should + * override if necessary before passing to the callback. + * + * Subclasses can use the helper functions + * mm_generic_cdma_query_reg_state_callback_info_new(), + * mm_generic_cdma_query_reg_state_set_callback_1x_state(), and + * mm_generic_cdma_query_reg_state_set_callback_evdo_state() to create the + * MMCallbackInfo object and to set the registration state which is passed + * to the callback when the subclass' registration query completes. + * + * Subclasses should generally not return parsing or other non-critical + * errors to the callback since that fails the entire registration check, + * rendering the superclass' checks useless. + */ void (*query_registration_state) (MMGenericCdma *self, + MMModemCdmaRegistrationState cur_cdma_state, + MMModemCdmaRegistrationState cur_evdo_state, MMModemCdmaRegistrationStateFn callback, gpointer user_data); @@ -71,8 +91,6 @@ MMModem *mm_generic_cdma_new (const char *device, /* Private, for subclasses */ -#define MM_GENERIC_CDMA_PREV_STATE_TAG "prev-state" - MMPort * mm_generic_cdma_grab_port (MMGenericCdma *self, const char *subsys, const char *name, @@ -80,7 +98,10 @@ MMPort * mm_generic_cdma_grab_port (MMGenericCdma *self, gpointer user_data, GError **error); -MMSerialPort *mm_generic_cdma_get_port (MMGenericCdma *modem, MMPortType ptype); +MMAtSerialPort *mm_generic_cdma_get_at_port (MMGenericCdma *modem, MMPortType ptype); + +MMAtSerialPort *mm_generic_cdma_get_best_at_port (MMGenericCdma *modem, + GError **error); void mm_generic_cdma_update_cdma1x_quality (MMGenericCdma *self, guint32 quality); void mm_generic_cdma_update_evdo_quality (MMGenericCdma *self, guint32 quality); @@ -99,12 +120,18 @@ MMModemCdmaRegistrationState mm_generic_cdma_evdo_get_registration_state_sync (M /* query_registration_state class function helpers */ MMCallbackInfo *mm_generic_cdma_query_reg_state_callback_info_new (MMGenericCdma *self, + MMModemCdmaRegistrationState cur_cdma_state, + MMModemCdmaRegistrationState cur_evdo_state, MMModemCdmaRegistrationStateFn callback, gpointer user_data); +MMModemCdmaRegistrationState mm_generic_cdma_query_reg_state_get_callback_1x_state (MMCallbackInfo *info); + void mm_generic_cdma_query_reg_state_set_callback_1x_state (MMCallbackInfo *info, MMModemCdmaRegistrationState new_state); +MMModemCdmaRegistrationState mm_generic_cdma_query_reg_state_get_callback_evdo_state (MMCallbackInfo *info); + void mm_generic_cdma_query_reg_state_set_callback_evdo_state (MMCallbackInfo *info, MMModemCdmaRegistrationState new_state); diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c index 4954ca1..08cde10 100644 --- a/src/mm-generic-gsm.c +++ b/src/mm-generic-gsm.c @@ -12,13 +12,15 @@ * * Copyright (C) 2008 - 2009 Novell, Inc. * Copyright (C) 2009 - 2010 Red Hat, Inc. - * Copyright (C) 2009 Ericsson + * Copyright (C) 2009 - 2010 Ericsson */ +#include <config.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <ctype.h> + #include "mm-generic-gsm.h" #include "mm-modem-gsm-card.h" #include "mm-modem-gsm-network.h" @@ -26,8 +28,13 @@ #include "mm-modem-simple.h" #include "mm-errors.h" #include "mm-callback-info.h" +#include "mm-at-serial-port.h" +#include "mm-qcdm-serial-port.h" #include "mm-serial-parsers.h" #include "mm-modem-helpers.h" +#include "mm-options.h" +#include "mm-properties-changed-signal.h" +#include "mm-utils.h" static void modem_init (MMModem *modem_class); static void modem_gsm_card_init (MMModemGsmCard *gsm_card_class); @@ -50,39 +57,88 @@ typedef struct { char *device; gboolean valid; + gboolean pin_checked; + guint32 pin_check_tries; + guint pin_check_timeout; + + MMModemGsmAllowedMode allowed_mode; + + gboolean roam_allowed; char *oper_code; char *oper_name; guint32 ip_method; - gboolean unsolicited_registration; - MMModemGsmNetworkRegStatus reg_status; + GPtrArray *reg_regex; + + guint poll_id; + + /* CREG and CGREG info */ + gboolean creg_poll; + gboolean cgreg_poll; + /* Index 0 for CREG, index 1 for CGREG */ + gulong lac[2]; + gulong cell_id[2]; + MMModemGsmAccessTech act; + + /* Index 0 for CREG, index 1 for CGREG */ + MMModemGsmNetworkRegStatus reg_status[2]; guint pending_reg_id; MMCallbackInfo *pending_reg_info; + gboolean manual_reg; + guint signal_quality_id; + time_t signal_quality_timestamp; guint32 signal_quality; - guint32 cid; + gint cid; + + guint32 charsets; + guint32 cur_charset; - MMSerialPort *primary; - MMSerialPort *secondary; + MMAtSerialPort *primary; + MMAtSerialPort *secondary; + MMQcdmSerialPort *qcdm; MMPort *data; } MMGenericGsmPrivate; -static void get_registration_status (MMSerialPort *port, MMCallbackInfo *info); -static void read_operator_code_done (MMSerialPort *port, +static void get_registration_status (MMAtSerialPort *port, MMCallbackInfo *info); +static void read_operator_code_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data); -static void read_operator_name_done (MMSerialPort *port, +static void read_operator_name_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data); -static void reg_state_changed (MMSerialPort *port, +static void reg_state_changed (MMAtSerialPort *port, GMatchInfo *match_info, gpointer user_data); +static void get_reg_status_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data); + +static gboolean handle_reg_status_response (MMGenericGsm *self, + GString *response, + GError **error); + +static MMModemGsmAccessTech etsi_act_to_mm_act (gint act); + +static void _internal_update_access_technology (MMGenericGsm *modem, + MMModemGsmAccessTech act); + +static void reg_info_updated (MMGenericGsm *self, + gboolean update_rs, + MMGenericGsmRegType rs_type, + MMModemGsmNetworkRegStatus status, + gboolean update_code, + const char *oper_code, + gboolean update_name, + const char *oper_name); + MMModem * mm_generic_gsm_new (const char *device, const char *driver, @@ -99,24 +155,7 @@ mm_generic_gsm_new (const char *device, NULL)); } -void -mm_generic_gsm_set_unsolicited_registration (MMGenericGsm *modem, - gboolean enabled) -{ - g_return_if_fail (MM_IS_GENERIC_GSM (modem)); - - MM_GENERIC_GSM_GET_PRIVATE (modem)->unsolicited_registration = enabled; -} - -void -mm_generic_gsm_set_cid (MMGenericGsm *modem, guint32 cid) -{ - g_return_if_fail (MM_IS_GENERIC_GSM (modem)); - - MM_GENERIC_GSM_GET_PRIVATE (modem)->cid = cid; -} - -guint32 +gint mm_generic_gsm_get_cid (MMGenericGsm *modem) { g_return_val_if_fail (MM_IS_GENERIC_GSM (modem), 0); @@ -124,93 +163,98 @@ mm_generic_gsm_get_cid (MMGenericGsm *modem) return MM_GENERIC_GSM_GET_PRIVATE (modem)->cid; } -static void -got_signal_quality (MMModem *modem, - guint32 result, - GError *error, - gpointer user_data) -{ -} - -void -mm_generic_gsm_set_reg_status (MMGenericGsm *modem, - MMModemGsmNetworkRegStatus status) -{ - MMGenericGsmPrivate *priv; - - g_return_if_fail (MM_IS_GENERIC_GSM (modem)); - - priv = MM_GENERIC_GSM_GET_PRIVATE (modem); - - if (priv->reg_status != status) { - priv->reg_status = status; - - g_debug ("Registration state changed: %d", status); - - if (status == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME || - status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) { - mm_serial_port_queue_command (priv->primary, "+COPS=3,2;+COPS?", 3, read_operator_code_done, modem); - mm_serial_port_queue_command (priv->primary, "+COPS=3,0;+COPS?", 3, read_operator_name_done, modem); - mm_modem_gsm_network_get_signal_quality (MM_MODEM_GSM_NETWORK (modem), got_signal_quality, NULL); - } else { - g_free (priv->oper_code); - g_free (priv->oper_name); - priv->oper_code = priv->oper_name = NULL; +typedef struct { + const char *result; + const char *normalized; + guint code; +} CPinResult; - mm_modem_gsm_network_registration_info (MM_MODEM_GSM_NETWORK (modem), priv->reg_status, - priv->oper_code, priv->oper_name); - } +static CPinResult unlock_results[] = { + /* Longer entries first so we catch the correct one with strcmp() */ + { "PH-NETSUB PIN", "ph-netsub-pin", MM_MOBILE_ERROR_NETWORK_SUBSET_PIN }, + { "PH-NETSUB PUK", "ph-netsub-puk", MM_MOBILE_ERROR_NETWORK_SUBSET_PUK }, + { "PH-FSIM PIN", "ph-fsim-pin", MM_MOBILE_ERROR_PH_FSIM_PIN }, + { "PH-FSIM PUK", "ph-fsim-puk", MM_MOBILE_ERROR_PH_FSIM_PUK }, + { "PH-CORP PIN", "ph-corp-pin", MM_MOBILE_ERROR_CORP_PIN }, + { "PH-CORP PUK", "ph-corp-puk", MM_MOBILE_ERROR_CORP_PUK }, + { "PH-SIM PIN", "ph-sim-pin", MM_MOBILE_ERROR_PH_SIM_PIN }, + { "PH-NET PIN", "ph-net-pin", MM_MOBILE_ERROR_NETWORK_PIN }, + { "PH-NET PUK", "ph-net-puk", MM_MOBILE_ERROR_NETWORK_PUK }, + { "PH-SP PIN", "ph-sp-pin", MM_MOBILE_ERROR_SERVICE_PIN }, + { "PH-SP PUK", "ph-sp-puk", MM_MOBILE_ERROR_SERVICE_PUK }, + { "SIM PIN2", "sim-pin2", MM_MOBILE_ERROR_SIM_PIN2 }, + { "SIM PUK2", "sim-puk2", MM_MOBILE_ERROR_SIM_PUK2 }, + { "SIM PIN", "sim-pin", MM_MOBILE_ERROR_SIM_PIN }, + { "SIM PUK", "sim-puk", MM_MOBILE_ERROR_SIM_PUK }, + { NULL, NULL, MM_MOBILE_ERROR_PHONE_FAILURE }, +}; + +static GError * +error_for_unlock_required (const char *unlock) +{ + CPinResult *iter = &unlock_results[0]; + + if (!unlock || !strlen (unlock)) + return NULL; - mm_generic_gsm_update_enabled_state (modem, TRUE, MM_MODEM_STATE_REASON_NONE); + /* Translate the error */ + while (iter->result) { + if (!strcmp (iter->normalized, unlock)) + return mm_mobile_error_for_code (iter->code); + iter++; } + + return g_error_new (MM_MOBILE_ERROR, + MM_MOBILE_ERROR_UNKNOWN, + "Unknown unlock request '%s'", unlock); } -typedef struct { - const char *result; - guint code; -} CPinResult; +static void +get_unlock_retries_cb (MMModem *modem, + guint32 result, + GError *error, + gpointer user_data) +{ + if (!error) + mm_modem_base_set_unlock_retries (MM_MODEM_BASE (modem), result); + else + mm_modem_base_set_unlock_retries (MM_MODEM_BASE (modem), MM_MODEM_GSM_CARD_UNLOCK_RETRIES_NOT_SUPPORTED); +} static void -pin_check_done (MMSerialPort *port, +pin_check_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; gboolean parsed = FALSE; - static CPinResult results[] = { - { "SIM PIN", MM_MOBILE_ERROR_SIM_PIN }, - { "SIM PUK", MM_MOBILE_ERROR_SIM_PUK }, - { "PH-SIM PIN", MM_MOBILE_ERROR_PH_SIM_PIN }, - { "PH-FSIM PIN", MM_MOBILE_ERROR_PH_FSIM_PIN }, - { "PH-FSIM PUK", MM_MOBILE_ERROR_PH_FSIM_PUK }, - { "SIM PIN2", MM_MOBILE_ERROR_SIM_PIN2 }, - { "SIM PUK2", MM_MOBILE_ERROR_SIM_PUK2 }, - { "PH-NET PIN", MM_MOBILE_ERROR_NETWORK_PIN }, - { "PH-NET PUK", MM_MOBILE_ERROR_NETWORK_PUK }, - { "PH-NETSUB PIN", MM_MOBILE_ERROR_NETWORK_SUBSET_PIN }, - { "PH-NETSUB PUK", MM_MOBILE_ERROR_NETWORK_SUBSET_PUK }, - { "PH-SP PIN", MM_MOBILE_ERROR_SERVICE_PIN }, - { "PH-SP PUK", MM_MOBILE_ERROR_SERVICE_PUK }, - { "PH-CORP PIN", MM_MOBILE_ERROR_CORP_PIN }, - { "PH-CORP PUK", MM_MOBILE_ERROR_CORP_PUK }, - { NULL, MM_MOBILE_ERROR_PHONE_FAILURE }, - }; if (error) info->error = g_error_copy (error); - else if (g_str_has_prefix (response->str, "+CPIN: ")) { - const char *str = response->str + 7; + else if (response && strstr (response->str, "+CPIN: ")) { + const char *str = strstr (response->str, "+CPIN: ") + 7; - if (g_str_has_prefix (str, "READY")) + if (g_str_has_prefix (str, "READY")) { + mm_modem_base_set_unlock_required (MM_MODEM_BASE (info->modem), NULL); + if (MM_MODEM_GSM_CARD_GET_INTERFACE (info->modem)->get_unlock_retries) + mm_modem_base_set_unlock_retries (MM_MODEM_BASE (info->modem), 0); + else + mm_modem_base_set_unlock_retries (MM_MODEM_BASE (info->modem), + MM_MODEM_GSM_CARD_UNLOCK_RETRIES_NOT_SUPPORTED); parsed = TRUE; - else { - CPinResult *iter = &results[0]; + } else { + CPinResult *iter = &unlock_results[0]; /* Translate the error */ while (iter->result) { if (g_str_has_prefix (str, iter->result)) { info->error = mm_mobile_error_for_code (iter->code); + mm_modem_base_set_unlock_required (MM_MODEM_BASE (info->modem), iter->normalized); + mm_modem_gsm_card_get_unlock_retries (MM_MODEM_GSM_CARD (info->modem), + iter->normalized, + get_unlock_retries_cb, + NULL); parsed = TRUE; break; } @@ -219,20 +263,26 @@ pin_check_done (MMSerialPort *port, } } - if (!info->error && !parsed) { - info->error = g_error_new (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "Could not parse PIN request response '%s'", - response->str); + if (!parsed) { + /* Assume unlocked if we don't recognize the pin request result */ + mm_modem_base_set_unlock_required (MM_MODEM_BASE (info->modem), NULL); + mm_modem_base_set_unlock_retries (MM_MODEM_BASE (info->modem), 0); + + if (!info->error) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Could not parse PIN request response '%s'", + response->str); + } } mm_callback_info_schedule (info); } -void -mm_generic_gsm_check_pin (MMGenericGsm *modem, - MMModemFn callback, - gpointer user_data) +static void +check_pin (MMGenericGsm *modem, + MMModemFn callback, + gpointer user_data) { MMGenericGsmPrivate *priv; MMCallbackInfo *info; @@ -241,25 +291,66 @@ mm_generic_gsm_check_pin (MMGenericGsm *modem, priv = MM_GENERIC_GSM_GET_PRIVATE (modem); info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_queue_command (priv->primary, "+CPIN?", 3, pin_check_done, info); + mm_at_serial_port_queue_command (priv->primary, "+CPIN?", 3, pin_check_done, info); +} + +static void +get_imei_cb (MMModem *modem, + const char *result, + GError *error, + gpointer user_data) +{ + if (modem) { + mm_modem_base_set_equipment_identifier (MM_MODEM_BASE (modem), error ? "" : result); + mm_serial_port_close (MM_SERIAL_PORT (MM_GENERIC_GSM_GET_PRIVATE (modem)->primary)); + } } /*****************************************************************************/ +static MMModemGsmNetworkRegStatus +gsm_reg_status (MMGenericGsm *self) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + /* Some devices (Blackberries for example) will respond to +CGREG, but + * return ERROR for +CREG, probably because their firmware is just stupid. + * So here we prefer the +CREG response, but if we never got a successful + * +CREG response, we'll take +CGREG instead. + */ + + if ( priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME + || priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) + return priv->reg_status[0]; + + if ( priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME + || priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) + return priv->reg_status[1]; + + if (priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING) + return priv->reg_status[0]; + + if (priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING) + return priv->reg_status[1]; + + if (priv->reg_status[0] != MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN) + return priv->reg_status[0]; + + return priv->reg_status[1]; +} + void mm_generic_gsm_update_enabled_state (MMGenericGsm *self, gboolean stay_connected, MMModemStateReason reason) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); - /* While connected we don't want registration status changes to change * the modem's state away from CONNECTED. */ if (stay_connected && (mm_modem_get_state (MM_MODEM (self)) >= MM_MODEM_STATE_DISCONNECTING)) return; - switch (priv->reg_status) { + switch (gsm_reg_status (self)) { case MM_MODEM_GSM_NETWORK_REG_STATUS_HOME: case MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING: mm_modem_set_state (MM_MODEM (self), MM_MODEM_STATE_REGISTERED, reason); @@ -282,12 +373,109 @@ check_valid (MMGenericGsm *self) MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); gboolean new_valid = FALSE; - if (priv->primary && priv->data) + if (priv->primary && priv->data && priv->pin_checked) new_valid = TRUE; mm_modem_base_set_valid (MM_MODEM_BASE (self), new_valid); } + +static void initial_pin_check_done (MMModem *modem, GError *error, gpointer user_data); + +static gboolean +pin_check_again (gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + priv->pin_check_timeout = 0; + check_pin (self, initial_pin_check_done, NULL); + return FALSE; +} + +static void +initial_pin_check_done (MMModem *modem, GError *error, gpointer user_data) +{ + MMGenericGsmPrivate *priv; + + /* modem could have been removed before we get here, in which case + * 'modem' will be NULL. + */ + if (!modem) + return; + + g_return_if_fail (MM_IS_GENERIC_GSM (modem)); + priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + + if ( error + && priv->pin_check_tries++ < 3 + && !mm_modem_base_get_unlock_required (MM_MODEM_BASE (modem))) { + /* Try it again a few times */ + if (priv->pin_check_timeout) + g_source_remove (priv->pin_check_timeout); + priv->pin_check_timeout = g_timeout_add_seconds (2, pin_check_again, modem); + } else { + priv->pin_checked = TRUE; + mm_serial_port_close (MM_SERIAL_PORT (priv->primary)); + check_valid (MM_GENERIC_GSM (modem)); + } +} + +static void +initial_pin_check (MMGenericGsm *self) +{ + GError *error = NULL; + MMGenericGsmPrivate *priv; + + g_return_if_fail (MM_IS_GENERIC_GSM (self)); + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + g_return_if_fail (priv->primary != NULL); + + if (mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &error)) + check_pin (self, initial_pin_check_done, NULL); + else { + g_warning ("%s: failed to open serial port: (%d) %s", + __func__, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + + /* Ensure the modem is still somewhat usable if opening the serial + * port fails for some reason. + */ + initial_pin_check_done (MM_MODEM (self), NULL, NULL); + } +} + +static void +initial_imei_check (MMGenericGsm *self) +{ + GError *error = NULL; + MMGenericGsmPrivate *priv; + + g_return_if_fail (MM_IS_GENERIC_GSM (self)); + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + g_return_if_fail (priv->primary != NULL); + + if (mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &error)) { + /* Make sure echoing is off */ + mm_at_serial_port_queue_command (priv->primary, "E0", 3, NULL, NULL); + + /* Get modem's imei number */ + mm_modem_gsm_card_get_imei (MM_MODEM_GSM_CARD (self), + get_imei_cb, + NULL); + } else { + g_warning ("%s: failed to open serial port: (%d) %s", + __func__, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + } +} + static gboolean owns_port (MMModem *modem, const char *subsys, const char *name) { @@ -308,28 +496,50 @@ mm_generic_gsm_grab_port (MMGenericGsm *self, g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), FALSE); port = mm_modem_base_add_port (MM_MODEM_BASE (self), subsys, name, ptype); - if (port && MM_IS_SERIAL_PORT (port)) { - mm_serial_port_set_response_parser (MM_SERIAL_PORT (port), - mm_serial_parser_v1_parse, - mm_serial_parser_v1_new (), - mm_serial_parser_v1_destroy); + if (!port) { + g_warn_if_fail (port != NULL); + return NULL; + } + + if (MM_IS_AT_SERIAL_PORT (port)) { + GPtrArray *array; + int i; + + mm_at_serial_port_set_response_parser (MM_AT_SERIAL_PORT (port), + mm_serial_parser_v1_parse, + mm_serial_parser_v1_new (), + mm_serial_parser_v1_destroy); - regex = g_regex_new ("\\r\\n\\+CREG: (\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, reg_state_changed, self, NULL); - g_regex_unref (regex); + /* Set up CREG unsolicited message handlers */ + array = mm_gsm_creg_regex_get (FALSE); + for (i = 0; i < array->len; i++) { + regex = g_ptr_array_index (array, i); + + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, reg_state_changed, self, NULL); + } + mm_gsm_creg_regex_destroy (array); if (ptype == MM_PORT_TYPE_PRIMARY) { - priv->primary = MM_SERIAL_PORT (port); + priv->primary = MM_AT_SERIAL_PORT (port); if (!priv->data) { priv->data = port; g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); } - check_valid (self); + + /* Get modem's initial lock/unlock state */ + initial_pin_check (self); + + /* Get modem's IMEI number */ + initial_imei_check (self); + } else if (ptype == MM_PORT_TYPE_SECONDARY) - priv->secondary = MM_SERIAL_PORT (port); - } else { + priv->secondary = MM_AT_SERIAL_PORT (port); + } else if (MM_IS_QCDM_SERIAL_PORT (port)) { + if (!priv->qcdm) + priv->qcdm = MM_QCDM_SERIAL_PORT (port); + } else if (!strcmp (subsys, "net")) { /* Net device (if any) is the preferred data port */ - if (!priv->data || MM_IS_SERIAL_PORT (priv->data)) { + if (!priv->data || MM_IS_AT_SERIAL_PORT (priv->data)) { priv->data = port; g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); check_valid (self); @@ -381,7 +591,7 @@ release_port (MMModem *modem, const char *subsys, const char *name) if (!port) return; - if (port == MM_PORT (priv->primary)) { + if (port == (MMPort *) priv->primary) { mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); priv->primary = NULL; } @@ -391,59 +601,375 @@ release_port (MMModem *modem, const char *subsys, const char *name) g_object_notify (G_OBJECT (modem), MM_MODEM_DATA_DEVICE); } - if (port == MM_PORT (priv->secondary)) { + if (port == (MMPort *) priv->secondary) { mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); priv->secondary = NULL; } + if (port == (MMPort *) priv->qcdm) { + mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); + priv->qcdm = NULL; + } + check_valid (MM_GENERIC_GSM (modem)); } +static void +reg_poll_response (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + + if (!error) + handle_reg_status_response (self, response, NULL); +} + +static void +periodic_signal_quality_cb (MMModem *modem, + guint32 result, + GError *error, + gpointer user_data) +{ + /* Cached signal quality already updated */ +} + +static void +periodic_access_tech_cb (MMModem *modem, + guint32 act, + GError *error, + gpointer user_data) +{ + if (modem && !error && act) + mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (modem), act); +} + +static gboolean +periodic_poll_cb (gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMAtSerialPort *port; + + port = mm_generic_gsm_get_best_at_port (self, NULL); + if (!port) + return TRUE; /* oh well, try later */ + + if (priv->creg_poll) + mm_at_serial_port_queue_command (port, "+CREG?", 10, reg_poll_response, self); + if (priv->cgreg_poll) + mm_at_serial_port_queue_command (port, "+CGREG?", 10, reg_poll_response, self); + + mm_modem_gsm_network_get_signal_quality (MM_MODEM_GSM_NETWORK (self), + periodic_signal_quality_cb, + NULL); + + if (MM_GENERIC_GSM_GET_CLASS (self)->get_access_technology) + MM_GENERIC_GSM_GET_CLASS (self)->get_access_technology (self, periodic_access_tech_cb, NULL); + + return TRUE; /* continue running */ +} + +static void +cgreg1_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->modem) { + if (info->error) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + g_clear_error (&info->error); + + /* The modem doesn't like unsolicited CGREG, so we'll need to poll */ + priv->cgreg_poll = TRUE; + } + /* Success; get initial state */ + mm_at_serial_port_queue_command (port, "+CGREG?", 10, reg_poll_response, info->modem); + } + mm_callback_info_schedule (info); +} + +static void +cgreg2_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + + /* Ignore errors except modem removal errors */ + info->error = mm_modem_check_removed (info->modem, error); + if (info->modem) { + if (info->error) { + g_clear_error (&info->error); + /* Try CGREG=1 instead */ + mm_at_serial_port_queue_command (port, "+CGREG=1", 3, cgreg1_done, info); + } else { + /* Success; get initial state */ + mm_at_serial_port_queue_command (port, "+CGREG?", 10, reg_poll_response, info->modem); + + /* All done */ + mm_callback_info_schedule (info); + } + } else { + /* Modem got removed */ + mm_callback_info_schedule (info); + } +} + +static void +creg1_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->modem) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + if (info->error) { + g_clear_error (&info->error); + + /* The modem doesn't like unsolicited CREG, so we'll need to poll */ + priv->creg_poll = TRUE; + } + /* Success; get initial state */ + mm_at_serial_port_queue_command (port, "+CREG?", 10, reg_poll_response, info->modem); + + /* Now try to set up CGREG messages */ + mm_at_serial_port_queue_command (port, "+CGREG=2", 3, cgreg2_done, info); + } else { + /* Modem got removed */ + mm_callback_info_schedule (info); + } +} + +static void +creg2_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + + /* Ignore errors except modem removal errors */ + info->error = mm_modem_check_removed (info->modem, error); + if (info->modem) { + if (info->error) { + g_clear_error (&info->error); + mm_at_serial_port_queue_command (port, "+CREG=1", 3, creg1_done, info); + } else { + /* Success; get initial state */ + mm_at_serial_port_queue_command (port, "+CREG?", 10, reg_poll_response, info->modem); + + /* Now try to set up CGREG messages */ + mm_at_serial_port_queue_command (port, "+CGREG=2", 3, cgreg2_done, info); + } + } else { + /* Modem got removed */ + mm_callback_info_schedule (info); + } +} + +static void +enable_failed (MMModem *modem, GError *error, MMCallbackInfo *info) +{ + MMGenericGsmPrivate *priv; + + info->error = mm_modem_check_removed (modem, error); + + if (modem) { + mm_modem_set_state (modem, + MM_MODEM_STATE_DISABLED, + MM_MODEM_STATE_REASON_NONE); + + priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + + if (priv->primary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->primary))) + mm_serial_port_close_force (MM_SERIAL_PORT (priv->primary)); + if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) + mm_serial_port_close_force (MM_SERIAL_PORT (priv->secondary)); + } + + mm_callback_info_schedule (info); +} + +static guint32 best_charsets[] = { + MM_MODEM_CHARSET_UTF8, + MM_MODEM_CHARSET_UCS2, + MM_MODEM_CHARSET_8859_1, + MM_MODEM_CHARSET_IRA, + MM_MODEM_CHARSET_UNKNOWN +}; + +static void +enabled_set_charset_done (MMModem *modem, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + guint idx; + + /* only modem removals are really a hard error */ + if (error) { + if (!modem) { + enable_failed (modem, error, info); + return; + } + + /* Try the next best charset */ + idx = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "best-charset")) + 1; + if (best_charsets[idx] == MM_MODEM_CHARSET_UNKNOWN) { + GError *tmp_error; + + /* No more character sets we can use */ + tmp_error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET, + "Failed to find a usable modem character set"); + enable_failed (modem, tmp_error, info); + g_error_free (tmp_error); + } else { + /* Send the new charset */ + mm_callback_info_set_data (info, "best-charset", GUINT_TO_POINTER (idx), NULL); + mm_modem_set_charset (modem, best_charsets[idx], enabled_set_charset_done, info); + } + } else { + /* Modem is now enabled; update the state */ + mm_generic_gsm_update_enabled_state (MM_GENERIC_GSM (modem), FALSE, MM_MODEM_STATE_REASON_NONE); + + /* Set up unsolicited registration notifications */ + mm_at_serial_port_queue_command (MM_GENERIC_GSM_GET_PRIVATE (modem)->primary, + "+CREG=2", 3, creg2_done, info); + } +} + +static void +supported_charsets_done (MMModem *modem, + guint32 charsets, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (!modem) { + enable_failed (modem, error, info); + return; + } + + /* Switch the device's charset; we prefer UTF-8, but UCS2 will do too */ + mm_modem_set_charset (modem, MM_MODEM_CHARSET_UTF8, enabled_set_charset_done, info); +} + +static void +get_allowed_mode_done (MMModem *modem, + MMModemGsmAllowedMode mode, + GError *error, + gpointer user_data) +{ + if (modem) { + mm_generic_gsm_update_allowed_mode (MM_GENERIC_GSM (modem), + error ? MM_MODEM_GSM_ALLOWED_MODE_ANY : mode); + } +} + +static void +get_enable_info_done (MMModem *modem, + const char *manufacturer, + const char *model, + const char *version, + GError *error, + gpointer user_data) +{ + /* Modem base class handles the response for us */ +} + void -mm_generic_gsm_enable_complete (MMGenericGsm *modem, +mm_generic_gsm_enable_complete (MMGenericGsm *self, GError *error, MMCallbackInfo *info) { - g_return_if_fail (modem != NULL); - g_return_if_fail (MM_IS_GENERIC_GSM (modem)); + MMGenericGsmPrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_GENERIC_GSM (self)); g_return_if_fail (info != NULL); + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + if (error) { - mm_modem_set_state (MM_MODEM (modem), - MM_MODEM_STATE_DISABLED, - MM_MODEM_STATE_REASON_NONE); + enable_failed ((MMModem *) self, error, info); + return; + } - info->error = g_error_copy (error); - } else { - /* Modem is enabled; update the state */ - mm_generic_gsm_update_enabled_state (modem, FALSE, MM_MODEM_STATE_REASON_NONE); + /* Open the second port here if the modem has one. We'll use it for + * signal strength and registration updates when the device is connected, + * but also many devices will send unsolicited registration or other + * messages to the secondary port but not the primary. + */ + if (priv->secondary) { + if (!mm_serial_port_open (MM_SERIAL_PORT (priv->secondary), &error)) { + if (mm_options_debug ()) { + g_warning ("%s: error opening secondary port: (%d) %s", + __func__, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + } + } } - mm_callback_info_schedule (info); + /* Try to enable XON/XOFF flow control */ + mm_at_serial_port_queue_command (priv->primary, "+IFC=1,1", 3, NULL, NULL); + + /* Grab device info right away */ + mm_modem_get_info (MM_MODEM (self), get_enable_info_done, NULL); + + /* Get allowed mode */ + if (MM_GENERIC_GSM_GET_CLASS (self)->get_allowed_mode) + MM_GENERIC_GSM_GET_CLASS (self)->get_allowed_mode (self, get_allowed_mode_done, NULL); + + /* And supported character sets */ + mm_modem_get_supported_charsets (MM_MODEM (self), supported_charsets_done, info); } static void -enable_done (MMSerialPort *port, +real_do_enable_power_up_done (MMGenericGsm *self, + GString *response, + GError *error, + MMCallbackInfo *info) +{ + /* Ignore power-up errors as not all devices actually support CFUN=1 */ + mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), NULL, info); +} + +static void +enable_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - /* Ignore power-up command errors, not all devices actually support - * CFUN=1. - */ - /* FIXME: instead of just ignoring errors, since we allow subclasses - * to override the power-on command, make a class function for powering - * on the phone and let the subclass decided whether it wants to handle - * errors or ignore them. + /* Let subclasses handle the power up command response/error; many devices + * don't support +CFUN, but for those that do let them handle the error + * correctly. */ - - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), NULL, info); + g_assert (MM_GENERIC_GSM_GET_CLASS (info->modem)->do_enable_power_up_done); + MM_GENERIC_GSM_GET_CLASS (info->modem)->do_enable_power_up_done (MM_GENERIC_GSM (info->modem), + response, + error, + info); } static void -init_done (MMSerialPort *port, +init_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -457,22 +983,22 @@ init_done (MMSerialPort *port, } /* Ensure echo is off after the init command; some modems ignore the - * E0 when it's in the same like as ATZ (Option GIO322). - */ - mm_serial_port_queue_command (port, "E0 +CMEE=1", 2, NULL, NULL); + * E0 when it's in the same line as ATZ (Option GIO322). + */ + mm_at_serial_port_queue_command (port, "E0", 2, NULL, NULL); + + /* Some phones (like Blackberries) don't support +CMEE=1, so make it + * optional. It completely violates 3GPP TS 27.007 (9.1) but what can we do... + */ + mm_at_serial_port_queue_command (port, "+CMEE=1", 2, NULL, NULL); g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_INIT_CMD_OPTIONAL, &cmd, NULL); - mm_serial_port_queue_command (port, cmd, 2, NULL, NULL); + mm_at_serial_port_queue_command (port, cmd, 2, NULL, NULL); g_free (cmd); - if (MM_GENERIC_GSM_GET_PRIVATE (info->modem)->unsolicited_registration) - mm_serial_port_queue_command (port, "+CREG=1", 5, NULL, NULL); - else - mm_serial_port_queue_command (port, "+CREG=0", 5, NULL, NULL); - g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_UP_CMD, &cmd, NULL); if (cmd && strlen (cmd)) - mm_serial_port_queue_command (port, cmd, 5, enable_done, user_data); + mm_at_serial_port_queue_command (port, cmd, 5, enable_done, user_data); else enable_done (port, NULL, NULL, user_data); g_free (cmd); @@ -490,7 +1016,7 @@ enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data) } g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_INIT_CMD, &cmd, NULL); - mm_serial_port_queue_command (port, cmd, 3, init_done, user_data); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), cmd, 3, init_done, user_data); g_free (cmd); } @@ -501,7 +1027,7 @@ real_do_enable (MMGenericGsm *self, MMModemFn callback, gpointer user_data) MMCallbackInfo *info; info = mm_callback_info_new (MM_MODEM (self), callback, user_data); - mm_serial_port_flash (priv->primary, 100, enable_flash_done, info); + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 100, FALSE, enable_flash_done, info); } static void @@ -511,11 +1037,25 @@ enable (MMModem *modem, { MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); GError *error = NULL; + const char *unlock; + + /* If the device needs a PIN, deal with that now, but we don't care + * about SIM-PIN2/SIM-PUK2 since the device is operational without it. + */ + unlock = mm_modem_base_get_unlock_required (MM_MODEM_BASE (modem)); + if (unlock && strcmp (unlock, "sim-puk2") && strcmp (unlock, "sim-pin2")) { + MMCallbackInfo *info; + + info = mm_callback_info_new (modem, callback, user_data); + info->error = error_for_unlock_required (unlock); + mm_callback_info_schedule (info); + return; + } /* First, reset the previously used CID */ - mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0); + priv->cid = -1; - if (!mm_serial_port_open (priv->primary, &error)) { + if (!mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &error)) { MMCallbackInfo *info; g_assert (error); @@ -532,7 +1072,7 @@ enable (MMModem *modem, } static void -disable_done (MMSerialPort *port, +disable_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -541,13 +1081,25 @@ disable_done (MMSerialPort *port, info->error = mm_modem_check_removed (info->modem, error); if (!info->error) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + MMGenericGsm *self = MM_GENERIC_GSM (info->modem); - mm_serial_port_close (port); + mm_serial_port_close_force (MM_SERIAL_PORT (port)); mm_modem_set_state (MM_MODEM (info->modem), MM_MODEM_STATE_DISABLED, MM_MODEM_STATE_REASON_NONE); - priv->reg_status = MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN; + + /* Clear out circuit-switched registration info... */ + reg_info_updated (self, + MM_GENERIC_GSM_REG_TYPE_CS, + TRUE, MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN, + TRUE, NULL, + TRUE, NULL); + /* ...and packet-switched registration info */ + reg_info_updated (self, + MM_GENERIC_GSM_REG_TYPE_PS, + TRUE, MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN, + TRUE, NULL, + TRUE, NULL); } mm_callback_info_schedule (info); } @@ -575,11 +1127,15 @@ disable_flash_done (MMSerialPort *port, return; } + /* Disable unsolicited messages */ + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "AT+CREG=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "AT+CGREG=0", 3, NULL, NULL); + g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_DOWN_CMD, &cmd, NULL); if (cmd && strlen (cmd)) - mm_serial_port_queue_command (port, cmd, 5, disable_done, user_data); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), cmd, 5, disable_done, user_data); else - disable_done (port, NULL, NULL, user_data); + disable_done (MM_AT_SERIAL_PORT (port), NULL, NULL, user_data); g_free (cmd); } @@ -588,14 +1144,42 @@ disable (MMModem *modem, MMModemFn callback, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); MMCallbackInfo *info; MMModemState state; /* First, reset the previously used CID and clean up registration */ - mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0); + g_warn_if_fail (priv->cid == -1); + priv->cid = -1; + mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem)); + if (priv->poll_id) { + g_source_remove (priv->poll_id); + priv->poll_id = 0; + } + + if (priv->signal_quality_id) { + g_source_remove (priv->signal_quality_id); + priv->signal_quality_id = 0; + } + + if (priv->pin_check_timeout) { + g_source_remove (priv->pin_check_timeout); + priv->pin_check_timeout = 0; + } + + priv->lac[0] = 0; + priv->lac[1] = 0; + priv->cell_id[0] = 0; + priv->cell_id[1] = 0; + _internal_update_access_technology (self, MM_MODEM_GSM_ACCESS_TECH_UNKNOWN); + + /* Close the secondary port if its open */ + if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) + mm_serial_port_close_force (MM_SERIAL_PORT (priv->secondary)); + info = mm_callback_info_new (modem, callback, user_data); /* Cache the previous state so we can reset it if the operation fails */ @@ -610,13 +1194,13 @@ disable (MMModem *modem, MM_MODEM_STATE_REASON_NONE); if (mm_port_get_connected (MM_PORT (priv->primary))) - mm_serial_port_flash (priv->primary, 1000, disable_flash_done, info); + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disable_flash_done, info); else - disable_flash_done (priv->primary, NULL, info); + disable_flash_done (MM_SERIAL_PORT (priv->primary), NULL, info); } static void -get_string_done (MMSerialPort *port, +get_string_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -632,6 +1216,110 @@ get_string_done (MMSerialPort *port, } static void +get_mnc_length_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + int sw1, sw2; + const char *imsi; + gboolean success = FALSE; + char hex[51]; + char *bin; + + if (error) { + info->error = g_error_copy (error); + goto done; + } + + memset (hex, 0, sizeof (hex)); + if (sscanf (response->str, "+CRSM:%d,%d,\"%50c\"", &sw1, &sw2, (char *) &hex) == 3) + success = TRUE; + else { + /* May not include quotes... */ + if (sscanf (response->str, "+CRSM:%d,%d,%50c", &sw1, &sw2, (char *) &hex) == 3) + success = TRUE; + } + + if (!success) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Could not parse the CRSM response"); + goto done; + } + + if ((sw1 == 0x90 && sw2 == 0x00) || (sw1 == 0x91) || (sw1 == 0x92) || (sw1 == 0x9f)) { + gsize buflen = 0; + guint32 mnc_len; + + /* Make sure the buffer is only hex characters */ + while (buflen < sizeof (hex) && hex[buflen]) { + if (!isxdigit (hex[buflen])) { + hex[buflen] = 0x0; + break; + } + buflen++; + } + + /* Convert hex string to binary */ + bin = utils_hexstr2bin (hex, &buflen); + if (!bin || buflen < 4) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "SIM returned malformed response '%s'", + hex); + goto done; + } + + /* MNC length is byte 4 of this SIM file */ + mnc_len = bin[3] & 0xFF; + if (mnc_len == 2 || mnc_len == 3) { + imsi = mm_callback_info_get_data (info, "imsi"); + mm_callback_info_set_result (info, g_strndup (imsi, 3 + mnc_len), g_free); + } else { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "SIM returned invalid MNC length %d (should be either 2 or 3)", + mnc_len); + } + } else { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "SIM failed to handle CRSM request (sw1 %d sw2 %d)", + sw1, sw2); + } + +done: + mm_callback_info_schedule (info); +} + +static void +get_operator_id_imsi_done (MMModem *modem, + const char *result, + GError *error, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) { + info->error = g_error_copy (error); + mm_callback_info_schedule (info); + return; + } + + mm_callback_info_set_data (info, "imsi", g_strdup (result), g_free); + + /* READ BINARY of EFad (Administrative Data) ETSI 51.011 section 10.3.18 */ + mm_at_serial_port_queue_command_cached (priv->primary, + "+CRSM=176,28589,0,0,4", + 3, + get_mnc_length_done, + info); +} + +static void get_imei (MMModemGsmCard *modem, MMModemStringFn callback, gpointer user_data) @@ -640,7 +1328,7 @@ get_imei (MMModemGsmCard *modem, MMCallbackInfo *info; info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_queue_command_cached (priv->primary, "+CGSN", 3, get_string_done, info); + mm_at_serial_port_queue_command_cached (priv->primary, "+CGSN", 3, get_string_done, info); } static void @@ -652,112 +1340,116 @@ get_imsi (MMModemGsmCard *modem, MMCallbackInfo *info; info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_queue_command_cached (priv->primary, "+CIMI", 3, get_string_done, info); + mm_at_serial_port_queue_command_cached (priv->primary, "+CIMI", 3, get_string_done, info); } static void -card_info_invoke (MMCallbackInfo *info) +get_operator_id (MMModemGsmCard *modem, + MMModemStringFn callback, + gpointer user_data) { - MMModemInfoFn callback = (MMModemInfoFn) info->callback; - - callback (info->modem, - (char *) mm_callback_info_get_data (info, "card-info-manufacturer"), - (char *) mm_callback_info_get_data (info, "card-info-model"), - (char *) mm_callback_info_get_data (info, "card-info-version"), - info->error, info->user_data); -} - -#define GMI_RESP_TAG "+CGMI:" -#define GMM_RESP_TAG "+CGMM:" -#define GMR_RESP_TAG "+CGMR:" + MMCallbackInfo *info; -static const char * -strip_tag (const char *str, const char *tag) -{ - /* Strip the response header, if any */ - if (strncmp (str, tag, strlen (tag)) == 0) - str += strlen (tag); - while (*str && isspace (*str)) - str++; - return str; + info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); + mm_modem_gsm_card_get_imsi (MM_MODEM_GSM_CARD (modem), + get_operator_id_imsi_done, + info); } static void -get_version_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +get_card_info (MMModem *modem, + MMModemInfoFn callback, + gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *resp = strip_tag (response->str, GMR_RESP_TAG); - - if (!error) - mm_callback_info_set_data (info, "card-info-version", g_strdup (resp), g_free); - else if (!info->error) - info->error = g_error_copy (error); + MMAtSerialPort *port; + GError *error = NULL; - mm_callback_info_schedule (info); + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &error); + mm_modem_base_get_card_info (MM_MODEM_BASE (modem), port, error, callback, user_data); + g_clear_error (&error); } +#define PIN_PORT_TAG "pin-port" + static void -get_model_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +pin_puk_recheck_done (MMModem *modem, GError *error, gpointer user_data); + +static gboolean +pin_puk_recheck_again (gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *resp = strip_tag (response->str, GMM_RESP_TAG); - if (!error) - mm_callback_info_set_data (info, "card-info-model", g_strdup (resp), g_free); - else if (!info->error) - info->error = g_error_copy (error); + MM_GENERIC_GSM_GET_PRIVATE (info->modem)->pin_check_timeout = 0; + check_pin (MM_GENERIC_GSM (info->modem), pin_puk_recheck_done, info); + return FALSE; } static void -get_manufacturer_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +pin_puk_recheck_done (MMModem *modem, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *resp = strip_tag (response->str, GMI_RESP_TAG); + MMSerialPort *port; - if (!error) - mm_callback_info_set_data (info, "card-info-manufacturer", g_strdup (resp), g_free); - else - info->error = g_error_copy (error); -} + /* Clear the pin check timeout to ensure that it won't ever get a + * stale MMCallbackInfo if the modem got removed. We'll reschedule it here + * anyway if needed. + */ + if (info->modem) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); -static void -get_card_info (MMModem *modem, - MMModemInfoFn callback, - gpointer user_data) -{ - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); - MMCallbackInfo *info; + if (priv->pin_check_timeout) + g_source_remove (priv->pin_check_timeout); + priv->pin_check_timeout = 0; + } - info = mm_callback_info_new_full (MM_MODEM (modem), - card_info_invoke, - G_CALLBACK (callback), - user_data); + /* modem could have been removed before we get here, in which case + * 'modem' will be NULL. + */ + info->error = mm_modem_check_removed (modem, error); + + /* If the modem wasn't removed, and the modem isn't ready yet, ask it for + * the current PIN status a few times since some devices take a bit to fully + * enable themselves after a SIM PIN/PUK unlock. + */ + if ( info->modem + && info->error + && !g_error_matches (info->error, MM_MODEM_ERROR, MM_MODEM_ERROR_REMOVED)) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); - mm_serial_port_queue_command_cached (priv->primary, "+CGMI", 3, get_manufacturer_done, info); - mm_serial_port_queue_command_cached (priv->primary, "+CGMM", 3, get_model_done, info); - mm_serial_port_queue_command_cached (priv->primary, "+CGMR", 3, get_version_done, info); + if (priv->pin_check_tries < 4) { + g_clear_error (&info->error); + priv->pin_check_tries++; + priv->pin_check_timeout = g_timeout_add_seconds (2, pin_puk_recheck_again, info); + return; + } + } + + /* Otherwise, clean up and return the PIN check result */ + port = mm_callback_info_get_data (info, PIN_PORT_TAG); + if (modem && port) + mm_serial_port_close (port); + + mm_callback_info_schedule (info); } static void -send_puk_done (MMSerialPort *port, +send_puk_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - if (error) + if (error) { info->error = g_error_copy (error); - mm_callback_info_schedule (info); + mm_callback_info_schedule (info); + mm_serial_port_close (MM_SERIAL_PORT (port)); + return; + } + + /* Get latest PIN status */ + MM_GENERIC_GSM_GET_PRIVATE (info->modem)->pin_check_tries = 0; + check_pin (MM_GENERIC_GSM (info->modem), pin_puk_recheck_done, info); } static void @@ -767,27 +1459,52 @@ send_puk (MMModemGsmCard *modem, MMModemFn callback, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); MMCallbackInfo *info; char *command; + MMAtSerialPort *port; info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + + /* Ensure we have a usable port to use for the unlock */ + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + /* Modem may not be enabled yet, which sometimes can't be done until + * the device has been unlocked. In this case we have to open the port + * ourselves. + */ + if (!mm_serial_port_open (MM_SERIAL_PORT (port), &info->error)) { + mm_callback_info_schedule (info); + return; + } + mm_callback_info_set_data (info, PIN_PORT_TAG, port, NULL); + command = g_strdup_printf ("+CPIN=\"%s\",\"%s\"", puk, pin); - mm_serial_port_queue_command (priv->primary, command, 3, send_puk_done, info); + mm_at_serial_port_queue_command (port, command, 3, send_puk_done, info); g_free (command); } static void -send_pin_done (MMSerialPort *port, +send_pin_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - if (error) + if (error) { info->error = g_error_copy (error); - mm_callback_info_schedule (info); + mm_callback_info_schedule (info); + mm_serial_port_close (MM_SERIAL_PORT (port)); + return; + } + + /* Get latest PIN status */ + MM_GENERIC_GSM_GET_PRIVATE (info->modem)->pin_check_tries = 0; + check_pin (MM_GENERIC_GSM (info->modem), pin_puk_recheck_done, info); } static void @@ -796,18 +1513,36 @@ send_pin (MMModemGsmCard *modem, MMModemFn callback, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); MMCallbackInfo *info; char *command; + MMAtSerialPort *port; info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + + /* Ensure we have a usable port to use for the unlock */ + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + /* Modem may not be enabled yet, which sometimes can't be done until + * the device has been unlocked. In this case we have to open the port + * ourselves. + */ + if (!mm_serial_port_open (MM_SERIAL_PORT (port), &info->error)) { + mm_callback_info_schedule (info); + return; + } + mm_callback_info_set_data (info, PIN_PORT_TAG, port, NULL); + command = g_strdup_printf ("+CPIN=\"%s\"", pin); - mm_serial_port_queue_command (priv->primary, command, 3, send_pin_done, info); + mm_at_serial_port_queue_command (port, command, 3, send_pin_done, info); g_free (command); } static void -enable_pin_done (MMSerialPort *port, +enable_pin_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -832,12 +1567,12 @@ enable_pin (MMModemGsmCard *modem, info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); command = g_strdup_printf ("+CLCK=\"SC\",%d,\"%s\"", enabled ? 1 : 0, pin); - mm_serial_port_queue_command (priv->primary, command, 3, enable_pin_done, info); + mm_at_serial_port_queue_command (priv->primary, command, 3, enable_pin_done, info); g_free (command); } static void -change_pin_done (MMSerialPort *port, +change_pin_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -862,12 +1597,104 @@ change_pin (MMModemGsmCard *modem, info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); command = g_strdup_printf ("+CPWD=\"SC\",\"%s\",\"%s\"", old_pin, new_pin); - mm_serial_port_queue_command (priv->primary, command, 3, change_pin_done, info); + mm_at_serial_port_queue_command (priv->primary, command, 3, change_pin_done, info); g_free (command); } +static void +get_unlock_retries (MMModemGsmCard *modem, + const char *pin_type, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); + + mm_callback_info_set_result (info, + GUINT_TO_POINTER (MM_MODEM_GSM_CARD_UNLOCK_RETRIES_NOT_SUPPORTED), + NULL); + + mm_callback_info_schedule (info); +} + +static void +reg_info_updated (MMGenericGsm *self, + gboolean update_rs, + MMGenericGsmRegType rs_type, + MMModemGsmNetworkRegStatus status, + gboolean update_code, + const char *oper_code, + gboolean update_name, + const char *oper_name) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMModemGsmNetworkRegStatus old_status; + gboolean changed = FALSE; + + if (update_rs) { + g_return_if_fail ( rs_type == MM_GENERIC_GSM_REG_TYPE_CS + || rs_type == MM_GENERIC_GSM_REG_TYPE_PS); + + old_status = gsm_reg_status (self); + priv->reg_status[rs_type - 1] = status; + if (gsm_reg_status (self) != old_status) + changed = TRUE; + } + + if (update_code) { + if (g_strcmp0 (oper_code, priv->oper_code) != 0) { + g_free (priv->oper_code); + priv->oper_code = g_strdup (oper_code); + changed = TRUE; + } + } + + if (update_name) { + if (g_strcmp0 (oper_name, priv->oper_name) != 0) { + g_free (priv->oper_name); + priv->oper_name = g_strdup (oper_name); + changed = TRUE; + } + } + + if (changed) { + mm_modem_gsm_network_registration_info (MM_MODEM_GSM_NETWORK (self), + gsm_reg_status (self), + priv->oper_code, + priv->oper_name); + } +} + +static void +convert_operator_from_ucs2 (char **operator) +{ + const char *p; + char *converted; + size_t len; + + g_return_if_fail (operator != NULL); + g_return_if_fail (*operator != NULL); + + p = *operator; + len = strlen (p); + + /* Len needs to be a multiple of 4 for UCS2 */ + if ((len < 4) || ((len % 4) != 0)) + return; + + while (*p) { + if (!isxdigit (*p++)) + return; + } + + converted = mm_modem_charset_hex_to_utf8 (*operator, MM_MODEM_CHARSET_UCS2); + if (converted) { + g_free (*operator); + *operator = converted; + } +} + static char * -parse_operator (const char *reply) +parse_operator (const char *reply, MMModemCharset cur_charset) { char *operator = NULL; @@ -889,52 +1716,53 @@ parse_operator (const char *reply) g_regex_unref (r); } + /* Some modems (Option & HSO) return the operator name as a hexadecimal + * string of the bytes of the operator name as encoded by the current + * character set. + */ + if (operator && (cur_charset == MM_MODEM_CHARSET_UCS2)) + convert_operator_from_ucs2 (&operator); + return operator; } static void -read_operator_code_done (MMSerialPort *port, +read_operator_code_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (user_data); + MMGenericGsm *self = MM_GENERIC_GSM (user_data); char *oper; - if (error) - return; - - oper = parse_operator (response->str); - if (!oper) - return; - - g_free (priv->oper_code); - priv->oper_code = oper; + if (!error) { + oper = parse_operator (response->str, MM_MODEM_CHARSET_UNKNOWN); + if (oper) { + reg_info_updated (self, FALSE, MM_GENERIC_GSM_REG_TYPE_UNKNOWN, 0, + TRUE, oper, + FALSE, NULL); + } + } } static void -read_operator_name_done (MMSerialPort *port, +read_operator_name_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (user_data); + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); char *oper; - if (error) - return; - - oper = parse_operator (response->str); - if (!oper) - return; - - g_free (priv->oper_name); - priv->oper_name = oper; - - mm_modem_gsm_network_registration_info (MM_MODEM_GSM_NETWORK (user_data), - priv->reg_status, - priv->oper_code, - priv->oper_name); + if (!error) { + oper = parse_operator (response->str, priv->cur_charset); + if (oper) { + reg_info_updated (self, FALSE, MM_GENERIC_GSM_REG_TYPE_UNKNOWN, 0, + FALSE, NULL, + TRUE, oper); + } + } } /* Registration */ @@ -961,8 +1789,93 @@ mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem) } } +static void +got_signal_quality (MMModem *modem, + guint32 quality, + GError *error, + gpointer user_data) +{ + mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (modem), quality); +} + +static void +roam_disconnect_done (MMModem *modem, + GError *error, + gpointer user_data) +{ + g_message ("Disconnected because roaming is not allowed"); +} + +static void +get_reg_act_done (MMModem *modem, + guint32 act, + GError *error, + gpointer user_data) +{ + if (modem && !error && act) + mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (modem), act); +} + +void +mm_generic_gsm_set_reg_status (MMGenericGsm *self, + MMGenericGsmRegType rs_type, + MMModemGsmNetworkRegStatus status) +{ + MMGenericGsmPrivate *priv; + MMAtSerialPort *port; + + g_return_if_fail (MM_IS_GENERIC_GSM (self)); + + g_return_if_fail ( rs_type == MM_GENERIC_GSM_REG_TYPE_CS + || rs_type == MM_GENERIC_GSM_REG_TYPE_PS); + + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + if (priv->reg_status[rs_type - 1] == status) + return; + + g_debug ("%s registration state changed: %d", + (rs_type == MM_GENERIC_GSM_REG_TYPE_CS) ? "CS" : "PS", + status); + priv->reg_status[rs_type - 1] = status; + + port = mm_generic_gsm_get_best_at_port (self, NULL); + + if (status == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME || + status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) { + + /* If we're connected and we're not supposed to roam, but the device + * just roamed, disconnect the connection to avoid charging the user + * loads of money. + */ + if ( (status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) + && (mm_modem_get_state (MM_MODEM (self)) == MM_MODEM_STATE_CONNECTED) + && (priv->roam_allowed == FALSE)) { + mm_modem_disconnect (MM_MODEM (self), roam_disconnect_done, NULL); + } else { + /* Grab the new operator name and MCC/MNC */ + if (port) { + mm_at_serial_port_queue_command (port, "+COPS=3,2;+COPS?", 3, read_operator_code_done, self); + mm_at_serial_port_queue_command (port, "+COPS=3,0;+COPS?", 3, read_operator_name_done, self); + } + + /* And update signal quality and access technology */ + mm_modem_gsm_network_get_signal_quality (MM_MODEM_GSM_NETWORK (self), got_signal_quality, NULL); + if (MM_GENERIC_GSM_GET_CLASS (self)->get_access_technology) + MM_GENERIC_GSM_GET_CLASS (self)->get_access_technology (self, get_reg_act_done, NULL); + } + } else + reg_info_updated (self, FALSE, rs_type, 0, TRUE, NULL, TRUE, NULL); + + mm_generic_gsm_update_enabled_state (self, TRUE, MM_MODEM_STATE_REASON_NONE); +} + +/* Returns TRUE if the modem is "done", ie has registered or been denied */ static gboolean -reg_status_updated (MMGenericGsm *self, int new_value, GError **error) +reg_status_updated (MMGenericGsm *self, + MMGenericGsmRegType rs_type, + int new_value, + GError **error) { MMModemGsmNetworkRegStatus status; gboolean status_done = FALSE; @@ -991,7 +1904,7 @@ reg_status_updated (MMGenericGsm *self, int new_value, GError **error) break; } - mm_generic_gsm_set_reg_status (self, status); + mm_generic_gsm_set_reg_status (self, rs_type, status); /* Registration has either completed successfully or completely failed */ switch (status) { @@ -1022,21 +1935,35 @@ reg_status_updated (MMGenericGsm *self, int new_value, GError **error) return status_done; } +static MMGenericGsmRegType +cgreg_to_reg_type (gboolean cgreg) +{ + return (cgreg ? MM_GENERIC_GSM_REG_TYPE_PS : MM_GENERIC_GSM_REG_TYPE_CS); +} + static void -reg_state_changed (MMSerialPort *port, +reg_state_changed (MMAtSerialPort *port, GMatchInfo *match_info, gpointer user_data) { MMGenericGsm *self = MM_GENERIC_GSM (user_data); MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); - char *str; - gboolean done; + guint32 state = 0, idx; + gulong lac = 0, cell_id = 0; + gint act = -1; + gboolean cgreg = FALSE; + GError *error = NULL; - str = g_match_info_fetch (match_info, 1); - done = reg_status_updated (self, atoi (str), NULL); - g_free (str); + if (!mm_gsm_parse_creg_response (match_info, &state, &lac, &cell_id, &act, &cgreg, &error)) { + if (mm_options_debug ()) { + g_warning ("%s: error parsing unsolicited registration: %s", + __func__, + error && error->message ? error->message : "(unknown)"); + } + return; + } - if (done) { + if (reg_status_updated (self, cgreg_to_reg_type (cgreg), state, NULL)) { /* If registration is finished (either registered or failed) but the * registration query hasn't completed yet, just remove the timeout and * let the registration query complete. @@ -1046,6 +1973,14 @@ reg_state_changed (MMSerialPort *port, priv->pending_reg_id = 0; } } + + idx = cgreg ? 1 : 0; + priv->lac[idx] = lac; + priv->cell_id[idx] = cell_id; + + /* Only update access technology if it appeared in the CREG/CGREG response */ + if (act != -1) + mm_generic_gsm_update_access_technology (self, etsi_act_to_mm_act (act)); } static gboolean @@ -1072,8 +2007,62 @@ reg_status_again_remove (gpointer data) g_source_remove (id); } +static gboolean +handle_reg_status_response (MMGenericGsm *self, + GString *response, + GError **error) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + GMatchInfo *match_info; + guint32 status = 0, idx; + gulong lac = 0, ci = 0; + gint act = -1; + gboolean cgreg = FALSE; + guint i; + + /* Try to match the response */ + for (i = 0; i < priv->reg_regex->len; i++) { + GRegex *r = g_ptr_array_index (priv->reg_regex, i); + + if (g_regex_match (r, response->str, 0, &match_info)) + break; + g_match_info_free (match_info); + match_info = NULL; + } + + if (!match_info) { + g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Unknown registration status response"); + return FALSE; + } + + /* And parse it */ + if (!mm_gsm_parse_creg_response (match_info, &status, &lac, &ci, &act, &cgreg, error)) { + g_match_info_free (match_info); + return FALSE; + } + + /* Success; update cached location information */ + idx = cgreg ? 1 : 0; + priv->lac[idx] = lac; + priv->cell_id[idx] = ci; + + /* Only update access technology if it appeared in the CREG/CGREG response */ + if (act != -1) + mm_generic_gsm_update_access_technology (self, etsi_act_to_mm_act (act)); + + if (status >= 0) { + /* Update cached registration status */ + reg_status_updated (self, cgreg_to_reg_type (cgreg), status, NULL); + } + + return TRUE; +} + +#define CGREG_TRIED_TAG "cgreg-tried" + static void -get_reg_status_done (MMSerialPort *port, +get_reg_status_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1081,76 +2070,71 @@ get_reg_status_done (MMSerialPort *port, MMCallbackInfo *info = (MMCallbackInfo *) user_data; MMGenericGsm *self = MM_GENERIC_GSM (info->modem); MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); - int reg_status = -1; - GRegex *r; - GMatchInfo *match_info; - char *tmp; guint id; + MMModemGsmNetworkRegStatus status; - g_warn_if_fail (info == priv->pending_reg_info); + /* This function should only get called during the connect sequence when + * polling for registration state, since explicit registration requests + * from D-Bus clients are filled from the cached registration state. + */ + g_return_if_fail (info == priv->pending_reg_info); if (error) { - info->error = g_error_copy (error); - goto reg_done; - } - - r = g_regex_new ("\\+CREG:\\s*(\\d+),\\s*(\\d+)", - G_REGEX_RAW | G_REGEX_OPTIMIZE, - 0, &info->error); - if (r) { - g_regex_match_full (r, response->str, response->len, 0, 0, &match_info, &info->error); - if (g_match_info_matches (match_info)) { - /* Get reg status */ - tmp = g_match_info_fetch (match_info, 2); - if (isdigit (tmp[0])) { - tmp[1] = '\0'; - reg_status = atoi (tmp); - } else { - info->error = g_error_new (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "Unknown registration status '%s'", - tmp); - } - g_free (tmp); + gboolean cgreg_tried = !!mm_callback_info_get_data (info, CGREG_TRIED_TAG); + + /* If this was a +CREG error, try +CGREG. Some devices (blackberries) + * respond to +CREG with an error but return a valid +CGREG response. + * So try both. If we get an error from both +CREG and +CGREG, that's + * obviously a hard fail. + */ + if (cgreg_tried == FALSE) { + mm_callback_info_set_data (info, CGREG_TRIED_TAG, GUINT_TO_POINTER (TRUE), NULL); + mm_at_serial_port_queue_command (port, "+CGREG?", 10, get_reg_status_done, info); + return; } else { - info->error = g_error_new_literal (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "Could not parse the registration status response"); + info->error = g_error_copy (error); + goto reg_done; } - g_match_info_free (match_info); - g_regex_unref (r); } - if ( reg_status >= 0 - && !reg_status_updated (self, reg_status, &info->error) - && priv->pending_reg_info) { - g_clear_error (&info->error); + /* The unsolicited registration state handlers will intercept the CREG + * response and update the cached registration state for us, so we usually + * just need to check the cached state here. + */ + + if (strlen (response->str)) { + /* But just in case the unsolicited handlers doesn't do it... */ + if (!handle_reg_status_response (self, response, &info->error)) + goto reg_done; + } - /* Not registered yet; poll registration status again */ + status = gsm_reg_status (self); + if ( status != MM_MODEM_GSM_NETWORK_REG_STATUS_HOME + && status != MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING + && status != MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED) { + /* If we're still waiting for automatic registration to complete or + * fail, check again in a few seconds. + */ id = g_timeout_add_seconds (1, reg_status_again, info); mm_callback_info_set_data (info, REG_STATUS_AGAIN_TAG, - GUINT_TO_POINTER (id), - reg_status_again_remove); + GUINT_TO_POINTER (id), + reg_status_again_remove); return; } reg_done: - /* This will schedule the callback for us */ + /* This will schedule the pending registration's the callback for us */ mm_generic_gsm_pending_registration_stop (self); } static void -get_registration_status (MMSerialPort *port, MMCallbackInfo *info) +get_registration_status (MMAtSerialPort *port, MMCallbackInfo *info) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); - - g_warn_if_fail (info == priv->pending_reg_info); - - mm_serial_port_queue_command (port, "+CREG?", 10, get_reg_status_done, info); + mm_at_serial_port_queue_command (port, "+CREG?", 10, get_reg_status_done, info); } static void -register_done (MMSerialPort *port, +register_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1169,7 +2153,9 @@ register_done (MMSerialPort *port, if (priv->pending_reg_info) { g_warn_if_fail (info == priv->pending_reg_info); - /* Ignore errors here, get the actual registration status */ + /* Don't use cached registration state here since it could be up to + * 30 seconds old. Get fresh registration state. + */ get_registration_status (port, info); } } @@ -1182,7 +2168,16 @@ registration_timed_out (gpointer data) g_warn_if_fail (info == priv->pending_reg_info); - priv->reg_status = MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE; + /* Clear out circuit-switched registration info... */ + reg_info_updated (MM_GENERIC_GSM (info->modem), + TRUE, MM_GENERIC_GSM_REG_TYPE_CS, MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE, + TRUE, NULL, + TRUE, NULL); + /* ... and packet-switched registration info */ + reg_info_updated (MM_GENERIC_GSM (info->modem), + TRUE, MM_GENERIC_GSM_REG_TYPE_PS, MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE, + TRUE, NULL, + TRUE, NULL); info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_TIMEOUT); mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (info->modem)); @@ -1190,28 +2185,47 @@ registration_timed_out (gpointer data) return FALSE; } +static gboolean +reg_is_idle (MMModemGsmNetworkRegStatus status) +{ + if ( status == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME + || status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING + || status == MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING) + return FALSE; + return TRUE; +} + static void do_register (MMModemGsmNetwork *modem, const char *network_id, MMModemFn callback, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); MMCallbackInfo *info; - char *command; + char *command = NULL; /* Clear any previous registration */ - mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem)); + mm_generic_gsm_pending_registration_stop (self); info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); priv->pending_reg_id = g_timeout_add_seconds (60, registration_timed_out, info); priv->pending_reg_info = info; - if (network_id) + /* If the user sent a specific network to use, lock it in. If no specific + * network was given, and the modem is not registered and not searching, + * kick it to search for a network. Also do auto registration if the modem + * had been set to manual registration last time but now is not. + */ + if (network_id) { command = g_strdup_printf ("+COPS=1,2,\"%s\"", network_id); - else + priv->manual_reg = TRUE; + } else if (reg_is_idle (gsm_reg_status (self)) || priv->manual_reg) { command = g_strdup ("+COPS=0,,"); + priv->manual_reg = FALSE; + } /* Ref the callback info to ensure it stays alive for register_done() even * if the timeout triggers and ends registration (which calls the callback @@ -1231,8 +2245,12 @@ do_register (MMModemGsmNetwork *modem, * the +COPS response is never received. */ mm_callback_info_ref (info); - mm_serial_port_queue_command (priv->primary, command, 120, register_done, info); - g_free (command); + + if (command) { + mm_at_serial_port_queue_command (priv->primary, command, 120, register_done, info); + g_free (command); + } else + register_done (priv->primary, NULL, NULL, info); } static void @@ -1242,7 +2260,7 @@ gsm_network_reg_info_invoke (MMCallbackInfo *info) MMModemGsmNetworkRegInfoFn callback = (MMModemGsmNetworkRegInfoFn) info->callback; callback (MM_MODEM_GSM_NETWORK (info->modem), - priv->reg_status, + gsm_reg_status (MM_GENERIC_GSM (info->modem)), priv->oper_code, priv->oper_name, info->error, @@ -1260,7 +2278,9 @@ get_registration_info (MMModemGsmNetwork *self, gsm_network_reg_info_invoke, G_CALLBACK (callback), user_data); - + /* Registration info updates are handled internally either by unsolicited + * updates or by polling. Thus just return the cached registration state. + */ mm_callback_info_schedule (info); } @@ -1292,7 +2312,7 @@ mm_generic_gsm_connect_complete (MMGenericGsm *modem, } static void -connect_report_done (MMSerialPort *port, +connect_report_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1323,7 +2343,7 @@ connect_report_done (MMSerialPort *port, } static void -connect_done (MMSerialPort *port, +connect_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1335,7 +2355,7 @@ connect_done (MMSerialPort *port, info->error = g_error_copy (error); /* Try to get more information why it failed */ priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); - mm_serial_port_queue_command (priv->primary, "+CEER", 3, connect_report_done, info); + mm_at_serial_port_queue_command (priv->primary, "+CEER", 3, connect_report_done, info); } else mm_generic_gsm_connect_complete (MM_GENERIC_GSM (info->modem), NULL, info); } @@ -1369,21 +2389,21 @@ connect (MMModem *modem, } else command = g_strconcat ("DT", number, NULL); - mm_serial_port_queue_command (priv->primary, command, 60, connect_done, info); + mm_at_serial_port_queue_command (priv->primary, command, 60, connect_done, info); g_free (command); } static void -disconnect_flash_done (MMSerialPort *port, - GError *error, - gpointer user_data) +disconnect_done (MMModem *modem, + GError *error, + gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; MMModemState prev_state; - info->error = mm_modem_check_removed (info->modem, error); + info->error = mm_modem_check_removed (modem, error); if (info->error) { - if (info->modem) { + if (info->modem && modem) { /* Reset old state since the operation failed */ prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_GSM_PREV_STATE_TAG)); mm_modem_set_state (MM_MODEM (info->modem), @@ -1391,10 +2411,11 @@ disconnect_flash_done (MMSerialPort *port, MM_MODEM_STATE_REASON_NONE); } } else { - MMGenericGsm *self = MM_GENERIC_GSM (info->modem); + MMGenericGsm *self = MM_GENERIC_GSM (modem); MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); mm_port_set_connected (priv->data, FALSE); + priv->cid = -1; mm_generic_gsm_update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE); } @@ -1402,16 +2423,138 @@ disconnect_flash_done (MMSerialPort *port, } static void +disconnect_all_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + mm_callback_info_schedule ((MMCallbackInfo *) user_data); +} + +static void +disconnect_send_cgact (MMAtSerialPort *port, + gint cid, + MMAtSerialResponseFn callback, + gpointer user_data) +{ + char *command; + + if (cid >= 0) + command = g_strdup_printf ("+CGACT=0,%d", cid); + else { + /* Disable all PDP contexts */ + command = g_strdup_printf ("+CGACT=0"); + } + + mm_at_serial_port_queue_command (port, command, 3, callback, user_data); + g_free (command); +} + +#define DISCONNECT_CGACT_DONE_TAG "disconnect-cgact-done" + +static void +disconnect_flash_done (MMSerialPort *port, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + /* Ignore "NO CARRIER" response when modem disconnects and any flash + * failures we might encounter. Other errors are hard errors. + */ + if ( !g_error_matches (info->error, MM_MODEM_CONNECT_ERROR, MM_MODEM_CONNECT_ERROR_NO_CARRIER) + && !g_error_matches (info->error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_FLASH_FAILED)) { + mm_callback_info_schedule (info); + return; + } + g_clear_error (&info->error); + } + + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + mm_port_set_connected (priv->data, FALSE); + + /* Don't bother doing the CGACT again if it was done on a secondary port */ + if (mm_callback_info_get_data (info, DISCONNECT_CGACT_DONE_TAG)) + disconnect_all_done (MM_AT_SERIAL_PORT (priv->primary), NULL, NULL, info); + else { + disconnect_send_cgact (MM_AT_SERIAL_PORT (priv->primary), + priv->cid, + disconnect_all_done, + info); + } +} + +static void +disconnect_secondary_cgact_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMGenericGsm *self; + MMGenericGsmPrivate *priv; + + if (!info->modem) { + info->error = mm_modem_check_removed (info->modem, error); + mm_callback_info_schedule (info); + return; + } + + self = MM_GENERIC_GSM (info->modem); + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + /* Now that we've tried deactivating the PDP context on the secondary + * port, continue with flashing the primary port. + */ + if (!error) + mm_callback_info_set_data (info, DISCONNECT_CGACT_DONE_TAG, GUINT_TO_POINTER (TRUE), NULL); + + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disconnect_flash_done, info); +} + +static void +real_do_disconnect (MMGenericGsm *self, + gint cid, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMCallbackInfo *info; + + info = mm_callback_info_new (MM_MODEM (self), callback, user_data); + + /* If the primary port is connected (with PPP) then try sending the PDP + * context deactivation on the secondary port because not all modems will + * respond to flashing (since either the modem or the kernel's serial + * driver doesn't support it). + */ + if ( mm_port_get_connected (MM_PORT (priv->primary)) + && priv->secondary + && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) { + disconnect_send_cgact (MM_AT_SERIAL_PORT (priv->secondary), + priv->cid, + disconnect_secondary_cgact_done, + info); + } else { + /* Just flash the primary port */ + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disconnect_flash_done, info); + } +} + +static void disconnect (MMModem *modem, MMModemFn callback, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); MMCallbackInfo *info; MMModemState state; - /* First, reset the previously used CID */ - mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0); + priv->roam_allowed = TRUE; info = mm_callback_info_new (modem, callback, user_data); @@ -1423,7 +2566,9 @@ disconnect (MMModem *modem, NULL); mm_modem_set_state (modem, MM_MODEM_STATE_DISCONNECTING, MM_MODEM_STATE_REASON_NONE); - mm_serial_port_flash (priv->primary, 1000, disconnect_flash_done, info); + + g_assert (MM_GENERIC_GSM_GET_CLASS (self)->do_disconnect); + MM_GENERIC_GSM_GET_CLASS (self)->do_disconnect (self, priv->cid, disconnect_done, info); } static void @@ -1438,7 +2583,7 @@ gsm_network_scan_invoke (MMCallbackInfo *info) } static void -scan_done (MMSerialPort *port, +scan_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1470,30 +2615,33 @@ scan (MMModemGsmNetwork *modem, G_CALLBACK (callback), user_data); - mm_serial_port_queue_command (priv->primary, "+COPS=?", 120, scan_done, info); + mm_at_serial_port_queue_command (priv->primary, "+COPS=?", 120, scan_done, info); } /* SetApn */ +#define APN_CID_TAG "generic-gsm-cid" + static void -set_apn_done (MMSerialPort *port, +set_apn_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - if (error) - info->error = g_error_copy (error); - else - mm_generic_gsm_set_cid (MM_GENERIC_GSM (info->modem), - GPOINTER_TO_UINT (mm_callback_info_get_data (info, "cid"))); + info->error = mm_modem_check_removed (info->modem, error); + if (!info->error) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + priv->cid = GPOINTER_TO_INT (mm_callback_info_get_data (info, APN_CID_TAG)); + } mm_callback_info_schedule (info); } static void -cid_range_read (MMSerialPort *port, +cid_range_read (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1503,7 +2651,7 @@ cid_range_read (MMSerialPort *port, if (error) info->error = g_error_copy (error); - else if (g_str_has_prefix (response->str, "+CGDCONT: ")) { + else if (g_str_has_prefix (response->str, "+CGDCONT:")) { GRegex *r; GMatchInfo *match_info; @@ -1550,16 +2698,16 @@ cid_range_read (MMSerialPort *port, const char *apn = (const char *) mm_callback_info_get_data (info, "apn"); char *command; - mm_callback_info_set_data (info, "cid", GUINT_TO_POINTER (cid), NULL); + mm_callback_info_set_data (info, APN_CID_TAG, GINT_TO_POINTER (cid), NULL); command = g_strdup_printf ("+CGDCONT=%d,\"IP\",\"%s\"", cid, apn); - mm_serial_port_queue_command (port, command, 3, set_apn_done, info); + mm_at_serial_port_queue_command (port, command, 3, set_apn_done, info); g_free (command); } } static void -existing_apns_read (MMSerialPort *port, +existing_apns_read (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1567,9 +2715,10 @@ existing_apns_read (MMSerialPort *port, MMCallbackInfo *info = (MMCallbackInfo *) user_data; gboolean found = FALSE; - if (error) - info->error = g_error_copy (error); - else if (g_str_has_prefix (response->str, "+CGDCONT: ")) { + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) + goto done; + else if (g_str_has_prefix (response->str, "+CGDCONT:")) { GRegex *r; GMatchInfo *match_info; @@ -1592,7 +2741,7 @@ existing_apns_read (MMSerialPort *port, apn = g_match_info_fetch (match_info, 3); if (!strcmp (apn, new_apn)) { - mm_generic_gsm_set_cid (MM_GENERIC_GSM (info->modem), (guint32) num_cid); + MM_GENERIC_GSM_GET_PRIVATE (info->modem)->cid = num_cid; found = TRUE; } @@ -1620,11 +2769,13 @@ existing_apns_read (MMSerialPort *port, MM_MODEM_ERROR_GENERAL, "Could not parse the response"); +done: if (found || info->error) mm_callback_info_schedule (info); - else + else { /* APN not configured on the card. Get the allowed CID range */ - mm_serial_port_queue_command_cached (port, "+CGDCONT=?", 3, cid_range_read, info); + mm_at_serial_port_queue_command_cached (port, "+CGDCONT=?", 3, cid_range_read, info); + } } static void @@ -1640,31 +2791,96 @@ set_apn (MMModemGsmNetwork *modem, mm_callback_info_set_data (info, "apn", g_strdup (apn), g_free); /* Start by searching if the APN is already in card */ - mm_serial_port_queue_command (priv->primary, "+CGDCONT?", 3, existing_apns_read, info); + mm_at_serial_port_queue_command (priv->primary, "+CGDCONT?", 3, existing_apns_read, info); } /* GetSignalQuality */ +static gboolean +emit_signal_quality_change (gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + priv->signal_quality_id = 0; + priv->signal_quality_timestamp = time (NULL); + mm_modem_gsm_network_signal_quality (MM_MODEM_GSM_NETWORK (self), priv->signal_quality); + return FALSE; +} + +void +mm_generic_gsm_update_signal_quality (MMGenericGsm *self, guint32 quality) +{ + MMGenericGsmPrivate *priv; + guint delay = 0; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_GENERIC_GSM (self)); + g_return_if_fail (quality <= 100); + + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + if (priv->signal_quality == quality) + return; + + priv->signal_quality = quality; + + /* Some modems will send unsolcited signal quality changes quite often, + * so rate-limit them to every few seconds. Track the last time we + * emitted signal quality so that we send the signal immediately if there + * haven't been any updates in a while. + */ + if (!priv->signal_quality_id) { + if (priv->signal_quality_timestamp > 0) { + time_t curtime; + long int diff; + + curtime = time (NULL); + diff = curtime - priv->signal_quality_timestamp; + if (diff == 0) { + /* If the device is sending more than one update per second, + * make sure we don't spam clients with signals. + */ + delay = 3; + } else if ((diff > 0) && (diff <= 3)) { + /* Emitted an update less than 3 seconds ago; schedule an update + * 3 seconds after the previous one. + */ + delay = (guint) diff; + } else { + /* Otherwise, we haven't emitted an update in the last 3 seconds, + * or the user turned their clock back, or something like that. + */ + delay = 0; + } + } + + priv->signal_quality_id = g_timeout_add_seconds (delay, + emit_signal_quality_change, + self); + } +} + static void -get_signal_quality_done (MMSerialPort *port, +get_signal_quality_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { - MMGenericGsmPrivate *priv; MMCallbackInfo *info = (MMCallbackInfo *) user_data; char *reply = response->str; + gboolean parsed = FALSE; - if (error) - info->error = g_error_copy (error); - else if (!strncmp (reply, "+CSQ: ", 6)) { + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) + goto done; + + if (!strncmp (reply, "+CSQ: ", 6)) { /* Got valid reply */ int quality; int ber; - reply += 6; - - if (sscanf (reply, "%d, %d", &quality, &ber)) { + if (sscanf (reply + 6, "%d, %d", &quality, &ber)) { /* 99 means unknown */ if (quality == 99) { info->error = g_error_new_literal (MM_MOBILE_ERROR, @@ -1674,15 +2890,19 @@ get_signal_quality_done (MMSerialPort *port, /* Normalize the quality */ quality = CLAMP (quality, 0, 31) * 100 / 31; - priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); - priv->signal_quality = quality; + mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (info->modem), quality); mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL); } - } else - info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, - "Could not parse signal quality results"); + parsed = TRUE; + } } + if (!parsed && !info->error) { + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Could not parse signal quality results"); + } + +done: mm_callback_info_schedule (info); } @@ -1693,24 +2913,362 @@ get_signal_quality (MMModemGsmNetwork *modem, { MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); MMCallbackInfo *info; - gboolean connected; + MMAtSerialPort *port; + + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), NULL); + if (port) + mm_at_serial_port_queue_command (port, "+CSQ", 3, get_signal_quality_done, info); + else { + /* Use cached signal quality */ + mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->signal_quality), NULL); + mm_callback_info_schedule (info); + } +} + +/*****************************************************************************/ + +typedef struct { + MMModemGsmAccessTech mm_act; + gint etsi_act; +} ModeEtsi; + +static ModeEtsi modes_table[] = { + { MM_MODEM_GSM_ACCESS_TECH_GSM, 0 }, + { MM_MODEM_GSM_ACCESS_TECH_GSM_COMPACT, 1 }, + { MM_MODEM_GSM_ACCESS_TECH_UMTS, 2 }, + { MM_MODEM_GSM_ACCESS_TECH_EDGE, 3 }, + { MM_MODEM_GSM_ACCESS_TECH_HSDPA, 4 }, + { MM_MODEM_GSM_ACCESS_TECH_HSUPA, 5 }, + { MM_MODEM_GSM_ACCESS_TECH_HSPA, 6 }, + { MM_MODEM_GSM_ACCESS_TECH_HSPA, 7 }, /* E-UTRAN/LTE => HSPA for now */ + { MM_MODEM_GSM_ACCESS_TECH_UNKNOWN, -1 }, +}; + +static MMModemGsmAccessTech +etsi_act_to_mm_act (gint act) +{ + ModeEtsi *iter = &modes_table[0]; + + while (iter->mm_act != MM_MODEM_GSM_ACCESS_TECH_UNKNOWN) { + if (iter->etsi_act == act) + return iter->mm_act; + iter++; + } + return MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; +} + +static void +_internal_update_access_technology (MMGenericGsm *modem, + MMModemGsmAccessTech act) +{ + MMGenericGsmPrivate *priv; + + g_return_if_fail (modem != NULL); + g_return_if_fail (MM_IS_GENERIC_GSM (modem)); + g_return_if_fail (act >= MM_MODEM_GSM_ACCESS_TECH_UNKNOWN && act <= MM_MODEM_GSM_ACCESS_TECH_LAST); + + priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + + if (act != priv->act) { + MMModemDeprecatedMode old_mode; + + priv->act = act; + g_object_notify (G_OBJECT (modem), MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY); + + /* Deprecated value */ + old_mode = mm_modem_gsm_network_act_to_old_mode (act); + g_signal_emit_by_name (G_OBJECT (modem), "network-mode", old_mode); + } +} + +void +mm_generic_gsm_update_access_technology (MMGenericGsm *self, + MMModemGsmAccessTech act) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_GENERIC_GSM (self)); + + /* For plugins, don't update the access tech when the modem isn't enabled */ + if (mm_modem_get_state (MM_MODEM (self)) >= MM_MODEM_STATE_ENABLED) + _internal_update_access_technology (self, act); +} + +void +mm_generic_gsm_update_allowed_mode (MMGenericGsm *self, + MMModemGsmAllowedMode mode) +{ + MMGenericGsmPrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_GENERIC_GSM (self)); + + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + if (mode != priv->allowed_mode) { + priv->allowed_mode = mode; + g_object_notify (G_OBJECT (self), MM_MODEM_GSM_NETWORK_ALLOWED_MODE); + } +} + +static void +set_allowed_mode_done (MMModem *modem, GError *error, gpointer user_data) +{ + MMCallbackInfo *info = user_data; + + info->error = mm_modem_check_removed (info->modem, error); + if (!info->error) { + MMModemGsmAllowedMode mode = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "mode")); - connected = mm_port_get_connected (MM_PORT (priv->primary)); - if (connected && !priv->secondary) { - g_message ("Returning saved signal quality %d", priv->signal_quality); - callback (MM_MODEM (modem), priv->signal_quality, NULL, user_data); + mm_generic_gsm_update_allowed_mode (MM_GENERIC_GSM (info->modem), mode); + } + + mm_callback_info_schedule (info); +} + +static void +set_allowed_mode (MMModemGsmNetwork *net, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (net); + MMCallbackInfo *info; + + info = mm_callback_info_new (MM_MODEM (self), callback, user_data); + + switch (mode) { + case MM_MODEM_GSM_ALLOWED_MODE_ANY: + case MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED: + case MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED: + case MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY: + case MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY: + if (!MM_GENERIC_GSM_GET_CLASS (self)->set_allowed_mode) { + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + } else { + mm_callback_info_set_data (info, "mode", GUINT_TO_POINTER (mode), NULL); + MM_GENERIC_GSM_GET_CLASS (self)->set_allowed_mode (self, mode, set_allowed_mode_done, info); + } + break; + default: + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Invalid mode."); + break; + } + + if (info->error) + mm_callback_info_schedule (info); +} + +/*****************************************************************************/ +/* Charset stuff */ + +static void +get_charsets_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + mm_callback_info_schedule (info); return; } - info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_queue_command (connected ? priv->secondary : priv->primary, "+CSQ", 3, get_signal_quality_done, info); + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + priv->charsets = MM_MODEM_CHARSET_UNKNOWN; + if (!mm_gsm_parse_cscs_support_response (response->str, &priv->charsets)) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Failed to parse the supported character sets response"); + } else + mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->charsets), NULL); + + mm_callback_info_schedule (info); +} + +static void +get_supported_charsets (MMModem *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMCallbackInfo *info; + MMAtSerialPort *port; + + info = mm_callback_info_uint_new (MM_MODEM (self), callback, user_data); + + /* Use cached value if we have one */ + if (priv->charsets) { + mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->charsets), NULL); + mm_callback_info_schedule (info); + return; + } + + /* Otherwise hit up the modem */ + port = mm_generic_gsm_get_best_at_port (self, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "+CSCS=?", 3, get_charsets_done, info); +} + +static void +set_get_charset_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv; + MMModemCharset tried_charset; + const char *p; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + mm_callback_info_schedule (info); + return; + } + + p = response->str; + if (g_str_has_prefix (p, "+CSCS:")) + p += 6; + while (*p == ' ') + p++; + + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + priv->cur_charset = mm_modem_charset_from_string (p); + + tried_charset = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "charset")); + + if (tried_charset != priv->cur_charset) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET, + "Modem failed to change character set to %s", + mm_modem_charset_to_string (tried_charset)); + } + + mm_callback_info_schedule (info); +} + +#define TRIED_NO_QUOTES_TAG "tried-no-quotes" + +static void +set_charset_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + gboolean tried_no_quotes = !!mm_callback_info_get_data (info, TRIED_NO_QUOTES_TAG); + MMModemCharset charset = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "charset")); + char *command; + + if (!info->modem || tried_no_quotes) { + mm_callback_info_schedule (info); + return; + } + + /* Some modems puke if you include the quotes around the character + * set name, so lets try it again without them. + */ + mm_callback_info_set_data (info, TRIED_NO_QUOTES_TAG, GUINT_TO_POINTER (TRUE), NULL); + command = g_strdup_printf ("+CSCS=%s", mm_modem_charset_to_string (charset)); + mm_at_serial_port_queue_command (port, command, 3, set_charset_done, info); + g_free (command); + } else + mm_at_serial_port_queue_command (port, "+CSCS?", 3, set_get_charset_done, info); +} + +static gboolean +check_for_single_value (guint32 value) +{ + gboolean found = FALSE; + guint32 i; + + for (i = 1; i <= 32; i++) { + if (value & 0x1) { + if (found) + return FALSE; /* More than one bit set */ + found = TRUE; + } + value >>= 1; + } + + return TRUE; +} + +static void +set_charset (MMModem *modem, + MMModemCharset charset, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + const char *str; + char *command; + MMAtSerialPort *port; + + info = mm_callback_info_new (modem, callback, user_data); + + if (!(priv->charsets & charset) || !check_for_single_value (charset)) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET, + "Character set 0x%X not supported", + charset); + mm_callback_info_schedule (info); + return; + } + + str = mm_modem_charset_to_string (charset); + if (!str) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET, + "Unhandled character set 0x%X", + charset); + mm_callback_info_schedule (info); + return; + } + + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_callback_info_set_data (info, "charset", GUINT_TO_POINTER (charset), NULL); + + command = g_strdup_printf ("+CSCS=\"%s\"", str); + mm_at_serial_port_queue_command (port, command, 3, set_charset_done, info); + g_free (command); +} + +MMModemCharset +mm_generic_gsm_get_charset (MMGenericGsm *self) +{ + g_return_val_if_fail (self != NULL, MM_MODEM_CHARSET_UNKNOWN); + g_return_val_if_fail (MM_IS_GENERIC_GSM (self), MM_MODEM_CHARSET_UNKNOWN); + + return MM_GENERIC_GSM_GET_PRIVATE (self)->cur_charset; } /*****************************************************************************/ /* MMModemGsmSms interface */ static void -sms_send_done (MMSerialPort *port, +sms_send_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1733,39 +3291,29 @@ sms_send (MMModemGsmSms *modem, MMModemFn callback, gpointer user_data) { - MMGenericGsmPrivate *priv; MMCallbackInfo *info; char *command; - gboolean connected; - MMSerialPort *port = NULL; + MMAtSerialPort *port; info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); - priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); - connected = mm_port_get_connected (MM_PORT (priv->primary)); - if (connected) - port = priv->secondary; - else - port = priv->primary; - + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); if (!port) { - info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, - "Cannot send SMS while connected"); mm_callback_info_schedule (info); return; } /* FIXME: use the PDU mode instead */ - mm_serial_port_queue_command (port, "AT+CMGF=1", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "AT+CMGF=1", 3, NULL, NULL); command = g_strdup_printf ("+CMGS=\"%s\"\r%s\x1a", number, text); - mm_serial_port_queue_command (port, command, 10, sms_send_done, info); + mm_at_serial_port_queue_command (port, command, 10, sms_send_done, info); g_free (command); } -MMSerialPort * -mm_generic_gsm_get_port (MMGenericGsm *modem, - MMPortType ptype) +MMAtSerialPort * +mm_generic_gsm_get_at_port (MMGenericGsm *modem, + MMPortType ptype) { g_return_val_if_fail (MM_IS_GENERIC_GSM (modem), NULL); g_return_val_if_fail (ptype != MM_PORT_TYPE_UNKNOWN, NULL); @@ -1778,105 +3326,267 @@ mm_generic_gsm_get_port (MMGenericGsm *modem, return NULL; } +MMAtSerialPort * +mm_generic_gsm_get_best_at_port (MMGenericGsm *self, GError **error) +{ + MMGenericGsmPrivate *priv; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_GENERIC_GSM (self), NULL); + + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + if (!mm_port_get_connected (MM_PORT (priv->primary))) + return priv->primary; + + if (!priv->secondary) { + g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, + "Cannot perform this operation while connected"); + } + + return priv->secondary; +} + /*****************************************************************************/ /* MMModemSimple interface */ typedef enum { - SIMPLE_STATE_BEGIN = 0, + SIMPLE_STATE_CHECK_PIN = 0, SIMPLE_STATE_ENABLE, - SIMPLE_STATE_CHECK_PIN, + SIMPLE_STATE_ALLOWED_MODE, SIMPLE_STATE_REGISTER, SIMPLE_STATE_SET_APN, SIMPLE_STATE_CONNECT, SIMPLE_STATE_DONE } SimpleState; -static const char * -simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error) +/* Looks a value up in the simple connect properties dictionary. If the + * requested key is not present in the dict, NULL is returned. If the + * requested key is present but is not a string, an error is returned. + */ +static gboolean +simple_get_property (MMCallbackInfo *info, + const char *name, + GType expected_type, + const char **out_str, + guint32 *out_num, + gboolean *out_bool, + GError **error) { GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties"); GValue *value; + gint foo; + + g_return_val_if_fail (properties != NULL, FALSE); + g_return_val_if_fail (name != NULL, FALSE); + if (out_str) + g_return_val_if_fail (*out_str == NULL, FALSE); value = (GValue *) g_hash_table_lookup (properties, name); if (!value) - return NULL; - - if (G_VALUE_HOLDS_STRING (value)) - return g_value_get_string (value); + return FALSE; + + if ((expected_type == G_TYPE_STRING) && G_VALUE_HOLDS_STRING (value)) { + *out_str = g_value_get_string (value); + return TRUE; + } else if (expected_type == G_TYPE_UINT) { + if (G_VALUE_HOLDS_UINT (value)) { + *out_num = g_value_get_uint (value); + return TRUE; + } else if (G_VALUE_HOLDS_INT (value)) { + /* handle ints for convenience, but only if they are >= 0 */ + foo = g_value_get_int (value); + if (foo >= 0) { + *out_num = (guint) foo; + return TRUE; + } + } + } else if (expected_type == G_TYPE_BOOLEAN && G_VALUE_HOLDS_BOOLEAN (value)) { + *out_bool = g_value_get_boolean (value); + return TRUE; + } g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, - "Invalid property type for '%s': %s (string expected)", - name, G_VALUE_TYPE_NAME (value)); + "Invalid property type for '%s': %s (%s expected)", + name, G_VALUE_TYPE_NAME (value), g_type_name (expected_type)); - return NULL; + return FALSE; +} + +static const char * +simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error) +{ + const char *str = NULL; + + simple_get_property (info, name, G_TYPE_STRING, &str, NULL, NULL, error); + return str; +} + +static gboolean +simple_get_uint_property (MMCallbackInfo *info, const char *name, guint32 *out_val, GError **error) +{ + return simple_get_property (info, name, G_TYPE_UINT, NULL, out_val, NULL, error); +} + +static gboolean +simple_get_bool_property (MMCallbackInfo *info, const char *name, gboolean *out_val, GError **error) +{ + return simple_get_property (info, name, G_TYPE_BOOLEAN, NULL, NULL, out_val, error); +} + +static gboolean +simple_get_allowed_mode (MMCallbackInfo *info, + MMModemGsmAllowedMode *out_mode, + GError **error) +{ + MMModemDeprecatedMode old_mode = MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_ANY; + MMModemGsmAllowedMode allowed_mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; + GError *tmp_error = NULL; + + /* check for new allowed mode first */ + if (simple_get_uint_property (info, "allowed_mode", &allowed_mode, &tmp_error)) { + if (allowed_mode > MM_MODEM_GSM_ALLOWED_MODE_LAST) { + g_set_error (&tmp_error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Invalid allowed mode %d", old_mode); + } else { + *out_mode = allowed_mode; + return TRUE; + } + } else if (!tmp_error) { + /* and if not, the old allowed mode */ + if (simple_get_uint_property (info, "network_mode", &old_mode, &tmp_error)) { + if (old_mode > MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_LAST) { + g_set_error (&tmp_error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Invalid allowed mode %d", old_mode); + } else { + *out_mode = mm_modem_gsm_network_old_mode_to_allowed (old_mode); + return TRUE; + } + } + } + + if (error) + *error = tmp_error; + return FALSE; } static void simple_state_machine (MMModem *modem, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *str; + MMGenericGsmPrivate *priv; + const char *str, *unlock = NULL; SimpleState state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "simple-connect-state")); - gboolean need_pin = FALSE; + SimpleState next_state = state; + gboolean done = FALSE; + MMModemGsmAllowedMode allowed_mode; + gboolean home_only = FALSE; - if (error) { - if (g_error_matches (error, MM_MOBILE_ERROR, MM_MOBILE_ERROR_SIM_PIN)) { - need_pin = TRUE; - state = SIMPLE_STATE_CHECK_PIN; - } else { - info->error = g_error_copy (error); - goto out; - } + info->error = mm_modem_check_removed (modem, error); + if (info->error) + goto out; + + priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + + if (mm_options_debug ()) { + GTimeVal tv; + char *data_device; + + g_object_get (G_OBJECT (modem), MM_MODEM_DATA_DEVICE, &data_device, NULL); + g_get_current_time (&tv); + g_debug ("<%ld.%ld> (%s): simple connect state %d", + tv.tv_sec, tv.tv_usec, data_device, state); + g_free (data_device); } switch (state) { - case SIMPLE_STATE_BEGIN: - state = SIMPLE_STATE_ENABLE; - mm_modem_enable (modem, simple_state_machine, info); - break; - case SIMPLE_STATE_ENABLE: - state = SIMPLE_STATE_CHECK_PIN; - mm_generic_gsm_check_pin (MM_GENERIC_GSM (modem), simple_state_machine, info); - break; case SIMPLE_STATE_CHECK_PIN: - if (need_pin) { - str = simple_get_string_property (info, "pin", &info->error); - if (str) - mm_modem_gsm_card_send_pin (MM_MODEM_GSM_CARD (modem), str, simple_state_machine, info); - else - info->error = g_error_copy (error); - } else { - str = simple_get_string_property (info, "network_id", &info->error); - state = SIMPLE_STATE_REGISTER; - if (!info->error) - mm_modem_gsm_network_register (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info); + next_state = SIMPLE_STATE_ENABLE; + + /* If we need a PIN, send it now, but we don't care about SIM-PIN2/SIM-PUK2 + * since the device is operational without it. + */ + unlock = mm_modem_base_get_unlock_required (MM_MODEM_BASE (modem)); + if (unlock && strcmp (unlock, "sim-puk2") && strcmp (unlock, "sim-pin2")) { + gboolean success = FALSE; + + if (!strcmp (unlock, "sim-pin")) { + str = simple_get_string_property (info, "pin", &info->error); + if (str) { + mm_modem_gsm_card_send_pin (MM_MODEM_GSM_CARD (modem), str, simple_state_machine, info); + success = TRUE; + } + } + if (!success && !info->error) + info->error = error_for_unlock_required (unlock); + break; } + /* Fall through if no PIN required */ + case SIMPLE_STATE_ENABLE: + next_state = SIMPLE_STATE_ALLOWED_MODE; + mm_modem_enable (modem, simple_state_machine, info); break; + case SIMPLE_STATE_ALLOWED_MODE: + next_state = SIMPLE_STATE_REGISTER; + if ( simple_get_allowed_mode (info, &allowed_mode, &info->error) + && (allowed_mode != priv->allowed_mode)) { + mm_modem_gsm_network_set_allowed_mode (MM_MODEM_GSM_NETWORK (modem), + allowed_mode, + simple_state_machine, + info); + break; + } else if (info->error) + break; + /* otherwise fall through as no allowed mode was sent */ case SIMPLE_STATE_REGISTER: + next_state = SIMPLE_STATE_SET_APN; + str = simple_get_string_property (info, "network_id", &info->error); + if (info->error) + str = NULL; + mm_modem_gsm_network_register (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info); + break; + case SIMPLE_STATE_SET_APN: + next_state = SIMPLE_STATE_CONNECT; str = simple_get_string_property (info, "apn", &info->error); - if (str) { - state = SIMPLE_STATE_SET_APN; - mm_modem_gsm_network_set_apn (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info); + if (str || info->error) { + if (str) + mm_modem_gsm_network_set_apn (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info); break; } - /* Fall through */ - case SIMPLE_STATE_SET_APN: - str = simple_get_string_property (info, "number", &info->error); - state = SIMPLE_STATE_CONNECT; - mm_modem_connect (modem, str, simple_state_machine, info); - break; + /* Fall through if no APN or no 'apn' property error */ case SIMPLE_STATE_CONNECT: - state = SIMPLE_STATE_DONE; + next_state = SIMPLE_STATE_DONE; + str = simple_get_string_property (info, "number", &info->error); + if (!info->error) { + if (simple_get_bool_property (info, "home_only", &home_only, &info->error)) { + MMModemGsmNetworkRegStatus status; + + priv->roam_allowed = !home_only; + + /* Don't connect if we're not supposed to be roaming */ + status = gsm_reg_status (MM_GENERIC_GSM (modem)); + if (home_only && (status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING)) { + info->error = g_error_new_literal (MM_MOBILE_ERROR, + MM_MOBILE_ERROR_GPRS_ROAMING_NOT_ALLOWED, + "Roaming is not allowed."); + break; + } + } else if (info->error) + break; + + mm_modem_connect (modem, str, simple_state_machine, info); + } break; case SIMPLE_STATE_DONE: + done = TRUE; break; } out: - if (info->error || state == SIMPLE_STATE_DONE) + if (info->error || done) mm_callback_info_schedule (info); else - mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (state), NULL); + mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (next_state), NULL); } static void @@ -1887,6 +3597,29 @@ simple_connect (MMModemSimple *simple, { MMCallbackInfo *info; + /* If debugging, list all the simple connect properties */ + if (mm_options_debug ()) { + GHashTableIter iter; + gpointer key, value; + GTimeVal tv; + char *data_device; + + g_object_get (G_OBJECT (simple), MM_MODEM_DATA_DEVICE, &data_device, NULL); + g_get_current_time (&tv); + + g_hash_table_iter_init (&iter, properties); + while (g_hash_table_iter_next (&iter, &key, &value)) { + char *val_str; + + val_str = g_strdup_value_contents ((GValue *) value); + g_debug ("<%ld.%ld> (%s): %s => %s", + tv.tv_sec, tv.tv_usec, + data_device, (const char *) key, val_str); + g_free (val_str); + } + g_free (data_device); + } + info = mm_callback_info_new (MM_MODEM (simple), callback, user_data); mm_callback_info_set_data (info, "simple-connect-properties", g_hash_table_ref (properties), @@ -1895,8 +3628,6 @@ simple_connect (MMModemSimple *simple, simple_state_machine (MM_MODEM (simple), NULL, info); } - - static void simple_free_gvalue (gpointer data) { @@ -1928,16 +3659,22 @@ simple_string_value (const char *str) return val; } +#define SS_HASH_TAG "simple-get-status" + static void simple_status_got_signal_quality (MMModem *modem, guint32 result, GError *error, gpointer user_data) { - if (error) - g_warning ("Error getting signal quality: %s", error->message); - else - g_hash_table_insert ((GHashTable *) user_data, "signal_quality", simple_uint_value (result)); + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GHashTable *properties; + + if (!error) { + properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG); + g_hash_table_insert (properties, "signal_quality", simple_uint_value (result)); + } + mm_callback_info_chain_complete_one (info); } static void @@ -1946,20 +3683,14 @@ simple_status_got_band (MMModem *modem, GError *error, gpointer user_data) { - /* Ignore band errors since there's no generic implementation for it */ - if (!error) - g_hash_table_insert ((GHashTable *) user_data, "band", simple_uint_value (result)); -} + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GHashTable *properties; -static void -simple_status_got_mode (MMModem *modem, - guint32 result, - GError *error, - gpointer user_data) -{ - /* Ignore network mode errors since there's no generic implementation for it */ - if (!error) - g_hash_table_insert ((GHashTable *) user_data, "network_mode", simple_uint_value (result)); + if (!error) { + properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG); + g_hash_table_insert (properties, "band", simple_uint_value (result)); + } + mm_callback_info_chain_complete_one (info); } static void @@ -1973,17 +3704,15 @@ simple_status_got_reg_info (MMModemGsmNetwork *modem, MMCallbackInfo *info = (MMCallbackInfo *) user_data; GHashTable *properties; - if (error) - info->error = g_error_copy (error); - else { - properties = (GHashTable *) mm_callback_info_get_data (info, "simple-get-status"); - + info->error = mm_modem_check_removed ((MMModem *) modem, error); + if (!info->error) { + properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG); + g_hash_table_insert (properties, "registration_status", simple_uint_value (status)); g_hash_table_insert (properties, "operator_code", simple_string_value (oper_code)); g_hash_table_insert (properties, "operator_name", simple_string_value (oper_name)); } - - mm_callback_info_schedule (info); + mm_callback_info_chain_complete_one (info); } static void @@ -1992,7 +3721,7 @@ simple_get_status_invoke (MMCallbackInfo *info) MMModemSimpleGetStatusFn callback = (MMModemSimpleGetStatusFn) info->callback; callback (MM_MODEM_SIMPLE (info->modem), - (GHashTable *) mm_callback_info_get_data (info, "simple-get-status"), + (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG), info->error, info->user_data); } @@ -2001,23 +3730,54 @@ simple_get_status (MMModemSimple *simple, MMModemSimpleGetStatusFn callback, gpointer user_data) { - MMModemGsmNetwork *gsm; + MMModemGsmNetwork *gsm = MM_MODEM_GSM_NETWORK (simple); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (simple); GHashTable *properties; MMCallbackInfo *info; + MMModemDeprecatedMode old_mode; info = mm_callback_info_new_full (MM_MODEM (simple), simple_get_status_invoke, G_CALLBACK (callback), user_data); - properties = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, simple_free_gvalue); - mm_callback_info_set_data (info, "simple-get-status", properties, (GDestroyNotify) g_hash_table_unref); + properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, simple_free_gvalue); + mm_callback_info_set_data (info, SS_HASH_TAG, properties, (GDestroyNotify) g_hash_table_unref); + + mm_callback_info_chain_start (info, 3); + mm_modem_gsm_network_get_signal_quality (gsm, simple_status_got_signal_quality, info); + mm_modem_gsm_network_get_band (gsm, simple_status_got_band, info); + mm_modem_gsm_network_get_registration_info (gsm, simple_status_got_reg_info, info); + + if (priv->act > -1) { + /* Deprecated key */ + old_mode = mm_modem_gsm_network_act_to_old_mode (priv->act); + g_hash_table_insert (properties, "network_mode", simple_uint_value (old_mode)); + + /* New key */ + g_hash_table_insert (properties, "access_technology", simple_uint_value (priv->act)); + } +} + +/*****************************************************************************/ + +static void +modem_state_changed (MMGenericGsm *self, GParamSpec *pspec, gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMModemState state; - gsm = MM_MODEM_GSM_NETWORK (simple); - mm_modem_gsm_network_get_signal_quality (gsm, simple_status_got_signal_quality, properties); - mm_modem_gsm_network_get_band (gsm, simple_status_got_band, properties); - mm_modem_gsm_network_get_mode (gsm, simple_status_got_mode, properties); - mm_modem_gsm_network_get_registration_info (gsm, simple_status_got_reg_info, properties); + /* Start polling registration status and signal quality when enabled */ + + state = mm_modem_get_state (MM_MODEM (self)); + if (state >= MM_MODEM_STATE_ENABLED) { + if (!priv->poll_id) + priv->poll_id = g_timeout_add_seconds (30, periodic_poll_cb, self); + } else { + if (priv->poll_id) + g_source_remove (priv->poll_id); + priv->poll_id = 0; + } } /*****************************************************************************/ @@ -2033,6 +3793,8 @@ modem_init (MMModem *modem_class) modem_class->connect = connect; modem_class->disconnect = disconnect; modem_class->get_info = get_card_info; + modem_class->get_supported_charsets = get_supported_charsets; + modem_class->set_charset = set_charset; } static void @@ -2040,10 +3802,12 @@ modem_gsm_card_init (MMModemGsmCard *class) { class->get_imei = get_imei; class->get_imsi = get_imsi; + class->get_operator_id = get_operator_id; class->send_pin = send_pin; class->send_puk = send_puk; class->enable_pin = enable_pin; class->change_pin = change_pin; + class->get_unlock_retries = get_unlock_retries; } static void @@ -2051,6 +3815,7 @@ modem_gsm_network_init (MMModemGsmNetwork *class) { class->do_register = do_register; class->get_registration_info = get_registration_info; + class->set_allowed_mode = set_allowed_mode; class->set_apn = set_apn; class->scan = scan; class->get_signal_quality = get_signal_quality; @@ -2072,6 +3837,22 @@ modem_simple_init (MMModemSimple *class) static void mm_generic_gsm_init (MMGenericGsm *self) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + priv->act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + priv->reg_regex = mm_gsm_creg_regex_get (TRUE); + priv->roam_allowed = TRUE; + + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_GSM_NETWORK_ALLOWED_MODE, + MM_MODEM_GSM_NETWORK_DBUS_INTERFACE); + + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY, + MM_MODEM_GSM_NETWORK_DBUS_INTERFACE); + + g_signal_connect (self, "notify::" MM_MODEM_STATE, + G_CALLBACK (modem_state_changed), NULL); } static void @@ -2086,6 +3867,8 @@ set_property (GObject *object, guint prop_id, case MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL: case MM_GENERIC_GSM_PROP_SUPPORTED_BANDS: case MM_GENERIC_GSM_PROP_SUPPORTED_MODES: + case MM_GENERIC_GSM_PROP_ALLOWED_MODE: + case MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY: break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -2123,7 +3906,7 @@ get_property (GObject *object, guint prop_id, g_value_set_string (value, ""); break; case MM_GENERIC_GSM_PROP_INIT_CMD: - g_value_set_string (value, "Z E0 V1 +CMEE=1"); + g_value_set_string (value, "Z E0 V1"); break; case MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL: g_value_set_string (value, "X4 &C1"); @@ -2134,6 +3917,15 @@ get_property (GObject *object, guint prop_id, case MM_GENERIC_GSM_PROP_SUPPORTED_MODES: g_value_set_uint (value, 0); break; + case MM_GENERIC_GSM_PROP_ALLOWED_MODE: + g_value_set_uint (value, priv->allowed_mode); + break; + case MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY: + if (mm_modem_get_state (MM_MODEM (object)) >= MM_MODEM_STATE_ENABLED) + g_value_set_uint (value, priv->act); + else + g_value_set_uint (value, MM_MODEM_GSM_ACCESS_TECH_UNKNOWN); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2147,6 +3939,23 @@ finalize (GObject *object) mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (object)); + if (priv->pin_check_timeout) { + g_source_remove (priv->pin_check_timeout); + priv->pin_check_timeout = 0; + } + + if (priv->poll_id) { + g_source_remove (priv->poll_id); + priv->poll_id = 0; + } + + if (priv->signal_quality_id) { + g_source_remove (priv->signal_quality_id); + priv->signal_quality_id = 0; + } + + mm_gsm_creg_regex_destroy (priv->reg_regex); + g_free (priv->oper_code); g_free (priv->oper_name); @@ -2167,6 +3976,8 @@ mm_generic_gsm_class_init (MMGenericGsmClass *klass) object_class->finalize = finalize; klass->do_enable = real_do_enable; + klass->do_enable_power_up_done = real_do_enable_power_up_done; + klass->do_disconnect = real_do_disconnect; /* Properties */ g_object_class_override_property (object_class, @@ -2185,6 +3996,14 @@ mm_generic_gsm_class_init (MMGenericGsmClass *klass) MM_GENERIC_GSM_PROP_SUPPORTED_MODES, MM_MODEM_GSM_CARD_SUPPORTED_MODES); + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_ALLOWED_MODE, + MM_MODEM_GSM_NETWORK_ALLOWED_MODE); + + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY, + MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY); + g_object_class_install_property (object_class, MM_GENERIC_GSM_PROP_POWER_UP_CMD, g_param_spec_string (MM_GENERIC_GSM_POWER_UP_CMD, diff --git a/src/mm-generic-gsm.h b/src/mm-generic-gsm.h index de0b00b..de9dc89 100644 --- a/src/mm-generic-gsm.h +++ b/src/mm-generic-gsm.h @@ -11,7 +11,7 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #ifndef MM_GENERIC_GSM_H @@ -20,8 +20,9 @@ #include "mm-modem-gsm.h" #include "mm-modem-gsm-network.h" #include "mm-modem-base.h" -#include "mm-serial-port.h" +#include "mm-at-serial-port.h" #include "mm-callback-info.h" +#include "mm-charsets.h" #define MM_TYPE_GENERIC_GSM (mm_generic_gsm_get_type ()) #define MM_GENERIC_GSM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_GENERIC_GSM, MMGenericGsm)) @@ -43,9 +44,16 @@ typedef enum { MM_GENERIC_GSM_PROP_INIT_CMD, MM_GENERIC_GSM_PROP_SUPPORTED_BANDS, MM_GENERIC_GSM_PROP_SUPPORTED_MODES, - MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL + MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL, + MM_GENERIC_GSM_PROP_ALLOWED_MODE, + MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY } MMGenericGsmProp; +typedef enum { + MM_GENERIC_GSM_REG_TYPE_UNKNOWN = 0, + MM_GENERIC_GSM_REG_TYPE_CS = 1, + MM_GENERIC_GSM_REG_TYPE_PS = 2 +} MMGenericGsmRegType; typedef struct { MMModemBase parent; @@ -59,9 +67,49 @@ typedef struct { * that need to perform custom initialization sequences or other setup should * generally override this method instead of the MMModem interface's enable() * method, unless the customization must happen *after* the generic init - * sequence has completed. + * sequence has completed. When the subclass' enable attempt is complete + * the subclass should call mm_generic_gsm_enable_complete() with any error + * encountered during the process and the MMCallbackInfo created from the + * callback and user_data passed in here. */ void (*do_enable) (MMGenericGsm *self, MMModemFn callback, gpointer user_data); + + /* Called after the generic class has attempted to power up the modem. + * Subclasses can handle errors here if they know the device supports their + * power up command. Will only be called if the device does *not* override + * the MMModem enable() command or allows the generic class' do_enable() + * handler to execute. + */ + void (*do_enable_power_up_done) (MMGenericGsm *self, + GString *response, + GError *error, + MMCallbackInfo *info); + + /* Called to terminate the active data call and deactivate the given PDP + * context. + */ + void (*do_disconnect) (MMGenericGsm *self, + gint cid, + MMModemFn callback, + gpointer user_data); + + /* Called by the generic class to set the allowed operating mode of the device */ + void (*set_allowed_mode) (MMGenericGsm *self, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data); + + /* Called by the generic class to get the allowed operating mode of the device */ + void (*get_allowed_mode) (MMGenericGsm *self, + MMModemUIntFn callback, + gpointer user_data); + + /* Called by the generic class to the current radio access technology the + * device is using while communicating with the base station. + */ + void (*get_access_technology) (MMGenericGsm *self, + MMModemUIntFn callback, + gpointer user_data); } MMGenericGsmClass; GType mm_generic_gsm_get_type (void); @@ -74,24 +122,42 @@ MMModem *mm_generic_gsm_new (const char *device, #define MM_GENERIC_GSM_PREV_STATE_TAG "prev-state" -void mm_generic_gsm_set_unsolicited_registration (MMGenericGsm *modem, - gboolean enabled); - -void mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem); +void mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem); -void mm_generic_gsm_set_cid (MMGenericGsm *modem, - guint32 cid); +gint mm_generic_gsm_get_cid (MMGenericGsm *modem); -guint32 mm_generic_gsm_get_cid (MMGenericGsm *modem); void mm_generic_gsm_set_reg_status (MMGenericGsm *modem, + MMGenericGsmRegType reg_type, MMModemGsmNetworkRegStatus status); -void mm_generic_gsm_check_pin (MMGenericGsm *modem, - MMModemFn callback, - gpointer user_data); +MMModemCharset mm_generic_gsm_get_charset (MMGenericGsm *modem); + +/* Called to asynchronously update the current allowed operating mode that the + * device is allowed to use when connecting to a network. This isn't the + * specific access technology the device is currently using (see + * mm_generic_gsm_set_access_technology() for that) but the mode the device is + * allowed to choose from when connecting. + */ +void mm_generic_gsm_update_allowed_mode (MMGenericGsm *modem, + MMModemGsmAllowedMode mode); + +/* Called to asynchronously update the current access technology of the device; + * this is NOT the 2G/3G mode preference, but the current radio access + * technology being used to communicate with the base station. + */ +void mm_generic_gsm_update_access_technology (MMGenericGsm *modem, + MMModemGsmAccessTech act); + +/* Called to asynchronously update the current signal quality of the device; + * 'quality' is a 0 - 100% quality. + */ +void mm_generic_gsm_update_signal_quality (MMGenericGsm *modem, guint32 quality); + +MMAtSerialPort *mm_generic_gsm_get_at_port (MMGenericGsm *modem, + MMPortType ptype); -MMSerialPort *mm_generic_gsm_get_port (MMGenericGsm *modem, - MMPortType ptype); +MMAtSerialPort *mm_generic_gsm_get_best_at_port (MMGenericGsm *modem, + GError **error); MMPort *mm_generic_gsm_grab_port (MMGenericGsm *modem, const char *subsys, diff --git a/src/mm-manager.c b/src/mm-manager.c index 1a93170..1dd1902 100644 --- a/src/mm-manager.c +++ b/src/mm-manager.c @@ -11,10 +11,11 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #include <string.h> +#include <ctype.h> #include <gmodule.h> #define G_UDEV_API_IS_SUBJECT_TO_CHANGE #include <gudev/gudev.h> @@ -52,6 +53,21 @@ typedef struct { GHashTable *supports; } MMManagerPrivate; +typedef struct { + MMManager *manager; + char *subsys; + char *name; + char *physdev_path; + GSList *plugins; + GSList *cur_plugin; + guint defer_id; + guint done_id; + + guint32 best_level; + MMPlugin *best_plugin; +} SupportsInfo; + + static MMPlugin * load_plugin (const char *path) { @@ -188,44 +204,98 @@ remove_modem (MMManager *manager, MMModem *modem) } static void -modem_valid (MMModem *modem, GParamSpec *pspec, gpointer user_data) +check_export_modem (MMManager *self, MMModem *modem) { - MMManager *manager = MM_MANAGER (user_data); - MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); - static guint32 id = 0; - char *path, *device; + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (self); + char *modem_physdev; + GHashTableIter iter; + gpointer value; + + /* A modem is only exported to D-Bus when both of the following are true: + * + * 1) the modem is valid + * 2) all ports the modem provides have either been grabbed or are + * unsupported by any plugin + * + * This ensures that all the modem's ports are completely ready before + * any clients can do anything with it. + * + * FIXME: if udev or the kernel are really slow giving us ports, there's a + * chance that a port could show up after the modem is already created and + * all other ports are already handled. That chance is very small though. + */ + modem_physdev = mm_modem_get_device (modem); + g_assert (modem_physdev); + + /* Check for ports that are in the process of being interrogated by plugins */ + g_hash_table_iter_init (&iter, priv->supports); + while (g_hash_table_iter_next (&iter, NULL, &value)) { + SupportsInfo *info = value; + + if (!strcmp (info->physdev_path, modem_physdev)) { + g_debug ("(%s/%s): outstanding support task prevents export of %s", + info->subsys, info->name, modem_physdev); + goto out; + } + } + + /* Already exported? This can happen if the modem is exported and the kernel + * discovers another of the modem's ports. + */ + if (g_object_get_data (G_OBJECT (modem), DBUS_PATH_TAG)) + goto out; + + /* No outstanding port tasks, so if the modem is valid we can export it */ if (mm_modem_get_valid (modem)) { + static guint32 id = 0; + char *path, *data_device = NULL; + path = g_strdup_printf (MM_DBUS_PATH"/Modems/%d", id++); dbus_g_connection_register_g_object (priv->connection, path, G_OBJECT (modem)); g_object_set_data_full (G_OBJECT (modem), DBUS_PATH_TAG, path, (GDestroyNotify) g_free); - device = mm_modem_get_device (modem); - g_assert (device); - g_debug ("Exported modem %s as %s", device, path); - g_free (device); + g_debug ("Exported modem %s as %s", modem_physdev, path); - g_signal_emit (manager, signals[DEVICE_ADDED], 0, modem); - } else + g_object_get (G_OBJECT (modem), MM_MODEM_DATA_DEVICE, &data_device, NULL); + g_debug ("(%s): data port is %s", path, data_device); + g_free (data_device); + + g_signal_emit (self, signals[DEVICE_ADDED], 0, modem); + } + +out: + g_free (modem_physdev); +} + +static void +modem_valid (MMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + MMManager *manager = MM_MANAGER (user_data); + + if (mm_modem_get_valid (modem)) + check_export_modem (manager, modem); + else remove_modem (manager, modem); } +#define MANAGER_PLUGIN_TAG "manager-plugin" + static void -add_modem (MMManager *manager, MMModem *modem) +add_modem (MMManager *manager, MMModem *modem, MMPlugin *plugin) { MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); char *device; - gboolean valid = FALSE; device = mm_modem_get_device (modem); g_assert (device); if (!g_hash_table_lookup (priv->modems, device)) { g_hash_table_insert (priv->modems, g_strdup (device), modem); + g_object_set_data (G_OBJECT (modem), MANAGER_PLUGIN_TAG, plugin); + g_debug ("Added modem %s", device); - g_signal_connect (modem, "notify::valid", G_CALLBACK (modem_valid), manager); - g_object_get (modem, MM_MODEM_VALID, &valid, NULL); - if (valid) - modem_valid (modem, NULL, manager); + g_signal_connect (modem, "notify::" MM_MODEM_VALID, G_CALLBACK (modem_valid), manager); + check_export_modem (manager, modem); } g_free (device); } @@ -236,10 +306,8 @@ enumerate_devices_cb (gpointer key, gpointer val, gpointer user_data) MMModem *modem = MM_MODEM (val); GPtrArray **devices = (GPtrArray **) user_data; const char *path; - gboolean valid = FALSE; - g_object_get (G_OBJECT (modem), MM_MODEM_VALID, &valid, NULL); - if (valid) { + if (mm_modem_get_valid (modem)) { path = g_object_get_data (G_OBJECT (modem), DBUS_PATH_TAG); g_return_if_fail (path != NULL); g_ptr_array_add (*devices, g_strdup (path)); @@ -265,15 +333,18 @@ find_modem_for_device (MMManager *manager, const char *device) MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); GHashTableIter iter; gpointer key, value; + MMModem *found = NULL; g_hash_table_iter_init (&iter, priv->modems); - while (g_hash_table_iter_next (&iter, &key, &value)) { - MMModem *modem = MM_MODEM (value); + while (g_hash_table_iter_next (&iter, &key, &value) && !found) { + MMModem *candidate = MM_MODEM (value); + char *candidate_device = mm_modem_get_device (candidate); - if (!strcmp (device, mm_modem_get_device (modem))) - return modem; + if (!strcmp (device, candidate_device)) + found = candidate; + g_free (candidate_device); } - return NULL; + return found; } @@ -294,21 +365,11 @@ find_modem_for_port (MMManager *manager, const char *subsys, const char *name) return NULL; } -typedef struct { - MMManager *manager; - char *subsys; - char *name; - GSList *plugins; - GSList *cur_plugin; - guint defer_id; - guint done_id; - - guint32 best_level; - MMPlugin *best_plugin; -} SupportsInfo; - static SupportsInfo * -supports_info_new (MMManager *self, const char *subsys, const char *name) +supports_info_new (MMManager *self, + const char *subsys, + const char *name, + const char *physdev_path) { MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (self); SupportsInfo *info; @@ -317,6 +378,7 @@ supports_info_new (MMManager *self, const char *subsys, const char *name) info->manager = self; info->subsys = g_strdup (subsys); info->name = g_strdup (name); + info->physdev_path = g_strdup (physdev_path); info->plugins = g_slist_copy (priv->plugins); info->cur_plugin = info->plugins; return info; @@ -357,20 +419,21 @@ static void supports_callback (MMPlugin *plugin, static void try_supports_port (MMManager *manager, MMPlugin *plugin, - const char *subsys, - const char *name, + MMModem *existing, SupportsInfo *info); static gboolean supports_defer_timeout (gpointer user_data) { SupportsInfo *info = user_data; + MMModem *existing; + + existing = find_modem_for_device (info->manager, info->physdev_path); g_debug ("(%s): re-checking support...", info->name); try_supports_port (info->manager, MM_PLUGIN (info->cur_plugin->data), - info->subsys, - info->name, + existing, info); return FALSE; } @@ -378,23 +441,30 @@ supports_defer_timeout (gpointer user_data) static void try_supports_port (MMManager *manager, MMPlugin *plugin, - const char *subsys, - const char *name, + MMModem *existing, SupportsInfo *info) { MMPluginSupportsResult result; - result = mm_plugin_supports_port (plugin, subsys, name, supports_callback, info); + result = mm_plugin_supports_port (plugin, + info->subsys, + info->name, + info->physdev_path, + existing, + supports_callback, + info); switch (result) { case MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED: /* If the plugin knows it doesn't support the modem, just call the * callback and indicate 0 support. */ - supports_callback (plugin, subsys, name, 0, info); + supports_callback (plugin, info->subsys, info->name, 0, info); break; case MM_PLUGIN_SUPPORTS_PORT_DEFER: - g_debug ("(%s): (%s) deferring support check", mm_plugin_get_name (plugin), name); + g_debug ("(%s): (%s) deferring support check", + mm_plugin_get_name (plugin), + info->name); if (info->defer_id) g_source_remove (info->defer_id); @@ -407,23 +477,54 @@ try_supports_port (MMManager *manager, } } +static void +supports_cleanup (MMManager *self, + const char *subsys, + const char *name, + MMModem *modem) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (self); + char *key; + + g_return_if_fail (subsys != NULL); + g_return_if_fail (name != NULL); + + key = get_key (subsys, name); + g_hash_table_remove (priv->supports, key); + g_free (key); + + /* Each time a supports task is cleaned up, check whether the modem is + * now completely probed/handled and should be exported to D-Bus clients. + * + * IMPORTANT: this must be done after removing the supports into from + * priv->supports since check_export_modem() searches through priv->supports + * for outstanding supports tasks. + */ + if (modem) + check_export_modem (self, modem); +} + static gboolean do_grab_port (gpointer user_data) { SupportsInfo *info = user_data; MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (info->manager); - MMModem *modem; + MMModem *modem = NULL; GError *error = NULL; - char *key; GSList *iter; /* No more plugins to try */ if (info->best_plugin) { + MMModem *existing; + + existing = g_hash_table_lookup (priv->modems, info->physdev_path); + /* Create the modem */ - modem = mm_plugin_grab_port (info->best_plugin, info->subsys, info->name, &error); + modem = mm_plugin_grab_port (info->best_plugin, info->subsys, info->name, existing, &error); if (modem) { guint32 modem_type = MM_MODEM_TYPE_UNKNOWN; const char *type_name = "UNKNOWN"; + char *device;; g_object_get (G_OBJECT (modem), MM_MODEM_TYPE, &modem_type, NULL); if (modem_type == MM_MODEM_TYPE_GSM) @@ -431,13 +532,15 @@ do_grab_port (gpointer user_data) else if (modem_type == MM_MODEM_TYPE_CDMA) type_name = "CDMA"; + device = mm_modem_get_device (modem); g_message ("(%s): %s modem %s claimed port %s", mm_plugin_get_name (info->best_plugin), type_name, - mm_modem_get_device (modem), + device, info->name); + g_free (device); - add_modem (info->manager, modem); + add_modem (info->manager, modem, info->best_plugin); } else { g_warning ("%s: plugin '%s' claimed to support %s/%s but couldn't: (%d) %s", __func__, @@ -446,6 +549,7 @@ do_grab_port (gpointer user_data) info->name, error ? error->code : -1, (error && error->message) ? error->message : "(unknown)"); + modem = existing; } } @@ -455,10 +559,7 @@ do_grab_port (gpointer user_data) g_slist_free (info->plugins); info->cur_plugin = info->plugins = NULL; - key = get_key (info->subsys, info->name); - g_hash_table_remove (priv->supports, key); - g_free (key); - + supports_cleanup (info->manager, info->subsys, info->name, modem); return FALSE; } @@ -471,10 +572,7 @@ supports_callback (MMPlugin *plugin, { SupportsInfo *info = user_data; MMPlugin *next_plugin = NULL; - - info->cur_plugin = info->cur_plugin->next; - if (info->cur_plugin) - next_plugin = MM_PLUGIN (info->cur_plugin->data); + MMModem *existing; /* Is this plugin's result better than any one we've tried before? */ if (level > info->best_level) { @@ -482,32 +580,147 @@ supports_callback (MMPlugin *plugin, info->best_plugin = plugin; } - /* Prevent the generic plugin from probing devices that are already supported - * by other plugins. For Huawei for example, secondary ports shouldn't - * be probed, but the generic plugin would happily do so if allowed to. + /* If there's already a modem for this port's physical device, stop asking + * plugins because the same plugin that owns the modem gets this port no + * matter what. */ - if ( next_plugin - && !strcmp (mm_plugin_get_name (next_plugin), MM_PLUGIN_GENERIC_NAME) - && info->best_plugin) - next_plugin = NULL; + existing = find_modem_for_device (info->manager, info->physdev_path); + if (existing) { + MMPlugin *existing_plugin; + + existing_plugin = MM_PLUGIN (g_object_get_data (G_OBJECT (existing), MANAGER_PLUGIN_TAG)); + g_assert (existing_plugin); + + if (plugin == existing_plugin) { + if (level == 0) { + /* If the plugin that just completed the support check claims not to + * support this port, but this plugin is clearly the right plugin + * since it claimed this port's physical modem, just drop the port. + */ + g_debug ("(%s/%s): ignoring port unsupported by physical modem's plugin", + info->subsys, info->name); + supports_cleanup (info->manager, info->subsys, info->name, existing); + return; + } + + /* Otherwise, this port was supported by the plugin that owns the + * port's physical modem, so we stop the supports checks anyway. + */ + next_plugin = NULL; + } else if (info->best_plugin != existing_plugin) { + /* If this port hasn't yet been handled by the right plugin, stop + * asking all other plugins if they support this port, just let the + * plugin that handles this port's physical device see if it + * supports it. + */ + next_plugin = existing_plugin; + } else { + g_debug ("(%s/%s): plugin %p (%s) existing %p (%s) info->best %p (%s)", + info->subsys, info->name, + plugin, + plugin ? mm_plugin_get_name (plugin) : "none", + existing_plugin, + existing_plugin ? mm_plugin_get_name (existing_plugin) : "none", + info->best_plugin, + info->best_plugin ? mm_plugin_get_name (info->best_plugin) : "none"); + g_assert_not_reached (); + } + } else { + info->cur_plugin = g_slist_next (info->cur_plugin); + if (info->cur_plugin) + next_plugin = MM_PLUGIN (info->cur_plugin->data); + } + + /* Don't bother with Generic if some other plugin already supports this port */ + if (next_plugin) { + const char *next_name = mm_plugin_get_name (next_plugin); + + if (info->best_plugin && !strcmp (next_name, MM_PLUGIN_GENERIC_NAME)) + next_plugin = NULL; + } if (next_plugin) { /* Try the next plugin */ - try_supports_port (info->manager, next_plugin, info->subsys, info->name, info); + try_supports_port (info->manager, next_plugin, existing, info); } else { /* All done; let the best modem grab the port */ info->done_id = g_idle_add (do_grab_port, info); } } +static GUdevDevice * +find_physical_device (GUdevDevice *child) +{ + GUdevDevice *iter, *old = NULL; + GUdevDevice *physdev = NULL; + const char *subsys, *type; + guint32 i = 0; + gboolean is_usb = FALSE, is_pci = FALSE, is_pcmcia = FALSE, is_platform = FALSE; + + g_return_val_if_fail (child != NULL, NULL); + + iter = g_object_ref (child); + while (iter && i++ < 8) { + subsys = g_udev_device_get_subsystem (iter); + if (subsys) { + if (is_usb || !strcmp (subsys, "usb")) { + is_usb = TRUE; + type = g_udev_device_get_devtype (iter); + if (type && !strcmp (type, "usb_device")) { + physdev = iter; + break; + } + } else if (is_pcmcia || !strcmp (subsys, "pcmcia")) { + GUdevDevice *pcmcia_parent; + const char *tmp_subsys; + + is_pcmcia = TRUE; + + /* If the parent of this PCMCIA device is no longer part of + * the PCMCIA subsystem, we want to stop since we're looking + * for the base PCMCIA device, not the PCMCIA controller which + * is usually PCI or some other bus type. + */ + pcmcia_parent = g_udev_device_get_parent (iter); + if (pcmcia_parent) { + tmp_subsys = g_udev_device_get_subsystem (pcmcia_parent); + if (tmp_subsys && strcmp (tmp_subsys, "pcmcia")) + physdev = iter; + g_object_unref (pcmcia_parent); + if (physdev) + break; + } + } else if (is_platform || !strcmp (subsys, "platform")) { + /* Take the first platform parent as the physical device */ + is_platform = TRUE; + physdev = iter; + break; + } else if (is_pci || !strcmp (subsys, "pci")) { + is_pci = TRUE; + physdev = iter; + break; + } + } + + old = iter; + iter = g_udev_device_get_parent (old); + g_object_unref (old); + } + + return physdev; +} + static void device_added (MMManager *manager, GUdevDevice *device) { MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); - const char *subsys, *name; + const char *subsys, *name, *physdev_path, *physdev_subsys; SupportsInfo *info; char *key; gboolean found; + GUdevDevice *physdev = NULL; + MMPlugin *plugin; + MMModem *existing; g_return_if_fail (device != NULL); @@ -517,20 +730,77 @@ device_added (MMManager *manager, GUdevDevice *device) subsys = g_udev_device_get_subsystem (device); name = g_udev_device_get_name (device); + /* ignore VTs */ + if (strncmp (name, "tty", 3) == 0 && isdigit (name[3])) + return; + if (find_modem_for_port (manager, subsys, name)) return; key = get_key (subsys, name); found = !!g_hash_table_lookup (priv->supports, key); - if (found) { - g_free (key); - return; + if (found) + goto out; + + /* Find the port's physical device's sysfs path. This is the kernel device + * that "owns" all the ports of the device, like the USB device or the PCI + * device the provides each tty or network port. + */ + physdev = find_physical_device (device); + if (!physdev) { + /* Warn about it, but filter out some common ports that we know don't have + * anything to do with mobile broadband. + */ + if ( strcmp (name, "console") + && strcmp (name, "ptmx") + && strcmp (name, "lo") + && strcmp (name, "tty") + && !strstr (name, "virbr")) + g_debug ("(%s/%s): could not get port's parent device", subsys, name); + + goto out; + } + + /* Is the device blacklisted? */ + if (g_udev_device_get_property_as_boolean (physdev, "ID_MM_DEVICE_IGNORE")) { + g_debug ("(%s/%s): port's parent device is blacklisted", subsys, name); + goto out; + } + + /* If the physdev is a 'platform' device that's not whitelisted, ignore it */ + physdev_subsys = g_udev_device_get_subsystem (physdev); + if ( physdev_subsys + && !strcmp (physdev_subsys, "platform") + && !g_udev_device_get_property_as_boolean (physdev, "ID_MM_PLATFORM_DRIVER_PROBE")) { + g_debug ("(%s/%s): port's parent platform driver is not whitelisted", subsys, name); + goto out; } - info = supports_info_new (manager, subsys, name); - g_hash_table_insert (priv->supports, key, info); + physdev_path = g_udev_device_get_sysfs_path (physdev); + if (!physdev_path) { + g_debug ("(%s/%s): could not get port's parent device sysfs path", subsys, name); + goto out; + } + + /* Success; now ask plugins if they can handle this port */ + info = supports_info_new (manager, subsys, name, physdev_path); + g_hash_table_insert (priv->supports, g_strdup (key), info); + + /* If this port's physical modem is already owned by a plugin, don't bother + * asking all plugins whether they support this port, just let the owning + * plugin check if it supports the port. + */ + plugin = MM_PLUGIN (info->cur_plugin->data); + existing = find_modem_for_device (manager, physdev_path); + if (existing) + plugin = MM_PLUGIN (g_object_get_data (G_OBJECT (existing), MANAGER_PLUGIN_TAG)); - try_supports_port (manager, MM_PLUGIN (info->cur_plugin->data), subsys, name, info); + try_supports_port (manager, plugin, existing, info); + +out: + if (physdev) + g_object_unref (physdev); + g_free (key); } static void @@ -628,12 +898,83 @@ mm_manager_start (MMManager *manager) priv = MM_MANAGER_GET_PRIVATE (manager); devices = g_udev_client_query_by_subsystem (priv->udev, "tty"); - for (iter = devices; iter; iter = g_list_next (iter)) + for (iter = devices; iter; iter = g_list_next (iter)) { device_added (manager, G_UDEV_DEVICE (iter->data)); + g_object_unref (G_OBJECT (iter->data)); + } + g_list_free (devices); devices = g_udev_client_query_by_subsystem (priv->udev, "net"); - for (iter = devices; iter; iter = g_list_next (iter)) + for (iter = devices; iter; iter = g_list_next (iter)) { device_added (manager, G_UDEV_DEVICE (iter->data)); + g_object_unref (G_OBJECT (iter->data)); + } + g_list_free (devices); +} + +typedef struct { + MMManager *manager; + MMModem *modem; +} RemoveInfo; + +static gboolean +remove_disable_one (gpointer user_data) +{ + RemoveInfo *info = user_data; + + remove_modem (info->manager, info->modem); + g_free (info); + return FALSE; +} + +static void +remove_disable_done (MMModem *modem, + GError *error, + gpointer user_data) +{ + RemoveInfo *info; + + /* Schedule modem removal from an idle handler since we get here deep + * in the modem removal callchain and can't remove it quite yet from here. + */ + info = g_malloc0 (sizeof (RemoveInfo)); + info->manager = MM_MANAGER (user_data); + info->modem = modem; + g_idle_add (remove_disable_one, info); +} + +void +mm_manager_shutdown (MMManager *self) +{ + GList *modems, *iter; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MANAGER (self)); + + modems = g_hash_table_get_values (MM_MANAGER_GET_PRIVATE (self)->modems); + for (iter = modems; iter; iter = g_list_next (iter)) { + MMModem *modem = MM_MODEM (iter->data); + + if (mm_modem_get_state (modem) >= MM_MODEM_STATE_ENABLING) + mm_modem_disable (modem, remove_disable_done, self); + else + remove_disable_done (modem, NULL, self); + } + g_list_free (modems); + + /* Disabling may take a few iterations of the mainloop, so the caller + * has to iterate the mainloop until all devices have been disabled and + * removed. + */ +} + +guint32 +mm_manager_num_modems (MMManager *self) +{ + g_return_val_if_fail (self != NULL, 0); + g_return_val_if_fail (MM_IS_MANAGER (self), 0); + + return g_hash_table_size (MM_MANAGER_GET_PRIVATE (self)->modems); } static void diff --git a/src/mm-manager.h b/src/mm-manager.h index 5220b77..1c98458 100644 --- a/src/mm-manager.h +++ b/src/mm-manager.h @@ -50,4 +50,8 @@ MMManager *mm_manager_new (DBusGConnection *bus); void mm_manager_start (MMManager *manager); +void mm_manager_shutdown (MMManager *manager); + +guint32 mm_manager_num_modems (MMManager *manager); + #endif /* MM_MANAGER_H */ diff --git a/src/mm-modem-base.c b/src/mm-modem-base.c index 3d82f8e..0c39d2c 100644 --- a/src/mm-modem-base.c +++ b/src/mm-modem-base.c @@ -21,10 +21,13 @@ #include "mm-modem-base.h" #include "mm-modem.h" -#include "mm-serial-port.h" +#include "mm-at-serial-port.h" +#include "mm-qcdm-serial-port.h" #include "mm-errors.h" #include "mm-options.h" #include "mm-properties-changed-signal.h" +#include "mm-callback-info.h" +#include "mm-modem-helpers.h" static void modem_init (MMModem *modem_class); @@ -39,10 +42,19 @@ typedef struct { char *driver; char *plugin; char *device; + char *equipment_ident; + char *unlock_required; + guint32 unlock_retries; guint32 ip_method; gboolean valid; MMModemState state; + char *manf; + char *model; + char *revision; + + MMAuthProvider *authp; + GHashTable *ports; } MMModemBasePrivate; @@ -92,7 +104,7 @@ mm_modem_base_add_port (MMModemBase *self, { MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); MMPort *port = NULL; - char *key; + char *key, *device; g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); g_return_val_if_fail (subsys != NULL, NULL); @@ -111,9 +123,12 @@ mm_modem_base_add_port (MMModemBase *self, g_return_val_if_fail (port == NULL, FALSE); } - if (!strcmp (subsys, "tty")) - port = MM_PORT (mm_serial_port_new (name, ptype)); - else if (!strcmp (subsys, "net")) { + if (!strcmp (subsys, "tty")) { + if (ptype == MM_PORT_TYPE_QCDM) + port = MM_PORT (mm_qcdm_serial_port_new (name, ptype)); + else + port = MM_PORT (mm_at_serial_port_new (name, ptype)); + } else if (!strcmp (subsys, "net")) { port = MM_PORT (g_object_new (MM_TYPE_PORT, MM_PORT_DEVICE, name, MM_PORT_SUBSYS, MM_PORT_SUBSYS_NET, @@ -124,6 +139,15 @@ mm_modem_base_add_port (MMModemBase *self, if (!port) return NULL; + if (mm_options_debug ()) { + device = mm_modem_get_device (MM_MODEM (self)); + + g_message ("(%s) type %s claimed by %s", + name, + mm_port_type_to_name (ptype), + device); + g_free (device); + } key = get_hash_key (subsys, name); g_hash_table_insert (priv->ports, key, port); return port; @@ -169,6 +193,362 @@ mm_modem_base_get_valid (MMModemBase *self) return MM_MODEM_BASE_GET_PRIVATE (self)->valid; } +const char * +mm_modem_base_get_equipment_identifier (MMModemBase *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); + + return MM_MODEM_BASE_GET_PRIVATE (self)->equipment_ident; +} + +void +mm_modem_base_set_equipment_identifier (MMModemBase *self, const char *ident) +{ + MMModemBasePrivate *priv; + const char *dbus_path; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + + /* Only do something if the value changes */ + if ( (priv->equipment_ident == ident) + || (priv->equipment_ident && ident && !strcmp (priv->equipment_ident, ident))) + return; + + g_free (priv->equipment_ident); + priv->equipment_ident = g_strdup (ident); + + dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG); + if (dbus_path) { + if (priv->equipment_ident) + g_message ("Modem %s: Equipment identifier set (%s)", dbus_path, priv->equipment_ident); + else + g_message ("Modem %s: Equipment identifier not set", dbus_path); + } + + g_object_notify (G_OBJECT (self), MM_MODEM_EQUIPMENT_IDENTIFIER); +} + +const char * +mm_modem_base_get_unlock_required (MMModemBase *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); + + return MM_MODEM_BASE_GET_PRIVATE (self)->unlock_required; +} + +void +mm_modem_base_set_unlock_required (MMModemBase *self, const char *unlock_required) +{ + MMModemBasePrivate *priv; + const char *dbus_path; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + + /* Only do something if the value changes */ + if ( (priv->unlock_required == unlock_required) + || ( priv->unlock_required + && unlock_required + && !strcmp (priv->unlock_required, unlock_required))) + return; + + g_free (priv->unlock_required); + priv->unlock_required = g_strdup (unlock_required); + + dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG); + if (dbus_path) { + if (priv->unlock_required) + g_message ("Modem %s: unlock required (%s)", dbus_path, priv->unlock_required); + else + g_message ("Modem %s: unlock no longer required", dbus_path); + } + + g_object_notify (G_OBJECT (self), MM_MODEM_UNLOCK_REQUIRED); +} + +guint32 +mm_modem_base_get_unlock_retries (MMModemBase *self) +{ + g_return_val_if_fail (self != NULL, 0); + g_return_val_if_fail (MM_IS_MODEM_BASE (self), 0); + + return MM_MODEM_BASE_GET_PRIVATE (self)->unlock_retries; +} + +void +mm_modem_base_set_unlock_retries (MMModemBase *self, guint unlock_retries) +{ + MMModemBasePrivate *priv; + const char *dbus_path; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + + /* Only do something if the value changes */ + if (priv->unlock_retries == unlock_retries) + return; + + priv->unlock_retries = unlock_retries; + + dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG); + if (dbus_path) { + g_message ("Modem %s: # unlock retries for %s is %d", + dbus_path, priv->unlock_required, priv->unlock_retries); + } + + g_object_notify (G_OBJECT (self), MM_MODEM_UNLOCK_RETRIES); +} + +const char * +mm_modem_base_get_manf (MMModemBase *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); + + return MM_MODEM_BASE_GET_PRIVATE (self)->manf; +} + +void +mm_modem_base_set_manf (MMModemBase *self, const char *manf) +{ + MMModemBasePrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + g_free (priv->manf); + priv->manf = g_strdup (manf); +} + +const char * +mm_modem_base_get_model (MMModemBase *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); + + return MM_MODEM_BASE_GET_PRIVATE (self)->model; +} + +void +mm_modem_base_set_model (MMModemBase *self, const char *model) +{ + MMModemBasePrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + g_free (priv->model); + priv->model = g_strdup (model); +} + +const char * +mm_modem_base_get_revision (MMModemBase *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); + + return MM_MODEM_BASE_GET_PRIVATE (self)->revision; +} + +void +mm_modem_base_set_revision (MMModemBase *self, const char *revision) +{ + MMModemBasePrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + g_free (priv->revision); + priv->revision = g_strdup (revision); +} + +/*************************************************************************/ +static void +card_info_simple_invoke (MMCallbackInfo *info) +{ + MMModemBase *self = MM_MODEM_BASE (info->modem); + MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); + MMModemInfoFn callback = (MMModemInfoFn) info->callback; + + callback (info->modem, priv->manf, priv->model, priv->revision, info->error, info->user_data); +} + +static void +card_info_cache_invoke (MMCallbackInfo *info) +{ + MMModemBase *self = MM_MODEM_BASE (info->modem); + MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); + MMModemInfoFn callback = (MMModemInfoFn) info->callback; + const char *manf, *cmanf, *model, *cmodel, *rev, *crev; + + manf = mm_callback_info_get_data (info, "card-info-manf"); + cmanf = mm_callback_info_get_data (info, "card-info-c-manf"); + + model = mm_callback_info_get_data (info, "card-info-model"); + cmodel = mm_callback_info_get_data (info, "card-info-c-model"); + + rev = mm_callback_info_get_data (info, "card-info-revision"); + crev = mm_callback_info_get_data (info, "card-info-c-revision"); + + /* Prefer the 'C' responses over the plain responses */ + g_free (priv->manf); + priv->manf = g_strdup (cmanf ? cmanf : manf); + g_free (priv->model); + priv->model = g_strdup (cmodel ? cmodel : model); + g_free (priv->revision); + priv->revision = g_strdup (crev ? crev : rev); + + callback (info->modem, priv->manf, priv->model, priv->revision, info->error, info->user_data); +} + +static void +info_item_done (MMCallbackInfo *info, + GString *response, + GError *error, + const char *tag, + const char *desc) +{ + const char *p; + + if (!error) { + p = mm_strip_tag (response->str, tag); + mm_callback_info_set_data (info, desc, strlen (p) ? g_strdup (p) : NULL, g_free); + } + + mm_callback_info_chain_complete_one (info); +} + +#define GET_INFO_RESP_FN(func_name, tag, desc) \ +static void \ +func_name (MMAtSerialPort *port, \ + GString *response, \ + GError *error, \ + gpointer user_data) \ +{ \ + info_item_done ((MMCallbackInfo *) user_data, response, error, tag , desc ); \ +} + +GET_INFO_RESP_FN(get_revision_done, "+GMR:", "card-info-revision") +GET_INFO_RESP_FN(get_model_done, "+GMM:", "card-info-model") +GET_INFO_RESP_FN(get_manf_done, "+GMI:", "card-info-manf") + +GET_INFO_RESP_FN(get_c_revision_done, "+CGMR:", "card-info-c-revision") +GET_INFO_RESP_FN(get_c_model_done, "+CGMM:", "card-info-c-model") +GET_INFO_RESP_FN(get_c_manf_done, "+CGMI:", "card-info-c-manf") + +void +mm_modem_base_get_card_info (MMModemBase *self, + MMAtSerialPort *port, + GError *port_error, + MMModemInfoFn callback, + gpointer user_data) +{ + MMModemBasePrivate *priv; + MMCallbackInfo *info; + MMModemState state; + gboolean cached = FALSE; + GError *error = port_error; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + g_return_if_fail (port != NULL); + g_return_if_fail (MM_IS_AT_SERIAL_PORT (port)); + g_return_if_fail (callback != NULL); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + + /* Cached info and errors schedule the callback immediately and do + * not hit up the card for it's model information. + */ + if (priv->manf || priv->model || priv->revision) + cached = TRUE; + else { + state = mm_modem_get_state (MM_MODEM (self)); + + if (port_error) + error = g_error_copy (port_error); + else if (state < MM_MODEM_STATE_ENABLING) { + error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "The modem is not enabled."); + } + } + + /* If we have cached info or an error, don't hit up the card */ + if (cached || error) { + info = mm_callback_info_new_full (MM_MODEM (self), + card_info_simple_invoke, + G_CALLBACK (callback), + user_data); + info->error = error; + mm_callback_info_schedule (info); + return; + } + + /* Otherwise, ask the card */ + info = mm_callback_info_new_full (MM_MODEM (self), + card_info_cache_invoke, + G_CALLBACK (callback), + user_data); + + mm_callback_info_chain_start (info, 6); + mm_at_serial_port_queue_command_cached (port, "+GMI", 3, get_manf_done, info); + mm_at_serial_port_queue_command_cached (port, "+GMM", 3, get_model_done, info); + mm_at_serial_port_queue_command_cached (port, "+GMR", 3, get_revision_done, info); + mm_at_serial_port_queue_command_cached (port, "+CGMI", 3, get_c_manf_done, info); + mm_at_serial_port_queue_command_cached (port, "+CGMM", 3, get_c_model_done, info); + mm_at_serial_port_queue_command_cached (port, "+CGMR", 3, get_c_revision_done, info); +} + +/*****************************************************************************/ + +static gboolean +modem_auth_request (MMModem *modem, + const char *authorization, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify, + GError **error) +{ + MMModemBase *self = MM_MODEM_BASE (modem); + MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); + + g_assert (priv->authp); + return !!mm_auth_provider_request_auth (priv->authp, + authorization, + G_OBJECT (self), + context, + callback, + callback_data, + notify, + error); +} + +static gboolean +modem_auth_finish (MMModem *modem, MMAuthRequest *req, GError **error) +{ + if (mm_auth_request_get_result (req) != MM_AUTH_RESULT_AUTHORIZED) { + g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_AUTHORIZATION_REQUIRED, + "This request requires the '%s' authorization", + mm_auth_request_get_authorization (req)); + return FALSE; + } + + return TRUE; +} + /*****************************************************************************/ static void @@ -176,16 +556,29 @@ mm_modem_base_init (MMModemBase *self) { MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); + priv->authp = mm_auth_provider_get (); + priv->ports = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); mm_properties_changed_signal_register_property (G_OBJECT (self), MM_MODEM_ENABLED, MM_MODEM_DBUS_INTERFACE); + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_EQUIPMENT_IDENTIFIER, + MM_MODEM_DBUS_INTERFACE); + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_UNLOCK_REQUIRED, + MM_MODEM_DBUS_INTERFACE); + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_UNLOCK_RETRIES, + MM_MODEM_DBUS_INTERFACE); } static void modem_init (MMModem *modem_class) { + modem_class->auth_request = modem_auth_request; + modem_class->auth_finish = modem_auth_finish; } static gboolean @@ -227,6 +620,9 @@ set_property (GObject *object, guint prop_id, case MM_MODEM_PROP_VALID: case MM_MODEM_PROP_TYPE: case MM_MODEM_PROP_ENABLED: + case MM_MODEM_PROP_EQUIPMENT_IDENTIFIER: + case MM_MODEM_PROP_UNLOCK_REQUIRED: + case MM_MODEM_PROP_UNLOCK_RETRIES: break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -268,6 +664,15 @@ get_property (GObject *object, guint prop_id, case MM_MODEM_PROP_ENABLED: g_value_set_boolean (value, is_enabled (priv->state)); break; + case MM_MODEM_PROP_EQUIPMENT_IDENTIFIER: + g_value_set_string (value, priv->equipment_ident); + break; + case MM_MODEM_PROP_UNLOCK_REQUIRED: + g_value_set_string (value, priv->unlock_required); + break; + case MM_MODEM_PROP_UNLOCK_RETRIES: + g_value_set_uint (value, priv->unlock_retries); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -280,10 +685,14 @@ finalize (GObject *object) MMModemBase *self = MM_MODEM_BASE (object); MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); + mm_auth_provider_cancel_for_owner (priv->authp, object); + g_hash_table_destroy (priv->ports); g_free (priv->driver); g_free (priv->plugin); g_free (priv->device); + g_free (priv->equipment_ident); + g_free (priv->unlock_required); G_OBJECT_CLASS (mm_modem_base_parent_class)->finalize (object); } @@ -336,6 +745,18 @@ mm_modem_base_class_init (MMModemBaseClass *klass) MM_MODEM_PROP_ENABLED, MM_MODEM_ENABLED); + g_object_class_override_property (object_class, + MM_MODEM_PROP_EQUIPMENT_IDENTIFIER, + MM_MODEM_EQUIPMENT_IDENTIFIER); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_UNLOCK_REQUIRED, + MM_MODEM_UNLOCK_REQUIRED); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_UNLOCK_RETRIES, + MM_MODEM_UNLOCK_RETRIES); + mm_properties_changed_signal_new (object_class); } diff --git a/src/mm-modem-base.h b/src/mm-modem-base.h index 9eb64ce..0409957 100644 --- a/src/mm-modem-base.h +++ b/src/mm-modem-base.h @@ -22,6 +22,8 @@ #include <glib-object.h> #include "mm-port.h" +#include "mm-at-serial-port.h" +#include "mm-modem.h" #define MM_TYPE_MODEM_BASE (mm_modem_base_get_type ()) #define MM_MODEM_BASE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_BASE, MMModemBase)) @@ -60,5 +62,36 @@ void mm_modem_base_set_valid (MMModemBase *self, gboolean mm_modem_base_get_valid (MMModemBase *self); +const char *mm_modem_base_get_equipment_identifier (MMModemBase *self); + +void mm_modem_base_set_equipment_identifier (MMModemBase *self, + const char *ident); + +const char *mm_modem_base_get_unlock_required (MMModemBase *self); + +void mm_modem_base_set_unlock_required (MMModemBase *self, + const char *unlock_required); + +guint mm_modem_base_get_unlock_retries (MMModemBase *self); + +void mm_modem_base_set_unlock_retries (MMModemBase *self, + guint unlock_retries); + + +const char *mm_modem_base_get_manf (MMModemBase *self); +void mm_modem_base_set_manf (MMModemBase *self, const char *manf); + +const char *mm_modem_base_get_model (MMModemBase *self); +void mm_modem_base_set_model (MMModemBase *self, const char *model); + +const char *mm_modem_base_get_revision (MMModemBase *self); +void mm_modem_base_set_revision (MMModemBase *self, const char *revision); + +void mm_modem_base_get_card_info (MMModemBase *self, + MMAtSerialPort *port, + GError *port_error, + MMModemInfoFn callback, + gpointer user_data); + #endif /* MM_MODEM_BASE_H */ diff --git a/src/mm-modem-cdma.c b/src/mm-modem-cdma.c index 112b93f..e80dc4e 100644 --- a/src/mm-modem-cdma.c +++ b/src/mm-modem-cdma.c @@ -20,6 +20,7 @@ #include "mm-errors.h" #include "mm-callback-info.h" #include "mm-marshal.h" +#include "mm-auth-provider.h" static void impl_modem_cdma_get_signal_quality (MMModemCdma *modem, DBusGMethodInvocation *context); static void impl_modem_cdma_get_esn (MMModemCdma *modem, DBusGMethodInvocation *context); @@ -188,10 +189,38 @@ mm_modem_cdma_get_esn (MMModemCdma *self, } static void -impl_modem_cdma_get_esn (MMModemCdma *modem, - DBusGMethodInvocation *context) +esn_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) { - mm_modem_cdma_get_esn (modem, str_call_done, context); + MMModemCdma *self = MM_MODEM_CDMA (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the ESN */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_cdma_get_esn (self, str_call_done, context); +} + +static void +impl_modem_cdma_get_esn (MMModemCdma *self, DBusGMethodInvocation *context) +{ + GError *error = NULL; + + /* Make sure the caller is authorized to get the ESN */ + if (!mm_modem_auth_request (MM_MODEM (self), + MM_AUTHORIZATION_DEVICE_INFO, + context, + esn_auth_cb, + NULL, + NULL, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } void diff --git a/src/mm-modem-gsm-card.c b/src/mm-modem-gsm-card.c index dea4590..e03b964 100644 --- a/src/mm-modem-gsm-card.c +++ b/src/mm-modem-gsm-card.c @@ -15,6 +15,7 @@ */ #include <dbus/dbus-glib.h> +#include <string.h> #include "mm-modem-gsm-card.h" #include "mm-errors.h" @@ -27,6 +28,9 @@ static void impl_gsm_modem_get_imei (MMModemGsmCard *modem, static void impl_gsm_modem_get_imsi (MMModemGsmCard *modem, DBusGMethodInvocation *context); +static void impl_gsm_modem_get_operator_id (MMModemGsmCard *modem, + DBusGMethodInvocation *context); + static void impl_gsm_modem_send_pin (MMModemGsmCard *modem, const char *pin, DBusGMethodInvocation *context); @@ -76,6 +80,19 @@ str_call_not_supported (MMModemGsmCard *self, } static void +uint_call_not_supported (MMModemGsmCard *self, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_uint_new (MM_MODEM (self), callback, user_data); + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + mm_callback_info_schedule (info); +} + +static void async_call_done (MMModem *modem, GError *error, gpointer user_data) { DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data; @@ -129,6 +146,35 @@ mm_modem_gsm_card_get_imsi (MMModemGsmCard *self, str_call_not_supported (self, callback, user_data); } +void mm_modem_gsm_card_get_unlock_retries (MMModemGsmCard *self, + const char *pin_type, + MMModemUIntFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_GSM_CARD (self)); + g_return_if_fail (pin_type != NULL); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_unlock_retries) + MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_unlock_retries (self, pin_type, callback, user_data); + else + uint_call_not_supported (self, callback, user_data); +} + +void +mm_modem_gsm_card_get_operator_id (MMModemGsmCard *self, + MMModemStringFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_GSM_CARD (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_operator_id) + MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_operator_id (self, callback, user_data); + else + str_call_not_supported (self, callback, user_data); +} + void mm_modem_gsm_card_send_puk (MMModemGsmCard *self, const char *puk, @@ -201,26 +247,213 @@ mm_modem_gsm_card_change_pin (MMModemGsmCard *self, /*****************************************************************************/ static void -impl_gsm_modem_get_imei (MMModemGsmCard *modem, - DBusGMethodInvocation *context) +imei_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) { - mm_modem_gsm_card_get_imei (modem, str_call_done, context); + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the IMEI */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_get_imei (self, str_call_done, context); +} + +static void +impl_gsm_modem_get_imei (MMModemGsmCard *modem, DBusGMethodInvocation *context) +{ + GError *error = NULL; + + /* Make sure the caller is authorized to get the IMEI */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_INFO, + context, + imei_auth_cb, + NULL, + NULL, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +imsi_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the IMSI */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_get_imsi (self, str_call_done, context); +} + +static void +impl_gsm_modem_get_imsi (MMModemGsmCard *modem, DBusGMethodInvocation *context) +{ + GError *error = NULL; + + /* Make sure the caller is authorized to get the IMSI */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_INFO, + context, + imsi_auth_cb, + NULL, + NULL, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +operator_id_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the operator id */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_get_operator_id (self, str_call_done, context); } static void -impl_gsm_modem_get_imsi (MMModemGsmCard *modem, +impl_gsm_modem_get_operator_id (MMModemGsmCard *modem, DBusGMethodInvocation *context) +{ + GError *error = NULL; + + /* Make sure the caller is authorized to get the operator id */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_INFO, + context, + operator_id_auth_cb, + NULL, + NULL, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +typedef struct { + char *puk; + char *pin; + char *pin2; + gboolean enabled; +} SendPinPukInfo; + +static void +send_pin_puk_info_destroy (gpointer data) +{ + SendPinPukInfo *info = data; + + g_free (info->puk); + g_free (info->pin); + g_free (info->pin2); + memset (info, 0, sizeof (SendPinPukInfo)); + g_free (info); +} + +static SendPinPukInfo * +send_pin_puk_info_new (const char *puk, + const char *pin, + const char *pin2, + gboolean enabled) +{ + SendPinPukInfo *info; + + info = g_malloc0 (sizeof (SendPinPukInfo)); + info->puk = g_strdup (puk); + info->pin = g_strdup (pin); + info->pin2 = g_strdup (pin2); + info->enabled = enabled; + return info; +} + +/*****************************************************************************/ + +static void +send_puk_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + SendPinPukInfo *info = user_data; + GError *error = NULL; + + /* Return any authorization error, otherwise send the PUK */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_send_puk (self, info->puk, info->pin, async_call_done, context); +} + +static void +impl_gsm_modem_send_puk (MMModemGsmCard *modem, + const char *puk, + const char *pin, DBusGMethodInvocation *context) { - mm_modem_gsm_card_get_imsi (modem, str_call_done, context); + GError *error = NULL; + SendPinPukInfo *info; + + info = send_pin_puk_info_new (puk, pin, NULL, FALSE); + + /* Make sure the caller is authorized to send the PUK */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_CONTROL, + context, + send_puk_auth_cb, + info, + send_pin_puk_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } +/*****************************************************************************/ + static void - impl_gsm_modem_send_puk (MMModemGsmCard *modem, - const char *puk, - const char *pin, - DBusGMethodInvocation *context) +send_pin_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) { - mm_modem_gsm_card_send_puk (modem, puk, pin, async_call_done, context); + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + SendPinPukInfo *info = user_data; + GError *error = NULL; + + /* Return any authorization error, otherwise unlock the modem */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_send_pin (self, info->pin, async_call_done, context); } static void @@ -228,7 +461,42 @@ impl_gsm_modem_send_pin (MMModemGsmCard *modem, const char *pin, DBusGMethodInvocation *context) { - mm_modem_gsm_card_send_pin (modem, pin, async_call_done, context); + GError *error = NULL; + SendPinPukInfo *info; + + info = send_pin_puk_info_new (NULL, pin, NULL, FALSE); + + /* Make sure the caller is authorized to unlock the modem */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_CONTROL, + context, + send_pin_auth_cb, + info, + send_pin_puk_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +enable_pin_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + SendPinPukInfo *info = user_data; + GError *error = NULL; + + /* Return any authorization error, otherwise enable the PIN */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_enable_pin (self, info->pin, info->enabled, async_call_done, context); } static void @@ -237,7 +505,42 @@ impl_gsm_modem_enable_pin (MMModemGsmCard *modem, gboolean enabled, DBusGMethodInvocation *context) { - mm_modem_gsm_card_enable_pin (modem, pin, enabled, async_call_done, context); + GError *error = NULL; + SendPinPukInfo *info; + + info = send_pin_puk_info_new (NULL, pin, NULL, enabled); + + /* Make sure the caller is authorized to enable a PIN */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_CONTROL, + context, + enable_pin_auth_cb, + info, + send_pin_puk_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +change_pin_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + SendPinPukInfo *info = user_data; + GError *error = NULL; + + /* Return any authorization error, otherwise change the PIN */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_change_pin (self, info->pin, info->pin2, async_call_done, context); } static void @@ -246,7 +549,22 @@ impl_gsm_modem_change_pin (MMModemGsmCard *modem, const char *new_pin, DBusGMethodInvocation *context) { - mm_modem_gsm_card_change_pin (modem, old_pin, new_pin, async_call_done, context); + GError *error = NULL; + SendPinPukInfo *info; + + info = send_pin_puk_info_new (NULL, old_pin, new_pin, FALSE); + + /* Make sure the caller is authorized to change the PIN */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_CONTROL, + context, + change_pin_auth_cb, + info, + send_pin_puk_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } /*****************************************************************************/ @@ -267,7 +585,7 @@ mm_modem_gsm_card_init (gpointer g_iface) "Supported Modes", "Supported frequency bands of the card", MM_MODEM_GSM_BAND_UNKNOWN, - MM_MODEM_GSM_BAND_LAST, + G_MAXUINT32, MM_MODEM_GSM_BAND_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); @@ -277,7 +595,7 @@ mm_modem_gsm_card_init (gpointer g_iface) "Supported Modes", "Supported modes of the card (ex 2G preferred, 3G preferred, 2G only, etc", MM_MODEM_GSM_MODE_UNKNOWN, - MM_MODEM_GSM_MODE_LAST, + G_MAXUINT32, MM_MODEM_GSM_MODE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } @@ -305,6 +623,7 @@ mm_modem_gsm_card_get_type (void) &card_info, 0); g_type_interface_add_prerequisite (card_type, G_TYPE_OBJECT); + g_type_interface_add_prerequisite (card_type, MM_TYPE_MODEM); dbus_g_object_type_install_info (card_type, &dbus_glib_mm_modem_gsm_card_object_info); } diff --git a/src/mm-modem-gsm-card.h b/src/mm-modem-gsm-card.h index 4d690e6..584d734 100644 --- a/src/mm-modem-gsm-card.h +++ b/src/mm-modem-gsm-card.h @@ -27,6 +27,13 @@ #define MM_MODEM_GSM_CARD_SUPPORTED_BANDS "supported-bands" #define MM_MODEM_GSM_CARD_SUPPORTED_MODES "supported-modes" +#define MM_MODEM_GSM_CARD_SIM_PIN "sim-pin" +#define MM_MODEM_GSM_CARD_SIM_PIN2 "sim-pin2" +#define MM_MODEM_GSM_CARD_SIM_PUK "sim-puk" +#define MM_MODEM_GSM_CARD_SIM_PUK2 "sim-puk2" + +#define MM_MODEM_GSM_CARD_UNLOCK_RETRIES_NOT_SUPPORTED 999 + typedef struct _MMModemGsmCard MMModemGsmCard; struct _MMModemGsmCard { @@ -41,6 +48,15 @@ struct _MMModemGsmCard { MMModemStringFn callback, gpointer user_data); + void (*get_unlock_retries) (MMModemGsmCard *self, + const char *pin_type, + MMModemUIntFn callback, + gpointer user_data); + + void (*get_operator_id) (MMModemGsmCard *self, + MMModemStringFn callback, + gpointer user_data); + void (*send_puk) (MMModemGsmCard *self, const char *puk, const char *pin, @@ -75,6 +91,15 @@ void mm_modem_gsm_card_get_imsi (MMModemGsmCard *self, MMModemStringFn callback, gpointer user_data); +void mm_modem_gsm_card_get_unlock_retries (MMModemGsmCard *self, + const char *pin_type, + MMModemUIntFn callback, + gpointer user_data); + +void mm_modem_gsm_card_get_operator_id (MMModemGsmCard *self, + MMModemStringFn callback, + gpointer user_data); + void mm_modem_gsm_card_send_puk (MMModemGsmCard *self, const char *puk, const char *pin, diff --git a/src/mm-modem-gsm-network.c b/src/mm-modem-gsm-network.c index 26ff422..4cd6921 100644 --- a/src/mm-modem-gsm-network.c +++ b/src/mm-modem-gsm-network.c @@ -11,6 +11,7 @@ * GNU General Public License for more details: * * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2010 Red Hat, Inc. */ #include <string.h> @@ -42,8 +43,12 @@ static void impl_gsm_modem_set_band (MMModemGsmNetwork *modem, static void impl_gsm_modem_get_band (MMModemGsmNetwork *modem, DBusGMethodInvocation *context); +static void impl_gsm_modem_set_allowed_mode (MMModemGsmNetwork *modem, + MMModemGsmAllowedMode mode, + DBusGMethodInvocation *context); + static void impl_gsm_modem_set_network_mode (MMModemGsmNetwork *modem, - MMModemGsmMode mode, + MMModemDeprecatedMode old_mode, DBusGMethodInvocation *context); static void impl_gsm_modem_get_network_mode (MMModemGsmNetwork *modem, @@ -68,6 +73,47 @@ static guint signals[LAST_SIGNAL] = { 0 }; /*****************************************************************************/ +MMModemGsmAllowedMode +mm_modem_gsm_network_old_mode_to_allowed (MMModemDeprecatedMode old_mode) +{ + /* Translate deprecated mode into new mode */ + switch (old_mode) { + case MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_2G_PREFERRED: + return MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED; + case MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_3G_PREFERRED: + return MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED; + case MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_2G_ONLY: + return MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY; + case MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_3G_ONLY: + return MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY; + case MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_ANY: + default: + return MM_MODEM_GSM_ALLOWED_MODE_ANY; + } +} + +MMModemDeprecatedMode +mm_modem_gsm_network_act_to_old_mode (MMModemGsmAccessTech act) +{ + /* Translate new mode into old deprecated mode */ + if (act & MM_MODEM_GSM_ACCESS_TECH_GPRS) + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_GPRS; + else if (act & MM_MODEM_GSM_ACCESS_TECH_EDGE) + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_EDGE; + else if (act & MM_MODEM_GSM_ACCESS_TECH_UMTS) + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_UMTS; + else if (act & MM_MODEM_GSM_ACCESS_TECH_HSDPA) + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSDPA; + else if (act & MM_MODEM_GSM_ACCESS_TECH_HSUPA) + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSUPA; + else if (act & MM_MODEM_GSM_ACCESS_TECH_HSPA) + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSPA; + + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_ANY; +} + +/*****************************************************************************/ + static void async_call_done (MMModem *modem, GError *error, gpointer user_data) { @@ -305,35 +351,21 @@ mm_modem_gsm_network_get_band (MMModemGsmNetwork *self, } void -mm_modem_gsm_network_set_mode (MMModemGsmNetwork *self, - MMModemGsmMode mode, - MMModemFn callback, - gpointer user_data) +mm_modem_gsm_network_set_allowed_mode (MMModemGsmNetwork *self, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data) { g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self)); g_return_if_fail (callback != NULL); - if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_network_mode) - MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_network_mode (self, mode, callback, user_data); + if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_allowed_mode) + MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_allowed_mode (self, mode, callback, user_data); else async_call_not_supported (self, callback, user_data); } void -mm_modem_gsm_network_get_mode (MMModemGsmNetwork *self, - MMModemUIntFn callback, - gpointer user_data) -{ - g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self)); - g_return_if_fail (callback != NULL); - - if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_network_mode) - MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_network_mode (self, callback, user_data); - else - uint_call_not_supported (self, callback, user_data); -} - -void mm_modem_gsm_network_get_registration_info (MMModemGsmNetwork *self, MMModemGsmNetworkRegInfoFn callback, gpointer user_data) @@ -369,15 +401,6 @@ mm_modem_gsm_network_registration_info (MMModemGsmNetwork *self, oper_name ? oper_name : ""); } -void -mm_modem_gsm_network_mode (MMModemGsmNetwork *self, - MMModemGsmMode mode) -{ - g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self)); - - g_signal_emit (self, signals[NETWORK_MODE], 0, mode); -} - /*****************************************************************************/ static void @@ -398,10 +421,39 @@ impl_gsm_modem_register (MMModemGsmNetwork *modem, } static void +scan_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmNetwork *self = MM_MODEM_GSM_NETWORK (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the IMEI */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_network_scan (self, scan_call_done, context); +} + +static void impl_gsm_modem_scan (MMModemGsmNetwork *modem, DBusGMethodInvocation *context) { - mm_modem_gsm_network_scan (modem, scan_call_done, context); + GError *error = NULL; + + /* Make sure the caller is authorized to request a scan */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_CONTROL, + context, + scan_auth_cb, + NULL, + NULL, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } static void @@ -464,10 +516,10 @@ impl_gsm_modem_get_band (MMModemGsmNetwork *modem, static void impl_gsm_modem_set_network_mode (MMModemGsmNetwork *modem, - MMModemGsmMode mode, + MMModemDeprecatedMode old_mode, DBusGMethodInvocation *context) { - if (!check_for_single_value (mode)) { + if (!check_for_single_value (old_mode)) { GError *error; error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, @@ -477,14 +529,41 @@ impl_gsm_modem_set_network_mode (MMModemGsmNetwork *modem, return; } - mm_modem_gsm_network_set_mode (modem, mode, async_call_done, context); + mm_modem_gsm_network_set_allowed_mode (modem, + mm_modem_gsm_network_old_mode_to_allowed (old_mode), + async_call_done, + context); +} + +static void +impl_gsm_modem_set_allowed_mode (MMModemGsmNetwork *modem, + MMModemGsmAllowedMode mode, + DBusGMethodInvocation *context) +{ + if (mode > MM_MODEM_GSM_ALLOWED_MODE_LAST) { + GError *error; + + error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Unknown allowed mode %d", mode); + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + mm_modem_gsm_network_set_allowed_mode (modem, mode, async_call_done, context); } static void impl_gsm_modem_get_network_mode (MMModemGsmNetwork *modem, DBusGMethodInvocation *context) { - mm_modem_gsm_network_get_mode (modem, uint_call_done, context); + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + + /* DEPRECATED; it's now a property so it's quite easy to handle */ + g_object_get (G_OBJECT (modem), + MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY, &act, + NULL); + dbus_g_method_return (context, mm_modem_gsm_network_act_to_old_mode (act)); } static void @@ -505,6 +584,28 @@ mm_modem_gsm_network_init (gpointer g_iface) if (initialized) return; + /* Properties */ + g_object_interface_install_property + (g_iface, + g_param_spec_uint (MM_MODEM_GSM_NETWORK_ALLOWED_MODE, + "Allowed Mode", + "Allowed network access mode", + MM_MODEM_GSM_ALLOWED_MODE_ANY, + MM_MODEM_GSM_ALLOWED_MODE_LAST, + MM_MODEM_GSM_ALLOWED_MODE_ANY, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_uint (MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY, + "Access Technology", + "Current access technology in use when connected to " + "a mobile network.", + MM_MODEM_GSM_ACCESS_TECH_UNKNOWN, + MM_MODEM_GSM_ACCESS_TECH_LAST, + MM_MODEM_GSM_ACCESS_TECH_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + /* Signals */ signals[SIGNAL_QUALITY] = g_signal_new ("signal-quality", @@ -530,8 +631,7 @@ mm_modem_gsm_network_init (gpointer g_iface) g_signal_new ("network-mode", iface_type, G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (MMModemGsmNetwork, network_mode), - NULL, NULL, + 0, NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); @@ -562,6 +662,7 @@ mm_modem_gsm_network_get_type (void) &network_info, 0); g_type_interface_add_prerequisite (network_type, G_TYPE_OBJECT); + g_type_interface_add_prerequisite (network_type, MM_TYPE_MODEM); dbus_g_object_type_install_info (network_type, &dbus_glib_mm_modem_gsm_network_object_info); } diff --git a/src/mm-modem-gsm-network.h b/src/mm-modem-gsm-network.h index 493baec..a515789 100644 --- a/src/mm-modem-gsm-network.h +++ b/src/mm-modem-gsm-network.h @@ -11,7 +11,7 @@ * GNU General Public License for more details: * * Copyright (C) 2008 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #ifndef MM_MODEM_GSM_NETWORK_H @@ -20,11 +20,23 @@ #include <mm-modem.h> #include <mm-modem-gsm.h> +#define MM_MODEM_GSM_NETWORK_DBUS_INTERFACE "org.freedesktop.ModemManager.Modem.Gsm.Network" + #define MM_TYPE_MODEM_GSM_NETWORK (mm_modem_gsm_network_get_type ()) #define MM_MODEM_GSM_NETWORK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_GSM_NETWORK, MMModemGsmNetwork)) #define MM_IS_MODEM_GSM_NETWORK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_GSM_NETWORK)) #define MM_MODEM_GSM_NETWORK_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_GSM_NETWORK, MMModemGsmNetwork)) +#define MM_MODEM_GSM_NETWORK_ALLOWED_MODE "allowed-mode" +#define MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY "access-technology" + +typedef enum { + MM_MODEM_GSM_NETWORK_PROP_FIRST = 0x1200, + + MM_MODEM_GSM_NETWORK_PROP_ALLOWED_MODE = MM_MODEM_GSM_NETWORK_PROP_FIRST, + MM_MODEM_GSM_NETWORK_PROP_ACCESS_TECHNOLOGY, +} MMModemGsmNetworkProp; + typedef enum { MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE = 0, MM_MODEM_GSM_NETWORK_REG_STATUS_HOME = 1, @@ -80,15 +92,11 @@ struct _MMModemGsmNetwork { MMModemUIntFn callback, gpointer user_data); - void (*set_network_mode) (MMModemGsmNetwork *self, - MMModemGsmMode mode, + void (*set_allowed_mode) (MMModemGsmNetwork *self, + MMModemGsmAllowedMode mode, MMModemFn callback, gpointer user_data); - void (*get_network_mode) (MMModemGsmNetwork *self, - MMModemUIntFn callback, - gpointer user_data); - void (*get_registration_info) (MMModemGsmNetwork *self, MMModemGsmNetworkRegInfoFn callback, gpointer user_data); @@ -101,9 +109,6 @@ struct _MMModemGsmNetwork { MMModemGsmNetworkRegStatus status, const char *open_code, const char *oper_name); - - void (*network_mode) (MMModemGsmNetwork *self, - MMModemGsmMode mode); }; GType mm_modem_gsm_network_get_type (void); @@ -135,15 +140,6 @@ void mm_modem_gsm_network_get_band (MMModemGsmNetwork *self, MMModemUIntFn callback, gpointer user_data); -void mm_modem_gsm_network_set_mode (MMModemGsmNetwork *self, - MMModemGsmMode mode, - MMModemFn callback, - gpointer user_data); - -void mm_modem_gsm_network_get_mode (MMModemGsmNetwork *self, - MMModemUIntFn callback, - gpointer user_data); - void mm_modem_gsm_network_get_registration_info (MMModemGsmNetwork *self, MMModemGsmNetworkRegInfoFn callback, gpointer user_data); @@ -153,12 +149,19 @@ void mm_modem_gsm_network_get_registration_info (MMModemGsmNetwork *self, void mm_modem_gsm_network_signal_quality (MMModemGsmNetwork *self, guint32 quality); +void mm_modem_gsm_network_set_allowed_mode (MMModemGsmNetwork *self, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data); + void mm_modem_gsm_network_registration_info (MMModemGsmNetwork *self, MMModemGsmNetworkRegStatus status, const char *oper_code, const char *oper_name); -void mm_modem_gsm_network_mode (MMModemGsmNetwork *self, - MMModemGsmMode mode); +/* Private */ +MMModemDeprecatedMode mm_modem_gsm_network_act_to_old_mode (MMModemGsmAccessTech act); + +MMModemGsmAllowedMode mm_modem_gsm_network_old_mode_to_allowed (MMModemDeprecatedMode old_mode); #endif /* MM_MODEM_GSM_NETWORK_H */ diff --git a/src/mm-modem-gsm-sms.c b/src/mm-modem-gsm-sms.c index e8ec074..d74c7b3 100644 --- a/src/mm-modem-gsm-sms.c +++ b/src/mm-modem-gsm-sms.c @@ -129,12 +129,138 @@ mm_modem_gsm_sms_send (MMModemGsmSms *self, /*****************************************************************************/ +typedef struct { + guint num1; + guint num2; + guint num3; + guint num4; + guint num5; + char *str; + GHashTable *hash; +} SmsAuthInfo; + +static void +sms_auth_info_destroy (gpointer data) +{ + SmsAuthInfo *info = data; + + g_hash_table_destroy (info->hash); + g_free (info->str); + memset (info, 0, sizeof (SmsAuthInfo)); + g_free (info); +} + +static void +destroy_gvalue (gpointer data) +{ + GValue *value = (GValue *) data; + + g_value_unset (value); + g_slice_free (GValue, value); +} + +static SmsAuthInfo * +sms_auth_info_new (guint num1, + guint num2, + guint num3, + guint num4, + guint num5, + const char *str, + GHashTable *hash) +{ + SmsAuthInfo *info; + + info = g_malloc0 (sizeof (SmsAuthInfo)); + info->num1 = num1; + info->num2 = num2; + info->num3 = num3; + info->num4 = num4; + info->num5 = num5; + info->str = g_strdup (str); + + /* Copy the hash table if we're given one */ + if (hash) { + GHashTableIter iter; + gpointer key, value; + + info->hash = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, destroy_gvalue); + + g_hash_table_iter_init (&iter, hash); + while (g_hash_table_iter_next (&iter, &key, &value)) { + const char *str_key = (const char *) key; + GValue *src = (GValue *) value; + GValue *dst; + + dst = g_slice_new0 (GValue); + g_value_init (dst, G_VALUE_TYPE (src)); + g_value_copy (src, dst); + g_hash_table_insert (info->hash, g_strdup (str_key), dst); + } + } + + return info; +} + +/*****************************************************************************/ + +static void +sms_delete_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise delete the SMS */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); +} + static void impl_gsm_modem_sms_delete (MMModemGsmSms *modem, guint idx, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (idx, 0, 0, 0, 0, NULL, NULL); + + /* Make sure the caller is authorized to delete an SMS */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_delete_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +sms_get_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the SMS */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); } static void @@ -142,9 +268,26 @@ impl_gsm_modem_sms_get (MMModemGsmSms *modem, guint idx, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (idx, 0, 0, 0, 0, NULL, NULL); + + /* Make sure the caller is authorized to get an SMS */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_get_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } +/*****************************************************************************/ + static void impl_gsm_modem_sms_get_format (MMModemGsmSms *modem, DBusGMethodInvocation *context) @@ -167,19 +310,103 @@ impl_gsm_modem_sms_get_smsc (MMModemGsmSms *modem, async_call_not_supported (modem, async_call_done, context); } +/*****************************************************************************/ + +static void +sms_set_smsc_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise set the SMS service center */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); +} + static void impl_gsm_modem_sms_set_smsc (MMModemGsmSms *modem, const char *smsc, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (0, 0, 0, 0, 0, smsc, NULL); + + /* Make sure the caller is authorized to set the SMS service center */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_set_smsc_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +sms_list_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise list SMSs */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); } static void impl_gsm_modem_sms_list (MMModemGsmSms *modem, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + + /* Make sure the caller is authorized to list SMSs */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_list_auth_cb, + NULL, + NULL, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +sms_save_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise save the SMS */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); } static void @@ -187,54 +414,122 @@ impl_gsm_modem_sms_save (MMModemGsmSms *modem, GHashTable *properties, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (0, 0, 0, 0, 0, NULL, properties); + + /* Make sure the caller is authorized to save the SMS */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_save_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } +/*****************************************************************************/ + static void -impl_gsm_modem_sms_send (MMModemGsmSms *modem, - GHashTable *properties, - DBusGMethodInvocation *context) +sms_send_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) { + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + SmsAuthInfo *info = user_data; + GError *error = NULL; GValue *value; const char *number = NULL; const char *text = NULL ; const char *smsc = NULL; - GError *error = NULL; guint validity = 0; guint class = 0; - value = (GValue *) g_hash_table_lookup (properties, "number"); + /* Return any authorization error, otherwise delete the SMS */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) + goto done; + + value = (GValue *) g_hash_table_lookup (info->hash, "number"); if (value) number = g_value_get_string (value); - value = (GValue *) g_hash_table_lookup (properties, "text"); + value = (GValue *) g_hash_table_lookup (info->hash, "text"); if (value) text = g_value_get_string (value); - value = (GValue *) g_hash_table_lookup (properties, "smsc"); + value = (GValue *) g_hash_table_lookup (info->hash, "smsc"); if (value) smsc = g_value_get_string (value); - value = (GValue *) g_hash_table_lookup (properties, "validity"); + value = (GValue *) g_hash_table_lookup (info->hash, "validity"); if (value) validity = g_value_get_uint (value); - value = (GValue *) g_hash_table_lookup (properties, "class"); + value = (GValue *) g_hash_table_lookup (info->hash, "class"); if (value) class = g_value_get_uint (value); - if (!number) + if (!number) { error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Missing number"); - else if (!text) + } else if (!text) { error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Missing message text"); + } +done: if (error) { - async_call_done (MM_MODEM (modem), error, context); + async_call_done (MM_MODEM (self), error, context); g_error_free (error); } else - mm_modem_gsm_sms_send (modem, number, text, smsc, validity, class, async_call_done, context); + mm_modem_gsm_sms_send (self, number, text, smsc, validity, class, async_call_done, context); +} + +static void +impl_gsm_modem_sms_send (MMModemGsmSms *modem, + GHashTable *properties, + DBusGMethodInvocation *context) +{ + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (0, 0, 0, 0, 0, NULL, properties); + + /* Make sure the caller is authorized to send the PUK */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_send_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +sms_send_from_storage_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise delete the SMS */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); } static void @@ -242,7 +537,41 @@ impl_gsm_modem_sms_send_from_storage (MMModemGsmSms *modem, guint idx, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (idx, 0, 0, 0, 0, NULL, NULL); + + /* Make sure the caller is authorized to send the PUK */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_send_from_storage_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +sms_set_indication_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise delete the SMS */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); } static void @@ -254,7 +583,22 @@ impl_gsm_modem_sms_set_indication (MMModemGsmSms *modem, guint bfr, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (mode, mt, bm, ds, bfr, NULL, NULL); + + /* Make sure the caller is authorized to send the PUK */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_set_indication_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } /*****************************************************************************/ diff --git a/src/mm-modem-gsm.h b/src/mm-modem-gsm.h index 852ff85..6d9135a 100644 --- a/src/mm-modem-gsm.h +++ b/src/mm-modem-gsm.h @@ -30,11 +30,35 @@ typedef enum { MM_MODEM_GSM_MODE_3G_ONLY = 0x00000100, MM_MODEM_GSM_MODE_HSUPA = 0x00000200, MM_MODEM_GSM_MODE_HSPA = 0x00000400, - - MM_MODEM_GSM_MODE_LAST = MM_MODEM_GSM_MODE_HSPA + MM_MODEM_GSM_MODE_GSM = 0x00000800, + MM_MODEM_GSM_MODE_GSM_COMPACT = 0x00001000, } MMModemGsmMode; typedef enum { + MM_MODEM_GSM_ALLOWED_MODE_ANY = 0, + MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED = 1, + MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED = 2, + MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY = 3, + MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY = 4, + + MM_MODEM_GSM_ALLOWED_MODE_LAST = MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY +} MMModemGsmAllowedMode; + +typedef enum { + MM_MODEM_GSM_ACCESS_TECH_UNKNOWN = 0, + MM_MODEM_GSM_ACCESS_TECH_GSM = 1, + MM_MODEM_GSM_ACCESS_TECH_GSM_COMPACT = 2, + MM_MODEM_GSM_ACCESS_TECH_GPRS = 3, + MM_MODEM_GSM_ACCESS_TECH_EDGE = 4, /* GSM w/EGPRS */ + MM_MODEM_GSM_ACCESS_TECH_UMTS = 5, /* UTRAN */ + MM_MODEM_GSM_ACCESS_TECH_HSDPA = 6, /* UTRAN w/HSDPA */ + MM_MODEM_GSM_ACCESS_TECH_HSUPA = 7, /* UTRAN w/HSUPA */ + MM_MODEM_GSM_ACCESS_TECH_HSPA = 8, /* UTRAN w/HSDPA and HSUPA */ + + MM_MODEM_GSM_ACCESS_TECH_LAST = MM_MODEM_GSM_ACCESS_TECH_HSPA +} MMModemGsmAccessTech; + +typedef enum { MM_MODEM_GSM_BAND_UNKNOWN = 0x00000000, MM_MODEM_GSM_BAND_ANY = 0x00000001, MM_MODEM_GSM_BAND_EGSM = 0x00000002, /* 900 MHz */ @@ -48,10 +72,26 @@ typedef enum { MM_MODEM_GSM_BAND_U850 = 0x00000200, /* WCDMA 3GPP UMTS 850 MHz (Class V) */ MM_MODEM_GSM_BAND_U900 = 0x00000400, /* WCDMA 3GPP UMTS 900 MHz (Class VIII) */ MM_MODEM_GSM_BAND_U17IX = 0x00000800, /* WCDMA 3GPP UMTS 1700 MHz (Class IX) */ + MM_MODEM_GSM_BAND_U1900 = 0x00001000, /* WCDMA 3GPP UMTS 1900 MHz (Class II) */ - MM_MODEM_GSM_BAND_LAST = MM_MODEM_GSM_BAND_U17IX + MM_MODEM_GSM_BAND_LAST = MM_MODEM_GSM_BAND_U1900 } MMModemGsmBand; -#endif /* MM_MODEM_GSM_H */ +typedef enum { + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_ANY = 0, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_GPRS, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_EDGE, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_UMTS, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSDPA, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_2G_PREFERRED, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_3G_PREFERRED, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_2G_ONLY, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_3G_ONLY, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSUPA, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSPA, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_LAST = MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSPA +} MMModemDeprecatedMode; + +#endif /* MM_MODEM_GSM_H */ diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c index 1741b5f..6e76f46 100644 --- a/src/mm-modem-helpers.c +++ b/src/mm-modem-helpers.c @@ -14,13 +14,32 @@ * Copyright (C) 2009 - 2010 Red Hat, Inc. */ +#include <config.h> #include <glib.h> #include <string.h> #include <ctype.h> +#include <stdlib.h> +#include <errno.h> #include "mm-errors.h" #include "mm-modem-helpers.h" +const char * +mm_strip_tag (const char *str, const char *cmd) +{ + const char *p = str; + + if (p) { + if (!strncmp (p, cmd, strlen (cmd))) + p += strlen (cmd); + while (isspace (*p)) + p++; + } + return p; +} + +/*************************************************************************/ + static void save_scan_value (GHashTable *hash, const char *key, GMatchInfo *info, guint32 num) { @@ -201,3 +220,586 @@ mm_gsm_destroy_scan_data (gpointer data) g_ptr_array_free (results, TRUE); } +/*************************************************************************/ + +/* +CREG: <stat> (GSM 07.07 CREG=1 unsolicited) */ +#define CREG1 "\\+(CREG|CGREG):\\s*(\\d{1})" + +/* +CREG: <n>,<stat> (GSM 07.07 CREG=1 solicited) */ +#define CREG2 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*(\\d{1})" + +/* +CREG: <stat>,<lac>,<ci> (GSM 07.07 CREG=2 unsolicited) */ +#define CREG3 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)" + +/* +CREG: <n>,<stat>,<lac>,<ci> (GSM 07.07 solicited and some CREG=2 unsolicited) */ +#define CREG4 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*(\\d{1})\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)" + +/* +CREG: <stat>,<lac>,<ci>,<AcT> (ETSI 27.007 CREG=2 unsolicited) */ +#define CREG5 "\\+(CREG|CGREG):\\s*(\\d{1})\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*(\\d{1,2})" + +/* +CREG: <n>,<stat>,<lac>,<ci>,<AcT> (ETSI 27.007 solicited and some CREG=2 unsolicited) */ +#define CREG6 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*(\\d{1})\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*(\\d{1,2})" + +GPtrArray * +mm_gsm_creg_regex_get (gboolean solicited) +{ + GPtrArray *array = g_ptr_array_sized_new (6); + GRegex *regex; + + /* #1 */ + if (solicited) + regex = g_regex_new (CREG1 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + else + regex = g_regex_new ("\\r\\n" CREG1 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (regex); + g_ptr_array_add (array, regex); + + /* #2 */ + if (solicited) + regex = g_regex_new (CREG2 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + else + regex = g_regex_new ("\\r\\n" CREG2 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (regex); + g_ptr_array_add (array, regex); + + /* #3 */ + if (solicited) + regex = g_regex_new (CREG3 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + else + regex = g_regex_new ("\\r\\n" CREG3 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (regex); + g_ptr_array_add (array, regex); + + /* #4 */ + if (solicited) + regex = g_regex_new (CREG4 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + else + regex = g_regex_new ("\\r\\n" CREG4 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (regex); + g_ptr_array_add (array, regex); + + /* #5 */ + if (solicited) + regex = g_regex_new (CREG5 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + else + regex = g_regex_new ("\\r\\n" CREG5 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (regex); + g_ptr_array_add (array, regex); + + /* #6 */ + if (solicited) + regex = g_regex_new (CREG6 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + else + regex = g_regex_new ("\\r\\n" CREG6 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (regex); + g_ptr_array_add (array, regex); + + return array; +} + +void +mm_gsm_creg_regex_destroy (GPtrArray *array) +{ + g_ptr_array_foreach (array, (GFunc) g_regex_unref, NULL); + g_ptr_array_free (array, TRUE); +} + +/*************************************************************************/ + +static gulong +parse_uint (char *str, int base, glong nmin, glong nmax, gboolean *valid) +{ + gulong ret = 0; + char *endquote; + + *valid = FALSE; + if (!str) + return 0; + + /* Strip quotes */ + if (str[0] == '"') + str++; + endquote = strchr (str, '"'); + if (endquote) + *endquote = '\0'; + + if (strlen (str)) { + ret = strtol (str, NULL, base); + if ((nmin == nmax) || (ret >= nmin && ret <= nmax)) + *valid = TRUE; + } + return *valid ? (guint) ret : 0; +} + +gboolean +mm_gsm_parse_creg_response (GMatchInfo *info, + guint32 *out_reg_state, + gulong *out_lac, + gulong *out_ci, + gint *out_act, + gboolean *out_cgreg, + GError **error) +{ + gboolean success = FALSE, foo; + gint n_matches, act = -1; + gulong stat = 0, lac = 0, ci = 0; + guint istat = 0, ilac = 0, ici = 0, iact = 0; + char *str; + + g_return_val_if_fail (info != NULL, FALSE); + g_return_val_if_fail (out_reg_state != NULL, FALSE); + g_return_val_if_fail (out_lac != NULL, FALSE); + g_return_val_if_fail (out_ci != NULL, FALSE); + g_return_val_if_fail (out_act != NULL, FALSE); + g_return_val_if_fail (out_cgreg != NULL, FALSE); + + str = g_match_info_fetch (info, 1); + if (str && strstr (str, "CGREG")) + *out_cgreg = TRUE; + + /* Normally the number of matches could be used to determine what each + * item is, but we have overlap in one case. + */ + n_matches = g_match_info_get_match_count (info); + if (n_matches == 3) { + /* CREG=1: +CREG: <stat> */ + istat = 2; + } else if (n_matches == 4) { + /* Solicited response: +CREG: <n>,<stat> */ + istat = 3; + } else if (n_matches == 5) { + /* CREG=2 (GSM 07.07): +CREG: <stat>,<lac>,<ci> */ + istat = 2; + ilac = 3; + ici = 4; + } else if (n_matches == 6) { + /* CREG=2 (ETSI 27.007): +CREG: <stat>,<lac>,<ci>,<AcT> + * CREG=2 (non-standard): +CREG: <n>,<stat>,<lac>,<ci> + */ + + /* To distinguish, check length of the third match item. If it's + * more than one digit or has quotes in it then it's a LAC and we + * got the first format. + */ + str = g_match_info_fetch (info, 3); + if (str && (strchr (str, '"') || strlen (str) > 1)) { + g_free (str); + istat = 2; + ilac = 3; + ici = 4; + iact = 5; + } else { + istat = 3; + ilac = 4; + ici = 5; + } + } else if (n_matches == 7) { + /* CREG=2 (non-standard): +CREG: <n>,<stat>,<lac>,<ci>,<AcT> */ + istat = 3; + ilac = 4; + ici = 5; + iact = 6; + } + + /* Status */ + str = g_match_info_fetch (info, istat); + stat = parse_uint (str, 10, 0, 5, &success); + g_free (str); + if (!success) { + g_set_error_literal (error, + MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Could not parse the registration status response"); + return FALSE; + } + + /* Location Area Code */ + if (ilac) { + /* FIXME: some phones apparently swap the LAC bytes (LG, SonyEricsson, + * Sagem). Need to handle that. + */ + str = g_match_info_fetch (info, ilac); + lac = parse_uint (str, 16, 1, 0xFFFF, &foo); + g_free (str); + } + + /* Cell ID */ + if (ici) { + str = g_match_info_fetch (info, ici); + ci = parse_uint (str, 16, 1, 0x0FFFFFFE, &foo); + g_free (str); + } + + /* Access Technology */ + if (iact) { + str = g_match_info_fetch (info, iact); + act = (gint) parse_uint (str, 10, 0, 7, &foo); + g_free (str); + if (!foo) + act = -1; + } + + *out_reg_state = (guint32) stat; + if (stat != 4) { + /* Don't fill in lac/ci/act if the device's state is unknown */ + *out_lac = lac; + *out_ci = ci; + *out_act = act; + } + return TRUE; +} + +/*************************************************************************/ + +gboolean +mm_cdma_parse_spservice_response (const char *reply, + MMModemCdmaRegistrationState *out_cdma_1x_state, + MMModemCdmaRegistrationState *out_evdo_state) +{ + const char *p; + + g_return_val_if_fail (reply != NULL, FALSE); + g_return_val_if_fail (out_cdma_1x_state != NULL, FALSE); + g_return_val_if_fail (out_evdo_state != NULL, FALSE); + + p = mm_strip_tag (reply, "+SPSERVICE:"); + if (!isdigit (*p)) + return FALSE; + + switch (atoi (p)) { + case 0: /* no service */ + *out_cdma_1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + *out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + break; + case 1: /* 1xRTT */ + *out_cdma_1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + *out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + break; + case 2: /* EVDO rev 0 */ + case 3: /* EVDO rev A */ + *out_cdma_1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + *out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + break; + default: + return FALSE; + } + return TRUE; +} + +/*************************************************************************/ + +typedef struct { + int num; + gboolean roam_ind; + const char *banner; +} EriItem; + +/* NOTE: these may be Sprint-specific for now... */ +static const EriItem eris[] = { + { 0, TRUE, "Digital or Analog Roaming" }, + { 1, FALSE, "Home" }, + { 2, TRUE, "Digital or Analog Roaming" }, + { 3, TRUE, "Out of neighborhood" }, + { 4, TRUE, "Out of building" }, + { 5, TRUE, "Preferred system" }, + { 6, TRUE, "Available System" }, + { 7, TRUE, "Alliance Partner" }, + { 8, TRUE, "Premium Partner" }, + { 9, TRUE, "Full Service Functionality" }, + { 10, TRUE, "Partial Service Functionality" }, + { 64, TRUE, "Preferred system" }, + { 65, TRUE, "Available System" }, + { 66, TRUE, "Alliance Partner" }, + { 67, TRUE, "Premium Partner" }, + { 68, TRUE, "Full Service Functionality" }, + { 69, TRUE, "Partial Service Functionality" }, + { 70, TRUE, "Analog A" }, + { 71, TRUE, "Analog B" }, + { 72, TRUE, "CDMA 800 A" }, + { 73, TRUE, "CDMA 800 B" }, + { 74, TRUE, "International Roaming" }, + { 75, TRUE, "Extended Network" }, + { 76, FALSE, "Campus" }, + { 77, FALSE, "In Building" }, + { 78, TRUE, "Regional" }, + { 79, TRUE, "Community" }, + { 80, TRUE, "Business" }, + { 81, TRUE, "Zone 1" }, + { 82, TRUE, "Zone 2" }, + { 83, TRUE, "National" }, + { 84, TRUE, "Local" }, + { 85, TRUE, "City" }, + { 86, TRUE, "Government" }, + { 87, TRUE, "USA" }, + { 88, TRUE, "State" }, + { 89, TRUE, "Resort" }, + { 90, TRUE, "Headquarters" }, + { 91, TRUE, "Personal" }, + { 92, FALSE, "Home" }, + { 93, TRUE, "Residential" }, + { 94, TRUE, "University" }, + { 95, TRUE, "College" }, + { 96, TRUE, "Hotel Guest" }, + { 97, TRUE, "Rental" }, + { 98, FALSE, "Corporate" }, + { 99, FALSE, "Home Provider" }, + { 100, FALSE, "Campus" }, + { 101, FALSE, "In Building" }, + { 102, TRUE, "Regional" }, + { 103, TRUE, "Community" }, + { 104, TRUE, "Business" }, + { 105, TRUE, "Zone 1" }, + { 106, TRUE, "Zone 2" }, + { 107, TRUE, "National" }, + { 108, TRUE, "Local" }, + { 109, TRUE, "City" }, + { 110, TRUE, "Government" }, + { 111, TRUE, "USA" }, + { 112, TRUE, "State" }, + { 113, TRUE, "Resort" }, + { 114, TRUE, "Headquarters" }, + { 115, TRUE, "Personal" }, + { 116, FALSE, "Home" }, + { 117, TRUE, "Residential" }, + { 118, TRUE, "University" }, + { 119, TRUE, "College" }, + { 120, TRUE, "Hotel Guest" }, + { 121, TRUE, "Rental" }, + { 122, FALSE, "Corporate" }, + { 123, FALSE, "Home Provider" }, + { 124, TRUE, "International" }, + { 125, TRUE, "International" }, + { 126, TRUE, "International" }, + { 127, FALSE, "Premium Service" }, + { 128, FALSE, "Enhanced Service" }, + { 129, FALSE, "Enhanced Digital" }, + { 130, FALSE, "Enhanced Roaming" }, + { 131, FALSE, "Alliance Service" }, + { 132, FALSE, "Alliance Network" }, + { 133, FALSE, "Data Roaming" }, /* Sprint: Vision Roaming */ + { 134, FALSE, "Extended Service" }, + { 135, FALSE, "Expanded Services" }, + { 136, FALSE, "Expanded Network" }, + { 137, TRUE, "Premium Service" }, + { 138, TRUE, "Enhanced Service" }, + { 139, TRUE, "Enhanced Digital" }, + { 140, TRUE, "Enhanced Roaming" }, + { 141, TRUE, "Alliance Service" }, + { 142, TRUE, "Alliance Network" }, + { 143, TRUE, "Data Roaming" }, /* Sprint: Vision Roaming */ + { 144, TRUE, "Extended Service" }, + { 145, TRUE, "Expanded Services" }, + { 146, TRUE, "Expanded Network" }, + { 147, TRUE, "Premium Service" }, + { 148, TRUE, "Enhanced Service" }, + { 149, TRUE, "Enhanced Digital" }, + { 150, TRUE, "Enhanced Roaming" }, + { 151, TRUE, "Alliance Service" }, + { 152, TRUE, "Alliance Network" }, + { 153, TRUE, "Data Roaming" }, /* Sprint: Vision Roaming */ + { 154, TRUE, "Extended Service" }, + { 155, TRUE, "Expanded Services" }, + { 156, TRUE, "Expanded Network" }, + { 157, TRUE, "Premium International" }, + { 158, TRUE, "Premium International" }, + { 159, TRUE, "Premium International" }, + { 160, TRUE, NULL }, + { 161, TRUE, NULL }, + { 162, FALSE, NULL }, + { 163, FALSE, NULL }, + { 164, FALSE, "Extended Voice/Data Network" }, + { 165, FALSE, "Extended Voice/Data Network" }, + { 166, TRUE, "Extended Voice/Data Network" }, + { 167, FALSE, "Extended Broadband" }, + { 168, FALSE, "Extended Broadband" }, + { 169, TRUE, "Extended Broadband" }, + { 170, FALSE, "Extended Data" }, + { 171, FALSE, "Extended Data" }, + { 172, TRUE, "Extended Data" }, + { 173, FALSE, "Extended Data Network" }, + { 174, FALSE, "Extended Data Network" }, + { 175, TRUE, "Extended Data Network" }, + { 176, FALSE, "Extended Network" }, + { 177, FALSE, "Extended Network" }, + { 178, TRUE, "Extended Network" }, + { 179, FALSE, "Extended Service" }, + { 180, TRUE, "Extended Service" }, + { 181, FALSE, "Extended Voice" }, + { 182, FALSE, "Extended Voice" }, + { 183, TRUE, "Extended Voice" }, + { 184, FALSE, "Extended Voice/Data" }, + { 185, FALSE, "Extended Voice/Data" }, + { 186, TRUE, "Extended Voice/Data" }, + { 187, FALSE, "Extended Voice Network" }, + { 188, FALSE, "Extended Voice Network" }, + { 189, TRUE, "Extended Voice Network" }, + { 190, FALSE, "Extended Voice/Data" }, + { 191, FALSE, "Extended Voice/Data" }, + { 192, TRUE, "Extended Voice/Data" }, + { 193, TRUE, "International" }, + { 194, FALSE, "International Services" }, + { 195, FALSE, "International Voice" }, + { 196, FALSE, "International Voice/Data" }, + { 197, FALSE, "International Voice/Data" }, + { 198, TRUE, "International Voice/Data" }, + { 199, FALSE, "Extended Voice/Data Network" }, + { 200, TRUE, "Extended Voice/Data Network" }, + { 201, TRUE, "Extended Voice/Data Network" }, + { 202, FALSE, "Extended Broadband" }, + { 203, TRUE, "Extended Broadband" }, + { 204, TRUE, "Extended Broadband" }, + { 205, FALSE, "Extended Data" }, + { 206, TRUE, "Extended Data" }, + { 207, TRUE, "Extended Data" }, + { 208, FALSE, "Extended Data Network" }, + { 209, TRUE, "Extended Data Network" }, + { 210, TRUE, "Extended Data Network" }, + { 211, FALSE, "Extended Network" }, + { 212, TRUE, "Extended Network" }, + { 213, FALSE, "Extended Service" }, + { 214, TRUE, "Extended Service" }, + { 215, TRUE, "Extended Service" }, + { 216, FALSE, "Extended Voice" }, + { 217, TRUE, "Extended Voice" }, + { 218, TRUE, "Extended Voice" }, + { 219, FALSE, "Extended Voice/Data" }, + { 220, TRUE, "Extended Voice/Data" }, + { 221, TRUE, "Extended Voice/Data" }, + { 222, FALSE, "Extended Voice Network" }, + { 223, FALSE, "Extended Voice Network" }, + { 224, TRUE, "Extended Voice Network" }, + { 225, FALSE, "Extended Voice/Data" }, + { 226, TRUE, "Extended Voice/Data" }, + { 227, TRUE, "Extended Voice/Data" }, + { 228, TRUE, "International" }, + { 229, TRUE, "International" }, + { 230, TRUE, "International Services" }, + { 231, TRUE, "International Voice" }, + { 232, FALSE, "International Voice/Data" }, + { 233, TRUE, "International Voice/Data" }, + { 234, TRUE, "International Voice/Data" }, + { 235, TRUE, "Premium International" }, + { 236, TRUE, NULL }, + { 237, TRUE, NULL }, + { 238, FALSE, NULL }, + { 239, FALSE, NULL }, + { -1, FALSE, NULL }, +}; + +gboolean +mm_cdma_parse_eri (const char *reply, + gboolean *out_roaming, + guint32 *out_ind, + const char **out_desc) +{ + long int ind; + const EriItem *iter = &eris[0]; + gboolean found = FALSE; + + g_return_val_if_fail (reply != NULL, FALSE); + g_return_val_if_fail (out_roaming != NULL, FALSE); + + errno = 0; + ind = strtol (reply, NULL, 10); + if (errno == 0) { + if (out_ind) + *out_ind = ind; + + while (iter->num != -1) { + if (iter->num == ind) { + *out_roaming = iter->roam_ind; + if (out_desc) + *out_desc = iter->banner; + found = TRUE; + break; + } + iter++; + } + } + + return found; +} + +/*************************************************************************/ + +gboolean +mm_gsm_parse_cscs_support_response (const char *reply, + MMModemCharset *out_charsets) +{ + MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN; + GRegex *r; + GMatchInfo *match_info; + char *p, *str; + gboolean success = FALSE; + + g_return_val_if_fail (reply != NULL, FALSE); + g_return_val_if_fail (out_charsets != NULL, FALSE); + + /* Find the first '(' or '"'; the general format is: + * + * +CSCS: ("IRA","GSM","UCS2") + * + * but some devices (some Blackberries) don't include the (). + */ + p = strchr (reply, '('); + if (p) + p++; + else { + p = strchr (reply, '"'); + if (!p) + return FALSE; + } + + /* Now parse each charset */ + r = g_regex_new ("\\s*([^,\\)]+)\\s*", 0, 0, NULL); + if (!r) + return FALSE; + + if (g_regex_match_full (r, p, strlen (p), 0, 0, &match_info, NULL)) { + while (g_match_info_matches (match_info)) { + str = g_match_info_fetch (match_info, 1); + charsets |= mm_modem_charset_from_string (str); + g_free (str); + + g_match_info_next (match_info, NULL); + success = TRUE; + } + g_match_info_free (match_info); + } + g_regex_unref (r); + + if (success) + *out_charsets = charsets; + + return success; +} + +/*************************************************************************/ + +MMModemGsmAccessTech +mm_gsm_string_to_access_tech (const char *string) +{ + g_return_val_if_fail (string != NULL, MM_MODEM_GSM_ACCESS_TECH_UNKNOWN); + + /* Better technologies are listed first since modems sometimes say + * stuff like "GPRS/EDGE" and that should be handled as EDGE. + */ + if (strcasestr (string, "HSPA")) + return MM_MODEM_GSM_ACCESS_TECH_HSPA; + else if (strcasestr (string, "HSDPA/HSUPA")) + return MM_MODEM_GSM_ACCESS_TECH_HSPA; + else if (strcasestr (string, "HSUPA")) + return MM_MODEM_GSM_ACCESS_TECH_HSUPA; + else if (strcasestr (string, "HSDPA")) + return MM_MODEM_GSM_ACCESS_TECH_HSDPA; + else if (strcasestr (string, "UMTS")) + return MM_MODEM_GSM_ACCESS_TECH_UMTS; + else if (strcasestr (string, "EDGE")) + return MM_MODEM_GSM_ACCESS_TECH_EDGE; + else if (strcasestr (string, "GPRS")) + return MM_MODEM_GSM_ACCESS_TECH_GPRS; + else if (strcasestr (string, "GSM")) + return MM_MODEM_GSM_ACCESS_TECH_GSM; + + return MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; +} + diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h index ddc9cbc..fb100bc 100644 --- a/src/mm-modem-helpers.h +++ b/src/mm-modem-helpers.h @@ -17,6 +17,10 @@ #ifndef MM_MODEM_HELPERS_H #define MM_MODEM_HELPERS_H +#include "mm-modem-cdma.h" +#include "mm-modem-gsm.h" +#include "mm-charsets.h" + #define MM_SCAN_TAG_STATUS "status" #define MM_SCAN_TAG_OPER_LONG "operator-long" #define MM_SCAN_TAG_OPER_SHORT "operator-short" @@ -27,5 +31,33 @@ GPtrArray *mm_gsm_parse_scan_response (const char *reply, GError **error); void mm_gsm_destroy_scan_data (gpointer data); +GPtrArray *mm_gsm_creg_regex_get (gboolean solicited); + +void mm_gsm_creg_regex_destroy (GPtrArray *array); + +gboolean mm_gsm_parse_creg_response (GMatchInfo *info, + guint32 *out_reg_state, + gulong *out_lac, + gulong *out_ci, + gint *out_act, + gboolean *out_cgreg, + GError **error); + +const char *mm_strip_tag (const char *str, const char *cmd); + +gboolean mm_cdma_parse_spservice_response (const char *reply, + MMModemCdmaRegistrationState *out_cdma_1x_state, + MMModemCdmaRegistrationState *out_evdo_state); + +gboolean mm_cdma_parse_eri (const char *reply, + gboolean *out_roaming, + guint32 *out_ind, + const char **out_desc); + +gboolean mm_gsm_parse_cscs_support_response (const char *reply, + MMModemCharset *out_charsets); + +MMModemGsmAccessTech mm_gsm_string_to_access_tech (const char *string); + #endif /* MM_MODEM_HELPERS_H */ diff --git a/src/mm-modem-location.c b/src/mm-modem-location.c new file mode 100644 index 0000000..0018295 --- /dev/null +++ b/src/mm-modem-location.c @@ -0,0 +1,330 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include <string.h> +#include <dbus/dbus-glib.h> + +#include "mm-modem-location.h" +#include "mm-errors.h" +#include "mm-callback-info.h" +#include "mm-marshal.h" + +static void impl_modem_location_enable (MMModemLocation *modem, + gboolean enable, + gboolean signal_location, + DBusGMethodInvocation *context); + +static void impl_modem_location_get_location (MMModemLocation *modem, + DBusGMethodInvocation *context); + +#include "mm-modem-location-glue.h" + +/*****************************************************************************/ + +static void +async_call_done (MMModem *modem, GError *error, gpointer user_data) +{ + DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data; + + if (error) + dbus_g_method_return_error (context, error); + else + dbus_g_method_return (context); +} + +static void +async_call_not_supported (MMModemLocation *self, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new (MM_MODEM (self), callback, user_data); + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + mm_callback_info_schedule (info); +} + +/*****************************************************************************/ + +typedef struct { + gboolean enable; + gboolean signals_location; +} LocAuthInfo; + +static void +loc_auth_info_destroy (gpointer data) +{ + LocAuthInfo *info = data; + + memset (info, 0, sizeof (LocAuthInfo)); + g_free (info); +} + +static LocAuthInfo * +loc_auth_info_new (gboolean enable, gboolean signals_location) +{ + LocAuthInfo *info; + + info = g_malloc0 (sizeof (LocAuthInfo)); + info->enable = enable; + info->signals_location = signals_location; + return info; +} + +/*****************************************************************************/ + +void +mm_modem_location_enable (MMModemLocation *self, + gboolean enable, + gboolean signals_location, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_LOCATION (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_LOCATION_GET_INTERFACE (self)->enable) + MM_MODEM_LOCATION_GET_INTERFACE (self)->enable (self, enable, signals_location, callback, user_data); + else + async_call_not_supported (self, callback, user_data); +} + +/*****************************************************************************/ + +static void +loc_enable_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemLocation *self = MM_MODEM_LOCATION (owner); + LocAuthInfo *info = (LocAuthInfo *) user_data; + GError *error = NULL; + + /* Return any authorization error, otherwise enable location gathering */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_location_enable (self, info->enable, info->signals_location, async_call_done, context); +} + +static void +impl_modem_location_enable (MMModemLocation *modem, + gboolean enable, + gboolean signals_location, + DBusGMethodInvocation *context) +{ + GError *error = NULL; + LocAuthInfo *info; + + info = loc_auth_info_new (enable, signals_location); + + /* Make sure the caller is authorized to enable location gathering */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_LOCATION, + context, + loc_enable_auth_cb, + info, + loc_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +loc_get_invoke (MMCallbackInfo *info) +{ + MMModemLocationGetFn callback = (MMModemLocationGetFn) info->callback; + + callback ((MMModemLocation *) info->modem, NULL, info->error, info->user_data); +} + +static void +async_get_call_not_supported (MMModemLocation *self, + MMModemLocationGetFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new_full (MM_MODEM (self), + loc_get_invoke, + G_CALLBACK (callback), + user_data); + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + mm_callback_info_schedule (info); +} + +void +mm_modem_location_get_location (MMModemLocation *self, + MMModemLocationGetFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_LOCATION (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_LOCATION_GET_INTERFACE (self)->get_location) + MM_MODEM_LOCATION_GET_INTERFACE (self)->get_location (self, callback, user_data); + else + async_get_call_not_supported (self, callback, user_data); +} + +/*****************************************************************************/ + +static void +async_get_call_done (MMModemLocation *self, + GHashTable *locations, + GError *error, + gpointer user_data) +{ + DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data; + + if (error) + dbus_g_method_return_error (context, error); + else + dbus_g_method_return (context, locations); +} + +static void +loc_get_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemLocation *self = MM_MODEM_LOCATION (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the location */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_location_get_location (self, async_get_call_done, context); +} + +static void +impl_modem_location_get_location (MMModemLocation *modem, + DBusGMethodInvocation *context) +{ + GError *error = NULL; + LocAuthInfo *info; + + info = loc_auth_info_new (FALSE, FALSE); + + /* Make sure the caller is authorized to enable location gathering */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_LOCATION, + context, + loc_get_auth_cb, + info, + loc_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +mm_modem_location_init (gpointer g_iface) +{ + static gboolean initialized = FALSE; + + if (initialized) + return; + + /* Properties */ + g_object_interface_install_property + (g_iface, + g_param_spec_boxed (MM_MODEM_LOCATION_LOCATION, + "Location", + "Available location information", + G_TYPE_HASH_TABLE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_uint (MM_MODEM_LOCATION_CAPABILITIES, + "Capabilities", + "Supported location information methods", + MM_MODEM_LOCATION_CAPABILITY_UNKNOWN, + MM_MODEM_LOCATION_CAPABILITY_GPS_NMEA + | MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI, + MM_MODEM_LOCATION_CAPABILITY_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_boolean (MM_MODEM_LOCATION_ENABLED, + "Enabled", + "Whether or not location gathering is enabled", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_boolean (MM_MODEM_LOCATION_SIGNALS_LOCATION, + "SignalsLocation", + "Whether or not location updates are emitted as signals", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + initialized = TRUE; +} + +GType +mm_modem_location_get_type (void) +{ + static GType loc_type = 0; + + if (!G_UNLIKELY (loc_type)) { + const GTypeInfo loc_info = { + sizeof (MMModemLocation), /* class_size */ + mm_modem_location_init, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + loc_type = g_type_register_static (G_TYPE_INTERFACE, + "MMModemLocation", + &loc_info, 0); + + g_type_interface_add_prerequisite (loc_type, G_TYPE_OBJECT); + dbus_g_object_type_install_info (loc_type, &dbus_glib_mm_modem_location_object_info); + + /* Register some shadow properties to handle Enabled and Capabilities + * since these could be used by other interfaces. + */ + dbus_g_object_type_register_shadow_property (loc_type, + "Enabled", + MM_MODEM_LOCATION_ENABLED); + dbus_g_object_type_register_shadow_property (loc_type, + "Capabilities", + MM_MODEM_LOCATION_CAPABILITIES); + } + + return loc_type; +} diff --git a/src/mm-modem-location.h b/src/mm-modem-location.h new file mode 100644 index 0000000..dd622f1 --- /dev/null +++ b/src/mm-modem-location.h @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_MODEM_LOCATION_H +#define MM_MODEM_LOCATION_H + +#include <mm-modem.h> + +#define MM_TYPE_MODEM_LOCATION (mm_modem_location_get_type ()) +#define MM_MODEM_LOCATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_LOCATION, MMModemLocation)) +#define MM_IS_MODEM_LOCATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_LOCATION)) +#define MM_MODEM_LOCATION_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_LOCATION, MMModemLocation)) + +#define MM_MODEM_LOCATION_CAPABILITIES "location-capabilities" +#define MM_MODEM_LOCATION_ENABLED "location-enabled" +#define MM_MODEM_LOCATION_SIGNALS_LOCATION "signals-location" +#define MM_MODEM_LOCATION_LOCATION "location" + +typedef enum { + MM_MODEM_LOCATION_CAPABILITY_UNKNOWN = 0x00000000, + MM_MODEM_LOCATION_CAPABILITY_GPS_NMEA = 0x00000001, + MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI = 0x00000002, + + MM_MODEM_LOCATION_CAPABILITY_LAST = MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI +} MMModemLocationCapabilities; + +typedef struct _MMModemLocation MMModemLocation; + +typedef void (*MMModemLocationGetFn) (MMModemLocation *modem, + GHashTable *locations, + GError *error, + gpointer user_data); + +struct _MMModemLocation { + GTypeInterface g_iface; + + /* Methods */ + void (*enable) (MMModemLocation *modem, + gboolean enable, + gboolean signal_location, + MMModemFn callback, + gpointer user_data); + + void (*get_location) (MMModemLocation *modem, + MMModemLocationGetFn callback, + gpointer user_data); +}; + +GType mm_modem_location_get_type (void); + +void mm_modem_location_enable (MMModemLocation *self, + gboolean enable, + gboolean signal_location, + MMModemFn callback, + gpointer user_data); + +void mm_modem_location_get_location (MMModemLocation *self, + MMModemLocationGetFn callback, + gpointer user_data); + +#endif /* MM_MODEM_LOCATION_H */ diff --git a/src/mm-modem.c b/src/mm-modem.c index a65d883..221c9ea 100644 --- a/src/mm-modem.c +++ b/src/mm-modem.c @@ -14,9 +14,11 @@ * Copyright (C) 2009 - 2010 Red Hat, Inc. */ +#include <config.h> #include <string.h> #include <dbus/dbus-glib.h> #include "mm-modem.h" +#include "mm-options.h" #include "mm-errors.h" #include "mm-callback-info.h" #include "mm-marshal.h" @@ -26,6 +28,7 @@ static void impl_modem_connect (MMModem *modem, const char *number, DBusGMethodI static void impl_modem_disconnect (MMModem *modem, DBusGMethodInvocation *context); static void impl_modem_get_ip4_config (MMModem *modem, DBusGMethodInvocation *context); static void impl_modem_get_info (MMModem *modem, DBusGMethodInvocation *context); +static void impl_modem_factory_reset (MMModem *modem, const char *code, DBusGMethodInvocation *context); #include "mm-modem-glue.h" @@ -148,12 +151,15 @@ disable_disconnect_done (MMModem *self, } if (error) { + char *device = mm_modem_get_device (self); + /* Don't really care what the error was; log it and proceed to disable */ g_warning ("%s: (%s): error disconnecting the modem while disabling: (%d) %s", __func__, - mm_modem_get_device (self), + device, error ? error->code : -1, error && error->message ? error->message : "(unknown)"); + g_free (device); } finish_disable (self, cb_data->callback, cb_data->user_data); g_free (cb_data); @@ -471,6 +477,106 @@ impl_modem_get_info (MMModem *modem, /*****************************************************************************/ +static void +factory_reset_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModem *self = MM_MODEM (owner); + const char *code = user_data; + GError *error = NULL; + + /* Return any authorization error, otherwise try to reset the modem */ + if (!mm_modem_auth_finish (self, req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_factory_reset (self, code, async_call_done, context); +} + +static void +impl_modem_factory_reset (MMModem *modem, + const char *code, + DBusGMethodInvocation *context) +{ + GError *error = NULL; + + /* Make sure the caller is authorized to reset the device */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_CONTROL, + context, + factory_reset_auth_cb, + g_strdup (code), + g_free, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +void +mm_modem_factory_reset (MMModem *self, + const char *code, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + g_return_if_fail (code != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->factory_reset) + MM_MODEM_GET_INTERFACE (self)->factory_reset (self, code, callback, user_data); + else + async_op_not_supported (self, callback, user_data); +} + +/*****************************************************************************/ + +void +mm_modem_get_supported_charsets (MMModem *self, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->get_supported_charsets) + MM_MODEM_GET_INTERFACE (self)->get_supported_charsets (self, callback, user_data); + else { + info = mm_callback_info_uint_new (MM_MODEM (self), callback, user_data); + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + mm_callback_info_schedule (info); + } +} + +void +mm_modem_set_charset (MMModem *self, + MMModemCharset charset, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + g_return_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN); + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->set_charset) + MM_MODEM_GET_INTERFACE (self)->set_charset (self, charset, callback, user_data); + else { + info = mm_callback_info_new (MM_MODEM (self), callback, user_data); + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + mm_callback_info_schedule (info); + } +} + +/*****************************************************************************/ + gboolean mm_modem_owns_port (MMModem *self, const char *subsys, @@ -598,16 +704,76 @@ mm_modem_set_state (MMModem *self, dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG); if (dbus_path) { - g_message ("Modem %s: state changed (%s -> %s)", - dbus_path, - state_to_string (old_state), - state_to_string (new_state)); + if (mm_options_debug ()) { + GTimeVal tv; + + g_get_current_time (&tv); + g_debug ("<%ld.%ld> Modem %s: state changed (%s -> %s)", + tv.tv_sec, + tv.tv_usec, + dbus_path, + state_to_string (old_state), + state_to_string (new_state)); + } else { + g_message ("Modem %s: state changed (%s -> %s)", + dbus_path, + state_to_string (old_state), + state_to_string (new_state)); + } } } } /*****************************************************************************/ +gboolean +mm_modem_auth_request (MMModem *self, + const char *authorization, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify, + GError **error) +{ + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (MM_IS_MODEM (self), FALSE); + g_return_val_if_fail (authorization != NULL, FALSE); + g_return_val_if_fail (context != NULL, FALSE); + g_return_val_if_fail (callback != NULL, FALSE); + + g_return_val_if_fail (MM_MODEM_GET_INTERFACE (self)->auth_request, FALSE); + return MM_MODEM_GET_INTERFACE (self)->auth_request (self, + authorization, + context, + callback, + callback_data, + notify, + error); +} + +gboolean +mm_modem_auth_finish (MMModem *self, + MMAuthRequest *req, + GError **error) +{ + gboolean success; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (MM_IS_MODEM (self), FALSE); + g_return_val_if_fail (req != NULL, FALSE); + + g_return_val_if_fail (MM_MODEM_GET_INTERFACE (self)->auth_finish, FALSE); + success = MM_MODEM_GET_INTERFACE (self)->auth_finish (self, req, error); + + /* If the request failed, the implementor *should* return an error */ + if (!success && error) + g_warn_if_fail (*error != NULL); + + return success; +} + +/*****************************************************************************/ + static void mm_modem_init (gpointer g_iface) { @@ -694,6 +860,31 @@ mm_modem_init (gpointer g_iface) FALSE, G_PARAM_READABLE)); + g_object_interface_install_property + (g_iface, + g_param_spec_string (MM_MODEM_EQUIPMENT_IDENTIFIER, + "EquipmentIdentifier", + "The equipment identifier of the device", + NULL, + G_PARAM_READABLE)); + + g_object_interface_install_property + (g_iface, + g_param_spec_string (MM_MODEM_UNLOCK_REQUIRED, + "UnlockRequired", + "Whether or not the modem requires an unlock " + "code to become usable, and if so, which unlock code is required", + NULL, + G_PARAM_READABLE)); + + g_object_interface_install_property + (g_iface, + g_param_spec_uint (MM_MODEM_UNLOCK_RETRIES, + "UnlockRetries", + "The remaining number of unlock attempts", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE)); + /* Signals */ g_signal_new ("state-changed", iface_type, diff --git a/src/mm-modem.h b/src/mm-modem.h index e8dd7ea..0915180 100644 --- a/src/mm-modem.h +++ b/src/mm-modem.h @@ -18,8 +18,11 @@ #define MM_MODEM_H #include <glib-object.h> +#include <dbus/dbus-glib-lowlevel.h> #include "mm-port.h" +#include "mm-auth-provider.h" +#include "mm-charsets.h" typedef enum { MM_MODEM_STATE_UNKNOWN = 0, @@ -55,6 +58,9 @@ typedef enum { #define MM_MODEM_TYPE "type" #define MM_MODEM_IP_METHOD "ip-method" #define MM_MODEM_ENABLED "enabled" +#define MM_MODEM_EQUIPMENT_IDENTIFIER "equipment-identifier" +#define MM_MODEM_UNLOCK_REQUIRED "unlock-required" +#define MM_MODEM_UNLOCK_RETRIES "unlock-retries" #define MM_MODEM_VALID "valid" /* not exported */ #define MM_MODEM_PLUGIN "plugin" /* not exported */ #define MM_MODEM_STATE "state" /* not exported */ @@ -78,7 +84,10 @@ typedef enum { MM_MODEM_PROP_VALID, /* Not exported */ MM_MODEM_PROP_PLUGIN, /* Not exported */ MM_MODEM_PROP_STATE, /* Not exported */ - MM_MODEM_PROP_ENABLED + MM_MODEM_PROP_ENABLED, + MM_MODEM_PROP_EQUIPMENT_IDENTIFIER, + MM_MODEM_PROP_UNLOCK_REQUIRED, + MM_MODEM_PROP_UNLOCK_RETRIES } MMModemProp; typedef struct _MMModem MMModem; @@ -154,6 +163,36 @@ struct _MMModem { MMModemInfoFn callback, gpointer user_data); + void (*get_supported_charsets) (MMModem *self, + MMModemUIntFn callback, + gpointer user_data); + + void (*set_charset) (MMModem *self, + MMModemCharset charset, + MMModemFn callback, + gpointer user_data); + + + /* Normally implemented by the modem base class; plugins should + * never need to implement this. + */ + gboolean (*auth_request) (MMModem *self, + const char *authorization, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify, + GError **error); + + gboolean (*auth_finish) (MMModem *self, + MMAuthRequest *req, + GError **error); + + void (*factory_reset) (MMModem *self, + const char *code, + MMModemFn callback, + gpointer user_data); + /* Signals */ void (*state_changed) (MMModem *self, MMModemState new_state, @@ -203,6 +242,20 @@ void mm_modem_get_info (MMModem *self, MMModemInfoFn callback, gpointer user_data); +void mm_modem_get_supported_charsets (MMModem *self, + MMModemUIntFn callback, + gpointer user_data); + +void mm_modem_set_charset (MMModem *self, + MMModemCharset charset, + MMModemFn callback, + gpointer user_data); + +void mm_modem_factory_reset (MMModem *self, + const char *code, + MMModemFn callback, + gpointer user_data); + gboolean mm_modem_get_valid (MMModem *self); char *mm_modem_get_device (MMModem *self); @@ -215,5 +268,21 @@ void mm_modem_set_state (MMModem *self, GError *mm_modem_check_removed (MMModem *self, const GError *error); +/* Request authorization to perform an action. Used by D-Bus method + * handlers to ensure that the incoming request is authorized to perform + * the action it's requesting. + */ +gboolean mm_modem_auth_request (MMModem *self, + const char *authorization, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify, + GError **error); + +gboolean mm_modem_auth_finish (MMModem *self, + MMAuthRequest *req, + GError **error); + #endif /* MM_MODEM_H */ diff --git a/src/mm-plugin-base.c b/src/mm-plugin-base.c index 202eaac..80d0f90 100644 --- a/src/mm-plugin-base.c +++ b/src/mm-plugin-base.c @@ -11,7 +11,7 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #define _GNU_SOURCE /* for strcasestr */ @@ -26,10 +26,14 @@ #include <gudev/gudev.h> #include "mm-plugin-base.h" -#include "mm-serial-port.h" +#include "mm-at-serial-port.h" +#include "mm-qcdm-serial-port.h" #include "mm-serial-parsers.h" #include "mm-errors.h" #include "mm-marshal.h" +#include "mm-utils.h" +#include "libqcdm/src/commands.h" +#include "libqcdm/src/utils.h" static void plugin_init (MMPlugin *plugin_class); @@ -48,8 +52,6 @@ static GHashTable *cached_caps = NULL; typedef struct { char *name; GUdevClient *client; - - GHashTable *modems; GHashTable *tasks; } MMPluginBasePrivate; @@ -77,6 +79,8 @@ typedef enum { PROBE_STATE_LAST } ProbeState; +static void probe_complete (MMPluginBaseSupportsTask *task); + /*****************************************************************************/ G_DEFINE_TYPE (MMPluginBaseSupportsTask, mm_plugin_base_supports_task, G_TYPE_OBJECT) @@ -86,13 +90,15 @@ G_DEFINE_TYPE (MMPluginBaseSupportsTask, mm_plugin_base_supports_task, G_TYPE_OB typedef struct { MMPluginBase *plugin; GUdevDevice *port; - GUdevDevice *physdev; + char *physdev_path; char *driver; guint open_id; guint32 open_tries; + guint full_id; - MMSerialPort *probe_port; + MMAtSerialPort *probe_port; + MMQcdmSerialPort *qcdm_port; guint32 probed_caps; ProbeState probe_state; guint probe_id; @@ -112,7 +118,7 @@ typedef struct { static MMPluginBaseSupportsTask * supports_task_new (MMPluginBase *self, GUdevDevice *port, - GUdevDevice *physdev, + const char *physdev_path, const char *driver, MMSupportsPortResultFunc callback, gpointer callback_data) @@ -123,7 +129,7 @@ supports_task_new (MMPluginBase *self, g_return_val_if_fail (self != NULL, NULL); g_return_val_if_fail (MM_IS_PLUGIN_BASE (self), NULL); g_return_val_if_fail (port != NULL, NULL); - g_return_val_if_fail (physdev != NULL, NULL); + g_return_val_if_fail (physdev_path != NULL, NULL); g_return_val_if_fail (driver != NULL, NULL); g_return_val_if_fail (callback != NULL, NULL); @@ -132,7 +138,7 @@ supports_task_new (MMPluginBase *self, priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); priv->plugin = self; priv->port = g_object_ref (port); - priv->physdev = g_object_ref (physdev); + priv->physdev_path = g_strdup (physdev_path); priv->driver = g_strdup (driver); priv->callback = callback; priv->callback_data = callback_data; @@ -158,13 +164,13 @@ mm_plugin_base_supports_task_get_port (MMPluginBaseSupportsTask *task) return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->port; } -GUdevDevice * -mm_plugin_base_supports_task_get_physdev (MMPluginBaseSupportsTask *task) +const char * +mm_plugin_base_supports_task_get_physdev_path (MMPluginBaseSupportsTask *task) { g_return_val_if_fail (task != NULL, NULL); g_return_val_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task), NULL); - return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->physdev; + return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->physdev_path; } const char * @@ -198,6 +204,11 @@ mm_plugin_base_supports_task_complete (MMPluginBaseSupportsTask *task, priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); g_return_if_fail (priv->callback != NULL); + if (priv->full_id) { + g_source_remove (priv->full_id); + priv->full_id = 0; + } + subsys = g_udev_device_get_subsystem (priv->port); name = g_udev_device_get_name (priv->port); @@ -239,11 +250,11 @@ supports_task_dispose (GObject *object) { MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (object); - if (MM_IS_SERIAL_PORT (priv->port)) - mm_serial_port_flash_cancel (MM_SERIAL_PORT (priv->port)); + if (MM_IS_SERIAL_PORT (priv->probe_port)) + mm_serial_port_flash_cancel (MM_SERIAL_PORT (priv->probe_port)); g_object_unref (priv->port); - g_object_unref (priv->physdev); + g_free (priv->physdev_path); g_free (priv->driver); g_free (priv->probe_resp); g_clear_error (&(priv->probe_error)); @@ -251,11 +262,19 @@ supports_task_dispose (GObject *object) if (priv->open_id) g_source_remove (priv->open_id); + if (priv->full_id) + g_source_remove (priv->full_id); if (priv->probe_id) g_source_remove (priv->probe_id); - if (priv->probe_port) + if (priv->probe_port) { + mm_serial_port_close (MM_SERIAL_PORT (priv->probe_port)); g_object_unref (priv->probe_port); + } + if (priv->qcdm_port) { + mm_serial_port_close (MM_SERIAL_PORT (priv->qcdm_port)); + g_object_unref (priv->qcdm_port); + } G_OBJECT_CLASS (mm_plugin_base_supports_task_parent_class)->dispose (object); } @@ -334,7 +353,8 @@ parse_cpin (const char *buf) || strcasestr (buf, "PH-SP PIN") || strcasestr (buf, "PH-SP PUK") || strcasestr (buf, "PH-CORP PIN") - || strcasestr (buf, "PH-CORP PUK")) + || strcasestr (buf, "PH-CORP PUK") + || strcasestr (buf, "READY")) return MM_PLUGIN_BASE_PORT_CAP_GSM; return 0; @@ -349,6 +369,49 @@ parse_cgmm (const char *buf) return 0; } +static const char *dq_strings[] = { + /* Option Icera-based devices */ + "option/faema_", + "os_logids.h", + /* Sierra CnS port */ + "NETWORK SERVICE CHANGE", + NULL +}; + +static void +port_buffer_full (MMSerialPort *port, GByteArray *buffer, gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (user_data); + const char **iter; + size_t iter_len; + int i; + + /* Check for an immediate disqualification response. There are some + * ports (Option Icera-based chipsets have them, as do Qualcomm Gobi + * devices before their firmware is loaded) that just shouldn't be + * probed if we get a certain response because we know they can't be + * used. Kernel bugs (at least with 2.6.31 and 2.6.32) also trigger port + * flow control kernel oopses if we read too much data for these ports. + */ + + for (iter = &dq_strings[0]; iter && *iter; iter++) { + /* Search in the response for the item; the response could have embedded + * nulls so we can't use memcmp() or strstr() on the whole response. + */ + iter_len = strlen (*iter); + for (i = 0; i < buffer->len - iter_len; i++) { + if (!memcmp (&buffer->data[i], *iter, iter_len)) { + /* Immediately close the port and complete probing */ + priv->probed_caps = 0; + mm_serial_port_close (MM_SERIAL_PORT (priv->probe_port)); + probe_complete (task); + return; + } + } + } +} + static gboolean emit_probe_result (gpointer user_data) { @@ -356,9 +419,15 @@ emit_probe_result (gpointer user_data) MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); MMPlugin *self = mm_plugin_base_supports_task_get_plugin (task); - /* Close the serial port */ - g_object_unref (task_priv->probe_port); - task_priv->probe_port = NULL; + /* Close the serial ports */ + if (task_priv->probe_port) { + g_object_unref (task_priv->probe_port); + task_priv->probe_port = NULL; + } + if (task_priv->qcdm_port) { + g_object_unref (task_priv->qcdm_port); + task_priv->qcdm_port = NULL; + } task_priv->probe_id = 0; g_signal_emit (self, signals[PROBE_RESULT], 0, task, task_priv->probed_caps); @@ -368,17 +437,122 @@ emit_probe_result (gpointer user_data) static void probe_complete (MMPluginBaseSupportsTask *task) { - MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + MMPort *port; + port = priv->probe_port ? MM_PORT (priv->probe_port) : MM_PORT (priv->qcdm_port); + g_assert (port); g_hash_table_insert (cached_caps, - g_strdup (mm_port_get_device (MM_PORT (task_priv->probe_port))), - GUINT_TO_POINTER (task_priv->probed_caps)); + g_strdup (mm_port_get_device (port)), + GUINT_TO_POINTER (priv->probed_caps)); + + priv->probe_id = g_idle_add (emit_probe_result, task); +} + +static void +qcdm_verinfo_cb (MMQcdmSerialPort *port, + GByteArray *response, + GError *error, + gpointer user_data) +{ + MMPluginBaseSupportsTask *task; + MMPluginBaseSupportsTaskPrivate *priv; + QCDMResult *result; + GError *dm_error = NULL; + + /* Just the initial poke; ignore it */ + if (!user_data) + return; + + task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + + if (error) { + /* Probably not a QCDM port */ + goto done; + } + + /* Parse the response */ + result = qcdm_cmd_version_info_result ((const char *) response->data, response->len, &dm_error); + if (!result) { + g_warning ("(%s) failed to parse QCDM version info command result: (%d) %s.", + g_udev_device_get_name (priv->port), + dm_error ? dm_error->code : -1, + dm_error && dm_error->message ? dm_error->message : "(unknown)"); + g_clear_error (&dm_error); + goto done; + } + + /* yay, probably a QCDM port */ + qcdm_result_unref (result); + priv->probed_caps |= MM_PLUGIN_BASE_PORT_CAP_QCDM; - task_priv->probe_id = g_idle_add (emit_probe_result, task); +done: + probe_complete (task); } static void -parse_response (MMSerialPort *port, +try_qcdm_probe (MMPluginBaseSupportsTask *task) +{ + MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + const char *name; + GError *error = NULL; + GByteArray *verinfo = NULL, *verinfo2; + gint len; + + /* Close the AT port */ + if (priv->probe_port) { + mm_serial_port_close (MM_SERIAL_PORT (priv->probe_port)); + g_object_unref (priv->probe_port); + priv->probe_port = NULL; + } + + /* Open the QCDM port */ + name = g_udev_device_get_name (priv->port); + g_assert (name); + priv->qcdm_port = mm_qcdm_serial_port_new (name, MM_PORT_TYPE_PRIMARY); + if (priv->qcdm_port == NULL) { + g_warning ("(%s) failed to create new QCDM serial port.", name); + probe_complete (task); + return; + } + + if (!mm_serial_port_open (MM_SERIAL_PORT (priv->qcdm_port), &error)) { + g_warning ("(%s) failed to open new QCDM serial port: (%d) %s.", + name, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + probe_complete (task); + return; + } + + /* Build up the probe command */ + verinfo = g_byte_array_sized_new (50); + len = qcdm_cmd_version_info_new ((char *) verinfo->data, 50, &error); + if (len <= 0) { + g_byte_array_free (verinfo, TRUE); + g_warning ("(%s) failed to create QCDM version info command: (%d) %s.", + name, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + probe_complete (task); + return; + } + verinfo->len = len; + + /* Queuing the command takes ownership over it; copy it for the second try */ + verinfo2 = g_byte_array_sized_new (verinfo->len); + g_byte_array_append (verinfo2, verinfo->data, verinfo->len); + + /* Send the command twice; the ports often need to be woken up */ + mm_qcdm_serial_port_queue_command (priv->qcdm_port, verinfo, 3, qcdm_verinfo_cb, NULL); + mm_qcdm_serial_port_queue_command (priv->qcdm_port, verinfo2, 3, qcdm_verinfo_cb, task); +} + +static void +parse_response (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data); @@ -391,7 +565,7 @@ real_handle_probe_response (MMPluginBase *self, const GError *error) { MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); - MMSerialPort *port = task_priv->probe_port; + MMAtSerialPort *port = task_priv->probe_port; gboolean ignore_error = FALSE; /* Some modems (Huawei E160g) won't respond to +GCAP with no SIM, but @@ -401,16 +575,16 @@ real_handle_probe_response (MMPluginBase *self, ignore_error = TRUE; if (error && !ignore_error) { - if (error->code == MM_SERIAL_RESPONSE_TIMEOUT) { + if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { /* Try GCAP again */ if (task_priv->probe_state < PROBE_STATE_GCAP_TRY3) { task_priv->probe_state++; - mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, task); + mm_at_serial_port_queue_command (port, "+GCAP", 3, parse_response, task); } else { /* Otherwise, if all the GCAP tries timed out, ignore the port - * as it's probably not an AT-capable port. + * as it's probably not an AT-capable port. Try QCDM. */ - probe_complete (task); + try_qcdm_probe (task); } return; } @@ -459,19 +633,19 @@ real_handle_probe_response (MMPluginBase *self, switch (task_priv->probe_state) { case PROBE_STATE_GCAP_TRY2: case PROBE_STATE_GCAP_TRY3: - mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, task); + mm_at_serial_port_queue_command (port, "+GCAP", 3, parse_response, task); break; case PROBE_STATE_ATI: /* After the last GCAP attempt, try ATI */ - mm_serial_port_queue_command (port, "I", 3, parse_response, task); + mm_at_serial_port_queue_command (port, "I", 3, parse_response, task); break; case PROBE_STATE_CPIN: /* After the ATI attempt, try CPIN */ - mm_serial_port_queue_command (port, "+CPIN?", 3, parse_response, task); + mm_at_serial_port_queue_command (port, "+CPIN?", 3, parse_response, task); break; case PROBE_STATE_CGMM: /* After the CPIN attempt, try CGMM */ - mm_serial_port_queue_command (port, "+CGMM", 3, parse_response, task); + mm_at_serial_port_queue_command (port, "+CGMM", 3, parse_response, task); break; default: /* Probably not GSM or CDMA */ @@ -515,7 +689,7 @@ handle_probe_response (gpointer user_data) } static void -parse_response (MMSerialPort *port, +parse_response (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -545,7 +719,7 @@ parse_response (MMSerialPort *port, static void flash_done (MMSerialPort *port, GError *error, gpointer user_data); static void -custom_init_response (MMSerialPort *port, +custom_init_response (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -557,11 +731,11 @@ custom_init_response (MMSerialPort *port, task_priv->custom_init_tries++; if (task_priv->custom_init_tries < task_priv->custom_init_max_tries) { /* Try the custom command again */ - flash_done (port, NULL, user_data); + flash_done (MM_SERIAL_PORT (port), NULL, user_data); return; } else if (task_priv->custom_init_fail_if_timeout) { /* Fail the probe if the plugin wanted it and the command timed out */ - if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_RESPONSE_TIMEOUT)) { + if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { probe_complete (task); return; } @@ -569,7 +743,7 @@ custom_init_response (MMSerialPort *port, } /* Otherwise proceed to probing */ - mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, user_data); + mm_at_serial_port_queue_command (port, "+GCAP", 3, parse_response, user_data); } static void @@ -583,14 +757,14 @@ flash_done (MMSerialPort *port, GError *error, gpointer user_data) if (task_priv->custom_init) { if (!delay_secs) delay_secs = 3; - mm_serial_port_queue_command (port, - task_priv->custom_init, - delay_secs, - custom_init_response, - user_data); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), + task_priv->custom_init, + delay_secs, + custom_init_response, + user_data); } else { /* Otherwise start normal probing */ - custom_init_response (port, NULL, NULL, user_data); + custom_init_response (MM_AT_SERIAL_PORT (port), NULL, NULL, user_data); } } @@ -603,7 +777,7 @@ try_open (gpointer user_data) task_priv->open_id = 0; - if (!mm_serial_port_open (task_priv->probe_port, &error)) { + if (!mm_serial_port_open (MM_SERIAL_PORT (task_priv->probe_port), &error)) { if (++task_priv->open_tries > 4) { /* took too long to open the port; give up */ g_warning ("(%s): failed to open after 4 tries.", @@ -611,7 +785,7 @@ try_open (gpointer user_data) probe_complete (task); } else if (g_error_matches (error, MM_SERIAL_ERROR, - MM_SERIAL_OPEN_FAILED_NO_DEVICE)) { + MM_SERIAL_ERROR_OPEN_FAILED_NO_DEVICE)) { /* this is nozomi being dumb; try again */ task_priv->open_id = g_timeout_add_seconds (1, try_open, task); } else { @@ -626,10 +800,13 @@ try_open (gpointer user_data) port = mm_plugin_base_supports_task_get_port (task); g_assert (port); + task_priv->full_id = g_signal_connect (task_priv->probe_port, "buffer-full", + G_CALLBACK (port_buffer_full), task); + g_debug ("(%s): probe requested by plugin '%s'", g_udev_device_get_name (port), mm_plugin_get_name (MM_PLUGIN (task_priv->plugin))); - mm_serial_port_flash (task_priv->probe_port, 100, flash_done, task); + mm_serial_port_flash (MM_SERIAL_PORT (task_priv->probe_port), 100, TRUE, flash_done, task); } return FALSE; @@ -641,7 +818,7 @@ mm_plugin_base_probe_port (MMPluginBase *self, GError **error) { MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); - MMSerialPort *serial; + MMAtSerialPort *serial; const char *name; GUdevDevice *port; @@ -654,7 +831,7 @@ mm_plugin_base_probe_port (MMPluginBase *self, name = g_udev_device_get_name (port); g_assert (name); - serial = mm_serial_port_new (name, MM_PORT_TYPE_PRIMARY); + serial = mm_at_serial_port_new (name, MM_PORT_TYPE_PRIMARY); if (serial == NULL) { g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Failed to create new serial port."); @@ -666,10 +843,10 @@ mm_plugin_base_probe_port (MMPluginBase *self, MM_PORT_CARRIER_DETECT, FALSE, NULL); - mm_serial_port_set_response_parser (serial, - mm_serial_parser_v1_parse, - mm_serial_parser_v1_new (), - mm_serial_parser_v1_destroy); + mm_at_serial_port_set_response_parser (serial, + mm_serial_parser_v1_parse, + mm_serial_parser_v1_new (), + mm_serial_parser_v1_destroy); /* Open the port */ task_priv->probe_port = serial; @@ -695,20 +872,6 @@ mm_plugin_base_get_cached_port_capabilities (MMPluginBase *self, static void modem_destroyed (gpointer data, GObject *modem) { - MMPluginBase *self = MM_PLUGIN_BASE (data); - MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); - GHashTableIter iter; - gpointer key, value; - - /* Remove it from the modems info */ - g_hash_table_iter_init (&iter, priv->modems); - while (g_hash_table_iter_next (&iter, &key, &value)) { - if (value == modem) { - g_hash_table_iter_remove (&iter); - break; - } - } - /* Since we don't track port cached capabilities on a per-modem basis, * we just have to live with blowing away the cached capabilities whenever * a modem gets removed. Could do better here by storing a structure @@ -719,48 +882,6 @@ modem_destroyed (gpointer data, GObject *modem) g_hash_table_remove_all (cached_caps); } -MMModem * -mm_plugin_base_find_modem (MMPluginBase *self, - const char *master_device) -{ - MMPluginBasePrivate *priv; - - g_return_val_if_fail (self != NULL, NULL); - g_return_val_if_fail (MM_IS_PLUGIN_BASE (self), NULL); - g_return_val_if_fail (master_device != NULL, NULL); - g_return_val_if_fail (strlen (master_device) > 0, NULL); - - priv = MM_PLUGIN_BASE_GET_PRIVATE (self); - return g_hash_table_lookup (priv->modems, master_device); -} - -/* From hostap, Copyright (c) 2002-2005, Jouni Malinen <jkmaline@cc.hut.fi> */ - -static int hex2num (char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - return -1; -} - -static int hex2byte (const char *hex) -{ - int a, b; - a = hex2num(*hex++); - if (a < 0) - return -1; - b = hex2num(*hex++); - if (b < 0) - return -1; - return (a << 4) | b; -} - -/* End from hostap */ - gboolean mm_plugin_base_get_device_ids (MMPluginBase *self, const char *subsys, @@ -793,8 +914,8 @@ mm_plugin_base_get_device_ids (MMPluginBase *self, goto out; if (vendor) { - *vendor = (guint16) (hex2byte (vid + 2) & 0xFF); - *vendor |= (guint16) ((hex2byte (vid) & 0xFF) << 8); + *vendor = (guint16) (utils_hex2byte (vid + 2) & 0xFF); + *vendor |= (guint16) ((utils_hex2byte (vid) & 0xFF) << 8); } pid = g_udev_device_get_property (device, "ID_MODEL_ID"); @@ -804,8 +925,8 @@ mm_plugin_base_get_device_ids (MMPluginBase *self, } if (product) { - *product = (guint16) (hex2byte (pid + 2) & 0xFF); - *product |= (guint16) ((hex2byte (pid) & 0xFF) << 8); + *product = (guint16) (utils_hex2byte (pid + 2) & 0xFF); + *product |= (guint16) ((utils_hex2byte (pid) & 0xFF) << 8); } success = TRUE; @@ -859,58 +980,21 @@ get_driver_name (GUdevDevice *device) return ret; } -static GUdevDevice * -real_find_physical_device (MMPluginBase *plugin, GUdevDevice *child) -{ - GUdevDevice *iter, *old = NULL; - GUdevDevice *physdev = NULL; - const char *subsys, *type; - guint32 i = 0; - gboolean is_usb = FALSE, is_pci = FALSE; - - g_return_val_if_fail (child != NULL, NULL); - - iter = g_object_ref (child); - while (iter && i++ < 8) { - subsys = g_udev_device_get_subsystem (iter); - if (subsys) { - if (is_usb || !strcmp (subsys, "usb")) { - is_usb = TRUE; - type = g_udev_device_get_devtype (iter); - if (type && !strcmp (type, "usb_device")) { - physdev = iter; - break; - } - } else if (is_pci || !strcmp (subsys, "pci")) { - is_pci = TRUE; - physdev = iter; - break; - } - } - - old = iter; - iter = g_udev_device_get_parent (old); - g_object_unref (old); - } - - return physdev; -} - static MMPluginSupportsResult supports_port (MMPlugin *plugin, const char *subsys, const char *name, + const char *physdev_path, + MMModem *existing, MMSupportsPortResultFunc callback, gpointer callback_data) { MMPluginBase *self = MM_PLUGIN_BASE (plugin); MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; char *driver = NULL, *key = NULL; MMPluginBaseSupportsTask *task; MMPluginSupportsResult result = MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; - MMModem *existing; - const char *master_path; key = get_key (subsys, name); task = g_hash_table_lookup (priv->tasks, key); @@ -923,22 +1007,14 @@ supports_port (MMPlugin *plugin, if (!port) goto out; - physdev = MM_PLUGIN_BASE_GET_CLASS (self)->find_physical_device (self, port); - if (!physdev) - goto out; - driver = get_driver_name (port); if (!driver) goto out; - task = supports_task_new (self, port, physdev, driver, callback, callback_data); + task = supports_task_new (self, port, physdev_path, driver, callback, callback_data); g_assert (task); g_hash_table_insert (priv->tasks, g_strdup (key), g_object_ref (task)); - /* Help the plugin out a bit by finding an existing modem for this port */ - master_path = g_udev_device_get_sysfs_path (physdev); - existing = g_hash_table_lookup (priv->modems, master_path); - result = MM_PLUGIN_BASE_GET_CLASS (self)->supports_port (self, existing, task); if (result != MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS) { /* If the plugin doesn't support the port at all, the supports task is @@ -949,8 +1025,6 @@ supports_port (MMPlugin *plugin, g_object_unref (task); out: - if (physdev) - g_object_unref (physdev); if (port) g_object_unref (port); g_free (key); @@ -984,14 +1058,14 @@ static MMModem * grab_port (MMPlugin *plugin, const char *subsys, const char *name, + MMModem *existing, GError **error) { MMPluginBase *self = MM_PLUGIN_BASE (plugin); MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); MMPluginBaseSupportsTask *task; + MMModem *modem = NULL; char *key; - MMModem *existing = NULL, *modem = NULL; - const char *master_path; key = get_key (subsys, name); task = g_hash_table_lookup (priv->tasks, key); @@ -1000,16 +1074,10 @@ grab_port (MMPlugin *plugin, g_return_val_if_fail (task != NULL, FALSE); } - /* Help the plugin out a bit by finding an existing modem for this port */ - master_path = g_udev_device_get_sysfs_path (mm_plugin_base_supports_task_get_physdev (task)); - existing = g_hash_table_lookup (priv->modems, master_path); - /* Let the modem grab the port */ modem = MM_PLUGIN_BASE_GET_CLASS (self)->grab_port (self, existing, task, error); - if (modem && !existing) { - g_hash_table_insert (priv->modems, g_strdup (master_path), modem); + if (modem && !existing) g_object_weak_ref (G_OBJECT (modem), modem_destroyed, self); - } g_hash_table_remove (priv->tasks, key); g_free (key); @@ -1039,7 +1107,6 @@ mm_plugin_base_init (MMPluginBase *self) priv->client = g_udev_client_new (subsys); - priv->modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); priv->tasks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref); } @@ -1086,7 +1153,6 @@ finalize (GObject *object) g_object_unref (priv->client); - g_hash_table_destroy (priv->modems); g_hash_table_destroy (priv->tasks); G_OBJECT_CLASS (mm_plugin_base_parent_class)->finalize (object); @@ -1099,7 +1165,6 @@ mm_plugin_base_class_init (MMPluginBaseClass *klass) g_type_class_add_private (object_class, sizeof (MMPluginBasePrivate)); - klass->find_physical_device = real_find_physical_device; klass->handle_probe_response = real_handle_probe_response; /* Virtual methods */ diff --git a/src/mm-plugin-base.h b/src/mm-plugin-base.h index 49e68d4..a32d53b 100644 --- a/src/mm-plugin-base.h +++ b/src/mm-plugin-base.h @@ -37,6 +37,7 @@ #define MM_PLUGIN_BASE_PORT_CAP_W 0x0080 /* Wireless commands */ #define MM_PLUGIN_BASE_PORT_CAP_IS856 0x0100 /* CDMA 3G EVDO rev 0 */ #define MM_PLUGIN_BASE_PORT_CAP_IS856_A 0x0200 /* CDMA 3G EVDO rev A */ +#define MM_PLUGIN_BASE_PORT_CAP_QCDM 0x0400 /* QCDM-capable port */ #define MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK (mm_plugin_base_supports_task_get_type ()) #define MM_PLUGIN_BASE_SUPPORTS_TASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, MMPluginBaseSupportsTask)) @@ -59,7 +60,7 @@ MMPlugin *mm_plugin_base_supports_task_get_plugin (MMPluginBaseSupportsTask *tas GUdevDevice *mm_plugin_base_supports_task_get_port (MMPluginBaseSupportsTask *task); -GUdevDevice *mm_plugin_base_supports_task_get_physdev (MMPluginBaseSupportsTask *task); +const char *mm_plugin_base_supports_task_get_physdev_path (MMPluginBaseSupportsTask *task); const char *mm_plugin_base_supports_task_get_driver (MMPluginBaseSupportsTask *task); @@ -107,13 +108,6 @@ struct _MMPluginBaseClass { void (*cancel_task) (MMPluginBase *plugin, MMPluginBaseSupportsTask *task); - /* Find a the physical device of a port, ie the USB or PCI or whatever - * "master" device that owns the port. The GUdevDevice object returned - * will be unref-ed by the caller. - */ - GUdevDevice * (*find_physical_device) (MMPluginBase *plugin, - GUdevDevice *port); - void (*handle_probe_response) (MMPluginBase *plugin, MMPluginBaseSupportsTask *task, const char *command, @@ -128,9 +122,6 @@ struct _MMPluginBaseClass { GType mm_plugin_base_get_type (void); -MMModem *mm_plugin_base_find_modem (MMPluginBase *self, - const char *master_device); - gboolean mm_plugin_base_get_device_ids (MMPluginBase *self, const char *subsys, const char *name, diff --git a/src/mm-plugin.c b/src/mm-plugin.c index 830f2d6..7e5c582 100644 --- a/src/mm-plugin.c +++ b/src/mm-plugin.c @@ -28,15 +28,24 @@ MMPluginSupportsResult mm_plugin_supports_port (MMPlugin *plugin, const char *subsys, const char *name, + const char *physdev_path, + MMModem *existing, MMSupportsPortResultFunc callback, gpointer user_data) { g_return_val_if_fail (MM_IS_PLUGIN (plugin), FALSE); g_return_val_if_fail (subsys != NULL, FALSE); g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (physdev_path != NULL, FALSE); g_return_val_if_fail (callback != NULL, FALSE); - return MM_PLUGIN_GET_INTERFACE (plugin)->supports_port (plugin, subsys, name, callback, user_data); + return MM_PLUGIN_GET_INTERFACE (plugin)->supports_port (plugin, + subsys, + name, + physdev_path, + existing, + callback, + user_data); } void @@ -55,13 +64,14 @@ MMModem * mm_plugin_grab_port (MMPlugin *plugin, const char *subsys, const char *name, + MMModem *existing, GError **error) { g_return_val_if_fail (MM_IS_PLUGIN (plugin), FALSE); g_return_val_if_fail (subsys != NULL, FALSE); g_return_val_if_fail (name != NULL, FALSE); - return MM_PLUGIN_GET_INTERFACE (plugin)->grab_port (plugin, subsys, name, error); + return MM_PLUGIN_GET_INTERFACE (plugin)->grab_port (plugin, subsys, name, existing, error); } /*****************************************************************************/ diff --git a/src/mm-plugin.h b/src/mm-plugin.h index d1e85b6..4f1dfc0 100644 --- a/src/mm-plugin.h +++ b/src/mm-plugin.h @@ -70,6 +70,8 @@ struct _MMPlugin { MMPluginSupportsResult (*supports_port) (MMPlugin *self, const char *subsys, const char *name, + const char *physdev_path, + MMModem *existing, MMSupportsPortResultFunc callback, gpointer user_data); @@ -94,6 +96,7 @@ struct _MMPlugin { MMModem * (*grab_port) (MMPlugin *self, const char *subsys, const char *name, + MMModem *existing, GError **error); }; @@ -104,6 +107,8 @@ const char *mm_plugin_get_name (MMPlugin *plugin); MMPluginSupportsResult mm_plugin_supports_port (MMPlugin *plugin, const char *subsys, const char *name, + const char *physdev_path, + MMModem *existing, MMSupportsPortResultFunc callback, gpointer user_data); @@ -114,6 +119,7 @@ void mm_plugin_cancel_supports_port (MMPlugin *plugin, MMModem *mm_plugin_grab_port (MMPlugin *plugin, const char *subsys, const char *name, + MMModem *existing, GError **error); #endif /* MM_PLUGIN_H */ diff --git a/src/mm-port.c b/src/mm-port.c index 7e8edc4..b2018fe 100644 --- a/src/mm-port.c +++ b/src/mm-port.c @@ -19,6 +19,7 @@ #include <string.h> #include "mm-port.h" +#include "mm-options.h" G_DEFINE_TYPE (MMPort, mm_port, G_TYPE_OBJECT) @@ -45,6 +46,26 @@ typedef struct { /*****************************************************************************/ +const char * +mm_port_type_to_name (MMPortType ptype) +{ + switch (ptype) { + case MM_PORT_TYPE_PRIMARY: + return "primary"; + case MM_PORT_TYPE_SECONDARY: + return "secondary"; + case MM_PORT_TYPE_IGNORED: + return "ignored"; + case MM_PORT_TYPE_QCDM: + return "QCDM"; + default: + break; + } + return "(unknown)"; +} + +/*****************************************************************************/ + static GObject* constructor (GType type, guint n_construct_params, @@ -140,6 +161,16 @@ mm_port_set_connected (MMPort *self, gboolean connected) if (priv->connected != connected) { priv->connected = connected; g_object_notify (G_OBJECT (self), MM_PORT_CONNECTED); + if (mm_options_debug()) { + GTimeVal tv; + + g_get_current_time (&tv); + g_debug ("<%ld.%ld> (%s): port now %s", + tv.tv_sec, + tv.tv_usec, + priv->device, + connected ? "connected" : "disconnected"); + } } } diff --git a/src/mm-port.h b/src/mm-port.h index b537618..43f78f4 100644 --- a/src/mm-port.h +++ b/src/mm-port.h @@ -33,8 +33,9 @@ typedef enum { MM_PORT_TYPE_PRIMARY, MM_PORT_TYPE_SECONDARY, MM_PORT_TYPE_IGNORED, + MM_PORT_TYPE_QCDM, - MM_PORT_TYPE_LAST = MM_PORT_TYPE_IGNORED + MM_PORT_TYPE_LAST = MM_PORT_TYPE_QCDM } MMPortType; #define MM_TYPE_PORT (mm_port_get_type ()) @@ -75,5 +76,7 @@ gboolean mm_port_get_connected (MMPort *self); void mm_port_set_connected (MMPort *self, gboolean connected); +const char * mm_port_type_to_name (MMPortType ptype); + #endif /* MM_PORT_H */ diff --git a/src/mm-qcdm-serial-port.c b/src/mm-qcdm-serial-port.c new file mode 100644 index 0000000..1ce27e7 --- /dev/null +++ b/src/mm-qcdm-serial-port.c @@ -0,0 +1,225 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include "mm-qcdm-serial-port.h" +#include "mm-errors.h" +#include "mm-options.h" +#include "libqcdm/src/com.h" +#include "libqcdm/src/utils.h" + +G_DEFINE_TYPE (MMQcdmSerialPort, mm_qcdm_serial_port, MM_TYPE_SERIAL_PORT) + +#define MM_QCDM_SERIAL_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_QCDM_SERIAL_PORT, MMQcdmSerialPortPrivate)) + +typedef struct { + gboolean foo; +} MMQcdmSerialPortPrivate; + + +/*****************************************************************************/ + +static gboolean +parse_response (MMSerialPort *port, GByteArray *response, GError **error) +{ + int i; + + /* Look for the QCDM packet termination character; if we found it, treat + * the buffer as a qcdm command. + */ + for (i = 0; i < response->len; i++) { + if (response->data[i] == 0x7E) + return TRUE; + } + + /* Otherwise, need more data from the device */ + return FALSE; +} + +static gsize +handle_response (MMSerialPort *port, + GByteArray *response, + GError *error, + GCallback callback, + gpointer callback_data) +{ + MMQcdmSerialResponseFn response_callback = (MMQcdmSerialResponseFn) callback; + GByteArray *unescaped = NULL; + GError *dm_error = NULL; + gsize used = 0; + + /* Ignore empty frames */ + if (response->len > 0 && response->data[0] == 0x7E) + return 1; + + if (!error) { + gboolean more = FALSE, success; + gsize unescaped_len = 0; + + /* FIXME: don't munge around with byte array internals */ + unescaped = g_byte_array_sized_new (1024); + success = dm_decapsulate_buffer ((const char *) response->data, + response->len, + (char *) unescaped->data, + 1024, + &unescaped_len, + &used, + &more); + if (!success) { + g_set_error_literal (&dm_error, + MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Failed to unescape QCDM packet."); + g_byte_array_free (unescaped, TRUE); + unescaped = NULL; + } else if (more) { + /* Need more data; we shouldn't have gotten here since the parse + * function checks for the end-of-frame marker, but whatever. + */ + return 0; + } else { + /* Successfully decapsulated the DM command */ + unescaped->len = (guint) unescaped_len; + } + } + + response_callback (MM_QCDM_SERIAL_PORT (port), + unescaped, + dm_error ? dm_error : error, + callback_data); + + if (unescaped) + g_byte_array_free (unescaped, TRUE); + + return used; +} + +/*****************************************************************************/ + +void +mm_qcdm_serial_port_queue_command (MMQcdmSerialPort *self, + GByteArray *command, + guint32 timeout_seconds, + MMQcdmSerialResponseFn callback, + gpointer user_data) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_QCDM_SERIAL_PORT (self)); + g_return_if_fail (command != NULL); + + /* 'command' is expected to be already CRC-ed and escaped */ + mm_serial_port_queue_command (MM_SERIAL_PORT (self), + command, + TRUE, + timeout_seconds, + (MMSerialResponseFn) callback, + user_data); +} + +void +mm_qcdm_serial_port_queue_command_cached (MMQcdmSerialPort *self, + GByteArray *command, + guint32 timeout_seconds, + MMQcdmSerialResponseFn callback, + gpointer user_data) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_QCDM_SERIAL_PORT (self)); + g_return_if_fail (command != NULL); + + /* 'command' is expected to be already CRC-ed and escaped */ + mm_serial_port_queue_command_cached (MM_SERIAL_PORT (self), + command, + TRUE, + timeout_seconds, + (MMSerialResponseFn) callback, + user_data); +} + +static void +debug_log (MMSerialPort *port, const char *prefix, const char *buf, gsize len) +{ + static GString *debug = NULL; + const char *s = buf; + GTimeVal tv; + + if (!debug) + debug = g_string_sized_new (512); + + g_string_append (debug, prefix); + + while (len--) + g_string_append_printf (debug, " %02x", (guint8) (*s++ & 0xFF)); + + g_get_current_time (&tv); + g_debug ("<%ld.%ld> (%s): %s", + tv.tv_sec, + tv.tv_usec, + mm_port_get_device (MM_PORT (port)), + debug->str); + g_string_truncate (debug, 0); +} + +/*****************************************************************************/ + +static gboolean +config_fd (MMSerialPort *port, int fd, GError **error) +{ + return qcdm_port_setup (fd, error); +} + +/*****************************************************************************/ + +MMQcdmSerialPort * +mm_qcdm_serial_port_new (const char *name, MMPortType ptype) +{ + return MM_QCDM_SERIAL_PORT (g_object_new (MM_TYPE_QCDM_SERIAL_PORT, + MM_PORT_DEVICE, name, + MM_PORT_SUBSYS, MM_PORT_SUBSYS_TTY, + MM_PORT_TYPE, ptype, + NULL)); +} + +static void +mm_qcdm_serial_port_init (MMQcdmSerialPort *self) +{ +} + +static void +finalize (GObject *object) +{ + G_OBJECT_CLASS (mm_qcdm_serial_port_parent_class)->finalize (object); +} + +static void +mm_qcdm_serial_port_class_init (MMQcdmSerialPortClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMSerialPortClass *port_class = MM_SERIAL_PORT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMQcdmSerialPortPrivate)); + + /* Virtual methods */ + object_class->finalize = finalize; + + port_class->parse_response = parse_response; + port_class->handle_response = handle_response; + port_class->config_fd = config_fd; + port_class->debug_log = debug_log; +} diff --git a/src/mm-qcdm-serial-port.h b/src/mm-qcdm-serial-port.h new file mode 100644 index 0000000..e70e124 --- /dev/null +++ b/src/mm-qcdm-serial-port.h @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#ifndef MM_QCDM_SERIAL_PORT_H +#define MM_QCDM_SERIAL_PORT_H + +#include <glib.h> +#include <glib/gtypes.h> +#include <glib-object.h> + +#include "mm-serial-port.h" + +#define MM_TYPE_QCDM_SERIAL_PORT (mm_qcdm_serial_port_get_type ()) +#define MM_QCDM_SERIAL_PORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_QCDM_SERIAL_PORT, MMQcdmSerialPort)) +#define MM_QCDM_SERIAL_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_QCDM_SERIAL_PORT, MMQcdmSerialPortClass)) +#define MM_IS_QCDM_SERIAL_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_QCDM_SERIAL_PORT)) +#define MM_IS_QCDM_SERIAL_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_QCDM_SERIAL_PORT)) +#define MM_QCDM_SERIAL_PORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_QCDM_SERIAL_PORT, MMQcdmSerialPortClass)) + +typedef struct _MMQcdmSerialPort MMQcdmSerialPort; +typedef struct _MMQcdmSerialPortClass MMQcdmSerialPortClass; + +typedef void (*MMQcdmSerialResponseFn) (MMQcdmSerialPort *port, + GByteArray *response, + GError *error, + gpointer user_data); + +struct _MMQcdmSerialPort { + MMSerialPort parent; +}; + +struct _MMQcdmSerialPortClass { + MMSerialPortClass parent; +}; + +GType mm_qcdm_serial_port_get_type (void); + +MMQcdmSerialPort *mm_qcdm_serial_port_new (const char *name, MMPortType ptype); + +void mm_qcdm_serial_port_queue_command (MMQcdmSerialPort *self, + GByteArray *command, + guint32 timeout_seconds, + MMQcdmSerialResponseFn callback, + gpointer user_data); + +void mm_qcdm_serial_port_queue_command_cached (MMQcdmSerialPort *self, + GByteArray *command, + guint32 timeout_seconds, + MMQcdmSerialResponseFn callback, + gpointer user_data); + +#endif /* MM_QCDM_SERIAL_PORT_H */ + diff --git a/src/mm-serial-parsers.c b/src/mm-serial-parsers.c index 58985d9..7c9598e 100644 --- a/src/mm-serial-parsers.c +++ b/src/mm-serial-parsers.c @@ -190,7 +190,8 @@ mm_serial_parser_v0_destroy (gpointer data) typedef struct { GRegex *regex_ok; GRegex *regex_connect; - GRegex *regex_detailed_error; + GRegex *regex_cme_error; + GRegex *regex_cme_error_str; GRegex *regex_unknown_error; GRegex *regex_connect_failed; } MMSerialParserV1; @@ -205,7 +206,8 @@ mm_serial_parser_v1_new (void) parser->regex_ok = g_regex_new ("\\r\\nOK(\\r\\n)+$", flags, 0, NULL); parser->regex_connect = g_regex_new ("\\r\\nCONNECT.*\\r\\n", flags, 0, NULL); - parser->regex_detailed_error = g_regex_new ("\\r\\n\\+CME ERROR: (\\d+)\\r\\n$", flags, 0, NULL); + parser->regex_cme_error = g_regex_new ("\\r\\n\\+CME ERROR: (\\d+)\\r\\n$", flags, 0, NULL); + parser->regex_cme_error_str = g_regex_new ("\\r\\n\\+CME ERROR: ([^\\n\\r]+)\\r\\n$", flags, 0, NULL); parser->regex_unknown_error = g_regex_new ("\\r\\n(ERROR)|(COMMAND NOT SUPPORT)\\r\\n$", flags, 0, NULL); parser->regex_connect_failed = g_regex_new ("\\r\\n(NO CARRIER)|(BUSY)|(NO ANSWER)|(NO DIALTONE)\\r\\n$", flags, 0, NULL); @@ -219,9 +221,10 @@ mm_serial_parser_v1_parse (gpointer data, { MMSerialParserV1 *parser = (MMSerialParserV1 *) data; GMatchInfo *match_info; - GError *local_error; - int code; + GError *local_error = NULL; gboolean found = FALSE; + char *str; + int code; g_return_val_if_fail (parser != NULL, FALSE); g_return_val_if_fail (response != NULL, FALSE); @@ -243,56 +246,70 @@ mm_serial_parser_v1_parse (gpointer data, } /* Now failures */ - code = MM_MOBILE_ERROR_UNKNOWN; - local_error = NULL; - found = g_regex_match_full (parser->regex_detailed_error, + /* Numeric CME errors */ + found = g_regex_match_full (parser->regex_cme_error, response->str, response->len, 0, 0, &match_info, NULL); - if (found) { - char *str; + str = g_match_info_fetch (match_info, 1); + g_assert (str); + local_error = mm_mobile_error_for_code (atoi (str)); + g_free (str); + g_match_info_free (match_info); + goto done; + } + /* String CME errors */ + found = g_regex_match_full (parser->regex_cme_error_str, + response->str, response->len, + 0, 0, &match_info, NULL); + if (found) { str = g_match_info_fetch (match_info, 1); - if (str) { - code = atoi (str); - g_free (str); - } + g_assert (str); + local_error = mm_mobile_error_for_string (str); + g_free (str); g_match_info_free (match_info); - } else - found = g_regex_match_full (parser->regex_unknown_error, response->str, response->len, 0, 0, NULL, NULL); + goto done; + } - if (found) - local_error = mm_mobile_error_for_code (code); - else { - found = g_regex_match_full (parser->regex_connect_failed, - response->str, response->len, - 0, 0, &match_info, NULL); - if (found) { - char *str; + /* Last resort; unknown error */ + found = g_regex_match_full (parser->regex_unknown_error, + response->str, response->len, + 0, 0, NULL, NULL); + if (found) { + local_error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN); + goto done; + } - str = g_match_info_fetch (match_info, 1); - if (str) { - if (!strcmp (str, "NO CARRIER")) - code = MM_MODEM_CONNECT_ERROR_NO_CARRIER; - else if (!strcmp (str, "BUSY")) - code = MM_MODEM_CONNECT_ERROR_BUSY; - else if (!strcmp (str, "NO ANSWER")) - code = MM_MODEM_CONNECT_ERROR_NO_ANSWER; - else if (!strcmp (str, "NO DIALTONE")) - code = MM_MODEM_CONNECT_ERROR_NO_DIALTONE; - else - /* uhm... make something up (yes, ok, lie!). */ - code = MM_MODEM_CONNECT_ERROR_NO_CARRIER; + /* Connection failures */ + found = g_regex_match_full (parser->regex_connect_failed, + response->str, response->len, + 0, 0, &match_info, NULL); + if (found) { + str = g_match_info_fetch (match_info, 1); + g_assert (str); + + if (!strcmp (str, "NO CARRIER")) + code = MM_MODEM_CONNECT_ERROR_NO_CARRIER; + else if (!strcmp (str, "BUSY")) + code = MM_MODEM_CONNECT_ERROR_BUSY; + else if (!strcmp (str, "NO ANSWER")) + code = MM_MODEM_CONNECT_ERROR_NO_ANSWER; + else if (!strcmp (str, "NO DIALTONE")) + code = MM_MODEM_CONNECT_ERROR_NO_DIALTONE; + else { + /* uhm... make something up (yes, ok, lie!). */ + code = MM_MODEM_CONNECT_ERROR_NO_CARRIER; + } - g_free (str); - } - g_match_info_free (match_info); + g_free (str); + g_match_info_free (match_info); - local_error = mm_modem_connect_error_for_code (code); - } + local_error = mm_modem_connect_error_for_code (code); } +done: if (found) response_clean (response); @@ -313,7 +330,8 @@ mm_serial_parser_v1_destroy (gpointer data) g_regex_unref (parser->regex_ok); g_regex_unref (parser->regex_connect); - g_regex_unref (parser->regex_detailed_error); + g_regex_unref (parser->regex_cme_error); + g_regex_unref (parser->regex_cme_error_str); g_regex_unref (parser->regex_unknown_error); g_regex_unref (parser->regex_connect_failed); diff --git a/src/mm-serial-port.c b/src/mm-serial-port.c index 2600ae5..ed44167 100644 --- a/src/mm-serial-port.c +++ b/src/mm-serial-port.c @@ -11,14 +11,14 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #define _GNU_SOURCE /* for strcasestr() */ #include <stdio.h> #include <stdlib.h> -#include <termio.h> +#include <termios.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> @@ -51,20 +51,12 @@ enum { #define MM_SERIAL_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_SERIAL_PORT, MMSerialPortPrivate)) typedef struct { + guint32 open_count; int fd; GHashTable *reply_cache; GIOChannel *channel; GQueue *queue; - GString *command; - GString *response; - - gboolean connected; - - /* Response parser data */ - MMSerialResponseParserFn response_parser_fn; - gpointer response_parser_user_data; - GDestroyNotify response_parser_notify; - GSList *unsolicited_msg_handlers; + GByteArray *response; struct termios old_t; @@ -148,12 +140,12 @@ void mm_serial_port_print_config (MMSerialPort *port, const char *detail) { MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (port); - struct termio stbuf; + struct termios stbuf; int err; - err = ioctl (priv->fd, TCGETA, &stbuf); + err = tcgetattr (priv->fd, &stbuf); if (err) { - g_warning ("*** %s (%s): (%s) TCGETA error %d", + g_warning ("*** %s (%s): (%s) tcgetattr() error %d", __func__, detail, mm_port_get_device (MM_PORT (port)), errno); return; } @@ -165,33 +157,6 @@ mm_serial_port_print_config (MMSerialPort *port, const char *detail) } #endif -typedef struct { - GRegex *regex; - MMSerialUnsolicitedMsgFn callback; - gpointer user_data; - GDestroyNotify notify; -} MMUnsolicitedMsgHandler; - -static void -mm_serial_port_set_cached_reply (MMSerialPort *self, - const char *command, - const char *reply) -{ - if (reply) - g_hash_table_insert (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, - g_strdup (command), - g_strdup (reply)); - else - g_hash_table_remove (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, command); -} - -static const char * -mm_serial_port_get_cached_reply (MMSerialPort *self, - const char *command) -{ - return (char *) g_hash_table_lookup (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, command); -} - static int parse_baudrate (guint i) { @@ -327,10 +292,10 @@ parse_stopbits (guint i) } static gboolean -config_fd (MMSerialPort *self, GError **error) +real_config_fd (MMSerialPort *self, int fd, GError **error) { MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); - struct termio stbuf; + struct termios stbuf; int speed; int bits; int parity; @@ -341,9 +306,9 @@ config_fd (MMSerialPort *self, GError **error) parity = parse_parity (priv->parity); stopbits = parse_stopbits (priv->stopbits); - memset (&stbuf, 0, sizeof (struct termio)); - if (ioctl (priv->fd, TCGETA, &stbuf) != 0) { - g_warning ("%s (%s): TCGETA error: %d", + memset (&stbuf, 0, sizeof (struct termios)); + if (tcgetattr (fd, &stbuf) != 0) { + g_warning ("%s (%s): tcgetattr() error: %d", __func__, mm_port_get_device (MM_PORT (self)), errno); @@ -357,10 +322,16 @@ config_fd (MMSerialPort *self, GError **error) stbuf.c_cc[VTIME] = 0; stbuf.c_cc[VEOF] = 1; - stbuf.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | CLOCAL | PARENB); - stbuf.c_cflag |= (speed | bits | CREAD | 0 | parity | stopbits); + /* Use software handshaking */ + stbuf.c_iflag |= (IXON | IXOFF | IXANY); - if (ioctl (priv->fd, TCSETA, &stbuf) < 0) { + /* Set up port speed and serial attributes; also ignore modem control + * lines since most drivers don't implement RTS/CTS anyway. + */ + stbuf.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | PARENB | CRTSCTS); + stbuf.c_cflag |= (speed | bits | CREAD | 0 | parity | stopbits | CLOCAL); + + if (tcsetattr (fd, TCSANOW, &stbuf) < 0) { g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, @@ -373,105 +344,99 @@ config_fd (MMSerialPort *self, GError **error) } static void -serial_debug (MMSerialPort *self, const char *prefix, const char *buf, int len) +serial_debug (MMSerialPort *self, const char *prefix, const char *buf, gsize len) { - static GString *debug = NULL; - const char *s; - - if (!mm_options_debug ()) - return; - - if (len < 0) - len = strlen (buf); - - if (!debug) - debug = g_string_sized_new (256); - - g_string_append (debug, prefix); - g_string_append (debug, " '"); - - s = buf; - while (len--) { - if (g_ascii_isprint (*s)) - g_string_append_c (debug, *s); - else if (*s == '\r') - g_string_append (debug, "<CR>"); - else if (*s == '\n') - g_string_append (debug, "<LF>"); - else - g_string_append_printf (debug, "\\%d", *s); - - s++; - } + g_return_if_fail (len > 0); - g_string_append_c (debug, '\''); - g_debug ("(%s): %s", mm_port_get_device (MM_PORT (self)), debug->str); - g_string_truncate (debug, 0); + if (mm_options_debug () && MM_SERIAL_PORT_GET_CLASS (self)->debug_log) + MM_SERIAL_PORT_GET_CLASS (self)->debug_log (self, prefix, buf, len); } static gboolean mm_serial_port_send_command (MMSerialPort *self, - const char *command, + GByteArray *command, GError **error) { MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); - const char *s; - int status; + int status, i = 0; int eagain_count = 1000; + const guint8 *p; if (priv->fd < 0) { - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED, + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED, "%s", "Sending command failed: device is not enabled"); return FALSE; } if (mm_port_get_connected (MM_PORT (self))) { - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED, + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED, "%s", "Sending command failed: device is connected"); return FALSE; } - g_string_truncate (priv->command, g_str_has_prefix (command, "AT") ? 0 : 2); - g_string_append (priv->command, command); - - if (command[strlen (command)] != '\r') - g_string_append_c (priv->command, '\r'); - - serial_debug (self, "-->", priv->command->str, -1); + serial_debug (self, "-->", (const char *) command->data, command->len); /* Only accept about 3 seconds of EAGAIN */ if (priv->send_delay > 0) eagain_count = 3000000 / priv->send_delay; - s = priv->command->str; - while (*s) { - status = write (priv->fd, s, 1); + while (i < command->len) { + p = &command->data[i]; + status = write (priv->fd, p, 1); if (status < 0) { if (errno == EAGAIN) { eagain_count--; if (eagain_count <= 0) { - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED, + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED, "Sending command failed: '%s'", strerror (errno)); break; } } else { - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED, + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED, "Sending command failed: '%s'", strerror (errno)); break; } } else - s++; + i++; if (priv->send_delay) usleep (priv->send_delay); } - return *s == '\0'; + return i == command->len; +} + +static void +mm_serial_port_set_cached_reply (MMSerialPort *self, + const GByteArray *command, + const GByteArray *response) +{ + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_SERIAL_PORT (self)); + g_return_if_fail (command != NULL); + + if (response) { + GByteArray *cmd_copy = g_byte_array_sized_new (command->len); + GByteArray *rsp_copy = g_byte_array_sized_new (response->len); + + g_byte_array_append (cmd_copy, command->data, command->len); + g_byte_array_append (rsp_copy, response->data, response->len); + g_hash_table_insert (priv->reply_cache, cmd_copy, rsp_copy); + } else + g_hash_table_remove (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, command); +} + +static const GByteArray * +mm_serial_port_get_cached_reply (MMSerialPort *self, GByteArray *command) +{ + return (const GByteArray *) g_hash_table_lookup (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, command); } typedef struct { - char *command; - MMSerialResponseFn callback; + GByteArray *command; + GCallback callback; gpointer user_data; guint32 timeout; gboolean cached; @@ -500,11 +465,25 @@ mm_serial_port_schedule_queue_process (MMSerialPort *self) g_source_unref (source); } +static gsize +real_handle_response (MMSerialPort *self, + GByteArray *response, + GError *error, + GCallback callback, + gpointer callback_data) +{ + MMSerialResponseFn response_callback = (MMSerialResponseFn) callback; + + response_callback (self, response, error, callback_data); + return response->len; +} + static void mm_serial_port_got_response (MMSerialPort *self, GError *error) { MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); MMQueueData *info; + gsize consumed = priv->response->len; if (priv->timeout_id) { g_source_remove (priv->timeout_id); @@ -514,19 +493,26 @@ mm_serial_port_got_response (MMSerialPort *self, GError *error) info = (MMQueueData *) g_queue_pop_head (priv->queue); if (info) { if (info->cached && !error) - mm_serial_port_set_cached_reply (self, info->command, priv->response->str); - - if (info->callback) - info->callback (self, priv->response, error, info->user_data); + mm_serial_port_set_cached_reply (self, info->command, priv->response); + + if (info->callback) { + g_warn_if_fail (MM_SERIAL_PORT_GET_CLASS (self)->handle_response != NULL); + consumed = MM_SERIAL_PORT_GET_CLASS (self)->handle_response (self, + priv->response, + error, + info->callback, + info->user_data); + } - g_free (info->command); + g_byte_array_free (info->command, TRUE); g_slice_free (MMQueueData, info); } if (error) g_error_free (error); - g_string_truncate (priv->response, 0); + if (consumed) + g_byte_array_remove_range (priv->response, 0, consumed); if (!g_queue_is_empty (priv->queue)) mm_serial_port_schedule_queue_process (self); } @@ -541,7 +527,7 @@ mm_serial_port_timed_out (gpointer data) priv->timeout_id = 0; error = g_error_new_literal (MM_SERIAL_ERROR, - MM_SERIAL_RESPONSE_TIMEOUT, + MM_SERIAL_ERROR_RESPONSE_TIMEOUT, "Serial command timed out"); /* FIXME: This is not completely correct - if the response finally arrives and there's some other command waiting for response right now, the other command will @@ -566,10 +552,10 @@ mm_serial_port_queue_process (gpointer data) return FALSE; if (info->cached) { - const char *cached = mm_serial_port_get_cached_reply (self, info->command); + const GByteArray *cached = mm_serial_port_get_cached_reply (self, info->command); if (cached) { - g_string_append (priv->response, cached); + g_byte_array_append (priv->response, cached->data, cached->len); mm_serial_port_got_response (self, NULL); return FALSE; } @@ -590,111 +576,16 @@ mm_serial_port_queue_process (gpointer data) return FALSE; } -void -mm_serial_port_add_unsolicited_msg_handler (MMSerialPort *self, - GRegex *regex, - MMSerialUnsolicitedMsgFn callback, - gpointer user_data, - GDestroyNotify notify) -{ - MMUnsolicitedMsgHandler *handler; - MMSerialPortPrivate *priv; - - g_return_if_fail (MM_IS_SERIAL_PORT (self)); - g_return_if_fail (regex != NULL); - - handler = g_slice_new (MMUnsolicitedMsgHandler); - handler->regex = g_regex_ref (regex); - handler->callback = callback; - handler->user_data = user_data; - handler->notify = notify; - - priv = MM_SERIAL_PORT_GET_PRIVATE (self); - priv->unsolicited_msg_handlers = g_slist_append (priv->unsolicited_msg_handlers, handler); -} - -void -mm_serial_port_set_response_parser (MMSerialPort *self, - MMSerialResponseParserFn fn, - gpointer user_data, - GDestroyNotify notify) -{ - MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); - - g_return_if_fail (MM_IS_SERIAL_PORT (self)); - - if (priv->response_parser_notify) - priv->response_parser_notify (priv->response_parser_user_data); - - priv->response_parser_fn = fn; - priv->response_parser_user_data = user_data; - priv->response_parser_notify = notify; -} - -static gboolean -remove_eval_cb (const GMatchInfo *match_info, - GString *result, - gpointer user_data) -{ - int *result_len = (int *) user_data; - int start; - int end; - - if (g_match_info_fetch_pos (match_info, 0, &start, &end)) - *result_len -= (end - start); - - return FALSE; -} - -static void -parse_unsolicited_messages (MMSerialPort *self, - GString *response) -{ - MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); - GSList *iter; - - for (iter = priv->unsolicited_msg_handlers; iter; iter = iter->next) { - MMUnsolicitedMsgHandler *handler = (MMUnsolicitedMsgHandler *) iter->data; - GMatchInfo *match_info; - gboolean matches; - - matches = g_regex_match_full (handler->regex, response->str, response->len, 0, 0, &match_info, NULL); - if (handler->callback) { - while (g_match_info_matches (match_info)) { - handler->callback (self, match_info, handler->user_data); - g_match_info_next (match_info, NULL); - } - } - - g_match_info_free (match_info); - - if (matches) { - /* Remove matches */ - char *str; - int result_len = response->len; - - str = g_regex_replace_eval (handler->regex, response->str, response->len, 0, 0, - remove_eval_cb, &result_len, NULL); - - g_string_truncate (response, 0); - g_string_append_len (response, str, result_len); - g_free (str); - } - } -} - static gboolean parse_response (MMSerialPort *self, - GString *response, + GByteArray *response, GError **error) { - MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); - - g_return_val_if_fail (priv->response_parser_fn != NULL, FALSE); - - parse_unsolicited_messages (self, response); + if (MM_SERIAL_PORT_GET_CLASS (self)->parse_unsolicited) + MM_SERIAL_PORT_GET_CLASS (self)->parse_unsolicited (self, response); - return priv->response_parser_fn (priv->response_parser_user_data, response, error); + g_return_val_if_fail (MM_SERIAL_PORT_GET_CLASS (self)->parse_response, FALSE); + return MM_SERIAL_PORT_GET_CLASS (self)->parse_response (self, response, error); } static gboolean @@ -709,13 +600,15 @@ data_available (GIOChannel *source, GIOStatus status; if (condition & G_IO_HUP) { - g_string_truncate (priv->response, 0); - mm_serial_port_close (self); + if (priv->response->len) + g_byte_array_remove_range (priv->response, 0, priv->response->len); + mm_serial_port_close_force (self); return FALSE; } if (condition & G_IO_ERR) { - g_string_truncate (priv->response, 0); + if (priv->response->len) + g_byte_array_remove_range (priv->response, 0, priv->response->len); return TRUE; } @@ -724,9 +617,13 @@ data_available (GIOChannel *source, status = g_io_channel_read_chars (source, buf, SERIAL_BUF_SIZE, &bytes_read, &err); if (status == G_IO_STATUS_ERROR) { - g_warning ("%s", err->message); - g_error_free (err); - err = NULL; + if (err && err->message) + g_warning ("%s", err->message); + g_clear_error (&err); + + /* Serial port is closed; we're done */ + if (priv->watch_id == 0) + break; } /* If no bytes read, just let g_io_channel wait for more data */ @@ -735,14 +632,14 @@ data_available (GIOChannel *source, if (bytes_read > 0) { serial_debug (self, "<--", buf, bytes_read); - g_string_append_len (priv->response, buf, bytes_read); + g_byte_array_append (priv->response, (const guint8 *) buf, bytes_read); } - /* Make sure the string doesn't grow too long */ - if (priv->response->len > SERIAL_BUF_SIZE) { - g_warning ("%s (%s): response buffer filled before repsonse received", - G_STRFUNC, mm_port_get_device (MM_PORT (self))); - g_string_erase (priv->response, 0, (SERIAL_BUF_SIZE / 2)); + /* Make sure the response doesn't grow too long */ + if (priv->response->len > SERIAL_BUF_SIZE) { + /* Notify listeners and then trim the buffer */ + g_signal_emit_by_name (self, "buffer-full", priv->response); + g_byte_array_remove_range (priv->response, 0, (SERIAL_BUF_SIZE / 2)); } if (parse_response (self, priv->response, &err)) @@ -792,13 +689,13 @@ mm_serial_port_open (MMSerialPort *self, GError **error) priv = MM_SERIAL_PORT_GET_PRIVATE (self); - if (priv->fd >= 0) { + device = mm_port_get_device (MM_PORT (self)); + + if (priv->open_count) { /* Already open */ - return TRUE; + goto success; } - device = mm_port_get_device (MM_PORT (self)); - g_message ("(%s) opening serial device...", device); devfile = g_strdup_printf ("/dev/%s", device); errno = 0; @@ -812,32 +709,29 @@ mm_serial_port_open (MMSerialPort *self, GError **error) */ g_set_error (error, MM_SERIAL_ERROR, - (errno == ENODEV) ? MM_SERIAL_OPEN_FAILED_NO_DEVICE : MM_SERIAL_OPEN_FAILED, + (errno == ENODEV) ? MM_SERIAL_ERROR_OPEN_FAILED_NO_DEVICE : MM_SERIAL_ERROR_OPEN_FAILED, "Could not open serial device %s: %s", device, strerror (errno)); return FALSE; } if (ioctl (priv->fd, TIOCEXCL) < 0) { - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_OPEN_FAILED, + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_OPEN_FAILED, "Could not lock serial device %s: %s", device, strerror (errno)); - close (priv->fd); - priv->fd = -1; - return FALSE; + goto error; } - if (ioctl (priv->fd, TCGETA, &priv->old_t) < 0) { - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_OPEN_FAILED, + /* Flush any waiting IO */ + tcflush (priv->fd, TCIOFLUSH); + + if (tcgetattr (priv->fd, &priv->old_t) < 0) { + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_OPEN_FAILED, "Could not open serial device %s: %s", device, strerror (errno)); - close (priv->fd); - priv->fd = -1; - return FALSE; + goto error; } - if (!config_fd (self, error)) { - close (priv->fd); - priv->fd = -1; - return FALSE; - } + g_warn_if_fail (MM_SERIAL_PORT_GET_CLASS (self)->config_fd); + if (!MM_SERIAL_PORT_GET_CLASS (self)->config_fd (self, priv->fd, error)) + goto error; priv->channel = g_io_channel_unix_new (priv->fd); g_io_channel_set_encoding (priv->channel, NULL, NULL); @@ -849,17 +743,58 @@ mm_serial_port_open (MMSerialPort *self, GError **error) priv->connected_id = g_signal_connect (self, "notify::" MM_PORT_CONNECTED, G_CALLBACK (port_connected), NULL); +success: + priv->open_count++; + if (mm_options_debug ()) { + GTimeVal tv; + + g_get_current_time (&tv); + g_debug ("<%ld.%ld> (%s) device open count is %d (open)", + tv.tv_sec, tv.tv_usec, device, priv->open_count); + } return TRUE; + +error: + close (priv->fd); + priv->fd = -1; + return FALSE; +} + +gboolean +mm_serial_port_is_open (MMSerialPort *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (MM_IS_SERIAL_PORT (self), FALSE); + + return !!MM_SERIAL_PORT_GET_PRIVATE (self)->open_count; } void mm_serial_port_close (MMSerialPort *self) { MMSerialPortPrivate *priv; + const char *device; + int i; g_return_if_fail (MM_IS_SERIAL_PORT (self)); priv = MM_SERIAL_PORT_GET_PRIVATE (self); + g_return_if_fail (priv->open_count > 0); + + device = mm_port_get_device (MM_PORT (self)); + + priv->open_count--; + + if (mm_options_debug ()) { + GTimeVal tv; + + g_get_current_time (&tv); + g_debug ("<%ld.%ld> (%s) device open count is %d (close)", + tv.tv_sec, tv.tv_usec, device, priv->open_count); + } + + if (priv->open_count > 0) + return; if (priv->connected_id) { g_signal_handler_disconnect (self, priv->connected_id); @@ -867,31 +802,74 @@ mm_serial_port_close (MMSerialPort *self) } if (priv->fd >= 0) { - g_message ("(%s) closing serial device...", mm_port_get_device (MM_PORT (self))); + g_message ("(%s) closing serial device...", device); mm_port_set_connected (MM_PORT (self), FALSE); if (priv->channel) { g_source_remove (priv->watch_id); + priv->watch_id = 0; g_io_channel_shutdown (priv->channel, TRUE, NULL); g_io_channel_unref (priv->channel); priv->channel = NULL; } - if (priv->flash_id > 0) { - g_source_remove (priv->flash_id); - priv->flash_id = 0; - } + mm_serial_port_flash_cancel (self); - ioctl (priv->fd, TCSETA, &priv->old_t); + tcsetattr (priv->fd, TCSANOW, &priv->old_t); close (priv->fd); priv->fd = -1; } + + /* Clear the command queue */ + for (i = 0; i < g_queue_get_length (priv->queue); i++) { + MMQueueData *item = g_queue_peek_nth (priv->queue, i); + + if (item->callback) { + GError *error; + GByteArray *response; + + g_warn_if_fail (MM_SERIAL_PORT_GET_CLASS (self)->handle_response != NULL); + error = g_error_new_literal (MM_SERIAL_ERROR, + MM_SERIAL_ERROR_SEND_FAILED, + "Serial port is now closed"); + response = g_byte_array_sized_new (1); + g_byte_array_append (response, (const guint8 *) "\0", 1); + MM_SERIAL_PORT_GET_CLASS (self)->handle_response (self, + response, + error, + item->callback, + item->user_data); + g_error_free (error); + g_byte_array_free (response, TRUE); + } + + g_byte_array_free (item->command, TRUE); + g_slice_free (MMQueueData, item); + } + g_queue_clear (priv->queue); +} + +void +mm_serial_port_close_force (MMSerialPort *self) +{ + MMSerialPortPrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_SERIAL_PORT (self)); + + priv = MM_SERIAL_PORT_GET_PRIVATE (self); + g_return_if_fail (priv->open_count > 0); + + /* Force the port to close */ + priv->open_count = 1; + mm_serial_port_close (self); } static void internal_queue_command (MMSerialPort *self, - const char *command, + GByteArray *command, + gboolean take_command, gboolean cached, guint32 timeout_seconds, MMSerialResponseFn callback, @@ -904,15 +882,20 @@ internal_queue_command (MMSerialPort *self, g_return_if_fail (command != NULL); info = g_slice_new0 (MMQueueData); - info->command = g_strdup (command); + if (take_command) + info->command = command; + else { + info->command = g_byte_array_sized_new (command->len); + g_byte_array_append (info->command, command->data, command->len); + } info->cached = cached; info->timeout = timeout_seconds; - info->callback = callback; + info->callback = (GCallback) callback; info->user_data = user_data; /* Clear the cached value for this command if not asking for cached value */ if (!cached) - mm_serial_port_set_cached_reply (self, command, NULL); + mm_serial_port_set_cached_reply (self, info->command, NULL); g_queue_push_tail (priv->queue, info); @@ -922,22 +905,24 @@ internal_queue_command (MMSerialPort *self, void mm_serial_port_queue_command (MMSerialPort *self, - const char *command, + GByteArray *command, + gboolean take_command, guint32 timeout_seconds, MMSerialResponseFn callback, gpointer user_data) { - internal_queue_command (self, command, FALSE, timeout_seconds, callback, user_data); + internal_queue_command (self, command, take_command, FALSE, timeout_seconds, callback, user_data); } void mm_serial_port_queue_command_cached (MMSerialPort *self, - const char *command, + GByteArray *command, + gboolean take_command, guint32 timeout_seconds, MMSerialResponseFn callback, gpointer user_data) { - internal_queue_command (self, command, TRUE, timeout_seconds, callback, user_data); + internal_queue_command (self, command, take_command, TRUE, timeout_seconds, callback, user_data); } typedef struct { @@ -1030,8 +1015,14 @@ flash_do (gpointer data) priv->flash_id = 0; - if (!set_speed (info->port, info->current_speed, &error)) - g_assert (error); + if (info->current_speed) { + if (!set_speed (info->port, info->current_speed, &error)) + g_assert (error); + } else { + error = g_error_new_literal (MM_SERIAL_ERROR, + MM_SERIAL_ERROR_FLASH_FAILED, + "Failed to retrieve current speed"); + } info->callback (info->port, error, info->user_data); g_clear_error (&error); @@ -1042,6 +1033,7 @@ flash_do (gpointer data) gboolean mm_serial_port_flash (MMSerialPort *self, guint32 flash_time, + gboolean ignore_errors, MMSerialFlashFn callback, gpointer user_data) { @@ -1049,12 +1041,22 @@ mm_serial_port_flash (MMSerialPort *self, MMSerialPortPrivate *priv; speed_t cur_speed = 0; GError *error = NULL; + gboolean success; g_return_val_if_fail (MM_IS_SERIAL_PORT (self), FALSE); g_return_val_if_fail (callback != NULL, FALSE); priv = MM_SERIAL_PORT_GET_PRIVATE (self); + if (!mm_serial_port_is_open (self)) { + error = g_error_new_literal (MM_SERIAL_ERROR, + MM_SERIAL_ERROR_NOT_OPEN, + "The serial port is not open."); + callback (self, error, user_data); + g_error_free (error); + return FALSE; + } + if (priv->flash_id > 0) { error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_IN_PROGRESS, @@ -1064,11 +1066,13 @@ mm_serial_port_flash (MMSerialPort *self, return FALSE; } - if (!get_speed (self, &cur_speed, &error)) { + success = get_speed (self, &cur_speed, &error); + if (!success && !ignore_errors) { callback (self, error, user_data); g_error_free (error); return FALSE; } + g_clear_error (&error); info = g_slice_new0 (FlashInfo); info->port = self; @@ -1076,7 +1080,8 @@ mm_serial_port_flash (MMSerialPort *self, info->callback = callback; info->user_data = user_data; - if (!set_speed (self, B0, &error)) { + success = set_speed (self, B0, &error); + if (!success && !ignore_errors) { callback (self, error, user_data); g_error_free (error); return FALSE; @@ -1113,12 +1118,54 @@ mm_serial_port_new (const char *name, MMPortType ptype) NULL)); } +static gboolean +ba_equal (gconstpointer v1, gconstpointer v2) +{ + const GByteArray *a = v1; + const GByteArray *b = v2; + + if (!a && b) + return -1; + else if (a && !b) + return 1; + else if (!a && !b) + return 0; + + g_assert (a && b); + if (a->len < b->len) + return -1; + else if (a->len > b->len) + return 1; + + g_assert (a->len == b->len); + return !memcmp (a->data, b->data, a->len); +} + +static guint +ba_hash (gconstpointer v) +{ + /* 31 bit hash function */ + const GByteArray *array = v; + guint32 i, h = (const signed char) array->data[0]; + + for (i = 1; i < array->len; i++) + h = (h << 5) - h + (const signed char) array->data[i]; + + return h; +} + +static void +ba_free (gpointer v) +{ + g_byte_array_free ((GByteArray *) v, TRUE); +} + static void mm_serial_port_init (MMSerialPort *self) { MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); - priv->reply_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + priv->reply_cache = g_hash_table_new_full (ba_hash, ba_equal, ba_free, ba_free); priv->fd = -1; priv->baud = 57600; @@ -1128,8 +1175,7 @@ mm_serial_port_init (MMSerialPort *self) priv->send_delay = 1000; priv->queue = g_queue_new (); - priv->command = g_string_new_len ("AT", SERIAL_BUF_SIZE); - priv->response = g_string_sized_new (SERIAL_BUF_SIZE); + priv->response = g_byte_array_sized_new (500); } static void @@ -1191,7 +1237,10 @@ get_property (GObject *object, guint prop_id, static void dispose (GObject *object) { - mm_serial_port_close (MM_SERIAL_PORT (object)); + if (mm_serial_port_is_open (MM_SERIAL_PORT (object))) + mm_serial_port_close_force (MM_SERIAL_PORT (object)); + + mm_serial_port_flash_cancel (MM_SERIAL_PORT (object)); G_OBJECT_CLASS (mm_serial_port_parent_class)->dispose (object); } @@ -1203,24 +1252,8 @@ finalize (GObject *object) MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); g_hash_table_destroy (priv->reply_cache); + g_byte_array_free (priv->response, TRUE); g_queue_free (priv->queue); - g_string_free (priv->command, TRUE); - g_string_free (priv->response, TRUE); - - while (priv->unsolicited_msg_handlers) { - MMUnsolicitedMsgHandler *handler = (MMUnsolicitedMsgHandler *) priv->unsolicited_msg_handlers->data; - - if (handler->notify) - handler->notify (handler->user_data); - - g_regex_unref (handler->regex); - g_slice_free (MMUnsolicitedMsgHandler, handler); - priv->unsolicited_msg_handlers = g_slist_delete_link (priv->unsolicited_msg_handlers, - priv->unsolicited_msg_handlers); - } - - if (priv->response_parser_notify) - priv->response_parser_notify (priv->response_parser_user_data); G_OBJECT_CLASS (mm_serial_port_parent_class)->finalize (object); } @@ -1238,6 +1271,9 @@ mm_serial_port_class_init (MMSerialPortClass *klass) object_class->dispose = dispose; object_class->finalize = finalize; + klass->config_fd = real_config_fd; + klass->handle_response = real_handle_response; + /* Properties */ g_object_class_install_property (object_class, PROP_BAUD, @@ -1278,4 +1314,14 @@ mm_serial_port_class_init (MMSerialPortClass *klass) "Send delay", 0, G_MAXUINT64, 0, G_PARAM_READWRITE)); + + /* Signals */ + g_signal_new ("buffer-full", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMSerialPortClass, buffer_full), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); } + diff --git a/src/mm-serial-port.h b/src/mm-serial-port.h index 841b4fa..f78f793 100644 --- a/src/mm-serial-port.h +++ b/src/mm-serial-port.h @@ -11,7 +11,7 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #ifndef MM_SERIAL_PORT_H @@ -39,67 +39,101 @@ typedef struct _MMSerialPort MMSerialPort; typedef struct _MMSerialPortClass MMSerialPortClass; -typedef gboolean (*MMSerialResponseParserFn) (gpointer user_data, - GString *response, - GError **error); - -typedef void (*MMSerialUnsolicitedMsgFn) (MMSerialPort *port, - GMatchInfo *match_info, - gpointer user_data); - -typedef void (*MMSerialResponseFn) (MMSerialPort *port, - GString *response, +typedef void (*MMSerialFlashFn) (MMSerialPort *port, GError *error, gpointer user_data); -typedef void (*MMSerialFlashFn) (MMSerialPort *port, +typedef void (*MMSerialResponseFn) (MMSerialPort *port, + GByteArray *response, GError *error, gpointer user_data); + struct _MMSerialPort { MMPort parent; }; struct _MMSerialPortClass { MMPortClass parent; + + /* Called for subclasses to parse unsolicited responses. If any recognized + * unsolicited response is found, it should be removed from the 'response' + * byte array before returning. + */ + void (*parse_unsolicited) (MMSerialPort *self, GByteArray *response); + + /* Called to parse the device's response to a command or determine if the + * response was an error response. If the response indicates an error, an + * appropriate error should be returned in the 'error' argument. The + * function should return FALSE if there is not enough data yet to determine + * the device's reply (whether success *or* error), and should return TRUE + * when the device's response has been recognized and parsed. + */ + gboolean (*parse_response) (MMSerialPort *self, + GByteArray *response, + GError **error); + + /* Called after parsing to allow the command response to be delivered to + * it's callback to be handled. Returns the # of bytes of the response + * consumed. + */ + gsize (*handle_response) (MMSerialPort *self, + GByteArray *response, + GError *error, + GCallback callback, + gpointer callback_data); + + /* Called to configure the serial port after it's opened. On error, should + * return FALSE and set 'error' as appropriate. + */ + gboolean (*config_fd) (MMSerialPort *self, int fd, GError **error); + + void (*debug_log) (MMSerialPort *self, + const char *prefix, + const char *buf, + gsize len); + + /* Signals */ + void (*buffer_full) (MMSerialPort *port, const GByteArray *buffer); }; GType mm_serial_port_get_type (void); MMSerialPort *mm_serial_port_new (const char *name, MMPortType ptype); -void mm_serial_port_add_unsolicited_msg_handler (MMSerialPort *self, - GRegex *regex, - MMSerialUnsolicitedMsgFn callback, - gpointer user_data, - GDestroyNotify notify); +/* Keep in mind that port open/close is refcounted, so ensure that + * open/close calls are properly balanced. + */ -void mm_serial_port_set_response_parser (MMSerialPort *self, - MMSerialResponseParserFn fn, - gpointer user_data, - GDestroyNotify notify); +gboolean mm_serial_port_is_open (MMSerialPort *self); gboolean mm_serial_port_open (MMSerialPort *self, GError **error); void mm_serial_port_close (MMSerialPort *self); + +void mm_serial_port_close_force (MMSerialPort *self); + +gboolean mm_serial_port_flash (MMSerialPort *self, + guint32 flash_time, + gboolean ignore_errors, + MMSerialFlashFn callback, + gpointer user_data); +void mm_serial_port_flash_cancel (MMSerialPort *self); + void mm_serial_port_queue_command (MMSerialPort *self, - const char *command, + GByteArray *command, + gboolean take_command, guint32 timeout_seconds, MMSerialResponseFn callback, gpointer user_data); void mm_serial_port_queue_command_cached (MMSerialPort *self, - const char *command, + GByteArray *command, + gboolean take_command, guint32 timeout_seconds, MMSerialResponseFn callback, gpointer user_data); -gboolean mm_serial_port_flash (MMSerialPort *self, - guint32 flash_time, - MMSerialFlashFn callback, - gpointer user_data); -void mm_serial_port_flash_cancel (MMSerialPort *self); - #endif /* MM_SERIAL_PORT_H */ diff --git a/src/mm-utils.c b/src/mm-utils.c new file mode 100644 index 0000000..56182c0 --- /dev/null +++ b/src/mm-utils.c @@ -0,0 +1,78 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include <config.h> +#include <glib.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <errno.h> + +#include "mm-utils.h" + +/* From hostap, Copyright (c) 2002-2005, Jouni Malinen <jkmaline@cc.hut.fi> */ + +static int hex2num (char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; +} + +int utils_hex2byte (const char *hex) +{ + int a, b; + a = hex2num(*hex++); + if (a < 0) + return -1; + b = hex2num(*hex++); + if (b < 0) + return -1; + return (a << 4) | b; +} + +char * +utils_hexstr2bin (const char *hex, gsize *out_len) +{ + size_t len = strlen (hex); + size_t i; + int a; + const char * ipos = hex; + char * buf = NULL; + char * opos; + + /* Length must be a multiple of 2 */ + g_return_val_if_fail ((len % 2) == 0, NULL); + + opos = buf = g_malloc0 ((len / 2) + 1); + for (i = 0; i < len; i += 2) { + a = utils_hex2byte (ipos); + if (a < 0) { + g_free (buf); + return NULL; + } + *opos++ = a; + ipos += 2; + } + *out_len = len / 2; + return buf; +} + +/* End from hostap */ + diff --git a/src/mm-utils.h b/src/mm-utils.h new file mode 100644 index 0000000..79e7827 --- /dev/null +++ b/src/mm-utils.h @@ -0,0 +1,24 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_UTILS_H +#define MM_UTILS_H + +int utils_hex2byte (const char *hex); + +char *utils_hexstr2bin (const char *hex, gsize *out_len); + +#endif /* MM_UTILS_H */ + diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c index 3d93423..92a7af8 100644 --- a/src/tests/test-modem-helpers.c +++ b/src/tests/test-modem-helpers.c @@ -18,6 +18,11 @@ #include "mm-modem-helpers.h" +typedef struct { + GPtrArray *solicited_creg; + GPtrArray *unsolicited_creg; +} TestData; + #define MM_SCAN_TAG_STATUS "status" #define MM_SCAN_TAG_OPER_LONG "operator-long" #define MM_SCAN_TAG_OPER_SHORT "operator-short" @@ -35,10 +40,10 @@ typedef struct { #define ARRAY_LEN(i) (sizeof (i) / sizeof (i[0])) static void -test_results (const char *desc, - const char *reply, - OperEntry *expected_results, - guint32 expected_results_len) +test_cops_results (const char *desc, + const char *reply, + OperEntry *expected_results, + guint32 expected_results_len) { guint i; GError *error = NULL; @@ -101,7 +106,7 @@ test_cops_response_tm506 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" } }; - test_results ("TM-506", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("TM-506", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -113,7 +118,7 @@ test_cops_response_gt3gplus (void *f, gpointer d) { "1", "Cingular", "Cingular", "310410", "0" }, }; - test_results ("GlobeTrotter 3G+ (nozomi)", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("GlobeTrotter 3G+ (nozomi)", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -126,7 +131,7 @@ test_cops_response_ac881 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Sierra AirCard 881", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Sierra AirCard 881", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -139,7 +144,7 @@ test_cops_response_gtmax36 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Option GlobeTrotter MAX 3.6", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Option GlobeTrotter MAX 3.6", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -152,7 +157,7 @@ test_cops_response_ac860 (void *f, gpointer d) { "1", "Cingular", "Cinglr", "310410", "0" }, }; - test_results ("Sierra AirCard 860", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Sierra AirCard 860", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -165,7 +170,7 @@ test_cops_response_gtm378 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Option GTM378", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Option GTM378", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -177,7 +182,7 @@ test_cops_response_motoc (void *f, gpointer d) { "0", "Cingular Wireless", NULL, "310410", NULL }, }; - test_results ("BUSlink SCWi275u (Motorola C-series)", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("BUSlink SCWi275u (Motorola C-series)", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -189,7 +194,7 @@ test_cops_response_mf627a (void *f, gpointer d) { "3", "Voicestream Wireless Corporation", "VSTREAM", "31026", "0" }, }; - test_results ("ZTE MF627 (A)", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("ZTE MF627 (A)", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -201,7 +206,7 @@ test_cops_response_mf627b (void *f, gpointer d) { "3", NULL, NULL, "31026", "0" }, }; - test_results ("ZTE MF627 (B)", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("ZTE MF627 (B)", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -213,7 +218,7 @@ test_cops_response_e160g (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Huawei E160G", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Huawei E160G", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -226,7 +231,7 @@ test_cops_response_mercury (void *f, gpointer d) { "1", "T-Mobile", "TMO", "31026", "0" }, }; - test_results ("Sierra AT&T USBConnect Mercury", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Sierra AT&T USBConnect Mercury", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -239,7 +244,7 @@ test_cops_response_quicksilver (void *f, gpointer d) { "1", "AT&T", NULL, "310260", "0" }, }; - test_results ("Option AT&T Quicksilver", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Option AT&T Quicksilver", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -251,7 +256,7 @@ test_cops_response_icon225 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Option iCON 225", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Option iCON 225", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -265,7 +270,7 @@ test_cops_response_icon452 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" } }; - test_results ("Option iCON 452", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Option iCON 452", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -278,7 +283,7 @@ test_cops_response_f3507g (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "2" } }; - test_results ("Ericsson F3507g", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Ericsson F3507g", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -291,7 +296,7 @@ test_cops_response_f3607gw (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" } }; - test_results ("Ericsson F3607gw", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Ericsson F3607gw", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -304,7 +309,7 @@ test_cops_response_mc8775 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" } }; - test_results ("Sierra MC8775", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Sierra MC8775", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -317,7 +322,7 @@ test_cops_response_n80 (void *f, gpointer d) { "1", "Cingular", NULL, "31041", NULL }, }; - test_results ("Nokia N80", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Nokia N80", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -329,7 +334,7 @@ test_cops_response_e1550 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Huawei E1550", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Huawei E1550", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -341,7 +346,7 @@ test_cops_response_mf622 (void *f, gpointer d) { "1", NULL, NULL, "310410", "0" }, }; - test_results ("ZTE MF622", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("ZTE MF622", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -354,7 +359,7 @@ test_cops_response_e226 (void *f, gpointer d) { "1", NULL, NULL, "310410", "0" }, }; - test_results ("Huawei E226", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Huawei E226", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -367,7 +372,7 @@ test_cops_response_xu870 (void *f, gpointer d) { "1", "T-Mobile", "TMO", "31026", "0" }, }; - test_results ("Novatel XU870", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Novatel XU870", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -380,7 +385,7 @@ test_cops_response_gtultraexpress (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Option GlobeTrotter Ultra Express", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Option GlobeTrotter Ultra Express", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -392,7 +397,7 @@ test_cops_response_n2720 (void *f, gpointer d) { "1", "AT&T", NULL, "310410", "0" }, }; - test_results ("Nokia 2720", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Nokia 2720", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -405,7 +410,28 @@ test_cops_response_gobi (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Qualcomm Gobi", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Qualcomm Gobi", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_sek600i (void *f, gpointer d) +{ + /* Phone is stupid enough to support 3G but not report cell technology, + * mixing together 2G and 3G cells without any way of distinguishing + * which is which... + */ + const char *reply = "+COPS: (2,\"blau\",\"\",\"26203\"),(2,\"blau\",\"\",\"26203\"),(3,\"\",\"\",\"26201\"),(3,\"\",\"\",\"26202\"),(3,\"\",\"\",\"26207\"),(3,\"\",\"\",\"26201\"),(3,\"\",\"\",\"26207\")"; + static OperEntry expected[] = { + { "2", "blau", NULL, "26203", NULL }, + { "2", "blau", NULL, "26203", NULL }, + { "3", NULL, NULL, "26201", NULL }, + { "3", NULL, NULL, "26202", NULL }, + { "3", NULL, NULL, "26207", NULL }, + { "3", NULL, NULL, "26201", NULL }, + { "3", NULL, NULL, "26207", NULL }, + }; + + test_cops_results ("Sony-Ericsson K600i", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -432,6 +458,338 @@ test_cops_response_umts_invalid (void *f, gpointer d) g_assert (error == NULL); } +typedef struct { + guint32 state; + gulong lac; + gulong ci; + gint act; + + guint regex_num; + gboolean cgreg; +} CregResult; + +static void +test_creg_match (const char *test, + gboolean solicited, + const char *reply, + TestData *data, + const CregResult *result) +{ + int i; + GMatchInfo *info = NULL; + guint32 state = 0; + gulong lac = 0, ci = 0; + gint access_tech = -1; + GError *error = NULL; + gboolean success, cgreg = FALSE; + guint regex_num = 0; + GPtrArray *array; + + g_assert (reply); + g_assert (test); + g_assert (data); + g_assert (result); + + g_print ("\nTesting %s +CREG %s response...\n", + test, + solicited ? "solicited" : "unsolicited"); + + array = solicited ? data->solicited_creg : data->unsolicited_creg; + for (i = 0; i < array->len; i++) { + GRegex *r = g_ptr_array_index (array, i); + + if (g_regex_match (r, reply, 0, &info)) { + regex_num = i + 1; + break; + } + g_match_info_free (info); + info = NULL; + } + + g_assert (info != NULL); + g_assert (regex_num == result->regex_num); + + success = mm_gsm_parse_creg_response (info, &state, &lac, &ci, &access_tech, &cgreg, &error); + g_assert (success); + g_assert (error == NULL); + g_assert (state == result->state); + g_assert (lac == result->lac); + g_assert (ci == result->ci); + g_assert (access_tech == result->act); + g_assert (cgreg == result->cgreg); +} + +static void +test_creg1_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CREG: 1,3"; + const CregResult result = { 3, 0, 0, -1 , 2, FALSE}; + + test_creg_match ("CREG=1", TRUE, reply, data, &result); +} + +static void +test_creg1_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 3\r\n"; + const CregResult result = { 3, 0, 0, -1 , 1, FALSE}; + + test_creg_match ("CREG=1", FALSE, reply, data, &result); +} + +static void +test_creg2_mercury_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CREG: 0,1,84CD,00D30173"; + const CregResult result = { 1, 0x84cd, 0xd30173, -1 , 4, FALSE}; + + test_creg_match ("Sierra Mercury CREG=2", TRUE, reply, data, &result); +} + +static void +test_creg2_mercury_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 1,84CD,00D30156\r\n"; + const CregResult result = { 1, 0x84cd, 0xd30156, -1 , 3, FALSE}; + + test_creg_match ("Sierra Mercury CREG=2", FALSE, reply, data, &result); +} + +static void +test_creg2_sek850i_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CREG: 2,1,\"CE00\",\"01CEAD8F\""; + const CregResult result = { 1, 0xce00, 0x01cead8f, -1 , 4, FALSE}; + + test_creg_match ("Sony Ericsson K850i CREG=2", TRUE, reply, data, &result); +} + +static void +test_creg2_sek850i_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 1,\"CE00\",\"00005449\"\r\n"; + const CregResult result = { 1, 0xce00, 0x5449, -1 , 3, FALSE}; + + test_creg_match ("Sony Ericsson K850i CREG=2", FALSE, reply, data, &result); +} + +static void +test_creg2_e160g_solicited_unregistered (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CREG: 2,0,00,0"; + const CregResult result = { 0, 0, 0, -1 , 4, FALSE}; + + test_creg_match ("Huawei E160G unregistered CREG=2", TRUE, reply, data, &result); +} + +static void +test_creg2_e160g_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CREG: 2,1,8BE3,2BAF"; + const CregResult result = { 1, 0x8be3, 0x2baf, -1 , 4, FALSE}; + + test_creg_match ("Huawei E160G CREG=2", TRUE, reply, data, &result); +} + +static void +test_creg2_e160g_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 2,8BE3,2BAF\r\n"; + const CregResult result = { 2, 0x8be3, 0x2baf, -1 , 3, FALSE}; + + test_creg_match ("Huawei E160G CREG=2", FALSE, reply, data, &result); +} + +static void +test_creg2_tm506_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CREG: 2,1,\"8BE3\",\"00002BAF\""; + const CregResult result = { 1, 0x8BE3, 0x2BAF, -1 , 4, FALSE}; + + /* Test leading zeros in the CI */ + test_creg_match ("Sony Ericsson TM-506 CREG=2", TRUE, reply, data, &result); +} + +static void +test_creg2_xu870_unsolicited_unregistered (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 2,,\r\n"; + const CregResult result = { 2, 0, 0, -1 , 3, FALSE}; + + test_creg_match ("Novatel XU870 unregistered CREG=2", FALSE, reply, data, &result); +} + +static void +test_cgreg1_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CGREG: 1,3"; + const CregResult result = { 3, 0, 0, -1 , 2, TRUE}; + + test_creg_match ("CGREG=1", TRUE, reply, data, &result); +} + +static void +test_cgreg1_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CGREG: 3\r\n"; + const CregResult result = { 3, 0, 0, -1 , 1, TRUE}; + + test_creg_match ("CGREG=1", FALSE, reply, data, &result); +} + +static void +test_cgreg2_f3607gw_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CGREG: 2,1,\"8BE3\",\"00002B5D\",3"; + const CregResult result = { 1, 0x8BE3, 0x2B5D, 3 , 6, TRUE}; + + test_creg_match ("Ericsson F3607gw CGREG=2", TRUE, reply, data, &result); +} + +static void +test_cgreg2_f3607gw_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CGREG: 1,\"8BE3\",\"00002B5D\",3\r\n"; + const CregResult result = { 1, 0x8BE3, 0x2B5D, 3 , 5, TRUE}; + + test_creg_match ("Ericsson F3607gw CGREG=2", FALSE, reply, data, &result); +} + +static void +test_creg2_md400_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 2,5,\"0502\",\"0404736D\"\r\n"; + const CregResult result = { 5, 0x0502, 0x0404736D, -1 , 4, FALSE}; + + test_creg_match ("Sony-Ericsson MD400 CREG=2", FALSE, reply, data, &result); +} + +static void +test_cgreg2_md400_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CGREG: 5,\"0502\",\"0404736D\",2\r\n"; + const CregResult result = { 5, 0x0502, 0x0404736D, 2, 5, TRUE}; + + test_creg_match ("Sony-Ericsson MD400 CGREG=2", FALSE, reply, data, &result); +} + +static void +test_creg_cgreg_multi_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 5\r\n\r\n+CGREG: 0\r\n"; + const CregResult result = { 5, 0, 0, -1, 1, FALSE}; + + test_creg_match ("Multi CREG/CGREG", FALSE, reply, data, &result); +} + +static void +test_creg_cgreg_multi2_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CGREG: 0\r\n\r\n+CREG: 5\r\n"; + const CregResult result = { 0, 0, 0, -1, 1, TRUE}; + + test_creg_match ("Multi CREG/CGREG #2", FALSE, reply, data, &result); +} + +static void +test_cscs_icon225_support_response (void *f, gpointer d) +{ + const char *reply = "\r\n+CSCS: (\"IRA\",\"GSM\",\"UCS2\")\r\n"; + MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN; + gboolean success; + + success = mm_gsm_parse_cscs_support_response (reply, &charsets); + g_assert (success); + + g_assert (charsets == (MM_MODEM_CHARSET_IRA | + MM_MODEM_CHARSET_GSM | + MM_MODEM_CHARSET_UCS2)); +} + +static void +test_cscs_sierra_mercury_support_response (void *f, gpointer d) +{ + const char *reply = "\r\n+CSCS: (\"IRA\",\"GSM\",\"UCS2\",\"PCCP437\")\r\n"; + MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN; + gboolean success; + + success = mm_gsm_parse_cscs_support_response (reply, &charsets); + g_assert (success); + + g_assert (charsets == (MM_MODEM_CHARSET_IRA | + MM_MODEM_CHARSET_GSM | + MM_MODEM_CHARSET_UCS2 | + MM_MODEM_CHARSET_PCCP437)); +} + +static void +test_cscs_buslink_support_response (void *f, gpointer d) +{ + const char *reply = "\r\n+CSCS: (\"8859-1\",\"ASCII\",\"GSM\",\"UCS2\",\"UTF8\")\r\n"; + MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN; + gboolean success; + + success = mm_gsm_parse_cscs_support_response (reply, &charsets); + g_assert (success); + + g_assert (charsets == (MM_MODEM_CHARSET_8859_1 | + MM_MODEM_CHARSET_IRA | + MM_MODEM_CHARSET_GSM | + MM_MODEM_CHARSET_UCS2 | + MM_MODEM_CHARSET_UTF8)); +} + +static void +test_cscs_blackberry_support_response (void *f, gpointer d) +{ + const char *reply = "\r\n+CSCS: \"IRA\"\r\n"; + MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN; + gboolean success; + + success = mm_gsm_parse_cscs_support_response (reply, &charsets); + g_assert (success); + + g_assert (charsets == MM_MODEM_CHARSET_IRA); +} + +static TestData * +test_data_new (void) +{ + TestData *data; + + data = g_malloc0 (sizeof (TestData)); + data->solicited_creg = mm_gsm_creg_regex_get (TRUE); + data->unsolicited_creg = mm_gsm_creg_regex_get (FALSE); + return data; +} + +static void +test_data_free (TestData *data) +{ + mm_gsm_creg_regex_destroy (data->solicited_creg); + mm_gsm_creg_regex_destroy (data->unsolicited_creg); + g_free (data); +} + typedef void (*TCFunc)(void); @@ -440,10 +798,13 @@ typedef void (*TCFunc)(void); int main (int argc, char **argv) { GTestSuite *suite; + TestData *data; + gint result; g_test_init (&argc, &argv, NULL); suite = g_test_get_root (); + data = test_data_new (); g_test_suite_add (suite, TESTCASE (test_cops_response_tm506, NULL)); g_test_suite_add (suite, TESTCASE (test_cops_response_gt3gplus, NULL)); @@ -470,10 +831,42 @@ int main (int argc, char **argv) g_test_suite_add (suite, TESTCASE (test_cops_response_gtultraexpress, NULL)); g_test_suite_add (suite, TESTCASE (test_cops_response_n2720, NULL)); g_test_suite_add (suite, TESTCASE (test_cops_response_gobi, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_sek600i, NULL)); g_test_suite_add (suite, TESTCASE (test_cops_response_gsm_invalid, NULL)); g_test_suite_add (suite, TESTCASE (test_cops_response_umts_invalid, NULL)); - return g_test_run (); + g_test_suite_add (suite, TESTCASE (test_creg1_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg1_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_mercury_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_mercury_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_sek850i_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_sek850i_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_e160g_solicited_unregistered, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_e160g_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_e160g_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_tm506_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_xu870_unsolicited_unregistered, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_md400_unsolicited, data)); + + g_test_suite_add (suite, TESTCASE (test_cgreg1_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_cgreg1_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_cgreg2_f3607gw_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_cgreg2_f3607gw_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_cgreg2_md400_unsolicited, data)); + + g_test_suite_add (suite, TESTCASE (test_creg_cgreg_multi_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg_cgreg_multi2_unsolicited, data)); + + g_test_suite_add (suite, TESTCASE (test_cscs_icon225_support_response, data)); + g_test_suite_add (suite, TESTCASE (test_cscs_sierra_mercury_support_response, data)); + g_test_suite_add (suite, TESTCASE (test_cscs_buslink_support_response, data)); + g_test_suite_add (suite, TESTCASE (test_cscs_blackberry_support_response, data)); + + result = g_test_run (); + + test_data_free (data); + + return result; } |