summaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
authorKevin Wolf <kwolf@redhat.com>2010-10-25 14:52:21 +0200
committerKevin Wolf <kwolf@redhat.com>2010-11-04 12:52:16 +0100
commit5dba48a882c126ccc86db6506cfa6dcca97badab (patch)
treedcdab53af51d9e0247ad5174e15c51a2b0d48f21 /hw
parent5fc9cfedfa09199e10b5f9b67dcd286bfeae4f7a (diff)
downloadqemu-5dba48a882c126ccc86db6506cfa6dcca97badab.tar.gz
scsi-disk: Implement rerror option
This implements the rerror option for SCSI disks. It also includes minor changes to the write path where the same code is used that was criticized in the review for the changes to the read path required for rerror support. Signed-off-by: Kevin Wolf <kwolf@redhat.com> Reviewed-by: Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
Diffstat (limited to 'hw')
-rw-r--r--hw/scsi-disk.c100
1 files changed, 66 insertions, 34 deletions
diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c
index 9628b39a21..43a5b59e26 100644
--- a/hw/scsi-disk.c
+++ b/hw/scsi-disk.c
@@ -41,7 +41,10 @@ do { fprintf(stderr, "scsi-disk: " fmt , ## __VA_ARGS__); } while (0)
#define SCSI_DMA_BUF_SIZE 131072
#define SCSI_MAX_INQUIRY_LEN 256
-#define SCSI_REQ_STATUS_RETRY 0x01
+#define SCSI_REQ_STATUS_RETRY 0x01
+#define SCSI_REQ_STATUS_RETRY_TYPE_MASK 0x06
+#define SCSI_REQ_STATUS_RETRY_READ 0x00
+#define SCSI_REQ_STATUS_RETRY_WRITE 0x02
typedef struct SCSIDiskState SCSIDiskState;
@@ -70,6 +73,8 @@ struct SCSIDiskState
char *serial;
};
+static int scsi_handle_rw_error(SCSIDiskReq *r, int error, int type);
+
static SCSIDiskReq *scsi_new_request(SCSIDiskState *s, uint32_t tag,
uint32_t lun)
{
@@ -127,34 +132,30 @@ static void scsi_cancel_io(SCSIDevice *d, uint32_t tag)
static void scsi_read_complete(void * opaque, int ret)
{
SCSIDiskReq *r = (SCSIDiskReq *)opaque;
+ int n;
r->req.aiocb = NULL;
if (ret) {
- DPRINTF("IO error\n");
- r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, 0);
- scsi_command_complete(r, CHECK_CONDITION, NO_SENSE);
- return;
+ if (scsi_handle_rw_error(r, -ret, SCSI_REQ_STATUS_RETRY_READ)) {
+ return;
+ }
}
+
DPRINTF("Data ready tag=0x%x len=%zd\n", r->req.tag, r->iov.iov_len);
+ n = r->iov.iov_len / 512;
+ r->sector += n;
+ r->sector_count -= n;
r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, r->iov.iov_len);
}
-/* Read more data from scsi device into buffer. */
-static void scsi_read_data(SCSIDevice *d, uint32_t tag)
+
+static void scsi_read_request(SCSIDiskReq *r)
{
- SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d);
- SCSIDiskReq *r;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
uint32_t n;
- r = scsi_find_request(s, tag);
- if (!r) {
- BADF("Bad read tag 0x%x\n", tag);
- /* ??? This is the wrong error. */
- scsi_command_complete(r, CHECK_CONDITION, HARDWARE_ERROR);
- return;
- }
if (r->sector_count == (uint32_t)-1) {
DPRINTF("Read buf_len=%zd\n", r->iov.iov_len);
r->sector_count = 0;
@@ -177,29 +178,54 @@ static void scsi_read_data(SCSIDevice *d, uint32_t tag)
scsi_read_complete, r);
if (r->req.aiocb == NULL)
scsi_command_complete(r, CHECK_CONDITION, HARDWARE_ERROR);
- r->sector += n;
- r->sector_count -= n;
}
-static int scsi_handle_write_error(SCSIDiskReq *r, int error)
+/* Read more data from scsi device into buffer. */
+static void scsi_read_data(SCSIDevice *d, uint32_t tag)
{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d);
+ SCSIDiskReq *r;
+
+ r = scsi_find_request(s, tag);
+ if (!r) {
+ BADF("Bad read tag 0x%x\n", tag);
+ /* ??? This is the wrong error. */
+ scsi_command_complete(r, CHECK_CONDITION, HARDWARE_ERROR);
+ return;
+ }
+
+ /* No data transfer may already be in progress */
+ assert(r->req.aiocb == NULL);
+
+ scsi_read_request(r);
+}
+
+static int scsi_handle_rw_error(SCSIDiskReq *r, int error, int type)
+{
+ int is_read = (type == SCSI_REQ_STATUS_RETRY_READ);
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
- BlockErrorAction action = bdrv_get_on_error(s->bs, 0);
+ BlockErrorAction action = bdrv_get_on_error(s->bs, is_read);
if (action == BLOCK_ERR_IGNORE) {
- bdrv_mon_event(s->bs, BDRV_ACTION_IGNORE, 0);
+ bdrv_mon_event(s->bs, BDRV_ACTION_IGNORE, is_read);
return 0;
}
if ((error == ENOSPC && action == BLOCK_ERR_STOP_ENOSPC)
|| action == BLOCK_ERR_STOP_ANY) {
- r->status |= SCSI_REQ_STATUS_RETRY;
- bdrv_mon_event(s->bs, BDRV_ACTION_STOP, 0);
+
+ type &= SCSI_REQ_STATUS_RETRY_TYPE_MASK;
+ r->status |= SCSI_REQ_STATUS_RETRY | type;
+
+ bdrv_mon_event(s->bs, BDRV_ACTION_STOP, is_read);
vm_stop(0);
} else {
+ if (type == SCSI_REQ_STATUS_RETRY_READ) {
+ r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, 0);
+ }
scsi_command_complete(r, CHECK_CONDITION,
HARDWARE_ERROR);
- bdrv_mon_event(s->bs, BDRV_ACTION_REPORT, 0);
+ bdrv_mon_event(s->bs, BDRV_ACTION_REPORT, is_read);
}
return 1;
@@ -214,8 +240,9 @@ static void scsi_write_complete(void * opaque, int ret)
r->req.aiocb = NULL;
if (ret) {
- if (scsi_handle_write_error(r, -ret))
+ if (scsi_handle_rw_error(r, -ret, SCSI_REQ_STATUS_RETRY_WRITE)) {
return;
+ }
}
n = r->iov.iov_len / 512;
@@ -268,8 +295,8 @@ static int scsi_write_data(SCSIDevice *d, uint32_t tag)
return 1;
}
- if (r->req.aiocb)
- BADF("Data transfer already in progress\n");
+ /* No data transfer may already be in progress */
+ assert(r->req.aiocb == NULL);
scsi_write_request(r);
@@ -288,8 +315,18 @@ static void scsi_dma_restart_bh(void *opaque)
QTAILQ_FOREACH(req, &s->qdev.requests, next) {
r = DO_UPCAST(SCSIDiskReq, req, req);
if (r->status & SCSI_REQ_STATUS_RETRY) {
- r->status &= ~SCSI_REQ_STATUS_RETRY;
- scsi_write_request(r);
+ int status = r->status;
+ r->status &=
+ ~(SCSI_REQ_STATUS_RETRY | SCSI_REQ_STATUS_RETRY_TYPE_MASK);
+
+ switch (status & SCSI_REQ_STATUS_RETRY_TYPE_MASK) {
+ case SCSI_REQ_STATUS_RETRY_READ:
+ scsi_read_request(r);
+ break;
+ case SCSI_REQ_STATUS_RETRY_WRITE:
+ scsi_write_request(r);
+ break;
+ }
}
}
}
@@ -1152,11 +1189,6 @@ static int scsi_disk_initfn(SCSIDevice *dev)
return -1;
}
- if (bdrv_get_on_error(s->bs, 1) != BLOCK_ERR_REPORT) {
- error_report("Device doesn't support drive option rerror");
- return -1;
- }
-
if (!s->serial) {
/* try to fall back to value set with legacy -drive serial=... */
dinfo = drive_get_by_blockdev(s->bs);