[S390] dasd: fix race between tasklet and dasd_sleep_on

The various dasd_sleep_on functions use a global wait queue when
waiting for a cqr. The wait condition checks the status and devlist
fields of the cqr to determine if it is safe to continue. This
evaluation may return true, although the tasklet has not finished
processing of the cqr and the callback function has not been called
yet. When the callback is finally called, the data in the cqr may
already be invalid. The sleep_on wait condition needs a safe way to
determine if the tasklet has finished processing. Use the
callback_data field of the cqr to store a token, which is set by
the callback function itself.

Cc: <stable@kernel.org>
Signed-off-by: Stefan Weinhuber <wein@de.ibm.com>
Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
This commit is contained in:
Stefan Weinhuber 2010-05-12 09:32:11 +02:00 committed by Martin Schwidefsky
parent cea0d767c2
commit 1c1e093cbf

View file

@ -37,6 +37,9 @@
*/ */
#define DASD_CHANQ_MAX_SIZE 4 #define DASD_CHANQ_MAX_SIZE 4
#define DASD_SLEEPON_START_TAG (void *) 1
#define DASD_SLEEPON_END_TAG (void *) 2
/* /*
* SECTION: exported variables of dasd.c * SECTION: exported variables of dasd.c
*/ */
@ -1472,7 +1475,10 @@ void dasd_add_request_tail(struct dasd_ccw_req *cqr)
*/ */
static void dasd_wakeup_cb(struct dasd_ccw_req *cqr, void *data) static void dasd_wakeup_cb(struct dasd_ccw_req *cqr, void *data)
{ {
wake_up((wait_queue_head_t *) data); spin_lock_irq(get_ccwdev_lock(cqr->startdev->cdev));
cqr->callback_data = DASD_SLEEPON_END_TAG;
spin_unlock_irq(get_ccwdev_lock(cqr->startdev->cdev));
wake_up(&generic_waitq);
} }
static inline int _wait_for_wakeup(struct dasd_ccw_req *cqr) static inline int _wait_for_wakeup(struct dasd_ccw_req *cqr)
@ -1482,10 +1488,7 @@ static inline int _wait_for_wakeup(struct dasd_ccw_req *cqr)
device = cqr->startdev; device = cqr->startdev;
spin_lock_irq(get_ccwdev_lock(device->cdev)); spin_lock_irq(get_ccwdev_lock(device->cdev));
rc = ((cqr->status == DASD_CQR_DONE || rc = (cqr->callback_data == DASD_SLEEPON_END_TAG);
cqr->status == DASD_CQR_NEED_ERP ||
cqr->status == DASD_CQR_TERMINATED) &&
list_empty(&cqr->devlist));
spin_unlock_irq(get_ccwdev_lock(device->cdev)); spin_unlock_irq(get_ccwdev_lock(device->cdev));
return rc; return rc;
} }
@ -1573,7 +1576,7 @@ static int _dasd_sleep_on(struct dasd_ccw_req *maincqr, int interruptible)
wait_event(generic_waitq, !(device->stopped)); wait_event(generic_waitq, !(device->stopped));
cqr->callback = dasd_wakeup_cb; cqr->callback = dasd_wakeup_cb;
cqr->callback_data = (void *) &generic_waitq; cqr->callback_data = DASD_SLEEPON_START_TAG;
dasd_add_request_tail(cqr); dasd_add_request_tail(cqr);
if (interruptible) { if (interruptible) {
rc = wait_event_interruptible( rc = wait_event_interruptible(
@ -1652,7 +1655,7 @@ int dasd_sleep_on_immediatly(struct dasd_ccw_req *cqr)
} }
cqr->callback = dasd_wakeup_cb; cqr->callback = dasd_wakeup_cb;
cqr->callback_data = (void *) &generic_waitq; cqr->callback_data = DASD_SLEEPON_START_TAG;
cqr->status = DASD_CQR_QUEUED; cqr->status = DASD_CQR_QUEUED;
list_add(&cqr->devlist, &device->ccw_queue); list_add(&cqr->devlist, &device->ccw_queue);