| /* |
| * drivers/s390/cio/device_pgid.c |
| * |
| * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, |
| * IBM Corporation |
| * Author(s): Cornelia Huck (cornelia.huck@de.ibm.com) |
| * Martin Schwidefsky (schwidefsky@de.ibm.com) |
| * |
| * Path Group ID functions. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| |
| #include <asm/ccwdev.h> |
| #include <asm/cio.h> |
| #include <asm/delay.h> |
| #include <asm/lowcore.h> |
| |
| #include "cio.h" |
| #include "cio_debug.h" |
| #include "css.h" |
| #include "device.h" |
| #include "ioasm.h" |
| |
| /* |
| * Helper function called from interrupt context to decide whether an |
| * operation should be tried again. |
| */ |
| static int __ccw_device_should_retry(struct scsw *scsw) |
| { |
| /* CC is only valid if start function bit is set. */ |
| if ((scsw->fctl & SCSW_FCTL_START_FUNC) && scsw->cc == 1) |
| return 1; |
| /* No more activity. For sense and set PGID we stubbornly try again. */ |
| if (!scsw->actl) |
| return 1; |
| return 0; |
| } |
| |
| /* |
| * Start Sense Path Group ID helper function. Used in ccw_device_recog |
| * and ccw_device_sense_pgid. |
| */ |
| static int |
| __ccw_device_sense_pgid_start(struct ccw_device *cdev) |
| { |
| struct subchannel *sch; |
| struct ccw1 *ccw; |
| int ret; |
| int i; |
| |
| sch = to_subchannel(cdev->dev.parent); |
| /* Return if we already checked on all paths. */ |
| if (cdev->private->imask == 0) |
| return (sch->lpm == 0) ? -ENODEV : -EACCES; |
| i = 8 - ffs(cdev->private->imask); |
| |
| /* Setup sense path group id channel program. */ |
| ccw = cdev->private->iccws; |
| ccw->cmd_code = CCW_CMD_SENSE_PGID; |
| ccw->count = sizeof (struct pgid); |
| ccw->flags = CCW_FLAG_SLI; |
| |
| /* Reset device status. */ |
| memset(&cdev->private->irb, 0, sizeof(struct irb)); |
| /* Try on every path. */ |
| ret = -ENODEV; |
| while (cdev->private->imask != 0) { |
| /* Try every path multiple times. */ |
| ccw->cda = (__u32) __pa (&cdev->private->pgid[i]); |
| if (cdev->private->iretry > 0) { |
| cdev->private->iretry--; |
| ret = cio_start (sch, cdev->private->iccws, |
| cdev->private->imask); |
| /* ret is 0, -EBUSY, -EACCES or -ENODEV */ |
| if (ret != -EACCES) |
| return ret; |
| CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel " |
| "0.%x.%04x, lpm %02X, became 'not " |
| "operational'\n", |
| cdev->private->dev_id.devno, |
| sch->schid.ssid, |
| sch->schid.sch_no, cdev->private->imask); |
| |
| } |
| cdev->private->imask >>= 1; |
| cdev->private->iretry = 5; |
| i++; |
| } |
| |
| return ret; |
| } |
| |
| void |
| ccw_device_sense_pgid_start(struct ccw_device *cdev) |
| { |
| int ret; |
| |
| /* Set a timeout of 60s */ |
| ccw_device_set_timeout(cdev, 60*HZ); |
| |
| cdev->private->state = DEV_STATE_SENSE_PGID; |
| cdev->private->imask = 0x80; |
| cdev->private->iretry = 5; |
| memset (&cdev->private->pgid, 0, sizeof (cdev->private->pgid)); |
| ret = __ccw_device_sense_pgid_start(cdev); |
| if (ret && ret != -EBUSY) |
| ccw_device_sense_pgid_done(cdev, ret); |
| } |
| |
| /* |
| * Called from interrupt context to check if a valid answer |
| * to Sense Path Group ID was received. |
| */ |
| static int |
| __ccw_device_check_sense_pgid(struct ccw_device *cdev) |
| { |
| struct subchannel *sch; |
| struct irb *irb; |
| int i; |
| |
| sch = to_subchannel(cdev->dev.parent); |
| irb = &cdev->private->irb; |
| if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) |
| return -ETIME; |
| if (irb->esw.esw0.erw.cons && |
| (irb->ecw[0]&(SNS0_CMD_REJECT|SNS0_INTERVENTION_REQ))) { |
| /* |
| * If the device doesn't support the Sense Path Group ID |
| * command further retries wouldn't help ... |
| */ |
| return -EOPNOTSUPP; |
| } |
| if (irb->esw.esw0.erw.cons) { |
| CIO_MSG_EVENT(2, "SNID - device 0.%x.%04x, unit check, " |
| "lpum %02X, cnt %02d, sns : " |
| "%02X%02X%02X%02X %02X%02X%02X%02X ...\n", |
| cdev->private->dev_id.ssid, |
| cdev->private->dev_id.devno, |
| irb->esw.esw0.sublog.lpum, |
| irb->esw.esw0.erw.scnt, |
| irb->ecw[0], irb->ecw[1], |
| irb->ecw[2], irb->ecw[3], |
| irb->ecw[4], irb->ecw[5], |
| irb->ecw[6], irb->ecw[7]); |
| return -EAGAIN; |
| } |
| if (irb->scsw.cc == 3) { |
| CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel 0.%x.%04x," |
| " lpm %02X, became 'not operational'\n", |
| cdev->private->dev_id.devno, sch->schid.ssid, |
| sch->schid.sch_no, sch->orb.lpm); |
| return -EACCES; |
| } |
| i = 8 - ffs(cdev->private->imask); |
| if (cdev->private->pgid[i].inf.ps.state2 == SNID_STATE2_RESVD_ELSE) { |
| CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel 0.%x.%04x " |
| "is reserved by someone else\n", |
| cdev->private->dev_id.devno, sch->schid.ssid, |
| sch->schid.sch_no); |
| return -EUSERS; |
| } |
| return 0; |
| } |
| |
| /* |
| * Got interrupt for Sense Path Group ID. |
| */ |
| void |
| ccw_device_sense_pgid_irq(struct ccw_device *cdev, enum dev_event dev_event) |
| { |
| struct subchannel *sch; |
| struct irb *irb; |
| int ret; |
| |
| irb = (struct irb *) __LC_IRB; |
| |
| if (irb->scsw.stctl == |
| (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { |
| if (__ccw_device_should_retry(&irb->scsw)) { |
| ret = __ccw_device_sense_pgid_start(cdev); |
| if (ret && ret != -EBUSY) |
| ccw_device_sense_pgid_done(cdev, ret); |
| } |
| return; |
| } |
| if (ccw_device_accumulate_and_sense(cdev, irb) != 0) |
| return; |
| sch = to_subchannel(cdev->dev.parent); |
| ret = __ccw_device_check_sense_pgid(cdev); |
| memset(&cdev->private->irb, 0, sizeof(struct irb)); |
| switch (ret) { |
| /* 0, -ETIME, -EOPNOTSUPP, -EAGAIN, -EACCES or -EUSERS */ |
| case -EOPNOTSUPP: /* Sense Path Group ID not supported */ |
| ccw_device_sense_pgid_done(cdev, -EOPNOTSUPP); |
| break; |
| case -ETIME: /* Sense path group id stopped by timeout. */ |
| ccw_device_sense_pgid_done(cdev, -ETIME); |
| break; |
| case -EACCES: /* channel is not operational. */ |
| sch->lpm &= ~cdev->private->imask; |
| /* Fall through. */ |
| case 0: /* Sense Path Group ID successful. */ |
| cdev->private->imask >>= 1; |
| cdev->private->iretry = 5; |
| /* Fall through. */ |
| case -EAGAIN: /* Try again. */ |
| ret = __ccw_device_sense_pgid_start(cdev); |
| if (ret != 0 && ret != -EBUSY) |
| ccw_device_sense_pgid_done(cdev, ret); |
| break; |
| case -EUSERS: /* device is reserved for someone else. */ |
| ccw_device_sense_pgid_done(cdev, -EUSERS); |
| break; |
| } |
| } |
| |
| /* |
| * Path Group ID helper function. |
| */ |
| static int |
| __ccw_device_do_pgid(struct ccw_device *cdev, __u8 func) |
| { |
| struct subchannel *sch; |
| struct ccw1 *ccw; |
| int ret; |
| |
| sch = to_subchannel(cdev->dev.parent); |
| |
| /* Setup sense path group id channel program. */ |
| cdev->private->pgid[0].inf.fc = func; |
| ccw = cdev->private->iccws; |
| if (!cdev->private->flags.pgid_single) { |
| cdev->private->pgid[0].inf.fc |= SPID_FUNC_MULTI_PATH; |
| ccw->cmd_code = CCW_CMD_SUSPEND_RECONN; |
| ccw->cda = 0; |
| ccw->count = 0; |
| ccw->flags = CCW_FLAG_SLI | CCW_FLAG_CC; |
| ccw++; |
| } else |
| cdev->private->pgid[0].inf.fc |= SPID_FUNC_SINGLE_PATH; |
| |
| ccw->cmd_code = CCW_CMD_SET_PGID; |
| ccw->cda = (__u32) __pa (&cdev->private->pgid[0]); |
| ccw->count = sizeof (struct pgid); |
| ccw->flags = CCW_FLAG_SLI; |
| |
| /* Reset device status. */ |
| memset(&cdev->private->irb, 0, sizeof(struct irb)); |
| |
| /* Try multiple times. */ |
| ret = -EACCES; |
| if (cdev->private->iretry > 0) { |
| cdev->private->iretry--; |
| ret = cio_start (sch, cdev->private->iccws, |
| cdev->private->imask); |
| /* We expect an interrupt in case of success or busy |
| * indication. */ |
| if ((ret == 0) || (ret == -EBUSY)) |
| return ret; |
| } |
| /* PGID command failed on this path. */ |
| CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel " |
| "0.%x.%04x, lpm %02X, became 'not operational'\n", |
| cdev->private->dev_id.devno, sch->schid.ssid, |
| sch->schid.sch_no, cdev->private->imask); |
| return ret; |
| } |
| |
| /* |
| * Helper function to send a nop ccw down a path. |
| */ |
| static int __ccw_device_do_nop(struct ccw_device *cdev) |
| { |
| struct subchannel *sch; |
| struct ccw1 *ccw; |
| int ret; |
| |
| sch = to_subchannel(cdev->dev.parent); |
| |
| /* Setup nop channel program. */ |
| ccw = cdev->private->iccws; |
| ccw->cmd_code = CCW_CMD_NOOP; |
| ccw->cda = 0; |
| ccw->count = 0; |
| ccw->flags = CCW_FLAG_SLI; |
| |
| /* Reset device status. */ |
| memset(&cdev->private->irb, 0, sizeof(struct irb)); |
| |
| /* Try multiple times. */ |
| ret = -EACCES; |
| if (cdev->private->iretry > 0) { |
| cdev->private->iretry--; |
| ret = cio_start (sch, cdev->private->iccws, |
| cdev->private->imask); |
| /* We expect an interrupt in case of success or busy |
| * indication. */ |
| if ((ret == 0) || (ret == -EBUSY)) |
| return ret; |
| } |
| /* nop command failed on this path. */ |
| CIO_MSG_EVENT(2, "NOP - Device %04x on Subchannel " |
| "0.%x.%04x, lpm %02X, became 'not operational'\n", |
| cdev->private->dev_id.devno, sch->schid.ssid, |
| sch->schid.sch_no, cdev->private->imask); |
| return ret; |
| } |
| |
| |
| /* |
| * Called from interrupt context to check if a valid answer |
| * to Set Path Group ID was received. |
| */ |
| static int |
| __ccw_device_check_pgid(struct ccw_device *cdev) |
| { |
| struct subchannel *sch; |
| struct irb *irb; |
| |
| sch = to_subchannel(cdev->dev.parent); |
| irb = &cdev->private->irb; |
| if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) |
| return -ETIME; |
| if (irb->esw.esw0.erw.cons) { |
| if (irb->ecw[0] & SNS0_CMD_REJECT) |
| return -EOPNOTSUPP; |
| /* Hmm, whatever happened, try again. */ |
| CIO_MSG_EVENT(2, "SPID - device 0.%x.%04x, unit check, " |
| "cnt %02d, " |
| "sns : %02X%02X%02X%02X %02X%02X%02X%02X ...\n", |
| cdev->private->dev_id.ssid, |
| cdev->private->dev_id.devno, |
| irb->esw.esw0.erw.scnt, |
| irb->ecw[0], irb->ecw[1], |
| irb->ecw[2], irb->ecw[3], |
| irb->ecw[4], irb->ecw[5], |
| irb->ecw[6], irb->ecw[7]); |
| return -EAGAIN; |
| } |
| if (irb->scsw.cc == 3) { |
| CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel 0.%x.%04x," |
| " lpm %02X, became 'not operational'\n", |
| cdev->private->dev_id.devno, sch->schid.ssid, |
| sch->schid.sch_no, cdev->private->imask); |
| return -EACCES; |
| } |
| return 0; |
| } |
| |
| /* |
| * Called from interrupt context to check the path status after a nop has |
| * been send. |
| */ |
| static int __ccw_device_check_nop(struct ccw_device *cdev) |
| { |
| struct subchannel *sch; |
| struct irb *irb; |
| |
| sch = to_subchannel(cdev->dev.parent); |
| irb = &cdev->private->irb; |
| if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) |
| return -ETIME; |
| if (irb->scsw.cc == 3) { |
| CIO_MSG_EVENT(2, "NOP - Device %04x on Subchannel 0.%x.%04x," |
| " lpm %02X, became 'not operational'\n", |
| cdev->private->dev_id.devno, sch->schid.ssid, |
| sch->schid.sch_no, cdev->private->imask); |
| return -EACCES; |
| } |
| return 0; |
| } |
| |
| static void |
| __ccw_device_verify_start(struct ccw_device *cdev) |
| { |
| struct subchannel *sch; |
| __u8 func; |
| int ret; |
| |
| sch = to_subchannel(cdev->dev.parent); |
| /* Repeat for all paths. */ |
| for (; cdev->private->imask; cdev->private->imask >>= 1, |
| cdev->private->iretry = 5) { |
| if ((cdev->private->imask & sch->schib.pmcw.pam) == 0) |
| /* Path not available, try next. */ |
| continue; |
| if (cdev->private->options.pgroup) { |
| if (sch->opm & cdev->private->imask) |
| func = SPID_FUNC_ESTABLISH; |
| else |
| func = SPID_FUNC_RESIGN; |
| ret = __ccw_device_do_pgid(cdev, func); |
| } else |
| ret = __ccw_device_do_nop(cdev); |
| /* We expect an interrupt in case of success or busy |
| * indication. */ |
| if (ret == 0 || ret == -EBUSY) |
| return; |
| /* Permanent path failure, try next. */ |
| } |
| /* Done with all paths. */ |
| ccw_device_verify_done(cdev, (sch->vpm != 0) ? 0 : -ENODEV); |
| } |
| |
| /* |
| * Got interrupt for Set Path Group ID. |
| */ |
| void |
| ccw_device_verify_irq(struct ccw_device *cdev, enum dev_event dev_event) |
| { |
| struct subchannel *sch; |
| struct irb *irb; |
| int ret; |
| |
| irb = (struct irb *) __LC_IRB; |
| |
| if (irb->scsw.stctl == |
| (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { |
| if (__ccw_device_should_retry(&irb->scsw)) |
| __ccw_device_verify_start(cdev); |
| return; |
| } |
| if (ccw_device_accumulate_and_sense(cdev, irb) != 0) |
| return; |
| sch = to_subchannel(cdev->dev.parent); |
| if (cdev->private->options.pgroup) |
| ret = __ccw_device_check_pgid(cdev); |
| else |
| ret = __ccw_device_check_nop(cdev); |
| memset(&cdev->private->irb, 0, sizeof(struct irb)); |
| |
| switch (ret) { |
| /* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */ |
| case 0: |
| /* Path verification ccw finished successfully, update lpm. */ |
| sch->vpm |= sch->opm & cdev->private->imask; |
| /* Go on with next path. */ |
| cdev->private->imask >>= 1; |
| cdev->private->iretry = 5; |
| __ccw_device_verify_start(cdev); |
| break; |
| case -EOPNOTSUPP: |
| /* |
| * One of those strange devices which claim to be able |
| * to do multipathing but not for Set Path Group ID. |
| */ |
| if (cdev->private->flags.pgid_single) |
| cdev->private->options.pgroup = 0; |
| else |
| cdev->private->flags.pgid_single = 1; |
| /* Retry */ |
| sch->vpm = 0; |
| cdev->private->imask = 0x80; |
| cdev->private->iretry = 5; |
| /* fall through. */ |
| case -EAGAIN: /* Try again. */ |
| __ccw_device_verify_start(cdev); |
| break; |
| case -ETIME: /* Set path group id stopped by timeout. */ |
| ccw_device_verify_done(cdev, -ETIME); |
| break; |
| case -EACCES: /* channel is not operational. */ |
| cdev->private->imask >>= 1; |
| cdev->private->iretry = 5; |
| __ccw_device_verify_start(cdev); |
| break; |
| } |
| } |
| |
| void |
| ccw_device_verify_start(struct ccw_device *cdev) |
| { |
| struct subchannel *sch = to_subchannel(cdev->dev.parent); |
| |
| cdev->private->flags.pgid_single = 0; |
| cdev->private->imask = 0x80; |
| cdev->private->iretry = 5; |
| |
| /* Start with empty vpm. */ |
| sch->vpm = 0; |
| |
| /* Get current pam. */ |
| if (stsch(sch->schid, &sch->schib)) { |
| ccw_device_verify_done(cdev, -ENODEV); |
| return; |
| } |
| /* After 60s path verification is considered to have failed. */ |
| ccw_device_set_timeout(cdev, 60*HZ); |
| __ccw_device_verify_start(cdev); |
| } |
| |
| static void |
| __ccw_device_disband_start(struct ccw_device *cdev) |
| { |
| struct subchannel *sch; |
| int ret; |
| |
| sch = to_subchannel(cdev->dev.parent); |
| while (cdev->private->imask != 0) { |
| if (sch->lpm & cdev->private->imask) { |
| ret = __ccw_device_do_pgid(cdev, SPID_FUNC_DISBAND); |
| if (ret == 0) |
| return; |
| } |
| cdev->private->iretry = 5; |
| cdev->private->imask >>= 1; |
| } |
| ccw_device_disband_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV); |
| } |
| |
| /* |
| * Got interrupt for Unset Path Group ID. |
| */ |
| void |
| ccw_device_disband_irq(struct ccw_device *cdev, enum dev_event dev_event) |
| { |
| struct subchannel *sch; |
| struct irb *irb; |
| int ret; |
| |
| irb = (struct irb *) __LC_IRB; |
| |
| if (irb->scsw.stctl == |
| (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { |
| if (__ccw_device_should_retry(&irb->scsw)) |
| __ccw_device_disband_start(cdev); |
| return; |
| } |
| if (ccw_device_accumulate_and_sense(cdev, irb) != 0) |
| return; |
| sch = to_subchannel(cdev->dev.parent); |
| ret = __ccw_device_check_pgid(cdev); |
| memset(&cdev->private->irb, 0, sizeof(struct irb)); |
| switch (ret) { |
| /* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */ |
| case 0: /* disband successful. */ |
| ccw_device_disband_done(cdev, ret); |
| break; |
| case -EOPNOTSUPP: |
| /* |
| * One of those strange devices which claim to be able |
| * to do multipathing but not for Unset Path Group ID. |
| */ |
| cdev->private->flags.pgid_single = 1; |
| /* fall through. */ |
| case -EAGAIN: /* Try again. */ |
| __ccw_device_disband_start(cdev); |
| break; |
| case -ETIME: /* Set path group id stopped by timeout. */ |
| ccw_device_disband_done(cdev, -ETIME); |
| break; |
| case -EACCES: /* channel is not operational. */ |
| cdev->private->imask >>= 1; |
| cdev->private->iretry = 5; |
| __ccw_device_disband_start(cdev); |
| break; |
| } |
| } |
| |
| void |
| ccw_device_disband_start(struct ccw_device *cdev) |
| { |
| /* After 60s disbanding is considered to have failed. */ |
| ccw_device_set_timeout(cdev, 60*HZ); |
| |
| cdev->private->flags.pgid_single = 0; |
| cdev->private->iretry = 5; |
| cdev->private->imask = 0x80; |
| __ccw_device_disband_start(cdev); |
| } |