aboutsummaryrefslogtreecommitdiff
path: root/drivers/s390/char/tape_core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/s390/char/tape_core.c')
-rw-r--r--drivers/s390/char/tape_core.c509
1 files changed, 299 insertions, 210 deletions
diff --git a/drivers/s390/char/tape_core.c b/drivers/s390/char/tape_core.c
index 8f486e1a850..f3b5123faf0 100644
--- a/drivers/s390/char/tape_core.c
+++ b/drivers/s390/char/tape_core.c
@@ -1,9 +1,8 @@
/*
- * drivers/s390/char/tape_core.c
* basic function of the tape device driver
*
* S390 and zSeries version
- * Copyright (C) 2001,2005 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * 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>
@@ -11,13 +10,16 @@
* Stefan Bader <shbader@de.ibm.com>
*/
-#include <linux/config.h>
+#define KMSG_COMPONENT "tape"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
#include <linux/module.h>
#include <linux/init.h> // for kernel parameters
#include <linux/kmod.h> // for requesting modules
#include <linux/spinlock.h> // for locks
#include <linux/vmalloc.h>
#include <linux/list.h>
+#include <linux/slab.h>
#include <asm/types.h> // for variable types
@@ -26,17 +28,18 @@
#include "tape.h"
#include "tape_std.h"
-#define PRINTK_HEADER "TAPE_CORE: "
+#define LONG_BUSY_TIMEOUT 180 /* seconds */
static void __tape_do_irq (struct ccw_device *, unsigned long, struct irb *);
-static void tape_delayed_next_request(void * data);
+static void tape_delayed_next_request(struct work_struct *);
+static void tape_long_busy_timeout(unsigned long data);
/*
* One list to contain all tape devices of all disciplines, so
* we can assign the devices to minor numbers of the same major
* The list is protected by the rwlock
*/
-static struct list_head tape_device_list = LIST_HEAD_INIT(tape_device_list);
+static LIST_HEAD(tape_device_list);
static DEFINE_RWLOCK(tape_device_lock);
/*
@@ -70,35 +73,14 @@ const char *tape_op_verbose[TO_SIZE] =
[TO_LOAD] = "LOA", [TO_READ_CONFIG] = "RCF",
[TO_READ_ATTMSG] = "RAT",
[TO_DIS] = "DIS", [TO_ASSIGN] = "ASS",
- [TO_UNASSIGN] = "UAS"
+ [TO_UNASSIGN] = "UAS", [TO_CRYPT_ON] = "CON",
+ [TO_CRYPT_OFF] = "COF", [TO_KEKL_SET] = "KLS",
+ [TO_KEKL_QUERY] = "KLQ",[TO_RDC] = "RDC",
};
-static inline int
-busid_to_int(char *bus_id)
+static int devid_to_int(struct ccw_dev_id *dev_id)
{
- int dec;
- int d;
- char * s;
-
- for(s = bus_id, d = 0; *s != '\0' && *s != '.'; s++)
- d = (d * 10) + (*s - '0');
- dec = d;
- for(s++, d = 0; *s != '\0' && *s != '.'; s++)
- d = (d * 10) + (*s - '0');
- dec = (dec << 8) + d;
-
- for(s++; *s != '\0'; s++) {
- if (*s >= '0' && *s <= '9') {
- d = *s - '0';
- } else if (*s >= 'a' && *s <= 'f') {
- d = *s - 'a' + 10;
- } else {
- d = *s - 'A' + 10;
- }
- dec = (dec << 4) + d;
- }
-
- return dec;
+ return dev_id->devno + (dev_id->ssid << 16);
}
/*
@@ -112,7 +94,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);
}
@@ -124,7 +106,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);
}
@@ -136,7 +118,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]);
}
@@ -150,7 +132,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");
@@ -176,7 +158,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);
}
@@ -210,18 +192,14 @@ tape_state_set(struct tape_device *device, enum tape_state newstate)
return;
}
DBF_EVENT(4, "ts. dev: %x\n", device->first_minor);
- if (device->tape_state < TO_SIZE && device->tape_state >= 0)
- str = tape_state_verbose[device->tape_state];
- else
- str = "UNKNOWN TS";
- DBF_EVENT(4, "old ts: %s\n", str);
- if (device->tape_state < TO_SIZE && device->tape_state >=0 )
+ DBF_EVENT(4, "old ts:\t\n");
+ if (device->tape_state < TS_SIZE && device->tape_state >=0 )
str = tape_state_verbose[device->tape_state];
else
str = "UNKNOWN TS";
DBF_EVENT(4, "%s\n", str);
DBF_EVENT(4, "new ts:\t\n");
- if (newstate < TO_SIZE && newstate >= 0)
+ if (newstate < TS_SIZE && newstate >= 0)
str = tape_state_verbose[newstate];
else
str = "UNKNOWN TS";
@@ -230,34 +208,86 @@ tape_state_set(struct tape_device *device, enum tape_state newstate)
wake_up(&device->state_change_wq);
}
+struct tape_med_state_work_data {
+ struct tape_device *device;
+ enum tape_medium_state state;
+ struct work_struct work;
+};
+
+static void
+tape_med_state_work_handler(struct work_struct *work)
+{
+ static char env_state_loaded[] = "MEDIUM_STATE=LOADED";
+ static char env_state_unloaded[] = "MEDIUM_STATE=UNLOADED";
+ struct tape_med_state_work_data *p =
+ container_of(work, struct tape_med_state_work_data, work);
+ struct tape_device *device = p->device;
+ char *envp[] = { NULL, NULL };
+
+ switch (p->state) {
+ case MS_UNLOADED:
+ pr_info("%s: The tape cartridge has been successfully "
+ "unloaded\n", dev_name(&device->cdev->dev));
+ envp[0] = env_state_unloaded;
+ kobject_uevent_env(&device->cdev->dev.kobj, KOBJ_CHANGE, envp);
+ break;
+ case MS_LOADED:
+ pr_info("%s: A tape cartridge has been mounted\n",
+ dev_name(&device->cdev->dev));
+ envp[0] = env_state_loaded;
+ kobject_uevent_env(&device->cdev->dev.kobj, KOBJ_CHANGE, envp);
+ break;
+ default:
+ break;
+ }
+ tape_put_device(device);
+ kfree(p);
+}
+
+static void
+tape_med_state_work(struct tape_device *device, enum tape_medium_state state)
+{
+ struct tape_med_state_work_data *p;
+
+ p = kzalloc(sizeof(*p), GFP_ATOMIC);
+ if (p) {
+ INIT_WORK(&p->work, tape_med_state_work_handler);
+ p->device = tape_get_device(device);
+ p->state = state;
+ schedule_work(&p->work);
+ }
+}
+
void
tape_med_state_set(struct tape_device *device, enum tape_medium_state newstate)
{
- if (device->medium_state == newstate)
+ enum tape_medium_state oldstate;
+
+ oldstate = device->medium_state;
+ if (oldstate == newstate)
return;
+ device->medium_state = newstate;
switch(newstate){
case MS_UNLOADED:
device->tape_generic_status |= GMT_DR_OPEN(~0);
- PRINT_INFO("(%s): Tape is unloaded\n",
- device->cdev->dev.bus_id);
+ if (oldstate == MS_LOADED)
+ tape_med_state_work(device, MS_UNLOADED);
break;
case MS_LOADED:
device->tape_generic_status &= ~GMT_DR_OPEN(~0);
- PRINT_INFO("(%s): Tape has been mounted\n",
- device->cdev->dev.bus_id);
+ if (oldstate == MS_UNLOADED)
+ tape_med_state_work(device, MS_LOADED);
break;
default:
- // print nothing
break;
}
- device->medium_state = newstate;
wake_up(&device->state_change_wq);
}
/*
* Stop running ccw. Has to be called with the device lock held.
*/
-static inline int
+static int
__tape_cancel_io(struct tape_device *device, struct tape_request *request)
{
int retries;
@@ -277,7 +307,7 @@ __tape_cancel_io(struct tape_device *device, struct tape_request *request)
return 0;
case -EBUSY:
request->status = TAPE_REQUEST_CANCEL;
- schedule_work(&device->tape_dnr);
+ schedule_delayed_work(&device->tape_dnr, 0);
return 0;
case -ENODEV:
DBF_EXCEPTION(2, "device gone, retry\n");
@@ -351,10 +381,12 @@ tape_generic_online(struct tape_device *device,
return -EINVAL;
}
+ init_timer(&device->lb_timeout);
+ device->lb_timeout.function = tape_long_busy_timeout;
+
/* Let the discipline have a go at the device. */
device->discipline = discipline;
if (!try_module_get(discipline->owner)) {
- PRINT_ERR("Cannot get module. Module gone.\n");
return -EINVAL;
}
@@ -368,9 +400,6 @@ tape_generic_online(struct tape_device *device,
rc = tapechar_setup_device(device);
if (rc)
goto out_minor;
- rc = tapeblock_setup_device(device);
- if (rc)
- goto out_char;
tape_state_set(device, TS_UNUSED);
@@ -378,22 +407,19 @@ tape_generic_online(struct tape_device *device,
return 0;
-out_char:
- tapechar_cleanup_device(device);
+out_minor:
+ tape_remove_minor(device);
out_discipline:
device->discipline->cleanup_device(device);
device->discipline = NULL;
-out_minor:
- tape_remove_minor(device);
out:
module_put(discipline->owner);
return rc;
}
-static inline void
+static void
tape_cleanup_device(struct tape_device *device)
{
- tapeblock_cleanup_device(device);
tapechar_cleanup_device(device);
device->discipline->cleanup_device(device);
module_put(device->discipline->owner);
@@ -402,6 +428,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 = dev_get_drvdata(&cdev->dev);
+ 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
@@ -409,10 +484,12 @@ tape_cleanup_device(struct tape_device *device)
* Manual offline is only allowed while the drive is not in use.
*/
int
-tape_generic_offline(struct tape_device *device)
+tape_generic_offline(struct ccw_device *cdev)
{
+ struct tape_device *device;
+
+ device = dev_get_drvdata(&cdev->dev);
if (!device) {
- PRINT_ERR("tape_generic_offline: no such device\n");
return -ENODEV;
}
@@ -434,9 +511,6 @@ tape_generic_offline(struct tape_device *device)
DBF_EVENT(3, "(%08x): Set offline failed "
"- drive in use.\n",
device->cdev_id);
- PRINT_WARN("(%s): Set offline failed "
- "- drive in use.\n",
- device->cdev->dev.bus_id);
spin_unlock_irq(get_ccwdev_lock(device->cdev));
return -EBUSY;
}
@@ -453,31 +527,28 @@ tape_alloc_device(void)
{
struct tape_device *device;
- device = (struct tape_device *)
- kmalloc(sizeof(struct tape_device), GFP_KERNEL);
+ device = kzalloc(sizeof(struct tape_device), GFP_KERNEL);
if (device == NULL) {
DBF_EXCEPTION(2, "ti:no mem\n");
- PRINT_INFO ("can't allocate memory for "
- "tape info structure\n");
return ERR_PTR(-ENOMEM);
}
- memset(device, 0, sizeof(struct tape_device));
- device->modeset_byte = (char *) kmalloc(1, GFP_KERNEL | GFP_DMA);
+ device->modeset_byte = kmalloc(1, GFP_KERNEL | GFP_DMA);
if (device->modeset_byte == NULL) {
DBF_EXCEPTION(2, "ti:no mem\n");
- PRINT_INFO("can't allocate memory for modeset byte\n");
kfree(device);
return ERR_PTR(-ENOMEM);
}
+ mutex_init(&device->mutex);
INIT_LIST_HEAD(&device->req_queue);
INIT_LIST_HEAD(&device->node);
init_waitqueue_head(&device->state_change_wq);
+ init_waitqueue_head(&device->wait_queue);
device->tape_state = TS_INIT;
device->medium_state = MS_UNKNOWN;
*device->modeset_byte = 0;
device->first_minor = -1;
atomic_set(&device->ref_count, 1);
- INIT_WORK(&device->tape_dnr, tape_delayed_next_request, device);
+ INIT_DELAYED_WORK(&device->tape_dnr, tape_delayed_next_request);
return device;
}
@@ -487,11 +558,12 @@ tape_alloc_device(void)
* increment the reference count.
*/
struct tape_device *
-tape_get_device_reference(struct tape_device *device)
+tape_get_device(struct tape_device *device)
{
- DBF_EVENT(4, "tape_get_device_reference(%p) = %i\n", device,
- atomic_inc_return(&device->ref_count));
+ int count;
+ count = atomic_inc_return(&device->ref_count);
+ DBF_EVENT(4, "tape_get_device(%p) = %i\n", device, count);
return device;
}
@@ -501,33 +573,25 @@ tape_get_device_reference(struct tape_device *device)
* The function returns a NULL pointer to be used by the caller
* for clearing reference pointers.
*/
-struct tape_device *
+void
tape_put_device(struct tape_device *device)
{
- int remain;
+ int count;
- remain = atomic_dec_return(&device->ref_count);
- if (remain > 0) {
- DBF_EVENT(4, "tape_put_device(%p) -> %i\n", device, remain);
- } else {
- if (remain < 0) {
- DBF_EVENT(4, "put device without reference\n");
- PRINT_ERR("put device without reference\n");
- } else {
- DBF_EVENT(4, "tape_free_device(%p)\n", device);
- kfree(device->modeset_byte);
- kfree(device);
- }
+ count = atomic_dec_return(&device->ref_count);
+ DBF_EVENT(4, "tape_put_device(%p) -> %i\n", device, count);
+ BUG_ON(count < 0);
+ if (count == 0) {
+ kfree(device->modeset_byte);
+ kfree(device);
}
-
- return NULL;
}
/*
* Find tape device by a device index.
*/
struct tape_device *
-tape_get_device(int devindex)
+tape_find_device(int devindex)
{
struct tape_device *device, *tmp;
@@ -535,7 +599,7 @@ tape_get_device(int devindex)
read_lock(&tape_device_lock);
list_for_each_entry(tmp, &tape_device_list, node) {
if (tmp->first_minor / TAPE_MINORS_PER_DEV == devindex) {
- device = tape_get_device_reference(tmp);
+ device = tape_get_device(tmp);
break;
}
}
@@ -550,23 +614,28 @@ int
tape_generic_probe(struct ccw_device *cdev)
{
struct tape_device *device;
+ int ret;
+ struct ccw_dev_id dev_id;
device = tape_alloc_device();
if (IS_ERR(device))
return -ENODEV;
- PRINT_INFO("tape device %s found\n", cdev->dev.bus_id);
- cdev->dev.driver_data = device;
- device->cdev = cdev;
- device->cdev_id = busid_to_int(cdev->dev.bus_id);
+ ccw_device_set_options(cdev, CCWDEV_DO_PATHGROUP |
+ CCWDEV_DO_MULTIPATH);
+ ret = sysfs_create_group(&cdev->dev.kobj, &tape_attr_group);
+ if (ret) {
+ tape_put_device(device);
+ return ret;
+ }
+ dev_set_drvdata(&cdev->dev, device);
cdev->handler = __tape_do_irq;
-
- ccw_device_set_options(cdev, CCWDEV_DO_PATHGROUP);
- sysfs_create_group(&cdev->dev.kobj, &tape_attr_group);
-
- return 0;
+ device->cdev = cdev;
+ ccw_device_get_id(cdev, &dev_id);
+ device->cdev_id = devid_to_int(&dev_id);
+ return ret;
}
-static inline void
+static void
__tape_discard_requests(struct tape_device *device)
{
struct tape_request * request;
@@ -579,7 +648,8 @@ __tape_discard_requests(struct tape_device *device)
list_del(&request->list);
/* Decrease ref_count for removed request. */
- request->device = tape_put_device(device);
+ request->device = NULL;
+ tape_put_device(device);
request->rc = -EIO;
if (request->callback != NULL)
request->callback(request, request->callback_data);
@@ -597,9 +667,8 @@ tape_generic_remove(struct ccw_device *cdev)
{
struct tape_device * device;
- device = cdev->dev.driver_data;
+ device = dev_get_drvdata(&cdev->dev);
if (!device) {
- PRINT_ERR("No device pointer in tape_generic_remove!\n");
return;
}
DBF_LH(3, "(%08x): tape_generic_remove(%p)\n", device->cdev_id, cdev);
@@ -630,19 +699,19 @@ tape_generic_remove(struct ccw_device *cdev)
*/
DBF_EVENT(3, "(%08x): Drive in use vanished!\n",
device->cdev_id);
- PRINT_WARN("(%s): Drive in use vanished - "
- "expect trouble!\n",
- device->cdev->dev.bus_id);
- PRINT_WARN("State was %i\n", device->tape_state);
+ pr_warning("%s: A tape unit was detached while in "
+ "use\n", dev_name(&device->cdev->dev));
tape_state_set(device, TS_NOT_OPER);
__tape_discard_requests(device);
spin_unlock_irq(get_ccwdev_lock(device->cdev));
tape_cleanup_device(device);
}
- if (cdev->dev.driver_data != NULL) {
+ device = dev_get_drvdata(&cdev->dev);
+ if (device) {
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, NULL);
+ tape_put_device(device);
}
}
@@ -654,39 +723,34 @@ tape_alloc_request(int cplength, int datasize)
{
struct tape_request *request;
- if (datasize > PAGE_SIZE || (cplength*sizeof(struct ccw1)) > PAGE_SIZE)
- BUG();
+ BUG_ON(datasize > PAGE_SIZE || (cplength*sizeof(struct ccw1)) > PAGE_SIZE);
DBF_LH(6, "tape_alloc_request(%d, %d)\n", cplength, datasize);
- request = (struct tape_request *) kmalloc(sizeof(struct tape_request),
- GFP_KERNEL);
+ request = kzalloc(sizeof(struct tape_request), GFP_KERNEL);
if (request == NULL) {
DBF_EXCEPTION(1, "cqra nomem\n");
return ERR_PTR(-ENOMEM);
}
- memset(request, 0, sizeof(struct tape_request));
/* allocate channel program */
if (cplength > 0) {
- request->cpaddr = kmalloc(cplength*sizeof(struct ccw1),
+ request->cpaddr = kcalloc(cplength, sizeof(struct ccw1),
GFP_ATOMIC | GFP_DMA);
if (request->cpaddr == NULL) {
DBF_EXCEPTION(1, "cqra nomem\n");
kfree(request);
return ERR_PTR(-ENOMEM);
}
- memset(request->cpaddr, 0, cplength*sizeof(struct ccw1));
}
/* alloc small kernel buffer */
if (datasize > 0) {
- request->cpdata = kmalloc(datasize, GFP_KERNEL | GFP_DMA);
+ request->cpdata = kzalloc(datasize, GFP_KERNEL | GFP_DMA);
if (request->cpdata == NULL) {
DBF_EXCEPTION(1, "cqra nomem\n");
kfree(request->cpaddr);
kfree(request);
return ERR_PTR(-ENOMEM);
}
- memset(request->cpdata, 0, datasize);
}
DBF_LH(6, "New request %p(%p/%p)\n", request, request->cpaddr,
request->cpdata);
@@ -702,23 +766,18 @@ tape_free_request (struct tape_request * request)
{
DBF_LH(6, "Free request %p\n", request);
- if (request->device != NULL) {
- request->device = tape_put_device(request->device);
- }
+ if (request->device)
+ tape_put_device(request->device);
kfree(request->cpdata);
kfree(request->cpaddr);
kfree(request);
}
-static inline int
+static int
__tape_start_io(struct tape_device *device, struct tape_request *request)
{
int rc;
-#ifdef CONFIG_S390_TAPE_BLOCK
- if (request->op == TO_BLOCK)
- device->discipline->check_locate(device, request);
-#endif
rc = ccw_device_start(
device->cdev,
request->cpaddr,
@@ -731,7 +790,7 @@ __tape_start_io(struct tape_device *device, struct tape_request *request)
} else if (rc == -EBUSY) {
/* The common I/O subsystem is currently busy. Retry later. */
request->status = TAPE_REQUEST_QUEUED;
- schedule_work(&device->tape_dnr);
+ schedule_delayed_work(&device->tape_dnr, 0);
rc = 0;
} else {
/* Start failed. Remove request and indicate failure. */
@@ -740,7 +799,7 @@ __tape_start_io(struct tape_device *device, struct tape_request *request)
return rc;
}
-static inline void
+static void
__tape_start_next_request(struct tape_device *device)
{
struct list_head *l, *n;
@@ -761,6 +820,13 @@ __tape_start_next_request(struct tape_device *device)
*/
if (request->status == TAPE_REQUEST_IN_IO)
return;
+ /*
+ * Request has already been stopped. We have to wait until
+ * the request is removed from the queue in the interrupt
+ * handling.
+ */
+ if (request->status == TAPE_REQUEST_DONE)
+ return;
/*
* We wanted to cancel the request but the common I/O layer
@@ -790,18 +856,34 @@ __tape_start_next_request(struct tape_device *device)
}
static void
-tape_delayed_next_request(void *data)
+tape_delayed_next_request(struct work_struct *work)
{
- struct tape_device * device;
+ struct tape_device *device =
+ container_of(work, struct tape_device, tape_dnr.work);
- device = (struct tape_device *) data;
DBF_LH(6, "tape_delayed_next_request(%p)\n", device);
spin_lock_irq(get_ccwdev_lock(device->cdev));
__tape_start_next_request(device);
spin_unlock_irq(get_ccwdev_lock(device->cdev));
}
-static inline void
+static void tape_long_busy_timeout(unsigned long data)
+{
+ struct tape_request *request;
+ struct tape_device *device;
+
+ device = (struct tape_device *) data;
+ spin_lock_irq(get_ccwdev_lock(device->cdev));
+ request = list_entry(device->req_queue.next, struct tape_request, list);
+ BUG_ON(request->status != TAPE_REQUEST_LONG_BUSY);
+ DBF_LH(6, "%08x: Long busy timeout.\n", device->cdev_id);
+ __tape_start_next_request(device);
+ device->lb_timeout.data = 0UL;
+ tape_put_device(device);
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+}
+
+static void
__tape_end_request(
struct tape_device * device,
struct tape_request * request,
@@ -826,30 +908,6 @@ __tape_end_request(
}
/*
- * Write sense data to console/dbf
- */
-void
-tape_dump_sense(struct tape_device* device, struct tape_request *request,
- struct irb *irb)
-{
- unsigned int *sptr;
-
- PRINT_INFO("-------------------------------------------------\n");
- PRINT_INFO("DSTAT : %02x CSTAT: %02x CPA: %04x\n",
- irb->scsw.dstat, irb->scsw.cstat, irb->scsw.cpa);
- PRINT_INFO("DEVICE: %s\n", device->cdev->dev.bus_id);
- if (request != NULL)
- PRINT_INFO("OP : %s\n", tape_op_verbose[request->op]);
-
- sptr = (unsigned int *) irb->ecw;
- PRINT_INFO("Sense data: %08X %08X %08X %08X \n",
- sptr[0], sptr[1], sptr[2], sptr[3]);
- PRINT_INFO("Sense data: %08X %08X %08X %08X \n",
- sptr[4], sptr[5], sptr[6], sptr[7]);
- PRINT_INFO("--------------------------------------------------\n");
-}
-
-/*
* Write sense data to dbf
*/
void
@@ -864,7 +922,7 @@ tape_dump_sense_dbf(struct tape_device *device, struct tape_request *request,
else
op = "---";
DBF_EVENT(3, "DSTAT : %02x CSTAT: %02x\n",
- irb->scsw.dstat,irb->scsw.cstat);
+ irb->scsw.cmd.dstat, irb->scsw.cmd.cstat);
DBF_EVENT(3, "DEVICE: %08x OP\t: %s\n", device->cdev_id, op);
sptr = (unsigned int *) irb->ecw;
DBF_EVENT(3, "%08x %08x\n", sptr[0], sptr[1]);
@@ -878,7 +936,7 @@ tape_dump_sense_dbf(struct tape_device *device, struct tape_request *request,
* and starts it if the tape is idle. Has to be called with
* the device lock held.
*/
-static inline int
+static int
__tape_start_request(struct tape_device *device, struct tape_request *request)
{
int rc;
@@ -888,6 +946,7 @@ __tape_start_request(struct tape_device *device, struct tape_request *request)
case TO_ASSIGN:
case TO_UNASSIGN:
case TO_READ_ATTMSG:
+ case TO_RDC:
if (device->tape_state == TS_INIT)
break;
if (device->tape_state == TS_UNUSED)
@@ -900,7 +959,7 @@ __tape_start_request(struct tape_device *device, struct tape_request *request)
}
/* Increase use count of device for the added request. */
- request->device = tape_get_device_reference(device);
+ request->device = tape_get_device(device);
if (list_empty(&device->req_queue)) {
/* No other requests are on the queue. Start this one. */
@@ -951,21 +1010,19 @@ __tape_wake_up(struct tape_request *request, void *data)
int
tape_do_io(struct tape_device *device, struct tape_request *request)
{
- wait_queue_head_t wq;
int rc;
- init_waitqueue_head(&wq);
spin_lock_irq(get_ccwdev_lock(device->cdev));
/* Setup callback */
request->callback = __tape_wake_up;
- request->callback_data = &wq;
+ request->callback_data = &device->wait_queue;
/* Add request to request queue and try to start it. */
rc = __tape_start_request(device, request);
spin_unlock_irq(get_ccwdev_lock(device->cdev));
if (rc)
return rc;
/* Request added to the queue. Wait for its completion. */
- wait_event(wq, (request->callback == NULL));
+ wait_event(device->wait_queue, (request->callback == NULL));
/* Get rc from request */
return request->rc;
}
@@ -986,20 +1043,19 @@ int
tape_do_io_interruptible(struct tape_device *device,
struct tape_request *request)
{
- wait_queue_head_t wq;
int rc;
- init_waitqueue_head(&wq);
spin_lock_irq(get_ccwdev_lock(device->cdev));
/* Setup callback */
request->callback = __tape_wake_up_interruptible;
- request->callback_data = &wq;
+ request->callback_data = &device->wait_queue;
rc = __tape_start_request(device, request);
spin_unlock_irq(get_ccwdev_lock(device->cdev));
if (rc)
return rc;
/* Request added to the queue. Wait for its completion. */
- rc = wait_event_interruptible(wq, (request->callback == NULL));
+ rc = wait_event_interruptible(device->wait_queue,
+ (request->callback == NULL));
if (rc != -ERESTARTSYS)
/* Request finished normally. */
return request->rc;
@@ -1012,10 +1068,10 @@ tape_do_io_interruptible(struct tape_device *device,
/* Wait for the interrupt that acknowledges the halt. */
do {
rc = wait_event_interruptible(
- wq,
+ device->wait_queue,
(request->callback == NULL)
);
- } while (rc != -ERESTARTSYS);
+ } while (rc == -ERESTARTSYS);
DBF_EVENT(3, "IO stopped on %08x\n", device->cdev_id);
rc = -ERESTARTSYS;
@@ -1024,6 +1080,20 @@ tape_do_io_interruptible(struct tape_device *device,
}
/*
+ * Stop running ccw.
+ */
+int
+tape_cancel_io(struct tape_device *device, struct tape_request *request)
+{
+ int rc;
+
+ spin_lock_irq(get_ccwdev_lock(device->cdev));
+ rc = __tape_cancel_io(device, request);
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
+ return rc;
+}
+
+/*
* Tape interrupt routine, called from the ccw_device layer
*/
static void
@@ -1033,10 +1103,8 @@ __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) {
- PRINT_ERR("could not get device structure for %s "
- "in interrupt\n", cdev->dev.bus_id);
return;
}
request = (struct tape_request *) intparm;
@@ -1048,15 +1116,14 @@ __tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
/* FIXME: What to do with the request? */
switch (PTR_ERR(irb)) {
case -ETIMEDOUT:
- PRINT_WARN("(%s): Request timed out\n",
- cdev->dev.bus_id);
+ DBF_LH(1, "(%08x): Request timed out\n",
+ device->cdev_id);
case -EIO:
__tape_end_request(device, request, -EIO);
break;
default:
- PRINT_ERR("(%s): Unexpected i/o error %li\n",
- cdev->dev.bus_id,
- PTR_ERR(irb));
+ DBF_LH(1, "(%08x): Unexpected i/o error %li\n",
+ device->cdev_id, PTR_ERR(irb));
}
return;
}
@@ -1064,23 +1131,40 @@ __tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
/*
* If the condition code is not zero and the start function bit is
* still set, this is an deferred error and the last start I/O did
- * not succeed. Restart the request now.
+ * not succeed. At this point the condition that caused the deferred
+ * error might still apply. So we just schedule the request to be
+ * started later.
*/
- if (irb->scsw.cc != 0 && (irb->scsw.fctl & SCSW_FCTL_START_FUNC)) {
- PRINT_WARN("(%s): deferred cc=%i. restaring\n",
- cdev->dev.bus_id,
- irb->scsw.cc);
- rc = __tape_start_io(device, request);
- if (rc)
- __tape_end_request(device, request, rc);
+ if (irb->scsw.cmd.cc != 0 &&
+ (irb->scsw.cmd.fctl & SCSW_FCTL_START_FUNC) &&
+ (request->status == TAPE_REQUEST_IN_IO)) {
+ DBF_EVENT(3,"(%08x): deferred cc=%i, fctl=%i. restarting\n",
+ device->cdev_id, irb->scsw.cmd.cc, irb->scsw.cmd.fctl);
+ request->status = TAPE_REQUEST_QUEUED;
+ schedule_delayed_work(&device->tape_dnr, HZ);
return;
}
/* May be an unsolicited irq */
if(request != NULL)
- request->rescnt = irb->scsw.count;
-
- if (irb->scsw.dstat != 0x0c) {
+ request->rescnt = irb->scsw.cmd.count;
+ else if ((irb->scsw.cmd.dstat == 0x85 || irb->scsw.cmd.dstat == 0x80) &&
+ !list_empty(&device->req_queue)) {
+ /* Not Ready to Ready after long busy ? */
+ struct tape_request *req;
+ req = list_entry(device->req_queue.next,
+ struct tape_request, list);
+ if (req->status == TAPE_REQUEST_LONG_BUSY) {
+ DBF_EVENT(3, "(%08x): del timer\n", device->cdev_id);
+ if (del_timer(&device->lb_timeout)) {
+ device->lb_timeout.data = 0UL;
+ tape_put_device(device);
+ __tape_start_next_request(device);
+ }
+ return;
+ }
+ }
+ if (irb->scsw.cmd.dstat != 0x0c) {
/* Set the 'ONLINE' flag depending on sense byte 1 */
if(*(((__u8 *) irb->ecw) + 1) & SENSE_DRIVE_ONLINE)
device->tape_generic_status |= GMT_ONLINE(~0);
@@ -1127,6 +1211,15 @@ __tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
break;
case TAPE_IO_PENDING:
break;
+ case TAPE_IO_LONG_BUSY:
+ device->lb_timeout.data =
+ (unsigned long) tape_get_device(device);
+ device->lb_timeout.expires = jiffies +
+ LONG_BUSY_TIMEOUT * HZ;
+ DBF_EVENT(3, "(%08x): add timer\n", device->cdev_id);
+ add_timer(&device->lb_timeout);
+ request->status = TAPE_REQUEST_LONG_BUSY;
+ break;
case TAPE_IO_RETRY:
rc = __tape_start_io(device, request);
if (rc)
@@ -1140,8 +1233,6 @@ __tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
default:
if (rc > 0) {
DBF_EVENT(6, "xunknownrc\n");
- PRINT_ERR("Invalid return code from discipline "
- "interrupt function.\n");
__tape_end_request(device, request, -EIO);
} else {
__tape_end_request(device, request, rc);
@@ -1151,14 +1242,14 @@ __tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
}
/*
- * Tape device open function used by tape_char & tape_block frontends.
+ * Tape device open function used by tape_char frontend.
*/
int
tape_open(struct tape_device *device)
{
int rc;
- spin_lock(get_ccwdev_lock(device->cdev));
+ spin_lock_irq(get_ccwdev_lock(device->cdev));
if (device->tape_state == TS_NOT_OPER) {
DBF_EVENT(6, "TAPE:nodev\n");
rc = -ENODEV;
@@ -1176,21 +1267,21 @@ tape_open(struct tape_device *device)
tape_state_set(device, TS_IN_USE);
rc = 0;
}
- spin_unlock(get_ccwdev_lock(device->cdev));
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
return rc;
}
/*
- * Tape device release function used by tape_char & tape_block frontends.
+ * Tape device release function used by tape_char frontend.
*/
int
tape_release(struct tape_device *device)
{
- spin_lock(get_ccwdev_lock(device->cdev));
+ spin_lock_irq(get_ccwdev_lock(device->cdev));
if (device->tape_state == TS_IN_USE)
tape_state_set(device, TS_UNUSED);
module_put(device->discipline->owner);
- spin_unlock(get_ccwdev_lock(device->cdev));
+ spin_unlock_irq(get_ccwdev_lock(device->cdev));
return 0;
}
@@ -1239,10 +1330,9 @@ tape_init (void)
#ifdef DBF_LIKE_HELL
debug_set_level(TAPE_DBF_AREA, 6);
#endif
- DBF_EVENT(3, "tape init: ($Revision: 1.54 $)\n");
+ DBF_EVENT(3, "tape init\n");
tape_proc_init();
tapechar_init ();
- tapeblock_init ();
return 0;
}
@@ -1256,15 +1346,13 @@ tape_exit(void)
/* Get rid of the frontends */
tapechar_exit();
- tapeblock_exit();
tape_proc_cleanup();
debug_unregister (TAPE_DBF_AREA);
}
MODULE_AUTHOR("(C) 2001 IBM Deutschland Entwicklung GmbH by Carsten Otte and "
"Michael Holzheu (cotte@de.ibm.com,holzheu@de.ibm.com)");
-MODULE_DESCRIPTION("Linux on zSeries channel attached "
- "tape device driver ($Revision: 1.54 $)");
+MODULE_DESCRIPTION("Linux on zSeries channel attached tape device driver");
MODULE_LICENSE("GPL");
module_init(tape_init);
@@ -1274,17 +1362,18 @@ 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_get_device);
EXPORT_SYMBOL(tape_state_verbose);
EXPORT_SYMBOL(tape_op_verbose);
EXPORT_SYMBOL(tape_state_set);
EXPORT_SYMBOL(tape_med_state_set);
EXPORT_SYMBOL(tape_alloc_request);
EXPORT_SYMBOL(tape_free_request);
-EXPORT_SYMBOL(tape_dump_sense);
EXPORT_SYMBOL(tape_dump_sense_dbf);
EXPORT_SYMBOL(tape_do_io);
EXPORT_SYMBOL(tape_do_io_async);
EXPORT_SYMBOL(tape_do_io_interruptible);
+EXPORT_SYMBOL(tape_cancel_io);
EXPORT_SYMBOL(tape_mtop);