From fead0c24106993bc0ca87b7788bf2d9fd24e0a33 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 9 Nov 2011 16:58:30 +0100 Subject: [PATCH 01/18] dma-helpers: make QEMUSGList target independent scsi-disk will manage scatter/gather list, but it does not create single entries so it remains target-independent. Make QEMUSGList available to it. Signed-off-by: Paolo Bonzini --- dma.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dma.h b/dma.h index a13209d7e..d50019b75 100644 --- a/dma.h +++ b/dma.h @@ -17,6 +17,13 @@ typedef struct ScatterGatherEntry ScatterGatherEntry; +struct QEMUSGList { + ScatterGatherEntry *sg; + int nsg; + int nalloc; + size_t size; +}; + #if defined(TARGET_PHYS_ADDR_BITS) typedef target_phys_addr_t dma_addr_t; @@ -32,13 +39,6 @@ struct ScatterGatherEntry { dma_addr_t len; }; -struct QEMUSGList { - ScatterGatherEntry *sg; - int nsg; - int nalloc; - dma_addr_t size; -}; - void qemu_sglist_init(QEMUSGList *qsg, int alloc_hint); void qemu_sglist_add(QEMUSGList *qsg, dma_addr_t base, dma_addr_t len); void qemu_sglist_destroy(QEMUSGList *qsg); From 8171ee35e2ff3cea6c76280e361cf628b3a8c8df Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 6 Jul 2011 08:02:14 +0200 Subject: [PATCH 02/18] dma-helpers: add dma_buf_read and dma_buf_write These helpers do a full transfer from an in-memory buffer to target memory, with support for scatter/gather lists. It will be used to store the reply of an emulated command into a QEMUSGList provided by the adapter. Signed-off-by: Paolo Bonzini --- dma-helpers.c | 31 +++++++++++++++++++++++++++++++ dma.h | 3 +++ 2 files changed, 34 insertions(+) diff --git a/dma-helpers.c b/dma-helpers.c index f08cdb545..df0a42172 100644 --- a/dma-helpers.c +++ b/dma-helpers.c @@ -204,3 +204,34 @@ BlockDriverAIOCB *dma_bdrv_write(BlockDriverState *bs, { return dma_bdrv_io(bs, sg, sector, bdrv_aio_writev, cb, opaque, true); } + + +static uint64_t dma_buf_rw(uint8_t *ptr, int32_t len, QEMUSGList *sg, bool to_dev) +{ + uint64_t resid; + int sg_cur_index; + + resid = sg->size; + sg_cur_index = 0; + len = MIN(len, resid); + while (len > 0) { + ScatterGatherEntry entry = sg->sg[sg_cur_index++]; + int32_t xfer = MIN(len, entry.len); + cpu_physical_memory_rw(entry.base, ptr, xfer, !to_dev); + ptr += xfer; + len -= xfer; + resid -= xfer; + } + + return resid; +} + +uint64_t dma_buf_read(uint8_t *ptr, int32_t len, QEMUSGList *sg) +{ + return dma_buf_rw(ptr, len, sg, 0); +} + +uint64_t dma_buf_write(uint8_t *ptr, int32_t len, QEMUSGList *sg) +{ + return dma_buf_rw(ptr, len, sg, 1); +} diff --git a/dma.h b/dma.h index d50019b75..346ac4f70 100644 --- a/dma.h +++ b/dma.h @@ -58,4 +58,7 @@ BlockDriverAIOCB *dma_bdrv_read(BlockDriverState *bs, BlockDriverAIOCB *dma_bdrv_write(BlockDriverState *bs, QEMUSGList *sg, uint64_t sector, BlockDriverCompletionFunc *cb, void *opaque); +uint64_t dma_buf_read(uint8_t *ptr, int32_t len, QEMUSGList *sg); +uint64_t dma_buf_write(uint8_t *ptr, int32_t len, QEMUSGList *sg); + #endif From 84a693562bd801dc964dcf72370bd981cf0a24d3 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 5 Sep 2011 14:20:29 +0200 Subject: [PATCH 03/18] dma-helpers: add accounting wrappers The length of the transfer is already in the sglist, the wrapper simply fetches it. Signed-off-by: Paolo Bonzini --- dma-helpers.c | 6 ++++++ dma.h | 3 +++ 2 files changed, 9 insertions(+) diff --git a/dma-helpers.c b/dma-helpers.c index df0a42172..c29ea6d3a 100644 --- a/dma-helpers.c +++ b/dma-helpers.c @@ -235,3 +235,9 @@ uint64_t dma_buf_write(uint8_t *ptr, int32_t len, QEMUSGList *sg) { return dma_buf_rw(ptr, len, sg, 1); } + +void dma_acct_start(BlockDriverState *bs, BlockAcctCookie *cookie, + QEMUSGList *sg, enum BlockAcctType type) +{ + bdrv_acct_start(bs, cookie, sg->size, type); +} diff --git a/dma.h b/dma.h index 346ac4f70..20e86d28e 100644 --- a/dma.h +++ b/dma.h @@ -61,4 +61,7 @@ BlockDriverAIOCB *dma_bdrv_write(BlockDriverState *bs, uint64_t dma_buf_read(uint8_t *ptr, int32_t len, QEMUSGList *sg); uint64_t dma_buf_write(uint8_t *ptr, int32_t len, QEMUSGList *sg); +void dma_acct_start(BlockDriverState *bs, BlockAcctCookie *cookie, + QEMUSGList *sg, enum BlockAcctType type); + #endif From da2213275237a35d1e063db7d6d820905e7d4084 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 15 Dec 2011 08:32:04 -0500 Subject: [PATCH 04/18] ahci: use new DMA helpers Signed-off-by: Paolo Bonzini --- hw/ide/ahci.c | 82 ++++++++------------------------------------------- 1 file changed, 13 insertions(+), 69 deletions(-) diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c index b515f417b..041ce1e89 100644 --- a/hw/ide/ahci.c +++ b/hw/ide/ahci.c @@ -428,55 +428,6 @@ static void ahci_reg_init(AHCIState *s) } } -static uint32_t read_from_sglist(uint8_t *buffer, uint32_t len, - QEMUSGList *sglist) -{ - uint32_t i = 0; - uint32_t total = 0, once; - ScatterGatherEntry *cur_prd; - uint32_t sgcount; - - cur_prd = sglist->sg; - sgcount = sglist->nsg; - for (i = 0; len && sgcount; i++) { - once = MIN(cur_prd->len, len); - cpu_physical_memory_read(cur_prd->base, buffer, once); - cur_prd++; - sgcount--; - len -= once; - buffer += once; - total += once; - } - - return total; -} - -static uint32_t write_to_sglist(uint8_t *buffer, uint32_t len, - QEMUSGList *sglist) -{ - uint32_t i = 0; - uint32_t total = 0, once; - ScatterGatherEntry *cur_prd; - uint32_t sgcount; - - DPRINTF(-1, "total: 0x%x bytes\n", len); - - cur_prd = sglist->sg; - sgcount = sglist->nsg; - for (i = 0; len && sgcount; i++) { - once = MIN(cur_prd->len, len); - DPRINTF(-1, "write 0x%x bytes to 0x%lx\n", once, (long)cur_prd->base); - cpu_physical_memory_write(cur_prd->base, buffer, once); - cur_prd++; - sgcount--; - len -= once; - buffer += once; - total += once; - } - - return total; -} - static void check_cmd(AHCIState *s, int port) { AHCIPortRegs *pr = &s->dev[port].port_regs; @@ -802,9 +753,8 @@ static void process_ncq_command(AHCIState *s, int port, uint8_t *cmd_fis, DPRINTF(port, "tag %d aio read %"PRId64"\n", ncq_tfs->tag, ncq_tfs->lba); - bdrv_acct_start(ncq_tfs->drive->port.ifs[0].bs, &ncq_tfs->acct, - (ncq_tfs->sector_count-1) * BDRV_SECTOR_SIZE, - BDRV_ACCT_READ); + dma_acct_start(ncq_tfs->drive->port.ifs[0].bs, &ncq_tfs->acct, + &ncq_tfs->sglist, BDRV_ACCT_READ); ncq_tfs->aiocb = dma_bdrv_read(ncq_tfs->drive->port.ifs[0].bs, &ncq_tfs->sglist, ncq_tfs->lba, ncq_cb, ncq_tfs); @@ -816,9 +766,8 @@ static void process_ncq_command(AHCIState *s, int port, uint8_t *cmd_fis, DPRINTF(port, "tag %d aio write %"PRId64"\n", ncq_tfs->tag, ncq_tfs->lba); - bdrv_acct_start(ncq_tfs->drive->port.ifs[0].bs, &ncq_tfs->acct, - (ncq_tfs->sector_count-1) * BDRV_SECTOR_SIZE, - BDRV_ACCT_WRITE); + dma_acct_start(ncq_tfs->drive->port.ifs[0].bs, &ncq_tfs->acct, + &ncq_tfs->sglist, BDRV_ACCT_WRITE); ncq_tfs->aiocb = dma_bdrv_write(ncq_tfs->drive->port.ifs[0].bs, &ncq_tfs->sglist, ncq_tfs->lba, ncq_cb, ncq_tfs); @@ -1023,12 +972,12 @@ static int ahci_start_transfer(IDEDMA *dma) is_write ? "writ" : "read", size, is_atapi ? "atapi" : "ata", has_sglist ? "" : "o"); - if (is_write && has_sglist && (s->data_ptr < s->data_end)) { - read_from_sglist(s->data_ptr, size, &s->sg); - } - - if (!is_write && has_sglist && (s->data_ptr < s->data_end)) { - write_to_sglist(s->data_ptr, size, &s->sg); + if (has_sglist && size) { + if (is_write) { + dma_buf_write(s->data_ptr, size, &s->sg); + } else { + dma_buf_read(s->data_ptr, size, &s->sg); + } } /* update number of transferred bytes */ @@ -1067,14 +1016,9 @@ static int ahci_dma_prepare_buf(IDEDMA *dma, int is_write) { AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); IDEState *s = &ad->port.ifs[0]; - int i; ahci_populate_sglist(ad, &s->sg); - - s->io_buffer_size = 0; - for (i = 0; i < s->sg.nsg; i++) { - s->io_buffer_size += s->sg.sg[i].len; - } + s->io_buffer_size = s->sg.size; DPRINTF(ad->port_no, "len=%#x\n", s->io_buffer_size); return s->io_buffer_size != 0; @@ -1092,9 +1036,9 @@ static int ahci_dma_rw_buf(IDEDMA *dma, int is_write) } if (is_write) { - write_to_sglist(p, l, &s->sg); + dma_buf_read(p, l, &s->sg); } else { - read_from_sglist(p, l, &s->sg); + dma_buf_write(p, l, &s->sg); } /* update number of transferred bytes */ From 01e95455889780871374e1b72cd6919cfbd23399 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 6 Jul 2011 11:55:37 +0200 Subject: [PATCH 05/18] scsi: pass residual amount to command_complete With the upcoming sglist support, HBAs will not see any transfer_data call and will not have a way to detect short transfers. So pass the residual amount of data upon command completion. Signed-off-by: Paolo Bonzini --- hw/esp.c | 3 ++- hw/lsi53c895a.c | 2 +- hw/scsi-bus.c | 12 ++++++++---- hw/scsi.h | 3 ++- hw/spapr_vscsi.c | 2 +- hw/usb-msd.c | 2 +- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/hw/esp.c b/hw/esp.c index 2dda8e358..8d73e5688 100644 --- a/hw/esp.c +++ b/hw/esp.c @@ -390,7 +390,8 @@ static void esp_do_dma(ESPState *s) esp_dma_done(s); } -static void esp_command_complete(SCSIRequest *req, uint32_t status) +static void esp_command_complete(SCSIRequest *req, uint32_t status, + size_t resid) { ESPState *s = DO_UPCAST(ESPState, busdev.qdev, req->bus->qbus.parent); diff --git a/hw/lsi53c895a.c b/hw/lsi53c895a.c index 0acd1d06d..edc09b730 100644 --- a/hw/lsi53c895a.c +++ b/hw/lsi53c895a.c @@ -699,7 +699,7 @@ static int lsi_queue_req(LSIState *s, SCSIRequest *req, uint32_t len) } /* Callback to indicate that the SCSI layer has completed a command. */ -static void lsi_command_complete(SCSIRequest *req, uint32_t status) +static void lsi_command_complete(SCSIRequest *req, uint32_t status, size_t resid) { LSIState *s = DO_UPCAST(LSIState, dev.qdev, req->bus->qbus.parent); int out; diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index b3e97ceee..eb97c878b 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -533,6 +533,8 @@ SCSIRequest *scsi_req_new(SCSIDevice *d, uint32_t tag, uint32_t lun, } req->cmd = cmd; + req->resid = req->cmd.xfer; + switch (buf[0]) { case INQUIRY: trace_scsi_inquiry(d->id, lun, tag, cmd.buf[1], cmd.buf[2]); @@ -1275,10 +1277,12 @@ void scsi_req_data(SCSIRequest *req, int len) { if (req->io_canceled) { trace_scsi_req_data_canceled(req->dev->id, req->lun, req->tag, len); - } else { - trace_scsi_req_data(req->dev->id, req->lun, req->tag, len); - req->bus->info->transfer_data(req, len); + return; } + trace_scsi_req_data(req->dev->id, req->lun, req->tag, len); + assert(req->cmd.mode != SCSI_XFER_NONE); + req->resid -= len; + req->bus->info->transfer_data(req, len); } void scsi_req_print(SCSIRequest *req) @@ -1337,7 +1341,7 @@ void scsi_req_complete(SCSIRequest *req, int status) scsi_req_ref(req); scsi_req_dequeue(req); - req->bus->info->complete(req, req->status); + req->bus->info->complete(req, req->status, req->resid); scsi_req_unref(req); } diff --git a/hw/scsi.h b/hw/scsi.h index dc72b6fc1..8722ef9be 100644 --- a/hw/scsi.h +++ b/hw/scsi.h @@ -46,6 +46,7 @@ struct SCSIRequest { uint32_t tag; uint32_t lun; uint32_t status; + size_t resid; SCSICommand cmd; BlockDriverAIOCB *aiocb; uint8_t sense[SCSI_SENSE_BUF_SIZE]; @@ -112,7 +113,7 @@ struct SCSIBusInfo { int tcq; int max_channel, max_target, max_lun; void (*transfer_data)(SCSIRequest *req, uint32_t arg); - void (*complete)(SCSIRequest *req, uint32_t arg); + void (*complete)(SCSIRequest *req, uint32_t arg, size_t resid); void (*cancel)(SCSIRequest *req); }; diff --git a/hw/spapr_vscsi.c b/hw/spapr_vscsi.c index ffce261f9..21670170e 100644 --- a/hw/spapr_vscsi.c +++ b/hw/spapr_vscsi.c @@ -494,7 +494,7 @@ static void vscsi_transfer_data(SCSIRequest *sreq, uint32_t len) } /* Callback to indicate that the SCSI layer has completed a transfer. */ -static void vscsi_command_complete(SCSIRequest *sreq, uint32_t status) +static void vscsi_command_complete(SCSIRequest *sreq, uint32_t status, size_t resid) { VSCSIState *s = DO_UPCAST(VSCSIState, vdev.qdev, sreq->bus->qbus.parent); vscsi_req *req = sreq->hba_private; diff --git a/hw/usb-msd.c b/hw/usb-msd.c index c933efe19..5fbd2d021 100644 --- a/hw/usb-msd.c +++ b/hw/usb-msd.c @@ -223,7 +223,7 @@ static void usb_msd_transfer_data(SCSIRequest *req, uint32_t len) } } -static void usb_msd_command_complete(SCSIRequest *req, uint32_t status) +static void usb_msd_command_complete(SCSIRequest *req, uint32_t status, size_t resid) { MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); USBPacket *p = s->packet; From 3d5aba97e9b3e92e8788e250de85080d15c7e8f9 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 6 Jul 2011 11:26:47 +0200 Subject: [PATCH 06/18] scsi: add scatter/gather functionality Scatter/gather functionality uses the newly added DMA helpers. The device can choose between doing DMA itself, or calling scsi_req_data as usual, which will use the newly added DMA helpers to copy piecewise to/from the destination area(s). Signed-off-by: Paolo Bonzini --- hw/scsi-bus.c | 28 ++++++++++++++++++++++++++-- hw/scsi.h | 3 +++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index eb97c878b..0c471e0ec 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -5,6 +5,7 @@ #include "qdev.h" #include "blockdev.h" #include "trace.h" +#include "dma.h" static char *scsibus_get_fw_dev_path(DeviceState *dev); static int scsi_req_parse(SCSICommand *cmd, SCSIDevice *dev, uint8_t *buf); @@ -651,6 +652,11 @@ int32_t scsi_req_enqueue(SCSIRequest *req) assert(!req->enqueued); scsi_req_ref(req); + if (req->bus->info->get_sg_list) { + req->sg = req->bus->info->get_sg_list(req); + } else { + req->sg = NULL; + } req->enqueued = true; QTAILQ_INSERT_TAIL(&req->dev->requests, req, next); @@ -1275,14 +1281,32 @@ void scsi_req_continue(SCSIRequest *req) Once it completes, calling scsi_req_continue will restart I/O. */ void scsi_req_data(SCSIRequest *req, int len) { + uint8_t *buf; if (req->io_canceled) { trace_scsi_req_data_canceled(req->dev->id, req->lun, req->tag, len); return; } trace_scsi_req_data(req->dev->id, req->lun, req->tag, len); assert(req->cmd.mode != SCSI_XFER_NONE); - req->resid -= len; - req->bus->info->transfer_data(req, len); + if (!req->sg) { + req->resid -= len; + req->bus->info->transfer_data(req, len); + return; + } + + /* If the device calls scsi_req_data and the HBA specified a + * scatter/gather list, the transfer has to happen in a single + * step. */ + assert(!req->dma_started); + req->dma_started = true; + + buf = scsi_req_get_buf(req); + if (req->cmd.mode == SCSI_XFER_FROM_DEV) { + req->resid = dma_buf_read(buf, len, req->sg); + } else { + req->resid = dma_buf_write(buf, len, req->sg); + } + scsi_req_continue(req); } void scsi_req_print(SCSIRequest *req) diff --git a/hw/scsi.h b/hw/scsi.h index 8722ef9be..34cdba8f6 100644 --- a/hw/scsi.h +++ b/hw/scsi.h @@ -49,6 +49,8 @@ struct SCSIRequest { size_t resid; SCSICommand cmd; BlockDriverAIOCB *aiocb; + QEMUSGList *sg; + bool dma_started; uint8_t sense[SCSI_SENSE_BUF_SIZE]; uint32_t sense_len; bool enqueued; @@ -115,6 +117,7 @@ struct SCSIBusInfo { void (*transfer_data)(SCSIRequest *req, uint32_t arg); void (*complete)(SCSIRequest *req, uint32_t arg, size_t resid); void (*cancel)(SCSIRequest *req); + QEMUSGList *(*get_sg_list)(SCSIRequest *req); }; struct SCSIBus { From 5d0d246792ec017df7e2363c2cd58185545ea1fe Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 12 Oct 2011 15:12:14 +0200 Subject: [PATCH 07/18] scsi-disk: enable scatter/gather functionality Signed-off-by: Paolo Bonzini --- hw/scsi-bus.c | 1 + hw/scsi-disk.c | 63 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index 0c471e0ec..dce620890 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -87,6 +87,7 @@ static void scsi_dma_restart_bh(void *opaque) scsi_req_continue(req); break; case SCSI_XFER_NONE: + assert(!req->sg); scsi_req_dequeue(req); scsi_req_enqueue(req); break; diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index c12e3a6cb..2a24840ef 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -38,6 +38,7 @@ do { fprintf(stderr, "scsi-disk: " fmt , ## __VA_ARGS__); } while (0) #include "sysemu.h" #include "blockdev.h" #include "block_int.h" +#include "dma.h" #ifdef __linux #include @@ -123,6 +124,27 @@ static uint32_t scsi_init_iovec(SCSIDiskReq *r) return r->qiov.size / 512; } +static void scsi_dma_complete(void *opaque, int ret) +{ + SCSIDiskReq *r = (SCSIDiskReq *)opaque; + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); + + bdrv_acct_done(s->qdev.conf.bs, &r->acct); + + if (ret) { + if (scsi_handle_rw_error(r, -ret)) { + goto done; + } + } + + r->sector += r->sector_count; + r->sector_count = 0; + scsi_req_complete(&r->req, GOOD); + +done: + scsi_req_unref(&r->req); +} + static void scsi_read_complete(void * opaque, int ret) { SCSIDiskReq *r = (SCSIDiskReq *)opaque; @@ -213,10 +235,17 @@ static void scsi_read_data(SCSIRequest *req) return; } - n = scsi_init_iovec(r); - bdrv_acct_start(s->qdev.conf.bs, &r->acct, n * BDRV_SECTOR_SIZE, BDRV_ACCT_READ); - r->req.aiocb = bdrv_aio_readv(s->qdev.conf.bs, r->sector, &r->qiov, n, - scsi_read_complete, r); + if (r->req.sg) { + dma_acct_start(s->qdev.conf.bs, &r->acct, r->req.sg, BDRV_ACCT_READ); + r->req.resid -= r->req.sg->size; + r->req.aiocb = dma_bdrv_read(s->qdev.conf.bs, r->req.sg, r->sector, + scsi_dma_complete, r); + } else { + n = scsi_init_iovec(r); + bdrv_acct_start(s->qdev.conf.bs, &r->acct, n * BDRV_SECTOR_SIZE, BDRV_ACCT_READ); + r->req.aiocb = bdrv_aio_readv(s->qdev.conf.bs, r->sector, &r->qiov, n, + scsi_read_complete, r); + } } /* @@ -315,18 +344,26 @@ static void scsi_write_data(SCSIRequest *req) return; } - n = r->qiov.size / 512; - if (n) { - if (s->tray_open) { - scsi_write_complete(r, -ENOMEDIUM); - return; - } + if (!r->req.sg && !r->qiov.size) { + /* Called for the first time. Ask the driver to send us more data. */ + scsi_write_complete(r, 0); + return; + } + if (s->tray_open) { + scsi_write_complete(r, -ENOMEDIUM); + return; + } + + if (r->req.sg) { + dma_acct_start(s->qdev.conf.bs, &r->acct, r->req.sg, BDRV_ACCT_WRITE); + r->req.resid -= r->req.sg->size; + r->req.aiocb = dma_bdrv_write(s->qdev.conf.bs, r->req.sg, r->sector, + scsi_dma_complete, r); + } else { + n = r->qiov.size / 512; bdrv_acct_start(s->qdev.conf.bs, &r->acct, n * BDRV_SECTOR_SIZE, BDRV_ACCT_WRITE); r->req.aiocb = bdrv_aio_writev(s->qdev.conf.bs, r->sector, &r->qiov, n, scsi_write_complete, r); - } else { - /* Called for the first time. Ask the driver to send us more data. */ - scsi_write_complete(r, 0); } } From 63f740dd9e66518ed65743835628503c04e72986 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 2 Dec 2011 16:27:02 +0100 Subject: [PATCH 08/18] scsi: add SCSIDevice vmstate definitions Signed-off-by: Paolo Bonzini --- hw/scsi-bus.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++-- hw/scsi.h | 16 ++++++++ 2 files changed, 120 insertions(+), 3 deletions(-) diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index dce620890..f0babb1f8 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -647,10 +647,8 @@ void scsi_req_build_sense(SCSIRequest *req, SCSISense sense) req->sense_len = 18; } -int32_t scsi_req_enqueue(SCSIRequest *req) +static void scsi_req_enqueue_internal(SCSIRequest *req) { - int32_t rc; - assert(!req->enqueued); scsi_req_ref(req); if (req->bus->info->get_sg_list) { @@ -660,7 +658,14 @@ int32_t scsi_req_enqueue(SCSIRequest *req) } req->enqueued = true; QTAILQ_INSERT_TAIL(&req->dev->requests, req, next); +} +int32_t scsi_req_enqueue(SCSIRequest *req) +{ + int32_t rc; + + assert(!req->retry); + scsi_req_enqueue_internal(req); scsi_req_ref(req); rc = req->ops->send_command(req, req->cmd.buf); scsi_req_unref(req); @@ -1442,6 +1447,102 @@ SCSIDevice *scsi_device_find(SCSIBus *bus, int channel, int id, int lun) return target_dev; } +/* SCSI request list. For simplicity, pv points to the whole device */ + +static void put_scsi_requests(QEMUFile *f, void *pv, size_t size) +{ + SCSIDevice *s = pv; + SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, s->qdev.parent_bus); + SCSIRequest *req; + + QTAILQ_FOREACH(req, &s->requests, next) { + assert(!req->io_canceled); + assert(req->status == -1); + assert(req->retry); + assert(req->enqueued); + + qemu_put_sbyte(f, 1); + qemu_put_buffer(f, req->cmd.buf, sizeof(req->cmd.buf)); + qemu_put_be32s(f, &req->tag); + qemu_put_be32s(f, &req->lun); + if (bus->info->save_request) { + bus->info->save_request(f, req); + } + if (req->ops->save_request) { + req->ops->save_request(f, req); + } + } + qemu_put_sbyte(f, 0); +} + +static int get_scsi_requests(QEMUFile *f, void *pv, size_t size) +{ + SCSIDevice *s = pv; + SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, s->qdev.parent_bus); + + while (qemu_get_sbyte(f)) { + uint8_t buf[SCSI_CMD_BUF_SIZE]; + uint32_t tag; + uint32_t lun; + SCSIRequest *req; + + qemu_get_buffer(f, buf, sizeof(buf)); + qemu_get_be32s(f, &tag); + qemu_get_be32s(f, &lun); + req = scsi_req_new(s, tag, lun, buf, NULL); + if (bus->info->load_request) { + req->hba_private = bus->info->load_request(f, req); + } + if (req->ops->load_request) { + req->ops->load_request(f, req); + } + + /* Just restart it later. */ + req->retry = true; + scsi_req_enqueue_internal(req); + + /* At this point, the request will be kept alive by the reference + * added by scsi_req_enqueue_internal, so we can release our reference. + * The HBA of course will add its own reference in the load_request + * callback if it needs to hold on the SCSIRequest. + */ + scsi_req_unref(req); + } + + return 0; +} + +const VMStateInfo vmstate_info_scsi_requests = { + .name = "scsi-requests", + .get = get_scsi_requests, + .put = put_scsi_requests, +}; + +const VMStateDescription vmstate_scsi_device = { + .name = "SCSIDevice", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(unit_attention.key, SCSIDevice), + VMSTATE_UINT8(unit_attention.asc, SCSIDevice), + VMSTATE_UINT8(unit_attention.ascq, SCSIDevice), + VMSTATE_BOOL(sense_is_ua, SCSIDevice), + VMSTATE_UINT8_ARRAY(sense, SCSIDevice, SCSI_SENSE_BUF_SIZE), + VMSTATE_UINT32(sense_len, SCSIDevice), + { + .name = "requests", + .version_id = 0, + .field_exists = NULL, + .size = 0, /* ouch */ + .info = &vmstate_info_scsi_requests, + .flags = VMS_SINGLE, + .offset = 0, + }, + VMSTATE_END_OF_LIST() + } +}; + static void scsi_device_class_init(ObjectClass *klass, void *data) { DeviceClass *k = DEVICE_CLASS(klass); diff --git a/hw/scsi.h b/hw/scsi.h index 34cdba8f6..2eb66f739 100644 --- a/hw/scsi.h +++ b/hw/scsi.h @@ -96,6 +96,16 @@ struct SCSIDevice uint64_t max_lba; }; +extern const VMStateDescription vmstate_scsi_device; + +#define VMSTATE_SCSI_DEVICE(_field, _state) { \ + .name = (stringify(_field)), \ + .size = sizeof(SCSIDevice), \ + .vmsd = &vmstate_scsi_device, \ + .flags = VMS_STRUCT, \ + .offset = vmstate_offset_value(_state, _field, SCSIDevice), \ +} + /* cdrom.c */ int cdrom_read_toc(int nb_sectors, uint8_t *buf, int msf, int start_track); int cdrom_read_toc_raw(int nb_sectors, uint8_t *buf, int msf, int session_num); @@ -109,6 +119,9 @@ struct SCSIReqOps { void (*write_data)(SCSIRequest *req); void (*cancel_io)(SCSIRequest *req); uint8_t *(*get_buf)(SCSIRequest *req); + + void (*save_request)(QEMUFile *f, SCSIRequest *req); + void (*load_request)(QEMUFile *f, SCSIRequest *req); }; struct SCSIBusInfo { @@ -118,6 +131,9 @@ struct SCSIBusInfo { void (*complete)(SCSIRequest *req, uint32_t arg, size_t resid); void (*cancel)(SCSIRequest *req); QEMUSGList *(*get_sg_list)(SCSIRequest *req); + + void (*save_request)(QEMUFile *f, SCSIRequest *req); + void *(*load_request)(QEMUFile *f, SCSIRequest *req); }; struct SCSIBus { From 56b1fc48233499a395827a7f370429a989f5dc86 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 15 Dec 2011 07:24:30 -0500 Subject: [PATCH 09/18] scsi-generic: add migration support Signed-off-by: Paolo Bonzini --- hw/scsi-generic.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/hw/scsi-generic.c b/hw/scsi-generic.c index 86014aa89..d856d23b3 100644 --- a/hw/scsi-generic.c +++ b/hw/scsi-generic.c @@ -59,6 +59,28 @@ typedef struct SCSIGenericReq { sg_io_hdr_t io_header; } SCSIGenericReq; +static void scsi_generic_save_request(QEMUFile *f, SCSIRequest *req) +{ + SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req); + + qemu_put_sbe32s(f, &r->buflen); + if (r->buflen && r->req.cmd.mode == SCSI_XFER_TO_DEV) { + assert(!r->req.sg); + qemu_put_buffer(f, r->buf, r->req.cmd.xfer); + } +} + +static void scsi_generic_load_request(QEMUFile *f, SCSIRequest *req) +{ + SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req); + + qemu_get_sbe32s(f, &r->buflen); + if (r->buflen && r->req.cmd.mode == SCSI_XFER_TO_DEV) { + assert(!r->req.sg); + qemu_get_buffer(f, r->buf, r->req.cmd.xfer); + } +} + static void scsi_free_request(SCSIRequest *req) { SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req); @@ -446,6 +468,8 @@ const SCSIReqOps scsi_generic_req_ops = { .write_data = scsi_write_data, .cancel_io = scsi_cancel_io, .get_buf = scsi_get_buf, + .load_request = scsi_generic_load_request, + .save_request = scsi_generic_save_request, }; static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun, @@ -474,6 +498,7 @@ static void scsi_generic_class_initfn(ObjectClass *klass, void *data) dc->desc = "pass through generic scsi device (/dev/sg*)"; dc->reset = scsi_generic_reset; dc->props = scsi_generic_properties; + dc->vmsd = &vmstate_scsi_device; } static TypeInfo scsi_generic_info = { From 43b978b932488d58fd3a79b43466baa06d032092 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 15 Dec 2011 07:24:47 -0500 Subject: [PATCH 10/18] scsi-disk: add migration support Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 2a24840ef..ec8e7cbff 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -111,12 +111,12 @@ static void scsi_cancel_io(SCSIRequest *req) r->req.aiocb = NULL; } -static uint32_t scsi_init_iovec(SCSIDiskReq *r) +static uint32_t scsi_init_iovec(SCSIDiskReq *r, size_t size) { SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); if (!r->iov.iov_base) { - r->buflen = SCSI_DMA_BUF_SIZE; + r->buflen = size; r->iov.iov_base = qemu_blockalign(s->qdev.conf.bs, r->buflen); } r->iov.iov_len = MIN(r->sector_count * 512, r->buflen); @@ -124,6 +124,35 @@ static uint32_t scsi_init_iovec(SCSIDiskReq *r) return r->qiov.size / 512; } +static void scsi_disk_save_request(QEMUFile *f, SCSIRequest *req) +{ + SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); + + qemu_put_be64s(f, &r->sector); + qemu_put_be32s(f, &r->sector_count); + qemu_put_be32s(f, &r->buflen); + if (r->buflen && r->req.cmd.mode == SCSI_XFER_TO_DEV) { + qemu_put_buffer(f, r->iov.iov_base, r->iov.iov_len); + } +} + +static void scsi_disk_load_request(QEMUFile *f, SCSIRequest *req) +{ + SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req); + + qemu_get_be64s(f, &r->sector); + qemu_get_be32s(f, &r->sector_count); + qemu_get_be32s(f, &r->buflen); + if (r->buflen) { + scsi_init_iovec(r, r->buflen); + if (r->req.cmd.mode == SCSI_XFER_TO_DEV) { + qemu_get_buffer(f, r->iov.iov_base, r->iov.iov_len); + } + } + + qemu_iovec_init_external(&r->qiov, &r->iov, 1); +} + static void scsi_dma_complete(void *opaque, int ret) { SCSIDiskReq *r = (SCSIDiskReq *)opaque; @@ -241,7 +270,7 @@ static void scsi_read_data(SCSIRequest *req) r->req.aiocb = dma_bdrv_read(s->qdev.conf.bs, r->req.sg, r->sector, scsi_dma_complete, r); } else { - n = scsi_init_iovec(r); + n = scsi_init_iovec(r, SCSI_DMA_BUF_SIZE); bdrv_acct_start(s->qdev.conf.bs, &r->acct, n * BDRV_SECTOR_SIZE, BDRV_ACCT_READ); r->req.aiocb = bdrv_aio_readv(s->qdev.conf.bs, r->sector, &r->qiov, n, scsi_read_complete, r); @@ -316,7 +345,7 @@ static void scsi_write_complete(void * opaque, int ret) if (r->sector_count == 0) { scsi_req_complete(&r->req, GOOD); } else { - scsi_init_iovec(r); + scsi_init_iovec(r, SCSI_DMA_BUF_SIZE); DPRINTF("Write complete tag=0x%x more=%d\n", r->req.tag, r->qiov.size); scsi_req_data(&r->req, r->qiov.size); } @@ -1621,6 +1650,8 @@ static const SCSIReqOps scsi_disk_reqops = { .write_data = scsi_write_data, .cancel_io = scsi_cancel_io, .get_buf = scsi_get_buf, + .load_request = scsi_disk_load_request, + .save_request = scsi_disk_save_request, }; static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun, @@ -1755,6 +1786,22 @@ static Property scsi_hd_properties[] = { DEFINE_PROP_END_OF_LIST(), }; +static const VMStateDescription vmstate_scsi_disk_state = { + .name = "scsi-disk", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_SCSI_DEVICE(qdev, SCSIDiskState), + VMSTATE_BOOL(media_changed, SCSIDiskState), + VMSTATE_BOOL(media_event, SCSIDiskState), + VMSTATE_BOOL(eject_request, SCSIDiskState), + VMSTATE_BOOL(tray_open, SCSIDiskState), + VMSTATE_BOOL(tray_locked, SCSIDiskState), + VMSTATE_END_OF_LIST() + } +}; + static void scsi_hd_class_initfn(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -1768,6 +1815,7 @@ static void scsi_hd_class_initfn(ObjectClass *klass, void *data) dc->desc = "virtual SCSI disk"; dc->reset = scsi_disk_reset; dc->props = scsi_hd_properties; + dc->vmsd = &vmstate_scsi_disk_state; } static TypeInfo scsi_hd_info = { @@ -1795,6 +1843,7 @@ static void scsi_cd_class_initfn(ObjectClass *klass, void *data) dc->desc = "virtual SCSI CD-ROM"; dc->reset = scsi_disk_reset; dc->props = scsi_cd_properties; + dc->vmsd = &vmstate_scsi_disk_state; } static TypeInfo scsi_cd_info = { @@ -1822,6 +1871,7 @@ static void scsi_block_class_initfn(ObjectClass *klass, void *data) dc->desc = "SCSI block device passthrough"; dc->reset = scsi_disk_reset; dc->props = scsi_block_properties; + dc->vmsd = &vmstate_scsi_disk_state; } static TypeInfo scsi_block_info = { @@ -1851,6 +1901,7 @@ static void scsi_disk_class_initfn(ObjectClass *klass, void *data) dc->desc = "virtual SCSI disk or CD-ROM (legacy)"; dc->reset = scsi_disk_reset; dc->props = scsi_disk_properties; + dc->vmsd = &vmstate_scsi_disk_state; } static TypeInfo scsi_disk_info = { From 973abc7f4159fabe26734680158ecc3495d791d8 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Fri, 11 Feb 2011 08:40:59 +0000 Subject: [PATCH 11/18] virtio-scsi: Add virtio-scsi stub device Add a useless virtio SCSI HBA device: qemu -device virtio-scsi-pci Signed-off-by: Stefan Hajnoczi Reviewed-by: Stefan Hajnoczi Signed-off-by: Paolo Bonzini --- Makefile.target | 1 + default-configs/pci.mak | 1 + default-configs/s390x-softmmu.mak | 1 + hw/pci.h | 1 + hw/s390-virtio-bus.c | 33 +++++ hw/s390-virtio-bus.h | 2 + hw/virtio-pci.c | 56 ++++++++ hw/virtio-pci.h | 2 + hw/virtio-scsi.c | 228 ++++++++++++++++++++++++++++++ hw/virtio-scsi.h | 36 +++++ hw/virtio.h | 3 + 11 files changed, 364 insertions(+) create mode 100644 hw/virtio-scsi.c create mode 100644 hw/virtio-scsi.h diff --git a/Makefile.target b/Makefile.target index 651500e06..eef4accb2 100644 --- a/Makefile.target +++ b/Makefile.target @@ -200,6 +200,7 @@ obj-y = arch_init.o cpus.o monitor.o machine.o gdbstub.o balloon.o ioport.o # need to fix this properly obj-$(CONFIG_NO_PCI) += pci-stub.o obj-$(CONFIG_VIRTIO) += virtio.o virtio-blk.o virtio-balloon.o virtio-net.o virtio-serial-bus.o +obj-$(CONFIG_VIRTIO_SCSI) += virtio-scsi.o obj-y += vhost_net.o obj-$(CONFIG_VHOST_NET) += vhost.o obj-$(CONFIG_REALLY_VIRTFS) += 9pfs/virtio-9p-device.o diff --git a/default-configs/pci.mak b/default-configs/pci.mak index 9d3e1dbda..21e4ccfb0 100644 --- a/default-configs/pci.mak +++ b/default-configs/pci.mak @@ -1,5 +1,6 @@ CONFIG_PCI=y CONFIG_VIRTIO_PCI=y +CONFIG_VIRTIO_SCSI=y CONFIG_VIRTIO=y CONFIG_USB_UHCI=y CONFIG_USB_OHCI=y diff --git a/default-configs/s390x-softmmu.mak b/default-configs/s390x-softmmu.mak index 300572920..e5888037c 100644 --- a/default-configs/s390x-softmmu.mak +++ b/default-configs/s390x-softmmu.mak @@ -1 +1,2 @@ CONFIG_VIRTIO=y +CONFIG_VIRTIO_SCSI=y diff --git a/hw/pci.h b/hw/pci.h index 33b0b184e..ff4c12d45 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -75,6 +75,7 @@ #define PCI_DEVICE_ID_VIRTIO_BLOCK 0x1001 #define PCI_DEVICE_ID_VIRTIO_BALLOON 0x1002 #define PCI_DEVICE_ID_VIRTIO_CONSOLE 0x1003 +#define PCI_DEVICE_ID_VIRTIO_SCSI 0x1004 #define FMT_PCIBUS PRIx64 diff --git a/hw/s390-virtio-bus.c b/hw/s390-virtio-bus.c index 9d480564d..c450e4bb5 100644 --- a/hw/s390-virtio-bus.c +++ b/hw/s390-virtio-bus.c @@ -169,6 +169,18 @@ static int s390_virtio_serial_init(VirtIOS390Device *dev) return r; } +static int s390_virtio_scsi_init(VirtIOS390Device *dev) +{ + VirtIODevice *vdev; + + vdev = virtio_scsi_init((DeviceState *)dev, &dev->scsi); + if (!vdev) { + return -1; + } + + return s390_virtio_device_init(dev, vdev); +} + static uint64_t s390_virtio_device_vq_token(VirtIOS390Device *dev, int vq) { ram_addr_t token_off; @@ -433,6 +445,26 @@ static TypeInfo virtio_s390_device_info = { .abstract = true, }; +static Property s390_virtio_scsi_properties[] = { + DEFINE_VIRTIO_SCSI_PROPERTIES(VirtIOS390Device, host_features, scsi), + DEFINE_PROP_END_OF_LIST(), +}; + +static void s390_virtio_scsi_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtIOS390DeviceClass *k = VIRTIO_S390_DEVICE_CLASS(klass); + + k->init = s390_virtio_scsi_init; + dc->props = s390_virtio_scsi_properties; +} + +static TypeInfo s390_virtio_scsi = { + .name = "virtio-scsi-s390", + .parent = TYPE_VIRTIO_S390_DEVICE, + .instance_size = sizeof(VirtIOS390Device), + .class_init = s390_virtio_scsi_class_init, +}; /***************** S390 Virtio Bus Bridge Device *******************/ /* Only required to have the virtio bus as child in the system bus */ @@ -465,6 +497,7 @@ static void s390_virtio_register_types(void) type_register_static(&s390_virtio_serial); type_register_static(&s390_virtio_blk); type_register_static(&s390_virtio_net); + type_register_static(&s390_virtio_scsi); type_register_static(&s390_virtio_bridge_info); } diff --git a/hw/s390-virtio-bus.h b/hw/s390-virtio-bus.h index b5e59b7d4..0e60bc0fa 100644 --- a/hw/s390-virtio-bus.h +++ b/hw/s390-virtio-bus.h @@ -19,6 +19,7 @@ #include "virtio-net.h" #include "virtio-serial.h" +#include "virtio-scsi.h" #define VIRTIO_DEV_OFFS_TYPE 0 /* 8 bits */ #define VIRTIO_DEV_OFFS_NUM_VQ 1 /* 8 bits */ @@ -67,6 +68,7 @@ struct VirtIOS390Device { uint32_t host_features; virtio_serial_conf serial; virtio_net_conf net; + VirtIOSCSIConf scsi; }; typedef struct VirtIOS390Bus { diff --git a/hw/virtio-pci.c b/hw/virtio-pci.c index 907b52a25..a0fb7c1b9 100644 --- a/hw/virtio-pci.c +++ b/hw/virtio-pci.c @@ -21,6 +21,7 @@ #include "virtio-blk.h" #include "virtio-net.h" #include "virtio-serial.h" +#include "virtio-scsi.h" #include "pci.h" #include "qemu-error.h" #include "msix.h" @@ -930,12 +931,67 @@ static TypeInfo virtio_balloon_info = { .class_init = virtio_balloon_class_init, }; +static int virtio_scsi_init_pci(PCIDevice *pci_dev) +{ + VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev); + VirtIODevice *vdev; + + vdev = virtio_scsi_init(&pci_dev->qdev, &proxy->scsi); + if (!vdev) { + return -EINVAL; + } + + vdev->nvectors = proxy->nvectors; + virtio_init_pci(proxy, vdev); + + /* make the actual value visible */ + proxy->nvectors = vdev->nvectors; + return 0; +} + +static int virtio_scsi_exit_pci(PCIDevice *pci_dev) +{ + VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev); + + virtio_scsi_exit(proxy->vdev); + return virtio_exit_pci(pci_dev); +} + +static Property virtio_scsi_properties[] = { + DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 2), + DEFINE_VIRTIO_SCSI_PROPERTIES(VirtIOPCIProxy, host_features, scsi), + DEFINE_PROP_END_OF_LIST(), +}; + +static void virtio_scsi_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = virtio_scsi_init_pci; + k->exit = virtio_scsi_exit_pci; + k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET; + k->device_id = PCI_DEVICE_ID_VIRTIO_SCSI; + k->revision = 0x00; + k->class_id = PCI_CLASS_STORAGE_SCSI; + dc->reset = virtio_pci_reset; + dc->props = virtio_scsi_properties; +} + +static TypeInfo virtio_scsi_info = { + .name = "virtio-scsi-pci", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(VirtIOPCIProxy), + .class_init = virtio_scsi_class_init, +}; + static void virtio_pci_register_types(void) { type_register_static(&virtio_blk_info); type_register_static(&virtio_net_info); type_register_static(&virtio_serial_info); type_register_static(&virtio_balloon_info); + type_register_static(&virtio_scsi_info); } type_init(virtio_pci_register_types) diff --git a/hw/virtio-pci.h b/hw/virtio-pci.h index 344c22b68..e5604282e 100644 --- a/hw/virtio-pci.h +++ b/hw/virtio-pci.h @@ -17,6 +17,7 @@ #include "virtio-net.h" #include "virtio-serial.h" +#include "virtio-scsi.h" /* Performance improves when virtqueue kick processing is decoupled from the * vcpu thread using ioeventfd for some devices. */ @@ -40,6 +41,7 @@ typedef struct { #endif virtio_serial_conf serial; virtio_net_conf net; + VirtIOSCSIConf scsi; bool ioeventfd_disabled; bool ioeventfd_started; } VirtIOPCIProxy; diff --git a/hw/virtio-scsi.c b/hw/virtio-scsi.c new file mode 100644 index 000000000..a297fedd0 --- /dev/null +++ b/hw/virtio-scsi.c @@ -0,0 +1,228 @@ +/* + * Virtio SCSI HBA + * + * Copyright IBM, Corp. 2010 + * Copyright Red Hat, Inc. 2011 + * + * Authors: + * Stefan Hajnoczi + * Paolo Bonzini + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "virtio-scsi.h" +#include +#include + +#define VIRTIO_SCSI_VQ_SIZE 128 +#define VIRTIO_SCSI_CDB_SIZE 32 +#define VIRTIO_SCSI_SENSE_SIZE 96 +#define VIRTIO_SCSI_MAX_CHANNEL 0 +#define VIRTIO_SCSI_MAX_TARGET 255 +#define VIRTIO_SCSI_MAX_LUN 16383 + +/* Response codes */ +#define VIRTIO_SCSI_S_OK 0 +#define VIRTIO_SCSI_S_OVERRUN 1 +#define VIRTIO_SCSI_S_ABORTED 2 +#define VIRTIO_SCSI_S_BAD_TARGET 3 +#define VIRTIO_SCSI_S_RESET 4 +#define VIRTIO_SCSI_S_BUSY 5 +#define VIRTIO_SCSI_S_TRANSPORT_FAILURE 6 +#define VIRTIO_SCSI_S_TARGET_FAILURE 7 +#define VIRTIO_SCSI_S_NEXUS_FAILURE 8 +#define VIRTIO_SCSI_S_FAILURE 9 +#define VIRTIO_SCSI_S_FUNCTION_SUCCEEDED 10 +#define VIRTIO_SCSI_S_FUNCTION_REJECTED 11 +#define VIRTIO_SCSI_S_INCORRECT_LUN 12 + +/* Controlq type codes. */ +#define VIRTIO_SCSI_T_TMF 0 +#define VIRTIO_SCSI_T_AN_QUERY 1 +#define VIRTIO_SCSI_T_AN_SUBSCRIBE 2 + +/* Valid TMF subtypes. */ +#define VIRTIO_SCSI_T_TMF_ABORT_TASK 0 +#define VIRTIO_SCSI_T_TMF_ABORT_TASK_SET 1 +#define VIRTIO_SCSI_T_TMF_CLEAR_ACA 2 +#define VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET 3 +#define VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET 4 +#define VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET 5 +#define VIRTIO_SCSI_T_TMF_QUERY_TASK 6 +#define VIRTIO_SCSI_T_TMF_QUERY_TASK_SET 7 + +/* Events. */ +#define VIRTIO_SCSI_T_EVENTS_MISSED 0x80000000 +#define VIRTIO_SCSI_T_NO_EVENT 0 +#define VIRTIO_SCSI_T_TRANSPORT_RESET 1 +#define VIRTIO_SCSI_T_ASYNC_NOTIFY 2 + +/* SCSI command request, followed by data-out */ +typedef struct { + uint8_t lun[8]; /* Logical Unit Number */ + uint64_t tag; /* Command identifier */ + uint8_t task_attr; /* Task attribute */ + uint8_t prio; + uint8_t crn; + uint8_t cdb[]; +} QEMU_PACKED VirtIOSCSICmdReq; + +/* Response, followed by sense data and data-in */ +typedef struct { + uint32_t sense_len; /* Sense data length */ + uint32_t resid; /* Residual bytes in data buffer */ + uint16_t status_qualifier; /* Status qualifier */ + uint8_t status; /* Command completion status */ + uint8_t response; /* Response values */ + uint8_t sense[]; +} QEMU_PACKED VirtIOSCSICmdResp; + +/* Task Management Request */ +typedef struct { + uint32_t type; + uint32_t subtype; + uint8_t lun[8]; + uint64_t tag; +} QEMU_PACKED VirtIOSCSICtrlTMFReq; + +typedef struct { + uint8_t response; +} QEMU_PACKED VirtIOSCSICtrlTMFResp; + +/* Asynchronous notification query/subscription */ +typedef struct { + uint32_t type; + uint8_t lun[8]; + uint32_t event_requested; +} QEMU_PACKED VirtIOSCSICtrlANReq; + +typedef struct { + uint32_t event_actual; + uint8_t response; +} QEMU_PACKED VirtIOSCSICtrlANResp; + +typedef struct { + uint32_t event; + uint8_t lun[8]; + uint32_t reason; +} QEMU_PACKED VirtIOSCSIEvent; + +typedef struct { + uint32_t num_queues; + uint32_t seg_max; + uint32_t max_sectors; + uint32_t cmd_per_lun; + uint32_t event_info_size; + uint32_t sense_size; + uint32_t cdb_size; + uint16_t max_channel; + uint16_t max_target; + uint32_t max_lun; +} QEMU_PACKED VirtIOSCSIConfig; + +typedef struct { + VirtIODevice vdev; + DeviceState *qdev; + VirtIOSCSIConf *conf; + + VirtQueue *ctrl_vq; + VirtQueue *event_vq; + VirtQueue *cmd_vq; + uint32_t sense_size; + uint32_t cdb_size; +} VirtIOSCSI; + +static void virtio_scsi_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) +{ + /* TODO */ +} + +static void virtio_scsi_handle_cmd(VirtIODevice *vdev, VirtQueue *vq) +{ + /* TODO */ +} + +static void virtio_scsi_get_config(VirtIODevice *vdev, + uint8_t *config) +{ + VirtIOSCSIConfig *scsiconf = (VirtIOSCSIConfig *)config; + VirtIOSCSI *s = (VirtIOSCSI *)vdev; + + stl_raw(&scsiconf->num_queues, s->conf->num_queues); + stl_raw(&scsiconf->seg_max, 128 - 2); + stl_raw(&scsiconf->max_sectors, s->conf->max_sectors); + stl_raw(&scsiconf->cmd_per_lun, s->conf->cmd_per_lun); + stl_raw(&scsiconf->event_info_size, sizeof(VirtIOSCSIEvent)); + stl_raw(&scsiconf->sense_size, s->sense_size); + stl_raw(&scsiconf->cdb_size, s->cdb_size); + stl_raw(&scsiconf->max_channel, VIRTIO_SCSI_MAX_CHANNEL); + stl_raw(&scsiconf->max_target, VIRTIO_SCSI_MAX_TARGET); + stl_raw(&scsiconf->max_lun, VIRTIO_SCSI_MAX_LUN); +} + +static void virtio_scsi_set_config(VirtIODevice *vdev, + const uint8_t *config) +{ + VirtIOSCSIConfig *scsiconf = (VirtIOSCSIConfig *)config; + VirtIOSCSI *s = (VirtIOSCSI *)vdev; + + if ((uint32_t) ldl_raw(&scsiconf->sense_size) >= 65536 || + (uint32_t) ldl_raw(&scsiconf->cdb_size) >= 256) { + error_report("bad data written to virtio-scsi configuration space"); + exit(1); + } + + s->sense_size = ldl_raw(&scsiconf->sense_size); + s->cdb_size = ldl_raw(&scsiconf->cdb_size); +} + +static uint32_t virtio_scsi_get_features(VirtIODevice *vdev, + uint32_t requested_features) +{ + return requested_features; +} + +static void virtio_scsi_reset(VirtIODevice *vdev) +{ + VirtIOSCSI *s = (VirtIOSCSI *)vdev; + + s->sense_size = VIRTIO_SCSI_SENSE_SIZE; + s->cdb_size = VIRTIO_SCSI_CDB_SIZE; +} + +VirtIODevice *virtio_scsi_init(DeviceState *dev, VirtIOSCSIConf *proxyconf) +{ + VirtIOSCSI *s; + + s = (VirtIOSCSI *)virtio_common_init("virtio-scsi", VIRTIO_ID_SCSI, + sizeof(VirtIOSCSIConfig), + sizeof(VirtIOSCSI)); + + s->qdev = dev; + s->conf = proxyconf; + + /* TODO set up vdev function pointers */ + s->vdev.get_config = virtio_scsi_get_config; + s->vdev.set_config = virtio_scsi_set_config; + s->vdev.get_features = virtio_scsi_get_features; + s->vdev.reset = virtio_scsi_reset; + + s->ctrl_vq = virtio_add_queue(&s->vdev, VIRTIO_SCSI_VQ_SIZE, + virtio_scsi_handle_ctrl); + s->event_vq = virtio_add_queue(&s->vdev, VIRTIO_SCSI_VQ_SIZE, + NULL); + s->cmd_vq = virtio_add_queue(&s->vdev, VIRTIO_SCSI_VQ_SIZE, + virtio_scsi_handle_cmd); + + /* TODO savevm */ + + return &s->vdev; +} + +void virtio_scsi_exit(VirtIODevice *vdev) +{ + virtio_cleanup(vdev); +} diff --git a/hw/virtio-scsi.h b/hw/virtio-scsi.h new file mode 100644 index 000000000..4bc889de0 --- /dev/null +++ b/hw/virtio-scsi.h @@ -0,0 +1,36 @@ +/* + * Virtio SCSI HBA + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Stefan Hajnoczi + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#ifndef _QEMU_VIRTIO_SCSI_H +#define _QEMU_VIRTIO_SCSI_H + +#include "virtio.h" +#include "net.h" +#include "pci.h" + +/* The ID for virtio_scsi */ +#define VIRTIO_ID_SCSI 8 + +struct VirtIOSCSIConf { + uint32_t num_queues; + uint32_t max_sectors; + uint32_t cmd_per_lun; +}; + +#define DEFINE_VIRTIO_SCSI_PROPERTIES(_state, _features_field, _conf_field) \ + DEFINE_VIRTIO_COMMON_FEATURES(_state, _features_field), \ + DEFINE_PROP_UINT32("num_queues", _state, _conf_field.num_queues, 1), \ + DEFINE_PROP_UINT32("max_sectors", _state, _conf_field.max_sectors, 0xFFFF), \ + DEFINE_PROP_UINT32("cmd_per_lun", _state, _conf_field.cmd_per_lun, 128) + +#endif /* _QEMU_VIRTIO_SCSI_H */ diff --git a/hw/virtio.h b/hw/virtio.h index 25f55647b..400c092c9 100644 --- a/hw/virtio.h +++ b/hw/virtio.h @@ -199,6 +199,8 @@ VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf, typedef struct virtio_serial_conf virtio_serial_conf; VirtIODevice *virtio_serial_init(DeviceState *dev, virtio_serial_conf *serial); VirtIODevice *virtio_balloon_init(DeviceState *dev); +typedef struct VirtIOSCSIConf VirtIOSCSIConf; +VirtIODevice *virtio_scsi_init(DeviceState *dev, VirtIOSCSIConf *conf); #ifdef CONFIG_LINUX VirtIODevice *virtio_9p_init(DeviceState *dev, V9fsConf *conf); #endif @@ -208,6 +210,7 @@ void virtio_net_exit(VirtIODevice *vdev); void virtio_blk_exit(VirtIODevice *vdev); void virtio_serial_exit(VirtIODevice *vdev); void virtio_balloon_exit(VirtIODevice *vdev); +void virtio_scsi_exit(VirtIODevice *vdev); #define DEFINE_VIRTIO_COMMON_FEATURES(_state, _field) \ DEFINE_PROP_BIT("indirect_desc", _state, _field, \ From 326799c0d00bd4157555bf867e97198e68dbfdc9 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Sun, 13 Feb 2011 10:55:52 +0000 Subject: [PATCH 12/18] virtio-scsi: Add basic request processing infrastructure Signed-off-by: Stefan Hajnoczi Reviewed-by: Stefan Hajnoczi Signed-off-by: Paolo Bonzini --- hw/virtio-scsi.c | 142 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 2 deletions(-) diff --git a/hw/virtio-scsi.c b/hw/virtio-scsi.c index a297fedd0..a9f883c10 100644 --- a/hw/virtio-scsi.c +++ b/hw/virtio-scsi.c @@ -135,14 +135,152 @@ typedef struct { uint32_t cdb_size; } VirtIOSCSI; +typedef struct VirtIOSCSIReq { + VirtIOSCSI *dev; + VirtQueue *vq; + VirtQueueElement elem; + QEMUSGList qsgl; + SCSIRequest *sreq; + union { + char *buf; + VirtIOSCSICmdReq *cmd; + VirtIOSCSICtrlTMFReq *tmf; + VirtIOSCSICtrlANReq *an; + } req; + union { + char *buf; + VirtIOSCSICmdResp *cmd; + VirtIOSCSICtrlTMFResp *tmf; + VirtIOSCSICtrlANResp *an; + VirtIOSCSIEvent *event; + } resp; +} VirtIOSCSIReq; + +static void virtio_scsi_complete_req(VirtIOSCSIReq *req) +{ + VirtIOSCSI *s = req->dev; + VirtQueue *vq = req->vq; + virtqueue_push(vq, &req->elem, req->qsgl.size + req->elem.in_sg[0].iov_len); + qemu_sglist_destroy(&req->qsgl); + if (req->sreq) { + req->sreq->hba_private = NULL; + scsi_req_unref(req->sreq); + } + g_free(req); + virtio_notify(&s->vdev, vq); +} + +static void virtio_scsi_bad_req(void) +{ + error_report("wrong size for virtio-scsi headers"); + exit(1); +} + +static void qemu_sgl_init_external(QEMUSGList *qsgl, struct iovec *sg, + target_phys_addr_t *addr, int num) +{ + memset(qsgl, 0, sizeof(*qsgl)); + while (num--) { + qemu_sglist_add(qsgl, *(addr++), (sg++)->iov_len); + } +} + +static void virtio_scsi_parse_req(VirtIOSCSI *s, VirtQueue *vq, + VirtIOSCSIReq *req) +{ + assert(req->elem.out_num && req->elem.in_num); + req->vq = vq; + req->dev = s; + req->sreq = NULL; + 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) { + qemu_sgl_init_external(&req->qsgl, &req->elem.out_sg[1], + &req->elem.out_addr[1], + req->elem.out_num - 1); + } else { + qemu_sgl_init_external(&req->qsgl, &req->elem.in_sg[1], + &req->elem.in_addr[1], + req->elem.in_num - 1); + } +} + +static VirtIOSCSIReq *virtio_scsi_pop_req(VirtIOSCSI *s, VirtQueue *vq) +{ + VirtIOSCSIReq *req; + req = g_malloc(sizeof(*req)); + if (!virtqueue_pop(vq, &req->elem)) { + g_free(req); + return NULL; + } + + virtio_scsi_parse_req(s, vq, req); + return req; +} + +static void virtio_scsi_fail_ctrl_req(VirtIOSCSIReq *req) +{ + if (req->req.tmf->type == VIRTIO_SCSI_T_TMF) { + req->resp.tmf->response = VIRTIO_SCSI_S_FAILURE; + } else { + req->resp.an->response = VIRTIO_SCSI_S_FAILURE; + } + + virtio_scsi_complete_req(req); +} + static void virtio_scsi_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) { - /* TODO */ + VirtIOSCSI *s = (VirtIOSCSI *)vdev; + VirtIOSCSIReq *req; + + while ((req = virtio_scsi_pop_req(s, vq))) { + virtio_scsi_fail_ctrl_req(req); + } +} + +static void virtio_scsi_fail_cmd_req(VirtIOSCSI *s, VirtIOSCSIReq *req) +{ + req->resp.cmd->response = VIRTIO_SCSI_S_FAILURE; + virtio_scsi_complete_req(req); } static void virtio_scsi_handle_cmd(VirtIODevice *vdev, VirtQueue *vq) { - /* TODO */ + VirtIOSCSI *s = (VirtIOSCSI *)vdev; + VirtIOSCSIReq *req; + + while ((req = virtio_scsi_pop_req(s, vq))) { + int out_size, in_size; + if (req->elem.out_num < 1 || req->elem.in_num < 1) { + virtio_scsi_bad_req(); + } + + out_size = req->elem.out_sg[0].iov_len; + in_size = req->elem.in_sg[0].iov_len; + if (out_size < sizeof(VirtIOSCSICmdReq) + s->cdb_size || + in_size < sizeof(VirtIOSCSICmdResp) + s->sense_size) { + virtio_scsi_bad_req(); + } + + if (req->elem.out_num > 1 && req->elem.in_num > 1) { + virtio_scsi_fail_cmd_req(s, req); + continue; + } + + req->resp.cmd->resid = 0; + req->resp.cmd->status_qualifier = 0; + req->resp.cmd->status = CHECK_CONDITION; + req->resp.cmd->sense_len = 4; + req->resp.cmd->sense[0] = 0xf0; /* Fixed format current sense */ + req->resp.cmd->sense[1] = ILLEGAL_REQUEST; + req->resp.cmd->sense[2] = 0x20; + req->resp.cmd->sense[3] = 0x00; + req->resp.cmd->response = VIRTIO_SCSI_S_OK; + + virtio_scsi_complete_req(req); + } } static void virtio_scsi_get_config(VirtIODevice *vdev, From 2ccdcd8d2bd083b51c9648acd982c3796612aa52 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 14 Nov 2011 16:58:41 +0100 Subject: [PATCH 13/18] virtio-scsi: add basic SCSI bus operation Reviewed-by: Stefan Hajnoczi Signed-off-by: Paolo Bonzini --- hw/virtio-scsi.c | 109 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 12 deletions(-) diff --git a/hw/virtio-scsi.c b/hw/virtio-scsi.c index a9f883c10..f4eb8c7e6 100644 --- a/hw/virtio-scsi.c +++ b/hw/virtio-scsi.c @@ -128,6 +128,7 @@ typedef struct { DeviceState *qdev; VirtIOSCSIConf *conf; + SCSIBus bus; VirtQueue *ctrl_vq; VirtQueue *event_vq; VirtQueue *cmd_vq; @@ -156,6 +157,22 @@ typedef struct VirtIOSCSIReq { } resp; } VirtIOSCSIReq; +static inline int virtio_scsi_get_lun(uint8_t *lun) +{ + return ((lun[2] << 8) | lun[3]) & 0x3FFF; +} + +static inline SCSIDevice *virtio_scsi_device_find(VirtIOSCSI *s, uint8_t *lun) +{ + if (lun[0] != 1) { + return NULL; + } + if (lun[2] != 0 && !(lun[2] >= 0x40 && lun[2] < 0x80)) { + return NULL; + } + return scsi_device_find(&s->bus, 0, lun[1], virtio_scsi_get_lun(lun)); +} + static void virtio_scsi_complete_req(VirtIOSCSIReq *req) { VirtIOSCSI *s = req->dev; @@ -240,7 +257,42 @@ static void virtio_scsi_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) } } -static void virtio_scsi_fail_cmd_req(VirtIOSCSI *s, VirtIOSCSIReq *req) +static void virtio_scsi_command_complete(SCSIRequest *r, uint32_t status, + size_t resid) +{ + VirtIOSCSIReq *req = r->hba_private; + + req->resp.cmd->response = VIRTIO_SCSI_S_OK; + req->resp.cmd->status = status; + if (req->resp.cmd->status == GOOD) { + req->resp.cmd->resid = resid; + } else { + req->resp.cmd->resid = 0; + req->resp.cmd->sense_len = + scsi_req_get_sense(r, req->resp.cmd->sense, VIRTIO_SCSI_SENSE_SIZE); + } + virtio_scsi_complete_req(req); +} + +static QEMUSGList *virtio_scsi_get_sg_list(SCSIRequest *r) +{ + VirtIOSCSIReq *req = r->hba_private; + + return &req->qsgl; +} + +static void virtio_scsi_request_cancelled(SCSIRequest *r) +{ + VirtIOSCSIReq *req = r->hba_private; + + if (!req) { + return; + } + req->resp.cmd->response = VIRTIO_SCSI_S_ABORTED; + virtio_scsi_complete_req(req); +} + +static void virtio_scsi_fail_cmd_req(VirtIOSCSIReq *req) { req->resp.cmd->response = VIRTIO_SCSI_S_FAILURE; virtio_scsi_complete_req(req); @@ -250,8 +302,10 @@ static void virtio_scsi_handle_cmd(VirtIODevice *vdev, VirtQueue *vq) { VirtIOSCSI *s = (VirtIOSCSI *)vdev; VirtIOSCSIReq *req; + int n; while ((req = virtio_scsi_pop_req(s, vq))) { + SCSIDevice *d; int out_size, in_size; if (req->elem.out_num < 1 || req->elem.in_num < 1) { virtio_scsi_bad_req(); @@ -265,21 +319,36 @@ static void virtio_scsi_handle_cmd(VirtIODevice *vdev, VirtQueue *vq) } if (req->elem.out_num > 1 && req->elem.in_num > 1) { - virtio_scsi_fail_cmd_req(s, req); + virtio_scsi_fail_cmd_req(req); continue; } - req->resp.cmd->resid = 0; - req->resp.cmd->status_qualifier = 0; - req->resp.cmd->status = CHECK_CONDITION; - req->resp.cmd->sense_len = 4; - req->resp.cmd->sense[0] = 0xf0; /* Fixed format current sense */ - req->resp.cmd->sense[1] = ILLEGAL_REQUEST; - req->resp.cmd->sense[2] = 0x20; - req->resp.cmd->sense[3] = 0x00; - req->resp.cmd->response = VIRTIO_SCSI_S_OK; + d = virtio_scsi_device_find(s, req->req.cmd->lun); + if (!d) { + req->resp.cmd->response = VIRTIO_SCSI_S_BAD_TARGET; + virtio_scsi_complete_req(req); + continue; + } + req->sreq = scsi_req_new(d, req->req.cmd->tag, + virtio_scsi_get_lun(req->req.cmd->lun), + req->req.cmd->cdb, req); - virtio_scsi_complete_req(req); + if (req->sreq->cmd.mode != SCSI_XFER_NONE) { + int req_mode = + (req->elem.in_num > 1 ? SCSI_XFER_FROM_DEV : SCSI_XFER_TO_DEV); + + if (req->sreq->cmd.mode != req_mode || + req->sreq->cmd.xfer > req->qsgl.size) { + req->resp.cmd->response = VIRTIO_SCSI_S_OVERRUN; + virtio_scsi_complete_req(req); + continue; + } + } + + n = scsi_req_enqueue(req->sreq); + if (n) { + scsi_req_continue(req->sreq); + } } } @@ -331,6 +400,17 @@ static void virtio_scsi_reset(VirtIODevice *vdev) s->cdb_size = VIRTIO_SCSI_CDB_SIZE; } +static struct SCSIBusInfo virtio_scsi_scsi_info = { + .tcq = true, + .max_channel = VIRTIO_SCSI_MAX_CHANNEL, + .max_target = VIRTIO_SCSI_MAX_TARGET, + .max_lun = VIRTIO_SCSI_MAX_LUN, + + .complete = virtio_scsi_command_complete, + .cancel = virtio_scsi_request_cancelled, + .get_sg_list = virtio_scsi_get_sg_list, +}; + VirtIODevice *virtio_scsi_init(DeviceState *dev, VirtIOSCSIConf *proxyconf) { VirtIOSCSI *s; @@ -355,6 +435,11 @@ VirtIODevice *virtio_scsi_init(DeviceState *dev, VirtIOSCSIConf *proxyconf) s->cmd_vq = virtio_add_queue(&s->vdev, VIRTIO_SCSI_VQ_SIZE, virtio_scsi_handle_cmd); + scsi_bus_new(&s->bus, dev, &virtio_scsi_scsi_info); + if (!dev->hotplugged) { + scsi_bus_legacy_handle_cmdline(&s->bus); + } + /* TODO savevm */ return &s->vdev; From 06114d72f6dba13c66a5d16eeb52d5b52165117c Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 14 Nov 2011 17:44:09 +0100 Subject: [PATCH 14/18] virtio-scsi: process control queue requests Reviewed-by: Stefan Hajnoczi Signed-off-by: Paolo Bonzini --- hw/virtio-scsi.c | 134 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 8 deletions(-) diff --git a/hw/virtio-scsi.c b/hw/virtio-scsi.c index f4eb8c7e6..1f3b851f2 100644 --- a/hw/virtio-scsi.c +++ b/hw/virtio-scsi.c @@ -134,6 +134,7 @@ typedef struct { VirtQueue *cmd_vq; uint32_t sense_size; uint32_t cdb_size; + int resetting; } VirtIOSCSI; typedef struct VirtIOSCSIReq { @@ -236,15 +237,104 @@ static VirtIOSCSIReq *virtio_scsi_pop_req(VirtIOSCSI *s, VirtQueue *vq) return req; } -static void virtio_scsi_fail_ctrl_req(VirtIOSCSIReq *req) +static void virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req) { - if (req->req.tmf->type == VIRTIO_SCSI_T_TMF) { - req->resp.tmf->response = VIRTIO_SCSI_S_FAILURE; - } else { - req->resp.an->response = VIRTIO_SCSI_S_FAILURE; + SCSIDevice *d = virtio_scsi_device_find(s, req->req.tmf->lun); + SCSIRequest *r, *next; + DeviceState *qdev; + int target; + + /* Here VIRTIO_SCSI_S_OK means "FUNCTION COMPLETE". */ + req->resp.tmf->response = VIRTIO_SCSI_S_OK; + + switch (req->req.tmf->subtype) { + case VIRTIO_SCSI_T_TMF_ABORT_TASK: + case VIRTIO_SCSI_T_TMF_QUERY_TASK: + if (!d) { + goto fail; + } + if (d->lun != virtio_scsi_get_lun(req->req.tmf->lun)) { + goto incorrect_lun; + } + QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) { + if (r->tag == req->req.tmf->tag) { + break; + } + } + if (r && r->hba_private) { + if (req->req.tmf->subtype == VIRTIO_SCSI_T_TMF_QUERY_TASK) { + /* "If the specified command is present in the task set, then + * return a service response set to FUNCTION SUCCEEDED". + */ + req->resp.tmf->response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED; + } else { + scsi_req_cancel(r); + } + } + break; + + case VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET: + if (!d) { + goto fail; + } + if (d->lun != virtio_scsi_get_lun(req->req.tmf->lun)) { + goto incorrect_lun; + } + s->resetting++; + qdev_reset_all(&d->qdev); + s->resetting--; + break; + + case VIRTIO_SCSI_T_TMF_ABORT_TASK_SET: + case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET: + case VIRTIO_SCSI_T_TMF_QUERY_TASK_SET: + if (!d) { + goto fail; + } + if (d->lun != virtio_scsi_get_lun(req->req.tmf->lun)) { + goto incorrect_lun; + } + QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) { + if (r->hba_private) { + if (req->req.tmf->subtype == VIRTIO_SCSI_T_TMF_QUERY_TASK_SET) { + /* "If there is any command present in the task set, then + * return a service response set to FUNCTION SUCCEEDED". + */ + req->resp.tmf->response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED; + break; + } else { + scsi_req_cancel(r); + } + } + } + break; + + case VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET: + target = req->req.tmf->lun[1]; + s->resetting++; + QTAILQ_FOREACH(qdev, &s->bus.qbus.children, sibling) { + d = DO_UPCAST(SCSIDevice, qdev, qdev); + if (d->channel == 0 && d->id == target) { + qdev_reset_all(&d->qdev); + } + } + s->resetting--; + break; + + case VIRTIO_SCSI_T_TMF_CLEAR_ACA: + default: + req->resp.tmf->response = VIRTIO_SCSI_S_FUNCTION_REJECTED; + break; } - virtio_scsi_complete_req(req); + return; + +incorrect_lun: + req->resp.tmf->response = VIRTIO_SCSI_S_INCORRECT_LUN; + return; + +fail: + req->resp.tmf->response = VIRTIO_SCSI_S_BAD_TARGET; } static void virtio_scsi_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) @@ -253,7 +343,31 @@ static void virtio_scsi_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) VirtIOSCSIReq *req; while ((req = virtio_scsi_pop_req(s, vq))) { - virtio_scsi_fail_ctrl_req(req); + int out_size, in_size; + if (req->elem.out_num < 1 || req->elem.in_num < 1) { + virtio_scsi_bad_req(); + continue; + } + + out_size = req->elem.out_sg[0].iov_len; + in_size = req->elem.in_sg[0].iov_len; + if (req->req.tmf->type == VIRTIO_SCSI_T_TMF) { + if (out_size < sizeof(VirtIOSCSICtrlTMFReq) || + in_size < sizeof(VirtIOSCSICtrlTMFResp)) { + virtio_scsi_bad_req(); + } + virtio_scsi_do_tmf(s, req); + + } else if (req->req.tmf->type == VIRTIO_SCSI_T_AN_QUERY || + req->req.tmf->type == VIRTIO_SCSI_T_AN_SUBSCRIBE) { + if (out_size < sizeof(VirtIOSCSICtrlANReq) || + in_size < sizeof(VirtIOSCSICtrlANResp)) { + virtio_scsi_bad_req(); + } + req->resp.an->event_actual = 0; + req->resp.an->response = VIRTIO_SCSI_S_OK; + } + virtio_scsi_complete_req(req); } } @@ -288,7 +402,11 @@ static void virtio_scsi_request_cancelled(SCSIRequest *r) if (!req) { return; } - req->resp.cmd->response = VIRTIO_SCSI_S_ABORTED; + if (req->dev->resetting) { + req->resp.cmd->response = VIRTIO_SCSI_S_RESET; + } else { + req->resp.cmd->response = VIRTIO_SCSI_S_ABORTED; + } virtio_scsi_complete_req(req); } From 5db1764cc1f6ea18d92935e91c182b5526a392d1 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 2 Dec 2011 15:23:15 +0100 Subject: [PATCH 15/18] virtio-scsi: add migration support Reviewed-by: Stefan Hajnoczi Signed-off-by: Paolo Bonzini --- hw/virtio-scsi.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/hw/virtio-scsi.c b/hw/virtio-scsi.c index 1f3b851f2..e607edc91 100644 --- a/hw/virtio-scsi.c +++ b/hw/virtio-scsi.c @@ -237,6 +237,34 @@ static VirtIOSCSIReq *virtio_scsi_pop_req(VirtIOSCSI *s, VirtQueue *vq) return req; } +static void virtio_scsi_save_request(QEMUFile *f, SCSIRequest *sreq) +{ + VirtIOSCSIReq *req = sreq->hba_private; + + qemu_put_buffer(f, (unsigned char *)&req->elem, sizeof(req->elem)); +} + +static void *virtio_scsi_load_request(QEMUFile *f, SCSIRequest *sreq) +{ + SCSIBus *bus = sreq->bus; + VirtIOSCSI *s = container_of(bus, VirtIOSCSI, bus); + VirtIOSCSIReq *req; + + req = g_malloc(sizeof(*req)); + qemu_get_buffer(f, (unsigned char *)&req->elem, sizeof(req->elem)); + virtio_scsi_parse_req(s, s->cmd_vq, req); + + scsi_req_ref(sreq); + req->sreq = sreq; + if (req->sreq->cmd.mode != SCSI_XFER_NONE) { + int req_mode = + (req->elem.in_num > 1 ? SCSI_XFER_FROM_DEV : SCSI_XFER_TO_DEV); + + assert(req->sreq->cmd.mode == req_mode); + } + return req; +} + static void virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req) { SCSIDevice *d = virtio_scsi_device_find(s, req->req.tmf->lun); @@ -518,6 +546,22 @@ static void virtio_scsi_reset(VirtIODevice *vdev) s->cdb_size = VIRTIO_SCSI_CDB_SIZE; } +/* The device does not have anything to save beyond the virtio data. + * Request data is saved with callbacks from SCSI devices. + */ +static void virtio_scsi_save(QEMUFile *f, void *opaque) +{ + VirtIOSCSI *s = opaque; + virtio_save(&s->vdev, f); +} + +static int virtio_scsi_load(QEMUFile *f, void *opaque, int version_id) +{ + VirtIOSCSI *s = opaque; + virtio_load(&s->vdev, f); + return 0; +} + static struct SCSIBusInfo virtio_scsi_scsi_info = { .tcq = true, .max_channel = VIRTIO_SCSI_MAX_CHANNEL, @@ -527,11 +571,14 @@ static struct SCSIBusInfo virtio_scsi_scsi_info = { .complete = virtio_scsi_command_complete, .cancel = virtio_scsi_request_cancelled, .get_sg_list = virtio_scsi_get_sg_list, + .save_request = virtio_scsi_save_request, + .load_request = virtio_scsi_load_request, }; VirtIODevice *virtio_scsi_init(DeviceState *dev, VirtIOSCSIConf *proxyconf) { VirtIOSCSI *s; + static int virtio_scsi_id; s = (VirtIOSCSI *)virtio_common_init("virtio-scsi", VIRTIO_ID_SCSI, sizeof(VirtIOSCSIConfig), @@ -558,7 +605,8 @@ VirtIODevice *virtio_scsi_init(DeviceState *dev, VirtIOSCSIConf *proxyconf) scsi_bus_legacy_handle_cmdline(&s->bus); } - /* TODO savevm */ + register_savevm(dev, "virtio-scsi", virtio_scsi_id++, 1, + virtio_scsi_save, virtio_scsi_load, s); return &s->vdev; } From 9fac25bf6ee6e3291ccb27feb9ad9b63506c7a60 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 15 Feb 2012 09:23:50 +0100 Subject: [PATCH 16/18] scsi: fix wrong return for target INQUIRY Signed-off-by: Paolo Bonzini --- hw/scsi-bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index f0babb1f8..2cf2f0d8c 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -380,7 +380,7 @@ static bool scsi_target_emulate_inquiry(SCSITargetReq *r) /* PAGE CODE == 0 */ if (r->req.cmd.xfer < 5) { - return -1; + return false; } r->len = MIN(r->req.cmd.xfer, 36); From d3d250bddb3f6aa6c26e9dadf10e82d9fd8bfce3 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 15 Feb 2012 09:22:54 +0100 Subject: [PATCH 17/18] scsi: fix searching for an empty id The conditions for detecting no free target or LUN were wrong. The LUN loop was followed by an "if" condition that is never true, because the loop is exited as soon as lun becomes equal to bus->info->max_lun, and never becomes greater than it. The target loop had a wrong condition (<= instead of <). Once this is fixed, the loop would fail in the same way as the LUN loop. The fix is to see whether scsi_device_find returned the device with the last (channel, target, LUN) pair, and fail if so. Signed-off-by: Paolo Bonzini --- hw/scsi-bus.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index 2cf2f0d8c..2cb5a18da 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -132,6 +132,10 @@ static int scsi_qdev_init(DeviceState *qdev) error_report("bad scsi device id: %d", dev->id); goto err; } + if (dev->lun != -1 && dev->lun > bus->info->max_lun) { + error_report("bad scsi device lun: %d", dev->lun); + goto err; + } if (dev->id == -1) { int id = -1; @@ -140,8 +144,8 @@ static int scsi_qdev_init(DeviceState *qdev) } do { d = scsi_device_find(bus, dev->channel, ++id, dev->lun); - } while (d && d->lun == dev->lun && id <= bus->info->max_target); - if (id > bus->info->max_target) { + } while (d && d->lun == dev->lun && id < bus->info->max_target); + if (d && d->lun == dev->lun) { error_report("no free target"); goto err; } @@ -151,14 +155,15 @@ static int scsi_qdev_init(DeviceState *qdev) do { d = scsi_device_find(bus, dev->channel, dev->id, ++lun); } while (d && d->lun == lun && lun < bus->info->max_lun); - if (lun > bus->info->max_lun) { + if (d && d->lun == lun) { error_report("no free lun"); goto err; } dev->lun = lun; } else { d = scsi_device_find(bus, dev->channel, dev->id, dev->lun); - if (dev->lun == d->lun && dev != d) { + assert(d); + if (d->lun == dev->lun && dev != d) { qdev_free(&d->qdev); } } @@ -217,7 +222,7 @@ int scsi_bus_legacy_handle_cmdline(SCSIBus *bus) int res = 0, unit; loc_push_none(&loc); - for (unit = 0; unit < bus->info->max_target; unit++) { + for (unit = 0; unit <= bus->info->max_target; unit++) { dinfo = drive_get(IF_SCSI, bus->busnr, unit); if (dinfo == NULL) { continue; From eaccf49e9524dbdd43feb6a1cdafddf64252389a Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 15 Feb 2012 14:11:22 +0100 Subject: [PATCH 18/18] scsi-block: always use scsi_generic_ops for cache != none Signed-off-by: Paolo Bonzini --- hw/scsi-disk.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index ec8e7cbff..4101a59d3 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -1754,6 +1754,15 @@ static SCSIRequest *scsi_block_new_request(SCSIDevice *d, uint32_t tag, case WRITE_VERIFY_10: case WRITE_VERIFY_12: case WRITE_VERIFY_16: + /* If we are not using O_DIRECT, we might read stale data from the + * host cache if writes were made using other commands than these + * ones (such as WRITE SAME or EXTENDED COPY, etc.). So, without + * O_DIRECT everything must go through SG_IO. + */ + if (!(s->qdev.conf.bs->open_flags & BDRV_O_NOCACHE)) { + break; + } + /* MMC writing cannot be done via pread/pwrite, because it sometimes * involves writing beyond the maximum LBA or to negative LBA (lead-in). * And once you do these writes, reading from the block device is @@ -1764,10 +1773,11 @@ static SCSIRequest *scsi_block_new_request(SCSIDevice *d, uint32_t tag, * 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) { - return scsi_req_alloc(&scsi_disk_reqops, &s->qdev, tag, lun, - hba_private); - } + if (s->qdev.type == TYPE_ROM) { + break; + } + return scsi_req_alloc(&scsi_disk_reqops, &s->qdev, tag, lun, + hba_private); } return scsi_req_alloc(&scsi_generic_req_ops, &s->qdev, tag, lun,