[4/6] consoles: polling support, kgdboc

From: Ingo Molnar
Date: Sun Feb 10 2008 - 02:14:58 EST


From: Jan Kiszka <jan.kiszka@xxxxxxxxxxx>

polled console handling support, to access a console in an irq-less
way while in debug or irq context.

absolutely zero impact as long as CONFIG_CONSOLE_POLL is disabled.
(which is the default)

kgdb over consoles support from:

Jason Wessel <jason.wessel@xxxxxxxxxxxxx>

[ mingo@xxxxxxx: redesign, splitups, cleanups. ]

Signed-off-by: Ingo Molnar <mingo@xxxxxxx>
Reviewed-by: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
---
drivers/char/tty_io.c | 47 ++++++++++++
drivers/serial/8250.c | 62 ++++++++++++++++
drivers/serial/Kconfig | 3
drivers/serial/Makefile | 1
drivers/serial/kgdboc.c | 164 +++++++++++++++++++++++++++++++++++++++++++
drivers/serial/serial_core.c | 67 +++++++++++++++++
include/linux/serial_core.h | 4 +
include/linux/tty_driver.h | 12 +++
8 files changed, 359 insertions(+), 1 deletion(-)

Index: linux-kgdb.q/drivers/char/tty_io.c
===================================================================
--- linux-kgdb.q.orig/drivers/char/tty_io.c
+++ linux-kgdb.q/drivers/char/tty_io.c
@@ -1155,6 +1155,48 @@ static struct tty_driver *get_tty_driver
return NULL;
}

+#ifdef CONFIG_CONSOLE_POLL
+
+/**
+ * tty_find_polling_driver - find device of a polled tty
+ * @name: name string to match
+ * @line: pointer to resulting tty line nr
+ *
+ * This routine returns a tty driver structure, given a name
+ * and the condition that the tty driver is capable of polled
+ * operation.
+ */
+struct tty_driver *tty_find_polling_driver(char *name, int *line)
+{
+ struct tty_driver *p, *res = NULL;
+ int tty_line = 0;
+ char *str;
+
+ mutex_lock(&tty_mutex);
+ /* Search through the tty devices to look for a match */
+ list_for_each_entry(p, &tty_drivers, tty_drivers) {
+ str = name + strlen(p->name);
+ tty_line = simple_strtoul(str, &str, 10);
+ if (*str == ',')
+ str++;
+ if (*str == '\0')
+ str = 0;
+
+ if (tty_line >= 0 && tty_line <= p->num && p->poll_init &&
+ !p->poll_init(p, tty_line, str)) {
+
+ res = p;
+ *line = tty_line;
+ break;
+ }
+ }
+ mutex_unlock(&tty_mutex);
+
+ return res;
+}
+EXPORT_SYMBOL_GPL(tty_find_polling_driver);
+#endif
+
/**
* tty_check_change - check for POSIX terminal changes
* @tty: tty to check
@@ -3850,6 +3892,11 @@ void tty_set_operations(struct tty_drive
driver->write_proc = op->write_proc;
driver->tiocmget = op->tiocmget;
driver->tiocmset = op->tiocmset;
+#ifdef CONFIG_CONSOLE_POLL
+ driver->poll_init = op->poll_init;
+ driver->poll_get_char = op->poll_get_char;
+ driver->poll_put_char = op->poll_put_char;
+#endif
}


Index: linux-kgdb.q/drivers/serial/8250.c
===================================================================
--- linux-kgdb.q.orig/drivers/serial/8250.c
+++ linux-kgdb.q/drivers/serial/8250.c
@@ -1740,6 +1740,64 @@ static inline void wait_for_xmitr(struct
}
}

+#ifdef CONFIG_CONSOLE_POLL
+/*
+ * Console polling routines for writing and reading from the uart while
+ * in an interrupt or debug context.
+ */
+
+static int serial8250_get_poll_char(struct uart_port *port)
+{
+ struct uart_8250_port *up = (struct uart_8250_port *)port;
+ unsigned char lsr = serial_inp(up, UART_LSR);
+
+ while (!(lsr & UART_LSR_DR))
+ lsr = serial_inp(up, UART_LSR);
+
+ return serial_inp(up, UART_RX);
+}
+
+
+static void serial8250_put_poll_char(struct uart_port *port,
+ unsigned char c)
+{
+ unsigned int ier;
+ struct uart_8250_port *up = (struct uart_8250_port *)port;
+
+ /*
+ * First save the IER then disable the interrupts
+ */
+ ier = serial_in(up, UART_IER);
+#ifdef UART_CAP_UUE
+ if (up->capabilities & UART_CAP_UUE)
+#else
+ if (up->port.type == PORT_XSCALE)
+#endif
+ serial_out(up, UART_IER, UART_IER_UUE);
+ else
+ serial_out(up, UART_IER, 0);
+
+ wait_for_xmitr(up, BOTH_EMPTY);
+ /*
+ * Send the character out.
+ * If a LF, also do CR...
+ */
+ serial_out(up, UART_TX, c);
+ if (c == 10) {
+ wait_for_xmitr(up, BOTH_EMPTY);
+ serial_out(up, UART_TX, 13);
+ }
+
+ /*
+ * Finally, wait for transmitter to become empty
+ * and restore the IER
+ */
+ wait_for_xmitr(up, BOTH_EMPTY);
+ serial_out(up, UART_IER, ier);
+}
+
+#endif /* CONFIG_CONSOLE_POLL */
+
static int serial8250_startup(struct uart_port *port)
{
struct uart_8250_port *up = (struct uart_8250_port *)port;
@@ -2386,6 +2444,10 @@ static struct uart_ops serial8250_pops =
.request_port = serial8250_request_port,
.config_port = serial8250_config_port,
.verify_port = serial8250_verify_port,
+#ifdef CONFIG_CONSOLE_POLL
+ .poll_get_char = serial8250_get_poll_char,
+ .poll_put_char = serial8250_put_poll_char,
+#endif
};

static struct uart_8250_port serial8250_ports[UART_NR];
Index: linux-kgdb.q/drivers/serial/Kconfig
===================================================================
--- linux-kgdb.q.orig/drivers/serial/Kconfig
+++ linux-kgdb.q/drivers/serial/Kconfig
@@ -961,6 +961,9 @@ config SERIAL_CORE
config SERIAL_CORE_CONSOLE
bool

+config CONSOLE_POLL
+ bool
+
config SERIAL_68328
bool "68328 serial support"
depends on M68328 || M68EZ328 || M68VZ328
Index: linux-kgdb.q/drivers/serial/Makefile
===================================================================
--- linux-kgdb.q.orig/drivers/serial/Makefile
+++ linux-kgdb.q/drivers/serial/Makefile
@@ -66,4 +66,5 @@ obj-$(CONFIG_SERIAL_UARTLITE) += uartlit
obj-$(CONFIG_SERIAL_NETX) += netx-serial.o
obj-$(CONFIG_SERIAL_OF_PLATFORM) += of_serial.o
obj-$(CONFIG_SERIAL_KS8695) += serial_ks8695.o
+obj-$(CONFIG_KGDBOC) += kgdboc.o
obj-$(CONFIG_SERIAL_QE) += ucc_uart.o
Index: linux-kgdb.q/drivers/serial/kgdboc.c
===================================================================
--- /dev/null
+++ linux-kgdb.q/drivers/serial/kgdboc.c
@@ -0,0 +1,164 @@
+/*
+ * drivers/serial/kgdboc.c
+ *
+ * Based on the same principle as kgdboe using the NETPOLL api, this
+ * driver uses a console polling api to implement a gdb serial inteface
+ * which is multiplexed on a console port.
+ *
+ * Maintainer: Jason Wessel <jason.wessel@xxxxxxxxxxxxx>
+ *
+ * 2007-2008 (c) Jason Wessel - Wind River Systems, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+#include <linux/kernel.h>
+#include <linux/ctype.h>
+#include <linux/kgdb.h>
+#include <linux/tty.h>
+
+#define MAX_KGDBOC_CONFIG_STR 40
+
+static struct kgdb_io kgdboc_io_ops;
+
+/* -1 = init not run yet, 0 = unconfigured, 1 = configured. */
+static int configured = -1;
+
+MODULE_DESCRIPTION("KGDB Console TTY Driver");
+MODULE_LICENSE("GPL");
+static char config[MAX_KGDBOC_CONFIG_STR];
+static struct kparam_string kps = {
+ .string = config,
+ .maxlen = MAX_KGDBOC_CONFIG_STR,
+};
+
+static struct tty_driver *kgdb_tty_driver;
+static int kgdb_tty_line;
+
+static int kgdboc_option_setup(char *opt)
+{
+ if (strlen(opt) > MAX_KGDBOC_CONFIG_STR) {
+ printk(KERN_ERR "kgdboc: config string too long\n");
+ return -ENOSPC;
+ }
+ strcpy(config, opt);
+
+ return 0;
+}
+__setup("kgdboc=", kgdboc_option_setup);
+
+static int configure_kgdboc(void)
+{
+ struct tty_driver *p;
+ int tty_line = 0;
+ int err;
+
+ err = kgdboc_option_setup(config);
+ if (err || !strlen(config) || isspace(config[0]))
+ goto noconfig;
+
+ err = -ENODEV;
+
+ p = tty_find_polling_driver(config, &tty_line);
+ if (!p)
+ goto noconfig;
+
+ kgdb_tty_driver = p;
+ kgdb_tty_line = tty_line;
+
+ err = kgdb_register_io_module(&kgdboc_io_ops);
+ if (err)
+ goto noconfig;
+
+ configured = 1;
+
+ return 0;
+
+noconfig:
+ config[0] = 0;
+ configured = 0;
+
+ return err;
+}
+
+static int init_kgdboc(void)
+{
+ /* Already configured? */
+ if (configured == 1)
+ return 0;
+
+ return configure_kgdboc();
+}
+
+static void cleanup_kgdboc(void)
+{
+ if (configured == 1)
+ kgdb_unregister_io_module(&kgdboc_io_ops);
+}
+
+static int kgdboc_get_char(void)
+{
+ return kgdb_tty_driver->poll_get_char(kgdb_tty_driver, kgdb_tty_line);
+}
+
+static void kgdboc_put_char(u8 chr)
+{
+ kgdb_tty_driver->poll_put_char(kgdb_tty_driver, kgdb_tty_line, chr);
+}
+
+static int param_set_kgdboc_var(const char *kmessage, struct kernel_param *kp)
+{
+ if (strlen(kmessage) >= MAX_KGDBOC_CONFIG_STR) {
+ printk(KERN_ERR "kgdboc: config string too long\n");
+ return -ENOSPC;
+ }
+
+ /* Only copy in the string if the init function has not run yet */
+ if (configured < 0) {
+ strcpy(config, kmessage);
+ return 0;
+ }
+
+ if (kgdb_connected) {
+ printk(KERN_ERR
+ "kgdboc: Cannot reconfigure while KGDB is connected.\n");
+
+ return -EBUSY;
+ }
+
+ strcpy(config, kmessage);
+
+ if (configured == 1)
+ cleanup_kgdboc();
+
+ /* Go and configure with the new params. */
+ return configure_kgdboc();
+}
+
+static void kgdboc_pre_exp_handler(void)
+{
+ /* Increment the module count when the debugger is active */
+ if (!kgdb_connected)
+ try_module_get(THIS_MODULE);
+}
+
+static void kgdboc_post_exp_handler(void)
+{
+ /* decrement the module count when the debugger detaches */
+ if (!kgdb_connected)
+ module_put(THIS_MODULE);
+}
+
+static struct kgdb_io kgdboc_io_ops = {
+ .name = "kgdboc",
+ .read_char = kgdboc_get_char,
+ .write_char = kgdboc_put_char,
+ .pre_exception = kgdboc_pre_exp_handler,
+ .post_exception = kgdboc_post_exp_handler,
+};
+
+module_init(init_kgdboc);
+module_exit(cleanup_kgdboc);
+module_param_call(kgdboc, param_set_kgdboc_var, param_get_string, &kps, 0644);
+MODULE_PARM_DESC(kgdboc, "<serial_device>[,baud]");
Index: linux-kgdb.q/drivers/serial/serial_core.c
===================================================================
--- linux-kgdb.q.orig/drivers/serial/serial_core.c
+++ linux-kgdb.q/drivers/serial/serial_core.c
@@ -1924,7 +1924,12 @@ uart_set_options(struct uart_port *port,
port->mctrl |= TIOCM_DTR;

port->ops->set_termios(port, &termios, &dummy);
- co->cflag = termios.c_cflag;
+ /*
+ * Allow the setting of the UART parameters with a NULL console
+ * too:
+ */
+ if (co)
+ co->cflag = termios.c_cflag;

return 0;
}
@@ -2182,6 +2187,61 @@ uart_configure_port(struct uart_driver *
}
}

+#ifdef CONFIG_CONSOLE_POLL
+
+static int uart_poll_init(struct tty_driver *driver, int line, char *options)
+{
+ struct uart_driver *drv = driver->driver_state;
+ struct uart_state *state = drv->state + line;
+ struct uart_port *port;
+ int baud = 9600;
+ int bits = 8;
+ int parity = 'n';
+ int flow = 'n';
+
+ if (!state || !state->port)
+ return -1;
+
+ port = state->port;
+ if (!(port->ops->poll_get_char &&
+ port->ops->poll_put_char))
+ return -1;
+
+ if (options) {
+ uart_parse_options(options, &baud, &parity, &bits, &flow);
+ return uart_set_options(port, NULL, baud, parity, bits, flow);
+ }
+
+ return 0;
+}
+
+static int uart_poll_get_char(struct tty_driver *driver, int line)
+{
+ struct uart_driver *drv = driver->driver_state;
+ struct uart_state *state = drv->state + line;
+ struct uart_port *port;
+
+ if (!state || !state->port)
+ return -1;
+
+ port = state->port;
+ return port->ops->poll_get_char(port);
+}
+
+static void uart_poll_put_char(struct tty_driver *driver, int line, char ch)
+{
+ struct uart_driver *drv = driver->driver_state;
+ struct uart_state *state = drv->state + line;
+ struct uart_port *port;
+
+ if (!state || !state->port)
+ return;
+
+ port = state->port;
+ port->ops->poll_put_char(port, ch);
+}
+#endif
+
static const struct tty_operations uart_ops = {
.open = uart_open,
.close = uart_close,
@@ -2206,6 +2266,11 @@ static const struct tty_operations uart_
#endif
.tiocmget = uart_tiocmget,
.tiocmset = uart_tiocmset,
+#ifdef CONFIG_CONSOLE_POLL
+ .poll_init = uart_poll_init,
+ .poll_get_char = uart_poll_get_char,
+ .poll_put_char = uart_poll_put_char,
+#endif
};

/**
Index: linux-kgdb.q/include/linux/serial_core.h
===================================================================
--- linux-kgdb.q.orig/include/linux/serial_core.h
+++ linux-kgdb.q/include/linux/serial_core.h
@@ -211,6 +211,10 @@ struct uart_ops {
void (*config_port)(struct uart_port *, int);
int (*verify_port)(struct uart_port *, struct serial_struct *);
int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
+#ifdef CONFIG_CONSOLE_POLL
+ void (*poll_put_char)(struct uart_port *, unsigned char);
+ int (*poll_get_char)(struct uart_port *);
+#endif
};

#define UART_CONFIG_TYPE (1 << 0)
Index: linux-kgdb.q/include/linux/tty_driver.h
===================================================================
--- linux-kgdb.q.orig/include/linux/tty_driver.h
+++ linux-kgdb.q/include/linux/tty_driver.h
@@ -125,6 +125,7 @@
#include <linux/cdev.h>

struct tty_struct;
+struct tty_driver;

struct tty_operations {
int (*open)(struct tty_struct * tty, struct file * filp);
@@ -157,6 +158,11 @@ struct tty_operations {
int (*tiocmget)(struct tty_struct *tty, struct file *file);
int (*tiocmset)(struct tty_struct *tty, struct file *file,
unsigned int set, unsigned int clear);
+#ifdef CONFIG_CONSOLE_POLL
+ int (*poll_init)(struct tty_driver *driver, int line, char *options);
+ int (*poll_get_char)(struct tty_driver *driver, int line);
+ void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
+#endif
};

struct tty_driver {
@@ -220,6 +226,11 @@ struct tty_driver {
int (*tiocmget)(struct tty_struct *tty, struct file *file);
int (*tiocmset)(struct tty_struct *tty, struct file *file,
unsigned int set, unsigned int clear);
+#ifdef CONFIG_CONSOLE_POLL
+ int (*poll_init)(struct tty_driver *driver, int line, char *options);
+ int (*poll_get_char)(struct tty_driver *driver, int line);
+ void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
+#endif

struct list_head tty_drivers;
};
@@ -230,6 +241,7 @@ struct tty_driver *alloc_tty_driver(int
void put_tty_driver(struct tty_driver *driver);
void tty_set_operations(struct tty_driver *driver,
const struct tty_operations *op);
+extern struct tty_driver *tty_find_polling_driver(char *name, int *line);

/* tty driver magic number */
#define TTY_DRIVER_MAGIC 0x5402
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/