diff options
author | pbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162> | 2006-08-29 04:52:16 +0000 |
---|---|---|
committer | pbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162> | 2006-08-29 04:52:16 +0000 |
commit | a917d384ac0d09cd68266a6f2ca5c94212c8f81e (patch) | |
tree | f54b8fb2880361c0bf09087c3e382fc562193516 /hw/scsi-disk.c | |
parent | 508d92d0573319d7dbd827f6216aec44c1421832 (diff) |
SCSI TCQ support.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2139 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'hw/scsi-disk.c')
-rw-r--r-- | hw/scsi-disk.c | 494 |
1 files changed, 263 insertions, 231 deletions
diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 213e8d2930..c6280fd559 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -7,6 +7,10 @@ * Written by Paul Brook * * This code is licenced under the LGPL. + * + * Note that this file only handles the SCSI architecture model and device + * commands. Emultion of interface/link layer protocols is handled by + * the host adapter emulation. */ //#define DEBUG_SCSI @@ -28,231 +32,241 @@ do { fprintf(stderr, "scsi-disk: " fmt , ##args); } while (0) #define SENSE_HARDWARE_ERROR 4 #define SENSE_ILLEGAL_REQUEST 5 -struct SCSIDevice -{ - int command; +#define SCSI_DMA_BUF_SIZE 65536 + +typedef struct SCSIRequest { + SCSIDevice *dev; uint32_t tag; - BlockDriverState *bdrv; - /* The qemu block layer uses a fixed 512 byte sector size. - This is the number of 512 byte blocks in a single scsi sector. */ - int cluster_size; - /* When transfering data buf_pos and buf_len contain a partially - transferred block of data (or response to a command), and - sector/sector_count identify any remaining sectors. - Both sector and sector_count are in terms of qemu 512 byte blocks. */ /* ??? We should probably keep track of whether the data trasfer is a read or a write. Currently we rely on the host getting it right. */ + /* Both sector and sector_count are in terms of qemu 512 byte blocks. */ int sector; int sector_count; - int buf_pos; + /* The amounnt of data in the buffer. */ int buf_len; - int sense; + uint8_t dma_buf[SCSI_DMA_BUF_SIZE]; BlockDriverAIOCB *aiocb; - /* Data still to be transfered after this request completes. */ - uint8_t *aiodata; - uint32_t aiolen; - char buf[512]; + struct SCSIRequest *next; +} SCSIRequest; + +struct SCSIDevice +{ + BlockDriverState *bdrv; + SCSIRequest *requests; + /* The qemu block layer uses a fixed 512 byte sector size. + This is the number of 512 byte blocks in a single scsi sector. */ + int cluster_size; + int sense; + int tcq; /* Completion functions may be called from either scsi_{read,write}_data or from the AIO completion routines. */ scsi_completionfn completion; void *opaque; }; -static void scsi_command_complete(SCSIDevice *s, int sense) +/* Global pool of SCSIRequest structures. */ +static SCSIRequest *free_requests = NULL; + +static SCSIRequest *scsi_new_request(SCSIDevice *s, uint32_t tag) { - s->sense = sense; - s->completion(s->opaque, SCSI_REASON_DONE, sense); + SCSIRequest *r; + + if (free_requests) { + r = free_requests; + free_requests = r->next; + } else { + r = qemu_malloc(sizeof(SCSIRequest)); + } + r->dev = s; + r->tag = tag; + r->sector_count = 0; + r->buf_len = 0; + r->aiocb = NULL; + + r->next = s->requests; + s->requests = r; + return r; } -static void scsi_transfer_complete(SCSIDevice *s) +static void scsi_remove_request(SCSIRequest *r) { - s->completion(s->opaque, SCSI_REASON_DATA, 0); - s->aiocb = NULL; + SCSIRequest *last; + SCSIDevice *s = r->dev; + + if (s->requests == r) { + s->requests = r->next; + } else { + last = s->requests; + while (last && last->next != r) + last = last->next; + if (last) { + last->next = r->next; + } else { + BADF("Orphaned request\n"); + } + } + r->next = free_requests; + free_requests = r; } -static void scsi_read_complete(void * opaque, int ret) +static SCSIRequest *scsi_find_request(SCSIDevice *s, uint32_t tag) { - SCSIDevice *s = (SCSIDevice *)opaque; + SCSIRequest *r; - if (ret) { - DPRINTF("IO error\n"); - scsi_command_complete(s, SENSE_HARDWARE_ERROR); - } + r = s->requests; + while (r && r->tag != tag) + r = r->next; - if (s->aiolen) { - /* Read the remaining data. Full and partial sectors are transferred - separately. */ - scsi_read_data(s, s->aiodata, s->aiolen); - } else { - if (s->buf_len == 0 && s->sector_count == 0) - scsi_command_complete(s, SENSE_NO_SENSE); - else - scsi_transfer_complete(s); - } + return r; +} + +/* Helper function for command completion. */ +static void scsi_command_complete(SCSIRequest *r, int sense) +{ + SCSIDevice *s = r->dev; + uint32_t tag; + DPRINTF("Command complete tag=0x%x sense=%d\n", r->tag, sense); + s->sense = sense; + tag = r->tag; + scsi_remove_request(r); + s->completion(s->opaque, SCSI_REASON_DONE, tag, sense); } /* Cancel a pending data transfer. */ -void scsi_cancel_io(SCSIDevice *s) +void scsi_cancel_io(SCSIDevice *s, uint32_t tag) { - if (!s->aiocb) { - BADF("Cancel with no pending IO\n"); + SCSIRequest *r; + DPRINTF("Cancel tag=0x%x\n", tag); + r = scsi_find_request(s, tag); + if (r) { + if (r->aiocb) + bdrv_aio_cancel(r->aiocb); + r->aiocb = NULL; + scsi_remove_request(r); + } +} + +static void scsi_read_complete(void * opaque, int ret) +{ + SCSIRequest *r = (SCSIRequest *)opaque; + SCSIDevice *s = r->dev; + + if (ret) { + DPRINTF("IO error\n"); + scsi_command_complete(r, SENSE_HARDWARE_ERROR); return; } - bdrv_aio_cancel(s->aiocb); - s->aiocb = NULL; + DPRINTF("Data ready tag=0x%x len=%d\n", r->tag, r->buf_len); + + s->completion(s->opaque, SCSI_REASON_DATA, r->tag, r->buf_len); } -/* Read data from a scsi device. Returns nonzero on failure. - The transfer may complete asynchronously. */ -int scsi_read_data(SCSIDevice *s, uint8_t *data, uint32_t len) +/* Read more data from scsi device into buffer. */ +void scsi_read_data(SCSIDevice *s, uint32_t tag) { + SCSIRequest *r; uint32_t n; - DPRINTF("Read %d (%d/%d)\n", len, s->buf_len, s->sector_count); - if (s->buf_len == 0 && s->sector_count == 0) - return 1; - - if (s->buf_len) { - n = s->buf_len; - if (n > len) - n = len; - memcpy(data, s->buf + s->buf_pos, n); - s->buf_pos += n; - s->buf_len -= n; - data += n; - len -= n; - if (s->buf_len == 0) - s->buf_pos = 0; + 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, SENSE_HARDWARE_ERROR); + return; } - - n = len / 512; - if (n > s->sector_count) - n = s->sector_count; - - if (n != 0) { - s->aiolen = len - n * 512; - s->aiodata = data + n * 512; - s->aiocb = bdrv_aio_read(s->bdrv, s->sector, data, n, - scsi_read_complete, s); - if (s->aiocb == NULL) - scsi_command_complete(s, SENSE_HARDWARE_ERROR); - s->sector += n; - s->sector_count -= n; - return 0; + if (r->sector_count == (uint32_t)-1) { + DPRINTF("Read buf_len=%d\n", r->buf_len); + r->sector_count = 0; + s->completion(s->opaque, SCSI_REASON_DATA, r->tag, r->buf_len); + return; } - - if (len && s->sector_count) { - /* TODO: Make this use AIO. */ - bdrv_read(s->bdrv, s->sector, s->buf, 1); - s->sector++; - s->sector_count--; - s->buf_pos = 0; - s->buf_len = 512; - /* Recurse to complete the partial read. */ - return scsi_read_data(s, data, len); + DPRINTF("Read sector_count=%d\n", r->sector_count); + if (r->sector_count == 0) { + scsi_command_complete(r, SENSE_NO_SENSE); + return; } - if (len != 0) - return 1; - - if (s->buf_len == 0 && s->sector_count == 0) - scsi_command_complete(s, SENSE_NO_SENSE); - else - scsi_transfer_complete(s); - - return 0; + n = r->sector_count; + if (n > SCSI_DMA_BUF_SIZE / 512) + n = SCSI_DMA_BUF_SIZE / 512; + + r->buf_len = n * 512; + r->aiocb = bdrv_aio_read(s->bdrv, r->sector, r->dma_buf, n, + scsi_read_complete, r); + if (r->aiocb == NULL) + scsi_command_complete(r, SENSE_HARDWARE_ERROR); + r->sector += n; + r->sector_count -= n; } static void scsi_write_complete(void * opaque, int ret) { - SCSIDevice *s = (SCSIDevice *)opaque; + SCSIRequest *r = (SCSIRequest *)opaque; + SCSIDevice *s = r->dev; + uint32_t len; if (ret) { fprintf(stderr, "scsi-disc: IO write error\n"); exit(1); } - if (s->sector_count == 0) - scsi_command_complete(s, SENSE_NO_SENSE); - else - scsi_transfer_complete(s); -} - -static uint32_t scsi_write_partial_sector(SCSIDevice *s, uint8_t *data, - uint32_t len) -{ - int n; - - n = 512 - s->buf_len; - if (n > len) - n = len; - - memcpy(s->buf + s->buf_len, data, n); - data += n; - s->buf_len += n; - len -= n; - if (s->buf_len == 512) { - /* A full sector has been accumulated. Write it to disk. */ - /* TODO: Make this use async IO. */ - bdrv_write(s->bdrv, s->sector, s->buf, 1); - s->buf_len = 0; - s->sector++; - s->sector_count--; + r->aiocb = NULL; + if (r->sector_count == 0) { + scsi_command_complete(r, SENSE_NO_SENSE); + } else { + len = r->sector_count * 512; + if (len > SCSI_DMA_BUF_SIZE) { + len = SCSI_DMA_BUF_SIZE; + } + r->buf_len = len; + DPRINTF("Write complete tag=0x%x more=%d\n", r->tag, len); + s->completion(s->opaque, SCSI_REASON_DATA, r->tag, len); } - return n; } /* Write data to a scsi device. Returns nonzero on failure. The transfer may complete asynchronously. */ -int scsi_write_data(SCSIDevice *s, uint8_t *data, uint32_t len) +int scsi_write_data(SCSIDevice *s, uint32_t tag) { + SCSIRequest *r; uint32_t n; - DPRINTF("Write %d (%d/%d)\n", len, s->buf_len, s->sector_count); - if (s->buf_pos != 0) { - BADF("Bad state on write\n"); + DPRINTF("Write data tag=0x%x\n", tag); + r = scsi_find_request(s, tag); + if (!r) { + BADF("Bad write tag 0x%x\n", tag); + scsi_command_complete(r, SENSE_HARDWARE_ERROR); return 1; } - - if (s->sector_count == 0) - return 1; - - if (s->buf_len != 0 || len < 512) { - n = scsi_write_partial_sector(s, data, len); - len -= n; - data += n; + if (r->aiocb) + BADF("Data transfer already in progress\n"); + n = r->buf_len / 512; + if (n) { + r->aiocb = bdrv_aio_write(s->bdrv, r->sector, r->dma_buf, n, + scsi_write_complete, r); + if (r->aiocb == NULL) + scsi_command_complete(r, SENSE_HARDWARE_ERROR); + r->sector += n; + r->sector_count -= n; + } else { + /* Invoke completion routine to fetch data from host. */ + scsi_write_complete(r, 0); } - n = len / 512; - if (n > s->sector_count) - return 1; + return 0; +} - if (n != 0) { - s->aiocb = bdrv_aio_write(s->bdrv, s->sector, data, n, - scsi_write_complete, s); - if (s->aiocb == NULL) - scsi_command_complete(s, SENSE_HARDWARE_ERROR); - data += n * 512; - len -= n * 512; - s->sector += n; - s->sector_count -= n; - } +/* Return a pointer to the data buffer. */ +uint8_t *scsi_get_buf(SCSIDevice *s, uint32_t tag) +{ + SCSIRequest *r; - if (len) { - if (s->sector_count == 0) - return 1; - /* Complete a partial write. */ - scsi_write_partial_sector(s, data, len); + r = scsi_find_request(s, tag); + if (!r) { + BADF("Bad buffer tag 0x%x\n", tag); + return NULL; } - if (n == 0) { - /* Transfer completes immediately. */ - if (s->sector_count == 0) - scsi_command_complete(s, SENSE_NO_SENSE); - else - scsi_transfer_complete(s); - } - - return 0; + return r->dma_buf; } /* Execute a scsi command. Returns the length of the data expected by the @@ -267,15 +281,23 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) uint32_t len; int cmdlen; int is_write; - - s->command = buf[0]; - s->tag = tag; - s->sector_count = 0; - s->buf_pos = 0; - s->buf_len = 0; + uint8_t command; + uint8_t *outbuf; + SCSIRequest *r; + + command = buf[0]; + r = scsi_find_request(s, tag); + if (r) { + BADF("Tag 0x%x already in use\n", tag); + scsi_cancel_io(s, tag); + } + /* ??? Tags are not unique for different luns. We only implement a + single lun, so this should not matter. */ + r = scsi_new_request(s, tag); + outbuf = r->dma_buf; is_write = 0; - DPRINTF("Command: 0x%02x", buf[0]); - switch (s->command >> 5) { + DPRINTF("Command: lun=%d tag=0x%x data=0x%02x", lun, tag, buf[0]); + switch (command >> 5) { case 0: lba = buf[3] | (buf[2] << 8) | ((buf[1] & 0x1f) << 16); len = buf[4]; @@ -298,7 +320,7 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) cmdlen = 12; break; default: - BADF("Unsupported command length, command %x\n", s->command); + BADF("Unsupported command length, command %x\n", command); goto fail; } #ifdef DEBUG_SCSI @@ -315,7 +337,7 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) DPRINTF("Unimplemented LUN %d\n", lun ? lun : buf[1] >> 5); goto fail; } - switch (s->command) { + switch (command) { case 0x0: DPRINTF("Test Unit Ready\n"); break; @@ -324,33 +346,35 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) if (len < 4) goto fail; memset(buf, 0, 4); - s->buf[0] = 0xf0; - s->buf[1] = 0; - s->buf[2] = s->sense; - s->buf_len = 4; + outbuf[0] = 0xf0; + outbuf[1] = 0; + outbuf[2] = s->sense; + r->buf_len = 4; break; case 0x12: DPRINTF("Inquiry (len %d)\n", len); if (len < 36) { BADF("Inquiry buffer too small (%d)\n", len); } - memset(s->buf, 0, 36); + memset(outbuf, 0, 36); if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { - s->buf[0] = 5; - s->buf[1] = 0x80; - memcpy(&s->buf[16], "QEMU CD-ROM ", 16); + outbuf[0] = 5; + outbuf[1] = 0x80; + memcpy(&outbuf[16], "QEMU CD-ROM ", 16); } else { - s->buf[0] = 0; - memcpy(&s->buf[16], "QEMU HARDDISK ", 16); + outbuf[0] = 0; + memcpy(&outbuf[16], "QEMU HARDDISK ", 16); } - memcpy(&s->buf[8], "QEMU ", 8); - memcpy(&s->buf[32], QEMU_VERSION, 4); + memcpy(&outbuf[8], "QEMU ", 8); + memcpy(&outbuf[32], QEMU_VERSION, 4); /* Identify device as SCSI-3 rev 1. Some later commands are also implemented. */ - s->buf[2] = 3; - s->buf[3] = 2; /* Format 2 */ - s->buf[4] = 32; - s->buf_len = 36; + outbuf[2] = 3; + outbuf[3] = 2; /* Format 2 */ + outbuf[4] = 32; + /* Sync data transfer and TCQ. */ + outbuf[7] = 0x10 | (s->tcq ? 0x02 : 0); + r->buf_len = 36; break; case 0x16: DPRINTF("Reserve(6)\n"); @@ -365,17 +389,17 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) case 0x1a: case 0x5a: { - char *p; + uint8_t *p; int page; page = buf[2] & 0x3f; DPRINTF("Mode Sense (page %d, len %d)\n", page, len); - p = s->buf; + p = outbuf; memset(p, 0, 4); - s->buf[1] = 0; /* Default media type. */ - s->buf[3] = 0; /* Block descriptor length. */ + outbuf[1] = 0; /* Default media type. */ + outbuf[3] = 0; /* Block descriptor length. */ if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { - s->buf[2] = 0x80; /* Readonly. */ + outbuf[2] = 0x80; /* Readonly. */ } p += 4; if ((page == 8 || page == 0x3f)) { @@ -415,10 +439,10 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) p[21] = (16 * 176) & 0xff; p += 21; } - s->buf_len = p - s->buf; - s->buf[0] = s->buf_len - 4; - if (s->buf_len > len) - s->buf_len = len; + r->buf_len = p - outbuf; + outbuf[0] = r->buf_len - 4; + if (r->buf_len > len) + r->buf_len = len; } break; case 0x1b: @@ -431,36 +455,36 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) case 0x25: DPRINTF("Read Capacity\n"); /* The normal LEN field for this command is zero. */ - memset(s->buf, 0, 8); + memset(outbuf, 0, 8); bdrv_get_geometry(s->bdrv, &nb_sectors); /* Returned value is the address of the last sector. */ if (nb_sectors) { nb_sectors--; - s->buf[0] = (nb_sectors >> 24) & 0xff; - s->buf[1] = (nb_sectors >> 16) & 0xff; - s->buf[2] = (nb_sectors >> 8) & 0xff; - s->buf[3] = nb_sectors & 0xff; - s->buf[4] = 0; - s->buf[5] = 0; - s->buf[6] = s->cluster_size * 2; - s->buf[7] = 0; - s->buf_len = 8; + outbuf[0] = (nb_sectors >> 24) & 0xff; + outbuf[1] = (nb_sectors >> 16) & 0xff; + outbuf[2] = (nb_sectors >> 8) & 0xff; + outbuf[3] = nb_sectors & 0xff; + outbuf[4] = 0; + outbuf[5] = 0; + outbuf[6] = s->cluster_size * 2; + outbuf[7] = 0; + r->buf_len = 8; } else { - scsi_command_complete(s, SENSE_NOT_READY); + scsi_command_complete(r, SENSE_NOT_READY); return 0; } break; case 0x08: case 0x28: DPRINTF("Read (sector %d, count %d)\n", lba, len); - s->sector = lba * s->cluster_size; - s->sector_count = len * s->cluster_size; + r->sector = lba * s->cluster_size; + r->sector_count = len * s->cluster_size; break; case 0x0a: case 0x2a: DPRINTF("Write (sector %d, count %d)\n", lba, len); - s->sector = lba * s->cluster_size; - s->sector_count = len * s->cluster_size; + r->sector = lba * s->cluster_size; + r->sector_count = len * s->cluster_size; is_write = 1; break; case 0x35: @@ -478,18 +502,18 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) DPRINTF("Read TOC (track %d format %d msf %d)\n", start_track, format, msf >> 1); switch(format) { case 0: - toclen = cdrom_read_toc(nb_sectors, s->buf, msf, start_track); + toclen = cdrom_read_toc(nb_sectors, outbuf, msf, start_track); break; case 1: /* multi session : only a single session defined */ toclen = 12; - memset(s->buf, 0, 12); - s->buf[1] = 0x0a; - s->buf[2] = 0x01; - s->buf[3] = 0x01; + memset(outbuf, 0, 12); + outbuf[1] = 0x0a; + outbuf[2] = 0x01; + outbuf[3] = 0x01; break; case 2: - toclen = cdrom_read_toc_raw(nb_sectors, s->buf, msf, start_track); + toclen = cdrom_read_toc_raw(nb_sectors, outbuf, msf, start_track); break; default: goto error_cmd; @@ -497,7 +521,7 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) if (toclen > 0) { if (len > toclen) len = toclen; - s->buf_len = len; + r->buf_len = len; break; } error_cmd: @@ -506,11 +530,11 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) } case 0x46: DPRINTF("Get Configuration (rt %d, maxlen %d)\n", buf[1] & 3, len); - memset(s->buf, 0, 8); + memset(outbuf, 0, 8); /* ??? This shoud probably return much more information. For now just return the basic header indicating the CD-ROM profile. */ - s->buf[7] = 8; // CD-ROM - s->buf_len = 8; + outbuf[7] = 8; // CD-ROM + r->buf_len = 8; break; case 0x56: DPRINTF("Reserve(10)\n"); @@ -526,21 +550,27 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) DPRINTF("Report LUNs (len %d)\n", len); if (len < 16) goto fail; - memset(s->buf, 0, 16); - s->buf[3] = 8; - s->buf_len = 16; + memset(outbuf, 0, 16); + outbuf[3] = 8; + r->buf_len = 16; break; default: DPRINTF("Unknown SCSI command (%2.2x)\n", buf[0]); fail: - scsi_command_complete(s, SENSE_ILLEGAL_REQUEST); + scsi_command_complete(r, SENSE_ILLEGAL_REQUEST); return 0; } - if (s->sector_count == 0 && s->buf_len == 0) { - scsi_command_complete(s, SENSE_NO_SENSE); + if (r->sector_count == 0 && r->buf_len == 0) { + scsi_command_complete(r, SENSE_NO_SENSE); + } + len = r->sector_count * 512 + r->buf_len; + if (is_write) { + return -len; + } else { + if (!r->sector_count) + r->sector_count = -1; + return len; } - len = s->sector_count * 512 + s->buf_len; - return is_write ? -len : len; } void scsi_disk_destroy(SCSIDevice *s) @@ -549,6 +579,7 @@ void scsi_disk_destroy(SCSIDevice *s) } SCSIDevice *scsi_disk_init(BlockDriverState *bdrv, + int tcq, scsi_completionfn completion, void *opaque) { @@ -556,6 +587,7 @@ SCSIDevice *scsi_disk_init(BlockDriverState *bdrv, s = (SCSIDevice *)qemu_mallocz(sizeof(SCSIDevice)); s->bdrv = bdrv; + s->tcq = tcq; s->completion = completion; s->opaque = opaque; if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { |