/* $NetBSD: uhso.c,v 1.37 2022/10/26 23:53:03 riastradh 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 __KERNEL_RCSID(0, "$NetBSD: uhso.c,v 1.37 2022/10/26 23:53:03 riastradh Exp $"); #ifdef _KERNEL_OPT #include "opt_inet.h" #include "opt_usb.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include "ioconf.h" #undef DPRINTF #ifdef UHSO_DEBUG /* * defined levels * 0 warnings only * 1 informational * 5 really chatty */ int uhso_debug = 0; #define DPRINTF(n, ...) do { \ if (uhso_debug >= (n)) { \ printf("%s: ", __func__); \ printf(__VA_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, &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_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_GTHSUPAM, 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 */ struct usbd_interface *hp_ifh; /* interface handle */ unsigned int hp_index; /* usb request index */ int hp_iaddr; /* interrupt endpoint */ struct usbd_pipe *hp_ipipe; /* interrupt pipe */ void *hp_ibuf; /* interrupt buffer */ size_t hp_isize; /* allocated size */ int hp_raddr; /* bulk in endpoint */ struct usbd_pipe *hp_rpipe; /* bulk in pipe */ struct usbd_xfer *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 */ struct usbd_pipe *hp_wpipe; /* bulk out pipe */ struct usbd_xfer *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 */ struct usbd_device *sc_udev; int sc_refcnt; struct uhso_port *sc_port[UHSO_PORT_MAX]; }; #define UHSO_CONFIG_NO 1 static int uhso_match(device_t, cfdata_t, void *); static void uhso_attach(device_t, device_t, void *); static int uhso_detach(device_t, int); CFATTACH_DECL_NEW(uhso, sizeof(struct uhso_softc), uhso_match, uhso_attach, uhso_detach, NULL); Static int uhso_switch_mode(struct usbd_device *); Static int uhso_get_iface_spec(struct usb_attach_arg *, uint8_t, uint8_t *); Static usb_endpoint_descriptor_t *uhso_get_endpoint(struct usbd_interface *, int, int); Static void uhso_mux_attach(struct uhso_softc *, struct usbd_interface *, 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(struct usbd_xfer *, void *, usbd_status); Static void uhso_bulk_attach(struct uhso_softc *, struct usbd_interface *, 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(struct usbd_xfer *, void *, usbd_status); Static void uhso_tty_attach(struct uhso_port *); Static void uhso_tty_detach(struct uhso_port *); Static void uhso_tty_read_cb(struct usbd_xfer *, void *, usbd_status); Static void uhso_tty_write_cb(struct usbd_xfer *, void *, usbd_status); static dev_type_open(uhso_tty_open); static dev_type_close(uhso_tty_close); static dev_type_read(uhso_tty_read); static dev_type_write(uhso_tty_write); static dev_type_ioctl(uhso_tty_ioctl); static dev_type_stop(uhso_tty_stop); static dev_type_tty(uhso_tty_tty); static 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_discard = nodiscard, .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 TTUNIT_MASK #define UHSO_PORT_MASK 0x0000f #define UHSO_DIALOUT_MASK TTDIALOUT_MASK #define UHSO_CALLUNIT_MASK TTCALLUNIT_MASK #define UHSOUNIT(x) (TTUNIT(x) >> 4) #define UHSOPORT(x) (TTUNIT(x) & UHSO_PORT_MASK) #define UHSODIALOUT(x) TTDIALOUT(x) #define UHSOMINOR(u, p) ((((u) << 4) & UHSO_UNIT_MASK) | ((p) & UHSO_UNIT_MASK)) Static void uhso_ifnet_attach(struct uhso_softc *, struct usbd_interface *, int); Static int uhso_ifnet_abort(struct uhso_port *); Static int uhso_ifnet_detach(struct uhso_port *); Static void uhso_ifnet_read_cb(struct usbd_xfer *, void *, usbd_status); Static void uhso_ifnet_input(struct ifnet *, struct mbuf **, uint8_t *, size_t); Static void uhso_ifnet_write_cb(struct usbd_xfer *, void *, 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 *, const struct rtentry *); /******************************************************************************* * * USB autoconfig * */ static 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->uaa_class != UDCLASS_VENDOR) return UMATCH_NONE; if (uhso_lookup(uaa->uaa_vendor, uaa->uaa_product)) return UMATCH_VENDOR_PRODUCT; return UMATCH_NONE; } static void uhso_attach(device_t parent, device_t self, void *aux) { struct uhso_softc *sc = device_private(self); struct usb_attach_arg *uaa = aux; struct usbd_interface *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->uaa_device; aprint_naive("\n"); aprint_normal("\n"); devinfop = usbd_devinfo_alloc(uaa->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, "failed to set configuration" ", err=%s\n", usbd_errstr(status)); return; } if (uaa->uaa_class != UDCLASS_VENDOR) { aprint_verbose_dev(self, "Switching device into modem mode..\n"); if (uhso_switch_mode(uaa->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"); } static 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; 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_waitold(sc->sc_dev); } splx(s); /* * XXX the tty close routine increases/decreases refcnt causing * XXX another usb_detach_wakeupold() 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(struct usbd_device *udev) { umass_bbb_cbw_t cmd; usb_endpoint_descriptor_t *ed; struct usbd_interface *ifh; struct usbd_pipe *pipe; struct usbd_xfer *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; int error = usbd_create_xfer(pipe, sizeof(cmd), 0, 0, &xfer); if (error) return error; 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, NULL, &cmd, sizeof(cmd), USBD_SYNCHRONOUS, USBD_DEFAULT_TIMEOUT, NULL); status = usbd_transfer(xfer); usbd_destroy_xfer(xfer); usbd_close_pipe(pipe); 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->uaa_vendor, uaa->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->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(struct usbd_interface *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, struct usbd_interface *ifh, int index) { usbd_desc_iter_t iter; const usb_descriptor_t *desc; usb_endpoint_descriptor_t *ed; struct usbd_pipe *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); struct uhso_softc *sc = hp->hp_sc; struct usbd_pipe *pipe0 = usbd_get_pipe0(sc->sc_udev); int error; error = usbd_create_xfer(pipe0, hp->hp_rsize, 0, 0, &hp->hp_rxfer); if (error) return error; hp->hp_rbuf = usbd_get_buffer(hp->hp_rxfer); error = usbd_create_xfer(pipe0, hp->hp_wsize, 0, 0, &hp->hp_wxfer); if (error) return error; hp->hp_wbuf = usbd_get_buffer(hp->hp_wxfer); 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, 0, 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_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(struct usbd_xfer *xfer, void * 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, struct usbd_interface *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; } int error = usbd_create_xfer(hp->hp_rpipe, hp->hp_rsize, 0, 0, &hp->hp_rxfer); if (error) return error; hp->hp_rbuf = usbd_get_buffer(hp->hp_rxfer); error = usbd_create_xfer(hp->hp_wpipe, hp->hp_wsize, 0, 0, &hp->hp_wxfer); if (error) return error; hp->hp_wbuf = usbd_get_buffer(hp->hp_wxfer); 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); } if (hp->hp_wpipe != NULL) { usbd_abort_pipe(hp->hp_wpipe); } if (hp->hp_rxfer != NULL) { usbd_destroy_xfer(hp->hp_rxfer); hp->hp_rxfer = NULL; hp->hp_rbuf = NULL; } if (hp->hp_wxfer != NULL) { usbd_destroy_xfer(hp->hp_wxfer); hp->hp_wxfer = NULL; hp->hp_wbuf = NULL; } if (hp->hp_rpipe != NULL) { usbd_close_pipe(hp->hp_rpipe); hp->hp_rpipe = NULL; } if (hp->hp_wpipe != NULL) { 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->hp_wbuf, hp->hp_wlen, 0, 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->hp_rbuf, hp->hp_rsize, 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_wakeupold(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(struct usbd_xfer *xfer, void * 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 = tty_alloc(); 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); tty_free(hp->hp_tp); hp->hp_tp = NULL; } Static void uhso_tty_write_cb(struct usbd_xfer *xfer, void * 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_wakeupold(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(struct usbd_xfer *xfer, void * 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_wakeupold(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 * */ static 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 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; /* * 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; } static 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_wakeupold(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_destroy_xfer(hp->hp_rxfer); hp->hp_rxfer = NULL; hp->hp_rbuf = NULL; } if (hp->hp_wxfer != NULL) { usbd_destroy_xfer(hp->hp_wxfer); hp->hp_wxfer = NULL; hp->hp_wbuf = NULL; } } static 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_wakeupold(sc->sc_dev); return error; } static 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_wakeupold(sc->sc_dev); return error; } static 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_wakeupold(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; } static 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 KASSERT(ttylocked(tp)); } static 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; } static 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_wakeupold(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; } 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; KASSERT(ttylocked(tp)); 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, struct usbd_interface *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_attach(ifp, DLT_RAW, 0); 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_detach(ifp); if_detach(ifp); if_free(ifp); splx(s); kmem_free(hp, sizeof(struct uhso_port)); return 0; } Static void uhso_ifnet_write_cb(struct usbd_xfer *xfer, void * 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_wakeupold(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; if_statinc(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); if_statinc(ifp, if_opackets); } s = splnet(); CLR(ifp->if_flags, IFF_OACTIVE); ifp->if_start(ifp); splx(s); } Static void uhso_ifnet_read_cb(struct usbd_xfer *xfer, void * 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_wakeupold(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; if_statinc(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"); if_statinc(ifp, if_ierrors); break; } MCLGET(m, M_DONTWAIT); if (!ISSET(m->m_flags, M_EXT)) { aprint_error_ifnet(ifp, "no mbuf clusters\n"); if_statinc(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); if_statinc(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; } } m_set_rcvif(m, ifp); m->m_pkthdr.len = m->m_len = got; s = splnet(); bpf_mtap(ifp, m, BPF_D_IN); if (__predict_false(!pktq_enqueue(ip_pktq, m, 0))) { m_freem(m); } else { if_statadd2(ifp, if_ipackets, 1, if_ibytes, got); } 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; 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); } /* 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; } bpf_mtap(ifp, m, BPF_D_OUT); m_copydata(m, 0, hp->hp_wlen, hp->hp_wbuf); m_freem(m); if ((*hp->hp_write)(hp) != 0) { if_statinc(ifp, if_oerrors); CLR(ifp->if_flags, IFF_OACTIVE); } } Static int uhso_ifnet_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *dst, const struct rtentry *rt0) { int error; if (!ISSET(ifp->if_flags, IFF_RUNNING)) return EIO; IFQ_CLASSIFY(&ifp->if_snd, m, dst->sa_family); switch (dst->sa_family) { #ifdef INET case AF_INET: error = ifq_enqueue(ifp, m); break; #endif default: DPRINTF(0, "unsupported address family %d\n", dst->sa_family); error = EAFNOSUPPORT; m_freem(m); break; } return error; }