* qom: Do not unparent in instance_finalize

* linux-user: avoid -Werror=int-in-bool-context
 * docs: use the pyvenv version of Meson
 * rust: parse attributes using the attrs crate
 * rust: complete conversion of qdev properties to proc macro
 * docs: clarify AI-generated content policy
 -----BEGIN PGP SIGNATURE-----
 
 iQFIBAABCgAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmjTnTgUHHBib256aW5p
 QHJlZGhhdC5jb20ACgkQv/vSX3jHroNYUwf9EpJbiCN8Qif9JU3XQEaOMDGTDO07
 nMvn6RnRTFyn4iYzCc+pn6GFKWfJGZ6/cD9Qby7lyi3lHlhW8fLYbAcTXn1HoLNk
 lr/Ibmyaa8U2WP5u/QG+3dwn9zTgNFza3BFLguKrOhWjbv3ZL85xez29yChGgtYq
 sTUTigtl261JF4SvtOhzCMqUPo4wzqD0m0Vc/pjxrlgpHAb3rKf32Y6xPkNMVN84
 81egbF0ZRtUbubjvGzPFstMdRcVBdrac5wnFPWum9GazuWwB4K8p2iBFdmuXMOhy
 NW6M8HP516zhoNk7bA5zQghxmhPWLXah4iA7MflAzLTI30s23TNIMCeJRw==
 =ug+J
 -----END PGP SIGNATURE-----

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

* qom: Do not unparent in instance_finalize
* linux-user: avoid -Werror=int-in-bool-context
* docs: use the pyvenv version of Meson
* rust: parse attributes using the attrs crate
* rust: complete conversion of qdev properties to proc macro
* docs: clarify AI-generated content policy

# -----BEGIN PGP SIGNATURE-----
#
# iQFIBAABCgAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmjTnTgUHHBib256aW5p
# QHJlZGhhdC5jb20ACgkQv/vSX3jHroNYUwf9EpJbiCN8Qif9JU3XQEaOMDGTDO07
# nMvn6RnRTFyn4iYzCc+pn6GFKWfJGZ6/cD9Qby7lyi3lHlhW8fLYbAcTXn1HoLNk
# lr/Ibmyaa8U2WP5u/QG+3dwn9zTgNFza3BFLguKrOhWjbv3ZL85xez29yChGgtYq
# sTUTigtl261JF4SvtOhzCMqUPo4wzqD0m0Vc/pjxrlgpHAb3rKf32Y6xPkNMVN84
# 81egbF0ZRtUbubjvGzPFstMdRcVBdrac5wnFPWum9GazuWwB4K8p2iBFdmuXMOhy
# NW6M8HP516zhoNk7bA5zQghxmhPWLXah4iA7MflAzLTI30s23TNIMCeJRw==
# =ug+J
# -----END PGP SIGNATURE-----
# gpg: Signature made Wed 24 Sep 2025 12:26:48 AM PDT
# gpg:                using RSA key F13338574B662389866C7682BFFBD25F78C7AE83
# gpg:                issuer "pbonzini@redhat.com"
# gpg: Good signature from "Paolo Bonzini <bonzini@gnu.org>" [unknown]
# gpg:                 aka "Paolo Bonzini <pbonzini@redhat.com>" [unknown]
# gpg: WARNING: The key's User ID is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# 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: (29 commits)
  docs/code-provenance: AI exceptions are in addition to DCO
  docs/code-provenance: make the exception process more prominent
  docs/code-provenance: clarify scope very early
  hw/xen: Do not unparent in instance_finalize()
  vfio: Do not unparent in instance_finalize()
  hw/sd/sdhci: Do not unparent in instance_finalize()
  hv-balloon: hw/core/register: Do not unparent in instance_finalize()
  hw/core/register: Do not unparent in instance_finalize()
  vfio/pci: Do not unparent in instance_finalize()
  docs/devel: Do not unparent in instance_finalize()
  linux-user: avoid -Werror=int-in-bool-context
  rust/qdev: Drop declare_properties & define_property macros
  rust/hpet: Convert qdev properties to #property macro
  rust/hpet: Clean up type mismatch for num_timers property
  rust/qdev: Test bit property for #property
  rust/qdev: Support bit property in #property macro
  rust/qdev: Support property info for more common types
  rust/qdev: Refine the documentation for QDevProp trait
  rust/qdev: use addr_of! in QDevProp
  rust/common/uninit: Fix Clippy's complaints about lifetime
  ...

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Richard Henderson 2025-09-24 12:04:18 -07:00
commit 95b9e0d2ad
33 changed files with 306 additions and 260 deletions

View file

@ -450,7 +450,7 @@ are run with ``make bench``. Meson test suites such as ``unit`` can be ran
with ``make check-unit``, and ``make check-tcg`` builds and runs "non-Meson"
tests for all targets.
If desired, it is also possible to use ``ninja`` and ``meson test``,
If desired, it is also possible to use ``ninja`` and ``pyvenv/bin/meson test``,
respectively to build emulators and run tests defined in meson.build.
The main difference is that ``make`` needs the ``-jN`` flag in order to
enable parallel builds or tests.

View file

@ -285,8 +285,8 @@ Such tools are acceptable to use, provided there is clearly defined copyright
and licensing for their output. Note in particular the caveats applying to AI
content generators below.
Use of AI content generators
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use of AI-generated content
~~~~~~~~~~~~~~~~~~~~~~~~~~~
TL;DR:
@ -294,6 +294,10 @@ TL;DR:
believed to include or derive from AI generated content. This includes
ChatGPT, Claude, Copilot, Llama and similar tools.**
**This policy does not apply to other uses of AI, such as researching APIs
or algorithms, static analysis, or debugging, provided their output is not
included in contributions.**
The increasing prevalence of AI-assisted software development results in a
number of difficult legal questions and risks for software projects, including
QEMU. Of particular concern is content generated by `Large Language Models
@ -322,17 +326,24 @@ The QEMU project thus requires that contributors refrain from using AI content
generators on patches intended to be submitted to the project, and will
decline any contribution if use of AI is either known or suspected.
This policy does not apply to other uses of AI, such as researching APIs or
algorithms, static analysis, or debugging, provided their output is not to be
included in contributions.
Examples of tools impacted by this policy includes GitHub's CoPilot, OpenAI's
ChatGPT, Anthropic's Claude, and Meta's Code Llama, and code/content
generation agents which are built on top of such tools.
This policy may evolve as AI tools mature and the legal situation is
clarifed. In the meanwhile, requests for exceptions to this policy will be
evaluated by the QEMU project on a case by case basis. To be granted an
exception, a contributor will need to demonstrate clarity of the license and
copyright status for the tool's output in relation to its training model and
code, to the satisfaction of the project maintainers.
clarified.
Exceptions
^^^^^^^^^^
The QEMU project welcomes discussion on any exceptions to this policy,
or more general revisions. This can be done by contacting the qemu-devel
mailing list with details of a proposed tool, model, usage scenario, etc.
that is beneficial to QEMU, while still mitigating issues around compliance
with the DCO. After discussion, any exception will be listed below.
Exceptions do not remove the need for authors to comply with all other
requirements for contribution. In particular, the "Signed-off-by"
label in a patch submission is a statement that the author takes
responsibility for the entire contents of the patch, including any parts
that were generated or assisted by AI tools or other tools.

View file

@ -165,17 +165,14 @@ and finalized one by one. The order in which memory regions will be
finalized is not guaranteed.
If however the memory region is part of a dynamically allocated data
structure, you should call object_unparent() to destroy the memory region
before the data structure is freed. For an example see VFIOMSIXInfo
and VFIOQuirk in hw/vfio/pci.c.
structure, you should free the memory region in the instance_finalize
callback. For an example see VFIOMSIXInfo and VFIOQuirk in
hw/vfio/pci.c.
You must not destroy a memory region as long as it may be in use by a
device or CPU. In order to do this, as a general rule do not create or
destroy memory regions dynamically during a device's lifetime, and only
call object_unparent() in the memory region owner's instance_finalize
callback. The dynamically allocated data structure that contains the
memory region then should obviously be freed in the instance_finalize
callback as well.
destroy memory regions dynamically during a device's lifetime, and never
call object_unparent().
If you break this rule, the following situation can happen:
@ -201,9 +198,7 @@ this exception is rarely necessary, and therefore it is discouraged,
but nevertheless it is used in a few places.
For regions that "have no owner" (NULL is passed at creation time), the
machine object is actually used as the owner. Since instance_finalize is
never called for the machine object, you must never call object_unparent
on regions that have no owner, unless they are aliases or containers.
machine object is actually used as the owner.
Overlapping regions and priority

View file

@ -66,7 +66,7 @@ __ https://mesonbuild.com/Commands.html#devenv
As shown above, you can use the ``--tests`` option as usual to operate on test
code. Note however that you cannot *build* or run tests via ``cargo``, because
they need support C code from QEMU that Cargo does not know about. Tests can
be run via ``meson test`` or ``make``::
be run via Meson (``pyvenv/bin/meson test``) or ``make``::
make check-rust

View file

@ -54,7 +54,7 @@ directory:
.. code-block:: shell
meson test qtest-x86_64/qos-test
pyvenv/bin/meson test qtest-x86_64/qos-test
ethtool can test register accesses, interrupts, etc. It is automated as an
functional test and can be run from the build directory with the following

View file

@ -314,7 +314,6 @@ RegisterInfoArray *register_init_block64(DeviceState *owner,
void register_finalize_block(RegisterInfoArray *r_array)
{
object_unparent(OBJECT(&r_array->mem));
g_free(r_array->r);
g_free(r_array);
}

View file

@ -1475,16 +1475,6 @@ static void hv_balloon_ensure_mr(HvBalloon *balloon)
balloon->mr->align = memory_region_get_alignment(hostmem_mr);
}
static void hv_balloon_free_mr(HvBalloon *balloon)
{
if (!balloon->mr) {
return;
}
object_unparent(OBJECT(balloon->mr));
g_clear_pointer(&balloon->mr, g_free);
}
static void hv_balloon_vmdev_realize(VMBusDevice *vdev, Error **errp)
{
ERRP_GUARD();
@ -1580,7 +1570,7 @@ static void hv_balloon_vmdev_reset(VMBusDevice *vdev)
*/
static void hv_balloon_unrealize_finalize_common(HvBalloon *balloon)
{
hv_balloon_free_mr(balloon);
g_clear_pointer(&balloon->mr, g_free);
balloon->addr = 0;
balloon->memslot_count = 0;

View file

@ -1578,10 +1578,6 @@ static void sdhci_sysbus_finalize(Object *obj)
{
SDHCIState *s = SYSBUS_SDHCI(obj);
if (s->dma_mr) {
object_unparent(OBJECT(s->dma_mr));
}
sdhci_uninitfn(s);
}

View file

@ -1159,15 +1159,12 @@ void vfio_vga_quirk_exit(VFIOPCIDevice *vdev)
void vfio_vga_quirk_finalize(VFIOPCIDevice *vdev)
{
int i, j;
int i;
for (i = 0; i < ARRAY_SIZE(vdev->vga->region); i++) {
while (!QLIST_EMPTY(&vdev->vga->region[i].quirks)) {
VFIOQuirk *quirk = QLIST_FIRST(&vdev->vga->region[i].quirks);
QLIST_REMOVE(quirk, next);
for (j = 0; j < quirk->nr_mem; j++) {
object_unparent(OBJECT(&quirk->mem[j]));
}
g_free(quirk->mem);
g_free(quirk->data);
g_free(quirk);
@ -1207,14 +1204,10 @@ void vfio_bar_quirk_exit(VFIOPCIDevice *vdev, int nr)
void vfio_bar_quirk_finalize(VFIOPCIDevice *vdev, int nr)
{
VFIOBAR *bar = &vdev->bars[nr];
int i;
while (!QLIST_EMPTY(&bar->quirks)) {
VFIOQuirk *quirk = QLIST_FIRST(&bar->quirks);
QLIST_REMOVE(quirk, next);
for (i = 0; i < quirk->nr_mem; i++) {
object_unparent(OBJECT(&quirk->mem[i]));
}
g_free(quirk->mem);
g_free(quirk->data);
g_free(quirk);

View file

@ -2025,7 +2025,6 @@ static void vfio_bars_finalize(VFIOPCIDevice *vdev)
vfio_region_finalize(&bar->region);
if (bar->mr) {
assert(bar->size);
object_unparent(OBJECT(bar->mr));
g_free(bar->mr);
bar->mr = NULL;
}
@ -2033,9 +2032,6 @@ static void vfio_bars_finalize(VFIOPCIDevice *vdev)
if (vdev->vga) {
vfio_vga_quirk_finalize(vdev);
for (i = 0; i < ARRAY_SIZE(vdev->vga->region); i++) {
object_unparent(OBJECT(&vdev->vga->region[i].mem));
}
g_free(vdev->vga);
}
}

View file

@ -365,12 +365,9 @@ void vfio_region_finalize(VFIORegion *region)
for (i = 0; i < region->nr_mmaps; i++) {
if (region->mmaps[i].mmap) {
munmap(region->mmaps[i].mmap, region->mmaps[i].size);
object_unparent(OBJECT(&region->mmaps[i].mem));
}
}
object_unparent(OBJECT(region->mem));
g_free(region->mem);
g_free(region->mmaps);

View file

@ -637,14 +637,5 @@ void xen_pt_msix_unmap(XenPCIPassthroughState *s)
void xen_pt_msix_delete(XenPCIPassthroughState *s)
{
XenPTMSIX *msix = s->msix;
if (!msix) {
return;
}
object_unparent(OBJECT(&msix->mmio));
g_free(s->msix);
s->msix = NULL;
g_clear_pointer(&s->msix, g_free);
}

View file

@ -54,7 +54,7 @@ struct flags {
};
/* No 'struct flags' element should have a zero mask. */
#define FLAG_BASIC(V, M, N) { V, M | QEMU_BUILD_BUG_ON_ZERO(!(M)), N }
#define FLAG_BASIC(V, M, N) { V, M | QEMU_BUILD_BUG_ON_ZERO((M) == 0), N }
/* common flags for all architectures */
#define FLAG_GENERIC_MASK(V, M) FLAG_BASIC(V, M, #V)

11
rust/Cargo.lock generated
View file

@ -14,6 +14,16 @@ version = "1.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c84fc003e338a6f69fbd4f7fe9f92b535ff13e9af8997f3b14b6ddff8b1df46d"
[[package]]
name = "attrs"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a207d40f43de65285f3de0509bb6cb16bc46098864fce957122bbacce327e5f"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "bilge"
version = "0.2.0"
@ -188,6 +198,7 @@ dependencies = [
name = "qemu_macros"
version = "0.1.0"
dependencies = [
"attrs",
"proc-macro2",
"quote",
"syn",

View file

@ -47,6 +47,5 @@ bql_rs = declare_dependency(link_with: [_bql_rs],
# in a separate suite that is run by the "build" CI jobs rather than "check".
rust.doctest('rust-bql-rs-doctests',
_bql_rs,
protocol: 'rust',
dependencies: bql_rs,
suite: ['doc', 'rust'])

View file

@ -24,11 +24,13 @@ _common_rs = static_library(
common_rs = declare_dependency(link_with: [_common_rs])
rust.test('rust-common-tests', _common_rs,
suite: ['unit', 'rust'])
# Doctests are essentially integration tests, so they need the same dependencies.
# Note that running them requires the object files for C code, so place them
# in a separate suite that is run by the "build" CI jobs rather than "check".
rust.doctest('rust-common-doctests',
_common_rs,
protocol: 'rust',
dependencies: common_rs,
suite: ['doc', 'rust'])

View file

@ -35,7 +35,7 @@ impl<'a, T, U> MaybeUninitField<'a, T, U> {
}
}
impl<'a, T, U> Deref for MaybeUninitField<'a, T, U> {
impl<T, U> Deref for MaybeUninitField<'_, T, U> {
type Target = MaybeUninit<U>;
fn deref(&self) -> &MaybeUninit<U> {
@ -46,7 +46,7 @@ impl<'a, T, U> Deref for MaybeUninitField<'a, T, U> {
}
}
impl<'a, T, U> DerefMut for MaybeUninitField<'a, T, U> {
impl<T, U> DerefMut for MaybeUninitField<'_, T, U> {
fn deref_mut(&mut self) -> &mut MaybeUninit<U> {
// SAFETY: self.child was obtained by dereferencing a valid mutable
// reference; the content of the memory may be invalid or uninitialized

View file

@ -6,7 +6,7 @@
use std::{
ffi::{c_int, c_void, CStr, CString},
ptr::NonNull,
ptr::{addr_of, NonNull},
};
use chardev::Chardev;
@ -109,9 +109,16 @@ unsafe extern "C" fn rust_resettable_exit_fn<T: ResettablePhasesImpl>(
///
/// # Safety
///
/// This trait is marked as `unsafe` because currently having a `const` refer to
/// an `extern static` as a reference instead of a raw pointer results in this
/// compiler error:
/// This trait is marked as `unsafe` because `BASE_INFO` and `BIT_INFO` must be
/// valid raw references to [`bindings::PropertyInfo`].
///
/// Note we could not use a regular reference:
///
/// ```text
/// const VALUE: &bindings::PropertyInfo = ...
/// ```
///
/// because this results in the following compiler error:
///
/// ```text
/// constructing invalid value: encountered reference to `extern` static in `const`
@ -119,28 +126,37 @@ unsafe extern "C" fn rust_resettable_exit_fn<T: ResettablePhasesImpl>(
///
/// This is because the compiler generally might dereference a normal reference
/// during const evaluation, but not in this case (if it did, it'd need to
/// dereference the raw pointer so this would fail to compile).
/// dereference the raw pointer so using a `*const` would also fail to compile).
///
/// It is the implementer's responsibility to provide a valid
/// [`bindings::PropertyInfo`] pointer for the trait implementation to be safe.
pub unsafe trait QDevProp {
const VALUE: *const bindings::PropertyInfo;
const BASE_INFO: *const bindings::PropertyInfo;
const BIT_INFO: *const bindings::PropertyInfo = {
panic!("invalid type for bit property");
};
}
/// Use [`bindings::qdev_prop_bool`] for `bool`.
unsafe impl QDevProp for bool {
const VALUE: *const bindings::PropertyInfo = unsafe { &bindings::qdev_prop_bool };
macro_rules! impl_qdev_prop {
($type:ty,$info:ident$(, $bit_info:ident)?) => {
unsafe impl $crate::qdev::QDevProp for $type {
const BASE_INFO: *const $crate::bindings::PropertyInfo =
addr_of!($crate::bindings::$info);
$(const BIT_INFO: *const $crate::bindings::PropertyInfo =
addr_of!($crate::bindings::$bit_info);)?
}
};
}
/// Use [`bindings::qdev_prop_uint64`] for `u64`.
unsafe impl QDevProp for u64 {
const VALUE: *const bindings::PropertyInfo = unsafe { &bindings::qdev_prop_uint64 };
}
/// Use [`bindings::qdev_prop_chr`] for [`chardev::CharBackend`].
unsafe impl QDevProp for chardev::CharBackend {
const VALUE: *const bindings::PropertyInfo = unsafe { &bindings::qdev_prop_chr };
}
impl_qdev_prop!(bool, qdev_prop_bool);
impl_qdev_prop!(u8, qdev_prop_uint8);
impl_qdev_prop!(u16, qdev_prop_uint16);
impl_qdev_prop!(u32, qdev_prop_uint32, qdev_prop_bit);
impl_qdev_prop!(u64, qdev_prop_uint64, qdev_prop_bit64);
impl_qdev_prop!(usize, qdev_prop_usize);
impl_qdev_prop!(i32, qdev_prop_int32);
impl_qdev_prop!(i64, qdev_prop_int64);
impl_qdev_prop!(chardev::CharBackend, qdev_prop_chr);
/// Trait to define device properties.
///
@ -232,59 +248,6 @@ impl DeviceClass {
}
}
#[macro_export]
macro_rules! define_property {
($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty, bit = $bitnr:expr, default = $defval:expr$(,)*) => {
$crate::bindings::Property {
// use associated function syntax for type checking
name: ::std::ffi::CStr::as_ptr($name),
info: $prop,
offset: ::std::mem::offset_of!($state, $field) as isize,
bitnr: $bitnr,
set_default: true,
defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 },
..::common::zeroable::Zeroable::ZERO
}
};
($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty, default = $defval:expr$(,)*) => {
$crate::bindings::Property {
// use associated function syntax for type checking
name: ::std::ffi::CStr::as_ptr($name),
info: $prop,
offset: ::std::mem::offset_of!($state, $field) as isize,
set_default: true,
defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 },
..::common::zeroable::Zeroable::ZERO
}
};
($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty$(,)*) => {
$crate::bindings::Property {
// use associated function syntax for type checking
name: ::std::ffi::CStr::as_ptr($name),
info: $prop,
offset: ::std::mem::offset_of!($state, $field) as isize,
set_default: false,
..::common::zeroable::Zeroable::ZERO
}
};
}
#[macro_export]
macro_rules! declare_properties {
($ident:ident, $($prop:expr),*$(,)*) => {
pub static $ident: [$crate::bindings::Property; {
let mut len = 0;
$({
_ = stringify!($prop);
len += 1;
})*
len
}] = [
$($prop),*,
];
};
}
unsafe impl ObjectType for DeviceState {
type Class = DeviceClass;
const TYPE_NAME: &'static CStr =

View file

@ -13,9 +13,8 @@ use std::{
use bql::{BqlCell, BqlRefCell};
use common::{bitops::IntegerExt, uninit_field_mut};
use hwcore::{
bindings::{qdev_prop_bit, qdev_prop_bool, qdev_prop_uint32, qdev_prop_usize},
declare_properties, define_property, DeviceImpl, DeviceMethods, DeviceState, InterruptSource,
Property, ResetType, ResettablePhasesImpl, SysBusDevice, SysBusDeviceImpl, SysBusDeviceMethods,
DeviceImpl, DeviceMethods, DeviceState, InterruptSource, ResetType, ResettablePhasesImpl,
SysBusDevice, SysBusDeviceImpl, SysBusDeviceMethods,
};
use migration::{
self, impl_vmstate_struct, vmstate_fields, vmstate_of, vmstate_subsections, vmstate_validate,
@ -520,7 +519,7 @@ impl HPETTimer {
/// HPET Event Timer Block Abstraction
#[repr(C)]
#[derive(qom::Object)]
#[derive(qom::Object, hwcore::Device)]
pub struct HPETState {
parent_obj: ParentField<SysBusDevice>,
iomem: MemoryRegion,
@ -540,10 +539,12 @@ pub struct HPETState {
// Internal state
/// Capabilities that QEMU HPET supports.
/// bit 0: MSI (or FSB) support.
#[property(rename = "msi", bit = HPET_FLAG_MSI_SUPPORT_SHIFT as u8, default = false)]
flags: u32,
/// Offset of main counter relative to qemu clock.
hpet_offset: BqlCell<u64>,
#[property(rename = "hpet-offset-saved", default = true)]
hpet_offset_saved: bool,
irqs: [InterruptSource; HPET_NUM_IRQ_ROUTES],
@ -555,11 +556,13 @@ pub struct HPETState {
/// the timers' interrupt can be routed, and is encoded in the
/// bits 32:64 of timer N's config register:
#[doc(alias = "intcap")]
#[property(rename = "hpet-intcap", default = 0)]
int_route_cap: u32,
/// HPET timer array managed by this timer block.
#[doc(alias = "timer")]
timers: [BqlRefCell<HPETTimer>; HPET_MAX_TIMERS],
#[property(rename = "timers", default = HPET_MIN_TIMERS)]
num_timers: usize,
num_timers_save: BqlCell<u8>,
@ -901,44 +904,6 @@ impl ObjectImpl for HPETState {
const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::<Self>;
}
// TODO: Make these properties user-configurable!
declare_properties! {
HPET_PROPERTIES,
define_property!(
c"timers",
HPETState,
num_timers,
unsafe { &qdev_prop_usize },
u8,
default = HPET_MIN_TIMERS
),
define_property!(
c"msi",
HPETState,
flags,
unsafe { &qdev_prop_bit },
u32,
bit = HPET_FLAG_MSI_SUPPORT_SHIFT as u8,
default = false,
),
define_property!(
c"hpet-intcap",
HPETState,
int_route_cap,
unsafe { &qdev_prop_uint32 },
u32,
default = 0
),
define_property!(
c"hpet-offset-saved",
HPETState,
hpet_offset_saved,
unsafe { &qdev_prop_bool },
bool,
default = true
),
}
static VMSTATE_HPET_RTC_IRQ_LEVEL: VMStateDescription<HPETState> =
VMStateDescriptionBuilder::<HPETState>::new()
.name(c"hpet/rtc_irq_level")
@ -1001,12 +966,6 @@ const VMSTATE_HPET: VMStateDescription<HPETState> =
))
.build();
// SAFETY: HPET_PROPERTIES is a valid Property array constructed with the
// hwcore::declare_properties macro.
unsafe impl hwcore::DevicePropertiesImpl for HPETState {
const PROPERTIES: &'static [Property] = &HPET_PROPERTIES;
}
impl DeviceImpl for HPETState {
const VMSTATE: Option<VMStateDescription<Self>> = Some(VMSTATE_HPET);
const REALIZE: Option<fn(&Self) -> util::Result<()>> = Some(Self::realize);

View file

@ -13,10 +13,12 @@ libc_rs = dependency('libc-0.2-rs')
subproject('proc-macro2-1-rs', required: true)
subproject('quote-1-rs', required: true)
subproject('syn-2-rs', required: true)
subproject('attrs-0.2-rs', required: true)
quote_rs_native = dependency('quote-1-rs', native: true)
syn_rs_native = dependency('syn-2-rs', native: true)
proc_macro2_rs_native = dependency('proc-macro2-1-rs', native: true)
attrs_rs_native = dependency('attrs-0.2-rs', native: true)
genrs = []

View file

@ -48,6 +48,5 @@ migration_rs = declare_dependency(link_with: [_migration_rs],
# in a separate suite that is run by the "build" CI jobs rather than "check".
rust.doctest('rust-migration-rs-doctests',
_migration_rs,
protocol: 'rust',
dependencies: migration_rs,
suite: ['doc', 'rust'])

View file

@ -144,7 +144,7 @@ macro_rules! vmstate_of {
$crate::bindings::VMStateField {
name: ::core::concat!(::core::stringify!($field_name), "\0")
.as_bytes()
.as_ptr() as *const ::std::os::raw::c_char,
.as_ptr().cast::<::std::os::raw::c_char>(),
offset: ::std::mem::offset_of!($struct_name, $field_name),
$(num_offset: ::std::mem::offset_of!($struct_name, $num),)?
$(field_exists: $crate::vmstate_exist_fn!($struct_name, $test_fn),)?

View file

@ -16,6 +16,7 @@ rust-version.workspace = true
proc-macro = true
[dependencies]
attrs = "0.2.9"
proc-macro2 = "1"
quote = "1"
syn = { version = "2", features = ["extra-traits"] }

View file

@ -8,6 +8,7 @@ _qemu_macros_rs = rust.proc_macro(
'--cfg', 'feature="proc-macro"',
],
dependencies: [
attrs_rs_native,
proc_macro2_rs_native,
quote_rs_native,
syn_rs_native,

View file

@ -3,10 +3,14 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use proc_macro::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use quote::{quote, quote_spanned};
use syn::{
parse::Parse, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned,
token::Comma, Data, DeriveInput, Error, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token,
parse::{Parse, ParseStream},
parse_macro_input, parse_quote,
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
Attribute, Data, DeriveInput, Error, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token,
Variant,
};
mod bits;
@ -159,61 +163,39 @@ enum DevicePropertyName {
Str(syn::LitStr),
}
#[derive(Debug)]
impl Parse for DevicePropertyName {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let lo = input.lookahead1();
if lo.peek(syn::LitStr) {
Ok(Self::Str(input.parse()?))
} else if lo.peek(syn::LitCStr) {
Ok(Self::CStr(input.parse()?))
} else {
Err(lo.error())
}
}
}
#[derive(Default, Debug)]
struct DeviceProperty {
rename: Option<DevicePropertyName>,
bitnr: Option<syn::Expr>,
defval: Option<syn::Expr>,
}
impl Parse for DeviceProperty {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let _: syn::Token![#] = input.parse()?;
let bracketed;
_ = syn::bracketed!(bracketed in input);
let attribute = bracketed.parse::<syn::Ident>()?;
debug_assert_eq!(&attribute.to_string(), "property");
let mut retval = Self {
rename: None,
defval: None,
};
let content;
_ = syn::parenthesized!(content in bracketed);
while !content.is_empty() {
let value: syn::Ident = content.parse()?;
if value == "rename" {
let _: syn::Token![=] = content.parse()?;
if retval.rename.is_some() {
return Err(syn::Error::new(
value.span(),
"`rename` can only be used at most once",
));
}
if content.peek(syn::LitStr) {
retval.rename = Some(DevicePropertyName::Str(content.parse::<syn::LitStr>()?));
} else {
retval.rename =
Some(DevicePropertyName::CStr(content.parse::<syn::LitCStr>()?));
}
} else if value == "default" {
let _: syn::Token![=] = content.parse()?;
if retval.defval.is_some() {
return Err(syn::Error::new(
value.span(),
"`default` can only be used at most once",
));
}
retval.defval = Some(content.parse()?);
} else {
return Err(syn::Error::new(
value.span(),
format!("unrecognized field `{value}`"),
));
}
impl DeviceProperty {
fn parse_from(&mut self, a: &Attribute) -> syn::Result<()> {
use attrs::{set, with, Attrs};
let mut parser = Attrs::new();
parser.once("rename", with::eq(set::parse(&mut self.rename)));
parser.once("bit", with::eq(set::parse(&mut self.bitnr)));
parser.once("default", with::eq(set::parse(&mut self.defval)));
a.parse_args_with(&mut parser)
}
if !content.is_empty() {
let _: syn::Token![,] = content.parse()?;
}
}
fn parse(a: &Attribute) -> syn::Result<Self> {
let mut retval = Self::default();
retval.parse_from(a)?;
Ok(retval)
}
}
@ -235,14 +217,18 @@ fn derive_device_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream
f.attrs
.iter()
.filter(|a| a.path().is_ident("property"))
.map(|a| Ok((f.clone(), syn::parse2(a.to_token_stream())?)))
.map(|a| Ok((f.clone(), DeviceProperty::parse(a)?)))
})
.collect::<Result<Vec<_>, Error>>()?;
let name = &input.ident;
let mut properties_expanded = vec![];
for (field, prop) in properties {
let DeviceProperty { rename, defval } = prop;
let DeviceProperty {
rename,
bitnr,
defval,
} = prop;
let field_name = field.ident.unwrap();
macro_rules! str_to_c_str {
($value:expr, $span:expr) => {{
@ -262,8 +248,8 @@ fn derive_device_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream
let prop_name = rename.map_or_else(
|| str_to_c_str!(field_name.to_string(), field_name.span()),
|rename| -> Result<proc_macro2::TokenStream, Error> {
match rename {
|prop_rename| -> Result<proc_macro2::TokenStream, Error> {
match prop_rename {
DevicePropertyName::CStr(cstr_lit) => Ok(quote! { #cstr_lit }),
DevicePropertyName::Str(str_lit) => {
str_to_c_str!(str_lit.value(), str_lit.span())
@ -272,14 +258,20 @@ fn derive_device_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream
},
)?;
let field_ty = field.ty.clone();
let qdev_prop = quote! { <#field_ty as ::hwcore::QDevProp>::VALUE };
let qdev_prop = if bitnr.is_none() {
quote! { <#field_ty as ::hwcore::QDevProp>::BASE_INFO }
} else {
quote! { <#field_ty as ::hwcore::QDevProp>::BIT_INFO }
};
let bitnr = bitnr.unwrap_or(syn::Expr::Verbatim(quote! { 0 }));
let set_default = defval.is_some();
let defval = defval.unwrap_or(syn::Expr::Verbatim(quote! { 0 }));
properties_expanded.push(quote! {
::hwcore::bindings::Property {
name: ::std::ffi::CStr::as_ptr(#prop_name),
info: #qdev_prop ,
info: #qdev_prop,
offset: ::core::mem::offset_of!(#name, #field_name) as isize,
bitnr: #bitnr,
set_default: #set_default,
defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: #defval as u64 },
..::common::Zeroable::ZERO

View file

@ -60,7 +60,7 @@ fn test_derive_device() {
migrate_clock: bool,
}
},
"unrecognized field `defalt`"
"Expected one of `bit`, `default` or `rename`"
);
// Check that repeated attributes are not allowed:
derive_compile_fail!(
@ -73,7 +73,8 @@ fn test_derive_device() {
migrate_clock: bool,
}
},
"`rename` can only be used at most once"
"Duplicate argument",
"Already used here",
);
derive_compile_fail!(
derive_device_or_error,
@ -85,7 +86,21 @@ fn test_derive_device() {
migrate_clock: bool,
}
},
"`default` can only be used at most once"
"Duplicate argument",
"Already used here",
);
derive_compile_fail!(
derive_device_or_error,
quote! {
#[repr(C)]
#[derive(Device)]
struct DummyState {
#[property(bit = 0, bit = 1)]
flags: u32,
}
},
"Duplicate argument",
"Already used here",
);
// Check that the field name is preserved when `rename` isn't used:
derive_compile!(
@ -104,8 +119,9 @@ fn test_derive_device() {
const PROPERTIES: &'static [::hwcore::bindings::Property] = &[
::hwcore::bindings::Property {
name: ::std::ffi::CStr::as_ptr(c"migrate_clock"),
info: <bool as ::hwcore::QDevProp>::VALUE,
info: <bool as ::hwcore::QDevProp>::BASE_INFO,
offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize,
bitnr: 0,
set_default: true,
defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 },
..::common::Zeroable::ZERO
@ -131,8 +147,9 @@ fn test_derive_device() {
const PROPERTIES: &'static [::hwcore::bindings::Property] = &[
::hwcore::bindings::Property {
name: ::std::ffi::CStr::as_ptr(c"migrate-clk"),
info: <bool as ::hwcore::QDevProp>::VALUE,
info: <bool as ::hwcore::QDevProp>::BASE_INFO,
offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize,
bitnr: 0,
set_default: true,
defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 },
..::common::Zeroable::ZERO
@ -141,6 +158,92 @@ fn test_derive_device() {
}
}
);
// Check that `bit` value is used for the bit property without default
// value (note: though C macro (e.g., DEFINE_PROP_BIT) always requires
// default value, Rust side allows to default this field to "0"):
derive_compile!(
derive_device_or_error,
quote! {
#[repr(C)]
#[derive(Device)]
pub struct DummyState {
parent: ParentField<DeviceState>,
#[property(bit = 3)]
flags: u32,
}
},
quote! {
unsafe impl ::hwcore::DevicePropertiesImpl for DummyState {
const PROPERTIES: &'static [::hwcore::bindings::Property] = &[
::hwcore::bindings::Property {
name: ::std::ffi::CStr::as_ptr(c"flags"),
info: <u32 as ::hwcore::QDevProp>::BIT_INFO,
offset: ::core::mem::offset_of!(DummyState, flags) as isize,
bitnr: 3,
set_default: false,
defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: 0 as u64 },
..::common::Zeroable::ZERO
}
];
}
}
);
// Check that `bit` value is used for the bit property when used:
derive_compile!(
derive_device_or_error,
quote! {
#[repr(C)]
#[derive(Device)]
pub struct DummyState {
parent: ParentField<DeviceState>,
#[property(bit = 3, default = true)]
flags: u32,
}
},
quote! {
unsafe impl ::hwcore::DevicePropertiesImpl for DummyState {
const PROPERTIES: &'static [::hwcore::bindings::Property] = &[
::hwcore::bindings::Property {
name: ::std::ffi::CStr::as_ptr(c"flags"),
info: <u32 as ::hwcore::QDevProp>::BIT_INFO,
offset: ::core::mem::offset_of!(DummyState, flags) as isize,
bitnr: 3,
set_default: true,
defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 },
..::common::Zeroable::ZERO
}
];
}
}
);
// Check that `bit` value is used for the bit property with rename when used:
derive_compile!(
derive_device_or_error,
quote! {
#[repr(C)]
#[derive(Device)]
pub struct DummyState {
parent: ParentField<DeviceState>,
#[property(rename = "msi", bit = 3, default = false)]
flags: u64,
}
},
quote! {
unsafe impl ::hwcore::DevicePropertiesImpl for DummyState {
const PROPERTIES: &'static [::hwcore::bindings::Property] = &[
::hwcore::bindings::Property {
name: ::std::ffi::CStr::as_ptr(c"msi"),
info: <u64 as ::hwcore::QDevProp>::BIT_INFO,
offset: ::core::mem::offset_of!(DummyState, flags) as isize,
bitnr: 3,
set_default: true,
defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: false as u64 },
..::common::Zeroable::ZERO
}
];
}
}
);
}
#[test]

View file

@ -38,6 +38,5 @@ qom_rs = declare_dependency(link_with: [_qom_rs], dependencies: [qemu_macros, qo
# in a separate suite that is run by the "build" CI jobs rather than "check".
rust.doctest('rust-qom-rs-doctests',
_qom_rs,
protocol: 'rust',
dependencies: qom_rs,
suite: ['doc', 'rust'])

View file

@ -44,12 +44,15 @@ _util_rs = static_library(
util_rs = declare_dependency(link_with: [_util_rs], dependencies: [qemuutil, qom])
rust.test('rust-util-tests', _util_rs,
dependencies: [qemuutil, qom],
suite: ['unit', 'rust'])
# Doctests are essentially integration tests, so they need the same dependencies.
# Note that running them requires the object files for C code, so place them
# in a separate suite that is run by the "build" CI jobs rather than "check".
rust.doctest('rust-util-rs-doctests',
_util_rs,
protocol: 'rust',
dependencies: util_rs,
suite: ['doc', 'rust']
)

View file

@ -27,7 +27,7 @@ sub_file="${sub_tdir}/submodule.tar"
# in their checkout, because the build environment is completely
# different to the host OS.
subprojects="keycodemapdb libvfio-user berkeley-softfloat-3
berkeley-testfloat-3 anyhow-1-rs arbitrary-int-1-rs bilge-0.2-rs
berkeley-testfloat-3 anyhow-1-rs arbitrary-int-1-rs attrs-0.2-rs bilge-0.2-rs
bilge-impl-0.2-rs either-1-rs foreign-0.3-rs itertools-0.11-rs
libc-0.2-rs proc-macro2-1-rs
proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs

View file

@ -40,7 +40,7 @@ fi
# Only include wraps that are invoked with subproject()
SUBPROJECTS="libvfio-user keycodemapdb berkeley-softfloat-3
berkeley-testfloat-3 anyhow-1-rs arbitrary-int-1-rs bilge-0.2-rs
berkeley-testfloat-3 anyhow-1-rs arbitrary-int-1-rs attrs-0.2-rs bilge-0.2-rs
bilge-impl-0.2-rs either-1-rs foreign-0.3-rs itertools-0.11-rs
libc-0.2-rs proc-macro2-1-rs
proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs

View file

@ -8,6 +8,7 @@
/slirp
/anyhow-1.0.98
/arbitrary-int-1.2.7
/attrs-0.2.9
/bilge-0.2.0
/bilge-impl-0.2.0
/either-1.12.0
@ -16,7 +17,10 @@
/libc-0.2.162
/proc-macro-error-1.0.4
/proc-macro-error-attr-1.0.4
/proc-macro2-1.0.84
/proc-macro2-1.0.95
/quote-1.0.36
/syn-2.0.66
/unicode-ident-1.0.12
# Workaround for Meson v1.9.0 https://github.com/mesonbuild/meson/issues/14948
/.wraplock

View file

@ -0,0 +1,7 @@
[wrap-file]
directory = attrs-0.2.9
source_url = https://crates.io/api/v1/crates/attrs/0.2.9/download
source_filename = attrs-0.2.9.tar.gz
source_hash = 2a207d40f43de65285f3de0509bb6cb16bc46098864fce957122bbacce327e5f
#method = cargo
patch_directory = attrs-0.2-rs

View file

@ -0,0 +1,33 @@
project('attrs-0.2-rs', 'rust',
meson_version: '>=1.5.0',
version: '0.2.9',
license: 'MIT OR Apache-2.0',
default_options: [])
subproject('proc-macro2-1-rs', required: true)
subproject('syn-2-rs', required: true)
proc_macro2_dep = dependency('proc-macro2-1-rs', native: true)
syn_dep = dependency('syn-2-rs', native: true)
_attrs_rs = static_library(
'attrs',
files('src/lib.rs'),
gnu_symbol_visibility: 'hidden',
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_abi: 'rust',
rust_args: [
'--cap-lints', 'allow',
],
dependencies: [
proc_macro2_dep,
syn_dep,
],
native: true,
)
attrs_dep = declare_dependency(
link_with: _attrs_rs,
)
meson.override_dependency('attrs-0.2-rs', attrs_dep, native: true)