Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / drivers / s390 / char / tape_core.c
index e51046a..e6e4086 100644 (file)
@@ -3,11 +3,12 @@
  *    basic function of the tape device driver
  *
  *  S390 and zSeries version
- *    Copyright (C) 2001,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ *    Copyright (C) 2001,2005 IBM Deutschland Entwicklung GmbH, IBM Corporation
  *    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>
  */
 
 #include <linux/config.h>
@@ -28,7 +29,7 @@
 #define PRINTK_HEADER "TAPE_CORE: "
 
 static void __tape_do_irq (struct ccw_device *, unsigned long, struct irb *);
-static void __tape_remove_request(struct tape_device *, struct tape_request *);
+static void tape_delayed_next_request(void * data);
 
 /*
  * One list to contain all tape devices of all disciplines, so
@@ -107,7 +108,7 @@ busid_to_int(char *bus_id)
  *        replaced by a link to the cdev tree.
  */
 static ssize_t
-tape_medium_state_show(struct device *dev, char *buf)
+tape_medium_state_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
        struct tape_device *tdev;
 
@@ -119,7 +120,7 @@ static
 DEVICE_ATTR(medium_state, 0444, tape_medium_state_show, NULL);
 
 static ssize_t
-tape_first_minor_show(struct device *dev, char *buf)
+tape_first_minor_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
        struct tape_device *tdev;
 
@@ -131,7 +132,7 @@ static
 DEVICE_ATTR(first_minor, 0444, tape_first_minor_show, NULL);
 
 static ssize_t
-tape_state_show(struct device *dev, char *buf)
+tape_state_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
        struct tape_device *tdev;
 
@@ -144,7 +145,7 @@ static
 DEVICE_ATTR(state, 0444, tape_state_show, NULL);
 
 static ssize_t
-tape_operation_show(struct device *dev, char *buf)
+tape_operation_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
        struct tape_device *tdev;
        ssize_t rc;
@@ -171,7 +172,7 @@ static
 DEVICE_ATTR(operation, 0444, tape_operation_show, NULL);
 
 static ssize_t
-tape_blocksize_show(struct device *dev, char *buf)
+tape_blocksize_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
        struct tape_device *tdev;
 
@@ -209,18 +210,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";
@@ -257,7 +254,7 @@ tape_med_state_set(struct tape_device *device, enum tape_medium_state newstate)
  * Stop running ccw. Has to be called with the device lock held.
  */
 static inline int
-__tape_halt_io(struct tape_device *device, struct tape_request *request)
+__tape_cancel_io(struct tape_device *device, struct tape_request *request)
 {
        int retries;
        int rc;
@@ -270,20 +267,23 @@ __tape_halt_io(struct tape_device *device, struct tape_request *request)
        for (retries = 0; retries < 5; retries++) {
                rc = ccw_device_clear(device->cdev, (long) request);
 
-               if (rc == 0) {                     /* Termination successful */
-                       request->rc     = -EIO;
-                       request->status = TAPE_REQUEST_DONE;
-                       return 0;
+               switch (rc) {
+                       case 0:
+                               request->status = TAPE_REQUEST_DONE;
+                               return 0;
+                       case -EBUSY:
+                               request->status = TAPE_REQUEST_CANCEL;
+                               schedule_work(&device->tape_dnr);
+                               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();
                }
-
-               if (rc == -ENODEV)
-                       DBF_EXCEPTION(2, "device gone, retry\n");
-               else if (rc == -EIO)
-                       DBF_EXCEPTION(2, "I/O error, retry\n");
-               else if (rc == -EBUSY)
-                       DBF_EXCEPTION(2, "device busy, retry late\n");
-               else
-                       BUG();
        }
 
        return rc;
@@ -449,16 +449,14 @@ 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");
@@ -473,6 +471,7 @@ tape_alloc_device(void)
        *device->modeset_byte = 0;
        device->first_minor = -1;
        atomic_set(&device->ref_count, 1);
+       INIT_WORK(&device->tape_dnr, tape_delayed_next_request, device);
 
        return device;
 }
@@ -654,35 +653,30 @@ tape_alloc_request(int cplength, int datasize)
 
        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");
-                       if (request->cpaddr != NULL)
-                               kfree(request->cpaddr);
+                       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);
@@ -701,61 +695,131 @@ tape_free_request (struct tape_request * request)
        if (request->device != NULL) {
                request->device = tape_put_device(request->device);
        }
-       if (request->cpdata != NULL)
-               kfree(request->cpdata);
-       if (request->cpaddr != NULL)
-               kfree(request->cpaddr);
+       kfree(request->cpdata);
+       kfree(request->cpaddr);
        kfree(request);
 }
 
+static inline 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_work(&device->tape_dnr);
+               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 inline void
-__tape_do_io_list(struct tape_device *device)
+__tape_start_next_request(struct tape_device *device)
 {
        struct list_head *l, *n;
        struct tape_request *request;
        int rc;
 
-       DBF_LH(6, "__tape_do_io_list(%p)\n", device);
+       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);
-#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;
-                       break;
+
+               /*
+                * 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);
                }
-               /* Start failed. Remove request and indicate failure. */
-               DBF_EVENT(1, "tape: DOIO failed with er = %i\n", rc);
+               if (rc == 0)
+                       return;
 
-               /* Set ending status and do callback. */
+               /* Set ending status. */
                request->rc = rc;
                request->status = TAPE_REQUEST_DONE;
-               __tape_remove_request(device, request);
+
+               /* Remove from request queue. */
+               list_del(&request->list);
+
+               /* Do callback. */
+               if (request->callback != NULL)
+                       request->callback(request, request->callback_data);
        }
 }
 
 static void
-__tape_remove_request(struct tape_device *device, struct tape_request *request)
+tape_delayed_next_request(void *data)
+{
+       struct tape_device *    device;
+
+       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
+__tape_end_request(
+       struct tape_device *    device,
+       struct tape_request *   request,
+       int                     rc)
 {
-       /* Remove from request queue. */
-       list_del(&request->list);
+       DBF_LH(6, "__tape_end_request(%p, %p, %i)\n", device, request, rc);
+       if (request) {
+               request->rc = rc;
+               request->status = TAPE_REQUEST_DONE;
 
-       /* Do callback. */
-       if (request->callback != NULL)
-               request->callback(request, request->callback_data);
+               /* 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_do_io_list(device);
+               __tape_start_next_request(device);
 }
 
 /*
@@ -812,7 +876,7 @@ tape_dump_sense_dbf(struct tape_device *device, struct tape_request *request,
  * the device lock held.
  */
 static inline int
-__tape_do_io(struct tape_device *device, struct tape_request *request)
+__tape_start_request(struct tape_device *device, struct tape_request *request)
 {
        int rc;
 
@@ -837,24 +901,16 @@ __tape_do_io(struct tape_device *device, struct tape_request *request)
 
        if (list_empty(&device->req_queue)) {
                /* No other requests are on the queue. Start this one. */
-#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) {
-                       DBF_EVENT(1, "tape: DOIO failed with rc = %i\n", rc);
+               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);
-               request->status = TAPE_REQUEST_IN_IO;
        } else {
                DBF_LH(5, "Request %p add to queue.\n", request);
-               list_add_tail(&request->list, &device->req_queue);
                request->status = TAPE_REQUEST_QUEUED;
+               list_add_tail(&request->list, &device->req_queue);
        }
        return 0;
 }
@@ -872,7 +928,7 @@ tape_do_io_async(struct tape_device *device, struct tape_request *request)
 
        spin_lock_irq(get_ccwdev_lock(device->cdev));
        /* Add request to request queue and try to start it. */
-       rc = __tape_do_io(device, request);
+       rc = __tape_start_request(device, request);
        spin_unlock_irq(get_ccwdev_lock(device->cdev));
        return rc;
 }
@@ -901,7 +957,7 @@ tape_do_io(struct tape_device *device, struct tape_request *request)
        request->callback = __tape_wake_up;
        request->callback_data = &wq;
        /* Add request to request queue and try to start it. */
-       rc = __tape_do_io(device, request);
+       rc = __tape_start_request(device, request);
        spin_unlock_irq(get_ccwdev_lock(device->cdev));
        if (rc)
                return rc;
@@ -935,7 +991,7 @@ tape_do_io_interruptible(struct tape_device *device,
        /* Setup callback */
        request->callback = __tape_wake_up_interruptible;
        request->callback_data = &wq;
-       rc = __tape_do_io(device, request);
+       rc = __tape_start_request(device, request);
        spin_unlock_irq(get_ccwdev_lock(device->cdev));
        if (rc)
                return rc;
@@ -944,33 +1000,38 @@ tape_do_io_interruptible(struct tape_device *device,
        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_halt_io(device, request);
+       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(
+                               wq,
+                               (request->callback == NULL)
+                       );
+               } while (rc == -ERESTARTSYS);
+
                DBF_EVENT(3, "IO stopped on %08x\n", device->cdev_id);
                rc = -ERESTARTSYS;
        }
-       spin_unlock_irq(get_ccwdev_lock(device->cdev));
        return rc;
 }
 
 /*
- * Handle requests that return an i/o error in the irb.
+ * Stop running ccw.
  */
-static inline void
-tape_handle_killed_request(
-       struct tape_device *device,
-       struct tape_request *request)
+int
+tape_cancel_io(struct tape_device *device, struct tape_request *request)
 {
-       if(request != NULL) {
-               /* Set ending status. FIXME: Should the request be retried? */
-               request->rc = -EIO;
-               request->status = TAPE_REQUEST_DONE;
-               __tape_remove_request(device, request);
-       } else {
-               __tape_do_io_list(device);
-       }
+       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;
 }
 
 /*
@@ -981,7 +1042,6 @@ __tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
 {
        struct tape_device *device;
        struct tape_request *request;
-       int final;
        int rc;
 
        device = (struct tape_device *) cdev->dev.driver_data;
@@ -996,12 +1056,13 @@ __tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
 
        /* 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:
                                PRINT_WARN("(%s): Request timed out\n",
                                        cdev->dev.bus_id);
                        case -EIO:
-                               tape_handle_killed_request(device, request);
+                               __tape_end_request(device, request, -EIO);
                                break;
                        default:
                                PRINT_ERR("(%s): Unexpected i/o error %li\n",
@@ -1011,6 +1072,22 @@ __tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *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.cc != 0 && (irb->scsw.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.cc, irb->scsw.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;
@@ -1042,7 +1119,7 @@ __tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
         * To detect these request the state will be set to TAPE_REQUEST_DONE.
         */
        if(request != NULL && request->status == TAPE_REQUEST_DONE) {
-               __tape_remove_request(device, request);
+               __tape_end_request(device, request, -EIO);
                return;
        }
 
@@ -1054,51 +1131,34 @@ __tape_do_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
         * rc == TAPE_IO_RETRY: request finished but needs another go.
         * rc == TAPE_IO_STOP: request needs to get terminated.
         */
-       final = 0;
        switch (rc) {
-       case TAPE_IO_SUCCESS:
-               /* Upon normal completion the device _is_ online */
-               device->tape_generic_status |= GMT_ONLINE(~0);
-               final = 1;
-               break;
-       case TAPE_IO_PENDING:
-               break;
-       case TAPE_IO_RETRY:
-#ifdef CONFIG_S390_TAPE_BLOCK
-               if (request->op == TO_BLOCK)
-                       device->discipline->check_locate(device, request);
-#endif
-               rc = ccw_device_start(cdev, request->cpaddr,
-                                     (unsigned long) request, 0x00,
-                                     request->options);
-               if (rc) {
-                       DBF_EVENT(1, "tape: DOIO failed with er = %i\n", rc);
-                       final = 1;
-               }
-               break;
-       case TAPE_IO_STOP:
-               __tape_halt_io(device, request);
-               break;
-       default:
-               if (rc > 0) {
-                       DBF_EVENT(6, "xunknownrc\n");
-                       PRINT_ERR("Invalid return code from discipline "
-                                 "interrupt function.\n");
-                       rc = -EIO;
-               }
-               final = 1;
-               break;
-       }
-       if (final) {
-               /* May be an unsolicited irq */
-               if(request != NULL) {
-                       /* Set ending status. */
-                       request->rc = rc;
-                       request->status = TAPE_REQUEST_DONE;
-                       __tape_remove_request(device, request);
-               } else {
-                       __tape_do_io_list(device);
-               }
+               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_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");
+                               PRINT_ERR("Invalid return code from discipline "
+                                       "interrupt function.\n");
+                               __tape_end_request(device, request, -EIO);
+                       } else {
+                               __tape_end_request(device, request, rc);
+                       }
+                       break;
        }
 }
 
@@ -1186,12 +1246,12 @@ tape_mtop(struct tape_device *device, int mt_op, int mt_count)
 static int
 tape_init (void)
 {
-       TAPE_DBF_AREA = debug_register ( "tape", 1, 2, 4*sizeof(long));
+       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: ($Revision: 1.51 $)\n");
+       DBF_EVENT(3, "tape init\n");
        tape_proc_init();
        tapechar_init ();
        tapeblock_init ();
@@ -1215,8 +1275,7 @@ tape_exit(void)
 
 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.51 $)");
+MODULE_DESCRIPTION("Linux on zSeries channel attached tape device driver");
 MODULE_LICENSE("GPL");
 
 module_init(tape_init);
@@ -1239,4 +1298,5 @@ 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);