summaryrefslogtreecommitdiff
path: root/sys/dev
diff options
context:
space:
mode:
authorplunky <plunky@NetBSD.org>2010-03-06 21:05:36 +0000
committerplunky <plunky@NetBSD.org>2010-03-06 21:05:36 +0000
commit800cedea24f45ea52c859dc5cebaf5a66cdc225e (patch)
tree3657d1db9af804eecabfecb8fbc684c4cfd1f761 /sys/dev
parent7a23a88ab7e02837c7755d3569f1a61f9ae5bff2 (diff)
Add uhso(4) driver and manpage for Option N.V. Wireless WAN modems
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/DEVNAMES3
-rw-r--r--sys/dev/usb/files.usb7
-rw-r--r--sys/dev/usb/uhso.c2376
3 files changed, 2384 insertions, 2 deletions
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;
+}