diff options
Diffstat (limited to 'drivers/s390/char/sclp_vt220.c')
| -rw-r--r-- | drivers/s390/char/sclp_vt220.c | 288 |
1 files changed, 178 insertions, 110 deletions
diff --git a/drivers/s390/char/sclp_vt220.c b/drivers/s390/char/sclp_vt220.c index ad51738c426..b9a9f721716 100644 --- a/drivers/s390/char/sclp_vt220.c +++ b/drivers/s390/char/sclp_vt220.c @@ -1,10 +1,9 @@ /* - * drivers/s390/char/sclp_vt220.c - * SCLP VT220 terminal driver. + * SCLP VT220 terminal driver. * - * S390 version - * Copyright IBM Corp. 2003,2008 - * Author(s): Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com> + * Copyright IBM Corp. 2003, 2009 + * + * Author(s): Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com> */ #include <linux/module.h> @@ -21,9 +20,11 @@ #include <linux/major.h> #include <linux/console.h> #include <linux/kdev_t.h> -#include <linux/bootmem.h> #include <linux/interrupt.h> #include <linux/init.h> +#include <linux/reboot.h> +#include <linux/slab.h> + #include <asm/uaccess.h> #include "sclp.h" @@ -33,7 +34,6 @@ #define SCLP_VT220_DEVICE_NAME "ttysclp" #define SCLP_VT220_CONSOLE_NAME "ttyS" #define SCLP_VT220_CONSOLE_INDEX 1 /* console=ttyS1 */ -#define SCLP_VT220_BUF_SIZE 80 /* Representation of a single write request */ struct sclp_vt220_request { @@ -55,8 +55,7 @@ struct sclp_vt220_sccb { /* Structures and data needed to register tty driver */ static struct tty_driver *sclp_vt220_driver; -/* The tty_struct that the kernel associated with us */ -static struct tty_struct *sclp_vt220_tty; +static struct tty_port sclp_vt220_port; /* Lock to protect internal data from concurrent access */ static spinlock_t sclp_vt220_lock; @@ -67,8 +66,11 @@ static struct list_head sclp_vt220_empty; /* List of pending requests */ static struct list_head sclp_vt220_outqueue; -/* Number of requests in outqueue */ -static int sclp_vt220_outqueue_count; +/* Suspend mode flag */ +static int sclp_vt220_suspended; + +/* Flag that output queue is currently running */ +static int sclp_vt220_queue_running; /* Timer used for delaying write requests to merge subsequent messages into * a single buffer */ @@ -90,15 +92,21 @@ static int __initdata sclp_vt220_init_count; static int sclp_vt220_flush_later; static void sclp_vt220_receiver_fn(struct evbuf_header *evbuf); +static void sclp_vt220_pm_event_fn(struct sclp_register *reg, + enum sclp_pm_event sclp_pm_event); static int __sclp_vt220_emit(struct sclp_vt220_request *request); static void sclp_vt220_emit_current(void); -/* Registration structure for our interest in SCLP event buffers */ +/* Registration structure for SCLP output event buffers */ static struct sclp_register sclp_vt220_register = { .send_mask = EVTYP_VT220MSG_MASK, + .pm_event_fn = sclp_vt220_pm_event_fn, +}; + +/* Registration structure for SCLP input event buffers */ +static struct sclp_register sclp_vt220_register_input = { .receive_mask = EVTYP_VT220MSG_MASK, - .state_change_fn = NULL, - .receiver_fn = sclp_vt220_receiver_fn + .receiver_fn = sclp_vt220_receiver_fn, }; @@ -118,21 +126,22 @@ sclp_vt220_process_queue(struct sclp_vt220_request *request) spin_lock_irqsave(&sclp_vt220_lock, flags); /* Move request from outqueue to empty queue */ list_del(&request->list); - sclp_vt220_outqueue_count--; list_add_tail((struct list_head *) page, &sclp_vt220_empty); /* Check if there is a pending buffer on the out queue. */ request = NULL; if (!list_empty(&sclp_vt220_outqueue)) request = list_entry(sclp_vt220_outqueue.next, struct sclp_vt220_request, list); + if (!request || sclp_vt220_suspended) { + sclp_vt220_queue_running = 0; + spin_unlock_irqrestore(&sclp_vt220_lock, flags); + break; + } spin_unlock_irqrestore(&sclp_vt220_lock, flags); - } while (request && __sclp_vt220_emit(request)); + } while (__sclp_vt220_emit(request)); if (request == NULL && sclp_vt220_flush_later) sclp_vt220_emit_current(); - /* Check if the tty needs a wake up call */ - if (sclp_vt220_tty != NULL) { - tty_wakeup(sclp_vt220_tty); - } + tty_port_tty_wakeup(&sclp_vt220_port); } #define SCLP_BUFFER_MAX_RETRY 1 @@ -210,26 +219,7 @@ __sclp_vt220_emit(struct sclp_vt220_request *request) } /* - * Queue and emit given request. - */ -static void -sclp_vt220_emit(struct sclp_vt220_request *request) -{ - unsigned long flags; - int count; - - spin_lock_irqsave(&sclp_vt220_lock, flags); - list_add_tail(&request->list, &sclp_vt220_outqueue); - count = sclp_vt220_outqueue_count++; - spin_unlock_irqrestore(&sclp_vt220_lock, flags); - /* Emit only the first buffer immediately - callback takes care of - * the rest */ - if (count == 0 && __sclp_vt220_emit(request)) - sclp_vt220_process_queue(request); -} - -/* - * Queue and emit current request. Return zero on success, non-zero otherwise. + * Queue and emit current request. */ static void sclp_vt220_emit_current(void) @@ -239,22 +229,33 @@ sclp_vt220_emit_current(void) struct sclp_vt220_sccb *sccb; spin_lock_irqsave(&sclp_vt220_lock, flags); - request = NULL; - if (sclp_vt220_current_request != NULL) { + if (sclp_vt220_current_request) { sccb = (struct sclp_vt220_sccb *) sclp_vt220_current_request->sclp_req.sccb; /* Only emit buffers with content */ if (sccb->header.length != sizeof(struct sclp_vt220_sccb)) { - request = sclp_vt220_current_request; + list_add_tail(&sclp_vt220_current_request->list, + &sclp_vt220_outqueue); sclp_vt220_current_request = NULL; if (timer_pending(&sclp_vt220_timer)) del_timer(&sclp_vt220_timer); } sclp_vt220_flush_later = 0; } + if (sclp_vt220_queue_running || sclp_vt220_suspended) + goto out_unlock; + if (list_empty(&sclp_vt220_outqueue)) + goto out_unlock; + request = list_first_entry(&sclp_vt220_outqueue, + struct sclp_vt220_request, list); + sclp_vt220_queue_running = 1; + spin_unlock_irqrestore(&sclp_vt220_lock, flags); + + if (__sclp_vt220_emit(request)) + sclp_vt220_process_queue(request); + return; +out_unlock: spin_unlock_irqrestore(&sclp_vt220_lock, flags); - if (request != NULL) - sclp_vt220_emit(request); } #define SCLP_NORMAL_WRITE 0x00 @@ -364,6 +365,31 @@ sclp_vt220_timeout(unsigned long data) #define BUFFER_MAX_DELAY HZ/20 +/* + * Drop oldest console buffer if sclp_con_drop is set + */ +static int +sclp_vt220_drop_buffer(void) +{ + struct list_head *list; + struct sclp_vt220_request *request; + void *page; + + if (!sclp_console_drop) + return 0; + list = sclp_vt220_outqueue.next; + if (sclp_vt220_queue_running) + /* The first element is in I/O */ + list = list->next; + if (list == &sclp_vt220_outqueue) + return 0; + list_del(list); + request = list_entry(list, struct sclp_vt220_request, list); + page = request->sclp_req.sccb; + list_add_tail((struct list_head *) page, &sclp_vt220_empty); + return 1; +} + /* * Internal implementation of the write function. Write COUNT bytes of data * from memory at BUF @@ -392,12 +418,16 @@ __sclp_vt220_write(const unsigned char *buf, int count, int do_schedule, do { /* Create an sclp output buffer if none exists yet */ if (sclp_vt220_current_request == NULL) { + if (list_empty(&sclp_vt220_empty)) + sclp_console_full++; while (list_empty(&sclp_vt220_empty)) { - spin_unlock_irqrestore(&sclp_vt220_lock, flags); - if (may_fail) + if (may_fail || sclp_vt220_suspended) goto out; - else - sclp_sync_wait(); + if (sclp_vt220_drop_buffer()) + break; + spin_unlock_irqrestore(&sclp_vt220_lock, flags); + + sclp_sync_wait(); spin_lock_irqsave(&sclp_vt220_lock, flags); } page = (void *) sclp_vt220_empty.next; @@ -430,8 +460,8 @@ __sclp_vt220_write(const unsigned char *buf, int count, int do_schedule, sclp_vt220_timer.expires = jiffies + BUFFER_MAX_DELAY; add_timer(&sclp_vt220_timer); } - spin_unlock_irqrestore(&sclp_vt220_lock, flags); out: + spin_unlock_irqrestore(&sclp_vt220_lock, flags); return overall_written; } @@ -460,10 +490,6 @@ sclp_vt220_receiver_fn(struct evbuf_header *evbuf) char *buffer; unsigned int count; - /* Ignore input if device is not open */ - if (sclp_vt220_tty == NULL) - return; - buffer = (char *) ((addr_t) evbuf + sizeof(struct evbuf_header)); count = evbuf->length - sizeof(struct evbuf_header); @@ -475,8 +501,8 @@ sclp_vt220_receiver_fn(struct evbuf_header *evbuf) /* Send input to line discipline */ buffer++; count--; - tty_insert_flip_string(sclp_vt220_tty, buffer, count); - tty_flip_buffer_push(sclp_vt220_tty); + tty_insert_flip_string(&sclp_vt220_port, buffer, count); + tty_flip_buffer_push(&sclp_vt220_port); break; } } @@ -488,11 +514,12 @@ static int sclp_vt220_open(struct tty_struct *tty, struct file *filp) { if (tty->count == 1) { - sclp_vt220_tty = tty; - tty->driver_data = kmalloc(SCLP_VT220_BUF_SIZE, GFP_KERNEL); - if (tty->driver_data == NULL) - return -ENOMEM; - tty->low_latency = 0; + tty_port_tty_set(&sclp_vt220_port, tty); + sclp_vt220_port.low_latency = 0; + if (!tty->winsize.ws_row && !tty->winsize.ws_col) { + tty->winsize.ws_row = 24; + tty->winsize.ws_col = 80; + } } return 0; } @@ -503,11 +530,8 @@ sclp_vt220_open(struct tty_struct *tty, struct file *filp) static void sclp_vt220_close(struct tty_struct *tty, struct file *filp) { - if (tty->count == 1) { - sclp_vt220_tty = NULL; - kfree(tty->driver_data); - tty->driver_data = NULL; - } + if (tty->count == 1) + tty_port_tty_set(&sclp_vt220_port, NULL); } /* @@ -529,7 +553,7 @@ sclp_vt220_put_char(struct tty_struct *tty, unsigned char ch) static void sclp_vt220_flush_chars(struct tty_struct *tty) { - if (sclp_vt220_outqueue_count == 0) + if (!sclp_vt220_queue_running) sclp_vt220_emit_current(); else sclp_vt220_flush_later = 1; @@ -581,23 +605,6 @@ sclp_vt220_chars_in_buffer(struct tty_struct *tty) return count; } -static void -__sclp_vt220_flush_buffer(void) -{ - unsigned long flags; - - sclp_vt220_emit_current(); - spin_lock_irqsave(&sclp_vt220_lock, flags); - if (timer_pending(&sclp_vt220_timer)) - del_timer(&sclp_vt220_timer); - while (sclp_vt220_outqueue_count > 0) { - spin_unlock_irqrestore(&sclp_vt220_lock, flags); - sclp_sync_wait(); - spin_lock_irqsave(&sclp_vt220_lock, flags); - } - spin_unlock_irqrestore(&sclp_vt220_lock, flags); -} - /* * Pass on all buffers to the hardware. Return only when there are no more * buffers pending. @@ -615,10 +622,7 @@ static void __init __sclp_vt220_free_pages(void) list_for_each_safe(page, p, &sclp_vt220_empty) { list_del(page); - if (slab_is_available()) - free_page((unsigned long) page); - else - free_bootmem((unsigned long) page, PAGE_SIZE); + free_page((unsigned long) page); } } @@ -631,6 +635,7 @@ static void __init __sclp_vt220_cleanup(void) return; sclp_unregister(&sclp_vt220_register); __sclp_vt220_free_pages(); + tty_port_destroy(&sclp_vt220_port); } /* Allocate buffer pages and register with sclp core. Controlled by init @@ -648,29 +653,25 @@ static int __init __sclp_vt220_init(int num_pages) INIT_LIST_HEAD(&sclp_vt220_empty); INIT_LIST_HEAD(&sclp_vt220_outqueue); init_timer(&sclp_vt220_timer); + tty_port_init(&sclp_vt220_port); sclp_vt220_current_request = NULL; sclp_vt220_buffered_chars = 0; - sclp_vt220_outqueue_count = 0; - sclp_vt220_tty = NULL; sclp_vt220_flush_later = 0; /* Allocate pages for output buffering */ + rc = -ENOMEM; for (i = 0; i < num_pages; i++) { - if (slab_is_available()) - page = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA); - else - page = alloc_bootmem_low_pages(PAGE_SIZE); - if (!page) { - rc = -ENOMEM; + page = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA); + if (!page) goto out; - } - list_add_tail((struct list_head *) page, &sclp_vt220_empty); + list_add_tail(page, &sclp_vt220_empty); } rc = sclp_register(&sclp_vt220_register); out: if (rc) { __sclp_vt220_free_pages(); sclp_vt220_init_count--; + tty_port_destroy(&sclp_vt220_port); } return rc; } @@ -703,7 +704,6 @@ static int __init sclp_vt220_tty_init(void) if (rc) goto out_driver; - driver->owner = THIS_MODULE; driver->driver_name = SCLP_VT220_DRIVER_NAME; driver->name = SCLP_VT220_DEVICE_NAME; driver->major = SCLP_VT220_MAJOR; @@ -713,13 +713,19 @@ static int __init sclp_vt220_tty_init(void) driver->init_termios = tty_std_termios; driver->flags = TTY_DRIVER_REAL_RAW; tty_set_operations(driver, &sclp_vt220_ops); + tty_port_link_device(&sclp_vt220_port, driver, 0); rc = tty_register_driver(driver); if (rc) goto out_init; + rc = sclp_register(&sclp_vt220_register_input); + if (rc) + goto out_reg; sclp_vt220_driver = driver; return 0; +out_reg: + tty_unregister_driver(driver); out_init: __sclp_vt220_cleanup(); out_driver: @@ -728,6 +734,62 @@ out_driver: } __initcall(sclp_vt220_tty_init); +static void __sclp_vt220_flush_buffer(void) +{ + unsigned long flags; + + sclp_vt220_emit_current(); + spin_lock_irqsave(&sclp_vt220_lock, flags); + if (timer_pending(&sclp_vt220_timer)) + del_timer(&sclp_vt220_timer); + while (sclp_vt220_queue_running) { + spin_unlock_irqrestore(&sclp_vt220_lock, flags); + sclp_sync_wait(); + spin_lock_irqsave(&sclp_vt220_lock, flags); + } + spin_unlock_irqrestore(&sclp_vt220_lock, flags); +} + +/* + * Resume console: If there are cached messages, emit them. + */ +static void sclp_vt220_resume(void) +{ + unsigned long flags; + + spin_lock_irqsave(&sclp_vt220_lock, flags); + sclp_vt220_suspended = 0; + spin_unlock_irqrestore(&sclp_vt220_lock, flags); + sclp_vt220_emit_current(); +} + +/* + * Suspend console: Set suspend flag and flush console + */ +static void sclp_vt220_suspend(void) +{ + unsigned long flags; + + spin_lock_irqsave(&sclp_vt220_lock, flags); + sclp_vt220_suspended = 1; + spin_unlock_irqrestore(&sclp_vt220_lock, flags); + __sclp_vt220_flush_buffer(); +} + +static void sclp_vt220_pm_event_fn(struct sclp_register *reg, + enum sclp_pm_event sclp_pm_event) +{ + switch (sclp_pm_event) { + case SCLP_PM_EVENT_FREEZE: + sclp_vt220_suspend(); + break; + case SCLP_PM_EVENT_RESTORE: + case SCLP_PM_EVENT_THAW: + sclp_vt220_resume(); + break; + } +} + #ifdef CONFIG_SCLP_VT220_CONSOLE static void @@ -743,24 +805,30 @@ sclp_vt220_con_device(struct console *c, int *index) return sclp_vt220_driver; } -/* - * This routine is called from panic when the kernel is going to give up. - * We have to make sure that all buffers will be flushed to the SCLP. - * Note that this function may be called from within an interrupt context. - */ -static void -sclp_vt220_con_unblank(void) +static int +sclp_vt220_notify(struct notifier_block *self, + unsigned long event, void *data) { __sclp_vt220_flush_buffer(); + return NOTIFY_OK; } +static struct notifier_block on_panic_nb = { + .notifier_call = sclp_vt220_notify, + .priority = 1, +}; + +static struct notifier_block on_reboot_nb = { + .notifier_call = sclp_vt220_notify, + .priority = 1, +}; + /* Structure needed to register with printk */ static struct console sclp_vt220_console = { .name = SCLP_VT220_CONSOLE_NAME, .write = sclp_vt220_con_write, .device = sclp_vt220_con_device, - .unblank = sclp_vt220_con_unblank, .flags = CON_PRINTBUFFER, .index = SCLP_VT220_CONSOLE_INDEX }; @@ -770,12 +838,12 @@ sclp_vt220_con_init(void) { int rc; - if (!CONSOLE_IS_SCLP) - return 0; - rc = __sclp_vt220_init(MAX_CONSOLE_PAGES); + rc = __sclp_vt220_init(sclp_console_pages); if (rc) return rc; /* Attach linux console */ + atomic_notifier_chain_register(&panic_notifier_list, &on_panic_nb); + register_reboot_notifier(&on_reboot_nb); register_console(&sclp_vt220_console); return 0; } |
