* 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-----

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>
This commit is contained in:
Peter Maydell 2024-11-06 21:27:47 +00:00
commit a1dacb6691
45 changed files with 1379 additions and 414 deletions

2
.gitattributes vendored
View File

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

View File

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

View File

@ -107,6 +107,18 @@ Python build dependencies
required, it may be necessary to fetch python modules from the Python
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
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

View File

@ -749,7 +749,7 @@ const PropertyInfo qdev_prop_array = {
/* --- 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) {
return NULL;
@ -763,10 +763,10 @@ static Property *qdev_prop_walk(Property *props, const char *name)
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;
Property *prop;
const Property *prop;
/* device properties */
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)
{
Property *prop;
const Property *prop;
prop = qdev_prop_find(dev, name);
object_property_set_str(OBJECT(dev), name,
@ -956,7 +956,7 @@ const PropertyInfo qdev_prop_size = {
/* --- object link property --- */
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,
prop->offset,
@ -969,7 +969,7 @@ const PropertyInfo qdev_prop_link = {
.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);
ObjectProperty *op;
@ -980,7 +980,7 @@ void qdev_property_add_static(DeviceState *dev, Property *prop)
field_prop_getter(prop->info),
field_prop_setter(prop->info),
prop->info->release,
prop);
(Property *)prop);
object_property_set_description(obj, prop->name,
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,
Property *prop)
const Property *prop)
{
ObjectClass *oc = OBJECT_CLASS(klass);
ObjectProperty *op;
@ -1007,7 +1007,7 @@ static void qdev_class_add_property(DeviceClass *klass, const char *name,
field_prop_getter(prop->info),
field_prop_setter(prop->info),
prop->info->release,
prop);
(Property *)prop);
}
if (prop->set_default) {
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
* 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;
@ -1058,12 +1058,12 @@ static void qdev_class_add_legacy_property(DeviceClass *dc, Property *prop)
name = g_strdup_printf("legacy-%s", prop->name);
object_class_property_add(OBJECT_CLASS(dc), name, "str",
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;
for (prop = props; prop && prop->name; prop++) {

View File

@ -136,7 +136,7 @@ struct DeviceClass {
* ensures a compile-time error if someone attempts to assign
* dc->props directly.
*/
Property *props_;
const Property *props_;
/**
* @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.
* 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

View File

@ -37,7 +37,7 @@ struct PropertyInfo {
int (*print)(Object *obj, Property *prop, char *dest, size_t len);
void (*set_default_value)(ObjectProperty *op, const Property *prop);
ObjectProperty *(*create)(ObjectClass *oc, const char *name,
Property *prop);
const Property *prop);
ObjectPropertyAccessor *get;
ObjectPropertyAccessor *set;
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.
* 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

View File

@ -15,6 +15,7 @@ meson.add_postconf_script(find_program('scripts/symlink-install-tree.py'))
not_found = dependency('', required: false)
keyval = import('keyval')
rust = import('rust')
ss = import('sourceset')
fs = import('fs')
@ -52,6 +53,17 @@ cpu = host_machine.cpu_family()
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 #
############
@ -70,21 +82,45 @@ if host_os == 'darwin' and \
all_languages += ['objc']
objc = meson.get_compiler('objc')
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')
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.80.0')
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. Please upgrade to at least 1.80.0 to use Rust.')
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()
error('bindgen version ' + bindgen.version() + ' is unsupported. You can install a new version with "cargo install bindgen-cli"')
else
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
endif
endif
endif
if have_rust
rustfmt = find_program('rustfmt', required: false)
endif
dtrace = not_found
stap = not_found
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 = 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') \
.disable_auto_if(not have_system) \
.allowed()
@ -3374,6 +3399,35 @@ endif
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')
shaderinclude = find_program('scripts/shaderinclude.py')
qapi_gen = find_program('scripts/qapi-gen.py')
@ -3971,32 +4025,37 @@ common_all = static_library('common',
implicit_include_directories: false,
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 = [
'--disable-header-comment',
'--raw-line', '// @generated',
'--ctypes-prefix', 'core::ffi',
'--formatter', 'rustfmt',
'--ctypes-prefix', 'std::os::raw',
'--generate-block',
'--generate-cstr',
'--impl-debug',
'--merge-extern-blocks',
'--no-doc-comments',
'--use-core',
'--with-derive-default',
'--no-size_t-is-usize',
'--no-layout-tests',
'--no-prepend-enum-name',
'--allowlist-file', meson.project_source_root() + '/include/.*',
'--allowlist-file', meson.project_source_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 = [
'DeviceCategory',
'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
# command invocation using the environment variables CLANG_PATH and
# LIBCLANG_PATH
bindings_rs = import('rust').bindgen(
bindings_rs = rust.bindgen(
input: 'rust/wrapper.h',
dependencies: common_ss.all_dependencies(),
output: 'bindings.rs',
include_directories: include_directories('.', 'include'),
bindgen_version: ['>=0.69.4'],
bindgen_version: ['>=0.60.0'],
args: bindgen_args,
)
subdir('rust')
@ -4040,6 +4099,7 @@ endif
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'
entitlement = find_program('scripts/entitlement.sh')
@ -4132,7 +4192,7 @@ foreach target : target_dirs
arch_srcs += target_specific.sources()
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)
crates = []
foreach dep : target_rust.dependencies()
@ -4141,7 +4201,7 @@ foreach target : target_dirs
if crates.length() > 0
rlib_rs = custom_target('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,
build_by_default: true,
build_always_stale: true)
@ -4149,7 +4209,6 @@ foreach target : target_dirs
rlib_rs,
dependencies: target_rust.dependencies(),
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_args: rustc_args,
rust_abi: 'c')
arch_deps += declare_dependency(link_whole: [rlib])
endif
@ -4495,9 +4554,11 @@ else
endif
summary_info += {'Rust support': 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 += {'rustc': ' '.join(rustc.cmd_array())}
summary_info += {'rustc version': rustc.version()}
summary_info += {'bindgen': bindgen.full_path()}
summary_info += {'bindgen version': bindgen.version()}
endif
option_cflags = (get_option('debug') ? ['-g'] : [])
if get_option('optimization') != 'plain'

View File

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

View File

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

7
rust/Cargo.toml Normal file
View File

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

View File

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

View File

@ -21,6 +21,3 @@ bilge = { version = "0.2.0" }
bilge-impl = { version = "0.2.0" }
qemu_api = { path = "../../../qemu-api" }
qemu_api_macros = { path = "../../../qemu-api-macros" }
# Do not include in any global workspace
[workspace]

View File

@ -2,14 +2,17 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// 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::{
bindings::{self, *},
c_str,
definitions::ObjectImpl,
device_class::TYPE_SYS_BUS_DEVICE,
};
use crate::{
@ -18,15 +21,42 @@ use crate::{
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;
/// QEMU sourced constant.
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)]
#[derive(Debug, qemu_api_macros::Object)]
#[derive(Debug, qemu_api_macros::Object, qemu_api_macros::offsets)]
/// PL011 Device Model in QEMU
pub struct PL011State {
pub parent_obj: SysBusDevice,
@ -69,6 +99,8 @@ pub struct PL011State {
pub clock: NonNull<Clock>,
#[doc(alias = "migrate_clk")]
pub migrate_clock: bool,
/// The byte string that identifies the device.
device_id: DeviceId,
}
impl ObjectImpl for PL011State {
@ -88,17 +120,13 @@ pub struct 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<
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;
}
#[used]
pub static CLK_NAME: &CStr = c"clk";
impl 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
/// location/instance. All its fields are expected to hold unitialized
/// 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>();
// SAFETY:
//
@ -148,23 +178,18 @@ impl PL011State {
}
}
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::*;
std::ops::ControlFlow::Break(match RegisterOffset::try_from(offset) {
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(_) => {
// qemu_log_mask(LOG_GUEST_ERROR, "pl011_read: Bad offset 0x%x\n", (int)offset);
0
}
Ok(DR) => {
// s->flags &= ~PL011_FLAG_RXFF;
self.flags.set_receive_fifo_full(false);
let c = self.read_fifo[self.read_pos];
if self.read_count > 0 {
@ -172,11 +197,9 @@ impl PL011State {
self.read_pos = (self.read_pos + 1) & (self.fifo_depth() - 1);
}
if self.read_count == 0 {
// self.flags |= PL011_FLAG_RXFE;
self.flags.set_receive_fifo_empty(true);
}
if self.read_count + 1 == self.read_trigger {
//self.int_level &= ~ INT_RX;
self.int_level &= !registers::INT_RX;
}
// Update error bits.
@ -346,13 +369,6 @@ impl PL011State {
* 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_data_carrier_detect(self.control.out_1());
self.flags.set_clear_to_send(self.control.request_to_send());
@ -363,10 +379,6 @@ impl PL011State {
let mut il = self.int_level;
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() {
il |= Interrupt::DSR as u32;
@ -472,10 +484,8 @@ impl PL011State {
let slot = (self.read_pos + self.read_count) & (depth - 1);
self.read_fifo[slot] = value;
self.read_count += 1;
// s->flags &= ~PL011_FLAG_RXFE;
self.flags.set_receive_fifo_empty(false);
if self.read_count == depth {
//s->flags |= PL011_FLAG_RXFF;
self.flags.set_receive_fifo_full(true);
}
@ -492,6 +502,27 @@ impl PL011State {
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 ?
@ -514,7 +545,6 @@ pub const IRQMASK: [u32; 6] = [
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
#[no_mangle]
pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int {
unsafe {
debug_assert!(!opaque.is_null());
@ -530,12 +560,7 @@ pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int {
/// readable/writeable from one thread at any time.
///
/// 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 {
debug_assert!(!opaque.is_null());
let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
@ -554,8 +579,7 @@ pub unsafe extern "C" fn pl011_receive(
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
#[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 {
debug_assert!(!opaque.is_null());
let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
@ -576,7 +600,7 @@ pub unsafe extern "C" fn pl011_create(
let dev: *mut DeviceState = qdev_new(PL011State::TYPE_INFO.name);
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_mmio_map(sysbus, 0, addr);
sysbus_connect_irq(sysbus, 0, irq);
@ -589,7 +613,6 @@ pub unsafe extern "C" fn pl011_create(
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
#[no_mangle]
pub unsafe extern "C" fn pl011_init(obj: *mut Object) {
unsafe {
debug_assert!(!obj.is_null());
@ -597,3 +620,50 @@ pub unsafe extern "C" fn pl011_init(obj: *mut Object) {
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;
}

View File

@ -3,33 +3,93 @@
// SPDX-License-Identifier: GPL-2.0-or-later
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;
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
}
}
/// 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 {
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! {
PL011_PROPERTIES,
qemu_api::define_property!(
c"chardev",
c_str!("chardev"),
PL011State,
char_backend,
unsafe { &qdev_prop_chr },
CharBackend
),
qemu_api::define_property!(
c"migrate-clk",
c_str!("migrate-clk"),
PL011State,
migrate_clock,
unsafe { &qdev_prop_bool },
bool
bool,
default = true
),
}
@ -46,7 +106,6 @@ qemu_api::device_class_init! {
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
#[no_mangle]
pub unsafe extern "C" fn pl011_realize(dev: *mut DeviceState, _errp: *mut *mut Error) {
unsafe {
assert!(!dev.is_null());
@ -60,7 +119,6 @@ pub unsafe extern "C" fn pl011_realize(dev: *mut DeviceState, _errp: *mut *mut E
/// We expect the FFI user of this function to pass a valid pointer, that has
/// the same size as [`PL011State`]. We also expect the device is
/// readable/writeable from one thread at any time.
#[no_mangle]
pub unsafe extern "C" fn pl011_reset(dev: *mut DeviceState) {
unsafe {
assert!(!dev.is_null());

View File

@ -36,16 +36,20 @@
clippy::cognitive_complexity,
clippy::missing_safety_doc,
)]
#![allow(clippy::result_unit_err)]
extern crate bilge;
extern crate bilge_impl;
extern crate qemu_api;
use qemu_api::c_str;
pub mod device;
pub mod device_class;
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.
///

View File

@ -2,9 +2,10 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// 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;
@ -14,20 +15,15 @@ pub static PL011_OPS: MemoryRegionOps = MemoryRegionOps {
read_with_attrs: None,
write_with_attrs: None,
endianness: device_endian::DEVICE_NATIVE_ENDIAN,
valid: unsafe { MaybeUninit::<MemoryRegionOps__bindgen_ty_1>::zeroed().assume_init() },
valid: Zeroable::ZERO,
impl_: MemoryRegionOps__bindgen_ty_2 {
min_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());
let mut state = unsafe { NonNull::new_unchecked(opaque.cast::<PL011State>()) };
let val = unsafe { state.as_mut().read(addr, size) };
@ -44,13 +40,7 @@ unsafe extern "C" fn pl011_read(
}
}
#[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 {
assert!(!opaque.is_null());
let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());

View File

@ -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"

View File

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

View File

@ -2,7 +2,7 @@ quote_dep = dependency('quote-1-rs', native: true)
syn_dep = dependency('syn-2-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',
files('src/lib.rs'),
override_options: ['rust_std=2021', 'build.rust_std=2021'],

View File

@ -3,41 +3,92 @@
// SPDX-License-Identifier: GPL-2.0-or-later
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)]
pub fn derive_object(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let module_static = format_ident!("__{}_LOAD_MODULE", name);
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
);
}
}
__load
};
}
};
TokenStream::from(expanded)
}
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)
}

View File

@ -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"

View File

@ -14,13 +14,15 @@ keywords = []
categories = []
[dependencies]
qemu_api_macros = { path = "../qemu-api-macros" }
[build-dependencies]
version_check = "~0.9"
[features]
default = []
allocator = []
# Do not include in any global workspace
[workspace]
[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)'] }

View File

@ -4,6 +4,8 @@
use std::path::Path;
use version_check as rustc;
fn main() {
if !Path::new("src/bindings.rs").exists() {
panic!(
@ -11,4 +13,11 @@ fn main() {
(`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");
}

View File

@ -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',
structured_sources(
[
'src/lib.rs',
'src/c_str.rs',
'src/definitions.rs',
'src/device_class.rs',
'src/offset_of.rs',
'src/vmstate.rs',
'src/zeroable.rs',
],
{'.' : bindings_rs},
),
override_options: ['rust_std=2021', 'build.rust_std=2021'],
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(
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'])

View File

@ -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);
}
}

View File

@ -4,7 +4,7 @@
//! 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};
@ -29,46 +29,40 @@ pub trait Class {
#[macro_export]
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
}
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]
@ -81,13 +75,13 @@ macro_rules! type_info {
} else {
::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_post_init: <$t as $crate::definitions::ObjectImpl>::INSTANCE_POST_INIT,
instance_finalize: <$t as $crate::definitions::ObjectImpl>::INSTANCE_FINALIZE,
abstract_: <$t as $crate::definitions::ObjectImpl>::ABSTRACT,
class_size: ::core::mem::size_of::<<$t as $crate::definitions::ObjectImpl>::Class>() 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_base_init: <<$t as $crate::definitions::ObjectImpl>::Class as $crate::definitions::Class>::CLASS_BASE_INIT,
class_data: ::core::ptr::null_mut(),

View File

@ -2,127 +2,73 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// 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_rules! device_class_init {
($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(
klass: *mut $crate::bindings::ObjectClass,
_: *mut ::core::ffi::c_void,
_: *mut ::std::os::raw::c_void,
) {
let mut dc =
::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_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 {
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,
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,
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 {
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,
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,
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_rules! declare_properties {
($ident:ident, $($prop:expr),*$(,)*) => {
const fn _calc_prop_len() -> usize {
pub static $ident: [$crate::bindings::Property; {
let mut len = 1;
$({
_ = stringify!($prop);
len += 1;
})*
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) };

View File

@ -26,14 +26,20 @@ unsafe impl Send for bindings::Property {}
unsafe impl Sync for bindings::Property {}
unsafe impl Sync for bindings::TypeInfo {}
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 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)]
extern "C" {
@ -47,8 +53,8 @@ extern "C" {
#[cfg(not(HAVE_GLIB_WITH_ALIGNED_ALLOC))]
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" {
@ -113,7 +119,7 @@ impl Default for QemuAllocator {
}
// 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 fn alloc(&self, layout: Layout) -> *mut u8 {
@ -164,3 +170,6 @@ unsafe impl GlobalAlloc for QemuAllocator {
}
}
}
#[cfg(has_offset_of)]
pub use core::mem::offset_of;

View File

@ -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);
}
}

View File

@ -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,
}
}

View File

@ -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()
}}
}

View File

@ -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,
};
}

View File

@ -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 _);
}
}

View File

@ -30,6 +30,23 @@
* 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/module.h"
#include "qemu-io.h"

View File

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

View File

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

View File

@ -47,6 +47,8 @@ meson_options_help() {
printf "%s\n" ' getrandom()'
printf "%s\n" ' --enable-safe-stack SafeStack Stack Smash Protection (requires'
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-tcg-interpreter TCG with bytecode interpreter (slow)'
printf "%s\n" ' --enable-trace-backends=CHOICES'
@ -495,6 +497,8 @@ _meson_option_parse() {
--disable-spice-protocol) printf "%s" -Dspice_protocol=disabled ;;
--enable-stack-protector) printf "%s" -Dstack_protector=enabled ;;
--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 ;;
--disable-strip) printf "%s" -Dstrip=false ;;
--sysconfdir=*) quote_sh "-Dsysconfdir=$2" ;;

View File

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

View File

@ -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 {

View File

@ -15,7 +15,9 @@ _proc_macro2_rs = static_library(
rust_abi: 'rust',
rust_args: [
'--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',
],
dependencies: [

View File

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

View File

@ -752,7 +752,7 @@ DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
#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)
{
if (!props)

View File

@ -13,7 +13,6 @@ RUN export DEBIAN_FRONTEND=noninteractive && \
eatmydata apt-get install --no-install-recommends -y \
bash \
bc \
bindgen \
bison \
bsdextrautils \
bzip2 \
@ -150,6 +149,11 @@ ENV LANG "en_US.UTF-8"
ENV MAKE "/usr/bin/make"
ENV NINJA "/usr/bin/ninja"
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)
ARG USER
ARG UID

View File

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

View File

@ -137,6 +137,14 @@ fedora_rustup_nightly_extras = [
'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):
conf = "ENV QEMU_CONFIGURE_OPTS --cross-prefix=%s\n" % (prefix)
targets = "ENV DEF_TARGET_LIST %s\n" % (targets)
@ -157,7 +165,8 @@ try:
trailer="".join(debian12_extras))
generate_dockerfile("fedora", "fedora-40")
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