From 10d6530c64b4b4935fea3a4317e9c46d92a5e6db Mon Sep 17 00:00:00 2001 From: Hannes Reinecke Date: Wed, 11 Jul 2012 13:35:16 +0200 Subject: [PATCH 01/32] megasas: Replace trace_megasas_dcmd_dump_frame() trace_megasas_dcmd_dump_frame() takes 9 arguments, which is rather much. Plus the trace infrastructure doesn't support it. As we can get the information via other means it's pointless to have it in the driver, so rather use some proper trace point here and remove the old one. Signed-off-by: Hannes Reinecke Cc: Stefan Weil Signed-off-by: Paolo Bonzini --- hw/megasas.c | 37 +++++++++---------------------------- trace-events | 2 +- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/hw/megasas.c b/hw/megasas.c index b99fa9792e..c389ddfb6a 100644 --- a/hw/megasas.c +++ b/hw/megasas.c @@ -1290,35 +1290,16 @@ static int megasas_cluster_reset_ld(MegasasState *s, MegasasCmd *cmd) static int megasas_dcmd_set_properties(MegasasState *s, MegasasCmd *cmd) { - uint8_t *dummy = g_malloc(cmd->iov_size); + struct mfi_ctrl_props info; + size_t dcmd_size = sizeof(info); - dma_buf_write(dummy, cmd->iov_size, &cmd->qsg); - - trace_megasas_dcmd_dump_frame(0, - dummy[0x00], dummy[0x01], dummy[0x02], dummy[0x03], - dummy[0x04], dummy[0x05], dummy[0x06], dummy[0x07]); - trace_megasas_dcmd_dump_frame(1, - dummy[0x08], dummy[0x09], dummy[0x0a], dummy[0x0b], - dummy[0x0c], dummy[0x0d], dummy[0x0e], dummy[0x0f]); - trace_megasas_dcmd_dump_frame(2, - dummy[0x10], dummy[0x11], dummy[0x12], dummy[0x13], - dummy[0x14], dummy[0x15], dummy[0x16], dummy[0x17]); - trace_megasas_dcmd_dump_frame(3, - dummy[0x18], dummy[0x19], dummy[0x1a], dummy[0x1b], - dummy[0x1c], dummy[0x1d], dummy[0x1e], dummy[0x1f]); - trace_megasas_dcmd_dump_frame(4, - dummy[0x20], dummy[0x21], dummy[0x22], dummy[0x23], - dummy[0x24], dummy[0x25], dummy[0x26], dummy[0x27]); - trace_megasas_dcmd_dump_frame(5, - dummy[0x28], dummy[0x29], dummy[0x2a], dummy[0x2b], - dummy[0x2c], dummy[0x2d], dummy[0x2e], dummy[0x2f]); - trace_megasas_dcmd_dump_frame(6, - dummy[0x30], dummy[0x31], dummy[0x32], dummy[0x33], - dummy[0x34], dummy[0x35], dummy[0x36], dummy[0x37]); - trace_megasas_dcmd_dump_frame(7, - dummy[0x38], dummy[0x39], dummy[0x3a], dummy[0x3b], - dummy[0x3c], dummy[0x3d], dummy[0x3e], dummy[0x3f]); - g_free(dummy); + if (cmd->iov_size < dcmd_size) { + trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size, + dcmd_size); + return MFI_STAT_INVALID_PARAMETER; + } + dma_buf_write((uint8_t *)&info, cmd->iov_size, &cmd->qsg); + trace_megasas_dcmd_unsupported(cmd->index, cmd->iov_size); return MFI_STAT_OK; } diff --git a/trace-events b/trace-events index 2a5f074137..6fb5eb547e 100644 --- a/trace-events +++ b/trace-events @@ -595,7 +595,7 @@ megasas_dcmd_ld_get_list(int cmd, int num, int max) "scmd %d: DCMD LD get list: megasas_dcmd_ld_get_info(int cmd, int ld_id) "scmd %d: DCMD LD get info for dev %d" megasas_dcmd_pd_get_info(int cmd, int pd_id) "scmd %d: DCMD PD get info for dev %d" megasas_dcmd_pd_list_query(int cmd, int flags) "scmd %d: DCMD PD list query flags %x" -megasas_dcmd_dump_frame(int offset, char f0, char f1, char f2, char f3, char f4, char f5, char f6, char f7) "0x%x: %02x %02x %02x %02x %02x %02x %02x %02x" +megasas_dcmd_unsupported(int cmd, unsigned long size) "scmd %d: set properties len %ld" megasas_abort_frame(int cmd, int abort_cmd) "scmd %d: aborting frame %x" megasas_abort_no_cmd(int cmd, uint64_t context) "scmd %d: no active command for frame context %" PRIx64 "" megasas_abort_invalid_context(int cmd, uint64_t context, int abort_cmd) "scmd %d: invalid frame context %" PRIx64 " for abort frame %x" From e2b06058f7a0812729b95bcd4600238165323220 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 12 Jul 2012 15:02:29 +0200 Subject: [PATCH 02/32] megasas: fix misuse of scsi_req_abort scsi_req_abort is for terminating a command with a non-zero status. The ABORT task management function is invoked by scsi_req_cancel. In fact, ABORTED_COMMAND is a sense key, not a SAM status code. Signed-off-by: Paolo Bonzini --- hw/megasas.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/megasas.c b/hw/megasas.c index c389ddfb6a..9a0eab1c98 100644 --- a/hw/megasas.c +++ b/hw/megasas.c @@ -544,7 +544,7 @@ static void megasas_reset_frames(MegasasState *s) static void megasas_abort_command(MegasasCmd *cmd) { if (cmd->req) { - scsi_req_abort(cmd->req, ABORTED_COMMAND); + scsi_req_cancel(cmd->req); cmd->req = NULL; } } From 2f0772c5b4818d4b2078be9dace0036d1030faee Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 2 Jul 2012 17:03:55 +0200 Subject: [PATCH 03/32] lsi: use qdev_reset_all By first resetting the devices, lsi_soft_reset will find the queue already cleared so there is no need to do that forcibly (which may also leak SCSIRequests, and/or worse due to dangling references to the lsi_request in the hba_private field). Signed-off-by: Paolo Bonzini --- hw/lsi53c895a.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/hw/lsi53c895a.c b/hw/lsi53c895a.c index 2fe141d24e..f04cc68280 100644 --- a/hw/lsi53c895a.c +++ b/hw/lsi53c895a.c @@ -282,8 +282,6 @@ static inline int lsi_irq_on_rsl(LSIState *s) static void lsi_soft_reset(LSIState *s) { - lsi_request *p; - DPRINTF("Reset\n"); s->carry = 0; @@ -350,15 +348,8 @@ static void lsi_soft_reset(LSIState *s) s->sbc = 0; s->csbc = 0; s->sbr = 0; - while (!QTAILQ_EMPTY(&s->queue)) { - p = QTAILQ_FIRST(&s->queue); - QTAILQ_REMOVE(&s->queue, p, next); - g_free(p); - } - if (s->current) { - g_free(s->current); - s->current = NULL; - } + assert(QTAILQ_EMPTY(&s->queue)); + assert(!s->current); } static int lsi_dma_40bit(LSIState *s) @@ -1738,7 +1729,7 @@ static void lsi_reg_writeb(LSIState *s, int offset, uint8_t val) lsi_execute_script(s); } if (val & LSI_ISTAT0_SRST) { - lsi_soft_reset(s); + qdev_reset_all(&s->dev.qdev); } break; case 0x16: /* MBOX0 */ From d2a9998f8864e6f816262d9416d8b538186a8cad Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 2 Jul 2012 17:04:40 +0200 Subject: [PATCH 04/32] lsi: introduce lsi_request_free Split the common bits of lsi_request_cancelled and lsi_command_complete out to a new function. Signed-off-by: Paolo Bonzini --- hw/lsi53c895a.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/hw/lsi53c895a.c b/hw/lsi53c895a.c index f04cc68280..078ab6d8ba 100644 --- a/hw/lsi53c895a.c +++ b/hw/lsi53c895a.c @@ -641,23 +641,24 @@ static lsi_request *lsi_find_by_tag(LSIState *s, uint32_t tag) return NULL; } +static void lsi_request_free(LSIState *s, lsi_request *p) +{ + if (p == s->current) { + s->current = NULL; + } else { + QTAILQ_REMOVE(&s->queue, p, next); + } + g_free(p); +} + static void lsi_request_cancelled(SCSIRequest *req) { LSIState *s = DO_UPCAST(LSIState, dev.qdev, req->bus->qbus.parent); lsi_request *p = req->hba_private; - if (s->current && req == s->current->req) { - scsi_req_unref(req); - g_free(s->current); - s->current = NULL; - return; - } - - if (p) { - QTAILQ_REMOVE(&s->queue, p, next); - scsi_req_unref(req); - g_free(p); - } + req->hba_private = NULL; + lsi_request_free(s, p); + scsi_req_unref(req); } /* Record that data is available for a queued command. Returns zero if @@ -706,9 +707,9 @@ static void lsi_command_complete(SCSIRequest *req, uint32_t status, size_t resid } if (s->current && req == s->current->req) { - scsi_req_unref(s->current->req); - g_free(s->current); - s->current = NULL; + req->hba_private = NULL; + lsi_request_free(s, s->current); + scsi_req_unref(req); } lsi_resume_script(s); } From 8f6e699ddbcad32480fa64796ccf44cbaf5b4b91 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 2 Jul 2012 17:07:40 +0200 Subject: [PATCH 05/32] lsi: avoid redundant tests of s->current != NULL Simplify the code by checking against req->hba_private directly, and asserting that it is non-NULL before a command is completed or canceled. Signed-off-by: Paolo Bonzini --- hw/lsi53c895a.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hw/lsi53c895a.c b/hw/lsi53c895a.c index 078ab6d8ba..5f6cb17bda 100644 --- a/hw/lsi53c895a.c +++ b/hw/lsi53c895a.c @@ -706,7 +706,7 @@ static void lsi_command_complete(SCSIRequest *req, uint32_t status, size_t resid lsi_set_phase(s, PHASE_ST); } - if (s->current && req == s->current->req) { + if (req->hba_private == s->current) { req->hba_private = NULL; lsi_request_free(s, s->current); scsi_req_unref(req); @@ -720,7 +720,8 @@ static void lsi_transfer_data(SCSIRequest *req, uint32_t len) LSIState *s = DO_UPCAST(LSIState, dev.qdev, req->bus->qbus.parent); int out; - if (s->waiting == 1 || !s->current || req->hba_private != s->current || + assert(req->hba_private); + if (s->waiting == 1 || req->hba_private != s->current || (lsi_irq_on_rsl(s) && !(s->scntl1 & LSI_SCNTL1_CON))) { if (lsi_queue_req(s, req, len)) { return; From 038478370d5c7ed8f66fc9523bf3f60b8800fe86 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 9 Jul 2012 12:11:04 +0200 Subject: [PATCH 06/32] scsi-block: remove properties that are not relevant for passthrough scsi-block is a passthrough device and does not allow customization of vendor, product, removable, DPOFUA, block size or any other piece of information. Thus, drop DEFINE_SCSI_DISK_PROPERTIES() from the list of qdev properties. Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 525816cb76..8907197d59 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -2040,7 +2040,7 @@ static TypeInfo scsi_cd_info = { #ifdef __linux__ static Property scsi_block_properties[] = { - DEFINE_SCSI_DISK_PROPERTIES(), + DEFINE_PROP_DRIVE("drive", SCSIDiskState, qdev.conf.bs), DEFINE_PROP_END_OF_LIST(), }; From 2a025ae454c361fb03aadf88e8a2f678b80b38e6 Mon Sep 17 00:00:00 2001 From: Dmitry Fleytman Date: Mon, 9 Jul 2012 08:50:43 +0200 Subject: [PATCH 07/32] cutils: add strpadcpy() Signed-off-by: Yan Vugenfirer Signed-off-by: Dmitry Fleytman Signed-off-by: Paolo Bonzini --- cutils.c | 7 +++++++ qemu-common.h | 1 + 2 files changed, 8 insertions(+) diff --git a/cutils.c b/cutils.c index e2bc1b89df..9d4c570939 100644 --- a/cutils.c +++ b/cutils.c @@ -28,6 +28,13 @@ #include "qemu_socket.h" #include "iov.h" +void strpadcpy(char *buf, int buf_size, const char *str, char pad) +{ + int len = qemu_strnlen(str, buf_size); + memcpy(buf, str, len); + memset(buf + len, pad, buf_size - len); +} + void pstrcpy(char *buf, int buf_size, const char *str) { int c; diff --git a/qemu-common.h b/qemu-common.h index 7c8dac80a2..d26ff39e87 100644 --- a/qemu-common.h +++ b/qemu-common.h @@ -138,6 +138,7 @@ int qemu_timedate_diff(struct tm *tm); /* cutils.c */ void pstrcpy(char *buf, int buf_size, const char *str); +void strpadcpy(char *buf, int buf_size, const char *str, char pad); char *pstrcat(char *buf, int buf_size, const char *s); int strstart(const char *str, const char *val, const char **ptr); int stristart(const char *str, const char *val, const char **ptr); From 353815aa6d706b1960fbeb75c4fd2bef7b23ed3e Mon Sep 17 00:00:00 2001 From: Dmitry Fleytman Date: Fri, 6 Jul 2012 22:03:35 -0700 Subject: [PATCH 08/32] scsi-disk: let the user customize vendor and product name This patch adds two new properties vendor and product to SCSI disks. These options let the user customize the inquiry data returned by the disk. Signed-off-by: Yan Vugenfirer Signed-off-by: Dmitry Fleytman [ Use vendor and product property names, avoid "if" statements. - PB ] Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 8907197d59..788fe8639e 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -72,6 +72,8 @@ struct SCSIDiskState QEMUBH *bh; char *version; char *serial; + char *vendor; + char *product; bool tray_open; bool tray_locked; }; @@ -669,12 +671,10 @@ static int scsi_disk_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf) outbuf[0] = s->qdev.type & 0x1f; outbuf[1] = (s->features & (1 << SCSI_DISK_F_REMOVABLE)) ? 0x80 : 0; - if (s->qdev.type == TYPE_ROM) { - memcpy(&outbuf[16], "QEMU CD-ROM ", 16); - } else { - memcpy(&outbuf[16], "QEMU HARDDISK ", 16); - } - memcpy(&outbuf[8], "QEMU ", 8); + + strpadcpy((char *) &outbuf[16], 16, s->product, ' '); + strpadcpy((char *) &outbuf[8], 8, s->vendor, ' '); + memset(&outbuf[32], 0, 4); memcpy(&outbuf[32], s->version, MIN(4, strlen(s->version))); /* @@ -1757,6 +1757,9 @@ static int scsi_initfn(SCSIDevice *dev) if (!s->version) { s->version = g_strdup(qemu_get_version()); } + if (!s->vendor) { + s->vendor = g_strdup("QEMU"); + } if (bdrv_is_sg(s->qdev.conf.bs)) { error_report("unwanted /dev/sg*"); @@ -1778,6 +1781,9 @@ static int scsi_hd_initfn(SCSIDevice *dev) SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev); s->qdev.blocksize = s->qdev.conf.logical_block_size; s->qdev.type = TYPE_DISK; + if (!s->product) { + s->product = g_strdup("QEMU HARDDISK"); + } return scsi_initfn(&s->qdev); } @@ -1787,6 +1793,9 @@ static int scsi_cd_initfn(SCSIDevice *dev) s->qdev.blocksize = 2048; s->qdev.type = TYPE_ROM; s->features |= 1 << SCSI_DISK_F_REMOVABLE; + if (!s->product) { + s->product = g_strdup("QEMU CD-ROM"); + } return scsi_initfn(&s->qdev); } @@ -1954,10 +1963,12 @@ static SCSIRequest *scsi_block_new_request(SCSIDevice *d, uint32_t tag, } #endif -#define DEFINE_SCSI_DISK_PROPERTIES() \ - DEFINE_BLOCK_PROPERTIES(SCSIDiskState, qdev.conf), \ - DEFINE_PROP_STRING("ver", SCSIDiskState, version), \ - DEFINE_PROP_STRING("serial", SCSIDiskState, serial) +#define DEFINE_SCSI_DISK_PROPERTIES() \ + DEFINE_BLOCK_PROPERTIES(SCSIDiskState, qdev.conf), \ + DEFINE_PROP_STRING("ver", SCSIDiskState, version), \ + DEFINE_PROP_STRING("serial", SCSIDiskState, serial), \ + DEFINE_PROP_STRING("vendor", SCSIDiskState, vendor), \ + DEFINE_PROP_STRING("product", SCSIDiskState, product) static Property scsi_hd_properties[] = { DEFINE_SCSI_DISK_PROPERTIES(), From c1b3524788c156c49bff5f0415c39f9f0b7cc0e2 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 8 Feb 2012 10:37:48 +0100 Subject: [PATCH 09/32] scsi-disk: make discard asynchronous By making discard asynchronous, we can reuse all the error handling code that is used for other commands. Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 788fe8639e..f8e9dc1419 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -169,7 +169,7 @@ static void scsi_disk_load_request(QEMUFile *f, SCSIRequest *req) qemu_iovec_init_external(&r->qiov, &r->iov, 1); } -static void scsi_flush_complete(void * opaque, int ret) +static void scsi_aio_complete(void *opaque, int ret) { SCSIDiskReq *r = (SCSIDiskReq *)opaque; SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); @@ -222,7 +222,7 @@ static void scsi_write_do_fua(SCSIDiskReq *r) if (scsi_is_cmd_fua(&r->req.cmd)) { bdrv_acct_start(s->qdev.conf.bs, &r->acct, 0, BDRV_ACCT_FLUSH); - r->req.aiocb = bdrv_aio_flush(s->qdev.conf.bs, scsi_flush_complete, r); + r->req.aiocb = bdrv_aio_flush(s->qdev.conf.bs, scsi_aio_complete, r); return; } @@ -1543,7 +1543,7 @@ static int32_t scsi_send_command(SCSIRequest *req, uint8_t *buf) /* The request is used as the AIO opaque value, so add a ref. */ scsi_req_ref(&r->req); bdrv_acct_start(s->qdev.conf.bs, &r->acct, 0, BDRV_ACCT_FLUSH); - r->req.aiocb = bdrv_aio_flush(s->qdev.conf.bs, scsi_flush_complete, r); + r->req.aiocb = bdrv_aio_flush(s->qdev.conf.bs, scsi_aio_complete, r); return 0; case READ_6: case READ_10: @@ -1620,15 +1620,13 @@ static int32_t scsi_send_command(SCSIRequest *req, uint8_t *buf) goto fail; } - rc = bdrv_discard(s->qdev.conf.bs, - r->req.cmd.lba * (s->qdev.blocksize / 512), - len * (s->qdev.blocksize / 512)); - if (rc < 0) { - /* XXX: better error code ?*/ - goto fail; - } - - break; + /* The request is used as the AIO opaque value, so add a ref. */ + scsi_req_ref(&r->req); + r->req.aiocb = bdrv_aio_discard(s->qdev.conf.bs, + r->req.cmd.lba * (s->qdev.blocksize / 512), + len * (s->qdev.blocksize / 512), + scsi_aio_complete, r); + return 0; default: DPRINTF("Unknown SCSI command (%2.2x)\n", buf[0]); scsi_check_condition(r, SENSE_CODE(INVALID_OPCODE)); From 101aa85f9862f23e17dec570221f53c46c5e86e1 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 8 Feb 2012 09:43:52 +0100 Subject: [PATCH 10/32] scsi-disk: move all non-DMA commands to scsi_disk_emulate_command We want to use separate SCSIReqOps for emulated commands needing an allocated buffer vs. those that are zerocopy when the HBA supports S/G lists. Ensure that all of the former are in scsi_disk_emulate_command. Commands that do not have any parameters are more similar to emulated commands, so also move them, even if they do I/O. Finally, MODE SELECT and MODE SELECT(10) are broken because we do not yet support passing parameter data _to_ emulated commands, so disable them. Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 156 +++++++++++++++++++++---------------------------- 1 file changed, 68 insertions(+), 88 deletions(-) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index f8e9dc1419..9f7258d3d2 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1448,10 +1448,65 @@ static int scsi_disk_emulate_command(SCSIDiskReq *r) } DPRINTF("Unsupported Service Action In\n"); goto illegal_request; + case SYNCHRONIZE_CACHE: + /* The request is used as the AIO opaque value, so add a ref. */ + scsi_req_ref(&r->req); + bdrv_acct_start(s->qdev.conf.bs, &r->acct, 0, BDRV_ACCT_FLUSH); + r->req.aiocb = bdrv_aio_flush(s->qdev.conf.bs, scsi_aio_complete, r); + return 0; + case SEEK_10: + DPRINTF("Seek(10) (sector %" PRId64 ")\n", r->req.cmd.lba); + if (r->req.cmd.lba > s->qdev.max_lba) { + goto illegal_lba; + } + break; +#if 0 + case MODE_SELECT: + DPRINTF("Mode Select(6) (len %lu)\n", (long)r->req.cmd.xfer); + /* We don't support mode parameter changes. + Allow the mode parameter header + block descriptors only. */ + if (r->req.cmd.xfer > 12) { + goto illegal_request; + } + break; + case MODE_SELECT_10: + DPRINTF("Mode Select(10) (len %lu)\n", (long)r->req.cmd.xfer); + /* We don't support mode parameter changes. + Allow the mode parameter header + block descriptors only. */ + if (r->req.cmd.xfer > 16) { + goto illegal_request; + } + break; +#endif + case WRITE_SAME_10: + nb_sectors = lduw_be_p(&req->cmd.buf[7]); + goto write_same; + case WRITE_SAME_16: + nb_sectors = ldl_be_p(&req->cmd.buf[10]) & 0xffffffffULL; + write_same: + if (r->req.cmd.lba > s->qdev.max_lba) { + goto illegal_lba; + } + + /* + * We only support WRITE SAME with the unmap bit set for now. + */ + if (!(req->cmd.buf[1] & 0x8)) { + goto illegal_request; + } + + /* The request is used as the AIO opaque value, so add a ref. */ + scsi_req_ref(&r->req); + r->req.aiocb = bdrv_aio_discard(s->qdev.conf.bs, + r->req.cmd.lba * (s->qdev.blocksize / 512), + nb_sectors * (s->qdev.blocksize / 512), + scsi_aio_complete, r); + return 0; default: scsi_check_condition(r, SENSE_CODE(INVALID_OPCODE)); return -1; } + assert(r->sector_count == 0); buflen = MIN(buflen, req->cmd.xfer); return buflen; @@ -1460,6 +1515,10 @@ illegal_request: scsi_check_condition(r, SENSE_CODE(INVALID_FIELD)); } return -1; + +illegal_lba: + scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE)); + return 0; } /* Execute a scsi command. Returns the length of the data expected by the @@ -1513,38 +1572,6 @@ static int32_t scsi_send_command(SCSIRequest *req, uint8_t *buf) } switch (command) { - case TEST_UNIT_READY: - case INQUIRY: - case MODE_SENSE: - case MODE_SENSE_10: - case RESERVE: - case RESERVE_10: - case RELEASE: - case RELEASE_10: - case START_STOP: - case ALLOW_MEDIUM_REMOVAL: - case READ_CAPACITY_10: - case READ_TOC: - case READ_DISC_INFORMATION: - case READ_DVD_STRUCTURE: - case GET_CONFIGURATION: - case GET_EVENT_STATUS_NOTIFICATION: - case MECHANISM_STATUS: - case SERVICE_ACTION_IN_16: - case REQUEST_SENSE: - rc = scsi_disk_emulate_command(r); - if (rc < 0) { - return 0; - } - - r->iov.iov_len = rc; - break; - case SYNCHRONIZE_CACHE: - /* The request is used as the AIO opaque value, so add a ref. */ - scsi_req_ref(&r->req); - bdrv_acct_start(s->qdev.conf.bs, &r->acct, 0, BDRV_ACCT_FLUSH); - r->req.aiocb = bdrv_aio_flush(s->qdev.conf.bs, scsi_aio_complete, r); - return 0; case READ_6: case READ_10: case READ_12: @@ -1577,63 +1604,16 @@ static int32_t scsi_send_command(SCSIRequest *req, uint8_t *buf) r->sector = r->req.cmd.lba * (s->qdev.blocksize / 512); r->sector_count = len * (s->qdev.blocksize / 512); break; - case MODE_SELECT: - DPRINTF("Mode Select(6) (len %lu)\n", (long)r->req.cmd.xfer); - /* We don't support mode parameter changes. - Allow the mode parameter header + block descriptors only. */ - if (r->req.cmd.xfer > 12) { - goto fail; - } - break; - case MODE_SELECT_10: - DPRINTF("Mode Select(10) (len %lu)\n", (long)r->req.cmd.xfer); - /* We don't support mode parameter changes. - Allow the mode parameter header + block descriptors only. */ - if (r->req.cmd.xfer > 16) { - goto fail; - } - break; - case SEEK_10: - DPRINTF("Seek(10) (sector %" PRId64 ")\n", r->req.cmd.lba); - if (r->req.cmd.lba > s->qdev.max_lba) { - goto illegal_lba; - } - break; - case WRITE_SAME_10: - len = lduw_be_p(&buf[7]); - goto write_same; - case WRITE_SAME_16: - len = ldl_be_p(&buf[10]) & 0xffffffffULL; - write_same: - - DPRINTF("WRITE SAME() (sector %" PRId64 ", count %d)\n", - r->req.cmd.lba, len); - - if (r->req.cmd.lba > s->qdev.max_lba) { - goto illegal_lba; - } - - /* - * We only support WRITE SAME with the unmap bit set for now. - */ - if (!(buf[1] & 0x8)) { - goto fail; - } - - /* The request is used as the AIO opaque value, so add a ref. */ - scsi_req_ref(&r->req); - r->req.aiocb = bdrv_aio_discard(s->qdev.conf.bs, - r->req.cmd.lba * (s->qdev.blocksize / 512), - len * (s->qdev.blocksize / 512), - scsi_aio_complete, r); - return 0; default: - DPRINTF("Unknown SCSI command (%2.2x)\n", buf[0]); - scsi_check_condition(r, SENSE_CODE(INVALID_OPCODE)); - return 0; - fail: - scsi_check_condition(r, SENSE_CODE(INVALID_FIELD)); - return 0; + rc = scsi_disk_emulate_command(r); + if (rc < 0) { + return 0; + } + if (r->req.aiocb) { + return 0; + } + r->iov.iov_len = rc; + break; illegal_lba: scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE)); return 0; From b08d0ea0446aa91f373c9df4254ba3bc4ee84098 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 8 Feb 2012 09:53:42 +0100 Subject: [PATCH 11/32] scsi-disk: split scsi-disk reqops Only checks for present medium were still done in scsi_send_command for emulated commands. So move those to scsi_disk_emulate_command and return different SCSIReqOps depending on the kind of command. Checks for present medium can be done unconditionally for the scsi_disk_dma_reqops case. Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 191 +++++++++++++++++++++++++++++++------------------ 1 file changed, 123 insertions(+), 68 deletions(-) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 9f7258d3d2..ea0b05e48a 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1262,14 +1262,39 @@ static int scsi_disk_emulate_start_stop(SCSIDiskReq *r) return 0; } -static int scsi_disk_emulate_command(SCSIDiskReq *r) +static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) { - SCSIRequest *req = &r->req; + SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev); uint64_t nb_sectors; uint8_t *outbuf; int buflen = 0; + switch (req->cmd.buf[0]) { + case INQUIRY: + case MODE_SENSE: + case MODE_SENSE_10: + case RESERVE: + case RESERVE_10: + case RELEASE: + case RELEASE_10: + case START_STOP: + case ALLOW_MEDIUM_REMOVAL: + case GET_CONFIGURATION: + case GET_EVENT_STATUS_NOTIFICATION: + case MECHANISM_STATUS: + case REQUEST_SENSE: + break; + + default: + if (s->tray_open || !bdrv_is_inserted(s->qdev.conf.bs)) { + scsi_check_condition(r, SENSE_CODE(NO_MEDIUM)); + return 0; + } + break; + } + + assert(req->cmd.mode != SCSI_XFER_TO_DEV); if (!r->iov.iov_base) { /* * FIXME: we shouldn't return anything bigger than 4k, but the code @@ -1332,7 +1357,7 @@ static int scsi_disk_emulate_command(SCSIDiskReq *r) break; case START_STOP: if (scsi_disk_emulate_start_stop(r) < 0) { - return -1; + return 0; } break; case ALLOW_MEDIUM_REMOVAL: @@ -1503,18 +1528,23 @@ static int scsi_disk_emulate_command(SCSIDiskReq *r) scsi_aio_complete, r); return 0; default: + DPRINTF("Unknown SCSI command (%2.2x)\n", buf[0]); scsi_check_condition(r, SENSE_CODE(INVALID_OPCODE)); - return -1; + return 0; } - assert(r->sector_count == 0); - buflen = MIN(buflen, req->cmd.xfer); - return buflen; + assert(!r->req.aiocb && r->sector_count == 0); + r->iov.iov_len = MIN(buflen, req->cmd.xfer); + r->sector_count = -1; + if (r->iov.iov_len == 0) { + scsi_req_complete(&r->req, GOOD); + } + return r->iov.iov_len; illegal_request: if (r->req.status == -1) { scsi_check_condition(r, SENSE_CODE(INVALID_FIELD)); } - return -1; + return 0; illegal_lba: scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE)); @@ -1526,49 +1556,18 @@ illegal_lba: (eg. disk reads), negative for transfers to the device (eg. disk writes), and zero if the command does not transfer any data. */ -static int32_t scsi_send_command(SCSIRequest *req, uint8_t *buf) +static int32_t scsi_disk_dma_command(SCSIRequest *req, uint8_t *buf) { SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev); int32_t len; uint8_t command; - int rc; command = buf[0]; - DPRINTF("Command: lun=%d tag=0x%x data=0x%02x", req->lun, req->tag, buf[0]); -#ifdef DEBUG_SCSI - { - int i; - for (i = 1; i < r->req.cmd.len; i++) { - printf(" 0x%02x", buf[i]); - } - printf("\n"); - } -#endif - - switch (command) { - case INQUIRY: - case MODE_SENSE: - case MODE_SENSE_10: - case RESERVE: - case RESERVE_10: - case RELEASE: - case RELEASE_10: - case START_STOP: - case ALLOW_MEDIUM_REMOVAL: - case GET_CONFIGURATION: - case GET_EVENT_STATUS_NOTIFICATION: - case MECHANISM_STATUS: - case REQUEST_SENSE: - break; - - default: - if (s->tray_open || !bdrv_is_inserted(s->qdev.conf.bs)) { - scsi_check_condition(r, SENSE_CODE(NO_MEDIUM)); - return 0; - } - break; + if (s->tray_open || !bdrv_is_inserted(s->qdev.conf.bs)) { + scsi_check_condition(r, SENSE_CODE(NO_MEDIUM)); + return 0; } switch (command) { @@ -1605,30 +1604,19 @@ static int32_t scsi_send_command(SCSIRequest *req, uint8_t *buf) r->sector_count = len * (s->qdev.blocksize / 512); break; default: - rc = scsi_disk_emulate_command(r); - if (rc < 0) { - return 0; - } - if (r->req.aiocb) { - return 0; - } - r->iov.iov_len = rc; - break; + abort(); illegal_lba: scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE)); return 0; } - if (r->sector_count == 0 && r->iov.iov_len == 0) { + if (r->sector_count == 0) { scsi_req_complete(&r->req, GOOD); } - len = r->sector_count * 512 + r->iov.iov_len; + assert(r->iov.iov_len == 0); if (r->req.cmd.mode == SCSI_XFER_TO_DEV) { - return -len; + return -r->sector_count * 512; } else { - if (!r->sector_count) { - r->sector_count = -1; - } - return len; + return r->sector_count * 512; } } @@ -1793,10 +1781,19 @@ static int scsi_disk_initfn(SCSIDevice *dev) } } -static const SCSIReqOps scsi_disk_reqops = { +static const SCSIReqOps scsi_disk_emulate_reqops = { .size = sizeof(SCSIDiskReq), .free_req = scsi_free_request, - .send_command = scsi_send_command, + .send_command = scsi_disk_emulate_command, + .read_data = scsi_read_data, + .write_data = scsi_write_data, + .get_buf = scsi_get_buf, +}; + +static const SCSIReqOps scsi_disk_dma_reqops = { + .size = sizeof(SCSIDiskReq), + .free_req = scsi_free_request, + .send_command = scsi_disk_dma_command, .read_data = scsi_read_data, .write_data = scsi_write_data, .cancel_io = scsi_cancel_io, @@ -1805,13 +1802,72 @@ static const SCSIReqOps scsi_disk_reqops = { .save_request = scsi_disk_save_request, }; +static const SCSIReqOps *const scsi_disk_reqops_dispatch[256] = { + [TEST_UNIT_READY] = &scsi_disk_emulate_reqops, + [INQUIRY] = &scsi_disk_emulate_reqops, + [MODE_SENSE] = &scsi_disk_emulate_reqops, + [MODE_SENSE_10] = &scsi_disk_emulate_reqops, + [START_STOP] = &scsi_disk_emulate_reqops, + [ALLOW_MEDIUM_REMOVAL] = &scsi_disk_emulate_reqops, + [READ_CAPACITY_10] = &scsi_disk_emulate_reqops, + [READ_TOC] = &scsi_disk_emulate_reqops, + [READ_DVD_STRUCTURE] = &scsi_disk_emulate_reqops, + [READ_DISC_INFORMATION] = &scsi_disk_emulate_reqops, + [GET_CONFIGURATION] = &scsi_disk_emulate_reqops, + [GET_EVENT_STATUS_NOTIFICATION] = &scsi_disk_emulate_reqops, + [MECHANISM_STATUS] = &scsi_disk_emulate_reqops, + [SERVICE_ACTION_IN_16] = &scsi_disk_emulate_reqops, + [REQUEST_SENSE] = &scsi_disk_emulate_reqops, + [SYNCHRONIZE_CACHE] = &scsi_disk_emulate_reqops, + [SEEK_10] = &scsi_disk_emulate_reqops, +#if 0 + [MODE_SELECT] = &scsi_disk_emulate_reqops, + [MODE_SELECT_10] = &scsi_disk_emulate_reqops, +#endif + [WRITE_SAME_10] = &scsi_disk_emulate_reqops, + [WRITE_SAME_16] = &scsi_disk_emulate_reqops, + + [READ_6] = &scsi_disk_dma_reqops, + [READ_10] = &scsi_disk_dma_reqops, + [READ_12] = &scsi_disk_dma_reqops, + [READ_16] = &scsi_disk_dma_reqops, + [VERIFY_10] = &scsi_disk_dma_reqops, + [VERIFY_12] = &scsi_disk_dma_reqops, + [VERIFY_16] = &scsi_disk_dma_reqops, + [WRITE_6] = &scsi_disk_dma_reqops, + [WRITE_10] = &scsi_disk_dma_reqops, + [WRITE_12] = &scsi_disk_dma_reqops, + [WRITE_16] = &scsi_disk_dma_reqops, + [WRITE_VERIFY_10] = &scsi_disk_dma_reqops, + [WRITE_VERIFY_12] = &scsi_disk_dma_reqops, + [WRITE_VERIFY_16] = &scsi_disk_dma_reqops, +}; + static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun, uint8_t *buf, void *hba_private) { SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d); SCSIRequest *req; + const SCSIReqOps *ops; + uint8_t command; - req = scsi_req_alloc(&scsi_disk_reqops, &s->qdev, tag, lun, hba_private); +#ifdef DEBUG_SCSI + DPRINTF("Command: lun=%d tag=0x%x data=0x%02x", lun, buf[0]); + { + int i; + for (i = 1; i < r->req.cmd.len; i++) { + printf(" 0x%02x", buf[i]); + } + printf("\n"); + } +#endif + + command = buf[0]; + ops = scsi_disk_reqops_dispatch[command]; + if (!ops) { + ops = &scsi_disk_emulate_reqops; + } + req = scsi_req_alloc(ops, &s->qdev, tag, lun, hba_private); return req; } @@ -1925,15 +1981,14 @@ static SCSIRequest *scsi_block_new_request(SCSIDevice *d, uint32_t tag, * unreliable, too. It is even possible that reads deliver random data * from the host page cache (this is probably a Linux bug). * - * We might use scsi_disk_reqops as long as no writing commands are + * We might use scsi_disk_dma_reqops as long as no writing commands are * seen, but performance usually isn't paramount on optical media. So, * just make scsi-block operate the same as scsi-generic for them. */ - if (s->qdev.type == TYPE_ROM) { - break; - } - return scsi_req_alloc(&scsi_disk_reqops, &s->qdev, tag, lun, - hba_private); + if (s->qdev.type != TYPE_ROM) { + return scsi_req_alloc(&scsi_disk_dma_reqops, &s->qdev, tag, lun, + hba_private); + } } return scsi_req_alloc(&scsi_generic_req_ops, &s->qdev, tag, lun, From 314a329906d5238ab5993da95516fc5c40f58f54 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 8 Feb 2012 10:26:21 +0100 Subject: [PATCH 12/32] scsi-disk: separate read_data/write_data implementation for emulate_reqops The previous patch only separated the send_command callback. Use different implementations also for read_data and write_data. The latter is still unreachable, so it aborts for now. read_data passes the data buffer that was prepared and completes the command. Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index ea0b05e48a..1633177e00 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -343,13 +343,6 @@ static void scsi_read_data(SCSIRequest *req) SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); bool first; - if (r->sector_count == (uint32_t)-1) { - DPRINTF("Read buf_len=%zd\n", r->iov.iov_len); - r->sector_count = 0; - r->started = true; - scsi_req_data(&r->req, r->iov.iov_len); - return; - } DPRINTF("Read sector_count=%d\n", r->sector_count); if (r->sector_count == 0) { /* This also clears the sense buffer for REQUEST SENSE. */ @@ -1262,6 +1255,28 @@ static int scsi_disk_emulate_start_stop(SCSIDiskReq *r) return 0; } +static void scsi_disk_emulate_read_data(SCSIRequest *req) +{ + SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); + int buflen = r->iov.iov_len; + + if (buflen) { + DPRINTF("Read buf_len=%zd\n", buflen); + r->iov.iov_len = 0; + r->started = true; + scsi_req_data(&r->req, buflen); + return; + } + + /* This also clears the sense buffer for REQUEST SENSE. */ + scsi_req_complete(&r->req, GOOD); +} + +static void scsi_disk_emulate_write_data(SCSIRequest *req) +{ + abort(); +} + static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) { SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); @@ -1532,9 +1547,8 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) scsi_check_condition(r, SENSE_CODE(INVALID_OPCODE)); return 0; } - assert(!r->req.aiocb && r->sector_count == 0); + assert(!r->req.aiocb); r->iov.iov_len = MIN(buflen, req->cmd.xfer); - r->sector_count = -1; if (r->iov.iov_len == 0) { scsi_req_complete(&r->req, GOOD); } @@ -1785,8 +1799,8 @@ static const SCSIReqOps scsi_disk_emulate_reqops = { .size = sizeof(SCSIDiskReq), .free_req = scsi_free_request, .send_command = scsi_disk_emulate_command, - .read_data = scsi_read_data, - .write_data = scsi_write_data, + .read_data = scsi_disk_emulate_read_data, + .write_data = scsi_disk_emulate_write_data, .get_buf = scsi_get_buf, }; From af6d510ddbef5e0be9ce40464c2bf46da58eacf2 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 8 Feb 2012 10:32:55 +0100 Subject: [PATCH 13/32] scsi-disk: support emulated TO_DEV requests This adds the implementation of write_data for the emulated command case. The first time through it asks for more data, the second time it finishes the processing of the command. MODE SELECT and MODE SELECT(10) can now be re-enabled, but they will not do much. Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 1633177e00..b8d60862cb 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1274,7 +1274,26 @@ static void scsi_disk_emulate_read_data(SCSIRequest *req) static void scsi_disk_emulate_write_data(SCSIRequest *req) { - abort(); + SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); + + if (r->iov.iov_len) { + int buflen = r->iov.iov_len; + DPRINTF("Write buf_len=%zd\n", buflen); + r->iov.iov_len = 0; + scsi_req_data(&r->req, buflen); + return; + } + + switch (req->cmd.buf[0]) { + case MODE_SELECT: + case MODE_SELECT_10: + /* This also clears the sense buffer for REQUEST SENSE. */ + scsi_req_complete(&r->req, GOOD); + break; + + default: + abort(); + } } static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) @@ -1283,7 +1302,7 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev); uint64_t nb_sectors; uint8_t *outbuf; - int buflen = 0; + int buflen; switch (req->cmd.buf[0]) { case INQUIRY: @@ -1309,7 +1328,6 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) break; } - assert(req->cmd.mode != SCSI_XFER_TO_DEV); if (!r->iov.iov_base) { /* * FIXME: we shouldn't return anything bigger than 4k, but the code @@ -1326,6 +1344,7 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) r->iov.iov_base = qemu_blockalign(s->qdev.conf.bs, r->buflen); } + buflen = req->cmd.xfer; outbuf = r->iov.iov_base; switch (req->cmd.buf[0]) { case TEST_UNIT_READY: @@ -1500,7 +1519,6 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) goto illegal_lba; } break; -#if 0 case MODE_SELECT: DPRINTF("Mode Select(6) (len %lu)\n", (long)r->req.cmd.xfer); /* We don't support mode parameter changes. @@ -1517,7 +1535,6 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) goto illegal_request; } break; -#endif case WRITE_SAME_10: nb_sectors = lduw_be_p(&req->cmd.buf[7]); goto write_same; @@ -1552,7 +1569,12 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) if (r->iov.iov_len == 0) { scsi_req_complete(&r->req, GOOD); } - return r->iov.iov_len; + if (r->req.cmd.mode == SCSI_XFER_TO_DEV) { + assert(r->iov.iov_len == req->cmd.xfer); + return -r->iov.iov_len; + } else { + return r->iov.iov_len; + } illegal_request: if (r->req.status == -1) { @@ -1834,10 +1856,8 @@ static const SCSIReqOps *const scsi_disk_reqops_dispatch[256] = { [REQUEST_SENSE] = &scsi_disk_emulate_reqops, [SYNCHRONIZE_CACHE] = &scsi_disk_emulate_reqops, [SEEK_10] = &scsi_disk_emulate_reqops, -#if 0 [MODE_SELECT] = &scsi_disk_emulate_reqops, [MODE_SELECT_10] = &scsi_disk_emulate_reqops, -#endif [WRITE_SAME_10] = &scsi_disk_emulate_reqops, [WRITE_SAME_16] = &scsi_disk_emulate_reqops, From ef405611168015cfd28669e04506bb99d7c39288 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 12 Jul 2012 16:08:24 +0200 Subject: [PATCH 14/32] scsi-disk: adjust offsets in MODE SENSE by 2 This will make offsets the same when implementing MODE SELECT. This is because MODE SELECT has to deal with both 2-byte and 4-byte headers. Unfortunately, this means that the offsets are now off by two compared to the descriptions in the SCSI specs, which include the header. Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 140 ++++++++++++++++++++++++++----------------------- 1 file changed, 74 insertions(+), 66 deletions(-) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index b8d60862cb..dca98664a6 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -959,148 +959,156 @@ static int mode_sense_page(SCSIDiskState *s, int page, uint8_t **p_outbuf, [MODE_PAGE_AUDIO_CTL] = (1 << TYPE_ROM), [MODE_PAGE_CAPABILITIES] = (1 << TYPE_ROM), }; - uint8_t *p = *p_outbuf; + + uint8_t *p = *p_outbuf + 2; + int length; if ((mode_sense_valid[page] & (1 << s->qdev.type)) == 0) { return -1; } - p[0] = page; - /* * If Changeable Values are requested, a mask denoting those mode parameters * that are changeable shall be returned. As we currently don't support * parameter changes via MODE_SELECT all bits are returned set to zero. * The buffer was already menset to zero by the caller of this function. + * + * The offsets here are off by two compared to the descriptions in the + * SCSI specs, because those include a 2-byte header. This is unfortunate, + * but it is done so that offsets are consistent within our implementation + * of MODE SENSE and MODE SELECT. MODE SELECT has to deal with both + * 2-byte and 4-byte headers. */ switch (page) { case MODE_PAGE_HD_GEOMETRY: - p[1] = 0x16; + length = 0x16; if (page_control == 1) { /* Changeable Values */ break; } /* if a geometry hint is available, use it */ - p[2] = (s->qdev.conf.cyls >> 16) & 0xff; - p[3] = (s->qdev.conf.cyls >> 8) & 0xff; - p[4] = s->qdev.conf.cyls & 0xff; - p[5] = s->qdev.conf.heads & 0xff; + p[0] = (s->qdev.conf.cyls >> 16) & 0xff; + p[1] = (s->qdev.conf.cyls >> 8) & 0xff; + p[2] = s->qdev.conf.cyls & 0xff; + p[3] = s->qdev.conf.heads & 0xff; /* Write precomp start cylinder, disabled */ - p[6] = (s->qdev.conf.cyls >> 16) & 0xff; - p[7] = (s->qdev.conf.cyls >> 8) & 0xff; - p[8] = s->qdev.conf.cyls & 0xff; + p[4] = (s->qdev.conf.cyls >> 16) & 0xff; + p[5] = (s->qdev.conf.cyls >> 8) & 0xff; + p[6] = s->qdev.conf.cyls & 0xff; /* Reduced current start cylinder, disabled */ - p[9] = (s->qdev.conf.cyls >> 16) & 0xff; - p[10] = (s->qdev.conf.cyls >> 8) & 0xff; - p[11] = s->qdev.conf.cyls & 0xff; + p[7] = (s->qdev.conf.cyls >> 16) & 0xff; + p[8] = (s->qdev.conf.cyls >> 8) & 0xff; + p[9] = s->qdev.conf.cyls & 0xff; /* Device step rate [ns], 200ns */ - p[12] = 0; - p[13] = 200; + p[10] = 0; + p[11] = 200; /* Landing zone cylinder */ + p[12] = 0xff; + p[13] = 0xff; 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[18] = (5400 >> 8) & 0xff; + p[19] = 5400 & 0xff; break; case MODE_PAGE_FLEXIBLE_DISK_GEOMETRY: - p[1] = 0x1e; + length = 0x1e; if (page_control == 1) { /* Changeable Values */ break; } /* Transfer rate [kbit/s], 5Mbit/s */ - p[2] = 5000 >> 8; - p[3] = 5000 & 0xff; + p[0] = 5000 >> 8; + p[1] = 5000 & 0xff; /* if a geometry hint is available, use it */ - p[4] = s->qdev.conf.heads & 0xff; - p[5] = s->qdev.conf.secs & 0xff; - p[6] = s->qdev.blocksize >> 8; + p[2] = s->qdev.conf.heads & 0xff; + p[3] = s->qdev.conf.secs & 0xff; + p[4] = s->qdev.blocksize >> 8; + p[6] = (s->qdev.conf.cyls >> 8) & 0xff; + p[7] = s->qdev.conf.cyls & 0xff; + /* Write precomp start cylinder, disabled */ p[8] = (s->qdev.conf.cyls >> 8) & 0xff; p[9] = s->qdev.conf.cyls & 0xff; - /* Write precomp start cylinder, disabled */ + /* Reduced current start cylinder, disabled */ p[10] = (s->qdev.conf.cyls >> 8) & 0xff; p[11] = s->qdev.conf.cyls & 0xff; - /* Reduced current start cylinder, disabled */ - p[12] = (s->qdev.conf.cyls >> 8) & 0xff; - p[13] = s->qdev.conf.cyls & 0xff; /* Device step rate [100us], 100us */ - p[14] = 0; - p[15] = 1; + p[12] = 0; + p[13] = 1; /* Device step pulse width [us], 1us */ - p[16] = 1; + p[14] = 1; /* Device head settle delay [100us], 100us */ - p[17] = 0; - p[18] = 1; + p[15] = 0; + p[16] = 1; /* Motor on delay [0.1s], 0.1s */ - p[19] = 1; + p[17] = 1; /* Motor off delay [0.1s], 0.1s */ - p[20] = 1; + p[18] = 1; /* Medium rotation rate [rpm], 5400 rpm */ - p[28] = (5400 >> 8) & 0xff; - p[29] = 5400 & 0xff; + p[26] = (5400 >> 8) & 0xff; + p[27] = 5400 & 0xff; break; case MODE_PAGE_CACHING: - p[0] = 8; - p[1] = 0x12; + length = 0x12; if (page_control == 1) { /* Changeable Values */ break; } if (bdrv_enable_write_cache(s->qdev.conf.bs)) { - p[2] = 4; /* WCE */ + p[0] = 4; /* WCE */ } break; case MODE_PAGE_R_W_ERROR: - p[1] = 10; - p[2] = 0x80; /* Automatic Write Reallocation Enabled */ + length = 10; + p[0] = 0x80; /* Automatic Write Reallocation Enabled */ if (s->qdev.type == TYPE_ROM) { - p[3] = 0x20; /* Read Retry Count */ + p[1] = 0x20; /* Read Retry Count */ } break; case MODE_PAGE_AUDIO_CTL: - p[1] = 14; + length = 14; break; case MODE_PAGE_CAPABILITIES: - p[1] = 0x14; + length = 0x14; if (page_control == 1) { /* Changeable Values */ break; } - p[2] = 0x3b; /* CD-R & CD-RW read */ - p[3] = 0; /* Writing not supported */ - p[4] = 0x7f; /* Audio, composite, digital out, + p[0] = 0x3b; /* CD-R & CD-RW read */ + p[1] = 0; /* Writing not supported */ + p[2] = 0x7f; /* Audio, composite, digital out, mode 2 form 1&2, multi session */ - p[5] = 0xff; /* CD DA, DA accurate, RW supported, + p[3] = 0xff; /* CD DA, DA accurate, RW supported, RW corrected, C2 errors, ISRC, UPC, Bar code */ - p[6] = 0x2d | (s->tray_locked ? 2 : 0); + p[4] = 0x2d | (s->tray_locked ? 2 : 0); /* Locking supported, jumper present, eject, tray */ - p[7] = 0; /* no volume & mute control, no + p[5] = 0; /* no volume & mute control, no changer */ - p[8] = (50 * 176) >> 8; /* 50x read speed */ - p[9] = (50 * 176) & 0xff; - p[10] = 2 >> 8; /* Two volume levels */ - p[11] = 2 & 0xff; - p[12] = 2048 >> 8; /* 2M buffer */ - p[13] = 2048 & 0xff; - p[14] = (16 * 176) >> 8; /* 16x read speed current */ - p[15] = (16 * 176) & 0xff; - p[18] = (16 * 176) >> 8; /* 16x write speed */ + p[6] = (50 * 176) >> 8; /* 50x read speed */ + p[7] = (50 * 176) & 0xff; + p[8] = 2 >> 8; /* Two volume levels */ + p[9] = 2 & 0xff; + p[10] = 2048 >> 8; /* 2M buffer */ + p[11] = 2048 & 0xff; + p[12] = (16 * 176) >> 8; /* 16x read speed current */ + p[13] = (16 * 176) & 0xff; + p[16] = (16 * 176) >> 8; /* 16x write speed */ + p[17] = (16 * 176) & 0xff; + p[18] = (16 * 176) >> 8; /* 16x write speed current */ p[19] = (16 * 176) & 0xff; - p[20] = (16 * 176) >> 8; /* 16x write speed current */ - p[21] = (16 * 176) & 0xff; break; default: return -1; } - *p_outbuf += p[1] + 2; - return p[1] + 2; + assert(length < 256); + (*p_outbuf)[0] = page; + (*p_outbuf)[1] = length; + *p_outbuf += length + 2; + return length + 2; } static int scsi_disk_emulate_mode_sense(SCSIDiskReq *r, uint8_t *outbuf) From 4f588b151127a2556ad0c52158f98cfe1a9c0cfa Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 10 Jul 2012 14:04:51 +0200 Subject: [PATCH 15/32] scsi-disk: fix changeable values for MODE_PAGE_R_W_ERROR The changeable values were not all-zeros for this mode page, fix it. Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index dca98664a6..70154200c1 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1059,6 +1059,9 @@ static int mode_sense_page(SCSIDiskState *s, int page, uint8_t **p_outbuf, case MODE_PAGE_R_W_ERROR: length = 10; + if (page_control == 1) { /* Changeable Values */ + break; + } p[0] = 0x80; /* Automatic Write Reallocation Enabled */ if (s->qdev.type == TYPE_ROM) { p[1] = 0x20; /* Read Retry Count */ From 380feaffb0fcc8e5f615ed8e86d2e93717a6f2c6 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 10 Jul 2012 15:02:55 +0200 Subject: [PATCH 16/32] scsi-disk: parse MODE SELECT commands and parameters This adds the bulk of the parsing code for MODE SELECT, including breaking out changes to different mode pages, and checking that only changeable values are modified. In order to report errors correctly two passes are made through the parameters; the first only looks for errors, the second actually applies the changes to the mode page. Signed-off-by: Paolo Bonzini --- hw/scsi-bus.c | 10 +++ hw/scsi-disk.c | 166 +++++++++++++++++++++++++++++++++++++++++++++---- hw/scsi.h | 4 ++ 3 files changed, 169 insertions(+), 11 deletions(-) diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index dc7406389d..efbda6f877 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -1112,6 +1112,16 @@ const struct SCSISense sense_code_INVALID_FIELD = { .key = ILLEGAL_REQUEST, .asc = 0x24, .ascq = 0x00 }; +/* Illegal request, Invalid field in parameter list */ +const struct SCSISense sense_code_INVALID_PARAM = { + .key = ILLEGAL_REQUEST, .asc = 0x26, .ascq = 0x00 +}; + +/* Illegal request, Parameter list length error */ +const struct SCSISense sense_code_INVALID_PARAM_LEN = { + .key = ILLEGAL_REQUEST, .asc = 0x1a, .ascq = 0x00 +}; + /* Illegal request, LUN not supported */ const struct SCSISense sense_code_LUN_NOT_SUPPORTED = { .key = ILLEGAL_REQUEST, .asc = 0x25, .ascq = 0x00 diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 70154200c1..ad6c00ddcf 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -43,6 +43,7 @@ do { printf("scsi-disk: " fmt , ## __VA_ARGS__); } while (0) #define SCSI_DMA_BUF_SIZE 131072 #define SCSI_MAX_INQUIRY_LEN 256 +#define SCSI_MAX_MODE_LEN 256 typedef struct SCSIDiskState SCSIDiskState; @@ -1283,6 +1284,159 @@ static void scsi_disk_emulate_read_data(SCSIRequest *req) scsi_req_complete(&r->req, GOOD); } +static int scsi_disk_check_mode_select(SCSIDiskState *s, int page, + uint8_t *inbuf, int inlen) +{ + uint8_t mode_current[SCSI_MAX_MODE_LEN]; + uint8_t mode_changeable[SCSI_MAX_MODE_LEN]; + uint8_t *p; + int len, expected_len, changeable_len, i; + + /* The input buffer does not include the page header, so it is + * off by 2 bytes. + */ + expected_len = inlen + 2; + if (expected_len > SCSI_MAX_MODE_LEN) { + return -1; + } + + p = mode_current; + memset(mode_current, 0, inlen + 2); + len = mode_sense_page(s, page, &p, 0); + if (len < 0 || len != expected_len) { + return -1; + } + + p = mode_changeable; + memset(mode_changeable, 0, inlen + 2); + changeable_len = mode_sense_page(s, page, &p, 1); + assert(changeable_len == len); + + /* Check that unchangeable bits are the same as what MODE SENSE + * would return. + */ + for (i = 2; i < len; i++) { + if (((mode_current[i] ^ inbuf[i - 2]) & ~mode_changeable[i]) != 0) { + return -1; + } + } + return 0; +} + +static void scsi_disk_apply_mode_select(SCSIDiskState *s, int page, uint8_t *p) +{ +} + +static int mode_select_pages(SCSIDiskReq *r, uint8_t *p, int len, bool change) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); + + while (len > 0) { + int page, subpage, page_len; + + /* Parse both possible formats for the mode page headers. */ + page = p[0] & 0x3f; + if (p[0] & 0x40) { + if (len < 4) { + goto invalid_param_len; + } + subpage = p[1]; + page_len = lduw_be_p(&p[2]); + p += 4; + len -= 4; + } else { + if (len < 2) { + goto invalid_param_len; + } + subpage = 0; + page_len = p[1]; + p += 2; + len -= 2; + } + + if (subpage) { + goto invalid_param; + } + if (page_len > len) { + goto invalid_param_len; + } + + if (!change) { + if (scsi_disk_check_mode_select(s, page, p, page_len) < 0) { + goto invalid_param; + } + } else { + scsi_disk_apply_mode_select(s, page, p); + } + + p += page_len; + len -= page_len; + } + return 0; + +invalid_param: + scsi_check_condition(r, SENSE_CODE(INVALID_PARAM)); + return -1; + +invalid_param_len: + scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN)); + return -1; +} + +static void scsi_disk_emulate_mode_select(SCSIDiskReq *r, uint8_t *inbuf) +{ + uint8_t *p = inbuf; + int cmd = r->req.cmd.buf[0]; + int len = r->req.cmd.xfer; + int hdr_len = (cmd == MODE_SELECT ? 4 : 8); + int bd_len; + int pass; + + /* We only support PF=1, SP=0. */ + if ((r->req.cmd.buf[1] & 0x11) != 0x10) { + goto invalid_field; + } + + if (len < hdr_len) { + goto invalid_param_len; + } + + bd_len = (cmd == MODE_SELECT ? p[3] : lduw_be_p(&p[6])); + len -= hdr_len; + p += hdr_len; + if (len < bd_len) { + goto invalid_param_len; + } + if (bd_len != 0 && bd_len != 8) { + goto invalid_param; + } + + len -= bd_len; + p += bd_len; + + /* Ensure no change is made if there is an error! */ + for (pass = 0; pass < 2; pass++) { + if (mode_select_pages(r, p, len, pass == 1) < 0) { + assert(pass == 0); + return; + } + } + scsi_req_complete(&r->req, GOOD); + return; + +invalid_param: + scsi_check_condition(r, SENSE_CODE(INVALID_PARAM)); + return; + +invalid_param_len: + scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN)); + return; + +invalid_field: + scsi_check_condition(r, SENSE_CODE(INVALID_FIELD)); + return; +} + static void scsi_disk_emulate_write_data(SCSIRequest *req) { SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); @@ -1299,7 +1453,7 @@ static void scsi_disk_emulate_write_data(SCSIRequest *req) case MODE_SELECT: case MODE_SELECT_10: /* This also clears the sense buffer for REQUEST SENSE. */ - scsi_req_complete(&r->req, GOOD); + scsi_disk_emulate_mode_select(r, r->iov.iov_base); break; default: @@ -1532,19 +1686,9 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) break; case MODE_SELECT: DPRINTF("Mode Select(6) (len %lu)\n", (long)r->req.cmd.xfer); - /* We don't support mode parameter changes. - Allow the mode parameter header + block descriptors only. */ - if (r->req.cmd.xfer > 12) { - goto illegal_request; - } break; case MODE_SELECT_10: DPRINTF("Mode Select(10) (len %lu)\n", (long)r->req.cmd.xfer); - /* We don't support mode parameter changes. - Allow the mode parameter header + block descriptors only. */ - if (r->req.cmd.xfer > 16) { - goto illegal_request; - } break; case WRITE_SAME_10: nb_sectors = lduw_be_p(&req->cmd.buf[7]); diff --git a/hw/scsi.h b/hw/scsi.h index ea8a15567d..e2fb8a4870 100644 --- a/hw/scsi.h +++ b/hw/scsi.h @@ -180,6 +180,10 @@ extern const struct SCSISense sense_code_INVALID_OPCODE; extern const struct SCSISense sense_code_LBA_OUT_OF_RANGE; /* Illegal request, Invalid field in CDB */ extern const struct SCSISense sense_code_INVALID_FIELD; +/* Illegal request, Invalid field in parameter list */ +extern const struct SCSISense sense_code_INVALID_PARAM; +/* Illegal request, Parameter list length error */ +extern const struct SCSISense sense_code_INVALID_PARAM_LEN; /* Illegal request, LUN not supported */ extern const struct SCSISense sense_code_LUN_NOT_SUPPORTED; /* Illegal request, Saving parameters not supported */ From 96c91bbf6090b3098db22ed19ab5ae2f9c6e4f16 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 10 Jul 2012 15:03:17 +0200 Subject: [PATCH 17/32] scsi-disk: support toggling the write cache Finally, this uses the "plumbing" in the previous patch to add support for toggling the WCE bit of the caching mode page. Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index ad6c00ddcf..fd5cd709a9 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1050,10 +1050,8 @@ static int mode_sense_page(SCSIDiskState *s, int page, uint8_t **p_outbuf, case MODE_PAGE_CACHING: length = 0x12; - if (page_control == 1) { /* Changeable Values */ - break; - } - if (bdrv_enable_write_cache(s->qdev.conf.bs)) { + if (page_control == 1 || /* Changeable Values */ + bdrv_enable_write_cache(s->qdev.conf.bs)) { p[0] = 4; /* WCE */ } break; @@ -1325,6 +1323,14 @@ static int scsi_disk_check_mode_select(SCSIDiskState *s, int page, static void scsi_disk_apply_mode_select(SCSIDiskState *s, int page, uint8_t *p) { + switch (page) { + case MODE_PAGE_CACHING: + bdrv_set_enable_write_cache(s->qdev.conf.bs, (p[0] & 4) != 0); + break; + + default: + break; + } } static int mode_select_pages(SCSIDiskReq *r, uint8_t *p, int len, bool change) From 96bdbbab55976b106f9db2b61042ebf5f0493e5a Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 12 Jul 2012 10:19:38 +1000 Subject: [PATCH 18/32] scsi-disk: rd/wr/vr-protect !=0 is an error The QEMU SCSI emulation does not support protection information, so any READ/WRITE/VERIFY commands that has the protect bits set to non-zero should fail with ILLEGAL_REQUEST/INVALID_FIELD_IN_CDB From SCSI SBC : If the logical unit does not support protection information, then the device server should terminate the command with CHECK CONDITION status with the sense key set to ILLEGAL REQUEST and the additional sense code set to INVALID FIELD IN CDB. Signed-off-by: Ronnie Sahlberg [ Rebase after scsi_dma_reqops introduction - Paolo ] Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index fd5cd709a9..526da4b4fc 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1774,6 +1774,9 @@ static int32_t scsi_disk_dma_command(SCSIRequest *req, uint8_t *buf) case READ_16: len = r->req.cmd.xfer / s->qdev.blocksize; DPRINTF("Read (sector %" PRId64 ", count %d)\n", r->req.cmd.lba, len); + if (r->req.cmd.buf[1] & 0xe0) { + goto illegal_request; + } if (r->req.cmd.lba > s->qdev.max_lba) { goto illegal_lba; } @@ -1794,6 +1797,9 @@ static int32_t scsi_disk_dma_command(SCSIRequest *req, uint8_t *buf) DPRINTF("Write %s(sector %" PRId64 ", count %d)\n", (command & 0xe) == 0xe ? "And Verify " : "", r->req.cmd.lba, len); + if (r->req.cmd.buf[1] & 0xe0) { + goto illegal_request; + } if (r->req.cmd.lba > s->qdev.max_lba) { goto illegal_lba; } @@ -1802,6 +1808,9 @@ static int32_t scsi_disk_dma_command(SCSIRequest *req, uint8_t *buf) break; default: abort(); + illegal_request: + scsi_check_condition(r, SENSE_CODE(INVALID_FIELD)); + return 0; illegal_lba: scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE)); return 0; From ba6095cd6b497783e3621606b6b2320781aa3f52 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Thu, 12 Jul 2012 16:52:47 +1000 Subject: [PATCH 19/32] scsi-disk: improve the lba-out-of-range tests for read/write/verify Improve the tests for the LBA to cover more cases. For the 16 byte opcodes, the lba is a uint64, so we need to check is to make sure that we do not wrap. For example if an opcode would specify the LBA:0xffffffffffffffff and LEN:2 then lba+len would wrap to 1. Also verify that ALL requested blocks are available, not just the first one. Signed-off-by: Ronnie Sahlberg Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 526da4b4fc..3c03159c1f 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1777,7 +1777,8 @@ static int32_t scsi_disk_dma_command(SCSIRequest *req, uint8_t *buf) if (r->req.cmd.buf[1] & 0xe0) { goto illegal_request; } - if (r->req.cmd.lba > s->qdev.max_lba) { + if (r->req.cmd.lba > r->req.cmd.lba + len || + r->req.cmd.lba + len - 1 > s->qdev.max_lba) { goto illegal_lba; } r->sector = r->req.cmd.lba * (s->qdev.blocksize / 512); @@ -1800,7 +1801,8 @@ static int32_t scsi_disk_dma_command(SCSIRequest *req, uint8_t *buf) if (r->req.cmd.buf[1] & 0xe0) { goto illegal_request; } - if (r->req.cmd.lba > s->qdev.max_lba) { + if (r->req.cmd.lba > r->req.cmd.lba + len || + r->req.cmd.lba + len - 1 > s->qdev.max_lba) { goto illegal_lba; } r->sector = r->req.cmd.lba * (s->qdev.blocksize / 512); From 6a8a685c4d022d4edee57b0fb78c0ae1b4876478 Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg Date: Mon, 16 Jul 2012 08:53:28 +0200 Subject: [PATCH 20/32] scsi-disk: Fail medium writes with proper sense for readonly LUNs Add sense code for DATA_PROTECT/WRITE_PROTECTED and return this error for any WRITE*/WRITE_VERIFY* calls if the device is readonly=on, i.e. write-protected Signed-off-by: Ronnie Sahlberg Signed-off-by: Paolo Bonzini --- hw/scsi-bus.c | 5 +++++ hw/scsi-disk.c | 15 ++++++++++++--- hw/scsi.h | 2 ++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index efbda6f877..dd0cdd0570 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -1182,6 +1182,11 @@ const struct SCSISense sense_code_DEVICE_INTERNAL_RESET = { .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x04 }; +/* Data Protection, Write Protected */ +const struct SCSISense sense_code_WRITE_PROTECTED = { + .key = DATA_PROTECT, .asc = 0x27, .ascq = 0x00 +}; + /* * scsi_build_sense * diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 3c03159c1f..fb0540a3a4 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1702,6 +1702,10 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) case WRITE_SAME_16: nb_sectors = ldl_be_p(&req->cmd.buf[10]) & 0xffffffffULL; write_same: + if (bdrv_is_read_only(s->qdev.conf.bs)) { + scsi_check_condition(r, SENSE_CODE(WRITE_PROTECTED)); + return 0; + } if (r->req.cmd.lba > s->qdev.max_lba) { goto illegal_lba; } @@ -1784,9 +1788,6 @@ static int32_t scsi_disk_dma_command(SCSIRequest *req, uint8_t *buf) r->sector = r->req.cmd.lba * (s->qdev.blocksize / 512); r->sector_count = len * (s->qdev.blocksize / 512); break; - case VERIFY_10: - case VERIFY_12: - case VERIFY_16: case WRITE_6: case WRITE_10: case WRITE_12: @@ -1794,6 +1795,14 @@ static int32_t scsi_disk_dma_command(SCSIRequest *req, uint8_t *buf) case WRITE_VERIFY_10: case WRITE_VERIFY_12: case WRITE_VERIFY_16: + if (bdrv_is_read_only(s->qdev.conf.bs)) { + scsi_check_condition(r, SENSE_CODE(WRITE_PROTECTED)); + return 0; + } + /* fallthrough */ + case VERIFY_10: + case VERIFY_12: + case VERIFY_16: len = r->req.cmd.xfer / s->qdev.blocksize; DPRINTF("Write %s(sector %" PRId64 ", count %d)\n", (command & 0xe) == 0xe ? "And Verify " : "", diff --git a/hw/scsi.h b/hw/scsi.h index e2fb8a4870..fc9dc50e43 100644 --- a/hw/scsi.h +++ b/hw/scsi.h @@ -208,6 +208,8 @@ extern const struct SCSISense sense_code_MEDIUM_CHANGED; extern const struct SCSISense sense_code_REPORTED_LUNS_CHANGED; /* Unit attention, Device internal reset */ extern const struct SCSISense sense_code_DEVICE_INTERNAL_RESET; +/* Data Protection, Write Protected */ +extern const struct SCSISense sense_code_WRITE_PROTECTED; #define SENSE_CODE(x) sense_code_ ## x From b456a71c4a1eb5704d135fd08da9a0de8fd81231 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 16 Jul 2012 15:59:57 +0200 Subject: [PATCH 21/32] scsi-disk: removable hard disks support load/eject Support for the LOEJ bit of the START/STOP UNIT command right now is limited to CD-ROMs. This is wrong, since removable hard disks (in the real world: SD card readers) also support it in pretty much the same way. Without the LOEJ bit, START/STOP UNIT does nothing for all devices. Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index fb0540a3a4..b52e30448a 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1248,7 +1248,7 @@ static int scsi_disk_emulate_start_stop(SCSIDiskReq *r) bool start = req->cmd.buf[4] & 1; bool loej = req->cmd.buf[4] & 2; /* load on start, eject on !start */ - if (s->qdev.type == TYPE_ROM && loej) { + if ((s->features & (1 << SCSI_DISK_F_REMOVABLE)) && loej) { if (!start && !s->tray_open && s->tray_locked) { scsi_check_condition(r, bdrv_is_inserted(s->qdev.conf.bs) From 814589c450440da2fd7567aafaf6259bc8ea02f4 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 27 Jul 2012 08:23:07 +0200 Subject: [PATCH 22/32] scsi: add tracepoint for scsi_req_cancel Signed-off-by: Paolo Bonzini --- hw/scsi-bus.c | 1 + trace-events | 1 + 2 files changed, 2 insertions(+) diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index dd0cdd0570..543d9d2649 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -1496,6 +1496,7 @@ void scsi_req_complete(SCSIRequest *req, int status) void scsi_req_cancel(SCSIRequest *req) { + trace_scsi_req_cancel(req->dev->id, req->lun, req->tag); if (!req->enqueued) { return; } diff --git a/trace-events b/trace-events index 6fb5eb547e..7aa28b1b3e 100644 --- a/trace-events +++ b/trace-events @@ -403,6 +403,7 @@ usb_host_parse_error(int bus, int addr, const char *errmsg) "dev %d:%d, msg %s" # hw/scsi-bus.c scsi_req_alloc(int target, int lun, int tag) "target %d lun %d tag %d" +scsi_req_cancel(int target, int lun, int tag) "target %d lun %d tag %d" scsi_req_data(int target, int lun, int tag, int len) "target %d lun %d tag %d len %d" scsi_req_data_canceled(int target, int lun, int tag, int len) "target %d lun %d tag %d len %d" scsi_req_dequeue(int target, int lun, int tag) "target %d lun %d tag %d" From 350e6e419902991b073b313aa65b240d1024d57e Mon Sep 17 00:00:00 2001 From: Cong Meng Date: Wed, 20 Jun 2012 14:47:10 +0800 Subject: [PATCH 23/32] scsi: introduce hotplug() and hot_unplug() interfaces for SCSI bus Add two interfaces hotplug() and hot_unplug() to scsi bus info. The scsi bus can implement these two interfaces to signal the HBA driver of guest kernel to add/remove the scsi device in question. Signed-off-by: Sen Wang Signed-off-by: Cong Meng [ Fixed braces and indentation - Paolo ] Signed-off-by: Paolo Bonzini --- hw/scsi-bus.c | 17 ++++++++++++++++- hw/scsi.h | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index 543d9d2649..6ee0c10028 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -186,6 +186,10 @@ static int scsi_qdev_init(DeviceState *qdev) dev); } + if (bus->info->hotplug) { + bus->info->hotplug(bus, dev); + } + err: return rc; } @@ -1650,6 +1654,17 @@ static int get_scsi_requests(QEMUFile *f, void *pv, size_t size) return 0; } +static int scsi_qdev_unplug(DeviceState *qdev) +{ + SCSIDevice *dev = SCSI_DEVICE(qdev); + SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus); + + if (bus->info->hot_unplug) { + bus->info->hot_unplug(bus, dev); + } + return qdev_simple_unplug_cb(qdev); +} + static const VMStateInfo vmstate_info_scsi_requests = { .name = "scsi-requests", .get = get_scsi_requests, @@ -1686,7 +1701,7 @@ static void scsi_device_class_init(ObjectClass *klass, void *data) DeviceClass *k = DEVICE_CLASS(klass); k->bus_type = TYPE_SCSI_BUS; k->init = scsi_qdev_init; - k->unplug = qdev_simple_unplug_cb; + k->unplug = scsi_qdev_unplug; k->exit = scsi_qdev_exit; k->props = scsi_props; } diff --git a/hw/scsi.h b/hw/scsi.h index fc9dc50e43..088d519b67 100644 --- a/hw/scsi.h +++ b/hw/scsi.h @@ -131,6 +131,8 @@ struct SCSIBusInfo { void (*transfer_data)(SCSIRequest *req, uint32_t arg); void (*complete)(SCSIRequest *req, uint32_t arg, size_t resid); void (*cancel)(SCSIRequest *req); + void (*hotplug)(SCSIBus *bus, SCSIDevice *dev); + void (*hot_unplug)(SCSIBus *bus, SCSIDevice *dev); QEMUSGList *(*get_sg_list)(SCSIRequest *req); void (*save_request)(QEMUFile *f, SCSIRequest *req); From e48e84ea80cb2e7fe6e48196ce187cfba6e3eb2c Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 16 Jul 2012 14:18:58 +0200 Subject: [PATCH 24/32] scsi: establish precedence levels for unit attention When a device is resized, we will report a unit attention condition for CAPACITY DATA HAS CHANGED. However, we should ensure that this condition does not override a more important unit attention condition. Signed-off-by: Paolo Bonzini --- hw/scsi-bus.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++++- hw/scsi-disk.c | 4 ++-- hw/scsi.h | 1 + trace-events | 1 + 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index 6ee0c10028..c38c0ec574 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -1531,6 +1531,55 @@ void scsi_req_abort(SCSIRequest *req, int status) scsi_req_unref(req); } +static int scsi_ua_precedence(SCSISense sense) +{ + if (sense.key != UNIT_ATTENTION) { + return INT_MAX; + } + if (sense.asc == 0x29 && sense.ascq == 0x04) { + /* DEVICE INTERNAL RESET goes with POWER ON OCCURRED */ + return 1; + } else if (sense.asc == 0x3F && sense.ascq == 0x01) { + /* MICROCODE HAS BEEN CHANGED goes with SCSI BUS RESET OCCURRED */ + return 2; + } else if (sense.asc == 0x29 && (sense.ascq == 0x05 || sense.ascq == 0x06)) { + /* These two go with "all others". */ + ; + } else if (sense.asc == 0x29 && sense.ascq <= 0x07) { + /* POWER ON, RESET OR BUS DEVICE RESET OCCURRED = 0 + * POWER ON OCCURRED = 1 + * SCSI BUS RESET OCCURRED = 2 + * BUS DEVICE RESET FUNCTION OCCURRED = 3 + * I_T NEXUS LOSS OCCURRED = 7 + */ + return sense.ascq; + } else if (sense.asc == 0x2F && sense.ascq == 0x01) { + /* COMMANDS CLEARED BY POWER LOSS NOTIFICATION */ + return 8; + } + return (sense.asc << 8) | sense.ascq; +} + +void scsi_device_set_ua(SCSIDevice *sdev, SCSISense sense) +{ + int prec1, prec2; + if (sense.key != UNIT_ATTENTION) { + return; + } + trace_scsi_device_set_ua(sdev->id, sdev->lun, sense.key, + sense.asc, sense.ascq); + + /* + * Override a pre-existing unit attention condition, except for a more + * important reset condition. + */ + prec1 = scsi_ua_precedence(sdev->unit_attention); + prec2 = scsi_ua_precedence(sense); + if (prec2 < prec1) { + sdev->unit_attention = sense; + } +} + void scsi_device_purge_requests(SCSIDevice *sdev, SCSISense sense) { SCSIRequest *req; @@ -1539,7 +1588,8 @@ void scsi_device_purge_requests(SCSIDevice *sdev, SCSISense sense) req = QTAILQ_FIRST(&sdev->requests); scsi_req_cancel(req); } - sdev->unit_attention = sense; + + scsi_device_set_ua(sdev, sense); } static char *scsibus_get_dev_path(DeviceState *dev) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index b52e30448a..734fe13c65 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1876,7 +1876,7 @@ static void scsi_cd_change_media_cb(void *opaque, bool load) */ s->media_changed = load; s->tray_open = !load; - s->qdev.unit_attention = SENSE_CODE(UNIT_ATTENTION_NO_MEDIUM); + scsi_device_set_ua(&s->qdev, SENSE_CODE(UNIT_ATTENTION_NO_MEDIUM)); s->media_event = true; s->eject_request = false; } @@ -1913,7 +1913,7 @@ static void scsi_disk_unit_attention_reported(SCSIDevice *dev) SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev); if (s->media_changed) { s->media_changed = false; - s->qdev.unit_attention = SENSE_CODE(MEDIUM_CHANGED); + scsi_device_set_ua(&s->qdev, SENSE_CODE(MEDIUM_CHANGED)); } } diff --git a/hw/scsi.h b/hw/scsi.h index 088d519b67..f920208fb2 100644 --- a/hw/scsi.h +++ b/hw/scsi.h @@ -239,6 +239,7 @@ void scsi_req_abort(SCSIRequest *req, int status); void scsi_req_cancel(SCSIRequest *req); void scsi_req_retry(SCSIRequest *req); void scsi_device_purge_requests(SCSIDevice *sdev, SCSISense sense); +void scsi_device_set_ua(SCSIDevice *sdev, SCSISense sense); int scsi_device_get_sense(SCSIDevice *dev, uint8_t *buf, int len, bool fixed); SCSIDevice *scsi_device_find(SCSIBus *bus, int channel, int target, int lun); diff --git a/trace-events b/trace-events index 7aa28b1b3e..6b12f83de0 100644 --- a/trace-events +++ b/trace-events @@ -412,6 +412,7 @@ scsi_req_parsed(int target, int lun, int tag, int cmd, int mode, int xfer) "targ scsi_req_parsed_lba(int target, int lun, int tag, int cmd, uint64_t lba) "target %d lun %d tag %d command %d lba %"PRIu64 scsi_req_parse_bad(int target, int lun, int tag, int cmd) "target %d lun %d tag %d command %d" scsi_req_build_sense(int target, int lun, int tag, int key, int asc, int ascq) "target %d lun %d tag %d key %#02x asc %#02x ascq %#02x" +scsi_device_set_ua(int target, int lun, int key, int asc, int ascq) "target %d lun %d key %#02x asc %#02x ascq %#02x" scsi_report_luns(int target, int lun, int tag) "target %d lun %d tag %d" scsi_inquiry(int target, int lun, int tag, int cdb1, int cdb2) "target %d lun %d tag %d page %#02x/%#02x" scsi_test_unit_ready(int target, int lun, int tag) "target %d lun %d tag %d" From aaebacef0a55a7113335ae3f998031d3c538a477 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 16 Jul 2012 14:07:55 +0200 Subject: [PATCH 25/32] scsi-disk: report resized disk via sense codes Linux will not use these, but a very similar mechanism will be used to report the condition via virtio-scsi events. Signed-off-by: Paolo Bonzini --- hw/scsi-bus.c | 5 +++++ hw/scsi-disk.c | 24 ++++++++++++++++++++++-- hw/scsi.h | 2 ++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index c38c0ec574..04262de44a 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -1161,6 +1161,11 @@ const struct SCSISense sense_code_LUN_FAILURE = { .key = ABORTED_COMMAND, .asc = 0x3e, .ascq = 0x01 }; +/* Unit attention, Capacity data has changed */ +const struct SCSISense sense_code_CAPACITY_CHANGED = { + .key = UNIT_ATTENTION, .asc = 0x2a, .ascq = 0x09 +}; + /* Unit attention, Power on, reset or bus device reset occurred */ const struct SCSISense sense_code_RESET = { .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x00 diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 734fe13c65..9ba405ff90 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1860,6 +1860,18 @@ static void scsi_destroy(SCSIDevice *dev) blockdev_mark_auto_del(s->qdev.conf.bs); } +static void scsi_disk_resize_cb(void *opaque) +{ + SCSIDiskState *s = opaque; + + /* SPC lists this sense code as available only for + * direct-access devices. + */ + if (s->qdev.type == TYPE_DISK) { + scsi_device_set_ua(&s->qdev, SENSE_CODE(CAPACITY_CHANGED)); + } +} + static void scsi_cd_change_media_cb(void *opaque, bool load) { SCSIDiskState *s = opaque; @@ -1901,11 +1913,17 @@ static bool scsi_cd_is_medium_locked(void *opaque) return ((SCSIDiskState *)opaque)->tray_locked; } -static const BlockDevOps scsi_cd_block_ops = { +static const BlockDevOps scsi_disk_removable_block_ops = { .change_media_cb = scsi_cd_change_media_cb, .eject_request_cb = scsi_cd_eject_request_cb, .is_tray_open = scsi_cd_is_tray_open, .is_medium_locked = scsi_cd_is_medium_locked, + + .resize_cb = scsi_disk_resize_cb, +}; + +static const BlockDevOps scsi_disk_block_ops = { + .resize_cb = scsi_disk_resize_cb, }; static void scsi_disk_unit_attention_reported(SCSIDevice *dev) @@ -1950,7 +1968,9 @@ static int scsi_initfn(SCSIDevice *dev) } if (s->features & (1 << SCSI_DISK_F_REMOVABLE)) { - bdrv_set_dev_ops(s->qdev.conf.bs, &scsi_cd_block_ops, s); + bdrv_set_dev_ops(s->qdev.conf.bs, &scsi_disk_removable_block_ops, s); + } else { + bdrv_set_dev_ops(s->qdev.conf.bs, &scsi_disk_block_ops, s); } bdrv_set_buffer_alignment(s->qdev.conf.bs, s->qdev.blocksize); diff --git a/hw/scsi.h b/hw/scsi.h index f920208fb2..82a5fc8a94 100644 --- a/hw/scsi.h +++ b/hw/scsi.h @@ -200,6 +200,8 @@ extern const struct SCSISense sense_code_IO_ERROR; extern const struct SCSISense sense_code_I_T_NEXUS_LOSS; /* Command aborted, Logical Unit failure */ extern const struct SCSISense sense_code_LUN_FAILURE; +/* LUN not ready, Capacity data has changed */ +extern const struct SCSISense sense_code_CAPACITY_CHANGED; /* LUN not ready, Medium not present */ extern const struct SCSISense sense_code_UNIT_ATTENTION_NO_MEDIUM; /* Unit attention, Power on, reset or bus device reset occurred */ From 53200fad0e61046909254ba07a3a1374cebd818f Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 16 Jul 2012 14:22:36 +0200 Subject: [PATCH 26/32] scsi: report parameter changes to HBA drivers Signed-off-by: Paolo Bonzini --- hw/scsi-bus.c | 10 ++++++++++ hw/scsi-disk.c | 1 + hw/scsi.h | 2 ++ 3 files changed, 13 insertions(+) diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index 04262de44a..e4ec19e051 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -1072,6 +1072,16 @@ int scsi_req_parse(SCSICommand *cmd, SCSIDevice *dev, uint8_t *buf) return 0; } +void scsi_device_report_change(SCSIDevice *dev, SCSISense sense) +{ + SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus); + + scsi_device_set_ua(dev, sense); + if (bus->info->change) { + bus->info->change(bus, dev, sense); + } +} + /* * Predefined sense codes */ diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 9ba405ff90..84b63ffafb 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1869,6 +1869,7 @@ static void scsi_disk_resize_cb(void *opaque) */ if (s->qdev.type == TYPE_DISK) { scsi_device_set_ua(&s->qdev, SENSE_CODE(CAPACITY_CHANGED)); + scsi_device_report_change(&s->qdev, SENSE_CODE(CAPACITY_CHANGED)); } } diff --git a/hw/scsi.h b/hw/scsi.h index 82a5fc8a94..1aeee4659c 100644 --- a/hw/scsi.h +++ b/hw/scsi.h @@ -133,6 +133,7 @@ struct SCSIBusInfo { void (*cancel)(SCSIRequest *req); void (*hotplug)(SCSIBus *bus, SCSIDevice *dev); void (*hot_unplug)(SCSIBus *bus, SCSIDevice *dev); + void (*change)(SCSIBus *bus, SCSIDevice *dev, SCSISense sense); QEMUSGList *(*get_sg_list)(SCSIRequest *req); void (*save_request)(QEMUFile *f, SCSIRequest *req); @@ -242,6 +243,7 @@ void scsi_req_cancel(SCSIRequest *req); void scsi_req_retry(SCSIRequest *req); void scsi_device_purge_requests(SCSIDevice *sdev, SCSISense sense); void scsi_device_set_ua(SCSIDevice *sdev, SCSISense sense); +void scsi_device_report_change(SCSIDevice *dev, SCSISense sense); int scsi_device_get_sense(SCSIDevice *dev, uint8_t *buf, int len, bool fixed); SCSIDevice *scsi_device_find(SCSIBus *bus, int channel, int target, int lun); From b6866fee58bc25b01485dcfcf8be44a915c5f573 Mon Sep 17 00:00:00 2001 From: Cong Meng Date: Wed, 20 Jun 2012 14:47:11 +0800 Subject: [PATCH 27/32] virtio-scsi: Implement hotplug support for virtio-scsi Implement the hotplug() and hot_unplug() interfaces in virtio-scsi, by signal the virtio_scsi.ko in guest kernel via event virtual queue. The counterpart patch of virtio_scsi.ko will be sent soon in another thread. Signed-off-by: Sen Wang Signed-off-by: Cong Meng [ Add memset, fix LUN field, placate checkpatch - Paolo ] Signed-off-by: Paolo Bonzini --- hw/virtio-scsi.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/hw/virtio-scsi.c b/hw/virtio-scsi.c index 0a5ac40e2f..4f021957f7 100644 --- a/hw/virtio-scsi.c +++ b/hw/virtio-scsi.c @@ -24,6 +24,10 @@ #define VIRTIO_SCSI_MAX_TARGET 255 #define VIRTIO_SCSI_MAX_LUN 16383 +/* Feature Bits */ +#define VIRTIO_SCSI_F_INOUT 0 +#define VIRTIO_SCSI_F_HOTPLUG 1 + /* Response codes */ #define VIRTIO_SCSI_S_OK 0 #define VIRTIO_SCSI_S_OVERRUN 1 @@ -60,6 +64,11 @@ #define VIRTIO_SCSI_T_TRANSPORT_RESET 1 #define VIRTIO_SCSI_T_ASYNC_NOTIFY 2 +/* Reasons for transport reset event */ +#define VIRTIO_SCSI_EVT_RESET_HARD 0 +#define VIRTIO_SCSI_EVT_RESET_RESCAN 1 +#define VIRTIO_SCSI_EVT_RESET_REMOVED 2 + /* SCSI command request, followed by data-out */ typedef struct { uint8_t lun[8]; /* Logical Unit Number */ @@ -206,11 +215,13 @@ static void qemu_sgl_init_external(QEMUSGList *qsgl, struct iovec *sg, static void virtio_scsi_parse_req(VirtIOSCSI *s, VirtQueue *vq, VirtIOSCSIReq *req) { - assert(req->elem.out_num && req->elem.in_num); + assert(req->elem.in_num); req->vq = vq; req->dev = s; req->sreq = NULL; - req->req.buf = req->elem.out_sg[0].iov_base; + if (req->elem.out_num) { + req->req.buf = req->elem.out_sg[0].iov_base; + } req->resp.buf = req->elem.in_sg[0].iov_base; if (req->elem.out_num > 1) { @@ -545,6 +556,7 @@ static void virtio_scsi_set_config(VirtIODevice *vdev, static uint32_t virtio_scsi_get_features(VirtIODevice *vdev, uint32_t requested_features) { + requested_features |= (1UL << VIRTIO_SCSI_F_HOTPLUG); return requested_features; } @@ -577,6 +589,60 @@ static int virtio_scsi_load(QEMUFile *f, void *opaque, int version_id) return 0; } +static void virtio_scsi_push_event(VirtIOSCSI *s, SCSIDevice *dev, + uint32_t event, uint32_t reason) +{ + VirtIOSCSIReq *req = virtio_scsi_pop_req(s, s->event_vq); + VirtIOSCSIEvent *evt; + + if (req) { + int in_size; + if (req->elem.out_num || req->elem.in_num != 1) { + virtio_scsi_bad_req(); + } + + in_size = req->elem.in_sg[0].iov_len; + if (in_size < sizeof(VirtIOSCSIEvent)) { + virtio_scsi_bad_req(); + } + + evt = req->resp.event; + memset(evt, 0, sizeof(VirtIOSCSIEvent)); + evt->event = event; + evt->reason = reason; + evt->lun[0] = 1; + evt->lun[1] = dev->id; + + /* Linux wants us to keep the same encoding we use for REPORT LUNS. */ + if (dev->lun >= 256) { + evt->lun[2] = (dev->lun >> 8) | 0x40; + } + evt->lun[3] = dev->lun & 0xFF; + virtio_scsi_complete_req(req); + } +} + +static void virtio_scsi_hotplug(SCSIBus *bus, SCSIDevice *dev) +{ + VirtIOSCSI *s = container_of(bus, VirtIOSCSI, bus); + + if (((s->vdev.guest_features >> VIRTIO_SCSI_F_HOTPLUG) & 1) && + (s->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK)) { + virtio_scsi_push_event(s, dev, VIRTIO_SCSI_T_TRANSPORT_RESET, + VIRTIO_SCSI_EVT_RESET_RESCAN); + } +} + +static void virtio_scsi_hot_unplug(SCSIBus *bus, SCSIDevice *dev) +{ + VirtIOSCSI *s = container_of(bus, VirtIOSCSI, bus); + + if ((s->vdev.guest_features >> VIRTIO_SCSI_F_HOTPLUG) & 1) { + virtio_scsi_push_event(s, dev, VIRTIO_SCSI_T_TRANSPORT_RESET, + VIRTIO_SCSI_EVT_RESET_REMOVED); + } +} + static struct SCSIBusInfo virtio_scsi_scsi_info = { .tcq = true, .max_channel = VIRTIO_SCSI_MAX_CHANNEL, @@ -585,6 +651,8 @@ static struct SCSIBusInfo virtio_scsi_scsi_info = { .complete = virtio_scsi_command_complete, .cancel = virtio_scsi_request_cancelled, + .hotplug = virtio_scsi_hotplug, + .hot_unplug = virtio_scsi_hot_unplug, .get_sg_list = virtio_scsi_get_sg_list, .save_request = virtio_scsi_save_request, .load_request = virtio_scsi_load_request, From 64f64855d0ec2c5979f6986d2f52e8861d58e208 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 2 Jul 2012 10:47:35 +0200 Subject: [PATCH 28/32] virtio-scsi: Report missed events When an event is reported but no buffers are present in the event vq, we can set a flag and report a dummy event as soon as one is added. Signed-off-by: Paolo Bonzini --- hw/virtio-scsi.c | 54 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/hw/virtio-scsi.c b/hw/virtio-scsi.c index 4f021957f7..d5c5984e08 100644 --- a/hw/virtio-scsi.c +++ b/hw/virtio-scsi.c @@ -141,6 +141,7 @@ typedef struct { uint32_t sense_size; uint32_t cdb_size; int resetting; + bool events_dropped; VirtQueue *ctrl_vq; VirtQueue *event_vq; VirtQueue *cmd_vqs[0]; @@ -416,10 +417,6 @@ static void virtio_scsi_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) } } -static void virtio_scsi_handle_event(VirtIODevice *vdev, VirtQueue *vq) -{ -} - static void virtio_scsi_command_complete(SCSIRequest *r, uint32_t status, size_t resid) { @@ -594,22 +591,34 @@ static void virtio_scsi_push_event(VirtIOSCSI *s, SCSIDevice *dev, { VirtIOSCSIReq *req = virtio_scsi_pop_req(s, s->event_vq); VirtIOSCSIEvent *evt; + int in_size; - if (req) { - int in_size; - if (req->elem.out_num || req->elem.in_num != 1) { - virtio_scsi_bad_req(); - } + if (!req) { + s->events_dropped = true; + return; + } - in_size = req->elem.in_sg[0].iov_len; - if (in_size < sizeof(VirtIOSCSIEvent)) { - virtio_scsi_bad_req(); - } + if (req->elem.out_num || req->elem.in_num != 1) { + virtio_scsi_bad_req(); + } - evt = req->resp.event; - memset(evt, 0, sizeof(VirtIOSCSIEvent)); - evt->event = event; - evt->reason = reason; + if (s->events_dropped) { + event |= VIRTIO_SCSI_T_EVENTS_MISSED; + s->events_dropped = false; + } + + in_size = req->elem.in_sg[0].iov_len; + if (in_size < sizeof(VirtIOSCSIEvent)) { + virtio_scsi_bad_req(); + } + + evt = req->resp.event; + memset(evt, 0, sizeof(VirtIOSCSIEvent)); + evt->event = event; + evt->reason = reason; + if (!dev) { + assert(event == VIRTIO_SCSI_T_NO_EVENT); + } else { evt->lun[0] = 1; evt->lun[1] = dev->id; @@ -618,7 +627,16 @@ static void virtio_scsi_push_event(VirtIOSCSI *s, SCSIDevice *dev, evt->lun[2] = (dev->lun >> 8) | 0x40; } evt->lun[3] = dev->lun & 0xFF; - virtio_scsi_complete_req(req); + } + virtio_scsi_complete_req(req); +} + +static void virtio_scsi_handle_event(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIOSCSI *s = (VirtIOSCSI *)vdev; + + if (s->events_dropped) { + virtio_scsi_push_event(s, NULL, VIRTIO_SCSI_T_NO_EVENT, 0); } } From 2baa1beb1e47ec207e0f9bcacdb5c68e4bf1d8f5 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 16 Jul 2012 14:50:27 +0200 Subject: [PATCH 29/32] virtio-scsi: do not report dropped events after reset Signed-off-by: Paolo Bonzini --- hw/virtio-scsi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/virtio-scsi.c b/hw/virtio-scsi.c index d5c5984e08..bf18861e94 100644 --- a/hw/virtio-scsi.c +++ b/hw/virtio-scsi.c @@ -563,6 +563,7 @@ static void virtio_scsi_reset(VirtIODevice *vdev) s->sense_size = VIRTIO_SCSI_SENSE_SIZE; s->cdb_size = VIRTIO_SCSI_CDB_SIZE; + s->events_dropped = false; } /* The device does not have anything to save beyond the virtio data. From feda01e40f101956021077ad5c587519410d2073 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 16 Jul 2012 14:22:52 +0200 Subject: [PATCH 30/32] virtio-scsi: report parameter change events Signed-off-by: Paolo Bonzini --- hw/virtio-scsi.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/hw/virtio-scsi.c b/hw/virtio-scsi.c index bf18861e94..c4a5b22f94 100644 --- a/hw/virtio-scsi.c +++ b/hw/virtio-scsi.c @@ -27,6 +27,7 @@ /* Feature Bits */ #define VIRTIO_SCSI_F_INOUT 0 #define VIRTIO_SCSI_F_HOTPLUG 1 +#define VIRTIO_SCSI_F_CHANGE 2 /* Response codes */ #define VIRTIO_SCSI_S_OK 0 @@ -63,6 +64,7 @@ #define VIRTIO_SCSI_T_NO_EVENT 0 #define VIRTIO_SCSI_T_TRANSPORT_RESET 1 #define VIRTIO_SCSI_T_ASYNC_NOTIFY 2 +#define VIRTIO_SCSI_T_PARAM_CHANGE 3 /* Reasons for transport reset event */ #define VIRTIO_SCSI_EVT_RESET_HARD 0 @@ -554,6 +556,7 @@ static uint32_t virtio_scsi_get_features(VirtIODevice *vdev, uint32_t requested_features) { requested_features |= (1UL << VIRTIO_SCSI_F_HOTPLUG); + requested_features |= (1UL << VIRTIO_SCSI_F_CHANGE); return requested_features; } @@ -641,6 +644,18 @@ static void virtio_scsi_handle_event(VirtIODevice *vdev, VirtQueue *vq) } } +static void virtio_scsi_change(SCSIBus *bus, SCSIDevice *dev, SCSISense sense) +{ + VirtIOSCSI *s = container_of(bus, VirtIOSCSI, bus); + + if (((s->vdev.guest_features >> VIRTIO_SCSI_F_CHANGE) & 1) && + (s->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK) && + dev->type != TYPE_ROM) { + virtio_scsi_push_event(s, dev, VIRTIO_SCSI_T_PARAM_CHANGE, + sense.asc | (sense.ascq << 8)); + } +} + static void virtio_scsi_hotplug(SCSIBus *bus, SCSIDevice *dev) { VirtIOSCSI *s = container_of(bus, VirtIOSCSI, bus); @@ -670,6 +685,7 @@ static struct SCSIBusInfo virtio_scsi_scsi_info = { .complete = virtio_scsi_command_complete, .cancel = virtio_scsi_request_cancelled, + .change = virtio_scsi_change, .hotplug = virtio_scsi_hotplug, .hot_unplug = virtio_scsi_hot_unplug, .get_sg_list = virtio_scsi_get_sg_list, From 3f910904ecce48bcfd6ee269e8ac84a571fbc1e3 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 27 Jul 2012 14:36:07 +0200 Subject: [PATCH 31/32] virtio-scsi: add ioeventfd support Probably due to bad merge months ago, virtio-scsi-pci did not have ioeventfd support. Fix this and enable it by default, as is the case for other virtio-pci devices. Signed-off-by: Paolo Bonzini --- hw/virtio-pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/virtio-pci.c b/hw/virtio-pci.c index 4e03f0b42c..82859a5724 100644 --- a/hw/virtio-pci.c +++ b/hw/virtio-pci.c @@ -1040,6 +1040,7 @@ static int virtio_scsi_exit_pci(PCIDevice *pci_dev) } static Property virtio_scsi_properties[] = { + DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags, VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true), DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 2), DEFINE_VIRTIO_SCSI_PROPERTIES(VirtIOPCIProxy, host_features, scsi), DEFINE_PROP_END_OF_LIST(), From 4c205d0cb1c8bde5a53f6acceda74dae1043a197 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 27 Jul 2012 14:38:03 +0200 Subject: [PATCH 32/32] virtio-scsi: enable MSI-X support While virtio-scsi does support multiqueue, the default number of interrupt vectors is not enough to actually enable usage of multiple queues in the driver; this is because with only 2 vectors the driver will not be able to use a separate interrupt for each request queue. Derive the desired number of vectors from the number of request queues. Signed-off-by: Paolo Bonzini --- hw/virtio-pci.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hw/virtio-pci.c b/hw/virtio-pci.c index 82859a5724..1109467d19 100644 --- a/hw/virtio-pci.c +++ b/hw/virtio-pci.c @@ -1023,7 +1023,9 @@ static int virtio_scsi_init_pci(PCIDevice *pci_dev) return -EINVAL; } - vdev->nvectors = proxy->nvectors; + vdev->nvectors = proxy->nvectors == DEV_NVECTORS_UNSPECIFIED + ? proxy->scsi.num_queues + 3 + : proxy->nvectors; virtio_init_pci(proxy, vdev); /* make the actual value visible */ @@ -1041,7 +1043,7 @@ static int virtio_scsi_exit_pci(PCIDevice *pci_dev) static Property virtio_scsi_properties[] = { DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags, VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true), - DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 2), + DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, DEV_NVECTORS_UNSPECIFIED), DEFINE_VIRTIO_SCSI_PROPERTIES(VirtIOPCIProxy, host_features, scsi), DEFINE_PROP_END_OF_LIST(), };