| /* | |
| * Handling of internal CCW device requests. | |
| * | |
| * Copyright IBM Corp. 2009, 2011 | |
| * Author(s): Peter Oberparleiter <peter.oberparleiter@de.ibm.com> | |
| */ | |
| #define KMSG_COMPONENT "cio" | |
| #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | |
| #include <linux/types.h> | |
| #include <linux/err.h> | |
| #include <asm/ccwdev.h> | |
| #include <asm/cio.h> | |
| #include "io_sch.h" | |
| #include "cio.h" | |
| #include "device.h" | |
| #include "cio_debug.h" | |
| /** | |
| * lpm_adjust - adjust path mask | |
| * @lpm: path mask to adjust | |
| * @mask: mask of available paths | |
| * | |
| * Shift @lpm right until @lpm and @mask have at least one bit in common or | |
| * until @lpm is zero. Return the resulting lpm. | |
| */ | |
| int lpm_adjust(int lpm, int mask) | |
| { | |
| while (lpm && ((lpm & mask) == 0)) | |
| lpm >>= 1; | |
| return lpm; | |
| } | |
| /* | |
| * Adjust path mask to use next path and reset retry count. Return resulting | |
| * path mask. | |
| */ | |
| static u16 ccwreq_next_path(struct ccw_device *cdev) | |
| { | |
| struct ccw_request *req = &cdev->private->req; | |
| if (!req->singlepath) { | |
| req->mask = 0; | |
| goto out; | |
| } | |
| req->retries = req->maxretries; | |
| req->mask = lpm_adjust(req->mask >> 1, req->lpm); | |
| out: | |
| return req->mask; | |
| } | |
| /* | |
| * Clean up device state and report to callback. | |
| */ | |
| static void ccwreq_stop(struct ccw_device *cdev, int rc) | |
| { | |
| struct ccw_request *req = &cdev->private->req; | |
| if (req->done) | |
| return; | |
| req->done = 1; | |
| ccw_device_set_timeout(cdev, 0); | |
| memset(&cdev->private->irb, 0, sizeof(struct irb)); | |
| if (rc && rc != -ENODEV && req->drc) | |
| rc = req->drc; | |
| req->callback(cdev, req->data, rc); | |
| } | |
| /* | |
| * (Re-)Start the operation until retries and paths are exhausted. | |
| */ | |
| static void ccwreq_do(struct ccw_device *cdev) | |
| { | |
| struct ccw_request *req = &cdev->private->req; | |
| struct subchannel *sch = to_subchannel(cdev->dev.parent); | |
| struct ccw1 *cp = req->cp; | |
| int rc = -EACCES; | |
| while (req->mask) { | |
| if (req->retries-- == 0) { | |
| /* Retries exhausted, try next path. */ | |
| ccwreq_next_path(cdev); | |
| continue; | |
| } | |
| /* Perform start function. */ | |
| memset(&cdev->private->irb, 0, sizeof(struct irb)); | |
| rc = cio_start(sch, cp, (u8) req->mask); | |
| if (rc == 0) { | |
| /* I/O started successfully. */ | |
| ccw_device_set_timeout(cdev, req->timeout); | |
| return; | |
| } | |
| if (rc == -ENODEV) { | |
| /* Permanent device error. */ | |
| break; | |
| } | |
| if (rc == -EACCES) { | |
| /* Permant path error. */ | |
| ccwreq_next_path(cdev); | |
| continue; | |
| } | |
| /* Temporary improper status. */ | |
| rc = cio_clear(sch); | |
| if (rc) | |
| break; | |
| return; | |
| } | |
| ccwreq_stop(cdev, rc); | |
| } | |
| /** | |
| * ccw_request_start - perform I/O request | |
| * @cdev: ccw device | |
| * | |
| * Perform the I/O request specified by cdev->req. | |
| */ | |
| void ccw_request_start(struct ccw_device *cdev) | |
| { | |
| struct ccw_request *req = &cdev->private->req; | |
| if (req->singlepath) { | |
| /* Try all paths twice to counter link flapping. */ | |
| req->mask = 0x8080; | |
| } else | |
| req->mask = req->lpm; | |
| req->retries = req->maxretries; | |
| req->mask = lpm_adjust(req->mask, req->lpm); | |
| req->drc = 0; | |
| req->done = 0; | |
| req->cancel = 0; | |
| if (!req->mask) | |
| goto out_nopath; | |
| ccwreq_do(cdev); | |
| return; | |
| out_nopath: | |
| ccwreq_stop(cdev, -EACCES); | |
| } | |
| /** | |
| * ccw_request_cancel - cancel running I/O request | |
| * @cdev: ccw device | |
| * | |
| * Cancel the I/O request specified by cdev->req. Return non-zero if request | |
| * has already finished, zero otherwise. | |
| */ | |
| int ccw_request_cancel(struct ccw_device *cdev) | |
| { | |
| struct subchannel *sch = to_subchannel(cdev->dev.parent); | |
| struct ccw_request *req = &cdev->private->req; | |
| int rc; | |
| if (req->done) | |
| return 1; | |
| req->cancel = 1; | |
| rc = cio_clear(sch); | |
| if (rc) | |
| ccwreq_stop(cdev, rc); | |
| return 0; | |
| } | |
| /* | |
| * Return the status of the internal I/O started on the specified ccw device. | |
| * Perform BASIC SENSE if required. | |
| */ | |
| static enum io_status ccwreq_status(struct ccw_device *cdev, struct irb *lcirb) | |
| { | |
| struct irb *irb = &cdev->private->irb; | |
| struct cmd_scsw *scsw = &irb->scsw.cmd; | |
| enum uc_todo todo; | |
| /* Perform BASIC SENSE if needed. */ | |
| if (ccw_device_accumulate_and_sense(cdev, lcirb)) | |
| return IO_RUNNING; | |
| /* Check for halt/clear interrupt. */ | |
| if (scsw->fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) | |
| return IO_KILLED; | |
| /* Check for path error. */ | |
| if (scsw->cc == 3 || scsw->pno) | |
| return IO_PATH_ERROR; | |
| /* Handle BASIC SENSE data. */ | |
| if (irb->esw.esw0.erw.cons) { | |
| CIO_TRACE_EVENT(2, "sensedata"); | |
| CIO_HEX_EVENT(2, &cdev->private->dev_id, | |
| sizeof(struct ccw_dev_id)); | |
| CIO_HEX_EVENT(2, &cdev->private->irb.ecw, SENSE_MAX_COUNT); | |
| /* Check for command reject. */ | |
| if (irb->ecw[0] & SNS0_CMD_REJECT) | |
| return IO_REJECTED; | |
| /* Ask the driver what to do */ | |
| if (cdev->drv && cdev->drv->uc_handler) { | |
| todo = cdev->drv->uc_handler(cdev, lcirb); | |
| CIO_TRACE_EVENT(2, "uc_response"); | |
| CIO_HEX_EVENT(2, &todo, sizeof(todo)); | |
| switch (todo) { | |
| case UC_TODO_RETRY: | |
| return IO_STATUS_ERROR; | |
| case UC_TODO_RETRY_ON_NEW_PATH: | |
| return IO_PATH_ERROR; | |
| case UC_TODO_STOP: | |
| return IO_REJECTED; | |
| default: | |
| return IO_STATUS_ERROR; | |
| } | |
| } | |
| /* Assume that unexpected SENSE data implies an error. */ | |
| return IO_STATUS_ERROR; | |
| } | |
| /* Check for channel errors. */ | |
| if (scsw->cstat != 0) | |
| return IO_STATUS_ERROR; | |
| /* Check for device errors. */ | |
| if (scsw->dstat & ~(DEV_STAT_CHN_END | DEV_STAT_DEV_END)) | |
| return IO_STATUS_ERROR; | |
| /* Check for final state. */ | |
| if (!(scsw->dstat & DEV_STAT_DEV_END)) | |
| return IO_RUNNING; | |
| /* Check for other improper status. */ | |
| if (scsw->cc == 1 && (scsw->stctl & SCSW_STCTL_ALERT_STATUS)) | |
| return IO_STATUS_ERROR; | |
| return IO_DONE; | |
| } | |
| /* | |
| * Log ccw request status. | |
| */ | |
| static void ccwreq_log_status(struct ccw_device *cdev, enum io_status status) | |
| { | |
| struct ccw_request *req = &cdev->private->req; | |
| struct { | |
| struct ccw_dev_id dev_id; | |
| u16 retries; | |
| u8 lpm; | |
| u8 status; | |
| } __attribute__ ((packed)) data; | |
| data.dev_id = cdev->private->dev_id; | |
| data.retries = req->retries; | |
| data.lpm = (u8) req->mask; | |
| data.status = (u8) status; | |
| CIO_TRACE_EVENT(2, "reqstat"); | |
| CIO_HEX_EVENT(2, &data, sizeof(data)); | |
| } | |
| /** | |
| * ccw_request_handler - interrupt handler for I/O request procedure. | |
| * @cdev: ccw device | |
| * | |
| * Handle interrupt during I/O request procedure. | |
| */ | |
| void ccw_request_handler(struct ccw_device *cdev) | |
| { | |
| struct irb *irb = this_cpu_ptr(&cio_irb); | |
| struct ccw_request *req = &cdev->private->req; | |
| enum io_status status; | |
| int rc = -EOPNOTSUPP; | |
| /* Check status of I/O request. */ | |
| status = ccwreq_status(cdev, irb); | |
| if (req->filter) | |
| status = req->filter(cdev, req->data, irb, status); | |
| if (status != IO_RUNNING) | |
| ccw_device_set_timeout(cdev, 0); | |
| if (status != IO_DONE && status != IO_RUNNING) | |
| ccwreq_log_status(cdev, status); | |
| switch (status) { | |
| case IO_DONE: | |
| break; | |
| case IO_RUNNING: | |
| return; | |
| case IO_REJECTED: | |
| goto err; | |
| case IO_PATH_ERROR: | |
| goto out_next_path; | |
| case IO_STATUS_ERROR: | |
| goto out_restart; | |
| case IO_KILLED: | |
| /* Check if request was cancelled on purpose. */ | |
| if (req->cancel) { | |
| rc = -EIO; | |
| goto err; | |
| } | |
| goto out_restart; | |
| } | |
| /* Check back with request initiator. */ | |
| if (!req->check) | |
| goto out; | |
| switch (req->check(cdev, req->data)) { | |
| case 0: | |
| break; | |
| case -EAGAIN: | |
| goto out_restart; | |
| case -EACCES: | |
| goto out_next_path; | |
| default: | |
| goto err; | |
| } | |
| out: | |
| ccwreq_stop(cdev, 0); | |
| return; | |
| out_next_path: | |
| /* Try next path and restart I/O. */ | |
| if (!ccwreq_next_path(cdev)) { | |
| rc = -EACCES; | |
| goto err; | |
| } | |
| out_restart: | |
| /* Restart. */ | |
| ccwreq_do(cdev); | |
| return; | |
| err: | |
| ccwreq_stop(cdev, rc); | |
| } | |
| /** | |
| * ccw_request_timeout - timeout handler for I/O request procedure | |
| * @cdev: ccw device | |
| * | |
| * Handle timeout during I/O request procedure. | |
| */ | |
| void ccw_request_timeout(struct ccw_device *cdev) | |
| { | |
| struct subchannel *sch = to_subchannel(cdev->dev.parent); | |
| struct ccw_request *req = &cdev->private->req; | |
| int rc = -ENODEV, chp; | |
| if (cio_update_schib(sch)) | |
| goto err; | |
| for (chp = 0; chp < 8; chp++) { | |
| if ((0x80 >> chp) & sch->schib.pmcw.lpum) | |
| pr_warn("%s: No interrupt was received within %lus (CS=%02x, DS=%02x, CHPID=%x.%02x)\n", | |
| dev_name(&cdev->dev), req->timeout / HZ, | |
| scsw_cstat(&sch->schib.scsw), | |
| scsw_dstat(&sch->schib.scsw), | |
| sch->schid.cssid, | |
| sch->schib.pmcw.chpid[chp]); | |
| } | |
| if (!ccwreq_next_path(cdev)) { | |
| /* set the final return code for this request */ | |
| req->drc = -ETIME; | |
| } | |
| rc = cio_clear(sch); | |
| if (rc) | |
| goto err; | |
| return; | |
| err: | |
| ccwreq_stop(cdev, rc); | |
| } | |
| /** | |
| * ccw_request_notoper - notoper handler for I/O request procedure | |
| * @cdev: ccw device | |
| * | |
| * Handle notoper during I/O request procedure. | |
| */ | |
| void ccw_request_notoper(struct ccw_device *cdev) | |
| { | |
| ccwreq_stop(cdev, -ENODEV); | |
| } |