diff options
author | Stefan Bader <shbader@de.ibm.com> | 2005-07-27 11:45:04 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2005-07-27 16:26:05 -0700 |
commit | 4111796d89b8cfa36054d65d9858460b5ec0e8c7 (patch) | |
tree | f42f2822983882f391a99414d98a572cbc330f77 /drivers/s390/char/tape_core.c | |
parent | 6bb0e01081c2ca585b5e145783fea53bb0589786 (diff) |
[PATCH] s390: channel tape fixes
Tape driver fixes:
- Added deferred condition handling to tape driver core.
- Added ability to handle busy conditions.
- Code cleanup.
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers/s390/char/tape_core.c')
-rw-r--r-- | drivers/s390/char/tape_core.c | 299 |
1 files changed, 175 insertions, 124 deletions
diff --git a/drivers/s390/char/tape_core.c b/drivers/s390/char/tape_core.c index 0597aa0e27ee..6c52e8307dc5 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 <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 @@ -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; @@ -473,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; } @@ -708,54 +713,119 @@ tape_free_request (struct tape_request * request) 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); } /* @@ -812,7 +882,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 +907,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 +934,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 +963,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 +997,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,36 +1006,27 @@ 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 */ static void @@ -981,7 +1034,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 +1048,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 +1064,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; @@ -1042,7 +1110,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 +1122,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; } } @@ -1191,7 +1242,7 @@ tape_init (void) #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: ($Revision: 1.54 $)\n"); tape_proc_init(); tapechar_init (); tapeblock_init (); @@ -1216,7 +1267,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 $)"); + "tape device driver ($Revision: 1.54 $)"); MODULE_LICENSE("GPL"); module_init(tape_init); |