qemu/tests/unit/test-qdev.c
Akihiko Odaki c38575a126 hw/qdev: Parent device before setting parent bus
Commit 9940b2cfbc ("qdev: New qdev_new(), qdev_realize(), etc.") says
"device state 'no QOM parent, but plugged into bus' is dangerous". In
such a case, unrealizing the bus will hang in bus_unparent():

    while ((kid = QTAILQ_FIRST(&bus->children)) != NULL) {
        DeviceState *dev = kid->child;
        object_unparent(OBJECT(dev));
    }

object_unparent() does nothing when its argument has no QOM parent,
and the loop spins forever.

However, that commit did not completely eliminate such a situation.
When the device is not parented, device_set_realized() lets
/machine/unattached parent it, but it happens after setting parent bus.
Therefore, any failure between the two operations can leave the device
in a dangerous state.

qdev_realize() at least asserts that the device is not already realized
and prevents one realization failure pattern, but it is not
comprehensive. Besides, it will trip with a command line like the
following:

    qemu-system-x86_64 -M none -nodefaults -nographic \
        -device ipmi-bmc-sim,realized=on

Eliminate the dangerous state by ensuring that the device is parented
before calling qdev_set_parent_bus(). Also, stop asserting that the
device is not already realized in qdev_realize(); it is broken and
no longer serves any purpose.

Fixes: 9940b2cfbc ("qdev: New qdev_new(), qdev_realize(), etc.")
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Message-ID: <20260721-qdev-v3-14-d2e226fa002e@rsg.ci.i.u-tokyo.ac.jp>
2026-08-16 16:32:41 +02:00

109 lines
2.9 KiB
C

#include "qemu/osdep.h"
#include "hw/core/qdev-properties.h"
#include "qom/object.h"
#include "qapi/error.h"
#include "qapi/visitor.h"
#define TYPE_MY_DEV "my-dev"
typedef struct MyDev MyDev;
DECLARE_INSTANCE_CHECKER(MyDev, STATIC_TYPE,
TYPE_MY_DEV)
struct MyDev {
DeviceState parent_obj;
uint32_t prop_u32;
char *prop_string;
uint32_t *prop_array_u32;
uint32_t prop_array_u32_nb;
};
static const Property my_dev_props[] = {
DEFINE_PROP_UINT32("u32", MyDev, prop_u32, 100),
DEFINE_PROP_STRING("string", MyDev, prop_string),
DEFINE_PROP_ARRAY("array-u32", MyDev, prop_array_u32_nb, prop_array_u32,
qdev_prop_uint32, uint32_t),
};
static void my_dev_class_init(ObjectClass *oc, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
dc->realize = NULL;
device_class_set_props(dc, my_dev_props);
}
static const TypeInfo my_dev_type_info = {
.name = TYPE_MY_DEV,
.parent = TYPE_DEVICE,
.instance_size = sizeof(MyDev),
.class_init = my_dev_class_init,
};
/*
* Initialize a fake machine, being prepared for future tests.
*
* Realization of anonymous qdev (with no parent object) requires both
* the machine object and its "unattached" container to be at least present.
*/
static void test_init_machine(void)
{
/* This is a fake machine - it doesn't need to be a machine object */
Object *machine = object_property_add_new_container(
object_get_root(), "machine");
/* This container must exist for anonymous qdevs to realize() */
object_property_add_new_container(machine, "unattached");
}
static void test_qdev_free_properties(void)
{
MyDev *mt;
mt = STATIC_TYPE(object_new(TYPE_MY_DEV));
object_set_props(OBJECT(mt), &error_fatal,
"string", "something",
"array-u32", "12,13",
NULL);
qdev_realize(DEVICE(mt), NULL, &error_fatal);
g_assert_cmpuint(mt->prop_u32, ==, 100);
g_assert_cmpstr(mt->prop_string, ==, "something");
g_assert_cmpuint(mt->prop_array_u32_nb, ==, 2);
g_assert_cmpuint(mt->prop_array_u32[0], ==, 12);
g_assert_cmpuint(mt->prop_array_u32[1], ==, 13);
object_unparent(OBJECT(mt));
object_unref(mt);
}
static void test_qdev_double_realization(void)
{
MyDev *mt = STATIC_TYPE(object_new(TYPE_MY_DEV));
qdev_realize(DEVICE(mt), NULL, &error_fatal);
qdev_realize(DEVICE(mt), NULL, &error_fatal);
object_unparent(OBJECT(mt));
object_unref(OBJECT(mt));
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
type_register_static(&my_dev_type_info);
test_init_machine();
g_test_add_func("/qdev/free-properties",
test_qdev_free_properties);
g_test_add_func("/qdev/double-realization",
test_qdev_double_realization);
g_test_run();
return 0;
}