summaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
authorAnthony Liguori <aliguori@us.ibm.com>2011-06-15 09:02:18 -0500
committerAnthony Liguori <aliguori@us.ibm.com>2011-06-15 09:02:18 -0500
commit1f8bedba79f453596b06e9ebbfa2be8db1eccb68 (patch)
tree225ac48db5dcaff9641d404ec2760921f76f800e /hw
parentf79116867ec80ed5d1d10043a3fd9ac8afd182c1 (diff)
parent7887f6201ff3fdc9a142eba14d61c563adb57596 (diff)
downloadqemu-1f8bedba79f453596b06e9ebbfa2be8db1eccb68.tar.gz
qemu-1f8bedba79f453596b06e9ebbfa2be8db1eccb68.tar.bz2
qemu-1f8bedba79f453596b06e9ebbfa2be8db1eccb68.zip
Merge remote-tracking branch 'kwolf/for-anthony' into staging
Diffstat (limited to 'hw')
-rw-r--r--hw/ide/core.c154
-rw-r--r--hw/ide/internal.h32
-rw-r--r--hw/ide/macio.c13
-rw-r--r--hw/ide/pci.c88
-rw-r--r--hw/ide/pci.h4
-rw-r--r--hw/ide/qdev.c5
6 files changed, 267 insertions, 29 deletions
diff --git a/hw/ide/core.c b/hw/ide/core.c
index 95beb175b3..ca17a436c0 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -78,7 +78,7 @@ static void ide_identify(IDEState *s)
{
uint16_t *p;
unsigned int oldsize;
- IDEDevice *dev;
+ IDEDevice *dev = s->unit ? s->bus->slave : s->bus->master;
if (s->identify_set) {
memcpy(s->io_buffer, s->identify_data, sizeof(s->identify_data));
@@ -124,6 +124,9 @@ static void ide_identify(IDEState *s)
put_le16(p + 66, 120);
put_le16(p + 67, 120);
put_le16(p + 68, 120);
+ if (dev && dev->conf.discard_granularity) {
+ put_le16(p + 69, (1 << 14)); /* determinate TRIM behavior */
+ }
if (s->ncq_queues) {
put_le16(p + 75, s->ncq_queues - 1);
@@ -154,9 +157,12 @@ static void ide_identify(IDEState *s)
put_le16(p + 101, s->nb_sectors >> 16);
put_le16(p + 102, s->nb_sectors >> 32);
put_le16(p + 103, s->nb_sectors >> 48);
- dev = s->unit ? s->bus->slave : s->bus->master;
+
if (dev && dev->conf.physical_block_size)
put_le16(p + 106, 0x6000 | get_physical_block_exp(&dev->conf));
+ if (dev && dev->conf.discard_granularity) {
+ put_le16(p + 169, 1); /* TRIM support */
+ }
memcpy(s->identify_data, p, sizeof(s->identify_data));
s->identify_set = 1;
@@ -299,6 +305,74 @@ static void ide_set_signature(IDEState *s)
}
}
+typedef struct TrimAIOCB {
+ BlockDriverAIOCB common;
+ QEMUBH *bh;
+ int ret;
+} TrimAIOCB;
+
+static void trim_aio_cancel(BlockDriverAIOCB *acb)
+{
+ TrimAIOCB *iocb = container_of(acb, TrimAIOCB, common);
+
+ qemu_bh_delete(iocb->bh);
+ iocb->bh = NULL;
+ qemu_aio_release(iocb);
+}
+
+static AIOPool trim_aio_pool = {
+ .aiocb_size = sizeof(TrimAIOCB),
+ .cancel = trim_aio_cancel,
+};
+
+static void ide_trim_bh_cb(void *opaque)
+{
+ TrimAIOCB *iocb = opaque;
+
+ iocb->common.cb(iocb->common.opaque, iocb->ret);
+
+ qemu_bh_delete(iocb->bh);
+ iocb->bh = NULL;
+
+ qemu_aio_release(iocb);
+}
+
+BlockDriverAIOCB *ide_issue_trim(BlockDriverState *bs,
+ int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
+ BlockDriverCompletionFunc *cb, void *opaque)
+{
+ TrimAIOCB *iocb;
+ int i, j, ret;
+
+ iocb = qemu_aio_get(&trim_aio_pool, bs, cb, opaque);
+ iocb->bh = qemu_bh_new(ide_trim_bh_cb, iocb);
+ iocb->ret = 0;
+
+ for (j = 0; j < qiov->niov; j++) {
+ uint64_t *buffer = qiov->iov[j].iov_base;
+
+ for (i = 0; i < qiov->iov[j].iov_len / 8; i++) {
+ /* 6-byte LBA + 2-byte range per entry */
+ uint64_t entry = le64_to_cpu(buffer[i]);
+ uint64_t sector = entry & 0x0000ffffffffffffULL;
+ uint16_t count = entry >> 48;
+
+ if (count == 0) {
+ break;
+ }
+
+ ret = bdrv_discard(bs, sector, count);
+ if (!iocb->ret) {
+ iocb->ret = ret;
+ }
+ }
+ }
+
+ qemu_bh_schedule(iocb->bh);
+
+ return &iocb->common;
+}
+
static inline void ide_abort_command(IDEState *s)
{
s->status = READY_STAT | ERR_STAT;
@@ -446,7 +520,7 @@ static int ide_handle_rw_error(IDEState *s, int error, int op)
if ((error == ENOSPC && action == BLOCK_ERR_STOP_ENOSPC)
|| action == BLOCK_ERR_STOP_ANY) {
s->bus->dma->ops->set_unit(s->bus->dma, s->unit);
- s->bus->dma->ops->add_status(s->bus->dma, op);
+ s->bus->error_status = op;
bdrv_mon_event(s->bs, BDRV_ACTION_STOP, is_read);
vm_stop(VMSTOP_DISKFULL);
} else {
@@ -472,8 +546,11 @@ handle_rw_error:
if (ret < 0) {
int op = BM_STATUS_DMA_RETRY;
- if (s->is_read)
+ if (s->dma_cmd == IDE_DMA_READ)
op |= BM_STATUS_RETRY_READ;
+ else if (s->dma_cmd == IDE_DMA_TRIM)
+ op |= BM_STATUS_RETRY_TRIM;
+
if (ide_handle_rw_error(s, -ret, op)) {
return;
}
@@ -482,7 +559,7 @@ handle_rw_error:
n = s->io_buffer_size >> 9;
sector_num = ide_get_sector(s);
if (n > 0) {
- dma_buf_commit(s, s->is_read);
+ dma_buf_commit(s, ide_cmd_is_read(s));
sector_num += n;
ide_set_sector(s, sector_num);
s->nsector -= n;
@@ -499,23 +576,30 @@ handle_rw_error:
n = s->nsector;
s->io_buffer_index = 0;
s->io_buffer_size = n * 512;
- if (s->bus->dma->ops->prepare_buf(s->bus->dma, s->is_read) == 0) {
+ if (s->bus->dma->ops->prepare_buf(s->bus->dma, ide_cmd_is_read(s)) == 0) {
/* The PRDs were too short. Reset the Active bit, but don't raise an
* interrupt. */
goto eot;
}
#ifdef DEBUG_AIO
- printf("ide_dma_cb: sector_num=%" PRId64 " n=%d, is_read=%d\n",
- sector_num, n, s->is_read);
+ printf("ide_dma_cb: sector_num=%" PRId64 " n=%d, cmd_cmd=%d\n",
+ sector_num, n, s->dma_cmd);
#endif
- if (s->is_read) {
+ switch (s->dma_cmd) {
+ case IDE_DMA_READ:
s->bus->dma->aiocb = dma_bdrv_read(s->bs, &s->sg, sector_num,
ide_dma_cb, s);
- } else {
+ break;
+ case IDE_DMA_WRITE:
s->bus->dma->aiocb = dma_bdrv_write(s->bs, &s->sg, sector_num,
ide_dma_cb, s);
+ break;
+ case IDE_DMA_TRIM:
+ s->bus->dma->aiocb = dma_bdrv_io(s->bs, &s->sg, sector_num,
+ ide_issue_trim, ide_dma_cb, s, 1);
+ break;
}
if (!s->bus->dma->aiocb) {
@@ -528,12 +612,12 @@ eot:
ide_set_inactive(s);
}
-static void ide_sector_start_dma(IDEState *s, int is_read)
+static void ide_sector_start_dma(IDEState *s, enum ide_dma_cmd dma_cmd)
{
s->status = READY_STAT | SEEK_STAT | DRQ_STAT | BUSY_STAT;
s->io_buffer_index = 0;
s->io_buffer_size = 0;
- s->is_read = is_read;
+ s->dma_cmd = dma_cmd;
s->bus->dma->ops->start_dma(s->bus->dma, s, ide_dma_cb);
}
@@ -815,6 +899,18 @@ void ide_exec_cmd(IDEBus *bus, uint32_t val)
return;
switch(val) {
+ case WIN_DSM:
+ switch (s->feature) {
+ case DSM_TRIM:
+ if (!s->bs) {
+ goto abort_cmd;
+ }
+ ide_sector_start_dma(s, IDE_DMA_TRIM);
+ break;
+ default:
+ goto abort_cmd;
+ }
+ break;
case WIN_IDENTIFY:
if (s->bs && s->drive_kind != IDE_CD) {
if (s->drive_kind != IDE_CFATA)
@@ -916,7 +1012,7 @@ void ide_exec_cmd(IDEBus *bus, uint32_t val)
if (!s->bs)
goto abort_cmd;
ide_cmd_lba48_transform(s, lba48);
- ide_sector_start_dma(s, 1);
+ ide_sector_start_dma(s, IDE_DMA_READ);
break;
case WIN_WRITEDMA_EXT:
lba48 = 1;
@@ -925,7 +1021,7 @@ void ide_exec_cmd(IDEBus *bus, uint32_t val)
if (!s->bs)
goto abort_cmd;
ide_cmd_lba48_transform(s, lba48);
- ide_sector_start_dma(s, 0);
+ ide_sector_start_dma(s, IDE_DMA_WRITE);
s->media_changed = 1;
break;
case WIN_READ_NATIVE_MAX_EXT:
@@ -1837,7 +1933,8 @@ static bool ide_drive_pio_state_needed(void *opaque)
{
IDEState *s = opaque;
- return (s->status & DRQ_STAT) != 0;
+ return ((s->status & DRQ_STAT) != 0)
+ || (s->bus->error_status & BM_STATUS_PIO_RETRY);
}
static bool ide_atapi_gesn_needed(void *opaque)
@@ -1847,6 +1944,13 @@ static bool ide_atapi_gesn_needed(void *opaque)
return s->events.new_media || s->events.eject_request;
}
+static bool ide_error_needed(void *opaque)
+{
+ IDEBus *bus = opaque;
+
+ return (bus->error_status != 0);
+}
+
/* Fields for GET_EVENT_STATUS_NOTIFICATION ATAPI command */
const VMStateDescription vmstate_ide_atapi_gesn_state = {
.name ="ide_drive/atapi/gesn_state",
@@ -1856,6 +1960,7 @@ const VMStateDescription vmstate_ide_atapi_gesn_state = {
.fields = (VMStateField []) {
VMSTATE_BOOL(events.new_media, IDEState),
VMSTATE_BOOL(events.eject_request, IDEState),
+ VMSTATE_END_OF_LIST()
}
};
@@ -1921,6 +2026,17 @@ const VMStateDescription vmstate_ide_drive = {
}
};
+const VMStateDescription vmstate_ide_error_status = {
+ .name ="ide_bus/error",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField []) {
+ VMSTATE_INT32(error_status, IDEBus),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
const VMStateDescription vmstate_ide_bus = {
.name = "ide_bus",
.version_id = 1,
@@ -1930,6 +2046,14 @@ const VMStateDescription vmstate_ide_bus = {
VMSTATE_UINT8(cmd, IDEBus),
VMSTATE_UINT8(unit, IDEBus),
VMSTATE_END_OF_LIST()
+ },
+ .subsections = (VMStateSubsection []) {
+ {
+ .vmsd = &vmstate_ide_error_status,
+ .needed = ide_error_needed,
+ }, {
+ /* empty */
+ }
}
};
diff --git a/hw/ide/internal.h b/hw/ide/internal.h
index c2b35ec5e6..02e805f070 100644
--- a/hw/ide/internal.h
+++ b/hw/ide/internal.h
@@ -62,7 +62,11 @@ typedef struct IDEDMAOps IDEDMAOps;
*/
#define CFA_REQ_EXT_ERROR_CODE 0x03 /* CFA Request Extended Error Code */
/*
- * 0x04->0x07 Reserved
+ * 0x04->0x05 Reserved
+ */
+#define WIN_DSM 0x06
+/*
+ * 0x07 Reserved
*/
#define WIN_SRST 0x08 /* ATAPI soft reset command */
#define WIN_DEVICE_RESET 0x08
@@ -190,6 +194,9 @@ typedef struct IDEDMAOps IDEDMAOps;
#define IDE_DMA_BUF_SECTORS 256
+/* feature values for Data Set Management */
+#define DSM_TRIM 0x01
+
#if (IDE_DMA_BUF_SECTORS < MAX_MULT_SECTORS)
#error "IDE_DMA_BUF_SECTORS must be bigger or equal to MAX_MULT_SECTORS"
#endif
@@ -379,6 +386,15 @@ struct unreported_events {
bool new_media;
};
+enum ide_dma_cmd {
+ IDE_DMA_READ,
+ IDE_DMA_WRITE,
+ IDE_DMA_TRIM,
+};
+
+#define ide_cmd_is_read(s) \
+ ((s)->dma_cmd == IDE_DMA_READ)
+
/* NOTE: IDEState represents in fact one drive */
struct IDEState {
IDEBus *bus;
@@ -446,7 +462,7 @@ struct IDEState {
uint32_t mdata_size;
uint8_t *mdata_storage;
int media_changed;
- int is_read;
+ enum ide_dma_cmd dma_cmd;
/* SMART */
uint8_t smart_enabled;
uint8_t smart_autosave;
@@ -486,6 +502,8 @@ struct IDEBus {
uint8_t unit;
uint8_t cmd;
qemu_irq irq;
+
+ int error_status;
};
struct IDEDevice {
@@ -505,10 +523,17 @@ struct IDEDeviceInfo {
#define BM_STATUS_DMAING 0x01
#define BM_STATUS_ERROR 0x02
#define BM_STATUS_INT 0x04
+
+/* FIXME These are not status register bits */
#define BM_STATUS_DMA_RETRY 0x08
#define BM_STATUS_PIO_RETRY 0x10
#define BM_STATUS_RETRY_READ 0x20
#define BM_STATUS_RETRY_FLUSH 0x40
+#define BM_STATUS_RETRY_TRIM 0x80
+
+#define BM_MIGRATION_COMPAT_STATUS_BITS \
+ (BM_STATUS_DMA_RETRY | BM_STATUS_PIO_RETRY | \
+ BM_STATUS_RETRY_READ | BM_STATUS_RETRY_FLUSH)
#define BM_CMD_START 0x01
#define BM_CMD_READ 0x08
@@ -575,6 +600,9 @@ void ide_transfer_start(IDEState *s, uint8_t *buf, int size,
EndTransferFunc *end_transfer_func);
void ide_transfer_stop(IDEState *s);
void ide_set_inactive(IDEState *s);
+BlockDriverAIOCB *ide_issue_trim(BlockDriverState *bs,
+ int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
+ BlockDriverCompletionFunc *cb, void *opaque);
/* hw/ide/atapi.c */
void ide_atapi_cmd(IDEState *s);
diff --git a/hw/ide/macio.c b/hw/ide/macio.c
index 7107f6b3c2..7daeb31ec3 100644
--- a/hw/ide/macio.c
+++ b/hw/ide/macio.c
@@ -145,12 +145,21 @@ static void pmac_ide_transfer_cb(void *opaque, int ret)
io->addr += io->len;
io->len = 0;
- if (s->is_read)
+ switch (s->dma_cmd) {
+ case IDE_DMA_READ:
m->aiocb = dma_bdrv_read(s->bs, &s->sg, sector_num,
pmac_ide_transfer_cb, io);
- else
+ break;
+ case IDE_DMA_WRITE:
m->aiocb = dma_bdrv_write(s->bs, &s->sg, sector_num,
pmac_ide_transfer_cb, io);
+ break;
+ case IDE_DMA_TRIM:
+ m->aiocb = dma_bdrv_io(s->bs, &s->sg, sector_num,
+ ide_issue_trim, pmac_ide_transfer_cb, s, 1);
+ break;
+ }
+
if (!m->aiocb)
pmac_ide_transfer_cb(io, -1);
}
diff --git a/hw/ide/pci.c b/hw/ide/pci.c
index a4726adbea..9f3050a15e 100644
--- a/hw/ide/pci.c
+++ b/hw/ide/pci.c
@@ -169,7 +169,7 @@ static int bmdma_set_inactive(IDEDMA *dma)
return 0;
}
-static void bmdma_restart_dma(BMDMAState *bm, int is_read)
+static void bmdma_restart_dma(BMDMAState *bm, enum ide_dma_cmd dma_cmd)
{
IDEState *s = bmdma_active_if(bm);
@@ -177,33 +177,48 @@ static void bmdma_restart_dma(BMDMAState *bm, int is_read)
s->io_buffer_index = 0;
s->io_buffer_size = 0;
s->nsector = bm->nsector;
- s->is_read = is_read;
+ s->dma_cmd = dma_cmd;
bm->cur_addr = bm->addr;
bm->dma_cb = ide_dma_cb;
bmdma_start_dma(&bm->dma, s, bm->dma_cb);
}
+/* TODO This should be common IDE code */
static void bmdma_restart_bh(void *opaque)
{
BMDMAState *bm = opaque;
+ IDEBus *bus = bm->bus;
int is_read;
+ int error_status;
qemu_bh_delete(bm->bh);
bm->bh = NULL;
- is_read = !!(bm->status & BM_STATUS_RETRY_READ);
+ if (bm->unit == (uint8_t) -1) {
+ return;
+ }
+
+ is_read = !!(bus->error_status & BM_STATUS_RETRY_READ);
- if (bm->status & BM_STATUS_DMA_RETRY) {
- bm->status &= ~(BM_STATUS_DMA_RETRY | BM_STATUS_RETRY_READ);
- bmdma_restart_dma(bm, is_read);
- } else if (bm->status & BM_STATUS_PIO_RETRY) {
- bm->status &= ~(BM_STATUS_PIO_RETRY | BM_STATUS_RETRY_READ);
+ /* The error status must be cleared before resubmitting the request: The
+ * request may fail again, and this case can only be distinguished if the
+ * called function can set a new error status. */
+ error_status = bus->error_status;
+ bus->error_status = 0;
+
+ if (error_status & BM_STATUS_DMA_RETRY) {
+ if (error_status & BM_STATUS_RETRY_TRIM) {
+ bmdma_restart_dma(bm, IDE_DMA_TRIM);
+ } else {
+ bmdma_restart_dma(bm, is_read ? IDE_DMA_READ : IDE_DMA_WRITE);
+ }
+ } else if (error_status & BM_STATUS_PIO_RETRY) {
if (is_read) {
ide_sector_read(bmdma_active_if(bm));
} else {
ide_sector_write(bmdma_active_if(bm));
}
- } else if (bm->status & BM_STATUS_RETRY_FLUSH) {
+ } else if (error_status & BM_STATUS_RETRY_FLUSH) {
ide_flush_cache(bmdma_active_if(bm));
}
}
@@ -351,6 +366,43 @@ static bool ide_bmdma_current_needed(void *opaque)
return (bm->cur_prd_len != 0);
}
+static bool ide_bmdma_status_needed(void *opaque)
+{
+ BMDMAState *bm = opaque;
+
+ /* Older versions abused some bits in the status register for internal
+ * error state. If any of these bits are set, we must add a subsection to
+ * transfer the real status register */
+ uint8_t abused_bits = BM_MIGRATION_COMPAT_STATUS_BITS;
+
+ return ((bm->status & abused_bits) != 0);
+}
+
+static void ide_bmdma_pre_save(void *opaque)
+{
+ BMDMAState *bm = opaque;
+ uint8_t abused_bits = BM_MIGRATION_COMPAT_STATUS_BITS;
+
+ bm->migration_compat_status =
+ (bm->status & ~abused_bits) | (bm->bus->error_status & abused_bits);
+}
+
+/* This function accesses bm->bus->error_status which is loaded only after
+ * BMDMA itself. This is why the function is called from ide_pci_post_load
+ * instead of being registered with VMState where it would run too early. */
+static int ide_bmdma_post_load(void *opaque, int version_id)
+{
+ BMDMAState *bm = opaque;
+ uint8_t abused_bits = BM_MIGRATION_COMPAT_STATUS_BITS;
+
+ if (bm->status == 0) {
+ bm->status = bm->migration_compat_status & ~abused_bits;
+ bm->bus->error_status |= bm->migration_compat_status & abused_bits;
+ }
+
+ return 0;
+}
+
static const VMStateDescription vmstate_bmdma_current = {
.name = "ide bmdma_current",
.version_id = 1,
@@ -365,15 +417,26 @@ static const VMStateDescription vmstate_bmdma_current = {
}
};
+const VMStateDescription vmstate_bmdma_status = {
+ .name ="ide bmdma/status",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField []) {
+ VMSTATE_UINT8(status, BMDMAState),
+ VMSTATE_END_OF_LIST()
+ }
+};
static const VMStateDescription vmstate_bmdma = {
.name = "ide bmdma",
.version_id = 3,
.minimum_version_id = 0,
.minimum_version_id_old = 0,
+ .pre_save = ide_bmdma_pre_save,
.fields = (VMStateField []) {
VMSTATE_UINT8(cmd, BMDMAState),
- VMSTATE_UINT8(status, BMDMAState),
+ VMSTATE_UINT8(migration_compat_status, BMDMAState),
VMSTATE_UINT32(addr, BMDMAState),
VMSTATE_INT64(sector_num, BMDMAState),
VMSTATE_UINT32(nsector, BMDMAState),
@@ -385,6 +448,9 @@ static const VMStateDescription vmstate_bmdma = {
.vmsd = &vmstate_bmdma_current,
.needed = ide_bmdma_current_needed,
}, {
+ .vmsd = &vmstate_bmdma_status,
+ .needed = ide_bmdma_status_needed,
+ }, {
/* empty */
}
}
@@ -399,7 +465,9 @@ static int ide_pci_post_load(void *opaque, int version_id)
/* current versions always store 0/1, but older version
stored bigger values. We only need last bit */
d->bmdma[i].unit &= 1;
+ ide_bmdma_post_load(&d->bmdma[i], -1);
}
+
return 0;
}
diff --git a/hw/ide/pci.h b/hw/ide/pci.h
index cd72cbaeb9..b4f3691a5c 100644
--- a/hw/ide/pci.h
+++ b/hw/ide/pci.h
@@ -22,6 +22,10 @@ typedef struct BMDMAState {
IORange addr_ioport;
QEMUBH *bh;
qemu_irq irq;
+
+ /* Bit 0-2 and 7: BM status register
+ * Bit 3-6: bus->error_status */
+ uint8_t migration_compat_status;
} BMDMAState;
typedef struct PCIIDEState {
diff --git a/hw/ide/qdev.c b/hw/ide/qdev.c
index 3f9dc89c6d..d9b8f24bb5 100644
--- a/hw/ide/qdev.c
+++ b/hw/ide/qdev.c
@@ -125,6 +125,11 @@ static int ide_dev_initfn(IDEDevice *dev, IDEDriveKind kind)
const char *serial;
DriveInfo *dinfo;
+ if (dev->conf.discard_granularity && dev->conf.discard_granularity != 512) {
+ error_report("discard_granularity must be 512 for ide");
+ return -1;
+ }
+
serial = dev->serial;
if (!serial) {
/* try to fall back to value set with legacy -drive serial=... */