summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorthompsa <thompsa@FreeBSD.org>2010-01-13 03:16:31 +0000
committerthompsa <thompsa@FreeBSD.org>2010-01-13 03:16:31 +0000
commit38619cd9707a9e9b073d14381cde7d38e14cce71 (patch)
treeb8181f5e4ad60f2fc859794657eb0574c468b30e
parentcf2c03a0f9961b5c072df009fd9a1bed000a7d06 (diff)
downloadFreeBSD-src-38619cd9707a9e9b073d14381cde7d38e14cce71.zip
FreeBSD-src-38619cd9707a9e9b073d14381cde7d38e14cce71.tar.gz
Add a driver by Fredrik Lindberg for Option HSDPA USB devices. These differ
from standard 3G wireless units by supplying a raw IP/IPv6 endpoint rather than using PPP over serial. uhsoctl(1) is used to initiate and close the WAN connection. Obtained from: Fredrik Lindberg <fli@shapeshifter.se>
-rw-r--r--share/man/man4/Makefile1
-rw-r--r--share/man/man4/uhso.4115
-rw-r--r--sys/conf/NOTES3
-rw-r--r--sys/conf/files3
-rw-r--r--sys/dev/usb/net/uhso.c1754
-rw-r--r--sys/dev/usb/usbdevs11
-rw-r--r--sys/modules/usb/Makefile2
-rw-r--r--sys/modules/usb/uhso/Makefile37
-rw-r--r--usr.sbin/Makefile2
-rw-r--r--usr.sbin/uhsoctl/Makefile10
-rw-r--r--usr.sbin/uhsoctl/uhsoctl.1104
-rw-r--r--usr.sbin/uhsoctl/uhsoctl.c1532
12 files changed, 3572 insertions, 2 deletions
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index 30f2ca5..95e1d4f 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -424,6 +424,7 @@ MAN= aac.4 \
ugen.4 \
uhci.4 \
uhid.4 \
+ uhso.4 \
uipaq.4 \
ukbd.4 \
ulpt.4 \
diff --git a/share/man/man4/uhso.4 b/share/man/man4/uhso.4
new file mode 100644
index 0000000..0ec602a
--- /dev/null
+++ b/share/man/man4/uhso.4
@@ -0,0 +1,115 @@
+.\" Copyright (c) 2009 Fredrik Lindberg
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd Aug 12, 2009
+.Os
+.Dt UHSO 4
+.Sh NAME
+.Nm hso
+.Nd support for several HSxPA devices from Option N.V.
+.Sh SYNOPSIS
+The module can be loaded at boot time by placing the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+uhso_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for several HSxPA devices from Option N.V. that are
+based on their packet interface.
+Each device has a set of serial ports and a raw IP packet interface.
+The serial ports of the device are accessed through the
+.Xr ucom 4
+driver which makes them behave like a
+.Xr tty 4 .
+The packet interface is exposed as a network interface.
+.Pp
+To establish a connection on the packet interface the use of the proprietary
+AT commands
+.Dq Li AT_OWANCALL
+and
+.Dq Li AT_OWANDATA
+are required on any of the serial ports.
+.Pp
+The network interface must be configured manually using the data obtain from
+these calls.
+.Pp
+Each device usually have at least two or more serial ports, their individual purpose
+can be identified through
+.Xr sysctl 8 .
+.Sh HARDWARE
+The
+.Nm
+driver supports at least the following cards
+.Pp
+.Bl -bullet -compact
+.It
+Option GlobeSurfer iCON 7.2 (new firmware)
+.It
+Option iCON 225
+.El
+.Pp
+The device features a mass storage device referred to as
+.Dq Zero-CD
+which contains drivers for Microsoft Windows.
+The driver automatically switches the device to modem mode.
+.Sh EXAMPLES
+Establishing a packet interface connection
+.Bd -literal -offset indent
+AT+CGDCONT=1,,"apn.provider"
+AT_OWANCALL=1,1,1
+OK
+_OWANCALL=1,1
+
+AT_OWANDATA=1
+_OWANDATA: 1, 10.11.12.13, 0.0.0.0, 10.2.3.4, 10.2.3.5, \e
+ 0.0.0.0, 0.0.0.0, 72000
+.Ed
+.Pp
+Configuring the interface
+.Bd -literal -offset indent
+ifconfig uhso0 10.11.12.13 up
+route add default -interface uhso0
+echo "nameserver 10.2.3.4" > /etc/resolv.conf
+echo "nameserver 10.2.3.5" >> /etc/resolv.conf
+.Ed
+.Pp
+The connection can be terminated with
+.Bd -literal -offset indent
+AT_OWANCALL=1,0,1
+.Ed
+.Sh FILES
+.Bl -tag -width "XXXXXX"
+.It Pa /dev/cuaU?.?
+.El
+.Sh SEE ALSO
+.Xr ucom 4 ,
+.Xr usb 4
+.Sh AUTHORS
+The
+.Nm
+driver was written by
+.An Fredrik Lindberg Aq fli@shapeshifter.se .
diff --git a/sys/conf/NOTES b/sys/conf/NOTES
index 8e949b5..cd38da0 100644
--- a/sys/conf/NOTES
+++ b/sys/conf/NOTES
@@ -2652,6 +2652,9 @@ device rue
#
# Davicom DM9601E USB to fast ethernet. Supports the Corega FEther USB-TXC.
device udav
+#
+# HSxPA devices from Option N.V
+device uhso
#
# Ralink Technology RT2501USB/RT2601USB wireless driver
diff --git a/sys/conf/files b/sys/conf/files
index e93c384..71a8e53 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1676,7 +1676,7 @@ dev/usb/usb_request.c optional usb
dev/usb/usb_transfer.c optional usb
dev/usb/usb_util.c optional usb
#
-# USB ethernet drivers
+# USB network drivers
#
dev/usb/net/if_aue.c optional aue
dev/usb/net/if_axe.c optional axe
@@ -1687,6 +1687,7 @@ dev/usb/net/if_rue.c optional rue
dev/usb/net/if_udav.c optional udav
dev/usb/net/usb_ethernet.c optional aue | axe | cdce | cue | kue | rue | \
udav
+dev/usb/net/uhso.c optional uhso
#
# USB WLAN drivers
#
diff --git a/sys/dev/usb/net/uhso.c b/sys/dev/usb/net/uhso.c
new file mode 100644
index 0000000..4a3edd3
--- /dev/null
+++ b/sys/dev/usb/net/uhso.c
@@ -0,0 +1,1754 @@
+/*-
+ * Copyright (c) 2009 Fredrik Lindberg
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/sockio.h>
+#include <sys/mbuf.h>
+#include <sys/malloc.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/tty.h>
+#include <sys/sysctl.h>
+#include <sys/condvar.h>
+#include <sys/sx.h>
+#include <sys/proc.h>
+#include <sys/conf.h>
+#include <sys/bus.h>
+#include <sys/systm.h>
+
+#include <machine/bus.h>
+
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/netisr.h>
+#include <net/bpf.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_cdc.h>
+#include "usbdevs.h"
+#define USB_DEBUG_VAR uhso_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/serial/usb_serial.h>
+#include <dev/usb/usb_msctest.h>
+
+struct uhso_tty {
+ struct uhso_softc *ht_sc;
+ struct usb_xfer *ht_xfer[3];
+ int ht_muxport;
+ int ht_open;
+ char ht_name[32];
+};
+
+struct uhso_softc {
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+ uint32_t sc_type;
+
+ struct usb_xfer *sc_xfer[3];
+ uint8_t sc_iface_no;
+ uint8_t sc_iface_index;
+
+ /* Control pipe */
+ struct usb_xfer * sc_ctrl_xfer[2];
+ uint8_t sc_ctrl_iface_no;
+
+ /* Network */
+ struct usb_xfer *sc_if_xfer[2];
+ struct ifnet *sc_ifp;
+ struct mbuf *sc_mwait; /* partial packet */
+ size_t sc_waitlen; /* no. of outstanding bytes */
+ struct ifqueue sc_rxq;
+ struct callout sc_c;
+
+ /* TTY related structures */
+ struct ucom_super_softc sc_super_ucom;
+ int sc_ttys;
+ struct uhso_tty *sc_tty;
+ struct ucom_softc *sc_ucom;
+ int sc_msr;
+ int sc_lsr;
+ int sc_line;
+};
+
+
+#define UHSO_MAX_MTU 2048
+
+/*
+ * There are mainly two type of cards floating around.
+ * The first one has 2,3 or 4 interfaces with a multiplexed serial port
+ * and packet interface on the first interface and bulk serial ports
+ * on the others.
+ * The second type of card has several other interfaces, their purpose
+ * can be detected during run-time.
+ */
+#define UHSO_IFACE_SPEC(usb_type, port, port_type) \
+ (((usb_type) << 24) | ((port) << 16) | (port_type))
+
+#define UHSO_IFACE_USB_TYPE(x) ((x >> 24) & 0xff)
+#define UHSO_IFACE_PORT(x) ((x >> 16) & 0xff)
+#define UHSO_IFACE_PORT_TYPE(x) (x & 0xff)
+
+/*
+ * USB interface types
+ */
+#define UHSO_IF_NET 0x01 /* Network packet interface */
+#define UHSO_IF_MUX 0x02 /* Multiplexed serial port */
+#define UHSO_IF_BULK 0x04 /* Bulk interface */
+
+/*
+ * Port types
+ */
+#define UHSO_PORT_UNKNOWN 0x00
+#define UHSO_PORT_SERIAL 0x01 /* Serial port */
+#define UHSO_PORT_NETWORK 0x02 /* Network packet interface */
+
+/*
+ * Multiplexed serial port destination sub-port names
+ */
+#define UHSO_MPORT_TYPE_CTL 0x00 /* Control port */
+#define UHSO_MPORT_TYPE_APP 0x01 /* Application */
+#define UHSO_MPORT_TYPE_PCSC 0x02
+#define UHSO_MPORT_TYPE_GPS 0x03
+#define UHSO_MPORT_TYPE_APP2 0x04
+#define UHSO_MPORT_TYPE_MAX UHSO_MPORT_TYPE_APP2
+#define UHSO_MPORT_TYPE_NOMAX 8 /* Max number of mux ports */
+
+/*
+ * Port definitions
+ */
+#define UHSO_PORT_TYPE_CTL 0x01
+#define UHSO_PORT_TYPE_APP 0x02
+#define UHSO_PORT_TYPE_APP2 0x03
+#define UHSO_PORT_TYPE_MODEM 0x04
+#define UHSO_PORT_TYPE_NETWORK 0x05
+#define UHSO_PORT_TYPE_DIAG 0x06
+#define UHSO_PORT_TYPE_DIAG2 0x07
+#define UHSO_PORT_TYPE_GPS 0x08
+#define UHSO_PORT_TYPE_GPSCTL 0x09
+#define UHSO_PORT_TYPE_PCSC 0x0a
+#define UHSO_PORT_TYPE_MSD 0x0b
+#define UHSO_PORT_TYPE_VOICE 0x0c
+#define UHSO_PORT_TYPE_MAX 0x0c
+
+static eventhandler_tag uhso_etag;
+
+/* Overall port type */
+static char *uhso_port[] = {
+ "Unknown",
+ "Serial",
+ "Network",
+ "Network/Serial"
+};
+
+/* Map between interface port type read from device and description type */
+static char uhso_port_map[] = {
+ 0,
+ UHSO_PORT_TYPE_DIAG,
+ UHSO_PORT_TYPE_GPS,
+ UHSO_PORT_TYPE_GPSCTL,
+ UHSO_PORT_TYPE_APP,
+ UHSO_PORT_TYPE_APP2,
+ UHSO_PORT_TYPE_CTL,
+ UHSO_PORT_TYPE_NETWORK,
+ UHSO_PORT_TYPE_MODEM,
+ UHSO_PORT_TYPE_MSD,
+ UHSO_PORT_TYPE_PCSC,
+ UHSO_PORT_TYPE_VOICE
+};
+static char uhso_port_map_max = sizeof(uhso_port_map) / sizeof(char);
+
+static char uhso_mux_port_map[] = {
+ UHSO_PORT_TYPE_CTL,
+ UHSO_PORT_TYPE_APP,
+ UHSO_PORT_TYPE_PCSC,
+ UHSO_PORT_TYPE_GPS,
+ UHSO_PORT_TYPE_APP2
+};
+
+static char *uhso_port_type[] = {
+ "Unknown",
+ "Control",
+ "Application",
+ "Application (Secondary)",
+ "Modem",
+ "Network",
+ "Diagnostic",
+ "Diagnostic (Secondary)",
+ "GPS",
+ "GPS Control",
+ "PC Smartcard",
+ "MSD",
+ "Voice",
+};
+
+static char *uhso_port_type_sysctl[] = {
+ "unknown",
+ "control",
+ "application",
+ "application",
+ "modem",
+ "network",
+ "diagnostic",
+ "diagnostic",
+ "gps",
+ "gps_control",
+ "pcsc",
+ "msd",
+ "voice",
+};
+
+
+#define UHSO_STATIC_IFACE 0x01
+#define UHSO_AUTO_IFACE 0x02
+
+static const struct usb_device_id uhso_devs[] = {
+#define UHSO_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
+ /* Option GlobeSurfer iCON 7.2 */
+ UHSO_DEV(OPTION, GSICON72, UHSO_STATIC_IFACE),
+ /* Option iCON 225 */
+ UHSO_DEV(OPTION, GTHSDPA, UHSO_STATIC_IFACE),
+ /* Option GlobeSurfer iCON HSUPA */
+ UHSO_DEV(OPTION, GSICONHSUPA, UHSO_STATIC_IFACE),
+ /* Option GlobeTrotter HSUPA */
+ UHSO_DEV(OPTION, GTHSUPA, UHSO_STATIC_IFACE),
+ /* GE40x */
+ UHSO_DEV(OPTION, GE40X, UHSO_AUTO_IFACE),
+ UHSO_DEV(OPTION, GE40X_1, UHSO_AUTO_IFACE),
+ UHSO_DEV(OPTION, GE40X_2, UHSO_AUTO_IFACE),
+ UHSO_DEV(OPTION, GE40X_3, UHSO_AUTO_IFACE),
+ /* Option GlobeSurfer iCON 401 */
+ UHSO_DEV(OPTION, ICON401, UHSO_AUTO_IFACE),
+ /* Option GlobeTrotter Module 382 */
+ UHSO_DEV(OPTION, GMT382, UHSO_AUTO_IFACE),
+ /* Option iCON EDGE */
+ UHSO_DEV(OPTION, ICONEDGE, UHSO_STATIC_IFACE),
+ /* Option Module HSxPA */
+ UHSO_DEV(OPTION, MODHSXPA, UHSO_STATIC_IFACE),
+ /* Option iCON 321 */
+ UHSO_DEV(OPTION, ICON321, UHSO_STATIC_IFACE),
+ /* Option iCON 322 */
+ UHSO_DEV(OPTION, GTICON322, UHSO_STATIC_IFACE)
+#undef UHSO_DEV
+};
+
+SYSCTL_NODE(_hw_usb, OID_AUTO, uhso, CTLFLAG_RW, 0, "USB uhso");
+
+#ifdef USB_DEBUG
+#ifdef UHSO_DEBUG
+static int uhso_debug = UHSO_DEBUG;
+#else
+static int uhso_debug = -1;
+#endif
+
+SYSCTL_INT(_hw_usb_uhso, OID_AUTO, debug, CTLFLAG_RW,
+ &uhso_debug, 0, "Debug level");
+
+#define UHSO_DPRINTF(n, x, ...) {\
+ if (uhso_debug >= n) {\
+ printf("%s: " x, __func__, ##__VA_ARGS__);\
+ }\
+}
+#else
+#define UHSO_DPRINTF(n, x, ...)
+#endif
+
+#ifdef UHSO_DEBUG_HEXDUMP
+# define UHSO_HEXDUMP(_buf, _len) do { \
+ { \
+ size_t __tmp; \
+ const char *__buf = (const char *)_buf; \
+ for (__tmp = 0; __tmp < _len; __tmp++) \
+ printf("%02hhx ", *__buf++); \
+ printf("\n"); \
+ } \
+} while(0)
+#else
+# define UHSO_HEXDUMP(_buf, _len)
+#endif
+
+enum {
+ UHSO_MUX_ENDPT_INTR = 0,
+ UHSO_MUX_ENDPT_MAX
+};
+
+enum {
+ UHSO_CTRL_READ = 0,
+ UHSO_CTRL_WRITE,
+ UHSO_CTRL_MAX
+};
+
+enum {
+ UHSO_IFNET_READ = 0,
+ UHSO_IFNET_WRITE,
+ UHSO_IFNET_MAX
+};
+
+enum {
+ UHSO_BULK_ENDPT_READ = 0,
+ UHSO_BULK_ENDPT_WRITE,
+ UHSO_BULK_ENDPT_INTR,
+ UHSO_BULK_ENDPT_MAX
+};
+
+static usb_callback_t uhso_mux_intr_callback;
+static usb_callback_t uhso_mux_read_callback;
+static usb_callback_t uhso_mux_write_callback;
+static usb_callback_t uhso_bs_read_callback;
+static usb_callback_t uhso_bs_write_callback;
+static usb_callback_t uhso_bs_intr_callback;
+static usb_callback_t uhso_ifnet_read_callback;
+static usb_callback_t uhso_ifnet_write_callback;
+
+static const struct usb_config uhso_ctrl_config[UHSO_CTRL_MAX] = {
+ [UHSO_CTRL_READ] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00,
+ .direction = UE_DIR_ANY,
+ .flags = { .pipe_bof = 1, .short_xfer_ok = 1 },
+ .bufsize = sizeof(struct usb_device_request) + 1024,
+ .callback = &uhso_mux_read_callback
+ },
+
+ [UHSO_CTRL_WRITE] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00,
+ .direction = UE_DIR_ANY,
+ .flags = { .pipe_bof = 1, .force_short_xfer = 1 },
+ .bufsize = sizeof(struct usb_device_request) + 1024,
+ .timeout = 1000,
+ .callback = &uhso_mux_write_callback
+ }
+};
+
+static const struct usb_config uhso_mux_config[UHSO_MUX_ENDPT_MAX] = {
+ [UHSO_MUX_ENDPT_INTR] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = { .short_xfer_ok = 1 },
+ .bufsize = 0,
+ .callback = &uhso_mux_intr_callback,
+ }
+};
+
+static const struct usb_config uhso_ifnet_config[UHSO_IFNET_MAX] = {
+ [UHSO_IFNET_READ] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = { .pipe_bof = 1, .short_xfer_ok = 1 },
+ .bufsize = MCLBYTES,
+ .callback = &uhso_ifnet_read_callback
+ },
+ [UHSO_IFNET_WRITE] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .flags = { .pipe_bof = 1, .force_short_xfer = 1 },
+ .bufsize = MCLBYTES,
+ .timeout = 5 * USB_MS_HZ,
+ .callback = &uhso_ifnet_write_callback
+ }
+};
+
+static const struct usb_config uhso_bs_config[UHSO_BULK_ENDPT_MAX] = {
+ [UHSO_BULK_ENDPT_READ] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = { .pipe_bof = 1, .short_xfer_ok = 1 },
+ .bufsize = 4096,
+ .callback = &uhso_bs_read_callback
+ },
+
+ [UHSO_BULK_ENDPT_WRITE] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .flags = { .pipe_bof = 1, .force_short_xfer = 1 },
+ .bufsize = 8192,
+ .callback = &uhso_bs_write_callback
+ },
+
+ [UHSO_BULK_ENDPT_INTR] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = { .short_xfer_ok = 1 },
+ .bufsize = 0,
+ .callback = &uhso_bs_intr_callback,
+ }
+};
+
+static int uhso_probe_iface(struct uhso_softc *, int,
+ int (*probe)(struct uhso_softc *, int));
+static int uhso_probe_iface_auto(struct uhso_softc *, int);
+static int uhso_probe_iface_static(struct uhso_softc *, int);
+
+static int uhso_attach_muxserial(struct uhso_softc *, struct usb_interface *,
+ int type);
+static int uhso_attach_bulkserial(struct uhso_softc *, struct usb_interface *,
+ int type);
+static int uhso_attach_ifnet(struct uhso_softc *, struct usb_interface *,
+ int type);
+static void uhso_test_autoinst(void *, struct usb_device *,
+ struct usb_attach_arg *);
+static int uhso_driver_loaded(struct module *, int, void *);
+
+static void uhso_ucom_start_read(struct ucom_softc *);
+static void uhso_ucom_stop_read(struct ucom_softc *);
+static void uhso_ucom_start_write(struct ucom_softc *);
+static void uhso_ucom_stop_write(struct ucom_softc *);
+static void uhso_ucom_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *);
+static void uhso_ucom_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void uhso_ucom_cfg_set_rts(struct ucom_softc *, uint8_t);
+
+static void uhso_if_init(void *);
+static void uhso_if_start(struct ifnet *);
+static void uhso_if_stop(struct uhso_softc *);
+static int uhso_if_ioctl(struct ifnet *, u_long, caddr_t);
+static int uhso_if_output(struct ifnet *, struct mbuf *, struct sockaddr *,
+ struct route *);
+static void uhso_if_rxflush(void *);
+
+static device_probe_t uhso_probe;
+static device_attach_t uhso_attach;
+static device_detach_t uhso_detach;
+
+static device_method_t uhso_methods[] = {
+ DEVMETHOD(device_probe, uhso_probe),
+ DEVMETHOD(device_attach, uhso_attach),
+ DEVMETHOD(device_detach, uhso_detach),
+ { 0, 0 }
+};
+
+static driver_t uhso_driver = {
+ "uhso",
+ uhso_methods,
+ sizeof(struct uhso_softc)
+};
+
+static devclass_t uhso_devclass;
+DRIVER_MODULE(uhso, uhub, uhso_driver, uhso_devclass, uhso_driver_loaded, 0);
+MODULE_DEPEND(uhso, ucom, 1, 1, 1);
+MODULE_DEPEND(uhso, usb, 1, 1, 1);
+MODULE_VERSION(uhso, 1);
+
+static struct ucom_callback uhso_ucom_callback = {
+ .ucom_cfg_get_status = &uhso_ucom_cfg_get_status,
+ .ucom_cfg_set_dtr = &uhso_ucom_cfg_set_dtr,
+ .ucom_cfg_set_rts = &uhso_ucom_cfg_set_rts,
+ .ucom_start_read = uhso_ucom_start_read,
+ .ucom_stop_read = uhso_ucom_stop_read,
+ .ucom_start_write = uhso_ucom_start_write,
+ .ucom_stop_write = uhso_ucom_stop_write
+};
+
+static int
+uhso_probe(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != 0)
+ return (ENXIO);
+ if (uaa->device->ddesc.bDeviceClass != 0xff)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(uhso_devs, sizeof(uhso_devs), uaa));
+}
+
+static int
+uhso_attach(device_t self)
+{
+ struct uhso_softc *sc = device_get_softc(self);
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+ struct usb_config_descriptor *cd;
+ struct usb_interface_descriptor *id;
+ struct sysctl_ctx_list *sctx;
+ struct sysctl_oid *soid;
+ struct sysctl_oid *tree, *tty_node;
+ struct ucom_softc *ucom;
+ struct uhso_tty *ht;
+ int i, error, port;
+ void *probe_f;
+ usb_error_t uerr;
+ char *desc;
+
+ device_set_usb_desc(self);
+
+ UHSO_DPRINTF(0, "Device is in modem mode, devClass=%x\n",
+ uaa->device->ddesc.bDeviceClass);
+
+ sc->sc_dev = self;
+ sc->sc_udev = uaa->device;
+ mtx_init(&sc->sc_mtx, "uhso", NULL, MTX_DEF);
+
+ sc->sc_ucom = NULL;
+ sc->sc_ttys = 0;
+
+ cd = usbd_get_config_descriptor(uaa->device);
+ id = usbd_get_interface_descriptor(uaa->iface);
+ sc->sc_ctrl_iface_no = id->bInterfaceNumber;
+
+ sc->sc_iface_no = uaa->info.bIfaceNum;
+ sc->sc_iface_index = uaa->info.bIfaceIndex;
+
+ /* Setup control pipe */
+ uerr = usbd_transfer_setup(uaa->device,
+ &sc->sc_iface_index, sc->sc_ctrl_xfer,
+ uhso_ctrl_config, UHSO_CTRL_MAX, sc, &sc->sc_mtx);
+ if (uerr) {
+ device_printf(self, "Failed to setup control pipe: %s\n",
+ usbd_errstr(uerr));
+ goto out;
+ }
+
+ if (USB_GET_DRIVER_INFO(uaa) == UHSO_STATIC_IFACE)
+ probe_f = uhso_probe_iface_static;
+ else if (USB_GET_DRIVER_INFO(uaa) == UHSO_AUTO_IFACE)
+ probe_f = uhso_probe_iface_auto;
+ else
+ goto out;
+
+ error = uhso_probe_iface(sc, uaa->info.bIfaceNum, probe_f);
+ if (error != 0)
+ goto out;
+
+
+ sctx = device_get_sysctl_ctx(sc->sc_dev);
+ soid = device_get_sysctl_tree(sc->sc_dev);
+
+ SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "type",
+ CTLFLAG_RD, uhso_port[UHSO_IFACE_PORT(sc->sc_type)], 0,
+ "Port available at this interface");
+
+ if (sc->sc_ttys > 0) {
+ SYSCTL_ADD_INT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "ports",
+ CTLFLAG_RD, &sc->sc_ttys, 0, "Number of attached serial ports");
+
+ tree = SYSCTL_ADD_NODE(sctx, SYSCTL_CHILDREN(soid), OID_AUTO,
+ "port", CTLFLAG_RD, NULL, "Serial ports");
+ }
+
+ for (i = 0; i < sc->sc_ttys; i++) {
+ ht = &sc->sc_tty[i];
+ ucom = &sc->sc_ucom[i];
+
+
+ if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX)
+ port = uhso_mux_port_map[ht->ht_muxport];
+ else
+ port = UHSO_IFACE_PORT_TYPE(sc->sc_type);
+
+ desc = uhso_port_type_sysctl[port];
+
+ tty_node = SYSCTL_ADD_NODE(sctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+ desc, CTLFLAG_RD, NULL, "");
+
+ ht->ht_name[0] = 0;
+ if (sc->sc_ttys == 1)
+ snprintf(ht->ht_name, 32, "cuaU%d", ucom->sc_unit);
+ else {
+ snprintf(ht->ht_name, 32, "cuaU%d.%d",
+ ucom->sc_unit - ucom->sc_local_unit,
+ ucom->sc_local_unit);
+ }
+
+ desc = uhso_port_type[port];
+ SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(tty_node), OID_AUTO,
+ "tty", CTLFLAG_RD, ht->ht_name, 0, "");
+ SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(tty_node), OID_AUTO,
+ "desc", CTLFLAG_RD, desc, 0, "");
+
+ if (bootverbose)
+ device_printf(sc->sc_dev,
+ "\"%s\" port at %s\n", desc, ht->ht_name);
+ }
+
+ return (0);
+out:
+ uhso_detach(sc->sc_dev);
+ return (ENXIO);
+
+}
+
+static int
+uhso_detach(device_t self)
+{
+ struct uhso_softc *sc = device_get_softc(self);
+ int i;
+
+ usbd_transfer_unsetup(sc->sc_xfer, 3);
+ usbd_transfer_unsetup(sc->sc_ctrl_xfer, UHSO_CTRL_MAX);
+ if (sc->sc_ttys > 0) {
+ ucom_detach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_ttys);
+
+ for (i = 0; i < sc->sc_ttys; i++) {
+ if (sc->sc_tty[i].ht_muxport != -1) {
+ usbd_transfer_unsetup(sc->sc_tty[i].ht_xfer,
+ UHSO_CTRL_MAX);
+ }
+ }
+
+ free(sc->sc_tty, M_USBDEV);
+ free(sc->sc_ucom, M_USBDEV);
+ }
+
+ if (sc->sc_ifp != NULL) {
+
+ callout_drain(&sc->sc_c);
+
+ mtx_lock(&sc->sc_mtx);
+ uhso_if_stop(sc);
+ bpfdetach(sc->sc_ifp);
+ if_detach(sc->sc_ifp);
+ if_free(sc->sc_ifp);
+ mtx_unlock(&sc->sc_mtx);
+
+ usbd_transfer_unsetup(sc->sc_if_xfer, UHSO_IFNET_MAX);
+ }
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+uhso_test_autoinst(void *arg, struct usb_device *udev,
+ struct usb_attach_arg *uaa)
+{
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *id;
+
+ if (uaa->dev_state != UAA_DEV_READY)
+ return;
+
+ iface = usbd_get_iface(udev, 0);
+ if (iface == NULL)
+ return;
+ id = iface->idesc;
+ if (id == NULL || id->bInterfaceClass != UICLASS_MASS)
+ return;
+ if (usbd_lookup_id_by_uaa(uhso_devs, sizeof(uhso_devs), uaa))
+ return; /* no device match */
+
+ if (usb_msc_eject(udev, 0, MSC_EJECT_REZERO) == 0) {
+ /* success, mark the udev as disappearing */
+ uaa->dev_state = UAA_DEV_EJECTING;
+ }
+}
+
+static int
+uhso_driver_loaded(struct module *mod, int what, void *arg)
+{
+ switch (what) {
+ case MOD_LOAD:
+ /* register our autoinstall handler */
+ uhso_etag = EVENTHANDLER_REGISTER(usb_dev_configured,
+ uhso_test_autoinst, NULL, EVENTHANDLER_PRI_ANY);
+ break;
+ case MOD_UNLOAD:
+ EVENTHANDLER_DEREGISTER(usb_dev_configured, uhso_etag);
+ break;
+ default:
+ return (EOPNOTSUPP);
+ }
+ return (0);
+}
+
+static int uhso_probe_iface_auto(struct uhso_softc *sc, int index)
+{
+ struct usb_device_request req;
+ usb_error_t uerr;
+ uint16_t actlen = 0;
+ char port;
+ char buf[17] = {0};
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = 0x86;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 17);
+
+ uerr = usbd_do_request_flags(sc->sc_udev, NULL, &req, buf,
+ 0, &actlen, USB_MS_HZ);
+ if (uerr != 0) {
+ device_printf(sc->sc_dev, "usbd_do_request_flags failed: %s\n",
+ usbd_errstr(uerr));
+ return (0);
+ }
+
+ UHSO_DPRINTF(3, "actlen=%d\n", actlen);
+ UHSO_HEXDUMP(buf, 17);
+
+ if (index < 0 || index > 16) {
+ UHSO_DPRINTF(0, "Index %d out of range\n", index);
+ return (0);
+ }
+
+ UHSO_DPRINTF(3, "index=%d, type=%x\n", index, buf[index]);
+
+ if (buf[index] >= uhso_port_map_max)
+ port = 0;
+ else
+ port = uhso_port_map[(int)buf[index]];
+
+ if (port == UHSO_PORT_TYPE_NETWORK)
+ return (UHSO_IFACE_SPEC(UHSO_IF_BULK,
+ UHSO_PORT_NETWORK, port));
+ else if (port == UHSO_PORT_TYPE_VOICE)
+ return (0);
+ else
+ return (UHSO_IFACE_SPEC(UHSO_IF_BULK,
+ UHSO_PORT_SERIAL, port));
+
+ return (0);
+}
+
+static int
+uhso_probe_iface_static(struct uhso_softc *sc, int index)
+{
+ struct usb_config_descriptor *cd;
+
+ cd = usbd_get_config_descriptor(sc->sc_udev);
+ if (cd->bNumInterface <= 3) {
+ switch (index) {
+ case 0:
+ return UHSO_IFACE_SPEC(UHSO_IF_NET | UHSO_IF_MUX,
+ UHSO_PORT_SERIAL | UHSO_PORT_NETWORK, 0);
+ case 1:
+ return UHSO_IFACE_SPEC(UHSO_IF_BULK,
+ UHSO_PORT_SERIAL, UHSO_PORT_TYPE_DIAG);
+ case 2:
+ return UHSO_IFACE_SPEC(UHSO_IF_BULK,
+ UHSO_PORT_SERIAL, UHSO_PORT_TYPE_MODEM);
+ }
+ }
+ else {
+ switch (index) {
+ case 0:
+ return UHSO_IFACE_SPEC(UHSO_IF_NET | UHSO_IF_MUX,
+ UHSO_PORT_SERIAL | UHSO_PORT_NETWORK, 0);
+ case 1:
+ return UHSO_IFACE_SPEC(UHSO_IF_BULK,
+ UHSO_PORT_SERIAL, UHSO_PORT_TYPE_DIAG2);
+ case 2:
+ return UHSO_IFACE_SPEC(UHSO_IF_BULK,
+ UHSO_PORT_SERIAL, UHSO_PORT_TYPE_MODEM);
+ case 3:
+ return UHSO_IFACE_SPEC(UHSO_IF_BULK,
+ UHSO_PORT_SERIAL, UHSO_PORT_TYPE_DIAG);
+ }
+ }
+ return (0);
+}
+
+static int
+uhso_probe_iface(struct uhso_softc *sc, int index,
+ int (*probe)(struct uhso_softc *, int))
+{
+ struct usb_interface *iface;
+ int type, error, error0;
+
+ UHSO_DPRINTF(1, "Probing for interface %d, cb=%p\n", index, probe);
+
+ type = probe(sc, index);
+ UHSO_DPRINTF(1, "Probe result %x\n", type);
+ if (type <= 0)
+ return (ENXIO);
+
+ sc->sc_type = type;
+ iface = usbd_get_iface(sc->sc_udev, index);
+
+ if (UHSO_IFACE_USB_TYPE(type) & (UHSO_IF_MUX | UHSO_IF_NET)) {
+ error0 = uhso_attach_muxserial(sc, iface, type);
+ error = uhso_attach_ifnet(sc, iface, type);
+
+ if (error0 && error)
+ return (ENXIO);
+
+ if (sc->sc_ttys > 0) {
+ error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom,
+ sc->sc_ttys, sc, &uhso_ucom_callback, &sc->sc_mtx);
+ if (error) {
+ device_printf(sc->sc_dev, "ucom_attach failed\n");
+ return (ENXIO);
+ }
+ }
+
+ mtx_lock(&sc->sc_mtx);
+ usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]);
+ mtx_unlock(&sc->sc_mtx);
+ }
+ else if ((UHSO_IFACE_USB_TYPE(type) & UHSO_IF_BULK) &&
+ UHSO_IFACE_PORT(type) & UHSO_PORT_SERIAL) {
+
+ error = uhso_attach_bulkserial(sc, iface, type);
+ if (error)
+ return (ENXIO);
+
+ error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom,
+ sc->sc_ttys, sc, &uhso_ucom_callback, &sc->sc_mtx);
+ if (error) {
+ device_printf(sc->sc_dev, "ucom_attach failed\n");
+ return (ENXIO);
+ }
+ }
+ else {
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+static int
+uhso_alloc_tty(struct uhso_softc *sc)
+{
+
+ sc->sc_ttys++;
+ sc->sc_tty = reallocf(sc->sc_tty, sizeof(struct uhso_tty) * sc->sc_ttys,
+ M_USBDEV, M_WAITOK | M_ZERO);
+ if (sc->sc_tty == NULL)
+ return (-1);
+
+ sc->sc_ucom = reallocf(sc->sc_ucom,
+ sizeof(struct ucom_softc) * sc->sc_ttys, M_USBDEV, M_WAITOK | M_ZERO);
+ if (sc->sc_ucom == NULL)
+ return (-1);
+
+ sc->sc_tty[sc->sc_ttys - 1].ht_sc = sc;
+
+ UHSO_DPRINTF(2, "Allocated TTY %d\n", sc->sc_ttys - 1);
+ return (sc->sc_ttys - 1);
+}
+
+
+static int
+uhso_attach_muxserial(struct uhso_softc *sc, struct usb_interface *iface,
+ int type)
+{
+ struct usb_descriptor *desc;
+ int i, port, tty;
+ usb_error_t uerr;
+
+ /*
+ * The class specific interface (type 0x24) descriptor subtype field
+ * contains a bitmask that specifies which (and how many) ports that
+ * are available through this multiplexed serial port.
+ */
+ desc = usbd_find_descriptor(sc->sc_udev, NULL,
+ iface->idesc->bInterfaceNumber, UDESC_CS_INTERFACE, 0xff, 0, 0);
+ if (desc == NULL) {
+ UHSO_DPRINTF(0, "Failed to find UDESC_CS_INTERFACE\n");
+ return (ENXIO);
+ }
+
+ UHSO_DPRINTF(1, "Mux port mask %x\n", desc->bDescriptorSubtype);
+ if (desc->bDescriptorSubtype == 0)
+ return (ENXIO);
+
+ for (i = 0; i < 8; i++) {
+ port = (1 << i);
+ if ((port & desc->bDescriptorSubtype) == port) {
+ UHSO_DPRINTF(2, "Found mux port %x (%d)\n", port, i);
+ tty = uhso_alloc_tty(sc);
+ if (tty < 0)
+ return (ENOMEM);
+ sc->sc_tty[tty].ht_muxport = i;
+ uerr = usbd_transfer_setup(sc->sc_udev,
+ &sc->sc_iface_index, sc->sc_tty[tty].ht_xfer,
+ uhso_ctrl_config, UHSO_CTRL_MAX, sc, &sc->sc_mtx);
+ if (uerr) {
+ device_printf(sc->sc_dev,
+ "Failed to setup control pipe: %s\n",
+ usbd_errstr(uerr));
+ return (ENXIO);
+ }
+ }
+ }
+
+ uerr = usbd_transfer_setup(sc->sc_udev,
+ &iface->idesc->bInterfaceNumber, sc->sc_xfer,
+ uhso_mux_config, 1, sc, &sc->sc_mtx);
+ if (uerr)
+ return (ENXIO);
+
+ return (0);
+}
+
+static void
+uhso_mux_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usb_page_cache *pc;
+ struct usb_page_search res;
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ unsigned int i, mux;
+
+ UHSO_DPRINTF(3, "status %d\n", USB_GET_STATE(xfer));
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ /*
+ * The multiplexed port number can be found at the first byte.
+ * It contains a bit mask, we transform this in to an integer.
+ */
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_get_page(pc, 0, &res);
+
+ i = *((unsigned char *)res.buffer);
+ mux = 0;
+ while (i >>= 1) {
+ mux++;
+ }
+
+ UHSO_DPRINTF(3, "mux port %d (%d)\n", mux, i);
+ if (mux > UHSO_MPORT_TYPE_NOMAX)
+ break;
+
+ usbd_xfer_set_priv(
+ sc->sc_tty[mux].ht_xfer[UHSO_CTRL_READ],
+ &sc->sc_tty[mux]);
+ usbd_transfer_start(sc->sc_tty[mux].ht_xfer[UHSO_CTRL_READ]);
+
+ break;
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+
+}
+
+static void
+uhso_mux_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ struct usb_device_request req;
+ struct uhso_tty *ht;
+ int actlen, len;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ UHSO_DPRINTF(3, "status %d\n", USB_GET_STATE(xfer));
+
+ ht = usbd_xfer_get_priv(xfer);
+ UHSO_DPRINTF(3, "ht=%p open=%d\n", ht, ht->ht_open);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ /* Got data, send to ucom */
+ pc = usbd_xfer_get_frame(xfer, 1);
+ len = usbd_xfer_frame_len(xfer, 1);
+
+ UHSO_DPRINTF(3, "got %d bytes on mux port %d\n", len,
+ ht->ht_muxport);
+ if (len <= 0) {
+ usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]);
+ break;
+ }
+
+ /* Deliver data if the TTY is open, discard otherwise */
+ if (ht->ht_open)
+ ucom_put_data(&sc->sc_ucom[ht->ht_muxport], pc, 0, len);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ bzero(&req, sizeof(struct usb_device_request));
+ req.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, ht->ht_muxport);
+ USETW(req.wLength, 1024);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, 1024);
+ usbd_xfer_set_frames(xfer, 2);
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+}
+
+static void
+uhso_mux_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ struct uhso_tty *ht;
+ struct usb_page_cache *pc;
+ struct usb_device_request req;
+ int actlen;
+ struct usb_page_search res;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ ht = usbd_xfer_get_priv(xfer);
+ UHSO_DPRINTF(3, "status=%d, using mux port %d\n",
+ USB_GET_STATE(xfer), ht->ht_muxport);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ UHSO_DPRINTF(3, "wrote %zd data bytes to muxport %d\n",
+ actlen - sizeof(struct usb_device_request) ,
+ ht->ht_muxport);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+ pc = usbd_xfer_get_frame(xfer, 1);
+ if (ucom_get_data(&sc->sc_ucom[ht->ht_muxport], pc,
+ 0, 32, &actlen)) {
+
+ usbd_get_page(pc, 0, &res);
+
+ bzero(&req, sizeof(struct usb_device_request));
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, ht->ht_muxport);
+ USETW(req.wLength, actlen);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, actlen);
+ usbd_xfer_set_frames(xfer, 2);
+
+ UHSO_DPRINTF(3, "Prepared %d bytes for transmit "
+ "on muxport %d\n", actlen, ht->ht_muxport);
+
+ usbd_transfer_submit(xfer);
+ }
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+ break;
+ }
+
+}
+
+static int
+uhso_attach_bulkserial(struct uhso_softc *sc, struct usb_interface *iface,
+ int type)
+{
+ usb_error_t uerr;
+ int tty;
+
+ /*
+ * Try attaching RD/WR/INTR first
+ */
+ uerr = usbd_transfer_setup(sc->sc_udev,
+ &iface->idesc->bInterfaceNumber, sc->sc_xfer,
+ uhso_bs_config, UHSO_BULK_ENDPT_MAX, sc, &sc->sc_mtx);
+ if (uerr) {
+ /* Try only RD/WR */
+ uerr = usbd_transfer_setup(sc->sc_udev,
+ &iface->idesc->bInterfaceNumber, sc->sc_xfer,
+ uhso_bs_config, UHSO_BULK_ENDPT_MAX - 1, sc, &sc->sc_mtx);
+ }
+ if (uerr) {
+ UHSO_DPRINTF(0, "usbd_transfer_setup failed");
+ return (-1);
+ }
+
+ tty = uhso_alloc_tty(sc);
+ if (tty < 0) {
+ usbd_transfer_unsetup(sc->sc_xfer, UHSO_BULK_ENDPT_MAX);
+ return (ENOMEM);
+ }
+
+ sc->sc_tty[tty].ht_muxport = -1;
+ return (0);
+}
+
+static void
+uhso_bs_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom[0], pc, 0, actlen);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+}
+
+
+static void
+uhso_bs_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(&sc->sc_ucom[0], pc, 0, 8192, &actlen)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+}
+
+static void
+uhso_bs_cfg(struct uhso_softc *sc)
+{
+ struct usb_device_request req;
+ usb_error_t uerr;
+
+ if (!(UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK))
+ return;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
+ USETW(req.wValue, sc->sc_line);
+ USETW(req.wIndex, sc->sc_iface_no);
+ USETW(req.wLength, 0);
+
+ uerr = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom[0], &req, NULL, 0, 1000);
+ if (uerr != 0) {
+ device_printf(sc->sc_dev, "failed to set ctrl line state to "
+ "0x%02x: %s\n", sc->sc_line, usbd_errstr(uerr));
+ }
+}
+
+static void
+uhso_bs_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+ struct usb_cdc_notification cdc;
+
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (actlen < UCDC_NOTIFICATION_LENGTH) {
+ UHSO_DPRINTF(0, "UCDC notification too short: %d\n", actlen);
+ goto tr_setup;
+ }
+ else if (actlen > sizeof(struct usb_cdc_notification)) {
+ UHSO_DPRINTF(0, "UCDC notification too large: %d\n", actlen);
+ actlen = sizeof(struct usb_cdc_notification);
+ }
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &cdc, actlen);
+
+ if (UGETW(cdc.wIndex) != sc->sc_iface_no) {
+ UHSO_DPRINTF(0, "Interface missmatch, got %d expected %d\n",
+ UGETW(cdc.wIndex), sc->sc_iface_no);
+ goto tr_setup;
+ }
+
+ if (cdc.bmRequestType == UCDC_NOTIFICATION &&
+ cdc.bNotification == UCDC_N_SERIAL_STATE) {
+ UHSO_DPRINTF(1, "notify = 0x%02x\n", cdc.data[0]);
+
+ sc->sc_msr = 0;
+ sc->sc_lsr = 0;
+ if (cdc.data[0] & UCDC_N_SERIAL_RI)
+ sc->sc_msr |= SER_RI;
+ if (cdc.data[0] & UCDC_N_SERIAL_DSR)
+ sc->sc_msr |= SER_DSR;
+ if (cdc.data[0] & UCDC_N_SERIAL_DCD)
+ sc->sc_msr |= SER_DCD;
+
+ ucom_status_change(&sc->sc_ucom[0]);
+ }
+ case USB_ST_SETUP:
+tr_setup:
+ default:
+ if (error == USB_ERR_CANCELLED)
+ break;
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+}
+
+static void
+uhso_ucom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ *lsr = sc->sc_lsr;
+ *msr = sc->sc_msr;
+}
+
+static void
+uhso_ucom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ if (!(UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK))
+ return;
+
+ if (onoff)
+ sc->sc_line |= UCDC_LINE_DTR;
+ else
+ sc->sc_line &= UCDC_LINE_DTR;
+
+ uhso_bs_cfg(sc);
+}
+
+static void
+uhso_ucom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ if (!(UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK))
+ return;
+
+ if (onoff)
+ sc->sc_line |= UCDC_LINE_RTS;
+ else
+ sc->sc_line &= UCDC_LINE_DTR;
+
+ uhso_bs_cfg(sc);
+}
+
+
+static void
+uhso_ucom_start_read(struct ucom_softc *ucom)
+{
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ UHSO_DPRINTF(3, "unit=%d, local_unit=%d\n",
+ ucom->sc_unit, ucom->sc_local_unit);
+
+ if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) {
+ sc->sc_tty[ucom->sc_local_unit].ht_open = 1;
+ usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]);
+ }
+ else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) {
+ sc->sc_tty[0].ht_open = 1;
+ usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_READ]);
+ if (sc->sc_xfer[UHSO_BULK_ENDPT_INTR] != NULL)
+ usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_INTR]);
+ }
+}
+
+static void
+uhso_ucom_stop_read(struct ucom_softc *ucom)
+{
+
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) {
+ sc->sc_tty[ucom->sc_local_unit].ht_open = 0;
+ usbd_transfer_stop(
+ sc->sc_tty[ucom->sc_local_unit].ht_xfer[UHSO_CTRL_READ]);
+ }
+ else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) {
+ sc->sc_tty[0].ht_open = 0;
+ usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_READ]);
+ if (sc->sc_xfer[UHSO_BULK_ENDPT_INTR] != NULL)
+ usbd_transfer_stop(sc->sc_xfer[UHSO_BULK_ENDPT_INTR]);
+ }
+}
+
+static void
+uhso_ucom_start_write(struct ucom_softc *ucom)
+{
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) {
+ UHSO_DPRINTF(3, "local unit %d\n", ucom->sc_local_unit);
+
+ usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]);
+
+ usbd_xfer_set_priv(
+ sc->sc_tty[ucom->sc_local_unit].ht_xfer[UHSO_CTRL_WRITE],
+ &sc->sc_tty[ucom->sc_local_unit]);
+ usbd_transfer_start(
+ sc->sc_tty[ucom->sc_local_unit].ht_xfer[UHSO_CTRL_WRITE]);
+
+ }
+ else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) {
+ usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_WRITE]);
+ }
+}
+
+static void
+uhso_ucom_stop_write(struct ucom_softc *ucom)
+{
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) {
+ usbd_transfer_stop(
+ sc->sc_tty[ucom->sc_local_unit].ht_xfer[UHSO_CTRL_WRITE]);
+ }
+ else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) {
+ usbd_transfer_stop(sc->sc_xfer[UHSO_BULK_ENDPT_WRITE]);
+ }
+
+}
+
+static int uhso_attach_ifnet(struct uhso_softc *sc, struct usb_interface *iface,
+ int type)
+{
+ struct ifnet *ifp;
+ usb_error_t uerr;
+ struct sysctl_ctx_list *sctx;
+ struct sysctl_oid *soid;
+
+ uerr = usbd_transfer_setup(sc->sc_udev,
+ &iface->idesc->bInterfaceNumber, sc->sc_if_xfer,
+ uhso_ifnet_config, UHSO_IFNET_MAX, sc, &sc->sc_mtx);
+ if (uerr) {
+ UHSO_DPRINTF(0, "usbd_transfer_setup failed: %s\n",
+ usbd_errstr(uerr));
+ return (-1);
+ }
+
+ sc->sc_ifp = ifp = if_alloc(IFT_PPP);
+ if (sc->sc_ifp == NULL) {
+ device_printf(sc->sc_dev, "if_alloc() failed\n");
+ return (-1);
+ }
+
+ callout_init_mtx(&sc->sc_c, &sc->sc_mtx, 0);
+ mtx_lock(&sc->sc_mtx);
+ callout_reset(&sc->sc_c, 1, uhso_if_rxflush, sc);
+ mtx_unlock(&sc->sc_mtx);
+
+ if_initname(ifp, device_get_name(sc->sc_dev), device_get_unit(sc->sc_dev));
+ ifp->if_mtu = UHSO_MAX_MTU;
+
+ ifp->if_ioctl = uhso_if_ioctl;
+ ifp->if_init = uhso_if_init;
+ ifp->if_start = uhso_if_start;
+ ifp->if_output = uhso_if_output;
+ ifp->if_flags = 0;
+ ifp->if_softc = sc;
+ IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN);
+ ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN;
+ IFQ_SET_READY(&ifp->if_snd);
+
+ if_attach(ifp);
+ bpfattach(ifp, DLT_RAW, 0);
+
+ sctx = device_get_sysctl_ctx(sc->sc_dev);
+ soid = device_get_sysctl_tree(sc->sc_dev);
+ /* Unlocked read... */
+ SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "netif",
+ CTLFLAG_RD, ifp->if_xname, 0, "Attached network interface");
+
+ return (0);
+}
+
+static void
+uhso_ifnet_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ struct mbuf *m;
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ UHSO_DPRINTF(3, "status=%d, actlen=%d\n", USB_GET_STATE(xfer), actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (actlen > 0 && (sc->sc_ifp->if_drv_flags & IFF_DRV_RUNNING)) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
+ usbd_copy_out(pc, 0, mtod(m, uint8_t *), actlen);
+ m->m_pkthdr.len = m->m_len = actlen;
+ _IF_ENQUEUE(&sc->sc_rxq, m);
+ if (!callout_pending(&sc->sc_c) ||
+ !callout_active(&sc->sc_c)) {
+ callout_schedule(&sc->sc_c, 1);
+ }
+ }
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+}
+
+/*
+ * Defered RX processing, called with mutex locked.
+ */
+static void
+uhso_if_rxflush(void *arg)
+{
+ struct uhso_softc *sc = arg;
+ struct ifnet *ifp = sc->sc_ifp;
+ uint8_t *cp;
+ struct mbuf *m, *m0, *mwait;
+ struct ip *ip;
+#ifdef INET6
+ struct ip6_hdr *ip6;
+#endif
+ uint16_t iplen;
+ int len, isr;
+
+ m = NULL;
+ mwait = sc->sc_mwait;
+ for (;;) {
+ if (m == NULL) {
+ _IF_DEQUEUE(&sc->sc_rxq, m);
+ if (m == NULL)
+ break;
+ UHSO_DPRINTF(2, "dequeue m=%p, len=%d\n", m, m->m_len);
+ }
+ mtx_unlock(&sc->sc_mtx);
+
+ /* Do we have a partial packet waiting? */
+ if (mwait != NULL) {
+ m0 = mwait;
+ mwait = NULL;
+
+ UHSO_DPRINTF(1, "partial m0=%p(%d), concat w/ m=%p(%d)\n",
+ m0, m0->m_len, m, m->m_len);
+ len = m->m_len + m0->m_len;
+
+ /* Concat mbufs and fix headers */
+ m_cat(m0, m);
+ m0->m_pkthdr.len = len;
+ m->m_flags &= ~M_PKTHDR;
+
+ m = m_pullup(m0, sizeof(struct ip));
+ if (m == NULL) {
+ ifp->if_ierrors++;
+ UHSO_DPRINTF(0, "m_pullup failed\n");
+ mtx_lock(&sc->sc_mtx);
+ continue;
+ }
+ UHSO_DPRINTF(2, "Constructed mbuf=%p, len=%d\n",
+ m, m->m_pkthdr.len);
+ }
+
+ cp = mtod(m, uint8_t *);
+ ip = (struct ip *)cp;
+#ifdef INET6
+ ip6 = (struct ip6_hdr *)cp;
+#endif
+
+ /* Check for IPv4 */
+ if (ip->ip_v == IPVERSION) {
+ iplen = htons(ip->ip_len);
+ isr = NETISR_IP;
+ }
+#ifdef INET6
+ /* Check for IPv6 */
+ else if ((ip6->ip6_vfc & IPV6_VERSION_MASK) == IPV6_VERSION) {
+ iplen = htons(ip6->ip6_plen);
+ isr = NETISR_IPV6;
+ }
+#endif
+ else {
+ UHSO_DPRINTF(0, "got unexpected ip version %d, "
+ "m=%p, len=%d\n", (*cp & 0xf0) >> 4, m, m->m_len);
+ ifp->if_ierrors++;
+ UHSO_HEXDUMP(cp, 4);
+ m_freem(m);
+ m = NULL;
+ mtx_lock(&sc->sc_mtx);
+ continue;
+ }
+
+ if (iplen == 0) {
+ UHSO_DPRINTF(0, "Zero IP length\n");
+ ifp->if_ierrors++;
+ m_freem(m);
+ m = NULL;
+ mtx_lock(&sc->sc_mtx);
+ continue;
+ }
+
+ UHSO_DPRINTF(1, "m=%p, len=%d, cp=%p, iplen=%d\n",
+ m, m->m_pkthdr.len, cp, iplen);
+
+ m0 = NULL;
+
+ /* More IP packets in this mbuf */
+ if (iplen < m->m_pkthdr.len) {
+ m0 = m;
+
+ /*
+ * Allocate a new mbuf for this IP packet and
+ * copy the IP-packet into it.
+ */
+ m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
+ bcopy(mtod(m0, uint8_t *), mtod(m, uint8_t *), iplen);
+ m->m_pkthdr.len = m->m_len = iplen;
+
+ /* Adjust the size of the original mbuf */
+ m_adj(m0, iplen);
+ m0 = m_defrag(m0, M_WAIT);
+
+ UHSO_DPRINTF(1, "New mbuf=%p, len=%d/%d, m0=%p, "
+ "m0_len=%d/%d\n", m, m->m_pkthdr.len, m->m_len,
+ m0, m0->m_pkthdr.len, m0->m_len);
+ }
+ else if (iplen > m->m_pkthdr.len) {
+ UHSO_DPRINTF(1, "Defered mbuf=%p, len=%d\n",
+ m, m->m_pkthdr.len);
+ mwait = m;
+ m = NULL;
+ mtx_lock(&sc->sc_mtx);
+ continue;
+ }
+
+ ifp->if_ipackets++;
+ m->m_pkthdr.rcvif = ifp;
+
+ /* Dispatch to IP layer */
+ BPF_MTAP(sc->sc_ifp, m);
+ netisr_dispatch(isr, m);
+ m = m0 != NULL ? m0 : NULL;
+ mtx_lock(&sc->sc_mtx);
+ }
+ sc->sc_mwait = mwait;
+}
+
+static void
+uhso_ifnet_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ struct ifnet *ifp = sc->sc_ifp;
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ ifp->if_opackets++;
+ ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
+ case USB_ST_SETUP:
+tr_setup:
+ IFQ_DRV_DEQUEUE(&ifp->if_snd, m);
+ if (m == NULL)
+ break;
+
+ ifp->if_drv_flags |= IFF_DRV_OACTIVE;
+
+ if (m->m_pkthdr.len > MCLBYTES)
+ m->m_pkthdr.len = MCLBYTES;
+
+ usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len);
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len);
+ usbd_transfer_submit(xfer);
+
+ BPF_MTAP(ifp, m);
+ m_freem(m);
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+
+}
+
+static int
+uhso_if_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+ struct uhso_softc *sc;
+
+ sc = ifp->if_softc;
+
+ switch (cmd) {
+ case SIOCSIFFLAGS:
+ if (ifp->if_flags & IFF_UP) {
+ if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
+ uhso_if_init(sc);
+ }
+ }
+ else {
+ if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
+ mtx_lock(&sc->sc_mtx);
+ uhso_if_stop(sc);
+ mtx_unlock(&sc->sc_mtx);
+ }
+ }
+ break;
+ case SIOCSIFADDR:
+ case SIOCSIFDSTADDR:
+ case SIOCADDMULTI:
+ case SIOCDELMULTI:
+ break;
+ default:
+ return (EINVAL);
+ }
+ return (0);
+}
+
+static void
+uhso_if_init(void *priv)
+{
+ struct uhso_softc *sc = priv;
+ struct ifnet *ifp = sc->sc_ifp;
+
+ mtx_lock(&sc->sc_mtx);
+ uhso_if_stop(sc);
+ ifp = sc->sc_ifp;
+ ifp->if_flags |= IFF_UP;
+ ifp->if_drv_flags |= IFF_DRV_RUNNING;
+ mtx_unlock(&sc->sc_mtx);
+
+ UHSO_DPRINTF(3, "ifnet initialized\n");
+}
+
+static int
+uhso_if_output(struct ifnet *ifp, struct mbuf *m0, struct sockaddr *dst,
+ struct route *ro)
+{
+ int error;
+
+ /* Only IPv4/6 support */
+ if (dst->sa_family != AF_INET
+#ifdef INET6
+ && dst->sa_family != AF_INET6
+#endif
+ ) {
+ return (EAFNOSUPPORT);
+ }
+
+ error = (ifp->if_transmit)(ifp, m0);
+ if (error) {
+ ifp->if_oerrors++;
+ return (ENOBUFS);
+ }
+ ifp->if_opackets++;
+
+ return (0);
+}
+
+static void
+uhso_if_start(struct ifnet *ifp)
+{
+ struct uhso_softc *sc = ifp->if_softc;
+
+ if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
+ UHSO_DPRINTF(1, "Not running\n");
+ return;
+ }
+
+ mtx_lock(&sc->sc_mtx);
+ usbd_transfer_start(sc->sc_if_xfer[UHSO_IFNET_READ]);
+ usbd_transfer_start(sc->sc_if_xfer[UHSO_IFNET_WRITE]);
+ mtx_unlock(&sc->sc_mtx);
+ UHSO_DPRINTF(3, "interface started\n");
+}
+
+static void
+uhso_if_stop(struct uhso_softc *sc)
+{
+
+ usbd_transfer_stop(sc->sc_if_xfer[UHSO_IFNET_READ]);
+ usbd_transfer_stop(sc->sc_if_xfer[UHSO_IFNET_WRITE]);
+
+ sc->sc_ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
+}
diff --git a/sys/dev/usb/usbdevs b/sys/dev/usb/usbdevs
index 54a1b45..2037a5c 100644
--- a/sys/dev/usb/usbdevs
+++ b/sys/dev/usb/usbdevs
@@ -2100,6 +2100,17 @@ product OPTION E7061 0x7061 3G modem
product OPTION E7100 0x7100 3G modem
product OPTION GTM380 0x7201 3G modem
product OPTION GE40X 0x7601 Globetrotter HSUPA
+product OPTION GSICON72 0x6911 GlobeSurfer iCON
+product OPTION GSICONHSUPA 0x7251 Globetrotter HSUPA
+product OPTION ICON401 0x7401 GlobeSurfer iCON 401
+product OPTION GTHSUPA 0x7011 Globetrotter HSUPA
+product OPTION GMT382 0x7501 Globetrotter HSUPA
+product OPTION GE40X_1 0x7301 Globetrotter HSUPA
+product OPTION GE40X_2 0x7361 Globetrotter HSUPA
+product OPTION GE40X_3 0x7381 Globetrotter HSUPA
+product OPTION ICONEDGE 0xc031 GlobeSurfer iCON EDGE
+product OPTION MODHSXPA 0xd013 Globetrotter HSUPA
+product OPTION ICON321 0xd031 Globetrotter HSUPA
/* OQO */
product OQO WIFI01 0x0002 model 01 WiFi interface
diff --git a/sys/modules/usb/Makefile b/sys/modules/usb/Makefile
index 8c771d7..09ad669 100644
--- a/sys/modules/usb/Makefile
+++ b/sys/modules/usb/Makefile
@@ -31,7 +31,7 @@ SUBDIR += rum uath upgt ural zyd ${_urtw}
SUBDIR += atp uhid ukbd ums udbp ufm
SUBDIR += ucom u3g uark ubsa ubser uchcom ucycom ufoma uftdi ugensa uipaq ulpt \
umct umodem umoscom uplcom uslcom uvisor uvscom
-SUBDIR += uether aue axe cdce cue kue rue udav
+SUBDIR += uether aue axe cdce cue kue rue udav uhso
SUBDIR += usfs umass urio
SUBDIR += quirk template
diff --git a/sys/modules/usb/uhso/Makefile b/sys/modules/usb/uhso/Makefile
new file mode 100644
index 0000000..b08dc42
--- /dev/null
+++ b/sys/modules/usb/uhso/Makefile
@@ -0,0 +1,37 @@
+#
+# $FreeBSD$
+#
+# Copyright (c) 2010 Andrew Thompson. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+
+S= ${.CURDIR}/../../..
+
+.PATH: $S/dev/usb/net
+
+KMOD= uhso
+SRCS= opt_bus.h opt_usb.h device_if.h bus_if.h usb_if.h usbdevs.h \
+ opt_inet.h \
+ uhso.c
+
+.include <bsd.kmod.mk>
diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile
index f3e0a1b..6f7df81 100644
--- a/usr.sbin/Makefile
+++ b/usr.sbin/Makefile
@@ -189,6 +189,7 @@ SUBDIR= ${_ac} \
tzsetup \
${_uathload} \
ugidfw \
+ ${_uhsoctl} \
${_usbdevs} \
${_usbconfig} \
${_vidcontrol} \
@@ -410,6 +411,7 @@ _crunch= crunch
.if ${MACHINE_ARCH} != "ia64"
_uathload= uathload
.endif
+_uhsoctl= uhsoctl
#_usbdevs= usbdevs
_usbconfig= usbconfig
.endif
diff --git a/usr.sbin/uhsoctl/Makefile b/usr.sbin/uhsoctl/Makefile
new file mode 100644
index 0000000..9704923
--- /dev/null
+++ b/usr.sbin/uhsoctl/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= uhsoctl
+MAN= uhsoctl.1
+WARNS= 1
+
+DPADD= ${LIBUTIL}
+LDADD= -lutil
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/uhsoctl/uhsoctl.1 b/usr.sbin/uhsoctl/uhsoctl.1
new file mode 100644
index 0000000..932cefa
--- /dev/null
+++ b/usr.sbin/uhsoctl/uhsoctl.1
@@ -0,0 +1,104 @@
+.\" Copyright (c) 2008-2009 Fredrik Lindberg
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd Aug 12, 2009
+.Os
+.Dt UHSOCTL 1
+.Sh NAME
+.Nm uhsoctl
+.Nd connection utility for Option based devices
+.Sh SYNOPSIS
+.Nm
+.Op Fl a Ar apn
+.Op Fl c Ar cid
+.Op Fl p Ar pin
+.Op Fl u Ar username
+.Op Fl k Ar password
+.Op Fl r Ar path
+.Op Fl f Ar path
+.Op Fl b | n
+.Ar interface
+.Nm
+.Fl d
+.Ar interface
+.Nm
+.Fl h
+.Sh DESCRIPTION
+.Nm
+is a small connection utility for Option N.V. devices that are based on Options
+packet interface and uses proprietary AT_* calls to establish connections.
+The utility (tries to) configure both default route and name servers
+(/etc/resolv.conf).
+.Pp
+By default
+.Nm
+detaches from the terminal upon on a successful connection, a few command-line
+options exists that allows this behavior to be changed.
+.Pp
+.Nm
+attempts to find a usable controlling serial port based on the provided network
+interface.
+If this fails you might specify a serial port manually.
+.Sh OPTIONS
+.Bl -tag -width XXXX
+.It Fl a Ar apn
+Specify APN to connect to.
+.It Fl c Ar cid
+Specify CID (Context ID) to use, by default CID 1 is used.
+If an APN has been configured once, it's enough to specify the CID used for
+further accesses.
+.It Fl p Ar pin
+Specify SIM PIN.
+.It Fl u Ar username
+Specify username.
+.It Fl k Ar password
+Specify username.
+.It Fl r Ar path
+Path to resolv.conf, default /etc/resolv.conf.
+Use /dev/null to disable updating of name servers.
+.It Fl f Ar path
+Explicitly set the serial port to use as controlling terminal.
+Might be needed if the automatic detection fails.
+.It Fl b
+Fork into background directly, before a connection has been established.
+.It Fl n
+Never fork into background, run entirely in foreground.
+.El
+.Sh EXAMPLES
+Connect to
+.Dq Li apn.example.com
+on interface
+.Dq Li uhso0
+and use PIN
+.Dq 1234
+to enable the SIM card.
+
+.Dl "uhsoctl -a apn.example.com -p 1234 uhso0"
+
+Disconnect from a previously established connection
+
+.Dl "uhsoctl -d uhso0"
+.Sh SEE ALSO
+.Xr uhso 4
diff --git a/usr.sbin/uhsoctl/uhsoctl.c b/usr.sbin/uhsoctl/uhsoctl.c
new file mode 100644
index 0000000..6ed5245
--- /dev/null
+++ b/usr.sbin/uhsoctl/uhsoctl.c
@@ -0,0 +1,1532 @@
+/*-
+ * Copyright (c) 2008-2009 Fredrik Lindberg
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <signal.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <ifaddrs.h>
+#include <libutil.h>
+#include <time.h>
+
+/*
+ * Connection utility to ease connectivity using the raw IP packet interface
+ * available on uhso(4) devices.
+ */
+
+#define TTY_NAME "/dev/%s"
+#define SYSCTL_TEST "dev.uhso.%d.%%driver"
+#define SYSCTL_PORTS "dev.uhso.%d.ports"
+#define SYSCTL_NETIF "dev.uhso.%d.netif"
+#define SYSCTL_NAME_TTY "dev.uhso.%d.port.%s.tty"
+#define SYSCTL_NAME_DESC "dev.uhso.%d.port.%s.desc"
+#define RESOLV_PATH "/etc/resolv.conf"
+#define PIDFILE "/var/run/uhsoctl.%s.pid"
+
+static const char *network_access_type[] = {
+ "GSM",
+ "Compact GSM",
+ "UMTS",
+ "GSM (EGPRS)",
+ "HSDPA",
+ "HSUPA",
+ "HSDPA/HSUPA"
+};
+
+static const char *network_reg_status[] = {
+ "Not registered",
+ "Registered",
+ "Searching for network",
+ "Network registration denied",
+ "Unknown",
+ "Registered (roaming)"
+};
+
+struct ctx {
+ int fd;
+ int flags;
+#define IPASSIGNED 0x01
+#define FLG_NODAEMON 0x02 /* Don't detach from terminal */
+#define FLG_DAEMON 0x04 /* Running as daemon */
+#define FLG_DELAYED 0x08 /* Fork into background after connect */
+#define FLG_NEWDATA 0x10
+#define FLG_WATCHDOG 0x20 /* Watchdog enabled */
+#define FLG_WDEXP 0x40 /* Watchdog expired */
+ const char *ifnam;
+ const char *pin; /* device PIN */
+
+ char pidfile[128];
+ struct pidfh *pfh;
+
+ time_t watchdog;
+
+ /* PDP context settings */
+ int pdp_ctx;
+ const char *pdp_apn;
+ const char *pdp_user;
+ const char *pdp_pwd;
+
+ /* Connection status */
+ int con_status; /* Connected? */
+ char *con_apn; /* Connected APN */
+ char *con_oper; /* Operator name */
+ int con_net_stat; /* Network connection status */
+ int con_net_type; /* Network connection type */
+
+ /* Misc. status */
+ int dbm;
+
+ /* IP and nameserver settings */
+ struct in_addr ip;
+ char **ns;
+ const char *resolv_path;
+ char *resolv; /* Old resolv.conf */
+ size_t resolv_sz;
+};
+
+static int readline_buf(const char *, const char *, char *, size_t);
+static int readline(int, char *, size_t);
+static void daemonize(struct ctx *);
+
+static int at_cmd_async(int, const char *, ...);
+
+typedef union {
+ void *ptr;
+ uint32_t int32;
+} resp_data;
+typedef struct {
+ resp_data val[2];
+} resp_arg;
+typedef void (*resp_cb)(resp_arg *, const char *, const char *);
+
+typedef void (*async_cb)(void *, const char *);
+struct async_handle {
+ const char *cmd;
+ async_cb func;
+};
+
+static void at_async_creg(void *, const char *);
+static void at_async_cgreg(void *, const char *);
+static void at_async_cops(void *, const char *);
+static void at_async_owancall(void *, const char *);
+static void at_async_owandata(void *, const char *);
+static void at_async_csq(void *, const char *);
+
+static struct async_handle async_cmd[] = {
+ { "+CREG", at_async_creg },
+ { "+CGREG", at_async_cgreg },
+ { "+COPS", at_async_cops },
+ { "+CSQ", at_async_csq },
+ { "_OWANCALL", at_async_owancall },
+ { "_OWANDATA", at_async_owandata },
+ { NULL, NULL }
+};
+
+struct timer_entry;
+struct timers {
+ TAILQ_HEAD(, timer_entry) head;
+ int res;
+};
+
+typedef void (*tmr_cb)(int, void *);
+struct timer_entry {
+ TAILQ_ENTRY(timer_entry) next;
+ int id;
+ int timeout;
+ tmr_cb func;
+ void *arg;
+};
+
+
+static struct timers timers;
+static volatile int running = 1;
+static int syslog_open = 0;
+static char syslog_title[64];
+
+/* Periodic timer, runs ready timer tasks every tick */
+static void
+tmr_run(struct timers *tmrs)
+{
+ struct timer_entry *te, *te2;
+
+ te = TAILQ_FIRST(&tmrs->head);
+ if (te == NULL)
+ return;
+
+ te->timeout -= tmrs->res;
+ while (te->timeout <= 0) {
+ te2 = TAILQ_NEXT(te, next);
+ TAILQ_REMOVE(&tmrs->head, te, next);
+ if (te2 != NULL)
+ te2->timeout -= tmrs->res;
+
+ te->func(te->id, te->arg);
+ free(te);
+ te = te2;
+ if (te == NULL)
+ break;
+ }
+}
+
+/* Add a new timer */
+static void
+tmr_add(struct timers *tmrs, int id, int timeout, tmr_cb func, void *arg)
+{
+ struct timer_entry *te, *te2, *te3;
+
+ te = malloc(sizeof(struct timer_entry));
+ memset(te, 0, sizeof(struct timer_entry));
+
+ te->timeout = timeout;
+ te->func = func;
+ te->arg = arg;
+ te->id = id;
+
+ te2 = TAILQ_FIRST(&tmrs->head);
+
+ if (TAILQ_EMPTY(&tmrs->head)) {
+ TAILQ_INSERT_HEAD(&tmrs->head, te, next);
+ } else if (te->timeout < te2->timeout) {
+ te2->timeout -= te->timeout;
+ TAILQ_INSERT_HEAD(&tmrs->head, te, next);
+ } else {
+ while (te->timeout >= te2->timeout) {
+ te->timeout -= te2->timeout;
+ te3 = TAILQ_NEXT(te2, next);
+ if (te3 == NULL || te3->timeout > te->timeout)
+ break;
+ te2 = te3;
+ }
+ TAILQ_INSERT_AFTER(&tmrs->head, te2, te, next);
+ }
+}
+
+#define watchdog_enable(ctx) (ctx)->flags |= FLG_WATCHDOG
+#define watchdog_disable(ctx) (ctx)->flags &= ~FLG_WATCHDOG
+
+static void
+watchdog_reset(struct ctx *ctx, int timeout)
+{
+ struct timespec tp;
+
+ clock_gettime(CLOCK_MONOTONIC, &tp),
+ ctx->watchdog = tp.tv_sec + timeout;
+
+ watchdog_enable(ctx);
+}
+
+static void
+tmr_creg(int id, void *arg)
+{
+ struct ctx *ctx = arg;
+
+ at_cmd_async(ctx->fd, "AT+CREG?\r\n");
+ watchdog_reset(ctx, 10);
+}
+
+static void
+tmr_cgreg(int id, void *arg)
+{
+ struct ctx *ctx = arg;
+
+ at_cmd_async(ctx->fd, "AT+CGREG?\r\n");
+ watchdog_reset(ctx, 10);
+}
+
+static void
+tmr_status(int id, void *arg)
+{
+ struct ctx *ctx = arg;
+
+ at_cmd_async(ctx->fd, "AT+CSQ\r\n");
+ watchdog_reset(ctx, 10);
+}
+
+static void
+tmr_watchdog(int id, void *arg)
+{
+ struct ctx *ctx = arg;
+ pid_t self;
+ struct timespec tp;
+
+ tmr_add(&timers, 1, 5, tmr_watchdog, ctx);
+
+ if (!(ctx->flags & FLG_WATCHDOG))
+ return;
+
+ clock_gettime(CLOCK_MONOTONIC, &tp);
+
+ if (tp.tv_sec >= ctx->watchdog) {
+#ifdef DEBUG
+ fprintf(stderr, "Watchdog expired\n");
+#endif
+ ctx->flags |= FLG_WDEXP;
+ self = getpid();
+ kill(self, SIGHUP);
+ }
+}
+
+static void
+sig_handle(int sig)
+{
+
+ switch (sig) {
+ case SIGHUP:
+ case SIGINT:
+ case SIGQUIT:
+ case SIGTERM:
+ running = 0;
+ break;
+ case SIGALRM:
+ tmr_run(&timers);
+ break;
+ }
+}
+
+static void
+logger(int pri, const char *fmt, ...)
+{
+ char *buf;
+ va_list ap;
+
+ va_start(ap, fmt);
+ vasprintf(&buf, fmt, ap);
+ if (syslog_open)
+ syslog(pri, buf);
+ else {
+ switch (pri) {
+ case LOG_INFO:
+ case LOG_NOTICE:
+ printf("%s\n", buf);
+ break;
+ default:
+ fprintf(stderr, "%s: %s\n", getprogname(), buf);
+ break;
+ }
+ }
+
+ free(buf);
+ va_end(ap);
+}
+
+/* Add/remove IP address from an interface */
+static int
+ifaddr_ad(int d, const char *ifnam, struct sockaddr *sa, struct sockaddr *mask)
+{
+ struct ifaliasreq req;
+ int fd, error;
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0)
+ return (-1);
+
+ memset(&req, 0, sizeof(struct ifaliasreq));
+ strlcpy(req.ifra_name, ifnam, sizeof(req.ifra_name));
+ memcpy(&req.ifra_addr, sa, sa->sa_len);
+ memcpy(&req.ifra_mask, mask, mask->sa_len);
+
+ error = ioctl(fd, d, (char *)&req);
+ close(fd);
+ return (error);
+}
+
+#define if_ifup(ifnam) if_setflags(ifnam, IFF_UP)
+#define if_ifdown(ifnam) if_setflags(ifnam, -IFF_UP)
+
+static int
+if_setflags(const char *ifnam, int flags)
+{
+ struct ifreq ifr;
+ int fd, error;
+ unsigned int oflags = 0;
+
+ memset(&ifr, 0, sizeof(struct ifreq));
+ strlcpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name));
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0)
+ return (-1);
+
+ error = ioctl(fd, SIOCGIFFLAGS, &ifr);
+ if (error == 0) {
+ oflags = (ifr.ifr_flags & 0xffff) | (ifr.ifr_flagshigh << 16);
+ }
+
+ if (flags < 0)
+ oflags &= ~(-flags);
+ else
+ oflags |= flags;
+
+ ifr.ifr_flags = oflags & 0xffff;
+ ifr.ifr_flagshigh = oflags >> 16;
+
+ error = ioctl(fd, SIOCSIFFLAGS, &ifr);
+ if (error != 0)
+ warn("ioctl SIOCSIFFLAGS");
+
+ close(fd);
+ return (error);
+}
+
+static int
+ifaddr_add(const char *ifnam, struct sockaddr *sa, struct sockaddr *mask)
+{
+ int error;
+
+ error = ifaddr_ad(SIOCAIFADDR, ifnam, sa, mask);
+ if (error != 0)
+ warn("ioctl SIOCAIFADDR");
+ return (error);
+}
+
+static int
+ifaddr_del(const char *ifnam, struct sockaddr *sa, struct sockaddr *mask)
+{
+ int error;
+
+ error = ifaddr_ad(SIOCDIFADDR, ifnam, sa, mask);
+ if (error != 0)
+ warn("ioctl SIOCDIFADDR");
+ return (error);
+}
+
+static int
+set_nameservers(struct ctx *ctx, const char *respath, int ns, ...)
+{
+ int i, n, fd;
+ FILE *fp;
+ char *p;
+ va_list ap;
+ struct stat sb;
+ char buf[512];
+
+ if (ctx->ns != NULL) {
+ for (i = 0; ctx->ns[i] != NULL; i++) {
+ free(ctx->ns[i]);
+ }
+ free(ctx->ns);
+ }
+
+ fd = open(respath, O_RDWR | O_CREAT | O_NOFOLLOW);
+ if (fd < 0)
+ return (-1);
+
+ if (ns == 0) {
+ /* Attempt to restore old resolv.conf */
+ if (ctx->resolv != NULL) {
+ ftruncate(fd, 0);
+ lseek(fd, 0, SEEK_SET);
+ write(fd, ctx->resolv, ctx->resolv_sz);
+ free(ctx->resolv);
+ ctx->resolv = NULL;
+ ctx->resolv_sz = 0;
+ }
+ close(fd);
+ return (0);
+ }
+
+
+ ctx->ns = malloc(sizeof(char *) * (ns + 1));
+ if (ctx->ns == NULL) {
+ close(fd);
+ return (-1);
+ }
+
+ va_start(ap, ns);
+ for (i = 0; i < ns; i++) {
+ p = va_arg(ap, char *);
+ ctx->ns[i] = strdup(p);
+ }
+ ctx->ns[i] = NULL;
+ va_end(ap);
+
+ /* Attempt to backup the old resolv.conf */
+ if (ctx->resolv == NULL) {
+ i = fstat(fd, &sb);
+ if (i == 0 && sb.st_size != 0) {
+ ctx->resolv_sz = sb.st_size;
+ ctx->resolv = malloc(sb.st_size);
+ if (ctx->resolv != NULL) {
+ n = read(fd, ctx->resolv, sb.st_size);
+ if (n != sb.st_size) {
+ free(ctx->resolv);
+ ctx->resolv = NULL;
+ }
+ }
+ }
+ }
+
+
+ ftruncate(fd, 0);
+ lseek(fd, 0, SEEK_SET);
+ fp = fdopen(fd, "w");
+
+ /*
+ * Write back everything other than nameserver entries to the
+ * new resolv.conf
+ */
+ if (ctx->resolv != NULL) {
+ p = ctx->resolv;
+ while ((i = readline_buf(p, ctx->resolv + ctx->resolv_sz, buf,
+ sizeof(buf))) > 0) {
+ p += i;
+ if (strncasecmp(buf, "nameserver", 10) == 0)
+ continue;
+ fprintf(fp, "%s", buf);
+ }
+ }
+
+ for (i = 0; ctx->ns[i] != NULL; i++) {
+ fprintf(fp, "nameserver %s\n", ctx->ns[i]);
+ }
+ fclose(fp);
+ return (0);
+}
+
+/* Read a \n-terminated line from buffer */
+static int
+readline_buf(const char *s, const char *e, char *buf, size_t bufsz)
+{
+ int pos = 0;
+ char *p = buf;
+
+ for (; s < e; s++) {
+ *p = *s;
+ pos++;
+ if (pos >= (bufsz - 1))
+ break;
+ if (*p++ == '\n')
+ break;
+ }
+ *p = '\0';
+ return (pos);
+}
+
+/* Read a \n-terminated line from file */
+static int
+readline(int fd, char *buf, size_t bufsz)
+{
+ int n = 0, pos = 0;
+ char *p = buf;
+
+ for (;;) {
+ n = read(fd, p, 1);
+ if (n <= 0)
+ break;
+ pos++;
+ if (pos >= (bufsz - 1))
+ break;
+ if (*p++ == '\n')
+ break;
+ }
+ *p = '\0';
+ return (n <= 0 ? n : pos);
+}
+
+/*
+ * Synchronous AT command
+ */
+static int
+at_cmd(struct ctx *ctx, const char *resp, resp_cb cb, resp_arg *ra, const char *cf, ...)
+{
+ size_t l;
+ int n, error, retval = 0;
+ va_list ap;
+ fd_set set;
+ char buf[512];
+ char cmd[64];
+
+ va_start(ap, cf);
+ vsnprintf(cmd, sizeof(cmd), cf, ap);
+ va_end(ap);
+
+#ifdef DEBUG
+ fprintf(stderr, "SYNC_CMD: %s", cmd);
+#endif
+
+ l = strlen(cmd);
+ n = write(ctx->fd, cmd, l);
+ if (n <= 0)
+ return (-1);
+
+ if (resp != NULL) {
+ l = strlen(resp);
+#ifdef DEBUG
+ fprintf(stderr, "SYNC_EXP: %s (%d)\n", resp, l);
+#endif
+ }
+
+ for (;;) {
+ bzero(buf, sizeof(buf));
+
+ FD_ZERO(&set);
+ watchdog_reset(ctx, 5);
+ do {
+ FD_SET(ctx->fd, &set);
+ error = select(ctx->fd + 1, &set, NULL, NULL, NULL);
+ if (error < 0 && errno == EINTR && ctx->flags & FLG_WDEXP) {
+ watchdog_disable(ctx);
+ retval = -2;
+ break;
+ }
+ } while (error <= 0 && errno == EINTR);
+
+ if (error <= 0) {
+ retval = -2;
+ break;
+ }
+
+ n = readline(ctx->fd, buf, sizeof(buf));
+ if (n <= 0) {
+ retval = -2;
+ break;
+ }
+
+ if (strcmp(buf, "\r\n") == 0 || strcmp(buf, "\n") == 0)
+ continue;
+
+#ifdef DEBUG
+ fprintf(stderr, "SYNC_RESP: %s", buf);
+#endif
+
+ if (strncmp(buf, "OK", 2) == 0) {
+ break;
+ }
+ else if (strncmp(buf, "ERROR", 5) == 0) {
+ retval = -1;
+ break;
+ }
+
+ if (resp != NULL) {
+ retval = strncmp(resp, buf, l);
+ if (retval == 0 && cb != NULL) {
+ cb(ra, cmd, buf);
+ }
+ }
+ }
+#ifdef DEBUG
+ fprintf(stderr, "SYNC_RETVAL=%d\n", retval);
+#endif
+ return (retval);
+}
+
+static int
+at_cmd_async(int fd, const char *cf, ...)
+{
+ size_t l;
+ va_list ap;
+ char cmd[64];
+
+ va_start(ap, cf);
+ vsnprintf(cmd, sizeof(cmd), cf, ap);
+ va_end(ap);
+
+#ifdef DEBUG
+ fprintf(stderr, "CMD: %s", cmd);
+#endif
+ l = strlen(cmd);
+ return (write(fd, cmd, l));
+}
+
+static void
+saveresp(resp_arg *ra, const char *cmd, const char *resp)
+{
+ char **buf;
+ int i = ra->val[1].int32;
+
+ buf = realloc(ra->val[0].ptr, sizeof(char *) * (i + 1));
+ if (buf == NULL)
+ return;
+
+ buf[i] = strdup(resp);
+
+ ra->val[0].ptr = buf;
+ ra->val[1].int32 = i + 1;
+}
+
+static void
+at_async_creg(void *arg, const char *resp)
+{
+ struct ctx *ctx = arg;
+ int n, reg;
+
+ n = sscanf(resp, "+CREG: %*d,%d", &reg);
+ if (n != 1) {
+ n = sscanf(resp, "+CREG: %d", &reg);
+ if (n != 1)
+ return;
+ }
+
+ if (ctx->con_net_stat != 1 && ctx->con_net_stat != 5) {
+ tmr_add(&timers, 1, 1, tmr_creg, ctx);
+ }
+ else {
+ tmr_add(&timers, 1, 30, tmr_creg, ctx);
+ }
+
+ if (ctx->con_net_stat == reg)
+ return;
+
+ ctx->con_net_stat = reg;
+ at_cmd_async(ctx->fd, "AT+COPS?\r\n");
+}
+
+static void
+at_async_cgreg(void *arg, const char *resp)
+{
+ struct ctx *ctx = arg;
+ int n, reg;
+
+ n = sscanf(resp, "+CGREG: %*d,%d", &reg);
+ if (n != 1) {
+ n = sscanf(resp, "+CGREG: %d", &reg);
+ if (n != 1)
+ return;
+ }
+
+ if (ctx->con_net_stat != 1 && ctx->con_net_stat != 5) {
+ tmr_add(&timers, 1, 1, tmr_cgreg, ctx);
+ }
+ else {
+ tmr_add(&timers, 1, 30, tmr_cgreg, ctx);
+ }
+
+ if (ctx->con_net_stat == reg)
+ return;
+
+ ctx->con_net_stat = reg;
+ at_cmd_async(ctx->fd, "AT+COPS?\r\n");
+}
+
+
+static void
+at_async_cops(void *arg, const char *resp)
+{
+ struct ctx *ctx = arg;
+ int n, at;
+ char opr[64];
+
+ n = sscanf(resp, "+COPS: %*d,%*d,\"%[^\"]\",%d",
+ opr, &at);
+ if (n != 2)
+ return;
+
+ if (ctx->con_oper != NULL) {
+ if (ctx->con_net_type == at &&
+ strcasecmp(opr, ctx->con_oper) == 0)
+ return;
+ free(ctx->con_oper);
+ }
+
+ ctx->con_oper = strdup(opr);
+ ctx->con_net_type = at;
+
+ if (ctx->con_net_stat == 1 || ctx->con_net_stat == 5) {
+ logger(LOG_NOTICE, "%s to \"%s\" (%s)",
+ network_reg_status[ctx->con_net_stat],
+ ctx->con_oper, network_access_type[ctx->con_net_type]);
+ if (ctx->con_status != 1) {
+ at_cmd_async(ctx->fd, "AT_OWANCALL=%d,1,1\r\n",
+ ctx->pdp_ctx);
+ }
+ }
+ else {
+ logger(LOG_NOTICE, "%s (%s)",
+ network_reg_status[ctx->con_net_stat],
+ network_access_type[ctx->con_net_type]);
+ }
+}
+
+/*
+ * Signal strength for pretty console output
+ *
+ * From 3GPP TS 27.007 V8.3.0, Section 8.5
+ * 0 = -113 dBm or less
+ * 1 = -111 dBm
+ * 2...30 = -109...-53 dBm
+ * 31 = -51 dBm or greater
+ *
+ * So, dbm = (rssi * 2) - 113
+*/
+static void
+at_async_csq(void *arg, const char *resp)
+{
+ struct ctx *ctx = arg;
+ int n, rssi;
+
+ n = sscanf(resp, "+CSQ: %d,%*d", &rssi);
+ if (n != 1)
+ return;
+ if (rssi == 99)
+ ctx->dbm = 0;
+ else {
+ ctx->dbm = (rssi * 2) - 113;
+ tmr_add(&timers, 1, 15, tmr_status, ctx);
+ }
+
+ ctx->flags |= FLG_NEWDATA;
+}
+
+static void
+at_async_owancall(void *arg, const char *resp)
+{
+ struct ctx *ctx = arg;
+ int n, i;
+
+ n = sscanf(resp, "_OWANCALL: %*d,%d", &i);
+ if (n != 1)
+ return;
+
+ if (i == ctx->con_status)
+ return;
+
+ at_cmd_async(ctx->fd, "AT_OWANDATA=%d\r\n", ctx->pdp_ctx);
+
+ ctx->con_status = i;
+ if (ctx->con_status == 1) {
+ logger(LOG_NOTICE, "Connected to \"%s\" (%s), %s",
+ ctx->con_oper, ctx->con_apn,
+ network_access_type[ctx->con_net_type]);
+ }
+ else {
+ logger(LOG_NOTICE, "Disconnected from \"%s\" (%s)",
+ ctx->con_oper, ctx->con_apn);
+ }
+}
+
+static void
+at_async_owandata(void *arg, const char *resp)
+{
+ struct ctx *ctx = arg;
+ char ip[40], ns1[40], ns2[40];
+ int n, error, rs;
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_in sin, mask;
+ struct sockaddr_dl sdl;
+ struct {
+ struct rt_msghdr rtm;
+ char buf[512];
+ } r;
+ char *cp = r.buf;
+
+ n = sscanf(resp, "_OWANDATA: %*d, %[^,], %*[^,], %[^,], %[^,]",
+ ip, ns1, ns2);
+ if (n != 3)
+ return;
+
+ /* XXX: AF_INET assumption */
+
+ logger(LOG_NOTICE, "IP address: %s, Nameservers: %s, %s", ip, ns1, ns2);
+
+ sin.sin_len = mask.sin_len = sizeof(struct sockaddr_in);
+ memset(&mask.sin_addr.s_addr, 0xff, sizeof(mask.sin_addr.s_addr));
+ sin.sin_family = mask.sin_family = AF_INET;
+
+ if (ctx->flags & IPASSIGNED) {
+ memcpy(&sin.sin_addr.s_addr, &ctx->ip.s_addr,
+ sizeof(sin.sin_addr.s_addr));
+ ifaddr_del(ctx->ifnam, (struct sockaddr *)&sin,
+ (struct sockaddr *)&mask);
+ }
+ inet_pton(AF_INET, ip, &ctx->ip.s_addr);
+ memcpy(&sin.sin_addr.s_addr, &ctx->ip.s_addr,
+ sizeof(sin.sin_addr.s_addr));
+
+ error = ifaddr_add(ctx->ifnam, (struct sockaddr *)&sin,
+ (struct sockaddr *)&mask);
+ if (error != 0) {
+ logger(LOG_ERR, "failed to set ip-address");
+ return;
+ }
+
+ if_ifup(ctx->ifnam);
+
+ ctx->flags |= IPASSIGNED;
+
+ set_nameservers(ctx, ctx->resolv_path, 0);
+ error = set_nameservers(ctx, ctx->resolv_path, 2, ns1, ns2);
+ if (error != 0) {
+ logger(LOG_ERR, "failed to set nameservers");
+ }
+
+ error = getifaddrs(&ifap);
+ if (error != 0) {
+ logger(LOG_ERR, "getifaddrs: %s", strerror(errno));
+ return;
+ }
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+ if (strcmp(ctx->ifnam, ifa->ifa_name) == 0) {
+ memcpy(&sdl, (struct sockaddr_dl *)ifa->ifa_addr,
+ sizeof(struct sockaddr_dl));
+ break;
+ }
+ }
+ if (ifa == NULL)
+ return;
+
+ rs = socket(PF_ROUTE, SOCK_RAW, 0);
+ if (rs < 0) {
+ logger(LOG_ERR, "socket PF_ROUTE: %s", strerror(errno));
+ return;
+ }
+
+ memset(&r, 0, sizeof(r));
+
+ r.rtm.rtm_version = RTM_VERSION;
+ r.rtm.rtm_type = RTM_ADD;
+ r.rtm.rtm_flags = RTF_UP | RTF_STATIC;
+ r.rtm.rtm_pid = getpid();
+ memset(&sin, 0, sizeof(struct sockaddr_in));
+ sin.sin_family = AF_INET;
+ sin.sin_len = sizeof(struct sockaddr_in);
+
+ memcpy(cp, &sin, sin.sin_len);
+ cp += SA_SIZE(&sin);
+ memcpy(cp, &sdl, sdl.sdl_len);
+ cp += SA_SIZE(&sdl);
+ memcpy(cp, &sin, sin.sin_len);
+ r.rtm.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
+ r.rtm.rtm_msglen = sizeof(r);
+
+ n = write(rs, &r, r.rtm.rtm_msglen);
+ if (n != r.rtm.rtm_msglen) {
+ r.rtm.rtm_type = RTM_DELETE;
+ n = write(rs, &r, r.rtm.rtm_msglen);
+ r.rtm.rtm_type = RTM_ADD;
+ n = write(rs, &r, r.rtm.rtm_msglen);
+ }
+
+ if (n != r.rtm.rtm_msglen) {
+ logger(LOG_ERR, "failed to set default route: %s",
+ strerror(errno));
+ }
+ close(rs);
+
+ /* Delayed daemonization */
+ if ((ctx->flags & FLG_DELAYED) && !(ctx->flags & FLG_NODAEMON))
+ daemonize(ctx);
+}
+
+static int
+at_async(struct ctx *ctx, void *arg)
+{
+ int n, i;
+ size_t l;
+ char buf[512];
+
+ watchdog_reset(ctx, 15);
+
+ bzero(buf, sizeof(buf));
+ n = readline(ctx->fd, buf, sizeof(buf));
+ if (n <= 0)
+ return (n <= 0 ? -1 : 0);
+
+#ifdef DEBUG
+ fprintf(stderr, "AT_ASYNC_RESP: %s", buf);
+#endif
+ for (i = 0; async_cmd[i].cmd != NULL; i++) {
+ l = strlen(async_cmd[i].cmd);
+ if (strncmp(buf, async_cmd[i].cmd, l) == 0) {
+ async_cmd[i].func(arg, buf);
+ }
+ }
+ return (0);
+}
+
+static const char *port_type_list[] = {
+ "control", "application", "application2", NULL
+};
+
+/*
+ * Attempts to find a list of control tty for the interface
+ * FreeBSD attaches USb devices per interface so we have to go through
+ * hoops to find which ttys that belong to our network interface.
+ */
+static char **
+get_tty(struct ctx *ctx)
+{
+ char buf[64];
+ char data[128];
+ size_t len;
+ int error;
+ unsigned int i;
+ char **list = NULL;
+ int list_size = 0;
+ const char **p;
+
+ for (i = 0; ; i++) {
+ /* Basic test to check if we're even in the right ballpark */
+ snprintf(buf, 64, SYSCTL_TEST, i);
+ len = 127;
+ error = sysctlbyname(buf, data, &len, NULL, 0);
+#ifdef DEBUG
+ fprintf(stderr, "sysctl %s returned(%d): %s\n",
+ buf, error, error == 0 ? data : "FAILED");
+#endif
+ if (error < 0)
+ return NULL;
+ if (strcasecmp(data, "uhso") != 0)
+ return NULL;
+
+ /* Check for interface */
+ snprintf(buf, 64, SYSCTL_NETIF, i);
+ len = 127;
+ error = sysctlbyname(buf, data, &len, NULL, 0);
+#ifdef DEBUG
+ fprintf(stderr, "sysctl %s returned(%d): %s\n",
+ buf, error, error == 0 ? data : "FAILED");
+#endif
+ if (error < 0)
+ continue;
+
+ if (strcasecmp(data, ctx->ifnam) != 0)
+ continue;
+#ifdef DEBUG
+ fprintf(stderr, "Found %s at %s\n", ctx->ifnam, buf);
+#endif
+ break;
+ }
+
+ /* Add multiplexed ports */
+ for (p = port_type_list; *p != NULL; p++) {
+ snprintf(buf, 64, SYSCTL_NAME_TTY, i, *p);
+ len = 127;
+ error = sysctlbyname(buf, data, &len, NULL, 0);
+#ifdef DEBUG
+ fprintf(stderr, "sysctl %s returned(%d): %s\n",
+ buf, error, error == 0 ? data : "FAILED");
+#endif
+ if (error == 0) {
+ list = realloc(list, (list_size + 1) * sizeof(char *));
+ list[list_size] = malloc(strlen(data) + strlen(TTY_NAME));
+ sprintf(list[list_size], TTY_NAME, data);
+ list_size++;
+ }
+ }
+
+ /*
+ * We can return directly if we found multiplexed serial ports because
+ * devices with these ports only have additional diagnostic ports (useless)
+ * and modem ports (for used with pppd).
+ */
+ if (list_size > 0) {
+ list = realloc(list, (list_size + 1) * sizeof(char *));
+ list[list_size] = NULL;
+ return list;
+ }
+
+ /*
+ * The network port is on a high numbered interface so we walk backwards until
+ * we hit anything other than application/control.
+ */
+
+ for (--i; i >= 0; i--) {
+ /* Basic test to check if we're even in the right ballpark */
+ snprintf(buf, 64, SYSCTL_TEST, i);
+ len = 127;
+ error = sysctlbyname(buf, data, &len, NULL, 0);
+#ifdef DEBUG
+ fprintf(stderr, "sysctl %s returned(%d): %s\n",
+ buf, error, error == 0 ? data : "FAILED");
+#endif
+ if (error < 0)
+ break;
+ if (strcasecmp(data, "uhso") != 0)
+ break;
+
+ /* Test for useable ports */
+ for (p = port_type_list; *p != NULL; p++) {
+ snprintf(buf, 64, SYSCTL_NAME_TTY, i, p);
+ len = 127;
+ error = sysctlbyname(buf, data, &len, NULL, 0);
+ if (error == 0) {
+ list = realloc(list, (list_size + 1) * sizeof(char *));
+ list[list_size] = malloc(strlen(data) + strlen(TTY_NAME));
+ sprintf(list[list_size], TTY_NAME, data);
+ list_size++;
+ }
+ }
+
+ /* HACK! first port is a diagnostic port, we abort here */
+ snprintf(buf, 64, SYSCTL_NAME_TTY, i, "diagnostic");
+ len = 127;
+ error = sysctlbyname(buf, data, &len, NULL, 0);
+#ifdef DEBUG
+ fprintf(stderr, "sysctl %s returned(%d): %s\n",
+ buf, error, error == 0 ? data : "FAILED");
+#endif
+ if (error == 0)
+ break;
+ }
+
+ list = realloc(list, (list_size + 1) * sizeof(char *));
+ list[list_size] = NULL;
+ return list;
+}
+
+static int
+do_connect(struct ctx *ctx, const char *tty)
+{
+ int i, error, needcfg;
+ resp_arg ra;
+ struct termios t;
+ char **buf;
+
+#ifdef DEBUG
+ fprintf(stderr, "Attempting to open %s\n", tty);
+#endif
+
+ ctx->fd = open(tty, O_RDWR);
+ if (ctx->fd < 0) {
+#ifdef DEBUG
+ fprintf(stderr, "Failed to open %s\n", tty);
+#endif
+ return (-1);
+ }
+
+ tcgetattr(ctx->fd, &t);
+ t.c_oflag = 0;
+ t.c_iflag = 0;
+ t.c_cflag = CLOCAL | CREAD;
+ t.c_lflag = 0;
+ tcsetattr(ctx->fd, TCSAFLUSH, &t);
+
+ error = at_cmd(ctx, NULL, NULL, NULL, "AT\r\n");
+ if (error == -2) {
+ warnx("failed to read from device");
+ return (-1);
+ }
+
+ /* Check for PIN */
+ error = at_cmd(ctx, "+CPIN: READY", NULL, NULL, "AT+CPIN?\r\n");
+ if (error != 0) {
+ if (ctx->pin == NULL) {
+ errx(1, "device requires PIN");
+ }
+
+ error = at_cmd(ctx, NULL, NULL, NULL, "AT+CPIN=\"%s\"\r\n",
+ ctx->pin);
+ if (error != 0) {
+ errx(1, "wrong PIN");
+ }
+ }
+
+ /*
+ * Check if a PDP context has been configured and configure one
+ * if needed.
+ */
+ ra.val[0].ptr = NULL;
+ ra.val[1].int32 = 0;
+ error = at_cmd(ctx, "+CGDCONT", saveresp, &ra, "AT+CGDCONT?\r\n");
+ buf = ra.val[0].ptr;
+ needcfg = 1;
+ for (i = 0; i < ra.val[1].int32; i++) {
+ char apn[256];
+ int cid;
+ error = sscanf(buf[i], "+CGDCONT: %d,\"%*[^\"]\",\"%[^\"]\"",
+ &cid, apn);
+ if (error != 2) {
+ free(buf[i]);
+ continue;
+ }
+
+ if (cid == ctx->pdp_ctx) {
+ ctx->con_apn = strdup(apn);
+ if (ctx->pdp_apn != NULL) {
+ if (strcmp(apn, ctx->pdp_apn) == 0)
+ needcfg = 0;
+ }
+ else {
+ needcfg = 0;
+ }
+ }
+ free(buf[i]);
+ }
+ free(buf);
+
+ if (needcfg) {
+ if (ctx->pdp_apn == NULL)
+ errx(1, "device is not configured and no APN given");
+
+ error = at_cmd(ctx, NULL, NULL, NULL,
+ "AT+CGDCONT=%d,,\"%s\"\r\n", ctx->pdp_ctx, ctx->pdp_apn);
+ if (error != 0) {
+ errx(1, "failed to configure device");
+ }
+ ctx->con_apn = strdup(ctx->pdp_apn);
+ }
+
+ if (ctx->pdp_user != NULL || ctx->pdp_pwd != NULL) {
+ at_cmd(ctx, NULL, NULL, NULL,
+ "AT$QCPDPP=%d,1,\"%s\",\"%s\"\r\n", ctx->pdp_ctx,
+ (ctx->pdp_user != NULL) ? ctx->pdp_user : "",
+ (ctx->pdp_pwd != NULL) ? ctx->pdp_pwd : "");
+ }
+
+ error = at_cmd(ctx, NULL, NULL, NULL, "AT_OWANCALL=%d,0,0\r\n",
+ ctx->pdp_ctx);
+ if (error != 0)
+ return (-1);
+
+ at_cmd_async(ctx->fd, "AT+CGREG?\r\n");
+ at_cmd_async(ctx->fd, "AT+CREG?\r\n");
+
+ tmr_add(&timers, 1, 5, tmr_status, ctx);
+ return (0);
+}
+
+static void
+do_disconnect(struct ctx *ctx)
+{
+ struct sockaddr_in sin, mask;
+
+ /* Disconnect */
+ at_cmd(ctx, NULL, NULL, NULL, "AT_OWANCALL=%d,0,0\r\n",
+ ctx->pdp_ctx);
+ close(ctx->fd);
+
+ /* Remove ip-address from interface */
+ if (ctx->flags & IPASSIGNED) {
+ sin.sin_len = mask.sin_len = sizeof(struct sockaddr_in);
+ memset(&mask.sin_addr.s_addr, 0xff,
+ sizeof(mask.sin_addr.s_addr));
+ sin.sin_family = mask.sin_family = AF_INET;
+ memcpy(&sin.sin_addr.s_addr, &ctx->ip.s_addr,
+ sizeof(sin.sin_addr.s_addr));
+ ifaddr_del(ctx->ifnam, (struct sockaddr *)&sin,
+ (struct sockaddr *)&mask);
+
+ if_ifdown(ctx->ifnam);
+ ctx->flags &= ~IPASSIGNED;
+ }
+
+ /* Attempt to reset resolv.conf */
+ set_nameservers(ctx, ctx->resolv_path, 0);
+}
+
+static void
+daemonize(struct ctx *ctx)
+{
+ struct pidfh *pfh;
+ pid_t opid;
+
+ snprintf(ctx->pidfile, 127, PIDFILE, ctx->ifnam);
+
+ pfh = pidfile_open(ctx->pidfile, 0600, &opid);
+ if (pfh == NULL) {
+ warn("Cannot create pidfile %s", ctx->pidfile);
+ return;
+ }
+
+ if (daemon(0, 0) == -1) {
+ warn("Cannot daemonize");
+ pidfile_remove(pfh);
+ return;
+ }
+
+ pidfile_write(pfh);
+ ctx->pfh = pfh;
+ ctx->flags |= FLG_DAEMON;
+
+ snprintf(syslog_title, 63, "%s:%s", getprogname(), ctx->ifnam);
+ openlog(syslog_title, LOG_PID, LOG_USER);
+ syslog_open = 1;
+}
+
+static void
+send_disconnect(const char *ifnam)
+{
+ char pidfile[128];
+ FILE *fp;
+ pid_t pid;
+ int n;
+
+ snprintf(pidfile, 127, PIDFILE, ifnam);
+ fp = fopen(pidfile, "r");
+ if (fp == NULL) {
+ warn("Cannot open %s", pidfile);
+ return;
+ }
+
+ n = fscanf(fp, "%d", &pid);
+ fclose(fp);
+ if (n != 1) {
+ warnx("unable to read daemon pid");
+ return;
+ }
+#ifdef DEBUG
+ fprintf(stderr, "Sending SIGTERM to %d\n", pid);
+#endif
+ kill(pid, SIGTERM);
+}
+
+static void
+usage(const char *exec)
+{
+
+ printf("usage %s [-b] [-n] [-a apn] [-c cid] [-p pin] [-u username] "
+ "[-k password] [-r resolvpath] [-f tty] interface\n", exec);
+ printf("usage %s -d interface\n", exec);
+}
+
+enum {
+ MODE_CONN,
+ MODE_DISC
+};
+
+int
+main(int argc, char *argv[])
+{
+ int ch, error, mode;
+ const char *ifnam = NULL;
+ char *tty = NULL;
+ char **p, **tty_list;
+ fd_set set;
+ struct ctx ctx;
+ struct itimerval it;
+
+ TAILQ_INIT(&timers.head);
+ timers.res = 1;
+
+ ctx.pdp_ctx = 1;
+ ctx.pdp_apn = ctx.pdp_user = ctx.pdp_pwd = NULL;
+ ctx.pin = NULL;
+
+ ctx.con_status = 0;
+ ctx.con_apn = NULL;
+ ctx.con_oper = NULL;
+ ctx.con_net_stat = 0;
+ ctx.con_net_type = -1;
+ ctx.flags = 0;
+ ctx.resolv_path = RESOLV_PATH;
+ ctx.resolv = NULL;
+ ctx.ns = NULL;
+ ctx.dbm = 0;
+
+ mode = MODE_CONN;
+ ctx.flags |= FLG_DELAYED;
+
+ while ((ch = getopt(argc, argv, "?ha:p:c:u:k:r:f:dbn")) != -1) {
+ switch (ch) {
+ case 'a':
+ ctx.pdp_apn = argv[optind - 1];
+ break;
+ case 'c':
+ ctx.pdp_ctx = strtol(argv[optind - 1], NULL, 10);
+ if (ctx.pdp_ctx < 1) {
+ warnx("Invalid context ID, defaulting to 1");
+ ctx.pdp_ctx = 1;
+ }
+ break;
+ case 'p':
+ ctx.pin = argv[optind - 1];
+ break;
+ case 'u':
+ ctx.pdp_user = argv[optind - 1];
+ break;
+ case 'k':
+ ctx.pdp_pwd = argv[optind - 1];
+ break;
+ case 'r':
+ ctx.resolv_path = argv[optind - 1];
+ break;
+ case 'd':
+ mode = MODE_DISC;
+ break;
+ case 'b':
+ ctx.flags &= ~FLG_DELAYED;
+ break;
+ case 'n':
+ ctx.flags |= FLG_NODAEMON;
+ break;
+ case 'f':
+ tty = argv[optind - 1];
+ break;
+ case 'h':
+ case '?':
+ default:
+ usage(argv[0]);
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ errx(1, "no interface given");
+
+ ifnam = argv[argc - 1];
+ ctx.ifnam = strdup(ifnam);
+
+ switch (mode) {
+ case MODE_DISC:
+ printf("Disconnecting %s\n", ifnam);
+ send_disconnect(ifnam);
+ exit(EXIT_SUCCESS);
+ default:
+ break;
+ }
+
+ signal(SIGHUP, sig_handle);
+ signal(SIGINT, sig_handle);
+ signal(SIGQUIT, sig_handle);
+ signal(SIGTERM, sig_handle);
+ signal(SIGALRM, sig_handle);
+
+ it.it_interval.tv_sec = 1;
+ it.it_interval.tv_usec = 0;
+ it.it_value.tv_sec = 1;
+ it.it_value.tv_usec = 0;
+ error = setitimer(ITIMER_REAL, &it, NULL);
+ if (error != 0)
+ errx(1, "setitimer");
+
+ tmr_add(&timers, 1, 5, &tmr_watchdog, &ctx);
+ watchdog_reset(&ctx, 15);
+
+ if (tty != NULL) {
+ error = do_connect(&ctx, tty);
+ if (error != 0)
+ errx(1, "Failed to open %s", tty);
+ }
+ else {
+ tty_list = get_tty(&ctx);
+#ifdef DEBUG
+ if (tty_list == NULL) {
+ fprintf(stderr, "get_tty returned empty list\n");
+ } else {
+ fprintf(stderr, "tty list:\n");
+ for (p = tty_list; *p != NULL; p++) {
+ fprintf(stderr, "\t %s\n", *p);
+ }
+ }
+#endif
+ for (p = tty_list; *p != NULL; p++) {
+ error = do_connect(&ctx, *p);
+ if (error == 0) {
+ tty = *p;
+ break;
+ }
+ }
+ if (*p == NULL)
+ errx(1, "Failed to obtain a control port, "
+ "try specifying one manually");
+ }
+
+ if (!(ctx.flags & FLG_DELAYED) && !(ctx.flags & FLG_NODAEMON))
+ daemonize(&ctx);
+
+
+ FD_ZERO(&set);
+ FD_SET(ctx.fd, &set);
+ for (;;) {
+
+ watchdog_disable(&ctx);
+ error = select(ctx.fd + 1, &set, NULL, NULL, NULL);
+ if (error <= 0) {
+ if (running && errno == EINTR)
+ continue;
+ if (ctx.flags & FLG_WDEXP) {
+ ctx.flags &= ~FLG_WDEXP;
+ watchdog_reset(&ctx, 5);
+ do_disconnect(&ctx);
+ watchdog_reset(&ctx, 15);
+ do_connect(&ctx, tty);
+ running = 1;
+ continue;
+ }
+
+ break;
+ }
+
+ if (FD_ISSET(ctx.fd, &set)) {
+ watchdog_reset(&ctx, 15);
+ error = at_async(&ctx, &ctx);
+ if (error != 0)
+ break;
+ }
+ FD_SET(ctx.fd, &set);
+
+ if (!(ctx.flags & FLG_DAEMON) && (ctx.flags & IPASSIGNED)) {
+ printf("Status: %s (%s)",
+ ctx.con_status ? "connected" : "disconnected",
+ network_access_type[ctx.con_net_type]);
+ if (ctx.dbm < 0)
+ printf(", signal: %d dBm", ctx.dbm);
+ printf("\r");
+ fflush(stdout);
+ }
+ }
+ if (!(ctx.flags & FLG_DAEMON) && (ctx.flags & IPASSIGNED))
+ printf("\n");
+
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGALRM, SIG_IGN);
+
+ do_disconnect(&ctx);
+
+ if (ctx.flags & FLG_DAEMON) {
+ pidfile_remove(ctx.pfh);
+ if (syslog_open)
+ closelog();
+ }
+
+ return (0);
+}
OpenPOWER on IntegriCloud