--- freebsd/sys/dev/usb/serial/uplcom.c | 936 +++++++++++++++++ freebsd/sys/dev/usb/serial/usb_serial.c | 1719 +++++++++++++++++++++++++++++++ freebsd/sys/dev/usb/serial/usb_serial.h | 221 ++++ 3 files changed, 2876 insertions(+) create mode 100644 freebsd/sys/dev/usb/serial/uplcom.c create mode 100644 freebsd/sys/dev/usb/serial/usb_serial.c create mode 100644 freebsd/sys/dev/usb/serial/usb_serial.h
diff --git a/freebsd/sys/dev/usb/serial/uplcom.c b/freebsd/sys/dev/usb/serial/uplcom.c new file mode 100644 index 0000000..31b5867 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/uplcom.c @@ -0,0 +1,936 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $NetBSD: uplcom.c,v 1.21 2001/11/13 06:24:56 lukem Exp $ */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2001-2003, 2005 Shunsuke Akiyama <akiy...@jp.freebsd.org>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ichiro FUKUHARA (ich...@ichiro.org). + * + * 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This driver supports several USB-to-RS232 serial adapters driven by + * Prolific PL-2303, PL-2303X and probably PL-2303HX USB-to-RS232 + * bridge chip. The adapters are sold under many different brand + * names. + * + * Datasheets are available at Prolific www site at + * http://www.prolific.com.tw. The datasheets don't contain full + * programming information for the chip. + * + * PL-2303HX is probably programmed the same as PL-2303X. + * + * There are several differences between PL-2303 and PL-2303(H)X. + * PL-2303(H)X can do higher bitrate in bulk mode, has _probably_ + * different command for controlling CRTSCTS and needs special + * sequence of commands for initialization which aren't also + * documented in the datasheet. + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usb_cdc.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR uplcom_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#ifdef USB_DEBUG +static int uplcom_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uplcom, CTLFLAG_RW, 0, "USB uplcom"); +SYSCTL_INT(_hw_usb_uplcom, OID_AUTO, debug, CTLFLAG_RWTUN, + &uplcom_debug, 0, "Debug level"); +#endif + +#define UPLCOM_MODVER 1 /* module version */ + +#define UPLCOM_CONFIG_INDEX 0 +#define UPLCOM_IFACE_INDEX 0 +#define UPLCOM_SECOND_IFACE_INDEX 1 + +#ifndef UPLCOM_INTR_INTERVAL +#define UPLCOM_INTR_INTERVAL 0 /* default */ +#endif + +#define UPLCOM_BULK_BUF_SIZE 1024 /* bytes */ + +#define UPLCOM_SET_REQUEST 0x01 +#define UPLCOM_SET_CRTSCTS 0x41 +#define UPLCOM_SET_CRTSCTS_PL2303X 0x61 +#define RSAQ_STATUS_CTS 0x80 +#define RSAQ_STATUS_DSR 0x02 +#define RSAQ_STATUS_DCD 0x01 + +#define TYPE_PL2303 0 +#define TYPE_PL2303HX 1 + +enum { + UPLCOM_BULK_DT_WR, + UPLCOM_BULK_DT_RD, + UPLCOM_INTR_DT_RD, + UPLCOM_N_TRANSFER, +}; + +struct uplcom_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UPLCOM_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint16_t sc_line; + + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* uplcom status register */ + uint8_t sc_chiptype; /* type of chip */ + uint8_t sc_ctrl_iface_no; + uint8_t sc_data_iface_no; + uint8_t sc_iface_index[2]; +}; + +/* prototypes */ + +static usb_error_t uplcom_reset(struct uplcom_softc *, struct usb_device *); +static usb_error_t uplcom_pl2303_do(struct usb_device *, uint8_t, uint8_t, + uint16_t, uint16_t, uint16_t); +static int uplcom_pl2303_init(struct usb_device *, uint8_t); +static void uplcom_free(struct ucom_softc *); +static void uplcom_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void uplcom_cfg_set_rts(struct ucom_softc *, uint8_t); +static void uplcom_cfg_set_break(struct ucom_softc *, uint8_t); +static int uplcom_pre_param(struct ucom_softc *, struct termios *); +static void uplcom_cfg_param(struct ucom_softc *, struct termios *); +static void uplcom_start_read(struct ucom_softc *); +static void uplcom_stop_read(struct ucom_softc *); +static void uplcom_start_write(struct ucom_softc *); +static void uplcom_stop_write(struct ucom_softc *); +static void uplcom_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void uplcom_poll(struct ucom_softc *ucom); + +static device_probe_t uplcom_probe; +static device_attach_t uplcom_attach; +static device_detach_t uplcom_detach; +static void uplcom_free_softc(struct uplcom_softc *); + +static usb_callback_t uplcom_intr_callback; +static usb_callback_t uplcom_write_callback; +static usb_callback_t uplcom_read_callback; + +static const struct usb_config uplcom_config_data[UPLCOM_N_TRANSFER] = { + + [UPLCOM_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UPLCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &uplcom_write_callback, + .if_index = 0, + }, + + [UPLCOM_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UPLCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uplcom_read_callback, + .if_index = 0, + }, + + [UPLCOM_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &uplcom_intr_callback, + .if_index = 1, + }, +}; + +static struct ucom_callback uplcom_callback = { + .ucom_cfg_get_status = &uplcom_cfg_get_status, + .ucom_cfg_set_dtr = &uplcom_cfg_set_dtr, + .ucom_cfg_set_rts = &uplcom_cfg_set_rts, + .ucom_cfg_set_break = &uplcom_cfg_set_break, + .ucom_cfg_param = &uplcom_cfg_param, + .ucom_pre_param = &uplcom_pre_param, + .ucom_start_read = &uplcom_start_read, + .ucom_stop_read = &uplcom_stop_read, + .ucom_start_write = &uplcom_start_write, + .ucom_stop_write = &uplcom_stop_write, + .ucom_poll = &uplcom_poll, + .ucom_free = &uplcom_free, +}; + +#define UPLCOM_DEV(v,p) \ + { USB_VENDOR(USB_VENDOR_##v), USB_PRODUCT(USB_PRODUCT_##v##_##p) } + +static const STRUCT_USB_HOST_ID uplcom_devs[] = { + UPLCOM_DEV(ACERP, S81), /* BenQ S81 phone */ + UPLCOM_DEV(ADLINK, ND6530), /* ADLINK ND-6530 USB-Serial */ + UPLCOM_DEV(ALCATEL, OT535), /* Alcatel One Touch 535/735 */ + UPLCOM_DEV(ALCOR, AU9720), /* Alcor AU9720 USB 2.0-RS232 */ + UPLCOM_DEV(ANCHOR, SERIAL), /* Anchor Serial adapter */ + UPLCOM_DEV(ATEN, UC232A), /* PLANEX USB-RS232 URS-03 */ + UPLCOM_DEV(BELKIN, F5U257), /* Belkin F5U257 USB to Serial */ + UPLCOM_DEV(COREGA, CGUSBRS232R), /* Corega CG-USBRS232R */ + UPLCOM_DEV(EPSON, CRESSI_EDY), /* Cressi Edy diving computer */ + UPLCOM_DEV(EPSON, N2ITION3), /* Zeagle N2iTion3 diving computer */ + UPLCOM_DEV(ELECOM, UCSGT), /* ELECOM UC-SGT Serial Adapter */ + UPLCOM_DEV(ELECOM, UCSGT0), /* ELECOM UC-SGT Serial Adapter */ + UPLCOM_DEV(HAL, IMR001), /* HAL Corporation Crossam2+USB */ + UPLCOM_DEV(HP, LD220), /* HP LD220 POS Display */ + UPLCOM_DEV(IODATA, USBRSAQ), /* I/O DATA USB-RSAQ */ + UPLCOM_DEV(IODATA, USBRSAQ5), /* I/O DATA USB-RSAQ5 */ + UPLCOM_DEV(ITEGNO, WM1080A), /* iTegno WM1080A GSM/GFPRS modem */ + UPLCOM_DEV(ITEGNO, WM2080A), /* iTegno WM2080A CDMA modem */ + UPLCOM_DEV(LEADTEK, 9531), /* Leadtek 9531 GPS */ + UPLCOM_DEV(MICROSOFT, 700WX), /* Microsoft Palm 700WX */ + UPLCOM_DEV(MOBILEACTION, MA620), /* Mobile Action MA-620 Infrared Adapter */ + UPLCOM_DEV(NETINDEX, WS002IN), /* Willcom W-S002IN */ + UPLCOM_DEV(NOKIA2, CA42), /* Nokia CA-42 cable */ + UPLCOM_DEV(OTI, DKU5), /* OTI DKU-5 cable */ + UPLCOM_DEV(PANASONIC, TYTP50P6S), /* Panasonic TY-TP50P6-S flat screen */ + UPLCOM_DEV(PLX, CA42), /* PLX CA-42 clone cable */ + UPLCOM_DEV(PROLIFIC, ALLTRONIX_GPRS), /* Alltronix ACM003U00 modem */ + UPLCOM_DEV(PROLIFIC, ALDIGA_AL11U), /* AlDiga AL-11U modem */ + UPLCOM_DEV(PROLIFIC, DCU11), /* DCU-11 Phone Cable */ + UPLCOM_DEV(PROLIFIC, HCR331), /* HCR331 Card Reader */ + UPLCOM_DEV(PROLIFIC, MICROMAX_610U), /* Micromax 610U modem */ + UPLCOM_DEV(PROLIFIC, MOTOROLA), /* Motorola cable */ + UPLCOM_DEV(PROLIFIC, PHAROS), /* Prolific Pharos */ + UPLCOM_DEV(PROLIFIC, PL2303), /* Generic adapter */ + UPLCOM_DEV(PROLIFIC, RSAQ2), /* I/O DATA USB-RSAQ2 */ + UPLCOM_DEV(PROLIFIC, RSAQ3), /* I/O DATA USB-RSAQ3 */ + UPLCOM_DEV(PROLIFIC, UIC_MSR206), /* UIC MSR206 Card Reader */ + UPLCOM_DEV(PROLIFIC2, PL2303), /* Prolific adapter */ + UPLCOM_DEV(RADIOSHACK, USBCABLE), /* Radio Shack USB Adapter */ + UPLCOM_DEV(RATOC, REXUSB60), /* RATOC REX-USB60 */ + UPLCOM_DEV(SAGEM, USBSERIAL), /* Sagem USB-Serial Controller */ + UPLCOM_DEV(SAMSUNG, I330), /* Samsung I330 phone cradle */ + UPLCOM_DEV(SANWA, KB_USB2), /* Sanwa KB-USB2 Multimeter cable */ + UPLCOM_DEV(SIEMENS3, EF81), /* Siemens EF81 */ + UPLCOM_DEV(SIEMENS3, SX1), /* Siemens SX1 */ + UPLCOM_DEV(SIEMENS3, X65), /* Siemens X65 */ + UPLCOM_DEV(SIEMENS3, X75), /* Siemens X75 */ + UPLCOM_DEV(SITECOM, SERIAL), /* Sitecom USB to Serial */ + UPLCOM_DEV(SMART, PL2303), /* SMART Technologies USB to Serial */ + UPLCOM_DEV(SONY, QN3), /* Sony QN3 phone cable */ + UPLCOM_DEV(SONYERICSSON, DATAPILOT), /* Sony Ericsson Datapilot */ + UPLCOM_DEV(SONYERICSSON, DCU10), /* Sony Ericsson DCU-10 Cable */ + UPLCOM_DEV(SOURCENEXT, KEIKAI8), /* SOURCENEXT KeikaiDenwa 8 */ + UPLCOM_DEV(SOURCENEXT, KEIKAI8_CHG), /* SOURCENEXT KeikaiDenwa 8 with charger */ + UPLCOM_DEV(SPEEDDRAGON, MS3303H), /* Speed Dragon USB-Serial */ + UPLCOM_DEV(SYNTECH, CPT8001C), /* Syntech CPT-8001C Barcode scanner */ + UPLCOM_DEV(TDK, UHA6400), /* TDK USB-PHS Adapter UHA6400 */ + UPLCOM_DEV(TDK, UPA9664), /* TDK USB-PHS Adapter UPA9664 */ + UPLCOM_DEV(TRIPPLITE, U209), /* Tripp-Lite U209-000-R USB to Serial */ + UPLCOM_DEV(YCCABLE, PL2303), /* YC Cable USB-Serial */ +}; +#undef UPLCOM_DEV + +static device_method_t uplcom_methods[] = { + DEVMETHOD(device_probe, uplcom_probe), + DEVMETHOD(device_attach, uplcom_attach), + DEVMETHOD(device_detach, uplcom_detach), + DEVMETHOD_END +}; + +static devclass_t uplcom_devclass; + +static driver_t uplcom_driver = { + .name = "uplcom", + .methods = uplcom_methods, + .size = sizeof(struct uplcom_softc), +}; + +DRIVER_MODULE(uplcom, uhub, uplcom_driver, uplcom_devclass, NULL, 0); +MODULE_DEPEND(uplcom, ucom, 1, 1, 1); +MODULE_DEPEND(uplcom, usb, 1, 1, 1); +MODULE_VERSION(uplcom, UPLCOM_MODVER); +USB_PNP_HOST_INFO(uplcom_devs); + +static int +uplcom_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + DPRINTFN(11, "\n"); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UPLCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UPLCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uplcom_devs, sizeof(uplcom_devs), uaa)); +} + +static int +uplcom_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uplcom_softc *sc = device_get_softc(dev); + struct usb_interface *iface; + struct usb_interface_descriptor *id; + struct usb_device_descriptor *dd; + int error; + + DPRINTFN(11, "\n"); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uplcom", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + DPRINTF("sc = %p\n", sc); + + sc->sc_udev = uaa->device; + + /* Determine the chip type. This algorithm is taken from Linux. */ + dd = usbd_get_device_descriptor(sc->sc_udev); + if (dd->bDeviceClass == 0x02) + sc->sc_chiptype = TYPE_PL2303; + else if (dd->bMaxPacketSize == 0x40) + sc->sc_chiptype = TYPE_PL2303HX; + else + sc->sc_chiptype = TYPE_PL2303; + + DPRINTF("chiptype: %s\n", + (sc->sc_chiptype == TYPE_PL2303HX) ? + "2303X" : "2303"); + + /* + * USB-RSAQ1 has two interface + * + * USB-RSAQ1 | USB-RSAQ2 + * -----------------+----------------- + * Interface 0 |Interface 0 + * Interrupt(0x81) | Interrupt(0x81) + * -----------------+ BulkIN(0x02) + * Interface 1 | BulkOUT(0x83) + * BulkIN(0x02) | + * BulkOUT(0x83) | + */ + + sc->sc_ctrl_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index[1] = UPLCOM_IFACE_INDEX; + + iface = usbd_get_iface(uaa->device, UPLCOM_SECOND_IFACE_INDEX); + if (iface) { + id = usbd_get_interface_descriptor(iface); + if (id == NULL) { + device_printf(dev, "no interface descriptor (2)\n"); + goto detach; + } + sc->sc_data_iface_no = id->bInterfaceNumber; + sc->sc_iface_index[0] = UPLCOM_SECOND_IFACE_INDEX; + usbd_set_parent_iface(uaa->device, + UPLCOM_SECOND_IFACE_INDEX, uaa->info.bIfaceIndex); + } else { + sc->sc_data_iface_no = sc->sc_ctrl_iface_no; + sc->sc_iface_index[0] = UPLCOM_IFACE_INDEX; + } + + error = usbd_transfer_setup(uaa->device, + sc->sc_iface_index, sc->sc_xfer, uplcom_config_data, + UPLCOM_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + DPRINTF("one or more missing USB endpoints, " + "error=%s\n", usbd_errstr(error)); + goto detach; + } + error = uplcom_reset(sc, uaa->device); + if (error) { + device_printf(dev, "reset failed, error=%s\n", + usbd_errstr(error)); + goto detach; + } + + if (sc->sc_chiptype != TYPE_PL2303HX) { + /* HX variants seem to lock up after a clear stall request. */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + } else { + if (uplcom_pl2303_do(sc->sc_udev, UT_WRITE_VENDOR_DEVICE, + UPLCOM_SET_REQUEST, 8, 0, 0) || + uplcom_pl2303_do(sc->sc_udev, UT_WRITE_VENDOR_DEVICE, + UPLCOM_SET_REQUEST, 9, 0, 0)) { + goto detach; + } + } + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uplcom_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + /* + * do the initialization during attach so that the system does not + * sleep during open: + */ + if (uplcom_pl2303_init(uaa->device, sc->sc_chiptype)) { + device_printf(dev, "init failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + uplcom_detach(dev); + return (ENXIO); +} + +static int +uplcom_detach(device_t dev) +{ + struct uplcom_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UPLCOM_N_TRANSFER); + + device_claim_softc(dev); + + uplcom_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(uplcom); + +static void +uplcom_free_softc(struct uplcom_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +uplcom_free(struct ucom_softc *ucom) +{ + uplcom_free_softc(ucom->sc_parent); +} + +static usb_error_t +uplcom_reset(struct uplcom_softc *sc, struct usb_device *udev) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + return (usbd_do_request(udev, NULL, &req, NULL)); +} + +static usb_error_t +uplcom_pl2303_do(struct usb_device *udev, uint8_t req_type, uint8_t request, + uint16_t value, uint16_t index, uint16_t length) +{ + struct usb_device_request req; + usb_error_t err; + uint8_t buf[4]; + + req.bmRequestType = req_type; + req.bRequest = request; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, length); + + err = usbd_do_request(udev, NULL, &req, buf); + if (err) { + DPRINTF("error=%s\n", usbd_errstr(err)); + return (1); + } + return (0); +} + +static int +uplcom_pl2303_init(struct usb_device *udev, uint8_t chiptype) +{ + int err; + + if (uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) + || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 0, 0) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 1) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) + || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 1, 0) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 1) + || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0, 1, 0) + || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 1, 0, 0)) + return (EIO); + + if (chiptype == TYPE_PL2303HX) + err = uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x44, 0); + else + err = uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x24, 0); + if (err) + return (EIO); + + return (0); +} + +static void +uplcom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uplcom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uplcom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t temp; + + DPRINTF("onoff = %d\n", onoff); + + temp = (onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, temp); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static const uint32_t uplcom_rates[] = { + 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, + 19200, 28800, 38400, 57600, 115200, + /* + * Higher speeds are probably possible. PL2303X supports up to + * 6Mb and can set any rate + */ + 230400, 460800, 614400, 921600, 1228800 +}; + +#define N_UPLCOM_RATES nitems(uplcom_rates) + +static int +uplcom_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uplcom_softc *sc = ucom->sc_parent; + uint8_t i; + + DPRINTF("\n"); + + /** + * Check requested baud rate. + * + * The PL2303 can only set specific baud rates, up to 1228800 baud. + * The PL2303X can set any baud rate up to 6Mb. + * The PL2303HX rev. D can set any baud rate up to 12Mb. + * + * XXX: We currently cannot identify the PL2303HX rev. D, so treat + * it the same as the PL2303X. + */ + if (sc->sc_chiptype != TYPE_PL2303HX) { + for (i = 0; i < N_UPLCOM_RATES; i++) { + if (uplcom_rates[i] == t->c_ospeed) + return (0); + } + } else { + if (t->c_ospeed <= 6000000) + return (0); + } + + DPRINTF("uplcom_param: bad baud rate (%d)\n", t->c_ospeed); + return (EIO); +} + +static void +uplcom_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb_cdc_line_state ls; + struct usb_device_request req; + + DPRINTF("sc = %p\n", sc); + + memset(&ls, 0, sizeof(ls)); + + USETDW(ls.dwDTERate, t->c_ospeed); + + if (t->c_cflag & CSTOPB) { + ls.bCharFormat = UCDC_STOP_BIT_2; + } else { + ls.bCharFormat = UCDC_STOP_BIT_1; + } + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) { + ls.bParityType = UCDC_PARITY_ODD; + } else { + ls.bParityType = UCDC_PARITY_EVEN; + } + } else { + ls.bParityType = UCDC_PARITY_NONE; + } + + switch (t->c_cflag & CSIZE) { + case CS5: + ls.bDataBits = 5; + break; + case CS6: + ls.bDataBits = 6; + break; + case CS7: + ls.bDataBits = 7; + break; + case CS8: + ls.bDataBits = 8; + break; + } + + DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n", + UGETDW(ls.dwDTERate), ls.bCharFormat, + ls.bParityType, ls.bDataBits); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_LINE_CODING; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, UCDC_LINE_STATE_LENGTH); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, &ls, 0, 1000); + + if (t->c_cflag & CRTSCTS) { + + DPRINTF("crtscts = on\n"); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + if (sc->sc_chiptype == TYPE_PL2303HX) + USETW(req.wIndex, UPLCOM_SET_CRTSCTS_PL2303X); + else + USETW(req.wIndex, UPLCOM_SET_CRTSCTS); + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + } else { + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + } +} + +static void +uplcom_start_read(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint */ + usbd_transfer_start(sc->sc_xfer[UPLCOM_INTR_DT_RD]); + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UPLCOM_BULK_DT_RD]); +} + +static void +uplcom_stop_read(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint */ + usbd_transfer_stop(sc->sc_xfer[UPLCOM_INTR_DT_RD]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UPLCOM_BULK_DT_RD]); +} + +static void +uplcom_start_write(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UPLCOM_BULK_DT_WR]); +} + +static void +uplcom_stop_write(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UPLCOM_BULK_DT_WR]); +} + +static void +uplcom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + /* XXX Note: sc_lsr is always zero */ + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +uplcom_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uplcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[9]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("actlen = %u\n", actlen); + + if (actlen >= 9) { + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, sizeof(buf)); + + DPRINTF("status = 0x%02x\n", buf[8]); + + sc->sc_lsr = 0; + sc->sc_msr = 0; + + if (buf[8] & RSAQ_STATUS_CTS) { + sc->sc_msr |= SER_CTS; + } + if (buf[8] & RSAQ_STATUS_DSR) { + sc->sc_msr |= SER_DSR; + } + if (buf[8] & RSAQ_STATUS_DCD) { + sc->sc_msr |= SER_DCD; + } + ucom_status_change(&sc->sc_ucom); + } + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uplcom_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uplcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UPLCOM_BULK_BUF_SIZE, &actlen)) { + + DPRINTF("actlen = %d\n", actlen); + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uplcom_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uplcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uplcom_poll(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UPLCOM_N_TRANSFER); +} diff --git a/freebsd/sys/dev/usb/serial/usb_serial.c b/freebsd/sys/dev/usb/serial/usb_serial.c new file mode 100644 index 0000000..f425a16 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/usb_serial.c @@ -0,0 +1,1719 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $NetBSD: ucom.c,v 1.40 2001/11/13 06:24:54 lukem Exp $ */ + +/*- + * Copyright (c) 2001-2003, 2005, 2008 + * Shunsuke Akiyama <akiy...@jp.freebsd.org>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lenn...@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> +#include <sys/cons.h> + +#include <dev/uart/uart_ppstypes.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> + +#define USB_DEBUG_VAR ucom_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_busdma.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#include <rtems/bsd/local/opt_gdb.h> + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom"); + +static int ucom_pps_mode; + +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, pps_mode, CTLFLAG_RWTUN, + &ucom_pps_mode, 0, + "pulse capture mode: 0/1/2=disabled/CTS/DCD; add 0x10 to invert"); + +#ifdef USB_DEBUG +static int ucom_debug = 0; + +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RWTUN, + &ucom_debug, 0, "ucom debug level"); +#endif + +#define UCOM_CONS_BUFSIZE 1024 + +static uint8_t ucom_cons_rx_buf[UCOM_CONS_BUFSIZE]; +static uint8_t ucom_cons_tx_buf[UCOM_CONS_BUFSIZE]; + +static unsigned int ucom_cons_rx_low = 0; +static unsigned int ucom_cons_rx_high = 0; + +static unsigned int ucom_cons_tx_low = 0; +static unsigned int ucom_cons_tx_high = 0; + +static int ucom_cons_unit = -1; +static int ucom_cons_subunit = 0; +static int ucom_cons_baud = 9600; +static struct ucom_softc *ucom_cons_softc = NULL; + +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_unit, CTLFLAG_RWTUN, + &ucom_cons_unit, 0, "console unit number"); +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_subunit, CTLFLAG_RWTUN, + &ucom_cons_subunit, 0, "console subunit number"); +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_baud, CTLFLAG_RWTUN, + &ucom_cons_baud, 0, "console baud rate"); + +static usb_proc_callback_t ucom_cfg_start_transfers; +static usb_proc_callback_t ucom_cfg_open; +static usb_proc_callback_t ucom_cfg_close; +static usb_proc_callback_t ucom_cfg_line_state; +static usb_proc_callback_t ucom_cfg_status_change; +static usb_proc_callback_t ucom_cfg_param; + +static int ucom_unit_alloc(void); +static void ucom_unit_free(int); +static int ucom_attach_tty(struct ucom_super_softc *, struct ucom_softc *); +static void ucom_detach_tty(struct ucom_super_softc *, struct ucom_softc *); +static void ucom_queue_command(struct ucom_softc *, + usb_proc_callback_t *, struct termios *pt, + struct usb_proc_msg *t0, struct usb_proc_msg *t1); +static void ucom_shutdown(struct ucom_softc *); +static void ucom_ring(struct ucom_softc *, uint8_t); +static void ucom_break(struct ucom_softc *, uint8_t); +static void ucom_dtr(struct ucom_softc *, uint8_t); +static void ucom_rts(struct ucom_softc *, uint8_t); + +static tsw_open_t ucom_open; +static tsw_close_t ucom_close; +static tsw_ioctl_t ucom_ioctl; +static tsw_modem_t ucom_modem; +static tsw_param_t ucom_param; +static tsw_outwakeup_t ucom_outwakeup; +static tsw_inwakeup_t ucom_inwakeup; +static tsw_free_t ucom_free; + +static struct ttydevsw ucom_class = { + .tsw_flags = TF_INITLOCK | TF_CALLOUT, + .tsw_open = ucom_open, + .tsw_close = ucom_close, + .tsw_outwakeup = ucom_outwakeup, + .tsw_inwakeup = ucom_inwakeup, + .tsw_ioctl = ucom_ioctl, + .tsw_param = ucom_param, + .tsw_modem = ucom_modem, + .tsw_free = ucom_free, +}; + +MODULE_DEPEND(ucom, usb, 1, 1, 1); +MODULE_VERSION(ucom, 1); + +#define UCOM_UNIT_MAX 128 /* maximum number of units */ +#define UCOM_TTY_PREFIX "U" + +static struct unrhdr *ucom_unrhdr; +static struct mtx ucom_mtx; +static int ucom_close_refs; + +static void +ucom_init(void *arg) +{ + DPRINTF("\n"); + ucom_unrhdr = new_unrhdr(0, UCOM_UNIT_MAX - 1, NULL); + mtx_init(&ucom_mtx, "UCOM MTX", NULL, MTX_DEF); +} +SYSINIT(ucom_init, SI_SUB_KLD - 1, SI_ORDER_ANY, ucom_init, NULL); + +static void +ucom_uninit(void *arg) +{ + struct unrhdr *hdr; + hdr = ucom_unrhdr; + ucom_unrhdr = NULL; + + DPRINTF("\n"); + + if (hdr != NULL) + delete_unrhdr(hdr); + + mtx_destroy(&ucom_mtx); +} +SYSUNINIT(ucom_uninit, SI_SUB_KLD - 3, SI_ORDER_ANY, ucom_uninit, NULL); + +/* + * Mark a unit number (the X in cuaUX) as in use. + * + * Note that devices using a different naming scheme (see ucom_tty_name() + * callback) still use this unit allocation. + */ +static int +ucom_unit_alloc(void) +{ + int unit; + + /* sanity checks */ + if (ucom_unrhdr == NULL) { + DPRINTF("ucom_unrhdr is NULL\n"); + return (-1); + } + unit = alloc_unr(ucom_unrhdr); + DPRINTF("unit %d is allocated\n", unit); + return (unit); +} + +/* + * Mark the unit number as not in use. + */ +static void +ucom_unit_free(int unit) +{ + /* sanity checks */ + if (unit < 0 || unit >= UCOM_UNIT_MAX || ucom_unrhdr == NULL) { + DPRINTF("cannot free unit number\n"); + return; + } + DPRINTF("unit %d is freed\n", unit); + free_unr(ucom_unrhdr, unit); +} + +/* + * Setup a group of one or more serial ports. + * + * The mutex pointed to by "mtx" is applied before all + * callbacks are called back. Also "mtx" must be applied + * before calling into the ucom-layer! + */ +int +ucom_attach(struct ucom_super_softc *ssc, struct ucom_softc *sc, + int subunits, void *parent, + const struct ucom_callback *callback, struct mtx *mtx) +{ + int subunit; + int error = 0; + + if ((sc == NULL) || + (subunits <= 0) || + (callback == NULL) || + (mtx == NULL)) { + return (EINVAL); + } + + /* allocate a uniq unit number */ + ssc->sc_unit = ucom_unit_alloc(); + if (ssc->sc_unit == -1) + return (ENOMEM); + + /* generate TTY name string */ + snprintf(ssc->sc_ttyname, sizeof(ssc->sc_ttyname), + UCOM_TTY_PREFIX "%d", ssc->sc_unit); + + /* create USB request handling process */ + error = usb_proc_create(&ssc->sc_tq, mtx, "ucom", USB_PRI_MED); + if (error) { + ucom_unit_free(ssc->sc_unit); + return (error); + } + ssc->sc_subunits = subunits; + ssc->sc_flag = UCOM_FLAG_ATTACHED | + UCOM_FLAG_FREE_UNIT; + + if (callback->ucom_free == NULL) + ssc->sc_flag |= UCOM_FLAG_WAIT_REFS; + + /* increment reference count */ + ucom_ref(ssc); + + for (subunit = 0; subunit < ssc->sc_subunits; subunit++) { + sc[subunit].sc_subunit = subunit; + sc[subunit].sc_super = ssc; + sc[subunit].sc_mtx = mtx; + sc[subunit].sc_parent = parent; + sc[subunit].sc_callback = callback; + + error = ucom_attach_tty(ssc, &sc[subunit]); + if (error) { + ucom_detach(ssc, &sc[0]); + return (error); + } + /* increment reference count */ + ucom_ref(ssc); + + /* set subunit attached */ + sc[subunit].sc_flag |= UCOM_FLAG_ATTACHED; + } + + DPRINTF("tp = %p, unit = %d, subunits = %d\n", + sc->sc_tty, ssc->sc_unit, ssc->sc_subunits); + + return (0); +} + +/* + * The following function will do nothing if the structure pointed to + * by "ssc" and "sc" is zero or has already been detached. + */ +void +ucom_detach(struct ucom_super_softc *ssc, struct ucom_softc *sc) +{ + int subunit; + + if (!(ssc->sc_flag & UCOM_FLAG_ATTACHED)) + return; /* not initialized */ + + if (ssc->sc_sysctl_ttyname != NULL) { + sysctl_remove_oid(ssc->sc_sysctl_ttyname, 1, 0); + ssc->sc_sysctl_ttyname = NULL; + } + + if (ssc->sc_sysctl_ttyports != NULL) { + sysctl_remove_oid(ssc->sc_sysctl_ttyports, 1, 0); + ssc->sc_sysctl_ttyports = NULL; + } + + usb_proc_drain(&ssc->sc_tq); + + for (subunit = 0; subunit < ssc->sc_subunits; subunit++) { + if (sc[subunit].sc_flag & UCOM_FLAG_ATTACHED) { + + ucom_detach_tty(ssc, &sc[subunit]); + + /* avoid duplicate detach */ + sc[subunit].sc_flag &= ~UCOM_FLAG_ATTACHED; + } + } + usb_proc_free(&ssc->sc_tq); + + ucom_unref(ssc); + + if (ssc->sc_flag & UCOM_FLAG_WAIT_REFS) + ucom_drain(ssc); + + /* make sure we don't detach twice */ + ssc->sc_flag &= ~UCOM_FLAG_ATTACHED; +} + +void +ucom_drain(struct ucom_super_softc *ssc) +{ + mtx_lock(&ucom_mtx); + while (ssc->sc_refs > 0) { + printf("ucom: Waiting for a TTY device to close.\n"); + usb_pause_mtx(&ucom_mtx, hz); + } + mtx_unlock(&ucom_mtx); +} + +void +ucom_drain_all(void *arg) +{ + mtx_lock(&ucom_mtx); + while (ucom_close_refs > 0) { + printf("ucom: Waiting for all detached TTY " + "devices to have open fds closed.\n"); + usb_pause_mtx(&ucom_mtx, hz); + } + mtx_unlock(&ucom_mtx); +} + +static int +ucom_attach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc) +{ + struct tty *tp; + char buf[32]; /* temporary TTY device name buffer */ + + tp = tty_alloc_mutex(&ucom_class, sc, sc->sc_mtx); + if (tp == NULL) + return (ENOMEM); + + /* Check if the client has a custom TTY name */ + buf[0] = '\0'; + if (sc->sc_callback->ucom_tty_name) { + sc->sc_callback->ucom_tty_name(sc, buf, + sizeof(buf), ssc->sc_unit, sc->sc_subunit); + } + if (buf[0] == 0) { + /* Use default TTY name */ + if (ssc->sc_subunits > 1) { + /* multiple modems in one */ + snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u.%u", + ssc->sc_unit, sc->sc_subunit); + } else { + /* single modem */ + snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u", + ssc->sc_unit); + } + } + tty_makedev(tp, NULL, "%s", buf); + + sc->sc_tty = tp; + + sc->sc_pps.ppscap = PPS_CAPTUREBOTH; + sc->sc_pps.driver_abi = PPS_ABI_VERSION; + sc->sc_pps.driver_mtx = sc->sc_mtx; + pps_init_abi(&sc->sc_pps); + + DPRINTF("ttycreate: %s\n", buf); + + /* Check if this device should be a console */ + if ((ucom_cons_softc == NULL) && + (ssc->sc_unit == ucom_cons_unit) && + (sc->sc_subunit == ucom_cons_subunit)) { + + DPRINTF("unit %d subunit %d is console", + ssc->sc_unit, sc->sc_subunit); + + ucom_cons_softc = sc; + + tty_init_console(tp, ucom_cons_baud); + + UCOM_MTX_LOCK(ucom_cons_softc); + ucom_cons_rx_low = 0; + ucom_cons_rx_high = 0; + ucom_cons_tx_low = 0; + ucom_cons_tx_high = 0; + sc->sc_flag |= UCOM_FLAG_CONSOLE; + ucom_open(ucom_cons_softc->sc_tty); + ucom_param(ucom_cons_softc->sc_tty, &tp->t_termios_init_in); + UCOM_MTX_UNLOCK(ucom_cons_softc); + } + + return (0); +} + +static void +ucom_detach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc) +{ + struct tty *tp = sc->sc_tty; + + DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty); + + if (sc->sc_flag & UCOM_FLAG_CONSOLE) { + UCOM_MTX_LOCK(ucom_cons_softc); + ucom_close(ucom_cons_softc->sc_tty); + sc->sc_flag &= ~UCOM_FLAG_CONSOLE; + UCOM_MTX_UNLOCK(ucom_cons_softc); + ucom_cons_softc = NULL; + } + + /* the config thread has been stopped when we get here */ + + UCOM_MTX_LOCK(sc); + sc->sc_flag |= UCOM_FLAG_GONE; + sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_LL_READY); + UCOM_MTX_UNLOCK(sc); + + if (tp) { + mtx_lock(&ucom_mtx); + ucom_close_refs++; + mtx_unlock(&ucom_mtx); + + tty_lock(tp); + + ucom_close(tp); /* close, if any */ + + tty_rel_gone(tp); + + UCOM_MTX_LOCK(sc); + /* + * make sure that read and write transfers are stopped + */ + if (sc->sc_callback->ucom_stop_read) + (sc->sc_callback->ucom_stop_read) (sc); + if (sc->sc_callback->ucom_stop_write) + (sc->sc_callback->ucom_stop_write) (sc); + UCOM_MTX_UNLOCK(sc); + } +} + +void +ucom_set_pnpinfo_usb(struct ucom_super_softc *ssc, device_t dev) +{ + char buf[64]; + uint8_t iface_index; + struct usb_attach_arg *uaa; + + snprintf(buf, sizeof(buf), "ttyname=" UCOM_TTY_PREFIX + "%d ttyports=%d", ssc->sc_unit, ssc->sc_subunits); + + /* Store the PNP info in the first interface for the device */ + uaa = device_get_ivars(dev); + iface_index = uaa->info.bIfaceIndex; + + if (usbd_set_pnpinfo(uaa->device, iface_index, buf) != 0) + device_printf(dev, "Could not set PNP info\n"); + + /* + * The following information is also replicated in the PNP-info + * string which is registered above: + */ + if (ssc->sc_sysctl_ttyname == NULL) { + ssc->sc_sysctl_ttyname = SYSCTL_ADD_STRING(NULL, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "ttyname", CTLFLAG_RD, ssc->sc_ttyname, 0, + "TTY device basename"); + } + if (ssc->sc_sysctl_ttyports == NULL) { + ssc->sc_sysctl_ttyports = SYSCTL_ADD_INT(NULL, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "ttyports", CTLFLAG_RD, + NULL, ssc->sc_subunits, "Number of ports"); + } +} + +static void +ucom_queue_command(struct ucom_softc *sc, + usb_proc_callback_t *fn, struct termios *pt, + struct usb_proc_msg *t0, struct usb_proc_msg *t1) +{ + struct ucom_super_softc *ssc = sc->sc_super; + struct ucom_param_task *task; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (usb_proc_is_gone(&ssc->sc_tq)) { + DPRINTF("proc is gone\n"); + return; /* nothing to do */ + } + /* + * NOTE: The task cannot get executed before we drop the + * "sc_mtx" mutex. It is safe to update fields in the message + * structure after that the message got queued. + */ + task = (struct ucom_param_task *) + usb_proc_msignal(&ssc->sc_tq, t0, t1); + + /* Setup callback and softc pointers */ + task->hdr.pm_callback = fn; + task->sc = sc; + + /* + * Make a copy of the termios. This field is only present if + * the "pt" field is not NULL. + */ + if (pt != NULL) + task->termios_copy = *pt; + + /* + * Closing the device should be synchronous. + */ + if (fn == ucom_cfg_close) + usb_proc_mwait(&ssc->sc_tq, t0, t1); + + /* + * In case of multiple configure requests, + * keep track of the last one! + */ + if (fn == ucom_cfg_start_transfers) + sc->sc_last_start_xfer = &task->hdr; +} + +static void +ucom_shutdown(struct ucom_softc *sc) +{ + struct tty *tp = sc->sc_tty; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + DPRINTF("\n"); + + /* + * Hang up if necessary: + */ + if (tp->t_termios.c_cflag & HUPCL) { + ucom_modem(tp, 0, SER_DTR); + } +} + +/* + * Return values: + * 0: normal + * else: taskqueue is draining or gone + */ +uint8_t +ucom_cfg_is_gone(struct ucom_softc *sc) +{ + struct ucom_super_softc *ssc = sc->sc_super; + + return (usb_proc_is_gone(&ssc->sc_tq)); +} + +static void +ucom_cfg_start_transfers(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + /* TTY device closed */ + return; + } + + if (_task == sc->sc_last_start_xfer) + sc->sc_flag |= UCOM_FLAG_GP_DATA; + + if (sc->sc_callback->ucom_start_read) { + (sc->sc_callback->ucom_start_read) (sc); + } + if (sc->sc_callback->ucom_start_write) { + (sc->sc_callback->ucom_start_write) (sc); + } +} + +static void +ucom_start_transfers(struct ucom_softc *sc) +{ + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + /* + * Make sure that data transfers are started in both + * directions: + */ + if (sc->sc_callback->ucom_start_read) { + (sc->sc_callback->ucom_start_read) (sc); + } + if (sc->sc_callback->ucom_start_write) { + (sc->sc_callback->ucom_start_write) (sc); + } +} + +static void +ucom_cfg_open(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + + DPRINTF("\n"); + + if (sc->sc_flag & UCOM_FLAG_LL_READY) { + + /* already opened */ + + } else { + + sc->sc_flag |= UCOM_FLAG_LL_READY; + + if (sc->sc_callback->ucom_cfg_open) { + (sc->sc_callback->ucom_cfg_open) (sc); + + /* wait a little */ + usb_pause_mtx(sc->sc_mtx, hz / 10); + } + } +} + +static int +ucom_open(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + int error; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (sc->sc_flag & UCOM_FLAG_GONE) { + return (ENXIO); + } + if (sc->sc_flag & UCOM_FLAG_HL_READY) { + /* already opened */ + return (0); + } + DPRINTF("tp = %p\n", tp); + + if (sc->sc_callback->ucom_pre_open) { + /* + * give the lower layer a chance to disallow TTY open, for + * example if the device is not present: + */ + error = (sc->sc_callback->ucom_pre_open) (sc); + if (error) { + return (error); + } + } + sc->sc_flag |= UCOM_FLAG_HL_READY; + + /* Disable transfers */ + sc->sc_flag &= ~UCOM_FLAG_GP_DATA; + + sc->sc_lsr = 0; + sc->sc_msr = 0; + sc->sc_mcr = 0; + + /* reset programmed line state */ + sc->sc_pls_curr = 0; + sc->sc_pls_set = 0; + sc->sc_pls_clr = 0; + + /* reset jitter buffer */ + sc->sc_jitterbuf_in = 0; + sc->sc_jitterbuf_out = 0; + + ucom_queue_command(sc, ucom_cfg_open, NULL, + &sc->sc_open_task[0].hdr, + &sc->sc_open_task[1].hdr); + + /* Queue transfer enable command last */ + ucom_queue_command(sc, ucom_cfg_start_transfers, NULL, + &sc->sc_start_task[0].hdr, + &sc->sc_start_task[1].hdr); + + ucom_modem(tp, SER_DTR | SER_RTS, 0); + + ucom_ring(sc, 0); + + ucom_break(sc, 0); + + ucom_status_change(sc); + + return (0); +} + +static void +ucom_cfg_close(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + + DPRINTF("\n"); + + if (sc->sc_flag & UCOM_FLAG_LL_READY) { + sc->sc_flag &= ~UCOM_FLAG_LL_READY; + if (sc->sc_callback->ucom_cfg_close) + (sc->sc_callback->ucom_cfg_close) (sc); + } else { + /* already closed */ + } +} + +static void +ucom_close(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + DPRINTF("tp=%p\n", tp); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + DPRINTF("tp=%p already closed\n", tp); + return; + } + ucom_shutdown(sc); + + ucom_queue_command(sc, ucom_cfg_close, NULL, + &sc->sc_close_task[0].hdr, + &sc->sc_close_task[1].hdr); + + sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW); + + if (sc->sc_callback->ucom_stop_read) { + (sc->sc_callback->ucom_stop_read) (sc); + } +} + +static void +ucom_inwakeup(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + uint16_t pos; + + if (sc == NULL) + return; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + DPRINTF("tp=%p\n", tp); + + if (ttydisc_can_bypass(tp) != 0 || + (sc->sc_flag & UCOM_FLAG_HL_READY) == 0 || + (sc->sc_flag & UCOM_FLAG_INWAKEUP) != 0) { + return; + } + + /* prevent recursion */ + sc->sc_flag |= UCOM_FLAG_INWAKEUP; + + pos = sc->sc_jitterbuf_out; + + while (sc->sc_jitterbuf_in != pos) { + int c; + + c = (char)sc->sc_jitterbuf[pos]; + + if (ttydisc_rint(tp, c, 0) == -1) + break; + pos++; + if (pos >= UCOM_JITTERBUF_SIZE) + pos -= UCOM_JITTERBUF_SIZE; + } + + sc->sc_jitterbuf_out = pos; + + /* clear RTS in async fashion */ + if ((sc->sc_jitterbuf_in == pos) && + (sc->sc_flag & UCOM_FLAG_RTS_IFLOW)) + ucom_rts(sc, 0); + + sc->sc_flag &= ~UCOM_FLAG_INWAKEUP; +} + +static int +ucom_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) +{ + struct ucom_softc *sc = tty_softc(tp); + int error; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return (EIO); + } + DPRINTF("cmd = 0x%08lx\n", cmd); + + switch (cmd) { +#if 0 + case TIOCSRING: + ucom_ring(sc, 1); + error = 0; + break; + case TIOCCRING: + ucom_ring(sc, 0); + error = 0; + break; +#endif + case TIOCSBRK: + ucom_break(sc, 1); + error = 0; + break; + case TIOCCBRK: + ucom_break(sc, 0); + error = 0; + break; + default: + if (sc->sc_callback->ucom_ioctl) { + error = (sc->sc_callback->ucom_ioctl) + (sc, cmd, data, 0, td); + } else { + error = ENOIOCTL; + } + if (error == ENOIOCTL) + error = pps_ioctl(cmd, data, &sc->sc_pps); + break; + } + return (error); +} + +static int +ucom_modem(struct tty *tp, int sigon, int sigoff) +{ + struct ucom_softc *sc = tty_softc(tp); + uint8_t onoff; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return (0); + } + if ((sigon == 0) && (sigoff == 0)) { + + if (sc->sc_mcr & SER_DTR) { + sigon |= SER_DTR; + } + if (sc->sc_mcr & SER_RTS) { + sigon |= SER_RTS; + } + if (sc->sc_msr & SER_CTS) { + sigon |= SER_CTS; + } + if (sc->sc_msr & SER_DCD) { + sigon |= SER_DCD; + } + if (sc->sc_msr & SER_DSR) { + sigon |= SER_DSR; + } + if (sc->sc_msr & SER_RI) { + sigon |= SER_RI; + } + return (sigon); + } + if (sigon & SER_DTR) { + sc->sc_mcr |= SER_DTR; + } + if (sigoff & SER_DTR) { + sc->sc_mcr &= ~SER_DTR; + } + if (sigon & SER_RTS) { + sc->sc_mcr |= SER_RTS; + } + if (sigoff & SER_RTS) { + sc->sc_mcr &= ~SER_RTS; + } + onoff = (sc->sc_mcr & SER_DTR) ? 1 : 0; + ucom_dtr(sc, onoff); + + onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0; + ucom_rts(sc, onoff); + + return (0); +} + +static void +ucom_cfg_line_state(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + uint8_t notch_bits; + uint8_t any_bits; + uint8_t prev_value; + uint8_t last_value; + uint8_t mask; + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + + mask = 0; + /* compute callback mask */ + if (sc->sc_callback->ucom_cfg_set_dtr) + mask |= UCOM_LS_DTR; + if (sc->sc_callback->ucom_cfg_set_rts) + mask |= UCOM_LS_RTS; + if (sc->sc_callback->ucom_cfg_set_break) + mask |= UCOM_LS_BREAK; + if (sc->sc_callback->ucom_cfg_set_ring) + mask |= UCOM_LS_RING; + + /* compute the bits we are to program */ + notch_bits = (sc->sc_pls_set & sc->sc_pls_clr) & mask; + any_bits = (sc->sc_pls_set | sc->sc_pls_clr) & mask; + prev_value = sc->sc_pls_curr ^ notch_bits; + last_value = sc->sc_pls_curr; + + /* reset programmed line state */ + sc->sc_pls_curr = 0; + sc->sc_pls_set = 0; + sc->sc_pls_clr = 0; + + /* ensure that we don't lose any levels */ + if (notch_bits & UCOM_LS_DTR) + sc->sc_callback->ucom_cfg_set_dtr(sc, + (prev_value & UCOM_LS_DTR) ? 1 : 0); + if (notch_bits & UCOM_LS_RTS) + sc->sc_callback->ucom_cfg_set_rts(sc, + (prev_value & UCOM_LS_RTS) ? 1 : 0); + if (notch_bits & UCOM_LS_BREAK) + sc->sc_callback->ucom_cfg_set_break(sc, + (prev_value & UCOM_LS_BREAK) ? 1 : 0); + if (notch_bits & UCOM_LS_RING) + sc->sc_callback->ucom_cfg_set_ring(sc, + (prev_value & UCOM_LS_RING) ? 1 : 0); + + /* set last value */ + if (any_bits & UCOM_LS_DTR) + sc->sc_callback->ucom_cfg_set_dtr(sc, + (last_value & UCOM_LS_DTR) ? 1 : 0); + if (any_bits & UCOM_LS_RTS) + sc->sc_callback->ucom_cfg_set_rts(sc, + (last_value & UCOM_LS_RTS) ? 1 : 0); + if (any_bits & UCOM_LS_BREAK) + sc->sc_callback->ucom_cfg_set_break(sc, + (last_value & UCOM_LS_BREAK) ? 1 : 0); + if (any_bits & UCOM_LS_RING) + sc->sc_callback->ucom_cfg_set_ring(sc, + (last_value & UCOM_LS_RING) ? 1 : 0); +} + +static void +ucom_line_state(struct ucom_softc *sc, + uint8_t set_bits, uint8_t clear_bits) +{ + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + + DPRINTF("on=0x%02x, off=0x%02x\n", set_bits, clear_bits); + + /* update current programmed line state */ + sc->sc_pls_curr |= set_bits; + sc->sc_pls_curr &= ~clear_bits; + sc->sc_pls_set |= set_bits; + sc->sc_pls_clr |= clear_bits; + + /* defer driver programming */ + ucom_queue_command(sc, ucom_cfg_line_state, NULL, + &sc->sc_line_state_task[0].hdr, + &sc->sc_line_state_task[1].hdr); +} + +static void +ucom_ring(struct ucom_softc *sc, uint8_t onoff) +{ + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + ucom_line_state(sc, UCOM_LS_RING, 0); + else + ucom_line_state(sc, 0, UCOM_LS_RING); +} + +static void +ucom_break(struct ucom_softc *sc, uint8_t onoff) +{ + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + ucom_line_state(sc, UCOM_LS_BREAK, 0); + else + ucom_line_state(sc, 0, UCOM_LS_BREAK); +} + +static void +ucom_dtr(struct ucom_softc *sc, uint8_t onoff) +{ + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + ucom_line_state(sc, UCOM_LS_DTR, 0); + else + ucom_line_state(sc, 0, UCOM_LS_DTR); +} + +static void +ucom_rts(struct ucom_softc *sc, uint8_t onoff) +{ + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + ucom_line_state(sc, UCOM_LS_RTS, 0); + else + ucom_line_state(sc, 0, UCOM_LS_RTS); +} + +static void +ucom_cfg_status_change(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + struct tty *tp; + int onoff; + uint8_t new_msr; + uint8_t new_lsr; + uint8_t msr_delta; + uint8_t lsr_delta; + uint8_t pps_signal; + + tp = sc->sc_tty; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + if (sc->sc_callback->ucom_cfg_get_status == NULL) { + return; + } + /* get status */ + + new_msr = 0; + new_lsr = 0; + + (sc->sc_callback->ucom_cfg_get_status) (sc, &new_lsr, &new_msr); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + /* TTY device closed */ + return; + } + msr_delta = (sc->sc_msr ^ new_msr); + lsr_delta = (sc->sc_lsr ^ new_lsr); + + sc->sc_msr = new_msr; + sc->sc_lsr = new_lsr; + + /* + * Time pulse counting support. + */ + switch(ucom_pps_mode & UART_PPS_SIGNAL_MASK) { + case UART_PPS_CTS: + pps_signal = SER_CTS; + break; + case UART_PPS_DCD: + pps_signal = SER_DCD; + break; + default: + pps_signal = 0; + break; + } + + if ((sc->sc_pps.ppsparam.mode & PPS_CAPTUREBOTH) && + (msr_delta & pps_signal)) { + pps_capture(&sc->sc_pps); + onoff = (sc->sc_msr & pps_signal) ? 1 : 0; + if (ucom_pps_mode & UART_PPS_INVERT_PULSE) + onoff = !onoff; + pps_event(&sc->sc_pps, onoff ? PPS_CAPTUREASSERT : + PPS_CAPTURECLEAR); + } + + if (msr_delta & SER_DCD) { + + onoff = (sc->sc_msr & SER_DCD) ? 1 : 0; + + DPRINTF("DCD changed to %d\n", onoff); + + ttydisc_modem(tp, onoff); + } + + if ((lsr_delta & ULSR_BI) && (sc->sc_lsr & ULSR_BI)) { + + DPRINTF("BREAK detected\n"); + + ttydisc_rint(tp, 0, TRE_BREAK); + ttydisc_rint_done(tp); + } + + if ((lsr_delta & ULSR_FE) && (sc->sc_lsr & ULSR_FE)) { + + DPRINTF("Frame error detected\n"); + + ttydisc_rint(tp, 0, TRE_FRAMING); + ttydisc_rint_done(tp); + } + + if ((lsr_delta & ULSR_PE) && (sc->sc_lsr & ULSR_PE)) { + + DPRINTF("Parity error detected\n"); + + ttydisc_rint(tp, 0, TRE_PARITY); + ttydisc_rint_done(tp); + } +} + +void +ucom_status_change(struct ucom_softc *sc) +{ + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (sc->sc_flag & UCOM_FLAG_CONSOLE) + return; /* not supported */ + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + DPRINTF("\n"); + + ucom_queue_command(sc, ucom_cfg_status_change, NULL, + &sc->sc_status_task[0].hdr, + &sc->sc_status_task[1].hdr); +} + +static void +ucom_cfg_param(struct usb_proc_msg *_task) +{ + struct ucom_param_task *task = + (struct ucom_param_task *)_task; + struct ucom_softc *sc = task->sc; + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + if (sc->sc_callback->ucom_cfg_param == NULL) { + return; + } + + (sc->sc_callback->ucom_cfg_param) (sc, &task->termios_copy); + + /* wait a little */ + usb_pause_mtx(sc->sc_mtx, hz / 10); +} + +static int +ucom_param(struct tty *tp, struct termios *t) +{ + struct ucom_softc *sc = tty_softc(tp); + uint8_t opened; + int error; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + opened = 0; + error = 0; + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + + /* XXX the TTY layer should call "open()" first! */ + /* + * Not quite: Its ordering is partly backwards, but + * some parameters must be set early in ttydev_open(), + * possibly before calling ttydevsw_open(). + */ + error = ucom_open(tp); + if (error) + goto done; + + opened = 1; + } + DPRINTF("sc = %p\n", sc); + + /* Check requested parameters. */ + if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) { + /* XXX c_ospeed == 0 is perfectly valid. */ + DPRINTF("mismatch ispeed and ospeed\n"); + error = EINVAL; + goto done; + } + t->c_ispeed = t->c_ospeed; + + if (sc->sc_callback->ucom_pre_param) { + /* Let the lower layer verify the parameters */ + error = (sc->sc_callback->ucom_pre_param) (sc, t); + if (error) { + DPRINTF("callback error = %d\n", error); + goto done; + } + } + + /* Disable transfers */ + sc->sc_flag &= ~UCOM_FLAG_GP_DATA; + + /* Queue baud rate programming command first */ + ucom_queue_command(sc, ucom_cfg_param, t, + &sc->sc_param_task[0].hdr, + &sc->sc_param_task[1].hdr); + + /* Queue transfer enable command last */ + ucom_queue_command(sc, ucom_cfg_start_transfers, NULL, + &sc->sc_start_task[0].hdr, + &sc->sc_start_task[1].hdr); + + if (t->c_cflag & CRTS_IFLOW) { + sc->sc_flag |= UCOM_FLAG_RTS_IFLOW; + } else if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) { + sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW; + ucom_modem(tp, SER_RTS, 0); + } +done: + if (error) { + if (opened) { + ucom_close(tp); + } + } + return (error); +} + +static void +ucom_outwakeup(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + DPRINTF("sc = %p\n", sc); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + /* The higher layer is not ready */ + return; + } + ucom_start_transfers(sc); +} + +/*------------------------------------------------------------------------* + * ucom_get_data + * + * Return values: + * 0: No data is available. + * Else: Data is available. + *------------------------------------------------------------------------*/ +uint8_t +ucom_get_data(struct ucom_softc *sc, struct usb_page_cache *pc, + uint32_t offset, uint32_t len, uint32_t *actlen) +{ + struct usb_page_search res; + struct tty *tp = sc->sc_tty; + uint32_t cnt; + uint32_t offset_orig; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (sc->sc_flag & UCOM_FLAG_CONSOLE) { + unsigned int temp; + + /* get total TX length */ + + temp = ucom_cons_tx_high - ucom_cons_tx_low; + temp %= UCOM_CONS_BUFSIZE; + + /* limit TX length */ + + if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_tx_low)) + temp = (UCOM_CONS_BUFSIZE - ucom_cons_tx_low); + + if (temp > len) + temp = len; + + /* copy in data */ + + usbd_copy_in(pc, offset, ucom_cons_tx_buf + ucom_cons_tx_low, temp); + + /* update counters */ + + ucom_cons_tx_low += temp; + ucom_cons_tx_low %= UCOM_CONS_BUFSIZE; + + /* store actual length */ + + *actlen = temp; + + return (temp ? 1 : 0); + } + + if (tty_gone(tp) || + !(sc->sc_flag & UCOM_FLAG_GP_DATA)) { + actlen[0] = 0; + return (0); /* multiport device polling */ + } + offset_orig = offset; + + while (len != 0) { + + usbd_get_page(pc, offset, &res); + + if (res.length > len) { + res.length = len; + } + /* copy data directly into USB buffer */ + cnt = ttydisc_getc(tp, res.buffer, res.length); + + offset += cnt; + len -= cnt; + + if (cnt < res.length) { + /* end of buffer */ + break; + } + } + + actlen[0] = offset - offset_orig; + + DPRINTF("cnt=%d\n", actlen[0]); + + if (actlen[0] == 0) { + return (0); + } + return (1); +} + +void +ucom_put_data(struct ucom_softc *sc, struct usb_page_cache *pc, + uint32_t offset, uint32_t len) +{ + struct usb_page_search res; + struct tty *tp = sc->sc_tty; + char *buf; + uint32_t cnt; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (sc->sc_flag & UCOM_FLAG_CONSOLE) { + unsigned int temp; + + /* get maximum RX length */ + + temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_rx_high + ucom_cons_rx_low; + temp %= UCOM_CONS_BUFSIZE; + + /* limit RX length */ + + if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_rx_high)) + temp = (UCOM_CONS_BUFSIZE - ucom_cons_rx_high); + + if (temp > len) + temp = len; + + /* copy out data */ + + usbd_copy_out(pc, offset, ucom_cons_rx_buf + ucom_cons_rx_high, temp); + + /* update counters */ + + ucom_cons_rx_high += temp; + ucom_cons_rx_high %= UCOM_CONS_BUFSIZE; + + return; + } + + if (tty_gone(tp)) + return; /* multiport device polling */ + + if (len == 0) + return; /* no data */ + + /* set a flag to prevent recursation ? */ + + while (len > 0) { + + usbd_get_page(pc, offset, &res); + + if (res.length > len) { + res.length = len; + } + len -= res.length; + offset += res.length; + + /* pass characters to tty layer */ + + buf = res.buffer; + cnt = res.length; + + /* first check if we can pass the buffer directly */ + + if (ttydisc_can_bypass(tp)) { + + /* clear any jitter buffer */ + sc->sc_jitterbuf_in = 0; + sc->sc_jitterbuf_out = 0; + + if (ttydisc_rint_bypass(tp, buf, cnt) != cnt) { + DPRINTF("tp=%p, data lost\n", tp); + } + continue; + } + /* need to loop */ + + for (cnt = 0; cnt != res.length; cnt++) { + if (sc->sc_jitterbuf_in != sc->sc_jitterbuf_out || + ttydisc_rint(tp, buf[cnt], 0) == -1) { + uint16_t end; + uint16_t pos; + + pos = sc->sc_jitterbuf_in; + end = sc->sc_jitterbuf_out + + UCOM_JITTERBUF_SIZE - 1; + if (end >= UCOM_JITTERBUF_SIZE) + end -= UCOM_JITTERBUF_SIZE; + + for (; cnt != res.length; cnt++) { + if (pos == end) + break; + sc->sc_jitterbuf[pos] = buf[cnt]; + pos++; + if (pos >= UCOM_JITTERBUF_SIZE) + pos -= UCOM_JITTERBUF_SIZE; + } + + sc->sc_jitterbuf_in = pos; + + /* set RTS in async fashion */ + if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) + ucom_rts(sc, 1); + + DPRINTF("tp=%p, lost %d " + "chars\n", tp, res.length - cnt); + break; + } + } + } + ttydisc_rint_done(tp); +} + +static void +ucom_free(void *xsc) +{ + struct ucom_softc *sc = xsc; + + if (sc->sc_callback->ucom_free != NULL) + sc->sc_callback->ucom_free(sc); + else + ucom_unref(sc->sc_super); + + mtx_lock(&ucom_mtx); + ucom_close_refs--; + mtx_unlock(&ucom_mtx); +} + +static cn_probe_t ucom_cnprobe; +static cn_init_t ucom_cninit; +static cn_term_t ucom_cnterm; +static cn_getc_t ucom_cngetc; +static cn_putc_t ucom_cnputc; +static cn_grab_t ucom_cngrab; +static cn_ungrab_t ucom_cnungrab; + +CONSOLE_DRIVER(ucom); + +static void +ucom_cnprobe(struct consdev *cp) +{ + if (ucom_cons_unit != -1) + cp->cn_pri = CN_NORMAL; + else + cp->cn_pri = CN_DEAD; + + strlcpy(cp->cn_name, "ucom", sizeof(cp->cn_name)); +} + +static void +ucom_cninit(struct consdev *cp) +{ +} + +static void +ucom_cnterm(struct consdev *cp) +{ +} + +static void +ucom_cngrab(struct consdev *cp) +{ +} + +static void +ucom_cnungrab(struct consdev *cp) +{ +} + +static int +ucom_cngetc(struct consdev *cd) +{ + struct ucom_softc *sc = ucom_cons_softc; + int c; + + if (sc == NULL) + return (-1); + + UCOM_MTX_LOCK(sc); + + if (ucom_cons_rx_low != ucom_cons_rx_high) { + c = ucom_cons_rx_buf[ucom_cons_rx_low]; + ucom_cons_rx_low ++; + ucom_cons_rx_low %= UCOM_CONS_BUFSIZE; + } else { + c = -1; + } + + /* start USB transfers */ + ucom_outwakeup(sc->sc_tty); + + UCOM_MTX_UNLOCK(sc); + + /* poll if necessary */ + if (USB_IN_POLLING_MODE_FUNC() && sc->sc_callback->ucom_poll) + (sc->sc_callback->ucom_poll) (sc); + + return (c); +} + +static void +ucom_cnputc(struct consdev *cd, int c) +{ + struct ucom_softc *sc = ucom_cons_softc; + unsigned int temp; + + if (sc == NULL) + return; + + repeat: + + UCOM_MTX_LOCK(sc); + + /* compute maximum TX length */ + + temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_tx_high + ucom_cons_tx_low; + temp %= UCOM_CONS_BUFSIZE; + + if (temp) { + ucom_cons_tx_buf[ucom_cons_tx_high] = c; + ucom_cons_tx_high ++; + ucom_cons_tx_high %= UCOM_CONS_BUFSIZE; + } + + /* start USB transfers */ + ucom_outwakeup(sc->sc_tty); + + UCOM_MTX_UNLOCK(sc); + + /* poll if necessary */ + if (USB_IN_POLLING_MODE_FUNC() && sc->sc_callback->ucom_poll) { + (sc->sc_callback->ucom_poll) (sc); + /* simple flow control */ + if (temp == 0) + goto repeat; + } +} + +/*------------------------------------------------------------------------* + * ucom_ref + * + * This function will increment the super UCOM reference count. + *------------------------------------------------------------------------*/ +void +ucom_ref(struct ucom_super_softc *ssc) +{ + mtx_lock(&ucom_mtx); + ssc->sc_refs++; + mtx_unlock(&ucom_mtx); +} + +/*------------------------------------------------------------------------* + * ucom_free_unit + * + * This function will free the super UCOM's allocated unit + * number. This function can be called on a zero-initialized + * structure. This function can be called multiple times. + *------------------------------------------------------------------------*/ +static void +ucom_free_unit(struct ucom_super_softc *ssc) +{ + if (!(ssc->sc_flag & UCOM_FLAG_FREE_UNIT)) + return; + + ucom_unit_free(ssc->sc_unit); + + ssc->sc_flag &= ~UCOM_FLAG_FREE_UNIT; +} + +/*------------------------------------------------------------------------* + * ucom_unref + * + * This function will decrement the super UCOM reference count. + * + * Return values: + * 0: UCOM structures are still referenced. + * Else: UCOM structures are no longer referenced. + *------------------------------------------------------------------------*/ +int +ucom_unref(struct ucom_super_softc *ssc) +{ + int retval; + + mtx_lock(&ucom_mtx); + retval = (ssc->sc_refs < 2); + ssc->sc_refs--; + mtx_unlock(&ucom_mtx); + + if (retval) + ucom_free_unit(ssc); + + return (retval); +} + +#if defined(GDB) + +#include <gdb/gdb.h> + +static gdb_probe_f ucom_gdbprobe; +static gdb_init_f ucom_gdbinit; +static gdb_term_f ucom_gdbterm; +static gdb_getc_f ucom_gdbgetc; +static gdb_putc_f ucom_gdbputc; + +GDB_DBGPORT(sio, ucom_gdbprobe, ucom_gdbinit, ucom_gdbterm, ucom_gdbgetc, ucom_gdbputc); + +static int +ucom_gdbprobe(void) +{ + return ((ucom_cons_softc != NULL) ? 0 : -1); +} + +static void +ucom_gdbinit(void) +{ +} + +static void +ucom_gdbterm(void) +{ +} + +static void +ucom_gdbputc(int c) +{ + ucom_cnputc(NULL, c); +} + +static int +ucom_gdbgetc(void) +{ + return (ucom_cngetc(NULL)); +} + +#endif diff --git a/freebsd/sys/dev/usb/serial/usb_serial.h b/freebsd/sys/dev/usb/serial/usb_serial.h new file mode 100644 index 0000000..9fbd373 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/usb_serial.h @@ -0,0 +1,221 @@ +/* $NetBSD: ucomvar.h,v 1.9 2001/01/23 21:56:17 augustss Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2001-2002, Shunsuke Akiyama <akiy...@jp.freebsd.org>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lenn...@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _USB_SERIAL_H_ +#define _USB_SERIAL_H_ + +#include <sys/tty.h> +#include <sys/serial.h> +#include <sys/fcntl.h> +#include <sys/sysctl.h> +#include <sys/timepps.h> + +/* Module interface related macros */ +#define UCOM_MODVER 1 + +#define UCOM_MINVER 1 +#define UCOM_PREFVER UCOM_MODVER +#define UCOM_MAXVER 1 +#define UCOM_JITTERBUF_SIZE 128 /* bytes */ + +struct usb_device; +struct ucom_softc; +struct usb_device_request; +struct thread; + +/* + * NOTE: There is no guarantee that "ucom_cfg_close()" will + * be called after "ucom_cfg_open()" if the device is detached + * while it is open! + */ +struct ucom_callback { + void (*ucom_cfg_get_status) (struct ucom_softc *, uint8_t *plsr, uint8_t *pmsr); + void (*ucom_cfg_set_dtr) (struct ucom_softc *, uint8_t); + void (*ucom_cfg_set_rts) (struct ucom_softc *, uint8_t); + void (*ucom_cfg_set_break) (struct ucom_softc *, uint8_t); + void (*ucom_cfg_set_ring) (struct ucom_softc *, uint8_t); + void (*ucom_cfg_param) (struct ucom_softc *, struct termios *); + void (*ucom_cfg_open) (struct ucom_softc *); + void (*ucom_cfg_close) (struct ucom_softc *); + int (*ucom_pre_open) (struct ucom_softc *); + int (*ucom_pre_param) (struct ucom_softc *, struct termios *); + int (*ucom_ioctl) (struct ucom_softc *, uint32_t, caddr_t, int, struct thread *); + void (*ucom_start_read) (struct ucom_softc *); + void (*ucom_stop_read) (struct ucom_softc *); + void (*ucom_start_write) (struct ucom_softc *); + void (*ucom_stop_write) (struct ucom_softc *); + void (*ucom_tty_name) (struct ucom_softc *, char *pbuf, uint16_t buflen, uint16_t unit, uint16_t subunit); + void (*ucom_poll) (struct ucom_softc *); + void (*ucom_free) (struct ucom_softc *); +}; + +/* Line status register */ +#define ULSR_RCV_FIFO 0x80 +#define ULSR_TSRE 0x40 /* Transmitter empty: byte sent */ +#define ULSR_TXRDY 0x20 /* Transmitter buffer empty */ +#define ULSR_BI 0x10 /* Break detected */ +#define ULSR_FE 0x08 /* Framing error: bad stop bit */ +#define ULSR_PE 0x04 /* Parity error */ +#define ULSR_OE 0x02 /* Overrun, lost incoming byte */ +#define ULSR_RXRDY 0x01 /* Byte ready in Receive Buffer */ +#define ULSR_RCV_MASK 0x1f /* Mask for incoming data or error */ + +struct ucom_cfg_task { + struct usb_proc_msg hdr; + struct ucom_softc *sc; +}; + +struct ucom_param_task { + struct usb_proc_msg hdr; + struct ucom_softc *sc; + struct termios termios_copy; +}; + +struct ucom_super_softc { + struct usb_process sc_tq; + int sc_unit; + int sc_subunits; + int sc_refs; + int sc_flag; /* see UCOM_FLAG_XXX */ + struct sysctl_oid *sc_sysctl_ttyname; + struct sysctl_oid *sc_sysctl_ttyports; + char sc_ttyname[16]; +}; + +struct ucom_softc { + /* + * NOTE: To avoid losing level change information we use two + * tasks instead of one for all commands. + * + * Level changes are transitions like: + * + * ON->OFF + * OFF->ON + * OPEN->CLOSE + * CLOSE->OPEN + */ + struct ucom_cfg_task sc_start_task[2]; + struct ucom_cfg_task sc_open_task[2]; + struct ucom_cfg_task sc_close_task[2]; + struct ucom_cfg_task sc_line_state_task[2]; + struct ucom_cfg_task sc_status_task[2]; + struct ucom_param_task sc_param_task[2]; + /* pulse capturing support, PPS */ + struct pps_state sc_pps; + /* Used to set "UCOM_FLAG_GP_DATA" flag: */ + struct usb_proc_msg *sc_last_start_xfer; + const struct ucom_callback *sc_callback; + struct ucom_super_softc *sc_super; + struct tty *sc_tty; + struct mtx *sc_mtx; + void *sc_parent; + int sc_subunit; + uint16_t sc_jitterbuf_in; + uint16_t sc_jitterbuf_out; + uint16_t sc_portno; + uint16_t sc_flag; +#define UCOM_FLAG_RTS_IFLOW 0x01 /* use RTS input flow control */ +#define UCOM_FLAG_GONE 0x02 /* the device is gone */ +#define UCOM_FLAG_ATTACHED 0x04 /* set if attached */ +#define UCOM_FLAG_GP_DATA 0x08 /* set if get and put data is possible */ +#define UCOM_FLAG_LL_READY 0x20 /* set if low layer is ready */ +#define UCOM_FLAG_HL_READY 0x40 /* set if high layer is ready */ +#define UCOM_FLAG_CONSOLE 0x80 /* set if device is a console */ +#define UCOM_FLAG_WAIT_REFS 0x0100 /* set if we must wait for refs */ +#define UCOM_FLAG_FREE_UNIT 0x0200 /* set if we must free the unit */ +#define UCOM_FLAG_INWAKEUP 0x0400 /* set if we are in the tsw_inwakeup callback */ + uint8_t sc_lsr; + uint8_t sc_msr; + uint8_t sc_mcr; + /* programmed line state bits */ + uint8_t sc_pls_set; /* set bits */ + uint8_t sc_pls_clr; /* cleared bits */ + uint8_t sc_pls_curr; /* last state */ +#define UCOM_LS_DTR 0x01 +#define UCOM_LS_RTS 0x02 +#define UCOM_LS_BREAK 0x04 +#define UCOM_LS_RING 0x08 + uint8_t sc_jitterbuf[UCOM_JITTERBUF_SIZE]; +}; + +#define UCOM_MTX_ASSERT(sc, what) USB_MTX_ASSERT((sc)->sc_mtx, what) +#define UCOM_MTX_LOCK(sc) USB_MTX_LOCK((sc)->sc_mtx) +#define UCOM_MTX_UNLOCK(sc) USB_MTX_UNLOCK((sc)->sc_mtx) +#define UCOM_UNLOAD_DRAIN(x) \ +SYSUNINIT(var, SI_SUB_KLD - 2, SI_ORDER_ANY, ucom_drain_all, 0) + +#define ucom_cfg_do_request(udev,com,req,ptr,flags,timo) \ + usbd_do_request_proc(udev,&(com)->sc_super->sc_tq,req,ptr,flags,NULL,timo) + +int ucom_attach(struct ucom_super_softc *, + struct ucom_softc *, int, void *, + const struct ucom_callback *callback, struct mtx *); +void ucom_detach(struct ucom_super_softc *, struct ucom_softc *); +void ucom_set_pnpinfo_usb(struct ucom_super_softc *, device_t); +void ucom_status_change(struct ucom_softc *); +uint8_t ucom_get_data(struct ucom_softc *, struct usb_page_cache *, + uint32_t, uint32_t, uint32_t *); +void ucom_put_data(struct ucom_softc *, struct usb_page_cache *, + uint32_t, uint32_t); +uint8_t ucom_cfg_is_gone(struct ucom_softc *); +void ucom_drain(struct ucom_super_softc *); +void ucom_drain_all(void *); +void ucom_ref(struct ucom_super_softc *); +int ucom_unref(struct ucom_super_softc *); +#endif /* _USB_SERIAL_H_ */ -- 1.9.1 _______________________________________________ devel mailing list devel@rtems.org http://lists.rtems.org/mailman/listinfo/devel