* rust: better integration with clippy, rustfmt and rustdoc

* rust: interior mutability types
 * rust: add a bit operations module
 * rust: first part of QOM rework
 * kvm: remove unnecessary #ifdef
 * clock: small cleanups, improve handling of Clock lifetimes
 -----BEGIN PGP SIGNATURE-----
 
 iQFIBAABCAAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmdZqFkUHHBib256aW5p
 QHJlZGhhdC5jb20ACgkQv/vSX3jHroOzRwf/SYUD+CJCn2x7kUH/JG893jwN1WbJ
 meGZ0PQDUpOZJFWg6T4g0MuW4O+Wevy2pF4SfGojgqaYxKBbTQVkeliDEMyNUxpr
 vSKXego0K3pkX3cRDXNVTaXFbsHsMt/3pfzMQM6ocF9qbL+Emvx7Og6WdAcyJ4hc
 lA17EHlnrWKUSnqN/Ow/pZXsa4ijCklXFFh4barfbdGVhMQc2QekUU45GsP2AvGT
 NkXTQC05HqxBaAIDeSxbprDSzNihyT71dAooVoxqKboprPu5uoUSJwgaD8rADPr4
 EOfsz61V4mji+DWDcIzTtYoAdY41vVXI9lvCKOcCFkimA29xO0W6P7mG2w==
 =JSh5
 -----END PGP SIGNATURE-----

Merge tag 'for-upstream' of https://gitlab.com/bonzini/qemu into staging

* rust: better integration with clippy, rustfmt and rustdoc
* rust: interior mutability types
* rust: add a bit operations module
* rust: first part of QOM rework
* kvm: remove unnecessary #ifdef
* clock: small cleanups, improve handling of Clock lifetimes

# -----BEGIN PGP SIGNATURE-----
#
# iQFIBAABCAAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmdZqFkUHHBib256aW5p
# QHJlZGhhdC5jb20ACgkQv/vSX3jHroOzRwf/SYUD+CJCn2x7kUH/JG893jwN1WbJ
# meGZ0PQDUpOZJFWg6T4g0MuW4O+Wevy2pF4SfGojgqaYxKBbTQVkeliDEMyNUxpr
# vSKXego0K3pkX3cRDXNVTaXFbsHsMt/3pfzMQM6ocF9qbL+Emvx7Og6WdAcyJ4hc
# lA17EHlnrWKUSnqN/Ow/pZXsa4ijCklXFFh4barfbdGVhMQc2QekUU45GsP2AvGT
# NkXTQC05HqxBaAIDeSxbprDSzNihyT71dAooVoxqKboprPu5uoUSJwgaD8rADPr4
# EOfsz61V4mji+DWDcIzTtYoAdY41vVXI9lvCKOcCFkimA29xO0W6P7mG2w==
# =JSh5
# -----END PGP SIGNATURE-----
# gpg: Signature made Wed 11 Dec 2024 09:57:29 EST
# gpg:                using RSA key F13338574B662389866C7682BFFBD25F78C7AE83
# gpg:                issuer "pbonzini@redhat.com"
# gpg: Good signature from "Paolo Bonzini <bonzini@gnu.org>" [full]
# gpg:                 aka "Paolo Bonzini <pbonzini@redhat.com>" [full]
# Primary key fingerprint: 46F5 9FBD 57D6 12E7 BFD4  E2F7 7E15 100C CD36 69B1
#      Subkey fingerprint: F133 3857 4B66 2389 866C  7682 BFFB D25F 78C7 AE83

* tag 'for-upstream' of https://gitlab.com/bonzini/qemu: (49 commits)
  rust: qom: change the parent type to an associated type
  rust: qom: split ObjectType from ObjectImpl trait
  rust: qom: move bridge for TypeInfo functions out of pl011
  rust: qdev: move bridge for realize and reset functions out of pl011
  rust: qdev: move device_class_init! body to generic function, ClassInitImpl implementation to macro
  rust: qom: move ClassInitImpl to the instance side
  rust: qom: convert type_info! macro to an associated const
  rust: qom: rename Class trait to ClassInitImpl
  rust: qom: add default definitions for ObjectImpl
  rust: add a bit operation module
  rust: add bindings for interrupt sources
  rust: define prelude
  rust: cell: add BQL-enforcing RefCell variant
  rust: cell: add BQL-enforcing Cell variant
  bql: check that the BQL is not dropped within marked sections
  qom/object: Remove type_register()
  script/codeconverter/qom_type_info: Deprecate MakeTypeRegisterStatic and MakeTypeRegisterNotStatic
  ui: Replace type_register() with type_register_static()
  target/xtensa: Replace type_register() with type_register_static()
  target/sparc: Replace type_register() with type_register_static()
  ...

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi 2024-12-12 18:40:32 -05:00
commit 1eec82cc06
67 changed files with 1927 additions and 445 deletions

View file

@ -40,7 +40,7 @@ build-system-ubuntu:
job: amd64-ubuntu2204-container job: amd64-ubuntu2204-container
variables: variables:
IMAGE: ubuntu2204 IMAGE: ubuntu2204
CONFIGURE_ARGS: --enable-docs CONFIGURE_ARGS: --enable-docs --enable-rust
TARGETS: alpha-softmmu microblazeel-softmmu mips64el-softmmu TARGETS: alpha-softmmu microblazeel-softmmu mips64el-softmmu
MAKE_CHECK_ARGS: check-build MAKE_CHECK_ARGS: check-build
@ -71,7 +71,7 @@ build-system-debian:
job: amd64-debian-container job: amd64-debian-container
variables: variables:
IMAGE: debian IMAGE: debian
CONFIGURE_ARGS: --with-coroutine=sigaltstack CONFIGURE_ARGS: --with-coroutine=sigaltstack --enable-rust
TARGETS: arm-softmmu i386-softmmu riscv64-softmmu sh4eb-softmmu TARGETS: arm-softmmu i386-softmmu riscv64-softmmu sh4eb-softmmu
sparc-softmmu xtensa-softmmu sparc-softmmu xtensa-softmmu
MAKE_CHECK_ARGS: check-build MAKE_CHECK_ARGS: check-build

View file

@ -46,3 +46,27 @@ check-python-tox:
QEMU_JOB_OPTIONAL: 1 QEMU_JOB_OPTIONAL: 1
needs: needs:
job: python-container job: python-container
check-rust-tools-nightly:
extends: .base_job_template
stage: test
image: $CI_REGISTRY_IMAGE/qemu/fedora-rust-nightly:$QEMU_CI_CONTAINER_TAG
script:
- source scripts/ci/gitlab-ci-section
- section_start test "Running Rust code checks"
- cd build
- pyvenv/bin/meson devenv -w ../rust ${CARGO-cargo} fmt --check
- make clippy
- make rustdoc
- section_end test
variables:
GIT_DEPTH: 1
allow_failure: true
needs:
- job: build-system-fedora-rust-nightly
artifacts: true
artifacts:
when: on_success
expire_in: 2 days
paths:
- rust/target/doc

View file

@ -1731,7 +1731,7 @@ static void armsse_register_types(void)
.class_init = armsse_class_init, .class_init = armsse_class_init,
.class_data = (void *)&armsse_variants[i], .class_data = (void *)&armsse_variants[i],
}; };
type_register(&ti); type_register_static(&ti);
} }
} }

View file

@ -2065,8 +2065,8 @@ static const TypeInfo smmuv3_iommu_memory_region_info = {
static void smmuv3_register_types(void) static void smmuv3_register_types(void)
{ {
type_register(&smmuv3_type_info); type_register_static(&smmuv3_type_info);
type_register(&smmuv3_iommu_memory_region_info); type_register_static(&smmuv3_iommu_memory_region_info);
} }
type_init(smmuv3_register_types) type_init(smmuv3_register_types)

View file

@ -1894,7 +1894,7 @@ static void m25p80_register_types(void)
.class_init = m25p80_class_init, .class_init = m25p80_class_init,
.class_data = (void *)&known_devices[i], .class_data = (void *)&known_devices[i],
}; };
type_register(&ti); type_register_static(&ti);
} }
} }

View file

@ -44,16 +44,12 @@ Clock *clock_new(Object *parent, const char *name)
void clock_set_callback(Clock *clk, ClockCallback *cb, void *opaque, void clock_set_callback(Clock *clk, ClockCallback *cb, void *opaque,
unsigned int events) unsigned int events)
{ {
assert(OBJECT(clk)->parent);
clk->callback = cb; clk->callback = cb;
clk->callback_opaque = opaque; clk->callback_opaque = opaque;
clk->callback_events = events; clk->callback_events = events;
} }
void clock_clear_callback(Clock *clk)
{
clock_set_callback(clk, NULL, NULL, 0);
}
bool clock_set(Clock *clk, uint64_t period) bool clock_set(Clock *clk, uint64_t period)
{ {
if (clk->period == period) { if (clk->period == period) {
@ -168,6 +164,16 @@ static void clock_period_prop_get(Object *obj, Visitor *v, const char *name,
visit_type_uint64(v, name, &period, errp); visit_type_uint64(v, name, &period, errp);
} }
static void clock_unparent(Object *obj)
{
/*
* Callback are registered by the parent, which might die anytime after
* it's unparented the children. Avoid having a callback to a deleted
* object in case the clock is still referenced somewhere else (eg: by
* a clock output).
*/
clock_set_callback(CLOCK(obj), NULL, NULL, 0);
}
static void clock_initfn(Object *obj) static void clock_initfn(Object *obj)
{ {
@ -200,11 +206,17 @@ static void clock_finalizefn(Object *obj)
g_free(clk->canonical_path); g_free(clk->canonical_path);
} }
static void clock_class_init(ObjectClass *klass, void *data)
{
klass->unparent = clock_unparent;
}
static const TypeInfo clock_info = { static const TypeInfo clock_info = {
.name = TYPE_CLOCK, .name = TYPE_CLOCK,
.parent = TYPE_OBJECT, .parent = TYPE_OBJECT,
.instance_size = sizeof(Clock), .instance_size = sizeof(Clock),
.instance_init = clock_initfn, .instance_init = clock_initfn,
.class_init = clock_class_init,
.instance_finalize = clock_finalizefn, .instance_finalize = clock_finalizefn,
}; };

View file

@ -22,7 +22,7 @@
* Add a new clock in a device * Add a new clock in a device
*/ */
static NamedClockList *qdev_init_clocklist(DeviceState *dev, const char *name, static NamedClockList *qdev_init_clocklist(DeviceState *dev, const char *name,
bool output, Clock *clk) bool alias, bool output, Clock *clk)
{ {
NamedClockList *ncl; NamedClockList *ncl;
@ -38,39 +38,8 @@ static NamedClockList *qdev_init_clocklist(DeviceState *dev, const char *name,
*/ */
ncl = g_new0(NamedClockList, 1); ncl = g_new0(NamedClockList, 1);
ncl->name = g_strdup(name); ncl->name = g_strdup(name);
ncl->alias = alias;
ncl->output = output; ncl->output = output;
ncl->alias = (clk != NULL);
/*
* Trying to create a clock whose name clashes with some other
* clock or property is a bug in the caller and we will abort().
*/
if (clk == NULL) {
clk = CLOCK(object_new(TYPE_CLOCK));
object_property_add_child(OBJECT(dev), name, OBJECT(clk));
if (output) {
/*
* Remove object_new()'s initial reference.
* Note that for inputs, the reference created by object_new()
* will be deleted in qdev_finalize_clocklist().
*/
object_unref(OBJECT(clk));
}
} else {
object_property_add_link(OBJECT(dev), name,
object_get_typename(OBJECT(clk)),
(Object **) &ncl->clock,
NULL, OBJ_PROP_LINK_STRONG);
/*
* Since the link property has the OBJ_PROP_LINK_STRONG flag, the clk
* object reference count gets decremented on property deletion.
* However object_property_add_link does not increment it since it
* doesn't know the linked object. Increment it here to ensure the
* aliased clock stays alive during this device life-time.
*/
object_ref(OBJECT(clk));
}
ncl->clock = clk; ncl->clock = clk;
QLIST_INSERT_HEAD(&dev->clocks, ncl, node); QLIST_INSERT_HEAD(&dev->clocks, ncl, node);
@ -84,14 +53,11 @@ void qdev_finalize_clocklist(DeviceState *dev)
QLIST_FOREACH_SAFE(ncl, &dev->clocks, node, ncl_next) { QLIST_FOREACH_SAFE(ncl, &dev->clocks, node, ncl_next) {
QLIST_REMOVE(ncl, node); QLIST_REMOVE(ncl, node);
if (!ncl->output && !ncl->alias) { if (!ncl->alias) {
/* /*
* We kept a reference on the input clock to ensure it lives up to * We kept a reference on the input clock to ensure it lives up to
* this point so we can safely remove the callback. * this point; it is used by the monitor to show the frequency.
* It avoids having a callback to a deleted object if ncl->clock
* is still referenced somewhere else (eg: by a clock output).
*/ */
clock_clear_callback(ncl->clock);
object_unref(OBJECT(ncl->clock)); object_unref(OBJECT(ncl->clock));
} }
g_free(ncl->name); g_free(ncl->name);
@ -101,29 +67,25 @@ void qdev_finalize_clocklist(DeviceState *dev)
Clock *qdev_init_clock_out(DeviceState *dev, const char *name) Clock *qdev_init_clock_out(DeviceState *dev, const char *name)
{ {
NamedClockList *ncl; Clock *clk = CLOCK(object_new(TYPE_CLOCK));
object_property_add_child(OBJECT(dev), name, OBJECT(clk));
assert(name); qdev_init_clocklist(dev, name, false, true, clk);
return clk;
ncl = qdev_init_clocklist(dev, name, true, NULL);
return ncl->clock;
} }
Clock *qdev_init_clock_in(DeviceState *dev, const char *name, Clock *qdev_init_clock_in(DeviceState *dev, const char *name,
ClockCallback *callback, void *opaque, ClockCallback *callback, void *opaque,
unsigned int events) unsigned int events)
{ {
NamedClockList *ncl; Clock *clk = CLOCK(object_new(TYPE_CLOCK));
object_property_add_child(OBJECT(dev), name, OBJECT(clk));
assert(name);
ncl = qdev_init_clocklist(dev, name, false, NULL);
qdev_init_clocklist(dev, name, false, false, clk);
if (callback) { if (callback) {
clock_set_callback(ncl->clock, callback, opaque, events); clock_set_callback(clk, callback, opaque, events);
} }
return ncl->clock; return clk;
} }
void qdev_init_clocks(DeviceState *dev, const ClockPortInitArray clocks) void qdev_init_clocks(DeviceState *dev, const ClockPortInitArray clocks)
@ -194,15 +156,25 @@ Clock *qdev_get_clock_out(DeviceState *dev, const char *name)
Clock *qdev_alias_clock(DeviceState *dev, const char *name, Clock *qdev_alias_clock(DeviceState *dev, const char *name,
DeviceState *alias_dev, const char *alias_name) DeviceState *alias_dev, const char *alias_name)
{ {
NamedClockList *ncl; NamedClockList *ncl = qdev_get_clocklist(dev, name);
Clock *clk = ncl->clock;
assert(name && alias_name); ncl = qdev_init_clocklist(alias_dev, alias_name, true, ncl->output, clk);
ncl = qdev_get_clocklist(dev, name); object_property_add_link(OBJECT(alias_dev), alias_name,
TYPE_CLOCK,
(Object **) &ncl->clock,
NULL, OBJ_PROP_LINK_STRONG);
/*
* Since the link property has the OBJ_PROP_LINK_STRONG flag, the clk
* object reference count gets decremented on property deletion.
* However object_property_add_link does not increment it since it
* doesn't know the linked object. Increment it here to ensure the
* aliased clock stays alive during this device life-time.
*/
object_ref(OBJECT(clk));
qdev_init_clocklist(alias_dev, alias_name, ncl->output, ncl->clock); return clk;
return ncl->clock;
} }
void qdev_connect_clock_in(DeviceState *dev, const char *name, Clock *source) void qdev_connect_clock_in(DeviceState *dev, const char *name, Clock *source)

View file

@ -1774,7 +1774,7 @@ static void e1000_register_types(void)
type_info.class_data = (void *)info; type_info.class_data = (void *)info;
type_info.class_init = e1000_class_init; type_info.class_init = e1000_class_init;
type_register(&type_info); type_register_static(&type_info);
} }
} }

View file

@ -2102,7 +2102,7 @@ static void eepro100_register_types(void)
{ }, { },
}; };
type_register(&type_info); type_register_static(&type_info);
} }
} }

View file

@ -4723,7 +4723,7 @@ static void spapr_machine_latest_class_options(MachineClass *mc)
static void MACHINE_VER_SYM(register, spapr, __VA_ARGS__)(void) \ static void MACHINE_VER_SYM(register, spapr, __VA_ARGS__)(void) \
{ \ { \
MACHINE_VER_DELETION(__VA_ARGS__); \ MACHINE_VER_DELETION(__VA_ARGS__); \
type_register(&MACHINE_VER_SYM(info, spapr, __VA_ARGS__)); \ type_register_static(&MACHINE_VER_SYM(info, spapr, __VA_ARGS__)); \
} \ } \
type_init(MACHINE_VER_SYM(register, spapr, __VA_ARGS__)) type_init(MACHINE_VER_SYM(register, spapr, __VA_ARGS__))

View file

@ -161,7 +161,7 @@ static void m48t59_isa_register_types(void)
for (i = 0; i < ARRAY_SIZE(m48txx_isa_info); i++) { for (i = 0; i < ARRAY_SIZE(m48txx_isa_info); i++) {
isa_type_info.name = m48txx_isa_info[i].bus_name; isa_type_info.name = m48txx_isa_info[i].bus_name;
isa_type_info.class_data = &m48txx_isa_info[i]; isa_type_info.class_data = &m48txx_isa_info[i];
type_register(&isa_type_info); type_register_static(&isa_type_info);
} }
} }

View file

@ -679,7 +679,7 @@ static void m48t59_register_types(void)
for (i = 0; i < ARRAY_SIZE(m48txx_sysbus_info); i++) { for (i = 0; i < ARRAY_SIZE(m48txx_sysbus_info); i++) {
sysbus_type_info.name = m48txx_sysbus_info[i].bus_name; sysbus_type_info.name = m48txx_sysbus_info[i].bus_name;
sysbus_type_info.class_data = &m48txx_sysbus_info[i]; sysbus_type_info.class_data = &m48txx_sysbus_info[i];
type_register(&sysbus_type_info); type_register_static(&sysbus_type_info);
} }
} }

View file

@ -2576,7 +2576,7 @@ static void megasas_register_types(void)
type_info.class_init = megasas_class_init; type_info.class_init = megasas_class_init;
type_info.interfaces = info->interfaces; type_info.interfaces = info->interfaces;
type_register(&type_info); type_register_static(&type_info);
} }
} }

View file

@ -1450,7 +1450,7 @@ static const TypeInfo mptsas_info = {
static void mptsas_register_types(void) static void mptsas_register_types(void)
{ {
type_register(&mptsas_info); type_register_static(&mptsas_info);
} }
type_init(mptsas_register_types) type_init(mptsas_register_types)

View file

@ -384,7 +384,7 @@ static void tmp421_register_types(void)
.class_init = tmp421_class_init, .class_init = tmp421_class_init,
.class_data = (void *) &devices[i], .class_data = (void *) &devices[i],
}; };
type_register(&ti); type_register_static(&ti);
} }
} }

View file

@ -228,7 +228,7 @@ static void ehci_pci_register_types(void)
for (i = 0; i < ARRAY_SIZE(ehci_pci_info); i++) { for (i = 0; i < ARRAY_SIZE(ehci_pci_info); i++) {
ehci_type_info.name = ehci_pci_info[i].name; ehci_type_info.name = ehci_pci_info[i].name;
ehci_type_info.class_data = ehci_pci_info + i; ehci_type_info.class_data = ehci_pci_info + i;
type_register(&ehci_type_info); type_register_static(&ehci_type_info);
} }
} }

View file

@ -1362,7 +1362,7 @@ static void uhci_register_types(void)
for (i = 0; i < ARRAY_SIZE(uhci_info); i++) { for (i = 0; i < ARRAY_SIZE(uhci_info); i++) {
uhci_type_info.name = uhci_info[i].name; uhci_type_info.name = uhci_info[i].name;
uhci_type_info.class_data = uhci_info + i; uhci_type_info.class_data = uhci_info + i;
type_register(&uhci_type_info); type_register_static(&uhci_type_info);
} }
} }

View file

@ -2511,9 +2511,9 @@ void virtio_pci_types_register(const VirtioPCIDeviceTypeInfo *t)
base_type_info.class_data = (void *)t; base_type_info.class_data = (void *)t;
} }
type_register(&base_type_info); type_register_static(&base_type_info);
if (generic_type_info.name) { if (generic_type_info.name) {
type_register(&generic_type_info); type_register_static(&generic_type_info);
} }
if (t->non_transitional_name) { if (t->non_transitional_name) {
@ -2527,7 +2527,7 @@ void virtio_pci_types_register(const VirtioPCIDeviceTypeInfo *t)
{ } { }
}, },
}; };
type_register(&non_transitional_type_info); type_register_static(&non_transitional_type_info);
} }
if (t->transitional_name) { if (t->transitional_name) {
@ -2544,7 +2544,7 @@ void virtio_pci_types_register(const VirtioPCIDeviceTypeInfo *t)
{ } { }
}, },
}; };
type_register(&transitional_type_info); type_register_static(&transitional_type_info);
} }
g_free(base_name); g_free(base_name);
} }

View file

@ -141,14 +141,6 @@ Clock *clock_new(Object *parent, const char *name);
void clock_set_callback(Clock *clk, ClockCallback *cb, void clock_set_callback(Clock *clk, ClockCallback *cb,
void *opaque, unsigned int events); void *opaque, unsigned int events);
/**
* clock_clear_callback:
* @clk: the clock to delete the callback from
*
* Unregister the callback registered with clock_set_callback.
*/
void clock_clear_callback(Clock *clk);
/** /**
* clock_set_source: * clock_set_source:
* @clk: the clock. * @clk: the clock.

View file

@ -319,7 +319,7 @@ extern const size_t pc_compat_2_3_len;
}; \ }; \
static void pc_machine_init_##suffix(void) \ static void pc_machine_init_##suffix(void) \
{ \ { \
type_register(&pc_machine_type_##suffix); \ type_register_static(&pc_machine_type_##suffix); \
} \ } \
type_init(pc_machine_init_##suffix) type_init(pc_machine_init_##suffix)
@ -349,7 +349,7 @@ extern const size_t pc_compat_2_3_len;
static void MACHINE_VER_SYM(register, namesym, __VA_ARGS__)(void) \ static void MACHINE_VER_SYM(register, namesym, __VA_ARGS__)(void) \
{ \ { \
MACHINE_VER_DELETION(__VA_ARGS__); \ MACHINE_VER_DELETION(__VA_ARGS__); \
type_register(&MACHINE_VER_SYM(info, namesym, __VA_ARGS__)); \ type_register_static(&MACHINE_VER_SYM(info, namesym, __VA_ARGS__)); \
} \ } \
type_init(MACHINE_VER_SYM(register, namesym, __VA_ARGS__)); type_init(MACHINE_VER_SYM(register, namesym, __VA_ARGS__));

View file

@ -262,6 +262,21 @@ AioContext *iohandler_get_aio_context(void);
*/ */
bool bql_locked(void); bool bql_locked(void);
/**
* bql_block: Allow/deny releasing the BQL
*
* The Big QEMU Lock (BQL) is used to provide interior mutability to
* Rust code, but this only works if other threads cannot run while
* the Rust code has an active borrow. This is because C code in
* other threads could come in and mutate data under the Rust code's
* feet.
*
* @increase: Whether to increase or decrease the blocking counter.
* Releasing the BQL while the counter is nonzero triggers
* an assertion failure.
*/
void bql_block_unlock(bool increase);
/** /**
* qemu_in_main_thread: return whether it's possible to safely access * qemu_in_main_thread: return whether it's possible to safely access
* the global state of the block layer. * the global state of the block layer.

View file

@ -880,24 +880,10 @@ const char *object_get_typename(const Object *obj);
* type_register_static: * type_register_static:
* @info: The #TypeInfo of the new type. * @info: The #TypeInfo of the new type.
* *
* @info and all of the strings it points to should exist for the life time
* that the type is registered.
*
* Returns: the new #Type. * Returns: the new #Type.
*/ */
Type type_register_static(const TypeInfo *info); Type type_register_static(const TypeInfo *info);
/**
* type_register:
* @info: The #TypeInfo of the new type
*
* Unlike type_register_static(), this call does not require @info or its
* string members to continue to exist after the call returns.
*
* Returns: the new #Type.
*/
Type type_register(const TypeInfo *info);
/** /**
* type_register_static_array: * type_register_static_array:
* @infos: The array of the new type #TypeInfo structures. * @infos: The array of the new type #TypeInfo structures.

View file

@ -3,6 +3,8 @@ project('qemu', ['c'], meson_version: '>=1.5.0',
'b_staticpic=false', 'stdsplit=false', 'optimization=2', 'b_pie=true'], 'b_staticpic=false', 'stdsplit=false', 'optimization=2', 'b_pie=true'],
version: files('VERSION')) version: files('VERSION'))
meson.add_devenv({ 'MESON_BUILD_ROOT' : meson.project_build_root() })
add_test_setup('quick', exclude_suites: ['slow', 'thorough'], is_default: true) add_test_setup('quick', exclude_suites: ['slow', 'thorough'], is_default: true)
add_test_setup('slow', exclude_suites: ['thorough'], env: ['G_TEST_SLOW=1', 'SPEED=slow']) add_test_setup('slow', exclude_suites: ['thorough'], env: ['G_TEST_SLOW=1', 'SPEED=slow'])
add_test_setup('thorough', env: ['G_TEST_SLOW=1', 'SPEED=thorough']) add_test_setup('thorough', env: ['G_TEST_SLOW=1', 'SPEED=thorough'])
@ -118,7 +120,28 @@ if have_rust
endif endif
if have_rust if have_rust
rustc_args = [find_program('scripts/rust/rustc_args.py'),
'--rustc-version', rustc.version(),
'--workspace', meson.project_source_root() / 'rust']
if get_option('strict_rust_lints')
rustc_args += ['--strict-lints']
endif
rustfmt = find_program('rustfmt', required: false) rustfmt = find_program('rustfmt', required: false)
rustc_lint_args = run_command(rustc_args, '--lints',
capture: true, check: true).stdout().strip().splitlines()
# Apart from procedural macros, our Rust executables will often link
# with C code, so include all the libraries that C code needs. This
# is safe; https://github.com/rust-lang/rust/pull/54675 says that
# passing -nodefaultlibs to the linker "was more ideological to
# start with than anything".
add_project_arguments(rustc_lint_args +
['--cfg', 'MESON', '-C', 'default-linker-libraries'],
native: false, language: 'rust')
add_project_arguments(rustc_lint_args + ['--cfg', 'MESON'],
native: true, language: 'rust')
endif endif
dtrace = not_found dtrace = not_found
@ -3397,36 +3420,8 @@ endif
# Generated sources # # Generated sources #
##################### #####################
genh += configure_file(output: 'config-host.h', configuration: config_host_data) config_host_h = configure_file(output: 'config-host.h', configuration: config_host_data)
genh += config_host_h
if have_rust
rustc_args = run_command(
find_program('scripts/rust/rustc_args.py'),
'--config-headers', meson.project_build_root() / 'config-host.h',
capture : true,
check: true).stdout().strip().split()
# Prohibit code that is forbidden in Rust 2024
rustc_args += ['-D', 'unsafe_op_in_unsafe_fn']
# Occasionally, we may need to silence warnings and clippy lints that
# were only introduced in newer Rust compiler versions. Do not croak
# in that case; a CI job with rust_strict_lints == true ensures that
# we do not have misspelled allow() attributes.
if not get_option('strict_rust_lints')
rustc_args += ['-A', 'unknown_lints']
endif
# Apart from procedural macros, our Rust executables will often link
# with C code, so include all the libraries that C code needs. This
# is safe; https://github.com/rust-lang/rust/pull/54675 says that
# passing -nodefaultlibs to the linker "was more ideological to
# start with than anything".
add_project_arguments(rustc_args + ['-C', 'default-linker-libraries'],
native: false, language: 'rust')
add_project_arguments(rustc_args, native: true, language: 'rust')
endif
hxtool = find_program('scripts/hxtool') hxtool = find_program('scripts/hxtool')
shaderinclude = find_program('scripts/shaderinclude.py') shaderinclude = find_program('scripts/shaderinclude.py')
@ -4089,7 +4084,7 @@ if have_rust
bindings_rs = rust.bindgen( bindings_rs = rust.bindgen(
input: 'rust/wrapper.h', input: 'rust/wrapper.h',
dependencies: common_ss.all_dependencies(), dependencies: common_ss.all_dependencies(),
output: 'bindings.rs', output: 'bindings.inc.rs',
include_directories: include_directories('.', 'include'), include_directories: include_directories('.', 'include'),
bindgen_version: ['>=0.60.0'], bindgen_version: ['>=0.60.0'],
args: bindgen_args, args: bindgen_args,

View file

@ -175,17 +175,12 @@ static TypeImpl *type_register_internal(const TypeInfo *info)
return ti; return ti;
} }
TypeImpl *type_register(const TypeInfo *info) TypeImpl *type_register_static(const TypeInfo *info)
{ {
assert(info->parent); assert(info->parent);
return type_register_internal(info); return type_register_internal(info);
} }
TypeImpl *type_register_static(const TypeInfo *info)
{
return type_register(info);
}
void type_register_static_array(const TypeInfo *infos, int nr_infos) void type_register_static_array(const TypeInfo *infos, int nr_infos)
{ {
int i; int i;

View file

@ -5,3 +5,85 @@ members = [
"qemu-api", "qemu-api",
"hw/char/pl011", "hw/char/pl011",
] ]
[workspace.lints.rust]
unexpected_cfgs = { level = "deny", check-cfg = [
'cfg(MESON)', 'cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)',
'cfg(has_offset_of)'] }
# Occasionally, we may need to silence warnings and clippy lints that
# were only introduced in newer Rust compiler versions. Do not croak
# in that case; a CI job with rust_strict_lints == true disables this
# and ensures that we do not have misspelled allow() attributes.
unknown_lints = "allow"
# Prohibit code that is forbidden in Rust 2024
unsafe_op_in_unsafe_fn = "deny"
[workspace.lints.rustdoc]
private_intra_doc_links = "allow"
broken_intra_doc_links = "deny"
invalid_html_tags = "deny"
invalid_rust_codeblocks = "deny"
bare_urls = "deny"
unescaped_backticks = "deny"
redundant_explicit_links = "deny"
[workspace.lints.clippy]
# default-warn lints
result_unit_err = "allow"
should_implement_trait = "deny"
# can be for a reason, e.g. in callbacks
unused_self = "allow"
# default-allow lints
as_underscore = "deny"
assertions_on_result_states = "deny"
bool_to_int_with_if = "deny"
borrow_as_ptr = "deny"
cast_lossless = "deny"
dbg_macro = "deny"
debug_assert_with_mut_call = "deny"
derive_partial_eq_without_eq = "deny"
doc_markdown = "deny"
empty_structs_with_brackets = "deny"
ignored_unit_patterns = "deny"
implicit_clone = "deny"
macro_use_imports = "deny"
missing_const_for_fn = "deny"
missing_safety_doc = "deny"
multiple_crate_versions = "deny"
mut_mut = "deny"
needless_bitwise_bool = "deny"
needless_pass_by_ref_mut = "deny"
no_effect_underscore_binding = "deny"
option_option = "deny"
or_fun_call = "deny"
ptr_as_ptr = "deny"
pub_underscore_fields = "deny"
redundant_clone = "deny"
redundant_closure_for_method_calls = "deny"
redundant_else = "deny"
redundant_pub_crate = "deny"
ref_binding_to_reference = "deny"
ref_option_ref = "deny"
return_self_not_must_use = "deny"
same_name_method = "deny"
semicolon_inside_block = "deny"
shadow_unrelated = "deny"
significant_drop_in_scrutinee = "deny"
significant_drop_tightening = "deny"
suspicious_operation_groupings = "deny"
transmute_ptr_to_ptr = "deny"
transmute_undefined_repr = "deny"
type_repetition_in_bounds = "deny"
used_underscore_binding = "deny"
# nice to have, but cannot be enabled yet
#wildcard_imports = "deny" # still have many bindings::* imports
#ptr_cast_constness = "deny" # needs 1.65.0 for cast_mut()/cast_const()
# these may have false positives
#option_if_let_else = "deny"
cognitive_complexity = "deny"

View file

@ -1,2 +0,0 @@
# Ignore generated bindings file overrides.
src/bindings.rs.inc

View file

@ -21,3 +21,6 @@ bilge = { version = "0.2.0" }
bilge-impl = { version = "0.2.0" } bilge-impl = { version = "0.2.0" }
qemu_api = { path = "../../../qemu-api" } qemu_api = { path = "../../../qemu-api" }
qemu_api_macros = { path = "../../../qemu-api-macros" } qemu_api_macros = { path = "../../../qemu-api-macros" }
[lints]
workspace = true

View file

@ -2,7 +2,7 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> // Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
use core::ptr::{addr_of, addr_of_mut, NonNull}; use core::ptr::{addr_of_mut, NonNull};
use std::{ use std::{
ffi::CStr, ffi::CStr,
os::raw::{c_int, c_uchar, c_uint, c_void}, os::raw::{c_int, c_uchar, c_uint, c_void},
@ -12,10 +12,14 @@ use qemu_api::{
bindings::{self, *}, bindings::{self, *},
c_str, c_str,
definitions::ObjectImpl, definitions::ObjectImpl,
device_class::TYPE_SYS_BUS_DEVICE, device_class::DeviceImpl,
impl_device_class,
irq::InterruptSource,
prelude::*,
}; };
use crate::{ use crate::{
device_class,
memory_ops::PL011_OPS, memory_ops::PL011_OPS,
registers::{self, Interrupt}, registers::{self, Interrupt},
RegisterOffset, RegisterOffset,
@ -94,7 +98,7 @@ pub struct PL011State {
/// * sysbus IRQ 5: `UARTEINTR` (error interrupt line) /// * sysbus IRQ 5: `UARTEINTR` (error interrupt line)
/// ``` /// ```
#[doc(alias = "irq")] #[doc(alias = "irq")]
pub interrupts: [qemu_irq; 6usize], pub interrupts: [InterruptSource; IRQMASK.len()],
#[doc(alias = "clk")] #[doc(alias = "clk")]
pub clock: NonNull<Clock>, pub clock: NonNull<Clock>,
#[doc(alias = "migrate_clk")] #[doc(alias = "migrate_clk")]
@ -103,15 +107,15 @@ pub struct PL011State {
device_id: DeviceId, device_id: DeviceId,
} }
impl ObjectImpl for PL011State { unsafe impl ObjectType for PL011State {
type Class = PL011Class; type Class = PL011Class;
const TYPE_INFO: qemu_api::bindings::TypeInfo = qemu_api::type_info! { Self };
const TYPE_NAME: &'static CStr = crate::TYPE_PL011; const TYPE_NAME: &'static CStr = crate::TYPE_PL011;
const PARENT_TYPE_NAME: Option<&'static CStr> = Some(TYPE_SYS_BUS_DEVICE); }
const ABSTRACT: bool = false;
const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = Some(pl011_init); impl ObjectImpl for PL011State {
const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = None; type ParentType = SysBusDevice;
const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
const INSTANCE_INIT: Option<unsafe fn(&mut Self)> = Some(Self::init);
} }
#[repr(C)] #[repr(C)]
@ -119,13 +123,18 @@ pub struct PL011Class {
_inner: [u8; 0], _inner: [u8; 0],
} }
impl qemu_api::definitions::Class for PL011Class { impl DeviceImpl for PL011State {
const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)> = fn properties() -> &'static [Property] {
Some(crate::device_class::pl011_class_init); &device_class::PL011_PROPERTIES
const CLASS_BASE_INIT: Option<
unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
> = None;
} }
fn vmsd() -> Option<&'static VMStateDescription> {
Some(&device_class::VMSTATE_PL011)
}
const REALIZE: Option<fn(&mut Self)> = Some(Self::realize);
const RESET: Option<fn(&mut Self)> = Some(Self::reset);
}
impl_device_class!(PL011State);
impl PL011State { impl PL011State {
/// Initializes a pre-allocated, unitialized instance of `PL011State`. /// Initializes a pre-allocated, unitialized instance of `PL011State`.
@ -139,7 +148,8 @@ impl PL011State {
unsafe fn init(&mut self) { unsafe fn init(&mut self) {
const CLK_NAME: &CStr = c_str!("clk"); const CLK_NAME: &CStr = c_str!("clk");
let dev = addr_of_mut!(*self).cast::<DeviceState>(); let sbd = unsafe { &mut *(addr_of_mut!(*self).cast::<SysBusDevice>()) };
// SAFETY: // SAFETY:
// //
// self and self.iomem are guaranteed to be valid at this point since callers // self and self.iomem are guaranteed to be valid at this point since callers
@ -150,15 +160,18 @@ impl PL011State {
addr_of_mut!(*self).cast::<Object>(), addr_of_mut!(*self).cast::<Object>(),
&PL011_OPS, &PL011_OPS,
addr_of_mut!(*self).cast::<c_void>(), addr_of_mut!(*self).cast::<c_void>(),
Self::TYPE_INFO.name, Self::TYPE_NAME.as_ptr(),
0x1000, 0x1000,
); );
let sbd = addr_of_mut!(*self).cast::<SysBusDevice>();
sysbus_init_mmio(sbd, addr_of_mut!(self.iomem)); sysbus_init_mmio(sbd, addr_of_mut!(self.iomem));
for irq in self.interrupts.iter_mut() {
sysbus_init_irq(sbd, irq);
} }
for irq in self.interrupts.iter() {
sbd.init_irq(irq);
} }
let dev = addr_of_mut!(*self).cast::<DeviceState>();
// SAFETY: // SAFETY:
// //
// self.clock is not initialized at this point; but since `NonNull<_>` is Copy, // self.clock is not initialized at this point; but since `NonNull<_>` is Copy,
@ -498,8 +511,7 @@ impl PL011State {
pub fn update(&self) { pub fn update(&self) {
let flags = self.int_level & self.int_enabled; let flags = self.int_level & self.int_enabled;
for (irq, i) in self.interrupts.iter().zip(IRQMASK) { for (irq, i) in self.interrupts.iter().zip(IRQMASK) {
// SAFETY: self.interrupts have been initialized in init(). irq.set(flags & i != 0);
unsafe { qemu_set_irq(*irq, i32::from(flags & i != 0)) };
} }
} }
@ -597,30 +609,17 @@ pub unsafe extern "C" fn pl011_create(
chr: *mut Chardev, chr: *mut Chardev,
) -> *mut DeviceState { ) -> *mut DeviceState {
unsafe { unsafe {
let dev: *mut DeviceState = qdev_new(PL011State::TYPE_INFO.name); let dev: *mut DeviceState = qdev_new(PL011State::TYPE_NAME.as_ptr());
let sysbus: *mut SysBusDevice = dev.cast::<SysBusDevice>(); let sysbus: *mut SysBusDevice = dev.cast::<SysBusDevice>();
qdev_prop_set_chr(dev, c_str!("chardev").as_ptr(), chr); qdev_prop_set_chr(dev, c_str!("chardev").as_ptr(), chr);
sysbus_realize_and_unref(sysbus, addr_of!(error_fatal) as *mut *mut Error); sysbus_realize_and_unref(sysbus, addr_of_mut!(error_fatal));
sysbus_mmio_map(sysbus, 0, addr); sysbus_mmio_map(sysbus, 0, addr);
sysbus_connect_irq(sysbus, 0, irq); sysbus_connect_irq(sysbus, 0, irq);
dev dev
} }
} }
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
pub unsafe extern "C" fn pl011_init(obj: *mut Object) {
unsafe {
debug_assert!(!obj.is_null());
let mut state = NonNull::new_unchecked(obj.cast::<PL011State>());
state.as_mut().init();
}
}
#[repr(C)] #[repr(C)]
#[derive(Debug, qemu_api_macros::Object)] #[derive(Debug, qemu_api_macros::Object)]
/// PL011 Luminary device model. /// PL011 Luminary device model.
@ -633,37 +632,30 @@ pub struct PL011LuminaryClass {
_inner: [u8; 0], _inner: [u8; 0],
} }
impl PL011Luminary {
/// Initializes a pre-allocated, unitialized instance of `PL011Luminary`. /// Initializes a pre-allocated, unitialized instance of `PL011Luminary`.
/// ///
/// # Safety /// # Safety
/// ///
/// We expect the FFI user of this function to pass a valid pointer, that has /// We expect the FFI user of this function to pass a valid pointer, that
/// the same size as [`PL011Luminary`]. We also expect the device is /// has the same size as [`PL011Luminary`]. We also expect the device is
/// readable/writeable from one thread at any time. /// readable/writeable from one thread at any time.
pub unsafe extern "C" fn pl011_luminary_init(obj: *mut Object) { unsafe fn init(&mut self) {
unsafe { self.parent_obj.device_id = DeviceId::Luminary;
debug_assert!(!obj.is_null());
let mut state = NonNull::new_unchecked(obj.cast::<PL011Luminary>());
let state = state.as_mut();
state.parent_obj.device_id = DeviceId::Luminary;
} }
} }
impl qemu_api::definitions::Class for PL011LuminaryClass { unsafe impl ObjectType for PL011Luminary {
const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)> = type Class = PL011LuminaryClass;
None; const TYPE_NAME: &'static CStr = crate::TYPE_PL011_LUMINARY;
const CLASS_BASE_INIT: Option<
unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
> = None;
} }
impl ObjectImpl for PL011Luminary { impl ObjectImpl for PL011Luminary {
type Class = PL011LuminaryClass; type ParentType = PL011State;
const TYPE_INFO: qemu_api::bindings::TypeInfo = qemu_api::type_info! { Self };
const TYPE_NAME: &'static CStr = crate::TYPE_PL011_LUMINARY; const INSTANCE_INIT: Option<unsafe fn(&mut Self)> = Some(Self::init);
const PARENT_TYPE_NAME: Option<&'static CStr> = Some(crate::TYPE_PL011);
const ABSTRACT: bool = false;
const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = Some(pl011_luminary_init);
const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
} }
impl DeviceImpl for PL011Luminary {}
impl_device_class!(PL011Luminary);

View file

@ -92,37 +92,3 @@ qemu_api::declare_properties! {
default = true default = true
), ),
} }
qemu_api::device_class_init! {
pl011_class_init,
props => PL011_PROPERTIES,
realize_fn => Some(pl011_realize),
legacy_reset_fn => Some(pl011_reset),
vmsd => VMSTATE_PL011,
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
pub unsafe extern "C" fn pl011_realize(dev: *mut DeviceState, _errp: *mut *mut Error) {
unsafe {
assert!(!dev.is_null());
let mut state = NonNull::new_unchecked(dev.cast::<PL011State>());
state.as_mut().realize();
}
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
pub unsafe extern "C" fn pl011_reset(dev: *mut DeviceState) {
unsafe {
assert!(!dev.is_null());
let mut state = NonNull::new_unchecked(dev.cast::<PL011State>());
state.as_mut().reset();
}
}

View file

@ -14,28 +14,15 @@
//! the [`registers`] module for register types. //! the [`registers`] module for register types.
#![deny( #![deny(
rustdoc::broken_intra_doc_links,
rustdoc::redundant_explicit_links,
clippy::correctness, clippy::correctness,
clippy::suspicious, clippy::suspicious,
clippy::complexity, clippy::complexity,
clippy::perf, clippy::perf,
clippy::cargo, clippy::cargo,
clippy::nursery, clippy::nursery,
clippy::style, clippy::style
// restriction group
clippy::dbg_macro,
clippy::as_underscore,
clippy::assertions_on_result_states,
// pedantic group
clippy::doc_markdown,
clippy::borrow_as_ptr,
clippy::cast_lossless,
clippy::option_if_let_else,
clippy::missing_const_for_fn,
clippy::cognitive_complexity,
clippy::missing_safety_doc,
)] )]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::result_unit_err)] #![allow(clippy::result_unit_err)]
extern crate bilge; extern crate bilge;

View file

@ -33,7 +33,9 @@ unsafe extern "C" fn pl011_read(opaque: *mut c_void, addr: hwaddr, size: c_uint)
// SAFETY: self.char_backend is a valid CharBackend instance after it's been // SAFETY: self.char_backend is a valid CharBackend instance after it's been
// initialized in realize(). // initialized in realize().
let cb_ptr = unsafe { core::ptr::addr_of_mut!(state.as_mut().char_backend) }; let cb_ptr = unsafe { core::ptr::addr_of_mut!(state.as_mut().char_backend) };
unsafe { qemu_chr_fe_accept_input(cb_ptr) }; unsafe {
qemu_chr_fe_accept_input(cb_ptr);
}
val val
} }

View file

@ -2,3 +2,25 @@ subdir('qemu-api-macros')
subdir('qemu-api') subdir('qemu-api')
subdir('hw') subdir('hw')
cargo = find_program('cargo', required: false)
if cargo.found()
run_target('clippy',
command: [config_host['MESON'], 'devenv',
'--workdir', '@CURRENT_SOURCE_DIR@',
cargo, 'clippy', '--tests'],
depends: bindings_rs)
run_target('rustfmt',
command: [config_host['MESON'], 'devenv',
'--workdir', '@CURRENT_SOURCE_DIR@',
cargo, 'fmt'],
depends: bindings_rs)
run_target('rustdoc',
command: [config_host['MESON'], 'devenv',
'--workdir', '@CURRENT_SOURCE_DIR@',
cargo, 'doc', '--no-deps', '--document-private-items'],
depends: bindings_rs)
endif

View file

@ -20,3 +20,6 @@ proc-macro = true
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
syn = { version = "2", features = ["extra-traits"] } syn = { version = "2", features = ["extra-traits"] }
[lints]
workspace = true

View file

@ -1,2 +1,2 @@
# Ignore generated bindings file overrides. # Ignore generated bindings file overrides.
src/bindings.rs /src/bindings.inc.rs

View file

@ -20,9 +20,9 @@ qemu_api_macros = { path = "../qemu-api-macros" }
version_check = "~0.9" version_check = "~0.9"
[features] [features]
default = [] default = ["debug_cell"]
allocator = [] allocator = []
debug_cell = []
[lints.rust] [lints]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(MESON)', 'cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)', workspace = true
'cfg(has_offset_of)'] }

View file

@ -5,13 +5,15 @@ This library exports helper Rust types, Rust macros and C FFI bindings for inter
The C bindings can be generated with `bindgen`, using this build target: The C bindings can be generated with `bindgen`, using this build target:
```console ```console
$ ninja bindings.rs $ make bindings.inc.rs
``` ```
## Generate Rust documentation ## Generate Rust documentation
To generate docs for this crate, including private items: Common Cargo tasks can be performed from the QEMU build directory
```sh ```console
cargo doc --no-deps --document-private-items $ make clippy
$ make rustfmt
$ make rustdoc
``` ```

View file

@ -2,22 +2,47 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> // Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
use std::path::Path; #[cfg(unix)]
use std::os::unix::fs::symlink as symlink_file;
#[cfg(windows)]
use std::os::windows::fs::symlink_file;
use std::{env, fs::remove_file, io::Result, path::Path};
use version_check as rustc; use version_check as rustc;
fn main() { fn main() -> Result<()> {
if !Path::new("src/bindings.rs").exists() { // Placing bindings.inc.rs in the source directory is supported
panic!( // but not documented or encouraged.
"No generated C bindings found! Either build them manually with bindgen or with meson \ let path = env::var("MESON_BUILD_ROOT")
(`ninja bindings.rs`) and copy them to src/bindings.rs, or build through meson." .unwrap_or_else(|_| format!("{}/src", env!("CARGO_MANIFEST_DIR")));
);
let file = format!("{}/bindings.inc.rs", path);
let file = Path::new(&file);
if !Path::new(&file).exists() {
panic!(concat!(
"\n",
" No generated C bindings found! Maybe you wanted one of\n",
" `make clippy`, `make rustfmt`, `make rustdoc`?\n",
"\n",
" For other uses of `cargo`, start a subshell with\n",
" `pyvenv/bin/meson devenv`, or point MESON_BUILD_ROOT to\n",
" the top of the build tree."
));
} }
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = format!("{}/bindings.inc.rs", out_dir);
let dest_path = Path::new(&dest_path);
if dest_path.symlink_metadata().is_ok() {
remove_file(dest_path)?;
}
symlink_file(file, dest_path)?;
// Check for available rustc features // Check for available rustc features
if rustc::is_min_version("1.77.0").unwrap_or(false) { if rustc::is_min_version("1.77.0").unwrap_or(false) {
println!("cargo:rustc-cfg=has_offset_of"); println!("cargo:rustc-cfg=has_offset_of");
} }
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
Ok(())
} }

View file

@ -1,18 +1,30 @@
_qemu_api_cfg = ['--cfg', 'MESON'] _qemu_api_cfg = run_command(rustc_args,
'--config-headers', config_host_h, '--features', files('Cargo.toml'),
capture: true, check: true).stdout().strip().splitlines()
# _qemu_api_cfg += ['--cfg', 'feature="allocator"'] # _qemu_api_cfg += ['--cfg', 'feature="allocator"']
if rustc.version().version_compare('>=1.77.0') if rustc.version().version_compare('>=1.77.0')
_qemu_api_cfg += ['--cfg', 'has_offset_of'] _qemu_api_cfg += ['--cfg', 'has_offset_of']
endif endif
if get_option('debug_mutex')
_qemu_api_cfg += ['--feature', 'debug_cell']
endif
_qemu_api_rs = static_library( _qemu_api_rs = static_library(
'qemu_api', 'qemu_api',
structured_sources( structured_sources(
[ [
'src/lib.rs', 'src/lib.rs',
'src/bindings.rs',
'src/bitops.rs',
'src/cell.rs',
'src/c_str.rs', 'src/c_str.rs',
'src/definitions.rs', 'src/definitions.rs',
'src/device_class.rs', 'src/device_class.rs',
'src/irq.rs',
'src/offset_of.rs', 'src/offset_of.rs',
'src/prelude.rs',
'src/sysbus.rs',
'src/vmstate.rs', 'src/vmstate.rs',
'src/zeroable.rs', 'src/zeroable.rs',
], ],

View file

@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#![allow(
dead_code,
improper_ctypes_definitions,
improper_ctypes,
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
unsafe_op_in_unsafe_fn,
clippy::pedantic,
clippy::restriction,
clippy::style,
clippy::missing_const_for_fn,
clippy::useless_transmute,
clippy::missing_safety_doc
)]
#[cfg(MESON)]
include!("bindings.inc.rs");
#[cfg(not(MESON))]
include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs"));
unsafe impl Send for Property {}
unsafe impl Sync for Property {}
unsafe impl Sync for TypeInfo {}
unsafe impl Sync for VMStateDescription {}
unsafe impl Sync for VMStateField {}
unsafe impl Sync for VMStateInfo {}

119
rust/qemu-api/src/bitops.rs Normal file
View file

@ -0,0 +1,119 @@
// Copyright (C) 2024 Intel Corporation.
// Author(s): Zhao Liu <zhai1.liu@intel.com>
// SPDX-License-Identifier: GPL-2.0-or-later
//! This module provides bit operation extensions to integer types.
//! It is usually included via the `qemu_api` prelude.
use std::ops::{
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign,
Mul, MulAssign, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign,
};
/// Trait for extensions to integer types
pub trait IntegerExt:
Add<Self, Output = Self> + AddAssign<Self> +
BitAnd<Self, Output = Self> + BitAndAssign<Self> +
BitOr<Self, Output = Self> + BitOrAssign<Self> +
BitXor<Self, Output = Self> + BitXorAssign<Self> +
Copy +
Div<Self, Output = Self> + DivAssign<Self> +
Eq +
Mul<Self, Output = Self> + MulAssign<Self> +
Not<Output = Self> + Ord + PartialOrd +
Rem<Self, Output = Self> + RemAssign<Self> +
Shl<Self, Output = Self> + ShlAssign<Self> +
Shl<u32, Output = Self> + ShlAssign<u32> + // add more as needed
Shr<Self, Output = Self> + ShrAssign<Self> +
Shr<u32, Output = Self> + ShrAssign<u32> // add more as needed
{
const BITS: u32;
const MAX: Self;
const MIN: Self;
const ONE: Self;
const ZERO: Self;
#[inline]
#[must_use]
fn bit(start: u32) -> Self
{
debug_assert!(start < Self::BITS);
Self::ONE << start
}
#[inline]
#[must_use]
fn mask(start: u32, length: u32) -> Self
{
/* FIXME: Implement a more elegant check with error handling support? */
debug_assert!(start < Self::BITS && length > 0 && length <= Self::BITS - start);
(Self::MAX >> (Self::BITS - length)) << start
}
#[inline]
#[must_use]
fn deposit<U: IntegerExt>(self, start: u32, length: u32,
fieldval: U) -> Self
where Self: From<U>
{
debug_assert!(length <= U::BITS);
let mask = Self::mask(start, length);
(self & !mask) | ((Self::from(fieldval) << start) & mask)
}
#[inline]
#[must_use]
fn extract(self, start: u32, length: u32) -> Self
{
let mask = Self::mask(start, length);
(self & mask) >> start
}
}
macro_rules! impl_num_ext {
($type:ty) => {
impl IntegerExt for $type {
const BITS: u32 = <$type>::BITS;
const MAX: Self = <$type>::MAX;
const MIN: Self = <$type>::MIN;
const ONE: Self = 1;
const ZERO: Self = 0;
}
};
}
impl_num_ext!(u8);
impl_num_ext!(u16);
impl_num_ext!(u32);
impl_num_ext!(u64);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deposit() {
assert_eq!(15u32.deposit(8, 8, 1u32), 256 + 15);
assert_eq!(15u32.deposit(8, 1, 255u8), 256 + 15);
}
#[test]
fn test_extract() {
assert_eq!(15u32.extract(2, 4), 3);
}
#[test]
fn test_bit() {
assert_eq!(u8::bit(7), 128);
assert_eq!(u32::bit(16), 0x10000);
}
#[test]
fn test_mask() {
assert_eq!(u8::mask(7, 1), 128);
assert_eq!(u32::mask(8, 8), 0xff00);
}
}

822
rust/qemu-api/src/cell.rs Normal file
View file

@ -0,0 +1,822 @@
// SPDX-License-Identifier: MIT
//
// This file is based on library/core/src/cell.rs from
// Rust 1.82.0.
//
// 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.
//! BQL-protected mutable containers.
//!
//! Rust memory safety is based on this rule: Given an object `T`, it is only
//! possible to have one of the following:
//!
//! - Having several immutable references (`&T`) to the object (also known as
//! **aliasing**).
//! - Having one mutable reference (`&mut T`) to the object (also known as
//! **mutability**).
//!
//! This is enforced by the Rust compiler. However, there are situations where
//! this rule is not flexible enough. Sometimes it is required to have multiple
//! references to an object and yet mutate it. In particular, QEMU objects
//! usually have their pointer shared with the "outside world very early in
//! their lifetime", for example when they create their
//! [`MemoryRegion`s](crate::bindings::MemoryRegion). Therefore, individual
//! parts of a device must be made mutable in a controlled manner through the
//! use of cell types.
//!
//! [`BqlCell<T>`] and [`BqlRefCell<T>`] allow doing this via the Big QEMU Lock.
//! While they are essentially the same single-threaded primitives that are
//! available in `std::cell`, the BQL allows them to be used from a
//! multi-threaded context and to share references across threads, while
//! maintaining Rust's safety guarantees. For this reason, unlike
//! their `std::cell` counterparts, `BqlCell` and `BqlRefCell` implement the
//! `Sync` trait.
//!
//! BQL checks are performed in debug builds but can be optimized away in
//! release builds, providing runtime safety during development with no overhead
//! in production.
//!
//! The two provide different ways of handling interior mutability.
//! `BqlRefCell` is best suited for data that is primarily accessed by the
//! device's own methods, where multiple reads and writes can be grouped within
//! a single borrow and a mutable reference can be passed around. Instead,
//! [`BqlCell`] is a better choice when sharing small pieces of data with
//! external code (especially C code), because it provides simple get/set
//! operations that can be used one at a time.
//!
//! Warning: While `BqlCell` and `BqlRefCell` are similar to their `std::cell`
//! counterparts, they are not interchangeable. Using `std::cell` types in
//! QEMU device implementations is usually incorrect and can lead to
//! thread-safety issues.
//!
//! ## `BqlCell<T>`
//!
//! [`BqlCell<T>`] implements interior mutability by moving values in and out of
//! the cell. That is, an `&mut T` to the inner value can never be obtained as
//! long as the cell is shared. The value itself cannot be directly obtained
//! without copying it, cloning it, or replacing it with something else. This
//! type provides the following methods, all of which can be called only while
//! the BQL is held:
//!
//! - For types that implement [`Copy`], the [`get`](BqlCell::get) method
//! retrieves the current interior value by duplicating it.
//! - For types that implement [`Default`], the [`take`](BqlCell::take) method
//! replaces the current interior value with [`Default::default()`] and
//! returns the replaced value.
//! - All types have:
//! - [`replace`](BqlCell::replace): replaces the current interior value and
//! returns the replaced value.
//! - [`set`](BqlCell::set): this method replaces the interior value,
//! dropping the replaced value.
//!
//! ## `BqlRefCell<T>`
//!
//! [`BqlRefCell<T>`] uses Rust's lifetimes to implement "dynamic borrowing", a
//! process whereby one can claim temporary, exclusive, mutable access to the
//! inner value:
//!
//! ```ignore
//! fn clear_interrupts(&self, val: u32) {
//! // A mutable borrow gives read-write access to the registers
//! let mut regs = self.registers.borrow_mut();
//! let old = regs.interrupt_status();
//! regs.update_interrupt_status(old & !val);
//! }
//! ```
//!
//! Borrows for `BqlRefCell<T>`s are tracked at _runtime_, unlike Rust's native
//! reference types which are entirely tracked statically, at compile time.
//! Multiple immutable borrows are allowed via [`borrow`](BqlRefCell::borrow),
//! or a single mutable borrow via [`borrow_mut`](BqlRefCell::borrow_mut). The
//! thread will panic if these rules are violated or if the BQL is not held.
use std::{
cell::{Cell, UnsafeCell},
cmp::Ordering,
fmt,
marker::PhantomData,
mem,
ops::{Deref, DerefMut},
ptr::NonNull,
};
use crate::bindings;
// TODO: When building doctests do not include the actual BQL, because cargo
// does not know how to link them to libqemuutil. This can be fixed by
// running rustdoc from "meson test" instead of relying on cargo.
pub fn bql_locked() -> bool {
// SAFETY: the function does nothing but return a thread-local bool
!cfg!(MESON) || unsafe { bindings::bql_locked() }
}
fn bql_block_unlock(increase: bool) {
if cfg!(MESON) {
// SAFETY: this only adjusts a counter
unsafe {
bindings::bql_block_unlock(increase);
}
}
}
/// A mutable memory location that is protected by the Big QEMU Lock.
///
/// # Memory layout
///
/// `BqlCell<T>` has the same in-memory representation as its inner type `T`.
#[repr(transparent)]
pub struct BqlCell<T> {
value: UnsafeCell<T>,
}
// SAFETY: Same as for std::sync::Mutex. In the end this *is* a Mutex,
// except it is stored out-of-line
unsafe impl<T: Send> Send for BqlCell<T> {}
unsafe impl<T: Send> Sync for BqlCell<T> {}
impl<T: Copy> Clone for BqlCell<T> {
#[inline]
fn clone(&self) -> BqlCell<T> {
BqlCell::new(self.get())
}
}
impl<T: Default> Default for BqlCell<T> {
/// Creates a `BqlCell<T>`, with the `Default` value for T.
#[inline]
fn default() -> BqlCell<T> {
BqlCell::new(Default::default())
}
}
impl<T: PartialEq + Copy> PartialEq for BqlCell<T> {
#[inline]
fn eq(&self, other: &BqlCell<T>) -> bool {
self.get() == other.get()
}
}
impl<T: Eq + Copy> Eq for BqlCell<T> {}
impl<T: PartialOrd + Copy> PartialOrd for BqlCell<T> {
#[inline]
fn partial_cmp(&self, other: &BqlCell<T>) -> Option<Ordering> {
self.get().partial_cmp(&other.get())
}
}
impl<T: Ord + Copy> Ord for BqlCell<T> {
#[inline]
fn cmp(&self, other: &BqlCell<T>) -> Ordering {
self.get().cmp(&other.get())
}
}
impl<T> From<T> for BqlCell<T> {
/// Creates a new `BqlCell<T>` containing the given value.
fn from(t: T) -> BqlCell<T> {
BqlCell::new(t)
}
}
impl<T: fmt::Debug + Copy> fmt::Debug for BqlCell<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.get().fmt(f)
}
}
impl<T: fmt::Display + Copy> fmt::Display for BqlCell<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.get().fmt(f)
}
}
impl<T> BqlCell<T> {
/// Creates a new `BqlCell` containing the given value.
///
/// # Examples
///
/// ```
/// use qemu_api::cell::BqlCell;
///
/// let c = BqlCell::new(5);
/// ```
#[inline]
pub const fn new(value: T) -> BqlCell<T> {
BqlCell {
value: UnsafeCell::new(value),
}
}
/// Sets the contained value.
///
/// # Examples
///
/// ```
/// use qemu_api::cell::BqlCell;
///
/// let c = BqlCell::new(5);
///
/// c.set(10);
/// ```
#[inline]
pub fn set(&self, val: T) {
self.replace(val);
}
/// Replaces the contained value with `val`, and returns the old contained
/// value.
///
/// # Examples
///
/// ```
/// use qemu_api::cell::BqlCell;
///
/// let cell = BqlCell::new(5);
/// assert_eq!(cell.get(), 5);
/// assert_eq!(cell.replace(10), 5);
/// assert_eq!(cell.get(), 10);
/// ```
#[inline]
pub fn replace(&self, val: T) -> T {
assert!(bql_locked());
// SAFETY: This can cause data races if called from multiple threads,
// but it won't happen as long as C code accesses the value
// under BQL protection only.
mem::replace(unsafe { &mut *self.value.get() }, val)
}
/// Unwraps the value, consuming the cell.
///
/// # Examples
///
/// ```
/// use qemu_api::cell::BqlCell;
///
/// let c = BqlCell::new(5);
/// let five = c.into_inner();
///
/// assert_eq!(five, 5);
/// ```
pub fn into_inner(self) -> T {
assert!(bql_locked());
self.value.into_inner()
}
}
impl<T: Copy> BqlCell<T> {
/// Returns a copy of the contained value.
///
/// # Examples
///
/// ```
/// use qemu_api::cell::BqlCell;
///
/// let c = BqlCell::new(5);
///
/// let five = c.get();
/// ```
#[inline]
pub fn get(&self) -> T {
assert!(bql_locked());
// SAFETY: This can cause data races if called from multiple threads,
// but it won't happen as long as C code accesses the value
// under BQL protection only.
unsafe { *self.value.get() }
}
}
impl<T> BqlCell<T> {
/// Returns a raw pointer to the underlying data in this cell.
///
/// # Examples
///
/// ```
/// use qemu_api::cell::BqlCell;
///
/// let c = BqlCell::new(5);
///
/// let ptr = c.as_ptr();
/// ```
#[inline]
pub const fn as_ptr(&self) -> *mut T {
self.value.get()
}
}
impl<T: Default> BqlCell<T> {
/// Takes the value of the cell, leaving `Default::default()` in its place.
///
/// # Examples
///
/// ```
/// use qemu_api::cell::BqlCell;
///
/// let c = BqlCell::new(5);
/// let five = c.take();
///
/// assert_eq!(five, 5);
/// assert_eq!(c.into_inner(), 0);
/// ```
pub fn take(&self) -> T {
self.replace(Default::default())
}
}
/// A mutable memory location with dynamically checked borrow rules,
/// protected by the Big QEMU Lock.
///
/// See the [module-level documentation](self) for more.
///
/// # Memory layout
///
/// `BqlRefCell<T>` starts with the same in-memory representation as its
/// inner type `T`.
#[repr(C)]
pub struct BqlRefCell<T> {
// It is important that this is the first field (which is not the case
// for std::cell::BqlRefCell), so that we can use offset_of! on it.
// UnsafeCell and repr(C) both prevent usage of niches.
value: UnsafeCell<T>,
borrow: Cell<BorrowFlag>,
// Stores the location of the earliest currently active borrow.
// This gets updated whenever we go from having zero borrows
// to having a single borrow. When a borrow occurs, this gets included
// in the panic message
#[cfg(feature = "debug_cell")]
borrowed_at: Cell<Option<&'static std::panic::Location<'static>>>,
}
// Positive values represent the number of `BqlRef` active. Negative values
// represent the number of `BqlRefMut` active. Right now QEMU's implementation
// does not allow to create `BqlRefMut`s that refer to distinct, nonoverlapping
// components of a `BqlRefCell` (e.g., different ranges of a slice).
//
// `BqlRef` and `BqlRefMut` are both two words in size, and so there will likely
// never be enough `BqlRef`s or `BqlRefMut`s in existence to overflow half of
// the `usize` range. Thus, a `BorrowFlag` will probably never overflow or
// underflow. However, this is not a guarantee, as a pathological program could
// repeatedly create and then mem::forget `BqlRef`s or `BqlRefMut`s. Thus, all
// code must explicitly check for overflow and underflow in order to avoid
// unsafety, or at least behave correctly in the event that overflow or
// underflow happens (e.g., see BorrowRef::new).
type BorrowFlag = isize;
const UNUSED: BorrowFlag = 0;
#[inline(always)]
const fn is_writing(x: BorrowFlag) -> bool {
x < UNUSED
}
#[inline(always)]
const fn is_reading(x: BorrowFlag) -> bool {
x > UNUSED
}
impl<T> BqlRefCell<T> {
/// Creates a new `BqlRefCell` containing `value`.
///
/// # Examples
///
/// ```
/// use qemu_api::cell::BqlRefCell;
///
/// let c = BqlRefCell::new(5);
/// ```
#[inline]
pub const fn new(value: T) -> BqlRefCell<T> {
BqlRefCell {
value: UnsafeCell::new(value),
borrow: Cell::new(UNUSED),
#[cfg(feature = "debug_cell")]
borrowed_at: Cell::new(None),
}
}
}
// This ensures the panicking code is outlined from `borrow_mut` for
// `BqlRefCell`.
#[inline(never)]
#[cold]
#[cfg(feature = "debug_cell")]
fn panic_already_borrowed(source: &Cell<Option<&'static std::panic::Location<'static>>>) -> ! {
// If a borrow occurred, then we must already have an outstanding borrow,
// so `borrowed_at` will be `Some`
panic!("already borrowed at {:?}", source.take().unwrap())
}
#[inline(never)]
#[cold]
#[cfg(not(feature = "debug_cell"))]
fn panic_already_borrowed() -> ! {
panic!("already borrowed")
}
impl<T> BqlRefCell<T> {
#[inline]
#[allow(clippy::unused_self)]
fn panic_already_borrowed(&self) -> ! {
#[cfg(feature = "debug_cell")]
{
panic_already_borrowed(&self.borrowed_at)
}
#[cfg(not(feature = "debug_cell"))]
{
panic_already_borrowed()
}
}
/// Immutably borrows the wrapped value.
///
/// The borrow lasts until the returned `BqlRef` exits scope. Multiple
/// immutable borrows can be taken out at the same time.
///
/// # Panics
///
/// Panics if the value is currently mutably borrowed.
///
/// # Examples
///
/// ```
/// use qemu_api::cell::BqlRefCell;
///
/// let c = BqlRefCell::new(5);
///
/// let borrowed_five = c.borrow();
/// let borrowed_five2 = c.borrow();
/// ```
///
/// An example of panic:
///
/// ```should_panic
/// use qemu_api::cell::BqlRefCell;
///
/// let c = BqlRefCell::new(5);
///
/// let m = c.borrow_mut();
/// let b = c.borrow(); // this causes a panic
/// ```
#[inline]
#[track_caller]
pub fn borrow(&self) -> BqlRef<'_, T> {
if let Some(b) = BorrowRef::new(&self.borrow) {
// `borrowed_at` is always the *first* active borrow
if b.borrow.get() == 1 {
#[cfg(feature = "debug_cell")]
self.borrowed_at.set(Some(std::panic::Location::caller()));
}
bql_block_unlock(true);
// SAFETY: `BorrowRef` ensures that there is only immutable access
// to the value while borrowed.
let value = unsafe { NonNull::new_unchecked(self.value.get()) };
BqlRef { value, borrow: b }
} else {
self.panic_already_borrowed()
}
}
/// Mutably borrows the wrapped value.
///
/// The borrow lasts until the returned `BqlRefMut` or all `BqlRefMut`s
/// derived from it exit scope. The value cannot be borrowed while this
/// borrow is active.
///
/// # Panics
///
/// Panics if the value is currently borrowed.
///
/// # Examples
///
/// ```
/// use qemu_api::cell::BqlRefCell;
///
/// let c = BqlRefCell::new("hello".to_owned());
///
/// *c.borrow_mut() = "bonjour".to_owned();
///
/// assert_eq!(&*c.borrow(), "bonjour");
/// ```
///
/// An example of panic:
///
/// ```should_panic
/// use qemu_api::cell::BqlRefCell;
///
/// let c = BqlRefCell::new(5);
/// let m = c.borrow();
///
/// let b = c.borrow_mut(); // this causes a panic
/// ```
#[inline]
#[track_caller]
pub fn borrow_mut(&self) -> BqlRefMut<'_, T> {
if let Some(b) = BorrowRefMut::new(&self.borrow) {
#[cfg(feature = "debug_cell")]
{
self.borrowed_at.set(Some(std::panic::Location::caller()));
}
// SAFETY: this only adjusts a counter
bql_block_unlock(true);
// SAFETY: `BorrowRefMut` guarantees unique access.
let value = unsafe { NonNull::new_unchecked(self.value.get()) };
BqlRefMut {
value,
_borrow: b,
marker: PhantomData,
}
} else {
self.panic_already_borrowed()
}
}
/// Returns a raw pointer to the underlying data in this cell.
///
/// # Examples
///
/// ```
/// use qemu_api::cell::BqlRefCell;
///
/// let c = BqlRefCell::new(5);
///
/// let ptr = c.as_ptr();
/// ```
#[inline]
pub const fn as_ptr(&self) -> *mut T {
self.value.get()
}
}
// SAFETY: Same as for std::sync::Mutex. In the end this is a Mutex that is
// stored out-of-line. Even though BqlRefCell includes Cells, they are
// themselves protected by the Big QEMU Lock. Furtheremore, the Big QEMU
// Lock cannot be released while any borrows is active.
unsafe impl<T> Send for BqlRefCell<T> where T: Send {}
unsafe impl<T> Sync for BqlRefCell<T> {}
impl<T: Clone> Clone for BqlRefCell<T> {
/// # Panics
///
/// Panics if the value is currently mutably borrowed.
#[inline]
#[track_caller]
fn clone(&self) -> BqlRefCell<T> {
BqlRefCell::new(self.borrow().clone())
}
/// # Panics
///
/// Panics if `source` is currently mutably borrowed.
#[inline]
#[track_caller]
fn clone_from(&mut self, source: &Self) {
self.value.get_mut().clone_from(&source.borrow())
}
}
impl<T: Default> Default for BqlRefCell<T> {
/// Creates a `BqlRefCell<T>`, with the `Default` value for T.
#[inline]
fn default() -> BqlRefCell<T> {
BqlRefCell::new(Default::default())
}
}
impl<T: PartialEq> PartialEq for BqlRefCell<T> {
/// # Panics
///
/// Panics if the value in either `BqlRefCell` is currently mutably
/// borrowed.
#[inline]
fn eq(&self, other: &BqlRefCell<T>) -> bool {
*self.borrow() == *other.borrow()
}
}
impl<T: Eq> Eq for BqlRefCell<T> {}
impl<T: PartialOrd> PartialOrd for BqlRefCell<T> {
/// # Panics
///
/// Panics if the value in either `BqlRefCell` is currently mutably
/// borrowed.
#[inline]
fn partial_cmp(&self, other: &BqlRefCell<T>) -> Option<Ordering> {
self.borrow().partial_cmp(&*other.borrow())
}
}
impl<T: Ord> Ord for BqlRefCell<T> {
/// # Panics
///
/// Panics if the value in either `BqlRefCell` is currently mutably
/// borrowed.
#[inline]
fn cmp(&self, other: &BqlRefCell<T>) -> Ordering {
self.borrow().cmp(&*other.borrow())
}
}
impl<T> From<T> for BqlRefCell<T> {
/// Creates a new `BqlRefCell<T>` containing the given value.
fn from(t: T) -> BqlRefCell<T> {
BqlRefCell::new(t)
}
}
struct BorrowRef<'b> {
borrow: &'b Cell<BorrowFlag>,
}
impl<'b> BorrowRef<'b> {
#[inline]
fn new(borrow: &'b Cell<BorrowFlag>) -> Option<BorrowRef<'b>> {
let b = borrow.get().wrapping_add(1);
if !is_reading(b) {
// Incrementing borrow can result in a non-reading value (<= 0) in these cases:
// 1. It was < 0, i.e. there are writing borrows, so we can't allow a read
// borrow due to Rust's reference aliasing rules
// 2. It was isize::MAX (the max amount of reading borrows) and it overflowed
// into isize::MIN (the max amount of writing borrows) so we can't allow an
// additional read borrow because isize can't represent so many read borrows
// (this can only happen if you mem::forget more than a small constant amount
// of `BqlRef`s, which is not good practice)
None
} else {
// Incrementing borrow can result in a reading value (> 0) in these cases:
// 1. It was = 0, i.e. it wasn't borrowed, and we are taking the first read
// borrow
// 2. It was > 0 and < isize::MAX, i.e. there were read borrows, and isize is
// large enough to represent having one more read borrow
borrow.set(b);
Some(BorrowRef { borrow })
}
}
}
impl Drop for BorrowRef<'_> {
#[inline]
fn drop(&mut self) {
let borrow = self.borrow.get();
debug_assert!(is_reading(borrow));
self.borrow.set(borrow - 1);
bql_block_unlock(false)
}
}
impl Clone for BorrowRef<'_> {
#[inline]
fn clone(&self) -> Self {
BorrowRef::new(self.borrow).unwrap()
}
}
/// Wraps a borrowed reference to a value in a `BqlRefCell` box.
/// A wrapper type for an immutably borrowed value from a `BqlRefCell<T>`.
///
/// See the [module-level documentation](self) for more.
pub struct BqlRef<'b, T: 'b> {
// NB: we use a pointer instead of `&'b T` to avoid `noalias` violations, because a
// `BqlRef` argument doesn't hold immutability for its whole scope, only until it drops.
// `NonNull` is also covariant over `T`, just like we would have with `&T`.
value: NonNull<T>,
borrow: BorrowRef<'b>,
}
impl<T> Deref for BqlRef<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
// SAFETY: the value is accessible as long as we hold our borrow.
unsafe { self.value.as_ref() }
}
}
impl<'b, T> BqlRef<'b, T> {
/// Copies a `BqlRef`.
///
/// The `BqlRefCell` is already immutably borrowed, so this cannot fail.
///
/// This is an associated function that needs to be used as
/// `BqlRef::clone(...)`. A `Clone` implementation or a method would
/// interfere with the widespread use of `r.borrow().clone()` to clone
/// the contents of a `BqlRefCell`.
#[must_use]
#[inline]
#[allow(clippy::should_implement_trait)]
pub fn clone(orig: &BqlRef<'b, T>) -> BqlRef<'b, T> {
BqlRef {
value: orig.value,
borrow: orig.borrow.clone(),
}
}
}
impl<T: fmt::Debug> fmt::Debug for BqlRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for BqlRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
struct BorrowRefMut<'b> {
borrow: &'b Cell<BorrowFlag>,
}
impl<'b> BorrowRefMut<'b> {
#[inline]
fn new(borrow: &'b Cell<BorrowFlag>) -> Option<BorrowRefMut<'b>> {
// There must currently be no existing references when borrow_mut() is
// called, so we explicitly only allow going from UNUSED to UNUSED - 1.
match borrow.get() {
UNUSED => {
borrow.set(UNUSED - 1);
Some(BorrowRefMut { borrow })
}
_ => None,
}
}
}
impl Drop for BorrowRefMut<'_> {
#[inline]
fn drop(&mut self) {
let borrow = self.borrow.get();
debug_assert!(is_writing(borrow));
self.borrow.set(borrow + 1);
bql_block_unlock(false)
}
}
/// A wrapper type for a mutably borrowed value from a `BqlRefCell<T>`.
///
/// See the [module-level documentation](self) for more.
pub struct BqlRefMut<'b, T: 'b> {
// NB: we use a pointer instead of `&'b mut T` to avoid `noalias` violations, because a
// `BqlRefMut` argument doesn't hold exclusivity for its whole scope, only until it drops.
value: NonNull<T>,
_borrow: BorrowRefMut<'b>,
// `NonNull` is covariant over `T`, so we need to reintroduce invariance.
marker: PhantomData<&'b mut T>,
}
impl<T> Deref for BqlRefMut<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
// SAFETY: the value is accessible as long as we hold our borrow.
unsafe { self.value.as_ref() }
}
}
impl<T> DerefMut for BqlRefMut<'_, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
// SAFETY: the value is accessible as long as we hold our borrow.
unsafe { self.value.as_mut() }
}
}
impl<T: fmt::Debug> fmt::Debug for BqlRefMut<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for BqlRefMut<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}

View file

@ -8,20 +8,122 @@ use std::{ffi::CStr, os::raw::c_void};
use crate::bindings::{Object, ObjectClass, TypeInfo}; use crate::bindings::{Object, ObjectClass, TypeInfo};
/// Trait a type must implement to be registered with QEMU. unsafe extern "C" fn rust_instance_init<T: ObjectImpl>(obj: *mut Object) {
pub trait ObjectImpl { // SAFETY: obj is an instance of T, since rust_instance_init<T>
type Class; // is called from QOM core as the instance_init function
const TYPE_INFO: TypeInfo; // for class T
const TYPE_NAME: &'static CStr; unsafe { T::INSTANCE_INIT.unwrap()(&mut *obj.cast::<T>()) }
const PARENT_TYPE_NAME: Option<&'static CStr>;
const ABSTRACT: bool;
const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>;
const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)>;
} }
pub trait Class { unsafe extern "C" fn rust_instance_post_init<T: ObjectImpl>(obj: *mut Object) {
// SAFETY: obj is an instance of T, since rust_instance_post_init<T>
// is called from QOM core as the instance_post_init function
// for class T
//
// FIXME: it's not really guaranteed that there are no backpointers to
// obj; it's quite possible that they have been created by instance_init().
// The receiver should be &self, not &mut self.
T::INSTANCE_POST_INIT.unwrap()(unsafe { &mut *obj.cast::<T>() })
}
/// Trait exposed by all structs corresponding to QOM objects.
///
/// # Safety
///
/// For classes declared in C:
///
/// - `Class` and `TYPE` must match the data in the `TypeInfo`;
///
/// - the first field of the struct must be of the instance type corresponding
/// to the superclass, as declared in the `TypeInfo`
///
/// - likewise, the first field of the `Class` struct must be of the class type
/// corresponding to the superclass
///
/// For classes declared in Rust and implementing [`ObjectImpl`]:
///
/// - the struct must be `#[repr(C)]`;
///
/// - the first field of the struct must be of the instance struct corresponding
/// to the superclass, which is `ObjectImpl::ParentType`
///
/// - likewise, the first field of the `Class` must be of the class struct
/// corresponding to the superclass, which is `ObjectImpl::ParentType::Class`.
pub unsafe trait ObjectType: Sized {
/// The QOM class object corresponding to this struct. Not used yet.
type Class;
/// The name of the type, which can be passed to `object_new()` to
/// generate an instance of this type.
const TYPE_NAME: &'static CStr;
}
/// Trait a type must implement to be registered with QEMU.
pub trait ObjectImpl: ObjectType + ClassInitImpl {
/// The parent of the type. This should match the first field of
/// the struct that implements `ObjectImpl`:
type ParentType: ObjectType;
/// Whether the object can be instantiated
const ABSTRACT: bool = false;
const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
/// Function that is called to initialize an object. The parent class will
/// have already been initialized so the type is only responsible for
/// initializing its own members.
///
/// FIXME: The argument is not really a valid reference. `&mut
/// MaybeUninit<Self>` would be a better description.
const INSTANCE_INIT: Option<unsafe fn(&mut Self)> = None;
/// Function that is called to finish initialization of an object, once
/// `INSTANCE_INIT` functions have been called.
const INSTANCE_POST_INIT: Option<fn(&mut Self)> = None;
const TYPE_INFO: TypeInfo = TypeInfo {
name: Self::TYPE_NAME.as_ptr(),
parent: Self::ParentType::TYPE_NAME.as_ptr(),
instance_size: core::mem::size_of::<Self>(),
instance_align: core::mem::align_of::<Self>(),
instance_init: match Self::INSTANCE_INIT {
None => None,
Some(_) => Some(rust_instance_init::<Self>),
},
instance_post_init: match Self::INSTANCE_POST_INIT {
None => None,
Some(_) => Some(rust_instance_post_init::<Self>),
},
instance_finalize: Self::INSTANCE_FINALIZE,
abstract_: Self::ABSTRACT,
class_size: core::mem::size_of::<Self::Class>(),
class_init: <Self as ClassInitImpl>::CLASS_INIT,
class_base_init: <Self as ClassInitImpl>::CLASS_BASE_INIT,
class_data: core::ptr::null_mut(),
interfaces: core::ptr::null_mut(),
};
}
/// Trait used to fill in a class struct.
///
/// Each QOM class that has virtual methods describes them in a
/// _class struct_. Class structs include a parent field corresponding
/// to the vtable of the parent class, all the way up to [`ObjectClass`].
/// Each QOM type has one such class struct.
///
/// The Rust implementation of methods will usually come from a trait
/// like [`ObjectImpl`] or [`DeviceImpl`](crate::device_class::DeviceImpl).
pub trait ClassInitImpl {
/// Function that is called after all parent class initialization
/// has occurred. On entry, the virtual method pointers are set to
/// the default values coming from the parent classes; the function
/// can change them to override virtual methods of a parent class.
const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)>; const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)>;
/// Called on descendent classes after all parent class initialization
/// has occurred, but before the class itself is initialized. This
/// is only useful if a class is not a leaf, and can be used to undo
/// the effects of copying the contents of the parent's class struct
/// to the descendants.
const CLASS_BASE_INIT: Option< const CLASS_BASE_INIT: Option<
unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void), unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
>; >;
@ -64,28 +166,3 @@ macro_rules! module_init {
} }
}; };
} }
#[macro_export]
macro_rules! type_info {
($t:ty) => {
$crate::bindings::TypeInfo {
name: <$t as $crate::definitions::ObjectImpl>::TYPE_NAME.as_ptr(),
parent: if let Some(pname) = <$t as $crate::definitions::ObjectImpl>::PARENT_TYPE_NAME {
pname.as_ptr()
} else {
::core::ptr::null_mut()
},
instance_size: ::core::mem::size_of::<$t>(),
instance_align: ::core::mem::align_of::<$t>(),
instance_init: <$t as $crate::definitions::ObjectImpl>::INSTANCE_INIT,
instance_post_init: <$t as $crate::definitions::ObjectImpl>::INSTANCE_POST_INIT,
instance_finalize: <$t as $crate::definitions::ObjectImpl>::INSTANCE_FINALIZE,
abstract_: <$t as $crate::definitions::ObjectImpl>::ABSTRACT,
class_size: ::core::mem::size_of::<<$t as $crate::definitions::ObjectImpl>::Class>(),
class_init: <<$t as $crate::definitions::ObjectImpl>::Class as $crate::definitions::Class>::CLASS_INIT,
class_base_init: <<$t as $crate::definitions::ObjectImpl>::Class as $crate::definitions::Class>::CLASS_BASE_INIT,
class_data: ::core::ptr::null_mut(),
interfaces: ::core::ptr::null_mut(),
};
}
}

View file

@ -2,32 +2,112 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> // Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
use std::ffi::CStr; use std::{ffi::CStr, os::raw::c_void};
use crate::bindings; use crate::{
bindings::{self, DeviceClass, DeviceState, Error, ObjectClass, Property, VMStateDescription},
prelude::*,
zeroable::Zeroable,
};
/// Trait providing the contents of [`DeviceClass`].
pub trait DeviceImpl {
/// _Realization_ is the second stage of device creation. It contains
/// all operations that depend on device properties and can fail (note:
/// this is not yet supported for Rust devices).
///
/// If not `None`, the parent class's `realize` method is overridden
/// with the function pointed to by `REALIZE`.
const REALIZE: Option<fn(&mut Self)> = None;
/// If not `None`, the parent class's `reset` method is overridden
/// with the function pointed to by `RESET`.
///
/// Rust does not yet support the three-phase reset protocol; this is
/// usually okay for leaf classes.
const RESET: Option<fn(&mut Self)> = None;
/// An array providing the properties that the user can set on the
/// device. Not a `const` because referencing statics in constants
/// is unstable until Rust 1.83.0.
fn properties() -> &'static [Property] {
&[Zeroable::ZERO; 1]
}
/// A `VMStateDescription` providing the migration format for the device
/// Not a `const` because referencing statics in constants is unstable
/// until Rust 1.83.0.
fn vmsd() -> Option<&'static VMStateDescription> {
None
}
}
/// # Safety
///
/// This function is only called through the QOM machinery and
/// the `impl_device_class!` macro.
/// We expect the FFI user of this function to pass a valid pointer that
/// can be downcasted to type `T`. We also expect the device is
/// readable/writeable from one thread at any time.
unsafe extern "C" fn rust_realize_fn<T: DeviceImpl>(dev: *mut DeviceState, _errp: *mut *mut Error) {
assert!(!dev.is_null());
let state = dev.cast::<T>();
T::REALIZE.unwrap()(unsafe { &mut *state });
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer that
/// can be downcasted to type `T`. We also expect the device is
/// readable/writeable from one thread at any time.
unsafe extern "C" fn rust_reset_fn<T: DeviceImpl>(dev: *mut DeviceState) {
assert!(!dev.is_null());
let state = dev.cast::<T>();
T::RESET.unwrap()(unsafe { &mut *state });
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer that
/// can be downcasted to type `DeviceClass`, because `T` implements
/// `DeviceImpl`.
pub unsafe extern "C" fn rust_device_class_init<T: DeviceImpl>(
klass: *mut ObjectClass,
_: *mut c_void,
) {
let mut dc = ::core::ptr::NonNull::new(klass.cast::<DeviceClass>()).unwrap();
unsafe {
let dc = dc.as_mut();
if <T as DeviceImpl>::REALIZE.is_some() {
dc.realize = Some(rust_realize_fn::<T>);
}
if <T as DeviceImpl>::RESET.is_some() {
bindings::device_class_set_legacy_reset(dc, Some(rust_reset_fn::<T>));
}
if let Some(vmsd) = <T as DeviceImpl>::vmsd() {
dc.vmsd = vmsd;
}
bindings::device_class_set_props(dc, <T as DeviceImpl>::properties().as_ptr());
}
}
#[macro_export] #[macro_export]
macro_rules! device_class_init { macro_rules! impl_device_class {
($func:ident, props => $props:ident, realize_fn => $realize_fn:expr, legacy_reset_fn => $legacy_reset_fn:expr, vmsd => $vmsd:ident$(,)*) => { ($type:ty) => {
pub unsafe extern "C" fn $func( impl $crate::definitions::ClassInitImpl for $type {
klass: *mut $crate::bindings::ObjectClass, const CLASS_INIT: Option<
_: *mut ::std::os::raw::c_void, unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut ::std::os::raw::c_void),
) { > = Some($crate::device_class::rust_device_class_init::<$type>);
let mut dc = const CLASS_BASE_INIT: Option<
::core::ptr::NonNull::new(klass.cast::<$crate::bindings::DeviceClass>()).unwrap(); unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut ::std::os::raw::c_void),
unsafe { > = None;
dc.as_mut().realize = $realize_fn;
dc.as_mut().vmsd = &$vmsd;
$crate::bindings::device_class_set_legacy_reset(dc.as_mut(), $legacy_reset_fn);
$crate::bindings::device_class_set_props(dc.as_mut(), $props.as_ptr());
}
} }
}; };
} }
#[macro_export] #[macro_export]
macro_rules! define_property { macro_rules! define_property {
($name:expr, $state:ty, $field:ident, $prop:expr, $type:expr, default = $defval:expr$(,)*) => { ($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty, default = $defval:expr$(,)*) => {
$crate::bindings::Property { $crate::bindings::Property {
// use associated function syntax for type checking // use associated function syntax for type checking
name: ::std::ffi::CStr::as_ptr($name), name: ::std::ffi::CStr::as_ptr($name),
@ -38,7 +118,7 @@ macro_rules! define_property {
..$crate::zeroable::Zeroable::ZERO ..$crate::zeroable::Zeroable::ZERO
} }
}; };
($name:expr, $state:ty, $field:ident, $prop:expr, $type:expr$(,)*) => { ($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty$(,)*) => {
$crate::bindings::Property { $crate::bindings::Property {
// use associated function syntax for type checking // use associated function syntax for type checking
name: ::std::ffi::CStr::as_ptr($name), name: ::std::ffi::CStr::as_ptr($name),
@ -67,8 +147,8 @@ macro_rules! declare_properties {
}; };
} }
// workaround until we can use --generate-cstr in bindgen. unsafe impl ObjectType for bindings::DeviceState {
pub const TYPE_DEVICE: &CStr = type Class = bindings::DeviceClass;
const TYPE_NAME: &'static CStr =
unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_DEVICE) }; unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_DEVICE) };
pub const TYPE_SYS_BUS_DEVICE: &CStr = }
unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_SYS_BUS_DEVICE) };

91
rust/qemu-api/src/irq.rs Normal file
View file

@ -0,0 +1,91 @@
// Copyright 2024 Red Hat, Inc.
// Author(s): Paolo Bonzini <pbonzini@redhat.com>
// SPDX-License-Identifier: GPL-2.0-or-later
//! Bindings for interrupt sources
use core::ptr;
use std::{marker::PhantomData, os::raw::c_int};
use crate::{
bindings::{qemu_set_irq, IRQState},
prelude::*,
};
/// Interrupt sources are used by devices to pass changes to a value (typically
/// a boolean). The interrupt sink is usually an interrupt controller or
/// GPIO controller.
///
/// As far as devices are concerned, interrupt sources are always active-high:
/// for example, `InterruptSource<bool>`'s [`raise`](InterruptSource::raise)
/// method sends a `true` value to the sink. If the guest has to see a
/// different polarity, that change is performed by the board between the
/// device and the interrupt controller.
///
/// Interrupts are implemented as a pointer to the interrupt "sink", which has
/// type [`IRQState`]. A device exposes its source as a QOM link property using
/// a function such as
/// [`SysBusDevice::init_irq`](crate::sysbus::SysBusDevice::init_irq), and
/// initially leaves the pointer to a NULL value, representing an unconnected
/// interrupt. To connect it, whoever creates the device fills the pointer with
/// the sink's `IRQState *`, for example using `sysbus_connect_irq`. Because
/// devices are generally shared objects, interrupt sources are an example of
/// the interior mutability pattern.
///
/// Interrupt sources can only be triggered under the Big QEMU Lock; `BqlCell`
/// allows access from whatever thread has it.
#[derive(Debug)]
#[repr(transparent)]
pub struct InterruptSource<T = bool>
where
c_int: From<T>,
{
cell: BqlCell<*mut IRQState>,
_marker: PhantomData<T>,
}
impl InterruptSource<bool> {
/// Send a low (`false`) value to the interrupt sink.
pub fn lower(&self) {
self.set(false);
}
/// Send a high-low pulse to the interrupt sink.
pub fn pulse(&self) {
self.set(true);
self.set(false);
}
/// Send a high (`true`) value to the interrupt sink.
pub fn raise(&self) {
self.set(true);
}
}
impl<T> InterruptSource<T>
where
c_int: From<T>,
{
/// Send `level` to the interrupt sink.
pub fn set(&self, level: T) {
let ptr = self.cell.get();
// SAFETY: the pointer is retrieved under the BQL and remains valid
// until the BQL is released, which is after qemu_set_irq() is entered.
unsafe {
qemu_set_irq(ptr, level.into());
}
}
pub(crate) const fn as_ptr(&self) -> *mut *mut IRQState {
self.cell.as_ptr()
}
}
impl Default for InterruptSource {
fn default() -> Self {
InterruptSource {
cell: BqlCell::new(ptr::null_mut()),
_marker: PhantomData,
}
}
}

View file

@ -4,35 +4,22 @@
#![cfg_attr(not(MESON), doc = include_str!("../README.md"))] #![cfg_attr(not(MESON), doc = include_str!("../README.md"))]
#[allow(
dead_code,
improper_ctypes_definitions,
improper_ctypes,
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
unsafe_op_in_unsafe_fn,
clippy::missing_const_for_fn,
clippy::too_many_arguments,
clippy::approx_constant,
clippy::use_self,
clippy::useless_transmute,
clippy::missing_safety_doc,
)]
#[rustfmt::skip] #[rustfmt::skip]
pub mod bindings; pub mod bindings;
unsafe impl Send for bindings::Property {} // preserve one-item-per-"use" syntax, it is clearer
unsafe impl Sync for bindings::Property {} // for prelude-like modules
unsafe impl Sync for bindings::TypeInfo {} #[rustfmt::skip]
unsafe impl Sync for bindings::VMStateDescription {} pub mod prelude;
unsafe impl Sync for bindings::VMStateField {}
unsafe impl Sync for bindings::VMStateInfo {}
pub mod bitops;
pub mod c_str; pub mod c_str;
pub mod cell;
pub mod definitions; pub mod definitions;
pub mod device_class; pub mod device_class;
pub mod irq;
pub mod offset_of; pub mod offset_of;
pub mod sysbus;
pub mod vmstate; pub mod vmstate;
pub mod zeroable; pub mod zeroable;

View file

@ -0,0 +1,10 @@
// Copyright 2024 Red Hat, Inc.
// Author(s): Paolo Bonzini <pbonzini@redhat.com>
// SPDX-License-Identifier: GPL-2.0-or-later
pub use crate::bitops::IntegerExt;
pub use crate::cell::BqlCell;
pub use crate::cell::BqlRefCell;
pub use crate::definitions::ObjectType;

View file

@ -0,0 +1,33 @@
// Copyright 2024 Red Hat, Inc.
// Author(s): Paolo Bonzini <pbonzini@redhat.com>
// SPDX-License-Identifier: GPL-2.0-or-later
use std::{ffi::CStr, ptr::addr_of};
pub use bindings::{SysBusDevice, SysBusDeviceClass};
use crate::{bindings, cell::bql_locked, irq::InterruptSource, prelude::*};
unsafe impl ObjectType for SysBusDevice {
type Class = SysBusDeviceClass;
const TYPE_NAME: &'static CStr =
unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_SYS_BUS_DEVICE) };
}
impl SysBusDevice {
/// Return `self` cast to a mutable pointer, for use in calls to C code.
const fn as_mut_ptr(&self) -> *mut SysBusDevice {
addr_of!(*self) as *mut _
}
/// Expose an interrupt source outside the device as a qdev GPIO output.
/// Note that the ordering of calls to `init_irq` is important, since
/// whoever creates the sysbus device will refer to the interrupts with
/// a number that corresponds to the order of calls to `init_irq`.
pub fn init_irq(&self, irq: &InterruptSource) {
assert!(bql_locked());
unsafe {
bindings::sysbus_init_irq(self.as_mut_ptr(), irq.as_ptr());
}
}
}

View file

@ -7,9 +7,9 @@ use std::ptr;
/// behavior. This trait in principle could be implemented as just: /// behavior. This trait in principle could be implemented as just:
/// ///
/// ``` /// ```
/// const ZERO: Self = unsafe { /// pub unsafe trait Zeroable: Default {
/// ::core::mem::MaybeUninit::<$crate::bindings::Property>::zeroed().assume_init() /// const ZERO: Self = unsafe { ::core::mem::MaybeUninit::<Self>::zeroed().assume_init() };
/// }, /// }
/// ``` /// ```
/// ///
/// The need for a manual implementation is only because `zeroed()` cannot /// The need for a manual implementation is only because `zeroed()` cannot

View file

@ -2,14 +2,11 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> // Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
use std::{ffi::CStr, os::raw::c_void}; use std::ffi::CStr;
use qemu_api::{ use qemu_api::{
bindings::*, bindings::*, c_str, declare_properties, define_property, definitions::ObjectImpl,
c_str, declare_properties, define_property, device_class::DeviceImpl, impl_device_class, prelude::*, zeroable::Zeroable,
definitions::{Class, ObjectImpl},
device_class, device_class_init,
zeroable::Zeroable,
}; };
#[test] #[test]
@ -45,35 +42,29 @@ fn test_device_decl_macros() {
), ),
} }
device_class_init! { unsafe impl ObjectType for DummyState {
dummy_class_init, type Class = DummyClass;
props => DUMMY_PROPERTIES, const TYPE_NAME: &'static CStr = c_str!("dummy");
realize_fn => None,
legacy_reset_fn => None,
vmsd => VMSTATE,
} }
impl ObjectImpl for DummyState { impl ObjectImpl for DummyState {
type Class = DummyClass; type ParentType = DeviceState;
const TYPE_INFO: qemu_api::bindings::TypeInfo = qemu_api::type_info! { Self };
const TYPE_NAME: &'static CStr = c_str!("dummy");
const PARENT_TYPE_NAME: Option<&'static CStr> = Some(device_class::TYPE_DEVICE);
const ABSTRACT: bool = false; const ABSTRACT: bool = false;
const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)> = None;
} }
impl Class for DummyClass { impl DeviceImpl for DummyState {
const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)> = fn properties() -> &'static [Property] {
Some(dummy_class_init); &DUMMY_PROPERTIES
const CLASS_BASE_INIT: Option<
unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
> = None;
} }
fn vmsd() -> Option<&'static VMStateDescription> {
Some(&VMSTATE)
}
}
impl_device_class!(DummyState);
unsafe { unsafe {
module_call_init(module_init_type::MODULE_INIT_QOM); module_call_init(module_init_type::MODULE_INIT_QOM);
object_unref(object_new(DummyState::TYPE_NAME.as_ptr()) as *mut _); object_unref(object_new(DummyState::TYPE_NAME.as_ptr()).cast());
} }
} }

View file

@ -901,26 +901,6 @@ class TypeRegisterCall(FileMatch):
regexp = S(r'^[ \t]*', NAMED('func_name', 'type_register'), regexp = S(r'^[ \t]*', NAMED('func_name', 'type_register'),
r'\s*\(&\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);[ \t]*\n') r'\s*\(&\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);[ \t]*\n')
class MakeTypeRegisterStatic(TypeRegisterCall):
"""Make type_register() call static if variable is static const"""
def gen_patches(self):
var = self.file.find_match(TypeInfoVar, self.name)
if var is None:
self.warn("can't find TypeInfo var declaration for %s", self.name)
return
if var.is_static() and var.is_const():
yield self.group_match('func_name').make_patch('type_register_static')
class MakeTypeRegisterNotStatic(TypeRegisterStaticCall):
"""Make type_register() call static if variable is static const"""
def gen_patches(self):
var = self.file.find_match(TypeInfoVar, self.name)
if var is None:
self.warn("can't find TypeInfo var declaration for %s", self.name)
return
if not var.is_static() or not var.is_const():
yield self.group_match('func_name').make_patch('type_register')
class TypeInfoMacro(FileMatch): class TypeInfoMacro(FileMatch):
"""TYPE_INFO macro usage""" """TYPE_INFO macro usage"""
regexp = S(r'^[ \t]*TYPE_INFO\s*\(\s*', NAMED('name', RE_IDENTIFIER), r'\s*\)[ \t]*;?[ \t]*\n') regexp = S(r'^[ \t]*TYPE_INFO\s*\(\s*', NAMED('name', RE_IDENTIFIER), r'\s*\)[ \t]*;?[ \t]*\n')

View file

@ -25,31 +25,110 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
import argparse import argparse
from dataclasses import dataclass
import logging import logging
from pathlib import Path
from typing import Any, Iterable, List, Mapping, Optional, Set
from typing import List try:
import tomllib
except ImportError:
import tomli as tomllib
STRICT_LINTS = {"unknown_lints", "warnings"}
def generate_cfg_flags(header: str) -> List[str]: class CargoTOML:
tomldata: Mapping[Any, Any]
workspace_data: Mapping[Any, Any]
check_cfg: Set[str]
def __init__(self, path: Optional[str], workspace: Optional[str]):
if path is not None:
with open(path, 'rb') as f:
self.tomldata = tomllib.load(f)
else:
self.tomldata = {"lints": {"workspace": True}}
if workspace is not None:
with open(workspace, 'rb') as f:
self.workspace_data = tomllib.load(f)
if "workspace" not in self.workspace_data:
self.workspace_data["workspace"] = {}
self.check_cfg = set(self.find_check_cfg())
def find_check_cfg(self) -> Iterable[str]:
toml_lints = self.lints
rust_lints = toml_lints.get("rust", {})
cfg_lint = rust_lints.get("unexpected_cfgs", {})
return cfg_lint.get("check-cfg", [])
@property
def lints(self) -> Mapping[Any, Any]:
return self.get_table("lints", True)
def get_table(self, key: str, can_be_workspace: bool = False) -> Mapping[Any, Any]:
table = self.tomldata.get(key, {})
if can_be_workspace and table.get("workspace", False) is True:
table = self.workspace_data["workspace"].get(key, {})
return table
@dataclass
class LintFlag:
flags: List[str]
priority: int
def generate_lint_flags(cargo_toml: CargoTOML, strict_lints: bool) -> Iterable[str]:
"""Converts Cargo.toml lints to rustc -A/-D/-F/-W flags."""
toml_lints = cargo_toml.lints
lint_list = []
for k, v in toml_lints.items():
prefix = "" if k == "rust" else k + "::"
for lint, data in v.items():
level = data if isinstance(data, str) else data["level"]
priority = 0 if isinstance(data, str) else data.get("priority", 0)
if level == "deny":
flag = "-D"
elif level == "allow":
flag = "-A"
elif level == "warn":
flag = "-W"
elif level == "forbid":
flag = "-F"
else:
raise Exception(f"invalid level {level} for {prefix}{lint}")
# This may change if QEMU ever invokes clippy-driver or rustdoc by
# hand. For now, check the syntax but do not add non-rustc lints to
# the command line.
if k == "rust" and not (strict_lints and lint in STRICT_LINTS):
lint_list.append(LintFlag(flags=[flag, prefix + lint], priority=priority))
if strict_lints:
for lint in STRICT_LINTS:
lint_list.append(LintFlag(flags=["-D", lint], priority=1000000))
lint_list.sort(key=lambda x: x.priority)
for lint in lint_list:
yield from lint.flags
def generate_cfg_flags(header: str, cargo_toml: CargoTOML) -> Iterable[str]:
"""Converts defines from config[..].h headers to rustc --cfg flags.""" """Converts defines from config[..].h headers to rustc --cfg flags."""
def cfg_name(name: str) -> str:
"""Filter function for C #defines"""
if (
name.startswith("CONFIG_")
or name.startswith("TARGET_")
or name.startswith("HAVE_")
):
return name
return ""
with open(header, encoding="utf-8") as cfg: with open(header, encoding="utf-8") as cfg:
config = [l.split()[1:] for l in cfg if l.startswith("#define")] config = [l.split()[1:] for l in cfg if l.startswith("#define")]
cfg_list = [] cfg_list = []
for cfg in config: for cfg in config:
name = cfg_name(cfg[0]) name = cfg[0]
if not name: if f'cfg({name})' not in cargo_toml.check_cfg:
continue continue
if len(cfg) >= 2 and cfg[1] != "1": if len(cfg) >= 2 and cfg[1] != "1":
continue continue
@ -59,7 +138,6 @@ def generate_cfg_flags(header: str) -> List[str]:
def main() -> None: def main() -> None:
# pylint: disable=missing-function-docstring
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("-v", "--verbose", action="store_true") parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument( parser.add_argument(
@ -71,12 +149,83 @@ def main() -> None:
required=False, required=False,
default=[], default=[],
) )
parser.add_argument(
metavar="TOML_FILE",
action="store",
dest="cargo_toml",
help="path to Cargo.toml file",
nargs='?',
)
parser.add_argument(
"--workspace",
metavar="DIR",
action="store",
dest="workspace",
help="path to root of the workspace",
required=False,
default=None,
)
parser.add_argument(
"--features",
action="store_true",
dest="features",
help="generate --check-cfg arguments for features",
required=False,
default=None,
)
parser.add_argument(
"--lints",
action="store_true",
dest="lints",
help="generate arguments from [lints] table",
required=False,
default=None,
)
parser.add_argument(
"--rustc-version",
metavar="VERSION",
dest="rustc_version",
action="store",
help="version of rustc",
required=False,
default="1.0.0",
)
parser.add_argument(
"--strict-lints",
action="store_true",
dest="strict_lints",
help="apply stricter checks (for nightly Rust)",
default=False,
)
args = parser.parse_args() args = parser.parse_args()
if args.verbose: if args.verbose:
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
logging.debug("args: %s", args) logging.debug("args: %s", args)
rustc_version = tuple((int(x) for x in args.rustc_version.split('.')[0:2]))
if args.workspace:
workspace_cargo_toml = Path(args.workspace, "Cargo.toml").resolve()
cargo_toml = CargoTOML(args.cargo_toml, str(workspace_cargo_toml))
else:
cargo_toml = CargoTOML(args.cargo_toml, None)
if args.lints:
for tok in generate_lint_flags(cargo_toml, args.strict_lints):
print(tok)
if rustc_version >= (1, 80):
if args.lints:
for cfg in sorted(cargo_toml.check_cfg):
print("--check-cfg")
print(cfg)
if args.features:
for feature in cargo_toml.get_table("features"):
if feature != "default":
print("--check-cfg")
print(f'cfg(feature,values("{feature}"))')
for header in args.config_headers: for header in args.config_headers:
for tok in generate_cfg_flags(header): for tok in generate_cfg_flags(header, cargo_toml):
print(tok) print(tok)

View file

@ -1,6 +1,8 @@
#include "qemu/osdep.h" #include "qemu/osdep.h"
#include "qemu/main-loop.h" #include "qemu/main-loop.h"
static uint32_t bql_unlock_blocked;
bool bql_locked(void) bool bql_locked(void)
{ {
return false; return false;
@ -12,4 +14,17 @@ void bql_lock_impl(const char *file, int line)
void bql_unlock(void) void bql_unlock(void)
{ {
assert(!bql_unlock_blocked);
}
void bql_block_unlock(bool increase)
{
uint32_t new_value;
assert(bql_locked());
/* check for overflow! */
new_value = bql_unlock_blocked + increase - !increase;
assert((new_value > bql_unlock_blocked) == increase);
bql_unlock_blocked = new_value;
} }

View file

@ -514,6 +514,20 @@ bool qemu_in_vcpu_thread(void)
QEMU_DEFINE_STATIC_CO_TLS(bool, bql_locked) QEMU_DEFINE_STATIC_CO_TLS(bool, bql_locked)
static uint32_t bql_unlock_blocked;
void bql_block_unlock(bool increase)
{
uint32_t new_value;
assert(bql_locked());
/* check for overflow! */
new_value = bql_unlock_blocked + increase - !increase;
assert((new_value > bql_unlock_blocked) == increase);
bql_unlock_blocked = new_value;
}
bool bql_locked(void) bool bql_locked(void)
{ {
return get_bql_locked(); return get_bql_locked();
@ -540,6 +554,7 @@ void bql_lock_impl(const char *file, int line)
void bql_unlock(void) void bql_unlock(void)
{ {
g_assert(bql_locked()); g_assert(bql_locked());
g_assert(!bql_unlock_blocked);
set_bql_locked(false); set_bql_locked(false);
qemu_mutex_unlock(&bql); qemu_mutex_unlock(&bql);
} }

View file

@ -2765,7 +2765,7 @@ void arm_cpu_register(const ARMCPUInfo *info)
}; };
type_info.name = g_strdup_printf("%s-" TYPE_ARM_CPU, info->name); type_info.name = g_strdup_printf("%s-" TYPE_ARM_CPU, info->name);
type_register(&type_info); type_register_static(&type_info);
g_free((void *)type_info.name); g_free((void *)type_info.name);
} }

View file

@ -841,7 +841,7 @@ void aarch64_cpu_register(const ARMCPUInfo *info)
}; };
type_info.name = g_strdup_printf("%s-" TYPE_ARM_CPU, info->name); type_info.name = g_strdup_printf("%s-" TYPE_ARM_CPU, info->name);
type_register(&type_info); type_register_static(&type_info);
g_free((void *)type_info.name); g_free((void *)type_info.name);
} }

View file

@ -6429,7 +6429,7 @@ static void x86_register_cpu_model_type(const char *name, X86CPUModel *model)
.class_data = model, .class_data = model,
}; };
type_register(&ti); type_register_static(&ti);
} }

View file

@ -13,8 +13,7 @@
#include "sysemu/kvm.h" #include "sysemu/kvm.h"
#ifdef CONFIG_KVM /* always false if !CONFIG_KVM */
#define kvm_pit_in_kernel() \ #define kvm_pit_in_kernel() \
(kvm_irqchip_in_kernel() && !kvm_irqchip_is_split()) (kvm_irqchip_in_kernel() && !kvm_irqchip_is_split())
#define kvm_pic_in_kernel() \ #define kvm_pic_in_kernel() \
@ -22,14 +21,6 @@
#define kvm_ioapic_in_kernel() \ #define kvm_ioapic_in_kernel() \
(kvm_irqchip_in_kernel() && !kvm_irqchip_is_split()) (kvm_irqchip_in_kernel() && !kvm_irqchip_is_split())
#else
#define kvm_pit_in_kernel() 0
#define kvm_pic_in_kernel() 0
#define kvm_ioapic_in_kernel() 0
#endif /* CONFIG_KVM */
bool kvm_has_smm(void); bool kvm_has_smm(void);
bool kvm_enable_x2apic(void); bool kvm_enable_x2apic(void);
bool kvm_hv_vpindex_settable(void); bool kvm_hv_vpindex_settable(void);

View file

@ -626,7 +626,7 @@ static void mips_register_cpudef_type(const struct mips_def_t *def)
.class_data = (void *)def, .class_data = (void *)def,
}; };
type_register(&ti); type_register_static(&ti);
g_free(typename); g_free(typename);
} }

View file

@ -2633,7 +2633,7 @@ static int kvm_ppc_register_host_cpu_type(void)
return -1; return -1;
} }
type_info.parent = object_class_get_name(OBJECT_CLASS(pvr_pcc)); type_info.parent = object_class_get_name(OBJECT_CLASS(pvr_pcc));
type_register(&type_info); type_register_static(&type_info);
/* override TCG default cpu type with 'host' cpu model */ /* override TCG default cpu type with 'host' cpu model */
object_class_foreach(pseries_machine_class_fixup, TYPE_SPAPR_MACHINE, object_class_foreach(pseries_machine_class_fixup, TYPE_SPAPR_MACHINE,
false, NULL); false, NULL);

View file

@ -1014,7 +1014,7 @@ static void sparc_register_cpudef_type(const struct sparc_def_t *def)
.class_data = (void *)def, .class_data = (void *)def,
}; };
type_register(&ti); type_register_static(&ti);
g_free(typename); g_free(typename);
} }

View file

@ -198,7 +198,7 @@ void xtensa_register_core(XtensaConfigList *node)
node->next = xtensa_cores; node->next = xtensa_cores;
xtensa_cores = node; xtensa_cores = node;
type.name = g_strdup_printf(XTENSA_CPU_TYPE_NAME("%s"), node->config->name); type.name = g_strdup_printf(XTENSA_CPU_TYPE_NAME("%s"), node->config->name);
type_register(&type); type_register_static(&type);
g_free((gpointer)type.name); g_free((gpointer)type.name);
} }

View file

@ -155,6 +155,7 @@ ENV PYTHON "/usr/bin/python3"
RUN dnf install -y wget RUN dnf install -y wget
ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo
ENV RUSTC=/usr/local/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc ENV RUSTC=/usr/local/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc
ENV CARGO=/usr/local/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo
RUN set -eux && \ RUN set -eux && \
rustArch='x86_64-unknown-linux-gnu' && \ rustArch='x86_64-unknown-linux-gnu' && \
rustupSha256='6aeece6993e902708983b209d04c0d1dbb14ebb405ddb87def578d41f920f56d' && \ rustupSha256='6aeece6993e902708983b209d04c0d1dbb14ebb405ddb87def578d41f920f56d' && \
@ -165,10 +166,13 @@ RUN set -eux && \
./rustup-init -y --no-modify-path --profile default --default-toolchain nightly --default-host ${rustArch} && \ ./rustup-init -y --no-modify-path --profile default --default-toolchain nightly --default-host ${rustArch} && \
chmod -R a+w $RUSTUP_HOME $CARGO_HOME && \ chmod -R a+w $RUSTUP_HOME $CARGO_HOME && \
/usr/local/cargo/bin/rustup --version && \ /usr/local/cargo/bin/rustup --version && \
/usr/local/cargo/bin/rustup run nightly cargo --version && \
/usr/local/cargo/bin/rustup run nightly rustc --version && \ /usr/local/cargo/bin/rustup run nightly rustc --version && \
test "$CARGO" = "$(/usr/local/cargo/bin/rustup +nightly which cargo)" && \
test "$RUSTC" = "$(/usr/local/cargo/bin/rustup +nightly which rustc)" test "$RUSTC" = "$(/usr/local/cargo/bin/rustup +nightly which rustc)"
ENV PATH=$CARGO_HOME/bin:$PATH ENV PATH=$CARGO_HOME/bin:$PATH
RUN /usr/local/cargo/bin/rustup run nightly cargo install bindgen-cli RUN /usr/local/cargo/bin/rustup run nightly cargo install bindgen-cli
RUN $CARGO --list
# As a final step configure the user (if env is defined) # As a final step configure the user (if env is defined)
ARG USER ARG USER
ARG UID ARG UID

View file

@ -121,6 +121,7 @@ fedora_rustup_nightly_extras = [
"RUN dnf install -y wget\n", "RUN dnf install -y wget\n",
"ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo\n", "ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo\n",
"ENV RUSTC=/usr/local/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc\n", "ENV RUSTC=/usr/local/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc\n",
"ENV CARGO=/usr/local/rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo\n",
"RUN set -eux && \\\n", "RUN set -eux && \\\n",
" rustArch='x86_64-unknown-linux-gnu' && \\\n", " rustArch='x86_64-unknown-linux-gnu' && \\\n",
" rustupSha256='6aeece6993e902708983b209d04c0d1dbb14ebb405ddb87def578d41f920f56d' && \\\n", " rustupSha256='6aeece6993e902708983b209d04c0d1dbb14ebb405ddb87def578d41f920f56d' && \\\n",
@ -131,10 +132,13 @@ fedora_rustup_nightly_extras = [
" ./rustup-init -y --no-modify-path --profile default --default-toolchain nightly --default-host ${rustArch} && \\\n", " ./rustup-init -y --no-modify-path --profile default --default-toolchain nightly --default-host ${rustArch} && \\\n",
" chmod -R a+w $RUSTUP_HOME $CARGO_HOME && \\\n", " chmod -R a+w $RUSTUP_HOME $CARGO_HOME && \\\n",
" /usr/local/cargo/bin/rustup --version && \\\n", " /usr/local/cargo/bin/rustup --version && \\\n",
" /usr/local/cargo/bin/rustup run nightly cargo --version && \\\n",
" /usr/local/cargo/bin/rustup run nightly rustc --version && \\\n", " /usr/local/cargo/bin/rustup run nightly rustc --version && \\\n",
' test "$CARGO" = "$(/usr/local/cargo/bin/rustup +nightly which cargo)" && \\\n',
' test "$RUSTC" = "$(/usr/local/cargo/bin/rustup +nightly which rustc)"\n', ' test "$RUSTC" = "$(/usr/local/cargo/bin/rustup +nightly which rustc)"\n',
'ENV PATH=$CARGO_HOME/bin:$PATH\n', 'ENV PATH=$CARGO_HOME/bin:$PATH\n',
'RUN /usr/local/cargo/bin/rustup run nightly cargo install bindgen-cli\n', 'RUN /usr/local/cargo/bin/rustup run nightly cargo install bindgen-cli\n',
'RUN $CARGO --list\n',
] ]
ubuntu2204_bindgen_extras = [ ubuntu2204_bindgen_extras = [

View file

@ -1073,6 +1073,6 @@ void qemu_console_early_init(void)
{ {
/* set the default vc driver */ /* set the default vc driver */
if (!object_class_by_name(TYPE_CHARDEV_VC)) { if (!object_class_by_name(TYPE_CHARDEV_VC)) {
type_register(&char_vc_type_info); type_register_static(&char_vc_type_info);
} }
} }

View file

@ -476,7 +476,7 @@ early_dbus_init(DisplayOptions *opts)
#endif #endif
} }
type_register(&dbus_vc_type_info); type_register_static(&dbus_vc_type_info);
} }
static void static void

View file

@ -2540,7 +2540,7 @@ static void early_gtk_display_init(DisplayOptions *opts)
keycode_map = gd_get_keymap(&keycode_maplen); keycode_map = gd_get_keymap(&keycode_maplen);
#if defined(CONFIG_VTE) #if defined(CONFIG_VTE)
type_register(&char_gd_vc_type_info); type_register_static(&char_gd_vc_type_info);
#endif #endif
} }

View file

@ -173,7 +173,7 @@ static void spice_app_display_early_init(DisplayOptions *opts)
exit(1); exit(1);
} }
type_register(&char_vc_type_info); type_register_static(&char_vc_type_info);
sock_path = g_strjoin("", app_dir, "/", "spice.sock", NULL); sock_path = g_strjoin("", app_dir, "/", "spice.sock", NULL);
qopts = qemu_opts_create(list, NULL, 0, &error_abort); qopts = qemu_opts_create(list, NULL, 0, &error_abort);