Index: head/sys/dev/altera/jtag_uart/altera_jtag_uart_cons.c =================================================================== --- head/sys/dev/altera/jtag_uart/altera_jtag_uart_cons.c (revision 274820) +++ head/sys/dev/altera/jtag_uart/altera_jtag_uart_cons.c (revision 274821) @@ -1,321 +1,324 @@ /*- * Copyright (c) 2011-2012 Robert N. M. Watson * All rights reserved. * * This software was developed by SRI International and the University of * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) * ("CTSRD"), as part of the DARPA CRASH research programme. * * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include devclass_t altera_jtag_uart_devclass; /* * One-byte buffer as we can't check whether the UART is readable without * actually reading from it, synchronised by a spinlock; this lock also * synchronises access to the I/O ports for non-atomic sequences. These * symbols are public so that the TTY layer can use them when working on an * instance of the UART that is also a low-level console. */ char aju_cons_buffer_data; int aju_cons_buffer_valid; int aju_cons_jtag_present; u_int aju_cons_jtag_missed; struct mtx aju_cons_lock; /* * Low-level console driver functions. */ static cn_probe_t aju_cnprobe; static cn_init_t aju_cninit; static cn_term_t aju_cnterm; static cn_getc_t aju_cngetc; static cn_putc_t aju_cnputc; static cn_grab_t aju_cngrab; static cn_ungrab_t aju_cnungrab; /* * JTAG sets the ALTERA_JTAG_UART_CONTROL_AC bit whenever it accesses the * FIFO. This allows us to (sort of) tell when JTAG is present, so that we * can adopt lossy, rather than blocking, behaviour when JTAG isn't there. * When it is present, we do full flow control. This delay is how long we * wait to see if JTAG has really disappeared when finding a full buffer and * no AC bit set. */ #define ALTERA_JTAG_UART_AC_POLL_DELAY 10000 /* * I/O routines lifted from Deimos. This is not only MIPS-specific, but also * BERI-specific, as we're hard coding the address at which we expect to * find the Altera JTAG UART and using it unconditionally. We use these * low-level routines so that we can perform console I/O long before newbus * has initialised and devices have attached. The TTY layer of the driver * knows about this, and uses the console-layer spinlock instead of the * TTY-layer lock to avoid confusion between layers for the console UART. * * XXXRW: The only place this inter-layer behaviour breaks down is if the * low-level console is used for polled read while the TTY driver is also * looking for input. Probably we should also share buffers between layers. */ #define MIPS_XKPHYS_UNCACHED_BASE 0x9000000000000000 typedef uint64_t paddr_t; typedef uint64_t vaddr_t; static inline vaddr_t mips_phys_to_uncached(paddr_t phys) { return (phys | MIPS_XKPHYS_UNCACHED_BASE); } static inline uint32_t mips_ioread_uint32(vaddr_t vaddr) { uint32_t v; __asm__ __volatile__ ("lw %0, 0(%1)" : "=r" (v) : "r" (vaddr)); return (v); } static inline void mips_iowrite_uint32(vaddr_t vaddr, uint32_t v) { __asm__ __volatile__ ("sw %0, 0(%1)" : : "r" (v), "r" (vaddr)); } /* * Little-endian versions of 32-bit I/O routines. */ static inline uint32_t mips_ioread_uint32le(vaddr_t vaddr) { return (le32toh(mips_ioread_uint32(vaddr))); } static inline void mips_iowrite_uint32le(vaddr_t vaddr, uint32_t v) { mips_iowrite_uint32(vaddr, htole32(v)); } /* * Low-level read and write register routines; the Altera UART is little * endian, so we byte swap 32-bit reads and writes. */ static inline uint32_t aju_cons_data_read(void) { return (mips_ioread_uint32le(mips_phys_to_uncached(BERI_UART_BASE + ALTERA_JTAG_UART_DATA_OFF))); } static inline void aju_cons_data_write(uint32_t v) { mips_iowrite_uint32le(mips_phys_to_uncached(BERI_UART_BASE + ALTERA_JTAG_UART_DATA_OFF), v); } static inline uint32_t aju_cons_control_read(void) { return (mips_ioread_uint32le(mips_phys_to_uncached(BERI_UART_BASE + ALTERA_JTAG_UART_CONTROL_OFF))); } static inline void aju_cons_control_write(uint32_t v) { mips_iowrite_uint32le(mips_phys_to_uncached(BERI_UART_BASE + ALTERA_JTAG_UART_CONTROL_OFF), v); } /* * Slightly higher-level routines aware of buffering and flow control. */ static int aju_cons_readable(void) { uint32_t v; AJU_CONSOLE_LOCK_ASSERT(); if (aju_cons_buffer_valid) return (1); v = aju_cons_data_read(); if ((v & ALTERA_JTAG_UART_DATA_RVALID) != 0) { aju_cons_buffer_valid = 1; aju_cons_buffer_data = (v & ALTERA_JTAG_UART_DATA_DATA); return (1); } return (0); } static void aju_cons_write(char ch) { uint32_t v; AJU_CONSOLE_LOCK_ASSERT(); /* * The flow control logic here is somewhat subtle: we want to wait for * write buffer space only while JTAG is present. However, we can't * directly ask if JTAG is present -- just whether it's been seen * since we last cleared the ALTERA_JTAG_UART_CONTROL_AC bit. As * such, implement a polling loop in which we both wait for space and * try to decide whether JTAG has disappeared on us. We will have to * wait one complete polling delay to detect that JTAG has gone away, * but otherwise shouldn't wait any further once it has gone. And we * had to wait for buffer space anyway, if it was there. * * If JTAG is spotted, reset the TTY-layer miss counter so console- * layer clearing of the bit doesn't trigger a TTY-layer * disconnection. * * XXXRW: The polling delay may require tuning. + * + * XXXRW: Notice the inherent race with hardware: in clearing the + * bit, we may race with hardware setting the same bit. */ v = aju_cons_control_read(); if (v & ALTERA_JTAG_UART_CONTROL_AC) { aju_cons_jtag_present = 1; aju_cons_jtag_missed = 0; v &= ~ALTERA_JTAG_UART_CONTROL_AC; aju_cons_control_write(v); } while ((v & ALTERA_JTAG_UART_CONTROL_WSPACE) == 0) { if (!aju_cons_jtag_present) return; DELAY(ALTERA_JTAG_UART_AC_POLL_DELAY); v = aju_cons_control_read(); if (v & ALTERA_JTAG_UART_CONTROL_AC) { aju_cons_jtag_present = 1; v &= ~ALTERA_JTAG_UART_CONTROL_AC; aju_cons_control_write(v); } else aju_cons_jtag_present = 0; } aju_cons_data_write(ch); } static char aju_cons_read(void) { AJU_CONSOLE_LOCK_ASSERT(); while (!aju_cons_readable()); aju_cons_buffer_valid = 0; return (aju_cons_buffer_data); } /* * Implementation of a FreeBSD low-level, polled console driver. */ static void aju_cnprobe(struct consdev *cp) { sprintf(cp->cn_name, "%s%d", AJU_TTYNAME, 0); cp->cn_pri = (boothowto & RB_SERIAL) ? CN_REMOTE : CN_NORMAL; } static void aju_cninit(struct consdev *cp) { uint32_t v; AJU_CONSOLE_LOCK_INIT(); AJU_CONSOLE_LOCK(); v = aju_cons_control_read(); v &= ~ALTERA_JTAG_UART_CONTROL_AC; aju_cons_control_write(v); AJU_CONSOLE_UNLOCK(); } static void aju_cnterm(struct consdev *cp) { } static int aju_cngetc(struct consdev *cp) { int ret; AJU_CONSOLE_LOCK(); ret = aju_cons_read(); AJU_CONSOLE_UNLOCK(); return (ret); } static void aju_cnputc(struct consdev *cp, int c) { AJU_CONSOLE_LOCK(); aju_cons_write(c); AJU_CONSOLE_UNLOCK(); } static void aju_cngrab(struct consdev *cp) { } static void aju_cnungrab(struct consdev *cp) { } CONSOLE_DRIVER(aju); Index: head/sys/dev/altera/jtag_uart/altera_jtag_uart_tty.c =================================================================== --- head/sys/dev/altera/jtag_uart/altera_jtag_uart_tty.c (revision 274820) +++ head/sys/dev/altera/jtag_uart/altera_jtag_uart_tty.c (revision 274821) @@ -1,474 +1,490 @@ /*- * Copyright (c) 2011-2012 Robert N. M. Watson * All rights reserved. * * This software was developed by SRI International and the University of * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) * ("CTSRD"), as part of the DARPA CRASH research programme. * * 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$"); #include #include #include #include #include #include #include #include #include #include #include #include #include /* * If one of the Altera JTAG UARTs is currently the system console, register * it here. */ static struct altera_jtag_uart_softc *aju_cons_sc; static tsw_outwakeup_t aju_outwakeup; static void aju_ac_callout(void *); static void aju_io_callout(void *); static struct ttydevsw aju_ttydevsw = { .tsw_flags = TF_NOPREFIX, .tsw_outwakeup = aju_outwakeup, }; /* * When polling for the AC bit, the number of times we have to not see it - * before assuming JTAG has disappeared on us. By default, one second. + * before assuming JTAG has disappeared on us. By default, two seconds. */ -#define AJU_JTAG_MAXMISS 5 +#define AJU_JTAG_MAXMISS 10 /* * Polling intervals for input/output and JTAG connection events. */ #define AJU_IO_POLLINTERVAL (hz/100) #define AJU_AC_POLLINTERVAL (hz/5) /* * Low-level read and write register routines; the Altera UART is little * endian, so we byte swap 32-bit reads and writes. */ static inline uint32_t aju_data_read(struct altera_jtag_uart_softc *sc) { return (le32toh(bus_read_4(sc->ajus_mem_res, ALTERA_JTAG_UART_DATA_OFF))); } static inline void aju_data_write(struct altera_jtag_uart_softc *sc, uint32_t v) { bus_write_4(sc->ajus_mem_res, ALTERA_JTAG_UART_DATA_OFF, htole32(v)); } static inline uint32_t aju_control_read(struct altera_jtag_uart_softc *sc) { return (le32toh(bus_read_4(sc->ajus_mem_res, ALTERA_JTAG_UART_CONTROL_OFF))); } static inline void aju_control_write(struct altera_jtag_uart_softc *sc, uint32_t v) { bus_write_4(sc->ajus_mem_res, ALTERA_JTAG_UART_CONTROL_OFF, htole32(v)); } /* * Slightly higher-level routines aware of buffering and flow control. */ static inline int aju_writable(struct altera_jtag_uart_softc *sc) { return ((aju_control_read(sc) & ALTERA_JTAG_UART_CONTROL_WSPACE) != 0); } static inline int aju_readable(struct altera_jtag_uart_softc *sc) { uint32_t v; AJU_LOCK_ASSERT(sc); if (*sc->ajus_buffer_validp) return (1); v = aju_data_read(sc); if ((v & ALTERA_JTAG_UART_DATA_RVALID) != 0) { *sc->ajus_buffer_validp = 1; *sc->ajus_buffer_datap = (v & ALTERA_JTAG_UART_DATA_DATA); return (1); } return (0); } static char aju_read(struct altera_jtag_uart_softc *sc) { AJU_LOCK_ASSERT(sc); while (!aju_readable(sc)); *sc->ajus_buffer_validp = 0; return (*sc->ajus_buffer_datap); } /* * Routines for enabling and disabling interrupts for read and write. */ static void aju_intr_readable_enable(struct altera_jtag_uart_softc *sc) { uint32_t v; AJU_LOCK_ASSERT(sc); v = aju_control_read(sc); v |= ALTERA_JTAG_UART_CONTROL_RE; aju_control_write(sc, v); } static void aju_intr_writable_enable(struct altera_jtag_uart_softc *sc) { uint32_t v; AJU_LOCK_ASSERT(sc); v = aju_control_read(sc); v |= ALTERA_JTAG_UART_CONTROL_WE; aju_control_write(sc, v); } static void aju_intr_writable_disable(struct altera_jtag_uart_softc *sc) { uint32_t v; AJU_LOCK_ASSERT(sc); v = aju_control_read(sc); v &= ~ALTERA_JTAG_UART_CONTROL_WE; aju_control_write(sc, v); } static void aju_intr_disable(struct altera_jtag_uart_softc *sc) { uint32_t v; AJU_LOCK_ASSERT(sc); v = aju_control_read(sc); v &= ~(ALTERA_JTAG_UART_CONTROL_RE | ALTERA_JTAG_UART_CONTROL_WE); aju_control_write(sc, v); } /* * The actual work of checking for, and handling, available reads. This is * used in both polled and interrupt-driven modes, as JTAG UARTs may be hooked * up with, or without, IRQs allocated. */ static void aju_handle_input(struct altera_jtag_uart_softc *sc, struct tty *tp) { int c; tty_lock_assert(tp, MA_OWNED); AJU_LOCK_ASSERT(sc); while (aju_readable(sc)) { c = aju_read(sc); AJU_UNLOCK(sc); #ifdef KDB if (sc->ajus_flags & ALTERA_JTAG_UART_FLAG_CONSOLE) kdb_alt_break(c, &sc->ajus_alt_break_state); #endif ttydisc_rint(tp, c, 0); AJU_LOCK(sc); } AJU_UNLOCK(sc); ttydisc_rint_done(tp); AJU_LOCK(sc); } /* * Send output to the UART until either there's none left to send, or we run * out of room and need to await an interrupt so that we can start sending * again. * * XXXRW: It would be nice to query WSPACE at the beginning and write to the * FIFO in bugger chunks. */ static void aju_handle_output(struct altera_jtag_uart_softc *sc, struct tty *tp) { uint32_t v; uint8_t ch; tty_lock_assert(tp, MA_OWNED); AJU_LOCK_ASSERT(sc); AJU_UNLOCK(sc); while (ttydisc_getc_poll(tp) != 0) { AJU_LOCK(sc); v = aju_control_read(sc); if ((v & ALTERA_JTAG_UART_CONTROL_WSPACE) != 0) { AJU_UNLOCK(sc); if (ttydisc_getc(tp, &ch, sizeof(ch)) != sizeof(ch)) panic("%s: ttydisc_getc", __func__); AJU_LOCK(sc); + + /* + * XXXRW: There is a slight race here in which we test + * for writability, drop the lock, get the character + * from the tty layer, re-acquire the lock, and then + * write. It's possible for other code -- + * specifically, the low-level console -- to have + * written in the mean time, which might mean that + * there is no longer space. The BERI memory bus will + * cause this write to block, wedging the processor + * until space is available -- which could be a while + * if JTAG is not attached! + * + * The 'easy' fix is to drop the character if WSPACE + * has become unset. Not sure what the 'hard' fix is. + */ aju_data_write(sc, ch); } else { /* * If JTAG is not present, then we will drop this * character instead of perhaps scheduling an * interrupt to let us know when there is buffer * space. Otherwise we might get a write interrupt * later even though we aren't interested in sending * anymore. Loop to drain TTY-layer buffer. */ if (*sc->ajus_jtag_presentp == 0) { if (ttydisc_getc(tp, &ch, sizeof(ch)) != sizeof(ch)) panic("%s: ttydisc_getc 2", __func__); AJU_UNLOCK(sc); continue; } if (sc->ajus_irq_res != NULL) aju_intr_writable_enable(sc); return; } AJU_UNLOCK(sc); } AJU_LOCK(sc); aju_intr_writable_disable(sc); } static void aju_outwakeup(struct tty *tp) { struct altera_jtag_uart_softc *sc = tty_softc(tp); tty_lock_assert(tp, MA_OWNED); AJU_LOCK(sc); aju_handle_output(sc, tp); AJU_UNLOCK(sc); } static void aju_io_callout(void *arg) { struct altera_jtag_uart_softc *sc = arg; struct tty *tp = sc->ajus_ttyp; tty_lock(tp); AJU_LOCK(sc); /* * It would be convenient if we could share code with aju_intr() here * by testing the control register for ALTERA_JTAG_UART_CONTROL_RI and * ALTERA_JTAG_UART_CONTROL_WI. Unfortunately, it's not clear that * this is supported, so do all the work to poll for both input and * output. */ aju_handle_input(sc, tp); aju_handle_output(sc, tp); /* * Reschedule next poll attempt. There's some argument that we should * do adaptive polling based on the expectation of I/O: is something * pending in the output buffer, or have we recently had input, but we * don't. */ callout_reset(&sc->ajus_io_callout, AJU_IO_POLLINTERVAL, aju_io_callout, sc); AJU_UNLOCK(sc); tty_unlock(tp); } static void aju_ac_callout(void *arg) { struct altera_jtag_uart_softc *sc = arg; struct tty *tp = sc->ajus_ttyp; uint32_t v; tty_lock(tp); AJU_LOCK(sc); v = aju_control_read(sc); if (v & ALTERA_JTAG_UART_CONTROL_AC) { v &= ~ALTERA_JTAG_UART_CONTROL_AC; aju_control_write(sc, v); if (*sc->ajus_jtag_presentp == 0) { *sc->ajus_jtag_missedp = 0; *sc->ajus_jtag_presentp = 1; aju_handle_output(sc, tp); } } else if (*sc->ajus_jtag_presentp != 0) { (*sc->ajus_jtag_missedp)++; if (*sc->ajus_jtag_missedp >= AJU_JTAG_MAXMISS) { *sc->ajus_jtag_presentp = 0; aju_handle_output(sc, tp); } } callout_reset(&sc->ajus_ac_callout, AJU_AC_POLLINTERVAL, aju_ac_callout, sc); AJU_UNLOCK(sc); tty_unlock(tp); } static void aju_intr(void *arg) { struct altera_jtag_uart_softc *sc = arg; struct tty *tp = sc->ajus_ttyp; uint32_t v; tty_lock(tp); AJU_LOCK(sc); v = aju_control_read(sc); if (v & ALTERA_JTAG_UART_CONTROL_RI) aju_handle_input(sc, tp); if (v & ALTERA_JTAG_UART_CONTROL_WI) aju_handle_output(sc, tp); AJU_UNLOCK(sc); tty_unlock(tp); } int altera_jtag_uart_attach(struct altera_jtag_uart_softc *sc) { struct tty *tp; int error; AJU_LOCK_INIT(sc); /* * XXXRW: Currently, we detect the console solely based on it using a * reserved address, and borrow console-level locks and buffer if so. * Is there a better way? */ if (rman_get_start(sc->ajus_mem_res) == BERI_UART_BASE) { sc->ajus_lockp = &aju_cons_lock; sc->ajus_buffer_validp = &aju_cons_buffer_valid; sc->ajus_buffer_datap = &aju_cons_buffer_data; sc->ajus_jtag_presentp = &aju_cons_jtag_present; sc->ajus_jtag_missedp = &aju_cons_jtag_missed; sc->ajus_flags |= ALTERA_JTAG_UART_FLAG_CONSOLE; } else { sc->ajus_lockp = &sc->ajus_lock; sc->ajus_buffer_validp = &sc->ajus_buffer_valid; sc->ajus_buffer_datap = &sc->ajus_buffer_data; sc->ajus_jtag_presentp = &sc->ajus_jtag_present; sc->ajus_jtag_missedp = &sc->ajus_jtag_missed; } /* * Disable interrupts regardless of whether or not we plan to use * them. We will register an interrupt handler now if they will be * used, but not re-enable intil later once the remainder of the tty * layer is properly initialised, as we're not ready for input yet. */ AJU_LOCK(sc); aju_intr_disable(sc); AJU_UNLOCK(sc); if (sc->ajus_irq_res != NULL) { error = bus_setup_intr(sc->ajus_dev, sc->ajus_irq_res, INTR_ENTROPY | INTR_TYPE_TTY | INTR_MPSAFE, NULL, aju_intr, sc, &sc->ajus_irq_cookie); if (error) { device_printf(sc->ajus_dev, "could not activate interrupt\n"); AJU_LOCK_DESTROY(sc); return (error); } } tp = sc->ajus_ttyp = tty_alloc(&aju_ttydevsw, sc); if (sc->ajus_flags & ALTERA_JTAG_UART_FLAG_CONSOLE) { aju_cons_sc = sc; tty_init_console(tp, 0); } tty_makedev(tp, NULL, "%s%d", AJU_TTYNAME, sc->ajus_unit); /* * If we will be using interrupts, enable them now; otherwise, start * polling. From this point onwards, input can arrive. */ if (sc->ajus_irq_res != NULL) { AJU_LOCK(sc); aju_intr_readable_enable(sc); AJU_UNLOCK(sc); } else { callout_init(&sc->ajus_io_callout, CALLOUT_MPSAFE); callout_reset(&sc->ajus_io_callout, AJU_IO_POLLINTERVAL, aju_io_callout, sc); } callout_init(&sc->ajus_ac_callout, CALLOUT_MPSAFE); callout_reset(&sc->ajus_ac_callout, AJU_AC_POLLINTERVAL, aju_ac_callout, sc); return (0); } void altera_jtag_uart_detach(struct altera_jtag_uart_softc *sc) { struct tty *tp = sc->ajus_ttyp; /* * If we're using interrupts, disable and release the interrupt * handler now. Otherwise drain the polling timeout. */ if (sc->ajus_irq_res != NULL) { AJU_LOCK(sc); aju_intr_disable(sc); AJU_UNLOCK(sc); bus_teardown_intr(sc->ajus_dev, sc->ajus_irq_res, sc->ajus_irq_cookie); } else callout_drain(&sc->ajus_io_callout); callout_drain(&sc->ajus_ac_callout); if (sc->ajus_flags & ALTERA_JTAG_UART_FLAG_CONSOLE) aju_cons_sc = NULL; tty_lock(tp); tty_rel_gone(tp); AJU_LOCK_DESTROY(sc); }