diff options
| author | plunky <plunky@NetBSD.org> | 2010-03-06 21:05:36 +0000 |
|---|---|---|
| committer | plunky <plunky@NetBSD.org> | 2010-03-06 21:05:36 +0000 |
| commit | 800cedea24f45ea52c859dc5cebaf5a66cdc225e (patch) | |
| tree | 3657d1db9af804eecabfecb8fbc684c4cfd1f761 | |
| parent | 7a23a88ab7e02837c7755d3569f1a61f9ae5bff2 (diff) | |
Add uhso(4) driver and manpage for Option N.V. Wireless WAN modems
| -rw-r--r-- | distrib/sets/lists/man/mi | 5 | ||||
| -rw-r--r-- | etc/MAKEDEV.tmpl | 14 | ||||
| -rw-r--r-- | share/man/man4/Makefile | 4 | ||||
| -rw-r--r-- | share/man/man4/uhso.4 | 140 | ||||
| -rw-r--r-- | sys/dev/DEVNAMES | 3 | ||||
| -rw-r--r-- | sys/dev/usb/files.usb | 7 | ||||
| -rw-r--r-- | sys/dev/usb/uhso.c | 2376 |
7 files changed, 2543 insertions, 6 deletions
diff --git a/distrib/sets/lists/man/mi b/distrib/sets/lists/man/mi index 8ba5237e184..70b6f318f27 100644 --- a/distrib/sets/lists/man/mi +++ b/distrib/sets/lists/man/mi @@ -1,4 +1,4 @@ -# $NetBSD: mi,v 1.1195 2010/02/27 16:13:28 jruoho Exp $ +# $NetBSD: mi,v 1.1196 2010/03/06 21:05:36 plunky Exp $ # # Note: don't delete entries from here - mark them as "obsolete" instead. # @@ -1580,6 +1580,7 @@ ./usr/share/man/cat4/uhid.0 man-sys-catman .cat ./usr/share/man/cat4/uhidev.0 man-sys-catman .cat ./usr/share/man/cat4/uhmodem.0 man-sys-catman .cat +./usr/share/man/cat4/uhso.0 man-sys-catman .cat ./usr/share/man/cat4/uhub.0 man-sys-catman .cat ./usr/share/man/cat4/uipaq.0 man-sys-catman .cat ./usr/share/man/cat4/uirda.0 man-sys-catman .cat @@ -4151,6 +4152,7 @@ ./usr/share/man/html4/uhid.html man-sys-htmlman html ./usr/share/man/html4/uhidev.html man-sys-htmlman html ./usr/share/man/html4/uhmodem.html man-sys-htmlman html +./usr/share/man/html4/uhso.html man-sys-htmlman html ./usr/share/man/html4/uhub.html man-sys-htmlman html ./usr/share/man/html4/uipaq.html man-sys-htmlman html ./usr/share/man/html4/uirda.html man-sys-htmlman html @@ -6652,6 +6654,7 @@ ./usr/share/man/man4/uhid.4 man-sys-man .man ./usr/share/man/man4/uhidev.4 man-sys-man .man ./usr/share/man/man4/uhmodem.4 man-sys-man .man +./usr/share/man/man4/uhso.4 man-sys-man .man ./usr/share/man/man4/uhub.4 man-sys-man .man ./usr/share/man/man4/uipaq.4 man-sys-man .man ./usr/share/man/man4/uirda.4 man-sys-man .man diff --git a/etc/MAKEDEV.tmpl b/etc/MAKEDEV.tmpl index fe91d717636..abd1cf262c4 100644 --- a/etc/MAKEDEV.tmpl +++ b/etc/MAKEDEV.tmpl @@ -1,5 +1,5 @@ #!/bin/sh - -# $NetBSD: MAKEDEV.tmpl,v 1.130 2009/09/25 06:06:04 mrg Exp $ +# $NetBSD: MAKEDEV.tmpl,v 1.131 2010/03/06 21:05:36 plunky Exp $ # # Copyright (c) 2003,2007,2008 The NetBSD Foundation, Inc. # All rights reserved. @@ -179,6 +179,7 @@ # ugen* USB generic devices # urio* USB Diamond Rio 500 devices # uscanner* USB scanners +# ttyHS* USB Option N.V. modems # ttyU* USB modems # ttyY* USB serial adapters # @@ -861,6 +862,17 @@ utoppy*) mkdev utoppy$unit c %utoppy_chr% $unit ;; +ttyHS*) + unit=${i#ttyHS} + for j in 00 01 02 03 04 05 06 07 08 09 10 + do + base=$(($unit * 16 + ${j#0})) + mkdev ttyHS$unit.$j c %uhso_chr% $(($base + $dialin )) "" "" $u_uucp + mkdev dtyHS$unit.$j c %uhso_chr% $(($base + $dialout )) "" "" $u_uucp + mkdev ctyHS$unit.$j c %uhso_chr% $(($base + $callunit)) "" "" $u_uucp + done + ;; + ttyY*) unit=${i#ttyY} mkdev ttyY$unit c %ucycom_chr% $(($unit + $dialin )) "" "" $u_uucp diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index ab256e45b4c..568fa9f71bc 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.509 2010/02/27 16:12:49 jruoho Exp $ +# $NetBSD: Makefile,v 1.510 2010/03/06 21:05:36 plunky Exp $ # @(#)Makefile 8.1 (Berkeley) 6/18/93 MAN= aac.4 ac97.4 acardide.4 aceride.4 acphy.4 acpidalb.4 \ @@ -68,7 +68,7 @@ MAN= aac.4 ac97.4 acardide.4 aceride.4 acphy.4 acpidalb.4 \ # USB devices MAN+= stuirda.4 u3g.4 uaudio.4 uberry.4 ubsa.4 ubt.4 uchcom.4 ucom.4 ucycom.4 \ udsbr.4 uftdi.4 ugen.4 ugensa.4 uhid.4 \ - uhidev.4 uhmodem.4 uipaq.4 uirda.4 ukbd.4 ukyopon.4 ulpt.4 \ + uhidev.4 uhmodem.4 uhso.4 uipaq.4 uirda.4 ukbd.4 ukyopon.4 ulpt.4 \ umass.4 umct.4 umidi.4 umodem.4 ums.4 uplcom.4 urio.4 usb.4 \ uscanner.4 uslsa.4 usscanner.4 ustir.4 uvisor.4 uvscom.4 uyap.4 \ aue.4 atu.4 axe.4 cdce.4 cue.4 kue.4 upl.4 url.4 udav.4 \ diff --git a/share/man/man4/uhso.4 b/share/man/man4/uhso.4 new file mode 100644 index 00000000000..c7dbb8903b9 --- /dev/null +++ b/share/man/man4/uhso.4 @@ -0,0 +1,140 @@ +.\" $NetBSD: uhso.4,v 1.1 2010/03/06 21:05:36 plunky Exp $ +.\" +.\" Copyright (c) 2009 Iain Hibbert +.\" 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. +.\" +.Dd March 6, 2010 +.Dt UHSO 4 +.Os +.Sh NAME +.Nm uhso +.Nd Option N.V. Wireless WAN modem driver +.Sh SYNOPSIS +.Cd "uhso* at uhub? port ?" +.Sh HARDWARE +The +.Nm +driver supports at least the following adapters: +.Lp +.Bl -tag -compact -offset indent +.It GlobeTrotter Max HSDPA +.It GlobeSurfer iCON 7.2 +.It GlobeTrotter iCON 225 +.It GlobeTrotter Express HSUPA +.It GlobeTrotter HSUPA +.It GlobeSurfer HSUPA +.It GlobeTrotter iCON EDGE +.It GlobeTrotter iCON 321 +.It GlobeTrotter iCON 322 +.It GlobeTrotter Express 40x +.It GlobeTrotter iCON 401 +.It GlobeTrotter Module 382 +.It GlobeTrotter iCON 505 +.El +.Sh DESCRIPTION +The +.Tn Option N.V. +modems appear at first as a +.Xr umass 4 +device containing the +.Tn Windows +and +.Tn MacOS +drivers and, upon receipt of a SCSI +.Qq REZERO UNIT +command, will detach from the USB bus and reattach as a +Wireless WAN modem. +Unless disabled by clearing the +.Xr sysctl 8 +variable +.Dv hw.uhso.autoswitch , +the driver will handle that automatically. +.Pp +The modems provide a number of IO channels spread over several USB +interfaces which are mapped by function to a standard port number +in each driver instance. +The defined channels are: +.Lp +.Bl -column -offset indent "Sy Channel Name" "Sy Port" +.It Sy "Channel Name" Ta Sy "Port" +.It Control Ta 0 +.It Diagnostic Ta 1 +.It Diagnostic 2 Ta 2 +.It Application Ta 3 +.It Application 2 Ta 4 +.It GPS Ta 5 +.It GPS Control Ta 6 +.It PC Smartcard Ta 7 +.It Modem Ta 8 +.It MSD Ta 9 +.It Voice Ta 10 +.It Network Ta 11 +.El +.Pp +Apart from the Network port, which is attached as a network +interface, the ports are attached as +.Xr tty 4 +devices using the port number as the minor device number. +.Pp +Before the Network port can be used, the modem needs to +be placed in connected mode and network settings subsequently +retrieved using the proprietary +.Qq _OWANCALL +and +.Qq _OWANDATA +AT commands on the Control port. +.Pp +Note that the Modem and Network ports should not be enabled at the +same time for USB performance reasons. +.Sh FILES +.Bl -tag -compact +.It Pa /dev/ttyHS?.?? +.It Pa /dev/dtyHS?.?? +.It Pa /dev/ctyHS?.?? +.El +.Sh SEE ALSO +.Xr intro 4 , +.Xr netintro 4 , +.Xr tty 4 , +.Xr uhub 4 , +.Xr usb 4 , +.Xr ifconfig 8 +.Sh HISTORY +This driver originated as the +.Nm hso +module for +.Fx +written by +.An Frederik Lindberg . +It was rewritten for +.Nx , +and to provide more complete device support with +information extracted from the +.Nm hso +driver for +.Tn Linux +provided by +.Tn Option N.V. +.Pp +The rewrite and this manual page by +.An Iain Hibbert . diff --git a/sys/dev/DEVNAMES b/sys/dev/DEVNAMES index af183d0d5b6..5f9bbb46584 100644 --- a/sys/dev/DEVNAMES +++ b/sys/dev/DEVNAMES @@ -1,4 +1,4 @@ -# $NetBSD: DEVNAMES,v 1.256 2010/03/06 05:58:56 cnst Exp $ +# $NetBSD: DEVNAMES,v 1.257 2010/03/06 21:05:36 plunky Exp $ # # This file contains all used device names and defined attributes in # alphabetical order. New devices added to the system somewhere should first @@ -1339,6 +1339,7 @@ uhid MI uhidbus MI Attribute uhidev MI uhmodem MI +uhso MI uhub MI uipaq MI uirda MI diff --git a/sys/dev/usb/files.usb b/sys/dev/usb/files.usb index f09ee669d72..e9cf24b60c1 100644 --- a/sys/dev/usb/files.usb +++ b/sys/dev/usb/files.usb @@ -1,4 +1,4 @@ -# $NetBSD: files.usb,v 1.95 2010/02/06 11:17:17 tonio Exp $ +# $NetBSD: files.usb,v 1.96 2010/03/06 21:05:37 plunky Exp $ # # Config file and device description for machine-independent USB code. # Included by ports that need it. Ports that use it must provide @@ -341,3 +341,8 @@ file dev/usb/if_zyd.c zyd device udl: wsemuldisplaydev, rasops16, edid, firmload attach udl at usbdevif file dev/usb/udl.c udl + +# Wireless WAN modems from Option N.V. +device uhso: ifnet +attach uhso at usbdevif +file dev/usb/uhso.c uhso diff --git a/sys/dev/usb/uhso.c b/sys/dev/usb/uhso.c new file mode 100644 index 00000000000..82b9a772fe4 --- /dev/null +++ b/sys/dev/usb/uhso.c @@ -0,0 +1,2376 @@ +/* $NetBSD: uhso.c,v 1.1 2010/03/06 21:05:37 plunky Exp $ */ + +/*- + * Copyright (c) 2009 Iain Hibbert + * Copyright (c) 2008 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. + */ + +/* + * This driver originated as the hso module for FreeBSD written by + * Fredrik Lindberg[1]. It has been rewritten almost completely for + * NetBSD, and to support more devices with information extracted from + * the Linux hso driver provided by Option N.V.[2] + * + * [1] http://www.shapeshifter.se/code/hso + * [2] http://www.pharscape.org/hso.htm + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: uhso.c,v 1.1 2010/03/06 21:05:37 plunky Exp $"); + +#include "opt_inet.h" + +#include <sys/param.h> +#include <sys/conf.h> +#include <sys/fcntl.h> +#include <sys/kauth.h> +#include <sys/kernel.h> +#include <sys/kmem.h> +#include <sys/mbuf.h> +#include <sys/poll.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/sysctl.h> +#include <sys/systm.h> +#include <sys/tty.h> +#include <sys/vnode.h> + +#include <net/bpf.h> +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <net/netisr.h> + +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbcdc.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/umassvar.h> + +#include <dev/scsipi/scsi_disk.h> + +#include "usbdevs.h" + +#undef DPRINTF +#ifdef UHSO_DEBUG +/* + * defined levels + * 0 warnings only + * 1 informational + * 5 really chatty + */ +int uhso_debug = 0; + +#define DPRINTF(n, fmt, args...) do { \ + if (uhso_debug >= (n)) \ + printf("%s: "fmt, __func__ , ##args); \ +} while (/* CONSTCOND */0) +#else +#define DPRINTF(...) ((void)0) +#endif + +/* + * When first attached, the device class will be 0 and the modem + * will attach as UMASS until a SCSI REZERO_UNIT command is sent, + * in which case it will detach and reattach with device class set + * to UDCLASS_VENDOR (0xff) and provide the serial interfaces. + * + * If autoswitch is set (the default) this will happen automatically. + */ +Static int uhso_autoswitch = 1; + +SYSCTL_SETUP(sysctl_hw_uhso_setup, "uhso sysctl setup") +{ + const struct sysctlnode *node = NULL; + + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT, + CTLTYPE_NODE, "hw", + NULL, + NULL, 0, + NULL, 0, + CTL_HW, CTL_EOL); + + sysctl_createv(clog, 0, NULL, &node, + CTLFLAG_PERMANENT, + CTLTYPE_NODE, "uhso", + NULL, + NULL, 0, + NULL, 0, + CTL_HW, CTL_CREATE, CTL_EOL); + + if (node == NULL) + return; + +#ifdef UHSO_DEBUG + sysctl_createv(clog, 0, &node, NULL, + CTLFLAG_PERMANENT | CTLFLAG_READWRITE, + CTLTYPE_INT, "debug", + SYSCTL_DESCR("uhso debug level (0, 1, 5)"), + NULL, 0, + &uhso_debug, sizeof(uhso_debug), + CTL_CREATE, CTL_EOL); +#endif + + sysctl_createv(clog, 0, &node, NULL, + CTLFLAG_PERMANENT | CTLFLAG_READWRITE, + CTLTYPE_INT, "autoswitch", + SYSCTL_DESCR("automatically switch device into modem mode"), + NULL, 0, + &uhso_autoswitch, sizeof(uhso_autoswitch), + CTL_CREATE, CTL_EOL); +} + +/* + * The uhso modems have a number of interfaces providing a variety of + * IO ports using the bulk endpoints, or multiplexed on the control + * endpoints. We separate the ports by function and provide each with + * a predictable index number used to construct the device minor number. + * + * The Network port is configured as a network interface rather than + * a tty as it provides raw IPv4 packets. + */ + +Static const char *uhso_port_name[] = { + "Control", + "Diagnostic", + "Diagnostic2", + "Application", + "Application2", + "GPS", + "GPS Control", + "PC Smartcard", + "Modem", + "MSD", /* "Modem Sharing Device" ? */ + "Voice", + "Network", +}; + +#define UHSO_PORT_CONTROL 0x00 +#define UHSO_PORT_DIAG 0x01 +#define UHSO_PORT_DIAG2 0x02 +#define UHSO_PORT_APP 0x03 +#define UHSO_PORT_APP2 0x04 +#define UHSO_PORT_GPS 0x05 +#define UHSO_PORT_GPS_CONTROL 0x06 +#define UHSO_PORT_PCSC 0x07 +#define UHSO_PORT_MODEM 0x08 +#define UHSO_PORT_MSD 0x09 +#define UHSO_PORT_VOICE 0x0a +#define UHSO_PORT_NETWORK 0x0b + +#define UHSO_PORT_MAX __arraycount(uhso_port_name) + +#define UHSO_IFACE_MUX 0x20 +#define UHSO_IFACE_BULK 0x40 +#define UHSO_IFACE_IFNET 0x80 + +/* + * The interface specification can sometimes be deduced from the device + * type and interface number, or some modems support a vendor specific + * way to read config info which we can translate to the port index. + */ +Static const uint8_t uhso_spec_default[] = { + UHSO_IFACE_IFNET | UHSO_PORT_NETWORK | UHSO_IFACE_MUX, + UHSO_IFACE_BULK | UHSO_PORT_DIAG, + UHSO_IFACE_BULK | UHSO_PORT_MODEM, +}; + +Static const uint8_t uhso_spec_icon321[] = { + UHSO_IFACE_IFNET | UHSO_PORT_NETWORK | UHSO_IFACE_MUX, + UHSO_IFACE_BULK | UHSO_PORT_DIAG2, + UHSO_IFACE_BULK | UHSO_PORT_MODEM, + UHSO_IFACE_BULK | UHSO_PORT_DIAG, +}; + +Static const uint8_t uhso_spec_config[] = { + 0, + UHSO_IFACE_BULK | UHSO_PORT_DIAG, + UHSO_IFACE_BULK | UHSO_PORT_GPS, + UHSO_IFACE_BULK | UHSO_PORT_GPS_CONTROL, + UHSO_IFACE_BULK | UHSO_PORT_APP, + UHSO_IFACE_BULK | UHSO_PORT_APP2, + UHSO_IFACE_BULK | UHSO_PORT_CONTROL, + UHSO_IFACE_IFNET | UHSO_PORT_NETWORK, + UHSO_IFACE_BULK | UHSO_PORT_MODEM, + UHSO_IFACE_BULK | UHSO_PORT_MSD, + UHSO_IFACE_BULK | UHSO_PORT_PCSC, + UHSO_IFACE_BULK | UHSO_PORT_VOICE, +}; + +struct uhso_dev { + uint16_t vendor; + uint16_t product; + uint16_t type; +}; + +#define UHSOTYPE_DEFAULT 1 +#define UHSOTYPE_ICON321 2 +#define UHSOTYPE_CONFIG 3 + +Static const struct uhso_dev uhso_devs[] = { + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_MAXHSDPA, UHSOTYPE_DEFAULT }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_GSICON72, UHSOTYPE_DEFAULT }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_ICON225, UHSOTYPE_DEFAULT }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_GEHSUPA, UHSOTYPE_DEFAULT }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_GTHSUPA, UHSOTYPE_DEFAULT }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_GSHSUPA, UHSOTYPE_DEFAULT }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_GE40X1, UHSOTYPE_CONFIG }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_GE40X2, UHSOTYPE_CONFIG }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_GE40X3, UHSOTYPE_CONFIG }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_ICON401, UHSOTYPE_CONFIG }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_GTM382, UHSOTYPE_CONFIG }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_GE40X4, UHSOTYPE_CONFIG }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_ICONEDGE, UHSOTYPE_DEFAULT }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_MODHSXPA, UHSOTYPE_ICON321 }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_ICON321, UHSOTYPE_ICON321 }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_ICON322, UHSOTYPE_ICON321 }, + { USB_VENDOR_OPTIONNV, USB_PRODUCT_OPTIONNV_ICON505, UHSOTYPE_CONFIG }, +}; + +#define uhso_lookup(p, v) ((const struct uhso_dev *)usb_lookup(uhso_devs, (p), (v))) + +/* IO buffer sizes */ +#define UHSO_MUX_WSIZE 64 +#define UHSO_MUX_RSIZE 1024 +#define UHSO_BULK_WSIZE 8192 +#define UHSO_BULK_RSIZE 4096 +#define UHSO_IFNET_MTU 1500 + +/* + * Each IO port provided by the modem can be mapped to a network + * interface (when hp_ifp != NULL) or a tty (when hp_tp != NULL) + * which may be multiplexed and sharing interrupt and control endpoints + * from an interface, or using the dedicated bulk endpoints. + */ + +struct uhso_port; +struct uhso_softc; + +/* uhso callback functions return errno on failure */ +typedef int (*uhso_callback)(struct uhso_port *); + +struct uhso_port { + struct uhso_softc *hp_sc; /* master softc */ + struct tty *hp_tp; /* tty pointer */ + struct ifnet *hp_ifp; /* ifnet pointer */ + unsigned int hp_flags; /* see below */ + int hp_swflags; /* persistent tty flags */ + int hp_status; /* modem status */ + + /* port type specific handlers */ + uhso_callback hp_abort; /* abort any transfers */ + uhso_callback hp_detach; /* detach port completely */ + uhso_callback hp_init; /* init port (first open) */ + uhso_callback hp_clean; /* clean port (last close) */ + uhso_callback hp_write; /* write data */ + usbd_callback hp_write_cb; /* write callback */ + uhso_callback hp_read; /* read data */ + usbd_callback hp_read_cb; /* read callback */ + uhso_callback hp_control; /* set control lines */ + + usbd_interface_handle hp_ifh; /* interface handle */ + unsigned int hp_index; /* usb request index */ + + int hp_iaddr; /* interrupt endpoint */ + usbd_pipe_handle hp_ipipe; /* interrupt pipe */ + void *hp_ibuf; /* interrupt buffer */ + size_t hp_isize; /* allocated size */ + + int hp_raddr; /* bulk in endpoint */ + usbd_pipe_handle hp_rpipe; /* bulk in pipe */ + usbd_xfer_handle hp_rxfer; /* input xfer */ + void *hp_rbuf; /* input buffer */ + size_t hp_rlen; /* fill length */ + size_t hp_rsize; /* allocated size */ + + int hp_waddr; /* bulk out endpoint */ + usbd_pipe_handle hp_wpipe; /* bulk out pipe */ + usbd_xfer_handle hp_wxfer; /* output xfer */ + void *hp_wbuf; /* output buffer */ + size_t hp_wlen; /* fill length */ + size_t hp_wsize; /* allocated size */ + + struct mbuf *hp_mbuf; /* partial packet */ +}; + +/* hp_flags */ +#define UHSO_PORT_MUXPIPE __BIT(0) /* duplicate ipipe/ibuf references */ +#define UHSO_PORT_MUXREADY __BIT(1) /* input is ready */ +#define UHSO_PORT_MUXBUSY __BIT(2) /* read in progress */ + +struct uhso_softc { + device_t sc_dev; /* self */ + usbd_device_handle sc_udev; + int sc_refcnt; + struct uhso_port *sc_port[UHSO_PORT_MAX]; +}; + +#define UHSO_CONFIG_NO 1 + +int uhso_match(device_t, cfdata_t, void *); +void uhso_attach(device_t, device_t, void *); +int uhso_detach(device_t, int); + +extern struct cfdriver uhso_cd; + +CFATTACH_DECL_NEW(uhso, sizeof(struct uhso_softc), uhso_match, uhso_attach, + uhso_detach, NULL); + +Static int uhso_switch_mode(usbd_device_handle); +Static int uhso_get_iface_spec(struct usb_attach_arg *, uint8_t, uint8_t *); +Static usb_endpoint_descriptor_t *uhso_get_endpoint(usbd_interface_handle, int, int); + +Static void uhso_mux_attach(struct uhso_softc *, usbd_interface_handle, int); +Static int uhso_mux_abort(struct uhso_port *); +Static int uhso_mux_detach(struct uhso_port *); +Static int uhso_mux_init(struct uhso_port *); +Static int uhso_mux_clean(struct uhso_port *); +Static int uhso_mux_write(struct uhso_port *); +Static int uhso_mux_read(struct uhso_port *); +Static int uhso_mux_control(struct uhso_port *); +Static void uhso_mux_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); + +Static void uhso_bulk_attach(struct uhso_softc *, usbd_interface_handle, int); +Static int uhso_bulk_abort(struct uhso_port *); +Static int uhso_bulk_detach(struct uhso_port *); +Static int uhso_bulk_init(struct uhso_port *); +Static int uhso_bulk_clean(struct uhso_port *); +Static int uhso_bulk_write(struct uhso_port *); +Static int uhso_bulk_read(struct uhso_port *); +Static int uhso_bulk_control(struct uhso_port *); +Static void uhso_bulk_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); + +Static void uhso_tty_attach(struct uhso_port *); +Static void uhso_tty_detach(struct uhso_port *); +Static void uhso_tty_read_cb(usbd_xfer_handle, usbd_private_handle, usbd_status); +Static void uhso_tty_write_cb(usbd_xfer_handle, usbd_private_handle, usbd_status); + +dev_type_open(uhso_tty_open); +dev_type_close(uhso_tty_close); +dev_type_read(uhso_tty_read); +dev_type_write(uhso_tty_write); +dev_type_ioctl(uhso_tty_ioctl); +dev_type_stop(uhso_tty_stop); +dev_type_tty(uhso_tty_tty); +dev_type_poll(uhso_tty_poll); + +const struct cdevsw uhso_cdevsw = { + .d_open = uhso_tty_open, + .d_close = uhso_tty_close, + .d_read = uhso_tty_read, + .d_write = uhso_tty_write, + .d_ioctl = uhso_tty_ioctl, + .d_stop = uhso_tty_stop, + .d_tty = uhso_tty_tty, + .d_poll = uhso_tty_poll, + .d_mmap = nommap, + .d_kqfilter = ttykqfilter, + .d_flag = D_TTY, +}; + +Static int uhso_tty_init(struct uhso_port *); +Static void uhso_tty_clean(struct uhso_port *); +Static int uhso_tty_do_ioctl(struct uhso_port *, u_long, void *, int, struct lwp *); +Static void uhso_tty_start(struct tty *); +Static int uhso_tty_param(struct tty *, struct termios *); +Static int uhso_tty_control(struct uhso_port *, u_long, int); + +#define UHSO_UNIT_MASK 0x0fff0 +#define UHSO_PORT_MASK 0x0000f +#define UHSO_DIALOUT_MASK 0x80000 +#define UHSO_CALLUNIT_MASK 0x40000 + +#define UHSOUNIT(x) ((minor(x) & UHSO_UNIT_MASK) >> 4) +#define UHSOPORT(x) (minor(x) & UHSO_PORT_MASK) +#define UHSODIALOUT(x) (minor(x) & UHSO_DIALOUT_MASK) +#define UHSOMINOR(u, p) ((((u) << 4) & UHSO_UNIT_MASK) | ((p) & UHSO_UNIT_MASK)) + +Static void uhso_ifnet_attach(struct uhso_softc *, usbd_interface_handle, int); +Static int uhso_ifnet_abort(struct uhso_port *); +Static int uhso_ifnet_detach(struct uhso_port *); +Static void uhso_ifnet_read_cb(usbd_xfer_handle, usbd_private_handle, usbd_status); +Static void uhso_ifnet_input(struct ifnet *, struct mbuf **, uint8_t *, size_t); +Static void uhso_ifnet_write_cb(usbd_xfer_handle, usbd_private_handle, usbd_status); + +Static int uhso_ifnet_ioctl(struct ifnet *, u_long, void *); +Static int uhso_ifnet_init(struct uhso_port *); +Static void uhso_ifnet_clean(struct uhso_port *); +Static void uhso_ifnet_start(struct ifnet *); +Static int uhso_ifnet_output(struct ifnet *, struct mbuf *, const struct sockaddr *, struct rtentry *); + + +/******************************************************************************* + * + * USB autoconfig + * + */ + +int +uhso_match(device_t parent, cfdata_t match, void *aux) +{ + struct usb_attach_arg *uaa = aux; + + /* + * don't claim this device if autoswitch is disabled + * and it is not in modem mode already + */ + if (!uhso_autoswitch && uaa->class != UDCLASS_VENDOR) + return UMATCH_NONE; + + if (uhso_lookup(uaa->vendor, uaa->product)) + return UMATCH_VENDOR_PRODUCT; + + return UMATCH_NONE; +} + +void +uhso_attach(device_t parent, device_t self, void *aux) +{ + struct uhso_softc *sc = device_private(self); + struct usb_attach_arg *uaa = aux; + usbd_interface_handle ifh; + char *devinfop; + uint8_t count, i, spec; + usbd_status status; + + DPRINTF(1, ": sc = %p, self=%p", sc, self); + + sc->sc_dev = self; + sc->sc_udev = uaa->device; + + aprint_naive("\n"); + aprint_normal("\n"); + + devinfop = usbd_devinfo_alloc(uaa->device, 0); + aprint_normal_dev(self, "%s\n", devinfop); + usbd_devinfo_free(devinfop); + + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev); + + status = usbd_set_config_no(sc->sc_udev, UHSO_CONFIG_NO, 1); + if (status != USBD_NORMAL_COMPLETION) { + aprint_error_dev(self, "could not set config no %d: %s\n", + UHSO_CONFIG_NO, usbd_errstr(status)); + + return; + } + + if (uaa->class != UDCLASS_VENDOR) { + aprint_verbose_dev(self, "Switching device into modem mode..\n"); + if (uhso_switch_mode(uaa->device) != 0) + aprint_error_dev(self, "modem switch failed\n"); + + return; + } + + count = 0; + (void)usbd_interface_count(sc->sc_udev, &count); + DPRINTF(1, "interface count %d\n", count); + + for (i = 0; i < count; i++) { + status = usbd_device2interface_handle(sc->sc_udev, i, &ifh); + if (status != USBD_NORMAL_COMPLETION) { + aprint_error_dev(self, + "could not get interface %d: %s\n", + i, usbd_errstr(status)); + + return; + } + + if (!uhso_get_iface_spec(uaa, i, &spec)) { + aprint_error_dev(self, + "could not get interface %d specification\n", i); + + return; + } + + if (ISSET(spec, UHSO_IFACE_MUX)) + uhso_mux_attach(sc, ifh, UHSOPORT(spec)); + + if (ISSET(spec, UHSO_IFACE_BULK)) + uhso_bulk_attach(sc, ifh, UHSOPORT(spec)); + + if (ISSET(spec, UHSO_IFACE_IFNET)) + uhso_ifnet_attach(sc, ifh, UHSOPORT(spec)); + } + + if (!pmf_device_register(self, NULL, NULL)) + aprint_error_dev(self, "couldn't establish power handler\n"); +} + +int +uhso_detach(device_t self, int flags) +{ + struct uhso_softc *sc = device_private(self); + struct uhso_port *hp; + devmajor_t major; + devminor_t minor; + unsigned int i; + int s; + + if (device_pmf_is_registered(self)) + pmf_device_deregister(self); + + for (i = 0; i < UHSO_PORT_MAX; i++) { + hp = sc->sc_port[i]; + if (hp != NULL) + (*hp->hp_abort)(hp); + } + + s = splusb(); + if (sc->sc_refcnt-- > 0) { + DPRINTF(1, "waiting for refcnt (%d)..\n", sc->sc_refcnt); + usb_detach_wait(sc->sc_dev); + } + splx(s); + + /* + * XXX the tty close routine increases/decreases refcnt causing + * XXX another usb_detach_wakeup() does it matter, should these + * XXX be before the detach_wait? or before the abort? + */ + + /* Nuke the vnodes for any open instances (calls close). */ + major = cdevsw_lookup_major(&uhso_cdevsw); + minor = UHSOMINOR(device_unit(sc->sc_dev), 0); + vdevgone(major, minor, minor + UHSO_PORT_MAX, VCHR); + minor = UHSOMINOR(device_unit(sc->sc_dev), 0) | UHSO_DIALOUT_MASK; + vdevgone(major, minor, minor + UHSO_PORT_MAX, VCHR); + minor = UHSOMINOR(device_unit(sc->sc_dev), 0) | UHSO_CALLUNIT_MASK; + vdevgone(major, minor, minor + UHSO_PORT_MAX, VCHR); + + for (i = 0; i < UHSO_PORT_MAX; i++) { + hp = sc->sc_port[i]; + if (hp != NULL) + (*hp->hp_detach)(hp); + } + + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev); + + return 0; +} + +/* + * Send SCSI REZERO_UNIT command to switch device into modem mode + */ +Static int +uhso_switch_mode(usbd_device_handle udev) +{ + umass_bbb_cbw_t cmd; + usb_endpoint_descriptor_t *ed; + usbd_interface_handle ifh; + usbd_pipe_handle pipe; + usbd_xfer_handle xfer; + usbd_status status; + + status = usbd_device2interface_handle(udev, 0, &ifh); + if (status != USBD_NORMAL_COMPLETION) + return EIO; + + ed = uhso_get_endpoint(ifh, UE_BULK, UE_DIR_OUT); + if (ed == NULL) + return ENODEV; + + status = usbd_open_pipe(ifh, ed->bEndpointAddress, 0, &pipe); + if (status != USBD_NORMAL_COMPLETION) + return EIO; + + xfer = usbd_alloc_xfer(udev); + if (xfer == NULL) + return ENOMEM; + + USETDW(cmd.dCBWSignature, CBWSIGNATURE); + USETDW(cmd.dCBWTag, 1); + USETDW(cmd.dCBWDataTransferLength, 0); + cmd.bCBWFlags = CBWFLAGS_OUT; + cmd.bCBWLUN = 0; + cmd.bCDBLength = 6; + + memset(&cmd.CBWCDB, 0, CBWCDBLENGTH); + cmd.CBWCDB[0] = SCSI_REZERO_UNIT; + + usbd_setup_xfer(xfer, pipe, NULL, &cmd, sizeof(cmd), + USBD_SYNCHRONOUS, USBD_DEFAULT_TIMEOUT, NULL); + + status = usbd_transfer(xfer); + + usbd_abort_pipe(pipe); + usbd_close_pipe(pipe); + usbd_free_xfer(xfer); + + return (status == USBD_NORMAL_COMPLETION ? 0 : EIO); +} + +Static int +uhso_get_iface_spec(struct usb_attach_arg *uaa, uint8_t ifnum, uint8_t *spec) +{ + const struct uhso_dev *hd; + uint8_t config[17]; + usb_device_request_t req; + usbd_status status; + + hd = uhso_lookup(uaa->vendor, uaa->product); + KASSERT(hd != NULL); + + switch (hd->type) { + case UHSOTYPE_DEFAULT: + if (ifnum > __arraycount(uhso_spec_default)) + break; + + *spec = uhso_spec_default[ifnum]; + return 1; + + case UHSOTYPE_ICON321: + if (ifnum > __arraycount(uhso_spec_icon321)) + break; + + *spec = uhso_spec_icon321[ifnum]; + return 1; + + case UHSOTYPE_CONFIG: + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = 0x86; /* "Config Info" */ + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(config)); + + status = usbd_do_request(uaa->device, &req, config); + if (status != USBD_NORMAL_COMPLETION) + break; + + if (ifnum > __arraycount(config) + || config[ifnum] > __arraycount(uhso_spec_config)) + break; + + *spec = uhso_spec_config[config[ifnum]]; + + /* + * Apparently some modems also have a CRC bug that is + * indicated by ISSET(config[16], __BIT(0)) but we dont + * handle it at this time. + */ + return 1; + + default: + DPRINTF(0, "unknown interface type\n"); + break; + } + + return 0; +} + +Static usb_endpoint_descriptor_t * +uhso_get_endpoint(usbd_interface_handle ifh, int type, int dir) +{ + usb_endpoint_descriptor_t *ed; + uint8_t count, i; + + count = 0; + (void)usbd_endpoint_count(ifh, &count); + + for (i = 0; i < count; i++) { + ed = usbd_interface2endpoint_descriptor(ifh, i); + if (ed != NULL + && UE_GET_XFERTYPE(ed->bmAttributes) == type + && UE_GET_DIR(ed->bEndpointAddress) == dir) + return ed; + } + + return NULL; +} + + +/****************************************************************************** + * + * Multiplexed ports signal with the interrupt endpoint to indicate + * when data is available for reading, and a separate request is made on + * the control endpoint to read or write on each port. The offsets in the + * table below relate to bit numbers in the mux mask, identifying each port. + */ + +Static const int uhso_mux_port[] = { + UHSO_PORT_CONTROL, + UHSO_PORT_APP, + UHSO_PORT_PCSC, + UHSO_PORT_GPS, + UHSO_PORT_APP2, +}; + +Static void +uhso_mux_attach(struct uhso_softc *sc, usbd_interface_handle ifh, int index) +{ + usbd_desc_iter_t iter; + const usb_descriptor_t *desc; + usb_endpoint_descriptor_t *ed; + usbd_pipe_handle pipe; + struct uhso_port *hp; + uint8_t *buf; + size_t size; + unsigned int i, mux, flags; + int addr; + usbd_status status; + + ed = uhso_get_endpoint(ifh, UE_INTERRUPT, UE_DIR_IN); + if (ed == NULL) { + aprint_error_dev(sc->sc_dev, "no interrupt endpoint\n"); + return; + } + addr = ed->bEndpointAddress; + size = UGETW(ed->wMaxPacketSize); + + /* + * There should be an additional "Class Specific" descriptor on + * the mux interface containing a single byte with a bitmask of + * enabled ports. We need to look through the device descriptor + * to find it and the port index is found from the uhso_mux_port + * array, above. + */ + usb_desc_iter_init(sc->sc_udev, &iter); + + /* skip past the current interface descriptor */ + iter.cur = (const uByte *)usbd_get_interface_descriptor(ifh); + desc = usb_desc_iter_next(&iter); + + for (;;) { + desc = usb_desc_iter_next(&iter); + if (desc == NULL + || desc->bDescriptorType == UDESC_INTERFACE) { + mux = 0; + break; /* not found */ + } + + if (desc->bDescriptorType == UDESC_CS_INTERFACE + && desc->bLength == 3) { + mux = ((const uint8_t *)desc)[2]; + break; + } + } + + DPRINTF(1, "addr=%d, size=%zd, mux=0x%02x\n", addr, size, mux); + + buf = kmem_alloc(size, KM_SLEEP); + status = usbd_open_pipe_intr(ifh, addr, USBD_SHORT_XFER_OK, &pipe, + sc, buf, size, uhso_mux_intr, USBD_DEFAULT_INTERVAL); + + if (status != USBD_NORMAL_COMPLETION) { + aprint_error_dev(sc->sc_dev, "failed to open interrupt pipe: %s", + usbd_errstr(status)); + + kmem_free(buf, size); + return; + } + + flags = 0; + for (i = 0; i < __arraycount(uhso_mux_port); i++) { + if (ISSET(mux, __BIT(i))) { + if (sc->sc_port[uhso_mux_port[i]] != NULL) { + aprint_error_dev(sc->sc_dev, + "mux port %d is duplicate!\n", i); + + continue; + } + + hp = kmem_zalloc(sizeof(struct uhso_port), KM_SLEEP); + sc->sc_port[uhso_mux_port[i]] = hp; + + hp->hp_sc = sc; + hp->hp_index = i; + hp->hp_ipipe = pipe; + hp->hp_ibuf = buf; + hp->hp_isize = size; + hp->hp_flags = flags; + hp->hp_abort = uhso_mux_abort; + hp->hp_detach = uhso_mux_detach; + hp->hp_init = uhso_mux_init; + hp->hp_clean = uhso_mux_clean; + hp->hp_write = uhso_mux_write; + hp->hp_write_cb = uhso_tty_write_cb; + hp->hp_read = uhso_mux_read; + hp->hp_read_cb = uhso_tty_read_cb; + hp->hp_control = uhso_mux_control; + hp->hp_wsize = UHSO_MUX_WSIZE; + hp->hp_rsize = UHSO_MUX_RSIZE; + + uhso_tty_attach(hp); + + aprint_normal_dev(sc->sc_dev, + "%s (port %d) attached as mux tty\n", + uhso_port_name[uhso_mux_port[i]], uhso_mux_port[i]); + + /* + * As the pipe handle is stored in each mux, mark + * secondary references so they don't get released + */ + flags = UHSO_PORT_MUXPIPE; + } + } + + if (flags == 0) { + /* for whatever reasons, nothing was attached */ + usbd_abort_pipe(pipe); + usbd_close_pipe(pipe); + kmem_free(buf, size); + } +} + +Static int +uhso_mux_abort(struct uhso_port *hp) +{ + struct uhso_softc *sc = hp->hp_sc; + + DPRINTF(1, "hp=%p\n", hp); + + if (!ISSET(hp->hp_flags, UHSO_PORT_MUXPIPE)) + usbd_abort_pipe(hp->hp_ipipe); + + usbd_abort_default_pipe(sc->sc_udev); + + return (*hp->hp_clean)(hp); +} + +Static int +uhso_mux_detach(struct uhso_port *hp) +{ + + DPRINTF(1, "hp=%p\n", hp); + + if (!ISSET(hp->hp_flags, UHSO_PORT_MUXPIPE)) { + DPRINTF(1, "interrupt pipe closed\n"); + usbd_abort_pipe(hp->hp_ipipe); + usbd_close_pipe(hp->hp_ipipe); + kmem_free(hp->hp_ibuf, hp->hp_isize); + } + + uhso_tty_detach(hp); + kmem_free(hp, sizeof(struct uhso_port)); + return 0; +} + +Static int +uhso_mux_init(struct uhso_port *hp) +{ + + DPRINTF(1, "hp=%p\n", hp); + + CLR(hp->hp_flags, UHSO_PORT_MUXBUSY | UHSO_PORT_MUXREADY); + SET(hp->hp_status, TIOCM_DSR | TIOCM_CAR); + return 0; +} + +Static int +uhso_mux_clean(struct uhso_port *hp) +{ + + DPRINTF(1, "hp=%p\n", hp); + + CLR(hp->hp_flags, UHSO_PORT_MUXREADY); + CLR(hp->hp_status, TIOCM_DTR | TIOCM_DSR | TIOCM_CAR); + return 0; +} + +Static int +uhso_mux_write(struct uhso_port *hp) +{ + struct uhso_softc *sc = hp->hp_sc; + usb_device_request_t req; + usbd_status status; + + DPRINTF(5, "hp=%p, index=%d, wlen=%zd\n", hp, hp->hp_index, hp->hp_wlen); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND; + USETW(req.wValue, 0); + USETW(req.wIndex, hp->hp_index); + USETW(req.wLength, hp->hp_wlen); + + usbd_setup_default_xfer(hp->hp_wxfer, sc->sc_udev, hp, USBD_NO_TIMEOUT, + &req, hp->hp_wbuf, hp->hp_wlen, USBD_NO_COPY, hp->hp_write_cb); + + status = usbd_transfer(hp->hp_wxfer); + if (status != USBD_IN_PROGRESS) { + DPRINTF(0, "non-normal status %s\n", usbd_errstr(status)); + return EIO; + } + + sc->sc_refcnt++; + return 0; +} + +Static int +uhso_mux_read(struct uhso_port *hp) +{ + struct uhso_softc *sc = hp->hp_sc; + usb_device_request_t req; + usbd_status status; + + CLR(hp->hp_flags, UHSO_PORT_MUXBUSY); + + if (hp->hp_rlen == 0 && !ISSET(hp->hp_flags, UHSO_PORT_MUXREADY)) + return 0; + + SET(hp->hp_flags, UHSO_PORT_MUXBUSY); + CLR(hp->hp_flags, UHSO_PORT_MUXREADY); + + DPRINTF(5, "hp=%p, index=%d\n", hp, hp->hp_index); + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE; + USETW(req.wValue, 0); + USETW(req.wIndex, hp->hp_index); + USETW(req.wLength, hp->hp_rsize); + + usbd_setup_default_xfer(hp->hp_rxfer, sc->sc_udev, hp, USBD_NO_TIMEOUT, + &req, hp->hp_rbuf, hp->hp_rsize, USBD_NO_COPY | USBD_SHORT_XFER_OK, + hp->hp_read_cb); + + status = usbd_transfer(hp->hp_rxfer); + if (status != USBD_IN_PROGRESS) { + DPRINTF(0, "non-normal status %s\n", usbd_errstr(status)); + CLR(hp->hp_flags, UHSO_PORT_MUXBUSY); + return EIO; + } + + sc->sc_refcnt++; + return 0; +} + +Static int +uhso_mux_control(struct uhso_port *hp) +{ + + DPRINTF(1, "hp=%p\n", hp); + + return 0; +} + +Static void +uhso_mux_intr(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status) +{ + struct uhso_softc *sc = p; + struct uhso_port *hp; + uint32_t cc; + uint8_t *buf; + unsigned int i; + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(0, "non-normal status %s\n", usbd_errstr(status)); + return; + } + + usbd_get_xfer_status(xfer, NULL, (void **)&buf, &cc, NULL); + if (cc == 0) + return; + + DPRINTF(5, "mux mask 0x%02x, cc=%u\n", buf[0], cc); + + for (i = 0; i < __arraycount(uhso_mux_port); i++) { + if (!ISSET(buf[0], __BIT(i))) + continue; + + DPRINTF(5, "mux %d port %d\n", i, uhso_mux_port[i]); + hp = sc->sc_port[uhso_mux_port[i]]; + if (hp == NULL + || hp->hp_tp == NULL + || !ISSET(hp->hp_status, TIOCM_DTR)) + continue; + + SET(hp->hp_flags, UHSO_PORT_MUXREADY); + if (ISSET(hp->hp_flags, UHSO_PORT_MUXBUSY)) + continue; + + uhso_mux_read(hp); + } +} + + +/****************************************************************************** + * + * Bulk ports operate using the bulk endpoints on an interface, though + * the Modem port (at least) may have an interrupt endpoint that will pass + * CDC Notification messages with the modem status. + */ + +Static void +uhso_bulk_attach(struct uhso_softc *sc, usbd_interface_handle ifh, int index) +{ + usb_endpoint_descriptor_t *ed; + usb_interface_descriptor_t *id; + struct uhso_port *hp; + int in, out; + + ed = uhso_get_endpoint(ifh, UE_BULK, UE_DIR_IN); + if (ed == NULL) { + aprint_error_dev(sc->sc_dev, "bulk-in endpoint not found\n"); + return; + } + in = ed->bEndpointAddress; + + ed = uhso_get_endpoint(ifh, UE_BULK, UE_DIR_OUT); + if (ed == NULL) { + aprint_error_dev(sc->sc_dev, "bulk-out endpoint not found\n"); + return; + } + out = ed->bEndpointAddress; + + id = usbd_get_interface_descriptor(ifh); + if (id == NULL) { + aprint_error_dev(sc->sc_dev, "interface descriptor not found\n"); + return; + } + + DPRINTF(1, "bulk endpoints in=%x, out=%x\n", in, out); + + if (sc->sc_port[index] != NULL) { + aprint_error_dev(sc->sc_dev, "bulk port %d is duplicate!\n", + index); + + return; + } + + hp = kmem_zalloc(sizeof(struct uhso_port), KM_SLEEP); + sc->sc_port[index] = hp; + + hp->hp_sc = sc; + hp->hp_ifh = ifh; + hp->hp_index = id->bInterfaceNumber; + hp->hp_raddr = in; + hp->hp_waddr = out; + hp->hp_abort = uhso_bulk_abort; + hp->hp_detach = uhso_bulk_detach; + hp->hp_init = uhso_bulk_init; + hp->hp_clean = uhso_bulk_clean; + hp->hp_write = uhso_bulk_write; + hp->hp_write_cb = uhso_tty_write_cb; + hp->hp_read = uhso_bulk_read; + hp->hp_read_cb = uhso_tty_read_cb; + hp->hp_control = uhso_bulk_control; + hp->hp_wsize = UHSO_BULK_WSIZE; + hp->hp_rsize = UHSO_BULK_RSIZE; + + if (index == UHSO_PORT_MODEM) { + ed = uhso_get_endpoint(ifh, UE_INTERRUPT, UE_DIR_IN); + if (ed != NULL) { + hp->hp_iaddr = ed->bEndpointAddress; + hp->hp_isize = UGETW(ed->wMaxPacketSize); + } + } + + uhso_tty_attach(hp); + + aprint_normal_dev(sc->sc_dev, + "%s (port %d) attached as bulk tty\n", + uhso_port_name[index], index); +} + +Static int +uhso_bulk_abort(struct uhso_port *hp) +{ + + DPRINTF(1, "hp=%p\n", hp); + + return (*hp->hp_clean)(hp); +} + +Static int +uhso_bulk_detach(struct uhso_port *hp) +{ + + DPRINTF(1, "hp=%p\n", hp); + + uhso_tty_detach(hp); + kmem_free(hp, sizeof(struct uhso_port)); + return 0; +} + +Static int +uhso_bulk_init(struct uhso_port *hp) +{ + usbd_status status; + + DPRINTF(1, "hp=%p\n", hp); + + if (hp->hp_isize > 0) { + hp->hp_ibuf = kmem_alloc(hp->hp_isize, KM_SLEEP); + + status = usbd_open_pipe_intr(hp->hp_ifh, hp->hp_iaddr, + USBD_SHORT_XFER_OK, &hp->hp_ipipe, hp, hp->hp_ibuf, + hp->hp_isize, uhso_bulk_intr, USBD_DEFAULT_INTERVAL); + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(0, "interrupt pipe open failed: %s\n", + usbd_errstr(status)); + + return EIO; + } + } + + status = usbd_open_pipe(hp->hp_ifh, hp->hp_raddr, 0, &hp->hp_rpipe); + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(0, "read pipe open failed: %s\n", usbd_errstr(status)); + return EIO; + } + + status = usbd_open_pipe(hp->hp_ifh, hp->hp_waddr, 0, &hp->hp_wpipe); + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(0, "write pipe open failed: %s\n", usbd_errstr(status)); + return EIO; + } + + return 0; +} + +Static int +uhso_bulk_clean(struct uhso_port *hp) +{ + + DPRINTF(1, "hp=%p\n", hp); + + if (hp->hp_ipipe != NULL) { + usbd_abort_pipe(hp->hp_ipipe); + usbd_close_pipe(hp->hp_ipipe); + hp->hp_ipipe = NULL; + } + + if (hp->hp_ibuf != NULL) { + kmem_free(hp->hp_ibuf, hp->hp_isize); + hp->hp_ibuf = NULL; + } + + if (hp->hp_rpipe != NULL) { + usbd_abort_pipe(hp->hp_rpipe); + usbd_close_pipe(hp->hp_rpipe); + hp->hp_rpipe = NULL; + } + + if (hp->hp_wpipe != NULL) { + usbd_abort_pipe(hp->hp_wpipe); + usbd_close_pipe(hp->hp_wpipe); + hp->hp_wpipe = NULL; + } + + return 0; +} + +Static int +uhso_bulk_write(struct uhso_port *hp) +{ + struct uhso_softc *sc = hp->hp_sc; + usbd_status status; + + DPRINTF(5, "hp=%p, wlen=%zd\n", hp, hp->hp_wlen); + + usbd_setup_xfer(hp->hp_wxfer, hp->hp_wpipe, hp, hp->hp_wbuf, + hp->hp_wlen, USBD_NO_COPY, USBD_NO_TIMEOUT, hp->hp_write_cb); + + status = usbd_transfer(hp->hp_wxfer); + if (status != USBD_IN_PROGRESS) { + DPRINTF(0, "non-normal status %s\n", usbd_errstr(status)); + return EIO; + } + + sc->sc_refcnt++; + return 0; +} + +Static int +uhso_bulk_read(struct uhso_port *hp) +{ + struct uhso_softc *sc = hp->hp_sc; + usbd_status status; + + DPRINTF(5, "hp=%p\n", hp); + + usbd_setup_xfer(hp->hp_rxfer, hp->hp_rpipe, hp, hp->hp_rbuf, + hp->hp_rsize, USBD_NO_COPY | USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, hp->hp_read_cb); + + status = usbd_transfer(hp->hp_rxfer); + if (status != USBD_IN_PROGRESS) { + DPRINTF(0, "non-normal status %s\n", usbd_errstr(status)); + return EIO; + } + + sc->sc_refcnt++; + return 0; +} + +Static int +uhso_bulk_control(struct uhso_port *hp) +{ + struct uhso_softc *sc = hp->hp_sc; + usb_device_request_t req; + usbd_status status; + int val; + + DPRINTF(1, "hp=%p\n", hp); + + if (hp->hp_isize == 0) + return 0; + + val = 0; + if (ISSET(hp->hp_status, TIOCM_DTR)) + SET(val, UCDC_LINE_DTR); + if (ISSET(hp->hp_status, TIOCM_RTS)) + SET(val, UCDC_LINE_RTS); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, val); + USETW(req.wIndex, hp->hp_index); + USETW(req.wLength, 0); + + sc->sc_refcnt++; + + status = usbd_do_request(sc->sc_udev, &req, NULL); + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(0, "non-normal status %s\n", usbd_errstr(status)); + return EIO; + } + + return 0; +} + +Static void +uhso_bulk_intr(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status) +{ + struct uhso_port *hp = p; + struct tty *tp = hp->hp_tp; + usb_cdc_notification_t *msg; + uint32_t cc; + int s, old; + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(0, "non-normal status %s\n", usbd_errstr(status)); + return; + } + + usbd_get_xfer_status(xfer, NULL, (void **)&msg, &cc, NULL); + + if (cc < UCDC_NOTIFICATION_LENGTH + || msg->bmRequestType != UCDC_NOTIFICATION + || msg->bNotification != UCDC_N_SERIAL_STATE + || UGETW(msg->wValue) != 0 + || UGETW(msg->wIndex) != hp->hp_index + || UGETW(msg->wLength) < 1) + return; + + DPRINTF(5, "state=%02x\n", msg->data[0]); + + old = hp->hp_status; + CLR(hp->hp_status, TIOCM_RNG | TIOCM_DSR | TIOCM_CAR); + if (ISSET(msg->data[0], UCDC_N_SERIAL_RI)) + SET(hp->hp_status, TIOCM_RNG); + if (ISSET(msg->data[0], UCDC_N_SERIAL_DSR)) + SET(hp->hp_status, TIOCM_DSR); + if (ISSET(msg->data[0], UCDC_N_SERIAL_DCD)) + SET(hp->hp_status, TIOCM_CAR); + + if (ISSET(hp->hp_status ^ old, TIOCM_CAR)) { + s = spltty(); + tp->t_linesw->l_modem(tp, ISSET(hp->hp_status, TIOCM_CAR)); + splx(s); + } + + if (ISSET((hp->hp_status ^ old), TIOCM_RNG | TIOCM_DSR | TIOCM_CAR)) + DPRINTF(1, "RNG %s, DSR %s, DCD %s\n", + (ISSET(hp->hp_status, TIOCM_RNG) ? "on" : "off"), + (ISSET(hp->hp_status, TIOCM_DSR) ? "on" : "off"), + (ISSET(hp->hp_status, TIOCM_CAR) ? "on" : "off")); +} + + +/****************************************************************************** + * + * TTY management + * + */ + +Static void +uhso_tty_attach(struct uhso_port *hp) +{ + struct tty *tp; + + tp = ttymalloc(); + tp->t_oproc = uhso_tty_start; + tp->t_param = uhso_tty_param; + + hp->hp_tp = tp; + tty_attach(tp); + + DPRINTF(1, "hp=%p, tp=%p\n", hp, tp); +} + +Static void +uhso_tty_detach(struct uhso_port *hp) +{ + + DPRINTF(1, "hp=%p\n", hp); + + uhso_tty_clean(hp); + + tty_detach(hp->hp_tp); + ttyfree(hp->hp_tp); + hp->hp_tp = NULL; +} + +Static void +uhso_tty_write_cb(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status) +{ + struct uhso_port *hp = p; + struct uhso_softc *sc = hp->hp_sc; + struct tty *tp = hp->hp_tp; + uint32_t cc; + int s; + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(0, "non-normal status %s\n", usbd_errstr(status)); + + if (status == USBD_STALLED && hp->hp_wpipe != NULL) + usbd_clear_endpoint_stall_async(hp->hp_wpipe); + else + return; + } else { + usbd_get_xfer_status(xfer, NULL, NULL, &cc, NULL); + + DPRINTF(5, "wrote %d bytes (of %zd)\n", cc, hp->hp_wlen); + if (cc != hp->hp_wlen) + DPRINTF(0, "cc=%u, wlen=%zd\n", cc, hp->hp_wlen); + } + + s = spltty(); + CLR(tp->t_state, TS_BUSY); + tp->t_linesw->l_start(tp); + splx(s); +} + +Static void +uhso_tty_read_cb(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status) +{ + struct uhso_port *hp = p; + struct uhso_softc *sc = hp->hp_sc; + struct tty *tp = hp->hp_tp; + uint8_t *cp; + uint32_t cc; + int s; + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(0, "non-normal status: %s\n", usbd_errstr(status)); + + if (status == USBD_STALLED && hp->hp_rpipe != NULL) + usbd_clear_endpoint_stall_async(hp->hp_rpipe); + else + return; + + hp->hp_rlen = 0; + } else { + usbd_get_xfer_status(xfer, NULL, (void **)&cp, &cc, NULL); + + hp->hp_rlen = cc; + DPRINTF(5, "read %d bytes\n", cc); + + s = spltty(); + while (cc > 0) { + if (tp->t_linesw->l_rint(*cp++, tp) == -1) { + DPRINTF(0, "lost %d bytes\n", cc); + break; + } + + cc--; + } + splx(s); + } + + (*hp->hp_read)(hp); +} + + +/****************************************************************************** + * + * TTY subsystem + * + */ + +int +uhso_tty_open(dev_t dev, int flag, int mode, struct lwp *l) +{ + struct uhso_softc *sc; + struct uhso_port *hp; + struct tty *tp; + int error, s; + + DPRINTF(1, "unit %d port %d\n", UHSOUNIT(dev), UHSOPORT(dev)); + + sc = device_lookup_private(&uhso_cd, UHSOUNIT(dev)); + if (sc == NULL + || !device_is_active(sc->sc_dev) + || UHSOPORT(dev) >= UHSO_PORT_MAX) + return ENXIO; + + hp = sc->sc_port[UHSOPORT(dev)]; + if (hp == NULL || hp->hp_tp == NULL) + return ENXIO; + + tp = hp->hp_tp; + if (kauth_authorize_device_tty(l->l_cred, KAUTH_DEVICE_TTY_OPEN, tp)) + return EBUSY; + + error = 0; + s = spltty(); + if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) { + tp->t_dev = dev; + error = uhso_tty_init(hp); + } + splx(s); + + if (error == 0) { + error = ttyopen(tp, UHSODIALOUT(dev), ISSET(flag, O_NONBLOCK)); + if (error == 0) { + error = tp->t_linesw->l_open(dev, tp); + } + } + + if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) + uhso_tty_clean(hp); + + DPRINTF(1, "sc=%p, hp=%p, tp=%p, error=%d\n", sc, hp, tp, error); + + return error; +} + +Static int +uhso_tty_init(struct uhso_port *hp) +{ + struct uhso_softc *sc = hp->hp_sc; + struct tty *tp = hp->hp_tp; + struct termios t; + int error; + + DPRINTF(1, "sc=%p, hp=%p, tp=%p\n", sc, hp, tp); + + /* + * Initialize the termios status to the defaults. Add in the + * sticky bits from TIOCSFLAGS. + */ + t.c_ispeed = 0; + t.c_ospeed = TTYDEF_SPEED; + t.c_cflag = TTYDEF_CFLAG; + if (ISSET(hp->hp_swflags, TIOCFLAG_CLOCAL)) + SET(t.c_cflag, CLOCAL); + if (ISSET(hp->hp_swflags, TIOCFLAG_CRTSCTS)) + SET(t.c_cflag, CRTSCTS); + if (ISSET(hp->hp_swflags, TIOCFLAG_MDMBUF)) + SET(t.c_cflag, MDMBUF); + + /* Ensure uhso_tty_param() will do something. */ + tp->t_ospeed = 0; + (void)uhso_tty_param(tp, &t); + + tp->t_iflag = TTYDEF_IFLAG; + tp->t_oflag = TTYDEF_OFLAG; + tp->t_lflag = TTYDEF_LFLAG; + ttychars(tp); + ttsetwater(tp); + + hp->hp_status = 0; + error = (*hp->hp_init)(hp); + if (error != 0) + return error; + + hp->hp_rxfer = usbd_alloc_xfer(sc->sc_udev); + if (hp->hp_rxfer == NULL) + return ENOMEM; + + hp->hp_rbuf = usbd_alloc_buffer(hp->hp_rxfer, hp->hp_rsize); + if (hp->hp_rbuf == NULL) + return ENOMEM; + + hp->hp_wxfer = usbd_alloc_xfer(sc->sc_udev); + if (hp->hp_wxfer == NULL) + return ENOMEM; + + hp->hp_wbuf = usbd_alloc_buffer(hp->hp_wxfer, hp->hp_wsize); + if (hp->hp_wbuf == NULL) + return ENOMEM; + + /* + * Turn on DTR. We must always do this, even if carrier is not + * present, because otherwise we'd have to use TIOCSDTR + * immediately after setting CLOCAL, which applications do not + * expect. We always assert DTR while the port is open + * unless explicitly requested to deassert it. Ditto RTS. + */ + uhso_tty_control(hp, TIOCMBIS, TIOCM_DTR | TIOCM_RTS); + + /* and start reading */ + error = (*hp->hp_read)(hp); + if (error != 0) + return error; + + return 0; +} + +int +uhso_tty_close(dev_t dev, int flag, int mode, struct lwp *l) +{ + struct uhso_softc *sc = device_lookup_private(&uhso_cd, UHSOUNIT(dev)); + struct uhso_port *hp = sc->sc_port[UHSOPORT(dev)]; + struct tty *tp = hp->hp_tp; + + if (!ISSET(tp->t_state, TS_ISOPEN)) + return 0; + + DPRINTF(1, "sc=%p, hp=%p, tp=%p\n", sc, hp, tp); + + sc->sc_refcnt++; + + tp->t_linesw->l_close(tp, flag); + ttyclose(tp); + + if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) + uhso_tty_clean(hp); + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + return 0; +} + +Static void +uhso_tty_clean(struct uhso_port *hp) +{ + + DPRINTF(1, "hp=%p\n", hp); + + if (ISSET(hp->hp_status, TIOCM_DTR) + && ISSET(hp->hp_tp->t_cflag, HUPCL)) + uhso_tty_control(hp, TIOCMBIC, TIOCM_DTR); + + (*hp->hp_clean)(hp); + + if (hp->hp_rxfer != NULL) { + usbd_free_xfer(hp->hp_rxfer); + hp->hp_rxfer = NULL; + hp->hp_rbuf = NULL; + } + + if (hp->hp_wxfer != NULL) { + usbd_free_xfer(hp->hp_wxfer); + hp->hp_wxfer = NULL; + hp->hp_wbuf = NULL; + } +} + +int +uhso_tty_read(dev_t dev, struct uio *uio, int flag) +{ + struct uhso_softc *sc = device_lookup_private(&uhso_cd, UHSOUNIT(dev)); + struct uhso_port *hp = sc->sc_port[UHSOPORT(dev)]; + struct tty *tp = hp->hp_tp; + int error; + + if (!device_is_active(sc->sc_dev)) + return EIO; + + DPRINTF(5, "sc=%p, hp=%p, tp=%p\n", sc, hp, tp); + + sc->sc_refcnt++; + + error = tp->t_linesw->l_read(tp, uio, flag); + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + return error; +} + +int +uhso_tty_write(dev_t dev, struct uio *uio, int flag) +{ + struct uhso_softc *sc = device_lookup_private(&uhso_cd, UHSOUNIT(dev)); + struct uhso_port *hp = sc->sc_port[UHSOPORT(dev)]; + struct tty *tp = hp->hp_tp; + int error; + + if (!device_is_active(sc->sc_dev)) + return EIO; + + DPRINTF(5, "sc=%p, hp=%p, tp=%p\n", sc, hp, tp); + + sc->sc_refcnt++; + + error = tp->t_linesw->l_write(tp, uio, flag); + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + return error; +} + +int +uhso_tty_ioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) +{ + struct uhso_softc *sc = device_lookup_private(&uhso_cd, UHSOUNIT(dev)); + struct uhso_port *hp = sc->sc_port[UHSOPORT(dev)]; + int error; + + if (!device_is_active(sc->sc_dev)) + return EIO; + + DPRINTF(1, "sc=%p, hp=%p\n", sc, hp); + + sc->sc_refcnt++; + + error = uhso_tty_do_ioctl(hp, cmd, data, flag, l); + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + return error; +} + +Static int +uhso_tty_do_ioctl(struct uhso_port *hp, u_long cmd, void *data, int flag, + struct lwp *l) +{ + struct tty *tp = hp->hp_tp; + int error, s; + + error = tp->t_linesw->l_ioctl(tp, cmd, data, flag, l); + if (error != EPASSTHROUGH) + return error; + + error = ttioctl(tp, cmd, data, flag, l); + if (error != EPASSTHROUGH) + return error; + + error = 0; + + s = spltty(); + + switch (cmd) { + case TIOCSDTR: + error = uhso_tty_control(hp, TIOCMBIS, TIOCM_DTR); + break; + + case TIOCCDTR: + error = uhso_tty_control(hp, TIOCMBIC, TIOCM_DTR); + break; + + case TIOCGFLAGS: + *(int *)data = hp->hp_swflags; + break; + + case TIOCSFLAGS: + error = kauth_authorize_device_tty(l->l_cred, + KAUTH_DEVICE_TTY_PRIVSET, tp); + + if (error) + break; + + hp->hp_swflags = *(int *)data; + break; + + case TIOCMSET: + case TIOCMBIS: + case TIOCMBIC: + error = uhso_tty_control(hp, cmd, *(int *)data); + break; + + case TIOCMGET: + *(int *)data = hp->hp_status; + break; + + default: + error = EPASSTHROUGH; + break; + } + + splx(s); + + return error; +} + +/* this is called with tty_lock held */ +void +uhso_tty_stop(struct tty *tp, int flag) +{ +#if 0 + struct uhso_softc *sc = device_lookup_private(&uhso_cd, UHSOUNIT(tp->t_dev)); + struct uhso_port *hp = sc->sc_port[UHSOPORT(tp->t_dev)]; +#endif +} + +struct tty * +uhso_tty_tty(dev_t dev) +{ + struct uhso_softc *sc = device_lookup_private(&uhso_cd, UHSOUNIT(dev)); + struct uhso_port *hp = sc->sc_port[UHSOPORT(dev)]; + + return hp->hp_tp; +} + +int +uhso_tty_poll(dev_t dev, int events, struct lwp *l) +{ + struct uhso_softc *sc = device_lookup_private(&uhso_cd, UHSOUNIT(dev)); + struct uhso_port *hp = sc->sc_port[UHSOPORT(dev)]; + struct tty *tp = hp->hp_tp; + int revents; + + if (!device_is_active(sc->sc_dev)) + return POLLHUP; + + sc->sc_refcnt++; + + revents = tp->t_linesw->l_poll(tp, events, l); + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + return revents; +} + +Static int +uhso_tty_param(struct tty *tp, struct termios *t) +{ + struct uhso_softc *sc = device_lookup_private(&uhso_cd, UHSOUNIT(tp->t_dev)); + struct uhso_port *hp = sc->sc_port[UHSOPORT(tp->t_dev)]; + + if (!device_is_active(sc->sc_dev)) + return EIO; + + DPRINTF(1, "hp=%p, tp=%p, termios iflag=%x, oflag=%x, cflag=%x\n", + hp, tp, t->c_iflag, t->c_oflag, t->c_cflag); + + /* Check requested parameters. */ + if (t->c_ispeed != 0 + && t->c_ispeed != t->c_ospeed) + return EINVAL; + + /* force CLOCAL and !HUPCL for console */ + if (ISSET(hp->hp_swflags, TIOCFLAG_SOFTCAR)) { + SET(t->c_cflag, CLOCAL); + CLR(t->c_cflag, HUPCL); + } + + /* If there were no changes, don't do anything. */ + if (tp->t_ospeed == t->c_ospeed + && tp->t_cflag == t->c_cflag) + return 0; + + tp->t_ispeed = 0; + tp->t_ospeed = t->c_ospeed; + tp->t_cflag = t->c_cflag; + + /* update tty layers idea of carrier bit */ + tp->t_linesw->l_modem(tp, ISSET(hp->hp_status, TIOCM_CAR)); + return 0; +} + +/* this is called with tty_lock held */ +Static void +uhso_tty_start(struct tty *tp) +{ + struct uhso_softc *sc = device_lookup_private(&uhso_cd, UHSOUNIT(tp->t_dev)); + struct uhso_port *hp = sc->sc_port[UHSOPORT(tp->t_dev)]; + int s; + + if (!device_is_active(sc->sc_dev)) + return; + + s = spltty(); + + if (!ISSET(tp->t_state, TS_BUSY | TS_TIMEOUT | TS_TTSTOP) + && ttypull(tp) != 0) { + hp->hp_wlen = q_to_b(&tp->t_outq, hp->hp_wbuf, hp->hp_wsize); + if (hp->hp_wlen > 0) { + SET(tp->t_state, TS_BUSY); + (*hp->hp_write)(hp); + } + } + + splx(s); +} + +Static int +uhso_tty_control(struct uhso_port *hp, u_long cmd, int bits) +{ + + bits &= (TIOCM_DTR | TIOCM_RTS); + DPRINTF(1, "cmd %s, DTR=%d, RTS=%d\n", + (cmd == TIOCMBIC ? "BIC" : (cmd == TIOCMBIS ? "BIS" : "SET")), + (bits & TIOCM_DTR) ? 1 : 0, + (bits & TIOCM_RTS) ? 1 : 0); + + switch (cmd) { + case TIOCMBIC: + CLR(hp->hp_status, bits); + break; + + case TIOCMBIS: + SET(hp->hp_status, bits); + break; + + case TIOCMSET: + CLR(hp->hp_status, TIOCM_DTR | TIOCM_RTS); + SET(hp->hp_status, bits); + break; + } + + return (*hp->hp_control)(hp); +} + + +/****************************************************************************** + * + * Network Interface + * + */ + +Static void +uhso_ifnet_attach(struct uhso_softc *sc, usbd_interface_handle ifh, int index) +{ + usb_endpoint_descriptor_t *ed; + struct uhso_port *hp; + struct ifnet *ifp; + int in, out; + + ed = uhso_get_endpoint(ifh, UE_BULK, UE_DIR_IN); + if (ed == NULL) { + aprint_error_dev(sc->sc_dev, + "could not find bulk-in endpoint\n"); + + return; + } + in = ed->bEndpointAddress; + + ed = uhso_get_endpoint(ifh, UE_BULK, UE_DIR_OUT); + if (ed == NULL) { + aprint_error_dev(sc->sc_dev, + "could not find bulk-out endpoint\n"); + + return; + } + out = ed->bEndpointAddress; + + DPRINTF(1, "in=%d, out=%d\n", in, out); + + if (sc->sc_port[index] != NULL) { + aprint_error_dev(sc->sc_dev, + "ifnet port %d is duplicate!\n", index); + + return; + } + + hp = kmem_zalloc(sizeof(struct uhso_port), KM_SLEEP); + sc->sc_port[index] = hp; + + ifp = if_alloc(IFT_IP); + strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ); + ifp->if_softc = hp; + ifp->if_mtu = UHSO_IFNET_MTU; + ifp->if_dlt = DLT_RAW; + ifp->if_type = IFT_IP; + ifp->if_flags = IFF_NOARP | IFF_SIMPLEX; + ifp->if_ioctl = uhso_ifnet_ioctl; + ifp->if_start = uhso_ifnet_start; + ifp->if_output = uhso_ifnet_output; + IFQ_SET_READY(&ifp->if_snd); + + hp->hp_sc = sc; + hp->hp_ifp = ifp; + hp->hp_ifh = ifh; + hp->hp_raddr = in; + hp->hp_waddr = out; + hp->hp_abort = uhso_ifnet_abort; + hp->hp_detach = uhso_ifnet_detach; + hp->hp_init = uhso_bulk_init; + hp->hp_clean = uhso_bulk_clean; + hp->hp_write = uhso_bulk_write; + hp->hp_write_cb = uhso_ifnet_write_cb; + hp->hp_read = uhso_bulk_read; + hp->hp_read_cb = uhso_ifnet_read_cb; + hp->hp_wsize = MCLBYTES; + hp->hp_rsize = MCLBYTES; + + if_attach(ifp); + if_alloc_sadl(ifp); + bpf_ops->bpf_attach(ifp, DLT_RAW, 0, &ifp->if_bpf); + + aprint_normal_dev(sc->sc_dev, "%s (port %d) attached as ifnet\n", + uhso_port_name[index], index); +} + +Static int +uhso_ifnet_abort(struct uhso_port *hp) +{ + struct ifnet *ifp = hp->hp_ifp; + + /* All ifnet IO will abort when IFF_RUNNING is not set */ + CLR(ifp->if_flags, IFF_RUNNING); + + return (*hp->hp_clean)(hp); +} + +Static int +uhso_ifnet_detach(struct uhso_port *hp) +{ + struct ifnet *ifp = hp->hp_ifp; + int s; + + s = splnet(); + bpf_ops->bpf_detach(ifp); + if_detach(ifp); + splx(s); + + kmem_free(hp, sizeof(struct uhso_port)); + return 0; +} + +Static void +uhso_ifnet_write_cb(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status) +{ + struct uhso_port *hp = p; + struct uhso_softc *sc= hp->hp_sc; + struct ifnet *ifp = hp->hp_ifp; + uint32_t cc; + int s; + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + if (!ISSET(ifp->if_flags, IFF_RUNNING)) + return; + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(0, "non-normal status %s\n", usbd_errstr(status)); + + if (status == USBD_STALLED && hp->hp_wpipe != NULL) + usbd_clear_endpoint_stall_async(hp->hp_wpipe); + else + return; + + ifp->if_oerrors++; + } else { + usbd_get_xfer_status(xfer, NULL, NULL, &cc, NULL); + DPRINTF(5, "wrote %d bytes (of %zd)\n", cc, hp->hp_wlen); + + if (cc != hp->hp_wlen) + DPRINTF(0, "cc=%u, wlen=%zd\n", cc, hp->hp_wlen); + + ifp->if_opackets++; + } + + s = splnet(); + CLR(ifp->if_flags, IFF_OACTIVE); + ifp->if_start(ifp); + splx(s); +} + +Static void +uhso_ifnet_read_cb(usbd_xfer_handle xfer, usbd_private_handle p, + usbd_status status) +{ + struct uhso_port *hp = p; + struct uhso_softc *sc= hp->hp_sc; + struct ifnet *ifp = hp->hp_ifp; + void *cp; + uint32_t cc; + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + if (!ISSET(ifp->if_flags, IFF_RUNNING)) + return; + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(0, "non-normal status: %s\n", usbd_errstr(status)); + + if (status == USBD_STALLED && hp->hp_rpipe != NULL) + usbd_clear_endpoint_stall_async(hp->hp_rpipe); + else + return; + + ifp->if_ierrors++; + hp->hp_rlen = 0; + } else { + usbd_get_xfer_status(xfer, NULL, (void **)&cp, &cc, NULL); + + hp->hp_rlen = cc; + DPRINTF(5, "read %d bytes\n", cc); + + uhso_ifnet_input(ifp, &hp->hp_mbuf, cp, cc); + } + + (*hp->hp_read)(hp); +} + +Static void +uhso_ifnet_input(struct ifnet *ifp, struct mbuf **mb, uint8_t *cp, size_t cc) +{ + struct mbuf *m; + size_t got, len, want; + int s; + + /* + * Several IP packets might be in the same buffer, we need to + * separate them before handing it to the ip-stack. We might + * also receive partial packets which we need to defer until + * we get more data. + */ + while (cc > 0) { + if (*mb == NULL) { + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + aprint_error_ifnet(ifp, "no mbufs\n"); + ifp->if_ierrors++; + break; + } + + MCLGET(m, M_DONTWAIT); + if (!ISSET(m->m_flags, M_EXT)) { + aprint_error_ifnet(ifp, "no mbuf clusters\n"); + ifp->if_ierrors++; + m_freem(m); + break; + } + + got = 0; + } else { + m = *mb; + *mb = NULL; + got = m->m_pkthdr.len; + } + + /* make sure that the incoming packet is ok */ + if (got == 0) + mtod(m, uint8_t *)[0] = cp[0]; + + want = mtod(m, struct ip *)->ip_hl << 2; + if (mtod(m, struct ip *)->ip_v != 4 + || want != sizeof(struct ip)) { + aprint_error_ifnet(ifp, "bad IP header (v=%d, hl=%zd)\n", + mtod(m, struct ip *)->ip_v, want); + + ifp->if_ierrors++; + m_freem(m); + break; + } + + /* ensure we have the IP header.. */ + if (got < want) { + len = MIN(want - got, cc); + memcpy(mtod(m, uint8_t *) + got, cp, len); + got += len; + cc -= len; + cp += len; + + if (got < want) { + DPRINTF(5, "waiting for IP header " + "(got %zd want %zd)\n", got, want); + + m->m_pkthdr.len = got; + *mb = m; + break; + } + } + + /* ..and the packet body */ + want = ntohs(mtod(m, struct ip *)->ip_len); + if (got < want) { + len = MIN(want - got, cc); + memcpy(mtod(m, uint8_t *) + got, cp, len); + got += len; + cc -= len; + cp += len; + + if (got < want) { + DPRINTF(5, "waiting for IP packet " + "(got %zd want %zd)\n", got, want); + + m->m_pkthdr.len = got; + *mb = m; + break; + } + } else if (want > got) { + aprint_error_ifnet(ifp, "bad IP packet (len=%zd)\n", + want); + + ifp->if_ierrors++; + m_freem(m); + break; + } + + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = got; + + s = splnet(); + + if (ifp->if_bpf) + bpf_ops->bpf_mtap(ifp->if_bpf, m); + + ifp->if_ipackets++; + ifp->if_ibytes += m->m_pkthdr.len; + + if (IF_QFULL(&ipintrq)) { + IF_DROP(&ipintrq); + m_freem(m); + } else { + IF_ENQUEUE(&ipintrq, m); + schednetisr(NETISR_IP); + } + + splx(s); + } +} + +Static int +uhso_ifnet_ioctl(struct ifnet *ifp, u_long cmd, void *data) +{ + struct uhso_port *hp = ifp->if_softc; + int error, s; + + s = splnet(); + + switch (cmd) { + case SIOCINITIFADDR: + switch (((struct ifaddr *)data)->ifa_addr->sa_family) { +#ifdef INET + case AF_INET: + if (!ISSET(ifp->if_flags, IFF_RUNNING)) { + SET(ifp->if_flags, IFF_UP); + error = uhso_ifnet_init(hp); + if (error != 0) { + uhso_ifnet_clean(hp); + break; + } + + SET(ifp->if_flags, IFF_RUNNING); + DPRINTF(1, "hp=%p, ifp=%p INITIFADDR\n", hp, ifp); + break; + } + + error = 0; + break; +#endif + + default: + error = EAFNOSUPPORT; + break; + } + break; + + case SIOCSIFMTU: + if (((struct ifreq *)data)->ifr_mtu > hp->hp_wsize) { + error = EINVAL; + break; + } + + error = ifioctl_common(ifp, cmd, data); + if (error == ENETRESET) + error = 0; + + break; + + case SIOCSIFFLAGS: + error = ifioctl_common(ifp, cmd, data); + if (error != 0) + break; + + switch (ifp->if_flags & (IFF_UP | IFF_RUNNING)) { + case IFF_UP: + error = uhso_ifnet_init(hp); + if (error != 0) { + uhso_ifnet_clean(hp); + break; + } + + SET(ifp->if_flags, IFF_RUNNING); + DPRINTF(1, "hp=%p, ifp=%p RUNNING\n", hp, ifp); + break; + + case IFF_RUNNING: + uhso_ifnet_clean(hp); + CLR(ifp->if_flags, IFF_RUNNING); + DPRINTF(1, "hp=%p, ifp=%p STOPPED\n", hp, ifp); + break; + + default: + break; + } + break; + + default: + error = ifioctl_common(ifp, cmd, data); + break; + } + + splx(s); + + return error; +} + +/* is only called if IFF_RUNNING not set */ +Static int +uhso_ifnet_init(struct uhso_port *hp) +{ + struct uhso_softc *sc = hp->hp_sc; + int error; + + DPRINTF(1, "sc=%p, hp=%p\n", sc, hp); + + if (!device_is_active(sc->sc_dev)) + return EIO; + + error = (*hp->hp_init)(hp); + if (error != 0) + return error; + + hp->hp_rxfer = usbd_alloc_xfer(sc->sc_udev); + if (hp->hp_rxfer == NULL) + return ENOMEM; + + hp->hp_rbuf = usbd_alloc_buffer(hp->hp_rxfer, hp->hp_rsize); + if (hp->hp_rbuf == NULL) + return ENOMEM; + + hp->hp_wxfer = usbd_alloc_xfer(sc->sc_udev); + if (hp->hp_wxfer == NULL) + return ENOMEM; + + hp->hp_wbuf = usbd_alloc_buffer(hp->hp_wxfer, hp->hp_wsize); + if (hp->hp_wbuf == NULL) + return ENOMEM; + + error = (*hp->hp_read)(hp); + if (error != 0) + return error; + + return 0; +} + +Static void +uhso_ifnet_clean(struct uhso_port *hp) +{ + + DPRINTF(1, "hp=%p\n", hp); + + (*hp->hp_clean)(hp); + + if (hp->hp_rxfer != NULL) { + usbd_free_xfer(hp->hp_rxfer); + hp->hp_rxfer = NULL; + hp->hp_rbuf = NULL; + } + + if (hp->hp_wxfer != NULL) { + usbd_free_xfer(hp->hp_wxfer); + hp->hp_wxfer = NULL; + hp->hp_wbuf = NULL; + } +} + +/* called at splnet() with IFF_OACTIVE not set */ +Static void +uhso_ifnet_start(struct ifnet *ifp) +{ + struct uhso_port *hp = ifp->if_softc; + struct mbuf *m; + + KASSERT(!ISSET(ifp->if_flags, IFF_OACTIVE)); + + if (!ISSET(ifp->if_flags, IFF_RUNNING)) + return; + + if (IFQ_IS_EMPTY(&ifp->if_snd)) { + DPRINTF(5, "finished sending\n"); + return; + } + + SET(ifp->if_flags, IFF_OACTIVE); + IFQ_DEQUEUE(&ifp->if_snd, m); + hp->hp_wlen = m->m_pkthdr.len; + if (hp->hp_wlen > hp->hp_wsize) { + aprint_error_ifnet(ifp, + "packet too long (%zd > %zd), truncating\n", + hp->hp_wlen, hp->hp_wsize); + + hp->hp_wlen = hp->hp_wsize; + } + + if (ifp->if_bpf) + bpf_ops->bpf_mtap(ifp->if_bpf, m); + + m_copydata(m, 0, hp->hp_wlen, hp->hp_wbuf); + m_freem(m); + + if ((*hp->hp_write)(hp) != 0) { + ifp->if_oerrors++; + CLR(ifp->if_flags, IFF_OACTIVE); + } +} + +Static int +uhso_ifnet_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *dst, + struct rtentry *rt0) +{ + ALTQ_DECL(struct altq_pktattr pktattr); + int error; + + if (!ISSET(ifp->if_flags, IFF_RUNNING)) + return EIO; + + IFQ_CLASSIFY(&ifp->if_snd, m, dst->sa_family, &pktattr); + + switch (dst->sa_family) { +#ifdef INET + case AF_INET: + error = ifq_enqueue(ifp, m ALTQ_COMMA ALTQ_DECL(&pktattr)); + break; +#endif + + default: + DPRINTF(0, "unsupported address family %d\n", dst->sa_family); + error = EAFNOSUPPORT; + m_freem(m); + break; + } + + return error; +} |
