[PATCH][2/6] A different KGDB stub

From: Tom Rini (trini_at_kernel.crashing.org)
Date: 02/12/04

  • Next message: Tom Rini: "[PATCH][1/6] A different KGDB stub"
    Date:	Wed, 11 Feb 2004 17:03:18 -0700
    To: Andrew Morton <akpm@osdl.org>, Kernel Mailing List <linux-kernel@vger.kernel.org>
    
    

    The following is the 8250 driver for this KGDB stub.

     drivers/serial/8250.c | 39 +++
     drivers/serial/Kconfig | 7
     drivers/serial/Makefile | 2
     drivers/serial/kgdb_8250.c | 506 +++++++++++++++++++++++++++++++++++++++++++
     drivers/serial/serial_core.c | 4
     5 files changed, 558 insertions(+)
    --- a/drivers/serial/8250.c Wed Feb 11 15:41:38 2004
    +++ b/drivers/serial/8250.c Wed Feb 11 15:41:38 2004
    @@ -1198,12 +1198,17 @@
             spin_unlock_irqrestore(&up->port.lock, flags);
     }
     
    +static int released_irq = -1;
    +
     static int serial8250_startup(struct uart_port *port)
     {
             struct uart_8250_port *up = (struct uart_8250_port *)port;
             unsigned long flags;
             int retval;
     
    + if (up->port.irq == released_irq)
    + return -EBUSY;
    +
             up->capabilities = uart_config[up->port.type].flags;
     
             if (up->port.type == PORT_16C950) {
    @@ -1869,6 +1874,10 @@
             for (i = 0; i < UART_NR; i++) {
                     struct uart_8250_port *up = &serial8250_ports[i];
     
    + if (up->port.irq == released_irq) {
    + continue;
    + }
    +
                     up->port.line = i;
                     up->port.ops = &serial8250_pops;
                     init_timer(&up->timer);
    @@ -2136,6 +2145,36 @@
     void serial8250_resume_port(int line)
     {
             uart_resume_port(&serial8250_reg, &serial8250_ports[line].port);
    +}
    +
    +/*
    + * Find all the ports using the given irq and shut them down.
    + * Result should be that the irq will be released.
    + * At most one irq can be released this way.
    + * Once an irq is released, any attempts to initialize a port with that irq
    + * will fail with EBUSY.
    + */
    +
    +int serial8250_release_irq(int irq)
    +{
    + struct uart_8250_port *up;
    + int ttyS;
    +
    + if (released_irq != -1) {
    + return 1;
    + }
    + released_irq = irq;
    + for (ttyS = 0; ttyS < UART_NR; ttyS++){
    + up = &serial8250_ports[ttyS];
    + if (up->port.irq == irq && (irq_lists + irq)->head) {
    +#ifdef CONFIG_DEBUG_SPINLOCK /* ugly business... */
    + if(up->port.lock.magic != SPINLOCK_MAGIC)
    + spin_lock_init(&up->port.lock);
    +#endif
    + serial8250_shutdown(&up->port);
    + }
    + }
    + return 0;
     }
     
     static int __init serial8250_init(void)
    --- a/drivers/serial/Kconfig Wed Feb 11 15:41:38 2004
    +++ b/drivers/serial/Kconfig Wed Feb 11 15:41:38 2004
    @@ -6,6 +6,13 @@
     
     menu "Serial drivers"
     
    +config KGDB_8250
    + bool "KGDB: On generic serial port (8250)"
    + depends on KGDB
    + help
    + Uses generic serial port (8250) for kgdb. This is independent of the
    + option 9250/16550 and compatible serial port.
    +
     #
     # The new 8250/16550 serial drivers
     config SERIAL_8250
    --- a/drivers/serial/Makefile Wed Feb 11 15:41:38 2004
    +++ b/drivers/serial/Makefile Wed Feb 11 15:41:38 2004
    @@ -35,3 +35,5 @@
     obj-$(CONFIG_SERIAL_PMACZILOG) += pmac_zilog.o
     obj-$(CONFIG_SERIAL_AU1X00) += au1x00_uart.o
     obj-$(CONFIG_SERIAL_DZ) += dz.o
    +
    +obj-$(CONFIG_KGDB_8250) += kgdb_8250.o
    --- /dev/null Wed Dec 31 16:00:00 1969
    +++ b/drivers/serial/kgdb_8250.c Wed Feb 11 15:41:38 2004
    @@ -0,0 +1,506 @@
    +/*
    + * 8250 interface for kgdb.
    + *
    + * This is a merging of many different drivers, and all of the people have
    + * had an impact in some form or another:
    + *
    + * Amit Kale <amitkale@emsyssoft.com>
    + * David Grothe <dave@gcom.com>
    + * Scott Foehner <sfoehner@engr.sgi.com>
    + * George Anzinger <george@mvista.com>
    + * Robert Walsh <rjwalsh@durables.org>
    + * wangdi <wangdi@clusterfs.com>
    + * San Mehat
    + * Tom Rini <trini@mvista.com>
    + */
    +
    +#include <linux/config.h>
    +#include <linux/kernel.h>
    +#include <linux/init.h>
    +#include <linux/spinlock.h>
    +#include <linux/kgdb.h>
    +#include <linux/interrupt.h>
    +#include <linux/tty.h>
    +#include <linux/serial.h>
    +#include <linux/serial_core.h>
    +#include <linux/serial_reg.h>
    +#include <linux/serialP.h>
    +
    +#include <asm/io.h>
    +#include <asm/serial.h> /* For BASE_BAUD and SERIAL_PORT_DFNS */
    +
    +#define GDB_BUF_SIZE 512 /* power of 2, please */
    +
    +#if defined(CONFIG_KGDB_9600BAUD)
    +#define SERIAL_BAUD 9600
    +#elif defined(CONFIG_KGDB_19200BAUD)
    +#define SERIAL_BAUD 19200
    +#elif defined(CONFIG_KGDB_38400BAUD)
    +#define SERIAL_BAUD 38400
    +#elif defined(CONFIG_KGDB_57600BAUD)
    +#define SERIAL_BAUD 57600
    +#elif defined(CONFIG_KGDB_115200BAUD)
    +#define SERIAL_BAUD 115200
    +#else
    +#define SERIAL_BAUD 115200 /* Start with this if not given */
    +#endif
    +
    +#if defined(CONFIG_KGDB_TTYS0)
    +#define KGDB_PORT 0
    +#elif defined(CONFIG_KGDB_TTYS1)
    +#define KGDB_PORT 1
    +#elif defined(CONFIG_KGDB_TTYS2)
    +#define KGDB_PORT 2
    +#elif defined(CONFIG_KGDB_TTYS3)
    +#define KGDB_PORT 3
    +#else
    +#define KGDB_PORT 0 /* Start with this if not given */
    +#endif
    +
    +int kgdb8250_baud = SERIAL_BAUD;
    +int kgdb8250_ttyS = KGDB_PORT;
    +
    +static char kgdb8250_buf[GDB_BUF_SIZE];
    +static int kgdb8250_buf_in_inx;
    +static atomic_t kgdb8250_buf_in_cnt;
    +static int kgdb8250_buf_out_inx;
    +
    +/* Determine serial information. */
    +static struct serial_state state = {
    + .magic = SSTATE_MAGIC,
    + .baud_base = BASE_BAUD,
    + .custom_divisor = BASE_BAUD / SERIAL_BAUD,
    +};
    +
    +static struct async_struct gdb_async_info = {
    + .magic = SERIAL_MAGIC,
    + .state = &state,
    + .tty = (struct tty_struct *) &state,
    +};
    +
    +/* Space between registers. */
    +static int reg_shift;
    +
    +/* Not all arches define this. */
    +#ifndef SERIAL_PORT_DFNS
    +#define SERIAL_PORT_DFNS
    +#endif
    +static struct serial_state rs_table[] = {
    + SERIAL_PORT_DFNS /* defined in <asm/serial.h> */
    +};
    +
    +/* Do we need to look in the rs_table? */
    +static int serial_from_rs_table = 0;
    +
    +static void (*serial_outb) (unsigned char val, unsigned long addr);
    +static unsigned long (*serial_inb) (unsigned long addr);
    +
    +int serial8250_release_irq(int irq);
    +
    +static int kgdb8250_init(void);
    +static unsigned long kgdb8250_port;
    +
    +static int initialized = -1;
    +
    +static unsigned long
    +direct_inb(unsigned long addr)
    +{
    + return readb(addr);
    +}
    +
    +static void
    +direct_outb(unsigned char val, unsigned long addr)
    +{
    + writeb(val, addr);
    +}
    +
    +static unsigned long
    +io_inb(unsigned long port)
    +{
    + return inb(port);
    +}
    +
    +static void
    +io_outb(unsigned char val, unsigned long port)
    +{
    + outb(val, port);
    +}
    +
    +/*
    + * Wait until the interface can accept a char, then write it.
    + */
    +static void
    +kgdb8250_write_char(int chr)
    +{
    + while (!(serial_inb(kgdb8250_port + (UART_LSR << reg_shift)) &
    + UART_LSR_THRE))
    + ;
    +
    + serial_outb(chr, kgdb8250_port + (UART_TX << reg_shift));
    +}
    +
    +/*
    + * Get a byte from the hardware data buffer and return it
    + */
    +static int
    +read_data_bfr(void)
    +{
    + char it = serial_inb(kgdb8250_port + (UART_LSR << reg_shift));
    +
    + if (it & UART_LSR_DR)
    + return serial_inb(kgdb8250_port + (UART_RX << reg_shift));
    +
    + /*
    + * If we have a framing error assume somebody messed with
    + * our uart. Reprogram it and send '-' both ways...
    + */
    + if (it & 0xc) {
    + kgdb8250_init();
    + kgdb8250_write_char('-');
    + return '-';
    + }
    +
    + return -1;
    +}
    +
    +/*
    + * Get a char if available, return -1 if nothing available.
    + * Empty the receive buffer first, then look at the interface hardware.
    + *
    + * Locking here is a bit of a problem. We MUST not lock out communication
    + * if we are trying to talk to gdb about a kgdb entry. ON the other hand
    + * we can loose chars in the console pass thru if we don't lock. It is also
    + * possible that we could hold the lock or be waiting for it when kgdb
    + * NEEDS to talk. Since kgdb locks down the world, it does not need locks.
    + * We do, of course have possible issues with interrupting a uart operation,
    + * but we will just depend on the uart status to help keep that straight.
    + */
    +
    +static spinlock_t uart_interrupt_lock = SPIN_LOCK_UNLOCKED;
    +#ifdef CONFIG_SMP
    +extern spinlock_t kgdb_spinlock;
    +#endif
    +
    +static int
    +kgdb8250_read_char(void)
    +{
    + int retchr;
    + unsigned long flags;
    + local_irq_save(flags);
    +#ifdef CONFIG_SMP
    + if (!spin_is_locked(&kgdb_spinlock)) {
    + spin_lock(&uart_interrupt_lock);
    + }
    +#endif
    + /* intr routine has q'd chars */
    + if (atomic_read(&kgdb8250_buf_in_cnt) != 0) {
    + retchr = kgdb8250_buf[kgdb8250_buf_out_inx++];
    + kgdb8250_buf_out_inx &= (GDB_BUF_SIZE - 1);
    + atomic_dec(&kgdb8250_buf_in_cnt);
    + goto out;
    + }
    +
    + do {
    + retchr = read_data_bfr();
    + } while (retchr < 0);
    +
    +out:
    +#ifdef CONFIG_SMP
    + if (!spin_is_locked(&kgdb_spinlock)) {
    + spin_unlock(&uart_interrupt_lock);
    + }
    +#endif
    + local_irq_restore(flags);
    +
    + return retchr;
    +}
    +
    +/*
    + * This is the receiver interrupt routine for the GDB stub.
    + * It will receive a limited number of characters of input
    + * from the gdb host machine and save them up in a buffer.
    + *
    + * When kgdb8250_read_char() is called it
    + * draws characters out of the buffer until it is empty and
    + * then reads directly from the serial port.
    + *
    + * We do not attempt to write chars from the interrupt routine
    + * since the stubs do all of that via kgdb8250_write_char() which
    + * writes one byte after waiting for the interface to become
    + * ready.
    + *
    + * The debug stubs like to run with interrupts disabled since,
    + * after all, they run as a consequence of a breakpoint in
    + * the kernel.
    + *
    + * Perhaps someone who knows more about the tty driver than I
    + * care to learn can make this work for any low level serial
    + * driver.
    + */
    +static irqreturn_t
    +kgdb8250_interrupt(int irq, void *dev_id, struct pt_regs *regs)
    +{
    + int chr, iir;
    + unsigned long flags;
    +
    + if (irq != gdb_async_info.line)
    + return IRQ_NONE;
    +
    + /* If we get an interrupt, then KGDB is trying to connect. */
    + if (!kgdb_connected) {
    + kgdb_schedule_breakpoint();
    + return IRQ_HANDLED;
    + }
    +
    + local_irq_save(flags);
    + spin_lock(&uart_interrupt_lock);
    +
    + do {
    + chr = read_data_bfr();
    + iir = serial_inb(kgdb8250_port + (UART_IIR << reg_shift));
    + if (chr < 0)
    + continue;
    +
    + /* Check whether gdb sent interrupt */
    + if (chr == 3) {
    + breakpoint();
    + continue;
    + }
    +
    + if (atomic_read(&kgdb8250_buf_in_cnt) >= GDB_BUF_SIZE) {
    + /* buffer overflow, clear it */
    + kgdb8250_buf_in_inx = 0;
    + atomic_set(&kgdb8250_buf_in_cnt, 0);
    + kgdb8250_buf_out_inx = 0;
    + break;
    + }
    +
    + kgdb8250_buf[kgdb8250_buf_in_inx++] = chr;
    + kgdb8250_buf_in_inx &= (GDB_BUF_SIZE - 1);
    + atomic_inc(&kgdb8250_buf_in_cnt);
    + } while (iir & UART_IIR_RDI);
    +
    + spin_unlock(&uart_interrupt_lock);
    + local_irq_restore(flags);
    +
    + return IRQ_HANDLED;
    +}
    +
    +/*
    + * Returns:
    + * 0 on success, 1 on failure.
    + */
    +static int
    +kgdb8250_init(void)
    +{
    + unsigned cval;
    + int bits = 8;
    + int parity = 'n';
    + int cflag = CREAD | HUPCL | CLOCAL;
    +
    + /*
    + * Now construct a cflag setting.
    + */
    + switch (kgdb8250_baud) {
    + case 1200:
    + cflag |= B1200;
    + break;
    + case 2400:
    + cflag |= B2400;
    + break;
    + case 4800:
    + cflag |= B4800;
    + break;
    + case 19200:
    + cflag |= B19200;
    + break;
    + case 38400:
    + cflag |= B38400;
    + break;
    + case 57600:
    + cflag |= B57600;
    + break;
    + case 115200:
    + cflag |= B115200;
    + break;
    + default:
    + kgdb8250_baud = 9600;
    + /* Fall through */
    + case 9600:
    + cflag |= B9600;
    + break;
    + }
    + switch (bits) {
    + case 7:
    + cflag |= CS7;
    + break;
    + default:
    + case 8:
    + cflag |= CS8;
    + break;
    + }
    + switch (parity) {
    + case 'o':
    + case 'O':
    + cflag |= PARODD;
    + break;
    + case 'e':
    + case 'E':
    + cflag |= PARENB;
    + break;
    + }
    +
    + /*
    + * Divisor, bytesize and parity
    + *
    + */
    +
    + cval = cflag & (CSIZE | CSTOPB);
    + cval >>= 4;
    + if (cflag & PARENB)
    + cval |= UART_LCR_PARITY;
    + if (!(cflag & PARODD))
    + cval |= UART_LCR_EPAR;
    +
    + /*
    + * Disable UART interrupts, set DTR and RTS high
    + * and set speed.
    + */
    + cval = 0x3;
    + /* set DLAB */
    + serial_outb(cval | UART_LCR_DLAB, kgdb8250_port +
    + (UART_LCR << reg_shift));
    + /* LS */
    + serial_outb(gdb_async_info.state->custom_divisor & 0xff,
    + kgdb8250_port + (UART_DLL << reg_shift));
    + /* MS */
    + serial_outb(gdb_async_info.state->custom_divisor >> 8,
    + kgdb8250_port + (UART_DLM << reg_shift));
    + /* reset DLAB */
    + serial_outb(cval, kgdb8250_port + (UART_LCR << reg_shift));
    + /* turn on interrupts */
    + serial_outb(UART_IER_RDI, kgdb8250_port + (UART_IER << reg_shift));
    + serial_outb(UART_MCR_OUT2 | UART_MCR_DTR | UART_MCR_RTS,
    + kgdb8250_port + (UART_MCR << reg_shift));
    +
    + /*
    + * If we read 0xff from the LSR, there is no UART here.
    + */
    + if (serial_inb(kgdb8250_port + (UART_LSR << reg_shift)) == 0xff)
    + return -1;
    + return 0;
    +}
    +
    +int
    +kgdb8250_hook(void)
    +{
    + int retval;
    +
    + /* Setup any serial port information we may need to */
    +#ifdef CONFIG_KGDB_SIMPLE_SERIAL
    + /* We must look in the rs_table[]. */
    + serial_from_rs_table = 1;
    +#endif
    + /* If the user has overriden our definitions, or if we've only
    + * been told the ttyS to use, look at rs_table. */
    + if (serial_from_rs_table) {
    + /* The user has selected one of ttyS[0-3], which we pull
    + * from rs_table[]. If this doesn't exist, user error. */
    + gdb_async_info.port = gdb_async_info.state->port =
    + rs_table[KGDB_PORT].port;
    + gdb_async_info.line = gdb_async_info.state->irq =
    + rs_table[KGDB_PORT].irq;
    + gdb_async_info.state->io_type = rs_table[KGDB_PORT].io_type;
    + reg_shift = rs_table[KGDB_PORT].iomem_reg_shift;
    + }
    +
    + switch (gdb_async_info.state->io_type) {
    + case SERIAL_IO_MEM:
    + kgdb8250_port = (unsigned long)
    + rs_table[kgdb8250_ttyS].iomem_base;
    + serial_outb = direct_outb;
    + serial_inb = direct_inb;
    + break;
    + case SERIAL_IO_PORT:
    + default:
    + kgdb8250_port = rs_table[kgdb8250_ttyS].port;
    + serial_outb = io_outb;
    + serial_inb = io_inb;
    + }
    +
    +#ifndef CONFIG_KGDB_SIMPLE_SERIAL
    + /* The user has provided the IRQ and I/O location. */
    + kgdb8250_port = gdb_async_info.port = gdb_async_info.state->port =
    + CONFIG_KGDB_PORT;
    + gdb_async_info.line = gdb_async_info.state->irq = CONFIG_KGDB_IRQ;
    +#endif
    +
    +#ifdef CONFIG_SERIAL_8250
    + if (serial8250_release_irq(gdb_async_info.line))
    + return -1;
    +#endif
    +
    + if (kgdb8250_init() == -1)
    + return -1;
    +
    + retval = request_irq(gdb_async_info.line, kgdb8250_interrupt,
    + SA_INTERRUPT, "GDB-stub", NULL);
    + if (retval == 0)
    + initialized = 1;
    + else
    + initialized = 0;
    +
    + return 0;
    +}
    +
    +void
    +kgdb8250_add_port(int i, struct uart_port *serial_req)
    +{
    + rs_table[i].io_type = serial_req->iotype;
    + rs_table[i].port = serial_req->line;
    + rs_table[i].irq = serial_req->irq;
    + rs_table[i].iomem_base = serial_req->membase;
    + rs_table[i].iomem_reg_shift = serial_req->regshift;
    +
    + /* We will want to look in the rs_table now. */
    + serial_from_rs_table = 1;
    +}
    +
    +struct kgdb_serial kgdb8250_serial_driver = {
    + .read_char = kgdb8250_read_char,
    + .write_char = kgdb8250_write_char,
    + .hook = kgdb8250_hook
    +};
    +
    +/*
    + * Syntax for this cmdline option is
    + * kgdb8250=ttyno,baudrate
    + */
    +
    +static int __init
    +kgdb8250_opt(char *str)
    +{
    + if (*str < '0' || *str > '3')
    + goto errout;
    + kgdb8250_ttyS = *str - '0';
    + str++;
    + if (*str != ',')
    + goto errout;
    + str++;
    + kgdb8250_baud = simple_strtoul(str, NULL, 10);
    + if (kgdb8250_baud != 9600 && kgdb8250_baud != 19200 &&
    + kgdb8250_baud != 38400 && kgdb8250_baud != 57600 &&
    + kgdb8250_baud != 115200)
    + goto errout;
    +
    + /* Make the baud rate change happen. */
    + gdb_async_info.state->custom_divisor = BASE_BAUD / kgdb8250_baud;
    +
    + return 1;
    +
    + errout:
    + printk("Invalid syntax for option kgdb8250=\n");
    + return 0;
    +}
    +
    +__setup("kgdb8250=", kgdb8250_opt);
    --- a/drivers/serial/serial_core.c Wed Feb 11 15:41:38 2004
    +++ b/drivers/serial/serial_core.c Wed Feb 11 15:41:38 2004
    @@ -1214,6 +1214,10 @@
             struct uart_state *state = tty->driver_data;
             struct uart_port *port;
     
    + if (!state)
    + return;
    + port = state->port;
    +
             BUG_ON(!kernel_locked());
     
             if (!state || !state->port)

    -- 
    Tom Rini
    http://gate.crashing.org/~trini/
    -
    To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
    the body of a message to majordomo@vger.kernel.org
    More majordomo info at  http://vger.kernel.org/majordomo-info.html
    Please read the FAQ at  http://www.tux.org/lkml/
    

  • Next message: Tom Rini: "[PATCH][1/6] A different KGDB stub"

    Relevant Pages