/*
* 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, final