diff options
Diffstat (limited to 'drivers/s390/block/dasd_eer.c')
-rw-r--r-- | drivers/s390/block/dasd_eer.c | 868 |
1 files changed, 230 insertions, 638 deletions
diff --git a/drivers/s390/block/dasd_eer.c b/drivers/s390/block/dasd_eer.c index f70cd7716b24..2d946b6ca074 100644 --- a/drivers/s390/block/dasd_eer.c +++ b/drivers/s390/block/dasd_eer.c @@ -1,11 +1,9 @@ /* - * character device driver for extended error reporting - * - * - * Copyright (C) 2005 IBM Corporation - * extended error reporting for DASD ECKD devices - * Author(s): Stefan Weinhuber <wein@de.ibm.com> + * Character device driver for extended error reporting. * + * Copyright (C) 2005 IBM Corporation + * extended error reporting for DASD ECKD devices + * Author(s): Stefan Weinhuber <wein@de.ibm.com> */ #include <linux/init.h> @@ -15,9 +13,7 @@ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/device.h> -#include <linux/workqueue.h> #include <linux/poll.h> -#include <linux/notifier.h> #include <asm/uaccess.h> #include <asm/semaphore.h> @@ -27,40 +23,30 @@ #include "dasd_int.h" #include "dasd_eckd.h" - -MODULE_LICENSE("GPL"); - -MODULE_AUTHOR("Stefan Weinhuber <wein@de.ibm.com>"); -MODULE_DESCRIPTION("DASD extended error reporting module"); - - #ifdef PRINTK_HEADER #undef PRINTK_HEADER #endif /* PRINTK_HEADER */ #define PRINTK_HEADER "dasd(eer):" - - - - -/*****************************************************************************/ -/* the internal buffer */ -/*****************************************************************************/ +/* + * SECTION: the internal buffer + */ /* - * The internal buffer is meant to store obaque blobs of data, so it doesn't - * know of higher level concepts like triggers. + * The internal buffer is meant to store obaque blobs of data, so it does + * not know of higher level concepts like triggers. * It consists of a number of pages that are used as a ringbuffer. Each data * blob is stored in a simple record that consists of an integer, which * contains the size of the following data, and the data bytes themselfes. * * To allow for multiple independent readers we create one internal buffer * each time the device is opened and destroy the buffer when the file is - * closed again. + * closed again. The number of pages used for this buffer is determined by + * the module parmeter eer_pages. * * One record can be written to a buffer by using the functions - * - dasd_eer_start_record (one time per record to write the size to the buffer - * and reserve the space for the data) + * - dasd_eer_start_record (one time per record to write the size to the + * buffer and reserve the space for the data) * - dasd_eer_write_buffer (one or more times per record to write the data) * The data can be written in several steps but you will have to compute * the total size up front for the invocation of dasd_eer_start_record. @@ -72,17 +58,14 @@ MODULE_DESCRIPTION("DASD extended error reporting module"); * Both can be done by * - dasd_eer_read_buffer * - * For all mentioned functions you need to get the bufferlock first and keep it - * until a complete record is written or read. - */ - - -/* - * Alle information necessary to keep track of an internal buffer is kept in + * For all mentioned functions you need to get the bufferlock first and keep + * it until a complete record is written or read. + * + * All information necessary to keep track of an internal buffer is kept in * a struct eerbuffer. The buffer specific to a file pointer is strored in * the private_data field of that file. To be able to write data to all * existing buffers, each buffer is also added to the bufferlist. - * If the user doesn't want to read a complete record in one go, we have to + * If the user does not want to read a complete record in one go, we have to * keep track of the rest of the record. residual stores the number of bytes * that are still to deliver. If the rest of the record is invalidated between * two reads then residual will be set to -1 so that the next read will fail. @@ -92,6 +75,9 @@ MODULE_DESCRIPTION("DASD extended error reporting module"); * to protect the bufferlist. */ +static int eer_pages = 5; +module_param(eer_pages, int, S_IRUGO|S_IWUSR); + struct eerbuffer { struct list_head list; char **buffer; @@ -102,47 +88,41 @@ struct eerbuffer { int residual; }; -LIST_HEAD(bufferlist); - +static LIST_HEAD(bufferlist); static spinlock_t bufferlock = SPIN_LOCK_UNLOCKED; - -DECLARE_WAIT_QUEUE_HEAD(dasd_eer_read_wait_queue); +static DECLARE_WAIT_QUEUE_HEAD(dasd_eer_read_wait_queue); /* * How many free bytes are available on the buffer. - * needs to be called with bufferlock held + * Needs to be called with bufferlock held. */ -static int -dasd_eer_get_free_bytes(struct eerbuffer *eerb) +static int dasd_eer_get_free_bytes(struct eerbuffer *eerb) { - if (eerb->head < eerb->tail) { + if (eerb->head < eerb->tail) return eerb->tail - eerb->head - 1; - } else - return eerb->buffersize - eerb->head + eerb->tail -1; + return eerb->buffersize - eerb->head + eerb->tail -1; } /* * How many bytes of buffer space are used. - * needs to be called with bufferlock held + * Needs to be called with bufferlock held. */ -static int -dasd_eer_get_filled_bytes(struct eerbuffer *eerb) +static int dasd_eer_get_filled_bytes(struct eerbuffer *eerb) { - if (eerb->head >= eerb->tail) { + if (eerb->head >= eerb->tail) return eerb->head - eerb->tail; - } else - return eerb->buffersize - eerb->tail + eerb->head; + return eerb->buffersize - eerb->tail + eerb->head; } /* * The dasd_eer_write_buffer function just copies count bytes of data * to the buffer. Make sure to call dasd_eer_start_record first, to * make sure that enough free space is available. - * needs to be called with bufferlock held + * Needs to be called with bufferlock held. */ -static void -dasd_eer_write_buffer(struct eerbuffer *eerb, int count, char *data) +static void dasd_eer_write_buffer(struct eerbuffer *eerb, + char *data, int count) { unsigned long headindex,localhead; @@ -154,25 +134,21 @@ dasd_eer_write_buffer(struct eerbuffer *eerb, int count, char *data) while (rest > 0) { headindex = eerb->head / PAGE_SIZE; localhead = eerb->head % PAGE_SIZE; - len = min(rest, (PAGE_SIZE - localhead)); + len = min(rest, PAGE_SIZE - localhead); memcpy(eerb->buffer[headindex]+localhead, nextdata, len); nextdata += len; rest -= len; eerb->head += len; - if ( eerb->head == eerb->buffersize ) + if (eerb->head == eerb->buffersize) eerb->head = 0; /* wrap around */ - if (eerb->head > eerb->buffersize) { - MESSAGE(KERN_ERR, "%s", "runaway buffer head."); - BUG(); - } + BUG_ON(eerb->head > eerb->buffersize); } } /* - * needs to be called with bufferlock held + * Needs to be called with bufferlock held. */ -static int -dasd_eer_read_buffer(struct eerbuffer *eerb, int count, char *data) +static int dasd_eer_read_buffer(struct eerbuffer *eerb, char *data, int count) { unsigned long tailindex,localtail; @@ -185,17 +161,14 @@ dasd_eer_read_buffer(struct eerbuffer *eerb, int count, char *data) while (rest > 0) { tailindex = eerb->tail / PAGE_SIZE; localtail = eerb->tail % PAGE_SIZE; - len = min(rest, (PAGE_SIZE - localtail)); - memcpy(nextdata, eerb->buffer[tailindex]+localtail, len); + len = min(rest, PAGE_SIZE - localtail); + memcpy(nextdata, eerb->buffer[tailindex] + localtail, len); nextdata += len; rest -= len; eerb->tail += len; - if ( eerb->tail == eerb->buffersize ) + if (eerb->tail == eerb->buffersize) eerb->tail = 0; /* wrap around */ - if (eerb->tail > eerb->buffersize) { - MESSAGE(KERN_ERR, "%s", "runaway buffer tail."); - BUG(); - } + BUG_ON(eerb->tail > eerb->buffersize); } return finalcount; } @@ -205,12 +178,12 @@ dasd_eer_read_buffer(struct eerbuffer *eerb, int count, char *data) * have to start by using this function first. It will write the number * of bytes that will be written to the buffer. If necessary it will remove * old records to make room for the new one. - * needs to be called with bufferlock held + * Needs to be called with bufferlock held. */ -static int -dasd_eer_start_record(struct eerbuffer *eerb, int count) +static int dasd_eer_start_record(struct eerbuffer *eerb, int count) { int tailcount; + if (count + sizeof(count) > eerb->buffersize) return -ENOMEM; while (dasd_eer_get_free_bytes(eerb) < count + sizeof(count)) { @@ -220,39 +193,36 @@ dasd_eer_start_record(struct eerbuffer *eerb, int count) eerb->tail -= eerb->buffersize; eerb->residual = -1; } - dasd_eer_read_buffer(eerb, sizeof(tailcount), - (char*)(&tailcount)); + dasd_eer_read_buffer(eerb, (char *) &tailcount, + sizeof(tailcount)); eerb->tail += tailcount; if (eerb->tail >= eerb->buffersize) eerb->tail -= eerb->buffersize; } - dasd_eer_write_buffer(eerb, sizeof(count), (char*)(&count)); + dasd_eer_write_buffer(eerb, (char*) &count, sizeof(count)); return 0; }; /* - * release pages that are not used anymore + * Release pages that are not used anymore. */ -static void -dasd_eer_free_buffer_pages(char **buf, int no_pages) +static void dasd_eer_free_buffer_pages(char **buf, int no_pages) { int i; - for (i = 0; i < no_pages; ++i) { - free_page((unsigned long)buf[i]); - } + for (i = 0; i < no_pages; i++) + free_page((unsigned long) buf[i]); } /* - * allocate a new set of memory pages + * Allocate a new set of memory pages. */ -static int -dasd_eer_allocate_buffer_pages(char **buf, int no_pages) +static int dasd_eer_allocate_buffer_pages(char **buf, int no_pages) { int i; - for (i = 0; i < no_pages; ++i) { + for (i = 0; i < no_pages; i++) { buf[i] = (char *) get_zeroed_page(GFP_KERNEL); if (!buf[i]) { dasd_eer_free_buffer_pages(buf, i); @@ -263,92 +233,13 @@ dasd_eer_allocate_buffer_pages(char **buf, int no_pages) } /* - * empty the buffer by resetting head and tail - * In case there is a half read data blob in the buffer, we set residual - * to -1 to indicate that the remainder of the blob is lost. + * SECTION: The extended error reporting functionality */ -static void -dasd_eer_purge_buffer(struct eerbuffer *eerb) -{ - unsigned long flags; - - spin_lock_irqsave(&bufferlock, flags); - if (eerb->residual > 0) - eerb->residual = -1; - eerb->tail=0; - eerb->head=0; - spin_unlock_irqrestore(&bufferlock, flags); -} - -/* - * set the size of the buffer, newsize is the new number of pages to be used - * we don't try to copy any data back an forth, so any resize will also purge - * the buffer - */ -static int -dasd_eer_resize_buffer(struct eerbuffer *eerb, int newsize) -{ - int i, oldcount, reuse; - char **new; - char **old; - unsigned long flags; - - if (newsize < 1) - return -EINVAL; - if (eerb->buffer_page_count == newsize) { - /* documented behaviour is that any successfull invocation - * will purge all records */ - dasd_eer_purge_buffer(eerb); - return 0; - } - new = kmalloc(newsize*sizeof(char*), GFP_KERNEL); - if (!new) - return -ENOMEM; - - reuse=min(eerb->buffer_page_count, newsize); - for (i = 0; i < reuse; ++i) { - new[i] = eerb->buffer[i]; - } - if (eerb->buffer_page_count < newsize) { - if (dasd_eer_allocate_buffer_pages( - &new[eerb->buffer_page_count], - newsize - eerb->buffer_page_count)) { - kfree(new); - return -ENOMEM; - } - } - - spin_lock_irqsave(&bufferlock, flags); - old = eerb->buffer; - eerb->buffer = new; - if (eerb->residual > 0) - eerb->residual = -1; - eerb->tail = 0; - eerb->head = 0; - oldcount = eerb->buffer_page_count; - eerb->buffer_page_count = newsize; - spin_unlock_irqrestore(&bufferlock, flags); - - if (oldcount > newsize) { - for (i = newsize; i < oldcount; ++i) { - free_page((unsigned long)old[i]); - } - } - kfree(old); - - return 0; -} - - -/*****************************************************************************/ -/* The extended error reporting functionality */ -/*****************************************************************************/ /* * When a DASD device driver wants to report an error, it calls the - * function dasd_eer_write_trigger (via a notifier mechanism) and gives the - * respective trigger ID as parameter. - * Currently there are four kinds of triggers: + * function dasd_eer_write and gives the respective trigger ID as + * parameter. Currently there are four kinds of triggers: * * DASD_EER_FATALERROR: all kinds of unrecoverable I/O problems * DASD_EER_PPRCSUSPEND: PPRC was suspended @@ -359,51 +250,24 @@ dasd_eer_resize_buffer(struct eerbuffer *eerb, int newsize) * the caller. For these triggers a record is written by the function * dasd_eer_write_standard_trigger. * - * When dasd_eer_write_trigger is called to write a DASD_EER_STATECHANGE - * trigger, we have to gather the necessary sense data first. We cannot queue - * the necessary SNSS (sense subsystem status) request immediatly, since we - * are likely to run in a deadlock situation. Instead, we schedule a - * work_struct that calls the function dasd_eer_sense_subsystem_status to - * create and start an SNSS request asynchronously. + * The DASD_EER_STATECHANGE trigger is special since a sense subsystem + * status ccw need to be executed to gather the necessary sense data first. + * The dasd_eer_snss function will queue the SNSS request and the request + * callback will then call dasd_eer_write with the DASD_EER_STATCHANGE + * trigger. * * To avoid memory allocations at runtime, the necessary memory is allocated * when the extended error reporting is enabled for a device (by - * dasd_eer_probe). There is one private eer data structure for each eer - * enabled DASD device. It contains memory for the work_struct, one SNSS cqr - * and a flags field that is used to coordinate the use of the cqr. The call - * to write a state change trigger can come in at any time, so we have one flag - * CQR_IN_USE that protects the cqr itself. When this flag indicates that the - * cqr is currently in use, dasd_eer_sense_subsystem_status cannot start a - * second request but sets the SNSS_REQUESTED flag instead. - * - * When the request is finished, the callback function dasd_eer_SNSS_cb - * is called. This function will invoke the function - * dasd_eer_write_SNSS_trigger to finally write the trigger. It will also - * check the SNSS_REQUESTED flag and if it is set it will call - * dasd_eer_sense_subsystem_status again. - * - * To avoid race conditions during the handling of the lock, the flags must - * be protected by the snsslock. + * dasd_eer_probe). There is one sense subsystem status request for each + * eer enabled DASD device. The presence of the cqr in device->eer_cqr + * indicates that eer is enable for the device. The use of the snss request + * is protected by the DASD_FLAG_EER_IN_USE bit. When this flag indicates + * that the cqr is currently in use, dasd_eer_snss cannot start a second + * request but sets the DASD_FLAG_EER_SNSS flag instead. The callback of + * the SNSS request will check the bit and call dasd_eer_snss again. */ -struct dasd_eer_private { - struct dasd_ccw_req *cqr; - unsigned long flags; - struct work_struct worker; -}; - -static void dasd_eer_destroy(struct dasd_device *device, - struct dasd_eer_private *eer); -static int -dasd_eer_write_trigger(struct dasd_eer_trigger *trigger); -static void dasd_eer_sense_subsystem_status(void *data); -static int dasd_eer_notify(struct notifier_block *self, - unsigned long action, void *data); - -struct workqueue_struct *dasd_eer_workqueue; - #define SNSS_DATA_SIZE 44 -static spinlock_t snsslock = SPIN_LOCK_UNLOCKED; #define DASD_EER_BUSID_SIZE 10 struct dasd_eer_header { @@ -412,207 +276,18 @@ struct dasd_eer_header { __u64 tv_sec; __u64 tv_usec; char busid[DASD_EER_BUSID_SIZE]; -} __attribute__ ((packed)); - -static struct notifier_block dasd_eer_nb = { - .notifier_call = dasd_eer_notify, }; /* - * flags for use with dasd_eer_private - */ -#define CQR_IN_USE 0 -#define SNSS_REQUESTED 1 - -/* - * This function checks if extended error reporting is available for a given - * dasd_device. If yes, then it creates and returns a struct dasd_eer, - * otherwise it returns an -EPERM error pointer. - */ -struct dasd_eer_private * -dasd_eer_probe(struct dasd_device *device) -{ - struct dasd_eer_private *private; - - if (!(device && device->discipline - && !strcmp(device->discipline->name, "ECKD"))) { - return ERR_PTR(-EPERM); - } - /* allocate the private data structure */ - private = (struct dasd_eer_private *)kmalloc( - sizeof(struct dasd_eer_private), GFP_KERNEL); - if (!private) { - return ERR_PTR(-ENOMEM); - } - INIT_WORK(&private->worker, dasd_eer_sense_subsystem_status, - (void *)device); - private->cqr = dasd_kmalloc_request("ECKD", - 1 /* SNSS */ , - SNSS_DATA_SIZE , - device); - if (!private->cqr) { - kfree(private); - return ERR_PTR(-ENOMEM); - } - private->flags = 0; - return private; -}; - -/* - * If our private SNSS request is queued, remove it from the - * dasd ccw queue so we can free the requests memory. - */ -static void -dasd_eer_dequeue_SNSS_request(struct dasd_device *device, - struct dasd_eer_private *eer) -{ - struct list_head *lst, *nxt; - struct dasd_ccw_req *cqr, *erpcqr; - dasd_erp_fn_t erp_fn; - - spin_lock_irq(get_ccwdev_lock(device->cdev)); - list_for_each_safe(lst, nxt, &device->ccw_queue) { - cqr = list_entry(lst, struct dasd_ccw_req, list); - /* we are looking for two kinds or requests */ - /* first kind: our SNSS request: */ - if (cqr == eer->cqr) { - if (cqr->status == DASD_CQR_IN_IO) - device->discipline->term_IO(cqr); - list_del(&cqr->list); - break; - } - /* second kind: ERP requests for our SNSS request */ - if (cqr->refers) { - /* If this erp request chain ends in our cqr, then */ - /* cal the erp_postaction to clean it up */ - erpcqr = cqr; - while (erpcqr->refers) { - erpcqr = erpcqr->refers; - } - if (erpcqr == eer->cqr) { - erp_fn = device->discipline->erp_postaction( - cqr); - erp_fn(cqr); - } - continue; - } - } - spin_unlock_irq(get_ccwdev_lock(device->cdev)); -} - -/* - * This function dismantles a struct dasd_eer that was created by - * dasd_eer_probe. Since we want to free our private data structure, - * we must make sure that the memory is not in use anymore. - * We have to flush the work queue and remove a possible SNSS request - * from the dasd queue. - */ -static void -dasd_eer_destroy(struct dasd_device *device, struct dasd_eer_private *eer) -{ - flush_workqueue(dasd_eer_workqueue); - dasd_eer_dequeue_SNSS_request(device, eer); - dasd_kfree_request(eer->cqr, device); - kfree(eer); -}; - -/* - * enable the extended error reporting for a particular device - */ -static int -dasd_eer_enable_on_device(struct dasd_device *device) -{ - void *eer; - if (!device) - return -ENODEV; - if (device->eer) - return 0; - if (!try_module_get(THIS_MODULE)) { - return -EINVAL; - } - eer = (void *)dasd_eer_probe(device); - if (IS_ERR(eer)) { - module_put(THIS_MODULE); - return PTR_ERR(eer); - } - device->eer = eer; - return 0; -} - -/* - * enable the extended error reporting for a particular device - */ -static int -dasd_eer_disable_on_device(struct dasd_device *device) -{ - struct dasd_eer_private *eer = device->eer; - - if (!device) - return -ENODEV; - if (!device->eer) - return 0; - device->eer = NULL; - dasd_eer_destroy(device,eer); - module_put(THIS_MODULE); - - return 0; -} - -/* - * Set extended error reporting (eer) - * Note: This will be registered as a DASD ioctl, to be called on DASD devices. - */ -static int -dasd_ioctl_set_eer(struct block_device *bdev, int no, long args) -{ - struct dasd_device *device; - int intval; - - if (!capable(CAP_SYS_ADMIN)) - return -EACCES; - if (bdev != bdev->bd_contains) - /* Error-reporting is not allowed for partitions */ - return -EINVAL; - if (get_user(intval, (int __user *) args)) - return -EFAULT; - device = bdev->bd_disk->private_data; - if (device == NULL) - return -ENODEV; - - intval = (intval != 0); - DEV_MESSAGE (KERN_DEBUG, device, - "set eer on device to %d", intval); - if (intval) - return dasd_eer_enable_on_device(device); - else - return dasd_eer_disable_on_device(device); -} - -/* - * Get value of extended error reporting. - * Note: This will be registered as a DASD ioctl, to be called on DASD devices. - */ -static int -dasd_ioctl_get_eer(struct block_device *bdev, int no, long args) -{ - struct dasd_device *device; - - device = bdev->bd_disk->private_data; - if (device == NULL) - return -ENODEV; - return put_user((device->eer != NULL), (int __user *) args); -} - -/* * The following function can be used for those triggers that have * all necessary data available when the function is called. * If the parameter cqr is not NULL, the chain of requests will be searched * for valid sense data, and all valid sense data sets will be added to * the triggers data. */ -static int -dasd_eer_write_standard_trigger(int trigger, struct dasd_device *device, - struct dasd_ccw_req *cqr) +static void dasd_eer_write_standard_trigger(struct dasd_device *device, + struct dasd_ccw_req *cqr, + int trigger) { struct dasd_ccw_req *temp_cqr; int data_size; @@ -622,13 +297,10 @@ dasd_eer_write_standard_trigger(int trigger, struct dasd_device *device, struct eerbuffer *eerb; /* go through cqr chain and count the valid sense data sets */ - temp_cqr = cqr; data_size = 0; - while (temp_cqr) { + for (temp_cqr = cqr; temp_cqr; temp_cqr = temp_cqr->refers) if (temp_cqr->irb.esw.esw0.erw.cons) data_size += 32; - temp_cqr = temp_cqr->refers; - } header.total_size = sizeof(header) + data_size + 4; /* "EOR" */ header.trigger = trigger; @@ -640,28 +312,22 @@ dasd_eer_write_standard_trigger(int trigger, struct dasd_device *device, spin_lock_irqsave(&bufferlock, flags); list_for_each_entry(eerb, &bufferlist, list) { dasd_eer_start_record(eerb, header.total_size); - dasd_eer_write_buffer(eerb, sizeof(header), (char*)(&header)); - temp_cqr = cqr; - while (temp_cqr) { + dasd_eer_write_buffer(eerb, (char *) &header, sizeof(header)); + for (temp_cqr = cqr; temp_cqr; temp_cqr = temp_cqr->refers) if (temp_cqr->irb.esw.esw0.erw.cons) - dasd_eer_write_buffer(eerb, 32, cqr->irb.ecw); - temp_cqr = temp_cqr->refers; - } - dasd_eer_write_buffer(eerb, 4,"EOR"); + dasd_eer_write_buffer(eerb, cqr->irb.ecw, 32); + dasd_eer_write_buffer(eerb, "EOR", 4); } spin_unlock_irqrestore(&bufferlock, flags); - wake_up_interruptible(&dasd_eer_read_wait_queue); - - return 0; } /* * This function writes a DASD_EER_STATECHANGE trigger. */ -static void -dasd_eer_write_SNSS_trigger(struct dasd_device *device, - struct dasd_ccw_req *cqr) +static void dasd_eer_write_snss_trigger(struct dasd_device *device, + struct dasd_ccw_req *cqr, + int trigger) { int data_size; int snss_rc; @@ -686,167 +352,160 @@ dasd_eer_write_SNSS_trigger(struct dasd_device *device, spin_lock_irqsave(&bufferlock, flags); list_for_each_entry(eerb, &bufferlist, list) { dasd_eer_start_record(eerb, header.total_size); - dasd_eer_write_buffer(eerb, sizeof(header),(char*)(&header)); + dasd_eer_write_buffer(eerb, (char *) &header , sizeof(header)); if (!snss_rc) - dasd_eer_write_buffer(eerb, SNSS_DATA_SIZE, cqr->data); - dasd_eer_write_buffer(eerb, 4,"EOR"); + dasd_eer_write_buffer(eerb, cqr->data, SNSS_DATA_SIZE); + dasd_eer_write_buffer(eerb, "EOR", 4); } spin_unlock_irqrestore(&bufferlock, flags); - wake_up_interruptible(&dasd_eer_read_wait_queue); } /* - * callback function for use with SNSS request + * This function is called for all triggers. It calls the appropriate + * function that writes the actual trigger records. */ -static void -dasd_eer_SNSS_cb(struct dasd_ccw_req *cqr, void *data) +void dasd_eer_write(struct dasd_device *device, struct dasd_ccw_req *cqr, + unsigned int id) { - struct dasd_device *device; - struct dasd_eer_private *private; - unsigned long irqflags; - - device = (struct dasd_device *)data; - private = (struct dasd_eer_private *)device->eer; - dasd_eer_write_SNSS_trigger(device, cqr); - spin_lock_irqsave(&snsslock, irqflags); - if(!test_and_clear_bit(SNSS_REQUESTED, &private->flags)) { - clear_bit(CQR_IN_USE, &private->flags); - spin_unlock_irqrestore(&snsslock, irqflags); + if (!device->eer_cqr) return; - }; - clear_bit(CQR_IN_USE, &private->flags); - spin_unlock_irqrestore(&snsslock, irqflags); - dasd_eer_sense_subsystem_status(device); - return; + switch (id) { + case DASD_EER_FATALERROR: + case DASD_EER_PPRCSUSPEND: + dasd_eer_write_standard_trigger(device, cqr, id); + break; + case DASD_EER_NOPATH: + dasd_eer_write_standard_trigger(device, NULL, id); + break; + case DASD_EER_STATECHANGE: + dasd_eer_write_snss_trigger(device, cqr, id); + break; + default: /* unknown trigger, so we write it without any sense data */ + dasd_eer_write_standard_trigger(device, NULL, id); + break; + } +} +EXPORT_SYMBOL(dasd_eer_write); + +/* + * Start a sense subsystem status request. + * Needs to be called with the device held. + */ +void dasd_eer_snss(struct dasd_device *device) +{ + struct dasd_ccw_req *cqr; + + cqr = device->eer_cqr; + if (!cqr) /* Device not eer enabled. */ + return; + if (test_and_set_bit(DASD_FLAG_EER_IN_USE, &device->flags)) { + /* Sense subsystem status request in use. */ + set_bit(DASD_FLAG_EER_SNSS, &device->flags); + return; + } + clear_bit(DASD_FLAG_EER_SNSS, &device->flags); + cqr->status = DASD_CQR_QUEUED; + list_add(&cqr->list, &device->ccw_queue); + dasd_schedule_bh(device); } /* - * clean a used cqr before using it again + * Callback function for use with sense subsystem status request. */ -static void -dasd_eer_clean_SNSS_request(struct dasd_ccw_req *cqr) +static void dasd_eer_snss_cb(struct dasd_ccw_req *cqr, void *data) { - struct ccw1 *cpaddr = cqr->cpaddr; - void *data = cqr->data; - - memset(cqr, 0, sizeof(struct dasd_ccw_req)); - memset(cpaddr, 0, sizeof(struct ccw1)); - memset(data, 0, SNSS_DATA_SIZE); - cqr->cpaddr = cpaddr; - cqr->data = data; - strncpy((char *) &cqr->magic, "ECKD", 4); - ASCEBC((char *) &cqr->magic, 4); - set_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags); + struct dasd_device *device = cqr->device; + unsigned long flags; + + dasd_eer_write(device, cqr, DASD_EER_STATECHANGE); + spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); + if (device->eer_cqr == cqr) { + clear_bit(DASD_FLAG_EER_IN_USE, &device->flags); + if (test_bit(DASD_FLAG_EER_SNSS, &device->flags)) + /* Another SNSS has been requested in the meantime. */ + dasd_eer_snss(device); + cqr = NULL; + } + spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); + if (cqr) + /* + * Extended error recovery has been switched off while + * the SNSS request was running. It could even have + * been switched off and on again in which case there + * is a new ccw in device->eer_cqr. Free the "old" + * snss request now. + */ + dasd_kfree_request(cqr, device); } /* - * build and start an SNSS request - * This function is called from a work queue so we have to - * pass the dasd_device pointer as a void pointer. + * Enable error reporting on a given device. */ -static void -dasd_eer_sense_subsystem_status(void *data) +int dasd_eer_enable(struct dasd_device *device) { - struct dasd_device *device; - struct dasd_eer_private *private; struct dasd_ccw_req *cqr; - struct ccw1 *ccw; - unsigned long irqflags; + unsigned long flags; + + if (device->eer_cqr) + return 0; + + if (!device->discipline || strcmp(device->discipline->name, "ECKD")) + return -EPERM; /* FIXME: -EMEDIUMTYPE ? */ + + cqr = dasd_kmalloc_request("ECKD", 1 /* SNSS */, + SNSS_DATA_SIZE, device); + if (!cqr) + return -ENOMEM; - device = (struct dasd_device *)data; - private = (struct dasd_eer_private *)device->eer; - if (!private) /* device not eer enabled any more */ - return; - cqr = private->cqr; - spin_lock_irqsave(&snsslock, irqflags); - if(test_and_set_bit(CQR_IN_USE, &private->flags)) { - set_bit(SNSS_REQUESTED, &private->flags); - spin_unlock_irqrestore(&snsslock, irqflags); - return; - }; - spin_unlock_irqrestore(&snsslock, irqflags); - dasd_eer_clean_SNSS_request(cqr); cqr->device = device; cqr->retries = 255; cqr->expires = 10 * HZ; - ccw = cqr->cpaddr; - ccw->cmd_code = DASD_ECKD_CCW_SNSS; - ccw->count = SNSS_DATA_SIZE; - ccw->flags = 0; - ccw->cda = (__u32)(addr_t)cqr->data; + cqr->cpaddr->cmd_code = DASD_ECKD_CCW_SNSS; + cqr->cpaddr->count = SNSS_DATA_SIZE; + cqr->cpaddr->flags = 0; + cqr->cpaddr->cda = (__u32)(addr_t) cqr->data; cqr->buildclk = get_clock(); cqr->status = DASD_CQR_FILLED; - cqr->callback = dasd_eer_SNSS_cb; - cqr->callback_data = (void *)device; - dasd_add_request_head(cqr); + cqr->callback = dasd_eer_snss_cb; - return; + spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); + if (!device->eer_cqr) { + device->eer_cqr = cqr; + cqr = NULL; + } + spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); + if (cqr) + dasd_kfree_request(cqr, device); + return 0; } /* - * This function is called for all triggers. It calls the appropriate - * function that writes the actual trigger records. + * Disable error reporting on a given device. */ -static int -dasd_eer_write_trigger(struct dasd_eer_trigger *trigger) +void dasd_eer_disable(struct dasd_device *device) { - int rc; - struct dasd_eer_private *private = trigger->device->eer; + struct dasd_ccw_req *cqr; + unsigned long flags; + int in_use; - switch (trigger->id) { - case DASD_EER_FATALERROR: - case DASD_EER_PPRCSUSPEND: - rc = dasd_eer_write_standard_trigger( - trigger->id, trigger->device, trigger->cqr); - break; - case DASD_EER_NOPATH: - rc = dasd_eer_write_standard_trigger( - trigger->id, trigger->device, NULL); - break; - case DASD_EER_STATECHANGE: - if (queue_work(dasd_eer_workqueue, &private->worker)) { - rc=0; - } else { - /* If the work_struct was already queued, it can't - * be queued again. But this is OK since we don't - * need to have it queued twice. - */ - rc = -EBUSY; - } - break; - default: /* unknown trigger, so we write it without any sense data */ - rc = dasd_eer_write_standard_trigger( - trigger->id, trigger->device, NULL); - break; - } - return rc; + if (!device->eer_cqr) + return; + spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); + cqr = device->eer_cqr; + device->eer_cqr = NULL; + clear_bit(DASD_FLAG_EER_SNSS, &device->flags); + in_use = test_and_clear_bit(DASD_FLAG_EER_IN_USE, &device->flags); + spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); + if (cqr && !in_use) + dasd_kfree_request(cqr, device); } /* - * This function is registered with the dasd device driver and gets called - * for all dasd eer notifications. + * SECTION: the device operations */ -static int dasd_eer_notify(struct notifier_block *self, - unsigned long action, void *data) -{ - switch (action) { - case DASD_EER_DISABLE: - dasd_eer_disable_on_device((struct dasd_device *)data); - break; - case DASD_EER_TRIGGER: - dasd_eer_write_trigger((struct dasd_eer_trigger *)data); - break; - } - return NOTIFY_OK; -} - - -/*****************************************************************************/ -/* the device operations */ -/*****************************************************************************/ /* * On the one side we need a lock to access our internal buffer, on the @@ -854,28 +513,34 @@ static int dasd_eer_notify(struct notifier_block *self, * to transfer in a readbuffer, which is protected by the readbuffer_mutex. */ static char readbuffer[PAGE_SIZE]; -DECLARE_MUTEX(readbuffer_mutex); - +static DECLARE_MUTEX(readbuffer_mutex); -static int -dasd_eer_open(struct inode *inp, struct file *filp) +static int dasd_eer_open(struct inode *inp, struct file *filp) { struct eerbuffer *eerb; unsigned long flags; - eerb = kmalloc(sizeof(struct eerbuffer), GFP_KERNEL); - eerb->head = 0; - eerb->tail = 0; - eerb->residual = 0; - eerb->buffer_page_count = 1; + eerb = kzalloc(sizeof(struct eerbuffer), GFP_KERNEL); + eerb->buffer_page_count = eer_pages; + if (eerb->buffer_page_count < 1 || + eerb->buffer_page_count > INT_MAX / PAGE_SIZE) { + kfree(eerb); + MESSAGE(KERN_WARNING, "can't open device since module " + "parameter eer_pages is smaller then 1 or" + " bigger then %d", (int)(INT_MAX / PAGE_SIZE)); + return -EINVAL; + } eerb->buffersize = eerb->buffer_page_count * PAGE_SIZE; - eerb->buffer = kmalloc(eerb->buffer_page_count*sizeof(char*), + eerb->buffer = kmalloc(eerb->buffer_page_count * sizeof(char *), GFP_KERNEL); - if (!eerb->buffer) + if (!eerb->buffer) { + kfree(eerb); return -ENOMEM; + } if (dasd_eer_allocate_buffer_pages(eerb->buffer, eerb->buffer_page_count)) { kfree(eerb->buffer); + kfree(eerb); return -ENOMEM; } filp->private_data = eerb; @@ -886,13 +551,12 @@ dasd_eer_open(struct inode *inp, struct file *filp) return nonseekable_open(inp,filp); } -static int -dasd_eer_close(struct inode *inp, struct file *filp) +static int dasd_eer_close(struct inode *inp, struct file *filp) { struct eerbuffer *eerb; unsigned long flags; - eerb = (struct eerbuffer *)filp->private_data; + eerb = (struct eerbuffer *) filp->private_data; spin_lock_irqsave(&bufferlock, flags); list_del(&eerb->list); spin_unlock_irqrestore(&bufferlock, flags); @@ -903,36 +567,16 @@ dasd_eer_close(struct inode *inp, struct file *filp) return 0; } -static long -dasd_eer_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) -{ - int intval; - struct eerbuffer *eerb; - - eerb = (struct eerbuffer *)filp->private_data; - switch (cmd) { - case DASD_EER_PURGE: - dasd_eer_purge_buffer(eerb); - return 0; - case DASD_EER_SETBUFSIZE: - if (get_user(intval, (int __user *)arg)) - return -EFAULT; - return dasd_eer_resize_buffer(eerb, intval); - default: - return -ENOIOCTLCMD; - } -} - -static ssize_t -dasd_eer_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) +static ssize_t dasd_eer_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) { int tc,rc; int tailcount,effective_count; unsigned long flags; struct eerbuffer *eerb; - eerb = (struct eerbuffer *)filp->private_data; - if(down_interruptible(&readbuffer_mutex)) + eerb = (struct eerbuffer *) filp->private_data; + if (down_interruptible(&readbuffer_mutex)) return -ERESTARTSYS; spin_lock_irqsave(&bufferlock, flags); @@ -945,13 +589,13 @@ dasd_eer_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) return -EIO; } else if (eerb->residual > 0) { /* OK we still have a second half of a record to deliver */ - effective_count = min(eerb->residual, (int)count); + effective_count = min(eerb->residual, (int) count); eerb->residual -= effective_count; } else { tc = 0; while (!tc) { - tc = dasd_eer_read_buffer(eerb, - sizeof(tailcount), (char*)(&tailcount)); + tc = dasd_eer_read_buffer(eerb, (char *) &tailcount, + sizeof(tailcount)); if (!tc) { /* no data available */ spin_unlock_irqrestore(&bufferlock, flags); @@ -961,10 +605,9 @@ dasd_eer_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) rc = wait_event_interruptible( dasd_eer_read_wait_queue, eerb->head != eerb->tail); - if (rc) { + if (rc) return rc; - } - if(down_interruptible(&readbuffer_mutex)) + if (down_interruptible(&readbuffer_mutex)) return -ERESTARTSYS; spin_lock_irqsave(&bufferlock, flags); } @@ -974,7 +617,7 @@ dasd_eer_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) eerb->residual = tailcount - effective_count; } - tc = dasd_eer_read_buffer(eerb, effective_count, readbuffer); + tc = dasd_eer_read_buffer(eerb, readbuffer, effective_count); WARN_ON(tc != effective_count); spin_unlock_irqrestore(&bufferlock, flags); @@ -988,14 +631,13 @@ dasd_eer_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) return effective_count; } -static unsigned int -dasd_eer_poll (struct file *filp, poll_table *ptable) +static unsigned int dasd_eer_poll(struct file *filp, poll_table *ptable) { unsigned int mask; unsigned long flags; struct eerbuffer *eerb; - eerb = (struct eerbuffer *)filp->private_data; + eerb = (struct eerbuffer *) filp->private_data; poll_wait(filp, &dasd_eer_read_wait_queue, ptable); spin_lock_irqsave(&bufferlock, flags); if (eerb->head != eerb->tail) @@ -1009,8 +651,6 @@ dasd_eer_poll (struct file *filp, poll_table *ptable) static struct file_operations dasd_eer_fops = { .open = &dasd_eer_open, .release = &dasd_eer_close, - .unlocked_ioctl = &dasd_eer_ioctl, - .compat_ioctl = &dasd_eer_ioctl, .read = &dasd_eer_read, .poll = &dasd_eer_poll, .owner = THIS_MODULE, @@ -1022,69 +662,21 @@ static struct miscdevice dasd_eer_dev = { .fops = &dasd_eer_fops, }; - -/*****************************************************************************/ -/* Init and exit */ -/*****************************************************************************/ - -static int -__init dasd_eer_init(void) +int __init dasd_eer_init(void) { int rc; - dasd_eer_workqueue = create_singlethread_workqueue("dasd_eer"); - if (!dasd_eer_workqueue) { - MESSAGE(KERN_ERR , "%s", "dasd_eer_init could not " - "create workqueue \n"); - rc = -ENOMEM; - goto out; - } - - rc = dasd_register_eer_notifier(&dasd_eer_nb); - if (rc) { - MESSAGE(KERN_ERR, "%s", "dasd_eer_init could not " - "register error reporting"); - goto queue; - } - - dasd_ioctl_no_register(THIS_MODULE, BIODASDEERSET, dasd_ioctl_set_eer); - dasd_ioctl_no_register(THIS_MODULE, BIODASDEERGET, dasd_ioctl_get_eer); - - /* we don't need our own character device, - * so we just register as misc device */ rc = misc_register(&dasd_eer_dev); if (rc) { MESSAGE(KERN_ERR, "%s", "dasd_eer_init could not " "register misc device"); - goto unregister; + return rc; } return 0; - -unregister: - dasd_unregister_eer_notifier(&dasd_eer_nb); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDEERSET, - dasd_ioctl_set_eer); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDEERGET, - dasd_ioctl_get_eer); -queue: - destroy_workqueue(dasd_eer_workqueue); -out: - return rc; - } -module_init(dasd_eer_init); -static void -__exit dasd_eer_exit(void) +void __exit dasd_eer_exit(void) { - dasd_unregister_eer_notifier(&dasd_eer_nb); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDEERSET, - dasd_ioctl_set_eer); - dasd_ioctl_no_unregister(THIS_MODULE, BIODASDEERGET, - dasd_ioctl_get_eer); - destroy_workqueue(dasd_eer_workqueue); - WARN_ON(misc_deregister(&dasd_eer_dev) != 0); } -module_exit(dasd_eer_exit); |