diff options
37 files changed, 4911 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..12af19c8 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Tambet Ingo <tambet@gmail.com> + diff --git a/COPYING b/COPYING new file mode 120000 index 00000000..0b6cbf81 --- /dev/null +++ b/COPYING @@ -0,0 +1 @@ +/usr/share/automake-1.10/COPYING
\ No newline at end of file diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ChangeLog diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..e0de740d --- /dev/null +++ b/Makefile.am @@ -0,0 +1,30 @@ +SUBDIRS = src plugins introspection + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = ModemManager.pc + +dbusservicedir = $(DBUS_SYS_DIR) +dbusservice_DATA = org.freedesktop.ModemManager.conf + +dbusactivationdir = $(datadir)/dbus-1/system-services +dbusactivation_in_files = org.freedesktop.ModemManager.service.in +dbusactivation_DATA = $(dbusactivation_in_files:.service.in=.service) + +%service: %service.in + $(edit) $< >$@ + +edit = @sed \ + -e 's|@sbindir[@]|$(sbindir)|g' \ + -e 's|@sysconfdir[@]|$(sysconfdir)|g' \ + -e 's|@localstatedir[@]|$(localstatedir)|g' \ + -e 's|@libexecdir[@]|$(libexecdir)|g' + +DISTCLEANFILES = \ + ModemManager.pc \ + $(dbusactivation_DATA) + + +EXTRA_DIST = \ + ModemManager.pc.in \ + $(dbusservice_DATA) \ + $(dbusactivation_in_files) diff --git a/ModemManager.pc.in b/ModemManager.pc.in new file mode 100644 index 00000000..9214209a --- /dev/null +++ b/ModemManager.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ +libgnome_serverdir=@libexecdir@ + +Name: ModemManager +Description: DBus service to interact with modems. +Requires: dbus-glib-1 glib-2.0 +Version: @VERSION@ +Cflags: -I${includedir}/ModemManager +Libs: -L${libdir} -lmm diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..7d7a26dc --- /dev/null +++ b/autogen.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Run this to generate all the initial makefiles, etc. + +srcdir=`dirname $0` +test -z "$srcdir" && srcdir=. +REQUIRED_AUTOMAKE_VERSION=1.7 +PKG_NAME=ModemManager + +(test -f $srcdir/configure.in \ + && test -f $srcdir/src/mm-modem.c) || { + echo -n "**Error**: Directory "\`$srcdir\'" does not look like the" + echo " top-level $PKG_NAME directory" + exit 1 +} + +(cd $srcdir; + autoreconf --install --symlink && + autoreconf && + ./configure --enable-maintainer-mode $@ +) diff --git a/configure.in b/configure.in new file mode 100644 index 00000000..98f2e1c8 --- /dev/null +++ b/configure.in @@ -0,0 +1,24 @@ +AC_PREREQ(2.52) + +AC_INIT(ModemManager, 0.1, tambet@gmail.com, ModemManager) +AM_INIT_AUTOMAKE([]) +AM_MAINTAINER_MODE + +AC_CONFIG_HEADERS(config.h) + +dnl Required programs +AC_PROG_CC +AM_PROG_CC_C_O +AC_PROG_INSTALL +AC_PROG_LIBTOOL + +PKG_CHECK_MODULES(MM, dbus-glib-1 >= 0.75 glib-2.0 >= 2.14 gmodule-2.0 gobject-2.0 hal) + +AC_CONFIG_FILES([ +Makefile +src/Makefile +plugins/Makefile +introspection/Makefile +ModemManager.pc +]) +AC_OUTPUT diff --git a/introspection/Makefile.am b/introspection/Makefile.am new file mode 100644 index 00000000..a3c5b122 --- /dev/null +++ b/introspection/Makefile.am @@ -0,0 +1,4 @@ +EXTRA_DIST = \ + mm-manager.xml \ + mm-modem.xml + diff --git a/introspection/mm-manager.xml b/introspection/mm-manager.xml new file mode 100644 index 00000000..55912905 --- /dev/null +++ b/introspection/mm-manager.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<node name="/"> + <interface name="org.freedesktop.ModemManager"> + <method name="EnumerateDevices"> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_manager_enumerate_devices"/> + <arg name="devices" type="ao" direction="out"/> + </method> + + <signal name="DeviceAdded"> + <arg name="device" type="o"/> + </signal> + + <signal name="DeviceRemoved"> + <arg name="device" type="o"/> + </signal> + + </interface> +</node> diff --git a/introspection/mm-modem.xml b/introspection/mm-modem.xml new file mode 100644 index 00000000..44bb72df --- /dev/null +++ b/introspection/mm-modem.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<node name="/"> + <interface name="org.freedesktop.ModemManager.Modem"> + <method name="Enable"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_modem_enable"/> + <arg name="enable" type="b" direction="in"/> + </method> + + <method name="SetPin"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_modem_set_pin"/> + <arg name="secret" type="s" direction="in"/> + </method> + + <method name="Register"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_modem_register"/> + <arg name="network_id" type="s" direction="in"/> + </method> + + <method name="Connect"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_modem_connect"/> + <arg name="number" type="s" direction="in"/> + <arg name="apn" type="s" direction="in"/> + </method> + + <method name="Disconnect"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_modem_disconnect"/> + </method> + + <method name="Scan"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_modem_scan"/> + <arg name="results" type="aa{ss}" direction="out"/> + </method> + + <method name="GetSignalQuality"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_modem_get_signal_quality"/> + <arg name="quality" type="u" direction="out"/> + </method> + + <method name="SetBand"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_modem_set_band"/> + <arg name="band" type="u" direction="in"/> + </method> + + <method name="GetBand"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_modem_get_band"/> + <arg name="band" type="u" direction="out"/> + </method> + + <method name="SetNetworkMode"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_modem_set_network_mode"/> + <arg name="mode" type="u" direction="in"/> + </method> + + <method name="GetNetworkMode"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_modem_get_network_mode"/> + <arg name="mode" type="u" direction="out"/> + </method> + + + <property name="DataDevice" type="s" access="read"/> + <property name="Driver" type="s" access="read"/> + <property name="Type" type="u" access="read"/> + + <signal name="SignalQuality"> + <arg name="quality" type="u"/> + </signal> + + <signal name="NetworkMode"> + <arg name="mode" type="u"/> + </signal> + + </interface> +</node> diff --git a/org.freedesktop.ModemManager.conf b/org.freedesktop.ModemManager.conf new file mode 100644 index 00000000..a4af7a93 --- /dev/null +++ b/org.freedesktop.ModemManager.conf @@ -0,0 +1,19 @@ +<!DOCTYPE busconfig PUBLIC + "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <policy user="root"> + <allow own="org.freedesktop.ModemManager"/> + + <allow send_destination="org.freedesktop.ModemManager"/> + <allow send_interface="org.freedesktop.ModemManager"/> + </policy> + <policy context="default"> + <deny own="org.freedesktop.ModemManager"/> + + <allow send_destination="org.freedesktop.ModemManager"/> + <allow send_interface="org.freedesktop.ModemManager"/> + </policy> + + <limit name="max_replies_per_connection">512</limit> +</busconfig> diff --git a/org.freedesktop.ModemManager.service.in b/org.freedesktop.ModemManager.service.in new file mode 100644 index 00000000..2b73ccea --- /dev/null +++ b/org.freedesktop.ModemManager.service.in @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=org.freedesktop.ModemManager +Exec=@sbindir@/modem-manager +User=root diff --git a/plugins/Makefile.am b/plugins/Makefile.am new file mode 100644 index 00000000..1ca505ac --- /dev/null +++ b/plugins/Makefile.am @@ -0,0 +1,14 @@ +pkglib_LTLIBRARIES = \ + libmm-plugin-huawei.la + +libmm_plugin_huawei_la_SOURCES = \ + mm-modem-huawei.c \ + mm-modem-huawei.h \ + mm-plugin-huawei.c \ + mm-plugin-huawei.h + +libmm_plugin_huawei_la_CPPFLAGS = \ + $(MM_CFLAGS) \ + -I$(top_srcdir)/src + +libmm_plugin_huawei_la_LDFLAGS = -modeule -avoid-version diff --git a/plugins/mm-modem-huawei.c b/plugins/mm-modem-huawei.c new file mode 100644 index 00000000..990d1d03 --- /dev/null +++ b/plugins/mm-modem-huawei.c @@ -0,0 +1,556 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "mm-modem-huawei.h" +#include "mm-modem-error.h" +#include "mm-callback-info.h" + +static MMModem *parent_class_iface = NULL; + +static void modem_init (MMModem *modem_class); + +G_DEFINE_TYPE_EXTENDED (MMModemHuawei, mm_modem_huawei, MM_TYPE_GENERIC_GSM, + 0, G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)) + +#define MM_MODEM_HUAWEI_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_HUAWEI, MMModemHuaweiPrivate)) + +typedef struct { + MMSerial *monitor_device; +} MMModemHuaweiPrivate; + +enum { + PROP_0, + PROP_MONITOR_DEVICE, + + LAST_PROP +}; + +MMModem * +mm_modem_huawei_new (const char *data_device, + const char *monitor_device, + const char *driver) +{ + g_return_val_if_fail (data_device != NULL, NULL); + g_return_val_if_fail (monitor_device != NULL, NULL); + g_return_val_if_fail (driver != NULL, NULL); + + return MM_MODEM (g_object_new (MM_TYPE_MODEM_HUAWEI, + MM_SERIAL_DEVICE, data_device, + MM_MODEM_DRIVER, driver, + MM_MODEM_HUAWEI_MONITOR_DEVICE, monitor_device, + NULL)); +} + + +/*****************************************************************************/ + +static void +parse_monitor_line (MMModem *modem, char *buf) +{ + char **lines; + char **iter; + + lines = g_strsplit (buf, "\r\n", 0); + + for (iter = lines; iter && *iter; iter++) { + char *line = *iter; + + g_strstrip (line); + if (strlen (line) < 1 || line[0] != '^') + continue; + + line += 1; + + if (!strncmp (line, "RSSI:", 5)) { + int quality = atoi (line + 5); + + if (quality == 99) + /* 99 means unknown */ + quality = 0; + else + /* Normalize the quality */ + quality = quality * 100 / 31; + + g_debug ("Signal quality: %d", quality); + mm_modem_signal_quality (modem, (guint32) quality); + } else if (!strncmp (line, "MODE:", 5)) { + MMModemNetworkMode mode = 0; + int a; + int b; + + if (sscanf (line + 5, "%d,%d", &a, &b)) { + if (a == 3 && b == 2) + mode = MM_MODEM_NETWORK_MODE_GPRS; + else if (a == 3 && b == 3) + mode = MM_MODEM_NETWORK_MODE_EDGE; + else if (a == 5 && b == 4) + mode = MM_MODEM_NETWORK_MODE_3G; + else if (a ==5 && b == 5) + mode = MM_MODEM_NETWORK_MODE_HSDPA; + + if (mode) { + g_debug ("Mode: %d", mode); + mm_modem_network_mode (modem, mode); + } + } + } + } + + g_strfreev (lines); +} + +static gboolean +monitor_device_got_data (GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + gsize bytes_read; + char buf[4096]; + GIOStatus status; + + if (condition & G_IO_IN) { + do { + status = g_io_channel_read_chars (source, buf, 4096, &bytes_read, NULL); + + if (bytes_read) { + buf[bytes_read] = '\0'; + parse_monitor_line (MM_MODEM (data), buf); + } + } while (bytes_read == 4096 || status == G_IO_STATUS_AGAIN); + } + + if (condition & G_IO_HUP || condition & G_IO_ERR) { + return FALSE; + } + + return TRUE; +} + +static void +enable (MMModem *modem, + gboolean enable, + MMModemFn callback, + gpointer user_data) +{ + MMModemHuaweiPrivate *priv = MM_MODEM_HUAWEI_GET_PRIVATE (modem); + + if (enable) { + GIOChannel *channel; + + mm_serial_open (priv->monitor_device); + channel = mm_serial_get_io_channel (priv->monitor_device); + g_io_add_watch (channel, G_IO_IN | G_IO_ERR | G_IO_HUP, + monitor_device_got_data, modem); + + g_io_channel_unref (channel); + } else + mm_serial_close (priv->monitor_device); + + parent_class_iface->enable (modem, enable, callback, user_data); +} + +static gboolean +parse_syscfg (const char *reply, int *mode_a, int *mode_b, guint32 *band, int *unknown1, int *unknown2) +{ + if (strncmp (reply, "^SYSCFG:", 8)) + return FALSE; + + if (sscanf (reply + 8, "%d,%d,%x,%d,%d", mode_a, mode_b, band, unknown1, unknown2)) + return TRUE; + + return FALSE; +} + +static void +get_network_mode_done (MMSerial *serial, const char *reply, gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + int a, b, u1, u2; + guint32 band; + + if (parse_syscfg (reply, &a, &b, &band, &u1, &u2)) { + if (a == 2 && b == 1) + info->uint_result = MM_MODEM_NETWORK_MODE_PREFER_2G; + else if (a == 2 && b == 2) + info->uint_result = MM_MODEM_NETWORK_MODE_PREFER_3G; + else if (a == 13 && b == 1) + info->uint_result = MM_MODEM_NETWORK_MODE_GPRS; + else if (a == 14 && b == 2) + info->uint_result = MM_MODEM_NETWORK_MODE_3G; + } + + if (info->uint_result == 0) + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "%s", "Could not parse network mode results"); + + mm_callback_info_schedule (info); +} + +static void +get_network_mode (MMModem *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + char *terminators = "\r\n"; + guint id = 0; + + info = mm_callback_info_uint_new (modem, callback, user_data); + + if (mm_serial_send_command_string (MM_SERIAL (modem), "AT^SYSCFG?")) + id = mm_serial_get_reply (MM_SERIAL (modem), 10, terminators, get_network_mode_done, info); + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Getting network mode failed."); + mm_callback_info_schedule (info); + } +} + +static void +set_network_mode_done (MMSerial *serial, + int reply_index, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + switch (reply_index) { + case 0: + /* Success */ + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Setting network mode failed"); + break; + } + + mm_callback_info_schedule (info); +} + +static void +set_network_mode_get_done (MMSerial *serial, const char *reply, gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + int a, b, u1, u2; + guint32 band; + guint id = 0; + + if (parse_syscfg (reply, &a, &b, &band, &u1, &u2)) { + char *responses[] = { "OK", "ERROR", NULL }; + char *command; + + a = GPOINTER_TO_INT (mm_callback_info_get_data (info, "mode-a")); + b = GPOINTER_TO_INT (mm_callback_info_get_data (info, "mode-b")); + command = g_strdup_printf ("AT^SYSCFG=%d,%d,%X,%d,%d", a, b, band, u1, u2); + + if (mm_serial_send_command_string (serial, command)) + id = mm_serial_wait_for_reply (serial, 3, responses, responses, set_network_mode_done, info); + + g_free (command); + } + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "%s", "Could not set network mode"); + mm_callback_info_schedule (info); + } +} + +static void +set_network_mode (MMModem *modem, + MMModemNetworkMode mode, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + char *terminators = "\r\n"; + guint id = 0; + + info = mm_callback_info_new (modem, callback, user_data); + + switch (mode) { + case MM_MODEM_NETWORK_MODE_ANY: + /* Do nothing */ + mm_callback_info_schedule (info); + return; + break; + case MM_MODEM_NETWORK_MODE_GPRS: + case MM_MODEM_NETWORK_MODE_EDGE: + mm_callback_info_set_data (info, "mode-a", GINT_TO_POINTER (13), NULL); + mm_callback_info_set_data (info, "mode-b", GINT_TO_POINTER (1), NULL); + break; + case MM_MODEM_NETWORK_MODE_3G: + case MM_MODEM_NETWORK_MODE_HSDPA: + mm_callback_info_set_data (info, "mode-a", GINT_TO_POINTER (14), NULL); + mm_callback_info_set_data (info, "mode-b", GINT_TO_POINTER (2), NULL); + break; + case MM_MODEM_NETWORK_MODE_PREFER_2G: + mm_callback_info_set_data (info, "mode-a", GINT_TO_POINTER (2), NULL); + mm_callback_info_set_data (info, "mode-b", GINT_TO_POINTER (1), NULL); + break; + case MM_MODEM_NETWORK_MODE_PREFER_3G: + mm_callback_info_set_data (info, "mode-a", GINT_TO_POINTER (2), NULL); + mm_callback_info_set_data (info, "mode-b", GINT_TO_POINTER (2), NULL); + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Invalid mode."); + mm_callback_info_schedule (info); + return; + break; + } + + if (mm_serial_send_command_string (MM_SERIAL (modem), "AT^SYSCFG?")) + id = mm_serial_get_reply (MM_SERIAL (modem), 10, terminators, set_network_mode_get_done, info); + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Setting network mode failed."); + mm_callback_info_schedule (info); + } +} + +static void +get_band_done (MMSerial *serial, const char *reply, gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + int a, b, u1, u2; + guint32 band; + + info->uint_result = 0xdeadbeaf; + + if (parse_syscfg (reply, &a, &b, &band, &u1, &u2)) { + if (band == 0x3FFFFFFF) + info->uint_result = MM_MODEM_BAND_ANY; + else if (band == 0x400380) + info->uint_result = MM_MODEM_BAND_DCS; + else if (band == 0x200000) + info->uint_result = MM_MODEM_BAND_PCS; + } + + if (info->uint_result == 0xdeadbeaf) { + info->uint_result = 0; + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "%s", "Could not parse band results"); + } + + mm_callback_info_schedule (info); +} + +static void +get_band (MMModem *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + char *terminators = "\r\n"; + guint id = 0; + + info = mm_callback_info_uint_new (modem, callback, user_data); + + if (mm_serial_send_command_string (MM_SERIAL (modem), "AT^SYSCFG?")) + id = mm_serial_get_reply (MM_SERIAL (modem), 10, terminators, get_band_done, info); + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Getting band failed."); + mm_callback_info_schedule (info); + } +} + +static void +set_band_done (MMSerial *serial, + int reply_index, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + switch (reply_index) { + case 0: + /* Success */ + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Setting band failed"); + break; + } + + mm_callback_info_schedule (info); +} + +static void +set_band_get_done (MMSerial *serial, const char *reply, gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + int a, b, u1, u2; + guint32 band; + guint id = 0; + + if (parse_syscfg (reply, &a, &b, &band, &u1, &u2)) { + char *responses[] = { "OK", "ERROR", NULL }; + char *command; + + band = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "band")); + command = g_strdup_printf ("AT^SYSCFG=%d,%d,%X,%d,%d", a, b, band, u1, u2); + + if (mm_serial_send_command_string (serial, command)) + id = mm_serial_wait_for_reply (serial, 3, responses, responses, set_band_done, info); + + g_free (command); + } + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "%s", "Could not set band"); + mm_callback_info_schedule (info); + } +} + +static void +set_band (MMModem *modem, + MMModemBand band, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + char *terminators = "\r\n"; + guint id = 0; + + info = mm_callback_info_new (modem, callback, user_data); + + switch (band) { + case MM_MODEM_BAND_ANY: + mm_callback_info_set_data (info, "band", GUINT_TO_POINTER (0x3FFFFFFF), NULL); + break; + case MM_MODEM_BAND_EGSM: + case MM_MODEM_BAND_DCS: + case MM_MODEM_BAND_U2100: + mm_callback_info_set_data (info, "band", GUINT_TO_POINTER (0x400380), NULL); + break; + case MM_MODEM_BAND_PCS: + mm_callback_info_set_data (info, "band", GUINT_TO_POINTER (0x200000), NULL); + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Invalid band."); + mm_callback_info_schedule (info); + return; + break; + } + + if (mm_serial_send_command_string (MM_SERIAL (modem), "AT^SYSCFG?")) + id = mm_serial_get_reply (MM_SERIAL (modem), 10, terminators, set_band_get_done, info); + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Setting band failed."); + mm_callback_info_schedule (info); + } +} + +/*****************************************************************************/ + +static void +modem_init (MMModem *modem_class) +{ + parent_class_iface = g_type_interface_peek_parent (modem_class); + + /* interface implementation */ + modem_class->enable = enable; + modem_class->set_network_mode = set_network_mode; + modem_class->get_network_mode = get_network_mode; + modem_class->set_band = set_band; + modem_class->get_band = get_band; +} + +static void +mm_modem_huawei_init (MMModemHuawei *self) +{ +} + +static GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + MMModemHuaweiPrivate *priv; + + object = G_OBJECT_CLASS (mm_modem_huawei_parent_class)->constructor (type, + n_construct_params, + construct_params); + if (!object) + return NULL; + + priv = MM_MODEM_HUAWEI_GET_PRIVATE (object); + + if (!priv->monitor_device) { + g_warning ("No monitor device provided"); + g_object_unref (object); + return NULL; + } + + return object; +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + MMModemHuaweiPrivate *priv = MM_MODEM_HUAWEI_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_MONITOR_DEVICE: + /* Construct only */ + priv->monitor_device = MM_SERIAL (g_object_new (MM_TYPE_SERIAL, + MM_SERIAL_DEVICE, g_value_get_string (value), + NULL)); + 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) +{ + MMModemHuaweiPrivate *priv = MM_MODEM_HUAWEI_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_MONITOR_DEVICE: + g_value_set_string (value, mm_serial_get_device (priv->monitor_device)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} +static void +finalize (GObject *object) +{ + MMModemHuaweiPrivate *priv = MM_MODEM_HUAWEI_GET_PRIVATE (object); + + if (priv->monitor_device) + g_object_unref (priv->monitor_device); + + G_OBJECT_CLASS (mm_modem_huawei_parent_class)->finalize (object); +} + +static void +mm_modem_huawei_class_init (MMModemHuaweiClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMModemHuaweiPrivate)); + + /* Virtual methods */ + object_class->constructor = constructor; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->finalize = finalize; + + /* Properties */ + g_object_class_install_property + (object_class, PROP_MONITOR_DEVICE, + g_param_spec_string (MM_MODEM_HUAWEI_MONITOR_DEVICE, + "MonitorDevice", + "Monitor device", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} diff --git a/plugins/mm-modem-huawei.h b/plugins/mm-modem-huawei.h new file mode 100644 index 00000000..dd377581 --- /dev/null +++ b/plugins/mm-modem-huawei.h @@ -0,0 +1,31 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#ifndef MM_MODEM_HUAWEI_H +#define MM_MODEM_HUAWEI_H + +#include "mm-generic-gsm.h" + +#define MM_TYPE_MODEM_HUAWEI (mm_modem_huawei_get_type ()) +#define MM_MODEM_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_HUAWEI, MMModemHuawei)) +#define MM_MODEM_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_MODEM_HUAWEI, MMModemHuaweiClass)) +#define MM_IS_MODEM_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_HUAWEI)) +#define MM_IS_MODEM_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_MODEM_HUAWEI)) +#define MM_MODEM_HUAWEI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_MODEM_HUAWEI, MMModemHuaweiClass)) + +#define MM_MODEM_HUAWEI_MONITOR_DEVICE "monitor-device" + +typedef struct { + MMGenericGsm parent; +} MMModemHuawei; + +typedef struct { + MMGenericGsmClass parent; +} MMModemHuaweiClass; + +GType mm_modem_huawei_get_type (void); + +MMModem *mm_modem_huawei_new (const char *data_device, + const char *monitor_device, + const char *driver); + +#endif /* MM_MODEM_HUAWEI_H */ diff --git a/plugins/mm-plugin-huawei.c b/plugins/mm-plugin-huawei.c new file mode 100644 index 00000000..6c621fdb --- /dev/null +++ b/plugins/mm-plugin-huawei.c @@ -0,0 +1,155 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#include <string.h> +#include <gmodule.h> +#include "mm-plugin-huawei.h" +#include "mm-modem-huawei.h" + +static void plugin_init (MMPlugin *plugin_class); + +G_DEFINE_TYPE_EXTENDED (MMPluginHuawei, mm_plugin_huawei, G_TYPE_OBJECT, + 0, G_IMPLEMENT_INTERFACE (MM_TYPE_PLUGIN, plugin_init)) + +int mm_plugin_major_version = MM_PLUGIN_MAJOR_VERSION; +int mm_plugin_minor_version = MM_PLUGIN_MINOR_VERSION; + +G_MODULE_EXPORT MMPlugin * +mm_plugin_create (void) +{ + return MM_PLUGIN (g_object_new (MM_TYPE_PLUGIN_HUAWEI, NULL)); +} + +/*****************************************************************************/ + +static const char * +get_name (MMPlugin *plugin) +{ + return "Huawei"; +} + +static char ** +list_supported_udis (MMPlugin *plugin, LibHalContext *hal_ctx) +{ + char **supported = NULL; + char **devices; + int num_devices; + int i; + + devices = libhal_find_device_by_capability (hal_ctx, "modem", &num_devices, NULL); + if (devices) { + GPtrArray *array; + + array = g_ptr_array_new (); + + for (i = 0; i < num_devices; i++) { + char *udi = devices[i]; + + if (mm_plugin_supports_udi (plugin, hal_ctx, udi)) + g_ptr_array_add (array, g_strdup (udi)); + } + + if (array->len > 0) { + g_ptr_array_add (array, NULL); + supported = (char **) g_ptr_array_free (array, FALSE); + } else + g_ptr_array_free (array, TRUE); + } + + g_strfreev (devices); + + return supported; +} + +static gboolean +supports_udi (MMPlugin *plugin, LibHalContext *hal_ctx, const char *udi) +{ + char **capabilities; + char **iter; + gboolean supported = FALSE; + + capabilities = libhal_device_get_property_strlist (hal_ctx, udi, "modem.command_sets", NULL); + for (iter = capabilities; iter && *iter && !supported; iter++) { + if (!strcmp (*iter, "GSM-07.07")) { + char *parent_udi; + + parent_udi = libhal_device_get_property_string (hal_ctx, udi, "info.parent", NULL); + if (parent_udi) { + int vendor; + int product; + + vendor = libhal_device_get_property_int (hal_ctx, parent_udi, "usb.vendor_id", NULL); + product = libhal_device_get_property_int (hal_ctx, parent_udi, "usb.product_id", NULL); + + if (vendor == 0x12d1 && (product == 0x1001 || product == 0x1003 || product == 0x1004)) + supported = TRUE; + + libhal_free_string (parent_udi); + } + } + } + g_strfreev (capabilities); + + return supported; +} + +static char * +get_driver_name (LibHalContext *ctx, const char *udi) +{ + char *parent_udi; + char *driver = NULL; + + parent_udi = libhal_device_get_property_string (ctx, udi, "info.parent", NULL); + if (parent_udi) { + driver = libhal_device_get_property_string (ctx, parent_udi, "info.linux.driver", NULL); + libhal_free_string (parent_udi); + } + + return driver; +} + +static MMModem * +create_modem (MMPlugin *plugin, LibHalContext *hal_ctx, const char *udi) +{ + char *data_device; + char *monitor_device; + char *driver; + MMModem *modem; + + data_device = libhal_device_get_property_string (hal_ctx, udi, "serial.device", NULL); + g_return_val_if_fail (data_device != NULL, NULL); + + driver = get_driver_name (hal_ctx, udi); + g_return_val_if_fail (driver != NULL, NULL); + + /* FIXME: Get monitoring device from HAL */ + monitor_device = "/dev/ttyUSB1"; + + modem = MM_MODEM (mm_modem_huawei_new (data_device, monitor_device, driver)); + + g_free (data_device); + g_free (driver); + + return modem; +} + +/*****************************************************************************/ + +static void +plugin_init (MMPlugin *plugin_class) +{ + /* interface implementation */ + plugin_class->get_name = get_name; + plugin_class->list_supported_udis = list_supported_udis; + plugin_class->supports_udi = supports_udi; + plugin_class->create_modem = create_modem; +} + +static void +mm_plugin_huawei_init (MMPluginHuawei *self) +{ +} + +static void +mm_plugin_huawei_class_init (MMPluginHuaweiClass *klass) +{ +} diff --git a/plugins/mm-plugin-huawei.h b/plugins/mm-plugin-huawei.h new file mode 100644 index 00000000..291c18d9 --- /dev/null +++ b/plugins/mm-plugin-huawei.h @@ -0,0 +1,26 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#ifndef MM_PLUGIN_HUAWEI_H +#define MM_PLUGIN_HUAWEI_H + +#include "mm-plugin.h" +#include "mm-generic-gsm.h" + +#define MM_TYPE_PLUGIN_HUAWEI (mm_plugin_huawei_get_type ()) +#define MM_PLUGIN_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_HUAWEI, MMPluginHuawei)) +#define MM_PLUGIN_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_HUAWEI, MMPluginHuaweiClass)) +#define MM_IS_PLUGIN_HUAWEI(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_HUAWEI)) +#define MM_IS_PLUGIN_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_HUAWEI)) +#define MM_PLUGIN_HUAWEI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_HUAWEI, MMPluginHuaweiClass)) + +typedef struct { + GObject parent; +} MMPluginHuawei; + +typedef struct { + GObjectClass parent; +} MMPluginHuaweiClass; + +GType mm_plugin_huawei_get_type (void); + +#endif /* MM_PLUGIN_HUAWEI_H */ diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..5a0a317e --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,39 @@ +sbin_PROGRAMS = modem-manager + +modem_manager_CPPFLAGS = \ + $(MM_CFLAGS) \ + -DPLUGINDIR=\"$(pkglibdir)\" + +modem_manager_LDADD = \ + $(MM_LIBS) + +modem_manager_SOURCES = \ + main.c \ + mm-callback-info.c \ + mm-callback-info.h \ + mm-generic-cdma.c \ + mm-generic-cdma.h \ + mm-generic-gsm.c \ + mm-generic-gsm.h \ + mm-manager.c \ + mm-manager.h \ + mm-modem.c \ + mm-modem.h \ + mm-modem-error.c \ + mm-modem-error.h \ + mm-plugin.c \ + mm-plugin.h \ + mm-serial.c \ + mm-serial.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=$@ $< + +BUILT_SOURCES = \ + mm-manager-glue.h \ + mm-modem-glue.h + +CLEANFILES = $(BUILT_SOURCES) diff --git a/src/main.c b/src/main.c new file mode 100644 index 00000000..a18ad3b5 --- /dev/null +++ b/src/main.c @@ -0,0 +1,166 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#include <syslog.h> +#include <dbus/dbus-glib.h> +#include "mm-manager.h" + +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) +{ + GMainLoop *loop = (GMainLoop *) user_data; + + g_message ("disconnected from the system bus, exiting."); + g_main_loop_quit (loop); +} + +static gboolean +dbus_init (GMainLoop *loop) +{ + DBusGConnection *connection; + DBusGProxy *proxy; + GError *err = NULL; + int request_name_result; + + connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err); + if (!connection) { + 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 FALSE; + } + + proxy = dbus_g_proxy_new_for_name (connection, + "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); + goto err; + } + + if (request_name_result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + g_warning ("Could not acquire the NetworkManagerSystemSettings service " + "as it is already taken. Return: %d", + request_name_result); + goto err; + } + + g_signal_connect (proxy, "destroy", G_CALLBACK (destroy_cb), loop); + + return TRUE; + + err: + dbus_g_connection_unref (connection); + g_object_unref (proxy); + + return FALSE; +} + +int +main (int argc, char *argv[]) +{ + GOptionContext *opt_ctx; + GError *error = NULL; + GMainLoop *loop; + MMManager *manager; + gboolean debug = FALSE; + 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); + return 1; + } + + g_option_context_free (opt_ctx); + + g_type_init (); + + if (!debug) + logging_setup (); + + loop = g_main_loop_new (NULL, FALSE); + + if (!dbus_init (loop)) + return -1; + + manager = mm_manager_new (); + + g_main_loop_run (loop); + + g_object_unref (manager); + logging_shutdown (); + + return 0; +} diff --git a/src/mm-callback-info.c b/src/mm-callback-info.c new file mode 100644 index 00000000..d4fa55c5 --- /dev/null +++ b/src/mm-callback-info.c @@ -0,0 +1,98 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#include "mm-callback-info.h" +#include "mm-modem-error.h" + +static void +callback_info_done (gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + info->pending_id = 0; + + if (info->callback) + info->callback (info->modem, info->error, info->user_data); + else if (info->uint_callback) + info->uint_callback (info->modem, info->uint_result, info->error, info->user_data); + + if (info->error) + g_error_free (info->error); + + g_object_unref (info->modem); + g_datalist_clear (&info->qdata); + + g_slice_free (MMCallbackInfo, 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) +{ + info->pending_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, callback_info_do, info, callback_info_done); +} + +void +mm_callback_info_cancel (MMCallbackInfo *info) +{ + if (info->pending_id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Call cancelled"); + mm_callback_info_schedule (info); + } +} + +MMCallbackInfo * +mm_callback_info_new (MMModem *modem, MMModemFn callback, gpointer user_data) +{ + MMCallbackInfo *info; + + info = g_slice_new0 (MMCallbackInfo); + g_datalist_init (&info->qdata); + info->modem = g_object_ref (modem); + info->callback = callback; + info->user_data = user_data; + + return info; +} + +MMCallbackInfo * +mm_callback_info_uint_new (MMModem *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = g_slice_new0 (MMCallbackInfo); + g_datalist_init (&info->qdata); + info->modem = g_object_ref (modem); + info->uint_callback = callback; + info->user_data = user_data; + + return info; +} + +void +mm_callback_info_set_data (MMCallbackInfo *info, + const char *key, + gpointer data, + GDestroyNotify destroy) +{ + 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; + + quark = g_quark_try_string (key); + + return quark ? g_datalist_id_get_data (&info->qdata, quark) : NULL; +} diff --git a/src/mm-callback-info.h b/src/mm-callback-info.h new file mode 100644 index 00000000..c8321bb1 --- /dev/null +++ b/src/mm-callback-info.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#ifndef MM_CALLBACK_INFO_H +#define MM_CALLBACK_INFO_H + +#include "mm-modem.h" + +typedef struct { + GData *qdata; + MMModem *modem; + MMModemFn callback; + MMModemUIntFn uint_callback; + guint32 uint_result; + gpointer user_data; + GError *error; + guint pending_id; +} MMCallbackInfo; + +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); + +void mm_callback_info_schedule (MMCallbackInfo *info); +void mm_callback_info_cancel (MMCallbackInfo *info); + +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); + +#endif /* MM_CALLBACK_INFO_H */ diff --git a/src/mm-generic-cdma.c b/src/mm-generic-cdma.c new file mode 100644 index 00000000..1bef4fe2 --- /dev/null +++ b/src/mm-generic-cdma.c @@ -0,0 +1,259 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#include "mm-generic-cdma.h" +#include "mm-modem-error.h" +#include "mm-callback-info.h" + +static void modem_init (MMModem *modem_class); + +G_DEFINE_TYPE_EXTENDED (MMGenericCdma, mm_generic_cdma, MM_TYPE_SERIAL, + 0, G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)) + +#define MM_GENERIC_CDMA_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_GENERIC_CDMA, MMGenericCdmaPrivate)) + +typedef struct { + char *driver; +} MMGenericCdmaPrivate; + +MMModem * +mm_generic_cdma_new (const char *serial_device, const char *driver) +{ + g_return_val_if_fail (serial_device != NULL, NULL); + g_return_val_if_fail (driver != NULL, NULL); + + return MM_MODEM (g_object_new (MM_TYPE_GENERIC_CDMA, + MM_SERIAL_DEVICE, serial_device, + MM_MODEM_DRIVER, driver, + NULL)); +} + +/*****************************************************************************/ + +static void +init_done (MMSerial *serial, + int reply_index, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + switch (reply_index) { + case 0: + /* success */ + break; + case -1: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Modem initialization timed out."); + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Modem initialization failed"); + } + + mm_callback_info_schedule (info); +} + +static void +flash_done (MMSerial *serial, gpointer user_data) +{ + char *responses[] = { "OK", "ERROR", "ERR", NULL }; + guint id = 0; + + if (mm_serial_send_command_string (serial, "AT E0")) + id = mm_serial_wait_for_reply (serial, 10, responses, responses, init_done, user_data); + + if (!id) { + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Turning modem echo off failed."); + mm_callback_info_schedule (info); + } +} + +static void +enable (MMModem *modem, + gboolean enable, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new (modem, callback, user_data); + + if (!enable) { + mm_serial_close (MM_SERIAL (modem)); + mm_callback_info_schedule (info); + return; + } + + if (mm_serial_open (MM_SERIAL (modem))) { + guint id; + + id = mm_serial_flash (MM_SERIAL (modem), 100, flash_done, info); + if (!id) + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "%s", "Could not communicate with serial device."); + } else + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Could not open serial device."); + + if (info->error) + mm_callback_info_schedule (info); +} + +static void +dial_done (MMSerial *serial, + int reply_index, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + switch (reply_index) { + case 0: + /* success */ + break; + case 1: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Dial failed: Busy"); + break; + case 2: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Dial failed: No dial tone"); + break; + case 3: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Dial failed: No carrier"); + break; + case -1: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Dialing timed out"); + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Dialing failed"); + break; + } + + mm_callback_info_schedule (info); +} + +static void +connect (MMModem *modem, + const char *number, + const char *apn, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + char *command; + char *responses[] = { "CONNECT", "BUSY", "NO DIAL TONE", "NO CARRIER", NULL }; + guint id = 0; + + info = mm_callback_info_new (modem, callback, user_data); + + command = g_strconcat ("ATDT", number, NULL); + if (mm_serial_send_command_string (MM_SERIAL (modem), command)) + id = mm_serial_wait_for_reply (MM_SERIAL (modem), 60, responses, responses, dial_done, info); + + g_free (command); + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Dialing failed."); + mm_callback_info_schedule (info); + } +} + +static void +disconnect (MMModem *modem, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new (modem, callback, user_data); + mm_serial_close (MM_SERIAL (modem)); + mm_callback_info_schedule (info); +} + +/*****************************************************************************/ + +static void +modem_init (MMModem *modem_class) +{ + /* interface implementation */ + modem_class->enable = enable; + modem_class->connect = connect; + modem_class->disconnect = disconnect; +} + +static void +mm_generic_cdma_init (MMGenericCdma *self) +{ +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + case MM_MODEM_PROP_DRIVER: + /* Construct only */ + MM_GENERIC_CDMA_GET_PRIVATE (object)->driver = g_value_dup_string (value); + break; + case MM_MODEM_PROP_DATA_DEVICE: + case MM_MODEM_PROP_TYPE: + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + case MM_MODEM_PROP_DATA_DEVICE: + g_value_set_string (value, mm_serial_get_device (MM_SERIAL (object))); + break; + case MM_MODEM_PROP_DRIVER: + g_value_set_string (value, MM_GENERIC_CDMA_GET_PRIVATE (object)->driver); + break; + case MM_MODEM_PROP_TYPE: + g_value_set_uint (value, MM_MODEM_TYPE_CDMA); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +finalize (GObject *object) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (object); + + g_free (priv->driver); + + 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); + + 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->finalize = finalize; + + /* 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_DRIVER, + MM_MODEM_DRIVER); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_TYPE, + MM_MODEM_TYPE); + + mm_modem_install_dbus_info (G_TYPE_FROM_CLASS (klass)); +} diff --git a/src/mm-generic-cdma.h b/src/mm-generic-cdma.h new file mode 100644 index 00000000..125b540a --- /dev/null +++ b/src/mm-generic-cdma.h @@ -0,0 +1,29 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#ifndef MM_GENERIC_CDMA_H +#define MM_GENERIC_CDMA_H + +#include "mm-modem.h" +#include "mm-serial.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)) + +typedef struct { + MMSerial parent; +} MMGenericCdma; + +typedef struct { + MMSerialClass parent; +} MMGenericCdmaClass; + +GType mm_generic_cdma_get_type (void); + +MMModem *mm_generic_cdma_new (const char *serial_device, + const char *driver); + +#endif /* MM_GENERIC_CDMA_H */ diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c new file mode 100644 index 00000000..2713cc00 --- /dev/null +++ b/src/mm-generic-gsm.c @@ -0,0 +1,705 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "mm-generic-gsm.h" +#include "mm-modem-error.h" +#include "mm-callback-info.h" + +static void modem_init (MMModem *modem_class); + +G_DEFINE_TYPE_EXTENDED (MMGenericGsm, mm_generic_gsm, MM_TYPE_SERIAL, + 0, G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)) + +#define MM_GENERIC_GSM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_GENERIC_GSM, MMGenericGsmPrivate)) + +typedef struct { + char *driver; + guint32 pending_id; +} MMGenericGsmPrivate; + +static void register_auto (MMModem *modem, MMCallbackInfo *info); + +MMModem * +mm_generic_gsm_new (const char *serial_device, const char *driver) +{ + g_return_val_if_fail (serial_device != NULL, NULL); + g_return_val_if_fail (driver != NULL, NULL); + + return MM_MODEM (g_object_new (MM_TYPE_GENERIC_GSM, + MM_SERIAL_DEVICE, serial_device, + MM_MODEM_DRIVER, driver, + NULL)); +} + +/*****************************************************************************/ + +static void +check_pin_done (MMSerial *serial, + int reply_index, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + switch (reply_index) { + case 0: + /* success */ + break; + case 1: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_PIN_NEEDED, "%s", "PIN needed"); + break; + case 2: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_PUK_NEEDED, "%s", "PUK needed"); + break; + case -1: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "PIN checking timed out."); + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "PIN checking failed."); + break; + } + + mm_callback_info_schedule (info); +} + +static void +check_pin (MMSerial *serial, gpointer user_data) +{ + char *responses[] = { "READY", "SIM PIN", "SIM PUK", "ERROR", "ERR", NULL }; + char *terminators[] = { "OK", "ERROR", "ERR", NULL }; + guint id = 0; + + if (mm_serial_send_command_string (serial, "AT+CPIN?")) + id = mm_serial_wait_for_reply (serial, 3, responses, terminators, check_pin_done, user_data); + + if (!id) { + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "PIN checking failed."); + mm_callback_info_schedule (info); + } +} + +static void +init_done (MMSerial *serial, + int reply_index, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + switch (reply_index) { + case 0: + /* success */ + check_pin (serial, user_data); + break; + case -1: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Modem initialization timed out."); + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Modem initialization failed"); + } + + if (info->error) + mm_callback_info_schedule (info); +} + +static void +flash_done (MMSerial *serial, gpointer user_data) +{ + char *responses[] = { "OK", "ERROR", "ERR", NULL }; + guint id = 0; + + if (mm_serial_send_command_string (serial, "AT E0")) + id = mm_serial_wait_for_reply (serial, 10, responses, responses, init_done, user_data); + + if (!id) { + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Turning modem echo off failed."); + mm_callback_info_schedule (info); + } +} + +static void +enable (MMModem *modem, + gboolean enable, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new (modem, callback, user_data); + + if (!enable) { + mm_serial_close (MM_SERIAL (modem)); + mm_callback_info_schedule (info); + return; + } + + if (mm_serial_open (MM_SERIAL (modem))) { + guint id; + + id = mm_serial_flash (MM_SERIAL (modem), 100, flash_done, info); + if (!id) + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "%s", "Could not communicate with serial device."); + } else + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Could not open serial device."); + + if (info->error) + mm_callback_info_schedule (info); +} + +static void +set_pin_done (MMSerial *serial, + int reply_index, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + switch (reply_index) { + case 0: + /* success */ + break; + case -1: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "%s", "Did not receive response for secret"); + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_INVALID_SECRET, "%s", "Invalid secret"); + break; + } + + mm_callback_info_schedule (info); +} + +static void +set_pin (MMModem *modem, + const char *pin, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + char *command; + char *responses[] = { "OK", "ERROR", "ERR", NULL }; + guint id = 0; + + info = mm_callback_info_new (modem, callback, user_data); + + command = g_strdup_printf ("AT+CPIN=\"%s\"", pin); + if (mm_serial_send_command_string (MM_SERIAL (modem), command)) + id = mm_serial_wait_for_reply (MM_SERIAL (modem), 3, responses, responses, set_pin_done, info); + + g_free (command); + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "PIN checking failed."); + mm_callback_info_schedule (info); + } +} + +static void +register_manual_done (MMSerial *serial, + int reply_index, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + switch (reply_index) { + case 0: + /* success */ + break; + case -1: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Manual registration timed out"); + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Manual registration failed"); + break; + } + + mm_callback_info_schedule (info); +} + +static void +register_manual (MMModem *modem, const char *network_id, MMCallbackInfo *info) +{ + char *command; + char *responses[] = { "OK", "ERROR", "ERR", NULL }; + guint id = 0; + + command = g_strdup_printf ("AT+COPS=1,2,\"%s\"", network_id); + if (mm_serial_send_command_string (MM_SERIAL (modem), command)) + id = mm_serial_wait_for_reply (MM_SERIAL (modem), 30, responses, responses, + register_manual_done, info); + + g_free (command); + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Manual registration failed."); + mm_callback_info_schedule (info); + } +} + +static gboolean +automatic_registration_again (gpointer data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) data; + + register_auto (MM_MODEM (mm_callback_info_get_data (info, "modem")), info); + + mm_callback_info_set_data (info, "modem", NULL, NULL); + + return FALSE; +} + +static void +register_auto_done (MMSerial *serial, + int reply_index, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + switch (reply_index) { + case 0: + g_message ("Registered on Home network"); + break; + case 1: + g_message ("Registered on Roaming network"); + break; + case 2: + mm_callback_info_set_data (info, "modem", g_object_ref (serial), g_object_unref); + MM_GENERIC_GSM_GET_PRIVATE (serial)->pending_id = g_timeout_add (1000, automatic_registration_again, info); + return; + break; + case 3: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "%s", "Automatic registration failed: not registered and not searching."); + break; + case -1: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Automatic registration timed out"); + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Automatic registration failed"); + break; + } + + mm_callback_info_schedule (info); +} + +static void +register_auto (MMModem *modem, MMCallbackInfo *info) +{ + char *responses[] = { "+CREG: 0,1", "+CREG: 0,5", "+CREG: 0,2", "+CREG: 0,0", NULL }; + char *terminators[] = { "OK", "ERROR", "ERR", NULL }; + guint id = 0; + + if (mm_serial_send_command_string (MM_SERIAL (modem), "AT+CREG?")) + id = mm_serial_wait_for_reply (MM_SERIAL (modem), 60, responses, terminators, + register_auto_done, info); + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Automatic registration failed."); + mm_callback_info_schedule (info); + } +} + +static void +do_register (MMModem *modem, + const char *network_id, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new (modem, callback, user_data); + + if (network_id) + register_manual (modem, network_id, info); + else + register_auto (modem, info); +} + +static void +dial_done (MMSerial *serial, + int reply_index, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + switch (reply_index) { + case 0: + /* success */ + break; + case 1: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Dial failed: Busy"); + break; + case 2: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Dial failed: No dial tone"); + break; + case 3: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Dial failed: No carrier"); + break; + case -1: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Dialing timed out"); + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Dialing failed"); + break; + } + + mm_callback_info_schedule (info); +} + + +static void +dial (MMModem *modem, guint cid, const char *number, MMCallbackInfo *info) +{ + char *command; + char *responses[] = { "CONNECT", "BUSY", "NO DIAL TONE", "NO CARRIER", NULL }; + guint id = 0; + + if (cid) { + GString *str; + + str = g_string_new ("ATD"); + 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 ("ATDT", number, NULL); + + if (mm_serial_send_command_string (MM_SERIAL (modem), command)) + id = mm_serial_wait_for_reply (MM_SERIAL (modem), 60, responses, responses, dial_done, info); + + g_free (command); + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Dialing failed."); + mm_callback_info_schedule (info); + } +} + +static void +set_apn_done (MMSerial *serial, + int reply_index, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *number = (char *) mm_callback_info_get_data (info, "number"); + + switch (reply_index) { + case 0: + dial (MM_MODEM (serial), 1, number, info); + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Setting APN failed"); + break; + } + + if (info->error) + mm_callback_info_schedule (info); +} + +static void +set_apn (MMModem *modem, const char *apn, MMCallbackInfo *info) +{ + char *command; + char *responses[] = { "OK", "ERROR", NULL }; + guint cid = 1; + guint id = 0; + + command = g_strdup_printf ("AT+CGDCONT=%d, \"IP\", \"%s\"", cid, apn); + if (mm_serial_send_command_string (MM_SERIAL (modem), command)) + id = mm_serial_wait_for_reply (MM_SERIAL (modem), 3, responses, responses, set_apn_done, info); + + g_free (command); + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Setting APN failed."); + mm_callback_info_schedule (info); + } +} + +static void +connect (MMModem *modem, + const char *number, + const char *apn, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new (modem, callback, user_data); + + if (apn) { + mm_callback_info_set_data (info, "number", g_strdup (number), g_free); + set_apn (modem, apn, info); + } else + dial (modem, 0, number, info); +} + +static void +disconnect (MMModem *modem, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new (modem, callback, user_data); + mm_serial_close (MM_SERIAL (modem)); + mm_callback_info_schedule (info); +} + +static void +scan_callback_wrapper (MMModem *modem, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMModemScanFn scan_fn; + GPtrArray *results; + gpointer data; + + scan_fn = (MMModemScanFn) mm_callback_info_get_data (info, "scan-callback"); + results = (GPtrArray *) mm_callback_info_get_data (info, "scan-results"); + data = mm_callback_info_get_data (info, "scan-data"); + + scan_fn (modem, results, error, data); +} + +static void +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); +} + +static void +scan_done (MMSerial *serial, const char *reply, gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GPtrArray *results; + + results = g_ptr_array_new (); + + if (!strncmp (reply, "+COPS: ", 7)) { + /* Got valid reply */ + GRegex *r; + GMatchInfo *match_info; + GError *err = NULL; + + reply += 7; + + /* Pattern without crazy escaping using | for matching: (|\d|,"|.+|","|.+|","|.+|",|\d|) */ + r = g_regex_new ("\\((\\d),\"(.+)\",\"(.+)\",\"(.+)\",(\\d)\\)", G_REGEX_UNGREEDY, 0, &err); + if (err) { + g_error ("Invalid regular expression: %s", err->message); + g_error_free (err); + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Could not parse scan results."); + goto out; + } + + g_regex_match (r, reply, 0, &match_info); + while (g_match_info_matches (match_info)) { + GHashTable *hash; + + hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_insert (hash, g_strdup ("status"), g_match_info_fetch (match_info, 1)); + g_hash_table_insert (hash, g_strdup ("operator-long"), g_match_info_fetch (match_info, 2)); + g_hash_table_insert (hash, g_strdup ("operator-short"), g_match_info_fetch (match_info, 3)); + g_hash_table_insert (hash, g_strdup ("operator-num"), g_match_info_fetch (match_info, 4)); + + g_ptr_array_add (results, hash); + g_match_info_next (match_info, NULL); + } + + g_match_info_free (match_info); + g_regex_unref (r); + } else + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Could not parse scan results"); + + mm_callback_info_set_data (info, "scan-results", results, destroy_scan_data); + + out: + mm_callback_info_schedule (info); +} + +static void +scan (MMModem *modem, + MMModemScanFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + char *terminators = "\r\n"; + guint id = 0; + + info = mm_callback_info_new (modem, scan_callback_wrapper, NULL); + info->user_data = info; + mm_callback_info_set_data (info, "scan-callback", callback, NULL); + mm_callback_info_set_data (info, "scan-data", user_data, NULL); + + if (mm_serial_send_command_string (MM_SERIAL (modem), "AT+COPS=?")) + id = mm_serial_get_reply (MM_SERIAL (modem), 60, terminators, scan_done, info); + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Scanning failed."); + mm_callback_info_schedule (info); + } +} + +static void +get_signal_quality_done (MMSerial *serial, const char *reply, gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + guint32 result = 0; + + 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) + /* Normalize the quality */ + result = quality * 100 / 31; + } else + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "%s", "Could not parse signal quality results"); + } else + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "%s", "Could not parse signal quality results"); + + info->uint_result = result; + mm_callback_info_schedule (info); +} + +static void +get_signal_quality (MMModem *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + char *terminators = "\r\n"; + guint id = 0; + + info = mm_callback_info_uint_new (modem, callback, user_data); + + if (mm_serial_send_command_string (MM_SERIAL (modem), "AT+CSQ")) + id = mm_serial_get_reply (MM_SERIAL (modem), 10, terminators, get_signal_quality_done, info); + + if (!id) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "%s", "Getting signal quality failed."); + mm_callback_info_schedule (info); + } +} + +/*****************************************************************************/ + +static void +modem_init (MMModem *modem_class) +{ + /* interface implementation */ + modem_class->enable = enable; + modem_class->set_pin = set_pin; + modem_class->do_register = do_register; + modem_class->connect = connect; + modem_class->disconnect = disconnect; + modem_class->scan = scan; + modem_class->get_signal_quality = get_signal_quality; +} + +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_DRIVER: + /* Construct only */ + MM_GENERIC_GSM_GET_PRIVATE (object)->driver = g_value_dup_string (value); + break; + case MM_MODEM_PROP_DATA_DEVICE: + case MM_MODEM_PROP_TYPE: + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + case MM_MODEM_PROP_DATA_DEVICE: + g_value_set_string (value, mm_serial_get_device (MM_SERIAL (object))); + break; + case MM_MODEM_PROP_DRIVER: + g_value_set_string (value, MM_GENERIC_GSM_GET_PRIVATE (object)->driver); + break; + case MM_MODEM_PROP_TYPE: + g_value_set_uint (value, MM_MODEM_TYPE_GSM); + 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); + + if (priv->pending_id) { + g_source_remove (priv->pending_id); + priv->pending_id = 0; + } + + g_free (priv->driver); + + 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); + + 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; + + /* 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_DRIVER, + MM_MODEM_DRIVER); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_TYPE, + MM_MODEM_TYPE); + + mm_modem_install_dbus_info (G_TYPE_FROM_CLASS (klass)); +} diff --git a/src/mm-generic-gsm.h b/src/mm-generic-gsm.h new file mode 100644 index 00000000..25e9d7c2 --- /dev/null +++ b/src/mm-generic-gsm.h @@ -0,0 +1,29 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#ifndef MM_GENERIC_GSM_H +#define MM_GENERIC_GSM_H + +#include "mm-modem.h" +#include "mm-serial.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)) + +typedef struct { + MMSerial parent; +} MMGenericGsm; + +typedef struct { + MMSerialClass parent; +} MMGenericGsmClass; + +GType mm_generic_gsm_get_type (void); + +MMModem *mm_generic_gsm_new (const char *serial_device, + const char *driver); + +#endif /* MM_GENERIC_GSM_H */ diff --git a/src/mm-manager.c b/src/mm-manager.c new file mode 100644 index 00000000..6ea936f6 --- /dev/null +++ b/src/mm-manager.c @@ -0,0 +1,457 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#include <string.h> +#include <gmodule.h> +#include <libhal.h> +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> +#include "mm-manager.h" +#include "mm-modem-error.h" +#include "mm-generic-gsm.h" +#include "mm-generic-cdma.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; + LibHalContext *hal_ctx; + GSList *plugins; + GHashTable *modems; +} 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; + + 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 (!strstr (fname, G_MODULE_SUFFIX)) + continue; + + path = g_module_build_path (PLUGINDIR, fname); + plugin = load_plugin (path); + g_free (path); + + if (plugin) + priv->plugins = g_slist_append (priv->plugins, plugin); + } + + g_dir_close (dir); +} + +MMManager * +mm_manager_new (void) +{ + return g_object_new (MM_TYPE_MANAGER, NULL); +} + +static char * +get_driver_name (LibHalContext *ctx, const char *udi) +{ + char *parent_udi; + char *driver = NULL; + + parent_udi = libhal_device_get_property_string (ctx, udi, "info.parent", NULL); + if (parent_udi) { + driver = libhal_device_get_property_string (ctx, parent_udi, "info.linux.driver", NULL); + libhal_free_string (parent_udi); + } + + return driver; +} + +static MMModem * +create_generic_modem (MMManager *manager, const char *udi) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + MMModem *modem; + char **capabilities; + char **iter; + char *serial_device; + char *driver; + gboolean type_gsm = FALSE; + gboolean type_cdma = FALSE; + + capabilities = libhal_device_get_property_strlist (priv->hal_ctx, udi, "modem.command_sets", NULL); + for (iter = capabilities; iter && *iter; iter++) { + if (!strcmp (*iter, "GSM-07.07")) { + type_gsm = TRUE; + break; + } + if (!strcmp (*iter, "IS-707-A")) { + type_cdma = TRUE; + break; + } + } + g_strfreev (capabilities); + + if (!type_gsm && !type_cdma) + return NULL; + + serial_device = libhal_device_get_property_string (priv->hal_ctx, udi, "serial.device", NULL); + g_return_val_if_fail (serial_device != NULL, NULL); + + driver = get_driver_name (priv->hal_ctx, udi); + g_return_val_if_fail (driver != NULL, NULL); + + if (type_gsm) + modem = mm_generic_gsm_new (serial_device, driver); + else + modem = mm_generic_cdma_new (serial_device, driver); + + g_free (serial_device); + g_free (driver); + + return modem; +} + +static void +add_modem (MMManager *manager, const char *udi, MMModem *modem) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + + g_debug ("Added modem %s", udi); + g_hash_table_insert (priv->modems, g_strdup (udi), modem); + dbus_g_connection_register_g_object (priv->connection, udi, G_OBJECT (modem)); + + g_signal_emit (manager, signals[DEVICE_ADDED], 0, modem); +} + +static MMModem * +modem_exists (MMManager *manager, const char *udi) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + + return (MMModem *) g_hash_table_lookup (priv->modems, udi); +} + +static void +create_initial_modems_from_plugins (MMManager *manager) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + GSList *iter; + + for (iter = priv->plugins; iter; iter = iter->next) { + MMPlugin *plugin = MM_PLUGIN (iter->data); + GSList *udis; + GSList *udi_iter; + + udis = mm_plugin_list_supported_udis (plugin, priv->hal_ctx); + for (udi_iter = udis; udi_iter; udi_iter = udi_iter->next) { + char *udi = (char *) udi_iter->data; + MMModem *modem; + + if (modem_exists (manager, udi)) { + g_warning ("Modem for UDI %s already exists, ignoring", udi); + continue; + } + + modem = mm_plugin_create_modem (plugin, priv->hal_ctx, udi); + if (modem) + add_modem (manager, udi, modem); + else + g_warning ("Plugin failed to create modem for UDI %s", udi); + } + + g_slist_foreach (udis, (GFunc) g_free, NULL); + g_slist_free (udis); + } +} + +static void +create_initial_modems_generic (MMManager *manager) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + char **devices; + int num_devices; + int i; + DBusError err; + + dbus_error_init (&err); + devices = libhal_find_device_by_capability (priv->hal_ctx, "modem", &num_devices, &err); + if (dbus_error_is_set (&err)) { + g_warning ("Could not list HAL devices: %s", err.message); + dbus_error_free (&err); + } + + if (devices) { + for (i = 0; i < num_devices; i++) { + char *udi = devices[i]; + MMModem *modem; + + if (modem_exists (manager, udi)) + /* Already exists, most likely handled by a plugin */ + continue; + + modem = create_generic_modem (manager, udi); + if (modem) + add_modem (manager, g_strdup (udi), modem); + } + } + + g_strfreev (devices); +} + +static void +create_initial_modems (MMManager *manager) +{ + create_initial_modems_from_plugins (manager); + create_initial_modems_generic (manager); +} + +static void +enumerate_devices_cb (gpointer key, gpointer val, gpointer user_data) +{ + GPtrArray **devices = (GPtrArray **) user_data; + + g_ptr_array_add (*devices, g_strdup ((char *) key)); +} + +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 void +device_added (LibHalContext *ctx, const char *udi) +{ + MMManager *manager = MM_MANAGER (libhal_ctx_get_user_data (ctx)); + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + GSList *iter; + MMModem *modem = NULL; + + if (modem_exists (manager, udi)) + /* Shouldn't happen */ + return; + + for (iter = priv->plugins; iter && modem == NULL; iter = iter->next) { + MMPlugin *plugin = MM_PLUGIN (iter->data); + + if (mm_plugin_supports_udi (plugin, ctx, udi)) { + modem = mm_plugin_create_modem (plugin, ctx, udi); + if (modem) + break; + } + } + + if (!modem) + /* None of the plugins supported the udi, try generic devices */ + modem = create_generic_modem (manager, udi); + + if (modem) + add_modem (manager, udi, modem); +} + +static void +device_removed (LibHalContext *ctx, const char *udi) +{ + MMManager *manager = MM_MANAGER (libhal_ctx_get_user_data (ctx)); + MMModem *modem; + + modem = modem_exists (manager, udi); + if (modem) { + g_debug ("Removed modem %s", udi); + g_signal_emit (manager, signals[DEVICE_REMOVED], 0, modem); + g_hash_table_remove (MM_MANAGER_GET_PRIVATE (manager)->modems, udi); + } +} + +static void +device_new_capability (LibHalContext *ctx, const char *udi, const char *capability) +{ + device_added (ctx, udi); +} + +static void +mm_manager_init (MMManager *manager) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); + GError *err = NULL; + DBusError dbus_error; + + priv->modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + + priv->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err); + if (!priv->connection) + g_error ("Could not connect to system bus."); + + dbus_g_connection_register_g_object (priv->connection, + MM_DBUS_PATH, + G_OBJECT (manager)); + + priv->hal_ctx = libhal_ctx_new (); + if (!priv->hal_ctx) + g_error ("Could not get connection to the HAL service."); + + libhal_ctx_set_dbus_connection (priv->hal_ctx, dbus_g_connection_get_connection (priv->connection)); + + dbus_error_init (&dbus_error); + if (!libhal_ctx_init (priv->hal_ctx, &dbus_error)) + g_error ("libhal_ctx_init() failed: %s\n" + "Make sure the hal daemon is running?", + dbus_error.message); + + load_plugins (manager); + + libhal_ctx_set_user_data (priv->hal_ctx, manager); + libhal_ctx_set_device_added (priv->hal_ctx, device_added); + libhal_ctx_set_device_removed (priv->hal_ctx, device_removed); + libhal_ctx_set_device_new_capability (priv->hal_ctx, device_new_capability); + + create_initial_modems (manager); +} + +static void +finalize (GObject *object) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (object); + + g_hash_table_destroy (priv->modems); + + g_slist_foreach (priv->plugins, (GFunc) g_object_unref, NULL); + g_slist_free (priv->plugins); + + if (priv->hal_ctx) { + libhal_ctx_shutdown (priv->hal_ctx, NULL); + libhal_ctx_free (priv->hal_ctx); + } + + 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_MODEM_ERROR, NULL, MM_TYPE_MODEM_ERROR); +} diff --git a/src/mm-manager.h b/src/mm-manager.h new file mode 100644 index 00000000..ecb7f408 --- /dev/null +++ b/src/mm-manager.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#ifndef MM_MANAGER_H +#define MM_MANAGER_H + +#include <glib/gtypes.h> +#include <glib-object.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 (void); + +#endif /* MM_MANAGER_H */ diff --git a/src/mm-modem-error.c b/src/mm-modem-error.c new file mode 100644 index 00000000..c415fb1f --- /dev/null +++ b/src/mm-modem-error.c @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#include "mm-modem-error.h" + +GQuark +mm_modem_error_quark (void) +{ + static GQuark ret = 0; + + if (ret == 0) + ret = g_quark_from_static_string ("mm_modem_error"); + + return ret; +} + +#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } + +GType +mm_modem_error_get_type (void) +{ + static GType etype = 0; + + if (etype == 0) { + static const GEnumValue values[] = { + ENUM_ENTRY (MM_MODEM_ERROR_GENERAL, "GeneralError"), + ENUM_ENTRY (MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, "OperationNotSupported"), + ENUM_ENTRY (MM_MODEM_ERROR_PIN_NEEDED, "PINNeeded"), + ENUM_ENTRY (MM_MODEM_ERROR_PUK_NEEDED, "PUKNeeded"), + ENUM_ENTRY (MM_MODEM_ERROR_INVALID_SECRET, "InvalidSecret"), + { 0, 0, 0 } + }; + + etype = g_enum_register_static ("MMModemError", values); + } + + return etype; +} diff --git a/src/mm-modem-error.h b/src/mm-modem-error.h new file mode 100644 index 00000000..f5c3c57e --- /dev/null +++ b/src/mm-modem-error.h @@ -0,0 +1,22 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#ifndef MM_MODEM_ERROR_H +#define MM_MODEM_ERROR_H + +#include <glib-object.h> + +enum { + MM_MODEM_ERROR_GENERAL = 0, + MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + MM_MODEM_ERROR_PIN_NEEDED, + MM_MODEM_ERROR_PUK_NEEDED, + MM_MODEM_ERROR_INVALID_SECRET +}; + +#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); + +#endif /* MM_MODEM_ERROR_H */ diff --git a/src/mm-modem.c b/src/mm-modem.c new file mode 100644 index 00000000..27052585 --- /dev/null +++ b/src/mm-modem.c @@ -0,0 +1,460 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#include <string.h> +#include <dbus/dbus-glib.h> +#include "mm-modem.h" +#include "mm-modem-error.h" +#include "mm-callback-info.h" + +static void impl_modem_enable (MMModem *modem, gboolean enable, DBusGMethodInvocation *context); +static void impl_modem_set_pin (MMModem *modem, const char *pin, DBusGMethodInvocation *context); +static void impl_modem_register (MMModem *modem, const char *network_id, DBusGMethodInvocation *context); +static void impl_modem_connect (MMModem *modem, const char *number, const char *apn, DBusGMethodInvocation *context); +static void impl_modem_disconnect (MMModem *modem, DBusGMethodInvocation *context); +static void impl_modem_scan (MMModem *modem, DBusGMethodInvocation *context); +static void impl_modem_get_signal_quality (MMModem *modem, DBusGMethodInvocation *context); +static void impl_modem_set_band (MMModem *modem, guint32 band, DBusGMethodInvocation *context); +static void impl_modem_get_band (MMModem *modem, DBusGMethodInvocation *context); +static void impl_modem_set_network_mode (MMModem *modem, guint32 mode, DBusGMethodInvocation *context); +static void impl_modem_get_network_mode (MMModem *modem, DBusGMethodInvocation *context); + +#include "mm-modem-glue.h" + +enum { + SIGNAL_QUALITY, + NETWORK_MODE, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +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 (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "%s", "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 +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 (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "%s", "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); +} + +void +mm_modem_enable (MMModem *self, + gboolean enable, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->enable) + MM_MODEM_GET_INTERFACE (self)->enable (self, enable, callback, user_data); + else + async_op_not_supported (self, callback, user_data); +} + +static void +impl_modem_enable (MMModem *modem, + gboolean enable, + DBusGMethodInvocation *context) +{ + mm_modem_enable (modem, enable, async_call_done, context); +} + +void +mm_modem_set_pin (MMModem *self, + const char *pin, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + g_return_if_fail (pin != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->set_pin) + MM_MODEM_GET_INTERFACE (self)->set_pin (self, pin, callback, user_data); + else + async_op_not_supported (self, callback, user_data); +} + +static void +impl_modem_set_pin (MMModem *modem, + const char *pin, + DBusGMethodInvocation *context) +{ + mm_modem_set_pin (modem, pin, async_call_done, context); +} + +void +mm_modem_register (MMModem *self, + const char *network_id, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->do_register) + MM_MODEM_GET_INTERFACE (self)->do_register (self, network_id, callback, user_data); + else + async_op_not_supported (self, callback, user_data); +} + +static void +impl_modem_register (MMModem *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_register (modem, id, async_call_done, context); +} + +void +mm_modem_connect (MMModem *self, + const char *number, + const char *apn, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + g_return_if_fail (number != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->connect) + MM_MODEM_GET_INTERFACE (self)->connect (self, number, apn, callback, user_data); + else + async_op_not_supported (self, callback, user_data); +} + +static void +impl_modem_connect (MMModem *modem, + const char *number, + const char *apn, + DBusGMethodInvocation *context) +{ + const char *real_apn; + + /* DBus does not support NULL strings, so the caller should pass an empty string + for no APN. */ + if (strlen (apn) < 1) + real_apn = NULL; + else + real_apn = apn; + + mm_modem_connect (modem, number, real_apn, async_call_done, context); +} + +void +mm_modem_disconnect (MMModem *self, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + 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); +} + +void +mm_modem_scan (MMModem *self, + MMModemScanFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->scan) + MM_MODEM_GET_INTERFACE (self)->scan (self, callback, user_data); + else + /* FIXME */ ; +} + +static void +impl_scan_done (MMModem *modem, 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 +impl_modem_scan (MMModem *modem, + DBusGMethodInvocation *context) +{ + mm_modem_scan (modem, impl_scan_done, context); +} + +void +mm_modem_get_signal_quality (MMModem *self, + MMModemUIntFn 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_signal_quality) + MM_MODEM_GET_INTERFACE (self)->get_signal_quality (self, callback, user_data); + else + uint_op_not_supported (self, callback, user_data); +} + +static void +impl_modem_get_signal_quality (MMModem *modem, DBusGMethodInvocation *context) +{ + mm_modem_get_signal_quality (modem, uint_call_done, context); +} + +void +mm_modem_set_band (MMModem *self, + MMModemBand band, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->set_band) + MM_MODEM_GET_INTERFACE (self)->set_band (self, band, callback, user_data); + else + async_op_not_supported (self, callback, user_data); +} + +static void +impl_modem_set_band (MMModem *modem, guint32 band, DBusGMethodInvocation *context) +{ + mm_modem_set_band (modem, band, async_call_done, context); +} + +void +mm_modem_get_band (MMModem *self, + MMModemUIntFn 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_band) + MM_MODEM_GET_INTERFACE (self)->get_band (self, callback, user_data); + else + uint_op_not_supported (self, callback, user_data); +} + +static void +impl_modem_get_band (MMModem *modem, DBusGMethodInvocation *context) +{ + mm_modem_get_band (modem, uint_call_done, context); +} + +void +mm_modem_set_network_mode (MMModem *self, + MMModemNetworkMode mode, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->set_network_mode) + MM_MODEM_GET_INTERFACE (self)->set_network_mode (self, mode, callback, user_data); + else + async_op_not_supported (self, callback, user_data); +} + +static void +impl_modem_set_network_mode (MMModem *modem, guint32 mode, DBusGMethodInvocation *context) +{ + mm_modem_set_network_mode (modem, mode, async_call_done, context); +} + +void +mm_modem_get_network_mode (MMModem *self, + MMModemUIntFn 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_network_mode) + MM_MODEM_GET_INTERFACE (self)->get_network_mode (self, callback, user_data); + else + uint_op_not_supported (self, callback, user_data); +} + +static void +impl_modem_get_network_mode (MMModem *modem, DBusGMethodInvocation *context) +{ + mm_modem_get_network_mode (modem, uint_call_done, context); +} + + +void +mm_modem_install_dbus_info (GType type) +{ + dbus_g_object_type_install_info (type, &dbus_glib_mm_modem_object_info); +} + +void +mm_modem_signal_quality (MMModem *self, + guint32 quality) +{ + g_return_if_fail (MM_IS_MODEM (self)); + + g_signal_emit (self, signals[SIGNAL_QUALITY], 0, quality); +} + +void +mm_modem_network_mode (MMModem *self, + MMModemNetworkMode mode) +{ + g_return_if_fail (MM_IS_MODEM (self)); + + g_signal_emit (self, signals[NETWORK_MODE], 0, mode); +} + +/*****************************************************************************/ + +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, + "DataDevice", + "DataDevice", + 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_uint (MM_MODEM_TYPE, + "Type", + "Type", + 0, G_MAXUINT32, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + /* Signals */ + signals[SIGNAL_QUALITY] = + g_signal_new ("signal-quality", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMModem, signal_quality), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, + G_TYPE_UINT); + + signals[NETWORK_MODE] = + g_signal_new ("network-mode", + iface_type, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMModem, network_mode), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, + 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); + } + + return modem_type; +} diff --git a/src/mm-modem.h b/src/mm-modem.h new file mode 100644 index 00000000..c664a323 --- /dev/null +++ b/src/mm-modem.h @@ -0,0 +1,196 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#ifndef MM_MODEM_H +#define MM_MODEM_H + +#include <glib-object.h> + +#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_DATA_DEVICE "data-device" +#define MM_MODEM_DRIVER "driver" +#define MM_MODEM_TYPE "type" + +#define MM_MODEM_TYPE_GSM 1 +#define MM_MODEM_TYPE_CDMA 2 + +typedef enum { + MM_MODEM_PROP_FIRST = 0x1000, + + MM_MODEM_PROP_DATA_DEVICE = MM_MODEM_PROP_FIRST, + MM_MODEM_PROP_DRIVER, + MM_MODEM_PROP_TYPE +} MMModemProp; + +typedef enum { + MM_MODEM_NETWORK_MODE_ANY = 0, + MM_MODEM_NETWORK_MODE_GPRS = 1, + MM_MODEM_NETWORK_MODE_EDGE = 2, + MM_MODEM_NETWORK_MODE_3G = 3, + MM_MODEM_NETWORK_MODE_HSDPA = 4, + MM_MODEM_NETWORK_MODE_PREFER_2G = 5, + MM_MODEM_NETWORK_MODE_PREFER_3G = 6 +} MMModemNetworkMode; + +typedef enum { + MM_MODEM_BAND_ANY = 0, + MM_MODEM_BAND_EGSM = 1, /* 900 MHz */ + MM_MODEM_BAND_DCS = 2, /* 1800 MHz */ + MM_MODEM_BAND_PCS = 3, /* 1900 MHz */ + MM_MODEM_BAND_G850 = 4, /* 850 MHz */ + MM_MODEM_BAND_U2100 = 5, /* WCDMA 2100 MHz (Class I) */ + MM_MODEM_BAND_U1700 = 6, /* WCDMA 3GPP UMTS1800 MHz (Class III) */ + MM_MODEM_BAND_17IV = 7, /* WCDMA 3GPP AWS 1700/2100 MHz (Class IV) */ + MM_MODEM_BAND_U800 = 8, /* WCDMA 3GPP UMTS800 MHz (Class VI) */ + MM_MODEM_BAND_U850 = 9, /* WCDMA 3GPP UMTS850 MHz (Class V) */ + MM_MODEM_BAND_U900 = 10, /* WCDMA 3GPP UMTS900 MHz (Class VIII) */ + MM_MODEM_BAND_U17IX = 11 /* WCDMA 3GPP UMTS MHz (Class IX) */ +} MMModemBand; + +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 (*MMModemScanFn) (MMModem *modem, + GPtrArray *results, + GError *error, + gpointer user_data); + +struct _MMModem { + GTypeInterface g_iface; + + /* Methods */ + void (*enable) (MMModem *self, + gboolean enable, + MMModemFn callback, + gpointer user_data); + + void (*set_pin) (MMModem *self, + const char *pin, + MMModemFn callback, + gpointer user_data); + + /* 'register' is a reserved word */ + void (*do_register) (MMModem *self, + const char *network_id, + MMModemFn callback, + gpointer user_data); + + void (*connect) (MMModem *self, + const char *number, + const char *apn, + MMModemFn callback, + gpointer user_data); + + void (*disconnect) (MMModem *self, + MMModemFn callback, + gpointer user_data); + + void (*scan) (MMModem *self, + MMModemScanFn callback, + gpointer user_data); + + void (*get_signal_quality) (MMModem *self, + MMModemUIntFn callback, + gpointer user_data); + + void (*set_band) (MMModem *self, + MMModemBand band, + MMModemFn callback, + gpointer user_data); + + void (*get_band) (MMModem *self, + MMModemUIntFn callback, + gpointer user_data); + + void (*set_network_mode) (MMModem *self, + MMModemNetworkMode mode, + MMModemFn callback, + gpointer user_data); + + void (*get_network_mode) (MMModem *self, + MMModemUIntFn callback, + gpointer user_data); + + /* Signals */ + void (*signal_quality) (MMModem *self, + guint32 quality); + + void (*network_mode) (MMModem *self, + MMModemNetworkMode mode); +}; + +GType mm_modem_get_type (void); + +void mm_modem_enable (MMModem *self, + gboolean enable, + MMModemFn callback, + gpointer user_data); + +void mm_modem_set_pin (MMModem *self, + const char *pin, + MMModemFn callback, + gpointer user_data); + +void mm_modem_register (MMModem *self, + const char *network_id, + MMModemFn callback, + gpointer user_data); + +void mm_modem_connect (MMModem *self, + const char *number, + const char *apn, + MMModemFn callback, + gpointer user_data); + +void mm_modem_disconnect (MMModem *self, + MMModemFn callback, + gpointer user_data); + +void mm_modem_scan (MMModem *self, + MMModemScanFn callback, + gpointer user_data); + +void mm_modem_get_signal_quality (MMModem *self, + MMModemUIntFn callback, + gpointer user_data); + +void mm_modem_set_band (MMModem *self, + MMModemBand band, + MMModemFn callback, + gpointer user_data); + +void mm_modem_get_band (MMModem *self, + MMModemUIntFn callback, + gpointer user_data); + +void mm_modem_set_network_mode (MMModem *self, + MMModemNetworkMode mode, + MMModemFn callback, + gpointer user_data); + +void mm_modem_get_network_mode (MMModem *self, + MMModemUIntFn callback, + gpointer user_data); + +/* Protected */ + +void mm_modem_install_dbus_info (GType type); + +void mm_modem_signal_quality (MMModem *self, + guint32 quality); + +void mm_modem_network_mode (MMModem *self, + MMModemNetworkMode mode); + +#endif /* MM_MODEM_H */ diff --git a/src/mm-plugin.c b/src/mm-plugin.c new file mode 100644 index 00000000..deca9291 --- /dev/null +++ b/src/mm-plugin.c @@ -0,0 +1,81 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#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); +} + +char ** +mm_plugin_list_supported_udis (MMPlugin *plugin, + LibHalContext *hal_ctx) +{ + g_return_val_if_fail (MM_IS_PLUGIN (plugin), NULL); + g_return_val_if_fail (hal_ctx != NULL, NULL); + + return MM_PLUGIN_GET_INTERFACE (plugin)->list_supported_udis (plugin, hal_ctx); +} + +gboolean +mm_plugin_supports_udi (MMPlugin *plugin, + LibHalContext *hal_ctx, + const char *udi) +{ + g_return_val_if_fail (MM_IS_PLUGIN (plugin), FALSE); + g_return_val_if_fail (hal_ctx != NULL, FALSE); + g_return_val_if_fail (udi != NULL, FALSE); + + return MM_PLUGIN_GET_INTERFACE (plugin)->supports_udi (plugin, hal_ctx, udi); +} + +MMModem * +mm_plugin_create_modem (MMPlugin *plugin, + LibHalContext *hal_ctx, + const char *udi) +{ + g_return_val_if_fail (MM_IS_PLUGIN (plugin), NULL); + g_return_val_if_fail (hal_ctx != NULL, NULL); + g_return_val_if_fail (udi != NULL, NULL); + + return MM_PLUGIN_GET_INTERFACE (plugin)->create_modem (plugin, hal_ctx, udi); +} + + +/*****************************************************************************/ + +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 00000000..0f9a600b --- /dev/null +++ b/src/mm-plugin.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#ifndef MM_PLUGIN_H +#define MM_PLUGIN_H + +#include <glib-object.h> +#include <libhal.h> +#include <mm-modem.h> + +#define MM_PLUGIN_MAJOR_VERSION 1 +#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); + +struct _MMPlugin { + GTypeInterface g_iface; + + /* Methods */ + const char *(*get_name) (MMPlugin *self); + + char **(*list_supported_udis) (MMPlugin *self, + LibHalContext *hal_ctx); + + gboolean (*supports_udi) (MMPlugin *self, + LibHalContext *hal_ctx, + const char *udi); + + MMModem *(*create_modem) (MMPlugin *self, + LibHalContext *hal_ctx, + const char *udi); +}; + +GType mm_plugin_get_type (void); + +const char *mm_plugin_get_name (MMPlugin *plugin); + +char **mm_plugin_list_supported_udis (MMPlugin *plugin, + LibHalContext *hal_ctx); + +gboolean mm_plugin_supports_udi (MMPlugin *plugin, + LibHalContext *hal_ctx, + const char *udi); + +MMModem *mm_plugin_create_modem (MMPlugin *plugin, + LibHalContext *hal_ctx, + const char *udi); + +#endif /* MM_PLUGIN_H */ diff --git a/src/mm-serial.c b/src/mm-serial.c new file mode 100644 index 00000000..a9f3f349 --- /dev/null +++ b/src/mm-serial.c @@ -0,0 +1,1117 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#define _GNU_SOURCE /* for strcasestr() */ + +#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.h" + +G_DEFINE_TYPE (MMSerial, mm_serial, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_DEVICE, + PROP_BAUD, + PROP_BITS, + PROP_PARITY, + PROP_STOPBITS, + PROP_SEND_DELAY, + + LAST_PROP +}; + +#define MM_DEBUG_SERIAL 1 +#define SERIAL_BUF_SIZE 2048 + +#define MM_SERIAL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_SERIAL, MMSerialPrivate)) + +typedef struct { + int fd; + GIOChannel *channel; + struct termios old_t; + + char *device; + guint baud; + guint bits; + char parity; + guint stopbits; + guint64 send_delay; + + guint pending_id; + guint timeout_id; +} MMSerialPrivate; + +const char * +mm_serial_get_device (MMSerial *serial) +{ + g_return_val_if_fail (MM_IS_SERIAL (serial), NULL); + + return MM_SERIAL_GET_PRIVATE (serial)->device; +} + +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; +} + +#ifdef MM_DEBUG_SERIAL +static inline void +serial_debug (const char *prefix, const char *data, int len) +{ + GString *str; + int i; + + str = g_string_sized_new (len); + for (i = 0; i < len; i++) { + if (data[i] == '\0') + g_string_append_c (str, ' '); + else if (data[i] == '\r') + g_string_append_c (str, '\n'); + else + g_string_append_c (str, data[i]); + } + + g_debug ("%s '%s'", prefix, str->str); + g_string_free (str, TRUE); +} +#else +static inline void +serial_debug (const char *prefix, const char *data, int len) +{ +} +#endif /* MM_DEBUG_SERIAL */ + +/* Timeout handling */ + +static void +mm_serial_timeout_removed (gpointer data) +{ + MMSerialPrivate *priv = MM_SERIAL_GET_PRIVATE (data); + + priv->timeout_id = 0; +} + +static gboolean +mm_serial_timed_out (gpointer data) +{ + MMSerialPrivate *priv = MM_SERIAL_GET_PRIVATE (data); + + /* Cancel data reading */ + if (priv->pending_id) + g_source_remove (priv->pending_id); + else + g_warning ("Timeout reached, but there's nothing to time out"); + + return FALSE; +} + +static void +mm_serial_add_timeout (MMSerial *self, guint timeout) +{ + MMSerialPrivate *priv = MM_SERIAL_GET_PRIVATE (self); + + if (priv->pending_id == 0) + g_warning ("Adding a time out while not waiting for any data"); + + if (priv->timeout_id) { + g_warning ("Trying to add a new time out while the old one still exists"); + g_source_remove (priv->timeout_id); + } + + priv->timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT, + timeout * 1000, + mm_serial_timed_out, + self, + mm_serial_timeout_removed); + if (G_UNLIKELY (priv->timeout_id == 0)) + g_warning ("Registering serial device time out failed."); +} + +static void +mm_serial_remove_timeout (MMSerial *self) +{ + MMSerialPrivate *priv = MM_SERIAL_GET_PRIVATE (self); + + if (priv->timeout_id) + g_source_remove (priv->timeout_id); +} + +/* Pending data reading */ + +static guint +mm_serial_set_pending (MMSerial *self, + guint timeout, + GIOFunc callback, + gpointer user_data, + GDestroyNotify notify) +{ + MMSerialPrivate *priv = MM_SERIAL_GET_PRIVATE (self); + + if (G_UNLIKELY (priv->pending_id)) { + /* FIXME: Probably should queue up pending calls instead? */ + /* Multiple pending calls on the same GIOChannel doesn't work, so let's cancel the previous one. */ + g_warning ("Adding new pending call while previous one isn't finished."); + g_warning ("Cancelling the previous pending call."); + g_source_remove (priv->pending_id); + } + + priv->pending_id = g_io_add_watch_full (priv->channel, + G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_ERR | G_IO_HUP, + callback, user_data, notify); + + mm_serial_add_timeout (self, timeout); + + return priv->pending_id; +} + +static void +mm_serial_pending_done (MMSerial *self) +{ + MM_SERIAL_GET_PRIVATE (self)->pending_id = 0; + mm_serial_remove_timeout (self); +} + +/****/ + +static gboolean +config_fd (MMSerial *self) +{ + MMSerialPrivate *priv = MM_SERIAL_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); + + ioctl (priv->fd, TCGETA, &stbuf); + + 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_warning ("(%s) cannot control device (errno %d)", priv->device, errno); + return FALSE; + } + + return TRUE; +} + +gboolean +mm_serial_open (MMSerial *self) +{ + MMSerialPrivate *priv; + + g_return_val_if_fail (MM_IS_SERIAL (self), FALSE); + + priv = MM_SERIAL_GET_PRIVATE (self); + + g_debug ("(%s) opening serial device...", priv->device); + priv->fd = open (priv->device, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY); + + if (priv->fd < 0) { + g_warning ("(%s) cannot open device: %s", priv->device, strerror (errno)); + return FALSE; + } + + if (ioctl (priv->fd, TCGETA, &priv->old_t) < 0) { + g_warning ("(%s) cannot control device (errno %d)", priv->device, errno); + close (priv->fd); + return FALSE; + } + + if (!config_fd (self)) { + close (priv->fd); + return FALSE; + } + + priv->channel = g_io_channel_unix_new (priv->fd); + + return TRUE; +} + +void +mm_serial_close (MMSerial *self) +{ + MMSerialPrivate *priv; + + g_return_if_fail (MM_IS_SERIAL (self)); + + priv = MM_SERIAL_GET_PRIVATE (self); + + if (priv->pending_id) + g_source_remove (priv->pending_id); + + if (priv->fd) { + g_message ("Closing device '%s'", priv->device); + + if (priv->channel) { + g_io_channel_unref (priv->channel); + priv->channel = NULL; + } + + ioctl (priv->fd, TCSETA, &priv->old_t); + close (priv->fd); + priv->fd = 0; + } +} + +gboolean +mm_serial_send_command (MMSerial *self, GByteArray *command) +{ + MMSerialPrivate *priv; + int fd; + int i; + ssize_t status; + + g_return_val_if_fail (MM_IS_SERIAL (self), FALSE); + g_return_val_if_fail (command != NULL, FALSE); + + priv = MM_SERIAL_GET_PRIVATE (self); + + fd = priv->fd; + + serial_debug ("Sending:", (char *) command->data, command->len); + + for (i = 0; i < command->len; i++) { + again: + status = write (fd, command->data + i, 1); + + if (status < 0) { + if (errno == EAGAIN) + goto again; + + g_warning ("Error in writing (errno %d)", errno); + return FALSE; + } + + if (priv->send_delay) + usleep (priv->send_delay); + } + + return TRUE; +} + +gboolean +mm_serial_send_command_string (MMSerial *self, const char *str) +{ + GByteArray *command; + gboolean ret; + + g_return_val_if_fail (MM_IS_SERIAL (self), FALSE); + g_return_val_if_fail (str != NULL, FALSE); + + command = g_byte_array_new (); + g_byte_array_append (command, (guint8 *) str, strlen (str)); + g_byte_array_append (command, (guint8 *) "\r", 1); + + ret = mm_serial_send_command (self, command); + g_byte_array_free (command, TRUE); + + return ret; +} + +typedef struct { + MMSerial *serial; + char *terminators; + GString *result; + MMSerialGetReplyFn callback; + gpointer user_data; +} GetReplyInfo; + +static void +get_reply_done (gpointer data) +{ + GetReplyInfo *info = (GetReplyInfo *) data; + + mm_serial_pending_done (info->serial); + + /* Call the callback */ + info->callback (info->serial, info->result->str, info->user_data); + + /* Free info */ + g_free (info->terminators); + g_string_free (info->result, TRUE); + + g_slice_free (GetReplyInfo, info); +} + +static gboolean +get_reply_got_data (GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + GetReplyInfo *info = (GetReplyInfo *) data; + gsize bytes_read; + char buf[SERIAL_BUF_SIZE + 1]; + GIOStatus status; + gboolean done = FALSE; + int i; + + if (condition & G_IO_HUP || condition & G_IO_ERR) { + g_string_truncate (info->result, 0); + return FALSE; + } + + 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 (bytes_read > 0) { + char *p; + + serial_debug ("Got:", buf, bytes_read); + + p = &buf[0]; + for (i = 0; i < bytes_read && !done; i++, p++) { + int j; + gboolean is_terminator = FALSE; + + for (j = 0; j < strlen (info->terminators); j++) { + if (*p == info->terminators[j]) { + is_terminator = TRUE; + break; + } + } + + if (is_terminator) { + /* Ignore terminators in the beginning of the output */ + if (info->result->len > 0) + done = TRUE; + } else + g_string_append_c (info->result, *p); + } + } + + /* Limit the size of the buffer */ + if (info->result->len > SERIAL_BUF_SIZE) { + g_warning ("%s (%s): response buffer filled before repsonse received", + __func__, MM_SERIAL_GET_PRIVATE (info->serial)->device); + g_string_truncate (info->result, 0); + done = TRUE; + } + } while (!done || bytes_read == SERIAL_BUF_SIZE || status == G_IO_STATUS_AGAIN); + + return !done; +} + +guint +mm_serial_get_reply (MMSerial *self, + guint timeout, + const char *terminators, + MMSerialGetReplyFn callback, + gpointer user_data) +{ + GetReplyInfo *info; + + g_return_val_if_fail (MM_IS_SERIAL (self), 0); + g_return_val_if_fail (terminators != NULL, 0); + g_return_val_if_fail (callback != NULL, 0); + + info = g_slice_new0 (GetReplyInfo); + info->serial = self; + info->terminators = g_strdup (terminators); + info->result = g_string_new (NULL); + info->callback = callback; + info->user_data = user_data; + + return mm_serial_set_pending (self, timeout, get_reply_got_data, info, get_reply_done); +} + +typedef struct { + MMSerial *serial; + char **str_needles; + char **terminators; + GString *result; + MMSerialWaitForReplyFn callback; + gpointer user_data; + int reply_index; + guint timeout; + time_t start; +} WaitForReplyInfo; + +static void +wait_for_reply_done (gpointer data) +{ + WaitForReplyInfo *info = (WaitForReplyInfo *) data; + + mm_serial_pending_done (info->serial); + + /* Call the callback */ + info->callback (info->serial, info->reply_index, info->user_data); + + /* Free info */ + if (info->result) + g_string_free (info->result, TRUE); + + g_strfreev (info->str_needles); + g_strfreev (info->terminators); + g_slice_free (WaitForReplyInfo, info); +} + +static gboolean +find_terminator (const char *line, char **terminators) +{ + int i; + + for (i = 0; terminators[i]; i++) { + if (!strncasecmp (line, terminators[i], strlen (terminators[i]))) + return TRUE; + } + return FALSE; +} + +static gboolean +find_response (const char *line, char **responses, gint *idx) +{ + int i; + + /* Don't look for a result again if we got one previously */ + for (i = 0; responses[i]; i++) { + if (strcasestr (line, responses[i])) { + *idx = i; + return TRUE; + } + } + return FALSE; +} + +#define RESPONSE_LINE_MAX 128 + +static gboolean +wait_for_reply_got_data (GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + WaitForReplyInfo *info = (WaitForReplyInfo *) data; + gchar buf[SERIAL_BUF_SIZE + 1]; + gsize bytes_read; + GIOStatus status; + gboolean got_response = FALSE; + gboolean done = FALSE; + + if (condition & G_IO_HUP || condition & G_IO_ERR) + return FALSE; + + 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 (bytes_read > 0) { + buf[bytes_read] = 0; + g_string_append (info->result, buf); + + serial_debug ("Got:", info->result->str, info->result->len); + } + + /* Look for needles and terminators */ + if ((bytes_read > 0) && info->result->str) { + char *p = info->result->str; + + /* Break the response up into lines and process each one */ + while ( (p < info->result->str + strlen (info->result->str)) + && !(done && got_response)) { + char line[RESPONSE_LINE_MAX] = { '\0', }; + char *tmp; + int i; + gboolean got_something = FALSE; + + for (i = 0; *p && (i < RESPONSE_LINE_MAX - 1); p++) { + /* Ignore front CR/LF */ + if ((*p == '\n') || (*p == '\r')) { + if (got_something) + break; + } else { + line[i++] = *p; + got_something = TRUE; + } + } + line[i] = '\0'; + + tmp = g_strstrip (line); + if (tmp && strlen (tmp)) { + done = find_terminator (tmp, info->terminators); + if (info->reply_index == -1) + got_response = find_response (tmp, info->str_needles, &(info->reply_index)); + } + } + + if (done && got_response) + break; + } + + /* Limit the size of the buffer */ + if (info->result->len > SERIAL_BUF_SIZE) { + g_warning ("%s (%s): response buffer filled before repsonse received", + __func__, MM_SERIAL_GET_PRIVATE (info->serial)->device); + done = TRUE; + break; + } + + /* Make sure we don't go over the timeout, in addition to the timeout + * handler that's been scheduled. If for some reason this loop doesn't + * terminate (terminator not found, whatever) then this should make + * sure that we don't spin the CPU forever. + */ + if (time (NULL) - info->start > info->timeout + 1) { + done = TRUE; + break; + } else + g_usleep (50); + } while (!done || bytes_read == SERIAL_BUF_SIZE || status == G_IO_STATUS_AGAIN); + + return !done; +} + +guint +mm_serial_wait_for_reply (MMSerial *self, + guint timeout, + char **responses, + char **terminators, + MMSerialWaitForReplyFn callback, + gpointer user_data) +{ + WaitForReplyInfo *info; + + g_return_val_if_fail (MM_IS_SERIAL (self), 0); + g_return_val_if_fail (responses != NULL, 0); + g_return_val_if_fail (callback != NULL, 0); + + info = g_slice_new0 (WaitForReplyInfo); + info->serial = self; + info->str_needles = g_strdupv (responses); + info->terminators = g_strdupv (terminators); + info->result = g_string_new (NULL); + info->callback = callback; + info->user_data = user_data; + info->reply_index = -1; + info->timeout = timeout * 1000; + info->start = time (NULL); + + return mm_serial_set_pending (self, timeout, wait_for_reply_got_data, info, wait_for_reply_done); +} + +#if 0 +typedef struct { + MMSerial *serial; + gboolean timed_out; + MMSerialWaitQuietFn callback; + gpointer user_data; +} WaitQuietInfo; + +static void +wait_quiet_done (gpointer data) +{ + WaitQuietInfo *info = (WaitQuietInfo *) data; + + mm_serial_pending_done (info->serial); + + /* Call the callback */ + info->callback (info->serial, info->timed_out, info->user_data); + + /* Free info */ + g_slice_free (WaitQuietInfo, info); +} + +static gboolean +wait_quiet_quiettime (gpointer data) +{ + WaitQuietInfo *info = (WaitQuietInfo *) data; + + info->timed_out = FALSE; + g_source_remove (MM_SERIAL_GET_PRIVATE (info->serial)->pending); + + return FALSE; +} + +static gboolean +wait_quiet_got_data (GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + WaitQuietInfo *info = (WaitQuietInfo *) data; + gsize bytes_read; + char buf[4096]; + GIOStatus status; + + if (condition & G_IO_HUP || condition & G_IO_ERR) + return FALSE; + + if (condition & G_IO_IN) { + do { + status = g_io_channel_read_chars (source, buf, 4096, &bytes_read, NULL); + + if (bytes_read) { + /* Reset the quiet time timeout */ + g_source_remove (info->quiet_id); + info->quiet_id = g_timeout_add (info->quiet_time, wait_quiet_quiettime, info); + } + } while (bytes_read == 4096 || status == G_IO_STATUS_AGAIN); + } + + return TRUE; +} + +void +mm_serial_wait_quiet (MMSerial *self, + guint timeout, + guint quiet_time, + MMSerialWaitQuietFn callback, + gpointer user_data) +{ + WaitQuietInfo *info; + + g_return_if_fail (MM_IS_SERIAL (self)); + g_return_if_fail (callback != NULL); + + info = g_slice_new0 (WaitQuietInfo); + info->serial = self; + info->timed_out = TRUE; + info->callback = callback; + info->user_data = user_data; + info->quiet_id = g_timeout_add (quiet_time, + wait_quiet_timeout, + info); + + return mm_serial_set_pending (self, timeout, wait_quiet_got_data, info, wait_quiet_done); +} + +#endif + +typedef struct { + MMSerial *serial; + speed_t current_speed; + MMSerialFlashFn callback; + gpointer user_data; +} FlashInfo; + +static speed_t +get_speed (MMSerial *self) +{ + struct termios options; + + tcgetattr (MM_SERIAL_GET_PRIVATE (self)->fd, &options); + + return cfgetospeed (&options); +} + +static void +set_speed (MMSerial *self, speed_t speed) +{ + struct termios options; + int fd; + + fd = MM_SERIAL_GET_PRIVATE (self)->fd; + tcgetattr (fd, &options); + + cfsetispeed (&options, speed); + cfsetospeed (&options, speed); + + options.c_cflag |= (CLOCAL | CREAD); + tcsetattr (fd, TCSANOW, &options); +} + +static void +flash_done (gpointer data) +{ + FlashInfo *info = (FlashInfo *) data; + + MM_SERIAL_GET_PRIVATE (info->serial)->pending_id = 0; + + info->callback (info->serial, info->user_data); + + g_slice_free (FlashInfo, info); +} + +static gboolean +flash_do (gpointer data) +{ + FlashInfo *info = (FlashInfo *) data; + + set_speed (info->serial, info->current_speed); + + return FALSE; +} + +guint +mm_serial_flash (MMSerial *self, + guint32 flash_time, + MMSerialFlashFn callback, + gpointer user_data) +{ + FlashInfo *info; + guint id; + + g_return_val_if_fail (MM_IS_SERIAL (self), 0); + g_return_val_if_fail (callback != NULL, 0); + + info = g_slice_new0 (FlashInfo); + info->serial = self; + info->current_speed = get_speed (self); + info->callback = callback; + info->user_data = user_data; + + set_speed (self, B0); + + id = g_timeout_add_full (G_PRIORITY_DEFAULT, + flash_time, + flash_do, + info, + flash_done); + + MM_SERIAL_GET_PRIVATE (self)->pending_id = id; + + return id; +} + +GIOChannel * +mm_serial_get_io_channel (MMSerial *self) +{ + MMSerialPrivate *priv; + + g_return_val_if_fail (MM_IS_SERIAL (self), NULL); + + priv = MM_SERIAL_GET_PRIVATE (self); + if (priv->channel) + return g_io_channel_ref (priv->channel); + + return NULL; +} + +/*****************************************************************************/ + +static void +mm_serial_init (MMSerial *self) +{ + MMSerialPrivate *priv = MM_SERIAL_GET_PRIVATE (self); + + priv->baud = 57600; + priv->bits = 8; + priv->parity = 'n'; + priv->stopbits = 1; + priv->send_delay = 0; +} + +static GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + MMSerialPrivate *priv; + + object = G_OBJECT_CLASS (mm_serial_parent_class)->constructor (type, + n_construct_params, + construct_params); + if (!object) + return NULL; + + priv = MM_SERIAL_GET_PRIVATE (object); + + if (!priv->device) { + g_warning ("No device provided"); + g_object_unref (object); + return NULL; + } + + return object; +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + MMSerialPrivate *priv = MM_SERIAL_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_DEVICE: + /* Construct only */ + priv->device = g_value_dup_string (value); + break; + 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) +{ + MMSerialPrivate *priv = MM_SERIAL_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_DEVICE: + g_value_set_string (value, priv->device); + break; + 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 +finalize (GObject *object) +{ + MMSerial *self = MM_SERIAL (object); + MMSerialPrivate *priv = MM_SERIAL_GET_PRIVATE (self); + + mm_serial_close (self); + g_free (priv->device); + + G_OBJECT_CLASS (mm_serial_parent_class)->finalize (object); +} + +static void +mm_serial_class_init (MMSerialClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMSerialPrivate)); + + /* Virtual methods */ + object_class->constructor = constructor; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->finalize = finalize; + + /* Properties */ + g_object_class_install_property + (object_class, PROP_DEVICE, + g_param_spec_string (MM_SERIAL_DEVICE, + "Device", + "Serial device", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_BAUD, + g_param_spec_uint (MM_SERIAL_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_BITS, + "Bits", + "Bits", + 5, 8, 8, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_PARITY, + g_param_spec_char (MM_SERIAL_PARITY, + "Parity", + "Parity", + 'E', 'o', 'n', + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_STOPBITS, + g_param_spec_uint (MM_SERIAL_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_SEND_DELAY, + "SendDelay", + "Send delay", + 0, G_MAXUINT64, 0, + G_PARAM_READWRITE)); +} diff --git a/src/mm-serial.h b/src/mm-serial.h new file mode 100644 index 00000000..bd138516 --- /dev/null +++ b/src/mm-serial.h @@ -0,0 +1,85 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#ifndef MM_SERIAL_H +#define MM_SERIAL_H + +#include <glib/gtypes.h> +#include <glib-object.h> + +#define MM_TYPE_SERIAL (mm_serial_get_type ()) +#define MM_SERIAL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SERIAL, MMSerial)) +#define MM_SERIAL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_SERIAL, MMSerialClass)) +#define MM_IS_SERIAL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_SERIAL)) +#define MM_IS_SERIAL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_SERIAL)) +#define MM_SERIAL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_SERIAL, MMSerialClass)) + +#define MM_SERIAL_DEVICE "device" +#define MM_SERIAL_BAUD "baud" +#define MM_SERIAL_BITS "bits" +#define MM_SERIAL_PARITY "parity" +#define MM_SERIAL_STOPBITS "stopbits" +#define MM_SERIAL_SEND_DELAY "send-delay" + +typedef struct { + GObject parent; +} MMSerial; + +typedef struct { + GObjectClass parent; +} MMSerialClass; + +GType mm_serial_get_type (void); + +typedef void (*MMSerialGetReplyFn) (MMSerial *serial, + const char *reply, + gpointer user_data); + +typedef void (*MMSerialWaitForReplyFn) (MMSerial *serial, + int reply_index, + gpointer user_data); + +typedef void (*MMSerialWaitQuietFn) (MMSerial *serial, + gboolean timed_out, + gpointer user_data); + +typedef void (*MMSerialFlashFn) (MMSerial *serial, + gpointer user_data); + +const char *mm_serial_get_device (MMSerial *serial); + +gboolean mm_serial_open (MMSerial *self); + +void mm_serial_close (MMSerial *self); +gboolean mm_serial_send_command (MMSerial *self, + GByteArray *command); + +gboolean mm_serial_send_command_string (MMSerial *self, + const char *str); + +guint mm_serial_get_reply (MMSerial *self, + guint timeout, + const char *terminators, + MMSerialGetReplyFn callback, + gpointer user_data); + +guint mm_serial_wait_for_reply (MMSerial *self, + guint timeout, + char **responses, + char **terminators, + MMSerialWaitForReplyFn callback, + gpointer user_data); + +void mm_serial_wait_quiet (MMSerial *self, + guint timeout, + guint quiet_time, + MMSerialWaitQuietFn callback, + gpointer user_data); + +guint mm_serial_flash (MMSerial *self, + guint32 flash_time, + MMSerialFlashFn callback, + gpointer user_data); + +GIOChannel *mm_serial_get_io_channel (MMSerial *self); + +#endif /* MM_SERIAL_H */ |