summaryrefslogtreecommitdiff
path: root/drivers/block/sunvdc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/block/sunvdc.c')
-rw-r--r--drivers/block/sunvdc.c26
1 files changed, 25 insertions, 1 deletions
diff --git a/drivers/block/sunvdc.c b/drivers/block/sunvdc.c
index 020bd9f1a7b6..2be8231dcd5b 100644
--- a/drivers/block/sunvdc.c
+++ b/drivers/block/sunvdc.c
@@ -525,6 +525,23 @@ static int __send_request(struct request *req)
err = __vdc_tx_trigger(port);
if (err < 0) {
printk(KERN_ERR PFX "vdc_tx_trigger() failure, err=%d\n", err);
+ /*
+ * If the port was reset (-ENOTCONN), the dring and the
+ * LDC channel including all of its mappings are already
+ * torn down and reallocated - there is nothing to undo
+ * and @desc must not be touched.
+ *
+ * For any other failure the descriptor was never handed
+ * to the peer: unmap the cookies and free the descriptor
+ * again, so that a later retry of the request does not
+ * leak LDC map table entries.
+ */
+ if (err != -ENOTCONN) {
+ ldc_unmap(port->vio.lp, desc->cookies,
+ desc->ncookies);
+ desc->hdr.state = VIO_DESC_FREE;
+ rqe->req = NULL;
+ }
} else {
port->req_id++;
dr->prod = vio_dring_next(dr, dr->prod);
@@ -539,6 +556,7 @@ static blk_status_t vdc_queue_rq(struct blk_mq_hw_ctx *hctx,
struct vdc_port *port = hctx->queue->queuedata;
struct vio_dring_state *dr;
unsigned long flags;
+ int ret;
dr = &port->vio.drings[VIO_DRIVER_TX_RING];
@@ -560,7 +578,13 @@ static blk_status_t vdc_queue_rq(struct blk_mq_hw_ctx *hctx,
return BLK_STS_DEV_RESOURCE;
}
- if (__send_request(bd->rq) < 0) {
+ ret = __send_request(bd->rq);
+ if (ret == -EAGAIN) {
+ spin_unlock_irqrestore(&port->vio.lock, flags);
+ /* already spun for 10msec, defer 10msec and retry */
+ blk_mq_delay_kick_requeue_list(hctx->queue, 10);
+ return BLK_STS_DEV_RESOURCE;
+ } else if (ret < 0) {
spin_unlock_irqrestore(&port->vio.lock, flags);
return BLK_STS_IOERR;
}