From dc645b92b9a7db3076ae34986ac219d01677d124 Mon Sep 17 00:00:00 2001 From: Guido Günther Date: Wed, 5 Feb 2014 08:38:23 +0100 Subject: Imported Upstream version 0.4+git.20100624t180933.6e79d15 --- .gitignore | 18 + Makefile.am | 37 +- autogen.sh | 1 + configure.ac | 95 +- introspection/Makefile.am | 4 +- introspection/all.xml | 2 + introspection/mm-modem-gsm-card.xml | 24 +- introspection/mm-modem-gsm-network.xml | 172 +- introspection/mm-modem-gsm-ussd.xml | 78 + introspection/mm-modem-gsm.xml | 88 +- introspection/mm-modem-location.xml | 253 ++ introspection/mm-modem.xml | 37 + libqcdm/Makefile.am | 2 + libqcdm/src/Makefile.am | 42 + libqcdm/src/com.c | 61 + libqcdm/src/com.h | 25 + libqcdm/src/commands.c | 1064 +++++++++ libqcdm/src/commands.h | 437 ++++ libqcdm/src/dm-commands.h | 370 +++ libqcdm/src/error.c | 87 + libqcdm/src/error.h | 53 + libqcdm/src/libqcdm.ver | 6 + libqcdm/src/nv-items.h | 64 + libqcdm/src/result-private.h | 51 + libqcdm/src/result.c | 248 ++ libqcdm/src/result.h | 42 + libqcdm/src/utils.c | 311 +++ libqcdm/src/utils.h | 54 + libqcdm/tests/Makefile.am | 32 + libqcdm/tests/test-qcdm-com.c | 1184 +++++++++ libqcdm/tests/test-qcdm-com.h | 51 + libqcdm/tests/test-qcdm-crc.c | 65 + libqcdm/tests/test-qcdm-crc.h | 25 + libqcdm/tests/test-qcdm-escaping.c | 124 + libqcdm/tests/test-qcdm-escaping.h | 26 + libqcdm/tests/test-qcdm-result.c | 71 + libqcdm/tests/test-qcdm-result.h | 26 + libqcdm/tests/test-qcdm-utils.c | 86 + libqcdm/tests/test-qcdm-utils.h | 26 + libqcdm/tests/test-qcdm.c | 115 + m4/compiler_warnings.m4 | 31 + marshallers/mm-marshal.list | 1 + org.freedesktop.ModemManager.conf | 14 - org.freedesktop.ModemManager.conf.nopolkit | 17 + org.freedesktop.ModemManager.conf.polkit | 154 ++ plugins/77-mm-ericsson-mbm.rules | 10 + plugins/77-mm-longcheer-port-types.rules | 134 ++ plugins/77-mm-simtech-port-types.rules | 29 + plugins/Makefile.am | 35 +- plugins/mm-modem-anydata-cdma.c | 86 +- plugins/mm-modem-gobi-gsm.c | 14 +- plugins/mm-modem-hso.c | 602 +++-- plugins/mm-modem-huawei-cdma.c | 73 +- plugins/mm-modem-huawei-gsm.c | 705 +++--- plugins/mm-modem-longcheer-gsm.c | 222 ++ plugins/mm-modem-longcheer-gsm.h | 43 + plugins/mm-modem-mbm.c | 578 +++-- plugins/mm-modem-mbm.h | 4 - plugins/mm-modem-nokia.c | 14 +- plugins/mm-modem-novatel-cdma.c | 183 ++ plugins/mm-modem-novatel-cdma.h | 45 + plugins/mm-modem-novatel-gsm.c | 276 ++- plugins/mm-modem-option-utils.c | 451 ++++ plugins/mm-modem-option.c | 256 +- plugins/mm-modem-sierra-cdma.c | 148 +- plugins/mm-modem-sierra-gsm.c | 268 ++- plugins/mm-modem-simtech-gsm.c | 471 ++++ plugins/mm-modem-simtech-gsm.h | 43 + plugins/mm-modem-zte.c | 329 ++- plugins/mm-plugin-anydata.c | 30 +- plugins/mm-plugin-generic.c | 28 +- plugins/mm-plugin-gobi.c | 21 +- plugins/mm-plugin-hso.c | 11 +- plugins/mm-plugin-huawei.c | 75 +- plugins/mm-plugin-longcheer.c | 38 +- plugins/mm-plugin-mbm.c | 38 +- plugins/mm-plugin-mbm.h | 4 - plugins/mm-plugin-moto-c.c | 31 +- plugins/mm-plugin-nokia.c | 21 +- plugins/mm-plugin-novatel.c | 40 +- plugins/mm-plugin-option.c | 24 +- plugins/mm-plugin-sierra.c | 22 +- plugins/mm-plugin-simtech.c | 189 ++ plugins/mm-plugin-simtech.h | 41 + plugins/mm-plugin-zte.c | 28 +- po/LINGUAS | 0 po/POTFILES.in | 5 + policy/Makefile.am | 15 + policy/modem-manager.png | Bin 0 -> 817 bytes policy/org.freedesktop.modem-manager.policy.in | 57 + src/77-mm-pcmcia-device-blacklist.rules | 10 + src/77-mm-platform-serial-whitelist.rules | 14 + src/77-mm-usb-device-blacklist.rules | 66 + src/Makefile.am | 65 +- src/main.c | 27 +- src/mm-at-serial-port.c | 364 +++ src/mm-at-serial-port.h | 85 + src/mm-auth-provider-factory.c | 45 + src/mm-auth-provider-polkit.c | 153 ++ src/mm-auth-provider-polkit.h | 43 + src/mm-auth-provider.c | 300 +++ src/mm-auth-provider.h | 86 + src/mm-auth-request-polkit.c | 175 ++ src/mm-auth-request-polkit.h | 53 + src/mm-auth-request.c | 182 ++ src/mm-auth-request.h | 72 + src/mm-callback-info.c | 23 + src/mm-callback-info.h | 6 + src/mm-charsets.c | 175 ++ src/mm-charsets.h | 52 + src/mm-errors.c | 185 +- src/mm-errors.h | 18 +- src/mm-generic-cdma.c | 975 +++++--- src/mm-generic-cdma.h | 35 +- src/mm-generic-gsm.c | 3041 +++++++++++++++++++----- src/mm-generic-gsm.h | 98 +- src/mm-manager.c | 499 +++- src/mm-manager.h | 4 + src/mm-modem-base.c | 431 +++- src/mm-modem-base.h | 33 + src/mm-modem-cdma.c | 35 +- src/mm-modem-gsm-card.c | 349 ++- src/mm-modem-gsm-card.h | 25 + src/mm-modem-gsm-network.c | 175 +- src/mm-modem-gsm-network.h | 45 +- src/mm-modem-gsm-sms.c | 384 ++- src/mm-modem-gsm.h | 48 +- src/mm-modem-helpers.c | 602 +++++ src/mm-modem-helpers.h | 32 + src/mm-modem-location.c | 330 +++ src/mm-modem-location.h | 73 + src/mm-modem.c | 201 +- src/mm-modem.h | 71 +- src/mm-plugin-base.c | 419 ++-- src/mm-plugin-base.h | 13 +- src/mm-plugin.c | 14 +- src/mm-plugin.h | 6 + src/mm-port.c | 31 + src/mm-port.h | 5 +- src/mm-qcdm-serial-port.c | 225 ++ src/mm-qcdm-serial-port.h | 66 + src/mm-serial-parsers.c | 102 +- src/mm-serial-port.c | 610 ++--- src/mm-serial-port.h | 92 +- src/mm-utils.c | 78 + src/mm-utils.h | 24 + src/tests/test-modem-helpers.c | 453 +++- test/lsudev.c | 17 +- 148 files changed, 20106 insertions(+), 3245 deletions(-) create mode 100644 introspection/mm-modem-gsm-ussd.xml create mode 100644 introspection/mm-modem-location.xml create mode 100644 libqcdm/Makefile.am create mode 100644 libqcdm/src/Makefile.am create mode 100644 libqcdm/src/com.c create mode 100644 libqcdm/src/com.h create mode 100644 libqcdm/src/commands.c create mode 100644 libqcdm/src/commands.h create mode 100644 libqcdm/src/dm-commands.h create mode 100644 libqcdm/src/error.c create mode 100644 libqcdm/src/error.h create mode 100644 libqcdm/src/libqcdm.ver create mode 100644 libqcdm/src/nv-items.h create mode 100644 libqcdm/src/result-private.h create mode 100644 libqcdm/src/result.c create mode 100644 libqcdm/src/result.h create mode 100644 libqcdm/src/utils.c create mode 100644 libqcdm/src/utils.h create mode 100644 libqcdm/tests/Makefile.am create mode 100644 libqcdm/tests/test-qcdm-com.c create mode 100644 libqcdm/tests/test-qcdm-com.h create mode 100644 libqcdm/tests/test-qcdm-crc.c create mode 100644 libqcdm/tests/test-qcdm-crc.h create mode 100644 libqcdm/tests/test-qcdm-escaping.c create mode 100644 libqcdm/tests/test-qcdm-escaping.h create mode 100644 libqcdm/tests/test-qcdm-result.c create mode 100644 libqcdm/tests/test-qcdm-result.h create mode 100644 libqcdm/tests/test-qcdm-utils.c create mode 100644 libqcdm/tests/test-qcdm-utils.h create mode 100644 libqcdm/tests/test-qcdm.c create mode 100644 m4/compiler_warnings.m4 delete mode 100644 org.freedesktop.ModemManager.conf create mode 100644 org.freedesktop.ModemManager.conf.nopolkit create mode 100644 org.freedesktop.ModemManager.conf.polkit create mode 100644 plugins/77-mm-simtech-port-types.rules create mode 100644 plugins/mm-modem-longcheer-gsm.c create mode 100644 plugins/mm-modem-longcheer-gsm.h create mode 100644 plugins/mm-modem-novatel-cdma.c create mode 100644 plugins/mm-modem-novatel-cdma.h create mode 100644 plugins/mm-modem-option-utils.c create mode 100644 plugins/mm-modem-simtech-gsm.c create mode 100644 plugins/mm-modem-simtech-gsm.h create mode 100644 plugins/mm-plugin-simtech.c create mode 100644 plugins/mm-plugin-simtech.h create mode 100644 po/LINGUAS create mode 100644 po/POTFILES.in create mode 100644 policy/Makefile.am create mode 100644 policy/modem-manager.png create mode 100644 policy/org.freedesktop.modem-manager.policy.in create mode 100644 src/77-mm-pcmcia-device-blacklist.rules create mode 100644 src/77-mm-platform-serial-whitelist.rules create mode 100644 src/77-mm-usb-device-blacklist.rules create mode 100644 src/mm-at-serial-port.c create mode 100644 src/mm-at-serial-port.h create mode 100644 src/mm-auth-provider-factory.c create mode 100644 src/mm-auth-provider-polkit.c create mode 100644 src/mm-auth-provider-polkit.h create mode 100644 src/mm-auth-provider.c create mode 100644 src/mm-auth-provider.h create mode 100644 src/mm-auth-request-polkit.c create mode 100644 src/mm-auth-request-polkit.h create mode 100644 src/mm-auth-request.c create mode 100644 src/mm-auth-request.h create mode 100644 src/mm-charsets.c create mode 100644 src/mm-charsets.h create mode 100644 src/mm-modem-location.c create mode 100644 src/mm-modem-location.h create mode 100644 src/mm-qcdm-serial-port.c create mode 100644 src/mm-qcdm-serial-port.h create mode 100644 src/mm-utils.c create mode 100644 src/mm-utils.h diff --git a/.gitignore b/.gitignore index c6c7fda..dd0a8bb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ configure depcomp install-sh ltmain.sh +intltool-* missing stamp-h1 *~ @@ -20,10 +21,27 @@ libtool .deps .libs *-glue.h +*.tar.bz2 org.freedesktop.ModemManager.service +org.freedesktop.ModemManager.conf ModemManager.pc marshallers/mm-marshal.[ch] src/modem-manager docs/spec.html callouts/mm-modem-probe +test/lsudev +src/tests/test-modem-helpers +policy/org.freedesktop.modem-manager.policy +libqcdm/tests/test-qcdm + +po/Makefile.in.in +po/POTFILES +po/stamp-it +po/.intltool-merge-cache +po/ModemManager.pot + +m4/gtk-doc.m4 +m4/intltool.m4 +m4/libtool.m4 +m4/lt*.m4 diff --git a/Makefile.am b/Makefile.am index eee0847..276a276 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,11 +19,25 @@ all: $(GENERATED_FILES) CLEANFILES = $(GENERATED_FILES) endif -SUBDIRS = marshallers src plugins introspection test +SUBDIRS = marshallers libqcdm src plugins introspection po policy test + +if WITH_POLKIT +SUBDIRS += policy +endif dbusservicedir = $(DBUS_SYS_DIR) dbusservice_DATA = org.freedesktop.ModemManager.conf +dbusservice_file_polkit = org.freedesktop.ModemManager.conf.polkit +dbusservice_file_nopolkit = org.freedesktop.ModemManager.conf.nopolkit + +org.freedesktop.ModemManager.conf: +if WITH_POLKIT + cp -f $(top_srcdir)/$(dbusservice_file_polkit) $(dbusservice_DATA) +else + cp -f $(top_srcdir)/$(dbusservice_file_nopolkit) $(dbusservice_DATA) +endif + dbusactivationdir = $(datadir)/dbus-1/system-services dbusactivation_in_files = org.freedesktop.ModemManager.service.in dbusactivation_DATA = $(dbusactivation_in_files:.service.in=.service) @@ -39,11 +53,24 @@ edit = @sed \ DISTCHECK_CONFIGURE_FLAGS = --with-udev-base-dir=$dc_install_base -DISTCLEANFILES = \ - $(dbusactivation_DATA) +INTLTOOL_FILES = \ + intltool-extract.in \ + intltool-merge.in \ + intltool-update.in +DISTCLEANFILES = \ + $(dbusactivation_DATA) \ + $(dbusservice_DATA) \ + intltool-extract \ + intltool-merge \ + intltool-update \ + po/.intltool-merge-cache EXTRA_DIST = \ doc-generator.xsl \ - $(dbusservice_DATA) \ - $(dbusactivation_in_files) + $(dbusactivation_in_files) \ + $(INTLTOOL_FILES) \ + $(dbusservice_file_polkit) \ + $(dbusservice_file_nopolkit) + +ACLOCAL_AMFLAGS = -I m4 diff --git a/autogen.sh b/autogen.sh index d2ed9b5..62614d9 100755 --- a/autogen.sh +++ b/autogen.sh @@ -15,6 +15,7 @@ PKG_NAME=ModemManager (cd $srcdir; autoreconf --install --symlink && + intltoolize --force && autoreconf && ./configure --enable-maintainer-mode $@ ) diff --git a/configure.ac b/configure.ac index aae718e..b763d7b 100644 --- a/configure.ac +++ b/configure.ac @@ -1,9 +1,11 @@ AC_PREREQ(2.52) -AC_INIT(ModemManager, 0.3, dcbw@redhat.com, ModemManager) +AC_INIT(ModemManager, 0.4, dcbw@redhat.com, ModemManager) AM_INIT_AUTOMAKE([1.9 subdir-objects tar-ustar no-dist-gzip dist-bzip2]) AM_MAINTAINER_MODE +AC_CONFIG_MACRO_DIR([m4]) + AC_CONFIG_HEADERS(config.h) dnl Required programs @@ -12,6 +14,18 @@ AM_PROG_CC_C_O AC_PROG_INSTALL AC_PROG_LIBTOOL +dnl Define _GNU_SOURCE for various things like strcasestr() +AC_GNU_SOURCE + +dnl +dnl translation support +dnl +GETTEXT_PACKAGE=ModemManager +AC_SUBST(GETTEXT_PACKAGE) +AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [Gettext package]) +IT_PROG_INTLTOOL([0.35.0]) +AM_GLIB_GNU_GETTEXT + PKG_CHECK_MODULES(MM, dbus-glib-1 >= 0.75 glib-2.0 >= 2.18 gmodule-2.0 gobject-2.0) PKG_CHECK_MODULES(GUDEV, gudev-1.0) @@ -39,6 +53,22 @@ AC_SUBST(UDEV_BASE_DIR) GLIB_GENMARSHAL=`pkg-config --variable=glib_genmarshal glib-2.0` AC_SUBST(GLIB_GENMARSHAL) +# PolicyKit +AC_ARG_WITH(polkit, AS_HELP_STRING([--with-polkit], [Build with PolicyKit support])) +AM_CONDITIONAL(WITH_POLKIT, test "x$with_polkit" = "xyes") +case $with_polkit in + yes) + with_polkit=yes + PKG_CHECK_MODULES(POLKIT, polkit-gobject-1 >= 0.95) + AC_DEFINE(WITH_POLKIT, 1, [Define if you want to use PolicyKit]) + AC_SUBST(POLKIT_CFLAGS) + AC_SUBST(POLKIT_LIBS) + ;; + *) + with_polkit=no + ;; +esac + # PPPD AC_CHECK_HEADERS(pppd/pppd.h, have_pppd_headers="yes", have_pppd_headers="no") AM_CONDITIONAL(HAVE_PPPD_H, test "x$have_pppd_headers" = "xyes") @@ -54,39 +84,10 @@ AC_ARG_WITH([pppd-plugin-dir], AS_HELP_STRING([--with-pppd-plugin-dir=DIR], [pat if test -n "$with_pppd_plugin_dir" ; then PPPD_PLUGIN_DIR="$with_pppd_plugin_dir" else - PPPD_PLUGIN_DIR="${libdir}/pppd/2.4.4" + PPPD_PLUGIN_DIR="${libdir}/pppd/2.4.5" fi AC_SUBST(PPPD_PLUGIN_DIR) -AC_ARG_ENABLE(more-warnings, -AS_HELP_STRING([--enable-more-warnings], [Maximum compiler warnings]), set_more_warnings="$enableval",set_more_warnings=yes) -AC_MSG_CHECKING(for more warnings, including -Werror) -if test "$GCC" = "yes" -a "$set_more_warnings" != "no"; then - AC_MSG_RESULT(yes) - CFLAGS="-Wall -Werror -std=gnu89 $CFLAGS" - - for option in -Wshadow -Wmissing-declarations -Wmissing-prototypes \ - -Wdeclaration-after-statement -Wstrict-prototypes \ - -Wfloat-equal -Wno-unused-parameter -Wno-sign-compare \ - -fno-strict-aliasing; do - SAVE_CFLAGS="$CFLAGS" - CFLAGS="$CFLAGS $option" - AC_MSG_CHECKING([whether gcc understands $option]) - AC_TRY_COMPILE([], [], - has_option=yes, - has_option=no) - if test $has_option = no; then - CFLAGS="$SAVE_CFLAGS" - fi - AC_MSG_RESULT($has_option) - unset has_option - unset SAVE_CFLAGS - done - unset option -else - AC_MSG_RESULT(no) -fi - AC_ARG_WITH(docs, AC_HELP_STRING([--with-docs], [Build ModemManager documentation])) AM_CONDITIONAL(WITH_DOCS, test "x$with_docs" = "xyes") case $with_docs in @@ -110,14 +111,42 @@ case $with_tests in ;; esac +NM_COMPILER_WARNINGS + + +dnl +dnl dbus-glib >= 0.86 is required for Location API support +dnl +with_location_api=no +PKG_CHECK_MODULES(DBUS_GLIB, dbus-glib-1 >= 0.86, with_location_api="yes", with_location_api="no") +if test x"$with_location_api" = xyes; then + AC_DEFINE(LOCATION_API, 1, [Define if you have dbus-glib 0.86 or higher]) +else + AC_MSG_WARN([dbus-glib >= 0.86 is required for Location API support]) +fi +AM_CONDITIONAL(WITH_LOCATION_API, test "x$with_location_api" = "xyes") + +dnl +dnl Distribution version string +dnl +AC_ARG_WITH(dist-version, AS_HELP_STRING([--with-dist-version=], [Define the custom version (like distribution package name and revision)]), ac_distver=$withval, ac_distver="") +if ! test x"$ac_distver" = x""; then + AC_DEFINE_UNQUOTED(MM_DIST_VERSION, "$ac_distver", [Define the distribution version string]) +fi + AC_CONFIG_FILES([ Makefile marshallers/Makefile +libqcdm/Makefile +libqcdm/src/Makefile +libqcdm/tests/Makefile src/Makefile src/tests/Makefile plugins/Makefile test/Makefile introspection/Makefile +po/Makefile.in +policy/Makefile ]) AC_OUTPUT @@ -130,4 +159,8 @@ echo Building documentation: ${with_docs} echo echo Building PPP-enabled tests: ${have_pppd_headers} echo +echo Building with PolicyKit support: ${with_polkit} +echo +echo Building with Location API support: ${with_location_api} +echo diff --git a/introspection/Makefile.am b/introspection/Makefile.am index 404c1d9..941c924 100644 --- a/introspection/Makefile.am +++ b/introspection/Makefile.am @@ -13,4 +13,6 @@ EXTRA_DIST = \ mm-modem-gsm-network.xml \ mm-modem-gsm-sms.xml \ mm-modem-simple.xml \ - mm-serial-error.xml + mm-serial-error.xml \ + mm-modem-location.xml \ + mm-modem-gsm-ussd.xml diff --git a/introspection/all.xml b/introspection/all.xml index 426baf0..967e90d 100644 --- a/introspection/all.xml +++ b/introspection/all.xml @@ -25,6 +25,7 @@ + @@ -32,6 +33,7 @@ + diff --git a/introspection/mm-modem-gsm-card.xml b/introspection/mm-modem-gsm-card.xml index ad9348d..9c9fdc1 100644 --- a/introspection/mm-modem-gsm-card.xml +++ b/introspection/mm-modem-gsm-card.xml @@ -28,6 +28,20 @@ + + + Returns the ID of the network operator that issued the SIM card, + formatted as a 5 or 6-digit MCC/MNC code (ex "310410"). + + + + + + The operator ID formatted as an MCC/MNC code. + + + + Send the PUK and a new PIN to unlock the SIM card. @@ -96,11 +110,17 @@ - Bands supported by the card. (Note for plugin writers: returned value must not contain ANY) + + Bands supported by the card. (Note for plugin writers: + returned value must not contain ANY) + - Network selection modes supported by the card. (Note for plugin writers: returned value must not contain ANY) + + Network selection modes supported by the card. (Note for plugin writers: + returned value must not contain ANY) + diff --git a/introspection/mm-modem-gsm-network.xml b/introspection/mm-modem-gsm-network.xml index 934e8e0..7c26681 100644 --- a/introspection/mm-modem-gsm-network.xml +++ b/introspection/mm-modem-gsm-network.xml @@ -23,7 +23,38 @@ - Found networks. It's an array of dictionaries (strings for keys and values), the list of known keys is the following: status, operator-long, operator-short, operator-num, access-tech. +

Found networks. It's an array of dictionaries (strings for both + keys and values) with each array element describing a mobile network + found in the scan. Each dict may include one or more of the following + keys:

+
    +
  • + "status": a number representing network availability status as + defined in 3GPP TS 27.007 section 7.3. e.g. "0" (unknown), "1" + (available), "2" (current), or "3" (forbidden). This key will + always be present. +
  • +
  • + "operator-long": long-format name of operator. If the name is + unknown, this field should not be present. +
  • +
  • + "operator-short": short-format name of operator. If the name is + unknown, this field should not be present. +
  • +
  • + "operator-num": mobile code of the operator. Returned in the + format "MCCMNC", where MCC is the three-digit ITU E.212 Mobile + Country Code and MNC is the two- or three-digit GSM Mobile + Network Code. e.g. "31026" or "310260". +
  • +
  • + "access-tech": a number representing the access technology used by + this mobile network as described in 3GPP TS 27.007 section 7.3. + e.g. "0" (GSM), "1" (GSM Compact), "2" (UTRAN/UMTS), "3" (EDGE), + etc. +
  • +
@@ -82,11 +113,12 @@ - Set the desired mode the device may use when connecting to a mobile network. + Set the desired mode the device may use when connecting to a mobile + network (DEPRECATED; see SetAllowedMode instead). - + The desired network mode. Only one mode may be specified, and may not be UNKNOWN. @@ -95,11 +127,14 @@ - Returns the current network mode of the device. (Note for plugin writers: returned value *may* be ANY) + Returns the current network mode of the device (DEPRECATED; does not + allow returning both the saved mode preference *and* the current access + technology of the device at the same time. See the AllowedMode + property instead). - + Returns the general network mode (ex. 2G/3G preference) of the device. @@ -114,14 +149,65 @@ - The returned information contains: - * Network status. - * Current operator code. - * Current operator name, + The returned information is composed of the following items in the + following order: +
    +
  • + Mobile registration status as defined in 3GPP TS 27.007 section + 10.1.19. See the MM_MODEM_GSM_NETWORK_REG_STATUS enumeration for + possible values. +
  • +
  • + Current operator code of the operator to which the mobile is + currently registered. Returned in the format "MCCMNC", where MCC + is the three-digit ITU E.212 Mobile Country Code and MNC is the + two- or three-digit GSM Mobile Network Code. If the MCC and MNC + are not known or the mobile is not registered to a mobile network, + this value should be a zero-length (blank) string. e.g. "31026" + or "310260". +
  • +
  • + Current operator name of the operator to which the mobile is + currently registered. If the operator name is not knowon or the + mobile is not registered to a mobile network, this value should + be a zero-length (blank) string. +
  • +
+ + + Set the access technologies a device is allowed to use when connecting + to a mobile network. + + + + + + The allowed mode. The device may not support all modes; see + the org.freedesktop.ModemManager.Gsm.Card.SupportedModes property for + allowed modes for each device. All devices support the "ANY" flag. + + + + + + + The allowed access technologies (eg 2G/3G preference) the device is allowed + to use when connecting to a mobile network. + + + + + + The current network access technology used by the device to communicate + with the base station. (Note to plugin writers: if the device's access + technology cannot be determined, use UNKNOWN) + + + The signal quality changed. @@ -139,33 +225,45 @@ - The network status. + Mobile registration status as defined in 3GPP TS 27.007 section + 10.1.19. - The current operator code. + Current operator code of the operator to which the mobile is + currently registered. Returned in the format "MCCMNC", where MCC + is the three-digit ITU E.212 Mobile Country Code and MNC is the + two- or three-digit GSM Mobile Network Code. If the MCC and MNC + are not known or the mobile is not registered to a mobile network, + this value should be a zero-length (blank) string. e.g. "31026" or + "310260". - The current operator name. + Current operator name of the operator to which the mobile is + currently registered. If the operator name is not knowon or the + mobile is not registered to a mobile network, this value should + be a zero-length (blank) string. - The network mode changed. + The network mode preference changed. (DEPRECATED; see documentation + for GetNetworkMode/SetNetworkMode) - - - The new network mode. - + + The new network mode. + + GSM registration code as defined in 3GPP TS 27.007 section 10.1.19. + Not registered, not searching for new operator to register. @@ -198,5 +296,45 @@ + + + DEPRECATED; should not be used in new applications. Use + AccessTechnology, AllowedMode, and SetAllowedMode() instead. + + + Any network mode can be used + + + GPRS + + + EDGE + + + UMTS (3G) + + + HSDPA + + + Prefer 2G (GPRS or EDGE) + + + Prefer 3G (UMTS/HSDPA/HSUPA/HSPA) + + + Use only 2G (GPRS or EDGE) + + + Use only 3G (UMTS/HSDPA/HSUPA/HSPA) + + + HSUPA + + + HSPA (HSDPA + HSUPA) + + + diff --git a/introspection/mm-modem-gsm-ussd.xml b/introspection/mm-modem-gsm-ussd.xml new file mode 100644 index 0000000..ae6884f --- /dev/null +++ b/introspection/mm-modem-gsm-ussd.xml @@ -0,0 +1,78 @@ + + + + + + + + Sends a USSD command string to the network initiating a USSD session. + When the request is handled by the network, the method returns the + response or an appropriate error. The network may be awaiting further + response from the ME after returning from this method and no new command + can be initiated until this one is cancelled or ended. + + + + + + The command to start the USSD session with. + + + + + The network response to the command which started the USSD session. + + + + + + + Respond to a USSD request that is either initiated by the mobile network, + or that is awaiting further input after Initiate() was called. + + + + + + The response to network-initiated USSD command, or a response to a + request for further input. + + + + + + + Cancel an ongoing USSD session, either mobile or network initiated. + + + + + + + + Indicates the state of any ongoing USSD session. Values may be one of + the following: "idle" (no active session), "active" (a session is active + and the mobile is waiting for a response), "user-response" (the network + is waiting for the client's response, which must be sent using Respond()). + + + + + + Contains any network-initiated request to which no USSD response is + required. When no USSD session is active, or when there is no network- + initiated request, this property will be a zero-length string. + + + + + + Contains any pending network-initiated request for a response. Client + should call Respond() with the appropriate response to this request. + When no USSD session is active, or when there is no pending + network-initiated request, this property will be a zero-length string. + + + + + diff --git a/introspection/mm-modem-gsm.xml b/introspection/mm-modem-gsm.xml index d6c42c8..ea23adc 100644 --- a/introspection/mm-modem-gsm.xml +++ b/introspection/mm-modem-gsm.xml @@ -3,6 +3,11 @@ + + A bitfield describing the specifc access modes and technologies + supported by a device and the access technology in-use when connected to + a mobile network. + Unknown or invalid mode. @@ -19,28 +24,39 @@ UMTS (3G) - HSDPA + HSDPA (3G) Prefer 2G (GPRS or EDGE) - Prefer 3G (UMTS or HSDPA) + Prefer 3G (UMTS/HSDPA/HSUPA/HSPA) Use only 2G (GPRS or EDGE) - Use only 3G (UMTS or HSDPA) + Use only 3G (UMTS/HSDPA/HSUPA/HSPA) - HSUPA + HSUPA (3G) + + + HSPA (3G) + + + GSM + + + GSM Compact - GSM network and device bands. + A bitfield describing the specific radio bands supported by the device + and the radio bands the device is allowed to use when connecting to a + mobile network. Unknown or invalid band @@ -81,6 +97,68 @@ WCDMA 3GPP UMTS 1700 MHz (Class IX) + + WCDMA 3GPP UMTS 1900 MHz (Class II) + + + + + Describes the device's current access mode preference; ie the specific + technology preferences the device is allowed to use when connecting to + a mobile network. + + + Any mode can be used + + + Prefer 2G (GPRS or EDGE) + + + Prefer 3G (UMTS or HSxPA) + + + Use only 2G (GPRS or EDGE) + + + Use only 3G (UMTS or HSxPA) + + + + + + Describes various access technologies that a device uses when connected + to a mobile network. + + + The access technology used is unknown + + + GSM + + + Compact GSM + + + GPRS + + + EDGE (ETSI 27.007: "GSM w/EGPRS") + + + UMTS (ETSI 27.007: "UTRAN") + + + HSDPA (ETSI 27.007: "UTRAN w/HSDPA") + + + HSUPA (ETSI 27.007: "UTRAN w/HSUPA") + + + HSPA (ETSI 27.007: "UTRAN w/HSDPA and HSUPA") + + + + diff --git a/introspection/mm-modem-location.xml b/introspection/mm-modem-location.xml new file mode 100644 index 0000000..58dca68 --- /dev/null +++ b/introspection/mm-modem-location.xml @@ -0,0 +1,253 @@ + + + + + + This interface allows devices to provide location information to client + applications. Not all devices can provide this information, or even if + they do, they may not be able to provide it while a data session is + active. + + + + + Location capabilities of the device. + + + + + + TRUE if location information gathering is enabled for this device, FALSE + if it is disabled. When disabled, the device will not provide location + information. + + + + + + Enable or disable location information gathering. This method may + require the client to authenticate itself. This method may also cause + any necessary functionality of the mobile be be turned on, including + enabling the modem device itself. + + + + + + TRUE to enable location information gathering, FALSE to disable. + + + + + When enabling location information gathering, this argument controls + whether the device emits signals with new location information or not. + When signals are emitted, any client application (including malicious + ones!) can listen for location updates unless D-Bus permissions + restrict these signals from certain users. If further security is + desired, this argument can be set to FALSE to disable location + updates via D-Bus signals and require applications to call + authenticated APIs (like GetLocation) to get location information. + This argument is ignored when disabling location information + gathering. + + + + + + + Return current location information, if any. This method may require + the client to authenticate itself. + + + + + + Dict of available location information when location information + gathering is enabled. If the modem supports multiple location types + it may return more than one here. + + + + + + + TRUE if location updates will be emitted via D-Bus signals, FALSE + if location updates will not be emitted. See the Enable method for + more information. + + + + + + Dict of available location information when location information + gathering is enabled. If the modem supports multiple location types + it may return more than one here. Note that if the device was told + not to emit updated location information when location information + gathering was initially enabled, this property may not return + any location information for security reasons. + + + + + + A mapping from location type to type-specific location information. + + + + Identifies the type and format of the associated location information. + Contrary to the value description, this is not a bitfield but uses the + same values as the MM_MODEM_LOCATION_CAPABILITIES bitfield. + + + + + Contains type-specific location information. See the documentation for + each type for a description of its data format. + + + + + + +

Unknown or no capabilties.

+
+ + +

For capability reporting, indicates the device is capable of + providing GPS NMEA-format location information.

+ +

For location reporting, devices supporting this capability return + a string containing one or more NMEA sentences (D-Bus signature 's'). + The manager will cache the most recent NMEA sentence of each type for + a period of time not less than 30 seconds. When reporting multiple + NMEA sentences, sentences shall be separated by an ASCII Carriage + Return and Line Feed (<CR><LF>) sequence. +

+

+ For example, if the device sends a $GPRMC sentence immediately + followed by a $GPGGA sentence, the reported location string would be + (where of course the <CR><LF> is replaced with the actual + ASCII CR (0x0D) and LF (0x0A) control characters): +

+          $GPRMC,134523.92,V,,,,,,,030136,,,N*73<CR><LF>$GPGGA,,,,,,0,00,0.5,,M,0.0001999,M,0.0000099,0000*45
+          
+ If the device sends a new $GPRMC three seconds later, the new $GPRMC + replaces the previously received $GPRMC sentence, and the updated + string would be: +
+          $GPRMC,134526.92,V,,,,,,,030136,,,N*76<CR><LF>$GPGGA,,,,,,0,00,0.5,,M,0.0001999,M,0.0000099,0000*45
+          
+ If the device then sends a $GPGSA sentence about 5 seconds later, the + $GPGSA sentence is added to the string (since no $GPGSA sentence was + previously received in this session), the updated string would be: +
+          $GPRMC,134526.92,V,,,,,,,030136,,,N*76<CR><LF>$GPGGA,,,,,,0,00,0.5,,M,0.0001999,M,0.0000099,0000*45<CR><LF>$GPGSA,A,1,,,,,,,,,,,,,1.1,0.5,1.0*34
+          
+ The manager may discard any cached sentences older than 30 seconds. +

+

This allows clients to read the latest positioning data as soon as + possible after they start, even if the device is not providing + frequent location data updates. +

+
+
+ + +

For capability reporting, indicates the device is capable of + providing GSM Location Area Code/Cell ID location information.

+ +

For location reporting, devices supporting this + capability return a string in the format "MCC,MNC,LAC,CI" (without the + quotes of course) where the following applies:

+
    +
  • + MCC is the three-digit ITU E.212 Mobile Country Code of the + network provider to which the mobile is currently registered. + This value should be the same MCC as reported by the + org.freedesktop.Modem.Gsm.Network.GetRegistrationInfo() method's + returned "operator code" argument. + e.g. "310" +
  • +
  • + MNC is the two- or three-digit GSM Mobile Network Code of the + network provider to which the mobile is currently registered. + This value should be the same MCC as reported by the + org.freedesktop.Modem.Gsm.Network.GetRegistrationInfo() method's + returned "operator code" argument. + e.g. "26" or "260" +
  • +
  • + LAC is the two-byte Location Area Code of the base station with + which the mobile is registered, in upper-case hexadecimal format + without leading zeros, as specified in 3GPP TS 27.007 section + 10.1.19. e.g. "84CD". +
  • +
  • + CI is the two- or four-byte Cell Identifier with which the mobile + is registered, in upper-case hexadecimal format without leading + zeros, as specified in 3GPP TS 27.007. e.g. "2BAF" or "D30156". +
  • +
+

The entire string may only be composed of the ASCII digits [0-9], + the alphabetical characters [A-F], and the comma (,) character. No + other characters are allowed. For example: "310,260,8BE3,2BAF" or + "250,40,CE00,1CEAD8F0".

+ +

If any of these four items (MCC,MNC,LAC,CI) is unknown or the + mobile is not registered with a network, then the GSM_LAC_CI location + information item should not be provided as a returned value from the + GetLocation() method or in the Location property.

+
+
+ + +

For capability reporting, indicates the device is capable of + providing raw GPS information using a series of defined key/value + pairs.

+ +

For location reporting, devices supporting this + capability return a D-Bus dict (signature a{sv}) mapping well-known + keys to values with defined formats. The allowed key/value pairs + and their formats are:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyValue TypeValue contentsExample
latitudedLatitude in Decimal Degrees (positive numbers mean N quadrasphere, negative mean S quadrasphere)38.889722 (ie, 38d 53' 22" N)
longitudedLongitude in Decimal Degrees (positive numbers mean E quadrasphere, negative mean W quadrasphere)-77.008889 (ie, 77d 0' 32" W)
altitudedAltitude above sea level in meters33.5
horiz-velocitydHorizontal velocity in meters-per-second.5
vert-velocitydVertical velocity in meters-per-second.01
+

The 'latitude' and 'longitude' keys are required; other keys are + optional.

+
+
+
+ +
+
+ diff --git a/introspection/mm-modem.xml b/introspection/mm-modem.xml index 7896fff..7d54dd3 100644 --- a/introspection/mm-modem.xml +++ b/introspection/mm-modem.xml @@ -81,6 +81,17 @@
+ + + Reset the modem to as close to factory state as possible. + + + + + Carrier-supplied code required to reset the modem. Ignored if not required. + + + The modem port to use for IP configuration and traffic. @@ -113,6 +124,32 @@ + + + The identity of the device. This will be the IMEI number for + GSM devices and the hex-format ESN/MEID for CDMA devices. + + + + + + Empty if the device is usable without an unlock code or has already + been unlocked. If the device needs to be unlocked before becoming usable this + property contains the specific unlock code required.  Valid unlock code values + are "" (blank), "sim-pin", "sim-puk", "ph-sim-pin", "ph-fsim-pin", + "ph-fsim-puk", "sim-pin2", "sim-puk2", "ph-net-pin", "ph-net-puk", + "ph-netsub-pin", "ph-netsub-puk", "ph-sp-pin", "ph-sp-puk", "ph-corp-pin", and + "ph-corp-puk". + + + + + + The number of unlock retries remaining for the unlock code given by the property UnlockRequired, or 999 if + the device does not support reporting unlock retries. + + + The IP configuration method. diff --git a/libqcdm/Makefile.am b/libqcdm/Makefile.am new file mode 100644 index 0000000..77f28d7 --- /dev/null +++ b/libqcdm/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS=src tests + diff --git a/libqcdm/src/Makefile.am b/libqcdm/src/Makefile.am new file mode 100644 index 0000000..5cc0568 --- /dev/null +++ b/libqcdm/src/Makefile.am @@ -0,0 +1,42 @@ +noinst_LTLIBRARIES = libqcdm.la libqcdm-test.la + + +libqcdm_la_CPPFLAGS = \ + $(MM_CFLAGS) + +libqcdm_la_SOURCES = \ + dm-commands.h \ + nv-items.h \ + com.c \ + com.h \ + commands.c \ + commands.h \ + error.c \ + error.h \ + result.c \ + result.h \ + result-private.h \ + utils.c \ + utils.h + +libqcdm_la_LIBADD = \ + $(MM_LIBS) + +libqcdm_la_LDFLAGS = -Wl,--version-script=$(srcdir)/libqcdm.ver \ + -version-info "0:0:0" + + +########################################### +# Test library without symbol versioning +########################################### + +libqcdm_test_la_CPPFLAGS = \ + $(MM_CFLAGS) + +libqcdm_test_la_SOURCES = \ + utils.c \ + utils.h + +libqcdm_test_la_LIBADD = \ + $(MM_LIBS) + diff --git a/libqcdm/src/com.c b/libqcdm/src/com.c new file mode 100644 index 0000000..353103a --- /dev/null +++ b/libqcdm/src/com.c @@ -0,0 +1,61 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "com.h" +#include "error.h" + +gboolean +qcdm_port_setup (int fd, GError **error) +{ + struct termios stbuf; + + g_type_init (); + + errno = 0; + memset (&stbuf, 0, sizeof (stbuf)); + if (tcgetattr (fd, &stbuf) != 0) { + g_set_error (error, + QCDM_SERIAL_ERROR, QCDM_SERIAL_CONFIG_FAILED, + "tcgetattr() error: %d", errno); + } + + stbuf.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | CLOCAL | PARENB); + stbuf.c_iflag &= ~(HUPCL | IUTF8 | IUCLC | ISTRIP | IXON | IXOFF | IXANY | ICRNL); + stbuf.c_oflag &= ~(OPOST | OCRNL | ONLCR | OLCUC | ONLRET); + stbuf.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO | ECHOE | ECHOK | ECHONL); + stbuf.c_lflag &= ~(NOFLSH | XCASE | TOSTOP | ECHOPRT | ECHOCTL | ECHOKE); + stbuf.c_cc[VMIN] = 1; + stbuf.c_cc[VTIME] = 0; + stbuf.c_cc[VEOF] = 1; + stbuf.c_cflag |= (B115200 | CS8 | CREAD | 0 | 0); /* No parity, 1 stop bit */ + + errno = 0; + if (tcsetattr (fd, TCSANOW, &stbuf) < 0) { + g_set_error (error, + QCDM_SERIAL_ERROR, QCDM_SERIAL_CONFIG_FAILED, + "tcsetattr() error: %d", errno); + return FALSE; + } + + return TRUE; +} + diff --git a/libqcdm/src/com.h b/libqcdm/src/com.h new file mode 100644 index 0000000..97561d0 --- /dev/null +++ b/libqcdm/src/com.h @@ -0,0 +1,25 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBQCDM_COM_H +#define LIBQCDM_COM_H + +#include + +gboolean qcdm_port_setup (int fd, GError **error); + +#endif /* LIBQCDM_COM_H */ diff --git a/libqcdm/src/commands.c b/libqcdm/src/commands.c new file mode 100644 index 0000000..11a1a38 --- /dev/null +++ b/libqcdm/src/commands.c @@ -0,0 +1,1064 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "commands.h" +#include "error.h" +#include "dm-commands.h" +#include "nv-items.h" +#include "result-private.h" +#include "utils.h" + + +/**********************************************************************/ + +static guint8 +cdma_prev_to_qcdm (guint8 cdma) +{ + switch (cdma) { + case CDMA_PREV_IS_95: + return QCDM_CDMA_PREV_IS_95; + case CDMA_PREV_IS_95A: + return QCDM_CDMA_PREV_IS_95A; + case CDMA_PREV_IS_95A_TSB74: + return QCDM_CDMA_PREV_IS_95A_TSB74; + case CDMA_PREV_IS_95B_PHASE1: + return QCDM_CDMA_PREV_IS_95B_PHASE1; + case CDMA_PREV_IS_95B_PHASE2: + return QCDM_CDMA_PREV_IS_95B_PHASE2; + case CDMA_PREV_IS2000_REL0: + return QCDM_CDMA_PREV_IS2000_REL0; + case CDMA_PREV_IS2000_RELA: + return QCDM_CDMA_PREV_IS2000_RELA; + default: + break; + } + return QCDM_CDMA_PREV_UNKNOWN; +} + +static guint8 +cdma_band_class_to_qcdm (guint8 cdma) +{ + switch (cdma) { + case CDMA_BAND_CLASS_0_CELLULAR_800: + return QCDM_CDMA_BAND_CLASS_0_CELLULAR_800; + case CDMA_BAND_CLASS_1_PCS: + return QCDM_CDMA_BAND_CLASS_1_PCS; + case CDMA_BAND_CLASS_2_TACS: + return QCDM_CDMA_BAND_CLASS_2_TACS; + case CDMA_BAND_CLASS_3_JTACS: + return QCDM_CDMA_BAND_CLASS_3_JTACS; + case CDMA_BAND_CLASS_4_KOREAN_PCS: + return QCDM_CDMA_BAND_CLASS_4_KOREAN_PCS; + case CDMA_BAND_CLASS_5_NMT450: + return QCDM_CDMA_BAND_CLASS_5_NMT450; + case CDMA_BAND_CLASS_6_IMT2000: + return QCDM_CDMA_BAND_CLASS_6_IMT2000; + case CDMA_BAND_CLASS_7_CELLULAR_700: + return QCDM_CDMA_BAND_CLASS_7_CELLULAR_700; + case CDMA_BAND_CLASS_8_1800: + return QCDM_CDMA_BAND_CLASS_8_1800; + case CDMA_BAND_CLASS_9_900: + return QCDM_CDMA_BAND_CLASS_9_900; + case CDMA_BAND_CLASS_10_SECONDARY_800: + return QCDM_CDMA_BAND_CLASS_10_SECONDARY_800; + case CDMA_BAND_CLASS_11_PAMR_400: + return QCDM_CDMA_BAND_CLASS_11_PAMR_400; + case CDMA_BAND_CLASS_12_PAMR_800: + return QCDM_CDMA_BAND_CLASS_12_PAMR_800; + default: + break; + } + return QCDM_CDMA_BAND_CLASS_UNKNOWN; +} + +/**********************************************************************/ + +/* + * utils_bin2hexstr + * + * Convert a byte-array into a hexadecimal string. + * + * Code originally by Alex Larsson and + * copyright Red Hat, Inc. under terms of the LGPL. + * + */ +static char * +bin2hexstr (const guint8 *bytes, int len) +{ + static char hex_digits[] = "0123456789abcdef"; + char *result; + int i; + gsize buflen = (len * 2) + 1; + + g_return_val_if_fail (bytes != NULL, NULL); + g_return_val_if_fail (len > 0, NULL); + g_return_val_if_fail (len < 4096, NULL); /* Arbitrary limit */ + + result = g_malloc0 (buflen); + for (i = 0; i < len; i++) { + result[2*i] = hex_digits[(bytes[i] >> 4) & 0xf]; + result[2*i+1] = hex_digits[bytes[i] & 0xf]; + } + result[buflen - 1] = '\0'; + return result; +} + +static gboolean +check_command (const char *buf, gsize len, guint8 cmd, gsize min_len, GError **error) +{ + if (len < 1) { + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_MALFORMED_RESPONSE, + "DM command response malformed (must be at least 1 byte in length)"); + return FALSE; + } + + switch (buf[0]) { + case DIAG_CMD_BAD_CMD: + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_COMMAND, + "DM command %d unknown or unimplemented by the device", + cmd); + return FALSE; + case DIAG_CMD_BAD_PARM: + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_PARAMETER, + "DM command %d contained invalid parameter", + cmd); + return FALSE; + case DIAG_CMD_BAD_LEN: + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_LENGTH, + "DM command %d was the wrong size", + cmd); + return FALSE; + case DIAG_CMD_BAD_DEV: + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_NOT_ACCEPTED, + "DM command %d was not accepted by the device", + cmd); + return FALSE; + case DIAG_CMD_BAD_MODE: + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_MODE, + "DM command %d not allowed in the current device mode", + cmd); + return FALSE; + default: + break; + } + + if (buf[0] != cmd) { + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_UNEXPECTED, + "Unexpected DM command response (expected %d, got %d)", + cmd, buf[0]); + return FALSE; + } + + if (len < min_len) { + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_LENGTH, + "DM command %d response not long enough (got %zu, expected " + "at least %zu).", cmd, len, min_len); + return FALSE; + } + + return TRUE; +} + +static gboolean +check_nv_cmd (DMCmdNVReadWrite *cmd, guint16 nv_item, GError **error) +{ + guint16 cmd_item; + + g_return_val_if_fail (cmd != NULL, FALSE); + g_return_val_if_fail ((cmd->code == DIAG_CMD_NV_READ) || (cmd->code == DIAG_CMD_NV_WRITE), FALSE); + + /* NV read/write have a status byte at the end */ + if (cmd->status != 0) { + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_NVCMD_FAILED, + "The NV operation failed (status 0x%X).", + GUINT16_FROM_LE (cmd->status)); + return FALSE; + } + + cmd_item = GUINT16_FROM_LE (cmd->nv_item); + if (cmd_item != nv_item) { + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_UNEXPECTED, + "Unexpected DM NV command response (expected item %d, got " + "item %d)", nv_item, cmd_item); + return FALSE; + } + + return TRUE; +} + +/**********************************************************************/ + +gsize +qcdm_cmd_version_info_new (char *buf, gsize len, GError **error) +{ + char cmdbuf[3]; + DMCmdHeader *cmd = (DMCmdHeader *) &cmdbuf[0]; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + memset (cmd, 0, sizeof (*cmd)); + cmd->code = DIAG_CMD_VERSION_INFO; + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +QCDMResult * +qcdm_cmd_version_info_result (const char *buf, gsize len, GError **error) +{ + QCDMResult *result = NULL; + DMCmdVersionInfoRsp *rsp = (DMCmdVersionInfoRsp *) buf; + char tmp[12]; + + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_VERSION_INFO, sizeof (DMCmdVersionInfoRsp), error)) + return NULL; + + result = qcdm_result_new (); + + memset (tmp, 0, sizeof (tmp)); + g_assert (sizeof (rsp->comp_date) <= sizeof (tmp)); + memcpy (tmp, rsp->comp_date, sizeof (rsp->comp_date)); + qcdm_result_add_string (result, QCDM_CMD_VERSION_INFO_ITEM_COMP_DATE, tmp); + + memset (tmp, 0, sizeof (tmp)); + g_assert (sizeof (rsp->comp_time) <= sizeof (tmp)); + memcpy (tmp, rsp->comp_time, sizeof (rsp->comp_time)); + qcdm_result_add_string (result, QCDM_CMD_VERSION_INFO_ITEM_COMP_TIME, tmp); + + memset (tmp, 0, sizeof (tmp)); + g_assert (sizeof (rsp->rel_date) <= sizeof (tmp)); + memcpy (tmp, rsp->rel_date, sizeof (rsp->rel_date)); + qcdm_result_add_string (result, QCDM_CMD_VERSION_INFO_ITEM_RELEASE_DATE, tmp); + + memset (tmp, 0, sizeof (tmp)); + g_assert (sizeof (rsp->rel_time) <= sizeof (tmp)); + memcpy (tmp, rsp->rel_time, sizeof (rsp->rel_time)); + qcdm_result_add_string (result, QCDM_CMD_VERSION_INFO_ITEM_RELEASE_TIME, tmp); + + memset (tmp, 0, sizeof (tmp)); + g_assert (sizeof (rsp->model) <= sizeof (tmp)); + memcpy (tmp, rsp->model, sizeof (rsp->model)); + qcdm_result_add_string (result, QCDM_CMD_VERSION_INFO_ITEM_MODEL, tmp); + + return result; +} + +/**********************************************************************/ + +gsize +qcdm_cmd_esn_new (char *buf, gsize len, GError **error) +{ + char cmdbuf[3]; + DMCmdHeader *cmd = (DMCmdHeader *) &cmdbuf[0]; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + memset (cmd, 0, sizeof (*cmd)); + cmd->code = DIAG_CMD_ESN; + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +QCDMResult * +qcdm_cmd_esn_result (const char *buf, gsize len, GError **error) +{ + QCDMResult *result = NULL; + DMCmdEsnRsp *rsp = (DMCmdEsnRsp *) buf; + char *tmp; + guint8 swapped[4]; + + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_ESN, sizeof (DMCmdEsnRsp), error)) + return NULL; + + result = qcdm_result_new (); + + /* Convert the ESN from binary to a hex string; it's LE so we have to + * swap it to get the correct ordering. + */ + swapped[0] = rsp->esn[3]; + swapped[1] = rsp->esn[2]; + swapped[2] = rsp->esn[1]; + swapped[3] = rsp->esn[0]; + + tmp = bin2hexstr (&swapped[0], sizeof (swapped)); + qcdm_result_add_string (result, QCDM_CMD_ESN_ITEM_ESN, tmp); + g_free (tmp); + + return result; +} + +/**********************************************************************/ + +gsize +qcdm_cmd_cdma_status_new (char *buf, gsize len, GError **error) +{ + char cmdbuf[3]; + DMCmdHeader *cmd = (DMCmdHeader *) &cmdbuf[0]; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + memset (cmd, 0, sizeof (*cmd)); + cmd->code = DIAG_CMD_STATUS; + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +QCDMResult * +qcdm_cmd_cdma_status_result (const char *buf, gsize len, GError **error) +{ + QCDMResult *result = NULL; + DMCmdStatusRsp *rsp = (DMCmdStatusRsp *) buf; + char *tmp; + guint8 swapped[4]; + guint32 tmp_num; + + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_STATUS, sizeof (DMCmdStatusRsp), error)) + return NULL; + + result = qcdm_result_new (); + + /* Convert the ESN from binary to a hex string; it's LE so we have to + * swap it to get the correct ordering. + */ + swapped[0] = rsp->esn[3]; + swapped[1] = rsp->esn[2]; + swapped[2] = rsp->esn[1]; + swapped[3] = rsp->esn[0]; + + tmp = bin2hexstr (&swapped[0], sizeof (swapped)); + qcdm_result_add_string (result, QCDM_CMD_CDMA_STATUS_ITEM_ESN, tmp); + g_free (tmp); + + tmp_num = (guint32) GUINT16_FROM_LE (rsp->rf_mode); + qcdm_result_add_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_RF_MODE, tmp_num); + + tmp_num = (guint32) GUINT16_FROM_LE (rsp->cdma_rx_state); + qcdm_result_add_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_RX_STATE, tmp_num); + + tmp_num = (guint32) GUINT16_FROM_LE (rsp->entry_reason); + qcdm_result_add_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_ENTRY_REASON, tmp_num); + + tmp_num = (guint32) GUINT16_FROM_LE (rsp->curr_chan); + qcdm_result_add_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_CURRENT_CHANNEL, tmp_num); + + qcdm_result_add_uint8 (result, QCDM_CMD_CDMA_STATUS_ITEM_CODE_CHANNEL, rsp->cdma_code_chan); + + tmp_num = (guint32) GUINT16_FROM_LE (rsp->pilot_base); + qcdm_result_add_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_PILOT_BASE, tmp_num); + + tmp_num = (guint32) GUINT16_FROM_LE (rsp->sid); + qcdm_result_add_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_SID, tmp_num); + + tmp_num = (guint32) GUINT16_FROM_LE (rsp->nid); + qcdm_result_add_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_NID, tmp_num); + + return result; +} + +/**********************************************************************/ + +gsize +qcdm_cmd_sw_version_new (char *buf, gsize len, GError **error) +{ + char cmdbuf[3]; + DMCmdHeader *cmd = (DMCmdHeader *) &cmdbuf[0]; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + memset (cmd, 0, sizeof (*cmd)); + cmd->code = DIAG_CMD_SW_VERSION; + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +QCDMResult * +qcdm_cmd_sw_version_result (const char *buf, gsize len, GError **error) +{ + QCDMResult *result = NULL; + DMCmdSwVersionRsp *rsp = (DMCmdSwVersionRsp *) buf; + char tmp[25]; + + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_SW_VERSION, sizeof (*rsp), error)) + return NULL; + + result = qcdm_result_new (); + + memset (tmp, 0, sizeof (tmp)); + g_assert (sizeof (rsp->version) <= sizeof (tmp)); + memcpy (tmp, rsp->version, sizeof (rsp->version)); + qcdm_result_add_string (result, QCDM_CMD_SW_VERSION_ITEM_VERSION, tmp); + + memset (tmp, 0, sizeof (tmp)); + g_assert (sizeof (rsp->comp_date) <= sizeof (tmp)); + memcpy (tmp, rsp->comp_date, sizeof (rsp->comp_date)); + qcdm_result_add_string (result, QCDM_CMD_SW_VERSION_ITEM_COMP_DATE, tmp); + + memset (tmp, 0, sizeof (tmp)); + g_assert (sizeof (rsp->comp_time) <= sizeof (tmp)); + memcpy (tmp, rsp->comp_time, sizeof (rsp->comp_time)); + qcdm_result_add_string (result, QCDM_CMD_SW_VERSION_ITEM_COMP_TIME, tmp); + + return result; +} + +/**********************************************************************/ + +gsize +qcdm_cmd_pilot_sets_new (char *buf, gsize len, GError **error) +{ + char cmdbuf[3]; + DMCmdHeader *cmd = (DMCmdHeader *) &cmdbuf[0]; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + memset (cmd, 0, sizeof (*cmd)); + cmd->code = DIAG_CMD_PILOT_SETS; + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +#define PILOT_SETS_CMD_ACTIVE_SET "active-set" +#define PILOT_SETS_CMD_CANDIDATE_SET "candidate-set" +#define PILOT_SETS_CMD_NEIGHBOR_SET "neighbor-set" + +static const char * +set_num_to_str (guint32 num) +{ + if (num == QCDM_CMD_PILOT_SETS_TYPE_ACTIVE) + return PILOT_SETS_CMD_ACTIVE_SET; + if (num == QCDM_CMD_PILOT_SETS_TYPE_CANDIDATE) + return PILOT_SETS_CMD_CANDIDATE_SET; + if (num == QCDM_CMD_PILOT_SETS_TYPE_NEIGHBOR) + return PILOT_SETS_CMD_NEIGHBOR_SET; + return NULL; +} + +QCDMResult * +qcdm_cmd_pilot_sets_result (const char *buf, gsize len, GError **error) +{ + QCDMResult *result = NULL; + DMCmdPilotSetsRsp *rsp = (DMCmdPilotSetsRsp *) buf; + GByteArray *array; + gsize sets_len; + + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_PILOT_SETS, sizeof (DMCmdPilotSetsRsp), error)) + return NULL; + + result = qcdm_result_new (); + + sets_len = rsp->active_count * sizeof (DMCmdPilotSetsSet); + if (sets_len > 0) { + array = g_byte_array_sized_new (sets_len); + g_byte_array_append (array, (const guint8 *) &rsp->sets[0], sets_len); + qcdm_result_add_boxed (result, PILOT_SETS_CMD_ACTIVE_SET, G_TYPE_BYTE_ARRAY, array); + } + + sets_len = rsp->candidate_count * sizeof (DMCmdPilotSetsSet); + if (sets_len > 0) { + array = g_byte_array_sized_new (sets_len); + g_byte_array_append (array, (const guint8 *) &rsp->sets[rsp->active_count], sets_len); + qcdm_result_add_boxed (result, PILOT_SETS_CMD_CANDIDATE_SET, G_TYPE_BYTE_ARRAY, array); + } + + sets_len = rsp->neighbor_count * sizeof (DMCmdPilotSetsSet); + if (sets_len > 0) { + array = g_byte_array_sized_new (sets_len); + g_byte_array_append (array, (const guint8 *) &rsp->sets[rsp->active_count + rsp->candidate_count], sets_len); + qcdm_result_add_boxed (result, PILOT_SETS_CMD_NEIGHBOR_SET, G_TYPE_BYTE_ARRAY, array); + } + + return result; +} + +gboolean +qcdm_cmd_pilot_sets_result_get_num (QCDMResult *result, + guint32 set_type, + guint32 *out_num) +{ + const char *set_name; + GByteArray *array = NULL; + + g_return_val_if_fail (result != NULL, FALSE); + + set_name = set_num_to_str (set_type); + g_return_val_if_fail (set_name != NULL, FALSE); + + if (!qcdm_result_get_boxed (result, set_name, (gpointer) &array)) + return FALSE; + + *out_num = array->len / sizeof (DMCmdPilotSetsSet); + return TRUE; +} + +gboolean +qcdm_cmd_pilot_sets_result_get_pilot (QCDMResult *result, + guint32 set_type, + guint32 num, + guint32 *out_pn_offset, + guint32 *out_ecio, + float *out_db) +{ + const char *set_name; + GByteArray *array = NULL; + DMCmdPilotSetsSet *set; + + g_return_val_if_fail (result != NULL, FALSE); + + set_name = set_num_to_str (set_type); + g_return_val_if_fail (set_name != NULL, FALSE); + + if (!qcdm_result_get_boxed (result, set_name, (gpointer) &array)) + return FALSE; + + g_return_val_if_fail (num < array->len / sizeof (DMCmdPilotSetsSet), FALSE); + + set = (DMCmdPilotSetsSet *) &array->data[num * sizeof (DMCmdPilotSetsSet)]; + *out_pn_offset = set->pn_offset; + *out_ecio = set->ecio; + /* EC/IO is in units of -0.5 dB per the specs */ + *out_db = (float) set->ecio * -0.5; + return TRUE; +} + +/**********************************************************************/ + +gsize +qcdm_cmd_nv_get_mdn_new (char *buf, gsize len, guint8 profile, GError **error) +{ + char cmdbuf[sizeof (DMCmdNVReadWrite) + 2]; + DMCmdNVReadWrite *cmd = (DMCmdNVReadWrite *) &cmdbuf[0]; + DMNVItemMdn *req; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + memset (cmd, 0, sizeof (*cmd)); + cmd->code = DIAG_CMD_NV_READ; + cmd->nv_item = GUINT16_TO_LE (DIAG_NV_DIR_NUMBER); + + req = (DMNVItemMdn *) &cmd->data[0]; + req->profile = profile; + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +QCDMResult * +qcdm_cmd_nv_get_mdn_result (const char *buf, gsize len, GError **error) +{ + QCDMResult *result = NULL; + DMCmdNVReadWrite *rsp = (DMCmdNVReadWrite *) buf; + DMNVItemMdn *mdn; + char tmp[11]; + + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_NV_READ, sizeof (DMCmdNVReadWrite), error)) + return NULL; + + if (!check_nv_cmd (rsp, DIAG_NV_DIR_NUMBER, error)) + return NULL; + + mdn = (DMNVItemMdn *) &rsp->data[0]; + + result = qcdm_result_new (); + + qcdm_result_add_uint8 (result, QCDM_CMD_NV_GET_MDN_ITEM_PROFILE, mdn->profile); + + memset (tmp, 0, sizeof (tmp)); + g_assert (sizeof (mdn->mdn) <= sizeof (tmp)); + memcpy (tmp, mdn->mdn, sizeof (mdn->mdn)); + qcdm_result_add_string (result, QCDM_CMD_NV_GET_MDN_ITEM_MDN, tmp); + + return result; +} + +/**********************************************************************/ + +static gboolean +roam_pref_validate (guint8 dm) +{ + if ( dm == DIAG_NV_ROAM_PREF_HOME_ONLY + || dm == DIAG_NV_ROAM_PREF_ROAM_ONLY + || dm == DIAG_NV_ROAM_PREF_AUTO) + return TRUE; + return FALSE; +} + +gsize +qcdm_cmd_nv_get_roam_pref_new (char *buf, gsize len, guint8 profile, GError **error) +{ + char cmdbuf[sizeof (DMCmdNVReadWrite) + 2]; + DMCmdNVReadWrite *cmd = (DMCmdNVReadWrite *) &cmdbuf[0]; + DMNVItemRoamPref *req; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + memset (cmd, 0, sizeof (*cmd)); + cmd->code = DIAG_CMD_NV_READ; + cmd->nv_item = GUINT16_TO_LE (DIAG_NV_ROAM_PREF); + + req = (DMNVItemRoamPref *) &cmd->data[0]; + req->profile = profile; + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +QCDMResult * +qcdm_cmd_nv_get_roam_pref_result (const char *buf, gsize len, GError **error) +{ + QCDMResult *result = NULL; + DMCmdNVReadWrite *rsp = (DMCmdNVReadWrite *) buf; + DMNVItemRoamPref *roam; + + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_NV_READ, sizeof (DMCmdNVReadWrite), error)) + return NULL; + + if (!check_nv_cmd (rsp, DIAG_NV_ROAM_PREF, error)) + return NULL; + + roam = (DMNVItemRoamPref *) &rsp->data[0]; + + if (!roam_pref_validate (roam->roam_pref)) { + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_PARAMETER, + "Unknown roam preference 0x%X", + roam->roam_pref); + return NULL; + } + + result = qcdm_result_new (); + qcdm_result_add_uint8 (result, QCDM_CMD_NV_GET_ROAM_PREF_ITEM_PROFILE, roam->profile); + qcdm_result_add_uint8 (result, QCDM_CMD_NV_GET_ROAM_PREF_ITEM_ROAM_PREF, roam->roam_pref); + + return result; +} + +gsize +qcdm_cmd_nv_set_roam_pref_new (char *buf, + gsize len, + guint8 profile, + guint8 roam_pref, + GError **error) +{ + char cmdbuf[sizeof (DMCmdNVReadWrite) + 2]; + DMCmdNVReadWrite *cmd = (DMCmdNVReadWrite *) &cmdbuf[0]; + DMNVItemRoamPref *req; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + if (!roam_pref_validate (roam_pref)) { + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_PARAMETER, + "Invalid roam preference %d", roam_pref); + return 0; + } + + memset (cmd, 0, sizeof (*cmd)); + cmd->code = DIAG_CMD_NV_WRITE; + cmd->nv_item = GUINT16_TO_LE (DIAG_NV_ROAM_PREF); + + req = (DMNVItemRoamPref *) &cmd->data[0]; + req->profile = profile; + req->roam_pref = roam_pref; + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +QCDMResult * +qcdm_cmd_nv_set_roam_pref_result (const char *buf, gsize len, GError **error) +{ + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_NV_WRITE, sizeof (DMCmdNVReadWrite), error)) + return NULL; + + if (!check_nv_cmd ((DMCmdNVReadWrite *) buf, DIAG_NV_ROAM_PREF, error)) + return NULL; + + return qcdm_result_new (); +} + +/**********************************************************************/ + +static gboolean +mode_pref_validate (guint8 dm) +{ + if ( dm == DIAG_NV_MODE_PREF_1X_ONLY + || dm == DIAG_NV_MODE_PREF_HDR_ONLY + || dm == DIAG_NV_MODE_PREF_AUTO) + return TRUE; + return FALSE; +} + +gsize +qcdm_cmd_nv_get_mode_pref_new (char *buf, gsize len, guint8 profile, GError **error) +{ + char cmdbuf[sizeof (DMCmdNVReadWrite) + 2]; + DMCmdNVReadWrite *cmd = (DMCmdNVReadWrite *) &cmdbuf[0]; + DMNVItemModePref *req; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + memset (cmd, 0, sizeof (*cmd)); + cmd->code = DIAG_CMD_NV_READ; + cmd->nv_item = GUINT16_TO_LE (DIAG_NV_MODE_PREF); + + req = (DMNVItemModePref *) &cmd->data[0]; + req->profile = profile; + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +QCDMResult * +qcdm_cmd_nv_get_mode_pref_result (const char *buf, gsize len, GError **error) +{ + QCDMResult *result = NULL; + DMCmdNVReadWrite *rsp = (DMCmdNVReadWrite *) buf; + DMNVItemModePref *mode; + + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_NV_READ, sizeof (DMCmdNVReadWrite), error)) + return NULL; + + if (!check_nv_cmd (rsp, DIAG_NV_MODE_PREF, error)) + return NULL; + + mode = (DMNVItemModePref *) &rsp->data[0]; + + if (!mode_pref_validate (mode->mode_pref)) { + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_PARAMETER, + "Unknown mode preference 0x%X", + mode->mode_pref); + return NULL; + } + + result = qcdm_result_new (); + qcdm_result_add_uint8 (result, QCDM_CMD_NV_GET_MODE_PREF_ITEM_PROFILE, mode->profile); + qcdm_result_add_uint8 (result, QCDM_CMD_NV_GET_MODE_PREF_ITEM_MODE_PREF, mode->mode_pref); + + return result; +} + +gsize +qcdm_cmd_nv_set_mode_pref_new (char *buf, + gsize len, + guint8 profile, + guint8 mode_pref, + GError **error) +{ + char cmdbuf[sizeof (DMCmdNVReadWrite) + 2]; + DMCmdNVReadWrite *cmd = (DMCmdNVReadWrite *) &cmdbuf[0]; + DMNVItemModePref *req; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + if (!mode_pref_validate (mode_pref)) { + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_PARAMETER, + "Invalid mode preference %d", mode_pref); + return 0; + } + + memset (cmd, 0, sizeof (*cmd)); + cmd->code = DIAG_CMD_NV_WRITE; + cmd->nv_item = GUINT16_TO_LE (DIAG_NV_MODE_PREF); + + req = (DMNVItemModePref *) &cmd->data[0]; + req->profile = profile; + req->mode_pref = mode_pref; + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +QCDMResult * +qcdm_cmd_nv_set_mode_pref_result (const char *buf, gsize len, GError **error) +{ + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_NV_WRITE, sizeof (DMCmdNVReadWrite), error)) + return NULL; + + if (!check_nv_cmd ((DMCmdNVReadWrite *) buf, DIAG_NV_MODE_PREF, error)) + return NULL; + + return qcdm_result_new (); +} + +/**********************************************************************/ + +gsize +qcdm_cmd_cm_subsys_state_info_new (char *buf, gsize len, GError **error) +{ + char cmdbuf[sizeof (DMCmdSubsysHeader) + 2]; + DMCmdSubsysHeader *cmd = (DMCmdSubsysHeader *) &cmdbuf[0]; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + memset (cmd, 0, sizeof (*cmd)); + cmd->code = DIAG_CMD_SUBSYS; + cmd->subsys_id = DIAG_SUBSYS_CM; + cmd->subsys_cmd = GUINT16_TO_LE (DIAG_SUBSYS_CM_STATE_INFO); + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +QCDMResult * +qcdm_cmd_cm_subsys_state_info_result (const char *buf, gsize len, GError **error) +{ + QCDMResult *result = NULL; + DMCmdSubsysCMStateInfoRsp *rsp = (DMCmdSubsysCMStateInfoRsp *) buf; + guint32 tmp_num; + guint32 roam_pref; + + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_SUBSYS, sizeof (DMCmdSubsysCMStateInfoRsp), error)) + return NULL; + + roam_pref = (guint32) GUINT32_FROM_LE (rsp->roam_pref); + if (!roam_pref_validate (roam_pref)) { + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_PARAMETER, + "Unknown roam preference 0x%X", + roam_pref); + return NULL; + } + + result = qcdm_result_new (); + + tmp_num = (guint32) GUINT32_FROM_LE (rsp->call_state); + qcdm_result_add_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_CALL_STATE, tmp_num); + + tmp_num = (guint32) GUINT32_FROM_LE (rsp->oper_mode); + qcdm_result_add_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE, tmp_num); + + tmp_num = (guint32) GUINT32_FROM_LE (rsp->system_mode); + qcdm_result_add_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE, tmp_num); + + tmp_num = (guint32) GUINT32_FROM_LE (rsp->mode_pref); + qcdm_result_add_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_MODE_PREF, tmp_num); + + tmp_num = (guint32) GUINT32_FROM_LE (rsp->band_pref); + qcdm_result_add_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_BAND_PREF, tmp_num); + + qcdm_result_add_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_ROAM_PREF, roam_pref); + + tmp_num = (guint32) GUINT32_FROM_LE (rsp->srv_domain_pref); + qcdm_result_add_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SERVICE_DOMAIN_PREF, tmp_num); + + tmp_num = (guint32) GUINT32_FROM_LE (rsp->acq_order_pref); + qcdm_result_add_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_ACQ_ORDER_PREF, tmp_num); + + tmp_num = (guint32) GUINT32_FROM_LE (rsp->hybrid_pref); + qcdm_result_add_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_HYBRID_PREF, tmp_num); + + tmp_num = (guint32) GUINT32_FROM_LE (rsp->network_sel_mode_pref); + qcdm_result_add_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_NETWORK_SELECTION_PREF, tmp_num); + + return result; +} + +/**********************************************************************/ + +gsize +qcdm_cmd_hdr_subsys_state_info_new (char *buf, gsize len, GError **error) +{ + char cmdbuf[sizeof (DMCmdSubsysHeader) + 2]; + DMCmdSubsysHeader *cmd = (DMCmdSubsysHeader *) &cmdbuf[0]; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + memset (cmd, 0, sizeof (*cmd)); + cmd->code = DIAG_CMD_SUBSYS; + cmd->subsys_id = DIAG_SUBSYS_HDR; + cmd->subsys_cmd = GUINT16_TO_LE (DIAG_SUBSYS_HDR_STATE_INFO); + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +QCDMResult * +qcdm_cmd_hdr_subsys_state_info_result (const char *buf, gsize len, GError **error) +{ + QCDMResult *result = NULL; + DMCmdSubsysHDRStateInfoRsp *rsp = (DMCmdSubsysHDRStateInfoRsp *) buf; + + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_SUBSYS, sizeof (DMCmdSubsysHDRStateInfoRsp), error)) + return NULL; + + result = qcdm_result_new (); + + qcdm_result_add_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_AT_STATE, rsp->at_state); + qcdm_result_add_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_SESSION_STATE, rsp->session_state); + qcdm_result_add_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ALMP_STATE, rsp->almp_state); + qcdm_result_add_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_INIT_STATE, rsp->init_state); + qcdm_result_add_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_IDLE_STATE, rsp->idle_state); + qcdm_result_add_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_CONNECTED_STATE, rsp->connected_state); + qcdm_result_add_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ROUTE_UPDATE_STATE, rsp->route_update_state); + qcdm_result_add_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_OVERHEAD_MSG_STATE, rsp->overhead_msg_state); + qcdm_result_add_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_HDR_HYBRID_MODE, rsp->hdr_hybrid_mode); + + return result; +} + +/**********************************************************************/ + +gsize +qcdm_cmd_zte_subsys_status_new (char *buf, gsize len, GError **error) +{ + char cmdbuf[sizeof (DMCmdSubsysHeader) + 2]; + DMCmdSubsysHeader *cmd = (DMCmdSubsysHeader *) &cmdbuf[0]; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + memset (cmd, 0, sizeof (*cmd)); + cmd->code = DIAG_CMD_SUBSYS; + cmd->subsys_id = DIAG_SUBSYS_ZTE; + cmd->subsys_cmd = GUINT16_TO_LE (DIAG_SUBSYS_ZTE_STATUS); + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +QCDMResult * +qcdm_cmd_zte_subsys_status_result (const char *buf, gsize len, GError **error) +{ + QCDMResult *result = NULL; + DMCmdSubsysZteStatusRsp *rsp = (DMCmdSubsysZteStatusRsp *) buf; + + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_SUBSYS, sizeof (DMCmdSubsysZteStatusRsp), error)) + return NULL; + + result = qcdm_result_new (); + + qcdm_result_add_uint8 (result, QCDM_CMD_ZTE_SUBSYS_STATUS_ITEM_SIGNAL_INDICATOR, rsp->signal_ind); + + return result; +} + +/**********************************************************************/ + +gsize +qcdm_cmd_nw_subsys_modem_snapshot_cdma_new (char *buf, + gsize len, + guint8 chipset, + GError **error) +{ + char cmdbuf[sizeof (DMCmdSubsysNwSnapshotReq) + 2]; + DMCmdSubsysNwSnapshotReq *cmd = (DMCmdSubsysNwSnapshotReq *) &cmdbuf[0]; + + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (len >= sizeof (*cmd) + DIAG_TRAILER_LEN, 0); + + /* Validate chipset */ + if (chipset != QCDM_NW_CHIPSET_6500 && chipset != QCDM_NW_CHIPSET_6800) { + g_set_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_PARAMETER, + "Unknown Novatel chipset 0x%X", + chipset); + return 0; + } + + memset (cmd, 0, sizeof (*cmd)); + cmd->hdr.code = DIAG_CMD_SUBSYS; + switch (chipset) { + case QCDM_NW_CHIPSET_6500: + cmd->hdr.subsys_id = DIAG_SUBSYS_NW_CONTROL_6500; + break; + case QCDM_NW_CHIPSET_6800: + cmd->hdr.subsys_id = DIAG_SUBSYS_NW_CONTROL_6800; + break; + default: + g_assert_not_reached (); + } + cmd->hdr.subsys_cmd = GUINT16_TO_LE (DIAG_SUBSYS_NW_CONTROL_MODEM_SNAPSHOT); + cmd->technology = DIAG_SUBSYS_NW_CONTROL_MODEM_SNAPSHOT_TECH_CDMA_EVDO; + cmd->snapshot_mask = GUINT32_TO_LE (0xFFFF); + + return dm_encapsulate_buffer (cmdbuf, sizeof (*cmd), sizeof (cmdbuf), buf, len); +} + +QCDMResult * +qcdm_cmd_nw_subsys_modem_snapshot_cdma_result (const char *buf, gsize len, GError **error) +{ + QCDMResult *result = NULL; + DMCmdSubsysNwSnapshotRsp *rsp = (DMCmdSubsysNwSnapshotRsp *) buf; + DMCmdSubsysNwSnapshotCdma *cdma = (DMCmdSubsysNwSnapshotCdma *) &rsp->data; + guint32 num; + guint8 num8; + + g_return_val_if_fail (buf != NULL, NULL); + + if (!check_command (buf, len, DIAG_CMD_SUBSYS, sizeof (DMCmdSubsysNwSnapshotRsp), error)) + return NULL; + + /* FIXME: check response_code when we know what it means */ + + result = qcdm_result_new (); + + num = GUINT32_FROM_LE (cdma->rssi); + qcdm_result_add_uint32 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_RSSI, num); + + num8 = cdma_prev_to_qcdm (cdma->prev); + qcdm_result_add_uint8 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_PREV, num8); + + num8 = cdma_band_class_to_qcdm (cdma->band_class); + qcdm_result_add_uint8 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_BAND_CLASS, num8); + + qcdm_result_add_uint8 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_ERI, cdma->eri); + + num8 = QCDM_HDR_REV_UNKNOWN; + switch (cdma->hdr_rev) { + case 0: + num8 = QCDM_HDR_REV_0; + break; + case 1: + num8 = QCDM_HDR_REV_A; + break; + default: + break; + } + qcdm_result_add_uint8 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_HDR_REV, num8); + + return result; +} + +/**********************************************************************/ + diff --git a/libqcdm/src/commands.h b/libqcdm/src/commands.h new file mode 100644 index 0000000..75e83a7 --- /dev/null +++ b/libqcdm/src/commands.h @@ -0,0 +1,437 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBQCDM_COMMANDS_H +#define LIBQCDM_COMMANDS_H + +#include + +#include "result.h" + +/**********************************************************************/ + +/* Generic enums */ + +enum { + QCDM_CDMA_PREV_UNKNOWN = 0, + QCDM_CDMA_PREV_IS_95 = 1, /* and J_STD008 */ + QCDM_CDMA_PREV_IS_95A = 2, + QCDM_CDMA_PREV_IS_95A_TSB74 = 3, + QCDM_CDMA_PREV_IS_95B_PHASE1 = 4, + QCDM_CDMA_PREV_IS_95B_PHASE2 = 5, + QCDM_CDMA_PREV_IS2000_REL0 = 6, + QCDM_CDMA_PREV_IS2000_RELA = 7 +}; + +enum { + QCDM_CDMA_BAND_CLASS_UNKNOWN = 0, + QCDM_CDMA_BAND_CLASS_0_CELLULAR_800 = 1, /* US cellular 850MHz */ + QCDM_CDMA_BAND_CLASS_1_PCS = 2, /* US PCS 1900MHz */ + QCDM_CDMA_BAND_CLASS_2_TACS = 3, + QCDM_CDMA_BAND_CLASS_3_JTACS = 4, /* Japanese TACS */ + QCDM_CDMA_BAND_CLASS_4_KOREAN_PCS = 5, + QCDM_CDMA_BAND_CLASS_5_NMT450 = 6, + QCDM_CDMA_BAND_CLASS_6_IMT2000 = 7, /* 2100HMz */ + QCDM_CDMA_BAND_CLASS_7_CELLULAR_700 = 8, + QCDM_CDMA_BAND_CLASS_8_1800 = 9, + QCDM_CDMA_BAND_CLASS_9_900 = 10, + QCDM_CDMA_BAND_CLASS_10_SECONDARY_800 = 11, + QCDM_CDMA_BAND_CLASS_11_PAMR_400 = 12, + QCDM_CDMA_BAND_CLASS_12_PAMR_800 = 13 +}; + +enum { + QCDM_HDR_REV_UNKNOWN = 0x00, + QCDM_HDR_REV_0 = 0x01, + QCDM_HDR_REV_A = 0x02 +}; + +/**********************************************************************/ + +#define QCDM_CMD_VERSION_INFO_ITEM_COMP_DATE "comp-date" +#define QCDM_CMD_VERSION_INFO_ITEM_COMP_TIME "comp-time" +#define QCDM_CMD_VERSION_INFO_ITEM_RELEASE_DATE "release-date" +#define QCDM_CMD_VERSION_INFO_ITEM_RELEASE_TIME "release-time" +#define QCDM_CMD_VERSION_INFO_ITEM_MODEL "model" + +gsize qcdm_cmd_version_info_new (char *buf, + gsize len, + GError **error); + +QCDMResult *qcdm_cmd_version_info_result (const char *buf, + gsize len, + GError **error); + +/**********************************************************************/ + +#define QCDM_CMD_ESN_ITEM_ESN "esn" + +gsize qcdm_cmd_esn_new (char *buf, + gsize len, + GError **error); + +QCDMResult *qcdm_cmd_esn_result (const char *buf, + gsize len, + GError **error); + +/**********************************************************************/ + +/* Values for QCDM_CMD_CDMA_STATUS_ITEM_RF_MODE */ +enum { + QCDM_CMD_CDMA_STATUS_RF_MODE_ANALOG = 0, + QCDM_CMD_CDMA_STATUS_RF_MODE_CDMA_CELLULAR = 1, + QCDM_CMD_CDMA_STATUS_RF_MODE_CDMA_PCS = 2, + QCDM_CMD_CDMA_STATUS_RF_MODE_SLEEP = 3, + QCDM_CMD_CDMA_STATUS_RF_MODE_GPS = 4, + QCDM_CMD_CDMA_STATUS_RF_MODE_HDR = 5, +}; + +/* Values for QCDM_CMD_CDMA_STATUS_ITEM_RX_STATE */ +enum { + QCDM_CMD_CDMA_STATUS_RX_STATE_ENTERING_CDMA = 0, + QCDM_CMD_CDMA_STATUS_RX_STATE_SYNC_CHANNEL = 1, + QCDM_CMD_CDMA_STATUS_RX_STATE_PAGING_CHANNEL = 2, + QCDM_CMD_CDMA_STATUS_RX_STATE_TRAFFIC_CHANNEL_INIT = 3, + QCDM_CMD_CDMA_STATUS_RX_STATE_TRAFFIC_CHANNEL = 4, + QCDM_CMD_CDMA_STATUS_RX_STATE_EXITING_CDMA = 5, +}; + +#define QCDM_CMD_CDMA_STATUS_ITEM_ESN "esn" +#define QCDM_CMD_CDMA_STATUS_ITEM_RF_MODE "rf-mode" +#define QCDM_CMD_CDMA_STATUS_ITEM_RX_STATE "rx-state" +#define QCDM_CMD_CDMA_STATUS_ITEM_ENTRY_REASON "entry-reason" +#define QCDM_CMD_CDMA_STATUS_ITEM_CURRENT_CHANNEL "current-channel" +#define QCDM_CMD_CDMA_STATUS_ITEM_CODE_CHANNEL "code-channel" +#define QCDM_CMD_CDMA_STATUS_ITEM_PILOT_BASE "pilot-base" +#define QCDM_CMD_CDMA_STATUS_ITEM_SID "sid" +#define QCDM_CMD_CDMA_STATUS_ITEM_NID "nid" + +gsize qcdm_cmd_cdma_status_new (char *buf, + gsize len, + GError **error); + +QCDMResult *qcdm_cmd_cdma_status_result (const char *buf, + gsize len, + GError **error); + +/**********************************************************************/ + +/* NOTE: this command does not appear to be implemented in recent + * devices and probably returns (QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_COMMAND). + */ + +#define QCDM_CMD_SW_VERSION_ITEM_VERSION "version" +#define QCDM_CMD_SW_VERSION_ITEM_COMP_DATE "comp-date" +#define QCDM_CMD_SW_VERSION_ITEM_COMP_TIME "comp-time" + +gsize qcdm_cmd_sw_version_new (char *buf, + gsize len, + GError **error); + +QCDMResult *qcdm_cmd_sw_version_result (const char *buf, + gsize len, + GError **error); + +/**********************************************************************/ + +enum { + QCDM_CMD_PILOT_SETS_TYPE_UNKNOWN = 0, + QCDM_CMD_PILOT_SETS_TYPE_ACTIVE = 1, + QCDM_CMD_PILOT_SETS_TYPE_CANDIDATE = 2, + QCDM_CMD_PILOT_SETS_TYPE_NEIGHBOR = 3, +}; + +gsize qcdm_cmd_pilot_sets_new (char *buf, + gsize len, + GError **error); + +QCDMResult *qcdm_cmd_pilot_sets_result (const char *buf, + gsize len, + GError **error); + +gboolean qcdm_cmd_pilot_sets_result_get_num (QCDMResult *result, + guint32 set_type, + guint32 *out_num); + +gboolean qcdm_cmd_pilot_sets_result_get_pilot (QCDMResult *result, + guint32 set_type, + guint32 num, + guint32 *out_pn_offset, + guint32 *out_ecio, + float *out_db); + +/**********************************************************************/ + +#define QCDM_CMD_NV_GET_MDN_ITEM_PROFILE "profile" +#define QCDM_CMD_NV_GET_MDN_ITEM_MDN "mdn" + +gsize qcdm_cmd_nv_get_mdn_new (char *buf, + gsize len, + guint8 profile, + GError **error); + +QCDMResult *qcdm_cmd_nv_get_mdn_result (const char *buf, + gsize len, + GError **error); + +/**********************************************************************/ + +/* Values for QCDM_CMD_NV_GET_ROAM_PREF_ITEM_ROAM_PREF */ +enum { + QCDM_CMD_NV_ROAM_PREF_ITEM_ROAM_PREF_HOME_ONLY = 0x01, + QCDM_CMD_NV_ROAM_PREF_ITEM_ROAM_PREF_ROAM_ONLY = 0x06, + QCDM_CMD_NV_ROAM_PREF_ITEM_ROAM_PREF_AUTO = 0xFF, +}; + +#define QCDM_CMD_NV_GET_ROAM_PREF_ITEM_PROFILE "profile" +#define QCDM_CMD_NV_GET_ROAM_PREF_ITEM_ROAM_PREF "roam-pref" + +gsize qcdm_cmd_nv_get_roam_pref_new (char *buf, + gsize len, + guint8 profile, + GError **error); + +QCDMResult *qcdm_cmd_nv_get_roam_pref_result (const char *buf, + gsize len, + GError **error); + +gsize qcdm_cmd_nv_set_roam_pref_new (char *buf, + gsize len, + guint8 profile, + guint8 roam_pref, + GError **error); + +QCDMResult *qcdm_cmd_nv_set_roam_pref_result (const char *buf, + gsize len, + GError **error); + +/**********************************************************************/ + +/* Values for QCDM_CMD_NV_GET_MODE_PREF_ITEM_MODE_PREF */ +enum { + QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_AUTO = 0x04, + QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_1X_ONLY = 0x09, + QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_HDR_ONLY = 0x0A, +}; + +#define QCDM_CMD_NV_GET_MODE_PREF_ITEM_PROFILE "profile" +#define QCDM_CMD_NV_GET_MODE_PREF_ITEM_MODE_PREF "mode-pref" + +gsize qcdm_cmd_nv_get_mode_pref_new (char *buf, + gsize len, + guint8 profile, + GError **error); + +QCDMResult *qcdm_cmd_nv_get_mode_pref_result (const char *buf, + gsize len, + GError **error); + +gsize qcdm_cmd_nv_set_mode_pref_new (char *buf, + gsize len, + guint8 profile, + guint8 mode_pref, + GError **error); + +QCDMResult *qcdm_cmd_nv_set_mode_pref_result (const char *buf, + gsize len, + GError **error); + +/**********************************************************************/ + +/* Values for QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE */ +enum { + QCDM_CMD_CM_SUBSYS_STATE_INFO_OPERATING_MODE_ONLINE = 5 +}; + +/* Values for QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE */ +enum { + QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_NO_SERVICE = 0, + QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_AMPS = 1, + QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_CDMA = 2, + QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_GSM = 3, + QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_HDR = 4, + QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_WCDMA = 5, + QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_GPS = 6 +}; + +/* Values for QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_ROAM_PREF */ +enum { + QCDM_CMD_CM_SUBSYS_STATE_INFO_ROAM_PREF_HOME_ONLY = 0x01, + QCDM_CMD_CM_SUBSYS_STATE_INFO_ROAM_PREF_ROAM_ONLY = 0x06, + QCDM_CMD_CM_SUBSYS_STATE_INFO_ROAM_PREF_AUTO = 0xFF, +}; + +/* Values for QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_MODE_PREF */ +enum { + /* Note: not the same values as QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF has; + * AUTO really is 0x02 here, not 0x04 like the NV item value for AUTO. + */ + QCDM_CMD_CM_SUBSYS_STATE_INFO_MODE_PREF_DIGITAL_ONLY = 0x01, + QCDM_CMD_CM_SUBSYS_STATE_INFO_MODE_PREF_AUTO = 0x02, + QCDM_CMD_CM_SUBSYS_STATE_INFO_MODE_PREF_1X_ONLY = 0x09, + QCDM_CMD_CM_SUBSYS_STATE_INFO_MODE_PREF_HDR_ONLY = 0x0A, +}; + +#define QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_CALL_STATE "call-state" +#define QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE "operating-mode" +#define QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE "system-mode" +#define QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_MODE_PREF "mode-pref" +#define QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_BAND_PREF "band-pref" +#define QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_ROAM_PREF "roam-pref" +#define QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SERVICE_DOMAIN_PREF "service-domain-pref" +#define QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_ACQ_ORDER_PREF "acq-order-pref" +#define QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_HYBRID_PREF "hybrid-pref" +#define QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_NETWORK_SELECTION_PREF "network-selection-pref" + +gsize qcdm_cmd_cm_subsys_state_info_new (char *buf, + gsize len, + GError **error); + +QCDMResult *qcdm_cmd_cm_subsys_state_info_result (const char *buf, + gsize len, + GError **error); + +/**********************************************************************/ + +/* Values for QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_AT_STATE */ +enum { + QCDM_CMD_HDR_SUBSYS_STATE_INFO_AT_STATE_INACTIVE = 0, + QCDM_CMD_HDR_SUBSYS_STATE_INFO_AT_STATE_ACQUISITION = 1, + QCDM_CMD_HDR_SUBSYS_STATE_INFO_AT_STATE_SYNC = 2, + QCDM_CMD_HDR_SUBSYS_STATE_INFO_AT_STATE_IDLE = 3, + QCDM_CMD_HDR_SUBSYS_STATE_INFO_AT_STATE_ACCESS = 4, + QCDM_CMD_HDR_SUBSYS_STATE_INFO_AT_STATE_CONNECTED = 5 +}; + +/* Values for QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_SESSION_STATE */ +enum { + QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_CLOSED = 0, + QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_SETUP = 1, + QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_AT_INIT = 2, /* initiated by Access Terminal */ + QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_AN_INIT = 3, /* initiated by Access Node */ + QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_OPEN = 4, + QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_CLOSING = 5 +}; + +/* Values for QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ALMP_STATE (TIA-856-A section 9.2.1) */ +enum { + QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_INACTIVE = 0, /* initial state */ + QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_INIT = 1, /* terminal has yet to acquire network */ + QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_IDLE = 2, /* network acquired but no connection */ + QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_CONNECTED = 3, /* open connection to the network */ +}; + +/* Values for QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_INIT_STATE (TIA-856-A section 9.3.1) */ +enum { + QCDM_CMD_HDR_SUBSYS_STATE_INFO_INIT_STATE_INACTIVE = 0, /* protocol waiting for ACTIVATE command */ + QCDM_CMD_HDR_SUBSYS_STATE_INFO_INIT_STATE_NET_DETERMINE = 1, /* choosing a network to operate on */ + QCDM_CMD_HDR_SUBSYS_STATE_INFO_INIT_STATE_ACQUISITION = 2, /* acquiring Forward Pilot Channel */ + QCDM_CMD_HDR_SUBSYS_STATE_INFO_INIT_STATE_SYNC = 3, /* synchronizing to Control Channel */ +}; + +/* Values for QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_IDLE_STATE (TIA-856-A section 9.4.1) */ +enum { + QCDM_CMD_HDR_SUBSYS_STATE_INFO_IDLE_STATE_INACTIVE = 0, /* protocol waiting for ACTIVATE command */ + QCDM_CMD_HDR_SUBSYS_STATE_INFO_IDLE_STATE_SLEEP = 1, /* sleeping */ + QCDM_CMD_HDR_SUBSYS_STATE_INFO_IDLE_STATE_MONITOR = 2, /* monitoring the Control Channel */ + QCDM_CMD_HDR_SUBSYS_STATE_INFO_IDLE_STATE_SETUP = 3, /* setting up a connection */ +}; + +/* Values for QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_CONNECTED_STATE (TIA-856-A section 9.6.1) */ +enum { + QCDM_CMD_HDR_SUBSYS_STATE_INFO_CONNECTED_STATE_INACTIVE = 0, /* protocol waiting for ACTIVATE command */ + QCDM_CMD_HDR_SUBSYS_STATE_INFO_CONNECTED_STATE_OPEN = 1, /* connection is open */ + QCDM_CMD_HDR_SUBSYS_STATE_INFO_CONNECTED_STATE_CLOSING = 2, /* connection is closed */ +}; + +/* Values for QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ROUTE_UPDATE (TIA-856-A section 9.7.1) */ +enum { + QCDM_CMD_HDR_SUBSYS_STATE_INFO_ROUTE_UPDATE_STATE_INACTIVE = 0, + QCDM_CMD_HDR_SUBSYS_STATE_INFO_ROUTE_UPDATE_STATE_IDLE = 1, + QCDM_CMD_HDR_SUBSYS_STATE_INFO_ROUTE_UPDATE_STATE_CONNECTED = 2, +}; + +/* Values for QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_OVERHEAD_MSG (TIA-856-A section 9.9.1) */ +enum { + QCDM_CMD_HDR_SUBSYS_STATE_INFO_OVERHEAD_MSG_STATE_INIT = 0, + QCDM_CMD_HDR_SUBSYS_STATE_INFO_OVERHEAD_MSG_STATE_INACTIVE = 1, + QCDM_CMD_HDR_SUBSYS_STATE_INFO_OVERHEAD_MSG_STATE_ACTIVE = 2, +}; + +#define QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_AT_STATE "at-state" /* State of Access Terminal */ +#define QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_SESSION_STATE "session-state" /* Current session state */ +#define QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ALMP_STATE "almp-state" /* Air Link Management Protocol (ALMP) state */ +#define QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_INIT_STATE "init-state" /* Initialization State Protocol (ISP) state */ +#define QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_IDLE_STATE "idle-state" /* Idle State Protocol (IDP) state */ +#define QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_CONNECTED_STATE "connected-state" /* Connected State Protocol (CSP) state */ +#define QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ROUTE_UPDATE_STATE "route-update-state" /* Route Update Protocol (RUP) state */ +#define QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_OVERHEAD_MSG_STATE "overhead-msg-state" +#define QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_HDR_HYBRID_MODE "hdr-hybrid-mode" + +gsize qcdm_cmd_hdr_subsys_state_info_new (char *buf, + gsize len, + GError **error); + +QCDMResult *qcdm_cmd_hdr_subsys_state_info_result (const char *buf, + gsize len, + GError **error); + +/**********************************************************************/ + +#define QCDM_CMD_ZTE_SUBSYS_STATUS_ITEM_SIGNAL_INDICATOR "signal-indicator" + +gsize qcdm_cmd_zte_subsys_status_new (char *buf, + gsize len, + GError **error); + +QCDMResult *qcdm_cmd_zte_subsys_status_result (const char *buf, + gsize len, + GError **error); + +/**********************************************************************/ + +#define QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_RSSI "rssi" + +/* One of QCDM_CDMA_PREV_* */ +#define QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_PREV "prev" + +/* One of QCDM_CDMA_BAND_CLASS_* */ +#define QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_BAND_CLASS "band-class" + +#define QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_ERI "eri" + +/* One of QCDM_HDR_REV_* */ +#define QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_HDR_REV "hdr-rev" + +enum { + QCDM_NW_CHIPSET_UNKNOWN = 0, + QCDM_NW_CHIPSET_6500 = 1, + QCDM_NW_CHIPSET_6800 = 2, +}; + +gsize qcdm_cmd_nw_subsys_modem_snapshot_cdma_new (char *buf, + gsize len, + guint8 chipset, + GError **error); + +QCDMResult *qcdm_cmd_nw_subsys_modem_snapshot_cdma_result (const char *buf, + gsize len, + GError **error); + +/**********************************************************************/ + +#endif /* LIBQCDM_COMMANDS_H */ diff --git a/libqcdm/src/dm-commands.h b/libqcdm/src/dm-commands.h new file mode 100644 index 0000000..cfc98f7 --- /dev/null +++ b/libqcdm/src/dm-commands.h @@ -0,0 +1,370 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBQCDM_DM_COMMANDS_H +#define LIBQCDM_DM_COMMANDS_H + +enum { + DIAG_CMD_VERSION_INFO = 0, /* Version info */ + DIAG_CMD_ESN = 1, /* ESN */ + DIAG_CMD_PEEKB = 2, /* Peek byte */ + DIAG_CMD_PEEKW = 3, /* Peek word */ + DIAG_CMD_PEEKD = 4, /* Peek dword */ + DIAG_CMD_POKEB = 5, /* Poke byte */ + DIAG_CMD_POKEW = 6, /* Poke word */ + DIAG_CMD_POKED = 7, /* Poke dword */ + DIAG_CMD_OUTP = 8, /* Byte output */ + DIAG_CMD_OUTPW = 9, /* Word output */ + DIAG_CMD_INP = 10, /* Byte input */ + DIAG_CMD_INPW = 11, /* Word input */ + DIAG_CMD_STATUS = 12, /* Station status */ + DIAG_CMD_LOGMASK = 15, /* Set logging mask */ + DIAG_CMD_LOG = 16, /* Log packet */ + DIAG_CMD_NV_PEEK = 17, /* Peek NV memory */ + DIAG_CMD_NV_POKE = 18, /* Poke NV memory */ + DIAG_CMD_BAD_CMD = 19, /* Invalid command (response) */ + DIAG_CMD_BAD_PARM = 20, /* Invalid parameter (response) */ + DIAG_CMD_BAD_LEN = 21, /* Invalid packet length (response) */ + DIAG_CMD_BAD_DEV = 22, /* Not accepted by the device (response) */ + DIAG_CMD_BAD_MODE = 24, /* Not allowed in this mode (response) */ + DIAG_CMD_TAGRAPH = 25, /* Info for TA power and voice graphs */ + DIAG_CMD_MARKOV = 26, /* Markov stats */ + DIAG_CMD_MARKOV_RESET = 27, /* Reset Markov stats */ + DIAG_CMD_DIAG_VER = 28, /* Diagnostic Monitor version */ + DIAG_CMD_TIMESTAMP = 29, /* Return a timestamp */ + DIAG_CMD_TA_PARM = 30, /* Set TA parameters */ + DIAG_CMD_MESSAGE = 31, /* Request for msg report */ + DIAG_CMD_HS_KEY = 32, /* Handset emulation -- keypress */ + DIAG_CMD_HS_LOCK = 33, /* Handset emulation -- lock or unlock */ + DIAG_CMD_HS_SCREEN = 34, /* Handset emulation -- display request */ + DIAG_CMD_PARM_SET = 36, /* Parameter download */ + DIAG_CMD_NV_READ = 38, /* Read NV item */ + DIAG_CMD_NV_WRITE = 39, /* Write NV item */ + DIAG_CMD_CONTROL = 41, /* Mode change request */ + DIAG_CMD_ERR_READ = 42, /* Error record retreival */ + DIAG_CMD_ERR_CLEAR = 43, /* Error record clear */ + DIAG_CMD_SER_RESET = 44, /* Symbol error rate counter reset */ + DIAG_CMD_SER_REPORT = 45, /* Symbol error rate counter report */ + DIAG_CMD_TEST = 46, /* Run a specified test */ + DIAG_CMD_GET_DIPSW = 47, /* Retreive the current DIP switch setting */ + DIAG_CMD_SET_DIPSW = 48, /* Write new DIP switch setting */ + DIAG_CMD_VOC_PCM_LB = 49, /* Start/Stop Vocoder PCM loopback */ + DIAG_CMD_VOC_PKT_LB = 50, /* Start/Stop Vocoder PKT loopback */ + DIAG_CMD_ORIG = 53, /* Originate a call */ + DIAG_CMD_END = 54, /* End a call */ + DIAG_CMD_SW_VERSION = 56, /* Get software version */ + DIAG_CMD_DLOAD = 58, /* Switch to downloader */ + DIAG_CMD_TMOB = 59, /* Test Mode Commands and FTM commands*/ + DIAG_CMD_STATE = 63, /* Current state of the phone */ + DIAG_CMD_PILOT_SETS = 64, /* Return all current sets of pilots */ + DIAG_CMD_SPC = 65, /* Send the Service Programming Code to unlock */ + DIAG_CMD_BAD_SPC_MODE = 66, /* Invalid NV read/write because SP is locked */ + DIAG_CMD_PARM_GET2 = 67, /* (obsolete) */ + DIAG_CMD_SERIAL_CHG = 68, /* Serial mode change */ + DIAG_CMD_PASSWORD = 70, /* Send password to unlock secure operations */ + DIAG_CMD_BAD_SEC_MODE = 71, /* Operation not allowed in this security state */ + DIAG_CMD_PRL_WRITE = 72, /* Write PRL */ + DIAG_CMD_PRL_READ = 73, /* Read PRL */ + DIAG_CMD_SUBSYS = 75, /* Subsystem commands */ + DIAG_CMD_FEATURE_QUERY = 81, + DIAG_CMD_SMS_READ = 83, /* Read SMS message out of NV memory */ + DIAG_CMD_SMS_WRITE = 84, /* Write SMS message into NV memory */ + DIAG_CMD_SUP_FER = 85, /* Frame Error Rate info on multiple channels */ + DIAG_CMD_SUP_WALSH_CODES = 86, /* Supplemental channel walsh codes */ + DIAG_CMD_SET_MAX_SUP_CH = 87, /* Sets the maximum # supplemental channels */ + DIAG_CMD_PARM_GET_IS95B = 88, /* Get parameters including SUPP and MUX2 */ + DIAG_CMD_FS_OP = 89, /* Embedded File System (EFS) operations */ + DIAG_CMD_AKEY_VERIFY = 90, /* AKEY Verification */ + DIAG_CMD_HS_BMP_SCREEN = 91, /* Handset Emulation -- Bitmap screen */ + DIAG_CMD_CONFIG_COMM = 92, /* Configure communications */ + DIAG_CMD_EXT_LOGMASK = 93, /* Extended logmask for > 32 bits */ + DIAG_CMD_EVENT_REPORT = 96, /* Static Event reporting */ + DIAG_CMD_STREAMING_CONFIG = 97, /* Load balancing etc */ + DIAG_CMD_PARM_RETRIEVE = 98, /* Parameter retrieval */ + DIAG_CMD_STATUS_SNAPSHOT = 99, /* Status snapshot */ + DIAG_CMD_RPC = 100, /* Used for RPC */ + DIAG_CMD_GET_PROPERTY = 101, + DIAG_CMD_PUT_PROPERTY = 102, + DIAG_CMD_GET_GUID = 103, /* GUID requests */ + DIAG_CMD_USER_CMD = 104, /* User callbacks */ + DIAG_CMD_GET_PERM_PROPERTY = 105, + DIAG_CMD_PUT_PERM_PROPERTY = 106, + DIAG_CMD_PERM_USER_CMD = 107, /* Permanent user callbacks */ + DIAG_CMD_GPS_SESS_CTRL = 108, /* GPS session control */ + DIAG_CMD_GPS_GRID = 109, /* GPS search grid */ + DIAG_CMD_GPS_STATISTICS = 110, + DIAG_CMD_TUNNEL = 111, /* Tunneling command code */ + DIAG_CMD_RAM_RW = 112, /* Calibration RAM control using DM */ + DIAG_CMD_CPU_RW = 113, /* Calibration CPU control using DM */ + DIAG_CMD_SET_FTM_TEST_MODE = 114, /* Field (or Factory?) Test Mode */ +}; + +/* Subsystem IDs used with DIAG_CMD_SUBSYS; these often obsolete many of + * the original DM commands. + */ +enum { + DIAG_SUBSYS_HDR = 5, /* High Data Rate (ie, EVDO) */ + DIAG_SUBSYS_GPS = 13, + DIAG_SUBSYS_SMS = 14, + DIAG_SUBSYS_CM = 15, /* Call manager */ + DIAG_SUBSYS_NW_CONTROL_6500 = 50, /* for Novatel Wireless MSM6500-based devices */ + DIAG_SUBSYS_ZTE = 101, /* for ZTE EVDO devices */ + DIAG_SUBSYS_NW_CONTROL_6800 = 250 /* for Novatel Wireless MSM6800-based devices */ +}; + +/* HDR subsystem command codes */ +enum { + DIAG_SUBSYS_HDR_STATE_INFO = 8, /* Gets EVDO state */ +}; + +enum { + DIAG_SUBSYS_CM_STATE_INFO = 0, /* Gets Call Manager state */ +}; + +/* NW_CONTROL subsystem command codes (only for Novatel Wireless devices) */ +enum { + DIAG_SUBSYS_NW_CONTROL_AT_REQUEST = 3, /* AT commands via diag */ + DIAG_SUBSYS_NW_CONTROL_AT_RESPONSE = 4, + DIAG_SUBSYS_NW_CONTROL_MODEM_SNAPSHOT = 7, + DIAG_SUBSYS_NW_CONTROL_ERI = 8, /* Extended Roaming Indicator */ + DIAG_SUBSYS_NW_CONTROL_PRL = 12, +}; + +enum { + DIAG_SUBSYS_NW_CONTROL_MODEM_SNAPSHOT_TECH_CDMA_EVDO = 7, + DIAG_SUBSYS_NW_CONTROL_MODEM_SNAPSHOT_TECH_WCDMA = 20, +}; + +enum { + DIAG_SUBSYS_ZTE_STATUS = 0, +}; + +enum { + CDMA_PREV_UNKNOWN = 0, + CDMA_PREV_IS_95 = 1, /* and J_STD008 */ + CDMA_PREV_IS_95A = 2, + CDMA_PREV_IS_95A_TSB74 = 3, + CDMA_PREV_IS_95B_PHASE1 = 4, + CDMA_PREV_IS_95B_PHASE2 = 5, + CDMA_PREV_IS2000_REL0 = 6, + CDMA_PREV_IS2000_RELA = 7 +}; + +enum { + CDMA_BAND_CLASS_0_CELLULAR_800 = 0, /* US cellular 850MHz */ + CDMA_BAND_CLASS_1_PCS = 1, /* US PCS 1900MHz */ + CDMA_BAND_CLASS_2_TACS = 2, + CDMA_BAND_CLASS_3_JTACS = 3, /* Japanese TACS */ + CDMA_BAND_CLASS_4_KOREAN_PCS = 4, + CDMA_BAND_CLASS_5_NMT450 = 5, + CDMA_BAND_CLASS_6_IMT2000 = 6, /* 2100HMz */ + CDMA_BAND_CLASS_7_CELLULAR_700 = 7, + CDMA_BAND_CLASS_8_1800 = 8, + CDMA_BAND_CLASS_9_900 = 9, + CDMA_BAND_CLASS_10_SECONDARY_800 = 10, + CDMA_BAND_CLASS_11_PAMR_400 = 11, + CDMA_BAND_CLASS_12_PAMR_800 = 12 +}; + +/* Generic DM command header */ +struct DMCmdHeader { + guint8 code; +} __attribute__ ((packed)); +typedef struct DMCmdHeader DMCmdHeader; + +/* DIAG_CMD_SUBSYS */ +struct DMCmdSubsysHeader { + guint8 code; + guint8 subsys_id; + guint16 subsys_cmd; +} __attribute__ ((packed)); +typedef struct DMCmdSubsysHeader DMCmdSubsysHeader; + +/* DIAG_CMD_NV_READ / DIAG_CMD_NV_WRITE */ +struct DMCmdNVReadWrite { + guint8 code; + guint16 nv_item; + guint8 data[128]; + guint16 status; +} __attribute__ ((packed)); +typedef struct DMCmdNVReadWrite DMCmdNVReadWrite; + +/* DIAG_CMD_VERSION_INFO */ +struct DMCmdVersionInfoRsp { + guint8 code; + char comp_date[11]; + char comp_time[8]; + char rel_date[11]; + char rel_time[8]; + char model[8]; + guint8 scm; + guint8 mob_cai_rev; + guint8 mob_model; + guint16 mob_firmware_rev; + guint8 slot_cycle_index; + guint8 msm_ver; + guint8 _unknown; +} __attribute__ ((packed)); +typedef struct DMCmdVersionInfoRsp DMCmdVersionInfoRsp; + +/* DIAG_CMD_ESN */ +struct DMCmdEsnRsp { + guint8 code; + guint8 esn[4]; +} __attribute__ ((packed)); +typedef struct DMCmdEsnRsp DMCmdEsnRsp; + +/* DIAG_CMD_STATUS */ +struct DMCmdStatusRsp { + guint8 code; + guint8 _unknown[3]; + guint8 esn[4]; + guint16 rf_mode; + guint8 min1_analog[4]; + guint8 min1_cdma[4]; + guint8 min2_analog[2]; + guint8 min2_cdma[2]; + guint8 _unknown1; + guint16 cdma_rx_state; + guint8 good_frames; + guint16 analog_corrected_frames; + guint16 analog_bad_frames; + guint16 analog_word_syncs; + guint16 entry_reason; + guint16 curr_chan; + guint8 cdma_code_chan; + guint16 pilot_base; + guint16 sid; + guint16 nid; + guint16 analog_locaid; + guint16 analog_rssi; + guint8 analog_power; +} __attribute__ ((packed)); +typedef struct DMCmdStatusRsp DMCmdStatusRsp; + +/* DIAG_CMD_SW_VERSION */ +struct DMCmdSwVersionRsp { + guint8 code; + char version[20]; + char comp_date[11]; + char comp_time[8]; +} __attribute__ ((packed)); +typedef struct DMCmdSwVersionRsp DMCmdSwVersionRsp; + +/* DIAG_SUBSYS_CM_STATE_INFO subsys command */ +struct DMCmdSubsysCMStateInfoRsp { + DMCmdSubsysHeader header; + guint32 call_state; + guint32 oper_mode; + guint32 system_mode; + guint32 mode_pref; + guint32 band_pref; + guint32 roam_pref; + guint32 srv_domain_pref; + guint32 acq_order_pref; + guint32 hybrid_pref; + guint32 network_sel_mode_pref; +} __attribute__ ((packed)); +typedef struct DMCmdSubsysCMStateInfoRsp DMCmdSubsysCMStateInfoRsp; + +/* DIAG_SUBSYS_HDR_STATE_INFO subsys command */ +struct DMCmdSubsysHDRStateInfoRsp { + DMCmdSubsysHeader header; + guint8 at_state; + guint8 session_state; + guint8 almp_state; + guint8 init_state; + guint8 idle_state; + guint8 connected_state; + guint8 route_update_state; + guint8 overhead_msg_state; + guint8 hdr_hybrid_mode; +} __attribute__ ((packed)); +typedef struct DMCmdSubsysHDRStateInfoRsp DMCmdSubsysHDRStateInfoRsp; + + +/* DIAG_SUBSYS_ZTE_STATUS subsys command */ +struct DMCmdSubsysZteStatusRsp { + DMCmdSubsysHeader header; + guint8 _unknown1[8]; + guint8 signal_ind; + guint8 _unknown2; +} __attribute__ ((packed)); +typedef struct DMCmdSubsysZteStatusRsp DMCmdSubsysZteStatusRsp; + +/* DIAG_CMD_PILOT_SETS command */ +struct DMCmdPilotSetsSet { + guint16 pn_offset; + guint16 ecio; +} __attribute__ ((packed)); +typedef struct DMCmdPilotSetsSet DMCmdPilotSetsSet; + +struct DMCmdPilotSetsRsp { + guint8 code; + guint16 pilot_inc; + guint8 active_count; + guint8 candidate_count; + guint8 neighbor_count; + DMCmdPilotSetsSet sets[52]; +} __attribute__ ((packed)); +typedef struct DMCmdPilotSetsRsp DMCmdPilotSetsRsp; + +/* DIAG_SUBSYS_NW_CONTROL_* subsys command */ +struct DMCmdSubsysNwSnapshotReq { + DMCmdSubsysHeader hdr; + guint8 technology; /* DIAG_SUBSYS_NW_CONTROL_MODEM_SNAPSHOT_TECH_* */ + guint32 snapshot_mask; +} __attribute__ ((packed)); +typedef struct DMCmdSubsysNwSnapshotReq DMCmdSubsysNwSnapshotReq; + +/* DIAG_SUBSYS_NW_CONTROL_MODEM_SNAPSHOT response */ +struct DMCmdSubsysNwSnapshotRsp { + DMCmdSubsysHeader hdr; + guint8 response_code; + guint32 bitfield1; + guint32 bitfield2; + guint8 data[100]; +} __attribute__ ((packed)); +typedef struct DMCmdSubsysNwSnapshotRsp DMCmdSubsysNwSnapshotRsp; + +struct DMCmdSubsysNwSnapshotCdma { + guint32 rssi; + guint32 battery_level; + guint8 call_info; + guint8 new_sms_ind; + guint8 missed_calls; + guint32 voicemail_ind; + guint8 pkt_call_ctrl_state; + guint8 mip_rrp_err_code; + guint8 cur_packet_zone_id; + guint8 prev; + guint8 band_class; + guint8 eri; + guint8 eri_alert_id; + guint32 cur_call_total_time; + guint32 cur_call_active_time; + guint32 cur_call_tx_ip_bytes; + guint32 cur_call_rx_ip_bytes; + guint8 connection_status; + guint16 dominant_pn; + guint8 wdisable_mask; + guint8 hdr_rev; +} __attribute__ ((packed)); +typedef struct DMCmdSubsysNwSnapshotCdma DMCmdSubsysNwSnapshotCdma; + +#endif /* LIBQCDM_DM_COMMANDS_H */ + diff --git a/libqcdm/src/error.c b/libqcdm/src/error.c new file mode 100644 index 0000000..e3b97a0 --- /dev/null +++ b/libqcdm/src/error.c @@ -0,0 +1,87 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "error.h" + +#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } + +GQuark +qcdm_serial_error_quark (void) +{ + static GQuark ret = 0; + + if (ret == 0) + ret = g_quark_from_static_string ("qcdm-serial-error"); + + return ret; +} + +GType +qcdm_serial_error_get_type (void) +{ + static GType etype = 0; + + if (etype == 0) { + static const GEnumValue values[] = { + ENUM_ENTRY (QCDM_SERIAL_CONFIG_FAILED, "SerialConfigFailed"), + { 0, 0, 0 } + }; + + etype = g_enum_register_static ("QcdmSerialError", values); + } + + return etype; +} + +/***************************************************************/ + +GQuark +qcdm_command_error_quark (void) +{ + static GQuark ret = 0; + + if (ret == 0) + ret = g_quark_from_static_string ("qcdm-command-error"); + + return ret; +} + +GType +qcdm_command_error_get_type (void) +{ + static GType etype = 0; + + if (etype == 0) { + static const GEnumValue values[] = { + ENUM_ENTRY (QCDM_COMMAND_MALFORMED_RESPONSE, "QcdmCommandMalformedResponse"), + ENUM_ENTRY (QCDM_COMMAND_UNEXPECTED, "QcdmCommandUnexpected"), + ENUM_ENTRY (QCDM_COMMAND_BAD_LENGTH, "QcdmCommandBadLength"), + ENUM_ENTRY (QCDM_COMMAND_BAD_COMMAND, "QcdmCommandBadCommand"), + ENUM_ENTRY (QCDM_COMMAND_BAD_PARAMETER, "QcdmCommandBadParameter"), + ENUM_ENTRY (QCDM_COMMAND_NOT_ACCEPTED, "QcdmCommandNotAccepted"), + ENUM_ENTRY (QCDM_COMMAND_BAD_MODE, "QcdmCommandBadMode"), + ENUM_ENTRY (QCDM_COMMAND_NVCMD_FAILED, "QcdmCommandNvCmdFailed"), + { 0, 0, 0 } + }; + + etype = g_enum_register_static ("QcdmCommandError", values); + } + + return etype; +} + + diff --git a/libqcdm/src/error.h b/libqcdm/src/error.h new file mode 100644 index 0000000..7a02ae2 --- /dev/null +++ b/libqcdm/src/error.h @@ -0,0 +1,53 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBQCDM_ERROR_H +#define LIBQCDM_ERROR_H + +#include +#include + +enum { + QCDM_SERIAL_CONFIG_FAILED = 0, +}; + +#define QCDM_SERIAL_ERROR (qcdm_serial_error_quark ()) +#define QCDM_TYPE_SERIAL_ERROR (qcdm_serial_error_get_type ()) + +GQuark qcdm_serial_error_quark (void); +GType qcdm_serial_error_get_type (void); + + +enum { + QCDM_COMMAND_MALFORMED_RESPONSE = 0, + QCDM_COMMAND_UNEXPECTED = 1, + QCDM_COMMAND_BAD_LENGTH = 2, + QCDM_COMMAND_BAD_COMMAND = 3, + QCDM_COMMAND_BAD_PARAMETER = 4, + QCDM_COMMAND_NOT_ACCEPTED = 5, + QCDM_COMMAND_BAD_MODE = 6, + QCDM_COMMAND_NVCMD_FAILED = 7, +}; + +#define QCDM_COMMAND_ERROR (qcdm_command_error_quark ()) +#define QCDM_TYPE_COMMAND_ERROR (qcdm_command_error_get_type ()) + +GQuark qcdm_command_error_quark (void); +GType qcdm_command_error_get_type (void); + +#endif /* LIBQCDM_ERROR_H */ + diff --git a/libqcdm/src/libqcdm.ver b/libqcdm/src/libqcdm.ver new file mode 100644 index 0000000..b1567e2 --- /dev/null +++ b/libqcdm/src/libqcdm.ver @@ -0,0 +1,6 @@ +{ +global: + nm_vpn_connection_new; +local: + *; +}; diff --git a/libqcdm/src/nv-items.h b/libqcdm/src/nv-items.h new file mode 100644 index 0000000..a0ca10a --- /dev/null +++ b/libqcdm/src/nv-items.h @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBQCDM_NV_ITEMS_H +#define LIBQCDM_NV_ITEMS_H + +enum { + DIAG_NV_MODE_PREF = 10, /* Mode preference: 1x, HDR, auto */ + DIAG_NV_DIR_NUMBER = 178, /* Mobile Directory Number (MDN) */ + DIAG_NV_ROAM_PREF = 442, /* Roaming preference */ +}; + + +/* Mode preference values */ +enum { + DIAG_NV_MODE_PREF_AUTO = 0x04, + DIAG_NV_MODE_PREF_1X_ONLY = 0x09, + DIAG_NV_MODE_PREF_HDR_ONLY = 0x0A, +}; + +/* DIAG_NV_MODE_PREF */ +struct DMNVItemModePref { + guint8 profile; + guint8 mode_pref; +} __attribute__ ((packed)); +typedef struct DMNVItemModePref DMNVItemModePref; + +/* DIAG_NV_DIR_NUMBER */ +struct DMNVItemMdn { + guint8 profile; + guint8 mdn[10]; +} __attribute__ ((packed)); +typedef struct DMNVItemMdn DMNVItemMdn; + +/* Roam preference values */ +enum { + DIAG_NV_ROAM_PREF_HOME_ONLY = 0x01, + DIAG_NV_ROAM_PREF_ROAM_ONLY = 0x06, + DIAG_NV_ROAM_PREF_AUTO = 0xFF, +}; + +/* DIAG_NV_ROAM_PREF */ +struct DMNVItemRoamPref { + guint8 profile; + guint8 roam_pref; +} __attribute__ ((packed)); +typedef struct DMNVItemRoamPref DMNVItemRoamPref; + +#endif /* LIBQCDM_NV_ITEMS_H */ + diff --git a/libqcdm/src/result-private.h b/libqcdm/src/result-private.h new file mode 100644 index 0000000..0db63f5 --- /dev/null +++ b/libqcdm/src/result-private.h @@ -0,0 +1,51 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBQCDM_RESULT_PRIVATE_H +#define LIBQCDM_RESULT_PRIVATE_H + +#include +#include +#include "result.h" + +QCDMResult *qcdm_result_new (void); + +/* For these functions, 'key' *must* be a constant, not allocated and freed */ + +void qcdm_result_add_string (QCDMResult *result, + const char *key, + const char *str); + +void qcdm_result_add_uint8 (QCDMResult *result, + const char *key, + guint8 num); + +void qcdm_result_add_uint32 (QCDMResult *result, + const char *key, + guint32 num); + +void qcdm_result_add_boxed (QCDMResult *result, + const char *key, + GType btype, + gpointer boxed); + +gboolean qcdm_result_get_boxed (QCDMResult *result, + const char *key, + gpointer *out_val); + +#endif /* LIBQCDM_RESULT_PRIVATE_H */ + diff --git a/libqcdm/src/result.c b/libqcdm/src/result.c new file mode 100644 index 0000000..2440478 --- /dev/null +++ b/libqcdm/src/result.c @@ -0,0 +1,248 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "result.h" +#include "result-private.h" +#include "error.h" + +struct QCDMResult { + guint32 refcount; + GHashTable *hash; +}; + + +static void +gvalue_destroy (gpointer data) +{ + GValue *value = (GValue *) data; + + g_value_unset (value); + g_slice_free (GValue, value); +} + +QCDMResult * +qcdm_result_new (void) +{ + QCDMResult *result; + + g_type_init (); + + result = g_malloc0 (sizeof (QCDMResult)); + result->hash = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, gvalue_destroy); + result->refcount = 1; + return result; +} + +QCDMResult * +qcdm_result_ref (QCDMResult *result) +{ + g_return_val_if_fail (result != NULL, NULL); + g_return_val_if_fail (result->refcount > 0, NULL); + + result->refcount++; + return result; +} + +void +qcdm_result_unref (QCDMResult *result) +{ + g_return_if_fail (result != NULL); + g_return_if_fail (result->refcount > 0); + + result->refcount--; + if (result->refcount == 0) { + g_hash_table_destroy (result->hash); + memset (result, 0, sizeof (QCDMResult)); + g_free (result); + } +} + +void +qcdm_result_add_string (QCDMResult *result, + const char *key, + const char *str) +{ + GValue *val; + + g_return_if_fail (result != NULL); + g_return_if_fail (result->refcount > 0); + g_return_if_fail (key != NULL); + g_return_if_fail (str != NULL); + + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_STRING); + g_value_set_string (val, str); + + g_hash_table_insert (result->hash, (gpointer) key, val); +} + +gboolean +qcdm_result_get_string (QCDMResult *result, + const char *key, + const char **out_val) +{ + GValue *val; + + g_return_val_if_fail (result != NULL, FALSE); + g_return_val_if_fail (result->refcount > 0, FALSE); + g_return_val_if_fail (key != NULL, FALSE); + g_return_val_if_fail (out_val != NULL, FALSE); + g_return_val_if_fail (*out_val == NULL, FALSE); + + val = g_hash_table_lookup (result->hash, key); + if (!val) + return FALSE; + + g_warn_if_fail (G_VALUE_HOLDS_STRING (val)); + if (!G_VALUE_HOLDS_STRING (val)) + return FALSE; + + *out_val = g_value_get_string (val); + return TRUE; +} + +void +qcdm_result_add_uint8 (QCDMResult *result, + const char *key, + guint8 num) +{ + GValue *val; + + g_return_if_fail (result != NULL); + g_return_if_fail (result->refcount > 0); + g_return_if_fail (key != NULL); + + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_UCHAR); + g_value_set_uchar (val, (unsigned char) num); + + g_hash_table_insert (result->hash, (gpointer) key, val); +} + +gboolean +qcdm_result_get_uint8 (QCDMResult *result, + const char *key, + guint8 *out_val) +{ + GValue *val; + + g_return_val_if_fail (result != NULL, FALSE); + g_return_val_if_fail (result->refcount > 0, FALSE); + g_return_val_if_fail (key != NULL, FALSE); + g_return_val_if_fail (out_val != NULL, FALSE); + + val = g_hash_table_lookup (result->hash, key); + if (!val) + return FALSE; + + g_warn_if_fail (G_VALUE_HOLDS_UCHAR (val)); + if (!G_VALUE_HOLDS_UCHAR (val)) + return FALSE; + + *out_val = (guint8) g_value_get_uchar (val); + return TRUE; +} + +void +qcdm_result_add_uint32 (QCDMResult *result, + const char *key, + guint32 num) +{ + GValue *val; + + g_return_if_fail (result != NULL); + g_return_if_fail (result->refcount > 0); + g_return_if_fail (key != NULL); + + val = g_slice_new0 (GValue); + g_value_init (val, G_TYPE_UINT); + g_value_set_uint (val, num); + + g_hash_table_insert (result->hash, (gpointer) key, val); +} + +gboolean +qcdm_result_get_uint32 (QCDMResult *result, + const char *key, + guint32 *out_val) +{ + GValue *val; + + g_return_val_if_fail (result != NULL, FALSE); + g_return_val_if_fail (result->refcount > 0, FALSE); + g_return_val_if_fail (key != NULL, FALSE); + g_return_val_if_fail (out_val != NULL, FALSE); + + val = g_hash_table_lookup (result->hash, key); + if (!val) + return FALSE; + + g_warn_if_fail (G_VALUE_HOLDS_UINT (val)); + if (!G_VALUE_HOLDS_UINT (val)) + return FALSE; + + *out_val = (guint32) g_value_get_uint (val); + return TRUE; +} + +void +qcdm_result_add_boxed (QCDMResult *result, + const char *key, + GType btype, + gpointer boxed) +{ + GValue *val; + + g_return_if_fail (result != NULL); + g_return_if_fail (result->refcount > 0); + g_return_if_fail (key != NULL); + + val = g_slice_new0 (GValue); + g_value_init (val, btype); + g_value_set_static_boxed (val, boxed); + + g_hash_table_insert (result->hash, (gpointer) key, val); +} + +gboolean +qcdm_result_get_boxed (QCDMResult *result, + const char *key, + gpointer *out_val) +{ + GValue *val; + + g_return_val_if_fail (result != NULL, FALSE); + g_return_val_if_fail (result->refcount > 0, FALSE); + g_return_val_if_fail (key != NULL, FALSE); + g_return_val_if_fail (out_val != NULL, FALSE); + + val = g_hash_table_lookup (result->hash, key); + if (!val) + return FALSE; + + g_warn_if_fail (G_VALUE_HOLDS_BOXED (val)); + if (!G_VALUE_HOLDS_BOXED (val)) + return FALSE; + + *out_val = g_value_get_boxed (val); + return TRUE; +} + diff --git a/libqcdm/src/result.h b/libqcdm/src/result.h new file mode 100644 index 0000000..4912b07 --- /dev/null +++ b/libqcdm/src/result.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBQCDM_RESULT_H +#define LIBQCDM_RESULT_H + +#include + +typedef struct QCDMResult QCDMResult; + +gboolean qcdm_result_get_string (QCDMResult *result, + const char *key, + const char **out_val); + +gboolean qcdm_result_get_uint8 (QCDMResult *result, + const char *key, + guint8 *out_val); + +gboolean qcdm_result_get_uint32 (QCDMResult *result, + const char *key, + guint32 *out_val); + +QCDMResult *qcdm_result_ref (QCDMResult *result); + +void qcdm_result_unref (QCDMResult *result); + +#endif /* LIBQCDM_RESULT_H */ + diff --git a/libqcdm/src/utils.c b/libqcdm/src/utils.c new file mode 100644 index 0000000..b581548 --- /dev/null +++ b/libqcdm/src/utils.c @@ -0,0 +1,311 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +/* QCDM protocol frames are pseudo Async HDLC frames which end with a 3-byte + * trailer. This trailer consists of the 16-bit CRC of the frame plus an ending + * "async control character" whose value is 0x7E. The frame *and* the CRC are + * escaped before adding the trailing control character so that the control + * character (0x7E) and the escape marker (0x7D) are never seen in the frame. + */ + +/* Table of CRCs for each possible byte, with a generator polynomial of 0x8408 */ +const guint16 crc_table[256] = { + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, + 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, + 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, + 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, + 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, + 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, + 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, + 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, + 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, + 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, + 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, + 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, + 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, + 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, + 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 +}; + +/* Calculate the CRC for a buffer using a seed of 0xffff */ +guint16 +crc16 (const char *buffer, gsize len) +{ + guint16 crc = 0xffff; + + while (len--) + crc = crc_table[(crc ^ *buffer++) & 0xff] ^ (crc >> 8); + return ~crc; +} + +#define DIAG_ESC_CHAR 0x7D /* Escape sequence 1st character value */ +#define DIAG_ESC_MASK 0x20 /* Escape sequence complement value */ + +/* Performs DM escaping on inbuf putting the result into outbuf, and returns + * the final length of the buffer. + */ +gsize +dm_escape (const char *inbuf, + gsize inbuf_len, + char *outbuf, + gsize outbuf_len) +{ + const char *src = inbuf; + char *dst = outbuf; + size_t i = inbuf_len; + + g_return_val_if_fail (inbuf != NULL, 0); + g_return_val_if_fail (inbuf_len > 0, 0); + g_return_val_if_fail (outbuf != NULL, 0); + g_return_val_if_fail (outbuf_len > inbuf_len, 0); + + /* Since escaping potentially doubles the # of bytes, short-circuit the + * length check if destination buffer is clearly large enough. Note the + * + */ + if (outbuf_len <= inbuf_len << 1) { + size_t outbuf_required = inbuf_len + 1; /* +1 for the trailing control char */ + + /* Each escaped character takes up two bytes in the output buffer */ + while (i--) { + if (*src == DIAG_CONTROL_CHAR || *src == DIAG_ESC_CHAR) + outbuf_required++; + src++; + } + + if (outbuf_len < outbuf_required) + return 0; + } + + /* Do the actual escaping. Replace both the control character and + * the escape character in the source buffer with the following sequence: + * + * + */ + src = inbuf; + i = inbuf_len; + while (i--) { + if (*src == DIAG_CONTROL_CHAR || *src == DIAG_ESC_CHAR) { + *dst++ = DIAG_ESC_CHAR; + *dst++ = *src ^ DIAG_ESC_MASK; + } else + *dst++ = *src; + src++; + } + + return (dst - outbuf); +} + +gsize +dm_unescape (const char *inbuf, + gsize inbuf_len, + char *outbuf, + gsize outbuf_len, + gboolean *escaping) +{ + size_t i, outsize; + + g_return_val_if_fail (inbuf_len > 0, 0); + g_return_val_if_fail (outbuf_len >= inbuf_len, 0); + g_return_val_if_fail (escaping != NULL, 0); + + for (i = 0, outsize = 0; i < inbuf_len; i++) { + if (*escaping) { + outbuf[outsize++] = inbuf[i] ^ DIAG_ESC_MASK; + *escaping = FALSE; + } else if (inbuf[i] == DIAG_ESC_CHAR) + *escaping = TRUE; + else + outbuf[outsize++] = inbuf[i]; + + /* About to overrun output buffer size */ + if (outsize >= outbuf_len) + return 0; + } + + return outsize; +} + +/** + * dm_encapsulate_buffer: + * @inbuf: buffer in which a valid QCDM packet exists + * @cmd_len: size of the QCDM packet contained in @inbuf + * @inbuf_len: total size of @inbuf itself (not just the packet) + * @outbuf: buffer in which to put the encapsulated QCDM packet + * @outbuf_len: total size of @outbuf + * + * Escapes and CRCs a QCDM packet, and finally adds the trailing control + * character that denotes the end of the QCDM packet. + * + * Returns: size of the encapsulated QCDM command writted to @outbuf. + **/ +gsize +dm_encapsulate_buffer (char *inbuf, + gsize cmd_len, + gsize inbuf_len, + char *outbuf, + gsize outbuf_len) +{ + guint16 crc; + gsize escaped_len; + + g_return_val_if_fail (inbuf != NULL, 0); + g_return_val_if_fail (cmd_len >= 1, 0); + g_return_val_if_fail (inbuf_len >= cmd_len + 2, 0); /* space for CRC */ + g_return_val_if_fail (outbuf != NULL, 0); + + /* Add the CRC */ + crc = crc16 (inbuf, cmd_len); + inbuf[cmd_len++] = crc & 0xFF; + inbuf[cmd_len++] = (crc >> 8) & 0xFF; + + escaped_len = dm_escape (inbuf, cmd_len, outbuf, outbuf_len); + g_return_val_if_fail (outbuf_len > escaped_len, 0); + outbuf[escaped_len++] = DIAG_CONTROL_CHAR; + + return escaped_len; +} + +/** + * dm_decapsulate_buffer: + * @inbuf: buffer in which to look for a QCDM packet + * @inbuf_len: length of valid data in @inbuf + * @outbuf: buffer in which to put decapsulated QCDM packet + * @outbuf_len: max size of @outbuf + * @out_decap_len: on success, size of the decapsulated QCDM packet + * @out_used: on either success or failure, amount of data used; caller should + * discard this much data from @inbuf before the next call to this function + * @out_need_more: when TRUE, indicates that more data is required before + * and determination about a valid QCDM packet can be made; caller should add + * more data to @inbuf before calling this function again. + * + * Attempts to retrieve, unescape, and CRC-check a QCDM packet from the given + * buffer. + * + * Returns: FALSE on error (packet was invalid or malformed, or the CRC check + * failed, etc) and places number of bytes to discard from @inbuf in @out_used. + * When TRUE, either more data is required (in which case @out_need_more will + * be TRUE), or a QCDM packet was successfully retrieved from @inbuf and the + * decapsulated packet of length @out_decap_len was placed into @outbuf. In + * all cases the caller should advance the buffer by the number of bytes + * returned in @out_used before calling this function again. + **/ +gboolean +dm_decapsulate_buffer (const char *inbuf, + gsize inbuf_len, + char *outbuf, + gsize outbuf_len, + gsize *out_decap_len, + gsize *out_used, + gboolean *out_need_more) +{ + gboolean escaping = FALSE; + gsize i, pkt_len = 0, unesc_len; + guint16 crc, pkt_crc; + + g_return_val_if_fail (inbuf != NULL, FALSE); + g_return_val_if_fail (outbuf != NULL, FALSE); + g_return_val_if_fail (outbuf_len > 0, FALSE); + g_return_val_if_fail (out_decap_len != NULL, FALSE); + g_return_val_if_fail (out_used != NULL, FALSE); + g_return_val_if_fail (out_need_more != NULL, FALSE); + + *out_decap_len = 0; + *out_used = 0; + *out_need_more = FALSE; + + if (inbuf_len < 4) { + *out_need_more = TRUE; + return TRUE; + } + + /* Find the async control character */ + for (i = 0; i < inbuf_len; i++) { + if (inbuf[i] == DIAG_CONTROL_CHAR) { + /* If the control character shows up in a position before a valid + * QCDM packet length (4), the packet is malformed. + */ + if (i < 3) { + /* Tell the caller to advance the buffer past the control char */ + *out_used = i + 1; + return FALSE; + } + + pkt_len = i; + break; + } + } + + /* No control char yet, need more data */ + if (!pkt_len) { + *out_need_more = TRUE; + return TRUE; + } + + /* Unescape first; note that pkt_len */ + unesc_len = dm_unescape (inbuf, pkt_len, outbuf, outbuf_len, &escaping); + if (!unesc_len) { + /* Tell the caller to advance the buffer past the control char */ + *out_used = pkt_len + 1; + return FALSE; + } + + if (escaping) { + *out_need_more = TRUE; + return TRUE; + } + + /* Check the CRC of the packet's data */ + crc = crc16 (outbuf, unesc_len - 2); + pkt_crc = outbuf[unesc_len - 2] & 0xFF; + pkt_crc |= (outbuf[unesc_len - 1] & 0xFF) << 8; + if (crc != pkt_crc) { + *out_used = pkt_len + 1; /* packet + CRC + 0x7E */ + return FALSE; + } + + *out_used = pkt_len + 1; /* packet + CRC + 0x7E */ + *out_decap_len = unesc_len - 2; /* decap_len should not include the CRC */ + return TRUE; +} + diff --git a/libqcdm/src/utils.h b/libqcdm/src/utils.h new file mode 100644 index 0000000..5fccf7f --- /dev/null +++ b/libqcdm/src/utils.h @@ -0,0 +1,54 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UTILS_H +#define UTILS_H + +#include + +#define DIAG_CONTROL_CHAR 0x7E +#define DIAG_TRAILER_LEN 3 + +guint16 crc16 (const char *buffer, gsize len); + +gsize dm_escape (const char *inbuf, + gsize inbuf_len, + char *outbuf, + gsize outbuf_len); + +gsize dm_unescape (const char *inbuf, + gsize inbuf_len, + char *outbuf, + gsize outbuf_len, + gboolean *escaping); + +gsize dm_encapsulate_buffer (char *inbuf, + gsize cmd_len, + gsize inbuf_len, + char *outbuf, + gsize outbuf_len); + +gboolean dm_decapsulate_buffer (const char *inbuf, + gsize inbuf_len, + char *outbuf, + gsize outbuf_len, + gsize *out_decap_len, + gsize *out_used, + gboolean *out_need_more); + +#endif /* UTILS_H */ + diff --git a/libqcdm/tests/Makefile.am b/libqcdm/tests/Makefile.am new file mode 100644 index 0000000..eb38fdb --- /dev/null +++ b/libqcdm/tests/Makefile.am @@ -0,0 +1,32 @@ +INCLUDES = \ + -I$(top_srcdir)/libqcdm/src + +noinst_PROGRAMS = test-qcdm + +test_qcdm_SOURCES = \ + test-qcdm-crc.c \ + test-qcdm-crc.h \ + test-qcdm-escaping.c \ + test-qcdm-escaping.h \ + test-qcdm-utils.c \ + test-qcdm-utils.h \ + test-qcdm-com.c \ + test-qcdm-com.h \ + test-qcdm-result.c \ + test-qcdm-result.h \ + test-qcdm.c + +test_qcdm_CPPFLAGS = \ + $(MM_CFLAGS) + +test_qcdm_LDADD = \ + $(top_builddir)/libqcdm/src/libqcdm.la \ + $(MM_LIBS) + +if WITH_TESTS + +check-local: test-qcdm + $(abs_builddir)/test-qcdm + +endif + diff --git a/libqcdm/tests/test-qcdm-com.c b/libqcdm/tests/test-qcdm-com.c new file mode 100644 index 0000000..f41d249 --- /dev/null +++ b/libqcdm/tests/test-qcdm-com.c @@ -0,0 +1,1184 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test-qcdm-com.h" +#include "com.h" +#include "utils.h" +#include "result.h" +#include "commands.h" +#include "error.h" + +/************************************************************/ + +static const char * +prev_to_string (guint8 prev) +{ + switch (prev) { + case QCDM_CDMA_PREV_IS_95: + return "IS-95"; + case QCDM_CDMA_PREV_IS_95A: + return "IS-95A"; + case QCDM_CDMA_PREV_IS_95A_TSB74: + return "IS-95A TSB-74"; + case QCDM_CDMA_PREV_IS_95B_PHASE1: + return "IS-95B Phase I"; + case QCDM_CDMA_PREV_IS_95B_PHASE2: + return "IS-95B Phase II"; + case QCDM_CDMA_PREV_IS2000_REL0: + return "IS-2000 Release 0"; + case QCDM_CDMA_PREV_IS2000_RELA: + return "IS-2000 Release A"; + default: + break; + } + return "unknown"; +} + +static const char * +band_class_to_string (guint8 band_class) +{ + switch (band_class) { + case QCDM_CDMA_BAND_CLASS_0_CELLULAR_800: + return "0 (Cellular 800)"; + case QCDM_CDMA_BAND_CLASS_1_PCS: + return "1 (PCS 1900)"; + case QCDM_CDMA_BAND_CLASS_2_TACS: + return "2 (TACS)"; + case QCDM_CDMA_BAND_CLASS_3_JTACS: + return "3 (JTACS)"; + case QCDM_CDMA_BAND_CLASS_4_KOREAN_PCS: + return "4 (Korean PCS)"; + case QCDM_CDMA_BAND_CLASS_5_NMT450: + return "5 (NMT-450)"; + case QCDM_CDMA_BAND_CLASS_6_IMT2000: + return "6 (IMT-2000)"; + case QCDM_CDMA_BAND_CLASS_7_CELLULAR_700: + return "7 (Cellular 700)"; + case QCDM_CDMA_BAND_CLASS_8_1800: + return "8 (1800 MHz)"; + case QCDM_CDMA_BAND_CLASS_9_900: + return "9 (1900 MHz)"; + case QCDM_CDMA_BAND_CLASS_10_SECONDARY_800: + return "10 (Secondary 800 MHz)"; + case QCDM_CDMA_BAND_CLASS_11_PAMR_400: + return "11 (PAMR 400)"; + case QCDM_CDMA_BAND_CLASS_12_PAMR_800: + return "11 (PAMR 800)"; + default: + break; + } + return "unknown"; +} + +static const char * +hdr_rev_to_string (guint8 hdr_rev) +{ + switch (hdr_rev) { + case QCDM_HDR_REV_0: + return "0"; + case QCDM_HDR_REV_A: + return "A"; + default: + break; + } + return "unknown"; +} + +/************************************************************/ + +typedef struct { + char *port; + int fd; + struct termios old_t; + gboolean debug; +} TestComData; + +gpointer +test_com_setup (const char *port) +{ + TestComData *d; + int ret; + + d = g_malloc0 (sizeof (TestComData)); + g_assert (d); + + if (getenv ("SERIAL_DEBUG")) + d->debug = TRUE; + + errno = 0; + d->fd = open (port, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY); + if (d->fd < 0) + g_warning ("%s: open failed: (%d) %s", port, errno, strerror (errno)); + g_assert (d->fd >= 0); + + ret = ioctl (d->fd, TIOCEXCL); + if (ret) { + g_warning ("%s: lock failed: (%d) %s", port, errno, strerror (errno)); + close (d->fd); + d->fd = -1; + } + g_assert (ret == 0); + + ret = ioctl (d->fd, TCGETA, &d->old_t); + if (ret) { + g_warning ("%s: old termios failed: (%d) %s", port, errno, strerror (errno)); + close (d->fd); + d->fd = -1; + } + g_assert (ret == 0); + + d->port = g_strdup (port); + return d; +} + +void +test_com_teardown (gpointer user_data) +{ + TestComData *d = user_data; + + g_assert (d); + + g_free (d->port); + close (d->fd); + g_free (d); +} + +static void +print_buf (const char *detail, const char *buf, gsize len) +{ + int i = 0; + gboolean newline = FALSE; + + g_print ("%s (%zu) ", detail, len); + for (i = 0; i < len; i++) { + g_print ("0x%02x ", buf[i] & 0xFF); + if (((i + 1) % 12) == 0) { + g_print ("\n"); + newline = TRUE; + } else + newline = FALSE; + } + + if (!newline) + g_print ("\n"); +} + +static gboolean +send_command (TestComData *d, char *buf, gsize len) +{ + int status; + int eagain_count = 1000; + gsize i = 0; + + if (d->debug) + print_buf (">>>", buf, len); + + while (i < len) { + errno = 0; + status = write (d->fd, &buf[i], 1); + if (status < 0) { + if (errno == EAGAIN) { + eagain_count--; + if (eagain_count <= 0) + return FALSE; + } else + g_assert (errno == 0); + } else + i++; + + usleep (1000); + } + + return TRUE; +} + +static gsize +wait_reply (TestComData *d, char *buf, gsize len) +{ + fd_set in; + int result; + struct timeval timeout = { 1, 0 }; + char readbuf[1024]; + ssize_t bytes_read; + int total = 0, retries = 0; + gsize decap_len = 0; + + FD_ZERO (&in); + FD_SET (d->fd, &in); + result = select (d->fd + 1, &in, NULL, NULL, &timeout); + if (result != 1 || !FD_ISSET (d->fd, &in)) + return 0; + + do { + errno = 0; + bytes_read = read (d->fd, &readbuf[total], 1); + if ((bytes_read == 0) || (errno == EAGAIN)) { + /* Haven't gotten the async control char yet */ + if (retries > 20) + return 0; /* 2 seconds, give up */ + + /* Otherwise wait a bit and try again */ + usleep (100000); + retries++; + continue; + } else if (bytes_read == 1) { + gboolean more = FALSE, success; + gsize used = 0; + + total++; + decap_len = 0; + success = dm_decapsulate_buffer (readbuf, total, buf, len, &decap_len, &used, &more); + + /* Discard used data */ + if (used > 0) { + total -= used; + memmove (readbuf, &readbuf[used], total); + } + + if (success && !more) { + /* Success; we have a packet */ + break; + } + } else { + /* Some error occurred */ + return 0; + } + } while (total < sizeof (readbuf)); + + if (d->debug) { + print_buf ("<<<", readbuf, total); + print_buf ("D<<", buf, decap_len); + } + + return decap_len; +} + +void +test_com_port_init (void *f, void *data) +{ + TestComData *d = data; + GError *error = NULL; + gboolean success; + + success = qcdm_port_setup (d->fd, &error); + if (!success) { + g_warning ("%s: error setting up port: (%d) %s", + d->port, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + } + g_assert (success); +} + +void +test_com_version_info (void *f, void *data) +{ + TestComData *d = data; + gboolean success; + GError *error = NULL; + char buf[512]; + const char *str; + gint len; + QCDMResult *result; + gsize reply_len; + + len = qcdm_cmd_version_info_new (buf, sizeof (buf), NULL); + g_assert (len == 4); + + /* Send the command */ + success = send_command (d, buf, len); + g_assert (success); + + /* Get a response */ + reply_len = wait_reply (d, buf, sizeof (buf)); + + /* Parse the response into a result structure */ + result = qcdm_cmd_version_info_result (buf, reply_len, &error); + g_assert (result); + + g_print ("\n"); + + str = NULL; + qcdm_result_get_string (result, QCDM_CMD_VERSION_INFO_ITEM_COMP_DATE, &str); + g_message ("%s: Compiled Date: %s", __func__, str); + + str = NULL; + qcdm_result_get_string (result, QCDM_CMD_VERSION_INFO_ITEM_COMP_TIME, &str); + g_message ("%s: Compiled Time: %s", __func__, str); + + str = NULL; + qcdm_result_get_string (result, QCDM_CMD_VERSION_INFO_ITEM_RELEASE_DATE, &str); + g_message ("%s: Release Date: %s", __func__, str); + + str = NULL; + qcdm_result_get_string (result, QCDM_CMD_VERSION_INFO_ITEM_RELEASE_TIME, &str); + g_message ("%s: Release Time: %s", __func__, str); + + str = NULL; + qcdm_result_get_string (result, QCDM_CMD_VERSION_INFO_ITEM_MODEL, &str); + g_message ("%s: Model: %s", __func__, str); + + qcdm_result_unref (result); +} + +void +test_com_esn (void *f, void *data) +{ + TestComData *d = data; + gboolean success; + GError *error = NULL; + char buf[512]; + const char *str; + gint len; + QCDMResult *result; + gsize reply_len; + + len = qcdm_cmd_esn_new (buf, sizeof (buf), NULL); + g_assert (len == 4); + + /* Send the command */ + success = send_command (d, buf, len); + g_assert (success); + + /* Get a response */ + reply_len = wait_reply (d, buf, sizeof (buf)); + + /* Parse the response into a result structure */ + result = qcdm_cmd_esn_result (buf, reply_len, &error); + g_assert (result); + + g_print ("\n"); + + str = NULL; + qcdm_result_get_string (result, QCDM_CMD_ESN_ITEM_ESN, &str); + g_message ("%s: ESN: %s", __func__, str); + + qcdm_result_unref (result); +} + +void +test_com_mdn (void *f, void *data) +{ + TestComData *d = data; + gboolean success; + GError *error = NULL; + char buf[512]; + const char *str; + gint len; + QCDMResult *result; + gsize reply_len; + + len = qcdm_cmd_nv_get_mdn_new (buf, sizeof (buf), 0, NULL); + g_assert (len > 0); + + /* Send the command */ + success = send_command (d, buf, len); + g_assert (success); + + /* Get a response */ + reply_len = wait_reply (d, buf, sizeof (buf)); + + /* Parse the response into a result structure */ + result = qcdm_cmd_nv_get_mdn_result (buf, reply_len, &error); + if (!result) { + g_assert_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_NVCMD_FAILED); + return; + } + + g_print ("\n"); + + str = NULL; + qcdm_result_get_string (result, QCDM_CMD_NV_GET_MDN_ITEM_MDN, &str); + g_message ("%s: MDN: %s", __func__, str); + + qcdm_result_unref (result); +} + +void +test_com_read_roam_pref (void *f, void *data) +{ + TestComData *d = data; + gboolean success; + GError *error = NULL; + char buf[512]; + guint8 pref; + const char *msg; + gint len; + QCDMResult *result; + gsize reply_len; + + len = qcdm_cmd_nv_get_roam_pref_new (buf, sizeof (buf), 0, NULL); + g_assert (len > 0); + + /* Send the command */ + success = send_command (d, buf, len); + g_assert (success); + + /* Get a response */ + reply_len = wait_reply (d, buf, sizeof (buf)); + + /* Parse the response into a result structure */ + result = qcdm_cmd_nv_get_roam_pref_result (buf, reply_len, &error); + g_assert (result); + + g_print ("\n"); + + success = qcdm_result_get_uint8 (result, QCDM_CMD_NV_GET_ROAM_PREF_ITEM_ROAM_PREF, &pref); + g_assert (success); + + switch (pref) { + case QCDM_CMD_NV_ROAM_PREF_ITEM_ROAM_PREF_HOME_ONLY: + msg = "home only"; + break; + case QCDM_CMD_NV_ROAM_PREF_ITEM_ROAM_PREF_ROAM_ONLY: + msg = "roaming only"; + break; + case QCDM_CMD_NV_ROAM_PREF_ITEM_ROAM_PREF_AUTO: + msg = "automatic"; + break; + default: + g_assert_not_reached (); + } + g_message ("%s: Roam preference: 0x%02X (%s)", __func__, pref, msg); + + qcdm_result_unref (result); +} + +void +test_com_read_mode_pref (void *f, void *data) +{ + TestComData *d = data; + gboolean success; + GError *error = NULL; + char buf[512]; + guint8 pref; + const char *msg; + gint len; + QCDMResult *result; + gsize reply_len; + + len = qcdm_cmd_nv_get_mode_pref_new (buf, sizeof (buf), 0, NULL); + g_assert (len > 0); + + /* Send the command */ + success = send_command (d, buf, len); + g_assert (success); + + /* Get a response */ + reply_len = wait_reply (d, buf, sizeof (buf)); + + /* Parse the response into a result structure */ + result = qcdm_cmd_nv_get_mode_pref_result (buf, reply_len, &error); + if (!result) { + g_assert_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_NVCMD_FAILED); + return; + } + + g_print ("\n"); + + success = qcdm_result_get_uint8 (result, QCDM_CMD_NV_GET_MODE_PREF_ITEM_MODE_PREF, &pref); + g_assert (success); + + switch (pref) { + case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_1X_ONLY: + msg = "1X only"; + break; + case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_HDR_ONLY: + msg = "HDR only"; + break; + case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_AUTO: + msg = "automatic"; + break; + default: + msg = "unknown"; + break; + } + g_message ("%s: Mode preference: 0x%02X (%s)", __func__, pref, msg); + + qcdm_result_unref (result); +} + +void +test_com_status (void *f, void *data) +{ + TestComData *d = data; + gboolean success; + GError *error = NULL; + char buf[100]; + const char *str, *detail; + gint len; + QCDMResult *result; + gsize reply_len; + guint32 n32; + guint8 n8; + + len = qcdm_cmd_cdma_status_new (buf, sizeof (buf), NULL); + g_assert (len == 4); + + /* Send the command */ + success = send_command (d, buf, len); + g_assert (success); + + /* Get a response */ + reply_len = wait_reply (d, buf, sizeof (buf)); + + /* Parse the response into a result structure */ + result = qcdm_cmd_cdma_status_result (buf, reply_len, &error); + g_assert (result); + + g_print ("\n"); + + str = NULL; + qcdm_result_get_string (result, QCDM_CMD_CDMA_STATUS_ITEM_ESN, &str); + g_message ("%s: ESN: %s", __func__, str); + + n32 = 0; + detail = NULL; + qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_RF_MODE, &n32); + switch (n32) { + case QCDM_CMD_CDMA_STATUS_RF_MODE_ANALOG: + detail = "analog"; + break; + case QCDM_CMD_CDMA_STATUS_RF_MODE_CDMA_CELLULAR: + detail = "CDMA cellular"; + break; + case QCDM_CMD_CDMA_STATUS_RF_MODE_CDMA_PCS: + detail = "CDMA PCS"; + break; + case QCDM_CMD_CDMA_STATUS_RF_MODE_SLEEP: + detail = "sleep"; + break; + case QCDM_CMD_CDMA_STATUS_RF_MODE_GPS: + detail = "GPS"; + break; + case QCDM_CMD_CDMA_STATUS_RF_MODE_HDR: + detail = "HDR"; + break; + default: + detail = "unknown"; + break; + } + g_message ("%s: CDMA RF Mode: %u (%s)", __func__, n32, detail); + + n32 = 0; + detail = NULL; + qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_RX_STATE, &n32); + switch (n32) { + case QCDM_CMD_CDMA_STATUS_RX_STATE_ENTERING_CDMA: + detail = "entering CDMA"; + break; + case QCDM_CMD_CDMA_STATUS_RX_STATE_SYNC_CHANNEL: + detail = "sync channel"; + break; + case QCDM_CMD_CDMA_STATUS_RX_STATE_PAGING_CHANNEL: + detail = "paging channel"; + break; + case QCDM_CMD_CDMA_STATUS_RX_STATE_TRAFFIC_CHANNEL_INIT: + detail = "traffic channel init"; + break; + case QCDM_CMD_CDMA_STATUS_RX_STATE_TRAFFIC_CHANNEL: + detail = "traffic channel"; + break; + case QCDM_CMD_CDMA_STATUS_RX_STATE_EXITING_CDMA: + detail = "exiting CDMA"; + break; + default: + detail = "unknown"; + break; + } + g_message ("%s: CDMA RX State: %u (%s)", __func__, n32, detail); + + n32 = 0; + qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_ENTRY_REASON, &n32); + g_message ("%s: Entry Reason: %u", __func__, n32); + + n32 = 0; + qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_CURRENT_CHANNEL, &n32); + g_message ("%s: Current Channel: %u", __func__, n32); + + n8 = 0; + qcdm_result_get_uint8 (result, QCDM_CMD_CDMA_STATUS_ITEM_CODE_CHANNEL, &n8); + g_message ("%s: Code Channel: %u", __func__, n8); + + n32 = 0; + qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_PILOT_BASE, &n32); + g_message ("%s: Pilot Base: %u", __func__, n32); + + n32 = 0; + qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_SID, &n32); + g_message ("%s: CDMA System ID: %u", __func__, n32); + + n32 = 0; + qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_NID, &n32); + g_message ("%s: CDMA Network ID: %u", __func__, n32); + + qcdm_result_unref (result); +} + +void +test_com_sw_version (void *f, void *data) +{ + TestComData *d = data; + gboolean success; + GError *error = NULL; + char buf[100]; + gint len; + QCDMResult *result; + gsize reply_len; + + len = qcdm_cmd_sw_version_new (buf, sizeof (buf), NULL); + g_assert (len == 4); + + /* Send the command */ + success = send_command (d, buf, len); + g_assert (success); + + /* Get a response */ + reply_len = wait_reply (d, buf, sizeof (buf)); + + /* Parse the response into a result structure */ + result = qcdm_cmd_sw_version_result (buf, reply_len, &error); + + /* Recent devices don't appear to implement this command */ + g_assert (result == NULL); + g_assert_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_COMMAND); + +/* + str = NULL; + qcdm_result_get_string (result, QCDM_CMD_SW_VERSION_ITEM_VERSION, &str); + g_message ("%s: SW Version: %s", __func__, str); + + str = NULL; + qcdm_result_get_string (result, QCDM_CMD_SW_VERSION_ITEM_COMP_DATE, &str); + g_message ("%s: Compiled Date: %s", __func__, str); + + str = NULL; + qcdm_result_get_string (result, QCDM_CMD_SW_VERSION_ITEM_COMP_TIME, &str); + g_message ("%s: Compiled Time: %s", __func__, str); + + qcdm_result_unref (result); +*/ +} + +void +test_com_pilot_sets (void *f, void *data) +{ + TestComData *d = data; + gboolean success; + GError *error = NULL; + char buf[256]; + gint len; + QCDMResult *result; + gsize reply_len; + guint32 num, i; + + len = qcdm_cmd_pilot_sets_new (buf, sizeof (buf), NULL); + g_assert (len == 4); + + /* Send the command */ + success = send_command (d, buf, len); + g_assert (success); + + /* Get a response */ + reply_len = wait_reply (d, buf, sizeof (buf)); + + /* Parse the response into a result structure */ + result = qcdm_cmd_pilot_sets_result (buf, reply_len, &error); + g_assert (result); + + num = 0; + qcdm_cmd_pilot_sets_result_get_num (result, QCDM_CMD_PILOT_SETS_TYPE_ACTIVE, &num); + g_message ("%s: Active Pilots: %d", __func__, num); + for (i = 0; i < num; i++) { + guint32 pn_offset = 0, ecio = 0; + float db = 0; + + qcdm_cmd_pilot_sets_result_get_pilot (result, + QCDM_CMD_PILOT_SETS_TYPE_ACTIVE, + i, + &pn_offset, + &ecio, + &db); + g_message (" %d: PN offset %d", i, pn_offset); + g_message (" EC/IO %d (%.1f dB)", ecio, db); + } + + num = 0; + qcdm_cmd_pilot_sets_result_get_num (result, QCDM_CMD_PILOT_SETS_TYPE_CANDIDATE, &num); + g_message ("%s: Candidate Pilots: %d", __func__, num); + + num = 0; + qcdm_cmd_pilot_sets_result_get_num (result, QCDM_CMD_PILOT_SETS_TYPE_NEIGHBOR, &num); + g_message ("%s: Neighbor Pilots: %d", __func__, num); + + qcdm_result_unref (result); +} + +void +test_com_cm_subsys_state_info (void *f, void *data) +{ + TestComData *d = data; + gboolean success; + GError *error = NULL; + char buf[100]; + gint len; + QCDMResult *result; + gsize reply_len; + guint32 n32; + const char *detail; + + len = qcdm_cmd_cm_subsys_state_info_new (buf, sizeof (buf), NULL); + g_assert (len == 7); + + /* Send the command */ + success = send_command (d, buf, len); + g_assert (success); + + /* Get a response */ + reply_len = wait_reply (d, buf, sizeof (buf)); + + /* Parse the response into a result structure */ + result = qcdm_cmd_cm_subsys_state_info_result (buf, reply_len, &error); + g_assert (result); + + g_print ("\n"); + + n32 = 0; + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_CALL_STATE, &n32); + g_message ("%s: Call State: %u", __func__, n32); + + n32 = 0; + detail = NULL; + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE, &n32); + switch (n32) { + case QCDM_CMD_CM_SUBSYS_STATE_INFO_OPERATING_MODE_ONLINE: + detail = "online"; + break; + default: + detail = "unknown"; + break; + } + g_message ("%s: Operating Mode: %u (%s)", __func__, n32, detail); + + n32 = 0; + detail = NULL; + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE, &n32); + switch (n32) { + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_NO_SERVICE: + detail = "no service"; + break; + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_AMPS: + detail = "AMPS"; + break; + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_CDMA: + detail = "CDMA"; + break; + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_HDR: + detail = "HDR/EVDO"; + break; + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_WCDMA: + detail = "WCDMA"; + break; + default: + detail = "unknown"; + break; + } + g_message ("%s: System Mode: %u (%s)", __func__, n32, detail); + + n32 = 0; + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_MODE_PREF, &n32); + switch (n32) { + case QCDM_CMD_CM_SUBSYS_STATE_INFO_MODE_PREF_DIGITAL_ONLY: + detail = "digital only"; + break; + case QCDM_CMD_CM_SUBSYS_STATE_INFO_MODE_PREF_AUTO: + detail = "automatic"; + break; + case QCDM_CMD_CM_SUBSYS_STATE_INFO_MODE_PREF_1X_ONLY: + detail = "1X only"; + break; + case QCDM_CMD_CM_SUBSYS_STATE_INFO_MODE_PREF_HDR_ONLY: + detail = "HDR only"; + break; + default: + detail = "unknown"; + break; + } + g_message ("%s: Mode Preference: 0x%02X (%s)", __func__, n32 & 0xFF, detail); + + n32 = 0; + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_BAND_PREF, &n32); + g_message ("%s: Band Preference: %u", __func__, n32); + + n32 = 0; + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_ROAM_PREF, &n32); + switch (n32) { + case QCDM_CMD_CM_SUBSYS_STATE_INFO_ROAM_PREF_HOME_ONLY: + detail = "home only"; + break; + case QCDM_CMD_CM_SUBSYS_STATE_INFO_ROAM_PREF_ROAM_ONLY: + detail = "roaming only"; + break; + case QCDM_CMD_CM_SUBSYS_STATE_INFO_ROAM_PREF_AUTO: + detail = "automatic"; + break; + default: + g_assert_not_reached (); + } + g_message ("%s: Roam Preference: 0x%02X (%s)", __func__, n32 & 0xFF, detail); + + n32 = 0; + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SERVICE_DOMAIN_PREF, &n32); + g_message ("%s: Service Domain Preference: %u", __func__, n32); + + n32 = 0; + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_ACQ_ORDER_PREF, &n32); + g_message ("%s: Acquisition Order Preference: %u", __func__, n32); + + n32 = 0; + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_HYBRID_PREF, &n32); + g_message ("%s: Hybrid Preference: %u", __func__, n32); + + n32 = 0; + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_NETWORK_SELECTION_PREF, &n32); + g_message ("%s: Network Selection Preference: %u", __func__, n32); + + qcdm_result_unref (result); +} + +void +test_com_hdr_subsys_state_info (void *f, void *data) +{ + TestComData *d = data; + gboolean success; + GError *error = NULL; + char buf[100]; + gint len; + QCDMResult *result; + gsize reply_len; + guint8 num; + const char *detail; + + len = qcdm_cmd_hdr_subsys_state_info_new (buf, sizeof (buf), NULL); + g_assert (len == 7); + + /* Send the command */ + success = send_command (d, buf, len); + g_assert (success); + + /* Get a response */ + reply_len = wait_reply (d, buf, sizeof (buf)); + + g_print ("\n"); + + /* Parse the response into a result structure */ + result = qcdm_cmd_hdr_subsys_state_info_result (buf, reply_len, &error); + if (!result) { + /* 1x-only devices won't implement the HDR subsystem of course */ + g_assert_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_COMMAND); + g_message ("%s: device does not implement the HDR subsystem", __func__); + return; + } + g_assert (result); + + num = 0; + detail = NULL; + qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_AT_STATE, &num); + switch (num) { + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_AT_STATE_INACTIVE: + detail = "inactive"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_AT_STATE_ACQUISITION: + detail = "acquisition"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_AT_STATE_SYNC: + detail = "sync"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_AT_STATE_IDLE: + detail = "idle"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_AT_STATE_ACCESS: + detail = "access"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_AT_STATE_CONNECTED: + detail = "connected"; + break; + default: + detail = "unknown"; + break; + } + g_message ("%s: AT State: %u (%s)", __func__, num, detail); + + num = 0; + detail = NULL; + qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_SESSION_STATE, &num); + switch (num) { + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_CLOSED: + detail = "closed"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_SETUP: + detail = "setup"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_AT_INIT: + detail = "AT init"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_AN_INIT: + detail = "AN init"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_OPEN: + detail = "open"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_CLOSING: + detail = "closing"; + break; + default: + detail = "unknown"; + break; + } + g_message ("%s: Session State: %u (%s)", __func__, num, detail); + + num = 0; + detail = NULL; + qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ALMP_STATE, &num); + switch (num) { + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_INACTIVE: + detail = "inactive"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_INIT: + detail = "init"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_IDLE: + detail = "idle"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_CONNECTED: + detail = "connected"; + break; + default: + detail = "unknown"; + break; + } + g_message ("%s: ALMP State: %u (%s)", __func__, num, detail); + + num = 0; + detail = NULL; + qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_INIT_STATE, &num); + switch (num) { + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_INIT_STATE_INACTIVE: + detail = "inactive"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_INIT_STATE_NET_DETERMINE: + detail = "searching"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_INIT_STATE_ACQUISITION: + detail = "acquisition"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_INIT_STATE_SYNC: + detail = "sync"; + break; + default: + detail = "unknown"; + break; + } + g_message ("%s: Init State: %u (%s)", __func__, num, detail); + + num = 0; + detail = NULL; + qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_IDLE_STATE, &num); + switch (num) { + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_IDLE_STATE_INACTIVE: + detail = "inactive"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_IDLE_STATE_SLEEP: + detail = "sleep"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_IDLE_STATE_MONITOR: + detail = "monitor"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_IDLE_STATE_SETUP: + detail = "setup"; + break; + default: + detail = "unknown"; + break; + } + g_message ("%s: Idle State: %u (%s)", __func__, num, detail); + + num = 0; + detail = NULL; + qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_CONNECTED_STATE, &num); + switch (num) { + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_CONNECTED_STATE_INACTIVE: + detail = "inactive"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_CONNECTED_STATE_OPEN: + detail = "open"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_CONNECTED_STATE_CLOSING: + detail = "closing"; + break; + default: + detail = "unknown"; + break; + } + g_message ("%s: Connected State: %u (%s)", __func__, num, detail); + + num = 0; + detail = NULL; + qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ROUTE_UPDATE_STATE, &num); + switch (num) { + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_ROUTE_UPDATE_STATE_INACTIVE: + detail = "inactive"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_ROUTE_UPDATE_STATE_IDLE: + detail = "idle"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_ROUTE_UPDATE_STATE_CONNECTED: + detail = "connected"; + break; + default: + detail = "unknown"; + break; + } + g_message ("%s: Route Update State: %u (%s)", __func__, num, detail); + + num = 0; + detail = NULL; + qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_OVERHEAD_MSG_STATE, &num); + switch (num) { + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_OVERHEAD_MSG_STATE_INIT: + detail = "initial"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_OVERHEAD_MSG_STATE_INACTIVE: + detail = "inactive"; + break; + case QCDM_CMD_HDR_SUBSYS_STATE_INFO_OVERHEAD_MSG_STATE_ACTIVE: + detail = "active"; + break; + default: + detail = "unknown"; + break; + } + g_message ("%s: Overhead Msg State: %u (%s)", __func__, num, detail); + + num = 0; + qcdm_result_get_uint8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_HDR_HYBRID_MODE, &num); + g_message ("%s: HDR Hybrid Mode: %u", __func__, num); + + qcdm_result_unref (result); +} + +void +test_com_zte_subsys_status (void *f, void *data) +{ + TestComData *d = data; + gboolean success; + GError *error = NULL; + char buf[100]; + gint len; + QCDMResult *result; + gsize reply_len; + guint8 ind = 0; + + len = qcdm_cmd_zte_subsys_status_new (buf, sizeof (buf), NULL); + g_assert (len == 7); + + /* Send the command */ + success = send_command (d, buf, len); + g_assert (success); + + /* Get a response */ + reply_len = wait_reply (d, buf, sizeof (buf)); + + g_print ("\n"); + + /* Parse the response into a result structure */ + result = qcdm_cmd_zte_subsys_status_result (buf, reply_len, &error); + if (!result) { + /* Obviously not all devices implement this command */ + g_assert_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_COMMAND); + g_message ("%s: device does not implement the ZTE subsystem", __func__); + return; + } + g_assert (result); + + qcdm_result_get_uint8 (result, QCDM_CMD_ZTE_SUBSYS_STATUS_ITEM_SIGNAL_INDICATOR, &ind); + g_message ("%s: Signal Indicator: %d", __func__, ind); + + qcdm_result_unref (result); +} + +void +test_com_nw_subsys_modem_snapshot_cdma (void *f, void *data) +{ + TestComData *d = data; + gboolean success; + GError *error = NULL; + char buf[200]; + gint len; + QCDMResult *result; + gsize reply_len; + guint8 num8 = 0; + guint32 num32 = 0; + + len = qcdm_cmd_nw_subsys_modem_snapshot_cdma_new (buf, sizeof (buf), QCDM_NW_CHIPSET_6800, NULL); + g_assert (len == 12); + + /* Send the command */ + success = send_command (d, buf, len); + g_assert (success); + + /* Get a response */ + reply_len = wait_reply (d, buf, sizeof (buf)); + + g_print ("\n"); + + /* Parse the response into a result structure */ + result = qcdm_cmd_nw_subsys_modem_snapshot_cdma_result (buf, reply_len, &error); + if (!result) { + /* Obviously not all devices implement this command */ + g_assert_error (error, QCDM_COMMAND_ERROR, QCDM_COMMAND_BAD_COMMAND); + return; + } + g_assert (result); + + qcdm_result_get_uint32 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_RSSI, &num32); + g_message ("%s: RSSI: %d", __func__, num32); + + qcdm_result_get_uint8 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_PREV, &num8); + g_message ("%s: P_REV: %s", __func__, prev_to_string (num8)); + + qcdm_result_get_uint8 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_BAND_CLASS, &num8); + g_message ("%s: Band Class: %s", __func__, band_class_to_string (num8)); + + qcdm_result_get_uint8 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_ERI, &num8); + g_message ("%s: ERI: %d", __func__, num8); + + qcdm_result_get_uint8 (result, QCDM_CMD_NW_SUBSYS_MODEM_SNAPSHOT_CDMA_ITEM_HDR_REV, &num8); + g_message ("%s: HDR Revision: %s", __func__, hdr_rev_to_string (num8)); + + qcdm_result_unref (result); +} + diff --git a/libqcdm/tests/test-qcdm-com.h b/libqcdm/tests/test-qcdm-com.h new file mode 100644 index 0000000..3cf7c4f --- /dev/null +++ b/libqcdm/tests/test-qcdm-com.h @@ -0,0 +1,51 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TEST_QCDM_COM_H +#define TEST_QCDM_COM_H + +gpointer test_com_setup (const char *port); +void test_com_teardown (gpointer d); + +void test_com_port_init (void *f, void *data); + +void test_com_version_info (void *f, void *data); + +void test_com_esn (void *f, void *data); + +void test_com_mdn (void *f, void *data); + +void test_com_read_roam_pref (void *f, void *data); + +void test_com_read_mode_pref (void *f, void *data); + +void test_com_status (void *f, void *data); + +void test_com_sw_version (void *f, void *data); + +void test_com_pilot_sets (void *f, void *data); + +void test_com_cm_subsys_state_info (void *f, void *data); + +void test_com_hdr_subsys_state_info (void *f, void *data); + +void test_com_zte_subsys_status (void *f, void *data); + +void test_com_nw_subsys_modem_snapshot_cdma (void *f, void *data); + +#endif /* TEST_QCDM_COM_H */ + diff --git a/libqcdm/tests/test-qcdm-crc.c b/libqcdm/tests/test-qcdm-crc.c new file mode 100644 index 0000000..0cb3e86 --- /dev/null +++ b/libqcdm/tests/test-qcdm-crc.c @@ -0,0 +1,65 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "test-qcdm-crc.h" +#include "utils.h" + +void +test_crc16_2 (void *f, void *data) +{ + static const char buf[] = { + 0x26, 0xb2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00 + }; + guint16 crc; + guint16 expected = 0x6D69; + + /* CRC check */ + crc = crc16 (buf, sizeof (buf)); + g_assert (crc == expected); +} + +void +test_crc16_1 (void *f, void *data) +{ + static const char buf[] = { + 0x4b, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x3f, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff + }; + guint16 crc; + guint16 expected = 0x097A; + + /* CRC check */ + crc = crc16 (buf, sizeof (buf)); + g_assert (crc == expected); +} + diff --git a/libqcdm/tests/test-qcdm-crc.h b/libqcdm/tests/test-qcdm-crc.h new file mode 100644 index 0000000..91b95a0 --- /dev/null +++ b/libqcdm/tests/test-qcdm-crc.h @@ -0,0 +1,25 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TEST_QCDM_CRC_H +#define TEST_QCDM_CRC_H + +void test_crc16_2 (void *f, void *data); +void test_crc16_1 (void *f, void *data); + +#endif /* TEST_QCDM_CRC_H */ + diff --git a/libqcdm/tests/test-qcdm-escaping.c b/libqcdm/tests/test-qcdm-escaping.c new file mode 100644 index 0000000..fb5fb82 --- /dev/null +++ b/libqcdm/tests/test-qcdm-escaping.c @@ -0,0 +1,124 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "test-qcdm-escaping.h" +#include "utils.h" + +static const char data1[] = { + 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x0a, 0x6e, 0x6f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x74, 0x6f, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x0a, 0x70, 0x68, 0x6f, + 0x6e, 0x7e, 0x7e, 0x7e, 0x7d, 0x7d, 0x7e, 0x7d, 0x7e, 0x7d, 0x7e, 0x6e, + 0x6b, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x0a, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x6c, + 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x6e, 0x6f, 0x74, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, + 0x74, 0x65, 0x64, 0x0a, 0x70, 0x68, 0x73, 0x69, 0x6d, 0x70, 0x69, 0x6e, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x0a, 0x70, 0x68, 0x66, + 0x73, 0x69, 0x6d, 0x70, 0x69, 0x6e, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, + 0x65, 0x64, 0x0a, 0x70, 0x68, 0x66, 0x73, 0x69, 0x6d, 0x70, 0x75, 0x6b, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x0a, 0x73, 0x69, 0x6d, + 0x6e, 0x6f, 0x74, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x0a, + 0x73, 0x69, 0x6d, 0x70, 0x69, 0x6e, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, + 0x65, 0x64, 0x0a, 0x73, 0x69, 0x6d, 0x70, 0x75, 0x6b, 0x72, 0x65, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x64, 0x0a, 0x73, 0x69, 0x6d, 0x66, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x0a, 0x73, 0x69, 0x6d, 0x62, 0x75, 0x73, 0x79, + 0x0a, 0x73, 0x69, 0x6d, 0x77, 0x72, 0x6f, 0x6e, 0x67, 0x0a, 0x69, 0x6e, + 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x70, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x0a, 0x73, 0x69, 0x6d, 0x70, 0x69, 0x6e, 0x32, 0x72, + 0x65, 0x71, 0x75, 0x69 +}; + +static const char expected1[] = { + 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x0a, 0x6e, 0x6f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x74, 0x6f, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x0a, 0x70, 0x68, 0x6f, + 0x6e, 0x7d, 0x5e, 0x7d, 0x5e, 0x7d, 0x5e, 0x7d, 0x5d, 0x7d, 0x5d, 0x7d, + 0x5e, 0x7d, 0x5d, 0x7d, 0x5e, 0x7d, 0x5d, 0x7d, 0x5e, 0x6e, 0x6b, 0x72, + 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x0a, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x6c, 0x6c, 0x6f, + 0x77, 0x65, 0x64, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x6e, 0x6f, 0x74, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, + 0x64, 0x0a, 0x70, 0x68, 0x73, 0x69, 0x6d, 0x70, 0x69, 0x6e, 0x72, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x0a, 0x70, 0x68, 0x66, 0x73, 0x69, + 0x6d, 0x70, 0x69, 0x6e, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, + 0x0a, 0x70, 0x68, 0x66, 0x73, 0x69, 0x6d, 0x70, 0x75, 0x6b, 0x72, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x0a, 0x73, 0x69, 0x6d, 0x6e, 0x6f, + 0x74, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x0a, 0x73, 0x69, + 0x6d, 0x70, 0x69, 0x6e, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, + 0x0a, 0x73, 0x69, 0x6d, 0x70, 0x75, 0x6b, 0x72, 0x65, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x64, 0x0a, 0x73, 0x69, 0x6d, 0x66, 0x61, 0x69, 0x6c, 0x75, + 0x72, 0x65, 0x0a, 0x73, 0x69, 0x6d, 0x62, 0x75, 0x73, 0x79, 0x0a, 0x73, + 0x69, 0x6d, 0x77, 0x72, 0x6f, 0x6e, 0x67, 0x0a, 0x69, 0x6e, 0x63, 0x6f, + 0x72, 0x72, 0x65, 0x63, 0x74, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, + 0x64, 0x0a, 0x73, 0x69, 0x6d, 0x70, 0x69, 0x6e, 0x32, 0x72, 0x65, 0x71, + 0x75, 0x69 +}; + +void +test_escape1 (void *f, void *data) +{ + char escaped[1024]; + gsize len; + + /* Ensure that escaping in general works */ + len = dm_escape (data1, sizeof (data1), escaped, sizeof (escaped)); + g_assert (len == 266); + g_assert (len == sizeof (expected1)); + g_assert (memcmp (escaped, expected1, len) == 0); +} + +static const char data2[] = { + 0x4b, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x3f, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff +}; + +void +test_escape2 (void *f, void *data) +{ + char escaped[1024]; + gsize len; + + /* Ensure that escaping data that doesn't need escaping works */ + len = dm_escape (data2, sizeof (data2), escaped, sizeof (escaped)); + g_assert (len == sizeof (data2)); + g_assert (memcmp (escaped, data2, len) == 0); +} + +void +test_escape_unescape (void *f, void *data) +{ + char escaped[512]; + char unescaped[512]; + gsize len, unlen; + gboolean escaping = FALSE; + + /* Ensure that escaping data that needs escaping, and then unescaping it, + * produces the exact same data as was originally escaped. + */ + len = dm_escape (data1, sizeof (data1), escaped, sizeof (escaped)); + unlen = dm_unescape (escaped, len, unescaped, sizeof (unescaped), &escaping); + + g_assert (unlen == sizeof (data1)); + g_assert (memcmp (unescaped, data1, unlen) == 0); +} + diff --git a/libqcdm/tests/test-qcdm-escaping.h b/libqcdm/tests/test-qcdm-escaping.h new file mode 100644 index 0000000..91706ee --- /dev/null +++ b/libqcdm/tests/test-qcdm-escaping.h @@ -0,0 +1,26 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TEST_QCDM_ESCAPING_H +#define TEST_QCDM_ESCAPING_H + +void test_escape1 (void *f, void *data); +void test_escape2 (void *f, void *data); +void test_escape_unescape (void *f, void *data); + +#endif /* TEST_QCDM_ESCAPING_H */ + diff --git a/libqcdm/tests/test-qcdm-result.c b/libqcdm/tests/test-qcdm-result.c new file mode 100644 index 0000000..87f264b --- /dev/null +++ b/libqcdm/tests/test-qcdm-result.c @@ -0,0 +1,71 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "test-qcdm-result.h" +#include "result.h" +#include "result-private.h" + +#define TEST_TAG "test" + +void +test_result_string (void *f, void *data) +{ + const char *str = "foobarblahblahblah"; + const char *tmp = NULL; + QCDMResult *result; + + result = qcdm_result_new (); + qcdm_result_add_string (result, TEST_TAG, str); + + qcdm_result_get_string (result, TEST_TAG, &tmp); + g_assert (tmp); + g_assert (strcmp (tmp, str) == 0); + + qcdm_result_unref (result); +} + +void +test_result_uint32 (void *f, void *data) +{ + guint32 num = 0xDEADBEEF; + guint32 tmp = 0; + QCDMResult *result; + + result = qcdm_result_new (); + qcdm_result_add_uint32 (result, TEST_TAG, num); + + qcdm_result_get_uint32 (result, TEST_TAG, &tmp); + g_assert (tmp == num); +} + +void +test_result_uint8 (void *f, void *data) +{ + guint8 num = 0x1E; + guint8 tmp = 0; + QCDMResult *result; + + result = qcdm_result_new (); + qcdm_result_add_uint8 (result, TEST_TAG, num); + + qcdm_result_get_uint8 (result, TEST_TAG, &tmp); + g_assert (tmp == num); +} + diff --git a/libqcdm/tests/test-qcdm-result.h b/libqcdm/tests/test-qcdm-result.h new file mode 100644 index 0000000..20d6cec --- /dev/null +++ b/libqcdm/tests/test-qcdm-result.h @@ -0,0 +1,26 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TEST_QCDM_RESULT_H +#define TEST_QCDM_RESULT_H + +void test_result_string (void *f, void *data); +void test_result_uint32 (void *f, void *data); +void test_result_uint8 (void *f, void *data); + +#endif /* TEST_QCDM_RESULT_H */ + diff --git a/libqcdm/tests/test-qcdm-utils.c b/libqcdm/tests/test-qcdm-utils.c new file mode 100644 index 0000000..27ec8d6 --- /dev/null +++ b/libqcdm/tests/test-qcdm-utils.c @@ -0,0 +1,86 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "test-qcdm-utils.h" +#include "utils.h" + +static const char decap_inbuf[] = { + 0x40, 0x03, 0x00, 0x01, 0x00, 0x19, 0xf0, 0x00, 0x16, 0x00, 0x21, 0x00, + 0x1c, 0x00, 0xd8, 0x00, 0x3f, 0x00, 0x56, 0x01, 0x3f, 0x00, 0x15, 0x00, + 0x1a, 0x00, 0x11, 0x01, 0x3f, 0x00, 0x92, 0x01, 0x3f, 0x00, 0x39, 0x00, + 0x3f, 0x00, 0x95, 0x01, 0x3f, 0x00, 0x12, 0x00, 0x3f, 0x00, 0x23, 0x01, + 0x3f, 0x00, 0x66, 0x00, 0x3f, 0x00, 0x0b, 0x01, 0x3f, 0x00, 0xae, 0x00, + 0x3f, 0x00, 0x02, 0x01, 0x3f, 0x00, 0xa8, 0x00, 0x3f, 0x00, 0x50, 0x01, + 0x3f, 0x00, 0xf8, 0x01, 0x3f, 0x00, 0x57, 0x00, 0x3f, 0x00, 0x7d, 0x5e, + 0x00, 0x3f, 0x00, 0x93, 0x00, 0x3f, 0x00, 0xbd, 0x00, 0x3f, 0x00, 0x77, + 0x01, 0x3f, 0x00, 0xb7, 0x00, 0x3f, 0x00, 0xab, 0x00, 0x3f, 0x00, 0x33, + 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, + 0x13, 0x50, 0x1f, 0x00, 0x00, 0xff, 0xff, 0x00, 0xaa, 0x19, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0xc4, 0x7d, 0x5e, + 0x7d, 0x5e, 0x7d, 0x5d, 0x5d, 0x04, 0x58, 0x1b, 0x5b, 0x1b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x65, 0x69, 0x7e +}; + +void +test_utils_decapsulate_buffer (void *f, void *data) +{ + gboolean success; + char outbuf[512]; + gsize decap_len = 0; + gsize used = 0; + gboolean more = FALSE; + + success = dm_decapsulate_buffer (decap_inbuf, sizeof (decap_inbuf), + outbuf, sizeof (outbuf), + &decap_len, &used, &more); + g_assert (success); + g_assert (decap_len == 214); + g_assert (used == 221); + g_assert (more == FALSE); +} + + +static const char encap_outbuf[] = { + 0x4b, 0x05, 0x08, 0x00, 0x01, 0xdd, 0x7e +}; + +void +test_utils_encapsulate_buffer (void *f, void *data) +{ + char cmdbuf[10]; + char outbuf[512]; + gsize encap_len = 0; + + cmdbuf[0] = 0x4B; /* DIAG_CMD_SUBSYS */ + cmdbuf[1] = 0x05; /* DIAG_SUBSYS_HDR */ + cmdbuf[2] = 0x08; /* first byte of DIAG_SUBSYS_HDR_STATE_INFO in LE */ + cmdbuf[3] = 0x00; /* second byte of DIAG_SUBSYS_HDR_STATE_INFO in LE */ + + encap_len = dm_encapsulate_buffer (cmdbuf, 4, sizeof (cmdbuf), + &outbuf[0], sizeof (outbuf)); + g_assert (encap_len == sizeof (encap_outbuf)); + g_assert (memcmp (outbuf, encap_outbuf, encap_len) == 0); +} + diff --git a/libqcdm/tests/test-qcdm-utils.h b/libqcdm/tests/test-qcdm-utils.h new file mode 100644 index 0000000..8038666 --- /dev/null +++ b/libqcdm/tests/test-qcdm-utils.h @@ -0,0 +1,26 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TEST_QCDM_UTILS_H +#define TEST_QCDM_UTILS_H + +void test_utils_decapsulate_buffer (void *f, void *data); + +void test_utils_encapsulate_buffer (void *f, void *data); + +#endif /* TEST_QCDM_UTILS_H */ + diff --git a/libqcdm/tests/test-qcdm.c b/libqcdm/tests/test-qcdm.c new file mode 100644 index 0000000..a58780e --- /dev/null +++ b/libqcdm/tests/test-qcdm.c @@ -0,0 +1,115 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "test-qcdm-crc.h" +#include "test-qcdm-escaping.h" +#include "test-qcdm-com.h" +#include "test-qcdm-result.h" +#include "test-qcdm-utils.h" + +typedef struct { + gpointer com_data; +} TestData; + +typedef void (*TCFunc)(void); + +#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (TCFunc) t, NULL) + +static TestData * +test_data_new (const char *port) +{ + TestData *d; + + d = g_malloc0 (sizeof (TestData)); + g_assert (d); + + if (port) + d->com_data = test_com_setup (port); + + return d; +} + +static void +test_data_free (TestData *d) +{ + if (d->com_data) + test_com_teardown (d->com_data); + + g_free (d); +} + +int main (int argc, char **argv) +{ + GTestSuite *suite; + TestData *data; + int i; + const char *port = NULL; + gint result; + + g_test_init (&argc, &argv, NULL); + + /* See if we got passed a serial port for live testing */ + for (i = 0; i < argc; i++) { + if (!strcmp (argv[i], "--port")) { + /* Make sure there's actually a port in the next arg */ + g_assert (argc > i + 1); + port = argv[++i]; + } + } + + data = test_data_new (port); + + suite = g_test_get_root (); + + g_test_suite_add (suite, TESTCASE (test_crc16_1, NULL)); + g_test_suite_add (suite, TESTCASE (test_crc16_2, NULL)); + g_test_suite_add (suite, TESTCASE (test_escape1, NULL)); + g_test_suite_add (suite, TESTCASE (test_escape2, NULL)); + g_test_suite_add (suite, TESTCASE (test_escape_unescape, NULL)); + g_test_suite_add (suite, TESTCASE (test_utils_decapsulate_buffer, NULL)); + g_test_suite_add (suite, TESTCASE (test_utils_encapsulate_buffer, NULL)); + g_test_suite_add (suite, TESTCASE (test_result_string, NULL)); + g_test_suite_add (suite, TESTCASE (test_result_uint32, NULL)); + g_test_suite_add (suite, TESTCASE (test_result_uint8, NULL)); + + /* Live tests */ + if (port) { + g_test_suite_add (suite, TESTCASE (test_com_port_init, data->com_data)); + g_test_suite_add (suite, TESTCASE (test_com_version_info, data->com_data)); + g_test_suite_add (suite, TESTCASE (test_com_esn, data->com_data)); + g_test_suite_add (suite, TESTCASE (test_com_mdn, data->com_data)); + g_test_suite_add (suite, TESTCASE (test_com_read_roam_pref, data->com_data)); + g_test_suite_add (suite, TESTCASE (test_com_read_mode_pref, data->com_data)); + g_test_suite_add (suite, TESTCASE (test_com_status, data->com_data)); + g_test_suite_add (suite, TESTCASE (test_com_sw_version, data->com_data)); + g_test_suite_add (suite, TESTCASE (test_com_pilot_sets, data->com_data)); + g_test_suite_add (suite, TESTCASE (test_com_cm_subsys_state_info, data->com_data)); + g_test_suite_add (suite, TESTCASE (test_com_hdr_subsys_state_info, data->com_data)); + g_test_suite_add (suite, TESTCASE (test_com_zte_subsys_status, data->com_data)); + g_test_suite_add (suite, TESTCASE (test_com_nw_subsys_modem_snapshot_cdma, data->com_data)); + } + + result = g_test_run (); + + test_data_free (data); + + return result; +} + diff --git a/m4/compiler_warnings.m4 b/m4/compiler_warnings.m4 new file mode 100644 index 0000000..6cea2f7 --- /dev/null +++ b/m4/compiler_warnings.m4 @@ -0,0 +1,31 @@ +AC_DEFUN([NM_COMPILER_WARNINGS], +[AC_ARG_ENABLE(more-warnings, + AS_HELP_STRING([--enable-more-warnings], [Maximum compiler warnings]), + set_more_warnings="$enableval",set_more_warnings=yes) +AC_MSG_CHECKING(for more warnings, including -Werror) +if test "$GCC" = "yes" -a "$set_more_warnings" != "no"; then + AC_MSG_RESULT(yes) + CFLAGS="-Wall -Werror -std=gnu89 $CFLAGS" + + for option in -Wshadow -Wmissing-declarations -Wmissing-prototypes \ + -Wdeclaration-after-statement -Wstrict-prototypes \ + -Wfloat-equal -Wno-unused-parameter -Wno-sign-compare \ + -fno-strict-aliasing; do + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $option" + AC_MSG_CHECKING([whether gcc understands $option]) + AC_TRY_COMPILE([], [], + has_option=yes, + has_option=no,) + if test $has_option = no; then + CFLAGS="$SAVE_CFLAGS" + fi + AC_MSG_RESULT($has_option) + unset has_option + unset SAVE_CFLAGS + done + unset option +else + AC_MSG_RESULT(no) +fi +]) diff --git a/marshallers/mm-marshal.list b/marshallers/mm-marshal.list index 474d704..c209bbb 100644 --- a/marshallers/mm-marshal.list +++ b/marshallers/mm-marshal.list @@ -5,4 +5,5 @@ VOID:UINT,BOOLEAN VOID:UINT,UINT VOID:UINT,UINT,UINT VOID:STRING,BOXED +VOID:POINTER,UINT diff --git a/org.freedesktop.ModemManager.conf b/org.freedesktop.ModemManager.conf deleted file mode 100644 index 5d62d7a..0000000 --- a/org.freedesktop.ModemManager.conf +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - 512 - diff --git a/org.freedesktop.ModemManager.conf.nopolkit b/org.freedesktop.ModemManager.conf.nopolkit new file mode 100644 index 0000000..2f33161 --- /dev/null +++ b/org.freedesktop.ModemManager.conf.nopolkit @@ -0,0 +1,17 @@ + + + + + + + + + + + + + 512 + + diff --git a/org.freedesktop.ModemManager.conf.polkit b/org.freedesktop.ModemManager.conf.polkit new file mode 100644 index 0000000..25490e3 --- /dev/null +++ b/org.freedesktop.ModemManager.conf.polkit @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 512 + + diff --git a/plugins/77-mm-ericsson-mbm.rules b/plugins/77-mm-ericsson-mbm.rules index 71dc6b8..8804036 100644 --- a/plugins/77-mm-ericsson-mbm.rules +++ b/plugins/77-mm-ericsson-mbm.rules @@ -20,9 +20,15 @@ ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1909", ENV{ID_MM_ERICSSON_MBM}="1" # Ericsson C3607w ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="1049", ENV{ID_MM_ERICSSON_MBM}="1" +# Ericsson C3607w v2 +ATTRS{idVendor}=="0bdb", ATTRS{idProduct}=="190b", ENV{ID_MM_ERICSSON_MBM}="1" + # Sony-Ericsson MD300 ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d0cf", ENV{ID_MM_ERICSSON_MBM}="1" +# Sony-Ericsson MD400 +ATTRS{idVendor}=="0fce", ATTRS{idProduct}=="d0e1", ENV{ID_MM_ERICSSON_MBM}="1" + # Dell 5530 HSDPA ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8147", ENV{ID_MM_ERICSSON_MBM}="1" @@ -30,6 +36,10 @@ ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8147", ENV{ID_MM_ERICSSON_MBM}="1" ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8183", ENV{ID_MM_ERICSSON_MBM}="1" ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8184", ENV{ID_MM_ERICSSON_MBM}="1" +# Dell F3307 +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818b", ENV{ID_MM_ERICSSON_MBM}="1" +ATTRS{idVendor}=="413c", ATTRS{idProduct}=="818c", ENV{ID_MM_ERICSSON_MBM}="1" + # Toshiba ATTRS{idVendor}=="0930", ATTRS{idProduct}=="130b", ENV{ID_MM_ERICSSON_MBM}="1" diff --git a/plugins/77-mm-longcheer-port-types.rules b/plugins/77-mm-longcheer-port-types.rules index 7317df7..ce0134b 100644 --- a/plugins/77-mm-longcheer-port-types.rules +++ b/plugins/77-mm-longcheer-port-types.rules @@ -6,6 +6,11 @@ # Alcatel One Touch X030 # MobiData MBD-200HU # ST Mobile Connect HSUPA USB Modem +# +# Most of these values were scraped from various Longcheer-based Windows +# driver .inf files. cmmdm.inf lists the actual data (ie PPP) ports, while +# cmser.inf lists the aux ports that may be either AT-capable or not but +# cannot be used for PPP. ACTION!="add|change", GOTO="mm_longcheer_port_types_end" @@ -18,6 +23,14 @@ GOTO="mm_longcheer_port_types_end" LABEL="mm_longcheer_vendorcheck" SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" +ATTRS{idProduct}=="3197", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="3197", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="3197", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="6000", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="6000", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="6000", ENV{ID_MM_LONGCHEER_TAGGED}="1" + ATTRS{idProduct}=="6060", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" ATTRS{idProduct}=="6060", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" ATTRS{idProduct}=="6060", ENV{ID_MM_LONGCHEER_TAGGED}="1" @@ -27,6 +40,127 @@ ATTRS{idProduct}=="6061", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE ATTRS{idProduct}=="6061", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" ATTRS{idProduct}=="6061", ENV{ID_MM_LONGCHEER_TAGGED}="1" +ATTRS{idProduct}=="7001", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="7001", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="7001", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="7001", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="7002", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="7002", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="7002", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="7002", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="7002", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="7101", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="7101", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="7101", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="7101", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="7102", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="7102", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="7102", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="7102", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="7102", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="8000", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="8000", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="8000", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="8000", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="8001", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="8001", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="8001", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="8001", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="8002", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="8002", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="8002", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="8002", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +# ChinaBird PL68 +ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9000", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9000", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9001", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9001", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9002", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9002", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9002", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9002", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9003", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9003", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9003", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9003", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9003", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9004", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9004", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9004", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9005", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9005", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9005", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9010", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9010", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9010", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9010", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9012", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9012", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9012", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9012", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9020", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9020", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9020", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9020", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9022", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9022", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9022", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9022", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +# Zoom products +ATTRS{idProduct}=="9602", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9602", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9602", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9602", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9603", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9603", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9603", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9603", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9604", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9604", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9604", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9604", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9605", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9605", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9605", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9605", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9605", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9606", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9606", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9606", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9606", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9606", ENV{ID_MM_LONGCHEER_TAGGED}="1" + +ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_LONGCHEER_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9607", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_LONGCHEER_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9607", ENV{ID_MM_LONGCHEER_TAGGED}="1" + GOTO="mm_longcheer_port_types_end" diff --git a/plugins/77-mm-simtech-port-types.rules b/plugins/77-mm-simtech-port-types.rules new file mode 100644 index 0000000..9ec047c --- /dev/null +++ b/plugins/77-mm-simtech-port-types.rules @@ -0,0 +1,29 @@ +# do not edit this file, it will be overwritten on update + +# Simtech makes modules that other companies rebrand, like: +# +# A-LINK 3GU +# +# Most of these values were scraped from various SimTech-based Windows +# driver .inf files. *mdm.inf lists the main command ports, while +# *ser.inf lists the aux ports that may be used for PPP. + + +ACTION!="add|change", GOTO="mm_simtech_port_types_end" +SUBSYSTEM!="tty", GOTO="mm_simtech_port_types_end" + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1e0e", GOTO="mm_alink_vendorcheck" +GOTO="mm_simtech_port_types_end" + +LABEL="mm_alink_vendorcheck" +SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}" + +ATTRS{idProduct}=="9200", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_SIMTECH_PORT_TYPE_MODEM}="1" +ATTRS{idProduct}=="9200", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_SIMTECH_PORT_TYPE_AUX}="1" +ATTRS{idProduct}=="9200", ENV{ID_MM_SIMTECH_TAGGED}="1" + +GOTO="mm_simtech_port_types_end" + + +LABEL="mm_simtech_port_types_end" + diff --git a/plugins/Makefile.am b/plugins/Makefile.am index a361358..8192653 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -11,7 +11,8 @@ pkglib_LTLIBRARIES = \ libmm-plugin-zte.la \ libmm-plugin-mbm.la \ libmm-plugin-longcheer.la \ - libmm-plugin-anydata.la + libmm-plugin-anydata.la \ + libmm-plugin-simtech.la # Generic @@ -169,7 +170,9 @@ libmm_plugin_novatel_la_SOURCES = \ mm-plugin-novatel.c \ mm-plugin-novatel.h \ mm-modem-novatel-gsm.c \ - mm-modem-novatel-gsm.h + mm-modem-novatel-gsm.h \ + mm-modem-novatel-cdma.c \ + mm-modem-novatel-cdma.h libmm_plugin_novatel_la_CPPFLAGS = \ $(MM_CFLAGS) \ @@ -221,7 +224,9 @@ libmm_plugin_zte_la_LDFLAGS = \ libmm_plugin_longcheer_la_SOURCES = \ mm-plugin-longcheer.c \ - mm-plugin-longcheer.h + mm-plugin-longcheer.h \ + mm-modem-longcheer-gsm.c \ + mm-modem-longcheer-gsm.h libmm_plugin_longcheer_la_CPPFLAGS = \ $(MM_CFLAGS) \ @@ -251,12 +256,31 @@ libmm_plugin_anydata_la_LDFLAGS = \ -module \ -avoid-version +# SimTech + +libmm_plugin_simtech_la_SOURCES = \ + mm-plugin-simtech.c \ + mm-plugin-simtech.h \ + mm-modem-simtech-gsm.c \ + mm-modem-simtech-gsm.h + +libmm_plugin_simtech_la_CPPFLAGS = \ + $(MM_CFLAGS) \ + $(GUDEV_CFLAGS) \ + -I$(top_srcdir)/src + +libmm_plugin_simtech_la_LDFLAGS = \ + $(GUDEV_LDFLAGS) \ + -module \ + -avoid-version + udevrulesdir = $(UDEV_BASE_DIR)/rules.d udevrules_DATA = \ 77-mm-ericsson-mbm.rules \ 77-mm-zte-port-types.rules \ - 77-mm-longcheer-port-types.rules + 77-mm-longcheer-port-types.rules \ + 77-mm-simtech-port-types.rules BUILT_SOURCES = \ mm-modem-gsm-hso-glue.h @@ -264,5 +288,6 @@ BUILT_SOURCES = \ CLEANFILES = $(BUILT_SOURCES) EXTRA_DIST = \ - $(udevrules_DATA) + $(udevrules_DATA) \ + mm-modem-option-utils.c diff --git a/plugins/mm-modem-anydata-cdma.c b/plugins/mm-modem-anydata-cdma.c index f6528ec..c7cca46 100644 --- a/plugins/mm-modem-anydata-cdma.c +++ b/plugins/mm-modem-anydata-cdma.c @@ -112,7 +112,7 @@ int_from_match_item (GMatchInfo *match_info, guint32 num, gint *val) } static void -evdo_state_done (MMSerialPort *port, +evdo_state_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -123,15 +123,8 @@ evdo_state_done (MMSerialPort *port, GRegex *r; GMatchInfo *match_info; - info->error = mm_modem_check_removed (info->modem, error); - if (info->error) { - if (info->modem) { - /* If HSTATE returned an error, assume the device is not EVDO capable - * or EVDO is not registered. - */ - mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); - } - + if (error) { + /* Leave superclass' reg state alone if AT*HSTATE isn't supported */ mm_callback_info_schedule (info); return; } @@ -143,13 +136,8 @@ evdo_state_done (MMSerialPort *port, G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); if (!r) { /* Parse error; warn about it and assume EVDO is not available */ - g_warning ("AnyData(%s): failed to create EVDO state regex: (%d) %s", - __func__, - error ? error->code : -1, - error && error->message ? error->message : "(unknown)"); - mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); - mm_callback_info_schedule (info); - return; + g_warning ("AnyDATA(%s): *HSTATE parse regex creation failed.", __func__); + goto done; } g_regex_match (r, reply, 0, &match_info); @@ -185,13 +173,13 @@ evdo_state_done (MMSerialPort *port, } } +done: mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, reg_state); - mm_callback_info_schedule (info); } static void -state_done (MMSerialPort *port, +state_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -202,17 +190,8 @@ state_done (MMSerialPort *port, GRegex *r; GMatchInfo *match_info; - info->error = mm_modem_check_removed (info->modem, error); - if (info->error) { - if (info->modem) { - /* Assume if we got this far, we're registered even if an error - * occurred. We're not sure if all AnyData CDMA modems support - * the *STATE and *HSTATE commands. - */ - mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED); - mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); - } - + if (error) { + /* Leave superclass' reg state alone if AT*STATE isn't supported */ mm_callback_info_schedule (info); return; } @@ -223,9 +202,7 @@ state_done (MMSerialPort *port, r = g_regex_new ("\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*([^,\\)]*)\\s*,.*", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); if (!r) { - info->error = g_error_new_literal (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "Could not parse sysinfo results (regex creation failed)."); + g_warning ("AnyDATA(%s): *STATE parse regex creation failed.", __func__); mm_callback_info_schedule (info); return; } @@ -254,7 +231,7 @@ state_done (MMSerialPort *port, reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; break; default: - g_message ("ANYDATA: unknown *STATE (%d); assuming no service.", val); + g_warning ("ANYDATA: unknown *STATE (%d); assuming no service.", val); /* fall through */ case 0: /* NO SERVICE */ break; @@ -265,35 +242,28 @@ state_done (MMSerialPort *port, mm_generic_cdma_query_reg_state_set_callback_1x_state (info, reg_state); /* Try for EVDO state too */ - mm_serial_port_queue_command (port, "*HSTATE?", 3, evdo_state_done, info); + mm_at_serial_port_queue_command (port, "*HSTATE?", 3, evdo_state_done, info); } static void query_registration_state (MMGenericCdma *cdma, + MMModemCdmaRegistrationState cur_cdma_state, + MMModemCdmaRegistrationState cur_evdo_state, MMModemCdmaRegistrationStateFn callback, gpointer user_data) { MMCallbackInfo *info; - MMSerialPort *primary, *secondary, *port; - - port = primary = mm_generic_cdma_get_port (cdma, MM_PORT_TYPE_PRIMARY); - secondary = mm_generic_cdma_get_port (cdma, MM_PORT_TYPE_SECONDARY); - - info = mm_generic_cdma_query_reg_state_callback_info_new (cdma, callback, user_data); + MMAtSerialPort *port; - if (mm_port_get_connected (MM_PORT (primary))) { - if (!secondary) { - info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, - "Cannot get query registration state while connected"); - mm_callback_info_schedule (info); - return; - } + info = mm_generic_cdma_query_reg_state_callback_info_new (cdma, cur_cdma_state, cur_evdo_state, callback, user_data); - /* Use secondary port if primary is connected */ - port = secondary; + port = mm_generic_cdma_get_best_at_port (cdma, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; } - mm_serial_port_queue_command (port, "*STATE?", 3, state_done, info); + mm_at_serial_port_queue_command (port, "*STATE?", 3, state_done, info); } /*****************************************************************************/ @@ -310,22 +280,22 @@ grab_port (MMModem *modem, GRegex *regex; port = mm_generic_cdma_grab_port (MM_GENERIC_CDMA (modem), subsys, name, suggested_type, user_data, error); - if (port && MM_IS_SERIAL_PORT (port)) { + if (port && MM_IS_AT_SERIAL_PORT (port)) { /* Data state notifications */ /* Data call has connected */ regex = g_regex_new ("\\r\\n\\*ACTIVE:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, NULL, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); g_regex_unref (regex); /* Data call disconnected */ regex = g_regex_new ("\\r\\n\\*INACTIVE:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, NULL, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); g_regex_unref (regex); /* Modem is now dormant */ regex = g_regex_new ("\\r\\n\\*DORMANT:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, NULL, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); g_regex_unref (regex); /* Abnomral state notifications @@ -336,17 +306,17 @@ grab_port (MMModem *modem, /* Network acquisition fail */ regex = g_regex_new ("\\r\\n\\*OFFLINE:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, NULL, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); g_regex_unref (regex); /* Registration fail */ regex = g_regex_new ("\\r\\n\\*REGREQ:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, NULL, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); g_regex_unref (regex); /* Authentication fail */ regex = g_regex_new ("\\r\\n\\*AUTHREQ:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, NULL, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); g_regex_unref (regex); } diff --git a/plugins/mm-modem-gobi-gsm.c b/plugins/mm-modem-gobi-gsm.c index 7ea9f8f..3b9e9ec 100644 --- a/plugins/mm-modem-gobi-gsm.c +++ b/plugins/mm-modem-gobi-gsm.c @@ -23,6 +23,7 @@ #include "mm-errors.h" #include "mm-callback-info.h" #include "mm-modem-gsm-card.h" +#include "mm-at-serial-port.h" static void modem_init (MMModem *modem_class); static void modem_gsm_card_init (MMModemGsmCard *gsm_card_class); @@ -51,7 +52,7 @@ mm_modem_gobi_gsm_new (const char *device, /*****************************************************************************/ static void -get_string_done (MMSerialPort *port, +get_string_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -75,13 +76,16 @@ get_imsi (MMModemGsmCard *modem, MMModemStringFn callback, gpointer user_data) { - MMSerialPort *primary; + MMAtSerialPort *port; MMCallbackInfo *info; info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); - g_assert (primary); - mm_serial_port_queue_command_cached (primary, "+CIMI", 3, get_string_done, info); + + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (port) + mm_at_serial_port_queue_command_cached (port, "+CIMI", 3, get_string_done, info); + else + mm_callback_info_schedule (info); } static void diff --git a/plugins/mm-modem-hso.c b/plugins/mm-modem-hso.c index f1295e2..1fd4633 100644 --- a/plugins/mm-modem-hso.c +++ b/plugins/mm-modem-hso.c @@ -63,6 +63,9 @@ typedef struct { MMCallbackInfo *connect_pending_data; guint connect_pending_id; + char *username; + char *password; + guint32 auth_idx; } MMModemHsoPrivate; @@ -85,62 +88,122 @@ mm_modem_hso_new (const char *device, NULL)); } -#define IGNORE_ERRORS_TAG "ignore-errors" +#include "mm-modem-option-utils.c" + +/*****************************************************************************/ + +static gint +hso_get_cid (MMModemHso *self) +{ + gint cid; + + cid = mm_generic_gsm_get_cid (MM_GENERIC_GSM (self)); + if (cid < 0) { + g_warn_if_fail (cid >= 0); + cid = 0; + } + + return cid; +} static void -hso_call_control_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +auth_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMModemHso *self = MM_MODEM_HSO (info->modem); + MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (self); - if (error && !mm_callback_info_get_data (info, IGNORE_ERRORS_TAG)) - info->error = g_error_copy (error); + if (error) { + priv->auth_idx++; + if (auth_commands[priv->auth_idx]) { + /* Try the next auth command */ + _internal_hso_modem_authenticate (self, info); + return; + } else + info->error = g_error_copy (error); + } + /* Reset to 0 so something gets tried the next connection */ + priv->auth_idx = 0; mm_callback_info_schedule (info); } -static guint32 -hso_get_cid (MMModemHso *self) +static void +_internal_hso_modem_authenticate (MMModemHso *self, MMCallbackInfo *info) { - guint32 cid; + MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (self); + MMAtSerialPort *primary; + gint cid; + char *command; - cid = mm_generic_gsm_get_cid (MM_GENERIC_GSM (self)); - if (cid == 0) - cid = 1; + primary = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (self), MM_PORT_TYPE_PRIMARY); + g_assert (primary); - return cid; + cid = hso_get_cid (self); + g_warn_if_fail (cid >= 0); + + /* Both user and password are required; otherwise firmware returns an error */ + if (!priv->username || !priv->password) + command = g_strdup_printf ("%s=%d,0", auth_commands[priv->auth_idx], cid); + else { + command = g_strdup_printf ("%s=%d,1,\"%s\",\"%s\"", + auth_commands[priv->auth_idx], + cid, + priv->password ? priv->password : "", + priv->username ? priv->username : ""); + + } + + mm_at_serial_port_queue_command (primary, command, 3, auth_done, info); + g_free (command); } -static void -hso_call_control (MMModemHso *self, - gboolean activate, - gboolean ignore_errors, - MMModemFn callback, - gpointer user_data) +void +mm_hso_modem_authenticate (MMModemHso *self, + const char *username, + const char *password, + MMModemFn callback, + gpointer user_data) { + MMModemHsoPrivate *priv; MMCallbackInfo *info; - char *command; - MMSerialPort *primary; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_HSO (self)); + g_return_if_fail (callback != NULL); info = mm_callback_info_new (MM_MODEM (self), callback, user_data); - mm_callback_info_set_data (info, IGNORE_ERRORS_TAG, GUINT_TO_POINTER (ignore_errors), NULL); - command = g_strdup_printf ("AT_OWANCALL=%d,%d,1", hso_get_cid (self), activate ? 1 : 0); - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (self), MM_PORT_TYPE_PRIMARY); - g_assert (primary); - mm_serial_port_queue_command (primary, command, 3, hso_call_control_done, info); - g_free (command); + priv = MM_MODEM_HSO_GET_PRIVATE (self); + + g_free (priv->username); + priv->username = (username && strlen (username)) ? g_strdup (username) : NULL; + + g_free (priv->password); + priv->password = (password && strlen (password)) ? g_strdup (password) : NULL; + + _internal_hso_modem_authenticate (self, info); } +/*****************************************************************************/ + static void connect_pending_done (MMModemHso *self) { MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (self); + GError *error = NULL; if (priv->connect_pending_data) { - mm_callback_info_schedule (priv->connect_pending_data); + if (priv->connect_pending_data->error) { + error = priv->connect_pending_data->error; + priv->connect_pending_data->error = NULL; + } + + /* Complete the connect */ + mm_generic_gsm_connect_complete (MM_GENERIC_GSM (self), error, priv->connect_pending_data); priv->connect_pending_data = NULL; } @@ -150,177 +213,175 @@ connect_pending_done (MMModemHso *self) } } -static gboolean -hso_connect_timed_out (gpointer data) +static void +connection_enabled (MMAtSerialPort *port, + GMatchInfo *match_info, + gpointer user_data) { - MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (data); + MMModemHso *self = MM_MODEM_HSO (user_data); + MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (self); + char *str; + + str = g_match_info_fetch (match_info, 2); + if (str[0] == '1') + connect_pending_done (self); + else if (str[0] == '3') { + MMCallbackInfo *info = priv->connect_pending_data; - priv->connect_pending_data->error = g_error_new_literal (MM_SERIAL_ERROR, - MM_SERIAL_RESPONSE_TIMEOUT, - "Connection timed out"); - connect_pending_done (MM_MODEM_HSO (data)); + if (info) { + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Call setup failed"); + } - return FALSE; + connect_pending_done (self); + } else if (str[0] == '0') { + /* FIXME: disconnected. do something when we have modem status signals */ + } + + g_free (str); } +/*****************************************************************************/ + +#define IGNORE_ERRORS_TAG "ignore-errors" + static void -hso_enabled (MMModem *modem, - GError *error, - gpointer user_data) +hso_call_control_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - if (error) { + if (error && !mm_callback_info_get_data (info, IGNORE_ERRORS_TAG)) info->error = g_error_copy (error); - mm_callback_info_schedule (info); - } else { - MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (modem); - GSource *source; - source = g_timeout_source_new_seconds (30); - g_source_set_closure (source, g_cclosure_new_object (G_CALLBACK (hso_connect_timed_out), G_OBJECT (modem))); - g_source_attach (source, NULL); - priv->connect_pending_data = info; - priv->connect_pending_id = g_source_get_id (source); - g_source_unref (source); - } + mm_callback_info_schedule (info); } static void -clear_old_context (MMModem *modem, - GError *error, - gpointer user_data) +hso_call_control (MMModemHso *self, + gboolean activate, + gboolean ignore_errors, + MMModemFn callback, + gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMCallbackInfo *info; + char *command; + MMAtSerialPort *primary; - if (error) { - info->error = g_error_copy (error); - mm_callback_info_schedule (info); - } else { - /* Success, activate the PDP context and start the data session */ - hso_call_control (MM_MODEM_HSO (modem), TRUE, FALSE, hso_enabled, info); - } + info = mm_callback_info_new (MM_MODEM (self), callback, user_data); + mm_callback_info_set_data (info, IGNORE_ERRORS_TAG, GUINT_TO_POINTER (ignore_errors), NULL); + + command = g_strdup_printf ("AT_OWANCALL=%d,%d,1", hso_get_cid (self), activate ? 1 : 0); + primary = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (self), MM_PORT_TYPE_PRIMARY); + g_assert (primary); + mm_at_serial_port_queue_command (primary, command, 3, hso_call_control_done, info); + g_free (command); } static void -auth_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +timeout_done (MMModem *modem, + GError *error, + gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - MMModemHso *self = MM_MODEM_HSO (info->modem); - MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (self); - - if (error) { - priv->auth_idx++; - if (auth_commands[priv->auth_idx]) { - /* Try the next auth command */ - _internal_hso_modem_authenticate (self, info); - } else { - /* Reset to 0 so that something gets tried for the next connection */ - priv->auth_idx = 0; - - info->error = g_error_copy (error); - mm_callback_info_schedule (info); - } - } else { - priv->auth_idx = 0; - - /* success, kill any existing connections first */ - hso_call_control (self, FALSE, TRUE, clear_old_context, info); - } + if (modem) + connect_pending_done (MM_MODEM_HSO (modem)); } -static void -_internal_hso_modem_authenticate (MMModemHso *self, MMCallbackInfo *info) +static gboolean +hso_connect_timed_out (gpointer data) { + MMModemHso *self = MM_MODEM_HSO (data); MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (self); - MMSerialPort *primary; - guint32 cid; - char *command; - const char *username, *password; - - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (self), MM_PORT_TYPE_PRIMARY); - g_assert (primary); - - cid = hso_get_cid (self); - - username = mm_callback_info_get_data (info, "username"); - password = mm_callback_info_get_data (info, "password"); + MMCallbackInfo *info = priv->connect_pending_data; - if (!username && !password) - command = g_strdup_printf ("%s=%d,0", auth_commands[priv->auth_idx], cid); - else { - command = g_strdup_printf ("%s=%d,1,\"%s\",\"%s\"", - auth_commands[priv->auth_idx], - cid, - password ? password : "", - username ? username : ""); + priv->connect_pending_id = 0; + if (info) { + info->error = g_error_new_literal (MM_SERIAL_ERROR, + MM_SERIAL_ERROR_RESPONSE_TIMEOUT, + "Connection timed out"); } - mm_serial_port_queue_command (primary, command, 3, auth_done, info); - g_free (command); + hso_call_control (self, FALSE, TRUE, timeout_done, self); + return FALSE; } -void -mm_hso_modem_authenticate (MMModemHso *self, - const char *username, - const char *password, - MMModemFn callback, - gpointer user_data) +static void +hso_enabled (MMModem *modem, + GError *error, + gpointer user_data) { - MMCallbackInfo *info; - - g_return_if_fail (MM_IS_MODEM_HSO (self)); - g_return_if_fail (callback != NULL); + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GError *tmp_error; - info = mm_callback_info_new (MM_MODEM (self), callback, user_data); - if (username) - mm_callback_info_set_data (info, "username", g_strdup (username), g_free); - if (password) - mm_callback_info_set_data (info, "password", g_strdup (password), g_free); + tmp_error = mm_modem_check_removed (modem, error); + if (tmp_error) { + mm_generic_gsm_connect_complete (MM_GENERIC_GSM (modem), tmp_error, info); + g_clear_error (&tmp_error); + } else { + MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (modem); - _internal_hso_modem_authenticate (self, info); + priv->connect_pending_data = info; + priv->connect_pending_id = g_timeout_add_seconds (30, hso_connect_timed_out, modem); + } } -/*****************************************************************************/ - static void -enable_done (MMModem *modem, GError *error, gpointer user_data) +old_context_clear_done (MMModem *modem, + GError *error, + gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GError *tmp_error; - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (modem), error, info); + tmp_error = mm_modem_check_removed (modem, error); + if (tmp_error) { + mm_generic_gsm_connect_complete (MM_GENERIC_GSM (modem), tmp_error, info); + g_clear_error (&tmp_error); + } else { + /* Success, activate the PDP context and start the data session */ + hso_call_control (MM_MODEM_HSO (modem), TRUE, FALSE, hso_enabled, info); + } } static void -parent_enable_done (MMModem *modem, GError *error, gpointer user_data) +connect_auth_done (MMModem *modem, + GError *error, + gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - MMGenericGsm *self = MM_GENERIC_GSM (modem); + GError *tmp_error; - if (error) { - mm_generic_gsm_enable_complete (self, error, info); - return; + tmp_error = mm_modem_check_removed (modem, error); + if (tmp_error) { + mm_generic_gsm_connect_complete (MM_GENERIC_GSM (modem), tmp_error, info); + g_clear_error (&tmp_error); + } else { + /* Now connect; kill any existing connections first */ + hso_call_control (MM_MODEM_HSO (modem), FALSE, TRUE, old_context_clear_done, info); } - - /* HSO needs manual PIN checking */ - mm_generic_gsm_check_pin (self, enable_done, info); } static void -enable (MMModem *modem, MMModemFn callback, gpointer user_data) +do_connect (MMModem *modem, + const char *number, + MMModemFn callback, + gpointer user_data) { - MMModem *parent_modem_iface; - MMCallbackInfo *info; + MMModemHso *self = MM_MODEM_HSO (modem); + MMCallbackInfo *auth_info, *connect_info; - info = mm_callback_info_new (modem, callback, user_data); - parent_modem_iface = g_type_interface_peek_parent (MM_MODEM_GET_INTERFACE (info->modem)); - parent_modem_iface->enable (info->modem, parent_enable_done, info); + mm_modem_set_state (modem, MM_MODEM_STATE_CONNECTING, MM_MODEM_STATE_REASON_NONE); + + connect_info = mm_callback_info_new (modem, callback, user_data); + auth_info = mm_callback_info_new (modem, connect_auth_done, connect_info); + _internal_hso_modem_authenticate (self, auth_info); } +/*****************************************************************************/ + static void parent_disable_done (MMModem *modem, GError *error, gpointer user_data) { @@ -345,32 +406,56 @@ disable_done (MMModem *modem, } static void -disable (MMModem *modem, - MMModemFn callback, - gpointer user_data) +unsolicited_disable_done (MMModem *modem, + GError *error, + gpointer user_data) { - MMCallbackInfo *info; + MMCallbackInfo *info = user_data; - mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem)); + /* Handle modem removal, but ignore other errors */ + if (g_error_matches (error, MM_MODEM_ERROR, MM_MODEM_ERROR_REMOVED)) + info->error = g_error_copy (error); + else if (!modem) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_REMOVED, + "The modem was removed."); + } - info = mm_callback_info_new (modem, callback, user_data); + if (info->error) { + mm_callback_info_schedule (info); + return; + } - /* Kill any existing connection */ - hso_call_control (MM_MODEM_HSO (modem), FALSE, TRUE, disable_done, info); + /* Otherwise, kill any existing connection */ + if (mm_generic_gsm_get_cid (MM_GENERIC_GSM (modem)) >= 0) + hso_call_control (MM_MODEM_HSO (modem), FALSE, TRUE, disable_done, info); + else + disable_done (modem, NULL, info); } static void -do_connect (MMModem *modem, - const char *number, - MMModemFn callback, - gpointer user_data) +disable (MMModem *modem, + MMModemFn callback, + gpointer user_data) { + MMModemHso *self = MM_MODEM_HSO (modem); + MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (self); MMCallbackInfo *info; + mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem)); + + g_free (priv->username); + priv->username = NULL; + g_free (priv->password); + priv->password = NULL; + info = mm_callback_info_new (modem, callback, user_data); - mm_callback_info_schedule (info); + + /* Turn off unsolicited messages so they don't pile up in the modem */ + option_change_unsolicited_messages (MM_GENERIC_GSM (modem), FALSE, unsolicited_disable_done, info); } +/*****************************************************************************/ static void free_dns_array (gpointer data) @@ -390,7 +475,7 @@ ip4_config_invoke (MMCallbackInfo *info) } static void -get_ip4_config_done (MMSerialPort *port, +get_ip4_config_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -400,7 +485,7 @@ get_ip4_config_done (MMSerialPort *port, GArray *dns_array; int i; guint32 tmp; - guint cid; + gint cid; if (error) { info->error = g_error_copy (error); @@ -421,7 +506,7 @@ get_ip4_config_done (MMSerialPort *port, errno = 0; num = strtol (*iter, NULL, 10); - if (errno != 0 || num < 0 || (guint) num != cid) { + if (errno != 0 || num < 0 || (gint) num != cid) { info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Unknown CID in OWANDATA response (" "got %d, expected %d)", (guint) num, cid); @@ -453,28 +538,61 @@ get_ip4_config (MMModem *modem, { MMCallbackInfo *info; char *command; - MMSerialPort *primary; + MMAtSerialPort *primary; info = mm_callback_info_new_full (modem, ip4_config_invoke, G_CALLBACK (callback), user_data); command = g_strdup_printf ("AT_OWANDATA=%d", hso_get_cid (MM_MODEM_HSO (modem))); - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); + primary = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); g_assert (primary); - mm_serial_port_queue_command (primary, command, 3, get_ip4_config_done, info); + mm_at_serial_port_queue_command (primary, command, 3, get_ip4_config_done, info); g_free (command); } +/*****************************************************************************/ + static void -disconnect (MMModem *modem, - MMModemFn callback, - gpointer user_data) +disconnect_owancall_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + mm_callback_info_schedule ((MMCallbackInfo *) user_data); +} + +static void +do_disconnect (MMGenericGsm *gsm, + gint cid, + MMModemFn callback, + gpointer user_data) { MMCallbackInfo *info; - MMSerialPort *primary; + MMAtSerialPort *primary; + char *command; - info = mm_callback_info_new (modem, callback, user_data); - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); + info = mm_callback_info_new (MM_MODEM (gsm), callback, user_data); + + primary = mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY); g_assert (primary); - mm_serial_port_queue_command (primary, "AT_OWANCALL=1,0,0", 3, NULL, info); + + command = g_strdup_printf ("AT_OWANCALL=%d,0,0", cid); + mm_at_serial_port_queue_command (primary, command, 3, disconnect_owancall_done, info); + g_free (command); +} + +/*****************************************************************************/ + +static void +real_do_enable_power_up_done (MMGenericGsm *gsm, + GString *response, + GError *error, + MMCallbackInfo *info) +{ + /* Enable Option unsolicited messages */ + if (gsm && !error) + option_change_unsolicited_messages (gsm, TRUE, NULL, NULL); + + /* Chain up to parent */ + MM_GENERIC_GSM_CLASS (mm_modem_hso_parent_class)->do_enable_power_up_done (gsm, response, error, info); } /*****************************************************************************/ @@ -507,47 +625,11 @@ impl_hso_authenticate (MMModemHso *self, mm_hso_modem_authenticate (self, username, password, impl_hso_auth_done, context); } -static void -connection_enabled (MMSerialPort *port, - GMatchInfo *info, - gpointer user_data) -{ - MMModemHso *self = MM_MODEM_HSO (user_data); - MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (self); - char *str; - - str = g_match_info_fetch (info, 2); - if (str[0] == '1') - connect_pending_done (self); - else if (str[0] == '3') { - MMCallbackInfo *cb_info = priv->connect_pending_data; - - if (cb_info) - cb_info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, - "Call setup failed"); - - connect_pending_done (self); - } else if (str[0] == '0') - /* FIXME: disconnected. do something when we have modem status signals */ - ; - - g_free (str); -} - /*****************************************************************************/ -/* MMModemSimple interface */ - -typedef enum { - SIMPLE_STATE_BEGIN = 0, - SIMPLE_STATE_PARENT_CONNECT, - SIMPLE_STATE_AUTHENTICATE, - SIMPLE_STATE_DONE -} SimpleState; static const char * -simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error) +hso_simple_get_string_property (GHashTable *properties, const char *name, GError **error) { - GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties"); GValue *value; value = (GValue *) g_hash_table_lookup (properties, name); @@ -565,60 +647,47 @@ simple_get_string_property (MMCallbackInfo *info, const char *name, GError **err } static void -simple_state_machine (MMModem *modem, GError *error, gpointer user_data) +simple_connect (MMModemSimple *simple, + GHashTable *properties, + MMModemFn callback, + gpointer user_data) { + MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (simple); MMCallbackInfo *info = (MMCallbackInfo *) user_data; MMModemSimple *parent_iface; - const char *username; - const char *password; - GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties"); - SimpleState state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "simple-connect-state")); - - if (error) { - info->error = g_error_copy (error); - goto out; - } - switch (state) { - case SIMPLE_STATE_BEGIN: - state = SIMPLE_STATE_PARENT_CONNECT; - parent_iface = g_type_interface_peek_parent (MM_MODEM_SIMPLE_GET_INTERFACE (modem)); - parent_iface->connect (MM_MODEM_SIMPLE (modem), properties, simple_state_machine, info); - break; - case SIMPLE_STATE_PARENT_CONNECT: - state = SIMPLE_STATE_AUTHENTICATE; - username = simple_get_string_property (info, "username", &info->error); - password = simple_get_string_property (info, "password", &info->error); - mm_hso_modem_authenticate (MM_MODEM_HSO (modem), username, password, simple_state_machine, info); - break; - case SIMPLE_STATE_AUTHENTICATE: - state = SIMPLE_STATE_DONE; - break; - default: - break; - } + priv->username = g_strdup (hso_simple_get_string_property (properties, "username", NULL)); + priv->password = g_strdup (hso_simple_get_string_property (properties, "password", NULL)); - out: - if (info->error || state == SIMPLE_STATE_DONE) - mm_callback_info_schedule (info); - else - mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (state), NULL); + parent_iface = g_type_interface_peek_parent (MM_MODEM_SIMPLE_GET_INTERFACE (simple)); + parent_iface->connect (MM_MODEM_SIMPLE (simple), properties, callback, info); } +/*****************************************************************************/ + static void -simple_connect (MMModemSimple *simple, - GHashTable *properties, - MMModemFn callback, - gpointer user_data) +get_allowed_mode (MMGenericGsm *gsm, + MMModemUIntFn callback, + gpointer user_data) { - MMCallbackInfo *info; + option_get_allowed_mode (gsm, callback, user_data); +} - info = mm_callback_info_new (MM_MODEM (simple), callback, user_data); - mm_callback_info_set_data (info, "simple-connect-properties", - g_hash_table_ref (properties), - (GDestroyNotify) g_hash_table_unref); +static void +set_allowed_mode (MMGenericGsm *gsm, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data) +{ + option_set_allowed_mode (gsm, mode, callback, user_data); +} - simple_state_machine (MM_MODEM (simple), NULL, info); +static void +get_access_technology (MMGenericGsm *gsm, + MMModemUIntFn callback, + gpointer user_data) +{ + option_get_access_technology (gsm, callback, user_data); } /*****************************************************************************/ @@ -676,17 +745,20 @@ grab_port (MMModem *modem, if (!port) goto out; - if (MM_IS_SERIAL_PORT (port)) { + if (MM_IS_AT_SERIAL_PORT (port)) { g_object_set (G_OBJECT (port), MM_SERIAL_PORT_SEND_DELAY, (guint64) 10000, NULL); if (ptype == MM_PORT_TYPE_PRIMARY) { GRegex *regex; - mm_generic_gsm_set_unsolicited_registration (gsm, TRUE); - regex = g_regex_new ("_OWANCALL: (\\d),\\s*(\\d)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, connection_enabled, modem, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, connection_enabled, modem, NULL); + g_regex_unref (regex); + + regex = g_regex_new ("\\r\\n\\+PACSP0\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); g_regex_unref (regex); } + option_register_unsolicted_handlers (gsm, MM_AT_SERIAL_PORT (port)); } out: @@ -712,19 +784,23 @@ modem_simple_init (MMModemSimple *class) static void modem_init (MMModem *modem_class) { - modem_class->enable = enable; modem_class->disable = disable; modem_class->connect = do_connect; modem_class->get_ip4_config = get_ip4_config; - modem_class->disconnect = disconnect; modem_class->grab_port = grab_port; } static void finalize (GObject *object) { + MMModemHso *self = MM_MODEM_HSO (object); + MMModemHsoPrivate *priv = MM_MODEM_HSO_GET_PRIVATE (self); + /* Clear the pending connection if necessary */ - connect_pending_done (MM_MODEM_HSO (object)); + connect_pending_done (self); + + g_free (priv->username); + g_free (priv->password); G_OBJECT_CLASS (mm_modem_hso_parent_class)->finalize (object); } @@ -733,11 +809,17 @@ static void mm_modem_hso_class_init (MMModemHsoClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMGenericGsmClass *gsm_class = MM_GENERIC_GSM_CLASS (klass); mm_modem_hso_parent_class = g_type_class_peek_parent (klass); g_type_class_add_private (object_class, sizeof (MMModemHsoPrivate)); /* Virtual methods */ object_class->finalize = finalize; + gsm_class->do_disconnect = do_disconnect; + gsm_class->do_enable_power_up_done = real_do_enable_power_up_done; + gsm_class->set_allowed_mode = set_allowed_mode; + gsm_class->get_allowed_mode = get_allowed_mode; + gsm_class->get_access_technology = get_access_technology; } diff --git a/plugins/mm-modem-huawei-cdma.c b/plugins/mm-modem-huawei-cdma.c index 3b63a48..523578f 100644 --- a/plugins/mm-modem-huawei-cdma.c +++ b/plugins/mm-modem-huawei-cdma.c @@ -41,16 +41,26 @@ mm_modem_huawei_cdma_new (const char *device, gboolean evdo_rev0, gboolean evdo_revA) { + gboolean try_css = TRUE; + g_return_val_if_fail (device != NULL, NULL); g_return_val_if_fail (driver != NULL, NULL); g_return_val_if_fail (plugin != NULL, NULL); + /* Don't use AT+CSS on EVDO-capable hardware for determining registration + * status, because often the device will have only an EVDO connection and + * AT+CSS won't necessarily report EVDO registration status, only 1X. + */ + if (evdo_rev0 || evdo_revA) + try_css = FALSE; + return MM_MODEM (g_object_new (MM_TYPE_MODEM_HUAWEI_CDMA, MM_MODEM_MASTER_DEVICE, device, MM_MODEM_DRIVER, driver, MM_MODEM_PLUGIN, plugin, MM_GENERIC_CDMA_EVDO_REV0, evdo_rev0, MM_GENERIC_CDMA_EVDO_REVA, evdo_revA, + MM_GENERIC_CDMA_REGISTRATION_TRY_CSS, try_css, NULL)); } @@ -72,7 +82,7 @@ parse_quality (const char *str, const char *detail) } static void -handle_1x_quality_change (MMSerialPort *port, +handle_1x_quality_change (MMAtSerialPort *port, GMatchInfo *match_info, gpointer user_data) { @@ -89,9 +99,9 @@ handle_1x_quality_change (MMSerialPort *port, } static void -handle_evdo_quality_change (MMSerialPort *port, - GMatchInfo *match_info, - gpointer user_data) +handle_evdo_quality_change (MMAtSerialPort *port, + GMatchInfo *match_info, + gpointer user_data) { MMModemHuaweiCdma *self = MM_MODEM_HUAWEI_CDMA (user_data); char *str; @@ -142,7 +152,7 @@ uint_from_match_item (GMatchInfo *match_info, guint32 num, guint32 *val) } static void -sysinfo_done (MMSerialPort *port, +sysinfo_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -151,12 +161,10 @@ sysinfo_done (MMSerialPort *port, GRegex *r; GMatchInfo *match_info; const char *reply; - gboolean success = FALSE; if (error) { - info->error = g_error_copy (error); - mm_callback_info_schedule (info); - return; + /* Leave superclass' reg state alone if AT^SYSINFO isn't supported */ + goto done; } reply = strip_response (response->str, "^SYSINFO:"); @@ -165,9 +173,7 @@ sysinfo_done (MMSerialPort *port, r = g_regex_new ("\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); if (!r) { - info->error = g_error_new_literal (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "Could not parse sysinfo results (regex creation failed)."); + g_warning ("Huawei(%s): ^SYSINFO parse regex creation failed.", __func__); goto done; } @@ -207,48 +213,35 @@ sysinfo_done (MMSerialPort *port, /* Say we're registered to something even though sysmode parsing failed */ mm_generic_cdma_query_reg_state_set_callback_1x_state (info, reg_state); } - success = TRUE; - } + } else + g_warning ("Huawei(%s): failed to parse ^SYSINFO response.", __func__); -done: g_match_info_free (match_info); g_regex_unref (r); - - if (!success && !info->error) { - info->error = g_error_new_literal (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "Could not parse sysinfo results."); - } +done: mm_callback_info_schedule (info); } static void query_registration_state (MMGenericCdma *cdma, + MMModemCdmaRegistrationState cur_cdma_state, + MMModemCdmaRegistrationState cur_evdo_state, MMModemCdmaRegistrationStateFn callback, gpointer user_data) { MMCallbackInfo *info; - MMSerialPort *primary, *secondary, *port; - - port = primary = mm_generic_cdma_get_port (cdma, MM_PORT_TYPE_PRIMARY); - secondary = mm_generic_cdma_get_port (cdma, MM_PORT_TYPE_SECONDARY); + MMAtSerialPort *port; - info = mm_generic_cdma_query_reg_state_callback_info_new (cdma, callback, user_data); + info = mm_generic_cdma_query_reg_state_callback_info_new (cdma, cur_cdma_state, cur_evdo_state, callback, user_data); - if (mm_port_get_connected (MM_PORT (primary))) { - if (!secondary) { - info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, - "Cannot get query registration state while connected"); - mm_callback_info_schedule (info); - return; - } - - /* Use secondary port if primary is connected */ - port = secondary; + port = mm_generic_cdma_get_best_at_port (cdma, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; } - mm_serial_port_queue_command (port, "^SYSINFO", 3, sysinfo_done, info); + mm_at_serial_port_queue_command (port, "^SYSINFO", 3, sysinfo_done, info); } /*****************************************************************************/ @@ -265,14 +258,14 @@ grab_port (MMModem *modem, GRegex *regex; port = mm_generic_cdma_grab_port (MM_GENERIC_CDMA (modem), subsys, name, suggested_type, user_data, error); - if (port && MM_IS_SERIAL_PORT (port)) { + if (port && MM_IS_AT_SERIAL_PORT (port)) { gboolean evdo0 = FALSE, evdoA = FALSE; g_object_set (G_OBJECT (port), MM_PORT_CARRIER_DETECT, FALSE, NULL); /* 1x signal level */ regex = g_regex_new ("\\r\\n\\^RSSILVL:(\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, handle_1x_quality_change, modem, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, handle_1x_quality_change, modem, NULL); g_regex_unref (regex); g_object_get (G_OBJECT (modem), @@ -283,7 +276,7 @@ grab_port (MMModem *modem, if (evdo0 || evdoA) { /* EVDO signal level */ regex = g_regex_new ("\\r\\n\\^HRSSILVL:(\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, handle_evdo_quality_change, modem, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, handle_evdo_quality_change, modem, NULL); g_regex_unref (regex); } } diff --git a/plugins/mm-modem-huawei-gsm.c b/plugins/mm-modem-huawei-gsm.c index d450f25..5123e7f 100644 --- a/plugins/mm-modem-huawei-gsm.c +++ b/plugins/mm-modem-huawei-gsm.c @@ -11,38 +11,40 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #include #include #include +#include #define G_UDEV_API_IS_SUBJECT_TO_CHANGE #include #include "mm-modem-huawei-gsm.h" #include "mm-modem-gsm-network.h" +#include "mm-modem-gsm-card.h" #include "mm-errors.h" #include "mm-callback-info.h" -#include "mm-serial-port.h" +#include "mm-at-serial-port.h" #include "mm-serial-parsers.h" static void modem_init (MMModem *modem_class); static void modem_gsm_network_init (MMModemGsmNetwork *gsm_network_class); +static void modem_gsm_card_init (MMModemGsmCard *gsm_card_class); G_DEFINE_TYPE_EXTENDED (MMModemHuaweiGsm, mm_modem_huawei_gsm, MM_TYPE_GENERIC_GSM, 0, G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init) - G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_NETWORK, modem_gsm_network_init)) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_NETWORK, modem_gsm_network_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_CARD, modem_gsm_card_init)) #define MM_MODEM_HUAWEI_GSM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_HUAWEI_GSM, MMModemHuaweiGsmPrivate)) typedef struct { /* Cached state */ - guint signal_quality; - MMModemGsmMode mode; - MMModemGsmBand band; + guint32 band; } MMModemHuaweiGsmPrivate; MMModem * @@ -61,6 +63,61 @@ mm_modem_huawei_gsm_new (const char *device, NULL)); } +/*****************************************************************************/ + +typedef struct { + MMModemGsmBand mm; + guint32 huawei; +} BandTable; + +static BandTable bands[] = { + /* Sort 3G first since it's preferred */ + { MM_MODEM_GSM_BAND_U2100, 0x00400000 }, + { MM_MODEM_GSM_BAND_U1900, 0x00800000 }, + { MM_MODEM_GSM_BAND_U850, 0x04000000 }, + { MM_MODEM_GSM_BAND_U900, 0x00020000 }, + { MM_MODEM_GSM_BAND_G850, 0x00080000 }, + /* 2G second */ + { MM_MODEM_GSM_BAND_DCS, 0x00000080 }, + { MM_MODEM_GSM_BAND_EGSM, 0x00000300 }, /* 0x100 = Extended GSM, 0x200 = Primary GSM */ + { MM_MODEM_GSM_BAND_PCS, 0x00200000 }, + /* And ANY last since it's most inclusive */ + { MM_MODEM_GSM_BAND_ANY, 0x3FFFFFFF }, +}; + +static gboolean +band_mm_to_huawei (MMModemGsmBand band, guint32 *out_huawei) +{ + int i; + + for (i = 0; i < sizeof (bands) / sizeof (BandTable); i++) { + if (bands[i].mm == band) { + *out_huawei = bands[i].huawei; + return TRUE; + } + } + return FALSE; +} + +static gboolean +band_huawei_to_mm (guint32 huawei, MMModemGsmBand *out_mm) +{ + int i; + + for (i = 0; i < sizeof (bands) / sizeof (BandTable); i++) { + /* The dongle returns a bitfield, but since we don't support that + * yet in MM, take the "best" band and return it. + */ + if (bands[i].huawei & huawei) { + *out_mm = bands[i].mm; + return TRUE; + } + } + return FALSE; +} + +/*****************************************************************************/ + static gboolean parse_syscfg (MMModemHuaweiGsm *self, const char *reply, @@ -68,31 +125,31 @@ parse_syscfg (MMModemHuaweiGsm *self, int *mode_b, guint32 *band, int *unknown1, - int *unknown2) + int *unknown2, + MMModemGsmAllowedMode *out_mode) { if (reply == NULL || strncmp (reply, "^SYSCFG:", 8)) return FALSE; if (sscanf (reply + 8, "%d,%d,%x,%d,%d", mode_a, mode_b, band, unknown1, unknown2)) { MMModemHuaweiGsmPrivate *priv = MM_MODEM_HUAWEI_GSM_GET_PRIVATE (self); - + MMModemGsmAllowedMode new_mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; + /* Network mode */ if (*mode_a == 2 && *mode_b == 1) - priv->mode = MM_MODEM_GSM_MODE_2G_PREFERRED; + new_mode = MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED; else if (*mode_a == 2 && *mode_b == 2) - priv->mode = MM_MODEM_GSM_MODE_3G_PREFERRED; + new_mode = MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED; else if (*mode_a == 13 && *mode_b == 1) - priv->mode = MM_MODEM_GSM_MODE_2G_ONLY; + new_mode = MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY; else if (*mode_a == 14 && *mode_b == 2) - priv->mode = MM_MODEM_GSM_MODE_3G_ONLY; + new_mode = MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY; + + if (out_mode) + *out_mode = new_mode; /* Band */ - if (*band == 0x3FFFFFFF) - priv->band = MM_MODEM_GSM_BAND_ANY; - else if (*band == 0x400380) - priv->band = MM_MODEM_GSM_BAND_DCS; - else if (*band == 0x200000) - priv->band = MM_MODEM_GSM_BAND_PCS; + priv->band = *band; return TRUE; } @@ -101,165 +158,108 @@ parse_syscfg (MMModemHuaweiGsm *self, } static void -set_network_mode_done (MMSerialPort *port, +set_allowed_mode_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - MMModemHuaweiGsm *self = MM_MODEM_HUAWEI_GSM (info->modem); - MMModemHuaweiGsmPrivate *priv = MM_MODEM_HUAWEI_GSM_GET_PRIVATE (self); if (error) info->error = g_error_copy (error); - else - /* Success, cache the value */ - priv->mode = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "mode")); mm_callback_info_schedule (info); } static void -set_network_mode_get_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) -{ - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - - if (error) { - info->error = g_error_copy (error); - mm_callback_info_schedule (info); - } else { - int a, b, u1, u2; - guint32 band; - - if (parse_syscfg (MM_MODEM_HUAWEI_GSM (info->modem), response->str, &a, &b, &band, &u1, &u2)) { - char *command; - - switch (GPOINTER_TO_UINT (mm_callback_info_get_data (info, "mode"))) { - case MM_MODEM_GSM_MODE_ANY: - a = 2; - b = 0; - break; - case MM_MODEM_GSM_MODE_GPRS: - case MM_MODEM_GSM_MODE_EDGE: - case MM_MODEM_GSM_MODE_2G_ONLY: - a = 13; - b = 1; - break; - case MM_MODEM_GSM_MODE_UMTS: - case MM_MODEM_GSM_MODE_HSDPA: - case MM_MODEM_GSM_MODE_HSUPA: - case MM_MODEM_GSM_MODE_HSPA: - case MM_MODEM_GSM_MODE_3G_ONLY: - a = 14; - b = 2; - break; - case MM_MODEM_GSM_MODE_2G_PREFERRED: - a = 2; - b = 1; - break; - case MM_MODEM_GSM_MODE_3G_PREFERRED: - a = 2; - b = 2; - break; - default: - break; - } - - command = g_strdup_printf ("AT^SYSCFG=%d,%d,%X,%d,%d", a, b, band, u1, u2); - mm_serial_port_queue_command (port, command, 3, set_network_mode_done, info); - g_free (command); - } - } -} - -static void -set_network_mode (MMModemGsmNetwork *modem, - MMModemGsmMode mode, +set_allowed_mode (MMGenericGsm *gsm, + MMModemGsmAllowedMode mode, MMModemFn callback, gpointer user_data) { MMCallbackInfo *info; - MMSerialPort *primary; + MMAtSerialPort *port; + int a, b; + char *command; - info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + info = mm_callback_info_new (MM_MODEM (gsm), callback, user_data); - switch (mode) { - case MM_MODEM_GSM_MODE_ANY: - case MM_MODEM_GSM_MODE_GPRS: - case MM_MODEM_GSM_MODE_EDGE: - case MM_MODEM_GSM_MODE_UMTS: - case MM_MODEM_GSM_MODE_HSDPA: - case MM_MODEM_GSM_MODE_HSUPA: - case MM_MODEM_GSM_MODE_HSPA: - case MM_MODEM_GSM_MODE_2G_PREFERRED: - case MM_MODEM_GSM_MODE_3G_PREFERRED: - case MM_MODEM_GSM_MODE_2G_ONLY: - case MM_MODEM_GSM_MODE_3G_ONLY: - /* Allowed values */ - mm_callback_info_set_data (info, "mode", GUINT_TO_POINTER (mode), NULL); - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); - g_assert (primary); - mm_serial_port_queue_command (primary, "AT^SYSCFG?", 3, set_network_mode_get_done, info); + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { + mm_callback_info_schedule (info); return; + } + + switch (mode) { + case MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY: + a = 13; + b = 1; + break; + case MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY: + a = 14; + b = 2; + break; + case MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED: + a = 2; + b = 1; + break; + case MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED: + a = 2; + b = 2; + break; + case MM_MODEM_GSM_ALLOWED_MODE_ANY: default: - info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Invalid mode."); + a = 2; + b = 0; break; } - mm_callback_info_schedule (info); + command = g_strdup_printf ("AT^SYSCFG=%d,%d,40000000,2,4", a, b); + mm_at_serial_port_queue_command (port, command, 3, set_allowed_mode_done, info); + g_free (command); } static void -get_network_mode_done (MMSerialPort *port, +get_allowed_mode_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; MMModemHuaweiGsm *self = MM_MODEM_HUAWEI_GSM (info->modem); - MMModemHuaweiGsmPrivate *priv = MM_MODEM_HUAWEI_GSM_GET_PRIVATE (self); int mode_a, mode_b, u1, u2; guint32 band; + MMModemGsmAllowedMode mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; if (error) info->error = g_error_copy (error); - else if (parse_syscfg (self, response->str, &mode_a, &mode_b, &band, &u1, &u2)) - mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->mode), NULL); + else if (parse_syscfg (self, response->str, &mode_a, &mode_b, &band, &u1, &u2, &mode)) + mm_callback_info_set_result (info, GUINT_TO_POINTER (mode), NULL); mm_callback_info_schedule (info); } static void -get_network_mode (MMModemGsmNetwork *modem, +get_allowed_mode (MMGenericGsm *gsm, MMModemUIntFn callback, gpointer user_data) { - MMModemHuaweiGsmPrivate *priv = MM_MODEM_HUAWEI_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + MMAtSerialPort *port; - if (priv->mode != MM_MODEM_GSM_MODE_ANY) { - /* have cached mode (from an unsolicited message). Use that */ - MMCallbackInfo *info; + info = mm_callback_info_uint_new (MM_MODEM (gsm), callback, user_data); - info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->mode), NULL); + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { mm_callback_info_schedule (info); - } else { - /* Get it from modem */ - MMCallbackInfo *info; - MMSerialPort *primary; - - info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); - g_assert (primary); - mm_serial_port_queue_command (primary, "AT^SYSCFG?", 3, get_network_mode_done, info); + return; } + + mm_at_serial_port_queue_command (port, "AT^SYSCFG?", 3, get_allowed_mode_done, info); } static void -set_band_done (MMSerialPort *port, +set_band_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -270,62 +270,14 @@ set_band_done (MMSerialPort *port, if (error) info->error = g_error_copy (error); - else + else { /* Success, cache the value */ priv->band = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "band")); + } mm_callback_info_schedule (info); } -static void -set_band_get_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) -{ - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - MMModemHuaweiGsm *self = MM_MODEM_HUAWEI_GSM (info->modem); - - if (error) { - info->error = g_error_copy (error); - mm_callback_info_schedule (info); - } else { - int a, b, u1, u2; - guint32 band; - - if (parse_syscfg (self, response->str, &a, &b, &band, &u1, &u2)) { - char *command; - - switch (GPOINTER_TO_UINT (mm_callback_info_get_data (info, "band"))) { - case MM_MODEM_GSM_BAND_ANY: - band = 0x3FFFFFFF; - break; - case MM_MODEM_GSM_BAND_EGSM: - band = 0x100; - break; - case MM_MODEM_GSM_BAND_DCS: - band = 0x80; - break; - case MM_MODEM_GSM_BAND_U2100: - band = 0x400000; - break; - case MM_MODEM_GSM_BAND_PCS: - band = 0x200000; - break; - case MM_MODEM_GSM_BAND_G850: - band = 0x80000; - break; - default: - break; - } - - command = g_strdup_printf ("AT^SYSCFG=%d,%d,%X,%d,%d", a, b, band, u1, u2); - mm_serial_port_queue_command (port, command, 3, set_band_done, info); - g_free (command); - } - } -} - static void set_band (MMModemGsmNetwork *modem, MMModemGsmBand band, @@ -333,45 +285,48 @@ set_band (MMModemGsmNetwork *modem, gpointer user_data) { MMCallbackInfo *info; - MMSerialPort *primary; + MMAtSerialPort *port; + char *command; + guint32 huawei_band = 0x3FFFFFFF; info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); - switch (band) { - case MM_MODEM_GSM_BAND_ANY: - case MM_MODEM_GSM_BAND_EGSM: - case MM_MODEM_GSM_BAND_DCS: - case MM_MODEM_GSM_BAND_U2100: - case MM_MODEM_GSM_BAND_PCS: - mm_callback_info_set_data (info, "band", GUINT_TO_POINTER (band), NULL); - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); - g_assert (primary); - mm_serial_port_queue_command (primary, "AT^SYSCFG?", 3, set_band_get_done, info); + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); return; - default: - info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Invalid band."); - break; } - mm_callback_info_schedule (info); + if (!band_mm_to_huawei (band, &huawei_band)) { + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Invalid band."); + mm_callback_info_schedule (info); + } else { + mm_callback_info_set_data (info, "band", GUINT_TO_POINTER (huawei_band), NULL); + command = g_strdup_printf ("AT^SYSCFG=16,3,%X,2,4", huawei_band); + mm_at_serial_port_queue_command (port, command, 3, set_band_done, info); + g_free (command); + } } static void -get_band_done (MMSerialPort *port, +get_band_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; MMModemHuaweiGsm *self = MM_MODEM_HUAWEI_GSM (info->modem); - MMModemHuaweiGsmPrivate *priv = MM_MODEM_HUAWEI_GSM_GET_PRIVATE (self); int mode_a, mode_b, u1, u2; guint32 band; if (error) info->error = g_error_copy (error); - else if (parse_syscfg (self, response->str, &mode_a, &mode_b, &band, &u1, &u2)) - mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->band), NULL); + else if (parse_syscfg (self, response->str, &mode_a, &mode_b, &band, &u1, &u2, NULL)) { + MMModemGsmBand mm_band = MM_MODEM_GSM_BAND_ANY; + + band_huawei_to_mm (band, &mm_band); + mm_callback_info_set_result (info, GUINT_TO_POINTER (mm_band), NULL); + } mm_callback_info_schedule (info); } @@ -382,84 +337,289 @@ get_band (MMModemGsmNetwork *modem, gpointer user_data) { MMModemHuaweiGsmPrivate *priv = MM_MODEM_HUAWEI_GSM_GET_PRIVATE (modem); - MMSerialPort *primary; + MMAtSerialPort *port; + MMCallbackInfo *info; + + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - if (priv->band != MM_MODEM_GSM_BAND_ANY) { - /* have cached mode (from an unsolicited message). Use that */ - MMCallbackInfo *info; + /* Prefer cached band from unsolicited messages if we have it */ + if (priv->band != 0) { + MMModemGsmBand mm_band = MM_MODEM_GSM_BAND_ANY; - info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->band), NULL); + band_huawei_to_mm (priv->band, &mm_band); + mm_callback_info_set_result (info, GUINT_TO_POINTER (mm_band), NULL); mm_callback_info_schedule (info); - } else { - /* Get it from modem */ - MMCallbackInfo *info; + return; + } - info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); - g_assert (primary); - mm_serial_port_queue_command (primary, "AT^SYSCFG?", 3, get_band_done, info); + /* Otherwise ask the modem */ + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; } + + mm_at_serial_port_queue_command (port, "AT^SYSCFG?", 3, get_band_done, info); } static void -get_signal_quality (MMModemGsmNetwork *modem, +get_act_request_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + GRegex *r = NULL; + GMatchInfo *match_info = NULL; + char *str; + int srv_stat = 0; + + if (error) { + info->error = g_error_copy (error); + goto done; + } + + /* Can't just use \d here since sometimes you get "^SYSINFO:2,1,0,3,1,,3" */ + r = g_regex_new ("\\^SYSINFO:\\s*(\\d?),(\\d?),(\\d?),(\\d?),(\\d?),(\\d?),(\\d?)$", G_REGEX_UNGREEDY, 0, NULL); + if (!r) { + g_set_error_literal (&info->error, + MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Could not parse ^SYSINFO results."); + goto done; + } + + if (!g_regex_match_full (r, response->str, response->len, 0, 0, &match_info, &info->error)) { + g_set_error_literal (&info->error, + MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Could not parse ^SYSINFO results."); + goto done; + } + + str = g_match_info_fetch (match_info, 1); + if (str && strlen (str)) + srv_stat = atoi (str); + g_free (str); + + if (srv_stat != 0) { + /* Valid service */ + str = g_match_info_fetch (match_info, 7); + if (str && strlen (str)) { + if (str[0] == '1') + act = MM_MODEM_GSM_ACCESS_TECH_GSM; + else if (str[0] == '2') + act = MM_MODEM_GSM_ACCESS_TECH_GPRS; + else if (str[0] == '3') + act = MM_MODEM_GSM_ACCESS_TECH_EDGE; + else if (str[0] == '4') + act = MM_MODEM_GSM_ACCESS_TECH_UMTS; + else if (str[0] == '5') + act = MM_MODEM_GSM_ACCESS_TECH_HSDPA; + else if (str[0] == '6') + act = MM_MODEM_GSM_ACCESS_TECH_HSUPA; + else if (str[0] == '7') + act = MM_MODEM_GSM_ACCESS_TECH_HSPA; + } + g_free (str); + } + +done: + if (match_info) + g_match_info_free (match_info); + if (r) + g_regex_unref (r); + mm_callback_info_set_result (info, GUINT_TO_POINTER (act), NULL); + mm_callback_info_schedule (info); +} + +static void +get_access_technology (MMGenericGsm *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMAtSerialPort *port; + MMCallbackInfo *info; + + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (modem, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "^SYSINFO", 3, get_act_request_done, info); +} + +/*****************************************************************************/ + +static gboolean +parse_num (const char *str, guint32 *out_num, guint32 min, guint32 max) +{ + unsigned long int tmp; + + if (!str || !strlen (str)) + return FALSE; + + errno = 0; + tmp = strtoul (str, NULL, 10); + if (errno != 0 || tmp < min || tmp > max) + return FALSE; + *out_num = (guint32) tmp; + return TRUE; +} + +static void +send_huawei_cpin_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GRegex *r = NULL; + GMatchInfo *match_info = NULL; + const char *pin_type; + guint32 attempts_left = 0; + char *str = NULL; + guint32 num = 0; + + if (error) { + info->error = g_error_copy (error); + goto done; + } + + pin_type = mm_callback_info_get_data (info, "pin_type"); + + r = g_regex_new ("\\^CPIN:\\s*([^,]+),[^,]*,(\\d+),(\\d+),(\\d+),(\\d+)", G_REGEX_UNGREEDY, 0, NULL); + if (!r) { + g_set_error_literal (&info->error, + MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Could not parse ^CPIN results (error creating regex)."); + goto done; + } + + if (!g_regex_match_full (r, response->str, response->len, 0, 0, &match_info, &info->error)) { + g_set_error_literal (&info->error, + MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Could not parse ^CPIN results (match failed)."); + goto done; + } + + if (strstr (pin_type, MM_MODEM_GSM_CARD_SIM_PUK)) + num = 2; + else if (strstr (pin_type, MM_MODEM_GSM_CARD_SIM_PIN)) + num = 3; + else if (strstr (pin_type, MM_MODEM_GSM_CARD_SIM_PUK2)) + num = 4; + else if (strstr (pin_type, MM_MODEM_GSM_CARD_SIM_PIN2)) + num = 5; + else { + g_debug ("%s: unhandled pin type '%s'", __func__, pin_type); + + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Unhandled PIN type"); + } + + if (num > 0) { + gboolean success = FALSE; + + str = g_match_info_fetch (match_info, num); + if (str) { + success = parse_num (str, &attempts_left, 0, 10); + g_free (str); + } + + if (!success) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Could not parse ^CPIN results (missing or invalid match info)."); + } + } + + mm_callback_info_set_result (info, GUINT_TO_POINTER (attempts_left), NULL); + + g_match_info_free (match_info); + +done: + if (r) + g_regex_unref (r); + mm_serial_port_close (MM_SERIAL_PORT (port)); + mm_callback_info_schedule (info); +} + +static void +get_unlock_retries (MMModemGsmCard *modem, + const char *pin_type, MMModemUIntFn callback, gpointer user_data) { - MMModemHuaweiGsmPrivate *priv = MM_MODEM_HUAWEI_GSM_GET_PRIVATE (modem); + MMAtSerialPort *port; + char *command; + MMCallbackInfo *info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - if (priv->signal_quality) { - /* have cached signal quality (from an unsolicited message). Use that */ - MMCallbackInfo *info; + g_debug ("%s: pin type '%s'", __func__, pin_type); - info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->signal_quality), NULL); + /* Ensure we have a usable port to use for the command */ + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (!port) { mm_callback_info_schedule (info); - } else { - /* Use the generic implementation */ - MMModemGsmNetwork *parent_gsm_network_iface; + return; + } - parent_gsm_network_iface = g_type_interface_peek_parent (MM_MODEM_GSM_NETWORK_GET_INTERFACE (modem)); - parent_gsm_network_iface->get_signal_quality (modem, callback, user_data); + /* Modem may not be enabled yet, which sometimes can't be done until + * the device has been unlocked. In this case we have to open the port + * ourselves. + */ + if (!mm_serial_port_open (MM_SERIAL_PORT (port), &info->error)) { + mm_callback_info_schedule (info); + return; } + + /* if the modem have not yet been enabled we need to make sure echoing is turned off */ + command = g_strdup_printf ("E0"); + mm_at_serial_port_queue_command (port, command, 3, NULL, NULL); + g_free (command); + + mm_callback_info_set_data (info, "pin_type", g_strdup (pin_type), g_free); + + command = g_strdup_printf ("^CPIN?"); + mm_at_serial_port_queue_command (port, command, 3, send_huawei_cpin_done, info); + g_free (command); } +/*****************************************************************************/ /* Unsolicited message handlers */ static void -handle_signal_quality_change (MMSerialPort *port, +handle_signal_quality_change (MMAtSerialPort *port, GMatchInfo *match_info, gpointer user_data) { MMModemHuaweiGsm *self = MM_MODEM_HUAWEI_GSM (user_data); - MMModemHuaweiGsmPrivate *priv = MM_MODEM_HUAWEI_GSM_GET_PRIVATE (self); char *str; - int quality; + int quality = 0; str = g_match_info_fetch (match_info, 1); quality = atoi (str); g_free (str); - if (quality == 99) + if (quality == 99) { /* 99 means unknown */ quality = 0; - else + } else { /* Normalize the quality */ - quality = quality * 100 / 31; + quality = CLAMP (quality, 0, 31) * 100 / 31; + } - g_debug ("Signal quality: %d", quality); - priv->signal_quality = (guint32) quality; - mm_modem_gsm_network_signal_quality (MM_MODEM_GSM_NETWORK (self), (guint32) quality); + mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (self), (guint32) quality); } static void -handle_mode_change (MMSerialPort *port, +handle_mode_change (MMAtSerialPort *port, GMatchInfo *match_info, gpointer user_data) { MMModemHuaweiGsm *self = MM_MODEM_HUAWEI_GSM (user_data); - MMModemHuaweiGsmPrivate *priv = MM_MODEM_HUAWEI_GSM_GET_PRIVATE (self); + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; char *str; int a; int b; @@ -472,29 +632,35 @@ handle_mode_change (MMSerialPort *port, b = atoi (str); g_free (str); - if (a == 3 && b == 2) - priv->mode = MM_MODEM_GSM_MODE_GPRS; - else if (a == 3 && b == 3) - priv->mode = MM_MODEM_GSM_MODE_EDGE; - else if (a == 5 && b == 4) - priv->mode = MM_MODEM_GSM_MODE_UMTS; - else if (a == 5 && b == 5) - priv->mode = MM_MODEM_GSM_MODE_HSDPA; - else if (a == 5 && b == 6) - priv->mode = MM_MODEM_GSM_MODE_HSUPA; - else if (a == 5 && b == 7) - priv->mode = MM_MODEM_GSM_MODE_HSPA; + if (a == 3) { /* GSM/GPRS mode */ + if (b == 1) + act = MM_MODEM_GSM_ACCESS_TECH_GSM; + else if (b == 2) + act = MM_MODEM_GSM_ACCESS_TECH_GPRS; + else if (b == 3) + act = MM_MODEM_GSM_ACCESS_TECH_EDGE; + } else if (a == 5) { /* WCDMA mode */ + if (b == 4) + act = MM_MODEM_GSM_ACCESS_TECH_UMTS; + else if (b == 5) + act = MM_MODEM_GSM_ACCESS_TECH_HSDPA; + else if (b == 6) + act = MM_MODEM_GSM_ACCESS_TECH_HSUPA; + else if (b == 7) + act = MM_MODEM_GSM_ACCESS_TECH_HSPA; + } else if (a == 0) + act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; else { g_warning ("Couldn't parse mode change value: '%s'", str); return; } - g_debug ("Mode: %d", priv->mode); - mm_modem_gsm_network_mode (MM_MODEM_GSM_NETWORK (self), priv->mode); + g_debug ("Access Technology: %d", act); + mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (self), act); } static void -handle_status_change (MMSerialPort *port, +handle_status_change (MMAtSerialPort *port, GMatchInfo *match_info, gpointer user_data) { @@ -546,38 +712,35 @@ grab_port (MMModem *modem, } if (usbif == 0) { - if (!mm_generic_gsm_get_port (gsm, MM_PORT_TYPE_PRIMARY)) + if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY)) ptype = MM_PORT_TYPE_PRIMARY; } else if (suggested_type == MM_PORT_TYPE_SECONDARY) { - if (!mm_generic_gsm_get_port (gsm, MM_PORT_TYPE_SECONDARY)) + if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_SECONDARY)) ptype = MM_PORT_TYPE_SECONDARY; } port = mm_generic_gsm_grab_port (gsm, subsys, name, ptype, error); - if (port && MM_IS_SERIAL_PORT (port)) { - g_object_set (G_OBJECT (port), MM_PORT_CARRIER_DETECT, FALSE, NULL); - if (ptype == MM_PORT_TYPE_SECONDARY) { - GRegex *regex; + if (port && MM_IS_AT_SERIAL_PORT (port)) { + GRegex *regex; - mm_generic_gsm_set_unsolicited_registration (MM_GENERIC_GSM (modem), TRUE); + g_object_set (G_OBJECT (port), MM_PORT_CARRIER_DETECT, FALSE, NULL); - regex = g_regex_new ("\\r\\n\\^RSSI:(\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, handle_signal_quality_change, modem, NULL); - g_regex_unref (regex); + regex = g_regex_new ("\\r\\n\\^RSSI:(\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, handle_signal_quality_change, modem, NULL); + g_regex_unref (regex); - regex = g_regex_new ("\\r\\n\\^MODE:(\\d),(\\d)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, handle_mode_change, modem, NULL); - g_regex_unref (regex); + regex = g_regex_new ("\\r\\n\\^MODE:(\\d),(\\d)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, handle_mode_change, modem, NULL); + g_regex_unref (regex); - regex = g_regex_new ("\\r\\n\\^DSFLOWRPT:(.+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, handle_status_change, modem, NULL); - g_regex_unref (regex); + regex = g_regex_new ("\\r\\n\\^DSFLOWRPT:(.+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, handle_status_change, modem, NULL); + g_regex_unref (regex); - regex = g_regex_new ("\\r\\n\\^BOOT:.+\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, modem, NULL); - g_regex_unref (regex); - } + regex = g_regex_new ("\\r\\n\\^BOOT:.+\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, modem, NULL); + g_regex_unref (regex); } out: @@ -598,11 +761,14 @@ modem_init (MMModem *modem_class) static void modem_gsm_network_init (MMModemGsmNetwork *class) { - class->set_network_mode = set_network_mode; - class->get_network_mode = get_network_mode; class->set_band = set_band; class->get_band = get_band; - class->get_signal_quality = get_signal_quality; +} + +static void +modem_gsm_card_init (MMModemGsmCard *class) +{ + class->get_unlock_retries = get_unlock_retries; } static void @@ -614,8 +780,13 @@ static void mm_modem_huawei_gsm_class_init (MMModemHuaweiGsmClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMGenericGsmClass *gsm_class = MM_GENERIC_GSM_CLASS (klass); mm_modem_huawei_gsm_parent_class = g_type_class_peek_parent (klass); g_type_class_add_private (object_class, sizeof (MMModemHuaweiGsmPrivate)); + + gsm_class->set_allowed_mode = set_allowed_mode; + gsm_class->get_allowed_mode = get_allowed_mode; + gsm_class->get_access_technology = get_access_technology; } diff --git a/plugins/mm-modem-longcheer-gsm.c b/plugins/mm-modem-longcheer-gsm.c new file mode 100644 index 0000000..62980f7 --- /dev/null +++ b/plugins/mm-modem-longcheer-gsm.c @@ -0,0 +1,222 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include "mm-modem-longcheer-gsm.h" +#include "mm-at-serial-port.h" +#include "mm-errors.h" +#include "mm-callback-info.h" +#include "mm-modem-helpers.h" + +G_DEFINE_TYPE (MMModemLongcheerGsm, mm_modem_longcheer_gsm, MM_TYPE_GENERIC_GSM) + +MMModem * +mm_modem_longcheer_gsm_new (const char *device, + const char *driver, + const char *plugin) +{ + g_return_val_if_fail (device != NULL, NULL); + g_return_val_if_fail (driver != NULL, NULL); + g_return_val_if_fail (plugin != NULL, NULL); + + return MM_MODEM (g_object_new (MM_TYPE_MODEM_LONGCHEER_GSM, + MM_MODEM_MASTER_DEVICE, device, + MM_MODEM_DRIVER, driver, + MM_MODEM_PLUGIN, plugin, + NULL)); +} + +/*****************************************************************************/ + +static void +get_allowed_mode_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *p; + MMModemGsmAllowedMode mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; + gint mododr = -1; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) + goto done; + + p = mm_strip_tag (response->str, "+MODODR:"); + if (!p) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Failed to parse the allowed mode response"); + goto done; + } + + mododr = atoi (p); + switch (mododr) { + case 1: + mode = MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY; + break; + case 2: + case 4: + mode = MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED; + break; + case 3: + mode = MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY; + break; + default: + break; + } + mm_callback_info_set_result (info, GUINT_TO_POINTER (mode), NULL); + +done: + mm_callback_info_schedule (info); +} + +static void +get_allowed_mode (MMGenericGsm *gsm, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *port; + + info = mm_callback_info_uint_new (MM_MODEM (gsm), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "AT+MODODR?", 3, get_allowed_mode_done, info); +} + +static void +set_allowed_mode_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) + info->error = g_error_copy (error); + + mm_callback_info_schedule (info); +} + +static void +set_allowed_mode (MMGenericGsm *gsm, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *port; + char *command; + int mododr = 0; + + info = mm_callback_info_new (MM_MODEM (gsm), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + switch (mode) { + case MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY: + mododr = 3; + break; + case MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY: + mododr = 1; + break; + case MM_MODEM_GSM_ALLOWED_MODE_ANY: + case MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED: + case MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED: + default: + mododr = 2; + break; + } + + command = g_strdup_printf ("+MODODR=%d", mododr); + mm_at_serial_port_queue_command (port, command, 3, set_allowed_mode_done, info); + g_free (command); +} + +static void +get_act_request_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + const char *p; + + if (error) + info->error = g_error_copy (error); + else { + p = mm_strip_tag (response->str, "+PSRAT:"); + act = mm_gsm_string_to_access_tech (p); + } + + mm_callback_info_set_result (info, GUINT_TO_POINTER (act), NULL); + mm_callback_info_schedule (info); +} + +static void +get_access_technology (MMGenericGsm *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMAtSerialPort *port; + MMCallbackInfo *info; + + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (modem, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "+PSRAT", 3, get_act_request_done, info); +} + +/*****************************************************************************/ + +static void +mm_modem_longcheer_gsm_init (MMModemLongcheerGsm *self) +{ +} + +static void +mm_modem_longcheer_gsm_class_init (MMModemLongcheerGsmClass *klass) +{ + MMGenericGsmClass *gsm_class = MM_GENERIC_GSM_CLASS (klass); + + mm_modem_longcheer_gsm_parent_class = g_type_class_peek_parent (klass); + + gsm_class->set_allowed_mode = set_allowed_mode; + gsm_class->get_allowed_mode = get_allowed_mode; + gsm_class->get_access_technology = get_access_technology; +} + diff --git a/plugins/mm-modem-longcheer-gsm.h b/plugins/mm-modem-longcheer-gsm.h new file mode 100644 index 0000000..5383c52 --- /dev/null +++ b/plugins/mm-modem-longcheer-gsm.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#ifndef MM_MODEM_LONGCHEER_GSM_H +#define MM_MODEM_LONGCHEER_GSM_H + +#include "mm-generic-gsm.h" + +#define MM_TYPE_MODEM_LONGCHEER_GSM (mm_modem_longcheer_gsm_get_type ()) +#define MM_MODEM_LONGCHEER_GSM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_LONGCHEER_GSM, MMModemLongcheerGsm)) +#define MM_MODEM_LONGCHEER_GSM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_MODEM_LONGCHEER_GSM, MMModemLongcheerGsmClass)) +#define MM_IS_MODEM_LONGCHEER_GSM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_LONGCHEER_GSM)) +#define MM_IS_MODEM_LONGCHEER_GSM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_MODEM_LONGCHEER_GSM)) +#define MM_MODEM_LONGCHEER_GSM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_MODEM_LONGCHEER_GSM, MMModemLongcheerGsmClass)) + +typedef struct { + MMGenericGsm parent; +} MMModemLongcheerGsm; + +typedef struct { + MMGenericGsmClass parent; +} MMModemLongcheerGsmClass; + +GType mm_modem_longcheer_gsm_get_type (void); + +MMModem *mm_modem_longcheer_gsm_new (const char *device, + const char *driver, + const char *plugin); + +#endif /* MM_MODEM_LONGCHEER_H */ diff --git a/plugins/mm-modem-mbm.c b/plugins/mm-modem-mbm.c index 686b35c..7f6bc9c 100644 --- a/plugins/mm-modem-mbm.c +++ b/plugins/mm-modem-mbm.c @@ -1,11 +1,13 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* - * Copyright (C) 2008 Ericsson AB + * Copyright (C) 2008 - 2010 Ericsson AB + * Copyright (C) 2009 - 2010 Red Hat, Inc. * * Author: Per Hallsmark * Bjorn Runaker * Torgny Johansson * Jonas Sjöquist + * Dan Williams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +19,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * */ #include @@ -30,17 +28,20 @@ #include "mm-modem-mbm.h" #include "mm-modem-simple.h" +#include "mm-modem-gsm-card.h" #include "mm-errors.h" #include "mm-callback-info.h" static void modem_init (MMModem *modem_class); static void modem_gsm_network_init (MMModemGsmNetwork *gsm_network_class); static void modem_simple_init (MMModemSimple *class); +static void modem_gsm_card_init (MMModemGsmCard *class); G_DEFINE_TYPE_EXTENDED (MMModemMbm, mm_modem_mbm, MM_TYPE_GENERIC_GSM, 0, G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init) G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_NETWORK, modem_gsm_network_init) - G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_SIMPLE, modem_simple_init)) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_SIMPLE, modem_simple_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_CARD, modem_gsm_card_init)) #define MM_MODEM_MBM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_MBM, MMModemMbmPrivate)) @@ -54,11 +55,6 @@ G_DEFINE_TYPE_EXTENDED (MMModemMbm, mm_modem_mbm, MM_TYPE_GENERIC_GSM, 0, #define MBM_NETWORK_MODE_2G 5 #define MBM_NETWORK_MODE_3G 6 -#define MBM_ERINFO_2G_GPRS 1 -#define MBM_ERINFO_2G_EGPRS 2 -#define MBM_ERINFO_3G_UMTS 1 -#define MBM_ERINFO_3G_HSDPA 2 - typedef struct { guint reg_id; gboolean have_emrdy; @@ -83,12 +79,6 @@ mbm_modem_authenticate (MMModemMbm *self, const char *password, gpointer user_data); -static const char * -mbm_simple_get_string_property (GHashTable *properties, const char *name, GError **error); - -static uint -mbm_simple_get_uint_property (GHashTable *properties, const char *name, GError **error); - MMModem * mm_modem_mbm_new (const char *device, const char *driver, @@ -124,16 +114,9 @@ register_done (gpointer user_data) MMModemMbm *self = MM_MODEM_MBM (reg_data->modem); MMModemMbmPrivate *priv = MM_MODEM_MBM_GET_PRIVATE (self); MMModemGsmNetwork *parent_modem_iface; - MMSerialPort *primary; priv->reg_id = 0; - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (self), MM_PORT_TYPE_PRIMARY); - g_assert (primary); - - mm_serial_port_queue_command (primary, "+CREG=1", 3, NULL, NULL); - mm_serial_port_queue_command (primary, "+CMER=3,0,0,1", 3, NULL, NULL); - parent_modem_iface = g_type_interface_peek_parent (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)); parent_modem_iface->do_register (MM_MODEM_GSM_NETWORK (self), reg_data->network_id, @@ -167,21 +150,16 @@ do_register (MMModemGsmNetwork *modem, } static int -mbm_parse_network_mode (MMModemGsmMode network_mode) +mbm_parse_allowed_mode (MMModemGsmAllowedMode network_mode) { switch (network_mode) { - case MM_MODEM_GSM_MODE_ANY: - case MM_MODEM_GSM_MODE_3G_PREFERRED: - case MM_MODEM_GSM_MODE_2G_PREFERRED: + case MM_MODEM_GSM_ALLOWED_MODE_ANY: + case MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED: + case MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED: return MBM_NETWORK_MODE_ANY; - case MM_MODEM_GSM_MODE_GPRS: - case MM_MODEM_GSM_MODE_EDGE: - case MM_MODEM_GSM_MODE_2G_ONLY: + case MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY: return MBM_NETWORK_MODE_2G; - case MM_MODEM_GSM_MODE_3G_ONLY: - case MM_MODEM_GSM_MODE_HSDPA: - case MM_MODEM_GSM_MODE_HSUPA: - case MM_MODEM_GSM_MODE_HSPA: + case MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY: return MBM_NETWORK_MODE_3G; default: return MBM_NETWORK_MODE_ANY; @@ -189,7 +167,7 @@ mbm_parse_network_mode (MMModemGsmMode network_mode) } static void -mbm_set_network_mode_done (MMSerialPort *port, +mbm_set_allowed_mode_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -203,92 +181,144 @@ mbm_set_network_mode_done (MMSerialPort *port, } static void -set_network_mode (MMModemGsmNetwork *modem, - MMModemGsmMode mode, +set_allowed_mode (MMGenericGsm *gsm, + MMModemGsmAllowedMode mode, MMModemFn callback, gpointer user_data) { MMCallbackInfo *info; char *command; - MMSerialPort *primary; + MMAtSerialPort *port; - info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); - g_assert (primary); + info = mm_callback_info_new (MM_MODEM (gsm), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } - command = g_strdup_printf ("+CFUN=%d", mbm_parse_network_mode (mode)); - mm_serial_port_queue_command (primary, command, 3, mbm_set_network_mode_done, info); + command = g_strdup_printf ("+CFUN=%d", mbm_parse_allowed_mode (mode)); + mm_at_serial_port_queue_command (port, command, 3, mbm_set_allowed_mode_done, info); g_free (command); } static void -get_network_mode_done (MMSerialPort *port, +mbm_erinfo_received (MMAtSerialPort *port, + GMatchInfo *info, + gpointer user_data) +{ + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + char *str; + + str = g_match_info_fetch (info, 2); + if (str) { + switch (atoi (str)) { + case 1: + act = MM_MODEM_GSM_ACCESS_TECH_GPRS; + break; + case 2: + act = MM_MODEM_GSM_ACCESS_TECH_EDGE; + break; + default: + break; + } + } + g_free (str); + + /* 3G modes take precedence */ + str = g_match_info_fetch (info, 3); + if (str) { + switch (atoi (str)) { + case 1: + act = MM_MODEM_GSM_ACCESS_TECH_UMTS; + break; + case 2: + act = MM_MODEM_GSM_ACCESS_TECH_HSDPA; + break; + default: + break; + } + } + g_free (str); + + mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (user_data), act); +} + +static void +get_allowed_mode_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - char *erinfo; - int mode = 0, gsm = 0, umts = 0; gboolean parsed = FALSE; - if (error) { + if (error) info->error = g_error_copy (error); - goto done; - } - - erinfo = strstr (response->str, "*ERINFO:"); - if (!erinfo) - goto done; - - if (sscanf (erinfo + 8, "%d,%d,%d", &mode, &gsm, &umts) != 3) - goto done; + else if (!g_str_has_prefix (response->str, "CFUN: ")) { + MMModemGsmAllowedMode mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; + int a; - if (gsm || umts) { - MMModemGsmMode mm_mode = MM_MODEM_GSM_MODE_ANY; - - if (gsm == MBM_ERINFO_2G_GPRS) - mm_mode = MM_MODEM_GSM_MODE_GPRS; - else if (gsm == MBM_ERINFO_2G_EGPRS) - mm_mode = MM_MODEM_GSM_MODE_EDGE; - else if (umts == MBM_ERINFO_3G_UMTS) - mm_mode = MM_MODEM_GSM_MODE_UMTS; - else if (umts == MBM_ERINFO_3G_HSDPA) - mm_mode = MM_MODEM_GSM_MODE_HSDPA; - else - g_debug ("%s unknown network mode %d,%d", __FUNCTION__, gsm, umts); + a = atoi (response->str + 6); + if (a == MBM_NETWORK_MODE_2G) + mode = MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY; + else if (a == MBM_NETWORK_MODE_3G) + mode = MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY; - mm_callback_info_set_result (info, GUINT_TO_POINTER (mm_mode), NULL); + mm_callback_info_set_result (info, GUINT_TO_POINTER (mode), NULL); parsed = TRUE; } -done: - if (!error && !parsed) { + if (!error && !parsed) info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, - "Could not parse network mode results"); - } + "Could not parse allowed mode results"); mm_callback_info_schedule (info); } static void -get_network_mode (MMModemGsmNetwork *modem, +get_allowed_mode (MMGenericGsm *gsm, MMModemUIntFn callback, gpointer user_data) { MMCallbackInfo *info; - MMSerialPort *primary; + MMAtSerialPort *port; - info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); - g_assert (primary); - mm_serial_port_queue_command (primary, "*ERINFO?", 3, get_network_mode_done, info); + info = mm_callback_info_uint_new (MM_MODEM (gsm), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "+CFUN?", 3, get_allowed_mode_done, info); } /*****************************************************************************/ /* Simple Modem class override functions */ /*****************************************************************************/ +static const char * +mbm_simple_get_string_property (GHashTable *properties, const char *name, GError **error) +{ + GValue *value; + + value = (GValue *) g_hash_table_lookup (properties, name); + if (!value) + return NULL; + + if (G_VALUE_HOLDS_STRING (value)) + return g_value_get_string (value); + + g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Invalid property type for '%s': %s (string expected)", + name, G_VALUE_TYPE_NAME (value)); + + return NULL; +} + static void simple_connect (MMModemSimple *simple, GHashTable *properties, @@ -298,14 +328,10 @@ simple_connect (MMModemSimple *simple, MMModemMbmPrivate *priv = MM_MODEM_MBM_GET_PRIVATE (simple); MMCallbackInfo *info = (MMCallbackInfo *) user_data; MMModemSimple *parent_iface; - uint network_mode = 0; priv->username = mbm_simple_get_string_property (properties, "username", &info->error); priv->password = mbm_simple_get_string_property (properties, "password", &info->error); - network_mode = mbm_simple_get_uint_property (properties, "network_mode", &info->error); - priv->network_mode = mbm_parse_network_mode (network_mode); - parent_iface = g_type_interface_peek_parent (MM_MODEM_SIMPLE_GET_INTERFACE (simple)); parent_iface->connect (MM_MODEM_SIMPLE (simple), properties, callback, info); } @@ -315,18 +341,22 @@ simple_connect (MMModemSimple *simple, /*****************************************************************************/ static void -mbm_enable_done (MMSerialPort *port, +mbm_enable_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; + /* Start unsolicited signal strength and access technology responses */ + mm_at_serial_port_queue_command (port, "+CMER=3,0,0,1", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "*ERINFO=1", 3, NULL, NULL); + mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); } static void -mbm_enap0_done (MMSerialPort *port, +mbm_enap0_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -339,12 +369,12 @@ mbm_enap0_done (MMSerialPort *port, priv->network_mode = MBM_NETWORK_MODE_ANY; command = g_strdup_printf ("+CFUN=%d", priv->network_mode); - mm_serial_port_queue_command (port, command, 3, mbm_enable_done, info); + mm_at_serial_port_queue_command (port, command, 3, mbm_enable_done, info); g_free (command); } static void -mbm_init_done (MMSerialPort *port, +mbm_init_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -360,17 +390,17 @@ mbm_init_done (MMSerialPort *port, if (!priv->network_mode) priv->network_mode = MBM_NETWORK_MODE_ANY; - mm_serial_port_queue_command (port, "*ENAP=0", 3, mbm_enap0_done, info); + mm_at_serial_port_queue_command (port, "*ENAP=0", 3, mbm_enap0_done, info); } static void -do_init (MMSerialPort *port, MMCallbackInfo *info) +do_init (MMAtSerialPort *port, MMCallbackInfo *info) { - mm_serial_port_queue_command (port, "&F E0 V1 X4 &C1 +CMEE=1", 3, mbm_init_done, info); + mm_at_serial_port_queue_command (port, "&F E0 V1 X4 &C1 +CMEE=1", 3, mbm_init_done, info); } static void -mbm_emrdy_done (MMSerialPort *port, +mbm_emrdy_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -378,11 +408,9 @@ mbm_emrdy_done (MMSerialPort *port, MMCallbackInfo *info = user_data; MMModemMbmPrivate *priv = MM_MODEM_MBM_GET_PRIVATE (info->modem); - if ( error - && error->domain == MM_SERIAL_ERROR - && error->code == MM_SERIAL_RESPONSE_TIMEOUT) { + if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) g_warning ("%s: timed out waiting for EMRDY response.", __func__); - } else + else priv->have_emrdy = TRUE; do_init (port, info); @@ -393,18 +421,18 @@ do_enable (MMGenericGsm *self, MMModemFn callback, gpointer user_data) { MMModemMbmPrivate *priv = MM_MODEM_MBM_GET_PRIVATE (self); MMCallbackInfo *info; - MMSerialPort *primary; + MMAtSerialPort *primary; info = mm_callback_info_new (MM_MODEM (self), callback, user_data); - primary = mm_generic_gsm_get_port (self, MM_PORT_TYPE_PRIMARY); + primary = mm_generic_gsm_get_at_port (self, MM_PORT_TYPE_PRIMARY); g_assert (primary); if (priv->have_emrdy) { /* Modem is ready, no need to check EMRDY */ do_init (primary, info); } else - mm_serial_port_queue_command (primary, "*EMRDY?", 5, mbm_emrdy_done, info); + mm_at_serial_port_queue_command (primary, "*EMRDY?", 5, mbm_emrdy_done, info); } typedef struct { @@ -414,10 +442,10 @@ typedef struct { } DisableInfo; static void -disable_creg_cmer_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +disable_unsolicited_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { MMModem *parent_modem_iface; @@ -433,7 +461,7 @@ disable (MMModem *modem, MMModemFn callback, gpointer user_data) { - MMSerialPort *primary; + MMAtSerialPort *primary; DisableInfo *info; info = g_malloc0 (sizeof (DisableInfo)); @@ -441,11 +469,11 @@ disable (MMModem *modem, info->user_data = user_data; info->modem = modem; - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); + primary = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); g_assert (primary); - /* Turn off unsolicited +CIEV signal strength indicator */ - mm_serial_port_queue_command (primary, "+CREG=0;+CMER=0", 5, disable_creg_cmer_done, info); + /* Turn off unsolicited responses */ + mm_at_serial_port_queue_command (primary, "+CMER=0;*ERINFO=0", 5, disable_unsolicited_done, info); } static void @@ -466,29 +494,77 @@ do_connect (MMModem *modem, } static void -disconnect (MMModem *modem, - MMModemFn callback, - gpointer user_data) +do_disconnect (MMGenericGsm *gsm, + gint cid, + MMModemFn callback, + gpointer user_data) { - MMCallbackInfo *info; - MMSerialPort *primary; - - mm_modem_set_state (modem, MM_MODEM_STATE_DISCONNECTING, MM_MODEM_STATE_REASON_NONE); + MMAtSerialPort *primary; - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); + primary = mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY); g_assert (primary); - mm_serial_port_queue_command (primary, "*ENAP=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (primary, "*ENAP=0", 3, NULL, NULL); - mm_generic_gsm_update_enabled_state (MM_GENERIC_GSM (modem), FALSE, MM_MODEM_STATE_REASON_NONE); + MM_GENERIC_GSM_CLASS (mm_modem_mbm_parent_class)->do_disconnect (gsm, cid, callback, user_data); +} - info = mm_callback_info_new (modem, callback, user_data); +static void +factory_reset_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + mm_serial_port_close (MM_SERIAL_PORT (port)); mm_callback_info_schedule (info); } +static void +factory_reset (MMModem *self, + const char *code, + MMModemFn callback, + gpointer user_data) +{ + MMAtSerialPort *port; + MMCallbackInfo *info; + + info = mm_callback_info_new (self, callback, user_data); + + /* Ensure we have a usable port to use for the command */ + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (self), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + /* Modem may not be enabled yet, which sometimes can't be done until + * the device has been unlocked. In this case we have to open the port + * ourselves. + */ + if (!mm_serial_port_open (MM_SERIAL_PORT (port), &info->error)) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "&F +CMEE=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "+COPS=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "+CR=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "+CRC=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "+CREG=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "+CMER=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "*EPEE=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "+CNMI=2, 0, 0, 0, 0", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "+CGREG=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "*EIAD=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "+CGSMS=3", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "+CSCA=\"\",129", 3, factory_reset_done, info); +} + /*****************************************************************************/ static void -mbm_emrdy_received (MMSerialPort *port, +mbm_emrdy_received (MMAtSerialPort *port, GMatchInfo *info, gpointer user_data) { @@ -496,7 +572,7 @@ mbm_emrdy_received (MMSerialPort *port, } static void -mbm_pacsp_received (MMSerialPort *port, +mbm_pacsp_received (MMAtSerialPort *port, GMatchInfo *info, gpointer user_data) { @@ -504,65 +580,78 @@ mbm_pacsp_received (MMSerialPort *port, } static void -mbm_ciev_received (MMSerialPort *port, +mbm_ciev_received (MMAtSerialPort *port, GMatchInfo *info, gpointer user_data) { int quality = 0, ind = 0; - const char *str; + char *str; str = g_match_info_fetch (info, 1); if (str) ind = atoi (str); + g_free (str); if (ind == MBM_SIGNAL_INDICATOR) { str = g_match_info_fetch (info, 2); if (str) { quality = atoi (str); - mm_modem_gsm_network_signal_quality (MM_MODEM_GSM_NETWORK (user_data), quality * 20); + mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (user_data), quality * 20); } + g_free (str); } } static void -mbm_do_connect_done (MMModemMbm *self) +mbm_do_connect_done (MMModemMbm *self, gboolean success) { MMModemMbmPrivate *priv = MM_MODEM_MBM_GET_PRIVATE (self); - if (priv->pending_connect_info) { + if (!priv->pending_connect_info) + return; + + if (success) mm_generic_gsm_connect_complete (MM_GENERIC_GSM (self), NULL, priv->pending_connect_info); - priv->pending_connect_info = NULL; + else { + GError *connect_error; + + connect_error = mm_modem_connect_error_for_code (MM_MODEM_CONNECT_ERROR_BUSY); + mm_generic_gsm_connect_complete (MM_GENERIC_GSM (self), connect_error, priv->pending_connect_info); + g_error_free (connect_error); } + priv->pending_connect_info = NULL; } static void -mbm_e2nap_received (MMSerialPort *port, +mbm_e2nap_received (MMAtSerialPort *port, GMatchInfo *info, gpointer user_data) { int state = 0; - const char *str; + char *str; str = g_match_info_fetch (info, 1); if (str) state = atoi (str); - - if (MBM_E2NAP_DISCONNECTED == state) - g_debug ("%s, disconnected", __func__); - else if (MBM_E2NAP_CONNECTED == state) { - g_debug ("%s, connected", __func__); - mbm_do_connect_done (MM_MODEM_MBM (user_data)); + g_free (str); + + if (MBM_E2NAP_DISCONNECTED == state) { + g_debug ("%s: disconnected", __func__); + mbm_do_connect_done (MM_MODEM_MBM (user_data), FALSE); + } else if (MBM_E2NAP_CONNECTED == state) { + g_debug ("%s: connected", __func__); + mbm_do_connect_done (MM_MODEM_MBM (user_data), TRUE); } else if (MBM_E2NAP_CONNECTING == state) - g_debug("%s, connecting", __func__); + g_debug("%s: connecting", __func__); else { /* Should not happen */ - g_debug("%s, undefined e2nap status",__FUNCTION__); - g_assert_not_reached (); + g_debug("%s: unhandled E2NAP state %d", __func__, state); + mbm_do_connect_done (MM_MODEM_MBM (user_data), FALSE); } } static void -enap_poll_response (MMSerialPort *port, +enap_poll_response (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -597,20 +686,21 @@ static gboolean enap_poll (gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - MMSerialPort *port = mm_generic_gsm_get_port (MM_GENERIC_GSM (info->modem), MM_PORT_TYPE_PRIMARY); + MMAtSerialPort *port; + port = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (info->modem), MM_PORT_TYPE_PRIMARY); g_assert (port); - mm_serial_port_queue_command (port, "AT*ENAP?", 3, enap_poll_response, user_data); + mm_at_serial_port_queue_command (port, "AT*ENAP?", 3, enap_poll_response, user_data); /* we cancel this in the _done function if all is fine */ return TRUE; } static void -enap_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +enap_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; guint tid; @@ -623,12 +713,11 @@ enap_done (MMSerialPort *port, tid = g_timeout_add_seconds (1, enap_poll, user_data); /* remember poll id as callback info object, with source_remove as free func */ mm_callback_info_set_data (info, "mbm-enap-poll-id", GUINT_TO_POINTER (tid), (GFreeFunc) g_source_remove); - mm_serial_port_queue_command (port, "AT*E2NAP=1", 3, NULL, NULL); } static void mbm_auth_done (MMSerialPort *port, - GString *response, + GByteArray *response, GError *error, gpointer user_data) { @@ -643,8 +732,11 @@ mbm_auth_done (MMSerialPort *port, } cid = mm_generic_gsm_get_cid (modem); + + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "AT*E2NAP=1", 3, NULL, NULL); + command = g_strdup_printf ("AT*ENAP=1,%d", cid); - mm_serial_port_queue_command (port, command, 3, enap_done, user_data); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), command, 3, enap_done, user_data); g_free (command); } @@ -654,61 +746,133 @@ mbm_modem_authenticate (MMModemMbm *self, const char *password, gpointer user_data) { - MMSerialPort *primary; + MMAtSerialPort *primary; - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (self), MM_PORT_TYPE_PRIMARY); + primary = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (self), MM_PORT_TYPE_PRIMARY); g_assert (primary); if (username || password) { - char *command; + GByteArray *command; + MMModemCharset cur_set; + char *tmp; + + /* F3507g at least wants the username and password to be sent in the + * modem's current character set. + */ + cur_set = mm_generic_gsm_get_charset (MM_GENERIC_GSM (self)); + + command = g_byte_array_sized_new (75); + tmp = g_strdup_printf ("AT*EIAAUW=%d,1,", mm_generic_gsm_get_cid (MM_GENERIC_GSM (self))); + g_byte_array_append (command, (const guint8 *) tmp, strlen (tmp)); + g_free (tmp); + + if (username) + mm_modem_charset_byte_array_append (command, username, TRUE, cur_set); + else + g_byte_array_append (command, (const guint8 *) "\"\"", 2); - command = g_strdup_printf ("*EIAAUW=%d,1,\"%s\",\"%s\"", - mm_generic_gsm_get_cid (MM_GENERIC_GSM (self)), - username ? username : "", - password ? password : ""); + g_byte_array_append (command, (const guint8 *) ",", 1); - mm_serial_port_queue_command (primary, command, 3, mbm_auth_done, user_data); - g_free (command); + if (password) + mm_modem_charset_byte_array_append (command, password, TRUE, cur_set); + else + g_byte_array_append (command, (const guint8 *) "\"\"", 2); + + g_byte_array_append (command, (const guint8 *) "\r", 1); + + mm_serial_port_queue_command (MM_SERIAL_PORT (primary), + command, + TRUE, + 3, + mbm_auth_done, + user_data); } else - mbm_auth_done (primary, NULL, NULL, user_data); + mbm_auth_done (MM_SERIAL_PORT (primary), NULL, NULL, user_data); } -static const char * -mbm_simple_get_string_property (GHashTable *properties, const char *name, GError **error) +/*****************************************************************************/ + +static void +send_epin_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { - GValue *value; + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *pin_type; + int attempts_left = 0; - value = (GValue *) g_hash_table_lookup (properties, name); - if (!value) - return NULL; + if (error) { + info->error = g_error_copy (error); + goto done; + } - if (G_VALUE_HOLDS_STRING (value)) - return g_value_get_string (value); + pin_type = mm_callback_info_get_data (info, "pin_type"); - g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, - "Invalid property type for '%s': %s (string expected)", - name, G_VALUE_TYPE_NAME (value)); + if (strstr (pin_type, MM_MODEM_GSM_CARD_SIM_PIN)) + sscanf (response->str, "*EPIN: %d", &attempts_left); + else if (strstr (pin_type, MM_MODEM_GSM_CARD_SIM_PUK)) + sscanf (response->str, "*EPIN: %*d, %d", &attempts_left); + else if (strstr (pin_type, MM_MODEM_GSM_CARD_SIM_PIN2)) + sscanf (response->str, "*EPIN: %*d, %*d, %d", &attempts_left); + else if (strstr (pin_type, MM_MODEM_GSM_CARD_SIM_PUK2)) + sscanf (response->str, "*EPIN: %*d, %*d, %*d, %d", &attempts_left); + else { + g_debug ("%s: unhandled pin type '%s'", __func__, pin_type); - return NULL; + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Unhandled PIN type"); + } + + if (attempts_left < 0 || attempts_left > 998) { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Invalid PIN attempts left %d", attempts_left); + attempts_left = 0; + } + + mm_callback_info_set_result (info, GUINT_TO_POINTER (attempts_left), NULL); + +done: + mm_serial_port_close (MM_SERIAL_PORT (port)); + mm_callback_info_schedule (info); } -static uint -mbm_simple_get_uint_property (GHashTable *properties, const char *name, GError **error) +static void +mbm_get_unlock_retries (MMModemGsmCard *modem, + const char *pin_type, + MMModemUIntFn callback, + gpointer user_data) { - GValue *value; + MMAtSerialPort *port; + char *command; + MMCallbackInfo *info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - value = (GValue *) g_hash_table_lookup (properties, name); - if (!value) - return 0; + g_debug ("%s: pin type '%s'", __func__, pin_type); - if (G_VALUE_HOLDS_UINT (value)) - return g_value_get_uint (value); + /* Ensure we have a usable port to use for the command */ + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } - g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, - "Invalid property type for '%s': %s (uint expected)", - name, G_VALUE_TYPE_NAME (value)); + /* Modem may not be enabled yet, which sometimes can't be done until + * the device has been unlocked. In this case we have to open the port + * ourselves. + */ + if (!mm_serial_port_open (MM_SERIAL_PORT (port), &info->error)) { + mm_callback_info_schedule (info); + return; + } + + /* if the modem have not yet been enabled we need to make sure echoing is turned off */ + command = g_strdup_printf ("E0"); + mm_at_serial_port_queue_command (port, command, 3, NULL, NULL); + g_free (command); - return 0; + mm_callback_info_set_data (info, "pin_type", g_strdup (pin_type), g_free); + + command = g_strdup_printf ("*EPIN?"); + mm_at_serial_port_queue_command (port, command, 3, send_epin_done, info); + g_free (command); } /*****************************************************************************/ @@ -727,43 +891,52 @@ grab_port (MMModem *modem, if (!strcmp (subsys, "tty")) { if (suggested_type == MM_PORT_TYPE_UNKNOWN) { - if (!mm_generic_gsm_get_port (gsm, MM_PORT_TYPE_PRIMARY)) + if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY)) ptype = MM_PORT_TYPE_PRIMARY; - else if (!mm_generic_gsm_get_port (gsm, MM_PORT_TYPE_SECONDARY)) + else if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_SECONDARY)) ptype = MM_PORT_TYPE_SECONDARY; } else ptype = suggested_type; } port = mm_generic_gsm_grab_port (gsm, subsys, name, ptype, error); - if (port && MM_IS_SERIAL_PORT (port) && (ptype == MM_PORT_TYPE_PRIMARY)) { + if (port && MM_IS_AT_SERIAL_PORT (port)) { GRegex *regex; - mm_generic_gsm_set_unsolicited_registration (MM_GENERIC_GSM (modem), TRUE); + if (ptype == MM_PORT_TYPE_PRIMARY) { + regex = g_regex_new ("\\r\\n\\*E2NAP: (\\d)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, mbm_e2nap_received, modem, NULL); + g_regex_unref (regex); - regex = g_regex_new ("\\r\\n\\*EMRDY: \\d\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, mbm_emrdy_received, modem, NULL); - g_regex_unref (regex); + /* Catch the extended error status bit of the command too */ + regex = g_regex_new ("\\r\\n\\*E2NAP: (\\d),.*\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, mbm_e2nap_received, modem, NULL); + g_regex_unref (regex); + } - regex = g_regex_new ("\\r\\n\\*E2NAP: (\\d)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, mbm_e2nap_received, modem, NULL); + regex = g_regex_new ("\\r\\n\\*EMRDY: \\d\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, mbm_emrdy_received, modem, NULL); g_regex_unref (regex); regex = g_regex_new ("\\r\\n\\+PACSP(\\d)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, mbm_pacsp_received, modem, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, mbm_pacsp_received, modem, NULL); g_regex_unref (regex); regex = g_regex_new ("\\r\\n\\+CIEV: (\\d),(\\d)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, mbm_ciev_received, modem, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, mbm_ciev_received, modem, NULL); g_regex_unref (regex); /* also consume unsolicited mbm messages we are not interested in them - see LP: #416418 */ - regex = g_regex_new ("\\R\\*ESTKSMENU:.*\\R", G_REGEX_RAW | G_REGEX_OPTIMIZE | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF, G_REGEX_MATCH_NEWLINE_CRLF, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, modem, NULL); + regex = g_regex_new ("\\R\\*ESTKSMENU:.*\\R", G_REGEX_RAW | G_REGEX_OPTIMIZE | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF, G_REGEX_MATCH_NEWLINE_CRLF, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); g_regex_unref (regex); regex = g_regex_new ("\\r\\n\\*EMWI: (\\d),(\\d).*\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, NULL, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); + g_regex_unref (regex); + + regex = g_regex_new ("\\r\\n\\*ERINFO:\\s*(\\d),(\\d),(\\d).*\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, mbm_erinfo_received, modem, NULL); g_regex_unref (regex); } @@ -772,12 +945,16 @@ grab_port (MMModem *modem, /*****************************************************************************/ +static void +modem_gsm_card_init (MMModemGsmCard *class) +{ + class->get_unlock_retries = mbm_get_unlock_retries; +} + static void modem_gsm_network_init (MMModemGsmNetwork *class) { class->do_register = do_register; - class->get_network_mode = get_network_mode; - class->set_network_mode = set_network_mode; } static void @@ -792,7 +969,7 @@ modem_init (MMModem *modem_class) modem_class->grab_port = grab_port; modem_class->disable = disable; modem_class->connect = do_connect; - modem_class->disconnect = disconnect; + modem_class->factory_reset = factory_reset; } static void @@ -826,5 +1003,8 @@ mm_modem_mbm_class_init (MMModemMbmClass *klass) object_class->finalize = finalize; gsm_class->do_enable = do_enable; + gsm_class->do_disconnect = do_disconnect; + gsm_class->get_allowed_mode = get_allowed_mode; + gsm_class->set_allowed_mode = set_allowed_mode; } diff --git a/plugins/mm-modem-mbm.h b/plugins/mm-modem-mbm.h index 8756e47..db0f627 100644 --- a/plugins/mm-modem-mbm.h +++ b/plugins/mm-modem-mbm.h @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * */ #ifndef MM_MODEM_MBM_H diff --git a/plugins/mm-modem-nokia.c b/plugins/mm-modem-nokia.c index 677a089..eb90287 100644 --- a/plugins/mm-modem-nokia.c +++ b/plugins/mm-modem-nokia.c @@ -56,19 +56,19 @@ grab_port (MMModem *modem, MMPort *port = NULL; if (suggested_type == MM_PORT_TYPE_UNKNOWN) { - if (!mm_generic_gsm_get_port (gsm, MM_PORT_TYPE_PRIMARY)) + if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY)) ptype = MM_PORT_TYPE_PRIMARY; - else if (!mm_generic_gsm_get_port (gsm, MM_PORT_TYPE_SECONDARY)) + else if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_SECONDARY)) ptype = MM_PORT_TYPE_SECONDARY; } else ptype = suggested_type; port = mm_generic_gsm_grab_port (gsm, subsys, name, ptype, error); - if (port && MM_IS_SERIAL_PORT (port)) { - mm_serial_port_set_response_parser (MM_SERIAL_PORT (port), - mm_serial_parser_v1_e1_parse, - mm_serial_parser_v1_e1_new (), - mm_serial_parser_v1_e1_destroy); + if (port && MM_IS_AT_SERIAL_PORT (port)) { + mm_at_serial_port_set_response_parser (MM_AT_SERIAL_PORT (port), + mm_serial_parser_v1_e1_parse, + mm_serial_parser_v1_e1_new (), + mm_serial_parser_v1_e1_destroy); } return !!port; diff --git a/plugins/mm-modem-novatel-cdma.c b/plugins/mm-modem-novatel-cdma.c new file mode 100644 index 0000000..64ee15f --- /dev/null +++ b/plugins/mm-modem-novatel-cdma.c @@ -0,0 +1,183 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include + +#include "mm-modem-novatel-cdma.h" +#include "mm-errors.h" +#include "mm-callback-info.h" + +static void modem_cdma_init (MMModemCdma *cdma_class); + +G_DEFINE_TYPE_EXTENDED (MMModemNovatelCdma, mm_modem_novatel_cdma, MM_TYPE_GENERIC_CDMA, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_CDMA, modem_cdma_init)) + + +MMModem * +mm_modem_novatel_cdma_new (const char *device, + const char *driver, + const char *plugin, + gboolean evdo_rev0, + gboolean evdo_revA) +{ + g_return_val_if_fail (device != NULL, NULL); + g_return_val_if_fail (driver != NULL, NULL); + g_return_val_if_fail (plugin != NULL, NULL); + + return MM_MODEM (g_object_new (MM_TYPE_MODEM_NOVATEL_CDMA, + MM_MODEM_MASTER_DEVICE, device, + MM_MODEM_DRIVER, driver, + MM_MODEM_PLUGIN, plugin, + MM_GENERIC_CDMA_EVDO_REV0, evdo_rev0, + MM_GENERIC_CDMA_EVDO_REVA, evdo_revA, + NULL)); +} + +/*****************************************************************************/ + +static void +parent_csq_done (MMModem *modem, + guint32 result, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + + if (error) + info->error = g_error_copy (error); + else + mm_callback_info_set_result (info, GUINT_TO_POINTER (result), NULL); + mm_callback_info_schedule (info); +} + +static int +get_one_qual (const char *reply, const char *tag) +{ + int qual = -1; + const char *p; + + p = strstr (reply, tag); + if (!p) + return -1; + + /* Skip the tag */ + p += strlen (tag); + + /* Skip spaces */ + while (isspace (*p)) + p++; + if (*p == '-') { + long int dbm; + + errno = 0; + dbm = strtol (p, NULL, 10); + if (dbm < 0 && errno == 0) { + dbm = CLAMP (dbm, -113, -51); + qual = 100 - ((dbm + 51) * 100 / (-113 + 51)); + } + } + + return qual; +} + +static void +get_rssi_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMModemCdma *parent_iface; + int qual; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + if (info->modem) { + /* Fallback to parent's method */ + g_clear_error (&info->error); + parent_iface = g_type_interface_peek_parent (MM_MODEM_CDMA_GET_INTERFACE (info->modem)); + parent_iface->get_signal_quality (MM_MODEM_CDMA (info->modem), parent_csq_done, info); + } else + mm_callback_info_schedule (info); + + return; + } + + /* Parse the signal quality */ + qual = get_one_qual (response->str, "RX0="); + if (qual < 0) + qual = get_one_qual (response->str, "RX1="); + + if (qual >= 0) { + mm_callback_info_set_result (info, GUINT_TO_POINTER ((guint32) qual), NULL); + mm_generic_cdma_update_cdma1x_quality (MM_GENERIC_CDMA (info->modem), (guint32) qual); + } else { + info->error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "%s", "Could not parse signal quality results"); + } + + mm_callback_info_schedule (info); +} + +static void +get_signal_quality (MMModemCdma *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *port; + MMModemCdma *parent_iface; + + port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), NULL); + if (!port) { + /* Let the superclass handle it */ + parent_iface = g_type_interface_peek_parent (MM_MODEM_CDMA_GET_INTERFACE (modem)); + parent_iface->get_signal_quality (MM_MODEM_CDMA (modem), callback, user_data); + return; + } + + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); + + /* Many Novatel CDMA cards don't report CSQ in standard 0 - 31 and the CSQ + * reply doesn't appear to be in positive dBm either; instead try the custom + * Novatel command for it. + */ + mm_at_serial_port_queue_command (port, "$NWRSSI", 3, get_rssi_done, info); +} + +/*****************************************************************************/ + +static void +modem_cdma_init (MMModemCdma *cdma_class) +{ + cdma_class->get_signal_quality = get_signal_quality; +} + +static void +mm_modem_novatel_cdma_init (MMModemNovatelCdma *self) +{ +} + +static void +mm_modem_novatel_cdma_class_init (MMModemNovatelCdmaClass *klass) +{ + mm_modem_novatel_cdma_parent_class = g_type_class_peek_parent (klass); +} + diff --git a/plugins/mm-modem-novatel-cdma.h b/plugins/mm-modem-novatel-cdma.h new file mode 100644 index 0000000..4d38d8e --- /dev/null +++ b/plugins/mm-modem-novatel-cdma.h @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#ifndef MM_MODEM_NOVATEL_CDMA_H +#define MM_MODEM_NOVATEL_CDMA_H + +#include "mm-generic-cdma.h" + +#define MM_TYPE_MODEM_NOVATEL_CDMA (mm_modem_novatel_cdma_get_type ()) +#define MM_MODEM_NOVATEL_CDMA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_NOVATEL_CDMA, MMModemNovatelCdma)) +#define MM_MODEM_NOVATEL_CDMA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_MODEM_NOVATEL_CDMA, MMModemNovatelCdmaClass)) +#define MM_IS_MODEM_NOVATEL_CDMA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_NOVATEL_CDMA)) +#define MM_IS_MODEM_NOVATEL_CDMA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_MODEM_NOVATEL_CDMA)) +#define MM_MODEM_NOVATEL_CDMA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_MODEM_NOVATEL_CDMA, MMModemNovatelCdmaClass)) + +typedef struct { + MMGenericCdma parent; +} MMModemNovatelCdma; + +typedef struct { + MMGenericCdmaClass parent; +} MMModemNovatelCdmaClass; + +GType mm_modem_novatel_cdma_get_type (void); + +MMModem *mm_modem_novatel_cdma_new (const char *device, + const char *driver, + const char *plugin, + gboolean evdo_rev0, + gboolean evdo_revA); + +#endif /* MM_MODEM_NOVATEL_CDMA_H */ diff --git a/plugins/mm-modem-novatel-gsm.c b/plugins/mm-modem-novatel-gsm.c index 8189627..584156f 100644 --- a/plugins/mm-modem-novatel-gsm.c +++ b/plugins/mm-modem-novatel-gsm.c @@ -11,9 +11,10 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ +#include #include #include #include @@ -21,6 +22,7 @@ #include "mm-modem-novatel-gsm.h" #include "mm-errors.h" #include "mm-callback-info.h" +#include "mm-modem-helpers.h" static void modem_init (MMModem *modem_class); @@ -49,114 +51,246 @@ mm_modem_novatel_gsm_new (const char *device, /*****************************************************************************/ static void -init_modem_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +dmat_callback2 (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); + mm_serial_port_close (MM_SERIAL_PORT (port)); } static void -pin_check_done (MMModem *modem, GError *error, gpointer user_data) +dmat_callback (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - MMGenericGsm *self = MM_GENERIC_GSM (modem); - MMSerialPort *primary; - if (error) { - mm_generic_gsm_enable_complete (self, error, info); - return; + /* Try it again */ + if (mm_serial_port_open (MM_SERIAL_PORT (port), NULL)) + mm_at_serial_port_queue_command (port, "$NWDMAT=1", 2, dmat_callback2, NULL); + } + + mm_serial_port_close (MM_SERIAL_PORT (port)); +} + +static gboolean +grab_port (MMModem *modem, + const char *subsys, + const char *name, + MMPortType suggested_type, + gpointer user_data, + GError **error) +{ + MMGenericGsm *gsm = MM_GENERIC_GSM (modem); + MMPortType ptype = MM_PORT_TYPE_IGNORED; + MMPort *port = NULL; + + if (suggested_type == MM_PORT_TYPE_UNKNOWN) { + if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY)) + ptype = MM_PORT_TYPE_PRIMARY; + else if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_SECONDARY)) + ptype = MM_PORT_TYPE_SECONDARY; + } else + ptype = suggested_type; + + port = mm_generic_gsm_grab_port (gsm, subsys, name, ptype, error); + if (port && MM_IS_AT_SERIAL_PORT (port) && (ptype == MM_PORT_TYPE_PRIMARY)) { + /* Flip secondary ports to AT mode */ + if (mm_serial_port_open (MM_SERIAL_PORT (port), NULL)) + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "$NWDMAT=1", 2, dmat_callback, NULL); } - /* Finish the initialization */ - primary = mm_generic_gsm_get_port (self, MM_PORT_TYPE_PRIMARY); - g_assert (primary); - mm_serial_port_queue_command (primary, "Z E0 V1 X4 &C1 +CMEE=1;+CFUN=1", 10, init_modem_done, info); + return !!port; } +/*****************************************************************************/ + static void -pre_init_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +set_allowed_mode_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - if (error) { - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); + if (error) + info->error = g_error_copy (error); + + mm_callback_info_schedule (info); +} + +static void +set_allowed_mode (MMGenericGsm *gsm, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *port; + char *command; + int nw_mode = 0; /* 3G preferred */ + + info = mm_callback_info_new (MM_MODEM (gsm), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { + mm_callback_info_schedule (info); return; } - /* Now check the PIN explicitly, novatel doesn't seem to report - * that it needs it otherwise. - */ - mm_generic_gsm_check_pin (MM_GENERIC_GSM (info->modem), pin_check_done, info); + switch (mode) { + case MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY: + nw_mode = 1; + break; + case MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY: + nw_mode = 2; + break; + case MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED: + case MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED: + case MM_MODEM_GSM_ALLOWED_MODE_ANY: + default: + break; + } + + command = g_strdup_printf ("$NWRAT=%d,2", nw_mode); + mm_at_serial_port_queue_command (port, command, 3, set_allowed_mode_done, info); + g_free (command); +} + +static gboolean +parse_nwrat_response (GString *response, + MMModemGsmAllowedMode *out_mode, + GError **error) +{ + GRegex *r; + GMatchInfo *match_info; + char *str; + gint mode = -1; + gboolean success = FALSE; + + g_return_val_if_fail (response != NULL, FALSE); + g_return_val_if_fail (out_mode != NULL, FALSE); + + r = g_regex_new ("\\$NWRAT:\\s*(\\d),(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL); + if (!r) { + g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Internal error parsing mode/tech response"); + return FALSE; + } + + if (!g_regex_match_full (r, response->str, response->len, 0, 0, &match_info, NULL)) { + g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Failed to parse mode/tech response"); + goto out; + } + + str = g_match_info_fetch (match_info, 1); + mode = atoi (str); + g_free (str); + + g_match_info_free (match_info); + + if (mode < 0 || mode > 2) { + g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Failed to parse mode/tech response"); + goto out; + } + + if (out_mode) { + if (mode == 0) + *out_mode = MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED; + else if (mode == 1) + *out_mode = MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY; + else if (mode == 2) + *out_mode = MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY; + else + *out_mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; + } + success = TRUE; + +out: + g_regex_unref (r); + return success; } static void -enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data) +get_allowed_mode_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { - MMCallbackInfo *info = user_data; + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMModemGsmAllowedMode mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; - if (error) - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); - else - mm_serial_port_queue_command (port, "E0 V1", 3, pre_init_done, user_data); + info->error = mm_modem_check_removed (info->modem, error); + if (!info->error) { + parse_nwrat_response (response, &mode, &info->error); + mm_callback_info_set_result (info, GUINT_TO_POINTER (mode), NULL); + } + + mm_callback_info_schedule (info); } static void -do_enable (MMGenericGsm *modem, MMModemFn callback, gpointer user_data) +get_allowed_mode (MMGenericGsm *gsm, + MMModemUIntFn callback, + gpointer user_data) { MMCallbackInfo *info; - MMSerialPort *primary; + MMAtSerialPort *port; - primary = mm_generic_gsm_get_port (modem, MM_PORT_TYPE_PRIMARY); - g_assert (primary); + info = mm_callback_info_uint_new (MM_MODEM (gsm), callback, user_data); - info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_flash (primary, 100, enable_flash_done, info); + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "$NWRAT?", 3, get_allowed_mode_done, info); } static void -dmat_callback (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +get_act_request_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { - mm_serial_port_close (port); + MMCallbackInfo *info = user_data; + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + const char *p; + + if (error) + info->error = g_error_copy (error); + else { + p = mm_strip_tag (response->str, "$CNTI:"); + p = strchr (p, ','); + if (p) + act = mm_gsm_string_to_access_tech (p + 1); + } + + mm_callback_info_set_result (info, GUINT_TO_POINTER (act), NULL); + mm_callback_info_schedule (info); } -static gboolean -grab_port (MMModem *modem, - const char *subsys, - const char *name, - MMPortType suggested_type, - gpointer user_data, - GError **error) +static void +get_access_technology (MMGenericGsm *modem, + MMModemUIntFn callback, + gpointer user_data) { - MMGenericGsm *gsm = MM_GENERIC_GSM (modem); - MMPortType ptype = MM_PORT_TYPE_IGNORED; - MMPort *port = NULL; + MMAtSerialPort *port; + MMCallbackInfo *info; - if (suggested_type == MM_PORT_TYPE_UNKNOWN) { - if (!mm_generic_gsm_get_port (gsm, MM_PORT_TYPE_PRIMARY)) - ptype = MM_PORT_TYPE_PRIMARY; - else if (!mm_generic_gsm_get_port (gsm, MM_PORT_TYPE_SECONDARY)) - ptype = MM_PORT_TYPE_SECONDARY; - } else - ptype = suggested_type; + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - port = mm_generic_gsm_grab_port (gsm, subsys, name, ptype, error); - if (port && MM_IS_SERIAL_PORT (port) && (ptype == MM_PORT_TYPE_PRIMARY)) { - /* Flip secondary ports to AT mode */ - if (mm_serial_port_open (MM_SERIAL_PORT (port), NULL)) - mm_serial_port_queue_command (MM_SERIAL_PORT (port), "$NWDMAT=1", 2, dmat_callback, NULL); + port = mm_generic_gsm_get_best_at_port (modem, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; } - return !!port; + mm_at_serial_port_queue_command (port, "$CNTI=0", 3, get_act_request_done, info); } /*****************************************************************************/ @@ -179,6 +313,8 @@ mm_modem_novatel_gsm_class_init (MMModemNovatelGsmClass *klass) mm_modem_novatel_gsm_parent_class = g_type_class_peek_parent (klass); - gsm_class->do_enable = do_enable; + gsm_class->set_allowed_mode = set_allowed_mode; + gsm_class->get_allowed_mode = get_allowed_mode; + gsm_class->get_access_technology = get_access_technology; } diff --git a/plugins/mm-modem-option-utils.c b/plugins/mm-modem-option-utils.c new file mode 100644 index 0000000..35dd1ac --- /dev/null +++ b/plugins/mm-modem-option-utils.c @@ -0,0 +1,451 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +/****************************************** + * Generic utilities for Option NV modems + * Used with both 'option' and 'hso' + ******************************************/ + +#include "mm-callback-info.h" +#include "mm-at-serial-port.h" +#include "mm-generic-gsm.h" +#include "mm-modem-helpers.h" + +static void +option_get_allowed_mode_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + gboolean parsed = FALSE; + + if (error) + info->error = g_error_copy (error); + else if (!g_str_has_prefix (response->str, "_OPSYS: ")) { + int a, b; + + if (sscanf (response->str + 8, "%d,%d", &a, &b)) { + MMModemGsmAllowedMode mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; + + switch (a) { + case 0: + mode = MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY; + break; + case 1: + mode = MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY; + break; + case 2: + mode = MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED; + break; + case 3: + mode = MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED; + break; + default: + break; + } + + mm_callback_info_set_result (info, GUINT_TO_POINTER (mode), NULL); + parsed = TRUE; + } + } + + if (!error && !parsed) + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Could not parse allowed mode results"); + + mm_callback_info_schedule (info); +} + +static void +option_get_allowed_mode (MMGenericGsm *gsm, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *port; + + info = mm_callback_info_uint_new (MM_MODEM (gsm), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + mm_at_serial_port_queue_command (port, "AT_OPSYS?", 3, option_get_allowed_mode_done, info); +} + +static void +option_set_allowed_mode_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) + info->error = g_error_copy (error); + + mm_callback_info_schedule (info); +} + +static void +option_set_allowed_mode (MMGenericGsm *gsm, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *port; + char *command; + int i; + + info = mm_callback_info_new (MM_MODEM (gsm), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + switch (mode) { + case MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY: + i = 0; + break; + case MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY: + i = 1; + break; + case MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED: + i = 2; + break; + case MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED: + i = 3; + break; + case MM_MODEM_GSM_ALLOWED_MODE_ANY: + default: + i = 5; + break; + } + + command = g_strdup_printf ("AT_OPSYS=%d,2", i); + mm_at_serial_port_queue_command (port, command, 3, option_set_allowed_mode_done, info); + g_free (command); +} + +static gboolean +octi_to_mm (char octi, MMModemGsmAccessTech *out_act) +{ + if (octi == '1') { + *out_act = MM_MODEM_GSM_ACCESS_TECH_GSM; + return TRUE; + } else if (octi == '2') { + *out_act = MM_MODEM_GSM_ACCESS_TECH_GPRS; + return TRUE; + } else if (octi == '3') { + *out_act = MM_MODEM_GSM_ACCESS_TECH_EDGE; + return TRUE; + } + return FALSE; +} + +static gboolean +owcti_to_mm (char owcti, MMModemGsmAccessTech *out_act) +{ + if (owcti == '1') { + *out_act = MM_MODEM_GSM_ACCESS_TECH_UMTS; + return TRUE; + } else if (owcti == '2') { + *out_act = MM_MODEM_GSM_ACCESS_TECH_HSDPA; + return TRUE; + } else if (owcti == '3') { + *out_act = MM_MODEM_GSM_ACCESS_TECH_HSUPA; + return TRUE; + } else if (owcti == '4') { + *out_act = MM_MODEM_GSM_ACCESS_TECH_HSPA; + return TRUE; + } + return FALSE; +} + +static gboolean +parse_octi_response (GString *response, MMModemGsmAccessTech *act) +{ + MMModemGsmAccessTech cur_act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + const char *p; + GRegex *r; + GMatchInfo *match_info; + char *str; + gboolean success = FALSE; + + g_return_val_if_fail (act != NULL, FALSE); + g_return_val_if_fail (response != NULL, FALSE); + + p = mm_strip_tag (response->str, "_OCTI:"); + + r = g_regex_new ("(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL); + g_return_val_if_fail (r != NULL, FALSE); + + g_regex_match (r, p, 0, &match_info); + if (g_match_info_matches (match_info)) { + str = g_match_info_fetch (match_info, 2); + if (str && octi_to_mm (str[0], &cur_act)) { + *act = cur_act; + success = TRUE; + } + g_free (str); + } + g_match_info_free (match_info); + g_regex_unref (r); + + return success; +} + +static void +ossys_octi_request_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + + if (!error) { + if (parse_octi_response (response, &act)) + mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (user_data), act); + } +} + +static void +ossys_owcti_request_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + const char *p; + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + + if (!error) { + p = mm_strip_tag (response->str, "_OWCTI:"); + if (owcti_to_mm (*p, &act)) + mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (user_data), act); + } +} + +static void +option_ossys_tech_changed (MMAtSerialPort *port, + GMatchInfo *info, + gpointer user_data) +{ + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + char *str; + + str = g_match_info_fetch (info, 1); + if (str) { + switch (atoi (str)) { + case 0: + act = MM_MODEM_GSM_ACCESS_TECH_GPRS; + break; + case 2: + act = MM_MODEM_GSM_ACCESS_TECH_UMTS; + break; + default: + break; + } + } + g_free (str); + + mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (user_data), act); + + /* _OSSYSI only indicates general 2G/3G mode, so queue up some explicit + * access technology requests. + */ + if (act == MM_MODEM_GSM_ACCESS_TECH_GPRS) + mm_at_serial_port_queue_command (port, "_OCTI?", 3, ossys_octi_request_done, user_data); + else if (act == MM_MODEM_GSM_ACCESS_TECH_UMTS) + mm_at_serial_port_queue_command (port, "_OWCTI?", 3, ossys_owcti_request_done, user_data); +} + +static void +option_2g_tech_changed (MMAtSerialPort *port, + GMatchInfo *match_info, + gpointer user_data) +{ + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + char *str; + + str = g_match_info_fetch (match_info, 1); + if (octi_to_mm (str[0], &act)) + mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (user_data), act); + g_free (str); +} + +static void +option_3g_tech_changed (MMAtSerialPort *port, + GMatchInfo *match_info, + gpointer user_data) +{ + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + char *str; + + str = g_match_info_fetch (match_info, 1); + if (owcti_to_mm (str[0], &act)) + mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (user_data), act); + g_free (str); +} + +static void +option_signal_changed (MMAtSerialPort *port, + GMatchInfo *match_info, + gpointer user_data) +{ + char *str; + int quality = 0; + + str = g_match_info_fetch (match_info, 1); + quality = atoi (str); + g_free (str); + + if (quality == 99) { + /* 99 means unknown */ + quality = 0; + } else { + /* Normalize the quality */ + quality = CLAMP (quality, 0, 31) * 100 / 31; + } + + mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (user_data), (guint32) quality); +} + +static void +option_register_unsolicted_handlers (MMGenericGsm *modem, MMAtSerialPort *port) +{ + GRegex *regex; + + regex = g_regex_new ("\\r\\n_OSSYSI:\\s*(\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (port, regex, option_ossys_tech_changed, modem, NULL); + g_regex_unref (regex); + + regex = g_regex_new ("\\r\\n_OCTI:\\s*(\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (port, regex, option_2g_tech_changed, modem, NULL); + g_regex_unref (regex); + + regex = g_regex_new ("\\r\\n_OUWCTI:\\s*(\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (port, regex, option_3g_tech_changed, modem, NULL); + g_regex_unref (regex); + + regex = g_regex_new ("\\r\\n_OSIGQ:\\s*(\\d+),(\\d)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (port, regex, option_signal_changed, modem, NULL); + g_regex_unref (regex); +} + +static void +unsolicited_msg_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + + if (info) + mm_callback_info_chain_complete_one (info); +} + +static void +option_change_unsolicited_messages (MMGenericGsm *modem, + gboolean enabled, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info = NULL; + MMAtSerialPort *primary; + + if (callback) { + info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + mm_callback_info_chain_start (info, 4); + } + + primary = mm_generic_gsm_get_at_port (modem, MM_PORT_TYPE_PRIMARY); + g_assert (primary); + + mm_at_serial_port_queue_command (primary, enabled ? "_OSSYS=1" : "_OSSYS=0", 3, unsolicited_msg_done, info); + mm_at_serial_port_queue_command (primary, enabled ? "_OCTI=1" : "_OCTI=0", 3, unsolicited_msg_done, info); + mm_at_serial_port_queue_command (primary, enabled ? "_OUWCTI=1" : "_OUWCTI=0", 3, unsolicited_msg_done, info); + mm_at_serial_port_queue_command (primary, enabled ? "_OSQI=1" : "_OSQI=0", 3, unsolicited_msg_done, info); +} + +static void +get_act_octi_request_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMModemGsmAccessTech octi = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + MMModemGsmAccessTech owcti; + + if (!error) { + if (parse_octi_response (response, &octi)) { + /* If no 3G tech yet or current tech isn't 3G, then 2G tech is the best */ + owcti = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "owcti")); + if (octi && !owcti) + mm_callback_info_set_result (info, GUINT_TO_POINTER (octi), NULL); + } + } + + mm_callback_info_chain_complete_one (info); +} + +static void +get_act_owcti_request_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMModemGsmAccessTech owcti = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + const char *p; + + if (!error) { + p = mm_strip_tag (response->str, "_OWCTI:"); + if (owcti_to_mm (*p, &owcti)) { + /* 3G tech always takes precedence over 2G tech */ + if (owcti) + mm_callback_info_set_result (info, GUINT_TO_POINTER (owcti), NULL); + } + } + + mm_callback_info_chain_complete_one (info); +} + +static void +option_get_access_technology (MMGenericGsm *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMAtSerialPort *port; + MMCallbackInfo *info; + + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); + mm_callback_info_chain_start (info, 2); + + port = mm_generic_gsm_get_best_at_port (modem, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "_OCTI?", 3, get_act_octi_request_done, info); + mm_at_serial_port_queue_command (port, "_OWCTI?", 3, get_act_owcti_request_done, info); +} + diff --git a/plugins/mm-modem-option.c b/plugins/mm-modem-option.c index 2076ae6..ac04b0b 100644 --- a/plugins/mm-modem-option.c +++ b/plugins/mm-modem-option.c @@ -23,12 +23,15 @@ #include "mm-callback-info.h" static void modem_init (MMModem *modem_class); -static void modem_gsm_network_init (MMModemGsmNetwork *gsm_network_class); G_DEFINE_TYPE_EXTENDED (MMModemOption, mm_modem_option, MM_TYPE_GENERIC_GSM, 0, - G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init) - G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_NETWORK, modem_gsm_network_init)) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)) +#define MM_MODEM_OPTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_OPTION, MMModemOptionPrivate)) + +typedef struct { + guint enable_wait_id; +} MMModemOptionPrivate; MMModem * mm_modem_option_new (const char *device, @@ -46,174 +49,156 @@ mm_modem_option_new (const char *device, NULL)); } +#include "mm-modem-option-utils.c" + /*****************************************************************************/ -static void -pin_check_done (MMModem *modem, GError *error, gpointer user_data) +static gboolean +option_enabled (gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMCallbackInfo *info = user_data; + MMGenericGsm *modem; + MMModemOptionPrivate *priv; - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (modem), error, info); -} + /* Make sure we don't use an invalid modem that may have been removed */ + if (info->modem) { + modem = MM_GENERIC_GSM (info->modem); + priv = MM_MODEM_OPTION_GET_PRIVATE (modem); + priv->enable_wait_id = 0; -static gboolean -option_enabled (gpointer data) -{ - MMCallbackInfo *info = (MMCallbackInfo *) data; + option_change_unsolicited_messages (modem, TRUE, NULL, NULL); - /* Now check the PIN explicitly, option doesn't seem to report - * that it needs it otherwise. - */ - mm_generic_gsm_check_pin (MM_GENERIC_GSM (info->modem), pin_check_done, info); + MM_GENERIC_GSM_CLASS (mm_modem_option_parent_class)->do_enable_power_up_done (modem, NULL, NULL, info); + } return FALSE; } static void -parent_enable_done (MMModem *modem, GError *error, gpointer user_data) +real_do_enable_power_up_done (MMGenericGsm *gsm, + GString *response, + GError *error, + MMCallbackInfo *info) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMModemOptionPrivate *priv = MM_MODEM_OPTION_GET_PRIVATE (gsm); if (error) { - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (modem), error, info); + /* Chain up to parent */ + MM_GENERIC_GSM_CLASS (mm_modem_option_parent_class)->do_enable_power_up_done (gsm, NULL, error, info); return; } - /* Option returns OK on +CFUN=1 right away but needs some time - * to finish initialization + /* Some Option devices return OK on +CFUN=1 right away but need some time + * to finish initialization. */ - g_timeout_add_seconds (10, option_enabled, info); + g_warn_if_fail (priv->enable_wait_id == 0); + priv->enable_wait_id = g_timeout_add_seconds (10, option_enabled, info); } +/*****************************************************************************/ + static void -enable (MMModem *modem, - MMModemFn callback, - gpointer user_data) +get_allowed_mode (MMGenericGsm *gsm, + MMModemUIntFn callback, + gpointer user_data) { - MMModem *parent_modem_iface; - MMCallbackInfo *info; + option_get_allowed_mode (gsm, callback, user_data); +} - info = mm_callback_info_new (modem, callback, user_data); - parent_modem_iface = g_type_interface_peek_parent (MM_MODEM_GET_INTERFACE (modem)); - parent_modem_iface->enable (modem, parent_enable_done, info); +static void +set_allowed_mode (MMGenericGsm *gsm, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data) +{ + option_set_allowed_mode (gsm, mode, callback, user_data); } static void -get_network_mode_done (MMSerialPort *port, - GString *response, - GError *error, +get_access_technology (MMGenericGsm *gsm, + MMModemUIntFn callback, gpointer user_data) +{ + option_get_access_technology (gsm, callback, user_data); +} + +/*****************************************************************************/ + +static void +parent_disable_done (MMModem *modem, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - gboolean parsed = FALSE; if (error) info->error = g_error_copy (error); - else if (!g_str_has_prefix (response->str, "_OPSYS: ")) { - int a, b; - - if (sscanf (response->str + 8, "%d,%d", &a, &b)) { - MMModemGsmMode mode = MM_MODEM_GSM_MODE_ANY; - - switch (a) { - case 0: - mode = MM_MODEM_GSM_MODE_2G_ONLY; - break; - case 1: - mode = MM_MODEM_GSM_MODE_3G_ONLY; - break; - case 2: - mode = MM_MODEM_GSM_MODE_2G_PREFERRED; - break; - case 3: - mode = MM_MODEM_GSM_MODE_3G_PREFERRED; - break; - default: - break; - } - - mm_callback_info_set_result (info, GUINT_TO_POINTER (mode), NULL); - parsed = TRUE; - } - } - - if (!error && !parsed) - info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, - "Could not parse network mode results"); - mm_callback_info_schedule (info); } static void -get_network_mode (MMModemGsmNetwork *modem, - MMModemUIntFn callback, - gpointer user_data) +unsolicited_disable_done (MMModem *modem, + GError *error, + gpointer user_data) { - MMCallbackInfo *info; - MMSerialPort *primary; + MMCallbackInfo *info = user_data; + MMModem *parent_modem_iface; + GError *tmp_error = NULL; + + /* Handle modem removal, but ignore other errors */ + if (g_error_matches (error, MM_MODEM_ERROR, MM_MODEM_ERROR_REMOVED)) { + parent_disable_done (modem, error, user_data); + return; + } else if (!modem) { + tmp_error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_REMOVED, + "The modem was removed."); + parent_disable_done (modem, tmp_error, user_data); + g_error_free (tmp_error); + return; + } - info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); - g_assert (primary); - mm_serial_port_queue_command (primary, "AT_OPSYS?", 3, get_network_mode_done, info); + /* Chain up to parent */ + parent_modem_iface = g_type_interface_peek_parent (MM_MODEM_GET_INTERFACE (modem)); + parent_modem_iface->disable (info->modem, parent_disable_done, info); } static void -set_network_mode_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +disable (MMModem *modem, + MMModemFn callback, + gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMCallbackInfo *info; - if (error) - info->error = g_error_copy (error); - - mm_callback_info_schedule (info); + mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem)); + + info = mm_callback_info_new (modem, callback, user_data); + + /* Turn off unsolicited messages so they don't pile up in the modem */ + option_change_unsolicited_messages (MM_GENERIC_GSM (modem), FALSE, unsolicited_disable_done, info); } -static void -set_network_mode (MMModemGsmNetwork *modem, - MMModemGsmMode mode, - MMModemFn callback, - gpointer user_data) +static gboolean +grab_port (MMModem *modem, + const char *subsys, + const char *name, + MMPortType suggested_type, + gpointer user_data, + GError **error) { - MMCallbackInfo *info; - MMSerialPort *primary; - char *command; - int i; - - info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); - - switch (mode) { - case MM_MODEM_GSM_MODE_ANY: - case MM_MODEM_GSM_MODE_GPRS: - case MM_MODEM_GSM_MODE_EDGE: - case MM_MODEM_GSM_MODE_2G_ONLY: - i = 0; - break; - case MM_MODEM_GSM_MODE_UMTS: - case MM_MODEM_GSM_MODE_HSDPA: - case MM_MODEM_GSM_MODE_HSUPA: - case MM_MODEM_GSM_MODE_HSPA: - case MM_MODEM_GSM_MODE_3G_ONLY: - i = 1; - break; - case MM_MODEM_GSM_MODE_2G_PREFERRED: - i = 2; - break; - case MM_MODEM_GSM_MODE_3G_PREFERRED: - i = 3; - break; - default: - i = 5; - break; + MMGenericGsm *gsm = MM_GENERIC_GSM (modem); + MMPort *port = NULL; + + port = mm_generic_gsm_grab_port (gsm, subsys, name, suggested_type, error); + if (port && MM_IS_AT_SERIAL_PORT (port)) { + if (mm_port_get_port_type (port) == MM_PORT_TYPE_PRIMARY) { + GRegex *regex; + + regex = g_regex_new ("\\r\\n\\+PACSP0\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); + g_regex_unref (regex); + } + option_register_unsolicted_handlers (gsm, MM_AT_SERIAL_PORT (port)); } - command = g_strdup_printf ("AT_OPSYS=%d,2", i); - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); - g_assert (primary); - mm_serial_port_queue_command (primary, command, 3, set_network_mode_done, info); - g_free (command); + return !!port; } /*****************************************************************************/ @@ -221,24 +206,37 @@ set_network_mode (MMModemGsmNetwork *modem, static void modem_init (MMModem *modem_class) { - modem_class->enable = enable; + modem_class->disable = disable; + modem_class->grab_port = grab_port; } static void -modem_gsm_network_init (MMModemGsmNetwork *class) +mm_modem_option_init (MMModemOption *self) { - class->set_network_mode = set_network_mode; - class->get_network_mode = get_network_mode; } static void -mm_modem_option_init (MMModemOption *self) +dispose (GObject *object) { + MMModemOptionPrivate *priv = MM_MODEM_OPTION_GET_PRIVATE (object); + + if (priv->enable_wait_id) + g_source_remove (priv->enable_wait_id); } static void mm_modem_option_class_init (MMModemOptionClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMGenericGsmClass *gsm_class = MM_GENERIC_GSM_CLASS (klass); + mm_modem_option_parent_class = g_type_class_peek_parent (klass); + g_type_class_add_private (object_class, sizeof (MMModemOptionPrivate)); + + object_class->dispose = dispose; + gsm_class->do_enable_power_up_done = real_do_enable_power_up_done; + gsm_class->set_allowed_mode = set_allowed_mode; + gsm_class->get_allowed_mode = get_allowed_mode; + gsm_class->get_access_technology = get_access_technology; } diff --git a/plugins/mm-modem-sierra-cdma.c b/plugins/mm-modem-sierra-cdma.c index 4f3140b..fc62bf6 100644 --- a/plugins/mm-modem-sierra-cdma.c +++ b/plugins/mm-modem-sierra-cdma.c @@ -28,6 +28,7 @@ #include "mm-callback-info.h" #include "mm-serial-port.h" #include "mm-serial-parsers.h" +#include "mm-modem-helpers.h" G_DEFINE_TYPE (MMModemSierraCdma, mm_modem_sierra_cdma, MM_TYPE_GENERIC_CDMA) @@ -75,13 +76,19 @@ mm_modem_sierra_cdma_new (const char *device, #define SYS_MODE_NO_SERVICE_TAG "NO SRV" #define SYS_MODE_EVDO_TAG "HDR" #define SYS_MODE_1X_TAG "1x" +#define SYS_MODE_CDMA_TAG "CDMA" #define EVDO_REV_TAG "HDR Revision:" #define SID_TAG "SID:" static gboolean -get_roam_value (const char *reply, const char *tag, gboolean *roaming) +get_roam_value (const char *reply, + const char *tag, + gboolean is_eri, + gboolean *out_roaming) { char *p; + gboolean success; + guint32 ind = 0; p = strstr (reply, tag); if (!p) @@ -90,11 +97,26 @@ get_roam_value (const char *reply, const char *tag, gboolean *roaming) p += strlen (tag); while (*p && isspace (*p)) p++; + + /* Use generic ERI parsing if it's an ERI */ + if (is_eri) { + success = mm_cdma_parse_eri (p, out_roaming, &ind, NULL); + if (success) { + /* Sierra redefines ERI 0, 1, and 2 */ + if (ind == 0) + *out_roaming = FALSE; /* home */ + else if (ind == 1 || ind == 2) + *out_roaming = TRUE; /* roaming */ + } + return success; + } + + /* If it's not an ERI, roaming is just true/false */ if (*p == '1') { - *roaming = TRUE; + *out_roaming = TRUE; return TRUE; } else if (*p == '0') { - *roaming = FALSE; + *out_roaming = FALSE; return TRUE; } @@ -109,8 +131,14 @@ sys_mode_has_service (SysMode mode) || mode == SYS_MODE_EVDO_REVA); } +static gboolean +sys_mode_is_evdo (SysMode mode) +{ + return (mode == SYS_MODE_EVDO_REV0 || mode == SYS_MODE_EVDO_REVA); +} + static void -status_done (MMSerialPort *port, +status_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -122,16 +150,16 @@ status_done (MMSerialPort *port, gboolean have_sid = FALSE; SysMode evdo_mode = SYS_MODE_UNKNOWN; SysMode sys_mode = SYS_MODE_UNKNOWN; - gboolean cdma_1x_set = FALSE, evdo_set = FALSE; + gboolean evdo_roam = FALSE, cdma1x_roam = FALSE; if (error) { - info->error = g_error_copy (error); + /* Leave superclass' reg state alone if AT!STATUS isn't supported */ goto done; } lines = g_strsplit_set (response->str, "\n\r", 0); if (!lines) { - /* Whatever, just use default registration state */ + /* Whatever, just use superclass' registration state */ goto done; } @@ -197,29 +225,10 @@ status_done (MMSerialPort *port, } /* Roaming */ - if (get_roam_value (*iter, ROAM_1X_TAG, &bool_val)) { - mm_generic_cdma_query_reg_state_set_callback_1x_state (info, - bool_val ? MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING : - MM_MODEM_CDMA_REGISTRATION_STATE_HOME); - cdma_1x_set = TRUE; - } - if (get_roam_value (*iter, ROAM_EVDO_TAG, &bool_val)) { - mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, - bool_val ? MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING : - MM_MODEM_CDMA_REGISTRATION_STATE_HOME); - evdo_set = TRUE; - } - if (get_roam_value (*iter, GENERIC_ROAM_TAG, &bool_val)) { - MMModemCdmaRegistrationState reg_state; - - reg_state = bool_val ? MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING : - MM_MODEM_CDMA_REGISTRATION_STATE_HOME; - - mm_generic_cdma_query_reg_state_set_callback_1x_state (info, reg_state); - mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, reg_state); - cdma_1x_set = TRUE; - evdo_set = TRUE; - } + get_roam_value (*iter, ROAM_1X_TAG, TRUE, &cdma1x_roam); + get_roam_value (*iter, ROAM_EVDO_TAG, TRUE, &evdo_roam); + if (get_roam_value (*iter, GENERIC_ROAM_TAG, FALSE, &bool_val)) + cdma1x_roam = evdo_roam = bool_val; /* Current system mode */ p = strstr (*iter, SYS_MODE_TAG); @@ -231,7 +240,8 @@ status_done (MMSerialPort *port, sys_mode = SYS_MODE_NO_SERVICE; else if (!strncmp (p, SYS_MODE_EVDO_TAG, strlen (SYS_MODE_EVDO_TAG))) sys_mode = SYS_MODE_EVDO_REV0; - else if (!strncmp (p, SYS_MODE_1X_TAG, strlen (SYS_MODE_1X_TAG))) + else if ( !strncmp (p, SYS_MODE_1X_TAG, strlen (SYS_MODE_1X_TAG)) + || !strncmp (p, SYS_MODE_CDMA_TAG, strlen (SYS_MODE_CDMA_TAG))) sys_mode = SYS_MODE_CDMA_1X; } @@ -259,24 +269,36 @@ status_done (MMSerialPort *port, } /* Update current system mode */ - if (sys_mode == SYS_MODE_EVDO_REV0 || sys_mode == SYS_MODE_EVDO_REVA) { + if (sys_mode_is_evdo (sys_mode)) { /* Prefer the explicit EVDO mode from EVDO_REV_TAG */ if (evdo_mode != SYS_MODE_UNKNOWN) sys_mode = evdo_mode; } priv->sys_mode = sys_mode; - if (registered || have_sid || sys_mode_has_service (sys_mode)) { - /* As a backup, if for some reason the registration states didn't get - * figured out by parsing the status info, set some generic registration - * states here. - */ - if (!cdma_1x_set) - mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED); - - /* Ensure EVDO registration mode is set if we're at least in EVDO mode */ - if (!evdo_set && (sys_mode == SYS_MODE_EVDO_REV0 || sys_mode == SYS_MODE_EVDO_REVA)) - mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED); + /* If the modem didn't report explicit registration with "Modem has + * registered" then get registration status by looking at either system + * mode or (for older devices that don't report that) just the SID. + */ + if (!registered) { + if (sys_mode != SYS_MODE_UNKNOWN) + registered = sys_mode_has_service (sys_mode); + else + registered = have_sid; + } + + if (registered) { + mm_generic_cdma_query_reg_state_set_callback_1x_state (info, + cdma1x_roam ? MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING : + MM_MODEM_CDMA_REGISTRATION_STATE_HOME); + + if (sys_mode_is_evdo (sys_mode)) { + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, + evdo_roam ? MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING : + MM_MODEM_CDMA_REGISTRATION_STATE_HOME); + } else { + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + } } else { /* Not registered */ mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); @@ -289,35 +311,27 @@ done: static void query_registration_state (MMGenericCdma *cdma, + MMModemCdmaRegistrationState cur_cdma_state, + MMModemCdmaRegistrationState cur_evdo_state, MMModemCdmaRegistrationStateFn callback, gpointer user_data) { MMCallbackInfo *info; - MMSerialPort *primary, *secondary; - MMSerialPort *port; - - port = primary = mm_generic_cdma_get_port (cdma, MM_PORT_TYPE_PRIMARY); - secondary = mm_generic_cdma_get_port (cdma, MM_PORT_TYPE_SECONDARY); + MMAtSerialPort *port; - info = mm_generic_cdma_query_reg_state_callback_info_new (cdma, callback, user_data); - - if (mm_port_get_connected (MM_PORT (primary))) { - if (!secondary) { - info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, - "Cannot get query registration state while connected"); - mm_callback_info_schedule (info); - return; - } + info = mm_generic_cdma_query_reg_state_callback_info_new (cdma, cur_cdma_state, cur_evdo_state, callback, user_data); - /* Use secondary port if primary is connected */ - port = secondary; + port = mm_generic_cdma_get_best_at_port (cdma, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; } - mm_serial_port_queue_command (port, "!STATUS", 3, status_done, info); + mm_at_serial_port_queue_command (port, "!STATUS", 3, status_done, info); } static void -pcstate_done (MMSerialPort *port, +pcstate_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -334,14 +348,14 @@ post_enable (MMGenericCdma *cdma, gpointer user_data) { MMCallbackInfo *info; - MMSerialPort *primary; + MMAtSerialPort *primary; info = mm_callback_info_new (MM_MODEM (cdma), callback, user_data); - primary = mm_generic_cdma_get_port (cdma, MM_PORT_TYPE_PRIMARY); + primary = mm_generic_cdma_get_at_port (cdma, MM_PORT_TYPE_PRIMARY); g_assert (primary); - mm_serial_port_queue_command (primary, "!pcstate=1", 5, pcstate_done, info); + mm_at_serial_port_queue_command (primary, "!pcstate=1", 5, pcstate_done, info); } static void @@ -350,14 +364,14 @@ post_disable (MMGenericCdma *cdma, gpointer user_data) { MMCallbackInfo *info; - MMSerialPort *primary; + MMAtSerialPort *primary; info = mm_callback_info_new (MM_MODEM (cdma), callback, user_data); - primary = mm_generic_cdma_get_port (cdma, MM_PORT_TYPE_PRIMARY); + primary = mm_generic_cdma_get_at_port (cdma, MM_PORT_TYPE_PRIMARY); g_assert (primary); - mm_serial_port_queue_command (primary, "!pcstate=0", 5, pcstate_done, info); + mm_at_serial_port_queue_command (primary, "!pcstate=0", 5, pcstate_done, info); } /*****************************************************************************/ diff --git a/plugins/mm-modem-sierra-gsm.c b/plugins/mm-modem-sierra-gsm.c index ee82234..bf5df31 100644 --- a/plugins/mm-modem-sierra-gsm.c +++ b/plugins/mm-modem-sierra-gsm.c @@ -11,9 +11,10 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ +#include #include #include #include @@ -21,12 +22,18 @@ #include "mm-modem-sierra-gsm.h" #include "mm-errors.h" #include "mm-callback-info.h" +#include "mm-modem-helpers.h" static void modem_init (MMModem *modem_class); G_DEFINE_TYPE_EXTENDED (MMModemSierraGsm, mm_modem_sierra_gsm, MM_TYPE_GENERIC_GSM, 0, G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)) +#define MM_MODEM_SIERRA_GSM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_SIERRA_GSM, MMModemSierraGsmPrivate)) + +typedef struct { + guint enable_wait_id; +} MMModemSierraGsmPrivate; MMModem * mm_modem_sierra_gsm_new (const char *device, @@ -44,55 +51,233 @@ mm_modem_sierra_gsm_new (const char *device, NULL)); } -/*****************************************************************************/ -/* Modem class override functions */ /*****************************************************************************/ static void -pin_check_done (MMModem *modem, GError *error, gpointer user_data) +get_allowed_mode_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GRegex *r = NULL; + GMatchInfo *match_info; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) + goto done; + + /* Example response: !SELRAT: 03, UMTS 3G Preferred */ + r = g_regex_new ("!SELRAT:\\s*(\\d+).*$", 0, 0, NULL); + if (!r) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Failed to parse the allowed mode response"); + goto done; + } + + if (g_regex_match_full (r, response->str, response->len, 0, 0, &match_info, &info->error)) { + MMModemGsmAllowedMode mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; + char *str; + + str = g_match_info_fetch (match_info, 1); + switch (atoi (str)) { + case 0: + mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; + break; + case 1: + mode = MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY; + break; + case 2: + mode = MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY; + break; + case 3: + mode = MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED; + break; + case 4: + mode = MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED; + break; + default: + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Failed to parse the allowed mode response: '%s'", + response->str); + break; + } + g_free (str); + + mm_callback_info_set_result (info, GUINT_TO_POINTER (mode), NULL); + } - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (modem), error, info); +done: + if (r) + g_regex_unref (r); + mm_callback_info_schedule (info); } -static gboolean -sierra_enabled (gpointer data) +static void +get_allowed_mode (MMGenericGsm *gsm, + MMModemUIntFn callback, + gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) data; + MMCallbackInfo *info; + MMAtSerialPort *primary; - /* Now check the PIN explicitly, sierra doesn't seem to report - * that it needs it otherwise. - */ - mm_generic_gsm_check_pin (MM_GENERIC_GSM (info->modem), pin_check_done, info); - return FALSE; + info = mm_callback_info_uint_new (MM_MODEM (gsm), callback, user_data); + + /* Sierra secondary ports don't have full AT command interpreters */ + primary = mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY); + if (!primary || mm_port_get_connected (MM_PORT (primary))) { + g_set_error_literal (&info->error, MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, + "Cannot perform this operation while connected"); + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (primary, "!SELRAT?", 3, get_allowed_mode_done, info); } static void -parent_enable_done (MMModem *modem, GError *error, gpointer user_data) +set_allowed_mode_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - if (error) { - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (modem), error, info); + if (error) + info->error = g_error_copy (error); + + mm_callback_info_schedule (info); +} + +static void +set_allowed_mode (MMGenericGsm *gsm, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *primary; + char *command; + int idx = 0; + + info = mm_callback_info_new (MM_MODEM (gsm), callback, user_data); + + /* Sierra secondary ports don't have full AT command interpreters */ + primary = mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY); + if (!primary || mm_port_get_connected (MM_PORT (primary))) { + g_set_error_literal (&info->error, MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, + "Cannot perform this operation while connected"); + mm_callback_info_schedule (info); return; } - /* Sierra returns OK on +CFUN=1 right away but needs some time - * to finish initialization. - */ - g_timeout_add_seconds (10, sierra_enabled, info); + switch (mode) { + case MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY: + idx = 2; + break; + case MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY: + idx = 1; + break; + case MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED: + idx = 4; + break; + case MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED: + idx = 3; + break; + case MM_MODEM_GSM_ALLOWED_MODE_ANY: + default: + break; + } + + command = g_strdup_printf ("!SELRAT=%d", idx); + mm_at_serial_port_queue_command (primary, command, 3, set_allowed_mode_done, info); + g_free (command); } static void -enable (MMModem *modem, MMModemFn callback, gpointer user_data) +get_act_request_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { - MMModem *parent_modem_iface; + MMCallbackInfo *info = user_data; + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + const char *p; + + if (error) + info->error = g_error_copy (error); + else { + p = mm_strip_tag (response->str, "*CNTI:"); + p = strchr (p, ','); + if (p) + act = mm_gsm_string_to_access_tech (p + 1); + } + + mm_callback_info_set_result (info, GUINT_TO_POINTER (act), NULL); + mm_callback_info_schedule (info); +} + +static void +get_access_technology (MMGenericGsm *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMAtSerialPort *port; MMCallbackInfo *info; - info = mm_callback_info_new (modem, callback, user_data); - parent_modem_iface = g_type_interface_peek_parent (MM_MODEM_GET_INTERFACE (modem)); - parent_modem_iface->enable (modem, parent_enable_done, info); + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (modem, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "*CNTI=0", 3, get_act_request_done, info); +} + +/*****************************************************************************/ +/* Modem class override functions */ +/*****************************************************************************/ + +static gboolean +sierra_enabled (gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMGenericGsm *modem; + MMModemSierraGsmPrivate *priv; + + /* Make sure we don't use an invalid modem that may have been removed */ + if (info->modem) { + modem = MM_GENERIC_GSM (info->modem); + priv = MM_MODEM_SIERRA_GSM_GET_PRIVATE (modem); + priv->enable_wait_id = 0; + MM_GENERIC_GSM_CLASS (mm_modem_sierra_gsm_parent_class)->do_enable_power_up_done (modem, NULL, NULL, info); + } + return FALSE; +} + +static void +real_do_enable_power_up_done (MMGenericGsm *gsm, + GString *response, + GError *error, + MMCallbackInfo *info) +{ + MMModemSierraGsmPrivate *priv = MM_MODEM_SIERRA_GSM_GET_PRIVATE (gsm); + + if (error) { + /* Chain up to parent */ + MM_GENERIC_GSM_CLASS (mm_modem_sierra_gsm_parent_class)->do_enable_power_up_done (gsm, NULL, error, info); + return; + } + + /* Some Sierra devices return OK on +CFUN=1 right away but need some time + * to finish initialization. + */ + g_warn_if_fail (priv->enable_wait_id == 0); + priv->enable_wait_id = g_timeout_add_seconds (10, sierra_enabled, info); } static gboolean @@ -108,18 +293,25 @@ grab_port (MMModem *modem, MMPort *port; if (suggested_type == MM_PORT_TYPE_UNKNOWN) { - if (!mm_generic_gsm_get_port (gsm, MM_PORT_TYPE_PRIMARY)) + if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY)) ptype = MM_PORT_TYPE_PRIMARY; - else if (!mm_generic_gsm_get_port (gsm, MM_PORT_TYPE_SECONDARY)) + else if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_SECONDARY)) ptype = MM_PORT_TYPE_SECONDARY; } else ptype = suggested_type; port = mm_generic_gsm_grab_port (gsm, subsys, name, ptype, error); - if (port && MM_IS_SERIAL_PORT (port)) + if (port && MM_IS_AT_SERIAL_PORT (port)) { + GRegex *regex; + g_object_set (G_OBJECT (port), MM_PORT_CARRIER_DETECT, FALSE, NULL); + regex = g_regex_new ("\\r\\n\\+PACSP0\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); + g_regex_unref (regex); + } + return !!port; } @@ -128,7 +320,6 @@ grab_port (MMModem *modem, static void modem_init (MMModem *modem_class) { - modem_class->enable = enable; modem_class->grab_port = grab_port; } @@ -137,9 +328,28 @@ mm_modem_sierra_gsm_init (MMModemSierraGsm *self) { } +static void +dispose (GObject *object) +{ + MMModemSierraGsmPrivate *priv = MM_MODEM_SIERRA_GSM_GET_PRIVATE (object); + + if (priv->enable_wait_id) + g_source_remove (priv->enable_wait_id); +} + static void mm_modem_sierra_gsm_class_init (MMModemSierraGsmClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMGenericGsmClass *gsm_class = MM_GENERIC_GSM_CLASS (klass); + mm_modem_sierra_gsm_parent_class = g_type_class_peek_parent (klass); + g_type_class_add_private (object_class, sizeof (MMModemSierraGsmPrivate)); + + object_class->dispose = dispose; + gsm_class->do_enable_power_up_done = real_do_enable_power_up_done; + gsm_class->set_allowed_mode = set_allowed_mode; + gsm_class->get_allowed_mode = get_allowed_mode; + gsm_class->get_access_technology = get_access_technology; } diff --git a/plugins/mm-modem-simtech-gsm.c b/plugins/mm-modem-simtech-gsm.c new file mode 100644 index 0000000..07820b3 --- /dev/null +++ b/plugins/mm-modem-simtech-gsm.c @@ -0,0 +1,471 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include "mm-modem-simtech-gsm.h" +#include "mm-at-serial-port.h" +#include "mm-errors.h" +#include "mm-callback-info.h" +#include "mm-modem-helpers.h" + +static void modem_init (MMModem *modem_class); + +G_DEFINE_TYPE_EXTENDED (MMModemSimtechGsm, mm_modem_simtech_gsm, MM_TYPE_GENERIC_GSM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)) + +MMModem * +mm_modem_simtech_gsm_new (const char *device, + const char *driver, + const char *plugin) +{ + g_return_val_if_fail (device != NULL, NULL); + g_return_val_if_fail (driver != NULL, NULL); + g_return_val_if_fail (plugin != NULL, NULL); + + return MM_MODEM (g_object_new (MM_TYPE_MODEM_SIMTECH_GSM, + MM_MODEM_MASTER_DEVICE, device, + MM_MODEM_DRIVER, driver, + MM_MODEM_PLUGIN, plugin, + NULL)); +} + +/*****************************************************************************/ + +#define ACQ_ORDER_TAG "acq-order" + +static void +get_mode_pref_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *p; + gint modepref = -1; + guint32 acqord; + MMModemGsmAllowedMode allowed = MM_MODEM_GSM_ALLOWED_MODE_ANY; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) + goto done; + + p = mm_strip_tag (response->str, "+CNMP:"); + if (!p) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Failed to parse the mode preference response"); + goto done; + } + + acqord = GPOINTER_TO_UINT (mm_callback_info_get_data (info, ACQ_ORDER_TAG)); + modepref = atoi (p); + + if (modepref == 2) { + /* Automatic */ + if (acqord == 0) + allowed = MM_MODEM_GSM_ALLOWED_MODE_ANY; + else if (acqord == 1) + allowed = MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED; + else if (acqord == 2) + allowed = MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED; + else { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Unknown acqisition order preference %d", + acqord); + } + } else if (modepref == 13) { + /* GSM only */ + allowed = MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY; + } else if (modepref == 14) { + /* WCDMA only */ + allowed = MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY; + } else { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Unknown mode preference %d", + modepref); + } + +done: + if (!info->error) + mm_callback_info_set_result (info, GUINT_TO_POINTER (allowed), NULL); + mm_callback_info_schedule (info); +} + +static void +get_acq_order_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + const char *p; + gint acqord = -1; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) + goto done; + + p = mm_strip_tag (response->str, "+CNAOP:"); + if (!p) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Failed to parse the acqisition order response"); + goto done; + } + + acqord = atoi (p); + if (acqord < 0 || acqord > 2) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Unknown acquisition order response %d", + acqord); + } else { + /* Cache the acquisition preference */ + mm_callback_info_set_data (info, ACQ_ORDER_TAG, GUINT_TO_POINTER (acqord), NULL); + } + +done: + if (info->error) + mm_callback_info_schedule (info); + else + mm_at_serial_port_queue_command (port, "+CNMP?", 3, get_mode_pref_done, info); +} + +static void +get_allowed_mode (MMGenericGsm *gsm, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *port; + + info = mm_callback_info_uint_new (MM_MODEM (gsm), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "+CNAOP?", 3, get_acq_order_done, info); +} + +static void +set_acq_order_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) + info->error = g_error_copy (error); + + mm_callback_info_schedule (info); +} + +static void +set_mode_pref_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + guint32 naop; + char *command; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + mm_callback_info_schedule (info); + return; + } + + naop = GPOINTER_TO_UINT (mm_callback_info_get_data (info, ACQ_ORDER_TAG)); + command = g_strdup_printf ("+CNAOP=%u", naop); + mm_at_serial_port_queue_command (port, command, 3, set_acq_order_done, info); + g_free (command); +} + +static void +set_allowed_mode (MMGenericGsm *gsm, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *port; + char *command; + guint32 nmp = 2; /* automatic mode preference */ + guint32 naop = 0; /* automatic acquisition order */ + + info = mm_callback_info_new (MM_MODEM (gsm), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + switch (mode) { + case MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY: + nmp = 13; + break; + case MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY: + nmp = 14; + break; + case MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED: + naop = 2; + break; + case MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED: + naop = 3; + break; + case MM_MODEM_GSM_ALLOWED_MODE_ANY: + default: + break; + } + + mm_callback_info_set_data (info, ACQ_ORDER_TAG, GUINT_TO_POINTER (naop), NULL); + + command = g_strdup_printf ("+CNMP=%u", nmp); + mm_at_serial_port_queue_command (port, command, 3, set_mode_pref_done, info); + g_free (command); +} + +static MMModemGsmAccessTech +simtech_act_to_mm_act (int nsmod) +{ + if (nsmod == 1) + return MM_MODEM_GSM_ACCESS_TECH_GSM; + else if (nsmod == 2) + return MM_MODEM_GSM_ACCESS_TECH_GPRS; + else if (nsmod == 3) + return MM_MODEM_GSM_ACCESS_TECH_EDGE; + else if (nsmod == 4) + return MM_MODEM_GSM_ACCESS_TECH_UMTS; + else if (nsmod == 5) + return MM_MODEM_GSM_ACCESS_TECH_HSDPA; + else if (nsmod == 6) + return MM_MODEM_GSM_ACCESS_TECH_HSUPA; + else if (nsmod == 7) + return MM_MODEM_GSM_ACCESS_TECH_HSPA; + + return MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; +} + +static void +get_act_tech_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + const char *p; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + mm_callback_info_schedule (info); + return; + } + + p = mm_strip_tag (response->str, "+CNSMOD:"); + if (p) + p = strchr (p, ','); + + if (!p || !isdigit (*(p + 1))) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Failed to parse the access technology response"); + } else { + act = simtech_act_to_mm_act (atoi (p + 1)); + mm_callback_info_set_result (info, GUINT_TO_POINTER (act), NULL); + } + + mm_callback_info_schedule (info); +} + +static void +get_access_technology (MMGenericGsm *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMAtSerialPort *port; + MMCallbackInfo *info; + + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (modem, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "AT+CNSMOD?", 3, get_act_tech_done, info); +} + +static void +handle_act_change (MMAtSerialPort *port, + GMatchInfo *match_info, + gpointer user_data) +{ + MMModemSimtechGsm *self = MM_MODEM_SIMTECH_GSM (user_data); + MMModemGsmAccessTech act; + char *str; + + str = g_match_info_fetch (match_info, 1); + if (str && strlen (str)) { + act = simtech_act_to_mm_act (atoi (str)); + mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (self), act); + } + g_free (str); +} + +/*****************************************************************************/ + +static void +real_do_enable_power_up_done (MMGenericGsm *gsm, + GString *response, + GError *error, + MMCallbackInfo *info) +{ + if (!error) { + MMAtSerialPort *primary; + + /* Enable unsolicited result codes */ + primary = mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY); + g_assert (primary); + + /* Autoreport access technology changes */ + mm_at_serial_port_queue_command (primary, "+CNSMOD=1", 5, NULL, NULL); + + /* Autoreport CSQ (first arg), and only report when it changes (second arg) */ + mm_at_serial_port_queue_command (primary, "+AUTOCSQ=1,1", 5, NULL, NULL); + } + + /* Chain up to parent */ + MM_GENERIC_GSM_CLASS (mm_modem_simtech_gsm_parent_class)->do_enable_power_up_done (gsm, NULL, error, info); +} + +/*****************************************************************************/ + +typedef struct { + MMModem *modem; + MMModemFn callback; + gpointer user_data; +} DisableInfo; + +static void +disable_unsolicited_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) + +{ + MMModem *parent_modem_iface; + DisableInfo *info = user_data; + + parent_modem_iface = g_type_interface_peek_parent (MM_MODEM_GET_INTERFACE (info->modem)); + parent_modem_iface->disable (info->modem, info->callback, info->user_data); + g_free (info); +} + +static void +disable (MMModem *modem, + MMModemFn callback, + gpointer user_data) +{ + MMAtSerialPort *primary; + DisableInfo *info; + + info = g_malloc0 (sizeof (DisableInfo)); + info->callback = callback; + info->user_data = user_data; + info->modem = modem; + + primary = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); + g_assert (primary); + + /* Turn off unsolicited responses */ + mm_at_serial_port_queue_command (primary, "+CNSMOD=0;+AUTOCSQ=0", 5, disable_unsolicited_done, info); +} + +static gboolean +grab_port (MMModem *modem, + const char *subsys, + const char *name, + MMPortType suggested_type, + gpointer user_data, + GError **error) +{ + MMGenericGsm *gsm = MM_GENERIC_GSM (modem); + MMPortType ptype = MM_PORT_TYPE_IGNORED; + MMPort *port; + + if (suggested_type == MM_PORT_TYPE_UNKNOWN) { + if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY)) + ptype = MM_PORT_TYPE_PRIMARY; + else if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_SECONDARY)) + ptype = MM_PORT_TYPE_SECONDARY; + } else + ptype = suggested_type; + + port = mm_generic_gsm_grab_port (gsm, subsys, name, ptype, error); + + if (port && MM_IS_AT_SERIAL_PORT (port)) { + GRegex *regex; + + regex = g_regex_new ("\\r\\n\\+CNSMOD:\\s*(\\d)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, handle_act_change, modem, NULL); + g_regex_unref (regex); + } + + return !!port; +} + +/*****************************************************************************/ + +static void +modem_init (MMModem *modem_class) +{ + modem_class->disable = disable; + modem_class->grab_port = grab_port; +} + +static void +mm_modem_simtech_gsm_init (MMModemSimtechGsm *self) +{ +} + +static void +mm_modem_simtech_gsm_class_init (MMModemSimtechGsmClass *klass) +{ + MMGenericGsmClass *gsm_class = MM_GENERIC_GSM_CLASS (klass); + + mm_modem_simtech_gsm_parent_class = g_type_class_peek_parent (klass); + + gsm_class->do_enable_power_up_done = real_do_enable_power_up_done; + gsm_class->set_allowed_mode = set_allowed_mode; + gsm_class->get_allowed_mode = get_allowed_mode; + gsm_class->get_access_technology = get_access_technology; +} + diff --git a/plugins/mm-modem-simtech-gsm.h b/plugins/mm-modem-simtech-gsm.h new file mode 100644 index 0000000..0ba3c43 --- /dev/null +++ b/plugins/mm-modem-simtech-gsm.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#ifndef MM_MODEM_SIMTECH_GSM_H +#define MM_MODEM_SIMTECH_GSM_H + +#include "mm-generic-gsm.h" + +#define MM_TYPE_MODEM_SIMTECH_GSM (mm_modem_simtech_gsm_get_type ()) +#define MM_MODEM_SIMTECH_GSM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_SIMTECH_GSM, MMModemSimtechGsm)) +#define MM_MODEM_SIMTECH_GSM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_MODEM_SIMTECH_GSM, MMModemSimtechGsmClass)) +#define MM_IS_MODEM_SIMTECH_GSM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_SIMTECH_GSM)) +#define MM_IS_MODEM_SIMTECH_GSM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_MODEM_SIMTECH_GSM)) +#define MM_MODEM_SIMTECH_GSM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_MODEM_SIMTECH_GSM, MMModemSimtechGsmClass)) + +typedef struct { + MMGenericGsm parent; +} MMModemSimtechGsm; + +typedef struct { + MMGenericGsmClass parent; +} MMModemSimtechGsmClass; + +GType mm_modem_simtech_gsm_get_type (void); + +MMModem *mm_modem_simtech_gsm_new (const char *device, + const char *driver, + const char *plugin); + +#endif /* MM_MODEM_SIMTECH_H */ diff --git a/plugins/mm-modem-zte.c b/plugins/mm-modem-zte.c index 92c23ae..ba8a1db 100644 --- a/plugins/mm-modem-zte.c +++ b/plugins/mm-modem-zte.c @@ -11,9 +11,10 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ +#include #include #include #include @@ -22,6 +23,7 @@ #include "mm-serial-port.h" #include "mm-errors.h" #include "mm-callback-info.h" +#include "mm-modem-helpers.h" static void modem_init (MMModem *modem_class); @@ -32,6 +34,8 @@ G_DEFINE_TYPE_EXTENDED (MMModemZte, mm_modem_zte, MM_TYPE_GENERIC_GSM, 0, typedef struct { gboolean init_retried; + guint32 cpms_tries; + guint cpms_timeout; } MMModemZtePrivate; MMModem * @@ -51,35 +55,278 @@ mm_modem_zte_new (const char *device, } /*****************************************************************************/ -/* Modem class override functions */ + +static void +zte_access_tech_changed (MMAtSerialPort *port, + GMatchInfo *info, + gpointer user_data) +{ + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + char *str; + + str = g_match_info_fetch (info, 1); + if (str) + act = mm_gsm_string_to_access_tech (str); + g_free (str); + + mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (user_data), act); +} + /*****************************************************************************/ static void -init_modem_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +get_allowed_mode_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GRegex *r = NULL; + GMatchInfo *match_info; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) + goto done; + + r = g_regex_new ("+ZSNT:\\s*(\\d),(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL); + if (!r) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Failed to parse the allowed mode response"); + goto done; + } - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); + if (g_regex_match_full (r, response->str, response->len, 0, 0, &match_info, &info->error)) { + MMModemGsmAllowedMode mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; + char *str; + int cm_mode = -1, pref_acq = -1; + + str = g_match_info_fetch (match_info, 1); + cm_mode = atoi (str); + g_free (str); + + str = g_match_info_fetch (match_info, 3); + pref_acq = atoi (str); + g_free (str); + + g_match_info_free (match_info); + + if (cm_mode < 0 || cm_mode > 2 || pref_acq < 0 || pref_acq > 2) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Failed to parse the allowed mode response: '%s'", + response->str); + goto done; + } + + if (cm_mode == 0) { /* Both 2G and 3G allowed */ + if (pref_acq == 0) + mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; + else if (pref_acq == 1) + mode = MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED; + else if (pref_acq == 2) + mode = MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED; + } else if (cm_mode == 1) /* GSM only */ + mode = MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY; + else if (cm_mode == 2) /* WCDMA only */ + mode = MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY; + + mm_callback_info_set_result (info, GUINT_TO_POINTER (mode), NULL); + } + +done: + if (r) + g_regex_unref (r); + mm_callback_info_schedule (info); +} + +static void +get_allowed_mode (MMGenericGsm *gsm, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *port; + + info = mm_callback_info_uint_new (MM_MODEM (gsm), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "AT+ZSNT?", 3, get_allowed_mode_done, info); } static void -pin_check_done (MMModem *modem, GError *error, gpointer user_data) +set_allowed_mode_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - MMSerialPort *primary; - if (error) { - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (modem), error, info); + if (error) + info->error = g_error_copy (error); + + mm_callback_info_schedule (info); +} + +static void +set_allowed_mode (MMGenericGsm *gsm, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *port; + char *command; + int cm_mode = 0, pref_acq = 0; + + info = mm_callback_info_new (MM_MODEM (gsm), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (gsm, &info->error); + if (!port) { + mm_callback_info_schedule (info); return; } - /* Finish the initialization */ - primary = mm_generic_gsm_get_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); - g_assert (primary); - mm_serial_port_queue_command (primary, "Z E0 V1 X4 &C1 +CMEE=1;+CFUN=1;", 10, init_modem_done, info); + switch (mode) { + case MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY: + cm_mode = 1; + pref_acq = 0; + break; + case MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY: + cm_mode = 2; + pref_acq = 0; + break; + case MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED: + cm_mode = 0; + pref_acq = 1; + break; + case MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED: + cm_mode = 0; + pref_acq = 2; + break; + case MM_MODEM_GSM_ALLOWED_MODE_ANY: + default: + break; + } + + command = g_strdup_printf ("AT+ZSNT=%d,0,%d", cm_mode, pref_acq); + mm_at_serial_port_queue_command (port, command, 3, set_allowed_mode_done, info); + g_free (command); +} + +static void +get_act_request_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + const char *p; + + if (error) + info->error = g_error_copy (error); + else { + /* Sample response from an MF626: + * +ZPAS: "GPRS/EDGE","CS_ONLY" + */ + p = mm_strip_tag (response->str, "+ZPAS:"); + act = mm_gsm_string_to_access_tech (p); + } + + mm_callback_info_set_result (info, GUINT_TO_POINTER (act), NULL); + mm_callback_info_schedule (info); +} + +static void +get_access_technology (MMGenericGsm *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMAtSerialPort *port; + MMCallbackInfo *info; + + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (modem, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "+ZPAS?", 3, get_act_request_done, info); +} + +/*****************************************************************************/ +/* Modem class override functions */ +/*****************************************************************************/ + +static void cpms_try_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data); + +static gboolean +cpms_timeout_cb (gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMModem *modem = info->modem; + MMAtSerialPort *primary; + + if (modem) { + MM_MODEM_ZTE_GET_PRIVATE (modem)->cpms_timeout = 0; + primary = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (modem), MM_PORT_TYPE_PRIMARY); + g_assert (primary); + mm_at_serial_port_queue_command (primary, "+CPMS?", 10, cpms_try_done, info); + } + return FALSE; +} + +static void +cpms_try_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMModemZtePrivate *priv = MM_MODEM_ZTE_GET_PRIVATE (info->modem); + + if (error && g_error_matches (error, MM_MOBILE_ERROR, MM_MOBILE_ERROR_SIM_BUSY)) { + if (priv->cpms_tries++ < 4) { + if (priv->cpms_timeout) + g_source_remove (priv->cpms_timeout); + + /* Have to try a few times; sometimes the SIM is busy */ + priv->cpms_timeout = g_timeout_add_seconds (2, cpms_timeout_cb, info); + return; + } else { + /* oh well, proceed... */ + error = NULL; + } + } + + mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); +} + +static void +init_modem_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + /* Attempt to disable floods of "+ZUSIMR:2" unsolicited responses that + * eventually fill up the device's buffers and make it crash. Normally + * done during probing, but if the device has a PIN enabled it won't + * accept the +CPMS? during the probe and we have to do it here. + */ + mm_at_serial_port_queue_command (port, "+CPMS?", 10, cpms_try_done, info); } static void enable_flash_done (MMSerialPort *port, @@ -87,7 +334,7 @@ static void enable_flash_done (MMSerialPort *port, gpointer user_data); static void -pre_init_done (MMSerialPort *port, +pre_init_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -98,15 +345,14 @@ pre_init_done (MMSerialPort *port, if (error) { /* Retry the init string one more time; the modem sometimes throws it away */ if ( !priv->init_retried - && g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_RESPONSE_TIMEOUT)) { + && g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { priv->init_retried = TRUE; - enable_flash_done (port, NULL, user_data); + enable_flash_done (MM_SERIAL_PORT (port), NULL, user_data); } else mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); } else { - /* Now check the PIN explicitly, zte doesn't seem to report - that it needs it otherwise */ - mm_generic_gsm_check_pin (MM_GENERIC_GSM (info->modem), pin_check_done, info); + /* Finish the initialization */ + mm_at_serial_port_queue_command (port, "Z E0 V1 X4 &C1 +CMEE=1;+CFUN=1;", 10, init_modem_done, info); } } @@ -118,7 +364,7 @@ enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data) if (error) mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); else - mm_serial_port_queue_command (port, "E0 V1", 3, pre_init_done, user_data); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "E0 V1", 3, pre_init_done, user_data); } static void @@ -126,15 +372,15 @@ do_enable (MMGenericGsm *modem, MMModemFn callback, gpointer user_data) { MMModemZtePrivate *priv = MM_MODEM_ZTE_GET_PRIVATE (modem); MMCallbackInfo *info; - MMSerialPort *primary; + MMAtSerialPort *primary; priv->init_retried = FALSE; - primary = mm_generic_gsm_get_port (modem, MM_PORT_TYPE_PRIMARY); + primary = mm_generic_gsm_get_at_port (modem, MM_PORT_TYPE_PRIMARY); g_assert (primary); info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_flash (primary, 100, enable_flash_done, info); + mm_serial_port_flash (MM_SERIAL_PORT (primary), 100, FALSE, enable_flash_done, info); } static void @@ -165,42 +411,41 @@ grab_port (MMModem *modem, MMPort *port = NULL; if (suggested_type == MM_PORT_TYPE_UNKNOWN) { - if (!mm_generic_gsm_get_port (gsm, MM_PORT_TYPE_PRIMARY)) + if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY)) ptype = MM_PORT_TYPE_PRIMARY; - else if (!mm_generic_gsm_get_port (gsm, MM_PORT_TYPE_SECONDARY)) + else if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_SECONDARY)) ptype = MM_PORT_TYPE_SECONDARY; } else ptype = suggested_type; port = mm_generic_gsm_grab_port (gsm, subsys, name, ptype, error); - if (port && MM_IS_SERIAL_PORT (port)) { + if (port && MM_IS_AT_SERIAL_PORT (port)) { GRegex *regex; - mm_generic_gsm_set_unsolicited_registration (gsm, TRUE); g_object_set (port, MM_PORT_CARRIER_DETECT, FALSE, NULL); regex = g_regex_new ("\\r\\n\\+ZUSIMR:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, NULL, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); g_regex_unref (regex); /* Unsolicted operator display */ regex = g_regex_new ("\\r\\n\\+ZDONR: (.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, NULL, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); g_regex_unref (regex); /* Current network and service domain */ - regex = g_regex_new ("\\r\\n\\+ZPASR: (.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, NULL, NULL); + regex = g_regex_new ("\\r\\n\\+ZPASR:\\s*(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, zte_access_tech_changed, modem, NULL); g_regex_unref (regex); /* SIM request to Build Main Menu */ regex = g_regex_new ("\\r\\n\\+ZPSTM: (.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, NULL, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); g_regex_unref (regex); /* SIM request to Rebuild Main Menu */ regex = g_regex_new ("\\r\\n\\+ZEND\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, NULL, NULL, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, NULL, NULL, NULL); g_regex_unref (regex); } @@ -221,6 +466,16 @@ mm_modem_zte_init (MMModemZte *self) { } +static void +dispose (GObject *object) +{ + MMModemZte *self = MM_MODEM_ZTE (object); + MMModemZtePrivate *priv = MM_MODEM_ZTE_GET_PRIVATE (self); + + if (priv->cpms_timeout) + g_source_remove (priv->cpms_timeout); +} + static void mm_modem_zte_class_init (MMModemZteClass *klass) { @@ -230,6 +485,10 @@ mm_modem_zte_class_init (MMModemZteClass *klass) mm_modem_zte_parent_class = g_type_class_peek_parent (klass); g_type_class_add_private (object_class, sizeof (MMModemZtePrivate)); + object_class->dispose = dispose; gsm_class->do_enable = do_enable; + gsm_class->set_allowed_mode = set_allowed_mode; + gsm_class->get_allowed_mode = get_allowed_mode; + gsm_class->get_access_technology = get_access_technology; } diff --git a/plugins/mm-plugin-anydata.c b/plugins/mm-plugin-anydata.c index e451714..94f4f10 100644 --- a/plugins/mm-plugin-anydata.c +++ b/plugins/mm-plugin-anydata.c @@ -42,9 +42,11 @@ mm_plugin_create (void) static guint32 get_level_for_capabilities (guint32 capabilities) { - /* Only CDMA for now */ + /* Only CDMA and QCDM for now */ if (capabilities & CAP_CDMA) return 10; + if (capabilities & MM_PLUGIN_BASE_PORT_CAP_QCDM) + return 10; return 0; } @@ -104,7 +106,7 @@ grab_port (MMPluginBase *base, MMPluginBaseSupportsTask *task, GError **error) { - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; MMModem *modem = NULL; const char *name, *subsys, *devfile, *sysfs_path; guint32 caps; @@ -118,14 +120,6 @@ grab_port (MMPluginBase *base, return NULL; } - physdev = mm_plugin_base_supports_task_get_physdev (task); - g_assert (physdev); - sysfs_path = g_udev_device_get_sysfs_path (physdev); - if (!sysfs_path) { - g_set_error (error, 0, 0, "Could not get port's physical device sysfs path."); - return NULL; - } - subsys = g_udev_device_get_subsystem (port); name = g_udev_device_get_name (port); @@ -135,6 +129,7 @@ grab_port (MMPluginBase *base, return NULL; } + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); if (!existing) { if (caps & CAP_CDMA) { modem = mm_modem_anydata_cdma_new (sysfs_path, @@ -150,12 +145,15 @@ grab_port (MMPluginBase *base, return NULL; } } - } else { - if (caps & CAP_CDMA) { - modem = existing; - if (!mm_modem_grab_port (modem, subsys, name, MM_PORT_TYPE_UNKNOWN, NULL, error)) - return NULL; - } + } else if (get_level_for_capabilities (caps)) { + MMPortType ptype = MM_PORT_TYPE_UNKNOWN; + + if (caps & MM_PLUGIN_BASE_PORT_CAP_QCDM) + ptype = MM_PORT_TYPE_QCDM; + + modem = existing; + if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) + return NULL; } return modem; diff --git a/plugins/mm-plugin-generic.c b/plugins/mm-plugin-generic.c index e5e2ade..cdf2c66 100644 --- a/plugins/mm-plugin-generic.c +++ b/plugins/mm-plugin-generic.c @@ -59,6 +59,8 @@ get_level_for_capabilities (guint32 capabilities) return 5; if (capabilities & CAP_CDMA) return 5; + if (capabilities & MM_PLUGIN_BASE_PORT_CAP_QCDM) + return 5; return 0; } @@ -106,7 +108,7 @@ grab_port (MMPluginBase *base, MMPluginBaseSupportsTask *task, GError **error) { - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; MMModem *modem = NULL; const char *name, *subsys, *devfile, *sysfs_path, *driver; guint32 caps; @@ -131,15 +133,8 @@ grab_port (MMPluginBase *base, } } - physdev = mm_plugin_base_supports_task_get_physdev (task); - g_assert (physdev); - sysfs_path = g_udev_device_get_sysfs_path (physdev); - if (!sysfs_path) { - g_set_error (error, 0, 0, "Could not get port's physical device sysfs path."); - return NULL; - } - caps = mm_plugin_base_supports_task_get_probed_capabilities (task); + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); if (!existing) { if (caps & MM_PLUGIN_BASE_PORT_CAP_GSM) { modem = mm_generic_gsm_new (sysfs_path, @@ -159,12 +154,15 @@ grab_port (MMPluginBase *base, return NULL; } } - } else { - if (caps & (MM_PLUGIN_BASE_PORT_CAP_GSM | CAP_CDMA)) { - modem = existing; - if (!mm_modem_grab_port (modem, subsys, name, MM_PORT_TYPE_UNKNOWN, NULL, error)) - return NULL; - } + } else if (get_level_for_capabilities (caps)) { + MMPortType ptype = MM_PORT_TYPE_UNKNOWN; + + if (caps & MM_PLUGIN_BASE_PORT_CAP_QCDM) + ptype = MM_PORT_TYPE_QCDM; + + modem = existing; + if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) + return NULL; } return modem; diff --git a/plugins/mm-plugin-gobi.c b/plugins/mm-plugin-gobi.c index 77da965..fbe3878 100644 --- a/plugins/mm-plugin-gobi.c +++ b/plugins/mm-plugin-gobi.c @@ -102,7 +102,7 @@ grab_port (MMPluginBase *base, MMPluginBaseSupportsTask *task, GError **error) { - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; MMModem *modem = NULL; const char *name, *subsys, *sysfs_path; guint32 caps; @@ -110,18 +110,11 @@ grab_port (MMPluginBase *base, port = mm_plugin_base_supports_task_get_port (task); g_assert (port); - physdev = mm_plugin_base_supports_task_get_physdev (task); - g_assert (physdev); - sysfs_path = g_udev_device_get_sysfs_path (physdev); - if (!sysfs_path) { - g_set_error (error, 0, 0, "Could not get port's physical device sysfs path."); - return NULL; - } - subsys = g_udev_device_get_subsystem (port); name = g_udev_device_get_name (port); caps = mm_plugin_base_supports_task_get_probed_capabilities (task); + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); if (!existing) { if (caps & MM_PLUGIN_BASE_PORT_CAP_GSM) { modem = mm_modem_gobi_gsm_new (sysfs_path, @@ -141,12 +134,10 @@ grab_port (MMPluginBase *base, return NULL; } } - } else { - if (caps & (MM_PLUGIN_BASE_PORT_CAP_GSM | CAP_CDMA)) { - modem = existing; - if (!mm_modem_grab_port (modem, subsys, name, MM_PORT_TYPE_UNKNOWN, NULL, error)) - return NULL; - } + } else if (get_level_for_capabilities (caps)) { + modem = existing; + if (!mm_modem_grab_port (modem, subsys, name, MM_PORT_TYPE_UNKNOWN, NULL, error)) + return NULL; } return modem; diff --git a/plugins/mm-plugin-hso.c b/plugins/mm-plugin-hso.c index 8493c9c..dc0a8fc 100644 --- a/plugins/mm-plugin-hso.c +++ b/plugins/mm-plugin-hso.c @@ -100,7 +100,7 @@ grab_port (MMPluginBase *base, MMPluginBaseSupportsTask *task, GError **error) { - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; MMModem *modem = NULL; const char *name, *subsys, *sysfs_path; char *devfile; @@ -131,18 +131,11 @@ grab_port (MMPluginBase *base, } } - physdev = mm_plugin_base_supports_task_get_physdev (task); - g_assert (physdev); - sysfs_path = g_udev_device_get_sysfs_path (physdev); - if (!sysfs_path) { - g_set_error (error, 0, 0, "Could not get port's physical device sysfs path."); - goto out; - } - caps = mm_plugin_base_supports_task_get_probed_capabilities (task); if (!(caps & MM_PLUGIN_BASE_PORT_CAP_GSM) && strcmp (subsys, "net")) goto out; + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); if (!existing) { modem = mm_modem_hso_new (sysfs_path, mm_plugin_base_supports_task_get_driver (task), diff --git a/plugins/mm-plugin-huawei.c b/plugins/mm-plugin-huawei.c index ad799f0..2993689 100644 --- a/plugins/mm-plugin-huawei.c +++ b/plugins/mm-plugin-huawei.c @@ -26,6 +26,7 @@ #include "mm-modem-huawei-gsm.h" #include "mm-modem-huawei-cdma.h" #include "mm-serial-parsers.h" +#include "mm-at-serial-port.h" G_DEFINE_TYPE (MMPluginHuawei, mm_plugin_huawei, MM_TYPE_PLUGIN_BASE) @@ -54,6 +55,8 @@ get_level_for_capabilities (guint32 capabilities) return 10; if (capabilities & CAP_CDMA) return 10; + if (capabilities & MM_PLUGIN_BASE_PORT_CAP_QCDM) + return 10; return 0; } @@ -69,9 +72,14 @@ probe_result (MMPluginBase *base, #define TAG_SUPPORTS_INFO "huawei-supports-info" typedef struct { - MMSerialPort *serial; + MMAtSerialPort *serial; guint id; - gboolean secondary; + MMPortType ptype; + /* Whether or not there's already a detected modem that "owns" this port, + * in which case we'll claim it, but if no capabilities are detected it'll + * just be ignored. + */ + gboolean parent_modem; } HuaweiSupportsInfo; static void @@ -100,13 +108,13 @@ probe_secondary_supported (gpointer user_data) info->serial = NULL; /* Yay, supported, we got an unsolicited message */ - info->secondary = TRUE; + info->ptype = MM_PORT_TYPE_SECONDARY; mm_plugin_base_supports_task_complete (task, 10); return FALSE; } static void -probe_secondary_handle_msg (MMSerialPort *port, +probe_secondary_handle_msg (MMAtSerialPort *port, GMatchInfo *match_info, gpointer user_data) { @@ -123,24 +131,30 @@ probe_secondary_timeout (gpointer user_data) { MMPluginBaseSupportsTask *task = user_data; HuaweiSupportsInfo *info; + guint level = 0; info = g_object_get_data (G_OBJECT (task), TAG_SUPPORTS_INFO); info->id = 0; g_object_unref (info->serial); info->serial = NULL; - /* Not supported by this plugin */ - mm_plugin_base_supports_task_complete (task, 0); + /* Supported, but ignored if this port's parent device is already a modem */ + if (info->parent_modem) { + info->ptype = MM_PORT_TYPE_IGNORED; + level = 10; + } + + mm_plugin_base_supports_task_complete (task, level); return FALSE; } static void -add_regex (MMSerialPort *port, const char *match, gpointer user_data) +add_regex (MMAtSerialPort *port, const char *match, gpointer user_data) { GRegex *regex; regex = g_regex_new (match, G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (port, regex, probe_secondary_handle_msg, user_data, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (port, regex, probe_secondary_handle_msg, user_data, NULL); g_regex_unref (regex); } @@ -207,14 +221,15 @@ supports_port (MMPluginBase *base, /* Listen for Huawei-specific unsolicited messages */ info = g_malloc0 (sizeof (HuaweiSupportsInfo)); + info->parent_modem = !!existing; - info->serial = mm_serial_port_new (name, MM_PORT_TYPE_PRIMARY); + info->serial = mm_at_serial_port_new (name, MM_PORT_TYPE_PRIMARY); g_object_set (G_OBJECT (info->serial), MM_PORT_CARRIER_DETECT, FALSE, NULL); - mm_serial_port_set_response_parser (info->serial, - mm_serial_parser_v1_parse, - mm_serial_parser_v1_new (), - mm_serial_parser_v1_destroy); + mm_at_serial_port_set_response_parser (info->serial, + mm_serial_parser_v1_parse, + mm_serial_parser_v1_new (), + mm_serial_parser_v1_destroy); add_regex (info->serial, "\\r\\n\\^RSSI:(\\d+)\\r\\n", task); add_regex (info->serial, "\\r\\n\\^MODE:(\\d),(\\d)\\r\\n", task); @@ -222,12 +237,12 @@ supports_port (MMPluginBase *base, add_regex (info->serial, "\\r\\n\\^BOOT:.+\\r\\n", task); add_regex (info->serial, "\\r\\r\\^BOOT:.+\\r\\r", task); - info->id = g_timeout_add (5000, probe_secondary_timeout, task); + info->id = g_timeout_add_seconds (7, probe_secondary_timeout, task); g_object_set_data_full (G_OBJECT (task), TAG_SUPPORTS_INFO, info, huawei_supports_info_destroy); - if (!mm_serial_port_open (info->serial, &error)) { + if (!mm_serial_port_open (MM_SERIAL_PORT (info->serial), &error)) { g_warning ("%s: (Huawei) %s: couldn't open serial port: (%d) %s", __func__, name, error ? error->code : -1, @@ -249,7 +264,7 @@ grab_port (MMPluginBase *base, MMPluginBaseSupportsTask *task, GError **error) { - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; MMModem *modem = NULL; const char *name, *subsys, *devfile, *sysfs_path; guint32 caps; @@ -264,14 +279,6 @@ grab_port (MMPluginBase *base, return NULL; } - physdev = mm_plugin_base_supports_task_get_physdev (task); - g_assert (physdev); - sysfs_path = g_udev_device_get_sysfs_path (physdev); - if (!sysfs_path) { - g_set_error (error, 0, 0, "Could not get port's physical device sysfs path."); - return NULL; - } - subsys = g_udev_device_get_subsystem (port); name = g_udev_device_get_name (port); @@ -281,18 +288,12 @@ grab_port (MMPluginBase *base, } caps = mm_plugin_base_supports_task_get_probed_capabilities (task); + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); if (!existing) { if (caps & MM_PLUGIN_BASE_PORT_CAP_GSM) { - if (product == 0x1001) { - /* This modem is handled by generic GSM driver */ - modem = mm_generic_gsm_new (sysfs_path, - mm_plugin_base_supports_task_get_driver (task), - mm_plugin_get_name (MM_PLUGIN (base))); - } else { - modem = mm_modem_huawei_gsm_new (sysfs_path, - mm_plugin_base_supports_task_get_driver (task), - mm_plugin_get_name (MM_PLUGIN (base))); - } + modem = mm_modem_huawei_gsm_new (sysfs_path, + mm_plugin_base_supports_task_get_driver (task), + mm_plugin_get_name (MM_PLUGIN (base))); } else if (caps & CAP_CDMA) { modem = mm_modem_huawei_cdma_new (sysfs_path, mm_plugin_base_supports_task_get_driver (task), @@ -312,8 +313,10 @@ grab_port (MMPluginBase *base, MMPortType ptype = MM_PORT_TYPE_UNKNOWN; info = g_object_get_data (G_OBJECT (task), TAG_SUPPORTS_INFO); - if (info && info->secondary && (product != 0x1001)) - ptype = MM_PORT_TYPE_SECONDARY; + if (info) + ptype = info->ptype; + else if (caps & MM_PLUGIN_BASE_PORT_CAP_QCDM) + ptype = MM_PORT_TYPE_QCDM; modem = existing; if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) diff --git a/plugins/mm-plugin-longcheer.c b/plugins/mm-plugin-longcheer.c index 5c19b13..dbbe186 100644 --- a/plugins/mm-plugin-longcheer.c +++ b/plugins/mm-plugin-longcheer.c @@ -11,12 +11,13 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #include #include #include "mm-plugin-longcheer.h" +#include "mm-modem-longcheer-gsm.h" #include "mm-generic-gsm.h" #include "mm-generic-cdma.h" @@ -47,6 +48,8 @@ get_level_for_capabilities (guint32 capabilities) return 10; if (capabilities & CAP_CDMA) return 10; + if (capabilities & MM_PLUGIN_BASE_PORT_CAP_QCDM) + return 10; return 0; } @@ -80,7 +83,8 @@ supports_port (MMPluginBase *base, if (!mm_plugin_base_get_device_ids (base, subsys, name, &vendor, NULL)) return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; - if (vendor != 0x1c9e) + /* Longcheer and TAMobile */ + if (vendor != 0x1c9e && vendor != 0x1bbb) return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; if (mm_plugin_base_get_cached_port_capabilities (base, port, &cached)) { @@ -105,7 +109,7 @@ grab_port (MMPluginBase *base, MMPluginBaseSupportsTask *task, GError **error) { - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; MMModem *modem = NULL; const char *name, *subsys, *sysfs_path; guint32 caps; @@ -131,23 +135,16 @@ grab_port (MMPluginBase *base, && g_udev_device_get_property_as_boolean (port, "ID_MM_LONGCHEER_TAGGED")) ptype = MM_PORT_TYPE_IGNORED; - physdev = mm_plugin_base_supports_task_get_physdev (task); - g_assert (physdev); - sysfs_path = g_udev_device_get_sysfs_path (physdev); - if (!sysfs_path) { - g_set_error (error, 0, 0, "Could not get port's physical device sysfs path."); - return NULL; - } - subsys = g_udev_device_get_subsystem (port); name = g_udev_device_get_name (port); caps = mm_plugin_base_supports_task_get_probed_capabilities (task); + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); if (!existing) { if (caps & MM_PLUGIN_BASE_PORT_CAP_GSM) { - modem = mm_generic_gsm_new (sysfs_path, - mm_plugin_base_supports_task_get_driver (task), - mm_plugin_get_name (MM_PLUGIN (base))); + modem = mm_modem_longcheer_gsm_new (sysfs_path, + mm_plugin_base_supports_task_get_driver (task), + mm_plugin_get_name (MM_PLUGIN (base))); } else if (caps & CAP_CDMA) { modem = mm_generic_cdma_new (sysfs_path, mm_plugin_base_supports_task_get_driver (task), @@ -162,12 +159,13 @@ grab_port (MMPluginBase *base, return NULL; } } - } else { - if (caps & (MM_PLUGIN_BASE_PORT_CAP_GSM | CAP_CDMA)) { - modem = existing; - if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) - return NULL; - } + } else if (get_level_for_capabilities (caps)) { + if (caps & MM_PLUGIN_BASE_PORT_CAP_QCDM) + ptype = MM_PORT_TYPE_QCDM; + + modem = existing; + if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) + return NULL; } return modem; diff --git a/plugins/mm-plugin-mbm.c b/plugins/mm-plugin-mbm.c index a380f98..5554d84 100644 --- a/plugins/mm-plugin-mbm.c +++ b/plugins/mm-plugin-mbm.c @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * */ #include @@ -66,9 +62,12 @@ supports_port (MMPluginBase *base, MMModem *existing, MMPluginBaseSupportsTask *task) { + GUdevClient *client; + const char *sys[] = { "tty", "net", NULL }; GUdevDevice *port, *physdev; guint32 cached = 0, level; - const char *driver, *subsys; + const char *driver, *subsys, *physdev_path; + gboolean is_mbm; /* Can't do anything with non-serial ports */ port = mm_plugin_base_supports_task_get_port (task); @@ -83,9 +82,23 @@ supports_port (MMPluginBase *base, if (!driver) return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; - physdev = mm_plugin_base_supports_task_get_physdev (task); + client = g_udev_client_new (sys); + if (!client) { + g_warning ("mbm: could not get udev client."); + return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; + } + + /* Look up the port's physical device and see if this port is really an + * 'mbm' modem, since we have no other way of telling. + */ + physdev_path = mm_plugin_base_supports_task_get_physdev_path (task); + physdev = g_udev_client_query_by_sysfs_path (client, physdev_path); g_assert (physdev); - if (!g_udev_device_get_property_as_boolean (physdev, "ID_MM_ERICSSON_MBM")) + + is_mbm = g_udev_device_get_property_as_boolean (physdev, "ID_MM_ERICSSON_MBM"); + g_object_unref (client); + + if (!is_mbm) return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; if (!strcmp (subsys, "net")) { @@ -115,7 +128,7 @@ grab_port (MMPluginBase *base, MMPluginBaseSupportsTask *task, GError **error) { - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; MMModem *modem = NULL; const char *name, *subsys, *sysfs_path; guint32 caps; @@ -123,14 +136,6 @@ grab_port (MMPluginBase *base, port = mm_plugin_base_supports_task_get_port (task); g_assert (port); - physdev = mm_plugin_base_supports_task_get_physdev (task); - g_assert (physdev); - sysfs_path = g_udev_device_get_sysfs_path (physdev); - if (!sysfs_path) { - g_set_error (error, 0, 0, "Could not get port's physical device sysfs path."); - return NULL; - } - subsys = g_udev_device_get_subsystem (port); name = g_udev_device_get_name (port); @@ -138,6 +143,7 @@ grab_port (MMPluginBase *base, if (!(caps & MM_PLUGIN_BASE_PORT_CAP_GSM) && strcmp (subsys, "net")) return NULL; + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); if (!existing) { modem = mm_modem_mbm_new (sysfs_path, mm_plugin_base_supports_task_get_driver (task), diff --git a/plugins/mm-plugin-mbm.h b/plugins/mm-plugin-mbm.h index c0e73b5..c478f11 100644 --- a/plugins/mm-plugin-mbm.h +++ b/plugins/mm-plugin-mbm.h @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * */ #ifndef MM_PLUGIN_MBM_H diff --git a/plugins/mm-plugin-moto-c.c b/plugins/mm-plugin-moto-c.c index 5b32a1e..d798af4 100644 --- a/plugins/mm-plugin-moto-c.c +++ b/plugins/mm-plugin-moto-c.c @@ -37,18 +37,21 @@ mm_plugin_create (void) /*****************************************************************************/ +static guint32 +get_level_for_capabilities (guint32 capabilities) +{ + if (capabilities & MM_PLUGIN_BASE_PORT_CAP_GSM) + return 10; + return 0; +} + static void probe_result (MMPluginBase *base, MMPluginBaseSupportsTask *task, guint32 capabilities, gpointer user_data) { - guint32 level = 0; - - if (capabilities & MM_PLUGIN_BASE_PORT_CAP_GSM) - level = 10; - - mm_plugin_base_supports_task_complete (task, level); + mm_plugin_base_supports_task_complete (task, get_level_for_capabilities (capabilities)); } static MMPluginSupportsResult @@ -58,7 +61,7 @@ supports_port (MMPluginBase *base, { GUdevDevice *port; const char *tmp; - guint32 cached = 0; + guint32 cached = 0, level; /* Can't do anything with non-serial ports */ port = mm_plugin_base_supports_task_get_port (task); @@ -78,7 +81,8 @@ supports_port (MMPluginBase *base, return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; if (mm_plugin_base_get_cached_port_capabilities (base, port, &cached)) { - if (cached & MM_PLUGIN_BASE_PORT_CAP_GSM) { + level = get_level_for_capabilities (cached); + if (level) { mm_plugin_base_supports_task_complete (task, 10); return MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS; } @@ -98,7 +102,7 @@ grab_port (MMPluginBase *base, MMPluginBaseSupportsTask *task, GError **error) { - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; MMModem *modem = NULL; const char *name, *subsys, *devfile, *sysfs_path; @@ -111,17 +115,10 @@ grab_port (MMPluginBase *base, return NULL; } - physdev = mm_plugin_base_supports_task_get_physdev (task); - g_assert (physdev); - sysfs_path = g_udev_device_get_sysfs_path (physdev); - if (!sysfs_path) { - g_set_error (error, 0, 0, "Could not get port's physical device sysfs path."); - return NULL; - } - subsys = g_udev_device_get_subsystem (port); name = g_udev_device_get_name (port); + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); if (!existing) { modem = mm_modem_moto_c_gsm_new (sysfs_path, mm_plugin_base_supports_task_get_driver (task), diff --git a/plugins/mm-plugin-nokia.c b/plugins/mm-plugin-nokia.c index e088323..2d0d6af 100644 --- a/plugins/mm-plugin-nokia.c +++ b/plugins/mm-plugin-nokia.c @@ -105,7 +105,7 @@ grab_port (MMPluginBase *base, MMPluginBaseSupportsTask *task, GError **error) { - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; MMModem *modem = NULL; const char *name, *subsys, *devfile, *sysfs_path; guint32 caps; @@ -119,18 +119,11 @@ grab_port (MMPluginBase *base, return NULL; } - physdev = mm_plugin_base_supports_task_get_physdev (task); - g_assert (physdev); - sysfs_path = g_udev_device_get_sysfs_path (physdev); - if (!sysfs_path) { - g_set_error (error, 0, 0, "Could not get port's physical device sysfs path."); - return NULL; - } - subsys = g_udev_device_get_subsystem (port); name = g_udev_device_get_name (port); caps = mm_plugin_base_supports_task_get_probed_capabilities (task); + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); if (!existing) { if (caps & MM_PLUGIN_BASE_PORT_CAP_GSM) { modem = mm_modem_nokia_new (sysfs_path, @@ -150,12 +143,10 @@ grab_port (MMPluginBase *base, return NULL; } } - } else { - if (caps & (MM_PLUGIN_BASE_PORT_CAP_GSM | CAP_CDMA)) { - modem = existing; - if (!mm_modem_grab_port (modem, subsys, name, MM_PORT_TYPE_UNKNOWN, NULL, error)) - return NULL; - } + } else if (get_level_for_capabilities (caps)) { + modem = existing; + if (!mm_modem_grab_port (modem, subsys, name, MM_PORT_TYPE_UNKNOWN, NULL, error)) + return NULL; } return modem; diff --git a/plugins/mm-plugin-novatel.c b/plugins/mm-plugin-novatel.c index 48ff7ec..a968836 100644 --- a/plugins/mm-plugin-novatel.c +++ b/plugins/mm-plugin-novatel.c @@ -18,7 +18,7 @@ #include #include "mm-plugin-novatel.h" #include "mm-modem-novatel-gsm.h" -#include "mm-generic-cdma.h" +#include "mm-modem-novatel-cdma.h" G_DEFINE_TYPE (MMPluginNovatel, mm_plugin_novatel, MM_TYPE_PLUGIN_BASE) @@ -47,6 +47,8 @@ get_level_for_capabilities (guint32 capabilities) return 10; if (capabilities & CAP_CDMA) return 10; + if (capabilities & MM_PLUGIN_BASE_PORT_CAP_QCDM) + return 10; return 0; } @@ -109,7 +111,7 @@ grab_port (MMPluginBase *base, MMPluginBaseSupportsTask *task, GError **error) { - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; MMModem *modem = NULL; const char *name, *subsys, *devfile, *sysfs_path; guint32 caps; @@ -123,29 +125,22 @@ grab_port (MMPluginBase *base, return NULL; } - physdev = mm_plugin_base_supports_task_get_physdev (task); - g_assert (physdev); - sysfs_path = g_udev_device_get_sysfs_path (physdev); - if (!sysfs_path) { - g_set_error (error, 0, 0, "Could not get port's physical device sysfs path."); - return NULL; - } - subsys = g_udev_device_get_subsystem (port); name = g_udev_device_get_name (port); caps = mm_plugin_base_supports_task_get_probed_capabilities (task); + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); if (!existing) { if (caps & MM_PLUGIN_BASE_PORT_CAP_GSM) { modem = mm_modem_novatel_gsm_new (sysfs_path, mm_plugin_base_supports_task_get_driver (task), mm_plugin_get_name (MM_PLUGIN (base))); } else if (caps & CAP_CDMA) { - modem = mm_generic_cdma_new (sysfs_path, - mm_plugin_base_supports_task_get_driver (task), - mm_plugin_get_name (MM_PLUGIN (base)), - !!(caps & MM_PLUGIN_BASE_PORT_CAP_IS856), - !!(caps & MM_PLUGIN_BASE_PORT_CAP_IS856_A)); + modem = mm_modem_novatel_cdma_new (sysfs_path, + mm_plugin_base_supports_task_get_driver (task), + mm_plugin_get_name (MM_PLUGIN (base)), + !!(caps & MM_PLUGIN_BASE_PORT_CAP_IS856), + !!(caps & MM_PLUGIN_BASE_PORT_CAP_IS856_A)); } if (modem) { @@ -154,12 +149,15 @@ grab_port (MMPluginBase *base, return NULL; } } - } else { - if (caps & (MM_PLUGIN_BASE_PORT_CAP_GSM | CAP_CDMA)) { - modem = existing; - if (!mm_modem_grab_port (modem, subsys, name, MM_PORT_TYPE_UNKNOWN, NULL, error)) - return NULL; - } + } else if (get_level_for_capabilities (caps)) { + MMPortType ptype = MM_PORT_TYPE_UNKNOWN; + + if (caps & MM_PLUGIN_BASE_PORT_CAP_QCDM) + ptype = MM_PORT_TYPE_QCDM; + + modem = existing; + if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) + return NULL; } return modem; diff --git a/plugins/mm-plugin-option.c b/plugins/mm-plugin-option.c index d4c402d..101f9bd 100644 --- a/plugins/mm-plugin-option.c +++ b/plugins/mm-plugin-option.c @@ -101,7 +101,7 @@ grab_port (MMPluginBase *base, MMPluginBaseSupportsTask *task, GError **error) { - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; MMModem *modem = NULL; const char *name, *subsys, *devfile, *sysfs_path; guint32 caps; @@ -117,14 +117,6 @@ grab_port (MMPluginBase *base, return NULL; } - physdev = mm_plugin_base_supports_task_get_physdev (task); - g_assert (physdev); - sysfs_path = g_udev_device_get_sysfs_path (physdev); - if (!sysfs_path) { - g_set_error (error, 0, 0, "Could not get port's physical device sysfs path."); - return NULL; - } - subsys = g_udev_device_get_subsystem (port); name = g_udev_device_get_name (port); @@ -138,6 +130,7 @@ grab_port (MMPluginBase *base, ptype = MM_PORT_TYPE_PRIMARY; caps = mm_plugin_base_supports_task_get_probed_capabilities (task); + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); if (!existing) { if (caps & MM_PLUGIN_BASE_PORT_CAP_GSM) { modem = mm_modem_option_new (sysfs_path, @@ -151,12 +144,13 @@ grab_port (MMPluginBase *base, return NULL; } } - } else { - if (caps & MM_PLUGIN_BASE_PORT_CAP_GSM) { - modem = existing; - if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) - return NULL; - } + } else if (get_level_for_capabilities (caps)) { + if (caps & MM_PLUGIN_BASE_PORT_CAP_QCDM) + ptype = MM_PORT_TYPE_QCDM; + + modem = existing; + if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) + return NULL; } return modem; diff --git a/plugins/mm-plugin-sierra.c b/plugins/mm-plugin-sierra.c index 637f46d..8ace653 100644 --- a/plugins/mm-plugin-sierra.c +++ b/plugins/mm-plugin-sierra.c @@ -123,7 +123,7 @@ grab_port (MMPluginBase *base, MMPluginBaseSupportsTask *task, GError **error) { - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; MMModem *modem = NULL; const char *name, *subsys, *devfile, *sysfs_path; guint32 caps; @@ -138,14 +138,6 @@ grab_port (MMPluginBase *base, return NULL; } - physdev = mm_plugin_base_supports_task_get_physdev (task); - g_assert (physdev); - sysfs_path = g_udev_device_get_sysfs_path (physdev); - if (!sysfs_path) { - g_set_error (error, 0, 0, "Could not get port's physical device sysfs path."); - return NULL; - } - subsys = g_udev_device_get_subsystem (port); name = g_udev_device_get_name (port); @@ -154,6 +146,7 @@ grab_port (MMPluginBase *base, ptype = MM_PORT_TYPE_SECONDARY; caps = mm_plugin_base_supports_task_get_probed_capabilities (task); + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); if (!existing) { if ((caps & MM_PLUGIN_BASE_PORT_CAP_GSM) || (ptype != MM_PORT_TYPE_UNKNOWN)) { modem = mm_modem_sierra_gsm_new (sysfs_path, @@ -173,13 +166,10 @@ grab_port (MMPluginBase *base, return NULL; } } - } else { - if ( (caps & (MM_PLUGIN_BASE_PORT_CAP_GSM | CAP_CDMA)) - || (ptype != MM_PORT_TYPE_UNKNOWN)) { - modem = existing; - if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) - return NULL; - } + } else if (get_level_for_capabilities (caps) || (ptype != MM_PORT_TYPE_UNKNOWN)) { + modem = existing; + if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) + return NULL; } return modem; diff --git a/plugins/mm-plugin-simtech.c b/plugins/mm-plugin-simtech.c new file mode 100644 index 0000000..3c44873 --- /dev/null +++ b/plugins/mm-plugin-simtech.c @@ -0,0 +1,189 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#include +#include +#include "mm-plugin-simtech.h" +#include "mm-modem-simtech-gsm.h" +#include "mm-generic-gsm.h" +#include "mm-generic-cdma.h" + +G_DEFINE_TYPE (MMPluginSimtech, mm_plugin_simtech, MM_TYPE_PLUGIN_BASE) + +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_SIMTECH, + MM_PLUGIN_BASE_NAME, "SimTech", + NULL)); +} + +/*****************************************************************************/ + +#define CAP_CDMA (MM_PLUGIN_BASE_PORT_CAP_IS707_A | \ + MM_PLUGIN_BASE_PORT_CAP_IS707_P | \ + MM_PLUGIN_BASE_PORT_CAP_IS856 | \ + MM_PLUGIN_BASE_PORT_CAP_IS856_A) + +static guint32 +get_level_for_capabilities (guint32 capabilities) +{ + if (capabilities & MM_PLUGIN_BASE_PORT_CAP_GSM) + return 10; + if (capabilities & CAP_CDMA) + return 10; + if (capabilities & MM_PLUGIN_BASE_PORT_CAP_QCDM) + return 10; + return 0; +} + +static void +probe_result (MMPluginBase *base, + MMPluginBaseSupportsTask *task, + guint32 capabilities, + gpointer user_data) +{ + mm_plugin_base_supports_task_complete (task, get_level_for_capabilities (capabilities)); +} + +static MMPluginSupportsResult +supports_port (MMPluginBase *base, + MMModem *existing, + MMPluginBaseSupportsTask *task) +{ + GUdevDevice *port; + guint32 cached = 0, level; + guint16 vendor = 0; + const char *subsys, *name; + + /* Can't do anything with non-serial ports */ + port = mm_plugin_base_supports_task_get_port (task); + if (strcmp (g_udev_device_get_subsystem (port), "tty")) + return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; + + subsys = g_udev_device_get_subsystem (port); + name = g_udev_device_get_name (port); + + if (!mm_plugin_base_get_device_ids (base, subsys, name, &vendor, NULL)) + return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; + + /* A-Link (for now) */ + if (vendor != 0x1e0e) + return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; + + if (mm_plugin_base_get_cached_port_capabilities (base, port, &cached)) { + level = get_level_for_capabilities (cached); + if (level) { + mm_plugin_base_supports_task_complete (task, level); + return MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS; + } + return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; + } + + /* Otherwise kick off a probe */ + if (mm_plugin_base_probe_port (base, task, NULL)) + return MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS; + + return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; +} + +static MMModem * +grab_port (MMPluginBase *base, + MMModem *existing, + MMPluginBaseSupportsTask *task, + GError **error) +{ + GUdevDevice *port = NULL; + MMModem *modem = NULL; + const char *name, *subsys, *sysfs_path; + guint32 caps; + MMPortType ptype = MM_PORT_TYPE_UNKNOWN; + + port = mm_plugin_base_supports_task_get_port (task); + g_assert (port); + + /* Look for port type hints; just probing can't distinguish which port should + * be the data/primary port on these devices. We have to tag them based on + * what the Windows .INF files say the port layout should be. + */ + if (g_udev_device_get_property_as_boolean (port, "ID_MM_SIMTECH_PORT_TYPE_MODEM")) + ptype = MM_PORT_TYPE_PRIMARY; + else if (g_udev_device_get_property_as_boolean (port, "ID_MM_SIMTECH_PORT_TYPE_AUX")) + ptype = MM_PORT_TYPE_SECONDARY; + + /* If the device was tagged by the udev rules, then ignore any other ports + * to guard against race conditions if a device just happens to show up + * with more than two AT-capable ports. + */ + if ( (ptype == MM_PORT_TYPE_UNKNOWN) + && g_udev_device_get_property_as_boolean (port, "ID_MM_SIMTECH_TAGGED")) + ptype = MM_PORT_TYPE_IGNORED; + + subsys = g_udev_device_get_subsystem (port); + name = g_udev_device_get_name (port); + + caps = mm_plugin_base_supports_task_get_probed_capabilities (task); + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); + if (!existing) { + if (caps & MM_PLUGIN_BASE_PORT_CAP_GSM) { + modem = mm_modem_simtech_gsm_new (sysfs_path, + mm_plugin_base_supports_task_get_driver (task), + mm_plugin_get_name (MM_PLUGIN (base))); + } else if (caps & CAP_CDMA) { + modem = mm_generic_cdma_new (sysfs_path, + mm_plugin_base_supports_task_get_driver (task), + mm_plugin_get_name (MM_PLUGIN (base)), + !!(caps & MM_PLUGIN_BASE_PORT_CAP_IS856), + !!(caps & MM_PLUGIN_BASE_PORT_CAP_IS856_A)); + } + + if (modem) { + if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) { + g_object_unref (modem); + return NULL; + } + } + } else if (get_level_for_capabilities (caps)) { + if (caps & MM_PLUGIN_BASE_PORT_CAP_QCDM) + ptype = MM_PORT_TYPE_QCDM; + + modem = existing; + if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) + return NULL; + } + + return modem; +} + +/*****************************************************************************/ + +static void +mm_plugin_simtech_init (MMPluginSimtech *self) +{ + g_signal_connect (self, "probe-result", G_CALLBACK (probe_result), NULL); +} + +static void +mm_plugin_simtech_class_init (MMPluginSimtechClass *klass) +{ + MMPluginBaseClass *pb_class = MM_PLUGIN_BASE_CLASS (klass); + + pb_class->supports_port = supports_port; + pb_class->grab_port = grab_port; +} diff --git a/plugins/mm-plugin-simtech.h b/plugins/mm-plugin-simtech.h new file mode 100644 index 0000000..e316056 --- /dev/null +++ b/plugins/mm-plugin-simtech.h @@ -0,0 +1,41 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#ifndef MM_PLUGIN_SIMTECH_H +#define MM_PLUGIN_SIMTECH_H + +#include "mm-plugin-base.h" + +#define MM_TYPE_PLUGIN_SIMTECH (mm_plugin_simtech_get_type ()) +#define MM_PLUGIN_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_SIMTECH, MMPluginSimtech)) +#define MM_PLUGIN_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_PLUGIN_SIMTECH, MMPluginSimtechClass)) +#define MM_IS_PLUGIN_SIMTECH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_SIMTECH)) +#define MM_IS_PLUGIN_SIMTECH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_PLUGIN_SIMTECH)) +#define MM_PLUGIN_SIMTECH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_PLUGIN_SIMTECH, MMPluginSimtechClass)) + +typedef struct { + MMPluginBase parent; +} MMPluginSimtech; + +typedef struct { + MMPluginBaseClass parent; +} MMPluginSimtechClass; + +GType mm_plugin_simtech_get_type (void); + +G_MODULE_EXPORT MMPlugin *mm_plugin_create (void); + +#endif /* MM_PLUGIN_SIMTECH_H */ diff --git a/plugins/mm-plugin-zte.c b/plugins/mm-plugin-zte.c index 101fb46..e943bbf 100644 --- a/plugins/mm-plugin-zte.c +++ b/plugins/mm-plugin-zte.c @@ -47,6 +47,8 @@ get_level_for_capabilities (guint32 capabilities) return 10; if (capabilities & CAP_CDMA) return 10; + if (capabilities & MM_PLUGIN_BASE_PORT_CAP_QCDM) + return 10; return 0; } @@ -100,7 +102,7 @@ supports_port (MMPluginBase *base, * 1235f71b20c92cded4abd976ccc5010649aae1a0 and * f38ad328acfdc6ce29dd1380602c546b064161ae for more details. */ - mm_plugin_base_supports_task_set_custom_init_command (task, "ATE0+CPMS?", 3, 4, TRUE); + mm_plugin_base_supports_task_set_custom_init_command (task, "ATE0+CPMS?", 3, 4, FALSE); if (mm_plugin_base_probe_port (base, task, NULL)) return MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS; @@ -114,7 +116,7 @@ grab_port (MMPluginBase *base, MMPluginBaseSupportsTask *task, GError **error) { - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; MMModem *modem = NULL; const char *name, *subsys, *sysfs_path; guint32 caps; @@ -129,18 +131,11 @@ grab_port (MMPluginBase *base, else if (g_udev_device_get_property_as_boolean (port, "ID_MM_ZTE_PORT_TYPE_AUX")) ptype = MM_PORT_TYPE_SECONDARY; - physdev = mm_plugin_base_supports_task_get_physdev (task); - g_assert (physdev); - sysfs_path = g_udev_device_get_sysfs_path (physdev); - if (!sysfs_path) { - g_set_error (error, 0, 0, "Could not get port's physical device sysfs path."); - return NULL; - } - subsys = g_udev_device_get_subsystem (port); name = g_udev_device_get_name (port); caps = mm_plugin_base_supports_task_get_probed_capabilities (task); + sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task); if (!existing) { if (caps & MM_PLUGIN_BASE_PORT_CAP_GSM) { modem = mm_modem_zte_new (sysfs_path, @@ -160,12 +155,13 @@ grab_port (MMPluginBase *base, return NULL; } } - } else { - if (caps & (MM_PLUGIN_BASE_PORT_CAP_GSM | CAP_CDMA)) { - modem = existing; - if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) - return NULL; - } + } else if (get_level_for_capabilities (caps)) { + if (caps & MM_PLUGIN_BASE_PORT_CAP_QCDM) + ptype = MM_PORT_TYPE_QCDM; + + modem = existing; + if (!mm_modem_grab_port (modem, subsys, name, ptype, NULL, error)) + return NULL; } return modem; diff --git a/po/LINGUAS b/po/LINGUAS new file mode 100644 index 0000000..e69de29 diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..f97552d --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,5 @@ +[encoding: UTF-8] +# List of source files containing translatable strings. +# Please keep this file sorted alphabetically. +policy/org.freedesktop.modem-manager.policy.in + diff --git a/policy/Makefile.am b/policy/Makefile.am new file mode 100644 index 0000000..81a817b --- /dev/null +++ b/policy/Makefile.am @@ -0,0 +1,15 @@ +icondir=${datadir}/icons/hicolor/22x22/apps +icon_DATA = modem-manager.png + +polkit_policydir = $(datadir)/polkit-1/actions + +dist_polkit_policy_in_files = org.freedesktop.modem-manager.policy.in +dist_polkit_policy_DATA = $(dist_polkit_policy_in_files:.policy.in=.policy) + +@INTLTOOL_POLICY_RULE@ + +clean-local : + rm -f *~ + +EXTRA_DIST = $(dist_polkit_policy_in_files) $(icon_DATA) + diff --git a/policy/modem-manager.png b/policy/modem-manager.png new file mode 100644 index 0000000..386abd5 Binary files /dev/null and b/policy/modem-manager.png differ diff --git a/policy/org.freedesktop.modem-manager.policy.in b/policy/org.freedesktop.modem-manager.policy.in new file mode 100644 index 0000000..8719936 --- /dev/null +++ b/policy/org.freedesktop.modem-manager.policy.in @@ -0,0 +1,57 @@ + + + + + + ModemManager + http://www.freedesktop.org/wiki/ModemManager + modem-manager + + + <_description>Unlock and control a mobile broadband device + <_message>System policy prevents unlocking or controlling the mobile broadband device. + + no + auth_self_keep + + + + + <_description>Request mobile broadband device identifying information + <_message>System policy prevents requesting identifying information from the mobile broadband device. + + no + auth_self_keep + + + + + <_description>Add, modify, and delete mobile broadband contacts + <_message>System policy prevents adding, modifying, or deleteing this device's contacts. + + no + auth_self_keep + + + + + <_description>Send, save, modify, and delete text messages + <_message>System policy prevents sending or maniuplating this device's text messages. + + no + auth_self_keep + + + + + <_description>Enable and view geographic location and positioning information + <_message>System policy prevents enabling or viewing geographic location information. + + no + auth_self_keep + + + + diff --git a/src/77-mm-pcmcia-device-blacklist.rules b/src/77-mm-pcmcia-device-blacklist.rules new file mode 100644 index 0000000..76259a2 --- /dev/null +++ b/src/77-mm-pcmcia-device-blacklist.rules @@ -0,0 +1,10 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change", GOTO="mm_pcmcia_device_blacklist_end" +SUBSYSTEM!="pcmcia", GOTO="mm_pcmcia_device_blacklist_end" + +# Gemplus Serial Port smartcard adapter +ATTRS{prod_id1}=="Gemplus", ATTRS{prod_id2}=="SerialPort", ATTRS{prod_id3}=="GemPC Card", ENV{ID_MM_DEVICE_IGNORE}="1" + +LABEL="mm_pcmcia_device_blacklist_end" + diff --git a/src/77-mm-platform-serial-whitelist.rules b/src/77-mm-platform-serial-whitelist.rules new file mode 100644 index 0000000..b62d0a6 --- /dev/null +++ b/src/77-mm-platform-serial-whitelist.rules @@ -0,0 +1,14 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change", GOTO="mm_platform_device_whitelist_end" +SUBSYSTEM!="platform", GOTO="mm_platform_device_whitelist_end" + +# Be careful here since many devices connected to platform drivers on PCs +# are legacy devices that won't like probing. But often on embedded +# systems serial ports are provided by platform devices. + +# Allow atmel_usart +DRIVERS=="atmel_usart", ENV{ID_MM_PLATFORM_DRIVER_PROBE}="1" + +LABEL="mm_platform_device_whitelist_end" + diff --git a/src/77-mm-usb-device-blacklist.rules b/src/77-mm-usb-device-blacklist.rules new file mode 100644 index 0000000..78a6770 --- /dev/null +++ b/src/77-mm-usb-device-blacklist.rules @@ -0,0 +1,66 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change", GOTO="mm_usb_device_blacklist_end" +SUBSYSTEM!="usb", GOTO="mm_usb_device_blacklist_end" +ENV{DEVTYPE}!="usb_device", GOTO="mm_usb_device_blacklist_end" + +# APC UPS devices +ATTRS{idVendor}=="051d", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Sweex 1000VA +ATTRS{idVendor}=="0925", ATTRS{idProduct}=="1234", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Agiler UPS +ATTRS{idVendor}=="05b8", ATTRS{idProduct}=="0000", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Krauler UP-M500VA +ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0000", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Ablerex 625L USB +ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="0000", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Belkin F6C1200-UNV +ATTRS{idVendor}=="0665", ATTRS{idProduct}=="5161", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Various Liebert and Phoenixtec Power devices +ATTRS{idVendor}=="06da", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Unitek Alpha 1200Sx +ATTRS{idVendor}=="0f03", ATTRS{idProduct}=="0001", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Various Tripplite devices +ATTRS{idVendor}=="09ae", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Various MGE Office Protection Systems devices +ATTRS{idVendor}=="0463", ATTRS{idProduct}=="0001", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="0463", ATTRS{idProduct}=="ffff", ENV{ID_MM_DEVICE_IGNORE}="1" + +# CyberPower 900AVR/BC900D +ATTRS{idVendor}=="0764", ATTRS{idProduct}=="0005", ENV{ID_MM_DEVICE_IGNORE}="1" +# CyberPower CP1200AVR/BC1200D +ATTRS{idVendor}=="0764", ATTRS{idProduct}=="0501", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Various Belkin devices +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0980", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0900", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0910", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0912", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0551", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0751", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="0375", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="050d", ATTRS{idProduct}=="1100", ENV{ID_MM_DEVICE_IGNORE}="1" + +# HP R/T 2200 INTL (like SMART2200RMXL2U) +ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="1f0a", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Powerware devices +ATTRS{idVendor}=="0592", ATTRS{idProduct}=="0002", ENV{ID_MM_DEVICE_IGNORE}="1" + +# Palm Treo 700/900/etc +# Shouldn't be probed themselves, but you can install programs like +# "MobileStream USB Modem" which changes the USB PID of the device to something +# that isn't blacklisted. +ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0061", ENV{ID_MM_DEVICE_IGNORE}="1" + +LABEL="mm_usb_device_blacklist_end" + diff --git a/src/Makefile.am b/src/Makefile.am index 9209b55..2061ae8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,14 @@ SUBDIRS=. tests +udevrulesdir = $(UDEV_BASE_DIR)/rules.d +udevrules_DATA = \ + 77-mm-usb-device-blacklist.rules \ + 77-mm-pcmcia-device-blacklist.rules \ + 77-mm-platform-serial-whitelist.rules + +EXTRA_DIST = \ + $(udevrules_DATA) + noinst_LTLIBRARIES = libmodem-helpers.la libmodem_helpers_la_CPPFLAGS = \ @@ -9,38 +18,76 @@ libmodem_helpers_la_SOURCES = \ mm-errors.c \ mm-errors.h \ mm-modem-helpers.c \ - mm-modem-helpers.h + mm-modem-helpers.h \ + mm-charsets.c \ + mm-charsets.h \ + mm-utils.c \ + mm-utils.h sbin_PROGRAMS = modem-manager modem_manager_CPPFLAGS = \ $(MM_CFLAGS) \ $(GUDEV_CFLAGS) \ + -I$(top_srcdir) \ -I${top_builddir}/marshallers \ -DPLUGINDIR=\"$(pkglibdir)\" +if WITH_POLKIT +modem_manager_CPPFLAGS += $(POLKIT_CFLAGS) +endif + modem_manager_LDADD = \ $(MM_LIBS) \ $(GUDEV_LIBS) \ $(top_builddir)/marshallers/libmarshallers.la \ + $(top_builddir)/libqcdm/src/libqcdm.la \ $(builddir)/libmodem-helpers.la +if WITH_POLKIT +modem_manager_LDADD += $(POLKIT_LIBS) +endif + +auth_sources = \ + mm-auth-request.c \ + mm-auth-request.h \ + mm-auth-provider.h \ + mm-auth-provider.c \ + mm-auth-provider-factory.c + +if WITH_POLKIT +auth_sources += \ + mm-auth-request-polkit.c \ + mm-auth-request-polkit.h \ + mm-auth-provider-polkit.c \ + mm-auth-provider-polkit.h +endif + +loc_sources = \ + mm-modem-location.c \ + mm-modem-location.h + modem_manager_SOURCES = \ main.c \ mm-callback-info.c \ mm-callback-info.h \ + $(auth_sources) \ mm-manager.c \ mm-manager.h \ mm-modem.c \ mm-modem.h \ mm-port.c \ mm-port.h \ - mm-modem-base.c \ - mm-modem-base.h \ mm-serial-port.c \ mm-serial-port.h \ + mm-at-serial-port.c \ + mm-at-serial-port.h \ + mm-qcdm-serial-port.c \ + mm-qcdm-serial-port.h \ mm-serial-parsers.c \ mm-serial-parsers.h \ + mm-modem-base.c \ + mm-modem-base.h \ mm-generic-cdma.c \ mm-generic-cdma.h \ mm-generic-gsm.c \ @@ -86,7 +133,6 @@ mm-modem-gsm-network-glue.h: $(top_srcdir)/introspection/mm-modem-gsm-network.xm mm-modem-gsm-sms-glue.h: $(top_srcdir)/introspection/mm-modem-gsm-sms.xml dbus-binding-tool --prefix=mm_modem_gsm_sms --mode=glib-server --output=$@ $< - BUILT_SOURCES = \ mm-manager-glue.h \ mm-modem-glue.h \ @@ -96,4 +142,15 @@ BUILT_SOURCES = \ mm-modem-gsm-network-glue.h \ mm-modem-gsm-sms-glue.h +if WITH_LOCATION_API +mm-modem-location-glue.h: $(top_srcdir)/introspection/mm-modem-location.xml + dbus-binding-tool --prefix=mm_modem_location --mode=glib-server --output=$@ $< + +modem_manager_SOURCES += $(loc_sources) + +BUILT_SOURCES += mm-modem-location-glue.h +else +EXTRA_DIST += $(loc_sources) +endif + CLEANFILES = $(BUILT_SOURCES) diff --git a/src/main.c b/src/main.c index 3669115..72fa6dc 100644 --- a/src/main.c +++ b/src/main.c @@ -11,18 +11,22 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ +#include #include #include #include +#include #include #include #include "mm-manager.h" #include "mm-options.h" -#define HAL_DBUS_SERVICE "org.freedesktop.Hal" +#if !defined(MM_DIST_VERSION) +# define MM_DIST_VERSION VERSION +#endif static GMainLoop *loop = NULL; @@ -33,8 +37,11 @@ mm_signal_handler (int signo) mm_options_set_debug (!mm_options_debug ()); else if (signo == SIGINT || signo == SIGTERM) { g_message ("Caught signal %d, shutting down...", signo); - g_main_loop_quit (loop); - } + if (loop) + g_main_loop_quit (loop); + else + _exit (0); + } } static void @@ -178,6 +185,8 @@ main (int argc, char *argv[]) if (!mm_options_debug ()) logging_setup (); + g_message ("ModemManager (version " MM_DIST_VERSION ") starting..."); + bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err); if (!bus) { g_warning ("Could not get the system bus. Make sure " @@ -201,6 +210,16 @@ main (int argc, char *argv[]) g_signal_handler_disconnect (proxy, id); + mm_manager_shutdown (manager); + + /* Wait for all modems to be removed */ + while (mm_manager_num_modems (manager)) { + GMainContext *ctx = g_main_loop_get_context (loop); + + g_main_context_iteration (ctx, FALSE); + g_usleep (50); + } + g_object_unref (manager); g_object_unref (proxy); dbus_g_connection_unref (bus); diff --git a/src/mm-at-serial-port.c b/src/mm-at-serial-port.c new file mode 100644 index 0000000..068450d --- /dev/null +++ b/src/mm-at-serial-port.c @@ -0,0 +1,364 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#define _GNU_SOURCE /* for strcasestr() */ + +#include +#include +#include +#include + +#include "mm-at-serial-port.h" +#include "mm-errors.h" +#include "mm-options.h" + +G_DEFINE_TYPE (MMAtSerialPort, mm_at_serial_port, MM_TYPE_SERIAL_PORT) + +#define MM_AT_SERIAL_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_AT_SERIAL_PORT, MMAtSerialPortPrivate)) + +typedef struct { + /* Response parser data */ + MMAtSerialResponseParserFn response_parser_fn; + gpointer response_parser_user_data; + GDestroyNotify response_parser_notify; + GSList *unsolicited_msg_handlers; +} MMAtSerialPortPrivate; + + +/*****************************************************************************/ + +void +mm_at_serial_port_set_response_parser (MMAtSerialPort *self, + MMAtSerialResponseParserFn fn, + gpointer user_data, + GDestroyNotify notify) +{ + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + + g_return_if_fail (MM_IS_AT_SERIAL_PORT (self)); + + if (priv->response_parser_notify) + priv->response_parser_notify (priv->response_parser_user_data); + + priv->response_parser_fn = fn; + priv->response_parser_user_data = user_data; + priv->response_parser_notify = notify; +} + +static gboolean +parse_response (MMSerialPort *port, GByteArray *response, GError **error) +{ + MMAtSerialPort *self = MM_AT_SERIAL_PORT (port); + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + gboolean found; + GString *string; + + g_return_val_if_fail (priv->response_parser_fn != NULL, FALSE); + + /* Construct the string that AT-parsing functions expect */ + string = g_string_sized_new (response->len + 1); + g_string_append_len (string, (const char *) response->data, response->len); + + /* Parse it */ + found = priv->response_parser_fn (priv->response_parser_user_data, string, error); + + /* And copy it back into the response array after the parser has removed + * matches and cleaned it up. + */ + if (response->len) + g_byte_array_remove_range (response, 0, response->len); + g_byte_array_append (response, (const guint8 *) string->str, string->len); + g_string_free (string, TRUE); + return found; +} + +static gsize +handle_response (MMSerialPort *port, + GByteArray *response, + GError *error, + GCallback callback, + gpointer callback_data) +{ + MMAtSerialPort *self = MM_AT_SERIAL_PORT (port); + MMAtSerialResponseFn response_callback = (MMAtSerialResponseFn) callback; + GString *string; + + /* Convert to a string and call the callback */ + string = g_string_sized_new (response->len + 1); + g_string_append_len (string, (const char *) response->data, response->len); + response_callback (self, string, error, callback_data); + g_string_free (string, TRUE); + + return response->len; +} + +/*****************************************************************************/ + +typedef struct { + GRegex *regex; + MMAtSerialUnsolicitedMsgFn callback; + gpointer user_data; + GDestroyNotify notify; +} MMAtUnsolicitedMsgHandler; + +void +mm_at_serial_port_add_unsolicited_msg_handler (MMAtSerialPort *self, + GRegex *regex, + MMAtSerialUnsolicitedMsgFn callback, + gpointer user_data, + GDestroyNotify notify) +{ + MMAtUnsolicitedMsgHandler *handler; + MMAtSerialPortPrivate *priv; + + g_return_if_fail (MM_IS_AT_SERIAL_PORT (self)); + g_return_if_fail (regex != NULL); + + handler = g_slice_new (MMAtUnsolicitedMsgHandler); + handler->regex = g_regex_ref (regex); + handler->callback = callback; + handler->user_data = user_data; + handler->notify = notify; + + priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + priv->unsolicited_msg_handlers = g_slist_append (priv->unsolicited_msg_handlers, handler); +} + +static gboolean +remove_eval_cb (const GMatchInfo *match_info, + GString *result, + gpointer user_data) +{ + int *result_len = (int *) user_data; + int start; + int end; + + if (g_match_info_fetch_pos (match_info, 0, &start, &end)) + *result_len -= (end - start); + + return FALSE; +} + +static void +parse_unsolicited (MMSerialPort *port, GByteArray *response) +{ + MMAtSerialPort *self = MM_AT_SERIAL_PORT (port); + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + GSList *iter; + + for (iter = priv->unsolicited_msg_handlers; iter; iter = iter->next) { + MMAtUnsolicitedMsgHandler *handler = (MMAtUnsolicitedMsgHandler *) iter->data; + GMatchInfo *match_info; + gboolean matches; + + matches = g_regex_match_full (handler->regex, + (const char *) response->data, + response->len, + 0, 0, &match_info, NULL); + if (handler->callback) { + while (g_match_info_matches (match_info)) { + handler->callback (self, match_info, handler->user_data); + g_match_info_next (match_info, NULL); + } + } + + g_match_info_free (match_info); + + if (matches) { + /* Remove matches */ + char *str; + int result_len = response->len; + + str = g_regex_replace_eval (handler->regex, + (const char *) response->data, + response->len, + 0, 0, + remove_eval_cb, &result_len, NULL); + + g_byte_array_remove_range (response, 0, response->len); + g_byte_array_append (response, (const guint8 *) str, result_len); + g_free (str); + } + } +} + +/*****************************************************************************/ + +static GByteArray * +at_command_to_byte_array (const char *command) +{ + GByteArray *buf; + int cmdlen; + + g_return_val_if_fail (command != NULL, NULL); + + cmdlen = strlen (command); + buf = g_byte_array_sized_new (cmdlen + 3); + + /* Make sure there's an AT in the front */ + if (!g_str_has_prefix (command, "AT")) + g_byte_array_append (buf, (const guint8 *) "AT", 2); + g_byte_array_append (buf, (const guint8 *) command, cmdlen); + + /* Make sure there's a trailing carriage return */ + if (command[cmdlen] != '\r') + g_byte_array_append (buf, (const guint8 *) "\r", 1); + + return buf; +} + +void +mm_at_serial_port_queue_command (MMAtSerialPort *self, + const char *command, + guint32 timeout_seconds, + MMAtSerialResponseFn callback, + gpointer user_data) +{ + GByteArray *buf; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AT_SERIAL_PORT (self)); + g_return_if_fail (command != NULL); + + buf = at_command_to_byte_array (command); + g_return_if_fail (buf != NULL); + + mm_serial_port_queue_command (MM_SERIAL_PORT (self), + buf, + TRUE, + timeout_seconds, + (MMSerialResponseFn) callback, + user_data); +} + +void +mm_at_serial_port_queue_command_cached (MMAtSerialPort *self, + const char *command, + guint32 timeout_seconds, + MMAtSerialResponseFn callback, + gpointer user_data) +{ + GByteArray *buf; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AT_SERIAL_PORT (self)); + g_return_if_fail (command != NULL); + + buf = at_command_to_byte_array (command); + g_return_if_fail (buf != NULL); + + mm_serial_port_queue_command_cached (MM_SERIAL_PORT (self), + buf, + TRUE, + timeout_seconds, + (MMSerialResponseFn) callback, + user_data); +} + +static void +debug_log (MMSerialPort *port, const char *prefix, const char *buf, gsize len) +{ + static GString *debug = NULL; + const char *s; + GTimeVal tv; + + if (!debug) + debug = g_string_sized_new (256); + + g_string_append (debug, prefix); + g_string_append (debug, " '"); + + s = buf; + while (len--) { + if (g_ascii_isprint (*s)) + g_string_append_c (debug, *s); + else if (*s == '\r') + g_string_append (debug, ""); + else if (*s == '\n') + g_string_append (debug, ""); + else + g_string_append_printf (debug, "\\%d", *s); + + s++; + } + + g_string_append_c (debug, '\''); + g_get_current_time (&tv); + g_debug ("<%ld.%ld> (%s): %s", + tv.tv_sec, + tv.tv_usec, + mm_port_get_device (MM_PORT (port)), + debug->str); + g_string_truncate (debug, 0); +} + +/*****************************************************************************/ + +MMAtSerialPort * +mm_at_serial_port_new (const char *name, MMPortType ptype) +{ + return MM_AT_SERIAL_PORT (g_object_new (MM_TYPE_AT_SERIAL_PORT, + MM_PORT_DEVICE, name, + MM_PORT_SUBSYS, MM_PORT_SUBSYS_TTY, + MM_PORT_TYPE, ptype, + NULL)); +} + +static void +mm_at_serial_port_init (MMAtSerialPort *self) +{ +} + +static void +finalize (GObject *object) +{ + MMAtSerialPort *self = MM_AT_SERIAL_PORT (object); + MMAtSerialPortPrivate *priv = MM_AT_SERIAL_PORT_GET_PRIVATE (self); + + while (priv->unsolicited_msg_handlers) { + MMAtUnsolicitedMsgHandler *handler = (MMAtUnsolicitedMsgHandler *) priv->unsolicited_msg_handlers->data; + + if (handler->notify) + handler->notify (handler->user_data); + + g_regex_unref (handler->regex); + g_slice_free (MMAtUnsolicitedMsgHandler, handler); + priv->unsolicited_msg_handlers = g_slist_delete_link (priv->unsolicited_msg_handlers, + priv->unsolicited_msg_handlers); + } + + if (priv->response_parser_notify) + priv->response_parser_notify (priv->response_parser_user_data); + + G_OBJECT_CLASS (mm_at_serial_port_parent_class)->finalize (object); +} + +static void +mm_at_serial_port_class_init (MMAtSerialPortClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMSerialPortClass *port_class = MM_SERIAL_PORT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMAtSerialPortPrivate)); + + /* Virtual methods */ + object_class->finalize = finalize; + + port_class->parse_unsolicited = parse_unsolicited; + port_class->parse_response = parse_response; + port_class->handle_response = handle_response; + port_class->debug_log = debug_log; +} diff --git a/src/mm-at-serial-port.h b/src/mm-at-serial-port.h new file mode 100644 index 0000000..5d5f13f --- /dev/null +++ b/src/mm-at-serial-port.h @@ -0,0 +1,85 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#ifndef MM_AT_SERIAL_PORT_H +#define MM_AT_SERIAL_PORT_H + +#include +#include +#include + +#include "mm-serial-port.h" + +#define MM_TYPE_AT_SERIAL_PORT (mm_at_serial_port_get_type ()) +#define MM_AT_SERIAL_PORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_AT_SERIAL_PORT, MMAtSerialPort)) +#define MM_AT_SERIAL_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_AT_SERIAL_PORT, MMAtSerialPortClass)) +#define MM_IS_AT_SERIAL_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_AT_SERIAL_PORT)) +#define MM_IS_AT_SERIAL_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_AT_SERIAL_PORT)) +#define MM_AT_SERIAL_PORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_AT_SERIAL_PORT, MMAtSerialPortClass)) + +typedef struct _MMAtSerialPort MMAtSerialPort; +typedef struct _MMAtSerialPortClass MMAtSerialPortClass; + +typedef gboolean (*MMAtSerialResponseParserFn) (gpointer user_data, + GString *response, + GError **error); + +typedef void (*MMAtSerialUnsolicitedMsgFn) (MMAtSerialPort *port, + GMatchInfo *match_info, + gpointer user_data); + +typedef void (*MMAtSerialResponseFn) (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data); + +struct _MMAtSerialPort { + MMSerialPort parent; +}; + +struct _MMAtSerialPortClass { + MMSerialPortClass parent; +}; + +GType mm_at_serial_port_get_type (void); + +MMAtSerialPort *mm_at_serial_port_new (const char *name, MMPortType ptype); + +void mm_at_serial_port_add_unsolicited_msg_handler (MMAtSerialPort *self, + GRegex *regex, + MMAtSerialUnsolicitedMsgFn callback, + gpointer user_data, + GDestroyNotify notify); + +void mm_at_serial_port_set_response_parser (MMAtSerialPort *self, + MMAtSerialResponseParserFn fn, + gpointer user_data, + GDestroyNotify notify); + +void mm_at_serial_port_queue_command (MMAtSerialPort *self, + const char *command, + guint32 timeout_seconds, + MMAtSerialResponseFn callback, + gpointer user_data); + +void mm_at_serial_port_queue_command_cached (MMAtSerialPort *self, + const char *command, + guint32 timeout_seconds, + MMAtSerialResponseFn callback, + gpointer user_data); + +#endif /* MM_AT_SERIAL_PORT_H */ + diff --git a/src/mm-auth-provider-factory.c b/src/mm-auth-provider-factory.c new file mode 100644 index 0000000..356dcf2 --- /dev/null +++ b/src/mm-auth-provider-factory.c @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include + +#include "config.h" +#include "mm-auth-provider.h" + +GObject *mm_auth_provider_new (void); + +#ifdef WITH_POLKIT +#define IN_AUTH_PROVIDER_FACTORY_C +#include "mm-auth-provider-polkit.h" +#undef IN_AUTH_PROVIDER_FACTORY_C +#endif + +MMAuthProvider * +mm_auth_provider_get (void) +{ + static MMAuthProvider *singleton; + + if (!singleton) { +#if WITH_POLKIT + singleton = (MMAuthProvider *) mm_auth_provider_polkit_new (); +#else + singleton = (MMAuthProvider *) mm_auth_provider_new (); +#endif + } + + g_assert (singleton); + return singleton; +} + diff --git a/src/mm-auth-provider-polkit.c b/src/mm-auth-provider-polkit.c new file mode 100644 index 0000000..c457eaf --- /dev/null +++ b/src/mm-auth-provider-polkit.c @@ -0,0 +1,153 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include + +#include "mm-auth-request-polkit.h" +#include "mm-auth-provider-polkit.h" + +G_DEFINE_TYPE (MMAuthProviderPolkit, mm_auth_provider_polkit, MM_TYPE_AUTH_PROVIDER) + +#define MM_AUTH_PROVIDER_POLKIT_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_AUTH_PROVIDER_POLKIT, MMAuthProviderPolkitPrivate)) + +typedef struct { + PolkitAuthority *authority; + guint auth_changed_id; +} MMAuthProviderPolkitPrivate; + +enum { + PROP_NAME = 1000, +}; + +/*****************************************************************************/ + +GObject * +mm_auth_provider_polkit_new (void) +{ + return g_object_new (MM_TYPE_AUTH_PROVIDER_POLKIT, NULL); +} + +/*****************************************************************************/ + +static void +pk_authority_changed_cb (GObject *object, gpointer user_data) +{ + /* Let clients know they should re-check their authorization */ +} + +/*****************************************************************************/ + +static MMAuthRequest * +real_create_request (MMAuthProvider *provider, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify) +{ + MMAuthProviderPolkitPrivate *priv = MM_AUTH_PROVIDER_POLKIT_GET_PRIVATE (provider); + + return (MMAuthRequest *) mm_auth_request_polkit_new (priv->authority, + authorization, + owner, + context, + callback, + callback_data, + notify); +} + +/*****************************************************************************/ + +static void +mm_auth_provider_polkit_init (MMAuthProviderPolkit *self) +{ + MMAuthProviderPolkitPrivate *priv = MM_AUTH_PROVIDER_POLKIT_GET_PRIVATE (self); + + priv->authority = polkit_authority_get (); + if (priv->authority) { + priv->auth_changed_id = g_signal_connect (priv->authority, + "changed", + G_CALLBACK (pk_authority_changed_cb), + self); + } else + g_warning ("%s: failed to create PolicyKit authority.", __func__); +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_NAME: + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, "polkit"); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + MMAuthProviderPolkit *self = MM_AUTH_PROVIDER_POLKIT (object); + MMAuthProviderPolkitPrivate *priv = MM_AUTH_PROVIDER_POLKIT_GET_PRIVATE (self); + + if (priv->auth_changed_id) { + g_signal_handler_disconnect (priv->authority, priv->auth_changed_id); + priv->auth_changed_id = 0; + } + + G_OBJECT_CLASS (mm_auth_provider_polkit_parent_class)->dispose (object); +} + +static void +mm_auth_provider_polkit_class_init (MMAuthProviderPolkitClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + MMAuthProviderClass *ap_class = MM_AUTH_PROVIDER_CLASS (class); + + mm_auth_provider_polkit_parent_class = g_type_class_peek_parent (class); + g_type_class_add_private (class, sizeof (MMAuthProviderPolkitPrivate)); + + /* Virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + ap_class->create_request = real_create_request; + + /* Properties */ + g_object_class_override_property (object_class, PROP_NAME, MM_AUTH_PROVIDER_NAME); +} + diff --git a/src/mm-auth-provider-polkit.h b/src/mm-auth-provider-polkit.h new file mode 100644 index 0000000..a52c56d --- /dev/null +++ b/src/mm-auth-provider-polkit.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_AUTH_PROVIDER_POLKIT_H +#define MM_AUTH_PROVIDER_POLKIT_H + +#include + +#include "mm-auth-provider.h" + +#define MM_TYPE_AUTH_PROVIDER_POLKIT (mm_auth_provider_polkit_get_type ()) +#define MM_AUTH_PROVIDER_POLKIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_AUTH_PROVIDER_POLKIT, MMAuthProviderPolkit)) +#define MM_AUTH_PROVIDER_POLKIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_AUTH_PROVIDER_POLKIT, MMAuthProviderPolkitClass)) +#define MM_IS_AUTH_PROVIDER_POLKIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_AUTH_PROVIDER_POLKIT)) +#define MM_IS_AUTH_PROVIDER_POLKIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_AUTH_PROVIDER_POLKIT)) +#define MM_AUTH_PROVIDER_POLKIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_AUTH_PROVIDER_POLKIT, MMAuthProviderPolkitClass)) + +typedef struct { + MMAuthProvider parent; +} MMAuthProviderPolkit; + +typedef struct { + MMAuthProviderClass parent; +} MMAuthProviderPolkitClass; + +GType mm_auth_provider_polkit_get_type (void); + +GObject *mm_auth_provider_polkit_new (void); + +#endif /* MM_AUTH_PROVIDER_POLKIT_H */ + diff --git a/src/mm-auth-provider.c b/src/mm-auth-provider.c new file mode 100644 index 0000000..ceff9ad --- /dev/null +++ b/src/mm-auth-provider.c @@ -0,0 +1,300 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include + +#include "mm-marshal.h" +#include "mm-auth-provider.h" + +GObject *mm_auth_provider_new (void); + +G_DEFINE_TYPE (MMAuthProvider, mm_auth_provider, G_TYPE_OBJECT) + +#define MM_AUTH_PROVIDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_AUTH_PROVIDER, MMAuthProviderPrivate)) + +typedef struct { + GHashTable *requests; + guint process_id; +} MMAuthProviderPrivate; + +enum { + PROP_0, + PROP_NAME, + LAST_PROP +}; + +/*****************************************************************************/ + +GObject * +mm_auth_provider_new (void) +{ + return g_object_new (MM_TYPE_AUTH_PROVIDER, NULL); +} + +/*****************************************************************************/ + +static void +remove_requests (MMAuthProvider *self, GSList *remove) +{ + MMAuthProviderPrivate *priv = MM_AUTH_PROVIDER_GET_PRIVATE (self); + MMAuthRequest *req; + + while (remove) { + req = MM_AUTH_REQUEST (remove->data); + g_hash_table_remove (priv->requests, req); + remove = g_slist_remove (remove, req); + } +} + +void +mm_auth_provider_cancel_request (MMAuthProvider *provider, MMAuthRequest *req) +{ + MMAuthProviderPrivate *priv; + + g_return_if_fail (provider != NULL); + g_return_if_fail (MM_IS_AUTH_PROVIDER (provider)); + g_return_if_fail (req != NULL); + + priv = MM_AUTH_PROVIDER_GET_PRIVATE (provider); + + g_return_if_fail (g_hash_table_lookup (priv->requests, req) != NULL); + g_hash_table_remove (priv->requests, req); +} + +void +mm_auth_provider_cancel_for_owner (MMAuthProvider *self, GObject *owner) +{ + MMAuthProviderPrivate *priv; + GHashTableIter iter; + MMAuthRequest *req; + gpointer value; + GSList *remove = NULL; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AUTH_PROVIDER (self)); + + /* Find all requests from this owner */ + priv = MM_AUTH_PROVIDER_GET_PRIVATE (self); + g_hash_table_iter_init (&iter, priv->requests); + while (g_hash_table_iter_next (&iter, NULL, &value)) { + req = MM_AUTH_REQUEST (value); + if (mm_auth_request_get_owner (req) == owner) + remove = g_slist_prepend (remove, req); + } + + /* And cancel/remove them */ + remove_requests (self, remove); +} + +/*****************************************************************************/ + + +static MMAuthRequest * +real_create_request (MMAuthProvider *provider, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify) +{ + return (MMAuthRequest *) mm_auth_request_new (0, + authorization, + owner, + context, + callback, + callback_data, + notify); +} + +static gboolean +process_complete_requests (gpointer user_data) +{ + MMAuthProvider *self = MM_AUTH_PROVIDER (user_data); + MMAuthProviderPrivate *priv = MM_AUTH_PROVIDER_GET_PRIVATE (self); + GHashTableIter iter; + gpointer value; + GSList *remove = NULL; + MMAuthRequest *req; + + priv->process_id = 0; + + /* Call finished request's callbacks */ + g_hash_table_iter_init (&iter, priv->requests); + while (g_hash_table_iter_next (&iter, NULL, &value)) { + req = MM_AUTH_REQUEST (value); + + if (mm_auth_request_get_authorization (req) != MM_AUTH_RESULT_UNKNOWN) { + mm_auth_request_callback (req); + remove = g_slist_prepend (remove, req); + } + } + + /* And remove those requests from our pending request list */ + remove_requests (self, remove); + + return FALSE; +} + +static void +auth_result_cb (MMAuthRequest *req, gpointer user_data) +{ + MMAuthProvider *self = MM_AUTH_PROVIDER (user_data); + MMAuthProviderPrivate *priv = MM_AUTH_PROVIDER_GET_PRIVATE (self); + + /* Process results from an idle handler */ + if (priv->process_id == 0) + priv->process_id = g_idle_add (process_complete_requests, self); +} + +#define RESULT_SIGID_TAG "result-sigid" + +MMAuthRequest * +mm_auth_provider_request_auth (MMAuthProvider *self, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify, + GError **error) +{ + MMAuthProviderPrivate *priv; + MMAuthRequest *req; + guint32 sigid; + + g_return_val_if_fail (self != NULL, 0); + g_return_val_if_fail (MM_IS_AUTH_PROVIDER (self), 0); + g_return_val_if_fail (authorization != NULL, 0); + g_return_val_if_fail (callback != NULL, 0); + + priv = MM_AUTH_PROVIDER_GET_PRIVATE (self); + + req = MM_AUTH_PROVIDER_GET_CLASS (self)->create_request (self, + authorization, + owner, + context, + callback, + callback_data, + notify); + g_assert (req); + + sigid = g_signal_connect (req, "result", G_CALLBACK (auth_result_cb), self); + g_object_set_data (G_OBJECT (req), RESULT_SIGID_TAG, GUINT_TO_POINTER (sigid)); + + g_hash_table_insert (priv->requests, req, req); + if (!mm_auth_request_authenticate (req, error)) { + /* Error */ + g_hash_table_remove (priv->requests, req); + return NULL; + } + + return req; +} + +/*****************************************************************************/ + +static void +dispose_auth_request (gpointer data) +{ + MMAuthRequest *req = MM_AUTH_REQUEST (data); + guint sigid; + + sigid = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (req), RESULT_SIGID_TAG)); + if (sigid) + g_signal_handler_disconnect (req, sigid); + mm_auth_request_dispose (req); + g_object_unref (req); +} + +static void +mm_auth_provider_init (MMAuthProvider *self) +{ + MMAuthProviderPrivate *priv = MM_AUTH_PROVIDER_GET_PRIVATE (self); + + priv->requests = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + dispose_auth_request); +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_NAME: + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +#define NULL_PROVIDER "open" + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, NULL_PROVIDER); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + MMAuthProviderPrivate *priv = MM_AUTH_PROVIDER_GET_PRIVATE (object); + + if (priv->process_id) + g_source_remove (priv->process_id); + g_hash_table_destroy (priv->requests); + + G_OBJECT_CLASS (mm_auth_provider_parent_class)->dispose (object); +} + +static void +mm_auth_provider_class_init (MMAuthProviderClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + mm_auth_provider_parent_class = g_type_class_peek_parent (class); + g_type_class_add_private (class, sizeof (MMAuthProviderPrivate)); + + /* Virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + class->create_request = real_create_request; + + /* Properties */ + g_object_class_install_property (object_class, PROP_NAME, + g_param_spec_string (MM_AUTH_PROVIDER_NAME, + "Name", + "Provider name", + NULL_PROVIDER, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + diff --git a/src/mm-auth-provider.h b/src/mm-auth-provider.h new file mode 100644 index 0000000..26ff340 --- /dev/null +++ b/src/mm-auth-provider.h @@ -0,0 +1,86 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_AUTH_PROVIDER_H +#define MM_AUTH_PROVIDER_H + +#include +#include + +#include "mm-auth-request.h" + +/* Authorizations */ +#define MM_AUTHORIZATION_DEVICE_INFO "org.freedesktop.ModemManager.Device.Info" +#define MM_AUTHORIZATION_DEVICE_CONTROL "org.freedesktop.ModemManager.Device.Control" +#define MM_AUTHORIZATION_CONTACTS "org.freedesktop.ModemManager.Contacts" +#define MM_AUTHORIZATION_SMS "org.freedesktop.ModemManager.SMS" +#define MM_AUTHORIZATION_LOCATION "org.freedesktop.ModemManager.Location" +/******************/ + + +#define MM_TYPE_AUTH_PROVIDER (mm_auth_provider_get_type ()) +#define MM_AUTH_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_AUTH_PROVIDER, MMAuthProvider)) +#define MM_AUTH_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_AUTH_PROVIDER, MMAuthProviderClass)) +#define MM_IS_AUTH_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_AUTH_PROVIDER)) +#define MM_IS_AUTH_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_AUTH_PROVIDER)) +#define MM_AUTH_PROVIDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_AUTH_PROVIDER, MMAuthProviderClass)) + +#define MM_AUTH_PROVIDER_NAME "name" + +typedef struct { + GObject parent; +} MMAuthProvider; + +typedef struct { + GObjectClass parent; + + MMAuthRequest * (*create_request) (MMAuthProvider *provider, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify); +} MMAuthProviderClass; + +GType mm_auth_provider_get_type (void); + +/* Don't do anything clever from the notify callback... */ +MMAuthRequest *mm_auth_provider_request_auth (MMAuthProvider *provider, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify, + GError **error); + +void mm_auth_provider_cancel_for_owner (MMAuthProvider *provider, + GObject *owner); + +/* Subclass API */ + +/* To get an auth provider instance, implemented in mm-auth-provider-factory.c */ +MMAuthProvider *mm_auth_provider_get (void); + +/* schedules the request's completion */ +void mm_auth_provider_finish_request (MMAuthProvider *provider, + MMAuthRequest *req, + MMAuthResult result); + +void mm_auth_provider_cancel_request (MMAuthProvider *provider, MMAuthRequest *req); + +#endif /* MM_AUTH_PROVIDER_H */ + diff --git a/src/mm-auth-request-polkit.c b/src/mm-auth-request-polkit.c new file mode 100644 index 0000000..2a96bfe --- /dev/null +++ b/src/mm-auth-request-polkit.c @@ -0,0 +1,175 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include +#include + +#include "mm-auth-request-polkit.h" + +G_DEFINE_TYPE (MMAuthRequestPolkit, mm_auth_request_polkit, MM_TYPE_AUTH_REQUEST) + +#define MM_AUTH_REQUEST_POLKIT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_AUTH_REQUEST_POLKIT, MMAuthRequestPolkitPrivate)) + +typedef struct { + PolkitAuthority *authority; + GCancellable *cancellable; + PolkitSubject *subject; +} MMAuthRequestPolkitPrivate; + +/*****************************************************************************/ + +GObject * +mm_auth_request_polkit_new (PolkitAuthority *authority, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify) +{ + GObject *obj; + MMAuthRequestPolkitPrivate *priv; + char *sender; + + g_return_val_if_fail (authorization != NULL, NULL); + g_return_val_if_fail (owner != NULL, NULL); + g_return_val_if_fail (callback != NULL, NULL); + g_return_val_if_fail (context != NULL, NULL); + + obj = mm_auth_request_new (MM_TYPE_AUTH_REQUEST_POLKIT, + authorization, + owner, + context, + callback, + callback_data, + notify); + if (obj) { + priv = MM_AUTH_REQUEST_POLKIT_GET_PRIVATE (obj); + priv->authority = authority; + priv->cancellable = g_cancellable_new (); + + sender = dbus_g_method_get_sender (context); + priv->subject = polkit_system_bus_name_new (sender); + g_free (sender); + } + + return obj; +} + +/*****************************************************************************/ + +static void +pk_auth_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + MMAuthRequestPolkit *self = user_data; + MMAuthRequestPolkitPrivate *priv; + PolkitAuthorizationResult *pk_result; + GError *error = NULL; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AUTH_REQUEST_POLKIT (self)); + + priv = MM_AUTH_REQUEST_POLKIT_GET_PRIVATE (self); + if (!g_cancellable_is_cancelled (priv->cancellable)) { + pk_result = polkit_authority_check_authorization_finish (priv->authority, + result, + &error); + if (error) { + mm_auth_request_set_result (MM_AUTH_REQUEST (self), MM_AUTH_RESULT_INTERNAL_FAILURE); + g_warning ("%s: PolicyKit authentication error: (%d) %s", + __func__, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + } else if (polkit_authorization_result_get_is_authorized (pk_result)) + mm_auth_request_set_result (MM_AUTH_REQUEST (self), MM_AUTH_RESULT_AUTHORIZED); + else if (polkit_authorization_result_get_is_challenge (pk_result)) + mm_auth_request_set_result (MM_AUTH_REQUEST (self), MM_AUTH_RESULT_CHALLENGE); + else + mm_auth_request_set_result (MM_AUTH_REQUEST (self), MM_AUTH_RESULT_NOT_AUTHORIZED); + + g_signal_emit_by_name (self, "result"); + } + + g_object_unref (self); +} + +static gboolean +real_authenticate (MMAuthRequest *self, GError **error) +{ + MMAuthRequestPolkitPrivate *priv; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (MM_IS_AUTH_REQUEST_POLKIT (self), FALSE); + + /* We ref ourselves across the polkit call, because we can't get + * disposed of while the call is still in-progress, and even if we + * cancel ourselves we'll still get the callback. + */ + g_object_ref (self); + + priv = MM_AUTH_REQUEST_POLKIT_GET_PRIVATE (self); + polkit_authority_check_authorization (priv->authority, + priv->subject, + mm_auth_request_get_authorization (MM_AUTH_REQUEST (self)), + NULL, + POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, + priv->cancellable, + pk_auth_cb, + self); + return TRUE; +} + +static void +real_dispose (MMAuthRequest *req) +{ + g_return_if_fail (req != NULL); + g_return_if_fail (MM_IS_AUTH_REQUEST_POLKIT (req)); + + g_cancellable_cancel (MM_AUTH_REQUEST_POLKIT_GET_PRIVATE (req)->cancellable); +} + +/*****************************************************************************/ + +static void +mm_auth_request_polkit_init (MMAuthRequestPolkit *self) +{ +} + +static void +dispose (GObject *object) +{ + MMAuthRequestPolkitPrivate *priv = MM_AUTH_REQUEST_POLKIT_GET_PRIVATE (object); + + g_object_unref (priv->cancellable); + g_object_unref (priv->subject); + + G_OBJECT_CLASS (mm_auth_request_polkit_parent_class)->dispose (object); +} + +static void +mm_auth_request_polkit_class_init (MMAuthRequestPolkitClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + MMAuthRequestClass *ar_class = MM_AUTH_REQUEST_CLASS (class); + + mm_auth_request_polkit_parent_class = g_type_class_peek_parent (class); + g_type_class_add_private (class, sizeof (MMAuthRequestPolkitPrivate)); + + /* Virtual methods */ + object_class->dispose = dispose; + ar_class->authenticate = real_authenticate; + ar_class->dispose = real_dispose; +} + diff --git a/src/mm-auth-request-polkit.h b/src/mm-auth-request-polkit.h new file mode 100644 index 0000000..384ace8 --- /dev/null +++ b/src/mm-auth-request-polkit.h @@ -0,0 +1,53 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_AUTH_REQUEST_POLKIT_H +#define MM_AUTH_REQUEST_POLKIT_H + +#include +#include +#include + +#include "mm-auth-request.h" + +#define MM_TYPE_AUTH_REQUEST_POLKIT (mm_auth_request_polkit_get_type ()) +#define MM_AUTH_REQUEST_POLKIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_AUTH_REQUEST_POLKIT, MMAuthRequestPolkit)) +#define MM_AUTH_REQUEST_POLKIT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_AUTH_REQUEST_POLKIT, MMAuthRequestPolkitClass)) +#define MM_IS_AUTH_REQUEST_POLKIT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_AUTH_REQUEST_POLKIT)) +#define MM_IS_AUTH_REQUEST_POLKIT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_AUTH_REQUEST_POLKIT)) +#define MM_AUTH_REQUEST_POLKIT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_AUTH_REQUEST_POLKIT, MMAuthRequestPolkitClass)) + +typedef struct { + MMAuthRequest parent; +} MMAuthRequestPolkit; + +typedef struct { + MMAuthRequestClass parent; +} MMAuthRequestPolkitClass; + +GType mm_auth_request_polkit_get_type (void); + +GObject *mm_auth_request_polkit_new (PolkitAuthority *authority, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify); + +void mm_auth_request_polkit_cancel (MMAuthRequestPolkit *self); + +#endif /* MM_AUTH_REQUEST_POLKIT_H */ + diff --git a/src/mm-auth-request.c b/src/mm-auth-request.c new file mode 100644 index 0000000..79f0d42 --- /dev/null +++ b/src/mm-auth-request.c @@ -0,0 +1,182 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include "mm-auth-request.h" + +G_DEFINE_TYPE (MMAuthRequest, mm_auth_request, G_TYPE_OBJECT) + +#define MM_AUTH_REQUEST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_AUTH_REQUEST, MMAuthRequestPrivate)) + +typedef struct { + GObject *owner; + char *auth; + DBusGMethodInvocation *context; + MMAuthRequestCb callback; + gpointer callback_data; + + MMAuthResult result; +} MMAuthRequestPrivate; + +/*****************************************************************************/ + +GObject * +mm_auth_request_new (GType atype, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify) +{ + GObject *obj; + MMAuthRequestPrivate *priv; + + g_return_val_if_fail (authorization != NULL, NULL); + g_return_val_if_fail (owner != NULL, NULL); + g_return_val_if_fail (callback != NULL, NULL); + + obj = g_object_new (atype ? atype : MM_TYPE_AUTH_REQUEST, NULL); + if (obj) { + priv = MM_AUTH_REQUEST_GET_PRIVATE (obj); + priv->owner = owner; /* not reffed */ + priv->context = context; + priv->auth = g_strdup (authorization); + priv->callback = callback; + priv->callback_data = callback_data; + + g_object_set_data_full (obj, "caller-data", callback_data, notify); + } + + return obj; +} + +/*****************************************************************************/ + +const char * +mm_auth_request_get_authorization (MMAuthRequest *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_AUTH_REQUEST (self), NULL); + + return MM_AUTH_REQUEST_GET_PRIVATE (self)->auth; +} + +GObject * +mm_auth_request_get_owner (MMAuthRequest *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_AUTH_REQUEST (self), NULL); + + return MM_AUTH_REQUEST_GET_PRIVATE (self)->owner; +} + +MMAuthResult +mm_auth_request_get_result (MMAuthRequest *self) +{ + g_return_val_if_fail (self != NULL, MM_AUTH_RESULT_UNKNOWN); + g_return_val_if_fail (MM_IS_AUTH_REQUEST (self), MM_AUTH_RESULT_UNKNOWN); + + return MM_AUTH_REQUEST_GET_PRIVATE (self)->result; +} + +void +mm_auth_request_set_result (MMAuthRequest *self, MMAuthResult result) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AUTH_REQUEST (self)); + g_return_if_fail (result != MM_AUTH_RESULT_UNKNOWN); + + MM_AUTH_REQUEST_GET_PRIVATE (self)->result = result; +} + +gboolean +mm_auth_request_authenticate (MMAuthRequest *self, GError **error) +{ + return MM_AUTH_REQUEST_GET_CLASS (self)->authenticate (self, error); +} + +void +mm_auth_request_callback (MMAuthRequest *self) +{ + MMAuthRequestPrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AUTH_REQUEST (self)); + + priv = MM_AUTH_REQUEST_GET_PRIVATE (self); + g_warn_if_fail (priv->result != MM_AUTH_RESULT_UNKNOWN); + + if (priv->callback) + priv->callback (self, priv->owner, priv->context, priv->callback_data); +} + +void +mm_auth_request_dispose (MMAuthRequest *self) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_AUTH_REQUEST (self)); + + if (MM_AUTH_REQUEST_GET_CLASS (self)->dispose) + MM_AUTH_REQUEST_GET_CLASS (self)->dispose (self); +} + +/*****************************************************************************/ + +static gboolean +real_authenticate (MMAuthRequest *self, GError **error) +{ + /* Null auth; everything passes */ + mm_auth_request_set_result (self, MM_AUTH_RESULT_AUTHORIZED); + g_signal_emit_by_name (self, "result"); + return TRUE; +} + +/*****************************************************************************/ + +static void +mm_auth_request_init (MMAuthRequest *self) +{ +} + +static void +dispose (GObject *object) +{ + MMAuthRequestPrivate *priv = MM_AUTH_REQUEST_GET_PRIVATE (object); + + g_free (priv->auth); + + G_OBJECT_CLASS (mm_auth_request_parent_class)->dispose (object); +} + +static void +mm_auth_request_class_init (MMAuthRequestClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + mm_auth_request_parent_class = g_type_class_peek_parent (class); + g_type_class_add_private (class, sizeof (MMAuthRequestPrivate)); + + /* Virtual methods */ + object_class->dispose = dispose; + class->authenticate = real_authenticate; + + g_signal_new ("result", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0, G_TYPE_NONE); +} + diff --git a/src/mm-auth-request.h b/src/mm-auth-request.h new file mode 100644 index 0000000..e22f0a2 --- /dev/null +++ b/src/mm-auth-request.h @@ -0,0 +1,72 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_AUTH_REQUEST_H +#define MM_AUTH_REQUEST_H + +#include +#include + +#define MM_TYPE_AUTH_REQUEST (mm_auth_request_get_type ()) +#define MM_AUTH_REQUEST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_AUTH_REQUEST, MMAuthRequest)) +#define MM_AUTH_REQUEST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_AUTH_REQUEST, MMAuthRequestClass)) +#define MM_IS_AUTH_REQUEST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_AUTH_REQUEST)) +#define MM_IS_AUTH_REQUEST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_AUTH_REQUEST)) +#define MM_AUTH_REQUEST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_AUTH_REQUEST, MMAuthRequestClass)) + +typedef enum MMAuthResult { + MM_AUTH_RESULT_UNKNOWN = 0, + MM_AUTH_RESULT_INTERNAL_FAILURE, + MM_AUTH_RESULT_NOT_AUTHORIZED, + MM_AUTH_RESULT_CHALLENGE, + MM_AUTH_RESULT_AUTHORIZED +} MMAuthResult; + +typedef struct { + GObject parent; +} MMAuthRequest; + +typedef struct { + GObjectClass parent; + + gboolean (*authenticate) (MMAuthRequest *self, GError **error); + void (*dispose) (MMAuthRequest *self); +} MMAuthRequestClass; + +GType mm_auth_request_get_type (void); + +typedef void (*MMAuthRequestCb) (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data); + +GObject *mm_auth_request_new (GType atype, + const char *authorization, + GObject *owner, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify); + +const char * mm_auth_request_get_authorization (MMAuthRequest *req); +GObject * mm_auth_request_get_owner (MMAuthRequest *req); +MMAuthResult mm_auth_request_get_result (MMAuthRequest *req); +void mm_auth_request_set_result (MMAuthRequest *req, MMAuthResult result); +gboolean mm_auth_request_authenticate (MMAuthRequest *req, GError **error); +void mm_auth_request_callback (MMAuthRequest *req); +void mm_auth_request_dispose (MMAuthRequest *req); + +#endif /* MM_AUTH_REQUEST_H */ + diff --git a/src/mm-callback-info.c b/src/mm-callback-info.c index 1882898..1986bb5 100644 --- a/src/mm-callback-info.c +++ b/src/mm-callback-info.c @@ -91,6 +91,8 @@ mm_callback_info_schedule (MMCallbackInfo *info) g_return_if_fail (info->pending_id == 0); g_return_if_fail (info->called == FALSE); + g_warn_if_fail (info->chain_left == 0); + info->pending_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, callback_info_do, info, callback_info_done); } @@ -208,3 +210,24 @@ mm_callback_info_unref (MMCallbackInfo *info) } } +void +mm_callback_info_chain_start (MMCallbackInfo *info, guint num) +{ + g_return_if_fail (info != NULL); + g_return_if_fail (num > 0); + g_return_if_fail (info->chain_left == 0); + + info->chain_left = num; +} + +void +mm_callback_info_chain_complete_one (MMCallbackInfo *info) +{ + g_return_if_fail (info != NULL); + g_return_if_fail (info->chain_left > 0); + + info->chain_left--; + if (info->chain_left == 0) + mm_callback_info_schedule (info); +} + diff --git a/src/mm-callback-info.h b/src/mm-callback-info.h index 66c2048..ef4d65f 100644 --- a/src/mm-callback-info.h +++ b/src/mm-callback-info.h @@ -25,6 +25,9 @@ typedef void (*MMCallbackInfoInvokeFn) (MMCallbackInfo *info); struct _MMCallbackInfo { guint32 refcount; + /* # of ops left in this callback chain */ + guint32 chain_left; + GData *qdata; MMModem *modem; @@ -70,5 +73,8 @@ gpointer mm_callback_info_get_data (MMCallbackInfo *info, MMCallbackInfo *mm_callback_info_ref (MMCallbackInfo *info); void mm_callback_info_unref (MMCallbackInfo *info); +void mm_callback_info_chain_start (MMCallbackInfo *info, guint num); +void mm_callback_info_chain_complete_one (MMCallbackInfo *info); + #endif /* MM_CALLBACK_INFO_H */ diff --git a/src/mm-charsets.c b/src/mm-charsets.c new file mode 100644 index 0000000..c75c3a9 --- /dev/null +++ b/src/mm-charsets.c @@ -0,0 +1,175 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include + +#include "mm-charsets.h" +#include "mm-utils.h" + +typedef struct { + const char *gsm_name; + const char *other_name; + const char *iconv_from_name; + const char *iconv_to_name; + MMModemCharset charset; +} CharsetEntry; + +static CharsetEntry charset_map[] = { + { "UTF-8", "UTF8", "UTF-8", "UTF-8//TRANSLIT", MM_MODEM_CHARSET_UTF8 }, + { "UCS2", NULL, "UCS-2BE", "UCS-2BE//TRANSLIT", MM_MODEM_CHARSET_UCS2 }, + { "IRA", "ASCII", "ASCII", "ASCII//TRANSLIT", MM_MODEM_CHARSET_IRA }, + { "GSM", NULL, NULL, NULL, MM_MODEM_CHARSET_GSM }, + { "8859-1", NULL, "ISO8859-1", "ISO8859-1//TRANSLIT", MM_MODEM_CHARSET_8859_1 }, + { "PCCP437", NULL, NULL, NULL, MM_MODEM_CHARSET_PCCP437 }, + { "PCDN", NULL, NULL, NULL, MM_MODEM_CHARSET_PCDN }, + { "HEX", NULL, NULL, NULL, MM_MODEM_CHARSET_HEX }, + { NULL, NULL, NULL, NULL, MM_MODEM_CHARSET_UNKNOWN } +}; + +const char * +mm_modem_charset_to_string (MMModemCharset charset) +{ + CharsetEntry *iter = &charset_map[0]; + + g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL); + + while (iter->gsm_name) { + if (iter->charset == charset) + return iter->gsm_name; + iter++; + } + g_warn_if_reached (); + return NULL; +} + +MMModemCharset +mm_modem_charset_from_string (const char *string) +{ + CharsetEntry *iter = &charset_map[0]; + + g_return_val_if_fail (string != NULL, MM_MODEM_CHARSET_UNKNOWN); + + while (iter->gsm_name) { + if (strcasestr (string, iter->gsm_name)) + return iter->charset; + if (iter->other_name && strcasestr (string, iter->other_name)) + return iter->charset; + iter++; + } + return MM_MODEM_CHARSET_UNKNOWN; +} + +static const char * +charset_iconv_to (MMModemCharset charset) +{ + CharsetEntry *iter = &charset_map[0]; + + g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL); + + while (iter->gsm_name) { + if (iter->charset == charset) + return iter->iconv_to_name; + iter++; + } + g_warn_if_reached (); + return NULL; +} + +static const char * +charset_iconv_from (MMModemCharset charset) +{ + CharsetEntry *iter = &charset_map[0]; + + g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL); + + while (iter->gsm_name) { + if (iter->charset == charset) + return iter->iconv_from_name; + iter++; + } + g_warn_if_reached (); + return NULL; +} + +gboolean +mm_modem_charset_byte_array_append (GByteArray *array, + const char *string, + gboolean quoted, + MMModemCharset charset) +{ + const char *iconv_to; + char *converted; + GError *error = NULL; + gsize written = 0; + + g_return_val_if_fail (array != NULL, FALSE); + g_return_val_if_fail (string != NULL, FALSE); + + iconv_to = charset_iconv_to (charset); + g_return_val_if_fail (iconv_to != NULL, FALSE); + + converted = g_convert (string, + g_utf8_strlen (string, -1), + iconv_to, + "UTF-8", + NULL, + &written, + &error); + if (!converted) { + if (error) { + g_warning ("%s: failed to convert '%s' to %s character set: (%d) %s", + __func__, string, iconv_to, + error->code, error->message); + g_error_free (error); + } + return FALSE; + } + + if (quoted) + g_byte_array_append (array, (const guint8 *) "\"", 1); + g_byte_array_append (array, (const guint8 *) converted, written); + if (quoted) + g_byte_array_append (array, (const guint8 *) "\"", 1); + + g_free (converted); + return TRUE; +} + +char * +mm_modem_charset_hex_to_utf8 (const char *src, MMModemCharset charset) +{ + char *unconverted; + const char *iconv_from; + gsize unconverted_len = 0; + + g_return_val_if_fail (src != NULL, NULL); + g_return_val_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN, NULL); + + iconv_from = charset_iconv_from (charset); + g_return_val_if_fail (iconv_from != NULL, FALSE); + + unconverted = utils_hexstr2bin (src, &unconverted_len); + g_return_val_if_fail (unconverted != NULL, NULL); + + if (charset == MM_MODEM_CHARSET_UTF8 || charset == MM_MODEM_CHARSET_IRA) + return unconverted; + + return g_convert (unconverted, unconverted_len, "UTF-8//TRANSLIT", iconv_from, NULL, NULL, NULL); +} + diff --git a/src/mm-charsets.h b/src/mm-charsets.h new file mode 100644 index 0000000..5fa3406 --- /dev/null +++ b/src/mm-charsets.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_CHARSETS_H +#define MM_CHARSETS_H + +#include + +typedef enum { + MM_MODEM_CHARSET_UNKNOWN = 0x00000000, + MM_MODEM_CHARSET_GSM = 0x00000001, + MM_MODEM_CHARSET_IRA = 0x00000002, + MM_MODEM_CHARSET_8859_1 = 0x00000004, + MM_MODEM_CHARSET_UTF8 = 0x00000008, + MM_MODEM_CHARSET_UCS2 = 0x00000010, + MM_MODEM_CHARSET_PCCP437 = 0x00000020, + MM_MODEM_CHARSET_PCDN = 0x00000040, + MM_MODEM_CHARSET_HEX = 0x00000080 +} MMModemCharset; + +const char *mm_modem_charset_to_string (MMModemCharset charset); + +MMModemCharset mm_modem_charset_from_string (const char *string); + +/* Append the given string to the given byte array but re-encode it + * into the given charset first. The original string is assumed to be + * UTF-8 encoded. + */ +gboolean mm_modem_charset_byte_array_append (GByteArray *array, + const char *string, + gboolean quoted, + MMModemCharset charset); + +/* Take a string in hex representation ("00430052" or "A4BE11" for example) + * and convert it from the given character set to UTF-8. + */ +char *mm_modem_charset_hex_to_utf8 (const char *src, MMModemCharset charset); + +#endif /* MM_CHARSETS_H */ + diff --git a/src/mm-errors.c b/src/mm-errors.c index 34f56c1..e4fdda7 100644 --- a/src/mm-errors.c +++ b/src/mm-errors.c @@ -16,6 +16,9 @@ #include "mm-errors.h" +#include +#include + #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } GQuark @@ -36,10 +39,12 @@ mm_serial_error_get_type (void) if (etype == 0) { static const GEnumValue values[] = { - ENUM_ENTRY (MM_SERIAL_OPEN_FAILED, "SerialOpenFailed"), - ENUM_ENTRY (MM_SERIAL_SEND_FAILED, "SerialSendfailed"), - ENUM_ENTRY (MM_SERIAL_RESPONSE_TIMEOUT, "SerialResponseTimeout"), - ENUM_ENTRY (MM_SERIAL_OPEN_FAILED_NO_DEVICE, "SerialOpenFailedNoDevice"), + ENUM_ENTRY (MM_SERIAL_ERROR_OPEN_FAILED, "SerialOpenFailed"), + ENUM_ENTRY (MM_SERIAL_ERROR_SEND_FAILED, "SerialSendfailed"), + ENUM_ENTRY (MM_SERIAL_ERROR_RESPONSE_TIMEOUT, "SerialResponseTimeout"), + ENUM_ENTRY (MM_SERIAL_ERROR_OPEN_FAILED_NO_DEVICE, "SerialOpenFailedNoDevice"), + ENUM_ENTRY (MM_SERIAL_ERROR_FLASH_FAILED, "SerialFlashFailed"), + ENUM_ENTRY (MM_SERIAL_ERROR_NOT_OPEN, "SerialNotOpen"), { 0, 0, 0 } }; @@ -73,6 +78,8 @@ mm_modem_error_get_type (void) ENUM_ENTRY (MM_MODEM_ERROR_DISCONNECTED, "Disconnected"), ENUM_ENTRY (MM_MODEM_ERROR_OPERATION_IN_PROGRESS, "OperationInProgress"), ENUM_ENTRY (MM_MODEM_ERROR_REMOVED, "Removed"), + ENUM_ENTRY (MM_MODEM_ERROR_AUTHORIZATION_REQUIRED, "AuthorizationRequired"), + ENUM_ENTRY (MM_MODEM_ERROR_UNSUPPORTED_CHARSET, "UnsupportedCharset"), { 0, 0, 0 } }; @@ -222,69 +229,127 @@ mm_mobile_error_get_type (void) return etype; } +typedef struct { + int code; + const char *error; /* lowercase, and stripped of special chars and whitespace */ + const char *message; +} ErrorTable; + +static ErrorTable errors[] = { + { MM_MOBILE_ERROR_PHONE_FAILURE, "phonefailure", "Phone failure" }, + { MM_MOBILE_ERROR_NO_CONNECTION, "noconnectiontophone", "No connection to phone" }, + { MM_MOBILE_ERROR_LINK_RESERVED, "phoneadapterlinkreserved", "Phone-adaptor link reserved" }, + { MM_MOBILE_ERROR_NOT_ALLOWED, "operationnotallowed", "Operation not allowed" }, + { MM_MOBILE_ERROR_NOT_SUPPORTED, "operationnotsupported", "Operation not supported" }, + { MM_MOBILE_ERROR_PH_SIM_PIN, "phsimpinrequired", "PH-SIM PIN required" }, + { MM_MOBILE_ERROR_PH_FSIM_PIN, "phfsimpinrequired", "PH-FSIM PIN required" }, + { MM_MOBILE_ERROR_PH_FSIM_PUK, "phfsimpukrequired", "PH-FSIM PUK required" }, + { MM_MOBILE_ERROR_SIM_NOT_INSERTED, "simnotinserted", "SIM not inserted" }, + { MM_MOBILE_ERROR_SIM_PIN, "simpinrequired", "SIM PIN required" }, + { MM_MOBILE_ERROR_SIM_PUK, "simpukrequired", "SIM PUK required" }, + { MM_MOBILE_ERROR_SIM_FAILURE, "simfailure", "SIM failure" }, + { MM_MOBILE_ERROR_SIM_BUSY, "simbusy", "SIM busy" }, + { MM_MOBILE_ERROR_SIM_WRONG, "simwrong", "SIM wrong" }, + { MM_MOBILE_ERROR_WRONG_PASSWORD, "incorrectpassword", "Incorrect password" }, + { MM_MOBILE_ERROR_SIM_PIN2, "simpin2required", "SIM PIN2 required" }, + { MM_MOBILE_ERROR_SIM_PUK2, "simpuk2required", "SIM PUK2 required" }, + { MM_MOBILE_ERROR_MEMORY_FULL, "memoryfull", "Memory full" }, + { MM_MOBILE_ERROR_INVALID_INDEX, "invalidindex", "Invalid index" }, + { MM_MOBILE_ERROR_NOT_FOUND, "notfound", "Not found" }, + { MM_MOBILE_ERROR_MEMORY_FAILURE, "memoryfailure", "Memory failure" }, + { MM_MOBILE_ERROR_TEXT_TOO_LONG, "textstringtoolong", "Text string too long" }, + { MM_MOBILE_ERROR_INVALID_CHARS, "invalidcharactersintextstring", "Invalid characters in text string" }, + { MM_MOBILE_ERROR_DIAL_STRING_TOO_LONG, "dialstringtoolong", "Dial string too long" }, + { MM_MOBILE_ERROR_DIAL_STRING_INVALID, "invalidcharactersindialstring", "Invalid characters in dial string" }, + { MM_MOBILE_ERROR_NO_NETWORK, "nonetworkservice", "No network service" }, + { MM_MOBILE_ERROR_NETWORK_TIMEOUT, "networktimeout", "Network timeout" }, + { MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED, "networknotallowedemergencycallsonly", "Network not allowed - emergency calls only" }, + { MM_MOBILE_ERROR_NETWORK_PIN, "networkpersonalizationpinrequired", "Network personalization PIN required" }, + { MM_MOBILE_ERROR_NETWORK_PUK, "networkpersonalizationpukrequired", "Network personalization PUK required" }, + { MM_MOBILE_ERROR_NETWORK_SUBSET_PIN, "networksubsetpersonalizationpinrequired", "Network subset personalization PIN required" }, + { MM_MOBILE_ERROR_NETWORK_SUBSET_PUK, "networksubsetpersonalizationpukrequired", "Network subset personalization PUK required" }, + { MM_MOBILE_ERROR_SERVICE_PIN, "serviceproviderpersonalizationpinrequired", "Service provider personalization PIN required" }, + { MM_MOBILE_ERROR_SERVICE_PUK, "serviceproviderpersonalizationpukrequired", "Service provider personalization PUK required" }, + { MM_MOBILE_ERROR_CORP_PIN, "corporatepersonalizationpinrequired", "Corporate personalization PIN required" }, + { MM_MOBILE_ERROR_CORP_PUK, "corporatepersonalizationpukrequired", "Corporate personalization PUK required" }, + { MM_MOBILE_ERROR_HIDDEN_KEY, "phsimpukrequired", "Hidden key required" }, + { MM_MOBILE_ERROR_EAP_NOT_SUPPORTED, "eapmethodnotsupported", "EAP method not supported" }, + { MM_MOBILE_ERROR_INCORRECT_PARAMS, "incorrectparameters", "Incorrect parameters" }, + { MM_MOBILE_ERROR_UNKNOWN, "unknownerror", "Unknown error" }, + { MM_MOBILE_ERROR_GPRS_ILLEGAL_MS, "illegalms", "Illegal MS" }, + { MM_MOBILE_ERROR_GPRS_ILLEGAL_ME, "illegalme", "Illegal ME" }, + { MM_MOBILE_ERROR_GPRS_SERVICE_NOT_ALLOWED, "gprsservicesnotallowed", "GPRS services not allowed" }, + { MM_MOBILE_ERROR_GPRS_PLMN_NOT_ALLOWED, "plmnnotallowed", "PLMN not allowed" }, + { MM_MOBILE_ERROR_GPRS_LOCATION_NOT_ALLOWED, "locationareanotallowed", "Location area not allowed" }, + { MM_MOBILE_ERROR_GPRS_ROAMING_NOT_ALLOWED, "roamingnotallowedinthislocationarea", "Roaming not allowed in this location area" }, + { MM_MOBILE_ERROR_GPRS_OPTION_NOT_SUPPORTED, "serviceoperationnotsupported", "Service option not supported" }, + { MM_MOBILE_ERROR_GPRS_NOT_SUBSCRIBED, "requestedserviceoptionnotsubscribed", "Requested service option not subscribed" }, + { MM_MOBILE_ERROR_GPRS_OUT_OF_ORDER, "serviceoptiontemporarilyoutoforder", "Service option temporarily out of order" }, + { MM_MOBILE_ERROR_GPRS_UNKNOWN, "unspecifiedgprserror", "Unspecified GPRS error" }, + { MM_MOBILE_ERROR_GPRS_PDP_AUTH_FAILURE, "pdpauthenticationfailure", "PDP authentication failure" }, + { MM_MOBILE_ERROR_GPRS_INVALID_CLASS, "invalidmobileclass", "Invalid mobile class" }, + { -1, NULL, NULL } +}; + GError * mm_mobile_error_for_code (int error_code) { - const char *msg; + const char *msg = NULL; + const ErrorTable *ptr = &errors[0]; + + while (ptr->code >= 0) { + if (ptr->code == error_code) { + msg = ptr->message; + break; + } + ptr++; + } - switch (error_code) { - case MM_MOBILE_ERROR_PHONE_FAILURE: msg = "Phone failure"; break; - case MM_MOBILE_ERROR_NO_CONNECTION: msg = "No connection to phone"; break; - case MM_MOBILE_ERROR_LINK_RESERVED: msg = "Phone-adaptor link reserved"; break; - case MM_MOBILE_ERROR_NOT_ALLOWED: msg = "Operation not allowed"; break; - case MM_MOBILE_ERROR_NOT_SUPPORTED: msg = "Operation not supported"; break; - case MM_MOBILE_ERROR_PH_SIM_PIN: msg = "PH-SIM PIN required"; break; - case MM_MOBILE_ERROR_PH_FSIM_PIN: msg = "PH-FSIM PIN required"; break; - case MM_MOBILE_ERROR_PH_FSIM_PUK: msg = "PH-FSIM PUK required"; break; - case MM_MOBILE_ERROR_SIM_NOT_INSERTED: msg = "SIM not inserted"; break; - case MM_MOBILE_ERROR_SIM_PIN: msg = "SIM PIN required"; break; - case MM_MOBILE_ERROR_SIM_PUK: msg = "SIM PUK required"; break; - case MM_MOBILE_ERROR_SIM_FAILURE: msg = "SIM failure"; break; - case MM_MOBILE_ERROR_SIM_BUSY: msg = "SIM busy"; break; - case MM_MOBILE_ERROR_SIM_WRONG: msg = "SIM wrong"; break; - case MM_MOBILE_ERROR_WRONG_PASSWORD: msg = "Incorrect password"; break; - case MM_MOBILE_ERROR_SIM_PIN2: msg = "SIM PIN2 required"; break; - case MM_MOBILE_ERROR_SIM_PUK2: msg = "SIM PUK2 required"; break; - case MM_MOBILE_ERROR_MEMORY_FULL: msg = "Memory full"; break; - case MM_MOBILE_ERROR_INVALID_INDEX: msg = "Invalid index"; break; - case MM_MOBILE_ERROR_NOT_FOUND: msg = "Not found"; break; - case MM_MOBILE_ERROR_MEMORY_FAILURE: msg = "Memory failure"; break; - case MM_MOBILE_ERROR_TEXT_TOO_LONG: msg = "Text string too long"; break; - case MM_MOBILE_ERROR_INVALID_CHARS: msg = "Invalid characters in text string"; break; - case MM_MOBILE_ERROR_DIAL_STRING_TOO_LONG: msg = "Dial string too long"; break; - case MM_MOBILE_ERROR_DIAL_STRING_INVALID: msg = "Invalid characters in dial string"; break; - case MM_MOBILE_ERROR_NO_NETWORK: msg = "No network service"; break; - case MM_MOBILE_ERROR_NETWORK_TIMEOUT: msg = "Network timeout"; break; - case MM_MOBILE_ERROR_NETWORK_NOT_ALLOWED: msg = "Network not allowed - emergency calls only"; break; - case MM_MOBILE_ERROR_NETWORK_PIN: msg = "Network personalization PIN required"; break; - case MM_MOBILE_ERROR_NETWORK_PUK: msg = "Network personalization PUK required"; break; - case MM_MOBILE_ERROR_NETWORK_SUBSET_PIN: msg = "Network subset personalization PIN required"; break; - case MM_MOBILE_ERROR_NETWORK_SUBSET_PUK: msg = "Network subset personalization PUK required"; break; - case MM_MOBILE_ERROR_SERVICE_PIN: msg = "Service provider personalization PIN required"; break; - case MM_MOBILE_ERROR_SERVICE_PUK: msg = "Service provider personalization PUK required"; break; - case MM_MOBILE_ERROR_CORP_PIN: msg = "Corporate personalization PIN required"; break; - case MM_MOBILE_ERROR_CORP_PUK: msg = "Corporate personalization PUK required"; break; - case MM_MOBILE_ERROR_HIDDEN_KEY: msg = "Hidden key required"; break; - case MM_MOBILE_ERROR_EAP_NOT_SUPPORTED: msg = "EAP method not supported"; break; - case MM_MOBILE_ERROR_INCORRECT_PARAMS: msg = "Incorrect parameters"; break; - case MM_MOBILE_ERROR_UNKNOWN: msg = "Unknown error"; break; - case MM_MOBILE_ERROR_GPRS_ILLEGAL_MS: msg = "Illegal MS"; break; - case MM_MOBILE_ERROR_GPRS_ILLEGAL_ME: msg = "Illegal ME"; break; - case MM_MOBILE_ERROR_GPRS_SERVICE_NOT_ALLOWED: msg = "GPRS services not allowed"; break; - case MM_MOBILE_ERROR_GPRS_PLMN_NOT_ALLOWED: msg = "PLMN not allowed"; break; - case MM_MOBILE_ERROR_GPRS_LOCATION_NOT_ALLOWED: msg = "Location area not allowed"; break; - case MM_MOBILE_ERROR_GPRS_ROAMING_NOT_ALLOWED: msg = "Roaming not allowed in this location area"; break; - case MM_MOBILE_ERROR_GPRS_OPTION_NOT_SUPPORTED: msg = "Service option not supported"; break; - case MM_MOBILE_ERROR_GPRS_NOT_SUBSCRIBED: msg = "Requested service option not subscribed"; break; - case MM_MOBILE_ERROR_GPRS_OUT_OF_ORDER: msg = "Service option temporarily out of order"; break; - case MM_MOBILE_ERROR_GPRS_PDP_AUTH_FAILURE: msg = "PDP authentication failure"; break; - case MM_MOBILE_ERROR_GPRS_UNKNOWN: msg = "Unspecified GPRS error"; break; - case MM_MOBILE_ERROR_GPRS_INVALID_CLASS: msg = "Invalid mobile class"; break; - default: - g_warning ("Invalid error code"); + if (!msg) { + g_warning ("Invalid error code: %d", error_code); error_code = MM_MOBILE_ERROR_UNKNOWN; msg = "Unknown error"; } return g_error_new_literal (MM_MOBILE_ERROR, error_code, msg); } + +#define BUF_SIZE 100 + +GError * +mm_mobile_error_for_string (const char *str) +{ + int error_code = -1; + const ErrorTable *ptr = &errors[0]; + char buf[BUF_SIZE + 1]; + const char *msg = NULL, *p = str; + int i = 0; + + g_return_val_if_fail (str != NULL, NULL); + + /* Normalize the error code by stripping whitespace and odd characters */ + while (*p && i < BUF_SIZE) { + if (isalnum (*p)) + buf[i++] = tolower (*p); + p++; + } + buf[i] = '\0'; + + while (ptr->code >= 0) { + if (!strcmp (buf, ptr->error)) { + error_code = ptr->code; + msg = ptr->message; + break; + } + ptr++; + } + + if (!msg) { + g_warning ("Invalid error code: %d", error_code); + error_code = MM_MOBILE_ERROR_UNKNOWN; + msg = "Unknown error"; + } + + return g_error_new_literal (MM_MOBILE_ERROR, error_code, msg); +} + diff --git a/src/mm-errors.h b/src/mm-errors.h index c02a351..13a531d 100644 --- a/src/mm-errors.h +++ b/src/mm-errors.h @@ -20,10 +20,12 @@ #include enum { - MM_SERIAL_OPEN_FAILED = 0, - MM_SERIAL_SEND_FAILED = 1, - MM_SERIAL_RESPONSE_TIMEOUT = 2, - MM_SERIAL_OPEN_FAILED_NO_DEVICE = 3 + MM_SERIAL_ERROR_OPEN_FAILED = 0, + MM_SERIAL_ERROR_SEND_FAILED = 1, + MM_SERIAL_ERROR_RESPONSE_TIMEOUT = 2, + MM_SERIAL_ERROR_OPEN_FAILED_NO_DEVICE = 3, + MM_SERIAL_ERROR_FLASH_FAILED = 4, + MM_SERIAL_ERROR_NOT_OPEN = 5, }; #define MM_SERIAL_ERROR (mm_serial_error_quark ()) @@ -39,7 +41,9 @@ enum { MM_MODEM_ERROR_CONNECTED = 2, MM_MODEM_ERROR_DISCONNECTED = 3, MM_MODEM_ERROR_OPERATION_IN_PROGRESS = 4, - MM_MODEM_ERROR_REMOVED = 5 + MM_MODEM_ERROR_REMOVED = 5, + MM_MODEM_ERROR_AUTHORIZATION_REQUIRED = 6, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET = 7 }; #define MM_MODEM_ERROR (mm_modem_error_quark ()) @@ -64,6 +68,7 @@ GType mm_modem_connect_error_get_type (void); GError *mm_modem_connect_error_for_code (int error_code); +/* 3GPP TS 07.07 version 7.8.0 Release 1998 (page 90) ETSI TS 100 916 V7.8.0 (2003-03) */ enum { MM_MOBILE_ERROR_PHONE_FAILURE = 0, MM_MOBILE_ERROR_NO_CONNECTION = 1, @@ -115,8 +120,8 @@ enum { MM_MOBILE_ERROR_GPRS_OPTION_NOT_SUPPORTED = 132, MM_MOBILE_ERROR_GPRS_NOT_SUBSCRIBED = 133, MM_MOBILE_ERROR_GPRS_OUT_OF_ORDER = 134, - MM_MOBILE_ERROR_GPRS_PDP_AUTH_FAILURE = 149, MM_MOBILE_ERROR_GPRS_UNKNOWN = 148, + MM_MOBILE_ERROR_GPRS_PDP_AUTH_FAILURE = 149, MM_MOBILE_ERROR_GPRS_INVALID_CLASS = 150 }; @@ -127,5 +132,6 @@ enum { GQuark mm_mobile_error_quark (void); GType mm_mobile_error_get_type (void); GError *mm_mobile_error_for_code (int error_code); +GError *mm_mobile_error_for_string (const char *str); #endif /* MM_MODEM_ERROR_H */ diff --git a/src/mm-generic-cdma.c b/src/mm-generic-cdma.c index 50cd86c..9fe897f 100644 --- a/src/mm-generic-cdma.c +++ b/src/mm-generic-cdma.c @@ -23,10 +23,15 @@ #include "mm-generic-cdma.h" #include "mm-modem-cdma.h" #include "mm-modem-simple.h" -#include "mm-serial-port.h" +#include "mm-at-serial-port.h" +#include "mm-qcdm-serial-port.h" #include "mm-errors.h" #include "mm-callback-info.h" #include "mm-serial-parsers.h" +#include "mm-modem-helpers.h" +#include "libqcdm/src/commands.h" + +#define MM_GENERIC_CDMA_PREV_STATE_TAG "prev-state" static void simple_reg_callback (MMModemCdma *modem, MMModemCdmaRegistrationState cdma_1x_reg_state, @@ -58,6 +63,10 @@ typedef struct { gboolean evdo_rev0; gboolean evdo_revA; gboolean reg_try_css; + gboolean has_spservice; + gboolean has_speri; + + guint poll_id; MMModemCdmaRegistrationState cdma_1x_reg_state; MMModemCdmaRegistrationState evdo_reg_state; @@ -67,8 +76,9 @@ typedef struct { guint reg_state_changed_id; MMCallbackInfo *simple_connect_info; - MMSerialPort *primary; - MMSerialPort *secondary; + MMAtSerialPort *primary; + MMAtSerialPort *secondary; + MMQcdmSerialPort *qcdm; MMPort *data; } MMGenericCdmaPrivate; @@ -114,6 +124,44 @@ check_valid (MMGenericCdma *self) mm_modem_base_set_valid (MM_MODEM_BASE (self), new_valid); } +static void +get_esn_cb (MMModem *modem, + const char *result, + GError *error, + gpointer user_data) +{ + if (modem) { + mm_modem_base_set_equipment_identifier (MM_MODEM_BASE (modem), error ? "" : result); + mm_serial_port_close (MM_SERIAL_PORT (MM_GENERIC_CDMA_GET_PRIVATE (modem)->primary)); + check_valid (MM_GENERIC_CDMA (modem)); + } +} + +static void +initial_esn_check (MMGenericCdma *self) +{ + GError *error = NULL; + MMGenericCdmaPrivate *priv; + + g_return_if_fail (MM_IS_GENERIC_CDMA (self)); + priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + + g_return_if_fail (priv->primary != NULL); + + if (mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &error)) { + /* Make sure echoing is off */ + mm_at_serial_port_queue_command (priv->primary, "E0", 3, NULL, NULL); + mm_modem_cdma_get_esn (MM_MODEM_CDMA (self), get_esn_cb, NULL); + } else { + g_warning ("%s: failed to open serial port: (%d) %s", + __func__, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + check_valid (self); + } +} + static gboolean owns_port (MMModem *modem, const char *subsys, const char *name) { @@ -148,25 +196,36 @@ mm_generic_cdma_grab_port (MMGenericCdma *self, } port = mm_modem_base_add_port (MM_MODEM_BASE (self), subsys, name, ptype); - if (port && MM_IS_SERIAL_PORT (port)) { + if (!port) { + g_warn_if_fail (port != NULL); + return NULL; + } + + if (MM_IS_AT_SERIAL_PORT (port)) { g_object_set (G_OBJECT (port), MM_PORT_CARRIER_DETECT, FALSE, NULL); - mm_serial_port_set_response_parser (MM_SERIAL_PORT (port), - mm_serial_parser_v1_parse, - mm_serial_parser_v1_new (), - mm_serial_parser_v1_destroy); + mm_at_serial_port_set_response_parser (MM_AT_SERIAL_PORT (port), + mm_serial_parser_v1_e1_parse, + mm_serial_parser_v1_e1_new (), + mm_serial_parser_v1_e1_destroy); if (ptype == MM_PORT_TYPE_PRIMARY) { - priv->primary = MM_SERIAL_PORT (port); + priv->primary = MM_AT_SERIAL_PORT (port); if (!priv->data) { priv->data = port; g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); } - check_valid (self); + + /* Get modem's ESN number */ + initial_esn_check (self); + } else if (ptype == MM_PORT_TYPE_SECONDARY) - priv->secondary = MM_SERIAL_PORT (port); - } else { + priv->secondary = MM_AT_SERIAL_PORT (port); + } else if (MM_IS_QCDM_SERIAL_PORT (port)) { + if (!priv->qcdm) + priv->qcdm = MM_QCDM_SERIAL_PORT (port); + } else if (!strcmp (subsys, "net")) { /* Net device (if any) is the preferred data port */ - if (!priv->data || MM_IS_SERIAL_PORT (priv->data)) { + if (!priv->data || MM_IS_AT_SERIAL_PORT (priv->data)) { priv->data = port; g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); check_valid (self); @@ -197,7 +256,7 @@ release_port (MMModem *modem, const char *subsys, const char *name) if (!port) return; - if (port == MM_PORT (priv->primary)) { + if (port == (MMPort *) priv->primary) { mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); priv->primary = NULL; } @@ -207,17 +266,22 @@ release_port (MMModem *modem, const char *subsys, const char *name) g_object_notify (G_OBJECT (modem), MM_MODEM_DATA_DEVICE); } - if (port == MM_PORT (priv->secondary)) { + if (port == (MMPort *) priv->secondary) { mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); priv->secondary = NULL; } + if (port == (MMPort *) priv->qcdm) { + mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); + priv->qcdm = NULL; + } + check_valid (MM_GENERIC_CDMA (modem)); } -MMSerialPort * -mm_generic_cdma_get_port (MMGenericCdma *modem, - MMPortType ptype) +MMAtSerialPort * +mm_generic_cdma_get_at_port (MMGenericCdma *modem, + MMPortType ptype) { g_return_val_if_fail (MM_IS_GENERIC_CDMA (modem), NULL); g_return_val_if_fail (ptype != MM_PORT_TYPE_UNKNOWN, NULL); @@ -230,6 +294,27 @@ mm_generic_cdma_get_port (MMGenericCdma *modem, return NULL; } +MMAtSerialPort * +mm_generic_cdma_get_best_at_port (MMGenericCdma *self, GError **error) +{ + MMGenericCdmaPrivate *priv; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_GENERIC_CDMA (self), NULL); + + priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + + if (!mm_port_get_connected (MM_PORT (priv->primary))) + return priv->primary; + + if (!priv->secondary) { + g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, + "Cannot perform this operation while connected"); + } + + return priv->secondary; +} + /*****************************************************************************/ void @@ -300,6 +385,38 @@ mm_generic_cdma_evdo_get_registration_state_sync (MMGenericCdma *self) /*****************************************************************************/ +static void +periodic_poll_reg_cb (MMModemCdma *modem, + MMModemCdmaRegistrationState cdma_1x_reg_state, + MMModemCdmaRegistrationState evdo_reg_state, + GError *error, + gpointer user_data) +{ + /* cached reg state already updated */ +} + +static void +periodic_poll_signal_quality_cb (MMModem *modem, + guint32 result, + GError *error, + gpointer user_data) +{ + /* cached signal quality already updated */ +} + +static gboolean +periodic_poll_cb (gpointer user_data) +{ + MMGenericCdma *self = MM_GENERIC_CDMA (user_data); + + mm_modem_cdma_get_registration_state (MM_MODEM_CDMA (self), periodic_poll_reg_cb, NULL); + mm_modem_cdma_get_signal_quality (MM_MODEM_CDMA (self), periodic_poll_signal_quality_cb, NULL); + + return TRUE; +} + +/*****************************************************************************/ + static void update_enabled_state (MMGenericCdma *self, gboolean stay_connected, @@ -348,6 +465,43 @@ registration_cleanup (MMGenericCdma *self, GQuark error_class, guint32 error_num priv->simple_connect_info = NULL; } +static void +get_enable_info_done (MMModem *modem, + const char *manufacturer, + const char *model, + const char *version, + GError *error, + gpointer user_data) +{ + /* Modem base class handles the response for us */ +} + +static void +spservice_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + if (!error) { + MM_GENERIC_CDMA_GET_PRIVATE (user_data)->has_spservice = TRUE; + + /* +SPSERVICE provides a better indicator of registration status than + * +CSS, which some devices implement inconsistently. + */ + MM_GENERIC_CDMA_GET_PRIVATE (user_data)->reg_try_css = FALSE; + } +} + +static void +speri_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + if (!error) + MM_GENERIC_CDMA_GET_PRIVATE (user_data)->has_speri = TRUE; +} + static void enable_all_done (MMModem *modem, GError *error, gpointer user_data) { @@ -358,15 +512,33 @@ enable_all_done (MMModem *modem, GError *error, gpointer user_data) if (error) info->error = g_error_copy (error); else { + /* Try to enable XON/XOFF flow control */ + mm_at_serial_port_queue_command (priv->primary, "+IFC=1,1", 3, NULL, NULL); + /* Open up the second port, if one exists */ if (priv->secondary) { - if (!mm_serial_port_open (priv->secondary, &info->error)) { + if (!mm_serial_port_open (MM_SERIAL_PORT (priv->secondary), &info->error)) { + g_assert (info->error); + goto out; + } + } + + /* Open up the second port, if one exists */ + if (priv->qcdm) { + if (!mm_serial_port_open (MM_SERIAL_PORT (priv->qcdm), &info->error)) { g_assert (info->error); goto out; } } update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE); + + /* Grab device info right away */ + mm_modem_get_info (modem, get_enable_info_done, NULL); + + /* Check for support of Sprint-specific phone commands */ + mm_at_serial_port_queue_command (priv->primary, "+SPSERVICE?", 3, spservice_done, self); + mm_at_serial_port_queue_command (priv->primary, "$SPERI?", 3, speri_done, self); } out: @@ -380,7 +552,7 @@ out: } static void -enable_error_reporting_done (MMSerialPort *port, +enable_error_reporting_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -399,7 +571,7 @@ enable_error_reporting_done (MMSerialPort *port, } static void -init_done (MMSerialPort *port, +init_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -419,7 +591,7 @@ init_done (MMSerialPort *port, FIXME: It's mandatory by spec, so it really shouldn't be optional. Figure out which CDMA modems have problems with it and implement plugin for them. */ - mm_serial_port_queue_command (port, "+CMEE=1", 3, enable_error_reporting_done, user_data); + mm_at_serial_port_queue_command (port, "+CMEE=1", 3, enable_error_reporting_done, user_data); } } @@ -439,7 +611,7 @@ flash_done (MMSerialPort *port, GError *error, gpointer user_data) return; } - mm_serial_port_queue_command (port, "Z E0 V1 X4 &C1", 3, init_done, user_data); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "Z E0 V1 X4 &C1", 3, init_done, user_data); } static void @@ -453,7 +625,7 @@ enable (MMModem *modem, info = mm_callback_info_new (modem, callback, user_data); - if (!mm_serial_port_open (priv->primary, &info->error)) { + if (!mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &info->error)) { g_assert (info->error); mm_callback_info_schedule (info); return; @@ -463,7 +635,7 @@ enable (MMModem *modem, MM_MODEM_STATE_ENABLING, MM_MODEM_STATE_REASON_NONE); - mm_serial_port_flash (priv->primary, 100, flash_done, info); + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 100, FALSE, flash_done, info); } static void @@ -489,7 +661,7 @@ disable_all_done (MMModem *modem, GError *error, gpointer user_data) MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); - mm_serial_port_close (priv->primary); + mm_serial_port_close_force (MM_SERIAL_PORT (priv->primary)); mm_modem_set_state (modem, MM_MODEM_STATE_DISABLED, MM_MODEM_STATE_REASON_NONE); priv->cdma_1x_reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; @@ -545,21 +717,24 @@ disable (MMModem *modem, GUINT_TO_POINTER (state), NULL); + /* Close auxiliary serial ports */ if (priv->secondary) - mm_serial_port_close (priv->secondary); + mm_serial_port_close_force (MM_SERIAL_PORT (priv->secondary)); + if (priv->qcdm) + mm_serial_port_close_force (MM_SERIAL_PORT (priv->qcdm)); mm_modem_set_state (MM_MODEM (info->modem), MM_MODEM_STATE_DISABLING, MM_MODEM_STATE_REASON_NONE); if (mm_port_get_connected (MM_PORT (priv->primary))) - mm_serial_port_flash (priv->primary, 1000, disable_flash_done, info); + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disable_flash_done, info); else - disable_flash_done (priv->primary, NULL, info); + disable_flash_done (MM_SERIAL_PORT (priv->primary), NULL, info); } static void -dial_done (MMSerialPort *port, +dial_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -598,7 +773,7 @@ connect (MMModem *modem, info = mm_callback_info_new (modem, callback, user_data); command = g_strconcat ("DT", number, NULL); - mm_serial_port_queue_command (priv->primary, command, 90, dial_done, info); + mm_at_serial_port_queue_command (priv->primary, command, 90, dial_done, info); g_free (command); } @@ -648,83 +823,7 @@ disconnect (MMModem *modem, NULL); mm_modem_set_state (modem, MM_MODEM_STATE_DISCONNECTING, MM_MODEM_STATE_REASON_NONE); - mm_serial_port_flash (priv->primary, 1000, disconnect_flash_done, info); -} - -static void -card_info_invoke (MMCallbackInfo *info) -{ - MMModemInfoFn callback = (MMModemInfoFn) info->callback; - - callback (info->modem, - (char *) mm_callback_info_get_data (info, "card-info-manufacturer"), - (char *) mm_callback_info_get_data (info, "card-info-model"), - (char *) mm_callback_info_get_data (info, "card-info-version"), - info->error, info->user_data); -} - -static const char * -strip_response (const char *resp, const char *cmd) -{ - const char *p = resp; - - if (p) { - if (!strncmp (p, cmd, strlen (cmd))) - p += strlen (cmd); - while (*p == ' ') - p++; - } - return p; -} - -static void -get_version_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) -{ - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *p; - - if (!error) { - p = strip_response (response->str, "+GMR:"); - mm_callback_info_set_data (info, "card-info-version", g_strdup (p), g_free); - } else if (!info->error) - info->error = g_error_copy (error); - - mm_callback_info_schedule (info); -} - -static void -get_model_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) -{ - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *p; - - if (!error) { - p = strip_response (response->str, "+GMM:"); - mm_callback_info_set_data (info, "card-info-model", g_strdup (p), g_free); - } else if (!info->error) - info->error = g_error_copy (error); -} - -static void -get_manufacturer_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) -{ - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *p; - - if (!error) { - p = strip_response (response->str, "+GMI:"); - mm_callback_info_set_data (info, "card-info-manufacturer", g_strdup (p), g_free); - } else - info->error = g_error_copy (error); + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disconnect_flash_done, info); } static void @@ -732,30 +831,12 @@ get_card_info (MMModem *modem, MMModemInfoFn callback, gpointer user_data) { - MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); - MMCallbackInfo *info; - MMSerialPort *port = priv->primary; - - info = mm_callback_info_new_full (MM_MODEM (modem), - card_info_invoke, - G_CALLBACK (callback), - user_data); - - if (mm_port_get_connected (MM_PORT (priv->primary))) { - if (!priv->secondary) { - info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, - "Cannot modem info while connected"); - mm_callback_info_schedule (info); - return; - } - - /* Use secondary port if primary is connected */ - port = priv->secondary; - } + MMAtSerialPort *port; + GError *error = NULL; - mm_serial_port_queue_command_cached (port, "+GMI", 3, get_manufacturer_done, info); - mm_serial_port_queue_command_cached (port, "+GMM", 3, get_model_done, info); - mm_serial_port_queue_command_cached (port, "+GMR", 3, get_version_done, info); + port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &error); + mm_modem_base_get_card_info (MM_MODEM_BASE (modem), port, error, callback, user_data); + g_clear_error (&error); } /*****************************************************************************/ @@ -793,7 +874,7 @@ mm_generic_cdma_update_evdo_quality (MMGenericCdma *self, guint32 quality) #define CSQ2_TRIED "csq?-tried" static void -get_signal_quality_done (MMSerialPort *port, +get_signal_quality_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -811,7 +892,7 @@ get_signal_quality_done (MMSerialPort *port, * try the other command if the first one fails. */ mm_callback_info_set_data (info, CSQ2_TRIED, GUINT_TO_POINTER (1), NULL); - mm_serial_port_queue_command (port, "+CSQ?", 3, get_signal_quality_done, info); + mm_at_serial_port_queue_command (port, "+CSQ?", 3, get_signal_quality_done, info); return; } } else { @@ -846,6 +927,68 @@ get_signal_quality_done (MMSerialPort *port, mm_callback_info_schedule (info); } +static void +qcdm_pilot_sets_cb (MMQcdmSerialPort *port, + GByteArray *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMGenericCdmaPrivate *priv; + QCDMResult *result; + guint32 num = 0, quality = 0, i; + float best_db = -28; + + if (error) { + info->error = g_error_copy (error); + goto done; + } + + priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); + + /* Parse the response */ + result = qcdm_cmd_pilot_sets_result ((const char *) response->data, response->len, &info->error); + if (!result) + goto done; + + qcdm_cmd_pilot_sets_result_get_num (result, QCDM_CMD_PILOT_SETS_TYPE_ACTIVE, &num); + for (i = 0; i < num; i++) { + guint32 pn_offset = 0, ecio = 0; + float db = 0; + + qcdm_cmd_pilot_sets_result_get_pilot (result, + QCDM_CMD_PILOT_SETS_TYPE_ACTIVE, + i, + &pn_offset, + &ecio, + &db); + best_db = MAX (db, best_db); + } + qcdm_result_unref (result); + + if (num > 0) { + #define BEST_ECIO 3 + #define WORST_ECIO 25 + + /* EC/IO dB ranges from roughly 0 to -31 dB. Lower == worse. We + * really only care about -3 to -25 dB though, since that's about what + * you'll see in real-world usage. + */ + best_db = CLAMP (ABS (best_db), BEST_ECIO, WORST_ECIO) - BEST_ECIO; + quality = (guint32) (100 - (best_db * 100 / (WORST_ECIO - BEST_ECIO))); + } + + mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL); + + if (priv->cdma1x_quality != quality) { + priv->cdma1x_quality = quality; + mm_modem_cdma_emit_signal_quality_changed (MM_MODEM_CDMA (info->modem), quality); + } + +done: + mm_callback_info_schedule (info); +} + static void get_signal_quality (MMModemCdma *modem, MMModemUIntFn callback, @@ -853,25 +996,34 @@ get_signal_quality (MMModemCdma *modem, { MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); MMCallbackInfo *info; - MMSerialPort *port = priv->primary; + MMAtSerialPort *at_port; - if (mm_port_get_connected (MM_PORT (priv->primary))) { - if (!priv->secondary) { - g_message ("Returning saved signal quality %d", priv->cdma1x_quality); - callback (MM_MODEM (modem), priv->cdma1x_quality, NULL, user_data); - return; - } + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - /* Use secondary port if primary is connected */ - port = priv->secondary; + at_port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &info->error); + if (!at_port && !priv->qcdm) { + g_message ("Returning saved signal quality %d", priv->cdma1x_quality); + mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->cdma1x_quality), NULL); + mm_callback_info_schedule (info); + return; + } + g_clear_error (&info->error); + + if (at_port) + mm_at_serial_port_queue_command (at_port, "+CSQ", 3, get_signal_quality_done, info); + else if (priv->qcdm) { + GByteArray *pilot_sets; + + /* Use CDMA1x pilot EC/IO if we can */ + pilot_sets = g_byte_array_sized_new (25); + pilot_sets->len = qcdm_cmd_pilot_sets_new ((char *) pilot_sets->data, 25, NULL); + g_assert (pilot_sets->len); + mm_qcdm_serial_port_queue_command (priv->qcdm, pilot_sets, 3, qcdm_pilot_sets_cb, info); } - - info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_queue_command (port, "+CSQ", 3, get_signal_quality_done, info); } static void -get_string_done (MMSerialPort *port, +get_string_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -882,7 +1034,7 @@ get_string_done (MMSerialPort *port, if (error) info->error = g_error_copy (error); else { - p = strip_response (response->str, "+GSN:"); + p = mm_strip_tag (response->str, "+GSN:"); mm_callback_info_set_result (info, g_strdup (p), g_free); } @@ -894,26 +1046,18 @@ get_esn (MMModemCdma *modem, MMModemStringFn callback, gpointer user_data) { - MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); MMCallbackInfo *info; - GError *error; - MMSerialPort *port = priv->primary; - - if (mm_port_get_connected (MM_PORT (priv->primary))) { - if (!priv->secondary) { - error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, - "Cannot get ESN while connected"); - callback (MM_MODEM (modem), NULL, error, user_data); - g_error_free (error); - return; - } + MMAtSerialPort *port; + + info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); - /* Use secondary port if primary is connected */ - port = priv->secondary; + port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; } - info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_queue_command_cached (port, "+GSN", 3, get_string_done, info); + mm_at_serial_port_queue_command_cached (port, "+GSN", 3, get_string_done, info); } static void @@ -996,8 +1140,17 @@ convert_sid (const char *sid) return (int) tmp_sid; } +static GError * +new_css_no_service_error (void) +{ + /* NOTE: update reg_state_css_response() if this error changes */ + return g_error_new_literal (MM_MOBILE_ERROR, + MM_MOBILE_ERROR_NO_NETWORK, + "No service"); +} + static void -serving_system_done (MMSerialPort *port, +serving_system_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1089,12 +1242,9 @@ serving_system_done (MMSerialPort *port, sid = 99999; /* 99999 means unknown/no service */ - if (sid == 99999) { - /* NOTE: update reg_state_css_response() if this error changes */ - info->error = g_error_new_literal (MM_MOBILE_ERROR, - MM_MOBILE_ERROR_NO_NETWORK, - "No service"); - } else { + if (sid == 99999) + info->error = new_css_no_service_error (); + else { mm_callback_info_set_data (info, "class", GUINT_TO_POINTER (class), NULL); mm_callback_info_set_data (info, "band", GUINT_TO_POINTER ((guint32) band), NULL); mm_callback_info_set_data (info, "sid", GUINT_TO_POINTER (sid), NULL); @@ -1108,40 +1258,98 @@ serving_system_done (MMSerialPort *port, mm_callback_info_schedule (info); } +static void +legacy_get_serving_system (MMGenericCdma *self, MMCallbackInfo *info) +{ + MMAtSerialPort *port; + + port = mm_generic_cdma_get_best_at_port (self, &info->error); + if (port) + mm_at_serial_port_queue_command (port, "+CSS?", 3, serving_system_done, info); + else + mm_callback_info_schedule (info); +} + +static void +cdma_status_cb (MMQcdmSerialPort *port, + GByteArray *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + QCDMResult *result; + guint32 sid, rxstate; + + if (error) + goto error; + + /* Parse the response */ + result = qcdm_cmd_cdma_status_result ((const char *) response->data, response->len, &info->error); + if (!result) + goto error; + + qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_RX_STATE, &rxstate); + qcdm_result_get_uint32 (result, QCDM_CMD_CDMA_STATUS_ITEM_SID, &sid); + qcdm_result_unref (result); + + if (rxstate == QCDM_CMD_CDMA_STATUS_RX_STATE_ENTERING_CDMA) + info->error = new_css_no_service_error (); + else { + mm_callback_info_set_data (info, "class", GUINT_TO_POINTER (0), NULL); + mm_callback_info_set_data (info, "band", GUINT_TO_POINTER ((guint32) 'Z'), NULL); + mm_callback_info_set_data (info, "sid", GUINT_TO_POINTER (sid), NULL); + } + + mm_callback_info_schedule (info); + return; + +error: + /* If there was some error, fall back to use +CSS like we did before QCDM */ + legacy_get_serving_system (MM_GENERIC_CDMA (info->modem), info); +} + static void get_serving_system (MMModemCdma *modem, MMModemCdmaServingSystemFn callback, gpointer user_data) { - MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); + MMGenericCdma *self = MM_GENERIC_CDMA (modem); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); MMCallbackInfo *info; - GError *error; - MMSerialPort *port = priv->primary; - - if (mm_port_get_connected (MM_PORT (priv->primary))) { - if (!priv->secondary) { - error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, - "Cannot get serving system while connected"); - callback (modem, 0, 0, 0, error, user_data); - g_error_free (error); - return; - } - - /* Use secondary port if primary is connected */ - port = priv->secondary; - } info = mm_callback_info_new_full (MM_MODEM (modem), serving_system_invoke, G_CALLBACK (callback), user_data); - mm_serial_port_queue_command (port, "+CSS?", 3, serving_system_done, info); + if (priv->qcdm) { + GByteArray *cdma_status; + + cdma_status = g_byte_array_sized_new (25); + cdma_status->len = qcdm_cmd_cdma_status_new ((char *) cdma_status->data, 25, NULL); + g_assert (cdma_status->len); + mm_qcdm_serial_port_queue_command (priv->qcdm, cdma_status, 3, cdma_status_cb, info); + } else + legacy_get_serving_system (self, info); } +/*****************************************************************************/ + +/* Registration state stuff */ + #define CDMA_1X_STATE_TAG "cdma-1x-reg-state" #define EVDO_STATE_TAG "evdo-reg-state" +MMModemCdmaRegistrationState +mm_generic_cdma_query_reg_state_get_callback_1x_state (MMCallbackInfo *info) +{ + g_return_val_if_fail (info != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + g_return_val_if_fail (info->modem != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + g_return_val_if_fail (MM_IS_GENERIC_CDMA (info->modem), MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + + return GPOINTER_TO_UINT (mm_callback_info_get_data (info, CDMA_1X_STATE_TAG)); +} + void mm_generic_cdma_query_reg_state_set_callback_1x_state (MMCallbackInfo *info, MMModemCdmaRegistrationState new_state) @@ -1153,6 +1361,16 @@ mm_generic_cdma_query_reg_state_set_callback_1x_state (MMCallbackInfo *info, mm_callback_info_set_data (info, CDMA_1X_STATE_TAG, GUINT_TO_POINTER (new_state), NULL); } +MMModemCdmaRegistrationState +mm_generic_cdma_query_reg_state_get_callback_evdo_state (MMCallbackInfo *info) +{ + g_return_val_if_fail (info != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + g_return_val_if_fail (info->modem != NULL, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + g_return_val_if_fail (MM_IS_GENERIC_CDMA (info->modem), MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + + return GPOINTER_TO_UINT (mm_callback_info_get_data (info, EVDO_STATE_TAG)); +} + void mm_generic_cdma_query_reg_state_set_callback_evdo_state (MMCallbackInfo *info, MMModemCdmaRegistrationState new_state) @@ -1171,18 +1389,19 @@ registration_state_invoke (MMCallbackInfo *info) /* note: This is the MMModemCdma interface callback */ callback (MM_MODEM_CDMA (info->modem), - GPOINTER_TO_UINT (mm_callback_info_get_data (info, CDMA_1X_STATE_TAG)), - GPOINTER_TO_UINT (mm_callback_info_get_data (info, EVDO_STATE_TAG)), + mm_generic_cdma_query_reg_state_get_callback_1x_state (info), + mm_generic_cdma_query_reg_state_get_callback_evdo_state (info), info->error, info->user_data); } MMCallbackInfo * mm_generic_cdma_query_reg_state_callback_info_new (MMGenericCdma *self, + MMModemCdmaRegistrationState cur_cdma_state, + MMModemCdmaRegistrationState cur_evdo_state, MMModemCdmaRegistrationStateFn callback, gpointer user_data) { - MMGenericCdmaPrivate *priv; MMCallbackInfo *info; g_return_val_if_fail (self != NULL, NULL); @@ -1195,15 +1414,8 @@ mm_generic_cdma_query_reg_state_callback_info_new (MMGenericCdma *self, user_data); /* Fill with current state */ - priv = MM_GENERIC_CDMA_GET_PRIVATE (self); - mm_callback_info_set_data (info, - CDMA_1X_STATE_TAG, - GUINT_TO_POINTER (priv->cdma_1x_reg_state), - NULL); - mm_callback_info_set_data (info, - EVDO_STATE_TAG, - GUINT_TO_POINTER (priv->evdo_reg_state), - NULL); + mm_generic_cdma_query_reg_state_set_callback_1x_state (info, cur_cdma_state); + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, cur_evdo_state); return info; } @@ -1211,60 +1423,138 @@ static void set_callback_1x_state_helper (MMCallbackInfo *info, MMModemCdmaRegistrationState new_state) { - MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); - MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); + if (info->modem) { + MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); - mm_generic_cdma_set_1x_registration_state (self, new_state); - mm_generic_cdma_query_reg_state_set_callback_1x_state (info, priv->cdma_1x_reg_state); + mm_generic_cdma_set_1x_registration_state (self, new_state); + mm_generic_cdma_query_reg_state_set_callback_1x_state (info, priv->cdma_1x_reg_state); + } } static void set_callback_evdo_state_helper (MMCallbackInfo *info, MMModemCdmaRegistrationState new_state) { - MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); - MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); + if (info->modem) { + MMGenericCdma *self = MM_GENERIC_CDMA (info->modem); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem); - mm_generic_cdma_set_evdo_registration_state (self, new_state); - mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, priv->evdo_reg_state); + mm_generic_cdma_set_evdo_registration_state (self, new_state); + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, priv->evdo_reg_state); + } } static void -reg_state_query_done (MMModemCdma *cdma, - MMModemCdmaRegistrationState cdma_1x_reg_state, - MMModemCdmaRegistrationState evdo_reg_state, +subclass_reg_query_done (MMModemCdma *cdma, + MMModemCdmaRegistrationState cdma_reg_state, + MMModemCdmaRegistrationState evdo_reg_state, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + info->error = mm_modem_check_removed (info->modem, error); + if (!info->error) { + /* Set final registration state */ + set_callback_1x_state_helper (info, cdma_reg_state); + set_callback_evdo_state_helper (info, evdo_reg_state); + } + + mm_callback_info_schedule (info); +} + +static void +reg_query_speri_done (MMAtSerialPort *port, + GString *response, GError *error, gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMCallbackInfo *info = user_data; + gboolean roam = FALSE; + const char *p; + + if (error) + goto done; + + p = mm_strip_tag (response->str, "$SPERI:"); + if (!p || !mm_cdma_parse_eri (p, &roam, NULL, NULL)) + goto done; + + /* Change the 1x and EVDO registration states to roaming if they were + * anything other than UNKNOWN. + */ + if (roam) { + if (mm_generic_cdma_query_reg_state_get_callback_1x_state (info)) + mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING); + + if (mm_generic_cdma_query_reg_state_get_callback_evdo_state (info)) + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING); + } + +done: + mm_callback_info_schedule (info); +} + +static void +reg_query_spservice_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMModemCdmaRegistrationState cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; if (error) info->error = g_error_copy (error); - else { - set_callback_1x_state_helper (info, cdma_1x_reg_state); - set_callback_evdo_state_helper (info, evdo_reg_state); + else if (mm_cdma_parse_spservice_response (response->str, &cdma_state, &evdo_state)) { + mm_generic_cdma_query_reg_state_set_callback_1x_state (info, cdma_state); + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, evdo_state); + + if (MM_GENERIC_CDMA_GET_PRIVATE (info->modem)->has_speri) { + /* Get roaming status to override generic registration state */ + mm_at_serial_port_queue_command (port, "$SPERI?", 3, reg_query_speri_done, info); + return; + } } mm_callback_info_schedule (info); } static void -query_subclass_registration_state (MMGenericCdma *self, MMCallbackInfo *info) +real_query_registration_state (MMGenericCdma *self, + MMModemCdmaRegistrationState cur_cdma_state, + MMModemCdmaRegistrationState cur_evdo_state, + MMModemCdmaRegistrationStateFn callback, + gpointer user_data) { - /* Let subclasses figure out roaming and detailed registration state */ - if (MM_GENERIC_CDMA_GET_CLASS (self)->query_registration_state) { - MM_GENERIC_CDMA_GET_CLASS (self)->query_registration_state (self, - reg_state_query_done, - info); + MMCallbackInfo *info; + MMAtSerialPort *port; + + /* Seed this CallbackInfo with any previously determined registration state */ + info = mm_generic_cdma_query_reg_state_callback_info_new (self, + cur_cdma_state, + cur_evdo_state, + callback, + user_data); + + port = mm_generic_cdma_get_best_at_port (self, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + if (MM_GENERIC_CDMA_GET_PRIVATE (self)->has_spservice) { + /* Try Sprint-specific commands */ + mm_at_serial_port_queue_command (port, "+SPSERVICE?", 3, reg_query_spservice_done, info); } else { - /* Or if the subclass doesn't implement more specific checking, - * assume we're registered. + /* Assume we're registered on the 1x network if we passed +CAD, +CSS, + * and QCDM Call Manager checking. */ - reg_state_query_done (MM_MODEM_CDMA (self), - MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED, - MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED, - NULL, - info); + mm_generic_cdma_query_reg_state_set_callback_1x_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED); + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + mm_callback_info_schedule (info); } } @@ -1282,8 +1572,7 @@ reg_state_css_response (MMModemCdma *cdma, * report unknown registration state. */ if (error) { - if ( (error->domain == MM_MOBILE_ERROR) - && (error->code == MM_MOBILE_ERROR_NO_NETWORK)) { + if (g_error_matches (error, MM_MOBILE_ERROR, MM_MOBILE_ERROR_NO_NETWORK)) { set_callback_1x_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); set_callback_evdo_state_helper (info, MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); } else { @@ -1291,14 +1580,20 @@ reg_state_css_response (MMModemCdma *cdma, info->error = g_error_copy (error); } mm_callback_info_schedule (info); - return; + } else { + /* We're registered on the CDMA 1x network at least, but let subclasses + * do more specific registration checking. + */ + MM_GENERIC_CDMA_GET_CLASS (cdma)->query_registration_state (MM_GENERIC_CDMA (info->modem), + MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED, + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN, + subclass_reg_query_done, + info); } - - query_subclass_registration_state (MM_GENERIC_CDMA (info->modem), info); } static void -get_analog_digital_done (MMSerialPort *port, +get_analog_digital_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1313,7 +1608,7 @@ get_analog_digital_done (MMSerialPort *port, } /* Strip any leading command tag and spaces */ - reply = strip_response (response->str, "+CAD:"); + reply = mm_strip_tag (response->str, "+CAD:"); errno = 0; int_cad = strtol (reply, NULL, 10); @@ -1345,7 +1640,11 @@ get_analog_digital_done (MMSerialPort *port, /* Subclass knows that AT+CSS? will respond incorrectly to EVDO * state, so skip AT+CSS? query. */ - query_subclass_registration_state (MM_GENERIC_CDMA (info->modem), info); + MM_GENERIC_CDMA_GET_CLASS (info->modem)->query_registration_state (MM_GENERIC_CDMA (info->modem), + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN, + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN, + subclass_reg_query_done, + info); } return; } else { @@ -1358,6 +1657,76 @@ error: mm_callback_info_schedule (info); } +static void +reg_cmstate_cb (MMQcdmSerialPort *port, + GByteArray *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMAtSerialPort *at_port; + QCDMResult *result; + guint32 opmode = 0, sysmode = 0; + MMModemCdmaRegistrationState cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + + if (error) + goto error; + + /* Parse the response */ + result = qcdm_cmd_cm_subsys_state_info_result ((const char *) response->data, response->len, &info->error); + if (!result) + goto error; + + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE, &opmode); + qcdm_result_get_uint32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE, &sysmode); + qcdm_result_unref (result); + + if (opmode == QCDM_CMD_CM_SUBSYS_STATE_INFO_OPERATING_MODE_ONLINE) { + switch (sysmode) { + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_CDMA: + cdma_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + break; + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_HDR: + evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + break; + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_AMPS: + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_NO_SERVICE: + case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_WCDMA: + default: + break; + } + + if (cdma_state || evdo_state) { + /* Device is registered to something; see if the subclass has a + * better idea of whether we're roaming or not and what the + * access technology is. + */ + if (MM_GENERIC_CDMA_GET_CLASS (info->modem)->query_registration_state) { + MM_GENERIC_CDMA_GET_CLASS (info->modem)->query_registration_state (MM_GENERIC_CDMA (info->modem), + cdma_state, + evdo_state, + subclass_reg_query_done, + info); + return; + } + } + } + + set_callback_1x_state_helper (info, cdma_state); + set_callback_evdo_state_helper (info, evdo_state); + mm_callback_info_schedule (info); + return; + +error: + /* If there was some error, fall back to use +CAD like we did before QCDM */ + at_port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (info->modem), &info->error); + if (at_port) + mm_at_serial_port_queue_command (at_port, "+CAD?", 3, get_analog_digital_done, info); + else + mm_callback_info_schedule (info); +} + static void get_registration_state (MMModemCdma *modem, MMModemCdmaRegistrationStateFn callback, @@ -1365,22 +1734,37 @@ get_registration_state (MMModemCdma *modem, { MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (modem); MMCallbackInfo *info; - MMSerialPort *port = priv->primary; - - if (mm_port_get_connected (MM_PORT (priv->primary))) { - if (!priv->secondary) { - g_message ("Returning saved registration states: 1x: %d EVDO: %d", - priv->cdma_1x_reg_state, priv->evdo_reg_state); - callback (MM_MODEM_CDMA (modem), priv->cdma_1x_reg_state, priv->evdo_reg_state, NULL, user_data); - return; - } - - /* Use secondary port if primary is connected */ - port = priv->secondary; + MMAtSerialPort *port; + + info = mm_generic_cdma_query_reg_state_callback_info_new (MM_GENERIC_CDMA (modem), + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN, + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN, + callback, + user_data); + + port = mm_generic_cdma_get_best_at_port (MM_GENERIC_CDMA (modem), &info->error); + if (!port && !priv->qcdm) { + g_message ("Returning saved registration states: 1x: %d EVDO: %d", + priv->cdma_1x_reg_state, priv->evdo_reg_state); + mm_generic_cdma_query_reg_state_set_callback_1x_state (info, priv->cdma_1x_reg_state); + mm_generic_cdma_query_reg_state_set_callback_evdo_state (info, priv->evdo_reg_state); + mm_callback_info_schedule (info); + return; } - info = mm_generic_cdma_query_reg_state_callback_info_new (MM_GENERIC_CDMA (modem), callback, user_data); - mm_serial_port_queue_command (port, "+CAD?", 3, get_analog_digital_done, info); + /* Use QCDM for Call Manager state or HDR state before trying CAD, since + * CAD doesn't always reflect the state of the HDR radio's registration + * status. + */ + if (priv->qcdm) { + GByteArray *cmstate; + + cmstate = g_byte_array_sized_new (25); + cmstate->len = qcdm_cmd_cm_subsys_state_info_new ((char *) cmstate->data, 25, NULL); + g_assert (cmstate->len); + mm_qcdm_serial_port_queue_command (priv->qcdm, cmstate, 3, reg_cmstate_cb, info); + } else + mm_at_serial_port_queue_command (port, "+CAD?", 3, get_analog_digital_done, info); } /*****************************************************************************/ @@ -1512,10 +1896,9 @@ simple_state_machine (MMModem *modem, GError *error, gpointer user_data) const char *str; guint id; - if (error) { - info->error = g_error_copy (error); + info->error = mm_modem_check_removed (modem, error); + if (info->error) goto out; - } switch (state) { case SIMPLE_STATE_BEGIN: @@ -1550,7 +1933,8 @@ simple_state_machine (MMModem *modem, GError *error, gpointer user_data) out: if (info->error || state == SIMPLE_STATE_DONE) { - registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0); + if (modem) + registration_cleanup (MM_GENERIC_CDMA (modem), 0, 0); mm_callback_info_schedule (info); } } @@ -1609,16 +1993,26 @@ simple_uint_value (guint32 i) return val; } +#define SS_HASH_TAG "simple-get-status" + static void simple_status_got_signal_quality (MMModem *modem, guint32 result, GError *error, gpointer user_data) { - if (error) + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GHashTable *properties; + + if (error) { + info->error = g_error_copy (error); g_warning ("Error getting signal quality: %s", error->message); - else - g_hash_table_insert ((GHashTable *) user_data, "signal_quality", simple_uint_value (result)); + } else { + properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG); + g_hash_table_insert (properties, "signal_quality", simple_uint_value (result)); + } + + mm_callback_info_schedule (info); } static void @@ -1627,7 +2021,7 @@ simple_get_status_invoke (MMCallbackInfo *info) MMModemSimpleGetStatusFn callback = (MMModemSimpleGetStatusFn) info->callback; callback (MM_MODEM_SIMPLE (info->modem), - (GHashTable *) mm_callback_info_get_data (info, "simple-get-status"), + (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG), info->error, info->user_data); } @@ -1644,9 +2038,9 @@ simple_get_status (MMModemSimple *simple, G_CALLBACK (callback), user_data); - properties = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, simple_free_gvalue); - mm_callback_info_set_data (info, "simple-get-status", properties, (GDestroyNotify) g_hash_table_unref); - mm_modem_cdma_get_signal_quality (MM_MODEM_CDMA (simple), simple_status_got_signal_quality, properties); + properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, simple_free_gvalue); + mm_callback_info_set_data (info, SS_HASH_TAG, properties, (GDestroyNotify) g_hash_table_unref); + mm_modem_cdma_get_signal_quality (MM_MODEM_CDMA (simple), simple_status_got_signal_quality, info); } /*****************************************************************************/ @@ -1659,6 +2053,28 @@ modem_valid_changed (MMGenericCdma *self, GParamSpec *pspec, gpointer user_data) registration_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL); } +static void +modem_state_changed (MMGenericCdma *self, GParamSpec *pspec, gpointer user_data) +{ + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); + MMModemState state; + + /* Start polling registration status and signal quality when enabled */ + + state = mm_modem_get_state (MM_MODEM (self)); + if (state >= MM_MODEM_STATE_ENABLED) { + if (!priv->poll_id) { + priv->poll_id = g_timeout_add_seconds (30, periodic_poll_cb, self); + /* Kick one off immediately */ + periodic_poll_cb (self); + } + } else { + if (priv->poll_id) + g_source_remove (priv->poll_id); + priv->poll_id = 0; + } +} + /*****************************************************************************/ static void @@ -1690,27 +2106,13 @@ modem_simple_init (MMModemSimple *class) class->get_status = simple_get_status; } -static GObject* -constructor (GType type, - guint n_construct_params, - GObjectConstructParam *construct_params) -{ - GObject *object; - - object = G_OBJECT_CLASS (mm_generic_cdma_parent_class)->constructor (type, - n_construct_params, - construct_params); - if (object) { - g_signal_connect (object, "notify::" MM_MODEM_VALID, - G_CALLBACK (modem_valid_changed), NULL); - } - - return object; -} - static void mm_generic_cdma_init (MMGenericCdma *self) { + g_signal_connect (self, "notify::" MM_MODEM_VALID, + G_CALLBACK (modem_valid_changed), NULL); + g_signal_connect (self, "notify::" MM_MODEM_STATE, + G_CALLBACK (modem_state_changed), NULL); } static void @@ -1771,15 +2173,15 @@ get_property (GObject *object, guint prop_id, static void dispose (GObject *object) { - registration_cleanup (MM_GENERIC_CDMA (object), MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL); + MMGenericCdma *self = MM_GENERIC_CDMA (object); + MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (self); - G_OBJECT_CLASS (mm_generic_cdma_parent_class)->dispose (object); -} + registration_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL); -static void -finalize (GObject *object) -{ - G_OBJECT_CLASS (mm_generic_cdma_parent_class)->finalize (object); + if (priv->poll_id) + g_source_remove (priv->poll_id); + + G_OBJECT_CLASS (mm_generic_cdma_parent_class)->dispose (object); } static void @@ -1794,8 +2196,7 @@ mm_generic_cdma_class_init (MMGenericCdmaClass *klass) object_class->set_property = set_property; object_class->get_property = get_property; object_class->dispose = dispose; - object_class->finalize = finalize; - object_class->constructor = constructor; + klass->query_registration_state = real_query_registration_state; /* Properties */ g_object_class_override_property (object_class, diff --git a/src/mm-generic-cdma.h b/src/mm-generic-cdma.h index 5b4a0b6..e4f9e57 100644 --- a/src/mm-generic-cdma.h +++ b/src/mm-generic-cdma.h @@ -20,7 +20,7 @@ #include "mm-modem.h" #include "mm-modem-base.h" #include "mm-modem-cdma.h" -#include "mm-serial-port.h" +#include "mm-at-serial-port.h" #include "mm-callback-info.h" #define MM_TYPE_GENERIC_CDMA (mm_generic_cdma_get_type ()) @@ -42,7 +42,27 @@ typedef struct { typedef struct { MMModemBaseClass parent; + /* Subclasses should implement this function if they can more accurately + * determine the registration state and/or roaming status than the base + * class can (by using manufacturer custom AT commands or whatever). + * The base class passes its detected registration state in the + * cur_cdma_state and cur_evdo_state arguments, which the subclass should + * override if necessary before passing to the callback. + * + * Subclasses can use the helper functions + * mm_generic_cdma_query_reg_state_callback_info_new(), + * mm_generic_cdma_query_reg_state_set_callback_1x_state(), and + * mm_generic_cdma_query_reg_state_set_callback_evdo_state() to create the + * MMCallbackInfo object and to set the registration state which is passed + * to the callback when the subclass' registration query completes. + * + * Subclasses should generally not return parsing or other non-critical + * errors to the callback since that fails the entire registration check, + * rendering the superclass' checks useless. + */ void (*query_registration_state) (MMGenericCdma *self, + MMModemCdmaRegistrationState cur_cdma_state, + MMModemCdmaRegistrationState cur_evdo_state, MMModemCdmaRegistrationStateFn callback, gpointer user_data); @@ -71,8 +91,6 @@ MMModem *mm_generic_cdma_new (const char *device, /* Private, for subclasses */ -#define MM_GENERIC_CDMA_PREV_STATE_TAG "prev-state" - MMPort * mm_generic_cdma_grab_port (MMGenericCdma *self, const char *subsys, const char *name, @@ -80,7 +98,10 @@ MMPort * mm_generic_cdma_grab_port (MMGenericCdma *self, gpointer user_data, GError **error); -MMSerialPort *mm_generic_cdma_get_port (MMGenericCdma *modem, MMPortType ptype); +MMAtSerialPort *mm_generic_cdma_get_at_port (MMGenericCdma *modem, MMPortType ptype); + +MMAtSerialPort *mm_generic_cdma_get_best_at_port (MMGenericCdma *modem, + GError **error); void mm_generic_cdma_update_cdma1x_quality (MMGenericCdma *self, guint32 quality); void mm_generic_cdma_update_evdo_quality (MMGenericCdma *self, guint32 quality); @@ -99,12 +120,18 @@ MMModemCdmaRegistrationState mm_generic_cdma_evdo_get_registration_state_sync (M /* query_registration_state class function helpers */ MMCallbackInfo *mm_generic_cdma_query_reg_state_callback_info_new (MMGenericCdma *self, + MMModemCdmaRegistrationState cur_cdma_state, + MMModemCdmaRegistrationState cur_evdo_state, MMModemCdmaRegistrationStateFn callback, gpointer user_data); +MMModemCdmaRegistrationState mm_generic_cdma_query_reg_state_get_callback_1x_state (MMCallbackInfo *info); + void mm_generic_cdma_query_reg_state_set_callback_1x_state (MMCallbackInfo *info, MMModemCdmaRegistrationState new_state); +MMModemCdmaRegistrationState mm_generic_cdma_query_reg_state_get_callback_evdo_state (MMCallbackInfo *info); + void mm_generic_cdma_query_reg_state_set_callback_evdo_state (MMCallbackInfo *info, MMModemCdmaRegistrationState new_state); diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c index 4954ca1..08cde10 100644 --- a/src/mm-generic-gsm.c +++ b/src/mm-generic-gsm.c @@ -12,13 +12,15 @@ * * Copyright (C) 2008 - 2009 Novell, Inc. * Copyright (C) 2009 - 2010 Red Hat, Inc. - * Copyright (C) 2009 Ericsson + * Copyright (C) 2009 - 2010 Ericsson */ +#include #include #include #include #include + #include "mm-generic-gsm.h" #include "mm-modem-gsm-card.h" #include "mm-modem-gsm-network.h" @@ -26,8 +28,13 @@ #include "mm-modem-simple.h" #include "mm-errors.h" #include "mm-callback-info.h" +#include "mm-at-serial-port.h" +#include "mm-qcdm-serial-port.h" #include "mm-serial-parsers.h" #include "mm-modem-helpers.h" +#include "mm-options.h" +#include "mm-properties-changed-signal.h" +#include "mm-utils.h" static void modem_init (MMModem *modem_class); static void modem_gsm_card_init (MMModemGsmCard *gsm_card_class); @@ -50,39 +57,88 @@ typedef struct { char *device; gboolean valid; + gboolean pin_checked; + guint32 pin_check_tries; + guint pin_check_timeout; + + MMModemGsmAllowedMode allowed_mode; + + gboolean roam_allowed; char *oper_code; char *oper_name; guint32 ip_method; - gboolean unsolicited_registration; - MMModemGsmNetworkRegStatus reg_status; + GPtrArray *reg_regex; + + guint poll_id; + + /* CREG and CGREG info */ + gboolean creg_poll; + gboolean cgreg_poll; + /* Index 0 for CREG, index 1 for CGREG */ + gulong lac[2]; + gulong cell_id[2]; + MMModemGsmAccessTech act; + + /* Index 0 for CREG, index 1 for CGREG */ + MMModemGsmNetworkRegStatus reg_status[2]; guint pending_reg_id; MMCallbackInfo *pending_reg_info; + gboolean manual_reg; + guint signal_quality_id; + time_t signal_quality_timestamp; guint32 signal_quality; - guint32 cid; + gint cid; - MMSerialPort *primary; - MMSerialPort *secondary; + guint32 charsets; + guint32 cur_charset; + + MMAtSerialPort *primary; + MMAtSerialPort *secondary; + MMQcdmSerialPort *qcdm; MMPort *data; } MMGenericGsmPrivate; -static void get_registration_status (MMSerialPort *port, MMCallbackInfo *info); -static void read_operator_code_done (MMSerialPort *port, +static void get_registration_status (MMAtSerialPort *port, MMCallbackInfo *info); +static void read_operator_code_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data); -static void read_operator_name_done (MMSerialPort *port, +static void read_operator_name_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data); -static void reg_state_changed (MMSerialPort *port, +static void reg_state_changed (MMAtSerialPort *port, GMatchInfo *match_info, gpointer user_data); +static void get_reg_status_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data); + +static gboolean handle_reg_status_response (MMGenericGsm *self, + GString *response, + GError **error); + +static MMModemGsmAccessTech etsi_act_to_mm_act (gint act); + +static void _internal_update_access_technology (MMGenericGsm *modem, + MMModemGsmAccessTech act); + +static void reg_info_updated (MMGenericGsm *self, + gboolean update_rs, + MMGenericGsmRegType rs_type, + MMModemGsmNetworkRegStatus status, + gboolean update_code, + const char *oper_code, + gboolean update_name, + const char *oper_name); + MMModem * mm_generic_gsm_new (const char *device, const char *driver, @@ -99,24 +155,7 @@ mm_generic_gsm_new (const char *device, NULL)); } -void -mm_generic_gsm_set_unsolicited_registration (MMGenericGsm *modem, - gboolean enabled) -{ - g_return_if_fail (MM_IS_GENERIC_GSM (modem)); - - MM_GENERIC_GSM_GET_PRIVATE (modem)->unsolicited_registration = enabled; -} - -void -mm_generic_gsm_set_cid (MMGenericGsm *modem, guint32 cid) -{ - g_return_if_fail (MM_IS_GENERIC_GSM (modem)); - - MM_GENERIC_GSM_GET_PRIVATE (modem)->cid = cid; -} - -guint32 +gint mm_generic_gsm_get_cid (MMGenericGsm *modem) { g_return_val_if_fail (MM_IS_GENERIC_GSM (modem), 0); @@ -124,93 +163,98 @@ mm_generic_gsm_get_cid (MMGenericGsm *modem) return MM_GENERIC_GSM_GET_PRIVATE (modem)->cid; } -static void -got_signal_quality (MMModem *modem, - guint32 result, - GError *error, - gpointer user_data) -{ -} - -void -mm_generic_gsm_set_reg_status (MMGenericGsm *modem, - MMModemGsmNetworkRegStatus status) -{ - MMGenericGsmPrivate *priv; - - g_return_if_fail (MM_IS_GENERIC_GSM (modem)); - - priv = MM_GENERIC_GSM_GET_PRIVATE (modem); - - if (priv->reg_status != status) { - priv->reg_status = status; - - g_debug ("Registration state changed: %d", status); - - if (status == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME || - status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) { - mm_serial_port_queue_command (priv->primary, "+COPS=3,2;+COPS?", 3, read_operator_code_done, modem); - mm_serial_port_queue_command (priv->primary, "+COPS=3,0;+COPS?", 3, read_operator_name_done, modem); - mm_modem_gsm_network_get_signal_quality (MM_MODEM_GSM_NETWORK (modem), got_signal_quality, NULL); - } else { - g_free (priv->oper_code); - g_free (priv->oper_name); - priv->oper_code = priv->oper_name = NULL; +typedef struct { + const char *result; + const char *normalized; + guint code; +} CPinResult; - mm_modem_gsm_network_registration_info (MM_MODEM_GSM_NETWORK (modem), priv->reg_status, - priv->oper_code, priv->oper_name); - } +static CPinResult unlock_results[] = { + /* Longer entries first so we catch the correct one with strcmp() */ + { "PH-NETSUB PIN", "ph-netsub-pin", MM_MOBILE_ERROR_NETWORK_SUBSET_PIN }, + { "PH-NETSUB PUK", "ph-netsub-puk", MM_MOBILE_ERROR_NETWORK_SUBSET_PUK }, + { "PH-FSIM PIN", "ph-fsim-pin", MM_MOBILE_ERROR_PH_FSIM_PIN }, + { "PH-FSIM PUK", "ph-fsim-puk", MM_MOBILE_ERROR_PH_FSIM_PUK }, + { "PH-CORP PIN", "ph-corp-pin", MM_MOBILE_ERROR_CORP_PIN }, + { "PH-CORP PUK", "ph-corp-puk", MM_MOBILE_ERROR_CORP_PUK }, + { "PH-SIM PIN", "ph-sim-pin", MM_MOBILE_ERROR_PH_SIM_PIN }, + { "PH-NET PIN", "ph-net-pin", MM_MOBILE_ERROR_NETWORK_PIN }, + { "PH-NET PUK", "ph-net-puk", MM_MOBILE_ERROR_NETWORK_PUK }, + { "PH-SP PIN", "ph-sp-pin", MM_MOBILE_ERROR_SERVICE_PIN }, + { "PH-SP PUK", "ph-sp-puk", MM_MOBILE_ERROR_SERVICE_PUK }, + { "SIM PIN2", "sim-pin2", MM_MOBILE_ERROR_SIM_PIN2 }, + { "SIM PUK2", "sim-puk2", MM_MOBILE_ERROR_SIM_PUK2 }, + { "SIM PIN", "sim-pin", MM_MOBILE_ERROR_SIM_PIN }, + { "SIM PUK", "sim-puk", MM_MOBILE_ERROR_SIM_PUK }, + { NULL, NULL, MM_MOBILE_ERROR_PHONE_FAILURE }, +}; + +static GError * +error_for_unlock_required (const char *unlock) +{ + CPinResult *iter = &unlock_results[0]; + + if (!unlock || !strlen (unlock)) + return NULL; - mm_generic_gsm_update_enabled_state (modem, TRUE, MM_MODEM_STATE_REASON_NONE); + /* Translate the error */ + while (iter->result) { + if (!strcmp (iter->normalized, unlock)) + return mm_mobile_error_for_code (iter->code); + iter++; } + + return g_error_new (MM_MOBILE_ERROR, + MM_MOBILE_ERROR_UNKNOWN, + "Unknown unlock request '%s'", unlock); } -typedef struct { - const char *result; - guint code; -} CPinResult; +static void +get_unlock_retries_cb (MMModem *modem, + guint32 result, + GError *error, + gpointer user_data) +{ + if (!error) + mm_modem_base_set_unlock_retries (MM_MODEM_BASE (modem), result); + else + mm_modem_base_set_unlock_retries (MM_MODEM_BASE (modem), MM_MODEM_GSM_CARD_UNLOCK_RETRIES_NOT_SUPPORTED); +} static void -pin_check_done (MMSerialPort *port, +pin_check_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; gboolean parsed = FALSE; - static CPinResult results[] = { - { "SIM PIN", MM_MOBILE_ERROR_SIM_PIN }, - { "SIM PUK", MM_MOBILE_ERROR_SIM_PUK }, - { "PH-SIM PIN", MM_MOBILE_ERROR_PH_SIM_PIN }, - { "PH-FSIM PIN", MM_MOBILE_ERROR_PH_FSIM_PIN }, - { "PH-FSIM PUK", MM_MOBILE_ERROR_PH_FSIM_PUK }, - { "SIM PIN2", MM_MOBILE_ERROR_SIM_PIN2 }, - { "SIM PUK2", MM_MOBILE_ERROR_SIM_PUK2 }, - { "PH-NET PIN", MM_MOBILE_ERROR_NETWORK_PIN }, - { "PH-NET PUK", MM_MOBILE_ERROR_NETWORK_PUK }, - { "PH-NETSUB PIN", MM_MOBILE_ERROR_NETWORK_SUBSET_PIN }, - { "PH-NETSUB PUK", MM_MOBILE_ERROR_NETWORK_SUBSET_PUK }, - { "PH-SP PIN", MM_MOBILE_ERROR_SERVICE_PIN }, - { "PH-SP PUK", MM_MOBILE_ERROR_SERVICE_PUK }, - { "PH-CORP PIN", MM_MOBILE_ERROR_CORP_PIN }, - { "PH-CORP PUK", MM_MOBILE_ERROR_CORP_PUK }, - { NULL, MM_MOBILE_ERROR_PHONE_FAILURE }, - }; if (error) info->error = g_error_copy (error); - else if (g_str_has_prefix (response->str, "+CPIN: ")) { - const char *str = response->str + 7; + else if (response && strstr (response->str, "+CPIN: ")) { + const char *str = strstr (response->str, "+CPIN: ") + 7; - if (g_str_has_prefix (str, "READY")) + if (g_str_has_prefix (str, "READY")) { + mm_modem_base_set_unlock_required (MM_MODEM_BASE (info->modem), NULL); + if (MM_MODEM_GSM_CARD_GET_INTERFACE (info->modem)->get_unlock_retries) + mm_modem_base_set_unlock_retries (MM_MODEM_BASE (info->modem), 0); + else + mm_modem_base_set_unlock_retries (MM_MODEM_BASE (info->modem), + MM_MODEM_GSM_CARD_UNLOCK_RETRIES_NOT_SUPPORTED); parsed = TRUE; - else { - CPinResult *iter = &results[0]; + } else { + CPinResult *iter = &unlock_results[0]; /* Translate the error */ while (iter->result) { if (g_str_has_prefix (str, iter->result)) { info->error = mm_mobile_error_for_code (iter->code); + mm_modem_base_set_unlock_required (MM_MODEM_BASE (info->modem), iter->normalized); + mm_modem_gsm_card_get_unlock_retries (MM_MODEM_GSM_CARD (info->modem), + iter->normalized, + get_unlock_retries_cb, + NULL); parsed = TRUE; break; } @@ -219,20 +263,26 @@ pin_check_done (MMSerialPort *port, } } - if (!info->error && !parsed) { - info->error = g_error_new (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "Could not parse PIN request response '%s'", - response->str); + if (!parsed) { + /* Assume unlocked if we don't recognize the pin request result */ + mm_modem_base_set_unlock_required (MM_MODEM_BASE (info->modem), NULL); + mm_modem_base_set_unlock_retries (MM_MODEM_BASE (info->modem), 0); + + if (!info->error) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Could not parse PIN request response '%s'", + response->str); + } } mm_callback_info_schedule (info); } -void -mm_generic_gsm_check_pin (MMGenericGsm *modem, - MMModemFn callback, - gpointer user_data) +static void +check_pin (MMGenericGsm *modem, + MMModemFn callback, + gpointer user_data) { MMGenericGsmPrivate *priv; MMCallbackInfo *info; @@ -241,25 +291,66 @@ mm_generic_gsm_check_pin (MMGenericGsm *modem, priv = MM_GENERIC_GSM_GET_PRIVATE (modem); info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_queue_command (priv->primary, "+CPIN?", 3, pin_check_done, info); + mm_at_serial_port_queue_command (priv->primary, "+CPIN?", 3, pin_check_done, info); +} + +static void +get_imei_cb (MMModem *modem, + const char *result, + GError *error, + gpointer user_data) +{ + if (modem) { + mm_modem_base_set_equipment_identifier (MM_MODEM_BASE (modem), error ? "" : result); + mm_serial_port_close (MM_SERIAL_PORT (MM_GENERIC_GSM_GET_PRIVATE (modem)->primary)); + } } /*****************************************************************************/ +static MMModemGsmNetworkRegStatus +gsm_reg_status (MMGenericGsm *self) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + /* Some devices (Blackberries for example) will respond to +CGREG, but + * return ERROR for +CREG, probably because their firmware is just stupid. + * So here we prefer the +CREG response, but if we never got a successful + * +CREG response, we'll take +CGREG instead. + */ + + if ( priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME + || priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) + return priv->reg_status[0]; + + if ( priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME + || priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) + return priv->reg_status[1]; + + if (priv->reg_status[0] == MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING) + return priv->reg_status[0]; + + if (priv->reg_status[1] == MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING) + return priv->reg_status[1]; + + if (priv->reg_status[0] != MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN) + return priv->reg_status[0]; + + return priv->reg_status[1]; +} + void mm_generic_gsm_update_enabled_state (MMGenericGsm *self, gboolean stay_connected, MMModemStateReason reason) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); - /* While connected we don't want registration status changes to change * the modem's state away from CONNECTED. */ if (stay_connected && (mm_modem_get_state (MM_MODEM (self)) >= MM_MODEM_STATE_DISCONNECTING)) return; - switch (priv->reg_status) { + switch (gsm_reg_status (self)) { case MM_MODEM_GSM_NETWORK_REG_STATUS_HOME: case MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING: mm_modem_set_state (MM_MODEM (self), MM_MODEM_STATE_REGISTERED, reason); @@ -282,12 +373,109 @@ check_valid (MMGenericGsm *self) MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); gboolean new_valid = FALSE; - if (priv->primary && priv->data) + if (priv->primary && priv->data && priv->pin_checked) new_valid = TRUE; mm_modem_base_set_valid (MM_MODEM_BASE (self), new_valid); } + +static void initial_pin_check_done (MMModem *modem, GError *error, gpointer user_data); + +static gboolean +pin_check_again (gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + priv->pin_check_timeout = 0; + check_pin (self, initial_pin_check_done, NULL); + return FALSE; +} + +static void +initial_pin_check_done (MMModem *modem, GError *error, gpointer user_data) +{ + MMGenericGsmPrivate *priv; + + /* modem could have been removed before we get here, in which case + * 'modem' will be NULL. + */ + if (!modem) + return; + + g_return_if_fail (MM_IS_GENERIC_GSM (modem)); + priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + + if ( error + && priv->pin_check_tries++ < 3 + && !mm_modem_base_get_unlock_required (MM_MODEM_BASE (modem))) { + /* Try it again a few times */ + if (priv->pin_check_timeout) + g_source_remove (priv->pin_check_timeout); + priv->pin_check_timeout = g_timeout_add_seconds (2, pin_check_again, modem); + } else { + priv->pin_checked = TRUE; + mm_serial_port_close (MM_SERIAL_PORT (priv->primary)); + check_valid (MM_GENERIC_GSM (modem)); + } +} + +static void +initial_pin_check (MMGenericGsm *self) +{ + GError *error = NULL; + MMGenericGsmPrivate *priv; + + g_return_if_fail (MM_IS_GENERIC_GSM (self)); + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + g_return_if_fail (priv->primary != NULL); + + if (mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &error)) + check_pin (self, initial_pin_check_done, NULL); + else { + g_warning ("%s: failed to open serial port: (%d) %s", + __func__, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + + /* Ensure the modem is still somewhat usable if opening the serial + * port fails for some reason. + */ + initial_pin_check_done (MM_MODEM (self), NULL, NULL); + } +} + +static void +initial_imei_check (MMGenericGsm *self) +{ + GError *error = NULL; + MMGenericGsmPrivate *priv; + + g_return_if_fail (MM_IS_GENERIC_GSM (self)); + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + g_return_if_fail (priv->primary != NULL); + + if (mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &error)) { + /* Make sure echoing is off */ + mm_at_serial_port_queue_command (priv->primary, "E0", 3, NULL, NULL); + + /* Get modem's imei number */ + mm_modem_gsm_card_get_imei (MM_MODEM_GSM_CARD (self), + get_imei_cb, + NULL); + } else { + g_warning ("%s: failed to open serial port: (%d) %s", + __func__, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + } +} + static gboolean owns_port (MMModem *modem, const char *subsys, const char *name) { @@ -308,28 +496,50 @@ mm_generic_gsm_grab_port (MMGenericGsm *self, g_return_val_if_fail (!strcmp (subsys, "net") || !strcmp (subsys, "tty"), FALSE); port = mm_modem_base_add_port (MM_MODEM_BASE (self), subsys, name, ptype); - if (port && MM_IS_SERIAL_PORT (port)) { - mm_serial_port_set_response_parser (MM_SERIAL_PORT (port), - mm_serial_parser_v1_parse, - mm_serial_parser_v1_new (), - mm_serial_parser_v1_destroy); + if (!port) { + g_warn_if_fail (port != NULL); + return NULL; + } + + if (MM_IS_AT_SERIAL_PORT (port)) { + GPtrArray *array; + int i; - regex = g_regex_new ("\\r\\n\\+CREG: (\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); - mm_serial_port_add_unsolicited_msg_handler (MM_SERIAL_PORT (port), regex, reg_state_changed, self, NULL); - g_regex_unref (regex); + mm_at_serial_port_set_response_parser (MM_AT_SERIAL_PORT (port), + mm_serial_parser_v1_parse, + mm_serial_parser_v1_new (), + mm_serial_parser_v1_destroy); + + /* Set up CREG unsolicited message handlers */ + array = mm_gsm_creg_regex_get (FALSE); + for (i = 0; i < array->len; i++) { + regex = g_ptr_array_index (array, i); + + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, reg_state_changed, self, NULL); + } + mm_gsm_creg_regex_destroy (array); if (ptype == MM_PORT_TYPE_PRIMARY) { - priv->primary = MM_SERIAL_PORT (port); + priv->primary = MM_AT_SERIAL_PORT (port); if (!priv->data) { priv->data = port; g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); } - check_valid (self); + + /* Get modem's initial lock/unlock state */ + initial_pin_check (self); + + /* Get modem's IMEI number */ + initial_imei_check (self); + } else if (ptype == MM_PORT_TYPE_SECONDARY) - priv->secondary = MM_SERIAL_PORT (port); - } else { + priv->secondary = MM_AT_SERIAL_PORT (port); + } else if (MM_IS_QCDM_SERIAL_PORT (port)) { + if (!priv->qcdm) + priv->qcdm = MM_QCDM_SERIAL_PORT (port); + } else if (!strcmp (subsys, "net")) { /* Net device (if any) is the preferred data port */ - if (!priv->data || MM_IS_SERIAL_PORT (priv->data)) { + if (!priv->data || MM_IS_AT_SERIAL_PORT (priv->data)) { priv->data = port; g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE); check_valid (self); @@ -381,7 +591,7 @@ release_port (MMModem *modem, const char *subsys, const char *name) if (!port) return; - if (port == MM_PORT (priv->primary)) { + if (port == (MMPort *) priv->primary) { mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); priv->primary = NULL; } @@ -391,131 +601,461 @@ release_port (MMModem *modem, const char *subsys, const char *name) g_object_notify (G_OBJECT (modem), MM_MODEM_DATA_DEVICE); } - if (port == MM_PORT (priv->secondary)) { + if (port == (MMPort *) priv->secondary) { mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); priv->secondary = NULL; } + if (port == (MMPort *) priv->qcdm) { + mm_modem_base_remove_port (MM_MODEM_BASE (modem), port); + priv->qcdm = NULL; + } + check_valid (MM_GENERIC_GSM (modem)); } -void -mm_generic_gsm_enable_complete (MMGenericGsm *modem, - GError *error, - MMCallbackInfo *info) +static void +reg_poll_response (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { - g_return_if_fail (modem != NULL); - g_return_if_fail (MM_IS_GENERIC_GSM (modem)); - g_return_if_fail (info != NULL); - - if (error) { - mm_modem_set_state (MM_MODEM (modem), - MM_MODEM_STATE_DISABLED, - MM_MODEM_STATE_REASON_NONE); - - info->error = g_error_copy (error); - } else { - /* Modem is enabled; update the state */ - mm_generic_gsm_update_enabled_state (modem, FALSE, MM_MODEM_STATE_REASON_NONE); - } + MMGenericGsm *self = MM_GENERIC_GSM (user_data); - mm_callback_info_schedule (info); + if (!error) + handle_reg_status_response (self, response, NULL); } static void -enable_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +periodic_signal_quality_cb (MMModem *modem, + guint32 result, + GError *error, + gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - - /* Ignore power-up command errors, not all devices actually support - * CFUN=1. - */ - /* FIXME: instead of just ignoring errors, since we allow subclasses - * to override the power-on command, make a class function for powering - * on the phone and let the subclass decided whether it wants to handle - * errors or ignore them. - */ - - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), NULL, info); + /* Cached signal quality already updated */ } static void -init_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +periodic_access_tech_cb (MMModem *modem, + guint32 act, + GError *error, + gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - char *cmd = NULL; + if (modem && !error && act) + mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (modem), act); +} - if (error) { - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); - return; - } +static gboolean +periodic_poll_cb (gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMAtSerialPort *port; - /* Ensure echo is off after the init command; some modems ignore the - * E0 when it's in the same like as ATZ (Option GIO322). - */ - mm_serial_port_queue_command (port, "E0 +CMEE=1", 2, NULL, NULL); + port = mm_generic_gsm_get_best_at_port (self, NULL); + if (!port) + return TRUE; /* oh well, try later */ - g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_INIT_CMD_OPTIONAL, &cmd, NULL); - mm_serial_port_queue_command (port, cmd, 2, NULL, NULL); - g_free (cmd); + if (priv->creg_poll) + mm_at_serial_port_queue_command (port, "+CREG?", 10, reg_poll_response, self); + if (priv->cgreg_poll) + mm_at_serial_port_queue_command (port, "+CGREG?", 10, reg_poll_response, self); - if (MM_GENERIC_GSM_GET_PRIVATE (info->modem)->unsolicited_registration) - mm_serial_port_queue_command (port, "+CREG=1", 5, NULL, NULL); - else - mm_serial_port_queue_command (port, "+CREG=0", 5, NULL, NULL); + mm_modem_gsm_network_get_signal_quality (MM_MODEM_GSM_NETWORK (self), + periodic_signal_quality_cb, + NULL); - g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_UP_CMD, &cmd, NULL); - if (cmd && strlen (cmd)) - mm_serial_port_queue_command (port, cmd, 5, enable_done, user_data); - else - enable_done (port, NULL, NULL, user_data); - g_free (cmd); + if (MM_GENERIC_GSM_GET_CLASS (self)->get_access_technology) + MM_GENERIC_GSM_GET_CLASS (self)->get_access_technology (self, periodic_access_tech_cb, NULL); + + return TRUE; /* continue running */ } static void -enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data) +cgreg1_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { MMCallbackInfo *info = user_data; - char *cmd = NULL; - if (error) { - mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); - return; - } + info->error = mm_modem_check_removed (info->modem, error); + if (info->modem) { + if (info->error) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); - g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_INIT_CMD, &cmd, NULL); - mm_serial_port_queue_command (port, cmd, 3, init_done, user_data); - g_free (cmd); + g_clear_error (&info->error); + + /* The modem doesn't like unsolicited CGREG, so we'll need to poll */ + priv->cgreg_poll = TRUE; + } + /* Success; get initial state */ + mm_at_serial_port_queue_command (port, "+CGREG?", 10, reg_poll_response, info->modem); + } + mm_callback_info_schedule (info); } static void -real_do_enable (MMGenericGsm *self, MMModemFn callback, gpointer user_data) +cgreg2_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); - MMCallbackInfo *info; + MMCallbackInfo *info = user_data; - info = mm_callback_info_new (MM_MODEM (self), callback, user_data); - mm_serial_port_flash (priv->primary, 100, enable_flash_done, info); + /* Ignore errors except modem removal errors */ + info->error = mm_modem_check_removed (info->modem, error); + if (info->modem) { + if (info->error) { + g_clear_error (&info->error); + /* Try CGREG=1 instead */ + mm_at_serial_port_queue_command (port, "+CGREG=1", 3, cgreg1_done, info); + } else { + /* Success; get initial state */ + mm_at_serial_port_queue_command (port, "+CGREG?", 10, reg_poll_response, info->modem); + + /* All done */ + mm_callback_info_schedule (info); + } + } else { + /* Modem got removed */ + mm_callback_info_schedule (info); + } } static void -enable (MMModem *modem, - MMModemFn callback, - gpointer user_data) +creg1_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); - GError *error = NULL; + MMCallbackInfo *info = user_data; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->modem) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + if (info->error) { + g_clear_error (&info->error); + + /* The modem doesn't like unsolicited CREG, so we'll need to poll */ + priv->creg_poll = TRUE; + } + /* Success; get initial state */ + mm_at_serial_port_queue_command (port, "+CREG?", 10, reg_poll_response, info->modem); + + /* Now try to set up CGREG messages */ + mm_at_serial_port_queue_command (port, "+CGREG=2", 3, cgreg2_done, info); + } else { + /* Modem got removed */ + mm_callback_info_schedule (info); + } +} + +static void +creg2_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + + /* Ignore errors except modem removal errors */ + info->error = mm_modem_check_removed (info->modem, error); + if (info->modem) { + if (info->error) { + g_clear_error (&info->error); + mm_at_serial_port_queue_command (port, "+CREG=1", 3, creg1_done, info); + } else { + /* Success; get initial state */ + mm_at_serial_port_queue_command (port, "+CREG?", 10, reg_poll_response, info->modem); + + /* Now try to set up CGREG messages */ + mm_at_serial_port_queue_command (port, "+CGREG=2", 3, cgreg2_done, info); + } + } else { + /* Modem got removed */ + mm_callback_info_schedule (info); + } +} + +static void +enable_failed (MMModem *modem, GError *error, MMCallbackInfo *info) +{ + MMGenericGsmPrivate *priv; + + info->error = mm_modem_check_removed (modem, error); + + if (modem) { + mm_modem_set_state (modem, + MM_MODEM_STATE_DISABLED, + MM_MODEM_STATE_REASON_NONE); + + priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + + if (priv->primary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->primary))) + mm_serial_port_close_force (MM_SERIAL_PORT (priv->primary)); + if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) + mm_serial_port_close_force (MM_SERIAL_PORT (priv->secondary)); + } + + mm_callback_info_schedule (info); +} + +static guint32 best_charsets[] = { + MM_MODEM_CHARSET_UTF8, + MM_MODEM_CHARSET_UCS2, + MM_MODEM_CHARSET_8859_1, + MM_MODEM_CHARSET_IRA, + MM_MODEM_CHARSET_UNKNOWN +}; + +static void +enabled_set_charset_done (MMModem *modem, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + guint idx; + + /* only modem removals are really a hard error */ + if (error) { + if (!modem) { + enable_failed (modem, error, info); + return; + } + + /* Try the next best charset */ + idx = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "best-charset")) + 1; + if (best_charsets[idx] == MM_MODEM_CHARSET_UNKNOWN) { + GError *tmp_error; + + /* No more character sets we can use */ + tmp_error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET, + "Failed to find a usable modem character set"); + enable_failed (modem, tmp_error, info); + g_error_free (tmp_error); + } else { + /* Send the new charset */ + mm_callback_info_set_data (info, "best-charset", GUINT_TO_POINTER (idx), NULL); + mm_modem_set_charset (modem, best_charsets[idx], enabled_set_charset_done, info); + } + } else { + /* Modem is now enabled; update the state */ + mm_generic_gsm_update_enabled_state (MM_GENERIC_GSM (modem), FALSE, MM_MODEM_STATE_REASON_NONE); + + /* Set up unsolicited registration notifications */ + mm_at_serial_port_queue_command (MM_GENERIC_GSM_GET_PRIVATE (modem)->primary, + "+CREG=2", 3, creg2_done, info); + } +} + +static void +supported_charsets_done (MMModem *modem, + guint32 charsets, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (!modem) { + enable_failed (modem, error, info); + return; + } + + /* Switch the device's charset; we prefer UTF-8, but UCS2 will do too */ + mm_modem_set_charset (modem, MM_MODEM_CHARSET_UTF8, enabled_set_charset_done, info); +} + +static void +get_allowed_mode_done (MMModem *modem, + MMModemGsmAllowedMode mode, + GError *error, + gpointer user_data) +{ + if (modem) { + mm_generic_gsm_update_allowed_mode (MM_GENERIC_GSM (modem), + error ? MM_MODEM_GSM_ALLOWED_MODE_ANY : mode); + } +} + +static void +get_enable_info_done (MMModem *modem, + const char *manufacturer, + const char *model, + const char *version, + GError *error, + gpointer user_data) +{ + /* Modem base class handles the response for us */ +} + +void +mm_generic_gsm_enable_complete (MMGenericGsm *self, + GError *error, + MMCallbackInfo *info) +{ + MMGenericGsmPrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_GENERIC_GSM (self)); + g_return_if_fail (info != NULL); + + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + if (error) { + enable_failed ((MMModem *) self, error, info); + return; + } + + /* Open the second port here if the modem has one. We'll use it for + * signal strength and registration updates when the device is connected, + * but also many devices will send unsolicited registration or other + * messages to the secondary port but not the primary. + */ + if (priv->secondary) { + if (!mm_serial_port_open (MM_SERIAL_PORT (priv->secondary), &error)) { + if (mm_options_debug ()) { + g_warning ("%s: error opening secondary port: (%d) %s", + __func__, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + } + } + } + + /* Try to enable XON/XOFF flow control */ + mm_at_serial_port_queue_command (priv->primary, "+IFC=1,1", 3, NULL, NULL); + + /* Grab device info right away */ + mm_modem_get_info (MM_MODEM (self), get_enable_info_done, NULL); + + /* Get allowed mode */ + if (MM_GENERIC_GSM_GET_CLASS (self)->get_allowed_mode) + MM_GENERIC_GSM_GET_CLASS (self)->get_allowed_mode (self, get_allowed_mode_done, NULL); + + /* And supported character sets */ + mm_modem_get_supported_charsets (MM_MODEM (self), supported_charsets_done, info); +} + +static void +real_do_enable_power_up_done (MMGenericGsm *self, + GString *response, + GError *error, + MMCallbackInfo *info) +{ + /* Ignore power-up errors as not all devices actually support CFUN=1 */ + mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), NULL, info); +} + +static void +enable_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + /* Let subclasses handle the power up command response/error; many devices + * don't support +CFUN, but for those that do let them handle the error + * correctly. + */ + g_assert (MM_GENERIC_GSM_GET_CLASS (info->modem)->do_enable_power_up_done); + MM_GENERIC_GSM_GET_CLASS (info->modem)->do_enable_power_up_done (MM_GENERIC_GSM (info->modem), + response, + error, + info); +} + +static void +init_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + char *cmd = NULL; + + if (error) { + mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); + return; + } + + /* Ensure echo is off after the init command; some modems ignore the + * E0 when it's in the same line as ATZ (Option GIO322). + */ + mm_at_serial_port_queue_command (port, "E0", 2, NULL, NULL); + + /* Some phones (like Blackberries) don't support +CMEE=1, so make it + * optional. It completely violates 3GPP TS 27.007 (9.1) but what can we do... + */ + mm_at_serial_port_queue_command (port, "+CMEE=1", 2, NULL, NULL); + + g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_INIT_CMD_OPTIONAL, &cmd, NULL); + mm_at_serial_port_queue_command (port, cmd, 2, NULL, NULL); + g_free (cmd); + + g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_UP_CMD, &cmd, NULL); + if (cmd && strlen (cmd)) + mm_at_serial_port_queue_command (port, cmd, 5, enable_done, user_data); + else + enable_done (port, NULL, NULL, user_data); + g_free (cmd); +} + +static void +enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data) +{ + MMCallbackInfo *info = user_data; + char *cmd = NULL; + + if (error) { + mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info); + return; + } + + g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_INIT_CMD, &cmd, NULL); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), cmd, 3, init_done, user_data); + g_free (cmd); +} + +static void +real_do_enable (MMGenericGsm *self, MMModemFn callback, gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMCallbackInfo *info; + + info = mm_callback_info_new (MM_MODEM (self), callback, user_data); + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 100, FALSE, enable_flash_done, info); +} + +static void +enable (MMModem *modem, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + GError *error = NULL; + const char *unlock; + + /* If the device needs a PIN, deal with that now, but we don't care + * about SIM-PIN2/SIM-PUK2 since the device is operational without it. + */ + unlock = mm_modem_base_get_unlock_required (MM_MODEM_BASE (modem)); + if (unlock && strcmp (unlock, "sim-puk2") && strcmp (unlock, "sim-pin2")) { + MMCallbackInfo *info; + + info = mm_callback_info_new (modem, callback, user_data); + info->error = error_for_unlock_required (unlock); + mm_callback_info_schedule (info); + return; + } /* First, reset the previously used CID */ - mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0); + priv->cid = -1; - if (!mm_serial_port_open (priv->primary, &error)) { + if (!mm_serial_port_open (MM_SERIAL_PORT (priv->primary), &error)) { MMCallbackInfo *info; g_assert (error); @@ -532,7 +1072,7 @@ enable (MMModem *modem, } static void -disable_done (MMSerialPort *port, +disable_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -541,13 +1081,25 @@ disable_done (MMSerialPort *port, info->error = mm_modem_check_removed (info->modem, error); if (!info->error) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + MMGenericGsm *self = MM_GENERIC_GSM (info->modem); - mm_serial_port_close (port); + mm_serial_port_close_force (MM_SERIAL_PORT (port)); mm_modem_set_state (MM_MODEM (info->modem), MM_MODEM_STATE_DISABLED, MM_MODEM_STATE_REASON_NONE); - priv->reg_status = MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN; + + /* Clear out circuit-switched registration info... */ + reg_info_updated (self, + MM_GENERIC_GSM_REG_TYPE_CS, + TRUE, MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN, + TRUE, NULL, + TRUE, NULL); + /* ...and packet-switched registration info */ + reg_info_updated (self, + MM_GENERIC_GSM_REG_TYPE_PS, + TRUE, MM_MODEM_GSM_NETWORK_REG_STATUS_UNKNOWN, + TRUE, NULL, + TRUE, NULL); } mm_callback_info_schedule (info); } @@ -575,11 +1127,15 @@ disable_flash_done (MMSerialPort *port, return; } + /* Disable unsolicited messages */ + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "AT+CREG=0", 3, NULL, NULL); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "AT+CGREG=0", 3, NULL, NULL); + g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_DOWN_CMD, &cmd, NULL); if (cmd && strlen (cmd)) - mm_serial_port_queue_command (port, cmd, 5, disable_done, user_data); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), cmd, 5, disable_done, user_data); else - disable_done (port, NULL, NULL, user_data); + disable_done (MM_AT_SERIAL_PORT (port), NULL, NULL, user_data); g_free (cmd); } @@ -588,14 +1144,42 @@ disable (MMModem *modem, MMModemFn callback, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); MMCallbackInfo *info; MMModemState state; /* First, reset the previously used CID and clean up registration */ - mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0); + g_warn_if_fail (priv->cid == -1); + priv->cid = -1; + mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem)); + if (priv->poll_id) { + g_source_remove (priv->poll_id); + priv->poll_id = 0; + } + + if (priv->signal_quality_id) { + g_source_remove (priv->signal_quality_id); + priv->signal_quality_id = 0; + } + + if (priv->pin_check_timeout) { + g_source_remove (priv->pin_check_timeout); + priv->pin_check_timeout = 0; + } + + priv->lac[0] = 0; + priv->lac[1] = 0; + priv->cell_id[0] = 0; + priv->cell_id[1] = 0; + _internal_update_access_technology (self, MM_MODEM_GSM_ACCESS_TECH_UNKNOWN); + + /* Close the secondary port if its open */ + if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) + mm_serial_port_close_force (MM_SERIAL_PORT (priv->secondary)); + info = mm_callback_info_new (modem, callback, user_data); /* Cache the previous state so we can reset it if the operation fails */ @@ -610,13 +1194,13 @@ disable (MMModem *modem, MM_MODEM_STATE_REASON_NONE); if (mm_port_get_connected (MM_PORT (priv->primary))) - mm_serial_port_flash (priv->primary, 1000, disable_flash_done, info); + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disable_flash_done, info); else - disable_flash_done (priv->primary, NULL, info); + disable_flash_done (MM_SERIAL_PORT (priv->primary), NULL, info); } static void -get_string_done (MMSerialPort *port, +get_string_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -631,6 +1215,110 @@ get_string_done (MMSerialPort *port, mm_callback_info_schedule (info); } +static void +get_mnc_length_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + int sw1, sw2; + const char *imsi; + gboolean success = FALSE; + char hex[51]; + char *bin; + + if (error) { + info->error = g_error_copy (error); + goto done; + } + + memset (hex, 0, sizeof (hex)); + if (sscanf (response->str, "+CRSM:%d,%d,\"%50c\"", &sw1, &sw2, (char *) &hex) == 3) + success = TRUE; + else { + /* May not include quotes... */ + if (sscanf (response->str, "+CRSM:%d,%d,%50c", &sw1, &sw2, (char *) &hex) == 3) + success = TRUE; + } + + if (!success) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Could not parse the CRSM response"); + goto done; + } + + if ((sw1 == 0x90 && sw2 == 0x00) || (sw1 == 0x91) || (sw1 == 0x92) || (sw1 == 0x9f)) { + gsize buflen = 0; + guint32 mnc_len; + + /* Make sure the buffer is only hex characters */ + while (buflen < sizeof (hex) && hex[buflen]) { + if (!isxdigit (hex[buflen])) { + hex[buflen] = 0x0; + break; + } + buflen++; + } + + /* Convert hex string to binary */ + bin = utils_hexstr2bin (hex, &buflen); + if (!bin || buflen < 4) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "SIM returned malformed response '%s'", + hex); + goto done; + } + + /* MNC length is byte 4 of this SIM file */ + mnc_len = bin[3] & 0xFF; + if (mnc_len == 2 || mnc_len == 3) { + imsi = mm_callback_info_get_data (info, "imsi"); + mm_callback_info_set_result (info, g_strndup (imsi, 3 + mnc_len), g_free); + } else { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "SIM returned invalid MNC length %d (should be either 2 or 3)", + mnc_len); + } + } else { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "SIM failed to handle CRSM request (sw1 %d sw2 %d)", + sw1, sw2); + } + +done: + mm_callback_info_schedule (info); +} + +static void +get_operator_id_imsi_done (MMModem *modem, + const char *result, + GError *error, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) { + info->error = g_error_copy (error); + mm_callback_info_schedule (info); + return; + } + + mm_callback_info_set_data (info, "imsi", g_strdup (result), g_free); + + /* READ BINARY of EFad (Administrative Data) ETSI 51.011 section 10.3.18 */ + mm_at_serial_port_queue_command_cached (priv->primary, + "+CRSM=176,28589,0,0,4", + 3, + get_mnc_length_done, + info); +} + static void get_imei (MMModemGsmCard *modem, MMModemStringFn callback, @@ -640,7 +1328,7 @@ get_imei (MMModemGsmCard *modem, MMCallbackInfo *info; info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_queue_command_cached (priv->primary, "+CGSN", 3, get_string_done, info); + mm_at_serial_port_queue_command_cached (priv->primary, "+CGSN", 3, get_string_done, info); } static void @@ -652,112 +1340,116 @@ get_imsi (MMModemGsmCard *modem, MMCallbackInfo *info; info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_queue_command_cached (priv->primary, "+CIMI", 3, get_string_done, info); + mm_at_serial_port_queue_command_cached (priv->primary, "+CIMI", 3, get_string_done, info); } static void -card_info_invoke (MMCallbackInfo *info) +get_operator_id (MMModemGsmCard *modem, + MMModemStringFn callback, + gpointer user_data) { - MMModemInfoFn callback = (MMModemInfoFn) info->callback; - - callback (info->modem, - (char *) mm_callback_info_get_data (info, "card-info-manufacturer"), - (char *) mm_callback_info_get_data (info, "card-info-model"), - (char *) mm_callback_info_get_data (info, "card-info-version"), - info->error, info->user_data); -} - -#define GMI_RESP_TAG "+CGMI:" -#define GMM_RESP_TAG "+CGMM:" -#define GMR_RESP_TAG "+CGMR:" + MMCallbackInfo *info; -static const char * -strip_tag (const char *str, const char *tag) -{ - /* Strip the response header, if any */ - if (strncmp (str, tag, strlen (tag)) == 0) - str += strlen (tag); - while (*str && isspace (*str)) - str++; - return str; + info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); + mm_modem_gsm_card_get_imsi (MM_MODEM_GSM_CARD (modem), + get_operator_id_imsi_done, + info); } static void -get_version_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +get_card_info (MMModem *modem, + MMModemInfoFn callback, + gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *resp = strip_tag (response->str, GMR_RESP_TAG); - - if (!error) - mm_callback_info_set_data (info, "card-info-version", g_strdup (resp), g_free); - else if (!info->error) - info->error = g_error_copy (error); + MMAtSerialPort *port; + GError *error = NULL; - mm_callback_info_schedule (info); + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &error); + mm_modem_base_get_card_info (MM_MODEM_BASE (modem), port, error, callback, user_data); + g_clear_error (&error); } +#define PIN_PORT_TAG "pin-port" + static void -get_model_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +pin_puk_recheck_done (MMModem *modem, GError *error, gpointer user_data); + +static gboolean +pin_puk_recheck_again (gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *resp = strip_tag (response->str, GMM_RESP_TAG); - if (!error) - mm_callback_info_set_data (info, "card-info-model", g_strdup (resp), g_free); - else if (!info->error) - info->error = g_error_copy (error); + MM_GENERIC_GSM_GET_PRIVATE (info->modem)->pin_check_timeout = 0; + check_pin (MM_GENERIC_GSM (info->modem), pin_puk_recheck_done, info); + return FALSE; } static void -get_manufacturer_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +pin_puk_recheck_done (MMModem *modem, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *resp = strip_tag (response->str, GMI_RESP_TAG); + MMSerialPort *port; - if (!error) - mm_callback_info_set_data (info, "card-info-manufacturer", g_strdup (resp), g_free); - else - info->error = g_error_copy (error); -} + /* Clear the pin check timeout to ensure that it won't ever get a + * stale MMCallbackInfo if the modem got removed. We'll reschedule it here + * anyway if needed. + */ + if (info->modem) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); -static void -get_card_info (MMModem *modem, - MMModemInfoFn callback, - gpointer user_data) -{ - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); - MMCallbackInfo *info; + if (priv->pin_check_timeout) + g_source_remove (priv->pin_check_timeout); + priv->pin_check_timeout = 0; + } - info = mm_callback_info_new_full (MM_MODEM (modem), - card_info_invoke, - G_CALLBACK (callback), - user_data); + /* modem could have been removed before we get here, in which case + * 'modem' will be NULL. + */ + info->error = mm_modem_check_removed (modem, error); + + /* If the modem wasn't removed, and the modem isn't ready yet, ask it for + * the current PIN status a few times since some devices take a bit to fully + * enable themselves after a SIM PIN/PUK unlock. + */ + if ( info->modem + && info->error + && !g_error_matches (info->error, MM_MODEM_ERROR, MM_MODEM_ERROR_REMOVED)) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + if (priv->pin_check_tries < 4) { + g_clear_error (&info->error); + priv->pin_check_tries++; + priv->pin_check_timeout = g_timeout_add_seconds (2, pin_puk_recheck_again, info); + return; + } + } + + /* Otherwise, clean up and return the PIN check result */ + port = mm_callback_info_get_data (info, PIN_PORT_TAG); + if (modem && port) + mm_serial_port_close (port); - mm_serial_port_queue_command_cached (priv->primary, "+CGMI", 3, get_manufacturer_done, info); - mm_serial_port_queue_command_cached (priv->primary, "+CGMM", 3, get_model_done, info); - mm_serial_port_queue_command_cached (priv->primary, "+CGMR", 3, get_version_done, info); + mm_callback_info_schedule (info); } static void -send_puk_done (MMSerialPort *port, +send_puk_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - if (error) + if (error) { info->error = g_error_copy (error); - mm_callback_info_schedule (info); + mm_callback_info_schedule (info); + mm_serial_port_close (MM_SERIAL_PORT (port)); + return; + } + + /* Get latest PIN status */ + MM_GENERIC_GSM_GET_PRIVATE (info->modem)->pin_check_tries = 0; + check_pin (MM_GENERIC_GSM (info->modem), pin_puk_recheck_done, info); } static void @@ -767,27 +1459,52 @@ send_puk (MMModemGsmCard *modem, MMModemFn callback, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); MMCallbackInfo *info; char *command; + MMAtSerialPort *port; info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + + /* Ensure we have a usable port to use for the unlock */ + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + /* Modem may not be enabled yet, which sometimes can't be done until + * the device has been unlocked. In this case we have to open the port + * ourselves. + */ + if (!mm_serial_port_open (MM_SERIAL_PORT (port), &info->error)) { + mm_callback_info_schedule (info); + return; + } + mm_callback_info_set_data (info, PIN_PORT_TAG, port, NULL); + command = g_strdup_printf ("+CPIN=\"%s\",\"%s\"", puk, pin); - mm_serial_port_queue_command (priv->primary, command, 3, send_puk_done, info); + mm_at_serial_port_queue_command (port, command, 3, send_puk_done, info); g_free (command); } static void -send_pin_done (MMSerialPort *port, +send_pin_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - if (error) + if (error) { info->error = g_error_copy (error); - mm_callback_info_schedule (info); + mm_callback_info_schedule (info); + mm_serial_port_close (MM_SERIAL_PORT (port)); + return; + } + + /* Get latest PIN status */ + MM_GENERIC_GSM_GET_PRIVATE (info->modem)->pin_check_tries = 0; + check_pin (MM_GENERIC_GSM (info->modem), pin_puk_recheck_done, info); } static void @@ -796,18 +1513,36 @@ send_pin (MMModemGsmCard *modem, MMModemFn callback, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); MMCallbackInfo *info; char *command; + MMAtSerialPort *port; info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + + /* Ensure we have a usable port to use for the unlock */ + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + /* Modem may not be enabled yet, which sometimes can't be done until + * the device has been unlocked. In this case we have to open the port + * ourselves. + */ + if (!mm_serial_port_open (MM_SERIAL_PORT (port), &info->error)) { + mm_callback_info_schedule (info); + return; + } + mm_callback_info_set_data (info, PIN_PORT_TAG, port, NULL); + command = g_strdup_printf ("+CPIN=\"%s\"", pin); - mm_serial_port_queue_command (priv->primary, command, 3, send_pin_done, info); + mm_at_serial_port_queue_command (port, command, 3, send_pin_done, info); g_free (command); } static void -enable_pin_done (MMSerialPort *port, +enable_pin_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -832,12 +1567,12 @@ enable_pin (MMModemGsmCard *modem, info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); command = g_strdup_printf ("+CLCK=\"SC\",%d,\"%s\"", enabled ? 1 : 0, pin); - mm_serial_port_queue_command (priv->primary, command, 3, enable_pin_done, info); + mm_at_serial_port_queue_command (priv->primary, command, 3, enable_pin_done, info); g_free (command); } static void -change_pin_done (MMSerialPort *port, +change_pin_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -862,12 +1597,104 @@ change_pin (MMModemGsmCard *modem, info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); command = g_strdup_printf ("+CPWD=\"SC\",\"%s\",\"%s\"", old_pin, new_pin); - mm_serial_port_queue_command (priv->primary, command, 3, change_pin_done, info); + mm_at_serial_port_queue_command (priv->primary, command, 3, change_pin_done, info); g_free (command); } +static void +get_unlock_retries (MMModemGsmCard *modem, + const char *pin_type, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); + + mm_callback_info_set_result (info, + GUINT_TO_POINTER (MM_MODEM_GSM_CARD_UNLOCK_RETRIES_NOT_SUPPORTED), + NULL); + + mm_callback_info_schedule (info); +} + +static void +reg_info_updated (MMGenericGsm *self, + gboolean update_rs, + MMGenericGsmRegType rs_type, + MMModemGsmNetworkRegStatus status, + gboolean update_code, + const char *oper_code, + gboolean update_name, + const char *oper_name) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMModemGsmNetworkRegStatus old_status; + gboolean changed = FALSE; + + if (update_rs) { + g_return_if_fail ( rs_type == MM_GENERIC_GSM_REG_TYPE_CS + || rs_type == MM_GENERIC_GSM_REG_TYPE_PS); + + old_status = gsm_reg_status (self); + priv->reg_status[rs_type - 1] = status; + if (gsm_reg_status (self) != old_status) + changed = TRUE; + } + + if (update_code) { + if (g_strcmp0 (oper_code, priv->oper_code) != 0) { + g_free (priv->oper_code); + priv->oper_code = g_strdup (oper_code); + changed = TRUE; + } + } + + if (update_name) { + if (g_strcmp0 (oper_name, priv->oper_name) != 0) { + g_free (priv->oper_name); + priv->oper_name = g_strdup (oper_name); + changed = TRUE; + } + } + + if (changed) { + mm_modem_gsm_network_registration_info (MM_MODEM_GSM_NETWORK (self), + gsm_reg_status (self), + priv->oper_code, + priv->oper_name); + } +} + +static void +convert_operator_from_ucs2 (char **operator) +{ + const char *p; + char *converted; + size_t len; + + g_return_if_fail (operator != NULL); + g_return_if_fail (*operator != NULL); + + p = *operator; + len = strlen (p); + + /* Len needs to be a multiple of 4 for UCS2 */ + if ((len < 4) || ((len % 4) != 0)) + return; + + while (*p) { + if (!isxdigit (*p++)) + return; + } + + converted = mm_modem_charset_hex_to_utf8 (*operator, MM_MODEM_CHARSET_UCS2); + if (converted) { + g_free (*operator); + *operator = converted; + } +} + static char * -parse_operator (const char *reply) +parse_operator (const char *reply, MMModemCharset cur_charset) { char *operator = NULL; @@ -889,52 +1716,53 @@ parse_operator (const char *reply) g_regex_unref (r); } + /* Some modems (Option & HSO) return the operator name as a hexadecimal + * string of the bytes of the operator name as encoded by the current + * character set. + */ + if (operator && (cur_charset == MM_MODEM_CHARSET_UCS2)) + convert_operator_from_ucs2 (&operator); + return operator; } static void -read_operator_code_done (MMSerialPort *port, +read_operator_code_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (user_data); + MMGenericGsm *self = MM_GENERIC_GSM (user_data); char *oper; - if (error) - return; - - oper = parse_operator (response->str); - if (!oper) - return; - - g_free (priv->oper_code); - priv->oper_code = oper; + if (!error) { + oper = parse_operator (response->str, MM_MODEM_CHARSET_UNKNOWN); + if (oper) { + reg_info_updated (self, FALSE, MM_GENERIC_GSM_REG_TYPE_UNKNOWN, 0, + TRUE, oper, + FALSE, NULL); + } + } } static void -read_operator_name_done (MMSerialPort *port, +read_operator_name_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (user_data); + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); char *oper; - if (error) - return; - - oper = parse_operator (response->str); - if (!oper) - return; - - g_free (priv->oper_name); - priv->oper_name = oper; - - mm_modem_gsm_network_registration_info (MM_MODEM_GSM_NETWORK (user_data), - priv->reg_status, - priv->oper_code, - priv->oper_name); + if (!error) { + oper = parse_operator (response->str, priv->cur_charset); + if (oper) { + reg_info_updated (self, FALSE, MM_GENERIC_GSM_REG_TYPE_UNKNOWN, 0, + FALSE, NULL, + TRUE, oper); + } + } } /* Registration */ @@ -961,8 +1789,93 @@ mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem) } } +static void +got_signal_quality (MMModem *modem, + guint32 quality, + GError *error, + gpointer user_data) +{ + mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (modem), quality); +} + +static void +roam_disconnect_done (MMModem *modem, + GError *error, + gpointer user_data) +{ + g_message ("Disconnected because roaming is not allowed"); +} + +static void +get_reg_act_done (MMModem *modem, + guint32 act, + GError *error, + gpointer user_data) +{ + if (modem && !error && act) + mm_generic_gsm_update_access_technology (MM_GENERIC_GSM (modem), act); +} + +void +mm_generic_gsm_set_reg_status (MMGenericGsm *self, + MMGenericGsmRegType rs_type, + MMModemGsmNetworkRegStatus status) +{ + MMGenericGsmPrivate *priv; + MMAtSerialPort *port; + + g_return_if_fail (MM_IS_GENERIC_GSM (self)); + + g_return_if_fail ( rs_type == MM_GENERIC_GSM_REG_TYPE_CS + || rs_type == MM_GENERIC_GSM_REG_TYPE_PS); + + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + if (priv->reg_status[rs_type - 1] == status) + return; + + g_debug ("%s registration state changed: %d", + (rs_type == MM_GENERIC_GSM_REG_TYPE_CS) ? "CS" : "PS", + status); + priv->reg_status[rs_type - 1] = status; + + port = mm_generic_gsm_get_best_at_port (self, NULL); + + if (status == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME || + status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) { + + /* If we're connected and we're not supposed to roam, but the device + * just roamed, disconnect the connection to avoid charging the user + * loads of money. + */ + if ( (status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING) + && (mm_modem_get_state (MM_MODEM (self)) == MM_MODEM_STATE_CONNECTED) + && (priv->roam_allowed == FALSE)) { + mm_modem_disconnect (MM_MODEM (self), roam_disconnect_done, NULL); + } else { + /* Grab the new operator name and MCC/MNC */ + if (port) { + mm_at_serial_port_queue_command (port, "+COPS=3,2;+COPS?", 3, read_operator_code_done, self); + mm_at_serial_port_queue_command (port, "+COPS=3,0;+COPS?", 3, read_operator_name_done, self); + } + + /* And update signal quality and access technology */ + mm_modem_gsm_network_get_signal_quality (MM_MODEM_GSM_NETWORK (self), got_signal_quality, NULL); + if (MM_GENERIC_GSM_GET_CLASS (self)->get_access_technology) + MM_GENERIC_GSM_GET_CLASS (self)->get_access_technology (self, get_reg_act_done, NULL); + } + } else + reg_info_updated (self, FALSE, rs_type, 0, TRUE, NULL, TRUE, NULL); + + mm_generic_gsm_update_enabled_state (self, TRUE, MM_MODEM_STATE_REASON_NONE); +} + +/* Returns TRUE if the modem is "done", ie has registered or been denied */ static gboolean -reg_status_updated (MMGenericGsm *self, int new_value, GError **error) +reg_status_updated (MMGenericGsm *self, + MMGenericGsmRegType rs_type, + int new_value, + GError **error) { MMModemGsmNetworkRegStatus status; gboolean status_done = FALSE; @@ -991,7 +1904,7 @@ reg_status_updated (MMGenericGsm *self, int new_value, GError **error) break; } - mm_generic_gsm_set_reg_status (self, status); + mm_generic_gsm_set_reg_status (self, rs_type, status); /* Registration has either completed successfully or completely failed */ switch (status) { @@ -1022,21 +1935,35 @@ reg_status_updated (MMGenericGsm *self, int new_value, GError **error) return status_done; } +static MMGenericGsmRegType +cgreg_to_reg_type (gboolean cgreg) +{ + return (cgreg ? MM_GENERIC_GSM_REG_TYPE_PS : MM_GENERIC_GSM_REG_TYPE_CS); +} + static void -reg_state_changed (MMSerialPort *port, +reg_state_changed (MMAtSerialPort *port, GMatchInfo *match_info, gpointer user_data) { MMGenericGsm *self = MM_GENERIC_GSM (user_data); MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); - char *str; - gboolean done; + guint32 state = 0, idx; + gulong lac = 0, cell_id = 0; + gint act = -1; + gboolean cgreg = FALSE; + GError *error = NULL; - str = g_match_info_fetch (match_info, 1); - done = reg_status_updated (self, atoi (str), NULL); - g_free (str); + if (!mm_gsm_parse_creg_response (match_info, &state, &lac, &cell_id, &act, &cgreg, &error)) { + if (mm_options_debug ()) { + g_warning ("%s: error parsing unsolicited registration: %s", + __func__, + error && error->message ? error->message : "(unknown)"); + } + return; + } - if (done) { + if (reg_status_updated (self, cgreg_to_reg_type (cgreg), state, NULL)) { /* If registration is finished (either registered or failed) but the * registration query hasn't completed yet, just remove the timeout and * let the registration query complete. @@ -1046,6 +1973,14 @@ reg_state_changed (MMSerialPort *port, priv->pending_reg_id = 0; } } + + idx = cgreg ? 1 : 0; + priv->lac[idx] = lac; + priv->cell_id[idx] = cell_id; + + /* Only update access technology if it appeared in the CREG/CGREG response */ + if (act != -1) + mm_generic_gsm_update_access_technology (self, etsi_act_to_mm_act (act)); } static gboolean @@ -1072,8 +2007,62 @@ reg_status_again_remove (gpointer data) g_source_remove (id); } +static gboolean +handle_reg_status_response (MMGenericGsm *self, + GString *response, + GError **error) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + GMatchInfo *match_info; + guint32 status = 0, idx; + gulong lac = 0, ci = 0; + gint act = -1; + gboolean cgreg = FALSE; + guint i; + + /* Try to match the response */ + for (i = 0; i < priv->reg_regex->len; i++) { + GRegex *r = g_ptr_array_index (priv->reg_regex, i); + + if (g_regex_match (r, response->str, 0, &match_info)) + break; + g_match_info_free (match_info); + match_info = NULL; + } + + if (!match_info) { + g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Unknown registration status response"); + return FALSE; + } + + /* And parse it */ + if (!mm_gsm_parse_creg_response (match_info, &status, &lac, &ci, &act, &cgreg, error)) { + g_match_info_free (match_info); + return FALSE; + } + + /* Success; update cached location information */ + idx = cgreg ? 1 : 0; + priv->lac[idx] = lac; + priv->cell_id[idx] = ci; + + /* Only update access technology if it appeared in the CREG/CGREG response */ + if (act != -1) + mm_generic_gsm_update_access_technology (self, etsi_act_to_mm_act (act)); + + if (status >= 0) { + /* Update cached registration status */ + reg_status_updated (self, cgreg_to_reg_type (cgreg), status, NULL); + } + + return TRUE; +} + +#define CGREG_TRIED_TAG "cgreg-tried" + static void -get_reg_status_done (MMSerialPort *port, +get_reg_status_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1081,76 +2070,71 @@ get_reg_status_done (MMSerialPort *port, MMCallbackInfo *info = (MMCallbackInfo *) user_data; MMGenericGsm *self = MM_GENERIC_GSM (info->modem); MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); - int reg_status = -1; - GRegex *r; - GMatchInfo *match_info; - char *tmp; guint id; + MMModemGsmNetworkRegStatus status; - g_warn_if_fail (info == priv->pending_reg_info); + /* This function should only get called during the connect sequence when + * polling for registration state, since explicit registration requests + * from D-Bus clients are filled from the cached registration state. + */ + g_return_if_fail (info == priv->pending_reg_info); if (error) { - info->error = g_error_copy (error); - goto reg_done; - } - - r = g_regex_new ("\\+CREG:\\s*(\\d+),\\s*(\\d+)", - G_REGEX_RAW | G_REGEX_OPTIMIZE, - 0, &info->error); - if (r) { - g_regex_match_full (r, response->str, response->len, 0, 0, &match_info, &info->error); - if (g_match_info_matches (match_info)) { - /* Get reg status */ - tmp = g_match_info_fetch (match_info, 2); - if (isdigit (tmp[0])) { - tmp[1] = '\0'; - reg_status = atoi (tmp); - } else { - info->error = g_error_new (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "Unknown registration status '%s'", - tmp); - } - g_free (tmp); + gboolean cgreg_tried = !!mm_callback_info_get_data (info, CGREG_TRIED_TAG); + + /* If this was a +CREG error, try +CGREG. Some devices (blackberries) + * respond to +CREG with an error but return a valid +CGREG response. + * So try both. If we get an error from both +CREG and +CGREG, that's + * obviously a hard fail. + */ + if (cgreg_tried == FALSE) { + mm_callback_info_set_data (info, CGREG_TRIED_TAG, GUINT_TO_POINTER (TRUE), NULL); + mm_at_serial_port_queue_command (port, "+CGREG?", 10, get_reg_status_done, info); + return; } else { - info->error = g_error_new_literal (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "Could not parse the registration status response"); + info->error = g_error_copy (error); + goto reg_done; } - g_match_info_free (match_info); - g_regex_unref (r); } - if ( reg_status >= 0 - && !reg_status_updated (self, reg_status, &info->error) - && priv->pending_reg_info) { - g_clear_error (&info->error); + /* The unsolicited registration state handlers will intercept the CREG + * response and update the cached registration state for us, so we usually + * just need to check the cached state here. + */ + + if (strlen (response->str)) { + /* But just in case the unsolicited handlers doesn't do it... */ + if (!handle_reg_status_response (self, response, &info->error)) + goto reg_done; + } - /* Not registered yet; poll registration status again */ + status = gsm_reg_status (self); + if ( status != MM_MODEM_GSM_NETWORK_REG_STATUS_HOME + && status != MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING + && status != MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED) { + /* If we're still waiting for automatic registration to complete or + * fail, check again in a few seconds. + */ id = g_timeout_add_seconds (1, reg_status_again, info); mm_callback_info_set_data (info, REG_STATUS_AGAIN_TAG, - GUINT_TO_POINTER (id), - reg_status_again_remove); + GUINT_TO_POINTER (id), + reg_status_again_remove); return; } reg_done: - /* This will schedule the callback for us */ + /* This will schedule the pending registration's the callback for us */ mm_generic_gsm_pending_registration_stop (self); } static void -get_registration_status (MMSerialPort *port, MMCallbackInfo *info) +get_registration_status (MMAtSerialPort *port, MMCallbackInfo *info) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); - - g_warn_if_fail (info == priv->pending_reg_info); - - mm_serial_port_queue_command (port, "+CREG?", 10, get_reg_status_done, info); + mm_at_serial_port_queue_command (port, "+CREG?", 10, get_reg_status_done, info); } static void -register_done (MMSerialPort *port, +register_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1169,7 +2153,9 @@ register_done (MMSerialPort *port, if (priv->pending_reg_info) { g_warn_if_fail (info == priv->pending_reg_info); - /* Ignore errors here, get the actual registration status */ + /* Don't use cached registration state here since it could be up to + * 30 seconds old. Get fresh registration state. + */ get_registration_status (port, info); } } @@ -1182,7 +2168,16 @@ registration_timed_out (gpointer data) g_warn_if_fail (info == priv->pending_reg_info); - priv->reg_status = MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE; + /* Clear out circuit-switched registration info... */ + reg_info_updated (MM_GENERIC_GSM (info->modem), + TRUE, MM_GENERIC_GSM_REG_TYPE_CS, MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE, + TRUE, NULL, + TRUE, NULL); + /* ... and packet-switched registration info */ + reg_info_updated (MM_GENERIC_GSM (info->modem), + TRUE, MM_GENERIC_GSM_REG_TYPE_PS, MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE, + TRUE, NULL, + TRUE, NULL); info->error = mm_mobile_error_for_code (MM_MOBILE_ERROR_NETWORK_TIMEOUT); mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (info->modem)); @@ -1190,28 +2185,47 @@ registration_timed_out (gpointer data) return FALSE; } +static gboolean +reg_is_idle (MMModemGsmNetworkRegStatus status) +{ + if ( status == MM_MODEM_GSM_NETWORK_REG_STATUS_HOME + || status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING + || status == MM_MODEM_GSM_NETWORK_REG_STATUS_SEARCHING) + return FALSE; + return TRUE; +} + static void do_register (MMModemGsmNetwork *modem, const char *network_id, MMModemFn callback, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); MMCallbackInfo *info; - char *command; + char *command = NULL; /* Clear any previous registration */ - mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem)); + mm_generic_gsm_pending_registration_stop (self); info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); priv->pending_reg_id = g_timeout_add_seconds (60, registration_timed_out, info); priv->pending_reg_info = info; - if (network_id) + /* If the user sent a specific network to use, lock it in. If no specific + * network was given, and the modem is not registered and not searching, + * kick it to search for a network. Also do auto registration if the modem + * had been set to manual registration last time but now is not. + */ + if (network_id) { command = g_strdup_printf ("+COPS=1,2,\"%s\"", network_id); - else + priv->manual_reg = TRUE; + } else if (reg_is_idle (gsm_reg_status (self)) || priv->manual_reg) { command = g_strdup ("+COPS=0,,"); + priv->manual_reg = FALSE; + } /* Ref the callback info to ensure it stays alive for register_done() even * if the timeout triggers and ends registration (which calls the callback @@ -1231,8 +2245,12 @@ do_register (MMModemGsmNetwork *modem, * the +COPS response is never received. */ mm_callback_info_ref (info); - mm_serial_port_queue_command (priv->primary, command, 120, register_done, info); - g_free (command); + + if (command) { + mm_at_serial_port_queue_command (priv->primary, command, 120, register_done, info); + g_free (command); + } else + register_done (priv->primary, NULL, NULL, info); } static void @@ -1242,7 +2260,7 @@ gsm_network_reg_info_invoke (MMCallbackInfo *info) MMModemGsmNetworkRegInfoFn callback = (MMModemGsmNetworkRegInfoFn) info->callback; callback (MM_MODEM_GSM_NETWORK (info->modem), - priv->reg_status, + gsm_reg_status (MM_GENERIC_GSM (info->modem)), priv->oper_code, priv->oper_name, info->error, @@ -1260,7 +2278,9 @@ get_registration_info (MMModemGsmNetwork *self, gsm_network_reg_info_invoke, G_CALLBACK (callback), user_data); - + /* Registration info updates are handled internally either by unsolicited + * updates or by polling. Thus just return the cached registration state. + */ mm_callback_info_schedule (info); } @@ -1292,7 +2312,7 @@ mm_generic_gsm_connect_complete (MMGenericGsm *modem, } static void -connect_report_done (MMSerialPort *port, +connect_report_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1323,7 +2343,7 @@ connect_report_done (MMSerialPort *port, } static void -connect_done (MMSerialPort *port, +connect_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1335,7 +2355,7 @@ connect_done (MMSerialPort *port, info->error = g_error_copy (error); /* Try to get more information why it failed */ priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); - mm_serial_port_queue_command (priv->primary, "+CEER", 3, connect_report_done, info); + mm_at_serial_port_queue_command (priv->primary, "+CEER", 3, connect_report_done, info); } else mm_generic_gsm_connect_complete (MM_GENERIC_GSM (info->modem), NULL, info); } @@ -1358,47 +2378,170 @@ connect (MMModem *modem, if (cid > 0) { GString *str; - str = g_string_new ("D"); - if (g_str_has_suffix (number, "#")) - str = g_string_append_len (str, number, strlen (number) - 1); - else - str = g_string_append (str, number); + str = g_string_new ("D"); + if (g_str_has_suffix (number, "#")) + str = g_string_append_len (str, number, strlen (number) - 1); + else + str = g_string_append (str, number); + + g_string_append_printf (str, "***%d#", cid); + command = g_string_free (str, FALSE); + } else + command = g_strconcat ("DT", number, NULL); + + mm_at_serial_port_queue_command (priv->primary, command, 60, connect_done, info); + g_free (command); +} + +static void +disconnect_done (MMModem *modem, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMModemState prev_state; + + info->error = mm_modem_check_removed (modem, error); + if (info->error) { + if (info->modem && modem) { + /* Reset old state since the operation failed */ + prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_GSM_PREV_STATE_TAG)); + mm_modem_set_state (MM_MODEM (info->modem), + prev_state, + MM_MODEM_STATE_REASON_NONE); + } + } else { + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + mm_port_set_connected (priv->data, FALSE); + priv->cid = -1; + mm_generic_gsm_update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE); + } + + mm_callback_info_schedule (info); +} + +static void +disconnect_all_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + mm_callback_info_schedule ((MMCallbackInfo *) user_data); +} + +static void +disconnect_send_cgact (MMAtSerialPort *port, + gint cid, + MMAtSerialResponseFn callback, + gpointer user_data) +{ + char *command; + + if (cid >= 0) + command = g_strdup_printf ("+CGACT=0,%d", cid); + else { + /* Disable all PDP contexts */ + command = g_strdup_printf ("+CGACT=0"); + } + + mm_at_serial_port_queue_command (port, command, 3, callback, user_data); + g_free (command); +} + +#define DISCONNECT_CGACT_DONE_TAG "disconnect-cgact-done" + +static void +disconnect_flash_done (MMSerialPort *port, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + /* Ignore "NO CARRIER" response when modem disconnects and any flash + * failures we might encounter. Other errors are hard errors. + */ + if ( !g_error_matches (info->error, MM_MODEM_CONNECT_ERROR, MM_MODEM_CONNECT_ERROR_NO_CARRIER) + && !g_error_matches (info->error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_FLASH_FAILED)) { + mm_callback_info_schedule (info); + return; + } + g_clear_error (&info->error); + } + + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + mm_port_set_connected (priv->data, FALSE); + + /* Don't bother doing the CGACT again if it was done on a secondary port */ + if (mm_callback_info_get_data (info, DISCONNECT_CGACT_DONE_TAG)) + disconnect_all_done (MM_AT_SERIAL_PORT (priv->primary), NULL, NULL, info); + else { + disconnect_send_cgact (MM_AT_SERIAL_PORT (priv->primary), + priv->cid, + disconnect_all_done, + info); + } +} + +static void +disconnect_secondary_cgact_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = user_data; + MMGenericGsm *self; + MMGenericGsmPrivate *priv; + + if (!info->modem) { + info->error = mm_modem_check_removed (info->modem, error); + mm_callback_info_schedule (info); + return; + } - g_string_append_printf (str, "***%d#", cid); - command = g_string_free (str, FALSE); - } else - command = g_strconcat ("DT", number, NULL); + self = MM_GENERIC_GSM (info->modem); + priv = MM_GENERIC_GSM_GET_PRIVATE (self); - mm_serial_port_queue_command (priv->primary, command, 60, connect_done, info); - g_free (command); + /* Now that we've tried deactivating the PDP context on the secondary + * port, continue with flashing the primary port. + */ + if (!error) + mm_callback_info_set_data (info, DISCONNECT_CGACT_DONE_TAG, GUINT_TO_POINTER (TRUE), NULL); + + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disconnect_flash_done, info); } static void -disconnect_flash_done (MMSerialPort *port, - GError *error, - gpointer user_data) +real_do_disconnect (MMGenericGsm *self, + gint cid, + MMModemFn callback, + gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - MMModemState prev_state; + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMCallbackInfo *info; - info->error = mm_modem_check_removed (info->modem, error); - if (info->error) { - if (info->modem) { - /* Reset old state since the operation failed */ - prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_GSM_PREV_STATE_TAG)); - mm_modem_set_state (MM_MODEM (info->modem), - prev_state, - MM_MODEM_STATE_REASON_NONE); - } - } else { - MMGenericGsm *self = MM_GENERIC_GSM (info->modem); - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + info = mm_callback_info_new (MM_MODEM (self), callback, user_data); - mm_port_set_connected (priv->data, FALSE); - mm_generic_gsm_update_enabled_state (self, FALSE, MM_MODEM_STATE_REASON_NONE); + /* If the primary port is connected (with PPP) then try sending the PDP + * context deactivation on the secondary port because not all modems will + * respond to flashing (since either the modem or the kernel's serial + * driver doesn't support it). + */ + if ( mm_port_get_connected (MM_PORT (priv->primary)) + && priv->secondary + && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) { + disconnect_send_cgact (MM_AT_SERIAL_PORT (priv->secondary), + priv->cid, + disconnect_secondary_cgact_done, + info); + } else { + /* Just flash the primary port */ + mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disconnect_flash_done, info); } - - mm_callback_info_schedule (info); } static void @@ -1406,12 +2549,12 @@ disconnect (MMModem *modem, MMModemFn callback, gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); MMCallbackInfo *info; MMModemState state; - /* First, reset the previously used CID */ - mm_generic_gsm_set_cid (MM_GENERIC_GSM (modem), 0); + priv->roam_allowed = TRUE; info = mm_callback_info_new (modem, callback, user_data); @@ -1423,7 +2566,9 @@ disconnect (MMModem *modem, NULL); mm_modem_set_state (modem, MM_MODEM_STATE_DISCONNECTING, MM_MODEM_STATE_REASON_NONE); - mm_serial_port_flash (priv->primary, 1000, disconnect_flash_done, info); + + g_assert (MM_GENERIC_GSM_GET_CLASS (self)->do_disconnect); + MM_GENERIC_GSM_GET_CLASS (self)->do_disconnect (self, priv->cid, disconnect_done, info); } static void @@ -1438,7 +2583,7 @@ gsm_network_scan_invoke (MMCallbackInfo *info) } static void -scan_done (MMSerialPort *port, +scan_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1470,30 +2615,33 @@ scan (MMModemGsmNetwork *modem, G_CALLBACK (callback), user_data); - mm_serial_port_queue_command (priv->primary, "+COPS=?", 120, scan_done, info); + mm_at_serial_port_queue_command (priv->primary, "+COPS=?", 120, scan_done, info); } /* SetApn */ +#define APN_CID_TAG "generic-gsm-cid" + static void -set_apn_done (MMSerialPort *port, +set_apn_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - if (error) - info->error = g_error_copy (error); - else - mm_generic_gsm_set_cid (MM_GENERIC_GSM (info->modem), - GPOINTER_TO_UINT (mm_callback_info_get_data (info, "cid"))); + info->error = mm_modem_check_removed (info->modem, error); + if (!info->error) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + priv->cid = GPOINTER_TO_INT (mm_callback_info_get_data (info, APN_CID_TAG)); + } mm_callback_info_schedule (info); } static void -cid_range_read (MMSerialPort *port, +cid_range_read (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1503,7 +2651,7 @@ cid_range_read (MMSerialPort *port, if (error) info->error = g_error_copy (error); - else if (g_str_has_prefix (response->str, "+CGDCONT: ")) { + else if (g_str_has_prefix (response->str, "+CGDCONT:")) { GRegex *r; GMatchInfo *match_info; @@ -1550,16 +2698,16 @@ cid_range_read (MMSerialPort *port, const char *apn = (const char *) mm_callback_info_get_data (info, "apn"); char *command; - mm_callback_info_set_data (info, "cid", GUINT_TO_POINTER (cid), NULL); + mm_callback_info_set_data (info, APN_CID_TAG, GINT_TO_POINTER (cid), NULL); command = g_strdup_printf ("+CGDCONT=%d,\"IP\",\"%s\"", cid, apn); - mm_serial_port_queue_command (port, command, 3, set_apn_done, info); + mm_at_serial_port_queue_command (port, command, 3, set_apn_done, info); g_free (command); } } static void -existing_apns_read (MMSerialPort *port, +existing_apns_read (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1567,9 +2715,10 @@ existing_apns_read (MMSerialPort *port, MMCallbackInfo *info = (MMCallbackInfo *) user_data; gboolean found = FALSE; - if (error) - info->error = g_error_copy (error); - else if (g_str_has_prefix (response->str, "+CGDCONT: ")) { + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) + goto done; + else if (g_str_has_prefix (response->str, "+CGDCONT:")) { GRegex *r; GMatchInfo *match_info; @@ -1592,7 +2741,7 @@ existing_apns_read (MMSerialPort *port, apn = g_match_info_fetch (match_info, 3); if (!strcmp (apn, new_apn)) { - mm_generic_gsm_set_cid (MM_GENERIC_GSM (info->modem), (guint32) num_cid); + MM_GENERIC_GSM_GET_PRIVATE (info->modem)->cid = num_cid; found = TRUE; } @@ -1620,97 +2769,506 @@ existing_apns_read (MMSerialPort *port, MM_MODEM_ERROR_GENERAL, "Could not parse the response"); - if (found || info->error) +done: + if (found || info->error) + mm_callback_info_schedule (info); + else { + /* APN not configured on the card. Get the allowed CID range */ + mm_at_serial_port_queue_command_cached (port, "+CGDCONT=?", 3, cid_range_read, info); + } +} + +static void +set_apn (MMModemGsmNetwork *modem, + const char *apn, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + + info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + mm_callback_info_set_data (info, "apn", g_strdup (apn), g_free); + + /* Start by searching if the APN is already in card */ + mm_at_serial_port_queue_command (priv->primary, "+CGDCONT?", 3, existing_apns_read, info); +} + +/* GetSignalQuality */ + +static gboolean +emit_signal_quality_change (gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + priv->signal_quality_id = 0; + priv->signal_quality_timestamp = time (NULL); + mm_modem_gsm_network_signal_quality (MM_MODEM_GSM_NETWORK (self), priv->signal_quality); + return FALSE; +} + +void +mm_generic_gsm_update_signal_quality (MMGenericGsm *self, guint32 quality) +{ + MMGenericGsmPrivate *priv; + guint delay = 0; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_GENERIC_GSM (self)); + g_return_if_fail (quality <= 100); + + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + if (priv->signal_quality == quality) + return; + + priv->signal_quality = quality; + + /* Some modems will send unsolcited signal quality changes quite often, + * so rate-limit them to every few seconds. Track the last time we + * emitted signal quality so that we send the signal immediately if there + * haven't been any updates in a while. + */ + if (!priv->signal_quality_id) { + if (priv->signal_quality_timestamp > 0) { + time_t curtime; + long int diff; + + curtime = time (NULL); + diff = curtime - priv->signal_quality_timestamp; + if (diff == 0) { + /* If the device is sending more than one update per second, + * make sure we don't spam clients with signals. + */ + delay = 3; + } else if ((diff > 0) && (diff <= 3)) { + /* Emitted an update less than 3 seconds ago; schedule an update + * 3 seconds after the previous one. + */ + delay = (guint) diff; + } else { + /* Otherwise, we haven't emitted an update in the last 3 seconds, + * or the user turned their clock back, or something like that. + */ + delay = 0; + } + } + + priv->signal_quality_id = g_timeout_add_seconds (delay, + emit_signal_quality_change, + self); + } +} + +static void +get_signal_quality_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + char *reply = response->str; + gboolean parsed = FALSE; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) + goto done; + + if (!strncmp (reply, "+CSQ: ", 6)) { + /* Got valid reply */ + int quality; + int ber; + + if (sscanf (reply + 6, "%d, %d", &quality, &ber)) { + /* 99 means unknown */ + if (quality == 99) { + info->error = g_error_new_literal (MM_MOBILE_ERROR, + MM_MOBILE_ERROR_NO_NETWORK, + "No service"); + } else { + /* Normalize the quality */ + quality = CLAMP (quality, 0, 31) * 100 / 31; + + mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (info->modem), quality); + mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL); + } + parsed = TRUE; + } + } + + if (!parsed && !info->error) { + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Could not parse signal quality results"); + } + +done: + mm_callback_info_schedule (info); +} + +static void +get_signal_quality (MMModemGsmNetwork *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMCallbackInfo *info; + MMAtSerialPort *port; + + info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), NULL); + if (port) + mm_at_serial_port_queue_command (port, "+CSQ", 3, get_signal_quality_done, info); + else { + /* Use cached signal quality */ + mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->signal_quality), NULL); + mm_callback_info_schedule (info); + } +} + +/*****************************************************************************/ + +typedef struct { + MMModemGsmAccessTech mm_act; + gint etsi_act; +} ModeEtsi; + +static ModeEtsi modes_table[] = { + { MM_MODEM_GSM_ACCESS_TECH_GSM, 0 }, + { MM_MODEM_GSM_ACCESS_TECH_GSM_COMPACT, 1 }, + { MM_MODEM_GSM_ACCESS_TECH_UMTS, 2 }, + { MM_MODEM_GSM_ACCESS_TECH_EDGE, 3 }, + { MM_MODEM_GSM_ACCESS_TECH_HSDPA, 4 }, + { MM_MODEM_GSM_ACCESS_TECH_HSUPA, 5 }, + { MM_MODEM_GSM_ACCESS_TECH_HSPA, 6 }, + { MM_MODEM_GSM_ACCESS_TECH_HSPA, 7 }, /* E-UTRAN/LTE => HSPA for now */ + { MM_MODEM_GSM_ACCESS_TECH_UNKNOWN, -1 }, +}; + +static MMModemGsmAccessTech +etsi_act_to_mm_act (gint act) +{ + ModeEtsi *iter = &modes_table[0]; + + while (iter->mm_act != MM_MODEM_GSM_ACCESS_TECH_UNKNOWN) { + if (iter->etsi_act == act) + return iter->mm_act; + iter++; + } + return MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; +} + +static void +_internal_update_access_technology (MMGenericGsm *modem, + MMModemGsmAccessTech act) +{ + MMGenericGsmPrivate *priv; + + g_return_if_fail (modem != NULL); + g_return_if_fail (MM_IS_GENERIC_GSM (modem)); + g_return_if_fail (act >= MM_MODEM_GSM_ACCESS_TECH_UNKNOWN && act <= MM_MODEM_GSM_ACCESS_TECH_LAST); + + priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + + if (act != priv->act) { + MMModemDeprecatedMode old_mode; + + priv->act = act; + g_object_notify (G_OBJECT (modem), MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY); + + /* Deprecated value */ + old_mode = mm_modem_gsm_network_act_to_old_mode (act); + g_signal_emit_by_name (G_OBJECT (modem), "network-mode", old_mode); + } +} + +void +mm_generic_gsm_update_access_technology (MMGenericGsm *self, + MMModemGsmAccessTech act) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_GENERIC_GSM (self)); + + /* For plugins, don't update the access tech when the modem isn't enabled */ + if (mm_modem_get_state (MM_MODEM (self)) >= MM_MODEM_STATE_ENABLED) + _internal_update_access_technology (self, act); +} + +void +mm_generic_gsm_update_allowed_mode (MMGenericGsm *self, + MMModemGsmAllowedMode mode) +{ + MMGenericGsmPrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_GENERIC_GSM (self)); + + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + if (mode != priv->allowed_mode) { + priv->allowed_mode = mode; + g_object_notify (G_OBJECT (self), MM_MODEM_GSM_NETWORK_ALLOWED_MODE); + } +} + +static void +set_allowed_mode_done (MMModem *modem, GError *error, gpointer user_data) +{ + MMCallbackInfo *info = user_data; + + info->error = mm_modem_check_removed (info->modem, error); + if (!info->error) { + MMModemGsmAllowedMode mode = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "mode")); + + mm_generic_gsm_update_allowed_mode (MM_GENERIC_GSM (info->modem), mode); + } + + mm_callback_info_schedule (info); +} + +static void +set_allowed_mode (MMModemGsmNetwork *net, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (net); + MMCallbackInfo *info; + + info = mm_callback_info_new (MM_MODEM (self), callback, user_data); + + switch (mode) { + case MM_MODEM_GSM_ALLOWED_MODE_ANY: + case MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED: + case MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED: + case MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY: + case MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY: + if (!MM_GENERIC_GSM_GET_CLASS (self)->set_allowed_mode) { + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + } else { + mm_callback_info_set_data (info, "mode", GUINT_TO_POINTER (mode), NULL); + MM_GENERIC_GSM_GET_CLASS (self)->set_allowed_mode (self, mode, set_allowed_mode_done, info); + } + break; + default: + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Invalid mode."); + break; + } + + if (info->error) + mm_callback_info_schedule (info); +} + +/*****************************************************************************/ +/* Charset stuff */ + +static void +get_charsets_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv; + + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + mm_callback_info_schedule (info); + return; + } + + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + priv->charsets = MM_MODEM_CHARSET_UNKNOWN; + if (!mm_gsm_parse_cscs_support_response (response->str, &priv->charsets)) { + info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Failed to parse the supported character sets response"); + } else + mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->charsets), NULL); + + mm_callback_info_schedule (info); +} + +static void +get_supported_charsets (MMModem *modem, + MMModemUIntFn callback, + gpointer user_data) +{ + MMGenericGsm *self = MM_GENERIC_GSM (modem); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMCallbackInfo *info; + MMAtSerialPort *port; + + info = mm_callback_info_uint_new (MM_MODEM (self), callback, user_data); + + /* Use cached value if we have one */ + if (priv->charsets) { + mm_callback_info_set_result (info, GUINT_TO_POINTER (priv->charsets), NULL); mm_callback_info_schedule (info); - else - /* APN not configured on the card. Get the allowed CID range */ - mm_serial_port_queue_command_cached (port, "+CGDCONT=?", 3, cid_range_read, info); + return; + } + + /* Otherwise hit up the modem */ + port = mm_generic_gsm_get_best_at_port (self, &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "+CSCS=?", 3, get_charsets_done, info); } static void -set_apn (MMModemGsmNetwork *modem, - const char *apn, - MMModemFn callback, - gpointer user_data) +set_get_charset_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { - MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); - MMCallbackInfo *info; + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv; + MMModemCharset tried_charset; + const char *p; - info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); - mm_callback_info_set_data (info, "apn", g_strdup (apn), g_free); + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + mm_callback_info_schedule (info); + return; + } - /* Start by searching if the APN is already in card */ - mm_serial_port_queue_command (priv->primary, "+CGDCONT?", 3, existing_apns_read, info); + p = response->str; + if (g_str_has_prefix (p, "+CSCS:")) + p += 6; + while (*p == ' ') + p++; + + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + priv->cur_charset = mm_modem_charset_from_string (p); + + tried_charset = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "charset")); + + if (tried_charset != priv->cur_charset) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET, + "Modem failed to change character set to %s", + mm_modem_charset_to_string (tried_charset)); + } + + mm_callback_info_schedule (info); } -/* GetSignalQuality */ +#define TRIED_NO_QUOTES_TAG "tried-no-quotes" static void -get_signal_quality_done (MMSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +set_charset_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) { - MMGenericGsmPrivate *priv; MMCallbackInfo *info = (MMCallbackInfo *) user_data; - char *reply = response->str; - if (error) - info->error = g_error_copy (error); - else if (!strncmp (reply, "+CSQ: ", 6)) { - /* Got valid reply */ - int quality; - int ber; + info->error = mm_modem_check_removed (info->modem, error); + if (info->error) { + gboolean tried_no_quotes = !!mm_callback_info_get_data (info, TRIED_NO_QUOTES_TAG); + MMModemCharset charset = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "charset")); + char *command; - reply += 6; + if (!info->modem || tried_no_quotes) { + mm_callback_info_schedule (info); + return; + } - if (sscanf (reply, "%d, %d", &quality, &ber)) { - /* 99 means unknown */ - if (quality == 99) { - info->error = g_error_new_literal (MM_MOBILE_ERROR, - MM_MOBILE_ERROR_NO_NETWORK, - "No service"); - } else { - /* Normalize the quality */ - quality = CLAMP (quality, 0, 31) * 100 / 31; + /* Some modems puke if you include the quotes around the character + * set name, so lets try it again without them. + */ + mm_callback_info_set_data (info, TRIED_NO_QUOTES_TAG, GUINT_TO_POINTER (TRUE), NULL); + command = g_strdup_printf ("+CSCS=%s", mm_modem_charset_to_string (charset)); + mm_at_serial_port_queue_command (port, command, 3, set_charset_done, info); + g_free (command); + } else + mm_at_serial_port_queue_command (port, "+CSCS?", 3, set_get_charset_done, info); +} - priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); - priv->signal_quality = quality; - mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL); - } - } else - info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, - "Could not parse signal quality results"); +static gboolean +check_for_single_value (guint32 value) +{ + gboolean found = FALSE; + guint32 i; + + for (i = 1; i <= 32; i++) { + if (value & 0x1) { + if (found) + return FALSE; /* More than one bit set */ + found = TRUE; + } + value >>= 1; } - mm_callback_info_schedule (info); + return TRUE; } static void -get_signal_quality (MMModemGsmNetwork *modem, - MMModemUIntFn callback, - gpointer user_data) +set_charset (MMModem *modem, + MMModemCharset charset, + MMModemFn callback, + gpointer user_data) { MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); MMCallbackInfo *info; - gboolean connected; + const char *str; + char *command; + MMAtSerialPort *port; - connected = mm_port_get_connected (MM_PORT (priv->primary)); - if (connected && !priv->secondary) { - g_message ("Returning saved signal quality %d", priv->signal_quality); - callback (MM_MODEM (modem), priv->signal_quality, NULL, user_data); + info = mm_callback_info_new (modem, callback, user_data); + + if (!(priv->charsets & charset) || !check_for_single_value (charset)) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET, + "Character set 0x%X not supported", + charset); + mm_callback_info_schedule (info); return; } - info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data); - mm_serial_port_queue_command (connected ? priv->secondary : priv->primary, "+CSQ", 3, get_signal_quality_done, info); + str = mm_modem_charset_to_string (charset); + if (!str) { + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_UNSUPPORTED_CHARSET, + "Unhandled character set 0x%X", + charset); + mm_callback_info_schedule (info); + return; + } + + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_callback_info_set_data (info, "charset", GUINT_TO_POINTER (charset), NULL); + + command = g_strdup_printf ("+CSCS=\"%s\"", str); + mm_at_serial_port_queue_command (port, command, 3, set_charset_done, info); + g_free (command); +} + +MMModemCharset +mm_generic_gsm_get_charset (MMGenericGsm *self) +{ + g_return_val_if_fail (self != NULL, MM_MODEM_CHARSET_UNKNOWN); + g_return_val_if_fail (MM_IS_GENERIC_GSM (self), MM_MODEM_CHARSET_UNKNOWN); + + return MM_GENERIC_GSM_GET_PRIVATE (self)->cur_charset; } /*****************************************************************************/ /* MMModemGsmSms interface */ static void -sms_send_done (MMSerialPort *port, +sms_send_done (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -1733,39 +3291,29 @@ sms_send (MMModemGsmSms *modem, MMModemFn callback, gpointer user_data) { - MMGenericGsmPrivate *priv; MMCallbackInfo *info; char *command; - gboolean connected; - MMSerialPort *port = NULL; + MMAtSerialPort *port; info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); - priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); - connected = mm_port_get_connected (MM_PORT (priv->primary)); - if (connected) - port = priv->secondary; - else - port = priv->primary; - + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); if (!port) { - info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, - "Cannot send SMS while connected"); mm_callback_info_schedule (info); return; } /* FIXME: use the PDU mode instead */ - mm_serial_port_queue_command (port, "AT+CMGF=1", 3, NULL, NULL); + mm_at_serial_port_queue_command (port, "AT+CMGF=1", 3, NULL, NULL); command = g_strdup_printf ("+CMGS=\"%s\"\r%s\x1a", number, text); - mm_serial_port_queue_command (port, command, 10, sms_send_done, info); + mm_at_serial_port_queue_command (port, command, 10, sms_send_done, info); g_free (command); } -MMSerialPort * -mm_generic_gsm_get_port (MMGenericGsm *modem, - MMPortType ptype) +MMAtSerialPort * +mm_generic_gsm_get_at_port (MMGenericGsm *modem, + MMPortType ptype) { g_return_val_if_fail (MM_IS_GENERIC_GSM (modem), NULL); g_return_val_if_fail (ptype != MM_PORT_TYPE_UNKNOWN, NULL); @@ -1778,105 +3326,267 @@ mm_generic_gsm_get_port (MMGenericGsm *modem, return NULL; } +MMAtSerialPort * +mm_generic_gsm_get_best_at_port (MMGenericGsm *self, GError **error) +{ + MMGenericGsmPrivate *priv; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_GENERIC_GSM (self), NULL); + + priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + if (!mm_port_get_connected (MM_PORT (priv->primary))) + return priv->primary; + + if (!priv->secondary) { + g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_CONNECTED, + "Cannot perform this operation while connected"); + } + + return priv->secondary; +} + /*****************************************************************************/ /* MMModemSimple interface */ typedef enum { - SIMPLE_STATE_BEGIN = 0, + SIMPLE_STATE_CHECK_PIN = 0, SIMPLE_STATE_ENABLE, - SIMPLE_STATE_CHECK_PIN, + SIMPLE_STATE_ALLOWED_MODE, SIMPLE_STATE_REGISTER, SIMPLE_STATE_SET_APN, SIMPLE_STATE_CONNECT, SIMPLE_STATE_DONE } SimpleState; -static const char * -simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error) +/* Looks a value up in the simple connect properties dictionary. If the + * requested key is not present in the dict, NULL is returned. If the + * requested key is present but is not a string, an error is returned. + */ +static gboolean +simple_get_property (MMCallbackInfo *info, + const char *name, + GType expected_type, + const char **out_str, + guint32 *out_num, + gboolean *out_bool, + GError **error) { GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties"); GValue *value; + gint foo; + + g_return_val_if_fail (properties != NULL, FALSE); + g_return_val_if_fail (name != NULL, FALSE); + if (out_str) + g_return_val_if_fail (*out_str == NULL, FALSE); value = (GValue *) g_hash_table_lookup (properties, name); if (!value) - return NULL; - - if (G_VALUE_HOLDS_STRING (value)) - return g_value_get_string (value); + return FALSE; + + if ((expected_type == G_TYPE_STRING) && G_VALUE_HOLDS_STRING (value)) { + *out_str = g_value_get_string (value); + return TRUE; + } else if (expected_type == G_TYPE_UINT) { + if (G_VALUE_HOLDS_UINT (value)) { + *out_num = g_value_get_uint (value); + return TRUE; + } else if (G_VALUE_HOLDS_INT (value)) { + /* handle ints for convenience, but only if they are >= 0 */ + foo = g_value_get_int (value); + if (foo >= 0) { + *out_num = (guint) foo; + return TRUE; + } + } + } else if (expected_type == G_TYPE_BOOLEAN && G_VALUE_HOLDS_BOOLEAN (value)) { + *out_bool = g_value_get_boolean (value); + return TRUE; + } g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, - "Invalid property type for '%s': %s (string expected)", - name, G_VALUE_TYPE_NAME (value)); + "Invalid property type for '%s': %s (%s expected)", + name, G_VALUE_TYPE_NAME (value), g_type_name (expected_type)); - return NULL; + return FALSE; +} + +static const char * +simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error) +{ + const char *str = NULL; + + simple_get_property (info, name, G_TYPE_STRING, &str, NULL, NULL, error); + return str; +} + +static gboolean +simple_get_uint_property (MMCallbackInfo *info, const char *name, guint32 *out_val, GError **error) +{ + return simple_get_property (info, name, G_TYPE_UINT, NULL, out_val, NULL, error); +} + +static gboolean +simple_get_bool_property (MMCallbackInfo *info, const char *name, gboolean *out_val, GError **error) +{ + return simple_get_property (info, name, G_TYPE_BOOLEAN, NULL, NULL, out_val, error); +} + +static gboolean +simple_get_allowed_mode (MMCallbackInfo *info, + MMModemGsmAllowedMode *out_mode, + GError **error) +{ + MMModemDeprecatedMode old_mode = MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_ANY; + MMModemGsmAllowedMode allowed_mode = MM_MODEM_GSM_ALLOWED_MODE_ANY; + GError *tmp_error = NULL; + + /* check for new allowed mode first */ + if (simple_get_uint_property (info, "allowed_mode", &allowed_mode, &tmp_error)) { + if (allowed_mode > MM_MODEM_GSM_ALLOWED_MODE_LAST) { + g_set_error (&tmp_error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Invalid allowed mode %d", old_mode); + } else { + *out_mode = allowed_mode; + return TRUE; + } + } else if (!tmp_error) { + /* and if not, the old allowed mode */ + if (simple_get_uint_property (info, "network_mode", &old_mode, &tmp_error)) { + if (old_mode > MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_LAST) { + g_set_error (&tmp_error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Invalid allowed mode %d", old_mode); + } else { + *out_mode = mm_modem_gsm_network_old_mode_to_allowed (old_mode); + return TRUE; + } + } + } + + if (error) + *error = tmp_error; + return FALSE; } static void simple_state_machine (MMModem *modem, GError *error, gpointer user_data) { MMCallbackInfo *info = (MMCallbackInfo *) user_data; - const char *str; + MMGenericGsmPrivate *priv; + const char *str, *unlock = NULL; SimpleState state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "simple-connect-state")); - gboolean need_pin = FALSE; + SimpleState next_state = state; + gboolean done = FALSE; + MMModemGsmAllowedMode allowed_mode; + gboolean home_only = FALSE; - if (error) { - if (g_error_matches (error, MM_MOBILE_ERROR, MM_MOBILE_ERROR_SIM_PIN)) { - need_pin = TRUE; - state = SIMPLE_STATE_CHECK_PIN; - } else { - info->error = g_error_copy (error); - goto out; - } + info->error = mm_modem_check_removed (modem, error); + if (info->error) + goto out; + + priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + + if (mm_options_debug ()) { + GTimeVal tv; + char *data_device; + + g_object_get (G_OBJECT (modem), MM_MODEM_DATA_DEVICE, &data_device, NULL); + g_get_current_time (&tv); + g_debug ("<%ld.%ld> (%s): simple connect state %d", + tv.tv_sec, tv.tv_usec, data_device, state); + g_free (data_device); } switch (state) { - case SIMPLE_STATE_BEGIN: - state = SIMPLE_STATE_ENABLE; - mm_modem_enable (modem, simple_state_machine, info); - break; - case SIMPLE_STATE_ENABLE: - state = SIMPLE_STATE_CHECK_PIN; - mm_generic_gsm_check_pin (MM_GENERIC_GSM (modem), simple_state_machine, info); - break; case SIMPLE_STATE_CHECK_PIN: - if (need_pin) { - str = simple_get_string_property (info, "pin", &info->error); - if (str) - mm_modem_gsm_card_send_pin (MM_MODEM_GSM_CARD (modem), str, simple_state_machine, info); - else - info->error = g_error_copy (error); - } else { - str = simple_get_string_property (info, "network_id", &info->error); - state = SIMPLE_STATE_REGISTER; - if (!info->error) - mm_modem_gsm_network_register (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info); + next_state = SIMPLE_STATE_ENABLE; + + /* If we need a PIN, send it now, but we don't care about SIM-PIN2/SIM-PUK2 + * since the device is operational without it. + */ + unlock = mm_modem_base_get_unlock_required (MM_MODEM_BASE (modem)); + if (unlock && strcmp (unlock, "sim-puk2") && strcmp (unlock, "sim-pin2")) { + gboolean success = FALSE; + + if (!strcmp (unlock, "sim-pin")) { + str = simple_get_string_property (info, "pin", &info->error); + if (str) { + mm_modem_gsm_card_send_pin (MM_MODEM_GSM_CARD (modem), str, simple_state_machine, info); + success = TRUE; + } + } + if (!success && !info->error) + info->error = error_for_unlock_required (unlock); + break; } + /* Fall through if no PIN required */ + case SIMPLE_STATE_ENABLE: + next_state = SIMPLE_STATE_ALLOWED_MODE; + mm_modem_enable (modem, simple_state_machine, info); break; + case SIMPLE_STATE_ALLOWED_MODE: + next_state = SIMPLE_STATE_REGISTER; + if ( simple_get_allowed_mode (info, &allowed_mode, &info->error) + && (allowed_mode != priv->allowed_mode)) { + mm_modem_gsm_network_set_allowed_mode (MM_MODEM_GSM_NETWORK (modem), + allowed_mode, + simple_state_machine, + info); + break; + } else if (info->error) + break; + /* otherwise fall through as no allowed mode was sent */ case SIMPLE_STATE_REGISTER: + next_state = SIMPLE_STATE_SET_APN; + str = simple_get_string_property (info, "network_id", &info->error); + if (info->error) + str = NULL; + mm_modem_gsm_network_register (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info); + break; + case SIMPLE_STATE_SET_APN: + next_state = SIMPLE_STATE_CONNECT; str = simple_get_string_property (info, "apn", &info->error); - if (str) { - state = SIMPLE_STATE_SET_APN; - mm_modem_gsm_network_set_apn (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info); + if (str || info->error) { + if (str) + mm_modem_gsm_network_set_apn (MM_MODEM_GSM_NETWORK (modem), str, simple_state_machine, info); break; } - /* Fall through */ - case SIMPLE_STATE_SET_APN: - str = simple_get_string_property (info, "number", &info->error); - state = SIMPLE_STATE_CONNECT; - mm_modem_connect (modem, str, simple_state_machine, info); - break; + /* Fall through if no APN or no 'apn' property error */ case SIMPLE_STATE_CONNECT: - state = SIMPLE_STATE_DONE; + next_state = SIMPLE_STATE_DONE; + str = simple_get_string_property (info, "number", &info->error); + if (!info->error) { + if (simple_get_bool_property (info, "home_only", &home_only, &info->error)) { + MMModemGsmNetworkRegStatus status; + + priv->roam_allowed = !home_only; + + /* Don't connect if we're not supposed to be roaming */ + status = gsm_reg_status (MM_GENERIC_GSM (modem)); + if (home_only && (status == MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING)) { + info->error = g_error_new_literal (MM_MOBILE_ERROR, + MM_MOBILE_ERROR_GPRS_ROAMING_NOT_ALLOWED, + "Roaming is not allowed."); + break; + } + } else if (info->error) + break; + + mm_modem_connect (modem, str, simple_state_machine, info); + } break; case SIMPLE_STATE_DONE: + done = TRUE; break; } out: - if (info->error || state == SIMPLE_STATE_DONE) + if (info->error || done) mm_callback_info_schedule (info); else - mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (state), NULL); + mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (next_state), NULL); } static void @@ -1887,6 +3597,29 @@ simple_connect (MMModemSimple *simple, { MMCallbackInfo *info; + /* If debugging, list all the simple connect properties */ + if (mm_options_debug ()) { + GHashTableIter iter; + gpointer key, value; + GTimeVal tv; + char *data_device; + + g_object_get (G_OBJECT (simple), MM_MODEM_DATA_DEVICE, &data_device, NULL); + g_get_current_time (&tv); + + g_hash_table_iter_init (&iter, properties); + while (g_hash_table_iter_next (&iter, &key, &value)) { + char *val_str; + + val_str = g_strdup_value_contents ((GValue *) value); + g_debug ("<%ld.%ld> (%s): %s => %s", + tv.tv_sec, tv.tv_usec, + data_device, (const char *) key, val_str); + g_free (val_str); + } + g_free (data_device); + } + info = mm_callback_info_new (MM_MODEM (simple), callback, user_data); mm_callback_info_set_data (info, "simple-connect-properties", g_hash_table_ref (properties), @@ -1895,8 +3628,6 @@ simple_connect (MMModemSimple *simple, simple_state_machine (MM_MODEM (simple), NULL, info); } - - static void simple_free_gvalue (gpointer data) { @@ -1928,16 +3659,22 @@ simple_string_value (const char *str) return val; } +#define SS_HASH_TAG "simple-get-status" + static void simple_status_got_signal_quality (MMModem *modem, guint32 result, GError *error, gpointer user_data) { - if (error) - g_warning ("Error getting signal quality: %s", error->message); - else - g_hash_table_insert ((GHashTable *) user_data, "signal_quality", simple_uint_value (result)); + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GHashTable *properties; + + if (!error) { + properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG); + g_hash_table_insert (properties, "signal_quality", simple_uint_value (result)); + } + mm_callback_info_chain_complete_one (info); } static void @@ -1946,20 +3683,14 @@ simple_status_got_band (MMModem *modem, GError *error, gpointer user_data) { - /* Ignore band errors since there's no generic implementation for it */ - if (!error) - g_hash_table_insert ((GHashTable *) user_data, "band", simple_uint_value (result)); -} + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + GHashTable *properties; -static void -simple_status_got_mode (MMModem *modem, - guint32 result, - GError *error, - gpointer user_data) -{ - /* Ignore network mode errors since there's no generic implementation for it */ - if (!error) - g_hash_table_insert ((GHashTable *) user_data, "network_mode", simple_uint_value (result)); + if (!error) { + properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG); + g_hash_table_insert (properties, "band", simple_uint_value (result)); + } + mm_callback_info_chain_complete_one (info); } static void @@ -1973,17 +3704,15 @@ simple_status_got_reg_info (MMModemGsmNetwork *modem, MMCallbackInfo *info = (MMCallbackInfo *) user_data; GHashTable *properties; - if (error) - info->error = g_error_copy (error); - else { - properties = (GHashTable *) mm_callback_info_get_data (info, "simple-get-status"); - + info->error = mm_modem_check_removed ((MMModem *) modem, error); + if (!info->error) { + properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG); + g_hash_table_insert (properties, "registration_status", simple_uint_value (status)); g_hash_table_insert (properties, "operator_code", simple_string_value (oper_code)); g_hash_table_insert (properties, "operator_name", simple_string_value (oper_name)); } - - mm_callback_info_schedule (info); + mm_callback_info_chain_complete_one (info); } static void @@ -1992,7 +3721,7 @@ simple_get_status_invoke (MMCallbackInfo *info) MMModemSimpleGetStatusFn callback = (MMModemSimpleGetStatusFn) info->callback; callback (MM_MODEM_SIMPLE (info->modem), - (GHashTable *) mm_callback_info_get_data (info, "simple-get-status"), + (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG), info->error, info->user_data); } @@ -2001,23 +3730,54 @@ simple_get_status (MMModemSimple *simple, MMModemSimpleGetStatusFn callback, gpointer user_data) { - MMModemGsmNetwork *gsm; + MMModemGsmNetwork *gsm = MM_MODEM_GSM_NETWORK (simple); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (simple); GHashTable *properties; MMCallbackInfo *info; + MMModemDeprecatedMode old_mode; info = mm_callback_info_new_full (MM_MODEM (simple), simple_get_status_invoke, G_CALLBACK (callback), user_data); - properties = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, simple_free_gvalue); - mm_callback_info_set_data (info, "simple-get-status", properties, (GDestroyNotify) g_hash_table_unref); + properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, simple_free_gvalue); + mm_callback_info_set_data (info, SS_HASH_TAG, properties, (GDestroyNotify) g_hash_table_unref); + + mm_callback_info_chain_start (info, 3); + mm_modem_gsm_network_get_signal_quality (gsm, simple_status_got_signal_quality, info); + mm_modem_gsm_network_get_band (gsm, simple_status_got_band, info); + mm_modem_gsm_network_get_registration_info (gsm, simple_status_got_reg_info, info); + + if (priv->act > -1) { + /* Deprecated key */ + old_mode = mm_modem_gsm_network_act_to_old_mode (priv->act); + g_hash_table_insert (properties, "network_mode", simple_uint_value (old_mode)); - gsm = MM_MODEM_GSM_NETWORK (simple); - mm_modem_gsm_network_get_signal_quality (gsm, simple_status_got_signal_quality, properties); - mm_modem_gsm_network_get_band (gsm, simple_status_got_band, properties); - mm_modem_gsm_network_get_mode (gsm, simple_status_got_mode, properties); - mm_modem_gsm_network_get_registration_info (gsm, simple_status_got_reg_info, properties); + /* New key */ + g_hash_table_insert (properties, "access_technology", simple_uint_value (priv->act)); + } +} + +/*****************************************************************************/ + +static void +modem_state_changed (MMGenericGsm *self, GParamSpec *pspec, gpointer user_data) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + MMModemState state; + + /* Start polling registration status and signal quality when enabled */ + + state = mm_modem_get_state (MM_MODEM (self)); + if (state >= MM_MODEM_STATE_ENABLED) { + if (!priv->poll_id) + priv->poll_id = g_timeout_add_seconds (30, periodic_poll_cb, self); + } else { + if (priv->poll_id) + g_source_remove (priv->poll_id); + priv->poll_id = 0; + } } /*****************************************************************************/ @@ -2033,6 +3793,8 @@ modem_init (MMModem *modem_class) modem_class->connect = connect; modem_class->disconnect = disconnect; modem_class->get_info = get_card_info; + modem_class->get_supported_charsets = get_supported_charsets; + modem_class->set_charset = set_charset; } static void @@ -2040,10 +3802,12 @@ modem_gsm_card_init (MMModemGsmCard *class) { class->get_imei = get_imei; class->get_imsi = get_imsi; + class->get_operator_id = get_operator_id; class->send_pin = send_pin; class->send_puk = send_puk; class->enable_pin = enable_pin; class->change_pin = change_pin; + class->get_unlock_retries = get_unlock_retries; } static void @@ -2051,6 +3815,7 @@ modem_gsm_network_init (MMModemGsmNetwork *class) { class->do_register = do_register; class->get_registration_info = get_registration_info; + class->set_allowed_mode = set_allowed_mode; class->set_apn = set_apn; class->scan = scan; class->get_signal_quality = get_signal_quality; @@ -2072,6 +3837,22 @@ modem_simple_init (MMModemSimple *class) static void mm_generic_gsm_init (MMGenericGsm *self) { + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + priv->act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + priv->reg_regex = mm_gsm_creg_regex_get (TRUE); + priv->roam_allowed = TRUE; + + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_GSM_NETWORK_ALLOWED_MODE, + MM_MODEM_GSM_NETWORK_DBUS_INTERFACE); + + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY, + MM_MODEM_GSM_NETWORK_DBUS_INTERFACE); + + g_signal_connect (self, "notify::" MM_MODEM_STATE, + G_CALLBACK (modem_state_changed), NULL); } static void @@ -2086,6 +3867,8 @@ set_property (GObject *object, guint prop_id, case MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL: case MM_GENERIC_GSM_PROP_SUPPORTED_BANDS: case MM_GENERIC_GSM_PROP_SUPPORTED_MODES: + case MM_GENERIC_GSM_PROP_ALLOWED_MODE: + case MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY: break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -2123,7 +3906,7 @@ get_property (GObject *object, guint prop_id, g_value_set_string (value, ""); break; case MM_GENERIC_GSM_PROP_INIT_CMD: - g_value_set_string (value, "Z E0 V1 +CMEE=1"); + g_value_set_string (value, "Z E0 V1"); break; case MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL: g_value_set_string (value, "X4 &C1"); @@ -2134,6 +3917,15 @@ get_property (GObject *object, guint prop_id, case MM_GENERIC_GSM_PROP_SUPPORTED_MODES: g_value_set_uint (value, 0); break; + case MM_GENERIC_GSM_PROP_ALLOWED_MODE: + g_value_set_uint (value, priv->allowed_mode); + break; + case MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY: + if (mm_modem_get_state (MM_MODEM (object)) >= MM_MODEM_STATE_ENABLED) + g_value_set_uint (value, priv->act); + else + g_value_set_uint (value, MM_MODEM_GSM_ACCESS_TECH_UNKNOWN); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2147,6 +3939,23 @@ finalize (GObject *object) mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (object)); + if (priv->pin_check_timeout) { + g_source_remove (priv->pin_check_timeout); + priv->pin_check_timeout = 0; + } + + if (priv->poll_id) { + g_source_remove (priv->poll_id); + priv->poll_id = 0; + } + + if (priv->signal_quality_id) { + g_source_remove (priv->signal_quality_id); + priv->signal_quality_id = 0; + } + + mm_gsm_creg_regex_destroy (priv->reg_regex); + g_free (priv->oper_code); g_free (priv->oper_name); @@ -2167,6 +3976,8 @@ mm_generic_gsm_class_init (MMGenericGsmClass *klass) object_class->finalize = finalize; klass->do_enable = real_do_enable; + klass->do_enable_power_up_done = real_do_enable_power_up_done; + klass->do_disconnect = real_do_disconnect; /* Properties */ g_object_class_override_property (object_class, @@ -2185,6 +3996,14 @@ mm_generic_gsm_class_init (MMGenericGsmClass *klass) MM_GENERIC_GSM_PROP_SUPPORTED_MODES, MM_MODEM_GSM_CARD_SUPPORTED_MODES); + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_ALLOWED_MODE, + MM_MODEM_GSM_NETWORK_ALLOWED_MODE); + + g_object_class_override_property (object_class, + MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY, + MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY); + g_object_class_install_property (object_class, MM_GENERIC_GSM_PROP_POWER_UP_CMD, g_param_spec_string (MM_GENERIC_GSM_POWER_UP_CMD, diff --git a/src/mm-generic-gsm.h b/src/mm-generic-gsm.h index de0b00b..de9dc89 100644 --- a/src/mm-generic-gsm.h +++ b/src/mm-generic-gsm.h @@ -11,7 +11,7 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #ifndef MM_GENERIC_GSM_H @@ -20,8 +20,9 @@ #include "mm-modem-gsm.h" #include "mm-modem-gsm-network.h" #include "mm-modem-base.h" -#include "mm-serial-port.h" +#include "mm-at-serial-port.h" #include "mm-callback-info.h" +#include "mm-charsets.h" #define MM_TYPE_GENERIC_GSM (mm_generic_gsm_get_type ()) #define MM_GENERIC_GSM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_GENERIC_GSM, MMGenericGsm)) @@ -43,9 +44,16 @@ typedef enum { MM_GENERIC_GSM_PROP_INIT_CMD, MM_GENERIC_GSM_PROP_SUPPORTED_BANDS, MM_GENERIC_GSM_PROP_SUPPORTED_MODES, - MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL + MM_GENERIC_GSM_PROP_INIT_CMD_OPTIONAL, + MM_GENERIC_GSM_PROP_ALLOWED_MODE, + MM_GENERIC_GSM_PROP_ACCESS_TECHNOLOGY } MMGenericGsmProp; +typedef enum { + MM_GENERIC_GSM_REG_TYPE_UNKNOWN = 0, + MM_GENERIC_GSM_REG_TYPE_CS = 1, + MM_GENERIC_GSM_REG_TYPE_PS = 2 +} MMGenericGsmRegType; typedef struct { MMModemBase parent; @@ -59,9 +67,49 @@ typedef struct { * that need to perform custom initialization sequences or other setup should * generally override this method instead of the MMModem interface's enable() * method, unless the customization must happen *after* the generic init - * sequence has completed. + * sequence has completed. When the subclass' enable attempt is complete + * the subclass should call mm_generic_gsm_enable_complete() with any error + * encountered during the process and the MMCallbackInfo created from the + * callback and user_data passed in here. */ void (*do_enable) (MMGenericGsm *self, MMModemFn callback, gpointer user_data); + + /* Called after the generic class has attempted to power up the modem. + * Subclasses can handle errors here if they know the device supports their + * power up command. Will only be called if the device does *not* override + * the MMModem enable() command or allows the generic class' do_enable() + * handler to execute. + */ + void (*do_enable_power_up_done) (MMGenericGsm *self, + GString *response, + GError *error, + MMCallbackInfo *info); + + /* Called to terminate the active data call and deactivate the given PDP + * context. + */ + void (*do_disconnect) (MMGenericGsm *self, + gint cid, + MMModemFn callback, + gpointer user_data); + + /* Called by the generic class to set the allowed operating mode of the device */ + void (*set_allowed_mode) (MMGenericGsm *self, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data); + + /* Called by the generic class to get the allowed operating mode of the device */ + void (*get_allowed_mode) (MMGenericGsm *self, + MMModemUIntFn callback, + gpointer user_data); + + /* Called by the generic class to the current radio access technology the + * device is using while communicating with the base station. + */ + void (*get_access_technology) (MMGenericGsm *self, + MMModemUIntFn callback, + gpointer user_data); } MMGenericGsmClass; GType mm_generic_gsm_get_type (void); @@ -74,24 +122,42 @@ MMModem *mm_generic_gsm_new (const char *device, #define MM_GENERIC_GSM_PREV_STATE_TAG "prev-state" -void mm_generic_gsm_set_unsolicited_registration (MMGenericGsm *modem, - gboolean enabled); - -void mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem); +void mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem); -void mm_generic_gsm_set_cid (MMGenericGsm *modem, - guint32 cid); +gint mm_generic_gsm_get_cid (MMGenericGsm *modem); -guint32 mm_generic_gsm_get_cid (MMGenericGsm *modem); void mm_generic_gsm_set_reg_status (MMGenericGsm *modem, + MMGenericGsmRegType reg_type, MMModemGsmNetworkRegStatus status); -void mm_generic_gsm_check_pin (MMGenericGsm *modem, - MMModemFn callback, - gpointer user_data); +MMModemCharset mm_generic_gsm_get_charset (MMGenericGsm *modem); + +/* Called to asynchronously update the current allowed operating mode that the + * device is allowed to use when connecting to a network. This isn't the + * specific access technology the device is currently using (see + * mm_generic_gsm_set_access_technology() for that) but the mode the device is + * allowed to choose from when connecting. + */ +void mm_generic_gsm_update_allowed_mode (MMGenericGsm *modem, + MMModemGsmAllowedMode mode); + +/* Called to asynchronously update the current access technology of the device; + * this is NOT the 2G/3G mode preference, but the current radio access + * technology being used to communicate with the base station. + */ +void mm_generic_gsm_update_access_technology (MMGenericGsm *modem, + MMModemGsmAccessTech act); + +/* Called to asynchronously update the current signal quality of the device; + * 'quality' is a 0 - 100% quality. + */ +void mm_generic_gsm_update_signal_quality (MMGenericGsm *modem, guint32 quality); + +MMAtSerialPort *mm_generic_gsm_get_at_port (MMGenericGsm *modem, + MMPortType ptype); -MMSerialPort *mm_generic_gsm_get_port (MMGenericGsm *modem, - MMPortType ptype); +MMAtSerialPort *mm_generic_gsm_get_best_at_port (MMGenericGsm *modem, + GError **error); MMPort *mm_generic_gsm_grab_port (MMGenericGsm *modem, const char *subsys, diff --git a/src/mm-manager.c b/src/mm-manager.c index 1a93170..1dd1902 100644 --- a/src/mm-manager.c +++ b/src/mm-manager.c @@ -11,10 +11,11 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #include +#include #include #define G_UDEV_API_IS_SUBJECT_TO_CHANGE #include @@ -52,6 +53,21 @@ typedef struct { GHashTable *supports; } MMManagerPrivate; +typedef struct { + MMManager *manager; + char *subsys; + char *name; + char *physdev_path; + GSList *plugins; + GSList *cur_plugin; + guint defer_id; + guint done_id; + + guint32 best_level; + MMPlugin *best_plugin; +} SupportsInfo; + + static MMPlugin * load_plugin (const char *path) { @@ -188,44 +204,98 @@ remove_modem (MMManager *manager, MMModem *modem) } static void -modem_valid (MMModem *modem, GParamSpec *pspec, gpointer user_data) +check_export_modem (MMManager *self, MMModem *modem) { - MMManager *manager = MM_MANAGER (user_data); - MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); - static guint32 id = 0; - char *path, *device; + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (self); + char *modem_physdev; + GHashTableIter iter; + gpointer value; + + /* A modem is only exported to D-Bus when both of the following are true: + * + * 1) the modem is valid + * 2) all ports the modem provides have either been grabbed or are + * unsupported by any plugin + * + * This ensures that all the modem's ports are completely ready before + * any clients can do anything with it. + * + * FIXME: if udev or the kernel are really slow giving us ports, there's a + * chance that a port could show up after the modem is already created and + * all other ports are already handled. That chance is very small though. + */ + modem_physdev = mm_modem_get_device (modem); + g_assert (modem_physdev); + + /* Check for ports that are in the process of being interrogated by plugins */ + g_hash_table_iter_init (&iter, priv->supports); + while (g_hash_table_iter_next (&iter, NULL, &value)) { + SupportsInfo *info = value; + + if (!strcmp (info->physdev_path, modem_physdev)) { + g_debug ("(%s/%s): outstanding support task prevents export of %s", + info->subsys, info->name, modem_physdev); + goto out; + } + } + + /* Already exported? This can happen if the modem is exported and the kernel + * discovers another of the modem's ports. + */ + if (g_object_get_data (G_OBJECT (modem), DBUS_PATH_TAG)) + goto out; + + /* No outstanding port tasks, so if the modem is valid we can export it */ if (mm_modem_get_valid (modem)) { + static guint32 id = 0; + char *path, *data_device = NULL; + path = g_strdup_printf (MM_DBUS_PATH"/Modems/%d", id++); dbus_g_connection_register_g_object (priv->connection, path, G_OBJECT (modem)); g_object_set_data_full (G_OBJECT (modem), DBUS_PATH_TAG, path, (GDestroyNotify) g_free); - device = mm_modem_get_device (modem); - g_assert (device); - g_debug ("Exported modem %s as %s", device, path); - g_free (device); + g_debug ("Exported modem %s as %s", modem_physdev, path); - g_signal_emit (manager, signals[DEVICE_ADDED], 0, modem); - } else + g_object_get (G_OBJECT (modem), MM_MODEM_DATA_DEVICE, &data_device, NULL); + g_debug ("(%s): data port is %s", path, data_device); + g_free (data_device); + + g_signal_emit (self, signals[DEVICE_ADDED], 0, modem); + } + +out: + g_free (modem_physdev); +} + +static void +modem_valid (MMModem *modem, GParamSpec *pspec, gpointer user_data) +{ + MMManager *manager = MM_MANAGER (user_data); + + if (mm_modem_get_valid (modem)) + check_export_modem (manager, modem); + else remove_modem (manager, modem); } +#define MANAGER_PLUGIN_TAG "manager-plugin" + static void -add_modem (MMManager *manager, MMModem *modem) +add_modem (MMManager *manager, MMModem *modem, MMPlugin *plugin) { MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); char *device; - gboolean valid = FALSE; device = mm_modem_get_device (modem); g_assert (device); if (!g_hash_table_lookup (priv->modems, device)) { g_hash_table_insert (priv->modems, g_strdup (device), modem); + g_object_set_data (G_OBJECT (modem), MANAGER_PLUGIN_TAG, plugin); + g_debug ("Added modem %s", device); - g_signal_connect (modem, "notify::valid", G_CALLBACK (modem_valid), manager); - g_object_get (modem, MM_MODEM_VALID, &valid, NULL); - if (valid) - modem_valid (modem, NULL, manager); + g_signal_connect (modem, "notify::" MM_MODEM_VALID, G_CALLBACK (modem_valid), manager); + check_export_modem (manager, modem); } g_free (device); } @@ -236,10 +306,8 @@ enumerate_devices_cb (gpointer key, gpointer val, gpointer user_data) MMModem *modem = MM_MODEM (val); GPtrArray **devices = (GPtrArray **) user_data; const char *path; - gboolean valid = FALSE; - g_object_get (G_OBJECT (modem), MM_MODEM_VALID, &valid, NULL); - if (valid) { + if (mm_modem_get_valid (modem)) { path = g_object_get_data (G_OBJECT (modem), DBUS_PATH_TAG); g_return_if_fail (path != NULL); g_ptr_array_add (*devices, g_strdup (path)); @@ -265,15 +333,18 @@ find_modem_for_device (MMManager *manager, const char *device) MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); GHashTableIter iter; gpointer key, value; + MMModem *found = NULL; g_hash_table_iter_init (&iter, priv->modems); - while (g_hash_table_iter_next (&iter, &key, &value)) { - MMModem *modem = MM_MODEM (value); + while (g_hash_table_iter_next (&iter, &key, &value) && !found) { + MMModem *candidate = MM_MODEM (value); + char *candidate_device = mm_modem_get_device (candidate); - if (!strcmp (device, mm_modem_get_device (modem))) - return modem; + if (!strcmp (device, candidate_device)) + found = candidate; + g_free (candidate_device); } - return NULL; + return found; } @@ -294,21 +365,11 @@ find_modem_for_port (MMManager *manager, const char *subsys, const char *name) return NULL; } -typedef struct { - MMManager *manager; - char *subsys; - char *name; - GSList *plugins; - GSList *cur_plugin; - guint defer_id; - guint done_id; - - guint32 best_level; - MMPlugin *best_plugin; -} SupportsInfo; - static SupportsInfo * -supports_info_new (MMManager *self, const char *subsys, const char *name) +supports_info_new (MMManager *self, + const char *subsys, + const char *name, + const char *physdev_path) { MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (self); SupportsInfo *info; @@ -317,6 +378,7 @@ supports_info_new (MMManager *self, const char *subsys, const char *name) info->manager = self; info->subsys = g_strdup (subsys); info->name = g_strdup (name); + info->physdev_path = g_strdup (physdev_path); info->plugins = g_slist_copy (priv->plugins); info->cur_plugin = info->plugins; return info; @@ -357,20 +419,21 @@ static void supports_callback (MMPlugin *plugin, static void try_supports_port (MMManager *manager, MMPlugin *plugin, - const char *subsys, - const char *name, + MMModem *existing, SupportsInfo *info); static gboolean supports_defer_timeout (gpointer user_data) { SupportsInfo *info = user_data; + MMModem *existing; + + existing = find_modem_for_device (info->manager, info->physdev_path); g_debug ("(%s): re-checking support...", info->name); try_supports_port (info->manager, MM_PLUGIN (info->cur_plugin->data), - info->subsys, - info->name, + existing, info); return FALSE; } @@ -378,23 +441,30 @@ supports_defer_timeout (gpointer user_data) static void try_supports_port (MMManager *manager, MMPlugin *plugin, - const char *subsys, - const char *name, + MMModem *existing, SupportsInfo *info) { MMPluginSupportsResult result; - result = mm_plugin_supports_port (plugin, subsys, name, supports_callback, info); + result = mm_plugin_supports_port (plugin, + info->subsys, + info->name, + info->physdev_path, + existing, + supports_callback, + info); switch (result) { case MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED: /* If the plugin knows it doesn't support the modem, just call the * callback and indicate 0 support. */ - supports_callback (plugin, subsys, name, 0, info); + supports_callback (plugin, info->subsys, info->name, 0, info); break; case MM_PLUGIN_SUPPORTS_PORT_DEFER: - g_debug ("(%s): (%s) deferring support check", mm_plugin_get_name (plugin), name); + g_debug ("(%s): (%s) deferring support check", + mm_plugin_get_name (plugin), + info->name); if (info->defer_id) g_source_remove (info->defer_id); @@ -407,23 +477,54 @@ try_supports_port (MMManager *manager, } } +static void +supports_cleanup (MMManager *self, + const char *subsys, + const char *name, + MMModem *modem) +{ + MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (self); + char *key; + + g_return_if_fail (subsys != NULL); + g_return_if_fail (name != NULL); + + key = get_key (subsys, name); + g_hash_table_remove (priv->supports, key); + g_free (key); + + /* Each time a supports task is cleaned up, check whether the modem is + * now completely probed/handled and should be exported to D-Bus clients. + * + * IMPORTANT: this must be done after removing the supports into from + * priv->supports since check_export_modem() searches through priv->supports + * for outstanding supports tasks. + */ + if (modem) + check_export_modem (self, modem); +} + static gboolean do_grab_port (gpointer user_data) { SupportsInfo *info = user_data; MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (info->manager); - MMModem *modem; + MMModem *modem = NULL; GError *error = NULL; - char *key; GSList *iter; /* No more plugins to try */ if (info->best_plugin) { + MMModem *existing; + + existing = g_hash_table_lookup (priv->modems, info->physdev_path); + /* Create the modem */ - modem = mm_plugin_grab_port (info->best_plugin, info->subsys, info->name, &error); + modem = mm_plugin_grab_port (info->best_plugin, info->subsys, info->name, existing, &error); if (modem) { guint32 modem_type = MM_MODEM_TYPE_UNKNOWN; const char *type_name = "UNKNOWN"; + char *device;; g_object_get (G_OBJECT (modem), MM_MODEM_TYPE, &modem_type, NULL); if (modem_type == MM_MODEM_TYPE_GSM) @@ -431,13 +532,15 @@ do_grab_port (gpointer user_data) else if (modem_type == MM_MODEM_TYPE_CDMA) type_name = "CDMA"; + device = mm_modem_get_device (modem); g_message ("(%s): %s modem %s claimed port %s", mm_plugin_get_name (info->best_plugin), type_name, - mm_modem_get_device (modem), + device, info->name); + g_free (device); - add_modem (info->manager, modem); + add_modem (info->manager, modem, info->best_plugin); } else { g_warning ("%s: plugin '%s' claimed to support %s/%s but couldn't: (%d) %s", __func__, @@ -446,6 +549,7 @@ do_grab_port (gpointer user_data) info->name, error ? error->code : -1, (error && error->message) ? error->message : "(unknown)"); + modem = existing; } } @@ -455,10 +559,7 @@ do_grab_port (gpointer user_data) g_slist_free (info->plugins); info->cur_plugin = info->plugins = NULL; - key = get_key (info->subsys, info->name); - g_hash_table_remove (priv->supports, key); - g_free (key); - + supports_cleanup (info->manager, info->subsys, info->name, modem); return FALSE; } @@ -471,10 +572,7 @@ supports_callback (MMPlugin *plugin, { SupportsInfo *info = user_data; MMPlugin *next_plugin = NULL; - - info->cur_plugin = info->cur_plugin->next; - if (info->cur_plugin) - next_plugin = MM_PLUGIN (info->cur_plugin->data); + MMModem *existing; /* Is this plugin's result better than any one we've tried before? */ if (level > info->best_level) { @@ -482,32 +580,147 @@ supports_callback (MMPlugin *plugin, info->best_plugin = plugin; } - /* Prevent the generic plugin from probing devices that are already supported - * by other plugins. For Huawei for example, secondary ports shouldn't - * be probed, but the generic plugin would happily do so if allowed to. + /* If there's already a modem for this port's physical device, stop asking + * plugins because the same plugin that owns the modem gets this port no + * matter what. */ - if ( next_plugin - && !strcmp (mm_plugin_get_name (next_plugin), MM_PLUGIN_GENERIC_NAME) - && info->best_plugin) - next_plugin = NULL; + existing = find_modem_for_device (info->manager, info->physdev_path); + if (existing) { + MMPlugin *existing_plugin; + + existing_plugin = MM_PLUGIN (g_object_get_data (G_OBJECT (existing), MANAGER_PLUGIN_TAG)); + g_assert (existing_plugin); + + if (plugin == existing_plugin) { + if (level == 0) { + /* If the plugin that just completed the support check claims not to + * support this port, but this plugin is clearly the right plugin + * since it claimed this port's physical modem, just drop the port. + */ + g_debug ("(%s/%s): ignoring port unsupported by physical modem's plugin", + info->subsys, info->name); + supports_cleanup (info->manager, info->subsys, info->name, existing); + return; + } + + /* Otherwise, this port was supported by the plugin that owns the + * port's physical modem, so we stop the supports checks anyway. + */ + next_plugin = NULL; + } else if (info->best_plugin != existing_plugin) { + /* If this port hasn't yet been handled by the right plugin, stop + * asking all other plugins if they support this port, just let the + * plugin that handles this port's physical device see if it + * supports it. + */ + next_plugin = existing_plugin; + } else { + g_debug ("(%s/%s): plugin %p (%s) existing %p (%s) info->best %p (%s)", + info->subsys, info->name, + plugin, + plugin ? mm_plugin_get_name (plugin) : "none", + existing_plugin, + existing_plugin ? mm_plugin_get_name (existing_plugin) : "none", + info->best_plugin, + info->best_plugin ? mm_plugin_get_name (info->best_plugin) : "none"); + g_assert_not_reached (); + } + } else { + info->cur_plugin = g_slist_next (info->cur_plugin); + if (info->cur_plugin) + next_plugin = MM_PLUGIN (info->cur_plugin->data); + } + + /* Don't bother with Generic if some other plugin already supports this port */ + if (next_plugin) { + const char *next_name = mm_plugin_get_name (next_plugin); + + if (info->best_plugin && !strcmp (next_name, MM_PLUGIN_GENERIC_NAME)) + next_plugin = NULL; + } if (next_plugin) { /* Try the next plugin */ - try_supports_port (info->manager, next_plugin, info->subsys, info->name, info); + try_supports_port (info->manager, next_plugin, existing, info); } else { /* All done; let the best modem grab the port */ info->done_id = g_idle_add (do_grab_port, info); } } +static GUdevDevice * +find_physical_device (GUdevDevice *child) +{ + GUdevDevice *iter, *old = NULL; + GUdevDevice *physdev = NULL; + const char *subsys, *type; + guint32 i = 0; + gboolean is_usb = FALSE, is_pci = FALSE, is_pcmcia = FALSE, is_platform = FALSE; + + g_return_val_if_fail (child != NULL, NULL); + + iter = g_object_ref (child); + while (iter && i++ < 8) { + subsys = g_udev_device_get_subsystem (iter); + if (subsys) { + if (is_usb || !strcmp (subsys, "usb")) { + is_usb = TRUE; + type = g_udev_device_get_devtype (iter); + if (type && !strcmp (type, "usb_device")) { + physdev = iter; + break; + } + } else if (is_pcmcia || !strcmp (subsys, "pcmcia")) { + GUdevDevice *pcmcia_parent; + const char *tmp_subsys; + + is_pcmcia = TRUE; + + /* If the parent of this PCMCIA device is no longer part of + * the PCMCIA subsystem, we want to stop since we're looking + * for the base PCMCIA device, not the PCMCIA controller which + * is usually PCI or some other bus type. + */ + pcmcia_parent = g_udev_device_get_parent (iter); + if (pcmcia_parent) { + tmp_subsys = g_udev_device_get_subsystem (pcmcia_parent); + if (tmp_subsys && strcmp (tmp_subsys, "pcmcia")) + physdev = iter; + g_object_unref (pcmcia_parent); + if (physdev) + break; + } + } else if (is_platform || !strcmp (subsys, "platform")) { + /* Take the first platform parent as the physical device */ + is_platform = TRUE; + physdev = iter; + break; + } else if (is_pci || !strcmp (subsys, "pci")) { + is_pci = TRUE; + physdev = iter; + break; + } + } + + old = iter; + iter = g_udev_device_get_parent (old); + g_object_unref (old); + } + + return physdev; +} + static void device_added (MMManager *manager, GUdevDevice *device) { MMManagerPrivate *priv = MM_MANAGER_GET_PRIVATE (manager); - const char *subsys, *name; + const char *subsys, *name, *physdev_path, *physdev_subsys; SupportsInfo *info; char *key; gboolean found; + GUdevDevice *physdev = NULL; + MMPlugin *plugin; + MMModem *existing; g_return_if_fail (device != NULL); @@ -517,20 +730,77 @@ device_added (MMManager *manager, GUdevDevice *device) subsys = g_udev_device_get_subsystem (device); name = g_udev_device_get_name (device); + /* ignore VTs */ + if (strncmp (name, "tty", 3) == 0 && isdigit (name[3])) + return; + if (find_modem_for_port (manager, subsys, name)) return; key = get_key (subsys, name); found = !!g_hash_table_lookup (priv->supports, key); - if (found) { - g_free (key); - return; + if (found) + goto out; + + /* Find the port's physical device's sysfs path. This is the kernel device + * that "owns" all the ports of the device, like the USB device or the PCI + * device the provides each tty or network port. + */ + physdev = find_physical_device (device); + if (!physdev) { + /* Warn about it, but filter out some common ports that we know don't have + * anything to do with mobile broadband. + */ + if ( strcmp (name, "console") + && strcmp (name, "ptmx") + && strcmp (name, "lo") + && strcmp (name, "tty") + && !strstr (name, "virbr")) + g_debug ("(%s/%s): could not get port's parent device", subsys, name); + + goto out; + } + + /* Is the device blacklisted? */ + if (g_udev_device_get_property_as_boolean (physdev, "ID_MM_DEVICE_IGNORE")) { + g_debug ("(%s/%s): port's parent device is blacklisted", subsys, name); + goto out; + } + + /* If the physdev is a 'platform' device that's not whitelisted, ignore it */ + physdev_subsys = g_udev_device_get_subsystem (physdev); + if ( physdev_subsys + && !strcmp (physdev_subsys, "platform") + && !g_udev_device_get_property_as_boolean (physdev, "ID_MM_PLATFORM_DRIVER_PROBE")) { + g_debug ("(%s/%s): port's parent platform driver is not whitelisted", subsys, name); + goto out; } - info = supports_info_new (manager, subsys, name); - g_hash_table_insert (priv->supports, key, info); + physdev_path = g_udev_device_get_sysfs_path (physdev); + if (!physdev_path) { + g_debug ("(%s/%s): could not get port's parent device sysfs path", subsys, name); + goto out; + } + + /* Success; now ask plugins if they can handle this port */ + info = supports_info_new (manager, subsys, name, physdev_path); + g_hash_table_insert (priv->supports, g_strdup (key), info); + + /* If this port's physical modem is already owned by a plugin, don't bother + * asking all plugins whether they support this port, just let the owning + * plugin check if it supports the port. + */ + plugin = MM_PLUGIN (info->cur_plugin->data); + existing = find_modem_for_device (manager, physdev_path); + if (existing) + plugin = MM_PLUGIN (g_object_get_data (G_OBJECT (existing), MANAGER_PLUGIN_TAG)); - try_supports_port (manager, MM_PLUGIN (info->cur_plugin->data), subsys, name, info); + try_supports_port (manager, plugin, existing, info); + +out: + if (physdev) + g_object_unref (physdev); + g_free (key); } static void @@ -628,12 +898,83 @@ mm_manager_start (MMManager *manager) priv = MM_MANAGER_GET_PRIVATE (manager); devices = g_udev_client_query_by_subsystem (priv->udev, "tty"); - for (iter = devices; iter; iter = g_list_next (iter)) + for (iter = devices; iter; iter = g_list_next (iter)) { device_added (manager, G_UDEV_DEVICE (iter->data)); + g_object_unref (G_OBJECT (iter->data)); + } + g_list_free (devices); devices = g_udev_client_query_by_subsystem (priv->udev, "net"); - for (iter = devices; iter; iter = g_list_next (iter)) + for (iter = devices; iter; iter = g_list_next (iter)) { device_added (manager, G_UDEV_DEVICE (iter->data)); + g_object_unref (G_OBJECT (iter->data)); + } + g_list_free (devices); +} + +typedef struct { + MMManager *manager; + MMModem *modem; +} RemoveInfo; + +static gboolean +remove_disable_one (gpointer user_data) +{ + RemoveInfo *info = user_data; + + remove_modem (info->manager, info->modem); + g_free (info); + return FALSE; +} + +static void +remove_disable_done (MMModem *modem, + GError *error, + gpointer user_data) +{ + RemoveInfo *info; + + /* Schedule modem removal from an idle handler since we get here deep + * in the modem removal callchain and can't remove it quite yet from here. + */ + info = g_malloc0 (sizeof (RemoveInfo)); + info->manager = MM_MANAGER (user_data); + info->modem = modem; + g_idle_add (remove_disable_one, info); +} + +void +mm_manager_shutdown (MMManager *self) +{ + GList *modems, *iter; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MANAGER (self)); + + modems = g_hash_table_get_values (MM_MANAGER_GET_PRIVATE (self)->modems); + for (iter = modems; iter; iter = g_list_next (iter)) { + MMModem *modem = MM_MODEM (iter->data); + + if (mm_modem_get_state (modem) >= MM_MODEM_STATE_ENABLING) + mm_modem_disable (modem, remove_disable_done, self); + else + remove_disable_done (modem, NULL, self); + } + g_list_free (modems); + + /* Disabling may take a few iterations of the mainloop, so the caller + * has to iterate the mainloop until all devices have been disabled and + * removed. + */ +} + +guint32 +mm_manager_num_modems (MMManager *self) +{ + g_return_val_if_fail (self != NULL, 0); + g_return_val_if_fail (MM_IS_MANAGER (self), 0); + + return g_hash_table_size (MM_MANAGER_GET_PRIVATE (self)->modems); } static void diff --git a/src/mm-manager.h b/src/mm-manager.h index 5220b77..1c98458 100644 --- a/src/mm-manager.h +++ b/src/mm-manager.h @@ -50,4 +50,8 @@ MMManager *mm_manager_new (DBusGConnection *bus); void mm_manager_start (MMManager *manager); +void mm_manager_shutdown (MMManager *manager); + +guint32 mm_manager_num_modems (MMManager *manager); + #endif /* MM_MANAGER_H */ diff --git a/src/mm-modem-base.c b/src/mm-modem-base.c index 3d82f8e..0c39d2c 100644 --- a/src/mm-modem-base.c +++ b/src/mm-modem-base.c @@ -21,10 +21,13 @@ #include "mm-modem-base.h" #include "mm-modem.h" -#include "mm-serial-port.h" +#include "mm-at-serial-port.h" +#include "mm-qcdm-serial-port.h" #include "mm-errors.h" #include "mm-options.h" #include "mm-properties-changed-signal.h" +#include "mm-callback-info.h" +#include "mm-modem-helpers.h" static void modem_init (MMModem *modem_class); @@ -39,10 +42,19 @@ typedef struct { char *driver; char *plugin; char *device; + char *equipment_ident; + char *unlock_required; + guint32 unlock_retries; guint32 ip_method; gboolean valid; MMModemState state; + char *manf; + char *model; + char *revision; + + MMAuthProvider *authp; + GHashTable *ports; } MMModemBasePrivate; @@ -92,7 +104,7 @@ mm_modem_base_add_port (MMModemBase *self, { MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); MMPort *port = NULL; - char *key; + char *key, *device; g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); g_return_val_if_fail (subsys != NULL, NULL); @@ -111,9 +123,12 @@ mm_modem_base_add_port (MMModemBase *self, g_return_val_if_fail (port == NULL, FALSE); } - if (!strcmp (subsys, "tty")) - port = MM_PORT (mm_serial_port_new (name, ptype)); - else if (!strcmp (subsys, "net")) { + if (!strcmp (subsys, "tty")) { + if (ptype == MM_PORT_TYPE_QCDM) + port = MM_PORT (mm_qcdm_serial_port_new (name, ptype)); + else + port = MM_PORT (mm_at_serial_port_new (name, ptype)); + } else if (!strcmp (subsys, "net")) { port = MM_PORT (g_object_new (MM_TYPE_PORT, MM_PORT_DEVICE, name, MM_PORT_SUBSYS, MM_PORT_SUBSYS_NET, @@ -124,6 +139,15 @@ mm_modem_base_add_port (MMModemBase *self, if (!port) return NULL; + if (mm_options_debug ()) { + device = mm_modem_get_device (MM_MODEM (self)); + + g_message ("(%s) type %s claimed by %s", + name, + mm_port_type_to_name (ptype), + device); + g_free (device); + } key = get_hash_key (subsys, name); g_hash_table_insert (priv->ports, key, port); return port; @@ -169,6 +193,362 @@ mm_modem_base_get_valid (MMModemBase *self) return MM_MODEM_BASE_GET_PRIVATE (self)->valid; } +const char * +mm_modem_base_get_equipment_identifier (MMModemBase *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); + + return MM_MODEM_BASE_GET_PRIVATE (self)->equipment_ident; +} + +void +mm_modem_base_set_equipment_identifier (MMModemBase *self, const char *ident) +{ + MMModemBasePrivate *priv; + const char *dbus_path; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + + /* Only do something if the value changes */ + if ( (priv->equipment_ident == ident) + || (priv->equipment_ident && ident && !strcmp (priv->equipment_ident, ident))) + return; + + g_free (priv->equipment_ident); + priv->equipment_ident = g_strdup (ident); + + dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG); + if (dbus_path) { + if (priv->equipment_ident) + g_message ("Modem %s: Equipment identifier set (%s)", dbus_path, priv->equipment_ident); + else + g_message ("Modem %s: Equipment identifier not set", dbus_path); + } + + g_object_notify (G_OBJECT (self), MM_MODEM_EQUIPMENT_IDENTIFIER); +} + +const char * +mm_modem_base_get_unlock_required (MMModemBase *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); + + return MM_MODEM_BASE_GET_PRIVATE (self)->unlock_required; +} + +void +mm_modem_base_set_unlock_required (MMModemBase *self, const char *unlock_required) +{ + MMModemBasePrivate *priv; + const char *dbus_path; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + + /* Only do something if the value changes */ + if ( (priv->unlock_required == unlock_required) + || ( priv->unlock_required + && unlock_required + && !strcmp (priv->unlock_required, unlock_required))) + return; + + g_free (priv->unlock_required); + priv->unlock_required = g_strdup (unlock_required); + + dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG); + if (dbus_path) { + if (priv->unlock_required) + g_message ("Modem %s: unlock required (%s)", dbus_path, priv->unlock_required); + else + g_message ("Modem %s: unlock no longer required", dbus_path); + } + + g_object_notify (G_OBJECT (self), MM_MODEM_UNLOCK_REQUIRED); +} + +guint32 +mm_modem_base_get_unlock_retries (MMModemBase *self) +{ + g_return_val_if_fail (self != NULL, 0); + g_return_val_if_fail (MM_IS_MODEM_BASE (self), 0); + + return MM_MODEM_BASE_GET_PRIVATE (self)->unlock_retries; +} + +void +mm_modem_base_set_unlock_retries (MMModemBase *self, guint unlock_retries) +{ + MMModemBasePrivate *priv; + const char *dbus_path; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + + /* Only do something if the value changes */ + if (priv->unlock_retries == unlock_retries) + return; + + priv->unlock_retries = unlock_retries; + + dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG); + if (dbus_path) { + g_message ("Modem %s: # unlock retries for %s is %d", + dbus_path, priv->unlock_required, priv->unlock_retries); + } + + g_object_notify (G_OBJECT (self), MM_MODEM_UNLOCK_RETRIES); +} + +const char * +mm_modem_base_get_manf (MMModemBase *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); + + return MM_MODEM_BASE_GET_PRIVATE (self)->manf; +} + +void +mm_modem_base_set_manf (MMModemBase *self, const char *manf) +{ + MMModemBasePrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + g_free (priv->manf); + priv->manf = g_strdup (manf); +} + +const char * +mm_modem_base_get_model (MMModemBase *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); + + return MM_MODEM_BASE_GET_PRIVATE (self)->model; +} + +void +mm_modem_base_set_model (MMModemBase *self, const char *model) +{ + MMModemBasePrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + g_free (priv->model); + priv->model = g_strdup (model); +} + +const char * +mm_modem_base_get_revision (MMModemBase *self) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (MM_IS_MODEM_BASE (self), NULL); + + return MM_MODEM_BASE_GET_PRIVATE (self)->revision; +} + +void +mm_modem_base_set_revision (MMModemBase *self, const char *revision) +{ + MMModemBasePrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + g_free (priv->revision); + priv->revision = g_strdup (revision); +} + +/*************************************************************************/ +static void +card_info_simple_invoke (MMCallbackInfo *info) +{ + MMModemBase *self = MM_MODEM_BASE (info->modem); + MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); + MMModemInfoFn callback = (MMModemInfoFn) info->callback; + + callback (info->modem, priv->manf, priv->model, priv->revision, info->error, info->user_data); +} + +static void +card_info_cache_invoke (MMCallbackInfo *info) +{ + MMModemBase *self = MM_MODEM_BASE (info->modem); + MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); + MMModemInfoFn callback = (MMModemInfoFn) info->callback; + const char *manf, *cmanf, *model, *cmodel, *rev, *crev; + + manf = mm_callback_info_get_data (info, "card-info-manf"); + cmanf = mm_callback_info_get_data (info, "card-info-c-manf"); + + model = mm_callback_info_get_data (info, "card-info-model"); + cmodel = mm_callback_info_get_data (info, "card-info-c-model"); + + rev = mm_callback_info_get_data (info, "card-info-revision"); + crev = mm_callback_info_get_data (info, "card-info-c-revision"); + + /* Prefer the 'C' responses over the plain responses */ + g_free (priv->manf); + priv->manf = g_strdup (cmanf ? cmanf : manf); + g_free (priv->model); + priv->model = g_strdup (cmodel ? cmodel : model); + g_free (priv->revision); + priv->revision = g_strdup (crev ? crev : rev); + + callback (info->modem, priv->manf, priv->model, priv->revision, info->error, info->user_data); +} + +static void +info_item_done (MMCallbackInfo *info, + GString *response, + GError *error, + const char *tag, + const char *desc) +{ + const char *p; + + if (!error) { + p = mm_strip_tag (response->str, tag); + mm_callback_info_set_data (info, desc, strlen (p) ? g_strdup (p) : NULL, g_free); + } + + mm_callback_info_chain_complete_one (info); +} + +#define GET_INFO_RESP_FN(func_name, tag, desc) \ +static void \ +func_name (MMAtSerialPort *port, \ + GString *response, \ + GError *error, \ + gpointer user_data) \ +{ \ + info_item_done ((MMCallbackInfo *) user_data, response, error, tag , desc ); \ +} + +GET_INFO_RESP_FN(get_revision_done, "+GMR:", "card-info-revision") +GET_INFO_RESP_FN(get_model_done, "+GMM:", "card-info-model") +GET_INFO_RESP_FN(get_manf_done, "+GMI:", "card-info-manf") + +GET_INFO_RESP_FN(get_c_revision_done, "+CGMR:", "card-info-c-revision") +GET_INFO_RESP_FN(get_c_model_done, "+CGMM:", "card-info-c-model") +GET_INFO_RESP_FN(get_c_manf_done, "+CGMI:", "card-info-c-manf") + +void +mm_modem_base_get_card_info (MMModemBase *self, + MMAtSerialPort *port, + GError *port_error, + MMModemInfoFn callback, + gpointer user_data) +{ + MMModemBasePrivate *priv; + MMCallbackInfo *info; + MMModemState state; + gboolean cached = FALSE; + GError *error = port_error; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_MODEM_BASE (self)); + g_return_if_fail (port != NULL); + g_return_if_fail (MM_IS_AT_SERIAL_PORT (port)); + g_return_if_fail (callback != NULL); + + priv = MM_MODEM_BASE_GET_PRIVATE (self); + + /* Cached info and errors schedule the callback immediately and do + * not hit up the card for it's model information. + */ + if (priv->manf || priv->model || priv->revision) + cached = TRUE; + else { + state = mm_modem_get_state (MM_MODEM (self)); + + if (port_error) + error = g_error_copy (port_error); + else if (state < MM_MODEM_STATE_ENABLING) { + error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "The modem is not enabled."); + } + } + + /* If we have cached info or an error, don't hit up the card */ + if (cached || error) { + info = mm_callback_info_new_full (MM_MODEM (self), + card_info_simple_invoke, + G_CALLBACK (callback), + user_data); + info->error = error; + mm_callback_info_schedule (info); + return; + } + + /* Otherwise, ask the card */ + info = mm_callback_info_new_full (MM_MODEM (self), + card_info_cache_invoke, + G_CALLBACK (callback), + user_data); + + mm_callback_info_chain_start (info, 6); + mm_at_serial_port_queue_command_cached (port, "+GMI", 3, get_manf_done, info); + mm_at_serial_port_queue_command_cached (port, "+GMM", 3, get_model_done, info); + mm_at_serial_port_queue_command_cached (port, "+GMR", 3, get_revision_done, info); + mm_at_serial_port_queue_command_cached (port, "+CGMI", 3, get_c_manf_done, info); + mm_at_serial_port_queue_command_cached (port, "+CGMM", 3, get_c_model_done, info); + mm_at_serial_port_queue_command_cached (port, "+CGMR", 3, get_c_revision_done, info); +} + +/*****************************************************************************/ + +static gboolean +modem_auth_request (MMModem *modem, + const char *authorization, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify, + GError **error) +{ + MMModemBase *self = MM_MODEM_BASE (modem); + MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); + + g_assert (priv->authp); + return !!mm_auth_provider_request_auth (priv->authp, + authorization, + G_OBJECT (self), + context, + callback, + callback_data, + notify, + error); +} + +static gboolean +modem_auth_finish (MMModem *modem, MMAuthRequest *req, GError **error) +{ + if (mm_auth_request_get_result (req) != MM_AUTH_RESULT_AUTHORIZED) { + g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_AUTHORIZATION_REQUIRED, + "This request requires the '%s' authorization", + mm_auth_request_get_authorization (req)); + return FALSE; + } + + return TRUE; +} + /*****************************************************************************/ static void @@ -176,16 +556,29 @@ mm_modem_base_init (MMModemBase *self) { MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); + priv->authp = mm_auth_provider_get (); + priv->ports = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); mm_properties_changed_signal_register_property (G_OBJECT (self), MM_MODEM_ENABLED, MM_MODEM_DBUS_INTERFACE); + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_EQUIPMENT_IDENTIFIER, + MM_MODEM_DBUS_INTERFACE); + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_UNLOCK_REQUIRED, + MM_MODEM_DBUS_INTERFACE); + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_UNLOCK_RETRIES, + MM_MODEM_DBUS_INTERFACE); } static void modem_init (MMModem *modem_class) { + modem_class->auth_request = modem_auth_request; + modem_class->auth_finish = modem_auth_finish; } static gboolean @@ -227,6 +620,9 @@ set_property (GObject *object, guint prop_id, case MM_MODEM_PROP_VALID: case MM_MODEM_PROP_TYPE: case MM_MODEM_PROP_ENABLED: + case MM_MODEM_PROP_EQUIPMENT_IDENTIFIER: + case MM_MODEM_PROP_UNLOCK_REQUIRED: + case MM_MODEM_PROP_UNLOCK_RETRIES: break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -268,6 +664,15 @@ get_property (GObject *object, guint prop_id, case MM_MODEM_PROP_ENABLED: g_value_set_boolean (value, is_enabled (priv->state)); break; + case MM_MODEM_PROP_EQUIPMENT_IDENTIFIER: + g_value_set_string (value, priv->equipment_ident); + break; + case MM_MODEM_PROP_UNLOCK_REQUIRED: + g_value_set_string (value, priv->unlock_required); + break; + case MM_MODEM_PROP_UNLOCK_RETRIES: + g_value_set_uint (value, priv->unlock_retries); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -280,10 +685,14 @@ finalize (GObject *object) MMModemBase *self = MM_MODEM_BASE (object); MMModemBasePrivate *priv = MM_MODEM_BASE_GET_PRIVATE (self); + mm_auth_provider_cancel_for_owner (priv->authp, object); + g_hash_table_destroy (priv->ports); g_free (priv->driver); g_free (priv->plugin); g_free (priv->device); + g_free (priv->equipment_ident); + g_free (priv->unlock_required); G_OBJECT_CLASS (mm_modem_base_parent_class)->finalize (object); } @@ -336,6 +745,18 @@ mm_modem_base_class_init (MMModemBaseClass *klass) MM_MODEM_PROP_ENABLED, MM_MODEM_ENABLED); + g_object_class_override_property (object_class, + MM_MODEM_PROP_EQUIPMENT_IDENTIFIER, + MM_MODEM_EQUIPMENT_IDENTIFIER); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_UNLOCK_REQUIRED, + MM_MODEM_UNLOCK_REQUIRED); + + g_object_class_override_property (object_class, + MM_MODEM_PROP_UNLOCK_RETRIES, + MM_MODEM_UNLOCK_RETRIES); + mm_properties_changed_signal_new (object_class); } diff --git a/src/mm-modem-base.h b/src/mm-modem-base.h index 9eb64ce..0409957 100644 --- a/src/mm-modem-base.h +++ b/src/mm-modem-base.h @@ -22,6 +22,8 @@ #include #include "mm-port.h" +#include "mm-at-serial-port.h" +#include "mm-modem.h" #define MM_TYPE_MODEM_BASE (mm_modem_base_get_type ()) #define MM_MODEM_BASE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_BASE, MMModemBase)) @@ -60,5 +62,36 @@ void mm_modem_base_set_valid (MMModemBase *self, gboolean mm_modem_base_get_valid (MMModemBase *self); +const char *mm_modem_base_get_equipment_identifier (MMModemBase *self); + +void mm_modem_base_set_equipment_identifier (MMModemBase *self, + const char *ident); + +const char *mm_modem_base_get_unlock_required (MMModemBase *self); + +void mm_modem_base_set_unlock_required (MMModemBase *self, + const char *unlock_required); + +guint mm_modem_base_get_unlock_retries (MMModemBase *self); + +void mm_modem_base_set_unlock_retries (MMModemBase *self, + guint unlock_retries); + + +const char *mm_modem_base_get_manf (MMModemBase *self); +void mm_modem_base_set_manf (MMModemBase *self, const char *manf); + +const char *mm_modem_base_get_model (MMModemBase *self); +void mm_modem_base_set_model (MMModemBase *self, const char *model); + +const char *mm_modem_base_get_revision (MMModemBase *self); +void mm_modem_base_set_revision (MMModemBase *self, const char *revision); + +void mm_modem_base_get_card_info (MMModemBase *self, + MMAtSerialPort *port, + GError *port_error, + MMModemInfoFn callback, + gpointer user_data); + #endif /* MM_MODEM_BASE_H */ diff --git a/src/mm-modem-cdma.c b/src/mm-modem-cdma.c index 112b93f..e80dc4e 100644 --- a/src/mm-modem-cdma.c +++ b/src/mm-modem-cdma.c @@ -20,6 +20,7 @@ #include "mm-errors.h" #include "mm-callback-info.h" #include "mm-marshal.h" +#include "mm-auth-provider.h" static void impl_modem_cdma_get_signal_quality (MMModemCdma *modem, DBusGMethodInvocation *context); static void impl_modem_cdma_get_esn (MMModemCdma *modem, DBusGMethodInvocation *context); @@ -188,10 +189,38 @@ mm_modem_cdma_get_esn (MMModemCdma *self, } static void -impl_modem_cdma_get_esn (MMModemCdma *modem, - DBusGMethodInvocation *context) +esn_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) { - mm_modem_cdma_get_esn (modem, str_call_done, context); + MMModemCdma *self = MM_MODEM_CDMA (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the ESN */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_cdma_get_esn (self, str_call_done, context); +} + +static void +impl_modem_cdma_get_esn (MMModemCdma *self, DBusGMethodInvocation *context) +{ + GError *error = NULL; + + /* Make sure the caller is authorized to get the ESN */ + if (!mm_modem_auth_request (MM_MODEM (self), + MM_AUTHORIZATION_DEVICE_INFO, + context, + esn_auth_cb, + NULL, + NULL, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } void diff --git a/src/mm-modem-gsm-card.c b/src/mm-modem-gsm-card.c index dea4590..e03b964 100644 --- a/src/mm-modem-gsm-card.c +++ b/src/mm-modem-gsm-card.c @@ -15,6 +15,7 @@ */ #include +#include #include "mm-modem-gsm-card.h" #include "mm-errors.h" @@ -27,6 +28,9 @@ static void impl_gsm_modem_get_imei (MMModemGsmCard *modem, static void impl_gsm_modem_get_imsi (MMModemGsmCard *modem, DBusGMethodInvocation *context); +static void impl_gsm_modem_get_operator_id (MMModemGsmCard *modem, + DBusGMethodInvocation *context); + static void impl_gsm_modem_send_pin (MMModemGsmCard *modem, const char *pin, DBusGMethodInvocation *context); @@ -75,6 +79,19 @@ str_call_not_supported (MMModemGsmCard *self, mm_callback_info_schedule (info); } +static void +uint_call_not_supported (MMModemGsmCard *self, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_uint_new (MM_MODEM (self), callback, user_data); + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + mm_callback_info_schedule (info); +} + static void async_call_done (MMModem *modem, GError *error, gpointer user_data) { @@ -129,6 +146,35 @@ mm_modem_gsm_card_get_imsi (MMModemGsmCard *self, str_call_not_supported (self, callback, user_data); } +void mm_modem_gsm_card_get_unlock_retries (MMModemGsmCard *self, + const char *pin_type, + MMModemUIntFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_GSM_CARD (self)); + g_return_if_fail (pin_type != NULL); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_unlock_retries) + MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_unlock_retries (self, pin_type, callback, user_data); + else + uint_call_not_supported (self, callback, user_data); +} + +void +mm_modem_gsm_card_get_operator_id (MMModemGsmCard *self, + MMModemStringFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_GSM_CARD (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_operator_id) + MM_MODEM_GSM_CARD_GET_INTERFACE (self)->get_operator_id (self, callback, user_data); + else + str_call_not_supported (self, callback, user_data); +} + void mm_modem_gsm_card_send_puk (MMModemGsmCard *self, const char *puk, @@ -201,26 +247,213 @@ mm_modem_gsm_card_change_pin (MMModemGsmCard *self, /*****************************************************************************/ static void -impl_gsm_modem_get_imei (MMModemGsmCard *modem, - DBusGMethodInvocation *context) +imei_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) { - mm_modem_gsm_card_get_imei (modem, str_call_done, context); + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the IMEI */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_get_imei (self, str_call_done, context); +} + +static void +impl_gsm_modem_get_imei (MMModemGsmCard *modem, DBusGMethodInvocation *context) +{ + GError *error = NULL; + + /* Make sure the caller is authorized to get the IMEI */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_INFO, + context, + imei_auth_cb, + NULL, + NULL, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +imsi_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the IMSI */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_get_imsi (self, str_call_done, context); +} + +static void +impl_gsm_modem_get_imsi (MMModemGsmCard *modem, DBusGMethodInvocation *context) +{ + GError *error = NULL; + + /* Make sure the caller is authorized to get the IMSI */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_INFO, + context, + imsi_auth_cb, + NULL, + NULL, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +operator_id_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the operator id */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_get_operator_id (self, str_call_done, context); } static void -impl_gsm_modem_get_imsi (MMModemGsmCard *modem, +impl_gsm_modem_get_operator_id (MMModemGsmCard *modem, DBusGMethodInvocation *context) +{ + GError *error = NULL; + + /* Make sure the caller is authorized to get the operator id */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_INFO, + context, + operator_id_auth_cb, + NULL, + NULL, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +typedef struct { + char *puk; + char *pin; + char *pin2; + gboolean enabled; +} SendPinPukInfo; + +static void +send_pin_puk_info_destroy (gpointer data) +{ + SendPinPukInfo *info = data; + + g_free (info->puk); + g_free (info->pin); + g_free (info->pin2); + memset (info, 0, sizeof (SendPinPukInfo)); + g_free (info); +} + +static SendPinPukInfo * +send_pin_puk_info_new (const char *puk, + const char *pin, + const char *pin2, + gboolean enabled) +{ + SendPinPukInfo *info; + + info = g_malloc0 (sizeof (SendPinPukInfo)); + info->puk = g_strdup (puk); + info->pin = g_strdup (pin); + info->pin2 = g_strdup (pin2); + info->enabled = enabled; + return info; +} + +/*****************************************************************************/ + +static void +send_puk_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + SendPinPukInfo *info = user_data; + GError *error = NULL; + + /* Return any authorization error, otherwise send the PUK */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_send_puk (self, info->puk, info->pin, async_call_done, context); +} + +static void +impl_gsm_modem_send_puk (MMModemGsmCard *modem, + const char *puk, + const char *pin, DBusGMethodInvocation *context) { - mm_modem_gsm_card_get_imsi (modem, str_call_done, context); + GError *error = NULL; + SendPinPukInfo *info; + + info = send_pin_puk_info_new (puk, pin, NULL, FALSE); + + /* Make sure the caller is authorized to send the PUK */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_CONTROL, + context, + send_puk_auth_cb, + info, + send_pin_puk_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } +/*****************************************************************************/ + static void - impl_gsm_modem_send_puk (MMModemGsmCard *modem, - const char *puk, - const char *pin, - DBusGMethodInvocation *context) +send_pin_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) { - mm_modem_gsm_card_send_puk (modem, puk, pin, async_call_done, context); + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + SendPinPukInfo *info = user_data; + GError *error = NULL; + + /* Return any authorization error, otherwise unlock the modem */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_send_pin (self, info->pin, async_call_done, context); } static void @@ -228,7 +461,42 @@ impl_gsm_modem_send_pin (MMModemGsmCard *modem, const char *pin, DBusGMethodInvocation *context) { - mm_modem_gsm_card_send_pin (modem, pin, async_call_done, context); + GError *error = NULL; + SendPinPukInfo *info; + + info = send_pin_puk_info_new (NULL, pin, NULL, FALSE); + + /* Make sure the caller is authorized to unlock the modem */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_CONTROL, + context, + send_pin_auth_cb, + info, + send_pin_puk_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +enable_pin_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + SendPinPukInfo *info = user_data; + GError *error = NULL; + + /* Return any authorization error, otherwise enable the PIN */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_enable_pin (self, info->pin, info->enabled, async_call_done, context); } static void @@ -237,7 +505,42 @@ impl_gsm_modem_enable_pin (MMModemGsmCard *modem, gboolean enabled, DBusGMethodInvocation *context) { - mm_modem_gsm_card_enable_pin (modem, pin, enabled, async_call_done, context); + GError *error = NULL; + SendPinPukInfo *info; + + info = send_pin_puk_info_new (NULL, pin, NULL, enabled); + + /* Make sure the caller is authorized to enable a PIN */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_CONTROL, + context, + enable_pin_auth_cb, + info, + send_pin_puk_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +change_pin_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmCard *self = MM_MODEM_GSM_CARD (owner); + SendPinPukInfo *info = user_data; + GError *error = NULL; + + /* Return any authorization error, otherwise change the PIN */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_card_change_pin (self, info->pin, info->pin2, async_call_done, context); } static void @@ -246,7 +549,22 @@ impl_gsm_modem_change_pin (MMModemGsmCard *modem, const char *new_pin, DBusGMethodInvocation *context) { - mm_modem_gsm_card_change_pin (modem, old_pin, new_pin, async_call_done, context); + GError *error = NULL; + SendPinPukInfo *info; + + info = send_pin_puk_info_new (NULL, old_pin, new_pin, FALSE); + + /* Make sure the caller is authorized to change the PIN */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_CONTROL, + context, + change_pin_auth_cb, + info, + send_pin_puk_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } /*****************************************************************************/ @@ -267,7 +585,7 @@ mm_modem_gsm_card_init (gpointer g_iface) "Supported Modes", "Supported frequency bands of the card", MM_MODEM_GSM_BAND_UNKNOWN, - MM_MODEM_GSM_BAND_LAST, + G_MAXUINT32, MM_MODEM_GSM_BAND_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); @@ -277,7 +595,7 @@ mm_modem_gsm_card_init (gpointer g_iface) "Supported Modes", "Supported modes of the card (ex 2G preferred, 3G preferred, 2G only, etc", MM_MODEM_GSM_MODE_UNKNOWN, - MM_MODEM_GSM_MODE_LAST, + G_MAXUINT32, MM_MODEM_GSM_MODE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } @@ -305,6 +623,7 @@ mm_modem_gsm_card_get_type (void) &card_info, 0); g_type_interface_add_prerequisite (card_type, G_TYPE_OBJECT); + g_type_interface_add_prerequisite (card_type, MM_TYPE_MODEM); dbus_g_object_type_install_info (card_type, &dbus_glib_mm_modem_gsm_card_object_info); } diff --git a/src/mm-modem-gsm-card.h b/src/mm-modem-gsm-card.h index 4d690e6..584d734 100644 --- a/src/mm-modem-gsm-card.h +++ b/src/mm-modem-gsm-card.h @@ -27,6 +27,13 @@ #define MM_MODEM_GSM_CARD_SUPPORTED_BANDS "supported-bands" #define MM_MODEM_GSM_CARD_SUPPORTED_MODES "supported-modes" +#define MM_MODEM_GSM_CARD_SIM_PIN "sim-pin" +#define MM_MODEM_GSM_CARD_SIM_PIN2 "sim-pin2" +#define MM_MODEM_GSM_CARD_SIM_PUK "sim-puk" +#define MM_MODEM_GSM_CARD_SIM_PUK2 "sim-puk2" + +#define MM_MODEM_GSM_CARD_UNLOCK_RETRIES_NOT_SUPPORTED 999 + typedef struct _MMModemGsmCard MMModemGsmCard; struct _MMModemGsmCard { @@ -41,6 +48,15 @@ struct _MMModemGsmCard { MMModemStringFn callback, gpointer user_data); + void (*get_unlock_retries) (MMModemGsmCard *self, + const char *pin_type, + MMModemUIntFn callback, + gpointer user_data); + + void (*get_operator_id) (MMModemGsmCard *self, + MMModemStringFn callback, + gpointer user_data); + void (*send_puk) (MMModemGsmCard *self, const char *puk, const char *pin, @@ -75,6 +91,15 @@ void mm_modem_gsm_card_get_imsi (MMModemGsmCard *self, MMModemStringFn callback, gpointer user_data); +void mm_modem_gsm_card_get_unlock_retries (MMModemGsmCard *self, + const char *pin_type, + MMModemUIntFn callback, + gpointer user_data); + +void mm_modem_gsm_card_get_operator_id (MMModemGsmCard *self, + MMModemStringFn callback, + gpointer user_data); + void mm_modem_gsm_card_send_puk (MMModemGsmCard *self, const char *puk, const char *pin, diff --git a/src/mm-modem-gsm-network.c b/src/mm-modem-gsm-network.c index 26ff422..4cd6921 100644 --- a/src/mm-modem-gsm-network.c +++ b/src/mm-modem-gsm-network.c @@ -11,6 +11,7 @@ * GNU General Public License for more details: * * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2010 Red Hat, Inc. */ #include @@ -42,8 +43,12 @@ static void impl_gsm_modem_set_band (MMModemGsmNetwork *modem, static void impl_gsm_modem_get_band (MMModemGsmNetwork *modem, DBusGMethodInvocation *context); +static void impl_gsm_modem_set_allowed_mode (MMModemGsmNetwork *modem, + MMModemGsmAllowedMode mode, + DBusGMethodInvocation *context); + static void impl_gsm_modem_set_network_mode (MMModemGsmNetwork *modem, - MMModemGsmMode mode, + MMModemDeprecatedMode old_mode, DBusGMethodInvocation *context); static void impl_gsm_modem_get_network_mode (MMModemGsmNetwork *modem, @@ -68,6 +73,47 @@ static guint signals[LAST_SIGNAL] = { 0 }; /*****************************************************************************/ +MMModemGsmAllowedMode +mm_modem_gsm_network_old_mode_to_allowed (MMModemDeprecatedMode old_mode) +{ + /* Translate deprecated mode into new mode */ + switch (old_mode) { + case MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_2G_PREFERRED: + return MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED; + case MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_3G_PREFERRED: + return MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED; + case MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_2G_ONLY: + return MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY; + case MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_3G_ONLY: + return MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY; + case MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_ANY: + default: + return MM_MODEM_GSM_ALLOWED_MODE_ANY; + } +} + +MMModemDeprecatedMode +mm_modem_gsm_network_act_to_old_mode (MMModemGsmAccessTech act) +{ + /* Translate new mode into old deprecated mode */ + if (act & MM_MODEM_GSM_ACCESS_TECH_GPRS) + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_GPRS; + else if (act & MM_MODEM_GSM_ACCESS_TECH_EDGE) + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_EDGE; + else if (act & MM_MODEM_GSM_ACCESS_TECH_UMTS) + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_UMTS; + else if (act & MM_MODEM_GSM_ACCESS_TECH_HSDPA) + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSDPA; + else if (act & MM_MODEM_GSM_ACCESS_TECH_HSUPA) + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSUPA; + else if (act & MM_MODEM_GSM_ACCESS_TECH_HSPA) + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSPA; + + return MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_ANY; +} + +/*****************************************************************************/ + static void async_call_done (MMModem *modem, GError *error, gpointer user_data) { @@ -305,34 +351,20 @@ mm_modem_gsm_network_get_band (MMModemGsmNetwork *self, } void -mm_modem_gsm_network_set_mode (MMModemGsmNetwork *self, - MMModemGsmMode mode, - MMModemFn callback, - gpointer user_data) +mm_modem_gsm_network_set_allowed_mode (MMModemGsmNetwork *self, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data) { g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self)); g_return_if_fail (callback != NULL); - if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_network_mode) - MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_network_mode (self, mode, callback, user_data); + if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_allowed_mode) + MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->set_allowed_mode (self, mode, callback, user_data); else async_call_not_supported (self, callback, user_data); } -void -mm_modem_gsm_network_get_mode (MMModemGsmNetwork *self, - MMModemUIntFn callback, - gpointer user_data) -{ - g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self)); - g_return_if_fail (callback != NULL); - - if (MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_network_mode) - MM_MODEM_GSM_NETWORK_GET_INTERFACE (self)->get_network_mode (self, callback, user_data); - else - uint_call_not_supported (self, callback, user_data); -} - void mm_modem_gsm_network_get_registration_info (MMModemGsmNetwork *self, MMModemGsmNetworkRegInfoFn callback, @@ -369,15 +401,6 @@ mm_modem_gsm_network_registration_info (MMModemGsmNetwork *self, oper_name ? oper_name : ""); } -void -mm_modem_gsm_network_mode (MMModemGsmNetwork *self, - MMModemGsmMode mode) -{ - g_return_if_fail (MM_IS_MODEM_GSM_NETWORK (self)); - - g_signal_emit (self, signals[NETWORK_MODE], 0, mode); -} - /*****************************************************************************/ static void @@ -397,11 +420,40 @@ impl_gsm_modem_register (MMModemGsmNetwork *modem, mm_modem_gsm_network_register (modem, id, async_call_done, context); } +static void +scan_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmNetwork *self = MM_MODEM_GSM_NETWORK (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the IMEI */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_gsm_network_scan (self, scan_call_done, context); +} + static void impl_gsm_modem_scan (MMModemGsmNetwork *modem, DBusGMethodInvocation *context) { - mm_modem_gsm_network_scan (modem, scan_call_done, context); + GError *error = NULL; + + /* Make sure the caller is authorized to request a scan */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_CONTROL, + context, + scan_auth_cb, + NULL, + NULL, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } static void @@ -464,10 +516,10 @@ impl_gsm_modem_get_band (MMModemGsmNetwork *modem, static void impl_gsm_modem_set_network_mode (MMModemGsmNetwork *modem, - MMModemGsmMode mode, + MMModemDeprecatedMode old_mode, DBusGMethodInvocation *context) { - if (!check_for_single_value (mode)) { + if (!check_for_single_value (old_mode)) { GError *error; error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, @@ -477,14 +529,41 @@ impl_gsm_modem_set_network_mode (MMModemGsmNetwork *modem, return; } - mm_modem_gsm_network_set_mode (modem, mode, async_call_done, context); + mm_modem_gsm_network_set_allowed_mode (modem, + mm_modem_gsm_network_old_mode_to_allowed (old_mode), + async_call_done, + context); +} + +static void +impl_gsm_modem_set_allowed_mode (MMModemGsmNetwork *modem, + MMModemGsmAllowedMode mode, + DBusGMethodInvocation *context) +{ + if (mode > MM_MODEM_GSM_ALLOWED_MODE_LAST) { + GError *error; + + error = g_error_new (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Unknown allowed mode %d", mode); + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + mm_modem_gsm_network_set_allowed_mode (modem, mode, async_call_done, context); } static void impl_gsm_modem_get_network_mode (MMModemGsmNetwork *modem, DBusGMethodInvocation *context) { - mm_modem_gsm_network_get_mode (modem, uint_call_done, context); + MMModemGsmAccessTech act = MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; + + /* DEPRECATED; it's now a property so it's quite easy to handle */ + g_object_get (G_OBJECT (modem), + MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY, &act, + NULL); + dbus_g_method_return (context, mm_modem_gsm_network_act_to_old_mode (act)); } static void @@ -505,6 +584,28 @@ mm_modem_gsm_network_init (gpointer g_iface) if (initialized) return; + /* Properties */ + g_object_interface_install_property + (g_iface, + g_param_spec_uint (MM_MODEM_GSM_NETWORK_ALLOWED_MODE, + "Allowed Mode", + "Allowed network access mode", + MM_MODEM_GSM_ALLOWED_MODE_ANY, + MM_MODEM_GSM_ALLOWED_MODE_LAST, + MM_MODEM_GSM_ALLOWED_MODE_ANY, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_uint (MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY, + "Access Technology", + "Current access technology in use when connected to " + "a mobile network.", + MM_MODEM_GSM_ACCESS_TECH_UNKNOWN, + MM_MODEM_GSM_ACCESS_TECH_LAST, + MM_MODEM_GSM_ACCESS_TECH_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + /* Signals */ signals[SIGNAL_QUALITY] = g_signal_new ("signal-quality", @@ -530,8 +631,7 @@ mm_modem_gsm_network_init (gpointer g_iface) g_signal_new ("network-mode", iface_type, G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (MMModemGsmNetwork, network_mode), - NULL, NULL, + 0, NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); @@ -562,6 +662,7 @@ mm_modem_gsm_network_get_type (void) &network_info, 0); g_type_interface_add_prerequisite (network_type, G_TYPE_OBJECT); + g_type_interface_add_prerequisite (network_type, MM_TYPE_MODEM); dbus_g_object_type_install_info (network_type, &dbus_glib_mm_modem_gsm_network_object_info); } diff --git a/src/mm-modem-gsm-network.h b/src/mm-modem-gsm-network.h index 493baec..a515789 100644 --- a/src/mm-modem-gsm-network.h +++ b/src/mm-modem-gsm-network.h @@ -11,7 +11,7 @@ * GNU General Public License for more details: * * Copyright (C) 2008 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #ifndef MM_MODEM_GSM_NETWORK_H @@ -20,11 +20,23 @@ #include #include +#define MM_MODEM_GSM_NETWORK_DBUS_INTERFACE "org.freedesktop.ModemManager.Modem.Gsm.Network" + #define MM_TYPE_MODEM_GSM_NETWORK (mm_modem_gsm_network_get_type ()) #define MM_MODEM_GSM_NETWORK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_GSM_NETWORK, MMModemGsmNetwork)) #define MM_IS_MODEM_GSM_NETWORK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_GSM_NETWORK)) #define MM_MODEM_GSM_NETWORK_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_GSM_NETWORK, MMModemGsmNetwork)) +#define MM_MODEM_GSM_NETWORK_ALLOWED_MODE "allowed-mode" +#define MM_MODEM_GSM_NETWORK_ACCESS_TECHNOLOGY "access-technology" + +typedef enum { + MM_MODEM_GSM_NETWORK_PROP_FIRST = 0x1200, + + MM_MODEM_GSM_NETWORK_PROP_ALLOWED_MODE = MM_MODEM_GSM_NETWORK_PROP_FIRST, + MM_MODEM_GSM_NETWORK_PROP_ACCESS_TECHNOLOGY, +} MMModemGsmNetworkProp; + typedef enum { MM_MODEM_GSM_NETWORK_REG_STATUS_IDLE = 0, MM_MODEM_GSM_NETWORK_REG_STATUS_HOME = 1, @@ -80,15 +92,11 @@ struct _MMModemGsmNetwork { MMModemUIntFn callback, gpointer user_data); - void (*set_network_mode) (MMModemGsmNetwork *self, - MMModemGsmMode mode, + void (*set_allowed_mode) (MMModemGsmNetwork *self, + MMModemGsmAllowedMode mode, MMModemFn callback, gpointer user_data); - void (*get_network_mode) (MMModemGsmNetwork *self, - MMModemUIntFn callback, - gpointer user_data); - void (*get_registration_info) (MMModemGsmNetwork *self, MMModemGsmNetworkRegInfoFn callback, gpointer user_data); @@ -101,9 +109,6 @@ struct _MMModemGsmNetwork { MMModemGsmNetworkRegStatus status, const char *open_code, const char *oper_name); - - void (*network_mode) (MMModemGsmNetwork *self, - MMModemGsmMode mode); }; GType mm_modem_gsm_network_get_type (void); @@ -135,15 +140,6 @@ void mm_modem_gsm_network_get_band (MMModemGsmNetwork *self, MMModemUIntFn callback, gpointer user_data); -void mm_modem_gsm_network_set_mode (MMModemGsmNetwork *self, - MMModemGsmMode mode, - MMModemFn callback, - gpointer user_data); - -void mm_modem_gsm_network_get_mode (MMModemGsmNetwork *self, - MMModemUIntFn callback, - gpointer user_data); - void mm_modem_gsm_network_get_registration_info (MMModemGsmNetwork *self, MMModemGsmNetworkRegInfoFn callback, gpointer user_data); @@ -153,12 +149,19 @@ void mm_modem_gsm_network_get_registration_info (MMModemGsmNetwork *self, void mm_modem_gsm_network_signal_quality (MMModemGsmNetwork *self, guint32 quality); +void mm_modem_gsm_network_set_allowed_mode (MMModemGsmNetwork *self, + MMModemGsmAllowedMode mode, + MMModemFn callback, + gpointer user_data); + void mm_modem_gsm_network_registration_info (MMModemGsmNetwork *self, MMModemGsmNetworkRegStatus status, const char *oper_code, const char *oper_name); -void mm_modem_gsm_network_mode (MMModemGsmNetwork *self, - MMModemGsmMode mode); +/* Private */ +MMModemDeprecatedMode mm_modem_gsm_network_act_to_old_mode (MMModemGsmAccessTech act); + +MMModemGsmAllowedMode mm_modem_gsm_network_old_mode_to_allowed (MMModemDeprecatedMode old_mode); #endif /* MM_MODEM_GSM_NETWORK_H */ diff --git a/src/mm-modem-gsm-sms.c b/src/mm-modem-gsm-sms.c index e8ec074..d74c7b3 100644 --- a/src/mm-modem-gsm-sms.c +++ b/src/mm-modem-gsm-sms.c @@ -129,12 +129,138 @@ mm_modem_gsm_sms_send (MMModemGsmSms *self, /*****************************************************************************/ +typedef struct { + guint num1; + guint num2; + guint num3; + guint num4; + guint num5; + char *str; + GHashTable *hash; +} SmsAuthInfo; + +static void +sms_auth_info_destroy (gpointer data) +{ + SmsAuthInfo *info = data; + + g_hash_table_destroy (info->hash); + g_free (info->str); + memset (info, 0, sizeof (SmsAuthInfo)); + g_free (info); +} + +static void +destroy_gvalue (gpointer data) +{ + GValue *value = (GValue *) data; + + g_value_unset (value); + g_slice_free (GValue, value); +} + +static SmsAuthInfo * +sms_auth_info_new (guint num1, + guint num2, + guint num3, + guint num4, + guint num5, + const char *str, + GHashTable *hash) +{ + SmsAuthInfo *info; + + info = g_malloc0 (sizeof (SmsAuthInfo)); + info->num1 = num1; + info->num2 = num2; + info->num3 = num3; + info->num4 = num4; + info->num5 = num5; + info->str = g_strdup (str); + + /* Copy the hash table if we're given one */ + if (hash) { + GHashTableIter iter; + gpointer key, value; + + info->hash = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, destroy_gvalue); + + g_hash_table_iter_init (&iter, hash); + while (g_hash_table_iter_next (&iter, &key, &value)) { + const char *str_key = (const char *) key; + GValue *src = (GValue *) value; + GValue *dst; + + dst = g_slice_new0 (GValue); + g_value_init (dst, G_VALUE_TYPE (src)); + g_value_copy (src, dst); + g_hash_table_insert (info->hash, g_strdup (str_key), dst); + } + } + + return info; +} + +/*****************************************************************************/ + +static void +sms_delete_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise delete the SMS */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); +} + static void impl_gsm_modem_sms_delete (MMModemGsmSms *modem, guint idx, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (idx, 0, 0, 0, 0, NULL, NULL); + + /* Make sure the caller is authorized to delete an SMS */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_delete_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +sms_get_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the SMS */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); } static void @@ -142,9 +268,26 @@ impl_gsm_modem_sms_get (MMModemGsmSms *modem, guint idx, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (idx, 0, 0, 0, 0, NULL, NULL); + + /* Make sure the caller is authorized to get an SMS */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_get_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } +/*****************************************************************************/ + static void impl_gsm_modem_sms_get_format (MMModemGsmSms *modem, DBusGMethodInvocation *context) @@ -167,19 +310,103 @@ impl_gsm_modem_sms_get_smsc (MMModemGsmSms *modem, async_call_not_supported (modem, async_call_done, context); } +/*****************************************************************************/ + +static void +sms_set_smsc_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise set the SMS service center */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); +} + static void impl_gsm_modem_sms_set_smsc (MMModemGsmSms *modem, const char *smsc, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (0, 0, 0, 0, 0, smsc, NULL); + + /* Make sure the caller is authorized to set the SMS service center */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_set_smsc_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +sms_list_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise list SMSs */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); } static void impl_gsm_modem_sms_list (MMModemGsmSms *modem, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + + /* Make sure the caller is authorized to list SMSs */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_list_auth_cb, + NULL, + NULL, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +sms_save_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise save the SMS */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); } static void @@ -187,54 +414,122 @@ impl_gsm_modem_sms_save (MMModemGsmSms *modem, GHashTable *properties, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (0, 0, 0, 0, 0, NULL, properties); + + /* Make sure the caller is authorized to save the SMS */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_save_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } +/*****************************************************************************/ + static void -impl_gsm_modem_sms_send (MMModemGsmSms *modem, - GHashTable *properties, - DBusGMethodInvocation *context) +sms_send_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) { + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + SmsAuthInfo *info = user_data; + GError *error = NULL; GValue *value; const char *number = NULL; const char *text = NULL ; const char *smsc = NULL; - GError *error = NULL; guint validity = 0; guint class = 0; - value = (GValue *) g_hash_table_lookup (properties, "number"); + /* Return any authorization error, otherwise delete the SMS */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) + goto done; + + value = (GValue *) g_hash_table_lookup (info->hash, "number"); if (value) number = g_value_get_string (value); - value = (GValue *) g_hash_table_lookup (properties, "text"); + value = (GValue *) g_hash_table_lookup (info->hash, "text"); if (value) text = g_value_get_string (value); - value = (GValue *) g_hash_table_lookup (properties, "smsc"); + value = (GValue *) g_hash_table_lookup (info->hash, "smsc"); if (value) smsc = g_value_get_string (value); - value = (GValue *) g_hash_table_lookup (properties, "validity"); + value = (GValue *) g_hash_table_lookup (info->hash, "validity"); if (value) validity = g_value_get_uint (value); - value = (GValue *) g_hash_table_lookup (properties, "class"); + value = (GValue *) g_hash_table_lookup (info->hash, "class"); if (value) class = g_value_get_uint (value); - if (!number) + if (!number) { error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Missing number"); - else if (!text) + } else if (!text) { error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Missing message text"); + } +done: if (error) { - async_call_done (MM_MODEM (modem), error, context); + async_call_done (MM_MODEM (self), error, context); g_error_free (error); } else - mm_modem_gsm_sms_send (modem, number, text, smsc, validity, class, async_call_done, context); + mm_modem_gsm_sms_send (self, number, text, smsc, validity, class, async_call_done, context); +} + +static void +impl_gsm_modem_sms_send (MMModemGsmSms *modem, + GHashTable *properties, + DBusGMethodInvocation *context) +{ + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (0, 0, 0, 0, 0, NULL, properties); + + /* Make sure the caller is authorized to send the PUK */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_send_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +sms_send_from_storage_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise delete the SMS */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); } static void @@ -242,7 +537,41 @@ impl_gsm_modem_sms_send_from_storage (MMModemGsmSms *modem, guint idx, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (idx, 0, 0, 0, 0, NULL, NULL); + + /* Make sure the caller is authorized to send the PUK */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_send_from_storage_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +sms_set_indication_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemGsmSms *self = MM_MODEM_GSM_SMS (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise delete the SMS */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + async_call_not_supported (self, async_call_done, context); } static void @@ -254,7 +583,22 @@ impl_gsm_modem_sms_set_indication (MMModemGsmSms *modem, guint bfr, DBusGMethodInvocation *context) { - async_call_not_supported (modem, async_call_done, context); + GError *error = NULL; + SmsAuthInfo *info; + + info = sms_auth_info_new (mode, mt, bm, ds, bfr, NULL, NULL); + + /* Make sure the caller is authorized to send the PUK */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_SMS, + context, + sms_set_indication_auth_cb, + info, + sms_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } } /*****************************************************************************/ diff --git a/src/mm-modem-gsm.h b/src/mm-modem-gsm.h index 852ff85..6d9135a 100644 --- a/src/mm-modem-gsm.h +++ b/src/mm-modem-gsm.h @@ -30,10 +30,34 @@ typedef enum { MM_MODEM_GSM_MODE_3G_ONLY = 0x00000100, MM_MODEM_GSM_MODE_HSUPA = 0x00000200, MM_MODEM_GSM_MODE_HSPA = 0x00000400, - - MM_MODEM_GSM_MODE_LAST = MM_MODEM_GSM_MODE_HSPA + MM_MODEM_GSM_MODE_GSM = 0x00000800, + MM_MODEM_GSM_MODE_GSM_COMPACT = 0x00001000, } MMModemGsmMode; +typedef enum { + MM_MODEM_GSM_ALLOWED_MODE_ANY = 0, + MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED = 1, + MM_MODEM_GSM_ALLOWED_MODE_3G_PREFERRED = 2, + MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY = 3, + MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY = 4, + + MM_MODEM_GSM_ALLOWED_MODE_LAST = MM_MODEM_GSM_ALLOWED_MODE_3G_ONLY +} MMModemGsmAllowedMode; + +typedef enum { + MM_MODEM_GSM_ACCESS_TECH_UNKNOWN = 0, + MM_MODEM_GSM_ACCESS_TECH_GSM = 1, + MM_MODEM_GSM_ACCESS_TECH_GSM_COMPACT = 2, + MM_MODEM_GSM_ACCESS_TECH_GPRS = 3, + MM_MODEM_GSM_ACCESS_TECH_EDGE = 4, /* GSM w/EGPRS */ + MM_MODEM_GSM_ACCESS_TECH_UMTS = 5, /* UTRAN */ + MM_MODEM_GSM_ACCESS_TECH_HSDPA = 6, /* UTRAN w/HSDPA */ + MM_MODEM_GSM_ACCESS_TECH_HSUPA = 7, /* UTRAN w/HSUPA */ + MM_MODEM_GSM_ACCESS_TECH_HSPA = 8, /* UTRAN w/HSDPA and HSUPA */ + + MM_MODEM_GSM_ACCESS_TECH_LAST = MM_MODEM_GSM_ACCESS_TECH_HSPA +} MMModemGsmAccessTech; + typedef enum { MM_MODEM_GSM_BAND_UNKNOWN = 0x00000000, MM_MODEM_GSM_BAND_ANY = 0x00000001, @@ -48,10 +72,26 @@ typedef enum { MM_MODEM_GSM_BAND_U850 = 0x00000200, /* WCDMA 3GPP UMTS 850 MHz (Class V) */ MM_MODEM_GSM_BAND_U900 = 0x00000400, /* WCDMA 3GPP UMTS 900 MHz (Class VIII) */ MM_MODEM_GSM_BAND_U17IX = 0x00000800, /* WCDMA 3GPP UMTS 1700 MHz (Class IX) */ + MM_MODEM_GSM_BAND_U1900 = 0x00001000, /* WCDMA 3GPP UMTS 1900 MHz (Class II) */ - MM_MODEM_GSM_BAND_LAST = MM_MODEM_GSM_BAND_U17IX + MM_MODEM_GSM_BAND_LAST = MM_MODEM_GSM_BAND_U1900 } MMModemGsmBand; -#endif /* MM_MODEM_GSM_H */ +typedef enum { + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_ANY = 0, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_GPRS, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_EDGE, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_UMTS, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSDPA, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_2G_PREFERRED, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_3G_PREFERRED, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_2G_ONLY, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_3G_ONLY, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSUPA, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSPA, + MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_LAST = MM_MODEM_GSM_NETWORK_DEPRECATED_MODE_HSPA +} MMModemDeprecatedMode; + +#endif /* MM_MODEM_GSM_H */ diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c index 1741b5f..6e76f46 100644 --- a/src/mm-modem-helpers.c +++ b/src/mm-modem-helpers.c @@ -14,13 +14,32 @@ * Copyright (C) 2009 - 2010 Red Hat, Inc. */ +#include #include #include #include +#include +#include #include "mm-errors.h" #include "mm-modem-helpers.h" +const char * +mm_strip_tag (const char *str, const char *cmd) +{ + const char *p = str; + + if (p) { + if (!strncmp (p, cmd, strlen (cmd))) + p += strlen (cmd); + while (isspace (*p)) + p++; + } + return p; +} + +/*************************************************************************/ + static void save_scan_value (GHashTable *hash, const char *key, GMatchInfo *info, guint32 num) { @@ -201,3 +220,586 @@ mm_gsm_destroy_scan_data (gpointer data) g_ptr_array_free (results, TRUE); } +/*************************************************************************/ + +/* +CREG: (GSM 07.07 CREG=1 unsolicited) */ +#define CREG1 "\\+(CREG|CGREG):\\s*(\\d{1})" + +/* +CREG: , (GSM 07.07 CREG=1 solicited) */ +#define CREG2 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*(\\d{1})" + +/* +CREG: ,, (GSM 07.07 CREG=2 unsolicited) */ +#define CREG3 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)" + +/* +CREG: ,,, (GSM 07.07 solicited and some CREG=2 unsolicited) */ +#define CREG4 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*(\\d{1})\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)" + +/* +CREG: ,,, (ETSI 27.007 CREG=2 unsolicited) */ +#define CREG5 "\\+(CREG|CGREG):\\s*(\\d{1})\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*(\\d{1,2})" + +/* +CREG: ,,,, (ETSI 27.007 solicited and some CREG=2 unsolicited) */ +#define CREG6 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*(\\d{1})\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*(\\d{1,2})" + +GPtrArray * +mm_gsm_creg_regex_get (gboolean solicited) +{ + GPtrArray *array = g_ptr_array_sized_new (6); + GRegex *regex; + + /* #1 */ + if (solicited) + regex = g_regex_new (CREG1 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + else + regex = g_regex_new ("\\r\\n" CREG1 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (regex); + g_ptr_array_add (array, regex); + + /* #2 */ + if (solicited) + regex = g_regex_new (CREG2 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + else + regex = g_regex_new ("\\r\\n" CREG2 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (regex); + g_ptr_array_add (array, regex); + + /* #3 */ + if (solicited) + regex = g_regex_new (CREG3 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + else + regex = g_regex_new ("\\r\\n" CREG3 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (regex); + g_ptr_array_add (array, regex); + + /* #4 */ + if (solicited) + regex = g_regex_new (CREG4 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + else + regex = g_regex_new ("\\r\\n" CREG4 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (regex); + g_ptr_array_add (array, regex); + + /* #5 */ + if (solicited) + regex = g_regex_new (CREG5 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + else + regex = g_regex_new ("\\r\\n" CREG5 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (regex); + g_ptr_array_add (array, regex); + + /* #6 */ + if (solicited) + regex = g_regex_new (CREG6 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + else + regex = g_regex_new ("\\r\\n" CREG6 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + g_assert (regex); + g_ptr_array_add (array, regex); + + return array; +} + +void +mm_gsm_creg_regex_destroy (GPtrArray *array) +{ + g_ptr_array_foreach (array, (GFunc) g_regex_unref, NULL); + g_ptr_array_free (array, TRUE); +} + +/*************************************************************************/ + +static gulong +parse_uint (char *str, int base, glong nmin, glong nmax, gboolean *valid) +{ + gulong ret = 0; + char *endquote; + + *valid = FALSE; + if (!str) + return 0; + + /* Strip quotes */ + if (str[0] == '"') + str++; + endquote = strchr (str, '"'); + if (endquote) + *endquote = '\0'; + + if (strlen (str)) { + ret = strtol (str, NULL, base); + if ((nmin == nmax) || (ret >= nmin && ret <= nmax)) + *valid = TRUE; + } + return *valid ? (guint) ret : 0; +} + +gboolean +mm_gsm_parse_creg_response (GMatchInfo *info, + guint32 *out_reg_state, + gulong *out_lac, + gulong *out_ci, + gint *out_act, + gboolean *out_cgreg, + GError **error) +{ + gboolean success = FALSE, foo; + gint n_matches, act = -1; + gulong stat = 0, lac = 0, ci = 0; + guint istat = 0, ilac = 0, ici = 0, iact = 0; + char *str; + + g_return_val_if_fail (info != NULL, FALSE); + g_return_val_if_fail (out_reg_state != NULL, FALSE); + g_return_val_if_fail (out_lac != NULL, FALSE); + g_return_val_if_fail (out_ci != NULL, FALSE); + g_return_val_if_fail (out_act != NULL, FALSE); + g_return_val_if_fail (out_cgreg != NULL, FALSE); + + str = g_match_info_fetch (info, 1); + if (str && strstr (str, "CGREG")) + *out_cgreg = TRUE; + + /* Normally the number of matches could be used to determine what each + * item is, but we have overlap in one case. + */ + n_matches = g_match_info_get_match_count (info); + if (n_matches == 3) { + /* CREG=1: +CREG: */ + istat = 2; + } else if (n_matches == 4) { + /* Solicited response: +CREG: , */ + istat = 3; + } else if (n_matches == 5) { + /* CREG=2 (GSM 07.07): +CREG: ,, */ + istat = 2; + ilac = 3; + ici = 4; + } else if (n_matches == 6) { + /* CREG=2 (ETSI 27.007): +CREG: ,,, + * CREG=2 (non-standard): +CREG: ,,, + */ + + /* To distinguish, check length of the third match item. If it's + * more than one digit or has quotes in it then it's a LAC and we + * got the first format. + */ + str = g_match_info_fetch (info, 3); + if (str && (strchr (str, '"') || strlen (str) > 1)) { + g_free (str); + istat = 2; + ilac = 3; + ici = 4; + iact = 5; + } else { + istat = 3; + ilac = 4; + ici = 5; + } + } else if (n_matches == 7) { + /* CREG=2 (non-standard): +CREG: ,,,, */ + istat = 3; + ilac = 4; + ici = 5; + iact = 6; + } + + /* Status */ + str = g_match_info_fetch (info, istat); + stat = parse_uint (str, 10, 0, 5, &success); + g_free (str); + if (!success) { + g_set_error_literal (error, + MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Could not parse the registration status response"); + return FALSE; + } + + /* Location Area Code */ + if (ilac) { + /* FIXME: some phones apparently swap the LAC bytes (LG, SonyEricsson, + * Sagem). Need to handle that. + */ + str = g_match_info_fetch (info, ilac); + lac = parse_uint (str, 16, 1, 0xFFFF, &foo); + g_free (str); + } + + /* Cell ID */ + if (ici) { + str = g_match_info_fetch (info, ici); + ci = parse_uint (str, 16, 1, 0x0FFFFFFE, &foo); + g_free (str); + } + + /* Access Technology */ + if (iact) { + str = g_match_info_fetch (info, iact); + act = (gint) parse_uint (str, 10, 0, 7, &foo); + g_free (str); + if (!foo) + act = -1; + } + + *out_reg_state = (guint32) stat; + if (stat != 4) { + /* Don't fill in lac/ci/act if the device's state is unknown */ + *out_lac = lac; + *out_ci = ci; + *out_act = act; + } + return TRUE; +} + +/*************************************************************************/ + +gboolean +mm_cdma_parse_spservice_response (const char *reply, + MMModemCdmaRegistrationState *out_cdma_1x_state, + MMModemCdmaRegistrationState *out_evdo_state) +{ + const char *p; + + g_return_val_if_fail (reply != NULL, FALSE); + g_return_val_if_fail (out_cdma_1x_state != NULL, FALSE); + g_return_val_if_fail (out_evdo_state != NULL, FALSE); + + p = mm_strip_tag (reply, "+SPSERVICE:"); + if (!isdigit (*p)) + return FALSE; + + switch (atoi (p)) { + case 0: /* no service */ + *out_cdma_1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + *out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + break; + case 1: /* 1xRTT */ + *out_cdma_1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + *out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + break; + case 2: /* EVDO rev 0 */ + case 3: /* EVDO rev A */ + *out_cdma_1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + *out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; + break; + default: + return FALSE; + } + return TRUE; +} + +/*************************************************************************/ + +typedef struct { + int num; + gboolean roam_ind; + const char *banner; +} EriItem; + +/* NOTE: these may be Sprint-specific for now... */ +static const EriItem eris[] = { + { 0, TRUE, "Digital or Analog Roaming" }, + { 1, FALSE, "Home" }, + { 2, TRUE, "Digital or Analog Roaming" }, + { 3, TRUE, "Out of neighborhood" }, + { 4, TRUE, "Out of building" }, + { 5, TRUE, "Preferred system" }, + { 6, TRUE, "Available System" }, + { 7, TRUE, "Alliance Partner" }, + { 8, TRUE, "Premium Partner" }, + { 9, TRUE, "Full Service Functionality" }, + { 10, TRUE, "Partial Service Functionality" }, + { 64, TRUE, "Preferred system" }, + { 65, TRUE, "Available System" }, + { 66, TRUE, "Alliance Partner" }, + { 67, TRUE, "Premium Partner" }, + { 68, TRUE, "Full Service Functionality" }, + { 69, TRUE, "Partial Service Functionality" }, + { 70, TRUE, "Analog A" }, + { 71, TRUE, "Analog B" }, + { 72, TRUE, "CDMA 800 A" }, + { 73, TRUE, "CDMA 800 B" }, + { 74, TRUE, "International Roaming" }, + { 75, TRUE, "Extended Network" }, + { 76, FALSE, "Campus" }, + { 77, FALSE, "In Building" }, + { 78, TRUE, "Regional" }, + { 79, TRUE, "Community" }, + { 80, TRUE, "Business" }, + { 81, TRUE, "Zone 1" }, + { 82, TRUE, "Zone 2" }, + { 83, TRUE, "National" }, + { 84, TRUE, "Local" }, + { 85, TRUE, "City" }, + { 86, TRUE, "Government" }, + { 87, TRUE, "USA" }, + { 88, TRUE, "State" }, + { 89, TRUE, "Resort" }, + { 90, TRUE, "Headquarters" }, + { 91, TRUE, "Personal" }, + { 92, FALSE, "Home" }, + { 93, TRUE, "Residential" }, + { 94, TRUE, "University" }, + { 95, TRUE, "College" }, + { 96, TRUE, "Hotel Guest" }, + { 97, TRUE, "Rental" }, + { 98, FALSE, "Corporate" }, + { 99, FALSE, "Home Provider" }, + { 100, FALSE, "Campus" }, + { 101, FALSE, "In Building" }, + { 102, TRUE, "Regional" }, + { 103, TRUE, "Community" }, + { 104, TRUE, "Business" }, + { 105, TRUE, "Zone 1" }, + { 106, TRUE, "Zone 2" }, + { 107, TRUE, "National" }, + { 108, TRUE, "Local" }, + { 109, TRUE, "City" }, + { 110, TRUE, "Government" }, + { 111, TRUE, "USA" }, + { 112, TRUE, "State" }, + { 113, TRUE, "Resort" }, + { 114, TRUE, "Headquarters" }, + { 115, TRUE, "Personal" }, + { 116, FALSE, "Home" }, + { 117, TRUE, "Residential" }, + { 118, TRUE, "University" }, + { 119, TRUE, "College" }, + { 120, TRUE, "Hotel Guest" }, + { 121, TRUE, "Rental" }, + { 122, FALSE, "Corporate" }, + { 123, FALSE, "Home Provider" }, + { 124, TRUE, "International" }, + { 125, TRUE, "International" }, + { 126, TRUE, "International" }, + { 127, FALSE, "Premium Service" }, + { 128, FALSE, "Enhanced Service" }, + { 129, FALSE, "Enhanced Digital" }, + { 130, FALSE, "Enhanced Roaming" }, + { 131, FALSE, "Alliance Service" }, + { 132, FALSE, "Alliance Network" }, + { 133, FALSE, "Data Roaming" }, /* Sprint: Vision Roaming */ + { 134, FALSE, "Extended Service" }, + { 135, FALSE, "Expanded Services" }, + { 136, FALSE, "Expanded Network" }, + { 137, TRUE, "Premium Service" }, + { 138, TRUE, "Enhanced Service" }, + { 139, TRUE, "Enhanced Digital" }, + { 140, TRUE, "Enhanced Roaming" }, + { 141, TRUE, "Alliance Service" }, + { 142, TRUE, "Alliance Network" }, + { 143, TRUE, "Data Roaming" }, /* Sprint: Vision Roaming */ + { 144, TRUE, "Extended Service" }, + { 145, TRUE, "Expanded Services" }, + { 146, TRUE, "Expanded Network" }, + { 147, TRUE, "Premium Service" }, + { 148, TRUE, "Enhanced Service" }, + { 149, TRUE, "Enhanced Digital" }, + { 150, TRUE, "Enhanced Roaming" }, + { 151, TRUE, "Alliance Service" }, + { 152, TRUE, "Alliance Network" }, + { 153, TRUE, "Data Roaming" }, /* Sprint: Vision Roaming */ + { 154, TRUE, "Extended Service" }, + { 155, TRUE, "Expanded Services" }, + { 156, TRUE, "Expanded Network" }, + { 157, TRUE, "Premium International" }, + { 158, TRUE, "Premium International" }, + { 159, TRUE, "Premium International" }, + { 160, TRUE, NULL }, + { 161, TRUE, NULL }, + { 162, FALSE, NULL }, + { 163, FALSE, NULL }, + { 164, FALSE, "Extended Voice/Data Network" }, + { 165, FALSE, "Extended Voice/Data Network" }, + { 166, TRUE, "Extended Voice/Data Network" }, + { 167, FALSE, "Extended Broadband" }, + { 168, FALSE, "Extended Broadband" }, + { 169, TRUE, "Extended Broadband" }, + { 170, FALSE, "Extended Data" }, + { 171, FALSE, "Extended Data" }, + { 172, TRUE, "Extended Data" }, + { 173, FALSE, "Extended Data Network" }, + { 174, FALSE, "Extended Data Network" }, + { 175, TRUE, "Extended Data Network" }, + { 176, FALSE, "Extended Network" }, + { 177, FALSE, "Extended Network" }, + { 178, TRUE, "Extended Network" }, + { 179, FALSE, "Extended Service" }, + { 180, TRUE, "Extended Service" }, + { 181, FALSE, "Extended Voice" }, + { 182, FALSE, "Extended Voice" }, + { 183, TRUE, "Extended Voice" }, + { 184, FALSE, "Extended Voice/Data" }, + { 185, FALSE, "Extended Voice/Data" }, + { 186, TRUE, "Extended Voice/Data" }, + { 187, FALSE, "Extended Voice Network" }, + { 188, FALSE, "Extended Voice Network" }, + { 189, TRUE, "Extended Voice Network" }, + { 190, FALSE, "Extended Voice/Data" }, + { 191, FALSE, "Extended Voice/Data" }, + { 192, TRUE, "Extended Voice/Data" }, + { 193, TRUE, "International" }, + { 194, FALSE, "International Services" }, + { 195, FALSE, "International Voice" }, + { 196, FALSE, "International Voice/Data" }, + { 197, FALSE, "International Voice/Data" }, + { 198, TRUE, "International Voice/Data" }, + { 199, FALSE, "Extended Voice/Data Network" }, + { 200, TRUE, "Extended Voice/Data Network" }, + { 201, TRUE, "Extended Voice/Data Network" }, + { 202, FALSE, "Extended Broadband" }, + { 203, TRUE, "Extended Broadband" }, + { 204, TRUE, "Extended Broadband" }, + { 205, FALSE, "Extended Data" }, + { 206, TRUE, "Extended Data" }, + { 207, TRUE, "Extended Data" }, + { 208, FALSE, "Extended Data Network" }, + { 209, TRUE, "Extended Data Network" }, + { 210, TRUE, "Extended Data Network" }, + { 211, FALSE, "Extended Network" }, + { 212, TRUE, "Extended Network" }, + { 213, FALSE, "Extended Service" }, + { 214, TRUE, "Extended Service" }, + { 215, TRUE, "Extended Service" }, + { 216, FALSE, "Extended Voice" }, + { 217, TRUE, "Extended Voice" }, + { 218, TRUE, "Extended Voice" }, + { 219, FALSE, "Extended Voice/Data" }, + { 220, TRUE, "Extended Voice/Data" }, + { 221, TRUE, "Extended Voice/Data" }, + { 222, FALSE, "Extended Voice Network" }, + { 223, FALSE, "Extended Voice Network" }, + { 224, TRUE, "Extended Voice Network" }, + { 225, FALSE, "Extended Voice/Data" }, + { 226, TRUE, "Extended Voice/Data" }, + { 227, TRUE, "Extended Voice/Data" }, + { 228, TRUE, "International" }, + { 229, TRUE, "International" }, + { 230, TRUE, "International Services" }, + { 231, TRUE, "International Voice" }, + { 232, FALSE, "International Voice/Data" }, + { 233, TRUE, "International Voice/Data" }, + { 234, TRUE, "International Voice/Data" }, + { 235, TRUE, "Premium International" }, + { 236, TRUE, NULL }, + { 237, TRUE, NULL }, + { 238, FALSE, NULL }, + { 239, FALSE, NULL }, + { -1, FALSE, NULL }, +}; + +gboolean +mm_cdma_parse_eri (const char *reply, + gboolean *out_roaming, + guint32 *out_ind, + const char **out_desc) +{ + long int ind; + const EriItem *iter = &eris[0]; + gboolean found = FALSE; + + g_return_val_if_fail (reply != NULL, FALSE); + g_return_val_if_fail (out_roaming != NULL, FALSE); + + errno = 0; + ind = strtol (reply, NULL, 10); + if (errno == 0) { + if (out_ind) + *out_ind = ind; + + while (iter->num != -1) { + if (iter->num == ind) { + *out_roaming = iter->roam_ind; + if (out_desc) + *out_desc = iter->banner; + found = TRUE; + break; + } + iter++; + } + } + + return found; +} + +/*************************************************************************/ + +gboolean +mm_gsm_parse_cscs_support_response (const char *reply, + MMModemCharset *out_charsets) +{ + MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN; + GRegex *r; + GMatchInfo *match_info; + char *p, *str; + gboolean success = FALSE; + + g_return_val_if_fail (reply != NULL, FALSE); + g_return_val_if_fail (out_charsets != NULL, FALSE); + + /* Find the first '(' or '"'; the general format is: + * + * +CSCS: ("IRA","GSM","UCS2") + * + * but some devices (some Blackberries) don't include the (). + */ + p = strchr (reply, '('); + if (p) + p++; + else { + p = strchr (reply, '"'); + if (!p) + return FALSE; + } + + /* Now parse each charset */ + r = g_regex_new ("\\s*([^,\\)]+)\\s*", 0, 0, NULL); + if (!r) + return FALSE; + + if (g_regex_match_full (r, p, strlen (p), 0, 0, &match_info, NULL)) { + while (g_match_info_matches (match_info)) { + str = g_match_info_fetch (match_info, 1); + charsets |= mm_modem_charset_from_string (str); + g_free (str); + + g_match_info_next (match_info, NULL); + success = TRUE; + } + g_match_info_free (match_info); + } + g_regex_unref (r); + + if (success) + *out_charsets = charsets; + + return success; +} + +/*************************************************************************/ + +MMModemGsmAccessTech +mm_gsm_string_to_access_tech (const char *string) +{ + g_return_val_if_fail (string != NULL, MM_MODEM_GSM_ACCESS_TECH_UNKNOWN); + + /* Better technologies are listed first since modems sometimes say + * stuff like "GPRS/EDGE" and that should be handled as EDGE. + */ + if (strcasestr (string, "HSPA")) + return MM_MODEM_GSM_ACCESS_TECH_HSPA; + else if (strcasestr (string, "HSDPA/HSUPA")) + return MM_MODEM_GSM_ACCESS_TECH_HSPA; + else if (strcasestr (string, "HSUPA")) + return MM_MODEM_GSM_ACCESS_TECH_HSUPA; + else if (strcasestr (string, "HSDPA")) + return MM_MODEM_GSM_ACCESS_TECH_HSDPA; + else if (strcasestr (string, "UMTS")) + return MM_MODEM_GSM_ACCESS_TECH_UMTS; + else if (strcasestr (string, "EDGE")) + return MM_MODEM_GSM_ACCESS_TECH_EDGE; + else if (strcasestr (string, "GPRS")) + return MM_MODEM_GSM_ACCESS_TECH_GPRS; + else if (strcasestr (string, "GSM")) + return MM_MODEM_GSM_ACCESS_TECH_GSM; + + return MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; +} + diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h index ddc9cbc..fb100bc 100644 --- a/src/mm-modem-helpers.h +++ b/src/mm-modem-helpers.h @@ -17,6 +17,10 @@ #ifndef MM_MODEM_HELPERS_H #define MM_MODEM_HELPERS_H +#include "mm-modem-cdma.h" +#include "mm-modem-gsm.h" +#include "mm-charsets.h" + #define MM_SCAN_TAG_STATUS "status" #define MM_SCAN_TAG_OPER_LONG "operator-long" #define MM_SCAN_TAG_OPER_SHORT "operator-short" @@ -27,5 +31,33 @@ GPtrArray *mm_gsm_parse_scan_response (const char *reply, GError **error); void mm_gsm_destroy_scan_data (gpointer data); +GPtrArray *mm_gsm_creg_regex_get (gboolean solicited); + +void mm_gsm_creg_regex_destroy (GPtrArray *array); + +gboolean mm_gsm_parse_creg_response (GMatchInfo *info, + guint32 *out_reg_state, + gulong *out_lac, + gulong *out_ci, + gint *out_act, + gboolean *out_cgreg, + GError **error); + +const char *mm_strip_tag (const char *str, const char *cmd); + +gboolean mm_cdma_parse_spservice_response (const char *reply, + MMModemCdmaRegistrationState *out_cdma_1x_state, + MMModemCdmaRegistrationState *out_evdo_state); + +gboolean mm_cdma_parse_eri (const char *reply, + gboolean *out_roaming, + guint32 *out_ind, + const char **out_desc); + +gboolean mm_gsm_parse_cscs_support_response (const char *reply, + MMModemCharset *out_charsets); + +MMModemGsmAccessTech mm_gsm_string_to_access_tech (const char *string); + #endif /* MM_MODEM_HELPERS_H */ diff --git a/src/mm-modem-location.c b/src/mm-modem-location.c new file mode 100644 index 0000000..0018295 --- /dev/null +++ b/src/mm-modem-location.c @@ -0,0 +1,330 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include +#include + +#include "mm-modem-location.h" +#include "mm-errors.h" +#include "mm-callback-info.h" +#include "mm-marshal.h" + +static void impl_modem_location_enable (MMModemLocation *modem, + gboolean enable, + gboolean signal_location, + DBusGMethodInvocation *context); + +static void impl_modem_location_get_location (MMModemLocation *modem, + DBusGMethodInvocation *context); + +#include "mm-modem-location-glue.h" + +/*****************************************************************************/ + +static void +async_call_done (MMModem *modem, GError *error, gpointer user_data) +{ + DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data; + + if (error) + dbus_g_method_return_error (context, error); + else + dbus_g_method_return (context); +} + +static void +async_call_not_supported (MMModemLocation *self, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new (MM_MODEM (self), callback, user_data); + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + mm_callback_info_schedule (info); +} + +/*****************************************************************************/ + +typedef struct { + gboolean enable; + gboolean signals_location; +} LocAuthInfo; + +static void +loc_auth_info_destroy (gpointer data) +{ + LocAuthInfo *info = data; + + memset (info, 0, sizeof (LocAuthInfo)); + g_free (info); +} + +static LocAuthInfo * +loc_auth_info_new (gboolean enable, gboolean signals_location) +{ + LocAuthInfo *info; + + info = g_malloc0 (sizeof (LocAuthInfo)); + info->enable = enable; + info->signals_location = signals_location; + return info; +} + +/*****************************************************************************/ + +void +mm_modem_location_enable (MMModemLocation *self, + gboolean enable, + gboolean signals_location, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_LOCATION (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_LOCATION_GET_INTERFACE (self)->enable) + MM_MODEM_LOCATION_GET_INTERFACE (self)->enable (self, enable, signals_location, callback, user_data); + else + async_call_not_supported (self, callback, user_data); +} + +/*****************************************************************************/ + +static void +loc_enable_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemLocation *self = MM_MODEM_LOCATION (owner); + LocAuthInfo *info = (LocAuthInfo *) user_data; + GError *error = NULL; + + /* Return any authorization error, otherwise enable location gathering */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_location_enable (self, info->enable, info->signals_location, async_call_done, context); +} + +static void +impl_modem_location_enable (MMModemLocation *modem, + gboolean enable, + gboolean signals_location, + DBusGMethodInvocation *context) +{ + GError *error = NULL; + LocAuthInfo *info; + + info = loc_auth_info_new (enable, signals_location); + + /* Make sure the caller is authorized to enable location gathering */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_LOCATION, + context, + loc_enable_auth_cb, + info, + loc_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +loc_get_invoke (MMCallbackInfo *info) +{ + MMModemLocationGetFn callback = (MMModemLocationGetFn) info->callback; + + callback ((MMModemLocation *) info->modem, NULL, info->error, info->user_data); +} + +static void +async_get_call_not_supported (MMModemLocation *self, + MMModemLocationGetFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + info = mm_callback_info_new_full (MM_MODEM (self), + loc_get_invoke, + G_CALLBACK (callback), + user_data); + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + mm_callback_info_schedule (info); +} + +void +mm_modem_location_get_location (MMModemLocation *self, + MMModemLocationGetFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM_LOCATION (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_LOCATION_GET_INTERFACE (self)->get_location) + MM_MODEM_LOCATION_GET_INTERFACE (self)->get_location (self, callback, user_data); + else + async_get_call_not_supported (self, callback, user_data); +} + +/*****************************************************************************/ + +static void +async_get_call_done (MMModemLocation *self, + GHashTable *locations, + GError *error, + gpointer user_data) +{ + DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data; + + if (error) + dbus_g_method_return_error (context, error); + else + dbus_g_method_return (context, locations); +} + +static void +loc_get_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModemLocation *self = MM_MODEM_LOCATION (owner); + GError *error = NULL; + + /* Return any authorization error, otherwise get the location */ + if (!mm_modem_auth_finish (MM_MODEM (self), req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_location_get_location (self, async_get_call_done, context); +} + +static void +impl_modem_location_get_location (MMModemLocation *modem, + DBusGMethodInvocation *context) +{ + GError *error = NULL; + LocAuthInfo *info; + + info = loc_auth_info_new (FALSE, FALSE); + + /* Make sure the caller is authorized to enable location gathering */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_LOCATION, + context, + loc_get_auth_cb, + info, + loc_auth_info_destroy, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +/*****************************************************************************/ + +static void +mm_modem_location_init (gpointer g_iface) +{ + static gboolean initialized = FALSE; + + if (initialized) + return; + + /* Properties */ + g_object_interface_install_property + (g_iface, + g_param_spec_boxed (MM_MODEM_LOCATION_LOCATION, + "Location", + "Available location information", + G_TYPE_HASH_TABLE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_uint (MM_MODEM_LOCATION_CAPABILITIES, + "Capabilities", + "Supported location information methods", + MM_MODEM_LOCATION_CAPABILITY_UNKNOWN, + MM_MODEM_LOCATION_CAPABILITY_GPS_NMEA + | MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI, + MM_MODEM_LOCATION_CAPABILITY_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_boolean (MM_MODEM_LOCATION_ENABLED, + "Enabled", + "Whether or not location gathering is enabled", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_interface_install_property + (g_iface, + g_param_spec_boolean (MM_MODEM_LOCATION_SIGNALS_LOCATION, + "SignalsLocation", + "Whether or not location updates are emitted as signals", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + initialized = TRUE; +} + +GType +mm_modem_location_get_type (void) +{ + static GType loc_type = 0; + + if (!G_UNLIKELY (loc_type)) { + const GTypeInfo loc_info = { + sizeof (MMModemLocation), /* class_size */ + mm_modem_location_init, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + loc_type = g_type_register_static (G_TYPE_INTERFACE, + "MMModemLocation", + &loc_info, 0); + + g_type_interface_add_prerequisite (loc_type, G_TYPE_OBJECT); + dbus_g_object_type_install_info (loc_type, &dbus_glib_mm_modem_location_object_info); + + /* Register some shadow properties to handle Enabled and Capabilities + * since these could be used by other interfaces. + */ + dbus_g_object_type_register_shadow_property (loc_type, + "Enabled", + MM_MODEM_LOCATION_ENABLED); + dbus_g_object_type_register_shadow_property (loc_type, + "Capabilities", + MM_MODEM_LOCATION_CAPABILITIES); + } + + return loc_type; +} diff --git a/src/mm-modem-location.h b/src/mm-modem-location.h new file mode 100644 index 0000000..dd622f1 --- /dev/null +++ b/src/mm-modem-location.h @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_MODEM_LOCATION_H +#define MM_MODEM_LOCATION_H + +#include + +#define MM_TYPE_MODEM_LOCATION (mm_modem_location_get_type ()) +#define MM_MODEM_LOCATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_LOCATION, MMModemLocation)) +#define MM_IS_MODEM_LOCATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_LOCATION)) +#define MM_MODEM_LOCATION_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_LOCATION, MMModemLocation)) + +#define MM_MODEM_LOCATION_CAPABILITIES "location-capabilities" +#define MM_MODEM_LOCATION_ENABLED "location-enabled" +#define MM_MODEM_LOCATION_SIGNALS_LOCATION "signals-location" +#define MM_MODEM_LOCATION_LOCATION "location" + +typedef enum { + MM_MODEM_LOCATION_CAPABILITY_UNKNOWN = 0x00000000, + MM_MODEM_LOCATION_CAPABILITY_GPS_NMEA = 0x00000001, + MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI = 0x00000002, + + MM_MODEM_LOCATION_CAPABILITY_LAST = MM_MODEM_LOCATION_CAPABILITY_GSM_LAC_CI +} MMModemLocationCapabilities; + +typedef struct _MMModemLocation MMModemLocation; + +typedef void (*MMModemLocationGetFn) (MMModemLocation *modem, + GHashTable *locations, + GError *error, + gpointer user_data); + +struct _MMModemLocation { + GTypeInterface g_iface; + + /* Methods */ + void (*enable) (MMModemLocation *modem, + gboolean enable, + gboolean signal_location, + MMModemFn callback, + gpointer user_data); + + void (*get_location) (MMModemLocation *modem, + MMModemLocationGetFn callback, + gpointer user_data); +}; + +GType mm_modem_location_get_type (void); + +void mm_modem_location_enable (MMModemLocation *self, + gboolean enable, + gboolean signal_location, + MMModemFn callback, + gpointer user_data); + +void mm_modem_location_get_location (MMModemLocation *self, + MMModemLocationGetFn callback, + gpointer user_data); + +#endif /* MM_MODEM_LOCATION_H */ diff --git a/src/mm-modem.c b/src/mm-modem.c index a65d883..221c9ea 100644 --- a/src/mm-modem.c +++ b/src/mm-modem.c @@ -14,9 +14,11 @@ * Copyright (C) 2009 - 2010 Red Hat, Inc. */ +#include #include #include #include "mm-modem.h" +#include "mm-options.h" #include "mm-errors.h" #include "mm-callback-info.h" #include "mm-marshal.h" @@ -26,6 +28,7 @@ static void impl_modem_connect (MMModem *modem, const char *number, DBusGMethodI static void impl_modem_disconnect (MMModem *modem, DBusGMethodInvocation *context); static void impl_modem_get_ip4_config (MMModem *modem, DBusGMethodInvocation *context); static void impl_modem_get_info (MMModem *modem, DBusGMethodInvocation *context); +static void impl_modem_factory_reset (MMModem *modem, const char *code, DBusGMethodInvocation *context); #include "mm-modem-glue.h" @@ -148,12 +151,15 @@ disable_disconnect_done (MMModem *self, } if (error) { + char *device = mm_modem_get_device (self); + /* Don't really care what the error was; log it and proceed to disable */ g_warning ("%s: (%s): error disconnecting the modem while disabling: (%d) %s", __func__, - mm_modem_get_device (self), + device, error ? error->code : -1, error && error->message ? error->message : "(unknown)"); + g_free (device); } finish_disable (self, cb_data->callback, cb_data->user_data); g_free (cb_data); @@ -471,6 +477,106 @@ impl_modem_get_info (MMModem *modem, /*****************************************************************************/ +static void +factory_reset_auth_cb (MMAuthRequest *req, + GObject *owner, + DBusGMethodInvocation *context, + gpointer user_data) +{ + MMModem *self = MM_MODEM (owner); + const char *code = user_data; + GError *error = NULL; + + /* Return any authorization error, otherwise try to reset the modem */ + if (!mm_modem_auth_finish (self, req, &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } else + mm_modem_factory_reset (self, code, async_call_done, context); +} + +static void +impl_modem_factory_reset (MMModem *modem, + const char *code, + DBusGMethodInvocation *context) +{ + GError *error = NULL; + + /* Make sure the caller is authorized to reset the device */ + if (!mm_modem_auth_request (MM_MODEM (modem), + MM_AUTHORIZATION_DEVICE_CONTROL, + context, + factory_reset_auth_cb, + g_strdup (code), + g_free, + &error)) { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +void +mm_modem_factory_reset (MMModem *self, + const char *code, + MMModemFn callback, + gpointer user_data) +{ + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + g_return_if_fail (code != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->factory_reset) + MM_MODEM_GET_INTERFACE (self)->factory_reset (self, code, callback, user_data); + else + async_op_not_supported (self, callback, user_data); +} + +/*****************************************************************************/ + +void +mm_modem_get_supported_charsets (MMModem *self, + MMModemUIntFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->get_supported_charsets) + MM_MODEM_GET_INTERFACE (self)->get_supported_charsets (self, callback, user_data); + else { + info = mm_callback_info_uint_new (MM_MODEM (self), callback, user_data); + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + mm_callback_info_schedule (info); + } +} + +void +mm_modem_set_charset (MMModem *self, + MMModemCharset charset, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + + g_return_if_fail (charset != MM_MODEM_CHARSET_UNKNOWN); + g_return_if_fail (MM_IS_MODEM (self)); + g_return_if_fail (callback != NULL); + + if (MM_MODEM_GET_INTERFACE (self)->set_charset) + MM_MODEM_GET_INTERFACE (self)->set_charset (self, charset, callback, user_data); + else { + info = mm_callback_info_new (MM_MODEM (self), callback, user_data); + info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, + "Operation not supported"); + mm_callback_info_schedule (info); + } +} + +/*****************************************************************************/ + gboolean mm_modem_owns_port (MMModem *self, const char *subsys, @@ -598,16 +704,76 @@ mm_modem_set_state (MMModem *self, dbus_path = (const char *) g_object_get_data (G_OBJECT (self), DBUS_PATH_TAG); if (dbus_path) { - g_message ("Modem %s: state changed (%s -> %s)", - dbus_path, - state_to_string (old_state), - state_to_string (new_state)); + if (mm_options_debug ()) { + GTimeVal tv; + + g_get_current_time (&tv); + g_debug ("<%ld.%ld> Modem %s: state changed (%s -> %s)", + tv.tv_sec, + tv.tv_usec, + dbus_path, + state_to_string (old_state), + state_to_string (new_state)); + } else { + g_message ("Modem %s: state changed (%s -> %s)", + dbus_path, + state_to_string (old_state), + state_to_string (new_state)); + } } } } /*****************************************************************************/ +gboolean +mm_modem_auth_request (MMModem *self, + const char *authorization, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify, + GError **error) +{ + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (MM_IS_MODEM (self), FALSE); + g_return_val_if_fail (authorization != NULL, FALSE); + g_return_val_if_fail (context != NULL, FALSE); + g_return_val_if_fail (callback != NULL, FALSE); + + g_return_val_if_fail (MM_MODEM_GET_INTERFACE (self)->auth_request, FALSE); + return MM_MODEM_GET_INTERFACE (self)->auth_request (self, + authorization, + context, + callback, + callback_data, + notify, + error); +} + +gboolean +mm_modem_auth_finish (MMModem *self, + MMAuthRequest *req, + GError **error) +{ + gboolean success; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (MM_IS_MODEM (self), FALSE); + g_return_val_if_fail (req != NULL, FALSE); + + g_return_val_if_fail (MM_MODEM_GET_INTERFACE (self)->auth_finish, FALSE); + success = MM_MODEM_GET_INTERFACE (self)->auth_finish (self, req, error); + + /* If the request failed, the implementor *should* return an error */ + if (!success && error) + g_warn_if_fail (*error != NULL); + + return success; +} + +/*****************************************************************************/ + static void mm_modem_init (gpointer g_iface) { @@ -694,6 +860,31 @@ mm_modem_init (gpointer g_iface) FALSE, G_PARAM_READABLE)); + g_object_interface_install_property + (g_iface, + g_param_spec_string (MM_MODEM_EQUIPMENT_IDENTIFIER, + "EquipmentIdentifier", + "The equipment identifier of the device", + NULL, + G_PARAM_READABLE)); + + g_object_interface_install_property + (g_iface, + g_param_spec_string (MM_MODEM_UNLOCK_REQUIRED, + "UnlockRequired", + "Whether or not the modem requires an unlock " + "code to become usable, and if so, which unlock code is required", + NULL, + G_PARAM_READABLE)); + + g_object_interface_install_property + (g_iface, + g_param_spec_uint (MM_MODEM_UNLOCK_RETRIES, + "UnlockRetries", + "The remaining number of unlock attempts", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE)); + /* Signals */ g_signal_new ("state-changed", iface_type, diff --git a/src/mm-modem.h b/src/mm-modem.h index e8dd7ea..0915180 100644 --- a/src/mm-modem.h +++ b/src/mm-modem.h @@ -18,8 +18,11 @@ #define MM_MODEM_H #include +#include #include "mm-port.h" +#include "mm-auth-provider.h" +#include "mm-charsets.h" typedef enum { MM_MODEM_STATE_UNKNOWN = 0, @@ -55,6 +58,9 @@ typedef enum { #define MM_MODEM_TYPE "type" #define MM_MODEM_IP_METHOD "ip-method" #define MM_MODEM_ENABLED "enabled" +#define MM_MODEM_EQUIPMENT_IDENTIFIER "equipment-identifier" +#define MM_MODEM_UNLOCK_REQUIRED "unlock-required" +#define MM_MODEM_UNLOCK_RETRIES "unlock-retries" #define MM_MODEM_VALID "valid" /* not exported */ #define MM_MODEM_PLUGIN "plugin" /* not exported */ #define MM_MODEM_STATE "state" /* not exported */ @@ -78,7 +84,10 @@ typedef enum { MM_MODEM_PROP_VALID, /* Not exported */ MM_MODEM_PROP_PLUGIN, /* Not exported */ MM_MODEM_PROP_STATE, /* Not exported */ - MM_MODEM_PROP_ENABLED + MM_MODEM_PROP_ENABLED, + MM_MODEM_PROP_EQUIPMENT_IDENTIFIER, + MM_MODEM_PROP_UNLOCK_REQUIRED, + MM_MODEM_PROP_UNLOCK_RETRIES } MMModemProp; typedef struct _MMModem MMModem; @@ -154,6 +163,36 @@ struct _MMModem { MMModemInfoFn callback, gpointer user_data); + void (*get_supported_charsets) (MMModem *self, + MMModemUIntFn callback, + gpointer user_data); + + void (*set_charset) (MMModem *self, + MMModemCharset charset, + MMModemFn callback, + gpointer user_data); + + + /* Normally implemented by the modem base class; plugins should + * never need to implement this. + */ + gboolean (*auth_request) (MMModem *self, + const char *authorization, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify, + GError **error); + + gboolean (*auth_finish) (MMModem *self, + MMAuthRequest *req, + GError **error); + + void (*factory_reset) (MMModem *self, + const char *code, + MMModemFn callback, + gpointer user_data); + /* Signals */ void (*state_changed) (MMModem *self, MMModemState new_state, @@ -203,6 +242,20 @@ void mm_modem_get_info (MMModem *self, MMModemInfoFn callback, gpointer user_data); +void mm_modem_get_supported_charsets (MMModem *self, + MMModemUIntFn callback, + gpointer user_data); + +void mm_modem_set_charset (MMModem *self, + MMModemCharset charset, + MMModemFn callback, + gpointer user_data); + +void mm_modem_factory_reset (MMModem *self, + const char *code, + MMModemFn callback, + gpointer user_data); + gboolean mm_modem_get_valid (MMModem *self); char *mm_modem_get_device (MMModem *self); @@ -215,5 +268,21 @@ void mm_modem_set_state (MMModem *self, GError *mm_modem_check_removed (MMModem *self, const GError *error); +/* Request authorization to perform an action. Used by D-Bus method + * handlers to ensure that the incoming request is authorized to perform + * the action it's requesting. + */ +gboolean mm_modem_auth_request (MMModem *self, + const char *authorization, + DBusGMethodInvocation *context, + MMAuthRequestCb callback, + gpointer callback_data, + GDestroyNotify notify, + GError **error); + +gboolean mm_modem_auth_finish (MMModem *self, + MMAuthRequest *req, + GError **error); + #endif /* MM_MODEM_H */ diff --git a/src/mm-plugin-base.c b/src/mm-plugin-base.c index 202eaac..80d0f90 100644 --- a/src/mm-plugin-base.c +++ b/src/mm-plugin-base.c @@ -11,7 +11,7 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #define _GNU_SOURCE /* for strcasestr */ @@ -26,10 +26,14 @@ #include #include "mm-plugin-base.h" -#include "mm-serial-port.h" +#include "mm-at-serial-port.h" +#include "mm-qcdm-serial-port.h" #include "mm-serial-parsers.h" #include "mm-errors.h" #include "mm-marshal.h" +#include "mm-utils.h" +#include "libqcdm/src/commands.h" +#include "libqcdm/src/utils.h" static void plugin_init (MMPlugin *plugin_class); @@ -48,8 +52,6 @@ static GHashTable *cached_caps = NULL; typedef struct { char *name; GUdevClient *client; - - GHashTable *modems; GHashTable *tasks; } MMPluginBasePrivate; @@ -77,6 +79,8 @@ typedef enum { PROBE_STATE_LAST } ProbeState; +static void probe_complete (MMPluginBaseSupportsTask *task); + /*****************************************************************************/ G_DEFINE_TYPE (MMPluginBaseSupportsTask, mm_plugin_base_supports_task, G_TYPE_OBJECT) @@ -86,13 +90,15 @@ G_DEFINE_TYPE (MMPluginBaseSupportsTask, mm_plugin_base_supports_task, G_TYPE_OB typedef struct { MMPluginBase *plugin; GUdevDevice *port; - GUdevDevice *physdev; + char *physdev_path; char *driver; guint open_id; guint32 open_tries; + guint full_id; - MMSerialPort *probe_port; + MMAtSerialPort *probe_port; + MMQcdmSerialPort *qcdm_port; guint32 probed_caps; ProbeState probe_state; guint probe_id; @@ -112,7 +118,7 @@ typedef struct { static MMPluginBaseSupportsTask * supports_task_new (MMPluginBase *self, GUdevDevice *port, - GUdevDevice *physdev, + const char *physdev_path, const char *driver, MMSupportsPortResultFunc callback, gpointer callback_data) @@ -123,7 +129,7 @@ supports_task_new (MMPluginBase *self, g_return_val_if_fail (self != NULL, NULL); g_return_val_if_fail (MM_IS_PLUGIN_BASE (self), NULL); g_return_val_if_fail (port != NULL, NULL); - g_return_val_if_fail (physdev != NULL, NULL); + g_return_val_if_fail (physdev_path != NULL, NULL); g_return_val_if_fail (driver != NULL, NULL); g_return_val_if_fail (callback != NULL, NULL); @@ -132,7 +138,7 @@ supports_task_new (MMPluginBase *self, priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); priv->plugin = self; priv->port = g_object_ref (port); - priv->physdev = g_object_ref (physdev); + priv->physdev_path = g_strdup (physdev_path); priv->driver = g_strdup (driver); priv->callback = callback; priv->callback_data = callback_data; @@ -158,13 +164,13 @@ mm_plugin_base_supports_task_get_port (MMPluginBaseSupportsTask *task) return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->port; } -GUdevDevice * -mm_plugin_base_supports_task_get_physdev (MMPluginBaseSupportsTask *task) +const char * +mm_plugin_base_supports_task_get_physdev_path (MMPluginBaseSupportsTask *task) { g_return_val_if_fail (task != NULL, NULL); g_return_val_if_fail (MM_IS_PLUGIN_BASE_SUPPORTS_TASK (task), NULL); - return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->physdev; + return MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task)->physdev_path; } const char * @@ -198,6 +204,11 @@ mm_plugin_base_supports_task_complete (MMPluginBaseSupportsTask *task, priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); g_return_if_fail (priv->callback != NULL); + if (priv->full_id) { + g_source_remove (priv->full_id); + priv->full_id = 0; + } + subsys = g_udev_device_get_subsystem (priv->port); name = g_udev_device_get_name (priv->port); @@ -239,11 +250,11 @@ supports_task_dispose (GObject *object) { MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (object); - if (MM_IS_SERIAL_PORT (priv->port)) - mm_serial_port_flash_cancel (MM_SERIAL_PORT (priv->port)); + if (MM_IS_SERIAL_PORT (priv->probe_port)) + mm_serial_port_flash_cancel (MM_SERIAL_PORT (priv->probe_port)); g_object_unref (priv->port); - g_object_unref (priv->physdev); + g_free (priv->physdev_path); g_free (priv->driver); g_free (priv->probe_resp); g_clear_error (&(priv->probe_error)); @@ -251,11 +262,19 @@ supports_task_dispose (GObject *object) if (priv->open_id) g_source_remove (priv->open_id); + if (priv->full_id) + g_source_remove (priv->full_id); if (priv->probe_id) g_source_remove (priv->probe_id); - if (priv->probe_port) + if (priv->probe_port) { + mm_serial_port_close (MM_SERIAL_PORT (priv->probe_port)); g_object_unref (priv->probe_port); + } + if (priv->qcdm_port) { + mm_serial_port_close (MM_SERIAL_PORT (priv->qcdm_port)); + g_object_unref (priv->qcdm_port); + } G_OBJECT_CLASS (mm_plugin_base_supports_task_parent_class)->dispose (object); } @@ -334,7 +353,8 @@ parse_cpin (const char *buf) || strcasestr (buf, "PH-SP PIN") || strcasestr (buf, "PH-SP PUK") || strcasestr (buf, "PH-CORP PIN") - || strcasestr (buf, "PH-CORP PUK")) + || strcasestr (buf, "PH-CORP PUK") + || strcasestr (buf, "READY")) return MM_PLUGIN_BASE_PORT_CAP_GSM; return 0; @@ -349,6 +369,49 @@ parse_cgmm (const char *buf) return 0; } +static const char *dq_strings[] = { + /* Option Icera-based devices */ + "option/faema_", + "os_logids.h", + /* Sierra CnS port */ + "NETWORK SERVICE CHANGE", + NULL +}; + +static void +port_buffer_full (MMSerialPort *port, GByteArray *buffer, gpointer user_data) +{ + MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (user_data); + const char **iter; + size_t iter_len; + int i; + + /* Check for an immediate disqualification response. There are some + * ports (Option Icera-based chipsets have them, as do Qualcomm Gobi + * devices before their firmware is loaded) that just shouldn't be + * probed if we get a certain response because we know they can't be + * used. Kernel bugs (at least with 2.6.31 and 2.6.32) also trigger port + * flow control kernel oopses if we read too much data for these ports. + */ + + for (iter = &dq_strings[0]; iter && *iter; iter++) { + /* Search in the response for the item; the response could have embedded + * nulls so we can't use memcmp() or strstr() on the whole response. + */ + iter_len = strlen (*iter); + for (i = 0; i < buffer->len - iter_len; i++) { + if (!memcmp (&buffer->data[i], *iter, iter_len)) { + /* Immediately close the port and complete probing */ + priv->probed_caps = 0; + mm_serial_port_close (MM_SERIAL_PORT (priv->probe_port)); + probe_complete (task); + return; + } + } + } +} + static gboolean emit_probe_result (gpointer user_data) { @@ -356,9 +419,15 @@ emit_probe_result (gpointer user_data) MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); MMPlugin *self = mm_plugin_base_supports_task_get_plugin (task); - /* Close the serial port */ - g_object_unref (task_priv->probe_port); - task_priv->probe_port = NULL; + /* Close the serial ports */ + if (task_priv->probe_port) { + g_object_unref (task_priv->probe_port); + task_priv->probe_port = NULL; + } + if (task_priv->qcdm_port) { + g_object_unref (task_priv->qcdm_port); + task_priv->qcdm_port = NULL; + } task_priv->probe_id = 0; g_signal_emit (self, signals[PROBE_RESULT], 0, task, task_priv->probed_caps); @@ -368,17 +437,122 @@ emit_probe_result (gpointer user_data) static void probe_complete (MMPluginBaseSupportsTask *task) { - MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + MMPort *port; + port = priv->probe_port ? MM_PORT (priv->probe_port) : MM_PORT (priv->qcdm_port); + g_assert (port); g_hash_table_insert (cached_caps, - g_strdup (mm_port_get_device (MM_PORT (task_priv->probe_port))), - GUINT_TO_POINTER (task_priv->probed_caps)); + g_strdup (mm_port_get_device (port)), + GUINT_TO_POINTER (priv->probed_caps)); + + priv->probe_id = g_idle_add (emit_probe_result, task); +} + +static void +qcdm_verinfo_cb (MMQcdmSerialPort *port, + GByteArray *response, + GError *error, + gpointer user_data) +{ + MMPluginBaseSupportsTask *task; + MMPluginBaseSupportsTaskPrivate *priv; + QCDMResult *result; + GError *dm_error = NULL; + + /* Just the initial poke; ignore it */ + if (!user_data) + return; + + task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data); + priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + + if (error) { + /* Probably not a QCDM port */ + goto done; + } + + /* Parse the response */ + result = qcdm_cmd_version_info_result ((const char *) response->data, response->len, &dm_error); + if (!result) { + g_warning ("(%s) failed to parse QCDM version info command result: (%d) %s.", + g_udev_device_get_name (priv->port), + dm_error ? dm_error->code : -1, + dm_error && dm_error->message ? dm_error->message : "(unknown)"); + g_clear_error (&dm_error); + goto done; + } + + /* yay, probably a QCDM port */ + qcdm_result_unref (result); + priv->probed_caps |= MM_PLUGIN_BASE_PORT_CAP_QCDM; - task_priv->probe_id = g_idle_add (emit_probe_result, task); +done: + probe_complete (task); } static void -parse_response (MMSerialPort *port, +try_qcdm_probe (MMPluginBaseSupportsTask *task) +{ + MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); + const char *name; + GError *error = NULL; + GByteArray *verinfo = NULL, *verinfo2; + gint len; + + /* Close the AT port */ + if (priv->probe_port) { + mm_serial_port_close (MM_SERIAL_PORT (priv->probe_port)); + g_object_unref (priv->probe_port); + priv->probe_port = NULL; + } + + /* Open the QCDM port */ + name = g_udev_device_get_name (priv->port); + g_assert (name); + priv->qcdm_port = mm_qcdm_serial_port_new (name, MM_PORT_TYPE_PRIMARY); + if (priv->qcdm_port == NULL) { + g_warning ("(%s) failed to create new QCDM serial port.", name); + probe_complete (task); + return; + } + + if (!mm_serial_port_open (MM_SERIAL_PORT (priv->qcdm_port), &error)) { + g_warning ("(%s) failed to open new QCDM serial port: (%d) %s.", + name, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + probe_complete (task); + return; + } + + /* Build up the probe command */ + verinfo = g_byte_array_sized_new (50); + len = qcdm_cmd_version_info_new ((char *) verinfo->data, 50, &error); + if (len <= 0) { + g_byte_array_free (verinfo, TRUE); + g_warning ("(%s) failed to create QCDM version info command: (%d) %s.", + name, + error ? error->code : -1, + error && error->message ? error->message : "(unknown)"); + g_clear_error (&error); + probe_complete (task); + return; + } + verinfo->len = len; + + /* Queuing the command takes ownership over it; copy it for the second try */ + verinfo2 = g_byte_array_sized_new (verinfo->len); + g_byte_array_append (verinfo2, verinfo->data, verinfo->len); + + /* Send the command twice; the ports often need to be woken up */ + mm_qcdm_serial_port_queue_command (priv->qcdm_port, verinfo, 3, qcdm_verinfo_cb, NULL); + mm_qcdm_serial_port_queue_command (priv->qcdm_port, verinfo2, 3, qcdm_verinfo_cb, task); +} + +static void +parse_response (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data); @@ -391,7 +565,7 @@ real_handle_probe_response (MMPluginBase *self, const GError *error) { MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); - MMSerialPort *port = task_priv->probe_port; + MMAtSerialPort *port = task_priv->probe_port; gboolean ignore_error = FALSE; /* Some modems (Huawei E160g) won't respond to +GCAP with no SIM, but @@ -401,16 +575,16 @@ real_handle_probe_response (MMPluginBase *self, ignore_error = TRUE; if (error && !ignore_error) { - if (error->code == MM_SERIAL_RESPONSE_TIMEOUT) { + if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { /* Try GCAP again */ if (task_priv->probe_state < PROBE_STATE_GCAP_TRY3) { task_priv->probe_state++; - mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, task); + mm_at_serial_port_queue_command (port, "+GCAP", 3, parse_response, task); } else { /* Otherwise, if all the GCAP tries timed out, ignore the port - * as it's probably not an AT-capable port. + * as it's probably not an AT-capable port. Try QCDM. */ - probe_complete (task); + try_qcdm_probe (task); } return; } @@ -459,19 +633,19 @@ real_handle_probe_response (MMPluginBase *self, switch (task_priv->probe_state) { case PROBE_STATE_GCAP_TRY2: case PROBE_STATE_GCAP_TRY3: - mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, task); + mm_at_serial_port_queue_command (port, "+GCAP", 3, parse_response, task); break; case PROBE_STATE_ATI: /* After the last GCAP attempt, try ATI */ - mm_serial_port_queue_command (port, "I", 3, parse_response, task); + mm_at_serial_port_queue_command (port, "I", 3, parse_response, task); break; case PROBE_STATE_CPIN: /* After the ATI attempt, try CPIN */ - mm_serial_port_queue_command (port, "+CPIN?", 3, parse_response, task); + mm_at_serial_port_queue_command (port, "+CPIN?", 3, parse_response, task); break; case PROBE_STATE_CGMM: /* After the CPIN attempt, try CGMM */ - mm_serial_port_queue_command (port, "+CGMM", 3, parse_response, task); + mm_at_serial_port_queue_command (port, "+CGMM", 3, parse_response, task); break; default: /* Probably not GSM or CDMA */ @@ -515,7 +689,7 @@ handle_probe_response (gpointer user_data) } static void -parse_response (MMSerialPort *port, +parse_response (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -545,7 +719,7 @@ parse_response (MMSerialPort *port, static void flash_done (MMSerialPort *port, GError *error, gpointer user_data); static void -custom_init_response (MMSerialPort *port, +custom_init_response (MMAtSerialPort *port, GString *response, GError *error, gpointer user_data) @@ -557,11 +731,11 @@ custom_init_response (MMSerialPort *port, task_priv->custom_init_tries++; if (task_priv->custom_init_tries < task_priv->custom_init_max_tries) { /* Try the custom command again */ - flash_done (port, NULL, user_data); + flash_done (MM_SERIAL_PORT (port), NULL, user_data); return; } else if (task_priv->custom_init_fail_if_timeout) { /* Fail the probe if the plugin wanted it and the command timed out */ - if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_RESPONSE_TIMEOUT)) { + if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) { probe_complete (task); return; } @@ -569,7 +743,7 @@ custom_init_response (MMSerialPort *port, } /* Otherwise proceed to probing */ - mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, user_data); + mm_at_serial_port_queue_command (port, "+GCAP", 3, parse_response, user_data); } static void @@ -583,14 +757,14 @@ flash_done (MMSerialPort *port, GError *error, gpointer user_data) if (task_priv->custom_init) { if (!delay_secs) delay_secs = 3; - mm_serial_port_queue_command (port, - task_priv->custom_init, - delay_secs, - custom_init_response, - user_data); + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), + task_priv->custom_init, + delay_secs, + custom_init_response, + user_data); } else { /* Otherwise start normal probing */ - custom_init_response (port, NULL, NULL, user_data); + custom_init_response (MM_AT_SERIAL_PORT (port), NULL, NULL, user_data); } } @@ -603,7 +777,7 @@ try_open (gpointer user_data) task_priv->open_id = 0; - if (!mm_serial_port_open (task_priv->probe_port, &error)) { + if (!mm_serial_port_open (MM_SERIAL_PORT (task_priv->probe_port), &error)) { if (++task_priv->open_tries > 4) { /* took too long to open the port; give up */ g_warning ("(%s): failed to open after 4 tries.", @@ -611,7 +785,7 @@ try_open (gpointer user_data) probe_complete (task); } else if (g_error_matches (error, MM_SERIAL_ERROR, - MM_SERIAL_OPEN_FAILED_NO_DEVICE)) { + MM_SERIAL_ERROR_OPEN_FAILED_NO_DEVICE)) { /* this is nozomi being dumb; try again */ task_priv->open_id = g_timeout_add_seconds (1, try_open, task); } else { @@ -626,10 +800,13 @@ try_open (gpointer user_data) port = mm_plugin_base_supports_task_get_port (task); g_assert (port); + task_priv->full_id = g_signal_connect (task_priv->probe_port, "buffer-full", + G_CALLBACK (port_buffer_full), task); + g_debug ("(%s): probe requested by plugin '%s'", g_udev_device_get_name (port), mm_plugin_get_name (MM_PLUGIN (task_priv->plugin))); - mm_serial_port_flash (task_priv->probe_port, 100, flash_done, task); + mm_serial_port_flash (MM_SERIAL_PORT (task_priv->probe_port), 100, TRUE, flash_done, task); } return FALSE; @@ -641,7 +818,7 @@ mm_plugin_base_probe_port (MMPluginBase *self, GError **error) { MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task); - MMSerialPort *serial; + MMAtSerialPort *serial; const char *name; GUdevDevice *port; @@ -654,7 +831,7 @@ mm_plugin_base_probe_port (MMPluginBase *self, name = g_udev_device_get_name (port); g_assert (name); - serial = mm_serial_port_new (name, MM_PORT_TYPE_PRIMARY); + serial = mm_at_serial_port_new (name, MM_PORT_TYPE_PRIMARY); if (serial == NULL) { g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Failed to create new serial port."); @@ -666,10 +843,10 @@ mm_plugin_base_probe_port (MMPluginBase *self, MM_PORT_CARRIER_DETECT, FALSE, NULL); - mm_serial_port_set_response_parser (serial, - mm_serial_parser_v1_parse, - mm_serial_parser_v1_new (), - mm_serial_parser_v1_destroy); + mm_at_serial_port_set_response_parser (serial, + mm_serial_parser_v1_parse, + mm_serial_parser_v1_new (), + mm_serial_parser_v1_destroy); /* Open the port */ task_priv->probe_port = serial; @@ -695,20 +872,6 @@ mm_plugin_base_get_cached_port_capabilities (MMPluginBase *self, static void modem_destroyed (gpointer data, GObject *modem) { - MMPluginBase *self = MM_PLUGIN_BASE (data); - MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); - GHashTableIter iter; - gpointer key, value; - - /* Remove it from the modems info */ - g_hash_table_iter_init (&iter, priv->modems); - while (g_hash_table_iter_next (&iter, &key, &value)) { - if (value == modem) { - g_hash_table_iter_remove (&iter); - break; - } - } - /* Since we don't track port cached capabilities on a per-modem basis, * we just have to live with blowing away the cached capabilities whenever * a modem gets removed. Could do better here by storing a structure @@ -719,48 +882,6 @@ modem_destroyed (gpointer data, GObject *modem) g_hash_table_remove_all (cached_caps); } -MMModem * -mm_plugin_base_find_modem (MMPluginBase *self, - const char *master_device) -{ - MMPluginBasePrivate *priv; - - g_return_val_if_fail (self != NULL, NULL); - g_return_val_if_fail (MM_IS_PLUGIN_BASE (self), NULL); - g_return_val_if_fail (master_device != NULL, NULL); - g_return_val_if_fail (strlen (master_device) > 0, NULL); - - priv = MM_PLUGIN_BASE_GET_PRIVATE (self); - return g_hash_table_lookup (priv->modems, master_device); -} - -/* From hostap, Copyright (c) 2002-2005, Jouni Malinen */ - -static int hex2num (char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - return -1; -} - -static int hex2byte (const char *hex) -{ - int a, b; - a = hex2num(*hex++); - if (a < 0) - return -1; - b = hex2num(*hex++); - if (b < 0) - return -1; - return (a << 4) | b; -} - -/* End from hostap */ - gboolean mm_plugin_base_get_device_ids (MMPluginBase *self, const char *subsys, @@ -793,8 +914,8 @@ mm_plugin_base_get_device_ids (MMPluginBase *self, goto out; if (vendor) { - *vendor = (guint16) (hex2byte (vid + 2) & 0xFF); - *vendor |= (guint16) ((hex2byte (vid) & 0xFF) << 8); + *vendor = (guint16) (utils_hex2byte (vid + 2) & 0xFF); + *vendor |= (guint16) ((utils_hex2byte (vid) & 0xFF) << 8); } pid = g_udev_device_get_property (device, "ID_MODEL_ID"); @@ -804,8 +925,8 @@ mm_plugin_base_get_device_ids (MMPluginBase *self, } if (product) { - *product = (guint16) (hex2byte (pid + 2) & 0xFF); - *product |= (guint16) ((hex2byte (pid) & 0xFF) << 8); + *product = (guint16) (utils_hex2byte (pid + 2) & 0xFF); + *product |= (guint16) ((utils_hex2byte (pid) & 0xFF) << 8); } success = TRUE; @@ -859,58 +980,21 @@ get_driver_name (GUdevDevice *device) return ret; } -static GUdevDevice * -real_find_physical_device (MMPluginBase *plugin, GUdevDevice *child) -{ - GUdevDevice *iter, *old = NULL; - GUdevDevice *physdev = NULL; - const char *subsys, *type; - guint32 i = 0; - gboolean is_usb = FALSE, is_pci = FALSE; - - g_return_val_if_fail (child != NULL, NULL); - - iter = g_object_ref (child); - while (iter && i++ < 8) { - subsys = g_udev_device_get_subsystem (iter); - if (subsys) { - if (is_usb || !strcmp (subsys, "usb")) { - is_usb = TRUE; - type = g_udev_device_get_devtype (iter); - if (type && !strcmp (type, "usb_device")) { - physdev = iter; - break; - } - } else if (is_pci || !strcmp (subsys, "pci")) { - is_pci = TRUE; - physdev = iter; - break; - } - } - - old = iter; - iter = g_udev_device_get_parent (old); - g_object_unref (old); - } - - return physdev; -} - static MMPluginSupportsResult supports_port (MMPlugin *plugin, const char *subsys, const char *name, + const char *physdev_path, + MMModem *existing, MMSupportsPortResultFunc callback, gpointer callback_data) { MMPluginBase *self = MM_PLUGIN_BASE (plugin); MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); - GUdevDevice *port = NULL, *physdev = NULL; + GUdevDevice *port = NULL; char *driver = NULL, *key = NULL; MMPluginBaseSupportsTask *task; MMPluginSupportsResult result = MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED; - MMModem *existing; - const char *master_path; key = get_key (subsys, name); task = g_hash_table_lookup (priv->tasks, key); @@ -923,22 +1007,14 @@ supports_port (MMPlugin *plugin, if (!port) goto out; - physdev = MM_PLUGIN_BASE_GET_CLASS (self)->find_physical_device (self, port); - if (!physdev) - goto out; - driver = get_driver_name (port); if (!driver) goto out; - task = supports_task_new (self, port, physdev, driver, callback, callback_data); + task = supports_task_new (self, port, physdev_path, driver, callback, callback_data); g_assert (task); g_hash_table_insert (priv->tasks, g_strdup (key), g_object_ref (task)); - /* Help the plugin out a bit by finding an existing modem for this port */ - master_path = g_udev_device_get_sysfs_path (physdev); - existing = g_hash_table_lookup (priv->modems, master_path); - result = MM_PLUGIN_BASE_GET_CLASS (self)->supports_port (self, existing, task); if (result != MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS) { /* If the plugin doesn't support the port at all, the supports task is @@ -949,8 +1025,6 @@ supports_port (MMPlugin *plugin, g_object_unref (task); out: - if (physdev) - g_object_unref (physdev); if (port) g_object_unref (port); g_free (key); @@ -984,14 +1058,14 @@ static MMModem * grab_port (MMPlugin *plugin, const char *subsys, const char *name, + MMModem *existing, GError **error) { MMPluginBase *self = MM_PLUGIN_BASE (plugin); MMPluginBasePrivate *priv = MM_PLUGIN_BASE_GET_PRIVATE (self); MMPluginBaseSupportsTask *task; + MMModem *modem = NULL; char *key; - MMModem *existing = NULL, *modem = NULL; - const char *master_path; key = get_key (subsys, name); task = g_hash_table_lookup (priv->tasks, key); @@ -1000,16 +1074,10 @@ grab_port (MMPlugin *plugin, g_return_val_if_fail (task != NULL, FALSE); } - /* Help the plugin out a bit by finding an existing modem for this port */ - master_path = g_udev_device_get_sysfs_path (mm_plugin_base_supports_task_get_physdev (task)); - existing = g_hash_table_lookup (priv->modems, master_path); - /* Let the modem grab the port */ modem = MM_PLUGIN_BASE_GET_CLASS (self)->grab_port (self, existing, task, error); - if (modem && !existing) { - g_hash_table_insert (priv->modems, g_strdup (master_path), modem); + if (modem && !existing) g_object_weak_ref (G_OBJECT (modem), modem_destroyed, self); - } g_hash_table_remove (priv->tasks, key); g_free (key); @@ -1039,7 +1107,6 @@ mm_plugin_base_init (MMPluginBase *self) priv->client = g_udev_client_new (subsys); - priv->modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); priv->tasks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref); } @@ -1086,7 +1153,6 @@ finalize (GObject *object) g_object_unref (priv->client); - g_hash_table_destroy (priv->modems); g_hash_table_destroy (priv->tasks); G_OBJECT_CLASS (mm_plugin_base_parent_class)->finalize (object); @@ -1099,7 +1165,6 @@ mm_plugin_base_class_init (MMPluginBaseClass *klass) g_type_class_add_private (object_class, sizeof (MMPluginBasePrivate)); - klass->find_physical_device = real_find_physical_device; klass->handle_probe_response = real_handle_probe_response; /* Virtual methods */ diff --git a/src/mm-plugin-base.h b/src/mm-plugin-base.h index 49e68d4..a32d53b 100644 --- a/src/mm-plugin-base.h +++ b/src/mm-plugin-base.h @@ -37,6 +37,7 @@ #define MM_PLUGIN_BASE_PORT_CAP_W 0x0080 /* Wireless commands */ #define MM_PLUGIN_BASE_PORT_CAP_IS856 0x0100 /* CDMA 3G EVDO rev 0 */ #define MM_PLUGIN_BASE_PORT_CAP_IS856_A 0x0200 /* CDMA 3G EVDO rev A */ +#define MM_PLUGIN_BASE_PORT_CAP_QCDM 0x0400 /* QCDM-capable port */ #define MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK (mm_plugin_base_supports_task_get_type ()) #define MM_PLUGIN_BASE_SUPPORTS_TASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, MMPluginBaseSupportsTask)) @@ -59,7 +60,7 @@ MMPlugin *mm_plugin_base_supports_task_get_plugin (MMPluginBaseSupportsTask *tas GUdevDevice *mm_plugin_base_supports_task_get_port (MMPluginBaseSupportsTask *task); -GUdevDevice *mm_plugin_base_supports_task_get_physdev (MMPluginBaseSupportsTask *task); +const char *mm_plugin_base_supports_task_get_physdev_path (MMPluginBaseSupportsTask *task); const char *mm_plugin_base_supports_task_get_driver (MMPluginBaseSupportsTask *task); @@ -107,13 +108,6 @@ struct _MMPluginBaseClass { void (*cancel_task) (MMPluginBase *plugin, MMPluginBaseSupportsTask *task); - /* Find a the physical device of a port, ie the USB or PCI or whatever - * "master" device that owns the port. The GUdevDevice object returned - * will be unref-ed by the caller. - */ - GUdevDevice * (*find_physical_device) (MMPluginBase *plugin, - GUdevDevice *port); - void (*handle_probe_response) (MMPluginBase *plugin, MMPluginBaseSupportsTask *task, const char *command, @@ -128,9 +122,6 @@ struct _MMPluginBaseClass { GType mm_plugin_base_get_type (void); -MMModem *mm_plugin_base_find_modem (MMPluginBase *self, - const char *master_device); - gboolean mm_plugin_base_get_device_ids (MMPluginBase *self, const char *subsys, const char *name, diff --git a/src/mm-plugin.c b/src/mm-plugin.c index 830f2d6..7e5c582 100644 --- a/src/mm-plugin.c +++ b/src/mm-plugin.c @@ -28,15 +28,24 @@ MMPluginSupportsResult mm_plugin_supports_port (MMPlugin *plugin, const char *subsys, const char *name, + const char *physdev_path, + MMModem *existing, MMSupportsPortResultFunc callback, gpointer user_data) { g_return_val_if_fail (MM_IS_PLUGIN (plugin), FALSE); g_return_val_if_fail (subsys != NULL, FALSE); g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (physdev_path != NULL, FALSE); g_return_val_if_fail (callback != NULL, FALSE); - return MM_PLUGIN_GET_INTERFACE (plugin)->supports_port (plugin, subsys, name, callback, user_data); + return MM_PLUGIN_GET_INTERFACE (plugin)->supports_port (plugin, + subsys, + name, + physdev_path, + existing, + callback, + user_data); } void @@ -55,13 +64,14 @@ MMModem * mm_plugin_grab_port (MMPlugin *plugin, const char *subsys, const char *name, + MMModem *existing, GError **error) { g_return_val_if_fail (MM_IS_PLUGIN (plugin), FALSE); g_return_val_if_fail (subsys != NULL, FALSE); g_return_val_if_fail (name != NULL, FALSE); - return MM_PLUGIN_GET_INTERFACE (plugin)->grab_port (plugin, subsys, name, error); + return MM_PLUGIN_GET_INTERFACE (plugin)->grab_port (plugin, subsys, name, existing, error); } /*****************************************************************************/ diff --git a/src/mm-plugin.h b/src/mm-plugin.h index d1e85b6..4f1dfc0 100644 --- a/src/mm-plugin.h +++ b/src/mm-plugin.h @@ -70,6 +70,8 @@ struct _MMPlugin { MMPluginSupportsResult (*supports_port) (MMPlugin *self, const char *subsys, const char *name, + const char *physdev_path, + MMModem *existing, MMSupportsPortResultFunc callback, gpointer user_data); @@ -94,6 +96,7 @@ struct _MMPlugin { MMModem * (*grab_port) (MMPlugin *self, const char *subsys, const char *name, + MMModem *existing, GError **error); }; @@ -104,6 +107,8 @@ const char *mm_plugin_get_name (MMPlugin *plugin); MMPluginSupportsResult mm_plugin_supports_port (MMPlugin *plugin, const char *subsys, const char *name, + const char *physdev_path, + MMModem *existing, MMSupportsPortResultFunc callback, gpointer user_data); @@ -114,6 +119,7 @@ void mm_plugin_cancel_supports_port (MMPlugin *plugin, MMModem *mm_plugin_grab_port (MMPlugin *plugin, const char *subsys, const char *name, + MMModem *existing, GError **error); #endif /* MM_PLUGIN_H */ diff --git a/src/mm-port.c b/src/mm-port.c index 7e8edc4..b2018fe 100644 --- a/src/mm-port.c +++ b/src/mm-port.c @@ -19,6 +19,7 @@ #include #include "mm-port.h" +#include "mm-options.h" G_DEFINE_TYPE (MMPort, mm_port, G_TYPE_OBJECT) @@ -45,6 +46,26 @@ typedef struct { /*****************************************************************************/ +const char * +mm_port_type_to_name (MMPortType ptype) +{ + switch (ptype) { + case MM_PORT_TYPE_PRIMARY: + return "primary"; + case MM_PORT_TYPE_SECONDARY: + return "secondary"; + case MM_PORT_TYPE_IGNORED: + return "ignored"; + case MM_PORT_TYPE_QCDM: + return "QCDM"; + default: + break; + } + return "(unknown)"; +} + +/*****************************************************************************/ + static GObject* constructor (GType type, guint n_construct_params, @@ -140,6 +161,16 @@ mm_port_set_connected (MMPort *self, gboolean connected) if (priv->connected != connected) { priv->connected = connected; g_object_notify (G_OBJECT (self), MM_PORT_CONNECTED); + if (mm_options_debug()) { + GTimeVal tv; + + g_get_current_time (&tv); + g_debug ("<%ld.%ld> (%s): port now %s", + tv.tv_sec, + tv.tv_usec, + priv->device, + connected ? "connected" : "disconnected"); + } } } diff --git a/src/mm-port.h b/src/mm-port.h index b537618..43f78f4 100644 --- a/src/mm-port.h +++ b/src/mm-port.h @@ -33,8 +33,9 @@ typedef enum { MM_PORT_TYPE_PRIMARY, MM_PORT_TYPE_SECONDARY, MM_PORT_TYPE_IGNORED, + MM_PORT_TYPE_QCDM, - MM_PORT_TYPE_LAST = MM_PORT_TYPE_IGNORED + MM_PORT_TYPE_LAST = MM_PORT_TYPE_QCDM } MMPortType; #define MM_TYPE_PORT (mm_port_get_type ()) @@ -75,5 +76,7 @@ gboolean mm_port_get_connected (MMPort *self); void mm_port_set_connected (MMPort *self, gboolean connected); +const char * mm_port_type_to_name (MMPortType ptype); + #endif /* MM_PORT_H */ diff --git a/src/mm-qcdm-serial-port.c b/src/mm-qcdm-serial-port.c new file mode 100644 index 0000000..1ce27e7 --- /dev/null +++ b/src/mm-qcdm-serial-port.c @@ -0,0 +1,225 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#include +#include +#include +#include + +#include "mm-qcdm-serial-port.h" +#include "mm-errors.h" +#include "mm-options.h" +#include "libqcdm/src/com.h" +#include "libqcdm/src/utils.h" + +G_DEFINE_TYPE (MMQcdmSerialPort, mm_qcdm_serial_port, MM_TYPE_SERIAL_PORT) + +#define MM_QCDM_SERIAL_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_QCDM_SERIAL_PORT, MMQcdmSerialPortPrivate)) + +typedef struct { + gboolean foo; +} MMQcdmSerialPortPrivate; + + +/*****************************************************************************/ + +static gboolean +parse_response (MMSerialPort *port, GByteArray *response, GError **error) +{ + int i; + + /* Look for the QCDM packet termination character; if we found it, treat + * the buffer as a qcdm command. + */ + for (i = 0; i < response->len; i++) { + if (response->data[i] == 0x7E) + return TRUE; + } + + /* Otherwise, need more data from the device */ + return FALSE; +} + +static gsize +handle_response (MMSerialPort *port, + GByteArray *response, + GError *error, + GCallback callback, + gpointer callback_data) +{ + MMQcdmSerialResponseFn response_callback = (MMQcdmSerialResponseFn) callback; + GByteArray *unescaped = NULL; + GError *dm_error = NULL; + gsize used = 0; + + /* Ignore empty frames */ + if (response->len > 0 && response->data[0] == 0x7E) + return 1; + + if (!error) { + gboolean more = FALSE, success; + gsize unescaped_len = 0; + + /* FIXME: don't munge around with byte array internals */ + unescaped = g_byte_array_sized_new (1024); + success = dm_decapsulate_buffer ((const char *) response->data, + response->len, + (char *) unescaped->data, + 1024, + &unescaped_len, + &used, + &more); + if (!success) { + g_set_error_literal (&dm_error, + MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "Failed to unescape QCDM packet."); + g_byte_array_free (unescaped, TRUE); + unescaped = NULL; + } else if (more) { + /* Need more data; we shouldn't have gotten here since the parse + * function checks for the end-of-frame marker, but whatever. + */ + return 0; + } else { + /* Successfully decapsulated the DM command */ + unescaped->len = (guint) unescaped_len; + } + } + + response_callback (MM_QCDM_SERIAL_PORT (port), + unescaped, + dm_error ? dm_error : error, + callback_data); + + if (unescaped) + g_byte_array_free (unescaped, TRUE); + + return used; +} + +/*****************************************************************************/ + +void +mm_qcdm_serial_port_queue_command (MMQcdmSerialPort *self, + GByteArray *command, + guint32 timeout_seconds, + MMQcdmSerialResponseFn callback, + gpointer user_data) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_QCDM_SERIAL_PORT (self)); + g_return_if_fail (command != NULL); + + /* 'command' is expected to be already CRC-ed and escaped */ + mm_serial_port_queue_command (MM_SERIAL_PORT (self), + command, + TRUE, + timeout_seconds, + (MMSerialResponseFn) callback, + user_data); +} + +void +mm_qcdm_serial_port_queue_command_cached (MMQcdmSerialPort *self, + GByteArray *command, + guint32 timeout_seconds, + MMQcdmSerialResponseFn callback, + gpointer user_data) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_QCDM_SERIAL_PORT (self)); + g_return_if_fail (command != NULL); + + /* 'command' is expected to be already CRC-ed and escaped */ + mm_serial_port_queue_command_cached (MM_SERIAL_PORT (self), + command, + TRUE, + timeout_seconds, + (MMSerialResponseFn) callback, + user_data); +} + +static void +debug_log (MMSerialPort *port, const char *prefix, const char *buf, gsize len) +{ + static GString *debug = NULL; + const char *s = buf; + GTimeVal tv; + + if (!debug) + debug = g_string_sized_new (512); + + g_string_append (debug, prefix); + + while (len--) + g_string_append_printf (debug, " %02x", (guint8) (*s++ & 0xFF)); + + g_get_current_time (&tv); + g_debug ("<%ld.%ld> (%s): %s", + tv.tv_sec, + tv.tv_usec, + mm_port_get_device (MM_PORT (port)), + debug->str); + g_string_truncate (debug, 0); +} + +/*****************************************************************************/ + +static gboolean +config_fd (MMSerialPort *port, int fd, GError **error) +{ + return qcdm_port_setup (fd, error); +} + +/*****************************************************************************/ + +MMQcdmSerialPort * +mm_qcdm_serial_port_new (const char *name, MMPortType ptype) +{ + return MM_QCDM_SERIAL_PORT (g_object_new (MM_TYPE_QCDM_SERIAL_PORT, + MM_PORT_DEVICE, name, + MM_PORT_SUBSYS, MM_PORT_SUBSYS_TTY, + MM_PORT_TYPE, ptype, + NULL)); +} + +static void +mm_qcdm_serial_port_init (MMQcdmSerialPort *self) +{ +} + +static void +finalize (GObject *object) +{ + G_OBJECT_CLASS (mm_qcdm_serial_port_parent_class)->finalize (object); +} + +static void +mm_qcdm_serial_port_class_init (MMQcdmSerialPortClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMSerialPortClass *port_class = MM_SERIAL_PORT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMQcdmSerialPortPrivate)); + + /* Virtual methods */ + object_class->finalize = finalize; + + port_class->parse_response = parse_response; + port_class->handle_response = handle_response; + port_class->config_fd = config_fd; + port_class->debug_log = debug_log; +} diff --git a/src/mm-qcdm-serial-port.h b/src/mm-qcdm-serial-port.h new file mode 100644 index 0000000..e70e124 --- /dev/null +++ b/src/mm-qcdm-serial-port.h @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. + */ + +#ifndef MM_QCDM_SERIAL_PORT_H +#define MM_QCDM_SERIAL_PORT_H + +#include +#include +#include + +#include "mm-serial-port.h" + +#define MM_TYPE_QCDM_SERIAL_PORT (mm_qcdm_serial_port_get_type ()) +#define MM_QCDM_SERIAL_PORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_QCDM_SERIAL_PORT, MMQcdmSerialPort)) +#define MM_QCDM_SERIAL_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_QCDM_SERIAL_PORT, MMQcdmSerialPortClass)) +#define MM_IS_QCDM_SERIAL_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_QCDM_SERIAL_PORT)) +#define MM_IS_QCDM_SERIAL_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_QCDM_SERIAL_PORT)) +#define MM_QCDM_SERIAL_PORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_QCDM_SERIAL_PORT, MMQcdmSerialPortClass)) + +typedef struct _MMQcdmSerialPort MMQcdmSerialPort; +typedef struct _MMQcdmSerialPortClass MMQcdmSerialPortClass; + +typedef void (*MMQcdmSerialResponseFn) (MMQcdmSerialPort *port, + GByteArray *response, + GError *error, + gpointer user_data); + +struct _MMQcdmSerialPort { + MMSerialPort parent; +}; + +struct _MMQcdmSerialPortClass { + MMSerialPortClass parent; +}; + +GType mm_qcdm_serial_port_get_type (void); + +MMQcdmSerialPort *mm_qcdm_serial_port_new (const char *name, MMPortType ptype); + +void mm_qcdm_serial_port_queue_command (MMQcdmSerialPort *self, + GByteArray *command, + guint32 timeout_seconds, + MMQcdmSerialResponseFn callback, + gpointer user_data); + +void mm_qcdm_serial_port_queue_command_cached (MMQcdmSerialPort *self, + GByteArray *command, + guint32 timeout_seconds, + MMQcdmSerialResponseFn callback, + gpointer user_data); + +#endif /* MM_QCDM_SERIAL_PORT_H */ + diff --git a/src/mm-serial-parsers.c b/src/mm-serial-parsers.c index 58985d9..7c9598e 100644 --- a/src/mm-serial-parsers.c +++ b/src/mm-serial-parsers.c @@ -190,7 +190,8 @@ mm_serial_parser_v0_destroy (gpointer data) typedef struct { GRegex *regex_ok; GRegex *regex_connect; - GRegex *regex_detailed_error; + GRegex *regex_cme_error; + GRegex *regex_cme_error_str; GRegex *regex_unknown_error; GRegex *regex_connect_failed; } MMSerialParserV1; @@ -205,7 +206,8 @@ mm_serial_parser_v1_new (void) parser->regex_ok = g_regex_new ("\\r\\nOK(\\r\\n)+$", flags, 0, NULL); parser->regex_connect = g_regex_new ("\\r\\nCONNECT.*\\r\\n", flags, 0, NULL); - parser->regex_detailed_error = g_regex_new ("\\r\\n\\+CME ERROR: (\\d+)\\r\\n$", flags, 0, NULL); + parser->regex_cme_error = g_regex_new ("\\r\\n\\+CME ERROR: (\\d+)\\r\\n$", flags, 0, NULL); + parser->regex_cme_error_str = g_regex_new ("\\r\\n\\+CME ERROR: ([^\\n\\r]+)\\r\\n$", flags, 0, NULL); parser->regex_unknown_error = g_regex_new ("\\r\\n(ERROR)|(COMMAND NOT SUPPORT)\\r\\n$", flags, 0, NULL); parser->regex_connect_failed = g_regex_new ("\\r\\n(NO CARRIER)|(BUSY)|(NO ANSWER)|(NO DIALTONE)\\r\\n$", flags, 0, NULL); @@ -219,9 +221,10 @@ mm_serial_parser_v1_parse (gpointer data, { MMSerialParserV1 *parser = (MMSerialParserV1 *) data; GMatchInfo *match_info; - GError *local_error; - int code; + GError *local_error = NULL; gboolean found = FALSE; + char *str; + int code; g_return_val_if_fail (parser != NULL, FALSE); g_return_val_if_fail (response != NULL, FALSE); @@ -243,56 +246,70 @@ mm_serial_parser_v1_parse (gpointer data, } /* Now failures */ - code = MM_MOBILE_ERROR_UNKNOWN; - local_error = NULL; - found = g_regex_match_full (parser->regex_detailed_error, + /* Numeric CME errors */ + found = g_regex_match_full (parser->regex_cme_error, response->str, response->len, 0, 0, &match_info, NULL); - if (found) { - char *str; + str = g_match_info_fetch (match_info, 1); + g_assert (str); + local_error = mm_mobile_error_for_code (atoi (str)); + g_free (str); + g_match_info_free (match_info); + goto done; + } + /* String CME errors */ + found = g_regex_match_full (parser->regex_cme_error_str, + response->str, response->len, + 0, 0, &match_info, NULL); + if (found) { str = g_match_info_fetch (match_info, 1); - if (str) { - code = atoi (str); - g_free (str); - } + g_assert (str); + local_error = mm_mobile_error_for_string (str); + g_free (str); g_match_info_free (match_info); - } else - found = g_regex_match_full (parser->regex_unknown_error, response->str, response->len, 0, 0, NULL, NULL); + goto done; + } - if (found) - local_error = mm_mobile_error_for_code (code); - else { - found = g_regex_match_full (parser->regex_connect_failed, - response->str, response->len, - 0, 0, &match_info, NULL); - if (found) { - char *str; + /* Last resort; unknown error */ + found = g_regex_match_full (parser->regex_unknown_error, + response->str, response->len, + 0, 0, NULL, NULL); + if (found) { + local_error = mm_mobile_error_for_code (MM_MOBILE_ERROR_UNKNOWN); + goto done; + } - str = g_match_info_fetch (match_info, 1); - if (str) { - if (!strcmp (str, "NO CARRIER")) - code = MM_MODEM_CONNECT_ERROR_NO_CARRIER; - else if (!strcmp (str, "BUSY")) - code = MM_MODEM_CONNECT_ERROR_BUSY; - else if (!strcmp (str, "NO ANSWER")) - code = MM_MODEM_CONNECT_ERROR_NO_ANSWER; - else if (!strcmp (str, "NO DIALTONE")) - code = MM_MODEM_CONNECT_ERROR_NO_DIALTONE; - else - /* uhm... make something up (yes, ok, lie!). */ - code = MM_MODEM_CONNECT_ERROR_NO_CARRIER; + /* Connection failures */ + found = g_regex_match_full (parser->regex_connect_failed, + response->str, response->len, + 0, 0, &match_info, NULL); + if (found) { + str = g_match_info_fetch (match_info, 1); + g_assert (str); + + if (!strcmp (str, "NO CARRIER")) + code = MM_MODEM_CONNECT_ERROR_NO_CARRIER; + else if (!strcmp (str, "BUSY")) + code = MM_MODEM_CONNECT_ERROR_BUSY; + else if (!strcmp (str, "NO ANSWER")) + code = MM_MODEM_CONNECT_ERROR_NO_ANSWER; + else if (!strcmp (str, "NO DIALTONE")) + code = MM_MODEM_CONNECT_ERROR_NO_DIALTONE; + else { + /* uhm... make something up (yes, ok, lie!). */ + code = MM_MODEM_CONNECT_ERROR_NO_CARRIER; + } - g_free (str); - } - g_match_info_free (match_info); + g_free (str); + g_match_info_free (match_info); - local_error = mm_modem_connect_error_for_code (code); - } + local_error = mm_modem_connect_error_for_code (code); } +done: if (found) response_clean (response); @@ -313,7 +330,8 @@ mm_serial_parser_v1_destroy (gpointer data) g_regex_unref (parser->regex_ok); g_regex_unref (parser->regex_connect); - g_regex_unref (parser->regex_detailed_error); + g_regex_unref (parser->regex_cme_error); + g_regex_unref (parser->regex_cme_error_str); g_regex_unref (parser->regex_unknown_error); g_regex_unref (parser->regex_connect_failed); diff --git a/src/mm-serial-port.c b/src/mm-serial-port.c index 2600ae5..ed44167 100644 --- a/src/mm-serial-port.c +++ b/src/mm-serial-port.c @@ -11,14 +11,14 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #define _GNU_SOURCE /* for strcasestr() */ #include #include -#include +#include #include #include #include @@ -51,20 +51,12 @@ enum { #define MM_SERIAL_PORT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_SERIAL_PORT, MMSerialPortPrivate)) typedef struct { + guint32 open_count; int fd; GHashTable *reply_cache; GIOChannel *channel; GQueue *queue; - GString *command; - GString *response; - - gboolean connected; - - /* Response parser data */ - MMSerialResponseParserFn response_parser_fn; - gpointer response_parser_user_data; - GDestroyNotify response_parser_notify; - GSList *unsolicited_msg_handlers; + GByteArray *response; struct termios old_t; @@ -148,12 +140,12 @@ void mm_serial_port_print_config (MMSerialPort *port, const char *detail) { MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (port); - struct termio stbuf; + struct termios stbuf; int err; - err = ioctl (priv->fd, TCGETA, &stbuf); + err = tcgetattr (priv->fd, &stbuf); if (err) { - g_warning ("*** %s (%s): (%s) TCGETA error %d", + g_warning ("*** %s (%s): (%s) tcgetattr() error %d", __func__, detail, mm_port_get_device (MM_PORT (port)), errno); return; } @@ -165,33 +157,6 @@ mm_serial_port_print_config (MMSerialPort *port, const char *detail) } #endif -typedef struct { - GRegex *regex; - MMSerialUnsolicitedMsgFn callback; - gpointer user_data; - GDestroyNotify notify; -} MMUnsolicitedMsgHandler; - -static void -mm_serial_port_set_cached_reply (MMSerialPort *self, - const char *command, - const char *reply) -{ - if (reply) - g_hash_table_insert (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, - g_strdup (command), - g_strdup (reply)); - else - g_hash_table_remove (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, command); -} - -static const char * -mm_serial_port_get_cached_reply (MMSerialPort *self, - const char *command) -{ - return (char *) g_hash_table_lookup (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, command); -} - static int parse_baudrate (guint i) { @@ -327,10 +292,10 @@ parse_stopbits (guint i) } static gboolean -config_fd (MMSerialPort *self, GError **error) +real_config_fd (MMSerialPort *self, int fd, GError **error) { MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); - struct termio stbuf; + struct termios stbuf; int speed; int bits; int parity; @@ -341,9 +306,9 @@ config_fd (MMSerialPort *self, GError **error) parity = parse_parity (priv->parity); stopbits = parse_stopbits (priv->stopbits); - memset (&stbuf, 0, sizeof (struct termio)); - if (ioctl (priv->fd, TCGETA, &stbuf) != 0) { - g_warning ("%s (%s): TCGETA error: %d", + memset (&stbuf, 0, sizeof (struct termios)); + if (tcgetattr (fd, &stbuf) != 0) { + g_warning ("%s (%s): tcgetattr() error: %d", __func__, mm_port_get_device (MM_PORT (self)), errno); @@ -357,10 +322,16 @@ config_fd (MMSerialPort *self, GError **error) stbuf.c_cc[VTIME] = 0; stbuf.c_cc[VEOF] = 1; - stbuf.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | CLOCAL | PARENB); - stbuf.c_cflag |= (speed | bits | CREAD | 0 | parity | stopbits); + /* Use software handshaking */ + stbuf.c_iflag |= (IXON | IXOFF | IXANY); - if (ioctl (priv->fd, TCSETA, &stbuf) < 0) { + /* Set up port speed and serial attributes; also ignore modem control + * lines since most drivers don't implement RTS/CTS anyway. + */ + stbuf.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | PARENB | CRTSCTS); + stbuf.c_cflag |= (speed | bits | CREAD | 0 | parity | stopbits | CLOCAL); + + if (tcsetattr (fd, TCSANOW, &stbuf) < 0) { g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, @@ -373,105 +344,99 @@ config_fd (MMSerialPort *self, GError **error) } static void -serial_debug (MMSerialPort *self, const char *prefix, const char *buf, int len) +serial_debug (MMSerialPort *self, const char *prefix, const char *buf, gsize len) { - static GString *debug = NULL; - const char *s; - - if (!mm_options_debug ()) - return; - - if (len < 0) - len = strlen (buf); - - if (!debug) - debug = g_string_sized_new (256); - - g_string_append (debug, prefix); - g_string_append (debug, " '"); - - s = buf; - while (len--) { - if (g_ascii_isprint (*s)) - g_string_append_c (debug, *s); - else if (*s == '\r') - g_string_append (debug, ""); - else if (*s == '\n') - g_string_append (debug, ""); - else - g_string_append_printf (debug, "\\%d", *s); - - s++; - } + g_return_if_fail (len > 0); - g_string_append_c (debug, '\''); - g_debug ("(%s): %s", mm_port_get_device (MM_PORT (self)), debug->str); - g_string_truncate (debug, 0); + if (mm_options_debug () && MM_SERIAL_PORT_GET_CLASS (self)->debug_log) + MM_SERIAL_PORT_GET_CLASS (self)->debug_log (self, prefix, buf, len); } static gboolean mm_serial_port_send_command (MMSerialPort *self, - const char *command, + GByteArray *command, GError **error) { MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); - const char *s; - int status; + int status, i = 0; int eagain_count = 1000; + const guint8 *p; if (priv->fd < 0) { - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED, + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED, "%s", "Sending command failed: device is not enabled"); return FALSE; } if (mm_port_get_connected (MM_PORT (self))) { - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED, + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED, "%s", "Sending command failed: device is connected"); return FALSE; } - g_string_truncate (priv->command, g_str_has_prefix (command, "AT") ? 0 : 2); - g_string_append (priv->command, command); - - if (command[strlen (command)] != '\r') - g_string_append_c (priv->command, '\r'); - - serial_debug (self, "-->", priv->command->str, -1); + serial_debug (self, "-->", (const char *) command->data, command->len); /* Only accept about 3 seconds of EAGAIN */ if (priv->send_delay > 0) eagain_count = 3000000 / priv->send_delay; - s = priv->command->str; - while (*s) { - status = write (priv->fd, s, 1); + while (i < command->len) { + p = &command->data[i]; + status = write (priv->fd, p, 1); if (status < 0) { if (errno == EAGAIN) { eagain_count--; if (eagain_count <= 0) { - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED, + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED, "Sending command failed: '%s'", strerror (errno)); break; } } else { - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_SEND_FAILED, + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_SEND_FAILED, "Sending command failed: '%s'", strerror (errno)); break; } } else - s++; + i++; if (priv->send_delay) usleep (priv->send_delay); } - return *s == '\0'; + return i == command->len; +} + +static void +mm_serial_port_set_cached_reply (MMSerialPort *self, + const GByteArray *command, + const GByteArray *response) +{ + MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_SERIAL_PORT (self)); + g_return_if_fail (command != NULL); + + if (response) { + GByteArray *cmd_copy = g_byte_array_sized_new (command->len); + GByteArray *rsp_copy = g_byte_array_sized_new (response->len); + + g_byte_array_append (cmd_copy, command->data, command->len); + g_byte_array_append (rsp_copy, response->data, response->len); + g_hash_table_insert (priv->reply_cache, cmd_copy, rsp_copy); + } else + g_hash_table_remove (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, command); +} + +static const GByteArray * +mm_serial_port_get_cached_reply (MMSerialPort *self, GByteArray *command) +{ + return (const GByteArray *) g_hash_table_lookup (MM_SERIAL_PORT_GET_PRIVATE (self)->reply_cache, command); } typedef struct { - char *command; - MMSerialResponseFn callback; + GByteArray *command; + GCallback callback; gpointer user_data; guint32 timeout; gboolean cached; @@ -500,11 +465,25 @@ mm_serial_port_schedule_queue_process (MMSerialPort *self) g_source_unref (source); } +static gsize +real_handle_response (MMSerialPort *self, + GByteArray *response, + GError *error, + GCallback callback, + gpointer callback_data) +{ + MMSerialResponseFn response_callback = (MMSerialResponseFn) callback; + + response_callback (self, response, error, callback_data); + return response->len; +} + static void mm_serial_port_got_response (MMSerialPort *self, GError *error) { MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); MMQueueData *info; + gsize consumed = priv->response->len; if (priv->timeout_id) { g_source_remove (priv->timeout_id); @@ -514,19 +493,26 @@ mm_serial_port_got_response (MMSerialPort *self, GError *error) info = (MMQueueData *) g_queue_pop_head (priv->queue); if (info) { if (info->cached && !error) - mm_serial_port_set_cached_reply (self, info->command, priv->response->str); - - if (info->callback) - info->callback (self, priv->response, error, info->user_data); + mm_serial_port_set_cached_reply (self, info->command, priv->response); + + if (info->callback) { + g_warn_if_fail (MM_SERIAL_PORT_GET_CLASS (self)->handle_response != NULL); + consumed = MM_SERIAL_PORT_GET_CLASS (self)->handle_response (self, + priv->response, + error, + info->callback, + info->user_data); + } - g_free (info->command); + g_byte_array_free (info->command, TRUE); g_slice_free (MMQueueData, info); } if (error) g_error_free (error); - g_string_truncate (priv->response, 0); + if (consumed) + g_byte_array_remove_range (priv->response, 0, consumed); if (!g_queue_is_empty (priv->queue)) mm_serial_port_schedule_queue_process (self); } @@ -541,7 +527,7 @@ mm_serial_port_timed_out (gpointer data) priv->timeout_id = 0; error = g_error_new_literal (MM_SERIAL_ERROR, - MM_SERIAL_RESPONSE_TIMEOUT, + MM_SERIAL_ERROR_RESPONSE_TIMEOUT, "Serial command timed out"); /* FIXME: This is not completely correct - if the response finally arrives and there's some other command waiting for response right now, the other command will @@ -566,10 +552,10 @@ mm_serial_port_queue_process (gpointer data) return FALSE; if (info->cached) { - const char *cached = mm_serial_port_get_cached_reply (self, info->command); + const GByteArray *cached = mm_serial_port_get_cached_reply (self, info->command); if (cached) { - g_string_append (priv->response, cached); + g_byte_array_append (priv->response, cached->data, cached->len); mm_serial_port_got_response (self, NULL); return FALSE; } @@ -590,111 +576,16 @@ mm_serial_port_queue_process (gpointer data) return FALSE; } -void -mm_serial_port_add_unsolicited_msg_handler (MMSerialPort *self, - GRegex *regex, - MMSerialUnsolicitedMsgFn callback, - gpointer user_data, - GDestroyNotify notify) -{ - MMUnsolicitedMsgHandler *handler; - MMSerialPortPrivate *priv; - - g_return_if_fail (MM_IS_SERIAL_PORT (self)); - g_return_if_fail (regex != NULL); - - handler = g_slice_new (MMUnsolicitedMsgHandler); - handler->regex = g_regex_ref (regex); - handler->callback = callback; - handler->user_data = user_data; - handler->notify = notify; - - priv = MM_SERIAL_PORT_GET_PRIVATE (self); - priv->unsolicited_msg_handlers = g_slist_append (priv->unsolicited_msg_handlers, handler); -} - -void -mm_serial_port_set_response_parser (MMSerialPort *self, - MMSerialResponseParserFn fn, - gpointer user_data, - GDestroyNotify notify) -{ - MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); - - g_return_if_fail (MM_IS_SERIAL_PORT (self)); - - if (priv->response_parser_notify) - priv->response_parser_notify (priv->response_parser_user_data); - - priv->response_parser_fn = fn; - priv->response_parser_user_data = user_data; - priv->response_parser_notify = notify; -} - -static gboolean -remove_eval_cb (const GMatchInfo *match_info, - GString *result, - gpointer user_data) -{ - int *result_len = (int *) user_data; - int start; - int end; - - if (g_match_info_fetch_pos (match_info, 0, &start, &end)) - *result_len -= (end - start); - - return FALSE; -} - -static void -parse_unsolicited_messages (MMSerialPort *self, - GString *response) -{ - MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); - GSList *iter; - - for (iter = priv->unsolicited_msg_handlers; iter; iter = iter->next) { - MMUnsolicitedMsgHandler *handler = (MMUnsolicitedMsgHandler *) iter->data; - GMatchInfo *match_info; - gboolean matches; - - matches = g_regex_match_full (handler->regex, response->str, response->len, 0, 0, &match_info, NULL); - if (handler->callback) { - while (g_match_info_matches (match_info)) { - handler->callback (self, match_info, handler->user_data); - g_match_info_next (match_info, NULL); - } - } - - g_match_info_free (match_info); - - if (matches) { - /* Remove matches */ - char *str; - int result_len = response->len; - - str = g_regex_replace_eval (handler->regex, response->str, response->len, 0, 0, - remove_eval_cb, &result_len, NULL); - - g_string_truncate (response, 0); - g_string_append_len (response, str, result_len); - g_free (str); - } - } -} - static gboolean parse_response (MMSerialPort *self, - GString *response, + GByteArray *response, GError **error) { - MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); - - g_return_val_if_fail (priv->response_parser_fn != NULL, FALSE); - - parse_unsolicited_messages (self, response); + if (MM_SERIAL_PORT_GET_CLASS (self)->parse_unsolicited) + MM_SERIAL_PORT_GET_CLASS (self)->parse_unsolicited (self, response); - return priv->response_parser_fn (priv->response_parser_user_data, response, error); + g_return_val_if_fail (MM_SERIAL_PORT_GET_CLASS (self)->parse_response, FALSE); + return MM_SERIAL_PORT_GET_CLASS (self)->parse_response (self, response, error); } static gboolean @@ -709,13 +600,15 @@ data_available (GIOChannel *source, GIOStatus status; if (condition & G_IO_HUP) { - g_string_truncate (priv->response, 0); - mm_serial_port_close (self); + if (priv->response->len) + g_byte_array_remove_range (priv->response, 0, priv->response->len); + mm_serial_port_close_force (self); return FALSE; } if (condition & G_IO_ERR) { - g_string_truncate (priv->response, 0); + if (priv->response->len) + g_byte_array_remove_range (priv->response, 0, priv->response->len); return TRUE; } @@ -724,9 +617,13 @@ data_available (GIOChannel *source, status = g_io_channel_read_chars (source, buf, SERIAL_BUF_SIZE, &bytes_read, &err); if (status == G_IO_STATUS_ERROR) { - g_warning ("%s", err->message); - g_error_free (err); - err = NULL; + if (err && err->message) + g_warning ("%s", err->message); + g_clear_error (&err); + + /* Serial port is closed; we're done */ + if (priv->watch_id == 0) + break; } /* If no bytes read, just let g_io_channel wait for more data */ @@ -735,14 +632,14 @@ data_available (GIOChannel *source, if (bytes_read > 0) { serial_debug (self, "<--", buf, bytes_read); - g_string_append_len (priv->response, buf, bytes_read); + g_byte_array_append (priv->response, (const guint8 *) buf, bytes_read); } - /* Make sure the string doesn't grow too long */ - if (priv->response->len > SERIAL_BUF_SIZE) { - g_warning ("%s (%s): response buffer filled before repsonse received", - G_STRFUNC, mm_port_get_device (MM_PORT (self))); - g_string_erase (priv->response, 0, (SERIAL_BUF_SIZE / 2)); + /* Make sure the response doesn't grow too long */ + if (priv->response->len > SERIAL_BUF_SIZE) { + /* Notify listeners and then trim the buffer */ + g_signal_emit_by_name (self, "buffer-full", priv->response); + g_byte_array_remove_range (priv->response, 0, (SERIAL_BUF_SIZE / 2)); } if (parse_response (self, priv->response, &err)) @@ -792,13 +689,13 @@ mm_serial_port_open (MMSerialPort *self, GError **error) priv = MM_SERIAL_PORT_GET_PRIVATE (self); - if (priv->fd >= 0) { + device = mm_port_get_device (MM_PORT (self)); + + if (priv->open_count) { /* Already open */ - return TRUE; + goto success; } - device = mm_port_get_device (MM_PORT (self)); - g_message ("(%s) opening serial device...", device); devfile = g_strdup_printf ("/dev/%s", device); errno = 0; @@ -812,32 +709,29 @@ mm_serial_port_open (MMSerialPort *self, GError **error) */ g_set_error (error, MM_SERIAL_ERROR, - (errno == ENODEV) ? MM_SERIAL_OPEN_FAILED_NO_DEVICE : MM_SERIAL_OPEN_FAILED, + (errno == ENODEV) ? MM_SERIAL_ERROR_OPEN_FAILED_NO_DEVICE : MM_SERIAL_ERROR_OPEN_FAILED, "Could not open serial device %s: %s", device, strerror (errno)); return FALSE; } if (ioctl (priv->fd, TIOCEXCL) < 0) { - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_OPEN_FAILED, + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_OPEN_FAILED, "Could not lock serial device %s: %s", device, strerror (errno)); - close (priv->fd); - priv->fd = -1; - return FALSE; + goto error; } - if (ioctl (priv->fd, TCGETA, &priv->old_t) < 0) { - g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_OPEN_FAILED, + /* Flush any waiting IO */ + tcflush (priv->fd, TCIOFLUSH); + + if (tcgetattr (priv->fd, &priv->old_t) < 0) { + g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_OPEN_FAILED, "Could not open serial device %s: %s", device, strerror (errno)); - close (priv->fd); - priv->fd = -1; - return FALSE; + goto error; } - if (!config_fd (self, error)) { - close (priv->fd); - priv->fd = -1; - return FALSE; - } + g_warn_if_fail (MM_SERIAL_PORT_GET_CLASS (self)->config_fd); + if (!MM_SERIAL_PORT_GET_CLASS (self)->config_fd (self, priv->fd, error)) + goto error; priv->channel = g_io_channel_unix_new (priv->fd); g_io_channel_set_encoding (priv->channel, NULL, NULL); @@ -849,17 +743,58 @@ mm_serial_port_open (MMSerialPort *self, GError **error) priv->connected_id = g_signal_connect (self, "notify::" MM_PORT_CONNECTED, G_CALLBACK (port_connected), NULL); +success: + priv->open_count++; + if (mm_options_debug ()) { + GTimeVal tv; + + g_get_current_time (&tv); + g_debug ("<%ld.%ld> (%s) device open count is %d (open)", + tv.tv_sec, tv.tv_usec, device, priv->open_count); + } return TRUE; + +error: + close (priv->fd); + priv->fd = -1; + return FALSE; +} + +gboolean +mm_serial_port_is_open (MMSerialPort *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (MM_IS_SERIAL_PORT (self), FALSE); + + return !!MM_SERIAL_PORT_GET_PRIVATE (self)->open_count; } void mm_serial_port_close (MMSerialPort *self) { MMSerialPortPrivate *priv; + const char *device; + int i; g_return_if_fail (MM_IS_SERIAL_PORT (self)); priv = MM_SERIAL_PORT_GET_PRIVATE (self); + g_return_if_fail (priv->open_count > 0); + + device = mm_port_get_device (MM_PORT (self)); + + priv->open_count--; + + if (mm_options_debug ()) { + GTimeVal tv; + + g_get_current_time (&tv); + g_debug ("<%ld.%ld> (%s) device open count is %d (close)", + tv.tv_sec, tv.tv_usec, device, priv->open_count); + } + + if (priv->open_count > 0) + return; if (priv->connected_id) { g_signal_handler_disconnect (self, priv->connected_id); @@ -867,31 +802,74 @@ mm_serial_port_close (MMSerialPort *self) } if (priv->fd >= 0) { - g_message ("(%s) closing serial device...", mm_port_get_device (MM_PORT (self))); + g_message ("(%s) closing serial device...", device); mm_port_set_connected (MM_PORT (self), FALSE); if (priv->channel) { g_source_remove (priv->watch_id); + priv->watch_id = 0; g_io_channel_shutdown (priv->channel, TRUE, NULL); g_io_channel_unref (priv->channel); priv->channel = NULL; } - if (priv->flash_id > 0) { - g_source_remove (priv->flash_id); - priv->flash_id = 0; - } + mm_serial_port_flash_cancel (self); - ioctl (priv->fd, TCSETA, &priv->old_t); + tcsetattr (priv->fd, TCSANOW, &priv->old_t); close (priv->fd); priv->fd = -1; } + + /* Clear the command queue */ + for (i = 0; i < g_queue_get_length (priv->queue); i++) { + MMQueueData *item = g_queue_peek_nth (priv->queue, i); + + if (item->callback) { + GError *error; + GByteArray *response; + + g_warn_if_fail (MM_SERIAL_PORT_GET_CLASS (self)->handle_response != NULL); + error = g_error_new_literal (MM_SERIAL_ERROR, + MM_SERIAL_ERROR_SEND_FAILED, + "Serial port is now closed"); + response = g_byte_array_sized_new (1); + g_byte_array_append (response, (const guint8 *) "\0", 1); + MM_SERIAL_PORT_GET_CLASS (self)->handle_response (self, + response, + error, + item->callback, + item->user_data); + g_error_free (error); + g_byte_array_free (response, TRUE); + } + + g_byte_array_free (item->command, TRUE); + g_slice_free (MMQueueData, item); + } + g_queue_clear (priv->queue); +} + +void +mm_serial_port_close_force (MMSerialPort *self) +{ + MMSerialPortPrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_SERIAL_PORT (self)); + + priv = MM_SERIAL_PORT_GET_PRIVATE (self); + g_return_if_fail (priv->open_count > 0); + + /* Force the port to close */ + priv->open_count = 1; + mm_serial_port_close (self); } static void internal_queue_command (MMSerialPort *self, - const char *command, + GByteArray *command, + gboolean take_command, gboolean cached, guint32 timeout_seconds, MMSerialResponseFn callback, @@ -904,15 +882,20 @@ internal_queue_command (MMSerialPort *self, g_return_if_fail (command != NULL); info = g_slice_new0 (MMQueueData); - info->command = g_strdup (command); + if (take_command) + info->command = command; + else { + info->command = g_byte_array_sized_new (command->len); + g_byte_array_append (info->command, command->data, command->len); + } info->cached = cached; info->timeout = timeout_seconds; - info->callback = callback; + info->callback = (GCallback) callback; info->user_data = user_data; /* Clear the cached value for this command if not asking for cached value */ if (!cached) - mm_serial_port_set_cached_reply (self, command, NULL); + mm_serial_port_set_cached_reply (self, info->command, NULL); g_queue_push_tail (priv->queue, info); @@ -922,22 +905,24 @@ internal_queue_command (MMSerialPort *self, void mm_serial_port_queue_command (MMSerialPort *self, - const char *command, + GByteArray *command, + gboolean take_command, guint32 timeout_seconds, MMSerialResponseFn callback, gpointer user_data) { - internal_queue_command (self, command, FALSE, timeout_seconds, callback, user_data); + internal_queue_command (self, command, take_command, FALSE, timeout_seconds, callback, user_data); } void mm_serial_port_queue_command_cached (MMSerialPort *self, - const char *command, + GByteArray *command, + gboolean take_command, guint32 timeout_seconds, MMSerialResponseFn callback, gpointer user_data) { - internal_queue_command (self, command, TRUE, timeout_seconds, callback, user_data); + internal_queue_command (self, command, take_command, TRUE, timeout_seconds, callback, user_data); } typedef struct { @@ -1030,8 +1015,14 @@ flash_do (gpointer data) priv->flash_id = 0; - if (!set_speed (info->port, info->current_speed, &error)) - g_assert (error); + if (info->current_speed) { + if (!set_speed (info->port, info->current_speed, &error)) + g_assert (error); + } else { + error = g_error_new_literal (MM_SERIAL_ERROR, + MM_SERIAL_ERROR_FLASH_FAILED, + "Failed to retrieve current speed"); + } info->callback (info->port, error, info->user_data); g_clear_error (&error); @@ -1042,6 +1033,7 @@ flash_do (gpointer data) gboolean mm_serial_port_flash (MMSerialPort *self, guint32 flash_time, + gboolean ignore_errors, MMSerialFlashFn callback, gpointer user_data) { @@ -1049,12 +1041,22 @@ mm_serial_port_flash (MMSerialPort *self, MMSerialPortPrivate *priv; speed_t cur_speed = 0; GError *error = NULL; + gboolean success; g_return_val_if_fail (MM_IS_SERIAL_PORT (self), FALSE); g_return_val_if_fail (callback != NULL, FALSE); priv = MM_SERIAL_PORT_GET_PRIVATE (self); + if (!mm_serial_port_is_open (self)) { + error = g_error_new_literal (MM_SERIAL_ERROR, + MM_SERIAL_ERROR_NOT_OPEN, + "The serial port is not open."); + callback (self, error, user_data); + g_error_free (error); + return FALSE; + } + if (priv->flash_id > 0) { error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_OPERATION_IN_PROGRESS, @@ -1064,11 +1066,13 @@ mm_serial_port_flash (MMSerialPort *self, return FALSE; } - if (!get_speed (self, &cur_speed, &error)) { + success = get_speed (self, &cur_speed, &error); + if (!success && !ignore_errors) { callback (self, error, user_data); g_error_free (error); return FALSE; } + g_clear_error (&error); info = g_slice_new0 (FlashInfo); info->port = self; @@ -1076,7 +1080,8 @@ mm_serial_port_flash (MMSerialPort *self, info->callback = callback; info->user_data = user_data; - if (!set_speed (self, B0, &error)) { + success = set_speed (self, B0, &error); + if (!success && !ignore_errors) { callback (self, error, user_data); g_error_free (error); return FALSE; @@ -1113,12 +1118,54 @@ mm_serial_port_new (const char *name, MMPortType ptype) NULL)); } +static gboolean +ba_equal (gconstpointer v1, gconstpointer v2) +{ + const GByteArray *a = v1; + const GByteArray *b = v2; + + if (!a && b) + return -1; + else if (a && !b) + return 1; + else if (!a && !b) + return 0; + + g_assert (a && b); + if (a->len < b->len) + return -1; + else if (a->len > b->len) + return 1; + + g_assert (a->len == b->len); + return !memcmp (a->data, b->data, a->len); +} + +static guint +ba_hash (gconstpointer v) +{ + /* 31 bit hash function */ + const GByteArray *array = v; + guint32 i, h = (const signed char) array->data[0]; + + for (i = 1; i < array->len; i++) + h = (h << 5) - h + (const signed char) array->data[i]; + + return h; +} + +static void +ba_free (gpointer v) +{ + g_byte_array_free ((GByteArray *) v, TRUE); +} + static void mm_serial_port_init (MMSerialPort *self) { MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); - priv->reply_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + priv->reply_cache = g_hash_table_new_full (ba_hash, ba_equal, ba_free, ba_free); priv->fd = -1; priv->baud = 57600; @@ -1128,8 +1175,7 @@ mm_serial_port_init (MMSerialPort *self) priv->send_delay = 1000; priv->queue = g_queue_new (); - priv->command = g_string_new_len ("AT", SERIAL_BUF_SIZE); - priv->response = g_string_sized_new (SERIAL_BUF_SIZE); + priv->response = g_byte_array_sized_new (500); } static void @@ -1191,7 +1237,10 @@ get_property (GObject *object, guint prop_id, static void dispose (GObject *object) { - mm_serial_port_close (MM_SERIAL_PORT (object)); + if (mm_serial_port_is_open (MM_SERIAL_PORT (object))) + mm_serial_port_close_force (MM_SERIAL_PORT (object)); + + mm_serial_port_flash_cancel (MM_SERIAL_PORT (object)); G_OBJECT_CLASS (mm_serial_port_parent_class)->dispose (object); } @@ -1203,24 +1252,8 @@ finalize (GObject *object) MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self); g_hash_table_destroy (priv->reply_cache); + g_byte_array_free (priv->response, TRUE); g_queue_free (priv->queue); - g_string_free (priv->command, TRUE); - g_string_free (priv->response, TRUE); - - while (priv->unsolicited_msg_handlers) { - MMUnsolicitedMsgHandler *handler = (MMUnsolicitedMsgHandler *) priv->unsolicited_msg_handlers->data; - - if (handler->notify) - handler->notify (handler->user_data); - - g_regex_unref (handler->regex); - g_slice_free (MMUnsolicitedMsgHandler, handler); - priv->unsolicited_msg_handlers = g_slist_delete_link (priv->unsolicited_msg_handlers, - priv->unsolicited_msg_handlers); - } - - if (priv->response_parser_notify) - priv->response_parser_notify (priv->response_parser_user_data); G_OBJECT_CLASS (mm_serial_port_parent_class)->finalize (object); } @@ -1238,6 +1271,9 @@ mm_serial_port_class_init (MMSerialPortClass *klass) object_class->dispose = dispose; object_class->finalize = finalize; + klass->config_fd = real_config_fd; + klass->handle_response = real_handle_response; + /* Properties */ g_object_class_install_property (object_class, PROP_BAUD, @@ -1278,4 +1314,14 @@ mm_serial_port_class_init (MMSerialPortClass *klass) "Send delay", 0, G_MAXUINT64, 0, G_PARAM_READWRITE)); + + /* Signals */ + g_signal_new ("buffer-full", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMSerialPortClass, buffer_full), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); } + diff --git a/src/mm-serial-port.h b/src/mm-serial-port.h index 841b4fa..f78f793 100644 --- a/src/mm-serial-port.h +++ b/src/mm-serial-port.h @@ -11,7 +11,7 @@ * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 - 2010 Red Hat, Inc. */ #ifndef MM_SERIAL_PORT_H @@ -39,67 +39,101 @@ typedef struct _MMSerialPort MMSerialPort; typedef struct _MMSerialPortClass MMSerialPortClass; -typedef gboolean (*MMSerialResponseParserFn) (gpointer user_data, - GString *response, - GError **error); - -typedef void (*MMSerialUnsolicitedMsgFn) (MMSerialPort *port, - GMatchInfo *match_info, - gpointer user_data); - -typedef void (*MMSerialResponseFn) (MMSerialPort *port, - GString *response, +typedef void (*MMSerialFlashFn) (MMSerialPort *port, GError *error, gpointer user_data); -typedef void (*MMSerialFlashFn) (MMSerialPort *port, +typedef void (*MMSerialResponseFn) (MMSerialPort *port, + GByteArray *response, GError *error, gpointer user_data); + struct _MMSerialPort { MMPort parent; }; struct _MMSerialPortClass { MMPortClass parent; + + /* Called for subclasses to parse unsolicited responses. If any recognized + * unsolicited response is found, it should be removed from the 'response' + * byte array before returning. + */ + void (*parse_unsolicited) (MMSerialPort *self, GByteArray *response); + + /* Called to parse the device's response to a command or determine if the + * response was an error response. If the response indicates an error, an + * appropriate error should be returned in the 'error' argument. The + * function should return FALSE if there is not enough data yet to determine + * the device's reply (whether success *or* error), and should return TRUE + * when the device's response has been recognized and parsed. + */ + gboolean (*parse_response) (MMSerialPort *self, + GByteArray *response, + GError **error); + + /* Called after parsing to allow the command response to be delivered to + * it's callback to be handled. Returns the # of bytes of the response + * consumed. + */ + gsize (*handle_response) (MMSerialPort *self, + GByteArray *response, + GError *error, + GCallback callback, + gpointer callback_data); + + /* Called to configure the serial port after it's opened. On error, should + * return FALSE and set 'error' as appropriate. + */ + gboolean (*config_fd) (MMSerialPort *self, int fd, GError **error); + + void (*debug_log) (MMSerialPort *self, + const char *prefix, + const char *buf, + gsize len); + + /* Signals */ + void (*buffer_full) (MMSerialPort *port, const GByteArray *buffer); }; GType mm_serial_port_get_type (void); MMSerialPort *mm_serial_port_new (const char *name, MMPortType ptype); -void mm_serial_port_add_unsolicited_msg_handler (MMSerialPort *self, - GRegex *regex, - MMSerialUnsolicitedMsgFn callback, - gpointer user_data, - GDestroyNotify notify); +/* Keep in mind that port open/close is refcounted, so ensure that + * open/close calls are properly balanced. + */ -void mm_serial_port_set_response_parser (MMSerialPort *self, - MMSerialResponseParserFn fn, - gpointer user_data, - GDestroyNotify notify); +gboolean mm_serial_port_is_open (MMSerialPort *self); gboolean mm_serial_port_open (MMSerialPort *self, GError **error); void mm_serial_port_close (MMSerialPort *self); + +void mm_serial_port_close_force (MMSerialPort *self); + +gboolean mm_serial_port_flash (MMSerialPort *self, + guint32 flash_time, + gboolean ignore_errors, + MMSerialFlashFn callback, + gpointer user_data); +void mm_serial_port_flash_cancel (MMSerialPort *self); + void mm_serial_port_queue_command (MMSerialPort *self, - const char *command, + GByteArray *command, + gboolean take_command, guint32 timeout_seconds, MMSerialResponseFn callback, gpointer user_data); void mm_serial_port_queue_command_cached (MMSerialPort *self, - const char *command, + GByteArray *command, + gboolean take_command, guint32 timeout_seconds, MMSerialResponseFn callback, gpointer user_data); -gboolean mm_serial_port_flash (MMSerialPort *self, - guint32 flash_time, - MMSerialFlashFn callback, - gpointer user_data); -void mm_serial_port_flash_cancel (MMSerialPort *self); - #endif /* MM_SERIAL_PORT_H */ diff --git a/src/mm-utils.c b/src/mm-utils.c new file mode 100644 index 0000000..56182c0 --- /dev/null +++ b/src/mm-utils.c @@ -0,0 +1,78 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "mm-utils.h" + +/* From hostap, Copyright (c) 2002-2005, Jouni Malinen */ + +static int hex2num (char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; +} + +int utils_hex2byte (const char *hex) +{ + int a, b; + a = hex2num(*hex++); + if (a < 0) + return -1; + b = hex2num(*hex++); + if (b < 0) + return -1; + return (a << 4) | b; +} + +char * +utils_hexstr2bin (const char *hex, gsize *out_len) +{ + size_t len = strlen (hex); + size_t i; + int a; + const char * ipos = hex; + char * buf = NULL; + char * opos; + + /* Length must be a multiple of 2 */ + g_return_val_if_fail ((len % 2) == 0, NULL); + + opos = buf = g_malloc0 ((len / 2) + 1); + for (i = 0; i < len; i += 2) { + a = utils_hex2byte (ipos); + if (a < 0) { + g_free (buf); + return NULL; + } + *opos++ = a; + ipos += 2; + } + *out_len = len / 2; + return buf; +} + +/* End from hostap */ + diff --git a/src/mm-utils.h b/src/mm-utils.h new file mode 100644 index 0000000..79e7827 --- /dev/null +++ b/src/mm-utils.h @@ -0,0 +1,24 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details: + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef MM_UTILS_H +#define MM_UTILS_H + +int utils_hex2byte (const char *hex); + +char *utils_hexstr2bin (const char *hex, gsize *out_len); + +#endif /* MM_UTILS_H */ + diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c index 3d93423..92a7af8 100644 --- a/src/tests/test-modem-helpers.c +++ b/src/tests/test-modem-helpers.c @@ -18,6 +18,11 @@ #include "mm-modem-helpers.h" +typedef struct { + GPtrArray *solicited_creg; + GPtrArray *unsolicited_creg; +} TestData; + #define MM_SCAN_TAG_STATUS "status" #define MM_SCAN_TAG_OPER_LONG "operator-long" #define MM_SCAN_TAG_OPER_SHORT "operator-short" @@ -35,10 +40,10 @@ typedef struct { #define ARRAY_LEN(i) (sizeof (i) / sizeof (i[0])) static void -test_results (const char *desc, - const char *reply, - OperEntry *expected_results, - guint32 expected_results_len) +test_cops_results (const char *desc, + const char *reply, + OperEntry *expected_results, + guint32 expected_results_len) { guint i; GError *error = NULL; @@ -101,7 +106,7 @@ test_cops_response_tm506 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" } }; - test_results ("TM-506", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("TM-506", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -113,7 +118,7 @@ test_cops_response_gt3gplus (void *f, gpointer d) { "1", "Cingular", "Cingular", "310410", "0" }, }; - test_results ("GlobeTrotter 3G+ (nozomi)", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("GlobeTrotter 3G+ (nozomi)", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -126,7 +131,7 @@ test_cops_response_ac881 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Sierra AirCard 881", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Sierra AirCard 881", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -139,7 +144,7 @@ test_cops_response_gtmax36 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Option GlobeTrotter MAX 3.6", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Option GlobeTrotter MAX 3.6", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -152,7 +157,7 @@ test_cops_response_ac860 (void *f, gpointer d) { "1", "Cingular", "Cinglr", "310410", "0" }, }; - test_results ("Sierra AirCard 860", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Sierra AirCard 860", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -165,7 +170,7 @@ test_cops_response_gtm378 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Option GTM378", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Option GTM378", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -177,7 +182,7 @@ test_cops_response_motoc (void *f, gpointer d) { "0", "Cingular Wireless", NULL, "310410", NULL }, }; - test_results ("BUSlink SCWi275u (Motorola C-series)", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("BUSlink SCWi275u (Motorola C-series)", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -189,7 +194,7 @@ test_cops_response_mf627a (void *f, gpointer d) { "3", "Voicestream Wireless Corporation", "VSTREAM", "31026", "0" }, }; - test_results ("ZTE MF627 (A)", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("ZTE MF627 (A)", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -201,7 +206,7 @@ test_cops_response_mf627b (void *f, gpointer d) { "3", NULL, NULL, "31026", "0" }, }; - test_results ("ZTE MF627 (B)", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("ZTE MF627 (B)", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -213,7 +218,7 @@ test_cops_response_e160g (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Huawei E160G", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Huawei E160G", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -226,7 +231,7 @@ test_cops_response_mercury (void *f, gpointer d) { "1", "T-Mobile", "TMO", "31026", "0" }, }; - test_results ("Sierra AT&T USBConnect Mercury", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Sierra AT&T USBConnect Mercury", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -239,7 +244,7 @@ test_cops_response_quicksilver (void *f, gpointer d) { "1", "AT&T", NULL, "310260", "0" }, }; - test_results ("Option AT&T Quicksilver", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Option AT&T Quicksilver", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -251,7 +256,7 @@ test_cops_response_icon225 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Option iCON 225", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Option iCON 225", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -265,7 +270,7 @@ test_cops_response_icon452 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" } }; - test_results ("Option iCON 452", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Option iCON 452", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -278,7 +283,7 @@ test_cops_response_f3507g (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "2" } }; - test_results ("Ericsson F3507g", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Ericsson F3507g", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -291,7 +296,7 @@ test_cops_response_f3607gw (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" } }; - test_results ("Ericsson F3607gw", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Ericsson F3607gw", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -304,7 +309,7 @@ test_cops_response_mc8775 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" } }; - test_results ("Sierra MC8775", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Sierra MC8775", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -317,7 +322,7 @@ test_cops_response_n80 (void *f, gpointer d) { "1", "Cingular", NULL, "31041", NULL }, }; - test_results ("Nokia N80", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Nokia N80", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -329,7 +334,7 @@ test_cops_response_e1550 (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Huawei E1550", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Huawei E1550", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -341,7 +346,7 @@ test_cops_response_mf622 (void *f, gpointer d) { "1", NULL, NULL, "310410", "0" }, }; - test_results ("ZTE MF622", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("ZTE MF622", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -354,7 +359,7 @@ test_cops_response_e226 (void *f, gpointer d) { "1", NULL, NULL, "310410", "0" }, }; - test_results ("Huawei E226", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Huawei E226", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -367,7 +372,7 @@ test_cops_response_xu870 (void *f, gpointer d) { "1", "T-Mobile", "TMO", "31026", "0" }, }; - test_results ("Novatel XU870", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Novatel XU870", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -380,7 +385,7 @@ test_cops_response_gtultraexpress (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Option GlobeTrotter Ultra Express", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Option GlobeTrotter Ultra Express", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -392,7 +397,7 @@ test_cops_response_n2720 (void *f, gpointer d) { "1", "AT&T", NULL, "310410", "0" }, }; - test_results ("Nokia 2720", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Nokia 2720", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -405,7 +410,28 @@ test_cops_response_gobi (void *f, gpointer d) { "1", "AT&T", "AT&T", "310410", "0" }, }; - test_results ("Qualcomm Gobi", reply, &expected[0], ARRAY_LEN (expected)); + test_cops_results ("Qualcomm Gobi", reply, &expected[0], ARRAY_LEN (expected)); +} + +static void +test_cops_response_sek600i (void *f, gpointer d) +{ + /* Phone is stupid enough to support 3G but not report cell technology, + * mixing together 2G and 3G cells without any way of distinguishing + * which is which... + */ + const char *reply = "+COPS: (2,\"blau\",\"\",\"26203\"),(2,\"blau\",\"\",\"26203\"),(3,\"\",\"\",\"26201\"),(3,\"\",\"\",\"26202\"),(3,\"\",\"\",\"26207\"),(3,\"\",\"\",\"26201\"),(3,\"\",\"\",\"26207\")"; + static OperEntry expected[] = { + { "2", "blau", NULL, "26203", NULL }, + { "2", "blau", NULL, "26203", NULL }, + { "3", NULL, NULL, "26201", NULL }, + { "3", NULL, NULL, "26202", NULL }, + { "3", NULL, NULL, "26207", NULL }, + { "3", NULL, NULL, "26201", NULL }, + { "3", NULL, NULL, "26207", NULL }, + }; + + test_cops_results ("Sony-Ericsson K600i", reply, &expected[0], ARRAY_LEN (expected)); } static void @@ -432,6 +458,338 @@ test_cops_response_umts_invalid (void *f, gpointer d) g_assert (error == NULL); } +typedef struct { + guint32 state; + gulong lac; + gulong ci; + gint act; + + guint regex_num; + gboolean cgreg; +} CregResult; + +static void +test_creg_match (const char *test, + gboolean solicited, + const char *reply, + TestData *data, + const CregResult *result) +{ + int i; + GMatchInfo *info = NULL; + guint32 state = 0; + gulong lac = 0, ci = 0; + gint access_tech = -1; + GError *error = NULL; + gboolean success, cgreg = FALSE; + guint regex_num = 0; + GPtrArray *array; + + g_assert (reply); + g_assert (test); + g_assert (data); + g_assert (result); + + g_print ("\nTesting %s +CREG %s response...\n", + test, + solicited ? "solicited" : "unsolicited"); + + array = solicited ? data->solicited_creg : data->unsolicited_creg; + for (i = 0; i < array->len; i++) { + GRegex *r = g_ptr_array_index (array, i); + + if (g_regex_match (r, reply, 0, &info)) { + regex_num = i + 1; + break; + } + g_match_info_free (info); + info = NULL; + } + + g_assert (info != NULL); + g_assert (regex_num == result->regex_num); + + success = mm_gsm_parse_creg_response (info, &state, &lac, &ci, &access_tech, &cgreg, &error); + g_assert (success); + g_assert (error == NULL); + g_assert (state == result->state); + g_assert (lac == result->lac); + g_assert (ci == result->ci); + g_assert (access_tech == result->act); + g_assert (cgreg == result->cgreg); +} + +static void +test_creg1_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CREG: 1,3"; + const CregResult result = { 3, 0, 0, -1 , 2, FALSE}; + + test_creg_match ("CREG=1", TRUE, reply, data, &result); +} + +static void +test_creg1_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 3\r\n"; + const CregResult result = { 3, 0, 0, -1 , 1, FALSE}; + + test_creg_match ("CREG=1", FALSE, reply, data, &result); +} + +static void +test_creg2_mercury_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CREG: 0,1,84CD,00D30173"; + const CregResult result = { 1, 0x84cd, 0xd30173, -1 , 4, FALSE}; + + test_creg_match ("Sierra Mercury CREG=2", TRUE, reply, data, &result); +} + +static void +test_creg2_mercury_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 1,84CD,00D30156\r\n"; + const CregResult result = { 1, 0x84cd, 0xd30156, -1 , 3, FALSE}; + + test_creg_match ("Sierra Mercury CREG=2", FALSE, reply, data, &result); +} + +static void +test_creg2_sek850i_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CREG: 2,1,\"CE00\",\"01CEAD8F\""; + const CregResult result = { 1, 0xce00, 0x01cead8f, -1 , 4, FALSE}; + + test_creg_match ("Sony Ericsson K850i CREG=2", TRUE, reply, data, &result); +} + +static void +test_creg2_sek850i_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 1,\"CE00\",\"00005449\"\r\n"; + const CregResult result = { 1, 0xce00, 0x5449, -1 , 3, FALSE}; + + test_creg_match ("Sony Ericsson K850i CREG=2", FALSE, reply, data, &result); +} + +static void +test_creg2_e160g_solicited_unregistered (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CREG: 2,0,00,0"; + const CregResult result = { 0, 0, 0, -1 , 4, FALSE}; + + test_creg_match ("Huawei E160G unregistered CREG=2", TRUE, reply, data, &result); +} + +static void +test_creg2_e160g_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CREG: 2,1,8BE3,2BAF"; + const CregResult result = { 1, 0x8be3, 0x2baf, -1 , 4, FALSE}; + + test_creg_match ("Huawei E160G CREG=2", TRUE, reply, data, &result); +} + +static void +test_creg2_e160g_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 2,8BE3,2BAF\r\n"; + const CregResult result = { 2, 0x8be3, 0x2baf, -1 , 3, FALSE}; + + test_creg_match ("Huawei E160G CREG=2", FALSE, reply, data, &result); +} + +static void +test_creg2_tm506_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CREG: 2,1,\"8BE3\",\"00002BAF\""; + const CregResult result = { 1, 0x8BE3, 0x2BAF, -1 , 4, FALSE}; + + /* Test leading zeros in the CI */ + test_creg_match ("Sony Ericsson TM-506 CREG=2", TRUE, reply, data, &result); +} + +static void +test_creg2_xu870_unsolicited_unregistered (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 2,,\r\n"; + const CregResult result = { 2, 0, 0, -1 , 3, FALSE}; + + test_creg_match ("Novatel XU870 unregistered CREG=2", FALSE, reply, data, &result); +} + +static void +test_cgreg1_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CGREG: 1,3"; + const CregResult result = { 3, 0, 0, -1 , 2, TRUE}; + + test_creg_match ("CGREG=1", TRUE, reply, data, &result); +} + +static void +test_cgreg1_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CGREG: 3\r\n"; + const CregResult result = { 3, 0, 0, -1 , 1, TRUE}; + + test_creg_match ("CGREG=1", FALSE, reply, data, &result); +} + +static void +test_cgreg2_f3607gw_solicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "+CGREG: 2,1,\"8BE3\",\"00002B5D\",3"; + const CregResult result = { 1, 0x8BE3, 0x2B5D, 3 , 6, TRUE}; + + test_creg_match ("Ericsson F3607gw CGREG=2", TRUE, reply, data, &result); +} + +static void +test_cgreg2_f3607gw_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CGREG: 1,\"8BE3\",\"00002B5D\",3\r\n"; + const CregResult result = { 1, 0x8BE3, 0x2B5D, 3 , 5, TRUE}; + + test_creg_match ("Ericsson F3607gw CGREG=2", FALSE, reply, data, &result); +} + +static void +test_creg2_md400_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 2,5,\"0502\",\"0404736D\"\r\n"; + const CregResult result = { 5, 0x0502, 0x0404736D, -1 , 4, FALSE}; + + test_creg_match ("Sony-Ericsson MD400 CREG=2", FALSE, reply, data, &result); +} + +static void +test_cgreg2_md400_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CGREG: 5,\"0502\",\"0404736D\",2\r\n"; + const CregResult result = { 5, 0x0502, 0x0404736D, 2, 5, TRUE}; + + test_creg_match ("Sony-Ericsson MD400 CGREG=2", FALSE, reply, data, &result); +} + +static void +test_creg_cgreg_multi_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CREG: 5\r\n\r\n+CGREG: 0\r\n"; + const CregResult result = { 5, 0, 0, -1, 1, FALSE}; + + test_creg_match ("Multi CREG/CGREG", FALSE, reply, data, &result); +} + +static void +test_creg_cgreg_multi2_unsolicited (void *f, gpointer d) +{ + TestData *data = (TestData *) d; + const char *reply = "\r\n+CGREG: 0\r\n\r\n+CREG: 5\r\n"; + const CregResult result = { 0, 0, 0, -1, 1, TRUE}; + + test_creg_match ("Multi CREG/CGREG #2", FALSE, reply, data, &result); +} + +static void +test_cscs_icon225_support_response (void *f, gpointer d) +{ + const char *reply = "\r\n+CSCS: (\"IRA\",\"GSM\",\"UCS2\")\r\n"; + MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN; + gboolean success; + + success = mm_gsm_parse_cscs_support_response (reply, &charsets); + g_assert (success); + + g_assert (charsets == (MM_MODEM_CHARSET_IRA | + MM_MODEM_CHARSET_GSM | + MM_MODEM_CHARSET_UCS2)); +} + +static void +test_cscs_sierra_mercury_support_response (void *f, gpointer d) +{ + const char *reply = "\r\n+CSCS: (\"IRA\",\"GSM\",\"UCS2\",\"PCCP437\")\r\n"; + MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN; + gboolean success; + + success = mm_gsm_parse_cscs_support_response (reply, &charsets); + g_assert (success); + + g_assert (charsets == (MM_MODEM_CHARSET_IRA | + MM_MODEM_CHARSET_GSM | + MM_MODEM_CHARSET_UCS2 | + MM_MODEM_CHARSET_PCCP437)); +} + +static void +test_cscs_buslink_support_response (void *f, gpointer d) +{ + const char *reply = "\r\n+CSCS: (\"8859-1\",\"ASCII\",\"GSM\",\"UCS2\",\"UTF8\")\r\n"; + MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN; + gboolean success; + + success = mm_gsm_parse_cscs_support_response (reply, &charsets); + g_assert (success); + + g_assert (charsets == (MM_MODEM_CHARSET_8859_1 | + MM_MODEM_CHARSET_IRA | + MM_MODEM_CHARSET_GSM | + MM_MODEM_CHARSET_UCS2 | + MM_MODEM_CHARSET_UTF8)); +} + +static void +test_cscs_blackberry_support_response (void *f, gpointer d) +{ + const char *reply = "\r\n+CSCS: \"IRA\"\r\n"; + MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN; + gboolean success; + + success = mm_gsm_parse_cscs_support_response (reply, &charsets); + g_assert (success); + + g_assert (charsets == MM_MODEM_CHARSET_IRA); +} + +static TestData * +test_data_new (void) +{ + TestData *data; + + data = g_malloc0 (sizeof (TestData)); + data->solicited_creg = mm_gsm_creg_regex_get (TRUE); + data->unsolicited_creg = mm_gsm_creg_regex_get (FALSE); + return data; +} + +static void +test_data_free (TestData *data) +{ + mm_gsm_creg_regex_destroy (data->solicited_creg); + mm_gsm_creg_regex_destroy (data->unsolicited_creg); + g_free (data); +} + typedef void (*TCFunc)(void); @@ -440,10 +798,13 @@ typedef void (*TCFunc)(void); int main (int argc, char **argv) { GTestSuite *suite; + TestData *data; + gint result; g_test_init (&argc, &argv, NULL); suite = g_test_get_root (); + data = test_data_new (); g_test_suite_add (suite, TESTCASE (test_cops_response_tm506, NULL)); g_test_suite_add (suite, TESTCASE (test_cops_response_gt3gplus, NULL)); @@ -470,10 +831,42 @@ int main (int argc, char **argv) g_test_suite_add (suite, TESTCASE (test_cops_response_gtultraexpress, NULL)); g_test_suite_add (suite, TESTCASE (test_cops_response_n2720, NULL)); g_test_suite_add (suite, TESTCASE (test_cops_response_gobi, NULL)); + g_test_suite_add (suite, TESTCASE (test_cops_response_sek600i, NULL)); g_test_suite_add (suite, TESTCASE (test_cops_response_gsm_invalid, NULL)); g_test_suite_add (suite, TESTCASE (test_cops_response_umts_invalid, NULL)); - return g_test_run (); + g_test_suite_add (suite, TESTCASE (test_creg1_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg1_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_mercury_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_mercury_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_sek850i_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_sek850i_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_e160g_solicited_unregistered, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_e160g_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_e160g_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_tm506_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_xu870_unsolicited_unregistered, data)); + g_test_suite_add (suite, TESTCASE (test_creg2_md400_unsolicited, data)); + + g_test_suite_add (suite, TESTCASE (test_cgreg1_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_cgreg1_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_cgreg2_f3607gw_solicited, data)); + g_test_suite_add (suite, TESTCASE (test_cgreg2_f3607gw_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_cgreg2_md400_unsolicited, data)); + + g_test_suite_add (suite, TESTCASE (test_creg_cgreg_multi_unsolicited, data)); + g_test_suite_add (suite, TESTCASE (test_creg_cgreg_multi2_unsolicited, data)); + + g_test_suite_add (suite, TESTCASE (test_cscs_icon225_support_response, data)); + g_test_suite_add (suite, TESTCASE (test_cscs_sierra_mercury_support_response, data)); + g_test_suite_add (suite, TESTCASE (test_cscs_buslink_support_response, data)); + g_test_suite_add (suite, TESTCASE (test_cscs_blackberry_support_response, data)); + + result = g_test_run (); + + test_data_free (data); + + return result; } diff --git a/test/lsudev.c b/test/lsudev.c index 02d9ab2..f5e4001 100644 --- a/test/lsudev.c +++ b/test/lsudev.c @@ -55,21 +55,22 @@ static void println (guint indent, const char *fmt, ...) { va_list args; - char real_fmt[1000]; + GString *output; int i; g_return_if_fail (fmt != NULL); - g_return_if_fail (indent < sizeof (real_fmt) - 2 - strlen (fmt)); + + output = g_string_sized_new (250); for (i = 0; i < indent; i++) - real_fmt[i] = ' '; - strcpy (&real_fmt[i], fmt); - real_fmt[i + strlen (fmt)] = '\n'; - real_fmt[i + strlen (fmt) + 1] = '\0'; + g_string_append_c (output, ' '); va_start (args, fmt); - vprintf (real_fmt, args); + g_string_append_vprintf (output, fmt, args); va_end (args); + + g_print ("%s\n", output->str); + g_string_free (output, TRUE); } static void @@ -88,7 +89,7 @@ dump_device_and_parent (GUdevDevice *device, guint indent) println (indent, "Path: %s", g_udev_device_get_sysfs_path (device)); println (indent, "Driver: %s", g_udev_device_get_driver (device)); println (indent, "Action: %s", g_udev_device_get_action (device)); - println (indent, "Seq Num: %s", g_udev_device_get_seqnum (device)); + println (indent, "Seq Num: %lu", g_udev_device_get_seqnum (device)); println (indent, "Dev File: %s", g_udev_device_get_device_file (device)); println (indent, ""); -- cgit v1.2.3