699 changes: 569 additions & 130 deletions hw/vfio/common.c

Large diffs are not rendered by default.

28 changes: 24 additions & 4 deletions hw/vfio/migration.c
Expand Up @@ -521,7 +521,7 @@ static void vfio_migration_state_notifier(Notifier *notifier, void *data)
}
}

static void vfio_migration_exit(VFIODevice *vbasedev)
static void vfio_migration_free(VFIODevice *vbasedev)
{
g_free(vbasedev->migration);
vbasedev->migration = NULL;
Expand Down Expand Up @@ -555,6 +555,19 @@ static int vfio_migration_query_flags(VFIODevice *vbasedev, uint64_t *mig_flags)
return 0;
}

static bool vfio_dma_logging_supported(VFIODevice *vbasedev)
{
uint64_t buf[DIV_ROUND_UP(sizeof(struct vfio_device_feature),
sizeof(uint64_t))] = {};
struct vfio_device_feature *feature = (struct vfio_device_feature *)buf;

feature->argsz = sizeof(buf);
feature->flags = VFIO_DEVICE_FEATURE_PROBE |
VFIO_DEVICE_FEATURE_DMA_LOGGING_START;

return !ioctl(vbasedev->fd, VFIO_DEVICE_FEATURE, feature);
}

static int vfio_migration_init(VFIODevice *vbasedev)
{
int ret;
Expand Down Expand Up @@ -589,6 +602,8 @@ static int vfio_migration_init(VFIODevice *vbasedev)
migration->device_state = VFIO_DEVICE_STATE_RUNNING;
migration->data_fd = -1;

vbasedev->dirty_pages_supported = vfio_dma_logging_supported(vbasedev);

oid = vmstate_if_get_id(VMSTATE_IF(DEVICE(obj)));
if (oid) {
path = g_strdup_printf("%s/vfio", oid);
Expand Down Expand Up @@ -616,7 +631,7 @@ int64_t vfio_mig_bytes_transferred(void)
return bytes_transferred;
}

int vfio_migration_probe(VFIODevice *vbasedev, Error **errp)
int vfio_migration_realize(VFIODevice *vbasedev, Error **errp)
{
int ret = -ENOTSUP;

Expand All @@ -634,6 +649,11 @@ int vfio_migration_probe(VFIODevice *vbasedev, Error **errp)
return ret;
}

ret = vfio_block_giommu_migration(errp);
if (ret) {
return ret;
}

trace_vfio_migration_probe(vbasedev->name);
return 0;

Expand All @@ -649,15 +669,15 @@ int vfio_migration_probe(VFIODevice *vbasedev, Error **errp)
return ret;
}

void vfio_migration_finalize(VFIODevice *vbasedev)
void vfio_migration_exit(VFIODevice *vbasedev)
{
if (vbasedev->migration) {
VFIOMigration *migration = vbasedev->migration;

remove_migration_state_change_notifier(&migration->migration_state);
qemu_del_vm_change_state_handler(migration->vm_state);
unregister_savevm(VMSTATE_IF(vbasedev->dev), "vfio", vbasedev);
vfio_migration_exit(vbasedev);
vfio_migration_free(vbasedev);
vfio_unblock_multiple_devices_migration();
}

Expand Down
5 changes: 3 additions & 2 deletions hw/vfio/pci.c
Expand Up @@ -3145,7 +3145,7 @@ static void vfio_realize(PCIDevice *pdev, Error **errp)
}

if (!pdev->failover_pair_id) {
ret = vfio_migration_probe(vbasedev, errp);
ret = vfio_migration_realize(vbasedev, errp);
if (ret) {
error_report("%s: Migration disabled", vbasedev->name);
}
Expand Down Expand Up @@ -3185,6 +3185,7 @@ static void vfio_instance_finalize(Object *obj)
*/
vfio_put_device(vdev);
vfio_put_group(group);
vfio_migration_finalize();
}

static void vfio_exitfn(PCIDevice *pdev)
Expand All @@ -3203,7 +3204,7 @@ static void vfio_exitfn(PCIDevice *pdev)
}
vfio_teardown_msi(vdev);
vfio_bars_exit(vdev);
vfio_migration_finalize(&vdev->vbasedev);
vfio_migration_exit(&vdev->vbasedev);
}

static void vfio_pci_reset(DeviceState *dev)
Expand Down
7 changes: 4 additions & 3 deletions hw/vfio/trace-events
Expand Up @@ -96,14 +96,15 @@ vfio_pci_igd_lpc_bridge_enabled(const char *name) "%s"
vfio_region_write(const char *name, int index, uint64_t addr, uint64_t data, unsigned size) " (%s:region%d+0x%"PRIx64", 0x%"PRIx64 ", %d)"
vfio_region_read(char *name, int index, uint64_t addr, unsigned size, uint64_t data) " (%s:region%d+0x%"PRIx64", %d) = 0x%"PRIx64
vfio_iommu_map_notify(const char *op, uint64_t iova_start, uint64_t iova_end) "iommu %s @ 0x%"PRIx64" - 0x%"PRIx64
vfio_listener_region_add_skip(uint64_t start, uint64_t end) "SKIPPING region_add 0x%"PRIx64" - 0x%"PRIx64
vfio_listener_region_skip(const char *name, uint64_t start, uint64_t end) "SKIPPING %s 0x%"PRIx64" - 0x%"PRIx64
vfio_spapr_group_attach(int groupfd, int tablefd) "Attached groupfd %d to liobn fd %d"
vfio_listener_region_add_iommu(uint64_t start, uint64_t end) "region_add [iommu] 0x%"PRIx64" - 0x%"PRIx64
vfio_listener_region_add_ram(uint64_t iova_start, uint64_t iova_end, void *vaddr) "region_add [ram] 0x%"PRIx64" - 0x%"PRIx64" [%p]"
vfio_known_safe_misalignment(const char *name, uint64_t iova, uint64_t offset_within_region, uintptr_t page_size) "Region \"%s\" iova=0x%"PRIx64" offset_within_region=0x%"PRIx64" qemu_real_host_page_size=0x%"PRIxPTR
vfio_listener_region_add_no_dma_map(const char *name, uint64_t iova, uint64_t size, uint64_t page_size) "Region \"%s\" 0x%"PRIx64" size=0x%"PRIx64" is not aligned to 0x%"PRIx64" and cannot be mapped for DMA"
vfio_listener_region_del_skip(uint64_t start, uint64_t end) "SKIPPING region_del 0x%"PRIx64" - 0x%"PRIx64
vfio_listener_region_del(uint64_t start, uint64_t end) "region_del 0x%"PRIx64" - 0x%"PRIx64
vfio_device_dirty_tracking_update(uint64_t start, uint64_t end, uint64_t min, uint64_t max) "section 0x%"PRIx64" - 0x%"PRIx64" -> update [0x%"PRIx64" - 0x%"PRIx64"]"
vfio_device_dirty_tracking_start(int nr_ranges, uint64_t min32, uint64_t max32, uint64_t min64, uint64_t max64) "nr_ranges %d 32:[0x%"PRIx64" - 0x%"PRIx64"], 64:[0x%"PRIx64" - 0x%"PRIx64"]"
vfio_disconnect_container(int fd) "close container->fd=%d"
vfio_put_group(int fd) "close group->fd=%d"
vfio_get_device(const char * name, unsigned int flags, unsigned int num_regions, unsigned int num_irqs) "Device %s flags: %u, regions: %u, irqs: %u"
Expand All @@ -117,7 +118,7 @@ vfio_region_mmaps_set_enabled(const char *name, bool enabled) "Region %s mmaps e
vfio_region_unmap(const char *name, unsigned long offset, unsigned long end) "Region %s unmap [0x%lx - 0x%lx]"
vfio_region_sparse_mmap_header(const char *name, int index, int nr_areas) "Device %s region %d: %d sparse mmap entries"
vfio_region_sparse_mmap_entry(int i, unsigned long start, unsigned long end) "sparse entry %d [0x%lx - 0x%lx]"
vfio_get_dev_region(const char *name, int index, uint32_t type, uint32_t subtype) "%s index %d, %08x/%0x8"
vfio_get_dev_region(const char *name, int index, uint32_t type, uint32_t subtype) "%s index %d, %08x/%08x"
vfio_dma_unmap_overflow_workaround(void) ""
vfio_get_dirty_bitmap(int fd, uint64_t iova, uint64_t size, uint64_t bitmap_size, uint64_t start) "container fd=%d, iova=0x%"PRIx64" size= 0x%"PRIx64" bitmap_size=0x%"PRIx64" start=0x%"PRIx64
vfio_iommu_map_dirty_notify(uint64_t iova_start, uint64_t iova_end) "iommu dirty @ 0x%"PRIx64" - 0x%"PRIx64
Expand Down
2 changes: 0 additions & 2 deletions include/hw/i386/x86.h
Expand Up @@ -18,10 +18,8 @@
#define HW_I386_X86_H

#include "exec/hwaddr.h"
#include "qemu/notify.h"

#include "hw/boards.h"
#include "hw/nmi.h"
#include "hw/intc/ioapic.h"
#include "hw/isa/isa.h"
#include "qom/object.h"
Expand Down
8 changes: 6 additions & 2 deletions include/hw/vfio/vfio-common.h
Expand Up @@ -143,6 +143,8 @@ typedef struct VFIODevice {
VFIOMigration *migration;
Error *migration_blocker;
OnOffAuto pre_copy_dirty_page_tracking;
bool dirty_pages_supported;
bool dirty_tracking;
} VFIODevice;

struct VFIODeviceOps {
Expand Down Expand Up @@ -220,6 +222,7 @@ extern VFIOGroupList vfio_group_list;
bool vfio_mig_active(void);
int vfio_block_multiple_devices_migration(Error **errp);
void vfio_unblock_multiple_devices_migration(void);
int vfio_block_giommu_migration(Error **errp);
int64_t vfio_mig_bytes_transferred(void);

#ifdef CONFIG_LINUX
Expand All @@ -243,7 +246,8 @@ int vfio_spapr_create_window(VFIOContainer *container,
int vfio_spapr_remove_window(VFIOContainer *container,
hwaddr offset_within_address_space);

int vfio_migration_probe(VFIODevice *vbasedev, Error **errp);
void vfio_migration_finalize(VFIODevice *vbasedev);
int vfio_migration_realize(VFIODevice *vbasedev, Error **errp);
void vfio_migration_exit(VFIODevice *vbasedev);
void vfio_migration_finalize(void);

#endif /* HW_VFIO_VFIO_COMMON_H */
Binary file modified pc-bios/s390-ccw.img
Binary file not shown.
157 changes: 121 additions & 36 deletions pc-bios/s390-ccw/bootmap.c
Expand Up @@ -72,42 +72,74 @@ static inline void verify_boot_info(BootInfo *bip)
"Bad block size in zIPL section of the 1st record.");
}

static block_number_t eckd_block_num(EckdCHS *chs)
static void eckd_format_chs(ExtEckdBlockPtr *ptr, bool ldipl,
uint64_t *c,
uint64_t *h,
uint64_t *s)
{
if (ldipl) {
*c = ptr->ldptr.chs.cylinder;
*h = ptr->ldptr.chs.head;
*s = ptr->ldptr.chs.sector;
} else {
*c = ptr->bptr.chs.cylinder;
*h = ptr->bptr.chs.head;
*s = ptr->bptr.chs.sector;
}
}

static block_number_t eckd_chs_to_block(uint64_t c, uint64_t h, uint64_t s)
{
const uint64_t sectors = virtio_get_sectors();
const uint64_t heads = virtio_get_heads();
const uint64_t cylinder = chs->cylinder
+ ((chs->head & 0xfff0) << 12);
const uint64_t head = chs->head & 0x000f;
const uint64_t cylinder = c + ((h & 0xfff0) << 12);
const uint64_t head = h & 0x000f;
const block_number_t block = sectors * heads * cylinder
+ sectors * head
+ chs->sector
- 1; /* block nr starts with zero */
+ s - 1; /* block nr starts with zero */
return block;
}

static bool eckd_valid_address(BootMapPointer *p)
static block_number_t eckd_block_num(EckdCHS *chs)
{
const uint64_t head = p->eckd.chs.head & 0x000f;
return eckd_chs_to_block(chs->cylinder, chs->head, chs->sector);
}

static block_number_t gen_eckd_block_num(ExtEckdBlockPtr *ptr, bool ldipl)
{
uint64_t cyl, head, sec;
eckd_format_chs(ptr, ldipl, &cyl, &head, &sec);
return eckd_chs_to_block(cyl, head, sec);
}

static bool eckd_valid_chs(uint64_t cyl, uint64_t head, uint64_t sector)
{
if (head >= virtio_get_heads()
|| p->eckd.chs.sector > virtio_get_sectors()
|| p->eckd.chs.sector <= 0) {
|| sector > virtio_get_sectors()
|| sector <= 0) {
return false;
}

if (!virtio_guessed_disk_nature() &&
eckd_block_num(&p->eckd.chs) >= virtio_get_blocks()) {
eckd_chs_to_block(cyl, head, sector) >= virtio_get_blocks()) {
return false;
}

return true;
}

static block_number_t load_eckd_segments(block_number_t blk, uint64_t *address)
static bool eckd_valid_address(ExtEckdBlockPtr *ptr, bool ldipl)
{
uint64_t cyl, head, sec;
eckd_format_chs(ptr, ldipl, &cyl, &head, &sec);
return eckd_valid_chs(cyl, head, sec);
}

static block_number_t load_eckd_segments(block_number_t blk, bool ldipl,
uint64_t *address)
{
block_number_t block_nr;
int j, rc;
int j, rc, count;
BootMapPointer *bprs = (void *)_bprs;
bool more_data;

Expand All @@ -117,7 +149,7 @@ static block_number_t load_eckd_segments(block_number_t blk, uint64_t *address)
do {
more_data = false;
for (j = 0;; j++) {
block_nr = eckd_block_num(&bprs[j].xeckd.bptr.chs);
block_nr = gen_eckd_block_num(&bprs[j].xeckd, ldipl);
if (is_null_block_number(block_nr)) { /* end of chunk */
break;
}
Expand All @@ -129,11 +161,26 @@ static block_number_t load_eckd_segments(block_number_t blk, uint64_t *address)
break;
}

IPL_assert(block_size_ok(bprs[j].xeckd.bptr.size),
/* List directed pointer does not store block size */
IPL_assert(ldipl || block_size_ok(bprs[j].xeckd.bptr.size),
"bad chunk block size");
IPL_assert(eckd_valid_address(&bprs[j]), "bad chunk ECKD addr");

if ((bprs[j].xeckd.bptr.count == 0) && unused_space(&(bprs[j+1]),
if (!eckd_valid_address(&bprs[j].xeckd, ldipl)) {
/*
* If an invalid address is found during LD-IPL then break and
* retry as CCW
*/
IPL_assert(ldipl, "bad chunk ECKD addr");
break;
}

if (ldipl) {
count = bprs[j].xeckd.ldptr.count;
} else {
count = bprs[j].xeckd.bptr.count;
}

if (count == 0 && unused_space(&bprs[j + 1],
sizeof(EckdBlockPtr))) {
/* This is a "continue" pointer.
* This ptr should be the last one in the current
Expand All @@ -149,11 +196,10 @@ static block_number_t load_eckd_segments(block_number_t blk, uint64_t *address)
/* Load (count+1) blocks of code at (block_nr)
* to memory (address).
*/
rc = virtio_read_many(block_nr, (void *)(*address),
bprs[j].xeckd.bptr.count+1);
rc = virtio_read_many(block_nr, (void *)(*address), count + 1);
IPL_assert(rc == 0, "code chunk read failed");

*address += (bprs[j].xeckd.bptr.count+1) * virtio_get_block_size();
*address += (count + 1) * virtio_get_block_size();
}
} while (more_data);
return block_nr;
Expand Down Expand Up @@ -237,8 +283,10 @@ static void run_eckd_boot_script(block_number_t bmt_block_nr,
uint64_t address;
BootMapTable *bmt = (void *)sec;
BootMapScript *bms = (void *)sec;
/* The S1B block number is NULL_BLOCK_NR if and only if it's an LD-IPL */
bool ldipl = (s1b_block_nr == NULL_BLOCK_NR);

if (menu_is_enabled_zipl()) {
if (menu_is_enabled_zipl() && !ldipl) {
loadparm = eckd_get_boot_menu_index(s1b_block_nr);
}

Expand All @@ -249,7 +297,7 @@ static void run_eckd_boot_script(block_number_t bmt_block_nr,
memset(sec, FREE_SPACE_FILLER, sizeof(sec));
read_block(bmt_block_nr, sec, "Cannot read Boot Map Table");

block_nr = eckd_block_num(&bmt->entry[loadparm].xeckd.bptr.chs);
block_nr = gen_eckd_block_num(&bmt->entry[loadparm].xeckd, ldipl);
IPL_assert(block_nr != -1, "Cannot find Boot Map Table Entry");

memset(sec, FREE_SPACE_FILLER, sizeof(sec));
Expand All @@ -264,13 +312,18 @@ static void run_eckd_boot_script(block_number_t bmt_block_nr,
}

address = bms->entry[i].address.load_address;
block_nr = eckd_block_num(&bms->entry[i].blkptr.xeckd.bptr.chs);
block_nr = gen_eckd_block_num(&bms->entry[i].blkptr.xeckd, ldipl);

do {
block_nr = load_eckd_segments(block_nr, &address);
block_nr = load_eckd_segments(block_nr, ldipl, &address);
} while (block_nr != -1);
}

if (ldipl && bms->entry[i].type != BOOT_SCRIPT_EXEC) {
/* Abort LD-IPL and retry as CCW-IPL */
return;
}

IPL_assert(bms->entry[i].type == BOOT_SCRIPT_EXEC,
"Unknown script entry type");
write_reset_psw(bms->entry[i].address.load_address); /* no return */
Expand Down Expand Up @@ -380,6 +433,23 @@ static void ipl_eckd_ldl(ECKD_IPL_mode_t mode)
/* no return */
}

static block_number_t eckd_find_bmt(ExtEckdBlockPtr *ptr)
{
block_number_t blockno;
uint8_t tmp_sec[MAX_SECTOR_SIZE];
BootRecord *br;

blockno = gen_eckd_block_num(ptr, 0);
read_block(blockno, tmp_sec, "Cannot read boot record");
br = (BootRecord *)tmp_sec;
if (!magic_match(br->magic, ZIPL_MAGIC)) {
/* If the boot record is invalid, return and try CCW-IPL instead */
return NULL_BLOCK_NR;
}

return gen_eckd_block_num(&br->pgt.xeckd, 1);
}

static void print_eckd_msg(void)
{
char msg[] = "Using ECKD scheme (block size *****), ";
Expand All @@ -401,28 +471,43 @@ static void print_eckd_msg(void)

static void ipl_eckd(void)
{
XEckdMbr *mbr = (void *)sec;
LDL_VTOC *vlbl = (void *)sec;
IplVolumeLabel *vlbl = (void *)sec;
LDL_VTOC *vtoc = (void *)sec;
block_number_t ldipl_bmt; /* Boot Map Table for List-Directed IPL */

print_eckd_msg();

/* Grab the MBR again */
/* Block 2 can contain either the CDL VOL1 label or the LDL VTOC */
memset(sec, FREE_SPACE_FILLER, sizeof(sec));
read_block(0, mbr, "Cannot read block 0 on DASD");
read_block(2, vlbl, "Cannot read block 2");

if (magic_match(mbr->magic, IPL1_MAGIC)) {
ipl_eckd_cdl(); /* only returns in case of error */
return;
/*
* First check for a list-directed-format pointer which would
* supersede the CCW pointer.
*/
if (eckd_valid_address((ExtEckdBlockPtr *)&vlbl->f.br, 0)) {
ldipl_bmt = eckd_find_bmt((ExtEckdBlockPtr *)&vlbl->f.br);
if (ldipl_bmt) {
sclp_print("List-Directed\n");
/* LD-IPL does not use the S1B bock, just make it NULL */
run_eckd_boot_script(ldipl_bmt, NULL_BLOCK_NR);
/* Only return in error, retry as CCW-IPL */
sclp_print("Retrying IPL ");
print_eckd_msg();
}
memset(sec, FREE_SPACE_FILLER, sizeof(sec));
read_block(2, vtoc, "Cannot read block 2");
}

/* LDL/CMS? */
memset(sec, FREE_SPACE_FILLER, sizeof(sec));
read_block(2, vlbl, "Cannot read block 2");
/* Not list-directed */
if (magic_match(vtoc->magic, VOL1_MAGIC)) {
ipl_eckd_cdl(); /* may return in error */
}

if (magic_match(vlbl->magic, CMS1_MAGIC)) {
if (magic_match(vtoc->magic, CMS1_MAGIC)) {
ipl_eckd_ldl(ECKD_CMS); /* no return */
}
if (magic_match(vlbl->magic, LNX1_MAGIC)) {
if (magic_match(vtoc->magic, LNX1_MAGIC)) {
ipl_eckd_ldl(ECKD_LDL); /* no return */
}

Expand Down
30 changes: 27 additions & 3 deletions pc-bios/s390-ccw/bootmap.h
Expand Up @@ -45,9 +45,23 @@ typedef struct EckdBlockPtr {
* it's 0 for TablePtr, ScriptPtr, and SectionPtr */
} __attribute__ ((packed)) EckdBlockPtr;

typedef struct ExtEckdBlockPtr {
typedef struct LdEckdCHS {
uint32_t cylinder;
uint8_t head;
uint8_t sector;
} __attribute__ ((packed)) LdEckdCHS;

typedef struct LdEckdBlockPtr {
LdEckdCHS chs; /* cylinder/head/sector is an address of the block */
uint8_t reserved[4];
uint16_t count;
uint32_t pad;
} __attribute__ ((packed)) LdEckdBlockPtr;

/* bptr is used for CCW type IPL, while ldptr is for list-directed IPL */
typedef union ExtEckdBlockPtr {
EckdBlockPtr bptr;
uint8_t reserved[8];
LdEckdBlockPtr ldptr;
} __attribute__ ((packed)) ExtEckdBlockPtr;

typedef union BootMapPointer {
Expand All @@ -57,6 +71,15 @@ typedef union BootMapPointer {
ExtEckdBlockPtr xeckd;
} __attribute__ ((packed)) BootMapPointer;

typedef struct BootRecord {
uint8_t magic[4];
uint32_t version;
uint64_t res1;
BootMapPointer pgt;
uint8_t reserved[510 - 32];
uint16_t os_id;
} __attribute__ ((packed)) BootRecord;

/* aka Program Table */
typedef struct BootMapTable {
uint8_t magic[4];
Expand Down Expand Up @@ -292,7 +315,8 @@ typedef struct IplVolumeLabel {
struct {
unsigned char key[4]; /* == "VOL1" */
unsigned char volser[6];
unsigned char reserved[6];
unsigned char reserved[64];
EckdCHS br; /* Location of Boot Record for list-directed IPL */
} f;
};
} __attribute__((packed)) IplVolumeLabel;
Expand Down
2 changes: 1 addition & 1 deletion target/hexagon/meson.build
Expand Up @@ -183,7 +183,7 @@ if idef_parser_enabled and 'hexagon-linux-user' in target_dirs
)

bison = generator(
find_program('bison'),
find_program('bison', version: '>=3.0'),
output: ['@BASENAME@.tab.c', '@BASENAME@.tab.h'],
arguments: ['@INPUT@', '--defines=@OUTPUT1@', '--output=@OUTPUT0@']
)
Expand Down
10 changes: 6 additions & 4 deletions tests/qtest/meson.build
Expand Up @@ -309,10 +309,12 @@ qtests = {
'netdev-socket': files('netdev-socket.c', '../unit/socket-helpers.c'),
}

gvnc = dependency('gvnc-1.0', required: false)
if gvnc.found()
qtests += {'vnc-display-test': [gvnc]}
qtests_generic += [ 'vnc-display-test' ]
if vnc.found()
gvnc = dependency('gvnc-1.0', required: false)
if gvnc.found()
qtests += {'vnc-display-test': [gvnc]}
qtests_generic += [ 'vnc-display-test' ]
endif
endif

if dbus_display
Expand Down
36 changes: 29 additions & 7 deletions tests/qtest/readconfig-test.c
Expand Up @@ -124,13 +124,15 @@ static void test_spice(void)
}
#endif

static void test_object_rng_resp(QObject *res)
static void test_object_available(QObject *res, const char *name,
const char *type)
{
Visitor *v;
g_autoptr(ObjectPropertyInfoList) objs = NULL;
ObjectPropertyInfoList *tmp;
ObjectPropertyInfo *obj;
bool seen_rng = false;
bool object_available = false;
g_autofree char *childtype = g_strdup_printf("child<%s>", type);

g_assert(res);
v = qobject_input_visitor_new(res);
Expand All @@ -142,16 +144,15 @@ static void test_object_rng_resp(QObject *res)
g_assert(tmp->value);

obj = tmp->value;
if (g_str_equal(obj->name, "rng0") &&
g_str_equal(obj->type, "child<rng-builtin>")) {
seen_rng = true;
if (g_str_equal(obj->name, name) && g_str_equal(obj->type, childtype)) {
object_available = true;
break;
}

tmp = tmp->next;
}

g_assert(seen_rng);
g_assert(object_available);

visit_free(v);
}
Expand All @@ -170,7 +171,27 @@ static void test_object_rng(void)
resp = qtest_qmp(qts,
"{ 'execute': 'qom-list',"
" 'arguments': {'path': '/objects' }}");
test_object_rng_resp(qdict_get(resp, "return"));
test_object_available(qdict_get(resp, "return"), "rng0", "rng-builtin");
qobject_unref(resp);

qtest_quit(qts);
}

static void test_docs_config_ich9(void)
{
QTestState *qts;
QDict *resp;
QObject *qobj;

qts = qtest_initf("-nodefaults -readconfig docs/config/ich9-ehci-uhci.cfg");

resp = qtest_qmp(qts, "{ 'execute': 'qom-list',"
" 'arguments': {'path': '/machine/peripheral' }}");
qobj = qdict_get(resp, "return");
test_object_available(qobj, "ehci", "ich9-usb-ehci1");
test_object_available(qobj, "uhci-1", "ich9-usb-uhci1");
test_object_available(qobj, "uhci-2", "ich9-usb-uhci2");
test_object_available(qobj, "uhci-3", "ich9-usb-uhci3");
qobject_unref(resp);

qtest_quit(qts);
Expand All @@ -186,6 +207,7 @@ int main(int argc, char *argv[])
if (g_str_equal(arch, "i386") ||
g_str_equal(arch, "x86_64")) {
qtest_add_func("readconfig/x86/memdev", test_x86_memdev);
qtest_add_func("readconfig/x86/ich9-ehci-uhci", test_docs_config_ich9);
}
#ifdef CONFIG_SPICE
qtest_add_func("readconfig/spice", test_spice);
Expand Down