mirror of https://github.com/xemu-project/xemu.git
Merge remote branch 'kwolf/for-anthony' into staging
This commit is contained in:
commit
09fa35e5cd
|
@ -146,8 +146,7 @@ static int bmds_aio_inflight(BlkMigDevState *bmds, int64_t sector)
|
|||
{
|
||||
int64_t chunk = sector / (int64_t)BDRV_SECTORS_PER_DIRTY_CHUNK;
|
||||
|
||||
if (bmds->aio_bitmap &&
|
||||
(sector << BDRV_SECTOR_BITS) < bdrv_getlength(bmds->bs)) {
|
||||
if ((sector << BDRV_SECTOR_BITS) < bdrv_getlength(bmds->bs)) {
|
||||
return !!(bmds->aio_bitmap[chunk / (sizeof(unsigned long) * 8)] &
|
||||
(1UL << (chunk % (sizeof(unsigned long) * 8))));
|
||||
} else {
|
||||
|
@ -169,13 +168,9 @@ static void bmds_set_aio_inflight(BlkMigDevState *bmds, int64_t sector_num,
|
|||
bit = start % (sizeof(unsigned long) * 8);
|
||||
val = bmds->aio_bitmap[idx];
|
||||
if (set) {
|
||||
if (!(val & (1UL << bit))) {
|
||||
val |= 1UL << bit;
|
||||
}
|
||||
val |= 1UL << bit;
|
||||
} else {
|
||||
if (val & (1UL << bit)) {
|
||||
val &= ~(1UL << bit);
|
||||
}
|
||||
val &= ~(1UL << bit);
|
||||
}
|
||||
bmds->aio_bitmap[idx] = val;
|
||||
}
|
||||
|
@ -385,8 +380,9 @@ static int mig_save_device_dirty(Monitor *mon, QEMUFile *f,
|
|||
int nr_sectors;
|
||||
|
||||
for (sector = bmds->cur_dirty; sector < bmds->total_sectors;) {
|
||||
if (bmds_aio_inflight(bmds, sector))
|
||||
if (bmds_aio_inflight(bmds, sector)) {
|
||||
qemu_aio_flush();
|
||||
}
|
||||
if (bdrv_get_dirty(bmds->bs, sector)) {
|
||||
|
||||
if (total_sectors - sector < BDRV_SECTORS_PER_DIRTY_CHUNK) {
|
||||
|
|
|
@ -54,7 +54,6 @@ typedef struct QCowHeader {
|
|||
#define L2_CACHE_SIZE 16
|
||||
|
||||
typedef struct BDRVQcowState {
|
||||
BlockDriverState *hd;
|
||||
int cluster_bits;
|
||||
int cluster_size;
|
||||
int cluster_sectors;
|
||||
|
|
|
@ -79,7 +79,6 @@ typedef struct QCowSnapshot {
|
|||
} QCowSnapshot;
|
||||
|
||||
typedef struct BDRVQcowState {
|
||||
BlockDriverState *hd;
|
||||
int cluster_bits;
|
||||
int cluster_size;
|
||||
int cluster_sectors;
|
||||
|
|
|
@ -463,7 +463,7 @@ static int raw_pwrite(BlockDriverState *bs, int64_t offset,
|
|||
count -= ret;
|
||||
sum += ret;
|
||||
}
|
||||
/* here, count < 512 because (count & ~sector_mask) == 0 */
|
||||
/* here, count < sector_size because (count & ~sector_mask) == 0 */
|
||||
if (count) {
|
||||
ret = raw_pread_aligned(bs, offset, s->aligned_buf,
|
||||
bs->buffer_alignment);
|
||||
|
|
|
@ -186,7 +186,6 @@ typedef struct {
|
|||
} VdiHeader;
|
||||
|
||||
typedef struct {
|
||||
BlockDriverState *hd;
|
||||
/* The block map entries are little endian (even in memory). */
|
||||
uint32_t *bmap;
|
||||
/* Size of block (bytes). */
|
||||
|
|
|
@ -61,7 +61,6 @@ typedef struct {
|
|||
#define L2_CACHE_SIZE 16
|
||||
|
||||
typedef struct BDRVVmdkState {
|
||||
BlockDriverState *hd;
|
||||
int64_t l1_table_offset;
|
||||
int64_t l1_backup_table_offset;
|
||||
uint32_t *l1_table;
|
||||
|
|
|
@ -110,8 +110,6 @@ struct vhd_dyndisk_header {
|
|||
};
|
||||
|
||||
typedef struct BDRVVPCState {
|
||||
BlockDriverState *hd;
|
||||
|
||||
uint8_t footer_buf[HEADER_SIZE];
|
||||
uint64_t free_data_block_offset;
|
||||
int max_table_entries;
|
||||
|
|
39
blockdev.c
39
blockdev.c
|
@ -14,6 +14,8 @@
|
|||
#include "qemu-option.h"
|
||||
#include "qemu-config.h"
|
||||
#include "sysemu.h"
|
||||
#include "hw/qdev.h"
|
||||
#include "block_int.h"
|
||||
|
||||
static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives);
|
||||
|
||||
|
@ -597,3 +599,40 @@ int do_change_block(Monitor *mon, const char *device,
|
|||
}
|
||||
return monitor_read_bdrv_key_start(mon, bs, NULL, NULL);
|
||||
}
|
||||
|
||||
int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||
{
|
||||
const char *id = qdict_get_str(qdict, "id");
|
||||
BlockDriverState *bs;
|
||||
BlockDriverState **ptr;
|
||||
Property *prop;
|
||||
|
||||
bs = bdrv_find(id);
|
||||
if (!bs) {
|
||||
qerror_report(QERR_DEVICE_NOT_FOUND, id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* quiesce block driver; prevent further io */
|
||||
qemu_aio_flush();
|
||||
bdrv_flush(bs);
|
||||
bdrv_close(bs);
|
||||
|
||||
/* clean up guest state from pointing to host resource by
|
||||
* finding and removing DeviceState "drive" property */
|
||||
for (prop = bs->peer->info->props; prop && prop->name; prop++) {
|
||||
if (prop->info->type == PROP_TYPE_DRIVE) {
|
||||
ptr = qdev_get_prop_ptr(bs->peer, prop);
|
||||
if ((*ptr) == bs) {
|
||||
bdrv_detach(bs, bs->peer);
|
||||
*ptr = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* clean up host side */
|
||||
drive_uninit(drive_get_by_blockdev(bs));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ struct DriveInfo {
|
|||
};
|
||||
|
||||
#define MAX_IDE_DEVS 2
|
||||
#define MAX_SCSI_DEVS 7
|
||||
#define MAX_SCSI_DEVS 255
|
||||
|
||||
DriveInfo *drive_get(BlockInterfaceType type, int bus, int unit);
|
||||
int drive_get_max_bus(BlockInterfaceType type);
|
||||
|
@ -51,5 +51,6 @@ int do_eject(Monitor *mon, const QDict *qdict, QObject **ret_data);
|
|||
int do_block_set_passwd(Monitor *mon, const QDict *qdict, QObject **ret_data);
|
||||
int do_change_block(Monitor *mon, const char *device,
|
||||
const char *filename, const char *fmt);
|
||||
int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -65,6 +65,24 @@ STEXI
|
|||
@item eject [-f] @var{device}
|
||||
@findex eject
|
||||
Eject a removable medium (use -f to force it).
|
||||
ETEXI
|
||||
|
||||
{
|
||||
.name = "drive_del",
|
||||
.args_type = "id:s",
|
||||
.params = "device",
|
||||
.help = "remove host block device",
|
||||
.user_print = monitor_user_noop,
|
||||
.mhandler.cmd_new = do_drive_del,
|
||||
},
|
||||
|
||||
STEXI
|
||||
@item drive_del @var{device}
|
||||
@findex drive_del
|
||||
Remove host block device. The result is that guest generated IO is no longer
|
||||
submitted against the host device underlying the disk. Once a drive has
|
||||
been deleted, the QEMU Block layer returns -EIO which results in IO
|
||||
errors in the guest for applications that are reading/writing to the device.
|
||||
ETEXI
|
||||
|
||||
{
|
||||
|
|
|
@ -179,12 +179,8 @@ static void bmdma_map(PCIDevice *pci_dev, int region_num,
|
|||
register_ioport_read(addr, 4, 1, bmdma_readb_1, d);
|
||||
}
|
||||
|
||||
register_ioport_write(addr + 4, 4, 1, bmdma_addr_writeb, bm);
|
||||
register_ioport_read(addr + 4, 4, 1, bmdma_addr_readb, bm);
|
||||
register_ioport_write(addr + 4, 4, 2, bmdma_addr_writew, bm);
|
||||
register_ioport_read(addr + 4, 4, 2, bmdma_addr_readw, bm);
|
||||
register_ioport_write(addr + 4, 4, 4, bmdma_addr_writel, bm);
|
||||
register_ioport_read(addr + 4, 4, 4, bmdma_addr_readl, bm);
|
||||
iorange_init(&bm->addr_ioport, &bmdma_addr_ioport_ops, addr + 4, 4);
|
||||
ioport_register(&bm->addr_ioport);
|
||||
addr += 8;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -473,11 +473,21 @@ static void dma_buf_commit(IDEState *s, int is_write)
|
|||
qemu_sglist_destroy(&s->sg);
|
||||
}
|
||||
|
||||
static void ide_dma_set_inactive(BMDMAState *bm)
|
||||
{
|
||||
bm->status &= ~BM_STATUS_DMAING;
|
||||
bm->dma_cb = NULL;
|
||||
bm->unit = -1;
|
||||
bm->aiocb = NULL;
|
||||
}
|
||||
|
||||
void ide_dma_error(IDEState *s)
|
||||
{
|
||||
ide_transfer_stop(s);
|
||||
s->error = ABRT_ERR;
|
||||
s->status = READY_STAT | ERR_STAT;
|
||||
ide_dma_set_inactive(s->bus->bmdma);
|
||||
s->bus->bmdma->status |= BM_STATUS_INT;
|
||||
ide_set_irq(s->bus);
|
||||
}
|
||||
|
||||
|
@ -587,11 +597,8 @@ static void ide_read_dma_cb(void *opaque, int ret)
|
|||
s->status = READY_STAT | SEEK_STAT;
|
||||
ide_set_irq(s->bus);
|
||||
eot:
|
||||
bm->status &= ~BM_STATUS_DMAING;
|
||||
bm->status |= BM_STATUS_INT;
|
||||
bm->dma_cb = NULL;
|
||||
bm->unit = -1;
|
||||
bm->aiocb = NULL;
|
||||
ide_dma_set_inactive(bm);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -733,11 +740,8 @@ static void ide_write_dma_cb(void *opaque, int ret)
|
|||
s->status = READY_STAT | SEEK_STAT;
|
||||
ide_set_irq(s->bus);
|
||||
eot:
|
||||
bm->status &= ~BM_STATUS_DMAING;
|
||||
bm->status |= BM_STATUS_INT;
|
||||
bm->dma_cb = NULL;
|
||||
bm->unit = -1;
|
||||
bm->aiocb = NULL;
|
||||
ide_dma_set_inactive(bm);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1061,11 +1065,8 @@ static void ide_atapi_cmd_read_dma_cb(void *opaque, int ret)
|
|||
s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
|
||||
ide_set_irq(s->bus);
|
||||
eot:
|
||||
bm->status &= ~BM_STATUS_DMAING;
|
||||
bm->status |= BM_STATUS_INT;
|
||||
bm->dma_cb = NULL;
|
||||
bm->unit = -1;
|
||||
bm->aiocb = NULL;
|
||||
ide_dma_set_inactive(bm);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2954,12 +2955,10 @@ void ide_dma_cancel(BMDMAState *bm)
|
|||
printf("aio_cancel\n");
|
||||
#endif
|
||||
bdrv_aio_cancel(bm->aiocb);
|
||||
bm->aiocb = NULL;
|
||||
}
|
||||
bm->status &= ~BM_STATUS_DMAING;
|
||||
|
||||
/* cancel DMA request */
|
||||
bm->unit = -1;
|
||||
bm->dma_cb = NULL;
|
||||
ide_dma_set_inactive(bm);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
#include <hw/ide.h>
|
||||
#include "block_int.h"
|
||||
#include "iorange.h"
|
||||
|
||||
/* debug IDE devices */
|
||||
//#define DEBUG_IDE
|
||||
|
@ -496,6 +497,7 @@ struct BMDMAState {
|
|||
QEMUIOVector qiov;
|
||||
int64_t sector_num;
|
||||
uint32_t nsector;
|
||||
IORange addr_ioport;
|
||||
QEMUBH *bh;
|
||||
};
|
||||
|
||||
|
|
137
hw/ide/pci.c
137
hw/ide/pci.c
|
@ -39,106 +39,75 @@ void bmdma_cmd_writeb(void *opaque, uint32_t addr, uint32_t val)
|
|||
#ifdef DEBUG_IDE
|
||||
printf("%s: 0x%08x\n", __func__, val);
|
||||
#endif
|
||||
if (!(val & BM_CMD_START)) {
|
||||
/*
|
||||
* We can't cancel Scatter Gather DMA in the middle of the
|
||||
* operation or a partial (not full) DMA transfer would reach
|
||||
* the storage so we wait for completion instead (we beahve
|
||||
* like if the DMA was completed by the time the guest trying
|
||||
* to cancel dma with bmdma_cmd_writeb with BM_CMD_START not
|
||||
* set).
|
||||
*
|
||||
* In the future we'll be able to safely cancel the I/O if the
|
||||
* whole DMA operation will be submitted to disk with a single
|
||||
* aio operation with preadv/pwritev.
|
||||
*/
|
||||
if (bm->aiocb) {
|
||||
qemu_aio_flush();
|
||||
|
||||
/* Ignore writes to SSBM if it keeps the old value */
|
||||
if ((val & BM_CMD_START) != (bm->cmd & BM_CMD_START)) {
|
||||
if (!(val & BM_CMD_START)) {
|
||||
/*
|
||||
* We can't cancel Scatter Gather DMA in the middle of the
|
||||
* operation or a partial (not full) DMA transfer would reach
|
||||
* the storage so we wait for completion instead (we beahve
|
||||
* like if the DMA was completed by the time the guest trying
|
||||
* to cancel dma with bmdma_cmd_writeb with BM_CMD_START not
|
||||
* set).
|
||||
*
|
||||
* In the future we'll be able to safely cancel the I/O if the
|
||||
* whole DMA operation will be submitted to disk with a single
|
||||
* aio operation with preadv/pwritev.
|
||||
*/
|
||||
if (bm->aiocb) {
|
||||
qemu_aio_flush();
|
||||
#ifdef DEBUG_IDE
|
||||
if (bm->aiocb)
|
||||
printf("ide_dma_cancel: aiocb still pending");
|
||||
if (bm->status & BM_STATUS_DMAING)
|
||||
printf("ide_dma_cancel: BM_STATUS_DMAING still pending");
|
||||
if (bm->aiocb)
|
||||
printf("ide_dma_cancel: aiocb still pending");
|
||||
if (bm->status & BM_STATUS_DMAING)
|
||||
printf("ide_dma_cancel: BM_STATUS_DMAING still pending");
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
bm->cur_addr = bm->addr;
|
||||
if (!(bm->status & BM_STATUS_DMAING)) {
|
||||
bm->status |= BM_STATUS_DMAING;
|
||||
/* start dma transfer if possible */
|
||||
if (bm->dma_cb)
|
||||
bm->dma_cb(bm, 0);
|
||||
}
|
||||
}
|
||||
bm->cmd = val & 0x09;
|
||||
} else {
|
||||
if (!(bm->status & BM_STATUS_DMAING)) {
|
||||
bm->status |= BM_STATUS_DMAING;
|
||||
/* start dma transfer if possible */
|
||||
if (bm->dma_cb)
|
||||
bm->dma_cb(bm, 0);
|
||||
}
|
||||
bm->cmd = val & 0x09;
|
||||
}
|
||||
|
||||
bm->cmd = val & 0x09;
|
||||
}
|
||||
|
||||
uint32_t bmdma_addr_readb(void *opaque, uint32_t addr)
|
||||
static void bmdma_addr_read(IORange *ioport, uint64_t addr,
|
||||
unsigned width, uint64_t *data)
|
||||
{
|
||||
BMDMAState *bm = opaque;
|
||||
uint32_t val;
|
||||
val = (bm->addr >> ((addr & 3) * 8)) & 0xff;
|
||||
BMDMAState *bm = container_of(ioport, BMDMAState, addr_ioport);
|
||||
uint32_t mask = (1ULL << (width * 8)) - 1;
|
||||
|
||||
*data = (bm->addr >> (addr * 8)) & mask;
|
||||
#ifdef DEBUG_IDE
|
||||
printf("%s: 0x%08x\n", __func__, val);
|
||||
printf("%s: 0x%08x\n", __func__, (unsigned)*data);
|
||||
#endif
|
||||
return val;
|
||||
}
|
||||
|
||||
void bmdma_addr_writeb(void *opaque, uint32_t addr, uint32_t val)
|
||||
static void bmdma_addr_write(IORange *ioport, uint64_t addr,
|
||||
unsigned width, uint64_t data)
|
||||
{
|
||||
BMDMAState *bm = opaque;
|
||||
int shift = (addr & 3) * 8;
|
||||
BMDMAState *bm = container_of(ioport, BMDMAState, addr_ioport);
|
||||
int shift = addr * 8;
|
||||
uint32_t mask = (1ULL << (width * 8)) - 1;
|
||||
|
||||
#ifdef DEBUG_IDE
|
||||
printf("%s: 0x%08x\n", __func__, val);
|
||||
printf("%s: 0x%08x\n", __func__, (unsigned)data);
|
||||
#endif
|
||||
bm->addr &= ~(0xFF << shift);
|
||||
bm->addr |= ((val & 0xFF) << shift) & ~3;
|
||||
bm->cur_addr = bm->addr;
|
||||
bm->addr &= ~(mask << shift);
|
||||
bm->addr |= ((data & mask) << shift) & ~3;
|
||||
}
|
||||
|
||||
uint32_t bmdma_addr_readw(void *opaque, uint32_t addr)
|
||||
{
|
||||
BMDMAState *bm = opaque;
|
||||
uint32_t val;
|
||||
val = (bm->addr >> ((addr & 3) * 8)) & 0xffff;
|
||||
#ifdef DEBUG_IDE
|
||||
printf("%s: 0x%08x\n", __func__, val);
|
||||
#endif
|
||||
return val;
|
||||
}
|
||||
|
||||
void bmdma_addr_writew(void *opaque, uint32_t addr, uint32_t val)
|
||||
{
|
||||
BMDMAState *bm = opaque;
|
||||
int shift = (addr & 3) * 8;
|
||||
#ifdef DEBUG_IDE
|
||||
printf("%s: 0x%08x\n", __func__, val);
|
||||
#endif
|
||||
bm->addr &= ~(0xFFFF << shift);
|
||||
bm->addr |= ((val & 0xFFFF) << shift) & ~3;
|
||||
bm->cur_addr = bm->addr;
|
||||
}
|
||||
|
||||
uint32_t bmdma_addr_readl(void *opaque, uint32_t addr)
|
||||
{
|
||||
BMDMAState *bm = opaque;
|
||||
uint32_t val;
|
||||
val = bm->addr;
|
||||
#ifdef DEBUG_IDE
|
||||
printf("%s: 0x%08x\n", __func__, val);
|
||||
#endif
|
||||
return val;
|
||||
}
|
||||
|
||||
void bmdma_addr_writel(void *opaque, uint32_t addr, uint32_t val)
|
||||
{
|
||||
BMDMAState *bm = opaque;
|
||||
#ifdef DEBUG_IDE
|
||||
printf("%s: 0x%08x\n", __func__, val);
|
||||
#endif
|
||||
bm->addr = val & ~3;
|
||||
bm->cur_addr = bm->addr;
|
||||
}
|
||||
const IORangeOps bmdma_addr_ioport_ops = {
|
||||
.read = bmdma_addr_read,
|
||||
.write = bmdma_addr_write,
|
||||
};
|
||||
|
||||
static bool ide_bmdma_current_needed(void *opaque)
|
||||
{
|
||||
|
|
|
@ -11,12 +11,7 @@ typedef struct PCIIDEState {
|
|||
} PCIIDEState;
|
||||
|
||||
void bmdma_cmd_writeb(void *opaque, uint32_t addr, uint32_t val);
|
||||
uint32_t bmdma_addr_readb(void *opaque, uint32_t addr);
|
||||
void bmdma_addr_writeb(void *opaque, uint32_t addr, uint32_t val);
|
||||
uint32_t bmdma_addr_readw(void *opaque, uint32_t addr);
|
||||
void bmdma_addr_writew(void *opaque, uint32_t addr, uint32_t val);
|
||||
uint32_t bmdma_addr_readl(void *opaque, uint32_t addr);
|
||||
void bmdma_addr_writel(void *opaque, uint32_t addr, uint32_t val);
|
||||
extern const IORangeOps bmdma_addr_ioport_ops;
|
||||
void pci_ide_create_devs(PCIDevice *dev, DriveInfo **hd_table);
|
||||
|
||||
extern const VMStateDescription vmstate_ide_pci;
|
||||
|
|
|
@ -85,12 +85,8 @@ static void bmdma_map(PCIDevice *pci_dev, int region_num,
|
|||
register_ioport_write(addr + 1, 3, 1, bmdma_writeb, bm);
|
||||
register_ioport_read(addr, 4, 1, bmdma_readb, bm);
|
||||
|
||||
register_ioport_write(addr + 4, 4, 1, bmdma_addr_writeb, bm);
|
||||
register_ioport_read(addr + 4, 4, 1, bmdma_addr_readb, bm);
|
||||
register_ioport_write(addr + 4, 4, 2, bmdma_addr_writew, bm);
|
||||
register_ioport_read(addr + 4, 4, 2, bmdma_addr_readw, bm);
|
||||
register_ioport_write(addr + 4, 4, 4, bmdma_addr_writel, bm);
|
||||
register_ioport_read(addr + 4, 4, 4, bmdma_addr_readl, bm);
|
||||
iorange_init(&bm->addr_ioport, &bmdma_addr_ioport_ops, addr + 4, 4);
|
||||
ioport_register(&bm->addr_ioport);
|
||||
addr += 8;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,12 +87,8 @@ static void bmdma_map(PCIDevice *pci_dev, int region_num,
|
|||
register_ioport_write(addr + 1, 3, 1, bmdma_writeb, bm);
|
||||
register_ioport_read(addr, 4, 1, bmdma_readb, bm);
|
||||
|
||||
register_ioport_write(addr + 4, 4, 1, bmdma_addr_writeb, bm);
|
||||
register_ioport_read(addr + 4, 4, 1, bmdma_addr_readb, bm);
|
||||
register_ioport_write(addr + 4, 4, 2, bmdma_addr_writew, bm);
|
||||
register_ioport_read(addr + 4, 4, 2, bmdma_addr_readw, bm);
|
||||
register_ioport_write(addr + 4, 4, 4, bmdma_addr_writel, bm);
|
||||
register_ioport_read(addr + 4, 4, 4, bmdma_addr_readl, bm);
|
||||
iorange_init(&bm->addr_ioport, &bmdma_addr_ioport_ops, addr + 4, 4);
|
||||
ioport_register(&bm->addr_ioport);
|
||||
addr += 8;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ int scsi_bus_legacy_handle_cmdline(SCSIBus *bus)
|
|||
int res = 0, unit;
|
||||
|
||||
loc_push_none(&loc);
|
||||
for (unit = 0; unit < MAX_SCSI_DEVS; unit++) {
|
||||
for (unit = 0; unit < bus->ndev; unit++) {
|
||||
dinfo = drive_get(IF_SCSI, bus->busnr, unit);
|
||||
if (dinfo == NULL) {
|
||||
continue;
|
||||
|
@ -123,16 +123,6 @@ int scsi_bus_legacy_handle_cmdline(SCSIBus *bus)
|
|||
return res;
|
||||
}
|
||||
|
||||
void scsi_dev_clear_sense(SCSIDevice *dev)
|
||||
{
|
||||
memset(&dev->sense, 0, sizeof(dev->sense));
|
||||
}
|
||||
|
||||
void scsi_dev_set_sense(SCSIDevice *dev, uint8_t key)
|
||||
{
|
||||
dev->sense.key = key;
|
||||
}
|
||||
|
||||
SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, uint32_t lun)
|
||||
{
|
||||
SCSIRequest *req;
|
||||
|
|
|
@ -111,18 +111,20 @@
|
|||
#define BLANK 0xa1
|
||||
|
||||
/*
|
||||
* Status codes
|
||||
* SAM Status codes
|
||||
*/
|
||||
|
||||
#define GOOD 0x00
|
||||
#define CHECK_CONDITION 0x01
|
||||
#define CONDITION_GOOD 0x02
|
||||
#define BUSY 0x04
|
||||
#define INTERMEDIATE_GOOD 0x08
|
||||
#define INTERMEDIATE_C_GOOD 0x0a
|
||||
#define RESERVATION_CONFLICT 0x0c
|
||||
#define COMMAND_TERMINATED 0x11
|
||||
#define QUEUE_FULL 0x14
|
||||
#define CHECK_CONDITION 0x02
|
||||
#define CONDITION_GOOD 0x04
|
||||
#define BUSY 0x08
|
||||
#define INTERMEDIATE_GOOD 0x10
|
||||
#define INTERMEDIATE_C_GOOD 0x14
|
||||
#define RESERVATION_CONFLICT 0x18
|
||||
#define COMMAND_TERMINATED 0x22
|
||||
#define TASK_SET_FULL 0x28
|
||||
#define ACA_ACTIVE 0x30
|
||||
#define TASK_ABORTED 0x40
|
||||
|
||||
#define STATUS_MASK 0x3e
|
||||
|
||||
|
|
137
hw/scsi-disk.c
137
hw/scsi-disk.c
|
@ -49,6 +49,10 @@ do { fprintf(stderr, "scsi-disk: " fmt , ## __VA_ARGS__); } while (0)
|
|||
|
||||
typedef struct SCSIDiskState SCSIDiskState;
|
||||
|
||||
typedef struct SCSISense {
|
||||
uint8_t key;
|
||||
} SCSISense;
|
||||
|
||||
typedef struct SCSIDiskReq {
|
||||
SCSIRequest req;
|
||||
/* ??? We should probably keep track of whether the data transfer is
|
||||
|
@ -72,6 +76,7 @@ struct SCSIDiskState
|
|||
QEMUBH *bh;
|
||||
char *version;
|
||||
char *serial;
|
||||
SCSISense sense;
|
||||
};
|
||||
|
||||
static int scsi_handle_rw_error(SCSIDiskReq *r, int error, int type);
|
||||
|
@ -100,10 +105,22 @@ static SCSIDiskReq *scsi_find_request(SCSIDiskState *s, uint32_t tag)
|
|||
return DO_UPCAST(SCSIDiskReq, req, scsi_req_find(&s->qdev, tag));
|
||||
}
|
||||
|
||||
static void scsi_req_set_status(SCSIRequest *req, int status, int sense_code)
|
||||
static void scsi_disk_clear_sense(SCSIDiskState *s)
|
||||
{
|
||||
req->status = status;
|
||||
scsi_dev_set_sense(req->dev, sense_code);
|
||||
memset(&s->sense, 0, sizeof(s->sense));
|
||||
}
|
||||
|
||||
static void scsi_disk_set_sense(SCSIDiskState *s, uint8_t key)
|
||||
{
|
||||
s->sense.key = key;
|
||||
}
|
||||
|
||||
static void scsi_req_set_status(SCSIDiskReq *r, int status, int sense_code)
|
||||
{
|
||||
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
|
||||
|
||||
r->req.status = status;
|
||||
scsi_disk_set_sense(s, sense_code);
|
||||
}
|
||||
|
||||
/* Helper function for command completion. */
|
||||
|
@ -111,7 +128,7 @@ static void scsi_command_complete(SCSIDiskReq *r, int status, int sense)
|
|||
{
|
||||
DPRINTF("Command complete tag=0x%x status=%d sense=%d\n",
|
||||
r->req.tag, status, sense);
|
||||
scsi_req_set_status(&r->req, status, sense);
|
||||
scsi_req_set_status(r, status, sense);
|
||||
scsi_req_complete(&r->req);
|
||||
scsi_remove_request(r);
|
||||
}
|
||||
|
@ -170,6 +187,9 @@ static void scsi_read_request(SCSIDiskReq *r)
|
|||
return;
|
||||
}
|
||||
|
||||
/* No data transfer may already be in progress */
|
||||
assert(r->req.aiocb == NULL);
|
||||
|
||||
n = r->sector_count;
|
||||
if (n > SCSI_DMA_BUF_SIZE / 512)
|
||||
n = SCSI_DMA_BUF_SIZE / 512;
|
||||
|
@ -197,9 +217,6 @@ static void scsi_read_data(SCSIDevice *d, uint32_t tag)
|
|||
return;
|
||||
}
|
||||
|
||||
/* No data transfer may already be in progress */
|
||||
assert(r->req.aiocb == NULL);
|
||||
|
||||
scsi_read_request(r);
|
||||
}
|
||||
|
||||
|
@ -269,6 +286,9 @@ static void scsi_write_request(SCSIDiskReq *r)
|
|||
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
|
||||
uint32_t n;
|
||||
|
||||
/* No data transfer may already be in progress */
|
||||
assert(r->req.aiocb == NULL);
|
||||
|
||||
n = r->iov.iov_len / 512;
|
||||
if (n) {
|
||||
qemu_iovec_init_external(&r->qiov, &r->iov, 1);
|
||||
|
@ -298,9 +318,6 @@ static int scsi_write_data(SCSIDevice *d, uint32_t tag)
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* No data transfer may already be in progress */
|
||||
assert(r->req.aiocb == NULL);
|
||||
|
||||
scsi_write_request(r);
|
||||
|
||||
return 0;
|
||||
|
@ -398,15 +415,20 @@ static int scsi_disk_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf)
|
|||
|
||||
switch (page_code) {
|
||||
case 0x00: /* Supported page codes, mandatory */
|
||||
{
|
||||
int pages;
|
||||
DPRINTF("Inquiry EVPD[Supported pages] "
|
||||
"buffer size %zd\n", req->cmd.xfer);
|
||||
outbuf[buflen++] = 4; // number of pages
|
||||
pages = buflen++;
|
||||
outbuf[buflen++] = 0x00; // list of supported pages (this page)
|
||||
outbuf[buflen++] = 0x80; // unit serial number
|
||||
outbuf[buflen++] = 0x83; // device identification
|
||||
outbuf[buflen++] = 0xb0; // block device characteristics
|
||||
if (bdrv_get_type_hint(s->bs) != BDRV_TYPE_CDROM) {
|
||||
outbuf[buflen++] = 0xb0; // block device characteristics
|
||||
}
|
||||
outbuf[pages] = buflen - pages - 1; // number of pages
|
||||
break;
|
||||
|
||||
}
|
||||
case 0x80: /* Device serial number, optional */
|
||||
{
|
||||
int l = strlen(s->serial);
|
||||
|
@ -434,7 +456,7 @@ static int scsi_disk_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf)
|
|||
DPRINTF("Inquiry EVPD[Device identification] "
|
||||
"buffer size %zd\n", req->cmd.xfer);
|
||||
|
||||
outbuf[buflen++] = 3 + id_len;
|
||||
outbuf[buflen++] = 4 + id_len;
|
||||
outbuf[buflen++] = 0x2; // ASCII
|
||||
outbuf[buflen++] = 0; // not officially assigned
|
||||
outbuf[buflen++] = 0; // reserved
|
||||
|
@ -451,6 +473,11 @@ static int scsi_disk_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf)
|
|||
unsigned int opt_io_size =
|
||||
s->qdev.conf.opt_io_size / s->qdev.blocksize;
|
||||
|
||||
if (bdrv_get_type_hint(s->bs) == BDRV_TYPE_CDROM) {
|
||||
DPRINTF("Inquiry (EVPD[%02X] not supported for CDROM\n",
|
||||
page_code);
|
||||
return -1;
|
||||
}
|
||||
/* required VPD size with unmap support */
|
||||
outbuf[3] = buflen = 0x3c;
|
||||
|
||||
|
@ -812,7 +839,7 @@ static int scsi_disk_emulate_command(SCSIDiskReq *r, uint8_t *outbuf)
|
|||
goto illegal_request;
|
||||
memset(outbuf, 0, 4);
|
||||
buflen = 4;
|
||||
if (req->dev->sense.key == NOT_READY && req->cmd.xfer >= 18) {
|
||||
if (s->sense.key == NOT_READY && req->cmd.xfer >= 18) {
|
||||
memset(outbuf, 0, 18);
|
||||
buflen = 18;
|
||||
outbuf[7] = 10;
|
||||
|
@ -822,8 +849,8 @@ static int scsi_disk_emulate_command(SCSIDiskReq *r, uint8_t *outbuf)
|
|||
}
|
||||
outbuf[0] = 0xf0;
|
||||
outbuf[1] = 0;
|
||||
outbuf[2] = req->dev->sense.key;
|
||||
scsi_dev_clear_sense(req->dev);
|
||||
outbuf[2] = s->sense.key;
|
||||
scsi_disk_clear_sense(s);
|
||||
break;
|
||||
case INQUIRY:
|
||||
buflen = scsi_disk_emulate_inquiry(req, outbuf);
|
||||
|
@ -956,7 +983,7 @@ static int scsi_disk_emulate_command(SCSIDiskReq *r, uint8_t *outbuf)
|
|||
default:
|
||||
goto illegal_request;
|
||||
}
|
||||
scsi_req_set_status(req, GOOD, NO_SENSE);
|
||||
scsi_req_set_status(r, GOOD, NO_SENSE);
|
||||
return buflen;
|
||||
|
||||
not_ready:
|
||||
|
@ -977,9 +1004,7 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag,
|
|||
uint8_t *buf, int lun)
|
||||
{
|
||||
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d);
|
||||
uint64_t lba;
|
||||
uint32_t len;
|
||||
int cmdlen;
|
||||
int is_write;
|
||||
uint8_t command;
|
||||
uint8_t *outbuf;
|
||||
|
@ -998,55 +1023,21 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag,
|
|||
outbuf = (uint8_t *)r->iov.iov_base;
|
||||
is_write = 0;
|
||||
DPRINTF("Command: lun=%d tag=0x%x data=0x%02x", lun, tag, buf[0]);
|
||||
switch (command >> 5) {
|
||||
case 0:
|
||||
lba = (uint64_t) buf[3] | ((uint64_t) buf[2] << 8) |
|
||||
(((uint64_t) buf[1] & 0x1f) << 16);
|
||||
len = buf[4];
|
||||
cmdlen = 6;
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
lba = (uint64_t) buf[5] | ((uint64_t) buf[4] << 8) |
|
||||
((uint64_t) buf[3] << 16) | ((uint64_t) buf[2] << 24);
|
||||
len = buf[8] | (buf[7] << 8);
|
||||
cmdlen = 10;
|
||||
break;
|
||||
case 4:
|
||||
lba = (uint64_t) buf[9] | ((uint64_t) buf[8] << 8) |
|
||||
((uint64_t) buf[7] << 16) | ((uint64_t) buf[6] << 24) |
|
||||
((uint64_t) buf[5] << 32) | ((uint64_t) buf[4] << 40) |
|
||||
((uint64_t) buf[3] << 48) | ((uint64_t) buf[2] << 56);
|
||||
len = buf[13] | (buf[12] << 8) | (buf[11] << 16) | (buf[10] << 24);
|
||||
cmdlen = 16;
|
||||
break;
|
||||
case 5:
|
||||
lba = (uint64_t) buf[5] | ((uint64_t) buf[4] << 8) |
|
||||
((uint64_t) buf[3] << 16) | ((uint64_t) buf[2] << 24);
|
||||
len = buf[9] | (buf[8] << 8) | (buf[7] << 16) | (buf[6] << 24);
|
||||
cmdlen = 12;
|
||||
break;
|
||||
default:
|
||||
|
||||
if (scsi_req_parse(&r->req, buf) != 0) {
|
||||
BADF("Unsupported command length, command %x\n", command);
|
||||
goto fail;
|
||||
}
|
||||
#ifdef DEBUG_SCSI
|
||||
{
|
||||
int i;
|
||||
for (i = 1; i < cmdlen; i++) {
|
||||
for (i = 1; i < r->req.cmd.len; i++) {
|
||||
printf(" 0x%02x", buf[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (scsi_req_parse(&r->req, buf) != 0) {
|
||||
BADF("Unsupported command length, command %x\n", command);
|
||||
goto fail;
|
||||
}
|
||||
assert(r->req.cmd.len == cmdlen);
|
||||
assert(r->req.cmd.lba == lba);
|
||||
|
||||
if (lun || buf[1] >> 5) {
|
||||
/* Only LUN 0 supported. */
|
||||
DPRINTF("Unimplemented LUN %d\n", lun ? lun : buf[1] >> 5);
|
||||
|
@ -1084,10 +1075,11 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag,
|
|||
case READ_10:
|
||||
case READ_12:
|
||||
case READ_16:
|
||||
DPRINTF("Read (sector %" PRId64 ", count %d)\n", lba, len);
|
||||
if (lba > s->max_lba)
|
||||
len = r->req.cmd.xfer / d->blocksize;
|
||||
DPRINTF("Read (sector %" PRId64 ", count %d)\n", r->req.cmd.lba, len);
|
||||
if (r->req.cmd.lba > s->max_lba)
|
||||
goto illegal_lba;
|
||||
r->sector = lba * s->cluster_size;
|
||||
r->sector = r->req.cmd.lba * s->cluster_size;
|
||||
r->sector_count = len * s->cluster_size;
|
||||
break;
|
||||
case WRITE_6:
|
||||
|
@ -1097,42 +1089,45 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag,
|
|||
case WRITE_VERIFY:
|
||||
case WRITE_VERIFY_12:
|
||||
case WRITE_VERIFY_16:
|
||||
len = r->req.cmd.xfer / d->blocksize;
|
||||
DPRINTF("Write %s(sector %" PRId64 ", count %d)\n",
|
||||
(command & 0xe) == 0xe ? "And Verify " : "", lba, len);
|
||||
if (lba > s->max_lba)
|
||||
(command & 0xe) == 0xe ? "And Verify " : "",
|
||||
r->req.cmd.lba, len);
|
||||
if (r->req.cmd.lba > s->max_lba)
|
||||
goto illegal_lba;
|
||||
r->sector = lba * s->cluster_size;
|
||||
r->sector = r->req.cmd.lba * s->cluster_size;
|
||||
r->sector_count = len * s->cluster_size;
|
||||
is_write = 1;
|
||||
break;
|
||||
case MODE_SELECT:
|
||||
DPRINTF("Mode Select(6) (len %d)\n", len);
|
||||
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 (len > 12) {
|
||||
if (r->req.cmd.xfer > 12) {
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
case MODE_SELECT_10:
|
||||
DPRINTF("Mode Select(10) (len %d)\n", len);
|
||||
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 (len > 16) {
|
||||
if (r->req.cmd.xfer > 16) {
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
case SEEK_6:
|
||||
case SEEK_10:
|
||||
DPRINTF("Seek(%d) (sector %" PRId64 ")\n", command == SEEK_6 ? 6 : 10, lba);
|
||||
if (lba > s->max_lba) {
|
||||
DPRINTF("Seek(%d) (sector %" PRId64 ")\n", command == SEEK_6 ? 6 : 10,
|
||||
r->req.cmd.lba);
|
||||
if (r->req.cmd.lba > s->max_lba) {
|
||||
goto illegal_lba;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DPRINTF("Unknown SCSI command (%2.2x)\n", buf[0]);
|
||||
DPRINTF("Unknown SCSI command (%2.2x)\n", buf[0]);
|
||||
fail:
|
||||
scsi_command_complete(r, CHECK_CONDITION, ILLEGAL_REQUEST);
|
||||
return 0;
|
||||
return 0;
|
||||
illegal_lba:
|
||||
scsi_command_complete(r, CHECK_CONDITION, HARDWARE_ERROR);
|
||||
return 0;
|
||||
|
|
|
@ -96,17 +96,17 @@ static void scsi_command_complete(void *opaque, int ret)
|
|||
s->senselen = r->io_header.sb_len_wr;
|
||||
|
||||
if (ret != 0)
|
||||
r->req.status = BUSY << 1;
|
||||
r->req.status = BUSY;
|
||||
else {
|
||||
if (s->driver_status & SG_ERR_DRIVER_TIMEOUT) {
|
||||
r->req.status = BUSY << 1;
|
||||
r->req.status = BUSY;
|
||||
BADF("Driver Timeout\n");
|
||||
} else if (r->io_header.status)
|
||||
r->req.status = r->io_header.status;
|
||||
else if (s->driver_status & SG_ERR_DRIVER_SENSE)
|
||||
r->req.status = CHECK_CONDITION << 1;
|
||||
r->req.status = CHECK_CONDITION;
|
||||
else
|
||||
r->req.status = GOOD << 1;
|
||||
r->req.status = GOOD;
|
||||
}
|
||||
DPRINTF("Command complete 0x%p tag=0x%x status=%d\n",
|
||||
r, r->req.tag, r->req.status);
|
||||
|
@ -333,7 +333,7 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag,
|
|||
s->senselen = 7;
|
||||
s->driver_status = SG_ERR_DRIVER_SENSE;
|
||||
bus = scsi_bus_from_device(d);
|
||||
bus->complete(bus, SCSI_REASON_DONE, tag, CHECK_CONDITION << 1);
|
||||
bus->complete(bus, SCSI_REASON_DONE, tag, CHECK_CONDITION);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
11
hw/scsi.h
11
hw/scsi.h
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "qdev.h"
|
||||
#include "block.h"
|
||||
#include "blockdev.h"
|
||||
#include "block_int.h"
|
||||
|
||||
#define SCSI_CMD_BUF_SIZE 16
|
||||
|
@ -25,10 +26,6 @@ enum SCSIXferMode {
|
|||
SCSI_XFER_TO_DEV, /* WRITE, MODE_SELECT, ... */
|
||||
};
|
||||
|
||||
typedef struct SCSISense {
|
||||
uint8_t key;
|
||||
} SCSISense;
|
||||
|
||||
typedef struct SCSIRequest {
|
||||
SCSIBus *bus;
|
||||
SCSIDevice *dev;
|
||||
|
@ -56,7 +53,6 @@ struct SCSIDevice
|
|||
QTAILQ_HEAD(, SCSIRequest) requests;
|
||||
int blocksize;
|
||||
int type;
|
||||
struct SCSISense sense;
|
||||
};
|
||||
|
||||
/* cdrom.c */
|
||||
|
@ -86,7 +82,7 @@ struct SCSIBus {
|
|||
int tcq, ndev;
|
||||
scsi_completionfn complete;
|
||||
|
||||
SCSIDevice *devs[8];
|
||||
SCSIDevice *devs[MAX_SCSI_DEVS];
|
||||
};
|
||||
|
||||
void scsi_bus_new(SCSIBus *bus, DeviceState *host, int tcq, int ndev,
|
||||
|
@ -101,9 +97,6 @@ static inline SCSIBus *scsi_bus_from_device(SCSIDevice *d)
|
|||
SCSIDevice *scsi_bus_legacy_add_drive(SCSIBus *bus, BlockDriverState *bdrv, int unit);
|
||||
int scsi_bus_legacy_handle_cmdline(SCSIBus *bus);
|
||||
|
||||
void scsi_dev_clear_sense(SCSIDevice *dev);
|
||||
void scsi_dev_set_sense(SCSIDevice *dev, uint8_t key);
|
||||
|
||||
SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, uint32_t lun);
|
||||
SCSIRequest *scsi_req_find(SCSIDevice *d, uint32_t tag);
|
||||
void scsi_req_free(SCSIRequest *req);
|
||||
|
|
|
@ -181,6 +181,10 @@ static int ioreq_parse(struct ioreq *ioreq)
|
|||
ioreq->prot = PROT_WRITE; /* to memory */
|
||||
break;
|
||||
case BLKIF_OP_WRITE_BARRIER:
|
||||
if (!ioreq->req.nr_segments) {
|
||||
ioreq->presync = 1;
|
||||
return 0;
|
||||
}
|
||||
if (!syncwrite)
|
||||
ioreq->presync = ioreq->postsync = 1;
|
||||
/* fall through */
|
||||
|
@ -305,7 +309,7 @@ static int ioreq_runio_qemu_sync(struct ioreq *ioreq)
|
|||
int i, rc, len = 0;
|
||||
off_t pos;
|
||||
|
||||
if (ioreq_map(ioreq) == -1)
|
||||
if (ioreq->req.nr_segments && ioreq_map(ioreq) == -1)
|
||||
goto err;
|
||||
if (ioreq->presync)
|
||||
bdrv_flush(blkdev->bs);
|
||||
|
@ -329,6 +333,8 @@ static int ioreq_runio_qemu_sync(struct ioreq *ioreq)
|
|||
break;
|
||||
case BLKIF_OP_WRITE:
|
||||
case BLKIF_OP_WRITE_BARRIER:
|
||||
if (!ioreq->req.nr_segments)
|
||||
break;
|
||||
pos = ioreq->start;
|
||||
for (i = 0; i < ioreq->v.niov; i++) {
|
||||
rc = bdrv_write(blkdev->bs, pos / BLOCK_SIZE,
|
||||
|
@ -386,7 +392,7 @@ static int ioreq_runio_qemu_aio(struct ioreq *ioreq)
|
|||
{
|
||||
struct XenBlkDev *blkdev = ioreq->blkdev;
|
||||
|
||||
if (ioreq_map(ioreq) == -1)
|
||||
if (ioreq->req.nr_segments && ioreq_map(ioreq) == -1)
|
||||
goto err;
|
||||
|
||||
ioreq->aio_inflight++;
|
||||
|
@ -403,6 +409,8 @@ static int ioreq_runio_qemu_aio(struct ioreq *ioreq)
|
|||
case BLKIF_OP_WRITE:
|
||||
case BLKIF_OP_WRITE_BARRIER:
|
||||
ioreq->aio_inflight++;
|
||||
if (!ioreq->req.nr_segments)
|
||||
break;
|
||||
bdrv_aio_writev(blkdev->bs, ioreq->start / BLOCK_SIZE,
|
||||
&ioreq->v, ioreq->v.size / BLOCK_SIZE,
|
||||
qemu_aio_complete, ioreq);
|
||||
|
|
Loading…
Reference in New Issue