mirror of
https://github.com/qemu/qemu.git
synced 2025-08-16 14:54:29 +00:00
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);
|
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)
|
static void blkdebug_close(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
@ -699,6 +718,7 @@ static BlockDriver bdrv_blkdebug = {
|
|||||||
|
|
||||||
.bdrv_aio_readv = blkdebug_aio_readv,
|
.bdrv_aio_readv = blkdebug_aio_readv,
|
||||||
.bdrv_aio_writev = blkdebug_aio_writev,
|
.bdrv_aio_writev = blkdebug_aio_writev,
|
||||||
|
.bdrv_aio_flush = blkdebug_aio_flush,
|
||||||
|
|
||||||
.bdrv_debug_event = blkdebug_debug_event,
|
.bdrv_debug_event = blkdebug_debug_event,
|
||||||
.bdrv_debug_breakpoint = blkdebug_debug_breakpoint,
|
.bdrv_debug_breakpoint = blkdebug_debug_breakpoint,
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
/**************************************************************/
|
/**************************************************************/
|
||||||
|
|
||||||
#define HEADER_MAGIC "WithoutFreeSpace"
|
#define HEADER_MAGIC "WithoutFreeSpace"
|
||||||
|
#define HEADER_MAGIC2 "WithouFreSpacExt"
|
||||||
#define HEADER_VERSION 2
|
#define HEADER_VERSION 2
|
||||||
#define HEADER_SIZE 64
|
#define HEADER_SIZE 64
|
||||||
|
|
||||||
@ -41,8 +42,10 @@ struct parallels_header {
|
|||||||
uint32_t cylinders;
|
uint32_t cylinders;
|
||||||
uint32_t tracks;
|
uint32_t tracks;
|
||||||
uint32_t catalog_entries;
|
uint32_t catalog_entries;
|
||||||
uint32_t nb_sectors;
|
uint64_t nb_sectors;
|
||||||
char padding[24];
|
uint32_t inuse;
|
||||||
|
uint32_t data_off;
|
||||||
|
char padding[12];
|
||||||
} QEMU_PACKED;
|
} QEMU_PACKED;
|
||||||
|
|
||||||
typedef struct BDRVParallelsState {
|
typedef struct BDRVParallelsState {
|
||||||
@ -52,6 +55,8 @@ typedef struct BDRVParallelsState {
|
|||||||
unsigned int catalog_size;
|
unsigned int catalog_size;
|
||||||
|
|
||||||
unsigned int tracks;
|
unsigned int tracks;
|
||||||
|
|
||||||
|
unsigned int off_multiplier;
|
||||||
} BDRVParallelsState;
|
} BDRVParallelsState;
|
||||||
|
|
||||||
static int parallels_probe(const uint8_t *buf, int buf_size, const char *filename)
|
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;
|
const struct parallels_header *ph = (const void *)buf;
|
||||||
|
|
||||||
if (buf_size < HEADER_SIZE)
|
if (buf_size < HEADER_SIZE)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (!memcmp(ph->magic, HEADER_MAGIC, 16) &&
|
if ((!memcmp(ph->magic, HEADER_MAGIC, 16) ||
|
||||||
(le32_to_cpu(ph->version) == HEADER_VERSION))
|
!memcmp(ph->magic, HEADER_MAGIC2, 16)) &&
|
||||||
return 100;
|
(le32_to_cpu(ph->version) == HEADER_VERSION))
|
||||||
|
return 100;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -83,14 +89,19 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memcmp(ph.magic, HEADER_MAGIC, 16) ||
|
bs->total_sectors = le64_to_cpu(ph.nb_sectors);
|
||||||
(le32_to_cpu(ph.version) != HEADER_VERSION)) {
|
|
||||||
error_setg(errp, "Image not in Parallels format");
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
s->tracks = le32_to_cpu(ph.tracks);
|
||||||
if (s->tracks == 0) {
|
if (s->tracks == 0) {
|
||||||
@ -98,6 +109,11 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
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);
|
s->catalog_size = le32_to_cpu(ph.catalog_entries);
|
||||||
if (s->catalog_size > INT_MAX / 4) {
|
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++)
|
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);
|
qemu_co_mutex_init(&s->lock);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
fail_format:
|
||||||
|
error_setg(errp, "Image not in Parallels format");
|
||||||
|
ret = -EINVAL;
|
||||||
fail:
|
fail:
|
||||||
g_free(s->catalog_bitmap);
|
g_free(s->catalog_bitmap);
|
||||||
return ret;
|
return ret;
|
||||||
@ -137,8 +156,9 @@ static int64_t seek_to_sector(BlockDriverState *bs, int64_t sector_num)
|
|||||||
|
|
||||||
/* not allocated */
|
/* not allocated */
|
||||||
if ((index > s->catalog_size) || (s->catalog_bitmap[index] == 0))
|
if ((index > s->catalog_size) || (s->catalog_bitmap[index] == 0))
|
||||||
return -1;
|
return -1;
|
||||||
return (uint64_t)(s->catalog_bitmap[index] + offset) * 512;
|
return
|
||||||
|
((uint64_t)s->catalog_bitmap[index] * s->off_multiplier + offset) * 512;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parallels_read(BlockDriverState *bs, int64_t sector_num,
|
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,
|
ret = bdrv_pwrite_sync(bs->file, meta_offset, new_blocks,
|
||||||
blocks_clusters * s->cluster_size);
|
blocks_clusters * s->cluster_size);
|
||||||
g_free(new_blocks);
|
g_free(new_blocks);
|
||||||
|
new_blocks = NULL;
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail_table;
|
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 started;
|
||||||
bool starting;
|
bool starting;
|
||||||
bool stopping;
|
bool stopping;
|
||||||
|
bool disabled;
|
||||||
|
|
||||||
VirtIOBlkConf *blk;
|
VirtIOBlkConf *blk;
|
||||||
|
|
||||||
@ -218,8 +219,9 @@ void virtio_blk_data_plane_start(VirtIOBlockDataPlane *s)
|
|||||||
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
||||||
VirtIOBlock *vblk = VIRTIO_BLK(s->vdev);
|
VirtIOBlock *vblk = VIRTIO_BLK(s->vdev);
|
||||||
VirtQueue *vq;
|
VirtQueue *vq;
|
||||||
|
int r;
|
||||||
|
|
||||||
if (s->started) {
|
if (s->started || s->disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,22 +233,23 @@ void virtio_blk_data_plane_start(VirtIOBlockDataPlane *s)
|
|||||||
|
|
||||||
vq = virtio_get_queue(s->vdev, 0);
|
vq = virtio_get_queue(s->vdev, 0);
|
||||||
if (!vring_setup(&s->vring, s->vdev, 0)) {
|
if (!vring_setup(&s->vring, s->vdev, 0)) {
|
||||||
s->starting = false;
|
goto fail_vring;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set up guest notifier (irq) */
|
/* Set up guest notifier (irq) */
|
||||||
if (k->set_guest_notifiers(qbus->parent, 1, true) != 0) {
|
r = k->set_guest_notifiers(qbus->parent, 1, true);
|
||||||
fprintf(stderr, "virtio-blk failed to set guest notifier, "
|
if (r != 0) {
|
||||||
"ensure -enable-kvm is set\n");
|
fprintf(stderr, "virtio-blk failed to set guest notifier (%d), "
|
||||||
exit(1);
|
"ensure -enable-kvm is set\n", r);
|
||||||
|
goto fail_guest_notifiers;
|
||||||
}
|
}
|
||||||
s->guest_notifier = virtio_queue_get_guest_notifier(vq);
|
s->guest_notifier = virtio_queue_get_guest_notifier(vq);
|
||||||
|
|
||||||
/* Set up virtqueue notify */
|
/* Set up virtqueue notify */
|
||||||
if (k->set_host_notifier(qbus->parent, 0, true) != 0) {
|
r = k->set_host_notifier(qbus->parent, 0, true);
|
||||||
fprintf(stderr, "virtio-blk failed to set host notifier\n");
|
if (r != 0) {
|
||||||
exit(1);
|
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);
|
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_context_acquire(s->ctx);
|
||||||
aio_set_event_notifier(s->ctx, &s->host_notifier, handle_notify);
|
aio_set_event_notifier(s->ctx, &s->host_notifier, handle_notify);
|
||||||
aio_context_release(s->ctx);
|
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 */
|
/* 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)));
|
BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s->vdev)));
|
||||||
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
||||||
VirtIOBlock *vblk = VIRTIO_BLK(s->vdev);
|
VirtIOBlock *vblk = VIRTIO_BLK(s->vdev);
|
||||||
|
|
||||||
|
|
||||||
|
/* Better luck next time. */
|
||||||
|
if (s->disabled) {
|
||||||
|
s->disabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!s->started || s->stopping) {
|
if (!s->started || s->stopping) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -404,19 +404,19 @@ void virtio_blk_handle_request(VirtIOBlockReq *req, MultiReqBuffer *mrb)
|
|||||||
* NB: per existing s/n string convention the string is
|
* NB: per existing s/n string convention the string is
|
||||||
* terminated by '\0' only when shorter than buffer.
|
* terminated by '\0' only when shorter than buffer.
|
||||||
*/
|
*/
|
||||||
strncpy(req->elem.in_sg[0].iov_base,
|
const char *serial = s->blk.serial ? s->blk.serial : "";
|
||||||
s->blk.serial ? s->blk.serial : "",
|
size_t size = MIN(strlen(serial) + 1,
|
||||||
MIN(req->elem.in_sg[0].iov_len, VIRTIO_BLK_ID_BYTES));
|
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_req_complete(req, VIRTIO_BLK_S_OK);
|
||||||
virtio_blk_free_request(req);
|
virtio_blk_free_request(req);
|
||||||
} else if (type & VIRTIO_BLK_T_OUT) {
|
} else if (type & VIRTIO_BLK_T_OUT) {
|
||||||
qemu_iovec_init_external(&req->qiov, &req->elem.out_sg[1],
|
qemu_iovec_init_external(&req->qiov, iov, out_num);
|
||||||
req->elem.out_num - 1);
|
|
||||||
virtio_blk_handle_write(req, mrb);
|
virtio_blk_handle_write(req, mrb);
|
||||||
} else if (type == VIRTIO_BLK_T_IN || type == VIRTIO_BLK_T_BARRIER) {
|
} else if (type == VIRTIO_BLK_T_IN || type == VIRTIO_BLK_T_BARRIER) {
|
||||||
/* VIRTIO_BLK_T_IN is 0, so we can't just & it. */
|
/* VIRTIO_BLK_T_IN is 0, so we can't just & it. */
|
||||||
qemu_iovec_init_external(&req->qiov, &req->elem.in_sg[0],
|
qemu_iovec_init_external(&req->qiov, in_iov, in_num);
|
||||||
req->elem.in_num - 1);
|
|
||||||
virtio_blk_handle_read(req);
|
virtio_blk_handle_read(req);
|
||||||
} else {
|
} else {
|
||||||
virtio_blk_req_complete(req, VIRTIO_BLK_S_UNSUPP);
|
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];
|
gsi_state->i8259_irq[i] = i8259[i];
|
||||||
}
|
}
|
||||||
if (pci_enabled) {
|
if (pci_enabled) {
|
||||||
ioapic_init_gsi(gsi_state, NULL);
|
ioapic_init_gsi(gsi_state, "q35");
|
||||||
}
|
}
|
||||||
qdev_init_nofail(icc_bridge);
|
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;
|
s->dev[port].finished |= finished;
|
||||||
*(uint32_t*)(sdb_fis + 4) = cpu_to_le32(s->dev[port].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)
|
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) {
|
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);
|
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. */
|
/* We're ready to process the command in FIS byte 2. */
|
||||||
ide_exec_cmd(&s->dev[port].port, cmd_fis[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:
|
out:
|
||||||
@ -991,7 +1051,7 @@ out:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* DMA dev <-> ram */
|
/* DMA dev <-> ram */
|
||||||
static int ahci_start_transfer(IDEDMA *dma)
|
static void ahci_start_transfer(IDEDMA *dma)
|
||||||
{
|
{
|
||||||
AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
|
AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
|
||||||
IDEState *s = &ad->port.ifs[0];
|
IDEState *s = &ad->port.ifs[0];
|
||||||
@ -1038,11 +1098,9 @@ out:
|
|||||||
s->end_transfer_func(s);
|
s->end_transfer_func(s);
|
||||||
|
|
||||||
if (!(s->status & DRQ_STAT)) {
|
if (!(s->status & DRQ_STAT)) {
|
||||||
/* done with DMA */
|
/* done with PIO send/receive */
|
||||||
ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_DSS);
|
ahci_write_fis_pio(ad, le32_to_cpu(ad->cur_cmd->status));
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ahci_start_dma(IDEDMA *dma, IDEState *s,
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ahci_dma_add_status(IDEDMA *dma, int status)
|
static void ahci_cmd_done(IDEDMA *dma)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, 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 */
|
/* update d2h status */
|
||||||
ahci_write_fis_d2h(ad, NULL);
|
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);
|
ad->check_bh = qemu_bh_new(ahci_check_cmd_bh, ad);
|
||||||
qemu_bh_schedule(ad->check_bh);
|
qemu_bh_schedule(ad->check_bh);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ahci_irq_set(void *opaque, int n, int level)
|
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 = {
|
static const IDEDMAOps ahci_dma_ops = {
|
||||||
.start_dma = ahci_start_dma,
|
.start_dma = ahci_start_dma,
|
||||||
.start_transfer = ahci_start_transfer,
|
.start_transfer = ahci_start_transfer,
|
||||||
.prepare_buf = ahci_dma_prepare_buf,
|
.prepare_buf = ahci_dma_prepare_buf,
|
||||||
.rw_buf = ahci_dma_rw_buf,
|
.rw_buf = ahci_dma_rw_buf,
|
||||||
.set_unit = ahci_dma_set_unit,
|
.set_unit = ahci_dma_set_unit,
|
||||||
.add_status = ahci_dma_add_status,
|
.cmd_done = ahci_cmd_done,
|
||||||
.set_inactive = ahci_dma_set_inactive,
|
|
||||||
.async_cmd_done = ahci_async_cmd_done,
|
|
||||||
.restart_cb = ahci_dma_restart_cb,
|
.restart_cb = ahci_dma_restart_cb,
|
||||||
.reset = ahci_dma_reset,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void ahci_init(AHCIState *s, DeviceState *qdev, AddressSpace *as, int ports)
|
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_PARTIAL (0x2 << 28) /* Put i/f in partial state */
|
||||||
#define PORT_CMD_ICC_SLUMBER (0x6 << 28) /* Put i/f in slumber 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 */
|
/* ap->flags bits */
|
||||||
#define AHCI_FLAG_NO_NCQ (1 << 24)
|
#define AHCI_FLAG_NO_NCQ (1 << 24)
|
||||||
#define AHCI_FLAG_IGN_IRQ_IF_ERR (1 << 25) /* ignore IRQ_IF_ERR */
|
#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
|
#endif
|
||||||
if (s->packet_transfer_size <= 0) {
|
if (s->packet_transfer_size <= 0) {
|
||||||
/* end of transfer */
|
/* end of transfer */
|
||||||
ide_transfer_stop(s);
|
|
||||||
s->status = READY_STAT | SEEK_STAT;
|
s->status = READY_STAT | SEEK_STAT;
|
||||||
s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
|
s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
|
||||||
|
ide_transfer_stop(s);
|
||||||
ide_set_irq(s->bus);
|
ide_set_irq(s->bus);
|
||||||
#ifdef DEBUG_IDE_ATAPI
|
#ifdef DEBUG_IDE_ATAPI
|
||||||
printf("status=0x%x\n", s->status);
|
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) {
|
if (s->atapi_dma) {
|
||||||
bdrv_acct_start(s->bs, &s->acct, size, BDRV_ACCT_READ);
|
bdrv_acct_start(s->bs, &s->acct, size, BDRV_ACCT_READ);
|
||||||
s->status = READY_STAT | SEEK_STAT | DRQ_STAT;
|
s->status = READY_STAT | SEEK_STAT | DRQ_STAT;
|
||||||
s->bus->dma->ops->start_dma(s->bus->dma, s,
|
ide_start_dma(s, ide_atapi_cmd_read_dma_cb);
|
||||||
ide_atapi_cmd_read_dma_cb);
|
|
||||||
} else {
|
} else {
|
||||||
s->status = READY_STAT | SEEK_STAT;
|
s->status = READY_STAT | SEEK_STAT;
|
||||||
ide_atapi_cmd_reply_end(s);
|
ide_atapi_cmd_reply_end(s);
|
||||||
@ -356,8 +355,7 @@ static void ide_atapi_cmd_read_dma_cb(void *opaque, int ret)
|
|||||||
|
|
||||||
eot:
|
eot:
|
||||||
bdrv_acct_done(s->bs, &s->acct);
|
bdrv_acct_done(s->bs, &s->acct);
|
||||||
s->bus->dma->ops->add_status(s->bus->dma, BM_STATUS_INT);
|
ide_set_inactive(s, false);
|
||||||
ide_set_inactive(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* start a CD-CDROM read command with DMA */
|
/* 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 */
|
/* XXX: check if BUSY_STAT should be set */
|
||||||
s->status = READY_STAT | SEEK_STAT | DRQ_STAT | BUSY_STAT;
|
s->status = READY_STAT | SEEK_STAT | DRQ_STAT | BUSY_STAT;
|
||||||
s->bus->dma->ops->start_dma(s->bus->dma, s,
|
ide_start_dma(s, ide_atapi_cmd_read_dma_cb);
|
||||||
ide_atapi_cmd_read_dma_cb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ide_atapi_cmd_read(IDEState *s, int lba, int nb_sectors,
|
static void ide_atapi_cmd_read(IDEState *s, int lba, int nb_sectors,
|
||||||
|
@ -33,6 +33,13 @@
|
|||||||
#include <hw/ide/pci.h>
|
#include <hw/ide/pci.h>
|
||||||
|
|
||||||
/* CMD646 specific */
|
/* 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 0x71
|
||||||
#define MRDMODE_INTR_CH0 0x04
|
#define MRDMODE_INTR_CH0 0x04
|
||||||
#define MRDMODE_INTR_CH1 0x08
|
#define MRDMODE_INTR_CH1 0x08
|
||||||
@ -41,7 +48,7 @@
|
|||||||
#define UDIDETCR0 0x73
|
#define UDIDETCR0 0x73
|
||||||
#define UDIDETCR1 0x7B
|
#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,
|
static uint64_t cmd646_cmd_read(void *opaque, hwaddr addr,
|
||||||
unsigned size)
|
unsigned size)
|
||||||
@ -123,6 +130,38 @@ static void setup_cmd646_bar(PCIIDEState *d, int bus_num)
|
|||||||
"cmd646-data", 8);
|
"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,
|
static uint64_t bmdma_read(void *opaque, hwaddr addr,
|
||||||
unsigned size)
|
unsigned size)
|
||||||
{
|
{
|
||||||
@ -181,7 +220,8 @@ static void bmdma_write(void *opaque, hwaddr addr,
|
|||||||
case 1:
|
case 1:
|
||||||
pci_dev->config[MRDMODE] =
|
pci_dev->config[MRDMODE] =
|
||||||
(pci_dev->config[MRDMODE] & ~0x30) | (val & 0x30);
|
(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;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
bm->status = (val & 0x60) | (bm->status & 1) | (bm->status & ~val & 0x06);
|
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
|
static void cmd646_update_irq(PCIDevice *pd)
|
||||||
registers */
|
|
||||||
static void cmd646_update_irq(PCIIDEState *d)
|
|
||||||
{
|
{
|
||||||
PCIDevice *pd = PCI_DEVICE(d);
|
|
||||||
int pci_level;
|
int pci_level;
|
||||||
|
|
||||||
pci_level = ((pd->config[MRDMODE] & MRDMODE_INTR_CH0) &&
|
pci_level = ((pd->config[MRDMODE] & MRDMODE_INTR_CH0) &&
|
||||||
@ -246,7 +283,8 @@ static void cmd646_set_irq(void *opaque, int channel, int level)
|
|||||||
} else {
|
} else {
|
||||||
pd->config[MRDMODE] &= ~irq_mask;
|
pd->config[MRDMODE] &= ~irq_mask;
|
||||||
}
|
}
|
||||||
cmd646_update_irq(d);
|
cmd646_update_dma_interrupts(pd);
|
||||||
|
cmd646_update_irq(pd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cmd646_reset(void *opaque)
|
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 */
|
/* CMD646 PCI IDE controller */
|
||||||
static int pci_cmd646_ide_initfn(PCIDevice *dev)
|
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[PCI_CLASS_PROG] = 0x8f;
|
||||||
|
|
||||||
pci_conf[0x51] = 0x04; // enable IDE0
|
pci_conf[CNTRL] = CNTRL_EN_CH0; // enable IDE0
|
||||||
if (d->secondary) {
|
if (d->secondary) {
|
||||||
/* XXX: if not enabled, really disable the seconday IDE controller */
|
/* 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, 0);
|
||||||
setup_cmd646_bar(d, 1);
|
setup_cmd646_bar(d, 1);
|
||||||
pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &d->cmd646_bar[0].data);
|
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->device_id = PCI_DEVICE_ID_CMD_646;
|
||||||
k->revision = 0x07;
|
k->revision = 0x07;
|
||||||
k->class_id = PCI_CLASS_STORAGE_IDE;
|
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;
|
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)
|
static inline void ide_abort_command(IDEState *s)
|
||||||
{
|
{
|
||||||
|
ide_transfer_stop(s);
|
||||||
s->status = READY_STAT | ERR_STAT;
|
s->status = READY_STAT | ERR_STAT;
|
||||||
s->error = ABRT_ERR;
|
s->error = ABRT_ERR;
|
||||||
}
|
}
|
||||||
@ -434,7 +435,16 @@ void ide_transfer_start(IDEState *s, uint8_t *buf, int size,
|
|||||||
if (!(s->status & ERR_STAT)) {
|
if (!(s->status & ERR_STAT)) {
|
||||||
s->status |= DRQ_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)
|
void ide_transfer_stop(IDEState *s)
|
||||||
@ -443,6 +453,7 @@ void ide_transfer_stop(IDEState *s)
|
|||||||
s->data_ptr = s->io_buffer;
|
s->data_ptr = s->io_buffer;
|
||||||
s->data_end = s->io_buffer;
|
s->data_end = s->io_buffer;
|
||||||
s->status &= ~DRQ_STAT;
|
s->status &= ~DRQ_STAT;
|
||||||
|
ide_cmd_done(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t ide_get_sector(IDEState *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);
|
bdrv_acct_done(s->bs, &s->acct);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
if (ide_handle_rw_error(s, -ret, BM_STATUS_PIO_RETRY |
|
if (ide_handle_rw_error(s, -ret, IDE_RETRY_PIO |
|
||||||
BM_STATUS_RETRY_READ)) {
|
IDE_RETRY_READ)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -585,39 +596,32 @@ static void dma_buf_commit(IDEState *s)
|
|||||||
qemu_sglist_destroy(&s->sg);
|
qemu_sglist_destroy(&s->sg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ide_async_cmd_done(IDEState *s)
|
void ide_set_inactive(IDEState *s, bool more)
|
||||||
{
|
|
||||||
if (s->bus->dma->ops->async_cmd_done) {
|
|
||||||
s->bus->dma->ops->async_cmd_done(s->bus->dma);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ide_set_inactive(IDEState *s)
|
|
||||||
{
|
{
|
||||||
s->bus->dma->aiocb = NULL;
|
s->bus->dma->aiocb = NULL;
|
||||||
s->bus->dma->ops->set_inactive(s->bus->dma);
|
if (s->bus->dma->ops->set_inactive) {
|
||||||
ide_async_cmd_done(s);
|
s->bus->dma->ops->set_inactive(s->bus->dma, more);
|
||||||
|
}
|
||||||
|
ide_cmd_done(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ide_dma_error(IDEState *s)
|
void ide_dma_error(IDEState *s)
|
||||||
{
|
{
|
||||||
ide_transfer_stop(s);
|
ide_abort_command(s);
|
||||||
s->error = ABRT_ERR;
|
ide_set_inactive(s, false);
|
||||||
s->status = READY_STAT | ERR_STAT;
|
|
||||||
ide_set_inactive(s);
|
|
||||||
ide_set_irq(s->bus);
|
ide_set_irq(s->bus);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ide_handle_rw_error(IDEState *s, int error, int op)
|
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);
|
BlockErrorAction action = bdrv_get_error_action(s->bs, is_read, error);
|
||||||
|
|
||||||
if (action == BLOCK_ERROR_ACTION_STOP) {
|
if (action == BLOCK_ERROR_ACTION_STOP) {
|
||||||
s->bus->dma->ops->set_unit(s->bus->dma, s->unit);
|
s->bus->dma->ops->set_unit(s->bus->dma, s->unit);
|
||||||
s->bus->error_status = op;
|
s->bus->error_status = op;
|
||||||
} else if (action == BLOCK_ERROR_ACTION_REPORT) {
|
} else if (action == BLOCK_ERROR_ACTION_REPORT) {
|
||||||
if (op & BM_STATUS_DMA_RETRY) {
|
if (op & IDE_RETRY_DMA) {
|
||||||
dma_buf_commit(s);
|
dma_buf_commit(s);
|
||||||
ide_dma_error(s);
|
ide_dma_error(s);
|
||||||
} else {
|
} else {
|
||||||
@ -636,12 +640,12 @@ void ide_dma_cb(void *opaque, int ret)
|
|||||||
bool stay_active = false;
|
bool stay_active = false;
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
int op = BM_STATUS_DMA_RETRY;
|
int op = IDE_RETRY_DMA;
|
||||||
|
|
||||||
if (s->dma_cmd == IDE_DMA_READ)
|
if (s->dma_cmd == IDE_DMA_READ)
|
||||||
op |= BM_STATUS_RETRY_READ;
|
op |= IDE_RETRY_READ;
|
||||||
else if (s->dma_cmd == IDE_DMA_TRIM)
|
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)) {
|
if (ide_handle_rw_error(s, -ret, op)) {
|
||||||
return;
|
return;
|
||||||
@ -688,7 +692,8 @@ void ide_dma_cb(void *opaque, int ret)
|
|||||||
sector_num, n, s->dma_cmd);
|
sector_num, n, s->dma_cmd);
|
||||||
#endif
|
#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);
|
dma_buf_commit(s);
|
||||||
ide_dma_error(s);
|
ide_dma_error(s);
|
||||||
return;
|
return;
|
||||||
@ -715,10 +720,7 @@ eot:
|
|||||||
if (s->dma_cmd == IDE_DMA_READ || s->dma_cmd == IDE_DMA_WRITE) {
|
if (s->dma_cmd == IDE_DMA_READ || s->dma_cmd == IDE_DMA_WRITE) {
|
||||||
bdrv_acct_done(s->bs, &s->acct);
|
bdrv_acct_done(s->bs, &s->acct);
|
||||||
}
|
}
|
||||||
ide_set_inactive(s);
|
ide_set_inactive(s, stay_active);
|
||||||
if (stay_active) {
|
|
||||||
s->bus->dma->ops->add_status(s->bus->dma, BM_STATUS_DMAING);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ide_sector_start_dma(IDEState *s, enum ide_dma_cmd dma_cmd)
|
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;
|
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)
|
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;
|
s->status &= ~BUSY_STAT;
|
||||||
|
|
||||||
if (ret != 0) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -831,16 +840,20 @@ static void ide_flush_cb(void *opaque, int ret)
|
|||||||
{
|
{
|
||||||
IDEState *s = opaque;
|
IDEState *s = opaque;
|
||||||
|
|
||||||
|
s->pio_aiocb = NULL;
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
/* XXX: What sector number to set here? */
|
/* 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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_acct_done(s->bs, &s->acct);
|
if (s->bs) {
|
||||||
|
bdrv_acct_done(s->bs, &s->acct);
|
||||||
|
}
|
||||||
s->status = READY_STAT | SEEK_STAT;
|
s->status = READY_STAT | SEEK_STAT;
|
||||||
ide_async_cmd_done(s);
|
ide_cmd_done(s);
|
||||||
ide_set_irq(s->bus);
|
ide_set_irq(s->bus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -853,7 +866,7 @@ void ide_flush_cache(IDEState *s)
|
|||||||
|
|
||||||
s->status |= BUSY_STAT;
|
s->status |= BUSY_STAT;
|
||||||
bdrv_acct_start(s->bs, &s->acct, 0, BDRV_ACCT_FLUSH);
|
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)
|
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;
|
s->status |= SEEK_STAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ide_cmd_done(s);
|
||||||
ide_set_irq(s->bus);
|
ide_set_irq(s->bus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2086,7 +2100,9 @@ void ide_bus_reset(IDEBus *bus)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* reset dma provider too */
|
/* 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)
|
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);
|
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)
|
static int ide_nop_int(IDEDMA *dma, int x)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
@ -2216,15 +2222,10 @@ static void ide_nop_restart(void *opaque, int x, RunState y)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const IDEDMAOps ide_dma_nop_ops = {
|
static const IDEDMAOps ide_dma_nop_ops = {
|
||||||
.start_dma = ide_nop_start,
|
|
||||||
.start_transfer = ide_nop,
|
|
||||||
.prepare_buf = ide_nop_int,
|
.prepare_buf = ide_nop_int,
|
||||||
.rw_buf = ide_nop_int,
|
.rw_buf = ide_nop_int,
|
||||||
.set_unit = ide_nop_int,
|
.set_unit = ide_nop_int,
|
||||||
.add_status = ide_nop_int,
|
|
||||||
.set_inactive = ide_nop,
|
|
||||||
.restart_cb = ide_nop_restart,
|
.restart_cb = ide_nop_restart,
|
||||||
.reset = ide_nop,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static IDEDMA ide_dma_nop = {
|
static IDEDMA ide_dma_nop = {
|
||||||
@ -2341,7 +2342,7 @@ static bool ide_drive_pio_state_needed(void *opaque)
|
|||||||
IDEState *s = opaque;
|
IDEState *s = opaque;
|
||||||
|
|
||||||
return ((s->status & DRQ_STAT) != 0)
|
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)
|
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 EndTransferFunc(IDEState *);
|
||||||
|
|
||||||
typedef void DMAStartFunc(IDEDMA *, IDEState *, BlockDriverCompletionFunc *);
|
typedef void DMAStartFunc(IDEDMA *, IDEState *, BlockDriverCompletionFunc *);
|
||||||
typedef int DMAFunc(IDEDMA *);
|
typedef void DMAVoidFunc(IDEDMA *);
|
||||||
typedef int DMAIntFunc(IDEDMA *, int);
|
typedef int DMAIntFunc(IDEDMA *, int);
|
||||||
|
typedef void DMAStopFunc(IDEDMA *, bool);
|
||||||
typedef void DMARestartFunc(void *, int, RunState);
|
typedef void DMARestartFunc(void *, int, RunState);
|
||||||
|
|
||||||
struct unreported_events {
|
struct unreported_events {
|
||||||
@ -427,15 +428,14 @@ struct IDEState {
|
|||||||
|
|
||||||
struct IDEDMAOps {
|
struct IDEDMAOps {
|
||||||
DMAStartFunc *start_dma;
|
DMAStartFunc *start_dma;
|
||||||
DMAFunc *start_transfer;
|
DMAVoidFunc *start_transfer;
|
||||||
DMAIntFunc *prepare_buf;
|
DMAIntFunc *prepare_buf;
|
||||||
DMAIntFunc *rw_buf;
|
DMAIntFunc *rw_buf;
|
||||||
DMAIntFunc *set_unit;
|
DMAIntFunc *set_unit;
|
||||||
DMAIntFunc *add_status;
|
DMAStopFunc *set_inactive;
|
||||||
DMAFunc *set_inactive;
|
DMAVoidFunc *cmd_done;
|
||||||
DMAFunc *async_cmd_done;
|
|
||||||
DMARestartFunc *restart_cb;
|
DMARestartFunc *restart_cb;
|
||||||
DMAFunc *reset;
|
DMAVoidFunc *reset;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct IDEDMA {
|
struct IDEDMA {
|
||||||
@ -484,23 +484,12 @@ struct IDEDevice {
|
|||||||
uint64_t wwn;
|
uint64_t wwn;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define BM_STATUS_DMAING 0x01
|
/* These are used for the error_status field of IDEBus */
|
||||||
#define BM_STATUS_ERROR 0x02
|
#define IDE_RETRY_DMA 0x08
|
||||||
#define BM_STATUS_INT 0x04
|
#define IDE_RETRY_PIO 0x10
|
||||||
|
#define IDE_RETRY_READ 0x20
|
||||||
/* FIXME These are not status register bits */
|
#define IDE_RETRY_FLUSH 0x40
|
||||||
#define BM_STATUS_DMA_RETRY 0x08
|
#define IDE_RETRY_TRIM 0x80
|
||||||
#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
|
|
||||||
|
|
||||||
static inline IDEState *idebus_active_if(IDEBus *bus)
|
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);
|
int64_t ide_get_sector(IDEState *s);
|
||||||
void ide_set_sector(IDEState *s, int64_t sector_num);
|
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_dma_error(IDEState *s);
|
||||||
|
|
||||||
void ide_atapi_cmd_ok(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,
|
void ide_transfer_start(IDEState *s, uint8_t *buf, int size,
|
||||||
EndTransferFunc *end_transfer_func);
|
EndTransferFunc *end_transfer_func);
|
||||||
void ide_transfer_stop(IDEState *s);
|
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,
|
BlockDriverAIOCB *ide_issue_trim(BlockDriverState *bs,
|
||||||
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||||
BlockDriverCompletionFunc *cb, void *opaque);
|
BlockDriverCompletionFunc *cb, void *opaque);
|
||||||
|
@ -545,11 +545,6 @@ static void macio_ide_reset(DeviceState *dev)
|
|||||||
ide_bus_reset(&d->bus);
|
ide_bus_reset(&d->bus);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ide_nop(IDEDMA *dma)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ide_nop_int(IDEDMA *dma, int x)
|
static int ide_nop_int(IDEDMA *dma, int x)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
@ -571,14 +566,10 @@ static void ide_dbdma_start(IDEDMA *dma, IDEState *s,
|
|||||||
|
|
||||||
static const IDEDMAOps dbdma_ops = {
|
static const IDEDMAOps dbdma_ops = {
|
||||||
.start_dma = ide_dbdma_start,
|
.start_dma = ide_dbdma_start,
|
||||||
.start_transfer = ide_nop,
|
|
||||||
.prepare_buf = ide_nop_int,
|
.prepare_buf = ide_nop_int,
|
||||||
.rw_buf = ide_nop_int,
|
.rw_buf = ide_nop_int,
|
||||||
.set_unit = ide_nop_int,
|
.set_unit = ide_nop_int,
|
||||||
.add_status = ide_nop_int,
|
|
||||||
.set_inactive = ide_nop,
|
|
||||||
.restart_cb = ide_nop_restart,
|
.restart_cb = ide_nop_restart,
|
||||||
.reset = ide_nop,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void macio_ide_realizefn(DeviceState *dev, Error **errp)
|
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 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,
|
static void bmdma_start_dma(IDEDMA *dma, IDEState *s,
|
||||||
BlockDriverCompletionFunc *dma_cb)
|
BlockDriverCompletionFunc *dma_cb)
|
||||||
{
|
{
|
||||||
@ -152,23 +156,17 @@ static int bmdma_set_unit(IDEDMA *dma, int unit)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int bmdma_add_status(IDEDMA *dma, int status)
|
static void bmdma_set_inactive(IDEDMA *dma, bool more)
|
||||||
{
|
|
||||||
BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma);
|
|
||||||
bm->status |= status;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int bmdma_set_inactive(IDEDMA *dma)
|
|
||||||
{
|
{
|
||||||
BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma);
|
BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma);
|
||||||
|
|
||||||
bm->status &= ~BM_STATUS_DMAING;
|
|
||||||
bm->dma_cb = NULL;
|
bm->dma_cb = NULL;
|
||||||
bm->unit = -1;
|
bm->unit = -1;
|
||||||
|
if (more) {
|
||||||
return 0;
|
bm->status |= BM_STATUS_DMAING;
|
||||||
|
} else {
|
||||||
|
bm->status &= ~BM_STATUS_DMAING;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bmdma_restart_dma(BMDMAState *bm, enum ide_dma_cmd dma_cmd)
|
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;
|
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
|
/* The error status must be cleared before resubmitting the request: The
|
||||||
* request may fail again, and this case can only be distinguished if 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;
|
error_status = bus->error_status;
|
||||||
bus->error_status = 0;
|
bus->error_status = 0;
|
||||||
|
|
||||||
if (error_status & BM_STATUS_DMA_RETRY) {
|
if (error_status & IDE_RETRY_DMA) {
|
||||||
if (error_status & BM_STATUS_RETRY_TRIM) {
|
if (error_status & IDE_RETRY_TRIM) {
|
||||||
bmdma_restart_dma(bm, IDE_DMA_TRIM);
|
bmdma_restart_dma(bm, IDE_DMA_TRIM);
|
||||||
} else {
|
} else {
|
||||||
bmdma_restart_dma(bm, is_read ? IDE_DMA_READ : IDE_DMA_WRITE);
|
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) {
|
if (is_read) {
|
||||||
ide_sector_read(bmdma_active_if(bm));
|
ide_sector_read(bmdma_active_if(bm));
|
||||||
} else {
|
} else {
|
||||||
ide_sector_write(bmdma_active_if(bm));
|
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));
|
ide_flush_cache(bmdma_active_if(bm));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,11 +241,11 @@ static void bmdma_cancel(BMDMAState *bm)
|
|||||||
{
|
{
|
||||||
if (bm->status & BM_STATUS_DMAING) {
|
if (bm->status & BM_STATUS_DMAING) {
|
||||||
/* cancel DMA request */
|
/* 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);
|
BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma);
|
||||||
|
|
||||||
@ -264,13 +262,6 @@ static int bmdma_reset(IDEDMA *dma)
|
|||||||
bm->cur_prd_len = 0;
|
bm->cur_prd_len = 0;
|
||||||
bm->sector_num = 0;
|
bm->sector_num = 0;
|
||||||
bm->nsector = 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)
|
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 = {
|
static const struct IDEDMAOps bmdma_ops = {
|
||||||
.start_dma = bmdma_start_dma,
|
.start_dma = bmdma_start_dma,
|
||||||
.start_transfer = bmdma_start_transfer,
|
|
||||||
.prepare_buf = bmdma_prepare_buf,
|
.prepare_buf = bmdma_prepare_buf,
|
||||||
.rw_buf = bmdma_rw_buf,
|
.rw_buf = bmdma_rw_buf,
|
||||||
.set_unit = bmdma_set_unit,
|
.set_unit = bmdma_set_unit,
|
||||||
.add_status = bmdma_add_status,
|
|
||||||
.set_inactive = bmdma_set_inactive,
|
.set_inactive = bmdma_set_inactive,
|
||||||
.restart_cb = bmdma_restart_cb,
|
.restart_cb = bmdma_restart_cb,
|
||||||
.reset = bmdma_reset,
|
.reset = bmdma_reset,
|
||||||
|
@ -3,6 +3,13 @@
|
|||||||
|
|
||||||
#include <hw/ide/internal.h>
|
#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 {
|
typedef struct BMDMAState {
|
||||||
IDEDMA dma;
|
IDEDMA dma;
|
||||||
uint8_t cmd;
|
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 = g_malloc0(sizeof(FDCharDriver));
|
||||||
s->fd_in = io_channel_from_fd(fd_in);
|
s->fd_in = io_channel_from_fd(fd_in);
|
||||||
s->fd_out = io_channel_from_fd(fd_out);
|
s->fd_out = io_channel_from_fd(fd_out);
|
||||||
fcntl(fd_out, F_SETFL, O_NONBLOCK);
|
qemu_set_nonblock(fd_out);
|
||||||
s->chr = chr;
|
s->chr = chr;
|
||||||
chr->opaque = s;
|
chr->opaque = s;
|
||||||
chr->chr_add_watch = fd_chr_add_watch;
|
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);
|
old_fd0_flags = fcntl(0, F_GETFL);
|
||||||
tcgetattr (0, &oldtty);
|
tcgetattr (0, &oldtty);
|
||||||
fcntl(0, F_SETFL, O_NONBLOCK);
|
qemu_set_nonblock(0);
|
||||||
atexit(term_exit);
|
atexit(term_exit);
|
||||||
|
|
||||||
chr = qemu_chr_open_fd(0, 1);
|
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"
|
" [,serial=s][,addr=A][,rerror=ignore|stop|report]\n"
|
||||||
" [,werror=ignore|stop|report|enospc][,id=name][,aio=threads|native]\n"
|
" [,werror=ignore|stop|report|enospc][,id=name][,aio=threads|native]\n"
|
||||||
" [,readonly=on|off][,copy-on-read=on|off]\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"
|
" [[,bps=b]|[[,bps_rd=r][,bps_wr=w]]]\n"
|
||||||
" [[,iops=i]|[[,iops_rd=r][,iops_wr=w]]]\n"
|
" [[,iops=i]|[[,iops_rd=r][,iops_wr=w]]]\n"
|
||||||
" [[,bps_max=bm]|[[,bps_rd_max=rm][,bps_wr_max=wm]]]\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));
|
g_warning("error converting fd to gsocket: %s", strerror(errno));
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
fcntl(client_fd, F_SETFL, O_NONBLOCK);
|
qemu_set_nonblock(client_fd);
|
||||||
ret = ga_channel_client_add(c, client_fd);
|
ret = ga_channel_client_add(c, client_fd);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
g_warning("error setting up connection");
|
g_warning("error setting up connection");
|
||||||
|
@ -106,6 +106,7 @@ static QPCIBus *pcibus = NULL;
|
|||||||
static QGuestAllocator *guest_malloc;
|
static QGuestAllocator *guest_malloc;
|
||||||
|
|
||||||
static char tmp_path[] = "/tmp/qtest.XXXXXX";
|
static char tmp_path[] = "/tmp/qtest.XXXXXX";
|
||||||
|
static char debug_path[] = "/tmp/qtest-blkdebug.XXXXXX";
|
||||||
|
|
||||||
static void ide_test_start(const char *cmdline_fmt, ...)
|
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_start(cmdline);
|
||||||
qtest_irq_intercept_in(global_qtest, "ioapic");
|
qtest_irq_intercept_in(global_qtest, "ioapic");
|
||||||
guest_malloc = pc_alloc_init();
|
guest_malloc = pc_alloc_init();
|
||||||
|
|
||||||
|
g_free(cmdline);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ide_test_quit(void)
|
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);
|
g_assert(device_id == PCI_DEVICE_ID_INTEL_82371SB_1);
|
||||||
|
|
||||||
/* Map bmdma BAR */
|
/* 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);
|
qpci_device_enable(dev);
|
||||||
|
|
||||||
@ -489,6 +492,91 @@ static void test_flush(void)
|
|||||||
ide_test_quit();
|
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)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
const char *arch = qtest_get_arch();
|
const char *arch = qtest_get_arch();
|
||||||
@ -501,6 +589,11 @@ int main(int argc, char **argv)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Create temporary blkdebug instructions */
|
||||||
|
fd = mkstemp(debug_path);
|
||||||
|
g_assert(fd >= 0);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
/* Create a temporary raw image */
|
/* Create a temporary raw image */
|
||||||
fd = mkstemp(tmp_path);
|
fd = mkstemp(tmp_path);
|
||||||
g_assert(fd >= 0);
|
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/bmdma/teardown", test_bmdma_teardown);
|
||||||
|
|
||||||
qtest_add_func("/ide/flush", test_flush);
|
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();
|
ret = g_test_run();
|
||||||
|
|
||||||
/* Cleanup */
|
/* Cleanup */
|
||||||
unlink(tmp_path);
|
unlink(tmp_path);
|
||||||
|
unlink(debug_path);
|
||||||
|
|
||||||
return ret;
|
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 - 1);
|
||||||
size &= PAGE_SIZE;
|
size &= -PAGE_SIZE;
|
||||||
|
|
||||||
g_assert_cmpint((s->start + size), <=, s->end);
|
g_assert_cmpint((s->start + size), <=, s->end);
|
||||||
|
|
||||||
@ -67,5 +67,8 @@ QGuestAllocator *pc_alloc_init(void)
|
|||||||
/* Respect PCI hole */
|
/* Respect PCI hole */
|
||||||
s->end = MIN(ram_size, 0xE0000000);
|
s->end = MIN(ram_size, 0xE0000000);
|
||||||
|
|
||||||
|
/* clean-up */
|
||||||
|
g_free(fw_cfg);
|
||||||
|
|
||||||
return &s->alloc;
|
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)
|
static inline void guest_free(QGuestAllocator *allocator, uint64_t addr)
|
||||||
{
|
{
|
||||||
allocator->alloc(allocator, addr);
|
allocator->free(allocator, addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -144,7 +144,7 @@ static void qpci_pc_config_writel(QPCIBus *bus, int devfn, uint8_t offset, uint3
|
|||||||
outl(0xcfc, value);
|
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);
|
QPCIBusPC *s = container_of(bus, QPCIBusPC, bus);
|
||||||
static const int bar_reg_map[] = {
|
static const int bar_reg_map[] = {
|
||||||
@ -173,6 +173,9 @@ static void *qpci_pc_iomap(QPCIBus *bus, QPCIDevice *dev, int barno)
|
|||||||
if (size == 0) {
|
if (size == 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
if (sizeptr) {
|
||||||
|
*sizeptr = size;
|
||||||
|
}
|
||||||
|
|
||||||
if (io_type == PCI_BASE_ADDRESS_SPACE_IO) {
|
if (io_type == PCI_BASE_ADDRESS_SPACE_IO) {
|
||||||
uint16_t loc;
|
uint16_t loc;
|
||||||
@ -237,3 +240,10 @@ QPCIBus *qpci_init_pc(void)
|
|||||||
|
|
||||||
return &ret->bus;
|
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"
|
#include "libqos/pci.h"
|
||||||
|
|
||||||
QPCIBus *qpci_init_pc(void);
|
QPCIBus *qpci_init_pc(void);
|
||||||
|
void qpci_free_pc(QPCIBus *bus);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -138,9 +138,9 @@ void qpci_io_writel(QPCIDevice *dev, void *data, uint32_t value)
|
|||||||
dev->bus->io_writel(dev->bus, data, 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)
|
void qpci_iounmap(QPCIDevice *dev, void *data)
|
||||||
|
@ -41,7 +41,7 @@ struct QPCIBus
|
|||||||
void (*config_writel)(QPCIBus *bus, int devfn,
|
void (*config_writel)(QPCIBus *bus, int devfn,
|
||||||
uint8_t offset, uint32_t value);
|
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);
|
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_writew(QPCIDevice *dev, void *data, uint16_t value);
|
||||||
void qpci_io_writel(QPCIDevice *dev, void *data, uint32_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);
|
void qpci_iounmap(QPCIDevice *dev, void *data);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -167,11 +167,12 @@ QTestState *qtest_init(const char *extra_args)
|
|||||||
if (s->qemu_pid == 0) {
|
if (s->qemu_pid == 0) {
|
||||||
command = g_strdup_printf("exec %s "
|
command = g_strdup_printf("exec %s "
|
||||||
"-qtest unix:%s,nowait "
|
"-qtest unix:%s,nowait "
|
||||||
"-qtest-log /dev/null "
|
"-qtest-log %s "
|
||||||
"-qmp unix:%s,nowait "
|
"-qmp unix:%s,nowait "
|
||||||
"-machine accel=qtest "
|
"-machine accel=qtest "
|
||||||
"-display none "
|
"-display none "
|
||||||
"%s", qemu_binary, socket_path,
|
"%s", qemu_binary, socket_path,
|
||||||
|
getenv("QTEST_LOG") ? "/dev/fd/2" : "/dev/null",
|
||||||
qmp_socket_path,
|
qmp_socket_path,
|
||||||
extra_args ?: "");
|
extra_args ?: "");
|
||||||
execlp("/bin/sh", "sh", "-c", command, NULL);
|
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)
|
QDict *qtest_qmp_receive(QTestState *s)
|
||||||
{
|
{
|
||||||
QMPResponseParser qmp;
|
QMPResponseParser qmp;
|
||||||
|
bool log = getenv("QTEST_LOG") != NULL;
|
||||||
|
|
||||||
qmp.response = NULL;
|
qmp.response = NULL;
|
||||||
json_message_parser_init(&qmp.parser, qmp_response);
|
json_message_parser_init(&qmp.parser, qmp_response);
|
||||||
@ -375,6 +377,9 @@ QDict *qtest_qmp_receive(QTestState *s)
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
len = write(2, &c, 1);
|
||||||
|
}
|
||||||
json_message_parser_feed(&qmp.parser, &c, 1);
|
json_message_parser_feed(&qmp.parser, &c, 1);
|
||||||
}
|
}
|
||||||
json_message_parser_destroy(&qmp.parser);
|
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. */
|
/* No need to send anything for an empty QObject. */
|
||||||
if (qobj) {
|
if (qobj) {
|
||||||
|
int log = getenv("QTEST_LOG") != NULL;
|
||||||
QString *qstr = qobject_to_json(qobj);
|
QString *qstr = qobject_to_json(qobj);
|
||||||
const char *str = qstring_get_str(qstr);
|
const char *str = qstring_get_str(qstr);
|
||||||
size_t size = qstring_get_length(qstr);
|
size_t size = qstring_get_length(qstr);
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
fprintf(stderr, "%s", str);
|
||||||
|
}
|
||||||
/* Send QMP request */
|
/* Send QMP request */
|
||||||
socket_send(s->qmp_fd, str, size);
|
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);
|
gchar *path = g_strdup_printf("/%s/%s", qtest_get_arch(), str);
|
||||||
g_test_add_func(path, fn);
|
g_test_add_func(path, fn);
|
||||||
|
g_free(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size)
|
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);
|
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, ...)
|
QDict *qmp(const char *fmt, ...)
|
||||||
{
|
{
|
||||||
va_list ap;
|
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);
|
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:
|
* qtest_clock_step_next:
|
||||||
* @s: #QTestState instance to operate on.
|
* @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);
|
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:
|
* 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);
|
hc->dev = qpci_device_find(pcibus, devfn);
|
||||||
g_assert(hc->dev != NULL);
|
g_assert(hc->dev != NULL);
|
||||||
qpci_device_enable(hc->dev);
|
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);
|
g_assert(hc->base != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user