Date: Sat, 9 Jun 2012 14:12:41 GMT From: Robert Watson <rwatson@FreeBSD.org> To: Perforce Change Reviews <perforce@freebsd.org> Subject: PERFORCE change 212527 for review Message-ID: <201206091412.q59ECfXw072784@skunkworks.freebsd.org>
next in thread | raw e-mail | index | archive | help
http://p4web.freebsd.org/@@212527?ac=10 Change 212527 by rwatson@rwatson_svr_ctsrd_mipsbuild on 2012/06/09 14:12:32 Rework the Altera JTAG UART device driver to use FreeBSD's bus(9) primitives at the TTY layer, and allow multiple instances to be configured using device.hints. The low-level console portion of the driver continues to be aware of the MIPS physically mapped segment and UART address for the first UART device; however, the TTY layer is now portable and multiple instances can be configured. The default BERI.hints configures each of three JTAG UARTs in our demonstration DE4 setup, hung off of the Nexus. Some care is required to ensure that interactions between the two layers work correctly, such that the TTY for a low-level console borrow the console-layer lock and buffer, whereas TTY instances aren't bound to a console instance maintain their own lock and buffer. It is now possible to enable the ttyu1 and ttyu2 entries in /etc/ttys and use multiple terminal instances. Further driver changes are required to better handle the case where there's no JTAG client using a JTAG UART (which can lead to stalling), and to move to interrupt-driven operation at the TTY layer instead of polling. Affected files ... .. //depot/projects/ctsrd/beribsd/src/sys/dev/altera/jtag_uart/altera_jtag_uart.h#2 edit .. //depot/projects/ctsrd/beribsd/src/sys/dev/altera/jtag_uart/altera_jtag_uart_cons.c#2 edit .. //depot/projects/ctsrd/beribsd/src/sys/dev/altera/jtag_uart/altera_jtag_uart_nexus.c#1 add .. //depot/projects/ctsrd/beribsd/src/sys/dev/altera/jtag_uart/altera_jtag_uart_tty.c#2 edit .. //depot/projects/ctsrd/beribsd/src/sys/mips/beri/files.beri#16 edit .. //depot/projects/ctsrd/beribsd/src/sys/mips/conf/BERI_DE4.hints#5 edit Differences ... ==== //depot/projects/ctsrd/beribsd/src/sys/dev/altera/jtag_uart/altera_jtag_uart.h#2 (text+ko) ==== @@ -31,27 +31,92 @@ #ifndef _DEV_ALTERA_JTAG_UART_H_ #define _DEV_ALTERA_JTAG_UART_H_ -#define AJU_TTYNAME "ttyu0" +struct altera_jtag_uart_softc { + device_t ajus_dev; + int ajus_unit; + + /* + * Hardware resources. + */ + struct resource *ajus_mem_res; + int ajus_mem_rid; + + /* + * TTY resources. + */ + struct tty *ajus_ttyp; + int ajus_alt_break_state; + + /* + * Driver resources. + */ + struct mtx *ajus_lockp; + struct mtx ajus_lock; + struct callout ajus_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; +}; + +#define AJU_TTYNAME "ttyu" -#define AJU_LOCK_INIT() mtx_init(&aj_uart_lock, "aj_uart_lock", \ - NULL, MTX_SPIN) +/* + * 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_LOCK() do { \ +#define AJU_CONSOLE_LOCK() do { \ if (!kdb_active) \ - mtx_lock_spin(&aj_uart_lock); \ + mtx_lock_spin(&aju_cons_lock); \ } while (0) -#define AJU_LOCK_ASSERT() do { \ +#define AJU_CONSOLE_LOCK_ASSERT() { \ if (!kdb_active) \ - mtx_assert(&aj_uart_lock, MA_OWNED); \ + mtx_assert(&aju_cons_lock, MA_OWNED); \ } while (0) -#define AJU_UNLOCK() do { \ +#define AJU_CONSOLE_UNLOCK() do { \ if (!kdb_active) \ - mtx_unlock_spin(&aj_uart_lock); \ + 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) -extern struct mtx aj_uart_lock; +/* + * 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; /* * Base physical address of the JTAG UART in BERI. @@ -106,11 +171,9 @@ #define ALTERA_JTAG_UART_CONTROL_WSPACE_SHIFT 16 /* - * Low-level console routines currently used by the tty layer. + * Driver attachment functions for Nexus. */ -int aj_uart_writable(void); -int aj_uart_readable(void); -void aj_uart_write(char ch); -char aj_uart_read(void); +void 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_ */ ==== //depot/projects/ctsrd/beribsd/src/sys/dev/altera/jtag_uart/altera_jtag_uart_cons.c#2 (text+ko) ==== @@ -35,32 +35,51 @@ #include <sys/cons.h> #include <sys/endian.h> #include <sys/kdb.h> -#include <sys/systm.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> -struct mtx aj_uart_lock; +/* + * 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; +struct mtx aju_cons_lock; /* * Low-level console driver functions. */ -static cn_probe_t aj_uart_cnprobe; -static cn_init_t aj_uart_cninit; -static cn_term_t aj_uart_cnterm; -static cn_getc_t aj_uart_cngetc; -static cn_putc_t aj_uart_cnputc; -static cn_grab_t aj_uart_cngrab; -static cn_ungrab_t aj_uart_cnungrab; +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; /* - * I/O routines lifted from Deimos. + * 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: Should be using FreeBSD's bus routines here. + * 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 @@ -108,18 +127,11 @@ } /* - * One-byte buffer as we can't check whether the UART is readable without - * actually reading from it. - */ -static char buffer_data; -static int buffer_valid; - -/* * 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 -aj_uart_data_read(void) +aju_cons_data_read(void) { return (mips_ioread_uint32le(mips_phys_to_uncached(BERI_UART_BASE + @@ -127,7 +139,7 @@ } static inline void -aj_uart_data_write(uint32_t v) +aju_cons_data_write(uint32_t v) { mips_iowrite_uint32le(mips_phys_to_uncached(BERI_UART_BASE + @@ -135,125 +147,117 @@ } static inline uint32_t -aj_uart_control_read(void) +aju_cons_control_read(void) { return (mips_ioread_uint32le(mips_phys_to_uncached(BERI_UART_BASE + ALTERA_JTAG_UART_CONTROL_OFF))); } -static inline void -aj_uart_control_write(uint32_t v) -{ - - mips_iowrite_uint32le(mips_phys_to_uncached(BERI_UART_BASE + - ALTERA_JTAG_UART_DATA_OFF), v); -} - /* * Slightly higher-level routines aware of buffering and flow control. */ -int -aj_uart_writable(void) +static int +aju_cons_writable(void) { - return ((aj_uart_control_read() & + return ((aju_cons_control_read() & ALTERA_JTAG_UART_CONTROL_WSPACE) != 0); } -int -aj_uart_readable(void) +static int +aju_cons_readable(void) { uint32_t v; - AJU_LOCK_ASSERT(); + AJU_CONSOLE_LOCK_ASSERT(); - if (buffer_valid) + if (aju_cons_buffer_valid) return (1); - v = aj_uart_data_read(); + v = aju_cons_data_read(); if ((v & ALTERA_JTAG_UART_DATA_RVALID) != 0) { - buffer_valid = 1; - buffer_data = (v & ALTERA_JTAG_UART_DATA_DATA); + aju_cons_buffer_valid = 1; + aju_cons_buffer_data = (v & ALTERA_JTAG_UART_DATA_DATA); return (1); } return (0); } -void -aj_uart_write(char ch) +static void +aju_cons_write(char ch) { - AJU_LOCK_ASSERT(); + AJU_CONSOLE_LOCK_ASSERT(); - while (!aj_uart_writable()); - aj_uart_data_write(ch); + while (!aju_cons_writable()); + aju_cons_data_write(ch); } -char -aj_uart_read(void) +static char +aju_cons_read(void) { - AJU_LOCK_ASSERT(); + AJU_CONSOLE_LOCK_ASSERT(); - while (!aj_uart_readable()); - buffer_valid = 0; - return (buffer_data); + 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 -aj_uart_cnprobe(struct consdev *cp) +aju_cnprobe(struct consdev *cp) { - sprintf(cp->cn_name, "ttyu0"); + sprintf(cp->cn_name, "%s%d", AJU_TTYNAME, 0); cp->cn_pri = (boothowto & RB_SERIAL) ? CN_REMOTE : CN_NORMAL; } static void -aj_uart_cninit(struct consdev *cp) +aju_cninit(struct consdev *cp) { - AJU_LOCK_INIT(); + AJU_CONSOLE_LOCK_INIT(); } static void -aj_uart_cnterm(struct consdev *cp) +aju_cnterm(struct consdev *cp) { } static int -aj_uart_cngetc(struct consdev *cp) +aju_cngetc(struct consdev *cp) { int ret; - AJU_LOCK(); - ret = aj_uart_read(); - AJU_UNLOCK(); + AJU_CONSOLE_LOCK(); + ret = aju_cons_read(); + AJU_CONSOLE_UNLOCK(); return (ret); } static void -aj_uart_cnputc(struct consdev *cp, int c) +aju_cnputc(struct consdev *cp, int c) { - AJU_LOCK(); - aj_uart_write(c); - AJU_UNLOCK(); + AJU_CONSOLE_LOCK(); + aju_cons_write(c); + AJU_CONSOLE_UNLOCK(); } static void -aj_uart_cngrab(struct consdev *cp) +aju_cngrab(struct consdev *cp) { } static void -aj_uart_cnungrab(struct consdev *cp) +aju_cnungrab(struct consdev *cp) { } -CONSOLE_DRIVER(aj_uart); +CONSOLE_DRIVER(aju); ==== //depot/projects/ctsrd/beribsd/src/sys/dev/altera/jtag_uart/altera_jtag_uart_tty.c#2 (text+ko) ==== @@ -32,9 +32,11 @@ __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> @@ -42,47 +44,110 @@ #include <ddb/ddb.h> +#include <machine/bus.h> + #include <dev/altera/jtag_uart/altera_jtag_uart.h> /* - * TTY-level fields. + * If one of the Altera JTAG UARTs is currently the system console, register + * it here. */ -static tsw_outwakeup_t aj_uart_outwakeup; +static struct altera_jtag_uart_softc *aju_cons_sc; + +static tsw_outwakeup_t aju_outwakeup; +static void aju_timeout(void *); -static struct ttydevsw aj_uart_ttydevsw = { +static struct ttydevsw aju_ttydevsw = { .tsw_flags = TF_NOPREFIX, - .tsw_outwakeup = aj_uart_outwakeup, + .tsw_outwakeup = aju_outwakeup, }; -static struct callout aj_uart_callout; -static u_int aj_uart_polltime = 1; -#ifdef KDB -static int aj_uart_alt_break_state; -#endif +/* + * Poll for new input every (aju_pollinterval) ticks. + */ +static u_int aju_pollinterval = 1; + +/* + * 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) +{ -static void aj_uart_timeout(void *); + return (le32toh(bus_read_4(sc->ajus_mem_res, + ALTERA_JTAG_UART_CONTROL_OFF))); +} /* - * TTY-level functions for aj_uart. + * 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 void -aj_uart_ttyinit(void *unused) +aju_write(struct altera_jtag_uart_softc *sc, char ch) +{ + + AJU_LOCK_ASSERT(sc); + + while (!aju_writable(sc)); + aju_data_write(sc, ch); +} + +static char +aju_read(struct altera_jtag_uart_softc *sc) { - struct tty *tp; + + AJU_LOCK_ASSERT(sc); - tp = tty_alloc(&aj_uart_ttydevsw, NULL); - tty_init_console(tp, 0); - tty_makedev(tp, NULL, "%s", AJU_TTYNAME); - callout_init(&aj_uart_callout, CALLOUT_MPSAFE); - callout_reset(&aj_uart_callout, aj_uart_polltime, aj_uart_timeout, - tp); + while (!aju_readable(sc)); + *sc->ajus_buffer_validp = 0; + return (*sc->ajus_buffer_datap); } -SYSINIT(aj_uart_ttyinit, SI_SUB_CONFIGURE, SI_ORDER_MIDDLE, aj_uart_ttyinit, - NULL); static void -aj_uart_outwakeup(struct tty *tp) +aju_outwakeup(struct tty *tp) { + struct altera_jtag_uart_softc *sc; int len; u_char ch; @@ -90,37 +155,81 @@ * XXXRW: Would be nice not to do blocking writes to the UART here, * rescheduling on our timer tick if work remains to be done. */ + sc = tty_softc(tp); for (;;) { len = ttydisc_getc(tp, &ch, sizeof(ch)); if (len == 0) break; - AJU_LOCK(); - aj_uart_write(ch); - AJU_UNLOCK(); + AJU_LOCK(sc); + aju_write(sc, ch); + AJU_UNLOCK(sc); } } static void -aj_uart_timeout(void *v) +aju_timeout(void *v) { + struct altera_jtag_uart_softc *sc; struct tty *tp; int c; - tp = v; + sc = v; + tp = sc->ajus_ttyp; tty_lock(tp); - AJU_LOCK(); - while (aj_uart_readable()) { - c = aj_uart_read(); - AJU_UNLOCK(); + AJU_LOCK(sc); + while (aju_readable(sc)) { + c = aju_read(sc); + AJU_UNLOCK(sc); #ifdef KDB - kdb_alt_break(c, &aj_uart_alt_break_state); + kdb_alt_break(c, &sc->ajus_alt_break_state); #endif ttydisc_rint(tp, c, 0); - AJU_LOCK(); + AJU_LOCK(sc); } - AJU_UNLOCK(); + AJU_UNLOCK(sc); ttydisc_rint_done(tp); tty_unlock(tp); - callout_reset(&aj_uart_callout, aj_uart_polltime, aj_uart_timeout, - tp); + callout_reset(&sc->ajus_callout, aju_pollinterval, aju_timeout, sc); +} + +void +altera_jtag_uart_attach(struct altera_jtag_uart_softc *sc) +{ + struct tty *tp; + + tp = sc->ajus_ttyp = tty_alloc(&aju_ttydevsw, 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? + */ + AJU_LOCK_INIT(sc); + if (rman_get_start(sc->ajus_mem_res) == BERI_UART_BASE) { + tty_init_console(tp, 0); + sc->ajus_lockp = &aju_cons_lock; + sc->ajus_buffer_validp = &aju_cons_buffer_valid; + sc->ajus_buffer_datap = &aju_cons_buffer_data; + aju_cons_sc = sc; + } else { + sc->ajus_lockp = &sc->ajus_lock; + sc->ajus_buffer_validp = &sc->ajus_buffer_valid; + sc->ajus_buffer_datap = &sc->ajus_buffer_data; + } + tty_makedev(tp, NULL, "%s%d", AJU_TTYNAME, sc->ajus_unit); + callout_init(&sc->ajus_callout, CALLOUT_MPSAFE); + callout_reset(&sc->ajus_callout, aju_pollinterval, aju_timeout, sc); +} + +void +altera_jtag_uart_detach(struct altera_jtag_uart_softc *sc) +{ + struct tty *tp = sc->ajus_ttyp; + + if (sc == aju_cons_sc) + aju_cons_sc = NULL; + callout_drain(&sc->ajus_callout); + tty_lock(tp); + tty_rel_gone(tp); + AJU_LOCK_DESTROY(sc); } ==== //depot/projects/ctsrd/beribsd/src/sys/mips/beri/files.beri#16 (text+ko) ==== @@ -2,6 +2,7 @@ dev/altera/avgen/altera_avgen.c optional altera_avgen dev/altera/jtag_uart/altera_jtag_uart_cons.c optional altera_jtag_uart dev/altera/jtag_uart/altera_jtag_uart_tty.c optional altera_jtag_uart +dev/altera/jtag_uart/altera_jtag_uart_nexus.c optional altera_jtag_uart dev/altera/sdcard/altera_sdcard.c optional altera_sdcard dev/altera/sdcard/altera_sdcard_disk.c optional altera_sdcard dev/altera/sdcard/altera_sdcard_io.c optional altera_sdcard ==== //depot/projects/ctsrd/beribsd/src/sys/mips/conf/BERI_DE4.hints#5 (text+ko) ==== @@ -1,6 +1,22 @@ # $FreeBSD$ # +# Altera JTAG UARTs configured for console, debugging, and data putput on the +# DE-4. +# +hint.altera_jtag_uart.0.at="nexus0" +hint.altera_jtag_uart.0.maddr=0x7f000000 +hint.altera_jtag_uart.0.msize=0x40 + +hint.altera_jtag_uart.1.at="nexus0" +hint.altera_jtag_uart.1.maddr=0x7f001000 +hint.altera_jtag_uart.1.msize=0x40 + +hint.altera_jtag_uart.2.at="nexus0" +hint.altera_jtag_uart.2.maddr=0x7f002000 +hint.altera_jtag_uart.2.msize=0x40 + +# # On-board DE4 and tPad SD Card IP core # hint.altera_sdcardc.0.at="nexus0"
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201206091412.q59ECfXw072784>