diff options
author | kaf24@scramble.cl.cam.ac.uk <kaf24@scramble.cl.cam.ac.uk> | 2004-06-09 16:36:08 +0000 |
---|---|---|
committer | kaf24@scramble.cl.cam.ac.uk <kaf24@scramble.cl.cam.ac.uk> | 2004-06-09 16:36:08 +0000 |
commit | 03907beb680ee39bee212bb3cb170cb295238cda (patch) | |
tree | f594d4df409070fe7f41cb1709f1ae22b76ac17c /linux-2.4.26-xen-sparse/arch/xen/drivers/console/console.c | |
parent | 63cf7286eeb40cadb2581fef38376da82a25b2ba (diff) | |
download | xen-03907beb680ee39bee212bb3cb170cb295238cda.tar.gz xen-03907beb680ee39bee212bb3cb170cb295238cda.tar.bz2 xen-03907beb680ee39bee212bb3cb170cb295238cda.zip |
bitkeeper revision 1.946 (40c73c7805slPvnfEohXfQoiriAESg)
Rename Linux directories and upgrade build system.
Diffstat (limited to 'linux-2.4.26-xen-sparse/arch/xen/drivers/console/console.c')
-rw-r--r-- | linux-2.4.26-xen-sparse/arch/xen/drivers/console/console.c | 625 |
1 files changed, 625 insertions, 0 deletions
diff --git a/linux-2.4.26-xen-sparse/arch/xen/drivers/console/console.c b/linux-2.4.26-xen-sparse/arch/xen/drivers/console/console.c new file mode 100644 index 0000000000..91b97e83d9 --- /dev/null +++ b/linux-2.4.26-xen-sparse/arch/xen/drivers/console/console.c @@ -0,0 +1,625 @@ +/****************************************************************************** + * console.c + * + * Virtual console driver. + * + * Copyright (c) 2002-2004, K A Fraser. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <linux/major.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/console.h> +#include <asm/evtchn.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/uaccess.h> +#include <asm/hypervisor.h> +#include <asm/hypervisor-ifs/event_channel.h> +#include <asm/ctrl_if.h> + +/* + * Modes: + * 'xencons=off' [XC_OFF]: Console is disabled. + * 'xencons=tty' [XC_TTY]: Console attached to '/dev/tty[0-9]+'. + * 'xencons=ttyS' [XC_SERIAL]: Console attached to '/dev/ttyS[0-9]+'. + * [XC_DEFAULT]: DOM0 -> XC_SERIAL ; all others -> XC_TTY. + * + * NB. In mode XC_TTY, we create dummy consoles for tty2-63. This suppresses + * warnings from standard distro startup scripts. + */ +static enum { XC_OFF, XC_DEFAULT, XC_TTY, XC_SERIAL } xc_mode = XC_DEFAULT; + +static int __init xencons_setup(char *str) +{ + if ( !strcmp(str, "tty") ) + xc_mode = XC_TTY; + else if ( !strcmp(str, "ttyS") ) + xc_mode = XC_SERIAL; + else if ( !strcmp(str, "off") ) + xc_mode = XC_OFF; + return 1; +} +__setup("xencons", xencons_setup); + +/* The kernel and user-land drivers share a common transmit buffer. */ +#define WBUF_SIZE 4096 +#define WBUF_MASK(_i) ((_i)&(WBUF_SIZE-1)) +static char wbuf[WBUF_SIZE]; +static unsigned int wc, wp; /* write_cons, write_prod */ + +/* This lock protects accesses to the common transmit buffer. */ +static spinlock_t xencons_lock = SPIN_LOCK_UNLOCKED; + +/* Common transmit-kick routine. */ +static void __xencons_tx_flush(void); + +/* This task is used to defer sending console data until there is space. */ +static void xencons_tx_flush_task_routine(void *data); +static struct tq_struct xencons_tx_flush_task = { + routine: xencons_tx_flush_task_routine +}; + + +/******************** Kernel console driver ********************************/ + +static void kcons_write( + struct console *c, const char *s, unsigned int count) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&xencons_lock, flags); + + for ( i = 0; i < count; i++ ) + { + if ( (wp - wc) >= (WBUF_SIZE - 1) ) + break; + if ( (wbuf[WBUF_MASK(wp++)] = s[i]) == '\n' ) + wbuf[WBUF_MASK(wp++)] = '\r'; + } + + __xencons_tx_flush(); + + spin_unlock_irqrestore(&xencons_lock, flags); +} + +static void kcons_write_dom0( + struct console *c, const char *s, unsigned int count) +{ + int rc; + + while ( count > 0 ) + { + if ( (rc = HYPERVISOR_console_io(CONSOLEIO_write, count, s)) > 0 ) + { + count -= rc; + s += rc; + } + else + break; + } +} + +static kdev_t kcons_device(struct console *c) +{ + return MKDEV(TTY_MAJOR, (xc_mode == XC_SERIAL) ? 64 : 1); +} + +static struct console kcons_info = { + device: kcons_device, + flags: CON_PRINTBUFFER, + index: -1 +}; + +void xen_console_init(void) +{ + if ( start_info.flags & SIF_INITDOMAIN ) + { + if ( xc_mode == XC_DEFAULT ) + xc_mode = XC_SERIAL; + kcons_info.write = kcons_write_dom0; + } + else + { + if ( xc_mode == XC_DEFAULT ) + xc_mode = XC_TTY; + kcons_info.write = kcons_write; + } + + if ( xc_mode == XC_OFF ) + return; + + if ( xc_mode == XC_SERIAL ) + strcpy(kcons_info.name, "ttyS"); + else + strcpy(kcons_info.name, "tty"); + + register_console(&kcons_info); +} + + +/*** Useful function for console debugging -- goes straight to Xen. ***/ +asmlinkage int xprintk(const char *fmt, ...) +{ + va_list args; + int printk_len; + static char printk_buf[1024]; + + /* Emit the output into the temporary buffer */ + va_start(args, fmt); + printk_len = vsnprintf(printk_buf, sizeof(printk_buf), fmt, args); + va_end(args); + + /* Send the processed output directly to Xen. */ + kcons_write_dom0(NULL, printk_buf, printk_len); + + return 0; +} + +/*** Forcibly flush console data before dying. ***/ +void xencons_force_flush(void) +{ + ctrl_msg_t msg; + int sz; + + /* Emergency console is synchronous, so there's nothing to flush. */ + if ( start_info.flags & SIF_INITDOMAIN ) + return; + + /* + * We use dangerous control-interface functions that require a quiescent + * system and no interrupts. Try to ensure this with a global cli(). + */ + cli(); + + /* Spin until console data is flushed through to the domain controller. */ + while ( (wc != wp) && !ctrl_if_transmitter_empty() ) + { + /* Interrupts are disabled -- we must manually reap responses. */ + ctrl_if_discard_responses(); + + if ( (sz = wp - wc) == 0 ) + continue; + if ( sz > sizeof(msg.msg) ) + sz = sizeof(msg.msg); + if ( sz > (WBUF_SIZE - WBUF_MASK(wc)) ) + sz = WBUF_SIZE - WBUF_MASK(wc); + + msg.type = CMSG_CONSOLE; + msg.subtype = CMSG_CONSOLE_DATA; + msg.length = sz; + memcpy(msg.msg, &wbuf[WBUF_MASK(wc)], sz); + + if ( ctrl_if_send_message_noblock(&msg, NULL, 0) == 0 ) + wc += sz; + } +} + + +/******************** User-space console driver (/dev/console) ************/ + +static struct tty_driver xencons_driver; +static int xencons_refcount; +static struct tty_struct *xencons_table[MAX_NR_CONSOLES]; +static struct termios *xencons_termios[MAX_NR_CONSOLES]; +static struct termios *xencons_termios_locked[MAX_NR_CONSOLES]; +static struct tty_struct *xencons_tty; +static int xencons_priv_irq; +static char x_char; + +/* Non-privileged receive callback. */ +static void xencons_rx(ctrl_msg_t *msg, unsigned long id) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&xencons_lock, flags); + if ( xencons_tty != NULL ) + { + for ( i = 0; i < msg->length; i++ ) + tty_insert_flip_char(xencons_tty, msg->msg[i], 0); + tty_flip_buffer_push(xencons_tty); + } + spin_unlock_irqrestore(&xencons_lock, flags); + + msg->length = 0; + ctrl_if_send_response(msg); +} + +/* Privileged and non-privileged transmit worker. */ +static void __xencons_tx_flush(void) +{ + int sz, work_done = 0; + ctrl_msg_t msg; + + if ( start_info.flags & SIF_INITDOMAIN ) + { + if ( x_char ) + { + kcons_write_dom0(NULL, &x_char, 1); + x_char = 0; + work_done = 1; + } + + while ( wc != wp ) + { + sz = wp - wc; + if ( sz > (WBUF_SIZE - WBUF_MASK(wc)) ) + sz = WBUF_SIZE - WBUF_MASK(wc); + kcons_write_dom0(NULL, &wbuf[WBUF_MASK(wc)], sz); + wc += sz; + work_done = 1; + } + } + else + { + while ( x_char ) + { + msg.type = CMSG_CONSOLE; + msg.subtype = CMSG_CONSOLE_DATA; + msg.length = 1; + msg.msg[0] = x_char; + + if ( ctrl_if_send_message_noblock(&msg, NULL, 0) == 0 ) + x_char = 0; + else if ( ctrl_if_enqueue_space_callback(&xencons_tx_flush_task) ) + break; + + work_done = 1; + } + + while ( wc != wp ) + { + sz = wp - wc; + if ( sz > sizeof(msg.msg) ) + sz = sizeof(msg.msg); + if ( sz > (WBUF_SIZE - WBUF_MASK(wc)) ) + sz = WBUF_SIZE - WBUF_MASK(wc); + + msg.type = CMSG_CONSOLE; + msg.subtype = CMSG_CONSOLE_DATA; + msg.length = sz; + memcpy(msg.msg, &wbuf[WBUF_MASK(wc)], sz); + + if ( ctrl_if_send_message_noblock(&msg, NULL, 0) == 0 ) + wc += sz; + else if ( ctrl_if_enqueue_space_callback(&xencons_tx_flush_task) ) + break; + + work_done = 1; + } + } + + if ( work_done && (xencons_tty != NULL) ) + { + wake_up_interruptible(&xencons_tty->write_wait); + if ( (xencons_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + (xencons_tty->ldisc.write_wakeup != NULL) ) + (xencons_tty->ldisc.write_wakeup)(xencons_tty); + } +} + +/* Non-privileged transmit kicker. */ +static void xencons_tx_flush_task_routine(void *data) +{ + unsigned long flags; + spin_lock_irqsave(&xencons_lock, flags); + __xencons_tx_flush(); + spin_unlock_irqrestore(&xencons_lock, flags); +} + +/* Privileged receive callback and transmit kicker. */ +static void xencons_priv_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + static char rbuf[16]; + int i, l; + unsigned long flags; + + spin_lock_irqsave(&xencons_lock, flags); + + if ( xencons_tty != NULL ) + { + /* Receive work. */ + while ( (l = HYPERVISOR_console_io(CONSOLEIO_read, 16, rbuf)) > 0 ) + for ( i = 0; i < l; i++ ) + tty_insert_flip_char(xencons_tty, rbuf[i], 0); + if ( xencons_tty->flip.count != 0 ) + tty_flip_buffer_push(xencons_tty); + } + + /* Transmit work. */ + __xencons_tx_flush(); + + spin_unlock_irqrestore(&xencons_lock, flags); +} + +static int xencons_write_room(struct tty_struct *tty) +{ + return WBUF_SIZE - (wp - wc); +} + +static int xencons_chars_in_buffer(struct tty_struct *tty) +{ + return wp - wc; +} + +static void xencons_send_xchar(struct tty_struct *tty, char ch) +{ + unsigned long flags; + + if ( MINOR(tty->device) != xencons_driver.minor_start ) + return; + + spin_lock_irqsave(&xencons_lock, flags); + x_char = ch; + __xencons_tx_flush(); + spin_unlock_irqrestore(&xencons_lock, flags); +} + +static void xencons_throttle(struct tty_struct *tty) +{ + if ( MINOR(tty->device) != xencons_driver.minor_start ) + return; + + if ( I_IXOFF(tty) ) + xencons_send_xchar(tty, STOP_CHAR(tty)); +} + +static void xencons_unthrottle(struct tty_struct *tty) +{ + if ( MINOR(tty->device) != xencons_driver.minor_start ) + return; + + if ( I_IXOFF(tty) ) + { + if ( x_char != 0 ) + x_char = 0; + else + xencons_send_xchar(tty, START_CHAR(tty)); + } +} + +static void xencons_flush_buffer(struct tty_struct *tty) +{ + unsigned long flags; + + if ( MINOR(tty->device) != xencons_driver.minor_start ) + return; + + spin_lock_irqsave(&xencons_lock, flags); + wc = wp = 0; + spin_unlock_irqrestore(&xencons_lock, flags); +} + +static inline int __xencons_put_char(int ch) +{ + char _ch = (char)ch; + if ( (wp - wc) == WBUF_SIZE ) + return 0; + wbuf[WBUF_MASK(wp++)] = _ch; + return 1; +} + +static int xencons_write(struct tty_struct *tty, int from_user, + const u_char * buf, int count) +{ + int i; + unsigned long flags; + + if ( from_user && verify_area(VERIFY_READ, buf, count) ) + return -EINVAL; + + if ( MINOR(tty->device) != xencons_driver.minor_start ) + return count; + + spin_lock_irqsave(&xencons_lock, flags); + + for ( i = 0; i < count; i++ ) + { + char ch; + if ( from_user ) + __get_user(ch, buf + i); + else + ch = buf[i]; + if ( !__xencons_put_char(ch) ) + break; + } + + if ( i != 0 ) + __xencons_tx_flush(); + + spin_unlock_irqrestore(&xencons_lock, flags); + + return i; +} + +static void xencons_put_char(struct tty_struct *tty, u_char ch) +{ + unsigned long flags; + + if ( MINOR(tty->device) != xencons_driver.minor_start ) + return; + + spin_lock_irqsave(&xencons_lock, flags); + (void)__xencons_put_char(ch); + spin_unlock_irqrestore(&xencons_lock, flags); +} + +static void xencons_flush_chars(struct tty_struct *tty) +{ + unsigned long flags; + + if ( MINOR(tty->device) != xencons_driver.minor_start ) + return; + + spin_lock_irqsave(&xencons_lock, flags); + __xencons_tx_flush(); + spin_unlock_irqrestore(&xencons_lock, flags); +} + +static void xencons_wait_until_sent(struct tty_struct *tty, int timeout) +{ + unsigned long orig_jiffies = jiffies; + + if ( MINOR(tty->device) != xencons_driver.minor_start ) + return; + + while ( tty->driver.chars_in_buffer(tty) ) + { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + if ( signal_pending(current) ) + break; + if ( (timeout != 0) && time_after(jiffies, orig_jiffies + timeout) ) + break; + } + + set_current_state(TASK_RUNNING); +} + +static int xencons_open(struct tty_struct *tty, struct file *filp) +{ + int line; + unsigned long flags; + + if ( MINOR(tty->device) != xencons_driver.minor_start ) + return 0; + + MOD_INC_USE_COUNT; + line = MINOR(tty->device) - tty->driver.minor_start; + if ( line != 0 ) + { + MOD_DEC_USE_COUNT; + return -ENODEV; + } + + spin_lock_irqsave(&xencons_lock, flags); + tty->driver_data = NULL; + if ( xencons_tty == NULL ) + xencons_tty = tty; + __xencons_tx_flush(); + spin_unlock_irqrestore(&xencons_lock, flags); + + return 0; +} + +static void xencons_close(struct tty_struct *tty, struct file *filp) +{ + unsigned long flags; + + if ( MINOR(tty->device) != xencons_driver.minor_start ) + return; + + if ( tty->count == 1 ) + { + tty->closing = 1; + tty_wait_until_sent(tty, 0); + if ( tty->driver.flush_buffer != NULL ) + tty->driver.flush_buffer(tty); + if ( tty->ldisc.flush_buffer != NULL ) + tty->ldisc.flush_buffer(tty); + tty->closing = 0; + spin_lock_irqsave(&xencons_lock, flags); + xencons_tty = NULL; + spin_unlock_irqrestore(&xencons_lock, flags); + } + + MOD_DEC_USE_COUNT; +} + +static int __init xencons_init(void) +{ + memset(&xencons_driver, 0, sizeof(struct tty_driver)); + xencons_driver.magic = TTY_DRIVER_MAGIC; + xencons_driver.major = TTY_MAJOR; + xencons_driver.type = TTY_DRIVER_TYPE_SERIAL; + xencons_driver.subtype = SERIAL_TYPE_NORMAL; + xencons_driver.init_termios = tty_std_termios; + xencons_driver.flags = + TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_NO_DEVFS; + xencons_driver.refcount = &xencons_refcount; + xencons_driver.table = xencons_table; + xencons_driver.termios = xencons_termios; + xencons_driver.termios_locked = xencons_termios_locked; + + if ( xc_mode == XC_OFF ) + return 0; + + if ( xc_mode == XC_SERIAL ) + { + xencons_driver.name = "ttyS"; + xencons_driver.minor_start = 64; + xencons_driver.num = 1; + } + else + { + xencons_driver.name = "tty"; + xencons_driver.minor_start = 1; + xencons_driver.num = MAX_NR_CONSOLES; + } + + xencons_driver.open = xencons_open; + xencons_driver.close = xencons_close; + xencons_driver.write = xencons_write; + xencons_driver.write_room = xencons_write_room; + xencons_driver.put_char = xencons_put_char; + xencons_driver.flush_chars = xencons_flush_chars; + xencons_driver.chars_in_buffer = xencons_chars_in_buffer; + xencons_driver.send_xchar = xencons_send_xchar; + xencons_driver.flush_buffer = xencons_flush_buffer; + xencons_driver.throttle = xencons_throttle; + xencons_driver.unthrottle = xencons_unthrottle; + xencons_driver.wait_until_sent = xencons_wait_until_sent; + + if ( tty_register_driver(&xencons_driver) ) + panic("Couldn't register Xen virtual console driver\n"); + + if ( start_info.flags & SIF_INITDOMAIN ) + { + xencons_priv_irq = bind_virq_to_irq(VIRQ_CONSOLE); + (void)request_irq(xencons_priv_irq, + xencons_priv_interrupt, 0, "console", NULL); + } + else + { + (void)ctrl_if_register_receiver(CMSG_CONSOLE, xencons_rx, 0); + } + + printk("Xen virtual console successfully installed\n"); + + return 0; +} + +static void __exit xencons_fini(void) +{ + int ret; + + if ( (ret = tty_unregister_driver(&xencons_driver)) != 0 ) + printk(KERN_ERR "Unable to unregister Xen console driver: %d\n", ret); + + if ( start_info.flags & SIF_INITDOMAIN ) + { + free_irq(xencons_priv_irq, NULL); + unbind_virq_from_irq(VIRQ_CONSOLE); + } + else + { + ctrl_if_unregister_receiver(CMSG_CONSOLE, xencons_rx); + } +} + +module_init(xencons_init); +module_exit(xencons_fini); |