Block pull request
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJT7j2XAAoJEJykq7OBq3PIUL4H/11PwoUewF5hXqbsaTVbxLWK RdufFsy+31+FjM2JaGbJnGyuzRFOevx3SLllnASkltsC7AV+MlQw0qDfQ9MSvsT4 wotQKEfwApleq7u4wp/zTCyNCDRyPTvIDboG2NB/BqHMjsaar2EX3yacSZ+Bv+WJ cj2OOK9OlHHy0fycx9POgx3RB+OSNvzPcJ2DaNMuDY/0/ss5i6r2aQOT5bgHFTNU JCAGYB1MJ1dMBqHnfWdsBHXTliPnYoGyYGTLcE2lHO9VBj1hOw867Iemz9mNLWg5 LTWHLnYZLiUZIzGWlBrtnv4lgLsu2xtZCBuiMgDfl6zZtFhIR36SA1M8pdS2yMA= =3PII -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/stefanha/tags/block-pull-request' into staging Block pull request # gpg: Signature made Fri 15 Aug 2014 18:04:23 BST using RSA key ID 81AB73C8 # gpg: Good signature from "Stefan Hajnoczi <stefanha@redhat.com>" # gpg: aka "Stefan Hajnoczi <stefanha@gmail.com>" * remotes/stefanha/tags/block-pull-request: (55 commits) qcow2: fix new_blocks double-free in alloc_refcount_block() image-fuzzer: Reduce number of generator functions in __init__ image-fuzzer: Add generators of L1/L2 tables image-fuzzer: Add fuzzing functions for L1/L2 table entries docs: Expand the list of supported image elements with L1/L2 tables image-fuzzer: Public API for image-fuzzer/runner/runner.py image-fuzzer: Generator of fuzzed qcow2 images image-fuzzer: Fuzzing functions for qcow2 images image-fuzzer: Tool for fuzz tests execution docs: Specification for the image fuzzer ide: only constrain read/write requests to drive size, not other types virtio-blk: Correct bug in support for flexible descriptor layout libqos: Change free function called in malloc libqos: Correct mask to align size to PAGE_SIZE in malloc-pc libqtest: add QTEST_LOG for debugging qtest testcases ide: Fix segfault when flushing a device that doesn't exist qemu-options: add missing -drive discard option to cmdline help parallels: 2TB+ parallels images support parallels: split check for parallels format in parallels_open parallels: replace tabs with spaces in block/parallels.c ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
da398fcc25
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
238
docs/image-fuzzer.txt
Normal file
238
docs/image-fuzzer.txt
Normal file
@ -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.
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
115
hw/ide/ahci.c
115
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)
|
||||
|
@ -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 */
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
101
hw/ide/core.c
101
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)
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
45
hw/ide/pci.c
45
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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
1
tests/image-fuzzer/qcow2/__init__.py
Normal file
1
tests/image-fuzzer/qcow2/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from layout import create_image
|
355
tests/image-fuzzer/qcow2/fuzz.py
Normal file
355
tests/image-fuzzer/qcow2/fuzz.py
Normal file
@ -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
|
476
tests/image-fuzzer/qcow2/layout.py
Normal file
476
tests/image-fuzzer/qcow2/layout.py
Normal file
@ -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
|
405
tests/image-fuzzer/runner.py
Executable file
405
tests/image-fuzzer/runner.py
Executable file
@ -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
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -16,5 +16,6 @@
|
||||
#include "libqos/pci.h"
|
||||
|
||||
QPCIBus *qpci_init_pc(void);
|
||||
void qpci_free_pc(QPCIBus *bus);
|
||||
|
||||
#endif
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -282,6 +282,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.
|
||||
@ -620,6 +631,19 @@ static inline void memwrite(uint64_t addr, const void *data, size_t size)
|
||||
qtest_memwrite(global_qtest, addr, data, 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:
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user