diff --git a/sys/dev/usb/serial/uftdi.c b/sys/dev/usb/serial/uftdi.c index 8b7e620aaa0e..bdad31891390 100644 --- a/sys/dev/usb/serial/uftdi.c +++ b/sys/dev/usb/serial/uftdi.c @@ -1,2003 +1,2009 @@ /* $NetBSD: uftdi.c,v 1.13 2002/09/23 05:51:23 simonb Exp $ */ /*- * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net). * * 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 __FBSDID("$FreeBSD$"); /* * NOTE: all function names beginning like "uftdi_cfg_" can only * be called from within the config thread function ! */ /* * FTDI FT232x, FT2232x, FT4232x, FT8U100AX and FT8U232xM serial adapters. * * Note that we specifically do not do a reset or otherwise alter the state of * the chip during attach, detach, open, and close, because it could be * pre-initialized (via an attached serial eeprom) to power-on into a mode such * as bitbang in which the pins are being driven to a specific state which we * must not perturb. The device gets reset at power-on, and doesn't need to be * reset again after that to function, except as directed by ioctl() calls. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR uftdi_debug #include #include #include #include #include static SYSCTL_NODE(_hw_usb, OID_AUTO, uftdi, CTLFLAG_RW, 0, "USB uftdi"); #ifdef USB_DEBUG static int uftdi_debug = 0; SYSCTL_INT(_hw_usb_uftdi, OID_AUTO, debug, CTLFLAG_RWTUN, &uftdi_debug, 0, "Debug level"); #endif #define UFTDI_CONFIG_INDEX 0 /* * IO buffer sizes and FTDI device procotol sizes. * * Note that the output packet size in the following defines is not the usb * protocol packet size based on bus speed, it is the size dictated by the FTDI * device itself, and is used only on older chips. * * We allocate buffers bigger than the hardware's packet size, and process * multiple packets within each buffer. This allows the controller to make * optimal use of the usb bus by conducting multiple transfers with the device * during a single bus timeslice to fill or drain the chip's fifos. * * The output data on newer chips has no packet header, and we are able to pack * any number of output bytes into a buffer. On some older chips, each output * packet contains a 1-byte header and up to 63 bytes of payload. The size is * encoded in 6 bits of the header, hence the 64-byte limit on packet size. We * loop to fill the buffer with many of these header+payload packets. * * The input data on all chips consists of packets which contain a 2-byte header * followed by data payload. The total size of the packet is wMaxPacketSize * which can change based on the bus speed (e.g., 64 for full speed, 512 for * high speed). We loop to extract the headers and payloads from the packets * packed into an input buffer. */ #define UFTDI_IBUFSIZE 2048 #define UFTDI_IHDRSIZE 2 #define UFTDI_OBUFSIZE 2048 #define UFTDI_OPKTSIZE 64 enum { UFTDI_BULK_DT_WR, UFTDI_BULK_DT_RD, UFTDI_N_TRANSFER, }; enum { DEVT_SIO, DEVT_232A, DEVT_232B, DEVT_2232D, /* Includes 2232C */ DEVT_232R, DEVT_2232H, DEVT_4232H, DEVT_232H, DEVT_230X, }; #define DEVF_BAUDBITS_HINDEX 0x01 /* Baud bits in high byte of index. */ #define DEVF_BAUDCLK_12M 0X02 /* Base baud clock is 12MHz. */ struct uftdi_softc { struct ucom_super_softc sc_super_ucom; struct ucom_softc sc_ucom; struct usb_device *sc_udev; struct usb_xfer *sc_xfer[UFTDI_N_TRANSFER]; device_t sc_dev; struct mtx sc_mtx; uint32_t sc_unit; uint16_t sc_last_lcr; uint16_t sc_bcdDevice; uint8_t sc_devtype; uint8_t sc_devflags; uint8_t sc_hdrlen; uint8_t sc_msr; uint8_t sc_lsr; uint8_t sc_bitmode; }; struct uftdi_param_config { uint16_t baud_lobits; uint16_t baud_hibits; uint16_t lcr; uint8_t v_start; uint8_t v_stop; uint8_t v_flow; }; /* prototypes */ static device_probe_t uftdi_probe; static device_attach_t uftdi_attach; static device_detach_t uftdi_detach; static void uftdi_free_softc(struct uftdi_softc *); static usb_callback_t uftdi_write_callback; static usb_callback_t uftdi_read_callback; static void uftdi_free(struct ucom_softc *); static void uftdi_cfg_open(struct ucom_softc *); static void uftdi_cfg_close(struct ucom_softc *); static void uftdi_cfg_set_dtr(struct ucom_softc *, uint8_t); static void uftdi_cfg_set_rts(struct ucom_softc *, uint8_t); static void uftdi_cfg_set_break(struct ucom_softc *, uint8_t); static int uftdi_set_parm_soft(struct ucom_softc *, struct termios *, struct uftdi_param_config *); static int uftdi_pre_param(struct ucom_softc *, struct termios *); static void uftdi_cfg_param(struct ucom_softc *, struct termios *); static void uftdi_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); static int uftdi_reset(struct ucom_softc *, int); static int uftdi_set_bitmode(struct ucom_softc *, uint8_t, uint8_t); static int uftdi_get_bitmode(struct ucom_softc *, uint8_t *, uint8_t *); static int uftdi_set_latency(struct ucom_softc *, int); static int uftdi_get_latency(struct ucom_softc *, int *); static int uftdi_set_event_char(struct ucom_softc *, int); static int uftdi_set_error_char(struct ucom_softc *, int); static int uftdi_ioctl(struct ucom_softc *, uint32_t, caddr_t, int, struct thread *); static void uftdi_start_read(struct ucom_softc *); static void uftdi_stop_read(struct ucom_softc *); static void uftdi_start_write(struct ucom_softc *); static void uftdi_stop_write(struct ucom_softc *); static void uftdi_poll(struct ucom_softc *ucom); static const struct usb_config uftdi_config[UFTDI_N_TRANSFER] = { [UFTDI_BULK_DT_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = UFTDI_OBUFSIZE, .flags = {.pipe_bof = 1,}, .callback = &uftdi_write_callback, }, [UFTDI_BULK_DT_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = UFTDI_IBUFSIZE, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &uftdi_read_callback, }, }; static const struct ucom_callback uftdi_callback = { .ucom_cfg_get_status = &uftdi_cfg_get_status, .ucom_cfg_set_dtr = &uftdi_cfg_set_dtr, .ucom_cfg_set_rts = &uftdi_cfg_set_rts, .ucom_cfg_set_break = &uftdi_cfg_set_break, .ucom_cfg_param = &uftdi_cfg_param, .ucom_cfg_open = &uftdi_cfg_open, .ucom_cfg_close = &uftdi_cfg_close, .ucom_pre_param = &uftdi_pre_param, .ucom_ioctl = &uftdi_ioctl, .ucom_start_read = &uftdi_start_read, .ucom_stop_read = &uftdi_stop_read, .ucom_start_write = &uftdi_start_write, .ucom_stop_write = &uftdi_stop_write, .ucom_poll = &uftdi_poll, .ucom_free = &uftdi_free, }; static device_method_t uftdi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, uftdi_probe), DEVMETHOD(device_attach, uftdi_attach), DEVMETHOD(device_detach, uftdi_detach), DEVMETHOD_END }; static devclass_t uftdi_devclass; static driver_t uftdi_driver = { .name = "uftdi", .methods = uftdi_methods, .size = sizeof(struct uftdi_softc), }; static const STRUCT_USB_HOST_ID uftdi_devs[] = { #define UFTDI_DEV(v, p, i) \ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } UFTDI_DEV(ACTON, SPECTRAPRO, 0), UFTDI_DEV(ALTI2, N3, 0), UFTDI_DEV(ANALOGDEVICES, GNICE, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(ANALOGDEVICES, GNICEPLUS, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(ATMEL, STK541, 0), UFTDI_DEV(BAYER, CONTOUR_CABLE, 0), UFTDI_DEV(BBELECTRONICS, 232USB9M, 0), UFTDI_DEV(BBELECTRONICS, 485USB9F_2W, 0), UFTDI_DEV(BBELECTRONICS, 485USB9F_4W, 0), UFTDI_DEV(BBELECTRONICS, 485USBTB_2W, 0), UFTDI_DEV(BBELECTRONICS, 485USBTB_4W, 0), UFTDI_DEV(BBELECTRONICS, TTL3USB9M, 0), UFTDI_DEV(BBELECTRONICS, TTL5USB9M, 0), UFTDI_DEV(BBELECTRONICS, USO9ML2, 0), UFTDI_DEV(BBELECTRONICS, USO9ML2DR, 0), UFTDI_DEV(BBELECTRONICS, USO9ML2DR_2, 0), UFTDI_DEV(BBELECTRONICS, USOPTL4, 0), UFTDI_DEV(BBELECTRONICS, USOPTL4DR, 0), UFTDI_DEV(BBELECTRONICS, USOPTL4DR2, 0), UFTDI_DEV(BBELECTRONICS, USOTL4, 0), UFTDI_DEV(BBELECTRONICS, USPTL4, 0), UFTDI_DEV(BBELECTRONICS, USTL4, 0), UFTDI_DEV(BBELECTRONICS, ZZ_PROG1_USB, 0), UFTDI_DEV(CONTEC, COM1USBH, 0), UFTDI_DEV(DRESDENELEKTRONIK, SENSORTERMINALBOARD, 0), UFTDI_DEV(DRESDENELEKTRONIK, WIRELESSHANDHELDTERMINAL, 0), UFTDI_DEV(DRESDENELEKTRONIK, DE_RFNODE, 0), UFTDI_DEV(DRESDENELEKTRONIK, LEVELSHIFTERSTICKLOWCOST, 0), UFTDI_DEV(ELEKTOR, FT323R, 0), UFTDI_DEV(EVOLUTION, ER1, 0), UFTDI_DEV(EVOLUTION, HYBRID, 0), UFTDI_DEV(EVOLUTION, RCM4, 0), UFTDI_DEV(FALCOM, SAMBA, 0), UFTDI_DEV(FALCOM, TWIST, 0), UFTDI_DEV(FIC, NEO1973_DEBUG, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FIC, NEO1973_DEBUG, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, 232EX, 0), UFTDI_DEV(FTDI, 232H, 0), UFTDI_DEV(FTDI, 232RL, 0), UFTDI_DEV(FTDI, 4N_GALAXY_DE_1, 0), UFTDI_DEV(FTDI, 4N_GALAXY_DE_2, 0), UFTDI_DEV(FTDI, 4N_GALAXY_DE_3, 0), UFTDI_DEV(FTDI, 8U232AM_ALT, 0), UFTDI_DEV(FTDI, ACCESSO, 0), UFTDI_DEV(FTDI, ACG_HFDUAL, 0), UFTDI_DEV(FTDI, ACTIVE_ROBOTS, 0), UFTDI_DEV(FTDI, ACTZWAVE, 0), UFTDI_DEV(FTDI, AMC232, 0), UFTDI_DEV(FTDI, ARTEMIS, 0), UFTDI_DEV(FTDI, ASK_RDR400, 0), UFTDI_DEV(FTDI, ATIK_ATK16, 0), UFTDI_DEV(FTDI, ATIK_ATK16C, 0), UFTDI_DEV(FTDI, ATIK_ATK16HR, 0), UFTDI_DEV(FTDI, ATIK_ATK16HRC, 0), UFTDI_DEV(FTDI, ATIK_ATK16IC, 0), UFTDI_DEV(FTDI, BCS_SE923, 0), UFTDI_DEV(FTDI, CANDAPTER, 0), UFTDI_DEV(FTDI, CANUSB, 0), UFTDI_DEV(FTDI, CCSICDU20_0, 0), UFTDI_DEV(FTDI, CCSICDU40_1, 0), UFTDI_DEV(FTDI, CCSICDU64_4, 0), UFTDI_DEV(FTDI, CCSLOAD_N_GO_3, 0), UFTDI_DEV(FTDI, CCSMACHX_2, 0), UFTDI_DEV(FTDI, CCSPRIME8_5, 0), UFTDI_DEV(FTDI, CFA_631, 0), UFTDI_DEV(FTDI, CFA_632, 0), UFTDI_DEV(FTDI, CFA_633, 0), UFTDI_DEV(FTDI, CFA_634, 0), UFTDI_DEV(FTDI, CFA_635, 0), UFTDI_DEV(FTDI, CHAMSYS_24_MASTER_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_MAXI_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_MEDIA_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_MIDI_TIMECODE, 0), UFTDI_DEV(FTDI, CHAMSYS_MINI_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_PC_WING, 0), UFTDI_DEV(FTDI, CHAMSYS_USB_DMX, 0), UFTDI_DEV(FTDI, CHAMSYS_WING, 0), UFTDI_DEV(FTDI, COM4SM, 0), UFTDI_DEV(FTDI, CONVERTER_0, 0), UFTDI_DEV(FTDI, CONVERTER_1, 0), UFTDI_DEV(FTDI, CONVERTER_2, 0), UFTDI_DEV(FTDI, CONVERTER_3, 0), UFTDI_DEV(FTDI, CONVERTER_4, 0), UFTDI_DEV(FTDI, CONVERTER_5, 0), UFTDI_DEV(FTDI, CONVERTER_6, 0), UFTDI_DEV(FTDI, CONVERTER_7, 0), UFTDI_DEV(FTDI, CTI_USB_MINI_485, 0), UFTDI_DEV(FTDI, CTI_USB_NANO_485, 0), UFTDI_DEV(FTDI, DMX4ALL, 0), UFTDI_DEV(FTDI, DOMINTELL_DGQG, 0), UFTDI_DEV(FTDI, DOMINTELL_DUSB, 0), UFTDI_DEV(FTDI, DOTEC, 0), UFTDI_DEV(FTDI, ECLO_COM_1WIRE, 0), UFTDI_DEV(FTDI, ECO_PRO_CDS, 0), UFTDI_DEV(FTDI, EISCOU, 0), UFTDI_DEV(FTDI, ELSTER_UNICOM, 0), UFTDI_DEV(FTDI, ELV_ALC8500, 0), UFTDI_DEV(FTDI, ELV_CLI7000, 0), UFTDI_DEV(FTDI, ELV_CSI8, 0), UFTDI_DEV(FTDI, ELV_EC3000, 0), UFTDI_DEV(FTDI, ELV_EM1000DL, 0), UFTDI_DEV(FTDI, ELV_EM1010PC, 0), UFTDI_DEV(FTDI, ELV_FEM, 0), UFTDI_DEV(FTDI, ELV_FHZ1000PC, 0), UFTDI_DEV(FTDI, ELV_FHZ1300PC, 0), UFTDI_DEV(FTDI, ELV_FM3RX, 0), UFTDI_DEV(FTDI, ELV_FS20SIG, 0), UFTDI_DEV(FTDI, ELV_HS485, 0), UFTDI_DEV(FTDI, ELV_KL100, 0), UFTDI_DEV(FTDI, ELV_MSM1, 0), UFTDI_DEV(FTDI, ELV_PCD200, 0), UFTDI_DEV(FTDI, ELV_PCK100, 0), UFTDI_DEV(FTDI, ELV_PPS7330, 0), UFTDI_DEV(FTDI, ELV_RFP500, 0), UFTDI_DEV(FTDI, ELV_T1100, 0), UFTDI_DEV(FTDI, ELV_TFD128, 0), UFTDI_DEV(FTDI, ELV_TFM100, 0), UFTDI_DEV(FTDI, ELV_TWS550, 0), UFTDI_DEV(FTDI, ELV_UAD8, 0), UFTDI_DEV(FTDI, ELV_UDA7, 0), UFTDI_DEV(FTDI, ELV_UDF77, 0), UFTDI_DEV(FTDI, ELV_UIO88, 0), UFTDI_DEV(FTDI, ELV_ULA200, 0), UFTDI_DEV(FTDI, ELV_UM100, 0), UFTDI_DEV(FTDI, ELV_UMS100, 0), UFTDI_DEV(FTDI, ELV_UO100, 0), UFTDI_DEV(FTDI, ELV_UR100, 0), UFTDI_DEV(FTDI, ELV_USI2, 0), UFTDI_DEV(FTDI, ELV_USR, 0), UFTDI_DEV(FTDI, ELV_UTP8, 0), UFTDI_DEV(FTDI, ELV_WS300PC, 0), UFTDI_DEV(FTDI, ELV_WS444PC, 0), UFTDI_DEV(FTDI, ELV_WS500, 0), UFTDI_DEV(FTDI, ELV_WS550, 0), UFTDI_DEV(FTDI, ELV_WS777, 0), UFTDI_DEV(FTDI, ELV_WS888, 0), UFTDI_DEV(FTDI, EMCU2D, 0), UFTDI_DEV(FTDI, EMCU2H, 0), UFTDI_DEV(FTDI, FUTURE_0, 0), UFTDI_DEV(FTDI, FUTURE_1, 0), UFTDI_DEV(FTDI, FUTURE_2, 0), UFTDI_DEV(FTDI, GAMMASCOUT, 0), UFTDI_DEV(FTDI, GENERIC, 0), UFTDI_DEV(FTDI, GUDEADS_E808, 0), UFTDI_DEV(FTDI, GUDEADS_E809, 0), UFTDI_DEV(FTDI, GUDEADS_E80A, 0), UFTDI_DEV(FTDI, GUDEADS_E80B, 0), UFTDI_DEV(FTDI, GUDEADS_E80C, 0), UFTDI_DEV(FTDI, GUDEADS_E80D, 0), UFTDI_DEV(FTDI, GUDEADS_E80E, 0), UFTDI_DEV(FTDI, GUDEADS_E80F, 0), UFTDI_DEV(FTDI, GUDEADS_E88D, 0), UFTDI_DEV(FTDI, GUDEADS_E88E, 0), UFTDI_DEV(FTDI, GUDEADS_E88F, 0), UFTDI_DEV(FTDI, HD_RADIO, 0), UFTDI_DEV(FTDI, HO720, 0), UFTDI_DEV(FTDI, HO730, 0), UFTDI_DEV(FTDI, HO820, 0), UFTDI_DEV(FTDI, HO870, 0), UFTDI_DEV(FTDI, IBS_APP70, 0), UFTDI_DEV(FTDI, IBS_PCMCIA, 0), UFTDI_DEV(FTDI, IBS_PEDO, 0), UFTDI_DEV(FTDI, IBS_PICPRO, 0), UFTDI_DEV(FTDI, IBS_PK1, 0), UFTDI_DEV(FTDI, IBS_PROD, 0), UFTDI_DEV(FTDI, IBS_RS232MON, 0), UFTDI_DEV(FTDI, IBS_US485, 0), UFTDI_DEV(FTDI, IPLUS, 0), UFTDI_DEV(FTDI, IPLUS2, 0), UFTDI_DEV(FTDI, IRTRANS, 0), UFTDI_DEV(FTDI, KBS, 0), UFTDI_DEV(FTDI, KTLINK, 0), UFTDI_DEV(FTDI, LENZ_LIUSB, 0), UFTDI_DEV(FTDI, LK202, 0), UFTDI_DEV(FTDI, LK204, 0), UFTDI_DEV(FTDI, LM3S_DEVEL_BOARD, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, LM3S_EVAL_BOARD, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, LM3S_ICDI_B_BOARD, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, MASTERDEVEL2, 0), UFTDI_DEV(FTDI, MAXSTREAM, 0), UFTDI_DEV(FTDI, MHAM_DB9, 0), UFTDI_DEV(FTDI, MHAM_IC, 0), UFTDI_DEV(FTDI, MHAM_KW, 0), UFTDI_DEV(FTDI, MHAM_RS232, 0), UFTDI_DEV(FTDI, MHAM_Y6, 0), UFTDI_DEV(FTDI, MHAM_Y8, 0), UFTDI_DEV(FTDI, MHAM_Y9, 0), UFTDI_DEV(FTDI, MHAM_YS, 0), UFTDI_DEV(FTDI, MICRO_CHAMELEON, 0), UFTDI_DEV(FTDI, MTXORB_5, 0), UFTDI_DEV(FTDI, MTXORB_6, 0), UFTDI_DEV(FTDI, MX2_3, 0), UFTDI_DEV(FTDI, MX4_5, 0), UFTDI_DEV(FTDI, NXTCAM, 0), UFTDI_DEV(FTDI, OCEANIC, 0), UFTDI_DEV(FTDI, OOCDLINK, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, OPENDCC, 0), UFTDI_DEV(FTDI, OPENDCC_GATEWAY, 0), UFTDI_DEV(FTDI, OPENDCC_GBM, 0), UFTDI_DEV(FTDI, OPENDCC_SNIFFER, 0), UFTDI_DEV(FTDI, OPENDCC_THROTTLE, 0), UFTDI_DEV(FTDI, PCDJ_DAC2, 0), UFTDI_DEV(FTDI, PCMSFU, 0), UFTDI_DEV(FTDI, PERLE_ULTRAPORT, 0), UFTDI_DEV(FTDI, PHI_FISCO, 0), UFTDI_DEV(FTDI, PIEGROUP, 0), UFTDI_DEV(FTDI, PROPOX_JTAGCABLEII, 0), UFTDI_DEV(FTDI, R2000KU_TRUE_RNG, 0), UFTDI_DEV(FTDI, R2X0, 0), UFTDI_DEV(FTDI, RELAIS, 0), UFTDI_DEV(FTDI, REU_TINY, 0), UFTDI_DEV(FTDI, RMP200, 0), UFTDI_DEV(FTDI, RM_CANVIEW, 0), UFTDI_DEV(FTDI, RRCIRKITS_LOCOBUFFER, 0), UFTDI_DEV(FTDI, SCIENCESCOPE_HS_LOGBOOK, 0), UFTDI_DEV(FTDI, SCIENCESCOPE_LOGBOOKML, 0), UFTDI_DEV(FTDI, SCIENCESCOPE_LS_LOGBOOK, 0), UFTDI_DEV(FTDI, SCS_DEVICE_0, 0), UFTDI_DEV(FTDI, SCS_DEVICE_1, 0), UFTDI_DEV(FTDI, SCS_DEVICE_2, 0), UFTDI_DEV(FTDI, SCS_DEVICE_3, 0), UFTDI_DEV(FTDI, SCS_DEVICE_4, 0), UFTDI_DEV(FTDI, SCS_DEVICE_5, 0), UFTDI_DEV(FTDI, SCS_DEVICE_6, 0), UFTDI_DEV(FTDI, SCS_DEVICE_7, 0), UFTDI_DEV(FTDI, SCX8_USB_PHOENIX, 0), UFTDI_DEV(FTDI, SDMUSBQSS, 0), UFTDI_DEV(FTDI, SEMC_DSS20, 0), UFTDI_DEV(FTDI, SERIAL_2232C, UFTDI_JTAG_CHECK_STRING), UFTDI_DEV(FTDI, SERIAL_2232D, 0), UFTDI_DEV(FTDI, SERIAL_232RL, 0), UFTDI_DEV(FTDI, SERIAL_4232H, 0), UFTDI_DEV(FTDI, SERIAL_8U100AX, 0), UFTDI_DEV(FTDI, SERIAL_8U232AM, 0), UFTDI_DEV(FTDI, SERIAL_8U232AM4, 0), UFTDI_DEV(FTDI, SIGNALYZER_SH2, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, SIGNALYZER_SH4, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, SIGNALYZER_SLITE, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, SIGNALYZER_ST, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, SPECIAL_1, 0), UFTDI_DEV(FTDI, SPECIAL_3, 0), UFTDI_DEV(FTDI, SPECIAL_4, 0), UFTDI_DEV(FTDI, SPROG_II, 0), UFTDI_DEV(FTDI, SR_RADIO, 0), UFTDI_DEV(FTDI, SUUNTO_SPORTS, 0), UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13M, 0), UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13S, 0), UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13U, 0), UFTDI_DEV(FTDI, TAVIR_STK500, 0), UFTDI_DEV(FTDI, TERATRONIK_D2XX, 0), UFTDI_DEV(FTDI, TERATRONIK_VCP, 0), UFTDI_DEV(FTDI, THORLABS, 0), UFTDI_DEV(FTDI, TNC_X, 0), UFTDI_DEV(FTDI, TTUSB, 0), UFTDI_DEV(FTDI, TURTELIZER2, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, UOPTBR, 0), UFTDI_DEV(FTDI, USBSERIAL, 0), UFTDI_DEV(FTDI, USBX_707, 0), UFTDI_DEV(FTDI, USB_UIRT, 0), UFTDI_DEV(FTDI, USINT_CAT, 0), UFTDI_DEV(FTDI, USINT_RS232, 0), UFTDI_DEV(FTDI, USINT_WKEY, 0), UFTDI_DEV(FTDI, VARDAAN, 0), UFTDI_DEV(FTDI, VNHCPCUSB_D, 0), UFTDI_DEV(FTDI, WESTREX_MODEL_777, 0), UFTDI_DEV(FTDI, WESTREX_MODEL_8900F, 0), UFTDI_DEV(FTDI, XDS100V2, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, XDS100V3, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(FTDI, XF_547, 0), UFTDI_DEV(FTDI, XF_640, 0), UFTDI_DEV(FTDI, XF_642, 0), UFTDI_DEV(FTDI, XM_RADIO, 0), UFTDI_DEV(FTDI, YEI_SERVOCENTER31, 0), UFTDI_DEV(GNOTOMETRICS, USB, 0), UFTDI_DEV(ICOM, SP1, 0), UFTDI_DEV(ICOM, OPC_U_UC, 0), UFTDI_DEV(ICOM, RP2C1, 0), UFTDI_DEV(ICOM, RP2C2, 0), UFTDI_DEV(ICOM, RP2D, 0), UFTDI_DEV(ICOM, RP2KVR, 0), UFTDI_DEV(ICOM, RP2KVT, 0), UFTDI_DEV(ICOM, RP2VR, 0), UFTDI_DEV(ICOM, RP2VT, 0), UFTDI_DEV(ICOM, RP4KVR, 0), UFTDI_DEV(ICOM, RP4KVT, 0), UFTDI_DEV(IDTECH, IDT1221U, 0), UFTDI_DEV(INTERBIOMETRICS, IOBOARD, 0), UFTDI_DEV(INTERBIOMETRICS, MINI_IOBOARD, 0), UFTDI_DEV(INTREPIDCS, NEOVI, 0), UFTDI_DEV(INTREPIDCS, VALUECAN, 0), UFTDI_DEV(IONICS, PLUGCOMPUTER, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(JETI, SPC1201, 0), UFTDI_DEV(KOBIL, CONV_B1, 0), UFTDI_DEV(KOBIL, CONV_KAAN, 0), UFTDI_DEV(LARSENBRUSGAARD, ALTITRACK, 0), UFTDI_DEV(MARVELL, SHEEVAPLUG, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0100, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0101, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0102, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0103, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0104, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0105, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0106, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0107, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0108, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0109, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0110, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0111, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0112, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0113, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0114, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0115, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0116, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0117, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0118, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0119, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0120, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0121, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0122, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0123, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0124, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0125, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0126, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0128, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0129, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0130, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0131, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0132, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0133, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0134, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0135, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0136, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0137, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0138, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0139, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0140, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0141, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0142, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0143, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0144, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0145, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0146, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0147, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0148, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0149, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0150, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0151, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0152, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0159, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0160, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0161, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0162, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0163, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0164, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0165, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0166, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0167, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0168, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0169, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0170, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0171, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0172, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0173, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0174, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0175, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0176, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0177, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0178, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0179, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0180, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0181, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0182, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0183, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0184, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0185, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0186, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0187, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0188, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0189, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0190, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0191, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0192, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0193, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0194, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0195, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0196, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0197, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0198, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0199, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019A, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019B, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019C, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019D, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019E, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019F, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01ED, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EF, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F0, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F1, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F2, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F3, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F4, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F5, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F6, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F7, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F8, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F9, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FA, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FB, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FC, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FD, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FE, 0), UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FF, 0), UFTDI_DEV(MATRIXORBITAL, MOUA, 0), UFTDI_DEV(MELCO, PCOPRS1, 0), UFTDI_DEV(METAGEEK, TELLSTICK, 0), UFTDI_DEV(MOBILITY, USB_SERIAL, 0), UFTDI_DEV(OLIMEX, ARM_USB_OCD, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(OLIMEX, ARM_USB_OCD_H, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(OPTO, CRD7734, 0), UFTDI_DEV(OPTO, CRD7734_1, 0), UFTDI_DEV(PAPOUCH, AD4USB, 0), UFTDI_DEV(PAPOUCH, AP485, 0), UFTDI_DEV(PAPOUCH, AP485_2, 0), UFTDI_DEV(PAPOUCH, DRAK5, 0), UFTDI_DEV(PAPOUCH, DRAK6, 0), UFTDI_DEV(PAPOUCH, GMSR, 0), UFTDI_DEV(PAPOUCH, GMUX, 0), UFTDI_DEV(PAPOUCH, IRAMP, 0), UFTDI_DEV(PAPOUCH, LEC, 0), UFTDI_DEV(PAPOUCH, MU, 0), UFTDI_DEV(PAPOUCH, QUIDO10X1, 0), UFTDI_DEV(PAPOUCH, QUIDO2X16, 0), UFTDI_DEV(PAPOUCH, QUIDO2X2, 0), UFTDI_DEV(PAPOUCH, QUIDO30X3, 0), UFTDI_DEV(PAPOUCH, QUIDO3X32, 0), UFTDI_DEV(PAPOUCH, QUIDO4X4, 0), UFTDI_DEV(PAPOUCH, QUIDO60X3, 0), UFTDI_DEV(PAPOUCH, QUIDO8X8, 0), UFTDI_DEV(PAPOUCH, SB232, 0), UFTDI_DEV(PAPOUCH, SB422, 0), UFTDI_DEV(PAPOUCH, SB422_2, 0), UFTDI_DEV(PAPOUCH, SB485, 0), UFTDI_DEV(PAPOUCH, SB485C, 0), UFTDI_DEV(PAPOUCH, SB485S, 0), UFTDI_DEV(PAPOUCH, SB485_2, 0), UFTDI_DEV(PAPOUCH, SIMUKEY, 0), UFTDI_DEV(PAPOUCH, TMU, 0), UFTDI_DEV(PAPOUCH, UPSUSB, 0), UFTDI_DEV(POSIFLEX, PP7000, 0), UFTDI_DEV(QIHARDWARE, JTAGSERIAL, UFTDI_JTAG_IFACE(0)), UFTDI_DEV(RATOC, REXUSB60F, 0), UFTDI_DEV(RTSYSTEMS, CT29B, 0), UFTDI_DEV(RTSYSTEMS, SERIAL_VX7, 0), UFTDI_DEV(SEALEVEL, 2101, 0), UFTDI_DEV(SEALEVEL, 2102, 0), UFTDI_DEV(SEALEVEL, 2103, 0), UFTDI_DEV(SEALEVEL, 2104, 0), UFTDI_DEV(SEALEVEL, 2106, 0), UFTDI_DEV(SEALEVEL, 2201_1, 0), UFTDI_DEV(SEALEVEL, 2201_2, 0), UFTDI_DEV(SEALEVEL, 2202_1, 0), UFTDI_DEV(SEALEVEL, 2202_2, 0), UFTDI_DEV(SEALEVEL, 2203_1, 0), UFTDI_DEV(SEALEVEL, 2203_2, 0), UFTDI_DEV(SEALEVEL, 2401_1, 0), UFTDI_DEV(SEALEVEL, 2401_2, 0), UFTDI_DEV(SEALEVEL, 2401_3, 0), UFTDI_DEV(SEALEVEL, 2401_4, 0), UFTDI_DEV(SEALEVEL, 2402_1, 0), UFTDI_DEV(SEALEVEL, 2402_2, 0), UFTDI_DEV(SEALEVEL, 2402_3, 0), UFTDI_DEV(SEALEVEL, 2402_4, 0), UFTDI_DEV(SEALEVEL, 2403_1, 0), UFTDI_DEV(SEALEVEL, 2403_2, 0), UFTDI_DEV(SEALEVEL, 2403_3, 0), UFTDI_DEV(SEALEVEL, 2403_4, 0), UFTDI_DEV(SEALEVEL, 2801_1, 0), UFTDI_DEV(SEALEVEL, 2801_2, 0), UFTDI_DEV(SEALEVEL, 2801_3, 0), UFTDI_DEV(SEALEVEL, 2801_4, 0), UFTDI_DEV(SEALEVEL, 2801_5, 0), UFTDI_DEV(SEALEVEL, 2801_6, 0), UFTDI_DEV(SEALEVEL, 2801_7, 0), UFTDI_DEV(SEALEVEL, 2801_8, 0), UFTDI_DEV(SEALEVEL, 2802_1, 0), UFTDI_DEV(SEALEVEL, 2802_2, 0), UFTDI_DEV(SEALEVEL, 2802_3, 0), UFTDI_DEV(SEALEVEL, 2802_4, 0), UFTDI_DEV(SEALEVEL, 2802_5, 0), UFTDI_DEV(SEALEVEL, 2802_6, 0), UFTDI_DEV(SEALEVEL, 2802_7, 0), UFTDI_DEV(SEALEVEL, 2802_8, 0), UFTDI_DEV(SEALEVEL, 2803_1, 0), UFTDI_DEV(SEALEVEL, 2803_2, 0), UFTDI_DEV(SEALEVEL, 2803_3, 0), UFTDI_DEV(SEALEVEL, 2803_4, 0), UFTDI_DEV(SEALEVEL, 2803_5, 0), UFTDI_DEV(SEALEVEL, 2803_6, 0), UFTDI_DEV(SEALEVEL, 2803_7, 0), UFTDI_DEV(SEALEVEL, 2803_8, 0), UFTDI_DEV(SIIG2, DK201, 0), UFTDI_DEV(SIIG2, US2308, 0), UFTDI_DEV(TESTO, USB_INTERFACE, 0), UFTDI_DEV(TML, USB_SERIAL, 0), UFTDI_DEV(TTI, QL355P, 0), UFTDI_DEV(UNKNOWN4, NF_RIC, 0), #undef UFTDI_DEV }; DRIVER_MODULE(uftdi, uhub, uftdi_driver, uftdi_devclass, NULL, NULL); MODULE_DEPEND(uftdi, ucom, 1, 1, 1); MODULE_DEPEND(uftdi, usb, 1, 1, 1); MODULE_VERSION(uftdi, 1); USB_PNP_HOST_INFO(uftdi_devs); /* * Jtag product name strings table. Some products have one or more interfaces * dedicated to jtag or gpio, but use a product ID that's the same as other * products which don't. They are marked with a flag in the table above, and * the following string table is checked for flagged products. The string check * is done with strstr(); in effect there is an implicit wildcard at the * beginning and end of each product name string in table. */ static const struct jtag_by_name { const char * product_name; uint32_t jtag_interfaces; } jtag_products_by_name[] = { /* TI Beaglebone and TI XDS100Vn jtag product line. */ {"XDS100V", UFTDI_JTAG_IFACE(0)}, }; /* * Set up a sysctl and tunable to en/disable the feature of skipping the * creation of tty devices for jtag interfaces. Enabled by default. */ static int skip_jtag_interfaces = 1; SYSCTL_INT(_hw_usb_uftdi, OID_AUTO, skip_jtag_interfaces, CTLFLAG_RWTUN, &skip_jtag_interfaces, 1, "Skip creating tty devices for jtag interfaces"); static boolean_t is_jtag_interface(struct usb_attach_arg *uaa, const struct usb_device_id *id) { int i, iface_bit; const char * product_name; const struct jtag_by_name *jbn; /* We only allocate 8 flag bits for jtag interface flags. */ if (uaa->info.bIfaceIndex >= UFTDI_JTAG_IFACES_MAX) return (0); iface_bit = UFTDI_JTAG_IFACE(uaa->info.bIfaceIndex); /* * If requested, search the name strings table and use the interface * bits from that table when the product name string matches, else use * the jtag interface bits from the main ID table. */ if ((id->driver_info & UFTDI_JTAG_MASK) == UFTDI_JTAG_CHECK_STRING) { product_name = usb_get_product(uaa->device); for (i = 0; i < nitems(jtag_products_by_name); i++) { jbn = &jtag_products_by_name[i]; if (strstr(product_name, jbn->product_name) != NULL && (jbn->jtag_interfaces & iface_bit) != 0) return (1); } } else if ((id->driver_info & iface_bit) != 0) return (1); return (0); } /* * Set up softc fields whose value depends on the device type. * * Note that the 2232C and 2232D devices are the same for our purposes. In the * silicon the difference is that the D series has CPU FIFO mode and C doesn't. * I haven't found any way of determining the C/D difference from info provided * by the chip other than trying to set CPU FIFO mode and having it work or not. * * Due to a hardware bug, a 232B chip without an eeprom reports itself as a * 232A, but if the serial number is also zero we know it's really a 232B. */ static void uftdi_devtype_setup(struct uftdi_softc *sc, struct usb_attach_arg *uaa) { struct usb_device_descriptor *dd; sc->sc_bcdDevice = uaa->info.bcdDevice; switch (uaa->info.bcdDevice) { case 0x200: dd = usbd_get_device_descriptor(sc->sc_udev); if (dd->iSerialNumber == 0) { sc->sc_devtype = DEVT_232B; } else { sc->sc_devtype = DEVT_232A; } sc->sc_ucom.sc_portno = 0; break; case 0x400: sc->sc_devtype = DEVT_232B; sc->sc_ucom.sc_portno = 0; break; case 0x500: sc->sc_devtype = DEVT_2232D; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; case 0x600: sc->sc_devtype = DEVT_232R; sc->sc_ucom.sc_portno = 0; break; case 0x700: sc->sc_devtype = DEVT_2232H; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; case 0x800: sc->sc_devtype = DEVT_4232H; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; case 0x900: sc->sc_devtype = DEVT_232H; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; case 0x1000: sc->sc_devtype = DEVT_230X; sc->sc_devflags |= DEVF_BAUDBITS_HINDEX; sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; break; default: if (uaa->info.bcdDevice < 0x200) { sc->sc_devtype = DEVT_SIO; sc->sc_hdrlen = 1; } else { sc->sc_devtype = DEVT_232R; device_printf(sc->sc_dev, "Warning: unknown FTDI " "device type, bcdDevice=0x%04x, assuming 232R\n", uaa->info.bcdDevice); } sc->sc_ucom.sc_portno = 0; break; } } static int uftdi_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); const struct usb_device_id *id; if (uaa->usb_mode != USB_MODE_HOST) { return (ENXIO); } if (uaa->info.bConfigIndex != UFTDI_CONFIG_INDEX) { return (ENXIO); } /* * Attach to all present interfaces unless this is a JTAG one, which * we leave for userland. */ id = usbd_lookup_id_by_info(uftdi_devs, sizeof(uftdi_devs), &uaa->info); if (id == NULL) return (ENXIO); if (skip_jtag_interfaces && is_jtag_interface(uaa, id)) { printf("%s: skipping JTAG interface #%d for '%s' at %u.%u\n", device_get_name(dev), uaa->info.bIfaceIndex, usb_get_product(uaa->device), usbd_get_bus_index(uaa->device), usbd_get_device_index(uaa->device)); return (ENXIO); } uaa->driver_info = id->driver_info; return (BUS_PROBE_SPECIFIC); } static int uftdi_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uftdi_softc *sc = device_get_softc(dev); int error; DPRINTF("\n"); sc->sc_udev = uaa->device; sc->sc_dev = dev; sc->sc_unit = device_get_unit(dev); sc->sc_bitmode = UFTDI_BITMODE_NONE; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, "uftdi", NULL, MTX_DEF); ucom_ref(&sc->sc_super_ucom); uftdi_devtype_setup(sc, uaa); error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, uftdi_config, UFTDI_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(dev, "allocating USB " "transfers failed\n"); goto detach; } /* clear stall at first run */ mtx_lock(&sc->sc_mtx); usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_WR]); usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_RD]); mtx_unlock(&sc->sc_mtx); /* set a valid "lcr" value */ sc->sc_last_lcr = (FTDI_SIO_SET_DATA_STOP_BITS_2 | FTDI_SIO_SET_DATA_PARITY_NONE | FTDI_SIO_SET_DATA_BITS(8)); + /* Indicate tx bits in sc_lsr can be used to determine busy vs idle. */ + ucom_use_lsr_txbits(&sc->sc_ucom); + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, &uftdi_callback, &sc->sc_mtx); if (error) { goto detach; } ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); return (0); /* success */ detach: uftdi_detach(dev); return (ENXIO); } static int uftdi_detach(device_t dev) { struct uftdi_softc *sc = device_get_softc(dev); ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); usbd_transfer_unsetup(sc->sc_xfer, UFTDI_N_TRANSFER); device_claim_softc(dev); uftdi_free_softc(sc); return (0); } UCOM_UNLOAD_DRAIN(uftdi); static void uftdi_free_softc(struct uftdi_softc *sc) { if (ucom_unref(&sc->sc_super_ucom)) { mtx_destroy(&sc->sc_mtx); device_free_softc(sc); } } static void uftdi_free(struct ucom_softc *ucom) { uftdi_free_softc(ucom->sc_parent); } static void uftdi_cfg_open(struct ucom_softc *ucom) { /* * This do-nothing open routine exists for the sole purpose of this * DPRINTF() so that you can see the point at which open gets called * when debugging is enabled. */ DPRINTF("\n"); } static void uftdi_cfg_close(struct ucom_softc *ucom) { /* * This do-nothing close routine exists for the sole purpose of this * DPRINTF() so that you can see the point at which close gets called * when debugging is enabled. */ DPRINTF("\n"); } static void uftdi_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct uftdi_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint32_t pktlen; uint32_t buflen; uint8_t buf[1]; DPRINTFN(3, "\n"); switch (USB_GET_STATE(xfer)) { default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); } /* FALLTHROUGH */ case USB_ST_SETUP: case USB_ST_TRANSFERRED: /* * If output packets don't require headers (the common case) we * can just load the buffer up with payload bytes all at once. * Otherwise, loop to format packets into the buffer while there * is data available, and room for a packet header and at least * one byte of payload. * * NOTE: The FTDI chip doesn't accept zero length * packets. This cannot happen because the "pktlen" * will always be non-zero when "ucom_get_data()" * returns non-zero which we check below. */ pc = usbd_xfer_get_frame(xfer, 0); if (sc->sc_hdrlen == 0) { if (ucom_get_data(&sc->sc_ucom, pc, 0, UFTDI_OBUFSIZE, &buflen) == 0) break; } else { buflen = 0; while (buflen < UFTDI_OBUFSIZE - sc->sc_hdrlen - 1 && ucom_get_data(&sc->sc_ucom, pc, buflen + sc->sc_hdrlen, UFTDI_OPKTSIZE - sc->sc_hdrlen, &pktlen) != 0) { buf[0] = FTDI_OUT_TAG(pktlen, sc->sc_ucom.sc_portno); usbd_copy_in(pc, buflen, buf, 1); buflen += pktlen + sc->sc_hdrlen; } } if (buflen != 0) { usbd_xfer_set_frame_len(xfer, 0, buflen); usbd_transfer_submit(xfer); } break; } } static void uftdi_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct uftdi_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; uint8_t buf[2]; uint8_t ftdi_msr; uint8_t msr; uint8_t lsr; int buflen; int pktlen; int pktmax; int offset; DPRINTFN(3, "\n"); usbd_xfer_status(xfer, &buflen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (buflen < UFTDI_IHDRSIZE) goto tr_setup; pc = usbd_xfer_get_frame(xfer, 0); pktmax = xfer->max_packet_size - UFTDI_IHDRSIZE; lsr = 0; msr = 0; offset = 0; /* * Extract packet headers and payload bytes from the buffer. - * Feed payload bytes to ucom/tty layer; OR-accumulate header - * status bits which are transient and could toggle with each - * packet. After processing all packets in the buffer, process - * the accumulated transient MSR and LSR values along with the + * Feed payload bytes to ucom/tty layer; OR-accumulate the + * receiver-related header status bits which are transient and + * could toggle with each packet, but for transmitter-related + * bits keep only the ones from the last packet. + * + * After processing all packets in the buffer, process the + * accumulated transient MSR and LSR values along with the * non-transient bits from the last packet header. */ while (buflen >= UFTDI_IHDRSIZE) { usbd_copy_out(pc, offset, buf, UFTDI_IHDRSIZE); offset += UFTDI_IHDRSIZE; buflen -= UFTDI_IHDRSIZE; + lsr &= ~(ULSR_TXRDY | ULSR_TSRE); lsr |= FTDI_GET_LSR(buf); if (FTDI_GET_MSR(buf) & FTDI_SIO_RI_MASK) msr |= SER_RI; pktlen = min(buflen, pktmax); if (pktlen != 0) { ucom_put_data(&sc->sc_ucom, pc, offset, pktlen); offset += pktlen; buflen -= pktlen; } } ftdi_msr = FTDI_GET_MSR(buf); if (ftdi_msr & FTDI_SIO_CTS_MASK) msr |= SER_CTS; if (ftdi_msr & FTDI_SIO_DSR_MASK) msr |= SER_DSR; if (ftdi_msr & FTDI_SIO_RI_MASK) msr |= SER_RI; if (ftdi_msr & FTDI_SIO_RLSD_MASK) msr |= SER_DCD; - if ((sc->sc_msr != msr) || - ((sc->sc_lsr & FTDI_LSR_MASK) != (lsr & FTDI_LSR_MASK))) { + if (sc->sc_msr != msr || sc->sc_lsr != lsr) { DPRINTF("status change msr=0x%02x (0x%02x) " "lsr=0x%02x (0x%02x)\n", msr, sc->sc_msr, lsr, sc->sc_lsr); sc->sc_msr = msr; sc->sc_lsr = lsr; ucom_status_change(&sc->sc_ucom); } /* FALLTHROUGH */ 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 uftdi_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) { struct uftdi_softc *sc = ucom->sc_parent; uint16_t wIndex = ucom->sc_portno; uint16_t wValue; struct usb_device_request req; DPRINTFN(2, "DTR=%u\n", onoff); wValue = onoff ? FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_MODEM_CTRL; USETW(req.wValue, wValue); USETW(req.wIndex, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void uftdi_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) { struct uftdi_softc *sc = ucom->sc_parent; uint16_t wIndex = ucom->sc_portno; uint16_t wValue; struct usb_device_request req; DPRINTFN(2, "RTS=%u\n", onoff); wValue = onoff ? FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_MODEM_CTRL; USETW(req.wValue, wValue); USETW(req.wIndex, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void uftdi_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) { struct uftdi_softc *sc = ucom->sc_parent; uint16_t wIndex = ucom->sc_portno; uint16_t wValue; struct usb_device_request req; DPRINTFN(2, "BREAK=%u\n", onoff); if (onoff) { sc->sc_last_lcr |= FTDI_SIO_SET_BREAK; } else { sc->sc_last_lcr &= ~FTDI_SIO_SET_BREAK; } wValue = sc->sc_last_lcr; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_DATA; USETW(req.wValue, wValue); USETW(req.wIndex, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } /* * Return true if the given speed is within operational tolerance of the target * speed. FTDI recommends that the hardware speed be within 3% of nominal. */ static inline boolean_t uftdi_baud_within_tolerance(uint64_t speed, uint64_t target) { return ((speed >= (target * 100) / 103) && (speed <= (target * 100) / 97)); } static int uftdi_sio_encode_baudrate(struct uftdi_softc *sc, speed_t speed, struct uftdi_param_config *cfg) { u_int i; const speed_t sio_speeds[] = { 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 }; /* * The original SIO chips were limited to a small choice of speeds * listed in an internal table of speeds chosen by an index value. */ for (i = 0; i < nitems(sio_speeds); ++i) { if (speed == sio_speeds[i]) { cfg->baud_lobits = i; cfg->baud_hibits = 0; return (0); } } return (ERANGE); } static int uftdi_encode_baudrate(struct uftdi_softc *sc, speed_t speed, struct uftdi_param_config *cfg) { static const uint8_t encoded_fraction[8] = {0, 3, 2, 4, 1, 5, 6, 7}; static const uint8_t roundoff_232a[16] = { 0, 1, 0, 1, 0, -1, 2, 1, 0, -1, -2, -3, 4, 3, 2, 1, }; uint32_t clk, divisor, fastclk_flag, frac, hwspeed; /* * If this chip has the fast clock capability and the speed is within * range, use the 12MHz clock, otherwise the standard clock is 3MHz. */ if ((sc->sc_devflags & DEVF_BAUDCLK_12M) && speed >= 1200) { clk = 12000000; fastclk_flag = (1 << 17); } else { clk = 3000000; fastclk_flag = 0; } /* * Make sure the requested speed is reachable with the available clock * and a 14-bit divisor. */ if (speed < (clk >> 14) || speed > clk) return (ERANGE); /* * Calculate the divisor, initially yielding a fixed point number with a * 4-bit (1/16ths) fraction, then round it to the nearest fraction the * hardware can handle. When the integral part of the divisor is * greater than one, the fractional part is in 1/8ths of the base clock. * The FT8U232AM chips can handle only 0.125, 0.250, and 0.5 fractions. * Later chips can handle all 1/8th fractions. * * If the integral part of the divisor is 1, a special rule applies: the * fractional part can only be .0 or .5 (this is a limitation of the * hardware). We handle this by truncating the fraction rather than * rounding, because this only applies to the two fastest speeds the * chip can achieve and rounding doesn't matter, either you've asked for * that exact speed or you've asked for something the chip can't do. * * For the FT8U232AM chips, use a roundoff table to adjust the result * to the nearest 1/8th fraction that is supported by the hardware, * leaving a fixed-point number with a 3-bit fraction which exactly * represents the math the hardware divider will do. For later-series * chips that support all 8 fractional divisors, just round 16ths to * 8ths by adding 1 and dividing by 2. */ divisor = (clk << 4) / speed; if ((divisor & 0xf) == 1) divisor &= 0xfffffff8; else if (sc->sc_devtype == DEVT_232A) divisor += roundoff_232a[divisor & 0x0f]; else divisor += 1; /* Rounds odd 16ths up to next 8th. */ divisor >>= 1; /* * Ensure the resulting hardware speed will be within operational * tolerance (within 3% of nominal). */ hwspeed = (clk << 3) / divisor; if (!uftdi_baud_within_tolerance(hwspeed, speed)) return (ERANGE); /* * Re-pack the divisor into hardware format. The lower 14-bits hold the * integral part, while the upper bits specify the fraction by indexing * a table of fractions within the hardware which is laid out as: * {0.0, 0.5, 0.25, 0.125, 0.325, 0.625, 0.725, 0.875} * The A-series chips only have the first four table entries; the * roundoff table logic above ensures that the fractional part for those * chips will be one of the first four values. * * When the divisor is 1 a special encoding applies: 1.0 is encoded as * 0.0, and 1.5 is encoded as 1.0. The rounding logic above has already * ensured that the fraction is either .0 or .5 if the integral is 1. */ frac = divisor & 0x07; divisor >>= 3; if (divisor == 1) { if (frac == 0) divisor = 0; /* 1.0 becomes 0.0 */ else frac = 0; /* 1.5 becomes 1.0 */ } divisor |= (encoded_fraction[frac] << 14) | fastclk_flag; cfg->baud_lobits = (uint16_t)divisor; cfg->baud_hibits = (uint16_t)(divisor >> 16); /* * If this chip requires the baud bits to be in the high byte of the * index word, move the bits up to that location. */ if (sc->sc_devflags & DEVF_BAUDBITS_HINDEX) { cfg->baud_hibits <<= 8; } return (0); } static int uftdi_set_parm_soft(struct ucom_softc *ucom, struct termios *t, struct uftdi_param_config *cfg) { struct uftdi_softc *sc = ucom->sc_parent; int err; memset(cfg, 0, sizeof(*cfg)); if (sc->sc_devtype == DEVT_SIO) err = uftdi_sio_encode_baudrate(sc, t->c_ospeed, cfg); else err = uftdi_encode_baudrate(sc, t->c_ospeed, cfg); if (err != 0) return (err); if (t->c_cflag & CSTOPB) cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_2; else cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_1; if (t->c_cflag & PARENB) { if (t->c_cflag & PARODD) { cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_ODD; } else { cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_EVEN; } } else { cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_NONE; } switch (t->c_cflag & CSIZE) { case CS5: cfg->lcr |= FTDI_SIO_SET_DATA_BITS(5); break; case CS6: cfg->lcr |= FTDI_SIO_SET_DATA_BITS(6); break; case CS7: cfg->lcr |= FTDI_SIO_SET_DATA_BITS(7); break; case CS8: cfg->lcr |= FTDI_SIO_SET_DATA_BITS(8); break; } if (t->c_cflag & CRTSCTS) { cfg->v_flow = FTDI_SIO_RTS_CTS_HS; } else if (t->c_iflag & (IXON | IXOFF)) { cfg->v_flow = FTDI_SIO_XON_XOFF_HS; cfg->v_start = t->c_cc[VSTART]; cfg->v_stop = t->c_cc[VSTOP]; } else { cfg->v_flow = FTDI_SIO_DISABLE_FLOW_CTRL; } return (0); } static int uftdi_pre_param(struct ucom_softc *ucom, struct termios *t) { struct uftdi_param_config cfg; DPRINTF("\n"); return (uftdi_set_parm_soft(ucom, t, &cfg)); } static void uftdi_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct uftdi_softc *sc = ucom->sc_parent; uint16_t wIndex = ucom->sc_portno; struct uftdi_param_config cfg; struct usb_device_request req; DPRINTF("\n"); if (uftdi_set_parm_soft(ucom, t, &cfg)) { /* should not happen */ return; } sc->sc_last_lcr = cfg.lcr; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_BAUD_RATE; USETW(req.wValue, cfg.baud_lobits); USETW(req.wIndex, cfg.baud_hibits | wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_DATA; USETW(req.wValue, cfg.lcr); USETW(req.wIndex, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_FLOW_CTRL; USETW2(req.wValue, cfg.v_stop, cfg.v_start); USETW2(req.wIndex, cfg.v_flow, wIndex); USETW(req.wLength, 0); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, NULL, 0, 1000); } static void uftdi_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) { struct uftdi_softc *sc = ucom->sc_parent; DPRINTFN(3, "msr=0x%02x lsr=0x%02x\n", sc->sc_msr, sc->sc_lsr); *msr = sc->sc_msr; *lsr = sc->sc_lsr; } static int uftdi_reset(struct ucom_softc *ucom, int reset_type) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; DPRINTFN(2, "\n"); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_RESET; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); USETW(req.wValue, reset_type); return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); } static int uftdi_set_bitmode(struct ucom_softc *ucom, uint8_t bitmode, uint8_t iomask) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; int rv; DPRINTFN(2, "\n"); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_BITMODE; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); if (bitmode == UFTDI_BITMODE_NONE) USETW2(req.wValue, 0, 0); else USETW2(req.wValue, (1 << bitmode), iomask); rv = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL); if (rv == USB_ERR_NORMAL_COMPLETION) sc->sc_bitmode = bitmode; return (rv); } static int uftdi_get_bitmode(struct ucom_softc *ucom, uint8_t *bitmode, uint8_t *iomask) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; DPRINTFN(2, "\n"); req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = FTDI_SIO_GET_BITMODE; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 1); USETW(req.wValue, 0); *bitmode = sc->sc_bitmode; return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, iomask)); } static int uftdi_set_latency(struct ucom_softc *ucom, int latency) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; DPRINTFN(2, "\n"); if (latency < 0 || latency > 255) return (USB_ERR_INVAL); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_LATENCY; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); USETW2(req.wValue, 0, latency); return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); } static int uftdi_get_latency(struct ucom_softc *ucom, int *latency) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; usb_error_t err; uint8_t buf; DPRINTFN(2, "\n"); req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = FTDI_SIO_GET_LATENCY; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 1); USETW(req.wValue, 0); err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, &buf); *latency = buf; return (err); } static int uftdi_set_event_char(struct ucom_softc *ucom, int echar) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; uint8_t enable; DPRINTFN(2, "\n"); enable = (echar == -1) ? 0 : 1; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_EVENT_CHAR; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); USETW2(req.wValue, enable, echar & 0xff); return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); } static int uftdi_set_error_char(struct ucom_softc *ucom, int echar) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; uint8_t enable; DPRINTFN(2, "\n"); enable = (echar == -1) ? 0 : 1; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_SET_ERROR_CHAR; USETW(req.wIndex, sc->sc_ucom.sc_portno); USETW(req.wLength, 0); USETW2(req.wValue, enable, echar & 0xff); return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); } static int uftdi_read_eeprom(struct ucom_softc *ucom, struct uftdi_eeio *eeio) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; usb_error_t err; uint16_t widx, wlength, woffset; DPRINTFN(3, "\n"); /* Offset and length must both be evenly divisible by two. */ if ((eeio->offset | eeio->length) & 0x01) return (EINVAL); woffset = eeio->offset / 2U; wlength = eeio->length / 2U; for (widx = 0; widx < wlength; widx++) { req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = FTDI_SIO_READ_EEPROM; USETW(req.wIndex, widx + woffset); USETW(req.wLength, 2); USETW(req.wValue, 0); err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, &eeio->data[widx]); if (err != USB_ERR_NORMAL_COMPLETION) return (err); } return (USB_ERR_NORMAL_COMPLETION); } static int uftdi_write_eeprom(struct ucom_softc *ucom, struct uftdi_eeio *eeio) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; usb_error_t err; uint16_t widx, wlength, woffset; DPRINTFN(3, "\n"); /* Offset and length must both be evenly divisible by two. */ if ((eeio->offset | eeio->length) & 0x01) return (EINVAL); woffset = eeio->offset / 2U; wlength = eeio->length / 2U; for (widx = 0; widx < wlength; widx++) { req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_WRITE_EEPROM; USETW(req.wIndex, widx + woffset); USETW(req.wLength, 0); USETW(req.wValue, eeio->data[widx]); err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL); if (err != USB_ERR_NORMAL_COMPLETION) return (err); } return (USB_ERR_NORMAL_COMPLETION); } static int uftdi_erase_eeprom(struct ucom_softc *ucom, int confirmation) { struct uftdi_softc *sc = ucom->sc_parent; usb_device_request_t req; usb_error_t err; DPRINTFN(2, "\n"); /* Small effort to prevent accidental erasure. */ if (confirmation != UFTDI_CONFIRM_ERASE) return (EINVAL); req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = FTDI_SIO_ERASE_EEPROM; USETW(req.wIndex, 0); USETW(req.wLength, 0); USETW(req.wValue, 0); err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL); return (err); } static int uftdi_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data, int flag, struct thread *td) { struct uftdi_softc *sc = ucom->sc_parent; int err; struct uftdi_bitmode * mode; switch (cmd) { case UFTDIIOC_RESET_IO: case UFTDIIOC_RESET_RX: case UFTDIIOC_RESET_TX: err = uftdi_reset(ucom, cmd == UFTDIIOC_RESET_IO ? FTDI_SIO_RESET_SIO : (cmd == UFTDIIOC_RESET_RX ? FTDI_SIO_RESET_PURGE_RX : FTDI_SIO_RESET_PURGE_TX)); break; case UFTDIIOC_SET_BITMODE: mode = (struct uftdi_bitmode *)data; err = uftdi_set_bitmode(ucom, mode->mode, mode->iomask); break; case UFTDIIOC_GET_BITMODE: mode = (struct uftdi_bitmode *)data; err = uftdi_get_bitmode(ucom, &mode->mode, &mode->iomask); break; case UFTDIIOC_SET_LATENCY: err = uftdi_set_latency(ucom, *((int *)data)); break; case UFTDIIOC_GET_LATENCY: err = uftdi_get_latency(ucom, (int *)data); break; case UFTDIIOC_SET_ERROR_CHAR: err = uftdi_set_error_char(ucom, *(int *)data); break; case UFTDIIOC_SET_EVENT_CHAR: err = uftdi_set_event_char(ucom, *(int *)data); break; case UFTDIIOC_GET_HWREV: *(int *)data = sc->sc_bcdDevice; err = 0; break; case UFTDIIOC_READ_EEPROM: err = uftdi_read_eeprom(ucom, (struct uftdi_eeio *)data); break; case UFTDIIOC_WRITE_EEPROM: err = uftdi_write_eeprom(ucom, (struct uftdi_eeio *)data); break; case UFTDIIOC_ERASE_EEPROM: err = uftdi_erase_eeprom(ucom, *(int *)data); break; default: return (ENOIOCTL); } if (err != USB_ERR_NORMAL_COMPLETION) return (EIO); return (0); } static void uftdi_start_read(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_RD]); } static void uftdi_stop_read(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_RD]); } static void uftdi_start_write(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_WR]); } static void uftdi_stop_write(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_WR]); } static void uftdi_poll(struct ucom_softc *ucom) { struct uftdi_softc *sc = ucom->sc_parent; usbd_transfer_poll(sc->sc_xfer, UFTDI_N_TRANSFER); } diff --git a/sys/dev/usb/serial/usb_serial.c b/sys/dev/usb/serial/usb_serial.c index 23ee3305a181..c5fc14b41b4d 100644 --- a/sys/dev/usb/serial/usb_serial.c +++ b/sys/dev/usb/serial/usb_serial.c @@ -1,1717 +1,1740 @@ /* $NetBSD: ucom.c,v 1.40 2001/11/13 06:24:54 lukem Exp $ */ /*- * Copyright (c) 2001-2003, 2005, 2008 * Shunsuke Akiyama . * 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 __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 (lennart@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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR ucom_debug #include #include #include #include #include "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 tsw_busy_t ucom_busy; 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, + .tsw_busy = ucom_busy, }; 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); } +static bool +ucom_busy(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + const uint8_t txidle = ULSR_TXRDY | ULSR_TSRE; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + DPRINTFN(3, "sc = %p lsr 0x%02x\n", sc, sc->sc_lsr); + + /* + * If the driver maintains the txidle bits in LSR, we can use them to + * determine whether the transmitter is busy or idle. Otherwise we have + * to assume it is idle to avoid hanging forever on tcdrain(3). + */ + if (sc->sc_flag & UCOM_FLAG_LSRTXIDLE) + return ((sc->sc_lsr & txidle) != txidle); + else + return (false); +} + /*------------------------------------------------------------------------* * 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 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/sys/dev/usb/serial/usb_serial.h b/sys/dev/usb/serial/usb_serial.h index 9fbd373b0736..2c53f445b6d7 100644 --- a/sys/dev/usb/serial/usb_serial.h +++ b/sys/dev/usb/serial/usb_serial.h @@ -1,221 +1,230 @@ /* $NetBSD: ucomvar.h,v 1.9 2001/01/23 21:56:17 augustss Exp $ */ /* $FreeBSD$ */ /*- * Copyright (c) 2001-2002, Shunsuke Akiyama . * 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 (lennart@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 #include #include #include #include /* 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 */ +#define UCOM_FLAG_LSRTXIDLE 0x0800 /* set if sc_lsr bits ULSR_TSRE+TXRDY work */ 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 *); + +static inline void +ucom_use_lsr_txbits(struct ucom_softc *sc) +{ + + sc->sc_flag |= UCOM_FLAG_LSRTXIDLE; +} + #endif /* _USB_SERIAL_H_ */