Merge branch 'upstream' of git://git.infradead.org/users/pcmoore/audit
[firefly-linux-kernel-4.4.55.git] / drivers / tty / mips_ejtag_fdc.c
index 8d9bf6f90110a41f8a9019c7828c8dd3f3fe29c1..04d9e23d1ee16a508e0e0b1331407bcf6b20a94b 100644 (file)
 #include <linux/init.h>
 #include <linux/interrupt.h>
 #include <linux/kernel.h>
+#include <linux/kgdb.h>
 #include <linux/kthread.h>
 #include <linux/sched.h>
 #include <linux/serial.h>
+#include <linux/serial_core.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 #include <linux/string.h>
@@ -136,6 +138,8 @@ struct mips_ejtag_fdc_tty_port {
  * @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.
+ * @sysrq_pressed:     Whether the magic sysrq key combination has been
+ *                     detected. See mips_ejtag_fdc_handle().
  */
 struct mips_ejtag_fdc_tty {
        struct device                   *dev;
@@ -159,6 +163,10 @@ struct mips_ejtag_fdc_tty {
        int                              irq;
        bool                             removing;
        struct timer_list                poll_timer;
+
+#ifdef CONFIG_MAGIC_SYSRQ
+       bool                             sysrq_pressed;
+#endif
 };
 
 /* Hardware access */
@@ -568,21 +576,47 @@ static void mips_ejtag_fdc_handle(struct mips_ejtag_fdc_tty *priv)
                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)
+               for (i = 0; i < len; ++i) {
+#ifdef CONFIG_MAGIC_SYSRQ
+#ifdef CONFIG_MIPS_EJTAG_FDC_KGDB
+                       /* Support just Ctrl+C with KGDB channel */
+                       if (channel == CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN) {
+                               if (buf[i] == '\x03') { /* ^C */
+                                       handle_sysrq('g');
+                                       continue;
+                               }
+                       }
+#endif
+                       /* Support Ctrl+O for console channel */
+                       if (channel == mips_ejtag_fdc_con.cons.index) {
+                               if (buf[i] == '\x0f') { /* ^O */
+                                       priv->sysrq_pressed =
+                                               !priv->sysrq_pressed;
+                                       if (priv->sysrq_pressed)
+                                               continue;
+                               } else if (priv->sysrq_pressed) {
+                                       handle_sysrq(buf[i]);
+                                       priv->sysrq_pressed = false;
+                                       continue;
+                               }
+                       }
+#endif /* CONFIG_MAGIC_SYSRQ */
+
+                       /* Check the port isn't being shut down */
+                       if (!dport->rx_buf)
+                               continue;
+
                        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);
        }
 
@@ -1144,3 +1178,126 @@ int __init setup_early_fdc_console(void)
        return mips_ejtag_fdc_console_init(&mips_ejtag_fdc_earlycon);
 }
 #endif
+
+#ifdef CONFIG_MIPS_EJTAG_FDC_KGDB
+
+/* read buffer to allow decompaction */
+static unsigned int kgdbfdc_rbuflen;
+static unsigned int kgdbfdc_rpos;
+static char kgdbfdc_rbuf[4];
+
+/* write buffer to allow compaction */
+static unsigned int kgdbfdc_wbuflen;
+static char kgdbfdc_wbuf[4];
+
+static void __iomem *kgdbfdc_setup(void)
+{
+       void __iomem *regs;
+       unsigned int cpu;
+
+       /* Find address, piggy backing off console percpu regs */
+       cpu = smp_processor_id();
+       regs = mips_ejtag_fdc_con.regs[cpu];
+       /* First console output on this CPU? */
+       if (!regs) {
+               regs = mips_cdmm_early_probe(0xfd);
+               mips_ejtag_fdc_con.regs[cpu] = regs;
+       }
+       /* Already tried and failed to find FDC on this CPU? */
+       if (IS_ERR(regs))
+               return regs;
+
+       return regs;
+}
+
+/* read a character from the read buffer, filling from FDC RX FIFO */
+static int kgdbfdc_read_char(void)
+{
+       unsigned int stat, channel, data;
+       void __iomem *regs;
+
+       /* No more data, try and read another FDC word from RX FIFO */
+       if (kgdbfdc_rpos >= kgdbfdc_rbuflen) {
+               kgdbfdc_rpos = 0;
+               kgdbfdc_rbuflen = 0;
+
+               regs = kgdbfdc_setup();
+               if (IS_ERR(regs))
+                       return NO_POLL_CHAR;
+
+               /* Read next word from KGDB channel */
+               do {
+                       stat = ioread32(regs + REG_FDSTAT);
+
+                       /* No data waiting? */
+                       if (stat & REG_FDSTAT_RXE)
+                               return NO_POLL_CHAR;
+
+                       /* Read next word */
+                       channel = (stat & REG_FDSTAT_RXCHAN) >>
+                                       REG_FDSTAT_RXCHAN_SHIFT;
+                       data = ioread32(regs + REG_FDRX);
+               } while (channel != CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN);
+
+               /* Decode into rbuf */
+               kgdbfdc_rbuflen = mips_ejtag_fdc_decode(data, kgdbfdc_rbuf);
+       }
+       pr_devel("kgdbfdc r %c\n", kgdbfdc_rbuf[kgdbfdc_rpos]);
+       return kgdbfdc_rbuf[kgdbfdc_rpos++];
+}
+
+/* push an FDC word from write buffer to TX FIFO */
+static void kgdbfdc_push_one(void)
+{
+       const char *bufs[1] = { kgdbfdc_wbuf };
+       struct fdc_word word;
+       void __iomem *regs;
+       unsigned int i;
+
+       /* Construct a word from any data in buffer */
+       word = mips_ejtag_fdc_encode(bufs, &kgdbfdc_wbuflen, 1);
+       /* Relocate any remaining data to beginnning of buffer */
+       kgdbfdc_wbuflen -= word.bytes;
+       for (i = 0; i < kgdbfdc_wbuflen; ++i)
+               kgdbfdc_wbuf[i] = kgdbfdc_wbuf[i + word.bytes];
+
+       regs = kgdbfdc_setup();
+       if (IS_ERR(regs))
+               return;
+
+       /* Busy wait until there's space in fifo */
+       while (ioread32(regs + REG_FDSTAT) & REG_FDSTAT_TXF)
+               ;
+       iowrite32(word.word, regs + REG_FDTX(CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN));
+}
+
+/* flush the whole write buffer to the TX FIFO */
+static void kgdbfdc_flush(void)
+{
+       while (kgdbfdc_wbuflen)
+               kgdbfdc_push_one();
+}
+
+/* write a character into the write buffer, writing out if full */
+static void kgdbfdc_write_char(u8 chr)
+{
+       pr_devel("kgdbfdc w %c\n", chr);
+       kgdbfdc_wbuf[kgdbfdc_wbuflen++] = chr;
+       if (kgdbfdc_wbuflen >= sizeof(kgdbfdc_wbuf))
+               kgdbfdc_push_one();
+}
+
+static struct kgdb_io kgdbfdc_io_ops = {
+       .name           = "kgdbfdc",
+       .read_char      = kgdbfdc_read_char,
+       .write_char     = kgdbfdc_write_char,
+       .flush          = kgdbfdc_flush,
+};
+
+static int __init kgdbfdc_init(void)
+{
+       kgdb_register_io_module(&kgdbfdc_io_ops);
+       return 0;
+}
+early_initcall(kgdbfdc_init);
+#endif