Browse Source

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

* rust: cleanups
* rust: integration tests
* rust/pl011: add support for migration
* rust/pl011: add TYPE_PL011_LUMINARY device
* rust: add support for older compilers and bindgen

# -----BEGIN PGP SIGNATURE-----
#
# iQFIBAABCAAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmcrrtIUHHBib256aW5p
# QHJlZGhhdC5jb20ACgkQv/vSX3jHroPIBwf/W0Jo87UauGYufhEmoPvWG1EAQEqP
# EzNTzem9Iw92VdiSTkAtED0/TSd8RBJOwDfjjusVXZtuMPwpRNgXaFhYTT5gFTMj
# Nk3NZGaX/mbNrtdrukdx9mvUWeovytdZDZccTNkpc3oyiqY9NEz06wZ0tCNJEot6
# qO3dEtKXTOQTdx2R3o0oS+2OFDGEEPxZ0PuXN3sClN4iZhGfcIDsjGAWxEh6mCDy
# VxqKPdax1Ig1w7M+JMclnpOsVHwcefjHiToNPwhCEGelJ9BZilkViuvBzsVRJJz3
# ptYyywBE0FT8MiKQ/wyf7U64qoizJuIgHoQnUGj98hdgvbUUiW5jcBNY3A==
# =s591
# -----END PGP SIGNATURE-----
# gpg: Signature made Wed 06 Nov 2024 18:00:50 GMT
# 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-rust' of https://gitlab.com/bonzini/qemu: (39 commits)
  dockerfiles: install bindgen from cargo on Ubuntu 22.04
  rust: make rustfmt optional
  rust: allow older version of bindgen
  rust: do not use --generate-cstr
  rust: allow version 1.63.0 of rustc
  rust: clean up detection of the language
  rust: do not use MaybeUninit::zeroed()
  rust: introduce alternative implementation of offset_of!
  rust: create a cargo workspace
  rust: synchronize dependencies between subprojects and Cargo.lock
  rust: silence unknown warnings for the sake of old compilers
  rust: introduce a c_str macro
  rust: use std::os::raw instead of core::ffi
  rust: fix cfgs of proc-macro2 for 1.63.0
  rust: patch bilge-impl to allow compilation with 1.63.0
  rust/pl011: Use correct masks for IBRD and FBRD
  rust/pl011: remove commented out C code
  rust/pl011: add TYPE_PL011_LUMINARY device
  rust/pl011: move CLK_NAME static to function scope
  rust/pl011: add support for migration
  ...

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Peter Maydell 9 months ago
parent
commit
a1dacb6691
45 changed files with 1378 additions and 413 deletions
  1. 2 0
      .gitattributes
  2. 1 1
      .gitlab-ci.d/buildtest.yml
  3. 12 0
      docs/about/build-platforms.rst
  4. 13 13
      hw/core/qdev-properties.c
  5. 2 2
      include/hw/qdev-core.h
  6. 2 2
      include/hw/qdev-properties.h
  7. 99 38
      meson.build
  8. 2 0
      meson_options.txt
  9. 4 0
      rust/Cargo.lock
  10. 7 0
      rust/Cargo.toml
  11. 0 1
      rust/hw/char/Kconfig
  12. 0 3
      rust/hw/char/pl011/Cargo.toml
  13. 116 46
      rust/hw/char/pl011/src/device.rs
  14. 69 11
      rust/hw/char/pl011/src/device_class.rs
  15. 5 1
      rust/hw/char/pl011/src/lib.rs
  16. 7 17
      rust/hw/char/pl011/src/memory_ops.rs
  17. 0 47
      rust/qemu-api-macros/Cargo.lock
  18. 1 4
      rust/qemu-api-macros/Cargo.toml
  19. 1 1
      rust/qemu-api-macros/meson.build
  20. 76 25
      rust/qemu-api-macros/src/lib.rs
  21. 0 7
      rust/qemu-api/Cargo.lock
  22. 6 4
      rust/qemu-api/Cargo.toml
  23. 9 0
      rust/qemu-api/build.rs
  24. 37 7
      rust/qemu-api/meson.build
  25. 53 0
      rust/qemu-api/src/c_str.rs
  26. 31 37
      rust/qemu-api/src/definitions.rs
  27. 30 84
      rust/qemu-api/src/device_class.rs
  28. 16 7
      rust/qemu-api/src/lib.rs
  29. 161 0
      rust/qemu-api/src/offset_of.rs
  30. 0 49
      rust/qemu-api/src/tests.rs
  31. 360 0
      rust/qemu-api/src/vmstate.rs
  32. 86 0
      rust/qemu-api/src/zeroable.rs
  33. 79 0
      rust/qemu-api/tests/tests.rs
  34. 17 0
      rust/wrapper.h
  35. 0 1
      scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml
  36. 0 1
      scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml
  37. 4 0
      scripts/meson-buildoptions.sh
  38. 1 0
      subprojects/bilge-impl-0.2-rs.wrap
  39. 45 0
      subprojects/packagefiles/bilge-impl-1.63.0.patch
  40. 3 1
      subprojects/packagefiles/proc-macro2-1-rs/meson.build
  41. 1 0
      subprojects/packagefiles/syn-2-rs/meson.build
  42. 1 1
      system/qdev-monitor.c
  43. 5 1
      tests/docker/dockerfiles/ubuntu2204.docker
  44. 4 0
      tests/lcitool/mappings.yml
  45. 10 1
      tests/lcitool/refresh

+ 2 - 0
.gitattributes

@@ -5,3 +5,5 @@
 *.rs            diff=rust
 *.rs            diff=rust
 *.rs.inc        diff=rust
 *.rs.inc        diff=rust
 Cargo.lock      diff=toml merge=binary
 Cargo.lock      diff=toml merge=binary
+
+*.patch         -text -whitespace

+ 1 - 1
.gitlab-ci.d/buildtest.yml

@@ -128,7 +128,7 @@ build-system-fedora-rust-nightly:
     job: amd64-fedora-rust-nightly-container
     job: amd64-fedora-rust-nightly-container
   variables:
   variables:
     IMAGE: fedora-rust-nightly
     IMAGE: fedora-rust-nightly
-    CONFIGURE_ARGS: --disable-docs --enable-rust
+    CONFIGURE_ARGS: --disable-docs --enable-rust --enable-strict-rust-lints
     TARGETS: aarch64-softmmu
     TARGETS: aarch64-softmmu
     MAKE_CHECK_ARGS: check-build
     MAKE_CHECK_ARGS: check-build
   allow_failure: true
   allow_failure: true

+ 12 - 0
docs/about/build-platforms.rst

@@ -107,6 +107,18 @@ Python build dependencies
   required, it may be necessary to fetch python modules from the Python
   required, it may be necessary to fetch python modules from the Python
   Package Index (PyPI) via ``pip``, in order to build QEMU.
   Package Index (PyPI) via ``pip``, in order to build QEMU.
 
 
+Rust build dependencies
+  QEMU is generally conservative in adding new Rust dependencies, and all
+  of them are included in the distributed tarballs.  One exception is the
+  bindgen tool, which is too big to package and distribute.  The minimum
+  supported version of bindgen is 0.60.x.  For distributions that do not
+  include bindgen or have an older version, it is recommended to install
+  a newer version using ``cargo install bindgen-cli``.
+
+  Developers may want to use Cargo-based tools in the QEMU source tree;
+  this requires Cargo 1.74.0.  Note that Cargo is not required in order
+  to build QEMU.
+
 Optional build dependencies
 Optional build dependencies
   Build components whose absence does not affect the ability to build
   Build components whose absence does not affect the ability to build
   QEMU may not be available in distros, or may be too old for QEMU's
   QEMU may not be available in distros, or may be too old for QEMU's

+ 13 - 13
hw/core/qdev-properties.c

@@ -749,7 +749,7 @@ const PropertyInfo qdev_prop_array = {
 
 
 /* --- public helpers --- */
 /* --- public helpers --- */
 
 
-static Property *qdev_prop_walk(Property *props, const char *name)
+static const Property *qdev_prop_walk(const Property *props, const char *name)
 {
 {
     if (!props) {
     if (!props) {
         return NULL;
         return NULL;
@@ -763,10 +763,10 @@ static Property *qdev_prop_walk(Property *props, const char *name)
     return NULL;
     return NULL;
 }
 }
 
 
-static Property *qdev_prop_find(DeviceState *dev, const char *name)
+static const Property *qdev_prop_find(DeviceState *dev, const char *name)
 {
 {
     ObjectClass *class;
     ObjectClass *class;
-    Property *prop;
+    const Property *prop;
 
 
     /* device properties */
     /* device properties */
     class = object_get_class(OBJECT(dev));
     class = object_get_class(OBJECT(dev));
@@ -840,7 +840,7 @@ void qdev_prop_set_string(DeviceState *dev, const char *name, const char *value)
 
 
 void qdev_prop_set_enum(DeviceState *dev, const char *name, int value)
 void qdev_prop_set_enum(DeviceState *dev, const char *name, int value)
 {
 {
-    Property *prop;
+    const Property *prop;
 
 
     prop = qdev_prop_find(dev, name);
     prop = qdev_prop_find(dev, name);
     object_property_set_str(OBJECT(dev), name,
     object_property_set_str(OBJECT(dev), name,
@@ -956,7 +956,7 @@ const PropertyInfo qdev_prop_size = {
 /* --- object link property --- */
 /* --- object link property --- */
 
 
 static ObjectProperty *create_link_property(ObjectClass *oc, const char *name,
 static ObjectProperty *create_link_property(ObjectClass *oc, const char *name,
-                                            Property *prop)
+                                            const Property *prop)
 {
 {
     return object_class_property_add_link(oc, name, prop->link_type,
     return object_class_property_add_link(oc, name, prop->link_type,
                                           prop->offset,
                                           prop->offset,
@@ -969,7 +969,7 @@ const PropertyInfo qdev_prop_link = {
     .create = create_link_property,
     .create = create_link_property,
 };
 };
 
 
-void qdev_property_add_static(DeviceState *dev, Property *prop)
+void qdev_property_add_static(DeviceState *dev, const Property *prop)
 {
 {
     Object *obj = OBJECT(dev);
     Object *obj = OBJECT(dev);
     ObjectProperty *op;
     ObjectProperty *op;
@@ -980,7 +980,7 @@ void qdev_property_add_static(DeviceState *dev, Property *prop)
                              field_prop_getter(prop->info),
                              field_prop_getter(prop->info),
                              field_prop_setter(prop->info),
                              field_prop_setter(prop->info),
                              prop->info->release,
                              prop->info->release,
-                             prop);
+                             (Property *)prop);
 
 
     object_property_set_description(obj, prop->name,
     object_property_set_description(obj, prop->name,
                                     prop->info->description);
                                     prop->info->description);
@@ -994,7 +994,7 @@ void qdev_property_add_static(DeviceState *dev, Property *prop)
 }
 }
 
 
 static void qdev_class_add_property(DeviceClass *klass, const char *name,
 static void qdev_class_add_property(DeviceClass *klass, const char *name,
-                                    Property *prop)
+                                    const Property *prop)
 {
 {
     ObjectClass *oc = OBJECT_CLASS(klass);
     ObjectClass *oc = OBJECT_CLASS(klass);
     ObjectProperty *op;
     ObjectProperty *op;
@@ -1007,7 +1007,7 @@ static void qdev_class_add_property(DeviceClass *klass, const char *name,
                                        field_prop_getter(prop->info),
                                        field_prop_getter(prop->info),
                                        field_prop_setter(prop->info),
                                        field_prop_setter(prop->info),
                                        prop->info->release,
                                        prop->info->release,
-                                       prop);
+                                       (Property *)prop);
     }
     }
     if (prop->set_default) {
     if (prop->set_default) {
         prop->info->set_default_value(op, prop);
         prop->info->set_default_value(op, prop);
@@ -1046,7 +1046,7 @@ static void qdev_get_legacy_property(Object *obj, Visitor *v,
  * Do not use this in new code!  QOM Properties added through this interface
  * Do not use this in new code!  QOM Properties added through this interface
  * will be given names in the "legacy" namespace.
  * will be given names in the "legacy" namespace.
  */
  */
-static void qdev_class_add_legacy_property(DeviceClass *dc, Property *prop)
+static void qdev_class_add_legacy_property(DeviceClass *dc, const Property *prop)
 {
 {
     g_autofree char *name = NULL;
     g_autofree char *name = NULL;
 
 
@@ -1058,12 +1058,12 @@ static void qdev_class_add_legacy_property(DeviceClass *dc, Property *prop)
     name = g_strdup_printf("legacy-%s", prop->name);
     name = g_strdup_printf("legacy-%s", prop->name);
     object_class_property_add(OBJECT_CLASS(dc), name, "str",
     object_class_property_add(OBJECT_CLASS(dc), name, "str",
         prop->info->print ? qdev_get_legacy_property : prop->info->get,
         prop->info->print ? qdev_get_legacy_property : prop->info->get,
-        NULL, NULL, prop);
+        NULL, NULL, (Property *)prop);
 }
 }
 
 
-void device_class_set_props(DeviceClass *dc, Property *props)
+void device_class_set_props(DeviceClass *dc, const Property *props)
 {
 {
-    Property *prop;
+    const Property *prop;
 
 
     dc->props_ = props;
     dc->props_ = props;
     for (prop = props; prop && prop->name; prop++) {
     for (prop = props; prop && prop->name; prop++) {

+ 2 - 2
include/hw/qdev-core.h

@@ -136,7 +136,7 @@ struct DeviceClass {
      * ensures a compile-time error if someone attempts to assign
      * ensures a compile-time error if someone attempts to assign
      * dc->props directly.
      * dc->props directly.
      */
      */
-    Property *props_;
+    const Property *props_;
 
 
     /**
     /**
      * @user_creatable: Can user instantiate with -device / device_add?
      * @user_creatable: Can user instantiate with -device / device_add?
@@ -941,7 +941,7 @@ char *qdev_get_own_fw_dev_path_from_handler(BusState *bus, DeviceState *dev);
  * you attempt to add an existing property defined by a parent class.
  * you attempt to add an existing property defined by a parent class.
  * To modify an inherited property you need to use????
  * To modify an inherited property you need to use????
  */
  */
-void device_class_set_props(DeviceClass *dc, Property *props);
+void device_class_set_props(DeviceClass *dc, const Property *props);
 
 
 /**
 /**
  * device_class_set_parent_realize() - set up for chaining realize fns
  * device_class_set_parent_realize() - set up for chaining realize fns

+ 2 - 2
include/hw/qdev-properties.h

@@ -37,7 +37,7 @@ struct PropertyInfo {
     int (*print)(Object *obj, Property *prop, char *dest, size_t len);
     int (*print)(Object *obj, Property *prop, char *dest, size_t len);
     void (*set_default_value)(ObjectProperty *op, const Property *prop);
     void (*set_default_value)(ObjectProperty *op, const Property *prop);
     ObjectProperty *(*create)(ObjectClass *oc, const char *name,
     ObjectProperty *(*create)(ObjectClass *oc, const char *name,
-                              Property *prop);
+                              const Property *prop);
     ObjectPropertyAccessor *get;
     ObjectPropertyAccessor *get;
     ObjectPropertyAccessor *set;
     ObjectPropertyAccessor *set;
     ObjectPropertyRelease *release;
     ObjectPropertyRelease *release;
@@ -223,7 +223,7 @@ void error_set_from_qdev_prop_error(Error **errp, int ret, Object *obj,
  * On error, store error in @errp.  Static properties access data in a struct.
  * On error, store error in @errp.  Static properties access data in a struct.
  * The type of the QOM property is derived from prop->info.
  * The type of the QOM property is derived from prop->info.
  */
  */
-void qdev_property_add_static(DeviceState *dev, Property *prop);
+void qdev_property_add_static(DeviceState *dev, const Property *prop);
 
 
 /**
 /**
  * qdev_alias_all_properties: Create aliases on source for all target properties
  * qdev_alias_all_properties: Create aliases on source for all target properties

+ 99 - 38
meson.build

@@ -15,6 +15,7 @@ meson.add_postconf_script(find_program('scripts/symlink-install-tree.py'))
 
 
 not_found = dependency('', required: false)
 not_found = dependency('', required: false)
 keyval = import('keyval')
 keyval = import('keyval')
+rust = import('rust')
 ss = import('sourceset')
 ss = import('sourceset')
 fs = import('fs')
 fs = import('fs')
 
 
@@ -52,6 +53,17 @@ cpu = host_machine.cpu_family()
 
 
 target_dirs = config_host['TARGET_DIRS'].split()
 target_dirs = config_host['TARGET_DIRS'].split()
 
 
+# type of binaries to build
+have_linux_user = false
+have_bsd_user = false
+have_system = false
+foreach target : target_dirs
+  have_linux_user = have_linux_user or target.endswith('linux-user')
+  have_bsd_user = have_bsd_user or target.endswith('bsd-user')
+  have_system = have_system or target.endswith('-softmmu')
+endforeach
+have_user = have_linux_user or have_bsd_user
+
 ############
 ############
 # Programs #
 # Programs #
 ############
 ############
@@ -70,21 +82,45 @@ if host_os == 'darwin' and \
   all_languages += ['objc']
   all_languages += ['objc']
   objc = meson.get_compiler('objc')
   objc = meson.get_compiler('objc')
 endif
 endif
-have_rust = false
-if not get_option('rust').disabled() and add_languages('rust', required: get_option('rust'), native: false) \
-    and add_languages('rust', required: get_option('rust'), native: true)
+
+have_rust = add_languages('rust', native: false,
+    required: get_option('rust').disable_auto_if(not have_system))
+have_rust = have_rust and add_languages('rust', native: true,
+    required: get_option('rust').disable_auto_if(not have_system))
+if have_rust
   rustc = meson.get_compiler('rust')
   rustc = meson.get_compiler('rust')
-  have_rust = true
-  if rustc.version().version_compare('<1.80.0')
+  if rustc.version().version_compare('<1.63.0')
+    if get_option('rust').enabled()
+      error('rustc version ' + rustc.version() + ' is unsupported. Please upgrade to at least 1.63.0')
+    else
+      warning('rustc version ' + rustc.version() + ' is unsupported, disabling Rust compilation.')
+      message('Please upgrade to at least 1.63.0 to use Rust.')
+      have_rust = false
+    endif
+  endif
+endif
+
+if have_rust
+  bindgen = find_program('bindgen', required: get_option('rust'))
+  if not bindgen.found() or bindgen.version().version_compare('<0.60.0')
     if get_option('rust').enabled()
     if get_option('rust').enabled()
-      error('rustc version ' + rustc.version() + ' is unsupported: Please upgrade to at least 1.80.0')
+      error('bindgen version ' + bindgen.version() + ' is unsupported. You can install a new version with "cargo install bindgen-cli"')
     else
     else
-      warning('rustc version ' + rustc.version() + ' is unsupported: Disabling Rust compilation. Please upgrade to at least 1.80.0 to use Rust.')
+      if bindgen.found()
+        warning('bindgen version ' + bindgen.version() + ' is unsupported, disabling Rust compilation.')
+      else
+        warning('bindgen not found, disabling Rust compilation.')
+      endif
+      message('To use Rust you can install a new version with "cargo install bindgen-cli"')
       have_rust = false
       have_rust = false
     endif
     endif
   endif
   endif
 endif
 endif
 
 
+if have_rust
+  rustfmt = find_program('rustfmt', required: false)
+endif
+
 dtrace = not_found
 dtrace = not_found
 stap = not_found
 stap = not_found
 if 'dtrace' in get_option('trace_backends')
 if 'dtrace' in get_option('trace_backends')
@@ -185,17 +221,6 @@ have_vhost_net_vdpa = have_vhost_vdpa and get_option('vhost_net').allowed()
 have_vhost_net_kernel = have_vhost_kernel and get_option('vhost_net').allowed()
 have_vhost_net_kernel = have_vhost_kernel and get_option('vhost_net').allowed()
 have_vhost_net = have_vhost_net_kernel or have_vhost_net_user or have_vhost_net_vdpa
 have_vhost_net = have_vhost_net_kernel or have_vhost_net_user or have_vhost_net_vdpa
 
 
-# type of binaries to build
-have_linux_user = false
-have_bsd_user = false
-have_system = false
-foreach target : target_dirs
-  have_linux_user = have_linux_user or target.endswith('linux-user')
-  have_bsd_user = have_bsd_user or target.endswith('bsd-user')
-  have_system = have_system or target.endswith('-softmmu')
-endforeach
-have_user = have_linux_user or have_bsd_user
-
 have_tools = get_option('tools') \
 have_tools = get_option('tools') \
   .disable_auto_if(not have_system) \
   .disable_auto_if(not have_system) \
   .allowed()
   .allowed()
@@ -3374,6 +3399,35 @@ endif
 
 
 genh += configure_file(output: 'config-host.h', configuration: config_host_data)
 genh += configure_file(output: 'config-host.h', configuration: config_host_data)
 
 
+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')
 qapi_gen = find_program('scripts/qapi-gen.py')
 qapi_gen = find_program('scripts/qapi-gen.py')
@@ -3971,32 +4025,37 @@ common_all = static_library('common',
                             implicit_include_directories: false,
                             implicit_include_directories: false,
                             dependencies: common_ss.all_dependencies())
                             dependencies: common_ss.all_dependencies())
 
 
-if have_rust and have_system
-  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()
-  rustc_args += ['-D', 'unsafe_op_in_unsafe_fn']
+if have_rust
+  # We would like to use --generate-cstr, but it is only available
+  # starting with bindgen 0.66.0.  The oldest supported versions
+  # is 0.60.x (Debian 12 has 0.60.1) which introduces --allowlist-file.
   bindgen_args = [
   bindgen_args = [
     '--disable-header-comment',
     '--disable-header-comment',
     '--raw-line', '// @generated',
     '--raw-line', '// @generated',
-    '--ctypes-prefix', 'core::ffi',
-    '--formatter', 'rustfmt',
+    '--ctypes-prefix', 'std::os::raw',
     '--generate-block',
     '--generate-block',
-    '--generate-cstr',
     '--impl-debug',
     '--impl-debug',
-    '--merge-extern-blocks',
     '--no-doc-comments',
     '--no-doc-comments',
-    '--use-core',
     '--with-derive-default',
     '--with-derive-default',
-    '--no-size_t-is-usize',
     '--no-layout-tests',
     '--no-layout-tests',
     '--no-prepend-enum-name',
     '--no-prepend-enum-name',
     '--allowlist-file', meson.project_source_root() + '/include/.*',
     '--allowlist-file', meson.project_source_root() + '/include/.*',
     '--allowlist-file', meson.project_source_root() + '/.*',
     '--allowlist-file', meson.project_source_root() + '/.*',
     '--allowlist-file', meson.project_build_root() + '/.*'
     '--allowlist-file', meson.project_build_root() + '/.*'
     ]
     ]
+  if not rustfmt.found()
+    if bindgen.version().version_compare('<0.65.0')
+      bindgen_args += ['--no-rustfmt-bindings']
+    else
+      bindgen_args += ['--formatter', 'none']
+    endif
+  endif
+  if bindgen.version().version_compare('<0.61.0')
+    # default in 0.61+
+    bindgen_args += ['--size_t-is-usize']
+  else
+    bindgen_args += ['--merge-extern-blocks']
+  endif
   c_enums = [
   c_enums = [
     'DeviceCategory',
     'DeviceCategory',
     'GpioPolarity',
     'GpioPolarity',
@@ -4027,12 +4086,12 @@ if have_rust and have_system
   # this case you must pass the path to `clang` and `libclang` to your build
   # this case you must pass the path to `clang` and `libclang` to your build
   # command invocation using the environment variables CLANG_PATH and
   # command invocation using the environment variables CLANG_PATH and
   # LIBCLANG_PATH
   # LIBCLANG_PATH
-  bindings_rs = import('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.rs',
     include_directories: include_directories('.', 'include'),
     include_directories: include_directories('.', 'include'),
-    bindgen_version: ['>=0.69.4'],
+    bindgen_version: ['>=0.60.0'],
     args: bindgen_args,
     args: bindgen_args,
     )
     )
   subdir('rust')
   subdir('rust')
@@ -4040,6 +4099,7 @@ endif
 
 
 
 
 feature_to_c = find_program('scripts/feature_to_c.py')
 feature_to_c = find_program('scripts/feature_to_c.py')
+rust_root_crate = find_program('scripts/rust/rust_root_crate.sh')
 
 
 if host_os == 'darwin'
 if host_os == 'darwin'
   entitlement = find_program('scripts/entitlement.sh')
   entitlement = find_program('scripts/entitlement.sh')
@@ -4132,7 +4192,7 @@ foreach target : target_dirs
   arch_srcs += target_specific.sources()
   arch_srcs += target_specific.sources()
   arch_deps += target_specific.dependencies()
   arch_deps += target_specific.dependencies()
 
 
-  if have_rust and have_system
+  if have_rust and target_type == 'system'
     target_rust = rust_devices_ss.apply(config_target, strict: false)
     target_rust = rust_devices_ss.apply(config_target, strict: false)
     crates = []
     crates = []
     foreach dep : target_rust.dependencies()
     foreach dep : target_rust.dependencies()
@@ -4141,7 +4201,7 @@ foreach target : target_dirs
     if crates.length() > 0
     if crates.length() > 0
       rlib_rs = custom_target('rust_' + target.underscorify() + '.rs',
       rlib_rs = custom_target('rust_' + target.underscorify() + '.rs',
                               output: 'rust_' + target.underscorify() + '.rs',
                               output: 'rust_' + target.underscorify() + '.rs',
-                              command: [find_program('scripts/rust/rust_root_crate.sh')] + crates,
+                              command: [rust_root_crate, crates],
                               capture: true,
                               capture: true,
                               build_by_default: true,
                               build_by_default: true,
                               build_always_stale: true)
                               build_always_stale: true)
@@ -4149,7 +4209,6 @@ foreach target : target_dirs
                             rlib_rs,
                             rlib_rs,
                             dependencies: target_rust.dependencies(),
                             dependencies: target_rust.dependencies(),
                             override_options: ['rust_std=2021', 'build.rust_std=2021'],
                             override_options: ['rust_std=2021', 'build.rust_std=2021'],
-                            rust_args: rustc_args,
                             rust_abi: 'c')
                             rust_abi: 'c')
       arch_deps += declare_dependency(link_whole: [rlib])
       arch_deps += declare_dependency(link_whole: [rlib])
     endif
     endif
@@ -4495,9 +4554,11 @@ else
 endif
 endif
 summary_info += {'Rust support':      have_rust}
 summary_info += {'Rust support':      have_rust}
 if have_rust
 if have_rust
-  summary_info += {'rustc version':   rustc.version()}
-  summary_info += {'rustc':           ' '.join(rustc.cmd_array())}
   summary_info += {'Rust target':     config_host['RUST_TARGET_TRIPLE']}
   summary_info += {'Rust target':     config_host['RUST_TARGET_TRIPLE']}
+  summary_info += {'rustc':           ' '.join(rustc.cmd_array())}
+  summary_info += {'rustc version':   rustc.version()}
+  summary_info += {'bindgen':         bindgen.full_path()}
+  summary_info += {'bindgen version': bindgen.version()}
 endif
 endif
 option_cflags = (get_option('debug') ? ['-g'] : [])
 option_cflags = (get_option('debug') ? ['-g'] : [])
 if get_option('optimization') != 'plain'
 if get_option('optimization') != 'plain'

+ 2 - 0
meson_options.txt

@@ -380,3 +380,5 @@ option('x86_version', type : 'combo', choices : ['0', '1', '2', '3', '4'], value
 
 
 option('rust', type: 'feature', value: 'disabled',
 option('rust', type: 'feature', value: 'disabled',
        description: 'Rust support')
        description: 'Rust support')
+option('strict_rust_lints', type: 'boolean', value: false,
+       description: 'Enable stricter set of Rust warnings')

+ 4 - 0
rust/hw/char/pl011/Cargo.lock → rust/Cargo.lock

@@ -91,6 +91,10 @@ dependencies = [
 [[package]]
 [[package]]
 name = "qemu_api"
 name = "qemu_api"
 version = "0.1.0"
 version = "0.1.0"
+dependencies = [
+ "qemu_api_macros",
+ "version_check",
+]
 
 
 [[package]]
 [[package]]
 name = "qemu_api_macros"
 name = "qemu_api_macros"

+ 7 - 0
rust/Cargo.toml

@@ -0,0 +1,7 @@
+[workspace]
+resolver = "2"
+members = [
+    "qemu-api-macros",
+    "qemu-api",
+    "hw/char/pl011",
+]

+ 0 - 1
rust/hw/char/Kconfig

@@ -1,3 +1,2 @@
 config X_PL011_RUST
 config X_PL011_RUST
     bool
     bool
-    default y if HAVE_RUST

+ 0 - 3
rust/hw/char/pl011/Cargo.toml

@@ -21,6 +21,3 @@ 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" }
-
-# Do not include in any global workspace
-[workspace]

+ 116 - 46
rust/hw/char/pl011/src/device.rs

@@ -2,14 +2,17 @@
 // 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::{
-    ffi::{c_int, c_uchar, c_uint, c_void, CStr},
-    ptr::{addr_of, addr_of_mut, NonNull},
+use core::ptr::{addr_of, addr_of_mut, NonNull};
+use std::{
+    ffi::CStr,
+    os::raw::{c_int, c_uchar, c_uint, c_void},
 };
 };
 
 
 use qemu_api::{
 use qemu_api::{
     bindings::{self, *},
     bindings::{self, *},
+    c_str,
     definitions::ObjectImpl,
     definitions::ObjectImpl,
+    device_class::TYPE_SYS_BUS_DEVICE,
 };
 };
 
 
 use crate::{
 use crate::{
@@ -18,15 +21,42 @@
     RegisterOffset,
     RegisterOffset,
 };
 };
 
 
-static PL011_ID_ARM: [c_uchar; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
+/// Integer Baud Rate Divider, `UARTIBRD`
+const IBRD_MASK: u32 = 0xffff;
+
+/// Fractional Baud Rate Divider, `UARTFBRD`
+const FBRD_MASK: u32 = 0x3f;
 
 
 const DATA_BREAK: u32 = 1 << 10;
 const DATA_BREAK: u32 = 1 << 10;
 
 
 /// QEMU sourced constant.
 /// QEMU sourced constant.
 pub const PL011_FIFO_DEPTH: usize = 16_usize;
 pub const PL011_FIFO_DEPTH: usize = 16_usize;
 
 
+#[derive(Clone, Copy, Debug)]
+enum DeviceId {
+    #[allow(dead_code)]
+    Arm = 0,
+    Luminary,
+}
+
+impl std::ops::Index<hwaddr> for DeviceId {
+    type Output = c_uchar;
+
+    fn index(&self, idx: hwaddr) -> &Self::Output {
+        match self {
+            Self::Arm => &Self::PL011_ID_ARM[idx as usize],
+            Self::Luminary => &Self::PL011_ID_LUMINARY[idx as usize],
+        }
+    }
+}
+
+impl DeviceId {
+    const PL011_ID_ARM: [c_uchar; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
+    const PL011_ID_LUMINARY: [c_uchar; 8] = [0x11, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1];
+}
+
 #[repr(C)]
 #[repr(C)]
-#[derive(Debug, qemu_api_macros::Object)]
+#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::offsets)]
 /// PL011 Device Model in QEMU
 /// PL011 Device Model in QEMU
 pub struct PL011State {
 pub struct PL011State {
     pub parent_obj: SysBusDevice,
     pub parent_obj: SysBusDevice,
@@ -69,6 +99,8 @@ pub struct PL011State {
     pub clock: NonNull<Clock>,
     pub clock: NonNull<Clock>,
     #[doc(alias = "migrate_clk")]
     #[doc(alias = "migrate_clk")]
     pub migrate_clock: bool,
     pub migrate_clock: bool,
+    /// The byte string that identifies the device.
+    device_id: DeviceId,
 }
 }
 
 
 impl ObjectImpl for PL011State {
 impl ObjectImpl for PL011State {
@@ -88,17 +120,13 @@ pub struct PL011Class {
 }
 }
 
 
 impl qemu_api::definitions::Class for PL011Class {
 impl qemu_api::definitions::Class for PL011Class {
-    const CLASS_INIT: Option<
-        unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void),
-    > = Some(crate::device_class::pl011_class_init);
+    const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)> =
+        Some(crate::device_class::pl011_class_init);
     const CLASS_BASE_INIT: Option<
     const CLASS_BASE_INIT: Option<
-        unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut core::ffi::c_void),
+        unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
     > = None;
     > = None;
 }
 }
 
 
-#[used]
-pub static CLK_NAME: &CStr = c"clk";
-
 impl PL011State {
 impl PL011State {
     /// Initializes a pre-allocated, unitialized instance of `PL011State`.
     /// Initializes a pre-allocated, unitialized instance of `PL011State`.
     ///
     ///
@@ -108,7 +136,9 @@ impl PL011State {
     /// `PL011State` type. It must not be called more than once on the same
     /// `PL011State` type. It must not be called more than once on the same
     /// location/instance. All its fields are expected to hold unitialized
     /// location/instance. All its fields are expected to hold unitialized
     /// values with the sole exception of `parent_obj`.
     /// values with the sole exception of `parent_obj`.
-    pub unsafe fn init(&mut self) {
+    unsafe fn init(&mut self) {
+        const CLK_NAME: &CStr = c_str!("clk");
+
         let dev = addr_of_mut!(*self).cast::<DeviceState>();
         let dev = addr_of_mut!(*self).cast::<DeviceState>();
         // SAFETY:
         // SAFETY:
         //
         //
@@ -148,23 +178,18 @@ pub unsafe fn init(&mut self) {
         }
         }
     }
     }
 
 
-    pub fn read(
-        &mut self,
-        offset: hwaddr,
-        _size: core::ffi::c_uint,
-    ) -> std::ops::ControlFlow<u64, u64> {
+    pub fn read(&mut self, offset: hwaddr, _size: c_uint) -> std::ops::ControlFlow<u64, u64> {
         use RegisterOffset::*;
         use RegisterOffset::*;
 
 
         std::ops::ControlFlow::Break(match RegisterOffset::try_from(offset) {
         std::ops::ControlFlow::Break(match RegisterOffset::try_from(offset) {
             Err(v) if (0x3f8..0x400).contains(&v) => {
             Err(v) if (0x3f8..0x400).contains(&v) => {
-                u64::from(PL011_ID_ARM[((offset - 0xfe0) >> 2) as usize])
+                u64::from(self.device_id[(offset - 0xfe0) >> 2])
             }
             }
             Err(_) => {
             Err(_) => {
                 // qemu_log_mask(LOG_GUEST_ERROR, "pl011_read: Bad offset 0x%x\n", (int)offset);
                 // qemu_log_mask(LOG_GUEST_ERROR, "pl011_read: Bad offset 0x%x\n", (int)offset);
                 0
                 0
             }
             }
             Ok(DR) => {
             Ok(DR) => {
-                // s->flags &= ~PL011_FLAG_RXFF;
                 self.flags.set_receive_fifo_full(false);
                 self.flags.set_receive_fifo_full(false);
                 let c = self.read_fifo[self.read_pos];
                 let c = self.read_fifo[self.read_pos];
                 if self.read_count > 0 {
                 if self.read_count > 0 {
@@ -172,11 +197,9 @@ pub fn read(
                     self.read_pos = (self.read_pos + 1) & (self.fifo_depth() - 1);
                     self.read_pos = (self.read_pos + 1) & (self.fifo_depth() - 1);
                 }
                 }
                 if self.read_count == 0 {
                 if self.read_count == 0 {
-                    // self.flags |= PL011_FLAG_RXFE;
                     self.flags.set_receive_fifo_empty(true);
                     self.flags.set_receive_fifo_empty(true);
                 }
                 }
                 if self.read_count + 1 == self.read_trigger {
                 if self.read_count + 1 == self.read_trigger {
-                    //self.int_level &= ~ INT_RX;
                     self.int_level &= !registers::INT_RX;
                     self.int_level &= !registers::INT_RX;
                 }
                 }
                 // Update error bits.
                 // Update error bits.
@@ -346,13 +369,6 @@ fn loopback_mdmctrl(&mut self) {
          * dealt with here.
          * dealt with here.
          */
          */
 
 
-        //fr = s->flags & ~(PL011_FLAG_RI | PL011_FLAG_DCD |
-        //                  PL011_FLAG_DSR | PL011_FLAG_CTS);
-        //fr |= (cr & CR_OUT2) ? PL011_FLAG_RI  : 0;
-        //fr |= (cr & CR_OUT1) ? PL011_FLAG_DCD : 0;
-        //fr |= (cr & CR_RTS)  ? PL011_FLAG_CTS : 0;
-        //fr |= (cr & CR_DTR)  ? PL011_FLAG_DSR : 0;
-        //
         self.flags.set_ring_indicator(self.control.out_2());
         self.flags.set_ring_indicator(self.control.out_2());
         self.flags.set_data_carrier_detect(self.control.out_1());
         self.flags.set_data_carrier_detect(self.control.out_1());
         self.flags.set_clear_to_send(self.control.request_to_send());
         self.flags.set_clear_to_send(self.control.request_to_send());
@@ -363,10 +379,6 @@ fn loopback_mdmctrl(&mut self) {
         let mut il = self.int_level;
         let mut il = self.int_level;
 
 
         il &= !Interrupt::MS;
         il &= !Interrupt::MS;
-        //il |= (fr & PL011_FLAG_DSR) ? INT_DSR : 0;
-        //il |= (fr & PL011_FLAG_DCD) ? INT_DCD : 0;
-        //il |= (fr & PL011_FLAG_CTS) ? INT_CTS : 0;
-        //il |= (fr & PL011_FLAG_RI)  ? INT_RI  : 0;
 
 
         if self.flags.data_set_ready() {
         if self.flags.data_set_ready() {
             il |= Interrupt::DSR as u32;
             il |= Interrupt::DSR as u32;
@@ -472,10 +484,8 @@ pub fn put_fifo(&mut self, value: c_uint) {
         let slot = (self.read_pos + self.read_count) & (depth - 1);
         let slot = (self.read_pos + self.read_count) & (depth - 1);
         self.read_fifo[slot] = value;
         self.read_fifo[slot] = value;
         self.read_count += 1;
         self.read_count += 1;
-        // s->flags &= ~PL011_FLAG_RXFE;
         self.flags.set_receive_fifo_empty(false);
         self.flags.set_receive_fifo_empty(false);
         if self.read_count == depth {
         if self.read_count == depth {
-            //s->flags |= PL011_FLAG_RXFF;
             self.flags.set_receive_fifo_full(true);
             self.flags.set_receive_fifo_full(true);
         }
         }
 
 
@@ -492,6 +502,27 @@ pub fn update(&self) {
             unsafe { qemu_set_irq(*irq, i32::from(flags & i != 0)) };
             unsafe { qemu_set_irq(*irq, i32::from(flags & i != 0)) };
         }
         }
     }
     }
+
+    pub fn post_load(&mut self, _version_id: u32) -> Result<(), ()> {
+        /* Sanity-check input state */
+        if self.read_pos >= self.read_fifo.len() || self.read_count > self.read_fifo.len() {
+            return Err(());
+        }
+
+        if !self.fifo_enabled() && self.read_count > 0 && self.read_pos > 0 {
+            // Older versions of PL011 didn't ensure that the single
+            // character in the FIFO in FIFO-disabled mode is in
+            // element 0 of the array; convert to follow the current
+            // code's assumptions.
+            self.read_fifo[0] = self.read_fifo[self.read_pos];
+            self.read_pos = 0;
+        }
+
+        self.ibrd &= IBRD_MASK;
+        self.fbrd &= FBRD_MASK;
+
+        Ok(())
+    }
 }
 }
 
 
 /// Which bits in the interrupt status matter for each outbound IRQ line ?
 /// Which bits in the interrupt status matter for each outbound IRQ line ?
@@ -514,7 +545,6 @@ pub fn update(&self) {
 /// 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 has
 /// the same size as [`PL011State`]. We also expect the device is
 /// the same size as [`PL011State`]. We also expect the device is
 /// readable/writeable from one thread at any time.
 /// readable/writeable from one thread at any time.
-#[no_mangle]
 pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int {
 pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int {
     unsafe {
     unsafe {
         debug_assert!(!opaque.is_null());
         debug_assert!(!opaque.is_null());
@@ -530,12 +560,7 @@ pub fn update(&self) {
 /// readable/writeable from one thread at any time.
 /// readable/writeable from one thread at any time.
 ///
 ///
 /// The buffer and size arguments must also be valid.
 /// The buffer and size arguments must also be valid.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_receive(
-    opaque: *mut core::ffi::c_void,
-    buf: *const u8,
-    size: core::ffi::c_int,
-) {
+pub unsafe extern "C" fn pl011_receive(opaque: *mut c_void, buf: *const u8, size: c_int) {
     unsafe {
     unsafe {
         debug_assert!(!opaque.is_null());
         debug_assert!(!opaque.is_null());
         let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
         let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
@@ -554,8 +579,7 @@ pub fn update(&self) {
 /// 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 has
 /// the same size as [`PL011State`]. We also expect the device is
 /// the same size as [`PL011State`]. We also expect the device is
 /// readable/writeable from one thread at any time.
 /// readable/writeable from one thread at any time.
-#[no_mangle]
-pub unsafe extern "C" fn pl011_event(opaque: *mut core::ffi::c_void, event: QEMUChrEvent) {
+pub unsafe extern "C" fn pl011_event(opaque: *mut c_void, event: QEMUChrEvent) {
     unsafe {
     unsafe {
         debug_assert!(!opaque.is_null());
         debug_assert!(!opaque.is_null());
         let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
         let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
@@ -576,7 +600,7 @@ pub fn update(&self) {
         let dev: *mut DeviceState = qdev_new(PL011State::TYPE_INFO.name);
         let dev: *mut DeviceState = qdev_new(PL011State::TYPE_INFO.name);
         let sysbus: *mut SysBusDevice = dev.cast::<SysBusDevice>();
         let sysbus: *mut SysBusDevice = dev.cast::<SysBusDevice>();
 
 
-        qdev_prop_set_chr(dev, bindings::TYPE_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!(error_fatal) as *mut *mut Error);
         sysbus_mmio_map(sysbus, 0, addr);
         sysbus_mmio_map(sysbus, 0, addr);
         sysbus_connect_irq(sysbus, 0, irq);
         sysbus_connect_irq(sysbus, 0, irq);
@@ -589,7 +613,6 @@ pub fn update(&self) {
 /// 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 has
 /// the same size as [`PL011State`]. We also expect the device is
 /// the same size as [`PL011State`]. We also expect the device is
 /// readable/writeable from one thread at any time.
 /// readable/writeable from one thread at any time.
-#[no_mangle]
 pub unsafe extern "C" fn pl011_init(obj: *mut Object) {
 pub unsafe extern "C" fn pl011_init(obj: *mut Object) {
     unsafe {
     unsafe {
         debug_assert!(!obj.is_null());
         debug_assert!(!obj.is_null());
@@ -597,3 +620,50 @@ pub fn update(&self) {
         state.as_mut().init();
         state.as_mut().init();
     }
     }
 }
 }
+
+#[repr(C)]
+#[derive(Debug, qemu_api_macros::Object)]
+/// PL011 Luminary device model.
+pub struct PL011Luminary {
+    parent_obj: PL011State,
+}
+
+#[repr(C)]
+pub struct PL011LuminaryClass {
+    _inner: [u8; 0],
+}
+
+/// Initializes a pre-allocated, unitialized instance of `PL011Luminary`.
+///
+/// # Safety
+///
+/// We expect the FFI user of this function to pass a valid pointer, that has
+/// the same size as [`PL011Luminary`]. We also expect the device is
+/// readable/writeable from one thread at any time.
+pub unsafe extern "C" fn pl011_luminary_init(obj: *mut Object) {
+    unsafe {
+        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 {
+    const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)> =
+        None;
+    const CLASS_BASE_INIT: Option<
+        unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
+    > = None;
+}
+
+impl ObjectImpl for PL011Luminary {
+    type Class = PL011LuminaryClass;
+    const TYPE_INFO: qemu_api::bindings::TypeInfo = qemu_api::type_info! { Self };
+    const TYPE_NAME: &'static CStr = crate::TYPE_PL011_LUMINARY;
+    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;
+}

+ 69 - 11
rust/hw/char/pl011/src/device_class.rs

@@ -3,33 +3,93 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 
 use core::ptr::NonNull;
 use core::ptr::NonNull;
+use std::os::raw::{c_int, c_void};
 
 
-use qemu_api::{bindings::*, definitions::ObjectImpl};
+use qemu_api::{
+    bindings::*, c_str, vmstate_clock, vmstate_fields, vmstate_int32, vmstate_subsections,
+    vmstate_uint32, vmstate_uint32_array, vmstate_unused, zeroable::Zeroable,
+};
+
+use crate::device::{PL011State, PL011_FIFO_DEPTH};
+
+extern "C" fn pl011_clock_needed(opaque: *mut c_void) -> bool {
+    unsafe {
+        debug_assert!(!opaque.is_null());
+        let state = NonNull::new_unchecked(opaque.cast::<PL011State>());
+        state.as_ref().migrate_clock
+    }
+}
 
 
-use crate::device::PL011State;
+/// Migration subsection for [`PL011State`] clock.
+pub static VMSTATE_PL011_CLOCK: VMStateDescription = VMStateDescription {
+    name: c_str!("pl011/clock").as_ptr(),
+    version_id: 1,
+    minimum_version_id: 1,
+    needed: Some(pl011_clock_needed),
+    fields: vmstate_fields! {
+        vmstate_clock!(clock, PL011State),
+    },
+    ..Zeroable::ZERO
+};
+
+extern "C" fn pl011_post_load(opaque: *mut c_void, version_id: c_int) -> c_int {
+    unsafe {
+        debug_assert!(!opaque.is_null());
+        let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
+        let result = state.as_mut().post_load(version_id as u32);
+        if result.is_err() {
+            -1
+        } else {
+            0
+        }
+    }
+}
 
 
-#[used]
 pub static VMSTATE_PL011: VMStateDescription = VMStateDescription {
 pub static VMSTATE_PL011: VMStateDescription = VMStateDescription {
-    name: PL011State::TYPE_INFO.name,
-    unmigratable: true,
-    ..unsafe { ::core::mem::MaybeUninit::<VMStateDescription>::zeroed().assume_init() }
+    name: c_str!("pl011").as_ptr(),
+    version_id: 2,
+    minimum_version_id: 2,
+    post_load: Some(pl011_post_load),
+    fields: vmstate_fields! {
+        vmstate_unused!(core::mem::size_of::<u32>()),
+        vmstate_uint32!(flags, PL011State),
+        vmstate_uint32!(line_control, PL011State),
+        vmstate_uint32!(receive_status_error_clear, PL011State),
+        vmstate_uint32!(control, PL011State),
+        vmstate_uint32!(dmacr, PL011State),
+        vmstate_uint32!(int_enabled, PL011State),
+        vmstate_uint32!(int_level, PL011State),
+        vmstate_uint32_array!(read_fifo, PL011State, PL011_FIFO_DEPTH),
+        vmstate_uint32!(ilpr, PL011State),
+        vmstate_uint32!(ibrd, PL011State),
+        vmstate_uint32!(fbrd, PL011State),
+        vmstate_uint32!(ifl, PL011State),
+        vmstate_int32!(read_pos, PL011State),
+        vmstate_int32!(read_count, PL011State),
+        vmstate_int32!(read_trigger, PL011State),
+    },
+    subsections: vmstate_subsections! {
+        VMSTATE_PL011_CLOCK
+    },
+    ..Zeroable::ZERO
 };
 };
 
 
 qemu_api::declare_properties! {
 qemu_api::declare_properties! {
     PL011_PROPERTIES,
     PL011_PROPERTIES,
     qemu_api::define_property!(
     qemu_api::define_property!(
-        c"chardev",
+        c_str!("chardev"),
         PL011State,
         PL011State,
         char_backend,
         char_backend,
         unsafe { &qdev_prop_chr },
         unsafe { &qdev_prop_chr },
         CharBackend
         CharBackend
     ),
     ),
     qemu_api::define_property!(
     qemu_api::define_property!(
-        c"migrate-clk",
+        c_str!("migrate-clk"),
         PL011State,
         PL011State,
         migrate_clock,
         migrate_clock,
         unsafe { &qdev_prop_bool },
         unsafe { &qdev_prop_bool },
-        bool
+        bool,
+        default = true
     ),
     ),
 }
 }
 
 
@@ -46,7 +106,6 @@
 /// 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 has
 /// the same size as [`PL011State`]. We also expect the device is
 /// the same size as [`PL011State`]. We also expect the device is
 /// readable/writeable from one thread at any time.
 /// readable/writeable from one thread at any time.
-#[no_mangle]
 pub unsafe extern "C" fn pl011_realize(dev: *mut DeviceState, _errp: *mut *mut Error) {
 pub unsafe extern "C" fn pl011_realize(dev: *mut DeviceState, _errp: *mut *mut Error) {
     unsafe {
     unsafe {
         assert!(!dev.is_null());
         assert!(!dev.is_null());
@@ -60,7 +119,6 @@
 /// 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 has
 /// the same size as [`PL011State`]. We also expect the device is
 /// the same size as [`PL011State`]. We also expect the device is
 /// readable/writeable from one thread at any time.
 /// readable/writeable from one thread at any time.
-#[no_mangle]
 pub unsafe extern "C" fn pl011_reset(dev: *mut DeviceState) {
 pub unsafe extern "C" fn pl011_reset(dev: *mut DeviceState) {
     unsafe {
     unsafe {
         assert!(!dev.is_null());
         assert!(!dev.is_null());

+ 5 - 1
rust/hw/char/pl011/src/lib.rs

@@ -36,16 +36,20 @@
     clippy::cognitive_complexity,
     clippy::cognitive_complexity,
     clippy::missing_safety_doc,
     clippy::missing_safety_doc,
     )]
     )]
+#![allow(clippy::result_unit_err)]
 
 
 extern crate bilge;
 extern crate bilge;
 extern crate bilge_impl;
 extern crate bilge_impl;
 extern crate qemu_api;
 extern crate qemu_api;
 
 
+use qemu_api::c_str;
+
 pub mod device;
 pub mod device;
 pub mod device_class;
 pub mod device_class;
 pub mod memory_ops;
 pub mod memory_ops;
 
 
-pub const TYPE_PL011: &::core::ffi::CStr = c"pl011";
+pub const TYPE_PL011: &::std::ffi::CStr = c_str!("pl011");
+pub const TYPE_PL011_LUMINARY: &::std::ffi::CStr = c_str!("pl011_luminary");
 
 
 /// Offset of each register from the base memory address of the device.
 /// Offset of each register from the base memory address of the device.
 ///
 ///

+ 7 - 17
rust/hw/char/pl011/src/memory_ops.rs

@@ -2,9 +2,10 @@
 // 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::{mem::MaybeUninit, ptr::NonNull};
+use core::ptr::NonNull;
+use std::os::raw::{c_uint, c_void};
 
 
-use qemu_api::bindings::*;
+use qemu_api::{bindings::*, zeroable::Zeroable};
 
 
 use crate::device::PL011State;
 use crate::device::PL011State;
 
 
@@ -14,20 +15,15 @@
     read_with_attrs: None,
     read_with_attrs: None,
     write_with_attrs: None,
     write_with_attrs: None,
     endianness: device_endian::DEVICE_NATIVE_ENDIAN,
     endianness: device_endian::DEVICE_NATIVE_ENDIAN,
-    valid: unsafe { MaybeUninit::<MemoryRegionOps__bindgen_ty_1>::zeroed().assume_init() },
+    valid: Zeroable::ZERO,
     impl_: MemoryRegionOps__bindgen_ty_2 {
     impl_: MemoryRegionOps__bindgen_ty_2 {
         min_access_size: 4,
         min_access_size: 4,
         max_access_size: 4,
         max_access_size: 4,
-        ..unsafe { MaybeUninit::<MemoryRegionOps__bindgen_ty_2>::zeroed().assume_init() }
+        ..Zeroable::ZERO
     },
     },
 };
 };
 
 
-#[no_mangle]
-unsafe extern "C" fn pl011_read(
-    opaque: *mut core::ffi::c_void,
-    addr: hwaddr,
-    size: core::ffi::c_uint,
-) -> u64 {
+unsafe extern "C" fn pl011_read(opaque: *mut c_void, addr: hwaddr, size: c_uint) -> u64 {
     assert!(!opaque.is_null());
     assert!(!opaque.is_null());
     let mut state = unsafe { NonNull::new_unchecked(opaque.cast::<PL011State>()) };
     let mut state = unsafe { NonNull::new_unchecked(opaque.cast::<PL011State>()) };
     let val = unsafe { state.as_mut().read(addr, size) };
     let val = unsafe { state.as_mut().read(addr, size) };
@@ -44,13 +40,7 @@
     }
     }
 }
 }
 
 
-#[no_mangle]
-unsafe extern "C" fn pl011_write(
-    opaque: *mut core::ffi::c_void,
-    addr: hwaddr,
-    data: u64,
-    _size: core::ffi::c_uint,
-) {
+unsafe extern "C" fn pl011_write(opaque: *mut c_void, addr: hwaddr, data: u64, _size: c_uint) {
     unsafe {
     unsafe {
         assert!(!opaque.is_null());
         assert!(!opaque.is_null());
         let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
         let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());

+ 0 - 47
rust/qemu-api-macros/Cargo.lock

@@ -1,47 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.86"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "qemu_api_macros"
-version = "0.1.0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.36"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "syn"
-version = "2.0.72"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

+ 1 - 4
rust/qemu-api-macros/Cargo.toml

@@ -19,7 +19,4 @@ proc-macro = true
 [dependencies]
 [dependencies]
 proc-macro2 = "1"
 proc-macro2 = "1"
 quote = "1"
 quote = "1"
-syn = "2"
-
-# Do not include in any global workspace
-[workspace]
+syn = { version = "2", features = ["extra-traits"] }

+ 1 - 1
rust/qemu-api-macros/meson.build

@@ -2,7 +2,7 @@ quote_dep = dependency('quote-1-rs', native: true)
 syn_dep = dependency('syn-2-rs', native: true)
 syn_dep = dependency('syn-2-rs', native: true)
 proc_macro2_dep = dependency('proc-macro2-1-rs', native: true)
 proc_macro2_dep = dependency('proc-macro2-1-rs', native: true)
 
 
-_qemu_api_macros_rs = import('rust').proc_macro(
+_qemu_api_macros_rs = rust.proc_macro(
   'qemu_api_macros',
   'qemu_api_macros',
   files('src/lib.rs'),
   files('src/lib.rs'),
   override_options: ['rust_std=2021', 'build.rust_std=2021'],
   override_options: ['rust_std=2021', 'build.rust_std=2021'],

+ 76 - 25
rust/qemu-api-macros/src/lib.rs

@@ -3,41 +3,92 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 
 use proc_macro::TokenStream;
 use proc_macro::TokenStream;
-use quote::{format_ident, quote};
-use syn::{parse_macro_input, DeriveInput};
+use proc_macro2::Span;
+use quote::{quote, quote_spanned};
+use syn::{
+    parse_macro_input, parse_quote, punctuated::Punctuated, token::Comma, Data, DeriveInput, Field,
+    Fields, Ident, Type, Visibility,
+};
+
+struct CompileError(String, Span);
+
+impl From<CompileError> for proc_macro2::TokenStream {
+    fn from(err: CompileError) -> Self {
+        let CompileError(msg, span) = err;
+        quote_spanned! { span => compile_error!(#msg); }
+    }
+}
+
+fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), CompileError> {
+    let expected = parse_quote! { #[repr(C)] };
+
+    if input.attrs.iter().any(|attr| attr == &expected) {
+        Ok(())
+    } else {
+        Err(CompileError(
+            format!("#[repr(C)] required for {}", msg),
+            input.ident.span(),
+        ))
+    }
+}
 
 
 #[proc_macro_derive(Object)]
 #[proc_macro_derive(Object)]
 pub fn derive_object(input: TokenStream) -> TokenStream {
 pub fn derive_object(input: TokenStream) -> TokenStream {
     let input = parse_macro_input!(input as DeriveInput);
     let input = parse_macro_input!(input as DeriveInput);
-
     let name = input.ident;
     let name = input.ident;
-    let module_static = format_ident!("__{}_LOAD_MODULE", name);
 
 
     let expanded = quote! {
     let expanded = quote! {
-        #[allow(non_upper_case_globals)]
-        #[used]
-        #[cfg_attr(target_os = "linux", link_section = ".ctors")]
-        #[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
-        #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
-        pub static #module_static: extern "C" fn() = {
-            extern "C" fn __register() {
-                unsafe {
-                    ::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::definitions::ObjectImpl>::TYPE_INFO);
-                }
+        ::qemu_api::module_init! {
+            MODULE_INIT_QOM => unsafe {
+                ::qemu_api::bindings::type_register_static(&<#name as ::qemu_api::definitions::ObjectImpl>::TYPE_INFO);
             }
             }
+        }
+    };
 
 
-            extern "C" fn __load() {
-                unsafe {
-                    ::qemu_api::bindings::register_module_init(
-                        Some(__register),
-                        ::qemu_api::bindings::module_init_type::MODULE_INIT_QOM
-                    );
-                }
-            }
+    TokenStream::from(expanded)
+}
 
 
-            __load
-        };
-    };
+fn get_fields(input: &DeriveInput) -> Result<&Punctuated<Field, Comma>, CompileError> {
+    if let Data::Struct(s) = &input.data {
+        if let Fields::Named(fs) = &s.fields {
+            Ok(&fs.named)
+        } else {
+            Err(CompileError(
+                "Cannot generate offsets for unnamed fields.".to_string(),
+                input.ident.span(),
+            ))
+        }
+    } else {
+        Err(CompileError(
+            "Cannot generate offsets for union or enum.".to_string(),
+            input.ident.span(),
+        ))
+    }
+}
+
+#[rustfmt::skip::macros(quote)]
+fn derive_offsets_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, CompileError> {
+    is_c_repr(&input, "#[derive(offsets)]")?;
+
+    let name = &input.ident;
+    let fields = get_fields(&input)?;
+    let field_names: Vec<&Ident> = fields.iter().map(|f| f.ident.as_ref().unwrap()).collect();
+    let field_types: Vec<&Type> = fields.iter().map(|f| &f.ty).collect();
+    let field_vis: Vec<&Visibility> = fields.iter().map(|f| &f.vis).collect();
+
+    Ok(quote! {
+	::qemu_api::with_offsets! {
+	    struct #name {
+		#(#field_vis #field_names: #field_types,)*
+	    }
+	}
+    })
+}
+
+#[proc_macro_derive(offsets)]
+pub fn derive_offsets(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    let expanded = derive_offsets_or_error(input).unwrap_or_else(Into::into);
 
 
     TokenStream::from(expanded)
     TokenStream::from(expanded)
 }
 }

+ 0 - 7
rust/qemu-api/Cargo.lock

@@ -1,7 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "qemu_api"
-version = "0.1.0"

+ 6 - 4
rust/qemu-api/Cargo.toml

@@ -14,13 +14,15 @@ keywords = []
 categories = []
 categories = []
 
 
 [dependencies]
 [dependencies]
+qemu_api_macros = { path = "../qemu-api-macros" }
+
+[build-dependencies]
+version_check = "~0.9"
 
 
 [features]
 [features]
 default = []
 default = []
 allocator = []
 allocator = []
 
 
-# Do not include in any global workspace
-[workspace]
-
 [lints.rust]
 [lints.rust]
-unexpected_cfgs = { level = "warn", check-cfg = ['cfg(MESON)', 'cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)'] }
+unexpected_cfgs = { level = "warn", check-cfg = ['cfg(MESON)', 'cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)',
+                                                 'cfg(has_offset_of)'] }

+ 9 - 0
rust/qemu-api/build.rs

@@ -4,6 +4,8 @@
 
 
 use std::path::Path;
 use std::path::Path;
 
 
+use version_check as rustc;
+
 fn main() {
 fn main() {
     if !Path::new("src/bindings.rs").exists() {
     if !Path::new("src/bindings.rs").exists() {
         panic!(
         panic!(
@@ -11,4 +13,11 @@ fn main() {
              (`ninja bindings.rs`) and copy them to src/bindings.rs, or build through meson."
              (`ninja bindings.rs`) and copy them to src/bindings.rs, or build through meson."
         );
         );
     }
     }
+
+    // Check for available rustc features
+    if rustc::is_min_version("1.77.0").unwrap_or(false) {
+        println!("cargo:rustc-cfg=has_offset_of");
+    }
+
+    println!("cargo:rerun-if-changed=build.rs");
 }
 }

+ 37 - 7
rust/qemu-api/meson.build

@@ -1,24 +1,54 @@
+_qemu_api_cfg = ['--cfg', 'MESON']
+# _qemu_api_cfg += ['--cfg', 'feature="allocator"']
+if rustc.version().version_compare('>=1.77.0')
+  _qemu_api_cfg += ['--cfg', 'has_offset_of']
+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/c_str.rs',
       'src/definitions.rs',
       'src/definitions.rs',
       'src/device_class.rs',
       'src/device_class.rs',
+      'src/offset_of.rs',
+      'src/vmstate.rs',
+      'src/zeroable.rs',
     ],
     ],
     {'.' : bindings_rs},
     {'.' : bindings_rs},
   ),
   ),
   override_options: ['rust_std=2021', 'build.rust_std=2021'],
   override_options: ['rust_std=2021', 'build.rust_std=2021'],
   rust_abi: 'rust',
   rust_abi: 'rust',
-  rust_args: rustc_args + [
-    '--cfg', 'MESON',
-    # '--cfg', 'feature="allocator"',
-  ],
-  dependencies: [
-    qemu_api_macros,
-  ],
+  rust_args: _qemu_api_cfg,
 )
 )
 
 
+rust.test('rust-qemu-api-tests', _qemu_api_rs,
+          suite: ['unit', 'rust'])
+
 qemu_api = declare_dependency(
 qemu_api = declare_dependency(
   link_with: _qemu_api_rs,
   link_with: _qemu_api_rs,
+  dependencies: qemu_api_macros,
 )
 )
+
+# Rust executables do not support objects, so add an intermediate step.
+rust_qemu_api_objs = static_library(
+    'rust_qemu_api_objs',
+    objects: [libqom.extract_all_objects(recursive: false),
+              libhwcore.extract_all_objects(recursive: false)])
+
+test('rust-qemu-api-integration',
+    executable(
+        'rust-qemu-api-integration',
+        'tests/tests.rs',
+        override_options: ['rust_std=2021', 'build.rust_std=2021'],
+        rust_args: ['--test'],
+        install: false,
+        dependencies: [qemu_api, qemu_api_macros],
+        link_whole: [rust_qemu_api_objs, libqemuutil]),
+    args: [
+        '--test',
+        '--format', 'pretty',
+    ],
+    protocol: 'rust',
+    suite: ['unit', 'rust'])

+ 53 - 0
rust/qemu-api/src/c_str.rs

@@ -0,0 +1,53 @@
+// Copyright 2024 Red Hat, Inc.
+// Author(s): Paolo Bonzini <pbonzini@redhat.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#[macro_export]
+/// Given a string constant _without_ embedded or trailing NULs, return
+/// a `CStr`.
+///
+/// Needed for compatibility with Rust <1.77.
+macro_rules! c_str {
+    ($str:expr) => {{
+        const STRING: &str = concat!($str, "\0");
+        const BYTES: &[u8] = STRING.as_bytes();
+
+        // "for" is not allowed in const context... oh well,
+        // everybody loves some lisp.  This could be turned into
+        // a procedural macro if this is a problem; alternatively
+        // Rust 1.72 makes CStr::from_bytes_with_nul a const function.
+        const fn f(b: &[u8], i: usize) {
+            if i == b.len() - 1 {
+            } else if b[i] == 0 {
+                panic!("c_str argument contains NUL")
+            } else {
+                f(b, i + 1)
+            }
+        }
+        f(BYTES, 0);
+
+        // SAFETY: absence of NULs apart from the final byte was checked above
+        unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(BYTES) }
+    }};
+}
+
+#[cfg(test)]
+mod tests {
+    use std::ffi::CStr;
+
+    use crate::c_str;
+
+    #[test]
+    fn test_cstr_macro() {
+        let good = c_str!("🦀");
+        let good_bytes = b"\xf0\x9f\xa6\x80\0";
+        assert_eq!(good.to_bytes_with_nul(), good_bytes);
+    }
+
+    #[test]
+    fn test_cstr_macro_const() {
+        const GOOD: &CStr = c_str!("🦀");
+        const GOOD_BYTES: &[u8] = b"\xf0\x9f\xa6\x80\0";
+        assert_eq!(GOOD.to_bytes_with_nul(), GOOD_BYTES);
+    }
+}

+ 31 - 37
rust/qemu-api/src/definitions.rs

@@ -4,7 +4,7 @@
 
 
 //! Definitions required by QEMU when registering a device.
 //! Definitions required by QEMU when registering a device.
 
 
-use ::core::ffi::{c_void, CStr};
+use std::{ffi::CStr, os::raw::c_void};
 
 
 use crate::bindings::{Object, ObjectClass, TypeInfo};
 use crate::bindings::{Object, ObjectClass, TypeInfo};
 
 
@@ -29,46 +29,40 @@ pub trait Class {
 
 
 #[macro_export]
 #[macro_export]
 macro_rules! module_init {
 macro_rules! module_init {
-    ($func:expr, $type:expr) => {
-        #[used]
-        #[cfg_attr(target_os = "linux", link_section = ".ctors")]
-        #[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
-        #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
-        pub static LOAD_MODULE: extern "C" fn() = {
-            extern "C" fn __load() {
-                unsafe {
-                    $crate::bindings::register_module_init(Some($func), $type);
-                }
-            }
-
-            __load
-        };
-    };
-    (qom: $func:ident => $body:block) => {
-        // NOTE: To have custom identifiers for the ctor func we need to either supply
-        // them directly as a macro argument or create them with a proc macro.
-        #[used]
-        #[cfg_attr(target_os = "linux", link_section = ".ctors")]
-        #[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
-        #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
-        pub static LOAD_MODULE: extern "C" fn() = {
-            extern "C" fn __load() {
-                #[no_mangle]
-                unsafe extern "C" fn $func() {
+    ($type:ident => $body:block) => {
+        const _: () = {
+            #[used]
+            #[cfg_attr(
+                not(any(target_vendor = "apple", target_os = "windows")),
+                link_section = ".init_array"
+            )]
+            #[cfg_attr(target_vendor = "apple", link_section = "__DATA,__mod_init_func")]
+            #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
+            pub static LOAD_MODULE: extern "C" fn() = {
+                extern "C" fn init_fn() {
                     $body
                     $body
                 }
                 }
 
 
-                unsafe {
-                    $crate::bindings::register_module_init(
-                        Some($func),
-                        $crate::bindings::module_init_type::MODULE_INIT_QOM,
-                    );
+                extern "C" fn ctor_fn() {
+                    unsafe {
+                        $crate::bindings::register_module_init(
+                            Some(init_fn),
+                            $crate::bindings::module_init_type::$type,
+                        );
+                    }
                 }
                 }
-            }
 
 
-            __load
+                ctor_fn
+            };
         };
         };
     };
     };
+
+    // shortcut because it's quite common that $body needs unsafe {}
+    ($type:ident => unsafe $body:block) => {
+        $crate::module_init! {
+            $type => { unsafe { $body } }
+        }
+    };
 }
 }
 
 
 #[macro_export]
 #[macro_export]
@@ -81,13 +75,13 @@ macro_rules! type_info {
             } else {
             } else {
                 ::core::ptr::null_mut()
                 ::core::ptr::null_mut()
             },
             },
-            instance_size: ::core::mem::size_of::<$t>() as $crate::bindings::size_t,
-            instance_align: ::core::mem::align_of::<$t>() as $crate::bindings::size_t,
+            instance_size: ::core::mem::size_of::<$t>(),
+            instance_align: ::core::mem::align_of::<$t>(),
             instance_init: <$t as $crate::definitions::ObjectImpl>::INSTANCE_INIT,
             instance_init: <$t as $crate::definitions::ObjectImpl>::INSTANCE_INIT,
             instance_post_init: <$t as $crate::definitions::ObjectImpl>::INSTANCE_POST_INIT,
             instance_post_init: <$t as $crate::definitions::ObjectImpl>::INSTANCE_POST_INIT,
             instance_finalize: <$t as $crate::definitions::ObjectImpl>::INSTANCE_FINALIZE,
             instance_finalize: <$t as $crate::definitions::ObjectImpl>::INSTANCE_FINALIZE,
             abstract_: <$t as $crate::definitions::ObjectImpl>::ABSTRACT,
             abstract_: <$t as $crate::definitions::ObjectImpl>::ABSTRACT,
-            class_size:  ::core::mem::size_of::<<$t as $crate::definitions::ObjectImpl>::Class>() as $crate::bindings::size_t,
+            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_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_base_init: <<$t as $crate::definitions::ObjectImpl>::Class as $crate::definitions::Class>::CLASS_BASE_INIT,
             class_data: ::core::ptr::null_mut(),
             class_data: ::core::ptr::null_mut(),

+ 30 - 84
rust/qemu-api/src/device_class.rs

@@ -2,127 +2,73 @@
 // 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::sync::OnceLock;
+use std::ffi::CStr;
 
 
-use crate::bindings::Property;
+use crate::bindings;
 
 
 #[macro_export]
 #[macro_export]
 macro_rules! device_class_init {
 macro_rules! device_class_init {
     ($func:ident, props => $props:ident, realize_fn => $realize_fn:expr, legacy_reset_fn => $legacy_reset_fn:expr, vmsd => $vmsd:ident$(,)*) => {
     ($func:ident, props => $props:ident, realize_fn => $realize_fn:expr, legacy_reset_fn => $legacy_reset_fn:expr, vmsd => $vmsd:ident$(,)*) => {
-        #[no_mangle]
         pub unsafe extern "C" fn $func(
         pub unsafe extern "C" fn $func(
             klass: *mut $crate::bindings::ObjectClass,
             klass: *mut $crate::bindings::ObjectClass,
-            _: *mut ::core::ffi::c_void,
+            _: *mut ::std::os::raw::c_void,
         ) {
         ) {
             let mut dc =
             let mut dc =
                 ::core::ptr::NonNull::new(klass.cast::<$crate::bindings::DeviceClass>()).unwrap();
                 ::core::ptr::NonNull::new(klass.cast::<$crate::bindings::DeviceClass>()).unwrap();
-            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_mut_ptr());
+            unsafe {
+                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:expr, $prop:expr, $type:expr, default = $defval:expr$(,)*) => {
+    ($name:expr, $state:ty, $field:ident, $prop:expr, $type:expr, default = $defval:expr$(,)*) => {
         $crate::bindings::Property {
         $crate::bindings::Property {
-            name: {
-                #[used]
-                static _TEMP: &::core::ffi::CStr = $name;
-                _TEMP.as_ptr()
-            },
+            // use associated function syntax for type checking
+            name: ::std::ffi::CStr::as_ptr($name),
             info: $prop,
             info: $prop,
-            offset: ::core::mem::offset_of!($state, $field)
-                .try_into()
-                .expect("Could not fit offset value to type"),
-            bitnr: 0,
-            bitmask: 0,
+            offset: $crate::offset_of!($state, $field) as isize,
             set_default: true,
             set_default: true,
-            defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval.into() },
-            arrayoffset: 0,
-            arrayinfo: ::core::ptr::null(),
-            arrayfieldsize: 0,
-            link_type: ::core::ptr::null(),
+            defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 },
+            ..$crate::zeroable::Zeroable::ZERO
         }
         }
     };
     };
-    ($name:expr, $state:ty, $field:expr, $prop:expr, $type:expr$(,)*) => {
+    ($name:expr, $state:ty, $field:ident, $prop:expr, $type:expr$(,)*) => {
         $crate::bindings::Property {
         $crate::bindings::Property {
-            name: {
-                #[used]
-                static _TEMP: &::core::ffi::CStr = $name;
-                _TEMP.as_ptr()
-            },
+            // use associated function syntax for type checking
+            name: ::std::ffi::CStr::as_ptr($name),
             info: $prop,
             info: $prop,
-            offset: ::core::mem::offset_of!($state, $field)
-                .try_into()
-                .expect("Could not fit offset value to type"),
-            bitnr: 0,
-            bitmask: 0,
+            offset: $crate::offset_of!($state, $field) as isize,
             set_default: false,
             set_default: false,
-            defval: $crate::bindings::Property__bindgen_ty_1 { i: 0 },
-            arrayoffset: 0,
-            arrayinfo: ::core::ptr::null(),
-            arrayfieldsize: 0,
-            link_type: ::core::ptr::null(),
+            ..$crate::zeroable::Zeroable::ZERO
         }
         }
     };
     };
 }
 }
 
 
-#[repr(C)]
-pub struct Properties<const N: usize>(pub OnceLock<[Property; N]>, pub fn() -> [Property; N]);
-
-impl<const N: usize> Properties<N> {
-    pub fn as_mut_ptr(&mut self) -> *mut Property {
-        _ = self.0.get_or_init(self.1);
-        self.0.get_mut().unwrap().as_mut_ptr()
-    }
-}
-
 #[macro_export]
 #[macro_export]
 macro_rules! declare_properties {
 macro_rules! declare_properties {
     ($ident:ident, $($prop:expr),*$(,)*) => {
     ($ident:ident, $($prop:expr),*$(,)*) => {
-
-        const fn _calc_prop_len() -> usize {
+        pub static $ident: [$crate::bindings::Property; {
             let mut len = 1;
             let mut len = 1;
             $({
             $({
                 _ = stringify!($prop);
                 _ = stringify!($prop);
                 len += 1;
                 len += 1;
             })*
             })*
             len
             len
-        }
-        const PROP_LEN: usize = _calc_prop_len();
-
-        fn _make_properties() -> [$crate::bindings::Property; PROP_LEN] {
-            [
-                $($prop),*,
-                    unsafe { ::core::mem::MaybeUninit::<$crate::bindings::Property>::zeroed().assume_init() },
-            ]
-        }
-
-        #[no_mangle]
-        pub static mut $ident: $crate::device_class::Properties<PROP_LEN> = $crate::device_class::Properties(::std::sync::OnceLock::new(), _make_properties);
+        }] = [
+            $($prop),*,
+            $crate::zeroable::Zeroable::ZERO,
+        ];
     };
     };
 }
 }
 
 
-#[macro_export]
-macro_rules! vm_state_description {
-    ($(#[$outer:meta])*
-     $name:ident,
-     $(name: $vname:expr,)*
-     $(unmigratable: $um_val:expr,)*
-    ) => {
-        #[used]
-        $(#[$outer])*
-        pub static $name: $crate::bindings::VMStateDescription = $crate::bindings::VMStateDescription {
-            $(name: {
-                #[used]
-                static VMSTATE_NAME: &::core::ffi::CStr = $vname;
-                $vname.as_ptr()
-            },)*
-            unmigratable: true,
-            ..unsafe { ::core::mem::MaybeUninit::<$crate::bindings::VMStateDescription>::zeroed().assume_init() }
-        };
-    }
-}
+// workaround until we can use --generate-cstr in bindgen.
+pub const TYPE_DEVICE: &CStr =
+    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) };

+ 16 - 7
rust/qemu-api/src/lib.rs

@@ -26,14 +26,20 @@ unsafe impl Send for bindings::Property {}
 unsafe impl Sync for bindings::Property {}
 unsafe impl Sync for bindings::Property {}
 unsafe impl Sync for bindings::TypeInfo {}
 unsafe impl Sync for bindings::TypeInfo {}
 unsafe impl Sync for bindings::VMStateDescription {}
 unsafe impl Sync for bindings::VMStateDescription {}
+unsafe impl Sync for bindings::VMStateField {}
+unsafe impl Sync for bindings::VMStateInfo {}
 
 
+pub mod c_str;
 pub mod definitions;
 pub mod definitions;
 pub mod device_class;
 pub mod device_class;
+pub mod offset_of;
+pub mod vmstate;
+pub mod zeroable;
 
 
-#[cfg(test)]
-mod tests;
-
-use std::alloc::{GlobalAlloc, Layout};
+use std::{
+    alloc::{GlobalAlloc, Layout},
+    os::raw::c_void,
+};
 
 
 #[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)]
 #[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)]
 extern "C" {
 extern "C" {
@@ -47,8 +53,8 @@ fn g_aligned_alloc0(
 
 
 #[cfg(not(HAVE_GLIB_WITH_ALIGNED_ALLOC))]
 #[cfg(not(HAVE_GLIB_WITH_ALIGNED_ALLOC))]
 extern "C" {
 extern "C" {
-    fn qemu_memalign(alignment: usize, size: usize) -> *mut ::core::ffi::c_void;
-    fn qemu_vfree(ptr: *mut ::core::ffi::c_void);
+    fn qemu_memalign(alignment: usize, size: usize) -> *mut c_void;
+    fn qemu_vfree(ptr: *mut c_void);
 }
 }
 
 
 extern "C" {
 extern "C" {
@@ -113,7 +119,7 @@ fn default() -> Self {
 }
 }
 
 
 // Sanity check.
 // Sanity check.
-const _: [(); 8] = [(); ::core::mem::size_of::<*mut ::core::ffi::c_void>()];
+const _: [(); 8] = [(); ::core::mem::size_of::<*mut c_void>()];
 
 
 unsafe impl GlobalAlloc for QemuAllocator {
 unsafe impl GlobalAlloc for QemuAllocator {
     unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
     unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
@@ -164,3 +170,6 @@ unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
         }
         }
     }
     }
 }
 }
+
+#[cfg(has_offset_of)]
+pub use core::mem::offset_of;

+ 161 - 0
rust/qemu-api/src/offset_of.rs

@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: MIT
+
+/// This macro provides the same functionality as `core::mem::offset_of`,
+/// except that only one level of field access is supported.  The declaration
+/// of the struct must be wrapped with `with_offsets! { }`.
+///
+/// It is needed because `offset_of!` was only stabilized in Rust 1.77.
+#[cfg(not(has_offset_of))]
+#[macro_export]
+macro_rules! offset_of {
+    ($Container:ty, $field:ident) => {
+        <$Container>::OFFSET_TO__.$field
+    };
+}
+
+/// A wrapper for struct declarations, that allows using `offset_of!` in
+/// versions of Rust prior to 1.77
+#[macro_export]
+macro_rules! with_offsets {
+    // This method to generate field offset constants comes from:
+    //
+    //     https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=10a22a9b8393abd7b541d8fc844bc0df
+    //
+    // used under MIT license with permission of Yandros aka Daniel Henry-Mantilla
+    (
+        $(#[$struct_meta:meta])*
+        $struct_vis:vis
+        struct $StructName:ident {
+            $(
+                $(#[$field_meta:meta])*
+                $field_vis:vis
+                $field_name:ident : $field_ty:ty
+            ),*
+            $(,)?
+        }
+    ) => (
+        #[cfg(not(has_offset_of))]
+        const _: () = {
+            struct StructOffsetsHelper<T>(std::marker::PhantomData<T>);
+            const END_OF_PREV_FIELD: usize = 0;
+
+            // populate StructOffsetsHelper<T> with associated consts,
+            // one for each field
+            $crate::with_offsets! {
+                @struct $StructName
+                @names [ $($field_name)* ]
+                @tys [ $($field_ty ,)*]
+            }
+
+            // now turn StructOffsetsHelper<T>'s consts into a single struct,
+            // applying field visibility.  This provides better error messages
+            // than if offset_of! used StructOffsetsHelper::<T> directly.
+            pub
+            struct StructOffsets {
+                $(
+                    $field_vis
+                    $field_name: usize,
+                )*
+            }
+            impl $StructName {
+                pub
+                const OFFSET_TO__: StructOffsets = StructOffsets {
+                    $(
+                        $field_name: StructOffsetsHelper::<$StructName>::$field_name,
+                    )*
+                };
+            }
+        };
+    );
+
+    (
+        @struct $StructName:ident
+        @names []
+        @tys []
+    ) => ();
+
+    (
+        @struct $StructName:ident
+        @names [$field_name:ident $($other_names:tt)*]
+        @tys [$field_ty:ty , $($other_tys:tt)*]
+    ) => (
+        #[allow(non_local_definitions)]
+        #[allow(clippy::modulo_one)]
+        impl StructOffsetsHelper<$StructName> {
+            #[allow(nonstandard_style)]
+            const $field_name: usize = {
+                const ALIGN: usize = std::mem::align_of::<$field_ty>();
+                const TRAIL: usize = END_OF_PREV_FIELD % ALIGN;
+                END_OF_PREV_FIELD + (if TRAIL == 0 { 0usize } else { ALIGN - TRAIL })
+            };
+        }
+        const _: () = {
+            const END_OF_PREV_FIELD: usize =
+                StructOffsetsHelper::<$StructName>::$field_name +
+                std::mem::size_of::<$field_ty>()
+            ;
+            $crate::with_offsets! {
+                @struct $StructName
+                @names [$($other_names)*]
+                @tys [$($other_tys)*]
+            }
+        };
+    );
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::offset_of;
+
+    #[repr(C)]
+    struct Foo {
+        a: u16,
+        b: u32,
+        c: u64,
+        d: u16,
+    }
+
+    #[repr(C)]
+    struct Bar {
+        pub a: u16,
+        pub b: u64,
+        c: Foo,
+        d: u64,
+    }
+
+    crate::with_offsets! {
+        #[repr(C)]
+        struct Bar {
+            pub a: u16,
+            pub b: u64,
+            c: Foo,
+            d: u64,
+        }
+    }
+
+    #[repr(C)]
+    pub struct Baz {
+        b: u32,
+        a: u8,
+    }
+    crate::with_offsets! {
+        #[repr(C)]
+        pub struct Baz {
+            b: u32,
+            a: u8,
+        }
+    }
+
+    #[test]
+    fn test_offset_of() {
+        const OFFSET_TO_C: usize = offset_of!(Bar, c);
+
+        assert_eq!(offset_of!(Bar, a), 0);
+        assert_eq!(offset_of!(Bar, b), 8);
+        assert_eq!(OFFSET_TO_C, 16);
+        assert_eq!(offset_of!(Bar, d), 40);
+
+        assert_eq!(offset_of!(Baz, b), 0);
+        assert_eq!(offset_of!(Baz, a), 4);
+    }
+}

+ 0 - 49
rust/qemu-api/src/tests.rs

@@ -1,49 +0,0 @@
-// Copyright 2024, Linaro Limited
-// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-use crate::{
-    bindings::*, declare_properties, define_property, device_class_init, vm_state_description,
-};
-
-#[test]
-fn test_device_decl_macros() {
-    // Test that macros can compile.
-    vm_state_description! {
-        VMSTATE,
-        name: c"name",
-        unmigratable: true,
-    }
-
-    #[repr(C)]
-    pub struct DummyState {
-        pub char_backend: CharBackend,
-        pub migrate_clock: bool,
-    }
-
-    declare_properties! {
-        DUMMY_PROPERTIES,
-            define_property!(
-                c"chardev",
-                DummyState,
-                char_backend,
-                unsafe { &qdev_prop_chr },
-                CharBackend
-            ),
-            define_property!(
-                c"migrate-clk",
-                DummyState,
-                migrate_clock,
-                unsafe { &qdev_prop_bool },
-                bool
-            ),
-    }
-
-    device_class_init! {
-        dummy_class_init,
-        props => DUMMY_PROPERTIES,
-        realize_fn => None,
-        reset_fn => None,
-        vmsd => VMSTATE,
-    }
-}

+ 360 - 0
rust/qemu-api/src/vmstate.rs

@@ -0,0 +1,360 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Helper macros to declare migration state for device models.
+//!
+//! Some macros are direct equivalents to the C macros declared in
+//! `include/migration/vmstate.h` while
+//! [`vmstate_subsections`](crate::vmstate_subsections) and
+//! [`vmstate_fields`](crate::vmstate_fields) are meant to be used when
+//! declaring a device model state struct.
+
+#[doc(alias = "VMSTATE_UNUSED_BUFFER")]
+#[macro_export]
+macro_rules! vmstate_unused_buffer {
+    ($field_exists_fn:expr, $version_id:expr, $size:expr) => {{
+        $crate::bindings::VMStateField {
+            name: c_str!("unused").as_ptr(),
+            err_hint: ::core::ptr::null(),
+            offset: 0,
+            size: $size,
+            start: 0,
+            num: 0,
+            num_offset: 0,
+            size_offset: 0,
+            info: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_info_unused_buffer) },
+            flags: VMStateFlags::VMS_BUFFER,
+            vmsd: ::core::ptr::null(),
+            version_id: $version_id,
+            struct_version_id: 0,
+            field_exists: $field_exists_fn,
+        }
+    }};
+}
+
+#[doc(alias = "VMSTATE_UNUSED_V")]
+#[macro_export]
+macro_rules! vmstate_unused_v {
+    ($version_id:expr, $size:expr) => {{
+        $crate::vmstate_unused_buffer!(None, $version_id, $size)
+    }};
+}
+
+#[doc(alias = "VMSTATE_UNUSED")]
+#[macro_export]
+macro_rules! vmstate_unused {
+    ($size:expr) => {{
+        $crate::vmstate_unused_v!(0, $size)
+    }};
+}
+
+#[doc(alias = "VMSTATE_SINGLE_TEST")]
+#[macro_export]
+macro_rules! vmstate_single_test {
+    ($field_name:ident, $struct_name:ty, $field_exists_fn:expr, $version_id:expr, $info:expr, $size:expr) => {{
+        $crate::bindings::VMStateField {
+            name: ::core::concat!(::core::stringify!($field_name), 0)
+                .as_bytes()
+                .as_ptr() as *const ::std::os::raw::c_char,
+            err_hint: ::core::ptr::null(),
+            offset: $crate::offset_of!($struct_name, $field_name),
+            size: $size,
+            start: 0,
+            num: 0,
+            num_offset: 0,
+            size_offset: 0,
+            info: unsafe { $info },
+            flags: VMStateFlags::VMS_SINGLE,
+            vmsd: ::core::ptr::null(),
+            version_id: $version_id,
+            struct_version_id: 0,
+            field_exists: $field_exists_fn,
+        }
+    }};
+}
+
+#[doc(alias = "VMSTATE_SINGLE")]
+#[macro_export]
+macro_rules! vmstate_single {
+    ($field_name:ident, $struct_name:ty, $version_id:expr, $info:expr, $size:expr) => {{
+        $crate::vmstate_single_test!($field_name, $struct_name, None, $version_id, $info, $size)
+    }};
+}
+
+#[doc(alias = "VMSTATE_UINT32_V")]
+#[macro_export]
+macro_rules! vmstate_uint32_v {
+    ($field_name:ident, $struct_name:ty, $version_id:expr) => {{
+        $crate::vmstate_single!(
+            $field_name,
+            $struct_name,
+            $version_id,
+            ::core::ptr::addr_of!($crate::bindings::vmstate_info_uint32),
+            ::core::mem::size_of::<u32>()
+        )
+    }};
+}
+
+#[doc(alias = "VMSTATE_UINT32")]
+#[macro_export]
+macro_rules! vmstate_uint32 {
+    ($field_name:ident, $struct_name:ty) => {{
+        $crate::vmstate_uint32_v!($field_name, $struct_name, 0)
+    }};
+}
+
+#[doc(alias = "VMSTATE_INT32_V")]
+#[macro_export]
+macro_rules! vmstate_int32_v {
+    ($field_name:ident, $struct_name:ty, $version_id:expr) => {{
+        $crate::vmstate_single!(
+            $field_name,
+            $struct_name,
+            $version_id,
+            ::core::ptr::addr_of!($crate::bindings::vmstate_info_int32),
+            ::core::mem::size_of::<i32>()
+        )
+    }};
+}
+
+#[doc(alias = "VMSTATE_INT32")]
+#[macro_export]
+macro_rules! vmstate_int32 {
+    ($field_name:ident, $struct_name:ty) => {{
+        $crate::vmstate_int32_v!($field_name, $struct_name, 0)
+    }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY")]
+#[macro_export]
+macro_rules! vmstate_array {
+    ($field_name:ident, $struct_name:ty, $length:expr, $version_id:expr, $info:expr, $size:expr) => {{
+        $crate::bindings::VMStateField {
+            name: ::core::concat!(::core::stringify!($field_name), 0)
+                .as_bytes()
+                .as_ptr() as *const ::std::os::raw::c_char,
+            err_hint: ::core::ptr::null(),
+            offset: $crate::offset_of!($struct_name, $field_name),
+            size: $size,
+            start: 0,
+            num: $length as _,
+            num_offset: 0,
+            size_offset: 0,
+            info: unsafe { $info },
+            flags: VMStateFlags::VMS_ARRAY,
+            vmsd: ::core::ptr::null(),
+            version_id: $version_id,
+            struct_version_id: 0,
+            field_exists: None,
+        }
+    }};
+}
+
+#[doc(alias = "VMSTATE_UINT32_ARRAY_V")]
+#[macro_export]
+macro_rules! vmstate_uint32_array_v {
+    ($field_name:ident, $struct_name:ty, $length:expr, $version_id:expr) => {{
+        $crate::vmstate_array!(
+            $field_name,
+            $struct_name,
+            $length,
+            $version_id,
+            ::core::ptr::addr_of!($crate::bindings::vmstate_info_uint32),
+            ::core::mem::size_of::<u32>()
+        )
+    }};
+}
+
+#[doc(alias = "VMSTATE_UINT32_ARRAY")]
+#[macro_export]
+macro_rules! vmstate_uint32_array {
+    ($field_name:ident, $struct_name:ty, $length:expr) => {{
+        $crate::vmstate_uint32_array_v!($field_name, $struct_name, $length, 0)
+    }};
+}
+
+#[doc(alias = "VMSTATE_STRUCT_POINTER_V")]
+#[macro_export]
+macro_rules! vmstate_struct_pointer_v {
+    ($field_name:ident, $struct_name:ty, $version_id:expr, $vmsd:expr, $type:ty) => {{
+        $crate::bindings::VMStateField {
+            name: ::core::concat!(::core::stringify!($field_name), 0)
+                .as_bytes()
+                .as_ptr() as *const ::std::os::raw::c_char,
+            err_hint: ::core::ptr::null(),
+            offset: $crate::offset_of!($struct_name, $field_name),
+            size: ::core::mem::size_of::<*const $type>(),
+            start: 0,
+            num: 0,
+            num_offset: 0,
+            size_offset: 0,
+            info: ::core::ptr::null(),
+            flags: VMStateFlags(VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_POINTER.0),
+            vmsd: unsafe { $vmsd },
+            version_id: $version_id,
+            struct_version_id: 0,
+            field_exists: None,
+        }
+    }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY_OF_POINTER")]
+#[macro_export]
+macro_rules! vmstate_array_of_pointer {
+    ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr, $info:expr, $type:ty) => {{
+        $crate::bindings::VMStateField {
+            name: ::core::concat!(::core::stringify!($field_name), 0)
+                .as_bytes()
+                .as_ptr() as *const ::std::os::raw::c_char,
+            version_id: $version_id,
+            num: $num as _,
+            info: unsafe { $info },
+            size: ::core::mem::size_of::<*const $type>(),
+            flags: VMStateFlags(VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_ARRAY_OF_POINTER.0),
+            offset: $crate::offset_of!($struct_name, $field_name),
+            err_hint: ::core::ptr::null(),
+            start: 0,
+            num_offset: 0,
+            size_offset: 0,
+            vmsd: ::core::ptr::null(),
+            struct_version_id: 0,
+            field_exists: None,
+        }
+    }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY_OF_POINTER_TO_STRUCT")]
+#[macro_export]
+macro_rules! vmstate_array_of_pointer_to_struct {
+    ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr, $vmsd:expr, $type:ty) => {{
+        $crate::bindings::VMStateField {
+            name: ::core::concat!(::core::stringify!($field_name), 0)
+                .as_bytes()
+                .as_ptr() as *const ::std::os::raw::c_char,
+            version_id: $version_id,
+            num: $num as _,
+            vmsd: unsafe { $vmsd },
+            size: ::core::mem::size_of::<*const $type>(),
+            flags: VMStateFlags(
+                VMStateFlags::VMS_ARRAY.0
+                    | VMStateFlags::VMS_STRUCT.0
+                    | VMStateFlags::VMS_ARRAY_OF_POINTER.0,
+            ),
+            offset: $crate::offset_of!($struct_name, $field_name),
+            err_hint: ::core::ptr::null(),
+            start: 0,
+            num_offset: 0,
+            size_offset: 0,
+            vmsd: ::core::ptr::null(),
+            struct_version_id: 0,
+            field_exists: None,
+        }
+    }};
+}
+
+#[doc(alias = "VMSTATE_CLOCK_V")]
+#[macro_export]
+macro_rules! vmstate_clock_v {
+    ($field_name:ident, $struct_name:ty, $version_id:expr) => {{
+        $crate::vmstate_struct_pointer_v!(
+            $field_name,
+            $struct_name,
+            $version_id,
+            ::core::ptr::addr_of!($crate::bindings::vmstate_clock),
+            $crate::bindings::Clock
+        )
+    }};
+}
+
+#[doc(alias = "VMSTATE_CLOCK")]
+#[macro_export]
+macro_rules! vmstate_clock {
+    ($field_name:ident, $struct_name:ty) => {{
+        $crate::vmstate_clock_v!($field_name, $struct_name, 0)
+    }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY_CLOCK_V")]
+#[macro_export]
+macro_rules! vmstate_array_clock_v {
+    ($field_name:ident, $struct_name:ty, $num:expr, $version_id:expr) => {{
+        $crate::vmstate_array_of_pointer_to_struct!(
+            $field_name,
+            $struct_name,
+            $num,
+            $version_id,
+            ::core::ptr::addr_of!($crate::bindings::vmstate_clock),
+            $crate::bindings::Clock
+        )
+    }};
+}
+
+#[doc(alias = "VMSTATE_ARRAY_CLOCK")]
+#[macro_export]
+macro_rules! vmstate_array_clock {
+    ($field_name:ident, $struct_name:ty, $num:expr) => {{
+        $crate::vmstate_array_clock_v!($field_name, $struct_name, $name, 0)
+    }};
+}
+
+/// Helper macro to declare a list of
+/// ([`VMStateField`](`crate::bindings::VMStateField`)) into a static and return
+/// a pointer to the array of values it created.
+#[macro_export]
+macro_rules! vmstate_fields {
+    ($($field:expr),*$(,)*) => {{
+        static _FIELDS: &[$crate::bindings::VMStateField] = &[
+            $($field),*,
+            $crate::bindings::VMStateField {
+                name: ::core::ptr::null(),
+                err_hint: ::core::ptr::null(),
+                offset: 0,
+                size: 0,
+                start: 0,
+                num: 0,
+                num_offset: 0,
+                size_offset: 0,
+                info: ::core::ptr::null(),
+                flags: VMStateFlags::VMS_END,
+                vmsd: ::core::ptr::null(),
+                version_id: 0,
+                struct_version_id: 0,
+                field_exists: None,
+            }
+        ];
+        _FIELDS.as_ptr()
+    }}
+}
+
+/// A transparent wrapper type for the `subsections` field of
+/// [`VMStateDescription`](crate::bindings::VMStateDescription).
+///
+/// This is necessary to be able to declare subsection descriptions as statics,
+/// because the only way to implement `Sync` for a foreign type (and `*const`
+/// pointers are foreign types in Rust) is to create a wrapper struct and
+/// `unsafe impl Sync` for it.
+///
+/// This struct is used in the
+/// [`vm_state_subsections`](crate::vmstate_subsections) macro implementation.
+#[repr(transparent)]
+pub struct VMStateSubsectionsWrapper(pub &'static [*const crate::bindings::VMStateDescription]);
+
+unsafe impl Sync for VMStateSubsectionsWrapper {}
+
+/// Helper macro to declare a list of subsections
+/// ([`VMStateDescription`](`crate::bindings::VMStateDescription`)) into a
+/// static and return a pointer to the array of pointers it created.
+#[macro_export]
+macro_rules! vmstate_subsections {
+    ($($subsection:expr),*$(,)*) => {{
+        static _SUBSECTIONS: $crate::vmstate::VMStateSubsectionsWrapper = $crate::vmstate::VMStateSubsectionsWrapper(&[
+            $({
+                static _SUBSECTION: $crate::bindings::VMStateDescription = $subsection;
+                ::core::ptr::addr_of!(_SUBSECTION)
+            }),*,
+            ::core::ptr::null()
+        ]);
+        _SUBSECTIONS.0.as_ptr()
+    }}
+}

+ 86 - 0
rust/qemu-api/src/zeroable.rs

@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use std::ptr;
+
+/// Encapsulates the requirement that
+/// `MaybeUninit::<Self>::zeroed().assume_init()` does not cause undefined
+/// behavior.  This trait in principle could be implemented as just:
+///
+/// ```
+///     const ZERO: Self = unsafe {
+///         ::core::mem::MaybeUninit::<$crate::bindings::Property>::zeroed().assume_init()
+///     },
+/// ```
+///
+/// The need for a manual implementation is only because `zeroed()` cannot
+/// be used as a `const fn` prior to Rust 1.75.0. Once we can assume a new
+/// enough version of the compiler, we could provide a `#[derive(Zeroable)]`
+/// macro to check at compile-time that all struct fields are Zeroable, and
+/// use the above blanket implementation of the `ZERO` constant.
+///
+/// # Safety
+///
+/// Because the implementation of `ZERO` is manual, it does not make
+/// any assumption on the safety of `zeroed()`.  However, other users of the
+/// trait could use it that way.  Do not add this trait to a type unless
+/// all-zeroes is a valid value for the type.  In particular, remember that
+/// raw pointers can be zero, but references and `NonNull<T>` cannot
+pub unsafe trait Zeroable: Default {
+    const ZERO: Self;
+}
+
+unsafe impl Zeroable for crate::bindings::Property__bindgen_ty_1 {
+    const ZERO: Self = Self { i: 0 };
+}
+
+unsafe impl Zeroable for crate::bindings::Property {
+    const ZERO: Self = Self {
+        name: ptr::null(),
+        info: ptr::null(),
+        offset: 0,
+        bitnr: 0,
+        bitmask: 0,
+        set_default: false,
+        defval: Zeroable::ZERO,
+        arrayoffset: 0,
+        arrayinfo: ptr::null(),
+        arrayfieldsize: 0,
+        link_type: ptr::null(),
+    };
+}
+
+unsafe impl Zeroable for crate::bindings::VMStateDescription {
+    const ZERO: Self = Self {
+        name: ptr::null(),
+        unmigratable: false,
+        early_setup: false,
+        version_id: 0,
+        minimum_version_id: 0,
+        priority: crate::bindings::MigrationPriority::MIG_PRI_DEFAULT,
+        pre_load: None,
+        post_load: None,
+        pre_save: None,
+        post_save: None,
+        needed: None,
+        dev_unplug_pending: None,
+        fields: ptr::null(),
+        subsections: ptr::null(),
+    };
+}
+
+unsafe impl Zeroable for crate::bindings::MemoryRegionOps__bindgen_ty_1 {
+    const ZERO: Self = Self {
+        min_access_size: 0,
+        max_access_size: 0,
+        unaligned: false,
+        accepts: None,
+    };
+}
+
+unsafe impl Zeroable for crate::bindings::MemoryRegionOps__bindgen_ty_2 {
+    const ZERO: Self = Self {
+        min_access_size: 0,
+        max_access_size: 0,
+        unaligned: false,
+    };
+}

+ 79 - 0
rust/qemu-api/tests/tests.rs

@@ -0,0 +1,79 @@
+// Copyright 2024, Linaro Limited
+// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use std::{ffi::CStr, os::raw::c_void};
+
+use qemu_api::{
+    bindings::*,
+    c_str, declare_properties, define_property,
+    definitions::{Class, ObjectImpl},
+    device_class, device_class_init,
+    zeroable::Zeroable,
+};
+
+#[test]
+fn test_device_decl_macros() {
+    // Test that macros can compile.
+    pub static VMSTATE: VMStateDescription = VMStateDescription {
+        name: c_str!("name").as_ptr(),
+        unmigratable: true,
+        ..Zeroable::ZERO
+    };
+
+    #[derive(qemu_api_macros::offsets)]
+    #[repr(C)]
+    #[derive(qemu_api_macros::Object)]
+    pub struct DummyState {
+        pub _parent: DeviceState,
+        pub migrate_clock: bool,
+    }
+
+    #[repr(C)]
+    pub struct DummyClass {
+        pub _parent: DeviceClass,
+    }
+
+    declare_properties! {
+        DUMMY_PROPERTIES,
+            define_property!(
+                c_str!("migrate-clk"),
+                DummyState,
+                migrate_clock,
+                unsafe { &qdev_prop_bool },
+                bool
+            ),
+    }
+
+    device_class_init! {
+        dummy_class_init,
+        props => DUMMY_PROPERTIES,
+        realize_fn => None,
+        legacy_reset_fn => None,
+        vmsd => VMSTATE,
+    }
+
+    impl ObjectImpl for DummyState {
+        type Class = DummyClass;
+        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 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 {
+        const CLASS_INIT: Option<unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void)> =
+            Some(dummy_class_init);
+        const CLASS_BASE_INIT: Option<
+            unsafe extern "C" fn(klass: *mut ObjectClass, data: *mut c_void),
+        > = None;
+    }
+
+    unsafe {
+        module_call_init(module_init_type::MODULE_INIT_QOM);
+        object_unref(object_new(DummyState::TYPE_NAME.as_ptr()) as *mut _);
+    }
+}

+ 17 - 0
rust/wrapper.h

@@ -30,6 +30,23 @@
  * in order to generate C FFI compatible Rust bindings.
  * in order to generate C FFI compatible Rust bindings.
  */
  */
 
 
+#ifndef __CLANG_STDATOMIC_H
+#define __CLANG_STDATOMIC_H
+/*
+ * Fix potential missing stdatomic.h error in case bindgen does not insert the
+ * correct libclang header paths on its own. We do not use stdatomic.h symbols
+ * in QEMU code, so it's fine to declare dummy types instead.
+ */
+typedef enum memory_order {
+  memory_order_relaxed,
+  memory_order_consume,
+  memory_order_acquire,
+  memory_order_release,
+  memory_order_acq_rel,
+  memory_order_seq_cst,
+} memory_order;
+#endif /* __CLANG_STDATOMIC_H */
+
 #include "qemu/osdep.h"
 #include "qemu/osdep.h"
 #include "qemu/module.h"
 #include "qemu/module.h"
 #include "qemu-io.h"
 #include "qemu-io.h"

+ 0 - 1
scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml

@@ -7,7 +7,6 @@
 packages:
 packages:
   - bash
   - bash
   - bc
   - bc
-  - bindgen
   - bison
   - bison
   - bsdextrautils
   - bsdextrautils
   - bzip2
   - bzip2

+ 0 - 1
scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml

@@ -7,7 +7,6 @@
 packages:
 packages:
   - bash
   - bash
   - bc
   - bc
-  - bindgen
   - bison
   - bison
   - bsdextrautils
   - bsdextrautils
   - bzip2
   - bzip2

+ 4 - 0
scripts/meson-buildoptions.sh

@@ -47,6 +47,8 @@ meson_options_help() {
   printf "%s\n" '                           getrandom()'
   printf "%s\n" '                           getrandom()'
   printf "%s\n" '  --enable-safe-stack      SafeStack Stack Smash Protection (requires'
   printf "%s\n" '  --enable-safe-stack      SafeStack Stack Smash Protection (requires'
   printf "%s\n" '                           clang/llvm and coroutine backend ucontext)'
   printf "%s\n" '                           clang/llvm and coroutine backend ucontext)'
+  printf "%s\n" '  --enable-strict-rust-lints'
+  printf "%s\n" '                           Enable stricter set of Rust warnings'
   printf "%s\n" '  --enable-strip           Strip targets on install'
   printf "%s\n" '  --enable-strip           Strip targets on install'
   printf "%s\n" '  --enable-tcg-interpreter TCG with bytecode interpreter (slow)'
   printf "%s\n" '  --enable-tcg-interpreter TCG with bytecode interpreter (slow)'
   printf "%s\n" '  --enable-trace-backends=CHOICES'
   printf "%s\n" '  --enable-trace-backends=CHOICES'
@@ -495,6 +497,8 @@ _meson_option_parse() {
     --disable-spice-protocol) printf "%s" -Dspice_protocol=disabled ;;
     --disable-spice-protocol) printf "%s" -Dspice_protocol=disabled ;;
     --enable-stack-protector) printf "%s" -Dstack_protector=enabled ;;
     --enable-stack-protector) printf "%s" -Dstack_protector=enabled ;;
     --disable-stack-protector) printf "%s" -Dstack_protector=disabled ;;
     --disable-stack-protector) printf "%s" -Dstack_protector=disabled ;;
+    --enable-strict-rust-lints) printf "%s" -Dstrict_rust_lints=true ;;
+    --disable-strict-rust-lints) printf "%s" -Dstrict_rust_lints=false ;;
     --enable-strip) printf "%s" -Dstrip=true ;;
     --enable-strip) printf "%s" -Dstrip=true ;;
     --disable-strip) printf "%s" -Dstrip=false ;;
     --disable-strip) printf "%s" -Dstrip=false ;;
     --sysconfdir=*) quote_sh "-Dsysconfdir=$2" ;;
     --sysconfdir=*) quote_sh "-Dsysconfdir=$2" ;;

+ 1 - 0
subprojects/bilge-impl-0.2-rs.wrap

@@ -5,3 +5,4 @@ source_filename = bilge-impl-0.2.0.tar.gz
 source_hash = feb11e002038ad243af39c2068c8a72bcf147acf05025dcdb916fcc000adb2d8
 source_hash = feb11e002038ad243af39c2068c8a72bcf147acf05025dcdb916fcc000adb2d8
 #method = cargo
 #method = cargo
 patch_directory = bilge-impl-0.2-rs
 patch_directory = bilge-impl-0.2-rs
+diff_files = bilge-impl-1.63.0.patch

+ 45 - 0
subprojects/packagefiles/bilge-impl-1.63.0.patch

@@ -0,0 +1,45 @@
+--- a/src/shared/discriminant_assigner.rs
++++ b/src/shared/discriminant_assigner.rs
+@@ -26,20 +26,20 @@
+         let discriminant_expr = &discriminant.1;
+         let variant_name = &variant.ident;
+ 
+-        let Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) = discriminant_expr else {
++        if let Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) = discriminant_expr {
++            let discriminant_value: u128 = int.base10_parse().unwrap_or_else(unreachable);
++            if discriminant_value > self.max_value() {
++                abort!(variant, "Value of variant exceeds the given number of bits")
++            }
++
++            Some(discriminant_value)
++        } else {
+             abort!(
+                 discriminant_expr,
+                 "variant `{}` is not a number", variant_name;
+                 help = "only literal integers currently supported"
+             )
+-        };
+-
+-        let discriminant_value: u128 = int.base10_parse().unwrap_or_else(unreachable);
+-        if discriminant_value > self.max_value() {
+-            abort!(variant, "Value of variant exceeds the given number of bits")
+         }
+-
+-        Some(discriminant_value)
+     }
+ 
+     fn assign(&mut self, variant: &Variant) -> u128 {
+--- a/src/shared/fallback.rs
++++ b/src/shared/fallback.rs
+@@ -22,8 +22,9 @@
+             }
+             Unnamed(fields) => {
+                 let variant_fields = fields.unnamed.iter();
+-                let Ok(fallback_value) = variant_fields.exactly_one() else {
+-                    abort!(variant, "fallback variant must have exactly one field"; help = "use only one field or change to a unit variant")
++                let fallback_value = match variant_fields.exactly_one() {
++                    Ok(ok) => ok,
++                    _ => abort!(variant, "fallback variant must have exactly one field"; help = "use only one field or change to a unit variant")
+                 };
+ 
+                 if !is_last_variant {

+ 3 - 1
subprojects/packagefiles/proc-macro2-1-rs/meson.build

@@ -15,7 +15,9 @@ _proc_macro2_rs = static_library(
   rust_abi: 'rust',
   rust_abi: 'rust',
   rust_args: [
   rust_args: [
     '--cfg', 'feature="proc-macro"',
     '--cfg', 'feature="proc-macro"',
-    '--cfg', 'span_locations',
+    '--cfg', 'no_literal_byte_character',
+    '--cfg', 'no_literal_c_string',
+    '--cfg', 'no_source_text',
     '--cfg', 'wrap_proc_macro',
     '--cfg', 'wrap_proc_macro',
   ],
   ],
   dependencies: [
   dependencies: [

+ 1 - 0
subprojects/packagefiles/syn-2-rs/meson.build

@@ -24,6 +24,7 @@ _syn_rs = static_library(
     '--cfg', 'feature="printing"',
     '--cfg', 'feature="printing"',
     '--cfg', 'feature="clone-impls"',
     '--cfg', 'feature="clone-impls"',
     '--cfg', 'feature="proc-macro"',
     '--cfg', 'feature="proc-macro"',
+    '--cfg', 'feature="extra-traits"',
   ],
   ],
   dependencies: [
   dependencies: [
     quote_dep,
     quote_dep,

+ 1 - 1
system/qdev-monitor.c

@@ -752,7 +752,7 @@ DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
 
 
 #define qdev_printf(fmt, ...) monitor_printf(mon, "%*s" fmt, indent, "", ## __VA_ARGS__)
 #define qdev_printf(fmt, ...) monitor_printf(mon, "%*s" fmt, indent, "", ## __VA_ARGS__)
 
 
-static void qdev_print_props(Monitor *mon, DeviceState *dev, Property *props,
+static void qdev_print_props(Monitor *mon, DeviceState *dev, const Property *props,
                              int indent)
                              int indent)
 {
 {
     if (!props)
     if (!props)

+ 5 - 1
tests/docker/dockerfiles/ubuntu2204.docker

@@ -13,7 +13,6 @@ RUN export DEBIAN_FRONTEND=noninteractive && \
     eatmydata apt-get install --no-install-recommends -y \
     eatmydata apt-get install --no-install-recommends -y \
                       bash \
                       bash \
                       bc \
                       bc \
-                      bindgen \
                       bison \
                       bison \
                       bsdextrautils \
                       bsdextrautils \
                       bzip2 \
                       bzip2 \
@@ -150,6 +149,11 @@ ENV LANG "en_US.UTF-8"
 ENV MAKE "/usr/bin/make"
 ENV MAKE "/usr/bin/make"
 ENV NINJA "/usr/bin/ninja"
 ENV NINJA "/usr/bin/ninja"
 ENV PYTHON "/usr/bin/python3"
 ENV PYTHON "/usr/bin/python3"
+ENV CARGO_HOME=/usr/local/cargo
+ENV PATH=$CARGO_HOME/bin:$PATH
+RUN DEBIAN_FRONTEND=noninteractive eatmydata \
+  apt install -y --no-install-recommends cargo
+RUN cargo install bindgen-cli
 # 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

+ 4 - 0
tests/lcitool/mappings.yml

@@ -1,4 +1,8 @@
 mappings:
 mappings:
+  # Too old on Ubuntu 22.04; we install it from cargo instead
+  bindgen:
+    Ubuntu2204:
+
   flake8:
   flake8:
     OpenSUSELeap15:
     OpenSUSELeap15:
 
 

+ 10 - 1
tests/lcitool/refresh

@@ -137,6 +137,14 @@ fedora_rustup_nightly_extras = [
     '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',
 ]
 ]
 
 
+ubuntu2204_bindgen_extras = [
+    "ENV CARGO_HOME=/usr/local/cargo\n",
+    'ENV PATH=$CARGO_HOME/bin:$PATH\n',
+    "RUN DEBIAN_FRONTEND=noninteractive eatmydata \\\n",
+    "  apt install -y --no-install-recommends cargo\n",
+    'RUN cargo install bindgen-cli\n',
+]
+
 def cross_build(prefix, targets):
 def cross_build(prefix, targets):
     conf = "ENV QEMU_CONFIGURE_OPTS --cross-prefix=%s\n" % (prefix)
     conf = "ENV QEMU_CONFIGURE_OPTS --cross-prefix=%s\n" % (prefix)
     targets = "ENV DEF_TARGET_LIST %s\n" % (targets)
     targets = "ENV DEF_TARGET_LIST %s\n" % (targets)
@@ -157,7 +165,8 @@ try:
                         trailer="".join(debian12_extras))
                         trailer="".join(debian12_extras))
     generate_dockerfile("fedora", "fedora-40")
     generate_dockerfile("fedora", "fedora-40")
     generate_dockerfile("opensuse-leap", "opensuse-leap-15")
     generate_dockerfile("opensuse-leap", "opensuse-leap-15")
-    generate_dockerfile("ubuntu2204", "ubuntu-2204")
+    generate_dockerfile("ubuntu2204", "ubuntu-2204",
+                        trailer="".join(ubuntu2204_bindgen_extras))
 
 
     #
     #
     # Non-fatal Rust-enabled build
     # Non-fatal Rust-enabled build