diff options
| author | Robert Watson <rwatson@FreeBSD.org> | 2012-08-25 11:30:36 +0000 |
|---|---|---|
| committer | Robert Watson <rwatson@FreeBSD.org> | 2012-08-25 11:30:36 +0000 |
| commit | 697a77c1c45f6b521941dd1a4e9484967221a7ed (patch) | |
| tree | 4f3e8bdf7db997a5a95432389dc7e7b0057c532b /sys/dev/altera | |
| parent | d432e92a84604b18a645ca42884936428ffd2f1a (diff) | |
Notes
Diffstat (limited to 'sys/dev/altera')
| -rw-r--r-- | sys/dev/altera/jtag_uart/altera_jtag_uart.h | 197 | ||||
| -rw-r--r-- | sys/dev/altera/jtag_uart/altera_jtag_uart_cons.c | 318 | ||||
| -rw-r--r-- | sys/dev/altera/jtag_uart/altera_jtag_uart_nexus.c | 143 | ||||
| -rw-r--r-- | sys/dev/altera/jtag_uart/altera_jtag_uart_tty.c | 474 |
4 files changed, 1132 insertions, 0 deletions
diff --git a/sys/dev/altera/jtag_uart/altera_jtag_uart.h b/sys/dev/altera/jtag_uart/altera_jtag_uart.h new file mode 100644 index 000000000000..1ff3bcb4cfd8 --- /dev/null +++ b/sys/dev/altera/jtag_uart/altera_jtag_uart.h @@ -0,0 +1,197 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +#ifndef _DEV_ALTERA_JTAG_UART_H_ +#define _DEV_ALTERA_JTAG_UART_H_ + +struct altera_jtag_uart_softc { + device_t ajus_dev; + int ajus_unit; + + /* + * Hardware resources. + */ + struct resource *ajus_irq_res; + int ajus_irq_rid; + void *ajus_irq_cookie; + struct resource *ajus_mem_res; + int ajus_mem_rid; + + /* + * TTY resources. + */ + struct tty *ajus_ttyp; + int ajus_alt_break_state; + + /* + * Driver resources. + */ + u_int ajus_flags; + struct mtx *ajus_lockp; + struct mtx ajus_lock; + struct callout ajus_io_callout; + struct callout ajus_ac_callout; + + /* + * One-character buffer required because it's not possible to peek at + * the input FIFO without reading it. + */ + int ajus_buffer_valid; + int *ajus_buffer_validp; + uint8_t ajus_buffer_data; + uint8_t *ajus_buffer_datap; + int ajus_jtag_present; + int *ajus_jtag_presentp; + u_int ajus_jtag_missed; + u_int *ajus_jtag_missedp; +}; + +#define AJU_TTYNAME "ttyu" + +/* + * Flag values for ajus_flags. + */ +#define ALTERA_JTAG_UART_FLAG_CONSOLE 0x00000001 /* Is console. */ + +/* + * Because tty-level use of the I/O ports completes with low-level console + * use, spinlocks must be employed here. + */ +#define AJU_CONSOLE_LOCK_INIT() do { \ + mtx_init(&aju_cons_lock, "aju_cons_lock", NULL, MTX_SPIN); \ +} while (0) + +#define AJU_CONSOLE_LOCK() do { \ + if (!kdb_active) \ + mtx_lock_spin(&aju_cons_lock); \ +} while (0) + +#define AJU_CONSOLE_LOCK_ASSERT() { \ + if (!kdb_active) \ + mtx_assert(&aju_cons_lock, MA_OWNED); \ +} while (0) + +#define AJU_CONSOLE_UNLOCK() do { \ + if (!kdb_active) \ + mtx_unlock_spin(&aju_cons_lock); \ +} while (0) + +#define AJU_LOCK_INIT(sc) do { \ + mtx_init(&(sc)->ajus_lock, "aju_lock", NULL, MTX_SPIN); \ +} while (0) + +#define AJU_LOCK_DESTROY(sc) do { \ + mtx_destroy(&(sc)->ajus_lock); \ +} while (0) + +#define AJU_LOCK(sc) do { \ + mtx_lock_spin((sc)->ajus_lockp); \ +} while (0) + +#define AJU_LOCK_ASSERT(sc) do { \ + mtx_assert((sc)->ajus_lockp, MA_OWNED); \ +} while (0) + +#define AJU_UNLOCK(sc) do { \ + mtx_unlock_spin((sc)->ajus_lockp); \ +} while (0) + +/* + * When a TTY-level Altera JTAG UART instance is also the low-level console, + * the TTY layer borrows the console-layer lock and buffer rather than using + * its own. + */ +extern struct mtx aju_cons_lock; +extern char aju_cons_buffer_data; +extern int aju_cons_buffer_valid; +extern int aju_cons_jtag_present; +extern u_int aju_cons_jtag_missed; + +/* + * Base physical address of the JTAG UART in BERI. + */ +#define BERI_UART_BASE 0x7f000000 /* JTAG UART */ + +/*- + * Routines for interacting with the BERI console JTAG UART. Programming + * details from the June 2011 "Embedded Peripherals User Guide" by Altera + * Corporation, tables 6-2 (JTAG UART Core Register Map), 6-3 (Data Register + * Bits), and 6-4 (Control Register Bits). + * + * Offsets of data and control registers relative to the base. Altera + * conventions are maintained in BERI. + */ +#define ALTERA_JTAG_UART_DATA_OFF 0x00000000 +#define ALTERA_JTAG_UART_CONTROL_OFF 0x00000004 + +/* + * Offset 0: 'data' register -- bits 31-16 (RAVAIL), 15 (RVALID), + * 14-8 (Reserved), 7-0 (DATA). + * + * DATA - One byte read or written. + * RAVAIL - Bytes available to read (excluding the current byte). + * RVALID - Whether the byte in DATA is valid. + */ +#define ALTERA_JTAG_UART_DATA_DATA 0x000000ff +#define ALTERA_JTAG_UART_DATA_RESERVED 0x00007f00 +#define ALTERA_JTAG_UART_DATA_RVALID 0x00008000 +#define ALTERA_JTAG_UART_DATA_RAVAIL 0xffff0000 +#define ALTERA_JTAG_UART_DATA_RAVAIL_SHIFT 16 + +/*- + * Offset 1: 'control' register -- bits 31-16 (WSPACE), 15-11 (Reserved), + * 10 (AC), 9 (WI), 8 (RI), 7..2 (Reserved), 1 (WE), 0 (RE). + * + * RE - Enable read interrupts. + * WE - Enable write interrupts. + * RI - Read interrupt pending. + * WI - Write interrupt pending. + * AC - Activity bit; set to '1' to clear to '0'. + * WSPACE - Space available in the write FIFO. + */ +#define ALTERA_JTAG_UART_CONTROL_RE 0x00000001 +#define ALTERA_JTAG_UART_CONTROL_WE 0x00000002 +#define ALTERA_JTAG_UART_CONTROL_RESERVED0 0x000000fc +#define ALTERA_JTAG_UART_CONTROL_RI 0x00000100 +#define ALTERA_JTAG_UART_CONTROL_WI 0x00000200 +#define ALTERA_JTAG_UART_CONTROL_AC 0x00000400 +#define ALTERA_JTAG_UART_CONTROL_RESERVED1 0x0000f800 +#define ALTERA_JTAG_UART_CONTROL_WSPACE 0xffff0000 +#define ALTERA_JTAG_UART_CONTROL_WSPACE_SHIFT 16 + +/* + * Driver attachment functions for Nexus. + */ +int altera_jtag_uart_attach(struct altera_jtag_uart_softc *sc); +void altera_jtag_uart_detach(struct altera_jtag_uart_softc *sc); + +#endif /* _DEV_ALTERA_JTAG_UART_H_ */ diff --git a/sys/dev/altera/jtag_uart/altera_jtag_uart_cons.c b/sys/dev/altera/jtag_uart/altera_jtag_uart_cons.c new file mode 100644 index 000000000000..ee209cad1c56 --- /dev/null +++ b/sys/dev/altera/jtag_uart/altera_jtag_uart_cons.c @@ -0,0 +1,318 @@ +/*- + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/cons.h> +#include <sys/endian.h> +#include <sys/kdb.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/reboot.h> +#include <sys/systm.h> +#include <sys/tty.h> + +#include <ddb/ddb.h> + +#include <dev/altera/jtag_uart/altera_jtag_uart.h> + +/* + * 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 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. + */ + 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); diff --git a/sys/dev/altera/jtag_uart/altera_jtag_uart_nexus.c b/sys/dev/altera/jtag_uart/altera_jtag_uart_nexus.c new file mode 100644 index 000000000000..25922499058a --- /dev/null +++ b/sys/dev/altera/jtag_uart/altera_jtag_uart_nexus.c @@ -0,0 +1,143 @@ +/*- + * Copyright (c) 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/condvar.h> +#include <sys/conf.h> +#include <sys/bio.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/rman.h> +#include <sys/systm.h> +#include <sys/taskqueue.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +#include <geom/geom_disk.h> + +#include <dev/altera/jtag_uart/altera_jtag_uart.h> + +/* + * Nexus bus attachment for Altera JTAG UARTs. Appropriate for most Altera + * FPGA SoC-style configurations in which the IP core will be exposed to the + * processor via a memory-mapped Avalon bus. + */ +static int +altera_jtag_uart_nexus_probe(device_t dev) +{ + + device_set_desc(dev, "Altera JTAG UART"); + return (BUS_PROBE_DEFAULT); +} + +static int +altera_jtag_uart_nexus_attach(device_t dev) +{ + struct altera_jtag_uart_softc *sc; + int error; + + error = 0; + sc = device_get_softc(dev); + sc->ajus_dev = dev; + sc->ajus_unit = device_get_unit(dev); + sc->ajus_mem_rid = 0; + sc->ajus_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &sc->ajus_mem_rid, RF_ACTIVE); + if (sc->ajus_mem_res == NULL) { + device_printf(dev, "couldn't map memory\n"); + error = ENXIO; + goto out; + } + + /* + * Interrupt support is optional -- if we can't allocate an IRQ, then + * we fall back on polling. + */ + sc->ajus_irq_rid = 0; + sc->ajus_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, + &sc->ajus_irq_rid, RF_ACTIVE | RF_SHAREABLE); + if (sc->ajus_irq_res == NULL) + device_printf(dev, + "IRQ unavailable; selecting polled operation\n"); + error = altera_jtag_uart_attach(sc); +out: + if (error) { + if (sc->ajus_irq_res != NULL) + bus_release_resource(dev, SYS_RES_IRQ, + sc->ajus_irq_rid, sc->ajus_irq_res); + if (sc->ajus_mem_res != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, + sc->ajus_mem_rid, sc->ajus_mem_res); + } + return (error); +} + +static int +altera_jtag_uart_nexus_detach(device_t dev) +{ + struct altera_jtag_uart_softc *sc; + + sc = device_get_softc(dev); + KASSERT(sc->ajus_mem_res != NULL, ("%s: resources not allocated", + __func__)); + + altera_jtag_uart_detach(sc); + bus_release_resource(dev, SYS_RES_IRQ, sc->ajus_irq_rid, + sc->ajus_irq_res); + bus_release_resource(dev, SYS_RES_MEMORY, sc->ajus_mem_rid, + sc->ajus_mem_res); + return (0); +} + +static device_method_t altera_jtag_uart_nexus_methods[] = { + DEVMETHOD(device_probe, altera_jtag_uart_nexus_probe), + DEVMETHOD(device_attach, altera_jtag_uart_nexus_attach), + DEVMETHOD(device_detach, altera_jtag_uart_nexus_detach), + { 0, 0 } +}; + +static driver_t altera_jtag_uart_nexus_driver = { + "altera_jtag_uart", + altera_jtag_uart_nexus_methods, + sizeof(struct altera_jtag_uart_softc), +}; + +static devclass_t altera_jtag_uart_devclass; + +DRIVER_MODULE(altera_jtag_uart, nexus, altera_jtag_uart_nexus_driver, + altera_jtag_uart_devclass, 0, 0); diff --git a/sys/dev/altera/jtag_uart/altera_jtag_uart_tty.c b/sys/dev/altera/jtag_uart/altera_jtag_uart_tty.c new file mode 100644 index 000000000000..ad5b8a41bdc5 --- /dev/null +++ b/sys/dev/altera/jtag_uart/altera_jtag_uart_tty.c @@ -0,0 +1,474 @@ +/*- + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/cons.h> +#include <sys/endian.h> +#include <sys/kdb.h> +#include <sys/rman.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/reboot.h> +#include <sys/tty.h> + +#include <ddb/ddb.h> + +#include <machine/bus.h> + +#include <dev/altera/jtag_uart/altera_jtag_uart.h> + +/* + * 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. + */ +#define AJU_JTAG_MAXMISS 5 + +/* + * 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); + 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); +} |
