aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--block/blkdebug.c20
-rw-r--r--block/parallels.c52
-rw-r--r--block/qcow2-refcount.c1
-rw-r--r--docs/image-fuzzer.txt238
-rw-r--r--hw/block/dataplane/virtio-blk.c39
-rw-r--r--hw/block/virtio-blk.c14
-rw-r--r--hw/i386/pc_q35.c2
-rw-r--r--hw/ide/ahci.c115
-rw-r--r--hw/ide/ahci.h21
-rw-r--r--hw/ide/atapi.c11
-rw-r--r--hw/ide/cmd646.c94
-rw-r--r--hw/ide/core.c101
-rw-r--r--hw/ide/internal.h38
-rw-r--r--hw/ide/macio.c9
-rw-r--r--hw/ide/pci.c45
-rw-r--r--hw/ide/pci.h7
-rw-r--r--qemu-char.c4
-rw-r--r--qemu-options.hx2
-rw-r--r--qga/channel-posix.c2
-rw-r--r--tests/ide-test.c99
-rw-r--r--tests/image-fuzzer/qcow2/__init__.py1
-rw-r--r--tests/image-fuzzer/qcow2/fuzz.py355
-rw-r--r--tests/image-fuzzer/qcow2/layout.py476
-rwxr-xr-xtests/image-fuzzer/runner.py405
-rw-r--r--tests/libqos/malloc-pc.c5
-rw-r--r--tests/libqos/malloc.h2
-rw-r--r--tests/libqos/pci-pc.c12
-rw-r--r--tests/libqos/pci-pc.h1
-rw-r--r--tests/libqos/pci.c4
-rw-r--r--tests/libqos/pci.h4
-rw-r--r--tests/libqtest.c24
-rw-r--r--tests/libqtest.h24
-rw-r--r--tests/usb-hcd-ehci-test.c2
33 files changed, 1991 insertions, 238 deletions
diff --git a/block/blkdebug.c b/block/blkdebug.c
index f51407de3f..1586ed9664 100644
--- a/block/blkdebug.c
+++ b/block/blkdebug.c
@@ -522,6 +522,25 @@ static BlockDriverAIOCB *blkdebug_aio_writev(BlockDriverState *bs,
return bdrv_aio_writev(bs->file, sector_num, qiov, nb_sectors, cb, opaque);
}
+static BlockDriverAIOCB *blkdebug_aio_flush(BlockDriverState *bs,
+ BlockDriverCompletionFunc *cb, void *opaque)
+{
+ BDRVBlkdebugState *s = bs->opaque;
+ BlkdebugRule *rule = NULL;
+
+ QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) {
+ if (rule->options.inject.sector == -1) {
+ break;
+ }
+ }
+
+ if (rule && rule->options.inject.error) {
+ return inject_error(bs, cb, opaque, rule);
+ }
+
+ return bdrv_aio_flush(bs->file, cb, opaque);
+}
+
static void blkdebug_close(BlockDriverState *bs)
{
@@ -699,6 +718,7 @@ static BlockDriver bdrv_blkdebug = {
.bdrv_aio_readv = blkdebug_aio_readv,
.bdrv_aio_writev = blkdebug_aio_writev,
+ .bdrv_aio_flush = blkdebug_aio_flush,
.bdrv_debug_event = blkdebug_debug_event,
.bdrv_debug_breakpoint = blkdebug_debug_breakpoint,
diff --git a/block/parallels.c b/block/parallels.c
index 7325678a4d..1774ab8e8e 100644
--- a/block/parallels.c
+++ b/block/parallels.c
@@ -30,6 +30,7 @@
/**************************************************************/
#define HEADER_MAGIC "WithoutFreeSpace"
+#define HEADER_MAGIC2 "WithouFreSpacExt"
#define HEADER_VERSION 2
#define HEADER_SIZE 64
@@ -41,8 +42,10 @@ struct parallels_header {
uint32_t cylinders;
uint32_t tracks;
uint32_t catalog_entries;
- uint32_t nb_sectors;
- char padding[24];
+ uint64_t nb_sectors;
+ uint32_t inuse;
+ uint32_t data_off;
+ char padding[12];
} QEMU_PACKED;
typedef struct BDRVParallelsState {
@@ -52,6 +55,8 @@ typedef struct BDRVParallelsState {
unsigned int catalog_size;
unsigned int tracks;
+
+ unsigned int off_multiplier;
} BDRVParallelsState;
static int parallels_probe(const uint8_t *buf, int buf_size, const char *filename)
@@ -59,11 +64,12 @@ static int parallels_probe(const uint8_t *buf, int buf_size, const char *filenam
const struct parallels_header *ph = (const void *)buf;
if (buf_size < HEADER_SIZE)
- return 0;
+ return 0;
- if (!memcmp(ph->magic, HEADER_MAGIC, 16) &&
- (le32_to_cpu(ph->version) == HEADER_VERSION))
- return 100;
+ if ((!memcmp(ph->magic, HEADER_MAGIC, 16) ||
+ !memcmp(ph->magic, HEADER_MAGIC2, 16)) &&
+ (le32_to_cpu(ph->version) == HEADER_VERSION))
+ return 100;
return 0;
}
@@ -83,14 +89,19 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
goto fail;
}
- if (memcmp(ph.magic, HEADER_MAGIC, 16) ||
- (le32_to_cpu(ph.version) != HEADER_VERSION)) {
- error_setg(errp, "Image not in Parallels format");
- ret = -EINVAL;
- goto fail;
- }
+ bs->total_sectors = le64_to_cpu(ph.nb_sectors);
- bs->total_sectors = le32_to_cpu(ph.nb_sectors);
+ if (le32_to_cpu(ph.version) != HEADER_VERSION) {
+ goto fail_format;
+ }
+ if (!memcmp(ph.magic, HEADER_MAGIC, 16)) {
+ s->off_multiplier = 1;
+ bs->total_sectors = 0xffffffff & bs->total_sectors;
+ } else if (!memcmp(ph.magic, HEADER_MAGIC2, 16)) {
+ s->off_multiplier = le32_to_cpu(ph.tracks);
+ } else {
+ goto fail_format;
+ }
s->tracks = le32_to_cpu(ph.tracks);
if (s->tracks == 0) {
@@ -98,6 +109,11 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
ret = -EINVAL;
goto fail;
}
+ if (s->tracks > INT32_MAX/513) {
+ error_setg(errp, "Invalid image: Too big cluster");
+ ret = -EFBIG;
+ goto fail;
+ }
s->catalog_size = le32_to_cpu(ph.catalog_entries);
if (s->catalog_size > INT_MAX / 4) {
@@ -117,11 +133,14 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
}
for (i = 0; i < s->catalog_size; i++)
- le32_to_cpus(&s->catalog_bitmap[i]);
+ le32_to_cpus(&s->catalog_bitmap[i]);
qemu_co_mutex_init(&s->lock);
return 0;
+fail_format:
+ error_setg(errp, "Image not in Parallels format");
+ ret = -EINVAL;
fail:
g_free(s->catalog_bitmap);
return ret;
@@ -137,8 +156,9 @@ static int64_t seek_to_sector(BlockDriverState *bs, int64_t sector_num)
/* not allocated */
if ((index > s->catalog_size) || (s->catalog_bitmap[index] == 0))
- return -1;
- return (uint64_t)(s->catalog_bitmap[index] + offset) * 512;
+ return -1;
+ return
+ ((uint64_t)s->catalog_bitmap[index] * s->off_multiplier + offset) * 512;
}
static int parallels_read(BlockDriverState *bs, int64_t sector_num,
diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c
index d60e2feb10..3b7747048e 100644
--- a/block/qcow2-refcount.c
+++ b/block/qcow2-refcount.c
@@ -381,6 +381,7 @@ static int alloc_refcount_block(BlockDriverState *bs,
ret = bdrv_pwrite_sync(bs->file, meta_offset, new_blocks,
blocks_clusters * s->cluster_size);
g_free(new_blocks);
+ new_blocks = NULL;
if (ret < 0) {
goto fail_table;
}
diff --git a/docs/image-fuzzer.txt b/docs/image-fuzzer.txt
new file mode 100644
index 0000000000..0d0005d34a
--- /dev/null
+++ b/docs/image-fuzzer.txt
@@ -0,0 +1,238 @@
+# Specification for the fuzz testing tool
+#
+# Copyright (C) 2014 Maria Kustova <maria.k@catit.be>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+Image fuzzer
+============
+
+Description
+-----------
+
+The goal of the image fuzzer is to catch crashes of qemu-io/qemu-img
+by providing to them randomly corrupted images.
+Test images are generated from scratch and have valid inner structure with some
+elements, e.g. L1/L2 tables, having random invalid values.
+
+
+Test runner
+-----------
+
+The test runner generates test images, executes tests utilizing generated
+images, indicates their results and collects all test related artifacts (logs,
+core dumps, test images, backing files).
+The test means execution of all available commands under test with the same
+generated test image.
+By default, the test runner generates new tests and executes them until
+keyboard interruption. But if a test seed is specified via the '--seed' runner
+parameter, then only one test with this seed will be executed, after its finish
+the runner will exit.
+
+The runner uses an external image fuzzer to generate test images. An image
+generator should be specified as a mandatory parameter of the test runner.
+Details about interactions between the runner and fuzzers see "Module
+interfaces".
+
+The runner activates generation of core dumps during test executions, but it
+assumes that core dumps will be generated in the current working directory.
+For comprehensive test results, please, set up your test environment
+properly.
+
+Paths to binaries under test (SUTs) qemu-img and qemu-io are retrieved from
+environment variables. If the environment check fails the runner will
+use SUTs installed in system paths.
+qemu-img is required for creation of backing files, so it's mandatory to set
+the related environment variable if it's not installed in the system path.
+For details about environment variables see qemu-iotests/check.
+
+The runner accepts a JSON array of fields expected to be fuzzed via the
+'--config' argument, e.g.
+
+ '[["feature_name_table"], ["header", "l1_table_offset"]]'
+
+Each sublist can have one or two strings defining image structure elements.
+In the latter case a parent element should be placed on the first position,
+and a field name on the second one.
+
+The runner accepts a list of commands under test as a JSON array via
+the '--command' argument. Each command is a list containing a SUT and all its
+arguments, e.g.
+
+ runner.py -c '[["qemu-io", "$test_img", "-c", "write $off $len"]]'
+ /tmp/test ../qcow2
+
+For variable arguments next aliases can be used:
+ - $test_img for a fuzzed img
+ - $off for an offset in the fuzzed image
+ - $len for a data size
+
+Values for last two aliases will be generated based on a size of a virtual
+disk of the generated image.
+In case when no commands are specified the runner will execute commands from
+the default list:
+ - qemu-img check
+ - qemu-img info
+ - qemu-img convert
+ - qemu-io -c read
+ - qemu-io -c write
+ - qemu-io -c aio_read
+ - qemu-io -c aio_write
+ - qemu-io -c flush
+ - qemu-io -c discard
+ - qemu-io -c truncate
+
+
+Qcow2 image generator
+---------------------
+
+The 'qcow2' generator is a Python package providing 'create_image' method as
+a single public API. See details in 'Test runner/image fuzzer' chapter of
+'Module interfaces'.
+
+Qcow2 contains two submodules: fuzz.py and layout.py.
+
+'fuzz.py' contains all fuzzing functions, one per image field. It's assumed
+that after code analysis every field will have own constraints for its value.
+For now only universal potentially dangerous values are used, e.g. type limits
+for integers or unsafe symbols as '%s' for strings. For bitmasks random amount
+of bits are set to ones. All fuzzed values are checked on non-equality to the
+current valid value of the field. In case of equality the value will be
+regenerated.
+
+'layout.py' creates a random valid image, fuzzes a random subset of the image
+fields by 'fuzz.py' module and writes a fuzzed image to the file specified.
+If a fuzzer configuration is specified, then it has the next interpretation:
+
+ 1. If a list contains a parent image element only, then some random portion
+ of fields of this element will be fuzzed every test.
+ The same behavior is applied for the entire image if no configuration is
+ used. This case is useful for the test specialization.
+
+ 2. If a list contains a parent element and a field name, then a field
+ will be always fuzzed for every test. This case is useful for regression
+ testing.
+
+For now only header fields, header extensions and L1/L2 tables are generated.
+
+Module interfaces
+-----------------
+
+* Test runner/image fuzzer
+
+The runner calls an image generator specifying the path to a test image file,
+path to a backing file and its format and a fuzzer configuration.
+An image generator is expected to provide a
+
+ 'create_image(test_img_path, backing_file_path=None,
+ backing_file_format=None, fuzz_config=None)'
+
+method that creates a test image, writes it to the specified file and returns
+the size of the virtual disk.
+The file should be created if it doesn't exist or overwritten otherwise.
+fuzz_config has a form of a list of lists. Every sublist can have one
+or two elements: first element is a name of a parent image element, second one
+if exists is a name of a field in this element.
+Example,
+ [['header', 'l1_table_offset'],
+ ['header', 'nb_snapshots'],
+ ['feature_name_table']]
+
+Random seed is set by the runner at every test execution for the regression
+purpose, so an image generator is not recommended to modify it internally.
+
+
+Overall fuzzer requirements
+===========================
+
+Input data:
+----------
+
+ - image template (generator)
+ - work directory
+ - action vector (optional)
+ - seed (optional)
+ - SUT and its arguments (optional)
+
+
+Fuzzer requirements:
+-------------------
+
+1. Should be able to inject random data
+2. Should be able to select a random value from the manually pregenerated
+ vector (boundary values, e.g. max/min cluster size)
+3. Image template should describe a general structure invariant for all
+ test images (image format description)
+4. Image template should be autonomous and other fuzzer parts should not
+ rely on it
+5. Image template should contain reference rules (not only block+size
+ description)
+6. Should generate the test image with the correct structure based on an image
+ template
+7. Should accept a seed as an argument (for regression purpose)
+8. Should generate a seed if it is not specified as an input parameter.
+9. The same seed should generate the same image for the same action vector,
+ specified or generated.
+10. Should accept a vector of actions as an argument (for test reproducing and
+ for test case specification, e.g. group of tests for header structure,
+ group of test for snapshots, etc)
+11. Action vector should be randomly generated from the pool of available
+ actions, if it is not specified as an input parameter
+12. Pool of actions should be defined automatically based on an image template
+13. Should accept a SUT and its call parameters as an argument or select them
+ randomly otherwise. As far as it's expected to be rarely changed, the list
+ of all possible test commands can be available in the test runner
+ internally.
+14. Should support an external cancellation of a test run
+15. Seed should be logged (for regression purpose)
+16. All files related to a test result should be collected: a test image,
+ SUT logs, fuzzer logs and crash dumps
+17. Should be compatible with python version 2.4-2.7
+18. Usage of external libraries should be limited as much as possible.
+
+
+Image formats:
+-------------
+
+Main target image format is qcow2, but support of image templates should
+provide an ability to add any other image format.
+
+
+Effectiveness:
+-------------
+
+The fuzzer can be controlled via template, seed and action vector;
+it makes the fuzzer itself invariant to an image format and test logic.
+It should be able to perform rather complex and precise tests, that can be
+specified via an action vector. Otherwise, knowledge about an image structure
+allows the fuzzer to generate the pool of all available areas can be fuzzed
+and randomly select some of them and so compose its own action vector.
+Also complexity of a template defines complexity of the fuzzer, so its
+functionality can be varied from simple model-independent fuzzing to smart
+model-based one.
+
+
+Glossary:
+--------
+
+Action vector is a sequence of structure elements retrieved from an image
+format, each of them will be fuzzed for the test image. It's a subset of
+elements of the action pool. Example: header, refcount table, etc.
+Action pool is all available elements of an image structure that generated
+automatically from an image template.
+Image template is a formal description of an image structure and relations
+between image blocks.
+Test image is an output image of the fuzzer defined by the current seed and
+action vector.
diff --git a/hw/block/dataplane/virtio-blk.c b/hw/block/dataplane/virtio-blk.c
index d6ba65ca23..24a6b71395 100644
--- a/hw/block/dataplane/virtio-blk.c
+++ b/hw/block/dataplane/virtio-blk.c
@@ -28,6 +28,7 @@ struct VirtIOBlockDataPlane {
bool started;
bool starting;
bool stopping;
+ bool disabled;
VirtIOBlkConf *blk;
@@ -218,8 +219,9 @@ void virtio_blk_data_plane_start(VirtIOBlockDataPlane *s)
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
VirtIOBlock *vblk = VIRTIO_BLK(s->vdev);
VirtQueue *vq;
+ int r;
- if (s->started) {
+ if (s->started || s->disabled) {
return;
}
@@ -231,22 +233,23 @@ void virtio_blk_data_plane_start(VirtIOBlockDataPlane *s)
vq = virtio_get_queue(s->vdev, 0);
if (!vring_setup(&s->vring, s->vdev, 0)) {
- s->starting = false;
- return;
+ goto fail_vring;
}
/* Set up guest notifier (irq) */
- if (k->set_guest_notifiers(qbus->parent, 1, true) != 0) {
- fprintf(stderr, "virtio-blk failed to set guest notifier, "
- "ensure -enable-kvm is set\n");
- exit(1);
+ r = k->set_guest_notifiers(qbus->parent, 1, true);
+ if (r != 0) {
+ fprintf(stderr, "virtio-blk failed to set guest notifier (%d), "
+ "ensure -enable-kvm is set\n", r);
+ goto fail_guest_notifiers;
}
s->guest_notifier = virtio_queue_get_guest_notifier(vq);
/* Set up virtqueue notify */
- if (k->set_host_notifier(qbus->parent, 0, true) != 0) {
- fprintf(stderr, "virtio-blk failed to set host notifier\n");
- exit(1);
+ r = k->set_host_notifier(qbus->parent, 0, true);
+ if (r != 0) {
+ fprintf(stderr, "virtio-blk failed to set host notifier (%d)\n", r);
+ goto fail_host_notifier;
}
s->host_notifier = *virtio_queue_get_host_notifier(vq);
@@ -266,6 +269,15 @@ void virtio_blk_data_plane_start(VirtIOBlockDataPlane *s)
aio_context_acquire(s->ctx);
aio_set_event_notifier(s->ctx, &s->host_notifier, handle_notify);
aio_context_release(s->ctx);
+ return;
+
+ fail_host_notifier:
+ k->set_guest_notifiers(qbus->parent, 1, false);
+ fail_guest_notifiers:
+ vring_teardown(&s->vring, s->vdev, 0);
+ s->disabled = true;
+ fail_vring:
+ s->starting = false;
}
/* Context: QEMU global mutex held */
@@ -274,6 +286,13 @@ void virtio_blk_data_plane_stop(VirtIOBlockDataPlane *s)
BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s->vdev)));
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
VirtIOBlock *vblk = VIRTIO_BLK(s->vdev);
+
+
+ /* Better luck next time. */
+ if (s->disabled) {
+ s->disabled = false;
+ return;
+ }
if (!s->started || s->stopping) {
return;
}
diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
index c241c5002b..302c39e2be 100644
--- a/hw/block/virtio-blk.c
+++ b/hw/block/virtio-blk.c
@@ -404,19 +404,19 @@ void virtio_blk_handle_request(VirtIOBlockReq *req, MultiReqBuffer *mrb)
* NB: per existing s/n string convention the string is
* terminated by '\0' only when shorter than buffer.
*/
- strncpy(req->elem.in_sg[0].iov_base,
- s->blk.serial ? s->blk.serial : "",
- MIN(req->elem.in_sg[0].iov_len, VIRTIO_BLK_ID_BYTES));
+ const char *serial = s->blk.serial ? s->blk.serial : "";
+ size_t size = MIN(strlen(serial) + 1,
+ MIN(iov_size(in_iov, in_num),
+ VIRTIO_BLK_ID_BYTES));
+ iov_from_buf(in_iov, in_num, 0, serial, size);
virtio_blk_req_complete(req, VIRTIO_BLK_S_OK);
virtio_blk_free_request(req);
} else if (type & VIRTIO_BLK_T_OUT) {
- qemu_iovec_init_external(&req->qiov, &req->elem.out_sg[1],
- req->elem.out_num - 1);
+ qemu_iovec_init_external(&req->qiov, iov, out_num);
virtio_blk_handle_write(req, mrb);
} else if (type == VIRTIO_BLK_T_IN || type == VIRTIO_BLK_T_BARRIER) {
/* VIRTIO_BLK_T_IN is 0, so we can't just & it. */
- qemu_iovec_init_external(&req->qiov, &req->elem.in_sg[0],
- req->elem.in_num - 1);
+ qemu_iovec_init_external(&req->qiov, in_iov, in_num);
virtio_blk_handle_read(req);
} else {
virtio_blk_req_complete(req, VIRTIO_BLK_S_UNSUPP);
diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c
index 43350d7bcc..4b5a27404a 100644
--- a/hw/i386/pc_q35.c
+++ b/hw/i386/pc_q35.c
@@ -234,7 +234,7 @@ static void pc_q35_init(MachineState *machine)
gsi_state->i8259_irq[i] = i8259[i];
}
if (pci_enabled) {
- ioapic_init_gsi(gsi_state, NULL);
+ ioapic_init_gsi(gsi_state, "q35");
}
qdev_init_nofail(icc_bridge);
diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c
index 604152a823..4cda0d0075 100644
--- a/hw/ide/ahci.c
+++ b/hw/ide/ahci.c
@@ -584,7 +584,72 @@ static void ahci_write_fis_sdb(AHCIState *s, int port, uint32_t finished)
s->dev[port].finished |= finished;
*(uint32_t*)(sdb_fis + 4) = cpu_to_le32(s->dev[port].finished);
- ahci_trigger_irq(s, &s->dev[port], PORT_IRQ_STAT_SDBS);
+ ahci_trigger_irq(s, &s->dev[port], PORT_IRQ_SDB_FIS);
+}
+
+static void ahci_write_fis_pio(AHCIDevice *ad, uint16_t len)
+{
+ AHCIPortRegs *pr = &ad->port_regs;
+ uint8_t *pio_fis, *cmd_fis;
+ uint64_t tbl_addr;
+ dma_addr_t cmd_len = 0x80;
+
+ if (!ad->res_fis || !(pr->cmd & PORT_CMD_FIS_RX)) {
+ return;
+ }
+
+ /* map cmd_fis */
+ tbl_addr = le64_to_cpu(ad->cur_cmd->tbl_addr);
+ cmd_fis = dma_memory_map(ad->hba->as, tbl_addr, &cmd_len,
+ DMA_DIRECTION_TO_DEVICE);
+
+ if (cmd_fis == NULL) {
+ DPRINTF(ad->port_no, "dma_memory_map failed in ahci_write_fis_pio");
+ ahci_trigger_irq(ad->hba, ad, PORT_IRQ_HBUS_ERR);
+ return;
+ }
+
+ if (cmd_len != 0x80) {
+ DPRINTF(ad->port_no,
+ "dma_memory_map mapped too few bytes in ahci_write_fis_pio");
+ dma_memory_unmap(ad->hba->as, cmd_fis, cmd_len,
+ DMA_DIRECTION_TO_DEVICE, cmd_len);
+ ahci_trigger_irq(ad->hba, ad, PORT_IRQ_HBUS_ERR);
+ return;
+ }
+
+ pio_fis = &ad->res_fis[RES_FIS_PSFIS];
+
+ pio_fis[0] = 0x5f;
+ pio_fis[1] = (ad->hba->control_regs.irqstatus ? (1 << 6) : 0);
+ pio_fis[2] = ad->port.ifs[0].status;
+ pio_fis[3] = ad->port.ifs[0].error;
+
+ pio_fis[4] = cmd_fis[4];
+ pio_fis[5] = cmd_fis[5];
+ pio_fis[6] = cmd_fis[6];
+ pio_fis[7] = cmd_fis[7];
+ pio_fis[8] = cmd_fis[8];
+ pio_fis[9] = cmd_fis[9];
+ pio_fis[10] = cmd_fis[10];
+ pio_fis[11] = cmd_fis[11];
+ pio_fis[12] = cmd_fis[12];
+ pio_fis[13] = cmd_fis[13];
+ pio_fis[14] = 0;
+ pio_fis[15] = ad->port.ifs[0].status;
+ pio_fis[16] = len & 255;
+ pio_fis[17] = len >> 8;
+ pio_fis[18] = 0;
+ pio_fis[19] = 0;
+
+ if (pio_fis[2] & ERR_STAT) {
+ ahci_trigger_irq(ad->hba, ad, PORT_IRQ_TF_ERR);
+ }
+
+ ahci_trigger_irq(ad->hba, ad, PORT_IRQ_PIOS_FIS);
+
+ dma_memory_unmap(ad->hba->as, cmd_fis, cmd_len,
+ DMA_DIRECTION_TO_DEVICE, cmd_len);
}
static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis)
@@ -629,7 +694,7 @@ static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis)
}
if (d2h_fis[2] & ERR_STAT) {
- ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_TFES);
+ ahci_trigger_irq(ad->hba, ad, PORT_IRQ_TF_ERR);
}
ahci_trigger_irq(ad->hba, ad, PORT_IRQ_D2H_REG_FIS);
@@ -969,11 +1034,6 @@ static int handle_cmd(AHCIState *s, int port, int slot)
/* We're ready to process the command in FIS byte 2. */
ide_exec_cmd(&s->dev[port].port, cmd_fis[2]);
-
- if ((s->dev[port].port.ifs[0].status & (READY_STAT|DRQ_STAT|BUSY_STAT)) ==
- READY_STAT) {
- ahci_write_fis_d2h(&s->dev[port], cmd_fis);
- }
}
out:
@@ -991,7 +1051,7 @@ out:
}
/* DMA dev <-> ram */
-static int ahci_start_transfer(IDEDMA *dma)
+static void ahci_start_transfer(IDEDMA *dma)
{
AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
IDEState *s = &ad->port.ifs[0];
@@ -1038,11 +1098,9 @@ out:
s->end_transfer_func(s);
if (!(s->status & DRQ_STAT)) {
- /* done with DMA */
- ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_DSS);
+ /* done with PIO send/receive */
+ ahci_write_fis_pio(ad, le32_to_cpu(ad->cur_cmd->status));
}
-
- return 0;
}
static void ahci_start_dma(IDEDMA *dma, IDEState *s,
@@ -1104,28 +1162,11 @@ static int ahci_dma_set_unit(IDEDMA *dma, int unit)
return 0;
}
-static int ahci_dma_add_status(IDEDMA *dma, int status)
-{
- AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
- DPRINTF(ad->port_no, "set status: %x\n", status);
-
- if (status & BM_STATUS_INT) {
- ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_DSS);
- }
-
- return 0;
-}
-
-static int ahci_dma_set_inactive(IDEDMA *dma)
-{
- return 0;
-}
-
-static int ahci_async_cmd_done(IDEDMA *dma)
+static void ahci_cmd_done(IDEDMA *dma)
{
AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
- DPRINTF(ad->port_no, "async cmd done\n");
+ DPRINTF(ad->port_no, "cmd done\n");
/* update d2h status */
ahci_write_fis_d2h(ad, NULL);
@@ -1135,8 +1176,6 @@ static int ahci_async_cmd_done(IDEDMA *dma)
ad->check_bh = qemu_bh_new(ahci_check_cmd_bh, ad);
qemu_bh_schedule(ad->check_bh);
}
-
- return 0;
}
static void ahci_irq_set(void *opaque, int n, int level)
@@ -1147,22 +1186,14 @@ static void ahci_dma_restart_cb(void *opaque, int running, RunState state)
{
}
-static int ahci_dma_reset(IDEDMA *dma)
-{
- return 0;
-}
-
static const IDEDMAOps ahci_dma_ops = {
.start_dma = ahci_start_dma,
.start_transfer = ahci_start_transfer,
.prepare_buf = ahci_dma_prepare_buf,
.rw_buf = ahci_dma_rw_buf,
.set_unit = ahci_dma_set_unit,
- .add_status = ahci_dma_add_status,
- .set_inactive = ahci_dma_set_inactive,
- .async_cmd_done = ahci_async_cmd_done,
+ .cmd_done = ahci_cmd_done,
.restart_cb = ahci_dma_restart_cb,
- .reset = ahci_dma_reset,
};
void ahci_init(AHCIState *s, DeviceState *qdev, AddressSpace *as, int ports)
diff --git a/hw/ide/ahci.h b/hw/ide/ahci.h
index f418b30ce7..1543df7b7d 100644
--- a/hw/ide/ahci.h
+++ b/hw/ide/ahci.h
@@ -132,27 +132,6 @@
#define PORT_CMD_ICC_PARTIAL (0x2 << 28) /* Put i/f in partial state */
#define PORT_CMD_ICC_SLUMBER (0x6 << 28) /* Put i/f in slumber state */
-#define PORT_IRQ_STAT_DHRS (1 << 0) /* Device to Host Register FIS */
-#define PORT_IRQ_STAT_PSS (1 << 1) /* PIO Setup FIS */
-#define PORT_IRQ_STAT_DSS (1 << 2) /* DMA Setup FIS */
-#define PORT_IRQ_STAT_SDBS (1 << 3) /* Set Device Bits */
-#define PORT_IRQ_STAT_UFS (1 << 4) /* Unknown FIS */
-#define PORT_IRQ_STAT_DPS (1 << 5) /* Descriptor Processed */
-#define PORT_IRQ_STAT_PCS (1 << 6) /* Port Connect Change Status */
-#define PORT_IRQ_STAT_DMPS (1 << 7) /* Device Mechanical Presence
- Status */
-#define PORT_IRQ_STAT_PRCS (1 << 22) /* File Ready Status */
-#define PORT_IRQ_STAT_IPMS (1 << 23) /* Incorrect Port Multiplier
- Status */
-#define PORT_IRQ_STAT_OFS (1 << 24) /* Overflow Status */
-#define PORT_IRQ_STAT_INFS (1 << 26) /* Interface Non-Fatal Error
- Status */
-#define PORT_IRQ_STAT_IFS (1 << 27) /* Interface Fatal Error */
-#define PORT_IRQ_STAT_HBDS (1 << 28) /* Host Bus Data Error Status */
-#define PORT_IRQ_STAT_HBFS (1 << 29) /* Host Bus Fatal Error Status */
-#define PORT_IRQ_STAT_TFES (1 << 30) /* Task File Error Status */
-#define PORT_IRQ_STAT_CPDS (1U << 31) /* Code Port Detect Status */
-
/* ap->flags bits */
#define AHCI_FLAG_NO_NCQ (1 << 24)
#define AHCI_FLAG_IGN_IRQ_IF_ERR (1 << 25) /* ignore IRQ_IF_ERR */
diff --git a/hw/ide/atapi.c b/hw/ide/atapi.c
index f7d2009c00..3d92b52dbc 100644
--- a/hw/ide/atapi.c
+++ b/hw/ide/atapi.c
@@ -174,9 +174,9 @@ void ide_atapi_cmd_reply_end(IDEState *s)
#endif
if (s->packet_transfer_size <= 0) {
/* end of transfer */
- ide_transfer_stop(s);
s->status = READY_STAT | SEEK_STAT;
s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ ide_transfer_stop(s);
ide_set_irq(s->bus);
#ifdef DEBUG_IDE_ATAPI
printf("status=0x%x\n", s->status);
@@ -255,8 +255,7 @@ static void ide_atapi_cmd_reply(IDEState *s, int size, int max_size)
if (s->atapi_dma) {
bdrv_acct_start(s->bs, &s->acct, size, BDRV_ACCT_READ);
s->status = READY_STAT | SEEK_STAT | DRQ_STAT;
- s->bus->dma->ops->start_dma(s->bus->dma, s,
- ide_atapi_cmd_read_dma_cb);
+ ide_start_dma(s, ide_atapi_cmd_read_dma_cb);
} else {
s->status = READY_STAT | SEEK_STAT;
ide_atapi_cmd_reply_end(s);
@@ -356,8 +355,7 @@ static void ide_atapi_cmd_read_dma_cb(void *opaque, int ret)
eot:
bdrv_acct_done(s->bs, &s->acct);
- s->bus->dma->ops->add_status(s->bus->dma, BM_STATUS_INT);
- ide_set_inactive(s);
+ ide_set_inactive(s, false);
}
/* start a CD-CDROM read command with DMA */
@@ -375,8 +373,7 @@ static void ide_atapi_cmd_read_dma(IDEState *s, int lba, int nb_sectors,
/* XXX: check if BUSY_STAT should be set */
s->status = READY_STAT | SEEK_STAT | DRQ_STAT | BUSY_STAT;
- s->bus->dma->ops->start_dma(s->bus->dma, s,
- ide_atapi_cmd_read_dma_cb);
+ ide_start_dma(s, ide_atapi_cmd_read_dma_cb);
}
static void ide_atapi_cmd_read(IDEState *s, int lba, int nb_sectors,
diff --git a/hw/ide/cmd646.c b/hw/ide/cmd646.c
index a8e35fe38f..74d0deb6dd 100644
--- a/hw/ide/cmd646.c
+++ b/hw/ide/cmd646.c
@@ -33,6 +33,13 @@
#include <hw/ide/pci.h>
/* CMD646 specific */
+#define CFR 0x50
+#define CFR_INTR_CH0 0x04
+#define CNTRL 0x51
+#define CNTRL_EN_CH0 0x04
+#define CNTRL_EN_CH1 0x08
+#define ARTTIM23 0x57
+#define ARTTIM23_INTR_CH1 0x10
#define MRDMODE 0x71
#define MRDMODE_INTR_CH0 0x04
#define MRDMODE_INTR_CH1 0x08
@@ -41,7 +48,7 @@
#define UDIDETCR0 0x73
#define UDIDETCR1 0x7B
-static void cmd646_update_irq(PCIIDEState *d);
+static void cmd646_update_irq(PCIDevice *pd);
static uint64_t cmd646_cmd_read(void *opaque, hwaddr addr,
unsigned size)
@@ -123,6 +130,38 @@ static void setup_cmd646_bar(PCIIDEState *d, int bus_num)
"cmd646-data", 8);
}
+static void cmd646_update_dma_interrupts(PCIDevice *pd)
+{
+ /* Sync DMA interrupt status from UDMA interrupt status */
+ if (pd->config[MRDMODE] & MRDMODE_INTR_CH0) {
+ pd->config[CFR] |= CFR_INTR_CH0;
+ } else {
+ pd->config[CFR] &= ~CFR_INTR_CH0;
+ }
+
+ if (pd->config[MRDMODE] & MRDMODE_INTR_CH1) {
+ pd->config[ARTTIM23] |= ARTTIM23_INTR_CH1;
+ } else {
+ pd->config[ARTTIM23] &= ~ARTTIM23_INTR_CH1;
+ }
+}
+
+static void cmd646_update_udma_interrupts(PCIDevice *pd)
+{
+ /* Sync UDMA interrupt status from DMA interrupt status */
+ if (pd->config[CFR] & CFR_INTR_CH0) {
+ pd->config[MRDMODE] |= MRDMODE_INTR_CH0;
+ } else {
+ pd->config[MRDMODE] &= ~MRDMODE_INTR_CH0;
+ }
+
+ if (pd->config[ARTTIM23] & ARTTIM23_INTR_CH1) {
+ pd->config[MRDMODE] |= MRDMODE_INTR_CH1;
+ } else {
+ pd->config[MRDMODE] &= ~MRDMODE_INTR_CH1;
+ }
+}
+
static uint64_t bmdma_read(void *opaque, hwaddr addr,
unsigned size)
{
@@ -181,7 +220,8 @@ static void bmdma_write(void *opaque, hwaddr addr,
case 1:
pci_dev->config[MRDMODE] =
(pci_dev->config[MRDMODE] & ~0x30) | (val & 0x30);
- cmd646_update_irq(bm->pci_dev);
+ cmd646_update_dma_interrupts(pci_dev);
+ cmd646_update_irq(pci_dev);
break;
case 2:
bm->status = (val & 0x60) | (bm->status & 1) | (bm->status & ~val & 0x06);
@@ -219,11 +259,8 @@ static void bmdma_setup_bar(PCIIDEState *d)
}
}
-/* XXX: call it also when the MRDMODE is changed from the PCI config
- registers */
-static void cmd646_update_irq(PCIIDEState *d)
+static void cmd646_update_irq(PCIDevice *pd)
{
- PCIDevice *pd = PCI_DEVICE(d);
int pci_level;
pci_level = ((pd->config[MRDMODE] & MRDMODE_INTR_CH0) &&
@@ -246,7 +283,8 @@ static void cmd646_set_irq(void *opaque, int channel, int level)
} else {
pd->config[MRDMODE] &= ~irq_mask;
}
- cmd646_update_irq(d);
+ cmd646_update_dma_interrupts(pd);
+ cmd646_update_irq(pd);
}
static void cmd646_reset(void *opaque)
@@ -259,6 +297,34 @@ static void cmd646_reset(void *opaque)
}
}
+static uint32_t cmd646_pci_config_read(PCIDevice *d,
+ uint32_t address, int len)
+{
+ return pci_default_read_config(d, address, len);
+}
+
+static void cmd646_pci_config_write(PCIDevice *d, uint32_t addr, uint32_t val,
+ int l)
+{
+ uint32_t i;
+
+ pci_default_write_config(d, addr, val, l);
+
+ for (i = addr; i < addr + l; i++) {
+ switch (i) {
+ case CFR:
+ case ARTTIM23:
+ cmd646_update_udma_interrupts(d);
+ break;
+ case MRDMODE:
+ cmd646_update_dma_interrupts(d);
+ break;
+ }
+ }
+
+ cmd646_update_irq(d);
+}
+
/* CMD646 PCI IDE controller */
static int pci_cmd646_ide_initfn(PCIDevice *dev)
{
@@ -269,12 +335,20 @@ static int pci_cmd646_ide_initfn(PCIDevice *dev)
pci_conf[PCI_CLASS_PROG] = 0x8f;
- pci_conf[0x51] = 0x04; // enable IDE0
+ pci_conf[CNTRL] = CNTRL_EN_CH0; // enable IDE0
if (d->secondary) {
/* XXX: if not enabled, really disable the seconday IDE controller */
- pci_conf[0x51] |= 0x08; /* enable IDE1 */
+ pci_conf[CNTRL] |= CNTRL_EN_CH1; /* enable IDE1 */
}
+ /* Set write-to-clear interrupt bits */
+ dev->wmask[CFR] = 0x0;
+ dev->w1cmask[CFR] = CFR_INTR_CH0;
+ dev->wmask[ARTTIM23] = 0x0;
+ dev->w1cmask[ARTTIM23] = ARTTIM23_INTR_CH1;
+ dev->wmask[MRDMODE] = 0x0;
+ dev->w1cmask[MRDMODE] = MRDMODE_INTR_CH0 | MRDMODE_INTR_CH1;
+
setup_cmd646_bar(d, 0);
setup_cmd646_bar(d, 1);
pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &d->cmd646_bar[0].data);
@@ -347,6 +421,8 @@ static void cmd646_ide_class_init(ObjectClass *klass, void *data)
k->device_id = PCI_DEVICE_ID_CMD_646;
k->revision = 0x07;
k->class_id = PCI_CLASS_STORAGE_IDE;
+ k->config_read = cmd646_pci_config_read;
+ k->config_write = cmd646_pci_config_write;
dc->props = cmd646_ide_properties;
}
diff --git a/hw/ide/core.c b/hw/ide/core.c
index db191a6c3e..b48127f921 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -420,6 +420,7 @@ BlockDriverAIOCB *ide_issue_trim(BlockDriverState *bs,
static inline void ide_abort_command(IDEState *s)
{
+ ide_transfer_stop(s);
s->status = READY_STAT | ERR_STAT;
s->error = ABRT_ERR;
}
@@ -434,7 +435,16 @@ void ide_transfer_start(IDEState *s, uint8_t *buf, int size,
if (!(s->status & ERR_STAT)) {
s->status |= DRQ_STAT;
}
- s->bus->dma->ops->start_transfer(s->bus->dma);
+ if (s->bus->dma->ops->start_transfer) {
+ s->bus->dma->ops->start_transfer(s->bus->dma);
+ }
+}
+
+static void ide_cmd_done(IDEState *s)
+{
+ if (s->bus->dma->ops->cmd_done) {
+ s->bus->dma->ops->cmd_done(s->bus->dma);
+ }
}
void ide_transfer_stop(IDEState *s)
@@ -443,6 +453,7 @@ void ide_transfer_stop(IDEState *s)
s->data_ptr = s->io_buffer;
s->data_end = s->io_buffer;
s->status &= ~DRQ_STAT;
+ ide_cmd_done(s);
}
int64_t ide_get_sector(IDEState *s)
@@ -521,8 +532,8 @@ static void ide_sector_read_cb(void *opaque, int ret)
bdrv_acct_done(s->bs, &s->acct);
if (ret != 0) {
- if (ide_handle_rw_error(s, -ret, BM_STATUS_PIO_RETRY |
- BM_STATUS_RETRY_READ)) {
+ if (ide_handle_rw_error(s, -ret, IDE_RETRY_PIO |
+ IDE_RETRY_READ)) {
return;
}
}
@@ -585,39 +596,32 @@ static void dma_buf_commit(IDEState *s)
qemu_sglist_destroy(&s->sg);
}
-static void ide_async_cmd_done(IDEState *s)
-{
- if (s->bus->dma->ops->async_cmd_done) {
- s->bus->dma->ops->async_cmd_done(s->bus->dma);
- }
-}
-
-void ide_set_inactive(IDEState *s)
+void ide_set_inactive(IDEState *s, bool more)
{
s->bus->dma->aiocb = NULL;
- s->bus->dma->ops->set_inactive(s->bus->dma);
- ide_async_cmd_done(s);
+ if (s->bus->dma->ops->set_inactive) {
+ s->bus->dma->ops->set_inactive(s->bus->dma, more);
+ }
+ ide_cmd_done(s);
}
void ide_dma_error(IDEState *s)
{
- ide_transfer_stop(s);
- s->error = ABRT_ERR;
- s->status = READY_STAT | ERR_STAT;
- ide_set_inactive(s);
+ ide_abort_command(s);
+ ide_set_inactive(s, false);
ide_set_irq(s->bus);
}
static int ide_handle_rw_error(IDEState *s, int error, int op)
{
- bool is_read = (op & BM_STATUS_RETRY_READ) != 0;
+ bool is_read = (op & IDE_RETRY_READ) != 0;
BlockErrorAction action = bdrv_get_error_action(s->bs, is_read, error);
if (action == BLOCK_ERROR_ACTION_STOP) {
s->bus->dma->ops->set_unit(s->bus->dma, s->unit);
s->bus->error_status = op;
} else if (action == BLOCK_ERROR_ACTION_REPORT) {
- if (op & BM_STATUS_DMA_RETRY) {
+ if (op & IDE_RETRY_DMA) {
dma_buf_commit(s);
ide_dma_error(s);
} else {
@@ -636,12 +640,12 @@ void ide_dma_cb(void *opaque, int ret)
bool stay_active = false;
if (ret < 0) {
- int op = BM_STATUS_DMA_RETRY;
+ int op = IDE_RETRY_DMA;
if (s->dma_cmd == IDE_DMA_READ)
- op |= BM_STATUS_RETRY_READ;
+ op |= IDE_RETRY_READ;
else if (s->dma_cmd == IDE_DMA_TRIM)
- op |= BM_STATUS_RETRY_TRIM;
+ op |= IDE_RETRY_TRIM;
if (ide_handle_rw_error(s, -ret, op)) {
return;
@@ -688,7 +692,8 @@ void ide_dma_cb(void *opaque, int ret)
sector_num, n, s->dma_cmd);
#endif
- if (!ide_sect_range_ok(s, sector_num, n)) {
+ if ((s->dma_cmd == IDE_DMA_READ || s->dma_cmd == IDE_DMA_WRITE) &&
+ !ide_sect_range_ok(s, sector_num, n)) {
dma_buf_commit(s);
ide_dma_error(s);
return;
@@ -715,10 +720,7 @@ eot:
if (s->dma_cmd == IDE_DMA_READ || s->dma_cmd == IDE_DMA_WRITE) {
bdrv_acct_done(s->bs, &s->acct);
}
- ide_set_inactive(s);
- if (stay_active) {
- s->bus->dma->ops->add_status(s->bus->dma, BM_STATUS_DMAING);
- }
+ ide_set_inactive(s, stay_active);
}
static void ide_sector_start_dma(IDEState *s, enum ide_dma_cmd dma_cmd)
@@ -741,7 +743,14 @@ static void ide_sector_start_dma(IDEState *s, enum ide_dma_cmd dma_cmd)
break;
}
- s->bus->dma->ops->start_dma(s->bus->dma, s, ide_dma_cb);
+ ide_start_dma(s, ide_dma_cb);
+}
+
+void ide_start_dma(IDEState *s, BlockDriverCompletionFunc *cb)
+{
+ if (s->bus->dma->ops->start_dma) {
+ s->bus->dma->ops->start_dma(s->bus->dma, s, cb);
+ }
}
static void ide_sector_write_timer_cb(void *opaque)
@@ -761,7 +770,7 @@ static void ide_sector_write_cb(void *opaque, int ret)
s->status &= ~BUSY_STAT;
if (ret != 0) {
- if (ide_handle_rw_error(s, -ret, BM_STATUS_PIO_RETRY)) {
+ if (ide_handle_rw_error(s, -ret, IDE_RETRY_PIO)) {
return;
}
}
@@ -831,16 +840,20 @@ static void ide_flush_cb(void *opaque, int ret)
{
IDEState *s = opaque;
+ s->pio_aiocb = NULL;
+
if (ret < 0) {
/* XXX: What sector number to set here? */
- if (ide_handle_rw_error(s, -ret, BM_STATUS_RETRY_FLUSH)) {
+ if (ide_handle_rw_error(s, -ret, IDE_RETRY_FLUSH)) {
return;
}
}
- bdrv_acct_done(s->bs, &s->acct);
+ if (s->bs) {
+ bdrv_acct_done(s->bs, &s->acct);
+ }
s->status = READY_STAT | SEEK_STAT;
- ide_async_cmd_done(s);
+ ide_cmd_done(s);
ide_set_irq(s->bus);
}
@@ -853,7 +866,7 @@ void ide_flush_cache(IDEState *s)
s->status |= BUSY_STAT;
bdrv_acct_start(s->bs, &s->acct, 0, BDRV_ACCT_FLUSH);
- bdrv_aio_flush(s->bs, ide_flush_cb, s);
+ s->pio_aiocb = bdrv_aio_flush(s->bs, ide_flush_cb, s);
}
static void ide_cfata_metadata_inquiry(IDEState *s)
@@ -1764,6 +1777,7 @@ void ide_exec_cmd(IDEBus *bus, uint32_t val)
s->status |= SEEK_STAT;
}
+ ide_cmd_done(s);
ide_set_irq(s->bus);
}
}
@@ -2086,7 +2100,9 @@ void ide_bus_reset(IDEBus *bus)
}
/* reset dma provider too */
- bus->dma->ops->reset(bus->dma);
+ if (bus->dma->ops->reset) {
+ bus->dma->ops->reset(bus->dma);
+ }
}
static bool ide_cd_is_tray_open(void *opaque)
@@ -2196,16 +2212,6 @@ static void ide_init1(IDEBus *bus, int unit)
ide_sector_write_timer_cb, s);
}
-static void ide_nop_start(IDEDMA *dma, IDEState *s,
- BlockDriverCompletionFunc *cb)
-{
-}
-
-static int ide_nop(IDEDMA *dma)
-{
- return 0;
-}
-
static int ide_nop_int(IDEDMA *dma, int x)
{
return 0;
@@ -2216,15 +2222,10 @@ static void ide_nop_restart(void *opaque, int x, RunState y)
}
static const IDEDMAOps ide_dma_nop_ops = {
- .start_dma = ide_nop_start,
- .start_transfer = ide_nop,
.prepare_buf = ide_nop_int,
.rw_buf = ide_nop_int,
.set_unit = ide_nop_int,
- .add_status = ide_nop_int,
- .set_inactive = ide_nop,
.restart_cb = ide_nop_restart,
- .reset = ide_nop,
};
static IDEDMA ide_dma_nop = {
@@ -2341,7 +2342,7 @@ static bool ide_drive_pio_state_needed(void *opaque)
IDEState *s = opaque;
return ((s->status & DRQ_STAT) != 0)
- || (s->bus->error_status & BM_STATUS_PIO_RETRY);
+ || (s->bus->error_status & IDE_RETRY_PIO);
}
static bool ide_tray_state_needed(void *opaque)
diff --git a/hw/ide/internal.h b/hw/ide/internal.h
index 0567a522f5..5c19f79437 100644
--- a/hw/ide/internal.h
+++ b/hw/ide/internal.h
@@ -320,8 +320,9 @@ typedef enum { IDE_HD, IDE_CD, IDE_CFATA } IDEDriveKind;
typedef void EndTransferFunc(IDEState *);
typedef void DMAStartFunc(IDEDMA *, IDEState *, BlockDriverCompletionFunc *);
-typedef int DMAFunc(IDEDMA *);
+typedef void DMAVoidFunc(IDEDMA *);
typedef int DMAIntFunc(IDEDMA *, int);
+typedef void DMAStopFunc(IDEDMA *, bool);
typedef void DMARestartFunc(void *, int, RunState);
struct unreported_events {
@@ -427,15 +428,14 @@ struct IDEState {
struct IDEDMAOps {
DMAStartFunc *start_dma;
- DMAFunc *start_transfer;
+ DMAVoidFunc *start_transfer;
DMAIntFunc *prepare_buf;
DMAIntFunc *rw_buf;
DMAIntFunc *set_unit;
- DMAIntFunc *add_status;
- DMAFunc *set_inactive;
- DMAFunc *async_cmd_done;
+ DMAStopFunc *set_inactive;
+ DMAVoidFunc *cmd_done;
DMARestartFunc *restart_cb;
- DMAFunc *reset;
+ DMAVoidFunc *reset;
};
struct IDEDMA {
@@ -484,23 +484,12 @@ struct IDEDevice {
uint64_t wwn;
};
-#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
+/* These are used for the error_status field of IDEBus */
+#define IDE_RETRY_DMA 0x08
+#define IDE_RETRY_PIO 0x10
+#define IDE_RETRY_READ 0x20
+#define IDE_RETRY_FLUSH 0x40
+#define IDE_RETRY_TRIM 0x80
static inline IDEState *idebus_active_if(IDEBus *bus)
{
@@ -532,6 +521,7 @@ void ide_bus_reset(IDEBus *bus);
int64_t ide_get_sector(IDEState *s);
void ide_set_sector(IDEState *s, int64_t sector_num);
+void ide_start_dma(IDEState *s, BlockDriverCompletionFunc *cb);
void ide_dma_error(IDEState *s);
void ide_atapi_cmd_ok(IDEState *s);
@@ -564,7 +554,7 @@ void ide_flush_cache(IDEState *s);
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);
+void ide_set_inactive(IDEState *s, bool more);
BlockDriverAIOCB *ide_issue_trim(BlockDriverState *bs,
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
BlockDriverCompletionFunc *cb, void *opaque);
diff --git a/hw/ide/macio.c b/hw/ide/macio.c
index c14a1ddddb..b0c0d400d9 100644
--- a/hw/ide/macio.c
+++ b/hw/ide/macio.c
@@ -545,11 +545,6 @@ static void macio_ide_reset(DeviceState *dev)
ide_bus_reset(&d->bus);
}
-static int ide_nop(IDEDMA *dma)
-{
- return 0;
-}
-
static int ide_nop_int(IDEDMA *dma, int x)
{
return 0;
@@ -571,14 +566,10 @@ static void ide_dbdma_start(IDEDMA *dma, IDEState *s,
static const IDEDMAOps dbdma_ops = {
.start_dma = ide_dbdma_start,
- .start_transfer = ide_nop,
.prepare_buf = ide_nop_int,
.rw_buf = ide_nop_int,
.set_unit = ide_nop_int,
- .add_status = ide_nop_int,
- .set_inactive = ide_nop,
.restart_cb = ide_nop_restart,
- .reset = ide_nop,
};
static void macio_ide_realizefn(DeviceState *dev, Error **errp)
diff --git a/hw/ide/pci.c b/hw/ide/pci.c
index 6257a21ed2..2397f355cc 100644
--- a/hw/ide/pci.c
+++ b/hw/ide/pci.c
@@ -33,6 +33,10 @@
#define BMDMA_PAGE_SIZE 4096
+#define BM_MIGRATION_COMPAT_STATUS_BITS \
+ (IDE_RETRY_DMA | IDE_RETRY_PIO | \
+ IDE_RETRY_READ | IDE_RETRY_FLUSH)
+
static void bmdma_start_dma(IDEDMA *dma, IDEState *s,
BlockDriverCompletionFunc *dma_cb)
{
@@ -152,23 +156,17 @@ static int bmdma_set_unit(IDEDMA *dma, int unit)
return 0;
}
-static int bmdma_add_status(IDEDMA *dma, int status)
-{
- BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma);
- bm->status |= status;
-
- return 0;
-}
-
-static int bmdma_set_inactive(IDEDMA *dma)
+static void bmdma_set_inactive(IDEDMA *dma, bool more)
{
BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma);
- bm->status &= ~BM_STATUS_DMAING;
bm->dma_cb = NULL;
bm->unit = -1;
-
- return 0;
+ if (more) {
+ bm->status |= BM_STATUS_DMAING;
+ } else {
+ bm->status &= ~BM_STATUS_DMAING;
+ }
}
static void bmdma_restart_dma(BMDMAState *bm, enum ide_dma_cmd dma_cmd)
@@ -200,7 +198,7 @@ static void bmdma_restart_bh(void *opaque)
return;
}
- is_read = (bus->error_status & BM_STATUS_RETRY_READ) != 0;
+ is_read = (bus->error_status & IDE_RETRY_READ) != 0;
/* The error status must be cleared before resubmitting the request: The
* request may fail again, and this case can only be distinguished if the
@@ -208,19 +206,19 @@ static void bmdma_restart_bh(void *opaque)
error_status = bus->error_status;
bus->error_status = 0;
- if (error_status & BM_STATUS_DMA_RETRY) {
- if (error_status & BM_STATUS_RETRY_TRIM) {
+ if (error_status & IDE_RETRY_DMA) {
+ if (error_status & IDE_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) {
+ } else if (error_status & IDE_RETRY_PIO) {
if (is_read) {
ide_sector_read(bmdma_active_if(bm));
} else {
ide_sector_write(bmdma_active_if(bm));
}
- } else if (error_status & BM_STATUS_RETRY_FLUSH) {
+ } else if (error_status & IDE_RETRY_FLUSH) {
ide_flush_cache(bmdma_active_if(bm));
}
}
@@ -243,11 +241,11 @@ static void bmdma_cancel(BMDMAState *bm)
{
if (bm->status & BM_STATUS_DMAING) {
/* cancel DMA request */
- bmdma_set_inactive(&bm->dma);
+ bmdma_set_inactive(&bm->dma, false);
}
}
-static int bmdma_reset(IDEDMA *dma)
+static void bmdma_reset(IDEDMA *dma)
{
BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma);
@@ -264,13 +262,6 @@ static int bmdma_reset(IDEDMA *dma)
bm->cur_prd_len = 0;
bm->sector_num = 0;
bm->nsector = 0;
-
- return 0;
-}
-
-static int bmdma_start_transfer(IDEDMA *dma)
-{
- return 0;
}
static void bmdma_irq(void *opaque, int n, int level)
@@ -504,11 +495,9 @@ void pci_ide_create_devs(PCIDevice *dev, DriveInfo **hd_table)
static const struct IDEDMAOps bmdma_ops = {
.start_dma = bmdma_start_dma,
- .start_transfer = bmdma_start_transfer,
.prepare_buf = bmdma_prepare_buf,
.rw_buf = bmdma_rw_buf,
.set_unit = bmdma_set_unit,
- .add_status = bmdma_add_status,
.set_inactive = bmdma_set_inactive,
.restart_cb = bmdma_restart_cb,
.reset = bmdma_reset,
diff --git a/hw/ide/pci.h b/hw/ide/pci.h
index 2428275c8d..517711f913 100644
--- a/hw/ide/pci.h
+++ b/hw/ide/pci.h
@@ -3,6 +3,13 @@
#include <hw/ide/internal.h>
+#define BM_STATUS_DMAING 0x01
+#define BM_STATUS_ERROR 0x02
+#define BM_STATUS_INT 0x04
+
+#define BM_CMD_START 0x01
+#define BM_CMD_READ 0x08
+
typedef struct BMDMAState {
IDEDMA dma;
uint8_t cmd;
diff --git a/qemu-char.c b/qemu-char.c
index 3dcc39adf8..d4f327ab6d 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -975,7 +975,7 @@ static CharDriverState *qemu_chr_open_fd(int fd_in, int fd_out)
s = g_malloc0(sizeof(FDCharDriver));
s->fd_in = io_channel_from_fd(fd_in);
s->fd_out = io_channel_from_fd(fd_out);
- fcntl(fd_out, F_SETFL, O_NONBLOCK);
+ qemu_set_nonblock(fd_out);
s->chr = chr;
chr->opaque = s;
chr->chr_add_watch = fd_chr_add_watch;
@@ -1062,7 +1062,7 @@ static CharDriverState *qemu_chr_open_stdio(ChardevStdio *opts)
}
old_fd0_flags = fcntl(0, F_GETFL);
tcgetattr (0, &oldtty);
- fcntl(0, F_SETFL, O_NONBLOCK);
+ qemu_set_nonblock(0);
atexit(term_exit);
chr = qemu_chr_open_fd(0, 1);
diff --git a/qemu-options.hx b/qemu-options.hx
index 9d743e53da..c573dd8893 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -427,7 +427,7 @@ DEF("drive", HAS_ARG, QEMU_OPTION_drive,
" [,serial=s][,addr=A][,rerror=ignore|stop|report]\n"
" [,werror=ignore|stop|report|enospc][,id=name][,aio=threads|native]\n"
" [,readonly=on|off][,copy-on-read=on|off]\n"
- " [,detect-zeroes=on|off|unmap]\n"
+ " [,discard=ignore|unmap][,detect-zeroes=on|off|unmap]\n"
" [[,bps=b]|[[,bps_rd=r][,bps_wr=w]]]\n"
" [[,iops=i]|[[,iops_rd=r][,iops_wr=w]]]\n"
" [[,bps_max=bm]|[[,bps_rd_max=rm][,bps_wr_max=wm]]]\n"
diff --git a/qga/channel-posix.c b/qga/channel-posix.c
index e65dda3822..8aad4fee9f 100644
--- a/qga/channel-posix.c
+++ b/qga/channel-posix.c
@@ -42,7 +42,7 @@ static gboolean ga_channel_listen_accept(GIOChannel *channel,
g_warning("error converting fd to gsocket: %s", strerror(errno));
goto out;
}
- fcntl(client_fd, F_SETFL, O_NONBLOCK);
+ qemu_set_nonblock(client_fd);
ret = ga_channel_client_add(c, client_fd);
if (ret) {
g_warning("error setting up connection");
diff --git a/tests/ide-test.c b/tests/ide-test.c
index 4a0d97f197..ffce6ed669 100644
--- a/tests/ide-test.c
+++ b/tests/ide-test.c
@@ -106,6 +106,7 @@ static QPCIBus *pcibus = NULL;
static QGuestAllocator *guest_malloc;
static char tmp_path[] = "/tmp/qtest.XXXXXX";
+static char debug_path[] = "/tmp/qtest-blkdebug.XXXXXX";
static void ide_test_start(const char *cmdline_fmt, ...)
{
@@ -119,6 +120,8 @@ static void ide_test_start(const char *cmdline_fmt, ...)
qtest_start(cmdline);
qtest_irq_intercept_in(global_qtest, "ioapic");
guest_malloc = pc_alloc_init();
+
+ g_free(cmdline);
}
static void ide_test_quit(void)
@@ -145,7 +148,7 @@ static QPCIDevice *get_pci_device(uint16_t *bmdma_base)
g_assert(device_id == PCI_DEVICE_ID_INTEL_82371SB_1);
/* Map bmdma BAR */
- *bmdma_base = (uint16_t)(uintptr_t) qpci_iomap(dev, 4);
+ *bmdma_base = (uint16_t)(uintptr_t) qpci_iomap(dev, 4, NULL);
qpci_device_enable(dev);
@@ -489,6 +492,91 @@ static void test_flush(void)
ide_test_quit();
}
+static void prepare_blkdebug_script(const char *debug_fn, const char *event)
+{
+ FILE *debug_file = fopen(debug_fn, "w");
+ int ret;
+
+ fprintf(debug_file, "[inject-error]\n");
+ fprintf(debug_file, "event = \"%s\"\n", event);
+ fprintf(debug_file, "errno = \"5\"\n");
+ fprintf(debug_file, "state = \"1\"\n");
+ fprintf(debug_file, "immediately = \"off\"\n");
+ fprintf(debug_file, "once = \"on\"\n");
+
+ fprintf(debug_file, "[set-state]\n");
+ fprintf(debug_file, "event = \"%s\"\n", event);
+ fprintf(debug_file, "new_state = \"2\"\n");
+ fflush(debug_file);
+ g_assert(!ferror(debug_file));
+
+ ret = fclose(debug_file);
+ g_assert(ret == 0);
+}
+
+static void test_retry_flush(void)
+{
+ uint8_t data;
+ const char *s;
+ QDict *response;
+
+ prepare_blkdebug_script(debug_path, "flush_to_disk");
+
+ ide_test_start(
+ "-vnc none "
+ "-drive file=blkdebug:%s:%s,if=ide,cache=writeback,rerror=stop,werror=stop",
+ debug_path, tmp_path);
+
+ /* FLUSH CACHE command on device 0*/
+ outb(IDE_BASE + reg_device, 0);
+ outb(IDE_BASE + reg_command, CMD_FLUSH_CACHE);
+
+ /* Check status while request is in flight*/
+ data = inb(IDE_BASE + reg_status);
+ assert_bit_set(data, BSY | DRDY);
+ assert_bit_clear(data, DF | ERR | DRQ);
+
+ for (;; response = NULL) {
+ response = qmp_receive();
+ if ((qdict_haskey(response, "event")) &&
+ (strcmp(qdict_get_str(response, "event"), "STOP") == 0)) {
+ QDECREF(response);
+ break;
+ }
+ QDECREF(response);
+ }
+
+ /* Complete the command */
+ s = "{'execute':'cont' }";
+ qmp_discard_response(s);
+
+ /* Check registers */
+ data = inb(IDE_BASE + reg_device);
+ g_assert_cmpint(data & DEV, ==, 0);
+
+ do {
+ data = inb(IDE_BASE + reg_status);
+ } while (data & BSY);
+
+ assert_bit_set(data, DRDY);
+ assert_bit_clear(data, BSY | DF | ERR | DRQ);
+
+ ide_test_quit();
+}
+
+static void test_flush_nodev(void)
+{
+ ide_test_start("");
+
+ /* FLUSH CACHE command on device 0*/
+ outb(IDE_BASE + reg_device, 0);
+ outb(IDE_BASE + reg_command, CMD_FLUSH_CACHE);
+
+ /* Just testing that qemu doesn't crash... */
+
+ ide_test_quit();
+}
+
int main(int argc, char **argv)
{
const char *arch = qtest_get_arch();
@@ -501,6 +589,11 @@ int main(int argc, char **argv)
return 0;
}
+ /* Create temporary blkdebug instructions */
+ fd = mkstemp(debug_path);
+ g_assert(fd >= 0);
+ close(fd);
+
/* Create a temporary raw image */
fd = mkstemp(tmp_path);
g_assert(fd >= 0);
@@ -521,11 +614,15 @@ int main(int argc, char **argv)
qtest_add_func("/ide/bmdma/teardown", test_bmdma_teardown);
qtest_add_func("/ide/flush", test_flush);
+ qtest_add_func("/ide/flush_nodev", test_flush_nodev);
+
+ qtest_add_func("/ide/retry/flush", test_retry_flush);
ret = g_test_run();
/* Cleanup */
unlink(tmp_path);
+ unlink(debug_path);
return ret;
}
diff --git a/tests/image-fuzzer/qcow2/__init__.py b/tests/image-fuzzer/qcow2/__init__.py
new file mode 100644
index 0000000000..e2ebe19311
--- /dev/null
+++ b/tests/image-fuzzer/qcow2/__init__.py
@@ -0,0 +1 @@
+from layout import create_image
diff --git a/tests/image-fuzzer/qcow2/fuzz.py b/tests/image-fuzzer/qcow2/fuzz.py
new file mode 100644
index 0000000000..57527f9b4a
--- /dev/null
+++ b/tests/image-fuzzer/qcow2/fuzz.py
@@ -0,0 +1,355 @@
+# Fuzzing functions for qcow2 fields
+#
+# Copyright (C) 2014 Maria Kustova <maria.k@catit.be>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import random
+
+
+UINT8 = 0xff
+UINT32 = 0xffffffff
+UINT64 = 0xffffffffffffffff
+# Most significant bit orders
+UINT32_M = 31
+UINT64_M = 63
+# Fuzz vectors
+UINT8_V = [0, 0x10, UINT8/4, UINT8/2 - 1, UINT8/2, UINT8/2 + 1, UINT8 - 1,
+ UINT8]
+UINT32_V = [0, 0x100, 0x1000, 0x10000, 0x100000, UINT32/4, UINT32/2 - 1,
+ UINT32/2, UINT32/2 + 1, UINT32 - 1, UINT32]
+UINT64_V = UINT32_V + [0x1000000, 0x10000000, 0x100000000, UINT64/4,
+ UINT64/2 - 1, UINT64/2, UINT64/2 + 1, UINT64 - 1,
+ UINT64]
+STRING_V = ['%s%p%x%d', '.1024d', '%.2049d', '%p%p%p%p', '%x%x%x%x',
+ '%d%d%d%d', '%s%s%s%s', '%99999999999s', '%08x', '%%20d', '%%20n',
+ '%%20x', '%%20s', '%s%s%s%s%s%s%s%s%s%s', '%p%p%p%p%p%p%p%p%p%p',
+ '%#0123456x%08x%x%s%p%d%n%o%u%c%h%l%q%j%z%Z%t%i%e%g%f%a%C%S%08x%%',
+ '%s x 129', '%x x 257']
+
+
+def random_from_intervals(intervals):
+ """Select a random integer number from the list of specified intervals.
+
+ Each interval is a tuple of lower and upper limits of the interval. The
+ limits are included. Intervals in a list should not overlap.
+ """
+ total = reduce(lambda x, y: x + y[1] - y[0] + 1, intervals, 0)
+ r = random.randint(0, total - 1) + intervals[0][0]
+ for x in zip(intervals, intervals[1:]):
+ r = r + (r > x[0][1]) * (x[1][0] - x[0][1] - 1)
+ return r
+
+
+def random_bits(bit_ranges):
+ """Generate random binary mask with ones in the specified bit ranges.
+
+ Each bit_ranges is a list of tuples of lower and upper limits of bit
+ positions will be fuzzed. The limits are included. Random amount of bits
+ in range limits will be set to ones. The mask is returned in decimal
+ integer format.
+ """
+ bit_numbers = []
+ # Select random amount of random positions in bit_ranges
+ for rng in bit_ranges:
+ bit_numbers += random.sample(range(rng[0], rng[1] + 1),
+ random.randint(0, rng[1] - rng[0] + 1))
+ val = 0
+ # Set bits on selected positions to ones
+ for bit in bit_numbers:
+ val |= 1 << bit
+ return val
+
+
+def truncate_string(strings, length):
+ """Return strings truncated to specified length."""
+ if type(strings) == list:
+ return [s[:length] for s in strings]
+ else:
+ return strings[:length]
+
+
+def validator(current, pick, choices):
+ """Return a value not equal to the current selected by the pick
+ function from choices.
+ """
+ while True:
+ val = pick(choices)
+ if not val == current:
+ return val
+
+
+def int_validator(current, intervals):
+ """Return a random value from intervals not equal to the current.
+
+ This function is useful for selection from valid values except current one.
+ """
+ return validator(current, random_from_intervals, intervals)
+
+
+def bit_validator(current, bit_ranges):
+ """Return a random bit mask not equal to the current.
+
+ This function is useful for selection from valid values except current one.
+ """
+ return validator(current, random_bits, bit_ranges)
+
+
+def string_validator(current, strings):
+ """Return a random string value from the list not equal to the current.
+
+ This function is useful for selection from valid values except current one.
+ """
+ return validator(current, random.choice, strings)
+
+
+def selector(current, constraints, validate=int_validator):
+ """Select one value from all defined by constraints.
+
+ Each constraint produces one random value satisfying to it. The function
+ randomly selects one value satisfying at least one constraint (depending on
+ constraints overlaps).
+ """
+ def iter_validate(c):
+ """Apply validate() only to constraints represented as lists.
+
+ This auxiliary function replaces short circuit conditions not supported
+ in Python 2.4
+ """
+ if type(c) == list:
+ return validate(current, c)
+ else:
+ return c
+
+ fuzz_values = [iter_validate(c) for c in constraints]
+ # Remove current for cases it's implicitly specified in constraints
+ # Duplicate validator functionality to prevent decreasing of probability
+ # to get one of allowable values
+ # TODO: remove validators after implementation of intelligent selection
+ # of fields will be fuzzed
+ try:
+ fuzz_values.remove(current)
+ except ValueError:
+ pass
+ return random.choice(fuzz_values)
+
+
+def magic(current):
+ """Fuzz magic header field.
+
+ The function just returns the current magic value and provides uniformity
+ of calls for all fuzzing functions.
+ """
+ return current
+
+
+def version(current):
+ """Fuzz version header field."""
+ constraints = UINT32_V + [
+ [(2, 3)], # correct values
+ [(0, 1), (4, UINT32)]
+ ]
+ return selector(current, constraints)
+
+
+def backing_file_offset(current):
+ """Fuzz backing file offset header field."""
+ constraints = UINT64_V
+ return selector(current, constraints)
+
+
+def backing_file_size(current):
+ """Fuzz backing file size header field."""
+ constraints = UINT32_V
+ return selector(current, constraints)
+
+
+def cluster_bits(current):
+ """Fuzz cluster bits header field."""
+ constraints = UINT32_V + [
+ [(9, 20)], # correct values
+ [(0, 9), (20, UINT32)]
+ ]
+ return selector(current, constraints)
+
+
+def size(current):
+ """Fuzz image size header field."""
+ constraints = UINT64_V
+ return selector(current, constraints)
+
+
+def crypt_method(current):
+ """Fuzz crypt method header field."""
+ constraints = UINT32_V + [
+ 1,
+ [(2, UINT32)]
+ ]
+ return selector(current, constraints)
+
+
+def l1_size(current):
+ """Fuzz L1 table size header field."""
+ constraints = UINT32_V
+ return selector(current, constraints)
+
+
+def l1_table_offset(current):
+ """Fuzz L1 table offset header field."""
+ constraints = UINT64_V
+ return selector(current, constraints)
+
+
+def refcount_table_offset(current):
+ """Fuzz refcount table offset header field."""
+ constraints = UINT64_V
+ return selector(current, constraints)
+
+
+def refcount_table_clusters(current):
+ """Fuzz refcount table clusters header field."""
+ constraints = UINT32_V
+ return selector(current, constraints)
+
+
+def nb_snapshots(current):
+ """Fuzz number of snapshots header field."""
+ constraints = UINT32_V
+ return selector(current, constraints)
+
+
+def snapshots_offset(current):
+ """Fuzz snapshots offset header field."""
+ constraints = UINT64_V
+ return selector(current, constraints)
+
+
+def incompatible_features(current):
+ """Fuzz incompatible features header field."""
+ constraints = [
+ [(0, 1)], # allowable values
+ [(0, UINT64_M)]
+ ]
+ return selector(current, constraints, bit_validator)
+
+
+def compatible_features(current):
+ """Fuzz compatible features header field."""
+ constraints = [
+ [(0, UINT64_M)]
+ ]
+ return selector(current, constraints, bit_validator)
+
+
+def autoclear_features(current):
+ """Fuzz autoclear features header field."""
+ constraints = [
+ [(0, UINT64_M)]
+ ]
+ return selector(current, constraints, bit_validator)
+
+
+def refcount_order(current):
+ """Fuzz number of refcount order header field."""
+ constraints = UINT32_V
+ return selector(current, constraints)
+
+
+def header_length(current):
+ """Fuzz number of refcount order header field."""
+ constraints = UINT32_V + [
+ 72,
+ 104,
+ [(0, UINT32)]
+ ]
+ return selector(current, constraints)
+
+
+def bf_name(current):
+ """Fuzz the backing file name."""
+ constraints = [
+ truncate_string(STRING_V, len(current))
+ ]
+ return selector(current, constraints, string_validator)
+
+
+def ext_magic(current):
+ """Fuzz magic field of a header extension."""
+ constraints = UINT32_V
+ return selector(current, constraints)
+
+
+def ext_length(current):
+ """Fuzz length field of a header extension."""
+ constraints = UINT32_V
+ return selector(current, constraints)
+
+
+def bf_format(current):
+ """Fuzz backing file format in the corresponding header extension."""
+ constraints = [
+ truncate_string(STRING_V, len(current)),
+ truncate_string(STRING_V, (len(current) + 7) & ~7) # Fuzz padding
+ ]
+ return selector(current, constraints, string_validator)
+
+
+def feature_type(current):
+ """Fuzz feature type field of a feature name table header extension."""
+ constraints = UINT8_V
+ return selector(current, constraints)
+
+
+def feature_bit_number(current):
+ """Fuzz bit number field of a feature name table header extension."""
+ constraints = UINT8_V
+ return selector(current, constraints)
+
+
+def feature_name(current):
+ """Fuzz feature name field of a feature name table header extension."""
+ constraints = [
+ truncate_string(STRING_V, len(current)),
+ truncate_string(STRING_V, 46) # Fuzz padding (field length = 46)
+ ]
+ return selector(current, constraints, string_validator)
+
+
+def l1_entry(current):
+ """Fuzz an entry of the L1 table."""
+ constraints = UINT64_V
+ # Reserved bits are ignored
+ # Added a possibility when only flags are fuzzed
+ offset = 0x7fffffffffffffff & random.choice([selector(current,
+ constraints),
+ current])
+ is_cow = random.randint(0, 1)
+ return offset + (is_cow << UINT64_M)
+
+
+def l2_entry(current):
+ """Fuzz an entry of an L2 table."""
+ constraints = UINT64_V
+ # Reserved bits are ignored
+ # Add a possibility when only flags are fuzzed
+ offset = 0x3ffffffffffffffe & random.choice([selector(current,
+ constraints),
+ current])
+ is_compressed = random.randint(0, 1)
+ is_cow = random.randint(0, 1)
+ is_zero = random.randint(0, 1)
+ value = offset + (is_cow << UINT64_M) + \
+ (is_compressed << UINT64_M - 1) + is_zero
+ return value
diff --git a/tests/image-fuzzer/qcow2/layout.py b/tests/image-fuzzer/qcow2/layout.py
new file mode 100644
index 0000000000..730c771d3c
--- /dev/null
+++ b/tests/image-fuzzer/qcow2/layout.py
@@ -0,0 +1,476 @@
+# Generator of fuzzed qcow2 images
+#
+# Copyright (C) 2014 Maria Kustova <maria.k@catit.be>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import random
+import struct
+import fuzz
+from math import ceil
+from os import urandom
+from itertools import chain
+
+MAX_IMAGE_SIZE = 10 * (1 << 20)
+# Standard sizes
+UINT32_S = 4
+UINT64_S = 8
+
+
+class Field(object):
+
+ """Atomic image element (field).
+
+ The class represents an image field as quadruple of a data format
+ of value necessary for its packing to binary form, an offset from
+ the beginning of the image, a value and a name.
+
+ The field can be iterated as a list [format, offset, value, name].
+ """
+
+ __slots__ = ('fmt', 'offset', 'value', 'name')
+
+ def __init__(self, fmt, offset, val, name):
+ self.fmt = fmt
+ self.offset = offset
+ self.value = val
+ self.name = name
+
+ def __iter__(self):
+ return iter([self.fmt, self.offset, self.value, self.name])
+
+ def __repr__(self):
+ return "Field(fmt='%s', offset=%d, value=%s, name=%s)" % \
+ (self.fmt, self.offset, str(self.value), self.name)
+
+
+class FieldsList(object):
+
+ """List of fields.
+
+ The class allows access to a field in the list by its name.
+ """
+
+ def __init__(self, meta_data=None):
+ if meta_data is None:
+ self.data = []
+ else:
+ self.data = [Field(*f)
+ for f in meta_data]
+
+ def __getitem__(self, name):
+ return [x for x in self.data if x.name == name]
+
+ def __iter__(self):
+ return iter(self.data)
+
+ def __len__(self):
+ return len(self.data)
+
+
+class Image(object):
+
+ """ Qcow2 image object.
+
+ This class allows to create qcow2 images with random valid structures and
+ values, fuzz them via external qcow2.fuzz module and write the result to
+ a file.
+ """
+
+ def __init__(self, backing_file_name=None):
+ """Create a random valid qcow2 image with the correct header and stored
+ backing file name.
+ """
+ cluster_bits, self.image_size = self._size_params()
+ self.cluster_size = 1 << cluster_bits
+ self.header = FieldsList()
+ self.backing_file_name = FieldsList()
+ self.backing_file_format = FieldsList()
+ self.feature_name_table = FieldsList()
+ self.end_of_extension_area = FieldsList()
+ self.l2_tables = FieldsList()
+ self.l1_table = FieldsList()
+ self.ext_offset = 0
+ self.create_header(cluster_bits, backing_file_name)
+ self.set_backing_file_name(backing_file_name)
+ self.data_clusters = self._alloc_data(self.image_size,
+ self.cluster_size)
+ # Percentage of fields will be fuzzed
+ self.bias = random.uniform(0.2, 0.5)
+
+ def __iter__(self):
+ return chain(self.header, self.backing_file_format,
+ self.feature_name_table, self.end_of_extension_area,
+ self.backing_file_name, self.l1_table, self.l2_tables)
+
+ def create_header(self, cluster_bits, backing_file_name=None):
+ """Generate a random valid header."""
+ meta_header = [
+ ['>4s', 0, "QFI\xfb", 'magic'],
+ ['>I', 4, random.randint(2, 3), 'version'],
+ ['>Q', 8, 0, 'backing_file_offset'],
+ ['>I', 16, 0, 'backing_file_size'],
+ ['>I', 20, cluster_bits, 'cluster_bits'],
+ ['>Q', 24, self.image_size, 'size'],
+ ['>I', 32, 0, 'crypt_method'],
+ ['>I', 36, 0, 'l1_size'],
+ ['>Q', 40, 0, 'l1_table_offset'],
+ ['>Q', 48, 0, 'refcount_table_offset'],
+ ['>I', 56, 0, 'refcount_table_clusters'],
+ ['>I', 60, 0, 'nb_snapshots'],
+ ['>Q', 64, 0, 'snapshots_offset'],
+ ['>Q', 72, 0, 'incompatible_features'],
+ ['>Q', 80, 0, 'compatible_features'],
+ ['>Q', 88, 0, 'autoclear_features'],
+ # Only refcount_order = 4 is supported by current (07.2014)
+ # implementation of QEMU
+ ['>I', 96, 4, 'refcount_order'],
+ ['>I', 100, 0, 'header_length']
+ ]
+ self.header = FieldsList(meta_header)
+
+ if self.header['version'][0].value == 2:
+ self.header['header_length'][0].value = 72
+ else:
+ self.header['incompatible_features'][0].value = \
+ random.getrandbits(2)
+ self.header['compatible_features'][0].value = random.getrandbits(1)
+ self.header['header_length'][0].value = 104
+ # Extensions start at the header last field offset and the field size
+ self.ext_offset = struct.calcsize(
+ self.header['header_length'][0].fmt) + \
+ self.header['header_length'][0].offset
+ end_of_extension_area_len = 2 * UINT32_S
+ free_space = self.cluster_size - self.ext_offset - \
+ end_of_extension_area_len
+ # If the backing file name specified and there is enough space for it
+ # in the first cluster, then it's placed in the very end of the first
+ # cluster.
+ if (backing_file_name is not None) and \
+ (free_space >= len(backing_file_name)):
+ self.header['backing_file_size'][0].value = len(backing_file_name)
+ self.header['backing_file_offset'][0].value = \
+ self.cluster_size - len(backing_file_name)
+
+ def set_backing_file_name(self, backing_file_name=None):
+ """Add the name of the backing file at the offset specified
+ in the header.
+ """
+ if (backing_file_name is not None) and \
+ (not self.header['backing_file_offset'][0].value == 0):
+ data_len = len(backing_file_name)
+ data_fmt = '>' + str(data_len) + 's'
+ self.backing_file_name = FieldsList([
+ [data_fmt, self.header['backing_file_offset'][0].value,
+ backing_file_name, 'bf_name']
+ ])
+
+ def set_backing_file_format(self, backing_file_fmt=None):
+ """Generate the header extension for the backing file format."""
+ if backing_file_fmt is not None:
+ # Calculation of the free space available in the first cluster
+ end_of_extension_area_len = 2 * UINT32_S
+ high_border = (self.header['backing_file_offset'][0].value or
+ (self.cluster_size - 1)) - \
+ end_of_extension_area_len
+ free_space = high_border - self.ext_offset
+ ext_size = 2 * UINT32_S + ((len(backing_file_fmt) + 7) & ~7)
+
+ if free_space >= ext_size:
+ ext_data_len = len(backing_file_fmt)
+ ext_data_fmt = '>' + str(ext_data_len) + 's'
+ ext_padding_len = 7 - (ext_data_len - 1) % 8
+ self.backing_file_format = FieldsList([
+ ['>I', self.ext_offset, 0xE2792ACA, 'ext_magic'],
+ ['>I', self.ext_offset + UINT32_S, ext_data_len,
+ 'ext_length'],
+ [ext_data_fmt, self.ext_offset + UINT32_S * 2,
+ backing_file_fmt, 'bf_format']
+ ])
+ self.ext_offset = \
+ struct.calcsize(
+ self.backing_file_format['bf_format'][0].fmt) + \
+ ext_padding_len + \
+ self.backing_file_format['bf_format'][0].offset
+
+ def create_feature_name_table(self):
+ """Generate a random header extension for names of features used in
+ the image.
+ """
+ def gen_feat_ids():
+ """Return random feature type and feature bit."""
+ return (random.randint(0, 2), random.randint(0, 63))
+
+ end_of_extension_area_len = 2 * UINT32_S
+ high_border = (self.header['backing_file_offset'][0].value or
+ (self.cluster_size - 1)) - \
+ end_of_extension_area_len
+ free_space = high_border - self.ext_offset
+ # Sum of sizes of 'magic' and 'length' header extension fields
+ ext_header_len = 2 * UINT32_S
+ fnt_entry_size = 6 * UINT64_S
+ num_fnt_entries = min(10, (free_space - ext_header_len) /
+ fnt_entry_size)
+ if not num_fnt_entries == 0:
+ feature_tables = []
+ feature_ids = []
+ inner_offset = self.ext_offset + ext_header_len
+ feat_name = 'some cool feature'
+ while len(feature_tables) < num_fnt_entries * 3:
+ feat_type, feat_bit = gen_feat_ids()
+ # Remove duplicates
+ while (feat_type, feat_bit) in feature_ids:
+ feat_type, feat_bit = gen_feat_ids()
+ feature_ids.append((feat_type, feat_bit))
+ feat_fmt = '>' + str(len(feat_name)) + 's'
+ feature_tables += [['B', inner_offset,
+ feat_type, 'feature_type'],
+ ['B', inner_offset + 1, feat_bit,
+ 'feature_bit_number'],
+ [feat_fmt, inner_offset + 2,
+ feat_name, 'feature_name']
+ ]
+ inner_offset += fnt_entry_size
+ # No padding for the extension is necessary, because
+ # the extension length is multiple of 8
+ self.feature_name_table = FieldsList([
+ ['>I', self.ext_offset, 0x6803f857, 'ext_magic'],
+ # One feature table contains 3 fields and takes 48 bytes
+ ['>I', self.ext_offset + UINT32_S,
+ len(feature_tables) / 3 * 48, 'ext_length']
+ ] + feature_tables)
+ self.ext_offset = inner_offset
+
+ def set_end_of_extension_area(self):
+ """Generate a mandatory header extension marking end of header
+ extensions.
+ """
+ self.end_of_extension_area = FieldsList([
+ ['>I', self.ext_offset, 0, 'ext_magic'],
+ ['>I', self.ext_offset + UINT32_S, 0, 'ext_length']
+ ])
+
+ def create_l_structures(self):
+ """Generate random valid L1 and L2 tables."""
+ def create_l2_entry(host, guest, l2_cluster):
+ """Generate one L2 entry."""
+ offset = l2_cluster * self.cluster_size
+ l2_size = self.cluster_size / UINT64_S
+ entry_offset = offset + UINT64_S * (guest % l2_size)
+ cluster_descriptor = host * self.cluster_size
+ if not self.header['version'][0].value == 2:
+ cluster_descriptor += random.randint(0, 1)
+ # While snapshots are not supported, bit #63 = 1
+ # Compressed clusters are not supported => bit #62 = 0
+ entry_val = (1 << 63) + cluster_descriptor
+ return ['>Q', entry_offset, entry_val, 'l2_entry']
+
+ def create_l1_entry(l2_cluster, l1_offset, guest):
+ """Generate one L1 entry."""
+ l2_size = self.cluster_size / UINT64_S
+ entry_offset = l1_offset + UINT64_S * (guest / l2_size)
+ # While snapshots are not supported bit #63 = 1
+ entry_val = (1 << 63) + l2_cluster * self.cluster_size
+ return ['>Q', entry_offset, entry_val, 'l1_entry']
+
+ if len(self.data_clusters) == 0:
+ # All metadata for an empty guest image needs 4 clusters:
+ # header, rfc table, rfc block, L1 table.
+ # Header takes cluster #0, other clusters ##1-3 can be used
+ l1_offset = random.randint(1, 3) * self.cluster_size
+ l1 = [['>Q', l1_offset, 0, 'l1_entry']]
+ l2 = []
+ else:
+ meta_data = self._get_metadata()
+ guest_clusters = random.sample(range(self.image_size /
+ self.cluster_size),
+ len(self.data_clusters))
+ # Number of entries in a L1/L2 table
+ l_size = self.cluster_size / UINT64_S
+ # Number of clusters necessary for L1 table
+ l1_size = int(ceil((max(guest_clusters) + 1) / float(l_size**2)))
+ l1_start = self._get_adjacent_clusters(self.data_clusters |
+ meta_data, l1_size)
+ meta_data |= set(range(l1_start, l1_start + l1_size))
+ l1_offset = l1_start * self.cluster_size
+ # Indices of L2 tables
+ l2_ids = []
+ # Host clusters allocated for L2 tables
+ l2_clusters = []
+ # L1 entries
+ l1 = []
+ # L2 entries
+ l2 = []
+ for host, guest in zip(self.data_clusters, guest_clusters):
+ l2_id = guest / l_size
+ if l2_id not in l2_ids:
+ l2_ids.append(l2_id)
+ l2_clusters.append(self._get_adjacent_clusters(
+ self.data_clusters | meta_data | set(l2_clusters),
+ 1))
+ l1.append(create_l1_entry(l2_clusters[-1], l1_offset,
+ guest))
+ l2.append(create_l2_entry(host, guest,
+ l2_clusters[l2_ids.index(l2_id)]))
+ self.l2_tables = FieldsList(l2)
+ self.l1_table = FieldsList(l1)
+ self.header['l1_size'][0].value = int(ceil(UINT64_S * self.image_size /
+ float(self.cluster_size**2)))
+ self.header['l1_table_offset'][0].value = l1_offset
+
+ def fuzz(self, fields_to_fuzz=None):
+ """Fuzz an image by corrupting values of a random subset of its fields.
+
+ Without parameters the method fuzzes an entire image.
+
+ If 'fields_to_fuzz' is specified then only fields in this list will be
+ fuzzed. 'fields_to_fuzz' can contain both individual fields and more
+ general image elements as a header or tables.
+
+ In the first case the field will be fuzzed always.
+ In the second a random subset of fields will be selected and fuzzed.
+ """
+ def coin():
+ """Return boolean value proportional to a portion of fields to be
+ fuzzed.
+ """
+ return random.random() < self.bias
+
+ if fields_to_fuzz is None:
+ for field in self:
+ if coin():
+ field.value = getattr(fuzz, field.name)(field.value)
+ else:
+ for item in fields_to_fuzz:
+ if len(item) == 1:
+ for field in getattr(self, item[0]):
+ if coin():
+ field.value = getattr(fuzz,
+ field.name)(field.value)
+ else:
+ # If fields with the requested name were not generated
+ # getattr(self, item[0])[item[1]] returns an empty list
+ for field in getattr(self, item[0])[item[1]]:
+ field.value = getattr(fuzz, field.name)(field.value)
+
+ def write(self, filename):
+ """Write an entire image to the file."""
+ image_file = open(filename, 'w')
+ for field in self:
+ image_file.seek(field.offset)
+ image_file.write(struct.pack(field.fmt, field.value))
+
+ for cluster in sorted(self.data_clusters):
+ image_file.seek(cluster * self.cluster_size)
+ image_file.write(urandom(self.cluster_size))
+
+ # Align the real image size to the cluster size
+ image_file.seek(0, 2)
+ size = image_file.tell()
+ rounded = (size + self.cluster_size - 1) & ~(self.cluster_size - 1)
+ if rounded > size:
+ image_file.seek(rounded - 1)
+ image_file.write("\0")
+ image_file.close()
+
+ @staticmethod
+ def _size_params():
+ """Generate a random image size aligned to a random correct
+ cluster size.
+ """
+ cluster_bits = random.randrange(9, 21)
+ cluster_size = 1 << cluster_bits
+ img_size = random.randrange(0, MAX_IMAGE_SIZE + 1, cluster_size)
+ return (cluster_bits, img_size)
+
+ @staticmethod
+ def _get_available_clusters(used, number):
+ """Return a set of indices of not allocated clusters.
+
+ 'used' contains indices of currently allocated clusters.
+ All clusters that cannot be allocated between 'used' clusters will have
+ indices appended to the end of 'used'.
+ """
+ append_id = max(used) + 1
+ free = set(range(1, append_id)) - used
+ if len(free) >= number:
+ return set(random.sample(free, number))
+ else:
+ return free | set(range(append_id, append_id + number - len(free)))
+
+ @staticmethod
+ def _get_adjacent_clusters(used, size):
+ """Return an index of the first cluster in the sequence of free ones.
+
+ 'used' contains indices of currently allocated clusters. 'size' is the
+ length of the sequence of free clusters.
+ If the sequence of 'size' is not available between 'used' clusters, its
+ first index will be append to the end of 'used'.
+ """
+ def get_cluster_id(lst, length):
+ """Return the first index of the sequence of the specified length
+ or None if the sequence cannot be inserted in the list.
+ """
+ if len(lst) != 0:
+ pairs = []
+ pair = (lst[0], 1)
+ for i in range(1, len(lst)):
+ if lst[i] == lst[i-1] + 1:
+ pair = (lst[i], pair[1] + 1)
+ else:
+ pairs.append(pair)
+ pair = (lst[i], 1)
+ pairs.append(pair)
+ random.shuffle(pairs)
+ for x, s in pairs:
+ if s >= length:
+ return x - length + 1
+ return None
+
+ append_id = max(used) + 1
+ free = list(set(range(1, append_id)) - used)
+ idx = get_cluster_id(free, size)
+ if idx is None:
+ return append_id
+ else:
+ return idx
+
+ @staticmethod
+ def _alloc_data(img_size, cluster_size):
+ """Return a set of random indices of clusters allocated for guest data.
+ """
+ num_of_cls = img_size/cluster_size
+ return set(random.sample(range(1, num_of_cls + 1),
+ random.randint(0, num_of_cls)))
+
+ def _get_metadata(self):
+ """Return indices of clusters allocated for image metadata."""
+ ids = set()
+ for x in self:
+ ids.add(x.offset/self.cluster_size)
+ return ids
+
+
+def create_image(test_img_path, backing_file_name=None, backing_file_fmt=None,
+ fields_to_fuzz=None):
+ """Create a fuzzed image and write it to the specified file."""
+ image = Image(backing_file_name)
+ image.set_backing_file_format(backing_file_fmt)
+ image.create_feature_name_table()
+ image.set_end_of_extension_area()
+ image.create_l_structures()
+ image.fuzz(fields_to_fuzz)
+ image.write(test_img_path)
+ return image.image_size
diff --git a/tests/image-fuzzer/runner.py b/tests/image-fuzzer/runner.py
new file mode 100755
index 0000000000..58079d331e
--- /dev/null
+++ b/tests/image-fuzzer/runner.py
@@ -0,0 +1,405 @@
+#!/usr/bin/env python
+
+# Tool for running fuzz tests
+#
+# Copyright (C) 2014 Maria Kustova <maria.k@catit.be>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import sys
+import os
+import signal
+import subprocess
+import random
+import shutil
+from itertools import count
+import getopt
+import StringIO
+import resource
+
+try:
+ import json
+except ImportError:
+ try:
+ import simplejson as json
+ except ImportError:
+ print >>sys.stderr, \
+ "Warning: Module for JSON processing is not found.\n" \
+ "'--config' and '--command' options are not supported."
+
+# Backing file sizes in MB
+MAX_BACKING_FILE_SIZE = 10
+MIN_BACKING_FILE_SIZE = 1
+
+
+def multilog(msg, *output):
+ """ Write an object to all of specified file descriptors."""
+ for fd in output:
+ fd.write(msg)
+ fd.flush()
+
+
+def str_signal(sig):
+ """ Convert a numeric value of a system signal to the string one
+ defined by the current operational system.
+ """
+ for k, v in signal.__dict__.items():
+ if v == sig:
+ return k
+
+
+def run_app(fd, q_args):
+ """Start an application with specified arguments and return its exit code
+ or kill signal depending on the result of execution.
+ """
+ devnull = open('/dev/null', 'r+')
+ process = subprocess.Popen(q_args, stdin=devnull,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = process.communicate()
+ fd.write(out)
+ fd.write(err)
+ return process.returncode
+
+
+class TestException(Exception):
+ """Exception for errors risen by TestEnv objects."""
+ pass
+
+
+class TestEnv(object):
+
+ """Test object.
+
+ The class sets up test environment, generates backing and test images
+ and executes application under tests with specified arguments and a test
+ image provided.
+
+ All logs are collected.
+
+ The summary log will contain short descriptions and statuses of tests in
+ a run.
+
+ The test log will include application (e.g. 'qemu-img') logs besides info
+ sent to the summary log.
+ """
+
+ def __init__(self, test_id, seed, work_dir, run_log,
+ cleanup=True, log_all=False):
+ """Set test environment in a specified work directory.
+
+ Path to qemu-img and qemu-io will be retrieved from 'QEMU_IMG' and
+ 'QEMU_IO' environment variables.
+ """
+ if seed is not None:
+ self.seed = seed
+ else:
+ self.seed = str(random.randint(0, sys.maxint))
+ random.seed(self.seed)
+
+ self.init_path = os.getcwd()
+ self.work_dir = work_dir
+ self.current_dir = os.path.join(work_dir, 'test-' + test_id)
+ self.qemu_img = os.environ.get('QEMU_IMG', 'qemu-img')\
+ .strip().split(' ')
+ self.qemu_io = os.environ.get('QEMU_IO', 'qemu-io').strip().split(' ')
+ self.commands = [['qemu-img', 'check', '-f', 'qcow2', '$test_img'],
+ ['qemu-img', 'info', '-f', 'qcow2', '$test_img'],
+ ['qemu-io', '$test_img', '-c', 'read $off $len'],
+ ['qemu-io', '$test_img', '-c', 'write $off $len'],
+ ['qemu-io', '$test_img', '-c',
+ 'aio_read $off $len'],
+ ['qemu-io', '$test_img', '-c',
+ 'aio_write $off $len'],
+ ['qemu-io', '$test_img', '-c', 'flush'],
+ ['qemu-io', '$test_img', '-c',
+ 'discard $off $len'],
+ ['qemu-io', '$test_img', '-c',
+ 'truncate $off']]
+ for fmt in ['raw', 'vmdk', 'vdi', 'cow', 'qcow2', 'file',
+ 'qed', 'vpc']:
+ self.commands.append(
+ ['qemu-img', 'convert', '-f', 'qcow2', '-O', fmt,
+ '$test_img', 'converted_image.' + fmt])
+
+ try:
+ os.makedirs(self.current_dir)
+ except OSError, e:
+ print >>sys.stderr, \
+ "Error: The working directory '%s' cannot be used. Reason: %s"\
+ % (self.work_dir, e[1])
+ raise TestException
+ self.log = open(os.path.join(self.current_dir, "test.log"), "w")
+ self.parent_log = open(run_log, "a")
+ self.failed = False
+ self.cleanup = cleanup
+ self.log_all = log_all
+
+ def _create_backing_file(self):
+ """Create a backing file in the current directory.
+
+ Return a tuple of a backing file name and format.
+
+ Format of a backing file is randomly chosen from all formats supported
+ by 'qemu-img create'.
+ """
+ # All formats supported by the 'qemu-img create' command.
+ backing_file_fmt = random.choice(['raw', 'vmdk', 'vdi', 'cow', 'qcow2',
+ 'file', 'qed', 'vpc'])
+ backing_file_name = 'backing_img.' + backing_file_fmt
+ backing_file_size = random.randint(MIN_BACKING_FILE_SIZE,
+ MAX_BACKING_FILE_SIZE) * (1 << 20)
+ cmd = self.qemu_img + ['create', '-f', backing_file_fmt,
+ backing_file_name, str(backing_file_size)]
+ temp_log = StringIO.StringIO()
+ retcode = run_app(temp_log, cmd)
+ if retcode == 0:
+ temp_log.close()
+ return (backing_file_name, backing_file_fmt)
+ else:
+ multilog("Warning: The %s backing file was not created.\n\n"
+ % backing_file_fmt, sys.stderr, self.log, self.parent_log)
+ self.log.write("Log for the failure:\n" + temp_log.getvalue() +
+ '\n\n')
+ temp_log.close()
+ return (None, None)
+
+ def execute(self, input_commands=None, fuzz_config=None):
+ """ Execute a test.
+
+ The method creates backing and test images, runs test app and analyzes
+ its exit status. If the application was killed by a signal, the test
+ is marked as failed.
+ """
+ if input_commands is None:
+ commands = self.commands
+ else:
+ commands = input_commands
+
+ os.chdir(self.current_dir)
+ backing_file_name, backing_file_fmt = self._create_backing_file()
+ img_size = image_generator.create_image('test.img',
+ backing_file_name,
+ backing_file_fmt,
+ fuzz_config)
+ for item in commands:
+ shutil.copy('test.img', 'copy.img')
+ # 'off' and 'len' are multiple of the sector size
+ sector_size = 512
+ start = random.randrange(0, img_size + 1, sector_size)
+ end = random.randrange(start, img_size + 1, sector_size)
+
+ if item[0] == 'qemu-img':
+ current_cmd = list(self.qemu_img)
+ elif item[0] == 'qemu-io':
+ current_cmd = list(self.qemu_io)
+ else:
+ multilog("Warning: test command '%s' is not defined.\n" \
+ % item[0], sys.stderr, self.log, self.parent_log)
+ continue
+ # Replace all placeholders with their real values
+ for v in item[1:]:
+ c = (v
+ .replace('$test_img', 'copy.img')
+ .replace('$off', str(start))
+ .replace('$len', str(end - start)))
+ current_cmd.append(c)
+
+ # Log string with the test header
+ test_summary = "Seed: %s\nCommand: %s\nTest directory: %s\n" \
+ "Backing file: %s\n" \
+ % (self.seed, " ".join(current_cmd),
+ self.current_dir, backing_file_name)
+
+ temp_log = StringIO.StringIO()
+ try:
+ retcode = run_app(temp_log, current_cmd)
+ except OSError, e:
+ multilog(test_summary + "Error: Start of '%s' failed. " \
+ "Reason: %s\n\n" % (os.path.basename(
+ current_cmd[0]), e[1]),
+ sys.stderr, self.log, self.parent_log)
+ raise TestException
+
+ if retcode < 0:
+ self.log.write(temp_log.getvalue())
+ multilog(test_summary + "FAIL: Test terminated by signal " +
+ "%s\n\n" % str_signal(-retcode), sys.stderr, self.log,
+ self.parent_log)
+ self.failed = True
+ else:
+ if self.log_all:
+ self.log.write(temp_log.getvalue())
+ multilog(test_summary + "PASS: Application exited with" +
+ " the code '%d'\n\n" % retcode, sys.stdout,
+ self.log, self.parent_log)
+ temp_log.close()
+ os.remove('copy.img')
+
+ def finish(self):
+ """Restore the test environment after a test execution."""
+ self.log.close()
+ self.parent_log.close()
+ os.chdir(self.init_path)
+ if self.cleanup and not self.failed:
+ shutil.rmtree(self.current_dir)
+
+if __name__ == '__main__':
+
+ def usage():
+ print """
+ Usage: runner.py [OPTION...] TEST_DIR IMG_GENERATOR
+
+ Set up test environment in TEST_DIR and run a test in it. A module for
+ test image generation should be specified via IMG_GENERATOR.
+ Example:
+ runner.py -c '[["qemu-img", "info", "$test_img"]]' /tmp/test qcow2
+
+ Optional arguments:
+ -h, --help display this help and exit
+ -c, --command=JSON run tests for all commands specified in
+ the JSON array
+ -s, --seed=STRING seed for a test image generation,
+ by default will be generated randomly
+ --config=JSON take fuzzer configuration from the JSON
+ array
+ -k, --keep_passed don't remove folders of passed tests
+ -v, --verbose log information about passed tests
+
+ JSON:
+
+ '--command' accepts a JSON array of commands. Each command presents
+ an application under test with all its paramaters as a list of strings,
+ e.g.
+ ["qemu-io", "$test_img", "-c", "write $off $len"]
+
+ Supported application aliases: 'qemu-img' and 'qemu-io'.
+ Supported argument aliases: $test_img for the fuzzed image, $off
+ for an offset, $len for length.
+
+ Values for $off and $len will be generated based on the virtual disk
+ size of the fuzzed image
+ Paths to 'qemu-img' and 'qemu-io' are retrevied from 'QEMU_IMG' and
+ 'QEMU_IO' environment variables
+
+ '--config' accepts a JSON array of fields to be fuzzed, e.g.
+ '[["header"], ["header", "version"]]'
+ Each of the list elements can consist of a complex image element only
+ as ["header"] or ["feature_name_table"] or an exact field as
+ ["header", "version"]. In the first case random portion of the element
+ fields will be fuzzed, in the second one the specified field will be
+ fuzzed always.
+
+ If '--config' argument is specified, fields not listed in
+ the configuration array will not be fuzzed.
+ """
+
+ def run_test(test_id, seed, work_dir, run_log, cleanup, log_all,
+ command, fuzz_config):
+ """Setup environment for one test and execute this test."""
+ try:
+ test = TestEnv(test_id, seed, work_dir, run_log, cleanup,
+ log_all)
+ except TestException:
+ sys.exit(1)
+
+ # Python 2.4 doesn't support 'finally' and 'except' in the same 'try'
+ # block
+ try:
+ try:
+ test.execute(command, fuzz_config)
+ except TestException:
+ sys.exit(1)
+ finally:
+ test.finish()
+
+ try:
+ opts, args = getopt.gnu_getopt(sys.argv[1:], 'c:hs:kv',
+ ['command=', 'help', 'seed=', 'config=',
+ 'keep_passed', 'verbose'])
+ except getopt.error, e:
+ print >>sys.stderr, \
+ "Error: %s\n\nTry 'runner.py --help' for more information" % e
+ sys.exit(1)
+
+ command = None
+ cleanup = True
+ log_all = False
+ seed = None
+ config = None
+ for opt, arg in opts:
+ if opt in ('-h', '--help'):
+ usage()
+ sys.exit()
+ elif opt in ('-c', '--command'):
+ try:
+ command = json.loads(arg)
+ except (TypeError, ValueError, NameError), e:
+ print >>sys.stderr, \
+ "Error: JSON array of test commands cannot be loaded.\n" \
+ "Reason: %s" % e
+ sys.exit(1)
+ elif opt in ('-k', '--keep_passed'):
+ cleanup = False
+ elif opt in ('-v', '--verbose'):
+ log_all = True
+ elif opt in ('-s', '--seed'):
+ seed = arg
+ elif opt == '--config':
+ try:
+ config = json.loads(arg)
+ except (TypeError, ValueError, NameError), e:
+ print >>sys.stderr, \
+ "Error: JSON array with the fuzzer configuration cannot" \
+ " be loaded\nReason: %s" % e
+ sys.exit(1)
+
+ if not len(args) == 2:
+ print >>sys.stderr, \
+ "Expected two parameters\nTry 'runner.py --help'" \
+ " for more information."
+ sys.exit(1)
+
+ work_dir = os.path.realpath(args[0])
+ # run_log is created in 'main', because multiple tests are expected to
+ # log in it
+ run_log = os.path.join(work_dir, 'run.log')
+
+ # Add the path to the image generator module to sys.path
+ sys.path.append(os.path.realpath(os.path.dirname(args[1])))
+ # Remove a script extension from image generator module if any
+ generator_name = os.path.splitext(os.path.basename(args[1]))[0]
+
+ try:
+ image_generator = __import__(generator_name)
+ except ImportError, e:
+ print >>sys.stderr, \
+ "Error: The image generator '%s' cannot be imported.\n" \
+ "Reason: %s" % (generator_name, e)
+ sys.exit(1)
+
+ # Enable core dumps
+ resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
+ # If a seed is specified, only one test will be executed.
+ # Otherwise runner will terminate after a keyboard interruption
+ for test_id in count(1):
+ try:
+ run_test(str(test_id), seed, work_dir, run_log, cleanup,
+ log_all, command, config)
+ except (KeyboardInterrupt, SystemExit):
+ sys.exit(1)
+
+ if seed is not None:
+ break
diff --git a/tests/libqos/malloc-pc.c b/tests/libqos/malloc-pc.c
index db1496c667..be1d97f8bb 100644
--- a/tests/libqos/malloc-pc.c
+++ b/tests/libqos/malloc-pc.c
@@ -36,7 +36,7 @@ static uint64_t pc_alloc(QGuestAllocator *allocator, size_t size)
size += (PAGE_SIZE - 1);
- size &= PAGE_SIZE;
+ size &= -PAGE_SIZE;
g_assert_cmpint((s->start + size), <=, s->end);
@@ -67,5 +67,8 @@ QGuestAllocator *pc_alloc_init(void)
/* Respect PCI hole */
s->end = MIN(ram_size, 0xE0000000);
+ /* clean-up */
+ g_free(fw_cfg);
+
return &s->alloc;
}
diff --git a/tests/libqos/malloc.h b/tests/libqos/malloc.h
index 46f6000763..556538121e 100644
--- a/tests/libqos/malloc.h
+++ b/tests/libqos/malloc.h
@@ -32,7 +32,7 @@ static inline uint64_t guest_alloc(QGuestAllocator *allocator, size_t size)
static inline void guest_free(QGuestAllocator *allocator, uint64_t addr)
{
- allocator->alloc(allocator, addr);
+ allocator->free(allocator, addr);
}
#endif
diff --git a/tests/libqos/pci-pc.c b/tests/libqos/pci-pc.c
index 4adf4006ae..0609294af0 100644
--- a/tests/libqos/pci-pc.c
+++ b/tests/libqos/pci-pc.c
@@ -144,7 +144,7 @@ static void qpci_pc_config_writel(QPCIBus *bus, int devfn, uint8_t offset, uint3
outl(0xcfc, value);
}
-static void *qpci_pc_iomap(QPCIBus *bus, QPCIDevice *dev, int barno)
+static void *qpci_pc_iomap(QPCIBus *bus, QPCIDevice *dev, int barno, uint64_t *sizeptr)
{
QPCIBusPC *s = container_of(bus, QPCIBusPC, bus);
static const int bar_reg_map[] = {
@@ -173,6 +173,9 @@ static void *qpci_pc_iomap(QPCIBus *bus, QPCIDevice *dev, int barno)
if (size == 0) {
return NULL;
}
+ if (sizeptr) {
+ *sizeptr = size;
+ }
if (io_type == PCI_BASE_ADDRESS_SPACE_IO) {
uint16_t loc;
@@ -237,3 +240,10 @@ QPCIBus *qpci_init_pc(void)
return &ret->bus;
}
+
+void qpci_free_pc(QPCIBus *bus)
+{
+ QPCIBusPC *s = container_of(bus, QPCIBusPC, bus);
+
+ g_free(s);
+}
diff --git a/tests/libqos/pci-pc.h b/tests/libqos/pci-pc.h
index 4f7475f6f7..26211790cd 100644
--- a/tests/libqos/pci-pc.h
+++ b/tests/libqos/pci-pc.h
@@ -16,5 +16,6 @@
#include "libqos/pci.h"
QPCIBus *qpci_init_pc(void);
+void qpci_free_pc(QPCIBus *bus);
#endif
diff --git a/tests/libqos/pci.c b/tests/libqos/pci.c
index c9a0b9134a..ce0b308a83 100644
--- a/tests/libqos/pci.c
+++ b/tests/libqos/pci.c
@@ -138,9 +138,9 @@ void qpci_io_writel(QPCIDevice *dev, void *data, uint32_t value)
dev->bus->io_writel(dev->bus, data, value);
}
-void *qpci_iomap(QPCIDevice *dev, int barno)
+void *qpci_iomap(QPCIDevice *dev, int barno, uint64_t *sizeptr)
{
- return dev->bus->iomap(dev->bus, dev, barno);
+ return dev->bus->iomap(dev->bus, dev, barno, sizeptr);
}
void qpci_iounmap(QPCIDevice *dev, void *data)
diff --git a/tests/libqos/pci.h b/tests/libqos/pci.h
index 3439431540..9ee048b154 100644
--- a/tests/libqos/pci.h
+++ b/tests/libqos/pci.h
@@ -41,7 +41,7 @@ struct QPCIBus
void (*config_writel)(QPCIBus *bus, int devfn,
uint8_t offset, uint32_t value);
- void *(*iomap)(QPCIBus *bus, QPCIDevice *dev, int barno);
+ void *(*iomap)(QPCIBus *bus, QPCIDevice *dev, int barno, uint64_t *sizeptr);
void (*iounmap)(QPCIBus *bus, void *data);
};
@@ -74,7 +74,7 @@ void qpci_io_writeb(QPCIDevice *dev, void *data, uint8_t value);
void qpci_io_writew(QPCIDevice *dev, void *data, uint16_t value);
void qpci_io_writel(QPCIDevice *dev, void *data, uint32_t value);
-void *qpci_iomap(QPCIDevice *dev, int barno);
+void *qpci_iomap(QPCIDevice *dev, int barno, uint64_t *sizeptr);
void qpci_iounmap(QPCIDevice *dev, void *data);
#endif
diff --git a/tests/libqtest.c b/tests/libqtest.c
index 98e8f4b648..ed55686ce0 100644
--- a/tests/libqtest.c
+++ b/tests/libqtest.c
@@ -167,11 +167,12 @@ QTestState *qtest_init(const char *extra_args)
if (s->qemu_pid == 0) {
command = g_strdup_printf("exec %s "
"-qtest unix:%s,nowait "
- "-qtest-log /dev/null "
+ "-qtest-log %s "
"-qmp unix:%s,nowait "
"-machine accel=qtest "
"-display none "
"%s", qemu_binary, socket_path,
+ getenv("QTEST_LOG") ? "/dev/fd/2" : "/dev/null",
qmp_socket_path,
extra_args ?: "");
execlp("/bin/sh", "sh", "-c", command, NULL);
@@ -358,6 +359,7 @@ static void qmp_response(JSONMessageParser *parser, QList *tokens)
QDict *qtest_qmp_receive(QTestState *s)
{
QMPResponseParser qmp;
+ bool log = getenv("QTEST_LOG") != NULL;
qmp.response = NULL;
json_message_parser_init(&qmp.parser, qmp_response);
@@ -375,6 +377,9 @@ QDict *qtest_qmp_receive(QTestState *s)
exit(1);
}
+ if (log) {
+ len = write(2, &c, 1);
+ }
json_message_parser_feed(&qmp.parser, &c, 1);
}
json_message_parser_destroy(&qmp.parser);
@@ -397,10 +402,14 @@ QDict *qtest_qmpv(QTestState *s, const char *fmt, va_list ap)
/* No need to send anything for an empty QObject. */
if (qobj) {
+ int log = getenv("QTEST_LOG") != NULL;
QString *qstr = qobject_to_json(qobj);
const char *str = qstring_get_str(qstr);
size_t size = qstring_get_length(qstr);
+ if (log) {
+ fprintf(stderr, "%s", str);
+ }
/* Send QMP request */
socket_send(s->qmp_fd, str, size);
@@ -639,6 +648,7 @@ void qtest_add_func(const char *str, void (*fn))
{
gchar *path = g_strdup_printf("/%s/%s", qtest_get_arch(), str);
g_test_add_func(path, fn);
+ g_free(path);
}
void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size)
@@ -654,6 +664,18 @@ void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size)
qtest_rsp(s, 0);
}
+void qtest_memset(QTestState *s, uint64_t addr, uint8_t pattern, size_t size)
+{
+ size_t i;
+
+ qtest_sendf(s, "write 0x%" PRIx64 " 0x%zx 0x", addr, size);
+ for (i = 0; i < size; i++) {
+ qtest_sendf(s, "%02x", pattern);
+ }
+ qtest_sendf(s, "\n");
+ qtest_rsp(s, 0);
+}
+
QDict *qmp(const char *fmt, ...)
{
va_list ap;
diff --git a/tests/libqtest.h b/tests/libqtest.h
index 8f323c7030..1be0934f07 100644
--- a/tests/libqtest.h
+++ b/tests/libqtest.h
@@ -283,6 +283,17 @@ void qtest_memread(QTestState *s, uint64_t addr, void *data, size_t size);
void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size);
/**
+ * qtest_memset:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to write to.
+ * @patt: Byte pattern to fill the guest memory region with.
+ * @size: Number of bytes to write.
+ *
+ * Write a pattern to guest memory.
+ */
+void qtest_memset(QTestState *s, uint64_t addr, uint8_t patt, size_t size);
+
+/**
* qtest_clock_step_next:
* @s: #QTestState instance to operate on.
*
@@ -621,6 +632,19 @@ static inline void memwrite(uint64_t addr, const void *data, size_t size)
}
/**
+ * qmemset:
+ * @addr: Guest address to write to.
+ * @patt: Byte pattern to fill the guest memory region with.
+ * @size: Number of bytes to write.
+ *
+ * Write a pattern to guest memory.
+ */
+static inline void qmemset(uint64_t addr, uint8_t patt, size_t size)
+{
+ qtest_memset(global_qtest, addr, patt, size);
+}
+
+/**
* clock_step_next:
*
* Advance the QEMU_CLOCK_VIRTUAL to the next deadline.
diff --git a/tests/usb-hcd-ehci-test.c b/tests/usb-hcd-ehci-test.c
index bcdf62fc3b..c990492835 100644
--- a/tests/usb-hcd-ehci-test.c
+++ b/tests/usb-hcd-ehci-test.c
@@ -34,7 +34,7 @@ static void pci_init_one(struct qhc *hc, uint32_t devfn, int bar)
hc->dev = qpci_device_find(pcibus, devfn);
g_assert(hc->dev != NULL);
qpci_device_enable(hc->dev);
- hc->base = qpci_iomap(hc->dev, bar);
+ hc->base = qpci_iomap(hc->dev, bar, NULL);
g_assert(hc->base != NULL);
}