X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/35f1df84b6a9d4462764b833e915100f91e4f257..d9f75a4eb449c96dd47731a4d6f1619f3c23b5e7:/hw/scsi-disk.c diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 55a26239d8..8f1afab51a 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -7,8 +7,14 @@ * Written by Paul Brook * * This code is licenced under the LGPL. + * + * Note that this file only handles the SCSI architecture model and device + * commands. Emulation of interface/link layer protocols is handled by + * the host adapter emulator. */ +#include +#include //#define DEBUG_SCSI #ifdef DEBUG_SCSI @@ -21,238 +27,315 @@ do { printf("scsi-disk: " fmt , ##args); } while (0) #define BADF(fmt, args...) \ do { fprintf(stderr, "scsi-disk: " fmt , ##args); } while (0) -#include "vl.h" +#include "qemu-common.h" +#include "block.h" +#include "scsi-disk.h" #define SENSE_NO_SENSE 0 #define SENSE_NOT_READY 2 #define SENSE_HARDWARE_ERROR 4 #define SENSE_ILLEGAL_REQUEST 5 -struct SCSIDevice -{ - int command; +#define STATUS_GOOD 0 +#define STATUS_CHECK_CONDITION 2 + +#define SCSI_DMA_BUF_SIZE 131072 +#define SCSI_MAX_INQUIRY_LEN 256 + +#define SCSI_REQ_STATUS_RETRY 0x01 + +typedef struct SCSIRequest { + SCSIDeviceState *dev; uint32_t tag; + /* ??? We should probably keep track of whether the data transfer 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. */ + uint64_t sector; + uint32_t sector_count; + struct iovec iov; + QEMUIOVector qiov; + BlockDriverAIOCB *aiocb; + struct SCSIRequest *next; + uint32_t status; +} SCSIRequest; + +struct SCSIDeviceState +{ 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; - /* 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. */ - int sector; - int sector_count; - int buf_pos; - int buf_len; + uint64_t max_lba; int sense; - BlockDriverAIOCB *aiocb; - /* Data still to be transfered after this request completes. */ - uint8_t *aiodata; - uint32_t aiolen; - char buf[512]; + int tcq; /* Completion functions may be called from either scsi_{read,write}_data or from the AIO completion routines. */ scsi_completionfn completion; void *opaque; + char drive_serial_str[21]; }; -static void scsi_command_complete(SCSIDevice *s, int sense) +/* Global pool of SCSIRequest structures. */ +static SCSIRequest *free_requests = NULL; + +static SCSIRequest *scsi_new_request(SCSIDeviceState *s, uint32_t tag) +{ + SCSIRequest *r; + + if (free_requests) { + r = free_requests; + free_requests = r->next; + } else { + r = qemu_malloc(sizeof(SCSIRequest)); + r->iov.iov_base = qemu_memalign(512, SCSI_DMA_BUF_SIZE); + } + r->dev = s; + r->tag = tag; + r->sector_count = 0; + r->iov.iov_len = 0; + r->aiocb = NULL; + r->status = 0; + + r->next = s->requests; + s->requests = r; + return r; +} + +static void scsi_remove_request(SCSIRequest *r) +{ + SCSIRequest *last; + SCSIDeviceState *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 SCSIRequest *scsi_find_request(SCSIDeviceState *s, uint32_t tag) +{ + SCSIRequest *r; + + r = s->requests; + while (r && r->tag != tag) + r = r->next; + + return r; +} + +/* Helper function for command completion. */ +static void scsi_command_complete(SCSIRequest *r, int status, int sense) { + SCSIDeviceState *s = r->dev; + uint32_t tag; + DPRINTF("Command complete tag=0x%x status=%d sense=%d\n", r->tag, status, sense); s->sense = sense; - s->completion(s->opaque, SCSI_REASON_DONE, sense); + tag = r->tag; + scsi_remove_request(r); + s->completion(s->opaque, SCSI_REASON_DONE, tag, status); } -static void scsi_transfer_complete(SCSIDevice *s) +/* Cancel a pending data transfer. */ +static void scsi_cancel_io(SCSIDevice *d, uint32_t tag) { - s->completion(s->opaque, SCSI_REASON_DATA, 0); - s->aiocb = NULL; + SCSIDeviceState *s = d->state; + 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) { - SCSIDevice *s = (SCSIDevice *)opaque; + SCSIRequest *r = (SCSIRequest *)opaque; + SCSIDeviceState *s = r->dev; if (ret) { DPRINTF("IO error\n"); - scsi_command_complete(s, SENSE_HARDWARE_ERROR); + s->completion(s->opaque, SCSI_REASON_DATA, r->tag, 0); + scsi_command_complete(r, STATUS_CHECK_CONDITION, SENSE_NO_SENSE); + return; } + DPRINTF("Data ready tag=0x%x len=%d\n", r->tag, r->iov.iov_len); - 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); - } + s->completion(s->opaque, SCSI_REASON_DATA, r->tag, r->iov.iov_len); } -/* Cancel a pending data transfer. */ -void scsi_cancel_io(SCSIDevice *s) +/* Read more data from scsi device into buffer. */ +static void scsi_read_data(SCSIDevice *d, uint32_t tag) { - if (!s->aiocb) { - BADF("Cancel with no pending IO\n"); + SCSIDeviceState *s = d->state; + SCSIRequest *r; + 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, STATUS_CHECK_CONDITION, SENSE_HARDWARE_ERROR); + return; + } + if (r->sector_count == (uint32_t)-1) { + DPRINTF("Read buf_len=%d\n", r->iov.iov_len); + r->sector_count = 0; + s->completion(s->opaque, SCSI_REASON_DATA, r->tag, r->iov.iov_len); return; } - bdrv_aio_cancel(s->aiocb); - s->aiocb = NULL; + DPRINTF("Read sector_count=%d\n", r->sector_count); + if (r->sector_count == 0) { + scsi_command_complete(r, STATUS_GOOD, SENSE_NO_SENSE); + return; + } + + n = r->sector_count; + if (n > SCSI_DMA_BUF_SIZE / 512) + n = SCSI_DMA_BUF_SIZE / 512; + + r->iov.iov_len = n * 512; + qemu_iovec_init_external(&r->qiov, &r->iov, 1); + r->aiocb = bdrv_aio_readv(s->bdrv, r->sector, &r->qiov, n, + scsi_read_complete, r); + if (r->aiocb == NULL) + scsi_command_complete(r, STATUS_CHECK_CONDITION, SENSE_HARDWARE_ERROR); + r->sector += n; + r->sector_count -= n; } -/* 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) +static int scsi_handle_write_error(SCSIRequest *r, int error) { - 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; + BlockInterfaceErrorAction action = drive_get_onerror(r->dev->bdrv); - 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; - } - - 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; + if (action == BLOCK_ERR_IGNORE) return 0; - } - 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); + if ((error == ENOSPC && action == BLOCK_ERR_STOP_ENOSPC) + || action == BLOCK_ERR_STOP_ANY) { + r->status |= SCSI_REQ_STATUS_RETRY; + vm_stop(0); + } else { + scsi_command_complete(r, STATUS_CHECK_CONDITION, + SENSE_HARDWARE_ERROR); } - 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; + return 1; } static void scsi_write_complete(void * opaque, int ret) { - SCSIDevice *s = (SCSIDevice *)opaque; + SCSIRequest *r = (SCSIRequest *)opaque; + SCSIDeviceState *s = r->dev; + uint32_t len; + uint32_t n; + + r->aiocb = NULL; if (ret) { - fprintf(stderr, "scsi-disc: IO write error\n"); - exit(1); + if (scsi_handle_write_error(r, -ret)) + return; } - if (s->sector_count == 0) - scsi_command_complete(s, SENSE_NO_SENSE); - else - scsi_transfer_complete(s); + n = r->iov.iov_len / 512; + r->sector += n; + r->sector_count -= n; + if (r->sector_count == 0) { + scsi_command_complete(r, STATUS_GOOD, SENSE_NO_SENSE); + } else { + len = r->sector_count * 512; + if (len > SCSI_DMA_BUF_SIZE) { + len = SCSI_DMA_BUF_SIZE; + } + r->iov.iov_len = len; + DPRINTF("Write complete tag=0x%x more=%d\n", r->tag, len); + s->completion(s->opaque, SCSI_REASON_DATA, r->tag, len); + } } -static uint32_t scsi_write_partial_sector(SCSIDevice *s, uint8_t *data, - uint32_t len) +static void scsi_write_request(SCSIRequest *r) { - 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--; + SCSIDeviceState *s = r->dev; + uint32_t n; + + n = r->iov.iov_len / 512; + if (n) { + qemu_iovec_init_external(&r->qiov, &r->iov, 1); + r->aiocb = bdrv_aio_writev(s->bdrv, r->sector, &r->qiov, n, + scsi_write_complete, r); + if (r->aiocb == NULL) + scsi_command_complete(r, STATUS_CHECK_CONDITION, + SENSE_HARDWARE_ERROR); + } else { + /* Invoke completion routine to fetch data from host. */ + scsi_write_complete(r, 0); } - 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) +static int scsi_write_data(SCSIDevice *d, uint32_t tag) { - 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"); + SCSIDeviceState *s = d->state; + SCSIRequest *r; + + 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, STATUS_CHECK_CONDITION, SENSE_HARDWARE_ERROR); return 1; } - if (s->sector_count == 0) - return 1; + if (r->aiocb) + BADF("Data transfer already in progress\n"); - if (s->buf_len != 0 || len < 512) { - n = scsi_write_partial_sector(s, data, len); - len -= n; - data += n; - } + scsi_write_request(r); - 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; - } +static void scsi_dma_restart_cb(void *opaque, int running, int reason) +{ + SCSIDeviceState *s = opaque; + SCSIRequest *r = s->requests; + if (!running) + return; - if (len) { - if (s->sector_count == 0) - return 1; - /* Complete a partial write. */ - scsi_write_partial_sector(s, data, len); - } - if (n == 0) { - /* Transfer completes immediately. */ - if (s->sector_count == 0) - scsi_command_complete(s, SENSE_NO_SENSE); - else - scsi_transfer_complete(s); + while (r) { + if (r->status & SCSI_REQ_STATUS_RETRY) { + r->status &= ~SCSI_REQ_STATUS_RETRY; + scsi_write_request(r); + } + r = r->next; } +} - return 0; +/* Return a pointer to the data buffer. */ +static uint8_t *scsi_get_buf(SCSIDevice *d, uint32_t tag) +{ + SCSIDeviceState *s = d->state; + SCSIRequest *r; + + r = scsi_find_request(s, tag); + if (!r) { + BADF("Bad buffer tag 0x%x\n", tag); + return NULL; + } + return (uint8_t *)r->iov.iov_base; } /* Execute a scsi command. Returns the length of the data expected by the @@ -260,45 +343,61 @@ int scsi_write_data(SCSIDevice *s, uint8_t *data, uint32_t len) (eg. disk reads), negative for transfers to the device (eg. disk writes), and zero if the command does not transfer any data. */ -int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) +static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, + uint8_t *buf, int lun) { - int64_t nb_sectors; - uint32_t lba; + SCSIDeviceState *s = d->state; + uint64_t nb_sectors; + uint64_t lba; 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(d, 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 = (uint8_t *)r->iov.iov_base; 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); + lba = (uint64_t) buf[3] | ((uint64_t) buf[2] << 8) | + (((uint64_t) buf[1] & 0x1f) << 16); len = buf[4]; cmdlen = 6; break; case 1: case 2: - lba = buf[5] | (buf[4] << 8) | (buf[3] << 16) | (buf[2] << 24); + lba = (uint64_t) buf[5] | ((uint64_t) buf[4] << 8) | + ((uint64_t) buf[3] << 16) | ((uint64_t) buf[2] << 24); len = buf[8] | (buf[7] << 8); cmdlen = 10; break; case 4: - lba = buf[5] | (buf[4] << 8) | (buf[3] << 16) | (buf[2] << 24); + lba = (uint64_t) buf[9] | ((uint64_t) buf[8] << 8) | + ((uint64_t) buf[7] << 16) | ((uint64_t) buf[6] << 24) | + ((uint64_t) buf[5] << 32) | ((uint64_t) buf[4] << 40) | + ((uint64_t) buf[3] << 48) | ((uint64_t) buf[2] << 56); len = buf[13] | (buf[12] << 8) | (buf[11] << 16) | (buf[10] << 24); cmdlen = 16; break; case 5: - lba = buf[5] | (buf[4] << 8) | (buf[3] << 16) | (buf[2] << 24); + lba = (uint64_t) buf[5] | ((uint64_t) buf[4] << 8) | + ((uint64_t) buf[3] << 16) | ((uint64_t) buf[2] << 24); len = buf[9] | (buf[8] << 8) | (buf[7] << 16) | (buf[6] << 24); 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 @@ -313,44 +412,188 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) if (lun || buf[1] >> 5) { /* Only LUN 0 supported. */ DPRINTF("Unimplemented LUN %d\n", lun ? lun : buf[1] >> 5); - goto fail; + if (command != 0x03 && command != 0x12) /* REQUEST SENSE and INQUIRY */ + goto fail; } - switch (s->command) { + switch (command) { case 0x0: DPRINTF("Test Unit Ready\n"); + if (!bdrv_is_inserted(s->bdrv)) + goto notready; break; case 0x03: DPRINTF("Request Sense (len %d)\n", len); 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; + memset(outbuf, 0, 4); + r->iov.iov_len = 4; + if (s->sense == SENSE_NOT_READY && len >= 18) { + memset(outbuf, 0, 18); + r->iov.iov_len = 18; + outbuf[7] = 10; + /* asc 0x3a, ascq 0: Medium not present */ + outbuf[12] = 0x3a; + outbuf[13] = 0; + } + outbuf[0] = 0xf0; + outbuf[1] = 0; + outbuf[2] = s->sense; break; case 0x12: DPRINTF("Inquiry (len %d)\n", len); - if (len < 36) { - BADF("Inquiry buffer too small (%d)\n", len); + if (buf[1] & 0x2) { + /* Command support data - optional, not implemented */ + BADF("optional INQUIRY command support request not implemented\n"); + goto fail; + } + else if (buf[1] & 0x1) { + /* Vital product data */ + uint8_t page_code = buf[2]; + if (len < 4) { + BADF("Error: Inquiry (EVPD[%02X]) buffer size %d is " + "less than 4\n", page_code, len); + goto fail; + } + + switch (page_code) { + case 0x00: + { + /* Supported page codes, mandatory */ + DPRINTF("Inquiry EVPD[Supported pages] " + "buffer size %d\n", len); + + r->iov.iov_len = 0; + + if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { + outbuf[r->iov.iov_len++] = 5; + } else { + outbuf[r->iov.iov_len++] = 0; + } + + outbuf[r->iov.iov_len++] = 0x00; // this page + outbuf[r->iov.iov_len++] = 0x00; + outbuf[r->iov.iov_len++] = 3; // number of pages + outbuf[r->iov.iov_len++] = 0x00; // list of supported pages (this page) + outbuf[r->iov.iov_len++] = 0x80; // unit serial number + outbuf[r->iov.iov_len++] = 0x83; // device identification + } + break; + case 0x80: + { + int l; + + /* Device serial number, optional */ + if (len < 4) { + BADF("Error: EVPD[Serial number] Inquiry buffer " + "size %d too small, %d needed\n", len, 4); + goto fail; + } + + DPRINTF("Inquiry EVPD[Serial number] buffer size %d\n", len); + l = MIN(len, strlen(s->drive_serial_str)); + + r->iov.iov_len = 0; + + /* Supported page codes */ + if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { + outbuf[r->iov.iov_len++] = 5; + } else { + outbuf[r->iov.iov_len++] = 0; + } + + outbuf[r->iov.iov_len++] = 0x80; // this page + outbuf[r->iov.iov_len++] = 0x00; + outbuf[r->iov.iov_len++] = l; + memcpy(&outbuf[r->iov.iov_len], s->drive_serial_str, l); + r->iov.iov_len += l; + } + + break; + case 0x83: + { + /* Device identification page, mandatory */ + int max_len = 255 - 8; + int id_len = strlen(bdrv_get_device_name(s->bdrv)); + if (id_len > max_len) + id_len = max_len; + + DPRINTF("Inquiry EVPD[Device identification] " + "buffer size %d\n", len); + r->iov.iov_len = 0; + if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { + outbuf[r->iov.iov_len++] = 5; + } else { + outbuf[r->iov.iov_len++] = 0; + } + + outbuf[r->iov.iov_len++] = 0x83; // this page + outbuf[r->iov.iov_len++] = 0x00; + outbuf[r->iov.iov_len++] = 3 + id_len; + + outbuf[r->iov.iov_len++] = 0x2; // ASCII + outbuf[r->iov.iov_len++] = 0; // not officially assigned + outbuf[r->iov.iov_len++] = 0; // reserved + outbuf[r->iov.iov_len++] = id_len; // length of data following + + memcpy(&outbuf[r->iov.iov_len], + bdrv_get_device_name(s->bdrv), id_len); + r->iov.iov_len += id_len; + } + break; + default: + BADF("Error: unsupported Inquiry (EVPD[%02X]) " + "buffer size %d\n", page_code, len); + goto fail; + } + /* done with EVPD */ + break; + } + else { + /* Standard INQUIRY data */ + if (buf[2] != 0) { + BADF("Error: Inquiry (STANDARD) page or code " + "is non-zero [%02X]\n", buf[2]); + goto fail; + } + + /* PAGE CODE == 0 */ + if (len < 5) { + BADF("Error: Inquiry (STANDARD) buffer size %d " + "is less than 5\n", len); + goto fail; + } + + if (len < 36) { + BADF("Error: Inquiry (STANDARD) buffer size %d " + "is less than 36 (TODO: only 5 required)\n", len); + } } - memset(s->buf, 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); + + if(len > SCSI_MAX_INQUIRY_LEN) + len = SCSI_MAX_INQUIRY_LEN; + + memset(outbuf, 0, len); + + if (lun || buf[1] >> 5) { + outbuf[0] = 0x7f; /* LUN not supported */ + } else if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { + 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] = len - 5; /* Additional Length = (Len - 1) - 4 */ + /* Sync data transfer and TCQ. */ + outbuf[7] = 0x10 | (s->tcq ? 0x02 : 0); + r->iov.iov_len = len; break; case 0x16: DPRINTF("Reserve(6)\n"); @@ -365,25 +608,95 @@ 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)) { + if (page == 4) { + int cylinders, heads, secs; + + /* Rigid disk device geometry page. */ + p[0] = 4; + p[1] = 0x16; + /* if a geometry hint is available, use it */ + bdrv_get_geometry_hint(s->bdrv, &cylinders, &heads, &secs); + p[2] = (cylinders >> 16) & 0xff; + p[3] = (cylinders >> 8) & 0xff; + p[4] = cylinders & 0xff; + p[5] = heads & 0xff; + /* Write precomp start cylinder, disabled */ + p[6] = (cylinders >> 16) & 0xff; + p[7] = (cylinders >> 8) & 0xff; + p[8] = cylinders & 0xff; + /* Reduced current start cylinder, disabled */ + p[9] = (cylinders >> 16) & 0xff; + p[10] = (cylinders >> 8) & 0xff; + p[11] = cylinders & 0xff; + /* Device step rate [ns], 200ns */ + p[12] = 0; + p[13] = 200; + /* Landing zone cylinder */ + p[14] = 0xff; + p[15] = 0xff; + p[16] = 0xff; + /* Medium rotation rate [rpm], 5400 rpm */ + p[20] = (5400 >> 8) & 0xff; + p[21] = 5400 & 0xff; + p += 0x16; + } else if (page == 5) { + int cylinders, heads, secs; + + /* Flexible disk device geometry page. */ + p[0] = 5; + p[1] = 0x1e; + /* Transfer rate [kbit/s], 5Mbit/s */ + p[2] = 5000 >> 8; + p[3] = 5000 & 0xff; + /* if a geometry hint is available, use it */ + bdrv_get_geometry_hint(s->bdrv, &cylinders, &heads, &secs); + p[4] = heads & 0xff; + p[5] = secs & 0xff; + p[6] = s->cluster_size * 2; + p[8] = (cylinders >> 8) & 0xff; + p[9] = cylinders & 0xff; + /* Write precomp start cylinder, disabled */ + p[10] = (cylinders >> 8) & 0xff; + p[11] = cylinders & 0xff; + /* Reduced current start cylinder, disabled */ + p[12] = (cylinders >> 8) & 0xff; + p[13] = cylinders & 0xff; + /* Device step rate [100us], 100us */ + p[14] = 0; + p[15] = 1; + /* Device step pulse width [us], 1us */ + p[16] = 1; + /* Device head settle delay [100us], 100us */ + p[17] = 0; + p[18] = 1; + /* Motor on delay [0.1s], 0.1s */ + p[19] = 1; + /* Motor off delay [0.1s], 0.1s */ + p[20] = 1; + /* Medium rotation rate [rpm], 5400 rpm */ + p[28] = (5400 >> 8) & 0xff; + p[29] = 5400 & 0xff; + p += 0x1e; + } else if ((page == 8 || page == 0x3f)) { /* Caching page. */ + memset(p,0,20); p[0] = 8; p[1] = 0x12; p[2] = 4; /* WCE */ - p += 19; + p += 20; } if ((page == 0x3f || page == 0x2a) && (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM)) { @@ -413,16 +726,20 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) p[19] = (16 * 176) & 0xff; p[20] = (16 * 176) >> 8; // 16x write speed current p[21] = (16 * 176) & 0xff; - p += 21; + p += 22; } - s->buf_len = p - s->buf; - s->buf[0] = s->buf_len - 4; - if (s->buf_len > len) - s->buf_len = len; + r->iov.iov_len = p - outbuf; + outbuf[0] = r->iov.iov_len - 4; + if (r->iov.iov_len > len) + r->iov.iov_len = len; } break; case 0x1b: DPRINTF("Start Stop Unit\n"); + if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM && + (buf[4] & 2)) + /* load/eject medium */ + bdrv_eject(s->bdrv, !(buf[4] & 1)); break; case 0x1e: DPRINTF("Prevent Allow Medium Removal (prevent = %d)\n", buf[4] & 3); @@ -431,40 +748,53 @@ 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); + nb_sectors /= s->cluster_size; /* 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; + /* Remember the new size for read/write sanity checking. */ + s->max_lba = nb_sectors; + /* Clip to 2TB, instead of returning capacity modulo 2TB. */ + if (nb_sectors > UINT32_MAX) + nb_sectors = UINT32_MAX; + 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->iov.iov_len = 8; } else { - scsi_command_complete(s, SENSE_NOT_READY); + notready: + scsi_command_complete(r, STATUS_CHECK_CONDITION, 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; + case 0x88: + DPRINTF("Read (sector %lld, count %d)\n", lba, len); + if (lba > s->max_lba) + goto illegal_lba; + 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; + case 0x8a: + DPRINTF("Write (sector %lld, count %d)\n", lba, len); + if (lba > s->max_lba) + goto illegal_lba; + r->sector = lba * s->cluster_size; + r->sector_count = len * s->cluster_size; is_write = 1; break; case 0x35: - DPRINTF("Syncronise cache (sector %d, count %d)\n", lba, len); + DPRINTF("Synchronise cache (sector %d, count %d)\n", lba, len); bdrv_flush(s->bdrv); break; case 0x43: @@ -476,20 +806,21 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) start_track = buf[6]; bdrv_get_geometry(s->bdrv, &nb_sectors); DPRINTF("Read TOC (track %d format %d msf %d)\n", start_track, format, msf >> 1); + nb_sectors /= s->cluster_size; 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 +828,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->iov.iov_len = len; break; } error_cmd: @@ -506,11 +837,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); - /* ??? This shoud probably return much more information. For now + memset(outbuf, 0, 8); + /* ??? This should 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->iov.iov_len = 8; break; case 0x56: DPRINTF("Reserve(10)\n"); @@ -522,41 +853,89 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun) if (buf[1] & 3) goto fail; break; + case 0x9e: + /* Service Action In subcommands. */ + if ((buf[1] & 31) == 0x10) { + DPRINTF("SAI READ CAPACITY(16)\n"); + memset(outbuf, 0, len); + bdrv_get_geometry(s->bdrv, &nb_sectors); + nb_sectors /= s->cluster_size; + /* Returned value is the address of the last sector. */ + if (nb_sectors) { + nb_sectors--; + /* Remember the new size for read/write sanity checking. */ + s->max_lba = nb_sectors; + outbuf[0] = (nb_sectors >> 56) & 0xff; + outbuf[1] = (nb_sectors >> 48) & 0xff; + outbuf[2] = (nb_sectors >> 40) & 0xff; + outbuf[3] = (nb_sectors >> 32) & 0xff; + outbuf[4] = (nb_sectors >> 24) & 0xff; + outbuf[5] = (nb_sectors >> 16) & 0xff; + outbuf[6] = (nb_sectors >> 8) & 0xff; + outbuf[7] = nb_sectors & 0xff; + outbuf[8] = 0; + outbuf[9] = 0; + outbuf[10] = s->cluster_size * 2; + outbuf[11] = 0; + /* Protection, exponent and lowest lba field left blank. */ + r->iov.iov_len = len; + } else { + scsi_command_complete(r, STATUS_CHECK_CONDITION, SENSE_NOT_READY); + return 0; + } + break; + } + DPRINTF("Unsupported Service Action In\n"); + goto fail; case 0xa0: 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->iov.iov_len = 16; + break; + case 0x2f: + DPRINTF("Verify (sector %d, count %d)\n", lba, len); break; default: DPRINTF("Unknown SCSI command (%2.2x)\n", buf[0]); fail: - scsi_command_complete(s, SENSE_ILLEGAL_REQUEST); + scsi_command_complete(r, STATUS_CHECK_CONDITION, SENSE_ILLEGAL_REQUEST); return 0; + illegal_lba: + scsi_command_complete(r, STATUS_CHECK_CONDITION, SENSE_HARDWARE_ERROR); + return 0; } - if (s->sector_count == 0 && s->buf_len == 0) { - scsi_command_complete(s, SENSE_NO_SENSE); + if (r->sector_count == 0 && r->iov.iov_len == 0) { + scsi_command_complete(r, STATUS_GOOD, SENSE_NO_SENSE); + } + len = r->sector_count * 512 + r->iov.iov_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) +static void scsi_destroy(SCSIDevice *d) { - bdrv_close(s->bdrv); - qemu_free(s); + qemu_free(d->state); + qemu_free(d); } -SCSIDevice *scsi_disk_init(BlockDriverState *bdrv, - scsi_completionfn completion, - void *opaque) +SCSIDevice *scsi_disk_init(BlockDriverState *bdrv, int tcq, + scsi_completionfn completion, void *opaque) { - SCSIDevice *s; + SCSIDevice *d; + SCSIDeviceState *s; + uint64_t nb_sectors; - s = (SCSIDevice *)qemu_mallocz(sizeof(SCSIDevice)); + s = (SCSIDeviceState *)qemu_mallocz(sizeof(SCSIDeviceState)); s->bdrv = bdrv; + s->tcq = tcq; s->completion = completion; s->opaque = opaque; if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { @@ -564,7 +943,24 @@ SCSIDevice *scsi_disk_init(BlockDriverState *bdrv, } else { s->cluster_size = 1; } - - return s; + bdrv_get_geometry(s->bdrv, &nb_sectors); + nb_sectors /= s->cluster_size; + if (nb_sectors) + nb_sectors--; + s->max_lba = nb_sectors; + strncpy(s->drive_serial_str, drive_get_serial(s->bdrv), + sizeof(s->drive_serial_str)); + if (strlen(s->drive_serial_str) == 0) + pstrcpy(s->drive_serial_str, sizeof(s->drive_serial_str), "0"); + qemu_add_vm_change_state_handler(scsi_dma_restart_cb, s); + d = (SCSIDevice *)qemu_mallocz(sizeof(SCSIDevice)); + d->state = s; + d->destroy = scsi_destroy; + d->send_command = scsi_send_command; + d->read_data = scsi_read_data; + d->write_data = scsi_write_data; + d->cancel_io = scsi_cancel_io; + d->get_buf = scsi_get_buf; + + return d; } -