diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index 30f2ca5d64dc..95e1d4f9e57b 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 000000000000..0ec602a7bb8a --- /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 8e949b5ed418..cd38da08e572 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 e93c384dc801..71a8e537d850 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 000000000000..4a3edd3fd61b --- /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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" +#define USB_DEBUG_VAR uhso_debug +#include +#include +#include +#include +#include +#include + +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 54a1b456e881..2037a5cdb80f 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 8c771d7c3820..09ad669fe1ec 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 000000000000..b08dc42136cf --- /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 diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index f3e0a1b78048..6f7df81e79de 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 000000000000..97049237fffa --- /dev/null +++ b/usr.sbin/uhsoctl/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +PROG= uhsoctl +MAN= uhsoctl.1 +WARNS= 1 + +DPADD= ${LIBUTIL} +LDADD= -lutil + +.include diff --git a/usr.sbin/uhsoctl/uhsoctl.1 b/usr.sbin/uhsoctl/uhsoctl.1 new file mode 100644 index 000000000000..932cefa92610 --- /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 000000000000..6ed524544daa --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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", ®); + if (n != 1) { + n = sscanf(resp, "+CREG: %d", ®); + 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", ®); + if (n != 1) { + n = sscanf(resp, "+CGREG: %d", ®); + 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); +}