usb_serial.c revision 213872
1184610Salfred/* $NetBSD: ucom.c,v 1.40 2001/11/13 06:24:54 lukem Exp $ */ 2184610Salfred 3184610Salfred/*- 4184610Salfred * Copyright (c) 2001-2003, 2005, 2008 5184610Salfred * Shunsuke Akiyama <akiyama@jp.FreeBSD.org>. 6184610Salfred * All rights reserved. 7184610Salfred * 8184610Salfred * Redistribution and use in source and binary forms, with or without 9184610Salfred * modification, are permitted provided that the following conditions 10184610Salfred * are met: 11184610Salfred * 1. Redistributions of source code must retain the above copyright 12184610Salfred * notice, this list of conditions and the following disclaimer. 13184610Salfred * 2. Redistributions in binary form must reproduce the above copyright 14184610Salfred * notice, this list of conditions and the following disclaimer in the 15184610Salfred * documentation and/or other materials provided with the distribution. 16184610Salfred * 17184610Salfred * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18184610Salfred * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19184610Salfred * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20184610Salfred * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21184610Salfred * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22184610Salfred * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23184610Salfred * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24184610Salfred * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25184610Salfred * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26184610Salfred * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27184610Salfred * SUCH DAMAGE. 28184610Salfred */ 29184610Salfred 30184610Salfred#include <sys/cdefs.h> 31184610Salfred__FBSDID("$FreeBSD: head/sys/dev/usb/serial/usb_serial.c 213872 2010-10-14 21:45:41Z hselasky $"); 32184610Salfred 33184610Salfred/*- 34184610Salfred * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc. 35184610Salfred * All rights reserved. 36184610Salfred * 37184610Salfred * This code is derived from software contributed to The NetBSD Foundation 38184610Salfred * by Lennart Augustsson (lennart@augustsson.net) at 39184610Salfred * Carlstedt Research & Technology. 40184610Salfred * 41184610Salfred * Redistribution and use in source and binary forms, with or without 42184610Salfred * modification, are permitted provided that the following conditions 43184610Salfred * are met: 44184610Salfred * 1. Redistributions of source code must retain the above copyright 45184610Salfred * notice, this list of conditions and the following disclaimer. 46184610Salfred * 2. Redistributions in binary form must reproduce the above copyright 47184610Salfred * notice, this list of conditions and the following disclaimer in the 48184610Salfred * documentation and/or other materials provided with the distribution. 49184610Salfred * 3. All advertising materials mentioning features or use of this software 50184610Salfred * must display the following acknowledgement: 51184610Salfred * This product includes software developed by the NetBSD 52184610Salfred * Foundation, Inc. and its contributors. 53184610Salfred * 4. Neither the name of The NetBSD Foundation nor the names of its 54184610Salfred * contributors may be used to endorse or promote products derived 55184610Salfred * from this software without specific prior written permission. 56184610Salfred * 57184610Salfred * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 58184610Salfred * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 59184610Salfred * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 60184610Salfred * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 61184610Salfred * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 62184610Salfred * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 63184610Salfred * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 64184610Salfred * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 65184610Salfred * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 66184610Salfred * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 67184610Salfred * POSSIBILITY OF SUCH DAMAGE. 68184610Salfred */ 69184610Salfred 70194677Sthompsa#include <sys/stdint.h> 71194677Sthompsa#include <sys/stddef.h> 72194677Sthompsa#include <sys/param.h> 73194677Sthompsa#include <sys/queue.h> 74194677Sthompsa#include <sys/types.h> 75194677Sthompsa#include <sys/systm.h> 76194677Sthompsa#include <sys/kernel.h> 77194677Sthompsa#include <sys/bus.h> 78194677Sthompsa#include <sys/linker_set.h> 79194677Sthompsa#include <sys/module.h> 80194677Sthompsa#include <sys/lock.h> 81194677Sthompsa#include <sys/mutex.h> 82194677Sthompsa#include <sys/condvar.h> 83194677Sthompsa#include <sys/sysctl.h> 84194677Sthompsa#include <sys/sx.h> 85194677Sthompsa#include <sys/unistd.h> 86194677Sthompsa#include <sys/callout.h> 87194677Sthompsa#include <sys/malloc.h> 88194677Sthompsa#include <sys/priv.h> 89197570Sthompsa#include <sys/cons.h> 90197570Sthompsa#include <sys/kdb.h> 91194677Sthompsa 92188942Sthompsa#include <dev/usb/usb.h> 93194677Sthompsa#include <dev/usb/usbdi.h> 94194677Sthompsa#include <dev/usb/usbdi_util.h> 95184610Salfred 96194228Sthompsa#define USB_DEBUG_VAR ucom_debug 97188942Sthompsa#include <dev/usb/usb_debug.h> 98194677Sthompsa#include <dev/usb/usb_busdma.h> 99188942Sthompsa#include <dev/usb/usb_process.h> 100184610Salfred 101188942Sthompsa#include <dev/usb/serial/usb_serial.h> 102184610Salfred 103197570Sthompsa#include "opt_gdb.h" 104197570Sthompsa 105197570SthompsaSYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom"); 106197570Sthompsa 107207077Sthompsa#ifdef USB_DEBUG 108194228Sthompsastatic int ucom_debug = 0; 109184610Salfred 110192502SthompsaSYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RW, 111194228Sthompsa &ucom_debug, 0, "ucom debug level"); 112184610Salfred#endif 113184610Salfred 114197570Sthompsa#define UCOM_CONS_BUFSIZE 1024 115197570Sthompsa 116197570Sthompsastatic uint8_t ucom_cons_rx_buf[UCOM_CONS_BUFSIZE]; 117197570Sthompsastatic uint8_t ucom_cons_tx_buf[UCOM_CONS_BUFSIZE]; 118197570Sthompsa 119197570Sthompsastatic unsigned int ucom_cons_rx_low = 0; 120197570Sthompsastatic unsigned int ucom_cons_rx_high = 0; 121197570Sthompsa 122197570Sthompsastatic unsigned int ucom_cons_tx_low = 0; 123197570Sthompsastatic unsigned int ucom_cons_tx_high = 0; 124197570Sthompsa 125197570Sthompsastatic int ucom_cons_unit = -1; 126197570Sthompsastatic int ucom_cons_baud = 9600; 127197570Sthompsastatic struct ucom_softc *ucom_cons_softc = NULL; 128197570Sthompsa 129197570SthompsaTUNABLE_INT("hw.usb.ucom.cons_unit", &ucom_cons_unit); 130197570SthompsaSYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_unit, CTLFLAG_RW, 131197570Sthompsa &ucom_cons_unit, 0, "console unit number"); 132197570Sthompsa 133197570SthompsaTUNABLE_INT("hw.usb.ucom.cons_baud", &ucom_cons_baud); 134197570SthompsaSYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_baud, CTLFLAG_RW, 135197570Sthompsa &ucom_cons_baud, 0, "console baud rate"); 136197570Sthompsa 137194228Sthompsastatic usb_proc_callback_t ucom_cfg_start_transfers; 138194228Sthompsastatic usb_proc_callback_t ucom_cfg_open; 139194228Sthompsastatic usb_proc_callback_t ucom_cfg_close; 140194228Sthompsastatic usb_proc_callback_t ucom_cfg_line_state; 141194228Sthompsastatic usb_proc_callback_t ucom_cfg_status_change; 142194228Sthompsastatic usb_proc_callback_t ucom_cfg_param; 143184610Salfred 144194228Sthompsastatic uint8_t ucom_units_alloc(uint32_t, uint32_t *); 145194228Sthompsastatic void ucom_units_free(uint32_t, uint32_t); 146194228Sthompsastatic int ucom_attach_tty(struct ucom_softc *, uint32_t); 147194228Sthompsastatic void ucom_detach_tty(struct ucom_softc *); 148194228Sthompsastatic void ucom_queue_command(struct ucom_softc *, 149193045Sthompsa usb_proc_callback_t *, struct termios *pt, 150192984Sthompsa struct usb_proc_msg *t0, struct usb_proc_msg *t1); 151194228Sthompsastatic void ucom_shutdown(struct ucom_softc *); 152197570Sthompsastatic void ucom_ring(struct ucom_softc *, uint8_t); 153194228Sthompsastatic void ucom_break(struct ucom_softc *, uint8_t); 154194228Sthompsastatic void ucom_dtr(struct ucom_softc *, uint8_t); 155194228Sthompsastatic void ucom_rts(struct ucom_softc *, uint8_t); 156184610Salfred 157194228Sthompsastatic tsw_open_t ucom_open; 158194228Sthompsastatic tsw_close_t ucom_close; 159194228Sthompsastatic tsw_ioctl_t ucom_ioctl; 160194228Sthompsastatic tsw_modem_t ucom_modem; 161194228Sthompsastatic tsw_param_t ucom_param; 162194228Sthompsastatic tsw_outwakeup_t ucom_outwakeup; 163194228Sthompsastatic tsw_free_t ucom_free; 164184610Salfred 165194228Sthompsastatic struct ttydevsw ucom_class = { 166184610Salfred .tsw_flags = TF_INITLOCK | TF_CALLOUT, 167194228Sthompsa .tsw_open = ucom_open, 168194228Sthompsa .tsw_close = ucom_close, 169194228Sthompsa .tsw_outwakeup = ucom_outwakeup, 170194228Sthompsa .tsw_ioctl = ucom_ioctl, 171194228Sthompsa .tsw_param = ucom_param, 172194228Sthompsa .tsw_modem = ucom_modem, 173194228Sthompsa .tsw_free = ucom_free, 174184610Salfred}; 175184610Salfred 176188942SthompsaMODULE_DEPEND(ucom, usb, 1, 1, 1); 177188942SthompsaMODULE_VERSION(ucom, 1); 178184610Salfred 179197570Sthompsa#define UCOM_UNIT_MAX 0x200 /* exclusive */ 180184610Salfred#define UCOM_SUB_UNIT_MAX 0x100 /* exclusive */ 181184610Salfred 182194228Sthompsastatic uint8_t ucom_bitmap[(UCOM_UNIT_MAX + 7) / 8]; 183195146Sedstatic struct mtx ucom_bitmap_mtx; 184195146SedMTX_SYSINIT(ucom_bitmap_mtx, &ucom_bitmap_mtx, "ucom bitmap", MTX_DEF); 185184610Salfred 186184610Salfredstatic uint8_t 187194228Sthompsaucom_units_alloc(uint32_t sub_units, uint32_t *p_root_unit) 188184610Salfred{ 189184610Salfred uint32_t n; 190184610Salfred uint32_t o; 191184610Salfred uint32_t x; 192184610Salfred uint32_t max = UCOM_UNIT_MAX - (UCOM_UNIT_MAX % sub_units); 193184610Salfred uint8_t error = 1; 194184610Salfred 195195146Sed mtx_lock(&ucom_bitmap_mtx); 196184610Salfred 197184610Salfred for (n = 0; n < max; n += sub_units) { 198184610Salfred 199184610Salfred /* check for free consecutive bits */ 200184610Salfred 201184610Salfred for (o = 0; o < sub_units; o++) { 202184610Salfred 203184610Salfred x = n + o; 204184610Salfred 205194228Sthompsa if (ucom_bitmap[x / 8] & (1 << (x % 8))) { 206184610Salfred goto skip; 207184610Salfred } 208184610Salfred } 209184610Salfred 210184610Salfred /* allocate */ 211184610Salfred 212184610Salfred for (o = 0; o < sub_units; o++) { 213184610Salfred 214184610Salfred x = n + o; 215184610Salfred 216194228Sthompsa ucom_bitmap[x / 8] |= (1 << (x % 8)); 217184610Salfred } 218184610Salfred 219184610Salfred error = 0; 220184610Salfred 221184610Salfred break; 222184610Salfred 223184610Salfredskip: ; 224184610Salfred } 225184610Salfred 226195146Sed mtx_unlock(&ucom_bitmap_mtx); 227184610Salfred 228184610Salfred /* 229184610Salfred * Always set the variable pointed to by "p_root_unit" so that 230184610Salfred * the compiler does not think that it is used uninitialised: 231184610Salfred */ 232184610Salfred *p_root_unit = n; 233184610Salfred 234184610Salfred return (error); 235184610Salfred} 236184610Salfred 237184610Salfredstatic void 238194228Sthompsaucom_units_free(uint32_t root_unit, uint32_t sub_units) 239184610Salfred{ 240184610Salfred uint32_t x; 241184610Salfred 242195146Sed mtx_lock(&ucom_bitmap_mtx); 243184610Salfred 244184610Salfred while (sub_units--) { 245184610Salfred x = root_unit + sub_units; 246194228Sthompsa ucom_bitmap[x / 8] &= ~(1 << (x % 8)); 247184610Salfred } 248184610Salfred 249195146Sed mtx_unlock(&ucom_bitmap_mtx); 250184610Salfred} 251184610Salfred 252184610Salfred/* 253184610Salfred * "N" sub_units are setup at a time. All sub-units will 254184610Salfred * be given sequential unit numbers. The number of 255184610Salfred * sub-units can be used to differentiate among 256184610Salfred * different types of devices. 257184610Salfred * 258188413Sthompsa * The mutex pointed to by "mtx" is applied before all 259188413Sthompsa * callbacks are called back. Also "mtx" must be applied 260188413Sthompsa * before calling into the ucom-layer! 261184610Salfred */ 262184610Salfredint 263194228Sthompsaucom_attach(struct ucom_super_softc *ssc, struct ucom_softc *sc, 264184610Salfred uint32_t sub_units, void *parent, 265192984Sthompsa const struct ucom_callback *callback, struct mtx *mtx) 266184610Salfred{ 267184610Salfred uint32_t n; 268184610Salfred uint32_t root_unit; 269184610Salfred int error = 0; 270184610Salfred 271184610Salfred if ((sc == NULL) || 272184610Salfred (sub_units == 0) || 273184610Salfred (sub_units > UCOM_SUB_UNIT_MAX) || 274184610Salfred (callback == NULL)) { 275184610Salfred return (EINVAL); 276184610Salfred } 277188413Sthompsa 278188413Sthompsa /* XXX unit management does not really belong here */ 279194228Sthompsa if (ucom_units_alloc(sub_units, &root_unit)) { 280184610Salfred return (ENOMEM); 281184610Salfred } 282188413Sthompsa 283194228Sthompsa error = usb_proc_create(&ssc->sc_tq, mtx, "ucom", USB_PRI_MED); 284188413Sthompsa if (error) { 285194228Sthompsa ucom_units_free(root_unit, sub_units); 286188413Sthompsa return (error); 287184610Salfred } 288188413Sthompsa 289188413Sthompsa for (n = 0; n != sub_units; n++, sc++) { 290184610Salfred sc->sc_unit = root_unit + n; 291184610Salfred sc->sc_local_unit = n; 292184610Salfred sc->sc_super = ssc; 293188413Sthompsa sc->sc_mtx = mtx; 294184610Salfred sc->sc_parent = parent; 295184610Salfred sc->sc_callback = callback; 296184610Salfred 297194228Sthompsa error = ucom_attach_tty(sc, sub_units); 298184610Salfred if (error) { 299194228Sthompsa ucom_detach(ssc, sc - n, n); 300194228Sthompsa ucom_units_free(root_unit + n, sub_units - n); 301188413Sthompsa return (error); 302184610Salfred } 303184610Salfred sc->sc_flag |= UCOM_FLAG_ATTACHED; 304184610Salfred } 305188413Sthompsa return (0); 306184610Salfred} 307184610Salfred 308188413Sthompsa/* 309188413Sthompsa * NOTE: the following function will do nothing if 310184610Salfred * the structure pointed to by "ssc" and "sc" is zero. 311184610Salfred */ 312184610Salfredvoid 313194228Sthompsaucom_detach(struct ucom_super_softc *ssc, struct ucom_softc *sc, 314184610Salfred uint32_t sub_units) 315184610Salfred{ 316184610Salfred uint32_t n; 317184610Salfred 318194228Sthompsa usb_proc_drain(&ssc->sc_tq); 319184610Salfred 320188413Sthompsa for (n = 0; n != sub_units; n++, sc++) { 321184610Salfred if (sc->sc_flag & UCOM_FLAG_ATTACHED) { 322184610Salfred 323194228Sthompsa ucom_detach_tty(sc); 324184610Salfred 325194228Sthompsa ucom_units_free(sc->sc_unit, 1); 326184610Salfred 327184610Salfred /* avoid duplicate detach: */ 328184610Salfred sc->sc_flag &= ~UCOM_FLAG_ATTACHED; 329184610Salfred } 330184610Salfred } 331194228Sthompsa usb_proc_free(&ssc->sc_tq); 332184610Salfred} 333184610Salfred 334184610Salfredstatic int 335194228Sthompsaucom_attach_tty(struct ucom_softc *sc, uint32_t sub_units) 336184610Salfred{ 337184610Salfred struct tty *tp; 338184610Salfred int error = 0; 339184610Salfred char buf[32]; /* temporary TTY device name buffer */ 340184610Salfred 341194228Sthompsa tp = tty_alloc_mutex(&ucom_class, sc, sc->sc_mtx); 342184610Salfred if (tp == NULL) { 343184610Salfred error = ENOMEM; 344184610Salfred goto done; 345184610Salfred } 346184610Salfred DPRINTF("tp = %p, unit = %d\n", tp, sc->sc_unit); 347184610Salfred 348184610Salfred buf[0] = 0; /* set some default value */ 349184610Salfred 350184610Salfred /* Check if the client has a custom TTY name */ 351194228Sthompsa if (sc->sc_callback->ucom_tty_name) { 352194228Sthompsa sc->sc_callback->ucom_tty_name(sc, buf, 353184610Salfred sizeof(buf), sc->sc_local_unit); 354184610Salfred } 355184610Salfred if (buf[0] == 0) { 356184610Salfred /* Use default TTY name */ 357188413Sthompsa if (sub_units > 1) { 358188413Sthompsa /* multiple modems in one */ 359188413Sthompsa if (snprintf(buf, sizeof(buf), "U%u.%u", 360188413Sthompsa sc->sc_unit - sc->sc_local_unit, 361188413Sthompsa sc->sc_local_unit)) { 362188413Sthompsa /* ignore */ 363188413Sthompsa } 364188413Sthompsa } else { 365188413Sthompsa /* single modem */ 366188413Sthompsa if (snprintf(buf, sizeof(buf), "U%u", sc->sc_unit)) { 367188413Sthompsa /* ignore */ 368188413Sthompsa } 369184610Salfred } 370184610Salfred } 371184610Salfred tty_makedev(tp, NULL, "%s", buf); 372184610Salfred 373184610Salfred sc->sc_tty = tp; 374184610Salfred 375184610Salfred DPRINTF("ttycreate: %s\n", buf); 376194228Sthompsa cv_init(&sc->sc_cv, "ucom"); 377184610Salfred 378197570Sthompsa /* Check if this device should be a console */ 379197570Sthompsa if ((ucom_cons_softc == NULL) && 380197570Sthompsa (sc->sc_unit == ucom_cons_unit)) { 381197570Sthompsa 382197570Sthompsa struct termios t; 383197570Sthompsa 384197570Sthompsa ucom_cons_softc = sc; 385197570Sthompsa 386197570Sthompsa memset(&t, 0, sizeof(t)); 387197570Sthompsa t.c_ispeed = ucom_cons_baud; 388197570Sthompsa t.c_ospeed = t.c_ispeed; 389197570Sthompsa t.c_cflag = CS8; 390197570Sthompsa 391197570Sthompsa mtx_lock(ucom_cons_softc->sc_mtx); 392197570Sthompsa ucom_cons_rx_low = 0; 393197570Sthompsa ucom_cons_rx_high = 0; 394197570Sthompsa ucom_cons_tx_low = 0; 395197570Sthompsa ucom_cons_tx_high = 0; 396197570Sthompsa sc->sc_flag |= UCOM_FLAG_CONSOLE; 397197570Sthompsa ucom_open(ucom_cons_softc->sc_tty); 398197570Sthompsa ucom_param(ucom_cons_softc->sc_tty, &t); 399197570Sthompsa mtx_unlock(ucom_cons_softc->sc_mtx); 400197570Sthompsa } 401184610Salfreddone: 402184610Salfred return (error); 403184610Salfred} 404184610Salfred 405184610Salfredstatic void 406194228Sthompsaucom_detach_tty(struct ucom_softc *sc) 407184610Salfred{ 408184610Salfred struct tty *tp = sc->sc_tty; 409184610Salfred 410184610Salfred DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty); 411184610Salfred 412197570Sthompsa if (sc->sc_flag & UCOM_FLAG_CONSOLE) { 413197570Sthompsa mtx_lock(ucom_cons_softc->sc_mtx); 414197570Sthompsa ucom_close(ucom_cons_softc->sc_tty); 415197570Sthompsa mtx_unlock(ucom_cons_softc->sc_mtx); 416197570Sthompsa ucom_cons_softc = NULL; 417197570Sthompsa } 418197570Sthompsa 419184610Salfred /* the config thread has been stopped when we get here */ 420184610Salfred 421188413Sthompsa mtx_lock(sc->sc_mtx); 422184610Salfred sc->sc_flag |= UCOM_FLAG_GONE; 423197570Sthompsa sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_LL_READY); 424188413Sthompsa mtx_unlock(sc->sc_mtx); 425184610Salfred if (tp) { 426184610Salfred tty_lock(tp); 427184610Salfred 428194228Sthompsa ucom_close(tp); /* close, if any */ 429184610Salfred 430184610Salfred tty_rel_gone(tp); 431184610Salfred 432188413Sthompsa mtx_lock(sc->sc_mtx); 433184610Salfred /* Wait for the callback after the TTY is torn down */ 434184610Salfred while (sc->sc_ttyfreed == 0) 435194227Sthompsa cv_wait(&sc->sc_cv, sc->sc_mtx); 436184610Salfred /* 437184610Salfred * make sure that read and write transfers are stopped 438184610Salfred */ 439194228Sthompsa if (sc->sc_callback->ucom_stop_read) { 440194228Sthompsa (sc->sc_callback->ucom_stop_read) (sc); 441184610Salfred } 442194228Sthompsa if (sc->sc_callback->ucom_stop_write) { 443194228Sthompsa (sc->sc_callback->ucom_stop_write) (sc); 444184610Salfred } 445188413Sthompsa mtx_unlock(sc->sc_mtx); 446184610Salfred } 447194227Sthompsa cv_destroy(&sc->sc_cv); 448184610Salfred} 449184610Salfred 450184610Salfredstatic void 451194228Sthompsaucom_queue_command(struct ucom_softc *sc, 452193045Sthompsa usb_proc_callback_t *fn, struct termios *pt, 453192984Sthompsa struct usb_proc_msg *t0, struct usb_proc_msg *t1) 454184610Salfred{ 455192984Sthompsa struct ucom_super_softc *ssc = sc->sc_super; 456192984Sthompsa struct ucom_param_task *task; 457187176Sthompsa 458188413Sthompsa mtx_assert(sc->sc_mtx, MA_OWNED); 459188413Sthompsa 460194228Sthompsa if (usb_proc_is_gone(&ssc->sc_tq)) { 461187176Sthompsa DPRINTF("proc is gone\n"); 462188413Sthompsa return; /* nothing to do */ 463187176Sthompsa } 464188413Sthompsa /* 465188413Sthompsa * NOTE: The task cannot get executed before we drop the 466188413Sthompsa * "sc_mtx" mutex. It is safe to update fields in the message 467188413Sthompsa * structure after that the message got queued. 468188413Sthompsa */ 469192984Sthompsa task = (struct ucom_param_task *) 470194228Sthompsa usb_proc_msignal(&ssc->sc_tq, t0, t1); 471187176Sthompsa 472188413Sthompsa /* Setup callback and softc pointers */ 473188413Sthompsa task->hdr.pm_callback = fn; 474188413Sthompsa task->sc = sc; 475184610Salfred 476188413Sthompsa /* 477188413Sthompsa * Make a copy of the termios. This field is only present if 478188413Sthompsa * the "pt" field is not NULL. 479188413Sthompsa */ 480188413Sthompsa if (pt != NULL) 481188413Sthompsa task->termios_copy = *pt; 482184610Salfred 483188413Sthompsa /* 484188413Sthompsa * Closing the device should be synchronous. 485188413Sthompsa */ 486194228Sthompsa if (fn == ucom_cfg_close) 487194228Sthompsa usb_proc_mwait(&ssc->sc_tq, t0, t1); 488188413Sthompsa 489190742Sthompsa /* 490190742Sthompsa * In case of multiple configure requests, 491190742Sthompsa * keep track of the last one! 492190742Sthompsa */ 493194228Sthompsa if (fn == ucom_cfg_start_transfers) 494190742Sthompsa sc->sc_last_start_xfer = &task->hdr; 495184610Salfred} 496184610Salfred 497184610Salfredstatic void 498194228Sthompsaucom_shutdown(struct ucom_softc *sc) 499184610Salfred{ 500184610Salfred struct tty *tp = sc->sc_tty; 501184610Salfred 502188413Sthompsa mtx_assert(sc->sc_mtx, MA_OWNED); 503184610Salfred 504184610Salfred DPRINTF("\n"); 505184610Salfred 506184610Salfred /* 507184610Salfred * Hang up if necessary: 508184610Salfred */ 509184610Salfred if (tp->t_termios.c_cflag & HUPCL) { 510194228Sthompsa ucom_modem(tp, 0, SER_DTR); 511184610Salfred } 512184610Salfred} 513184610Salfred 514184610Salfred/* 515184610Salfred * Return values: 516184610Salfred * 0: normal 517188413Sthompsa * else: taskqueue is draining or gone 518184610Salfred */ 519184610Salfreduint8_t 520194228Sthompsaucom_cfg_is_gone(struct ucom_softc *sc) 521184610Salfred{ 522192984Sthompsa struct ucom_super_softc *ssc = sc->sc_super; 523184610Salfred 524194228Sthompsa return (usb_proc_is_gone(&ssc->sc_tq)); 525184610Salfred} 526184610Salfred 527184610Salfredstatic void 528194228Sthompsaucom_cfg_start_transfers(struct usb_proc_msg *_task) 529184610Salfred{ 530192984Sthompsa struct ucom_cfg_task *task = 531192984Sthompsa (struct ucom_cfg_task *)_task; 532192984Sthompsa struct ucom_softc *sc = task->sc; 533187176Sthompsa 534184610Salfred if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { 535184610Salfred return; 536184610Salfred } 537184610Salfred if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { 538184610Salfred /* TTY device closed */ 539184610Salfred return; 540184610Salfred } 541184610Salfred 542190742Sthompsa if (_task == sc->sc_last_start_xfer) 543190742Sthompsa sc->sc_flag |= UCOM_FLAG_GP_DATA; 544190742Sthompsa 545194228Sthompsa if (sc->sc_callback->ucom_start_read) { 546194228Sthompsa (sc->sc_callback->ucom_start_read) (sc); 547184610Salfred } 548194228Sthompsa if (sc->sc_callback->ucom_start_write) { 549194228Sthompsa (sc->sc_callback->ucom_start_write) (sc); 550184610Salfred } 551184610Salfred} 552184610Salfred 553184610Salfredstatic void 554194228Sthompsaucom_start_transfers(struct ucom_softc *sc) 555184610Salfred{ 556184610Salfred if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { 557184610Salfred return; 558184610Salfred } 559184610Salfred /* 560188413Sthompsa * Make sure that data transfers are started in both 561188413Sthompsa * directions: 562184610Salfred */ 563194228Sthompsa if (sc->sc_callback->ucom_start_read) { 564194228Sthompsa (sc->sc_callback->ucom_start_read) (sc); 565184610Salfred } 566194228Sthompsa if (sc->sc_callback->ucom_start_write) { 567194228Sthompsa (sc->sc_callback->ucom_start_write) (sc); 568184610Salfred } 569184610Salfred} 570184610Salfred 571184610Salfredstatic void 572194228Sthompsaucom_cfg_open(struct usb_proc_msg *_task) 573184610Salfred{ 574192984Sthompsa struct ucom_cfg_task *task = 575192984Sthompsa (struct ucom_cfg_task *)_task; 576192984Sthompsa struct ucom_softc *sc = task->sc; 577187176Sthompsa 578184610Salfred DPRINTF("\n"); 579184610Salfred 580184610Salfred if (sc->sc_flag & UCOM_FLAG_LL_READY) { 581184610Salfred 582184610Salfred /* already opened */ 583184610Salfred 584184610Salfred } else { 585184610Salfred 586184610Salfred sc->sc_flag |= UCOM_FLAG_LL_READY; 587184610Salfred 588194228Sthompsa if (sc->sc_callback->ucom_cfg_open) { 589194228Sthompsa (sc->sc_callback->ucom_cfg_open) (sc); 590184610Salfred 591184610Salfred /* wait a little */ 592194228Sthompsa usb_pause_mtx(sc->sc_mtx, hz / 10); 593184610Salfred } 594184610Salfred } 595184610Salfred} 596184610Salfred 597184610Salfredstatic int 598194228Sthompsaucom_open(struct tty *tp) 599184610Salfred{ 600192984Sthompsa struct ucom_softc *sc = tty_softc(tp); 601184610Salfred int error; 602184610Salfred 603188413Sthompsa mtx_assert(sc->sc_mtx, MA_OWNED); 604184610Salfred 605184610Salfred if (sc->sc_flag & UCOM_FLAG_GONE) { 606184610Salfred return (ENXIO); 607184610Salfred } 608184610Salfred if (sc->sc_flag & UCOM_FLAG_HL_READY) { 609184610Salfred /* already opened */ 610184610Salfred return (0); 611184610Salfred } 612184610Salfred DPRINTF("tp = %p\n", tp); 613184610Salfred 614194228Sthompsa if (sc->sc_callback->ucom_pre_open) { 615184610Salfred /* 616184610Salfred * give the lower layer a chance to disallow TTY open, for 617184610Salfred * example if the device is not present: 618184610Salfred */ 619194228Sthompsa error = (sc->sc_callback->ucom_pre_open) (sc); 620184610Salfred if (error) { 621184610Salfred return (error); 622184610Salfred } 623184610Salfred } 624184610Salfred sc->sc_flag |= UCOM_FLAG_HL_READY; 625184610Salfred 626184610Salfred /* Disable transfers */ 627184610Salfred sc->sc_flag &= ~UCOM_FLAG_GP_DATA; 628184610Salfred 629184610Salfred sc->sc_lsr = 0; 630184610Salfred sc->sc_msr = 0; 631184610Salfred sc->sc_mcr = 0; 632184610Salfred 633188413Sthompsa /* reset programmed line state */ 634188413Sthompsa sc->sc_pls_curr = 0; 635188413Sthompsa sc->sc_pls_set = 0; 636188413Sthompsa sc->sc_pls_clr = 0; 637184610Salfred 638194228Sthompsa ucom_queue_command(sc, ucom_cfg_open, NULL, 639188413Sthompsa &sc->sc_open_task[0].hdr, 640188413Sthompsa &sc->sc_open_task[1].hdr); 641184610Salfred 642188413Sthompsa /* Queue transfer enable command last */ 643194228Sthompsa ucom_queue_command(sc, ucom_cfg_start_transfers, NULL, 644188413Sthompsa &sc->sc_start_task[0].hdr, 645188413Sthompsa &sc->sc_start_task[1].hdr); 646188413Sthompsa 647194228Sthompsa ucom_modem(tp, SER_DTR | SER_RTS, 0); 648184610Salfred 649197570Sthompsa ucom_ring(sc, 0); 650197570Sthompsa 651194228Sthompsa ucom_break(sc, 0); 652184610Salfred 653194228Sthompsa ucom_status_change(sc); 654184610Salfred 655184610Salfred return (0); 656184610Salfred} 657184610Salfred 658184610Salfredstatic void 659194228Sthompsaucom_cfg_close(struct usb_proc_msg *_task) 660184610Salfred{ 661192984Sthompsa struct ucom_cfg_task *task = 662192984Sthompsa (struct ucom_cfg_task *)_task; 663192984Sthompsa struct ucom_softc *sc = task->sc; 664187176Sthompsa 665184610Salfred DPRINTF("\n"); 666184610Salfred 667184610Salfred if (sc->sc_flag & UCOM_FLAG_LL_READY) { 668192820Sthompsa sc->sc_flag &= ~UCOM_FLAG_LL_READY; 669194228Sthompsa if (sc->sc_callback->ucom_cfg_close) 670194228Sthompsa (sc->sc_callback->ucom_cfg_close) (sc); 671184610Salfred } else { 672184610Salfred /* already closed */ 673184610Salfred } 674184610Salfred} 675184610Salfred 676184610Salfredstatic void 677194228Sthompsaucom_close(struct tty *tp) 678184610Salfred{ 679192984Sthompsa struct ucom_softc *sc = tty_softc(tp); 680184610Salfred 681188413Sthompsa mtx_assert(sc->sc_mtx, MA_OWNED); 682188413Sthompsa 683184610Salfred DPRINTF("tp=%p\n", tp); 684184610Salfred 685184610Salfred if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { 686184610Salfred DPRINTF("tp=%p already closed\n", tp); 687184610Salfred return; 688184610Salfred } 689194228Sthompsa ucom_shutdown(sc); 690184610Salfred 691194228Sthompsa ucom_queue_command(sc, ucom_cfg_close, NULL, 692188413Sthompsa &sc->sc_close_task[0].hdr, 693188413Sthompsa &sc->sc_close_task[1].hdr); 694184610Salfred 695192820Sthompsa sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW); 696184610Salfred 697194228Sthompsa if (sc->sc_callback->ucom_stop_read) { 698194228Sthompsa (sc->sc_callback->ucom_stop_read) (sc); 699184610Salfred } 700184610Salfred} 701184610Salfred 702184610Salfredstatic int 703194228Sthompsaucom_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) 704184610Salfred{ 705192984Sthompsa struct ucom_softc *sc = tty_softc(tp); 706184610Salfred int error; 707184610Salfred 708188413Sthompsa mtx_assert(sc->sc_mtx, MA_OWNED); 709184610Salfred 710184610Salfred if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { 711184610Salfred return (EIO); 712184610Salfred } 713184610Salfred DPRINTF("cmd = 0x%08lx\n", cmd); 714184610Salfred 715184610Salfred switch (cmd) { 716197570Sthompsa#if 0 717197570Sthompsa case TIOCSRING: 718197570Sthompsa ucom_ring(sc, 1); 719197570Sthompsa error = 0; 720197570Sthompsa break; 721197570Sthompsa case TIOCCRING: 722197570Sthompsa ucom_ring(sc, 0); 723197570Sthompsa error = 0; 724197570Sthompsa break; 725197570Sthompsa#endif 726184610Salfred case TIOCSBRK: 727194228Sthompsa ucom_break(sc, 1); 728184610Salfred error = 0; 729184610Salfred break; 730184610Salfred case TIOCCBRK: 731194228Sthompsa ucom_break(sc, 0); 732184610Salfred error = 0; 733184610Salfred break; 734184610Salfred default: 735194228Sthompsa if (sc->sc_callback->ucom_ioctl) { 736194228Sthompsa error = (sc->sc_callback->ucom_ioctl) 737184610Salfred (sc, cmd, data, 0, td); 738184610Salfred } else { 739184610Salfred error = ENOIOCTL; 740184610Salfred } 741184610Salfred break; 742184610Salfred } 743184610Salfred return (error); 744184610Salfred} 745184610Salfred 746184610Salfredstatic int 747194228Sthompsaucom_modem(struct tty *tp, int sigon, int sigoff) 748184610Salfred{ 749192984Sthompsa struct ucom_softc *sc = tty_softc(tp); 750184610Salfred uint8_t onoff; 751184610Salfred 752188413Sthompsa mtx_assert(sc->sc_mtx, MA_OWNED); 753184610Salfred 754184610Salfred if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { 755184610Salfred return (0); 756184610Salfred } 757184610Salfred if ((sigon == 0) && (sigoff == 0)) { 758184610Salfred 759184610Salfred if (sc->sc_mcr & SER_DTR) { 760184610Salfred sigon |= SER_DTR; 761184610Salfred } 762184610Salfred if (sc->sc_mcr & SER_RTS) { 763184610Salfred sigon |= SER_RTS; 764184610Salfred } 765184610Salfred if (sc->sc_msr & SER_CTS) { 766184610Salfred sigon |= SER_CTS; 767184610Salfred } 768184610Salfred if (sc->sc_msr & SER_DCD) { 769184610Salfred sigon |= SER_DCD; 770184610Salfred } 771184610Salfred if (sc->sc_msr & SER_DSR) { 772184610Salfred sigon |= SER_DSR; 773184610Salfred } 774184610Salfred if (sc->sc_msr & SER_RI) { 775184610Salfred sigon |= SER_RI; 776184610Salfred } 777184610Salfred return (sigon); 778184610Salfred } 779184610Salfred if (sigon & SER_DTR) { 780184610Salfred sc->sc_mcr |= SER_DTR; 781184610Salfred } 782184610Salfred if (sigoff & SER_DTR) { 783184610Salfred sc->sc_mcr &= ~SER_DTR; 784184610Salfred } 785184610Salfred if (sigon & SER_RTS) { 786184610Salfred sc->sc_mcr |= SER_RTS; 787184610Salfred } 788184610Salfred if (sigoff & SER_RTS) { 789184610Salfred sc->sc_mcr &= ~SER_RTS; 790184610Salfred } 791184610Salfred onoff = (sc->sc_mcr & SER_DTR) ? 1 : 0; 792194228Sthompsa ucom_dtr(sc, onoff); 793184610Salfred 794184610Salfred onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0; 795194228Sthompsa ucom_rts(sc, onoff); 796184610Salfred 797184610Salfred return (0); 798184610Salfred} 799184610Salfred 800184610Salfredstatic void 801194228Sthompsaucom_cfg_line_state(struct usb_proc_msg *_task) 802184610Salfred{ 803192984Sthompsa struct ucom_cfg_task *task = 804192984Sthompsa (struct ucom_cfg_task *)_task; 805192984Sthompsa struct ucom_softc *sc = task->sc; 806188413Sthompsa uint8_t notch_bits; 807188413Sthompsa uint8_t any_bits; 808188413Sthompsa uint8_t prev_value; 809188413Sthompsa uint8_t last_value; 810188413Sthompsa uint8_t mask; 811187176Sthompsa 812184610Salfred if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { 813184610Salfred return; 814184610Salfred } 815184610Salfred 816188413Sthompsa mask = 0; 817188413Sthompsa /* compute callback mask */ 818194228Sthompsa if (sc->sc_callback->ucom_cfg_set_dtr) 819188413Sthompsa mask |= UCOM_LS_DTR; 820194228Sthompsa if (sc->sc_callback->ucom_cfg_set_rts) 821188413Sthompsa mask |= UCOM_LS_RTS; 822194228Sthompsa if (sc->sc_callback->ucom_cfg_set_break) 823188413Sthompsa mask |= UCOM_LS_BREAK; 824197570Sthompsa if (sc->sc_callback->ucom_cfg_set_ring) 825197570Sthompsa mask |= UCOM_LS_RING; 826184610Salfred 827188413Sthompsa /* compute the bits we are to program */ 828188413Sthompsa notch_bits = (sc->sc_pls_set & sc->sc_pls_clr) & mask; 829188413Sthompsa any_bits = (sc->sc_pls_set | sc->sc_pls_clr) & mask; 830188413Sthompsa prev_value = sc->sc_pls_curr ^ notch_bits; 831188413Sthompsa last_value = sc->sc_pls_curr; 832187176Sthompsa 833188413Sthompsa /* reset programmed line state */ 834188413Sthompsa sc->sc_pls_curr = 0; 835188413Sthompsa sc->sc_pls_set = 0; 836188413Sthompsa sc->sc_pls_clr = 0; 837188413Sthompsa 838188413Sthompsa /* ensure that we don't loose any levels */ 839188413Sthompsa if (notch_bits & UCOM_LS_DTR) 840194228Sthompsa sc->sc_callback->ucom_cfg_set_dtr(sc, 841188413Sthompsa (prev_value & UCOM_LS_DTR) ? 1 : 0); 842188413Sthompsa if (notch_bits & UCOM_LS_RTS) 843194228Sthompsa sc->sc_callback->ucom_cfg_set_rts(sc, 844188413Sthompsa (prev_value & UCOM_LS_RTS) ? 1 : 0); 845188413Sthompsa if (notch_bits & UCOM_LS_BREAK) 846194228Sthompsa sc->sc_callback->ucom_cfg_set_break(sc, 847188413Sthompsa (prev_value & UCOM_LS_BREAK) ? 1 : 0); 848197570Sthompsa if (notch_bits & UCOM_LS_RING) 849197570Sthompsa sc->sc_callback->ucom_cfg_set_ring(sc, 850197570Sthompsa (prev_value & UCOM_LS_RING) ? 1 : 0); 851188413Sthompsa 852188413Sthompsa /* set last value */ 853188413Sthompsa if (any_bits & UCOM_LS_DTR) 854194228Sthompsa sc->sc_callback->ucom_cfg_set_dtr(sc, 855188413Sthompsa (last_value & UCOM_LS_DTR) ? 1 : 0); 856188413Sthompsa if (any_bits & UCOM_LS_RTS) 857194228Sthompsa sc->sc_callback->ucom_cfg_set_rts(sc, 858188413Sthompsa (last_value & UCOM_LS_RTS) ? 1 : 0); 859188413Sthompsa if (any_bits & UCOM_LS_BREAK) 860194228Sthompsa sc->sc_callback->ucom_cfg_set_break(sc, 861188413Sthompsa (last_value & UCOM_LS_BREAK) ? 1 : 0); 862197570Sthompsa if (any_bits & UCOM_LS_RING) 863197570Sthompsa sc->sc_callback->ucom_cfg_set_ring(sc, 864197570Sthompsa (last_value & UCOM_LS_RING) ? 1 : 0); 865187176Sthompsa} 866187176Sthompsa 867187176Sthompsastatic void 868194228Sthompsaucom_line_state(struct ucom_softc *sc, 869188413Sthompsa uint8_t set_bits, uint8_t clear_bits) 870184610Salfred{ 871188413Sthompsa mtx_assert(sc->sc_mtx, MA_OWNED); 872184610Salfred 873184610Salfred if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { 874184610Salfred return; 875184610Salfred } 876184610Salfred 877188413Sthompsa DPRINTF("on=0x%02x, off=0x%02x\n", set_bits, clear_bits); 878184610Salfred 879188413Sthompsa /* update current programmed line state */ 880188413Sthompsa sc->sc_pls_curr |= set_bits; 881188413Sthompsa sc->sc_pls_curr &= ~clear_bits; 882188413Sthompsa sc->sc_pls_set |= set_bits; 883188413Sthompsa sc->sc_pls_clr |= clear_bits; 884187176Sthompsa 885188413Sthompsa /* defer driver programming */ 886194228Sthompsa ucom_queue_command(sc, ucom_cfg_line_state, NULL, 887188413Sthompsa &sc->sc_line_state_task[0].hdr, 888188413Sthompsa &sc->sc_line_state_task[1].hdr); 889184610Salfred} 890184610Salfred 891184610Salfredstatic void 892197570Sthompsaucom_ring(struct ucom_softc *sc, uint8_t onoff) 893197570Sthompsa{ 894197570Sthompsa DPRINTF("onoff = %d\n", onoff); 895197570Sthompsa 896197570Sthompsa if (onoff) 897197570Sthompsa ucom_line_state(sc, UCOM_LS_RING, 0); 898197570Sthompsa else 899197570Sthompsa ucom_line_state(sc, 0, UCOM_LS_RING); 900197570Sthompsa} 901197570Sthompsa 902197570Sthompsastatic void 903194228Sthompsaucom_break(struct ucom_softc *sc, uint8_t onoff) 904187176Sthompsa{ 905188413Sthompsa DPRINTF("onoff = %d\n", onoff); 906187176Sthompsa 907188413Sthompsa if (onoff) 908194228Sthompsa ucom_line_state(sc, UCOM_LS_BREAK, 0); 909188413Sthompsa else 910194228Sthompsa ucom_line_state(sc, 0, UCOM_LS_BREAK); 911187176Sthompsa} 912187176Sthompsa 913187176Sthompsastatic void 914194228Sthompsaucom_dtr(struct ucom_softc *sc, uint8_t onoff) 915184610Salfred{ 916184610Salfred DPRINTF("onoff = %d\n", onoff); 917184610Salfred 918188413Sthompsa if (onoff) 919194228Sthompsa ucom_line_state(sc, UCOM_LS_DTR, 0); 920188413Sthompsa else 921194228Sthompsa ucom_line_state(sc, 0, UCOM_LS_DTR); 922184610Salfred} 923184610Salfred 924184610Salfredstatic void 925194228Sthompsaucom_rts(struct ucom_softc *sc, uint8_t onoff) 926184610Salfred{ 927184610Salfred DPRINTF("onoff = %d\n", onoff); 928184610Salfred 929188413Sthompsa if (onoff) 930194228Sthompsa ucom_line_state(sc, UCOM_LS_RTS, 0); 931188413Sthompsa else 932194228Sthompsa ucom_line_state(sc, 0, UCOM_LS_RTS); 933184610Salfred} 934184610Salfred 935184610Salfredstatic void 936194228Sthompsaucom_cfg_status_change(struct usb_proc_msg *_task) 937184610Salfred{ 938192984Sthompsa struct ucom_cfg_task *task = 939192984Sthompsa (struct ucom_cfg_task *)_task; 940192984Sthompsa struct ucom_softc *sc = task->sc; 941184610Salfred struct tty *tp; 942184610Salfred uint8_t new_msr; 943184610Salfred uint8_t new_lsr; 944184610Salfred uint8_t onoff; 945213872Shselasky uint8_t lsr_delta; 946184610Salfred 947184610Salfred tp = sc->sc_tty; 948184610Salfred 949188413Sthompsa mtx_assert(sc->sc_mtx, MA_OWNED); 950184610Salfred 951184610Salfred if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { 952184610Salfred return; 953184610Salfred } 954194228Sthompsa if (sc->sc_callback->ucom_cfg_get_status == NULL) { 955184610Salfred return; 956184610Salfred } 957184610Salfred /* get status */ 958184610Salfred 959184610Salfred new_msr = 0; 960184610Salfred new_lsr = 0; 961184610Salfred 962194228Sthompsa (sc->sc_callback->ucom_cfg_get_status) (sc, &new_lsr, &new_msr); 963184610Salfred 964184610Salfred if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { 965184610Salfred /* TTY device closed */ 966184610Salfred return; 967184610Salfred } 968184610Salfred onoff = ((sc->sc_msr ^ new_msr) & SER_DCD); 969213872Shselasky lsr_delta = (sc->sc_lsr ^ new_lsr); 970184610Salfred 971184610Salfred sc->sc_msr = new_msr; 972184610Salfred sc->sc_lsr = new_lsr; 973184610Salfred 974184610Salfred if (onoff) { 975184610Salfred 976184610Salfred onoff = (sc->sc_msr & SER_DCD) ? 1 : 0; 977184610Salfred 978184610Salfred DPRINTF("DCD changed to %d\n", onoff); 979184610Salfred 980184610Salfred ttydisc_modem(tp, onoff); 981184610Salfred } 982213872Shselasky 983213872Shselasky if ((lsr_delta & ULSR_BI) && (sc->sc_lsr & ULSR_BI)) { 984213872Shselasky 985213872Shselasky DPRINTF("BREAK detected\n"); 986213872Shselasky 987213872Shselasky ttydisc_rint(tp, 0, TRE_BREAK); 988213872Shselasky ttydisc_rint_done(tp); 989213872Shselasky } 990213872Shselasky 991213872Shselasky if ((lsr_delta & ULSR_FE) && (sc->sc_lsr & ULSR_FE)) { 992213872Shselasky 993213872Shselasky DPRINTF("Frame error detected\n"); 994213872Shselasky 995213872Shselasky ttydisc_rint(tp, 0, TRE_FRAMING); 996213872Shselasky ttydisc_rint_done(tp); 997213872Shselasky } 998213872Shselasky 999213872Shselasky if ((lsr_delta & ULSR_PE) && (sc->sc_lsr & ULSR_PE)) { 1000213872Shselasky 1001213872Shselasky DPRINTF("Parity error detected\n"); 1002213872Shselasky 1003213872Shselasky ttydisc_rint(tp, 0, TRE_PARITY); 1004213872Shselasky ttydisc_rint_done(tp); 1005213872Shselasky } 1006184610Salfred} 1007184610Salfred 1008184610Salfredvoid 1009194228Sthompsaucom_status_change(struct ucom_softc *sc) 1010184610Salfred{ 1011188413Sthompsa mtx_assert(sc->sc_mtx, MA_OWNED); 1012184610Salfred 1013197570Sthompsa if (sc->sc_flag & UCOM_FLAG_CONSOLE) 1014197570Sthompsa return; /* not supported */ 1015197570Sthompsa 1016184610Salfred if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { 1017184610Salfred return; 1018184610Salfred } 1019184610Salfred DPRINTF("\n"); 1020184610Salfred 1021194228Sthompsa ucom_queue_command(sc, ucom_cfg_status_change, NULL, 1022188413Sthompsa &sc->sc_status_task[0].hdr, 1023188413Sthompsa &sc->sc_status_task[1].hdr); 1024184610Salfred} 1025184610Salfred 1026184610Salfredstatic void 1027194228Sthompsaucom_cfg_param(struct usb_proc_msg *_task) 1028184610Salfred{ 1029192984Sthompsa struct ucom_param_task *task = 1030192984Sthompsa (struct ucom_param_task *)_task; 1031192984Sthompsa struct ucom_softc *sc = task->sc; 1032184610Salfred 1033184610Salfred if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { 1034184610Salfred return; 1035184610Salfred } 1036194228Sthompsa if (sc->sc_callback->ucom_cfg_param == NULL) { 1037184610Salfred return; 1038184610Salfred } 1039184610Salfred 1040194228Sthompsa (sc->sc_callback->ucom_cfg_param) (sc, &task->termios_copy); 1041184610Salfred 1042184610Salfred /* wait a little */ 1043194228Sthompsa usb_pause_mtx(sc->sc_mtx, hz / 10); 1044184610Salfred} 1045184610Salfred 1046184610Salfredstatic int 1047194228Sthompsaucom_param(struct tty *tp, struct termios *t) 1048184610Salfred{ 1049192984Sthompsa struct ucom_softc *sc = tty_softc(tp); 1050184610Salfred uint8_t opened; 1051184610Salfred int error; 1052184610Salfred 1053188413Sthompsa mtx_assert(sc->sc_mtx, MA_OWNED); 1054184610Salfred 1055184610Salfred opened = 0; 1056184610Salfred error = 0; 1057184610Salfred 1058184610Salfred if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { 1059184610Salfred 1060184610Salfred /* XXX the TTY layer should call "open()" first! */ 1061184610Salfred 1062194228Sthompsa error = ucom_open(tp); 1063184610Salfred if (error) { 1064184610Salfred goto done; 1065184610Salfred } 1066184610Salfred opened = 1; 1067184610Salfred } 1068184610Salfred DPRINTF("sc = %p\n", sc); 1069184610Salfred 1070184610Salfred /* Check requested parameters. */ 1071184610Salfred if (t->c_ospeed < 0) { 1072184610Salfred DPRINTF("negative ospeed\n"); 1073184610Salfred error = EINVAL; 1074184610Salfred goto done; 1075184610Salfred } 1076184610Salfred if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) { 1077184610Salfred DPRINTF("mismatch ispeed and ospeed\n"); 1078184610Salfred error = EINVAL; 1079184610Salfred goto done; 1080184610Salfred } 1081184610Salfred t->c_ispeed = t->c_ospeed; 1082184610Salfred 1083194228Sthompsa if (sc->sc_callback->ucom_pre_param) { 1084184610Salfred /* Let the lower layer verify the parameters */ 1085194228Sthompsa error = (sc->sc_callback->ucom_pre_param) (sc, t); 1086184610Salfred if (error) { 1087184610Salfred DPRINTF("callback error = %d\n", error); 1088184610Salfred goto done; 1089184610Salfred } 1090184610Salfred } 1091184610Salfred 1092184610Salfred /* Disable transfers */ 1093184610Salfred sc->sc_flag &= ~UCOM_FLAG_GP_DATA; 1094184610Salfred 1095184610Salfred /* Queue baud rate programming command first */ 1096194228Sthompsa ucom_queue_command(sc, ucom_cfg_param, t, 1097188413Sthompsa &sc->sc_param_task[0].hdr, 1098188413Sthompsa &sc->sc_param_task[1].hdr); 1099184610Salfred 1100184610Salfred /* Queue transfer enable command last */ 1101194228Sthompsa ucom_queue_command(sc, ucom_cfg_start_transfers, NULL, 1102188413Sthompsa &sc->sc_start_task[0].hdr, 1103188413Sthompsa &sc->sc_start_task[1].hdr); 1104184610Salfred 1105184610Salfred if (t->c_cflag & CRTS_IFLOW) { 1106184610Salfred sc->sc_flag |= UCOM_FLAG_RTS_IFLOW; 1107184610Salfred } else if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) { 1108184610Salfred sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW; 1109194228Sthompsa ucom_modem(tp, SER_RTS, 0); 1110184610Salfred } 1111184610Salfreddone: 1112184610Salfred if (error) { 1113184610Salfred if (opened) { 1114194228Sthompsa ucom_close(tp); 1115184610Salfred } 1116184610Salfred } 1117184610Salfred return (error); 1118184610Salfred} 1119184610Salfred 1120184610Salfredstatic void 1121194228Sthompsaucom_outwakeup(struct tty *tp) 1122184610Salfred{ 1123192984Sthompsa struct ucom_softc *sc = tty_softc(tp); 1124184610Salfred 1125188413Sthompsa mtx_assert(sc->sc_mtx, MA_OWNED); 1126184610Salfred 1127184610Salfred DPRINTF("sc = %p\n", sc); 1128184610Salfred 1129184610Salfred if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { 1130184610Salfred /* The higher layer is not ready */ 1131184610Salfred return; 1132184610Salfred } 1133194228Sthompsa ucom_start_transfers(sc); 1134184610Salfred} 1135184610Salfred 1136184610Salfred/*------------------------------------------------------------------------* 1137194228Sthompsa * ucom_get_data 1138184610Salfred * 1139184610Salfred * Return values: 1140184610Salfred * 0: No data is available. 1141184610Salfred * Else: Data is available. 1142184610Salfred *------------------------------------------------------------------------*/ 1143184610Salfreduint8_t 1144194228Sthompsaucom_get_data(struct ucom_softc *sc, struct usb_page_cache *pc, 1145184610Salfred uint32_t offset, uint32_t len, uint32_t *actlen) 1146184610Salfred{ 1147192984Sthompsa struct usb_page_search res; 1148184610Salfred struct tty *tp = sc->sc_tty; 1149184610Salfred uint32_t cnt; 1150184610Salfred uint32_t offset_orig; 1151184610Salfred 1152188413Sthompsa mtx_assert(sc->sc_mtx, MA_OWNED); 1153184610Salfred 1154197570Sthompsa if (sc->sc_flag & UCOM_FLAG_CONSOLE) { 1155197570Sthompsa unsigned int temp; 1156197570Sthompsa 1157197570Sthompsa /* get total TX length */ 1158197570Sthompsa 1159197570Sthompsa temp = ucom_cons_tx_high - ucom_cons_tx_low; 1160197570Sthompsa temp %= UCOM_CONS_BUFSIZE; 1161197570Sthompsa 1162197570Sthompsa /* limit TX length */ 1163197570Sthompsa 1164197570Sthompsa if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_tx_low)) 1165197570Sthompsa temp = (UCOM_CONS_BUFSIZE - ucom_cons_tx_low); 1166197570Sthompsa 1167197570Sthompsa if (temp > len) 1168197570Sthompsa temp = len; 1169197570Sthompsa 1170197570Sthompsa /* copy in data */ 1171197570Sthompsa 1172197570Sthompsa usbd_copy_in(pc, offset, ucom_cons_tx_buf + ucom_cons_tx_low, temp); 1173197570Sthompsa 1174197570Sthompsa /* update counters */ 1175197570Sthompsa 1176197570Sthompsa ucom_cons_tx_low += temp; 1177197570Sthompsa ucom_cons_tx_low %= UCOM_CONS_BUFSIZE; 1178197570Sthompsa 1179197570Sthompsa /* store actual length */ 1180197570Sthompsa 1181197570Sthompsa *actlen = temp; 1182197570Sthompsa 1183197570Sthompsa return (temp ? 1 : 0); 1184197570Sthompsa } 1185197570Sthompsa 1186192820Sthompsa if (tty_gone(tp) || 1187192820Sthompsa !(sc->sc_flag & UCOM_FLAG_GP_DATA)) { 1188184610Salfred actlen[0] = 0; 1189184610Salfred return (0); /* multiport device polling */ 1190184610Salfred } 1191184610Salfred offset_orig = offset; 1192184610Salfred 1193184610Salfred while (len != 0) { 1194184610Salfred 1195194228Sthompsa usbd_get_page(pc, offset, &res); 1196184610Salfred 1197184610Salfred if (res.length > len) { 1198184610Salfred res.length = len; 1199184610Salfred } 1200184610Salfred /* copy data directly into USB buffer */ 1201184610Salfred cnt = ttydisc_getc(tp, res.buffer, res.length); 1202184610Salfred 1203184610Salfred offset += cnt; 1204184610Salfred len -= cnt; 1205184610Salfred 1206184610Salfred if (cnt < res.length) { 1207184610Salfred /* end of buffer */ 1208184610Salfred break; 1209184610Salfred } 1210184610Salfred } 1211184610Salfred 1212184610Salfred actlen[0] = offset - offset_orig; 1213184610Salfred 1214184610Salfred DPRINTF("cnt=%d\n", actlen[0]); 1215184610Salfred 1216184610Salfred if (actlen[0] == 0) { 1217184610Salfred return (0); 1218184610Salfred } 1219184610Salfred return (1); 1220184610Salfred} 1221184610Salfred 1222184610Salfredvoid 1223194228Sthompsaucom_put_data(struct ucom_softc *sc, struct usb_page_cache *pc, 1224184610Salfred uint32_t offset, uint32_t len) 1225184610Salfred{ 1226192984Sthompsa struct usb_page_search res; 1227184610Salfred struct tty *tp = sc->sc_tty; 1228184610Salfred char *buf; 1229184610Salfred uint32_t cnt; 1230184610Salfred 1231188413Sthompsa mtx_assert(sc->sc_mtx, MA_OWNED); 1232184610Salfred 1233197570Sthompsa if (sc->sc_flag & UCOM_FLAG_CONSOLE) { 1234197570Sthompsa unsigned int temp; 1235197570Sthompsa 1236197570Sthompsa /* get maximum RX length */ 1237197570Sthompsa 1238197570Sthompsa temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_rx_high + ucom_cons_rx_low; 1239197570Sthompsa temp %= UCOM_CONS_BUFSIZE; 1240197570Sthompsa 1241197570Sthompsa /* limit RX length */ 1242197570Sthompsa 1243197570Sthompsa if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_rx_high)) 1244197570Sthompsa temp = (UCOM_CONS_BUFSIZE - ucom_cons_rx_high); 1245197570Sthompsa 1246197570Sthompsa if (temp > len) 1247197570Sthompsa temp = len; 1248197570Sthompsa 1249197570Sthompsa /* copy out data */ 1250197570Sthompsa 1251197570Sthompsa usbd_copy_out(pc, offset, ucom_cons_rx_buf + ucom_cons_rx_high, temp); 1252197570Sthompsa 1253197570Sthompsa /* update counters */ 1254197570Sthompsa 1255197570Sthompsa ucom_cons_rx_high += temp; 1256197570Sthompsa ucom_cons_rx_high %= UCOM_CONS_BUFSIZE; 1257197570Sthompsa 1258197570Sthompsa return; 1259197570Sthompsa } 1260197570Sthompsa 1261192820Sthompsa if (tty_gone(tp)) 1262184610Salfred return; /* multiport device polling */ 1263192820Sthompsa 1264184610Salfred if (len == 0) 1265184610Salfred return; /* no data */ 1266184610Salfred 1267184610Salfred /* set a flag to prevent recursation ? */ 1268184610Salfred 1269184610Salfred while (len > 0) { 1270184610Salfred 1271194228Sthompsa usbd_get_page(pc, offset, &res); 1272184610Salfred 1273184610Salfred if (res.length > len) { 1274184610Salfred res.length = len; 1275184610Salfred } 1276184610Salfred len -= res.length; 1277184610Salfred offset += res.length; 1278184610Salfred 1279184610Salfred /* pass characters to tty layer */ 1280184610Salfred 1281184610Salfred buf = res.buffer; 1282184610Salfred cnt = res.length; 1283184610Salfred 1284184610Salfred /* first check if we can pass the buffer directly */ 1285184610Salfred 1286184610Salfred if (ttydisc_can_bypass(tp)) { 1287184610Salfred if (ttydisc_rint_bypass(tp, buf, cnt) != cnt) { 1288184610Salfred DPRINTF("tp=%p, data lost\n", tp); 1289184610Salfred } 1290184610Salfred continue; 1291184610Salfred } 1292184610Salfred /* need to loop */ 1293184610Salfred 1294184610Salfred for (cnt = 0; cnt != res.length; cnt++) { 1295184610Salfred if (ttydisc_rint(tp, buf[cnt], 0) == -1) { 1296184610Salfred /* XXX what should we do? */ 1297184610Salfred 1298184610Salfred DPRINTF("tp=%p, lost %d " 1299184610Salfred "chars\n", tp, res.length - cnt); 1300184610Salfred break; 1301184610Salfred } 1302184610Salfred } 1303184610Salfred } 1304184610Salfred ttydisc_rint_done(tp); 1305184610Salfred} 1306184610Salfred 1307184610Salfredstatic void 1308194228Sthompsaucom_free(void *xsc) 1309184610Salfred{ 1310192984Sthompsa struct ucom_softc *sc = xsc; 1311184610Salfred 1312188413Sthompsa mtx_lock(sc->sc_mtx); 1313184610Salfred sc->sc_ttyfreed = 1; 1314194227Sthompsa cv_signal(&sc->sc_cv); 1315188413Sthompsa mtx_unlock(sc->sc_mtx); 1316184610Salfred} 1317197570Sthompsa 1318197570Sthompsastatic cn_probe_t ucom_cnprobe; 1319197570Sthompsastatic cn_init_t ucom_cninit; 1320197570Sthompsastatic cn_term_t ucom_cnterm; 1321197570Sthompsastatic cn_getc_t ucom_cngetc; 1322197570Sthompsastatic cn_putc_t ucom_cnputc; 1323197570Sthompsa 1324197570SthompsaCONSOLE_DRIVER(ucom); 1325197570Sthompsa 1326197570Sthompsastatic void 1327197570Sthompsaucom_cnprobe(struct consdev *cp) 1328197570Sthompsa{ 1329198774Sthompsa if (ucom_cons_unit != -1) 1330198774Sthompsa cp->cn_pri = CN_NORMAL; 1331198774Sthompsa else 1332198774Sthompsa cp->cn_pri = CN_DEAD; 1333198774Sthompsa 1334198774Sthompsa strlcpy(cp->cn_name, "ucom", sizeof(cp->cn_name)); 1335197570Sthompsa} 1336197570Sthompsa 1337197570Sthompsastatic void 1338197570Sthompsaucom_cninit(struct consdev *cp) 1339197570Sthompsa{ 1340197570Sthompsa} 1341197570Sthompsa 1342197570Sthompsastatic void 1343197570Sthompsaucom_cnterm(struct consdev *cp) 1344197570Sthompsa{ 1345197570Sthompsa} 1346197570Sthompsa 1347197570Sthompsastatic int 1348197570Sthompsaucom_cngetc(struct consdev *cd) 1349197570Sthompsa{ 1350197570Sthompsa struct ucom_softc *sc = ucom_cons_softc; 1351197570Sthompsa int c; 1352197570Sthompsa 1353197570Sthompsa if (sc == NULL) 1354197570Sthompsa return (-1); 1355197570Sthompsa 1356197570Sthompsa mtx_lock(sc->sc_mtx); 1357197570Sthompsa 1358197570Sthompsa if (ucom_cons_rx_low != ucom_cons_rx_high) { 1359197570Sthompsa c = ucom_cons_rx_buf[ucom_cons_rx_low]; 1360197570Sthompsa ucom_cons_rx_low ++; 1361197570Sthompsa ucom_cons_rx_low %= UCOM_CONS_BUFSIZE; 1362197570Sthompsa } else { 1363197570Sthompsa c = -1; 1364197570Sthompsa } 1365197570Sthompsa 1366197570Sthompsa /* start USB transfers */ 1367197570Sthompsa ucom_outwakeup(sc->sc_tty); 1368197570Sthompsa 1369197570Sthompsa mtx_unlock(sc->sc_mtx); 1370197570Sthompsa 1371197570Sthompsa /* poll if necessary */ 1372197570Sthompsa if (kdb_active && sc->sc_callback->ucom_poll) 1373197570Sthompsa (sc->sc_callback->ucom_poll) (sc); 1374197570Sthompsa 1375197570Sthompsa return (c); 1376197570Sthompsa} 1377197570Sthompsa 1378197570Sthompsastatic void 1379197570Sthompsaucom_cnputc(struct consdev *cd, int c) 1380197570Sthompsa{ 1381197570Sthompsa struct ucom_softc *sc = ucom_cons_softc; 1382197570Sthompsa unsigned int temp; 1383197570Sthompsa 1384197570Sthompsa if (sc == NULL) 1385197570Sthompsa return; 1386197570Sthompsa 1387197570Sthompsa repeat: 1388197570Sthompsa 1389197570Sthompsa mtx_lock(sc->sc_mtx); 1390197570Sthompsa 1391197570Sthompsa /* compute maximum TX length */ 1392197570Sthompsa 1393197570Sthompsa temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_tx_high + ucom_cons_tx_low; 1394197570Sthompsa temp %= UCOM_CONS_BUFSIZE; 1395197570Sthompsa 1396197570Sthompsa if (temp) { 1397197570Sthompsa ucom_cons_tx_buf[ucom_cons_tx_high] = c; 1398197570Sthompsa ucom_cons_tx_high ++; 1399197570Sthompsa ucom_cons_tx_high %= UCOM_CONS_BUFSIZE; 1400197570Sthompsa } 1401197570Sthompsa 1402197570Sthompsa /* start USB transfers */ 1403197570Sthompsa ucom_outwakeup(sc->sc_tty); 1404197570Sthompsa 1405197570Sthompsa mtx_unlock(sc->sc_mtx); 1406197570Sthompsa 1407197570Sthompsa /* poll if necessary */ 1408197570Sthompsa if (kdb_active && sc->sc_callback->ucom_poll) { 1409197570Sthompsa (sc->sc_callback->ucom_poll) (sc); 1410197570Sthompsa /* simple flow control */ 1411197570Sthompsa if (temp == 0) 1412197570Sthompsa goto repeat; 1413197570Sthompsa } 1414197570Sthompsa} 1415197570Sthompsa 1416197570Sthompsa#if defined(GDB) 1417197570Sthompsa 1418197570Sthompsa#include <gdb/gdb.h> 1419197570Sthompsa 1420197570Sthompsastatic gdb_probe_f ucom_gdbprobe; 1421197570Sthompsastatic gdb_init_f ucom_gdbinit; 1422197570Sthompsastatic gdb_term_f ucom_gdbterm; 1423197570Sthompsastatic gdb_getc_f ucom_gdbgetc; 1424197570Sthompsastatic gdb_putc_f ucom_gdbputc; 1425197570Sthompsa 1426197570SthompsaGDB_DBGPORT(sio, ucom_gdbprobe, ucom_gdbinit, ucom_gdbterm, ucom_gdbgetc, ucom_gdbputc); 1427197570Sthompsa 1428197570Sthompsastatic int 1429197570Sthompsaucom_gdbprobe(void) 1430197570Sthompsa{ 1431197570Sthompsa return ((ucom_cons_softc != NULL) ? 0 : -1); 1432197570Sthompsa} 1433197570Sthompsa 1434197570Sthompsastatic void 1435197570Sthompsaucom_gdbinit(void) 1436197570Sthompsa{ 1437197570Sthompsa} 1438197570Sthompsa 1439197570Sthompsastatic void 1440197570Sthompsaucom_gdbterm(void) 1441197570Sthompsa{ 1442197570Sthompsa} 1443197570Sthompsa 1444197570Sthompsastatic void 1445197570Sthompsaucom_gdbputc(int c) 1446197570Sthompsa{ 1447197570Sthompsa ucom_cnputc(NULL, c); 1448197570Sthompsa} 1449197570Sthompsa 1450197570Sthompsastatic int 1451197570Sthompsaucom_gdbgetc(void) 1452197570Sthompsa{ 1453197570Sthompsa return (ucom_cngetc(NULL)); 1454197570Sthompsa} 1455197570Sthompsa 1456197570Sthompsa#endif 1457