X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fs390%2Fchar%2Ftape_core.c;h=4ea438c749c9c6f4764ea1056c1baae1d1c52446;hb=987b0145d94eecf292d8b301228356f44611ab7c;hp=668fe3f11586ba5543f743b6fdc82e12d6073e30;hpb=39b607ac274a09883c7fe5f03a7eff28960a1190;p=linux-2.6.git diff --git a/drivers/s390/char/tape_core.c b/drivers/s390/char/tape_core.c index 668fe3f11..4ea438c74 100644 --- a/drivers/s390/char/tape_core.c +++ b/drivers/s390/char/tape_core.c @@ -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 * Michael Holzheu * Tuan Ngo-Anh * Martin Schwidefsky + * Stefan Bader */ #include @@ -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 @@ -36,7 +37,7 @@ static void __tape_remove_request(struct tape_device *, struct tape_request *); * The list is protected by the rwlock */ static struct list_head tape_device_list = LIST_HEAD_INIT(tape_device_list); -static rwlock_t tape_device_lock = RW_LOCK_UNLOCKED; +static DEFINE_RWLOCK(tape_device_lock); /* * Pointer to debug area. @@ -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; @@ -257,7 +258,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 +271,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; @@ -349,6 +353,11 @@ tape_generic_online(struct tape_device *device, /* 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; + } + rc = discipline->setup_device(device); if (rc) goto out; @@ -377,6 +386,7 @@ out_discipline: out_minor: tape_remove_minor(device); out: + module_put(discipline->owner); return rc; } @@ -386,6 +396,7 @@ 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); } @@ -466,6 +477,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; } @@ -670,8 +682,7 @@ tape_alloc_request(int cplength, int datasize) request->cpdata = kmalloc(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); } @@ -694,61 +705,124 @@ 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; + + /* + * 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) { - /* Remove from request queue. */ - list_del(&request->list); + struct tape_device * device; - /* Do callback. */ - if (request->callback != NULL) - request->callback(request, request->callback_data); + 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) +{ + 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_do_io_list(device); + __tape_start_next_request(device); } /* @@ -805,7 +879,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; @@ -830,24 +904,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; } @@ -865,7 +931,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; } @@ -894,7 +960,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; @@ -928,7 +994,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; @@ -937,35 +1003,26 @@ 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. - */ -static inline void -tape_handle_killed_request( - 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); - } -} - /* * Tape interrupt routine, called from the ccw_device layer */ @@ -974,7 +1031,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; @@ -989,12 +1045,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", @@ -1004,6 +1061,21 @@ __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. Restart the request now. + */ + 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); + return; + } + /* May be an unsolicited irq */ if(request != NULL) request->rescnt = irb->scsw.count; @@ -1035,7 +1107,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; } @@ -1047,51 +1119,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; } } @@ -1179,12 +1234,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.50 $)\n"); + DBF_EVENT(3, "tape init\n"); tape_proc_init(); tapechar_init (); tapeblock_init (); @@ -1208,8 +1263,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.50 $)"); +MODULE_DESCRIPTION("Linux on zSeries channel attached tape device driver"); MODULE_LICENSE("GPL"); module_init(tape_init);