* exec/cpu-all: remove BSWAP_NEEDED

* pl011: pad C PL011State struct to same size as Rust struct
 * rust: hpet: fix type of "timers" property
 * rust: hpet: fix functional tests (and really everything that uses it)
 * rust: Kconfig: Factor out whether devices are Rust or C
 * rust: vmstate: Fixes and tests
 -----BEGIN PGP SIGNATURE-----
 
 iQFIBAABCgAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmfdsUsUHHBib256aW5p
 QHJlZGhhdC5jb20ACgkQv/vSX3jHroOGpwf/Qk4bAcLX7A1/nOmYT+DtWzZ9V/VS
 hSOe6BruzW8rzwMyn/d7oR+aUpk3sL+v2iPBWqoZ/wh0w8kcABcUfWsqqGI8ln/K
 pnTdiC+hra5z0AFH1tmjjtOI50WDOeSjh5SFvoPJtGzhEbo89QvsUWgy98HiHOMm
 YFPDuhg3Pfd1XDcdoaa85sOHO1vDsj45fCEJhx6Ktib4vOlEm2I4Z9YR/JxNMT33
 vy/y09HG4cpc6bWKLPL3nqR9RchUSI+YRDZ8rlaXUowiZzH2K/wi0qJOsvG6oJF5
 awni0YWuwyFi16jmUub8NFnWk6NKjbACqw74AwoVPbNbDoCrrogXzIF2Lw==
 =NzCN
 -----END PGP SIGNATURE-----

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

* exec/cpu-all: remove BSWAP_NEEDED
* pl011: pad C PL011State struct to same size as Rust struct
* rust: hpet: fix type of "timers" property
* rust: hpet: fix functional tests (and really everything that uses it)
* rust: Kconfig: Factor out whether devices are Rust or C
* rust: vmstate: Fixes and tests

# -----BEGIN PGP SIGNATURE-----
#
# iQFIBAABCgAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmfdsUsUHHBib256aW5p
# QHJlZGhhdC5jb20ACgkQv/vSX3jHroOGpwf/Qk4bAcLX7A1/nOmYT+DtWzZ9V/VS
# hSOe6BruzW8rzwMyn/d7oR+aUpk3sL+v2iPBWqoZ/wh0w8kcABcUfWsqqGI8ln/K
# pnTdiC+hra5z0AFH1tmjjtOI50WDOeSjh5SFvoPJtGzhEbo89QvsUWgy98HiHOMm
# YFPDuhg3Pfd1XDcdoaa85sOHO1vDsj45fCEJhx6Ktib4vOlEm2I4Z9YR/JxNMT33
# vy/y09HG4cpc6bWKLPL3nqR9RchUSI+YRDZ8rlaXUowiZzH2K/wi0qJOsvG6oJF5
# awni0YWuwyFi16jmUub8NFnWk6NKjbACqw74AwoVPbNbDoCrrogXzIF2Lw==
# =NzCN
# -----END PGP SIGNATURE-----
# gpg: Signature made Fri 21 Mar 2025 14:34:51 EDT
# gpg:                using RSA key F13338574B662389866C7682BFFBD25F78C7AE83
# gpg:                issuer "pbonzini@redhat.com"
# gpg: Good signature from "Paolo Bonzini <bonzini@gnu.org>" [full]
# gpg:                 aka "Paolo Bonzini <pbonzini@redhat.com>" [full]
# Primary key fingerprint: 46F5 9FBD 57D6 12E7 BFD4  E2F7 7E15 100C CD36 69B1
#      Subkey fingerprint: F133 3857 4B66 2389 866C  7682 BFFB D25F 78C7 AE83

* tag 'for-upstream' of https://gitlab.com/bonzini/qemu: (24 commits)
  rust: hpet: fix decoding of timer registers
  rust/vmstate: Include complete crate path of VMStateFlags in vmstate_clock
  rust/vmstate: Add unit test for vmstate_validate
  rust/vmstate: Add unit test for pointer case
  rust/vmstate: Add unit test for vmstate_{of|struct} macro
  rust/vmstate: Add unit test for vmstate_of macro
  rust/vmstate: Support vmstate_validate
  rust/vmstate: Re-implement VMState trait for timer binding
  rust/vmstate: Relax array check when build varray in vmstate_struct
  rust/vmstate: Fix unnecessary VMState bound of with_varray_flag()
  rust/vmstate: Fix "cannot infer type" error in vmstate_struct
  rust/vmstate: Fix type check for varray in vmstate_struct
  rust/vmstate: Fix size field of VMStateField with VMS_ARRAY_OF_POINTER flag
  rust/vmstate: Fix num field when varray flags are set
  rust/vmstate: Fix num_offset in vmstate macros
  rust/vmstate: Remove unnecessary unsafe
  exec/cpu-all: remove BSWAP_NEEDED
  load_aout: replace bswap_needed with big_endian
  rust: pl011: Check size of state struct at compile time
  hw/char/pl011: Pad PL011State struct to same size as Rust impl
  ...

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi 2025-03-23 11:04:40 -04:00
commit 71119ed365
33 changed files with 705 additions and 109 deletions

View file

@ -44,7 +44,7 @@ static inline void memcpy_fromfs(void *to, const void *from, unsigned long n)
memcpy(to, from, n);
}
#ifdef BSWAP_NEEDED
#if HOST_BIG_ENDIAN != TARGET_BIG_ENDIAN
static void bswap_ehdr(struct elfhdr *ehdr)
{
bswap16s(&ehdr->e_type); /* Object file type */
@ -111,7 +111,7 @@ static void bswap_note(struct elf_note *en)
bswap32s(&en->n_type);
}
#else /* ! BSWAP_NEEDED */
#else
static void bswap_ehdr(struct elfhdr *ehdr) { }
static void bswap_phdr(struct elf_phdr *phdr, int phnum) { }
@ -119,7 +119,7 @@ static void bswap_shdr(struct elf_shdr *shdr, int shnum) { }
static void bswap_sym(struct elf_sym *sym) { }
static void bswap_note(struct elf_note *en) { }
#endif /* ! BSWAP_NEEDED */
#endif /* HOST_BIG_ENDIAN != TARGET_BIG_ENDIAN */
#include "elfcore.c"

View file

@ -6,7 +6,6 @@
#CONFIG_APPLESMC=n
#CONFIG_FDC=n
#CONFIG_HPET=n
#CONFIG_X_HPET_RUST=n
#CONFIG_HYPERV=n
#CONFIG_ISA_DEBUG=n
#CONFIG_ISA_IPMI_BT=n

View file

@ -21,8 +21,7 @@ config ARM_VIRT
select PCI_EXPRESS
select PCI_EXPRESS_GENERIC_BRIDGE
select PFLASH_CFI01
select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL011 # UART
select PL031 # RTC
select PL061 # GPIO
select GPIO_PWR
@ -75,8 +74,7 @@ config HIGHBANK
select AHCI_SYSBUS
select ARM_TIMER # sp804
select ARM_V7M
select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL011 # UART
select PL022 # SPI
select PL031 # RTC
select PL061 # GPIO
@ -89,8 +87,7 @@ config INTEGRATOR
depends on TCG && ARM
select ARM_TIMER
select INTEGRATOR_DEBUG
select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL011 # UART
select PL031 # RTC
select PL041 # audio
select PL050 # keyboard/mouse
@ -108,8 +105,7 @@ config MUSCA
default y
depends on TCG && ARM
select ARMSSE
select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL011 # UART
select PL031
select SPLIT_IRQ
select UNIMP
@ -173,8 +169,7 @@ config REALVIEW
select WM8750 # audio codec
select LSI_SCSI_PCI
select PCI
select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL011 # UART
select PL031 # RTC
select PL041 # audio codec
select PL050 # keyboard/mouse
@ -199,8 +194,7 @@ config SBSA_REF
select PCI_EXPRESS
select PCI_EXPRESS_GENERIC_BRIDGE
select PFLASH_CFI01
select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL011 # UART
select PL031 # RTC
select PL061 # GPIO
select USB_XHCI_SYSBUS
@ -224,8 +218,7 @@ config STELLARIS
select ARM_V7M
select CMSDK_APB_WATCHDOG
select I2C
select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL011 # UART
select PL022 # SPI
select PL061 # GPIO
select SSD0303 # OLED display
@ -285,8 +278,7 @@ config VEXPRESS
select ARM_TIMER # sp804
select LAN9118
select PFLASH_CFI01
select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL011 # UART
select PL041 # audio codec
select PL181 # display
select REALVIEW
@ -371,8 +363,7 @@ config RASPI
default y
depends on TCG && ARM
select FRAMEBUFFER
select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL011 # UART
select SDHCI
select USB_DWC2
select BCM2835_SPI
@ -448,8 +439,7 @@ config XLNX_VERSAL
select ARM_GIC
select CPU_CLUSTER
select DEVICE_TREE
select PL011 if !HAVE_RUST # UART
select X_PL011_RUST if HAVE_RUST # UART
select PL011 # UART
select CADENCE
select VIRTIO_MMIO
select UNIMP

View file

@ -11,6 +11,12 @@ config PARALLEL
config PL011
bool
# The PL011 has both a Rust and a C implementation
select PL011_C if !HAVE_RUST
select X_PL011_RUST if HAVE_RUST
config PL011_C
bool
config SERIAL
bool

View file

@ -9,7 +9,7 @@ system_ss.add(when: 'CONFIG_ISA_BUS', if_true: files('parallel-isa.c'))
system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugcon.c'))
system_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_uart.c'))
system_ss.add(when: 'CONFIG_PARALLEL', if_true: files('parallel.c'))
system_ss.add(when: 'CONFIG_PL011', if_true: files('pl011.c'))
system_ss.add(when: 'CONFIG_PL011_C', if_true: files('pl011.c'))
system_ss.add(when: 'CONFIG_SCLPCONSOLE', if_true: files('sclpconsole.c', 'sclpconsole-lm.c'))
system_ss.add(when: 'CONFIG_SERIAL', if_true: files('serial.c'))
system_ss.add(when: 'CONFIG_SERIAL_ISA', if_true: files('serial-isa.c'))

View file

@ -226,7 +226,7 @@ static void bswap_ahdr(struct exec *e)
ssize_t load_aout(const char *filename, hwaddr addr, int max_sz,
int bswap_needed, hwaddr target_page_size)
bool big_endian, hwaddr target_page_size)
{
int fd;
ssize_t size, ret;
@ -241,7 +241,7 @@ ssize_t load_aout(const char *filename, hwaddr addr, int max_sz,
if (size < 0)
goto fail;
if (bswap_needed) {
if (big_endian != HOST_BIG_ENDIAN) {
bswap_ahdr(&e);
}

View file

@ -442,6 +442,43 @@ const PropertyInfo qdev_prop_uint64_checkmask = {
.set = set_uint64_checkmask,
};
/* --- pointer-size integer --- */
static void get_usize(Object *obj, Visitor *v, const char *name, void *opaque,
Error **errp)
{
const Property *prop = opaque;
#if HOST_LONG_BITS == 32
uint32_t *ptr = object_field_prop_ptr(obj, prop);
visit_type_uint32(v, name, ptr, errp);
#else
uint64_t *ptr = object_field_prop_ptr(obj, prop);
visit_type_uint64(v, name, ptr, errp);
#endif
}
static void set_usize(Object *obj, Visitor *v, const char *name, void *opaque,
Error **errp)
{
const Property *prop = opaque;
#if HOST_LONG_BITS == 32
uint32_t *ptr = object_field_prop_ptr(obj, prop);
visit_type_uint32(v, name, ptr, errp);
#else
uint64_t *ptr = object_field_prop_ptr(obj, prop);
visit_type_uint64(v, name, ptr, errp);
#endif
}
const PropertyInfo qdev_prop_usize = {
.type = "usize",
.get = get_usize,
.set = set_usize,
.set_default_value = qdev_propinfo_set_default_value_uint,
};
/* --- string --- */
static void release_string(Object *obj, const char *name, void *opaque)

View file

@ -26,7 +26,7 @@
#include CONFIG_DEVICES
#include "target/i386/cpu.h"
#if !defined(CONFIG_HPET) && !defined(CONFIG_X_HPET_RUST)
#if !defined(CONFIG_HPET)
struct hpet_fw_config hpet_fw_cfg = {.count = UINT8_MAX};
#endif

View file

@ -1704,7 +1704,7 @@ static void pc_machine_initfn(Object *obj)
pcms->sata_enabled = true;
pcms->i8042_enabled = true;
pcms->max_fw_size = 8 * MiB;
#if defined(CONFIG_HPET) || defined(CONFIG_X_HPET_RUST)
#if defined(CONFIG_HPET)
pcms->hpet_enabled = true;
#endif
pcms->fd_bootchk = true;

View file

@ -197,11 +197,6 @@ static void ppc_core99_init(MachineState *machine)
}
if (machine->kernel_filename) {
int bswap_needed = 0;
#ifdef BSWAP_NEEDED
bswap_needed = 1;
#endif
kernel_base = KERNEL_LOAD_ADDR;
kernel_size = load_elf(machine->kernel_filename, NULL,
translate_kernel_address, NULL, NULL, NULL,
@ -209,7 +204,7 @@ static void ppc_core99_init(MachineState *machine)
if (kernel_size < 0) {
kernel_size = load_aout(machine->kernel_filename, kernel_base,
machine->ram_size - kernel_base,
bswap_needed, TARGET_PAGE_SIZE);
true, TARGET_PAGE_SIZE);
}
if (kernel_size < 0) {
kernel_size = load_image_targphys(machine->kernel_filename,

View file

@ -153,11 +153,6 @@ static void ppc_heathrow_init(MachineState *machine)
}
if (machine->kernel_filename) {
int bswap_needed = 0;
#ifdef BSWAP_NEEDED
bswap_needed = 1;
#endif
kernel_base = KERNEL_LOAD_ADDR;
kernel_size = load_elf(machine->kernel_filename, NULL,
translate_kernel_address, NULL, NULL, NULL,
@ -165,7 +160,7 @@ static void ppc_heathrow_init(MachineState *machine)
if (kernel_size < 0) {
kernel_size = load_aout(machine->kernel_filename, kernel_base,
machine->ram_size - kernel_base,
bswap_needed, TARGET_PAGE_SIZE);
true, TARGET_PAGE_SIZE);
}
if (kernel_size < 0) {
kernel_size = load_image_targphys(machine->kernel_filename,

View file

@ -233,20 +233,13 @@ static unsigned long sun4m_load_kernel(const char *kernel_filename,
kernel_size = 0;
if (linux_boot) {
int bswap_needed;
#ifdef BSWAP_NEEDED
bswap_needed = 1;
#else
bswap_needed = 0;
#endif
kernel_size = load_elf(kernel_filename, NULL,
translate_kernel_address, NULL,
NULL, NULL, NULL, NULL,
ELFDATA2MSB, EM_SPARC, 0, 0);
if (kernel_size < 0)
kernel_size = load_aout(kernel_filename, KERNEL_LOAD_ADDR,
RAM_size - KERNEL_LOAD_ADDR, bswap_needed,
RAM_size - KERNEL_LOAD_ADDR, true,
TARGET_PAGE_SIZE);
if (kernel_size < 0)
kernel_size = load_image_targphys(kernel_filename,

View file

@ -168,13 +168,6 @@ static uint64_t sun4u_load_kernel(const char *kernel_filename,
kernel_size = 0;
if (linux_boot) {
int bswap_needed;
#ifdef BSWAP_NEEDED
bswap_needed = 1;
#else
bswap_needed = 0;
#endif
kernel_size = load_elf(kernel_filename, NULL, NULL, NULL, kernel_entry,
kernel_addr, &kernel_top, NULL,
ELFDATA2MSB, EM_SPARCV9, 0, 0);
@ -182,7 +175,7 @@ static uint64_t sun4u_load_kernel(const char *kernel_filename,
*kernel_addr = KERNEL_LOAD_ADDR;
*kernel_entry = KERNEL_LOAD_ADDR;
kernel_size = load_aout(kernel_filename, KERNEL_LOAD_ADDR,
RAM_size - KERNEL_LOAD_ADDR, bswap_needed,
RAM_size - KERNEL_LOAD_ADDR, true,
TARGET_PAGE_SIZE);
}
if (kernel_size < 0) {

View file

@ -11,7 +11,13 @@ config A9_GTIMER
config HPET
bool
default y if PC && !HAVE_RUST
default y if PC
# The HPET has both a Rust and a C implementation
select HPET_C if !HAVE_RUST
select X_HPET_RUST if HAVE_RUST
config HPET_C
bool
config I8254
bool

View file

@ -13,7 +13,7 @@ system_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic-timer.c'))
system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_mct.c'))
system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_pwm.c'))
system_ss.add(when: 'CONFIG_GRLIB', if_true: files('grlib_gptimer.c'))
system_ss.add(when: 'CONFIG_HPET', if_true: files('hpet.c'))
system_ss.add(when: 'CONFIG_HPET_C', if_true: files('hpet.c'))
system_ss.add(when: 'CONFIG_I8254', if_true: files('i8254_common.c', 'i8254.c'))
system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_epit.c'))
system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_gpt.c'))

View file

@ -26,18 +26,6 @@
#include "exec/tswap.h"
#include "hw/core/cpu.h"
/* some important defines:
*
* HOST_BIG_ENDIAN : whether the host cpu is big endian and
* otherwise little endian.
*
* TARGET_BIG_ENDIAN : same for the target cpu
*/
#if HOST_BIG_ENDIAN != TARGET_BIG_ENDIAN
#define BSWAP_NEEDED
#endif
/* Target-endianness CPU memory access functions. These fit into the
* {ld,st}{type}{sign}{size}{endian}_p naming scheme described in bswap.h.
*/

View file

@ -37,7 +37,6 @@
#pragma GCC poison TARGET_NAME
#pragma GCC poison TARGET_SUPPORTS_MTTCG
#pragma GCC poison TARGET_BIG_ENDIAN
#pragma GCC poison BSWAP_NEEDED
#pragma GCC poison TARGET_LONG_BITS
#pragma GCC poison TARGET_FMT_lx

View file

@ -52,6 +52,11 @@ struct PL011State {
Clock *clk;
bool migrate_clk;
const unsigned char *id;
/*
* Since some users embed this struct directly, we must
* ensure that the C struct is at least as big as the Rust one.
*/
uint8_t padding_for_rust[16];
};
DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr);

View file

@ -190,7 +190,7 @@ ssize_t load_elf(const char *filename,
void load_elf_hdr(const char *filename, void *hdr, bool *is64, Error **errp);
ssize_t load_aout(const char *filename, hwaddr addr, int max_sz,
int bswap_needed, hwaddr target_page_size);
bool big_endian, hwaddr target_page_size);
#define LOAD_UIMAGE_LOADADDR_INVALID (-1)

View file

@ -52,6 +52,7 @@ extern const PropertyInfo qdev_prop_bool;
extern const PropertyInfo qdev_prop_uint8;
extern const PropertyInfo qdev_prop_uint16;
extern const PropertyInfo qdev_prop_uint32;
extern const PropertyInfo qdev_prop_usize;
extern const PropertyInfo qdev_prop_int32;
extern const PropertyInfo qdev_prop_uint64;
extern const PropertyInfo qdev_prop_uint64_checkmask;

View file

@ -2121,7 +2121,7 @@ static inline void memcpy_fromfs(void * to, const void * from, unsigned long n)
memcpy(to, from, n);
}
#ifdef BSWAP_NEEDED
#if HOST_BIG_ENDIAN != TARGET_BIG_ENDIAN
static void bswap_ehdr(struct elfhdr *ehdr)
{
bswap16s(&ehdr->e_type); /* Object file type */
@ -3143,7 +3143,7 @@ static bool parse_elf_properties(const ImageSource *src,
* The contents of a valid PT_GNU_PROPERTY is a sequence of uint32_t.
* Swap most of them now, beyond the header and namesz.
*/
#ifdef BSWAP_NEEDED
#if HOST_BIG_ENDIAN != TARGET_BIG_ENDIAN
for (int i = 4; i < n / 4; i++) {
bswap32s(note.data + i);
}
@ -3999,7 +3999,7 @@ struct target_elf_prpsinfo {
char pr_psargs[ELF_PRARGSZ]; /* initial part of arg list */
};
#ifdef BSWAP_NEEDED
#if HOST_BIG_ENDIAN != TARGET_BIG_ENDIAN
static void bswap_prstatus(struct target_elf_prstatus *prstatus)
{
prstatus->pr_info.si_signo = tswap32(prstatus->pr_info.si_signo);
@ -4038,7 +4038,7 @@ static void bswap_note(struct elf_note *en)
static inline void bswap_prstatus(struct target_elf_prstatus *p) { }
static inline void bswap_psinfo(struct target_elf_prpsinfo *p) {}
static inline void bswap_note(struct elf_note *en) { }
#endif /* BSWAP_NEEDED */
#endif /* HOST_BIG_ENDIAN != TARGET_BIG_ENDIAN */
/*
* Calculate file (dump) size of given memory region.

View file

@ -462,7 +462,7 @@ typedef struct {
abi_ulong sig[TARGET_NSIG_WORDS];
} target_sigset_t;
#ifdef BSWAP_NEEDED
#if HOST_BIG_ENDIAN != TARGET_BIG_ENDIAN
static inline void tswap_sigset(target_sigset_t *d, const target_sigset_t *s)
{
int i;

View file

@ -2,7 +2,7 @@
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
use std::{ffi::CStr, ptr::addr_of_mut};
use std::{ffi::CStr, mem::size_of, ptr::addr_of_mut};
use qemu_api::{
chardev::{CharBackend, Chardev, Event},
@ -12,6 +12,7 @@ use qemu_api::{
prelude::*,
qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl},
qom::{ObjectImpl, Owned, ParentField},
static_assert,
sysbus::{SysBusDevice, SysBusDeviceImpl},
vmstate::VMStateDescription,
};
@ -124,6 +125,12 @@ pub struct PL011State {
pub migrate_clock: bool,
}
// Some C users of this device embed its state struct into their own
// structs, so the size of the Rust version must not be any larger
// than the size of the C one. If this assert triggers you need to
// expand the padding_for_rust[] array in the C PL011State struct.
static_assert!(size_of::<PL011State>() <= size_of::<qemu_api::bindings::PL011State>());
qom_isa!(PL011State : SysBusDevice, DeviceState, Object);
#[repr(C)]

View file

@ -8,8 +8,12 @@ use std::{
};
use qemu_api::{
bindings::*, c_str, prelude::*, vmstate_clock, vmstate_fields, vmstate_of, vmstate_struct,
vmstate_subsections, vmstate_unused, zeroable::Zeroable,
bindings::{qdev_prop_bool, qdev_prop_chr},
c_str,
prelude::*,
vmstate::VMStateDescription,
vmstate_clock, vmstate_fields, vmstate_of, vmstate_struct, vmstate_subsections, vmstate_unused,
zeroable::Zeroable,
};
use crate::device::{PL011Registers, PL011State};

View file

@ -1,3 +1,2 @@
config X_HPET_RUST
bool
default y if PC && HAVE_RUST

View file

@ -12,7 +12,7 @@ use std::{
use qemu_api::{
bindings::{
address_space_memory, address_space_stl_le, qdev_prop_bit, qdev_prop_bool,
qdev_prop_uint32, qdev_prop_uint8,
qdev_prop_uint32, qdev_prop_usize,
},
c_str,
cell::{BqlCell, BqlRefCell},
@ -776,7 +776,7 @@ impl HPETState {
let timer_id: usize = ((addr - 0x100) / 0x20) as usize;
if timer_id <= self.num_timers.get() {
// TODO: Add trace point - trace_hpet_ram_[read|write]_timer_id(timer_id)
TimerRegister::try_from(addr)
TimerRegister::try_from(addr & 0x18)
.map(|reg| HPETRegister::Timer(&self.timers[timer_id], reg))
} else {
// TODO: Add trace point - trace_hpet_timer_id_out_of_range(timer_id)
@ -859,8 +859,8 @@ qemu_api::declare_properties! {
c_str!("timers"),
HPETState,
num_timers,
unsafe { &qdev_prop_uint8 },
u8,
unsafe { &qdev_prop_usize },
usize,
default = HPET_MIN_TIMERS
),
qemu_api::define_property!(

View file

@ -58,7 +58,8 @@ rust_qemu_api_objs = static_library(
libchardev.extract_all_objects(recursive: false),
libcrypto.extract_all_objects(recursive: false),
libauthz.extract_all_objects(recursive: false),
libio.extract_all_objects(recursive: false)])
libio.extract_all_objects(recursive: false),
libmigration.extract_all_objects(recursive: false)])
rust_qemu_api_deps = declare_dependency(
dependencies: [
qom_ss.dependencies(),
@ -71,7 +72,7 @@ rust_qemu_api_deps = declare_dependency(
test('rust-qemu-api-integration',
executable(
'rust-qemu-api-integration',
'tests/tests.rs',
files('tests/tests.rs', 'tests/vmstate_tests.rs'),
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_args: ['--test'],
install: false,

View file

@ -91,6 +91,21 @@ macro_rules! assert_field_type {
}
};
};
($t:ty, $i:tt, $ti:ty, num = $num:ident) => {
const _: () = {
#[allow(unused)]
fn assert_field_type(v: $t) {
fn types_must_be_equal<T, U>(_: T)
where
T: $crate::assertions::EqType<Itself = U>,
{
}
let index: usize = v.$num.try_into().unwrap();
types_must_be_equal::<_, &$ti>(&v.$i[index]);
}
};
};
}
/// Assert that an expression matches a pattern. This can also be
@ -120,3 +135,25 @@ macro_rules! assert_match {
);
};
}
/// Assert at compile time that an expression is true. This is similar
/// to `const { assert!(...); }` but it works outside functions, as well as
/// on versions of Rust before 1.79.
///
/// # Examples
///
/// ```
/// # use qemu_api::static_assert;
/// static_assert!("abc".len() == 3);
/// ```
///
/// ```compile_fail
/// # use qemu_api::static_assert;
/// static_assert!("abc".len() == 2); // does not compile
/// ```
#[macro_export]
macro_rules! static_assert {
($x:expr) => {
const _: () = assert!($x);
};
}

View file

@ -25,13 +25,11 @@
//! functionality that is missing from `vmstate_of!`.
use core::{marker::PhantomData, mem, ptr::NonNull};
use std::os::raw::{c_int, c_void};
pub use crate::bindings::{VMStateDescription, VMStateField};
use crate::{
bindings::{self, VMStateFlags},
prelude::*,
qom::Owned,
zeroable::Zeroable,
bindings::VMStateFlags, callbacks::FnCall, prelude::*, qom::Owned, zeroable::Zeroable,
};
/// This macro is used to call a function with a generic argument bound
@ -208,7 +206,7 @@ macro_rules! vmstate_of {
.as_bytes()
.as_ptr() as *const ::std::os::raw::c_char,
offset: $crate::offset_of!($struct_name, $field_name),
$(.num_offset: $crate::offset_of!($struct_name, $num),)?
$(num_offset: $crate::offset_of!($struct_name, $num),)?
// The calls to `call_func_with_field!` are the magic that
// computes most of the VMStateField from the type of the field.
info: $crate::info_enum_to_ref!($crate::call_func_with_field!(
@ -256,6 +254,10 @@ impl VMStateField {
if (self.flags.0 & VMStateFlags::VMS_POINTER.0) != 0 {
self.flags = VMStateFlags(self.flags.0 & !VMStateFlags::VMS_POINTER.0);
self.flags = VMStateFlags(self.flags.0 | VMStateFlags::VMS_ARRAY_OF_POINTER.0);
// VMS_ARRAY_OF_POINTER flag stores the size of pointer.
// FIXME: *const, *mut, NonNull and Box<> have the same size as usize.
// Resize if more smart pointers are supported.
self.size = std::mem::size_of::<usize>();
}
self.flags = VMStateFlags(self.flags.0 & !VMStateFlags::VMS_SINGLE.0);
self.flags = VMStateFlags(self.flags.0 | VMStateFlags::VMS_ARRAY.0);
@ -271,13 +273,20 @@ impl VMStateField {
}
#[must_use]
pub const fn with_varray_flag<T: VMState>(mut self, flag: VMStateFlags) -> VMStateField {
assert!((self.flags.0 & VMStateFlags::VMS_ARRAY.0) != 0);
pub const fn with_varray_flag_unchecked(mut self, flag: VMStateFlags) -> VMStateField {
self.flags = VMStateFlags(self.flags.0 & !VMStateFlags::VMS_ARRAY.0);
self.flags = VMStateFlags(self.flags.0 | flag.0);
self.num = 0; // varray uses num_offset instead of num.
self
}
#[must_use]
#[allow(unused_mut)]
pub const fn with_varray_flag(mut self, flag: VMStateFlags) -> VMStateField {
assert!((self.flags.0 & VMStateFlags::VMS_ARRAY.0) != 0);
self.with_varray_flag_unchecked(flag)
}
#[must_use]
pub const fn with_varray_multiply(mut self, num: u32) -> VMStateField {
assert!(num <= 0x7FFF_FFFFu32);
@ -333,6 +342,7 @@ impl_vmstate_transparent!(std::cell::UnsafeCell<T> where T: VMState);
impl_vmstate_transparent!(std::pin::Pin<T> where T: VMState);
impl_vmstate_transparent!(crate::cell::BqlCell<T> where T: VMState);
impl_vmstate_transparent!(crate::cell::BqlRefCell<T> where T: VMState);
impl_vmstate_transparent!(crate::cell::Opaque<T> where T: VMState);
#[macro_export]
macro_rules! impl_vmstate_bitsized {
@ -379,7 +389,7 @@ impl_vmstate_scalar!(vmstate_info_uint8, u8, VMS_VARRAY_UINT8);
impl_vmstate_scalar!(vmstate_info_uint16, u16, VMS_VARRAY_UINT16);
impl_vmstate_scalar!(vmstate_info_uint32, u32, VMS_VARRAY_UINT32);
impl_vmstate_scalar!(vmstate_info_uint64, u64);
impl_vmstate_scalar!(vmstate_info_timer, bindings::QEMUTimer);
impl_vmstate_scalar!(vmstate_info_timer, crate::timer::Timer);
// Pointer types using the underlying type's VMState plus VMS_POINTER
// Note that references are not supported, though references to cells
@ -440,21 +450,23 @@ macro_rules! vmstate_struct {
name: ::core::concat!(::core::stringify!($field_name), "\0")
.as_bytes()
.as_ptr() as *const ::std::os::raw::c_char,
$(.num_offset: $crate::offset_of!($struct_name, $num),)?
$(num_offset: $crate::offset_of!($struct_name, $num),)?
offset: {
$crate::assert_field_type!($struct_name, $field_name, $type);
$crate::assert_field_type!($struct_name, $field_name, $type $(, num = $num)?);
$crate::offset_of!($struct_name, $field_name)
},
size: ::core::mem::size_of::<$type>(),
flags: $crate::bindings::VMStateFlags::VMS_STRUCT,
vmsd: unsafe { $vmsd },
..$crate::zeroable::Zeroable::ZERO $(
.with_varray_flag($crate::call_func_with_field!(
$crate::vmstate::vmstate_varray_flag,
$struct_name,
$num))
$(.with_varray_multiply($factor))?)?
}
vmsd: $vmsd,
..$crate::zeroable::Zeroable::ZERO
} $(.with_varray_flag_unchecked(
$crate::call_func_with_field!(
$crate::vmstate::vmstate_varray_flag,
$struct_name,
$num
)
)
$(.with_varray_multiply($factor))?)?
};
}
@ -475,7 +487,10 @@ macro_rules! vmstate_clock {
$crate::offset_of!($struct_name, $field_name)
},
size: ::core::mem::size_of::<*const $crate::qdev::Clock>(),
flags: VMStateFlags(VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_POINTER.0),
flags: $crate::bindings::VMStateFlags(
$crate::bindings::VMStateFlags::VMS_STRUCT.0
| $crate::bindings::VMStateFlags::VMS_POINTER.0,
),
vmsd: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_clock) },
..$crate::zeroable::Zeroable::ZERO
}
@ -499,6 +514,53 @@ macro_rules! vmstate_fields {
}}
}
pub extern "C" fn rust_vms_test_field_exists<T, F: for<'a> FnCall<(&'a T, u8), bool>>(
opaque: *mut c_void,
version_id: c_int,
) -> bool {
let owner: &T = unsafe { &*(opaque.cast::<T>()) };
let version: u8 = version_id.try_into().unwrap();
// SAFETY: the opaque was passed as a reference to `T`.
F::call((owner, version))
}
pub type VMSFieldExistCb = unsafe extern "C" fn(
opaque: *mut std::os::raw::c_void,
version_id: std::os::raw::c_int,
) -> bool;
#[doc(alias = "VMSTATE_VALIDATE")]
#[macro_export]
macro_rules! vmstate_validate {
($struct_name:ty, $test_name:expr, $test_fn:expr $(,)?) => {
$crate::bindings::VMStateField {
name: ::std::ffi::CStr::as_ptr($test_name),
field_exists: {
const fn test_cb_builder__<
T,
F: for<'a> $crate::callbacks::FnCall<(&'a T, u8), bool>,
>(
_phantom: ::core::marker::PhantomData<F>,
) -> $crate::vmstate::VMSFieldExistCb {
let _: () = F::ASSERT_IS_SOME;
$crate::vmstate::rust_vms_test_field_exists::<T, F>
}
const fn phantom__<T>(_: &T) -> ::core::marker::PhantomData<T> {
::core::marker::PhantomData
}
Some(test_cb_builder__::<$struct_name, _>(phantom__(&$test_fn)))
},
flags: $crate::bindings::VMStateFlags(
$crate::bindings::VMStateFlags::VMS_MUST_EXIST.0
| $crate::bindings::VMStateFlags::VMS_ARRAY.0,
),
num: 0, // 0 elements: no data, only run test_fn callback
..$crate::zeroable::Zeroable::ZERO
}
};
}
/// A transparent wrapper type for the `subsections` field of
/// [`VMStateDescription`].
///

View file

@ -17,6 +17,8 @@ use qemu_api::{
zeroable::Zeroable,
};
mod vmstate_tests;
// Test that macros can compile.
pub static VMSTATE: VMStateDescription = VMStateDescription {
name: c_str!("name").as_ptr(),

View file

@ -0,0 +1,477 @@
// Copyright (C) 2025 Intel Corporation.
// Author(s): Zhao Liu <zhai1.liu@intel.com>
// SPDX-License-Identifier: GPL-2.0-or-later
use std::{ffi::CStr, mem::size_of, os::raw::c_void, ptr::NonNull, slice};
use qemu_api::{
bindings::{
vmstate_info_bool, vmstate_info_int32, vmstate_info_int64, vmstate_info_int8,
vmstate_info_uint64, vmstate_info_uint8, vmstate_info_unused_buffer, VMStateFlags,
},
c_str,
cell::{BqlCell, Opaque},
impl_vmstate_forward,
vmstate::{VMStateDescription, VMStateField},
vmstate_fields, vmstate_of, vmstate_struct, vmstate_unused, vmstate_validate,
zeroable::Zeroable,
};
const FOO_ARRAY_MAX: usize = 3;
// =========================== Test VMSTATE_FOOA ===========================
// Test the use cases of the vmstate macro, corresponding to the following C
// macro variants:
// * VMSTATE_FOOA:
// - VMSTATE_U16
// - VMSTATE_UNUSED
// - VMSTATE_VARRAY_UINT16_UNSAFE
// - VMSTATE_VARRAY_MULTIPLY
#[repr(C)]
#[derive(qemu_api_macros::offsets)]
struct FooA {
arr: [u8; FOO_ARRAY_MAX],
num: u16,
arr_mul: [i8; FOO_ARRAY_MAX],
num_mul: u32,
elem: i8,
}
static VMSTATE_FOOA: VMStateDescription = VMStateDescription {
name: c_str!("foo_a").as_ptr(),
version_id: 1,
minimum_version_id: 1,
fields: vmstate_fields! {
vmstate_of!(FooA, elem),
vmstate_unused!(size_of::<i64>()),
vmstate_of!(FooA, arr[0 .. num]).with_version_id(0),
vmstate_of!(FooA, arr_mul[0 .. num_mul * 16]),
},
..Zeroable::ZERO
};
#[test]
fn test_vmstate_uint16() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) };
// 1st VMStateField ("elem") in VMSTATE_FOOA (corresponding to VMSTATE_UINT16)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[0].name) }.to_bytes_with_nul(),
b"elem\0"
);
assert_eq!(foo_fields[0].offset, 16);
assert_eq!(foo_fields[0].num_offset, 0);
assert_eq!(foo_fields[0].info, unsafe { &vmstate_info_int8 });
assert_eq!(foo_fields[0].version_id, 0);
assert_eq!(foo_fields[0].size, 1);
assert_eq!(foo_fields[0].num, 0);
assert_eq!(foo_fields[0].flags, VMStateFlags::VMS_SINGLE);
assert!(foo_fields[0].vmsd.is_null());
assert!(foo_fields[0].field_exists.is_none());
}
#[test]
fn test_vmstate_unused() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) };
// 2nd VMStateField ("unused") in VMSTATE_FOOA (corresponding to VMSTATE_UNUSED)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[1].name) }.to_bytes_with_nul(),
b"unused\0"
);
assert_eq!(foo_fields[1].offset, 0);
assert_eq!(foo_fields[1].num_offset, 0);
assert_eq!(foo_fields[1].info, unsafe { &vmstate_info_unused_buffer });
assert_eq!(foo_fields[1].version_id, 0);
assert_eq!(foo_fields[1].size, 8);
assert_eq!(foo_fields[1].num, 0);
assert_eq!(foo_fields[1].flags, VMStateFlags::VMS_BUFFER);
assert!(foo_fields[1].vmsd.is_null());
assert!(foo_fields[1].field_exists.is_none());
}
#[test]
fn test_vmstate_varray_uint16_unsafe() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) };
// 3rd VMStateField ("arr") in VMSTATE_FOOA (corresponding to
// VMSTATE_VARRAY_UINT16_UNSAFE)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[2].name) }.to_bytes_with_nul(),
b"arr\0"
);
assert_eq!(foo_fields[2].offset, 0);
assert_eq!(foo_fields[2].num_offset, 4);
assert_eq!(foo_fields[2].info, unsafe { &vmstate_info_uint8 });
assert_eq!(foo_fields[2].version_id, 0);
assert_eq!(foo_fields[2].size, 1);
assert_eq!(foo_fields[2].num, 0);
assert_eq!(foo_fields[2].flags, VMStateFlags::VMS_VARRAY_UINT16);
assert!(foo_fields[2].vmsd.is_null());
assert!(foo_fields[2].field_exists.is_none());
}
#[test]
fn test_vmstate_varray_multiply() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOA.fields, 5) };
// 4th VMStateField ("arr_mul") in VMSTATE_FOOA (corresponding to
// VMSTATE_VARRAY_MULTIPLY)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[3].name) }.to_bytes_with_nul(),
b"arr_mul\0"
);
assert_eq!(foo_fields[3].offset, 6);
assert_eq!(foo_fields[3].num_offset, 12);
assert_eq!(foo_fields[3].info, unsafe { &vmstate_info_int8 });
assert_eq!(foo_fields[3].version_id, 0);
assert_eq!(foo_fields[3].size, 1);
assert_eq!(foo_fields[3].num, 16);
assert_eq!(
foo_fields[3].flags.0,
VMStateFlags::VMS_VARRAY_UINT32.0 | VMStateFlags::VMS_MULTIPLY_ELEMENTS.0
);
assert!(foo_fields[3].vmsd.is_null());
assert!(foo_fields[3].field_exists.is_none());
// The last VMStateField in VMSTATE_FOOA.
assert_eq!(foo_fields[4].flags, VMStateFlags::VMS_END);
}
// =========================== Test VMSTATE_FOOB ===========================
// Test the use cases of the vmstate macro, corresponding to the following C
// macro variants:
// * VMSTATE_FOOB:
// - VMSTATE_BOOL_V
// - VMSTATE_U64
// - VMSTATE_STRUCT_VARRAY_UINT8
// - (no C version) MULTIPLY variant of VMSTATE_STRUCT_VARRAY_UINT32
// - VMSTATE_ARRAY
#[repr(C)]
#[derive(qemu_api_macros::offsets)]
struct FooB {
arr_a: [FooA; FOO_ARRAY_MAX],
num_a: u8,
arr_a_mul: [FooA; FOO_ARRAY_MAX],
num_a_mul: u32,
wrap: BqlCell<u64>,
val: bool,
// FIXME: Use Timer array. Now we can't since it's hard to link savevm.c to test.
arr_i64: [i64; FOO_ARRAY_MAX],
}
static VMSTATE_FOOB: VMStateDescription = VMStateDescription {
name: c_str!("foo_b").as_ptr(),
version_id: 2,
minimum_version_id: 1,
fields: vmstate_fields! {
vmstate_of!(FooB, val).with_version_id(2),
vmstate_of!(FooB, wrap),
vmstate_struct!(FooB, arr_a[0 .. num_a], &VMSTATE_FOOA, FooA).with_version_id(1),
vmstate_struct!(FooB, arr_a_mul[0 .. num_a_mul * 32], &VMSTATE_FOOA, FooA).with_version_id(2),
vmstate_of!(FooB, arr_i64),
},
..Zeroable::ZERO
};
#[test]
fn test_vmstate_bool_v() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 6) };
// 1st VMStateField ("val") in VMSTATE_FOOB (corresponding to VMSTATE_BOOL_V)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[0].name) }.to_bytes_with_nul(),
b"val\0"
);
assert_eq!(foo_fields[0].offset, 136);
assert_eq!(foo_fields[0].num_offset, 0);
assert_eq!(foo_fields[0].info, unsafe { &vmstate_info_bool });
assert_eq!(foo_fields[0].version_id, 2);
assert_eq!(foo_fields[0].size, 1);
assert_eq!(foo_fields[0].num, 0);
assert_eq!(foo_fields[0].flags, VMStateFlags::VMS_SINGLE);
assert!(foo_fields[0].vmsd.is_null());
assert!(foo_fields[0].field_exists.is_none());
}
#[test]
fn test_vmstate_uint64() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 6) };
// 2nd VMStateField ("wrap") in VMSTATE_FOOB (corresponding to VMSTATE_U64)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[1].name) }.to_bytes_with_nul(),
b"wrap\0"
);
assert_eq!(foo_fields[1].offset, 128);
assert_eq!(foo_fields[1].num_offset, 0);
assert_eq!(foo_fields[1].info, unsafe { &vmstate_info_uint64 });
assert_eq!(foo_fields[1].version_id, 0);
assert_eq!(foo_fields[1].size, 8);
assert_eq!(foo_fields[1].num, 0);
assert_eq!(foo_fields[1].flags, VMStateFlags::VMS_SINGLE);
assert!(foo_fields[1].vmsd.is_null());
assert!(foo_fields[1].field_exists.is_none());
}
#[test]
fn test_vmstate_struct_varray_uint8() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 6) };
// 3rd VMStateField ("arr_a") in VMSTATE_FOOB (corresponding to
// VMSTATE_STRUCT_VARRAY_UINT8)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[2].name) }.to_bytes_with_nul(),
b"arr_a\0"
);
assert_eq!(foo_fields[2].offset, 0);
assert_eq!(foo_fields[2].num_offset, 60);
assert!(foo_fields[2].info.is_null()); // VMSTATE_STRUCT_VARRAY_UINT8 doesn't set info field.
assert_eq!(foo_fields[2].version_id, 1);
assert_eq!(foo_fields[2].size, 20);
assert_eq!(foo_fields[2].num, 0);
assert_eq!(
foo_fields[2].flags.0,
VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_VARRAY_UINT8.0
);
assert_eq!(foo_fields[2].vmsd, &VMSTATE_FOOA);
assert!(foo_fields[2].field_exists.is_none());
}
#[test]
fn test_vmstate_struct_varray_uint32_multiply() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 6) };
// 4th VMStateField ("arr_a_mul") in VMSTATE_FOOB (corresponding to
// (no C version) MULTIPLY variant of VMSTATE_STRUCT_VARRAY_UINT32)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[3].name) }.to_bytes_with_nul(),
b"arr_a_mul\0"
);
assert_eq!(foo_fields[3].offset, 64);
assert_eq!(foo_fields[3].num_offset, 124);
assert!(foo_fields[3].info.is_null()); // VMSTATE_STRUCT_VARRAY_UINT8 doesn't set info field.
assert_eq!(foo_fields[3].version_id, 2);
assert_eq!(foo_fields[3].size, 20);
assert_eq!(foo_fields[3].num, 32);
assert_eq!(
foo_fields[3].flags.0,
VMStateFlags::VMS_STRUCT.0
| VMStateFlags::VMS_VARRAY_UINT32.0
| VMStateFlags::VMS_MULTIPLY_ELEMENTS.0
);
assert_eq!(foo_fields[3].vmsd, &VMSTATE_FOOA);
assert!(foo_fields[3].field_exists.is_none());
}
#[test]
fn test_vmstate_macro_array() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 6) };
// 5th VMStateField ("arr_i64") in VMSTATE_FOOB (corresponding to
// VMSTATE_ARRAY)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[4].name) }.to_bytes_with_nul(),
b"arr_i64\0"
);
assert_eq!(foo_fields[4].offset, 144);
assert_eq!(foo_fields[4].num_offset, 0);
assert_eq!(foo_fields[4].info, unsafe { &vmstate_info_int64 });
assert_eq!(foo_fields[4].version_id, 0);
assert_eq!(foo_fields[4].size, 8);
assert_eq!(foo_fields[4].num, FOO_ARRAY_MAX as i32);
assert_eq!(foo_fields[4].flags, VMStateFlags::VMS_ARRAY);
assert!(foo_fields[4].vmsd.is_null());
assert!(foo_fields[4].field_exists.is_none());
// The last VMStateField in VMSTATE_FOOB.
assert_eq!(foo_fields[5].flags, VMStateFlags::VMS_END);
}
// =========================== Test VMSTATE_FOOC ===========================
// Test the use cases of the vmstate macro, corresponding to the following C
// macro variants:
// * VMSTATE_FOOC:
// - VMSTATE_POINTER
// - VMSTATE_ARRAY_OF_POINTER
struct FooCWrapper([Opaque<*mut u8>; FOO_ARRAY_MAX]); // Though Opaque<> array is almost impossible.
impl_vmstate_forward!(FooCWrapper);
#[repr(C)]
#[derive(qemu_api_macros::offsets)]
struct FooC {
ptr: *const i32,
ptr_a: NonNull<FooA>,
arr_ptr: [Box<u8>; FOO_ARRAY_MAX],
arr_ptr_wrap: FooCWrapper,
}
static VMSTATE_FOOC: VMStateDescription = VMStateDescription {
name: c_str!("foo_c").as_ptr(),
version_id: 3,
minimum_version_id: 1,
fields: vmstate_fields! {
vmstate_of!(FooC, ptr).with_version_id(2),
// FIXME: Currently vmstate_struct doesn't support the pointer to structure.
// VMSTATE_STRUCT_POINTER: vmstate_struct!(FooC, ptr_a, VMSTATE_FOOA, NonNull<FooA>)
vmstate_unused!(size_of::<NonNull<FooA>>()),
vmstate_of!(FooC, arr_ptr),
vmstate_of!(FooC, arr_ptr_wrap),
},
..Zeroable::ZERO
};
const PTR_SIZE: usize = size_of::<*mut ()>();
#[test]
fn test_vmstate_pointer() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOC.fields, 6) };
// 1st VMStateField ("ptr") in VMSTATE_FOOC (corresponding to VMSTATE_POINTER)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[0].name) }.to_bytes_with_nul(),
b"ptr\0"
);
assert_eq!(foo_fields[0].offset, 0);
assert_eq!(foo_fields[0].num_offset, 0);
assert_eq!(foo_fields[0].info, unsafe { &vmstate_info_int32 });
assert_eq!(foo_fields[0].version_id, 2);
assert_eq!(foo_fields[0].size, 4);
assert_eq!(foo_fields[0].num, 0);
assert_eq!(
foo_fields[0].flags.0,
VMStateFlags::VMS_SINGLE.0 | VMStateFlags::VMS_POINTER.0
);
assert!(foo_fields[0].vmsd.is_null());
assert!(foo_fields[0].field_exists.is_none());
}
#[test]
fn test_vmstate_macro_array_of_pointer() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOC.fields, 6) };
// 3rd VMStateField ("arr_ptr") in VMSTATE_FOOC (corresponding to
// VMSTATE_ARRAY_OF_POINTER)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[2].name) }.to_bytes_with_nul(),
b"arr_ptr\0"
);
assert_eq!(foo_fields[2].offset, 2 * PTR_SIZE);
assert_eq!(foo_fields[2].num_offset, 0);
assert_eq!(foo_fields[2].info, unsafe { &vmstate_info_uint8 });
assert_eq!(foo_fields[2].version_id, 0);
assert_eq!(foo_fields[2].size, PTR_SIZE);
assert_eq!(foo_fields[2].num, FOO_ARRAY_MAX as i32);
assert_eq!(
foo_fields[2].flags.0,
VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_ARRAY_OF_POINTER.0
);
assert!(foo_fields[2].vmsd.is_null());
assert!(foo_fields[2].field_exists.is_none());
}
#[test]
fn test_vmstate_macro_array_of_pointer_wrapped() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOC.fields, 6) };
// 4th VMStateField ("arr_ptr_wrap") in VMSTATE_FOOC (corresponding to
// VMSTATE_ARRAY_OF_POINTER)
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[3].name) }.to_bytes_with_nul(),
b"arr_ptr_wrap\0"
);
assert_eq!(foo_fields[3].offset, (FOO_ARRAY_MAX + 2) * PTR_SIZE);
assert_eq!(foo_fields[3].num_offset, 0);
assert_eq!(foo_fields[2].info, unsafe { &vmstate_info_uint8 });
assert_eq!(foo_fields[3].version_id, 0);
assert_eq!(foo_fields[3].size, PTR_SIZE);
assert_eq!(foo_fields[3].num, FOO_ARRAY_MAX as i32);
assert_eq!(
foo_fields[2].flags.0,
VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_ARRAY_OF_POINTER.0
);
assert!(foo_fields[3].vmsd.is_null());
assert!(foo_fields[3].field_exists.is_none());
// The last VMStateField in VMSTATE_FOOC.
assert_eq!(foo_fields[4].flags, VMStateFlags::VMS_END);
}
// =========================== Test VMSTATE_FOOD ===========================
// Test the use cases of the vmstate macro, corresponding to the following C
// macro variants:
// * VMSTATE_FOOD:
// - VMSTATE_VALIDATE
// Add more member fields when vmstate_of/vmstate_struct support "test"
// parameter.
struct FooD;
impl FooD {
fn validate_food_0(&self, _version_id: u8) -> bool {
true
}
fn validate_food_1(_state: &FooD, _version_id: u8) -> bool {
false
}
}
fn validate_food_2(_state: &FooD, _version_id: u8) -> bool {
true
}
static VMSTATE_FOOD: VMStateDescription = VMStateDescription {
name: c_str!("foo_d").as_ptr(),
version_id: 3,
minimum_version_id: 1,
fields: vmstate_fields! {
vmstate_validate!(FooD, c_str!("foo_d_0"), FooD::validate_food_0),
vmstate_validate!(FooD, c_str!("foo_d_1"), FooD::validate_food_1),
vmstate_validate!(FooD, c_str!("foo_d_2"), validate_food_2),
},
..Zeroable::ZERO
};
#[test]
fn test_vmstate_validate() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOD.fields, 4) };
let mut foo_d = FooD;
let foo_d_p = std::ptr::addr_of_mut!(foo_d).cast::<c_void>();
// 1st VMStateField in VMSTATE_FOOD
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[0].name) }.to_bytes_with_nul(),
b"foo_d_0\0"
);
assert_eq!(foo_fields[0].offset, 0);
assert_eq!(foo_fields[0].num_offset, 0);
assert!(foo_fields[0].info.is_null());
assert_eq!(foo_fields[0].version_id, 0);
assert_eq!(foo_fields[0].size, 0);
assert_eq!(foo_fields[0].num, 0);
assert_eq!(
foo_fields[0].flags.0,
VMStateFlags::VMS_ARRAY.0 | VMStateFlags::VMS_MUST_EXIST.0
);
assert!(foo_fields[0].vmsd.is_null());
assert!(unsafe { foo_fields[0].field_exists.unwrap()(foo_d_p, 0) });
// 2nd VMStateField in VMSTATE_FOOD
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[1].name) }.to_bytes_with_nul(),
b"foo_d_1\0"
);
assert!(!unsafe { foo_fields[1].field_exists.unwrap()(foo_d_p, 1) });
// 3rd VMStateField in VMSTATE_FOOD
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[2].name) }.to_bytes_with_nul(),
b"foo_d_2\0"
);
assert!(unsafe { foo_fields[2].field_exists.unwrap()(foo_d_p, 2) });
// The last VMStateField in VMSTATE_FOOD.
assert_eq!(foo_fields[3].flags, VMStateFlags::VMS_END);
}

View file

@ -65,3 +65,4 @@ typedef enum memory_order {
#include "exec/memattrs.h"
#include "qemu/timer.h"
#include "exec/address-spaces.h"
#include "hw/char/pl011.h"

View file

@ -103,8 +103,7 @@ qtests_i386 = \
config_all_devices.has_key('CONFIG_VIRTIO_PCI') and \
slirp.found() ? ['virtio-net-failover'] : []) + \
(unpack_edk2_blobs and \
(config_all_devices.has_key('CONFIG_HPET') or \
config_all_devices.has_key('CONFIG_X_HPET_RUST')) and \
config_all_devices.has_key('CONFIG_HPET') and \
config_all_devices.has_key('CONFIG_PARALLEL') ? ['bios-tables-test'] : []) + \
qtests_pci + \
qtests_cxl + \