diff options
Diffstat (limited to 'drivers/s390/block/dasd.c')
-rw-r--r-- | drivers/s390/block/dasd.c | 200 |
1 files changed, 160 insertions, 40 deletions
diff --git a/drivers/s390/block/dasd.c b/drivers/s390/block/dasd.c index d1815272c43..3f62dd50bbb 100644 --- a/drivers/s390/block/dasd.c +++ b/drivers/s390/block/dasd.c @@ -5,8 +5,7 @@ * Carsten Otte <Cotte@de.ibm.com> * Martin Schwidefsky <schwidefsky@de.ibm.com> * Bugreports.to..: <Linux390@de.ibm.com> - * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001 - * + * Copyright IBM Corp. 1999, 2009 */ #define KMSG_COMPONENT "dasd" @@ -61,6 +60,7 @@ static int dasd_flush_block_queue(struct dasd_block *); static void dasd_device_tasklet(struct dasd_device *); static void dasd_block_tasklet(struct dasd_block *); static void do_kick_device(struct work_struct *); +static void do_restore_device(struct work_struct *); static void dasd_return_cqr_cb(struct dasd_ccw_req *, void *); static void dasd_device_timeout(unsigned long); static void dasd_block_timeout(unsigned long); @@ -109,6 +109,7 @@ struct dasd_device *dasd_alloc_device(void) device->timer.function = dasd_device_timeout; device->timer.data = (unsigned long) device; INIT_WORK(&device->kick_work, do_kick_device); + INIT_WORK(&device->restore_device, do_restore_device); device->state = DASD_STATE_NEW; device->target = DASD_STATE_NEW; @@ -469,7 +470,7 @@ static int dasd_decrease_state(struct dasd_device *device) */ static void dasd_change_state(struct dasd_device *device) { - int rc; + int rc; if (device->state == device->target) /* Already where we want to go today... */ @@ -478,8 +479,10 @@ static void dasd_change_state(struct dasd_device *device) rc = dasd_increase_state(device); else rc = dasd_decrease_state(device); - if (rc && rc != -EAGAIN) - device->target = device->state; + if (rc == -EAGAIN) + return; + if (rc) + device->target = device->state; if (device->state == device->target) { wake_up(&dasd_init_waitq); @@ -512,6 +515,25 @@ void dasd_kick_device(struct dasd_device *device) } /* + * dasd_restore_device will schedule a call do do_restore_device to the kernel + * event daemon. + */ +static void do_restore_device(struct work_struct *work) +{ + struct dasd_device *device = container_of(work, struct dasd_device, + restore_device); + device->cdev->drv->restore(device->cdev); + dasd_put_device(device); +} + +void dasd_restore_device(struct dasd_device *device) +{ + dasd_get_device(device); + /* queue call to dasd_restore_device to the kernel event daemon. */ + schedule_work(&device->restore_device); +} + +/* * Set the target state for a device and starts the state change. */ void dasd_set_target_state(struct dasd_device *device, int target) @@ -603,7 +625,7 @@ static void dasd_profile_end(struct dasd_block *block, if (dasd_profile_level != DASD_PROFILE_ON) return; - sectors = req->nr_sectors; + sectors = blk_rq_sectors(req); if (!cqr->buildclk || !cqr->startclk || !cqr->stopclk || !cqr->endclk || !sectors) @@ -851,8 +873,10 @@ int dasd_start_IO(struct dasd_ccw_req *cqr) /* Check the cqr */ rc = dasd_check_cqr(cqr); - if (rc) + if (rc) { + cqr->intrc = rc; return rc; + } device = (struct dasd_device *) cqr->startdev; if (cqr->retries < 0) { /* internal error 14 - start_IO run out of retries */ @@ -906,6 +930,12 @@ int dasd_start_IO(struct dasd_ccw_req *cqr) DBF_DEV_EVENT(DBF_DEBUG, device, "%s", "start_IO: -EIO device gone, retry"); break; + case -EINVAL: + /* most likely caused in power management context */ + DBF_DEV_EVENT(DBF_DEBUG, device, "%s", + "start_IO: -EINVAL device currently " + "not accessible"); + break; default: /* internal error 11 - unknown rc */ snprintf(errorstring, ERRORLENGTH, "11 %d", rc); @@ -915,6 +945,7 @@ int dasd_start_IO(struct dasd_ccw_req *cqr) BUG(); break; } + cqr->intrc = rc; return rc; } @@ -1454,8 +1485,12 @@ int dasd_sleep_on(struct dasd_ccw_req *cqr) dasd_add_request_tail(cqr); wait_event(generic_waitq, _wait_for_wakeup(cqr)); - /* Request status is either done or failed. */ - rc = (cqr->status == DASD_CQR_DONE) ? 0 : -EIO; + if (cqr->status == DASD_CQR_DONE) + rc = 0; + else if (cqr->intrc) + rc = cqr->intrc; + else + rc = -EIO; return rc; } @@ -1477,8 +1512,15 @@ int dasd_sleep_on_interruptible(struct dasd_ccw_req *cqr) dasd_cancel_req(cqr); /* wait (non-interruptible) for final status */ wait_event(generic_waitq, _wait_for_wakeup(cqr)); + cqr->intrc = rc; } - rc = (cqr->status == DASD_CQR_DONE) ? 0 : -EIO; + + if (cqr->status == DASD_CQR_DONE) + rc = 0; + else if (cqr->intrc) + rc = cqr->intrc; + else + rc = -EIO; return rc; } @@ -1523,8 +1565,12 @@ int dasd_sleep_on_immediatly(struct dasd_ccw_req *cqr) wait_event(generic_waitq, _wait_for_wakeup(cqr)); - /* Request status is either done or failed. */ - rc = (cqr->status == DASD_CQR_DONE) ? 0 : -EIO; + if (cqr->status == DASD_CQR_DONE) + rc = 0; + else if (cqr->intrc) + rc = cqr->intrc; + else + rc = -EIO; return rc; } @@ -1614,15 +1660,6 @@ void dasd_block_clear_timer(struct dasd_block *block) } /* - * posts the buffer_cache about a finalized request - */ -static inline void dasd_end_request(struct request *req, int error) -{ - if (__blk_end_request(req, error, blk_rq_bytes(req))) - BUG(); -} - -/* * Process finished error recovery ccw. */ static inline void __dasd_block_process_erp(struct dasd_block *block, @@ -1665,18 +1702,14 @@ static void __dasd_process_request_queue(struct dasd_block *block) if (basedev->state < DASD_STATE_READY) return; /* Now we try to fetch requests from the request queue */ - while (!blk_queue_plugged(queue) && - elv_next_request(queue)) { - - req = elv_next_request(queue); - + while (!blk_queue_plugged(queue) && (req = blk_peek_request(queue))) { if (basedev->features & DASD_FEATURE_READONLY && rq_data_dir(req) == WRITE) { DBF_DEV_EVENT(DBF_ERR, basedev, "Rejecting write request %p", req); - blkdev_dequeue_request(req); - dasd_end_request(req, -EIO); + blk_start_request(req); + __blk_end_request_all(req, -EIO); continue; } cqr = basedev->discipline->build_cp(basedev, block, req); @@ -1704,8 +1737,8 @@ static void __dasd_process_request_queue(struct dasd_block *block) "CCW creation failed (rc=%ld) " "on request %p", PTR_ERR(cqr), req); - blkdev_dequeue_request(req); - dasd_end_request(req, -EIO); + blk_start_request(req); + __blk_end_request_all(req, -EIO); continue; } /* @@ -1714,7 +1747,7 @@ static void __dasd_process_request_queue(struct dasd_block *block) */ cqr->callback_data = (void *) req; cqr->status = DASD_CQR_FILLED; - blkdev_dequeue_request(req); + blk_start_request(req); list_add_tail(&cqr->blocklist, &block->ccw_queue); dasd_profile_start(block, cqr, req); } @@ -1731,7 +1764,7 @@ static void __dasd_cleanup_cqr(struct dasd_ccw_req *cqr) status = cqr->block->base->discipline->free_cp(cqr, req); if (status <= 0) error = status ? status : -EIO; - dasd_end_request(req, error); + __blk_end_request_all(req, error); } /* @@ -2003,7 +2036,7 @@ static void dasd_setup_queue(struct dasd_block *block) { int max; - blk_queue_hardsect_size(block->request_queue, block->bp_block); + blk_queue_logical_block_size(block->request_queue, block->bp_block); max = block->base->discipline->max_blocks << block->s2b_shift; blk_queue_max_sectors(block->request_queue, max); blk_queue_max_phys_segments(block->request_queue, -1L); @@ -2038,10 +2071,8 @@ static void dasd_flush_request_queue(struct dasd_block *block) return; spin_lock_irq(&block->request_queue_lock); - while ((req = elv_next_request(block->request_queue))) { - blkdev_dequeue_request(req); - dasd_end_request(req, -EIO); - } + while ((req = blk_fetch_request(block->request_queue))) + __blk_end_request_all(req, -EIO); spin_unlock_irq(&block->request_queue_lock); } @@ -2104,9 +2135,9 @@ static int dasd_getgeo(struct block_device *bdev, struct hd_geometry *geo) struct dasd_device *base; block = bdev->bd_disk->private_data; - base = block->base; if (!block) return -ENODEV; + base = block->base; if (!base->discipline || !base->discipline->fill_geometry) @@ -2397,6 +2428,12 @@ int dasd_generic_notify(struct ccw_device *cdev, int event) case CIO_OPER: /* FIXME: add a sanity check. */ device->stopped &= ~DASD_STOPPED_DC_WAIT; + if (device->stopped & DASD_UNRESUMED_PM) { + device->stopped &= ~DASD_UNRESUMED_PM; + dasd_restore_device(device); + ret = 1; + break; + } dasd_schedule_device_bh(device); if (device->block) dasd_schedule_block_bh(device->block); @@ -2407,6 +2444,89 @@ int dasd_generic_notify(struct ccw_device *cdev, int event) return ret; } +int dasd_generic_pm_freeze(struct ccw_device *cdev) +{ + struct dasd_ccw_req *cqr, *n; + int rc; + struct list_head freeze_queue; + struct dasd_device *device = dasd_device_from_cdev(cdev); + + if (IS_ERR(device)) + return PTR_ERR(device); + /* disallow new I/O */ + device->stopped |= DASD_STOPPED_PM; + /* clear active requests */ + INIT_LIST_HEAD(&freeze_queue); + spin_lock_irq(get_ccwdev_lock(cdev)); + rc = 0; + list_for_each_entry_safe(cqr, n, &device->ccw_queue, devlist) { + /* Check status and move request to flush_queue */ + if (cqr->status == DASD_CQR_IN_IO) { + rc = device->discipline->term_IO(cqr); + if (rc) { + /* unable to terminate requeust */ + dev_err(&device->cdev->dev, + "Unable to terminate request %p " + "on suspend\n", cqr); + spin_unlock_irq(get_ccwdev_lock(cdev)); + dasd_put_device(device); + return rc; + } + } + list_move_tail(&cqr->devlist, &freeze_queue); + } + + spin_unlock_irq(get_ccwdev_lock(cdev)); + + list_for_each_entry_safe(cqr, n, &freeze_queue, devlist) { + wait_event(dasd_flush_wq, + (cqr->status != DASD_CQR_CLEAR_PENDING)); + if (cqr->status == DASD_CQR_CLEARED) + cqr->status = DASD_CQR_QUEUED; + } + /* move freeze_queue to start of the ccw_queue */ + spin_lock_irq(get_ccwdev_lock(cdev)); + list_splice_tail(&freeze_queue, &device->ccw_queue); + spin_unlock_irq(get_ccwdev_lock(cdev)); + + if (device->discipline->freeze) + rc = device->discipline->freeze(device); + + dasd_put_device(device); + return rc; +} +EXPORT_SYMBOL_GPL(dasd_generic_pm_freeze); + +int dasd_generic_restore_device(struct ccw_device *cdev) +{ + struct dasd_device *device = dasd_device_from_cdev(cdev); + int rc = 0; + + if (IS_ERR(device)) + return PTR_ERR(device); + + /* allow new IO again */ + device->stopped &= ~DASD_STOPPED_PM; + device->stopped &= ~DASD_UNRESUMED_PM; + + dasd_schedule_device_bh(device); + if (device->block) + dasd_schedule_block_bh(device->block); + + if (device->discipline->restore) + rc = device->discipline->restore(device); + if (rc) + /* + * if the resume failed for the DASD we put it in + * an UNRESUMED stop state + */ + device->stopped |= DASD_UNRESUMED_PM; + + dasd_put_device(device); + return 0; +} +EXPORT_SYMBOL_GPL(dasd_generic_restore_device); + static struct dasd_ccw_req *dasd_generic_build_rdc(struct dasd_device *device, void *rdc_buffer, int rdc_buffer_size, @@ -2442,12 +2562,12 @@ static struct dasd_ccw_req *dasd_generic_build_rdc(struct dasd_device *device, int dasd_generic_read_dev_chars(struct dasd_device *device, char *magic, - void **rdc_buffer, int rdc_buffer_size) + void *rdc_buffer, int rdc_buffer_size) { int ret; struct dasd_ccw_req *cqr; - cqr = dasd_generic_build_rdc(device, *rdc_buffer, rdc_buffer_size, + cqr = dasd_generic_build_rdc(device, rdc_buffer, rdc_buffer_size, magic); if (IS_ERR(cqr)) return PTR_ERR(cqr); |