With some emulated SCSI devices, like usb-storage or ide-scsi, DMA transfers are limited to 64 kiB or 32 kiB. This patch allows to split a READ or WRITE into several READ or WRITE.
Laurent --- hw/scsi-generic.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 4 deletions(-) Index: qemu/hw/scsi-generic.c =================================================================== --- qemu.orig/hw/scsi-generic.c 2007-12-20 10:55:24.000000000 +0100 +++ qemu/hw/scsi-generic.c 2007-12-20 16:23:24.000000000 +0100 @@ -61,6 +61,8 @@ do { fprintf(stderr, "scsi-generic: " fm #define MAX_UINT ((unsigned int)-1) #endif +#define MAX_CHUNK 65536 + typedef struct SCSIRequest { BlockDriverAIOCB *aiocb; struct SCSIRequest *next; @@ -72,6 +74,8 @@ typedef struct SCSIRequest { int buflen; int len; sg_io_hdr_t io_header; + int remaining; + int offset; } SCSIRequest; struct SCSIDeviceState @@ -84,6 +88,7 @@ struct SCSIDeviceState void *opaque; int driver_status; uint8_t sensebuf[SCSI_SENSE_BUF_SIZE]; + int max_chunk; }; /* Global pool of SCSIRequest structures. */ @@ -101,6 +106,7 @@ static SCSIRequest *scsi_new_request(SCS r->buf = NULL; r->buflen = 0; } + r->offset = 0; r->dev = s; r->tag = tag; memset(r->cmd, 0, sizeof(r->cmd)); @@ -191,24 +197,90 @@ static void scsi_cancel_io(SCSIDevice *d } } +static void scsi_cmd_next(uint8_t *cmd, uint32_t inc) +{ + uint32_t addr; + switch (cmd[0] >> 5) { + case 0: + addr = cmd[3] | (cmd[2] << 8); + addr += inc; + cmd[2] = addr >> 8; + cmd[3] = addr; + break; + case 1: + case 2: + case 4: + case 5: + addr = cmd[5] | ((cmd[4] << 8) | ((cmd[3] << 16) | (cmd[2] << 24))); + addr += inc; + cmd[2] = addr >> 24; + cmd[3] = addr >> 16; + cmd[4] = addr >> 8; + cmd[5] = addr; + break; + } +} +static void scsi_set_length(uint8_t *cmd, uint32_t len) +{ + switch (cmd[0] >> 5) { + case 0: + cmd[4] = len; + break; + case 1: + case 2: + cmd[7] = (len >> 8); + cmd[8] = len; + break; + case 4: + cmd[10] = len >> 24; + cmd[11] = len >> 16; + cmd[12] = len >> 8; + cmd[13] = len; + break; + case 5: + cmd[6] = len >> 24; + cmd[7] = len >> 16; + cmd[8] = len >> 8; + cmd[9] = len; + break; + } +} + static int execute_command(BlockDriverState *bdrv, SCSIRequest *r, int direction, BlockDriverCompletionFunc *complete) { + SCSIDeviceState *s = r->dev; + r->remaining = 0; +retry: + if (s->max_chunk && r->buflen > s->max_chunk) { + r->remaining = r->buflen - s->max_chunk; + scsi_set_length(r->cmd, s->max_chunk / s->blocksize); + r->buflen = s->max_chunk; + } r->io_header.interface_id = 'S'; r->io_header.dxfer_direction = direction; - r->io_header.dxferp = r->buf; + r->io_header.dxferp = r->buf + r->offset; r->io_header.dxfer_len = r->buflen; r->io_header.cmdp = r->cmd; r->io_header.cmd_len = r->cmdlen; - r->io_header.mx_sb_len = sizeof(r->dev->sensebuf); - r->io_header.sbp = r->dev->sensebuf; + r->io_header.mx_sb_len = sizeof(s->sensebuf); + r->io_header.sbp = s->sensebuf; r->io_header.timeout = MAX_UINT; r->io_header.usr_ptr = r; r->io_header.flags |= SG_FLAG_DIRECT_IO; if (bdrv_pwrite(bdrv, -1, &r->io_header, sizeof(r->io_header)) == -1) { + if (errno == 12) { + if (!s->max_chunk) { + s->max_chunk = MAX_CHUNK; + goto retry; + } else if (s->max_chunk > s->blocksize) { + s->max_chunk >>= 1; + goto retry; + } + } BADF("execute_command: write failed ! (%d)\n", errno); return -1; } @@ -246,7 +318,18 @@ static void scsi_read_complete(void * op scsi_command_complete(r, ret); return; } - len = r->io_header.dxfer_len - r->io_header.resid; + r->offset += r->io_header.dxfer_len - r->io_header.resid; + if (r->remaining != 0) { + scsi_cmd_next(r->cmd, r->buflen / s->blocksize); + scsi_set_length(r->cmd, r->remaining / s->blocksize); + r->buflen = r->remaining; + ret = execute_command(s->bdrv, r, SG_DXFER_FROM_DEV, + scsi_read_complete); + if (ret == -1) + scsi_command_complete(r, -EINVAL); + return; + } + len = r->offset; DPRINTF("Data ready tag=0x%x len=%d\n", r->tag, len); r->len = -1; @@ -293,6 +376,7 @@ static void scsi_read_data(SCSIDevice *d static void scsi_write_complete(void * opaque, int ret) { SCSIRequest *r = (SCSIRequest *)opaque; + SCSIDeviceState *s = r->dev; DPRINTF("scsi_write_complete() ret = %d\n", ret); if (ret) { @@ -300,6 +384,17 @@ static void scsi_write_complete(void * o scsi_command_complete(r, ret); return; } + r->offset += r->io_header.dxfer_len - r->io_header.resid; + if (r->remaining != 0) { + scsi_cmd_next(r->cmd, r->buflen / s->blocksize); + scsi_set_length(r->cmd, r->remaining / s->blocksize); + r->buflen = r->remaining; + ret = execute_command(s->bdrv, r, SG_DXFER_TO_DEV, + scsi_write_complete); + if (ret == -1) + scsi_command_complete(r, -EINVAL); + return; + } scsi_command_complete(r, ret); } @@ -641,6 +736,7 @@ SCSIDevice *scsi_generic_init(BlockDrive s->lun = scsiid.lun; s->blocksize = get_blocksize(s->bdrv); s->driver_status = 0; + s->max_chunk = 0; memset(s->sensebuf, 0, sizeof(s->sensebuf)); /* removable media returns 0 if not present */ if (s->blocksize <= 0)