diff options
-rw-r--r-- | Makefile.am | 5 | ||||
-rwxr-xr-x | autogen.sh | 21 | ||||
-rw-r--r-- | configure.ac | 96 | ||||
-rw-r--r-- | probe-modem/62-probe-modem-capabilities.rules | 12 | ||||
-rw-r--r-- | probe-modem/Makefile | 75 | ||||
-rw-r--r-- | probe-modem/Makefile.am | 12 | ||||
-rw-r--r-- | probe-modem/probe-modem.8 | 26 | ||||
-rw-r--r-- | probe-modem/probe-modem.c | 408 |
8 files changed, 655 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..e3041e4 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = probe-modem option-zerocd + +EXTRA_DIST = \ + CONTRIBUTING + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..8eede66 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# Run this to generate all the initial makefiles, etc. + +srcdir=`dirname $0` +test -z "$srcdir" && srcdir=. +REQUIRED_AUTOMAKE_VERSION=1.9 +PKG_NAME=udev-extras + +(test -f $srcdir/configure.ac \ + && test -f $srcdir/src/NetworkManager.c) || { + echo -n "**Error**: Directory "\`$srcdir\'" does not look like the" + echo " top-level $PKG_NAME directory" + exit 1 +} + +(cd $srcdir; + autoreconf --install --symlink && + autoreconf && + ./configure --enable-maintainer-mode $@ +) + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..489e9b1 --- /dev/null +++ b/configure.ac @@ -0,0 +1,96 @@ +AC_PREREQ(2.52) + +AC_INIT(udev-extras, 0.1, dcbw@redhat.com, udev-extras) +AM_INIT_AUTOMAKE([1.9 subdir-objects tar-ustar]) +AM_MAINTAINER_MODE + +AC_CONFIG_HEADERS(config.h) + +dnl +dnl Require programs +dnl +AC_PROG_CC +AM_PROG_CC_C_O +AC_PROG_INSTALL +AC_PROG_LIBTOOL + +dnl ensure that when the Automake generated makefile calls aclocal, +dnl it honours the $ACLOCAL_FLAGS environment variable +ACLOCAL_AMFLAGS="\${ACLOCAL_FLAGS}" +if test -n "$ac_macro_dir"; then + ACLOCAL_AMFLAGS="-I $ac_macro_dir $ACLOCAL_AMFLAGS" +fi +AC_SUBST([ACLOCAL_AMFLAGS]) + +dnl maintainer mode stuff +if test $USE_MAINTAINER_MODE = yes; then + DISABLE_DEPRECATED="-DG_DISABLE_DEPRECATED" +else + DISABLE_DEPRECATED="" +fi +AC_SUBST(DISABLE_DEPRECATED) + +dnl +dnl Required headers +dnl +AC_HEADER_STDC +AC_CHECK_HEADERS(fcntl.h paths.h sys/ioctl.h sys/time.h syslog.h unistd.h) + +dnl +dnl Checks for typedefs, structures, and compiler characteristics. +dnl +AC_TYPE_MODE_T +AC_TYPE_PID_T +AC_HEADER_TIME + +dnl +dnl Checks for library functions. +dnl +AC_PROG_GCC_TRADITIONAL +AC_FUNC_MEMCMP +AC_CHECK_FUNCS(select socket uname) + +dnl +dnl Make sha1.c happy on big endian systems +dnl +AC_C_BIGENDIAN + +PKG_CHECK_MODULES(GLIB, glib-2 >= 2.12) +AC_SUBST(GLIB_CFLAGS) +AC_SUBST(GLIB_LIBS) + +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_CONFIG_FILES([ +Makefile +probe-modem/Makefile +]) +AC_OUTPUT + diff --git a/probe-modem/62-probe-modem-capabilities.rules b/probe-modem/62-probe-modem-capabilities.rules new file mode 100644 index 0000000..b430fe9 --- /dev/null +++ b/probe-modem/62-probe-modem-capabilities.rules @@ -0,0 +1,12 @@ +# do not edit this file, it will be overwritten on update + +ACTION!="add", GOTO="persistent_storage_edd_end" +SUBSYSTEM!="block", GOTO="persistent_storage_edd_end" +KERNEL!="sd*|hd*", GOTO="persistent_storage_edd_end" + +# BIOS Enhanced Disk Device +ENV{DEVTYPE}=="disk", IMPORT{program}="edd_id --export $tempnode" +ENV{DEVTYPE}=="disk", ENV{ID_EDD}=="?*", SYMLINK+="disk/by-id/edd-$env{ID_EDD}" +ENV{DEVTYPE}=="partition", ENV{ID_EDD}=="?*", SYMLINK+="disk/by-id/edd-$env{ID_EDD}-part%n" + +LABEL="persistent_storage_edd_end" diff --git a/probe-modem/Makefile b/probe-modem/Makefile new file mode 100644 index 0000000..d6bf459 --- /dev/null +++ b/probe-modem/Makefile @@ -0,0 +1,75 @@ +# Makefile for udev extra invoked from the udev main Makefile +# +# Copyright (C) 2004-2005 Kay Sievers <kay.sievers@vrfy.org> +# +# Released under the GNU General Public License, version 2. +# + +PROG = modem_caps +OBJ = +HEADERS = +GEN_HEADERS = +MAN_PAGES = + +prefix = +etcdir = ${prefix}/etc +sbindir = ${prefix}/sbin +usrbindir = ${prefix}/usr/bin +usrsbindir = ${prefix}/usr/sbin +libudevdir = ${prefix}/lib/udev +mandir = ${prefix}/usr/share/man +configdir = ${etcdir}/udev/ + +INSTALL = install -c +INSTALL_PROGRAM = ${INSTALL} +INSTALL_DATA = ${INSTALL} -m 644 +INSTALL_SCRIPT = ${INSTALL} + +all: $(PROG) $(MAN_PAGES) +.PHONY: all +.DEFAULT: all + +%.o: %.c $(GEN_HEADERS) + $(E) " CC " $@ + $(Q) $(CC) -c $(CFLAGS) $< -o $@ + +$(PROG): %: $(HEADERS) %.o $(OBJS) + $(E) " LD " $@ + $(Q) $(LD) $(LDFLAGS) $@.o $(OBJS) -o $@ $(LIBUDEV) $(LIB_OBJS) + +# man pages +%.8: %.xml + $(E) " XMLTO " $@ + $(Q) xmlto man $? +.PRECIOUS: %.8 + +clean: + $(E) " CLEAN " + $(Q) rm -f $(PROG) $(OBJS) $(GEN_HEADERS) +.PHONY: clean + +install-bin: all + $(INSTALL) -d $(DESTDIR)$(libudevdir) + $(INSTALL_PROGRAM) $(PROG) $(DESTDIR)$(libudevdir)/$(PROG) + $(INSTALL) -d $(DESTDIR)$(configdir)/rules.d/ + $(INSTALL_DATA) 62-probe-modem-capabilities.rules $(DESTDIR)$(configdir)/rules.d/62-probe-modem-capabilities.rules +.PHONY: install-bin + +uninstall-bin: + - rm $(DESTDIR)$(libudevdir)/$(PROG) + - rm $(DESTDIR)$(configdir)/rules.d/62-probe-modem-capabilities.rules +.PHONY: uninstall-bin + +install-man: + $(INSTALL) -d $(DESTDIR)$(mandir)/man8 + $(INSTALL_DATA) $(PROG).8 $(DESTDIR)$(mandir)/man8/$(PROG).8 +.PHONY: install-man + +uninstall-man: + -rm -f $(DESTDIR)$(mandir)/man8/$(PROG).8 +.PHONY: uninstall-man + +install-config: + @echo "no config file to install" +.PHONY: install-config + diff --git a/probe-modem/Makefile.am b/probe-modem/Makefile.am new file mode 100644 index 0000000..7872d7d --- /dev/null +++ b/probe-modem/Makefile.am @@ -0,0 +1,12 @@ +libudevdir = $(prefix)/lib/udev + +libudev_PROGRAMS = probe-modem +probe_modem_SOURCES = probe-modem.c +probe_modem_CPPFLAGS = $(GLIB_CFLAGS) +probe_modem_LDADD = $(GLIB_LDFLAGS) + +rulesdir = $(sysconfdir)/udev/ +rules_DATA = 62-probe-modem-capabilities.rules + +man_MANS = probe-modem.8 + diff --git a/probe-modem/probe-modem.8 b/probe-modem/probe-modem.8 new file mode 100644 index 0000000..158bac0 --- /dev/null +++ b/probe-modem/probe-modem.8 @@ -0,0 +1,26 @@ +.TH EDD_ID 8 "November 2005" "" "Linux Administrator's Manual" +.SH NAME +modem_caps \- udev callout to identify Hayes-compatible modem capabilities +.SH SYNOPSIS +.BI modem_caps +[\fI--export\fP] \fI<devpath>\fP +.SH "DESCRIPTION" +.B modem_caps +is normally called from a udev rule, to provide udev with the modem +capabilities for Hayes-compatible modems. +.SH USAGE +.B modem_caps +opens the tty node specified at the commandline and prints the +discovered modem capabilities. +.SH OPTIONS +The following commandline switches are supported to specify what modem_caps +should print: +.TP +.BI --export +print values as environment keys +.RE +.SH SEE ALSO +.BR udev (7) +.SH AUTHORS +Developed by Dan Williams <dcbw@redhat.com>. + diff --git a/probe-modem/probe-modem.c b/probe-modem/probe-modem.c new file mode 100644 index 0000000..8ffe964 --- /dev/null +++ b/probe-modem/probe-modem.c @@ -0,0 +1,408 @@ +/* + * modem_caps - probe Hayes-compatible modem capabilities + * + * Copyright (C) 2008 Dan Williams <dcbw@redhat.com> + * + * 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 <termios.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <getopt.h> + +#include <glib.h> + +#include "../../udev.h" + +#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; + +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} +}; + +#define debug(fmt, args...) \ +if (verbose) { \ + g_printerr ("%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; + + debug ("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); + + debug ("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 -1; + + 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 -1; + + /* 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 = -1; + + 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 */ + debug ("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) { + debug ("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) { + debug ("GMM response: %s", reply); + ret |= parse_gmm (reply); + } + g_free (reply); + } + } + + return ret; +} + +void +print_usage (void) +{ + printf("Usage: modem_caps [options] <device>\n" + " --export export key/value pairs\n" + " --verbose print verbose debugging output\n" + " --help\n\n"); +} + +int +main(int argc, char *argv[]) +{ + static const struct option options[] = { + { "export", 0, NULL, 'x' }, + { "verbose", 0, NULL, 'v' }, + { "help", 0, NULL, 'h' }, + {} + }; + + const char *device = NULL; + int i; + gboolean export = 0; + struct termios orig, attrs; + char *udi; + int fd, caps; + + while (1) { + int option; + + option = getopt_long(argc, argv, "xvh", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'x': + export = TRUE; + break; + case 'v': + verbose = TRUE; + break; + case 'h': + print_usage (); + return 1; + default: + return 1; + } + } + + device = argv[optind]; + if (device == NULL) { + g_printerr ("no node specified\n"); + return 2; + } + + fd = open (device, O_RDWR | O_EXCL | O_NONBLOCK); + if (fd < 0) { + g_printerr ("open(%s) failed: %d\n", device, errno); + return 3; + } + + if (tcgetattr (fd, &orig)) { + g_printerr ("tcgetattr(%s): failed %d\n", device, errno); + return 4; + } + + 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); + return 5; + } + + if (export) { + if (caps & MODEM_CAP_GSM) + g_print ("ID_MODEM_GSM=1\n"); + if (caps & MODEM_CAP_IS707_A) + g_print ("ID_MODEM_IS707_A=1\n"); + if (caps & MODEM_CAP_IS707P) + g_print ("ID_MODEM_IS707P=1\n"); + if (caps & MODEM_CAP_IS856) + g_print ("ID_MODEM_IS856=1\n"); + if (caps & MODEM_CAP_IS856_A) + g_print ("ID_MODEM_IS856_A=1\n"); + } else { + g_print ("%s: caps 0x%X%s%s%s%s\n", device, caps, + caps & MODEM_CAP_GSM ? " GSM" : "", + caps & (MODEM_CAP_IS707_A | MODEM_CAP_IS707P) ? " CDMA-1x" : "", + caps & MODEM_CAP_IS856 ? " EVDOr0" : "", + caps & MODEM_CAP_IS856_A ? " EVDOrA" : ""); + } + + return 0; +} + |