From 2c6a71b39e1a7573efb74456d364617d368283d1 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 22 Jan 2009 18:59:20 -0500 Subject: modem-probe: rename from probe-modem for consistency --- Makefile.am | 2 +- autogen.sh | 2 +- configure.ac | 2 +- modem-probe/.gitignore | 1 + modem-probe/77-probe-modem-capabilities.rules | 10 + modem-probe/Makefile.am | 13 + modem-probe/modem-probe.8 | 34 ++ modem-probe/modem-probe.c | 460 ++++++++++++++++++++++++++ probe-modem/.gitignore | 1 - probe-modem/77-probe-modem-capabilities.rules | 10 - probe-modem/Makefile.am | 13 - probe-modem/probe-modem.8 | 34 -- probe-modem/probe-modem.c | 460 -------------------------- 13 files changed, 521 insertions(+), 521 deletions(-) create mode 100644 modem-probe/.gitignore create mode 100644 modem-probe/77-probe-modem-capabilities.rules create mode 100644 modem-probe/Makefile.am create mode 100644 modem-probe/modem-probe.8 create mode 100644 modem-probe/modem-probe.c delete mode 100644 probe-modem/.gitignore delete mode 100644 probe-modem/77-probe-modem-capabilities.rules delete mode 100644 probe-modem/Makefile.am delete mode 100644 probe-modem/probe-modem.8 delete mode 100644 probe-modem/probe-modem.c diff --git a/Makefile.am b/Makefile.am index ad89bb0..3de6226 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ include $(top_srcdir)/Makefile.am.inc SUBDIRS = \ - probe-modem \ + modem-probe \ modem-modeswitch \ udev-acl diff --git a/autogen.sh b/autogen.sh index e6bf5bc..4287fdb 100755 --- a/autogen.sh +++ b/autogen.sh @@ -7,7 +7,7 @@ REQUIRED_AUTOMAKE_VERSION=1.9 PKG_NAME=udev-extras (test -f $srcdir/configure.ac \ - && test -f $srcdir/probe-modem/probe-modem.c) || { + && test -f $srcdir/modem-probe/modem-probe.c) || { echo -n "**Error**: Directory "\`$srcdir\'" does not look like the" echo " top-level $PKG_NAME directory" exit 1 diff --git a/configure.ac b/configure.ac index cd0e2b7..4e0f22f 100644 --- a/configure.ac +++ b/configure.ac @@ -71,7 +71,7 @@ AC_SUBST(udev_prefix) AC_CONFIG_FILES([ Makefile -probe-modem/Makefile +modem-probe/Makefile modem-modeswitch/Makefile udev-acl/Makefile ]) diff --git a/modem-probe/.gitignore b/modem-probe/.gitignore new file mode 100644 index 0000000..02e771b --- /dev/null +++ b/modem-probe/.gitignore @@ -0,0 +1 @@ +probe-modem diff --git a/modem-probe/77-probe-modem-capabilities.rules b/modem-probe/77-probe-modem-capabilities.rules new file mode 100644 index 0000000..7efb56f --- /dev/null +++ b/modem-probe/77-probe-modem-capabilities.rules @@ -0,0 +1,10 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add|change", GOTO="modem_probe_end" +SUBSYSTEM!="tty", GOTO="modem_probe_end" +KERNEL!="tty*", GOTO="modem_probe_end" + +DRIVERS=="option|sierra|cdc_acm|hso", IMPORT{program}="modem-probe --export $tempnode" + +LABEL="modem_probe_end" + diff --git a/modem-probe/Makefile.am b/modem-probe/Makefile.am new file mode 100644 index 0000000..c1df87f --- /dev/null +++ b/modem-probe/Makefile.am @@ -0,0 +1,13 @@ +include $(top_srcdir)/Makefile.am.inc + +udevhomedir = $(udev_prefix)/lib/udev +udevhome_PROGRAMS = modem-probe + +modem_probe_SOURCES = modem-probe.c +modem_probe_CPPFLAGS = $(GLIB_CFLAGS) +modem_probe_LDADD = $(GLIB_LIBS) + +udevrulesdir = $(udev_prefix)/lib/udev/rules.d +dist_udevrules_DATA = 77-probe-modem-capabilities.rules +dist_man_MANS = modem-probe.8 + diff --git a/modem-probe/modem-probe.8 b/modem-probe/modem-probe.8 new file mode 100644 index 0000000..83956b7 --- /dev/null +++ b/modem-probe/modem-probe.8 @@ -0,0 +1,34 @@ +.TH MODEM_PROBE 8 "November 2005" "" "Linux Administrator's Manual" +.SH NAME +modem-probe \- udev callout to identify Hayes-compatible modem capabilities +.SH SYNOPSIS +.BI modem-probe +[\fI--export\fP] [\fI--verbose\fP] [\fI--quiet\fP] [\fI--log \fP] \fI\fP +.SH "DESCRIPTION" +.B modem-probe +is normally called from a udev rule, to provide udev with the modem +capabilities for Hayes-compatible modems. +.SH USAGE +.B modem-probe +opens the tty node specified at the commandline and prints the +discovered modem capabilities. +.SH OPTIONS +The following commandline switches alter modem-probe's behavior: +.TP +.BI \-\-export +print values as environment keys +.TP +.BI \-\-verbose +print debugging information +.TP +.BI \-\-quiet +only output to logfile, not stdout +.TP +.BI \-\-log\ +log output to +.RE +.SH SEE ALSO +.BR udev (7) +.SH AUTHORS +Developed by Dan Williams . + diff --git a/modem-probe/modem-probe.c b/modem-probe/modem-probe.c new file mode 100644 index 0000000..99497db --- /dev/null +++ b/modem-probe/modem-probe.c @@ -0,0 +1,460 @@ +/* + * modem_caps - probe Hayes-compatible modem capabilities + * + * Copyright (C) 2008 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 the + * Free Software Foundation version 2 of the License. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MODEM_CAP_GSM 0x0001 /* GSM */ +#define MODEM_CAP_IS707_A 0x0002 /* CDMA Circuit Switched Data */ +#define MODEM_CAP_IS707_P 0x0004 /* CDMA Packet Switched Data */ +#define MODEM_CAP_DS 0x0008 /* Data compression selection (v.42bis) */ +#define MODEM_CAP_ES 0x0010 /* Error control selection (v.42) */ +#define MODEM_CAP_FCLASS 0x0020 /* Group III Fax */ +#define MODEM_CAP_MS 0x0040 /* Modulation selection */ +#define MODEM_CAP_W 0x0080 /* Wireless commands */ +#define MODEM_CAP_IS856 0x0100 /* CDMA 3G EVDO rev 0 */ +#define MODEM_CAP_IS856_A 0x0200 /* CDMA 3G EVDO rev A */ + +static gboolean verbose = FALSE; +static gboolean quiet = FALSE; +static FILE *logfile = NULL; + +struct modem_caps { + char *name; + guint32 bits; +}; + +static struct modem_caps modem_caps[] = { + {"+CGSM", MODEM_CAP_GSM}, + {"+CIS707-A", MODEM_CAP_IS707_A}, + {"+CIS707", MODEM_CAP_IS707_A}, + {"+CIS707P", MODEM_CAP_IS707_P}, + {"CIS-856", MODEM_CAP_IS856}, + {"CIS-856-A", MODEM_CAP_IS856_A}, + {"+DS", MODEM_CAP_DS}, + {"+ES", MODEM_CAP_ES}, + {"+MS", MODEM_CAP_MS}, + {"+FCLASS", MODEM_CAP_FCLASS}, + {NULL} +}; + +static void +printerr_handler (const char *string) +{ + if (logfile) + fprintf (logfile, "E: %s", string); + if (!quiet) + fprintf (stderr, "E: %s", string); +} + +static void +print_handler (const char *string) +{ + if (logfile) + fprintf (logfile, "L: %s", string); + if (!quiet) + fprintf (stdout, "L: %s", string); +} + +#define verbose(fmt, args...) \ +if (verbose) { \ + g_print ("%s(): " fmt "\n", G_STRFUNC, ##args); \ +} + +static gboolean +modem_send_command (int fd, const char *cmd) +{ + int eagain_count = 1000; + guint32 i; + ssize_t written; + + verbose ("Sending: '%s'", cmd); + + for (i = 0; i < strlen (cmd) && eagain_count > 0;) { + written = write (fd, cmd + i, 1); + + if (written > 0) + i += written; + else { + /* Treat written == 0 as EAGAIN to ensure we break out of the + * for() loop eventually. + */ + if ((written < 0) && (errno != EAGAIN)) { + g_printerr ("error writing command: %d\n", errno); + return FALSE; + } + eagain_count--; + g_usleep (G_USEC_PER_SEC / 10000); + } + } + + return eagain_count <= 0 ? FALSE : TRUE; +} + +static int +find_terminator (const char *line, const char **terminators) +{ + int i; + + for (i = 0; terminators[i]; i++) { + if (!strncasecmp (line, terminators[i], strlen (terminators[i]))) + return i; + } + return -1; +} + +static const char * +find_response (const char *line, const char **responses, int *idx) +{ + int i; + + /* Don't look for a result again if we got one previously */ + for (i = 0; responses[i]; i++) { + if (strstr (line, responses[i])) { + *idx = i; + return line; + } + } + return NULL; +} + +#define RESPONSE_LINE_MAX 128 +#define SERIAL_BUF_SIZE 2048 + +static int +modem_wait_reply (int fd, + guint32 timeout_secs, + const char **needles, + const char **terminators, + int *out_terminator, + char **out_response) +{ + char buf[SERIAL_BUF_SIZE + 1]; + int reply_index = -1, bytes_read; + GString *result = g_string_sized_new (RESPONSE_LINE_MAX * 2); + time_t end; + const char *response = NULL; + gboolean done = FALSE; + + *out_terminator = -1; + end = time (NULL) + timeout_secs; + do { + bytes_read = read (fd, buf, SERIAL_BUF_SIZE); + if (bytes_read < 0 && errno != EAGAIN) { + g_string_free (result, TRUE); + g_printerr ("read error: %d\n", errno); + return -1; + } + + if (bytes_read == 0) + break; /* EOF */ + else if (bytes_read > 0) { + char **lines, **iter, *tmp; + + buf[bytes_read] = 0; + g_string_append (result, buf); + + verbose ("Got: '%s'", result->str); + + lines = g_strsplit_set (result->str, "\n\r", 0); + + /* Find response terminators */ + for (iter = lines; *iter && !done; iter++) { + tmp = g_strstrip (*iter); + if (tmp && strlen (tmp)) { + *out_terminator = find_terminator (tmp, terminators); + if (*out_terminator >= 0) + done = TRUE; + } + } + + /* If the terminator is found, look for expected responses */ + if (done) { + for (iter = lines; *iter && (reply_index < 0); iter++) { + tmp = g_strstrip (*iter); + if (tmp && strlen (tmp)) { + response = find_response (tmp, needles, &reply_index); + if (response) { + g_free (*out_response); + *out_response = g_strdup (response); + } + } + } + } + g_strfreev (lines); + } + + if (!done) + g_usleep (1000); + } while (!done && (time (NULL) < end) && (result->len <= SERIAL_BUF_SIZE)); + + g_string_free (result, TRUE); + return reply_index; +} + +#define GCAP_TAG "+GCAP:" +#define GMM_TAG "+GMM:" + +static int +parse_gcap (const char *buf) +{ + const char *p = buf + strlen (GCAP_TAG); + char **caps, **iter; + int ret = 0; + + caps = g_strsplit_set (p, " ,\t", 0); + if (!caps) + return 0; + + for (iter = caps; *iter; iter++) { + struct modem_caps *cap = modem_caps; + + while (cap->name) { + if (!strcmp(cap->name, *iter)) { + ret |= cap->bits; + break; + } + cap++; + } + } + + g_strfreev (caps); + return ret; +} + +static int +parse_gmm (const char *buf) +{ + const char *p = buf + strlen (GMM_TAG); + char **gmm, **iter; + gboolean gsm = FALSE; + + gmm = g_strsplit_set (p, " ,\t", 0); + if (!gmm) + return 0; + + /* BUSlink SCWi275u USB GPRS modem */ + for (iter = gmm; *iter && !gsm; iter++) { + if (strstr (*iter, "GSM900") || strstr (*iter, "GSM1800") || + strstr (*iter, "GSM1900") || strstr (*iter, "GSM850")) + gsm = TRUE; + } + + g_strfreev (gmm); + return gsm ? MODEM_CAP_GSM : 0; +} + +static int modem_probe_caps(int fd) +{ + const char *gcap_responses[] = { GCAP_TAG, NULL }; + const char *terminators[] = { "OK", "ERROR", "ERR", NULL }; + char *reply = NULL; + int idx, term_idx, ret = 0; + + if (!modem_send_command (fd, "AT+GCAP\r\n")) + return -1; + + idx = modem_wait_reply (fd, 3, gcap_responses, terminators, &term_idx, &reply); + if (0 == term_idx && 0 == idx) { + /* Success */ + verbose ("GCAP response: %s", reply); + ret = parse_gcap (reply); + } else if (1 == term_idx || 2 == term_idx) { + const char *ati_responses[] = { GCAP_TAG, NULL }; + + /* Many cards (ex Sierra 860 & 875) won't accept AT+GCAP but + * accept ATI when the SIM is missing. Often the GCAP info is + * in the ATI response too. + */ + g_free (reply); + reply = NULL; + + if (modem_send_command (fd, "ATI\r\n")) { + idx = modem_wait_reply (fd, 3, ati_responses, terminators, &term_idx, &reply); + if (0 == term_idx && 0 == idx) { + verbose ("ATI response: %s", reply); + ret = parse_gcap (reply); + } + } + } + g_free (reply); + reply = NULL; + + /* Try an alternate method on some hardware (ex BUSlink SCWi275u) */ + if (!(ret & MODEM_CAP_GSM) && !(ret & MODEM_CAP_IS707_A)) { + const char *gmm_responses[] = { GMM_TAG, NULL }; + + if (modem_send_command (fd, "AT+GMM\r\n")) { + idx = modem_wait_reply (fd, 5, gmm_responses, terminators, &term_idx, &reply); + if (0 == term_idx && 0 == idx) { + verbose ("GMM response: %s", reply); + ret |= parse_gmm (reply); + } + g_free (reply); + } + } + + return ret; +} + +static void +print_usage (void) +{ + printf("Usage: probe-modem [options] \n" + " --export export key/value pairs\n" + " --verbose print verbose debugging output\n" + " --log log all output\n" + " --help\n\n"); +} + +static void +do_exit (int val) +{ + if (logfile) fclose (logfile); + exit (val); +} + +int +main(int argc, char *argv[]) +{ + static const struct option options[] = { + { "export", 0, NULL, 'x' }, + { "verbose", 0, NULL, 'v' }, + { "quiet", 0, NULL, 'q' }, + { "log", required_argument, NULL, 'l' }, + { "help", 0, NULL, 'h' }, + {} + }; + + const char *device = NULL; + const char *logpath = NULL; + gboolean export = 0; + struct termios orig, attrs; + int fd, caps; + + while (1) { + int option; + + option = getopt_long (argc, argv, "xvl:qh", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'x': + export = TRUE; + break; + case 'v': + verbose = TRUE; + break; + case 'l': + logpath = optarg; + break; + case 'q': + quiet = TRUE; + break; + case 'h': + print_usage (); + return 0; + default: + return 1; + } + } + + if (logpath) { + time_t t = time (NULL); + + logfile = fopen (logpath, "a+"); + if (!logfile) { + fprintf (stderr, "Couldn't open/create logfile %s", logpath); + return 2; + } + + fprintf (logfile, "\n**** Started: %s\n", ctime (&t)); + g_set_printerr_handler (printerr_handler); + } + + g_set_print_handler (print_handler); + + device = argv[optind]; + if (device == NULL) { + g_printerr ("no node specified\n"); + do_exit (3); + } + + fd = open (device, O_RDWR | O_EXCL | O_NONBLOCK); + if (fd < 0) { + g_printerr ("open(%s) failed: %d\n", device, errno); + do_exit (4); + } + + if (tcgetattr (fd, &orig)) { + g_printerr ("tcgetattr(%s): failed %d\n", device, errno); + do_exit (5); + } + + memcpy (&attrs, &orig, sizeof (attrs)); + attrs.c_iflag &= ~(IGNCR | ICRNL | IUCLC | INPCK | IXON | IXANY | IGNPAR); + attrs.c_oflag &= ~(OPOST | OLCUC | OCRNL | ONLCR | ONLRET); + attrs.c_lflag &= ~(ICANON | XCASE | ECHO | ECHOE | ECHONL); + attrs.c_lflag &= ~(ECHO | ECHOE); + attrs.c_cc[VMIN] = 1; + attrs.c_cc[VTIME] = 0; + attrs.c_cc[VEOF] = 1; + + attrs.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | CLOCAL | PARENB); + attrs.c_cflag |= (B9600 | CS8 | CREAD | 0 | 0 | 0); + + tcsetattr (fd, TCSANOW, &attrs); + caps = modem_probe_caps (fd); + tcsetattr (fd, TCSANOW, &orig); + + if (caps < 0) { + g_printerr ("%s: couldn't get modem capabilities\n", device); + printf ("ID_MODEM_PROBED=1\n"); + do_exit (0); + } + + if (export) { + if (caps & MODEM_CAP_GSM) + printf ("ID_MODEM_GSM=1\n"); + if (caps & MODEM_CAP_IS707_A) + printf ("ID_MODEM_IS707_A=1\n"); + if (caps & MODEM_CAP_IS707_P) + printf ("ID_MODEM_IS707P=1\n"); + if (caps & MODEM_CAP_IS856) + printf ("ID_MODEM_IS856=1\n"); + if (caps & MODEM_CAP_IS856_A) + printf ("ID_MODEM_IS856_A=1\n"); + printf ("ID_MODEM_PROBED=1\n"); + } + + verbose ("%s: caps (0x%X)%s%s%s%s\n", device, caps, + caps & MODEM_CAP_GSM ? " GSM" : "", + caps & (MODEM_CAP_IS707_A | MODEM_CAP_IS707_P) ? " CDMA-1x" : "", + caps & MODEM_CAP_IS856 ? " EVDOr0" : "", + caps & MODEM_CAP_IS856_A ? " EVDOrA" : ""); + + do_exit (0); + return 0; +} + diff --git a/probe-modem/.gitignore b/probe-modem/.gitignore deleted file mode 100644 index 02e771b..0000000 --- a/probe-modem/.gitignore +++ /dev/null @@ -1 +0,0 @@ -probe-modem diff --git a/probe-modem/77-probe-modem-capabilities.rules b/probe-modem/77-probe-modem-capabilities.rules deleted file mode 100644 index e74f3ec..0000000 --- a/probe-modem/77-probe-modem-capabilities.rules +++ /dev/null @@ -1,10 +0,0 @@ -# do not edit this file, it will be overwritten on update - -ACTION!="add|change", GOTO="probe_modem_end" -SUBSYSTEM!="tty", GOTO="probe_modem_end" -KERNEL!="tty*", GOTO="probe_modem_end" - -DRIVERS=="option|sierra|cdc_acm|hso", IMPORT{program}="probe-modem --export $tempnode" - -LABEL="probe_modem_end" - diff --git a/probe-modem/Makefile.am b/probe-modem/Makefile.am deleted file mode 100644 index bf90fb3..0000000 --- a/probe-modem/Makefile.am +++ /dev/null @@ -1,13 +0,0 @@ -include $(top_srcdir)/Makefile.am.inc - -udevhomedir = $(udev_prefix)/lib/udev -udevhome_PROGRAMS = probe-modem - -probe_modem_SOURCES = probe-modem.c -probe_modem_CPPFLAGS = $(GLIB_CFLAGS) -probe_modem_LDADD = $(GLIB_LIBS) - -udevrulesdir = $(udev_prefix)/lib/udev/rules.d -dist_udevrules_DATA = 77-probe-modem-capabilities.rules -dist_man_MANS = probe-modem.8 - diff --git a/probe-modem/probe-modem.8 b/probe-modem/probe-modem.8 deleted file mode 100644 index aaf3874..0000000 --- a/probe-modem/probe-modem.8 +++ /dev/null @@ -1,34 +0,0 @@ -.TH PROBE_MODEM 8 "November 2005" "" "Linux Administrator's Manual" -.SH NAME -probe-modem \- udev callout to identify Hayes-compatible modem capabilities -.SH SYNOPSIS -.BI probe-modem -[\fI--export\fP] [\fI--verbose\fP] [\fI--quiet\fP] [\fI--log \fP] \fI\fP -.SH "DESCRIPTION" -.B probe-modem -is normally called from a udev rule, to provide udev with the modem -capabilities for Hayes-compatible modems. -.SH USAGE -.B probe-modem -opens the tty node specified at the commandline and prints the -discovered modem capabilities. -.SH OPTIONS -The following commandline switches alter probe-modem's behavior: -.TP -.BI \-\-export -print values as environment keys -.TP -.BI \-\-verbose -print debugging information -.TP -.BI \-\-quiet -only output to logfile, not stdout -.TP -.BI \-\-log\ -log output to -.RE -.SH SEE ALSO -.BR udev (7) -.SH AUTHORS -Developed by Dan Williams . - diff --git a/probe-modem/probe-modem.c b/probe-modem/probe-modem.c deleted file mode 100644 index 99497db..0000000 --- a/probe-modem/probe-modem.c +++ /dev/null @@ -1,460 +0,0 @@ -/* - * modem_caps - probe Hayes-compatible modem capabilities - * - * Copyright (C) 2008 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 the - * Free Software Foundation version 2 of the License. - */ - -#ifndef _GNU_SOURCE -#define _GNU_SOURCE 1 -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define MODEM_CAP_GSM 0x0001 /* GSM */ -#define MODEM_CAP_IS707_A 0x0002 /* CDMA Circuit Switched Data */ -#define MODEM_CAP_IS707_P 0x0004 /* CDMA Packet Switched Data */ -#define MODEM_CAP_DS 0x0008 /* Data compression selection (v.42bis) */ -#define MODEM_CAP_ES 0x0010 /* Error control selection (v.42) */ -#define MODEM_CAP_FCLASS 0x0020 /* Group III Fax */ -#define MODEM_CAP_MS 0x0040 /* Modulation selection */ -#define MODEM_CAP_W 0x0080 /* Wireless commands */ -#define MODEM_CAP_IS856 0x0100 /* CDMA 3G EVDO rev 0 */ -#define MODEM_CAP_IS856_A 0x0200 /* CDMA 3G EVDO rev A */ - -static gboolean verbose = FALSE; -static gboolean quiet = FALSE; -static FILE *logfile = NULL; - -struct modem_caps { - char *name; - guint32 bits; -}; - -static struct modem_caps modem_caps[] = { - {"+CGSM", MODEM_CAP_GSM}, - {"+CIS707-A", MODEM_CAP_IS707_A}, - {"+CIS707", MODEM_CAP_IS707_A}, - {"+CIS707P", MODEM_CAP_IS707_P}, - {"CIS-856", MODEM_CAP_IS856}, - {"CIS-856-A", MODEM_CAP_IS856_A}, - {"+DS", MODEM_CAP_DS}, - {"+ES", MODEM_CAP_ES}, - {"+MS", MODEM_CAP_MS}, - {"+FCLASS", MODEM_CAP_FCLASS}, - {NULL} -}; - -static void -printerr_handler (const char *string) -{ - if (logfile) - fprintf (logfile, "E: %s", string); - if (!quiet) - fprintf (stderr, "E: %s", string); -} - -static void -print_handler (const char *string) -{ - if (logfile) - fprintf (logfile, "L: %s", string); - if (!quiet) - fprintf (stdout, "L: %s", string); -} - -#define verbose(fmt, args...) \ -if (verbose) { \ - g_print ("%s(): " fmt "\n", G_STRFUNC, ##args); \ -} - -static gboolean -modem_send_command (int fd, const char *cmd) -{ - int eagain_count = 1000; - guint32 i; - ssize_t written; - - verbose ("Sending: '%s'", cmd); - - for (i = 0; i < strlen (cmd) && eagain_count > 0;) { - written = write (fd, cmd + i, 1); - - if (written > 0) - i += written; - else { - /* Treat written == 0 as EAGAIN to ensure we break out of the - * for() loop eventually. - */ - if ((written < 0) && (errno != EAGAIN)) { - g_printerr ("error writing command: %d\n", errno); - return FALSE; - } - eagain_count--; - g_usleep (G_USEC_PER_SEC / 10000); - } - } - - return eagain_count <= 0 ? FALSE : TRUE; -} - -static int -find_terminator (const char *line, const char **terminators) -{ - int i; - - for (i = 0; terminators[i]; i++) { - if (!strncasecmp (line, terminators[i], strlen (terminators[i]))) - return i; - } - return -1; -} - -static const char * -find_response (const char *line, const char **responses, int *idx) -{ - int i; - - /* Don't look for a result again if we got one previously */ - for (i = 0; responses[i]; i++) { - if (strstr (line, responses[i])) { - *idx = i; - return line; - } - } - return NULL; -} - -#define RESPONSE_LINE_MAX 128 -#define SERIAL_BUF_SIZE 2048 - -static int -modem_wait_reply (int fd, - guint32 timeout_secs, - const char **needles, - const char **terminators, - int *out_terminator, - char **out_response) -{ - char buf[SERIAL_BUF_SIZE + 1]; - int reply_index = -1, bytes_read; - GString *result = g_string_sized_new (RESPONSE_LINE_MAX * 2); - time_t end; - const char *response = NULL; - gboolean done = FALSE; - - *out_terminator = -1; - end = time (NULL) + timeout_secs; - do { - bytes_read = read (fd, buf, SERIAL_BUF_SIZE); - if (bytes_read < 0 && errno != EAGAIN) { - g_string_free (result, TRUE); - g_printerr ("read error: %d\n", errno); - return -1; - } - - if (bytes_read == 0) - break; /* EOF */ - else if (bytes_read > 0) { - char **lines, **iter, *tmp; - - buf[bytes_read] = 0; - g_string_append (result, buf); - - verbose ("Got: '%s'", result->str); - - lines = g_strsplit_set (result->str, "\n\r", 0); - - /* Find response terminators */ - for (iter = lines; *iter && !done; iter++) { - tmp = g_strstrip (*iter); - if (tmp && strlen (tmp)) { - *out_terminator = find_terminator (tmp, terminators); - if (*out_terminator >= 0) - done = TRUE; - } - } - - /* If the terminator is found, look for expected responses */ - if (done) { - for (iter = lines; *iter && (reply_index < 0); iter++) { - tmp = g_strstrip (*iter); - if (tmp && strlen (tmp)) { - response = find_response (tmp, needles, &reply_index); - if (response) { - g_free (*out_response); - *out_response = g_strdup (response); - } - } - } - } - g_strfreev (lines); - } - - if (!done) - g_usleep (1000); - } while (!done && (time (NULL) < end) && (result->len <= SERIAL_BUF_SIZE)); - - g_string_free (result, TRUE); - return reply_index; -} - -#define GCAP_TAG "+GCAP:" -#define GMM_TAG "+GMM:" - -static int -parse_gcap (const char *buf) -{ - const char *p = buf + strlen (GCAP_TAG); - char **caps, **iter; - int ret = 0; - - caps = g_strsplit_set (p, " ,\t", 0); - if (!caps) - return 0; - - for (iter = caps; *iter; iter++) { - struct modem_caps *cap = modem_caps; - - while (cap->name) { - if (!strcmp(cap->name, *iter)) { - ret |= cap->bits; - break; - } - cap++; - } - } - - g_strfreev (caps); - return ret; -} - -static int -parse_gmm (const char *buf) -{ - const char *p = buf + strlen (GMM_TAG); - char **gmm, **iter; - gboolean gsm = FALSE; - - gmm = g_strsplit_set (p, " ,\t", 0); - if (!gmm) - return 0; - - /* BUSlink SCWi275u USB GPRS modem */ - for (iter = gmm; *iter && !gsm; iter++) { - if (strstr (*iter, "GSM900") || strstr (*iter, "GSM1800") || - strstr (*iter, "GSM1900") || strstr (*iter, "GSM850")) - gsm = TRUE; - } - - g_strfreev (gmm); - return gsm ? MODEM_CAP_GSM : 0; -} - -static int modem_probe_caps(int fd) -{ - const char *gcap_responses[] = { GCAP_TAG, NULL }; - const char *terminators[] = { "OK", "ERROR", "ERR", NULL }; - char *reply = NULL; - int idx, term_idx, ret = 0; - - if (!modem_send_command (fd, "AT+GCAP\r\n")) - return -1; - - idx = modem_wait_reply (fd, 3, gcap_responses, terminators, &term_idx, &reply); - if (0 == term_idx && 0 == idx) { - /* Success */ - verbose ("GCAP response: %s", reply); - ret = parse_gcap (reply); - } else if (1 == term_idx || 2 == term_idx) { - const char *ati_responses[] = { GCAP_TAG, NULL }; - - /* Many cards (ex Sierra 860 & 875) won't accept AT+GCAP but - * accept ATI when the SIM is missing. Often the GCAP info is - * in the ATI response too. - */ - g_free (reply); - reply = NULL; - - if (modem_send_command (fd, "ATI\r\n")) { - idx = modem_wait_reply (fd, 3, ati_responses, terminators, &term_idx, &reply); - if (0 == term_idx && 0 == idx) { - verbose ("ATI response: %s", reply); - ret = parse_gcap (reply); - } - } - } - g_free (reply); - reply = NULL; - - /* Try an alternate method on some hardware (ex BUSlink SCWi275u) */ - if (!(ret & MODEM_CAP_GSM) && !(ret & MODEM_CAP_IS707_A)) { - const char *gmm_responses[] = { GMM_TAG, NULL }; - - if (modem_send_command (fd, "AT+GMM\r\n")) { - idx = modem_wait_reply (fd, 5, gmm_responses, terminators, &term_idx, &reply); - if (0 == term_idx && 0 == idx) { - verbose ("GMM response: %s", reply); - ret |= parse_gmm (reply); - } - g_free (reply); - } - } - - return ret; -} - -static void -print_usage (void) -{ - printf("Usage: probe-modem [options] \n" - " --export export key/value pairs\n" - " --verbose print verbose debugging output\n" - " --log log all output\n" - " --help\n\n"); -} - -static void -do_exit (int val) -{ - if (logfile) fclose (logfile); - exit (val); -} - -int -main(int argc, char *argv[]) -{ - static const struct option options[] = { - { "export", 0, NULL, 'x' }, - { "verbose", 0, NULL, 'v' }, - { "quiet", 0, NULL, 'q' }, - { "log", required_argument, NULL, 'l' }, - { "help", 0, NULL, 'h' }, - {} - }; - - const char *device = NULL; - const char *logpath = NULL; - gboolean export = 0; - struct termios orig, attrs; - int fd, caps; - - while (1) { - int option; - - option = getopt_long (argc, argv, "xvl:qh", options, NULL); - if (option == -1) - break; - - switch (option) { - case 'x': - export = TRUE; - break; - case 'v': - verbose = TRUE; - break; - case 'l': - logpath = optarg; - break; - case 'q': - quiet = TRUE; - break; - case 'h': - print_usage (); - return 0; - default: - return 1; - } - } - - if (logpath) { - time_t t = time (NULL); - - logfile = fopen (logpath, "a+"); - if (!logfile) { - fprintf (stderr, "Couldn't open/create logfile %s", logpath); - return 2; - } - - fprintf (logfile, "\n**** Started: %s\n", ctime (&t)); - g_set_printerr_handler (printerr_handler); - } - - g_set_print_handler (print_handler); - - device = argv[optind]; - if (device == NULL) { - g_printerr ("no node specified\n"); - do_exit (3); - } - - fd = open (device, O_RDWR | O_EXCL | O_NONBLOCK); - if (fd < 0) { - g_printerr ("open(%s) failed: %d\n", device, errno); - do_exit (4); - } - - if (tcgetattr (fd, &orig)) { - g_printerr ("tcgetattr(%s): failed %d\n", device, errno); - do_exit (5); - } - - memcpy (&attrs, &orig, sizeof (attrs)); - attrs.c_iflag &= ~(IGNCR | ICRNL | IUCLC | INPCK | IXON | IXANY | IGNPAR); - attrs.c_oflag &= ~(OPOST | OLCUC | OCRNL | ONLCR | ONLRET); - attrs.c_lflag &= ~(ICANON | XCASE | ECHO | ECHOE | ECHONL); - attrs.c_lflag &= ~(ECHO | ECHOE); - attrs.c_cc[VMIN] = 1; - attrs.c_cc[VTIME] = 0; - attrs.c_cc[VEOF] = 1; - - attrs.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | CLOCAL | PARENB); - attrs.c_cflag |= (B9600 | CS8 | CREAD | 0 | 0 | 0); - - tcsetattr (fd, TCSANOW, &attrs); - caps = modem_probe_caps (fd); - tcsetattr (fd, TCSANOW, &orig); - - if (caps < 0) { - g_printerr ("%s: couldn't get modem capabilities\n", device); - printf ("ID_MODEM_PROBED=1\n"); - do_exit (0); - } - - if (export) { - if (caps & MODEM_CAP_GSM) - printf ("ID_MODEM_GSM=1\n"); - if (caps & MODEM_CAP_IS707_A) - printf ("ID_MODEM_IS707_A=1\n"); - if (caps & MODEM_CAP_IS707_P) - printf ("ID_MODEM_IS707P=1\n"); - if (caps & MODEM_CAP_IS856) - printf ("ID_MODEM_IS856=1\n"); - if (caps & MODEM_CAP_IS856_A) - printf ("ID_MODEM_IS856_A=1\n"); - printf ("ID_MODEM_PROBED=1\n"); - } - - verbose ("%s: caps (0x%X)%s%s%s%s\n", device, caps, - caps & MODEM_CAP_GSM ? " GSM" : "", - caps & (MODEM_CAP_IS707_A | MODEM_CAP_IS707_P) ? " CDMA-1x" : "", - caps & MODEM_CAP_IS856 ? " EVDOr0" : "", - caps & MODEM_CAP_IS856_A ? " EVDOrA" : ""); - - do_exit (0); - return 0; -} - -- cgit