blob: f70cd7716b24460b040fa083c8ee62ad4c99ea29 [file] [log] [blame]
/*
* 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>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#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>
#include <asm/atomic.h>
#include <asm/ebcdic.h>
#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 */
/*****************************************************************************/
/*
* The internal buffer is meant to store obaque blobs of data, so it doesn't
* 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.
*
* 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_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.
* If the ringbuffer is full, dasd_eer_start_record will remove the required
* number of old records.
*
* A record is typically read in two steps, first read the integer that
* specifies the size of the following data, then read the data.
* 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
* 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
* 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.
* All entries in the eerbuffer structure are protected with the bufferlock.
* To avoid races between writing to a buffer on the one side and creating
* and destroying buffers on the other side, the bufferlock must also be used
* to protect the bufferlist.
*/
struct eerbuffer {
struct list_head list;
char **buffer;
int buffersize;
int buffer_page_count;
int head;
int tail;
int residual;
};
LIST_HEAD(bufferlist);
static spinlock_t bufferlock = SPIN_LOCK_UNLOCKED;
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
*/
static int
dasd_eer_get_free_bytes(struct eerbuffer *eerb)
{
if (eerb->head < eerb->tail) {
return eerb->tail - eerb->head - 1;
} else
return eerb->buffersize - eerb->head + eerb->tail -1;
}
/*
* How many bytes of buffer space are used.
* needs to be called with bufferlock held
*/
static int
dasd_eer_get_filled_bytes(struct eerbuffer *eerb)
{
if (eerb->head >= eerb->tail) {
return eerb->head - eerb->tail;
} else
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
*/
static void
dasd_eer_write_buffer(struct eerbuffer *eerb, int count, char *data)
{
unsigned long headindex,localhead;
unsigned long rest, len;
char *nextdata;
nextdata = data;
rest = count;
while (rest > 0) {
headindex = eerb->head / PAGE_SIZE;
localhead = eerb->head % PAGE_SIZE;
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 )
eerb->head = 0; /* wrap around */
if (eerb->head > eerb->buffersize) {
MESSAGE(KERN_ERR, "%s", "runaway buffer head.");
BUG();
}
}
}
/*
* needs to be called with bufferlock held
*/
static int
dasd_eer_read_buffer(struct eerbuffer *eerb, int count, char *data)
{
unsigned long tailindex,localtail;
unsigned long rest, len, finalcount;
char *nextdata;
finalcount = min(count, dasd_eer_get_filled_bytes(eerb));
nextdata = data;
rest = finalcount;
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);
nextdata += len;
rest -= len;
eerb->tail += len;
if ( eerb->tail == eerb->buffersize )
eerb->tail = 0; /* wrap around */
if (eerb->tail > eerb->buffersize) {
MESSAGE(KERN_ERR, "%s", "runaway buffer tail.");
BUG();
}
}
return finalcount;
}
/*
* Whenever you want to write a blob of data to the internal buffer you
* 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
*/
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)) {
if (eerb->residual > 0) {
eerb->tail += eerb->residual;
if (eerb->tail >= eerb->buffersize)
eerb->tail -= eerb->buffersize;
eerb->residual = -1;
}
dasd_eer_read_buffer(eerb, sizeof(tailcount),
(char*)(&tailcount));
eerb->tail += tailcount;
if (eerb->tail >= eerb->buffersize)
eerb->tail -= eerb->buffersize;
}
dasd_eer_write_buffer(eerb, sizeof(count), (char*)(&count));
return 0;
};
/*
* release pages that are not used anymore
*/
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]);
}
}
/*
* allocate a new set of memory pages
*/
static int
dasd_eer_allocate_buffer_pages(char **buf, int no_pages)
{
int 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);
return -ENOMEM;
}
}
return 0;
}
/*
* 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.
*/
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:
*
* DASD_EER_FATALERROR: all kinds of unrecoverable I/O problems
* DASD_EER_PPRCSUSPEND: PPRC was suspended
* DASD_EER_NOPATH: There is no path to the device left.
* DASD_EER_STATECHANGE: The state of the device has changed.
*
* For the first three triggers all required information can be supplied by
* 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.
*
* 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.
*/
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 {
__u32 total_size;
__u32 trigger;
__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)
{
struct dasd_ccw_req *temp_cqr;
int data_size;
struct timeval tv;
struct dasd_eer_header header;
unsigned long flags;
struct eerbuffer *eerb;
/* go through cqr chain and count the valid sense data sets */
temp_cqr = cqr;
data_size = 0;
while (temp_cqr) {
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;
do_gettimeofday(&tv);
header.tv_sec = tv.tv_sec;
header.tv_usec = tv.tv_usec;
strncpy(header.busid, device->cdev->dev.bus_id, DASD_EER_BUSID_SIZE);
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) {
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");
}
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)
{
int data_size;
int snss_rc;
struct timeval tv;
struct dasd_eer_header header;
unsigned long flags;
struct eerbuffer *eerb;
snss_rc = (cqr->status == DASD_CQR_FAILED) ? -EIO : 0;
if (snss_rc)
data_size = 0;
else
data_size = SNSS_DATA_SIZE;
header.total_size = sizeof(header) + data_size + 4; /* "EOR" */
header.trigger = DASD_EER_STATECHANGE;
do_gettimeofday(&tv);
header.tv_sec = tv.tv_sec;
header.tv_usec = tv.tv_usec;
strncpy(header.busid, device->cdev->dev.bus_id, DASD_EER_BUSID_SIZE);
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));
if (!snss_rc)
dasd_eer_write_buffer(eerb, SNSS_DATA_SIZE, cqr->data);
dasd_eer_write_buffer(eerb, 4,"EOR");
}
spin_unlock_irqrestore(&bufferlock, flags);
wake_up_interruptible(&dasd_eer_read_wait_queue);
}
/*
* callback function for use with SNSS request
*/
static void
dasd_eer_SNSS_cb(struct dasd_ccw_req *cqr, void *data)
{
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);
return;
};
clear_bit(CQR_IN_USE, &private->flags);
spin_unlock_irqrestore(&snsslock, irqflags);
dasd_eer_sense_subsystem_status(device);
return;
}
/*
* clean a used cqr before using it again
*/
static void
dasd_eer_clean_SNSS_request(struct dasd_ccw_req *cqr)
{
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);
}
/*
* 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.
*/
static void
dasd_eer_sense_subsystem_status(void *data)
{
struct dasd_device *device;
struct dasd_eer_private *private;
struct dasd_ccw_req *cqr;
struct ccw1 *ccw;
unsigned long irqflags;
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->buildclk = get_clock();
cqr->status = DASD_CQR_FILLED;
cqr->callback = dasd_eer_SNSS_cb;
cqr->callback_data = (void *)device;
dasd_add_request_head(cqr);
return;
}
/*
* This function is called for all triggers. It calls the appropriate
* function that writes the actual trigger records.
*/
static int
dasd_eer_write_trigger(struct dasd_eer_trigger *trigger)
{
int rc;
struct dasd_eer_private *private = trigger->device->eer;
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;
}
/*
* This function is registered with the dasd device driver and gets called
* for all dasd eer notifications.
*/
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
* other side a copy_to_user can sleep. So we need to copy the data we have
* to transfer in a readbuffer, which is protected by the readbuffer_mutex.
*/
static char readbuffer[PAGE_SIZE];
DECLARE_MUTEX(readbuffer_mutex);
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->buffersize = eerb->buffer_page_count * PAGE_SIZE;
eerb->buffer = kmalloc(eerb->buffer_page_count*sizeof(char*),
GFP_KERNEL);
if (!eerb->buffer)
return -ENOMEM;
if (dasd_eer_allocate_buffer_pages(eerb->buffer,
eerb->buffer_page_count)) {
kfree(eerb->buffer);
return -ENOMEM;
}
filp->private_data = eerb;
spin_lock_irqsave(&bufferlock, flags);
list_add(&eerb->list, &bufferlist);
spin_unlock_irqrestore(&bufferlock, flags);
return nonseekable_open(inp,filp);
}
static int
dasd_eer_close(struct inode *inp, struct file *filp)
{
struct eerbuffer *eerb;
unsigned long flags;
eerb = (struct eerbuffer *)filp->private_data;
spin_lock_irqsave(&bufferlock, flags);
list_del(&eerb->list);
spin_unlock_irqrestore(&bufferlock, flags);
dasd_eer_free_buffer_pages(eerb->buffer, eerb->buffer_page_count);
kfree(eerb->buffer);
kfree(eerb);
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)
{
int tc,rc;
int tailcount,effective_count;
unsigned long flags;
struct eerbuffer *eerb;
eerb = (struct eerbuffer *)filp->private_data;
if(down_interruptible(&readbuffer_mutex))
return -ERESTARTSYS;
spin_lock_irqsave(&bufferlock, flags);
if (eerb->residual < 0) { /* the remainder of this record */
/* has been deleted */
eerb->residual = 0;
spin_unlock_irqrestore(&bufferlock, flags);
up(&readbuffer_mutex);
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);
eerb->residual -= effective_count;
} else {
tc = 0;
while (!tc) {
tc = dasd_eer_read_buffer(eerb,
sizeof(tailcount), (char*)(&tailcount));
if (!tc) {
/* no data available */
spin_unlock_irqrestore(&bufferlock, flags);
up(&readbuffer_mutex);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
rc = wait_event_interruptible(
dasd_eer_read_wait_queue,
eerb->head != eerb->tail);
if (rc) {
return rc;
}
if(down_interruptible(&readbuffer_mutex))
return -ERESTARTSYS;
spin_lock_irqsave(&bufferlock, flags);
}
}
WARN_ON(tc != sizeof(tailcount));
effective_count = min(tailcount,(int)count);
eerb->residual = tailcount - effective_count;
}
tc = dasd_eer_read_buffer(eerb, effective_count, readbuffer);
WARN_ON(tc != effective_count);
spin_unlock_irqrestore(&bufferlock, flags);
if (copy_to_user(buf, readbuffer, effective_count)) {
up(&readbuffer_mutex);
return -EFAULT;
}
up(&readbuffer_mutex);
return effective_count;
}
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;
poll_wait(filp, &dasd_eer_read_wait_queue, ptable);
spin_lock_irqsave(&bufferlock, flags);
if (eerb->head != eerb->tail)
mask = POLLIN | POLLRDNORM ;
else
mask = 0;
spin_unlock_irqrestore(&bufferlock, flags);
return mask;
}
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,
};
static struct miscdevice dasd_eer_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "dasd_eer",
.fops = &dasd_eer_fops,
};
/*****************************************************************************/
/* Init and exit */
/*****************************************************************************/
static 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 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)
{
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);