X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fs390%2Fcio%2Fdevice_pgid.c;h=cb1879a96818b41b28371560b6f57173676ba14b;hb=97bf2856c6014879bd04983a3e9dfcdac1e7fe85;hp=85b1020a1fcce51e6371852fa4cf12c5d6cca772;hpb=43bc926fffd92024b46cafaf7350d669ba9ca884;p=linux-2.6.git diff --git a/drivers/s390/cio/device_pgid.c b/drivers/s390/cio/device_pgid.c index 85b1020a1..cb1879a96 100644 --- a/drivers/s390/cio/device_pgid.c +++ b/drivers/s390/cio/device_pgid.c @@ -9,7 +9,6 @@ * Path Group ID functions. */ -#include #include #include @@ -24,6 +23,21 @@ #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. @@ -34,12 +48,17 @@ __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->cda = (__u32) __pa (&cdev->private->pgid); ccw->count = sizeof (struct pgid); ccw->flags = CCW_FLAG_SLI; @@ -49,8 +68,11 @@ __ccw_device_sense_pgid_start(struct ccw_device *cdev) 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--; + /* Reset internal retry indication. */ + cdev->private->flags.intretry = 0; ret = cio_start (sch, cdev->private->iccws, cdev->private->imask); /* ret is 0, -EBUSY, -EACCES or -ENODEV */ @@ -59,13 +81,16 @@ __ccw_device_sense_pgid_start(struct ccw_device *cdev) CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel " "0.%x.%04x, lpm %02X, became 'not " "operational'\n", - cdev->private->devno, sch->schid.ssid, + 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; } @@ -74,10 +99,13 @@ 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 (struct pgid)); + 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); @@ -92,11 +120,18 @@ __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)) + if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) { + /* Retry Sense PGID if requested. */ + if (cdev->private->flags.intretry) { + cdev->private->flags.intretry = 0; + return -EAGAIN; + } return -ETIME; + } if (irb->esw.esw0.erw.cons && (irb->ecw[0]&(SNS0_CMD_REJECT|SNS0_INTERVENTION_REQ))) { /* @@ -109,7 +144,8 @@ __ccw_device_check_sense_pgid(struct ccw_device *cdev) 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->ssid, cdev->private->devno, + 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], @@ -121,14 +157,15 @@ __ccw_device_check_sense_pgid(struct ccw_device *cdev) 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->devno, sch->schid.ssid, + cdev->private->dev_id.devno, sch->schid.ssid, sch->schid.sch_no, sch->orb.lpm); return -EACCES; } - if (cdev->private->pgid.inf.ps.state2 == SNID_STATE2_RESVD_ELSE) { + 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->devno, sch->schid.ssid, + cdev->private->dev_id.devno, sch->schid.ssid, sch->schid.sch_no); return -EUSERS; } @@ -146,10 +183,10 @@ ccw_device_sense_pgid_irq(struct ccw_device *cdev, enum dev_event dev_event) int ret; irb = (struct irb *) __LC_IRB; - /* Retry sense pgid for cc=1. */ + if (irb->scsw.stctl == (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { - if (irb->scsw.cc == 1) { + 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); @@ -163,12 +200,6 @@ ccw_device_sense_pgid_irq(struct ccw_device *cdev, enum dev_event dev_event) memset(&cdev->private->irb, 0, sizeof(struct irb)); switch (ret) { /* 0, -ETIME, -EOPNOTSUPP, -EAGAIN, -EACCES or -EUSERS */ - case 0: /* Sense Path Group ID successful. */ - if (cdev->private->pgid.inf.ps.state1 == SNID_STATE1_RESET) - memcpy(&cdev->private->pgid, &css[0]->global_pgid, - sizeof(struct pgid)); - ccw_device_sense_pgid_done(cdev, 0); - break; case -EOPNOTSUPP: /* Sense Path Group ID not supported */ ccw_device_sense_pgid_done(cdev, -EOPNOTSUPP); break; @@ -177,13 +208,15 @@ ccw_device_sense_pgid_irq(struct ccw_device *cdev, enum dev_event dev_event) 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, -ENODEV); + ccw_device_sense_pgid_done(cdev, ret); break; case -EUSERS: /* device is reserved for someone else. */ ccw_device_sense_pgid_done(cdev, -EUSERS); @@ -204,20 +237,20 @@ __ccw_device_do_pgid(struct ccw_device *cdev, __u8 func) sch = to_subchannel(cdev->dev.parent); /* Setup sense path group id channel program. */ - cdev->private->pgid.inf.fc = func; + cdev->private->pgid[0].inf.fc = func; ccw = cdev->private->iccws; if (!cdev->private->flags.pgid_single) { - cdev->private->pgid.inf.fc |= SPID_FUNC_MULTI_PATH; + 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.inf.fc |= SPID_FUNC_SINGLE_PATH; + cdev->private->pgid[0].inf.fc |= SPID_FUNC_SINGLE_PATH; ccw->cmd_code = CCW_CMD_SET_PGID; - ccw->cda = (__u32) __pa (&cdev->private->pgid); + ccw->cda = (__u32) __pa (&cdev->private->pgid[0]); ccw->count = sizeof (struct pgid); ccw->flags = CCW_FLAG_SLI; @@ -225,25 +258,69 @@ __ccw_device_do_pgid(struct ccw_device *cdev, __u8 func) memset(&cdev->private->irb, 0, sizeof(struct irb)); /* Try multiple times. */ - ret = -ENODEV; + ret = -EACCES; if (cdev->private->iretry > 0) { cdev->private->iretry--; + /* Reset internal retry indication. */ + cdev->private->flags.intretry = 0; ret = cio_start (sch, cdev->private->iccws, cdev->private->imask); - /* ret is 0, -EBUSY, -EACCES or -ENODEV */ - if ((ret != -EACCES) && (ret != -ENODEV)) + /* We expect an interrupt in case of success or busy + * indication. */ + if ((ret == 0) || (ret == -EBUSY)) return ret; } - /* PGID command failed on this path. Switch it off. */ - sch->lpm &= ~cdev->private->imask; - sch->vpm &= ~cdev->private->imask; + /* 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->devno, sch->schid.ssid, + 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--; + /* Reset internal retry indication. */ + cdev->private->flags.intretry = 0; + 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. @@ -256,8 +333,14 @@ __ccw_device_check_pgid(struct ccw_device *cdev) sch = to_subchannel(cdev->dev.parent); irb = &cdev->private->irb; - if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) + if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) { + /* Retry Set PGID if requested. */ + if (cdev->private->flags.intretry) { + cdev->private->flags.intretry = 0; + return -EAGAIN; + } return -ETIME; + } if (irb->esw.esw0.erw.cons) { if (irb->ecw[0] & SNS0_CMD_REJECT) return -EOPNOTSUPP; @@ -265,8 +348,9 @@ __ccw_device_check_pgid(struct ccw_device *cdev) 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->ssid, - cdev->private->devno, irb->esw.esw0.erw.scnt, + 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], @@ -276,7 +360,36 @@ __ccw_device_check_pgid(struct ccw_device *cdev) 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->devno, sch->schid.ssid, + 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)) { + /* Retry NOP if requested. */ + if (cdev->private->flags.intretry) { + cdev->private->flags.intretry = 0; + return -EAGAIN; + } + 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; } @@ -287,24 +400,32 @@ static void __ccw_device_verify_start(struct ccw_device *cdev) { struct subchannel *sch; - __u8 imask, func; + __u8 func; int ret; sch = to_subchannel(cdev->dev.parent); - while (sch->vpm != sch->lpm) { - /* Find first unequal bit in vpm vs. lpm */ - for (imask = 0x80; imask != 0; imask >>= 1) - if ((sch->vpm & imask) != (sch->lpm & imask)) - break; - cdev->private->imask = imask; - func = (sch->vpm & imask) ? - SPID_FUNC_RESIGN : SPID_FUNC_ESTABLISH; - ret = __ccw_device_do_pgid(cdev, func); + /* 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; - cdev->private->iretry = 5; + /* Permanent path failure, try next. */ } - ccw_device_verify_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV); + /* Done with all paths. */ + ccw_device_verify_done(cdev, (sch->vpm != 0) ? 0 : -ENODEV); } /* @@ -318,26 +439,29 @@ ccw_device_verify_irq(struct ccw_device *cdev, enum dev_event dev_event) int ret; irb = (struct irb *) __LC_IRB; - /* Retry set pgid for cc=1. */ + if (irb->scsw.stctl == (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { - if (irb->scsw.cc == 1) + 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); - ret = __ccw_device_check_pgid(cdev); + 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: - /* Establish or Resign Path Group done. Update vpm. */ - if ((sch->lpm & cdev->private->imask) != 0) - sch->vpm |= cdev->private->imask; - else - sch->vpm &= ~cdev->private->imask; + /* 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; @@ -346,11 +470,14 @@ ccw_device_verify_irq(struct ccw_device *cdev, enum dev_event dev_event) * 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) { - ccw_device_verify_done(cdev, -EOPNOTSUPP); - break; - } - cdev->private->flags.pgid_single = 1; + 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); @@ -359,8 +486,7 @@ ccw_device_verify_irq(struct ccw_device *cdev, enum dev_event dev_event) ccw_device_verify_done(cdev, -ETIME); break; case -EACCES: /* channel is not operational. */ - sch->lpm &= ~cdev->private->imask; - sch->vpm &= ~cdev->private->imask; + cdev->private->imask >>= 1; cdev->private->iretry = 5; __ccw_device_verify_start(cdev); break; @@ -373,19 +499,19 @@ 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; - /* - * Update sch->lpm with current values to catch paths becoming - * available again. - */ + + /* Start with empty vpm. */ + sch->vpm = 0; + + /* Get current pam. */ if (stsch(sch->schid, &sch->schib)) { ccw_device_verify_done(cdev, -ENODEV); return; } - sch->lpm = sch->schib.pmcw.pim & - sch->schib.pmcw.pam & - sch->schib.pmcw.pom & - sch->opm; + /* After 60s path verification is considered to have failed. */ + ccw_device_set_timeout(cdev, 60*HZ); __ccw_device_verify_start(cdev); } @@ -419,10 +545,10 @@ ccw_device_disband_irq(struct ccw_device *cdev, enum dev_event dev_event) int ret; irb = (struct irb *) __LC_IRB; - /* Retry set pgid for cc=1. */ + if (irb->scsw.stctl == (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { - if (irb->scsw.cc == 1) + if (__ccw_device_should_retry(&irb->scsw)) __ccw_device_disband_start(cdev); return; } @@ -434,7 +560,6 @@ ccw_device_disband_irq(struct ccw_device *cdev, enum dev_event dev_event) switch (ret) { /* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */ case 0: /* disband successful. */ - sch->vpm = 0; ccw_device_disband_done(cdev, ret); break; case -EOPNOTSUPP: @@ -461,6 +586,9 @@ ccw_device_disband_irq(struct ccw_device *cdev, enum dev_event dev_event) 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;