diff options
author | Guido Günther <agx@sigxcpu.org> | 2014-02-05 08:38:15 +0100 |
---|---|---|
committer | Guido Günther <agx@sigxcpu.org> | 2014-02-05 08:38:15 +0100 |
commit | 87bd9deec22af69bb27226254803ac5c63b18d78 (patch) | |
tree | c34d42bf75c20b3fd740e4cd59e45aa6901a9fed /src |
Imported Upstream version 0.3upstream/0.3
Diffstat (limited to 'src')
45 files changed, 14461 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..9209b55 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,99 @@ +SUBDIRS=. tests + +noinst_LTLIBRARIES = libmodem-helpers.la + +libmodem_helpers_la_CPPFLAGS = \ + $(MM_CFLAGS) + +libmodem_helpers_la_SOURCES = \ + mm-errors.c \ + mm-errors.h \ + mm-modem-helpers.c \ + mm-modem-helpers.h + +sbin_PROGRAMS = modem-manager + +modem_manager_CPPFLAGS = \ + $(MM_CFLAGS) \ + $(GUDEV_CFLAGS) \ + -I${top_builddir}/marshallers \ + -DPLUGINDIR=\"$(pkglibdir)\" + +modem_manager_LDADD = \ + $(MM_LIBS) \ + $(GUDEV_LIBS) \ + $(top_builddir)/marshallers/libmarshallers.la \ + $(builddir)/libmodem-helpers.la + +modem_manager_SOURCES = \ + main.c \ + mm-callback-info.c \ + mm-callback-info.h \ + 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-serial-parsers.c \ + mm-serial-parsers.h \ + mm-generic-cdma.c \ + mm-generic-cdma.h \ + mm-generic-gsm.c \ + mm-generic-gsm.h \ + mm-modem-cdma.c \ + mm-modem-cdma.h \ + mm-modem-gsm.h \ + mm-modem-gsm-card.c \ + mm-modem-gsm-card.h \ + mm-modem-gsm-network.c \ + mm-modem-gsm-network.h \ + mm-modem-gsm-sms.c \ + mm-modem-gsm-sms.h \ + mm-modem-simple.c \ + mm-modem-simple.h \ + mm-options.c \ + mm-options.h \ + mm-plugin.c \ + mm-plugin.h \ + mm-plugin-base.c \ + mm-plugin-base.h \ + mm-properties-changed-signal.c \ + mm-properties-changed-signal.h + +mm-manager-glue.h: $(top_srcdir)/introspection/mm-manager.xml + dbus-binding-tool --prefix=mm_manager --mode=glib-server --output=$@ $< + +mm-modem-glue.h: $(top_srcdir)/introspection/mm-modem.xml + dbus-binding-tool --prefix=mm_modem --mode=glib-server --output=$@ $< + +mm-modem-simple-glue.h: $(top_srcdir)/introspection/mm-modem-simple.xml + dbus-binding-tool --prefix=mm_modem_simple --mode=glib-server --output=$@ $< + +mm-modem-cdma-glue.h: $(top_srcdir)/introspection/mm-modem-cdma.xml + dbus-binding-tool --prefix=mm_modem_cdma --mode=glib-server --output=$@ $< + +mm-modem-gsm-card-glue.h: $(top_srcdir)/introspection/mm-modem-gsm-card.xml + dbus-binding-tool --prefix=mm_modem_gsm_card --mode=glib-server --output=$@ $< + +mm-modem-gsm-network-glue.h: $(top_srcdir)/introspection/mm-modem-gsm-network.xml + dbus-binding-tool --prefix=mm_modem_gsm_network --mode=glib-server --output=$@ $< + +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 \ + mm-modem-simple-glue.h \ + mm-modem-cdma-glue.h \ + mm-modem-gsm-card-glue.h \ + mm-modem-gsm-network-glue.h \ + mm-modem-gsm-sms-glue.h + +CLEANFILES = $(BUILT_SOURCES) diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..3669115 --- /dev/null +++ b/src/main.c @@ -0,0 +1,211 @@ +/* -*- 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. + */ + +#include <signal.h> +#include <syslog.h> +#include <string.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" + +static GMainLoop *loop = NULL; + +static void +mm_signal_handler (int signo) +{ + if (signo == SIGUSR1) + 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); + } +} + +static void +setup_signals (void) +{ + struct sigaction action; + sigset_t mask; + + sigemptyset (&mask); + action.sa_handler = mm_signal_handler; + action.sa_mask = mask; + action.sa_flags = 0; + sigaction (SIGUSR1, &action, NULL); + sigaction (SIGTERM, &action, NULL); + sigaction (SIGINT, &action, NULL); +} + +static void +log_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer ignored) +{ + int syslog_priority; + + switch (log_level) { + case G_LOG_LEVEL_ERROR: + syslog_priority = LOG_CRIT; + break; + + case G_LOG_LEVEL_CRITICAL: + syslog_priority = LOG_ERR; + break; + + case G_LOG_LEVEL_WARNING: + syslog_priority = LOG_WARNING; + break; + + case G_LOG_LEVEL_MESSAGE: + syslog_priority = LOG_NOTICE; + break; + + case G_LOG_LEVEL_DEBUG: + syslog_priority = LOG_DEBUG; + break; + + case G_LOG_LEVEL_INFO: + default: + syslog_priority = LOG_INFO; + break; + } + + syslog (syslog_priority, "%s", message); +} + + +static void +logging_setup (void) +{ + openlog (G_LOG_DOMAIN, LOG_CONS, LOG_DAEMON); + g_log_set_handler (G_LOG_DOMAIN, + G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, + log_handler, + NULL); +} + +static void +logging_shutdown (void) +{ + closelog (); +} + +static void +destroy_cb (DBusGProxy *proxy, gpointer user_data) +{ + g_message ("disconnected from the system bus, exiting."); + g_main_loop_quit (loop); +} + +static DBusGProxy * +create_dbus_proxy (DBusGConnection *bus) +{ + DBusGProxy *proxy; + GError *err = NULL; + int request_name_result; + + proxy = dbus_g_proxy_new_for_name (bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus"); + + if (!dbus_g_proxy_call (proxy, "RequestName", &err, + G_TYPE_STRING, MM_DBUS_SERVICE, + G_TYPE_UINT, DBUS_NAME_FLAG_DO_NOT_QUEUE, + G_TYPE_INVALID, + G_TYPE_UINT, &request_name_result, + G_TYPE_INVALID)) { + g_warning ("Could not acquire the %s service.\n" + " Message: '%s'", MM_DBUS_SERVICE, err->message); + + g_error_free (err); + g_object_unref (proxy); + proxy = NULL; + } else if (request_name_result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + g_warning ("Could not acquire the " MM_DBUS_SERVICE + " service as it is already taken. Return: %d", + request_name_result); + + g_object_unref (proxy); + proxy = NULL; + } else { + dbus_g_proxy_add_signal (proxy, "NameOwnerChanged", + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_INVALID); + } + + return proxy; +} + +static gboolean +start_manager (gpointer user_data) +{ + mm_manager_start (MM_MANAGER (user_data)); + return FALSE; +} + +int +main (int argc, char *argv[]) +{ + DBusGConnection *bus; + DBusGProxy *proxy; + MMManager *manager; + GError *err = NULL; + guint id; + + mm_options_parse (argc, argv); + g_type_init (); + + setup_signals (); + + if (!mm_options_debug ()) + logging_setup (); + + bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err); + if (!bus) { + g_warning ("Could not get the system bus. Make sure " + "the message bus daemon is running! Message: %s", + err->message); + g_error_free (err); + return -1; + } + + proxy = create_dbus_proxy (bus); + if (!proxy) + return -1; + + manager = mm_manager_new (bus); + g_idle_add (start_manager, manager); + + loop = g_main_loop_new (NULL, FALSE); + id = g_signal_connect (proxy, "destroy", G_CALLBACK (destroy_cb), loop); + + g_main_loop_run (loop); + + g_signal_handler_disconnect (proxy, id); + + g_object_unref (manager); + g_object_unref (proxy); + dbus_g_connection_unref (bus); + + logging_shutdown (); + + return 0; +} diff --git a/src/mm-callback-info.c b/src/mm-callback-info.c new file mode 100644 index 0000000..1882898 --- /dev/null +++ b/src/mm-callback-info.c @@ -0,0 +1,210 @@ +/* -*- 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 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#include "mm-callback-info.h" +#include "mm-errors.h" + +#define CALLBACK_INFO_RESULT "callback-info-result" + +static void +invoke_mm_modem_fn (MMCallbackInfo *info) +{ + MMModemFn callback = (MMModemFn) info->callback; + + callback (info->modem, info->error, info->user_data); +} + +static void +invoke_mm_modem_uint_fn (MMCallbackInfo *info) +{ + MMModemUIntFn callback = (MMModemUIntFn) info->callback; + + callback (info->modem, + GPOINTER_TO_UINT (mm_callback_info_get_data (info, CALLBACK_INFO_RESULT)), + info->error, info->user_data); +} + +static void +invoke_mm_modem_string_fn (MMCallbackInfo *info) +{ + MMModemStringFn callback = (MMModemStringFn) info->callback; + + callback (info->modem, + (const char *) mm_callback_info_get_data (info, CALLBACK_INFO_RESULT), + info->error, info->user_data); +} + + +static void +modem_destroyed_cb (gpointer data, GObject *destroyed) +{ + MMCallbackInfo *info = data; + + info->modem = NULL; + if (!info->pending_id) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_REMOVED, + "The modem was removed."); + mm_callback_info_schedule (info); + } +} + +static void +callback_info_done (gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + info->pending_id = 0; + info->called = TRUE; + + if (info->invoke_fn && info->callback) + info->invoke_fn (info); + + mm_callback_info_unref (info); +} + +static gboolean +callback_info_do (gpointer user_data) +{ + /* Nothing here, everything is done in callback_info_done to make sure the info->callback + always gets called, even if the pending call gets cancelled. */ + return FALSE; +} + +void +mm_callback_info_schedule (MMCallbackInfo *info) +{ + g_return_if_fail (info != NULL); + g_return_if_fail (info->pending_id == 0); + g_return_if_fail (info->called == FALSE); + + info->pending_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, callback_info_do, info, callback_info_done); +} + +MMCallbackInfo * +mm_callback_info_new_full (MMModem *modem, + MMCallbackInfoInvokeFn invoke_fn, + GCallback callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + g_return_val_if_fail (modem != NULL, NULL); + + info = g_slice_new0 (MMCallbackInfo); + g_datalist_init (&info->qdata); + info->modem = modem; + g_object_weak_ref (G_OBJECT (modem), modem_destroyed_cb, info); + info->invoke_fn = invoke_fn; + info->callback = callback; + info->user_data = user_data; + info->refcount = 1; + + return info; +} + +MMCallbackInfo * +mm_callback_info_new (MMModem *modem, MMModemFn callback, gpointer user_data) +{ + g_return_val_if_fail (modem != NULL, NULL); + + return mm_callback_info_new_full (modem, invoke_mm_modem_fn, (GCallback) callback, user_data); +} + +MMCallbackInfo * +mm_callback_info_uint_new (MMModem *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + g_return_val_if_fail (modem != NULL, NULL); + + return mm_callback_info_new_full (modem, invoke_mm_modem_uint_fn, (GCallback) callback, user_data); +} + +MMCallbackInfo * +mm_callback_info_string_new (MMModem *modem, + MMModemStringFn callback, + gpointer user_data) +{ + g_return_val_if_fail (modem != NULL, NULL); + + return mm_callback_info_new_full (modem, invoke_mm_modem_string_fn, (GCallback) callback, user_data); +} + +void +mm_callback_info_set_result (MMCallbackInfo *info, + gpointer data, + GDestroyNotify destroy) +{ + g_return_if_fail (info != NULL); + + mm_callback_info_set_data (info, CALLBACK_INFO_RESULT, data, destroy); +} + +void +mm_callback_info_set_data (MMCallbackInfo *info, + const char *key, + gpointer data, + GDestroyNotify destroy) +{ + g_return_if_fail (info != NULL); + g_return_if_fail (key != NULL); + + g_datalist_id_set_data_full (&info->qdata, g_quark_from_string (key), data, + data ? destroy : (GDestroyNotify) NULL); +} + +gpointer +mm_callback_info_get_data (MMCallbackInfo *info, const char *key) +{ + GQuark quark; + + g_return_val_if_fail (info != NULL, NULL); + g_return_val_if_fail (key != NULL, NULL); + + quark = g_quark_try_string (key); + + return quark ? g_datalist_id_get_data (&info->qdata, quark) : NULL; +} + +MMCallbackInfo * +mm_callback_info_ref (MMCallbackInfo *info) +{ + g_return_val_if_fail (info != NULL, NULL); + g_return_val_if_fail (info->refcount > 0, NULL); + + info->refcount++; + return info; +} + +void +mm_callback_info_unref (MMCallbackInfo *info) +{ + g_return_if_fail (info != NULL); + + info->refcount--; + if (info->refcount == 0) { + if (info->error) + g_error_free (info->error); + + if (info->modem) + g_object_weak_unref (G_OBJECT (info->modem), modem_destroyed_cb, info); + + g_datalist_clear (&info->qdata); + g_slice_free (MMCallbackInfo, info); + } +} + diff --git a/src/mm-callback-info.h b/src/mm-callback-info.h new file mode 100644 index 0000000..66c2048 --- /dev/null +++ b/src/mm-callback-info.h @@ -0,0 +1,74 @@ +/* -*- 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 Novell, Inc. + */ + +#ifndef MM_CALLBACK_INFO_H +#define MM_CALLBACK_INFO_H + +#include "mm-modem.h" + +typedef struct _MMCallbackInfo MMCallbackInfo; + +typedef void (*MMCallbackInfoInvokeFn) (MMCallbackInfo *info); + +struct _MMCallbackInfo { + guint32 refcount; + + GData *qdata; + MMModem *modem; + + MMCallbackInfoInvokeFn invoke_fn; + GCallback callback; + gboolean called; + + gpointer user_data; + GError *error; + guint pending_id; +}; + +MMCallbackInfo *mm_callback_info_new_full (MMModem *modem, + MMCallbackInfoInvokeFn invoke_fn, + GCallback callback, + gpointer user_data); + +MMCallbackInfo *mm_callback_info_new (MMModem *modem, + MMModemFn callback, + gpointer user_data); + +MMCallbackInfo *mm_callback_info_uint_new (MMModem *modem, + MMModemUIntFn callback, + gpointer user_data); + +MMCallbackInfo *mm_callback_info_string_new (MMModem *modem, + MMModemStringFn callback, + gpointer user_data); + +void mm_callback_info_schedule (MMCallbackInfo *info); +void mm_callback_info_set_result (MMCallbackInfo *info, + gpointer data, + GDestroyNotify destroy); + +void mm_callback_info_set_data (MMCallbackInfo *info, + const char *key, + gpointer data, + GDestroyNotify destroy); + +gpointer mm_callback_info_get_data (MMCallbackInfo *info, + const char *key); + +MMCallbackInfo *mm_callback_info_ref (MMCallbackInfo *info); +void mm_callback_info_unref (MMCallbackInfo *info); + +#endif /* MM_CALLBACK_INFO_H */ + diff --git a/src/mm-errors.c b/src/mm-errors.c new file mode 100644 index 0000000..34f56c1 --- /dev/null +++ b/src/mm-errors.c @@ -0,0 +1,290 @@ +/* -*- 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 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#include "mm-errors.h" + +#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } + +GQuark +mm_serial_error_quark (void) +{ + static GQuark ret = 0; + + if (ret == 0) + ret = g_quark_from_static_string ("mm_serial_error"); + + return ret; +} + +GType +mm_serial_error_get_type (void) +{ + static GType etype = 0; + + 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"), + { 0, 0, 0 } + }; + + etype = g_enum_register_static ("MMSerialError", values); + } + + return etype; +} + +GQuark +mm_modem_error_quark (void) +{ + static GQuark ret = 0; + + if (ret == 0) + ret = g_quark_from_static_string ("mm_modem_error"); + + return ret; +} + +GType +mm_modem_error_get_type (void) +{ + static GType etype = 0; + + if (etype == 0) { + static const GEnumValue values[] = { + ENUM_ENTRY (MM_MODEM_ERROR_GENERAL, "General"), + ENUM_ENTRY (MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, "OperationNotSupported"), + ENUM_ENTRY (MM_MODEM_ERROR_CONNECTED, "Connected"), + ENUM_ENTRY (MM_MODEM_ERROR_DISCONNECTED, "Disconnected"), + ENUM_ENTRY (MM_MODEM_ERROR_OPERATION_IN_PROGRESS, "OperationInProgress"), + ENUM_ENTRY (MM_MODEM_ERROR_REMOVED, "Removed"), + { 0, 0, 0 } + }; + + etype = g_enum_register_static ("MMModemError", values); + } + + return etype; +} + +GQuark +mm_modem_connect_error_quark (void) +{ + static GQuark ret = 0; + + if (ret == 0) + ret = g_quark_from_static_string ("mm_modem_connect_error"); + + return ret; +} + +GType +mm_modem_connect_error_get_type (void) +{ + static GType etype = 0; + + if (etype == 0) { + static const GEnumValue values[] = { + ENUM_ENTRY (MM_MODEM_CONNECT_ERROR_NO_CARRIER, "NoCarrier"), + ENUM_ENTRY (MM_MODEM_CONNECT_ERROR_NO_DIALTONE, "NoDialtone"), + ENUM_ENTRY (MM_MODEM_CONNECT_ERROR_BUSY, "Busy"), + ENUM_ENTRY (MM_MODEM_CONNECT_ERROR_NO_ANSWER, "NoAnswer"), + { 0, 0, 0 } + }; + + etype = g_enum_register_static ("MMModemConnectError", values); + } + + return etype; +} + +GError * +mm_modem_connect_error_for_code (int error_code) +{ + const char *msg; + + switch (error_code) { + case MM_MODEM_CONNECT_ERROR_NO_CARRIER: + msg = "No carrier"; + break; + case MM_MODEM_CONNECT_ERROR_NO_DIALTONE: + msg = "No dialtone"; + break; + case MM_MODEM_CONNECT_ERROR_BUSY: + msg = "Busy"; + break; + case MM_MODEM_CONNECT_ERROR_NO_ANSWER: + msg = "No answer"; + break; + + default: + g_warning ("Invalid error code"); + /* uhm... make something up (yes, ok, lie!). */ + error_code = MM_MODEM_CONNECT_ERROR_NO_CARRIER; + msg = "No carrier"; + } + + return g_error_new_literal (MM_MODEM_CONNECT_ERROR, error_code, msg); +} + + +GQuark +mm_mobile_error_quark (void) +{ + static GQuark ret = 0; + + if (ret == 0) + ret = g_quark_from_static_string ("mm_mobile_error"); + + return ret; +} + +GType +mm_mobile_error_get_type (void) +{ + static GType etype = 0; + + if (etype == 0) { + static const GEnumValue values[] = { + ENUM_ENTRY (MM_MOBILE_ERROR_PHONE_FAILURE, "PhoneFailure"), + ENUM_ENTRY (MM_MOBILE_ERROR_NO_CONNECTION, "NoConnection"), + ENUM_ENTRY (MM_MOBILE_ERROR_LINK_RESERVED, "LinkReserved"), + ENUM_ENTRY (MM_MOBILE_ERROR_NOT_ALLOWED, "OperationNotAllowed"), + ENUM_ENTRY (MM_MOBILE_ERROR_NOT_SUPPORTED, "OperationNotSupported"), + ENUM_ENTRY (MM_MOBILE_ERROR_PH_SIM_PIN, "PhSimPinRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_PH_FSIM_PIN, "PhFSimPinRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_PH_FSIM_PUK, "PhFSimPukRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_SIM_NOT_INSERTED, "SimNotInserted"), + ENUM_ENTRY (MM_MOBILE_ERROR_SIM_PIN, "SimPinRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_SIM_PUK, "SimPukRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_SIM_FAILURE, "SimFailure"), + ENUM_ENTRY (MM_MOBILE_ERROR_SIM_BUSY, "SimBusy"), + ENUM_ENTRY (MM_MOBILE_ERROR_SIM_WRONG, "SimWrong"), + ENUM_ENTRY (MM_MOBILE_ERROR_WRONG_PASSWORD, "IncorrectPassword"), + ENUM_ENTRY (MM_MOBILE_ERROR_SIM_PIN2, "SimPin2Required"), + ENUM_ENTRY (MM_MOBILE_ERROR_SIM_PUK2, "SimPuk2Required"), + ENUM_ENTRY (MM_MOBILE_ERROR_MEMORY_FULL, "MemoryFull"), + ENUM_ENTRY (MM_MOBILE_ERROR_INVALID_INDEX, "InvalidIndex"), + ENUM_ENTRY (MM_MOBILE_ERROR_NOT_FOUND, "NotFound"), + ENUM_ENTRY (MM_MOBILE_ERROR_MEMORY_FAILURE, "MemoryFailure"), + ENUM_ENTRY (MM_MOBILE_ERROR_TEXT_TOO_LONG, "TextTooLong"), + ENUM_ENTRY (MM_MOBILE_ERROR_INVALID_CHARS, "InvalidChars"), + ENUM_ENTRY (MM_MOBILE_ERROR_DIAL_STRING_TOO_LONG, "DialStringTooLong"), + ENUM_ENTRY (MM_MOBILE_ERROR_DIAL_STRING_INVALID, "InvalidDialString"), + ENUM_ENTRY (MM_MOBILE_ERROR_NO_NETWORK, "NoNetwork"), + ENUM_ENTRY (MM_MOBILE_ERROR_NETWORK_TIMEOUT, "NetworkTimeout"), + ENUM_ENTRY (MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED, "NetworkNotAllowed"), + ENUM_ENTRY (MM_MOBILE_ERROR_NETWORK_PIN, "NetworkPinRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_NETWORK_PUK, "NetworkPukRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_NETWORK_SUBSET_PIN, "NetworkSubsetPinRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_NETWORK_SUBSET_PUK, "NetworkSubsetPukRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_SERVICE_PIN, "ServicePinRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_SERVICE_PUK, "ServicePukRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_CORP_PIN, "CorporatePinRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_CORP_PUK, "CorporatePukRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_HIDDEN_KEY, "HiddenKeyRequired"), + ENUM_ENTRY (MM_MOBILE_ERROR_EAP_NOT_SUPPORTED, "EapMethodNotSupported"), + ENUM_ENTRY (MM_MOBILE_ERROR_INCORRECT_PARAMS, "IncorrectParams"), + ENUM_ENTRY (MM_MOBILE_ERROR_UNKNOWN, "Unknown"), + ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_ILLEGAL_MS, "GprsIllegalMs"), + ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_ILLEGAL_ME, "GprsIllegalMe"), + ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_SERVICE_NOT_ALLOWED, "GprsServiceNotAllowed"), + ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_PLMN_NOT_ALLOWED, "GprsPlmnNotAllowed"), + ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_LOCATION_NOT_ALLOWED, "GprsLocationNotAllowed"), + ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_ROAMING_NOT_ALLOWED, "GprsRoamingNotAllowed"), + ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_OPTION_NOT_SUPPORTED, "GprsOptionNotSupported"), + ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_NOT_SUBSCRIBED, "GprsNotSubscribed"), + ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_OUT_OF_ORDER, "GprsOutOfOrder"), + ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_PDP_AUTH_FAILURE, "GprsPdpAuthFailure"), + ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_UNKNOWN, "GprsUnspecified"), + ENUM_ENTRY (MM_MOBILE_ERROR_GPRS_INVALID_CLASS, "GprsInvalidClass"), + { 0, 0, 0 } + }; + + etype = g_enum_register_static ("MMMobileError", values); + } + + return etype; +} + +GError * +mm_mobile_error_for_code (int error_code) +{ + const char *msg; + + 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"); + 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 new file mode 100644 index 0000000..c02a351 --- /dev/null +++ b/src/mm-errors.h @@ -0,0 +1,131 @@ +/* -*- 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 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef MM_MODEM_ERROR_H +#define MM_MODEM_ERROR_H + +#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 +}; + +#define MM_SERIAL_ERROR (mm_serial_error_quark ()) +#define MM_TYPE_SERIAL_ERROR (mm_serial_error_get_type ()) + +GQuark mm_serial_error_quark (void); +GType mm_serial_error_get_type (void); + + +enum { + MM_MODEM_ERROR_GENERAL = 0, + MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED = 1, + MM_MODEM_ERROR_CONNECTED = 2, + MM_MODEM_ERROR_DISCONNECTED = 3, + MM_MODEM_ERROR_OPERATION_IN_PROGRESS = 4, + MM_MODEM_ERROR_REMOVED = 5 +}; + +#define MM_MODEM_ERROR (mm_modem_error_quark ()) +#define MM_TYPE_MODEM_ERROR (mm_modem_error_get_type ()) + +GQuark mm_modem_error_quark (void); +GType mm_modem_error_get_type (void); + + +enum { + MM_MODEM_CONNECT_ERROR_NO_CARRIER = 3, + MM_MODEM_CONNECT_ERROR_NO_DIALTONE = 6, + MM_MODEM_CONNECT_ERROR_BUSY = 7, + MM_MODEM_CONNECT_ERROR_NO_ANSWER = 8, +}; + +#define MM_MODEM_CONNECT_ERROR (mm_modem_connect_error_quark ()) +#define MM_TYPE_MODEM_CONNECT_ERROR (mm_modem_connect_error_get_type ()) + +GQuark mm_modem_connect_error_quark (void); +GType mm_modem_connect_error_get_type (void); +GError *mm_modem_connect_error_for_code (int error_code); + + +enum { + MM_MOBILE_ERROR_PHONE_FAILURE = 0, + MM_MOBILE_ERROR_NO_CONNECTION = 1, + MM_MOBILE_ERROR_LINK_RESERVED = 2, + MM_MOBILE_ERROR_NOT_ALLOWED = 3, + MM_MOBILE_ERROR_NOT_SUPPORTED = 4, + MM_MOBILE_ERROR_PH_SIM_PIN = 5, + MM_MOBILE_ERROR_PH_FSIM_PIN = 6, + MM_MOBILE_ERROR_PH_FSIM_PUK = 7, + MM_MOBILE_ERROR_SIM_NOT_INSERTED = 10, + MM_MOBILE_ERROR_SIM_PIN = 11, + MM_MOBILE_ERROR_SIM_PUK = 12, + MM_MOBILE_ERROR_SIM_FAILURE = 13, + MM_MOBILE_ERROR_SIM_BUSY = 14, + MM_MOBILE_ERROR_SIM_WRONG = 15, + MM_MOBILE_ERROR_WRONG_PASSWORD = 16, + MM_MOBILE_ERROR_SIM_PIN2 = 17, + MM_MOBILE_ERROR_SIM_PUK2 = 18, + MM_MOBILE_ERROR_MEMORY_FULL = 20, + MM_MOBILE_ERROR_INVALID_INDEX = 21, + MM_MOBILE_ERROR_NOT_FOUND = 22, + MM_MOBILE_ERROR_MEMORY_FAILURE = 23, + MM_MOBILE_ERROR_TEXT_TOO_LONG = 24, + MM_MOBILE_ERROR_INVALID_CHARS = 25, + MM_MOBILE_ERROR_DIAL_STRING_TOO_LONG = 26, + MM_MOBILE_ERROR_DIAL_STRING_INVALID = 27, + MM_MOBILE_ERROR_NO_NETWORK = 30, + MM_MOBILE_ERROR_NETWORK_TIMEOUT = 31, + MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED = 32, + MM_MOBILE_ERROR_NETWORK_PIN = 40, + MM_MOBILE_ERROR_NETWORK_PUK = 41, + MM_MOBILE_ERROR_NETWORK_SUBSET_PIN = 42, + MM_MOBILE_ERROR_NETWORK_SUBSET_PUK = 43, + MM_MOBILE_ERROR_SERVICE_PIN = 44, + MM_MOBILE_ERROR_SERVICE_PUK = 45, + MM_MOBILE_ERROR_CORP_PIN = 46, + MM_MOBILE_ERROR_CORP_PUK = 47, + MM_MOBILE_ERROR_HIDDEN_KEY = 48, + MM_MOBILE_ERROR_EAP_NOT_SUPPORTED = 49, + MM_MOBILE_ERROR_INCORRECT_PARAMS = 50, + MM_MOBILE_ERROR_UNKNOWN = 100, + + MM_MOBILE_ERROR_GPRS_ILLEGAL_MS = 103, + MM_MOBILE_ERROR_GPRS_ILLEGAL_ME = 106, + MM_MOBILE_ERROR_GPRS_SERVICE_NOT_ALLOWED = 107, + MM_MOBILE_ERROR_GPRS_PLMN_NOT_ALLOWED = 111, + MM_MOBILE_ERROR_GPRS_LOCATION_NOT_ALLOWED = 112, + MM_MOBILE_ERROR_GPRS_ROAMING_NOT_ALLOWED = 113, + 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_INVALID_CLASS = 150 +}; + + +#define MM_MOBILE_ERROR (mm_mobile_error_quark ()) +#define MM_TYPE_MOBILE_ERROR (mm_mobile_error_get_type ()) + +GQuark mm_mobile_error_quark (void); +GType mm_mobile_error_get_type (void); +GError *mm_mobile_error_for_code (int error_code); + +#endif /* MM_MODEM_ERROR_H */ diff --git a/src/mm-generic-cdma.c b/src/mm-generic-cdma.c new file mode 100644 index 0000000..50cd86c --- /dev/null +++ b/src/mm-generic-cdma.c @@ -0,0 +1,1831 @@ +/* -*- 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 <string.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <stdlib.h> + +#include "mm-generic-cdma.h" +#include "mm-modem-cdma.h" +#include "mm-modem-simple.h" +#include "mm-serial-port.h" +#include "mm-errors.h" +#include "mm-callback-info.h" +#include "mm-serial-parsers.h" + +static void simple_reg_callback (MMModemCdma *modem, + MMModemCdmaRegistrationState cdma_1x_reg_state, + MMModemCdmaRegistrationState evdo_reg_state, + GError *error, + gpointer user_data); + +static void simple_state_machine (MMModem *modem, GError *error, gpointer user_data); + +static void update_enabled_state (MMGenericCdma *self, + gboolean stay_connected, + MMModemStateReason reason); + +static void modem_init (MMModem *modem_class); +static void modem_cdma_init (MMModemCdma *cdma_class); +static void modem_simple_init (MMModemSimple *class); + +G_DEFINE_TYPE_EXTENDED (MMGenericCdma, mm_generic_cdma, MM_TYPE_MODEM_BASE, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_CDMA, modem_cdma_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_SIMPLE, modem_simple_init)) + +#define MM_GENERIC_CDMA_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_GENERIC_CDMA, MMGenericCdmaPrivate)) + +typedef struct { + guint32 cdma1x_quality; + guint32 evdo_quality; + gboolean valid; + gboolean evdo_rev0; + gboolean evdo_revA; + gboolean reg_try_css; + + MMModemCdmaRegistrationState cdma_1x_reg_state; + MMModemCdmaRegistrationState evdo_reg_state; + + guint reg_tries; + guint reg_retry_id; + guint reg_state_changed_id; + MMCallbackInfo *simple_connect_info; + + MMSerialPort *primary; + MMSerialPort *secondary; + MMPort *data; +} MMGenericCdmaPrivate; + +enum { + PROP_0, + PROP_EVDO_REV0, + PROP_EVDO_REVA, + PROP_REG_TRY_CSS, + LAST_PROP +}; + +MMModem * +mm_generic_cdma_new (const char *device, + const char *driver, + const char *plugin, + gboolean evdo_rev0, + gboolean evdo_revA) +{ + g_return_val_if_fail (device != NULL, NULL); + g_return_val_if_fail (driver != NULL, NULL); + g_return_val_if_fail (plugin != NULL, NULL); + + return MM_MODEM (g_object_new (MM_TYPE_GENERIC_CDMA, + MM_MODEM_MASTER_DEVICE, device, + MM_MODEM_DRIVER, driver, + MM_MODEM_PLUGIN, plugin, + MM_GENERIC_CDMA_EVDO_REV0, evdo_rev0, + MM_GENERIC_CDMA_EVDO_REVA, evdo_revA, + NULL)); +} + +/*****************************************************************************/ + +static void +check_valid (MMGenericCdma *self) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + gboolean new_valid = FALSE; + + if (priv->primary && priv->data) + new_valid = TRUE; + + mm_modem_base_set_valid (MM_MODEM_BASE (self), new_valid); +} + +static gboolean +owns_port (MMModem *modem, const char *subsys, const char *name) +{ + return !!mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name); +} + +MMPort * +mm_generic_cdma_grab_port (MMGenericCdma *self, + const char *subsys, + const char *name, + MMPortType suggested_type, + gpointer user_data, + GError **error) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + MMPortType ptype = MM_PORT_TYPE_IGNORED; + MMPort *port; + + g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), FALSE); + if (priv->primary) + g_return_val_if_fail (suggested_type != MM_PORT_TYPE_PRIMARY, FALSE); + + if (!strcmp (subsys, "tty")) { + if (suggested_type != MM_PORT_TYPE_UNKNOWN) + ptype = suggested_type; + else { + if (!priv->primary) + ptype = MM_PORT_TYPE_PRIMARY; + else if (!priv->secondary) + ptype = MM_PORT_TYPE_SECONDARY; + } + } + + port = mm_modem_base_add_port (MM_MODEM_BASE (self), subsys, name, ptype); + if (port && MM_IS_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); + + if (ptype == MM_PORT_TYPE_PRIMARY) { + priv->primary = MM_SERIAL_PORT (port); + if (!priv->data) { + priv->data = port; + g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); + } + check_valid (self); + } else if (ptype == MM_PORT_TYPE_SECONDARY) + priv->secondary = MM_SERIAL_PORT (port); + } else { + /* Net device (if any) is the preferred data port */ + if (!priv->data || MM_IS_SERIAL_PORT (priv->data)) { + priv->data = port; + g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); + check_valid (self); + } + } + + return port; +} + +static gboolean +grab_port (MMModem *modem, + const char *subsys, + const char *name, + MMPortType suggested_type, + gpointer user_data, + GError **error) +{ + return !!mm_generic_cdma_grab_port (MM_GENERIC_CDMA (modem), subsys, name, suggested_type, user_data, error); +} + +static void +release_port (MMModem *modem, const char *subsys, const char *name) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); + MMPort *port; + + port = mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name); + if (!port) + return; + + if (port == MM_PORT (priv->primary)) { + mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); + priv->primary = NULL; + } + + if (port == priv->data) { + priv->data = NULL; + g_object_notify (G_OBJECT (modem), MM_MODEM_DATA_DEVICE); + } + + if (port == MM_PORT (priv->secondary)) { + mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); + priv->secondary = NULL; + } + + check_valid (MM_GENERIC_CDMA (modem)); +} + +MMSerialPort * +mm_generic_cdma_get_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); + + if (ptype == MM_PORT_TYPE_PRIMARY) + return MM_GENERIC_CDMA_GET_PRIVATE (modem)->primary; + else if (ptype == MM_PORT_TYPE_SECONDARY) + return MM_GENERIC_CDMA_GET_PRIVATE (modem)->secondary; + + return NULL; +} + +/*****************************************************************************/ + +void +mm_generic_cdma_set_1x_registration_state (MMGenericCdma *self, + MMModemCdmaRegistrationState new_state) +{ + MMGenericCdmaPrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_GENERIC_CDMA (self)); + + priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + + if (priv->cdma_1x_reg_state != new_state) { + priv->cdma_1x_reg_state = new_state; + + update_enabled_state (self, TRUE, MM_MODEM_STATE_REASON_NONE); + mm_modem_cdma_emit_registration_state_changed (MM_MODEM_CDMA (self), + priv->cdma_1x_reg_state, + priv->evdo_reg_state); + } +} + +void +mm_generic_cdma_set_evdo_registration_state (MMGenericCdma *self, + MMModemCdmaRegistrationState new_state) +{ + MMGenericCdmaPrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_GENERIC_CDMA (self)); + + priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + + if (priv->evdo_reg_state == new_state) + return; + + /* Don't update EVDO state if the card doesn't support it */ + if ( priv->evdo_rev0 + || priv->evdo_revA + || (new_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)) { + priv->evdo_reg_state = new_state; + + update_enabled_state (self, TRUE, MM_MODEM_STATE_REASON_NONE); + mm_modem_cdma_emit_registration_state_changed (MM_MODEM_CDMA (self), + priv->cdma_1x_reg_state, + priv->evdo_reg_state); + } +} + +MMModemCdmaRegistrationState +mm_generic_cdma_1x_get_registration_state_sync (MMGenericCdma *self) +{ + g_return_val_if_fail (self != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + + return MM_GENERIC_CDMA_GET_PRIVATE (self)->cdma_1x_reg_state; +} + +MMModemCdmaRegistrationState +mm_generic_cdma_evdo_get_registration_state_sync (MMGenericCdma *self) +{ + g_return_val_if_fail (self != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + + return MM_GENERIC_CDMA_GET_PRIVATE (self)->evdo_reg_state; +} + +/*****************************************************************************/ + +static void +update_enabled_state (MMGenericCdma *self, + gboolean stay_connected, + MMModemStateReason reason) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_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; + + if ( priv->cdma_1x_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN + || priv->evdo_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) + mm_modem_set_state (MM_MODEM (self), MM_MODEM_STATE_REGISTERED, reason); + else + mm_modem_set_state (MM_MODEM (self), MM_MODEM_STATE_ENABLED, reason); +} + +static void +registration_cleanup (MMGenericCdma *self, GQuark error_class, guint32 error_num) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + GError *error = NULL; + + priv->reg_tries = 0; + + if (priv->reg_state_changed_id) { + g_signal_handler_disconnect (self, priv->reg_state_changed_id); + priv->reg_state_changed_id = 0; + } + + if (priv->reg_retry_id) { + g_source_remove (priv->reg_retry_id); + priv->reg_retry_id = 0; + } + + /* Return an error to any explicit callers of simple_connect */ + if (priv->simple_connect_info && error_class) { + error = g_error_new_literal (error_class, error_num, + "Connection attempt terminated"); + simple_state_machine (MM_MODEM (self), error, priv->simple_connect_info); + g_error_free (error); + } + priv->simple_connect_info = NULL; +} + +static void +enable_all_done (MMModem *modem, GError *error, gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + + if (error) + info->error = g_error_copy (error); + else { + /* Open up the second port, if one exists */ + if (priv->secondary) { + if (!mm_serial_port_open (priv->secondary, &info->error)) { + g_assert (info->error); + goto out; + } + } + + update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE); + } + +out: + if (info->error) { + mm_modem_set_state (MM_MODEM (info->modem), + MM_MODEM_STATE_DISABLED, + MM_MODEM_STATE_REASON_NONE); + } + + mm_callback_info_schedule (info); +} + +static void +enable_error_reporting_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); + + /* Just ignore errors, see comment in init_done() */ + if (error) + g_warning ("Your CDMA modem does not support +CMEE command"); + + if (MM_GENERIC_CDMA_GET_CLASS (self)->post_enable) + MM_GENERIC_CDMA_GET_CLASS (self)->post_enable (self, enable_all_done, info); + else + enable_all_done (MM_MODEM (self), NULL, info); +} + +static void +init_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) { + mm_modem_set_state (MM_MODEM (info->modem), + MM_MODEM_STATE_DISABLED, + MM_MODEM_STATE_REASON_NONE); + + info->error = g_error_copy (error); + mm_callback_info_schedule (info); + } else { + /* Try to enable better error reporting. My experience so far indicates + there's some CDMA modems that does not support that. + 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); + } +} + +static void +flash_done (MMSerialPort *port, GError *error, gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) { + mm_modem_set_state (MM_MODEM (info->modem), + MM_MODEM_STATE_DISABLED, + MM_MODEM_STATE_REASON_NONE); + + /* Flash failed for some reason */ + info->error = g_error_copy (error); + mm_callback_info_schedule (info); + return; + } + + mm_serial_port_queue_command (port, "Z E0 V1 X4 &C1", 3, init_done, user_data); +} + +static void +enable (MMModem *modem, + MMModemFn callback, + gpointer user_data) +{ + MMGenericCdma *self = MM_GENERIC_CDMA (modem); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + MMCallbackInfo *info; + + info = mm_callback_info_new (modem, callback, user_data); + + if (!mm_serial_port_open (priv->primary, &info->error)) { + g_assert (info->error); + mm_callback_info_schedule (info); + return; + } + + mm_modem_set_state (MM_MODEM (info->modem), + MM_MODEM_STATE_ENABLING, + MM_MODEM_STATE_REASON_NONE); + + mm_serial_port_flash (priv->primary, 100, flash_done, info); +} + +static void +disable_set_previous_state (MMModem *modem, MMCallbackInfo *info) +{ + MMModemState prev_state; + + /* Reset old state since the operation failed */ + prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_CDMA_PREV_STATE_TAG)); + mm_modem_set_state (modem, prev_state, MM_MODEM_STATE_REASON_NONE); +} + +static void +disable_all_done (MMModem *modem, GError *error, gpointer user_data) +{ + MMCallbackInfo *info = user_data; + + info->error = mm_modem_check_removed (modem, error); + if (info->error) { + if (modem) + disable_set_previous_state (modem, info); + } else { + MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + + mm_serial_port_close (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; + priv->evdo_reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + } + + mm_callback_info_schedule (info); +} + +static void +disable_flash_done (MMSerialPort *port, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMGenericCdma *self; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + if (info->modem) + disable_set_previous_state (info->modem, info); + mm_callback_info_schedule (info); + return; + } + + self = MM_GENERIC_CDMA (info->modem); + + if (MM_GENERIC_CDMA_GET_CLASS (self)->post_disable) + MM_GENERIC_CDMA_GET_CLASS (self)->post_disable (self, disable_all_done, info); + else + disable_all_done (MM_MODEM (self), NULL, info); +} + +static void +disable (MMModem *modem, + MMModemFn callback, + gpointer user_data) +{ + MMGenericCdma *self = MM_GENERIC_CDMA (modem); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + MMCallbackInfo *info; + MMModemState state; + + /* Tear down any ongoing registration */ + registration_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL); + + info = mm_callback_info_new (modem, callback, user_data); + + /* Cache the previous state so we can reset it if the operation fails */ + state = mm_modem_get_state (modem); + mm_callback_info_set_data (info, + MM_GENERIC_CDMA_PREV_STATE_TAG, + GUINT_TO_POINTER (state), + NULL); + + if (priv->secondary) + mm_serial_port_close (priv->secondary); + + 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); + else + disable_flash_done (priv->primary, NULL, info); +} + +static void +dial_done (MMSerialPort *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) { + if (info->modem) + update_enabled_state (MM_GENERIC_CDMA (info->modem), FALSE, MM_MODEM_STATE_REASON_NONE); + } else { + MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + + /* Clear reg tries; we're obviously registered by this point */ + registration_cleanup (self, 0, 0); + + mm_port_set_connected (priv->data, TRUE); + mm_modem_set_state (info->modem, MM_MODEM_STATE_CONNECTED, MM_MODEM_STATE_REASON_NONE); + } + + mm_callback_info_schedule (info); +} + +static void +connect (MMModem *modem, + const char *number, + MMModemFn callback, + gpointer user_data) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); + MMCallbackInfo *info; + char *command; + + mm_modem_set_state (modem, MM_MODEM_STATE_CONNECTING, MM_MODEM_STATE_REASON_NONE); + + 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); + g_free (command); +} + +static void +disconnect_flash_done (MMSerialPort *port, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMModemState prev_state; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + if (info->modem) { + /* Reset old state since the operation failed */ + prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_CDMA_PREV_STATE_TAG)); + mm_modem_set_state (MM_MODEM (info->modem), + prev_state, + MM_MODEM_STATE_REASON_NONE); + } + } else { + mm_port_set_connected (MM_GENERIC_CDMA_GET_PRIVATE (info->modem)->data, FALSE); + update_enabled_state (MM_GENERIC_CDMA (info->modem), FALSE, MM_MODEM_STATE_REASON_NONE); + } + + mm_callback_info_schedule (info); +} + +static void +disconnect (MMModem *modem, + MMModemFn callback, + gpointer user_data) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); + MMCallbackInfo *info; + MMModemState state; + + g_return_if_fail (priv->primary != NULL); + + info = mm_callback_info_new (modem, callback, user_data); + + /* Cache the previous state so we can reset it if the operation fails */ + state = mm_modem_get_state (modem); + mm_callback_info_set_data (info, + MM_GENERIC_CDMA_PREV_STATE_TAG, + GUINT_TO_POINTER (state), + 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); +} + +static void +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; + } + + 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); +} + +/*****************************************************************************/ + +void +mm_generic_cdma_update_cdma1x_quality (MMGenericCdma *self, guint32 quality) +{ + MMGenericCdmaPrivate *priv; + + g_return_if_fail (MM_IS_GENERIC_CDMA (self)); + g_return_if_fail (quality >= 0 && quality <= 100); + + priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + if (priv->cdma1x_quality != quality) { + priv->cdma1x_quality = quality; + mm_modem_cdma_emit_signal_quality_changed (MM_MODEM_CDMA (self), quality); + } +} + +void +mm_generic_cdma_update_evdo_quality (MMGenericCdma *self, guint32 quality) +{ + MMGenericCdmaPrivate *priv; + + g_return_if_fail (MM_IS_GENERIC_CDMA (self)); + g_return_if_fail (quality >= 0 && quality <= 100); + + priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + if (priv->evdo_quality != quality) { + priv->evdo_quality = quality; + // FIXME: emit a signal + } +} + +#define CSQ2_TRIED "csq?-tried" + +static void +get_signal_quality_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMGenericCdmaPrivate *priv; + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + char *reply = response->str; + + if (error) { + if (mm_callback_info_get_data (info, CSQ2_TRIED)) + info->error = g_error_copy (error); + else { + /* Some modems want +CSQ, others want +CSQ?, and some of both types + * will return ERROR if they don't get the command they want. So + * 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); + return; + } + } else { + int quality, ber; + + /* Got valid reply */ + if (!strncmp (reply, "+CSQ: ", 6)) + reply += 6; + + if (sscanf (reply, "%d, %d", &quality, &ber)) { + /* 99 means unknown/no service */ + if (quality == 99) { + info->error = g_error_new_literal (MM_MOBILE_ERROR, + MM_MOBILE_ERROR_NO_NETWORK, + "No service"); + } else { + /* Normalize the quality */ + quality = CLAMP (quality, 0, 31) * 100 / 31; + + priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); + 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); + } + } + } else + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "%s", "Could not parse signal quality results"); + } + + 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; + + 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; + } + + /* Use secondary port if primary is connected */ + port = priv->secondary; + } + + 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, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *p; + + if (error) + info->error = g_error_copy (error); + else { + p = strip_response (response->str, "+GSN:"); + mm_callback_info_set_result (info, g_strdup (p), g_free); + } + + mm_callback_info_schedule (info); +} + +static void +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; + } + + /* Use secondary port if primary is connected */ + port = priv->secondary; + } + + 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); +} + +static void +serving_system_invoke (MMCallbackInfo *info) +{ + MMModemCdmaServingSystemFn callback = (MMModemCdmaServingSystemFn) info->callback; + + callback (MM_MODEM_CDMA (info->modem), + GPOINTER_TO_UINT (mm_callback_info_get_data (info, "class")), + (unsigned char) GPOINTER_TO_UINT (mm_callback_info_get_data (info, "band")), + GPOINTER_TO_UINT (mm_callback_info_get_data (info, "sid")), + info->error, + info->user_data); +} + +static int +normalize_class (const char *orig_class) +{ + char class; + + g_return_val_if_fail (orig_class != NULL, '0'); + + class = toupper (orig_class[0]); + + /* Cellular (850MHz) */ + if (class == '1' || class == 'C') + return 1; + /* PCS (1900MHz) */ + if (class == '2' || class == 'P') + return 2; + + /* Unknown/not registered */ + return 0; +} + +static char +normalize_band (const char *long_band, int *out_class) +{ + char band; + + g_return_val_if_fail (long_band != NULL, 'Z'); + + /* There are two response formats for the band; one includes the band + * class and the other doesn't. For modems that include the band class + * (ex Novatel S720) you'll see "Px" or "Cx" depending on whether the modem + * is registered on a PCS/1900 (P) or Cellular/850 (C) system. + */ + band = toupper (long_band[0]); + + /* Possible band class in first position; return it */ + if (band == 'C' || band == 'P') { + char tmp[2] = { band, '\0' }; + + *out_class = normalize_class (tmp); + band = toupper (long_band[1]); + } + + /* normalize to A - F, and Z */ + if (band >= 'A' && band <= 'F') + return band; + + /* Unknown/not registered */ + return 'Z'; +} + +static int +convert_sid (const char *sid) +{ + long int tmp_sid; + + g_return_val_if_fail (sid != NULL, 99999); + + errno = 0; + tmp_sid = strtol (sid, NULL, 10); + if ((errno == EINVAL) || (errno == ERANGE)) + return 99999; + else if (tmp_sid < G_MININT || tmp_sid > G_MAXINT) + return 99999; + + return (int) tmp_sid; +} + +static void +serving_system_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + char *reply = response->str; + int class = 0, sid = 99999, num; + unsigned char band = 'Z'; + gboolean success = FALSE; + + if (error) { + info->error = g_error_copy (error); + goto out; + } + + if (strstr (reply, "+CSS: ")) + reply += 6; + + num = sscanf (reply, "? , %d", &sid); + if (num == 1) { + /* UTStarcom and Huawei modems that use IS-707-A format; note that + * this format obviously doesn't have other indicators like band and + * class and thus SID 0 will be reported as "no service" (see below). + */ + class = 0; + band = 'Z'; + success = TRUE; + } else { + GRegex *r; + GMatchInfo *match_info; + int override_class = 0; + + /* Format is "<band_class>,<band>,<sid>" */ + r = g_regex_new ("\\s*([^,]*?)\\s*,\\s*([^,]*?)\\s*,\\s*(\\d+)", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + if (!r) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Could not parse Serving System results (regex creation failed)."); + goto out; + } + + g_regex_match (r, reply, 0, &match_info); + if (g_match_info_get_match_count (match_info) >= 3) { + char *str; + + /* band class */ + str = g_match_info_fetch (match_info, 1); + class = normalize_class (str); + g_free (str); + + /* band */ + str = g_match_info_fetch (match_info, 2); + band = normalize_band (str, &override_class); + if (override_class) + class = override_class; + g_free (str); + + /* sid */ + str = g_match_info_fetch (match_info, 3); + sid = convert_sid (str); + g_free (str); + + success = TRUE; + } + + g_match_info_free (match_info); + g_regex_unref (r); + } + + if (success) { + gboolean class_ok = FALSE, band_ok = FALSE; + + /* Normalize the SID */ + if (sid < 0 || sid > 32767) + sid = 99999; + + if (class == 1 || class == 2) + class_ok = TRUE; + if (band != 'Z') + band_ok = TRUE; + + /* Return 'no service' if none of the elements of the +CSS response + * indicate that the modem has service. Note that this allows SID 0 + * when at least one of the other elements indicates service. + * Normally we'd treat SID 0 as 'no service' but some modems + * (Sierra 5725) sometimes return SID 0 even when registered. + */ + if (sid == 0 && !class_ok && !band_ok) + 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 { + 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); + } + } else { + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Could not parse Serving System results."); + } + + out: + mm_callback_info_schedule (info); +} + +static void +get_serving_system (MMModemCdma *modem, + MMModemCdmaServingSystemFn 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 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); +} + +#define CDMA_1X_STATE_TAG "cdma-1x-reg-state" +#define EVDO_STATE_TAG "evdo-reg-state" + +void +mm_generic_cdma_query_reg_state_set_callback_1x_state (MMCallbackInfo *info, + MMModemCdmaRegistrationState new_state) +{ + g_return_if_fail (info != NULL); + g_return_if_fail (info->modem != NULL); + g_return_if_fail (MM_IS_GENERIC_CDMA (info->modem)); + + mm_callback_info_set_data (info, CDMA_1X_STATE_TAG, GUINT_TO_POINTER (new_state), NULL); +} + +void +mm_generic_cdma_query_reg_state_set_callback_evdo_state (MMCallbackInfo *info, + MMModemCdmaRegistrationState new_state) +{ + g_return_if_fail (info != NULL); + g_return_if_fail (info->modem != NULL); + g_return_if_fail (MM_IS_GENERIC_CDMA (info->modem)); + + mm_callback_info_set_data (info, EVDO_STATE_TAG, GUINT_TO_POINTER (new_state), NULL); +} + +static void +registration_state_invoke (MMCallbackInfo *info) +{ + MMModemCdmaRegistrationStateFn callback = (MMModemCdmaRegistrationStateFn) info->callback; + + /* 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)), + info->error, + info->user_data); +} + +MMCallbackInfo * +mm_generic_cdma_query_reg_state_callback_info_new (MMGenericCdma *self, + MMModemCdmaRegistrationStateFn callback, + gpointer user_data) +{ + MMGenericCdmaPrivate *priv; + MMCallbackInfo *info; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), NULL); + g_return_val_if_fail (callback != NULL, NULL); + + info = mm_callback_info_new_full (MM_MODEM (self), + registration_state_invoke, + G_CALLBACK (callback), + 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); + return info; +} + +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); + + 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); + + 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, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + 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); + } + + mm_callback_info_schedule (info); +} + +static void +query_subclass_registration_state (MMGenericCdma *self, MMCallbackInfo *info) +{ + /* 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); + } else { + /* Or if the subclass doesn't implement more specific checking, + * assume we're registered. + */ + reg_state_query_done (MM_MODEM_CDMA (self), + MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED, + MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED, + NULL, + info); + } +} + +static void +reg_state_css_response (MMModemCdma *cdma, + guint32 class, + unsigned char band, + guint32 sid, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + /* We'll get an error if the SID isn't valid, so detect that and + * report unknown registration state. + */ + if (error) { + if ( (error->domain == MM_MOBILE_ERROR) + && (error->code == 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 { + /* Some other error parsing CSS results */ + info->error = g_error_copy (error); + } + mm_callback_info_schedule (info); + return; + } + + query_subclass_registration_state (MM_GENERIC_CDMA (info->modem), info); +} + +static void +get_analog_digital_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *reply; + long int int_cad; + + if (error) { + info->error = g_error_copy (error); + goto error; + } + + /* Strip any leading command tag and spaces */ + reply = strip_response (response->str, "+CAD:"); + + errno = 0; + int_cad = strtol (reply, NULL, 10); + if ((errno == EINVAL) || (errno == ERANGE)) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Failed to parse +CAD response"); + goto error; + } + + if (int_cad == 1) { /* 1 == CDMA service */ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); + + /* Now that we have some sort of service, check if the the device is + * registered on the network. + */ + + /* Some devices key the AT+CSS? response off the 1X state, but if the + * device has EVDO service but no 1X service, then reading AT+CSS? will + * error out too early. Let subclasses that know that their AT+CSS? + * response is wrong in this case handle more specific registration + * themselves; if they do, they'll set priv->reg_try_css to FALSE. + */ + if (priv->reg_try_css) { + get_serving_system (MM_MODEM_CDMA (info->modem), + reg_state_css_response, + info); + } else { + /* 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); + } + return; + } else { + /* No service */ + set_callback_1x_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + set_callback_evdo_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + } + +error: + 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; + } + + 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); +} + +/*****************************************************************************/ +/* MMModemSimple interface */ + +typedef enum { + SIMPLE_STATE_BEGIN = 0, + SIMPLE_STATE_ENABLE, + SIMPLE_STATE_REGISTER, + SIMPLE_STATE_CONNECT, + SIMPLE_STATE_DONE +} SimpleState; + +static const char * +simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error) +{ + GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties"); + GValue *value; + + value = (GValue *) g_hash_table_lookup (properties, name); + if (!value) + return NULL; + + if (G_VALUE_HOLDS_STRING (value)) + return g_value_get_string (value); + + 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)); + + return NULL; +} + +static gboolean +simple_reg_retry (gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + mm_modem_cdma_get_registration_state (MM_MODEM_CDMA (info->modem), + simple_reg_callback, + info); + return TRUE; +} + +static void +simple_reg_callback (MMModemCdma *modem, + MMModemCdmaRegistrationState cdma_1x_reg_state, + MMModemCdmaRegistrationState evdo_reg_state, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); + gboolean no_service_error = FALSE; + + if ( error + && (error->domain == MM_MOBILE_ERROR) + && (error->code == MM_MOBILE_ERROR_NO_NETWORK)) + no_service_error = TRUE; + + /* Fail immediately on anything but "no service" */ + if (error && !no_service_error) { + simple_state_machine (MM_MODEM (modem), error, info); + return; + } + + if ( no_service_error + || ( (cdma_1x_reg_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) + && (evdo_reg_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN))) { + /* Not registered yet, queue up a retry */ + priv->reg_tries++; + if (priv->reg_tries > 15) { + error = g_error_new_literal (MM_MOBILE_ERROR, + MM_MOBILE_ERROR_NO_NETWORK, + "No service"); + simple_state_machine (MM_MODEM (modem), error, info); + g_error_free (error); + return; + } + + /* otherwise, just try again in a bit */ + if (!priv->reg_retry_id) + priv->reg_retry_id = g_timeout_add_seconds (4, simple_reg_retry, info); + } else { + /* Yay, at least one of 1x or EVDO is registered, we can proceed to dial */ + simple_state_machine (MM_MODEM (modem), NULL, info); + } +} + +static void +reg_state_changed (MMModemCdma *self, + MMModemCdmaRegistrationState cdma_1x_new_state, + MMModemCdmaRegistrationState evdo_new_state, + gpointer user_data) +{ +/* Disabled for now... changing the registration state from the + * subclass' query_registration_state handler also emits the registration + * state changed signal, which will call this function, and execute + * simple_state_machine() to advance to the next state. Then however + * query_registration_state will call its callback, which ends up in + * simple_reg_callback(), which calls simple_state_machine() too in + * the same mainloop iteration. Not good. So until that's sorted out + * we'll just have to poll registration state (every 4 seconds so its + * not that bad. + */ +#if 0 + MMCallbackInfo *info = user_data; + + /* If we're registered, we can proceed */ + if ( (cdma_1x_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) + || (evdo_reg_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)) + simple_state_machine (MM_MODEM (modem), NULL, info); +#endif +} + +static SimpleState +set_simple_state (MMCallbackInfo *info, SimpleState state) +{ + mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (state), NULL); + return state; +} + +static void +simple_state_machine (MMModem *modem, GError *error, gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); + SimpleState state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "simple-connect-state")); + const char *str; + guint id; + + if (error) { + info->error = g_error_copy (error); + goto out; + } + + switch (state) { + case SIMPLE_STATE_BEGIN: + state = set_simple_state (info, SIMPLE_STATE_ENABLE); + mm_modem_enable (modem, simple_state_machine, info); + break; + case SIMPLE_STATE_ENABLE: + state = set_simple_state (info, SIMPLE_STATE_REGISTER); + mm_modem_cdma_get_registration_state (MM_MODEM_CDMA (modem), + simple_reg_callback, + info); + id = g_signal_connect (modem, + MM_MODEM_CDMA_REGISTRATION_STATE_CHANGED, + G_CALLBACK (reg_state_changed), + info); + priv->reg_state_changed_id = id; + break; + case SIMPLE_STATE_REGISTER: + registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0); + state = set_simple_state (info, SIMPLE_STATE_CONNECT); + mm_modem_set_state (modem, MM_MODEM_STATE_REGISTERED, MM_MODEM_STATE_REASON_NONE); + + str = simple_get_string_property (info, "number", &info->error); + mm_modem_connect (modem, str, simple_state_machine, info); + break; + case SIMPLE_STATE_CONNECT: + state = set_simple_state (info, SIMPLE_STATE_DONE); + break; + case SIMPLE_STATE_DONE: + break; + } + + out: + if (info->error || state == SIMPLE_STATE_DONE) { + registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0); + mm_callback_info_schedule (info); + } +} + +static void +simple_connect (MMModemSimple *simple, + GHashTable *properties, + MMModemFn callback, + gpointer user_data) +{ + MMGenericCdma *self = MM_GENERIC_CDMA (simple); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + MMCallbackInfo *info; + GError *error = NULL; + + if (priv->simple_connect_info) { + error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_OPERATION_IN_PROGRESS, + "Connection is already in progress"); + callback (MM_MODEM (simple), error, user_data); + g_error_free (error); + return; + } + + info = mm_callback_info_new (MM_MODEM (simple), callback, user_data); + priv->simple_connect_info = info; + mm_callback_info_set_data (info, "simple-connect-properties", + g_hash_table_ref (properties), + (GDestroyNotify) g_hash_table_unref); + + /* At least number must be present */ + if (!simple_get_string_property (info, "number", &error)) { + if (!error) + error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Missing number property"); + } + + simple_state_machine (MM_MODEM (simple), error, info); +} + +static void +simple_free_gvalue (gpointer data) +{ + g_value_unset ((GValue *) data); + g_slice_free (GValue, data); +} + +static GValue * +simple_uint_value (guint32 i) +{ + GValue *val; + + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_UINT); + g_value_set_uint (val, i); + + return val; +} + +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)); +} + +static void +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"), + info->error, info->user_data); +} + +static void +simple_get_status (MMModemSimple *simple, + MMModemSimpleGetStatusFn callback, + gpointer user_data) +{ + GHashTable *properties; + MMCallbackInfo *info; + + 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); + mm_modem_cdma_get_signal_quality (MM_MODEM_CDMA (simple), simple_status_got_signal_quality, properties); +} + +/*****************************************************************************/ + +static void +modem_valid_changed (MMGenericCdma *self, GParamSpec *pspec, gpointer user_data) +{ + /* Be paranoid about tearing down any pending registration */ + if (!mm_modem_get_valid (MM_MODEM (self))) + registration_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL); +} + +/*****************************************************************************/ + +static void +modem_init (MMModem *modem_class) +{ + modem_class->owns_port = owns_port; + modem_class->grab_port = grab_port; + modem_class->release_port = release_port; + modem_class->enable = enable; + modem_class->disable = disable; + modem_class->connect = connect; + modem_class->disconnect = disconnect; + modem_class->get_info = get_card_info; +} + +static void +modem_cdma_init (MMModemCdma *cdma_class) +{ + cdma_class->get_signal_quality = get_signal_quality; + cdma_class->get_esn = get_esn; + cdma_class->get_serving_system = get_serving_system; + cdma_class->get_registration_state = get_registration_state; +} + +static void +modem_simple_init (MMModemSimple *class) +{ + class->connect = simple_connect; + 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) +{ +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (object); + + switch (prop_id) { + case MM_MODEM_PROP_TYPE: + break; + case PROP_EVDO_REV0: + priv->evdo_rev0 = g_value_get_boolean (value); + break; + case PROP_EVDO_REVA: + priv->evdo_revA = g_value_get_boolean (value); + break; + case PROP_REG_TRY_CSS: + priv->reg_try_css = g_value_get_boolean (value); + 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) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (object); + + switch (prop_id) { + case MM_MODEM_PROP_DATA_DEVICE: + if (priv->data) + g_value_set_string (value, mm_port_get_device (priv->data)); + else + g_value_set_string (value, NULL); + break; + case MM_MODEM_PROP_TYPE: + g_value_set_uint (value, MM_MODEM_TYPE_CDMA); + break; + case PROP_EVDO_REV0: + g_value_set_boolean (value, priv->evdo_rev0); + break; + case PROP_EVDO_REVA: + g_value_set_boolean (value, priv->evdo_revA); + break; + case PROP_REG_TRY_CSS: + g_value_set_boolean (value, priv->reg_try_css); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + registration_cleanup (MM_GENERIC_CDMA (object), MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL); + + G_OBJECT_CLASS (mm_generic_cdma_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + G_OBJECT_CLASS (mm_generic_cdma_parent_class)->finalize (object); +} + +static void +mm_generic_cdma_class_init (MMGenericCdmaClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + mm_generic_cdma_parent_class = g_type_class_peek_parent (klass); + g_type_class_add_private (object_class, sizeof (MMGenericCdmaPrivate)); + + /* Virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + object_class->constructor = constructor; + + /* Properties */ + g_object_class_override_property (object_class, + MM_MODEM_PROP_DATA_DEVICE, + MM_MODEM_DATA_DEVICE); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_TYPE, + MM_MODEM_TYPE); + + g_object_class_install_property (object_class, PROP_EVDO_REV0, + g_param_spec_boolean (MM_GENERIC_CDMA_EVDO_REV0, + "EVDO rev0", + "Supports EVDO rev0", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_EVDO_REVA, + g_param_spec_boolean (MM_GENERIC_CDMA_EVDO_REVA, + "EVDO revA", + "Supports EVDO revA", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_REG_TRY_CSS, + g_param_spec_boolean (MM_GENERIC_CDMA_REGISTRATION_TRY_CSS, + "RegistrationTryCss", + "Use Serving System response when checking modem" + " registration state.", + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + diff --git a/src/mm-generic-cdma.h b/src/mm-generic-cdma.h new file mode 100644 index 0000000..5b4a0b6 --- /dev/null +++ b/src/mm-generic-cdma.h @@ -0,0 +1,111 @@ +/* -*- 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 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef MM_GENERIC_CDMA_H +#define MM_GENERIC_CDMA_H + +#include "mm-modem.h" +#include "mm-modem-base.h" +#include "mm-modem-cdma.h" +#include "mm-serial-port.h" +#include "mm-callback-info.h" + +#define MM_TYPE_GENERIC_CDMA (mm_generic_cdma_get_type ()) +#define MM_GENERIC_CDMA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_GENERIC_CDMA, MMGenericCdma)) +#define MM_GENERIC_CDMA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_GENERIC_CDMA, MMGenericCdmaClass)) +#define MM_IS_GENERIC_CDMA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_GENERIC_CDMA)) +#define MM_IS_GENERIC_CDMA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_GENERIC_CDMA)) +#define MM_GENERIC_CDMA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_GENERIC_CDMA, MMGenericCdmaClass)) + +#define MM_GENERIC_CDMA_EVDO_REV0 "evdo-rev0" +#define MM_GENERIC_CDMA_EVDO_REVA "evdo-revA" + +#define MM_GENERIC_CDMA_REGISTRATION_TRY_CSS "registration-try-css" + +typedef struct { + MMModemBase parent; +} MMGenericCdma; + +typedef struct { + MMModemBaseClass parent; + + void (*query_registration_state) (MMGenericCdma *self, + MMModemCdmaRegistrationStateFn callback, + gpointer user_data); + + /* Called after generic enable operations, but before the modem has entered + * the ENABLED state. + */ + void (*post_enable) (MMGenericCdma *self, + MMModemFn callback, + gpointer user_data); + + /* Called after generic disable operations, but before the modem has entered + * the DISABLED state. + */ + void (*post_disable) (MMGenericCdma *self, + MMModemFn callback, + gpointer user_data); +} MMGenericCdmaClass; + +GType mm_generic_cdma_get_type (void); + +MMModem *mm_generic_cdma_new (const char *device, + const char *driver, + const char *plugin, + gboolean evdo_rev0, + gboolean evdo_revA); + +/* 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, + MMPortType suggested_type, + gpointer user_data, + GError **error); + +MMSerialPort *mm_generic_cdma_get_port (MMGenericCdma *modem, MMPortType ptype); + +void mm_generic_cdma_update_cdma1x_quality (MMGenericCdma *self, guint32 quality); +void mm_generic_cdma_update_evdo_quality (MMGenericCdma *self, guint32 quality); + +/* For unsolicited 1x registration state changes */ +void mm_generic_cdma_set_1x_registration_state (MMGenericCdma *self, + MMModemCdmaRegistrationState new_state); + +/* For unsolicited EVDO registration state changes */ +void mm_generic_cdma_set_evdo_registration_state (MMGenericCdma *self, + MMModemCdmaRegistrationState new_state); + +MMModemCdmaRegistrationState mm_generic_cdma_1x_get_registration_state_sync (MMGenericCdma *self); + +MMModemCdmaRegistrationState mm_generic_cdma_evdo_get_registration_state_sync (MMGenericCdma *self); + +/* query_registration_state class function helpers */ +MMCallbackInfo *mm_generic_cdma_query_reg_state_callback_info_new (MMGenericCdma *self, + MMModemCdmaRegistrationStateFn callback, + gpointer user_data); + +void mm_generic_cdma_query_reg_state_set_callback_1x_state (MMCallbackInfo *info, + MMModemCdmaRegistrationState new_state); + +void mm_generic_cdma_query_reg_state_set_callback_evdo_state (MMCallbackInfo *info, + MMModemCdmaRegistrationState new_state); + +#endif /* MM_GENERIC_CDMA_H */ diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c new file mode 100644 index 0000000..4954ca1 --- /dev/null +++ b/src/mm-generic-gsm.c @@ -0,0 +1,2220 @@ +/* -*- 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. + * Copyright (C) 2009 Ericsson + */ + +#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" +#include "mm-modem-gsm-sms.h" +#include "mm-modem-simple.h" +#include "mm-errors.h" +#include "mm-callback-info.h" +#include "mm-serial-parsers.h" +#include "mm-modem-helpers.h" + +static void modem_init (MMModem *modem_class); +static void modem_gsm_card_init (MMModemGsmCard *gsm_card_class); +static void modem_gsm_network_init (MMModemGsmNetwork *gsm_network_class); +static void modem_gsm_sms_init (MMModemGsmSms *gsm_sms_class); +static void modem_simple_init (MMModemSimple *class); + +G_DEFINE_TYPE_EXTENDED (MMGenericGsm, mm_generic_gsm, MM_TYPE_MODEM_BASE, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_CARD, modem_gsm_card_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_NETWORK, modem_gsm_network_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_SMS, modem_gsm_sms_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_SIMPLE, modem_simple_init)) + +#define MM_GENERIC_GSM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_GENERIC_GSM, MMGenericGsmPrivate)) + +typedef struct { + char *driver; + char *plugin; + char *device; + + gboolean valid; + + char *oper_code; + char *oper_name; + guint32 ip_method; + gboolean unsolicited_registration; + + MMModemGsmNetworkRegStatus reg_status; + guint pending_reg_id; + MMCallbackInfo *pending_reg_info; + + guint32 signal_quality; + guint32 cid; + + MMSerialPort *primary; + MMSerialPort *secondary; + MMPort *data; +} MMGenericGsmPrivate; + +static void get_registration_status (MMSerialPort *port, MMCallbackInfo *info); +static void read_operator_code_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data); + +static void read_operator_name_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data); + +static void reg_state_changed (MMSerialPort *port, + GMatchInfo *match_info, + gpointer user_data); + +MMModem * +mm_generic_gsm_new (const char *device, + const char *driver, + const char *plugin) +{ + g_return_val_if_fail (device != NULL, NULL); + g_return_val_if_fail (driver != NULL, NULL); + g_return_val_if_fail (plugin != NULL, NULL); + + return MM_MODEM (g_object_new (MM_TYPE_GENERIC_GSM, + MM_MODEM_MASTER_DEVICE, device, + MM_MODEM_DRIVER, driver, + MM_MODEM_PLUGIN, plugin, + 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 +mm_generic_gsm_get_cid (MMGenericGsm *modem) +{ + g_return_val_if_fail (MM_IS_GENERIC_GSM (modem), 0); + + 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; + + mm_modem_gsm_network_registration_info (MM_MODEM_GSM_NETWORK (modem), priv->reg_status, + priv->oper_code, priv->oper_name); + } + + mm_generic_gsm_update_enabled_state (modem, TRUE, MM_MODEM_STATE_REASON_NONE); + } +} + +typedef struct { + const char *result; + guint code; +} CPinResult; + +static void +pin_check_done (MMSerialPort *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; + + if (g_str_has_prefix (str, "READY")) + parsed = TRUE; + else { + CPinResult *iter = &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); + parsed = TRUE; + break; + } + iter++; + } + } + } + + 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); + } + + mm_callback_info_schedule (info); +} + +void +mm_generic_gsm_check_pin (MMGenericGsm *modem, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv; + MMCallbackInfo *info; + + g_return_if_fail (MM_IS_GENERIC_GSM (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); +} + +/*****************************************************************************/ + +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) { + 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); + break; + case MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING: + mm_modem_set_state (MM_MODEM (self), MM_MODEM_STATE_SEARCHING, reason); + break; + case MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE: + case MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED: + case MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN: + default: + mm_modem_set_state (MM_MODEM (self), MM_MODEM_STATE_ENABLED, reason); + break; + } +} + +static void +check_valid (MMGenericGsm *self) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + gboolean new_valid = FALSE; + + if (priv->primary && priv->data) + new_valid = TRUE; + + mm_modem_base_set_valid (MM_MODEM_BASE (self), new_valid); +} + +static gboolean +owns_port (MMModem *modem, const char *subsys, const char *name) +{ + return !!mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name); +} + +MMPort * +mm_generic_gsm_grab_port (MMGenericGsm *self, + const char *subsys, + const char *name, + MMPortType ptype, + GError **error) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMPort *port = NULL; + GRegex *regex; + + 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); + + 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); + + if (ptype == MM_PORT_TYPE_PRIMARY) { + priv->primary = MM_SERIAL_PORT (port); + if (!priv->data) { + priv->data = port; + g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); + } + check_valid (self); + } else if (ptype == MM_PORT_TYPE_SECONDARY) + priv->secondary = MM_SERIAL_PORT (port); + } else { + /* Net device (if any) is the preferred data port */ + if (!priv->data || MM_IS_SERIAL_PORT (priv->data)) { + priv->data = port; + g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); + check_valid (self); + } + } + + return port; +} + +static gboolean +grab_port (MMModem *modem, + const char *subsys, + const char *name, + MMPortType suggested_type, + gpointer user_data, + GError **error) +{ + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMPortType ptype = MM_PORT_TYPE_IGNORED; + + if (priv->primary) + g_return_val_if_fail (suggested_type != MM_PORT_TYPE_PRIMARY, FALSE); + + if (!strcmp (subsys, "tty")) { + if (suggested_type != MM_PORT_TYPE_UNKNOWN) + ptype = suggested_type; + else { + if (!priv->primary) + ptype = MM_PORT_TYPE_PRIMARY; + else if (!priv->secondary) + ptype = MM_PORT_TYPE_SECONDARY; + } + } + + return !!mm_generic_gsm_grab_port (self, subsys, name, ptype, error); +} + +static void +release_port (MMModem *modem, const char *subsys, const char *name) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMPort *port; + + if (strcmp (subsys, "tty") && strcmp (subsys, "net")) + return; + + port = mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name); + if (!port) + return; + + if (port == MM_PORT (priv->primary)) { + mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); + priv->primary = NULL; + } + + if (port == priv->data) { + priv->data = NULL; + g_object_notify (G_OBJECT (modem), MM_MODEM_DATA_DEVICE); + } + + if (port == MM_PORT (priv->secondary)) { + mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); + priv->secondary = NULL; + } + + check_valid (MM_GENERIC_GSM (modem)); +} + +void +mm_generic_gsm_enable_complete (MMGenericGsm *modem, + GError *error, + MMCallbackInfo *info) +{ + g_return_if_fail (modem != NULL); + g_return_if_fail (MM_IS_GENERIC_GSM (modem)); + g_return_if_fail (info != NULL); + + if (error) { + mm_modem_set_state (MM_MODEM (modem), + MM_MODEM_STATE_DISABLED, + MM_MODEM_STATE_REASON_NONE); + + 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); + } + + mm_callback_info_schedule (info); +} + +static void +enable_done (MMSerialPort *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. + */ + + mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), NULL, info); +} + +static void +init_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + char *cmd = NULL; + + if (error) { + mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); + return; + } + + /* 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); + + 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); + 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); + else + enable_done (port, NULL, NULL, user_data); + g_free (cmd); +} + +static void +enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data) +{ + MMCallbackInfo *info = user_data; + char *cmd = NULL; + + if (error) { + mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); + return; + } + + 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); + g_free (cmd); +} + +static void +real_do_enable (MMGenericGsm *self, 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); + mm_serial_port_flash (priv->primary, 100, enable_flash_done, info); +} + +static void +enable (MMModem *modem, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + GError *error = NULL; + + /* First, reset the previously used CID */ + mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0); + + if (!mm_serial_port_open (priv->primary, &error)) { + MMCallbackInfo *info; + + g_assert (error); + info = mm_callback_info_new (modem, callback, user_data); + info->error = error; + mm_callback_info_schedule (info); + return; + } + + mm_modem_set_state (modem, MM_MODEM_STATE_ENABLING, MM_MODEM_STATE_REASON_NONE); + + g_assert (MM_GENERIC_GSM_GET_CLASS (modem)->do_enable); + MM_GENERIC_GSM_GET_CLASS (modem)->do_enable (MM_GENERIC_GSM (modem), callback, user_data); +} + +static void +disable_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + + info->error = mm_modem_check_removed (info->modem, error); + if (!info->error) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + mm_serial_port_close (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; + } + mm_callback_info_schedule (info); +} + +static void +disable_flash_done (MMSerialPort *port, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMModemState prev_state; + char *cmd = NULL; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + if (info->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), + prev_state, + MM_MODEM_STATE_REASON_NONE); + } + + mm_callback_info_schedule (info); + return; + } + + 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); + else + disable_done (port, NULL, NULL, user_data); + g_free (cmd); +} + +static void +disable (MMModem *modem, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + MMModemState state; + + /* First, reset the previously used CID and clean up registration */ + mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0); + mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem)); + + info = mm_callback_info_new (modem, callback, user_data); + + /* Cache the previous state so we can reset it if the operation fails */ + state = mm_modem_get_state (modem); + mm_callback_info_set_data (info, + MM_GENERIC_GSM_PREV_STATE_TAG, + GUINT_TO_POINTER (state), + NULL); + + 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); + else + disable_flash_done (priv->primary, NULL, info); +} + +static void +get_string_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) + info->error = g_error_copy (error); + else + mm_callback_info_set_result (info, g_strdup (response->str), g_free); + + mm_callback_info_schedule (info); +} + +static void +get_imei (MMModemGsmCard *modem, + MMModemStringFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (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); +} + +static void +get_imsi (MMModemGsmCard *modem, + MMModemStringFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (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); +} + +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); +} + +#define GMI_RESP_TAG "+CGMI:" +#define GMM_RESP_TAG "+CGMM:" +#define GMR_RESP_TAG "+CGMR:" + +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; +} + +static void +get_version_done (MMSerialPort *port, + GString *response, + GError *error, + 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); + + 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 *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); +} + +static void +get_manufacturer_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *resp = strip_tag (response->str, GMI_RESP_TAG); + + if (!error) + mm_callback_info_set_data (info, "card-info-manufacturer", g_strdup (resp), g_free); + else + info->error = g_error_copy (error); +} + +static void +get_card_info (MMModem *modem, + MMModemInfoFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + + info = mm_callback_info_new_full (MM_MODEM (modem), + card_info_invoke, + G_CALLBACK (callback), + user_data); + + 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); +} + +static void +send_puk_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) + info->error = g_error_copy (error); + mm_callback_info_schedule (info); +} + +static void +send_puk (MMModemGsmCard *modem, + const char *puk, + const char *pin, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + char *command; + + info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + command = g_strdup_printf ("+CPIN=\"%s\",\"%s\"", puk, pin); + mm_serial_port_queue_command (priv->primary, command, 3, send_puk_done, info); + g_free (command); +} + +static void +send_pin_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) + info->error = g_error_copy (error); + mm_callback_info_schedule (info); +} + +static void +send_pin (MMModemGsmCard *modem, + const char *pin, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + char *command; + + info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + command = g_strdup_printf ("+CPIN=\"%s\"", pin); + mm_serial_port_queue_command (priv->primary, command, 3, send_pin_done, info); + g_free (command); +} + +static void +enable_pin_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) + info->error = g_error_copy (error); + mm_callback_info_schedule (info); +} + +static void +enable_pin (MMModemGsmCard *modem, + const char *pin, + gboolean enabled, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + char *command; + + 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); + g_free (command); +} + +static void +change_pin_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) + info->error = g_error_copy (error); + mm_callback_info_schedule (info); +} + +static void +change_pin (MMModemGsmCard *modem, + const char *old_pin, + const char *new_pin, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + char *command; + + 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); + g_free (command); +} + +static char * +parse_operator (const char *reply) +{ + char *operator = NULL; + + if (reply && !strncmp (reply, "+COPS: ", 7)) { + /* Got valid reply */ + GRegex *r; + GMatchInfo *match_info; + + reply += 7; + r = g_regex_new ("(\\d),(\\d),\"(.+)\"", G_REGEX_UNGREEDY, 0, NULL); + if (!r) + return NULL; + + g_regex_match (r, reply, 0, &match_info); + if (g_match_info_matches (match_info)) + operator = g_match_info_fetch (match_info, 3); + + g_match_info_free (match_info); + g_regex_unref (r); + } + + return operator; +} + +static void +read_operator_code_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (user_data); + char *oper; + + if (error) + return; + + oper = parse_operator (response->str); + if (!oper) + return; + + g_free (priv->oper_code); + priv->oper_code = oper; +} + +static void +read_operator_name_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (user_data); + 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); +} + +/* Registration */ +#define REG_STATUS_AGAIN_TAG "reg-status-again" + +void +mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + + if (priv->pending_reg_id) { + /* Clear the registration timeout handler */ + g_source_remove (priv->pending_reg_id); + priv->pending_reg_id = 0; + } + + if (priv->pending_reg_info) { + /* Clear any ongoing registration status callback */ + mm_callback_info_set_data (priv->pending_reg_info, REG_STATUS_AGAIN_TAG, NULL, NULL); + + /* And schedule the callback */ + mm_callback_info_schedule (priv->pending_reg_info); + priv->pending_reg_info = NULL; + } +} + +static gboolean +reg_status_updated (MMGenericGsm *self, int new_value, GError **error) +{ + MMModemGsmNetworkRegStatus status; + gboolean status_done = FALSE; + + switch (new_value) { + case 0: + status = MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE; + break; + case 1: + status = MM_MODEM_GSM_NETWORK_REG_STATUS_HOME; + break; + case 2: + status = MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING; + break; + case 3: + status = MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED; + break; + case 4: + status = MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN; + break; + case 5: + status = MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING; + break; + default: + status = MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN; + break; + } + + mm_generic_gsm_set_reg_status (self, status); + + /* Registration has either completed successfully or completely failed */ + switch (status) { + case MM_MODEM_GSM_NETWORK_REG_STATUS_HOME: + case MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING: + /* Successfully registered - stop registration */ + status_done = TRUE; + break; + case MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED: + /* registration failed - stop registration */ + if (error) + *error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED); + status_done = TRUE; + break; + case MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING: + if (error) + *error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_TIMEOUT); + break; + case MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE: + if (error) + *error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NO_NETWORK); + break; + default: + if (error) + *error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN); + break; + } + return status_done; +} + +static void +reg_state_changed (MMSerialPort *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; + + str = g_match_info_fetch (match_info, 1); + done = reg_status_updated (self, atoi (str), NULL); + g_free (str); + + if (done) { + /* 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. + */ + if (priv->pending_reg_id) { + g_source_remove (priv->pending_reg_id); + priv->pending_reg_id = 0; + } + } +} + +static gboolean +reg_status_again (gpointer data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) data; + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + g_warn_if_fail (info == priv->pending_reg_info); + + if (priv->pending_reg_info) + get_registration_status (priv->primary, info); + + return FALSE; +} + +static void +reg_status_again_remove (gpointer data) +{ + guint id = GPOINTER_TO_UINT (data); + + /* Technically the GSource ID can be 0, but in practice it won't be */ + if (id > 0) + g_source_remove (id); +} + +static void +get_reg_status_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + 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; + + g_warn_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); + } else { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Could not parse the registration status response"); + } + 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); + + /* Not registered yet; poll registration status again */ + 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); + return; + } + +reg_done: + /* This will schedule the callback for us */ + mm_generic_gsm_pending_registration_stop (self); +} + +static void +get_registration_status (MMSerialPort *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); +} + +static void +register_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + mm_callback_info_unref (info); + + /* If the registration timed out (and thus pending_reg_info will be NULL) + * and the modem eventually got around to sending the response for the + * registration request then just ignore the response since the callback is + * already called. + */ + + if (priv->pending_reg_info) { + g_warn_if_fail (info == priv->pending_reg_info); + + /* Ignore errors here, get the actual registration status */ + get_registration_status (port, info); + } +} + +static gboolean +registration_timed_out (gpointer data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) data; + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + g_warn_if_fail (info == priv->pending_reg_info); + + priv->reg_status = MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE; + + info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_TIMEOUT); + mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (info->modem)); + + return FALSE; +} + +static void +do_register (MMModemGsmNetwork *modem, + const char *network_id, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + char *command; + + /* Clear any previous registration */ + mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem)); + + 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) + command = g_strdup_printf ("+COPS=1,2,\"%s\"", network_id); + else + command = g_strdup ("+COPS=0,,"); + + /* Ref the callback info to ensure it stays alive for register_done() even + * if the timeout triggers and ends registration (which calls the callback + * and unrefs the callback info). Some devices (hso) will delay the + * registration response until the registration is done (and thus + * unsolicited registration responses will arrive before the +COPS is + * complete). Most other devices will return the +COPS response immediately + * and the unsolicited response (if any) at a later time. + * + * To handle both these cases, unsolicited registration responses will just + * remove the pending registration timeout but we let the +COPS command + * complete. For those devices that delay the +COPS response (hso) the + * callback will be called from register_done(). For those devices that + * return the +COPS response immediately, we'll poll the registration state + * and call the callback from get_reg_status_done() in response to the + * polled response. The registration timeout will only be triggered when + * 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); +} + +static void +gsm_network_reg_info_invoke (MMCallbackInfo *info) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + MMModemGsmNetworkRegInfoFn callback = (MMModemGsmNetworkRegInfoFn) info->callback; + + callback (MM_MODEM_GSM_NETWORK (info->modem), + priv->reg_status, + priv->oper_code, + priv->oper_name, + info->error, + info->user_data); +} + +static void +get_registration_info (MMModemGsmNetwork *self, + MMModemGsmNetworkRegInfoFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new_full (MM_MODEM (self), + gsm_network_reg_info_invoke, + G_CALLBACK (callback), + user_data); + + mm_callback_info_schedule (info); +} + +void +mm_generic_gsm_connect_complete (MMGenericGsm *modem, + GError *error, + MMCallbackInfo *info) +{ + MMGenericGsmPrivate *priv; + + g_return_if_fail (modem != NULL); + g_return_if_fail (MM_IS_GENERIC_GSM (modem)); + g_return_if_fail (info != NULL); + + priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + + if (error) { + mm_generic_gsm_update_enabled_state (modem, FALSE, MM_MODEM_STATE_REASON_NONE); + info->error = g_error_copy (error); + } else { + /* Modem is connected; update the state */ + mm_port_set_connected (priv->data, TRUE); + mm_modem_set_state (MM_MODEM (modem), + MM_MODEM_STATE_CONNECTED, + MM_MODEM_STATE_REASON_NONE); + } + + mm_callback_info_schedule (info); +} + +static void +connect_report_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GError *real_error; + + /* If the CEER command was successful, copy that error reason into the + * callback's error. If not, use the original error. + */ + + /* Have to do this little dance since mm_generic_gsm_connect_complete() + * copies the provided error into the callback info. + */ + real_error = info->error; + info->error = NULL; + + if ( !error + && g_str_has_prefix (response->str, "+CEER: ") + && (strlen (response->str) > 7)) { + /* copy the connect failure reason into the error */ + g_free (real_error->message); + real_error->message = g_strdup (response->str + 7); /* skip the "+CEER: " */ + } + + mm_generic_gsm_connect_complete (MM_GENERIC_GSM (info->modem), real_error, info); + g_error_free (real_error); +} + +static void +connect_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + if (error) { + 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); + } else + mm_generic_gsm_connect_complete (MM_GENERIC_GSM (info->modem), NULL, info); +} + +static void +connect (MMModem *modem, + const char *number, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + char *command; + guint32 cid = mm_generic_gsm_get_cid (MM_GENERIC_GSM (modem)); + + info = mm_callback_info_new (modem, callback, user_data); + + mm_modem_set_state (modem, MM_MODEM_STATE_CONNECTING, MM_MODEM_STATE_REASON_NONE); + + if (cid > 0) { + GString *str; + + str = g_string_new ("D"); + if (g_str_has_suffix (number, "#")) + str = g_string_append_len (str, number, strlen (number) - 1); + else + str = g_string_append (str, number); + + g_string_append_printf (str, "***%d#", cid); + command = g_string_free (str, FALSE); + } else + command = g_strconcat ("DT", number, NULL); + + mm_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) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMModemState prev_state; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + if (info->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), + prev_state, + MM_MODEM_STATE_REASON_NONE); + } + } else { + MMGenericGsm *self = MM_GENERIC_GSM (info->modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + mm_port_set_connected (priv->data, FALSE); + mm_generic_gsm_update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE); + } + + mm_callback_info_schedule (info); +} + +static void +disconnect (MMModem *modem, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + MMModemState state; + + /* First, reset the previously used CID */ + mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0); + + info = mm_callback_info_new (modem, callback, user_data); + + /* Cache the previous state so we can reset it if the operation fails */ + state = mm_modem_get_state (modem); + mm_callback_info_set_data (info, + MM_GENERIC_GSM_PREV_STATE_TAG, + GUINT_TO_POINTER (state), + 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 +gsm_network_scan_invoke (MMCallbackInfo *info) +{ + MMModemGsmNetworkScanFn callback = (MMModemGsmNetworkScanFn) info->callback; + + callback (MM_MODEM_GSM_NETWORK (info->modem), + (GPtrArray *) mm_callback_info_get_data (info, "scan-results"), + info->error, + info->user_data); +} + +static void +scan_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GPtrArray *results; + + if (error) + info->error = g_error_copy (error); + else { + results = mm_gsm_parse_scan_response (response->str, &info->error); + if (results) + mm_callback_info_set_data (info, "scan-results", results, mm_gsm_destroy_scan_data); + } + + mm_callback_info_schedule (info); +} + +static void +scan (MMModemGsmNetwork *modem, + MMModemGsmNetworkScanFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + + info = mm_callback_info_new_full (MM_MODEM (modem), + gsm_network_scan_invoke, + G_CALLBACK (callback), + user_data); + + mm_serial_port_queue_command (priv->primary, "+COPS=?", 120, scan_done, info); +} + +/* SetApn */ + +static void +set_apn_done (MMSerialPort *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"))); + + mm_callback_info_schedule (info); +} + +static void +cid_range_read (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + guint32 cid = 0; + + if (error) + info->error = g_error_copy (error); + else if (g_str_has_prefix (response->str, "+CGDCONT: ")) { + GRegex *r; + GMatchInfo *match_info; + + r = g_regex_new ("\\+CGDCONT:\\s*\\((\\d+)-(\\d+)\\),\\(?\"(\\S+)\"", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, + 0, &info->error); + if (r) { + g_regex_match_full (r, response->str, response->len, 0, 0, &match_info, &info->error); + while (cid == 0 && g_match_info_matches (match_info)) { + char *tmp; + + tmp = g_match_info_fetch (match_info, 3); + if (!strcmp (tmp, "IP")) { + int max_cid; + int highest_cid = GPOINTER_TO_INT (mm_callback_info_get_data (info, "highest-cid")); + + g_free (tmp); + + tmp = g_match_info_fetch (match_info, 2); + max_cid = atoi (tmp); + + if (highest_cid < max_cid) + cid = highest_cid + 1; + else + cid = highest_cid; + } + + g_free (tmp); + g_match_info_next (match_info, NULL); + } + + if (cid == 0) + /* Choose something */ + cid = 1; + } + } else + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Could not parse the response"); + + if (info->error) + mm_callback_info_schedule (info); + else { + 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); + + command = g_strdup_printf ("+CGDCONT=%d,\"IP\",\"%s\"", cid, apn); + mm_serial_port_queue_command (port, command, 3, set_apn_done, info); + g_free (command); + } +} + +static void +existing_apns_read (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + 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: ")) { + GRegex *r; + GMatchInfo *match_info; + + r = g_regex_new ("\\+CGDCONT:\\s*(\\d+)\\s*,\"(\\S+)\",\"(\\S+)\",\"(\\S*)\"", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, + 0, &info->error); + if (r) { + const char *new_apn = (const char *) mm_callback_info_get_data (info, "apn"); + + g_regex_match_full (r, response->str, response->len, 0, 0, &match_info, &info->error); + while (!found && g_match_info_matches (match_info)) { + char *cid; + char *pdp_type; + char *apn; + int num_cid; + + cid = g_match_info_fetch (match_info, 1); + num_cid = atoi (cid); + pdp_type = g_match_info_fetch (match_info, 2); + 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); + found = TRUE; + } + + if (!found && !strcmp (pdp_type, "IP")) { + int highest_cid; + + highest_cid = GPOINTER_TO_INT (mm_callback_info_get_data (info, "highest-cid")); + if (num_cid > highest_cid) + mm_callback_info_set_data (info, "highest-cid", GINT_TO_POINTER (num_cid), NULL); + } + + g_free (cid); + g_free (pdp_type); + g_free (apn); + g_match_info_next (match_info, NULL); + } + + g_match_info_free (match_info); + g_regex_unref (r); + } + } else if (strlen (response->str) == 0) { + /* No APNs configured, just don't set error */ + } else + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Could not parse the response"); + + if (found || info->error) + mm_callback_info_schedule (info); + 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); +} + +static void +set_apn (MMModemGsmNetwork *modem, + const char *apn, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + + info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + 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); +} + +/* GetSignalQuality */ + +static void +get_signal_quality_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMGenericGsmPrivate *priv; + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + char *reply = response->str; + + if (error) + info->error = g_error_copy (error); + else if (!strncmp (reply, "+CSQ: ", 6)) { + /* Got valid reply */ + int quality; + int ber; + + reply += 6; + + if (sscanf (reply, "%d, %d", &quality, &ber)) { + /* 99 means unknown */ + if (quality == 99) { + info->error = g_error_new_literal (MM_MOBILE_ERROR, + MM_MOBILE_ERROR_NO_NETWORK, + "No service"); + } else { + /* Normalize the quality */ + quality = CLAMP (quality, 0, 31) * 100 / 31; + + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + priv->signal_quality = 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"); + } + + mm_callback_info_schedule (info); +} + +static void +get_signal_quality (MMModemGsmNetwork *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + gboolean connected; + + 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); + 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); +} + +/*****************************************************************************/ +/* MMModemGsmSms interface */ + +static void +sms_send_done (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) + info->error = g_error_copy (error); + + mm_callback_info_schedule (info); +} + +static void +sms_send (MMModemGsmSms *modem, + const char *number, + const char *text, + const char *smsc, + guint validity, + guint class, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv; + MMCallbackInfo *info; + char *command; + gboolean connected; + MMSerialPort *port = NULL; + + 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; + + 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); + + command = g_strdup_printf ("+CMGS=\"%s\"\r%s\x1a", number, text); + mm_serial_port_queue_command (port, command, 10, sms_send_done, info); + g_free (command); +} + +MMSerialPort * +mm_generic_gsm_get_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); + + if (ptype == MM_PORT_TYPE_PRIMARY) + return MM_GENERIC_GSM_GET_PRIVATE (modem)->primary; + else if (ptype == MM_PORT_TYPE_SECONDARY) + return MM_GENERIC_GSM_GET_PRIVATE (modem)->secondary; + + return NULL; +} + +/*****************************************************************************/ +/* MMModemSimple interface */ + +typedef enum { + SIMPLE_STATE_BEGIN = 0, + SIMPLE_STATE_ENABLE, + SIMPLE_STATE_CHECK_PIN, + 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) +{ + GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties"); + GValue *value; + + value = (GValue *) g_hash_table_lookup (properties, name); + if (!value) + return NULL; + + if (G_VALUE_HOLDS_STRING (value)) + return g_value_get_string (value); + + 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)); + + return NULL; +} + +static void +simple_state_machine (MMModem *modem, GError *error, gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *str; + SimpleState state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "simple-connect-state")); + gboolean need_pin = 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; + } + } + + 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); + } + break; + case SIMPLE_STATE_REGISTER: + 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); + 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; + case SIMPLE_STATE_CONNECT: + state = SIMPLE_STATE_DONE; + break; + case SIMPLE_STATE_DONE: + break; + } + + out: + if (info->error || state == SIMPLE_STATE_DONE) + mm_callback_info_schedule (info); + else + mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (state), NULL); +} + +static void +simple_connect (MMModemSimple *simple, + GHashTable *properties, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + 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), + (GDestroyNotify) g_hash_table_unref); + + simple_state_machine (MM_MODEM (simple), NULL, info); +} + + + +static void +simple_free_gvalue (gpointer data) +{ + g_value_unset ((GValue *) data); + g_slice_free (GValue, data); +} + +static GValue * +simple_uint_value (guint32 i) +{ + GValue *val; + + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_UINT); + g_value_set_uint (val, i); + + return val; +} + +static GValue * +simple_string_value (const char *str) +{ + GValue *val; + + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_STRING); + g_value_set_string (val, str); + + return val; +} + +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)); +} + +static void +simple_status_got_band (MMModem *modem, + guint32 result, + 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)); +} + +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)); +} + +static void +simple_status_got_reg_info (MMModemGsmNetwork *modem, + MMModemGsmNetworkRegStatus status, + const char *oper_code, + const char *oper_name, + GError *error, + gpointer user_data) +{ + 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"); + + 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); +} + +static void +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"), + info->error, info->user_data); +} + +static void +simple_get_status (MMModemSimple *simple, + MMModemSimpleGetStatusFn callback, + gpointer user_data) +{ + MMModemGsmNetwork *gsm; + GHashTable *properties; + MMCallbackInfo *info; + + 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); + + 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); +} + +/*****************************************************************************/ + +static void +modem_init (MMModem *modem_class) +{ + modem_class->owns_port = owns_port; + modem_class->grab_port = grab_port; + modem_class->release_port = release_port; + modem_class->enable = enable; + modem_class->disable = disable; + modem_class->connect = connect; + modem_class->disconnect = disconnect; + modem_class->get_info = get_card_info; +} + +static void +modem_gsm_card_init (MMModemGsmCard *class) +{ + class->get_imei = get_imei; + class->get_imsi = get_imsi; + class->send_pin = send_pin; + class->send_puk = send_puk; + class->enable_pin = enable_pin; + class->change_pin = change_pin; +} + +static void +modem_gsm_network_init (MMModemGsmNetwork *class) +{ + class->do_register = do_register; + class->get_registration_info = get_registration_info; + class->set_apn = set_apn; + class->scan = scan; + class->get_signal_quality = get_signal_quality; +} + +static void +modem_gsm_sms_init (MMModemGsmSms *class) +{ + class->send = sms_send; +} + +static void +modem_simple_init (MMModemSimple *class) +{ + class->connect = simple_connect; + class->get_status = simple_get_status; +} + +static void +mm_generic_gsm_init (MMGenericGsm *self) +{ +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + case MM_MODEM_PROP_TYPE: + case MM_GENERIC_GSM_PROP_POWER_UP_CMD: + case MM_GENERIC_GSM_PROP_POWER_DOWN_CMD: + case MM_GENERIC_GSM_PROP_INIT_CMD: + case MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL: + case MM_GENERIC_GSM_PROP_SUPPORTED_BANDS: + case MM_GENERIC_GSM_PROP_SUPPORTED_MODES: + 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) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (object); + + switch (prop_id) { + case MM_MODEM_PROP_DATA_DEVICE: + if (priv->data) + g_value_set_string (value, mm_port_get_device (priv->data)); + else + g_value_set_string (value, NULL); + break; + case MM_MODEM_PROP_TYPE: + g_value_set_uint (value, MM_MODEM_TYPE_GSM); + break; + case MM_GENERIC_GSM_PROP_POWER_UP_CMD: + g_value_set_string (value, "+CFUN=1"); + break; + case MM_GENERIC_GSM_PROP_POWER_DOWN_CMD: + /* CFUN=0 is dangerous and often will shoot devices in the head (that's + * what it's supposed to do). So don't use CFUN=0 by default, but let + * specific plugins use it when they know it's safe to do so. For + * example, CFUN=0 will often make phones turn themselves off, but some + * dedicated devices (ex Sierra WWAN cards) will just turn off their + * radio but otherwise still work. + */ + g_value_set_string (value, ""); + break; + case MM_GENERIC_GSM_PROP_INIT_CMD: + g_value_set_string (value, "Z E0 V1 +CMEE=1"); + break; + case MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL: + g_value_set_string (value, "X4 &C1"); + break; + case MM_GENERIC_GSM_PROP_SUPPORTED_BANDS: + g_value_set_uint (value, 0); + break; + case MM_GENERIC_GSM_PROP_SUPPORTED_MODES: + g_value_set_uint (value, 0); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +finalize (GObject *object) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (object); + + mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (object)); + + g_free (priv->oper_code); + g_free (priv->oper_name); + + G_OBJECT_CLASS (mm_generic_gsm_parent_class)->finalize (object); +} + +static void +mm_generic_gsm_class_init (MMGenericGsmClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + mm_generic_gsm_parent_class = g_type_class_peek_parent (klass); + g_type_class_add_private (object_class, sizeof (MMGenericGsmPrivate)); + + /* Virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->finalize = finalize; + + klass->do_enable = real_do_enable; + + /* Properties */ + g_object_class_override_property (object_class, + MM_MODEM_PROP_DATA_DEVICE, + MM_MODEM_DATA_DEVICE); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_TYPE, + MM_MODEM_TYPE); + + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_SUPPORTED_BANDS, + MM_MODEM_GSM_CARD_SUPPORTED_BANDS); + + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_SUPPORTED_MODES, + MM_MODEM_GSM_CARD_SUPPORTED_MODES); + + g_object_class_install_property + (object_class, MM_GENERIC_GSM_PROP_POWER_UP_CMD, + g_param_spec_string (MM_GENERIC_GSM_POWER_UP_CMD, + "PowerUpCommand", + "Power up command", + "+CFUN=1", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, MM_GENERIC_GSM_PROP_POWER_DOWN_CMD, + g_param_spec_string (MM_GENERIC_GSM_POWER_DOWN_CMD, + "PowerDownCommand", + "Power down command", + "+CFUN=0", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, MM_GENERIC_GSM_PROP_INIT_CMD, + g_param_spec_string (MM_GENERIC_GSM_INIT_CMD, + "InitCommand", + "Initialization command", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL, + g_param_spec_string (MM_GENERIC_GSM_INIT_CMD_OPTIONAL, + "InitCommandOptional", + "Optional initialization command (errors ignored)", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + diff --git a/src/mm-generic-gsm.h b/src/mm-generic-gsm.h new file mode 100644 index 0000000..de0b00b --- /dev/null +++ b/src/mm-generic-gsm.h @@ -0,0 +1,129 @@ +/* -*- 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. + */ + +#ifndef MM_GENERIC_GSM_H +#define MM_GENERIC_GSM_H + +#include "mm-modem-gsm.h" +#include "mm-modem-gsm-network.h" +#include "mm-modem-base.h" +#include "mm-serial-port.h" +#include "mm-callback-info.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)) +#define MM_GENERIC_GSM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_GENERIC_GSM, MMGenericGsmClass)) +#define MM_IS_GENERIC_GSM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_GENERIC_GSM)) +#define MM_IS_GENERIC_GSM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_GENERIC_GSM)) +#define MM_GENERIC_GSM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_GENERIC_GSM, MMGenericGsmClass)) + +#define MM_GENERIC_GSM_POWER_UP_CMD "power-up-cmd" +#define MM_GENERIC_GSM_POWER_DOWN_CMD "power-down-cmd" +#define MM_GENERIC_GSM_INIT_CMD "init-cmd" +#define MM_GENERIC_GSM_INIT_CMD_OPTIONAL "init-cmd-optional" + +typedef enum { + MM_GENERIC_GSM_PROP_FIRST = 0x2000, + + MM_GENERIC_GSM_PROP_POWER_UP_CMD, + MM_GENERIC_GSM_PROP_POWER_DOWN_CMD, + 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 +} MMGenericGsmProp; + + +typedef struct { + MMModemBase parent; +} MMGenericGsm; + +typedef struct { + MMModemBaseClass parent; + + /* Called after opening the primary serial port and updating the modem's + * state to ENABLING, but before sending any commands to the device. Modems + * 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. + */ + void (*do_enable) (MMGenericGsm *self, MMModemFn callback, gpointer user_data); +} MMGenericGsmClass; + +GType mm_generic_gsm_get_type (void); + +MMModem *mm_generic_gsm_new (const char *device, + const char *driver, + const char *plugin); + +/* Private, for subclasses */ + +#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_set_cid (MMGenericGsm *modem, + guint32 cid); + +guint32 mm_generic_gsm_get_cid (MMGenericGsm *modem); +void mm_generic_gsm_set_reg_status (MMGenericGsm *modem, + MMModemGsmNetworkRegStatus status); + +void mm_generic_gsm_check_pin (MMGenericGsm *modem, + MMModemFn callback, + gpointer user_data); + +MMSerialPort *mm_generic_gsm_get_port (MMGenericGsm *modem, + MMPortType ptype); + +MMPort *mm_generic_gsm_grab_port (MMGenericGsm *modem, + const char *subsys, + const char *name, + MMPortType ptype, + GError **error); + +/* stay_connected should be TRUE for unsolicited registration updates, otherwise + * the registration update will clear connected/connecting/disconnecting state + * which we don't want. stay_connected should be FALSE for other cases like + * updating the state after disconnecting, or after a connect error occurs. + */ +void mm_generic_gsm_update_enabled_state (MMGenericGsm *modem, + gboolean stay_connected, + MMModemStateReason reason); + +/* Called to complete the enable operation for custom enable() handling; if an + * error is passed in, it copies the error to the callback info. This function + * always schedules the callback info. It will also update the modem with the + * correct state for both failure and success of the enable operation. + */ +void mm_generic_gsm_enable_complete (MMGenericGsm *modem, + GError *error, + MMCallbackInfo *info); + +/* Called to complete the enable operation for custom connect() handling; if an + * error is passed in, it copies the error to the callback info. This function + * always schedules the callback info. It will also update the modem with the + * correct state for both failure and success of the connect operation. + */ +void mm_generic_gsm_connect_complete (MMGenericGsm *modem, + GError *error, + MMCallbackInfo *info); + +#endif /* MM_GENERIC_GSM_H */ diff --git a/src/mm-manager.c b/src/mm-manager.c new file mode 100644 index 0000000..1a93170 --- /dev/null +++ b/src/mm-manager.c @@ -0,0 +1,714 @@ +/* -*- 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. + */ + +#include <string.h> +#include <gmodule.h> +#define G_UDEV_API_IS_SUBJECT_TO_CHANGE +#include <gudev/gudev.h> +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> +#include "mm-manager.h" +#include "mm-errors.h" +#include "mm-plugin.h" + +static gboolean impl_manager_enumerate_devices (MMManager *manager, + GPtrArray **devices, + GError **err); + +#include "mm-manager-glue.h" + +G_DEFINE_TYPE (MMManager, mm_manager, G_TYPE_OBJECT) + +enum { + DEVICE_ADDED, + DEVICE_REMOVED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +#define MM_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MANAGER, MMManagerPrivate)) + +typedef struct { + DBusGConnection *connection; + GUdevClient *udev; + GSList *plugins; + GHashTable *modems; + + GHashTable *supports; +} MMManagerPrivate; + +static MMPlugin * +load_plugin (const char *path) +{ + MMPlugin *plugin = NULL; + GModule *module; + MMPluginCreateFunc plugin_create_func; + int *major_plugin_version, *minor_plugin_version; + + module = g_module_open (path, G_MODULE_BIND_LAZY); + if (!module) { + g_warning ("Could not load plugin %s: %s", path, g_module_error ()); + return NULL; + } + + if (!g_module_symbol (module, "mm_plugin_major_version", (gpointer *) &major_plugin_version)) { + g_warning ("Could not load plugin %s: Missing major version info", path); + goto out; + } + + if (*major_plugin_version != MM_PLUGIN_MAJOR_VERSION) { + g_warning ("Could not load plugin %s: Plugin major version %d, %d is required", + path, *major_plugin_version, MM_PLUGIN_MAJOR_VERSION); + goto out; + } + + if (!g_module_symbol (module, "mm_plugin_minor_version", (gpointer *) &minor_plugin_version)) { + g_warning ("Could not load plugin %s: Missing minor version info", path); + goto out; + } + + if (*minor_plugin_version != MM_PLUGIN_MINOR_VERSION) { + g_warning ("Could not load plugin %s: Plugin minor version %d, %d is required", + path, *minor_plugin_version, MM_PLUGIN_MINOR_VERSION); + goto out; + } + + if (!g_module_symbol (module, "mm_plugin_create", (gpointer *) &plugin_create_func)) { + g_warning ("Could not load plugin %s: %s", path, g_module_error ()); + goto out; + } + + plugin = (*plugin_create_func) (); + if (plugin) { + g_object_weak_ref (G_OBJECT (plugin), (GWeakNotify) g_module_close, module); + g_message ("Loaded plugin %s", mm_plugin_get_name (plugin)); + } else + g_warning ("Could not load plugin %s: initialization failed", path); + + out: + if (!plugin) + g_module_close (module); + + return plugin; +} + +static void +load_plugins (MMManager *manager) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + GDir *dir; + const char *fname; + MMPlugin *generic_plugin = NULL; + + if (!g_module_supported ()) { + g_warning ("GModules are not supported on your platform!"); + return; + } + + dir = g_dir_open (PLUGINDIR, 0, NULL); + if (!dir) { + g_warning ("No plugins found"); + return; + } + + while ((fname = g_dir_read_name (dir)) != NULL) { + char *path; + MMPlugin *plugin; + + if (!g_str_has_suffix (fname, G_MODULE_SUFFIX)) + continue; + + path = g_module_build_path (PLUGINDIR, fname); + plugin = load_plugin (path); + g_free (path); + + if (plugin) { + if (!strcmp (mm_plugin_get_name (plugin), MM_PLUGIN_GENERIC_NAME)) + generic_plugin = plugin; + else + priv->plugins = g_slist_append (priv->plugins, plugin); + } + } + + /* Make sure the generic plugin is last */ + if (generic_plugin) + priv->plugins = g_slist_append (priv->plugins, generic_plugin); + + g_dir_close (dir); +} + +MMManager * +mm_manager_new (DBusGConnection *bus) +{ + MMManager *manager; + + g_return_val_if_fail (bus != NULL, NULL); + + manager = (MMManager *) g_object_new (MM_TYPE_MANAGER, NULL); + if (manager) { + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + + priv->connection = dbus_g_connection_ref (bus); + dbus_g_connection_register_g_object (priv->connection, + MM_DBUS_PATH, + G_OBJECT (manager)); + } + + return manager; +} + +static void +remove_modem (MMManager *manager, MMModem *modem) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + char *device; + + device = mm_modem_get_device (modem); + g_assert (device); + g_debug ("Removed modem %s", device); + + g_signal_emit (manager, signals[DEVICE_REMOVED], 0, modem); + g_hash_table_remove (priv->modems, device); + g_free (device); +} + +static void +modem_valid (MMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + MMManager *manager = MM_MANAGER (user_data); + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + static guint32 id = 0; + char *path, *device; + + if (mm_modem_get_valid (modem)) { + 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_signal_emit (manager, signals[DEVICE_ADDED], 0, modem); + } else + remove_modem (manager, modem); +} + +static void +add_modem (MMManager *manager, MMModem *modem) +{ + 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_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_free (device); +} + +static void +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) { + 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)); + } +} + +static gboolean +impl_manager_enumerate_devices (MMManager *manager, + GPtrArray **devices, + GError **err) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + + *devices = g_ptr_array_sized_new (g_hash_table_size (priv->modems)); + g_hash_table_foreach (priv->modems, enumerate_devices_cb, devices); + + return TRUE; +} + +static MMModem * +find_modem_for_device (MMManager *manager, const char *device) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, priv->modems); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MMModem *modem = MM_MODEM (value); + + if (!strcmp (device, mm_modem_get_device (modem))) + return modem; + } + return NULL; +} + + +static MMModem * +find_modem_for_port (MMManager *manager, const char *subsys, const char *name) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, priv->modems); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MMModem *modem = MM_MODEM (value); + + if (mm_modem_owns_port (modem, subsys, name)) + return modem; + } + 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) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (self); + SupportsInfo *info; + + info = g_malloc0 (sizeof (SupportsInfo)); + info->manager = self; + info->subsys = g_strdup (subsys); + info->name = g_strdup (name); + info->plugins = g_slist_copy (priv->plugins); + info->cur_plugin = info->plugins; + return info; +} + +static void +supports_info_free (SupportsInfo *info) +{ + /* Cancel any in-process operation on the first plugin */ + if (info->cur_plugin) + mm_plugin_cancel_supports_port (MM_PLUGIN (info->cur_plugin->data), info->subsys, info->name); + + if (info->defer_id) + g_source_remove (info->defer_id); + + if (info->done_id) + g_source_remove (info->done_id); + + g_free (info->subsys); + g_free (info->name); + g_slist_free (info->plugins); + memset (info, 0, sizeof (SupportsInfo)); + g_free (info); +} + +static char * +get_key (const char *subsys, const char *name) +{ + return g_strdup_printf ("%s%s", subsys, name); +} + + +static void supports_callback (MMPlugin *plugin, + const char *subsys, + const char *name, + guint32 level, + gpointer user_data); + +static void try_supports_port (MMManager *manager, + MMPlugin *plugin, + const char *subsys, + const char *name, + SupportsInfo *info); + +static gboolean +supports_defer_timeout (gpointer user_data) +{ + SupportsInfo *info = user_data; + + g_debug ("(%s): re-checking support...", info->name); + try_supports_port (info->manager, + MM_PLUGIN (info->cur_plugin->data), + info->subsys, + info->name, + info); + return FALSE; +} + +static void +try_supports_port (MMManager *manager, + MMPlugin *plugin, + const char *subsys, + const char *name, + SupportsInfo *info) +{ + MMPluginSupportsResult result; + + result = mm_plugin_supports_port (plugin, subsys, name, 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); + break; + case MM_PLUGIN_SUPPORTS_PORT_DEFER: + g_debug ("(%s): (%s) deferring support check", mm_plugin_get_name (plugin), name); + if (info->defer_id) + g_source_remove (info->defer_id); + + /* defer port detection for a bit as requested by the plugin */ + info->defer_id = g_timeout_add (3000, supports_defer_timeout, info); + break; + case MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS: + default: + break; + } +} + +static gboolean +do_grab_port (gpointer user_data) +{ + SupportsInfo *info = user_data; + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (info->manager); + MMModem *modem; + GError *error = NULL; + char *key; + GSList *iter; + + /* No more plugins to try */ + if (info->best_plugin) { + /* Create the modem */ + modem = mm_plugin_grab_port (info->best_plugin, info->subsys, info->name, &error); + if (modem) { + guint32 modem_type = MM_MODEM_TYPE_UNKNOWN; + const char *type_name = "UNKNOWN"; + + g_object_get (G_OBJECT (modem), MM_MODEM_TYPE, &modem_type, NULL); + if (modem_type == MM_MODEM_TYPE_GSM) + type_name = "GSM"; + else if (modem_type == MM_MODEM_TYPE_CDMA) + type_name = "CDMA"; + + g_message ("(%s): %s modem %s claimed port %s", + mm_plugin_get_name (info->best_plugin), + type_name, + mm_modem_get_device (modem), + info->name); + + add_modem (info->manager, modem); + } else { + g_warning ("%s: plugin '%s' claimed to support %s/%s but couldn't: (%d) %s", + __func__, + mm_plugin_get_name (info->best_plugin), + info->subsys, + info->name, + error ? error->code : -1, + (error && error->message) ? error->message : "(unknown)"); + } + } + + /* Tell each plugin to clean up any outstanding supports task */ + for (iter = info->plugins; iter; iter = g_slist_next (iter)) + mm_plugin_cancel_supports_port (MM_PLUGIN (iter->data), info->subsys, info->name); + 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); + + return FALSE; +} + +static void +supports_callback (MMPlugin *plugin, + const char *subsys, + const char *name, + guint32 level, + gpointer user_data) +{ + 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); + + /* Is this plugin's result better than any one we've tried before? */ + if (level > info->best_level) { + info->best_level = level; + 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 ( next_plugin + && !strcmp (mm_plugin_get_name (next_plugin), MM_PLUGIN_GENERIC_NAME) + && info->best_plugin) + next_plugin = NULL; + + if (next_plugin) { + /* Try the next plugin */ + try_supports_port (info->manager, next_plugin, info->subsys, info->name, info); + } else { + /* All done; let the best modem grab the port */ + info->done_id = g_idle_add (do_grab_port, info); + } +} + +static void +device_added (MMManager *manager, GUdevDevice *device) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + const char *subsys, *name; + SupportsInfo *info; + char *key; + gboolean found; + + g_return_if_fail (device != NULL); + + if (!g_slist_length (priv->plugins)) + return; + + subsys = g_udev_device_get_subsystem (device); + name = g_udev_device_get_name (device); + + 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; + } + + info = supports_info_new (manager, subsys, name); + g_hash_table_insert (priv->supports, key, info); + + try_supports_port (manager, MM_PLUGIN (info->cur_plugin->data), subsys, name, info); +} + +static void +device_removed (MMManager *manager, GUdevDevice *device) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + MMModem *modem; + const char *subsys, *name; + char *key; + SupportsInfo *info; + + g_return_if_fail (device != NULL); + + if (!g_slist_length (priv->plugins)) + return; + + subsys = g_udev_device_get_subsystem (device); + name = g_udev_device_get_name (device); + + if (strcmp (subsys, "usb") != 0) { + /* find_modem_for_port handles tty and net removal */ + modem = find_modem_for_port (manager, subsys, name); + if (modem) { + mm_modem_release_port (modem, subsys, name); + return; + } + } else { + /* This case is designed to handle the case where, at least with kernel 2.6.31, unplugging + * an in-use ttyACMx device results in udev generating remove events for the usb, but the + * ttyACMx device (subsystem tty) is not removed, since it was in-use. So if we have not + * found a modem for the port (above), we're going to look here to see if we have a modem + * associated with the newly removed device. If so, we'll remove the modem, since the + * device has been removed. That way, if the device is reinserted later, we'll go through + * the process of exporting it. + */ + const char *sysfs_path = g_udev_device_get_sysfs_path (device); + + // g_debug ("Looking for a modem for removed device %s", sysfs_path); + modem = find_modem_for_device (manager, sysfs_path); + if (modem) { + g_debug ("Removing modem claimed by removed device %s", sysfs_path); + remove_modem (manager, modem); + return; + } + } + + /* Maybe a plugin is checking whether or not the port is supported */ + key = get_key (subsys, name); + info = g_hash_table_lookup (priv->supports, key); + + if (info) { + if (info->plugins) + mm_plugin_cancel_supports_port (MM_PLUGIN (info->plugins->data), subsys, name); + g_hash_table_remove (priv->supports, key); + } + + g_free (key); +} + +static void +handle_uevent (GUdevClient *client, + const char *action, + GUdevDevice *device, + gpointer user_data) +{ + MMManager *self = MM_MANAGER (user_data); + const char *subsys; + + g_return_if_fail (action != NULL); + + /* A bit paranoid */ + subsys = g_udev_device_get_subsystem (device); + g_return_if_fail (subsys != NULL); + + g_return_if_fail (!strcmp (subsys, "tty") || !strcmp (subsys, "net") || !strcmp (subsys, "usb")); + + /* We only care about tty/net devices when adding modem ports, + * but for remove, also handle usb parent device remove events + */ + if ((!strcmp (action, "add") || !strcmp (action, "move")) && strcmp (subsys, "usb") !=0 ) + device_added (self, device); + else if (!strcmp (action, "remove")) + device_removed (self, device); +} + +void +mm_manager_start (MMManager *manager) +{ + MMManagerPrivate *priv; + GList *devices, *iter; + + g_return_if_fail (manager != NULL); + g_return_if_fail (MM_IS_MANAGER (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)) + device_added (manager, G_UDEV_DEVICE (iter->data)); + + devices = g_udev_client_query_by_subsystem (priv->udev, "net"); + for (iter = devices; iter; iter = g_list_next (iter)) + device_added (manager, G_UDEV_DEVICE (iter->data)); +} + +static void +mm_manager_init (MMManager *manager) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + const char *subsys[4] = { "tty", "net", "usb", NULL }; + + priv->modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + load_plugins (manager); + + priv->supports = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) supports_info_free); + + priv->udev = g_udev_client_new (subsys); + g_assert (priv->udev); + g_signal_connect (priv->udev, "uevent", G_CALLBACK (handle_uevent), manager); +} + +static void +finalize (GObject *object) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (object); + + g_hash_table_destroy (priv->supports); + g_hash_table_destroy (priv->modems); + + g_slist_foreach (priv->plugins, (GFunc) g_object_unref, NULL); + g_slist_free (priv->plugins); + + if (priv->udev) + g_object_unref (priv->udev); + + if (priv->connection) + dbus_g_connection_unref (priv->connection); + + G_OBJECT_CLASS (mm_manager_parent_class)->finalize (object); +} + +static void +mm_manager_class_init (MMManagerClass *manager_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (manager_class); + + g_type_class_add_private (object_class, sizeof (MMManagerPrivate)); + + /* Virtual methods */ + object_class->finalize = finalize; + + /* Signals */ + signals[DEVICE_ADDED] = + g_signal_new ("device-added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMManagerClass, device_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + G_TYPE_OBJECT); + + signals[DEVICE_REMOVED] = + g_signal_new ("device-removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMManagerClass, device_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + G_TYPE_OBJECT); + + dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (manager_class), + &dbus_glib_mm_manager_object_info); + + dbus_g_error_domain_register (MM_SERIAL_ERROR, "org.freedesktop.ModemManager.Modem", MM_TYPE_SERIAL_ERROR); + dbus_g_error_domain_register (MM_MODEM_ERROR, "org.freedesktop.ModemManager.Modem", MM_TYPE_MODEM_ERROR); + dbus_g_error_domain_register (MM_MODEM_CONNECT_ERROR, "org.freedesktop.ModemManager.Modem", MM_TYPE_MODEM_CONNECT_ERROR); + dbus_g_error_domain_register (MM_MOBILE_ERROR, "org.freedesktop.ModemManager.Modem.Gsm", MM_TYPE_MOBILE_ERROR); +} + diff --git a/src/mm-manager.h b/src/mm-manager.h new file mode 100644 index 0000000..5220b77 --- /dev/null +++ b/src/mm-manager.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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef MM_MANAGER_H +#define MM_MANAGER_H + +#include <glib/gtypes.h> +#include <glib-object.h> +#include <dbus/dbus-glib.h> +#include "mm-modem.h" + +#define MM_TYPE_MANAGER (mm_manager_get_type ()) +#define MM_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MANAGER, MMManager)) +#define MM_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_MANAGER, MMManagerClass)) +#define MM_IS_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MANAGER)) +#define MM_IS_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), MM_TYPE_MANAGER)) +#define MM_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_MANAGER, MMManagerClass)) + +#define MM_DBUS_SERVICE "org.freedesktop.ModemManager" +#define MM_DBUS_PATH "/org/freedesktop/ModemManager" + +typedef struct { + GObject parent; +} MMManager; + +typedef struct { + GObjectClass parent; + + /* Signals */ + void (*device_added) (MMManager *manager, MMModem *device); + void (*device_removed) (MMManager *manager, MMModem *device); +} MMManagerClass; + +GType mm_manager_get_type (void); + +MMManager *mm_manager_new (DBusGConnection *bus); + +void mm_manager_start (MMManager *manager); + +#endif /* MM_MANAGER_H */ diff --git a/src/mm-modem-base.c b/src/mm-modem-base.c new file mode 100644 index 0000000..3d82f8e --- /dev/null +++ b/src/mm-modem-base.c @@ -0,0 +1,341 @@ +/* -*- 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include "mm-modem-base.h" +#include "mm-modem.h" +#include "mm-serial-port.h" +#include "mm-errors.h" +#include "mm-options.h" +#include "mm-properties-changed-signal.h" + +static void modem_init (MMModem *modem_class); + +G_DEFINE_TYPE_EXTENDED (MMModemBase, mm_modem_base, + G_TYPE_OBJECT, + G_TYPE_FLAG_VALUE_ABSTRACT, + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)) + +#define MM_MODEM_BASE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_BASE, MMModemBasePrivate)) + +typedef struct { + char *driver; + char *plugin; + char *device; + guint32 ip_method; + gboolean valid; + MMModemState state; + + GHashTable *ports; +} MMModemBasePrivate; + + +static char * +get_hash_key (const char *subsys, const char *name) +{ + return g_strdup_printf ("%s%s", subsys, name); +} + +MMPort * +mm_modem_base_get_port (MMModemBase *self, + const char *subsys, + const char *name) +{ + MMPort *port; + char *key; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (subsys != NULL, NULL); + + g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), NULL); + + key = get_hash_key (subsys, name); + port = g_hash_table_lookup (MM_MODEM_BASE_GET_PRIVATE (self)->ports, key); + g_free (key); + return port; +} + +static void +find_primary (gpointer key, gpointer data, gpointer user_data) +{ + MMPort **found = user_data; + MMPort *port = MM_PORT (data); + + if (!*found && (mm_port_get_port_type (port) == MM_PORT_TYPE_PRIMARY)) + *found = port; +} + +MMPort * +mm_modem_base_add_port (MMModemBase *self, + const char *subsys, + const char *name, + MMPortType ptype) +{ + MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); + MMPort *port = NULL; + char *key; + + g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); + g_return_val_if_fail (subsys != NULL, NULL); + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (ptype != MM_PORT_TYPE_UNKNOWN, NULL); + + g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), NULL); + + key = get_hash_key (subsys, name); + port = g_hash_table_lookup (priv->ports, key); + g_free (key); + g_return_val_if_fail (port == NULL, NULL); + + if (ptype == MM_PORT_TYPE_PRIMARY) { + g_hash_table_foreach (priv->ports, find_primary, &port); + 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")) { + port = MM_PORT (g_object_new (MM_TYPE_PORT, + MM_PORT_DEVICE, name, + MM_PORT_SUBSYS, MM_PORT_SUBSYS_NET, + MM_PORT_TYPE, ptype, + NULL)); + } + + if (!port) + return NULL; + + key = get_hash_key (subsys, name); + g_hash_table_insert (priv->ports, key, port); + return port; +} + +gboolean +mm_modem_base_remove_port (MMModemBase *self, MMPort *port) +{ + g_return_val_if_fail (MM_IS_MODEM_BASE (self), FALSE); + g_return_val_if_fail (port != NULL, FALSE); + + return g_hash_table_remove (MM_MODEM_BASE_GET_PRIVATE (self)->ports, port); +} + +void +mm_modem_base_set_valid (MMModemBase *self, gboolean new_valid) +{ + MMModemBasePrivate *priv; + + g_return_if_fail (MM_IS_MODEM_BASE (self)); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + + if (priv->valid != new_valid) { + priv->valid = new_valid; + + /* Modem starts off in disabled state, and jumps to disabled when + * it's no longer valid. + */ + mm_modem_set_state (MM_MODEM (self), + MM_MODEM_STATE_DISABLED, + MM_MODEM_STATE_REASON_NONE); + + g_object_notify (G_OBJECT (self), MM_MODEM_VALID); + } +} + +gboolean +mm_modem_base_get_valid (MMModemBase *self) +{ + g_return_val_if_fail (MM_IS_MODEM_BASE (self), FALSE); + + return MM_MODEM_BASE_GET_PRIVATE (self)->valid; +} + +/*****************************************************************************/ + +static void +mm_modem_base_init (MMModemBase *self) +{ + MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); + + 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); +} + +static void +modem_init (MMModem *modem_class) +{ +} + +static gboolean +is_enabled (MMModemState state) +{ + return (state >= MM_MODEM_STATE_ENABLED); +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (object); + gboolean old_enabled; + + switch (prop_id) { + case MM_MODEM_PROP_STATE: + /* Ensure we update the 'enabled' property when the state changes */ + old_enabled = is_enabled (priv->state); + priv->state = g_value_get_uint (value); + if (old_enabled != is_enabled (priv->state)) + g_object_notify (object, MM_MODEM_ENABLED); + break; + case MM_MODEM_PROP_DRIVER: + /* Construct only */ + priv->driver = g_value_dup_string (value); + break; + case MM_MODEM_PROP_PLUGIN: + /* Construct only */ + priv->plugin = g_value_dup_string (value); + break; + case MM_MODEM_PROP_MASTER_DEVICE: + /* Construct only */ + priv->device = g_value_dup_string (value); + break; + case MM_MODEM_PROP_IP_METHOD: + priv->ip_method = g_value_get_uint (value); + break; + case MM_MODEM_PROP_VALID: + case MM_MODEM_PROP_TYPE: + case MM_MODEM_PROP_ENABLED: + 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) +{ + MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (object); + + switch (prop_id) { + case MM_MODEM_PROP_STATE: + g_value_set_uint (value, priv->state); + break; + case MM_MODEM_PROP_MASTER_DEVICE: + g_value_set_string (value, priv->device); + break; + case MM_MODEM_PROP_DATA_DEVICE: + g_value_set_string (value, NULL); + break; + case MM_MODEM_PROP_DRIVER: + g_value_set_string (value, priv->driver); + break; + case MM_MODEM_PROP_PLUGIN: + g_value_set_string (value, priv->plugin); + break; + case MM_MODEM_PROP_TYPE: + g_value_set_uint (value, MM_MODEM_TYPE_UNKNOWN); + break; + case MM_MODEM_PROP_IP_METHOD: + g_value_set_uint (value, priv->ip_method); + break; + case MM_MODEM_PROP_VALID: + g_value_set_boolean (value, priv->valid); + break; + case MM_MODEM_PROP_ENABLED: + g_value_set_boolean (value, is_enabled (priv->state)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +finalize (GObject *object) +{ + MMModemBase *self = MM_MODEM_BASE (object); + MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); + + g_hash_table_destroy (priv->ports); + g_free (priv->driver); + g_free (priv->plugin); + g_free (priv->device); + + G_OBJECT_CLASS (mm_modem_base_parent_class)->finalize (object); +} + +static void +mm_modem_base_class_init (MMModemBaseClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMModemBasePrivate)); + + /* Virtual methods */ + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + g_object_class_override_property (object_class, + MM_MODEM_PROP_STATE, + MM_MODEM_STATE); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_MASTER_DEVICE, + MM_MODEM_MASTER_DEVICE); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_DATA_DEVICE, + MM_MODEM_DATA_DEVICE); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_DRIVER, + MM_MODEM_DRIVER); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_PLUGIN, + MM_MODEM_PLUGIN); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_TYPE, + MM_MODEM_TYPE); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_IP_METHOD, + MM_MODEM_IP_METHOD); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_VALID, + MM_MODEM_VALID); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_ENABLED, + MM_MODEM_ENABLED); + + mm_properties_changed_signal_new (object_class); +} + diff --git a/src/mm-modem-base.h b/src/mm-modem-base.h new file mode 100644 index 0000000..9eb64ce --- /dev/null +++ b/src/mm-modem-base.h @@ -0,0 +1,64 @@ +/* -*- 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. + */ + +#ifndef MM_MODEM_BASE_H +#define MM_MODEM_BASE_H + +#include <glib.h> +#include <glib/gtypes.h> +#include <glib-object.h> + +#include "mm-port.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)) +#define MM_MODEM_BASE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_MODEM_BASE, MMModemBaseClass)) +#define MM_IS_MODEM_BASE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_BASE)) +#define MM_IS_MODEM_BASE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_MODEM_BASE)) +#define MM_MODEM_BASE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_MODEM_BASE, MMModemBaseClass)) + +typedef struct _MMModemBase MMModemBase; +typedef struct _MMModemBaseClass MMModemBaseClass; + +struct _MMModemBase { + GObject parent; +}; + +struct _MMModemBaseClass { + GObjectClass parent; +}; + +GType mm_modem_base_get_type (void); + +MMPort *mm_modem_base_get_port (MMModemBase *self, + const char *subsys, + const char *name); + +MMPort *mm_modem_base_add_port (MMModemBase *self, + const char *subsys, + const char *name, + MMPortType ptype); + +gboolean mm_modem_base_remove_port (MMModemBase *self, + MMPort *port); + +void mm_modem_base_set_valid (MMModemBase *self, + gboolean valid); + +gboolean mm_modem_base_get_valid (MMModemBase *self); + +#endif /* MM_MODEM_BASE_H */ + diff --git a/src/mm-modem-cdma.c b/src/mm-modem-cdma.c new file mode 100644 index 0000000..112b93f --- /dev/null +++ b/src/mm-modem-cdma.c @@ -0,0 +1,346 @@ +/* -*- 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 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#include <string.h> +#include <dbus/dbus-glib.h> +#include "mm-modem-cdma.h" +#include "mm-errors.h" +#include "mm-callback-info.h" +#include "mm-marshal.h" + +static void impl_modem_cdma_get_signal_quality (MMModemCdma *modem, DBusGMethodInvocation *context); +static void impl_modem_cdma_get_esn (MMModemCdma *modem, DBusGMethodInvocation *context); +static void impl_modem_cdma_get_serving_system (MMModemCdma *modem, DBusGMethodInvocation *context); +static void impl_modem_cdma_get_registration_state (MMModemCdma *modem, DBusGMethodInvocation *context); + +#include "mm-modem-cdma-glue.h" + +enum { + SIGNAL_QUALITY, + REGISTRATION_STATE_CHANGED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +/*****************************************************************************/ + +static void +str_call_done (MMModem *modem, const char *result, 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, result); +} + +static void +str_call_not_supported (MMModemCdma *self, + MMModemStringFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_string_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 +uint_op_not_supported (MMModem *self, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_uint_new (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 +uint_call_done (MMModem *modem, guint32 result, 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, result); +} + +static void +serving_system_call_done (MMModemCdma *self, + guint32 class, + unsigned char band, + guint32 sid, + GError *error, + gpointer user_data) +{ + DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data; + + if (error) + dbus_g_method_return_error (context, error); + else { + GValueArray *array; + GValue value = { 0, }; + char band_str[2] = { 0, 0 }; + + array = g_value_array_new (3); + + /* Band Class */ + g_value_init (&value, G_TYPE_UINT); + g_value_set_uint (&value, class); + g_value_array_append (array, &value); + g_value_unset (&value); + + /* Band */ + g_value_init (&value, G_TYPE_STRING); + band_str[0] = band; + g_value_set_string (&value, band_str); + g_value_array_append (array, &value); + g_value_unset (&value); + + /* SID */ + g_value_init (&value, G_TYPE_UINT); + g_value_set_uint (&value, sid); + g_value_array_append (array, &value); + g_value_unset (&value); + + dbus_g_method_return (context, array); + } +} + +static void +serving_system_invoke (MMCallbackInfo *info) +{ + MMModemCdmaServingSystemFn callback = (MMModemCdmaServingSystemFn) info->callback; + + callback (MM_MODEM_CDMA (info->modem), 0, 0, 0, info->error, info->user_data); +} + +static void +serving_system_call_not_supported (MMModemCdma *self, + MMModemCdmaServingSystemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new_full (MM_MODEM (self), serving_system_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_cdma_get_serving_system (MMModemCdma *self, + MMModemCdmaServingSystemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_CDMA (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_CDMA_GET_INTERFACE (self)->get_serving_system) + MM_MODEM_CDMA_GET_INTERFACE (self)->get_serving_system (self, callback, user_data); + else + serving_system_call_not_supported (self, callback, user_data); +} + +static void +impl_modem_cdma_get_serving_system (MMModemCdma *modem, + DBusGMethodInvocation *context) +{ + mm_modem_cdma_get_serving_system (modem, serving_system_call_done, context); +} + +void +mm_modem_cdma_get_esn (MMModemCdma *self, + MMModemStringFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_CDMA (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_CDMA_GET_INTERFACE (self)->get_esn) + MM_MODEM_CDMA_GET_INTERFACE (self)->get_esn (self, callback, user_data); + else + str_call_not_supported (self, callback, user_data); +} + +static void +impl_modem_cdma_get_esn (MMModemCdma *modem, + DBusGMethodInvocation *context) +{ + mm_modem_cdma_get_esn (modem, str_call_done, context); +} + +void +mm_modem_cdma_get_signal_quality (MMModemCdma *self, + MMModemUIntFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_CDMA (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_CDMA_GET_INTERFACE (self)->get_signal_quality) + MM_MODEM_CDMA_GET_INTERFACE (self)->get_signal_quality (self, callback, user_data); + else + uint_op_not_supported (MM_MODEM (self), callback, user_data); +} + +static void +impl_modem_cdma_get_signal_quality (MMModemCdma *modem, DBusGMethodInvocation *context) +{ + mm_modem_cdma_get_signal_quality (modem, uint_call_done, context); +} + +void +mm_modem_cdma_emit_signal_quality_changed (MMModemCdma *self, guint32 quality) +{ + g_return_if_fail (MM_IS_MODEM_CDMA (self)); + + g_signal_emit (self, signals[SIGNAL_QUALITY], 0, quality); +} + +/*****************************************************************************/ + +static void +get_registration_state_call_done (MMModemCdma *self, + MMModemCdmaRegistrationState cdma_1x_reg_state, + MMModemCdmaRegistrationState evdo_reg_state, + 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, cdma_1x_reg_state, evdo_reg_state); +} + +void +mm_modem_cdma_get_registration_state (MMModemCdma *self, + MMModemCdmaRegistrationStateFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_CDMA (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_CDMA_GET_INTERFACE (self)->get_registration_state) + MM_MODEM_CDMA_GET_INTERFACE (self)->get_registration_state (self, callback, user_data); + else { + GError *error; + + error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + + callback (self, + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN, + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN, + error, + user_data); + + g_error_free (error); + } +} + +static void +impl_modem_cdma_get_registration_state (MMModemCdma *modem, DBusGMethodInvocation *context) +{ + mm_modem_cdma_get_registration_state (modem, get_registration_state_call_done, context); +} + +void +mm_modem_cdma_emit_registration_state_changed (MMModemCdma *self, + MMModemCdmaRegistrationState cdma_1x_new_state, + MMModemCdmaRegistrationState evdo_new_state) +{ + g_return_if_fail (MM_IS_MODEM_CDMA (self)); + + g_signal_emit (self, signals[REGISTRATION_STATE_CHANGED], 0, cdma_1x_new_state, evdo_new_state); +} + +/*****************************************************************************/ + +static void +mm_modem_cdma_init (gpointer g_iface) +{ + GType iface_type = G_TYPE_FROM_INTERFACE (g_iface); + static gboolean initialized = FALSE; + + if (initialized) + return; + + /* Signals */ + signals[SIGNAL_QUALITY] = + g_signal_new ("signal-quality", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMModemCdma, signal_quality), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + + signals[REGISTRATION_STATE_CHANGED] = + g_signal_new ("registration-state-changed", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMModemCdma, registration_state_changed), + NULL, NULL, + mm_marshal_VOID__UINT_UINT, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); + + initialized = TRUE; +} + +GType +mm_modem_cdma_get_type (void) +{ + static GType modem_type = 0; + + if (!G_UNLIKELY (modem_type)) { + const GTypeInfo modem_info = { + sizeof (MMModemCdma), /* class_size */ + mm_modem_cdma_init, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + modem_type = g_type_register_static (G_TYPE_INTERFACE, + "MMModemCdma", + &modem_info, 0); + + g_type_interface_add_prerequisite (modem_type, MM_TYPE_MODEM); + + dbus_g_object_type_install_info (modem_type, &dbus_glib_mm_modem_cdma_object_info); + } + + return modem_type; +} diff --git a/src/mm-modem-cdma.h b/src/mm-modem-cdma.h new file mode 100644 index 0000000..a7a626f --- /dev/null +++ b/src/mm-modem-cdma.h @@ -0,0 +1,108 @@ +/* -*- 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 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef MM_MODEM_CDMA_H +#define MM_MODEM_CDMA_H + +#include <mm-modem.h> + +typedef enum { + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN = 0, + MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED = 1, + MM_MODEM_CDMA_REGISTRATION_STATE_HOME = 2, + MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING = 3, + + MM_MODEM_CDMA_REGISTRATION_STATE_LAST = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING +} MMModemCdmaRegistrationState; + +#define MM_TYPE_MODEM_CDMA (mm_modem_cdma_get_type ()) +#define MM_MODEM_CDMA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_CDMA, MMModemCdma)) +#define MM_IS_MODEM_CDMA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_CDMA)) +#define MM_MODEM_CDMA_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_CDMA, MMModemCdma)) + +#define MM_MODEM_CDMA_REGISTRATION_STATE_CHANGED "registration-state-changed" + +typedef struct _MMModemCdma MMModemCdma; + +typedef void (*MMModemCdmaServingSystemFn) (MMModemCdma *modem, + guint32 class, + unsigned char band, + guint32 sid, + GError *error, + gpointer user_data); + +typedef void (*MMModemCdmaRegistrationStateFn) (MMModemCdma *modem, + MMModemCdmaRegistrationState cdma_1x_reg_state, + MMModemCdmaRegistrationState evdo_reg_state, + GError *error, + gpointer user_data); + +struct _MMModemCdma { + GTypeInterface g_iface; + + /* Methods */ + void (*get_signal_quality) (MMModemCdma *self, + MMModemUIntFn callback, + gpointer user_data); + + void (*get_esn) (MMModemCdma *self, + MMModemStringFn callback, + gpointer user_data); + + void (*get_serving_system) (MMModemCdma *self, + MMModemCdmaServingSystemFn callback, + gpointer user_data); + + void (*get_registration_state) (MMModemCdma *self, + MMModemCdmaRegistrationStateFn callback, + gpointer user_data); + + /* Signals */ + void (*signal_quality) (MMModemCdma *self, + guint32 quality); + + void (*registration_state_changed) (MMModemCdma *self, + MMModemCdmaRegistrationState cdma_1x_new_state, + MMModemCdmaRegistrationState evdo_new_state); +}; + +GType mm_modem_cdma_get_type (void); + +void mm_modem_cdma_get_signal_quality (MMModemCdma *self, + MMModemUIntFn callback, + gpointer user_data); + +void mm_modem_cdma_get_esn (MMModemCdma *self, + MMModemStringFn callback, + gpointer user_data); + +void mm_modem_cdma_get_serving_system (MMModemCdma *self, + MMModemCdmaServingSystemFn callback, + gpointer user_data); + +void mm_modem_cdma_get_registration_state (MMModemCdma *self, + MMModemCdmaRegistrationStateFn callback, + gpointer user_data); + +/* Protected */ + +void mm_modem_cdma_emit_signal_quality_changed (MMModemCdma *self, guint32 new_quality); + +void mm_modem_cdma_emit_registration_state_changed (MMModemCdma *self, + MMModemCdmaRegistrationState cdma_1x_new_state, + MMModemCdmaRegistrationState evdo_new_state); + +#endif /* MM_MODEM_CDMA_H */ diff --git a/src/mm-modem-gsm-card.c b/src/mm-modem-gsm-card.c new file mode 100644 index 0000000..dea4590 --- /dev/null +++ b/src/mm-modem-gsm-card.c @@ -0,0 +1,312 @@ +/* -*- 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 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#include <dbus/dbus-glib.h> + +#include "mm-modem-gsm-card.h" +#include "mm-errors.h" +#include "mm-callback-info.h" +#include "mm-modem-gsm.h" + +static void impl_gsm_modem_get_imei (MMModemGsmCard *modem, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_get_imsi (MMModemGsmCard *modem, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_send_pin (MMModemGsmCard *modem, + const char *pin, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_send_puk (MMModemGsmCard *modem, + const char *puk, + const char *pin, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_enable_pin (MMModemGsmCard *modem, + const char *pin, + gboolean enabled, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_change_pin (MMModemGsmCard *modem, + const char *old_pin, + const char *new_pin, + DBusGMethodInvocation *context); + +#include "mm-modem-gsm-card-glue.h" + +/*****************************************************************************/ + +static void +str_call_done (MMModem *modem, const char *result, 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, result); +} + +static void +str_call_not_supported (MMModemGsmCard *self, + MMModemStringFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_string_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; + + if (error) + dbus_g_method_return_error (context, error); + else + dbus_g_method_return (context); +} + +static void +async_call_not_supported (MMModemGsmCard *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); +} + +/*****************************************************************************/ + +void +mm_modem_gsm_card_get_imei (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_imei) + MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_imei (self, callback, user_data); + else + str_call_not_supported (self, callback, user_data); +} + +void +mm_modem_gsm_card_get_imsi (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_imsi) + MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_imsi (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, + const char *pin, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_GSM_CARD (self)); + g_return_if_fail (puk != NULL); + g_return_if_fail (pin != NULL); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->send_puk) + MM_MODEM_GSM_CARD_GET_INTERFACE (self)->send_puk (self, puk, pin, callback, user_data); + else + async_call_not_supported (self, callback, user_data); +} + +void +mm_modem_gsm_card_send_pin (MMModemGsmCard *self, + const char *pin, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_GSM_CARD (self)); + g_return_if_fail (pin != NULL); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->send_pin) + MM_MODEM_GSM_CARD_GET_INTERFACE (self)->send_pin (self, pin, callback, user_data); + else + async_call_not_supported (self, callback, user_data); +} + +void +mm_modem_gsm_card_enable_pin (MMModemGsmCard *self, + const char *pin, + gboolean enabled, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_GSM_CARD (self)); + g_return_if_fail (pin != NULL); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->enable_pin) + MM_MODEM_GSM_CARD_GET_INTERFACE (self)->enable_pin (self, pin, enabled, callback, user_data); + else + async_call_not_supported (self, callback, user_data); +} + +void +mm_modem_gsm_card_change_pin (MMModemGsmCard *self, + const char *old_pin, + const char *new_pin, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_GSM_CARD (self)); + g_return_if_fail (old_pin != NULL); + g_return_if_fail (new_pin != NULL); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->change_pin) + MM_MODEM_GSM_CARD_GET_INTERFACE (self)->change_pin (self, old_pin, new_pin, callback, user_data); + else + async_call_not_supported (self, callback, user_data); +} + +/*****************************************************************************/ + +static void +impl_gsm_modem_get_imei (MMModemGsmCard *modem, + DBusGMethodInvocation *context) +{ + mm_modem_gsm_card_get_imei (modem, str_call_done, context); +} + +static void +impl_gsm_modem_get_imsi (MMModemGsmCard *modem, + DBusGMethodInvocation *context) +{ + mm_modem_gsm_card_get_imsi (modem, str_call_done, context); +} + +static void + impl_gsm_modem_send_puk (MMModemGsmCard *modem, + const char *puk, + const char *pin, + DBusGMethodInvocation *context) +{ + mm_modem_gsm_card_send_puk (modem, puk, pin, async_call_done, context); +} + +static void +impl_gsm_modem_send_pin (MMModemGsmCard *modem, + const char *pin, + DBusGMethodInvocation *context) +{ + mm_modem_gsm_card_send_pin (modem, pin, async_call_done, context); +} + +static void +impl_gsm_modem_enable_pin (MMModemGsmCard *modem, + const char *pin, + gboolean enabled, + DBusGMethodInvocation *context) +{ + mm_modem_gsm_card_enable_pin (modem, pin, enabled, async_call_done, context); +} + +static void +impl_gsm_modem_change_pin (MMModemGsmCard *modem, + const char *old_pin, + const char *new_pin, + DBusGMethodInvocation *context) +{ + mm_modem_gsm_card_change_pin (modem, old_pin, new_pin, async_call_done, context); +} + +/*****************************************************************************/ + +static void +mm_modem_gsm_card_init (gpointer g_iface) +{ + static gboolean initialized = FALSE; + + if (G_LIKELY (initialized)) + return; + + initialized = TRUE; + + g_object_interface_install_property + (g_iface, + g_param_spec_uint (MM_MODEM_GSM_CARD_SUPPORTED_BANDS, + "Supported Modes", + "Supported frequency bands of the card", + MM_MODEM_GSM_BAND_UNKNOWN, + MM_MODEM_GSM_BAND_LAST, + MM_MODEM_GSM_BAND_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_uint (MM_MODEM_GSM_CARD_SUPPORTED_MODES, + "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, + MM_MODEM_GSM_MODE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +GType +mm_modem_gsm_card_get_type (void) +{ + static GType card_type = 0; + + if (G_UNLIKELY (!card_type)) { + const GTypeInfo card_info = { + sizeof (MMModemGsmCard), /* class_size */ + mm_modem_gsm_card_init, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + card_type = g_type_register_static (G_TYPE_INTERFACE, + "MMModemGsmCard", + &card_info, 0); + + g_type_interface_add_prerequisite (card_type, G_TYPE_OBJECT); + dbus_g_object_type_install_info (card_type, &dbus_glib_mm_modem_gsm_card_object_info); + } + + return card_type; +} diff --git a/src/mm-modem-gsm-card.h b/src/mm-modem-gsm-card.h new file mode 100644 index 0000000..4d690e6 --- /dev/null +++ b/src/mm-modem-gsm-card.h @@ -0,0 +1,101 @@ +/* -*- 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 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef MM_MODEM_GSM_CARD_H +#define MM_MODEM_GSM_CARD_H + +#include <mm-modem.h> + +#define MM_TYPE_MODEM_GSM_CARD (mm_modem_gsm_card_get_type ()) +#define MM_MODEM_GSM_CARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_GSM_CARD, MMModemGsmCard)) +#define MM_IS_MODEM_GSM_CARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_GSM_CARD)) +#define MM_MODEM_GSM_CARD_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_GSM_CARD, MMModemGsmCard)) + +#define MM_MODEM_GSM_CARD_SUPPORTED_BANDS "supported-bands" +#define MM_MODEM_GSM_CARD_SUPPORTED_MODES "supported-modes" + +typedef struct _MMModemGsmCard MMModemGsmCard; + +struct _MMModemGsmCard { + GTypeInterface g_iface; + + /* Methods */ + void (*get_imei) (MMModemGsmCard *self, + MMModemStringFn callback, + gpointer user_data); + + void (*get_imsi) (MMModemGsmCard *self, + MMModemStringFn callback, + gpointer user_data); + + void (*send_puk) (MMModemGsmCard *self, + const char *puk, + const char *pin, + MMModemFn callback, + gpointer user_data); + + void (*send_pin) (MMModemGsmCard *self, + const char *pin, + MMModemFn callback, + gpointer user_data); + + void (*enable_pin) (MMModemGsmCard *self, + const char *pin, + gboolean enabled, + MMModemFn callback, + gpointer user_data); + + void (*change_pin) (MMModemGsmCard *self, + const char *old_pin, + const char *new_pin, + MMModemFn callback, + gpointer user_data); +}; + +GType mm_modem_gsm_card_get_type (void); + +void mm_modem_gsm_card_get_imei (MMModemGsmCard *self, + MMModemStringFn callback, + gpointer user_data); + +void mm_modem_gsm_card_get_imsi (MMModemGsmCard *self, + MMModemStringFn callback, + gpointer user_data); + +void mm_modem_gsm_card_send_puk (MMModemGsmCard *self, + const char *puk, + const char *pin, + MMModemFn callback, + gpointer user_data); + +void mm_modem_gsm_card_send_pin (MMModemGsmCard *self, + const char *pin, + MMModemFn callback, + gpointer user_data); + +void mm_modem_gsm_card_enable_pin (MMModemGsmCard *self, + const char *pin, + gboolean enabled, + MMModemFn callback, + gpointer user_data); + +void mm_modem_gsm_card_change_pin (MMModemGsmCard *self, + const char *old_pin, + const char *new_pin, + MMModemFn callback, + gpointer user_data); + +#endif /* MM_MODEM_GSM_CARD_H */ diff --git a/src/mm-modem-gsm-network.c b/src/mm-modem-gsm-network.c new file mode 100644 index 0000000..26ff422 --- /dev/null +++ b/src/mm-modem-gsm-network.c @@ -0,0 +1,569 @@ +/* -*- 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 Novell, Inc. + */ + +#include <string.h> +#include <dbus/dbus-glib.h> + +#include "mm-modem-gsm-network.h" +#include "mm-errors.h" +#include "mm-callback-info.h" +#include "mm-marshal.h" + +static void impl_gsm_modem_register (MMModemGsmNetwork *modem, + const char *network_id, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_scan (MMModemGsmNetwork *modem, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_set_apn (MMModemGsmNetwork *modem, + const char *apn, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_get_signal_quality (MMModemGsmNetwork *modem, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_set_band (MMModemGsmNetwork *modem, + MMModemGsmBand band, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_get_band (MMModemGsmNetwork *modem, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_set_network_mode (MMModemGsmNetwork *modem, + MMModemGsmMode mode, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_get_network_mode (MMModemGsmNetwork *modem, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_get_reg_info (MMModemGsmNetwork *modem, + DBusGMethodInvocation *context); + +#include "mm-modem-gsm-network-glue.h" + +/*****************************************************************************/ + +enum { + SIGNAL_QUALITY, + REGISTRATION_INFO, + NETWORK_MODE, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +/*****************************************************************************/ + +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 (MMModemGsmNetwork *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); +} + +static void +uint_call_done (MMModem *modem, guint32 result, 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, result); +} + +static void +uint_call_not_supported (MMModemGsmNetwork *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 +reg_info_call_done (MMModemGsmNetwork *self, + MMModemGsmNetworkRegStatus status, + const char *oper_code, + const char *oper_name, + GError *error, + gpointer user_data) +{ + DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data; + + if (error) + dbus_g_method_return_error (context, error); + else { + GValueArray *array; + GValue value = { 0, }; + + array = g_value_array_new (3); + + /* Status */ + g_value_init (&value, G_TYPE_UINT); + g_value_set_uint (&value, (guint32) status); + g_value_array_append (array, &value); + g_value_unset (&value); + + /* Operator code */ + g_value_init (&value, G_TYPE_STRING); + g_value_set_string (&value, oper_code); + g_value_array_append (array, &value); + g_value_unset (&value); + + /* Operator name */ + g_value_init (&value, G_TYPE_STRING); + g_value_set_string (&value, oper_name); + g_value_array_append (array, &value); + g_value_unset (&value); + + dbus_g_method_return (context, array); + } +} + +static void +reg_info_invoke (MMCallbackInfo *info) +{ + MMModemGsmNetworkRegInfoFn callback = (MMModemGsmNetworkRegInfoFn) info->callback; + + callback (MM_MODEM_GSM_NETWORK (info->modem), 0, NULL, NULL, info->error, info->user_data); +} + +static void +reg_info_call_not_supported (MMModemGsmNetwork *self, + MMModemGsmNetworkRegInfoFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new_full (MM_MODEM (self), reg_info_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); +} + +static void +scan_call_done (MMModemGsmNetwork *self, + GPtrArray *results, + 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, results); +} + +static void +gsm_network_scan_invoke (MMCallbackInfo *info) +{ + MMModemGsmNetworkScanFn callback = (MMModemGsmNetworkScanFn) info->callback; + + callback (MM_MODEM_GSM_NETWORK (info->modem), NULL, info->error, info->user_data); +} + +static void +scan_call_not_supported (MMModemGsmNetwork *self, + MMModemGsmNetworkScanFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new_full (MM_MODEM (self), gsm_network_scan_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_gsm_network_register (MMModemGsmNetwork *self, + const char *network_id, + 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)->do_register) + MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->do_register (self, network_id, callback, user_data); + else + async_call_not_supported (self, callback, user_data); +} + +void +mm_modem_gsm_network_scan (MMModemGsmNetwork *self, + MMModemGsmNetworkScanFn 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)->scan) + MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->scan (self, callback, user_data); + else + scan_call_not_supported (self, callback, user_data); +} + +void +mm_modem_gsm_network_set_apn (MMModemGsmNetwork *self, + const char *apn, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self)); + g_return_if_fail (apn != NULL); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_apn) + MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_apn (self, apn, callback, user_data); + else + async_call_not_supported (self, callback, user_data); +} + +void +mm_modem_gsm_network_get_signal_quality (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_signal_quality) + MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_signal_quality (self, callback, user_data); + else + uint_call_not_supported (self, callback, user_data); +} + +void +mm_modem_gsm_network_set_band (MMModemGsmNetwork *self, + MMModemGsmBand band, + 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_band) + MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_band (self, band, callback, user_data); + else + async_call_not_supported (self, callback, user_data); +} + +void +mm_modem_gsm_network_get_band (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_band) + MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_band (self, callback, user_data); + else + uint_call_not_supported (self, callback, user_data); +} + +void +mm_modem_gsm_network_set_mode (MMModemGsmNetwork *self, + MMModemGsmMode 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); + 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) +{ + 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_registration_info) + MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_registration_info (self, callback, user_data); + else + reg_info_call_not_supported (self, callback, user_data); +} + +void +mm_modem_gsm_network_signal_quality (MMModemGsmNetwork *self, + guint32 quality) +{ + g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self)); + + g_signal_emit (self, signals[SIGNAL_QUALITY], 0, quality); +} + +void +mm_modem_gsm_network_registration_info (MMModemGsmNetwork *self, + MMModemGsmNetworkRegStatus status, + const char *oper_code, + const char *oper_name) +{ + g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self)); + + g_signal_emit (self, signals[REGISTRATION_INFO], 0, status, + oper_code ? oper_code : "", + 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 +impl_gsm_modem_register (MMModemGsmNetwork *modem, + const char *network_id, + DBusGMethodInvocation *context) +{ + const char *id; + + /* DBus does not support NULL strings, so the caller should pass an empty string + for manual registration. */ + if (strlen (network_id) < 1) + id = NULL; + else + id = network_id; + + mm_modem_gsm_network_register (modem, id, async_call_done, context); +} + +static void +impl_gsm_modem_scan (MMModemGsmNetwork *modem, + DBusGMethodInvocation *context) +{ + mm_modem_gsm_network_scan (modem, scan_call_done, context); +} + +static void +impl_gsm_modem_set_apn (MMModemGsmNetwork *modem, + const char *apn, + DBusGMethodInvocation *context) +{ + mm_modem_gsm_network_set_apn (modem, apn, async_call_done, context); +} + +static void +impl_gsm_modem_get_signal_quality (MMModemGsmNetwork *modem, + DBusGMethodInvocation *context) +{ + mm_modem_gsm_network_get_signal_quality (modem, uint_call_done, context); +} + +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 +impl_gsm_modem_set_band (MMModemGsmNetwork *modem, + MMModemGsmBand band, + DBusGMethodInvocation *context) +{ + if (!check_for_single_value (band)) { + GError *error; + + error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Invalid arguments (more than one value given)"); + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + mm_modem_gsm_network_set_band (modem, band, async_call_done, context); +} + +static void +impl_gsm_modem_get_band (MMModemGsmNetwork *modem, + DBusGMethodInvocation *context) +{ + mm_modem_gsm_network_get_band (modem, uint_call_done, context); +} + +static void +impl_gsm_modem_set_network_mode (MMModemGsmNetwork *modem, + MMModemGsmMode mode, + DBusGMethodInvocation *context) +{ + if (!check_for_single_value (mode)) { + GError *error; + + error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Invalid arguments (more than one value given)"); + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + mm_modem_gsm_network_set_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); +} + +static void +impl_gsm_modem_get_reg_info (MMModemGsmNetwork *modem, + DBusGMethodInvocation *context) +{ + mm_modem_gsm_network_get_registration_info (modem, reg_info_call_done, context); +} + +/*****************************************************************************/ + +static void +mm_modem_gsm_network_init (gpointer g_iface) +{ + GType iface_type = G_TYPE_FROM_INTERFACE (g_iface); + static gboolean initialized = FALSE; + + if (initialized) + return; + + /* Signals */ + signals[SIGNAL_QUALITY] = + g_signal_new ("signal-quality", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMModemGsmNetwork, signal_quality), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, + G_TYPE_UINT); + + signals[REGISTRATION_INFO] = + g_signal_new ("registration-info", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMModemGsmNetwork, registration_info), + NULL, NULL, + mm_marshal_VOID__UINT_STRING_STRING, + G_TYPE_NONE, 3, + G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING); + + signals[NETWORK_MODE] = + g_signal_new ("network-mode", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMModemGsmNetwork, network_mode), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, + G_TYPE_UINT); + + initialized = TRUE; +} + +GType +mm_modem_gsm_network_get_type (void) +{ + static GType network_type = 0; + + if (!G_UNLIKELY (network_type)) { + const GTypeInfo network_info = { + sizeof (MMModemGsmNetwork), /* class_size */ + mm_modem_gsm_network_init, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + network_type = g_type_register_static (G_TYPE_INTERFACE, + "MMModemGsmNetwork", + &network_info, 0); + + g_type_interface_add_prerequisite (network_type, G_TYPE_OBJECT); + dbus_g_object_type_install_info (network_type, &dbus_glib_mm_modem_gsm_network_object_info); + } + + return network_type; +} diff --git a/src/mm-modem-gsm-network.h b/src/mm-modem-gsm-network.h new file mode 100644 index 0000000..493baec --- /dev/null +++ b/src/mm-modem-gsm-network.h @@ -0,0 +1,164 @@ +/* -*- 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 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef MM_MODEM_GSM_NETWORK_H +#define MM_MODEM_GSM_NETWORK_H + +#include <mm-modem.h> +#include <mm-modem-gsm.h> + +#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)) + +typedef enum { + MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE = 0, + MM_MODEM_GSM_NETWORK_REG_STATUS_HOME = 1, + MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING = 2, + MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED = 3, + MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN = 4, + MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING = 5 +} MMModemGsmNetworkRegStatus; + +typedef struct _MMModemGsmNetwork MMModemGsmNetwork; + +typedef void (*MMModemGsmNetworkScanFn) (MMModemGsmNetwork *self, + GPtrArray *results, + GError *error, + gpointer user_data); + +typedef void (*MMModemGsmNetworkRegInfoFn) (MMModemGsmNetwork *self, + MMModemGsmNetworkRegStatus status, + const char *oper_code, + const char *oper_name, + GError *error, + gpointer user_data); + +struct _MMModemGsmNetwork { + GTypeInterface g_iface; + + /* Methods */ + /* 'register' is a reserved word */ + void (*do_register) (MMModemGsmNetwork *self, + const char *network_id, + MMModemFn callback, + gpointer user_data); + + void (*scan) (MMModemGsmNetwork *self, + MMModemGsmNetworkScanFn callback, + gpointer user_data); + + void (*set_apn) (MMModemGsmNetwork *self, + const char *apn, + MMModemFn callback, + gpointer user_data); + + void (*get_signal_quality) (MMModemGsmNetwork *self, + MMModemUIntFn callback, + gpointer user_data); + + void (*set_band) (MMModemGsmNetwork *self, + MMModemGsmBand band, + MMModemFn callback, + gpointer user_data); + + void (*get_band) (MMModemGsmNetwork *self, + MMModemUIntFn callback, + gpointer user_data); + + void (*set_network_mode) (MMModemGsmNetwork *self, + MMModemGsmMode 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); + + /* Signals */ + void (*signal_quality) (MMModemGsmNetwork *self, + guint32 quality); + + void (*registration_info) (MMModemGsmNetwork *self, + 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); + +void mm_modem_gsm_network_register (MMModemGsmNetwork *self, + const char *network_id, + MMModemFn callback, + gpointer user_data); + +void mm_modem_gsm_network_scan (MMModemGsmNetwork *self, + MMModemGsmNetworkScanFn callback, + gpointer user_data); + +void mm_modem_gsm_network_set_apn (MMModemGsmNetwork *self, + const char *apn, + MMModemFn callback, + gpointer user_data); + +void mm_modem_gsm_network_get_signal_quality (MMModemGsmNetwork *self, + MMModemUIntFn callback, + gpointer user_data); + +void mm_modem_gsm_network_set_band (MMModemGsmNetwork *self, + MMModemGsmBand band, + MMModemFn callback, + gpointer user_data); + +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); + +/* Protected */ + +void mm_modem_gsm_network_signal_quality (MMModemGsmNetwork *self, + guint32 quality); + +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); + +#endif /* MM_MODEM_GSM_NETWORK_H */ diff --git a/src/mm-modem-gsm-sms.c b/src/mm-modem-gsm-sms.c new file mode 100644 index 0000000..e8ec074 --- /dev/null +++ b/src/mm-modem-gsm-sms.c @@ -0,0 +1,320 @@ +/* -*- 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) 2009 Novell, Inc. + */ + +#include <string.h> +#include <dbus/dbus-glib.h> + +#include "mm-modem-gsm-sms.h" +#include "mm-errors.h" +#include "mm-callback-info.h" +#include "mm-marshal.h" + +static void impl_gsm_modem_sms_delete (MMModemGsmSms *modem, + guint idx, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_sms_get (MMModemGsmSms *modem, + guint idx, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_sms_get_format (MMModemGsmSms *modem, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_sms_set_format (MMModemGsmSms *modem, + guint format, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_sms_get_smsc (MMModemGsmSms *modem, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_sms_set_smsc (MMModemGsmSms *modem, + const char *smsc, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_sms_list (MMModemGsmSms *modem, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_sms_save (MMModemGsmSms *modem, + GHashTable *properties, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_sms_send (MMModemGsmSms *modem, + GHashTable *properties, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_sms_send_from_storage (MMModemGsmSms *modem, + guint idx, + DBusGMethodInvocation *context); + +static void impl_gsm_modem_sms_set_indication (MMModemGsmSms *modem, + guint mode, + guint mt, + guint bm, + guint ds, + guint bfr, + DBusGMethodInvocation *context); + +#include "mm-modem-gsm-sms-glue.h" + +enum { + SMS_RECEIVED, + COMPLETED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +/*****************************************************************************/ + +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 (MMModemGsmSms *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); +} + +/*****************************************************************************/ + +void +mm_modem_gsm_sms_send (MMModemGsmSms *self, + const char *number, + const char *text, + const char *smsc, + guint validity, + guint class, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_GSM_SMS (self)); + g_return_if_fail (number != NULL); + g_return_if_fail (text != NULL); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GSM_SMS_GET_INTERFACE (self)->send) + MM_MODEM_GSM_SMS_GET_INTERFACE (self)->send (self, number, text, smsc, validity, class, callback, user_data); + else + async_call_not_supported (self, callback, user_data); + +} + +/*****************************************************************************/ + +static void +impl_gsm_modem_sms_delete (MMModemGsmSms *modem, + guint idx, + DBusGMethodInvocation *context) +{ + async_call_not_supported (modem, async_call_done, context); +} + +static void +impl_gsm_modem_sms_get (MMModemGsmSms *modem, + guint idx, + DBusGMethodInvocation *context) +{ + async_call_not_supported (modem, async_call_done, context); +} + +static void +impl_gsm_modem_sms_get_format (MMModemGsmSms *modem, + DBusGMethodInvocation *context) +{ + async_call_not_supported (modem, async_call_done, context); +} + +static void +impl_gsm_modem_sms_set_format (MMModemGsmSms *modem, + guint format, + DBusGMethodInvocation *context) +{ + async_call_not_supported (modem, async_call_done, context); +} + +static void +impl_gsm_modem_sms_get_smsc (MMModemGsmSms *modem, + DBusGMethodInvocation *context) +{ + async_call_not_supported (modem, 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); +} + +static void +impl_gsm_modem_sms_list (MMModemGsmSms *modem, + DBusGMethodInvocation *context) +{ + async_call_not_supported (modem, async_call_done, context); +} + +static void +impl_gsm_modem_sms_save (MMModemGsmSms *modem, + GHashTable *properties, + DBusGMethodInvocation *context) +{ + async_call_not_supported (modem, async_call_done, context); +} + +static void +impl_gsm_modem_sms_send (MMModemGsmSms *modem, + GHashTable *properties, + DBusGMethodInvocation *context) +{ + 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"); + if (value) + number = g_value_get_string (value); + + value = (GValue *) g_hash_table_lookup (properties, "text"); + if (value) + text = g_value_get_string (value); + + value = (GValue *) g_hash_table_lookup (properties, "smsc"); + if (value) + smsc = g_value_get_string (value); + + value = (GValue *) g_hash_table_lookup (properties, "validity"); + if (value) + validity = g_value_get_uint (value); + + value = (GValue *) g_hash_table_lookup (properties, "class"); + if (value) + class = g_value_get_uint (value); + + if (!number) + error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Missing number"); + else if (!text) + error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Missing message text"); + + if (error) { + async_call_done (MM_MODEM (modem), error, context); + g_error_free (error); + } else + mm_modem_gsm_sms_send (modem, number, text, smsc, validity, class, async_call_done, context); +} + +static void +impl_gsm_modem_sms_send_from_storage (MMModemGsmSms *modem, + guint idx, + DBusGMethodInvocation *context) +{ + async_call_not_supported (modem, async_call_done, context); +} + +static void +impl_gsm_modem_sms_set_indication (MMModemGsmSms *modem, + guint mode, + guint mt, + guint bm, + guint ds, + guint bfr, + DBusGMethodInvocation *context) +{ + async_call_not_supported (modem, async_call_done, context); +} + +/*****************************************************************************/ + +static void +mm_modem_gsm_sms_init (gpointer g_iface) +{ + GType iface_type = G_TYPE_FROM_INTERFACE (g_iface); + static gboolean initialized = FALSE; + + if (initialized) + return; + + /* Signals */ + signals[SMS_RECEIVED] = + g_signal_new ("sms-received", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMModemGsmSms, sms_received), + NULL, NULL, + mm_marshal_VOID__UINT_BOOLEAN, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_BOOLEAN); + + signals[COMPLETED] = + g_signal_new ("completed", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMModemGsmSms, completed), + NULL, NULL, + mm_marshal_VOID__UINT_BOOLEAN, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_BOOLEAN); + + initialized = TRUE; +} + +GType +mm_modem_gsm_sms_get_type (void) +{ + static GType sms_type = 0; + + if (!G_UNLIKELY (sms_type)) { + const GTypeInfo sms_info = { + sizeof (MMModemGsmSms), /* class_size */ + mm_modem_gsm_sms_init, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + sms_type = g_type_register_static (G_TYPE_INTERFACE, + "MMModemGsmSms", + &sms_info, 0); + + g_type_interface_add_prerequisite (sms_type, G_TYPE_OBJECT); + dbus_g_object_type_install_info (sms_type, &dbus_glib_mm_modem_gsm_sms_object_info); + } + + return sms_type; +} diff --git a/src/mm-modem-gsm-sms.h b/src/mm-modem-gsm-sms.h new file mode 100644 index 0000000..79a5bb0 --- /dev/null +++ b/src/mm-modem-gsm-sms.h @@ -0,0 +1,62 @@ +/* -*- 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) 2009 Novell, Inc. + */ + +#ifndef MM_MODEM_GSM_SMS_H +#define MM_MODEM_GSM_SMS_H + +#include <mm-modem.h> + +#define MM_TYPE_MODEM_GSM_SMS (mm_modem_gsm_sms_get_type ()) +#define MM_MODEM_GSM_SMS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_GSM_SMS, MMModemGsmSms)) +#define MM_IS_MODEM_GSM_SMS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_GSM_SMS)) +#define MM_MODEM_GSM_SMS_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_GSM_SMS, MMModemGsmSms)) + +typedef struct _MMModemGsmSms MMModemGsmSms; + +struct _MMModemGsmSms { + GTypeInterface g_iface; + + /* Methods */ + void (*send) (MMModemGsmSms *modem, + const char *number, + const char *text, + const char *smsc, + guint validity, + guint class, + MMModemFn callback, + gpointer user_data); + + /* Signals */ + void (*sms_received) (MMModemGsmSms *self, + guint32 index, + gboolean completed); + + void (*completed) (MMModemGsmSms *self, + guint32 index, + gboolean completed); +}; + +GType mm_modem_gsm_sms_get_type (void); + +void mm_modem_gsm_sms_send (MMModemGsmSms *self, + const char *number, + const char *text, + const char *smsc, + guint validity, + guint class, + MMModemFn callback, + gpointer user_data); + +#endif /* MM_MODEM_GSM_SMS_H */ diff --git a/src/mm-modem-gsm.h b/src/mm-modem-gsm.h new file mode 100644 index 0000000..852ff85 --- /dev/null +++ b/src/mm-modem-gsm.h @@ -0,0 +1,57 @@ +/* -*- 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. + */ + +#ifndef MM_MODEM_GSM_H +#define MM_MODEM_GSM_H + +typedef enum { + MM_MODEM_GSM_MODE_UNKNOWN = 0x00000000, + MM_MODEM_GSM_MODE_ANY = 0x00000001, + MM_MODEM_GSM_MODE_GPRS = 0x00000002, + MM_MODEM_GSM_MODE_EDGE = 0x00000004, + MM_MODEM_GSM_MODE_UMTS = 0x00000008, + MM_MODEM_GSM_MODE_HSDPA = 0x00000010, + MM_MODEM_GSM_MODE_2G_PREFERRED = 0x00000020, + MM_MODEM_GSM_MODE_3G_PREFERRED = 0x00000040, + MM_MODEM_GSM_MODE_2G_ONLY = 0x00000080, + 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 +} MMModemGsmMode; + +typedef enum { + MM_MODEM_GSM_BAND_UNKNOWN = 0x00000000, + MM_MODEM_GSM_BAND_ANY = 0x00000001, + MM_MODEM_GSM_BAND_EGSM = 0x00000002, /* 900 MHz */ + MM_MODEM_GSM_BAND_DCS = 0x00000004, /* 1800 MHz */ + MM_MODEM_GSM_BAND_PCS = 0x00000008, /* 1900 MHz */ + MM_MODEM_GSM_BAND_G850 = 0x00000010, /* 850 MHz */ + MM_MODEM_GSM_BAND_U2100 = 0x00000020, /* WCDMA 3GPP UMTS 2100 MHz (Class I) */ + MM_MODEM_GSM_BAND_U1800 = 0x00000040, /* WCDMA 3GPP UMTS 1800 MHz (Class III) */ + MM_MODEM_GSM_BAND_U17IV = 0x00000080, /* WCDMA 3GPP AWS 1700/2100 MHz (Class IV) */ + MM_MODEM_GSM_BAND_U800 = 0x00000100, /* WCDMA 3GPP UMTS 800 MHz (Class VI) */ + 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_LAST = MM_MODEM_GSM_BAND_U17IX +} MMModemGsmBand; + + +#endif /* MM_MODEM_GSM_H */ + diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c new file mode 100644 index 0000000..1741b5f --- /dev/null +++ b/src/mm-modem-helpers.c @@ -0,0 +1,203 @@ +/* -*- 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 <glib.h> +#include <string.h> +#include <ctype.h> + +#include "mm-errors.h" +#include "mm-modem-helpers.h" + +static void +save_scan_value (GHashTable *hash, const char *key, GMatchInfo *info, guint32 num) +{ + char *quoted; + size_t len; + + g_return_if_fail (info != NULL); + + quoted = g_match_info_fetch (info, num); + if (!quoted) + return; + + len = strlen (quoted); + + /* Unquote the item if needed */ + if ((len >= 2) && (quoted[0] == '"') && (quoted[len - 1] == '"')) { + quoted[0] = ' '; + quoted[len - 1] = ' '; + quoted = g_strstrip (quoted); + } + + if (!strlen (quoted)) { + g_free (quoted); + return; + } + + g_hash_table_insert (hash, g_strdup (key), quoted); +} + +/* If the response was successfully parsed (even if no valid entries were + * found) the pointer array will be returned. + */ +GPtrArray * +mm_gsm_parse_scan_response (const char *reply, GError **error) +{ + /* Got valid reply */ + GPtrArray *results = NULL; + GRegex *r; + GMatchInfo *match_info; + GError *err = NULL; + gboolean umts_format = TRUE; + + g_return_val_if_fail (reply != NULL, NULL); + if (error) + g_return_val_if_fail (*error == NULL, NULL); + + if (!strstr (reply, "+COPS: ")) { + g_set_error_literal (error, + MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Could not parse scan results."); + return NULL; + } + + reply = strstr (reply, "+COPS: ") + 7; + + /* Cell access technology (GSM, UTRAN, etc) got added later and not all + * modems implement it. Some modesm have quirks that make it hard to + * use one regular experession for matching both pre-UMTS and UMTS + * responses. So try UMTS-format first and fall back to pre-UMTS if + * we get no UMTS-formst matches. + */ + + /* Quirk: Sony-Ericsson TM-506 sometimes includes a stray ')' like so, + * which is what makes it hard to match both pre-UMTS and UMTS in + * the same regex: + * + * +COPS: (2,"","T-Mobile","31026",0),(1,"AT&T","AT&T","310410"),0) + */ + + r = g_regex_new ("\\((\\d),([^,\\)]*),([^,\\)]*),([^,\\)]*)[\\)]?,(\\d)\\)", G_REGEX_UNGREEDY, 0, NULL); + if (err) { + g_error ("Invalid regular expression: %s", err->message); + g_error_free (err); + g_set_error_literal (error, + MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Could not parse scan results."); + return NULL; + } + + /* If we didn't get any hits, try the pre-UMTS format match */ + if (!g_regex_match (r, reply, 0, &match_info)) { + g_regex_unref (r); + if (match_info) { + g_match_info_free (match_info); + match_info = NULL; + } + + /* Pre-UMTS format doesn't include the cell access technology after + * the numeric operator element. + * + * Ex: Motorola C-series (BUSlink SCWi275u) like so: + * + * +COPS: (2,"T-Mobile","","310260"),(0,"Cingular Wireless","","310410") + */ + + /* Quirk: Some Nokia phones (N80) don't send the quotes for empty values: + * + * +COPS: (2,"T - Mobile",,"31026"),(1,"Einstein PCS",,"31064"),(1,"Cingular",,"31041"),,(0,1,3),(0,2) + */ + + r = g_regex_new ("\\((\\d),([^,\\)]*),([^,\\)]*),([^\\)]*)\\)", G_REGEX_UNGREEDY, 0, NULL); + if (err) { + g_error ("Invalid regular expression: %s", err->message); + g_error_free (err); + g_set_error_literal (error, + MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Could not parse scan results."); + return NULL; + } + + g_regex_match (r, reply, 0, &match_info); + umts_format = FALSE; + } + + /* Parse the results */ + results = g_ptr_array_new (); + while (g_match_info_matches (match_info)) { + GHashTable *hash; + char *access_tech = NULL; + const char *tmp; + gboolean valid = FALSE; + + hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + save_scan_value (hash, MM_SCAN_TAG_STATUS, match_info, 1); + save_scan_value (hash, MM_SCAN_TAG_OPER_LONG, match_info, 2); + save_scan_value (hash, MM_SCAN_TAG_OPER_SHORT, match_info, 3); + save_scan_value (hash, MM_SCAN_TAG_OPER_NUM, match_info, 4); + + /* Only try for access technology with UMTS-format matches */ + if (umts_format) + access_tech = g_match_info_fetch (match_info, 5); + if (access_tech && (strlen (access_tech) == 1)) { + /* Recognized access technologies are between '0' and '6' inclusive... */ + if ((access_tech[0] >= '0') && (access_tech[0] <= '6')) + g_hash_table_insert (hash, g_strdup (MM_SCAN_TAG_ACCESS_TECH), access_tech); + } else + g_free (access_tech); + + /* If the operator number isn't valid (ie, at least 5 digits), + * ignore the scan result; it's probably the parameter stuff at the + * end of the +COPS response. The regex will sometimes catch this + * but there's no good way to ignore it. + */ + tmp = g_hash_table_lookup (hash, MM_SCAN_TAG_OPER_NUM); + if (tmp && (strlen (tmp) >= 5)) { + valid = TRUE; + while (*tmp) { + if (!isdigit (*tmp) && (*tmp != '-')) { + valid = FALSE; + break; + } + tmp++; + } + + if (valid) + g_ptr_array_add (results, hash); + } + + if (!valid) + g_hash_table_destroy (hash); + + g_match_info_next (match_info, NULL); + } + + g_match_info_free (match_info); + g_regex_unref (r); + + return results; +} + +void +mm_gsm_destroy_scan_data (gpointer data) +{ + GPtrArray *results = (GPtrArray *) data; + + g_ptr_array_foreach (results, (GFunc) g_hash_table_destroy, NULL); + g_ptr_array_free (results, TRUE); +} + diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h new file mode 100644 index 0000000..ddc9cbc --- /dev/null +++ b/src/mm-modem-helpers.h @@ -0,0 +1,31 @@ +/* -*- 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_MODEM_HELPERS_H +#define MM_MODEM_HELPERS_H + +#define MM_SCAN_TAG_STATUS "status" +#define MM_SCAN_TAG_OPER_LONG "operator-long" +#define MM_SCAN_TAG_OPER_SHORT "operator-short" +#define MM_SCAN_TAG_OPER_NUM "operator-num" +#define MM_SCAN_TAG_ACCESS_TECH "access-tech" + +GPtrArray *mm_gsm_parse_scan_response (const char *reply, GError **error); + +void mm_gsm_destroy_scan_data (gpointer data); + +#endif /* MM_MODEM_HELPERS_H */ + diff --git a/src/mm-modem-simple.c b/src/mm-modem-simple.c new file mode 100644 index 0000000..415fd44 --- /dev/null +++ b/src/mm-modem-simple.c @@ -0,0 +1,156 @@ +/* -*- 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) 2009 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#include <dbus/dbus-glib.h> + +#include "mm-modem-simple.h" +#include "mm-errors.h" +#include "mm-callback-info.h" + +static void impl_modem_simple_connect (MMModemSimple *self, GHashTable *properties, DBusGMethodInvocation *context); +static void impl_modem_simple_get_status (MMModemSimple *self, DBusGMethodInvocation *context); + +#include "mm-modem-simple-glue.h" + +void +mm_modem_simple_connect (MMModemSimple *self, + GHashTable *properties, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_SIMPLE (self)); + g_return_if_fail (properties != NULL); + + if (MM_MODEM_SIMPLE_GET_INTERFACE (self)->connect) + MM_MODEM_SIMPLE_GET_INTERFACE (self)->connect (self, properties, callback, user_data); + else { + 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); + } +} + +static void +simple_get_status_invoke (MMCallbackInfo *info) +{ + MMModemSimpleGetStatusFn callback = (MMModemSimpleGetStatusFn) info->callback; + + callback (MM_MODEM_SIMPLE (info->modem), NULL, info->error, info->user_data); +} + +void +mm_modem_simple_get_status (MMModemSimple *self, + MMModemSimpleGetStatusFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_SIMPLE (self)); + + if (MM_MODEM_SIMPLE_GET_INTERFACE (self)->get_status) + MM_MODEM_SIMPLE_GET_INTERFACE (self)->get_status (self, callback, user_data); + else { + MMCallbackInfo *info; + + info = mm_callback_info_new_full (MM_MODEM (self), + simple_get_status_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); + } +} + +/*****************************************************************************/ + +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 +impl_modem_simple_connect (MMModemSimple *self, + GHashTable *properties, + DBusGMethodInvocation *context) +{ + mm_modem_simple_connect (self, properties, async_call_done, context); +} + +static void +get_status_done (MMModemSimple *modem, + GHashTable *properties, + 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, properties); +} + +static void +impl_modem_simple_get_status (MMModemSimple *self, + DBusGMethodInvocation *context) +{ + mm_modem_simple_get_status (self, get_status_done, context); +} + +/*****************************************************************************/ + +static void +mm_modem_simple_init (gpointer g_iface) +{ +} + +GType +mm_modem_simple_get_type (void) +{ + static GType modem_type = 0; + + if (!G_UNLIKELY (modem_type)) { + const GTypeInfo modem_info = { + sizeof (MMModemSimple), /* class_size */ + mm_modem_simple_init, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + modem_type = g_type_register_static (G_TYPE_INTERFACE, + "MMModemSimple", + &modem_info, 0); + + g_type_interface_add_prerequisite (modem_type, G_TYPE_OBJECT); + dbus_g_object_type_install_info (modem_type, &dbus_glib_mm_modem_simple_object_info); + } + + return modem_type; +} diff --git a/src/mm-modem-simple.h b/src/mm-modem-simple.h new file mode 100644 index 0000000..03b5561 --- /dev/null +++ b/src/mm-modem-simple.h @@ -0,0 +1,59 @@ +/* -*- 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) 2009 Novell, Inc. + */ + +#ifndef MM_MODEM_SIMPLE_H +#define MM_MODEM_SIMPLE_H + +#include <glib-object.h> +#include <mm-modem.h> + +#define MM_TYPE_MODEM_SIMPLE (mm_modem_simple_get_type ()) +#define MM_MODEM_SIMPLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_SIMPLE, MMModemSimple)) +#define MM_IS_MODEM_SIMPLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_SIMPLE)) +#define MM_MODEM_SIMPLE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_SIMPLE, MMModemSimple)) + +typedef struct _MMModemSimple MMModemSimple; + +typedef void (*MMModemSimpleGetStatusFn) (MMModemSimple *modem, + GHashTable *properties, + GError *error, + gpointer user_data); + +struct _MMModemSimple { + GTypeInterface g_iface; + + /* Methods */ + void (*connect) (MMModemSimple *self, + GHashTable *properties, + MMModemFn callback, + gpointer user_data); + + void (*get_status) (MMModemSimple *self, + MMModemSimpleGetStatusFn callback, + gpointer user_data); +}; + +GType mm_modem_simple_get_type (void); + +void mm_modem_simple_connect (MMModemSimple *self, + GHashTable *properties, + MMModemFn callback, + gpointer user_data); + +void mm_modem_simple_get_status (MMModemSimple *self, + MMModemSimpleGetStatusFn callback, + gpointer user_data); + +#endif /* MM_MODEM_SIMPLE_H */ diff --git a/src/mm-modem.c b/src/mm-modem.c new file mode 100644 index 0000000..a65d883 --- /dev/null +++ b/src/mm-modem.c @@ -0,0 +1,738 @@ +/* -*- 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 <string.h> +#include <dbus/dbus-glib.h> +#include "mm-modem.h" +#include "mm-errors.h" +#include "mm-callback-info.h" +#include "mm-marshal.h" + +static void impl_modem_enable (MMModem *modem, gboolean enable, DBusGMethodInvocation *context); +static void impl_modem_connect (MMModem *modem, const char *number, DBusGMethodInvocation *context); +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); + +#include "mm-modem-glue.h" + +/* Should be used from callbacks to check whether the modem was removed after + * the callback's operation was started, but before the callback itself was + * called, in which case the MMModem passed to the callback is NULL. + */ +GError * +mm_modem_check_removed (MMModem *self, const GError *error) +{ + if (!self) { + /* If the modem was NULL, the error *should* have been + * MM_MODEM_ERROR_REMOVED. If it wasn't, make it that. + */ + return g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_REMOVED, + "The modem was removed."); + } + + return error ? g_error_copy (error) : NULL; +} + +static void +async_op_not_supported (MMModem *self, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new (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; + + if (error) + dbus_g_method_return_error (context, error); + else + dbus_g_method_return (context); +} + +void +mm_modem_enable (MMModem *self, + MMModemFn callback, + gpointer user_data) +{ + MMModemState state; + + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + state = mm_modem_get_state (self); + if (state >= MM_MODEM_STATE_ENABLED) { + MMCallbackInfo *info; + + info = mm_callback_info_new (self, callback, user_data); + + if (state == MM_MODEM_STATE_ENABLING) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_OPERATION_IN_PROGRESS, + "The device is already being enabled."); + } else { + /* Already enabled */ + } + + mm_callback_info_schedule (info); + return; + } + + if (MM_MODEM_GET_INTERFACE (self)->enable) + MM_MODEM_GET_INTERFACE (self)->enable (self, callback, user_data); + else + async_op_not_supported (self, callback, user_data); +} + +static void +finish_disable (MMModem *self, + MMModemFn callback, + gpointer user_data) +{ + if (MM_MODEM_GET_INTERFACE (self)->disable) + MM_MODEM_GET_INTERFACE (self)->disable (self, callback, user_data); + else + async_op_not_supported (self, callback, user_data); +} + +typedef struct { + MMModemFn callback; + gpointer user_data; +} DisableDisconnectInfo; + +static void +disable_disconnect_done (MMModem *self, + GError *error, + gpointer user_data) +{ + DisableDisconnectInfo *cb_data = user_data; + GError *tmp_error = NULL; + + /* Check for modem removal */ + if (g_error_matches (error, MM_MODEM_ERROR, MM_MODEM_ERROR_REMOVED)) + tmp_error = g_error_copy (error); + else if (!self) { + tmp_error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_REMOVED, + "The modem was removed."); + } + + /* And send an immediate error reply if the modem was removed */ + if (tmp_error) { + cb_data->callback (NULL, tmp_error, cb_data->user_data); + g_free (cb_data); + g_error_free (tmp_error); + return; + } + + if (error) { + /* 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), + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + } + finish_disable (self, cb_data->callback, cb_data->user_data); + g_free (cb_data); +} + +void +mm_modem_disable (MMModem *self, + MMModemFn callback, + gpointer user_data) +{ + MMModemState state; + + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + state = mm_modem_get_state (self); + if (state <= MM_MODEM_STATE_DISABLING) { + MMCallbackInfo *info; + + info = mm_callback_info_new (self, callback, user_data); + + if (state == MM_MODEM_STATE_DISABLING) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_OPERATION_IN_PROGRESS, + "The device is already being disabled."); + } else { + /* Already disabled */ + } + + mm_callback_info_schedule (info); + return; + } + + /* If the modem is connected, disconnect it */ + if (state >= MM_MODEM_STATE_CONNECTING) { + DisableDisconnectInfo *cb_data; + + cb_data = g_malloc0 (sizeof (DisableDisconnectInfo)); + cb_data->callback = callback; + cb_data->user_data = user_data; + mm_modem_disconnect (self, disable_disconnect_done, cb_data); + } else + finish_disable (self, callback, user_data); +} + +static void +impl_modem_enable (MMModem *modem, + gboolean enable, + DBusGMethodInvocation *context) +{ + if (enable) + mm_modem_enable (modem, async_call_done, context); + else + mm_modem_disable (modem, async_call_done, context); +} + +void +mm_modem_connect (MMModem *self, + const char *number, + MMModemFn callback, + gpointer user_data) +{ + MMModemState state; + + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + g_return_if_fail (number != NULL); + + state = mm_modem_get_state (self); + if (state >= MM_MODEM_STATE_CONNECTING) { + MMCallbackInfo *info; + + /* Already connecting */ + info = mm_callback_info_new (self, callback, user_data); + if (state == MM_MODEM_STATE_CONNECTING) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_OPERATION_IN_PROGRESS, + "The device is already being connected."); + } else { + /* already connected */ + } + + mm_callback_info_schedule (info); + return; + } + + if (MM_MODEM_GET_INTERFACE (self)->connect) + MM_MODEM_GET_INTERFACE (self)->connect (self, number, callback, user_data); + else + async_op_not_supported (self, callback, user_data); +} + +static void +impl_modem_connect (MMModem *modem, + const char *number, + DBusGMethodInvocation *context) +{ + mm_modem_connect (modem, number, async_call_done, context); +} + +static void +get_ip4_invoke (MMCallbackInfo *info) +{ + MMModemIp4Fn callback = (MMModemIp4Fn) info->callback; + + callback (info->modem, + GPOINTER_TO_UINT (mm_callback_info_get_data (info, "ip4-address")), + (GArray *) mm_callback_info_get_data (info, "ip4-dns"), + info->error, info->user_data); +} + +void +mm_modem_get_ip4_config (MMModem *self, + MMModemIp4Fn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->get_ip4_config) + MM_MODEM_GET_INTERFACE (self)->get_ip4_config (self, callback, user_data); + else { + MMCallbackInfo *info; + + info = mm_callback_info_new_full (self, + get_ip4_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); + } +} + +static void +value_array_add_uint (GValueArray *array, guint32 i) +{ + GValue value = { 0, }; + + g_value_init (&value, G_TYPE_UINT); + g_value_set_uint (&value, i); + g_value_array_append (array, &value); + g_value_unset (&value); +} + +static void +get_ip4_done (MMModem *modem, + guint32 address, + GArray *dns, + GError *error, + gpointer user_data) +{ + DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data; + + if (error) + dbus_g_method_return_error (context, error); + else { + GValueArray *array; + guint32 dns1 = 0; + guint32 dns2 = 0; + guint32 dns3 = 0; + + array = g_value_array_new (4); + + if (dns) { + if (dns->len > 0) + + dns1 = g_array_index (dns, guint32, 0); + if (dns->len > 1) + dns2 = g_array_index (dns, guint32, 1); + if (dns->len > 2) + dns3 = g_array_index (dns, guint32, 2); + } + + value_array_add_uint (array, address); + value_array_add_uint (array, dns1); + value_array_add_uint (array, dns2); + value_array_add_uint (array, dns3); + + dbus_g_method_return (context, array); + } +} + +static void +impl_modem_get_ip4_config (MMModem *modem, + DBusGMethodInvocation *context) +{ + mm_modem_get_ip4_config (modem, get_ip4_done, context); +} + +void +mm_modem_disconnect (MMModem *self, + MMModemFn callback, + gpointer user_data) +{ + MMModemState state; + + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + state = mm_modem_get_state (self); + if (state <= MM_MODEM_STATE_DISCONNECTING) { + MMCallbackInfo *info; + + /* Already connecting */ + info = mm_callback_info_new (self, callback, user_data); + if (state == MM_MODEM_STATE_DISCONNECTING) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_OPERATION_IN_PROGRESS, + "The device is already being disconnected."); + } else { + /* already disconnected */ + } + + mm_callback_info_schedule (info); + return; + } + + if (MM_MODEM_GET_INTERFACE (self)->disconnect) + MM_MODEM_GET_INTERFACE (self)->disconnect (self, callback, user_data); + else + async_op_not_supported (self, callback, user_data); +} + +static void +impl_modem_disconnect (MMModem *modem, + DBusGMethodInvocation *context) +{ + mm_modem_disconnect (modem, async_call_done, context); +} + +static void +info_call_done (MMModem *self, + const char *manufacturer, + const char *model, + const char *version, + GError *error, + gpointer user_data) +{ + DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data; + + if (error) + dbus_g_method_return_error (context, error); + else { + GValueArray *array; + GValue value = { 0, }; + + array = g_value_array_new (3); + + /* Manufacturer */ + g_value_init (&value, G_TYPE_STRING); + g_value_set_string (&value, manufacturer); + g_value_array_append (array, &value); + g_value_unset (&value); + + /* Model */ + g_value_init (&value, G_TYPE_STRING); + g_value_set_string (&value, model); + g_value_array_append (array, &value); + g_value_unset (&value); + + /* Version */ + g_value_init (&value, G_TYPE_STRING); + g_value_set_string (&value, version); + g_value_array_append (array, &value); + g_value_unset (&value); + + dbus_g_method_return (context, array); + } +} + +static void +info_invoke (MMCallbackInfo *info) +{ + MMModemInfoFn callback = (MMModemInfoFn) info->callback; + + callback (info->modem, NULL, NULL, NULL, info->error, info->user_data); +} + +static void +info_call_not_supported (MMModem *self, + MMModemInfoFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new_full (MM_MODEM (self), info_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_get_info (MMModem *self, + MMModemInfoFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->get_info) + MM_MODEM_GET_INTERFACE (self)->get_info (self, callback, user_data); + else + info_call_not_supported (self, callback, user_data); +} + +static void +impl_modem_get_info (MMModem *modem, + DBusGMethodInvocation *context) +{ + mm_modem_get_info (modem, info_call_done, context); +} + +/*****************************************************************************/ + +gboolean +mm_modem_owns_port (MMModem *self, + const char *subsys, + const char *name) +{ + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (MM_IS_MODEM (self), FALSE); + g_return_val_if_fail (subsys, FALSE); + g_return_val_if_fail (name, FALSE); + + g_assert (MM_MODEM_GET_INTERFACE (self)->owns_port); + return MM_MODEM_GET_INTERFACE (self)->owns_port (self, subsys, name); +} + +gboolean +mm_modem_grab_port (MMModem *self, + const char *subsys, + const char *name, + MMPortType suggested_type, + gpointer user_data, + 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 (subsys, FALSE); + g_return_val_if_fail (name, FALSE); + + g_assert (MM_MODEM_GET_INTERFACE (self)->grab_port); + return MM_MODEM_GET_INTERFACE (self)->grab_port (self, subsys, name, suggested_type, user_data, error); +} + +void +mm_modem_release_port (MMModem *self, + const char *subsys, + const char *name) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (subsys); + g_return_if_fail (name); + + g_assert (MM_MODEM_GET_INTERFACE (self)->release_port); + MM_MODEM_GET_INTERFACE (self)->release_port (self, subsys, name); +} + +gboolean +mm_modem_get_valid (MMModem *self) +{ + gboolean valid = FALSE; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (MM_IS_MODEM (self), FALSE); + + g_object_get (G_OBJECT (self), MM_MODEM_VALID, &valid, NULL); + return valid; +} + +char * +mm_modem_get_device (MMModem *self) +{ + char *device; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_MODEM (self), NULL); + + g_object_get (G_OBJECT (self), MM_MODEM_MASTER_DEVICE, &device, NULL); + return device; +} + +MMModemState +mm_modem_get_state (MMModem *self) +{ + MMModemState state = MM_MODEM_STATE_UNKNOWN; + + g_object_get (G_OBJECT (self), MM_MODEM_STATE, &state, NULL); + return state; +} + +static const char * +state_to_string (MMModemState state) +{ + switch (state) { + case MM_MODEM_STATE_UNKNOWN: + return "unknown"; + case MM_MODEM_STATE_DISABLED: + return "disabled"; + case MM_MODEM_STATE_DISABLING: + return "disabling"; + case MM_MODEM_STATE_ENABLING: + return "enabling"; + case MM_MODEM_STATE_ENABLED: + return "enabled"; + case MM_MODEM_STATE_SEARCHING: + return "searching"; + case MM_MODEM_STATE_REGISTERED: + return "registered"; + case MM_MODEM_STATE_DISCONNECTING: + return "disconnecting"; + case MM_MODEM_STATE_CONNECTING: + return "connecting"; + case MM_MODEM_STATE_CONNECTED: + return "connected"; + default: + g_assert_not_reached (); + break; + } + + g_assert_not_reached (); + return "(invalid)"; +} + +void +mm_modem_set_state (MMModem *self, + MMModemState new_state, + MMModemStateReason reason) +{ + MMModemState old_state = MM_MODEM_STATE_UNKNOWN; + const char *dbus_path; + + g_object_get (G_OBJECT (self), MM_MODEM_STATE, &old_state, NULL); + + if (new_state != old_state) { + g_object_set (G_OBJECT (self), MM_MODEM_STATE, new_state, NULL); + g_signal_emit_by_name (G_OBJECT (self), "state-changed", new_state, old_state, reason); + + 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)); + } + } +} + +/*****************************************************************************/ + +static void +mm_modem_init (gpointer g_iface) +{ + GType iface_type = G_TYPE_FROM_INTERFACE (g_iface); + static gboolean initialized = FALSE; + + if (initialized) + return; + + /* Properties */ + g_object_interface_install_property + (g_iface, + g_param_spec_string (MM_MODEM_DATA_DEVICE, + "Device", + "Data device", + NULL, + G_PARAM_READWRITE)); + + g_object_interface_install_property + (g_iface, + g_param_spec_string (MM_MODEM_MASTER_DEVICE, + "MasterDevice", + "Master modem parent device of all the modem's ports", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_string (MM_MODEM_DRIVER, + "Driver", + "Driver", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_string (MM_MODEM_PLUGIN, + "Plugin", + "Plugin name", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_uint (MM_MODEM_TYPE, + "Type", + "Type", + 0, G_MAXUINT32, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_uint (MM_MODEM_IP_METHOD, + "IP method", + "IP configuration method", + MM_MODEM_IP_METHOD_PPP, + MM_MODEM_IP_METHOD_DHCP, + MM_MODEM_IP_METHOD_PPP, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_boolean (MM_MODEM_VALID, + "Valid", + "Modem is valid", + FALSE, + G_PARAM_READABLE)); + + g_object_interface_install_property + (g_iface, + g_param_spec_uint (MM_MODEM_STATE, + "State", + "State", + MM_MODEM_STATE_UNKNOWN, + MM_MODEM_STATE_LAST, + MM_MODEM_STATE_UNKNOWN, + G_PARAM_READWRITE)); + + g_object_interface_install_property + (g_iface, + g_param_spec_boolean (MM_MODEM_ENABLED, + "Enabled", + "Modem is enabled", + FALSE, + G_PARAM_READABLE)); + + /* Signals */ + g_signal_new ("state-changed", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMModem, state_changed), + NULL, NULL, + mm_marshal_VOID__UINT_UINT_UINT, + G_TYPE_NONE, 3, + G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT); + + initialized = TRUE; +} + +GType +mm_modem_get_type (void) +{ + static GType modem_type = 0; + + if (!G_UNLIKELY (modem_type)) { + const GTypeInfo modem_info = { + sizeof (MMModem), /* class_size */ + mm_modem_init, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + modem_type = g_type_register_static (G_TYPE_INTERFACE, + "MMModem", + &modem_info, 0); + + g_type_interface_add_prerequisite (modem_type, G_TYPE_OBJECT); + + dbus_g_object_type_install_info (modem_type, &dbus_glib_mm_modem_object_info); + } + + return modem_type; +} diff --git a/src/mm-modem.h b/src/mm-modem.h new file mode 100644 index 0000000..e8dd7ea --- /dev/null +++ b/src/mm-modem.h @@ -0,0 +1,219 @@ +/* -*- 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. + */ + +#ifndef MM_MODEM_H +#define MM_MODEM_H + +#include <glib-object.h> + +#include "mm-port.h" + +typedef enum { + MM_MODEM_STATE_UNKNOWN = 0, + MM_MODEM_STATE_DISABLED = 10, + MM_MODEM_STATE_DISABLING = 20, + MM_MODEM_STATE_ENABLING = 30, + MM_MODEM_STATE_ENABLED = 40, + MM_MODEM_STATE_SEARCHING = 50, + MM_MODEM_STATE_REGISTERED = 60, + MM_MODEM_STATE_DISCONNECTING = 70, + MM_MODEM_STATE_CONNECTING = 80, + MM_MODEM_STATE_CONNECTED = 90, + + MM_MODEM_STATE_LAST = MM_MODEM_STATE_CONNECTED +} MMModemState; + +typedef enum { + MM_MODEM_STATE_REASON_NONE = 0 +} MMModemStateReason; + +#define DBUS_PATH_TAG "dbus-path" + +#define MM_TYPE_MODEM (mm_modem_get_type ()) +#define MM_MODEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM, MMModem)) +#define MM_IS_MODEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM)) +#define MM_MODEM_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM, MMModem)) + +#define MM_MODEM_DBUS_INTERFACE "org.freedesktop.ModemManager.Modem" + +#define MM_MODEM_DATA_DEVICE "device" +#define MM_MODEM_MASTER_DEVICE "master-device" +#define MM_MODEM_DRIVER "driver" +#define MM_MODEM_TYPE "type" +#define MM_MODEM_IP_METHOD "ip-method" +#define MM_MODEM_ENABLED "enabled" +#define MM_MODEM_VALID "valid" /* not exported */ +#define MM_MODEM_PLUGIN "plugin" /* not exported */ +#define MM_MODEM_STATE "state" /* not exported */ + +#define MM_MODEM_TYPE_UNKNOWN 0 +#define MM_MODEM_TYPE_GSM 1 +#define MM_MODEM_TYPE_CDMA 2 + +#define MM_MODEM_IP_METHOD_PPP 0 +#define MM_MODEM_IP_METHOD_STATIC 1 +#define MM_MODEM_IP_METHOD_DHCP 2 + +typedef enum { + MM_MODEM_PROP_FIRST = 0x1000, + + MM_MODEM_PROP_DATA_DEVICE = MM_MODEM_PROP_FIRST, + MM_MODEM_PROP_MASTER_DEVICE, + MM_MODEM_PROP_DRIVER, + MM_MODEM_PROP_TYPE, + MM_MODEM_PROP_IP_METHOD, + MM_MODEM_PROP_VALID, /* Not exported */ + MM_MODEM_PROP_PLUGIN, /* Not exported */ + MM_MODEM_PROP_STATE, /* Not exported */ + MM_MODEM_PROP_ENABLED +} MMModemProp; + +typedef struct _MMModem MMModem; + +typedef void (*MMModemFn) (MMModem *modem, + GError *error, + gpointer user_data); + +typedef void (*MMModemUIntFn) (MMModem *modem, + guint32 result, + GError *error, + gpointer user_data); + +typedef void (*MMModemStringFn) (MMModem *modem, + const char *result, + GError *error, + gpointer user_data); + +typedef void (*MMModemIp4Fn) (MMModem *modem, + guint32 address, + GArray *dns, + GError *error, + gpointer user_data); + +typedef void (*MMModemInfoFn) (MMModem *modem, + const char *manufacturer, + const char *model, + const char *version, + GError *error, + gpointer user_data); + +struct _MMModem { + GTypeInterface g_iface; + + /* Methods */ + gboolean (*owns_port) (MMModem *self, + const char *subsys, + const char *name); + + gboolean (*grab_port) (MMModem *self, + const char *subsys, + const char *name, + MMPortType suggested_type, + gpointer user_data, + GError **error); + + void (*release_port) (MMModem *self, + const char *subsys, + const char *name); + + void (*enable) (MMModem *self, + MMModemFn callback, + gpointer user_data); + + void (*disable) (MMModem *self, + MMModemFn callback, + gpointer user_data); + + void (*connect) (MMModem *self, + const char *number, + MMModemFn callback, + gpointer user_data); + + void (*get_ip4_config) (MMModem *self, + MMModemIp4Fn callback, + gpointer user_data); + + void (*disconnect) (MMModem *self, + MMModemFn callback, + gpointer user_data); + + void (*get_info) (MMModem *self, + MMModemInfoFn callback, + gpointer user_data); + + /* Signals */ + void (*state_changed) (MMModem *self, + MMModemState new_state, + MMModemState old_state, + MMModemStateReason reason); +}; + +GType mm_modem_get_type (void); + +gboolean mm_modem_owns_port (MMModem *self, + const char *subsys, + const char *name); + +gboolean mm_modem_grab_port (MMModem *self, + const char *subsys, + const char *name, + MMPortType suggested_type, + gpointer user_data, + GError **error); + +void mm_modem_release_port (MMModem *self, + const char *subsys, + const char *name); + +void mm_modem_enable (MMModem *self, + MMModemFn callback, + gpointer user_data); + +void mm_modem_disable (MMModem *self, + MMModemFn callback, + gpointer user_data); + +void mm_modem_connect (MMModem *self, + const char *number, + MMModemFn callback, + gpointer user_data); + +void mm_modem_get_ip4_config (MMModem *self, + MMModemIp4Fn callback, + gpointer user_data); + +void mm_modem_disconnect (MMModem *self, + MMModemFn callback, + gpointer user_data); + +void mm_modem_get_info (MMModem *self, + MMModemInfoFn callback, + gpointer user_data); + +gboolean mm_modem_get_valid (MMModem *self); + +char *mm_modem_get_device (MMModem *self); + +MMModemState mm_modem_get_state (MMModem *self); + +void mm_modem_set_state (MMModem *self, + MMModemState new_state, + MMModemStateReason reason); + +GError *mm_modem_check_removed (MMModem *self, const GError *error); + +#endif /* MM_MODEM_H */ + diff --git a/src/mm-options.c b/src/mm-options.c new file mode 100644 index 0000000..7bbeefd --- /dev/null +++ b/src/mm-options.c @@ -0,0 +1,55 @@ +/* -*- 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 Novell, Inc. + */ + +#include <stdlib.h> +#include <glib.h> +#include "mm-options.h" + +static gboolean debug = FALSE; + +void +mm_options_parse (int argc, char *argv[]) +{ + GOptionContext *opt_ctx; + GError *error = NULL; + GOptionEntry entries[] = { + { "debug", 0, 0, G_OPTION_ARG_NONE, &debug, "Output to console rather than syslog", NULL }, + { NULL } + }; + + opt_ctx = g_option_context_new (NULL); + g_option_context_set_summary (opt_ctx, "DBus system service to communicate with modems."); + g_option_context_add_main_entries (opt_ctx, entries, NULL); + + if (!g_option_context_parse (opt_ctx, &argc, &argv, &error)) { + g_warning ("%s\n", error->message); + g_error_free (error); + exit (1); + } + + g_option_context_free (opt_ctx); +} + +void +mm_options_set_debug (gboolean enabled) +{ + debug = enabled; +} + +gboolean +mm_options_debug (void) +{ + return debug; +} diff --git a/src/mm-options.h b/src/mm-options.h new file mode 100644 index 0000000..ce33e27 --- /dev/null +++ b/src/mm-options.h @@ -0,0 +1,23 @@ +/* -*- 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 Novell, Inc. + */ + +#ifndef MM_OPTIONS_H +#define MM_OPTIONS_H + +void mm_options_parse (int argc, char *argv[]); +void mm_options_set_debug (gboolean enabled); +gboolean mm_options_debug (void); + +#endif /* MM_OPTIONS_H */ diff --git a/src/mm-plugin-base.c b/src/mm-plugin-base.c new file mode 100644 index 0000000..202eaac --- /dev/null +++ b/src/mm-plugin-base.c @@ -0,0 +1,1126 @@ +/* -*- 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> + +#define G_UDEV_API_IS_SUBJECT_TO_CHANGE +#include <gudev/gudev.h> + +#include "mm-plugin-base.h" +#include "mm-serial-port.h" +#include "mm-serial-parsers.h" +#include "mm-errors.h" +#include "mm-marshal.h" + +static void plugin_init (MMPlugin *plugin_class); + +G_DEFINE_TYPE_EXTENDED (MMPluginBase, mm_plugin_base, G_TYPE_OBJECT, + 0, G_IMPLEMENT_INTERFACE (MM_TYPE_PLUGIN, plugin_init)) + +#define MM_PLUGIN_BASE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_PLUGIN_BASE, MMPluginBasePrivate)) + +/* A hash table shared between all instances of the plugin base that + * caches the probed capabilities so that only one plugin has to actually + * probe a port. + */ +static GHashTable *cached_caps = NULL; + + +typedef struct { + char *name; + GUdevClient *client; + + GHashTable *modems; + GHashTable *tasks; +} MMPluginBasePrivate; + +enum { + PROP_0, + PROP_NAME, + LAST_PROP +}; + +enum { + PROBE_RESULT, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + + +typedef enum { + PROBE_STATE_GCAP_TRY1 = 0, + PROBE_STATE_GCAP_TRY2, + PROBE_STATE_GCAP_TRY3, + PROBE_STATE_ATI, + PROBE_STATE_CPIN, + PROBE_STATE_CGMM, + PROBE_STATE_LAST +} ProbeState; + +/*****************************************************************************/ + +G_DEFINE_TYPE (MMPluginBaseSupportsTask, mm_plugin_base_supports_task, G_TYPE_OBJECT) + +#define MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, MMPluginBaseSupportsTaskPrivate)) + +typedef struct { + MMPluginBase *plugin; + GUdevDevice *port; + GUdevDevice *physdev; + char *driver; + + guint open_id; + guint32 open_tries; + + MMSerialPort *probe_port; + guint32 probed_caps; + ProbeState probe_state; + guint probe_id; + char *probe_resp; + GError *probe_error; + + char *custom_init; + guint32 custom_init_max_tries; + guint32 custom_init_tries; + guint32 custom_init_delay_seconds; + gboolean custom_init_fail_if_timeout; + + MMSupportsPortResultFunc callback; + gpointer callback_data; +} MMPluginBaseSupportsTaskPrivate; + +static MMPluginBaseSupportsTask * +supports_task_new (MMPluginBase *self, + GUdevDevice *port, + GUdevDevice *physdev, + const char *driver, + MMSupportsPortResultFunc callback, + gpointer callback_data) +{ + MMPluginBaseSupportsTask *task; + MMPluginBaseSupportsTaskPrivate *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 (port != NULL, NULL); + g_return_val_if_fail (physdev != NULL, NULL); + g_return_val_if_fail (driver != NULL, NULL); + g_return_val_if_fail (callback != NULL, NULL); + + task = (MMPluginBaseSupportsTask *) g_object_new (MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, NULL); + + 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->driver = g_strdup (driver); + priv->callback = callback; + priv->callback_data = callback_data; + + return task; +} + +MMPlugin * +mm_plugin_base_supports_task_get_plugin (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 (MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->plugin); +} + +GUdevDevice * +mm_plugin_base_supports_task_get_port (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)->port; +} + +GUdevDevice * +mm_plugin_base_supports_task_get_physdev (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; +} + +const char * +mm_plugin_base_supports_task_get_driver (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)->driver; +} + +guint32 +mm_plugin_base_supports_task_get_probed_capabilities (MMPluginBaseSupportsTask *task) +{ + g_return_val_if_fail (task != NULL, 0); + g_return_val_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task), 0); + + return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->probed_caps; +} + +void +mm_plugin_base_supports_task_complete (MMPluginBaseSupportsTask *task, + guint32 level) +{ + MMPluginBaseSupportsTaskPrivate *priv; + const char *subsys, *name; + + g_return_if_fail (task != NULL); + g_return_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task)); + + priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + g_return_if_fail (priv->callback != NULL); + + subsys = g_udev_device_get_subsystem (priv->port); + name = g_udev_device_get_name (priv->port); + + priv->callback (MM_PLUGIN (priv->plugin), subsys, name, level, priv->callback_data); + + /* Clear out the callback, it shouldn't be called more than once */ + priv->callback = NULL; + priv->callback_data = NULL; +} + +void +mm_plugin_base_supports_task_set_custom_init_command (MMPluginBaseSupportsTask *task, + const char *cmd, + guint32 delay_seconds, + guint32 max_tries, + gboolean fail_if_timeout) +{ + MMPluginBaseSupportsTaskPrivate *priv; + + g_return_if_fail (task != NULL); + g_return_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task)); + + priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + + g_free (priv->custom_init); + priv->custom_init = g_strdup (cmd); + priv->custom_init_max_tries = max_tries; + priv->custom_init_delay_seconds = delay_seconds; + priv->custom_init_fail_if_timeout = fail_if_timeout; +} + +static void +mm_plugin_base_supports_task_init (MMPluginBaseSupportsTask *self) +{ +} + +static void +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)); + + g_object_unref (priv->port); + g_object_unref (priv->physdev); + g_free (priv->driver); + g_free (priv->probe_resp); + g_clear_error (&(priv->probe_error)); + g_free (priv->custom_init); + + if (priv->open_id) + g_source_remove (priv->open_id); + + if (priv->probe_id) + g_source_remove (priv->probe_id); + if (priv->probe_port) + g_object_unref (priv->probe_port); + + G_OBJECT_CLASS (mm_plugin_base_supports_task_parent_class)->dispose (object); +} + +static void +mm_plugin_base_supports_task_class_init (MMPluginBaseSupportsTaskClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMPluginBaseSupportsTaskPrivate)); + + /* Virtual methods */ + object_class->dispose = supports_task_dispose; +} + +/*****************************************************************************/ + +#define MM_PLUGIN_BASE_PORT_CAP_CDMA (MM_PLUGIN_BASE_PORT_CAP_IS707_A | \ + MM_PLUGIN_BASE_PORT_CAP_IS707_P | \ + MM_PLUGIN_BASE_PORT_CAP_IS856 | \ + MM_PLUGIN_BASE_PORT_CAP_IS856_A) + +#define CAP_GSM_OR_CDMA (MM_PLUGIN_BASE_PORT_CAP_CDMA | MM_PLUGIN_BASE_PORT_CAP_GSM) + +struct modem_caps { + char *name; + guint32 bits; +}; + +static struct modem_caps modem_caps[] = { + {"+CGSM", MM_PLUGIN_BASE_PORT_CAP_GSM}, + {"+CIS707-A", MM_PLUGIN_BASE_PORT_CAP_IS707_A}, + {"+CIS707A", MM_PLUGIN_BASE_PORT_CAP_IS707_A}, /* Cmotech */ + {"+CIS707", MM_PLUGIN_BASE_PORT_CAP_IS707_A}, + {"CIS707", MM_PLUGIN_BASE_PORT_CAP_IS707_A}, /* Qualcomm Gobi */ + {"+CIS707P", MM_PLUGIN_BASE_PORT_CAP_IS707_P}, + {"CIS-856", MM_PLUGIN_BASE_PORT_CAP_IS856}, + {"+IS-856", MM_PLUGIN_BASE_PORT_CAP_IS856}, /* Cmotech */ + {"CIS-856-A", MM_PLUGIN_BASE_PORT_CAP_IS856_A}, + {"CIS-856A", MM_PLUGIN_BASE_PORT_CAP_IS856_A}, /* Kyocera KPC680 */ + {"+DS", MM_PLUGIN_BASE_PORT_CAP_DS}, + {"+ES", MM_PLUGIN_BASE_PORT_CAP_ES}, + {"+MS", MM_PLUGIN_BASE_PORT_CAP_MS}, + {"+FCLASS", MM_PLUGIN_BASE_PORT_CAP_FCLASS}, + {NULL} +}; + +static guint32 +parse_gcap (const char *buf) +{ + struct modem_caps *cap = modem_caps; + guint32 ret = 0; + + while (cap->name) { + if (strstr (buf, cap->name)) + ret |= cap->bits; + cap++; + } + return ret; +} + +static guint32 +parse_cpin (const char *buf) +{ + if ( strcasestr (buf, "SIM PIN") + || strcasestr (buf, "SIM PUK") + || strcasestr (buf, "PH-SIM PIN") + || strcasestr (buf, "PH-FSIM PIN") + || strcasestr (buf, "PH-FSIM PUK") + || strcasestr (buf, "SIM PIN2") + || strcasestr (buf, "SIM PUK2") + || strcasestr (buf, "PH-NET PIN") + || strcasestr (buf, "PH-NET PUK") + || strcasestr (buf, "PH-NETSUB PIN") + || strcasestr (buf, "PH-NETSUB PUK") + || strcasestr (buf, "PH-SP PIN") + || strcasestr (buf, "PH-SP PUK") + || strcasestr (buf, "PH-CORP PIN") + || strcasestr (buf, "PH-CORP PUK")) + return MM_PLUGIN_BASE_PORT_CAP_GSM; + + return 0; +} + +static guint32 +parse_cgmm (const char *buf) +{ + if (strstr (buf, "GSM900") || strstr (buf, "GSM1800") || + strstr (buf, "GSM1900") || strstr (buf, "GSM850")) + return MM_PLUGIN_BASE_PORT_CAP_GSM; + return 0; +} + +static gboolean +emit_probe_result (gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (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; + + task_priv->probe_id = 0; + g_signal_emit (self, signals[PROBE_RESULT], 0, task, task_priv->probed_caps); + return FALSE; +} + +static void +probe_complete (MMPluginBaseSupportsTask *task) +{ + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + + 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)); + + task_priv->probe_id = g_idle_add (emit_probe_result, task); +} + +static void +parse_response (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data); + +static void +real_handle_probe_response (MMPluginBase *self, + MMPluginBaseSupportsTask *task, + const char *cmd, + const char *response, + const GError *error) +{ + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + MMSerialPort *port = task_priv->probe_port; + gboolean ignore_error = FALSE; + + /* Some modems (Huawei E160g) won't respond to +GCAP with no SIM, but + * will respond to ATI. + */ + if (response && strstr (response, "+CME ERROR:")) + ignore_error = TRUE; + + if (error && !ignore_error) { + if (error->code == MM_SERIAL_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); + } else { + /* Otherwise, if all the GCAP tries timed out, ignore the port + * as it's probably not an AT-capable port. + */ + probe_complete (task); + } + return; + } + + /* Otherwise proceed to the next command */ + } else if (response) { + /* Parse the response */ + + switch (task_priv->probe_state) { + case PROBE_STATE_GCAP_TRY1: + case PROBE_STATE_GCAP_TRY2: + case PROBE_STATE_GCAP_TRY3: + case PROBE_STATE_ATI: + /* Some modems don't respond to AT+GCAP, but often they put a + * GCAP-style response as a line in the ATI response. + */ + task_priv->probed_caps = parse_gcap (response); + break; + case PROBE_STATE_CPIN: + /* Some devices (ZTE MF628/ONDA MT503HS for example) reply to + * anything but AT+CPIN? with ERROR if the device has a PIN set. + * Since no known CDMA modems support AT+CPIN? we can consider the + * device a GSM device if it returns a non-error response to AT+CPIN?. + */ + task_priv->probed_caps = parse_cpin (response); + break; + case PROBE_STATE_CGMM: + /* Some models (BUSlink SCWi275u) stick stupid stuff in the CGMM + * response but at least it allows us to identify them. + */ + task_priv->probed_caps = parse_cgmm (response); + break; + default: + break; + } + + if (task_priv->probed_caps & CAP_GSM_OR_CDMA) { + probe_complete (task); + return; + } + } + + task_priv->probe_state++; + + /* Try a different command */ + 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); + break; + case PROBE_STATE_ATI: + /* After the last GCAP attempt, try ATI */ + mm_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); + break; + case PROBE_STATE_CGMM: + /* After the CPIN attempt, try CGMM */ + mm_serial_port_queue_command (port, "+CGMM", 3, parse_response, task); + break; + default: + /* Probably not GSM or CDMA */ + probe_complete (task); + break; + } +} + +static gboolean +handle_probe_response (gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + MMPluginBase *self = MM_PLUGIN_BASE (mm_plugin_base_supports_task_get_plugin (task)); + const char *cmd = NULL; + + switch (task_priv->probe_state) { + case PROBE_STATE_GCAP_TRY1: + case PROBE_STATE_GCAP_TRY2: + case PROBE_STATE_GCAP_TRY3: + cmd = "+GCAP"; + break; + case PROBE_STATE_ATI: + cmd = "I"; + break; + case PROBE_STATE_CPIN: + cmd = "+CPIN?"; + break; + case PROBE_STATE_CGMM: + default: + cmd = "+CGMM"; + break; + } + + MM_PLUGIN_BASE_GET_CLASS (self)->handle_probe_response (self, + task, + cmd, + task_priv->probe_resp, + task_priv->probe_error); + return FALSE; +} + +static void +parse_response (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + + if (task_priv->probe_id) + g_source_remove (task_priv->probe_id); + g_free (task_priv->probe_resp); + task_priv->probe_resp = NULL; + g_clear_error (&(task_priv->probe_error)); + + if (response && response->len) + task_priv->probe_resp = g_strdup (response->str); + if (error) + task_priv->probe_error = g_error_copy (error); + + /* Schedule the response handler in an idle, since we can't emit the + * PROBE_RESULT signal from the serial port response handler without + * potentially destroying the serial port in the middle of its response + * handler, which it understandably doesn't like. + */ + task_priv->probe_id = g_idle_add (handle_probe_response, task); +} + +static void flash_done (MMSerialPort *port, GError *error, gpointer user_data); + +static void +custom_init_response (MMSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + + if (error) { + 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); + 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)) { + probe_complete (task); + return; + } + } + } + + /* Otherwise proceed to probing */ + mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, user_data); +} + +static void +flash_done (MMSerialPort *port, GError *error, gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + guint32 delay_secs = task_priv->custom_init_delay_seconds; + + /* Send the custom init command if any */ + 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); + } else { + /* Otherwise start normal probing */ + custom_init_response (port, NULL, NULL, user_data); + } +} + +static gboolean +try_open (gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + GError *error = NULL; + + task_priv->open_id = 0; + + if (!mm_serial_port_open (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.", + mm_port_get_device (MM_PORT (task_priv->probe_port))); + probe_complete (task); + } else if (g_error_matches (error, + MM_SERIAL_ERROR, + MM_SERIAL_OPEN_FAILED_NO_DEVICE)) { + /* this is nozomi being dumb; try again */ + task_priv->open_id = g_timeout_add_seconds (1, try_open, task); + } else { + /* some other hard error */ + probe_complete (task); + } + g_clear_error (&error); + } else { + /* success, start probing */ + GUdevDevice *port; + + port = mm_plugin_base_supports_task_get_port (task); + g_assert (port); + + 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); + } + + return FALSE; +} + +gboolean +mm_plugin_base_probe_port (MMPluginBase *self, + MMPluginBaseSupportsTask *task, + GError **error) +{ + MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + MMSerialPort *serial; + const char *name; + GUdevDevice *port; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (MM_IS_PLUGIN_BASE (self), FALSE); + g_return_val_if_fail (task != NULL, FALSE); + + port = mm_plugin_base_supports_task_get_port (task); + g_assert (port); + name = g_udev_device_get_name (port); + g_assert (name); + + serial = mm_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."); + return FALSE; + } + + g_object_set (serial, + MM_SERIAL_PORT_SEND_DELAY, (guint64) 100000, + 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); + + /* Open the port */ + task_priv->probe_port = serial; + task_priv->open_id = g_idle_add (try_open, task); + return TRUE; +} + +gboolean +mm_plugin_base_get_cached_port_capabilities (MMPluginBase *self, + GUdevDevice *port, + guint32 *capabilities) +{ + gpointer tmp = NULL; + gboolean found; + + found = g_hash_table_lookup_extended (cached_caps, g_udev_device_get_name (port), NULL, tmp); + *capabilities = GPOINTER_TO_UINT (tmp); + return found; +} + +/*****************************************************************************/ + +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 + * in the cached capabilities table that includes { caps, modem device } + * or something and then only removing cached capabilities for ports + * that the modem that was just removed owned, but whatever. + */ + 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, + const char *name, + guint16 *vendor, + guint16 *product) +{ + MMPluginBasePrivate *priv; + GUdevDevice *device = NULL; + const char *vid, *pid; + gboolean success = FALSE; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (MM_IS_PLUGIN_BASE (self), FALSE); + g_return_val_if_fail (subsys != NULL, FALSE); + g_return_val_if_fail (name != NULL, FALSE); + if (vendor) + g_return_val_if_fail (*vendor == 0, FALSE); + if (product) + g_return_val_if_fail (*product == 0, FALSE); + + priv = MM_PLUGIN_BASE_GET_PRIVATE (self); + + device = g_udev_client_query_by_subsystem_and_name (priv->client, subsys, name); + if (!device) + goto out; + + vid = g_udev_device_get_property (device, "ID_VENDOR_ID"); + if (!vid || (strlen (vid) != 4)) + goto out; + + if (vendor) { + *vendor = (guint16) (hex2byte (vid + 2) & 0xFF); + *vendor |= (guint16) ((hex2byte (vid) & 0xFF) << 8); + } + + pid = g_udev_device_get_property (device, "ID_MODEL_ID"); + if (!pid || (strlen (pid) != 4)) { + *vendor = 0; + goto out; + } + + if (product) { + *product = (guint16) (hex2byte (pid + 2) & 0xFF); + *product |= (guint16) ((hex2byte (pid) & 0xFF) << 8); + } + + success = TRUE; + +out: + if (device) + g_object_unref (device); + return success; +} + +static char * +get_key (const char *subsys, const char *name) +{ + return g_strdup_printf ("%s%s", subsys, name); +} + +static const char * +get_name (MMPlugin *plugin) +{ + return MM_PLUGIN_BASE_GET_PRIVATE (plugin)->name; +} + +static char * +get_driver_name (GUdevDevice *device) +{ + GUdevDevice *parent = NULL; + const char *driver, *subsys; + char *ret = NULL; + + driver = g_udev_device_get_driver (device); + if (!driver) { + parent = g_udev_device_get_parent (device); + if (parent) + driver = g_udev_device_get_driver (parent); + + /* Check for bluetooth; it's driver is a bunch of levels up so we + * just check for the subsystem of the parent being bluetooth. + */ + if (!driver && parent) { + subsys = g_udev_device_get_subsystem (parent); + if (subsys && !strcmp (subsys, "bluetooth")) + driver = "bluetooth"; + } + } + + if (driver) + ret = g_strdup (driver); + if (parent) + g_object_unref (parent); + + 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, + MMSupportsPortResultFunc callback, + gpointer callback_data) +{ + MMPluginBase *self = MM_PLUGIN_BASE (plugin); + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); + GUdevDevice *port = NULL, *physdev = 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); + if (task) { + g_free (key); + g_return_val_if_fail (task == NULL, MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED); + } + + port = g_udev_client_query_by_subsystem_and_name (priv->client, subsys, name); + 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); + 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 + * not needed. + */ + g_hash_table_remove (priv->tasks, key); + } + g_object_unref (task); + +out: + if (physdev) + g_object_unref (physdev); + if (port) + g_object_unref (port); + g_free (key); + g_free (driver); + return result; +} + +static void +cancel_supports_port (MMPlugin *plugin, + const char *subsys, + const char *name) +{ + MMPluginBase *self = MM_PLUGIN_BASE (plugin); + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); + MMPluginBaseSupportsTask *task; + char *key; + + key = get_key (subsys, name); + task = g_hash_table_lookup (priv->tasks, key); + if (task) { + /* Let the plugin cancel any ongoing tasks */ + if (MM_PLUGIN_BASE_GET_CLASS (self)->cancel_task) + MM_PLUGIN_BASE_GET_CLASS (self)->cancel_task (self, task); + g_hash_table_remove (priv->tasks, key); + } + + g_free (key); +} + +static MMModem * +grab_port (MMPlugin *plugin, + const char *subsys, + const char *name, + GError **error) +{ + MMPluginBase *self = MM_PLUGIN_BASE (plugin); + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); + MMPluginBaseSupportsTask *task; + char *key; + MMModem *existing = NULL, *modem = NULL; + const char *master_path; + + key = get_key (subsys, name); + task = g_hash_table_lookup (priv->tasks, key); + if (!task) { + g_free (key); + 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); + g_object_weak_ref (G_OBJECT (modem), modem_destroyed, self); + } + + g_hash_table_remove (priv->tasks, key); + g_free (key); + return modem; +} + +/*****************************************************************************/ + +static void +plugin_init (MMPlugin *plugin_class) +{ + /* interface implementation */ + plugin_class->get_name = get_name; + plugin_class->supports_port = supports_port; + plugin_class->cancel_supports_port = cancel_supports_port; + plugin_class->grab_port = grab_port; +} + +static void +mm_plugin_base_init (MMPluginBase *self) +{ + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); + const char *subsys[] = { "tty", "net", NULL }; + + if (!cached_caps) + cached_caps = g_hash_table_new (g_str_hash, g_str_equal); + + 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); +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_NAME: + /* Construct only */ + priv->name = g_value_dup_string (value); + 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) +{ + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, priv->name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +finalize (GObject *object) +{ + MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (object); + + g_free (priv->name); + + 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); +} + +static void +mm_plugin_base_class_init (MMPluginBaseClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (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 */ + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + g_object_class_install_property + (object_class, PROP_NAME, + g_param_spec_string (MM_PLUGIN_BASE_NAME, + "Name", + "Name", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + signals[PROBE_RESULT] = + g_signal_new ("probe-result", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMPluginBaseClass, probe_result), + NULL, NULL, + mm_marshal_VOID__OBJECT_UINT, + G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_UINT); +} diff --git a/src/mm-plugin-base.h b/src/mm-plugin-base.h new file mode 100644 index 0000000..49e68d4 --- /dev/null +++ b/src/mm-plugin-base.h @@ -0,0 +1,150 @@ +/* -*- 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) 2009 Red Hat, Inc. + */ + +#ifndef MM_PLUGIN_BASE_H +#define MM_PLUGIN_BASE_H + +#include <glib.h> +#include <glib/gtypes.h> +#include <glib-object.h> + +#define G_UDEV_API_IS_SUBJECT_TO_CHANGE +#include <gudev/gudev.h> + +#include "mm-plugin.h" +#include "mm-modem.h" +#include "mm-port.h" + +#define MM_PLUGIN_BASE_PORT_CAP_GSM 0x0001 /* GSM */ +#define MM_PLUGIN_BASE_PORT_CAP_IS707_A 0x0002 /* CDMA Circuit Switched Data */ +#define MM_PLUGIN_BASE_PORT_CAP_IS707_P 0x0004 /* CDMA Packet Switched Data */ +#define MM_PLUGIN_BASE_PORT_CAP_DS 0x0008 /* Data compression selection (v.42bis) */ +#define MM_PLUGIN_BASE_PORT_CAP_ES 0x0010 /* Error control selection (v.42) */ +#define MM_PLUGIN_BASE_PORT_CAP_FCLASS 0x0020 /* Group III Fax */ +#define MM_PLUGIN_BASE_PORT_CAP_MS 0x0040 /* Modulation selection */ +#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_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)) +#define MM_PLUGIN_BASE_SUPPORTS_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, MMPluginBaseSupportsTaskClass)) +#define MM_IS_PLUGIN_BASE_SUPPORTS_TASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK)) +#define MM_IS_PLUBIN_BASE_SUPPORTS_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK)) +#define MM_PLUGIN_BASE_SUPPORTS_TASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, MMPluginBaseSupportsTaskClass)) + +typedef struct { + GObject parent; +} MMPluginBaseSupportsTask; + +typedef struct { + GObjectClass parent; +} MMPluginBaseSupportsTaskClass; + +GType mm_plugin_base_supports_task_get_type (void); + +MMPlugin *mm_plugin_base_supports_task_get_plugin (MMPluginBaseSupportsTask *task); + +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_driver (MMPluginBaseSupportsTask *task); + +guint32 mm_plugin_base_supports_task_get_probed_capabilities (MMPluginBaseSupportsTask *task); + +void mm_plugin_base_supports_task_complete (MMPluginBaseSupportsTask *task, + guint32 level); + +void mm_plugin_base_supports_task_set_custom_init_command (MMPluginBaseSupportsTask *task, + const char *cmd, + guint32 delay_seconds, + guint32 max_tries, + gboolean fail_if_timeout); + +#define MM_TYPE_PLUGIN_BASE (mm_plugin_base_get_type ()) +#define MM_PLUGIN_BASE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_BASE, MMPluginBase)) +#define MM_PLUGIN_BASE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_BASE, MMPluginBaseClass)) +#define MM_IS_PLUGIN_BASE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_BASE)) +#define MM_IS_PLUBIN_BASE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_BASE)) +#define MM_PLUGIN_BASE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_BASE, MMPluginBaseClass)) + +#define MM_PLUGIN_BASE_NAME "name" + +typedef struct _MMPluginBase MMPluginBase; +typedef struct _MMPluginBaseClass MMPluginBaseClass; + +struct _MMPluginBase { + GObject parent; +}; + +struct _MMPluginBaseClass { + GObjectClass parent; + + /* Mandatory subclass functions */ + MMPluginSupportsResult (*supports_port) (MMPluginBase *plugin, + MMModem *existing, + MMPluginBaseSupportsTask *task); + + MMModem *(*grab_port) (MMPluginBase *plugin, + MMModem *existing, + MMPluginBaseSupportsTask *task, + GError **error); + + /* Optional subclass functions */ + 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, + const char *response, + const GError *error); + + /* Signals */ + void (*probe_result) (MMPluginBase *self, + MMPluginBaseSupportsTask *task, + guint32 capabilities); +}; + +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, + guint16 *vendor, + guint16 *product); + +gboolean mm_plugin_base_probe_port (MMPluginBase *self, + MMPluginBaseSupportsTask *task, + GError **error); + +/* Returns TRUE if the port was previously probed, FALSE if not */ +gboolean mm_plugin_base_get_cached_port_capabilities (MMPluginBase *self, + GUdevDevice *port, + guint32 *capabilities); + +#endif /* MM_PLUGIN_BASE_H */ + diff --git a/src/mm-plugin.c b/src/mm-plugin.c new file mode 100644 index 0000000..830f2d6 --- /dev/null +++ b/src/mm-plugin.c @@ -0,0 +1,100 @@ +/* -*- 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 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#include "mm-plugin.h" + +const char * +mm_plugin_get_name (MMPlugin *plugin) +{ + g_return_val_if_fail (MM_IS_PLUGIN (plugin), NULL); + + return MM_PLUGIN_GET_INTERFACE (plugin)->get_name (plugin); +} + +MMPluginSupportsResult +mm_plugin_supports_port (MMPlugin *plugin, + const char *subsys, + const char *name, + 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 (callback != NULL, FALSE); + + return MM_PLUGIN_GET_INTERFACE (plugin)->supports_port (plugin, subsys, name, callback, user_data); +} + +void +mm_plugin_cancel_supports_port (MMPlugin *plugin, + const char *subsys, + const char *name) +{ + g_return_if_fail (MM_IS_PLUGIN (plugin)); + g_return_if_fail (subsys != NULL); + g_return_if_fail (name != NULL); + + MM_PLUGIN_GET_INTERFACE (plugin)->cancel_supports_port (plugin, subsys, name); +} + +MMModem * +mm_plugin_grab_port (MMPlugin *plugin, + const char *subsys, + const char *name, + 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); +} + +/*****************************************************************************/ + +static void +mm_plugin_init (gpointer g_iface) +{ +} + +GType +mm_plugin_get_type (void) +{ + static GType plugin_type = 0; + + if (!G_UNLIKELY (plugin_type)) { + const GTypeInfo plugin_info = { + sizeof (MMPlugin), /* class_size */ + mm_plugin_init, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + plugin_type = g_type_register_static (G_TYPE_INTERFACE, + "MMPlugin", + &plugin_info, 0); + + g_type_interface_add_prerequisite (plugin_type, G_TYPE_OBJECT); + } + + return plugin_type; +} diff --git a/src/mm-plugin.h b/src/mm-plugin.h new file mode 100644 index 0000000..d1e85b6 --- /dev/null +++ b/src/mm-plugin.h @@ -0,0 +1,120 @@ +/* -*- 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 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef MM_PLUGIN_H +#define MM_PLUGIN_H + +#include <glib-object.h> +#include <mm-modem.h> + +#define MM_PLUGIN_GENERIC_NAME "Generic" + +#define MM_PLUGIN_MAJOR_VERSION 3 +#define MM_PLUGIN_MINOR_VERSION 0 + +#define MM_TYPE_PLUGIN (mm_plugin_get_type ()) +#define MM_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN, MMPlugin)) +#define MM_IS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN)) +#define MM_PLUGIN_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_PLUGIN, MMPlugin)) + +typedef struct _MMPlugin MMPlugin; + +typedef MMPlugin *(*MMPluginCreateFunc) (void); + +/* + * 'level' is a value between 0 and 100 inclusive, where 0 means the plugin has + * no support for the port, and 100 means the plugin has full support for the + * port. + */ +typedef void (*MMSupportsPortResultFunc) (MMPlugin *plugin, + const char *subsys, + const char *name, + guint32 level, + gpointer user_data); + +typedef enum { + MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED = 0x0, + MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS, + MM_PLUGIN_SUPPORTS_PORT_DEFER +} MMPluginSupportsResult; + +struct _MMPlugin { + GTypeInterface g_iface; + + /* Methods */ + const char *(*get_name) (MMPlugin *self); + + /* Check whether a plugin supports a particular modem port, and what level + * of support the plugin has for the device. If the plugin can immediately + * determine whether a port is unsupported, it should return + * FALSE. Otherwise, if the plugin needs to perform additional operations + * (ie, probing) to determine the level of support or additional details + * about a port, it should queue that operation (but *not* block on the + * result) and return TRUE to indicate the operation is ongoing. When the + * operation is finished or the level of support is known, the plugin should + * call the provided callback and pass that callback the provided user_data. + */ + MMPluginSupportsResult (*supports_port) (MMPlugin *self, + const char *subsys, + const char *name, + MMSupportsPortResultFunc callback, + gpointer user_data); + + /* Called to cancel an ongoing supports_port() operation or to notify the + * plugin to clean up that operation. For example, if two plugins support + * a particular port, but the first plugin grabs the port, this method will + * be called on the second plugin to allow that plugin to clean up resources + * used while determining it's level of support for the port. + */ + void (*cancel_supports_port) (MMPlugin *self, + const char *subsys, + const char *name); + + /* Will only be called if the plugin returns a value greater than 0 for + * the supports_port() method for this port. The plugin should create and + * return a new modem for the port's device if there is no existing modem + * to handle the port's hardware device, or should add the port to an + * existing modem and return that modem object. If an error is encountered + * while claiming the port, the error information should be returned in the + * error argument, and the plugin should return NULL. + */ + MMModem * (*grab_port) (MMPlugin *self, + const char *subsys, + const char *name, + GError **error); +}; + +GType mm_plugin_get_type (void); + +const char *mm_plugin_get_name (MMPlugin *plugin); + +MMPluginSupportsResult mm_plugin_supports_port (MMPlugin *plugin, + const char *subsys, + const char *name, + MMSupportsPortResultFunc callback, + gpointer user_data); + +void mm_plugin_cancel_supports_port (MMPlugin *plugin, + const char *subsys, + const char *name); + +MMModem *mm_plugin_grab_port (MMPlugin *plugin, + const char *subsys, + const char *name, + GError **error); + +#endif /* MM_PLUGIN_H */ + diff --git a/src/mm-port.c b/src/mm-port.c new file mode 100644 index 0000000..7e8edc4 --- /dev/null +++ b/src/mm-port.c @@ -0,0 +1,278 @@ +/* -*- 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) 2009 Red Hat, Inc. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include "mm-port.h" + +G_DEFINE_TYPE (MMPort, mm_port, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_DEVICE, + PROP_SUBSYS, + PROP_TYPE, + PROP_CARRIER_DETECT, + PROP_CONNECTED, + + LAST_PROP +}; + +#define MM_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_PORT, MMPortPrivate)) + +typedef struct { + char *device; + MMPortSubsys subsys; + MMPortType ptype; + gboolean carrier_detect; + gboolean connected; +} MMPortPrivate; + +/*****************************************************************************/ + +static GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + MMPortPrivate *priv; + + object = G_OBJECT_CLASS (mm_port_parent_class)->constructor (type, + n_construct_params, + construct_params); + if (!object) + return NULL; + + priv = MM_PORT_GET_PRIVATE (object); + + if (!priv->device) { + g_warning ("MMPort: no device provided"); + g_object_unref (object); + return NULL; + } + + if (priv->subsys == MM_PORT_SUBSYS_UNKNOWN) { + g_warning ("MMPort: invalid port subsystem"); + g_object_unref (object); + return NULL; + } + + if (priv->ptype == MM_PORT_TYPE_UNKNOWN) { + g_warning ("MMPort: invalid port type"); + g_object_unref (object); + return NULL; + } + + return object; +} + + +const char * +mm_port_get_device (MMPort *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_PORT (self), NULL); + + return MM_PORT_GET_PRIVATE (self)->device; +} + +MMPortSubsys +mm_port_get_subsys (MMPort *self) +{ + g_return_val_if_fail (self != NULL, MM_PORT_SUBSYS_UNKNOWN); + g_return_val_if_fail (MM_IS_PORT (self), MM_PORT_SUBSYS_UNKNOWN); + + return MM_PORT_GET_PRIVATE (self)->subsys; +} + +MMPortType +mm_port_get_port_type (MMPort *self) +{ + g_return_val_if_fail (self != NULL, MM_PORT_TYPE_UNKNOWN); + g_return_val_if_fail (MM_IS_PORT (self), MM_PORT_TYPE_UNKNOWN); + + return MM_PORT_GET_PRIVATE (self)->ptype; +} + +gboolean +mm_port_get_carrier_detect (MMPort *self) +{ + g_return_val_if_fail (self != NULL, MM_PORT_TYPE_UNKNOWN); + g_return_val_if_fail (MM_IS_PORT (self), MM_PORT_TYPE_UNKNOWN); + + return MM_PORT_GET_PRIVATE (self)->carrier_detect; +} + +gboolean +mm_port_get_connected (MMPort *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (MM_IS_PORT (self), FALSE); + + return MM_PORT_GET_PRIVATE (self)->connected; +} + +void +mm_port_set_connected (MMPort *self, gboolean connected) +{ + MMPortPrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_PORT (self)); + + priv = MM_PORT_GET_PRIVATE (self); + if (priv->connected != connected) { + priv->connected = connected; + g_object_notify (G_OBJECT (self), MM_PORT_CONNECTED); + } +} + +/*****************************************************************************/ + +static void +mm_port_init (MMPort *self) +{ +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + MMPortPrivate *priv = MM_PORT_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_DEVICE: + /* Construct only */ + priv->device = g_value_dup_string (value); + break; + case PROP_SUBSYS: + /* Construct only */ + priv->subsys = g_value_get_uint (value); + break; + case PROP_TYPE: + /* Construct only */ + priv->ptype = g_value_get_uint (value); + break; + case PROP_CARRIER_DETECT: + priv->carrier_detect = g_value_get_boolean (value); + break; + case PROP_CONNECTED: + priv->connected = g_value_get_boolean (value); + 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) +{ + MMPortPrivate *priv = MM_PORT_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_DEVICE: + g_value_set_string (value, priv->device); + break; + case PROP_SUBSYS: + g_value_set_uint (value, priv->subsys); + break; + case PROP_TYPE: + g_value_set_uint (value, priv->ptype); + break; + case PROP_CARRIER_DETECT: + g_value_set_boolean (value, priv->carrier_detect); + break; + case PROP_CONNECTED: + g_value_set_boolean (value, priv->connected); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +finalize (GObject *object) +{ + MMPortPrivate *priv = MM_PORT_GET_PRIVATE (object); + + g_free (priv->device); + + G_OBJECT_CLASS (mm_port_parent_class)->finalize (object); +} + +static void +mm_port_class_init (MMPortClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMPortPrivate)); + + /* Virtual methods */ + object_class->constructor = constructor; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->finalize = finalize; + + g_object_class_install_property + (object_class, PROP_DEVICE, + g_param_spec_string (MM_PORT_DEVICE, + "Device", + "Device", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_SUBSYS, + g_param_spec_uint (MM_PORT_SUBSYS, + "Subsystem", + "Subsystem", + MM_PORT_SUBSYS_UNKNOWN, + MM_PORT_SUBSYS_LAST, + MM_PORT_SUBSYS_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_TYPE, + g_param_spec_uint (MM_PORT_TYPE, + "Type", + "Type", + MM_PORT_TYPE_UNKNOWN, + MM_PORT_TYPE_LAST, + MM_PORT_TYPE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_CARRIER_DETECT, + g_param_spec_boolean (MM_PORT_CARRIER_DETECT, + "Carrier Detect", + "Has Carrier Detect", + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (object_class, PROP_CONNECTED, + g_param_spec_boolean (MM_PORT_CONNECTED, + "Connected", + "Is connected for data and not usable for control", + FALSE, + G_PARAM_READWRITE)); +} diff --git a/src/mm-port.h b/src/mm-port.h new file mode 100644 index 0000000..b537618 --- /dev/null +++ b/src/mm-port.h @@ -0,0 +1,79 @@ +/* -*- 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) 2009 Red Hat, Inc. + */ + +#ifndef MM_PORT_H +#define MM_PORT_H + +#include <glib.h> +#include <glib/gtypes.h> +#include <glib-object.h> + +typedef enum { + MM_PORT_SUBSYS_UNKNOWN = 0x0, + MM_PORT_SUBSYS_TTY, + MM_PORT_SUBSYS_NET, + + MM_PORT_SUBSYS_LAST = MM_PORT_SUBSYS_NET +} MMPortSubsys; + +typedef enum { + MM_PORT_TYPE_UNKNOWN = 0x0, + MM_PORT_TYPE_PRIMARY, + MM_PORT_TYPE_SECONDARY, + MM_PORT_TYPE_IGNORED, + + MM_PORT_TYPE_LAST = MM_PORT_TYPE_IGNORED +} MMPortType; + +#define MM_TYPE_PORT (mm_port_get_type ()) +#define MM_PORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PORT, MMPort)) +#define MM_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PORT, MMPortClass)) +#define MM_IS_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PORT)) +#define MM_IS_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PORT)) +#define MM_PORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PORT, MMPortClass)) + +#define MM_PORT_DEVICE "device" +#define MM_PORT_SUBSYS "subsys" +#define MM_PORT_TYPE "type" +#define MM_PORT_CARRIER_DETECT "carrier-detect" +#define MM_PORT_CONNECTED "connected" + +typedef struct _MMPort MMPort; +typedef struct _MMPortClass MMPortClass; + +struct _MMPort { + GObject parent; +}; + +struct _MMPortClass { + GObjectClass parent; +}; + +GType mm_port_get_type (void); + +const char * mm_port_get_device (MMPort *self); + +MMPortSubsys mm_port_get_subsys (MMPort *self); + +MMPortType mm_port_get_port_type (MMPort *self); + +gboolean mm_port_get_carrier_detect (MMPort *self); + +gboolean mm_port_get_connected (MMPort *self); + +void mm_port_set_connected (MMPort *self, gboolean connected); + +#endif /* MM_PORT_H */ + diff --git a/src/mm-properties-changed-signal.c b/src/mm-properties-changed-signal.c new file mode 100644 index 0000000..b7f3672 --- /dev/null +++ b/src/mm-properties-changed-signal.c @@ -0,0 +1,276 @@ +/* -*- 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) 2007 - 2008 Novell, Inc. + * Copyright (C) 2008 - 2009 Red Hat, Inc. + */ + +#include <string.h> +#include <stdio.h> + +#include <dbus/dbus-glib.h> +#include "mm-marshal.h" +#include "mm-properties-changed-signal.h" + +#define DBUS_TYPE_G_MAP_OF_VARIANT (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE)) + +#define PC_SIGNAL_NAME "mm-properties-changed" +#define MM_DBUS_PROPERTY_CHANGED "MM_DBUS_PROPERTY_CHANGED" + +typedef struct { + /* Whitelist of GObject property names for which changes will be emitted + * over the bus. + * + * Mapping of {property-name -> dbus-interface} + */ + GHashTable *registered; + + /* Table of each D-Bus interface of the object for which one or more + * properties have changed, and those properties and their new values. + * Destroyed after the changed signal has been sent. + * + * Mapping of {dbus-interface -> {property-name -> value}} + */ + GHashTable *hash; + + gulong signal_id; + guint idle_id; +} PropertiesChangedInfo; + +static void +destroy_value (gpointer data) +{ + GValue *val = (GValue *) data; + + g_value_unset (val); + g_slice_free (GValue, val); +} + +static PropertiesChangedInfo * +properties_changed_info_new (void) +{ + PropertiesChangedInfo *info; + + info = g_slice_new0 (PropertiesChangedInfo); + + info->registered = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + info->hash = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_hash_table_destroy); + return info; +} + +static void +properties_changed_info_destroy (gpointer data) +{ + PropertiesChangedInfo *info = (PropertiesChangedInfo *) data; + + if (info->idle_id) + g_source_remove (info->idle_id); + + g_hash_table_destroy (info->hash); + g_hash_table_destroy (info->registered); + g_slice_free (PropertiesChangedInfo, info); +} + +#ifdef DEBUG +static void +add_to_string (gpointer key, gpointer value, gpointer user_data) +{ + char *buf = (char *) user_data; + GValue str_val = { 0, }; + + g_value_init (&str_val, G_TYPE_STRING); + if (!g_value_transform ((GValue *) value, &str_val)) { + if (G_VALUE_HOLDS_OBJECT (value)) { + GObject *obj = g_value_get_object (value); + + if (g_value_get_object (value)) { + sprintf (buf + strlen (buf), "{%s: %p (%s)}, ", + (const char *) key, obj, G_OBJECT_TYPE_NAME (obj)); + } else { + sprintf (buf + strlen (buf), "{%s: %p}, ", (const char *) key, obj); + } + } else + sprintf (buf + strlen (buf), "{%s: <transform error>}, ", (const char *) key); + } else { + sprintf (buf + strlen (buf), "{%s: %s}, ", (const char *) key, g_value_get_string (&str_val)); + } + g_value_unset (&str_val); +} +#endif + +static gboolean +properties_changed (gpointer data) +{ + GObject *object = G_OBJECT (data); + PropertiesChangedInfo *info; + GHashTableIter iter; + gpointer key, value; + + info = (PropertiesChangedInfo *) g_object_get_data (object, MM_DBUS_PROPERTY_CHANGED); + g_assert (info); + + g_hash_table_iter_init (&iter, info->hash); + while (g_hash_table_iter_next (&iter, &key, &value)) { + const char *interface = (const char *) key; + GHashTable *props = (GHashTable *) value; + +#ifdef DEBUG + { + char buf[2048] = { 0, }; + g_hash_table_foreach (props, add_to_string, &buf); + g_message ("%s: %s -> (%s) %s", __func__, + G_OBJECT_TYPE_NAME (object), + interface, + buf); + } +#endif + + /* Send the PropertiesChanged signal */ + g_signal_emit (object, info->signal_id, 0, interface, props); + } + g_hash_table_remove_all (info->hash); + + return FALSE; +} + +static void +idle_id_reset (gpointer data) +{ + GObject *object = G_OBJECT (data); + PropertiesChangedInfo *info = (PropertiesChangedInfo *) g_object_get_data (object, MM_DBUS_PROPERTY_CHANGED); + + /* info is unset when the object is being destroyed */ + if (info) + info->idle_id = 0; +} + +static char* +uscore_to_wincaps (const char *uscore) +{ + const char *p; + GString *str; + gboolean last_was_uscore; + + last_was_uscore = TRUE; + + str = g_string_new (NULL); + p = uscore; + while (p && *p) { + if (*p == '-' || *p == '_') + last_was_uscore = TRUE; + else { + if (last_was_uscore) { + g_string_append_c (str, g_ascii_toupper (*p)); + last_was_uscore = FALSE; + } else + g_string_append_c (str, *p); + } + ++p; + } + + return g_string_free (str, FALSE); +} + +static PropertiesChangedInfo * +get_properties_changed_info (GObject *object) +{ + PropertiesChangedInfo *info = NULL; + + info = (PropertiesChangedInfo *) g_object_get_data (object, MM_DBUS_PROPERTY_CHANGED); + if (!info) { + info = properties_changed_info_new (); + g_object_set_data_full (object, MM_DBUS_PROPERTY_CHANGED, info, properties_changed_info_destroy); + info->signal_id = g_signal_lookup (PC_SIGNAL_NAME, G_OBJECT_TYPE (object)); + g_assert (info->signal_id); + } + + g_assert (info); + return info; +} + +static void +notify (GObject *object, GParamSpec *pspec) +{ + GHashTable *interfaces; + PropertiesChangedInfo *info; + const char *interface; + GValue *value; + + info = get_properties_changed_info (object); + + interface = g_hash_table_lookup (info->registered, pspec->name); + if (!interface) + return; + + /* Check if there are other changed properties for this interface already, + * otherwise create a new hash table for all changed properties for this + * D-Bus interface. + */ + interfaces = g_hash_table_lookup (info->hash, interface); + if (!interfaces) { + interfaces = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, destroy_value); + g_hash_table_insert (info->hash, g_strdup (interface), interfaces); + } + + /* Now put the changed property value into the hash table of changed values + * for its D-Bus interface. + */ + value = g_slice_new0 (GValue); + g_value_init (value, pspec->value_type); + g_object_get_property (object, pspec->name, value); + g_hash_table_insert (interfaces, uscore_to_wincaps (pspec->name), value); + + if (!info->idle_id) + info->idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, properties_changed, object, idle_id_reset); +} + +void +mm_properties_changed_signal_register_property (GObject *object, + const char *property, + const char *interface) +{ + PropertiesChangedInfo *info; + const char *tmp; + + /* All exported properties need to be registered explicitly for now since + * dbus-glib doesn't expose any method to find out the properties registered + * in the XML. + */ + + info = get_properties_changed_info (object); + tmp = g_hash_table_lookup (info->registered, property); + if (tmp) { + g_warning ("%s: property '%s' already registerd on interface '%s'", + __func__, property, tmp); + } else + g_hash_table_insert (info->registered, g_strdup (property), g_strdup (interface)); +} + +guint +mm_properties_changed_signal_new (GObjectClass *object_class) +{ + guint id; + + object_class->notify = notify; + + id = g_signal_new (PC_SIGNAL_NAME, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + mm_marshal_VOID__STRING_BOXED, + G_TYPE_NONE, 2, G_TYPE_STRING, DBUS_TYPE_G_MAP_OF_VARIANT); + + return id; +} + diff --git a/src/mm-properties-changed-signal.h b/src/mm-properties-changed-signal.h new file mode 100644 index 0000000..60e71b9 --- /dev/null +++ b/src/mm-properties-changed-signal.h @@ -0,0 +1,28 @@ +/* -*- 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) 2007 - 2008 Novell, Inc. + * Copyright (C) 2008 - 2009 Red Hat, Inc. + */ + +#ifndef _MM_PROPERTIES_CHANGED_SIGNAL_H_ +#define _MM_PROPERTIES_CHANGED_SIGNAL_H_ + +#include <glib-object.h> + +guint mm_properties_changed_signal_new (GObjectClass *object_class); + +void mm_properties_changed_signal_register_property (GObject *object, + const char *property, + const char *interface); + +#endif /* _MM_PROPERTIES_CHANGED_SIGNAL_H_ */ diff --git a/src/mm-serial-parsers.c b/src/mm-serial-parsers.c new file mode 100644 index 0000000..58985d9 --- /dev/null +++ b/src/mm-serial-parsers.c @@ -0,0 +1,372 @@ +/* -*- 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. + */ + +#include <string.h> +#include <stdlib.h> + +#include "mm-serial-parsers.h" +#include "mm-errors.h" + +/* Clean up the response by removing control characters like <CR><LF> etc */ +static void +response_clean (GString *response) +{ + char *s; + + /* Ends with one or more '<CR><LF>' */ + s = response->str + response->len - 1; + while ((s > response->str) && (*s == '\n') && (*(s - 1) == '\r')) { + g_string_truncate (response, response->len - 2); + s -= 2; + } + + /* Starts with one or more '<CR><LF>' */ + s = response->str; + while ((response->len >= 2) && (*s == '\r') && (*(s + 1) == '\n')) { + g_string_erase (response, 0, 2); + s = response->str; + } +} + + +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 TRUE; +} + +static void +remove_matches (GRegex *r, GString *string) +{ + char *str; + int result_len = string->len; + + str = g_regex_replace_eval (r, string->str, string->len, 0, 0, + remove_eval_cb, &result_len, NULL); + + g_string_truncate (string, 0); + g_string_append_len (string, str, result_len); + g_free (str); +} + +typedef struct { + GRegex *generic_response; + GRegex *detailed_error; +} MMSerialParserV0; + +gpointer +mm_serial_parser_v0_new (void) +{ + MMSerialParserV0 *parser; + GRegexCompileFlags flags = G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW | G_REGEX_OPTIMIZE; + + parser = g_slice_new (MMSerialParserV0); + + parser->generic_response = g_regex_new ("(\\d)\\0?\\r$", flags, 0, NULL); + parser->detailed_error = g_regex_new ("\\+CME ERROR: (\\d+)\\r\\n$", flags, 0, NULL); + + return parser; +} + +gboolean +mm_serial_parser_v0_parse (gpointer data, + GString *response, + GError **error) +{ + MMSerialParserV0 *parser = (MMSerialParserV0 *) data; + GMatchInfo *match_info; + char *str; + GError *local_error = NULL; + int code; + gboolean found; + + g_return_val_if_fail (parser != NULL, FALSE); + g_return_val_if_fail (response != NULL, FALSE); + + if (G_UNLIKELY (!response->len || !strlen (response->str))) + return FALSE; + + found = g_regex_match_full (parser->generic_response, 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); + } else + code = MM_MOBILE_ERROR_UNKNOWN; + + g_match_info_free (match_info); + + switch (code) { + case 0: /* OK */ + break; + case 1: /* CONNECT */ + break; + case 3: /* NO CARRIER */ + local_error = mm_modem_connect_error_for_code (MM_MODEM_CONNECT_ERROR_NO_CARRIER); + break; + case 4: /* ERROR */ + local_error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN); + break; + case 6: /* NO DIALTONE */ + local_error = mm_modem_connect_error_for_code (MM_MODEM_CONNECT_ERROR_NO_DIALTONE); + break; + case 7: /* BUSY */ + local_error = mm_modem_connect_error_for_code (MM_MODEM_CONNECT_ERROR_BUSY); + break; + case 8: /* NO ANSWER */ + local_error = mm_modem_connect_error_for_code (MM_MODEM_CONNECT_ERROR_NO_ANSWER); + break; + default: + local_error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN); + break; + } + + remove_matches (parser->generic_response, response); + } + + if (!found) { + found = g_regex_match_full (parser->detailed_error, 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); + } else + code = MM_MOBILE_ERROR_UNKNOWN; + + g_match_info_free (match_info); + local_error = mm_mobile_error_for_code (code); + } + } + + if (found) + response_clean (response); + + if (local_error) { + g_debug ("Got failure code %d: %s", local_error->code, local_error->message); + g_propagate_error (error, local_error); + } + + return found; +} + +void +mm_serial_parser_v0_destroy (gpointer data) +{ + MMSerialParserV0 *parser = (MMSerialParserV0 *) data; + + g_return_if_fail (parser != NULL); + + g_regex_unref (parser->generic_response); + g_regex_unref (parser->detailed_error); + + g_slice_free (MMSerialParserV0, data); +} + +typedef struct { + GRegex *regex_ok; + GRegex *regex_connect; + GRegex *regex_detailed_error; + GRegex *regex_unknown_error; + GRegex *regex_connect_failed; +} MMSerialParserV1; + +gpointer +mm_serial_parser_v1_new (void) +{ + MMSerialParserV1 *parser; + GRegexCompileFlags flags = G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW | G_REGEX_OPTIMIZE; + + parser = g_slice_new (MMSerialParserV1); + + 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_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); + + return parser; +} + +gboolean +mm_serial_parser_v1_parse (gpointer data, + GString *response, + GError **error) +{ + MMSerialParserV1 *parser = (MMSerialParserV1 *) data; + GMatchInfo *match_info; + GError *local_error; + int code; + gboolean found = FALSE; + + g_return_val_if_fail (parser != NULL, FALSE); + g_return_val_if_fail (response != NULL, FALSE); + + if (G_UNLIKELY (!response->len || !strlen (response->str))) + return FALSE; + + /* First, check for successful responses */ + + found = g_regex_match_full (parser->regex_ok, response->str, response->len, 0, 0, NULL, NULL); + if (found) + remove_matches (parser->regex_ok, response); + else + found = g_regex_match_full (parser->regex_connect, response->str, response->len, 0, 0, NULL, NULL); + + if (found) { + response_clean (response); + return TRUE; + } + + /* Now failures */ + code = MM_MOBILE_ERROR_UNKNOWN; + local_error = NULL; + + found = g_regex_match_full (parser->regex_detailed_error, + response->str, response->len, + 0, 0, &match_info, NULL); + + if (found) { + char *str; + + str = g_match_info_fetch (match_info, 1); + if (str) { + code = atoi (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); + + 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; + + 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; + + g_free (str); + } + g_match_info_free (match_info); + + local_error = mm_modem_connect_error_for_code (code); + } + } + + if (found) + response_clean (response); + + if (local_error) { + g_debug ("Got failure code %d: %s", local_error->code, local_error->message); + g_propagate_error (error, local_error); + } + + return found; +} + +void +mm_serial_parser_v1_destroy (gpointer data) +{ + MMSerialParserV1 *parser = (MMSerialParserV1 *) data; + + g_return_if_fail (parser != NULL); + + g_regex_unref (parser->regex_ok); + g_regex_unref (parser->regex_connect); + g_regex_unref (parser->regex_detailed_error); + g_regex_unref (parser->regex_unknown_error); + g_regex_unref (parser->regex_connect_failed); + + g_slice_free (MMSerialParserV1, data); +} + +typedef struct { + gpointer v1; + GRegex *regex_echo; +} MMSerialParserV1E1; + +gpointer +mm_serial_parser_v1_e1_new (void) +{ + MMSerialParserV1E1 *parser; + GRegexCompileFlags flags = G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW | G_REGEX_OPTIMIZE; + + parser = g_slice_new (MMSerialParserV1E1); + parser->v1 = mm_serial_parser_v1_new (); + + /* Does not start with '<CR><LF>' and ends with '<CR>'. */ + parser->regex_echo = g_regex_new ("^(?!\\r\\n).+\\r", flags, 0, NULL); + + return parser; +} + +gboolean +mm_serial_parser_v1_e1_parse (gpointer data, + GString *response, + GError **error) +{ + MMSerialParserV1E1 *parser = (MMSerialParserV1E1 *) data; + GMatchInfo *match_info = NULL; + + /* Remove the command echo */ + if (g_regex_match_full (parser->regex_echo, response->str, response->len, 0, 0, &match_info, NULL)) { + gchar *match = g_match_info_fetch (match_info, 0); + + g_string_erase (response, 0, strlen (match)); + g_free (match); + g_match_info_free (match_info); + } + + return mm_serial_parser_v1_parse (parser->v1, response, error); +} + +void +mm_serial_parser_v1_e1_destroy (gpointer data) +{ + MMSerialParserV1E1 *parser = (MMSerialParserV1E1 *) data; + + g_regex_unref (parser->regex_echo); + mm_serial_parser_v1_destroy (parser->v1); + + g_slice_free (MMSerialParserV1E1, data); +} diff --git a/src/mm-serial-parsers.h b/src/mm-serial-parsers.h new file mode 100644 index 0000000..3e1fb9f --- /dev/null +++ b/src/mm-serial-parsers.h @@ -0,0 +1,44 @@ +/* -*- 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. + */ + +#ifndef MM_SERIAL_PARSERS_H +#define MM_SERIAL_PARSERS_H + +#include <glib.h> + +gpointer mm_serial_parser_v0_new (void); +gboolean mm_serial_parser_v0_parse (gpointer parser, + GString *response, + GError **error); + +void mm_serial_parser_v0_destroy (gpointer parser); + + +gpointer mm_serial_parser_v1_new (void); +gboolean mm_serial_parser_v1_parse (gpointer parser, + GString *response, + GError **error); + +void mm_serial_parser_v1_destroy (gpointer parser); + + +gpointer mm_serial_parser_v1_e1_new (void); +gboolean mm_serial_parser_v1_e1_parse (gpointer parser, + GString *response, + GError **error); + +void mm_serial_parser_v1_e1_destroy (gpointer parser); + +#endif /* MM_SERIAL_PARSERS_H */ diff --git a/src/mm-serial-port.c b/src/mm-serial-port.c new file mode 100644 index 0000000..2600ae5 --- /dev/null +++ b/src/mm-serial-port.c @@ -0,0 +1,1281 @@ +/* -*- 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 <termio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <string.h> + +#include "mm-serial-port.h" +#include "mm-errors.h" +#include "mm-options.h" + +static gboolean mm_serial_port_queue_process (gpointer data); + +G_DEFINE_TYPE (MMSerialPort, mm_serial_port, MM_TYPE_PORT) + +enum { + PROP_0, + PROP_BAUD, + PROP_BITS, + PROP_PARITY, + PROP_STOPBITS, + PROP_SEND_DELAY, + + LAST_PROP +}; + +#define SERIAL_BUF_SIZE 2048 + +#define MM_SERIAL_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_SERIAL_PORT, MMSerialPortPrivate)) + +typedef struct { + 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; + + struct termios old_t; + + guint baud; + guint bits; + char parity; + guint stopbits; + guint64 send_delay; + + guint queue_schedule; + guint watch_id; + guint timeout_id; + + guint flash_id; + guint connected_id; +} MMSerialPortPrivate; + +#if 0 +static const char * +baud_to_string (int baud) +{ + const char *speed = NULL; + + switch (baud) { + case B0: + speed = "0"; + break; + case B50: + speed = "50"; + break; + case B75: + speed = "75"; + break; + case B110: + speed = "110"; + break; + case B150: + speed = "150"; + break; + case B300: + speed = "300"; + break; + case B600: + speed = "600"; + break; + case B1200: + speed = "1200"; + break; + case B2400: + speed = "2400"; + break; + case B4800: + speed = "4800"; + break; + case B9600: + speed = "9600"; + break; + case B19200: + speed = "19200"; + break; + case B38400: + speed = "38400"; + break; + case B57600: + speed = "57600"; + break; + case B115200: + speed = "115200"; + break; + case B460800: + speed = "460800"; + break; + default: + break; + } + + return speed; +} + +void +mm_serial_port_print_config (MMSerialPort *port, const char *detail) +{ + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (port); + struct termio stbuf; + int err; + + err = ioctl (priv->fd, TCGETA, &stbuf); + if (err) { + g_warning ("*** %s (%s): (%s) TCGETA error %d", + __func__, detail, mm_port_get_device (MM_PORT (port)), errno); + return; + } + + g_message ("*** %s (%s): (%s) baud rate: %d (%s)", + __func__, detail, mm_port_get_device (MM_PORT (port)), + stbuf.c_cflag & CBAUD, + baud_to_string (stbuf.c_cflag & CBAUD)); +} +#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) +{ + int speed; + + switch (i) { + case 0: + speed = B0; + break; + case 50: + speed = B50; + break; + case 75: + speed = B75; + break; + case 110: + speed = B110; + break; + case 150: + speed = B150; + break; + case 300: + speed = B300; + break; + case 600: + speed = B600; + break; + case 1200: + speed = B1200; + break; + case 2400: + speed = B2400; + break; + case 4800: + speed = B4800; + break; + case 9600: + speed = B9600; + break; + case 19200: + speed = B19200; + break; + case 38400: + speed = B38400; + break; + case 57600: + speed = B57600; + break; + case 115200: + speed = B115200; + break; + case 460800: + speed = B460800; + break; + default: + g_warning ("Invalid baudrate '%d'", i); + speed = B9600; + } + + return speed; +} + +static int +parse_bits (guint i) +{ + int bits; + + switch (i) { + case 5: + bits = CS5; + break; + case 6: + bits = CS6; + break; + case 7: + bits = CS7; + break; + case 8: + bits = CS8; + break; + default: + g_warning ("Invalid bits (%d). Valid values are 5, 6, 7, 8.", i); + bits = CS8; + } + + return bits; +} + +static int +parse_parity (char c) +{ + int parity; + + switch (c) { + case 'n': + case 'N': + parity = 0; + break; + case 'e': + case 'E': + parity = PARENB; + break; + case 'o': + case 'O': + parity = PARENB | PARODD; + break; + default: + g_warning ("Invalid parity (%c). Valid values are n, e, o", c); + parity = 0; + } + + return parity; +} + +static int +parse_stopbits (guint i) +{ + int stopbits; + + switch (i) { + case 1: + stopbits = 0; + break; + case 2: + stopbits = CSTOPB; + break; + default: + g_warning ("Invalid stop bits (%d). Valid values are 1 and 2)", i); + stopbits = 0; + } + + return stopbits; +} + +static gboolean +config_fd (MMSerialPort *self, GError **error) +{ + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); + struct termio stbuf; + int speed; + int bits; + int parity; + int stopbits; + + speed = parse_baudrate (priv->baud); + bits = parse_bits (priv->bits); + 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", + __func__, + mm_port_get_device (MM_PORT (self)), + errno); + } + + stbuf.c_iflag &= ~(IGNCR | ICRNL | IUCLC | INPCK | IXON | IXANY | IGNPAR ); + stbuf.c_oflag &= ~(OPOST | OLCUC | OCRNL | ONLCR | ONLRET); + stbuf.c_lflag &= ~(ICANON | XCASE | ECHO | ECHOE | ECHONL); + stbuf.c_lflag &= ~(ECHO | ECHOE); + stbuf.c_cc[VMIN] = 1; + 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); + + if (ioctl (priv->fd, TCSETA, &stbuf) < 0) { + g_set_error (error, + MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "%s: failed to set serial port attributes; errno %d", + __func__, errno); + return FALSE; + } + + return TRUE; +} + +static void +serial_debug (MMSerialPort *self, const char *prefix, const char *buf, int 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_string_append_c (debug, '\''); + g_debug ("(%s): %s", mm_port_get_device (MM_PORT (self)), debug->str); + g_string_truncate (debug, 0); +} + +static gboolean +mm_serial_port_send_command (MMSerialPort *self, + const char *command, + GError **error) +{ + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); + const char *s; + int status; + int eagain_count = 1000; + + if (priv->fd < 0) { + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_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, + "%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); + + /* 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); + if (status < 0) { + if (errno == EAGAIN) { + eagain_count--; + if (eagain_count <= 0) { + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED, + "Sending command failed: '%s'", strerror (errno)); + break; + } + } else { + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED, + "Sending command failed: '%s'", strerror (errno)); + break; + } + } else + s++; + + if (priv->send_delay) + usleep (priv->send_delay); + } + + return *s == '\0'; +} + +typedef struct { + char *command; + MMSerialResponseFn callback; + gpointer user_data; + guint32 timeout; + gboolean cached; +} MMQueueData; + +static void +mm_serial_port_schedule_queue_process (MMSerialPort *self) +{ + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); + GSource *source; + + if (priv->timeout_id) { + /* A command is already in progress */ + return; + } + + if (priv->queue_schedule) { + /* Already scheduled */ + return; + } + + source = g_idle_source_new (); + g_source_set_closure (source, g_cclosure_new_object (G_CALLBACK (mm_serial_port_queue_process), G_OBJECT (self))); + g_source_attach (source, NULL); + priv->queue_schedule = g_source_get_id (source); + g_source_unref (source); +} + +static void +mm_serial_port_got_response (MMSerialPort *self, GError *error) +{ + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); + MMQueueData *info; + + if (priv->timeout_id) { + g_source_remove (priv->timeout_id); + priv->timeout_id = 0; + } + + 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); + + g_free (info->command); + g_slice_free (MMQueueData, info); + } + + if (error) + g_error_free (error); + + g_string_truncate (priv->response, 0); + if (!g_queue_is_empty (priv->queue)) + mm_serial_port_schedule_queue_process (self); +} + +static gboolean +mm_serial_port_timed_out (gpointer data) +{ + MMSerialPort *self = MM_SERIAL_PORT (data); + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); + GError *error; + + priv->timeout_id = 0; + + error = g_error_new_literal (MM_SERIAL_ERROR, + MM_SERIAL_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 + get the output of the timed out command. Not sure what to do here. */ + mm_serial_port_got_response (self, error); + + return FALSE; +} + +static gboolean +mm_serial_port_queue_process (gpointer data) +{ + MMSerialPort *self = MM_SERIAL_PORT (data); + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); + MMQueueData *info; + GError *error = NULL; + + priv->queue_schedule = 0; + + info = (MMQueueData *) g_queue_peek_head (priv->queue); + if (!info) + return FALSE; + + if (info->cached) { + const char *cached = mm_serial_port_get_cached_reply (self, info->command); + + if (cached) { + g_string_append (priv->response, cached); + mm_serial_port_got_response (self, NULL); + return FALSE; + } + } + + if (mm_serial_port_send_command (self, info->command, &error)) { + GSource *source; + + source = g_timeout_source_new_seconds (info->timeout); + g_source_set_closure (source, g_cclosure_new_object (G_CALLBACK (mm_serial_port_timed_out), G_OBJECT (self))); + g_source_attach (source, NULL); + priv->timeout_id = g_source_get_id (source); + g_source_unref (source); + } else { + mm_serial_port_got_response (self, error); + } + + 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, + 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); + + return priv->response_parser_fn (priv->response_parser_user_data, response, error); +} + +static gboolean +data_available (GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + MMSerialPort *self = MM_SERIAL_PORT (data); + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); + char buf[SERIAL_BUF_SIZE + 1]; + gsize bytes_read; + GIOStatus status; + + if (condition & G_IO_HUP) { + g_string_truncate (priv->response, 0); + mm_serial_port_close (self); + return FALSE; + } + + if (condition & G_IO_ERR) { + g_string_truncate (priv->response, 0); + return TRUE; + } + + do { + GError *err = NULL; + + 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 no bytes read, just let g_io_channel wait for more data */ + if (bytes_read == 0) + break; + + if (bytes_read > 0) { + serial_debug (self, "<--", buf, bytes_read); + g_string_append_len (priv->response, 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)); + } + + if (parse_response (self, priv->response, &err)) + mm_serial_port_got_response (self, err); + } while (bytes_read == SERIAL_BUF_SIZE || status == G_IO_STATUS_AGAIN); + + return TRUE; +} + +static void +port_connected (MMSerialPort *self, GParamSpec *pspec, gpointer user_data) +{ + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); + gboolean connected; + + if (priv->fd < 0) + return; + + /* When the port is connected, drop the serial port lock so PPP can do + * something with the port. When the port is disconnected, grab the lock + * again. + */ + connected = mm_port_get_connected (MM_PORT (self)); + + if (ioctl (priv->fd, (connected ? TIOCNXCL : TIOCEXCL)) < 0) { + g_warning ("%s: (%s) could not %s serial port lock: (%d) %s", + __func__, + mm_port_get_device (MM_PORT (self)), + connected ? "drop" : "re-acquire", + errno, + strerror (errno)); + if (!connected) { + // FIXME: do something here, maybe try again in a few seconds or + // close the port and error out? + } + } +} + +gboolean +mm_serial_port_open (MMSerialPort *self, GError **error) +{ + MMSerialPortPrivate *priv; + char *devfile; + const char *device; + + g_return_val_if_fail (MM_IS_SERIAL_PORT (self), FALSE); + + priv = MM_SERIAL_PORT_GET_PRIVATE (self); + + if (priv->fd >= 0) { + /* Already open */ + return TRUE; + } + + device = mm_port_get_device (MM_PORT (self)); + + g_message ("(%s) opening serial device...", device); + devfile = g_strdup_printf ("/dev/%s", device); + errno = 0; + priv->fd = open (devfile, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY); + g_free (devfile); + + if (priv->fd < 0) { + /* nozomi isn't ready yet when the port appears, and it'll return + * ENODEV when open(2) is called on it. Make sure we can handle this + * by returning a special error in that case. + */ + g_set_error (error, + MM_SERIAL_ERROR, + (errno == ENODEV) ? MM_SERIAL_OPEN_FAILED_NO_DEVICE : MM_SERIAL_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, + "Could not lock serial device %s: %s", device, strerror (errno)); + close (priv->fd); + priv->fd = -1; + return FALSE; + } + + if (ioctl (priv->fd, TCGETA, &priv->old_t) < 0) { + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_OPEN_FAILED, + "Could not open serial device %s: %s", device, strerror (errno)); + close (priv->fd); + priv->fd = -1; + return FALSE; + } + + if (!config_fd (self, error)) { + close (priv->fd); + priv->fd = -1; + return FALSE; + } + + priv->channel = g_io_channel_unix_new (priv->fd); + g_io_channel_set_encoding (priv->channel, NULL, NULL); + priv->watch_id = g_io_add_watch (priv->channel, + G_IO_IN | G_IO_ERR | G_IO_HUP, + data_available, self); + + g_warn_if_fail (priv->connected_id == 0); + priv->connected_id = g_signal_connect (self, "notify::" MM_PORT_CONNECTED, + G_CALLBACK (port_connected), NULL); + + return TRUE; +} + +void +mm_serial_port_close (MMSerialPort *self) +{ + MMSerialPortPrivate *priv; + + g_return_if_fail (MM_IS_SERIAL_PORT (self)); + + priv = MM_SERIAL_PORT_GET_PRIVATE (self); + + if (priv->connected_id) { + g_signal_handler_disconnect (self, priv->connected_id); + priv->connected_id = 0; + } + + if (priv->fd >= 0) { + g_message ("(%s) closing serial device...", mm_port_get_device (MM_PORT (self))); + + mm_port_set_connected (MM_PORT (self), FALSE); + + if (priv->channel) { + g_source_remove (priv->watch_id); + 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; + } + + ioctl (priv->fd, TCSETA, &priv->old_t); + close (priv->fd); + priv->fd = -1; + } +} + +static void +internal_queue_command (MMSerialPort *self, + const char *command, + gboolean cached, + guint32 timeout_seconds, + MMSerialResponseFn callback, + gpointer user_data) +{ + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); + MMQueueData *info; + + g_return_if_fail (MM_IS_SERIAL_PORT (self)); + g_return_if_fail (command != NULL); + + info = g_slice_new0 (MMQueueData); + info->command = g_strdup (command); + info->cached = cached; + info->timeout = timeout_seconds; + info->callback = 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); + + g_queue_push_tail (priv->queue, info); + + if (g_queue_get_length (priv->queue) == 1) + mm_serial_port_schedule_queue_process (self); +} + +void +mm_serial_port_queue_command (MMSerialPort *self, + const char *command, + guint32 timeout_seconds, + MMSerialResponseFn callback, + gpointer user_data) +{ + internal_queue_command (self, command, FALSE, timeout_seconds, callback, user_data); +} + +void +mm_serial_port_queue_command_cached (MMSerialPort *self, + const char *command, + guint32 timeout_seconds, + MMSerialResponseFn callback, + gpointer user_data) +{ + internal_queue_command (self, command, TRUE, timeout_seconds, callback, user_data); +} + +typedef struct { + MMSerialPort *port; + speed_t current_speed; + MMSerialFlashFn callback; + gpointer user_data; +} FlashInfo; + +static gboolean +get_speed (MMSerialPort *self, speed_t *speed, GError **error) +{ + struct termios options; + + memset (&options, 0, sizeof (struct termios)); + if (tcgetattr (MM_SERIAL_PORT_GET_PRIVATE (self)->fd, &options) != 0) { + g_set_error (error, + MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "%s: tcgetattr() error %d", + __func__, errno); + return FALSE; + } + + *speed = cfgetospeed (&options); + return TRUE; +} + +static gboolean +set_speed (MMSerialPort *self, speed_t speed, GError **error) +{ + struct termios options; + int fd, count = 4; + gboolean success = FALSE; + + fd = MM_SERIAL_PORT_GET_PRIVATE (self)->fd; + + memset (&options, 0, sizeof (struct termios)); + if (tcgetattr (fd, &options) != 0) { + g_set_error (error, + MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "%s: tcgetattr() error %d", + __func__, errno); + return FALSE; + } + + cfsetispeed (&options, speed); + cfsetospeed (&options, speed); + options.c_cflag |= (CLOCAL | CREAD); + + while (count-- > 0) { + if (tcsetattr (fd, TCSANOW, &options) == 0) { + success = TRUE; + break; /* Operation successful */ + } + + /* Try a few times if EAGAIN */ + if (errno == EAGAIN) + g_usleep (100000); + else { + /* If not EAGAIN, hard error */ + g_set_error (error, + MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "%s: tcsetattr() error %d", + __func__, errno); + return FALSE; + } + } + + if (!success) { + g_set_error (error, + MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "%s: tcsetattr() retry timeout", + __func__); + return FALSE; + } + + return TRUE; +} + +static gboolean +flash_do (gpointer data) +{ + FlashInfo *info = (FlashInfo *) data; + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (info->port); + GError *error = NULL; + + priv->flash_id = 0; + + if (!set_speed (info->port, info->current_speed, &error)) + g_assert (error); + + info->callback (info->port, error, info->user_data); + g_clear_error (&error); + g_slice_free (FlashInfo, info); + return FALSE; +} + +gboolean +mm_serial_port_flash (MMSerialPort *self, + guint32 flash_time, + MMSerialFlashFn callback, + gpointer user_data) +{ + FlashInfo *info; + MMSerialPortPrivate *priv; + speed_t cur_speed = 0; + GError *error = NULL; + + 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 (priv->flash_id > 0) { + error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_OPERATION_IN_PROGRESS, + "Modem is already being flashed."); + callback (self, error, user_data); + g_error_free (error); + return FALSE; + } + + if (!get_speed (self, &cur_speed, &error)) { + callback (self, error, user_data); + g_error_free (error); + return FALSE; + } + + info = g_slice_new0 (FlashInfo); + info->port = self; + info->current_speed = cur_speed; + info->callback = callback; + info->user_data = user_data; + + if (!set_speed (self, B0, &error)) { + callback (self, error, user_data); + g_error_free (error); + return FALSE; + } + + priv->flash_id = g_timeout_add (flash_time, flash_do, info); + return TRUE; +} + +void +mm_serial_port_flash_cancel (MMSerialPort *self) +{ + MMSerialPortPrivate *priv; + + g_return_if_fail (MM_IS_SERIAL_PORT (self)); + + priv = MM_SERIAL_PORT_GET_PRIVATE (self); + + if (priv->flash_id > 0) { + g_source_remove (priv->flash_id); + priv->flash_id = 0; + } +} + +/*****************************************************************************/ + +MMSerialPort * +mm_serial_port_new (const char *name, MMPortType ptype) +{ + return MM_SERIAL_PORT (g_object_new (MM_TYPE_SERIAL_PORT, + MM_PORT_DEVICE, name, + MM_PORT_SUBSYS, MM_PORT_SUBSYS_TTY, + MM_PORT_TYPE, ptype, + NULL)); +} + +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->fd = -1; + priv->baud = 57600; + priv->bits = 8; + priv->parity = 'n'; + priv->stopbits = 1; + 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); +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_BAUD: + priv->baud = g_value_get_uint (value); + break; + case PROP_BITS: + priv->bits = g_value_get_uint (value); + break; + case PROP_PARITY: + priv->parity = g_value_get_char (value); + break; + case PROP_STOPBITS: + priv->stopbits = g_value_get_uint (value); + break; + case PROP_SEND_DELAY: + priv->send_delay = g_value_get_uint64 (value); + 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) +{ + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_BAUD: + g_value_set_uint (value, priv->baud); + break; + case PROP_BITS: + g_value_set_uint (value, priv->bits); + break; + case PROP_PARITY: + g_value_set_char (value, priv->parity); + break; + case PROP_STOPBITS: + g_value_set_uint (value, priv->stopbits); + break; + case PROP_SEND_DELAY: + g_value_set_uint64 (value, priv->send_delay); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + mm_serial_port_close (MM_SERIAL_PORT (object)); + + G_OBJECT_CLASS (mm_serial_port_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + MMSerialPort *self = MM_SERIAL_PORT (object); + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); + + g_hash_table_destroy (priv->reply_cache); + 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); +} + +static void +mm_serial_port_class_init (MMSerialPortClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMSerialPortPrivate)); + + /* Virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + /* Properties */ + g_object_class_install_property + (object_class, PROP_BAUD, + g_param_spec_uint (MM_SERIAL_PORT_BAUD, + "Baud", + "Baud rate", + 0, G_MAXUINT, 57600, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_BITS, + g_param_spec_uint (MM_SERIAL_PORT_BITS, + "Bits", + "Bits", + 5, 8, 8, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_PARITY, + g_param_spec_char (MM_SERIAL_PORT_PARITY, + "Parity", + "Parity", + 'E', 'o', 'n', + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_STOPBITS, + g_param_spec_uint (MM_SERIAL_PORT_STOPBITS, + "Stopbits", + "Stopbits", + 1, 2, 1, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_SEND_DELAY, + g_param_spec_uint64 (MM_SERIAL_PORT_SEND_DELAY, + "SendDelay", + "Send delay", + 0, G_MAXUINT64, 0, + G_PARAM_READWRITE)); +} diff --git a/src/mm-serial-port.h b/src/mm-serial-port.h new file mode 100644 index 0000000..841b4fa --- /dev/null +++ b/src/mm-serial-port.h @@ -0,0 +1,105 @@ +/* -*- 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. + */ + +#ifndef MM_SERIAL_PORT_H +#define MM_SERIAL_PORT_H + +#include <glib.h> +#include <glib/gtypes.h> +#include <glib-object.h> + +#include "mm-port.h" + +#define MM_TYPE_SERIAL_PORT (mm_serial_port_get_type ()) +#define MM_SERIAL_PORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SERIAL_PORT, MMSerialPort)) +#define MM_SERIAL_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SERIAL_PORT, MMSerialPortClass)) +#define MM_IS_SERIAL_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SERIAL_PORT)) +#define MM_IS_SERIAL_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SERIAL_PORT)) +#define MM_SERIAL_PORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SERIAL_PORT, MMSerialPortClass)) + +#define MM_SERIAL_PORT_BAUD "baud" +#define MM_SERIAL_PORT_BITS "bits" +#define MM_SERIAL_PORT_PARITY "parity" +#define MM_SERIAL_PORT_STOPBITS "stopbits" +#define MM_SERIAL_PORT_SEND_DELAY "send-delay" + +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, + GError *error, + gpointer user_data); + +typedef void (*MMSerialFlashFn) (MMSerialPort *port, + GError *error, + gpointer user_data); + +struct _MMSerialPort { + MMPort parent; +}; + +struct _MMSerialPortClass { + MMPortClass parent; +}; + +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); + +void mm_serial_port_set_response_parser (MMSerialPort *self, + MMSerialResponseParserFn fn, + gpointer user_data, + GDestroyNotify notify); + +gboolean mm_serial_port_open (MMSerialPort *self, + GError **error); + +void mm_serial_port_close (MMSerialPort *self); +void mm_serial_port_queue_command (MMSerialPort *self, + const char *command, + guint32 timeout_seconds, + MMSerialResponseFn callback, + gpointer user_data); + +void mm_serial_port_queue_command_cached (MMSerialPort *self, + const char *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/tests/Makefile.am b/src/tests/Makefile.am new file mode 100644 index 0000000..74255db --- /dev/null +++ b/src/tests/Makefile.am @@ -0,0 +1,22 @@ +INCLUDES = \ + -I$(top_srcdir)/src + +noinst_PROGRAMS = test-modem-helpers + +test_modem_helpers_SOURCES = \ + test-modem-helpers.c + +test_modem_helpers_CPPFLAGS = \ + $(MM_CFLAGS) + +test_modem_helpers_LDADD = \ + $(top_builddir)/src/libmodem-helpers.la \ + $(MM_LIBS) + +if WITH_TESTS + +check-local: test-modem-helpers + $(abs_builddir)/test-modem-helpers + +endif + diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c new file mode 100644 index 0000000..3d93423 --- /dev/null +++ b/src/tests/test-modem-helpers.c @@ -0,0 +1,479 @@ +/* -*- 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 <string.h> + +#include "mm-modem-helpers.h" + +#define MM_SCAN_TAG_STATUS "status" +#define MM_SCAN_TAG_OPER_LONG "operator-long" +#define MM_SCAN_TAG_OPER_SHORT "operator-short" +#define MM_SCAN_TAG_OPER_NUM "operator-num" +#define MM_SCAN_TAG_ACCESS_TECH "access-tech" + +typedef struct { + const char *status; + const char *oper_long; + const char *oper_short; + const char *oper_num; + const char *tech; +} OperEntry; + +#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) +{ + guint i; + GError *error = NULL; + GPtrArray *results; + + g_print ("\nTesting %s +COPS response...\n", desc); + + results = mm_gsm_parse_scan_response (reply, &error); + g_assert (results); + g_assert (error == NULL); + + g_assert (results->len == expected_results_len); + + for (i = 0; i < results->len; i++) { + GHashTable *entry = g_ptr_array_index (results, i); + const char *value; + OperEntry *expected = &expected_results[i]; + + value = g_hash_table_lookup (entry, MM_SCAN_TAG_STATUS); + g_assert (value); + g_assert (strcmp (value, expected->status) == 0); + + value = g_hash_table_lookup (entry, MM_SCAN_TAG_OPER_LONG); + if (expected->oper_long) { + g_assert (value); + g_assert (strcmp (value, expected->oper_long) == 0); + } else + g_assert (value == NULL); + + value = g_hash_table_lookup (entry, MM_SCAN_TAG_OPER_SHORT); + if (expected->oper_short) { + g_assert (value); + g_assert (strcmp (value, expected->oper_short) == 0); + } else + g_assert (value == NULL); + + value = g_hash_table_lookup (entry, MM_SCAN_TAG_OPER_NUM); + g_assert (expected->oper_num); + g_assert (value); + g_assert (strcmp (value, expected->oper_num) == 0); + + value = g_hash_table_lookup (entry, MM_SCAN_TAG_ACCESS_TECH); + if (expected->tech) { + g_assert (value); + g_assert (strcmp (value, expected->tech) == 0); + } else + g_assert (value == NULL); + } + + mm_gsm_destroy_scan_data (results); +} + +static void +test_cops_response_tm506 (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"\",\"T-Mobile\",\"31026\",0),(2,\"T - Mobile\",\"T - Mobile\",\"310260\"),2),(1,\"AT&T\",\"AT&T\",\"310410\"),0)"; + static OperEntry expected[] = { + { "2", NULL, "T-Mobile", "31026", "0" }, + { "2", "T - Mobile", "T - Mobile", "310260", "2" }, + { "1", "AT&T", "AT&T", "310410", "0" } + }; + + test_results ("TM-506", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_gt3gplus (void *f, gpointer d) +{ + const char *reply = "+COPS: (1,\"T-Mobile US\",\"TMO US\",\"31026\",0),(1,\"Cingular\",\"Cingular\",\"310410\",0),,(0, 1, 3),(0-2)"; + static OperEntry expected[] = { + { "1", "T-Mobile US", "TMO US", "31026", "0" }, + { "1", "Cingular", "Cingular", "310410", "0" }, + }; + + test_results ("GlobeTrotter 3G+ (nozomi)", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_ac881 (void *f, gpointer d) +{ + const char *reply = "+COPS: (1,\"T-Mobile\",\"TMO\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),)"; + static OperEntry expected[] = { + { "1", "T-Mobile", "TMO", "31026", "0" }, + { "1", "AT&T", "AT&T", "310410", "2" }, + { "1", "AT&T", "AT&T", "310410", "0" }, + }; + + test_results ("Sierra AirCard 881", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_gtmax36 (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T-Mobile US\",\"TMO US\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0, 1,)"; + static OperEntry expected[] = { + { "2", "T-Mobile US", "TMO US", "31026", "0" }, + { "1", "AT&T", "AT&T", "310410", "2" }, + { "1", "AT&T", "AT&T", "310410", "0" }, + }; + + test_results ("Option GlobeTrotter MAX 3.6", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_ac860 (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T-Mobile\",\"TMO\",\"31026\",0),(1,\"Cingular\",\"Cinglr\",\"310410\",2),(1,\"Cingular\",\"Cinglr\",\"310410\",0),,)"; + static OperEntry expected[] = { + { "2", "T-Mobile", "TMO", "31026", "0" }, + { "1", "Cingular", "Cinglr", "310410", "2" }, + { "1", "Cingular", "Cinglr", "310410", "0" }, + }; + + test_results ("Sierra AirCard 860", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_gtm378 (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T-Mobile\",\"T-Mobile\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0, 1, 3),(0-2)"; + static OperEntry expected[] = { + { "2", "T-Mobile", "T-Mobile", "31026", "0" }, + { "1", "AT&T", "AT&T", "310410", "2" }, + { "1", "AT&T", "AT&T", "310410", "0" }, + }; + + test_results ("Option GTM378", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_motoc (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T-Mobile\",\"\",\"310260\"),(0,\"Cingular Wireless\",\"\",\"310410\")"; + static OperEntry expected[] = { + { "2", "T-Mobile", NULL, "310260", NULL }, + { "0", "Cingular Wireless", NULL, "310410", NULL }, + }; + + test_results ("BUSlink SCWi275u (Motorola C-series)", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_mf627a (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"AT&T@\",\"AT&TD\",\"310410\",0),(3,\"Voicestream Wireless Corporation\",\"VSTREAM\",\"31026\",0),"; + static OperEntry expected[] = { + { "2", "AT&T@", "AT&TD", "310410", "0" }, + { "3", "Voicestream Wireless Corporation", "VSTREAM", "31026", "0" }, + }; + + test_results ("ZTE MF627 (A)", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_mf627b (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"AT&Tp\",\"AT&T@\",\"310410\",0),(3,\"\",\"\",\"31026\",0),"; + static OperEntry expected[] = { + { "2", "AT&Tp", "AT&T@", "310410", "0" }, + { "3", NULL, NULL, "31026", "0" }, + }; + + test_results ("ZTE MF627 (B)", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_e160g (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T-Mobile\",\"TMO\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),(0,1,2)"; + static OperEntry expected[] = { + { "2", "T-Mobile", "TMO", "31026", "0" }, + { "1", "AT&T", "AT&T", "310410", "0" }, + }; + + test_results ("Huawei E160G", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_mercury (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"\",\"\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),(1,\"T-Mobile\",\"TMO\",\"31026\",0),,(0,1,2,3,4),(0,1,2)"; + static OperEntry expected[] = { + { "2", NULL, NULL, "310410", "2" }, + { "1", "AT&T", "AT&T", "310410", "0" }, + { "1", "T-Mobile", "TMO", "31026", "0" }, + }; + + test_results ("Sierra AT&T USBConnect Mercury", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_quicksilver (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"AT&T\",\"\",\"310410\",0),(2,\"\",\"\",\"3104100\",2),(1,\"AT&T\",\"\",\"310260\",0),,(0-4),(0-2)"; + static OperEntry expected[] = { + { "2", "AT&T", NULL, "310410", "0" }, + { "2", NULL, NULL, "3104100", "2" }, + { "1", "AT&T", NULL, "310260", "0" }, + }; + + test_results ("Option AT&T Quicksilver", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_icon225 (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T-Mobile US\",\"TMO US\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0, 1, 3),(0-2)"; + static OperEntry expected[] = { + { "2", "T-Mobile US", "TMO US", "31026", "0" }, + { "1", "AT&T", "AT&T", "310410", "0" }, + }; + + test_results ("Option iCON 225", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_icon452 (void *f, gpointer d) +{ + const char *reply = "+COPS: (1,\"T-Mobile US\",\"TMO US\",\"31026\",0),(2,\"T-Mobile\",\"T-Mobile\",\"310260\",2),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),(0,1,2)"; + static OperEntry expected[] = { + { "1", "T-Mobile US", "TMO US", "31026", "0" }, + { "2", "T-Mobile", "T-Mobile", "310260", "2" }, + { "1", "AT&T", "AT&T", "310410", "2" }, + { "1", "AT&T", "AT&T", "310410", "0" } + }; + + test_results ("Option iCON 452", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_f3507g (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T - Mobile\",\"T - Mobile\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2)"; + static OperEntry expected[] = { + { "2", "T - Mobile", "T - Mobile", "31026", "0" }, + { "1", "AT&T", "AT&T", "310410", "0" }, + { "1", "AT&T", "AT&T", "310410", "2" } + }; + + test_results ("Ericsson F3507g", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_f3607gw (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T - Mobile\",\"T - Mobile\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\"),2),(1,\"AT&T\",\"AT&T\",\"310410\"),0)"; + static OperEntry expected[] = { + { "2", "T - Mobile", "T - Mobile", "31026", "0" }, + { "1", "AT&T", "AT&T", "310410", "2" }, + { "1", "AT&T", "AT&T", "310410", "0" } + }; + + test_results ("Ericsson F3607gw", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_mc8775 (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T-Mobile\",\"T-Mobile\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),(0,1,2)"; + static OperEntry expected[] = { + { "2", "T-Mobile", "T-Mobile", "31026", "0" }, + { "1", "AT&T", "AT&T", "310410", "2" }, + { "1", "AT&T", "AT&T", "310410", "0" } + }; + + test_results ("Sierra MC8775", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_n80 (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T - Mobile\",,\"31026\"),(1,\"Einstein PCS\",,\"31064\"),(1,\"Cingular\",,\"31041\"),,(0,1,3),(0,2)"; + static OperEntry expected[] = { + { "2", "T - Mobile", NULL, "31026", NULL }, + { "1", "Einstein PCS", NULL, "31064", NULL }, + { "1", "Cingular", NULL, "31041", NULL }, + }; + + test_results ("Nokia N80", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_e1550 (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T-Mobile\",\"TMO\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),(0,1,2)"; + static OperEntry expected[] = { + { "2", "T-Mobile", "TMO", "31026", "0" }, + { "1", "AT&T", "AT&T", "310410", "0" }, + }; + + test_results ("Huawei E1550", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_mf622 (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T-Mobile\",\"T-Mobile\",\"31026\",0),(1,\"\",\"\",\"310410\",0),"; + static OperEntry expected[] = { + { "2", "T-Mobile", "T-Mobile", "31026", "0" }, + { "1", NULL, NULL, "310410", "0" }, + }; + + test_results ("ZTE MF622", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_e226 (void *f, gpointer d) +{ + const char *reply = "+COPS: (1,\"\",\"\",\"31026\",0),(1,\"\",\"\",\"310410\",2),(1,\"\",\"\",\"310410\",0),,(0,1,3,4),(0,1,2)"; + static OperEntry expected[] = { + { "1", NULL, NULL, "31026", "0" }, + { "1", NULL, NULL, "310410", "2" }, + { "1", NULL, NULL, "310410", "0" }, + }; + + test_results ("Huawei E226", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_xu870 (void *f, gpointer d) +{ + const char *reply = "+COPS: (0,\"AT&T MicroCell\",\"AT&T MicroCell\",\"310410\",2)\r\n+COPS: (1,\"AT&T MicroCell\",\"AT&T MicroCell\",\"310410\",0)\r\n+COPS: (1,\"T-Mobile\",\"TMO\",\"31026\",0)\r\n"; + static OperEntry expected[] = { + { "0", "AT&T MicroCell", "AT&T MicroCell", "310410", "2" }, + { "1", "AT&T MicroCell", "AT&T MicroCell", "310410", "0" }, + { "1", "T-Mobile", "TMO", "31026", "0" }, + }; + + test_results ("Novatel XU870", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_gtultraexpress (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T-Mobile US\",\"TMO US\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),(0,1,2)"; + static OperEntry expected[] = { + { "2", "T-Mobile US", "TMO US", "31026", "0" }, + { "1", "AT&T", "AT&T", "310410", "2" }, + { "1", "AT&T", "AT&T", "310410", "0" }, + }; + + test_results ("Option GlobeTrotter Ultra Express", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_n2720 (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T - Mobile\",,\"31026\",0),\r\n(1,\"AT&T\",,\"310410\",0),,(0,1,3),(0,2)"; + static OperEntry expected[] = { + { "2", "T - Mobile", NULL, "31026", "0" }, + { "1", "AT&T", NULL, "310410", "0" }, + }; + + test_results ("Nokia 2720", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_gobi (void *f, gpointer d) +{ + const char *reply = "+COPS: (2,\"T-Mobile\",\"T-Mobile\",\"31026\",0),(1,\"AT&T\",\"AT&T\",\"310410\",2),(1,\"AT&T\",\"AT&T\",\"310410\",0),,(0,1,2,3,4),(0,1,2)"; + static OperEntry expected[] = { + { "2", "T-Mobile", "T-Mobile", "31026", "0" }, + { "1", "AT&T", "AT&T", "310410", "2" }, + { "1", "AT&T", "AT&T", "310410", "0" }, + }; + + test_results ("Qualcomm Gobi", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_gsm_invalid (void *f, gpointer d) +{ + const char *reply = "+COPS: (0,1,2,3),(1,2,3,4)"; + GPtrArray *results; + GError *error = NULL; + + results = mm_gsm_parse_scan_response (reply, &error); + g_assert (results != NULL); + g_assert (error == NULL); +} + +static void +test_cops_response_umts_invalid (void *f, gpointer d) +{ + const char *reply = "+COPS: (0,1,2,3,4),(1,2,3,4,5)"; + GPtrArray *results; + GError *error = NULL; + + results = mm_gsm_parse_scan_response (reply, &error); + g_assert (results != NULL); + g_assert (error == NULL); +} + + +typedef void (*TCFunc)(void); + +#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (TCFunc) t, NULL) + +int main (int argc, char **argv) +{ + GTestSuite *suite; + + g_test_init (&argc, &argv, NULL); + + suite = g_test_get_root (); + + g_test_suite_add (suite, TESTCASE (test_cops_response_tm506, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_gt3gplus, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_ac881, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_gtmax36, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_ac860, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_gtm378, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_motoc, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_mf627a, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_mf627b, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_e160g, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_mercury, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_quicksilver, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_icon225, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_icon452, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_f3507g, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_f3607gw, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_mc8775, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_n80, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_e1550, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_mf622, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_e226, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_xu870, NULL)); + 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_gsm_invalid, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_umts_invalid, NULL)); + + return g_test_run (); +} + |