diff options
Diffstat (limited to 'tests')
132 files changed, 8661 insertions, 646 deletions
diff --git a/tests/.gitignore b/tests/.gitignore index c71c11020..e2e495733 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -14,11 +14,14 @@ test-int128 test-iov test-mul64 test-opts-visitor +test-qapi-event.[ch] test-qapi-types.[ch] test-qapi-visit.[ch] test-qdev-global-props +test-qemu-opts test-qmp-commands test-qmp-commands.h +test-qmp-event test-qmp-input-strict test-qmp-input-visitor test-qmp-marshal.c diff --git a/tests/Makefile b/tests/Makefile index 4b2e1bbea..16f0e4c80 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -58,7 +58,7 @@ check-unit-y += tests/test-int128$(EXESUF) # all code tested by test-int128 is inside int128.h gcov-files-test-int128-y = check-unit-y += tests/test-bitops$(EXESUF) -check-unit-y += tests/test-qdev-global-props$(EXESUF) +check-unit-$(CONFIG_HAS_GLIB_SUBPROCESS_TESTS) += tests/test-qdev-global-props$(EXESUF) check-unit-y += tests/check-qom-interface$(EXESUF) gcov-files-check-qom-interface-y = qom/object.c check-unit-$(CONFIG_POSIX) += tests/test-vmstate$(EXESUF) @@ -132,6 +132,7 @@ check-qtest-i386-y = tests/endianness-test$(EXESUF) check-qtest-i386-y += tests/fdc-test$(EXESUF) gcov-files-i386-y = hw/block/fdc.c check-qtest-i386-y += tests/ide-test$(EXESUF) +check-qtest-i386-y += tests/ahci-test$(EXESUF) check-qtest-i386-y += tests/hd-geo-test$(EXESUF) gcov-files-i386-y += hw/block/hd-geometry.c check-qtest-i386-y += tests/boot-order-test$(EXESUF) @@ -139,8 +140,7 @@ check-qtest-i386-y += tests/bios-tables-test$(EXESUF) check-qtest-i386-y += tests/rtc-test$(EXESUF) check-qtest-i386-y += tests/i440fx-test$(EXESUF) check-qtest-i386-y += tests/fw_cfg-test$(EXESUF) -check-qtest-i386-y += tests/blockdev-test$(EXESUF) -check-qtest-i386-y += tests/qdev-monitor-test$(EXESUF) +check-qtest-i386-y += tests/drive_del-test$(EXESUF) check-qtest-i386-y += tests/wdt_ib700-test$(EXESUF) gcov-files-i386-y += hw/watchdog/watchdog.c hw/watchdog/wdt_ib700.c check-qtest-i386-y += $(check-qtest-pci-y) @@ -155,11 +155,16 @@ check-qtest-i386-y += tests/i82801b11-test$(EXESUF) gcov-files-i386-y += hw/pci-bridge/i82801b11.c check-qtest-i386-y += tests/ioh3420-test$(EXESUF) gcov-files-i386-y += hw/pci-bridge/ioh3420.c +check-qtest-i386-y += tests/usb-hcd-ohci-test$(EXESUF) +gcov-files-i386-y += hw/usb/hcd-ohci.c +check-qtest-i386-y += tests/usb-hcd-uhci-test$(EXESUF) +gcov-files-i386-y += hw/usb/hcd-uhci.c check-qtest-i386-y += tests/usb-hcd-ehci-test$(EXESUF) gcov-files-i386-y += hw/usb/hcd-ehci.c -gcov-files-i386-y += hw/usb/hcd-uhci.c gcov-files-i386-y += hw/usb/dev-hid.c gcov-files-i386-y += hw/usb/dev-storage.c +check-qtest-i386-y += tests/usb-hcd-xhci-test$(EXESUF) +gcov-files-i386-y += hw/usb/hcd-xhci.c check-qtest-i386-$(CONFIG_LINUX) += tests/vhost-user-test$(EXESUF) check-qtest-x86_64-y = $(check-qtest-i386-y) gcov-files-i386-y += i386-softmmu/hw/timer/mc146818rtc.c @@ -187,26 +192,27 @@ check-qtest-xtensaeb-y = $(check-qtest-xtensa-y) # qom-test works for all sysemu architectures: $(foreach target,$(SYSEMU_TARGET_LIST), \ - $(eval check-qtest-$(target)-y += tests/qom-test$(EXESUF))) + $(if $(findstring tests/qom-test$(EXESUF), $(check-qtest-$(target)-y)),, \ + $(eval check-qtest-$(target)-y += tests/qom-test$(EXESUF)))) check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ - comments.json empty.json funny-char.json indented-expr.json \ - missing-colon.json missing-comma-list.json \ - missing-comma-object.json non-objects.json \ - qapi-schema-test.json quoted-structural-chars.json \ - trailing-comma-list.json trailing-comma-object.json \ - unclosed-list.json unclosed-object.json unclosed-string.json \ - duplicate-key.json union-invalid-base.json flat-union-no-base.json \ - flat-union-invalid-discriminator.json \ - flat-union-invalid-branch-key.json flat-union-reverse-define.json \ - flat-union-string-discriminator.json \ - include-simple.json include-relpath.json include-format-err.json \ - include-non-file.json include-no-file.json include-before-err.json \ - include-nested-err.json include-self-cycle.json include-cycle.json \ - include-repetition.json event-nest-struct.json) + comments.json empty.json funny-char.json indented-expr.json \ + missing-colon.json missing-comma-list.json \ + missing-comma-object.json non-objects.json \ + qapi-schema-test.json quoted-structural-chars.json \ + trailing-comma-list.json trailing-comma-object.json \ + unclosed-list.json unclosed-object.json unclosed-string.json \ + duplicate-key.json union-invalid-base.json flat-union-no-base.json \ + flat-union-invalid-discriminator.json \ + flat-union-invalid-branch-key.json flat-union-reverse-define.json \ + flat-union-string-discriminator.json \ + include-simple.json include-relpath.json include-format-err.json \ + include-non-file.json include-no-file.json include-before-err.json \ + include-nested-err.json include-self-cycle.json include-cycle.json \ + include-repetition.json event-nest-struct.json) GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h \ - tests/test-qmp-commands.h tests/test-qapi-event.h + tests/test-qmp-commands.h tests/test-qapi-event.h test-obj-y = tests/check-qint.o tests/check-qstring.o tests/check-qdict.o \ tests/check-qlist.o tests/check-qfloat.o tests/check-qjson.o \ @@ -218,7 +224,7 @@ test-obj-y = tests/check-qint.o tests/check-qstring.o tests/check-qdict.o \ tests/test-opts-visitor.o tests/test-qmp-event.o test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \ - tests/test-qapi-event.o + tests/test-qapi-event.o $(test-obj-y): QEMU_INCLUDES += -Itests QEMU_CFLAGS += -I$(SRC_PATH)/tests @@ -252,8 +258,8 @@ tests/test-qdev-global-props$(EXESUF): tests/test-qdev-global-props.o \ $(test-qapi-obj-y) \ libqemuutil.a libqemustub.a tests/test-vmstate$(EXESUF): tests/test-vmstate.o \ - vmstate.o qemu-file.o \ - libqemuutil.a + vmstate.o qemu-file.o qemu-file-unix.o \ + libqemuutil.a libqemustub.a tests/test-qapi-types.c tests/test-qapi-types.h :\ $(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py @@ -282,7 +288,7 @@ tests/test-qmp-event$(EXESUF): tests/test-qmp-event.o $(test-qapi-obj-y) libqemu tests/test-qmp-output-visitor$(EXESUF): tests/test-qmp-output-visitor.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a tests/test-qmp-input-visitor$(EXESUF): tests/test-qmp-input-visitor.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a tests/test-qmp-input-strict$(EXESUF): tests/test-qmp-input-strict.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a -tests/test-qmp-commands$(EXESUF): tests/test-qmp-commands.o tests/test-qmp-marshal.o $(test-qapi-obj-y) qapi-types.o qapi-visit.o libqemuutil.a libqemustub.a +tests/test-qmp-commands$(EXESUF): tests/test-qmp-commands.o tests/test-qmp-marshal.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a tests/test-visitor-serialization$(EXESUF): tests/test-visitor-serialization.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a tests/test-opts-visitor$(EXESUF): tests/test-opts-visitor.o $(test-qapi-obj-y) libqemuutil.a libqemustub.a @@ -294,6 +300,8 @@ libqos-obj-y += tests/libqos/i2c.o libqos-pc-obj-y = $(libqos-obj-y) tests/libqos/pci-pc.o libqos-pc-obj-y += tests/libqos/malloc-pc.o libqos-omap-obj-y = $(libqos-obj-y) tests/libqos/i2c-omap.o +libqos-virtio-obj-y = $(libqos-obj-y) $(libqos-pc-obj-y) tests/libqos/virtio.o tests/libqos/virtio-pci.o +libqos-usb-obj-y = $(libqos-pc-obj-y) tests/libqos/usb.o tests/rtc-test$(EXESUF): tests/rtc-test.o tests/m48t59-test$(EXESUF): tests/m48t59-test.o @@ -301,6 +309,7 @@ tests/endianness-test$(EXESUF): tests/endianness-test.o tests/spapr-phb-test$(EXESUF): tests/spapr-phb-test.o $(libqos-obj-y) tests/fdc-test$(EXESUF): tests/fdc-test.o tests/ide-test$(EXESUF): tests/ide-test.o $(libqos-pc-obj-y) +tests/ahci-test$(EXESUF): tests/ahci-test.o $(libqos-pc-obj-y) tests/hd-geo-test$(EXESUF): tests/hd-geo-test.o tests/boot-order-test$(EXESUF): tests/boot-order-test.o $(libqos-obj-y) tests/bios-tables-test$(EXESUF): tests/bios-tables-test.o $(libqos-obj-y) @@ -315,9 +324,9 @@ tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o -tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o -tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o -tests/virtio-rng-test$(EXESUF): tests/virtio-rng-test.o +tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) +tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o $(libqos-pc-obj-y) +tests/virtio-rng-test$(EXESUF): tests/virtio-rng-test.o $(libqos-pc-obj-y) tests/virtio-scsi-test$(EXESUF): tests/virtio-scsi-test.o tests/virtio-9p-test$(EXESUF): tests/virtio-9p-test.o tests/virtio-serial-test$(EXESUF): tests/virtio-serial-test.o @@ -326,7 +335,7 @@ tests/tpci200-test$(EXESUF): tests/tpci200-test.o tests/display-vga-test$(EXESUF): tests/display-vga-test.o tests/ipoctal232-test$(EXESUF): tests/ipoctal232-test.o tests/qom-test$(EXESUF): tests/qom-test.o -tests/blockdev-test$(EXESUF): tests/blockdev-test.o $(libqos-pc-obj-y) +tests/drive_del-test$(EXESUF): tests/drive_del-test.o $(libqos-pc-obj-y) tests/qdev-monitor-test$(EXESUF): tests/qdev-monitor-test.o $(libqos-pc-obj-y) tests/nvme-test$(EXESUF): tests/nvme-test.o tests/pvpanic-test$(EXESUF): tests/pvpanic-test.o @@ -335,7 +344,10 @@ tests/ac97-test$(EXESUF): tests/ac97-test.o tests/es1370-test$(EXESUF): tests/es1370-test.o tests/intel-hda-test$(EXESUF): tests/intel-hda-test.o tests/ioh3420-test$(EXESUF): tests/ioh3420-test.o -tests/usb-hcd-ehci-test$(EXESUF): tests/usb-hcd-ehci-test.o $(libqos-pc-obj-y) +tests/usb-hcd-ohci-test$(EXESUF): tests/usb-hcd-ohci-test.o $(libqos-usb-obj-y) +tests/usb-hcd-uhci-test$(EXESUF): tests/usb-hcd-uhci-test.o $(libqos-usb-obj-y) +tests/usb-hcd-ehci-test$(EXESUF): tests/usb-hcd-ehci-test.o $(libqos-usb-obj-y) +tests/usb-hcd-xhci-test$(EXESUF): tests/usb-hcd-xhci-test.o $(libqos-usb-obj-y) tests/vhost-user-test$(EXESUF): tests/vhost-user-test.o qemu-char.o qemu-timer.o $(qtest-obj-y) tests/qemu-iotests/socket_scm_helper$(EXESUF): tests/qemu-iotests/socket_scm_helper.o tests/test-qemu-opts$(EXESUF): tests/test-qemu-opts.o libqemuutil.a libqemustub.a diff --git a/tests/acpi-test-data/pc/DSDT b/tests/acpi-test-data/pc/DSDT Binary files differindex d37ec3445..ee9cc6781 100644 --- a/tests/acpi-test-data/pc/DSDT +++ b/tests/acpi-test-data/pc/DSDT diff --git a/tests/acpi-test-data/pc/SSDT b/tests/acpi-test-data/pc/SSDT Binary files differindex eb2d8b698..558e4c85b 100644 --- a/tests/acpi-test-data/pc/SSDT +++ b/tests/acpi-test-data/pc/SSDT diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT Binary files differindex 2d2bc4ada..ef0c75f42 100644 --- a/tests/acpi-test-data/q35/DSDT +++ b/tests/acpi-test-data/q35/DSDT diff --git a/tests/acpi-test-data/q35/SSDT b/tests/acpi-test-data/q35/SSDT Binary files differindex 778b79bf4..4e4551018 100644 --- a/tests/acpi-test-data/q35/SSDT +++ b/tests/acpi-test-data/q35/SSDT diff --git a/tests/acpi-test-data/rebuild-expected-aml.sh b/tests/acpi-test-data/rebuild-expected-aml.sh index ab9849888..11bf74391 100755 --- a/tests/acpi-test-data/rebuild-expected-aml.sh +++ b/tests/acpi-test-data/rebuild-expected-aml.sh @@ -23,13 +23,13 @@ else exit 1; fi -if [ ! -e "tests/acpi-test" ]; then - echo "Test: acpi-test is required! Run make check before this script." +if [ ! -e "tests/bios-tables-test" ]; then + echo "Test: bios-tables-test is required! Run make check before this script." echo "Run this script from the build directory." exit 1; fi -TEST_ACPI_REBUILD_AML=y QTEST_QEMU_BINARY=$qemu tests/acpi-test +TEST_ACPI_REBUILD_AML=y QTEST_QEMU_BINARY=$qemu tests/bios-tables-test echo "The files were rebuilt and can be added to git." echo "However, if new files were created, please copy them manually" \ diff --git a/tests/ahci-test.c b/tests/ahci-test.c new file mode 100644 index 000000000..4c77ebec7 --- /dev/null +++ b/tests/ahci-test.c @@ -0,0 +1,1561 @@ +/* + * AHCI test cases + * + * Copyright (c) 2014 John Snow <jsnow@redhat.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#include <getopt.h> +#include <glib.h> + +#include "libqtest.h" +#include "libqos/pci-pc.h" +#include "libqos/malloc-pc.h" + +#include "qemu-common.h" +#include "qemu/host-utils.h" + +#include "hw/pci/pci_ids.h" +#include "hw/pci/pci_regs.h" + +/* Test-specific defines. */ +#define TEST_IMAGE_SIZE (64 * 1024 * 1024) + +/*** Supplementary PCI Config Space IDs & Masks ***/ +#define PCI_DEVICE_ID_INTEL_Q35_AHCI (0x2922) +#define PCI_MSI_FLAGS_RESERVED (0xFF00) +#define PCI_PM_CTRL_RESERVED (0xFC) +#define PCI_BCC(REG32) ((REG32) >> 24) +#define PCI_PI(REG32) (((REG32) >> 8) & 0xFF) +#define PCI_SCC(REG32) (((REG32) >> 16) & 0xFF) + +/*** Recognized AHCI Device Types ***/ +#define AHCI_INTEL_ICH9 (PCI_DEVICE_ID_INTEL_Q35_AHCI << 16 | \ + PCI_VENDOR_ID_INTEL) + +/*** AHCI/HBA Register Offsets and Bitmasks ***/ +#define AHCI_CAP (0) +#define AHCI_CAP_NP (0x1F) +#define AHCI_CAP_SXS (0x20) +#define AHCI_CAP_EMS (0x40) +#define AHCI_CAP_CCCS (0x80) +#define AHCI_CAP_NCS (0x1F00) +#define AHCI_CAP_PSC (0x2000) +#define AHCI_CAP_SSC (0x4000) +#define AHCI_CAP_PMD (0x8000) +#define AHCI_CAP_FBSS (0x10000) +#define AHCI_CAP_SPM (0x20000) +#define AHCI_CAP_SAM (0x40000) +#define AHCI_CAP_RESERVED (0x80000) +#define AHCI_CAP_ISS (0xF00000) +#define AHCI_CAP_SCLO (0x1000000) +#define AHCI_CAP_SAL (0x2000000) +#define AHCI_CAP_SALP (0x4000000) +#define AHCI_CAP_SSS (0x8000000) +#define AHCI_CAP_SMPS (0x10000000) +#define AHCI_CAP_SSNTF (0x20000000) +#define AHCI_CAP_SNCQ (0x40000000) +#define AHCI_CAP_S64A (0x80000000) + +#define AHCI_GHC (1) +#define AHCI_GHC_HR (0x01) +#define AHCI_GHC_IE (0x02) +#define AHCI_GHC_MRSM (0x04) +#define AHCI_GHC_RESERVED (0x7FFFFFF8) +#define AHCI_GHC_AE (0x80000000) + +#define AHCI_IS (2) +#define AHCI_PI (3) +#define AHCI_VS (4) + +#define AHCI_CCCCTL (5) +#define AHCI_CCCCTL_EN (0x01) +#define AHCI_CCCCTL_RESERVED (0x06) +#define AHCI_CCCCTL_CC (0xFF00) +#define AHCI_CCCCTL_TV (0xFFFF0000) + +#define AHCI_CCCPORTS (6) +#define AHCI_EMLOC (7) + +#define AHCI_EMCTL (8) +#define AHCI_EMCTL_STSMR (0x01) +#define AHCI_EMCTL_CTLTM (0x100) +#define AHCI_EMCTL_CTLRST (0x200) +#define AHCI_EMCTL_RESERVED (0xF0F0FCFE) + +#define AHCI_CAP2 (9) +#define AHCI_CAP2_BOH (0x01) +#define AHCI_CAP2_NVMP (0x02) +#define AHCI_CAP2_APST (0x04) +#define AHCI_CAP2_RESERVED (0xFFFFFFF8) + +#define AHCI_BOHC (10) +#define AHCI_RESERVED (11) +#define AHCI_NVMHCI (24) +#define AHCI_VENDOR (40) +#define AHCI_PORTS (64) + +/*** Port Memory Offsets & Bitmasks ***/ +#define AHCI_PX_CLB (0) +#define AHCI_PX_CLB_RESERVED (0x1FF) + +#define AHCI_PX_CLBU (1) + +#define AHCI_PX_FB (2) +#define AHCI_PX_FB_RESERVED (0xFF) + +#define AHCI_PX_FBU (3) + +#define AHCI_PX_IS (4) +#define AHCI_PX_IS_DHRS (0x1) +#define AHCI_PX_IS_PSS (0x2) +#define AHCI_PX_IS_DSS (0x4) +#define AHCI_PX_IS_SDBS (0x8) +#define AHCI_PX_IS_UFS (0x10) +#define AHCI_PX_IS_DPS (0x20) +#define AHCI_PX_IS_PCS (0x40) +#define AHCI_PX_IS_DMPS (0x80) +#define AHCI_PX_IS_RESERVED (0x23FFF00) +#define AHCI_PX_IS_PRCS (0x400000) +#define AHCI_PX_IS_IPMS (0x800000) +#define AHCI_PX_IS_OFS (0x1000000) +#define AHCI_PX_IS_INFS (0x4000000) +#define AHCI_PX_IS_IFS (0x8000000) +#define AHCI_PX_IS_HBDS (0x10000000) +#define AHCI_PX_IS_HBFS (0x20000000) +#define AHCI_PX_IS_TFES (0x40000000) +#define AHCI_PX_IS_CPDS (0x80000000) + +#define AHCI_PX_IE (5) +#define AHCI_PX_IE_DHRE (0x1) +#define AHCI_PX_IE_PSE (0x2) +#define AHCI_PX_IE_DSE (0x4) +#define AHCI_PX_IE_SDBE (0x8) +#define AHCI_PX_IE_UFE (0x10) +#define AHCI_PX_IE_DPE (0x20) +#define AHCI_PX_IE_PCE (0x40) +#define AHCI_PX_IE_DMPE (0x80) +#define AHCI_PX_IE_RESERVED (0x23FFF00) +#define AHCI_PX_IE_PRCE (0x400000) +#define AHCI_PX_IE_IPME (0x800000) +#define AHCI_PX_IE_OFE (0x1000000) +#define AHCI_PX_IE_INFE (0x4000000) +#define AHCI_PX_IE_IFE (0x8000000) +#define AHCI_PX_IE_HBDE (0x10000000) +#define AHCI_PX_IE_HBFE (0x20000000) +#define AHCI_PX_IE_TFEE (0x40000000) +#define AHCI_PX_IE_CPDE (0x80000000) + +#define AHCI_PX_CMD (6) +#define AHCI_PX_CMD_ST (0x1) +#define AHCI_PX_CMD_SUD (0x2) +#define AHCI_PX_CMD_POD (0x4) +#define AHCI_PX_CMD_CLO (0x8) +#define AHCI_PX_CMD_FRE (0x10) +#define AHCI_PX_CMD_RESERVED (0xE0) +#define AHCI_PX_CMD_CCS (0x1F00) +#define AHCI_PX_CMD_MPSS (0x2000) +#define AHCI_PX_CMD_FR (0x4000) +#define AHCI_PX_CMD_CR (0x8000) +#define AHCI_PX_CMD_CPS (0x10000) +#define AHCI_PX_CMD_PMA (0x20000) +#define AHCI_PX_CMD_HPCP (0x40000) +#define AHCI_PX_CMD_MPSP (0x80000) +#define AHCI_PX_CMD_CPD (0x100000) +#define AHCI_PX_CMD_ESP (0x200000) +#define AHCI_PX_CMD_FBSCP (0x400000) +#define AHCI_PX_CMD_APSTE (0x800000) +#define AHCI_PX_CMD_ATAPI (0x1000000) +#define AHCI_PX_CMD_DLAE (0x2000000) +#define AHCI_PX_CMD_ALPE (0x4000000) +#define AHCI_PX_CMD_ASP (0x8000000) +#define AHCI_PX_CMD_ICC (0xF0000000) + +#define AHCI_PX_RES1 (7) + +#define AHCI_PX_TFD (8) +#define AHCI_PX_TFD_STS (0xFF) +#define AHCI_PX_TFD_STS_ERR (0x01) +#define AHCI_PX_TFD_STS_CS1 (0x06) +#define AHCI_PX_TFD_STS_DRQ (0x08) +#define AHCI_PX_TFD_STS_CS2 (0x70) +#define AHCI_PX_TFD_STS_BSY (0x80) +#define AHCI_PX_TFD_ERR (0xFF00) +#define AHCI_PX_TFD_RESERVED (0xFFFF0000) + +#define AHCI_PX_SIG (9) +#define AHCI_PX_SIG_SECTOR_COUNT (0xFF) +#define AHCI_PX_SIG_LBA_LOW (0xFF00) +#define AHCI_PX_SIG_LBA_MID (0xFF0000) +#define AHCI_PX_SIG_LBA_HIGH (0xFF000000) + +#define AHCI_PX_SSTS (10) +#define AHCI_PX_SSTS_DET (0x0F) +#define AHCI_PX_SSTS_SPD (0xF0) +#define AHCI_PX_SSTS_IPM (0xF00) +#define AHCI_PX_SSTS_RESERVED (0xFFFFF000) +#define SSTS_DET_NO_DEVICE (0x00) +#define SSTS_DET_PRESENT (0x01) +#define SSTS_DET_ESTABLISHED (0x03) +#define SSTS_DET_OFFLINE (0x04) + +#define AHCI_PX_SCTL (11) + +#define AHCI_PX_SERR (12) +#define AHCI_PX_SERR_ERR (0xFFFF) +#define AHCI_PX_SERR_DIAG (0xFFFF0000) +#define AHCI_PX_SERR_DIAG_X (0x04000000) + +#define AHCI_PX_SACT (13) +#define AHCI_PX_CI (14) +#define AHCI_PX_SNTF (15) + +#define AHCI_PX_FBS (16) +#define AHCI_PX_FBS_EN (0x1) +#define AHCI_PX_FBS_DEC (0x2) +#define AHCI_PX_FBS_SDE (0x4) +#define AHCI_PX_FBS_DEV (0xF00) +#define AHCI_PX_FBS_ADO (0xF000) +#define AHCI_PX_FBS_DWE (0xF0000) +#define AHCI_PX_FBS_RESERVED (0xFFF000F8) + +#define AHCI_PX_RES2 (17) +#define AHCI_PX_VS (28) + +#define HBA_DATA_REGION_SIZE (256) +#define HBA_PORT_DATA_SIZE (128) +#define HBA_PORT_NUM_REG (HBA_PORT_DATA_SIZE/4) + +#define AHCI_VERSION_0_95 (0x00000905) +#define AHCI_VERSION_1_0 (0x00010000) +#define AHCI_VERSION_1_1 (0x00010100) +#define AHCI_VERSION_1_2 (0x00010200) +#define AHCI_VERSION_1_3 (0x00010300) + +/*** Structures ***/ + +/** + * Generic FIS structure. + */ +typedef struct FIS { + uint8_t fis_type; + uint8_t flags; + char data[0]; +} __attribute__((__packed__)) FIS; + +/** + * Register device-to-host FIS structure. + */ +typedef struct RegD2HFIS { + /* DW0 */ + uint8_t fis_type; + uint8_t flags; + uint8_t status; + uint8_t error; + /* DW1 */ + uint8_t lba_low; + uint8_t lba_mid; + uint8_t lba_high; + uint8_t device; + /* DW2 */ + uint8_t lba3; + uint8_t lba4; + uint8_t lba5; + uint8_t res1; + /* DW3 */ + uint16_t count; + uint8_t res2; + uint8_t res3; + /* DW4 */ + uint16_t res4; + uint16_t res5; +} __attribute__((__packed__)) RegD2HFIS; + +/** + * Register host-to-device FIS structure. + */ +typedef struct RegH2DFIS { + /* DW0 */ + uint8_t fis_type; + uint8_t flags; + uint8_t command; + uint8_t feature_low; + /* DW1 */ + uint8_t lba_low; + uint8_t lba_mid; + uint8_t lba_high; + uint8_t device; + /* DW2 */ + uint8_t lba3; + uint8_t lba4; + uint8_t lba5; + uint8_t feature_high; + /* DW3 */ + uint16_t count; + uint8_t icc; + uint8_t control; + /* DW4 */ + uint32_t aux; +} __attribute__((__packed__)) RegH2DFIS; + +/** + * Command List entry structure. + * The command list contains between 1-32 of these structures. + */ +typedef struct AHCICommand { + uint8_t b1; + uint8_t b2; + uint16_t prdtl; /* Phys Region Desc. Table Length */ + uint32_t prdbc; /* Phys Region Desc. Byte Count */ + uint32_t ctba; /* Command Table Descriptor Base Address */ + uint32_t ctbau; /* '' Upper */ + uint32_t res[4]; +} __attribute__((__packed__)) AHCICommand; + +/** + * Physical Region Descriptor; pointed to by the Command List Header, + * struct ahci_command. + */ +typedef struct PRD { + uint32_t dba; /* Data Base Address */ + uint32_t dbau; /* Data Base Address Upper */ + uint32_t res; /* Reserved */ + uint32_t dbc; /* Data Byte Count (0-indexed) & Interrupt Flag (bit 2^31) */ +} PRD; + +typedef struct HBACap { + uint32_t cap; + uint32_t cap2; +} HBACap; + +/*** Globals ***/ +static QGuestAllocator *guest_malloc; +static QPCIBus *pcibus; +static uint64_t barsize; +static char tmp_path[] = "/tmp/qtest.XXXXXX"; +static bool ahci_pedantic; +static uint32_t ahci_fingerprint; + +/*** Macro Utilities ***/ +#define BITANY(data, mask) (((data) & (mask)) != 0) +#define BITSET(data, mask) (((data) & (mask)) == (mask)) +#define BITCLR(data, mask) (((data) & (mask)) == 0) +#define ASSERT_BIT_SET(data, mask) g_assert_cmphex((data) & (mask), ==, (mask)) +#define ASSERT_BIT_CLEAR(data, mask) g_assert_cmphex((data) & (mask), ==, 0) + +/*** IO macros for the AHCI memory registers. ***/ +#define AHCI_READ(OFST) qpci_io_readl(ahci, hba_base + (OFST)) +#define AHCI_WRITE(OFST, VAL) qpci_io_writel(ahci, hba_base + (OFST), (VAL)) +#define AHCI_RREG(regno) AHCI_READ(4 * (regno)) +#define AHCI_WREG(regno, val) AHCI_WRITE(4 * (regno), (val)) +#define AHCI_SET(regno, mask) AHCI_WREG((regno), AHCI_RREG(regno) | (mask)) +#define AHCI_CLR(regno, mask) AHCI_WREG((regno), AHCI_RREG(regno) & ~(mask)) + +/*** IO macros for port-specific offsets inside of AHCI memory. ***/ +#define PX_OFST(port, regno) (HBA_PORT_NUM_REG * (port) + AHCI_PORTS + (regno)) +#define PX_RREG(port, regno) AHCI_RREG(PX_OFST((port), (regno))) +#define PX_WREG(port, regno, val) AHCI_WREG(PX_OFST((port), (regno)), (val)) +#define PX_SET(port, reg, mask) PX_WREG((port), (reg), \ + PX_RREG((port), (reg)) | (mask)); +#define PX_CLR(port, reg, mask) PX_WREG((port), (reg), \ + PX_RREG((port), (reg)) & ~(mask)); + +/* For calculating how big the PRD table needs to be: */ +#define CMD_TBL_SIZ(n) ((0x80 + ((n) * sizeof(PRD)) + 0x7F) & ~0x7F) + + +/*** Function Declarations ***/ +static QPCIDevice *get_ahci_device(void); +static QPCIDevice *start_ahci_device(QPCIDevice *dev, void **hba_base); +static void free_ahci_device(QPCIDevice *dev); +static void ahci_test_port_spec(QPCIDevice *ahci, void *hba_base, + HBACap *hcap, uint8_t port); +static void ahci_test_pci_spec(QPCIDevice *ahci); +static void ahci_test_pci_caps(QPCIDevice *ahci, uint16_t header, + uint8_t offset); +static void ahci_test_satacap(QPCIDevice *ahci, uint8_t offset); +static void ahci_test_msicap(QPCIDevice *ahci, uint8_t offset); +static void ahci_test_pmcap(QPCIDevice *ahci, uint8_t offset); + +/*** Utilities ***/ + +static void string_bswap16(uint16_t *s, size_t bytes) +{ + g_assert_cmphex((bytes & 1), ==, 0); + bytes /= 2; + + while (bytes--) { + *s = bswap16(*s); + s++; + } +} + +/** + * Locate, verify, and return a handle to the AHCI device. + */ +static QPCIDevice *get_ahci_device(void) +{ + QPCIDevice *ahci; + + pcibus = qpci_init_pc(); + + /* Find the AHCI PCI device and verify it's the right one. */ + ahci = qpci_device_find(pcibus, QPCI_DEVFN(0x1F, 0x02)); + g_assert(ahci != NULL); + + ahci_fingerprint = qpci_config_readl(ahci, PCI_VENDOR_ID); + + switch (ahci_fingerprint) { + case AHCI_INTEL_ICH9: + break; + default: + /* Unknown device. */ + g_assert_not_reached(); + } + + return ahci; +} + +static void free_ahci_device(QPCIDevice *ahci) +{ + /* libqos doesn't have a function for this, so free it manually */ + g_free(ahci); + + if (pcibus) { + qpci_free_pc(pcibus); + pcibus = NULL; + } + + /* Clear our cached barsize information. */ + barsize = 0; +} + +/*** Test Setup & Teardown ***/ + +/** + * Launch QEMU with the given command line, + * and then set up interrupts and our guest malloc interface. + */ +static void qtest_boot(const char *cmdline_fmt, ...) +{ + va_list ap; + char *cmdline; + + va_start(ap, cmdline_fmt); + cmdline = g_strdup_vprintf(cmdline_fmt, ap); + va_end(ap); + + qtest_start(cmdline); + qtest_irq_intercept_in(global_qtest, "ioapic"); + guest_malloc = pc_alloc_init(); + + g_free(cmdline); +} + +/** + * Tear down the QEMU instance. + */ +static void qtest_shutdown(void) +{ + g_free(guest_malloc); + guest_malloc = NULL; + qtest_end(); +} + +/** + * Start a Q35 machine and bookmark a handle to the AHCI device. + */ +static QPCIDevice *ahci_boot(void) +{ + qtest_boot("-drive if=none,id=drive0,file=%s,cache=writeback,serial=%s" + " -M q35 " + "-device ide-hd,drive=drive0 " + "-global ide-hd.ver=%s", + tmp_path, "testdisk", "version"); + + /* Verify that we have an AHCI device present. */ + return get_ahci_device(); +} + +/** + * Clean up the PCI device, then terminate the QEMU instance. + */ +static void ahci_shutdown(QPCIDevice *ahci) +{ + free_ahci_device(ahci); + qtest_shutdown(); +} + +/*** Logical Device Initialization ***/ + +/** + * Start the PCI device and sanity-check default operation. + */ +static void ahci_pci_enable(QPCIDevice *ahci, void **hba_base) +{ + uint8_t reg; + + start_ahci_device(ahci, hba_base); + + switch (ahci_fingerprint) { + case AHCI_INTEL_ICH9: + /* ICH9 has a register at PCI 0x92 that + * acts as a master port enabler mask. */ + reg = qpci_config_readb(ahci, 0x92); + reg |= 0x3F; + qpci_config_writeb(ahci, 0x92, reg); + /* 0...0111111b -- bit significant, ports 0-5 enabled. */ + ASSERT_BIT_SET(qpci_config_readb(ahci, 0x92), 0x3F); + break; + } + +} + +/** + * Map BAR5/ABAR, and engage the PCI device. + */ +static QPCIDevice *start_ahci_device(QPCIDevice *ahci, void **hba_base) +{ + /* Map AHCI's ABAR (BAR5) */ + *hba_base = qpci_iomap(ahci, 5, &barsize); + + /* turns on pci.cmd.iose, pci.cmd.mse and pci.cmd.bme */ + qpci_device_enable(ahci); + + return ahci; +} + +/** + * Test and initialize the AHCI's HBA memory areas. + * Initialize and start any ports with devices attached. + * Bring the HBA into the idle state. + */ +static void ahci_hba_enable(QPCIDevice *ahci, void *hba_base) +{ + /* Bits of interest in this section: + * GHC.AE Global Host Control / AHCI Enable + * PxCMD.ST Port Command: Start + * PxCMD.SUD "Spin Up Device" + * PxCMD.POD "Power On Device" + * PxCMD.FRE "FIS Receive Enable" + * PxCMD.FR "FIS Receive Running" + * PxCMD.CR "Command List Running" + */ + + g_assert(ahci != NULL); + g_assert(hba_base != NULL); + + uint32_t reg, ports_impl, clb, fb; + uint16_t i; + uint8_t num_cmd_slots; + + g_assert(hba_base != 0); + + /* Set GHC.AE to 1 */ + AHCI_SET(AHCI_GHC, AHCI_GHC_AE); + reg = AHCI_RREG(AHCI_GHC); + ASSERT_BIT_SET(reg, AHCI_GHC_AE); + + /* Read CAP.NCS, how many command slots do we have? */ + reg = AHCI_RREG(AHCI_CAP); + num_cmd_slots = ((reg & AHCI_CAP_NCS) >> ctzl(AHCI_CAP_NCS)) + 1; + g_test_message("Number of Command Slots: %u", num_cmd_slots); + + /* Determine which ports are implemented. */ + ports_impl = AHCI_RREG(AHCI_PI); + + for (i = 0; ports_impl; ports_impl >>= 1, ++i) { + if (!(ports_impl & 0x01)) { + continue; + } + + g_test_message("Initializing port %u", i); + + reg = PX_RREG(i, AHCI_PX_CMD); + if (BITCLR(reg, AHCI_PX_CMD_ST | AHCI_PX_CMD_CR | + AHCI_PX_CMD_FRE | AHCI_PX_CMD_FR)) { + g_test_message("port is idle"); + } else { + g_test_message("port needs to be idled"); + PX_CLR(i, AHCI_PX_CMD, (AHCI_PX_CMD_ST | AHCI_PX_CMD_FRE)); + /* The port has 500ms to disengage. */ + usleep(500000); + reg = PX_RREG(i, AHCI_PX_CMD); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_CR); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_FR); + g_test_message("port is now idle"); + /* The spec does allow for possibly needing a PORT RESET + * or HBA reset if we fail to idle the port. */ + } + + /* Allocate Memory for the Command List Buffer & FIS Buffer */ + /* PxCLB space ... 0x20 per command, as in 4.2.2 p 36 */ + clb = guest_alloc(guest_malloc, num_cmd_slots * 0x20); + g_test_message("CLB: 0x%08x", clb); + PX_WREG(i, AHCI_PX_CLB, clb); + g_assert_cmphex(clb, ==, PX_RREG(i, AHCI_PX_CLB)); + + /* PxFB space ... 0x100, as in 4.2.1 p 35 */ + fb = guest_alloc(guest_malloc, 0x100); + g_test_message("FB: 0x%08x", fb); + PX_WREG(i, AHCI_PX_FB, fb); + g_assert_cmphex(fb, ==, PX_RREG(i, AHCI_PX_FB)); + + /* Clear PxSERR, PxIS, then IS.IPS[x] by writing '1's. */ + PX_WREG(i, AHCI_PX_SERR, 0xFFFFFFFF); + PX_WREG(i, AHCI_PX_IS, 0xFFFFFFFF); + AHCI_WREG(AHCI_IS, (1 << i)); + + /* Verify Interrupts Cleared */ + reg = PX_RREG(i, AHCI_PX_SERR); + g_assert_cmphex(reg, ==, 0); + + reg = PX_RREG(i, AHCI_PX_IS); + g_assert_cmphex(reg, ==, 0); + + reg = AHCI_RREG(AHCI_IS); + ASSERT_BIT_CLEAR(reg, (1 << i)); + + /* Enable All Interrupts: */ + PX_WREG(i, AHCI_PX_IE, 0xFFFFFFFF); + reg = PX_RREG(i, AHCI_PX_IE); + g_assert_cmphex(reg, ==, ~((uint32_t)AHCI_PX_IE_RESERVED)); + + /* Enable the FIS Receive Engine. */ + PX_SET(i, AHCI_PX_CMD, AHCI_PX_CMD_FRE); + reg = PX_RREG(i, AHCI_PX_CMD); + ASSERT_BIT_SET(reg, AHCI_PX_CMD_FR); + + /* AHCI 1.3 spec: if !STS.BSY, !STS.DRQ and PxSSTS.DET indicates + * physical presence, a device is present and may be started. However, + * PxSERR.DIAG.X /may/ need to be cleared a priori. */ + reg = PX_RREG(i, AHCI_PX_SERR); + if (BITSET(reg, AHCI_PX_SERR_DIAG_X)) { + PX_SET(i, AHCI_PX_SERR, AHCI_PX_SERR_DIAG_X); + } + + reg = PX_RREG(i, AHCI_PX_TFD); + if (BITCLR(reg, AHCI_PX_TFD_STS_BSY | AHCI_PX_TFD_STS_DRQ)) { + reg = PX_RREG(i, AHCI_PX_SSTS); + if ((reg & AHCI_PX_SSTS_DET) == SSTS_DET_ESTABLISHED) { + /* Device Found: set PxCMD.ST := 1 */ + PX_SET(i, AHCI_PX_CMD, AHCI_PX_CMD_ST); + ASSERT_BIT_SET(PX_RREG(i, AHCI_PX_CMD), AHCI_PX_CMD_CR); + g_test_message("Started Device %u", i); + } else if ((reg & AHCI_PX_SSTS_DET)) { + /* Device present, but in some unknown state. */ + g_assert_not_reached(); + } + } + } + + /* Enable GHC.IE */ + AHCI_SET(AHCI_GHC, AHCI_GHC_IE); + reg = AHCI_RREG(AHCI_GHC); + ASSERT_BIT_SET(reg, AHCI_GHC_IE); + + /* TODO: The device should now be idling and waiting for commands. + * In the future, a small test-case to inspect the Register D2H FIS + * and clear the initial interrupts might be good. */ +} + +/*** Specification Adherence Tests ***/ + +/** + * Implementation for test_pci_spec. Ensures PCI configuration space is sane. + */ +static void ahci_test_pci_spec(QPCIDevice *ahci) +{ + uint8_t datab; + uint16_t data; + uint32_t datal; + + /* Most of these bits should start cleared until we turn them on. */ + data = qpci_config_readw(ahci, PCI_COMMAND); + ASSERT_BIT_CLEAR(data, PCI_COMMAND_MEMORY); + ASSERT_BIT_CLEAR(data, PCI_COMMAND_MASTER); + ASSERT_BIT_CLEAR(data, PCI_COMMAND_SPECIAL); /* Reserved */ + ASSERT_BIT_CLEAR(data, PCI_COMMAND_VGA_PALETTE); /* Reserved */ + ASSERT_BIT_CLEAR(data, PCI_COMMAND_PARITY); + ASSERT_BIT_CLEAR(data, PCI_COMMAND_WAIT); /* Reserved */ + ASSERT_BIT_CLEAR(data, PCI_COMMAND_SERR); + ASSERT_BIT_CLEAR(data, PCI_COMMAND_FAST_BACK); + ASSERT_BIT_CLEAR(data, PCI_COMMAND_INTX_DISABLE); + ASSERT_BIT_CLEAR(data, 0xF800); /* Reserved */ + + data = qpci_config_readw(ahci, PCI_STATUS); + ASSERT_BIT_CLEAR(data, 0x01 | 0x02 | 0x04); /* Reserved */ + ASSERT_BIT_CLEAR(data, PCI_STATUS_INTERRUPT); + ASSERT_BIT_SET(data, PCI_STATUS_CAP_LIST); /* must be set */ + ASSERT_BIT_CLEAR(data, PCI_STATUS_UDF); /* Reserved */ + ASSERT_BIT_CLEAR(data, PCI_STATUS_PARITY); + ASSERT_BIT_CLEAR(data, PCI_STATUS_SIG_TARGET_ABORT); + ASSERT_BIT_CLEAR(data, PCI_STATUS_REC_TARGET_ABORT); + ASSERT_BIT_CLEAR(data, PCI_STATUS_REC_MASTER_ABORT); + ASSERT_BIT_CLEAR(data, PCI_STATUS_SIG_SYSTEM_ERROR); + ASSERT_BIT_CLEAR(data, PCI_STATUS_DETECTED_PARITY); + + /* RID occupies the low byte, CCs occupy the high three. */ + datal = qpci_config_readl(ahci, PCI_CLASS_REVISION); + if (ahci_pedantic) { + /* AHCI 1.3 specifies that at-boot, the RID should reset to 0x00, + * Though in practice this is likely seldom true. */ + ASSERT_BIT_CLEAR(datal, 0xFF); + } + + /* BCC *must* equal 0x01. */ + g_assert_cmphex(PCI_BCC(datal), ==, 0x01); + if (PCI_SCC(datal) == 0x01) { + /* IDE */ + ASSERT_BIT_SET(0x80000000, datal); + ASSERT_BIT_CLEAR(0x60000000, datal); + } else if (PCI_SCC(datal) == 0x04) { + /* RAID */ + g_assert_cmphex(PCI_PI(datal), ==, 0); + } else if (PCI_SCC(datal) == 0x06) { + /* AHCI */ + g_assert_cmphex(PCI_PI(datal), ==, 0x01); + } else { + g_assert_not_reached(); + } + + datab = qpci_config_readb(ahci, PCI_CACHE_LINE_SIZE); + g_assert_cmphex(datab, ==, 0); + + datab = qpci_config_readb(ahci, PCI_LATENCY_TIMER); + g_assert_cmphex(datab, ==, 0); + + /* Only the bottom 7 bits must be off. */ + datab = qpci_config_readb(ahci, PCI_HEADER_TYPE); + ASSERT_BIT_CLEAR(datab, 0x7F); + + /* BIST is optional, but the low 7 bits must always start off regardless. */ + datab = qpci_config_readb(ahci, PCI_BIST); + ASSERT_BIT_CLEAR(datab, 0x7F); + + /* BARS 0-4 do not have a boot spec, but ABAR/BAR5 must be clean. */ + datal = qpci_config_readl(ahci, PCI_BASE_ADDRESS_5); + g_assert_cmphex(datal, ==, 0); + + qpci_config_writel(ahci, PCI_BASE_ADDRESS_5, 0xFFFFFFFF); + datal = qpci_config_readl(ahci, PCI_BASE_ADDRESS_5); + /* ABAR must be 32-bit, memory mapped, non-prefetchable and + * must be >= 512 bytes. To that end, bits 0-8 must be off. */ + ASSERT_BIT_CLEAR(datal, 0xFF); + + /* Capability list MUST be present, */ + datal = qpci_config_readl(ahci, PCI_CAPABILITY_LIST); + /* But these bits are reserved. */ + ASSERT_BIT_CLEAR(datal, ~0xFF); + g_assert_cmphex(datal, !=, 0); + + /* Check specification adherence for capability extenstions. */ + data = qpci_config_readw(ahci, datal); + + switch (ahci_fingerprint) { + case AHCI_INTEL_ICH9: + /* Intel ICH9 Family Datasheet 14.1.19 p.550 */ + g_assert_cmphex((data & 0xFF), ==, PCI_CAP_ID_MSI); + break; + default: + /* AHCI 1.3, Section 2.1.14 -- CAP must point to PMCAP. */ + g_assert_cmphex((data & 0xFF), ==, PCI_CAP_ID_PM); + } + + ahci_test_pci_caps(ahci, data, (uint8_t)datal); + + /* Reserved. */ + datal = qpci_config_readl(ahci, PCI_CAPABILITY_LIST + 4); + g_assert_cmphex(datal, ==, 0); + + /* IPIN might vary, but ILINE must be off. */ + datab = qpci_config_readb(ahci, PCI_INTERRUPT_LINE); + g_assert_cmphex(datab, ==, 0); +} + +/** + * Test PCI capabilities for AHCI specification adherence. + */ +static void ahci_test_pci_caps(QPCIDevice *ahci, uint16_t header, + uint8_t offset) +{ + uint8_t cid = header & 0xFF; + uint8_t next = header >> 8; + + g_test_message("CID: %02x; next: %02x", cid, next); + + switch (cid) { + case PCI_CAP_ID_PM: + ahci_test_pmcap(ahci, offset); + break; + case PCI_CAP_ID_MSI: + ahci_test_msicap(ahci, offset); + break; + case PCI_CAP_ID_SATA: + ahci_test_satacap(ahci, offset); + break; + + default: + g_test_message("Unknown CAP 0x%02x", cid); + } + + if (next) { + ahci_test_pci_caps(ahci, qpci_config_readw(ahci, next), next); + } +} + +/** + * Test SATA PCI capabilitity for AHCI specification adherence. + */ +static void ahci_test_satacap(QPCIDevice *ahci, uint8_t offset) +{ + uint16_t dataw; + uint32_t datal; + + g_test_message("Verifying SATACAP"); + + /* Assert that the SATACAP version is 1.0, And reserved bits are empty. */ + dataw = qpci_config_readw(ahci, offset + 2); + g_assert_cmphex(dataw, ==, 0x10); + + /* Grab the SATACR1 register. */ + datal = qpci_config_readw(ahci, offset + 4); + + switch (datal & 0x0F) { + case 0x04: /* BAR0 */ + case 0x05: /* BAR1 */ + case 0x06: + case 0x07: + case 0x08: + case 0x09: /* BAR5 */ + case 0x0F: /* Immediately following SATACR1 in PCI config space. */ + break; + default: + /* Invalid BARLOC for the Index Data Pair. */ + g_assert_not_reached(); + } + + /* Reserved. */ + g_assert_cmphex((datal >> 24), ==, 0x00); +} + +/** + * Test MSI PCI capability for AHCI specification adherence. + */ +static void ahci_test_msicap(QPCIDevice *ahci, uint8_t offset) +{ + uint16_t dataw; + uint32_t datal; + + g_test_message("Verifying MSICAP"); + + dataw = qpci_config_readw(ahci, offset + PCI_MSI_FLAGS); + ASSERT_BIT_CLEAR(dataw, PCI_MSI_FLAGS_ENABLE); + ASSERT_BIT_CLEAR(dataw, PCI_MSI_FLAGS_QSIZE); + ASSERT_BIT_CLEAR(dataw, PCI_MSI_FLAGS_RESERVED); + + datal = qpci_config_readl(ahci, offset + PCI_MSI_ADDRESS_LO); + g_assert_cmphex(datal, ==, 0); + + if (dataw & PCI_MSI_FLAGS_64BIT) { + g_test_message("MSICAP is 64bit"); + datal = qpci_config_readl(ahci, offset + PCI_MSI_ADDRESS_HI); + g_assert_cmphex(datal, ==, 0); + dataw = qpci_config_readw(ahci, offset + PCI_MSI_DATA_64); + g_assert_cmphex(dataw, ==, 0); + } else { + g_test_message("MSICAP is 32bit"); + dataw = qpci_config_readw(ahci, offset + PCI_MSI_DATA_32); + g_assert_cmphex(dataw, ==, 0); + } +} + +/** + * Test Power Management PCI capability for AHCI specification adherence. + */ +static void ahci_test_pmcap(QPCIDevice *ahci, uint8_t offset) +{ + uint16_t dataw; + + g_test_message("Verifying PMCAP"); + + dataw = qpci_config_readw(ahci, offset + PCI_PM_PMC); + ASSERT_BIT_CLEAR(dataw, PCI_PM_CAP_PME_CLOCK); + ASSERT_BIT_CLEAR(dataw, PCI_PM_CAP_RESERVED); + ASSERT_BIT_CLEAR(dataw, PCI_PM_CAP_D1); + ASSERT_BIT_CLEAR(dataw, PCI_PM_CAP_D2); + + dataw = qpci_config_readw(ahci, offset + PCI_PM_CTRL); + ASSERT_BIT_CLEAR(dataw, PCI_PM_CTRL_STATE_MASK); + ASSERT_BIT_CLEAR(dataw, PCI_PM_CTRL_RESERVED); + ASSERT_BIT_CLEAR(dataw, PCI_PM_CTRL_DATA_SEL_MASK); + ASSERT_BIT_CLEAR(dataw, PCI_PM_CTRL_DATA_SCALE_MASK); +} + +static void ahci_test_hba_spec(QPCIDevice *ahci, void *hba_base) +{ + HBACap hcap; + unsigned i; + uint32_t cap, cap2, reg; + uint32_t ports; + uint8_t nports_impl; + uint8_t maxports; + + g_assert(ahci != 0); + g_assert(hba_base != 0); + + /* + * Note that the AHCI spec does expect the BIOS to set up a few things: + * CAP.SSS - Support for staggered spin-up (t/f) + * CAP.SMPS - Support for mechanical presence switches (t/f) + * PI - Ports Implemented (1-32) + * PxCMD.HPCP - Hot Plug Capable Port + * PxCMD.MPSP - Mechanical Presence Switch Present + * PxCMD.CPD - Cold Presence Detection support + * + * Additional items are touched if CAP.SSS is on, see AHCI 10.1.1 p.97: + * Foreach Port Implemented: + * -PxCMD.ST, PxCMD.CR, PxCMD.FRE, PxCMD.FR, PxSCTL.DET are 0 + * -PxCLB/U and PxFB/U are set to valid regions in memory + * -PxSUD is set to 1. + * -PxSSTS.DET is polled for presence; if detected, we continue: + * -PxSERR is cleared with 1's. + * -If PxTFD.STS.BSY, PxTFD.STS.DRQ, and PxTFD.STS.ERR are all zero, + * the device is ready. + */ + + /* 1 CAP - Capabilities Register */ + cap = AHCI_RREG(AHCI_CAP); + ASSERT_BIT_CLEAR(cap, AHCI_CAP_RESERVED); + + /* 2 GHC - Global Host Control */ + reg = AHCI_RREG(AHCI_GHC); + ASSERT_BIT_CLEAR(reg, AHCI_GHC_HR); + ASSERT_BIT_CLEAR(reg, AHCI_GHC_IE); + ASSERT_BIT_CLEAR(reg, AHCI_GHC_MRSM); + if (BITSET(cap, AHCI_CAP_SAM)) { + g_test_message("Supports AHCI-Only Mode: GHC_AE is Read-Only."); + ASSERT_BIT_SET(reg, AHCI_GHC_AE); + } else { + g_test_message("Supports AHCI/Legacy mix."); + ASSERT_BIT_CLEAR(reg, AHCI_GHC_AE); + } + + /* 3 IS - Interrupt Status */ + reg = AHCI_RREG(AHCI_IS); + g_assert_cmphex(reg, ==, 0); + + /* 4 PI - Ports Implemented */ + ports = AHCI_RREG(AHCI_PI); + /* Ports Implemented must be non-zero. */ + g_assert_cmphex(ports, !=, 0); + /* Ports Implemented must be <= Number of Ports. */ + nports_impl = ctpopl(ports); + g_assert_cmpuint(((AHCI_CAP_NP & cap) + 1), >=, nports_impl); + + g_assert_cmphex(barsize, >, 0); + /* Ports must be within the proper range. Given a mapping of SIZE, + * 256 bytes are used for global HBA control, and the rest is used + * for ports data, at 0x80 bytes each. */ + maxports = (barsize - HBA_DATA_REGION_SIZE) / HBA_PORT_DATA_SIZE; + /* e.g, 30 ports for 4K of memory. (4096 - 256) / 128 = 30 */ + g_assert_cmphex((reg >> maxports), ==, 0); + + /* 5 AHCI Version */ + reg = AHCI_RREG(AHCI_VS); + switch (reg) { + case AHCI_VERSION_0_95: + case AHCI_VERSION_1_0: + case AHCI_VERSION_1_1: + case AHCI_VERSION_1_2: + case AHCI_VERSION_1_3: + break; + default: + g_assert_not_reached(); + } + + /* 6 Command Completion Coalescing Control: depends on CAP.CCCS. */ + reg = AHCI_RREG(AHCI_CCCCTL); + if (BITSET(cap, AHCI_CAP_CCCS)) { + ASSERT_BIT_CLEAR(reg, AHCI_CCCCTL_EN); + ASSERT_BIT_CLEAR(reg, AHCI_CCCCTL_RESERVED); + ASSERT_BIT_SET(reg, AHCI_CCCCTL_CC); + ASSERT_BIT_SET(reg, AHCI_CCCCTL_TV); + } else { + g_assert_cmphex(reg, ==, 0); + } + + /* 7 CCC_PORTS */ + reg = AHCI_RREG(AHCI_CCCPORTS); + /* Must be zeroes initially regardless of CAP.CCCS */ + g_assert_cmphex(reg, ==, 0); + + /* 8 EM_LOC */ + reg = AHCI_RREG(AHCI_EMLOC); + if (BITCLR(cap, AHCI_CAP_EMS)) { + g_assert_cmphex(reg, ==, 0); + } + + /* 9 EM_CTL */ + reg = AHCI_RREG(AHCI_EMCTL); + if (BITSET(cap, AHCI_CAP_EMS)) { + ASSERT_BIT_CLEAR(reg, AHCI_EMCTL_STSMR); + ASSERT_BIT_CLEAR(reg, AHCI_EMCTL_CTLTM); + ASSERT_BIT_CLEAR(reg, AHCI_EMCTL_CTLRST); + ASSERT_BIT_CLEAR(reg, AHCI_EMCTL_RESERVED); + } else { + g_assert_cmphex(reg, ==, 0); + } + + /* 10 CAP2 -- Capabilities Extended */ + cap2 = AHCI_RREG(AHCI_CAP2); + ASSERT_BIT_CLEAR(cap2, AHCI_CAP2_RESERVED); + + /* 11 BOHC -- Bios/OS Handoff Control */ + reg = AHCI_RREG(AHCI_BOHC); + g_assert_cmphex(reg, ==, 0); + + /* 12 -- 23: Reserved */ + g_test_message("Verifying HBA reserved area is empty."); + for (i = AHCI_RESERVED; i < AHCI_NVMHCI; ++i) { + reg = AHCI_RREG(i); + g_assert_cmphex(reg, ==, 0); + } + + /* 24 -- 39: NVMHCI */ + if (BITCLR(cap2, AHCI_CAP2_NVMP)) { + g_test_message("Verifying HBA/NVMHCI area is empty."); + for (i = AHCI_NVMHCI; i < AHCI_VENDOR; ++i) { + reg = AHCI_RREG(i); + g_assert_cmphex(reg, ==, 0); + } + } + + /* 40 -- 63: Vendor */ + g_test_message("Verifying HBA/Vendor area is empty."); + for (i = AHCI_VENDOR; i < AHCI_PORTS; ++i) { + reg = AHCI_RREG(i); + g_assert_cmphex(reg, ==, 0); + } + + /* 64 -- XX: Port Space */ + hcap.cap = cap; + hcap.cap2 = cap2; + for (i = 0; ports || (i < maxports); ports >>= 1, ++i) { + if (BITSET(ports, 0x1)) { + g_test_message("Testing port %u for spec", i); + ahci_test_port_spec(ahci, hba_base, &hcap, i); + } else { + uint16_t j; + uint16_t low = AHCI_PORTS + (32 * i); + uint16_t high = AHCI_PORTS + (32 * (i + 1)); + g_test_message("Asserting unimplemented port %u " + "(reg [%u-%u]) is empty.", + i, low, high - 1); + for (j = low; j < high; ++j) { + reg = AHCI_RREG(j); + g_assert_cmphex(reg, ==, 0); + } + } + } +} + +/** + * Test the memory space for one port for specification adherence. + */ +static void ahci_test_port_spec(QPCIDevice *ahci, void *hba_base, + HBACap *hcap, uint8_t port) +{ + uint32_t reg; + unsigned i; + + /* (0) CLB */ + reg = PX_RREG(port, AHCI_PX_CLB); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CLB_RESERVED); + + /* (1) CLBU */ + if (BITCLR(hcap->cap, AHCI_CAP_S64A)) { + reg = PX_RREG(port, AHCI_PX_CLBU); + g_assert_cmphex(reg, ==, 0); + } + + /* (2) FB */ + reg = PX_RREG(port, AHCI_PX_FB); + ASSERT_BIT_CLEAR(reg, AHCI_PX_FB_RESERVED); + + /* (3) FBU */ + if (BITCLR(hcap->cap, AHCI_CAP_S64A)) { + reg = PX_RREG(port, AHCI_PX_FBU); + g_assert_cmphex(reg, ==, 0); + } + + /* (4) IS */ + reg = PX_RREG(port, AHCI_PX_IS); + g_assert_cmphex(reg, ==, 0); + + /* (5) IE */ + reg = PX_RREG(port, AHCI_PX_IE); + g_assert_cmphex(reg, ==, 0); + + /* (6) CMD */ + reg = PX_RREG(port, AHCI_PX_CMD); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_FRE); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_RESERVED); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_CCS); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_FR); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_CR); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_PMA); /* And RW only if CAP.SPM */ + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_APSTE); /* RW only if CAP2.APST */ + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_ATAPI); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_DLAE); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_ALPE); /* RW only if CAP.SALP */ + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_ASP); /* RW only if CAP.SALP */ + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_ICC); + /* If CPDetect support does not exist, CPState must be off. */ + if (BITCLR(reg, AHCI_PX_CMD_CPD)) { + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_CPS); + } + /* If MPSPresence is not set, MPSState must be off. */ + if (BITCLR(reg, AHCI_PX_CMD_MPSP)) { + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_MPSS); + } + /* If we do not support MPS, MPSS and MPSP must be off. */ + if (BITCLR(hcap->cap, AHCI_CAP_SMPS)) { + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_MPSS); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_MPSP); + } + /* If, via CPD or MPSP we detect a drive, HPCP must be on. */ + if (BITANY(reg, AHCI_PX_CMD_CPD || AHCI_PX_CMD_MPSP)) { + ASSERT_BIT_SET(reg, AHCI_PX_CMD_HPCP); + } + /* HPCP and ESP cannot both be active. */ + g_assert(!BITSET(reg, AHCI_PX_CMD_HPCP | AHCI_PX_CMD_ESP)); + /* If CAP.FBSS is not set, FBSCP must not be set. */ + if (BITCLR(hcap->cap, AHCI_CAP_FBSS)) { + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_FBSCP); + } + + /* (7) RESERVED */ + reg = PX_RREG(port, AHCI_PX_RES1); + g_assert_cmphex(reg, ==, 0); + + /* (8) TFD */ + reg = PX_RREG(port, AHCI_PX_TFD); + /* At boot, prior to an FIS being received, the TFD register should be 0x7F, + * which breaks down as follows, as seen in AHCI 1.3 sec 3.3.8, p. 27. */ + ASSERT_BIT_SET(reg, AHCI_PX_TFD_STS_ERR); + ASSERT_BIT_SET(reg, AHCI_PX_TFD_STS_CS1); + ASSERT_BIT_SET(reg, AHCI_PX_TFD_STS_DRQ); + ASSERT_BIT_SET(reg, AHCI_PX_TFD_STS_CS2); + ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_STS_BSY); + ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_ERR); + ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_RESERVED); + + /* (9) SIG */ + /* Though AHCI specifies the boot value should be 0xFFFFFFFF, + * Even when GHC.ST is zero, the AHCI HBA may receive the initial + * D2H register FIS and update the signature asynchronously, + * so we cannot expect a value here. AHCI 1.3, sec 3.3.9, pp 27-28 */ + + /* (10) SSTS / SCR0: SStatus */ + reg = PX_RREG(port, AHCI_PX_SSTS); + ASSERT_BIT_CLEAR(reg, AHCI_PX_SSTS_RESERVED); + /* Even though the register should be 0 at boot, it is asynchronous and + * prone to change, so we cannot test any well known value. */ + + /* (11) SCTL / SCR2: SControl */ + reg = PX_RREG(port, AHCI_PX_SCTL); + g_assert_cmphex(reg, ==, 0); + + /* (12) SERR / SCR1: SError */ + reg = PX_RREG(port, AHCI_PX_SERR); + g_assert_cmphex(reg, ==, 0); + + /* (13) SACT / SCR3: SActive */ + reg = PX_RREG(port, AHCI_PX_SACT); + g_assert_cmphex(reg, ==, 0); + + /* (14) CI */ + reg = PX_RREG(port, AHCI_PX_CI); + g_assert_cmphex(reg, ==, 0); + + /* (15) SNTF */ + reg = PX_RREG(port, AHCI_PX_SNTF); + g_assert_cmphex(reg, ==, 0); + + /* (16) FBS */ + reg = PX_RREG(port, AHCI_PX_FBS); + ASSERT_BIT_CLEAR(reg, AHCI_PX_FBS_EN); + ASSERT_BIT_CLEAR(reg, AHCI_PX_FBS_DEC); + ASSERT_BIT_CLEAR(reg, AHCI_PX_FBS_SDE); + ASSERT_BIT_CLEAR(reg, AHCI_PX_FBS_DEV); + ASSERT_BIT_CLEAR(reg, AHCI_PX_FBS_DWE); + ASSERT_BIT_CLEAR(reg, AHCI_PX_FBS_RESERVED); + if (BITSET(hcap->cap, AHCI_CAP_FBSS)) { + /* if Port-Multiplier FIS-based switching avail, ADO must >= 2 */ + g_assert((reg & AHCI_PX_FBS_ADO) >> ctzl(AHCI_PX_FBS_ADO) >= 2); + } + + /* [17 -- 27] RESERVED */ + for (i = AHCI_PX_RES2; i < AHCI_PX_VS; ++i) { + reg = PX_RREG(port, i); + g_assert_cmphex(reg, ==, 0); + } + + /* [28 -- 31] Vendor-Specific */ + for (i = AHCI_PX_VS; i < 32; ++i) { + reg = PX_RREG(port, i); + if (reg) { + g_test_message("INFO: Vendor register %u non-empty", i); + } + } +} + +/** + * Utilizing an initialized AHCI HBA, issue an IDENTIFY command to the first + * device we see, then read and check the response. + */ +static void ahci_test_identify(QPCIDevice *ahci, void *hba_base) +{ + RegD2HFIS *d2h = g_malloc0(0x20); + RegD2HFIS *pio = g_malloc0(0x20); + RegH2DFIS fis; + AHCICommand cmd; + PRD prd; + uint32_t ports, reg, clb, table, fb, data_ptr; + uint16_t buff[256]; + unsigned i; + int rc; + + g_assert(ahci != NULL); + g_assert(hba_base != NULL); + + /* We need to: + * (1) Create a Command Table Buffer and update the Command List Slot #0 + * to point to this buffer. + * (2) Construct an FIS host-to-device command structure, and write it to + * the top of the command table buffer. + * (3) Create a data buffer for the IDENTIFY response to be sent to + * (4) Create a Physical Region Descriptor that points to the data buffer, + * and write it to the bottom (offset 0x80) of the command table. + * (5) Now, PxCLB points to the command list, command 0 points to + * our table, and our table contains an FIS instruction and a + * PRD that points to our rx buffer. + * (6) We inform the HBA via PxCI that there is a command ready in slot #0. + */ + + /* Pick the first implemented and running port */ + ports = AHCI_RREG(AHCI_PI); + for (i = 0; i < 32; ports >>= 1, ++i) { + if (ports == 0) { + i = 32; + } + + if (!(ports & 0x01)) { + continue; + } + + reg = PX_RREG(i, AHCI_PX_CMD); + if (BITSET(reg, AHCI_PX_CMD_ST)) { + break; + } + } + g_assert_cmphex(i, <, 32); + g_test_message("Selected port %u for test", i); + + /* Clear out this port's interrupts (ignore the init register d2h fis) */ + reg = PX_RREG(i, AHCI_PX_IS); + PX_WREG(i, AHCI_PX_IS, reg); + g_assert_cmphex(PX_RREG(i, AHCI_PX_IS), ==, 0); + + /* Wipe the FIS-Recieve Buffer */ + fb = PX_RREG(i, AHCI_PX_FB); + g_assert_cmphex(fb, !=, 0); + qmemset(fb, 0x00, 0x100); + + /* Create a Command Table buffer. 0x80 is the smallest with a PRDTL of 0. */ + /* We need at least one PRD, so round up to the nearest 0x80 multiple. */ + table = guest_alloc(guest_malloc, CMD_TBL_SIZ(1)); + g_assert(table); + ASSERT_BIT_CLEAR(table, 0x7F); + + /* Create a data buffer ... where we will dump the IDENTIFY data to. */ + data_ptr = guest_alloc(guest_malloc, 512); + g_assert(data_ptr); + + /* Grab the Command List Buffer pointer */ + clb = PX_RREG(i, AHCI_PX_CLB); + g_assert(clb); + + /* Copy the existing Command #0 structure from the CLB into local memory, + * and build a new command #0. */ + memread(clb, &cmd, sizeof(cmd)); + cmd.b1 = 5; /* reg_h2d_fis is 5 double-words long */ + cmd.b2 = 0x04; /* clear PxTFD.STS.BSY when done */ + cmd.prdtl = cpu_to_le16(1); /* One PRD table entry. */ + cmd.prdbc = 0; + cmd.ctba = cpu_to_le32(table); + cmd.ctbau = 0; + + /* Construct our PRD, noting that DBC is 0-indexed. */ + prd.dba = cpu_to_le32(data_ptr); + prd.dbau = 0; + prd.res = 0; + /* 511+1 bytes, request DPS interrupt */ + prd.dbc = cpu_to_le32(511 | 0x80000000); + + /* Construct our Command FIS, Based on http://wiki.osdev.org/AHCI */ + memset(&fis, 0x00, sizeof(fis)); + fis.fis_type = 0x27; /* Register Host-to-Device FIS */ + fis.command = 0xEC; /* IDENTIFY */ + fis.device = 0; + fis.flags = 0x80; /* Indicate this is a command FIS */ + + /* We've committed nothing yet, no interrupts should be posted yet. */ + g_assert_cmphex(PX_RREG(i, AHCI_PX_IS), ==, 0); + + /* Commit the Command FIS to the Command Table */ + memwrite(table, &fis, sizeof(fis)); + + /* Commit the PRD entry to the Command Table */ + memwrite(table + 0x80, &prd, sizeof(prd)); + + /* Commit Command #0, pointing to the Table, to the Command List Buffer. */ + memwrite(clb, &cmd, sizeof(cmd)); + + /* Everything is in place, but we haven't given the go-ahead yet. */ + g_assert_cmphex(PX_RREG(i, AHCI_PX_IS), ==, 0); + + /* Issue Command #0 via PxCI */ + PX_WREG(i, AHCI_PX_CI, (1 << 0)); + while (BITSET(PX_RREG(i, AHCI_PX_TFD), AHCI_PX_TFD_STS_BSY)) { + usleep(50); + } + + /* Check for expected interrupts */ + reg = PX_RREG(i, AHCI_PX_IS); + ASSERT_BIT_SET(reg, AHCI_PX_IS_DHRS); + ASSERT_BIT_SET(reg, AHCI_PX_IS_PSS); + /* BUG: we expect AHCI_PX_IS_DPS to be set. */ + ASSERT_BIT_CLEAR(reg, AHCI_PX_IS_DPS); + + /* Clear expected interrupts and assert all interrupts now cleared. */ + PX_WREG(i, AHCI_PX_IS, AHCI_PX_IS_DHRS | AHCI_PX_IS_PSS | AHCI_PX_IS_DPS); + g_assert_cmphex(PX_RREG(i, AHCI_PX_IS), ==, 0); + + /* Check for errors. */ + reg = PX_RREG(i, AHCI_PX_SERR); + g_assert_cmphex(reg, ==, 0); + reg = PX_RREG(i, AHCI_PX_TFD); + ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_STS_ERR); + ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_ERR); + + /* Investigate CMD #0, assert that we read 512 bytes */ + memread(clb, &cmd, sizeof(cmd)); + g_assert_cmphex(512, ==, le32_to_cpu(cmd.prdbc)); + + /* Investigate FIS responses */ + memread(fb + 0x20, pio, 0x20); + memread(fb + 0x40, d2h, 0x20); + g_assert_cmphex(pio->fis_type, ==, 0x5f); + g_assert_cmphex(d2h->fis_type, ==, 0x34); + g_assert_cmphex(pio->flags, ==, d2h->flags); + g_assert_cmphex(pio->status, ==, d2h->status); + g_assert_cmphex(pio->error, ==, d2h->error); + + reg = PX_RREG(i, AHCI_PX_TFD); + g_assert_cmphex((reg & AHCI_PX_TFD_ERR), ==, pio->error); + g_assert_cmphex((reg & AHCI_PX_TFD_STS), ==, pio->status); + /* The PIO Setup FIS contains a "bytes read" field, which is a + * 16-bit value. The Physical Region Descriptor Byte Count is + * 32-bit, but for small transfers using one PRD, it should match. */ + g_assert_cmphex(le16_to_cpu(pio->res4), ==, le32_to_cpu(cmd.prdbc)); + + /* Last, but not least: Investigate the IDENTIFY response data. */ + memread(data_ptr, &buff, 512); + + /* Check serial number/version in the buffer */ + /* NB: IDENTIFY strings are packed in 16bit little endian chunks. + * Since we copy byte-for-byte in ahci-test, on both LE and BE, we need to + * unchunk this data. By contrast, ide-test copies 2 bytes at a time, and + * as a consequence, only needs to unchunk the data on LE machines. */ + string_bswap16(&buff[10], 20); + rc = memcmp(&buff[10], "testdisk ", 20); + g_assert_cmphex(rc, ==, 0); + + string_bswap16(&buff[23], 8); + rc = memcmp(&buff[23], "version ", 8); + g_assert_cmphex(rc, ==, 0); + + g_free(d2h); + g_free(pio); +} + +/******************************************************************************/ +/* Test Interfaces */ +/******************************************************************************/ + +/** + * Basic sanity test to boot a machine, find an AHCI device, and shutdown. + */ +static void test_sanity(void) +{ + QPCIDevice *ahci; + ahci = ahci_boot(); + ahci_shutdown(ahci); +} + +/** + * Ensure that the PCI configuration space for the AHCI device is in-line with + * the AHCI 1.3 specification for initial values. + */ +static void test_pci_spec(void) +{ + QPCIDevice *ahci; + ahci = ahci_boot(); + ahci_test_pci_spec(ahci); + ahci_shutdown(ahci); +} + +/** + * Engage the PCI AHCI device and sanity check the response. + * Perform additional PCI config space bringup for the HBA. + */ +static void test_pci_enable(void) +{ + QPCIDevice *ahci; + void *hba_base; + ahci = ahci_boot(); + ahci_pci_enable(ahci, &hba_base); + ahci_shutdown(ahci); +} + +/** + * Investigate the memory mapped regions of the HBA, + * and test them for AHCI specification adherence. + */ +static void test_hba_spec(void) +{ + QPCIDevice *ahci; + void *hba_base; + + ahci = ahci_boot(); + ahci_pci_enable(ahci, &hba_base); + ahci_test_hba_spec(ahci, hba_base); + ahci_shutdown(ahci); +} + +/** + * Engage the HBA functionality of the AHCI PCI device, + * and bring it into a functional idle state. + */ +static void test_hba_enable(void) +{ + QPCIDevice *ahci; + void *hba_base; + + ahci = ahci_boot(); + ahci_pci_enable(ahci, &hba_base); + ahci_hba_enable(ahci, hba_base); + ahci_shutdown(ahci); +} + +/** + * Bring up the device and issue an IDENTIFY command. + * Inspect the state of the HBA device and the data returned. + */ +static void test_identify(void) +{ + QPCIDevice *ahci; + void *hba_base; + + ahci = ahci_boot(); + ahci_pci_enable(ahci, &hba_base); + ahci_hba_enable(ahci, hba_base); + ahci_test_identify(ahci, hba_base); + ahci_shutdown(ahci); +} + +/******************************************************************************/ + +int main(int argc, char **argv) +{ + const char *arch; + int fd; + int ret; + int c; + + static struct option long_options[] = { + {"pedantic", no_argument, 0, 'p' }, + {0, 0, 0, 0}, + }; + + /* Should be first to utilize g_test functionality, So we can see errors. */ + g_test_init(&argc, &argv, NULL); + + while (1) { + c = getopt_long(argc, argv, "", long_options, NULL); + if (c == -1) { + break; + } + switch (c) { + case -1: + break; + case 'p': + ahci_pedantic = 1; + break; + default: + fprintf(stderr, "Unrecognized ahci_test option.\n"); + g_assert_not_reached(); + } + } + + /* Check architecture */ + arch = qtest_get_arch(); + if (strcmp(arch, "i386") && strcmp(arch, "x86_64")) { + g_test_message("Skipping test for non-x86"); + return 0; + } + + /* Create a temporary raw image */ + fd = mkstemp(tmp_path); + g_assert(fd >= 0); + ret = ftruncate(fd, TEST_IMAGE_SIZE); + g_assert(ret == 0); + close(fd); + + /* Run the tests */ + qtest_add_func("/ahci/sanity", test_sanity); + qtest_add_func("/ahci/pci_spec", test_pci_spec); + qtest_add_func("/ahci/pci_enable", test_pci_enable); + qtest_add_func("/ahci/hba_spec", test_hba_spec); + qtest_add_func("/ahci/hba_enable", test_hba_enable); + qtest_add_func("/ahci/identify", test_identify); + + ret = g_test_run(); + + /* Cleanup */ + unlink(tmp_path); + + return ret; +} diff --git a/tests/bios-tables-test.c b/tests/bios-tables-test.c index 045eb2757..9e4d20592 100644 --- a/tests/bios-tables-test.c +++ b/tests/bios-tables-test.c @@ -714,14 +714,12 @@ static void test_acpi_one(const char *params, test_data *data) uint8_t signature_high; uint16_t signature; int i; - const char *device = ""; - if (!g_strcmp0(data->machine, MACHINE_Q35)) { - device = ",id=hd -device ide-hd,drive=hd"; - } + args = g_strdup_printf("-net none -display none %s " + "-drive id=hd0,if=none,file=%s " + "-device ide-hd,drive=hd0 ", + params ? params : "", disk); - args = g_strdup_printf("-net none -display none %s -drive file=%s%s,", - params ? params : "", disk, device); qtest_start(args); /* Wait at most 1 minute */ @@ -790,6 +788,11 @@ int main(int argc, char *argv[]) const char *arch = qtest_get_arch(); FILE *f = fopen(disk, "w"); int ret; + + if (!f) { + fprintf(stderr, "Couldn't open \"%s\": %s", disk, strerror(errno)); + return 1; + } fwrite(boot_sector, 1, sizeof boot_sector, f); fclose(f); diff --git a/tests/blockdev-test.c b/tests/blockdev-test.c deleted file mode 100644 index c940e0069..000000000 --- a/tests/blockdev-test.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - * blockdev.c test cases - * - * Copyright (C) 2013 Red Hat Inc. - * - * Authors: - * Stefan Hajnoczi <stefanha@redhat.com> - * - * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. - * See the COPYING.LIB file in the top-level directory. - */ - -#include <glib.h> -#include <string.h> -#include "libqtest.h" - -static void test_drive_add_empty(void) -{ - QDict *response; - const char *response_return; - - /* Start with an empty drive */ - qtest_start("-drive if=none,id=drive0"); - - /* Delete the drive */ - response = qmp("{\"execute\": \"human-monitor-command\"," - " \"arguments\": {" - " \"command-line\": \"drive_del drive0\"" - "}}"); - g_assert(response); - response_return = qdict_get_try_str(response, "return"); - g_assert(response_return); - g_assert(strcmp(response_return, "") == 0); - QDECREF(response); - - /* Ensure re-adding the drive works - there should be no duplicate ID error - * because the old drive must be gone. - */ - response = qmp("{\"execute\": \"human-monitor-command\"," - " \"arguments\": {" - " \"command-line\": \"drive_add 0 if=none,id=drive0\"" - "}}"); - g_assert(response); - response_return = qdict_get_try_str(response, "return"); - g_assert(response_return); - g_assert(strcmp(response_return, "OK\r\n") == 0); - QDECREF(response); - - qtest_end(); -} - -int main(int argc, char **argv) -{ - g_test_init(&argc, &argv, NULL); - - qtest_add_func("/qmp/drive_add_empty", test_drive_add_empty); - - return g_test_run(); -} diff --git a/tests/drive_del-test.c b/tests/drive_del-test.c new file mode 100644 index 000000000..53fa96926 --- /dev/null +++ b/tests/drive_del-test.c @@ -0,0 +1,137 @@ +/* + * blockdev.c test cases + * + * Copyright (C) 2013-2014 Red Hat Inc. + * + * Authors: + * Stefan Hajnoczi <stefanha@redhat.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ + +#include <glib.h> +#include <string.h> +#include "libqtest.h" + +static void drive_add(void) +{ + QDict *response; + + response = qmp("{'execute': 'human-monitor-command'," + " 'arguments': {" + " 'command-line': 'drive_add 0 if=none,id=drive0'" + "}}"); + g_assert(response); + g_assert_cmpstr(qdict_get_try_str(response, "return"), ==, "OK\r\n"); + QDECREF(response); +} + +static void drive_del(void) +{ + QDict *response; + + response = qmp("{'execute': 'human-monitor-command'," + " 'arguments': {" + " 'command-line': 'drive_del drive0'" + "}}"); + g_assert(response); + g_assert_cmpstr(qdict_get_try_str(response, "return"), ==, ""); + QDECREF(response); +} + +static void device_del(void) +{ + QDict *response; + + /* Complication: ignore DEVICE_DELETED event */ + qmp_discard_response("{'execute': 'device_del'," + " 'arguments': { 'id': 'dev0' } }"); + response = qmp_receive(); + g_assert(response); + g_assert(qdict_haskey(response, "return")); + QDECREF(response); +} + +static void test_drive_without_dev(void) +{ + /* Start with an empty drive */ + qtest_start("-drive if=none,id=drive0"); + + /* Delete the drive */ + drive_del(); + + /* Ensure re-adding the drive works - there should be no duplicate ID error + * because the old drive must be gone. + */ + drive_add(); + + qtest_end(); +} + +static void test_after_failed_device_add(void) +{ + QDict *response; + QDict *error; + + qtest_start("-drive if=none,id=drive0"); + + /* Make device_add fail. If this leaks the virtio-blk-pci device then a + * reference to drive0 will also be held (via qdev properties). + */ + response = qmp("{'execute': 'device_add'," + " 'arguments': {" + " 'driver': 'virtio-blk-pci'," + " 'drive': 'drive0'" + "}}"); + g_assert(response); + error = qdict_get_qdict(response, "error"); + g_assert_cmpstr(qdict_get_try_str(error, "class"), ==, "GenericError"); + QDECREF(response); + + /* Delete the drive */ + drive_del(); + + /* Try to re-add the drive. This fails with duplicate IDs if a leaked + * virtio-blk-pci exists that holds a reference to the old drive0. + */ + drive_add(); + + qtest_end(); +} + +static void test_drive_del_device_del(void) +{ + /* Start with a drive used by a device that unplugs instantaneously */ + qtest_start("-drive if=none,id=drive0,file=/dev/null" + " -device virtio-scsi-pci" + " -device scsi-hd,drive=drive0,id=dev0"); + + /* + * Delete the drive, and then the device + * Doing it in this order takes notoriously tricky special paths + */ + drive_del(); + device_del(); + + qtest_end(); +} + +int main(int argc, char **argv) +{ + const char *arch = qtest_get_arch(); + + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/drive_del/without-dev", test_drive_without_dev); + + /* TODO I guess any arch with PCI would do */ + if (!strcmp(arch, "i386") || !strcmp(arch, "x86_64")) { + qtest_add_func("/drive_del/after_failed_device_add", + test_after_failed_device_add); + qtest_add_func("/blockdev/drive_del_device_del", + test_drive_del_device_del); + } + + return g_test_run(); +} diff --git a/tests/ide-test.c b/tests/ide-test.c index 4a0d97f19..b7a97e936 100644 --- a/tests/ide-test.c +++ b/tests/ide-test.c @@ -106,6 +106,7 @@ static QPCIBus *pcibus = NULL; static QGuestAllocator *guest_malloc; static char tmp_path[] = "/tmp/qtest.XXXXXX"; +static char debug_path[] = "/tmp/qtest-blkdebug.XXXXXX"; static void ide_test_start(const char *cmdline_fmt, ...) { @@ -119,10 +120,14 @@ static void ide_test_start(const char *cmdline_fmt, ...) qtest_start(cmdline); qtest_irq_intercept_in(global_qtest, "ioapic"); guest_malloc = pc_alloc_init(); + + g_free(cmdline); } static void ide_test_quit(void) { + pc_alloc_uninit(guest_malloc); + guest_malloc = NULL; qtest_end(); } @@ -145,7 +150,7 @@ static QPCIDevice *get_pci_device(uint16_t *bmdma_base) g_assert(device_id == PCI_DEVICE_ID_INTEL_82371SB_1); /* Map bmdma BAR */ - *bmdma_base = (uint16_t)(uintptr_t) qpci_iomap(dev, 4); + *bmdma_base = (uint16_t)(uintptr_t) qpci_iomap(dev, 4, NULL); qpci_device_enable(dev); @@ -489,6 +494,91 @@ static void test_flush(void) ide_test_quit(); } +static void prepare_blkdebug_script(const char *debug_fn, const char *event) +{ + FILE *debug_file = fopen(debug_fn, "w"); + int ret; + + fprintf(debug_file, "[inject-error]\n"); + fprintf(debug_file, "event = \"%s\"\n", event); + fprintf(debug_file, "errno = \"5\"\n"); + fprintf(debug_file, "state = \"1\"\n"); + fprintf(debug_file, "immediately = \"off\"\n"); + fprintf(debug_file, "once = \"on\"\n"); + + fprintf(debug_file, "[set-state]\n"); + fprintf(debug_file, "event = \"%s\"\n", event); + fprintf(debug_file, "new_state = \"2\"\n"); + fflush(debug_file); + g_assert(!ferror(debug_file)); + + ret = fclose(debug_file); + g_assert(ret == 0); +} + +static void test_retry_flush(void) +{ + uint8_t data; + const char *s; + QDict *response; + + prepare_blkdebug_script(debug_path, "flush_to_disk"); + + ide_test_start( + "-vnc none " + "-drive file=blkdebug:%s:%s,if=ide,cache=writeback,rerror=stop,werror=stop", + debug_path, tmp_path); + + /* FLUSH CACHE command on device 0*/ + outb(IDE_BASE + reg_device, 0); + outb(IDE_BASE + reg_command, CMD_FLUSH_CACHE); + + /* Check status while request is in flight*/ + data = inb(IDE_BASE + reg_status); + assert_bit_set(data, BSY | DRDY); + assert_bit_clear(data, DF | ERR | DRQ); + + for (;; response = NULL) { + response = qmp_receive(); + if ((qdict_haskey(response, "event")) && + (strcmp(qdict_get_str(response, "event"), "STOP") == 0)) { + QDECREF(response); + break; + } + QDECREF(response); + } + + /* Complete the command */ + s = "{'execute':'cont' }"; + qmp_discard_response(s); + + /* Check registers */ + data = inb(IDE_BASE + reg_device); + g_assert_cmpint(data & DEV, ==, 0); + + do { + data = inb(IDE_BASE + reg_status); + } while (data & BSY); + + assert_bit_set(data, DRDY); + assert_bit_clear(data, BSY | DF | ERR | DRQ); + + ide_test_quit(); +} + +static void test_flush_nodev(void) +{ + ide_test_start(""); + + /* FLUSH CACHE command on device 0*/ + outb(IDE_BASE + reg_device, 0); + outb(IDE_BASE + reg_command, CMD_FLUSH_CACHE); + + /* Just testing that qemu doesn't crash... */ + + ide_test_quit(); +} + int main(int argc, char **argv) { const char *arch = qtest_get_arch(); @@ -501,6 +591,11 @@ int main(int argc, char **argv) return 0; } + /* Create temporary blkdebug instructions */ + fd = mkstemp(debug_path); + g_assert(fd >= 0); + close(fd); + /* Create a temporary raw image */ fd = mkstemp(tmp_path); g_assert(fd >= 0); @@ -521,11 +616,15 @@ int main(int argc, char **argv) qtest_add_func("/ide/bmdma/teardown", test_bmdma_teardown); qtest_add_func("/ide/flush", test_flush); + qtest_add_func("/ide/flush_nodev", test_flush_nodev); + + qtest_add_func("/ide/retry/flush", test_retry_flush); ret = g_test_run(); /* Cleanup */ unlink(tmp_path); + unlink(debug_path); return ret; } diff --git a/tests/image-fuzzer/qcow2/__init__.py b/tests/image-fuzzer/qcow2/__init__.py new file mode 100644 index 000000000..e2ebe1931 --- /dev/null +++ b/tests/image-fuzzer/qcow2/__init__.py @@ -0,0 +1 @@ +from layout import create_image diff --git a/tests/image-fuzzer/qcow2/fuzz.py b/tests/image-fuzzer/qcow2/fuzz.py new file mode 100644 index 000000000..20eba6bc1 --- /dev/null +++ b/tests/image-fuzzer/qcow2/fuzz.py @@ -0,0 +1,367 @@ +# 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 +UINT16 = 0xffff +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] +UINT16_V = [0, 0x100, 0x1000, UINT16/4, UINT16/2 - 1, UINT16/2, UINT16/2 + 1, + UINT16 - 1, UINT16] +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 + + +def refcount_table_entry(current): + """Fuzz an entry of the refcount table.""" + constraints = UINT64_V + return selector(current, constraints) + + +def refcount_block_entry(current): + """Fuzz an entry of a refcount block.""" + constraints = UINT16_V + return selector(current, constraints) diff --git a/tests/image-fuzzer/qcow2/layout.py b/tests/image-fuzzer/qcow2/layout.py new file mode 100644 index 000000000..63e801f4e --- /dev/null +++ b/tests/image-fuzzer/qcow2/layout.py @@ -0,0 +1,612 @@ +# 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.refcount_table = FieldsList() + self.refcount_blocks = 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, + self.refcount_table, self.refcount_blocks) + + 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 create_refcount_structures(self): + """Generate random refcount blocks and refcount table.""" + def allocate_rfc_blocks(data, size): + """Return indices of clusters allocated for refcount blocks.""" + cluster_ids = set() + diff = block_ids = set([x / size for x in data]) + while len(diff) != 0: + # Allocate all yet not allocated clusters + new = self._get_available_clusters(data | cluster_ids, + len(diff)) + # Indices of new refcount blocks necessary to cover clusters + # in 'new' + diff = set([x / size for x in new]) - block_ids + cluster_ids |= new + block_ids |= diff + return cluster_ids, block_ids + + def allocate_rfc_table(data, init_blocks, block_size): + """Return indices of clusters allocated for the refcount table + and updated indices of clusters allocated for blocks and indices + of blocks. + """ + blocks = set(init_blocks) + clusters = set() + # Number of entries in one cluster of the refcount table + size = self.cluster_size / UINT64_S + # Number of clusters necessary for the refcount table based on + # the current number of refcount blocks + table_size = int(ceil((max(blocks) + 1) / float(size))) + # Index of the first cluster of the refcount table + table_start = self._get_adjacent_clusters(data, table_size + 1) + # Clusters allocated for the current length of the refcount table + table_clusters = set(range(table_start, table_start + table_size)) + # Clusters allocated for the refcount table including + # last optional one for potential l1 growth + table_clusters_allocated = set(range(table_start, table_start + + table_size + 1)) + # New refcount blocks necessary for clusters occupied by the + # refcount table + diff = set([c / block_size for c in table_clusters]) - blocks + blocks |= diff + while len(diff) != 0: + # Allocate clusters for new refcount blocks + new = self._get_available_clusters((data | clusters) | + table_clusters_allocated, + len(diff)) + # Indices of new refcount blocks necessary to cover + # clusters in 'new' + diff = set([x / block_size for x in new]) - blocks + clusters |= new + blocks |= diff + # Check if the refcount table needs one more cluster + if int(ceil((max(blocks) + 1) / float(size))) > table_size: + new_block_id = (table_start + table_size) / block_size + # Check if the additional table cluster needs + # one more refcount block + if new_block_id not in blocks: + diff.add(new_block_id) + table_clusters.add(table_start + table_size) + table_size += 1 + return table_clusters, blocks, clusters + + def create_table_entry(table_offset, block_cluster, block_size, + cluster): + """Generate a refcount table entry.""" + offset = table_offset + UINT64_S * (cluster / block_size) + return ['>Q', offset, block_cluster * self.cluster_size, + 'refcount_table_entry'] + + def create_block_entry(block_cluster, block_size, cluster): + """Generate a list of entries for the current block.""" + entry_size = self.cluster_size / block_size + offset = block_cluster * self.cluster_size + entry_offset = offset + entry_size * (cluster % block_size) + # While snapshots are not supported all refcounts are set to 1 + return ['>H', entry_offset, 1, 'refcount_block_entry'] + # Size of a block entry in bits + refcount_bits = 1 << self.header['refcount_order'][0].value + # Number of refcount entries per refcount block + # Convert self.cluster_size from bytes to bits to have the same + # base for the numerator and denominator + block_size = self.cluster_size * 8 / refcount_bits + meta_data = self._get_metadata() + 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 + block_clusters = set([random.choice(list(set(range(1, 4)) - + meta_data))]) + block_ids = set([0]) + table_clusters = set([random.choice(list(set(range(1, 4)) - + meta_data - + block_clusters))]) + else: + block_clusters, block_ids = \ + allocate_rfc_blocks(self.data_clusters | + meta_data, block_size) + table_clusters, block_ids, new_clusters = \ + allocate_rfc_table(self.data_clusters | + meta_data | + block_clusters, + block_ids, + block_size) + block_clusters |= new_clusters + + meta_data |= block_clusters | table_clusters + table_offset = min(table_clusters) * self.cluster_size + block_id = None + # Clusters allocated for refcount blocks + block_clusters = list(block_clusters) + # Indices of refcount blocks + block_ids = list(block_ids) + # Refcount table entries + rfc_table = [] + # Refcount entries + rfc_blocks = [] + + for cluster in sorted(self.data_clusters | meta_data): + if cluster / block_size != block_id: + block_id = cluster / block_size + block_cluster = block_clusters[block_ids.index(block_id)] + rfc_table.append(create_table_entry(table_offset, + block_cluster, + block_size, cluster)) + rfc_blocks.append(create_block_entry(block_cluster, block_size, + cluster)) + self.refcount_table = FieldsList(rfc_table) + self.refcount_blocks = FieldsList(rfc_blocks) + + self.header['refcount_table_offset'][0].value = table_offset + self.header['refcount_table_clusters'][0].value = len(table_clusters) + + 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.create_refcount_structures() + image.fuzz(fields_to_fuzz) + image.write(test_img_path) + return image.image_size diff --git a/tests/image-fuzzer/runner.py b/tests/image-fuzzer/runner.py new file mode 100755 index 000000000..0a8743ef4 --- /dev/null +++ b/tests/image-fuzzer/runner.py @@ -0,0 +1,437 @@ +#!/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 time +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. + """ + + class Alarm(Exception): + """Exception for signal.alarm events.""" + pass + + def handler(*args): + """Notify that an alarm event occurred.""" + raise Alarm + + signal.signal(signal.SIGALRM, handler) + signal.alarm(600) + term_signal = signal.SIGKILL + devnull = open('/dev/null', 'r+') + process = subprocess.Popen(q_args, stdin=devnull, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + try: + out, err = process.communicate() + signal.alarm(0) + fd.write(out) + fd.write(err) + fd.flush() + return process.returncode + + except Alarm: + os.kill(process.pid, term_signal) + fd.write('The command was terminated by timeout.\n') + fd.flush() + return -term_signal + + +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', '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', '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("%sError: Start of '%s' failed. Reason: %s\n\n" + % (test_summary, 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("%sFAIL: Test terminated by signal %s\n\n" + % (test_summary, 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("%sPASS: Application exited with the code " \ + "'%d'\n\n" % (test_summary, 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 + -d, --duration=NUMBER finish tests after NUMBER of seconds + -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() + + def should_continue(duration, start_time): + """Return True if a new test can be started and False otherwise.""" + current_time = int(time.time()) + return (duration is None) or (current_time - start_time < duration) + + try: + opts, args = getopt.gnu_getopt(sys.argv[1:], 'c:hs:kvd:', + ['command=', 'help', 'seed=', 'config=', + 'keep_passed', 'verbose', 'duration=']) + 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 + duration = 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 in ('-d', '--duration'): + duration = int(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 + start_time = int(time.time()) + test_id = count(1) + while should_continue(duration, start_time): + try: + run_test(str(test_id.next()), seed, work_dir, run_log, cleanup, + log_all, command, config) + except (KeyboardInterrupt, SystemExit): + sys.exit(1) + + if seed is not None: + break diff --git a/tests/libqos/malloc-pc.c b/tests/libqos/malloc-pc.c index db1496c66..f4218c645 100644 --- a/tests/libqos/malloc-pc.c +++ b/tests/libqos/malloc-pc.c @@ -17,45 +17,294 @@ #include "hw/nvram/fw_cfg.h" #include "qemu-common.h" +#include "qemu/queue.h" #include <glib.h> #define PAGE_SIZE (4096) +#define MLIST_ENTNAME entries +typedef QTAILQ_HEAD(MemList, MemBlock) MemList; +typedef struct MemBlock { + QTAILQ_ENTRY(MemBlock) MLIST_ENTNAME; + uint64_t size; + uint64_t addr; +} MemBlock; + typedef struct PCAlloc { QGuestAllocator alloc; - + PCAllocOpts opts; uint64_t start; uint64_t end; + + MemList used; + MemList free; } PCAlloc; -static uint64_t pc_alloc(QGuestAllocator *allocator, size_t size) +static MemBlock *mlist_new(uint64_t addr, uint64_t size) { - PCAlloc *s = container_of(allocator, PCAlloc, alloc); - uint64_t addr; + MemBlock *block; + + if (!size) { + return NULL; + } + block = g_malloc0(sizeof(MemBlock)); + block->addr = addr; + block->size = size; - size += (PAGE_SIZE - 1); - size &= PAGE_SIZE; + return block; +} + +static void mlist_delete(MemList *list, MemBlock *node) +{ + g_assert(list && node); + QTAILQ_REMOVE(list, node, MLIST_ENTNAME); + g_free(node); +} + +static MemBlock *mlist_find_key(MemList *head, uint64_t addr) +{ + MemBlock *node; + QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { + if (node->addr == addr) { + return node; + } + } + return NULL; +} + +static MemBlock *mlist_find_space(MemList *head, uint64_t size) +{ + MemBlock *node; + + QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { + if (node->size >= size) { + return node; + } + } + return NULL; +} + +static MemBlock *mlist_sort_insert(MemList *head, MemBlock *insr) +{ + MemBlock *node; + g_assert(head && insr); + + QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { + if (insr->addr < node->addr) { + QTAILQ_INSERT_BEFORE(node, insr, MLIST_ENTNAME); + return insr; + } + } + + QTAILQ_INSERT_TAIL(head, insr, MLIST_ENTNAME); + return insr; +} + +static inline uint64_t mlist_boundary(MemBlock *node) +{ + return node->size + node->addr; +} + +static MemBlock *mlist_join(MemList *head, MemBlock *left, MemBlock *right) +{ + g_assert(head && left && right); + + left->size += right->size; + mlist_delete(head, right); + return left; +} + +static void mlist_coalesce(MemList *head, MemBlock *node) +{ + g_assert(node); + MemBlock *left; + MemBlock *right; + char merge; + + do { + merge = 0; + left = QTAILQ_PREV(node, MemList, MLIST_ENTNAME); + right = QTAILQ_NEXT(node, MLIST_ENTNAME); + + /* clowns to the left of me */ + if (left && mlist_boundary(left) == node->addr) { + node = mlist_join(head, left, node); + merge = 1; + } + + /* jokers to the right */ + if (right && mlist_boundary(node) == right->addr) { + node = mlist_join(head, node, right); + merge = 1; + } + + } while (merge); +} + +static uint64_t pc_mlist_fulfill(PCAlloc *s, MemBlock *freenode, uint64_t size) +{ + uint64_t addr; + MemBlock *usednode; - g_assert_cmpint((s->start + size), <=, s->end); + g_assert(freenode); + g_assert_cmpint(freenode->size, >=, size); - addr = s->start; - s->start += size; + addr = freenode->addr; + if (freenode->size == size) { + /* re-use this freenode as our used node */ + QTAILQ_REMOVE(&s->free, freenode, MLIST_ENTNAME); + usednode = freenode; + } else { + /* adjust the free node and create a new used node */ + freenode->addr += size; + freenode->size -= size; + usednode = mlist_new(addr, size); + } + mlist_sort_insert(&s->used, usednode); return addr; } +/* To assert the correctness of the list. + * Used only if PC_ALLOC_PARANOID is set. */ +static void pc_mlist_check(PCAlloc *s) +{ + MemBlock *node; + uint64_t addr = s->start > 0 ? s->start - 1 : 0; + uint64_t next = s->start; + + QTAILQ_FOREACH(node, &s->free, MLIST_ENTNAME) { + g_assert_cmpint(node->addr, >, addr); + g_assert_cmpint(node->addr, >=, next); + addr = node->addr; + next = node->addr + node->size; + } + + addr = s->start > 0 ? s->start - 1 : 0; + next = s->start; + QTAILQ_FOREACH(node, &s->used, MLIST_ENTNAME) { + g_assert_cmpint(node->addr, >, addr); + g_assert_cmpint(node->addr, >=, next); + addr = node->addr; + next = node->addr + node->size; + } +} + +static uint64_t pc_mlist_alloc(PCAlloc *s, uint64_t size) +{ + MemBlock *node; + + node = mlist_find_space(&s->free, size); + if (!node) { + fprintf(stderr, "Out of guest memory.\n"); + g_assert_not_reached(); + } + return pc_mlist_fulfill(s, node, size); +} + +static void pc_mlist_free(PCAlloc *s, uint64_t addr) +{ + MemBlock *node; + + if (addr == 0) { + return; + } + + node = mlist_find_key(&s->used, addr); + if (!node) { + fprintf(stderr, "Error: no record found for an allocation at " + "0x%016" PRIx64 ".\n", + addr); + g_assert_not_reached(); + } + + /* Rip it out of the used list and re-insert back into the free list. */ + QTAILQ_REMOVE(&s->used, node, MLIST_ENTNAME); + mlist_sort_insert(&s->free, node); + mlist_coalesce(&s->free, node); +} + +static uint64_t pc_alloc(QGuestAllocator *allocator, size_t size) +{ + PCAlloc *s = container_of(allocator, PCAlloc, alloc); + uint64_t rsize = size; + uint64_t naddr; + + rsize += (PAGE_SIZE - 1); + rsize &= -PAGE_SIZE; + g_assert_cmpint((s->start + rsize), <=, s->end); + g_assert_cmpint(rsize, >=, size); + + naddr = pc_mlist_alloc(s, rsize); + if (s->opts & PC_ALLOC_PARANOID) { + pc_mlist_check(s); + } + + return naddr; +} + static void pc_free(QGuestAllocator *allocator, uint64_t addr) { + PCAlloc *s = container_of(allocator, PCAlloc, alloc); + + pc_mlist_free(s, addr); + if (s->opts & PC_ALLOC_PARANOID) { + pc_mlist_check(s); + } +} + +/* + * Mostly for valgrind happiness, but it does offer + * a chokepoint for debugging guest memory leaks, too. + */ +void pc_alloc_uninit(QGuestAllocator *allocator) +{ + PCAlloc *s = container_of(allocator, PCAlloc, alloc); + MemBlock *node; + MemBlock *tmp; + PCAllocOpts mask; + + /* Check for guest leaks, and destroy the list. */ + QTAILQ_FOREACH_SAFE(node, &s->used, MLIST_ENTNAME, tmp) { + if (s->opts & (PC_ALLOC_LEAK_WARN | PC_ALLOC_LEAK_ASSERT)) { + fprintf(stderr, "guest malloc leak @ 0x%016" PRIx64 "; " + "size 0x%016" PRIx64 ".\n", + node->addr, node->size); + } + if (s->opts & (PC_ALLOC_LEAK_ASSERT)) { + g_assert_not_reached(); + } + g_free(node); + } + + /* If we have previously asserted that there are no leaks, then there + * should be only one node here with a specific address and size. */ + mask = PC_ALLOC_LEAK_ASSERT | PC_ALLOC_PARANOID; + QTAILQ_FOREACH_SAFE(node, &s->free, MLIST_ENTNAME, tmp) { + if ((s->opts & mask) == mask) { + if ((node->addr != s->start) || + (node->size != s->end - s->start)) { + fprintf(stderr, "Free list is corrupted.\n"); + g_assert_not_reached(); + } + } + + g_free(node); + } + + g_free(s); } -QGuestAllocator *pc_alloc_init(void) +QGuestAllocator *pc_alloc_init_flags(PCAllocOpts flags) { PCAlloc *s = g_malloc0(sizeof(*s)); uint64_t ram_size; QFWCFG *fw_cfg = pc_fw_cfg_init(); + MemBlock *node; + s->opts = flags; s->alloc.alloc = pc_alloc; s->alloc.free = pc_free; @@ -67,5 +316,19 @@ QGuestAllocator *pc_alloc_init(void) /* Respect PCI hole */ s->end = MIN(ram_size, 0xE0000000); + /* clean-up */ + g_free(fw_cfg); + + QTAILQ_INIT(&s->used); + QTAILQ_INIT(&s->free); + + node = mlist_new(s->start, s->end - s->start); + QTAILQ_INSERT_HEAD(&s->free, node, MLIST_ENTNAME); + return &s->alloc; } + +inline QGuestAllocator *pc_alloc_init(void) +{ + return pc_alloc_init_flags(PC_ALLOC_NO_FLAGS); +} diff --git a/tests/libqos/malloc-pc.h b/tests/libqos/malloc-pc.h index ff964abe5..9f525e3b9 100644 --- a/tests/libqos/malloc-pc.h +++ b/tests/libqos/malloc-pc.h @@ -15,6 +15,15 @@ #include "libqos/malloc.h" +typedef enum { + PC_ALLOC_NO_FLAGS = 0x00, + PC_ALLOC_LEAK_WARN = 0x01, + PC_ALLOC_LEAK_ASSERT = 0x02, + PC_ALLOC_PARANOID = 0x04 +} PCAllocOpts; + QGuestAllocator *pc_alloc_init(void); +QGuestAllocator *pc_alloc_init_flags(PCAllocOpts flags); +void pc_alloc_uninit(QGuestAllocator *allocator); #endif diff --git a/tests/libqos/malloc.h b/tests/libqos/malloc.h index 46f600076..556538121 100644 --- a/tests/libqos/malloc.h +++ b/tests/libqos/malloc.h @@ -32,7 +32,7 @@ static inline uint64_t guest_alloc(QGuestAllocator *allocator, size_t size) static inline void guest_free(QGuestAllocator *allocator, uint64_t addr) { - allocator->alloc(allocator, addr); + allocator->free(allocator, addr); } #endif diff --git a/tests/libqos/pci-pc.c b/tests/libqos/pci-pc.c index 4adf4006a..6dba0db00 100644 --- a/tests/libqos/pci-pc.c +++ b/tests/libqos/pci-pc.c @@ -20,6 +20,9 @@ #include <glib.h> +#define ACPI_PCIHP_ADDR 0xae00 +#define PCI_EJ_BASE 0x0008 + typedef struct QPCIBusPC { QPCIBus bus; @@ -144,7 +147,7 @@ static void qpci_pc_config_writel(QPCIBus *bus, int devfn, uint8_t offset, uint3 outl(0xcfc, value); } -static void *qpci_pc_iomap(QPCIBus *bus, QPCIDevice *dev, int barno) +static void *qpci_pc_iomap(QPCIBus *bus, QPCIDevice *dev, int barno, uint64_t *sizeptr) { QPCIBusPC *s = container_of(bus, QPCIBusPC, bus); static const int bar_reg_map[] = { @@ -173,6 +176,9 @@ static void *qpci_pc_iomap(QPCIBus *bus, QPCIDevice *dev, int barno) if (size == 0) { return NULL; } + if (sizeptr) { + *sizeptr = size; + } if (io_type == PCI_BASE_ADDRESS_SPACE_IO) { uint16_t loc; @@ -237,3 +243,56 @@ QPCIBus *qpci_init_pc(void) return &ret->bus; } + +void qpci_free_pc(QPCIBus *bus) +{ + QPCIBusPC *s = container_of(bus, QPCIBusPC, bus); + + g_free(s); +} + +void qpci_plug_device_test(const char *driver, const char *id, + uint8_t slot, const char *opts) +{ + QDict *response; + char *cmd; + + cmd = g_strdup_printf("{'execute': 'device_add'," + " 'arguments': {" + " 'driver': '%s'," + " 'addr': '%d'," + " %s%s" + " 'id': '%s'" + "}}", driver, slot, + opts ? opts : "", opts ? "," : "", + id); + response = qmp(cmd); + g_free(cmd); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); +} + +void qpci_unplug_acpi_device_test(const char *id, uint8_t slot) +{ + QDict *response; + char *cmd; + + cmd = g_strdup_printf("{'execute': 'device_del'," + " 'arguments': {" + " 'id': '%s'" + "}}", id); + response = qmp(cmd); + g_free(cmd); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + outb(ACPI_PCIHP_ADDR + PCI_EJ_BASE, 1 << slot); + + response = qmp(""); + g_assert(response); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); + QDECREF(response); +} diff --git a/tests/libqos/pci-pc.h b/tests/libqos/pci-pc.h index 4f7475f6f..26211790c 100644 --- a/tests/libqos/pci-pc.h +++ b/tests/libqos/pci-pc.h @@ -16,5 +16,6 @@ #include "libqos/pci.h" QPCIBus *qpci_init_pc(void); +void qpci_free_pc(QPCIBus *bus); #endif diff --git a/tests/libqos/pci.c b/tests/libqos/pci.c index c9a0b9134..4e630c250 100644 --- a/tests/libqos/pci.c +++ b/tests/libqos/pci.c @@ -15,8 +15,6 @@ #include "hw/pci/pci_regs.h" #include <glib.h> -#include <stdio.h> - void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id, void (*func)(QPCIDevice *dev, int devfn, void *data), void *data) @@ -73,6 +71,121 @@ void qpci_device_enable(QPCIDevice *dev) cmd = qpci_config_readw(dev, PCI_COMMAND); cmd |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; qpci_config_writew(dev, PCI_COMMAND, cmd); + + /* Verify the bits are now set. */ + cmd = qpci_config_readw(dev, PCI_COMMAND); + g_assert_cmphex(cmd & PCI_COMMAND_IO, ==, PCI_COMMAND_IO); + g_assert_cmphex(cmd & PCI_COMMAND_MEMORY, ==, PCI_COMMAND_MEMORY); + g_assert_cmphex(cmd & PCI_COMMAND_MASTER, ==, PCI_COMMAND_MASTER); +} + +uint8_t qpci_find_capability(QPCIDevice *dev, uint8_t id) +{ + uint8_t cap; + uint8_t addr = qpci_config_readb(dev, PCI_CAPABILITY_LIST); + + do { + cap = qpci_config_readb(dev, addr); + if (cap != id) { + addr = qpci_config_readb(dev, addr + PCI_CAP_LIST_NEXT); + } + } while (cap != id && addr != 0); + + return addr; +} + +void qpci_msix_enable(QPCIDevice *dev) +{ + uint8_t addr; + uint16_t val; + uint32_t table; + uint8_t bir_table; + uint8_t bir_pba; + void *offset; + + addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX); + g_assert_cmphex(addr, !=, 0); + + val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); + qpci_config_writew(dev, addr + PCI_MSIX_FLAGS, val | PCI_MSIX_FLAGS_ENABLE); + + table = qpci_config_readl(dev, addr + PCI_MSIX_TABLE); + bir_table = table & PCI_MSIX_FLAGS_BIRMASK; + offset = qpci_iomap(dev, bir_table, NULL); + dev->msix_table = offset + (table & ~PCI_MSIX_FLAGS_BIRMASK); + + table = qpci_config_readl(dev, addr + PCI_MSIX_PBA); + bir_pba = table & PCI_MSIX_FLAGS_BIRMASK; + if (bir_pba != bir_table) { + offset = qpci_iomap(dev, bir_pba, NULL); + } + dev->msix_pba = offset + (table & ~PCI_MSIX_FLAGS_BIRMASK); + + g_assert(dev->msix_table != NULL); + g_assert(dev->msix_pba != NULL); + dev->msix_enabled = true; +} + +void qpci_msix_disable(QPCIDevice *dev) +{ + uint8_t addr; + uint16_t val; + + g_assert(dev->msix_enabled); + addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX); + g_assert_cmphex(addr, !=, 0); + val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); + qpci_config_writew(dev, addr + PCI_MSIX_FLAGS, + val & ~PCI_MSIX_FLAGS_ENABLE); + + qpci_iounmap(dev, dev->msix_table); + qpci_iounmap(dev, dev->msix_pba); + dev->msix_enabled = 0; + dev->msix_table = NULL; + dev->msix_pba = NULL; +} + +bool qpci_msix_pending(QPCIDevice *dev, uint16_t entry) +{ + uint32_t pba_entry; + uint8_t bit_n = entry % 32; + void *addr = dev->msix_pba + (entry / 32) * PCI_MSIX_ENTRY_SIZE / 4; + + g_assert(dev->msix_enabled); + pba_entry = qpci_io_readl(dev, addr); + qpci_io_writel(dev, addr, pba_entry & ~(1 << bit_n)); + return (pba_entry & (1 << bit_n)) != 0; +} + +bool qpci_msix_masked(QPCIDevice *dev, uint16_t entry) +{ + uint8_t addr; + uint16_t val; + void *vector_addr = dev->msix_table + (entry * PCI_MSIX_ENTRY_SIZE); + + g_assert(dev->msix_enabled); + addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX); + g_assert_cmphex(addr, !=, 0); + val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); + + if (val & PCI_MSIX_FLAGS_MASKALL) { + return true; + } else { + return (qpci_io_readl(dev, vector_addr + PCI_MSIX_ENTRY_VECTOR_CTRL) + & PCI_MSIX_ENTRY_CTRL_MASKBIT) != 0; + } +} + +uint16_t qpci_msix_table_size(QPCIDevice *dev) +{ + uint8_t addr; + uint16_t control; + + addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX); + g_assert_cmphex(addr, !=, 0); + + control = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); + return (control & PCI_MSIX_FLAGS_QSIZE) + 1; } uint8_t qpci_config_readb(QPCIDevice *dev, uint8_t offset) @@ -138,9 +251,9 @@ void qpci_io_writel(QPCIDevice *dev, void *data, uint32_t value) dev->bus->io_writel(dev->bus, data, value); } -void *qpci_iomap(QPCIDevice *dev, int barno) +void *qpci_iomap(QPCIDevice *dev, int barno, uint64_t *sizeptr) { - return dev->bus->iomap(dev->bus, dev, barno); + return dev->bus->iomap(dev->bus, dev, barno, sizeptr); } void qpci_iounmap(QPCIDevice *dev, void *data) diff --git a/tests/libqos/pci.h b/tests/libqos/pci.h index 343943154..dfaee9ec3 100644 --- a/tests/libqos/pci.h +++ b/tests/libqos/pci.h @@ -14,6 +14,7 @@ #define LIBQOS_PCI_H #include <stdint.h> +#include "libqtest.h" #define QPCI_DEVFN(dev, fn) (((dev) << 3) | (fn)) @@ -41,7 +42,7 @@ struct QPCIBus void (*config_writel)(QPCIBus *bus, int devfn, uint8_t offset, uint32_t value); - void *(*iomap)(QPCIBus *bus, QPCIDevice *dev, int barno); + void *(*iomap)(QPCIBus *bus, QPCIDevice *dev, int barno, uint64_t *sizeptr); void (*iounmap)(QPCIBus *bus, void *data); }; @@ -49,6 +50,9 @@ struct QPCIDevice { QPCIBus *bus; int devfn; + bool msix_enabled; + void *msix_table; + void *msix_pba; }; void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id, @@ -57,6 +61,12 @@ void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id, QPCIDevice *qpci_device_find(QPCIBus *bus, int devfn); void qpci_device_enable(QPCIDevice *dev); +uint8_t qpci_find_capability(QPCIDevice *dev, uint8_t id); +void qpci_msix_enable(QPCIDevice *dev); +void qpci_msix_disable(QPCIDevice *dev); +bool qpci_msix_pending(QPCIDevice *dev, uint16_t entry); +bool qpci_msix_masked(QPCIDevice *dev, uint16_t entry); +uint16_t qpci_msix_table_size(QPCIDevice *dev); uint8_t qpci_config_readb(QPCIDevice *dev, uint8_t offset); uint16_t qpci_config_readw(QPCIDevice *dev, uint8_t offset); @@ -74,7 +84,10 @@ void qpci_io_writeb(QPCIDevice *dev, void *data, uint8_t value); void qpci_io_writew(QPCIDevice *dev, void *data, uint16_t value); void qpci_io_writel(QPCIDevice *dev, void *data, uint32_t value); -void *qpci_iomap(QPCIDevice *dev, int barno); +void *qpci_iomap(QPCIDevice *dev, int barno, uint64_t *sizeptr); void qpci_iounmap(QPCIDevice *dev, void *data); +void qpci_plug_device_test(const char *driver, const char *id, + uint8_t slot, const char *opts); +void qpci_unplug_acpi_device_test(const char *id, uint8_t slot); #endif diff --git a/tests/libqos/usb.c b/tests/libqos/usb.c new file mode 100644 index 000000000..41d89b848 --- /dev/null +++ b/tests/libqos/usb.c @@ -0,0 +1,71 @@ +/* + * common code shared by usb tests + * + * Copyright (c) 2014 Red Hat, Inc + * + * Authors: + * Gerd Hoffmann <kraxel@redhat.com> + * John Snow <jsnow@redhat.com> + * Igor Mammedov <imammedo@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#include <glib.h> +#include <string.h> +#include "libqtest.h" +#include "qemu/osdep.h" +#include "hw/usb/uhci-regs.h" +#include "libqos/usb.h" + +void qusb_pci_init_one(QPCIBus *pcibus, struct qhc *hc, uint32_t devfn, int bar) +{ + hc->dev = qpci_device_find(pcibus, devfn); + g_assert(hc->dev != NULL); + qpci_device_enable(hc->dev); + hc->base = qpci_iomap(hc->dev, bar, NULL); + g_assert(hc->base != NULL); +} + +void uhci_port_test(struct qhc *hc, int port, uint16_t expect) +{ + void *addr = hc->base + 0x10 + 2 * port; + uint16_t value = qpci_io_readw(hc->dev, addr); + uint16_t mask = ~(UHCI_PORT_WRITE_CLEAR | UHCI_PORT_RSVD1); + + g_assert((value & mask) == (expect & mask)); +} + +void usb_test_hotplug(const char *hcd_id, const int port, + void (*port_check)(void)) +{ + QDict *response; + char *cmd; + + cmd = g_strdup_printf("{'execute': 'device_add'," + " 'arguments': {" + " 'driver': 'usb-tablet'," + " 'port': '%d'," + " 'bus': '%s.0'," + " 'id': 'usbdev%d'" + "}}", port, hcd_id, port); + response = qmp(cmd); + g_free(cmd); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + if (port_check) { + port_check(); + } + + cmd = g_strdup_printf("{'execute': 'device_del'," + " 'arguments': {" + " 'id': 'usbdev%d'" + "}}", port); + response = qmp(cmd); + g_free(cmd); + g_assert(response); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); +} diff --git a/tests/libqos/usb.h b/tests/libqos/usb.h new file mode 100644 index 000000000..8fe56872b --- /dev/null +++ b/tests/libqos/usb.h @@ -0,0 +1,17 @@ +#ifndef LIBQOS_USB_H +#define LIBQOS_USB_H + +#include "libqos/pci-pc.h" + +struct qhc { + QPCIDevice *dev; + void *base; +}; + +void qusb_pci_init_one(QPCIBus *pcibus, struct qhc *hc, + uint32_t devfn, int bar); +void uhci_port_test(struct qhc *hc, int port, uint16_t expect); + +void usb_test_hotplug(const char *bus_name, const int port, + void (*port_check)(void)); +#endif diff --git a/tests/libqos/virtio-pci.c b/tests/libqos/virtio-pci.c new file mode 100644 index 000000000..788ebaff4 --- /dev/null +++ b/tests/libqos/virtio-pci.c @@ -0,0 +1,343 @@ +/* + * libqos virtio PCI driver + * + * Copyright (c) 2014 Marc MarĂ + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include <glib.h> +#include <stdio.h> +#include "libqtest.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "libqos/malloc.h" +#include "libqos/malloc-pc.h" + +#include "hw/pci/pci_regs.h" + +typedef struct QVirtioPCIForeachData { + void (*func)(QVirtioDevice *d, void *data); + uint16_t device_type; + void *user_data; +} QVirtioPCIForeachData; + +static QVirtioPCIDevice *qpcidevice_to_qvirtiodevice(QPCIDevice *pdev) +{ + QVirtioPCIDevice *vpcidev; + vpcidev = g_malloc0(sizeof(*vpcidev)); + + if (pdev) { + vpcidev->pdev = pdev; + vpcidev->vdev.device_type = + qpci_config_readw(vpcidev->pdev, PCI_SUBSYSTEM_ID); + } + + vpcidev->config_msix_entry = -1; + + return vpcidev; +} + +static void qvirtio_pci_foreach_callback( + QPCIDevice *dev, int devfn, void *data) +{ + QVirtioPCIForeachData *d = data; + QVirtioPCIDevice *vpcidev = qpcidevice_to_qvirtiodevice(dev); + + if (vpcidev->vdev.device_type == d->device_type) { + d->func(&vpcidev->vdev, d->user_data); + } else { + g_free(vpcidev); + } +} + +static void qvirtio_pci_assign_device(QVirtioDevice *d, void *data) +{ + QVirtioPCIDevice **vpcidev = data; + *vpcidev = (QVirtioPCIDevice *)d; +} + +static uint8_t qvirtio_pci_config_readb(QVirtioDevice *d, void *addr) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readb(dev->pdev, addr); +} + +static uint16_t qvirtio_pci_config_readw(QVirtioDevice *d, void *addr) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readw(dev->pdev, addr); +} + +static uint32_t qvirtio_pci_config_readl(QVirtioDevice *d, void *addr) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readl(dev->pdev, addr); +} + +static uint64_t qvirtio_pci_config_readq(QVirtioDevice *d, void *addr) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + int i; + uint64_t u64 = 0; + + if (qtest_big_endian()) { + for (i = 0; i < 8; ++i) { + u64 |= (uint64_t)qpci_io_readb(dev->pdev, addr + i) << (7 - i) * 8; + } + } else { + for (i = 0; i < 8; ++i) { + u64 |= (uint64_t)qpci_io_readb(dev->pdev, addr + i) << i * 8; + } + } + + return u64; +} + +static uint32_t qvirtio_pci_get_features(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readl(dev->pdev, dev->addr + QVIRTIO_DEVICE_FEATURES); +} + +static void qvirtio_pci_set_features(QVirtioDevice *d, uint32_t features) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + qpci_io_writel(dev->pdev, dev->addr + QVIRTIO_GUEST_FEATURES, features); +} + +static uint32_t qvirtio_pci_get_guest_features(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readl(dev->pdev, dev->addr + QVIRTIO_GUEST_FEATURES); +} + +static uint8_t qvirtio_pci_get_status(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readb(dev->pdev, dev->addr + QVIRTIO_DEVICE_STATUS); +} + +static void qvirtio_pci_set_status(QVirtioDevice *d, uint8_t status) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + qpci_io_writeb(dev->pdev, dev->addr + QVIRTIO_DEVICE_STATUS, status); +} + +static bool qvirtio_pci_get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + QVirtQueuePCI *vqpci = (QVirtQueuePCI *)vq; + uint32_t data; + + if (dev->pdev->msix_enabled) { + g_assert_cmpint(vqpci->msix_entry, !=, -1); + if (qpci_msix_masked(dev->pdev, vqpci->msix_entry)) { + /* No ISR checking should be done if masked, but read anyway */ + return qpci_msix_pending(dev->pdev, vqpci->msix_entry); + } else { + data = readl(vqpci->msix_addr); + writel(vqpci->msix_addr, 0); + return data == vqpci->msix_data; + } + } else { + return qpci_io_readb(dev->pdev, dev->addr + QVIRTIO_ISR_STATUS) & 1; + } +} + +static bool qvirtio_pci_get_config_isr_status(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + uint32_t data; + + if (dev->pdev->msix_enabled) { + g_assert_cmpint(dev->config_msix_entry, !=, -1); + if (qpci_msix_masked(dev->pdev, dev->config_msix_entry)) { + /* No ISR checking should be done if masked, but read anyway */ + return qpci_msix_pending(dev->pdev, dev->config_msix_entry); + } else { + data = readl(dev->config_msix_addr); + writel(dev->config_msix_addr, 0); + return data == dev->config_msix_data; + } + } else { + return qpci_io_readb(dev->pdev, dev->addr + QVIRTIO_ISR_STATUS) & 2; + } +} + +static void qvirtio_pci_queue_select(QVirtioDevice *d, uint16_t index) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + qpci_io_writeb(dev->pdev, dev->addr + QVIRTIO_QUEUE_SELECT, index); +} + +static uint16_t qvirtio_pci_get_queue_size(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + return qpci_io_readw(dev->pdev, dev->addr + QVIRTIO_QUEUE_SIZE); +} + +static void qvirtio_pci_set_queue_address(QVirtioDevice *d, uint32_t pfn) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + qpci_io_writel(dev->pdev, dev->addr + QVIRTIO_QUEUE_ADDRESS, pfn); +} + +static QVirtQueue *qvirtio_pci_virtqueue_setup(QVirtioDevice *d, + QGuestAllocator *alloc, uint16_t index) +{ + uint32_t feat; + uint64_t addr; + QVirtQueuePCI *vqpci; + + vqpci = g_malloc0(sizeof(*vqpci)); + feat = qvirtio_pci_get_guest_features(d); + + qvirtio_pci_queue_select(d, index); + vqpci->vq.index = index; + vqpci->vq.size = qvirtio_pci_get_queue_size(d); + vqpci->vq.free_head = 0; + vqpci->vq.num_free = vqpci->vq.size; + vqpci->vq.align = QVIRTIO_PCI_ALIGN; + vqpci->vq.indirect = (feat & QVIRTIO_F_RING_INDIRECT_DESC) != 0; + vqpci->vq.event = (feat & QVIRTIO_F_RING_EVENT_IDX) != 0; + + vqpci->msix_entry = -1; + vqpci->msix_addr = 0; + vqpci->msix_data = 0x12345678; + + /* Check different than 0 */ + g_assert_cmpint(vqpci->vq.size, !=, 0); + + /* Check power of 2 */ + g_assert_cmpint(vqpci->vq.size & (vqpci->vq.size - 1), ==, 0); + + addr = guest_alloc(alloc, qvring_size(vqpci->vq.size, QVIRTIO_PCI_ALIGN)); + qvring_init(alloc, &vqpci->vq, addr); + qvirtio_pci_set_queue_address(d, vqpci->vq.desc / QVIRTIO_PCI_ALIGN); + + return &vqpci->vq; +} + +static void qvirtio_pci_virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d; + qpci_io_writew(dev->pdev, dev->addr + QVIRTIO_QUEUE_NOTIFY, vq->index); +} + +const QVirtioBus qvirtio_pci = { + .config_readb = qvirtio_pci_config_readb, + .config_readw = qvirtio_pci_config_readw, + .config_readl = qvirtio_pci_config_readl, + .config_readq = qvirtio_pci_config_readq, + .get_features = qvirtio_pci_get_features, + .set_features = qvirtio_pci_set_features, + .get_guest_features = qvirtio_pci_get_guest_features, + .get_status = qvirtio_pci_get_status, + .set_status = qvirtio_pci_set_status, + .get_queue_isr_status = qvirtio_pci_get_queue_isr_status, + .get_config_isr_status = qvirtio_pci_get_config_isr_status, + .queue_select = qvirtio_pci_queue_select, + .get_queue_size = qvirtio_pci_get_queue_size, + .set_queue_address = qvirtio_pci_set_queue_address, + .virtqueue_setup = qvirtio_pci_virtqueue_setup, + .virtqueue_kick = qvirtio_pci_virtqueue_kick, +}; + +void qvirtio_pci_foreach(QPCIBus *bus, uint16_t device_type, + void (*func)(QVirtioDevice *d, void *data), void *data) +{ + QVirtioPCIForeachData d = { .func = func, + .device_type = device_type, + .user_data = data }; + + qpci_device_foreach(bus, QVIRTIO_VENDOR_ID, -1, + qvirtio_pci_foreach_callback, &d); +} + +QVirtioPCIDevice *qvirtio_pci_device_find(QPCIBus *bus, uint16_t device_type) +{ + QVirtioPCIDevice *dev = NULL; + qvirtio_pci_foreach(bus, device_type, qvirtio_pci_assign_device, &dev); + + return dev; +} + +void qvirtio_pci_device_enable(QVirtioPCIDevice *d) +{ + qpci_device_enable(d->pdev); + d->addr = qpci_iomap(d->pdev, 0, NULL); + g_assert(d->addr != NULL); +} + +void qvirtio_pci_device_disable(QVirtioPCIDevice *d) +{ + qpci_iounmap(d->pdev, d->addr); + d->addr = NULL; +} + +void qvirtqueue_pci_msix_setup(QVirtioPCIDevice *d, QVirtQueuePCI *vqpci, + QGuestAllocator *alloc, uint16_t entry) +{ + uint16_t vector; + uint32_t control; + void *addr; + + g_assert(d->pdev->msix_enabled); + addr = d->pdev->msix_table + (entry * 16); + + g_assert_cmpint(entry, >=, 0); + g_assert_cmpint(entry, <, qpci_msix_table_size(d->pdev)); + vqpci->msix_entry = entry; + + vqpci->msix_addr = guest_alloc(alloc, 4); + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_LOWER_ADDR, + vqpci->msix_addr & ~0UL); + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_UPPER_ADDR, + (vqpci->msix_addr >> 32) & ~0UL); + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_DATA, vqpci->msix_data); + + control = qpci_io_readl(d->pdev, addr + PCI_MSIX_ENTRY_VECTOR_CTRL); + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_VECTOR_CTRL, + control & ~PCI_MSIX_ENTRY_CTRL_MASKBIT); + + qvirtio_pci_queue_select(&d->vdev, vqpci->vq.index); + qpci_io_writew(d->pdev, d->addr + QVIRTIO_MSIX_QUEUE_VECTOR, entry); + vector = qpci_io_readw(d->pdev, d->addr + QVIRTIO_MSIX_QUEUE_VECTOR); + g_assert_cmphex(vector, !=, QVIRTIO_MSI_NO_VECTOR); +} + +void qvirtio_pci_set_msix_configuration_vector(QVirtioPCIDevice *d, + QGuestAllocator *alloc, uint16_t entry) +{ + uint16_t vector; + uint32_t control; + void *addr; + + g_assert(d->pdev->msix_enabled); + addr = d->pdev->msix_table + (entry * 16); + + g_assert_cmpint(entry, >=, 0); + g_assert_cmpint(entry, <, qpci_msix_table_size(d->pdev)); + d->config_msix_entry = entry; + + d->config_msix_data = 0x12345678; + d->config_msix_addr = guest_alloc(alloc, 4); + + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_LOWER_ADDR, + d->config_msix_addr & ~0UL); + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_UPPER_ADDR, + (d->config_msix_addr >> 32) & ~0UL); + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_DATA, d->config_msix_data); + + control = qpci_io_readl(d->pdev, addr + PCI_MSIX_ENTRY_VECTOR_CTRL); + qpci_io_writel(d->pdev, addr + PCI_MSIX_ENTRY_VECTOR_CTRL, + control & ~PCI_MSIX_ENTRY_CTRL_MASKBIT); + + qpci_io_writew(d->pdev, d->addr + QVIRTIO_MSIX_CONF_VECTOR, entry); + vector = qpci_io_readw(d->pdev, d->addr + QVIRTIO_MSIX_CONF_VECTOR); + g_assert_cmphex(vector, !=, QVIRTIO_MSI_NO_VECTOR); +} diff --git a/tests/libqos/virtio-pci.h b/tests/libqos/virtio-pci.h new file mode 100644 index 000000000..883f7ff26 --- /dev/null +++ b/tests/libqos/virtio-pci.h @@ -0,0 +1,61 @@ +/* + * libqos virtio PCI definitions + * + * Copyright (c) 2014 Marc MarĂ + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef LIBQOS_VIRTIO_PCI_H +#define LIBQOS_VIRTIO_PCI_H + +#include "libqos/virtio.h" +#include "libqos/pci.h" + +#define QVIRTIO_DEVICE_FEATURES 0x00 +#define QVIRTIO_GUEST_FEATURES 0x04 +#define QVIRTIO_QUEUE_ADDRESS 0x08 +#define QVIRTIO_QUEUE_SIZE 0x0C +#define QVIRTIO_QUEUE_SELECT 0x0E +#define QVIRTIO_QUEUE_NOTIFY 0x10 +#define QVIRTIO_DEVICE_STATUS 0x12 +#define QVIRTIO_ISR_STATUS 0x13 +#define QVIRTIO_MSIX_CONF_VECTOR 0x14 +#define QVIRTIO_MSIX_QUEUE_VECTOR 0x16 +#define QVIRTIO_DEVICE_SPECIFIC_MSIX 0x18 +#define QVIRTIO_DEVICE_SPECIFIC_NO_MSIX 0x14 + +#define QVIRTIO_PCI_ALIGN 4096 + +#define QVIRTIO_MSI_NO_VECTOR 0xFFFF + +typedef struct QVirtioPCIDevice { + QVirtioDevice vdev; + QPCIDevice *pdev; + void *addr; + uint16_t config_msix_entry; + uint64_t config_msix_addr; + uint32_t config_msix_data; +} QVirtioPCIDevice; + +typedef struct QVirtQueuePCI { + QVirtQueue vq; + uint16_t msix_entry; + uint64_t msix_addr; + uint32_t msix_data; +} QVirtQueuePCI; + +extern const QVirtioBus qvirtio_pci; + +void qvirtio_pci_foreach(QPCIBus *bus, uint16_t device_type, + void (*func)(QVirtioDevice *d, void *data), void *data); +QVirtioPCIDevice *qvirtio_pci_device_find(QPCIBus *bus, uint16_t device_type); +void qvirtio_pci_device_enable(QVirtioPCIDevice *d); +void qvirtio_pci_device_disable(QVirtioPCIDevice *d); + +void qvirtio_pci_set_msix_configuration_vector(QVirtioPCIDevice *d, + QGuestAllocator *alloc, uint16_t entry); +void qvirtqueue_pci_msix_setup(QVirtioPCIDevice *d, QVirtQueuePCI *vqpci, + QGuestAllocator *alloc, uint16_t entry); +#endif diff --git a/tests/libqos/virtio.c b/tests/libqos/virtio.c new file mode 100644 index 000000000..a06128924 --- /dev/null +++ b/tests/libqos/virtio.c @@ -0,0 +1,281 @@ +/* + * libqos virtio driver + * + * Copyright (c) 2014 Marc MarĂ + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include <glib.h> +#include "libqtest.h" +#include "libqos/virtio.h" + +uint8_t qvirtio_config_readb(const QVirtioBus *bus, QVirtioDevice *d, + void *addr) +{ + return bus->config_readb(d, addr); +} + +uint16_t qvirtio_config_readw(const QVirtioBus *bus, QVirtioDevice *d, + void *addr) +{ + return bus->config_readw(d, addr); +} + +uint32_t qvirtio_config_readl(const QVirtioBus *bus, QVirtioDevice *d, + void *addr) +{ + return bus->config_readl(d, addr); +} + +uint64_t qvirtio_config_readq(const QVirtioBus *bus, QVirtioDevice *d, + void *addr) +{ + return bus->config_readq(d, addr); +} + +uint32_t qvirtio_get_features(const QVirtioBus *bus, QVirtioDevice *d) +{ + return bus->get_features(d); +} + +void qvirtio_set_features(const QVirtioBus *bus, QVirtioDevice *d, + uint32_t features) +{ + bus->set_features(d, features); +} + +QVirtQueue *qvirtqueue_setup(const QVirtioBus *bus, QVirtioDevice *d, + QGuestAllocator *alloc, uint16_t index) +{ + return bus->virtqueue_setup(d, alloc, index); +} + +void qvirtio_reset(const QVirtioBus *bus, QVirtioDevice *d) +{ + bus->set_status(d, QVIRTIO_RESET); + g_assert_cmphex(bus->get_status(d), ==, QVIRTIO_RESET); +} + +void qvirtio_set_acknowledge(const QVirtioBus *bus, QVirtioDevice *d) +{ + bus->set_status(d, bus->get_status(d) | QVIRTIO_ACKNOWLEDGE); + g_assert_cmphex(bus->get_status(d), ==, QVIRTIO_ACKNOWLEDGE); +} + +void qvirtio_set_driver(const QVirtioBus *bus, QVirtioDevice *d) +{ + bus->set_status(d, bus->get_status(d) | QVIRTIO_DRIVER); + g_assert_cmphex(bus->get_status(d), ==, + QVIRTIO_DRIVER | QVIRTIO_ACKNOWLEDGE); +} + +void qvirtio_set_driver_ok(const QVirtioBus *bus, QVirtioDevice *d) +{ + bus->set_status(d, bus->get_status(d) | QVIRTIO_DRIVER_OK); + g_assert_cmphex(bus->get_status(d), ==, + QVIRTIO_DRIVER_OK | QVIRTIO_DRIVER | QVIRTIO_ACKNOWLEDGE); +} + +void qvirtio_wait_queue_isr(const QVirtioBus *bus, QVirtioDevice *d, + QVirtQueue *vq, gint64 timeout_us) +{ + gint64 start_time = g_get_monotonic_time(); + + for (;;) { + clock_step(100); + if (bus->get_queue_isr_status(d, vq)) { + return; + } + g_assert(g_get_monotonic_time() - start_time <= timeout_us); + } +} + +/* Wait for the status byte at given guest memory address to be set + * + * The virtqueue interrupt must not be raised, making this useful for testing + * event_index functionality. + */ +uint8_t qvirtio_wait_status_byte_no_isr(const QVirtioBus *bus, + QVirtioDevice *d, + QVirtQueue *vq, + uint64_t addr, + gint64 timeout_us) +{ + gint64 start_time = g_get_monotonic_time(); + uint8_t val; + + while ((val = readb(addr)) == 0xff) { + clock_step(100); + g_assert(!bus->get_queue_isr_status(d, vq)); + g_assert(g_get_monotonic_time() - start_time <= timeout_us); + } + return val; +} + +void qvirtio_wait_config_isr(const QVirtioBus *bus, QVirtioDevice *d, + gint64 timeout_us) +{ + gint64 start_time = g_get_monotonic_time(); + + for (;;) { + clock_step(100); + if (bus->get_config_isr_status(d)) { + return; + } + g_assert(g_get_monotonic_time() - start_time <= timeout_us); + } +} + +void qvring_init(const QGuestAllocator *alloc, QVirtQueue *vq, uint64_t addr) +{ + int i; + + vq->desc = addr; + vq->avail = vq->desc + vq->size*sizeof(QVRingDesc); + vq->used = (uint64_t)((vq->avail + sizeof(uint16_t) * (3 + vq->size) + + vq->align - 1) & ~(vq->align - 1)); + + for (i = 0; i < vq->size - 1; i++) { + /* vq->desc[i].addr */ + writew(vq->desc + (16 * i), 0); + /* vq->desc[i].next */ + writew(vq->desc + (16 * i) + 14, i + 1); + } + + /* vq->avail->flags */ + writew(vq->avail, 0); + /* vq->avail->idx */ + writew(vq->avail + 2, 0); + /* vq->avail->used_event */ + writew(vq->avail + 4 + (2 * vq->size), 0); + + /* vq->used->flags */ + writew(vq->used, 0); + /* vq->used->avail_event */ + writew(vq->used+2+(sizeof(struct QVRingUsedElem)*vq->size), 0); +} + +QVRingIndirectDesc *qvring_indirect_desc_setup(QVirtioDevice *d, + QGuestAllocator *alloc, uint16_t elem) +{ + int i; + QVRingIndirectDesc *indirect = g_malloc(sizeof(*indirect)); + + indirect->index = 0; + indirect->elem = elem; + indirect->desc = guest_alloc(alloc, sizeof(QVRingDesc)*elem); + + for (i = 0; i < elem - 1; ++i) { + /* indirect->desc[i].addr */ + writeq(indirect->desc + (16 * i), 0); + /* indirect->desc[i].flags */ + writew(indirect->desc + (16 * i) + 12, QVRING_DESC_F_NEXT); + /* indirect->desc[i].next */ + writew(indirect->desc + (16 * i) + 14, i + 1); + } + + return indirect; +} + +void qvring_indirect_desc_add(QVRingIndirectDesc *indirect, uint64_t data, + uint32_t len, bool write) +{ + uint16_t flags; + + g_assert_cmpint(indirect->index, <, indirect->elem); + + flags = readw(indirect->desc + (16 * indirect->index) + 12); + + if (write) { + flags |= QVRING_DESC_F_WRITE; + } + + /* indirect->desc[indirect->index].addr */ + writeq(indirect->desc + (16 * indirect->index), data); + /* indirect->desc[indirect->index].len */ + writel(indirect->desc + (16 * indirect->index) + 8, len); + /* indirect->desc[indirect->index].flags */ + writew(indirect->desc + (16 * indirect->index) + 12, flags); + + indirect->index++; +} + +uint32_t qvirtqueue_add(QVirtQueue *vq, uint64_t data, uint32_t len, bool write, + bool next) +{ + uint16_t flags = 0; + vq->num_free--; + + if (write) { + flags |= QVRING_DESC_F_WRITE; + } + + if (next) { + flags |= QVRING_DESC_F_NEXT; + } + + /* vq->desc[vq->free_head].addr */ + writeq(vq->desc + (16 * vq->free_head), data); + /* vq->desc[vq->free_head].len */ + writel(vq->desc + (16 * vq->free_head) + 8, len); + /* vq->desc[vq->free_head].flags */ + writew(vq->desc + (16 * vq->free_head) + 12, flags); + + return vq->free_head++; /* Return and increase, in this order */ +} + +uint32_t qvirtqueue_add_indirect(QVirtQueue *vq, QVRingIndirectDesc *indirect) +{ + g_assert(vq->indirect); + g_assert_cmpint(vq->size, >=, indirect->elem); + g_assert_cmpint(indirect->index, ==, indirect->elem); + + vq->num_free--; + + /* vq->desc[vq->free_head].addr */ + writeq(vq->desc + (16 * vq->free_head), indirect->desc); + /* vq->desc[vq->free_head].len */ + writel(vq->desc + (16 * vq->free_head) + 8, + sizeof(QVRingDesc) * indirect->elem); + /* vq->desc[vq->free_head].flags */ + writew(vq->desc + (16 * vq->free_head) + 12, QVRING_DESC_F_INDIRECT); + + return vq->free_head++; /* Return and increase, in this order */ +} + +void qvirtqueue_kick(const QVirtioBus *bus, QVirtioDevice *d, QVirtQueue *vq, + uint32_t free_head) +{ + /* vq->avail->idx */ + uint16_t idx = readl(vq->avail + 2); + /* vq->used->flags */ + uint16_t flags; + /* vq->used->avail_event */ + uint16_t avail_event; + + /* vq->avail->ring[idx % vq->size] */ + writel(vq->avail + 4 + (2 * (idx % vq->size)), free_head); + /* vq->avail->idx */ + writel(vq->avail + 2, idx + 1); + + /* Must read after idx is updated */ + flags = readw(vq->avail); + avail_event = readw(vq->used + 4 + + (sizeof(struct QVRingUsedElem) * vq->size)); + + /* < 1 because we add elements to avail queue one by one */ + if ((flags & QVRING_USED_F_NO_NOTIFY) == 0 && + (!vq->event || (uint16_t)(idx-avail_event) < 1)) { + bus->virtqueue_kick(d, vq); + } +} + +void qvirtqueue_set_used_event(QVirtQueue *vq, uint16_t idx) +{ + g_assert(vq->event); + + /* vq->avail->used_event */ + writew(vq->avail + 4 + (2 * vq->size), idx); +} diff --git a/tests/libqos/virtio.h b/tests/libqos/virtio.h new file mode 100644 index 000000000..29fbacbc9 --- /dev/null +++ b/tests/libqos/virtio.h @@ -0,0 +1,187 @@ +/* + * libqos virtio definitions + * + * Copyright (c) 2014 Marc MarĂ + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef LIBQOS_VIRTIO_H +#define LIBQOS_VIRTIO_H + +#include "libqos/malloc.h" + +#define QVIRTIO_VENDOR_ID 0x1AF4 + +#define QVIRTIO_RESET 0x0 +#define QVIRTIO_ACKNOWLEDGE 0x1 +#define QVIRTIO_DRIVER 0x2 +#define QVIRTIO_DRIVER_OK 0x4 + +#define QVIRTIO_NET_DEVICE_ID 0x1 +#define QVIRTIO_BLK_DEVICE_ID 0x2 + +#define QVIRTIO_F_NOTIFY_ON_EMPTY 0x01000000 +#define QVIRTIO_F_ANY_LAYOUT 0x08000000 +#define QVIRTIO_F_RING_INDIRECT_DESC 0x10000000 +#define QVIRTIO_F_RING_EVENT_IDX 0x20000000 +#define QVIRTIO_F_BAD_FEATURE 0x40000000 + +#define QVRING_DESC_F_NEXT 0x1 +#define QVRING_DESC_F_WRITE 0x2 +#define QVRING_DESC_F_INDIRECT 0x4 + +#define QVIRTIO_F_NOTIFY_ON_EMPTY 0x01000000 +#define QVIRTIO_F_ANY_LAYOUT 0x08000000 +#define QVIRTIO_F_RING_INDIRECT_DESC 0x10000000 +#define QVIRTIO_F_RING_EVENT_IDX 0x20000000 +#define QVIRTIO_F_BAD_FEATURE 0x40000000 + +#define QVRING_AVAIL_F_NO_INTERRUPT 1 + +#define QVRING_USED_F_NO_NOTIFY 1 + +typedef struct QVirtioDevice { + /* Device type */ + uint16_t device_type; +} QVirtioDevice; + +typedef struct QVRingDesc { + uint64_t addr; + uint32_t len; + uint16_t flags; + uint16_t next; +} QVRingDesc; + +typedef struct QVRingAvail { + uint16_t flags; + uint16_t idx; + uint16_t ring[0]; /* This is an array of uint16_t */ + uint16_t used_event; +} QVRingAvail; + +typedef struct QVRingUsedElem { + uint32_t id; + uint32_t len; +} QVRingUsedElem; + +typedef struct QVRingUsed { + uint16_t flags; + uint16_t idx; + QVRingUsedElem ring[0]; /* This is an array of QVRingUsedElem structs */ + uint16_t avail_event; +} QVRingUsed; + +typedef struct QVirtQueue { + uint64_t desc; /* This points to an array of QVRingDesc */ + uint64_t avail; /* This points to a QVRingAvail */ + uint64_t used; /* This points to a QVRingDesc */ + uint16_t index; + uint32_t size; + uint32_t free_head; + uint32_t num_free; + uint32_t align; + bool indirect; + bool event; +} QVirtQueue; + +typedef struct QVRingIndirectDesc { + uint64_t desc; /* This points to an array fo QVRingDesc */ + uint16_t index; + uint16_t elem; +} QVRingIndirectDesc; + +typedef struct QVirtioBus { + uint8_t (*config_readb)(QVirtioDevice *d, void *addr); + uint16_t (*config_readw)(QVirtioDevice *d, void *addr); + uint32_t (*config_readl)(QVirtioDevice *d, void *addr); + uint64_t (*config_readq)(QVirtioDevice *d, void *addr); + + /* Get features of the device */ + uint32_t (*get_features)(QVirtioDevice *d); + + /* Set features of the device */ + void (*set_features)(QVirtioDevice *d, uint32_t features); + + /* Get features of the guest */ + uint32_t (*get_guest_features)(QVirtioDevice *d); + + /* Get status of the device */ + uint8_t (*get_status)(QVirtioDevice *d); + + /* Set status of the device */ + void (*set_status)(QVirtioDevice *d, uint8_t status); + + /* Get the queue ISR status of the device */ + bool (*get_queue_isr_status)(QVirtioDevice *d, QVirtQueue *vq); + + /* Get the configuration ISR status of the device */ + bool (*get_config_isr_status)(QVirtioDevice *d); + + /* Select a queue to work on */ + void (*queue_select)(QVirtioDevice *d, uint16_t index); + + /* Get the size of the selected queue */ + uint16_t (*get_queue_size)(QVirtioDevice *d); + + /* Set the address of the selected queue */ + void (*set_queue_address)(QVirtioDevice *d, uint32_t pfn); + + /* Setup the virtqueue specified by index */ + QVirtQueue *(*virtqueue_setup)(QVirtioDevice *d, QGuestAllocator *alloc, + uint16_t index); + + /* Notify changes in virtqueue */ + void (*virtqueue_kick)(QVirtioDevice *d, QVirtQueue *vq); +} QVirtioBus; + +static inline uint32_t qvring_size(uint32_t num, uint32_t align) +{ + return ((sizeof(struct QVRingDesc) * num + sizeof(uint16_t) * (3 + num) + + align - 1) & ~(align - 1)) + + sizeof(uint16_t) * 3 + sizeof(struct QVRingUsedElem) * num; +} + +uint8_t qvirtio_config_readb(const QVirtioBus *bus, QVirtioDevice *d, + void *addr); +uint16_t qvirtio_config_readw(const QVirtioBus *bus, QVirtioDevice *d, + void *addr); +uint32_t qvirtio_config_readl(const QVirtioBus *bus, QVirtioDevice *d, + void *addr); +uint64_t qvirtio_config_readq(const QVirtioBus *bus, QVirtioDevice *d, + void *addr); +uint32_t qvirtio_get_features(const QVirtioBus *bus, QVirtioDevice *d); +void qvirtio_set_features(const QVirtioBus *bus, QVirtioDevice *d, + uint32_t features); + +void qvirtio_reset(const QVirtioBus *bus, QVirtioDevice *d); +void qvirtio_set_acknowledge(const QVirtioBus *bus, QVirtioDevice *d); +void qvirtio_set_driver(const QVirtioBus *bus, QVirtioDevice *d); +void qvirtio_set_driver_ok(const QVirtioBus *bus, QVirtioDevice *d); + +void qvirtio_wait_queue_isr(const QVirtioBus *bus, QVirtioDevice *d, + QVirtQueue *vq, gint64 timeout_us); +uint8_t qvirtio_wait_status_byte_no_isr(const QVirtioBus *bus, + QVirtioDevice *d, + QVirtQueue *vq, + uint64_t addr, + gint64 timeout_us); +void qvirtio_wait_config_isr(const QVirtioBus *bus, QVirtioDevice *d, + gint64 timeout_us); +QVirtQueue *qvirtqueue_setup(const QVirtioBus *bus, QVirtioDevice *d, + QGuestAllocator *alloc, uint16_t index); + +void qvring_init(const QGuestAllocator *alloc, QVirtQueue *vq, uint64_t addr); +QVRingIndirectDesc *qvring_indirect_desc_setup(QVirtioDevice *d, + QGuestAllocator *alloc, uint16_t elem); +void qvring_indirect_desc_add(QVRingIndirectDesc *indirect, uint64_t data, + uint32_t len, bool write); +uint32_t qvirtqueue_add(QVirtQueue *vq, uint64_t data, uint32_t len, bool write, + bool next); +uint32_t qvirtqueue_add_indirect(QVirtQueue *vq, QVRingIndirectDesc *indirect); +void qvirtqueue_kick(const QVirtioBus *bus, QVirtioDevice *d, QVirtQueue *vq, + uint32_t free_head); + +void qvirtqueue_set_used_event(QVirtQueue *vq, uint16_t idx); +#endif diff --git a/tests/libqtest.c b/tests/libqtest.c index 98e8f4b64..9a92aa70e 100644 --- a/tests/libqtest.c +++ b/tests/libqtest.c @@ -165,13 +165,15 @@ QTestState *qtest_init(const char *extra_args) s->qemu_pid = fork(); if (s->qemu_pid == 0) { + setenv("QEMU_AUDIO_DRV", "none", true); command = g_strdup_printf("exec %s " "-qtest unix:%s,nowait " - "-qtest-log /dev/null " + "-qtest-log %s " "-qmp unix:%s,nowait " "-machine accel=qtest " "-display none " "%s", qemu_binary, socket_path, + getenv("QTEST_LOG") ? "/dev/fd/2" : "/dev/null", qmp_socket_path, extra_args ?: ""); execlp("/bin/sh", "sh", "-c", command, NULL); @@ -358,6 +360,7 @@ static void qmp_response(JSONMessageParser *parser, QList *tokens) QDict *qtest_qmp_receive(QTestState *s) { QMPResponseParser qmp; + bool log = getenv("QTEST_LOG") != NULL; qmp.response = NULL; json_message_parser_init(&qmp.parser, qmp_response); @@ -375,6 +378,9 @@ QDict *qtest_qmp_receive(QTestState *s) exit(1); } + if (log) { + len = write(2, &c, 1); + } json_message_parser_feed(&qmp.parser, &c, 1); } json_message_parser_destroy(&qmp.parser); @@ -397,10 +403,14 @@ QDict *qtest_qmpv(QTestState *s, const char *fmt, va_list ap) /* No need to send anything for an empty QObject. */ if (qobj) { + int log = getenv("QTEST_LOG") != NULL; QString *qstr = qobject_to_json(qobj); const char *str = qstring_get_str(qstr); size_t size = qstring_get_length(qstr); + if (log) { + fprintf(stderr, "%s", str); + } /* Send QMP request */ socket_send(s->qmp_fd, str, size); @@ -639,6 +649,7 @@ void qtest_add_func(const char *str, void (*fn)) { gchar *path = g_strdup_printf("/%s/%s", qtest_get_arch(), str); g_test_add_func(path, fn); + g_free(path); } void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size) @@ -654,6 +665,18 @@ void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size) qtest_rsp(s, 0); } +void qtest_memset(QTestState *s, uint64_t addr, uint8_t pattern, size_t size) +{ + size_t i; + + qtest_sendf(s, "write 0x%" PRIx64 " 0x%zx 0x", addr, size); + for (i = 0; i < size; i++) { + qtest_sendf(s, "%02x", pattern); + } + qtest_sendf(s, "\n"); + qtest_rsp(s, 0); +} + QDict *qmp(const char *fmt, ...) { va_list ap; @@ -673,3 +696,51 @@ void qmp_discard_response(const char *fmt, ...) qtest_qmpv_discard_response(global_qtest, fmt, ap); va_end(ap); } + +bool qtest_big_endian(void) +{ + const char *arch = qtest_get_arch(); + int i; + + static const struct { + const char *arch; + bool big_endian; + } endianness[] = { + { "aarch64", false }, + { "alpha", false }, + { "arm", false }, + { "cris", false }, + { "i386", false }, + { "lm32", true }, + { "m68k", true }, + { "microblaze", true }, + { "microblazeel", false }, + { "mips", true }, + { "mips64", true }, + { "mips64el", false }, + { "mipsel", false }, + { "moxie", true }, + { "or32", true }, + { "ppc", true }, + { "ppc64", true }, + { "ppcemb", true }, + { "s390x", true }, + { "sh4", false }, + { "sh4eb", true }, + { "sparc", true }, + { "sparc64", true }, + { "unicore32", false }, + { "x86_64", false }, + { "xtensa", false }, + { "xtensaeb", true }, + {}, + }; + + for (i = 0; endianness[i].arch; i++) { + if (strcmp(endianness[i].arch, arch) == 0) { + return endianness[i].big_endian; + } + } + + return false; +} diff --git a/tests/libqtest.h b/tests/libqtest.h index 8f323c703..e7413d52d 100644 --- a/tests/libqtest.h +++ b/tests/libqtest.h @@ -23,6 +23,7 @@ #include <stdarg.h> #include <sys/types.h> #include "qapi/qmp/qdict.h" +#include "glib-compat.h" typedef struct QTestState QTestState; @@ -283,6 +284,17 @@ void qtest_memread(QTestState *s, uint64_t addr, void *data, size_t size); void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size); /** + * qtest_memset: + * @s: #QTestState instance to operate on. + * @addr: Guest address to write to. + * @patt: Byte pattern to fill the guest memory region with. + * @size: Number of bytes to write. + * + * Write a pattern to guest memory. + */ +void qtest_memset(QTestState *s, uint64_t addr, uint8_t patt, size_t size); + +/** * qtest_clock_step_next: * @s: #QTestState instance to operate on. * @@ -621,6 +633,19 @@ static inline void memwrite(uint64_t addr, const void *data, size_t size) } /** + * qmemset: + * @addr: Guest address to write to. + * @patt: Byte pattern to fill the guest memory region with. + * @size: Number of bytes to write. + * + * Write a pattern to guest memory. + */ +static inline void qmemset(uint64_t addr, uint8_t patt, size_t size) +{ + qtest_memset(global_qtest, addr, patt, size); +} + +/** * clock_step_next: * * Advance the QEMU_CLOCK_VIRTUAL to the next deadline. @@ -658,4 +683,11 @@ static inline int64_t clock_set(int64_t val) return qtest_clock_set(global_qtest, val); } +/** + * qtest_big_endian: + * + * Returns: True if the architecture under test has a big endian configuration. + */ +bool qtest_big_endian(void); + #endif diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index ab4d3d96b..d43b5fd2e 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -33,6 +33,9 @@ { 'type': 'UserDefB', 'data': { 'integer': 'int' } } +{ 'type': 'UserDefC', + 'data': { 'string1': 'str', 'string2': 'str' } } + { 'union': 'UserDefUnion', 'base': 'UserDefZero', 'data': { 'a' : 'UserDefA', 'b' : 'UserDefB' } } @@ -47,6 +50,13 @@ # FIXME generated struct UserDefFlatUnion has members for direct base # UserDefOne, but lacks members for indirect base UserDefZero +# this variant of UserDefFlatUnion defaults to a union that uses fields with +# allocated types to test corner cases in the cleanup/dealloc visitor +{ 'union': 'UserDefFlatUnion2', + 'base': 'UserDefUnionBase', + 'discriminator': 'enum1', + 'data': { 'value1' : 'UserDefC', 'value2' : 'UserDefB', 'value3' : 'UserDefA' } } + { 'union': 'UserDefAnonUnion', 'discriminator': {}, 'data': { 'uda': 'UserDefA', 's': 'str', 'i': 'int' } } diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 95e989925..08d7304df 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -6,9 +6,11 @@ OrderedDict([('type', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]), OrderedDict([('type', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]), OrderedDict([('type', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]), + OrderedDict([('type', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]), OrderedDict([('union', 'UserDefUnion'), ('base', 'UserDefZero'), ('data', OrderedDict([('a', 'UserDefA'), ('b', 'UserDefB')]))]), OrderedDict([('type', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]), OrderedDict([('union', 'UserDefFlatUnion'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefA'), ('value2', 'UserDefB'), ('value3', 'UserDefB')]))]), + OrderedDict([('union', 'UserDefFlatUnion2'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefC'), ('value2', 'UserDefB'), ('value3', 'UserDefA')]))]), OrderedDict([('union', 'UserDefAnonUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]), OrderedDict([('union', 'UserDefNativeListUnion'), ('data', OrderedDict([('integer', ['int']), ('s8', ['int8']), ('s16', ['int16']), ('s32', ['int32']), ('s64', ['int64']), ('u8', ['uint8']), ('u16', ['uint16']), ('u32', ['uint32']), ('u64', ['uint64']), ('number', ['number']), ('boolean', ['bool']), ('string', ['str'])]))]), OrderedDict([('command', 'user_def_cmd'), ('data', OrderedDict())]), @@ -32,6 +34,7 @@ OrderedDict([('type', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]), OrderedDict([('type', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]), OrderedDict([('type', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]), + OrderedDict([('type', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]), OrderedDict([('type', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]), OrderedDict([('type', 'UserDefOptions'), ('data', OrderedDict([('*i64', ['int']), ('*u64', ['uint64']), ('*u16', ['uint16']), ('*i64x', 'int'), ('*u64x', 'uint64')]))]), OrderedDict([('type', 'EventStructOne'), ('data', OrderedDict([('struct1', 'UserDefOne'), ('string', 'str'), ('*enum2', 'EnumOne')]))])] diff --git a/tests/qdev-monitor-test.c b/tests/qdev-monitor-test.c deleted file mode 100644 index e20ffd67a..000000000 --- a/tests/qdev-monitor-test.c +++ /dev/null @@ -1,77 +0,0 @@ -/* - * qdev-monitor.c test cases - * - * Copyright (C) 2013 Red Hat Inc. - * - * Authors: - * Stefan Hajnoczi <stefanha@redhat.com> - * - * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. - * See the COPYING.LIB file in the top-level directory. - */ - -#include <string.h> -#include <glib.h> -#include "libqtest.h" -#include "qapi/qmp/qjson.h" - -static void test_device_add(void) -{ - QDict *response; - QDict *error; - - qtest_start("-drive if=none,id=drive0"); - - /* Make device_add fail. If this leaks the virtio-blk-pci device then a - * reference to drive0 will also be held (via qdev properties). - */ - response = qmp("{\"execute\": \"device_add\"," - " \"arguments\": {" - " \"driver\": \"virtio-blk-pci\"," - " \"drive\": \"drive0\"" - "}}"); - g_assert(response); - error = qdict_get_qdict(response, "error"); - g_assert_cmpstr(qdict_get_try_str(error, "class"), ==, "GenericError"); - QDECREF(response); - - /* Delete the drive */ - response = qmp("{\"execute\": \"human-monitor-command\"," - " \"arguments\": {" - " \"command-line\": \"drive_del drive0\"" - "}}"); - g_assert(response); - g_assert_cmpstr(qdict_get_try_str(response, "return"), ==, ""); - QDECREF(response); - - /* Try to re-add the drive. This fails with duplicate IDs if a leaked - * virtio-blk-pci exists that holds a reference to the old drive0. - */ - response = qmp("{\"execute\": \"human-monitor-command\"," - " \"arguments\": {" - " \"command-line\": \"drive_add pci-addr=auto if=none,id=drive0\"" - "}}"); - g_assert(response); - g_assert_cmpstr(qdict_get_try_str(response, "return"), ==, "OK\r\n"); - QDECREF(response); - - qtest_end(); -} - -int main(int argc, char **argv) -{ - const char *arch = qtest_get_arch(); - - /* Check architecture */ - if (strcmp(arch, "i386") && strcmp(arch, "x86_64")) { - g_test_message("Skipping test for non-x86\n"); - return 0; - } - - /* Run the tests */ - g_test_init(&argc, &argv, NULL); - - qtest_add_func("/qmp/device_add", test_device_add); - - return g_test_run(); -} diff --git a/tests/qemu-iotests-quick.sh b/tests/qemu-iotests-quick.sh index 8a9a4c68e..12af731c6 100755 --- a/tests/qemu-iotests-quick.sh +++ b/tests/qemu-iotests-quick.sh @@ -3,6 +3,6 @@ cd tests/qemu-iotests ret=0 -./check -T -nocache -qcow2 -g quick || ret=1 +./check -T -qcow2 -g quick || ret=1 exit $ret diff --git a/tests/qemu-iotests/025 b/tests/qemu-iotests/025 index a5f45b454..467a4b709 100755 --- a/tests/qemu-iotests/025 +++ b/tests/qemu-iotests/025 @@ -40,7 +40,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.pattern _supported_fmt raw qcow2 qed -_supported_proto file sheepdog rbd nfs +_supported_proto file sheepdog rbd nfs archipelago _supported_os Linux echo "=== Creating image" diff --git a/tests/qemu-iotests/028 b/tests/qemu-iotests/028 index 9e701e1c2..a1f4423d4 100755 --- a/tests/qemu-iotests/028 +++ b/tests/qemu-iotests/028 @@ -113,6 +113,7 @@ QEMU_COMM_TIMEOUT=1 # Silence output since it contains the disk image path and QEMU's readline # character echoing makes it very hard to filter the output _send_qemu_cmd $h "drive_backup disk ${TEST_IMG}.copy" "(qemu)" >/dev/null +_send_qemu_cmd $h "" "Formatting" | _filter_img_create qemu_cmd_repeat=20 _send_qemu_cmd $h "info block-jobs" "No active jobs" _send_qemu_cmd $h 'quit' "" diff --git a/tests/qemu-iotests/028.out b/tests/qemu-iotests/028.out index 0e1a5ae65..e8d02459b 100644 --- a/tests/qemu-iotests/028.out +++ b/tests/qemu-iotests/028.out @@ -468,7 +468,8 @@ No errors were found on the image. block-backup -Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=4294968832 backing_file='TEST_DIR/t.qcow2.base' backing_fmt='qcow2' encryption=off cluster_size=65536 lazy_refcounts=off +Formatting 'TEST_DIR/t.IMGFMT.copy', fmt=IMGFMT size=4294968832 backing_file='TEST_DIR/t.IMGFMT.base' backing_fmt='IMGFMT' +(qemu) (qemu) i[K[Din[K[D[Dinf[K[D[D[Dinfo[K[D[D[D[Dinfo [K[D[D[D[D[Dinfo b[K[D[D[D[D[D[Dinfo bl[K[D[D[D[D[D[D[Dinfo blo[K[D[D[D[D[D[D[D[Dinfo bloc[K[D[D[D[D[D[D[D[D[Dinfo block[K[D[D[D[D[D[D[D[D[D[Dinfo block-[K[D[D[D[D[D[D[D[D[D[D[Dinfo block-j[K[D[D[D[D[D[D[D[D[D[D[D[Dinfo block-jo[K[D[D[D[D[D[D[D[D[D[D[D[D[Dinfo block-job[K[D[D[D[D[D[D[D[D[D[D[D[D[D[Dinfo block-jobs[K Type backup, device disk: Completed 0 of 4294968832 bytes, speed limit 0 bytes/s i[K[Din[K[D[Dinf[K[D[D[Dinfo[K[D[D[D[Dinfo [K[D[D[D[D[Dinfo b[K[D[D[D[D[D[Dinfo bl[K[D[D[D[D[D[D[Dinfo blo[K[D[D[D[D[D[D[D[Dinfo bloc[K[D[D[D[D[D[D[D[D[Dinfo block[K[D[D[D[D[D[D[D[D[D[Dinfo block-[K[D[D[D[D[D[D[D[D[D[D[Dinfo block-j[K[D[D[D[D[D[D[D[D[D[D[D[Dinfo block-jo[K[D[D[D[D[D[D[D[D[D[D[D[D[Dinfo block-job[K[D[D[D[D[D[D[D[D[D[D[D[D[D[Dinfo block-jobs[K diff --git a/tests/qemu-iotests/039.out b/tests/qemu-iotests/039.out index 67e774430..0adf1535e 100644 --- a/tests/qemu-iotests/039.out +++ b/tests/qemu-iotests/039.out @@ -25,7 +25,10 @@ read 512/512 bytes at offset 0 incompatible_features 0x1 == Repairing the image file must succeed == -Repairing cluster 5 refcount=0 reference=1 +ERROR cluster 5 refcount=0 reference=1 +Rebuilding refcount structure +Repairing cluster 1 refcount=1 reference=0 +Repairing cluster 2 refcount=1 reference=0 The following inconsistencies were found and repaired: 0 leaked clusters @@ -45,7 +48,10 @@ wrote 512/512 bytes at offset 0 512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) ./039: Aborted ( ulimit -c 0; exec "$@" ) incompatible_features 0x1 -Repairing cluster 5 refcount=0 reference=1 +ERROR cluster 5 refcount=0 reference=1 +Rebuilding refcount structure +Repairing cluster 1 refcount=1 reference=0 +Repairing cluster 2 refcount=1 reference=0 wrote 512/512 bytes at offset 0 512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) incompatible_features 0x0 diff --git a/tests/qemu-iotests/040 b/tests/qemu-iotests/040 index f1e16c11c..2b432ad7a 100755 --- a/tests/qemu-iotests/040 +++ b/tests/qemu-iotests/040 @@ -43,8 +43,7 @@ class ImageCommitTestCase(iotests.QMPTestCase): if event['event'] == 'BLOCK_JOB_COMPLETED': self.assert_qmp(event, 'data/type', 'commit') self.assert_qmp(event, 'data/device', 'drive0') - self.assert_qmp(event, 'data/offset', self.image_len) - self.assert_qmp(event, 'data/len', self.image_len) + self.assert_qmp(event, 'data/offset', event['data']['len']) if need_ready: self.assertTrue(ready, "Expecting BLOCK_JOB_COMPLETED event") completed = True @@ -52,7 +51,6 @@ class ImageCommitTestCase(iotests.QMPTestCase): ready = True self.assert_qmp(event, 'data/type', 'commit') self.assert_qmp(event, 'data/device', 'drive0') - self.assert_qmp(event, 'data/len', self.image_len) self.vm.qmp('block-job-complete', device='drive0') self.assert_no_active_block_jobs() diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041 index 5dbd4ee91..59a8f733f 100755 --- a/tests/qemu-iotests/041 +++ b/tests/qemu-iotests/041 @@ -52,8 +52,7 @@ class ImageMirroringTestCase(iotests.QMPTestCase): event = self.cancel_and_wait(drive=drive) self.assertEquals(event['event'], 'BLOCK_JOB_COMPLETED') self.assert_qmp(event, 'data/type', 'mirror') - self.assert_qmp(event, 'data/offset', self.image_len) - self.assert_qmp(event, 'data/len', self.image_len) + self.assert_qmp(event, 'data/offset', event['data']['len']) def complete_and_wait(self, drive='drive0', wait_ready=True): '''Complete a block job and wait for it to finish''' @@ -417,7 +416,6 @@ new_state = "1" self.assert_qmp(event, 'data/type', 'mirror') self.assert_qmp(event, 'data/device', 'drive0') self.assert_qmp(event, 'data/error', 'Input/output error') - self.assert_qmp(event, 'data/len', self.image_len) completed = True self.assert_no_active_block_jobs() @@ -568,7 +566,6 @@ new_state = "1" self.assert_qmp(event, 'data/type', 'mirror') self.assert_qmp(event, 'data/device', 'drive0') self.assert_qmp(event, 'data/error', 'Input/output error') - self.assert_qmp(event, 'data/len', self.image_len) completed = True self.assert_no_active_block_jobs() diff --git a/tests/qemu-iotests/049.out b/tests/qemu-iotests/049.out index 71ca44d76..09ca0aed4 100644 --- a/tests/qemu-iotests/049.out +++ b/tests/qemu-iotests/049.out @@ -179,7 +179,7 @@ qemu-img create -f qcow2 -o preallocation=metadata TEST_DIR/t.qcow2 64M Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=65536 preallocation='metadata' lazy_refcounts=off qemu-img create -f qcow2 -o preallocation=1234 TEST_DIR/t.qcow2 64M -qemu-img: TEST_DIR/t.qcow2: Invalid preallocation mode: '1234' +qemu-img: TEST_DIR/t.qcow2: invalid parameter value: 1234 Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 encryption=off cluster_size=65536 preallocation='1234' lazy_refcounts=off == Check encryption option == diff --git a/tests/qemu-iotests/051 b/tests/qemu-iotests/051 index a41334e02..11c858f27 100755 --- a/tests/qemu-iotests/051 +++ b/tests/qemu-iotests/051 @@ -199,6 +199,29 @@ run_qemu -drive file.driver=raw run_qemu -drive foo=bar echo +echo === Specifying both an option and its legacy alias === +echo + +run_qemu -drive file="$TEST_IMG",iops=1234,throttling.iops-total=5678 +run_qemu -drive file="$TEST_IMG",iops_rd=1234,throttling.iops-read=5678 +run_qemu -drive file="$TEST_IMG",iops_wr=1234,throttling.iops-write=5678 + +run_qemu -drive file="$TEST_IMG",bps=1234,throttling.bps-total=5678 +run_qemu -drive file="$TEST_IMG",bps_rd=1234,throttling.bps-read=5678 +run_qemu -drive file="$TEST_IMG",bps_wr=1234,throttling.bps-write=5678 + +run_qemu -drive file="$TEST_IMG",iops_max=1234,throttling.iops-total-max=5678 +run_qemu -drive file="$TEST_IMG",iops_rd_max=1234,throttling.iops-read-max=5678 +run_qemu -drive file="$TEST_IMG",iops_wr_max=1234,throttling.iops-write-max=5678 + +run_qemu -drive file="$TEST_IMG",bps_max=1234,throttling.bps-total-max=5678 +run_qemu -drive file="$TEST_IMG",bps_rd_max=1234,throttling.bps-read-max=5678 +run_qemu -drive file="$TEST_IMG",bps_wr_max=1234,throttling.bps-write-max=5678 + +run_qemu -drive file="$TEST_IMG",iops_size=1234,throttling.iops-size=5678 +run_qemu -drive file="$TEST_IMG",readonly=on,read-only=off + +echo echo === Parsing protocol from file name === echo diff --git a/tests/qemu-iotests/051.out b/tests/qemu-iotests/051.out index d7b0f503a..2c7e80876 100644 --- a/tests/qemu-iotests/051.out +++ b/tests/qemu-iotests/051.out @@ -149,13 +149,11 @@ QEMU_PROG: -device ide-hd,drive=disk: Device 'ide-hd' could not be initialized Testing: -drive if=none,id=disk -device lsi53c895a -device scsi-disk,drive=disk QEMU X.Y.Z monitor - type 'help' for more information (qemu) QEMU_PROG: -device scsi-disk,drive=disk: Device needs media, but drive is empty -QEMU_PROG: -device scsi-disk,drive=disk: Device initialization failed. QEMU_PROG: -device scsi-disk,drive=disk: Device 'scsi-disk' could not be initialized Testing: -drive if=none,id=disk -device lsi53c895a -device scsi-hd,drive=disk QEMU X.Y.Z monitor - type 'help' for more information (qemu) QEMU_PROG: -device scsi-hd,drive=disk: Device needs media, but drive is empty -QEMU_PROG: -device scsi-hd,drive=disk: Device initialization failed. QEMU_PROG: -device scsi-hd,drive=disk: Device 'scsi-hd' could not be initialized @@ -276,6 +274,51 @@ Testing: -drive foo=bar QEMU_PROG: -drive foo=bar: could not open disk image ide0-hd0: Must specify either driver or file +=== Specifying both an option and its legacy alias === + +Testing: -drive file=TEST_DIR/t.qcow2,iops=1234,throttling.iops-total=5678 +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,iops=1234,throttling.iops-total=5678: 'throttling.iops-total' and its alias 'iops' can't be used at the same time + +Testing: -drive file=TEST_DIR/t.qcow2,iops_rd=1234,throttling.iops-read=5678 +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,iops_rd=1234,throttling.iops-read=5678: 'throttling.iops-read' and its alias 'iops_rd' can't be used at the same time + +Testing: -drive file=TEST_DIR/t.qcow2,iops_wr=1234,throttling.iops-write=5678 +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,iops_wr=1234,throttling.iops-write=5678: 'throttling.iops-write' and its alias 'iops_wr' can't be used at the same time + +Testing: -drive file=TEST_DIR/t.qcow2,bps=1234,throttling.bps-total=5678 +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,bps=1234,throttling.bps-total=5678: 'throttling.bps-total' and its alias 'bps' can't be used at the same time + +Testing: -drive file=TEST_DIR/t.qcow2,bps_rd=1234,throttling.bps-read=5678 +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,bps_rd=1234,throttling.bps-read=5678: 'throttling.bps-read' and its alias 'bps_rd' can't be used at the same time + +Testing: -drive file=TEST_DIR/t.qcow2,bps_wr=1234,throttling.bps-write=5678 +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,bps_wr=1234,throttling.bps-write=5678: 'throttling.bps-write' and its alias 'bps_wr' can't be used at the same time + +Testing: -drive file=TEST_DIR/t.qcow2,iops_max=1234,throttling.iops-total-max=5678 +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,iops_max=1234,throttling.iops-total-max=5678: 'throttling.iops-total-max' and its alias 'iops_max' can't be used at the same time + +Testing: -drive file=TEST_DIR/t.qcow2,iops_rd_max=1234,throttling.iops-read-max=5678 +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,iops_rd_max=1234,throttling.iops-read-max=5678: 'throttling.iops-read-max' and its alias 'iops_rd_max' can't be used at the same time + +Testing: -drive file=TEST_DIR/t.qcow2,iops_wr_max=1234,throttling.iops-write-max=5678 +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,iops_wr_max=1234,throttling.iops-write-max=5678: 'throttling.iops-write-max' and its alias 'iops_wr_max' can't be used at the same time + +Testing: -drive file=TEST_DIR/t.qcow2,bps_max=1234,throttling.bps-total-max=5678 +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,bps_max=1234,throttling.bps-total-max=5678: 'throttling.bps-total-max' and its alias 'bps_max' can't be used at the same time + +Testing: -drive file=TEST_DIR/t.qcow2,bps_rd_max=1234,throttling.bps-read-max=5678 +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,bps_rd_max=1234,throttling.bps-read-max=5678: 'throttling.bps-read-max' and its alias 'bps_rd_max' can't be used at the same time + +Testing: -drive file=TEST_DIR/t.qcow2,bps_wr_max=1234,throttling.bps-write-max=5678 +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,bps_wr_max=1234,throttling.bps-write-max=5678: 'throttling.bps-write-max' and its alias 'bps_wr_max' can't be used at the same time + +Testing: -drive file=TEST_DIR/t.qcow2,iops_size=1234,throttling.iops-size=5678 +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,iops_size=1234,throttling.iops-size=5678: 'throttling.iops-size' and its alias 'iops_size' can't be used at the same time + +Testing: -drive file=TEST_DIR/t.qcow2,readonly=on,read-only=off +QEMU_PROG: -drive file=TEST_DIR/t.qcow2,readonly=on,read-only=off: 'read-only' and its alias 'readonly' can't be used at the same time + + === Parsing protocol from file name === Testing: -hda foo:bar diff --git a/tests/qemu-iotests/052 b/tests/qemu-iotests/052 index 6bdae9278..61959e286 100755 --- a/tests/qemu-iotests/052 +++ b/tests/qemu-iotests/052 @@ -41,8 +41,9 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 _supported_fmt generic _supported_proto file _supported_os Linux -_default_cache_mode "writethrough" -_supported_cache_modes "writethrough" + +# Don't do O_DIRECT on tmpfs +_supported_cache_modes "writeback" "writethrough" "unsafe" size=128M _make_test_img $size diff --git a/tests/qemu-iotests/059 b/tests/qemu-iotests/059 index 26a2fd3e0..3c053c29b 100755 --- a/tests/qemu-iotests/059 +++ b/tests/qemu-iotests/059 @@ -114,6 +114,10 @@ echo echo "=== Testing version 3 ===" _use_sample_img iotest-version3.vmdk.bz2 _img_info +for i in {0..99}; do + $QEMU_IO -r -c "read -P $(( i % 10 + 0x30 )) $(( i * 64 * 1024 * 10 + i * 512 )) 512" $TEST_IMG \ + | _filter_qemu_io +done echo echo "=== Testing 4TB monolithicFlat creation and IO ===" diff --git a/tests/qemu-iotests/059.out b/tests/qemu-iotests/059.out index eba0dedda..0dadba658 100644 --- a/tests/qemu-iotests/059.out +++ b/tests/qemu-iotests/059.out @@ -2056,8 +2056,208 @@ wrote 512/512 bytes at offset 10240 === Testing version 3 === image: TEST_DIR/iotest-version3.IMGFMT file format: IMGFMT -virtual size: 1.0G (1073741824 bytes) +virtual size: 16G (17179869184 bytes) cluster_size: 65536 +read 512/512 bytes at offset 0 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 655872 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 1311744 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 1967616 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 2623488 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 3279360 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 3935232 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 4591104 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 5246976 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 5902848 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 6558720 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 7214592 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 7870464 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 8526336 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 9182208 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 9838080 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 10493952 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 11149824 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 11805696 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 12461568 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 13117440 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 13773312 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 14429184 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 15085056 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 15740928 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 16396800 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 17052672 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 17708544 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 18364416 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 19020288 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 19676160 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 20332032 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 20987904 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 21643776 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 22299648 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 22955520 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 23611392 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 24267264 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 24923136 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 25579008 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 26234880 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 26890752 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 27546624 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 28202496 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 28858368 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 29514240 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 30170112 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 30825984 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 31481856 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 32137728 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 32793600 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 33449472 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 34105344 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 34761216 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 35417088 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 36072960 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 36728832 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 37384704 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 38040576 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 38696448 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 39352320 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 40008192 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 40664064 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 41319936 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 41975808 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 42631680 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 43287552 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 43943424 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 44599296 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 45255168 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 45911040 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 46566912 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 47222784 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 47878656 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 48534528 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 49190400 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 49846272 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 50502144 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 51158016 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 51813888 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 52469760 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 53125632 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 53781504 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 54437376 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 55093248 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 55749120 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 56404992 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 57060864 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 57716736 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 58372608 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 59028480 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 59684352 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 60340224 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 60996096 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 61651968 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 62307840 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 62963712 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 63619584 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 64275456 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 64931328 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) === Testing 4TB monolithicFlat creation and IO === Formatting 'TEST_DIR/iotest-version3.IMGFMT', fmt=IMGFMT size=4398046511104 diff --git a/tests/qemu-iotests/060 b/tests/qemu-iotests/060 index 3cffc12fe..9772d365a 100755 --- a/tests/qemu-iotests/060 +++ b/tests/qemu-iotests/060 @@ -76,6 +76,9 @@ $QEMU_IO -c "$OPEN_RW" -c "write -P 0x2a 0 512" | _filter_qemu_io # The corrupt bit must now be set $PYTHON qcow2.py "$TEST_IMG" dump-header | grep incompatible_features +# This information should be available through qemu-img info +$QEMU_IMG info "$TEST_IMG" | _filter_testdir + # Try to open the image R/W (which should fail) $QEMU_IO -c "$OPEN_RW" -c "read 0 512" 2>&1 | _filter_qemu_io \ | _filter_testdir \ @@ -164,6 +167,67 @@ wait_break 0 write 64k 64k resume 0" | $QEMU_IO | _filter_qemu_io +echo +echo "=== Testing unallocated image header ===" +echo +_make_test_img 64M +# Create L1/L2 +$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io +poke_file "$TEST_IMG" "$rb_offset" "\x00\x00" +$QEMU_IO -c "write 64k 64k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "=== Testing unaligned L1 entry ===" +echo +_make_test_img 64M +$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io +# This will be masked with ~(512 - 1) = ~0x1ff, so whether the lower 9 bits are +# aligned or not does not matter +poke_file "$TEST_IMG" "$l1_offset" "\x80\x00\x00\x00\x00\x04\x2a\x00" +$QEMU_IO -c "read 0 64k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "=== Testing unaligned L2 entry ===" +echo +_make_test_img 64M +$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io +poke_file "$TEST_IMG" "$l2_offset" "\x80\x00\x00\x00\x00\x05\x2a\x00" +$QEMU_IO -c "read 0 64k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "=== Testing unaligned reftable entry ===" +echo +_make_test_img 64M +poke_file "$TEST_IMG" "$rt_offset" "\x00\x00\x00\x00\x00\x02\x2a\x00" +$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "=== Testing non-fatal corruption on freeing ===" +echo +_make_test_img 64M +$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io +poke_file "$TEST_IMG" "$l2_offset" "\x80\x00\x00\x00\x00\x05\x2a\x00" +$QEMU_IO -c "discard 0 64k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "=== Testing read-only corruption report ===" +echo +_make_test_img 64M +$QEMU_IO -c "write 0 64k" "$TEST_IMG" | _filter_qemu_io +poke_file "$TEST_IMG" "$l2_offset" "\x80\x00\x00\x00\x00\x05\x2a\x00" +# Should only emit a single error message +$QEMU_IO -c "$OPEN_RO" -c "read 0 64k" -c "read 0 64k" | _filter_qemu_io + +echo +echo "=== Testing non-fatal and then fatal corruption report ===" +echo +_make_test_img 64M +$QEMU_IO -c "write 0 128k" "$TEST_IMG" | _filter_qemu_io +poke_file "$TEST_IMG" "$l2_offset" "\x80\x00\x00\x00\x00\x05\x2a\x00" +poke_file "$TEST_IMG" "$(($l2_offset+8))" "\x80\x00\x00\x00\x00\x06\x2a\x00" +# Should emit two error messages +$QEMU_IO -c "discard 0 64k" -c "read 64k 64k" "$TEST_IMG" | _filter_qemu_io + # success, all done echo "*** done" rm -f $seq.full diff --git a/tests/qemu-iotests/060.out b/tests/qemu-iotests/060.out index a51794803..9419da1b4 100644 --- a/tests/qemu-iotests/060.out +++ b/tests/qemu-iotests/060.out @@ -8,9 +8,18 @@ ERROR cluster 3 refcount=1 reference=3 1 errors were found on the image. Data may be corrupted, or further writes to the image may corrupt it. incompatible_features 0x0 -qcow2: Preventing invalid write on metadata (overlaps with active L1 table); image marked as corrupt. +qcow2: Marking image as corrupt: Preventing invalid write on metadata (overlaps with active L1 table); further corruption events will be suppressed write failed: Input/output error incompatible_features 0x2 +image: TEST_DIR/t.qcow2 +file format: qcow2 +virtual size: 64M (67108864 bytes) +disk size: 196K +cluster_size: 65536 +Format specific information: + compat: 1.1 + lazy refcounts: false + corrupt: true qemu-io: can't open device TEST_DIR/t.IMGFMT: IMGFMT: Image is corrupt; cannot be opened read/write read 512/512 bytes at offset 0 512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) @@ -24,14 +33,18 @@ ERROR cluster 2 refcount=1 reference=2 2 errors were found on the image. Data may be corrupted, or further writes to the image may corrupt it. incompatible_features 0x0 -qcow2: Preventing invalid write on metadata (overlaps with refcount block); image marked as corrupt. +qcow2: Marking image as corrupt: Preventing invalid write on metadata (overlaps with refcount block); further corruption events will be suppressed write failed: Input/output error incompatible_features 0x2 -Repairing refcount block 0 refcount=2 +ERROR refcount block 0 refcount=2 +ERROR cluster 2 refcount=1 reference=2 +Rebuilding refcount structure +Repairing cluster 1 refcount=1 reference=0 +Repairing cluster 2 refcount=2 reference=1 The following inconsistencies were found and repaired: 0 leaked clusters - 1 corruptions + 2 corruptions Double checking the fixed image now... No errors were found on the image. @@ -56,9 +69,11 @@ Data may be corrupted, or further writes to the image may corrupt it. 1 leaked clusters were found on the image. This means waste of disk space, but no harm to data. incompatible_features 0x0 -qcow2: Preventing invalid write on metadata (overlaps with inactive L2 table); image marked as corrupt. +qcow2: Marking image as corrupt: Preventing invalid write on metadata (overlaps with inactive L2 table); further corruption events will be suppressed write failed: Input/output error incompatible_features 0x2 +ERROR cluster 4 refcount=1 reference=2 +Leaked cluster 9 refcount=1 reference=0 Repairing cluster 4 refcount=1 reference=2 Repairing cluster 9 refcount=1 reference=0 Repairing OFLAG_COPIED data cluster: l2_entry=8000000000040000 refcount=2 @@ -88,9 +103,68 @@ wrote 65536/65536 bytes at offset 536870912 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) discard 65536/65536 bytes at offset 0 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) -qcow2: Preventing invalid write on metadata (overlaps with active L2 table); image marked as corrupt. +qcow2: Marking image as corrupt: Preventing invalid write on metadata (overlaps with active L2 table); further corruption events will be suppressed blkdebug: Suspended request '0' write failed: Input/output error blkdebug: Resuming request '0' aio_write failed: No medium found + +=== Testing unallocated image header === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qcow2: Marking image as corrupt: Preventing invalid write on metadata (overlaps with qcow2_header); further corruption events will be suppressed +write failed: Input/output error + +=== Testing unaligned L1 entry === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qcow2: Marking image as corrupt: L2 table offset 0x42a00 unaligned (L1 index: 0); further corruption events will be suppressed +read failed: Input/output error + +=== Testing unaligned L2 entry === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qcow2: Marking image as corrupt: Data cluster offset 0x52a00 unaligned (L2 offset: 0x40000, L2 index: 0); further corruption events will be suppressed +read failed: Input/output error + +=== Testing unaligned reftable entry === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +qcow2: Marking image as corrupt: Refblock offset 0x22a00 unaligned (reftable index: 0); further corruption events will be suppressed +write failed: Input/output error + +=== Testing non-fatal corruption on freeing === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qcow2: Image is corrupt: Cannot free unaligned cluster 0x52a00; further non-fatal corruption events will be suppressed +discard 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +=== Testing read-only corruption report === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qcow2: Image is corrupt: Data cluster offset 0x52a00 unaligned (L2 offset: 0x40000, L2 index: 0); further non-fatal corruption events will be suppressed +read failed: Input/output error +read failed: Input/output error + +=== Testing non-fatal and then fatal corruption report === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +wrote 131072/131072 bytes at offset 0 +128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qcow2: Image is corrupt: Cannot free unaligned cluster 0x52a00; further non-fatal corruption events will be suppressed +qcow2: Marking image as corrupt: Data cluster offset 0x62a00 unaligned (L2 offset: 0x40000, L2 index: 0x1); further corruption events will be suppressed +discard 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read failed: Input/output error *** done diff --git a/tests/qemu-iotests/061 b/tests/qemu-iotests/061 index ab98def6d..8d37f8a65 100755 --- a/tests/qemu-iotests/061 +++ b/tests/qemu-iotests/061 @@ -209,6 +209,31 @@ $QEMU_IMG amend -o "compat=0.10" "$TEST_IMG" _check_test_img $QEMU_IO -c "read -P 0 0 64M" "$TEST_IMG" | _filter_qemu_io +echo +echo "=== Testing progress report without snapshot ===" +echo +IMGOPTS="compat=1.1" TEST_IMG="$TEST_IMG.base" _make_test_img 4G +IMGOPTS="compat=1.1" _make_test_img -b "$TEST_IMG.base" 4G +$QEMU_IO -c "write -z 0 64k" \ + -c "write -z 1G 64k" \ + -c "write -z 2G 64k" \ + -c "write -z 3G 64k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IMG amend -p -o "compat=0.10" "$TEST_IMG" +_check_test_img + +echo +echo "=== Testing progress report with snapshot ===" +echo +IMGOPTS="compat=1.1" TEST_IMG="$TEST_IMG.base" _make_test_img 4G +IMGOPTS="compat=1.1" _make_test_img -b "$TEST_IMG.base" 4G +$QEMU_IO -c "write -z 0 64k" \ + -c "write -z 1G 64k" \ + -c "write -z 2G 64k" \ + -c "write -z 3G 64k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IMG snapshot -c foo "$TEST_IMG" +$QEMU_IMG amend -p -o "compat=0.10" "$TEST_IMG" +_check_test_img + # success, all done echo "*** done" rm -f $seq.full diff --git a/tests/qemu-iotests/061.out b/tests/qemu-iotests/061.out index e3724700b..9045544df 100644 --- a/tests/qemu-iotests/061.out +++ b/tests/qemu-iotests/061.out @@ -76,8 +76,11 @@ autoclear_features 0x0 refcount_order 4 header_length 104 -Repairing cluster 5 refcount=0 reference=1 -Repairing cluster 6 refcount=0 reference=1 +ERROR cluster 5 refcount=0 reference=1 +ERROR cluster 6 refcount=0 reference=1 +Rebuilding refcount structure +Repairing cluster 1 refcount=1 reference=0 +Repairing cluster 2 refcount=1 reference=0 magic 0x514649fb version 2 backing_file_offset 0x0 @@ -87,7 +90,7 @@ size 67108864 crypt_method 0 l1_size 1 l1_table_offset 0x30000 -refcount_table_offset 0x10000 +refcount_table_offset 0x80000 refcount_table_clusters 1 nb_snapshots 0 snapshot_offset 0x0 @@ -230,8 +233,11 @@ autoclear_features 0x0 refcount_order 4 header_length 104 -Repairing cluster 5 refcount=0 reference=1 -Repairing cluster 6 refcount=0 reference=1 +ERROR cluster 5 refcount=0 reference=1 +ERROR cluster 6 refcount=0 reference=1 +Rebuilding refcount structure +Repairing cluster 1 refcount=1 reference=0 +Repairing cluster 2 refcount=1 reference=0 magic 0x514649fb version 3 backing_file_offset 0x0 @@ -241,7 +247,7 @@ size 67108864 crypt_method 0 l1_size 1 l1_table_offset 0x30000 -refcount_table_offset 0x10000 +refcount_table_offset 0x80000 refcount_table_clusters 1 nb_snapshots 0 snapshot_offset 0x0 @@ -384,4 +390,34 @@ wrote 67108864/67108864 bytes at offset 0 No errors were found on the image. read 67108864/67108864 bytes at offset 0 64 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +=== Testing progress report without snapshot === + +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=4294967296 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4294967296 backing_file='TEST_DIR/t.IMGFMT.base' +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 65536/65536 bytes at offset 1073741824 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 65536/65536 bytes at offset 2147483648 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 65536/65536 bytes at offset 3221225472 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + (0.00/100%)
(12.50/100%)
(25.00/100%)
(37.50/100%)
(50.00/100%)
(62.50/100%)
(75.00/100%)
(87.50/100%)
(100.00/100%)
(100.00/100%) +No errors were found on the image. + +=== Testing progress report with snapshot === + +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=4294967296 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4294967296 backing_file='TEST_DIR/t.IMGFMT.base' +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 65536/65536 bytes at offset 1073741824 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 65536/65536 bytes at offset 2147483648 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 65536/65536 bytes at offset 3221225472 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + (0.00/100%)
(6.25/100%)
(12.50/100%)
(18.75/100%)
(25.00/100%)
(31.25/100%)
(37.50/100%)
(43.75/100%)
(50.00/100%)
(56.25/100%)
(62.50/100%)
(68.75/100%)
(75.00/100%)
(81.25/100%)
(87.50/100%)
(93.75/100%)
(100.00/100%)
(100.00/100%) +No errors were found on the image. *** done diff --git a/tests/qemu-iotests/065 b/tests/qemu-iotests/065 index e89b61d70..8d3a9c9af 100755 --- a/tests/qemu-iotests/065 +++ b/tests/qemu-iotests/065 @@ -94,28 +94,28 @@ class TestQCow2(TestQemuImgInfo): class TestQCow3NotLazy(TestQemuImgInfo): '''Testing a qcow2 version 3 image with lazy refcounts disabled''' img_options = 'compat=1.1,lazy_refcounts=off' - json_compare = { 'compat': '1.1', 'lazy-refcounts': False } - human_compare = [ 'compat: 1.1', 'lazy refcounts: false' ] + json_compare = { 'compat': '1.1', 'lazy-refcounts': False, 'corrupt': False } + human_compare = [ 'compat: 1.1', 'lazy refcounts: false', 'corrupt: false' ] class TestQCow3Lazy(TestQemuImgInfo): '''Testing a qcow2 version 3 image with lazy refcounts enabled''' img_options = 'compat=1.1,lazy_refcounts=on' - json_compare = { 'compat': '1.1', 'lazy-refcounts': True } - human_compare = [ 'compat: 1.1', 'lazy refcounts: true' ] + json_compare = { 'compat': '1.1', 'lazy-refcounts': True, 'corrupt': False } + human_compare = [ 'compat: 1.1', 'lazy refcounts: true', 'corrupt: false' ] class TestQCow3NotLazyQMP(TestQMP): '''Testing a qcow2 version 3 image with lazy refcounts disabled, opening with lazy refcounts enabled''' img_options = 'compat=1.1,lazy_refcounts=off' qemu_options = 'lazy-refcounts=on' - compare = { 'compat': '1.1', 'lazy-refcounts': False } + compare = { 'compat': '1.1', 'lazy-refcounts': False, 'corrupt': False } class TestQCow3LazyQMP(TestQMP): '''Testing a qcow2 version 3 image with lazy refcounts enabled, opening with lazy refcounts disabled''' img_options = 'compat=1.1,lazy_refcounts=on' qemu_options = 'lazy-refcounts=off' - compare = { 'compat': '1.1', 'lazy-refcounts': True } + compare = { 'compat': '1.1', 'lazy-refcounts': True, 'corrupt': False } TestImageInfoSpecific = None TestQemuImgInfo = None diff --git a/tests/qemu-iotests/067.out b/tests/qemu-iotests/067.out index 7e090b95a..0f72dcf63 100644 --- a/tests/qemu-iotests/067.out +++ b/tests/qemu-iotests/067.out @@ -6,7 +6,7 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,if=none,id=disk -device virtio-blk-pci,drive=disk,id=virtio0 QMP_VERSION {"return": {}} -{"return": [{"io-status": "ok", "device": "disk", "locked": false, "removable": false, "inserted": {"iops_rd": 0, "detect_zeroes": "off", "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "type": "unknown"}, {"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}]} +{"return": [{"io-status": "ok", "device": "disk", "locked": false, "removable": false, "inserted": {"iops_rd": 0, "detect_zeroes": "off", "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false, "corrupt": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "type": "unknown"}, {"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}]} {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_DELETED", "data": {"path": "/machine/peripheral/virtio0/virtio-backend"}} @@ -24,7 +24,7 @@ QMP_VERSION Testing: -drive file=TEST_DIR/t.qcow2,format=qcow2,if=none,id=disk QMP_VERSION {"return": {}} -{"return": [{"device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "detect_zeroes": "off", "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}, {"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}]} +{"return": [{"device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "detect_zeroes": "off", "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false, "corrupt": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}, {"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}]} {"return": {}} {"return": {}} {"return": {}} @@ -44,7 +44,7 @@ Testing: QMP_VERSION {"return": {}} {"return": "OK\r\n"} -{"return": [{"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "detect_zeroes": "off", "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}]} +{"return": [{"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "detect_zeroes": "off", "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false, "corrupt": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}]} {"return": {}} {"return": {}} {"return": {}} @@ -64,14 +64,14 @@ Testing: QMP_VERSION {"return": {}} {"return": {}} -{"return": [{"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "detect_zeroes": "off", "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}]} +{"return": [{"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "detect_zeroes": "off", "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false, "corrupt": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}]} {"return": {}} {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_DELETED", "data": {"path": "/machine/peripheral/virtio0/virtio-backend"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_DELETED", "data": {"device": "virtio0", "path": "/machine/peripheral/virtio0"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "RESET"} -{"return": [{"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"io-status": "ok", "device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "detect_zeroes": "off", "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}]} +{"return": [{"io-status": "ok", "device": "ide1-cd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "floppy0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"device": "sd0", "locked": false, "removable": true, "tray_open": false, "type": "unknown"}, {"io-status": "ok", "device": "disk", "locked": false, "removable": true, "inserted": {"iops_rd": 0, "detect_zeroes": "off", "image": {"virtual-size": 134217728, "filename": "TEST_DIR/t.qcow2", "cluster-size": 65536, "format": "qcow2", "actual-size": SIZE, "format-specific": {"type": "qcow2", "data": {"compat": "1.1", "lazy-refcounts": false, "corrupt": false}}, "dirty-flag": false}, "iops_wr": 0, "ro": false, "backing_file_depth": 0, "drv": "qcow2", "iops": 0, "bps_wr": 0, "encrypted": false, "bps": 0, "bps_rd": 0, "file": "TEST_DIR/t.qcow2", "encryption_key_missing": false}, "tray_open": false, "type": "unknown"}]} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN"} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "ide1-cd0", "tray-open": true}} diff --git a/tests/qemu-iotests/069 b/tests/qemu-iotests/069 index e661598c4..ce9e0541b 100755 --- a/tests/qemu-iotests/069 +++ b/tests/qemu-iotests/069 @@ -38,7 +38,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.rc . ./common.filter -_supported_fmt cow qed qcow qcow2 vmdk +_supported_fmt qed qcow qcow2 vmdk _supported_proto file _supported_os Linux _unsupported_imgopts "subformat=monolithicFlat" "subformat=twoGbMaxExtentFlat" diff --git a/tests/qemu-iotests/070 b/tests/qemu-iotests/070 index ea0dae7e9..d649ddf9b 100755 --- a/tests/qemu-iotests/070 +++ b/tests/qemu-iotests/070 @@ -77,7 +77,7 @@ _use_sample_img test-disk2vhd.vhdx.bz2 echo echo "=== Verify image created by Disk2VHD can be opened ===" -$QEMU_IMG info "$TEST_IMG" 2>&1 | _filter_testdir | _filter_qemu +_img_info # success, all done echo "*** done" diff --git a/tests/qemu-iotests/070.out b/tests/qemu-iotests/070.out index 15f1fc147..ca743831c 100644 --- a/tests/qemu-iotests/070.out +++ b/tests/qemu-iotests/070.out @@ -20,9 +20,8 @@ read 18874368/18874368 bytes at offset 0 18 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) === Verify image created by Disk2VHD can be opened === -image: TEST_DIR/test-disk2vhd.vhdx -file format: vhdx +image: TEST_DIR/test-disk2vhd.IMGFMT +file format: IMGFMT virtual size: 256M (268435456 bytes) -disk size: 260M cluster_size: 2097152 *** done diff --git a/tests/qemu-iotests/072 b/tests/qemu-iotests/072 index 58faa8b5a..e4a723d73 100755 --- a/tests/qemu-iotests/072 +++ b/tests/qemu-iotests/072 @@ -38,7 +38,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.rc . ./common.filter -_supported_fmt vpc vmdk vhdx vdi qed qcow2 qcow cow +_supported_fmt vpc vmdk vhdx vdi qed qcow2 qcow _supported_proto file _supported_os Linux diff --git a/tests/qemu-iotests/075 b/tests/qemu-iotests/075 index 40032c563..6117660c5 100755 --- a/tests/qemu-iotests/075 +++ b/tests/qemu-iotests/075 @@ -39,7 +39,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.filter _supported_fmt cloop -_supported_proto generic +_supported_proto file _supported_os Linux block_size_offset=128 diff --git a/tests/qemu-iotests/076 b/tests/qemu-iotests/076 index b614a7dd6..ed2be3581 100755 --- a/tests/qemu-iotests/076 +++ b/tests/qemu-iotests/076 @@ -39,7 +39,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.filter _supported_fmt parallels -_supported_proto generic +_supported_proto file _supported_os Linux tracks_offset=$((0x1c)) @@ -47,29 +47,34 @@ catalog_entries_offset=$((0x20)) nb_sectors_offset=$((0x24)) echo -echo "== Read from a valid (enough) image ==" -_use_sample_img fake.parallels.bz2 +echo "== Read from a valid v1 image ==" +_use_sample_img parallels-v1.bz2 { $QEMU_IO -c "read -P 0x11 0 64k" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir echo echo "== Negative catalog size ==" -_use_sample_img fake.parallels.bz2 +_use_sample_img parallels-v1.bz2 poke_file "$TEST_IMG" "$catalog_entries_offset" "\xff\xff\xff\xff" { $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir echo echo "== Overflow in catalog allocation ==" -_use_sample_img fake.parallels.bz2 +_use_sample_img parallels-v1.bz2 poke_file "$TEST_IMG" "$nb_sectors_offset" "\xff\xff\xff\xff" poke_file "$TEST_IMG" "$catalog_entries_offset" "\x01\x00\x00\x40" { $QEMU_IO -c "read 64M 64M" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir echo echo "== Zero sectors per track ==" -_use_sample_img fake.parallels.bz2 +_use_sample_img parallels-v1.bz2 poke_file "$TEST_IMG" "$tracks_offset" "\x00\x00\x00\x00" { $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir +echo +echo "== Read from a valid v2 image ==" +_use_sample_img parallels-v2.bz2 +{ $QEMU_IO -c "read -P 0x11 0 64k" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir + # success, all done echo "*** done" rm -f $seq.full diff --git a/tests/qemu-iotests/076.out b/tests/qemu-iotests/076.out index f7745d8b0..32ade0856 100644 --- a/tests/qemu-iotests/076.out +++ b/tests/qemu-iotests/076.out @@ -1,18 +1,22 @@ QA output created by 076 -== Read from a valid (enough) image == +== Read from a valid v1 image == read 65536/65536 bytes at offset 0 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) == Negative catalog size == -qemu-io: can't open device TEST_DIR/fake.parallels: Catalog too large +qemu-io: can't open device TEST_DIR/parallels-v1: Catalog too large no file open, try 'help open' == Overflow in catalog allocation == -qemu-io: can't open device TEST_DIR/fake.parallels: Catalog too large +qemu-io: can't open device TEST_DIR/parallels-v1: Catalog too large no file open, try 'help open' == Zero sectors per track == -qemu-io: can't open device TEST_DIR/fake.parallels: Invalid image: Zero sectors per track +qemu-io: can't open device TEST_DIR/parallels-v1: Invalid image: Zero sectors per track no file open, try 'help open' + +== Read from a valid v2 image == +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) *** done diff --git a/tests/qemu-iotests/078 b/tests/qemu-iotests/078 index d4d6da7b0..7be2c3f69 100755 --- a/tests/qemu-iotests/078 +++ b/tests/qemu-iotests/078 @@ -39,7 +39,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.filter _supported_fmt bochs -_supported_proto generic +_supported_proto file _supported_os Linux catalog_size_offset=$((0x48)) diff --git a/tests/qemu-iotests/079 b/tests/qemu-iotests/079 index 2142bbb37..6613cfb18 100755 --- a/tests/qemu-iotests/079 +++ b/tests/qemu-iotests/079 @@ -39,7 +39,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.filter _supported_fmt qcow2 -_supported_proto file +_supported_proto file nfs _supported_os Linux function test_qemu_img() diff --git a/tests/qemu-iotests/080 b/tests/qemu-iotests/080 index 6b3a3e77a..9de337c40 100755 --- a/tests/qemu-iotests/080 +++ b/tests/qemu-iotests/080 @@ -40,7 +40,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.filter _supported_fmt qcow2 -_supported_proto generic +_supported_proto file _supported_os Linux header_size=104 diff --git a/tests/qemu-iotests/081 b/tests/qemu-iotests/081 index 7ae4be205..ed3c29e13 100755 --- a/tests/qemu-iotests/081 +++ b/tests/qemu-iotests/081 @@ -41,7 +41,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.filter _supported_fmt raw -_supported_proto generic +_supported_proto file _supported_os Linux function do_run_qemu() diff --git a/tests/qemu-iotests/082 b/tests/qemu-iotests/082 index f6eb75f62..e64de2773 100755 --- a/tests/qemu-iotests/082 +++ b/tests/qemu-iotests/082 @@ -39,7 +39,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.filter _supported_fmt qcow2 -_supported_proto file +_supported_proto file nfs _supported_os Linux function run_qemu_img() @@ -56,7 +56,7 @@ echo === create: Options specified more than once === # Last -f should win run_qemu_img create -f foo -f $IMGFMT "$TEST_IMG" $size -run_qemu_img info "$TEST_IMG" +_img_info # Multiple -o should be merged run_qemu_img create -f $IMGFMT -o cluster_size=4k -o lazy_refcounts=on "$TEST_IMG" $size @@ -66,7 +66,7 @@ run_qemu_img info "$TEST_IMG" run_qemu_img create -f $IMGFMT -o cluster_size=4k -o lazy_refcounts=on -o cluster_size=8k "$TEST_IMG" $size run_qemu_img info "$TEST_IMG" run_qemu_img create -f $IMGFMT -o cluster_size=4k,cluster_size=8k "$TEST_IMG" $size -run_qemu_img info "$TEST_IMG" +_img_info echo echo === create: help for -o === @@ -106,11 +106,11 @@ run_qemu_img create -f $IMGFMT "$TEST_IMG" $size # Last -f should win run_qemu_img convert -f foo -f $IMGFMT "$TEST_IMG" "$TEST_IMG".base -run_qemu_img info "$TEST_IMG".base +TEST_IMG="${TEST_IMG}.base" _img_info # Last -O should win run_qemu_img convert -O foo -O $IMGFMT "$TEST_IMG" "$TEST_IMG".base -run_qemu_img info "$TEST_IMG".base +TEST_IMG="${TEST_IMG}.base" _img_info # Multiple -o should be merged run_qemu_img convert -O $IMGFMT -o cluster_size=4k -o lazy_refcounts=on "$TEST_IMG" "$TEST_IMG".base @@ -120,7 +120,7 @@ run_qemu_img info "$TEST_IMG".base run_qemu_img convert -O $IMGFMT -o cluster_size=4k -o lazy_refcounts=on -o cluster_size=8k "$TEST_IMG" "$TEST_IMG".base run_qemu_img info "$TEST_IMG".base run_qemu_img convert -O $IMGFMT -o cluster_size=4k,cluster_size=8k "$TEST_IMG" "$TEST_IMG".base -run_qemu_img info "$TEST_IMG".base +TEST_IMG="${TEST_IMG}.base" _img_info echo echo === convert: help for -o === @@ -167,7 +167,7 @@ run_qemu_img info "$TEST_IMG" run_qemu_img amend -f $IMGFMT -o size=8M -o lazy_refcounts=on -o size=132M "$TEST_IMG" run_qemu_img info "$TEST_IMG" run_qemu_img amend -f $IMGFMT -o size=4M,size=148M "$TEST_IMG" -run_qemu_img info "$TEST_IMG" +_img_info echo echo === amend: help for -o === diff --git a/tests/qemu-iotests/082.out b/tests/qemu-iotests/082.out index 413e7ef39..0a3ab5ac9 100644 --- a/tests/qemu-iotests/082.out +++ b/tests/qemu-iotests/082.out @@ -4,16 +4,10 @@ QA output created by 082 Testing: create -f foo -f qcow2 TEST_DIR/t.qcow2 128M Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=134217728 encryption=off cluster_size=65536 lazy_refcounts=off - -Testing: info TEST_DIR/t.qcow2 -image: TEST_DIR/t.qcow2 -file format: qcow2 +image: TEST_DIR/t.IMGFMT +file format: IMGFMT virtual size: 128M (134217728 bytes) -disk size: 196K cluster_size: 65536 -Format specific information: - compat: 1.1 - lazy refcounts: false Testing: create -f qcow2 -o cluster_size=4k -o lazy_refcounts=on TEST_DIR/t.qcow2 128M Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=134217728 encryption=off cluster_size=4096 lazy_refcounts=on @@ -27,6 +21,7 @@ cluster_size: 4096 Format specific information: compat: 1.1 lazy refcounts: true + corrupt: false Testing: create -f qcow2 -o cluster_size=4k -o lazy_refcounts=on -o cluster_size=8k TEST_DIR/t.qcow2 128M Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=134217728 encryption=off cluster_size=8192 lazy_refcounts=on @@ -40,19 +35,14 @@ cluster_size: 8192 Format specific information: compat: 1.1 lazy refcounts: true + corrupt: false Testing: create -f qcow2 -o cluster_size=4k,cluster_size=8k TEST_DIR/t.qcow2 128M Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=134217728 encryption=off cluster_size=8192 lazy_refcounts=off - -Testing: info TEST_DIR/t.qcow2 -image: TEST_DIR/t.qcow2 -file format: qcow2 +image: TEST_DIR/t.IMGFMT +file format: IMGFMT virtual size: 128M (134217728 bytes) -disk size: 28K cluster_size: 8192 -Format specific information: - compat: 1.1 - lazy refcounts: false === create: help for -o === @@ -64,7 +54,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -76,7 +66,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -88,7 +78,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -100,7 +90,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -112,7 +102,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -124,7 +114,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -136,7 +126,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -148,7 +138,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -175,7 +165,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates Testing: create -o help @@ -188,24 +178,15 @@ Testing: create -f qcow2 TEST_DIR/t.qcow2 128M Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=134217728 encryption=off cluster_size=65536 lazy_refcounts=off Testing: convert -f foo -f qcow2 TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base - -Testing: info TEST_DIR/t.qcow2.base -image: TEST_DIR/t.qcow2.base +image: TEST_DIR/t.IMGFMT.base file format: raw virtual size: 128M (134217728 bytes) -disk size: 0 Testing: convert -O foo -O qcow2 TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base - -Testing: info TEST_DIR/t.qcow2.base -image: TEST_DIR/t.qcow2.base -file format: qcow2 +image: TEST_DIR/t.IMGFMT.base +file format: IMGFMT virtual size: 128M (134217728 bytes) -disk size: 196K cluster_size: 65536 -Format specific information: - compat: 1.1 - lazy refcounts: false Testing: convert -O qcow2 -o cluster_size=4k -o lazy_refcounts=on TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base @@ -218,6 +199,7 @@ cluster_size: 4096 Format specific information: compat: 1.1 lazy refcounts: true + corrupt: false Testing: convert -O qcow2 -o cluster_size=4k -o lazy_refcounts=on -o cluster_size=8k TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base @@ -230,18 +212,13 @@ cluster_size: 8192 Format specific information: compat: 1.1 lazy refcounts: true + corrupt: false Testing: convert -O qcow2 -o cluster_size=4k,cluster_size=8k TEST_DIR/t.qcow2 TEST_DIR/t.qcow2.base - -Testing: info TEST_DIR/t.qcow2.base -image: TEST_DIR/t.qcow2.base -file format: qcow2 +image: TEST_DIR/t.IMGFMT.base +file format: IMGFMT virtual size: 128M (134217728 bytes) -disk size: 28K cluster_size: 8192 -Format specific information: - compat: 1.1 - lazy refcounts: false === convert: help for -o === @@ -253,7 +230,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -265,7 +242,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -277,7 +254,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -289,7 +266,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -301,7 +278,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -313,7 +290,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -325,7 +302,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -337,7 +314,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -364,7 +341,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates Testing: convert -o help @@ -384,6 +361,7 @@ cluster_size: 65536 Format specific information: compat: 1.1 lazy refcounts: true + corrupt: false Testing: amend -f qcow2 -o size=130M -o lazy_refcounts=off TEST_DIR/t.qcow2 @@ -396,6 +374,7 @@ cluster_size: 65536 Format specific information: compat: 1.1 lazy refcounts: false + corrupt: false Testing: amend -f qcow2 -o size=8M -o lazy_refcounts=on -o size=132M TEST_DIR/t.qcow2 @@ -408,18 +387,13 @@ cluster_size: 65536 Format specific information: compat: 1.1 lazy refcounts: true + corrupt: false Testing: amend -f qcow2 -o size=4M,size=148M TEST_DIR/t.qcow2 - -Testing: info TEST_DIR/t.qcow2 -image: TEST_DIR/t.qcow2 -file format: qcow2 +image: TEST_DIR/t.IMGFMT +file format: IMGFMT virtual size: 148M (155189248 bytes) -disk size: 196K cluster_size: 65536 -Format specific information: - compat: 1.1 - lazy refcounts: true === amend: help for -o === @@ -431,7 +405,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -443,7 +417,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -455,7 +429,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -467,7 +441,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -479,7 +453,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -491,7 +465,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -503,7 +477,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -515,7 +489,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates nocow Turn off copy-on-write (valid only on btrfs) @@ -544,7 +518,7 @@ backing_file File name of a base image backing_fmt Image format of the base image encryption Encrypt the image cluster_size qcow2 cluster size -preallocation Preallocation mode (allowed values: off, metadata) +preallocation Preallocation mode (allowed values: off, metadata, falloc, full) lazy_refcounts Postpone refcount updates Testing: convert -o help diff --git a/tests/qemu-iotests/084 b/tests/qemu-iotests/084 index cb4d7b729..733018d4a 100755 --- a/tests/qemu-iotests/084 +++ b/tests/qemu-iotests/084 @@ -1,6 +1,7 @@ #!/bin/bash # -# Test case for VDI header corruption; image too large, and too many blocks +# Test case for VDI header corruption; image too large, and too many blocks. +# Also simple test for creating dynamic and static VDI images. # # Copyright (C) 2013 Red Hat, Inc. # @@ -40,29 +41,40 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 # This tests vdi-specific header fields _supported_fmt vdi -_supported_proto generic +_supported_proto file _supported_os Linux +size=64M ds_offset=368 # disk image size field offset bs_offset=376 # block size field offset bii_offset=384 # block in image field offset echo +echo "=== Statically allocated image creation ===" +echo +_make_test_img $size -o static +_img_info +stat -c"disk image file size in bytes: %s" "${TEST_IMG}" +_cleanup_test_img + +echo echo "=== Testing image size bounds ===" echo -_make_test_img 64M +_make_test_img $size +_img_info +stat -c"disk image file size in bytes: %s" "${TEST_IMG}" # check for image size too large # poke max image size, and appropriate blocks_in_image value -echo "Test 1: Maximum size (1024 TB):" -poke_file "$TEST_IMG" "$ds_offset" "\x00\x00\xf0\xff\xff\xff\x03\x00" -poke_file "$TEST_IMG" "$bii_offset" "\xff\xff\xff\x3f" +echo "Test 1: Maximum size (512 TB - 128 MB):" +poke_file "$TEST_IMG" "$ds_offset" "\x00\x00\x00\xf8\xff\xff\x01\x00" +poke_file "$TEST_IMG" "$bii_offset" "\x80\xff\xff\x1f" _img_info echo -echo "Test 2: Size too large (1024TB + 1)" +echo "Test 2: Size too large (512 TB - 128 MB + 64 kB)" # This should be too large (-EINVAL): -poke_file "$TEST_IMG" "$ds_offset" "\x00\x00\xf1\xff\xff\xff\x03\x00" +poke_file "$TEST_IMG" "$ds_offset" "\x00\x00\x01\xf8\xff\xff\x01\x00" _img_info echo @@ -77,9 +89,9 @@ _img_info echo echo "Test 4: Size valid (64M), but Blocks In Image exceeds max allowed" -# Now check the bounds of blocks_in_image - 0x3fffffff should be the max +# Now check the bounds of blocks_in_image - 0x1fffff80 should be the max # value here, and we should get -ENOTSUP -poke_file "$TEST_IMG" "$bii_offset" "\x00\x00\x00\x40" +poke_file "$TEST_IMG" "$bii_offset" "\x81\xff\xff\x1f" _img_info # Finally, 1MB is the only block size supported. Verify that diff --git a/tests/qemu-iotests/084.out b/tests/qemu-iotests/084.out index c7120d9b0..5ece8299c 100644 --- a/tests/qemu-iotests/084.out +++ b/tests/qemu-iotests/084.out @@ -1,19 +1,36 @@ QA output created by 084 +=== Statically allocated image creation === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +image: TEST_DIR/t.IMGFMT +file format: IMGFMT +virtual size: 64M (67108864 bytes) +cluster_size: 1048576 +disk image file size in bytes: 67109888 + === Testing image size bounds === Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 -Test 1: Maximum size (1024 TB): -qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Could not open 'TEST_DIR/t.IMGFMT': Invalid argument +image: TEST_DIR/t.IMGFMT +file format: IMGFMT +virtual size: 64M (67108864 bytes) +cluster_size: 1048576 +disk image file size in bytes: 1024 +Test 1: Maximum size (512 TB - 128 MB): +image: TEST_DIR/t.IMGFMT +file format: IMGFMT +virtual size: 512T (562949819203584 bytes) +cluster_size: 1048576 -Test 2: Size too large (1024TB + 1) -qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Unsupported VDI image size (size is 0x3fffffff10000, max supported is 0x3fffffff00000) +Test 2: Size too large (512 TB - 128 MB + 64 kB) +qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Unsupported VDI image size (size is 0x1fffff8010000, max supported is 0x1fffff8000000) Test 3: Size valid (64M), but Blocks In Image too small (63) qemu-img: Could not open 'TEST_DIR/t.IMGFMT': unsupported VDI image (disk size 67108864, image bitmap has room for 66060288) Test 4: Size valid (64M), but Blocks In Image exceeds max allowed -qemu-img: Could not open 'TEST_DIR/t.IMGFMT': unsupported VDI image (too many blocks 1073741824, max is 1073741823) +qemu-img: Could not open 'TEST_DIR/t.IMGFMT': unsupported VDI image (too many blocks 536870785, max is 536870784) Test 5: Valid Image: 64MB, Blocks In Image 64, Block Size 1MB image: TEST_DIR/t.IMGFMT diff --git a/tests/qemu-iotests/086 b/tests/qemu-iotests/086 index d9a80cf86..234eb9a91 100755 --- a/tests/qemu-iotests/086 +++ b/tests/qemu-iotests/086 @@ -39,7 +39,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.filter _supported_fmt qcow2 -_supported_proto file +_supported_proto file nfs _supported_os Linux function run_qemu_img() diff --git a/tests/qemu-iotests/087 b/tests/qemu-iotests/087 index 82c56b139..d7454d13d 100755 --- a/tests/qemu-iotests/087 +++ b/tests/qemu-iotests/087 @@ -218,6 +218,23 @@ run_qemu <<EOF { "execute": "quit" } EOF +echo +echo === Missing driver === +echo + +_make_test_img -o encryption=on $size +run_qemu -S <<EOF +{ "execute": "qmp_capabilities" } +{ "execute": "blockdev-add", + "arguments": { + "options": { + "id": "disk" + } + } + } +{ "execute": "quit" } +EOF + # success, all done echo "*** done" rm -f $seq.full diff --git a/tests/qemu-iotests/087.out b/tests/qemu-iotests/087.out index 7fbee3ff5..e8795b3a1 100644 --- a/tests/qemu-iotests/087.out +++ b/tests/qemu-iotests/087.out @@ -20,7 +20,7 @@ QMP_VERSION {"return": {}} {"return": {}} {"error": {"class": "GenericError", "desc": "Device with id 'disk' already exists"}} -{"error": {"class": "GenericError", "desc": "Device with node-name 'test-node' already exists"}} +{"error": {"class": "GenericError", "desc": "Device name 'test-node' conflicts with an existing node name"}} main-loop: WARNING: I/O thread spun for 1000 iterations {"error": {"class": "GenericError", "desc": "could not open disk image disk2: node-name=disk is conflicting with a device id"}} {"error": {"class": "GenericError", "desc": "could not open disk image disk2: Duplicate node name"}} @@ -64,4 +64,17 @@ QMP_VERSION {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "ide1-cd0", "tray-open": true}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "floppy0", "tray-open": true}} + +=== Missing driver === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 encryption=on +Testing: -S +QMP_VERSION +{"return": {}} +{"error": {"class": "GenericError", "desc": "Invalid parameter type for 'driver', expected: string"}} +{"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN"} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "ide1-cd0", "tray-open": true}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "floppy0", "tray-open": true}} + *** done diff --git a/tests/qemu-iotests/088 b/tests/qemu-iotests/088 index c09adf802..f9c312918 100755 --- a/tests/qemu-iotests/088 +++ b/tests/qemu-iotests/088 @@ -40,7 +40,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.filter _supported_fmt vpc -_supported_proto generic +_supported_proto file _supported_os Linux offset_block_size=$((512 + 32)) diff --git a/tests/qemu-iotests/089.out b/tests/qemu-iotests/089.out index 4ca2f88e6..b2b039081 100644 --- a/tests/qemu-iotests/089.out +++ b/tests/qemu-iotests/089.out @@ -41,10 +41,12 @@ vm state offset: 512 MiB Format specific information: compat: 1.1 lazy refcounts: false + corrupt: false format name: IMGFMT cluster size: 64 KiB vm state offset: 512 MiB Format specific information: compat: 1.1 lazy refcounts: false + corrupt: false *** done diff --git a/tests/qemu-iotests/090 b/tests/qemu-iotests/090 index 8d032f811..70b5a6fd7 100755 --- a/tests/qemu-iotests/090 +++ b/tests/qemu-iotests/090 @@ -39,7 +39,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.filter _supported_fmt qcow2 -_supported_proto file +_supported_proto file nfs _supported_os Linux IMG_SIZE=128K diff --git a/tests/qemu-iotests/092 b/tests/qemu-iotests/092 index a8c0c9ca2..52c529bae 100755 --- a/tests/qemu-iotests/092 +++ b/tests/qemu-iotests/092 @@ -40,7 +40,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 . ./common.filter _supported_fmt qcow -_supported_proto generic +_supported_proto file _supported_os Linux offset_backing_file_offset=8 diff --git a/tests/qemu-iotests/095 b/tests/qemu-iotests/095 index acc7dbf18..6630181a7 100755 --- a/tests/qemu-iotests/095 +++ b/tests/qemu-iotests/095 @@ -60,7 +60,7 @@ _make_test_img -b "${TEST_IMG}.snp1" $size_larger echo echo "=== Base image info before commit and resize ===" -$QEMU_IMG info "${TEST_IMG}.base" | _filter_testdir +TEST_IMG="${TEST_IMG}.base" _img_info echo echo === Running QEMU Live Commit Test === @@ -78,7 +78,7 @@ _send_qemu_cmd $h "{ 'execute': 'block-commit', echo echo "=== Base image info after commit and resize ===" -$QEMU_IMG info "${TEST_IMG}.base" | _filter_testdir +TEST_IMG="${TEST_IMG}.base" _img_info # success, all done echo "*** done" diff --git a/tests/qemu-iotests/095.out b/tests/qemu-iotests/095.out index 5864ddac2..cc86efacc 100644 --- a/tests/qemu-iotests/095.out +++ b/tests/qemu-iotests/095.out @@ -4,14 +4,10 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=104857600 backing_file='TEST_DIR Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=104857600 backing_file='TEST_DIR/t.IMGFMT.snp1' === Base image info before commit and resize === -image: TEST_DIR/t.qcow2.base -file format: qcow2 +image: TEST_DIR/t.IMGFMT.base +file format: IMGFMT virtual size: 5.0M (5242880 bytes) -disk size: 196K cluster_size: 65536 -Format specific information: - compat: 1.1 - lazy refcounts: false === Running QEMU Live Commit Test === @@ -20,12 +16,8 @@ Format specific information: {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "test", "len": 104857600, "offset": 104857600, "speed": 0, "type": "commit"}} === Base image info after commit and resize === -image: TEST_DIR/t.qcow2.base -file format: qcow2 +image: TEST_DIR/t.IMGFMT.base +file format: IMGFMT virtual size: 100M (104857600 bytes) -disk size: 196K cluster_size: 65536 -Format specific information: - compat: 1.1 - lazy refcounts: false *** done diff --git a/tests/qemu-iotests/097 b/tests/qemu-iotests/097 new file mode 100755 index 000000000..c7a613b7e --- /dev/null +++ b/tests/qemu-iotests/097 @@ -0,0 +1,122 @@ +#!/bin/bash +# +# Commit changes into backing chains and empty the top image if the +# backing image is not explicitly specified +# +# Copyright (C) 2014 Red Hat, Inc. +# +# 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/>. +# + +# creator +owner=mreitz@redhat.com + +seq="$(basename $0)" +echo "QA output created by $seq" + +here="$PWD" +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img + _rm_test_img "$TEST_IMG.itmd" +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter +. ./common.pattern + +# Any format supporting backing files and bdrv_make_empty +_supported_fmt qcow qcow2 +_supported_proto file +_supported_os Linux + + +# Four passes: +# 0: Two-layer backing chain, commit to upper backing file (implicitly) +# (in this case, the top image will be emptied) +# 1: Two-layer backing chain, commit to upper backing file (explicitly) +# (in this case, the top image will implicitly stay unchanged) +# 2: Two-layer backing chain, commit to upper backing file (implicitly with -d) +# (in this case, the top image will explicitly stay unchanged) +# 3: Two-layer backing chain, commit to lower backing file +# (in this case, the top image will implicitly stay unchanged) +# +# 020 already tests committing, so this only tests whether image chains are +# working properly and that all images above the base are emptied; therefore, +# no complicated patterns are necessary +for i in 0 1 2 3; do + +echo +echo "=== Test pass $i ===" +echo + +TEST_IMG="$TEST_IMG.base" _make_test_img 64M +TEST_IMG="$TEST_IMG.itmd" _make_test_img -b "$TEST_IMG.base" 64M +_make_test_img -b "$TEST_IMG.itmd" 64M + +$QEMU_IO -c 'write -P 1 0 192k' "$TEST_IMG.base" | _filter_qemu_io +$QEMU_IO -c 'write -P 2 64k 128k' "$TEST_IMG.itmd" | _filter_qemu_io +$QEMU_IO -c 'write -P 3 128k 64k' "$TEST_IMG" | _filter_qemu_io + +if [ $i -lt 3 ]; then + if [ $i == 0 ]; then + # -b "$TEST_IMG.itmd" should be the default (that is, committing to the + # first backing file in the chain) + $QEMU_IMG commit "$TEST_IMG" + elif [ $i == 1 ]; then + # explicitly specify the commit target (this should imply -d) + $QEMU_IMG commit -b "$TEST_IMG.itmd" "$TEST_IMG" + else + # do not explicitly specify the commit target, but use -d to leave the + # top image unchanged + $QEMU_IMG commit -d "$TEST_IMG" + fi + + # Bottom should be unchanged + $QEMU_IO -c 'read -P 1 0 192k' "$TEST_IMG.base" | _filter_qemu_io + + # Intermediate should contain changes from top + $QEMU_IO -c 'read -P 1 0 64k' "$TEST_IMG.itmd" | _filter_qemu_io + $QEMU_IO -c 'read -P 2 64k 64k' "$TEST_IMG.itmd" | _filter_qemu_io + $QEMU_IO -c 'read -P 3 128k 64k' "$TEST_IMG.itmd" | _filter_qemu_io + + # And in pass 0, the top image should be empty, whereas in both other passes + # it should be unchanged (which is both checked by qemu-img map) +else + $QEMU_IMG commit -b "$TEST_IMG.base" "$TEST_IMG" + + # Bottom should contain all changes + $QEMU_IO -c 'read -P 1 0 64k' "$TEST_IMG.base" | _filter_qemu_io + $QEMU_IO -c 'read -P 2 64k 64k' "$TEST_IMG.base" | _filter_qemu_io + $QEMU_IO -c 'read -P 3 128k 64k' "$TEST_IMG.base" | _filter_qemu_io + + # Both top and intermediate should be unchanged +fi + +$QEMU_IMG map "$TEST_IMG.base" | _filter_qemu_img_map +$QEMU_IMG map "$TEST_IMG.itmd" | _filter_qemu_img_map +$QEMU_IMG map "$TEST_IMG" | _filter_qemu_img_map + +done + + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/097.out b/tests/qemu-iotests/097.out new file mode 100644 index 000000000..1cb7764fe --- /dev/null +++ b/tests/qemu-iotests/097.out @@ -0,0 +1,119 @@ +QA output created by 097 + +=== Test pass 0 === + +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864 +Formatting 'TEST_DIR/t.IMGFMT.itmd', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base' +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.itmd' +wrote 196608/196608 bytes at offset 0 +192 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 131072/131072 bytes at offset 65536 +128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 65536/65536 bytes at offset 131072 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Image committed. +read 196608/196608 bytes at offset 0 +192 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 65536 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 131072 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Offset Length File +0 0x30000 TEST_DIR/t.IMGFMT.base +Offset Length File +0 0x10000 TEST_DIR/t.IMGFMT.base +0x10000 0x20000 TEST_DIR/t.IMGFMT.itmd +Offset Length File +0 0x10000 TEST_DIR/t.IMGFMT.base +0x10000 0x20000 TEST_DIR/t.IMGFMT.itmd + +=== Test pass 1 === + +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864 +Formatting 'TEST_DIR/t.IMGFMT.itmd', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base' +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.itmd' +wrote 196608/196608 bytes at offset 0 +192 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 131072/131072 bytes at offset 65536 +128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 65536/65536 bytes at offset 131072 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Image committed. +read 196608/196608 bytes at offset 0 +192 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 65536 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 131072 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Offset Length File +0 0x30000 TEST_DIR/t.IMGFMT.base +Offset Length File +0 0x10000 TEST_DIR/t.IMGFMT.base +0x10000 0x20000 TEST_DIR/t.IMGFMT.itmd +Offset Length File +0 0x10000 TEST_DIR/t.IMGFMT.base +0x10000 0x10000 TEST_DIR/t.IMGFMT.itmd +0x20000 0x10000 TEST_DIR/t.IMGFMT + +=== Test pass 2 === + +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864 +Formatting 'TEST_DIR/t.IMGFMT.itmd', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base' +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.itmd' +wrote 196608/196608 bytes at offset 0 +192 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 131072/131072 bytes at offset 65536 +128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 65536/65536 bytes at offset 131072 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Image committed. +read 196608/196608 bytes at offset 0 +192 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 65536 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 131072 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Offset Length File +0 0x30000 TEST_DIR/t.IMGFMT.base +Offset Length File +0 0x10000 TEST_DIR/t.IMGFMT.base +0x10000 0x20000 TEST_DIR/t.IMGFMT.itmd +Offset Length File +0 0x10000 TEST_DIR/t.IMGFMT.base +0x10000 0x10000 TEST_DIR/t.IMGFMT.itmd +0x20000 0x10000 TEST_DIR/t.IMGFMT + +=== Test pass 3 === + +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864 +Formatting 'TEST_DIR/t.IMGFMT.itmd', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base' +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.itmd' +wrote 196608/196608 bytes at offset 0 +192 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 131072/131072 bytes at offset 65536 +128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 65536/65536 bytes at offset 131072 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Image committed. +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 65536 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 131072 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Offset Length File +0 0x30000 TEST_DIR/t.IMGFMT.base +Offset Length File +0 0x10000 TEST_DIR/t.IMGFMT.base +0x10000 0x20000 TEST_DIR/t.IMGFMT.itmd +Offset Length File +0 0x10000 TEST_DIR/t.IMGFMT.base +0x10000 0x10000 TEST_DIR/t.IMGFMT.itmd +0x20000 0x10000 TEST_DIR/t.IMGFMT +*** done diff --git a/tests/qemu-iotests/098 b/tests/qemu-iotests/098 new file mode 100755 index 000000000..e2230ad60 --- /dev/null +++ b/tests/qemu-iotests/098 @@ -0,0 +1,82 @@ +#!/bin/bash +# +# Test qcow2's bdrv_make_empty for images without internal snapshots +# +# Copyright (C) 2014 Red Hat, Inc. +# +# 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/>. +# + +# creator +owner=mreitz@redhat.com + +seq="$(basename $0)" +echo "QA output created by $seq" + +here="$PWD" +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img + rm -f "$TEST_DIR/blkdebug.conf" +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter +. ./common.pattern + +_supported_fmt qcow2 +_supported_proto file +_supported_os Linux + +IMGOPTS="compat=1.1" + +for event in l1_update empty_image_prepare reftable_update refblock_alloc; do + +echo +echo "=== $event ===" +echo + +TEST_IMG="$TEST_IMG.base" _make_test_img 64M +_make_test_img -b "$TEST_IMG.base" 64M + +# Some data that can be leaked when emptying the top image +$QEMU_IO -c 'write 0 64k' "$TEST_IMG" | _filter_qemu_io + +cat > "$TEST_DIR/blkdebug.conf" <<EOF +[inject-error] +event = "$event" +EOF + +$QEMU_IMG commit "blkdebug:$TEST_DIR/blkdebug.conf:$TEST_IMG" 2>&1 \ + | _filter_testdir | _filter_imgfmt + +# There may be errors, but they should be fixed by opening the image +$QEMU_IO -c close "$TEST_IMG" + +_check_test_img + +done + + +rm -f "$TEST_DIR/blkdebug.conf" + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/098.out b/tests/qemu-iotests/098.out new file mode 100644 index 000000000..586dfc809 --- /dev/null +++ b/tests/qemu-iotests/098.out @@ -0,0 +1,52 @@ +QA output created by 098 + +=== l1_update === + +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base' +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-img: Could not empty blkdebug:TEST_DIR/blkdebug.conf:TEST_DIR/t.IMGFMT: Input/output error +No errors were found on the image. + +=== empty_image_prepare === + +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base' +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-img: Could not empty blkdebug:TEST_DIR/blkdebug.conf:TEST_DIR/t.IMGFMT: Input/output error +Leaked cluster 4 refcount=1 reference=0 +Leaked cluster 5 refcount=1 reference=0 +Repairing cluster 4 refcount=1 reference=0 +Repairing cluster 5 refcount=1 reference=0 +No errors were found on the image. + +=== reftable_update === + +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base' +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-img: Could not empty blkdebug:TEST_DIR/blkdebug.conf:TEST_DIR/t.IMGFMT: Input/output error +ERROR cluster 0 refcount=0 reference=1 +ERROR cluster 1 refcount=0 reference=1 +ERROR cluster 3 refcount=0 reference=1 +Rebuilding refcount structure +Repairing cluster 1 refcount=1 reference=0 +No errors were found on the image. + +=== refblock_alloc === + +Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='TEST_DIR/t.IMGFMT.base' +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +qemu-img: Could not empty blkdebug:TEST_DIR/blkdebug.conf:TEST_DIR/t.IMGFMT: Input/output error +ERROR cluster 0 refcount=0 reference=1 +ERROR cluster 1 refcount=0 reference=1 +ERROR cluster 3 refcount=0 reference=1 +Rebuilding refcount structure +Repairing cluster 1 refcount=1 reference=0 +No errors were found on the image. +*** done diff --git a/tests/qemu-iotests/099 b/tests/qemu-iotests/099 new file mode 100755 index 000000000..ffc7ea795 --- /dev/null +++ b/tests/qemu-iotests/099 @@ -0,0 +1,116 @@ +#!/bin/bash +# +# Test valid filenames for blkdebug and blkverify representatively for +# other protocols (such as NBD) when queried +# +# Copyright (C) 2014 Red Hat, Inc. +# +# 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/>. +# + +# creator +owner=mreitz@redhat.com + +seq="$(basename $0)" +echo "QA output created by $seq" + +here="$PWD" +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +# Basically all formats, but "raw" has issues with _filter_imgfmt regarding the +# raw comparison image for blkverify; also, all images have to support creation +_supported_fmt qcow qcow2 qed vdi vhdx vmdk vpc +_supported_proto file +_supported_os Linux + + +function do_run_qemu() +{ + $QEMU -nographic -qmp stdio -serial none "$@" +} + +function run_qemu() +{ + # Get the "file": "foo" entry ($foo may only contain escaped double quotes, + # which is how we can extract it) + do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_imgfmt | _filter_qmp \ + | grep "drv0" \ + | sed -e 's/^.*"file": "\(\(\\"\|[^"]\)*\)".*$/\1/' -e 's/\\"/"/g' +} + +function test_qemu() +{ + run_qemu -drive "if=none,id=drv0,$1" <<EOF + { 'execute': 'qmp_capabilities' } + { 'execute': 'query-block' } + { 'execute': 'quit' } +EOF +} + + + +IMG_SIZE=128K + +_make_test_img $IMG_SIZE +$QEMU_IMG create -f raw "$TEST_IMG.compare" $IMG_SIZE \ + | _filter_testdir | _filter_imgfmt + +echo +echo '=== Testing simple filename for blkverify ===' +echo + +# This should return simply the filename itself +test_qemu "file=blkverify:$TEST_IMG.compare:$TEST_IMG" + +echo +echo '=== Testing filename reconstruction for blkverify ===' +echo + +# This should return the same filename as above +test_qemu "file.driver=blkverify,file.raw.filename=$TEST_IMG.compare,file.test.file.filename=$TEST_IMG" + +echo +echo '=== Testing JSON filename for blkdebug ===' +echo + +# blkdebug cannot create a configuration file, therefore it is unable to +# generate a plain filename here; thus this should return a JSON filename +test_qemu "file.driver=blkdebug,file.image.filename=$TEST_IMG,file.inject-error.0.event=l1_update" + +echo +echo '=== Testing indirectly enforced JSON filename ===' +echo + +# Because blkdebug cannot return a plain filename, blkverify is forced to +# generate a JSON object here as well +test_qemu "file.driver=blkverify,file.raw.filename=$TEST_IMG.compare,file.test.file.driver=blkdebug,file.test.file.image.filename=$TEST_IMG,file.test.file.inject-error.0.event=l1_update" + + +rm -f "$TEST_IMG.compare" + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/099.out b/tests/qemu-iotests/099.out new file mode 100644 index 000000000..55be4d4c5 --- /dev/null +++ b/tests/qemu-iotests/099.out @@ -0,0 +1,20 @@ +QA output created by 099 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=131072 +Formatting 'TEST_DIR/t.IMGFMT.compare', fmt=raw size=131072 + +=== Testing simple filename for blkverify === + +blkverify:TEST_DIR/t.IMGFMT.compare:TEST_DIR/t.IMGFMT + +=== Testing filename reconstruction for blkverify === + +blkverify:TEST_DIR/t.IMGFMT.compare:TEST_DIR/t.IMGFMT + +=== Testing JSON filename for blkdebug === + +json:{"driver": "IMGFMT", "file": {"inject-error": [{"immediately": false, "once": false, "state": 0, "sector": -1, "event": "l1_update", "errno": 5}], "image": {"driver": "file", "filename": "TEST_DIR/t.IMGFMT"}, "driver": "blkdebug"}} + +=== Testing indirectly enforced JSON filename === + +json:{"driver": "raw", "file": {"test": {"driver": "IMGFMT", "file": {"inject-error": [{"immediately": false, "once": false, "state": 0, "sector": -1, "event": "l1_update", "errno": 5}], "image": {"driver": "file", "filename": "TEST_DIR/t.IMGFMT"}, "driver": "blkdebug"}}, "driver": "blkverify", "raw": {"driver": "file", "filename": "TEST_DIR/t.IMGFMT.compare"}}} +*** done diff --git a/tests/qemu-iotests/100 b/tests/qemu-iotests/100 new file mode 100755 index 000000000..9124aba76 --- /dev/null +++ b/tests/qemu-iotests/100 @@ -0,0 +1,134 @@ +#!/bin/bash +# +# Test simple read/write using plain bdrv_read/bdrv_write +# +# Copyright (C) 2014 Red Hat, Inc. +# +# 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/>. +# + +# creator +owner=stefanha@redhat.com + +seq=`basename $0` +echo "QA output created by $seq" + +here=`pwd` +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt generic +_supported_proto generic +_supported_os Linux + + +size=128M + +echo +echo "== Single request ==" +_make_test_img $size +$QEMU_IO -c "multiwrite 0 4k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "== verify pattern ==" +$QEMU_IO -c "read -P 0xcd 0 4k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IO -c "read -P 0 4k 4k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "== Sequential requests ==" +_make_test_img $size +$QEMU_IO -c "multiwrite 0 4k ; 4k 4k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "== verify pattern ==" +$QEMU_IO -c "read -P 0xcd 0 4k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IO -c "read -P 0xce 4k 4k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IO -c "read -P 0 8k 4k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "== Superset overlapping requests ==" +_make_test_img $size +$QEMU_IO -c "multiwrite 0 4k ; 1k 2k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "== verify pattern ==" +# Order of overlapping in-flight requests is not guaranteed so we cannot verify +# [1k, 3k) since it could have either pattern 0xcd or 0xce. +$QEMU_IO -c "read -P 0xcd 0 1k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IO -c "read -P 0xcd 3k 1k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IO -c "read -P 0 4k 4k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "== Subset overlapping requests ==" +_make_test_img $size +$QEMU_IO -c "multiwrite 1k 2k ; 0k 4k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "== verify pattern ==" +# Order of overlapping in-flight requests is not guaranteed so we cannot verify +# [1k, 3k) since it could have either pattern 0xcd or 0xce. +$QEMU_IO -c "read -P 0xce 0 1k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IO -c "read -P 0xce 3k 1k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IO -c "read -P 0 4k 4k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "== Head overlapping requests ==" +_make_test_img $size +$QEMU_IO -c "multiwrite 0k 2k ; 0k 4k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "== verify pattern ==" +# Order of overlapping in-flight requests is not guaranteed so we cannot verify +# [0k, 2k) since it could have either pattern 0xcd or 0xce. +$QEMU_IO -c "read -P 0xce 2k 2k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IO -c "read -P 0 4k 4k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "== Tail overlapping requests ==" +_make_test_img $size +$QEMU_IO -c "multiwrite 2k 2k ; 0k 4k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "== verify pattern ==" +# Order of overlapping in-flight requests is not guaranteed so we cannot verify +# [2k, 4k) since it could have either pattern 0xcd or 0xce. +$QEMU_IO -c "read -P 0xce 0k 2k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IO -c "read -P 0 4k 4k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "== Disjoint requests ==" +_make_test_img $size +$QEMU_IO -c "multiwrite 0 4k ; 64k 4k" "$TEST_IMG" | _filter_qemu_io + +echo +echo "== verify pattern ==" +$QEMU_IO -c "read -P 0xcd 0 4k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IO -c "read -P 0 4k 60k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IO -c "read -P 0xce 64k 4k" "$TEST_IMG" | _filter_qemu_io +$QEMU_IO -c "read -P 0 68k 4k" "$TEST_IMG" | _filter_qemu_io + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/100.out b/tests/qemu-iotests/100.out new file mode 100644 index 000000000..2d6e9f0a7 --- /dev/null +++ b/tests/qemu-iotests/100.out @@ -0,0 +1,89 @@ +QA output created by 100 + +== Single request == +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 +wrote 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== verify pattern == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 4096 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== Sequential requests == +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 +wrote 8192/8192 bytes at offset 0 +8 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== verify pattern == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 4096 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 8192 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== Superset overlapping requests == +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 +wrote 6144/6144 bytes at offset 0 +6 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== verify pattern == +read 1024/1024 bytes at offset 0 +1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 1024/1024 bytes at offset 3072 +1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 4096 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== Subset overlapping requests == +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 +wrote 6144/6144 bytes at offset 1024 +6 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== verify pattern == +read 1024/1024 bytes at offset 0 +1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 1024/1024 bytes at offset 3072 +1 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 4096 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== Head overlapping requests == +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 +wrote 6144/6144 bytes at offset 0 +6 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== verify pattern == +read 2048/2048 bytes at offset 2048 +2 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 4096 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== Tail overlapping requests == +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 +wrote 6144/6144 bytes at offset 2048 +6 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== verify pattern == +read 2048/2048 bytes at offset 0 +2 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 4096 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== Disjoint requests == +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=134217728 +wrote 8192/8192 bytes at offset 0 +8 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +== verify pattern == +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 61440/61440 bytes at offset 4096 +60 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 65536 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 69632 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +*** done diff --git a/tests/qemu-iotests/101 b/tests/qemu-iotests/101 new file mode 100755 index 000000000..70fbf25f6 --- /dev/null +++ b/tests/qemu-iotests/101 @@ -0,0 +1,58 @@ +#!/bin/bash +# +# Test short file I/O +# +# Copyright (C) 2014 Red Hat, Inc. +# +# 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/>. +# + +# creator +owner=stefanha@redhat.com + +seq=`basename $0` +echo "QA output created by $seq" + +here=`pwd` +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt raw +_supported_proto file +_supported_os Linux + + +echo +echo "== creating short image file ==" +dd if=/dev/zero of="$TEST_IMG" bs=1 count=320 + +echo +echo "== reading bytes beyond EOF gives zeroes ==" +$QEMU_IO -c "read -P 0 0 512" "$TEST_IMG" | _filter_qemu_io + + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/101.out b/tests/qemu-iotests/101.out new file mode 100644 index 000000000..9a996e8fc --- /dev/null +++ b/tests/qemu-iotests/101.out @@ -0,0 +1,10 @@ +QA output created by 101 + +== creating short image file == +320+0 records in +320+0 records out + +== reading bytes beyond EOF gives zeroes == +read 512/512 bytes at offset 0 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +*** done diff --git a/tests/qemu-iotests/102 b/tests/qemu-iotests/102 new file mode 100755 index 000000000..161b1974c --- /dev/null +++ b/tests/qemu-iotests/102 @@ -0,0 +1,81 @@ +#!/bin/bash +# +# Test case for qemu-io -c map and qemu-img map +# +# Copyright (C) 2014 Red Hat, Inc. +# +# 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/>. +# + +# creator +owner=mreitz@redhat.com + +seq=$(basename $0) +echo "QA output created by $seq" + +here=$PWD +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and qemu instance handling +. ./common.rc +. ./common.filter +. ./common.qemu + +_supported_fmt qcow2 +_supported_proto file +_supported_os Linux + +IMG_SIZE=64K + +echo +echo '=== Testing map command on truncated image ===' +echo + +_make_test_img $IMG_SIZE +# Create cluster +$QEMU_IO -c 'write 0 64k' "$TEST_IMG" | _filter_qemu_io +# Remove data cluster from image (first cluster: image header, second: reftable, +# third: refblock, fourth: L1 table, fifth: L2 table) +$QEMU_IMG resize -f raw "$TEST_IMG" $((5 * 64 * 1024)) + +$QEMU_IO -c map "$TEST_IMG" +$QEMU_IMG map "$TEST_IMG" + +echo +echo '=== Testing map on an image file truncated outside of qemu ===' +echo + +# Same as above, only now we concurrently truncate and map the image +_make_test_img $IMG_SIZE +$QEMU_IO -c 'write 0 64k' "$TEST_IMG" | _filter_qemu_io + +qemu_comm_method=monitor _launch_qemu -drive if=none,file="$TEST_IMG",id=drv0 + +$QEMU_IMG resize -f raw "$TEST_IMG" $((5 * 64 * 1024)) + +_send_qemu_cmd $QEMU_HANDLE 'qemu-io drv0 map' 'allocated' \ + | sed -e 's/^(qemu).*qemu-io drv0 map...$/(qemu) qemu-io drv0 map/' +_send_qemu_cmd $QEMU_HANDLE 'quit' '' + +# success, all done +echo '*** done' +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/102.out b/tests/qemu-iotests/102.out new file mode 100644 index 000000000..eecde16ad --- /dev/null +++ b/tests/qemu-iotests/102.out @@ -0,0 +1,21 @@ +QA output created by 102 + +=== Testing map command on truncated image === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=65536 +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Image resized. +[ 0] 128/ 128 sectors allocated at offset 0 bytes (1) +Offset Length Mapped to File + +=== Testing map on an image file truncated outside of qemu === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=65536 +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Image resized. +QEMU X.Y.Z monitor - type 'help' for more information +(qemu) qemu-io drv0 map +[ 0] 128/ 128 sectors allocated at offset 0 bytes (1) +*** done diff --git a/tests/qemu-iotests/103 b/tests/qemu-iotests/103 new file mode 100755 index 000000000..ccab551f6 --- /dev/null +++ b/tests/qemu-iotests/103 @@ -0,0 +1,99 @@ +#!/bin/bash +# +# Test case for qcow2 metadata cache size specification +# +# Copyright (C) 2014 Red Hat, Inc. +# +# 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/>. +# + +# creator +owner=mreitz@redhat.com + +seq=$(basename $0) +echo "QA output created by $seq" + +here=$PWD +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt qcow2 +_supported_proto file nfs +_supported_os Linux + +IMG_SIZE=64K + +_make_test_img $IMG_SIZE +$QEMU_IO -c 'write -P 42 0 64k' "$TEST_IMG" | _filter_qemu_io + +echo +echo '=== Testing invalid option combinations ===' +echo + +# all sizes set at the same time +$QEMU_IO -c "open -o cache-size=1.25M,l2-cache-size=1M,refcount-cache-size=0.25M $TEST_IMG" \ + 2>&1 | _filter_testdir | _filter_imgfmt +# l2-cache-size may not exceed cache-size +$QEMU_IO -c "open -o cache-size=1M,l2-cache-size=2M $TEST_IMG" 2>&1 \ + | _filter_testdir | _filter_imgfmt +# refcount-cache-size may not exceed cache-size +$QEMU_IO -c "open -o cache-size=1M,refcount-cache-size=2M $TEST_IMG" 2>&1 \ + | _filter_testdir | _filter_imgfmt +# 0 should be a valid size (e.g. for enforcing the minimum), so this should not +# work +$QEMU_IO -c "open -o cache-size=0,l2-cache-size=0,refcount-cache-size=0 $TEST_IMG" \ + 2>&1 | _filter_testdir | _filter_imgfmt + +echo +echo '=== Testing valid option combinations ===' +echo + +# There should be a reasonable and working minimum +$QEMU_IO -c "open -o cache-size=0 $TEST_IMG" -c 'read -P 42 0 64k' \ + | _filter_qemu_io +$QEMU_IO -c "open -o l2-cache-size=0 $TEST_IMG" -c 'read -P 42 0 64k' \ + | _filter_qemu_io +$QEMU_IO -c "open -o refcount-cache-size=0 $TEST_IMG" -c 'read -P 42 0 64k' \ + | _filter_qemu_io + +# Derive cache sizes from combined size (with a reasonable ratio, but we cannot +# test that) +$QEMU_IO -c "open -o cache-size=2M $TEST_IMG" -c 'read -P 42 0 64k' \ + | _filter_qemu_io +# Fix one cache, derive the other +$QEMU_IO -c "open -o cache-size=2M,l2-cache-size=1M $TEST_IMG" \ + -c 'read -P 42 0 64k' \ + | _filter_qemu_io +$QEMU_IO -c "open -o cache-size=2M,refcount-cache-size=1M $TEST_IMG" \ + -c 'read -P 42 0 64k' \ + | _filter_qemu_io +# Directly set both caches +$QEMU_IO -c "open -o l2-cache-size=1M,refcount-cache-size=0.25M $TEST_IMG" \ + -c 'read -P 42 0 64k' \ + | _filter_qemu_io + +# success, all done +echo '*** done' +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/103.out b/tests/qemu-iotests/103.out new file mode 100644 index 000000000..ddf6b5a07 --- /dev/null +++ b/tests/qemu-iotests/103.out @@ -0,0 +1,29 @@ +QA output created by 103 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=65536 +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +=== Testing invalid option combinations === + +qemu-io: can't open device TEST_DIR/t.IMGFMT: cache-size, l2-cache-size and refcount-cache-size may not be set the same time +qemu-io: can't open device TEST_DIR/t.IMGFMT: l2-cache-size may not exceed cache-size +qemu-io: can't open device TEST_DIR/t.IMGFMT: refcount-cache-size may not exceed cache-size +qemu-io: can't open device TEST_DIR/t.IMGFMT: cache-size, l2-cache-size and refcount-cache-size may not be set the same time + +=== Testing valid option combinations === + +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +*** done diff --git a/tests/qemu-iotests/104 b/tests/qemu-iotests/104 new file mode 100755 index 000000000..b471aa5bb --- /dev/null +++ b/tests/qemu-iotests/104 @@ -0,0 +1,57 @@ +#!/bin/bash +# +# Test image creation with aligned and unaligned sizes +# +# Copyright (C) 2014 Fujitsu. +# +# 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/>. +# + +# creator +owner=hutao@cn.fujitsu.com + +seq=`basename $0` +echo "QA output created by $seq" + +here=`pwd` +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt generic +_supported_proto generic +_supported_os Linux + +echo "=== Check qemu-img info output ===" +echo +image_sizes="1024 1234" + +for s in $image_sizes; do + _make_test_img $s | _filter_img_create + _img_info | _filter_img_info +done + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/104.out b/tests/qemu-iotests/104.out new file mode 100644 index 000000000..de27852e3 --- /dev/null +++ b/tests/qemu-iotests/104.out @@ -0,0 +1,12 @@ +QA output created by 104 +=== Check qemu-img info output === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1024 +image: TEST_DIR/t.IMGFMT +file format: IMGFMT +virtual size: 1.0K (1024 bytes) +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1234 +image: TEST_DIR/t.IMGFMT +file format: IMGFMT +virtual size: 1.5K (1536 bytes) +***done diff --git a/tests/qemu-iotests/105 b/tests/qemu-iotests/105 new file mode 100755 index 000000000..9bae49e32 --- /dev/null +++ b/tests/qemu-iotests/105 @@ -0,0 +1,70 @@ +#!/bin/bash +# +# Create, read, write big image +# +# Copyright (C) 2014 Red Hat, Inc. +# +# 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/>. +# + +# creator +owner=famz@redhat.com + +seq=`basename $0` +echo "QA output created by $seq" + +here=`pwd` +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt qcow2 vmdk vhdx qed +_supported_proto generic +_supported_os Linux +_unsupported_imgopts "subformat=twoGbMaxExtentFlat" \ + "subformat=twoGbMaxExtentSparse" + +echo +echo "creating large image" +_make_test_img 16T + +echo +echo "small read" +$QEMU_IO -c "read 1024 4096" "$TEST_IMG" | _filter_qemu_io + +echo +echo "small write" +$QEMU_IO -c "write 8192 4096" "$TEST_IMG" | _filter_qemu_io + +echo +echo "small read at high offset" +$QEMU_IO -c "read 14T 4096" "$TEST_IMG" | _filter_qemu_io + +echo +echo "small write at high offset" +$QEMU_IO -c "write 14T 4096" "$TEST_IMG" | _filter_qemu_io + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/105.out b/tests/qemu-iotests/105.out new file mode 100644 index 000000000..13ffcb593 --- /dev/null +++ b/tests/qemu-iotests/105.out @@ -0,0 +1,21 @@ +QA output created by 105 + +creating large image +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=17592186044416 + +small read +read 4096/4096 bytes at offset 1024 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +small write +wrote 4096/4096 bytes at offset 8192 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +small read at high offset +read 4096/4096 bytes at offset 15393162788864 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +small write at high offset +wrote 4096/4096 bytes at offset 15393162788864 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +*** done diff --git a/tests/qemu-iotests/107 b/tests/qemu-iotests/107 new file mode 100755 index 000000000..986203046 --- /dev/null +++ b/tests/qemu-iotests/107 @@ -0,0 +1,61 @@ +#!/bin/bash +# +# Tests updates of the qcow2 L1 table +# +# Copyright (C) 2014 Red Hat, Inc. +# +# 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/>. +# + +# creator +owner=mreitz@redhat.com + +seq="$(basename $0)" +echo "QA output created by $seq" + +here="$PWD" +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt qcow2 +_supported_proto file nfs +_supported_os Linux + + +IMG_SIZE=64K + +echo +echo '=== Updates should not write random data ===' +echo + +_make_test_img $IMG_SIZE +$QEMU_IO -c 'write 0 64k' "$TEST_IMG" | _filter_qemu_io +$QEMU_IO -c "open -o driver=raw $TEST_IMG" -c 'read -p -P 0 196616 65528' \ + | _filter_qemu_io + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 + diff --git a/tests/qemu-iotests/107.out b/tests/qemu-iotests/107.out new file mode 100644 index 000000000..93445b731 --- /dev/null +++ b/tests/qemu-iotests/107.out @@ -0,0 +1,10 @@ +QA output created by 107 + +=== Updates should not write random data === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=65536 +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 65528/65528 bytes at offset 196616 +63.992 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +*** done diff --git a/tests/qemu-iotests/108 b/tests/qemu-iotests/108 new file mode 100755 index 000000000..12fc92a63 --- /dev/null +++ b/tests/qemu-iotests/108 @@ -0,0 +1,141 @@ +#!/bin/bash +# +# Test case for repairing qcow2 images which cannot be repaired using +# the on-disk refcount structures +# +# Copyright (C) 2014 Red Hat, Inc. +# +# 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/>. +# + +# creator +owner=mreitz@redhat.com + +seq="$(basename $0)" +echo "QA output created by $seq" + +here="$PWD" +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +# This tests qocw2-specific low-level functionality +_supported_fmt qcow2 +_supported_proto file +_supported_os Linux + +echo +echo '=== Repairing an image without any refcount table ===' +echo + +_make_test_img 64M +# just write some data +$QEMU_IO -c 'write -P 42 0 64k' "$TEST_IMG" | _filter_qemu_io + +# refcount_table_offset +poke_file "$TEST_IMG" $((0x30)) "\x00\x00\x00\x00\x00\x00\x00\x00" +# refcount_table_clusters +poke_file "$TEST_IMG" $((0x38)) "\x00\x00\x00\x00" + +_check_test_img -r all + +$QEMU_IO -c 'read -P 42 0 64k' "$TEST_IMG" | _filter_qemu_io + +echo +echo '=== Repairing unreferenced data cluster in new refblock area ===' +echo + +IMGOPTS='cluster_size=512' _make_test_img 64M +# Allocate the first 128 kB in the image (first refblock) +$QEMU_IO -c 'write 0 0x1b200' "$TEST_IMG" | _filter_qemu_io +# should be 131072 == 0x20000 +stat -c '%s' "$TEST_IMG" + +# Enter a cluster at 128 kB (0x20000) +# XXX: This should be the first free entry in the last L2 table, but we cannot +# be certain +poke_file "$TEST_IMG" $((0x1ccc8)) "\x80\x00\x00\x00\x00\x02\x00\x00" + +# Fill the cluster +truncate -s $((0x20200)) "$TEST_IMG" +$QEMU_IO -c "open -o driver=raw $TEST_IMG" -c 'write -P 42 128k 512' \ + | _filter_qemu_io + +# The data should now appear at this guest offset +$QEMU_IO -c 'read -P 42 0x1b200 512' "$TEST_IMG" | _filter_qemu_io + +# This cluster is unallocated; fix it +_check_test_img -r all + +# This repair operation must have allocated a new refblock; and that refblock +# should not overlap with the unallocated data cluster. If it does, the data +# will be damaged, so check it. +$QEMU_IO -c 'read -P 42 0x1b200 512' "$TEST_IMG" | _filter_qemu_io + +echo +echo '=== Repairing refblock beyond the image end ===' +echo + +echo +echo '--- Otherwise clean ---' +echo + +_make_test_img 64M +# Normally, qemu doesn't create empty refblocks, so we just have to do it by +# hand +# XXX: This should be the entry for the second refblock +poke_file "$TEST_IMG" $((0x10008)) "\x00\x00\x00\x00\x00\x10\x00\x00" +# Mark that refblock as used +# XXX: This should be the 17th entry (cluster 16) of the first +# refblock +poke_file "$TEST_IMG" $((0x20020)) "\x00\x01" +_check_test_img -r all + +echo +echo '--- Refblock is unallocated ---' +echo + +_make_test_img 64M +poke_file "$TEST_IMG" $((0x10008)) "\x00\x00\x00\x00\x00\x10\x00\x00" +_check_test_img -r all + +echo +echo '--- Signed overflow after the refblock ---' +echo + +_make_test_img 64M +poke_file "$TEST_IMG" $((0x10008)) "\x7f\xff\xff\xff\xff\xff\x00\x00" +_check_test_img -r all + +echo +echo '--- Unsigned overflow after the refblock ---' +echo + +_make_test_img 64M +poke_file "$TEST_IMG" $((0x10008)) "\xff\xff\xff\xff\xff\xff\x00\x00" +_check_test_img -r all + +# success, all done +echo '*** done' +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/108.out b/tests/qemu-iotests/108.out new file mode 100644 index 000000000..824d5cf13 --- /dev/null +++ b/tests/qemu-iotests/108.out @@ -0,0 +1,110 @@ +QA output created by 108 + +=== Repairing an image without any refcount table === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +wrote 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +ERROR cluster 0 refcount=0 reference=1 +ERROR cluster 3 refcount=0 reference=1 +ERROR cluster 4 refcount=0 reference=1 +ERROR cluster 5 refcount=0 reference=1 +Rebuilding refcount structure +The following inconsistencies were found and repaired: + + 0 leaked clusters + 4 corruptions + +Double checking the fixed image now... +No errors were found on the image. +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +=== Repairing unreferenced data cluster in new refblock area === + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +wrote 111104/111104 bytes at offset 0 +108.500 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +131072 +wrote 512/512 bytes at offset 131072 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 512/512 bytes at offset 111104 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +ERROR cluster 256 refcount=0 reference=1 +Rebuilding refcount structure +Repairing cluster 1 refcount=1 reference=0 +Repairing cluster 2 refcount=1 reference=0 +The following inconsistencies were found and repaired: + + 0 leaked clusters + 1 corruptions + +Double checking the fixed image now... +No errors were found on the image. +read 512/512 bytes at offset 111104 +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +=== Repairing refblock beyond the image end === + + +--- Otherwise clean --- + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +Repairing refcount block 1 is outside image +The following inconsistencies were found and repaired: + + 0 leaked clusters + 1 corruptions + +Double checking the fixed image now... +No errors were found on the image. + +--- Refblock is unallocated --- + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +Repairing refcount block 1 is outside image +ERROR cluster 16 refcount=0 reference=1 +Rebuilding refcount structure +Repairing cluster 1 refcount=1 reference=0 +Repairing cluster 2 refcount=1 reference=0 +Repairing cluster 16 refcount=1 reference=0 +The following inconsistencies were found and repaired: + + 0 leaked clusters + 2 corruptions + +Double checking the fixed image now... +No errors were found on the image. + +--- Signed overflow after the refblock --- + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +Repairing refcount block 1 is outside image +ERROR could not resize image: Invalid argument +Rebuilding refcount structure +Repairing cluster 1 refcount=1 reference=0 +Repairing cluster 2 refcount=1 reference=0 +The following inconsistencies were found and repaired: + + 0 leaked clusters + 1 corruptions + +Double checking the fixed image now... +No errors were found on the image. + +--- Unsigned overflow after the refblock --- + +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 +Repairing refcount block 1 is outside image +ERROR could not resize image: Invalid argument +Rebuilding refcount structure +Repairing cluster 1 refcount=1 reference=0 +Repairing cluster 2 refcount=1 reference=0 +The following inconsistencies were found and repaired: + + 0 leaked clusters + 1 corruptions + +Double checking the fixed image now... +No errors were found on the image. +*** done diff --git a/tests/qemu-iotests/111 b/tests/qemu-iotests/111 new file mode 100755 index 000000000..6011c94b7 --- /dev/null +++ b/tests/qemu-iotests/111 @@ -0,0 +1,53 @@ +#!/bin/bash +# +# Test case for non-existing backing file when creating a qcow2 image +# and not specifying the size +# +# Copyright (C) 2014 Red Hat, Inc. +# +# 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/>. +# + +# creator +owner=mreitz@redhat.com + +seq="$(basename $0)" +echo "QA output created by $seq" + +here="$PWD" +tmp=/tmp/$$ +status=1 # failure is the default! + +_cleanup() +{ + _cleanup_test_img +} +trap "_cleanup; exit \$status" 0 1 2 3 15 + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt qed qcow qcow2 vmdk +_supported_proto file +_supported_os Linux +_unsupported_imgopts "subformat=monolithicFlat" "subformat=twoGbMaxExtentFlat" + +$QEMU_IMG create -f $IMGFMT -b "$TEST_IMG.inexistent" "$TEST_IMG" 2>&1 \ + | _filter_testdir | _filter_imgfmt + +# success, all done +echo '*** done' +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/111.out b/tests/qemu-iotests/111.out new file mode 100644 index 000000000..683c01a67 --- /dev/null +++ b/tests/qemu-iotests/111.out @@ -0,0 +1,3 @@ +QA output created by 111 +qemu-img: TEST_DIR/t.IMGFMT: Could not open 'TEST_DIR/t.IMGFMT.inexistent': No such file or directory +*** done diff --git a/tests/qemu-iotests/common b/tests/qemu-iotests/common index e4083f421..9e12bec2b 100644 --- a/tests/qemu-iotests/common +++ b/tests/qemu-iotests/common @@ -136,7 +136,6 @@ common options check options -raw test raw (default) -bochs test bochs - -cow test cow -cloop test cloop -parallels test parallels -qcow test qcow @@ -152,6 +151,7 @@ check options -nbd test nbd -ssh test ssh -nfs test nfs + -archipelago test archipelago -xdiff graphical mode diff -nocache use O_DIRECT on backing file -misalign misalign memory allocations @@ -181,11 +181,6 @@ testlist options xpand=false ;; - -cow) - IMGFMT=cow - xpand=false - ;; - -cloop) IMGFMT=cloop IMGFMT_GENERIC=false @@ -263,6 +258,11 @@ testlist options xpand=false ;; + -archipelago) + IMGPROTO=archipelago + xpand=false + ;; + -nocache) CACHEMODE="none" CACHEMODE_IS_DEFAULT=false @@ -376,10 +376,16 @@ BEGIN { for (t='$start'; t<='$end'; t++) printf "%03d\n",t }' \ echo $id >>$tmp.list else # oops - echo "$id - unknown test, ignored" + if [ "$start" == "$end" -a "$id" == "$end" ] + then + echo "$id - unknown test" + exit 1 + else + echo "$id - unknown test, ignored" + fi fi fi - done + done || exit 1 fi done diff --git a/tests/qemu-iotests/common.filter b/tests/qemu-iotests/common.filter index a04df7f6d..3acdb307f 100644 --- a/tests/qemu-iotests/common.filter +++ b/tests/qemu-iotests/common.filter @@ -170,5 +170,55 @@ _filter_qmp() -e 's#^{"QMP":.*}$#QMP_VERSION#' } +# replace driver-specific options in the "Formatting..." line +_filter_img_create() +{ + sed -e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \ + -e "s#$TEST_DIR#TEST_DIR#g" \ + -e "s#$IMGFMT#IMGFMT#g" \ + -e "s# encryption=off##g" \ + -e "s# cluster_size=[0-9]\\+##g" \ + -e "s# table_size=[0-9]\\+##g" \ + -e "s# compat='[^']*'##g" \ + -e "s# compat6=\\(on\\|off\\)##g" \ + -e "s# static=\\(on\\|off\\)##g" \ + -e "s# zeroed_grain=\\(on\\|off\\)##g" \ + -e "s# subformat='[^']*'##g" \ + -e "s# adapter_type='[^']*'##g" \ + -e "s# lazy_refcounts=\\(on\\|off\\)##g" \ + -e "s# block_size=[0-9]\\+##g" \ + -e "s# block_state_zero=\\(on\\|off\\)##g" \ + -e "s# log_size=[0-9]\\+##g" \ + -e "s/archipelago:a/TEST_DIR\//g" +} + +_filter_img_info() +{ + sed -e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \ + -e "s#$TEST_DIR#TEST_DIR#g" \ + -e "s#$IMGFMT#IMGFMT#g" \ + -e "/encrypted: yes/d" \ + -e "/cluster_size: [0-9]\\+/d" \ + -e "/table_size: [0-9]\\+/d" \ + -e "/compat: '[^']*'/d" \ + -e "/compat6: \\(on\\|off\\)/d" \ + -e "/static: \\(on\\|off\\)/d" \ + -e "/zeroed_grain: \\(on\\|off\\)/d" \ + -e "/subformat: '[^']*'/d" \ + -e "/adapter_type: '[^']*'/d" \ + -e "/lazy_refcounts: \\(on\\|off\\)/d" \ + -e "/block_size: [0-9]\\+/d" \ + -e "/block_state_zero: \\(on\\|off\\)/d" \ + -e "/log_size: [0-9]\\+/d" \ + -e "s/archipelago:a/TEST_DIR\//g" +} + +# filter out offsets and file names from qemu-img map +_filter_qemu_img_map() +{ + sed -e 's/\([0-9a-fx]* *[0-9a-fx]* *\)[0-9a-fx]* */\1/g' \ + -e 's/Mapped to *//' | _filter_testdir | _filter_imgfmt +} + # make sure this script returns success /bin/true diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc index e0ea7e3a7..9c49deb3d 100644 --- a/tests/qemu-iotests/common.rc +++ b/tests/qemu-iotests/common.rc @@ -64,6 +64,8 @@ elif [ "$IMGPROTO" = "ssh" ]; then elif [ "$IMGPROTO" = "nfs" ]; then TEST_DIR="nfs://127.0.0.1/$TEST_DIR" TEST_IMG=$TEST_DIR/t.$IMGFMT +elif [ "$IMGPROTO" = "archipelago" ]; then + TEST_IMG="archipelago:at.$IMGFMT" else TEST_IMG=$IMGPROTO:$TEST_DIR/t.$IMGFMT fi @@ -147,23 +149,7 @@ _make_test_img() else $QEMU_IMG create -f $IMGFMT $extra_img_options "$img_name" $image_size 2>&1 fi - ) | \ - sed -e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \ - -e "s#$TEST_DIR#TEST_DIR#g" \ - -e "s#$IMGFMT#IMGFMT#g" \ - -e "s# encryption=off##g" \ - -e "s# cluster_size=[0-9]\\+##g" \ - -e "s# table_size=[0-9]\\+##g" \ - -e "s# compat='[^']*'##g" \ - -e "s# compat6=\\(on\\|off\\)##g" \ - -e "s# static=\\(on\\|off\\)##g" \ - -e "s# zeroed_grain=\\(on\\|off\\)##g" \ - -e "s# subformat='[^']*'##g" \ - -e "s# adapter_type='[^']*'##g" \ - -e "s# lazy_refcounts=\\(on\\|off\\)##g" \ - -e "s# block_size=[0-9]\\+##g" \ - -e "s# block_state_zero=\\(on\\|off\\)##g" \ - -e "s# log_size=[0-9]\\+##g" + ) | _filter_img_create # Start an NBD server on the image file, which is what we'll be talking to if [ $IMGPROTO = "nbd" ]; then @@ -206,6 +192,10 @@ _cleanup_test_img() rbd --no-progress rm "$TEST_DIR/t.$IMGFMT" > /dev/null ;; + archipelago) + vlmc remove "at.$IMGFMT" > /dev/null + ;; + sheepdog) collie vdi delete "$TEST_DIR/t.$IMGFMT" ;; diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group index 6e67f6126..7dfe46940 100644 --- a/tests/qemu-iotests/group +++ b/tests/qemu-iotests/group @@ -67,7 +67,7 @@ 058 rw auto quick 059 rw auto quick 060 rw auto quick -061 rw auto quick +061 rw auto 062 rw auto quick 063 rw auto quick 064 rw auto quick @@ -100,3 +100,15 @@ 091 rw auto quick 092 rw auto quick 095 rw auto quick +097 rw auto backing +098 rw auto backing quick +099 rw auto quick +100 rw auto quick +101 rw auto quick +102 rw auto quick +103 rw auto quick +104 rw auto +105 rw auto quick +107 rw auto quick +108 rw auto quick +111 rw auto quick diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index 39a4cfcf4..f57f1548a 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -267,8 +267,7 @@ class QMPTestCase(unittest.TestCase): self.assert_qmp(event, 'data/device', drive) self.assert_qmp_absent(event, 'data/error') if check_offset: - self.assert_qmp(event, 'data/offset', self.image_len) - self.assert_qmp(event, 'data/len', self.image_len) + self.assert_qmp(event, 'data/offset', event['data']['len']) completed = True self.assert_no_active_block_jobs() diff --git a/tests/qemu-iotests/sample_images/fake.parallels.bz2 b/tests/qemu-iotests/sample_images/fake.parallels.bz2 Binary files differdeleted file mode 100644 index ffb5f13ba..000000000 --- a/tests/qemu-iotests/sample_images/fake.parallels.bz2 +++ /dev/null diff --git a/tests/qemu-iotests/sample_images/iotest-version3.vmdk.bz2 b/tests/qemu-iotests/sample_images/iotest-version3.vmdk.bz2 Binary files differindex 30abf217e..619329a24 100644 --- a/tests/qemu-iotests/sample_images/iotest-version3.vmdk.bz2 +++ b/tests/qemu-iotests/sample_images/iotest-version3.vmdk.bz2 diff --git a/tests/qemu-iotests/sample_images/parallels-v1.bz2 b/tests/qemu-iotests/sample_images/parallels-v1.bz2 Binary files differnew file mode 100644 index 000000000..d1ef14205 --- /dev/null +++ b/tests/qemu-iotests/sample_images/parallels-v1.bz2 diff --git a/tests/qemu-iotests/sample_images/parallels-v2.bz2 b/tests/qemu-iotests/sample_images/parallels-v2.bz2 Binary files differnew file mode 100644 index 000000000..fd8614d06 --- /dev/null +++ b/tests/qemu-iotests/sample_images/parallels-v2.bz2 diff --git a/tests/qemu-iotests/socket_scm_helper.c b/tests/qemu-iotests/socket_scm_helper.c index 0e2b2859a..81959835e 100644 --- a/tests/qemu-iotests/socket_scm_helper.c +++ b/tests/qemu-iotests/socket_scm_helper.c @@ -52,7 +52,7 @@ static int send_fd(int fd, int fd_to_send) cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; - memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); + memcpy(CMSG_DATA(cmsg), &fd_to_send, sizeof(int)); do { ret = sendmsg(fd, &msg, 0); diff --git a/tests/tcg/xtensa/Makefile b/tests/tcg/xtensa/Makefile index a70c92be7..522a63e36 100644 --- a/tests/tcg/xtensa/Makefile +++ b/tests/tcg/xtensa/Makefile @@ -13,6 +13,7 @@ SIMFLAGS = --xtensa-core=DC_B_232L --exit_with_target_code $(EXTFLAGS) SIMDEBUG = --gdbserve=0 endif +HOST_CC = gcc CC = $(CROSS)gcc AS = $(CROSS)gcc -x assembler-with-cpp LD = $(CROSS)ld @@ -21,7 +22,7 @@ XTENSA_SRC_PATH = $(SRC_PATH)/tests/tcg/xtensa INCLUDE_DIRS = $(XTENSA_SRC_PATH) $(SRC_PATH)/target-xtensa/core-$(CORE) XTENSA_INC = $(addprefix -I,$(INCLUDE_DIRS)) -LDFLAGS = -T$(XTENSA_SRC_PATH)/linker.ld +LDFLAGS = -Tlinker.ld CRT = crt.o vectors.o @@ -59,13 +60,16 @@ TESTCASES += test_windowed.tst all: build +linker.ld: $(XTENSA_SRC_PATH)/linker.ld.S + $(HOST_CC) $(XTENSA_INC) -E -P $< -o $@ + %.o: $(XTENSA_SRC_PATH)/%.c $(CC) $(XTENSA_INC) $(CFLAGS) -c $< -o $@ %.o: $(XTENSA_SRC_PATH)/%.S $(CC) $(XTENSA_INC) $(ASFLAGS) -c $< -o $@ -%.tst: %.o $(XTENSA_SRC_PATH)/macros.inc $(CRT) Makefile +%.tst: %.o linker.ld $(XTENSA_SRC_PATH)/macros.inc $(CRT) Makefile $(LD) $(LDFLAGS) $(NOSTDFLAGS) $(CRT) $< -o $@ build: $(TESTCASES) @@ -85,4 +89,4 @@ host-debug-%.tst: %.tst gdb --args $(SIM) $(SIMFLAGS) ./$< clean: - $(RM) -fr $(TESTCASES) $(CRT) + $(RM) -fr $(TESTCASES) $(CRT) linker.ld diff --git a/tests/tcg/xtensa/linker.ld b/tests/tcg/xtensa/linker.ld deleted file mode 100644 index 4d0b307fd..000000000 --- a/tests/tcg/xtensa/linker.ld +++ /dev/null @@ -1,112 +0,0 @@ -OUTPUT_FORMAT("elf32-xtensa-le") -ENTRY(_start) - -__DYNAMIC = 0; - -MEMORY { - ram : ORIGIN = 0xd0000000, LENGTH = 0x08000000 /* 128M */ - rom : ORIGIN = 0xfe000000, LENGTH = 0x00001000 /* 4k */ -} - -SECTIONS -{ - .init : - { - *(.init) - *(.init.*) - } > rom - - .vector : - { - . = 0x00000000; - *(.vector.window_overflow_4) - *(.vector.window_overflow_4.*) - . = 0x00000040; - *(.vector.window_underflow_4) - *(.vector.window_underflow_4.*) - . = 0x00000080; - *(.vector.window_overflow_8) - *(.vector.window_overflow_8.*) - . = 0x000000c0; - *(.vector.window_underflow_8) - *(.vector.window_underflow_8.*) - . = 0x00000100; - *(.vector.window_overflow_12) - *(.vector.window_overflow_12.*) - . = 0x00000140; - *(.vector.window_underflow_12) - *(.vector.window_underflow_12.*) - - . = 0x00000180; - *(.vector.level2) - *(.vector.level2.*) - . = 0x000001c0; - *(.vector.level3) - *(.vector.level3.*) - . = 0x00000200; - *(.vector.level4) - *(.vector.level4.*) - . = 0x00000240; - *(.vector.level5) - *(.vector.level5.*) - . = 0x00000280; - *(.vector.level6) - *(.vector.level6.*) - . = 0x000002c0; - *(.vector.level7) - *(.vector.level7.*) - - . = 0x00000300; - *(.vector.kernel) - *(.vector.kernel.*) - . = 0x00000340; - *(.vector.user) - *(.vector.user.*) - . = 0x000003c0; - *(.vector.double) - *(.vector.double.*) - } > ram - - .text : - { - _ftext = .; - *(.text .stub .text.* .gnu.linkonce.t.* .literal .literal.*) - _etext = .; - } > ram - - .rodata : - { - . = ALIGN(4); - _frodata = .; - *(.rodata .rodata.* .gnu.linkonce.r.*) - *(.rodata1) - _erodata = .; - } > ram - - .data : - { - . = ALIGN(4); - _fdata = .; - *(.data .data.* .gnu.linkonce.d.*) - *(.data1) - _gp = ALIGN(16); - *(.sdata .sdata.* .gnu.linkonce.s.*) - _edata = .; - } > ram - - .bss : - { - . = ALIGN(4); - _fbss = .; - *(.dynsbss) - *(.sbss .sbss.* .gnu.linkonce.sb.*) - *(.scommon) - *(.dynbss) - *(.bss .bss.* .gnu.linkonce.b.*) - *(COMMON) - _ebss = .; - _end = .; - } > ram -} - -PROVIDE(_fstack = ORIGIN(ram) + LENGTH(ram) - 4); diff --git a/tests/tcg/xtensa/linker.ld.S b/tests/tcg/xtensa/linker.ld.S new file mode 100644 index 000000000..f1e7fa9f8 --- /dev/null +++ b/tests/tcg/xtensa/linker.ld.S @@ -0,0 +1,130 @@ +#include <core-isa.h> + +#if XTENSA_HAVE_BE +OUTPUT_FORMAT("elf32-xtensa-be") +#else +OUTPUT_FORMAT("elf32-xtensa-le") +#endif +ENTRY(_start) + +__DYNAMIC = 0; + +MEMORY { + ram : ORIGIN = XCHAL_VECBASE_RESET_VADDR, LENGTH = 0x08000000 /* 128M */ + rom : ORIGIN = XCHAL_RESET_VECTOR_VADDR, LENGTH = 0x00001000 /* 4k */ +} + +SECTIONS +{ + .init : + { + *(.init) + *(.init.*) + } > rom + + .vector : + { + . = XCHAL_WINDOW_OF4_VECOFS; + *(.vector.window_overflow_4) + *(.vector.window_overflow_4.*) + . = XCHAL_WINDOW_UF4_VECOFS; + *(.vector.window_underflow_4) + *(.vector.window_underflow_4.*) + . = XCHAL_WINDOW_OF8_VECOFS; + *(.vector.window_overflow_8) + *(.vector.window_overflow_8.*) + . = XCHAL_WINDOW_UF8_VECOFS; + *(.vector.window_underflow_8) + *(.vector.window_underflow_8.*) + . = XCHAL_WINDOW_OF12_VECOFS; + *(.vector.window_overflow_12) + *(.vector.window_overflow_12.*) + . = XCHAL_WINDOW_UF12_VECOFS; + *(.vector.window_underflow_12) + *(.vector.window_underflow_12.*) + +#if XCHAL_NUM_INTLEVELS + XCHAL_HAVE_NMI >= 2 + . = XCHAL_INTLEVEL2_VECOFS; + *(.vector.level2) + *(.vector.level2.*) +#endif +#if XCHAL_NUM_INTLEVELS + XCHAL_HAVE_NMI >= 3 + . = XCHAL_INTLEVEL3_VECOFS; + *(.vector.level3) + *(.vector.level3.*) +#endif +#if XCHAL_NUM_INTLEVELS + XCHAL_HAVE_NMI >= 4 + . = XCHAL_INTLEVEL4_VECOFS; + *(.vector.level4) + *(.vector.level4.*) +#endif +#if XCHAL_NUM_INTLEVELS + XCHAL_HAVE_NMI >= 5 + . = XCHAL_INTLEVEL5_VECOFS; + *(.vector.level5) + *(.vector.level5.*) +#endif +#if XCHAL_NUM_INTLEVELS + XCHAL_HAVE_NMI >= 6 + . = XCHAL_INTLEVEL6_VECOFS; + *(.vector.level6) + *(.vector.level6.*) +#endif +#if XCHAL_NUM_INTLEVELS + XCHAL_HAVE_NMI >= 7 + . = XCHAL_INTLEVEL7_VECOFS; + *(.vector.level7) + *(.vector.level7.*) +#endif + + . = XCHAL_KERNEL_VECOFS; + *(.vector.kernel) + *(.vector.kernel.*) + . = XCHAL_USER_VECOFS; + *(.vector.user) + *(.vector.user.*) + . = XCHAL_DOUBLEEXC_VECOFS; + *(.vector.double) + *(.vector.double.*) + } > ram + + .text : + { + _ftext = .; + *(.text .stub .text.* .gnu.linkonce.t.* .literal .literal.*) + _etext = .; + } > ram + + .rodata : + { + . = ALIGN(4); + _frodata = .; + *(.rodata .rodata.* .gnu.linkonce.r.*) + *(.rodata1) + _erodata = .; + } > ram + + .data : + { + . = ALIGN(4); + _fdata = .; + *(.data .data.* .gnu.linkonce.d.*) + *(.data1) + _gp = ALIGN(16); + *(.sdata .sdata.* .gnu.linkonce.s.*) + _edata = .; + } > ram + + .bss : + { + . = ALIGN(4); + _fbss = .; + *(.dynsbss) + *(.sbss .sbss.* .gnu.linkonce.sb.*) + *(.scommon) + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + _ebss = .; + _end = .; + } > ram +} + +PROVIDE(_fstack = (ORIGIN(ram) & 0xf0000000) + LENGTH(ram) - 16); diff --git a/tests/tcg/xtensa/test_windowed.S b/tests/tcg/xtensa/test_windowed.S index 3de6d3763..d851e8f43 100644 --- a/tests/tcg/xtensa/test_windowed.S +++ b/tests/tcg/xtensa/test_windowed.S @@ -299,4 +299,55 @@ test entry entry_test 12 test_end +.macro entry_overflow_test window, free, next_window + set_vector window_overflow_4, 0 + set_vector window_overflow_8, 0 + set_vector window_overflow_12, 0 + set_vector window_overflow_\next_window, 10f + + movi a2, \window + movi a2, \free + movi a2, \next_window + reset_window %(1 | ((1 | (1 << ((\next_window) / 4))) << ((\free) / 4))) + reset_ps + movi a2, 0x4000f | ((\window) << 14) + wsr a2, ps + isync + movi a3, 0x12345678 + j 1f + .align 4 +1: + entry a3, 0x5678 + test_fail + .align 4 +10: + rsr a2, epc1 + movi a3, 1b + assert eq, a2, a3 + movi a2, 2f + wsr a2, epc1 + + rsr a2, windowbase + movi a3, (\free) / 4 + assert eq, a2, a3 + rfwo +2: +.endm + +.macro all_entry_overflow_tests + .irp window, 4, 8, 12 + .irp next_window, 4, 8, 12 + .irp free, 4, 8, 12 + .if \free <= \window + entry_overflow_test \window, \free, \next_window + .endif + .endr + .endr + .endr +.endm + +test entry_overflow + all_entry_overflow_tests +test_end + test_suite_end diff --git a/tests/test-aio.c b/tests/test-aio.c index f12b6e0ae..a7cb5c991 100644 --- a/tests/test-aio.c +++ b/tests/test-aio.c @@ -14,6 +14,7 @@ #include "block/aio.h" #include "qemu/timer.h" #include "qemu/sockets.h" +#include "qemu/error-report.h" static AioContext *ctx; @@ -57,8 +58,6 @@ static void bh_test_cb(void *opaque) } } -#if !defined(_WIN32) - static void timer_test_cb(void *opaque) { TimerTestData *data = opaque; @@ -68,12 +67,10 @@ static void timer_test_cb(void *opaque) } } -static void dummy_io_handler_read(void *opaque) +static void dummy_io_handler_read(EventNotifier *e) { } -#endif /* !_WIN32 */ - static void bh_delete_cb(void *opaque) { BHTestData *data = opaque; @@ -428,24 +425,18 @@ static void test_wait_event_notifier_noflush(void) event_notifier_cleanup(&data.e); } -#if !defined(_WIN32) - static void test_timer_schedule(void) { TimerTestData data = { .n = 0, .ctx = ctx, .ns = SCALE_MS * 750LL, .max = 2, .clock_type = QEMU_CLOCK_VIRTUAL }; - int pipefd[2]; + EventNotifier e; /* aio_poll will not block to wait for timers to complete unless it has * an fd to wait on. Fixing this breaks other tests. So create a dummy one. */ - g_assert(!qemu_pipe(pipefd)); - qemu_set_nonblock(pipefd[0]); - qemu_set_nonblock(pipefd[1]); - - aio_set_fd_handler(ctx, pipefd[0], - dummy_io_handler_read, NULL, NULL); + event_notifier_init(&e, false); + aio_set_event_notifier(ctx, &e, dummy_io_handler_read); aio_poll(ctx, false); aio_timer_init(ctx, &data.timer, data.clock_type, @@ -484,15 +475,12 @@ static void test_timer_schedule(void) g_assert(!aio_poll(ctx, false)); g_assert_cmpint(data.n, ==, 2); - aio_set_fd_handler(ctx, pipefd[0], NULL, NULL, NULL); - close(pipefd[0]); - close(pipefd[1]); + aio_set_event_notifier(ctx, &e, NULL); + event_notifier_cleanup(&e); timer_del(&data.timer); } -#endif /* !_WIN32 */ - /* Now the same tests, using the context as a GSource. They are * very similar to the ones above, with g_main_context_iteration * replacing aio_poll. However: @@ -775,25 +763,19 @@ static void test_source_wait_event_notifier_noflush(void) event_notifier_cleanup(&data.e); } -#if !defined(_WIN32) - static void test_source_timer_schedule(void) { TimerTestData data = { .n = 0, .ctx = ctx, .ns = SCALE_MS * 750LL, .max = 2, .clock_type = QEMU_CLOCK_VIRTUAL }; - int pipefd[2]; + EventNotifier e; int64_t expiry; /* aio_poll will not block to wait for timers to complete unless it has * an fd to wait on. Fixing this breaks other tests. So create a dummy one. */ - g_assert(!qemu_pipe(pipefd)); - qemu_set_nonblock(pipefd[0]); - qemu_set_nonblock(pipefd[1]); - - aio_set_fd_handler(ctx, pipefd[0], - dummy_io_handler_read, NULL, NULL); + event_notifier_init(&e, false); + aio_set_event_notifier(ctx, &e, dummy_io_handler_read); do {} while (g_main_context_iteration(NULL, false)); aio_timer_init(ctx, &data.timer, data.clock_type, @@ -818,25 +800,29 @@ static void test_source_timer_schedule(void) g_assert_cmpint(data.n, ==, 2); g_assert(qemu_clock_get_ns(data.clock_type) > expiry); - aio_set_fd_handler(ctx, pipefd[0], NULL, NULL, NULL); - close(pipefd[0]); - close(pipefd[1]); + aio_set_event_notifier(ctx, &e, NULL); + event_notifier_cleanup(&e); timer_del(&data.timer); } -#endif /* !_WIN32 */ - /* End of tests. */ int main(int argc, char **argv) { + Error *local_error = NULL; GSource *src; init_clocks(); - ctx = aio_context_new(); + ctx = aio_context_new(&local_error); + if (!ctx) { + error_report("Failed to create AIO Context: '%s'", + error_get_pretty(local_error)); + error_free(local_error); + exit(1); + } src = aio_get_g_source(ctx); g_source_attach(src, NULL); g_source_unref(src); @@ -857,9 +843,7 @@ int main(int argc, char **argv) g_test_add_func("/aio/event/wait", test_wait_event_notifier); g_test_add_func("/aio/event/wait/no-flush-cb", test_wait_event_notifier_noflush); g_test_add_func("/aio/event/flush", test_flush_event_notifier); -#if !defined(_WIN32) g_test_add_func("/aio/timer/schedule", test_timer_schedule); -#endif g_test_add_func("/aio-gsource/notify", test_source_notify); g_test_add_func("/aio-gsource/flush", test_source_flush); @@ -874,8 +858,6 @@ int main(int argc, char **argv) g_test_add_func("/aio-gsource/event/wait", test_source_wait_event_notifier); g_test_add_func("/aio-gsource/event/wait/no-flush-cb", test_source_wait_event_notifier_noflush); g_test_add_func("/aio-gsource/event/flush", test_source_flush_event_notifier); -#if !defined(_WIN32) g_test_add_func("/aio-gsource/timer/schedule", test_source_timer_schedule); -#endif return g_test_run(); } diff --git a/tests/test-bitops.c b/tests/test-bitops.c index 8238eb5f6..47b5d3ed9 100644 --- a/tests/test-bitops.c +++ b/tests/test-bitops.c @@ -8,6 +8,7 @@ #include <glib.h> #include <stdint.h> +#include "qemu/osdep.h" #include "qemu/bitops.h" typedef struct { diff --git a/tests/test-coroutine.c b/tests/test-coroutine.c index 760636ddc..e22fae170 100644 --- a/tests/test-coroutine.c +++ b/tests/test-coroutine.c @@ -288,6 +288,58 @@ static void perf_yield(void) maxcycles, duration); } +static __attribute__((noinline)) void dummy(unsigned *i) +{ + (*i)--; +} + +static void perf_baseline(void) +{ + unsigned int i, maxcycles; + double duration; + + maxcycles = 100000000; + i = maxcycles; + + g_test_timer_start(); + while (i > 0) { + dummy(&i); + } + duration = g_test_timer_elapsed(); + + g_test_message("Function call %u iterations: %f s\n", + maxcycles, duration); +} + +static __attribute__((noinline)) void perf_cost_func(void *opaque) +{ + qemu_coroutine_yield(); +} + +static void perf_cost(void) +{ + const unsigned long maxcycles = 40000000; + unsigned long i = 0; + double duration; + unsigned long ops; + Coroutine *co; + + g_test_timer_start(); + while (i++ < maxcycles) { + co = qemu_coroutine_create(perf_cost_func); + qemu_coroutine_enter(co, &i); + qemu_coroutine_enter(co, NULL); + } + duration = g_test_timer_elapsed(); + ops = (long)(maxcycles / (duration * 1000)); + + g_test_message("Run operation %lu iterations %f s, %luK operations/s, " + "%luns per coroutine", + maxcycles, + duration, ops, + (unsigned long)(1000000000 * duration) / maxcycles); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); @@ -301,6 +353,8 @@ int main(int argc, char **argv) g_test_add_func("/perf/lifecycle", perf_lifecycle); g_test_add_func("/perf/nesting", perf_nesting); g_test_add_func("/perf/yield", perf_yield); + g_test_add_func("/perf/function-call", perf_baseline); + g_test_add_func("/perf/cost", perf_cost); } return g_test_run(); } diff --git a/tests/test-qdev-global-props.c b/tests/test-qdev-global-props.c index 2bef04c76..0be98355c 100644 --- a/tests/test-qdev-global-props.c +++ b/tests/test-qdev-global-props.c @@ -65,7 +65,7 @@ static const TypeInfo static_prop_type = { }; /* Test simple static property setting to default value */ -static void test_static_prop(void) +static void test_static_prop_subprocess(void) { MyType *mt; @@ -75,8 +75,16 @@ static void test_static_prop(void) g_assert_cmpuint(mt->prop1, ==, PROP_DEFAULT); } +static void test_static_prop(void) +{ + g_test_trap_subprocess("/qdev/properties/static/default/subprocess", 0, 0); + g_test_trap_assert_passed(); + g_test_trap_assert_stderr(""); + g_test_trap_assert_stdout(""); +} + /* Test setting of static property using global properties */ -static void test_static_globalprop(void) +static void test_static_globalprop_subprocess(void) { MyType *mt; static GlobalProperty props[] = { @@ -93,10 +101,21 @@ static void test_static_globalprop(void) g_assert_cmpuint(mt->prop2, ==, PROP_DEFAULT); } +static void test_static_globalprop(void) +{ + g_test_trap_subprocess("/qdev/properties/static/global/subprocess", 0, 0); + g_test_trap_assert_passed(); + g_test_trap_assert_stderr(""); + g_test_trap_assert_stdout(""); +} + #define TYPE_DYNAMIC_PROPS "dynamic-prop-type" #define DYNAMIC_TYPE(obj) \ OBJECT_CHECK(MyType, (obj), TYPE_DYNAMIC_PROPS) +#define TYPE_UNUSED_HOTPLUG "hotplug-type" +#define TYPE_UNUSED_NOHOTPLUG "nohotplug-type" + static void prop1_accessor(Object *obj, Visitor *v, void *opaque, @@ -143,14 +162,101 @@ static const TypeInfo dynamic_prop_type = { .class_init = dynamic_class_init, }; -/* Test setting of static property using global properties */ +static void hotplug_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = NULL; + dc->hotpluggable = true; +} + +static const TypeInfo hotplug_type = { + .name = TYPE_UNUSED_HOTPLUG, + .parent = TYPE_DEVICE, + .instance_size = sizeof(MyType), + .instance_init = dynamic_instance_init, + .class_init = hotplug_class_init, +}; + +static void nohotplug_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = NULL; + dc->hotpluggable = false; +} + +static const TypeInfo nohotplug_type = { + .name = TYPE_UNUSED_NOHOTPLUG, + .parent = TYPE_DEVICE, + .instance_size = sizeof(MyType), + .instance_init = dynamic_instance_init, + .class_init = nohotplug_class_init, +}; + +#define TYPE_NONDEVICE "nondevice-type" + +static const TypeInfo nondevice_type = { + .name = TYPE_NONDEVICE, + .parent = TYPE_OBJECT, +}; + +/* Test setting of dynamic properties using global properties */ +static void test_dynamic_globalprop_subprocess(void) +{ + MyType *mt; + static GlobalProperty props[] = { + { TYPE_DYNAMIC_PROPS, "prop1", "101", true }, + { TYPE_DYNAMIC_PROPS, "prop2", "102", true }, + { TYPE_DYNAMIC_PROPS"-bad", "prop3", "103", true }, + { TYPE_UNUSED_HOTPLUG, "prop4", "104", true }, + { TYPE_UNUSED_NOHOTPLUG, "prop5", "105", true }, + { TYPE_NONDEVICE, "prop6", "106", true }, + {} + }; + int all_used; + + qdev_prop_register_global_list(props); + + mt = DYNAMIC_TYPE(object_new(TYPE_DYNAMIC_PROPS)); + qdev_init_nofail(DEVICE(mt)); + + g_assert_cmpuint(mt->prop1, ==, 101); + g_assert_cmpuint(mt->prop2, ==, 102); + all_used = qdev_prop_check_globals(); + g_assert_cmpuint(all_used, ==, 1); + g_assert(props[0].used); + g_assert(props[1].used); + g_assert(!props[2].used); + g_assert(!props[3].used); + g_assert(!props[4].used); + g_assert(!props[5].used); +} + static void test_dynamic_globalprop(void) { + g_test_trap_subprocess("/qdev/properties/dynamic/global/subprocess", 0, 0); + g_test_trap_assert_passed(); + g_test_trap_assert_stderr_unmatched("*prop1*"); + g_test_trap_assert_stderr_unmatched("*prop2*"); + g_test_trap_assert_stderr("*Warning: global dynamic-prop-type-bad.prop3 has invalid class name\n*"); + g_test_trap_assert_stderr_unmatched("*prop4*"); + g_test_trap_assert_stderr("*Warning: global nohotplug-type.prop5=105 not used\n*"); + g_test_trap_assert_stderr("*Warning: global nondevice-type.prop6 has invalid class name\n*"); + g_test_trap_assert_stdout(""); +} + +/* Test setting of dynamic properties using user_provided=false properties */ +static void test_dynamic_globalprop_nouser_subprocess(void) +{ MyType *mt; static GlobalProperty props[] = { { TYPE_DYNAMIC_PROPS, "prop1", "101" }, { TYPE_DYNAMIC_PROPS, "prop2", "102" }, - { TYPE_DYNAMIC_PROPS"-bad", "prop3", "103", true }, + { TYPE_DYNAMIC_PROPS"-bad", "prop3", "103" }, + { TYPE_UNUSED_HOTPLUG, "prop4", "104" }, + { TYPE_UNUSED_NOHOTPLUG, "prop5", "105" }, + { TYPE_NONDEVICE, "prop6", "106" }, {} }; int all_used; @@ -162,8 +268,22 @@ static void test_dynamic_globalprop(void) g_assert_cmpuint(mt->prop1, ==, 101); g_assert_cmpuint(mt->prop2, ==, 102); - all_used = qdev_prop_check_global(); - g_assert_cmpuint(all_used, ==, 1); + all_used = qdev_prop_check_globals(); + g_assert_cmpuint(all_used, ==, 0); + g_assert(props[0].used); + g_assert(props[1].used); + g_assert(!props[2].used); + g_assert(!props[3].used); + g_assert(!props[4].used); + g_assert(!props[5].used); +} + +static void test_dynamic_globalprop_nouser(void) +{ + g_test_trap_subprocess("/qdev/properties/dynamic/global/nouser/subprocess", 0, 0); + g_test_trap_assert_passed(); + g_test_trap_assert_stderr(""); + g_test_trap_assert_stdout(""); } int main(int argc, char **argv) @@ -173,10 +293,29 @@ int main(int argc, char **argv) module_call_init(MODULE_INIT_QOM); type_register_static(&static_prop_type); type_register_static(&dynamic_prop_type); - - g_test_add_func("/qdev/properties/static/default", test_static_prop); - g_test_add_func("/qdev/properties/static/global", test_static_globalprop); - g_test_add_func("/qdev/properties/dynamic/global", test_dynamic_globalprop); + type_register_static(&hotplug_type); + type_register_static(&nohotplug_type); + type_register_static(&nondevice_type); + + g_test_add_func("/qdev/properties/static/default/subprocess", + test_static_prop_subprocess); + g_test_add_func("/qdev/properties/static/default", + test_static_prop); + + g_test_add_func("/qdev/properties/static/global/subprocess", + test_static_globalprop_subprocess); + g_test_add_func("/qdev/properties/static/global", + test_static_globalprop); + + g_test_add_func("/qdev/properties/dynamic/global/subprocess", + test_dynamic_globalprop_subprocess); + g_test_add_func("/qdev/properties/dynamic/global", + test_dynamic_globalprop); + + g_test_add_func("/qdev/properties/dynamic/global/nouser/subprocess", + test_dynamic_globalprop_nouser_subprocess); + g_test_add_func("/qdev/properties/dynamic/global/nouser", + test_dynamic_globalprop_nouser); g_test_run(); diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c index 0f770034b..d5360c6a8 100644 --- a/tests/test-qmp-input-strict.c +++ b/tests/test-qmp-input-strict.c @@ -260,6 +260,21 @@ static void test_validate_fail_union_flat(TestInputVisitorData *data, qapi_free_UserDefFlatUnion(tmp); } +static void test_validate_fail_union_flat_no_discrim(TestInputVisitorData *data, + const void *unused) +{ + UserDefFlatUnion2 *tmp = NULL; + Error *err = NULL; + Visitor *v; + + /* test situation where discriminator field ('enum1' here) is missing */ + v = validate_test_init(data, "{ 'string': 'c', 'string1': 'd', 'string2': 'e' }"); + + visit_type_UserDefFlatUnion2(v, &tmp, NULL, &err); + g_assert(err); + qapi_free_UserDefFlatUnion2(tmp); +} + static void test_validate_fail_union_anon(TestInputVisitorData *data, const void *unused) { @@ -310,6 +325,8 @@ int main(int argc, char **argv) &testdata, test_validate_fail_union); validate_test_add("/visitor/input-strict/fail/union-flat", &testdata, test_validate_fail_union_flat); + validate_test_add("/visitor/input-strict/fail/union-flat-no-discriminator", + &testdata, test_validate_fail_union_flat_no_discrim); validate_test_add("/visitor/input-strict/fail/union-anon", &testdata, test_validate_fail_union_anon); diff --git a/tests/test-thread-pool.c b/tests/test-thread-pool.c index f40b7fc17..6a0b9813f 100644 --- a/tests/test-thread-pool.c +++ b/tests/test-thread-pool.c @@ -4,13 +4,14 @@ #include "block/thread-pool.h" #include "block/block.h" #include "qemu/timer.h" +#include "qemu/error-report.h" static AioContext *ctx; static ThreadPool *pool; static int active; typedef struct { - BlockDriverAIOCB *aiocb; + BlockAIOCB *aiocb; int n; int ret; } WorkerTestData; @@ -33,7 +34,7 @@ static int long_cb(void *opaque) static void done_cb(void *opaque, int ret) { WorkerTestData *data = opaque; - g_assert_cmpint(data->ret, ==, -EINPROGRESS); + g_assert(data->ret == -EINPROGRESS || data->ret == -ECANCELED); data->ret = ret; data->aiocb = NULL; @@ -132,7 +133,7 @@ static void test_submit_many(void) } } -static void test_cancel(void) +static void do_test_cancel(bool sync) { WorkerTestData data[100]; int num_canceled; @@ -170,18 +171,25 @@ static void test_cancel(void) for (i = 0; i < 100; i++) { if (atomic_cmpxchg(&data[i].n, 0, 3) == 0) { data[i].ret = -ECANCELED; - bdrv_aio_cancel(data[i].aiocb); - active--; + if (sync) { + bdrv_aio_cancel(data[i].aiocb); + } else { + bdrv_aio_cancel_async(data[i].aiocb); + } num_canceled++; } } g_assert_cmpint(active, >, 0); g_assert_cmpint(num_canceled, <, 100); - /* Canceling the others will be a blocking operation. */ for (i = 0; i < 100; i++) { if (data[i].aiocb && data[i].n != 3) { - bdrv_aio_cancel(data[i].aiocb); + if (sync) { + /* Canceling the others will be a blocking operation. */ + bdrv_aio_cancel(data[i].aiocb); + } else { + bdrv_aio_cancel_async(data[i].aiocb); + } } } @@ -193,22 +201,39 @@ static void test_cancel(void) for (i = 0; i < 100; i++) { if (data[i].n == 3) { g_assert_cmpint(data[i].ret, ==, -ECANCELED); - g_assert(data[i].aiocb != NULL); + g_assert(data[i].aiocb == NULL); } else { g_assert_cmpint(data[i].n, ==, 2); - g_assert_cmpint(data[i].ret, ==, 0); + g_assert(data[i].ret == 0 || data[i].ret == -ECANCELED); g_assert(data[i].aiocb == NULL); } } } +static void test_cancel(void) +{ + do_test_cancel(true); +} + +static void test_cancel_async(void) +{ + do_test_cancel(false); +} + int main(int argc, char **argv) { int ret; + Error *local_error = NULL; init_clocks(); - ctx = aio_context_new(); + ctx = aio_context_new(&local_error); + if (!ctx) { + error_report("Failed to create AIO Context: '%s'", + error_get_pretty(local_error)); + error_free(local_error); + exit(1); + } pool = aio_get_thread_pool(ctx); g_test_init(&argc, &argv, NULL); @@ -217,6 +242,7 @@ int main(int argc, char **argv) g_test_add_func("/thread-pool/submit-co", test_submit_co); g_test_add_func("/thread-pool/submit-many", test_submit_many); g_test_add_func("/thread-pool/cancel", test_cancel); + g_test_add_func("/thread-pool/cancel-async", test_cancel_async); ret = g_test_run(); diff --git a/tests/test-throttle.c b/tests/test-throttle.c index 000ae31af..d8ba415e4 100644 --- a/tests/test-throttle.c +++ b/tests/test-throttle.c @@ -14,6 +14,7 @@ #include <math.h> #include "block/aio.h" #include "qemu/throttle.h" +#include "qemu/error-report.h" static AioContext *ctx; static LeakyBucket bkt; @@ -492,10 +493,17 @@ static void test_accounting(void) int main(int argc, char **argv) { GSource *src; + Error *local_error = NULL; init_clocks(); - ctx = aio_context_new(); + ctx = aio_context_new(&local_error); + if (!ctx) { + error_report("Failed to create AIO Context: '%s'", + error_get_pretty(local_error)); + error_free(local_error); + exit(1); + } src = aio_get_g_source(ctx); g_source_attach(src, NULL); g_source_unref(src); diff --git a/tests/test-vmstate.c b/tests/test-vmstate.c index d72c64c90..5e0fd13cc 100644 --- a/tests/test-vmstate.c +++ b/tests/test-vmstate.c @@ -43,6 +43,12 @@ void yield_until_fd_readable(int fd) select(fd + 1, &fds, NULL, NULL, NULL); } +/* + * Some tests use 'open_test_file' to work on a real fd, some use + * an in memory file (QEMUSizedBuffer+qemu_bufopen); we could pick one + * but this way we test both. + */ + /* Duplicate temp_fd and seek to the beginning of the file */ static QEMUFile *open_test_file(bool write) { @@ -54,6 +60,30 @@ static QEMUFile *open_test_file(bool write) return qemu_fdopen(fd, write ? "wb" : "rb"); } +/* Open a read-only qemu-file from an existing memory block */ +static QEMUFile *open_mem_file_read(const void *data, size_t len) +{ + /* The qsb gets freed by qemu_fclose */ + QEMUSizedBuffer *qsb = qsb_create(data, len); + g_assert(qsb); + + return qemu_bufopen("r", qsb); +} + +/* + * Check that the contents of the memory-buffered file f match + * the given size/data. + */ +static void check_mem_file(QEMUFile *f, void *data, size_t size) +{ + uint8_t *result = g_malloc(size); + const QEMUSizedBuffer *qsb = qemu_buf_get(f); + g_assert_cmpint(qsb_get_length(qsb), ==, size); + g_assert_cmpint(qsb_get_buffer(qsb, 0, size, result), ==, size); + g_assert_cmpint(memcmp(result, data, size), ==, 0); + g_free(result); +} + #define SUCCESS(val) \ g_assert_cmpint((val), ==, 0) @@ -371,14 +401,12 @@ static const VMStateDescription vmstate_skipping = { static void test_save_noskip(void) { - QEMUFile *fsave = open_test_file(true); + QEMUFile *fsave = qemu_bufopen("w", NULL); TestStruct obj = { .a = 1, .b = 2, .c = 3, .d = 4, .e = 5, .f = 6, .skip_c_e = false }; vmstate_save_state(fsave, &vmstate_skipping, &obj); g_assert(!qemu_file_get_error(fsave)); - qemu_fclose(fsave); - QEMUFile *loading = open_test_file(false); uint8_t expected[] = { 0, 0, 0, 1, /* a */ 0, 0, 0, 2, /* b */ @@ -387,52 +415,31 @@ static void test_save_noskip(void) 0, 0, 0, 5, /* e */ 0, 0, 0, 0, 0, 0, 0, 6, /* f */ }; - uint8_t result[sizeof(expected)]; - g_assert_cmpint(qemu_get_buffer(loading, result, sizeof(result)), ==, - sizeof(result)); - g_assert(!qemu_file_get_error(loading)); - g_assert_cmpint(memcmp(result, expected, sizeof(result)), ==, 0); - - /* Must reach EOF */ - qemu_get_byte(loading); - g_assert_cmpint(qemu_file_get_error(loading), ==, -EIO); - - qemu_fclose(loading); + check_mem_file(fsave, expected, sizeof(expected)); + qemu_fclose(fsave); } static void test_save_skip(void) { - QEMUFile *fsave = open_test_file(true); + QEMUFile *fsave = qemu_bufopen("w", NULL); TestStruct obj = { .a = 1, .b = 2, .c = 3, .d = 4, .e = 5, .f = 6, .skip_c_e = true }; vmstate_save_state(fsave, &vmstate_skipping, &obj); g_assert(!qemu_file_get_error(fsave)); - qemu_fclose(fsave); - QEMUFile *loading = open_test_file(false); uint8_t expected[] = { 0, 0, 0, 1, /* a */ 0, 0, 0, 2, /* b */ 0, 0, 0, 0, 0, 0, 0, 4, /* d */ 0, 0, 0, 0, 0, 0, 0, 6, /* f */ }; - uint8_t result[sizeof(expected)]; - g_assert_cmpint(qemu_get_buffer(loading, result, sizeof(result)), ==, - sizeof(result)); - g_assert(!qemu_file_get_error(loading)); - g_assert_cmpint(memcmp(result, expected, sizeof(result)), ==, 0); - - - /* Must reach EOF */ - qemu_get_byte(loading); - g_assert_cmpint(qemu_file_get_error(loading), ==, -EIO); + check_mem_file(fsave, expected, sizeof(expected)); - qemu_fclose(loading); + qemu_fclose(fsave); } static void test_load_noskip(void) { - QEMUFile *fsave = open_test_file(true); uint8_t buf[] = { 0, 0, 0, 10, /* a */ 0, 0, 0, 20, /* b */ @@ -442,10 +449,8 @@ static void test_load_noskip(void) 0, 0, 0, 0, 0, 0, 0, 60, /* f */ QEMU_VM_EOF, /* just to ensure we won't get EOF reported prematurely */ }; - qemu_put_buffer(fsave, buf, sizeof(buf)); - qemu_fclose(fsave); - QEMUFile *loading = open_test_file(false); + QEMUFile *loading = open_mem_file_read(buf, sizeof(buf)); TestStruct obj = { .skip_c_e = false }; vmstate_load_state(loading, &vmstate_skipping, &obj, 2); g_assert(!qemu_file_get_error(loading)); @@ -460,7 +465,6 @@ static void test_load_noskip(void) static void test_load_skip(void) { - QEMUFile *fsave = open_test_file(true); uint8_t buf[] = { 0, 0, 0, 10, /* a */ 0, 0, 0, 20, /* b */ @@ -468,10 +472,8 @@ static void test_load_skip(void) 0, 0, 0, 0, 0, 0, 0, 60, /* f */ QEMU_VM_EOF, /* just to ensure we won't get EOF reported prematurely */ }; - qemu_put_buffer(fsave, buf, sizeof(buf)); - qemu_fclose(fsave); - QEMUFile *loading = open_test_file(false); + QEMUFile *loading = open_mem_file_read(buf, sizeof(buf)); TestStruct obj = { .skip_c_e = true, .c = 300, .e = 500 }; vmstate_load_state(loading, &vmstate_skipping, &obj, 2); g_assert(!qemu_file_get_error(loading)); diff --git a/tests/usb-hcd-ehci-test.c b/tests/usb-hcd-ehci-test.c index bcdf62fc3..75073bf24 100644 --- a/tests/usb-hcd-ehci-test.c +++ b/tests/usb-hcd-ehci-test.c @@ -15,11 +15,7 @@ #include "qemu/osdep.h" #include "hw/usb/uhci-regs.h" #include "hw/usb/ehci-regs.h" - -struct qhc { - QPCIDevice *dev; - void *base; -}; +#include "libqos/usb.h" static QPCIBus *pcibus; static struct qhc uhci1; @@ -29,15 +25,6 @@ static struct qhc ehci1; /* helpers */ -static void pci_init_one(struct qhc *hc, uint32_t devfn, int bar) -{ - hc->dev = qpci_device_find(pcibus, devfn); - g_assert(hc->dev != NULL); - qpci_device_enable(hc->dev); - hc->base = qpci_iomap(hc->dev, bar); - g_assert(hc->base != NULL); -} - #if 0 static void uhci_port_update(struct qhc *hc, int port, uint16_t set, uint16_t clear) @@ -52,19 +39,6 @@ static void uhci_port_update(struct qhc *hc, int port, } #endif -static void uhci_port_test(struct qhc *hc, int port, uint16_t expect) -{ - void *addr = hc->base + 0x10 + 2 * port; - uint16_t value = qpci_io_readw(hc->dev, addr); - uint16_t mask = ~(UHCI_PORT_WRITE_CLEAR | UHCI_PORT_RSVD1); - -#if 0 - fprintf(stderr, "%s: %d, have 0x%04x, want 0x%04x\n", - __func__, port, value & mask, expect & mask); -#endif - g_assert((value & mask) == (expect & mask)); -} - static void ehci_port_test(struct qhc *hc, int port, uint32_t expect) { void *addr = hc->base + 0x64 + 4 * port; @@ -88,10 +62,10 @@ static void pci_init(void) pcibus = qpci_init_pc(); g_assert(pcibus != NULL); - pci_init_one(&uhci1, QPCI_DEVFN(0x1d, 0), 4); - pci_init_one(&uhci2, QPCI_DEVFN(0x1d, 1), 4); - pci_init_one(&uhci3, QPCI_DEVFN(0x1d, 2), 4); - pci_init_one(&ehci1, QPCI_DEVFN(0x1d, 7), 0); + qusb_pci_init_one(pcibus, &uhci1, QPCI_DEVFN(0x1d, 0), 4); + qusb_pci_init_one(pcibus, &uhci2, QPCI_DEVFN(0x1d, 1), 4); + qusb_pci_init_one(pcibus, &uhci3, QPCI_DEVFN(0x1d, 2), 4); + qusb_pci_init_one(pcibus, &ehci1, QPCI_DEVFN(0x1d, 7), 0); } static void pci_uhci_port_1(void) @@ -154,6 +128,19 @@ static void pci_ehci_port_2(void) } } +static void pci_ehci_port_3_hotplug(void) +{ + /* check for presence of hotplugged usb-tablet */ + g_assert(pcibus != NULL); + ehci_port_test(&ehci1, 2, PORTSC_PPOWER | PORTSC_CONNECT); +} + +static void pci_ehci_port_hotplug(void) +{ + usb_test_hotplug("ich9-ehci-1", 3, pci_ehci_port_3_hotplug); +} + + int main(int argc, char **argv) { int ret; @@ -165,6 +152,7 @@ int main(int argc, char **argv) qtest_add_func("/ehci/pci/ehci-config", pci_ehci_config); qtest_add_func("/ehci/pci/uhci-port-2", pci_uhci_port_2); qtest_add_func("/ehci/pci/ehci-port-2", pci_ehci_port_2); + qtest_add_func("/ehci/pci/ehci-port-3-hotplug", pci_ehci_port_hotplug); qtest_start("-machine q35 -device ich9-usb-ehci1,bus=pcie.0,addr=1d.7," "multifunction=on,id=ich9-ehci-1 " diff --git a/tests/usb-hcd-ohci-test.c b/tests/usb-hcd-ohci-test.c new file mode 100644 index 000000000..1160bde84 --- /dev/null +++ b/tests/usb-hcd-ohci-test.c @@ -0,0 +1,41 @@ +/* + * QTest testcase for USB OHCI controller + * + * Copyright (c) 2014 HUAWEI TECHNOLOGIES CO.,LTD. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include <glib.h> +#include <string.h> +#include "libqtest.h" +#include "qemu/osdep.h" +#include "libqos/usb.h" + + +static void test_ohci_init(void) +{ + +} + +static void test_ohci_hotplug(void) +{ + usb_test_hotplug("ohci", 1, NULL); +} + +int main(int argc, char **argv) +{ + int ret; + + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/ohci/pci/init", test_ohci_init); + qtest_add_func("/ohci/pci/hotplug", test_ohci_hotplug); + + qtest_start("-device pci-ohci,id=ohci"); + ret = g_test_run(); + qtest_end(); + + return ret; +} diff --git a/tests/usb-hcd-uhci-test.c b/tests/usb-hcd-uhci-test.c new file mode 100644 index 000000000..8cf2c5bca --- /dev/null +++ b/tests/usb-hcd-uhci-test.c @@ -0,0 +1,96 @@ +/* + * QTest testcase for USB UHCI controller + * + * Copyright (c) 2014 HUAWEI TECHNOLOGIES CO.,LTD. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include <glib.h> +#include <string.h> +#include "libqtest.h" +#include "qemu/osdep.h" +#include "libqos/usb.h" +#include "hw/usb/uhci-regs.h" + + +static void test_uhci_init(void) +{ +} + +static void test_port(int port) +{ + QPCIBus *pcibus; + struct qhc uhci; + + g_assert(port > 0); + pcibus = qpci_init_pc(); + g_assert(pcibus != NULL); + qusb_pci_init_one(pcibus, &uhci, QPCI_DEVFN(0x1d, 0), 4); + uhci_port_test(&uhci, port - 1, UHCI_PORT_CCS); +} + +static void test_port_1(void) +{ + test_port(1); +} + +static void test_port_2(void) +{ + test_port(2); +} + +static void test_uhci_hotplug(void) +{ + usb_test_hotplug("uhci", 2, test_port_2); +} + +static void test_usb_storage_hotplug(void) +{ + QDict *response; + + response = qmp("{'execute': 'device_add'," + " 'arguments': {" + " 'driver': 'usb-storage'," + " 'drive': 'drive0'," + " 'id': 'usbdev0'" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp("{'execute': 'device_del'," + " 'arguments': {" + " 'id': 'usbdev0'" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp(""); + g_assert(response); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); + QDECREF(response); +} + +int main(int argc, char **argv) +{ + int ret; + + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/uhci/pci/init", test_uhci_init); + qtest_add_func("/uhci/pci/port1", test_port_1); + qtest_add_func("/uhci/pci/hotplug", test_uhci_hotplug); + qtest_add_func("/uhci/pci/hotplug/usb-storage", test_usb_storage_hotplug); + + qtest_start("-device piix3-usb-uhci,id=uhci,addr=1d.0" + " -drive id=drive0,if=none,file=/dev/null" + " -device usb-tablet,bus=uhci.0,port=1"); + ret = g_test_run(); + qtest_end(); + + return ret; +} diff --git a/tests/usb-hcd-xhci-test.c b/tests/usb-hcd-xhci-test.c new file mode 100644 index 000000000..b1a7dec5b --- /dev/null +++ b/tests/usb-hcd-xhci-test.c @@ -0,0 +1,99 @@ +/* + * QTest testcase for USB xHCI controller + * + * Copyright (c) 2014 HUAWEI TECHNOLOGIES CO.,LTD. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include <glib.h> +#include <string.h> +#include "libqtest.h" +#include "qemu/osdep.h" +#include "libqos/usb.h" + + +static void test_xhci_init(void) +{ +} + +static void test_xhci_hotplug(void) +{ + usb_test_hotplug("xhci", 1, NULL); +} + +static void test_usb_uas_hotplug(void) +{ + QDict *response; + + response = qmp("{'execute': 'device_add'," + " 'arguments': {" + " 'driver': 'usb-uas'," + " 'id': 'uas'" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp("{'execute': 'device_add'," + " 'arguments': {" + " 'driver': 'scsi-hd'," + " 'drive': 'drive0'," + " 'id': 'scsi-hd'" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + /* TODO: + UAS HBA driver in libqos, to check that + added disk is visible after BUS rescan + */ + + response = qmp("{'execute': 'device_del'," + " 'arguments': {" + " 'id': 'scsi-hd'" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp(""); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); + QDECREF(response); + + + response = qmp("{'execute': 'device_del'," + " 'arguments': {" + " 'id': 'uas'" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp(""); + g_assert(response); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); + QDECREF(response); +} + +int main(int argc, char **argv) +{ + int ret; + + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/xhci/pci/init", test_xhci_init); + qtest_add_func("/xhci/pci/hotplug", test_xhci_hotplug); + qtest_add_func("/xhci/pci/hotplug/usb-uas", test_usb_uas_hotplug); + + qtest_start("-device nec-usb-xhci,id=xhci" + " -drive id=drive0,if=none,file=/dev/null"); + ret = g_test_run(); + qtest_end(); + + return ret; +} diff --git a/tests/virtio-blk-test.c b/tests/virtio-blk-test.c index d53f875b8..ead3911e2 100644 --- a/tests/virtio-blk-test.c +++ b/tests/virtio-blk-test.c @@ -2,6 +2,7 @@ * QTest testcase for VirtIO Block Device * * Copyright (c) 2014 SUSE LINUX Products GmbH + * Copyright (c) 2014 Marc MarĂ * * This work is licensed under the terms of the GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. @@ -9,12 +10,657 @@ #include <glib.h> #include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> #include "libqtest.h" -#include "qemu/osdep.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" +#include "libqos/pci-pc.h" +#include "libqos/malloc.h" +#include "libqos/malloc-pc.h" +#include "qemu/bswap.h" -/* Tests only initialization so far. TODO: Replace with functional tests */ -static void pci_nop(void) +#define QVIRTIO_BLK_F_BARRIER 0x00000001 +#define QVIRTIO_BLK_F_SIZE_MAX 0x00000002 +#define QVIRTIO_BLK_F_SEG_MAX 0x00000004 +#define QVIRTIO_BLK_F_GEOMETRY 0x00000010 +#define QVIRTIO_BLK_F_RO 0x00000020 +#define QVIRTIO_BLK_F_BLK_SIZE 0x00000040 +#define QVIRTIO_BLK_F_SCSI 0x00000080 +#define QVIRTIO_BLK_F_WCE 0x00000200 +#define QVIRTIO_BLK_F_TOPOLOGY 0x00000400 +#define QVIRTIO_BLK_F_CONFIG_WCE 0x00000800 + +#define QVIRTIO_BLK_T_IN 0 +#define QVIRTIO_BLK_T_OUT 1 +#define QVIRTIO_BLK_T_SCSI_CMD 2 +#define QVIRTIO_BLK_T_SCSI_CMD_OUT 3 +#define QVIRTIO_BLK_T_FLUSH 4 +#define QVIRTIO_BLK_T_FLUSH_OUT 5 +#define QVIRTIO_BLK_T_GET_ID 8 + +#define TEST_IMAGE_SIZE (64 * 1024 * 1024) +#define QVIRTIO_BLK_TIMEOUT_US (30 * 1000 * 1000) +#define PCI_SLOT 0x04 +#define PCI_FN 0x00 + +#define PCI_SLOT_HP 0x06 + +typedef struct QVirtioBlkReq { + uint32_t type; + uint32_t ioprio; + uint64_t sector; + char *data; + uint8_t status; +} QVirtioBlkReq; + +static QPCIBus *test_start(void) +{ + char *cmdline; + char tmp_path[] = "/tmp/qtest.XXXXXX"; + int fd, ret; + + /* Create a temporary raw image */ + fd = mkstemp(tmp_path); + g_assert_cmpint(fd, >=, 0); + ret = ftruncate(fd, TEST_IMAGE_SIZE); + g_assert_cmpint(ret, ==, 0); + close(fd); + + cmdline = g_strdup_printf("-drive if=none,id=drive0,file=%s " + "-drive if=none,id=drive1,file=/dev/null " + "-device virtio-blk-pci,id=drv0,drive=drive0," + "addr=%x.%x", + tmp_path, PCI_SLOT, PCI_FN); + qtest_start(cmdline); + unlink(tmp_path); + g_free(cmdline); + + return qpci_init_pc(); +} + +static void test_end(void) { + qtest_end(); +} + +static QVirtioPCIDevice *virtio_blk_init(QPCIBus *bus, int slot) +{ + QVirtioPCIDevice *dev; + + dev = qvirtio_pci_device_find(bus, QVIRTIO_BLK_DEVICE_ID); + g_assert(dev != NULL); + g_assert_cmphex(dev->vdev.device_type, ==, QVIRTIO_BLK_DEVICE_ID); + g_assert_cmphex(dev->pdev->devfn, ==, ((slot << 3) | PCI_FN)); + + qvirtio_pci_device_enable(dev); + qvirtio_reset(&qvirtio_pci, &dev->vdev); + qvirtio_set_acknowledge(&qvirtio_pci, &dev->vdev); + qvirtio_set_driver(&qvirtio_pci, &dev->vdev); + + return dev; +} + +static inline void virtio_blk_fix_request(QVirtioBlkReq *req) +{ +#ifdef HOST_WORDS_BIGENDIAN + bool host_endian = true; +#else + bool host_endian = false; +#endif + + if (qtest_big_endian() != host_endian) { + req->type = bswap32(req->type); + req->ioprio = bswap32(req->ioprio); + req->sector = bswap64(req->sector); + } +} + +static uint64_t virtio_blk_request(QGuestAllocator *alloc, QVirtioBlkReq *req, + uint64_t data_size) +{ + uint64_t addr; + uint8_t status = 0xFF; + + g_assert_cmpuint(data_size % 512, ==, 0); + addr = guest_alloc(alloc, sizeof(*req) + data_size); + + virtio_blk_fix_request(req); + + memwrite(addr, req, 16); + memwrite(addr + 16, req->data, data_size); + memwrite(addr + 16 + data_size, &status, sizeof(status)); + + return addr; +} + +static void pci_basic(void) +{ + QVirtioPCIDevice *dev; + QPCIBus *bus; + QVirtQueuePCI *vqpci; + QGuestAllocator *alloc; + QVirtioBlkReq req; + void *addr; + uint64_t req_addr; + uint64_t capacity; + uint32_t features; + uint32_t free_head; + uint8_t status; + char *data; + + bus = test_start(); + + dev = virtio_blk_init(bus, PCI_SLOT); + + /* MSI-X is not enabled */ + addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_NO_MSIX; + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512); + + features = qvirtio_get_features(&qvirtio_pci, &dev->vdev); + features = features & ~(QVIRTIO_F_BAD_FEATURE | + QVIRTIO_F_RING_INDIRECT_DESC | QVIRTIO_F_RING_EVENT_IDX | + QVIRTIO_BLK_F_SCSI); + qvirtio_set_features(&qvirtio_pci, &dev->vdev, features); + + alloc = pc_alloc_init(); + vqpci = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev, + alloc, 0); + + qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev); + + /* Write and read with 2 descriptor layout */ + /* Write request */ + req.type = QVIRTIO_BLK_T_OUT; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 528, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT_US); + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + guest_free(alloc, req_addr); + + /* Read request */ + req.type = QVIRTIO_BLK_T_IN; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 16, 513, true, false); + + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT_US); + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + data = g_malloc0(512); + memread(req_addr + 16, data, 512); + g_assert_cmpstr(data, ==, "TEST"); + g_free(data); + + guest_free(alloc, req_addr); + + /* Write and read with 3 descriptor layout */ + /* Write request */ + req.type = QVIRTIO_BLK_T_OUT; + req.ioprio = 1; + req.sector = 1; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 16, 512, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false); + + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT_US); + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + guest_free(alloc, req_addr); + + /* Read request */ + req.type = QVIRTIO_BLK_T_IN; + req.ioprio = 1; + req.sector = 1; + req.data = g_malloc0(512); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 16, 512, true, true); + qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false); + + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT_US); + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + data = g_malloc0(512); + memread(req_addr + 16, data, 512); + g_assert_cmpstr(data, ==, "TEST"); + g_free(data); + + guest_free(alloc, req_addr); + + /* End test */ + guest_free(alloc, vqpci->vq.desc); + qvirtio_pci_device_disable(dev); + g_free(dev); + test_end(); +} + +static void pci_indirect(void) +{ + QVirtioPCIDevice *dev; + QPCIBus *bus; + QVirtQueuePCI *vqpci; + QGuestAllocator *alloc; + QVirtioBlkReq req; + QVRingIndirectDesc *indirect; + void *addr; + uint64_t req_addr; + uint64_t capacity; + uint32_t features; + uint32_t free_head; + uint8_t status; + char *data; + + bus = test_start(); + + dev = virtio_blk_init(bus, PCI_SLOT); + + /* MSI-X is not enabled */ + addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_NO_MSIX; + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512); + + features = qvirtio_get_features(&qvirtio_pci, &dev->vdev); + g_assert_cmphex(features & QVIRTIO_F_RING_INDIRECT_DESC, !=, 0); + features = features & ~(QVIRTIO_F_BAD_FEATURE | QVIRTIO_F_RING_EVENT_IDX | + QVIRTIO_BLK_F_SCSI); + qvirtio_set_features(&qvirtio_pci, &dev->vdev, features); + + alloc = pc_alloc_init(); + vqpci = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev, + alloc, 0); + qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev); + + /* Write request */ + req.type = QVIRTIO_BLK_T_OUT; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + indirect = qvring_indirect_desc_setup(&dev->vdev, alloc, 2); + qvring_indirect_desc_add(indirect, req_addr, 528, false); + qvring_indirect_desc_add(indirect, req_addr + 528, 1, true); + free_head = qvirtqueue_add_indirect(&vqpci->vq, indirect); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT_US); + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + g_free(indirect); + guest_free(alloc, req_addr); + + /* Read request */ + req.type = QVIRTIO_BLK_T_IN; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + indirect = qvring_indirect_desc_setup(&dev->vdev, alloc, 2); + qvring_indirect_desc_add(indirect, req_addr, 16, false); + qvring_indirect_desc_add(indirect, req_addr + 16, 513, true); + free_head = qvirtqueue_add_indirect(&vqpci->vq, indirect); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT_US); + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + data = g_malloc0(512); + memread(req_addr + 16, data, 512); + g_assert_cmpstr(data, ==, "TEST"); + g_free(data); + + g_free(indirect); + guest_free(alloc, req_addr); + + /* End test */ + guest_free(alloc, vqpci->vq.desc); + qvirtio_pci_device_disable(dev); + g_free(dev); + test_end(); +} + +static void pci_config(void) +{ + QVirtioPCIDevice *dev; + QPCIBus *bus; + int n_size = TEST_IMAGE_SIZE / 2; + void *addr; + uint64_t capacity; + + bus = test_start(); + + dev = virtio_blk_init(bus, PCI_SLOT); + + /* MSI-X is not enabled */ + addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_NO_MSIX; + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512); + + qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev); + + qmp("{ 'execute': 'block_resize', 'arguments': { 'device': 'drive0', " + " 'size': %d } }", n_size); + qvirtio_wait_config_isr(&qvirtio_pci, &dev->vdev, QVIRTIO_BLK_TIMEOUT_US); + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, n_size / 512); + + qvirtio_pci_device_disable(dev); + g_free(dev); + test_end(); +} + +static void pci_msix(void) +{ + QVirtioPCIDevice *dev; + QPCIBus *bus; + QVirtQueuePCI *vqpci; + QGuestAllocator *alloc; + QVirtioBlkReq req; + int n_size = TEST_IMAGE_SIZE / 2; + void *addr; + uint64_t req_addr; + uint64_t capacity; + uint32_t features; + uint32_t free_head; + uint8_t status; + char *data; + + bus = test_start(); + alloc = pc_alloc_init(); + + dev = virtio_blk_init(bus, PCI_SLOT); + qpci_msix_enable(dev->pdev); + + qvirtio_pci_set_msix_configuration_vector(dev, alloc, 0); + + /* MSI-X is enabled */ + addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_MSIX; + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512); + + features = qvirtio_get_features(&qvirtio_pci, &dev->vdev); + features = features & ~(QVIRTIO_F_BAD_FEATURE | + QVIRTIO_F_RING_INDIRECT_DESC | + QVIRTIO_F_RING_EVENT_IDX | QVIRTIO_BLK_F_SCSI); + qvirtio_set_features(&qvirtio_pci, &dev->vdev, features); + + vqpci = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev, + alloc, 0); + qvirtqueue_pci_msix_setup(dev, vqpci, alloc, 1); + + qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev); + + qmp("{ 'execute': 'block_resize', 'arguments': { 'device': 'drive0', " + " 'size': %d } }", n_size); + + qvirtio_wait_config_isr(&qvirtio_pci, &dev->vdev, QVIRTIO_BLK_TIMEOUT_US); + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, n_size / 512); + + /* Write request */ + req.type = QVIRTIO_BLK_T_OUT; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 528, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT_US); + + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + guest_free(alloc, req_addr); + + /* Read request */ + req.type = QVIRTIO_BLK_T_IN; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 16, 513, true, false); + + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + + qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT_US); + + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + data = g_malloc0(512); + memread(req_addr + 16, data, 512); + g_assert_cmpstr(data, ==, "TEST"); + g_free(data); + + guest_free(alloc, req_addr); + + /* End test */ + guest_free(alloc, (uint64_t)vqpci->vq.desc); + qpci_msix_disable(dev->pdev); + qvirtio_pci_device_disable(dev); + g_free(dev); + test_end(); +} + +static void pci_idx(void) +{ + QVirtioPCIDevice *dev; + QPCIBus *bus; + QVirtQueuePCI *vqpci; + QGuestAllocator *alloc; + QVirtioBlkReq req; + void *addr; + uint64_t req_addr; + uint64_t capacity; + uint32_t features; + uint32_t free_head; + uint8_t status; + char *data; + + bus = test_start(); + alloc = pc_alloc_init(); + + dev = virtio_blk_init(bus, PCI_SLOT); + qpci_msix_enable(dev->pdev); + + qvirtio_pci_set_msix_configuration_vector(dev, alloc, 0); + + /* MSI-X is enabled */ + addr = dev->addr + QVIRTIO_DEVICE_SPECIFIC_MSIX; + + capacity = qvirtio_config_readq(&qvirtio_pci, &dev->vdev, addr); + g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512); + + features = qvirtio_get_features(&qvirtio_pci, &dev->vdev); + features = features & ~(QVIRTIO_F_BAD_FEATURE | + QVIRTIO_F_RING_INDIRECT_DESC | + QVIRTIO_F_NOTIFY_ON_EMPTY | QVIRTIO_BLK_F_SCSI); + qvirtio_set_features(&qvirtio_pci, &dev->vdev, features); + + vqpci = (QVirtQueuePCI *)qvirtqueue_setup(&qvirtio_pci, &dev->vdev, + alloc, 0); + qvirtqueue_pci_msix_setup(dev, vqpci, alloc, 1); + + qvirtio_set_driver_ok(&qvirtio_pci, &dev->vdev); + + /* Write request */ + req.type = QVIRTIO_BLK_T_OUT; + req.ioprio = 1; + req.sector = 0; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 528, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT_US); + + /* Write request */ + req.type = QVIRTIO_BLK_T_OUT; + req.ioprio = 1; + req.sector = 1; + req.data = g_malloc0(512); + strcpy(req.data, "TEST"); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + /* Notify after processing the third request */ + qvirtqueue_set_used_event(&vqpci->vq, 2); + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 528, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 528, 1, true, false); + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + /* No notification expected */ + status = qvirtio_wait_status_byte_no_isr(&qvirtio_pci, &dev->vdev, + &vqpci->vq, req_addr + 528, + QVIRTIO_BLK_TIMEOUT_US); + g_assert_cmpint(status, ==, 0); + + guest_free(alloc, req_addr); + + /* Read request */ + req.type = QVIRTIO_BLK_T_IN; + req.ioprio = 1; + req.sector = 1; + req.data = g_malloc0(512); + + req_addr = virtio_blk_request(alloc, &req, 512); + + g_free(req.data); + + free_head = qvirtqueue_add(&vqpci->vq, req_addr, 16, false, true); + qvirtqueue_add(&vqpci->vq, req_addr + 16, 513, true, false); + + qvirtqueue_kick(&qvirtio_pci, &dev->vdev, &vqpci->vq, free_head); + + + qvirtio_wait_queue_isr(&qvirtio_pci, &dev->vdev, &vqpci->vq, + QVIRTIO_BLK_TIMEOUT_US); + + status = readb(req_addr + 528); + g_assert_cmpint(status, ==, 0); + + data = g_malloc0(512); + memread(req_addr + 16, data, 512); + g_assert_cmpstr(data, ==, "TEST"); + g_free(data); + + guest_free(alloc, req_addr); + + /* End test */ + guest_free(alloc, vqpci->vq.desc); + qpci_msix_disable(dev->pdev); + qvirtio_pci_device_disable(dev); + g_free(dev); + test_end(); +} + +static void hotplug(void) +{ + QPCIBus *bus; + QVirtioPCIDevice *dev; + + bus = test_start(); + + /* plug secondary disk */ + qpci_plug_device_test("virtio-blk-pci", "drv1", PCI_SLOT_HP, + "'drive': 'drive1'"); + + dev = virtio_blk_init(bus, PCI_SLOT_HP); + g_assert(dev); + qvirtio_pci_device_disable(dev); + g_free(dev); + + /* unplug secondary disk */ + qpci_unplug_acpi_device_test("drv1", PCI_SLOT_HP); + test_end(); } int main(int argc, char **argv) @@ -22,13 +668,15 @@ int main(int argc, char **argv) int ret; g_test_init(&argc, &argv, NULL); - qtest_add_func("/virtio/blk/pci/nop", pci_nop); - qtest_start("-drive id=drv0,if=none,file=/dev/null " - "-device virtio-blk-pci,drive=drv0"); - ret = g_test_run(); + g_test_add_func("/virtio/blk/pci/basic", pci_basic); + g_test_add_func("/virtio/blk/pci/indirect", pci_indirect); + g_test_add_func("/virtio/blk/pci/config", pci_config); + g_test_add_func("/virtio/blk/pci/msix", pci_msix); + g_test_add_func("/virtio/blk/pci/idx", pci_idx); + g_test_add_func("/virtio/blk/pci/hotplug", hotplug); - qtest_end(); + ret = g_test_run(); return ret; } diff --git a/tests/virtio-net-test.c b/tests/virtio-net-test.c index df9934323..ea7478c27 100644 --- a/tests/virtio-net-test.c +++ b/tests/virtio-net-test.c @@ -11,18 +11,28 @@ #include <string.h> #include "libqtest.h" #include "qemu/osdep.h" +#include "libqos/pci.h" + +#define PCI_SLOT_HP 0x06 /* Tests only initialization so far. TODO: Replace with functional tests */ static void pci_nop(void) { } +static void hotplug(void) +{ + qpci_plug_device_test("virtio-net-pci", "net1", PCI_SLOT_HP, NULL); + qpci_unplug_acpi_device_test("net1", PCI_SLOT_HP); +} + int main(int argc, char **argv) { int ret; g_test_init(&argc, &argv, NULL); qtest_add_func("/virtio/net/pci/nop", pci_nop); + qtest_add_func("/virtio/net/pci/hotplug", hotplug); qtest_start("-device virtio-net-pci"); ret = g_test_run(); diff --git a/tests/virtio-rng-test.c b/tests/virtio-rng-test.c index 402c2060d..41c1cdb1a 100644 --- a/tests/virtio-rng-test.c +++ b/tests/virtio-rng-test.c @@ -11,18 +11,28 @@ #include <string.h> #include "libqtest.h" #include "qemu/osdep.h" +#include "libqos/pci.h" + +#define PCI_SLOT_HP 0x06 /* Tests only initialization so far. TODO: Replace with functional tests */ static void pci_nop(void) { } +static void hotplug(void) +{ + qpci_plug_device_test("virtio-rng-pci", "rng1", PCI_SLOT_HP, NULL); + qpci_unplug_acpi_device_test("rng1", PCI_SLOT_HP); +} + int main(int argc, char **argv) { int ret; g_test_init(&argc, &argv, NULL); qtest_add_func("/virtio/rng/pci/nop", pci_nop); + qtest_add_func("/virtio/rng/pci/hotplug", hotplug); qtest_start("-device virtio-rng-pci"); ret = g_test_run(); diff --git a/tests/virtio-scsi-test.c b/tests/virtio-scsi-test.c index 3230908b9..41f9602e4 100644 --- a/tests/virtio-scsi-test.c +++ b/tests/virtio-scsi-test.c @@ -17,14 +17,43 @@ static void pci_nop(void) { } +static void hotplug(void) +{ + QDict *response; + + response = qmp("{\"execute\": \"device_add\"," + " \"arguments\": {" + " \"driver\": \"scsi-hd\"," + " \"id\": \"scsi-hd\"," + " \"drive\": \"drv1\"" + "}}"); + + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp("{\"execute\": \"device_del\"," + " \"arguments\": {" + " \"id\": \"scsi-hd\"" + "}}"); + + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); + QDECREF(response); +} + int main(int argc, char **argv) { int ret; g_test_init(&argc, &argv, NULL); qtest_add_func("/virtio/scsi/pci/nop", pci_nop); + qtest_add_func("/virtio/scsi/pci/hotplug", hotplug); qtest_start("-drive id=drv0,if=none,file=/dev/null " + "-drive id=drv1,if=none,file=/dev/null " "-device virtio-scsi-pci,id=vscsi0 " "-device scsi-hd,bus=vscsi0.0,drive=drv0"); ret = g_test_run(); diff --git a/tests/virtio-serial-test.c b/tests/virtio-serial-test.c index e7438751e..bf030a616 100644 --- a/tests/virtio-serial-test.c +++ b/tests/virtio-serial-test.c @@ -17,12 +17,39 @@ static void pci_nop(void) { } +static void hotplug(void) +{ + QDict *response; + + response = qmp("{\"execute\": \"device_add\"," + " \"arguments\": {" + " \"driver\": \"virtserialport\"," + " \"id\": \"hp-port\"" + "}}"); + + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + QDECREF(response); + + response = qmp("{\"execute\": \"device_del\"," + " \"arguments\": {" + " \"id\": \"hp-port\"" + "}}"); + + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + g_assert(qdict_haskey(response, "event")); + g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); + QDECREF(response); +} + int main(int argc, char **argv) { int ret; g_test_init(&argc, &argv, NULL); qtest_add_func("/virtio/serial/pci/nop", pci_nop); + qtest_add_func("/virtio/serial/pci/hotplug", hotplug); qtest_start("-device virtio-serial-pci"); ret = g_test_run(); |