/* $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: head/sys/dev/usb2/serial/usb2_serial.c 187176 2009-01-13 19:03:47Z thompsa $"); /*- * 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * 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. */ /* * NOTE: all function names beginning like "usb2_com_cfg_" can only * be called from within the config thread function ! */ #include #include #include #include #define USB_DEBUG_VAR usb2_com_debug #include #include #include #include #include #include #include #if USB_DEBUG static int usb2_com_debug = 0; SYSCTL_NODE(_hw_usb2, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom"); SYSCTL_INT(_hw_usb2_ucom, OID_AUTO, debug, CTLFLAG_RW, &usb2_com_debug, 0, "ucom debug level"); #endif static usb2_proc_callback_t usb2_com_cfg_start_transfers; static usb2_proc_callback_t usb2_com_cfg_open; static usb2_proc_callback_t usb2_com_cfg_close; static usb2_proc_callback_t usb2_com_cfg_break_on; static usb2_proc_callback_t usb2_com_cfg_break_off; static usb2_proc_callback_t usb2_com_cfg_dtr_on; static usb2_proc_callback_t usb2_com_cfg_dtr_off; static usb2_proc_callback_t usb2_com_cfg_rts_on; static usb2_proc_callback_t usb2_com_cfg_rts_off; static usb2_proc_callback_t usb2_com_cfg_status_change; static usb2_proc_callback_t usb2_com_cfg_param; static uint8_t usb2_com_units_alloc(uint32_t, uint32_t *); static void usb2_com_units_free(uint32_t, uint32_t); static int usb2_com_attach_sub(struct usb2_com_softc *); static void usb2_com_detach_sub(struct usb2_com_softc *); static void usb2_com_queue_command(struct usb2_com_softc *sc, uint8_t cmd); static void usb2_com_wait_command(struct usb2_com_softc *sc, uint8_t cmd); static void usb2_com_shutdown(struct usb2_com_softc *); static void usb2_com_start_transfers(struct usb2_com_softc *); static void usb2_com_break(struct usb2_com_softc *, uint8_t); static void usb2_com_dtr(struct usb2_com_softc *, uint8_t); static void usb2_com_rts(struct usb2_com_softc *, uint8_t); static tsw_open_t usb2_com_open; static tsw_close_t usb2_com_close; static tsw_ioctl_t usb2_com_ioctl; static tsw_modem_t usb2_com_modem; static tsw_param_t usb2_com_param; static tsw_outwakeup_t usb2_com_start_write; static tsw_free_t usb2_com_free; static struct ttydevsw usb2_com_class = { .tsw_flags = TF_INITLOCK | TF_CALLOUT, .tsw_open = usb2_com_open, .tsw_close = usb2_com_close, .tsw_outwakeup = usb2_com_start_write, .tsw_ioctl = usb2_com_ioctl, .tsw_param = usb2_com_param, .tsw_modem = usb2_com_modem, .tsw_free = usb2_com_free, }; MODULE_DEPEND(usb2_serial, usb2_core, 1, 1, 1); MODULE_VERSION(usb2_serial, 1); #define UCOM_UNIT_MAX 0x1000 /* exclusive */ #define UCOM_SUB_UNIT_MAX 0x100 /* exclusive */ static uint8_t usb2_com_bitmap[(UCOM_UNIT_MAX + 7) / 8]; static uint8_t usb2_com_units_alloc(uint32_t sub_units, uint32_t *p_root_unit) { uint32_t n; uint32_t o; uint32_t x; uint32_t max = UCOM_UNIT_MAX - (UCOM_UNIT_MAX % sub_units); uint8_t error = 1; mtx_lock(&Giant); for (n = 0; n < max; n += sub_units) { /* check for free consecutive bits */ for (o = 0; o < sub_units; o++) { x = n + o; if (usb2_com_bitmap[x / 8] & (1 << (x % 8))) { goto skip; } } /* allocate */ for (o = 0; o < sub_units; o++) { x = n + o; usb2_com_bitmap[x / 8] |= (1 << (x % 8)); } error = 0; break; skip: ; } mtx_unlock(&Giant); /* * Always set the variable pointed to by "p_root_unit" so that * the compiler does not think that it is used uninitialised: */ *p_root_unit = n; return (error); } static void usb2_com_units_free(uint32_t root_unit, uint32_t sub_units) { uint32_t x; mtx_lock(&Giant); while (sub_units--) { x = root_unit + sub_units; usb2_com_bitmap[x / 8] &= ~(1 << (x % 8)); } mtx_unlock(&Giant); } /* * "N" sub_units are setup at a time. All sub-units will * be given sequential unit numbers. The number of * sub-units can be used to differentiate among * different types of devices. * * The mutex pointed to by "p_mtx" is applied before all * callbacks are called back. Also "p_mtx" must be applied * before calling into the ucom-layer! Currently only Giant * is supported. */ int usb2_com_attach(struct usb2_com_super_softc *ssc, struct usb2_com_softc *sc, uint32_t sub_units, void *parent, const struct usb2_com_callback *callback, struct mtx *p_mtx) { uint32_t n; uint32_t root_unit; int error = 0; if ((sc == NULL) || (sub_units == 0) || (sub_units > UCOM_SUB_UNIT_MAX) || (callback == NULL)) { return (EINVAL); } if (usb2_com_units_alloc(sub_units, &root_unit)) { return (ENOMEM); } if (usb2_proc_setup(&ssc->sc_config_td, p_mtx, USB_PRI_MED)) { usb2_com_units_free(root_unit, sub_units); return (ENOMEM); } for (n = 0; n < sub_units; n++, sc++) { sc->sc_unit = root_unit + n; sc->sc_local_unit = n; sc->sc_super = ssc; sc->sc_parent_mtx = p_mtx; sc->sc_parent = parent; sc->sc_callback = callback; error = usb2_com_attach_sub(sc); if (error) { usb2_com_detach(ssc, sc - n, n); usb2_com_units_free(root_unit + n, sub_units - n); break; } sc->sc_flag |= UCOM_FLAG_ATTACHED; } return (error); } /* NOTE: the following function will do nothing if * the structure pointed to by "ssc" and "sc" is zero. */ void usb2_com_detach(struct usb2_com_super_softc *ssc, struct usb2_com_softc *sc, uint32_t sub_units) { uint32_t n; usb2_proc_drain(&ssc->sc_config_td); for (n = 0; n < sub_units; n++, sc++) { if (sc->sc_flag & UCOM_FLAG_ATTACHED) { usb2_com_detach_sub(sc); usb2_com_units_free(sc->sc_unit, 1); /* avoid duplicate detach: */ sc->sc_flag &= ~UCOM_FLAG_ATTACHED; } } usb2_proc_unsetup(&ssc->sc_config_td); } static int usb2_com_attach_sub(struct usb2_com_softc *sc) { struct tty *tp; int error = 0; uint8_t n; char buf[32]; /* temporary TTY device name buffer */ tp = tty_alloc(&usb2_com_class, sc, sc->sc_parent_mtx); if (tp == NULL) { error = ENOMEM; goto done; } DPRINTF("tp = %p, unit = %d\n", tp, sc->sc_unit); buf[0] = 0; /* set some default value */ /* Check if the client has a custom TTY name */ if (sc->sc_callback->usb2_com_tty_name) { sc->sc_callback->usb2_com_tty_name(sc, buf, sizeof(buf), sc->sc_local_unit); } if (buf[0] == 0) { /* Use default TTY name */ if (snprintf(buf, sizeof(buf), "U%u", sc->sc_unit)) { /* ignore */ } } tty_makedev(tp, NULL, "%s", buf); sc->sc_tty = tp; DPRINTF("ttycreate: %s\n", buf); usb2_cv_init(&sc->sc_cv, "usb2_com"); /* * Set all function callback pointers for deferred COM * operations: */ for (n = 0; n != 2; n++) { sc->sc_cmds[(2*USB_COM_CFG_START_TRANSFERS) + n].hdr.pm_callback = &usb2_com_cfg_start_transfers; sc->sc_cmds[(2*USB_COM_CFG_OPEN) + n].hdr.pm_callback = &usb2_com_cfg_open; sc->sc_cmds[(2*USB_COM_CFG_CLOSE) + n].hdr.pm_callback = &usb2_com_cfg_close; sc->sc_cmds[(2*USB_COM_CFG_BREAK_ON) + n].hdr.pm_callback = &usb2_com_cfg_break_on; sc->sc_cmds[(2*USB_COM_CFG_BREAK_OFF) + n].hdr.pm_callback = &usb2_com_cfg_break_off; sc->sc_cmds[(2*USB_COM_CFG_DTR_ON) + n].hdr.pm_callback = &usb2_com_cfg_dtr_on; sc->sc_cmds[(2*USB_COM_CFG_DTR_OFF) + n].hdr.pm_callback = &usb2_com_cfg_dtr_off; sc->sc_cmds[(2*USB_COM_CFG_RTS_ON) + n].hdr.pm_callback = &usb2_com_cfg_rts_on; sc->sc_cmds[(2*USB_COM_CFG_RTS_OFF) + n].hdr.pm_callback = &usb2_com_cfg_rts_off; sc->sc_cmds[(2*USB_COM_CFG_STATUS_CHANGE) + n].hdr.pm_callback = &usb2_com_cfg_status_change; sc->sc_cmds[(2*USB_COM_CFG_PARAM) + n].hdr.pm_callback = &usb2_com_cfg_param; } /* initialise all callback pointer arguments */ for (n = 0; n != (2*USB_COM_CFG_MAX); n++) { sc->sc_cmds[n].cc_softc = sc; } done: return (error); } static void usb2_com_detach_sub(struct usb2_com_softc *sc) { struct tty *tp = sc->sc_tty; DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty); /* the config thread has been stopped when we get here */ mtx_lock(sc->sc_parent_mtx); sc->sc_flag |= UCOM_FLAG_GONE; sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_LL_READY); mtx_unlock(sc->sc_parent_mtx); if (tp) { tty_lock(tp); usb2_com_close(tp); /* close, if any */ tty_rel_gone(tp); mtx_lock(sc->sc_parent_mtx); /* Wait for the callback after the TTY is torn down */ while (sc->sc_ttyfreed == 0) usb2_cv_wait(&sc->sc_cv, sc->sc_parent_mtx); /* * make sure that read and write transfers are stopped */ if (sc->sc_callback->usb2_com_stop_read) { (sc->sc_callback->usb2_com_stop_read) (sc); } if (sc->sc_callback->usb2_com_stop_write) { (sc->sc_callback->usb2_com_stop_write) (sc); } mtx_unlock(sc->sc_parent_mtx); } usb2_cv_destroy(&sc->sc_cv); } /* * The following function queues a command for deferred execution. * The following function must be called locked. */ static void usb2_com_queue_command(struct usb2_com_softc *sc, uint8_t cmd) { struct usb2_com_super_softc *ssc = sc->sc_super; if (usb2_proc_is_gone(&ssc->sc_config_td)) { DPRINTF("proc is gone\n"); return; /* nothing to do */ } if (usb2_proc_msignal(&ssc->sc_config_td, &sc->sc_cmds[2*cmd], &sc->sc_cmds[(2*cmd)+1])) { /* ignore */ } } /* * The following function waits until a command has been executed. * The following function must be called locked. */ static void usb2_com_wait_command(struct usb2_com_softc *sc, uint8_t cmd) { struct usb2_com_super_softc *ssc = sc->sc_super; if (usb2_proc_is_gone(&ssc->sc_config_td)) { DPRINTF("proc is gone\n"); return; /* nothing to do */ } usb2_proc_mwait(&ssc->sc_config_td, &sc->sc_cmds[2*cmd], &sc->sc_cmds[(2*cmd)+1]); } static void usb2_com_shutdown(struct usb2_com_softc *sc) { struct tty *tp = sc->sc_tty; mtx_assert(sc->sc_parent_mtx, MA_OWNED); DPRINTF("\n"); /* * Hang up if necessary: */ if (tp->t_termios.c_cflag & HUPCL) { usb2_com_modem(tp, 0, SER_DTR); } } /* * This function will sleep "timeout" system ticks. * * Return values: * 0: normal delay * else: config thread is gone */ uint8_t usb2_com_cfg_sleep(struct usb2_com_softc *sc, uint32_t timeout) { struct usb2_com_super_softc *ssc = sc->sc_super; uint8_t is_gone; is_gone = usb2_proc_is_gone(&ssc->sc_config_td); if (is_gone) goto done; /* we are detaching */ if (timeout == 0) timeout = 1; /* one tick is the least timeout */ mtx_unlock(sc->sc_parent_mtx); if (pause("UCOMWAIT", timeout)) { /* ignore */ } mtx_lock(sc->sc_parent_mtx); /* refresh gone status */ is_gone = usb2_proc_is_gone(&ssc->sc_config_td); done: return (is_gone); } /* * Return values: * 0: normal * else: config thread is gone */ uint8_t usb2_com_cfg_is_gone(struct usb2_com_softc *sc) { struct usb2_com_super_softc *ssc = sc->sc_super; return (usb2_proc_is_gone(&ssc->sc_config_td)); } static void usb2_com_cfg_start_transfers(struct usb2_proc_msg *_cc) { struct usb2_com_command_msg *cc = (void *)_cc; struct usb2_com_softc *sc; sc = cc->cc_softc; if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { /* TTY device closed */ return; } sc->sc_flag |= UCOM_FLAG_GP_DATA; if (sc->sc_callback->usb2_com_start_read) { (sc->sc_callback->usb2_com_start_read) (sc); } if (sc->sc_callback->usb2_com_start_write) { (sc->sc_callback->usb2_com_start_write) (sc); } } static void usb2_com_start_transfers(struct usb2_com_softc *sc) { if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return; } /* * do a direct call first, to get hardware buffers flushed */ if (sc->sc_callback->usb2_com_start_read) { (sc->sc_callback->usb2_com_start_read) (sc); } if (sc->sc_callback->usb2_com_start_write) { (sc->sc_callback->usb2_com_start_write) (sc); } if (!(sc->sc_flag & UCOM_FLAG_GP_DATA)) { usb2_com_queue_command(sc, USB_COM_CFG_START_TRANSFERS); } } static void usb2_com_cfg_open(struct usb2_proc_msg *_cc) { struct usb2_com_command_msg *cc = (void *)_cc; struct usb2_com_softc *sc; sc = cc->cc_softc; DPRINTF("\n"); if (sc->sc_flag & UCOM_FLAG_LL_READY) { /* already opened */ } else { sc->sc_flag |= UCOM_FLAG_LL_READY; if (sc->sc_callback->usb2_com_cfg_open) { (sc->sc_callback->usb2_com_cfg_open) (sc); /* wait a little */ usb2_com_cfg_sleep(sc, hz / 10); } } } static int usb2_com_open(struct tty *tp) { struct usb2_com_softc *sc = tty_softc(tp); int error; mtx_assert(sc->sc_parent_mtx, 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->usb2_com_pre_open) { /* * give the lower layer a chance to disallow TTY open, for * example if the device is not present: */ error = (sc->sc_callback->usb2_com_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; usb2_com_queue_command(sc, USB_COM_CFG_OPEN); usb2_com_start_transfers(sc); usb2_com_modem(tp, SER_DTR | SER_RTS, 0); usb2_com_break(sc, 0); usb2_com_status_change(sc); return (0); } static void usb2_com_cfg_close(struct usb2_proc_msg *_cc) { struct usb2_com_command_msg *cc = (void *)_cc; struct usb2_com_softc *sc; sc = cc->cc_softc; DPRINTF("\n"); if (sc->sc_flag & UCOM_FLAG_LL_READY) { sc->sc_flag &= ~(UCOM_FLAG_LL_READY | UCOM_FLAG_GP_DATA); if (sc->sc_callback->usb2_com_cfg_close) { (sc->sc_callback->usb2_com_cfg_close) (sc); } } else { /* already closed */ } } static void usb2_com_close(struct tty *tp) { struct usb2_com_softc *sc = tty_softc(tp); mtx_assert(sc->sc_parent_mtx, MA_OWNED); DPRINTF("tp=%p\n", tp); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { DPRINTF("tp=%p already closed\n", tp); return; } usb2_com_shutdown(sc); /* Queue and wait for close command to complete */ usb2_com_queue_command(sc, USB_COM_CFG_CLOSE); usb2_com_wait_command(sc, USB_COM_CFG_CLOSE); sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_WR_START | UCOM_FLAG_RTS_IFLOW); if (sc->sc_callback->usb2_com_stop_read) { (sc->sc_callback->usb2_com_stop_read) (sc); } if (sc->sc_callback->usb2_com_stop_write) { (sc->sc_callback->usb2_com_stop_write) (sc); } } static int usb2_com_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) { struct usb2_com_softc *sc = tty_softc(tp); int error; mtx_assert(sc->sc_parent_mtx, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return (EIO); } DPRINTF("cmd = 0x%08lx\n", cmd); switch (cmd) { case TIOCSBRK: usb2_com_break(sc, 1); error = 0; break; case TIOCCBRK: usb2_com_break(sc, 0); error = 0; break; default: if (sc->sc_callback->usb2_com_ioctl) { error = (sc->sc_callback->usb2_com_ioctl) (sc, cmd, data, 0, td); } else { error = ENOIOCTL; } break; } return (error); } static int usb2_com_modem(struct tty *tp, int sigon, int sigoff) { struct usb2_com_softc *sc = tty_softc(tp); uint8_t onoff; mtx_assert(sc->sc_parent_mtx, 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; usb2_com_dtr(sc, onoff); onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0; usb2_com_rts(sc, onoff); return (0); } static void usb2_com_cfg_break(struct usb2_com_command_msg *cc, uint8_t onoff) { struct usb2_com_softc *sc; sc = cc->cc_softc; if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } DPRINTF("onoff=%d\n", onoff); if (sc->sc_callback->usb2_com_cfg_set_break) { (sc->sc_callback->usb2_com_cfg_set_break) (sc, onoff); } } static void usb2_com_cfg_break_on(struct usb2_proc_msg *_cc) { usb2_com_cfg_break((void *)_cc, 1); } static void usb2_com_cfg_break_off(struct usb2_proc_msg *_cc) { usb2_com_cfg_break((void *)_cc, 0); } static void usb2_com_break(struct usb2_com_softc *sc, uint8_t onoff) { mtx_assert(sc->sc_parent_mtx, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return; } DPRINTF("onoff = %d\n", onoff); usb2_com_queue_command(sc, onoff ? USB_COM_CFG_BREAK_ON : USB_COM_CFG_BREAK_OFF); } static void usb2_com_cfg_dtr(struct usb2_com_command_msg *cc, uint8_t onoff) { struct usb2_com_softc *sc; sc = cc->cc_softc; if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } DPRINTF("onoff=%d\n", onoff); if (sc->sc_callback->usb2_com_cfg_set_dtr) { (sc->sc_callback->usb2_com_cfg_set_dtr) (sc, onoff); } } static void usb2_com_cfg_dtr_on(struct usb2_proc_msg *_cc) { usb2_com_cfg_dtr((void *)_cc, 1); } static void usb2_com_cfg_dtr_off(struct usb2_proc_msg *_cc) { usb2_com_cfg_dtr((void *)_cc, 0); } static void usb2_com_dtr(struct usb2_com_softc *sc, uint8_t onoff) { mtx_assert(sc->sc_parent_mtx, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return; } DPRINTF("onoff = %d\n", onoff); usb2_com_queue_command(sc, onoff ? USB_COM_CFG_DTR_ON : USB_COM_CFG_DTR_OFF); } static void usb2_com_cfg_rts(struct usb2_com_command_msg *cc, uint8_t onoff) { struct usb2_com_softc *sc; sc = cc->cc_softc; DPRINTF("onoff=%d\n", onoff); if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } if (sc->sc_callback->usb2_com_cfg_set_rts) { (sc->sc_callback->usb2_com_cfg_set_rts) (sc, onoff); } } static void usb2_com_cfg_rts_on(struct usb2_proc_msg *_cc) { usb2_com_cfg_rts((void *)_cc, 1); } static void usb2_com_cfg_rts_off(struct usb2_proc_msg *_cc) { usb2_com_cfg_rts((void *)_cc, 0); } static void usb2_com_rts(struct usb2_com_softc *sc, uint8_t onoff) { mtx_assert(sc->sc_parent_mtx, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return; } DPRINTF("onoff = %d\n", onoff); usb2_com_queue_command(sc, onoff ? USB_COM_CFG_RTS_ON : USB_COM_CFG_RTS_OFF); } static void usb2_com_cfg_status_change(struct usb2_proc_msg *_cc) { struct usb2_com_command_msg *cc = (void *)_cc; struct usb2_com_softc *sc; struct tty *tp; uint8_t new_msr; uint8_t new_lsr; uint8_t onoff; sc = cc->cc_softc; tp = sc->sc_tty; mtx_assert(sc->sc_parent_mtx, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } if (sc->sc_callback->usb2_com_cfg_get_status == NULL) { return; } /* get status */ new_msr = 0; new_lsr = 0; (sc->sc_callback->usb2_com_cfg_get_status) (sc, &new_lsr, &new_msr); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { /* TTY device closed */ return; } onoff = ((sc->sc_msr ^ new_msr) & SER_DCD); sc->sc_msr = new_msr; sc->sc_lsr = new_lsr; if (onoff) { onoff = (sc->sc_msr & SER_DCD) ? 1 : 0; DPRINTF("DCD changed to %d\n", onoff); ttydisc_modem(tp, onoff); } } void usb2_com_status_change(struct usb2_com_softc *sc) { mtx_assert(sc->sc_parent_mtx, MA_OWNED); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { return; } DPRINTF("\n"); usb2_com_queue_command(sc, USB_COM_CFG_STATUS_CHANGE); } static void usb2_com_cfg_param(struct usb2_proc_msg *_cc) { struct usb2_com_command_msg *cc = (void *)_cc; struct usb2_com_softc *sc; struct termios t_copy; sc = cc->cc_softc; if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { return; } if (sc->sc_callback->usb2_com_cfg_param == NULL) { return; } t_copy = sc->sc_termios_copy; (sc->sc_callback->usb2_com_cfg_param) (sc, &t_copy); /* wait a little */ usb2_com_cfg_sleep(sc, hz / 10); } static int usb2_com_param(struct tty *tp, struct termios *t) { struct usb2_com_softc *sc = tty_softc(tp); uint8_t opened; int error; mtx_assert(sc->sc_parent_mtx, MA_OWNED); opened = 0; error = 0; if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { /* XXX the TTY layer should call "open()" first! */ error = usb2_com_open(tp); if (error) { goto done; } opened = 1; } DPRINTF("sc = %p\n", sc); /* Check requested parameters. */ if (t->c_ospeed < 0) { DPRINTF("negative ospeed\n"); error = EINVAL; goto done; } if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) { DPRINTF("mismatch ispeed and ospeed\n"); error = EINVAL; goto done; } t->c_ispeed = t->c_ospeed; if (sc->sc_callback->usb2_com_pre_param) { /* Let the lower layer verify the parameters */ error = (sc->sc_callback->usb2_com_pre_param) (sc, t); if (error) { DPRINTF("callback error = %d\n", error); goto done; } } /* Make a copy of the termios parameters */ sc->sc_termios_copy = *t; /* Disable transfers */ sc->sc_flag &= ~UCOM_FLAG_GP_DATA; /* Queue baud rate programming command first */ usb2_com_queue_command(sc, USB_COM_CFG_PARAM); /* Queue transfer enable command last */ usb2_com_start_transfers(sc); 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; usb2_com_modem(tp, SER_RTS, 0); } done: if (error) { if (opened) { usb2_com_close(tp); } } return (error); } static void usb2_com_start_write(struct tty *tp) { struct usb2_com_softc *sc = tty_softc(tp); mtx_assert(sc->sc_parent_mtx, MA_OWNED); DPRINTF("sc = %p\n", sc); if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { /* The higher layer is not ready */ return; } sc->sc_flag |= UCOM_FLAG_WR_START; usb2_com_start_transfers(sc); } /*------------------------------------------------------------------------* * usb2_com_get_data * * Return values: * 0: No data is available. * Else: Data is available. *------------------------------------------------------------------------*/ uint8_t usb2_com_get_data(struct usb2_com_softc *sc, struct usb2_page_cache *pc, uint32_t offset, uint32_t len, uint32_t *actlen) { struct usb2_page_search res; struct tty *tp = sc->sc_tty; uint32_t cnt; uint32_t offset_orig; mtx_assert(sc->sc_parent_mtx, MA_OWNED); if ((!(sc->sc_flag & UCOM_FLAG_HL_READY)) || (!(sc->sc_flag & UCOM_FLAG_GP_DATA)) || (!(sc->sc_flag & UCOM_FLAG_WR_START))) { actlen[0] = 0; return (0); /* multiport device polling */ } offset_orig = offset; while (len != 0) { usb2_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 usb2_com_put_data(struct usb2_com_softc *sc, struct usb2_page_cache *pc, uint32_t offset, uint32_t len) { struct usb2_page_search res; struct tty *tp = sc->sc_tty; char *buf; uint32_t cnt; mtx_assert(sc->sc_parent_mtx, MA_OWNED); if ((!(sc->sc_flag & UCOM_FLAG_HL_READY)) || (!(sc->sc_flag & UCOM_FLAG_GP_DATA))) { return; /* multiport device polling */ } if (len == 0) return; /* no data */ /* set a flag to prevent recursation ? */ while (len > 0) { usb2_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)) { 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 (ttydisc_rint(tp, buf[cnt], 0) == -1) { /* XXX what should we do? */ DPRINTF("tp=%p, lost %d " "chars\n", tp, res.length - cnt); break; } } } ttydisc_rint_done(tp); } static void usb2_com_free(void *xsc) { struct usb2_com_softc *sc = xsc; mtx_lock(sc->sc_parent_mtx); sc->sc_ttyfreed = 1; usb2_cv_signal(&sc->sc_cv); mtx_unlock(sc->sc_parent_mtx); }