forked from Minki/linux
5a0e3ad6af
percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
1344 lines
34 KiB
C
1344 lines
34 KiB
C
/*
|
|
* drivers/s390/char/tape_core.c
|
|
* basic function of the tape device driver
|
|
*
|
|
* S390 and zSeries version
|
|
* 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>
|
|
* Martin Schwidefsky <schwidefsky@de.ibm.com>
|
|
* Stefan Bader <shbader@de.ibm.com>
|
|
*/
|
|
|
|
#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
|
|
|
|
#define TAPE_DBF_AREA tape_core_dbf
|
|
|
|
#include "tape.h"
|
|
#include "tape_std.h"
|
|
|
|
#define LONG_BUSY_TIMEOUT 180 /* seconds */
|
|
|
|
static void __tape_do_irq (struct ccw_device *, unsigned long, struct irb *);
|
|
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 LIST_HEAD(tape_device_list);
|
|
static DEFINE_RWLOCK(tape_device_lock);
|
|
|
|
/*
|
|
* Pointer to debug area.
|
|
*/
|
|
debug_info_t *TAPE_DBF_AREA = NULL;
|
|
EXPORT_SYMBOL(TAPE_DBF_AREA);
|
|
|
|
/*
|
|
* Printable strings for tape enumerations.
|
|
*/
|
|
const char *tape_state_verbose[TS_SIZE] =
|
|
{
|
|
[TS_UNUSED] = "UNUSED",
|
|
[TS_IN_USE] = "IN_USE",
|
|
[TS_BLKUSE] = "BLKUSE",
|
|
[TS_INIT] = "INIT ",
|
|
[TS_NOT_OPER] = "NOT_OP"
|
|
};
|
|
|
|
const char *tape_op_verbose[TO_SIZE] =
|
|
{
|
|
[TO_BLOCK] = "BLK", [TO_BSB] = "BSB",
|
|
[TO_BSF] = "BSF", [TO_DSE] = "DSE",
|
|
[TO_FSB] = "FSB", [TO_FSF] = "FSF",
|
|
[TO_LBL] = "LBL", [TO_NOP] = "NOP",
|
|
[TO_RBA] = "RBA", [TO_RBI] = "RBI",
|
|
[TO_RFO] = "RFO", [TO_REW] = "REW",
|
|
[TO_RUN] = "RUN", [TO_WRI] = "WRI",
|
|
[TO_WTM] = "WTM", [TO_MSEN] = "MSN",
|
|
[TO_LOAD] = "LOA", [TO_READ_CONFIG] = "RCF",
|
|
[TO_READ_ATTMSG] = "RAT",
|
|
[TO_DIS] = "DIS", [TO_ASSIGN] = "ASS",
|
|
[TO_UNASSIGN] = "UAS", [TO_CRYPT_ON] = "CON",
|
|
[TO_CRYPT_OFF] = "COF", [TO_KEKL_SET] = "KLS",
|
|
[TO_KEKL_QUERY] = "KLQ",[TO_RDC] = "RDC",
|
|
};
|
|
|
|
static int devid_to_int(struct ccw_dev_id *dev_id)
|
|
{
|
|
return dev_id->devno + (dev_id->ssid << 16);
|
|
}
|
|
|
|
/*
|
|
* Some channel attached tape specific attributes.
|
|
*
|
|
* FIXME: In the future the first_minor and blocksize attribute should be
|
|
* replaced by a link to the cdev tree.
|
|
*/
|
|
static ssize_t
|
|
tape_medium_state_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct tape_device *tdev;
|
|
|
|
tdev = dev_get_drvdata(dev);
|
|
return scnprintf(buf, PAGE_SIZE, "%i\n", tdev->medium_state);
|
|
}
|
|
|
|
static
|
|
DEVICE_ATTR(medium_state, 0444, tape_medium_state_show, NULL);
|
|
|
|
static ssize_t
|
|
tape_first_minor_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct tape_device *tdev;
|
|
|
|
tdev = dev_get_drvdata(dev);
|
|
return scnprintf(buf, PAGE_SIZE, "%i\n", tdev->first_minor);
|
|
}
|
|
|
|
static
|
|
DEVICE_ATTR(first_minor, 0444, tape_first_minor_show, NULL);
|
|
|
|
static ssize_t
|
|
tape_state_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct tape_device *tdev;
|
|
|
|
tdev = dev_get_drvdata(dev);
|
|
return scnprintf(buf, PAGE_SIZE, "%s\n", (tdev->first_minor < 0) ?
|
|
"OFFLINE" : tape_state_verbose[tdev->tape_state]);
|
|
}
|
|
|
|
static
|
|
DEVICE_ATTR(state, 0444, tape_state_show, NULL);
|
|
|
|
static ssize_t
|
|
tape_operation_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct tape_device *tdev;
|
|
ssize_t rc;
|
|
|
|
tdev = dev_get_drvdata(dev);
|
|
if (tdev->first_minor < 0)
|
|
return scnprintf(buf, PAGE_SIZE, "N/A\n");
|
|
|
|
spin_lock_irq(get_ccwdev_lock(tdev->cdev));
|
|
if (list_empty(&tdev->req_queue))
|
|
rc = scnprintf(buf, PAGE_SIZE, "---\n");
|
|
else {
|
|
struct tape_request *req;
|
|
|
|
req = list_entry(tdev->req_queue.next, struct tape_request,
|
|
list);
|
|
rc = scnprintf(buf,PAGE_SIZE, "%s\n", tape_op_verbose[req->op]);
|
|
}
|
|
spin_unlock_irq(get_ccwdev_lock(tdev->cdev));
|
|
return rc;
|
|
}
|
|
|
|
static
|
|
DEVICE_ATTR(operation, 0444, tape_operation_show, NULL);
|
|
|
|
static ssize_t
|
|
tape_blocksize_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct tape_device *tdev;
|
|
|
|
tdev = dev_get_drvdata(dev);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%i\n", tdev->char_data.block_size);
|
|
}
|
|
|
|
static
|
|
DEVICE_ATTR(blocksize, 0444, tape_blocksize_show, NULL);
|
|
|
|
static struct attribute *tape_attrs[] = {
|
|
&dev_attr_medium_state.attr,
|
|
&dev_attr_first_minor.attr,
|
|
&dev_attr_state.attr,
|
|
&dev_attr_operation.attr,
|
|
&dev_attr_blocksize.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct attribute_group tape_attr_group = {
|
|
.attrs = tape_attrs,
|
|
};
|
|
|
|
/*
|
|
* Tape state functions
|
|
*/
|
|
void
|
|
tape_state_set(struct tape_device *device, enum tape_state newstate)
|
|
{
|
|
const char *str;
|
|
|
|
if (device->tape_state == TS_NOT_OPER) {
|
|
DBF_EVENT(3, "ts_set err: not oper\n");
|
|
return;
|
|
}
|
|
DBF_EVENT(4, "ts. dev: %x\n", device->first_minor);
|
|
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 < TS_SIZE && newstate >= 0)
|
|
str = tape_state_verbose[newstate];
|
|
else
|
|
str = "UNKNOWN TS";
|
|
DBF_EVENT(4, "%s\n", str);
|
|
device->tape_state = newstate;
|
|
wake_up(&device->state_change_wq);
|
|
}
|
|
|
|
void
|
|
tape_med_state_set(struct tape_device *device, enum tape_medium_state newstate)
|
|
{
|
|
if (device->medium_state == newstate)
|
|
return;
|
|
switch(newstate){
|
|
case MS_UNLOADED:
|
|
device->tape_generic_status |= GMT_DR_OPEN(~0);
|
|
if (device->medium_state == MS_LOADED)
|
|
pr_info("%s: The tape cartridge has been successfully "
|
|
"unloaded\n", dev_name(&device->cdev->dev));
|
|
break;
|
|
case MS_LOADED:
|
|
device->tape_generic_status &= ~GMT_DR_OPEN(~0);
|
|
if (device->medium_state == MS_UNLOADED)
|
|
pr_info("%s: A tape cartridge has been mounted\n",
|
|
dev_name(&device->cdev->dev));
|
|
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 int
|
|
__tape_cancel_io(struct tape_device *device, struct tape_request *request)
|
|
{
|
|
int retries;
|
|
int rc;
|
|
|
|
/* Check if interrupt has already been processed */
|
|
if (request->callback == NULL)
|
|
return 0;
|
|
|
|
rc = 0;
|
|
for (retries = 0; retries < 5; retries++) {
|
|
rc = ccw_device_clear(device->cdev, (long) request);
|
|
|
|
switch (rc) {
|
|
case 0:
|
|
request->status = TAPE_REQUEST_DONE;
|
|
return 0;
|
|
case -EBUSY:
|
|
request->status = TAPE_REQUEST_CANCEL;
|
|
schedule_delayed_work(&device->tape_dnr, 0);
|
|
return 0;
|
|
case -ENODEV:
|
|
DBF_EXCEPTION(2, "device gone, retry\n");
|
|
break;
|
|
case -EIO:
|
|
DBF_EXCEPTION(2, "I/O error, retry\n");
|
|
break;
|
|
default:
|
|
BUG();
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Add device into the sorted list, giving it the first
|
|
* available minor number.
|
|
*/
|
|
static int
|
|
tape_assign_minor(struct tape_device *device)
|
|
{
|
|
struct tape_device *tmp;
|
|
int minor;
|
|
|
|
minor = 0;
|
|
write_lock(&tape_device_lock);
|
|
list_for_each_entry(tmp, &tape_device_list, node) {
|
|
if (minor < tmp->first_minor)
|
|
break;
|
|
minor += TAPE_MINORS_PER_DEV;
|
|
}
|
|
if (minor >= 256) {
|
|
write_unlock(&tape_device_lock);
|
|
return -ENODEV;
|
|
}
|
|
device->first_minor = minor;
|
|
list_add_tail(&device->node, &tmp->node);
|
|
write_unlock(&tape_device_lock);
|
|
return 0;
|
|
}
|
|
|
|
/* remove device from the list */
|
|
static void
|
|
tape_remove_minor(struct tape_device *device)
|
|
{
|
|
write_lock(&tape_device_lock);
|
|
list_del_init(&device->node);
|
|
device->first_minor = -1;
|
|
write_unlock(&tape_device_lock);
|
|
}
|
|
|
|
/*
|
|
* Set a device online.
|
|
*
|
|
* This function is called by the common I/O layer to move a device from the
|
|
* detected but offline into the online state.
|
|
* If we return an error (RC < 0) the device remains in the offline state. This
|
|
* can happen if the device is assigned somewhere else, for example.
|
|
*/
|
|
int
|
|
tape_generic_online(struct tape_device *device,
|
|
struct tape_discipline *discipline)
|
|
{
|
|
int rc;
|
|
|
|
DBF_LH(6, "tape_enable_device(%p, %p)\n", device, discipline);
|
|
|
|
if (device->tape_state != TS_INIT) {
|
|
DBF_LH(3, "Tapestate not INIT (%d)\n", device->tape_state);
|
|
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)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = discipline->setup_device(device);
|
|
if (rc)
|
|
goto out;
|
|
rc = tape_assign_minor(device);
|
|
if (rc)
|
|
goto out_discipline;
|
|
|
|
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);
|
|
|
|
DBF_LH(3, "(%08x): Drive set online\n", device->cdev_id);
|
|
|
|
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:
|
|
module_put(discipline->owner);
|
|
return rc;
|
|
}
|
|
|
|
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);
|
|
tape_remove_minor(device);
|
|
tape_med_state_set(device, MS_UNKNOWN);
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
* request. We may prevent this by returning an error.
|
|
* Manual offline is only allowed while the drive is not in use.
|
|
*/
|
|
int
|
|
tape_generic_offline(struct ccw_device *cdev)
|
|
{
|
|
struct tape_device *device;
|
|
|
|
device = dev_get_drvdata(&cdev->dev);
|
|
if (!device) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
DBF_LH(3, "(%08x): tape_generic_offline(%p)\n",
|
|
device->cdev_id, device);
|
|
|
|
spin_lock_irq(get_ccwdev_lock(device->cdev));
|
|
switch (device->tape_state) {
|
|
case TS_INIT:
|
|
case TS_NOT_OPER:
|
|
spin_unlock_irq(get_ccwdev_lock(device->cdev));
|
|
break;
|
|
case TS_UNUSED:
|
|
tape_state_set(device, TS_INIT);
|
|
spin_unlock_irq(get_ccwdev_lock(device->cdev));
|
|
tape_cleanup_device(device);
|
|
break;
|
|
default:
|
|
DBF_EVENT(3, "(%08x): Set offline failed "
|
|
"- drive in use.\n",
|
|
device->cdev_id);
|
|
spin_unlock_irq(get_ccwdev_lock(device->cdev));
|
|
return -EBUSY;
|
|
}
|
|
|
|
DBF_LH(3, "(%08x): Drive set offline.\n", device->cdev_id);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Allocate memory for a new device structure.
|
|
*/
|
|
static struct tape_device *
|
|
tape_alloc_device(void)
|
|
{
|
|
struct tape_device *device;
|
|
|
|
device = kzalloc(sizeof(struct tape_device), GFP_KERNEL);
|
|
if (device == NULL) {
|
|
DBF_EXCEPTION(2, "ti:no mem\n");
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
device->modeset_byte = kmalloc(1, GFP_KERNEL | GFP_DMA);
|
|
if (device->modeset_byte == NULL) {
|
|
DBF_EXCEPTION(2, "ti:no mem\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_DELAYED_WORK(&device->tape_dnr, tape_delayed_next_request);
|
|
|
|
return device;
|
|
}
|
|
|
|
/*
|
|
* Get a reference to an existing device structure. This will automatically
|
|
* increment the reference count.
|
|
*/
|
|
struct tape_device *
|
|
tape_get_device(struct tape_device *device)
|
|
{
|
|
int count;
|
|
|
|
count = atomic_inc_return(&device->ref_count);
|
|
DBF_EVENT(4, "tape_get_device(%p) = %i\n", device, count);
|
|
return device;
|
|
}
|
|
|
|
/*
|
|
* Decrease the reference counter of a devices structure. If the
|
|
* reference counter reaches zero free the device structure.
|
|
* The function returns a NULL pointer to be used by the caller
|
|
* for clearing reference pointers.
|
|
*/
|
|
void
|
|
tape_put_device(struct tape_device *device)
|
|
{
|
|
int count;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find tape device by a device index.
|
|
*/
|
|
struct tape_device *
|
|
tape_find_device(int devindex)
|
|
{
|
|
struct tape_device *device, *tmp;
|
|
|
|
device = ERR_PTR(-ENODEV);
|
|
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(tmp);
|
|
break;
|
|
}
|
|
}
|
|
read_unlock(&tape_device_lock);
|
|
return device;
|
|
}
|
|
|
|
/*
|
|
* Driverfs tape probe function.
|
|
*/
|
|
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;
|
|
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;
|
|
device->cdev = cdev;
|
|
ccw_device_get_id(cdev, &dev_id);
|
|
device->cdev_id = devid_to_int(&dev_id);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
__tape_discard_requests(struct tape_device *device)
|
|
{
|
|
struct tape_request * request;
|
|
struct list_head * l, *n;
|
|
|
|
list_for_each_safe(l, n, &device->req_queue) {
|
|
request = list_entry(l, struct tape_request, list);
|
|
if (request->status == TAPE_REQUEST_IN_IO)
|
|
request->status = TAPE_REQUEST_DONE;
|
|
list_del(&request->list);
|
|
|
|
/* Decrease ref_count for removed request. */
|
|
request->device = NULL;
|
|
tape_put_device(device);
|
|
request->rc = -EIO;
|
|
if (request->callback != NULL)
|
|
request->callback(request, request->callback_data);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Driverfs tape remove function.
|
|
*
|
|
* This function is called whenever the common I/O layer detects the device
|
|
* gone. This can happen at any time and we cannot refuse.
|
|
*/
|
|
void
|
|
tape_generic_remove(struct ccw_device *cdev)
|
|
{
|
|
struct tape_device * device;
|
|
|
|
device = dev_get_drvdata(&cdev->dev);
|
|
if (!device) {
|
|
return;
|
|
}
|
|
DBF_LH(3, "(%08x): tape_generic_remove(%p)\n", device->cdev_id, cdev);
|
|
|
|
spin_lock_irq(get_ccwdev_lock(device->cdev));
|
|
switch (device->tape_state) {
|
|
case TS_INIT:
|
|
tape_state_set(device, TS_NOT_OPER);
|
|
case TS_NOT_OPER:
|
|
/*
|
|
* Nothing to do.
|
|
*/
|
|
spin_unlock_irq(get_ccwdev_lock(device->cdev));
|
|
break;
|
|
case TS_UNUSED:
|
|
/*
|
|
* Need only to release the device.
|
|
*/
|
|
tape_state_set(device, TS_NOT_OPER);
|
|
spin_unlock_irq(get_ccwdev_lock(device->cdev));
|
|
tape_cleanup_device(device);
|
|
break;
|
|
default:
|
|
/*
|
|
* There may be requests on the queue. We will not get
|
|
* an interrupt for a request that was running. So we
|
|
* just post them all as I/O errors.
|
|
*/
|
|
DBF_EVENT(3, "(%08x): Drive in use vanished!\n",
|
|
device->cdev_id);
|
|
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);
|
|
}
|
|
|
|
device = dev_get_drvdata(&cdev->dev);
|
|
if (device) {
|
|
sysfs_remove_group(&cdev->dev.kobj, &tape_attr_group);
|
|
dev_set_drvdata(&cdev->dev, NULL);
|
|
tape_put_device(device);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Allocate a new tape ccw request
|
|
*/
|
|
struct tape_request *
|
|
tape_alloc_request(int cplength, int datasize)
|
|
{
|
|
struct tape_request *request;
|
|
|
|
BUG_ON(datasize > PAGE_SIZE || (cplength*sizeof(struct ccw1)) > PAGE_SIZE);
|
|
|
|
DBF_LH(6, "tape_alloc_request(%d, %d)\n", cplength, datasize);
|
|
|
|
request = kzalloc(sizeof(struct tape_request), GFP_KERNEL);
|
|
if (request == NULL) {
|
|
DBF_EXCEPTION(1, "cqra nomem\n");
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
/* allocate channel program */
|
|
if (cplength > 0) {
|
|
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);
|
|
}
|
|
}
|
|
/* alloc small kernel buffer */
|
|
if (datasize > 0) {
|
|
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);
|
|
}
|
|
}
|
|
DBF_LH(6, "New request %p(%p/%p)\n", request, request->cpaddr,
|
|
request->cpdata);
|
|
|
|
return request;
|
|
}
|
|
|
|
/*
|
|
* Free tape ccw request
|
|
*/
|
|
void
|
|
tape_free_request (struct tape_request * request)
|
|
{
|
|
DBF_LH(6, "Free request %p\n", request);
|
|
|
|
if (request->device)
|
|
tape_put_device(request->device);
|
|
kfree(request->cpdata);
|
|
kfree(request->cpaddr);
|
|
kfree(request);
|
|
}
|
|
|
|
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,
|
|
(unsigned long) request,
|
|
0x00,
|
|
request->options
|
|
);
|
|
if (rc == 0) {
|
|
request->status = TAPE_REQUEST_IN_IO;
|
|
} else if (rc == -EBUSY) {
|
|
/* The common I/O subsystem is currently busy. Retry later. */
|
|
request->status = TAPE_REQUEST_QUEUED;
|
|
schedule_delayed_work(&device->tape_dnr, 0);
|
|
rc = 0;
|
|
} else {
|
|
/* Start failed. Remove request and indicate failure. */
|
|
DBF_EVENT(1, "tape: start request failed with RC = %i\n", rc);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static void
|
|
__tape_start_next_request(struct tape_device *device)
|
|
{
|
|
struct list_head *l, *n;
|
|
struct tape_request *request;
|
|
int rc;
|
|
|
|
DBF_LH(6, "__tape_start_next_request(%p)\n", device);
|
|
/*
|
|
* Try to start each request on request queue until one is
|
|
* started successful.
|
|
*/
|
|
list_for_each_safe(l, n, &device->req_queue) {
|
|
request = list_entry(l, struct tape_request, list);
|
|
|
|
/*
|
|
* Avoid race condition if bottom-half was triggered more than
|
|
* once.
|
|
*/
|
|
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
|
|
* was busy at that time. This can only happen if this
|
|
* function is called by delayed_next_request.
|
|
* Otherwise we start the next request on the queue.
|
|
*/
|
|
if (request->status == TAPE_REQUEST_CANCEL) {
|
|
rc = __tape_cancel_io(device, request);
|
|
} else {
|
|
rc = __tape_start_io(device, request);
|
|
}
|
|
if (rc == 0)
|
|
return;
|
|
|
|
/* Set ending status. */
|
|
request->rc = rc;
|
|
request->status = TAPE_REQUEST_DONE;
|
|
|
|
/* Remove from request queue. */
|
|
list_del(&request->list);
|
|
|
|
/* Do callback. */
|
|
if (request->callback != NULL)
|
|
request->callback(request, request->callback_data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
tape_delayed_next_request(struct work_struct *work)
|
|
{
|
|
struct tape_device *device =
|
|
container_of(work, struct tape_device, tape_dnr.work);
|
|
|
|
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 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,
|
|
int rc)
|
|
{
|
|
DBF_LH(6, "__tape_end_request(%p, %p, %i)\n", device, request, rc);
|
|
if (request) {
|
|
request->rc = rc;
|
|
request->status = TAPE_REQUEST_DONE;
|
|
|
|
/* Remove from request queue. */
|
|
list_del(&request->list);
|
|
|
|
/* Do callback. */
|
|
if (request->callback != NULL)
|
|
request->callback(request, request->callback_data);
|
|
}
|
|
|
|
/* Start next request. */
|
|
if (!list_empty(&device->req_queue))
|
|
__tape_start_next_request(device);
|
|
}
|
|
|
|
/*
|
|
* Write sense data to dbf
|
|
*/
|
|
void
|
|
tape_dump_sense_dbf(struct tape_device *device, struct tape_request *request,
|
|
struct irb *irb)
|
|
{
|
|
unsigned int *sptr;
|
|
const char* op;
|
|
|
|
if (request != NULL)
|
|
op = tape_op_verbose[request->op];
|
|
else
|
|
op = "---";
|
|
DBF_EVENT(3, "DSTAT : %02x CSTAT: %02x\n",
|
|
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]);
|
|
DBF_EVENT(3, "%08x %08x\n", sptr[2], sptr[3]);
|
|
DBF_EVENT(3, "%08x %08x\n", sptr[4], sptr[5]);
|
|
DBF_EVENT(3, "%08x %08x\n", sptr[6], sptr[7]);
|
|
}
|
|
|
|
/*
|
|
* I/O helper function. Adds the request to the request queue
|
|
* and starts it if the tape is idle. Has to be called with
|
|
* the device lock held.
|
|
*/
|
|
static int
|
|
__tape_start_request(struct tape_device *device, struct tape_request *request)
|
|
{
|
|
int rc;
|
|
|
|
switch (request->op) {
|
|
case TO_MSEN:
|
|
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)
|
|
break;
|
|
default:
|
|
if (device->tape_state == TS_BLKUSE)
|
|
break;
|
|
if (device->tape_state != TS_IN_USE)
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Increase use count of device for the added request. */
|
|
request->device = tape_get_device(device);
|
|
|
|
if (list_empty(&device->req_queue)) {
|
|
/* No other requests are on the queue. Start this one. */
|
|
rc = __tape_start_io(device, request);
|
|
if (rc)
|
|
return rc;
|
|
|
|
DBF_LH(5, "Request %p added for execution.\n", request);
|
|
list_add(&request->list, &device->req_queue);
|
|
} else {
|
|
DBF_LH(5, "Request %p add to queue.\n", request);
|
|
request->status = TAPE_REQUEST_QUEUED;
|
|
list_add_tail(&request->list, &device->req_queue);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Add the request to the request queue, try to start it if the
|
|
* tape is idle. Return without waiting for end of i/o.
|
|
*/
|
|
int
|
|
tape_do_io_async(struct tape_device *device, struct tape_request *request)
|
|
{
|
|
int rc;
|
|
|
|
DBF_LH(6, "tape_do_io_async(%p, %p)\n", device, request);
|
|
|
|
spin_lock_irq(get_ccwdev_lock(device->cdev));
|
|
/* Add request to request queue and try to start it. */
|
|
rc = __tape_start_request(device, request);
|
|
spin_unlock_irq(get_ccwdev_lock(device->cdev));
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* tape_do_io/__tape_wake_up
|
|
* Add the request to the request queue, try to start it if the
|
|
* tape is idle and wait uninterruptible for its completion.
|
|
*/
|
|
static void
|
|
__tape_wake_up(struct tape_request *request, void *data)
|
|
{
|
|
request->callback = NULL;
|
|
wake_up((wait_queue_head_t *) data);
|
|
}
|
|
|
|
int
|
|
tape_do_io(struct tape_device *device, struct tape_request *request)
|
|
{
|
|
int rc;
|
|
|
|
spin_lock_irq(get_ccwdev_lock(device->cdev));
|
|
/* Setup callback */
|
|
request->callback = __tape_wake_up;
|
|
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(device->wait_queue, (request->callback == NULL));
|
|
/* Get rc from request */
|
|
return request->rc;
|
|
}
|
|
|
|
/*
|
|
* tape_do_io_interruptible/__tape_wake_up_interruptible
|
|
* Add the request to the request queue, try to start it if the
|
|
* tape is idle and wait uninterruptible for its completion.
|
|
*/
|
|
static void
|
|
__tape_wake_up_interruptible(struct tape_request *request, void *data)
|
|
{
|
|
request->callback = NULL;
|
|
wake_up_interruptible((wait_queue_head_t *) data);
|
|
}
|
|
|
|
int
|
|
tape_do_io_interruptible(struct tape_device *device,
|
|
struct tape_request *request)
|
|
{
|
|
int rc;
|
|
|
|
spin_lock_irq(get_ccwdev_lock(device->cdev));
|
|
/* Setup callback */
|
|
request->callback = __tape_wake_up_interruptible;
|
|
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(device->wait_queue,
|
|
(request->callback == NULL));
|
|
if (rc != -ERESTARTSYS)
|
|
/* Request finished normally. */
|
|
return request->rc;
|
|
|
|
/* Interrupted by a signal. We have to stop the current request. */
|
|
spin_lock_irq(get_ccwdev_lock(device->cdev));
|
|
rc = __tape_cancel_io(device, request);
|
|
spin_unlock_irq(get_ccwdev_lock(device->cdev));
|
|
if (rc == 0) {
|
|
/* Wait for the interrupt that acknowledges the halt. */
|
|
do {
|
|
rc = wait_event_interruptible(
|
|
device->wait_queue,
|
|
(request->callback == NULL)
|
|
);
|
|
} while (rc == -ERESTARTSYS);
|
|
|
|
DBF_EVENT(3, "IO stopped on %08x\n", device->cdev_id);
|
|
rc = -ERESTARTSYS;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
__tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
|
|
{
|
|
struct tape_device *device;
|
|
struct tape_request *request;
|
|
int rc;
|
|
|
|
device = dev_get_drvdata(&cdev->dev);
|
|
if (device == NULL) {
|
|
return;
|
|
}
|
|
request = (struct tape_request *) intparm;
|
|
|
|
DBF_LH(6, "__tape_do_irq(device=%p, request=%p)\n", device, request);
|
|
|
|
/* On special conditions irb is an error pointer */
|
|
if (IS_ERR(irb)) {
|
|
/* FIXME: What to do with the request? */
|
|
switch (PTR_ERR(irb)) {
|
|
case -ETIMEDOUT:
|
|
DBF_LH(1, "(%s): Request timed out\n",
|
|
dev_name(&cdev->dev));
|
|
case -EIO:
|
|
__tape_end_request(device, request, -EIO);
|
|
break;
|
|
default:
|
|
DBF_LH(1, "(%s): Unexpected i/o error %li\n",
|
|
dev_name(&cdev->dev),
|
|
PTR_ERR(irb));
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* 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. 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.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.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);
|
|
else
|
|
device->tape_generic_status &= ~GMT_ONLINE(~0);
|
|
|
|
/*
|
|
* Any request that does not come back with channel end
|
|
* and device end is unusual. Log the sense data.
|
|
*/
|
|
DBF_EVENT(3,"-- Tape Interrupthandler --\n");
|
|
tape_dump_sense_dbf(device, request, irb);
|
|
} else {
|
|
/* Upon normal completion the device _is_ online */
|
|
device->tape_generic_status |= GMT_ONLINE(~0);
|
|
}
|
|
if (device->tape_state == TS_NOT_OPER) {
|
|
DBF_EVENT(6, "tape:device is not operational\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Request that were canceled still come back with an interrupt.
|
|
* To detect these request the state will be set to TAPE_REQUEST_DONE.
|
|
*/
|
|
if(request != NULL && request->status == TAPE_REQUEST_DONE) {
|
|
__tape_end_request(device, request, -EIO);
|
|
return;
|
|
}
|
|
|
|
rc = device->discipline->irq(device, request, irb);
|
|
/*
|
|
* rc < 0 : request finished unsuccessfully.
|
|
* rc == TAPE_IO_SUCCESS: request finished successfully.
|
|
* rc == TAPE_IO_PENDING: request is still running. Ignore rc.
|
|
* rc == TAPE_IO_RETRY: request finished but needs another go.
|
|
* rc == TAPE_IO_STOP: request needs to get terminated.
|
|
*/
|
|
switch (rc) {
|
|
case TAPE_IO_SUCCESS:
|
|
/* Upon normal completion the device _is_ online */
|
|
device->tape_generic_status |= GMT_ONLINE(~0);
|
|
__tape_end_request(device, request, rc);
|
|
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)
|
|
__tape_end_request(device, request, rc);
|
|
break;
|
|
case TAPE_IO_STOP:
|
|
rc = __tape_cancel_io(device, request);
|
|
if (rc)
|
|
__tape_end_request(device, request, rc);
|
|
break;
|
|
default:
|
|
if (rc > 0) {
|
|
DBF_EVENT(6, "xunknownrc\n");
|
|
__tape_end_request(device, request, -EIO);
|
|
} else {
|
|
__tape_end_request(device, request, rc);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Tape device open function used by tape_char & tape_block frontends.
|
|
*/
|
|
int
|
|
tape_open(struct tape_device *device)
|
|
{
|
|
int rc;
|
|
|
|
spin_lock_irq(get_ccwdev_lock(device->cdev));
|
|
if (device->tape_state == TS_NOT_OPER) {
|
|
DBF_EVENT(6, "TAPE:nodev\n");
|
|
rc = -ENODEV;
|
|
} else if (device->tape_state == TS_IN_USE) {
|
|
DBF_EVENT(6, "TAPE:dbusy\n");
|
|
rc = -EBUSY;
|
|
} else if (device->tape_state == TS_BLKUSE) {
|
|
DBF_EVENT(6, "TAPE:dbusy\n");
|
|
rc = -EBUSY;
|
|
} else if (device->discipline != NULL &&
|
|
!try_module_get(device->discipline->owner)) {
|
|
DBF_EVENT(6, "TAPE:nodisc\n");
|
|
rc = -ENODEV;
|
|
} else {
|
|
tape_state_set(device, TS_IN_USE);
|
|
rc = 0;
|
|
}
|
|
spin_unlock_irq(get_ccwdev_lock(device->cdev));
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Tape device release function used by tape_char & tape_block frontends.
|
|
*/
|
|
int
|
|
tape_release(struct tape_device *device)
|
|
{
|
|
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_irq(get_ccwdev_lock(device->cdev));
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Execute a magnetic tape command a number of times.
|
|
*/
|
|
int
|
|
tape_mtop(struct tape_device *device, int mt_op, int mt_count)
|
|
{
|
|
tape_mtop_fn fn;
|
|
int rc;
|
|
|
|
DBF_EVENT(6, "TAPE:mtio\n");
|
|
DBF_EVENT(6, "TAPE:ioop: %x\n", mt_op);
|
|
DBF_EVENT(6, "TAPE:arg: %x\n", mt_count);
|
|
|
|
if (mt_op < 0 || mt_op >= TAPE_NR_MTOPS)
|
|
return -EINVAL;
|
|
fn = device->discipline->mtop_array[mt_op];
|
|
if (fn == NULL)
|
|
return -EINVAL;
|
|
|
|
/* We assume that the backends can handle count up to 500. */
|
|
if (mt_op == MTBSR || mt_op == MTFSR || mt_op == MTFSF ||
|
|
mt_op == MTBSF || mt_op == MTFSFM || mt_op == MTBSFM) {
|
|
rc = 0;
|
|
for (; mt_count > 500; mt_count -= 500)
|
|
if ((rc = fn(device, 500)) != 0)
|
|
break;
|
|
if (rc == 0)
|
|
rc = fn(device, mt_count);
|
|
} else
|
|
rc = fn(device, mt_count);
|
|
return rc;
|
|
|
|
}
|
|
|
|
/*
|
|
* Tape init function.
|
|
*/
|
|
static int
|
|
tape_init (void)
|
|
{
|
|
TAPE_DBF_AREA = debug_register ( "tape", 2, 2, 4*sizeof(long));
|
|
debug_register_view(TAPE_DBF_AREA, &debug_sprintf_view);
|
|
#ifdef DBF_LIKE_HELL
|
|
debug_set_level(TAPE_DBF_AREA, 6);
|
|
#endif
|
|
DBF_EVENT(3, "tape init\n");
|
|
tape_proc_init();
|
|
tapechar_init ();
|
|
tapeblock_init ();
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Tape exit function.
|
|
*/
|
|
static void
|
|
tape_exit(void)
|
|
{
|
|
DBF_EVENT(6, "tape exit\n");
|
|
|
|
/* 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");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
module_init(tape_init);
|
|
module_exit(tape_exit);
|
|
|
|
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);
|
|
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_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);
|