diff options
Diffstat (limited to 'drivers/s390/char')
-rw-r--r-- | drivers/s390/char/con3215.c | 221 | ||||
-rw-r--r-- | drivers/s390/char/con3270.c | 12 | ||||
-rw-r--r-- | drivers/s390/char/fs3270.c | 16 | ||||
-rw-r--r-- | drivers/s390/char/monreader.c | 140 | ||||
-rw-r--r-- | drivers/s390/char/monwriter.c | 98 | ||||
-rw-r--r-- | drivers/s390/char/raw3270.c | 100 | ||||
-rw-r--r-- | drivers/s390/char/raw3270.h | 12 | ||||
-rw-r--r-- | drivers/s390/char/sclp.c | 248 | ||||
-rw-r--r-- | drivers/s390/char/sclp.h | 23 | ||||
-rw-r--r-- | drivers/s390/char/sclp_cmd.c | 42 | ||||
-rw-r--r-- | drivers/s390/char/sclp_con.c | 139 | ||||
-rw-r--r-- | drivers/s390/char/sclp_rw.c | 20 | ||||
-rw-r--r-- | drivers/s390/char/sclp_rw.h | 12 | ||||
-rw-r--r-- | drivers/s390/char/sclp_vt220.c | 118 | ||||
-rw-r--r-- | drivers/s390/char/tape.h | 3 | ||||
-rw-r--r-- | drivers/s390/char/tape_34xx.c | 5 | ||||
-rw-r--r-- | drivers/s390/char/tape_3590.c | 5 | ||||
-rw-r--r-- | drivers/s390/char/tape_core.c | 74 | ||||
-rw-r--r-- | drivers/s390/char/vmlogrdr.c | 39 | ||||
-rw-r--r-- | drivers/s390/char/vmur.c | 42 | ||||
-rw-r--r-- | drivers/s390/char/vmwatchdog.c | 81 |
21 files changed, 1132 insertions, 318 deletions
diff --git a/drivers/s390/char/con3215.c b/drivers/s390/char/con3215.c index 9ab06e0dad4..04dc734805c 100644 --- a/drivers/s390/char/con3215.c +++ b/drivers/s390/char/con3215.c @@ -1,14 +1,12 @@ /* - * drivers/s390/char/con3215.c - * 3215 line mode terminal driver. + * 3215 line mode terminal driver. * - * S390 version - * Copyright (C) 1999,2000 IBM Deutschland Entwicklung GmbH, IBM Corporation - * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com), + * Copyright IBM Corp. 1999, 2009 + * Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com> * - * Updated: - * Aug-2000: Added tab support - * Dan Morrison, IBM Corporation (dmorriso@cse.buffalo.edu) + * Updated: + * Aug-2000: Added tab support + * Dan Morrison, IBM Corporation <dmorriso@cse.buffalo.edu> */ #include <linux/module.h> @@ -56,6 +54,7 @@ #define RAW3215_CLOSING 32 /* set while in close process */ #define RAW3215_TIMER_RUNS 64 /* set if the output delay timer is on */ #define RAW3215_FLUSHING 128 /* set to flush buffer (no delay) */ +#define RAW3215_FROZEN 256 /* set if 3215 is frozen for suspend */ #define TAB_STOP_SIZE 8 /* tab stop size */ @@ -111,8 +110,8 @@ static struct tty_driver *tty3215_driver; /* * Get a request structure from the free list */ -static inline struct raw3215_req * -raw3215_alloc_req(void) { +static inline struct raw3215_req *raw3215_alloc_req(void) +{ struct raw3215_req *req; unsigned long flags; @@ -126,8 +125,8 @@ raw3215_alloc_req(void) { /* * Put a request structure back to the free list */ -static inline void -raw3215_free_req(struct raw3215_req *req) { +static inline void raw3215_free_req(struct raw3215_req *req) +{ unsigned long flags; if (req->type == RAW3215_FREE) @@ -145,8 +144,7 @@ raw3215_free_req(struct raw3215_req *req) { * because a 3215 terminal won't accept a new read before the old one is * completed. */ -static void -raw3215_mk_read_req(struct raw3215_info *raw) +static void raw3215_mk_read_req(struct raw3215_info *raw) { struct raw3215_req *req; struct ccw1 *ccw; @@ -174,8 +172,7 @@ raw3215_mk_read_req(struct raw3215_info *raw) * buffer to the 3215 device. If a queued write exists it is replaced by * the new, probably lengthened request. */ -static void -raw3215_mk_write_req(struct raw3215_info *raw) +static void raw3215_mk_write_req(struct raw3215_info *raw) { struct raw3215_req *req; struct ccw1 *ccw; @@ -251,8 +248,7 @@ raw3215_mk_write_req(struct raw3215_info *raw) /* * Start a read or a write request */ -static void -raw3215_start_io(struct raw3215_info *raw) +static void raw3215_start_io(struct raw3215_info *raw) { struct raw3215_req *req; int res; @@ -290,8 +286,7 @@ raw3215_start_io(struct raw3215_info *raw) /* * Function to start a delayed output after RAW3215_TIMEOUT seconds */ -static void -raw3215_timeout(unsigned long __data) +static void raw3215_timeout(unsigned long __data) { struct raw3215_info *raw = (struct raw3215_info *) __data; unsigned long flags; @@ -300,8 +295,10 @@ raw3215_timeout(unsigned long __data) if (raw->flags & RAW3215_TIMER_RUNS) { del_timer(&raw->timer); raw->flags &= ~RAW3215_TIMER_RUNS; - raw3215_mk_write_req(raw); - raw3215_start_io(raw); + if (!(raw->flags & RAW3215_FROZEN)) { + raw3215_mk_write_req(raw); + raw3215_start_io(raw); + } } spin_unlock_irqrestore(get_ccwdev_lock(raw->cdev), flags); } @@ -312,10 +309,9 @@ raw3215_timeout(unsigned long __data) * amount of data is bigger than RAW3215_MIN_WRITE. If a write is not * done immediately a timer is started with a delay of RAW3215_TIMEOUT. */ -static inline void -raw3215_try_io(struct raw3215_info *raw) +static inline void raw3215_try_io(struct raw3215_info *raw) { - if (!(raw->flags & RAW3215_ACTIVE)) + if (!(raw->flags & RAW3215_ACTIVE) || (raw->flags & RAW3215_FROZEN)) return; if (raw->queued_read != NULL) raw3215_start_io(raw); @@ -359,8 +355,8 @@ static void raw3215_next_io(struct raw3215_info *raw) /* * Interrupt routine, called from common io layer */ -static void -raw3215_irq(struct ccw_device *cdev, unsigned long intparm, struct irb *irb) +static void raw3215_irq(struct ccw_device *cdev, unsigned long intparm, + struct irb *irb) { struct raw3215_info *raw; struct raw3215_req *req; @@ -368,7 +364,7 @@ raw3215_irq(struct ccw_device *cdev, unsigned long intparm, struct irb *irb) int cstat, dstat; int count; - raw = cdev->dev.driver_data; + raw = dev_get_drvdata(&cdev->dev); req = (struct raw3215_req *) intparm; cstat = irb->scsw.cmd.cstat; dstat = irb->scsw.cmd.dstat; @@ -459,14 +455,40 @@ raw3215_irq(struct ccw_device *cdev, unsigned long intparm, struct irb *irb) } /* + * Drop the oldest line from the output buffer. + */ +static void raw3215_drop_line(struct raw3215_info *raw) +{ + int ix; + char ch; + + BUG_ON(raw->written != 0); + ix = (raw->head - raw->count) & (RAW3215_BUFFER_SIZE - 1); + while (raw->count > 0) { + ch = raw->buffer[ix]; + ix = (ix + 1) & (RAW3215_BUFFER_SIZE - 1); + raw->count--; + if (ch == 0x15) + break; + } + raw->head = ix; +} + +/* * Wait until length bytes are available int the output buffer. * Has to be called with the s390irq lock held. Can be called * disabled. */ -static void -raw3215_make_room(struct raw3215_info *raw, unsigned int length) +static void raw3215_make_room(struct raw3215_info *raw, unsigned int length) { while (RAW3215_BUFFER_SIZE - raw->count < length) { + /* While console is frozen for suspend we have no other + * choice but to drop message from the buffer to make + * room for even more messages. */ + if (raw->flags & RAW3215_FROZEN) { + raw3215_drop_line(raw); + continue; + } /* there might be a request pending */ raw->flags |= RAW3215_FLUSHING; raw3215_mk_write_req(raw); @@ -488,8 +510,8 @@ raw3215_make_room(struct raw3215_info *raw, unsigned int length) /* * String write routine for 3215 devices */ -static void -raw3215_write(struct raw3215_info *raw, const char *str, unsigned int length) +static void raw3215_write(struct raw3215_info *raw, const char *str, + unsigned int length) { unsigned long flags; int c, count; @@ -529,8 +551,7 @@ raw3215_write(struct raw3215_info *raw, const char *str, unsigned int length) /* * Put character routine for 3215 devices */ -static void -raw3215_putchar(struct raw3215_info *raw, unsigned char ch) +static void raw3215_putchar(struct raw3215_info *raw, unsigned char ch) { unsigned long flags; unsigned int length, i; @@ -566,8 +587,7 @@ raw3215_putchar(struct raw3215_info *raw, unsigned char ch) * Flush routine, it simply sets the flush flag and tries to start * pending IO. */ -static void -raw3215_flush_buffer(struct raw3215_info *raw) +static void raw3215_flush_buffer(struct raw3215_info *raw) { unsigned long flags; @@ -583,8 +603,7 @@ raw3215_flush_buffer(struct raw3215_info *raw) /* * Fire up a 3215 device. */ -static int -raw3215_startup(struct raw3215_info *raw) +static int raw3215_startup(struct raw3215_info *raw) { unsigned long flags; @@ -602,8 +621,7 @@ raw3215_startup(struct raw3215_info *raw) /* * Shutdown a 3215 device. */ -static void -raw3215_shutdown(struct raw3215_info *raw) +static void raw3215_shutdown(struct raw3215_info *raw) { DECLARE_WAITQUEUE(wait, current); unsigned long flags; @@ -628,14 +646,13 @@ raw3215_shutdown(struct raw3215_info *raw) spin_unlock_irqrestore(get_ccwdev_lock(raw->cdev), flags); } -static int -raw3215_probe (struct ccw_device *cdev) +static int raw3215_probe (struct ccw_device *cdev) { struct raw3215_info *raw; int line; /* Console is special. */ - if (raw3215[0] && (cdev->dev.driver_data == raw3215[0])) + if (raw3215[0] && (raw3215[0] == dev_get_drvdata(&cdev->dev))) return 0; raw = kmalloc(sizeof(struct raw3215_info) + RAW3215_INBUF_SIZE, GFP_KERNEL|GFP_DMA); @@ -669,44 +686,41 @@ raw3215_probe (struct ccw_device *cdev) } init_waitqueue_head(&raw->empty_wait); - cdev->dev.driver_data = raw; + dev_set_drvdata(&cdev->dev, raw); cdev->handler = raw3215_irq; return 0; } -static void -raw3215_remove (struct ccw_device *cdev) +static void raw3215_remove (struct ccw_device *cdev) { struct raw3215_info *raw; ccw_device_set_offline(cdev); - raw = cdev->dev.driver_data; + raw = dev_get_drvdata(&cdev->dev); if (raw) { - cdev->dev.driver_data = NULL; + dev_set_drvdata(&cdev->dev, NULL); kfree(raw->buffer); kfree(raw); } } -static int -raw3215_set_online (struct ccw_device *cdev) +static int raw3215_set_online (struct ccw_device *cdev) { struct raw3215_info *raw; - raw = cdev->dev.driver_data; + raw = dev_get_drvdata(&cdev->dev); if (!raw) return -ENODEV; return raw3215_startup(raw); } -static int -raw3215_set_offline (struct ccw_device *cdev) +static int raw3215_set_offline (struct ccw_device *cdev) { struct raw3215_info *raw; - raw = cdev->dev.driver_data; + raw = dev_get_drvdata(&cdev->dev); if (!raw) return -ENODEV; @@ -715,6 +729,36 @@ raw3215_set_offline (struct ccw_device *cdev) return 0; } +static int raw3215_pm_stop(struct ccw_device *cdev) +{ + struct raw3215_info *raw; + unsigned long flags; + + /* Empty the output buffer, then prevent new I/O. */ + raw = cdev->dev.driver_data; + spin_lock_irqsave(get_ccwdev_lock(raw->cdev), flags); + raw3215_make_room(raw, RAW3215_BUFFER_SIZE); + raw->flags |= RAW3215_FROZEN; + spin_unlock_irqrestore(get_ccwdev_lock(raw->cdev), flags); + return 0; +} + +static int raw3215_pm_start(struct ccw_device *cdev) +{ + struct raw3215_info *raw; + unsigned long flags; + + /* Allow I/O again and flush output buffer. */ + raw = cdev->dev.driver_data; + spin_lock_irqsave(get_ccwdev_lock(raw->cdev), flags); + raw->flags &= ~RAW3215_FROZEN; + raw->flags |= RAW3215_FLUSHING; + raw3215_try_io(raw); + raw->flags &= ~RAW3215_FLUSHING; + spin_unlock_irqrestore(get_ccwdev_lock(raw->cdev), flags); + return 0; +} + static struct ccw_device_id raw3215_id[] = { { CCW_DEVICE(0x3215, 0) }, { /* end of list */ }, @@ -728,14 +772,17 @@ static struct ccw_driver raw3215_ccw_driver = { .remove = &raw3215_remove, .set_online = &raw3215_set_online, .set_offline = &raw3215_set_offline, + .freeze = &raw3215_pm_stop, + .thaw = &raw3215_pm_start, + .restore = &raw3215_pm_start, }; #ifdef CONFIG_TN3215_CONSOLE /* * Write a string to the 3215 console */ -static void -con3215_write(struct console *co, const char *str, unsigned int count) +static void con3215_write(struct console *co, const char *str, + unsigned int count) { struct raw3215_info *raw; int i; @@ -768,13 +815,17 @@ static struct tty_driver *con3215_device(struct console *c, int *index) * panic() calls con3215_flush through a panic_notifier * before the system enters a disabled, endless loop. */ -static void -con3215_flush(void) +static void con3215_flush(void) { struct raw3215_info *raw; unsigned long flags; raw = raw3215[0]; /* console 3215 is the first one */ + if (raw->flags & RAW3215_FROZEN) + /* The console is still frozen for suspend. */ + if (ccw_device_force_console()) + /* Forcing didn't work, no panic message .. */ + return; spin_lock_irqsave(get_ccwdev_lock(raw->cdev), flags); raw3215_make_room(raw, RAW3215_BUFFER_SIZE); spin_unlock_irqrestore(get_ccwdev_lock(raw->cdev), flags); @@ -811,8 +862,7 @@ static struct console con3215 = { * 3215 console initialization code called from console_init(). * NOTE: This is called before kmalloc is available. */ -static int __init -con3215_init(void) +static int __init con3215_init(void) { struct ccw_device *cdev; struct raw3215_info *raw; @@ -848,7 +898,7 @@ con3215_init(void) raw->buffer = (char *) alloc_bootmem_low(RAW3215_BUFFER_SIZE); raw->inbuf = (char *) alloc_bootmem_low(RAW3215_INBUF_SIZE); raw->cdev = cdev; - cdev->dev.driver_data = raw; + dev_set_drvdata(&cdev->dev, raw); cdev->handler = raw3215_irq; raw->flags |= RAW3215_FIXED; @@ -875,8 +925,7 @@ console_initcall(con3215_init); * * This routine is called whenever a 3215 tty is opened. */ -static int -tty3215_open(struct tty_struct *tty, struct file * filp) +static int tty3215_open(struct tty_struct *tty, struct file * filp) { struct raw3215_info *raw; int retval, line; @@ -909,8 +958,7 @@ tty3215_open(struct tty_struct *tty, struct file * filp) * This routine is called when the 3215 tty is closed. We wait * for the remaining request to be completed. Then we clean up. */ -static void -tty3215_close(struct tty_struct *tty, struct file * filp) +static void tty3215_close(struct tty_struct *tty, struct file * filp) { struct raw3215_info *raw; @@ -927,8 +975,7 @@ tty3215_close(struct tty_struct *tty, struct file * filp) /* * Returns the amount of free space in the output buffer. */ -static int -tty3215_write_room(struct tty_struct *tty) +static int tty3215_write_room(struct tty_struct *tty) { struct raw3215_info *raw; @@ -944,9 +991,8 @@ tty3215_write_room(struct tty_struct *tty) /* * String write routine for 3215 ttys */ -static int -tty3215_write(struct tty_struct * tty, - const unsigned char *buf, int count) +static int tty3215_write(struct tty_struct * tty, + const unsigned char *buf, int count) { struct raw3215_info *raw; @@ -960,8 +1006,7 @@ tty3215_write(struct tty_struct * tty, /* * Put character routine for 3215 ttys */ -static int -tty3215_put_char(struct tty_struct *tty, unsigned char ch) +static int tty3215_put_char(struct tty_struct *tty, unsigned char ch) { struct raw3215_info *raw; @@ -972,16 +1017,14 @@ tty3215_put_char(struct tty_struct *tty, unsigned char ch) return 1; } -static void -tty3215_flush_chars(struct tty_struct *tty) +static void tty3215_flush_chars(struct tty_struct *tty) { } /* * Returns the number of characters in the output buffer */ -static int -tty3215_chars_in_buffer(struct tty_struct *tty) +static int tty3215_chars_in_buffer(struct tty_struct *tty) { struct raw3215_info *raw; @@ -989,8 +1032,7 @@ tty3215_chars_in_buffer(struct tty_struct *tty) return raw->count; } -static void -tty3215_flush_buffer(struct tty_struct *tty) +static void tty3215_flush_buffer(struct tty_struct *tty) { struct raw3215_info *raw; @@ -1002,9 +1044,8 @@ tty3215_flush_buffer(struct tty_struct *tty) /* * Currently we don't have any io controls for 3215 ttys */ -static int -tty3215_ioctl(struct tty_struct *tty, struct file * file, - unsigned int cmd, unsigned long arg) +static int tty3215_ioctl(struct tty_struct *tty, struct file * file, + unsigned int cmd, unsigned long arg) { if (tty->flags & (1 << TTY_IO_ERROR)) return -EIO; @@ -1019,8 +1060,7 @@ tty3215_ioctl(struct tty_struct *tty, struct file * file, /* * Disable reading from a 3215 tty */ -static void -tty3215_throttle(struct tty_struct * tty) +static void tty3215_throttle(struct tty_struct * tty) { struct raw3215_info *raw; @@ -1031,8 +1071,7 @@ tty3215_throttle(struct tty_struct * tty) /* * Enable reading from a 3215 tty */ -static void -tty3215_unthrottle(struct tty_struct * tty) +static void tty3215_unthrottle(struct tty_struct * tty) { struct raw3215_info *raw; unsigned long flags; @@ -1049,8 +1088,7 @@ tty3215_unthrottle(struct tty_struct * tty) /* * Disable writing to a 3215 tty */ -static void -tty3215_stop(struct tty_struct *tty) +static void tty3215_stop(struct tty_struct *tty) { struct raw3215_info *raw; @@ -1061,8 +1099,7 @@ tty3215_stop(struct tty_struct *tty) /* * Enable writing to a 3215 tty */ -static void -tty3215_start(struct tty_struct *tty) +static void tty3215_start(struct tty_struct *tty) { struct raw3215_info *raw; unsigned long flags; @@ -1096,8 +1133,7 @@ static const struct tty_operations tty3215_ops = { * 3215 tty registration code called from tty_init(). * Most kernel services (incl. kmalloc) are available at this poimt. */ -static int __init -tty3215_init(void) +static int __init tty3215_init(void) { struct tty_driver *driver; int ret; @@ -1142,8 +1178,7 @@ tty3215_init(void) return 0; } -static void __exit -tty3215_exit(void) +static void __exit tty3215_exit(void) { tty_unregister_driver(tty3215_driver); put_tty_driver(tty3215_driver); diff --git a/drivers/s390/char/con3270.c b/drivers/s390/char/con3270.c index ed5396dae58..44d02e371c0 100644 --- a/drivers/s390/char/con3270.c +++ b/drivers/s390/char/con3270.c @@ -1,11 +1,10 @@ /* - * drivers/s390/char/con3270.c - * IBM/3270 Driver - console view. + * IBM/3270 Driver - console view. * - * Author(s): - * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global) - * Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com> - * -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation + * Author(s): + * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global) + * Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com> + * Copyright IBM Corp. 2003, 2009 */ #include <linux/bootmem.h> @@ -530,6 +529,7 @@ con3270_flush(void) cp = condev; if (!cp->view.dev) return; + raw3270_pm_unfreeze(&cp->view); spin_lock_irqsave(&cp->view.lock, flags); con3270_wait_write(cp); cp->nr_up = 0; diff --git a/drivers/s390/char/fs3270.c b/drivers/s390/char/fs3270.c index 40759c33477..097d3846a82 100644 --- a/drivers/s390/char/fs3270.c +++ b/drivers/s390/char/fs3270.c @@ -1,11 +1,10 @@ /* - * drivers/s390/char/fs3270.c - * IBM/3270 Driver - fullscreen driver. + * IBM/3270 Driver - fullscreen driver. * - * Author(s): - * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global) - * Rewritten for 2.5/2.6 by Martin Schwidefsky <schwidefsky@de.ibm.com> - * -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation + * Author(s): + * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global) + * Rewritten for 2.5/2.6 by Martin Schwidefsky <schwidefsky@de.ibm.com> + * Copyright IBM Corp. 2003, 2009 */ #include <linux/bootmem.h> @@ -399,6 +398,11 @@ fs3270_free_view(struct raw3270_view *view) static void fs3270_release(struct raw3270_view *view) { + struct fs3270 *fp; + + fp = (struct fs3270 *) view; + if (fp->fs_pid) + kill_pid(fp->fs_pid, SIGHUP, 1); } /* View to a 3270 device. Can be console, tty or fullscreen. */ diff --git a/drivers/s390/char/monreader.c b/drivers/s390/char/monreader.c index 97e63cf4694..75a8831eebb 100644 --- a/drivers/s390/char/monreader.c +++ b/drivers/s390/char/monreader.c @@ -1,10 +1,9 @@ /* - * drivers/s390/char/monreader.c - * * Character device driver for reading z/VM *MONITOR service records. * - * Copyright IBM Corp. 2004, 2008 - * Author: Gerald Schaefer <gerald.schaefer@de.ibm.com> + * Copyright IBM Corp. 2004, 2009 + * + * Author: Gerald Schaefer <gerald.schaefer@de.ibm.com> */ #define KMSG_COMPONENT "monreader" @@ -22,6 +21,7 @@ #include <linux/spinlock.h> #include <linux/interrupt.h> #include <linux/poll.h> +#include <linux/device.h> #include <net/iucv/iucv.h> #include <asm/uaccess.h> #include <asm/ebcdic.h> @@ -78,6 +78,7 @@ static u8 user_data_sever[16] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }; +static struct device *monreader_device; /****************************************************************************** * helper functions * @@ -319,11 +320,12 @@ static int mon_open(struct inode *inode, struct file *filp) goto out_path; } filp->private_data = monpriv; + monreader_device->driver_data = monpriv; unlock_kernel(); return nonseekable_open(inode, filp); out_path: - kfree(monpriv->path); + iucv_path_free(monpriv->path); out_priv: mon_free_mem(monpriv); out_use: @@ -341,10 +343,13 @@ static int mon_close(struct inode *inode, struct file *filp) /* * Close IUCV connection and unregister */ - rc = iucv_path_sever(monpriv->path, user_data_sever); - if (rc) - pr_warning("Disconnecting the z/VM *MONITOR system service " - "failed with rc=%i\n", rc); + if (monpriv->path) { + rc = iucv_path_sever(monpriv->path, user_data_sever); + if (rc) + pr_warning("Disconnecting the z/VM *MONITOR system " + "service failed with rc=%i\n", rc); + iucv_path_free(monpriv->path); + } atomic_set(&monpriv->iucv_severed, 0); atomic_set(&monpriv->iucv_connected, 0); @@ -452,6 +457,94 @@ static struct miscdevice mon_dev = { .minor = MISC_DYNAMIC_MINOR, }; + +/****************************************************************************** + * suspend / resume * + *****************************************************************************/ +static int monreader_freeze(struct device *dev) +{ + struct mon_private *monpriv = dev->driver_data; + int rc; + + if (!monpriv) + return 0; + if (monpriv->path) { + rc = iucv_path_sever(monpriv->path, user_data_sever); + if (rc) + pr_warning("Disconnecting the z/VM *MONITOR system " + "service failed with rc=%i\n", rc); + iucv_path_free(monpriv->path); + } + atomic_set(&monpriv->iucv_severed, 0); + atomic_set(&monpriv->iucv_connected, 0); + atomic_set(&monpriv->read_ready, 0); + atomic_set(&monpriv->msglim_count, 0); + monpriv->write_index = 0; + monpriv->read_index = 0; + monpriv->path = NULL; + return 0; +} + +static int monreader_thaw(struct device *dev) +{ + struct mon_private *monpriv = dev->driver_data; + int rc; + + if (!monpriv) + return 0; + rc = -ENOMEM; + monpriv->path = iucv_path_alloc(MON_MSGLIM, IUCV_IPRMDATA, GFP_KERNEL); + if (!monpriv->path) + goto out; + rc = iucv_path_connect(monpriv->path, &monreader_iucv_handler, + MON_SERVICE, NULL, user_data_connect, monpriv); + if (rc) { + pr_err("Connecting to the z/VM *MONITOR system service " + "failed with rc=%i\n", rc); + goto out_path; + } + wait_event(mon_conn_wait_queue, + atomic_read(&monpriv->iucv_connected) || + atomic_read(&monpriv->iucv_severed)); + if (atomic_read(&monpriv->iucv_severed)) + goto out_path; + return 0; +out_path: + rc = -EIO; + iucv_path_free(monpriv->path); + monpriv->path = NULL; +out: + atomic_set(&monpriv->iucv_severed, 1); + return rc; +} + +static int monreader_restore(struct device *dev) +{ + int rc; + + segment_unload(mon_dcss_name); + rc = segment_load(mon_dcss_name, SEGMENT_SHARED, + &mon_dcss_start, &mon_dcss_end); + if (rc < 0) { + segment_warning(rc, mon_dcss_name); + panic("fatal monreader resume error: no monitor dcss\n"); + } + return monreader_thaw(dev); +} + +static struct dev_pm_ops monreader_pm_ops = { + .freeze = monreader_freeze, + .thaw = monreader_thaw, + .restore = monreader_restore, +}; + +static struct device_driver monreader_driver = { + .name = "monreader", + .bus = &iucv_bus, + .pm = &monreader_pm_ops, +}; + + /****************************************************************************** * module init/exit * *****************************************************************************/ @@ -475,16 +568,33 @@ static int __init mon_init(void) return rc; } + rc = driver_register(&monreader_driver); + if (rc) + goto out_iucv; + monreader_device = kzalloc(sizeof(struct device), GFP_KERNEL); + if (!monreader_device) + goto out_driver; + dev_set_name(monreader_device, "monreader-dev"); + monreader_device->bus = &iucv_bus; + monreader_device->parent = iucv_root; + monreader_device->driver = &monreader_driver; + monreader_device->release = (void (*)(struct device *))kfree; + rc = device_register(monreader_device); + if (rc) { + kfree(monreader_device); + goto out_driver; + } + rc = segment_type(mon_dcss_name); if (rc < 0) { segment_warning(rc, mon_dcss_name); - goto out_iucv; + goto out_device; } if (rc != SEG_TYPE_SC) { pr_err("The specified *MONITOR DCSS %s does not have the " "required type SC\n", mon_dcss_name); rc = -EINVAL; - goto out_iucv; + goto out_device; } rc = segment_load(mon_dcss_name, SEGMENT_SHARED, @@ -492,7 +602,7 @@ static int __init mon_init(void) if (rc < 0) { segment_warning(rc, mon_dcss_name); rc = -EINVAL; - goto out_iucv; + goto out_device; } dcss_mkname(mon_dcss_name, &user_data_connect[8]); @@ -503,6 +613,10 @@ static int __init mon_init(void) out: segment_unload(mon_dcss_name); +out_device: + device_unregister(monreader_device); +out_driver: + driver_unregister(&monreader_driver); out_iucv: iucv_unregister(&monreader_iucv_handler, 1); return rc; @@ -512,6 +626,8 @@ static void __exit mon_exit(void) { segment_unload(mon_dcss_name); WARN_ON(misc_deregister(&mon_dev) != 0); + device_unregister(monreader_device); + driver_unregister(&monreader_driver); iucv_unregister(&monreader_iucv_handler, 1); return; } diff --git a/drivers/s390/char/monwriter.c b/drivers/s390/char/monwriter.c index c7d7483bab9..66fb8eba93f 100644 --- a/drivers/s390/char/monwriter.c +++ b/drivers/s390/char/monwriter.c @@ -1,9 +1,7 @@ /* - * drivers/s390/char/monwriter.c - * * Character device driver for writing z/VM *MONITOR service records. * - * Copyright (C) IBM Corp. 2006 + * Copyright IBM Corp. 2006, 2009 * * Author(s): Melissa Howland <Melissa.Howland@us.ibm.com> */ @@ -22,6 +20,7 @@ #include <linux/ctype.h> #include <linux/poll.h> #include <linux/mutex.h> +#include <linux/platform_device.h> #include <asm/uaccess.h> #include <asm/ebcdic.h> #include <asm/io.h> @@ -40,7 +39,10 @@ struct mon_buf { char *data; }; +static LIST_HEAD(mon_priv_list); + struct mon_private { + struct list_head priv_list; struct list_head list; struct monwrite_hdr hdr; size_t hdr_to_read; @@ -188,6 +190,7 @@ static int monwrite_open(struct inode *inode, struct file *filp) monpriv->hdr_to_read = sizeof(monpriv->hdr); mutex_init(&monpriv->thread_mutex); filp->private_data = monpriv; + list_add_tail(&monpriv->priv_list, &mon_priv_list); unlock_kernel(); return nonseekable_open(inode, filp); } @@ -206,6 +209,7 @@ static int monwrite_close(struct inode *inode, struct file *filp) kfree(entry->data); kfree(entry); } + list_del(&monpriv->priv_list); kfree(monpriv); return 0; } @@ -281,20 +285,102 @@ static struct miscdevice mon_dev = { }; /* + * suspend/resume + */ + +static int monwriter_freeze(struct device *dev) +{ + struct mon_private *monpriv; + struct mon_buf *monbuf; + + list_for_each_entry(monpriv, &mon_priv_list, priv_list) { + list_for_each_entry(monbuf, &monpriv->list, list) { + if (monbuf->hdr.mon_function != MONWRITE_GEN_EVENT) + monwrite_diag(&monbuf->hdr, monbuf->data, + APPLDATA_STOP_REC); + } + } + return 0; +} + +static int monwriter_restore(struct device *dev) +{ + struct mon_private *monpriv; + struct mon_buf *monbuf; + + list_for_each_entry(monpriv, &mon_priv_list, priv_list) { + list_for_each_entry(monbuf, &monpriv->list, list) { + if (monbuf->hdr.mon_function == MONWRITE_START_INTERVAL) + monwrite_diag(&monbuf->hdr, monbuf->data, + APPLDATA_START_INTERVAL_REC); + if (monbuf->hdr.mon_function == MONWRITE_START_CONFIG) + monwrite_diag(&monbuf->hdr, monbuf->data, + APPLDATA_START_CONFIG_REC); + } + } + return 0; +} + +static int monwriter_thaw(struct device *dev) +{ + return monwriter_restore(dev); +} + +static struct dev_pm_ops monwriter_pm_ops = { + .freeze = monwriter_freeze, + .thaw = monwriter_thaw, + .restore = monwriter_restore, +}; + +static struct platform_driver monwriter_pdrv = { + .driver = { + .name = "monwriter", + .owner = THIS_MODULE, + .pm = &monwriter_pm_ops, + }, +}; + +static struct platform_device *monwriter_pdev; + +/* * module init/exit */ static int __init mon_init(void) { - if (MACHINE_IS_VM) - return misc_register(&mon_dev); - else + int rc; + + if (!MACHINE_IS_VM) return -ENODEV; + + rc = platform_driver_register(&monwriter_pdrv); + if (rc) + return rc; + + monwriter_pdev = platform_device_register_simple("monwriter", -1, NULL, + 0); + if (IS_ERR(monwriter_pdev)) { + rc = PTR_ERR(monwriter_pdev); + goto out_driver; + } + + rc = misc_register(&mon_dev); + if (rc) + goto out_device; + return 0; + +out_device: + platform_device_unregister(monwriter_pdev); +out_driver: + platform_driver_unregister(&monwriter_pdrv); + return rc; } static void __exit mon_exit(void) { WARN_ON(misc_deregister(&mon_dev) != 0); + platform_device_unregister(monwriter_pdev); + platform_driver_unregister(&monwriter_pdrv); } module_init(mon_init); diff --git a/drivers/s390/char/raw3270.c b/drivers/s390/char/raw3270.c index 0b15cf107ec..acab7b2dfe8 100644 --- a/drivers/s390/char/raw3270.c +++ b/drivers/s390/char/raw3270.c @@ -1,11 +1,10 @@ /* - * drivers/s390/char/raw3270.c - * IBM/3270 Driver - core functions. + * IBM/3270 Driver - core functions. * - * Author(s): - * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global) - * Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com> - * -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation + * Author(s): + * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global) + * Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com> + * Copyright IBM Corp. 2003, 2009 */ #include <linux/bootmem.h> @@ -61,6 +60,7 @@ struct raw3270 { #define RAW3270_FLAGS_ATTN 2 /* Device sent an ATTN interrupt */ #define RAW3270_FLAGS_READY 4 /* Device is useable by views */ #define RAW3270_FLAGS_CONSOLE 8 /* Device is the console. */ +#define RAW3270_FLAGS_FROZEN 16 /* set if 3270 is frozen for suspend */ /* Semaphore to protect global data of raw3270 (devices, views, etc). */ static DEFINE_MUTEX(raw3270_mutex); @@ -306,7 +306,8 @@ raw3270_start(struct raw3270_view *view, struct raw3270_request *rq) spin_lock_irqsave(get_ccwdev_lock(view->dev->cdev), flags); rp = view->dev; - if (!rp || rp->view != view) + if (!rp || rp->view != view || + test_bit(RAW3270_FLAGS_FROZEN, &rp->flags)) rc = -EACCES; else if (!test_bit(RAW3270_FLAGS_READY, &rp->flags)) rc = -ENODEV; @@ -323,7 +324,8 @@ raw3270_start_locked(struct raw3270_view *view, struct raw3270_request *rq) int rc; rp = view->dev; - if (!rp || rp->view != view) + if (!rp || rp->view != view || + test_bit(RAW3270_FLAGS_FROZEN, &rp->flags)) rc = -EACCES; else if (!test_bit(RAW3270_FLAGS_READY, &rp->flags)) rc = -ENODEV; @@ -355,7 +357,7 @@ raw3270_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb) struct raw3270_request *rq; int rc; - rp = (struct raw3270 *) cdev->dev.driver_data; + rp = dev_get_drvdata(&cdev->dev); if (!rp) return; rq = (struct raw3270_request *) intparm; @@ -764,7 +766,8 @@ raw3270_reset(struct raw3270_view *view) int rc; rp = view->dev; - if (!rp || rp->view != view) + if (!rp || rp->view != view || + test_bit(RAW3270_FLAGS_FROZEN, &rp->flags)) rc = -EACCES; else if (!test_bit(RAW3270_FLAGS_READY, &rp->flags)) rc = -ENODEV; @@ -828,7 +831,7 @@ raw3270_setup_device(struct ccw_device *cdev, struct raw3270 *rp, char *ascebc) if (rp->minor == -1) return -EUSERS; rp->cdev = cdev; - cdev->dev.driver_data = rp; + dev_set_drvdata(&cdev->dev, rp); cdev->handler = raw3270_irq; return 0; } @@ -922,6 +925,8 @@ raw3270_activate_view(struct raw3270_view *view) rc = 0; else if (!test_bit(RAW3270_FLAGS_READY, &rp->flags)) rc = -ENODEV; + else if (test_bit(RAW3270_FLAGS_FROZEN, &rp->flags)) + rc = -EACCES; else { oldview = NULL; if (rp->view) { @@ -969,7 +974,8 @@ raw3270_deactivate_view(struct raw3270_view *view) list_del_init(&view->list); list_add_tail(&view->list, &rp->view_list); /* Try to activate another view. */ - if (test_bit(RAW3270_FLAGS_READY, &rp->flags)) { + if (test_bit(RAW3270_FLAGS_READY, &rp->flags) && + !test_bit(RAW3270_FLAGS_FROZEN, &rp->flags)) { list_for_each_entry(view, &rp->view_list, list) { rp->view = view; if (view->fn->activate(view) == 0) @@ -1068,7 +1074,8 @@ raw3270_del_view(struct raw3270_view *view) rp->view = NULL; } list_del_init(&view->list); - if (!rp->view && test_bit(RAW3270_FLAGS_READY, &rp->flags)) { + if (!rp->view && test_bit(RAW3270_FLAGS_READY, &rp->flags) && + !test_bit(RAW3270_FLAGS_FROZEN, &rp->flags)) { /* Try to activate another view. */ list_for_each_entry(nv, &rp->view_list, list) { if (nv->fn->activate(nv) == 0) { @@ -1105,7 +1112,7 @@ raw3270_delete_device(struct raw3270 *rp) /* Disconnect from ccw_device. */ cdev = rp->cdev; rp->cdev = NULL; - cdev->dev.driver_data = NULL; + dev_set_drvdata(&cdev->dev, NULL); cdev->handler = NULL; /* Put ccw_device structure. */ @@ -1129,7 +1136,7 @@ static ssize_t raw3270_model_show(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%i\n", - ((struct raw3270 *) dev->driver_data)->model); + ((struct raw3270 *) dev_get_drvdata(dev))->model); } static DEVICE_ATTR(model, 0444, raw3270_model_show, NULL); @@ -1137,7 +1144,7 @@ static ssize_t raw3270_rows_show(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%i\n", - ((struct raw3270 *) dev->driver_data)->rows); + ((struct raw3270 *) dev_get_drvdata(dev))->rows); } static DEVICE_ATTR(rows, 0444, raw3270_rows_show, NULL); @@ -1145,7 +1152,7 @@ static ssize_t raw3270_columns_show(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%i\n", - ((struct raw3270 *) dev->driver_data)->cols); + ((struct raw3270 *) dev_get_drvdata(dev))->cols); } static DEVICE_ATTR(columns, 0444, raw3270_columns_show, NULL); @@ -1282,7 +1289,7 @@ raw3270_remove (struct ccw_device *cdev) struct raw3270_view *v; struct raw3270_notifier *np; - rp = cdev->dev.driver_data; + rp = dev_get_drvdata(&cdev->dev); /* * _remove is the opposite of _probe; it's probe that * should set up rp. raw3270_remove gets entered for @@ -1330,13 +1337,65 @@ raw3270_set_offline (struct ccw_device *cdev) { struct raw3270 *rp; - rp = cdev->dev.driver_data; + rp = dev_get_drvdata(&cdev->dev); if (test_bit(RAW3270_FLAGS_CONSOLE, &rp->flags)) return -EBUSY; raw3270_remove(cdev); return 0; } +static int raw3270_pm_stop(struct ccw_device *cdev) +{ + struct raw3270 *rp; + struct raw3270_view *view; + unsigned long flags; + + rp = cdev->dev.driver_data; + if (!rp) + return 0; + spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags); + if (rp->view) + rp->view->fn->deactivate(rp->view); + if (!test_bit(RAW3270_FLAGS_CONSOLE, &rp->flags)) { + /* + * Release tty and fullscreen for all non-console + * devices. + */ + list_for_each_entry(view, &rp->view_list, list) { + if (view->fn->release) + view->fn->release(view); + } + } + set_bit(RAW3270_FLAGS_FROZEN, &rp->flags); + spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags); + return 0; +} + +static int raw3270_pm_start(struct ccw_device *cdev) +{ + struct raw3270 *rp; + unsigned long flags; + + rp = cdev->dev.driver_data; + if (!rp) + return 0; + spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags); + clear_bit(RAW3270_FLAGS_FROZEN, &rp->flags); + if (rp->view) + rp->view->fn->activate(rp->view); + spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags); + return 0; +} + +void raw3270_pm_unfreeze(struct raw3270_view *view) +{ + struct raw3270 *rp; + + rp = view->dev; + if (rp && test_bit(RAW3270_FLAGS_FROZEN, &rp->flags)) + ccw_device_force_console(); +} + static struct ccw_device_id raw3270_id[] = { { CCW_DEVICE(0x3270, 0) }, { CCW_DEVICE(0x3271, 0) }, @@ -1360,6 +1419,9 @@ static struct ccw_driver raw3270_ccw_driver = { .remove = &raw3270_remove, .set_online = &raw3270_set_online, .set_offline = &raw3270_set_offline, + .freeze = &raw3270_pm_stop, + .thaw = &raw3270_pm_start, + .restore = &raw3270_pm_start, }; static int diff --git a/drivers/s390/char/raw3270.h b/drivers/s390/char/raw3270.h index 90beaa80a78..ed34eb2199c 100644 --- a/drivers/s390/char/raw3270.h +++ b/drivers/s390/char/raw3270.h @@ -1,11 +1,10 @@ /* - * drivers/s390/char/raw3270.h - * IBM/3270 Driver + * IBM/3270 Driver * - * Author(s): - * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global) - * Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com> - * -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation + * Author(s): + * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global) + * Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com> + * Copyright IBM Corp. 2003, 2009 */ #include <asm/idals.h> @@ -195,6 +194,7 @@ void raw3270_wait_cons_dev(struct raw3270 *); /* Notifier for device addition/removal */ int raw3270_register_notifier(void (*notifier)(int, int)); void raw3270_unregister_notifier(void (*notifier)(int, int)); +void raw3270_pm_unfreeze(struct raw3270_view *); /* * Little memory allocator for string objects. diff --git a/drivers/s390/char/sclp.c b/drivers/s390/char/sclp.c index 4377e93a43d..a983f508678 100644 --- a/drivers/s390/char/sclp.c +++ b/drivers/s390/char/sclp.c @@ -1,11 +1,10 @@ /* - * drivers/s390/char/sclp.c - * core function to access sclp interface + * core function to access sclp interface * - * S390 version - * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation - * Author(s): Martin Peschke <mpeschke@de.ibm.com> - * Martin Schwidefsky <schwidefsky@de.ibm.com> + * Copyright IBM Corp. 1999, 2009 + * + * Author(s): Martin Peschke <mpeschke@de.ibm.com> + * Martin Schwidefsky <schwidefsky@de.ibm.com> */ #include <linux/module.h> @@ -16,6 +15,9 @@ #include <linux/reboot.h> #include <linux/jiffies.h> #include <linux/init.h> +#include <linux/suspend.h> +#include <linux/completion.h> +#include <linux/platform_device.h> #include <asm/types.h> #include <asm/s390_ext.h> @@ -47,6 +49,16 @@ static struct sclp_req sclp_init_req; static char sclp_read_sccb[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE))); static char sclp_init_sccb[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE))); +/* Suspend request */ +static DECLARE_COMPLETION(sclp_request_queue_flushed); + +static void sclp_suspend_req_cb(struct sclp_req *req, void *data) +{ + complete(&sclp_request_queue_flushed); +} + +static struct sclp_req sclp_suspend_req; + /* Timer for request retries. */ static struct timer_list sclp_request_timer; @@ -84,6 +96,12 @@ static volatile enum sclp_mask_state_t { sclp_mask_state_initializing } sclp_mask_state = sclp_mask_state_idle; +/* Internal state: is the driver suspended? */ +static enum sclp_suspend_state_t { + sclp_suspend_state_running, + sclp_suspend_state_suspended, +} sclp_suspend_state = sclp_suspend_state_running; + /* Maximum retry counts */ #define SCLP_INIT_RETRY 3 #define SCLP_MASK_RETRY 3 @@ -211,6 +229,8 @@ sclp_process_queue(void) del_timer(&sclp_request_timer); while (!list_empty(&sclp_req_queue)) { req = list_entry(sclp_req_queue.next, struct sclp_req, list); + if (!req->sccb) + goto do_post; rc = __sclp_start_request(req); if (rc == 0) break; @@ -222,6 +242,7 @@ sclp_process_queue(void) sclp_request_timeout, 0); break; } +do_post: /* Post-processing for aborted request */ list_del(&req->list); if (req->callback) { @@ -233,6 +254,19 @@ sclp_process_queue(void) spin_unlock_irqrestore(&sclp_lock, flags); } +static int __sclp_can_add_request(struct sclp_req *req) +{ + if (req == &sclp_suspend_req || req == &sclp_init_req) + return 1; + if (sclp_suspend_state != sclp_suspend_state_running) + return 0; + if (sclp_init_state != sclp_init_state_initialized) + return 0; + if (sclp_activation_state != sclp_activation_state_active) + return 0; + return 1; +} + /* Queue a new request. Return zero on success, non-zero otherwise. */ int sclp_add_request(struct sclp_req *req) @@ -241,9 +275,7 @@ sclp_add_request(struct sclp_req *req) int rc; spin_lock_irqsave(&sclp_lock, flags); - if ((sclp_init_state != sclp_init_state_initialized || - sclp_activation_state != sclp_activation_state_active) && - req != &sclp_init_req) { + if (!__sclp_can_add_request(req)) { spin_unlock_irqrestore(&sclp_lock, flags); return -EIO; } @@ -254,10 +286,16 @@ sclp_add_request(struct sclp_req *req) /* Start if request is first in list */ if (sclp_running_state == sclp_running_state_idle && req->list.prev == &sclp_req_queue) { + if (!req->sccb) { + list_del(&req->list); + rc = -ENODATA; + goto out; + } rc = __sclp_start_request(req); if (rc) list_del(&req->list); } +out: spin_unlock_irqrestore(&sclp_lock, flags); return rc; } @@ -560,6 +598,7 @@ sclp_register(struct sclp_register *reg) /* Trigger initial state change callback */ reg->sclp_receive_mask = 0; reg->sclp_send_mask = 0; + reg->pm_event_posted = 0; list_add(®->list, &sclp_reg_list); spin_unlock_irqrestore(&sclp_lock, flags); rc = sclp_init_mask(1); @@ -880,20 +919,134 @@ static struct notifier_block sclp_reboot_notifier = { .notifier_call = sclp_reboot_event }; +/* + * Suspend/resume SCLP notifier implementation + */ + +static void sclp_pm_event(enum sclp_pm_event sclp_pm_event, int rollback) +{ + struct sclp_register *reg; + unsigned long flags; + + if (!rollback) { + spin_lock_irqsave(&sclp_lock, flags); + list_for_each_entry(reg, &sclp_reg_list, list) + reg->pm_event_posted = 0; + spin_unlock_irqrestore(&sclp_lock, flags); + } + do { + spin_lock_irqsave(&sclp_lock, flags); + list_for_each_entry(reg, &sclp_reg_list, list) { + if (rollback && reg->pm_event_posted) + goto found; + if (!rollback && !reg->pm_event_posted) + goto found; + } + spin_unlock_irqrestore(&sclp_lock, flags); + return; +found: + spin_unlock_irqrestore(&sclp_lock, flags); + if (reg->pm_event_fn) + reg->pm_event_fn(reg, sclp_pm_event); + reg->pm_event_posted = rollback ? 0 : 1; + } while (1); +} + +/* + * Susend/resume callbacks for platform device + */ + +static int sclp_freeze(struct device *dev) +{ + unsigned long flags; + int rc; + + sclp_pm_event(SCLP_PM_EVENT_FREEZE, 0); + + spin_lock_irqsave(&sclp_lock, flags); + sclp_suspend_state = sclp_suspend_state_suspended; + spin_unlock_irqrestore(&sclp_lock, flags); + + /* Init supend data */ + memset(&sclp_suspend_req, 0, sizeof(sclp_suspend_req)); + sclp_suspend_req.callback = sclp_suspend_req_cb; + sclp_suspend_req.status = SCLP_REQ_FILLED; + init_completion(&sclp_request_queue_flushed); + + rc = sclp_add_request(&sclp_suspend_req); + if (rc == 0) + wait_for_completion(&sclp_request_queue_flushed); + else if (rc != -ENODATA) + goto fail_thaw; + + rc = sclp_deactivate(); + if (rc) + goto fail_thaw; + return 0; + +fail_thaw: + spin_lock_irqsave(&sclp_lock, flags); + sclp_suspend_state = sclp_suspend_state_running; + spin_unlock_irqrestore(&sclp_lock, flags); + sclp_pm_event(SCLP_PM_EVENT_THAW, 1); + return rc; +} + +static int sclp_undo_suspend(enum sclp_pm_event event) +{ + unsigned long flags; + int rc; + + rc = sclp_reactivate(); + if (rc) + return rc; + + spin_lock_irqsave(&sclp_lock, flags); + sclp_suspend_state = sclp_suspend_state_running; + spin_unlock_irqrestore(&sclp_lock, flags); + + sclp_pm_event(event, 0); + return 0; +} + +static int sclp_thaw(struct device *dev) +{ + return sclp_undo_suspend(SCLP_PM_EVENT_THAW); +} + +static int sclp_restore(struct device *dev) +{ + return sclp_undo_suspend(SCLP_PM_EVENT_RESTORE); +} + +static struct dev_pm_ops sclp_pm_ops = { + .freeze = sclp_freeze, + .thaw = sclp_thaw, + .restore = sclp_restore, +}; + +static struct platform_driver sclp_pdrv = { + .driver = { + .name = "sclp", + .owner = THIS_MODULE, + .pm = &sclp_pm_ops, + }, +}; + +static struct platform_device *sclp_pdev; + /* Initialize SCLP driver. Return zero if driver is operational, non-zero * otherwise. */ static int sclp_init(void) { unsigned long flags; - int rc; + int rc = 0; spin_lock_irqsave(&sclp_lock, flags); /* Check for previous or running initialization */ - if (sclp_init_state != sclp_init_state_uninitialized) { - spin_unlock_irqrestore(&sclp_lock, flags); - return 0; - } + if (sclp_init_state != sclp_init_state_uninitialized) + goto fail_unlock; sclp_init_state = sclp_init_state_initializing; /* Set up variables */ INIT_LIST_HEAD(&sclp_req_queue); @@ -904,27 +1057,17 @@ sclp_init(void) spin_unlock_irqrestore(&sclp_lock, flags); rc = sclp_check_interface(); spin_lock_irqsave(&sclp_lock, flags); - if (rc) { - sclp_init_state = sclp_init_state_uninitialized; - spin_unlock_irqrestore(&sclp_lock, flags); - return rc; - } + if (rc) + goto fail_init_state_uninitialized; /* Register reboot handler */ rc = register_reboot_notifier(&sclp_reboot_notifier); - if (rc) { - sclp_init_state = sclp_init_state_uninitialized; - spin_unlock_irqrestore(&sclp_lock, flags); - return rc; - } + if (rc) + goto fail_init_state_uninitialized; /* Register interrupt handler */ rc = register_early_external_interrupt(0x2401, sclp_interrupt_handler, &ext_int_info_hwc); - if (rc) { - unregister_reboot_notifier(&sclp_reboot_notifier); - sclp_init_state = sclp_init_state_uninitialized; - spin_unlock_irqrestore(&sclp_lock, flags); - return rc; - } + if (rc) + goto fail_unregister_reboot_notifier; sclp_init_state = sclp_init_state_initialized; spin_unlock_irqrestore(&sclp_lock, flags); /* Enable service-signal external interruption - needs to happen with @@ -932,11 +1075,56 @@ sclp_init(void) ctl_set_bit(0, 9); sclp_init_mask(1); return 0; + +fail_unregister_reboot_notifier: + unregister_reboot_notifier(&sclp_reboot_notifier); +fail_init_state_uninitialized: + sclp_init_state = sclp_init_state_uninitialized; +fail_unlock: + spin_unlock_irqrestore(&sclp_lock, flags); + return rc; } +/* + * SCLP panic notifier: If we are suspended, we thaw SCLP in order to be able + * to print the panic message. + */ +static int sclp_panic_notify(struct notifier_block *self, + unsigned long event, void *data) +{ + if (sclp_suspend_state == sclp_suspend_state_suspended) + sclp_undo_suspend(SCLP_PM_EVENT_THAW); + return NOTIFY_OK; +} + +static struct notifier_block sclp_on_panic_nb = { + .notifier_call = sclp_panic_notify, + .priority = SCLP_PANIC_PRIO, +}; + static __init int sclp_initcall(void) { + int rc; + + rc = platform_driver_register(&sclp_pdrv); + if (rc) + return rc; + sclp_pdev = platform_device_register_simple("sclp", -1, NULL, 0); + rc = IS_ERR(sclp_pdev) ? PTR_ERR(sclp_pdev) : 0; + if (rc) + goto fail_platform_driver_unregister; + rc = atomic_notifier_chain_register(&panic_notifier_list, + &sclp_on_panic_nb); + if (rc) + goto fail_platform_device_unregister; + return sclp_init(); + +fail_platform_device_unregister: + platform_device_unregister(sclp_pdev); +fail_platform_driver_unregister: + platform_driver_unregister(&sclp_pdrv); + return rc; } arch_initcall(sclp_initcall); diff --git a/drivers/s390/char/sclp.h b/drivers/s390/char/sclp.h index bac80e856f9..60e7cb07095 100644 --- a/drivers/s390/char/sclp.h +++ b/drivers/s390/char/sclp.h @@ -1,10 +1,8 @@ /* - * drivers/s390/char/sclp.h + * Copyright IBM Corp. 1999, 2009 * - * S390 version - * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation - * Author(s): Martin Peschke <mpeschke@de.ibm.com> - * Martin Schwidefsky <schwidefsky@de.ibm.com> + * Author(s): Martin Peschke <mpeschke@de.ibm.com> + * Martin Schwidefsky <schwidefsky@de.ibm.com> */ #ifndef __SCLP_H__ @@ -17,7 +15,7 @@ /* maximum number of pages concerning our own memory management */ #define MAX_KMEM_PAGES (sizeof(unsigned long) << 3) -#define MAX_CONSOLE_PAGES 4 +#define MAX_CONSOLE_PAGES 6 #define EVTYP_OPCMD 0x01 #define EVTYP_MSG 0x02 @@ -68,6 +66,15 @@ typedef unsigned int sclp_cmdw_t; #define GDS_KEY_SELFDEFTEXTMSG 0x31 +enum sclp_pm_event { + SCLP_PM_EVENT_FREEZE, + SCLP_PM_EVENT_THAW, + SCLP_PM_EVENT_RESTORE, +}; + +#define SCLP_PANIC_PRIO 1 +#define SCLP_PANIC_PRIO_CLIENT 0 + typedef u32 sccb_mask_t; /* ATTENTION: assumes 32bit mask !!! */ struct sccb_header { @@ -134,6 +141,10 @@ struct sclp_register { void (*state_change_fn)(struct sclp_register *); /* called for events in cp_receive_mask/sclp_receive_mask */ void (*receiver_fn)(struct evbuf_header *); + /* called for power management events */ + void (*pm_event_fn)(struct sclp_register *, enum sclp_pm_event); + /* pm event posted flag */ + int pm_event_posted; }; /* externals from sclp.c */ diff --git a/drivers/s390/char/sclp_cmd.c b/drivers/s390/char/sclp_cmd.c index 77ab6e34a10..5cc11c636d3 100644 --- a/drivers/s390/char/sclp_cmd.c +++ b/drivers/s390/char/sclp_cmd.c @@ -1,9 +1,8 @@ /* - * drivers/s390/char/sclp_cmd.c + * Copyright IBM Corp. 2007, 2009 * - * Copyright IBM Corp. 2007 - * Author(s): Heiko Carstens <heiko.carstens@de.ibm.com>, - * Peter Oberparleiter <peter.oberparleiter@de.ibm.com> + * Author(s): Heiko Carstens <heiko.carstens@de.ibm.com>, + * Peter Oberparleiter <peter.oberparleiter@de.ibm.com> */ #define KMSG_COMPONENT "sclp_cmd" @@ -12,11 +11,13 @@ #include <linux/completion.h> #include <linux/init.h> #include <linux/errno.h> +#include <linux/err.h> #include <linux/slab.h> #include <linux/string.h> #include <linux/mm.h> #include <linux/mmzone.h> #include <linux/memory.h> +#include <linux/platform_device.h> #include <asm/chpid.h> #include <asm/sclp.h> #include <asm/setup.h> @@ -292,6 +293,7 @@ static DEFINE_MUTEX(sclp_mem_mutex); static LIST_HEAD(sclp_mem_list); static u8 sclp_max_storage_id; static unsigned long sclp_storage_ids[256 / BITS_PER_LONG]; +static int sclp_mem_state_changed; struct memory_increment { struct list_head list; @@ -450,6 +452,8 @@ static int sclp_mem_notifier(struct notifier_block *nb, rc = -EINVAL; break; } + if (!rc) + sclp_mem_state_changed = 1; mutex_unlock(&sclp_mem_mutex); return rc ? NOTIFY_BAD : NOTIFY_OK; } @@ -525,6 +529,14 @@ static void __init insert_increment(u16 rn, int standby, int assigned) list_add(&new_incr->list, prev); } +static int sclp_mem_freeze(struct device *dev) +{ + if (!sclp_mem_state_changed) + return 0; + pr_err("Memory hotplug state changed, suspend refused.\n"); + return -EPERM; +} + struct read_storage_sccb { struct sccb_header header; u16 max_id; @@ -534,8 +546,20 @@ struct read_storage_sccb { u32 entries[0]; } __packed; +static struct dev_pm_ops sclp_mem_pm_ops = { + .freeze = sclp_mem_freeze, +}; + +static struct platform_driver sclp_mem_pdrv = { + .driver = { + .name = "sclp_mem", + .pm = &sclp_mem_pm_ops, + }, +}; + static int __init sclp_detect_standby_memory(void) { + struct platform_device *sclp_pdev; struct read_storage_sccb *sccb; int i, id, assigned, rc; @@ -588,7 +612,17 @@ static int __init sclp_detect_standby_memory(void) rc = register_memory_notifier(&sclp_mem_nb); if (rc) goto out; + rc = platform_driver_register(&sclp_mem_pdrv); + if (rc) + goto out; + sclp_pdev = platform_device_register_simple("sclp_mem", -1, NULL, 0); + rc = IS_ERR(sclp_pdev) ? PTR_ERR(sclp_pdev) : 0; + if (rc) + goto out_driver; sclp_add_standby_memory(); + goto out; +out_driver: + platform_driver_unregister(&sclp_mem_pdrv); out: free_page((unsigned long) sccb); return rc; diff --git a/drivers/s390/char/sclp_con.c b/drivers/s390/char/sclp_con.c index 9a25c4bd142..336811a7767 100644 --- a/drivers/s390/char/sclp_con.c +++ b/drivers/s390/char/sclp_con.c @@ -1,11 +1,9 @@ /* - * drivers/s390/char/sclp_con.c - * SCLP line mode console driver + * SCLP line mode console driver * - * S390 version - * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation - * Author(s): Martin Peschke <mpeschke@de.ibm.com> - * Martin Schwidefsky <schwidefsky@de.ibm.com> + * Copyright IBM Corp. 1999, 2009 + * Author(s): Martin Peschke <mpeschke@de.ibm.com> + * Martin Schwidefsky <schwidefsky@de.ibm.com> */ #include <linux/kmod.h> @@ -32,13 +30,14 @@ static spinlock_t sclp_con_lock; static struct list_head sclp_con_pages; /* List of full struct sclp_buffer structures ready for output */ static struct list_head sclp_con_outqueue; -/* Counter how many buffers are emitted (max 1) and how many */ -/* are on the output queue. */ -static int sclp_con_buffer_count; /* Pointer to current console buffer */ static struct sclp_buffer *sclp_conbuf; /* Timer for delayed output of console messages */ static struct timer_list sclp_con_timer; +/* Suspend mode flag */ +static int sclp_con_suspended; +/* Flag that output queue is currently running */ +static int sclp_con_queue_running; /* Output format for console messages */ static unsigned short sclp_con_columns; @@ -53,42 +52,71 @@ sclp_conbuf_callback(struct sclp_buffer *buffer, int rc) do { page = sclp_unmake_buffer(buffer); spin_lock_irqsave(&sclp_con_lock, flags); + /* Remove buffer from outqueue */ list_del(&buffer->list); - sclp_con_buffer_count--; list_add_tail((struct list_head *) page, &sclp_con_pages); + /* Check if there is a pending buffer on the out queue. */ buffer = NULL; if (!list_empty(&sclp_con_outqueue)) - buffer = list_entry(sclp_con_outqueue.next, - struct sclp_buffer, list); + buffer = list_first_entry(&sclp_con_outqueue, + struct sclp_buffer, list); + if (!buffer || sclp_con_suspended) { + sclp_con_queue_running = 0; + spin_unlock_irqrestore(&sclp_con_lock, flags); + break; + } spin_unlock_irqrestore(&sclp_con_lock, flags); - } while (buffer && sclp_emit_buffer(buffer, sclp_conbuf_callback)); + } while (sclp_emit_buffer(buffer, sclp_conbuf_callback)); } -static void -sclp_conbuf_emit(void) +/* + * Finalize and emit first pending buffer. + */ +static void sclp_conbuf_emit(void) { struct sclp_buffer* buffer; unsigned long flags; - int count; int rc; spin_lock_irqsave(&sclp_con_lock, flags); - buffer = sclp_conbuf; + if (sclp_conbuf) + list_add_tail(&sclp_conbuf->list, &sclp_con_outqueue); sclp_conbuf = NULL; - if (buffer == NULL) { - spin_unlock_irqrestore(&sclp_con_lock, flags); - return; - } - list_add_tail(&buffer->list, &sclp_con_outqueue); - count = sclp_con_buffer_count++; + if (sclp_con_queue_running || sclp_con_suspended) + goto out_unlock; + if (list_empty(&sclp_con_outqueue)) + goto out_unlock; + buffer = list_first_entry(&sclp_con_outqueue, struct sclp_buffer, + list); + sclp_con_queue_running = 1; spin_unlock_irqrestore(&sclp_con_lock, flags); - if (count) - return; + rc = sclp_emit_buffer(buffer, sclp_conbuf_callback); if (rc) sclp_conbuf_callback(buffer, rc); + return; +out_unlock: + spin_unlock_irqrestore(&sclp_con_lock, flags); +} + +/* + * Wait until out queue is empty + */ +static void sclp_console_sync_queue(void) +{ + unsigned long flags; + + spin_lock_irqsave(&sclp_con_lock, flags); + if (timer_pending(&sclp_con_timer)) + del_timer_sync(&sclp_con_timer); + while (sclp_con_queue_running) { + spin_unlock_irqrestore(&sclp_con_lock, flags); + sclp_sync_wait(); + spin_lock_irqsave(&sclp_con_lock, flags); + } + spin_unlock_irqrestore(&sclp_con_lock, flags); } /* @@ -123,6 +151,8 @@ sclp_console_write(struct console *console, const char *message, /* make sure we have a console output buffer */ if (sclp_conbuf == NULL) { while (list_empty(&sclp_con_pages)) { + if (sclp_con_suspended) + goto out; spin_unlock_irqrestore(&sclp_con_lock, flags); sclp_sync_wait(); spin_lock_irqsave(&sclp_con_lock, flags); @@ -157,6 +187,7 @@ sclp_console_write(struct console *console, const char *message, sclp_con_timer.expires = jiffies + HZ/10; add_timer(&sclp_con_timer); } +out: spin_unlock_irqrestore(&sclp_con_lock, flags); } @@ -168,30 +199,43 @@ sclp_console_device(struct console *c, int *index) } /* - * 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. + * Make sure that all buffers will be flushed to the SCLP. */ static void sclp_console_flush(void) { + sclp_conbuf_emit(); + sclp_console_sync_queue(); +} + +/* + * Resume console: If there are cached messages, emit them. + */ +static void sclp_console_resume(void) +{ unsigned long flags; + spin_lock_irqsave(&sclp_con_lock, flags); + sclp_con_suspended = 0; + spin_unlock_irqrestore(&sclp_con_lock, flags); sclp_conbuf_emit(); +} + +/* + * Suspend console: Set suspend flag and flush console + */ +static void sclp_console_suspend(void) +{ + unsigned long flags; + spin_lock_irqsave(&sclp_con_lock, flags); - if (timer_pending(&sclp_con_timer)) - del_timer(&sclp_con_timer); - while (sclp_con_buffer_count > 0) { - spin_unlock_irqrestore(&sclp_con_lock, flags); - sclp_sync_wait(); - spin_lock_irqsave(&sclp_con_lock, flags); - } + sclp_con_suspended = 1; spin_unlock_irqrestore(&sclp_con_lock, flags); + sclp_console_flush(); } -static int -sclp_console_notify(struct notifier_block *self, - unsigned long event, void *data) +static int sclp_console_notify(struct notifier_block *self, + unsigned long event, void *data) { sclp_console_flush(); return NOTIFY_OK; @@ -199,7 +243,7 @@ sclp_console_notify(struct notifier_block *self, static struct notifier_block on_panic_nb = { .notifier_call = sclp_console_notify, - .priority = 1, + .priority = SCLP_PANIC_PRIO_CLIENT, }; static struct notifier_block on_reboot_nb = { @@ -221,6 +265,22 @@ static struct console sclp_console = }; /* + * This function is called for SCLP suspend and resume events. + */ +void sclp_console_pm_event(enum sclp_pm_event sclp_pm_event) +{ + switch (sclp_pm_event) { + case SCLP_PM_EVENT_FREEZE: + sclp_console_suspend(); + break; + case SCLP_PM_EVENT_RESTORE: + case SCLP_PM_EVENT_THAW: + sclp_console_resume(); + break; + } +} + +/* * called by console_init() in drivers/char/tty_io.c at boot-time. */ static int __init @@ -243,7 +303,6 @@ sclp_console_init(void) } INIT_LIST_HEAD(&sclp_con_outqueue); spin_lock_init(&sclp_con_lock); - sclp_con_buffer_count = 0; sclp_conbuf = NULL; init_timer(&sclp_con_timer); diff --git a/drivers/s390/char/sclp_rw.c b/drivers/s390/char/sclp_rw.c index 710af42603f..4be63be7344 100644 --- a/drivers/s390/char/sclp_rw.c +++ b/drivers/s390/char/sclp_rw.c @@ -1,11 +1,10 @@ /* - * drivers/s390/char/sclp_rw.c - * driver: reading from and writing to system console on S/390 via SCLP + * driver: reading from and writing to system console on S/390 via SCLP * - * S390 version - * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation - * Author(s): Martin Peschke <mpeschke@de.ibm.com> - * Martin Schwidefsky <schwidefsky@de.ibm.com> + * Copyright IBM Corp. 1999, 2009 + * + * Author(s): Martin Peschke <mpeschke@de.ibm.com> + * Martin Schwidefsky <schwidefsky@de.ibm.com> */ #include <linux/kmod.h> @@ -26,9 +25,16 @@ */ #define MAX_SCCB_ROOM (PAGE_SIZE - sizeof(struct sclp_buffer)) +static void sclp_rw_pm_event(struct sclp_register *reg, + enum sclp_pm_event sclp_pm_event) +{ + sclp_console_pm_event(sclp_pm_event); +} + /* Event type structure for write message and write priority message */ static struct sclp_register sclp_rw_event = { - .send_mask = EVTYP_MSG_MASK | EVTYP_PMSGCMD_MASK + .send_mask = EVTYP_MSG_MASK | EVTYP_PMSGCMD_MASK, + .pm_event_fn = sclp_rw_pm_event, }; /* diff --git a/drivers/s390/char/sclp_rw.h b/drivers/s390/char/sclp_rw.h index 6aa7a6948bc..85f491ea929 100644 --- a/drivers/s390/char/sclp_rw.h +++ b/drivers/s390/char/sclp_rw.h @@ -1,11 +1,10 @@ /* - * drivers/s390/char/sclp_rw.h - * interface to the SCLP-read/write driver + * interface to the SCLP-read/write driver * - * S390 version - * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation - * Author(s): Martin Peschke <mpeschke@de.ibm.com> - * Martin Schwidefsky <schwidefsky@de.ibm.com> + * Copyright IBM Corporation 1999, 2009 + * + * Author(s): Martin Peschke <mpeschke@de.ibm.com> + * Martin Schwidefsky <schwidefsky@de.ibm.com> */ #ifndef __SCLP_RW_H__ @@ -93,4 +92,5 @@ void sclp_set_columns(struct sclp_buffer *, unsigned short); void sclp_set_htab(struct sclp_buffer *, unsigned short); int sclp_chars_in_buffer(struct sclp_buffer *); +void sclp_console_pm_event(enum sclp_pm_event sclp_pm_event); #endif /* __SCLP_RW_H__ */ diff --git a/drivers/s390/char/sclp_vt220.c b/drivers/s390/char/sclp_vt220.c index a839aa531d7..5518e24946a 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> @@ -69,8 +68,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 */ @@ -92,6 +94,8 @@ 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); @@ -100,7 +104,8 @@ static struct sclp_register sclp_vt220_register = { .send_mask = EVTYP_VT220MSG_MASK, .receive_mask = EVTYP_VT220MSG_MASK, .state_change_fn = NULL, - .receiver_fn = sclp_vt220_receiver_fn + .receiver_fn = sclp_vt220_receiver_fn, + .pm_event_fn = sclp_vt220_pm_event_fn, }; @@ -120,15 +125,19 @@ 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 */ @@ -212,26 +221,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) @@ -241,22 +231,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 @@ -396,7 +397,7 @@ __sclp_vt220_write(const unsigned char *buf, int count, int do_schedule, if (sclp_vt220_current_request == NULL) { 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(); @@ -531,7 +532,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; @@ -635,7 +636,6 @@ static int __init __sclp_vt220_init(int num_pages) init_timer(&sclp_vt220_timer); sclp_vt220_current_request = NULL; sclp_vt220_buffered_chars = 0; - sclp_vt220_outqueue_count = 0; sclp_vt220_tty = NULL; sclp_vt220_flush_later = 0; @@ -736,7 +736,7 @@ static void __sclp_vt220_flush_buffer(void) spin_lock_irqsave(&sclp_vt220_lock, flags); if (timer_pending(&sclp_vt220_timer)) del_timer(&sclp_vt220_timer); - while (sclp_vt220_outqueue_count > 0) { + while (sclp_vt220_queue_running) { spin_unlock_irqrestore(&sclp_vt220_lock, flags); sclp_sync_wait(); spin_lock_irqsave(&sclp_vt220_lock, flags); @@ -744,6 +744,46 @@ static void __sclp_vt220_flush_buffer(void) 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; + } +} + static int sclp_vt220_notify(struct notifier_block *self, unsigned long event, void *data) diff --git a/drivers/s390/char/tape.h b/drivers/s390/char/tape.h index 5469e099597..a2633377470 100644 --- a/drivers/s390/char/tape.h +++ b/drivers/s390/char/tape.h @@ -3,7 +3,7 @@ * tape device driver for 3480/3490E/3590 tapes. * * S390 and zSeries version - * Copyright IBM Corp. 2001,2006 + * Copyright IBM Corp. 2001, 2009 * Author(s): Carsten Otte <cotte@de.ibm.com> * Tuan Ngo-Anh <ngoanh@de.ibm.com> * Martin Schwidefsky <schwidefsky@de.ibm.com> @@ -286,6 +286,7 @@ extern void tape_state_set(struct tape_device *, enum tape_state); extern int tape_generic_online(struct tape_device *, struct tape_discipline *); extern int tape_generic_offline(struct ccw_device *); +extern int tape_generic_pm_suspend(struct ccw_device *); /* Externals from tape_devmap.c */ extern int tape_generic_probe(struct ccw_device *); diff --git a/drivers/s390/char/tape_34xx.c b/drivers/s390/char/tape_34xx.c index 2d00a383a47..5a519fac37b 100644 --- a/drivers/s390/char/tape_34xx.c +++ b/drivers/s390/char/tape_34xx.c @@ -2,7 +2,7 @@ * drivers/s390/char/tape_34xx.c * tape device discipline for 3480/3490 tapes. * - * Copyright (C) IBM Corp. 2001,2006 + * Copyright IBM Corp. 2001, 2009 * Author(s): Carsten Otte <cotte@de.ibm.com> * Tuan Ngo-Anh <ngoanh@de.ibm.com> * Martin Schwidefsky <schwidefsky@de.ibm.com> @@ -1289,7 +1289,7 @@ static int tape_34xx_online(struct ccw_device *cdev) { return tape_generic_online( - cdev->dev.driver_data, + dev_get_drvdata(&cdev->dev), &tape_discipline_34xx ); } @@ -1302,6 +1302,7 @@ static struct ccw_driver tape_34xx_driver = { .remove = tape_generic_remove, .set_online = tape_34xx_online, .set_offline = tape_generic_offline, + .freeze = tape_generic_pm_suspend, }; static int diff --git a/drivers/s390/char/tape_3590.c b/drivers/s390/char/tape_3590.c index c453b2f3e9f..418f72dd39b 100644 --- a/drivers/s390/char/tape_3590.c +++ b/drivers/s390/char/tape_3590.c @@ -2,7 +2,7 @@ * drivers/s390/char/tape_3590.c * tape device discipline for 3590 tapes. * - * Copyright IBM Corp. 2001,2006 + * Copyright IBM Corp. 2001, 2009 * Author(s): Stefan Bader <shbader@de.ibm.com> * Michael Holzheu <holzheu@de.ibm.com> * Martin Schwidefsky <schwidefsky@de.ibm.com> @@ -1703,7 +1703,7 @@ static struct ccw_device_id tape_3590_ids[] = { static int tape_3590_online(struct ccw_device *cdev) { - return tape_generic_online(cdev->dev.driver_data, + return tape_generic_online(dev_get_drvdata(&cdev->dev), &tape_discipline_3590); } @@ -1715,6 +1715,7 @@ static struct ccw_driver tape_3590_driver = { .remove = tape_generic_remove, .set_offline = tape_generic_offline, .set_online = tape_3590_online, + .freeze = tape_generic_pm_suspend, }; /* diff --git a/drivers/s390/char/tape_core.c b/drivers/s390/char/tape_core.c index 8a109f3b69c..595aa04cfd0 100644 --- a/drivers/s390/char/tape_core.c +++ b/drivers/s390/char/tape_core.c @@ -3,7 +3,7 @@ * basic function of the tape device driver * * S390 and zSeries version - * Copyright IBM Corp. 2001,2006 + * Copyright IBM Corp. 2001, 2009 * Author(s): Carsten Otte <cotte@de.ibm.com> * Michael Holzheu <holzheu@de.ibm.com> * Tuan Ngo-Anh <ngoanh@de.ibm.com> @@ -92,7 +92,7 @@ tape_medium_state_show(struct device *dev, struct device_attribute *attr, char * { struct tape_device *tdev; - tdev = (struct tape_device *) dev->driver_data; + tdev = dev_get_drvdata(dev); return scnprintf(buf, PAGE_SIZE, "%i\n", tdev->medium_state); } @@ -104,7 +104,7 @@ tape_first_minor_show(struct device *dev, struct device_attribute *attr, char *b { struct tape_device *tdev; - tdev = (struct tape_device *) dev->driver_data; + tdev = dev_get_drvdata(dev); return scnprintf(buf, PAGE_SIZE, "%i\n", tdev->first_minor); } @@ -116,7 +116,7 @@ tape_state_show(struct device *dev, struct device_attribute *attr, char *buf) { struct tape_device *tdev; - tdev = (struct tape_device *) dev->driver_data; + tdev = dev_get_drvdata(dev); return scnprintf(buf, PAGE_SIZE, "%s\n", (tdev->first_minor < 0) ? "OFFLINE" : tape_state_verbose[tdev->tape_state]); } @@ -130,7 +130,7 @@ tape_operation_show(struct device *dev, struct device_attribute *attr, char *buf struct tape_device *tdev; ssize_t rc; - tdev = (struct tape_device *) dev->driver_data; + tdev = dev_get_drvdata(dev); if (tdev->first_minor < 0) return scnprintf(buf, PAGE_SIZE, "N/A\n"); @@ -156,7 +156,7 @@ tape_blocksize_show(struct device *dev, struct device_attribute *attr, char *buf { struct tape_device *tdev; - tdev = (struct tape_device *) dev->driver_data; + tdev = dev_get_drvdata(dev); return scnprintf(buf, PAGE_SIZE, "%i\n", tdev->char_data.block_size); } @@ -380,6 +380,55 @@ tape_cleanup_device(struct tape_device *device) } /* + * Suspend device. + * + * Called by the common I/O layer if the drive should be suspended on user + * request. We refuse to suspend if the device is loaded or in use for the + * following reason: + * While the Linux guest is suspended, it might be logged off which causes + * devices to be detached. Tape devices are automatically rewound and unloaded + * during DETACH processing (unless the tape device was attached with the + * NOASSIGN or MULTIUSER option). After rewind/unload, there is no way to + * resume the original state of the tape device, since we would need to + * manually re-load the cartridge which was active at suspend time. + */ +int tape_generic_pm_suspend(struct ccw_device *cdev) +{ + struct tape_device *device; + + device = cdev->dev.driver_data; + if (!device) { + return -ENODEV; + } + + DBF_LH(3, "(%08x): tape_generic_pm_suspend(%p)\n", + device->cdev_id, device); + + if (device->medium_state != MS_UNLOADED) { + pr_err("A cartridge is loaded in tape device %s, " + "refusing to suspend\n", dev_name(&cdev->dev)); + return -EBUSY; + } + + spin_lock_irq(get_ccwdev_lock(device->cdev)); + switch (device->tape_state) { + case TS_INIT: + case TS_NOT_OPER: + case TS_UNUSED: + spin_unlock_irq(get_ccwdev_lock(device->cdev)); + break; + default: + pr_err("Tape device %s is busy, refusing to " + "suspend\n", dev_name(&cdev->dev)); + spin_unlock_irq(get_ccwdev_lock(device->cdev)); + return -EBUSY; + } + + DBF_LH(3, "(%08x): Drive suspended.\n", device->cdev_id); + return 0; +} + +/* * Set device offline. * * Called by the common I/O layer if the drive should set offline on user @@ -391,7 +440,7 @@ tape_generic_offline(struct ccw_device *cdev) { struct tape_device *device; - device = cdev->dev.driver_data; + device = dev_get_drvdata(&cdev->dev); if (!device) { return -ENODEV; } @@ -534,7 +583,7 @@ tape_generic_probe(struct ccw_device *cdev) tape_put_device(device); return ret; } - cdev->dev.driver_data = device; + dev_set_drvdata(&cdev->dev, device); cdev->handler = __tape_do_irq; device->cdev = cdev; ccw_device_get_id(cdev, &dev_id); @@ -573,7 +622,7 @@ tape_generic_remove(struct ccw_device *cdev) { struct tape_device * device; - device = cdev->dev.driver_data; + device = dev_get_drvdata(&cdev->dev); if (!device) { return; } @@ -613,9 +662,9 @@ tape_generic_remove(struct ccw_device *cdev) tape_cleanup_device(device); } - if (cdev->dev.driver_data != NULL) { + if (!dev_get_drvdata(&cdev->dev)) { sysfs_remove_group(&cdev->dev.kobj, &tape_attr_group); - cdev->dev.driver_data = tape_put_device(cdev->dev.driver_data); + dev_set_drvdata(&cdev->dev, tape_put_device(dev_get_drvdata(&cdev->dev))); } } @@ -1011,7 +1060,7 @@ __tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb) struct tape_request *request; int rc; - device = (struct tape_device *) cdev->dev.driver_data; + device = dev_get_drvdata(&cdev->dev); if (device == NULL) { return; } @@ -1273,6 +1322,7 @@ EXPORT_SYMBOL(tape_generic_remove); EXPORT_SYMBOL(tape_generic_probe); EXPORT_SYMBOL(tape_generic_online); EXPORT_SYMBOL(tape_generic_offline); +EXPORT_SYMBOL(tape_generic_pm_suspend); EXPORT_SYMBOL(tape_put_device); EXPORT_SYMBOL(tape_get_device_reference); EXPORT_SYMBOL(tape_state_verbose); diff --git a/drivers/s390/char/vmlogrdr.c b/drivers/s390/char/vmlogrdr.c index d8a2289fcb6..411cfa3c771 100644 --- a/drivers/s390/char/vmlogrdr.c +++ b/drivers/s390/char/vmlogrdr.c @@ -3,7 +3,7 @@ * character device driver for reading z/VM system service records * * - * Copyright 2004 IBM Corporation + * Copyright IBM Corp. 2004, 2009 * character device driver for reading z/VM system service records, * Version 1.0 * Author(s): Xenia Tkatschow <xenia@us.ibm.com> @@ -504,7 +504,7 @@ static ssize_t vmlogrdr_autopurge_store(struct device * dev, struct device_attribute *attr, const char * buf, size_t count) { - struct vmlogrdr_priv_t *priv = dev->driver_data; + struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev); ssize_t ret = count; switch (buf[0]) { @@ -525,7 +525,7 @@ static ssize_t vmlogrdr_autopurge_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct vmlogrdr_priv_t *priv = dev->driver_data; + struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev); return sprintf(buf, "%u\n", priv->autopurge); } @@ -541,7 +541,7 @@ static ssize_t vmlogrdr_purge_store(struct device * dev, char cp_command[80]; char cp_response[80]; - struct vmlogrdr_priv_t *priv = dev->driver_data; + struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev); if (buf[0] != '1') return -EINVAL; @@ -578,7 +578,7 @@ static ssize_t vmlogrdr_autorecording_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct vmlogrdr_priv_t *priv = dev->driver_data; + struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev); ssize_t ret = count; switch (buf[0]) { @@ -599,7 +599,7 @@ static ssize_t vmlogrdr_autorecording_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct vmlogrdr_priv_t *priv = dev->driver_data; + struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev); return sprintf(buf, "%u\n", priv->autorecording); } @@ -612,7 +612,7 @@ static ssize_t vmlogrdr_recording_store(struct device * dev, struct device_attribute *attr, const char * buf, size_t count) { - struct vmlogrdr_priv_t *priv = dev->driver_data; + struct vmlogrdr_priv_t *priv = dev_get_drvdata(dev); ssize_t ret; switch (buf[0]) { @@ -660,6 +660,29 @@ static struct attribute *vmlogrdr_attrs[] = { NULL, }; +static int vmlogrdr_pm_prepare(struct device *dev) +{ + int rc; + struct vmlogrdr_priv_t *priv = dev->driver_data; + + rc = 0; + if (priv) { + spin_lock_bh(&priv->priv_lock); + if (priv->dev_in_use) + rc = -EBUSY; + spin_unlock_bh(&priv->priv_lock); + } + if (rc) + pr_err("vmlogrdr: device %s is busy. Refuse to suspend.\n", + dev_name(dev)); + return rc; +} + + +static struct dev_pm_ops vmlogrdr_pm_ops = { + .prepare = vmlogrdr_pm_prepare, +}; + static struct attribute_group vmlogrdr_attr_group = { .attrs = vmlogrdr_attrs, }; @@ -668,6 +691,7 @@ static struct class *vmlogrdr_class; static struct device_driver vmlogrdr_driver = { .name = "vmlogrdr", .bus = &iucv_bus, + .pm = &vmlogrdr_pm_ops, }; @@ -729,6 +753,7 @@ static int vmlogrdr_register_device(struct vmlogrdr_priv_t *priv) dev->bus = &iucv_bus; dev->parent = iucv_root; dev->driver = &vmlogrdr_driver; + dev->driver_data = priv; /* * The release function could be called after the * module has been unloaded. It's _only_ task is to diff --git a/drivers/s390/char/vmur.c b/drivers/s390/char/vmur.c index 5dcef81fc9d..7d9e67cb647 100644 --- a/drivers/s390/char/vmur.c +++ b/drivers/s390/char/vmur.c @@ -2,7 +2,7 @@ * Linux driver for System z and s390 unit record devices * (z/VM virtual punch, reader, printer) * - * Copyright IBM Corp. 2001, 2007 + * Copyright IBM Corp. 2001, 2009 * Authors: Malcolm Beattie <beattiem@uk.ibm.com> * Michael Holzheu <holzheu@de.ibm.com> * Frank Munzert <munzert@de.ibm.com> @@ -60,6 +60,7 @@ static int ur_probe(struct ccw_device *cdev); static void ur_remove(struct ccw_device *cdev); static int ur_set_online(struct ccw_device *cdev); static int ur_set_offline(struct ccw_device *cdev); +static int ur_pm_suspend(struct ccw_device *cdev); static struct ccw_driver ur_driver = { .name = "vmur", @@ -69,6 +70,7 @@ static struct ccw_driver ur_driver = { .remove = ur_remove, .set_online = ur_set_online, .set_offline = ur_set_offline, + .freeze = ur_pm_suspend, }; static DEFINE_MUTEX(vmur_mutex); @@ -78,11 +80,11 @@ static DEFINE_MUTEX(vmur_mutex); * * Each ur device (urd) contains a reference to its corresponding ccw device * (cdev) using the urd->cdev pointer. Each ccw device has a reference to the - * ur device using the cdev->dev.driver_data pointer. + * ur device using dev_get_drvdata(&cdev->dev) pointer. * * urd references: * - ur_probe gets a urd reference, ur_remove drops the reference - * (cdev->dev.driver_data) + * dev_get_drvdata(&cdev->dev) * - ur_open gets a urd reference, ur_relase drops the reference * (urf->urd) * @@ -90,7 +92,7 @@ static DEFINE_MUTEX(vmur_mutex); * - urdev_alloc get a cdev reference (urd->cdev) * - urdev_free drops the cdev reference (urd->cdev) * - * Setting and clearing of cdev->dev.driver_data is protected by the ccwdev lock + * Setting and clearing of dev_get_drvdata(&cdev->dev) is protected by the ccwdev lock */ static struct urdev *urdev_alloc(struct ccw_device *cdev) { @@ -129,7 +131,7 @@ static struct urdev *urdev_get_from_cdev(struct ccw_device *cdev) unsigned long flags; spin_lock_irqsave(get_ccwdev_lock(cdev), flags); - urd = cdev->dev.driver_data; + urd = dev_get_drvdata(&cdev->dev); if (urd) urdev_get(urd); spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); @@ -158,6 +160,28 @@ static void urdev_put(struct urdev *urd) } /* + * State and contents of ur devices can be changed by class D users issuing + * CP commands such as PURGE or TRANSFER, while the Linux guest is suspended. + * Also the Linux guest might be logged off, which causes all active spool + * files to be closed. + * So we cannot guarantee that spool files are still the same when the Linux + * guest is resumed. In order to avoid unpredictable results at resume time + * we simply refuse to suspend if a ur device node is open. + */ +static int ur_pm_suspend(struct ccw_device *cdev) +{ + struct urdev *urd = cdev->dev.driver_data; + + TRACE("ur_pm_suspend: cdev=%p\n", cdev); + if (urd->open_flag) { + pr_err("Unit record device %s is busy, %s refusing to " + "suspend.\n", dev_name(&cdev->dev), ur_banner); + return -EBUSY; + } + return 0; +} + +/* * Low-level functions to do I/O to a ur device. * alloc_chan_prog * free_chan_prog @@ -286,7 +310,7 @@ static void ur_int_handler(struct ccw_device *cdev, unsigned long intparm, TRACE("ur_int_handler: unsolicited interrupt\n"); return; } - urd = cdev->dev.driver_data; + urd = dev_get_drvdata(&cdev->dev); BUG_ON(!urd); /* On special conditions irb is an error pointer */ if (IS_ERR(irb)) @@ -832,7 +856,7 @@ static int ur_probe(struct ccw_device *cdev) goto fail_remove_attr; } spin_lock_irq(get_ccwdev_lock(cdev)); - cdev->dev.driver_data = urd; + dev_set_drvdata(&cdev->dev, urd); spin_unlock_irq(get_ccwdev_lock(cdev)); mutex_unlock(&vmur_mutex); @@ -972,8 +996,8 @@ static void ur_remove(struct ccw_device *cdev) ur_remove_attributes(&cdev->dev); spin_lock_irqsave(get_ccwdev_lock(cdev), flags); - urdev_put(cdev->dev.driver_data); - cdev->dev.driver_data = NULL; + urdev_put(dev_get_drvdata(&cdev->dev)); + dev_set_drvdata(&cdev->dev, NULL); spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); mutex_unlock(&vmur_mutex); diff --git a/drivers/s390/char/vmwatchdog.c b/drivers/s390/char/vmwatchdog.c index 21a2a829bf4..cb7854c10c0 100644 --- a/drivers/s390/char/vmwatchdog.c +++ b/drivers/s390/char/vmwatchdog.c @@ -1,17 +1,23 @@ /* * Watchdog implementation based on z/VM Watchdog Timer API * + * Copyright IBM Corp. 2004,2009 + * * The user space watchdog daemon can use this driver as * /dev/vmwatchdog to have z/VM execute the specified CP * command when the timeout expires. The default command is * "IPL", which which cause an immediate reboot. */ +#define KMSG_COMPONENT "vmwatchdog" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + #include <linux/init.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/miscdevice.h> #include <linux/module.h> #include <linux/moduleparam.h> +#include <linux/suspend.h> #include <linux/watchdog.h> #include <linux/smp_lock.h> @@ -43,6 +49,9 @@ static unsigned int vmwdt_interval = 60; static unsigned long vmwdt_is_open; static int vmwdt_expect_close; +#define VMWDT_OPEN 0 /* devnode is open or suspend in progress */ +#define VMWDT_RUNNING 1 /* The watchdog is armed */ + enum vmwdt_func { /* function codes */ wdt_init = 0, @@ -92,6 +101,7 @@ static int vmwdt_keepalive(void) EBC_TOUPPER(ebc_cmd, MAX_CMDLEN); func = vmwdt_conceal ? (wdt_init | wdt_conceal) : wdt_init; + set_bit(VMWDT_RUNNING, &vmwdt_is_open); ret = __diag288(func, vmwdt_interval, ebc_cmd, len); WARN_ON(ret != 0); kfree(ebc_cmd); @@ -102,6 +112,7 @@ static int vmwdt_disable(void) { int ret = __diag288(wdt_cancel, 0, "", 0); WARN_ON(ret != 0); + clear_bit(VMWDT_RUNNING, &vmwdt_is_open); return ret; } @@ -123,13 +134,13 @@ static int vmwdt_open(struct inode *i, struct file *f) { int ret; lock_kernel(); - if (test_and_set_bit(0, &vmwdt_is_open)) { + if (test_and_set_bit(VMWDT_OPEN, &vmwdt_is_open)) { unlock_kernel(); return -EBUSY; } ret = vmwdt_keepalive(); if (ret) - clear_bit(0, &vmwdt_is_open); + clear_bit(VMWDT_OPEN, &vmwdt_is_open); unlock_kernel(); return ret ? ret : nonseekable_open(i, f); } @@ -139,7 +150,7 @@ static int vmwdt_close(struct inode *i, struct file *f) if (vmwdt_expect_close == 42) vmwdt_disable(); vmwdt_expect_close = 0; - clear_bit(0, &vmwdt_is_open); + clear_bit(VMWDT_OPEN, &vmwdt_is_open); return 0; } @@ -223,6 +234,57 @@ static ssize_t vmwdt_write(struct file *f, const char __user *buf, return count; } +static int vmwdt_resume(void) +{ + clear_bit(VMWDT_OPEN, &vmwdt_is_open); + return NOTIFY_DONE; +} + +/* + * It makes no sense to go into suspend while the watchdog is running. + * Depending on the memory size, the watchdog might trigger, while we + * are still saving the memory. + * We reuse the open flag to ensure that suspend and watchdog open are + * exclusive operations + */ +static int vmwdt_suspend(void) +{ + if (test_and_set_bit(VMWDT_OPEN, &vmwdt_is_open)) { + pr_err("The watchdog is in use. " + "This prevents hibernation or suspend.\n"); + return NOTIFY_BAD; + } + if (test_bit(VMWDT_RUNNING, &vmwdt_is_open)) { + clear_bit(VMWDT_OPEN, &vmwdt_is_open); + pr_err("The watchdog is running. " + "This prevents hibernation or suspend.\n"); + return NOTIFY_BAD; + } + return NOTIFY_DONE; +} + +/* + * This function is called for suspend and resume. + */ +static int vmwdt_power_event(struct notifier_block *this, unsigned long event, + void *ptr) +{ + switch (event) { + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + return vmwdt_resume(); + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + return vmwdt_suspend(); + default: + return NOTIFY_DONE; + } +} + +static struct notifier_block vmwdt_power_notifier = { + .notifier_call = vmwdt_power_event, +}; + static const struct file_operations vmwdt_fops = { .open = &vmwdt_open, .release = &vmwdt_close, @@ -244,12 +306,21 @@ static int __init vmwdt_init(void) ret = vmwdt_probe(); if (ret) return ret; - return misc_register(&vmwdt_dev); + ret = register_pm_notifier(&vmwdt_power_notifier); + if (ret) + return ret; + ret = misc_register(&vmwdt_dev); + if (ret) { + unregister_pm_notifier(&vmwdt_power_notifier); + return ret; + } + return 0; } module_init(vmwdt_init); static void __exit vmwdt_exit(void) { - WARN_ON(misc_deregister(&vmwdt_dev) != 0); + unregister_pm_notifier(&vmwdt_power_notifier); + misc_deregister(&vmwdt_dev); } module_exit(vmwdt_exit); |