diff options
-rw-r--r-- | drivers/tty/Kconfig | 18 | ||||
-rw-r--r-- | drivers/tty/Makefile | 1 | ||||
-rw-r--r-- | drivers/tty/mips_ejtag_fdc.c | 1126 |
3 files changed, 1145 insertions, 0 deletions
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig index b24aa010f68c..39469ca4231c 100644 --- a/drivers/tty/Kconfig +++ b/drivers/tty/Kconfig @@ -419,4 +419,22 @@ config DA_CONSOLE help This enables a console on a Dash channel. +config MIPS_EJTAG_FDC_TTY + bool "MIPS EJTAG Fast Debug Channel TTY" + depends on MIPS_CDMM + help + This enables a TTY and console on the MIPS EJTAG Fast Debug Channels, + if they are present. This can be useful when working with an EJTAG + probe which supports it, to get console output and a login prompt via + EJTAG without needing to connect a serial cable. + + TTY devices are named e.g. ttyFDC3c2 (for FDC channel 2 of the FDC on + CPU3). + + The console can be enabled with console=fdc1 (for FDC channel 1 on all + CPUs). Do not use the console unless there is a debug probe attached + to drain the FDC TX FIFO. + + If unsure, say N. + endif # TTY diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index 58ad1c05b7f8..5817e2397463 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -29,5 +29,6 @@ obj-$(CONFIG_SYNCLINK) += synclink.o obj-$(CONFIG_PPC_EPAPR_HV_BYTECHAN) += ehv_bytechan.o obj-$(CONFIG_GOLDFISH_TTY) += goldfish.o obj-$(CONFIG_DA_TTY) += metag_da.o +obj-$(CONFIG_MIPS_EJTAG_FDC_TTY) += mips_ejtag_fdc.o obj-y += ipwireless/ diff --git a/drivers/tty/mips_ejtag_fdc.c b/drivers/tty/mips_ejtag_fdc.c new file mode 100644 index 000000000000..51672cfe7e45 --- /dev/null +++ b/drivers/tty/mips_ejtag_fdc.c @@ -0,0 +1,1126 @@ +/* + * TTY driver for MIPS EJTAG Fast Debug Channels. + * + * Copyright (C) 2007-2015 Imagination Technologies Ltd + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for more + * details. + */ + +#include <linux/atomic.h> +#include <linux/bitops.h> +#include <linux/completion.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/sched.h> +#include <linux/serial.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/uaccess.h> + +#include <asm/cdmm.h> +#include <asm/irq.h> + +/* Register offsets */ +#define REG_FDACSR 0x00 /* FDC Access Control and Status Register */ +#define REG_FDCFG 0x08 /* FDC Configuration Register */ +#define REG_FDSTAT 0x10 /* FDC Status Register */ +#define REG_FDRX 0x18 /* FDC Receive Register */ +#define REG_FDTX(N) (0x20+0x8*(N)) /* FDC Transmit Register n (0..15) */ + +/* Register fields */ + +#define REG_FDCFG_TXINTTHRES_SHIFT 18 +#define REG_FDCFG_TXINTTHRES (0x3 << REG_FDCFG_TXINTTHRES_SHIFT) +#define REG_FDCFG_TXINTTHRES_DISABLED (0x0 << REG_FDCFG_TXINTTHRES_SHIFT) +#define REG_FDCFG_TXINTTHRES_EMPTY (0x1 << REG_FDCFG_TXINTTHRES_SHIFT) +#define REG_FDCFG_TXINTTHRES_NOTFULL (0x2 << REG_FDCFG_TXINTTHRES_SHIFT) +#define REG_FDCFG_TXINTTHRES_NEAREMPTY (0x3 << REG_FDCFG_TXINTTHRES_SHIFT) +#define REG_FDCFG_RXINTTHRES_SHIFT 16 +#define REG_FDCFG_RXINTTHRES (0x3 << REG_FDCFG_RXINTTHRES_SHIFT) +#define REG_FDCFG_RXINTTHRES_DISABLED (0x0 << REG_FDCFG_RXINTTHRES_SHIFT) +#define REG_FDCFG_RXINTTHRES_FULL (0x1 << REG_FDCFG_RXINTTHRES_SHIFT) +#define REG_FDCFG_RXINTTHRES_NOTEMPTY (0x2 << REG_FDCFG_RXINTTHRES_SHIFT) +#define REG_FDCFG_RXINTTHRES_NEARFULL (0x3 << REG_FDCFG_RXINTTHRES_SHIFT) +#define REG_FDCFG_TXFIFOSIZE_SHIFT 8 +#define REG_FDCFG_TXFIFOSIZE (0xff << REG_FDCFG_TXFIFOSIZE_SHIFT) +#define REG_FDCFG_RXFIFOSIZE_SHIFT 0 +#define REG_FDCFG_RXFIFOSIZE (0xff << REG_FDCFG_RXFIFOSIZE_SHIFT) + +#define REG_FDSTAT_TXCOUNT_SHIFT 24 +#define REG_FDSTAT_TXCOUNT (0xff << REG_FDSTAT_TXCOUNT_SHIFT) +#define REG_FDSTAT_RXCOUNT_SHIFT 16 +#define REG_FDSTAT_RXCOUNT (0xff << REG_FDSTAT_RXCOUNT_SHIFT) +#define REG_FDSTAT_RXCHAN_SHIFT 4 +#define REG_FDSTAT_RXCHAN (0xf << REG_FDSTAT_RXCHAN_SHIFT) +#define REG_FDSTAT_RXE BIT(3) /* Rx Empty */ +#define REG_FDSTAT_RXF BIT(2) /* Rx Full */ +#define REG_FDSTAT_TXE BIT(1) /* Tx Empty */ +#define REG_FDSTAT_TXF BIT(0) /* Tx Full */ + +#define NUM_TTY_CHANNELS 16 + +#define RX_BUF_SIZE 1024 + +/* + * When the IRQ is unavailable, the FDC state must be polled for incoming data + * and space becoming available in TX FIFO. + */ +#define FDC_TTY_POLL (HZ / 50) + +struct mips_ejtag_fdc_tty; + +/** + * struct mips_ejtag_fdc_tty_port - Wrapper struct for FDC tty_port. + * @port: TTY port data + * @driver: TTY driver. + * @rx_lock: Lock for rx_buf. + * This protects between the hard interrupt and user + * context. It's also held during read SWITCH operations. + * @rx_buf: Read buffer. + * @xmit_lock: Lock for xmit_*, and port.xmit_buf. + * This protects between user context and kernel thread. + * It is used from chars_in_buffer()/write_room() TTY + * callbacks which are used during wait operations, so a + * mutex is unsuitable. + * @xmit_cnt: Size of xmit buffer contents. + * @xmit_head: Head of xmit buffer where data is written. + * @xmit_tail: Tail of xmit buffer where data is read. + * @xmit_empty: Completion for xmit buffer being empty. + */ +struct mips_ejtag_fdc_tty_port { + struct tty_port port; + struct mips_ejtag_fdc_tty *driver; + raw_spinlock_t rx_lock; + void *rx_buf; + spinlock_t xmit_lock; + unsigned int xmit_cnt; + unsigned int xmit_head; + unsigned int xmit_tail; + struct completion xmit_empty; +}; + +/** + * struct mips_ejtag_fdc_tty - Driver data for FDC as a whole. + * @dev: FDC device (for dev_*() logging). + * @driver: TTY driver. + * @cpu: CPU number for this FDC. + * @fdc_name: FDC name (not for base of channel names). + * @driver_name: Base of driver name. + * @ports: Per-channel data. + * @waitqueue: Wait queue for waiting for TX data, or for space in TX + * FIFO. + * @lock: Lock to protect FDCFG (interrupt enable). + * @thread: KThread for writing out data to FDC. + * @reg: FDC registers. + * @tx_fifo: TX FIFO size. + * @xmit_size: Size of each port's xmit buffer. + * @xmit_total: Total number of bytes (from all ports) to transmit. + * @xmit_next: Next port number to transmit from (round robin). + * @xmit_full: Indicates TX FIFO is full, we're waiting for space. + * @irq: IRQ number (negative if no IRQ). + * @removing: Indicates the device is being removed and @poll_timer + * should not be restarted. + * @poll_timer: Timer for polling for interrupt events when @irq < 0. + */ +struct mips_ejtag_fdc_tty { + struct device *dev; + struct tty_driver *driver; + unsigned int cpu; + char fdc_name[16]; + char driver_name[16]; + struct mips_ejtag_fdc_tty_port ports[NUM_TTY_CHANNELS]; + wait_queue_head_t waitqueue; + raw_spinlock_t lock; + struct task_struct *thread; + + void __iomem *reg; + u8 tx_fifo; + + unsigned int xmit_size; + atomic_t xmit_total; + unsigned int xmit_next; + bool xmit_full; + + int irq; + bool removing; + struct timer_list poll_timer; +}; + +/* Hardware access */ + +static inline void mips_ejtag_fdc_write(struct mips_ejtag_fdc_tty *priv, + unsigned int offs, unsigned int data) +{ + iowrite32(data, priv->reg + offs); +} + +static inline unsigned int mips_ejtag_fdc_read(struct mips_ejtag_fdc_tty *priv, + unsigned int offs) +{ + return ioread32(priv->reg + offs); +} + +/* Encoding of byte stream in FDC words */ + +/** + * struct fdc_word - FDC word encoding some number of bytes of data. + * @word: Raw FDC word. + * @bytes: Number of bytes encoded by @word. + */ +struct fdc_word { + u32 word; + unsigned int bytes; +}; + +/* + * This is a compact encoding which allows every 1 byte, 2 byte, and 3 byte + * sequence to be encoded in a single word, while allowing the majority of 4 + * byte sequences (including all ASCII and common binary data) to be encoded in + * a single word too. + * _______________________ _____________ + * | FDC Word | | + * |31-24|23-16|15-8 | 7-0 | Bytes | + * |_____|_____|_____|_____|_____________| + * | | | | | | + * |0x80 |0x80 |0x80 | WW | WW | + * |0x81 |0x81 | XX | WW | WW XX | + * |0x82 | YY | XX | WW | WW XX YY | + * | ZZ | YY | XX | WW | WW XX YY ZZ | + * |_____|_____|_____|_____|_____________| + * + * Note that the 4-byte encoding can only be used where none of the other 3 + * encodings match, otherwise it must fall back to the 3 byte encoding. + */ + +/* ranges >= 1 && sizes[0] >= 1 */ +static struct fdc_word mips_ejtag_fdc_encode(const char **ptrs, + unsigned int *sizes, + unsigned int ranges) +{ + struct fdc_word word = { 0, 0 }; + const char **ptrs_end = ptrs + ranges; + + for (; ptrs < ptrs_end; ++ptrs) { + const char *ptr = *(ptrs++); + const char *end = ptr + *(sizes++); + + for (; ptr < end; ++ptr) { + word.word |= (u8)*ptr << (8*word.bytes); + ++word.bytes; + if (word.bytes == 4) + goto done; + } + } +done: + /* Choose the appropriate encoding */ + switch (word.bytes) { + case 4: + /* 4 byte encoding, but don't match the 1-3 byte encodings */ + if ((word.word >> 8) != 0x808080 && + (word.word >> 16) != 0x8181 && + (word.word >> 24) != 0x82) + break; + /* Fall back to a 3 byte encoding */ + word.bytes = 3; + word.word &= 0x00ffffff; + case 3: + /* 3 byte encoding */ + word.word |= 0x82000000; + break; + case 2: + /* 2 byte encoding */ + word.word |= 0x81810000; + break; + case 1: + /* 1 byte encoding */ + word.word |= 0x80808000; + break; + } + return word; +} + +static unsigned int mips_ejtag_fdc_decode(u32 word, char *buf) +{ + buf[0] = (u8)word; + word >>= 8; + if (word == 0x808080) + return 1; + buf[1] = (u8)word; + word >>= 8; + if (word == 0x8181) + return 2; + buf[2] = (u8)word; + word >>= 8; + if (word == 0x82) + return 3; + buf[3] = (u8)word; + return 4; +} + +/* Console operations */ + +/** + * struct mips_ejtag_fdc_console - Wrapper struct for FDC consoles. + * @cons: Console object. + * @tty_drv: TTY driver associated with this console. + * @lock: Lock to protect concurrent access to other fields. + * This is raw because it may be used very early. + * @initialised: Whether the console is initialised. + * @regs: Registers base address for each CPU. + */ +struct mips_ejtag_fdc_console { + struct console cons; + struct tty_driver *tty_drv; + raw_spinlock_t lock; + bool initialised; + void __iomem *regs[NR_CPUS]; +}; + +/* Low level console write shared by early console and normal console */ +static void mips_ejtag_fdc_console_write(struct console *c, const char *s, + unsigned int count) +{ + struct mips_ejtag_fdc_console *cons = + container_of(c, struct mips_ejtag_fdc_console, cons); + void __iomem *regs; + struct fdc_word word; + unsigned long flags; + unsigned int i, buf_len, cpu; + bool done_cr = false; + char buf[4]; + const char *buf_ptr = buf; + /* Number of bytes of input data encoded up to each byte in buf */ + u8 inc[4]; + + local_irq_save(flags); + cpu = smp_processor_id(); + regs = cons->regs[cpu]; + /* First console output on this CPU? */ + if (!regs) { + regs = mips_cdmm_early_probe(0xfd); + cons->regs[cpu] = regs; + } + /* Already tried and failed to find FDC on this CPU? */ + if (IS_ERR(regs)) + goto out; + while (count) { + /* + * Copy the next few characters to a buffer so we can inject + * carriage returns before newlines. + */ + for (buf_len = 0, i = 0; buf_len < 4 && i < count; ++buf_len) { + if (s[i] == '\n' && !done_cr) { + buf[buf_len] = '\r'; + done_cr = true; + } else { + buf[buf_len] = s[i]; + done_cr = false; + ++i; + } + inc[buf_len] = i; + } + word = mips_ejtag_fdc_encode(&buf_ptr, &buf_len, 1); + count -= inc[word.bytes - 1]; + s += inc[word.bytes - 1]; + + /* Busy wait until there's space in fifo */ + while (ioread32(regs + REG_FDSTAT) & REG_FDSTAT_TXF) + ; + iowrite32(word.word, regs + REG_FDTX(c->index)); + } +out: + local_irq_restore(flags); +} + +static struct tty_driver *mips_ejtag_fdc_console_device(struct console *c, + int *index) +{ + struct mips_ejtag_fdc_console *cons = + container_of(c, struct mips_ejtag_fdc_console, cons); + + *index = c->index; + return cons->tty_drv; +} + +/* Initialise an FDC console (early or normal */ +static int __init mips_ejtag_fdc_console_init(struct mips_ejtag_fdc_console *c) +{ + void __iomem *regs; + unsigned long flags; + int ret = 0; + + raw_spin_lock_irqsave(&c->lock, flags); + /* Don't init twice */ + if (c->initialised) + goto out; + /* Look for the FDC device */ + regs = mips_cdmm_early_probe(0xfd); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + goto out; + } + + c->initialised = true; + c->regs[smp_processor_id()] = regs; + register_console(&c->cons); +out: + raw_spin_unlock_irqrestore(&c->lock, flags); + return ret; +} + +static struct mips_ejtag_fdc_console mips_ejtag_fdc_con = { + .cons = { + .name = "fdc", + .write = mips_ejtag_fdc_console_write, + .device = mips_ejtag_fdc_console_device, + .flags = CON_PRINTBUFFER, + .index = -1, + }, + .lock = __RAW_SPIN_LOCK_UNLOCKED(mips_ejtag_fdc_con.lock), +}; + +/* TTY RX/TX operations */ + +/** + * mips_ejtag_fdc_put_chan() - Write out a block of channel data. + * @priv: Pointer to driver private data. + * @chan: Channel number. + * + * Write a single block of data out to the debug adapter. If the circular buffer + * is wrapped then only the first block is written. + * + * Returns: The number of bytes that were written. + */ +static unsigned int mips_ejtag_fdc_put_chan(struct mips_ejtag_fdc_tty *priv, + unsigned int chan) +{ + struct mips_ejtag_fdc_tty_port *dport; + struct tty_struct *tty; + const char *ptrs[2]; + unsigned int sizes[2] = { 0 }; + struct fdc_word word = { .bytes = 0 }; + unsigned long flags; + + dport = &priv->ports[chan]; + spin_lock(&dport->xmit_lock); + if (dport->xmit_cnt) { + ptrs[0] = dport->port.xmit_buf + dport->xmit_tail; + sizes[0] = min_t(unsigned int, + priv->xmit_size - dport->xmit_tail, + dport->xmit_cnt); + ptrs[1] = dport->port.xmit_buf; + sizes[1] = dport->xmit_cnt - sizes[0]; + word = mips_ejtag_fdc_encode(ptrs, sizes, 1 + !!sizes[1]); + + dev_dbg(priv->dev, "%s%u: out %08x: \"%*pE%*pE\"\n", + priv->driver_name, chan, word.word, + min_t(int, word.bytes, sizes[0]), ptrs[0], + max_t(int, 0, word.bytes - sizes[0]), ptrs[1]); + + local_irq_save(flags); + /* Maybe we raced with the console and TX FIFO is full */ + if (mips_ejtag_fdc_read(priv, REG_FDSTAT) & REG_FDSTAT_TXF) + word.bytes = 0; + else + mips_ejtag_fdc_write(priv, REG_FDTX(chan), word.word); + local_irq_restore(flags); + + dport->xmit_cnt -= word.bytes; + if (!dport->xmit_cnt) { + /* Reset pointers to avoid wraps */ + dport->xmit_head = 0; + dport->xmit_tail = 0; + complete(&dport->xmit_empty); + } else { + dport->xmit_tail += word.bytes; + if (dport->xmit_tail >= priv->xmit_size) + dport->xmit_tail -= priv->xmit_size; + } + atomic_sub(word.bytes, &priv->xmit_total); + } + spin_unlock(&dport->xmit_lock); + + /* If we've made more data available, wake up tty */ + if (sizes[0] && word.bytes) { + tty = tty_port_tty_get(&dport->port); + if (tty) { + tty_wakeup(tty); + tty_kref_put(tty); + } + } + + return word.bytes; +} + +/** + * mips_ejtag_fdc_put() - Kernel thread to write out channel data to FDC. + * @arg: Driver pointer. + * + * This kernel thread runs while @priv->xmit_total != 0, and round robins the + * channels writing out blocks of buffered data to the FDC TX FIFO. + */ +static int mips_ejtag_fdc_put(void *arg) +{ + struct mips_ejtag_fdc_tty *priv = arg; + struct mips_ejtag_fdc_tty_port *dport; + unsigned int ret; + u32 cfg; + + __set_current_state(TASK_RUNNING); + while (!kthread_should_stop()) { + /* Wait for data to actually write */ + wait_event_interruptible(priv->waitqueue, + atomic_read(&priv->xmit_total) || + kthread_should_stop()); + if (kthread_should_stop()) + break; + + /* Wait for TX FIFO space to write data */ + raw_spin_lock_irq(&priv->lock); + if (mips_ejtag_fdc_read(priv, REG_FDSTAT) & REG_FDSTAT_TXF) { + priv->xmit_full = true; + if (priv->irq >= 0) { + /* Enable TX interrupt */ + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + cfg &= ~REG_FDCFG_TXINTTHRES; + cfg |= REG_FDCFG_TXINTTHRES_NOTFULL; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + } + } + raw_spin_unlock_irq(&priv->lock); + wait_event_interruptible(priv->waitqueue, + !(mips_ejtag_fdc_read(priv, REG_FDSTAT) + & REG_FDSTAT_TXF) || + kthread_should_stop()); + if (kthread_should_stop()) + break; + + /* Find next channel with data to output */ + for (;;) { + dport = &priv->ports[priv->xmit_next]; + spin_lock(&dport->xmit_lock); + ret = dport->xmit_cnt; + spin_unlock(&dport->xmit_lock); + if (ret) + break; + /* Round robin */ + ++priv->xmit_next; + if (priv->xmit_next >= NUM_TTY_CHANNELS) + priv->xmit_next = 0; + } + + /* Try writing data to the chosen channel */ + ret = mips_ejtag_fdc_put_chan(priv, priv->xmit_next); + + /* + * If anything was output, move on to the next channel so as not + * to starve other channels. + */ + if (ret) { + ++priv->xmit_next; + if (priv->xmit_next >= NUM_TTY_CHANNELS) + priv->xmit_next = 0; + } + } + + return 0; +} + +/** + * mips_ejtag_fdc_handle() - Handle FDC events. + * @priv: Pointer to driver private data. + * + * Handle FDC events, such as new incoming data which needs draining out of the + * RX FIFO and feeding into the appropriate TTY ports, and space becoming + * available in the TX FIFO which would allow more data to be written out. + */ +static void mips_ejtag_fdc_handle(struct mips_ejtag_fdc_tty *priv) +{ + struct mips_ejtag_fdc_tty_port *dport; + unsigned int stat, channel, data, cfg, i, flipped; + int len; + char buf[4]; + + for (;;) { + /* Find which channel the next FDC word is destined for */ + stat = mips_ejtag_fdc_read(priv, REG_FDSTAT); + if (stat & REG_FDSTAT_RXE) + break; + channel = (stat & REG_FDSTAT_RXCHAN) >> REG_FDSTAT_RXCHAN_SHIFT; + dport = &priv->ports[channel]; + + /* Read out the FDC word, decode it, and pass to tty layer */ + raw_spin_lock(&dport->rx_lock); + data = mips_ejtag_fdc_read(priv, REG_FDRX); + + /* Check the port isn't being shut down */ + if (!dport->rx_buf) + goto unlock; + + len = mips_ejtag_fdc_decode(data, buf); + dev_dbg(priv->dev, "%s%u: in %08x: \"%*pE\"\n", + priv->driver_name, channel, data, len, buf); + + flipped = 0; + for (i = 0; i < len; ++i) + flipped += tty_insert_flip_char(&dport->port, buf[i], + TTY_NORMAL); + if (flipped) + tty_flip_buffer_push(&dport->port); +unlock: + raw_spin_unlock(&dport->rx_lock); + } + + /* If TX FIFO no longer full we may be able to write more data */ + raw_spin_lock(&priv->lock); + if (priv->xmit_full && !(stat & REG_FDSTAT_TXF)) { + priv->xmit_full = false; + + /* Disable TX interrupt */ + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + cfg &= ~REG_FDCFG_TXINTTHRES; + cfg |= REG_FDCFG_TXINTTHRES_DISABLED; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + + /* Wait the kthread so it can try writing more data */ + wake_up_interruptible(&priv->waitqueue); + } + raw_spin_unlock(&priv->lock); +} + +/** + * mips_ejtag_fdc_isr() - Interrupt handler. + * @irq: IRQ number. + * @dev_id: Pointer to driver private data. + * + * This is the interrupt handler, used when interrupts are enabled. + * + * It simply triggers the common FDC handler code. + * + * Returns: IRQ_HANDLED if an FDC interrupt was pending. + * IRQ_NONE otherwise. + */ +static irqreturn_t mips_ejtag_fdc_isr(int irq, void *dev_id) +{ + struct mips_ejtag_fdc_tty *priv = dev_id; + + /* + * We're not using proper per-cpu IRQs, so we must be careful not to + * handle IRQs on CPUs we're not interested in. + * + * Ideally proper per-cpu IRQ handlers could be used, but that doesn't + * fit well with the whole sharing of the main CPU IRQ lines. When we + * have something with a GIC that routes the FDC IRQs (i.e. no sharing + * between handlers) then support could be added more easily. + */ + if (smp_processor_id() != priv->cpu) + return IRQ_NONE; + + /* If no FDC interrupt pending, it wasn't for us */ + if (!(read_c0_cause() & CAUSEF_FDCI)) + return IRQ_NONE; + + mips_ejtag_fdc_handle(priv); + return IRQ_HANDLED; +} + +/** + * mips_ejtag_fdc_tty_timer() - Poll FDC for incoming data. + * @opaque: Pointer to driver private data. + * + * This is the timer handler for when interrupts are disabled and polling the + * FDC state is required. + * + * It simply triggers the common FDC handler code and arranges for further + * polling. + */ +static void mips_ejtag_fdc_tty_timer(unsigned long opaque) +{ + struct mips_ejtag_fdc_tty *priv = (void *)opaque; + + mips_ejtag_fdc_handle(priv); + if (!priv->removing) + mod_timer_pinned(&priv->poll_timer, jiffies + FDC_TTY_POLL); +} + +/* TTY Port operations */ + +static int mips_ejtag_fdc_tty_port_activate(struct tty_port *port, + struct tty_struct *tty) +{ + struct mips_ejtag_fdc_tty_port *dport = + container_of(port, struct mips_ejtag_fdc_tty_port, port); + void *rx_buf; + + /* Allocate the buffer we use for writing data */ + if (tty_port_alloc_xmit_buf(port) < 0) + goto err; + + /* Allocate the buffer we use for reading data */ + rx_buf = kzalloc(RX_BUF_SIZE, GFP_KERNEL); + if (!rx_buf) + goto err_free_xmit; + + raw_spin_lock_irq(&dport->rx_lock); + dport->rx_buf = rx_buf; + raw_spin_unlock_irq(&dport->rx_lock); + + return 0; +err_free_xmit: + tty_port_free_xmit_buf(port); +err: + return -ENOMEM; +} + +static void mips_ejtag_fdc_tty_port_shutdown(struct tty_port *port) +{ + struct mips_ejtag_fdc_tty_port *dport = + container_of(port, struct mips_ejtag_fdc_tty_port, port); + struct mips_ejtag_fdc_tty *priv = dport->driver; + void *rx_buf; + unsigned int count; + + spin_lock(&dport->xmit_lock); + count = dport->xmit_cnt; + spin_unlock(&dport->xmit_lock); + if (count) { + /* + * There's still data to write out, so wake and wait for the + * writer thread to drain the buffer. + */ + wake_up_interruptible(&priv->waitqueue); + wait_for_completion(&dport->xmit_empty); + } + + /* Null the read buffer (timer could still be running!) */ + raw_spin_lock_irq(&dport->rx_lock); + rx_buf = dport->rx_buf; + dport->rx_buf = NULL; + raw_spin_unlock_irq(&dport->rx_lock); + /* Free the read buffer */ + kfree(rx_buf); + + /* Free the write buffer */ + tty_port_free_xmit_buf(port); +} + +static const struct tty_port_operations mips_ejtag_fdc_tty_port_ops = { + .activate = mips_ejtag_fdc_tty_port_activate, + .shutdown = mips_ejtag_fdc_tty_port_shutdown, +}; + +/* TTY operations */ + +static int mips_ejtag_fdc_tty_install(struct tty_driver *driver, + struct tty_struct *tty) +{ + struct mips_ejtag_fdc_tty *priv = driver->driver_state; + + tty->driver_data = &priv->ports[tty->index]; + return tty_port_install(&priv->ports[tty->index].port, driver, tty); +} + +static int mips_ejtag_fdc_tty_open(struct tty_struct *tty, struct file *filp) +{ + return tty_port_open(tty->port, tty, filp); +} + +static void mips_ejtag_fdc_tty_close(struct tty_struct *tty, struct file *filp) +{ + return tty_port_close(tty->port, tty, filp); +} + +static void mips_ejtag_fdc_tty_hangup(struct tty_struct *tty) +{ + struct mips_ejtag_fdc_tty_port *dport = tty->driver_data; + struct mips_ejtag_fdc_tty *priv = dport->driver; + + /* Drop any data in the xmit buffer */ + spin_lock(&dport->xmit_lock); + if (dport->xmit_cnt) { + atomic_sub(dport->xmit_cnt, &priv->xmit_total); + dport->xmit_cnt = 0; + dport->xmit_head = 0; + dport->xmit_tail = 0; + complete(&dport->xmit_empty); + } + spin_unlock(&dport->xmit_lock); + + tty_port_hangup(tty->port); +} + +static int mips_ejtag_fdc_tty_write(struct tty_struct *tty, + const unsigned char *buf, int total) +{ + int count, block; + struct mips_ejtag_fdc_tty_port *dport = tty->driver_data; + struct mips_ejtag_fdc_tty *priv = dport->driver; + + /* + * Write to output buffer. + * + * The reason that we asynchronously write the buffer is because if we + * were to write the buffer synchronously then because the channels are + * per-CPU the buffer would be written to the channel of whatever CPU + * we're running on. + * + * What we actually want to happen is have all input and output done on + * one CPU. + */ + spin_lock(&dport->xmit_lock); + /* Work out how many bytes we can write to the xmit buffer */ + total = min(total, (int)(priv->xmit_size - dport->xmit_cnt)); + atomic_add(total, &priv->xmit_total); + dport->xmit_cnt += total; + /* Write the actual bytes (may need splitting if it wraps) */ + for (count = total; count; count -= block) { + block = min(count, (int)(priv->xmit_size - dport->xmit_head)); + memcpy(dport->port.xmit_buf + dport->xmit_head, buf, block); + dport->xmit_head += block; + if (dport->xmit_head >= priv->xmit_size) + dport->xmit_head -= priv->xmit_size; + buf += block; + } + count = dport->xmit_cnt; + /* Xmit buffer no longer empty? */ + if (count) + reinit_completion(&dport->xmit_empty); + spin_unlock(&dport->xmit_lock); + + /* Wake up the kthread */ + if (total) + wake_up_interruptible(&priv->waitqueue); + return total; +} + +static int mips_ejtag_fdc_tty_write_room(struct tty_struct *tty) +{ + struct mips_ejtag_fdc_tty_port *dport = tty->driver_data; + struct mips_ejtag_fdc_tty *priv = dport->driver; + int room; + + /* Report the space in the xmit buffer */ + spin_lock(&dport->xmit_lock); + room = priv->xmit_size - dport->xmit_cnt; + spin_unlock(&dport->xmit_lock); + + return room; +} + +static int mips_ejtag_fdc_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct mips_ejtag_fdc_tty_port *dport = tty->driver_data; + int chars; + + /* Report the number of bytes in the xmit buffer */ + spin_lock(&dport->xmit_lock); + chars = dport->xmit_cnt; + spin_unlock(&dport->xmit_lock); + + return chars; +} + +static const struct tty_operations mips_ejtag_fdc_tty_ops = { + .install = mips_ejtag_fdc_tty_install, + .open = mips_ejtag_fdc_tty_open, + .close = mips_ejtag_fdc_tty_close, + .hangup = mips_ejtag_fdc_tty_hangup, + .write = mips_ejtag_fdc_tty_write, + .write_room = mips_ejtag_fdc_tty_write_room, + .chars_in_buffer = mips_ejtag_fdc_tty_chars_in_buffer, +}; + +static int mips_ejtag_fdc_tty_probe(struct mips_cdmm_device *dev) +{ + int ret, nport; + struct mips_ejtag_fdc_tty_port *dport; + struct mips_ejtag_fdc_tty *priv; + struct tty_driver *driver; + unsigned int cfg, tx_fifo; + + priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->cpu = dev->cpu; + priv->dev = &dev->dev; + mips_cdmm_set_drvdata(dev, priv); + atomic_set(&priv->xmit_total, 0); + raw_spin_lock_init(&priv->lock); + + priv->reg = devm_ioremap_nocache(priv->dev, dev->res.start, + resource_size(&dev->res)); + if (!priv->reg) { + dev_err(priv->dev, "ioremap failed for resource %pR\n", + &dev->res); + return -ENOMEM; + } + + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + tx_fifo = (cfg & REG_FDCFG_TXFIFOSIZE) >> REG_FDCFG_TXFIFOSIZE_SHIFT; + /* Disable interrupts */ + cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); + cfg |= REG_FDCFG_TXINTTHRES_DISABLED; + cfg |= REG_FDCFG_RXINTTHRES_DISABLED; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + + /* Make each port's xmit FIFO big enough to fill FDC TX FIFO */ + priv->xmit_size = min(tx_fifo * 4, (unsigned int)SERIAL_XMIT_SIZE); + + driver = tty_alloc_driver(NUM_TTY_CHANNELS, TTY_DRIVER_REAL_RAW); + if (IS_ERR(driver)) + return PTR_ERR(driver); + priv->driver = driver; + + driver->driver_name = "ejtag_fdc"; + snprintf(priv->fdc_name, sizeof(priv->fdc_name), "ttyFDC%u", dev->cpu); + snprintf(priv->driver_name, sizeof(priv->driver_name), "%sc", + priv->fdc_name); + driver->name = priv->driver_name; + driver->major = 0; /* Auto-allocate */ + driver->minor_start = 0; + driver->type = TTY_DRIVER_TYPE_SERIAL; + driver->subtype = SERIAL_TYPE_NORMAL; + driver->init_termios = tty_std_termios; + driver->init_termios.c_cflag |= CLOCAL; + driver->driver_state = priv; + + tty_set_operations(driver, &mips_ejtag_fdc_tty_ops); + for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) { + dport = &priv->ports[nport]; + dport->driver = priv; + tty_port_init(&dport->port); + dport->port.ops = &mips_ejtag_fdc_tty_port_ops; + raw_spin_lock_init(&dport->rx_lock); + spin_lock_init(&dport->xmit_lock); + /* The xmit buffer starts empty, i.e. completely written */ + init_completion(&dport->xmit_empty); + complete(&dport->xmit_empty); + } + + /* Set up the console */ + mips_ejtag_fdc_con.regs[dev->cpu] = priv->reg; + if (dev->cpu == 0) + mips_ejtag_fdc_con.tty_drv = driver; + + init_waitqueue_head(&priv->waitqueue); + priv->thread = kthread_create(mips_ejtag_fdc_put, priv, priv->fdc_name); + if (IS_ERR(priv->thread)) { + ret = PTR_ERR(priv->thread); + dev_err(priv->dev, "Couldn't create kthread (%d)\n", ret); + goto err_destroy_ports; + } + /* + * Bind the writer thread to the right CPU so it can't migrate. + * The channels are per-CPU and we want all channel I/O to be on a + * single predictable CPU. + */ + kthread_bind(priv->thread, dev->cpu); + wake_up_process(priv->thread); + + /* Look for an FDC IRQ */ + priv->irq = -1; + if (get_c0_fdc_int) + priv->irq = get_c0_fdc_int(); + + /* Try requesting the IRQ */ + if (priv->irq >= 0) { + /* + * IRQF_SHARED, IRQF_NO_SUSPEND: The FDC IRQ may be shared with + * other local interrupts such as the timer which sets + * IRQF_TIMER (including IRQF_NO_SUSPEND). + * + * IRQF_NO_THREAD: The FDC IRQ isn't individually maskable so it + * cannot be deferred and handled by a thread on RT kernels. For + * this reason any spinlocks used from the ISR are raw. + */ + ret = devm_request_irq(priv->dev, priv->irq, mips_ejtag_fdc_isr, + IRQF_PERCPU | IRQF_SHARED | + IRQF_NO_THREAD | IRQF_NO_SUSPEND, + priv->fdc_name, priv); + if (ret) + priv->irq = -1; + } + if (priv->irq >= 0) { + /* IRQ is usable, enable RX interrupt */ + raw_spin_lock_irq(&priv->lock); + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + cfg &= ~REG_FDCFG_RXINTTHRES; + cfg |= REG_FDCFG_RXINTTHRES_NOTEMPTY; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + raw_spin_unlock_irq(&priv->lock); + } else { + /* If we didn't get an usable IRQ, poll instead */ + setup_timer(&priv->poll_timer, mips_ejtag_fdc_tty_timer, + (unsigned long)priv); + priv->poll_timer.expires = jiffies + FDC_TTY_POLL; + /* + * Always attach the timer to the right CPU. The channels are + * per-CPU so all polling should be from a single CPU. + */ + add_timer_on(&priv->poll_timer, dev->cpu); + + dev_info(priv->dev, "No usable IRQ, polling enabled\n"); + } + + ret = tty_register_driver(driver); + if (ret < 0) { + dev_err(priv->dev, "Couldn't install tty driver (%d)\n", ret); + goto err_stop_irq; + } + + return 0; + +err_stop_irq: + if (priv->irq >= 0) { + raw_spin_lock_irq(&priv->lock); + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + /* Disable interrupts */ + cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); + cfg |= REG_FDCFG_TXINTTHRES_DISABLED; + cfg |= REG_FDCFG_RXINTTHRES_DISABLED; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + raw_spin_unlock_irq(&priv->lock); + } else { + priv->removing = true; + del_timer_sync(&priv->poll_timer); + } + kthread_stop(priv->thread); +err_destroy_ports: + if (dev->cpu == 0) + mips_ejtag_fdc_con.tty_drv = NULL; + for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) { + dport = &priv->ports[nport]; + tty_port_destroy(&dport->port); + } + put_tty_driver(priv->driver); + return ret; +} + +static int mips_ejtag_fdc_tty_remove(struct mips_cdmm_device *dev) +{ + struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev); + struct mips_ejtag_fdc_tty_port *dport; + int nport; + unsigned int cfg; + + if (priv->irq >= 0) { + raw_spin_lock_irq(&priv->lock); + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + /* Disable interrupts */ + cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); + cfg |= REG_FDCFG_TXINTTHRES_DISABLED; + cfg |= REG_FDCFG_RXINTTHRES_DISABLED; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + raw_spin_unlock_irq(&priv->lock); + } else { + priv->removing = true; + del_timer_sync(&priv->poll_timer); + } + kthread_stop(priv->thread); + if (dev->cpu == 0) + mips_ejtag_fdc_con.tty_drv = NULL; + tty_unregister_driver(priv->driver); + for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) { + dport = &priv->ports[nport]; + tty_port_destroy(&dport->port); + } + put_tty_driver(priv->driver); + return 0; +} + +static int mips_ejtag_fdc_tty_cpu_down(struct mips_cdmm_device *dev) +{ + struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev); + unsigned int cfg; + + if (priv->irq >= 0) { + raw_spin_lock_irq(&priv->lock); + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + /* Disable interrupts */ + cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); + cfg |= REG_FDCFG_TXINTTHRES_DISABLED; + cfg |= REG_FDCFG_RXINTTHRES_DISABLED; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + raw_spin_unlock_irq(&priv->lock); + } else { + priv->removing = true; + del_timer_sync(&priv->poll_timer); + } + kthread_stop(priv->thread); + + return 0; +} + +static int mips_ejtag_fdc_tty_cpu_up(struct mips_cdmm_device *dev) +{ + struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev); + unsigned int cfg; + int ret = 0; + + if (priv->irq >= 0) { + /* + * IRQ is usable, enable RX interrupt + * This must be before kthread is restarted, as kthread may + * enable TX interrupt. + */ + raw_spin_lock_irq(&priv->lock); + cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); + cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); + cfg |= REG_FDCFG_TXINTTHRES_DISABLED; + cfg |= REG_FDCFG_RXINTTHRES_NOTEMPTY; + mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); + raw_spin_unlock_irq(&priv->lock); + } else { + /* Restart poll timer */ + priv->removing = false; + add_timer_on(&priv->poll_timer, dev->cpu); + } + + /* Restart the kthread */ + priv->thread = kthread_create(mips_ejtag_fdc_put, priv, priv->fdc_name); + if (IS_ERR(priv->thread)) { + ret = PTR_ERR(priv->thread); + dev_err(priv->dev, "Couldn't re-create kthread (%d)\n", ret); + goto out; + } + /* Bind it back to the right CPU and set it off */ + kthread_bind(priv->thread, dev->cpu); + wake_up_process(priv->thread); +out: + return ret; +} + +static struct mips_cdmm_device_id mips_ejtag_fdc_tty_ids[] = { + { .type = 0xfd }, + { } +}; + +static struct mips_cdmm_driver mips_ejtag_fdc_tty_driver = { + .drv = { + .name = "mips_ejtag_fdc", + }, + .probe = mips_ejtag_fdc_tty_probe, + .remove = mips_ejtag_fdc_tty_remove, + .cpu_down = mips_ejtag_fdc_tty_cpu_down, + .cpu_up = mips_ejtag_fdc_tty_cpu_up, + .id_table = mips_ejtag_fdc_tty_ids, +}; +module_mips_cdmm_driver(mips_ejtag_fdc_tty_driver); + +static int __init mips_ejtag_fdc_init_console(void) +{ + return mips_ejtag_fdc_console_init(&mips_ejtag_fdc_con); +} +console_initcall(mips_ejtag_fdc_init_console); |