aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--block.c2
-rw-r--r--block/qcow2-cluster.c41
-rw-r--r--block/qcow2-refcount.c136
-rw-r--r--block/qcow2-snapshot.c6
-rw-r--r--block/qcow2.c30
-rw-r--r--block/qcow2.h32
-rw-r--r--block/vmdk.c9
-rw-r--r--blockdev.c118
-rw-r--r--hw/ide/core.c1242
-rw-r--r--tests/qemu-iotests/group2
10 files changed, 892 insertions, 726 deletions
diff --git a/block.c b/block.c
index b88ad2fa04..8e77d46b02 100644
--- a/block.c
+++ b/block.c
@@ -1045,7 +1045,7 @@ int bdrv_open(BlockDriverState *bs, const char *filename, QDict *options,
extract_subqdict(options, &file_options, "file.");
ret = bdrv_file_open(&file, filename, file_options,
- bdrv_open_flags(bs, flags));
+ bdrv_open_flags(bs, flags | BDRV_O_UNMAP));
if (ret < 0) {
goto fail;
}
diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
index 76f30e5c26..cca76d4fcd 100644
--- a/block/qcow2-cluster.c
+++ b/block/qcow2-cluster.c
@@ -98,14 +98,16 @@ int qcow2_grow_l1_table(BlockDriverState *bs, uint64_t min_size,
goto fail;
}
g_free(s->l1_table);
- qcow2_free_clusters(bs, s->l1_table_offset, s->l1_size * sizeof(uint64_t));
+ qcow2_free_clusters(bs, s->l1_table_offset, s->l1_size * sizeof(uint64_t),
+ QCOW2_DISCARD_OTHER);
s->l1_table_offset = new_l1_table_offset;
s->l1_table = new_l1_table;
s->l1_size = new_l1_size;
return 0;
fail:
g_free(new_l1_table);
- qcow2_free_clusters(bs, new_l1_table_offset, new_l1_size2);
+ qcow2_free_clusters(bs, new_l1_table_offset, new_l1_size2,
+ QCOW2_DISCARD_OTHER);
return ret;
}
@@ -548,7 +550,8 @@ static int get_cluster_table(BlockDriverState *bs, uint64_t offset,
/* Then decrease the refcount of the old table */
if (l2_offset) {
- qcow2_free_clusters(bs, l2_offset, s->l2_size * sizeof(uint64_t));
+ qcow2_free_clusters(bs, l2_offset, s->l2_size * sizeof(uint64_t),
+ QCOW2_DISCARD_OTHER);
}
}
@@ -715,10 +718,14 @@ int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m)
/*
* If this was a COW, we need to decrease the refcount of the old cluster.
* Also flush bs->file to get the right order for L2 and refcount update.
+ *
+ * Don't discard clusters that reach a refcount of 0 (e.g. compressed
+ * clusters), the next write will reuse them anyway.
*/
if (j != 0) {
for (i = 0; i < j; i++) {
- qcow2_free_any_clusters(bs, be64_to_cpu(old_cluster[i]), 1);
+ qcow2_free_any_clusters(bs, be64_to_cpu(old_cluster[i]), 1,
+ QCOW2_DISCARD_NEVER);
}
}
@@ -1339,7 +1346,7 @@ static int discard_single_l2(BlockDriverState *bs, uint64_t offset,
l2_table[l2_index + i] = cpu_to_be64(0);
/* Then decrease the refcount */
- qcow2_free_any_clusters(bs, old_offset, 1);
+ qcow2_free_any_clusters(bs, old_offset, 1, QCOW2_DISCARD_REQUEST);
}
ret = qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
@@ -1370,18 +1377,25 @@ int qcow2_discard_clusters(BlockDriverState *bs, uint64_t offset,
nb_clusters = size_to_clusters(s, end_offset - offset);
+ s->cache_discards = true;
+
/* Each L2 table is handled by its own loop iteration */
while (nb_clusters > 0) {
ret = discard_single_l2(bs, offset, nb_clusters);
if (ret < 0) {
- return ret;
+ goto fail;
}
nb_clusters -= ret;
offset += (ret * s->cluster_size);
}
- return 0;
+ ret = 0;
+fail:
+ s->cache_discards = false;
+ qcow2_process_discards(bs, ret);
+
+ return ret;
}
/*
@@ -1415,7 +1429,7 @@ static int zero_single_l2(BlockDriverState *bs, uint64_t offset,
qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_table);
if (old_offset & QCOW_OFLAG_COMPRESSED) {
l2_table[l2_index + i] = cpu_to_be64(QCOW_OFLAG_ZERO);
- qcow2_free_any_clusters(bs, old_offset, 1);
+ qcow2_free_any_clusters(bs, old_offset, 1, QCOW2_DISCARD_REQUEST);
} else {
l2_table[l2_index + i] |= cpu_to_be64(QCOW_OFLAG_ZERO);
}
@@ -1443,15 +1457,22 @@ int qcow2_zero_clusters(BlockDriverState *bs, uint64_t offset, int nb_sectors)
/* Each L2 table is handled by its own loop iteration */
nb_clusters = size_to_clusters(s, nb_sectors << BDRV_SECTOR_BITS);
+ s->cache_discards = true;
+
while (nb_clusters > 0) {
ret = zero_single_l2(bs, offset, nb_clusters);
if (ret < 0) {
- return ret;
+ goto fail;
}
nb_clusters -= ret;
offset += (ret * s->cluster_size);
}
- return 0;
+ ret = 0;
+fail:
+ s->cache_discards = false;
+ qcow2_process_discards(bs, ret);
+
+ return ret;
}
diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c
index b32738f8d9..1244693f39 100644
--- a/block/qcow2-refcount.c
+++ b/block/qcow2-refcount.c
@@ -29,7 +29,7 @@
static int64_t alloc_clusters_noref(BlockDriverState *bs, int64_t size);
static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
int64_t offset, int64_t length,
- int addend);
+ int addend, enum qcow2_discard_type type);
/*********************************************************/
@@ -235,7 +235,8 @@ static int alloc_refcount_block(BlockDriverState *bs,
} else {
/* Described somewhere else. This can recurse at most twice before we
* arrive at a block that describes itself. */
- ret = update_refcount(bs, new_block, s->cluster_size, 1);
+ ret = update_refcount(bs, new_block, s->cluster_size, 1,
+ QCOW2_DISCARD_NEVER);
if (ret < 0) {
goto fail_block;
}
@@ -399,7 +400,8 @@ static int alloc_refcount_block(BlockDriverState *bs,
/* Free old table. Remember, we must not change free_cluster_index */
uint64_t old_free_cluster_index = s->free_cluster_index;
- qcow2_free_clusters(bs, old_table_offset, old_table_size * sizeof(uint64_t));
+ qcow2_free_clusters(bs, old_table_offset, old_table_size * sizeof(uint64_t),
+ QCOW2_DISCARD_OTHER);
s->free_cluster_index = old_free_cluster_index;
ret = load_refcount_block(bs, new_block, (void**) refcount_block);
@@ -418,9 +420,77 @@ fail_block:
return ret;
}
+void qcow2_process_discards(BlockDriverState *bs, int ret)
+{
+ BDRVQcowState *s = bs->opaque;
+ Qcow2DiscardRegion *d, *next;
+
+ QTAILQ_FOREACH_SAFE(d, &s->discards, next, next) {
+ QTAILQ_REMOVE(&s->discards, d, next);
+
+ /* Discard is optional, ignore the return value */
+ if (ret >= 0) {
+ bdrv_discard(bs->file,
+ d->offset >> BDRV_SECTOR_BITS,
+ d->bytes >> BDRV_SECTOR_BITS);
+ }
+
+ g_free(d);
+ }
+}
+
+static void update_refcount_discard(BlockDriverState *bs,
+ uint64_t offset, uint64_t length)
+{
+ BDRVQcowState *s = bs->opaque;
+ Qcow2DiscardRegion *d, *p, *next;
+
+ QTAILQ_FOREACH(d, &s->discards, next) {
+ uint64_t new_start = MIN(offset, d->offset);
+ uint64_t new_end = MAX(offset + length, d->offset + d->bytes);
+
+ if (new_end - new_start <= length + d->bytes) {
+ /* There can't be any overlap, areas ending up here have no
+ * references any more and therefore shouldn't get freed another
+ * time. */
+ assert(d->bytes + length == new_end - new_start);
+ d->offset = new_start;
+ d->bytes = new_end - new_start;
+ goto found;
+ }
+ }
+
+ d = g_malloc(sizeof(*d));
+ *d = (Qcow2DiscardRegion) {
+ .bs = bs,
+ .offset = offset,
+ .bytes = length,
+ };
+ QTAILQ_INSERT_TAIL(&s->discards, d, next);
+
+found:
+ /* Merge discard requests if they are adjacent now */
+ QTAILQ_FOREACH_SAFE(p, &s->discards, next, next) {
+ if (p == d
+ || p->offset > d->offset + d->bytes
+ || d->offset > p->offset + p->bytes)
+ {
+ continue;
+ }
+
+ /* Still no overlap possible */
+ assert(p->offset == d->offset + d->bytes
+ || d->offset == p->offset + p->bytes);
+
+ QTAILQ_REMOVE(&s->discards, p, next);
+ d->offset = MIN(d->offset, p->offset);
+ d->bytes += p->bytes;
+ }
+}
+
/* XXX: cache several refcount block clusters ? */
static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
- int64_t offset, int64_t length, int addend)
+ int64_t offset, int64_t length, int addend, enum qcow2_discard_type type)
{
BDRVQcowState *s = bs->opaque;
int64_t start, last, cluster_offset;
@@ -486,10 +556,18 @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
s->free_cluster_index = cluster_index;
}
refcount_block[block_index] = cpu_to_be16(refcount);
+
+ if (refcount == 0 && s->discard_passthrough[type]) {
+ update_refcount_discard(bs, cluster_offset, s->cluster_size);
+ }
}
ret = 0;
fail:
+ if (!s->cache_discards) {
+ qcow2_process_discards(bs, ret);
+ }
+
/* Write last changed block to disk */
if (refcount_block) {
int wret;
@@ -506,7 +584,8 @@ fail:
*/
if (ret < 0) {
int dummy;
- dummy = update_refcount(bs, offset, cluster_offset - offset, -addend);
+ dummy = update_refcount(bs, offset, cluster_offset - offset, -addend,
+ QCOW2_DISCARD_NEVER);
(void)dummy;
}
@@ -522,12 +601,14 @@ fail:
*/
static int update_cluster_refcount(BlockDriverState *bs,
int64_t cluster_index,
- int addend)
+ int addend,
+ enum qcow2_discard_type type)
{
BDRVQcowState *s = bs->opaque;
int ret;
- ret = update_refcount(bs, cluster_index << s->cluster_bits, 1, addend);
+ ret = update_refcount(bs, cluster_index << s->cluster_bits, 1, addend,
+ type);
if (ret < 0) {
return ret;
}
@@ -579,7 +660,7 @@ int64_t qcow2_alloc_clusters(BlockDriverState *bs, int64_t size)
return offset;
}
- ret = update_refcount(bs, offset, size, 1);
+ ret = update_refcount(bs, offset, size, 1, QCOW2_DISCARD_NEVER);
if (ret < 0) {
return ret;
}
@@ -611,7 +692,8 @@ int qcow2_alloc_clusters_at(BlockDriverState *bs, uint64_t offset,
old_free_cluster_index = s->free_cluster_index;
s->free_cluster_index = cluster_index + i;
- ret = update_refcount(bs, offset, i << s->cluster_bits, 1);
+ ret = update_refcount(bs, offset, i << s->cluster_bits, 1,
+ QCOW2_DISCARD_NEVER);
if (ret < 0) {
return ret;
}
@@ -649,7 +731,8 @@ int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size)
if (free_in_cluster == 0)
s->free_byte_offset = 0;
if ((offset & (s->cluster_size - 1)) != 0)
- update_cluster_refcount(bs, offset >> s->cluster_bits, 1);
+ update_cluster_refcount(bs, offset >> s->cluster_bits, 1,
+ QCOW2_DISCARD_NEVER);
} else {
offset = qcow2_alloc_clusters(bs, s->cluster_size);
if (offset < 0) {
@@ -659,7 +742,8 @@ int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size)
if ((cluster_offset + s->cluster_size) == offset) {
/* we are lucky: contiguous data */
offset = s->free_byte_offset;
- update_cluster_refcount(bs, offset >> s->cluster_bits, 1);
+ update_cluster_refcount(bs, offset >> s->cluster_bits, 1,
+ QCOW2_DISCARD_NEVER);
s->free_byte_offset += size;
} else {
s->free_byte_offset = offset;
@@ -676,12 +760,13 @@ int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size)
}
void qcow2_free_clusters(BlockDriverState *bs,
- int64_t offset, int64_t size)
+ int64_t offset, int64_t size,
+ enum qcow2_discard_type type)
{
int ret;
BLKDBG_EVENT(bs->file, BLKDBG_CLUSTER_FREE);
- ret = update_refcount(bs, offset, size, -1);
+ ret = update_refcount(bs, offset, size, -1, type);
if (ret < 0) {
fprintf(stderr, "qcow2_free_clusters failed: %s\n", strerror(-ret));
/* TODO Remember the clusters to free them later and avoid leaking */
@@ -692,8 +777,8 @@ void qcow2_free_clusters(BlockDriverState *bs,
* Free a cluster using its L2 entry (handles clusters of all types, e.g.
* normal cluster, compressed cluster, etc.)
*/
-void qcow2_free_any_clusters(BlockDriverState *bs,
- uint64_t l2_entry, int nb_clusters)
+void qcow2_free_any_clusters(BlockDriverState *bs, uint64_t l2_entry,
+ int nb_clusters, enum qcow2_discard_type type)
{
BDRVQcowState *s = bs->opaque;
@@ -705,12 +790,12 @@ void qcow2_free_any_clusters(BlockDriverState *bs,
s->csize_mask) + 1;
qcow2_free_clusters(bs,
(l2_entry & s->cluster_offset_mask) & ~511,
- nb_csectors * 512);
+ nb_csectors * 512, type);
}
break;
case QCOW2_CLUSTER_NORMAL:
qcow2_free_clusters(bs, l2_entry & L2E_OFFSET_MASK,
- nb_clusters << s->cluster_bits);
+ nb_clusters << s->cluster_bits, type);
break;
case QCOW2_CLUSTER_UNALLOCATED:
case QCOW2_CLUSTER_ZERO:
@@ -741,6 +826,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
l1_table = NULL;
l1_size2 = l1_size * sizeof(uint64_t);
+ s->cache_discards = true;
+
/* WARNING: qcow2_snapshot_goto relies on this function not using the
* l1_table_offset when it is the current s->l1_table_offset! Be careful
* when changing this! */
@@ -785,7 +872,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
int ret;
ret = update_refcount(bs,
(offset & s->cluster_offset_mask) & ~511,
- nb_csectors * 512, addend);
+ nb_csectors * 512, addend,
+ QCOW2_DISCARD_SNAPSHOT);
if (ret < 0) {
goto fail;
}
@@ -795,7 +883,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
} else {
uint64_t cluster_index = (offset & L2E_OFFSET_MASK) >> s->cluster_bits;
if (addend != 0) {
- refcount = update_cluster_refcount(bs, cluster_index, addend);
+ refcount = update_cluster_refcount(bs, cluster_index, addend,
+ QCOW2_DISCARD_SNAPSHOT);
} else {
refcount = get_refcount(bs, cluster_index);
}
@@ -827,7 +916,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
if (addend != 0) {
- refcount = update_cluster_refcount(bs, l2_offset >> s->cluster_bits, addend);
+ refcount = update_cluster_refcount(bs, l2_offset >> s->cluster_bits, addend,
+ QCOW2_DISCARD_SNAPSHOT);
} else {
refcount = get_refcount(bs, l2_offset >> s->cluster_bits);
}
@@ -850,6 +940,9 @@ fail:
qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
}
+ s->cache_discards = false;
+ qcow2_process_discards(bs, ret);
+
/* Update L1 only if it isn't deleted anyway (addend = -1) */
if (ret == 0 && addend >= 0 && l1_modified) {
for (i = 0; i < l1_size; i++) {
@@ -1253,7 +1346,8 @@ int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
if (num_fixed) {
ret = update_refcount(bs, i << s->cluster_bits, 1,
- refcount2 - refcount1);
+ refcount2 - refcount1,
+ QCOW2_DISCARD_ALWAYS);
if (ret >= 0) {
(*num_fixed)++;
continue;
diff --git a/block/qcow2-snapshot.c b/block/qcow2-snapshot.c
index 992a5c865c..0caac9055f 100644
--- a/block/qcow2-snapshot.c
+++ b/block/qcow2-snapshot.c
@@ -262,7 +262,8 @@ static int qcow2_write_snapshots(BlockDriverState *bs)
}
/* free the old snapshot table */
- qcow2_free_clusters(bs, s->snapshots_offset, s->snapshots_size);
+ qcow2_free_clusters(bs, s->snapshots_offset, s->snapshots_size,
+ QCOW2_DISCARD_SNAPSHOT);
s->snapshots_offset = snapshots_offset;
s->snapshots_size = snapshots_size;
return 0;
@@ -569,7 +570,8 @@ int qcow2_snapshot_delete(BlockDriverState *bs, const char *snapshot_id)
if (ret < 0) {
return ret;
}
- qcow2_free_clusters(bs, sn.l1_table_offset, sn.l1_size * sizeof(uint64_t));
+ qcow2_free_clusters(bs, sn.l1_table_offset, sn.l1_size * sizeof(uint64_t),
+ QCOW2_DISCARD_SNAPSHOT);
/* must update the copied flag on the current cluster offsets */
ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 0);
diff --git a/block/qcow2.c b/block/qcow2.c
index 0fa5cb29ae..9383990193 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -295,6 +295,22 @@ static QemuOptsList qcow2_runtime_opts = {
.type = QEMU_OPT_BOOL,
.help = "Postpone refcount updates",
},
+ {
+ .name = QCOW2_OPT_DISCARD_REQUEST,
+ .type = QEMU_OPT_BOOL,
+ .help = "Pass guest discard requests to the layer below",
+ },
+ {
+ .name = QCOW2_OPT_DISCARD_SNAPSHOT,
+ .type = QEMU_OPT_BOOL,
+ .help = "Generate discard requests when snapshot related space "
+ "is freed",
+ },
+ {
+ .name = QCOW2_OPT_DISCARD_OTHER,
+ .type = QEMU_OPT_BOOL,
+ .help = "Generate discard requests when other clusters are freed",
+ },
{ /* end of list */ }
},
};
@@ -470,6 +486,7 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags)
}
QLIST_INIT(&s->cluster_allocs);
+ QTAILQ_INIT(&s->discards);
/* read qcow2 extensions */
if (qcow2_read_extensions(bs, header.header_length, ext_end, NULL)) {
@@ -532,6 +549,16 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags)
s->use_lazy_refcounts = qemu_opt_get_bool(opts, QCOW2_OPT_LAZY_REFCOUNTS,
(s->compatible_features & QCOW2_COMPAT_LAZY_REFCOUNTS));
+ s->discard_passthrough[QCOW2_DISCARD_NEVER] = false;
+ s->discard_passthrough[QCOW2_DISCARD_ALWAYS] = true;
+ s->discard_passthrough[QCOW2_DISCARD_REQUEST] =
+ qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_REQUEST,
+ flags & BDRV_O_UNMAP);
+ s->discard_passthrough[QCOW2_DISCARD_SNAPSHOT] =
+ qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_SNAPSHOT, true);
+ s->discard_passthrough[QCOW2_DISCARD_OTHER] =
+ qemu_opt_get_bool(opts, QCOW2_OPT_DISCARD_OTHER, false);
+
qemu_opts_del(opts);
if (s->use_lazy_refcounts && s->qcow_version < 3) {
@@ -1196,7 +1223,8 @@ static int preallocate(BlockDriverState *bs)
ret = qcow2_alloc_cluster_link_l2(bs, meta);
if (ret < 0) {
- qcow2_free_any_clusters(bs, meta->alloc_offset, meta->nb_clusters);
+ qcow2_free_any_clusters(bs, meta->alloc_offset, meta->nb_clusters,
+ QCOW2_DISCARD_NEVER);
return ret;
}
diff --git a/block/qcow2.h b/block/qcow2.h
index 6959c6a05f..3b2d5cda71 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -60,6 +60,9 @@
#define QCOW2_OPT_LAZY_REFCOUNTS "lazy_refcounts"
+#define QCOW2_OPT_DISCARD_REQUEST "pass_discard_request"
+#define QCOW2_OPT_DISCARD_SNAPSHOT "pass_discard_snapshot"
+#define QCOW2_OPT_DISCARD_OTHER "pass_discard_other"
typedef struct QCowHeader {
uint32_t magic;
@@ -129,12 +132,28 @@ enum {
QCOW2_COMPAT_FEAT_MASK = QCOW2_COMPAT_LAZY_REFCOUNTS,
};
+enum qcow2_discard_type {
+ QCOW2_DISCARD_NEVER = 0,
+ QCOW2_DISCARD_ALWAYS,
+ QCOW2_DISCARD_REQUEST,
+ QCOW2_DISCARD_SNAPSHOT,
+ QCOW2_DISCARD_OTHER,
+ QCOW2_DISCARD_MAX
+};
+
typedef struct Qcow2Feature {
uint8_t type;
uint8_t bit;
char name[46];
} QEMU_PACKED Qcow2Feature;
+typedef struct Qcow2DiscardRegion {
+ BlockDriverState *bs;
+ uint64_t offset;
+ uint64_t bytes;
+ QTAILQ_ENTRY(Qcow2DiscardRegion) next;
+} Qcow2DiscardRegion;
+
typedef struct BDRVQcowState {
int cluster_bits;
int cluster_size;
@@ -178,6 +197,8 @@ typedef struct BDRVQcowState {
int qcow_version;
bool use_lazy_refcounts;
+ bool discard_passthrough[QCOW2_DISCARD_MAX];
+
uint64_t incompatible_features;
uint64_t compatible_features;
uint64_t autoclear_features;
@@ -185,6 +206,8 @@ typedef struct BDRVQcowState {
size_t unknown_header_fields_size;
void* unknown_header_fields;
QLIST_HEAD(, Qcow2UnknownHeaderExtension) unknown_header_ext;
+ QTAILQ_HEAD (, Qcow2DiscardRegion) discards;
+ bool cache_discards;
} BDRVQcowState;
/* XXX: use std qcow open function ? */
@@ -349,9 +372,10 @@ int qcow2_alloc_clusters_at(BlockDriverState *bs, uint64_t offset,
int nb_clusters);
int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size);
void qcow2_free_clusters(BlockDriverState *bs,
- int64_t offset, int64_t size);
-void qcow2_free_any_clusters(BlockDriverState *bs,
- uint64_t cluster_offset, int nb_clusters);
+ int64_t offset, int64_t size,
+ enum qcow2_discard_type type);
+void qcow2_free_any_clusters(BlockDriverState *bs, uint64_t l2_entry,
+ int nb_clusters, enum qcow2_discard_type type);
int qcow2_update_snapshot_refcount(BlockDriverState *bs,
int64_t l1_table_offset, int l1_size, int addend);
@@ -359,6 +383,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
BdrvCheckMode fix);
+void qcow2_process_discards(BlockDriverState *bs, int ret);
+
/* qcow2-cluster.c functions */
int qcow2_grow_l1_table(BlockDriverState *bs, uint64_t min_size,
bool exact_size);
diff --git a/block/vmdk.c b/block/vmdk.c
index 65ae0119c4..975e1d41ff 100644
--- a/block/vmdk.c
+++ b/block/vmdk.c
@@ -561,6 +561,15 @@ static int vmdk_open_vmdk4(BlockDriverState *bs,
header = footer.header;
}
+ if (le32_to_cpu(header.version) >= 3) {
+ char buf[64];
+ snprintf(buf, sizeof(buf), "VMDK version %d",
+ le32_to_cpu(header.version));
+ qerror_report(QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,
+ bs->device_name, "vmdk", buf);
+ return -ENOTSUP;
+ }
+
l1_entry_sectors = le32_to_cpu(header.num_gtes_per_gte)
* le64_to_cpu(header.granularity);
if (l1_entry_sectors == 0) {
diff --git a/blockdev.c b/blockdev.c
index 5975dde5d0..459a87e8d0 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1737,120 +1737,10 @@ QemuOptsList qemu_drive_opts = {
.name = "drive",
.head = QTAILQ_HEAD_INITIALIZER(qemu_drive_opts.head),
.desc = {
- {
- .name = "bus",
- .type = QEMU_OPT_NUMBER,
- .help = "bus number",
- },{
- .name = "unit",
- .type = QEMU_OPT_NUMBER,
- .help = "unit number (i.e. lun for scsi)",
- },{
- .name = "if",
- .type = QEMU_OPT_STRING,
- .help = "interface (ide, scsi, sd, mtd, floppy, pflash, virtio)",
- },{
- .name = "index",
- .type = QEMU_OPT_NUMBER,
- .help = "index number",
- },{
- .name = "cyls",
- .type = QEMU_OPT_NUMBER,
- .help = "number of cylinders (ide disk geometry)",
- },{
- .name = "heads",
- .type = QEMU_OPT_NUMBER,
- .help = "number of heads (ide disk geometry)",
- },{
- .name = "secs",
- .type = QEMU_OPT_NUMBER,
- .help = "number of sectors (ide disk geometry)",
- },{
- .name = "trans",
- .type = QEMU_OPT_STRING,
- .help = "chs translation (auto, lba. none)",
- },{
- .name = "media",
- .type = QEMU_OPT_STRING,
- .help = "media type (disk, cdrom)",
- },{
- .name = "snapshot",
- .type = QEMU_OPT_BOOL,
- .help = "enable/disable snapshot mode",
- },{
- .name = "file",
- .type = QEMU_OPT_STRING,
- .help = "disk image",
- },{
- .name = "discard",
- .type = QEMU_OPT_STRING,
- .help = "discard operation (ignore/off, unmap/on)",
- },{
- .name = "cache",
- .type = QEMU_OPT_STRING,
- .help = "host cache usage (none, writeback, writethrough, "
- "directsync, unsafe)",
- },{
- .name = "aio",
- .type = QEMU_OPT_STRING,
- .help = "host AIO implementation (threads, native)",
- },{
- .name = "format",
- .type = QEMU_OPT_STRING,
- .help = "disk format (raw, qcow2, ...)",
- },{
- .name = "serial",
- .type = QEMU_OPT_STRING,
- .help = "disk serial number",
- },{
- .name = "rerror",
- .type = QEMU_OPT_STRING,
- .help = "read error action",
- },{
- .name = "werror",
- .type = QEMU_OPT_STRING,
- .help = "write error action",
- },{
- .name = "addr",
- .type = QEMU_OPT_STRING,
- .help = "pci address (virtio only)",
- },{
- .name = "readonly",
- .type = QEMU_OPT_BOOL,
- .help = "open drive file as read-only",
- },{
- .name = "iops",
- .type = QEMU_OPT_NUMBER,
- .help = "limit total I/O operations per second",
- },{
- .name = "iops_rd",
- .type = QEMU_OPT_NUMBER,
- .help = "limit read operations per second",
- },{
- .name = "iops_wr",
- .type = QEMU_OPT_NUMBER,
- .help = "limit write operations per second",
- },{
- .name = "bps",
- .type = QEMU_OPT_NUMBER,
- .help = "limit total bytes per second",
- },{
- .name = "bps_rd",
- .type = QEMU_OPT_NUMBER,
- .help = "limit read bytes per second",
- },{
- .name = "bps_wr",
- .type = QEMU_OPT_NUMBER,
- .help = "limit write bytes per second",
- },{
- .name = "copy-on-read",
- .type = QEMU_OPT_BOOL,
- .help = "copy read data from backing file into image file",
- },{
- .name = "boot",
- .type = QEMU_OPT_BOOL,
- .help = "(deprecated, ignored)",
- },
+ /*
+ * no elements => accept any params
+ * validation will happen later
+ */
{ /* end of list */ }
},
};
diff --git a/hw/ide/core.c b/hw/ide/core.c
index 9926d9202b..03d1cfa7d2 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -1004,633 +1004,729 @@ void ide_ioport_write(void *opaque, uint32_t addr, uint32_t val)
}
}
-#define HD_OK (1u << IDE_HD)
-#define CD_OK (1u << IDE_CD)
-#define CFA_OK (1u << IDE_CFATA)
-#define HD_CFA_OK (HD_OK | CFA_OK)
-#define ALL_OK (HD_OK | CD_OK | CFA_OK)
+static bool cmd_nop(IDEState *s, uint8_t cmd)
+{
+ return true;
+}
-/* See ACS-2 T13/2015-D Table B.2 Command codes */
-static const uint8_t ide_cmd_table[0x100] = {
- /* NOP not implemented, mandatory for CD */
- [CFA_REQ_EXT_ERROR_CODE] = CFA_OK,
- [WIN_DSM] = ALL_OK,
- [WIN_DEVICE_RESET] = CD_OK,
- [WIN_RECAL] = HD_CFA_OK,
- [WIN_READ] = ALL_OK,
- [WIN_READ_ONCE] = ALL_OK,
- [WIN_READ_EXT] = HD_CFA_OK,
- [WIN_READDMA_EXT] = HD_CFA_OK,
- [WIN_READ_NATIVE_MAX_EXT] = HD_CFA_OK,
- [WIN_MULTREAD_EXT] = HD_CFA_OK,
- [WIN_WRITE] = HD_CFA_OK,
- [WIN_WRITE_ONCE] = HD_CFA_OK,
- [WIN_WRITE_EXT] = HD_CFA_OK,
- [WIN_WRITEDMA_EXT] = HD_CFA_OK,
- [CFA_WRITE_SECT_WO_ERASE] = CFA_OK,
- [WIN_MULTWRITE_EXT] = HD_CFA_OK,
- [WIN_WRITE_VERIFY] = HD_CFA_OK,
- [WIN_VERIFY] = HD_CFA_OK,
- [WIN_VERIFY_ONCE] = HD_CFA_OK,
- [WIN_VERIFY_EXT] = HD_CFA_OK,
- [WIN_SEEK] = HD_CFA_OK,
- [CFA_TRANSLATE_SECTOR] = CFA_OK,
- [WIN_DIAGNOSE] = ALL_OK,
- [WIN_SPECIFY] = HD_CFA_OK,
- [WIN_STANDBYNOW2] = ALL_OK,
- [WIN_IDLEIMMEDIATE2] = ALL_OK,
- [WIN_STANDBY2] = ALL_OK,
- [WIN_SETIDLE2] = ALL_OK,
- [WIN_CHECKPOWERMODE2] = ALL_OK,
- [WIN_SLEEPNOW2] = ALL_OK,
- [WIN_PACKETCMD] = CD_OK,
- [WIN_PIDENTIFY] = CD_OK,
- [WIN_SMART] = HD_CFA_OK,
- [CFA_ACCESS_METADATA_STORAGE] = CFA_OK,
- [CFA_ERASE_SECTORS] = CFA_OK,
- [WIN_MULTREAD] = HD_CFA_OK,
- [WIN_MULTWRITE] = HD_CFA_OK,
- [WIN_SETMULT] = HD_CFA_OK,
- [WIN_READDMA] = HD_CFA_OK,
- [WIN_READDMA_ONCE] = HD_CFA_OK,
- [WIN_WRITEDMA] = HD_CFA_OK,
- [WIN_WRITEDMA_ONCE] = HD_CFA_OK,
- [CFA_WRITE_MULTI_WO_ERASE] = CFA_OK,
- [WIN_STANDBYNOW1] = ALL_OK,
- [WIN_IDLEIMMEDIATE] = ALL_OK,
- [WIN_STANDBY] = ALL_OK,
- [WIN_SETIDLE1] = ALL_OK,
- [WIN_CHECKPOWERMODE1] = ALL_OK,
- [WIN_SLEEPNOW1] = ALL_OK,
- [WIN_FLUSH_CACHE] = ALL_OK,
- [WIN_FLUSH_CACHE_EXT] = HD_CFA_OK,
- [WIN_IDENTIFY] = ALL_OK,
- [WIN_SETFEATURES] = ALL_OK,
- [IBM_SENSE_CONDITION] = CFA_OK,
- [CFA_WEAR_LEVEL] = HD_CFA_OK,
- [WIN_READ_NATIVE_MAX] = ALL_OK,
-};
+static bool cmd_data_set_management(IDEState *s, uint8_t cmd)
+{
+ switch (s->feature) {
+ case DSM_TRIM:
+ if (s->bs) {
+ ide_sector_start_dma(s, IDE_DMA_TRIM);
+ return false;
+ }
+ break;
+ }
-static bool ide_cmd_permitted(IDEState *s, uint32_t cmd)
+ ide_abort_command(s);
+ return true;
+}
+
+static bool cmd_identify(IDEState *s, uint8_t cmd)
{
- return cmd < ARRAY_SIZE(ide_cmd_table)
- && (ide_cmd_table[cmd] & (1u << s->drive_kind));
+ if (s->bs && s->drive_kind != IDE_CD) {
+ if (s->drive_kind != IDE_CFATA) {
+ ide_identify(s);
+ } else {
+ ide_cfata_identify(s);
+ }
+ s->status = READY_STAT | SEEK_STAT;
+ ide_transfer_start(s, s->io_buffer, 512, ide_transfer_stop);
+ ide_set_irq(s->bus);
+ return false;
+ } else {
+ if (s->drive_kind == IDE_CD) {
+ ide_set_signature(s);
+ }
+ ide_abort_command(s);
+ }
+
+ return true;
}
-void ide_exec_cmd(IDEBus *bus, uint32_t val)
+static bool cmd_verify(IDEState *s, uint8_t cmd)
{
- uint16_t *identify_data;
- IDEState *s;
+ bool lba48 = (cmd == WIN_VERIFY_EXT);
+
+ /* do sector number check ? */
+ ide_cmd_lba48_transform(s, lba48);
+
+ return true;
+}
+
+static bool cmd_set_multiple_mode(IDEState *s, uint8_t cmd)
+{
+ if (s->drive_kind == IDE_CFATA && s->nsector == 0) {
+ /* Disable Read and Write Multiple */
+ s->mult_sectors = 0;
+ } else if ((s->nsector & 0xff) != 0 &&
+ ((s->nsector & 0xff) > MAX_MULT_SECTORS ||
+ (s->nsector & (s->nsector - 1)) != 0)) {
+ ide_abort_command(s);
+ } else {
+ s->mult_sectors = s->nsector & 0xff;
+ }
+
+ return true;
+}
+
+static bool cmd_read_multiple(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_MULTREAD_EXT);
+
+ if (!s->bs || !s->mult_sectors) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_cmd_lba48_transform(s, lba48);
+ s->req_nb_sectors = s->mult_sectors;
+ ide_sector_read(s);
+ return false;
+}
+
+static bool cmd_write_multiple(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_MULTWRITE_EXT);
int n;
- int lba48 = 0;
-#if defined(DEBUG_IDE)
- printf("ide: CMD=%02x\n", val);
-#endif
- s = idebus_active_if(bus);
- /* ignore commands to non existent slave */
- if (s != bus->ifs && !s->bs)
- return;
+ if (!s->bs || !s->mult_sectors) {
+ ide_abort_command(s);
+ return true;
+ }
- /* Only DEVICE RESET is allowed while BSY or/and DRQ are set */
- if ((s->status & (BUSY_STAT|DRQ_STAT)) && val != WIN_DEVICE_RESET)
- return;
+ ide_cmd_lba48_transform(s, lba48);
- if (!ide_cmd_permitted(s, val)) {
- goto abort_cmd;
+ s->req_nb_sectors = s->mult_sectors;
+ n = MIN(s->nsector, s->req_nb_sectors);
+
+ s->status = SEEK_STAT | READY_STAT;
+ ide_transfer_start(s, s->io_buffer, 512 * n, ide_sector_write);
+
+ s->media_changed = 1;
+
+ return false;
+}
+
+static bool cmd_read_pio(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_READ_EXT);
+
+ if (s->drive_kind == IDE_CD) {
+ ide_set_signature(s); /* odd, but ATA4 8.27.5.2 requires it */
+ ide_abort_command(s);
+ return true;
}
- switch(val) {
- case WIN_DSM:
- switch (s->feature) {
- case DSM_TRIM:
- if (!s->bs) {
+ if (!s->bs) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_cmd_lba48_transform(s, lba48);
+ s->req_nb_sectors = 1;
+ ide_sector_read(s);
+
+ return false;
+}
+
+static bool cmd_write_pio(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_WRITE_EXT);
+
+ if (!s->bs) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_cmd_lba48_transform(s, lba48);
+
+ s->req_nb_sectors = 1;
+ s->status = SEEK_STAT | READY_STAT;
+ ide_transfer_start(s, s->io_buffer, 512, ide_sector_write);
+
+ s->media_changed = 1;
+
+ return false;
+}
+
+static bool cmd_read_dma(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_READDMA_EXT);
+
+ if (!s->bs) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_cmd_lba48_transform(s, lba48);
+ ide_sector_start_dma(s, IDE_DMA_READ);
+
+ return false;
+}
+
+static bool cmd_write_dma(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_WRITEDMA_EXT);
+
+ if (!s->bs) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_cmd_lba48_transform(s, lba48);
+ ide_sector_start_dma(s, IDE_DMA_WRITE);
+
+ s->media_changed = 1;
+
+ return false;
+}
+
+static bool cmd_flush_cache(IDEState *s, uint8_t cmd)
+{
+ ide_flush_cache(s);
+ return false;
+}
+
+static bool cmd_seek(IDEState *s, uint8_t cmd)
+{
+ /* XXX: Check that seek is within bounds */
+ return true;
+}
+
+static bool cmd_read_native_max(IDEState *s, uint8_t cmd)
+{
+ bool lba48 = (cmd == WIN_READ_NATIVE_MAX_EXT);
+
+ /* Refuse if no sectors are addressable (e.g. medium not inserted) */
+ if (s->nb_sectors == 0) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ ide_cmd_lba48_transform(s, lba48);
+ ide_set_sector(s, s->nb_sectors - 1);
+
+ return true;
+}
+
+static bool cmd_check_power_mode(IDEState *s, uint8_t cmd)
+{
+ s->nsector = 0xff; /* device active or idle */
+ return true;
+}
+
+static bool cmd_set_features(IDEState *s, uint8_t cmd)
+{
+ uint16_t *identify_data;
+
+ if (!s->bs) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ /* XXX: valid for CDROM ? */
+ switch (s->feature) {
+ case 0x02: /* write cache enable */
+ bdrv_set_enable_write_cache(s->bs, true);
+ identify_data = (uint16_t *)s->identify_data;
+ put_le16(identify_data + 85, (1 << 14) | (1 << 5) | 1);
+ return true;
+ case 0x82: /* write cache disable */
+ bdrv_set_enable_write_cache(s->bs, false);
+ identify_data = (uint16_t *)s->identify_data;
+ put_le16(identify_data + 85, (1 << 14) | 1);
+ ide_flush_cache(s);
+ return false;
+ case 0xcc: /* reverting to power-on defaults enable */
+ case 0x66: /* reverting to power-on defaults disable */
+ case 0xaa: /* read look-ahead enable */
+ case 0x55: /* read look-ahead disable */
+ case 0x05: /* set advanced power management mode */
+ case 0x85: /* disable advanced power management mode */
+ case 0x69: /* NOP */
+ case 0x67: /* NOP */
+ case 0x96: /* NOP */
+ case 0x9a: /* NOP */
+ case 0x42: /* enable Automatic Acoustic Mode */
+ case 0xc2: /* disable Automatic Acoustic Mode */
+ return true;
+ case 0x03: /* set transfer mode */
+ {
+ uint8_t val = s->nsector & 0x07;
+ identify_data = (uint16_t *)s->identify_data;
+
+ switch (s->nsector >> 3) {
+ case 0x00: /* pio default */
+ case 0x01: /* pio mode */
+ put_le16(identify_data + 62, 0x07);
+ put_le16(identify_data + 63, 0x07);
+ put_le16(identify_data + 88, 0x3f);
+ break;
+ case 0x02: /* sigle word dma mode*/
+ put_le16(identify_data + 62, 0x07 | (1 << (val + 8)));
+ put_le16(identify_data + 63, 0x07);
+ put_le16(identify_data + 88, 0x3f);
+ break;
+ case 0x04: /* mdma mode */
+ put_le16(identify_data + 62, 0x07);
+ put_le16(identify_data + 63, 0x07 | (1 << (val + 8)));
+ put_le16(identify_data + 88, 0x3f);
+ break;
+ case 0x08: /* udma mode */
+ put_le16(identify_data + 62, 0x07);
+ put_le16(identify_data + 63, 0x07);
+ put_le16(identify_data + 88, 0x3f | (1 << (val + 8)));
+ break;
+ default:
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)
- ide_identify(s);
- else
- ide_cfata_identify(s);
- s->status = READY_STAT | SEEK_STAT;
- ide_transfer_start(s, s->io_buffer, 512, ide_transfer_stop);
- } else {
- if (s->drive_kind == IDE_CD) {
- ide_set_signature(s);
- }
- ide_abort_command(s);
- }
- ide_set_irq(s->bus);
- break;
- case WIN_SPECIFY:
- case WIN_RECAL:
- s->error = 0;
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
- case WIN_SETMULT:
- if (s->drive_kind == IDE_CFATA && s->nsector == 0) {
- /* Disable Read and Write Multiple */
- s->mult_sectors = 0;
- s->status = READY_STAT | SEEK_STAT;
- } else if ((s->nsector & 0xff) != 0 &&
- ((s->nsector & 0xff) > MAX_MULT_SECTORS ||
- (s->nsector & (s->nsector - 1)) != 0)) {
- ide_abort_command(s);
- } else {
- s->mult_sectors = s->nsector & 0xff;
- s->status = READY_STAT | SEEK_STAT;
+ return true;
}
- ide_set_irq(s->bus);
- break;
+ }
+
+abort_cmd:
+ ide_abort_command(s);
+ return true;
+}
+
+
+/*** ATAPI commands ***/
+
+static bool cmd_identify_packet(IDEState *s, uint8_t cmd)
+{
+ ide_atapi_identify(s);
+ s->status = READY_STAT | SEEK_STAT;
+ ide_transfer_start(s, s->io_buffer, 512, ide_transfer_stop);
+ ide_set_irq(s->bus);
+ return false;
+}
- case WIN_VERIFY_EXT:
- lba48 = 1;
- /* fall through */
- case WIN_VERIFY:
- case WIN_VERIFY_ONCE:
- /* do sector number check ? */
- ide_cmd_lba48_transform(s, lba48);
+static bool cmd_exec_dev_diagnostic(IDEState *s, uint8_t cmd)
+{
+ ide_set_signature(s);
+
+ if (s->drive_kind == IDE_CD) {
+ s->status = 0; /* ATAPI spec (v6) section 9.10 defines packet
+ * devices to return a clear status register
+ * with READY_STAT *not* set. */
+ } else {
s->status = READY_STAT | SEEK_STAT;
+ /* The bits of the error register are not as usual for this command!
+ * They are part of the regular output (this is why ERR_STAT isn't set)
+ * Device 0 passed, Device 1 passed or not present. */
+ s->error = 0x01;
ide_set_irq(s->bus);
- break;
+ }
- case WIN_READ_EXT:
- lba48 = 1;
- /* fall through */
- case WIN_READ:
- case WIN_READ_ONCE:
- if (s->drive_kind == IDE_CD) {
- ide_set_signature(s); /* odd, but ATA4 8.27.5.2 requires it */
- goto abort_cmd;
- }
- if (!s->bs) {
- goto abort_cmd;
- }
- ide_cmd_lba48_transform(s, lba48);
- s->req_nb_sectors = 1;
- ide_sector_read(s);
- break;
+ return false;
+}
- case WIN_WRITE_EXT:
- lba48 = 1;
- /* fall through */
- case WIN_WRITE:
- case WIN_WRITE_ONCE:
- case CFA_WRITE_SECT_WO_ERASE:
- case WIN_WRITE_VERIFY:
- if (!s->bs) {
- goto abort_cmd;
- }
- ide_cmd_lba48_transform(s, lba48);
- s->error = 0;
- s->status = SEEK_STAT | READY_STAT;
- s->req_nb_sectors = 1;
- ide_transfer_start(s, s->io_buffer, 512, ide_sector_write);
+static bool cmd_device_reset(IDEState *s, uint8_t cmd)
+{
+ ide_set_signature(s);
+ s->status = 0x00; /* NOTE: READY is _not_ set */
+ s->error = 0x01;
+
+ return false;
+}
+
+static bool cmd_packet(IDEState *s, uint8_t cmd)
+{
+ /* overlapping commands not supported */
+ if (s->feature & 0x02) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ s->status = READY_STAT | SEEK_STAT;
+ s->atapi_dma = s->feature & 1;
+ s->nsector = 1;
+ ide_transfer_start(s, s->io_buffer, ATAPI_PACKET_SIZE,
+ ide_atapi_cmd);
+ return false;
+}
+
+
+/*** CF-ATA commands ***/
+
+static bool cmd_cfa_req_ext_error_code(IDEState *s, uint8_t cmd)
+{
+ s->error = 0x09; /* miscellaneous error */
+ s->status = READY_STAT | SEEK_STAT;
+ ide_set_irq(s->bus);
+
+ return false;
+}
+
+static bool cmd_cfa_erase_sectors(IDEState *s, uint8_t cmd)
+{
+ /* WIN_SECURITY_FREEZE_LOCK has the same ID as CFA_WEAR_LEVEL and is
+ * required for Windows 8 to work with AHCI */
+
+ if (cmd == CFA_WEAR_LEVEL) {
+ s->nsector = 0;
+ }
+
+ if (cmd == CFA_ERASE_SECTORS) {
s->media_changed = 1;
+ }
+
+ return true;
+}
+
+static bool cmd_cfa_translate_sector(IDEState *s, uint8_t cmd)
+{
+ s->status = READY_STAT | SEEK_STAT;
+
+ memset(s->io_buffer, 0, 0x200);
+ s->io_buffer[0x00] = s->hcyl; /* Cyl MSB */
+ s->io_buffer[0x01] = s->lcyl; /* Cyl LSB */
+ s->io_buffer[0x02] = s->select; /* Head */
+ s->io_buffer[0x03] = s->sector; /* Sector */
+ s->io_buffer[0x04] = ide_get_sector(s) >> 16; /* LBA MSB */
+ s->io_buffer[0x05] = ide_get_sector(s) >> 8; /* LBA */
+ s->io_buffer[0x06] = ide_get_sector(s) >> 0; /* LBA LSB */
+ s->io_buffer[0x13] = 0x00; /* Erase flag */
+ s->io_buffer[0x18] = 0x00; /* Hot count */
+ s->io_buffer[0x19] = 0x00; /* Hot count */
+ s->io_buffer[0x1a] = 0x01; /* Hot count */
+
+ ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
+ ide_set_irq(s->bus);
+
+ return false;
+}
+
+static bool cmd_cfa_access_metadata_storage(IDEState *s, uint8_t cmd)
+{
+ switch (s->feature) {
+ case 0x02: /* Inquiry Metadata Storage */
+ ide_cfata_metadata_inquiry(s);
break;
+ case 0x03: /* Read Metadata Storage */
+ ide_cfata_metadata_read(s);
+ break;
+ case 0x04: /* Write Metadata Storage */
+ ide_cfata_metadata_write(s);
+ break;
+ default:
+ ide_abort_command(s);
+ return true;
+ }
- case WIN_MULTREAD_EXT:
- lba48 = 1;
- /* fall through */
- case WIN_MULTREAD:
- if (!s->bs) {
- goto abort_cmd;
- }
- if (!s->mult_sectors) {
- goto abort_cmd;
- }
- ide_cmd_lba48_transform(s, lba48);
- s->req_nb_sectors = s->mult_sectors;
- ide_sector_read(s);
+ ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
+ s->status = 0x00; /* NOTE: READY is _not_ set */
+ ide_set_irq(s->bus);
+
+ return false;
+}
+
+static bool cmd_ibm_sense_condition(IDEState *s, uint8_t cmd)
+{
+ switch (s->feature) {
+ case 0x01: /* sense temperature in device */
+ s->nsector = 0x50; /* +20 C */
break;
+ default:
+ ide_abort_command(s);
+ return true;
+ }
- case WIN_MULTWRITE_EXT:
- lba48 = 1;
- /* fall through */
- case WIN_MULTWRITE:
- case CFA_WRITE_MULTI_WO_ERASE:
- if (!s->bs) {
- goto abort_cmd;
- }
- if (!s->mult_sectors) {
+ return true;
+}
+
+
+/*** SMART commands ***/
+
+static bool cmd_smart(IDEState *s, uint8_t cmd)
+{
+ int n;
+
+ if (s->hcyl != 0xc2 || s->lcyl != 0x4f) {
+ goto abort_cmd;
+ }
+
+ if (!s->smart_enabled && s->feature != SMART_ENABLE) {
+ goto abort_cmd;
+ }
+
+ switch (s->feature) {
+ case SMART_DISABLE:
+ s->smart_enabled = 0;
+ return true;
+
+ case SMART_ENABLE:
+ s->smart_enabled = 1;
+ return true;
+
+ case SMART_ATTR_AUTOSAVE:
+ switch (s->sector) {
+ case 0x00:
+ s->smart_autosave = 0;
+ break;
+ case 0xf1:
+ s->smart_autosave = 1;
+ break;
+ default:
goto abort_cmd;
}
- ide_cmd_lba48_transform(s, lba48);
- s->error = 0;
- s->status = SEEK_STAT | READY_STAT;
- s->req_nb_sectors = s->mult_sectors;
- n = s->nsector;
- if (n > s->req_nb_sectors)
- n = s->req_nb_sectors;
- ide_transfer_start(s, s->io_buffer, 512 * n, ide_sector_write);
- s->media_changed = 1;
- break;
+ return true;
- case WIN_READDMA_EXT:
- lba48 = 1;
- /* fall through */
- case WIN_READDMA:
- case WIN_READDMA_ONCE:
- if (!s->bs) {
- goto abort_cmd;
+ case SMART_STATUS:
+ if (!s->smart_errors) {
+ s->hcyl = 0xc2;
+ s->lcyl = 0x4f;
+ } else {
+ s->hcyl = 0x2c;
+ s->lcyl = 0xf4;
}
- ide_cmd_lba48_transform(s, lba48);
- ide_sector_start_dma(s, IDE_DMA_READ);
- break;
+ return true;
- case WIN_WRITEDMA_EXT:
- lba48 = 1;
- /* fall through */
- case WIN_WRITEDMA:
- case WIN_WRITEDMA_ONCE:
- if (!s->bs) {
- goto abort_cmd;
+ case SMART_READ_THRESH:
+ memset(s->io_buffer, 0, 0x200);
+ s->io_buffer[0] = 0x01; /* smart struct version */
+
+ for (n = 0; n < ARRAY_SIZE(smart_attributes); n++) {
+ s->io_buffer[2 + 0 + (n * 12)] = smart_attributes[n][0];
+ s->io_buffer[2 + 1 + (n * 12)] = smart_attributes[n][11];
}
- ide_cmd_lba48_transform(s, lba48);
- ide_sector_start_dma(s, IDE_DMA_WRITE);
- s->media_changed = 1;
- break;
- case WIN_READ_NATIVE_MAX_EXT:
- lba48 = 1;
- /* fall through */
- case WIN_READ_NATIVE_MAX:
- /* Refuse if no sectors are addressable (e.g. medium not inserted) */
- if (s->nb_sectors == 0) {
- goto abort_cmd;
+ /* checksum */
+ for (n = 0; n < 511; n++) {
+ s->io_buffer[511] += s->io_buffer[n];
}
- ide_cmd_lba48_transform(s, lba48);
- ide_set_sector(s, s->nb_sectors - 1);
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
+ s->io_buffer[511] = 0x100 - s->io_buffer[511];
- case WIN_CHECKPOWERMODE1:
- case WIN_CHECKPOWERMODE2:
- s->error = 0;
- s->nsector = 0xff; /* device active or idle */
s->status = READY_STAT | SEEK_STAT;
+ ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
ide_set_irq(s->bus);
- break;
- case WIN_SETFEATURES:
- if (!s->bs)
- goto abort_cmd;
- /* XXX: valid for CDROM ? */
- switch(s->feature) {
- case 0x02: /* write cache enable */
- bdrv_set_enable_write_cache(s->bs, true);
- identify_data = (uint16_t *)s->identify_data;
- put_le16(identify_data + 85, (1 << 14) | (1 << 5) | 1);
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
- case 0x82: /* write cache disable */
- bdrv_set_enable_write_cache(s->bs, false);
- identify_data = (uint16_t *)s->identify_data;
- put_le16(identify_data + 85, (1 << 14) | 1);
- ide_flush_cache(s);
- break;
- case 0xcc: /* reverting to power-on defaults enable */
- case 0x66: /* reverting to power-on defaults disable */
- case 0xaa: /* read look-ahead enable */
- case 0x55: /* read look-ahead disable */
- case 0x05: /* set advanced power management mode */
- case 0x85: /* disable advanced power management mode */
- case 0x69: /* NOP */
- case 0x67: /* NOP */
- case 0x96: /* NOP */
- case 0x9a: /* NOP */
- case 0x42: /* enable Automatic Acoustic Mode */
- case 0xc2: /* disable Automatic Acoustic Mode */
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
- case 0x03: { /* set transfer mode */
- uint8_t val = s->nsector & 0x07;
- identify_data = (uint16_t *)s->identify_data;
-
- switch (s->nsector >> 3) {
- case 0x00: /* pio default */
- case 0x01: /* pio mode */
- put_le16(identify_data + 62,0x07);
- put_le16(identify_data + 63,0x07);
- put_le16(identify_data + 88,0x3f);
- break;
- case 0x02: /* sigle word dma mode*/
- put_le16(identify_data + 62,0x07 | (1 << (val + 8)));
- put_le16(identify_data + 63,0x07);
- put_le16(identify_data + 88,0x3f);
- break;
- case 0x04: /* mdma mode */
- put_le16(identify_data + 62,0x07);
- put_le16(identify_data + 63,0x07 | (1 << (val + 8)));
- put_le16(identify_data + 88,0x3f);
- break;
- case 0x08: /* udma mode */
- put_le16(identify_data + 62,0x07);
- put_le16(identify_data + 63,0x07);
- put_le16(identify_data + 88,0x3f | (1 << (val + 8)));
- break;
- default:
- goto abort_cmd;
- }
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
- }
- default:
- goto abort_cmd;
+ return false;
+
+ case SMART_READ_DATA:
+ memset(s->io_buffer, 0, 0x200);
+ s->io_buffer[0] = 0x01; /* smart struct version */
+
+ for (n = 0; n < ARRAY_SIZE(smart_attributes); n++) {
+ int i;
+ for (i = 0; i < 11; i++) {
+ s->io_buffer[2 + i + (n * 12)] = smart_attributes[n][i];
+ }
}
- break;
- case WIN_FLUSH_CACHE:
- case WIN_FLUSH_CACHE_EXT:
- ide_flush_cache(s);
- break;
- case WIN_STANDBY:
- case WIN_STANDBY2:
- case WIN_STANDBYNOW1:
- case WIN_STANDBYNOW2:
- case WIN_IDLEIMMEDIATE:
- case WIN_IDLEIMMEDIATE2:
- case WIN_SETIDLE1:
- case WIN_SETIDLE2:
- case WIN_SLEEPNOW1:
- case WIN_SLEEPNOW2:
- s->status = READY_STAT;
- ide_set_irq(s->bus);
- break;
- case WIN_SEEK:
- /* XXX: Check that seek is within bounds */
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
- /* ATAPI commands */
- case WIN_PIDENTIFY:
- ide_atapi_identify(s);
- s->status = READY_STAT | SEEK_STAT;
- ide_transfer_start(s, s->io_buffer, 512, ide_transfer_stop);
- ide_set_irq(s->bus);
- break;
- case WIN_DIAGNOSE:
- ide_set_signature(s);
- if (s->drive_kind == IDE_CD)
- s->status = 0; /* ATAPI spec (v6) section 9.10 defines packet
- * devices to return a clear status register
- * with READY_STAT *not* set. */
- else
- s->status = READY_STAT | SEEK_STAT;
- s->error = 0x01; /* Device 0 passed, Device 1 passed or not
- * present.
- */
- ide_set_irq(s->bus);
- break;
- case WIN_DEVICE_RESET:
- ide_set_signature(s);
- s->status = 0x00; /* NOTE: READY is _not_ set */
- s->error = 0x01;
- break;
- case WIN_PACKETCMD:
- /* overlapping commands not supported */
- if (s->feature & 0x02)
- goto abort_cmd;
- s->status = READY_STAT | SEEK_STAT;
- s->atapi_dma = s->feature & 1;
- s->nsector = 1;
- ide_transfer_start(s, s->io_buffer, ATAPI_PACKET_SIZE,
- ide_atapi_cmd);
- break;
- /* CF-ATA commands */
- case CFA_REQ_EXT_ERROR_CODE:
- s->error = 0x09; /* miscellaneous error */
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
- case CFA_ERASE_SECTORS:
- case CFA_WEAR_LEVEL:
-#if 0
- /* This one has the same ID as CFA_WEAR_LEVEL and is required for
- Windows 8 to work with AHCI */
- case WIN_SECURITY_FREEZE_LOCK:
-#endif
- if (val == CFA_WEAR_LEVEL)
- s->nsector = 0;
- if (val == CFA_ERASE_SECTORS)
- s->media_changed = 1;
- s->error = 0x00;
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
- case CFA_TRANSLATE_SECTOR:
- s->error = 0x00;
+
+ s->io_buffer[362] = 0x02 | (s->smart_autosave ? 0x80 : 0x00);
+ if (s->smart_selftest_count == 0) {
+ s->io_buffer[363] = 0;
+ } else {
+ s->io_buffer[363] =
+ s->smart_selftest_data[3 +
+ (s->smart_selftest_count - 1) *
+ 24];
+ }
+ s->io_buffer[364] = 0x20;
+ s->io_buffer[365] = 0x01;
+ /* offline data collection capacity: execute + self-test*/
+ s->io_buffer[367] = (1 << 4 | 1 << 3 | 1);
+ s->io_buffer[368] = 0x03; /* smart capability (1) */
+ s->io_buffer[369] = 0x00; /* smart capability (2) */
+ s->io_buffer[370] = 0x01; /* error logging supported */
+ s->io_buffer[372] = 0x02; /* minutes for poll short test */
+ s->io_buffer[373] = 0x36; /* minutes for poll ext test */
+ s->io_buffer[374] = 0x01; /* minutes for poll conveyance */
+
+ for (n = 0; n < 511; n++) {
+ s->io_buffer[511] += s->io_buffer[n];
+ }
+ s->io_buffer[511] = 0x100 - s->io_buffer[511];
+
s->status = READY_STAT | SEEK_STAT;
- memset(s->io_buffer, 0, 0x200);
- s->io_buffer[0x00] = s->hcyl; /* Cyl MSB */
- s->io_buffer[0x01] = s->lcyl; /* Cyl LSB */
- s->io_buffer[0x02] = s->select; /* Head */
- s->io_buffer[0x03] = s->sector; /* Sector */
- s->io_buffer[0x04] = ide_get_sector(s) >> 16; /* LBA MSB */
- s->io_buffer[0x05] = ide_get_sector(s) >> 8; /* LBA */
- s->io_buffer[0x06] = ide_get_sector(s) >> 0; /* LBA LSB */
- s->io_buffer[0x13] = 0x00; /* Erase flag */
- s->io_buffer[0x18] = 0x00; /* Hot count */
- s->io_buffer[0x19] = 0x00; /* Hot count */
- s->io_buffer[0x1a] = 0x01; /* Hot count */
ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
ide_set_irq(s->bus);
- break;
- case CFA_ACCESS_METADATA_STORAGE:
- switch (s->feature) {
- case 0x02: /* Inquiry Metadata Storage */
- ide_cfata_metadata_inquiry(s);
- break;
- case 0x03: /* Read Metadata Storage */
- ide_cfata_metadata_read(s);
+ return false;
+
+ case SMART_READ_LOG:
+ switch (s->sector) {
+ case 0x01: /* summary smart error log */
+ memset(s->io_buffer, 0, 0x200);
+ s->io_buffer[0] = 0x01;
+ s->io_buffer[1] = 0x00; /* no error entries */
+ s->io_buffer[452] = s->smart_errors & 0xff;
+ s->io_buffer[453] = (s->smart_errors & 0xff00) >> 8;
+
+ for (n = 0; n < 511; n++) {
+ s->io_buffer[511] += s->io_buffer[n];
+ }
+ s->io_buffer[511] = 0x100 - s->io_buffer[511];
break;
- case 0x04: /* Write Metadata Storage */
- ide_cfata_metadata_write(s);
+ case 0x06: /* smart self test log */
+ memset(s->io_buffer, 0, 0x200);
+ s->io_buffer[0] = 0x01;
+ if (s->smart_selftest_count == 0) {
+ s->io_buffer[508] = 0;
+ } else {
+ s->io_buffer[508] = s->smart_selftest_count;
+ for (n = 2; n < 506; n++) {
+ s->io_buffer[n] = s->smart_selftest_data[n];
+ }
+ }
+
+ for (n = 0; n < 511; n++) {
+ s->io_buffer[511] += s->io_buffer[n];
+ }
+ s->io_buffer[511] = 0x100 - s->io_buffer[511];
break;
default:
goto abort_cmd;
}
+ s->status = READY_STAT | SEEK_STAT;
ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
- s->status = 0x00; /* NOTE: READY is _not_ set */
ide_set_irq(s->bus);
- break;
- case IBM_SENSE_CONDITION:
- switch (s->feature) {
- case 0x01: /* sense temperature in device */
- s->nsector = 0x50; /* +20 C */
+ return false;
+
+ case SMART_EXECUTE_OFFLINE:
+ switch (s->sector) {
+ case 0: /* off-line routine */
+ case 1: /* short self test */
+ case 2: /* extended self test */
+ s->smart_selftest_count++;
+ if (s->smart_selftest_count > 21) {
+ s->smart_selftest_count = 0;
+ }
+ n = 2 + (s->smart_selftest_count - 1) * 24;
+ s->smart_selftest_data[n] = s->sector;
+ s->smart_selftest_data[n + 1] = 0x00; /* OK and finished */
+ s->smart_selftest_data[n + 2] = 0x34; /* hour count lsb */
+ s->smart_selftest_data[n + 3] = 0x12; /* hour count msb */
break;
default:
goto abort_cmd;
}
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
+ return true;
+ }
- case WIN_SMART:
- if (s->hcyl != 0xc2 || s->lcyl != 0x4f)
- goto abort_cmd;
- if (!s->smart_enabled && s->feature != SMART_ENABLE)
- goto abort_cmd;
- switch (s->feature) {
- case SMART_DISABLE:
- s->smart_enabled = 0;
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
- case SMART_ENABLE:
- s->smart_enabled = 1;
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
- case SMART_ATTR_AUTOSAVE:
- switch (s->sector) {
- case 0x00:
- s->smart_autosave = 0;
- break;
- case 0xf1:
- s->smart_autosave = 1;
- break;
- default:
- goto abort_cmd;
- }
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
- case SMART_STATUS:
- if (!s->smart_errors) {
- s->hcyl = 0xc2;
- s->lcyl = 0x4f;
- } else {
- s->hcyl = 0x2c;
- s->lcyl = 0xf4;
- }
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
- case SMART_READ_THRESH:
- memset(s->io_buffer, 0, 0x200);
- s->io_buffer[0] = 0x01; /* smart struct version */
- for (n = 0; n < ARRAY_SIZE(smart_attributes); n++) {
- s->io_buffer[2+0+(n*12)] = smart_attributes[n][0];
- s->io_buffer[2+1+(n*12)] = smart_attributes[n][11];
- }
- for (n=0; n<511; n++) /* checksum */
- s->io_buffer[511] += s->io_buffer[n];
- s->io_buffer[511] = 0x100 - s->io_buffer[511];
- s->status = READY_STAT | SEEK_STAT;
- ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
- ide_set_irq(s->bus);
- break;
- case SMART_READ_DATA:
- memset(s->io_buffer, 0, 0x200);
- s->io_buffer[0] = 0x01; /* smart struct version */
- for (n = 0; n < ARRAY_SIZE(smart_attributes); n++) {
- int i;
- for(i = 0; i < 11; i++) {
- s->io_buffer[2+i+(n*12)] = smart_attributes[n][i];
- }
- }
- s->io_buffer[362] = 0x02 | (s->smart_autosave?0x80:0x00);
- if (s->smart_selftest_count == 0) {
- s->io_buffer[363] = 0;
- } else {
- s->io_buffer[363] =
- s->smart_selftest_data[3 +
- (s->smart_selftest_count - 1) *
- 24];
- }
- s->io_buffer[364] = 0x20;
- s->io_buffer[365] = 0x01;
- /* offline data collection capacity: execute + self-test*/
- s->io_buffer[367] = (1<<4 | 1<<3 | 1);
- s->io_buffer[368] = 0x03; /* smart capability (1) */
- s->io_buffer[369] = 0x00; /* smart capability (2) */
- s->io_buffer[370] = 0x01; /* error logging supported */
- s->io_buffer[372] = 0x02; /* minutes for poll short test */
- s->io_buffer[373] = 0x36; /* minutes for poll ext test */
- s->io_buffer[374] = 0x01; /* minutes for poll conveyance */
-
- for (n=0; n<511; n++)
- s->io_buffer[511] += s->io_buffer[n];
- s->io_buffer[511] = 0x100 - s->io_buffer[511];
- s->status = READY_STAT | SEEK_STAT;
- ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
- ide_set_irq(s->bus);
- break;
- case SMART_READ_LOG:
- switch (s->sector) {
- case 0x01: /* summary smart error log */
- memset(s->io_buffer, 0, 0x200);
- s->io_buffer[0] = 0x01;
- s->io_buffer[1] = 0x00; /* no error entries */
- s->io_buffer[452] = s->smart_errors & 0xff;
- s->io_buffer[453] = (s->smart_errors & 0xff00) >> 8;
-
- for (n=0; n<511; n++)
- s->io_buffer[511] += s->io_buffer[n];
- s->io_buffer[511] = 0x100 - s->io_buffer[511];
- break;
- case 0x06: /* smart self test log */
- memset(s->io_buffer, 0, 0x200);
- s->io_buffer[0] = 0x01;
- if (s->smart_selftest_count == 0) {
- s->io_buffer[508] = 0;
- } else {
- s->io_buffer[508] = s->smart_selftest_count;
- for (n=2; n<506; n++)
- s->io_buffer[n] = s->smart_selftest_data[n];
- }
- for (n=0; n<511; n++)
- s->io_buffer[511] += s->io_buffer[n];
- s->io_buffer[511] = 0x100 - s->io_buffer[511];
- break;
- default:
- goto abort_cmd;
- }
- s->status = READY_STAT | SEEK_STAT;
- ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop);
- ide_set_irq(s->bus);
- break;
- case SMART_EXECUTE_OFFLINE:
- switch (s->sector) {
- case 0: /* off-line routine */
- case 1: /* short self test */
- case 2: /* extended self test */
- s->smart_selftest_count++;
- if(s->smart_selftest_count > 21)
- s->smart_selftest_count = 0;
- n = 2 + (s->smart_selftest_count - 1) * 24;
- s->smart_selftest_data[n] = s->sector;
- s->smart_selftest_data[n+1] = 0x00; /* OK and finished */
- s->smart_selftest_data[n+2] = 0x34; /* hour count lsb */
- s->smart_selftest_data[n+3] = 0x12; /* hour count msb */
- s->status = READY_STAT | SEEK_STAT;
- ide_set_irq(s->bus);
- break;
- default:
- goto abort_cmd;
- }
- break;
- default:
- goto abort_cmd;
- }
- break;
- default:
- /* should not be reachable */
- abort_cmd:
+abort_cmd:
+ ide_abort_command(s);
+ return true;
+}
+
+#define HD_OK (1u << IDE_HD)
+#define CD_OK (1u << IDE_CD)
+#define CFA_OK (1u << IDE_CFATA)
+#define HD_CFA_OK (HD_OK | CFA_OK)
+#define ALL_OK (HD_OK | CD_OK | CFA_OK)
+
+/* Set the Disk Seek Completed status bit during completion */
+#define SET_DSC (1u << 8)
+
+/* See ACS-2 T13/2015-D Table B.2 Command codes */
+static const struct {
+ /* Returns true if the completion code should be run */
+ bool (*handler)(IDEState *s, uint8_t cmd);
+ int flags;
+} ide_cmd_table[0x100] = {
+ /* NOP not implemented, mandatory for CD */
+ [CFA_REQ_EXT_ERROR_CODE] = { cmd_cfa_req_ext_error_code, CFA_OK },
+ [WIN_DSM] = { cmd_data_set_management, ALL_OK },
+ [WIN_DEVICE_RESET] = { cmd_device_reset, CD_OK },
+ [WIN_RECAL] = { cmd_nop, HD_CFA_OK | SET_DSC},
+ [WIN_READ] = { cmd_read_pio, ALL_OK },
+ [WIN_READ_ONCE] = { cmd_read_pio, ALL_OK },
+ [WIN_READ_EXT] = { cmd_read_pio, HD_CFA_OK },
+ [WIN_READDMA_EXT] = { cmd_read_dma, HD_CFA_OK },
+ [WIN_READ_NATIVE_MAX_EXT] = { cmd_read_native_max, HD_CFA_OK | SET_DSC },
+ [WIN_MULTREAD_EXT] = { cmd_read_multiple, HD_CFA_OK },
+ [WIN_WRITE] = { cmd_write_pio, HD_CFA_OK },
+ [WIN_WRITE_ONCE] = { cmd_write_pio, HD_CFA_OK },
+ [WIN_WRITE_EXT] = { cmd_write_pio, HD_CFA_OK },
+ [WIN_WRITEDMA_EXT] = { cmd_write_dma, HD_CFA_OK },
+ [CFA_WRITE_SECT_WO_ERASE] = { cmd_write_pio, CFA_OK },
+ [WIN_MULTWRITE_EXT] = { cmd_write_multiple, HD_CFA_OK },
+ [WIN_WRITE_VERIFY] = { cmd_write_pio, HD_CFA_OK },
+ [WIN_VERIFY] = { cmd_verify, HD_CFA_OK | SET_DSC },
+ [WIN_VERIFY_ONCE] = { cmd_verify, HD_CFA_OK | SET_DSC },
+ [WIN_VERIFY_EXT] = { cmd_verify, HD_CFA_OK | SET_DSC },
+ [WIN_SEEK] = { cmd_seek, HD_CFA_OK | SET_DSC },
+ [CFA_TRANSLATE_SECTOR] = { cmd_cfa_translate_sector, CFA_OK },
+ [WIN_DIAGNOSE] = { cmd_exec_dev_diagnostic, ALL_OK },
+ [WIN_SPECIFY] = { cmd_nop, HD_CFA_OK | SET_DSC },
+ [WIN_STANDBYNOW2] = { cmd_nop, ALL_OK },
+ [WIN_IDLEIMMEDIATE2] = { cmd_nop, ALL_OK },
+ [WIN_STANDBY2] = { cmd_nop, ALL_OK },
+ [WIN_SETIDLE2] = { cmd_nop, ALL_OK },
+ [WIN_CHECKPOWERMODE2] = { cmd_check_power_mode, ALL_OK | SET_DSC },
+ [WIN_SLEEPNOW2] = { cmd_nop, ALL_OK },
+ [WIN_PACKETCMD] = { cmd_packet, CD_OK },
+ [WIN_PIDENTIFY] = { cmd_identify_packet, CD_OK },
+ [WIN_SMART] = { cmd_smart, HD_CFA_OK | SET_DSC },
+ [CFA_ACCESS_METADATA_STORAGE] = { cmd_cfa_access_metadata_storage, CFA_OK },
+ [CFA_ERASE_SECTORS] = { cmd_cfa_erase_sectors, CFA_OK | SET_DSC },
+ [WIN_MULTREAD] = { cmd_read_multiple, HD_CFA_OK },
+ [WIN_MULTWRITE] = { cmd_write_multiple, HD_CFA_OK },
+ [WIN_SETMULT] = { cmd_set_multiple_mode, HD_CFA_OK | SET_DSC },
+ [WIN_READDMA] = { cmd_read_dma, HD_CFA_OK },
+ [WIN_READDMA_ONCE] = { cmd_read_dma, HD_CFA_OK },
+ [WIN_WRITEDMA] = { cmd_write_dma, HD_CFA_OK },
+ [WIN_WRITEDMA_ONCE] = { cmd_write_dma, HD_CFA_OK },
+ [CFA_WRITE_MULTI_WO_ERASE] = { cmd_write_multiple, CFA_OK },
+ [WIN_STANDBYNOW1] = { cmd_nop, ALL_OK },
+ [WIN_IDLEIMMEDIATE] = { cmd_nop, ALL_OK },
+ [WIN_STANDBY] = { cmd_nop, ALL_OK },
+ [WIN_SETIDLE1] = { cmd_nop, ALL_OK },
+ [WIN_CHECKPOWERMODE1] = { cmd_check_power_mode, ALL_OK | SET_DSC },
+ [WIN_SLEEPNOW1] = { cmd_nop, ALL_OK },
+ [WIN_FLUSH_CACHE] = { cmd_flush_cache, ALL_OK },
+ [WIN_FLUSH_CACHE_EXT] = { cmd_flush_cache, HD_CFA_OK },
+ [WIN_IDENTIFY] = { cmd_identify, ALL_OK },
+ [WIN_SETFEATURES] = { cmd_set_features, ALL_OK | SET_DSC },
+ [IBM_SENSE_CONDITION] = { cmd_ibm_sense_condition, CFA_OK | SET_DSC },
+ [CFA_WEAR_LEVEL] = { cmd_cfa_erase_sectors, HD_CFA_OK | SET_DSC },
+ [WIN_READ_NATIVE_MAX] = { cmd_read_native_max, ALL_OK | SET_DSC },
+};
+
+static bool ide_cmd_permitted(IDEState *s, uint32_t cmd)
+{
+ return cmd < ARRAY_SIZE(ide_cmd_table)
+ && (ide_cmd_table[cmd].flags & (1u << s->drive_kind));
+}
+
+void ide_exec_cmd(IDEBus *bus, uint32_t val)
+{
+ IDEState *s;
+ bool complete;
+
+#if defined(DEBUG_IDE)
+ printf("ide: CMD=%02x\n", val);
+#endif
+ s = idebus_active_if(bus);
+ /* ignore commands to non existent slave */
+ if (s != bus->ifs && !s->bs)
+ return;
+
+ /* Only DEVICE RESET is allowed while BSY or/and DRQ are set */
+ if ((s->status & (BUSY_STAT|DRQ_STAT)) && val != WIN_DEVICE_RESET)
+ return;
+
+ if (!ide_cmd_permitted(s, val)) {
ide_abort_command(s);
ide_set_irq(s->bus);
- break;
+ return;
+ }
+
+ s->status = READY_STAT | BUSY_STAT;
+ s->error = 0;
+
+ complete = ide_cmd_table[val].handler(s, val);
+ if (complete) {
+ s->status &= ~BUSY_STAT;
+ assert(!!s->error == !!(s->status & ERR_STAT));
+
+ if ((ide_cmd_table[val].flags & SET_DSC) && !s->error) {
+ s->status |= SEEK_STAT;
+ }
+
+ ide_set_irq(s->bus);
}
}
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 387b050987..f487a8fd93 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -57,7 +57,7 @@
048 img auto quick
049 rw auto
050 rw auto backing quick
-#051 rw auto
+051 rw auto
052 rw auto backing
053 rw auto
054 rw auto