diff options
author | Peter Oberparleiter <peter.oberparleiter@de.ibm.com> | 2009-12-07 06:51:32 -0500 |
---|---|---|
committer | Martin Schwidefsky <sky@mschwide.boeblingen.de.ibm.com> | 2009-12-07 06:51:32 -0500 |
commit | d7d12ef2befac4fed0dccaddff11338b654804df (patch) | |
tree | 1563b299e609024844affbc3ebba99c0718db238 /drivers/s390/cio | |
parent | 52ef0608e3ee4a511725e443c4b572fece22b353 (diff) |
[S390] cio: make steal lock procedure more robust
An Unconditional Reserve + Release operation (steal lock) for a
boxed device may fail when encountering special error cases
(e.g. unit checks or path errors). Fix this by using the more
robust ccw_request infrastructure for performing the steal lock
CCW program.
Signed-off-by: Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'drivers/s390/cio')
-rw-r--r-- | drivers/s390/cio/device.h | 4 | ||||
-rw-r--r-- | drivers/s390/cio/device_fsm.c | 55 | ||||
-rw-r--r-- | drivers/s390/cio/device_ops.c | 112 | ||||
-rw-r--r-- | drivers/s390/cio/device_pgid.c | 52 |
4 files changed, 134 insertions, 89 deletions
diff --git a/drivers/s390/cio/device.h b/drivers/s390/cio/device.h index 2df519bb877e..bcfe13e42638 100644 --- a/drivers/s390/cio/device.h +++ b/drivers/s390/cio/device.h | |||
@@ -28,6 +28,7 @@ enum dev_state { | |||
28 | DEV_STATE_DISCONNECTED_SENSE_ID, | 28 | DEV_STATE_DISCONNECTED_SENSE_ID, |
29 | DEV_STATE_CMFCHANGE, | 29 | DEV_STATE_CMFCHANGE, |
30 | DEV_STATE_CMFUPDATE, | 30 | DEV_STATE_CMFUPDATE, |
31 | DEV_STATE_STEAL_LOCK, | ||
31 | /* last element! */ | 32 | /* last element! */ |
32 | NR_DEV_STATES | 33 | NR_DEV_STATES |
33 | }; | 34 | }; |
@@ -116,6 +117,9 @@ void ccw_device_verify_done(struct ccw_device *, int); | |||
116 | void ccw_device_disband_start(struct ccw_device *); | 117 | void ccw_device_disband_start(struct ccw_device *); |
117 | void ccw_device_disband_done(struct ccw_device *, int); | 118 | void ccw_device_disband_done(struct ccw_device *, int); |
118 | 119 | ||
120 | void ccw_device_stlck_start(struct ccw_device *, void *, void *, void *); | ||
121 | void ccw_device_stlck_done(struct ccw_device *, void *, int); | ||
122 | |||
119 | int ccw_device_call_handler(struct ccw_device *); | 123 | int ccw_device_call_handler(struct ccw_device *); |
120 | 124 | ||
121 | int ccw_device_stlck(struct ccw_device *); | 125 | int ccw_device_stlck(struct ccw_device *); |
diff --git a/drivers/s390/cio/device_fsm.c b/drivers/s390/cio/device_fsm.c index 8d565ff85e43..7d42417bc2c7 100644 --- a/drivers/s390/cio/device_fsm.c +++ b/drivers/s390/cio/device_fsm.c | |||
@@ -641,6 +641,23 @@ ccw_device_online_verify(struct ccw_device *cdev, enum dev_event dev_event) | |||
641 | } | 641 | } |
642 | 642 | ||
643 | /* | 643 | /* |
644 | * Handle path verification event in boxed state. | ||
645 | */ | ||
646 | static void ccw_device_boxed_verify(struct ccw_device *cdev, | ||
647 | enum dev_event dev_event) | ||
648 | { | ||
649 | struct subchannel *sch = to_subchannel(cdev->dev.parent); | ||
650 | |||
651 | if (cdev->online) { | ||
652 | if (cio_enable_subchannel(sch, (u32) (addr_t) sch)) | ||
653 | ccw_device_done(cdev, DEV_STATE_NOT_OPER); | ||
654 | else | ||
655 | ccw_device_online_verify(cdev, dev_event); | ||
656 | } else | ||
657 | css_schedule_eval(sch->schid); | ||
658 | } | ||
659 | |||
660 | /* | ||
644 | * Got an interrupt for a normal io (state online). | 661 | * Got an interrupt for a normal io (state online). |
645 | */ | 662 | */ |
646 | static void | 663 | static void |
@@ -817,32 +834,6 @@ ccw_device_delay_verify(struct ccw_device *cdev, enum dev_event dev_event) | |||
817 | } | 834 | } |
818 | 835 | ||
819 | static void | 836 | static void |
820 | ccw_device_stlck_done(struct ccw_device *cdev, enum dev_event dev_event) | ||
821 | { | ||
822 | struct irb *irb; | ||
823 | |||
824 | switch (dev_event) { | ||
825 | case DEV_EVENT_INTERRUPT: | ||
826 | irb = (struct irb *) __LC_IRB; | ||
827 | /* Check for unsolicited interrupt. */ | ||
828 | if ((scsw_stctl(&irb->scsw) == | ||
829 | (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) && | ||
830 | (!scsw_cc(&irb->scsw))) | ||
831 | /* FIXME: we should restart stlck here, but this | ||
832 | * is extremely unlikely ... */ | ||
833 | goto out_wakeup; | ||
834 | |||
835 | ccw_device_accumulate_irb(cdev, irb); | ||
836 | /* We don't care about basic sense etc. */ | ||
837 | break; | ||
838 | default: /* timeout */ | ||
839 | break; | ||
840 | } | ||
841 | out_wakeup: | ||
842 | wake_up(&cdev->private->wait_q); | ||
843 | } | ||
844 | |||
845 | static void | ||
846 | ccw_device_start_id(struct ccw_device *cdev, enum dev_event dev_event) | 837 | ccw_device_start_id(struct ccw_device *cdev, enum dev_event dev_event) |
847 | { | 838 | { |
848 | struct subchannel *sch; | 839 | struct subchannel *sch; |
@@ -1010,9 +1001,9 @@ fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = { | |||
1010 | }, | 1001 | }, |
1011 | [DEV_STATE_BOXED] = { | 1002 | [DEV_STATE_BOXED] = { |
1012 | [DEV_EVENT_NOTOPER] = ccw_device_generic_notoper, | 1003 | [DEV_EVENT_NOTOPER] = ccw_device_generic_notoper, |
1013 | [DEV_EVENT_INTERRUPT] = ccw_device_stlck_done, | 1004 | [DEV_EVENT_INTERRUPT] = ccw_device_nop, |
1014 | [DEV_EVENT_TIMEOUT] = ccw_device_stlck_done, | 1005 | [DEV_EVENT_TIMEOUT] = ccw_device_nop, |
1015 | [DEV_EVENT_VERIFY] = ccw_device_nop, | 1006 | [DEV_EVENT_VERIFY] = ccw_device_boxed_verify, |
1016 | }, | 1007 | }, |
1017 | /* states to wait for i/o completion before doing something */ | 1008 | /* states to wait for i/o completion before doing something */ |
1018 | [DEV_STATE_TIMEOUT_KILL] = { | 1009 | [DEV_STATE_TIMEOUT_KILL] = { |
@@ -1052,6 +1043,12 @@ fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = { | |||
1052 | [DEV_EVENT_TIMEOUT] = ccw_device_update_cmfblock, | 1043 | [DEV_EVENT_TIMEOUT] = ccw_device_update_cmfblock, |
1053 | [DEV_EVENT_VERIFY] = ccw_device_update_cmfblock, | 1044 | [DEV_EVENT_VERIFY] = ccw_device_update_cmfblock, |
1054 | }, | 1045 | }, |
1046 | [DEV_STATE_STEAL_LOCK] = { | ||
1047 | [DEV_EVENT_NOTOPER] = ccw_device_request_event, | ||
1048 | [DEV_EVENT_INTERRUPT] = ccw_device_request_event, | ||
1049 | [DEV_EVENT_TIMEOUT] = ccw_device_request_event, | ||
1050 | [DEV_EVENT_VERIFY] = ccw_device_nop, | ||
1051 | }, | ||
1055 | }; | 1052 | }; |
1056 | 1053 | ||
1057 | EXPORT_SYMBOL_GPL(ccw_device_set_timeout); | 1054 | EXPORT_SYMBOL_GPL(ccw_device_set_timeout); |
diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c index d4be16acebe4..6da84543dfe9 100644 --- a/drivers/s390/cio/device_ops.c +++ b/drivers/s390/cio/device_ops.c | |||
@@ -11,6 +11,7 @@ | |||
11 | #include <linux/list.h> | 11 | #include <linux/list.h> |
12 | #include <linux/device.h> | 12 | #include <linux/device.h> |
13 | #include <linux/delay.h> | 13 | #include <linux/delay.h> |
14 | #include <linux/completion.h> | ||
14 | 15 | ||
15 | #include <asm/ccwdev.h> | 16 | #include <asm/ccwdev.h> |
16 | #include <asm/idals.h> | 17 | #include <asm/idals.h> |
@@ -504,74 +505,65 @@ __u8 ccw_device_get_path_mask(struct ccw_device *cdev) | |||
504 | return sch->lpm; | 505 | return sch->lpm; |
505 | } | 506 | } |
506 | 507 | ||
507 | /* | 508 | struct stlck_data { |
508 | * Try to break the lock on a boxed device. | 509 | struct completion done; |
509 | */ | 510 | int rc; |
510 | int | 511 | }; |
511 | ccw_device_stlck(struct ccw_device *cdev) | ||
512 | { | ||
513 | void *buf, *buf2; | ||
514 | unsigned long flags; | ||
515 | struct subchannel *sch; | ||
516 | int ret; | ||
517 | 512 | ||
518 | if (!cdev) | 513 | void ccw_device_stlck_done(struct ccw_device *cdev, void *data, int rc) |
519 | return -ENODEV; | 514 | { |
515 | struct stlck_data *sdata = data; | ||
520 | 516 | ||
521 | if (cdev->drv && !cdev->private->options.force) | 517 | sdata->rc = rc; |
522 | return -EINVAL; | 518 | complete(&sdata->done); |
519 | } | ||
523 | 520 | ||
524 | sch = to_subchannel(cdev->dev.parent); | 521 | /* |
525 | 522 | * Perform unconditional reserve + release. | |
526 | CIO_TRACE_EVENT(2, "stl lock"); | 523 | */ |
527 | CIO_TRACE_EVENT(2, dev_name(&cdev->dev)); | 524 | int ccw_device_stlck(struct ccw_device *cdev) |
525 | { | ||
526 | struct subchannel *sch = to_subchannel(cdev->dev.parent); | ||
527 | struct stlck_data data; | ||
528 | u8 *buffer; | ||
529 | int rc; | ||
528 | 530 | ||
529 | buf = kmalloc(32*sizeof(char), GFP_DMA|GFP_KERNEL); | 531 | /* Check if steal lock operation is valid for this device. */ |
530 | if (!buf) | 532 | if (cdev->drv) { |
531 | return -ENOMEM; | 533 | if (!cdev->private->options.force) |
532 | buf2 = kmalloc(32*sizeof(char), GFP_DMA|GFP_KERNEL); | 534 | return -EINVAL; |
533 | if (!buf2) { | ||
534 | kfree(buf); | ||
535 | return -ENOMEM; | ||
536 | } | 535 | } |
537 | spin_lock_irqsave(sch->lock, flags); | 536 | buffer = kzalloc(64, GFP_DMA | GFP_KERNEL); |
538 | ret = cio_enable_subchannel(sch, (u32)(addr_t)sch); | 537 | if (!buffer) |
539 | if (ret) | 538 | return -ENOMEM; |
540 | goto out_unlock; | 539 | init_completion(&data.done); |
541 | /* | 540 | data.rc = -EIO; |
542 | * Setup ccw. We chain an unconditional reserve and a release so we | 541 | spin_lock_irq(sch->lock); |
543 | * only break the lock. | 542 | rc = cio_enable_subchannel(sch, (u32) (addr_t) sch); |
544 | */ | 543 | if (rc) |
545 | cdev->private->iccws[0].cmd_code = CCW_CMD_STLCK; | ||
546 | cdev->private->iccws[0].cda = (__u32) __pa(buf); | ||
547 | cdev->private->iccws[0].count = 32; | ||
548 | cdev->private->iccws[0].flags = CCW_FLAG_CC; | ||
549 | cdev->private->iccws[1].cmd_code = CCW_CMD_RELEASE; | ||
550 | cdev->private->iccws[1].cda = (__u32) __pa(buf2); | ||
551 | cdev->private->iccws[1].count = 32; | ||
552 | cdev->private->iccws[1].flags = 0; | ||
553 | ret = cio_start(sch, cdev->private->iccws, 0); | ||
554 | if (ret) { | ||
555 | cio_disable_subchannel(sch); //FIXME: return code? | ||
556 | goto out_unlock; | 544 | goto out_unlock; |
545 | /* Perform operation. */ | ||
546 | cdev->private->state = DEV_STATE_STEAL_LOCK, | ||
547 | ccw_device_stlck_start(cdev, &data, &buffer[0], &buffer[32]); | ||
548 | spin_unlock_irq(sch->lock); | ||
549 | /* Wait for operation to finish. */ | ||
550 | if (wait_for_completion_interruptible(&data.done)) { | ||
551 | /* Got a signal. */ | ||
552 | spin_lock_irq(sch->lock); | ||
553 | ccw_request_cancel(cdev); | ||
554 | spin_unlock_irq(sch->lock); | ||
555 | wait_for_completion(&data.done); | ||
557 | } | 556 | } |
558 | cdev->private->irb.scsw.cmd.actl |= SCSW_ACTL_START_PEND; | 557 | rc = data.rc; |
559 | spin_unlock_irqrestore(sch->lock, flags); | 558 | /* Check results. */ |
560 | wait_event(cdev->private->wait_q, | 559 | spin_lock_irq(sch->lock); |
561 | cdev->private->irb.scsw.cmd.actl == 0); | 560 | cio_disable_subchannel(sch); |
562 | spin_lock_irqsave(sch->lock, flags); | 561 | cdev->private->state = DEV_STATE_BOXED; |
563 | cio_disable_subchannel(sch); //FIXME: return code? | ||
564 | if ((cdev->private->irb.scsw.cmd.dstat != | ||
565 | (DEV_STAT_CHN_END|DEV_STAT_DEV_END)) || | ||
566 | (cdev->private->irb.scsw.cmd.cstat != 0)) | ||
567 | ret = -EIO; | ||
568 | /* Clear irb. */ | ||
569 | memset(&cdev->private->irb, 0, sizeof(struct irb)); | ||
570 | out_unlock: | 562 | out_unlock: |
571 | kfree(buf); | 563 | spin_unlock_irq(sch->lock); |
572 | kfree(buf2); | 564 | kfree(buffer); |
573 | spin_unlock_irqrestore(sch->lock, flags); | 565 | |
574 | return ret; | 566 | return rc; |
575 | } | 567 | } |
576 | 568 | ||
577 | void *ccw_device_get_chp_desc(struct ccw_device *cdev, int chp_no) | 569 | void *ccw_device_get_chp_desc(struct ccw_device *cdev, int chp_no) |
diff --git a/drivers/s390/cio/device_pgid.c b/drivers/s390/cio/device_pgid.c index 4d54abd82b8c..5bcefeaff744 100644 --- a/drivers/s390/cio/device_pgid.c +++ b/drivers/s390/cio/device_pgid.c | |||
@@ -507,3 +507,55 @@ void ccw_device_disband_start(struct ccw_device *cdev) | |||
507 | spid_build_cp(cdev, fn); | 507 | spid_build_cp(cdev, fn); |
508 | ccw_request_start(cdev); | 508 | ccw_request_start(cdev); |
509 | } | 509 | } |
510 | |||
511 | static void stlck_build_cp(struct ccw_device *cdev, void *buf1, void *buf2) | ||
512 | { | ||
513 | struct ccw_request *req = &cdev->private->req; | ||
514 | struct ccw1 *cp = cdev->private->iccws; | ||
515 | |||
516 | cp[0].cmd_code = CCW_CMD_STLCK; | ||
517 | cp[0].cda = (u32) (addr_t) buf1; | ||
518 | cp[0].count = 32; | ||
519 | cp[0].flags = CCW_FLAG_CC; | ||
520 | cp[1].cmd_code = CCW_CMD_RELEASE; | ||
521 | cp[1].cda = (u32) (addr_t) buf2; | ||
522 | cp[1].count = 32; | ||
523 | cp[1].flags = 0; | ||
524 | req->cp = cp; | ||
525 | } | ||
526 | |||
527 | static void stlck_callback(struct ccw_device *cdev, void *data, int rc) | ||
528 | { | ||
529 | ccw_device_stlck_done(cdev, data, rc); | ||
530 | } | ||
531 | |||
532 | /** | ||
533 | * ccw_device_stlck_start - perform unconditional release | ||
534 | * @cdev: ccw device | ||
535 | * @data: data pointer to be passed to ccw_device_stlck_done | ||
536 | * @buf1: data pointer used in channel program | ||
537 | * @buf2: data pointer used in channel program | ||
538 | * | ||
539 | * Execute a channel program on @cdev to release an existing PGID reservation. | ||
540 | * When finished, call ccw_device_stlck_done with a return code specifying the | ||
541 | * result. | ||
542 | */ | ||
543 | void ccw_device_stlck_start(struct ccw_device *cdev, void *data, void *buf1, | ||
544 | void *buf2) | ||
545 | { | ||
546 | struct subchannel *sch = to_subchannel(cdev->dev.parent); | ||
547 | struct ccw_request *req = &cdev->private->req; | ||
548 | |||
549 | CIO_TRACE_EVENT(4, "stlck"); | ||
550 | CIO_HEX_EVENT(4, &cdev->private->dev_id, sizeof(cdev->private->dev_id)); | ||
551 | /* Request setup. */ | ||
552 | memset(req, 0, sizeof(*req)); | ||
553 | req->timeout = PGID_TIMEOUT; | ||
554 | req->maxretries = PGID_RETRIES; | ||
555 | req->lpm = sch->schib.pmcw.pam & sch->opm; | ||
556 | req->data = data; | ||
557 | req->callback = stlck_callback; | ||
558 | stlck_build_cp(cdev, buf1, buf2); | ||
559 | ccw_request_start(cdev); | ||
560 | } | ||
561 | |||