#include "blockdev.h"
#include "block_int.h"
+#ifdef __linux
+#include <scsi/sg.h>
+#endif
+
#define SCSI_DMA_BUF_SIZE 131072
#define SCSI_MAX_INQUIRY_LEN 256
-#define SCSI_REQ_STATUS_RETRY 0x01
-#define SCSI_REQ_STATUS_RETRY_TYPE_MASK 0x06
-#define SCSI_REQ_STATUS_RETRY_READ 0x00
-#define SCSI_REQ_STATUS_RETRY_WRITE 0x02
-#define SCSI_REQ_STATUS_RETRY_FLUSH 0x04
-
typedef struct SCSIDiskState SCSIDiskState;
typedef struct SCSIDiskReq {
uint32_t buflen;
struct iovec iov;
QEMUIOVector qiov;
- uint32_t status;
BlockAcctCookie acct;
} SCSIDiskReq;
{
SCSIDevice qdev;
uint32_t removable;
- uint64_t max_lba;
bool media_changed;
bool media_event;
+ bool eject_request;
QEMUBH *bh;
char *version;
char *serial;
bool tray_locked;
};
-static int scsi_handle_rw_error(SCSIDiskReq *r, int error, int type);
-static int32_t scsi_send_command(SCSIRequest *req, uint8_t *buf);
+static int scsi_handle_rw_error(SCSIDiskReq *r, int error);
static void scsi_free_request(SCSIRequest *req)
{
DPRINTF("Cancel tag=0x%x\n", req->tag);
if (r->req.aiocb) {
bdrv_aio_cancel(r->req.aiocb);
+
+ /* This reference was left in by scsi_*_data. We take ownership of
+ * it the moment scsi_req_cancel is called, independent of whether
+ * bdrv_aio_cancel completes the request or not. */
+ scsi_req_unref(&r->req);
}
r->req.aiocb = NULL;
}
}
if (ret) {
- if (scsi_handle_rw_error(r, -ret, SCSI_REQ_STATUS_RETRY_READ)) {
- return;
+ if (scsi_handle_rw_error(r, -ret)) {
+ goto done;
}
}
r->sector += n;
r->sector_count -= n;
scsi_req_data(&r->req, r->qiov.size);
+
+done:
+ if (!r->req.io_canceled) {
+ scsi_req_unref(&r->req);
+ }
}
static void scsi_flush_complete(void * opaque, int ret)
}
if (ret < 0) {
- if (scsi_handle_rw_error(r, -ret, SCSI_REQ_STATUS_RETRY_FLUSH)) {
- return;
+ if (scsi_handle_rw_error(r, -ret)) {
+ goto done;
}
}
scsi_req_complete(&r->req, GOOD);
+
+done:
+ if (!r->req.io_canceled) {
+ scsi_req_unref(&r->req);
+ }
}
/* Read more data from scsi device into buffer. */
/* No data transfer may already be in progress */
assert(r->req.aiocb == NULL);
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
DPRINTF("Data transfer direction invalid\n");
scsi_read_complete(r, -EINVAL);
if (s->tray_open) {
scsi_read_complete(r, -ENOMEDIUM);
+ 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.aiocb == NULL) {
- scsi_read_complete(r, -EIO);
- }
}
-static int scsi_handle_rw_error(SCSIDiskReq *r, int error, int type)
+/*
+ * scsi_handle_rw_error has two return values. 0 means that the error
+ * must be ignored, 1 means that the error has been processed and the
+ * caller should not do anything else for this request. Note that
+ * scsi_handle_rw_error always manages its reference counts, independent
+ * of the return value.
+ */
+static int scsi_handle_rw_error(SCSIDiskReq *r, int error)
{
- int is_read = (type == SCSI_REQ_STATUS_RETRY_READ);
+ int is_read = (r->req.cmd.xfer == SCSI_XFER_FROM_DEV);
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
BlockErrorAction action = bdrv_get_on_error(s->qdev.conf.bs, is_read);
if ((error == ENOSPC && action == BLOCK_ERR_STOP_ENOSPC)
|| action == BLOCK_ERR_STOP_ANY) {
- type &= SCSI_REQ_STATUS_RETRY_TYPE_MASK;
- r->status |= SCSI_REQ_STATUS_RETRY | type;
-
bdrv_mon_event(s->qdev.conf.bs, BDRV_ACTION_STOP, is_read);
vm_stop(RUN_STATE_IO_ERROR);
bdrv_iostatus_set_err(s->qdev.conf.bs, error);
+ scsi_req_retry(&r->req);
} else {
switch (error) {
case ENOMEDIUM:
}
if (ret) {
- if (scsi_handle_rw_error(r, -ret, SCSI_REQ_STATUS_RETRY_WRITE)) {
- return;
+ if (scsi_handle_rw_error(r, -ret)) {
+ goto done;
}
}
DPRINTF("Write complete tag=0x%x more=%d\n", r->req.tag, r->qiov.size);
scsi_req_data(&r->req, r->qiov.size);
}
+
+done:
+ if (!r->req.io_canceled) {
+ scsi_req_unref(&r->req);
+ }
}
static void scsi_write_data(SCSIRequest *req)
/* No data transfer may already be in progress */
assert(r->req.aiocb == NULL);
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
if (r->req.cmd.mode != SCSI_XFER_TO_DEV) {
DPRINTF("Data transfer direction invalid\n");
scsi_write_complete(r, -EINVAL);
if (n) {
if (s->tray_open) {
scsi_write_complete(r, -ENOMEDIUM);
+ return;
}
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);
- if (r->req.aiocb == NULL) {
- scsi_write_complete(r, -ENOMEM);
- }
} else {
/* Called for the first time. Ask the driver to send us more data. */
scsi_write_complete(r, 0);
}
}
-static void scsi_dma_restart_bh(void *opaque)
-{
- SCSIDiskState *s = opaque;
- SCSIRequest *req;
- SCSIDiskReq *r;
-
- qemu_bh_delete(s->bh);
- s->bh = NULL;
-
- QTAILQ_FOREACH(req, &s->qdev.requests, next) {
- r = DO_UPCAST(SCSIDiskReq, req, req);
- if (r->status & SCSI_REQ_STATUS_RETRY) {
- int status = r->status;
-
- r->status &=
- ~(SCSI_REQ_STATUS_RETRY | SCSI_REQ_STATUS_RETRY_TYPE_MASK);
-
- switch (status & SCSI_REQ_STATUS_RETRY_TYPE_MASK) {
- case SCSI_REQ_STATUS_RETRY_READ:
- scsi_read_data(&r->req);
- break;
- case SCSI_REQ_STATUS_RETRY_WRITE:
- scsi_write_data(&r->req);
- break;
- case SCSI_REQ_STATUS_RETRY_FLUSH:
- scsi_send_command(&r->req, r->req.cmd.buf);
- break;
- }
- }
- }
-}
-
-static void scsi_dma_restart_cb(void *opaque, int running, RunState state)
-{
- SCSIDiskState *s = opaque;
-
- if (!running) {
- return;
- }
- if (!s->bh) {
- s->bh = qemu_bh_new(scsi_dma_restart_bh, s);
- qemu_bh_schedule(s->bh);
- }
-}
-
/* Return a pointer to the data buffer. */
static uint8_t *scsi_get_buf(SCSIRequest *req)
{
/* Event notification descriptor */
event_code = MEC_NO_CHANGE;
- if (media_status != MS_TRAY_OPEN && s->media_event) {
- event_code = MEC_NEW_MEDIA;
- s->media_event = false;
+ if (media_status != MS_TRAY_OPEN) {
+ if (s->media_event) {
+ event_code = MEC_NEW_MEDIA;
+ s->media_event = false;
+ } else if (s->eject_request) {
+ event_code = MEC_EJECT_REQUESTED;
+ s->eject_request = false;
+ }
}
outbuf[0] = event_code;
break;
}
/* if a geometry hint is available, use it */
- bdrv_get_geometry_hint(bdrv, &cylinders, &heads, &secs);
+ bdrv_guess_geometry(bdrv, &cylinders, &heads, &secs);
p[2] = (cylinders >> 16) & 0xff;
p[3] = (cylinders >> 8) & 0xff;
p[4] = cylinders & 0xff;
p[2] = 5000 >> 8;
p[3] = 5000 & 0xff;
/* if a geometry hint is available, use it */
- bdrv_get_geometry_hint(bdrv, &cylinders, &heads, &secs);
+ bdrv_guess_geometry(bdrv, &cylinders, &heads, &secs);
p[4] = heads & 0xff;
p[5] = secs & 0xff;
p[6] = s->qdev.blocksize >> 8;
p += 8;
}
+ /* MMC prescribes that CD/DVD drives have no block descriptors. */
bdrv_get_geometry(s->qdev.conf.bs, &nb_sectors);
- if (!dbd && nb_sectors) {
+ if (!dbd && s->qdev.type == TYPE_DISK && nb_sectors) {
if (r->req.cmd.buf[0] == MODE_SENSE) {
outbuf[3] = 8; /* Block descriptor length */
} else { /* MODE_SENSE_10 */
/* Returned value is the address of the last sector. */
nb_sectors--;
/* Remember the new size for read/write sanity checking. */
- s->max_lba = nb_sectors;
+ s->qdev.max_lba = nb_sectors;
/* Clip to 2TB, instead of returning capacity modulo 2TB. */
if (nb_sectors > UINT32_MAX) {
nb_sectors = UINT32_MAX;
outbuf[7] = 0;
buflen = 8;
break;
+ case REQUEST_SENSE:
+ /* Just return "NO SENSE". */
+ buflen = scsi_build_sense(NULL, 0, outbuf, r->buflen,
+ (req->cmd.buf[1] & 1) == 0);
+ break;
case MECHANISM_STATUS:
buflen = scsi_emulate_mechanism_status(s, outbuf);
if (buflen < 0) {
/* Returned value is the address of the last sector. */
nb_sectors--;
/* Remember the new size for read/write sanity checking. */
- s->max_lba = nb_sectors;
+ s->qdev.max_lba = nb_sectors;
outbuf[0] = (nb_sectors >> 56) & 0xff;
outbuf[1] = (nb_sectors >> 48) & 0xff;
outbuf[2] = (nb_sectors >> 40) & 0xff;
case GET_EVENT_STATUS_NOTIFICATION:
case MECHANISM_STATUS:
case SERVICE_ACTION_IN_16:
+ case REQUEST_SENSE:
case VERIFY_10:
rc = scsi_disk_emulate_command(r);
if (rc < 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_flush_complete, r);
- if (r->req.aiocb == NULL) {
- scsi_flush_complete(r, -EIO);
- }
return 0;
case READ_6:
case READ_10:
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.lba > s->max_lba) {
+ if (r->req.cmd.lba > s->qdev.max_lba) {
goto illegal_lba;
}
r->sector = r->req.cmd.lba * (s->qdev.blocksize / 512);
DPRINTF("Write %s(sector %" PRId64 ", count %d)\n",
(command & 0xe) == 0xe ? "And Verify " : "",
r->req.cmd.lba, len);
- if (r->req.cmd.lba > s->max_lba) {
+ if (r->req.cmd.lba > s->qdev.max_lba) {
goto illegal_lba;
}
r->sector = r->req.cmd.lba * (s->qdev.blocksize / 512);
goto fail;
}
break;
- case SEEK_6:
case SEEK_10:
- DPRINTF("Seek(%d) (sector %" PRId64 ")\n", command == SEEK_6 ? 6 : 10,
- r->req.cmd.lba);
- if (r->req.cmd.lba > s->max_lba) {
+ DPRINTF("Seek(10) (sector %" PRId64 ")\n", r->req.cmd.lba);
+ if (r->req.cmd.lba > s->qdev.max_lba) {
goto illegal_lba;
}
break;
DPRINTF("WRITE SAME(16) (sector %" PRId64 ", count %d)\n",
r->req.cmd.lba, len);
- if (r->req.cmd.lba > s->max_lba) {
+ if (r->req.cmd.lba > s->qdev.max_lba) {
goto illegal_lba;
}
}
break;
- case REQUEST_SENSE:
- abort();
default:
DPRINTF("Unknown SCSI command (%2.2x)\n", buf[0]);
scsi_check_condition(r, SENSE_CODE(INVALID_OPCODE));
if (nb_sectors) {
nb_sectors--;
}
- s->max_lba = nb_sectors;
+ s->qdev.max_lba = nb_sectors;
}
static void scsi_destroy(SCSIDevice *dev)
s->tray_open = !load;
s->qdev.unit_attention = SENSE_CODE(UNIT_ATTENTION_NO_MEDIUM);
s->media_event = true;
+ s->eject_request = false;
+}
+
+static void scsi_cd_eject_request_cb(void *opaque, bool force)
+{
+ SCSIDiskState *s = opaque;
+
+ s->eject_request = true;
+ if (force) {
+ s->tray_locked = false;
+ }
}
static bool scsi_cd_is_tray_open(void *opaque)
static const BlockDevOps scsi_cd_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,
};
}
bdrv_set_buffer_alignment(s->qdev.conf.bs, s->qdev.blocksize);
- qemu_add_vm_change_state_handler(scsi_dma_restart_cb, s);
bdrv_iostatus_enable(s->qdev.conf.bs);
- add_boot_device_path(s->qdev.conf.bootindex, &dev->qdev, ",0");
+ add_boot_device_path(s->qdev.conf.bootindex, &dev->qdev, NULL);
return 0;
}
}
}
-static SCSIReqOps scsi_disk_reqops = {
+static const SCSIReqOps scsi_disk_reqops = {
.size = sizeof(SCSIDiskReq),
.free_req = scsi_free_request,
.send_command = scsi_send_command,
.get_buf = scsi_get_buf,
};
-static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag,
- uint32_t lun, void *hba_private)
+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;
return req;
}
+#ifdef __linux__
+static int get_device_type(SCSIDiskState *s)
+{
+ BlockDriverState *bdrv = s->qdev.conf.bs;
+ uint8_t cmd[16];
+ uint8_t buf[36];
+ uint8_t sensebuf[8];
+ sg_io_hdr_t io_header;
+ int ret;
+
+ memset(cmd, 0, sizeof(cmd));
+ memset(buf, 0, sizeof(buf));
+ cmd[0] = INQUIRY;
+ cmd[4] = sizeof(buf);
+
+ memset(&io_header, 0, sizeof(io_header));
+ io_header.interface_id = 'S';
+ io_header.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_header.dxfer_len = sizeof(buf);
+ io_header.dxferp = buf;
+ io_header.cmdp = cmd;
+ io_header.cmd_len = sizeof(cmd);
+ io_header.mx_sb_len = sizeof(sensebuf);
+ io_header.sbp = sensebuf;
+ io_header.timeout = 6000; /* XXX */
+
+ ret = bdrv_ioctl(bdrv, SG_IO, &io_header);
+ if (ret < 0 || io_header.driver_status || io_header.host_status) {
+ return -1;
+ }
+ s->qdev.type = buf[0];
+ s->removable = (buf[1] & 0x80) != 0;
+ return 0;
+}
+
+static int scsi_block_initfn(SCSIDevice *dev)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+ int sg_version;
+ int rc;
+
+ if (!s->qdev.conf.bs) {
+ error_report("scsi-block: drive property not set");
+ return -1;
+ }
+
+ /* check we are using a driver managing SG_IO (version 3 and after) */
+ if (bdrv_ioctl(s->qdev.conf.bs, SG_GET_VERSION_NUM, &sg_version) < 0 ||
+ sg_version < 30000) {
+ error_report("scsi-block: scsi generic interface too old");
+ return -1;
+ }
+
+ /* get device type from INQUIRY data */
+ rc = get_device_type(s);
+ if (rc < 0) {
+ error_report("scsi-block: INQUIRY failed");
+ return -1;
+ }
+
+ /* Make a guess for the block size, we'll fix it when the guest sends.
+ * READ CAPACITY. If they don't, they likely would assume these sizes
+ * anyway. (TODO: check in /sys).
+ */
+ if (s->qdev.type == TYPE_ROM || s->qdev.type == TYPE_WORM) {
+ s->qdev.blocksize = 2048;
+ } else {
+ s->qdev.blocksize = 512;
+ }
+ return scsi_initfn(&s->qdev);
+}
+
+static SCSIRequest *scsi_block_new_request(SCSIDevice *d, uint32_t tag,
+ uint32_t lun, uint8_t *buf,
+ void *hba_private)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d);
+
+ switch (buf[0]) {
+ case READ_6:
+ case READ_10:
+ case READ_12:
+ case READ_16:
+ case WRITE_6:
+ case WRITE_10:
+ case WRITE_12:
+ case WRITE_16:
+ case WRITE_VERIFY_10:
+ case WRITE_VERIFY_12:
+ case WRITE_VERIFY_16:
+ /* 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
+ * 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
+ * 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);
+ }
+ }
+
+ return scsi_req_alloc(&scsi_generic_req_ops, &s->qdev, tag, lun,
+ hba_private);
+}
+#endif
+
#define DEFINE_SCSI_DISK_PROPERTIES() \
DEFINE_BLOCK_PROPERTIES(SCSIDiskState, qdev.conf), \
DEFINE_PROP_STRING("ver", SCSIDiskState, version), \
DEFINE_SCSI_DISK_PROPERTIES(),
DEFINE_PROP_END_OF_LIST(),
},
+#ifdef __linux__
+ },{
+ .qdev.name = "scsi-block",
+ .qdev.fw_name = "disk",
+ .qdev.desc = "SCSI block device passthrough",
+ .qdev.size = sizeof(SCSIDiskState),
+ .qdev.reset = scsi_disk_reset,
+ .init = scsi_block_initfn,
+ .destroy = scsi_destroy,
+ .alloc_req = scsi_block_new_request,
+ .qdev.props = (Property[]) {
+ DEFINE_SCSI_DISK_PROPERTIES(),
+ DEFINE_PROP_END_OF_LIST(),
+ },
+#endif
},{
.qdev.name = "scsi-disk", /* legacy -device scsi-disk */
.qdev.fw_name = "disk",