* rust: support migration of HPET device

* target/i386/hvf: fix compilation errors
 * target/i386/tcg: fix some interrupt shadow cases
 * hw/char/serial: remove unused prog_if compat property
 * rust: centralize config in workspace root
 * monitor: fix race on exiting QEMU
 -----BEGIN PGP SIGNATURE-----
 
 iQFIBAABCgAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmgVQzkUHHBib256aW5p
 QHJlZGhhdC5jb20ACgkQv/vSX3jHroOR8Af/Tke7kRZQyvoKURaKpVOBgP91fTQu
 IKwmX1OYe9JMPBwZV5g/++2HSaAddDzkFq90gmgTY+hpvRE3kDWOA86QtDRP4LKa
 Oq3yW48yrFiRZBAxERgRxRCsEvzlPC3cAEqCQd4fTL+cW6NVorbj4x/tQcALb47V
 cgXXVp59TW4lJk7nJUjd0mCFK1qEoIbZuuBgMn32K+fpBV/UghcoImT2giMeM24Y
 WW3olrLA9UN2fh5da7923WUvA9mSjnE0Yfdk6eKC3nCzlgMKktofwKHilm0tA6xA
 7sJbwYTDSB9QxgnNw3WvAFAOMapJmedaSNorZdmcxCss7ed0s8hV8am9vQ==
 =LFS/
 -----END PGP SIGNATURE-----

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

* rust: support migration of HPET device
* target/i386/hvf: fix compilation errors
* target/i386/tcg: fix some interrupt shadow cases
* hw/char/serial: remove unused prog_if compat property
* rust: centralize config in workspace root
* monitor: fix race on exiting QEMU

# -----BEGIN PGP SIGNATURE-----
#
# iQFIBAABCgAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmgVQzkUHHBib256aW5p
# QHJlZGhhdC5jb20ACgkQv/vSX3jHroOR8Af/Tke7kRZQyvoKURaKpVOBgP91fTQu
# IKwmX1OYe9JMPBwZV5g/++2HSaAddDzkFq90gmgTY+hpvRE3kDWOA86QtDRP4LKa
# Oq3yW48yrFiRZBAxERgRxRCsEvzlPC3cAEqCQd4fTL+cW6NVorbj4x/tQcALb47V
# cgXXVp59TW4lJk7nJUjd0mCFK1qEoIbZuuBgMn32K+fpBV/UghcoImT2giMeM24Y
# WW3olrLA9UN2fh5da7923WUvA9mSjnE0Yfdk6eKC3nCzlgMKktofwKHilm0tA6xA
# 7sJbwYTDSB9QxgnNw3WvAFAOMapJmedaSNorZdmcxCss7ed0s8hV8am9vQ==
# =LFS/
# -----END PGP SIGNATURE-----
# gpg: Signature made Fri 02 May 2025 18:12:09 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:
  monitor: don't wake up qmp_dispatcher_co coroutine upon cleanup
  rust: centralize config in workspace root
  hw/char/serial: Remove unused prog_if compat property
  target/i386: do not block singlestep for STI
  target/i386: do not trigger IRQ shadow for LSS
  target/i386/hvf: fix a compilation error
  target/i386/emulate: remove rflags leftovers
  rust/hpet: Support migration
  rust/timer: Define NANOSECONDS_PER_SECOND binding as u64
  rust/vmstate_test: Test varray with num field wrapped in BqlCell
  rust: assertions: Support index field wrapped in BqlCell
  vmstate: support varray for vmstate_clock!
  rust/vmstate: Add support for field_exists checks

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi 2025-05-05 11:26:47 -04:00
commit dc1ed8f256
19 changed files with 307 additions and 127 deletions

View file

@ -153,8 +153,7 @@ QEMU includes four crates:
.. [#issues] The ``pl011`` crate is synchronized with ``hw/char/pl011.c``
as of commit 02b1f7f61928. The ``hpet`` crate is synchronized as of
commit f32352ff9e. Both are lacking tracing functionality; ``hpet``
is also lacking support for migration.
commit 1433e38cc8. Both are lacking tracing functionality.
This section explains how to work with them.

View file

@ -51,7 +51,6 @@ typedef struct PCIDivaSerialState {
SerialState state[PCI_SERIAL_MAX_PORTS];
uint32_t level[PCI_SERIAL_MAX_PORTS];
qemu_irq *irqs;
uint8_t prog_if;
bool disable;
} PCIDivaSerialState;
@ -124,8 +123,8 @@ static void diva_pci_realize(PCIDevice *dev, Error **errp)
size_t i, offset = 0;
size_t portmask = di.omask;
pci->dev.config[PCI_CLASS_PROG] = pci->prog_if;
pci->dev.config[PCI_INTERRUPT_PIN] = 0x01;
pci->dev.config[PCI_CLASS_PROG] = 2; /* 16550 compatible */
pci->dev.config[PCI_INTERRUPT_PIN] = 1;
memory_region_init(&pci->membar, OBJECT(pci), "serial_ports", 4096);
pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &pci->membar);
pci->irqs = qemu_allocate_irqs(multi_serial_irq_mux, pci, di.nports);
@ -178,7 +177,6 @@ static const Property diva_serial_properties[] = {
DEFINE_PROP_CHR("chardev2", PCIDivaSerialState, state[1].chr),
DEFINE_PROP_CHR("chardev3", PCIDivaSerialState, state[2].chr),
DEFINE_PROP_CHR("chardev4", PCIDivaSerialState, state[3].chr),
DEFINE_PROP_UINT8("prog_if", PCIDivaSerialState, prog_if, 0x02),
DEFINE_PROP_UINT32("subvendor", PCIDivaSerialState, subvendor,
PCI_DEVICE_ID_HP_DIVA_TOSCA1),
};

View file

@ -46,7 +46,6 @@ typedef struct PCIMultiSerialState {
SerialState state[PCI_SERIAL_MAX_PORTS];
uint32_t level[PCI_SERIAL_MAX_PORTS];
IRQState irqs[PCI_SERIAL_MAX_PORTS];
uint8_t prog_if;
} PCIMultiSerialState;
static void multi_serial_pci_exit(PCIDevice *dev)
@ -97,8 +96,8 @@ static void multi_serial_pci_realize(PCIDevice *dev, Error **errp)
SerialState *s;
size_t i, nports = multi_serial_get_port_count(pc);
pci->dev.config[PCI_CLASS_PROG] = pci->prog_if;
pci->dev.config[PCI_INTERRUPT_PIN] = 0x01;
pci->dev.config[PCI_CLASS_PROG] = 2; /* 16550 compatible */
pci->dev.config[PCI_INTERRUPT_PIN] = 1;
memory_region_init(&pci->iobar, OBJECT(pci), "multiserial", 8 * nports);
pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &pci->iobar);
@ -133,7 +132,6 @@ static const VMStateDescription vmstate_pci_multi_serial = {
static const Property multi_2x_serial_pci_properties[] = {
DEFINE_PROP_CHR("chardev1", PCIMultiSerialState, state[0].chr),
DEFINE_PROP_CHR("chardev2", PCIMultiSerialState, state[1].chr),
DEFINE_PROP_UINT8("prog_if", PCIMultiSerialState, prog_if, 0x02),
};
static const Property multi_4x_serial_pci_properties[] = {
@ -141,7 +139,6 @@ static const Property multi_4x_serial_pci_properties[] = {
DEFINE_PROP_CHR("chardev2", PCIMultiSerialState, state[1].chr),
DEFINE_PROP_CHR("chardev3", PCIMultiSerialState, state[2].chr),
DEFINE_PROP_CHR("chardev4", PCIMultiSerialState, state[3].chr),
DEFINE_PROP_UINT8("prog_if", PCIMultiSerialState, prog_if, 0x02),
};
static void multi_2x_serial_pci_class_initfn(ObjectClass *klass,

View file

@ -38,7 +38,6 @@
struct PCISerialState {
PCIDevice dev;
SerialState state;
uint8_t prog_if;
};
#define TYPE_PCI_SERIAL "pci-serial"
@ -53,8 +52,8 @@ static void serial_pci_realize(PCIDevice *dev, Error **errp)
return;
}
pci->dev.config[PCI_CLASS_PROG] = pci->prog_if;
pci->dev.config[PCI_INTERRUPT_PIN] = 0x01;
pci->dev.config[PCI_CLASS_PROG] = 2; /* 16550 compatible */
pci->dev.config[PCI_INTERRUPT_PIN] = 1;
s->irq = pci_allocate_irq(&pci->dev);
memory_region_init_io(&s->io, OBJECT(pci), &serial_io_ops, s, "serial", 8);
@ -81,10 +80,6 @@ static const VMStateDescription vmstate_pci_serial = {
}
};
static const Property serial_pci_properties[] = {
DEFINE_PROP_UINT8("prog_if", PCISerialState, prog_if, 0x02),
};
static void serial_pci_class_initfn(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
@ -96,7 +91,6 @@ static void serial_pci_class_initfn(ObjectClass *klass, const void *data)
pc->revision = 1;
pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL;
dc->vmsd = &vmstate_pci_serial;
device_class_set_props(dc, serial_pci_properties);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}

View file

@ -356,7 +356,8 @@ void qmp_dispatcher_co_wake(void)
/* Write request before reading qmp_dispatcher_co_busy. */
smp_mb__before_rmw();
if (!qatomic_xchg(&qmp_dispatcher_co_busy, true)) {
if (!qatomic_xchg(&qmp_dispatcher_co_busy, true) &&
qatomic_read(&qmp_dispatcher_co)) {
aio_co_wake(qmp_dispatcher_co);
}
}

View file

@ -7,6 +7,13 @@ members = [
"hw/timer/hpet",
]
[workspace.package]
edition = "2021"
homepage = "https://www.qemu.org"
license = "GPL-2.0-or-later"
repository = "https://gitlab.com/qemu-project/qemu/"
rust-version = "1.63.0"
[workspace.lints.rust]
unexpected_cfgs = { level = "deny", check-cfg = [
'cfg(MESON)', 'cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)',

View file

@ -1,15 +1,16 @@
[package]
name = "pl011"
version = "0.1.0"
edition = "2021"
authors = ["Manos Pitsidianakis <manos.pitsidianakis@linaro.org>"]
license = "GPL-2.0-or-later"
description = "pl011 device model for QEMU"
resolver = "2"
publish = false
keywords = []
categories = []
rust-version = "1.63.0"
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[lib]
crate-type = ["staticlib"]

View file

@ -1,11 +1,14 @@
[package]
name = "hpet"
version = "0.1.0"
edition = "2021"
authors = ["Zhao Liu <zhao1.liu@intel.com>"]
license = "GPL-2.0-or-later"
description = "IA-PC High Precision Event Timer emulation in Rust"
rust-version = "1.63.0"
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[lib]
crate-type = ["staticlib"]

View file

@ -4,6 +4,7 @@
use std::{
ffi::CStr,
os::raw::{c_int, c_void},
pin::Pin,
ptr::{addr_of_mut, null_mut, NonNull},
slice::from_ref,
@ -25,7 +26,10 @@ use qemu_api::{
qom::{ObjectImpl, ObjectType, ParentField},
qom_isa,
sysbus::{SysBusDevice, SysBusDeviceImpl},
timer::{Timer, CLOCK_VIRTUAL},
timer::{Timer, CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND},
vmstate::VMStateDescription,
vmstate_fields, vmstate_of, vmstate_struct, vmstate_subsections, vmstate_validate,
zeroable::Zeroable,
};
use crate::fw_cfg::HPETFwConfig;
@ -561,6 +565,7 @@ pub struct HPETState {
#[doc(alias = "timer")]
timers: [BqlRefCell<HPETTimer>; HPET_MAX_TIMERS as usize],
num_timers: BqlCell<u8>,
num_timers_save: BqlCell<u8>,
/// Instance id (HPET timer block ID).
hpet_id: BqlCell<usize>,
@ -839,6 +844,49 @@ impl HPETState {
}
}
}
fn pre_save(&self) -> i32 {
if self.is_hpet_enabled() {
self.counter.set(self.get_ticks());
}
/*
* The number of timers must match on source and destination, but it was
* also added to the migration stream. Check that it matches the value
* that was configured.
*/
self.num_timers_save.set(self.num_timers.get());
0
}
fn post_load(&self, _version_id: u8) -> i32 {
for timer in self.timers.iter().take(self.get_num_timers()) {
let mut t = timer.borrow_mut();
t.cmp64 = t.calculate_cmp64(t.get_state().counter.get(), t.cmp);
t.last = CLOCK_VIRTUAL.get_ns() - NANOSECONDS_PER_SECOND;
}
// Recalculate the offset between the main counter and guest time
if !self.hpet_offset_saved {
self.hpet_offset
.set(ticks_to_ns(self.counter.get()) - CLOCK_VIRTUAL.get_ns());
}
0
}
fn is_rtc_irq_level_needed(&self) -> bool {
self.rtc_irq_level.get() != 0
}
fn is_offset_needed(&self) -> bool {
self.is_hpet_enabled() && self.hpet_offset_saved
}
fn validate_num_timers(&self, _version_id: u8) -> bool {
self.num_timers.get() == self.num_timers_save.get()
}
}
qom_isa!(HPETState: SysBusDevice, DeviceState, Object);
@ -895,11 +943,107 @@ qemu_api::declare_properties! {
),
}
unsafe extern "C" fn hpet_rtc_irq_level_needed(opaque: *mut c_void) -> bool {
// SAFETY:
// the pointer is convertible to a reference
let state: &HPETState = unsafe { NonNull::new(opaque.cast::<HPETState>()).unwrap().as_ref() };
state.is_rtc_irq_level_needed()
}
unsafe extern "C" fn hpet_offset_needed(opaque: *mut c_void) -> bool {
// SAFETY:
// the pointer is convertible to a reference
let state: &HPETState = unsafe { NonNull::new(opaque.cast::<HPETState>()).unwrap().as_ref() };
state.is_offset_needed()
}
unsafe extern "C" fn hpet_pre_save(opaque: *mut c_void) -> c_int {
// SAFETY:
// the pointer is convertible to a reference
let state: &mut HPETState =
unsafe { NonNull::new(opaque.cast::<HPETState>()).unwrap().as_mut() };
state.pre_save() as c_int
}
unsafe extern "C" fn hpet_post_load(opaque: *mut c_void, version_id: c_int) -> c_int {
// SAFETY:
// the pointer is convertible to a reference
let state: &mut HPETState =
unsafe { NonNull::new(opaque.cast::<HPETState>()).unwrap().as_mut() };
let version: u8 = version_id.try_into().unwrap();
state.post_load(version) as c_int
}
static VMSTATE_HPET_RTC_IRQ_LEVEL: VMStateDescription = VMStateDescription {
name: c_str!("hpet/rtc_irq_level").as_ptr(),
version_id: 1,
minimum_version_id: 1,
needed: Some(hpet_rtc_irq_level_needed),
fields: vmstate_fields! {
vmstate_of!(HPETState, rtc_irq_level),
},
..Zeroable::ZERO
};
static VMSTATE_HPET_OFFSET: VMStateDescription = VMStateDescription {
name: c_str!("hpet/offset").as_ptr(),
version_id: 1,
minimum_version_id: 1,
needed: Some(hpet_offset_needed),
fields: vmstate_fields! {
vmstate_of!(HPETState, hpet_offset),
},
..Zeroable::ZERO
};
static VMSTATE_HPET_TIMER: VMStateDescription = VMStateDescription {
name: c_str!("hpet_timer").as_ptr(),
version_id: 1,
minimum_version_id: 1,
fields: vmstate_fields! {
vmstate_of!(HPETTimer, index),
vmstate_of!(HPETTimer, config),
vmstate_of!(HPETTimer, cmp),
vmstate_of!(HPETTimer, fsb),
vmstate_of!(HPETTimer, period),
vmstate_of!(HPETTimer, wrap_flag),
vmstate_of!(HPETTimer, qemu_timer),
},
..Zeroable::ZERO
};
const VALIDATE_TIMERS_NAME: &CStr = c_str!("num_timers must match");
static VMSTATE_HPET: VMStateDescription = VMStateDescription {
name: c_str!("hpet").as_ptr(),
version_id: 2,
minimum_version_id: 1,
pre_save: Some(hpet_pre_save),
post_load: Some(hpet_post_load),
fields: vmstate_fields! {
vmstate_of!(HPETState, config),
vmstate_of!(HPETState, int_status),
vmstate_of!(HPETState, counter),
vmstate_of!(HPETState, num_timers_save).with_version_id(2),
vmstate_validate!(HPETState, VALIDATE_TIMERS_NAME, HPETState::validate_num_timers),
vmstate_struct!(HPETState, timers[0 .. num_timers], &VMSTATE_HPET_TIMER, BqlRefCell<HPETTimer>, HPETState::validate_num_timers).with_version_id(0),
},
subsections: vmstate_subsections! {
VMSTATE_HPET_RTC_IRQ_LEVEL,
VMSTATE_HPET_OFFSET,
},
..Zeroable::ZERO
};
impl DeviceImpl for HPETState {
fn properties() -> &'static [Property] {
&HPET_PROPERTIES
}
fn vmsd() -> Option<&'static VMStateDescription> {
Some(&VMSTATE_HPET)
}
const REALIZE: Option<fn(&Self)> = Some(Self::realize);
}

View file

@ -1,15 +1,16 @@
[package]
name = "qemu_api_macros"
version = "0.1.0"
edition = "2021"
authors = ["Manos Pitsidianakis <manos.pitsidianakis@linaro.org>"]
license = "GPL-2.0-or-later"
description = "Rust bindings for QEMU - Utility macros"
resolver = "2"
publish = false
keywords = []
categories = []
rust-version = "1.63.0"
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[lib]
proc-macro = true

View file

@ -1,18 +1,17 @@
[package]
name = "qemu_api"
version = "0.1.0"
edition = "2021"
authors = ["Manos Pitsidianakis <manos.pitsidianakis@linaro.org>"]
license = "GPL-2.0-or-later"
readme = "README.md"
homepage = "https://www.qemu.org"
description = "Rust bindings for QEMU"
repository = "https://gitlab.com/qemu-project/qemu/"
readme = "README.md"
resolver = "2"
publish = false
keywords = []
categories = []
rust-version = "1.63.0"
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[dependencies]
qemu_api_macros = { path = "../qemu-api-macros" }

View file

@ -78,33 +78,26 @@ macro_rules! assert_same_type {
/// ```
#[macro_export]
macro_rules! assert_field_type {
($t:ty, $i:tt, $ti:ty) => {
(@internal $param_name:ident, $ti:ty, $t:ty, $($field:tt)*) => {
const _: () = {
#[allow(unused)]
fn assert_field_type(v: $t) {
fn types_must_be_equal<T, U>(_: T)
fn assert_field_type($param_name: &$t) {
fn types_must_be_equal<T, U>(_: &T)
where
T: $crate::assertions::EqType<Itself = U>,
{
}
types_must_be_equal::<_, $ti>(v.$i);
types_must_be_equal::<_, $ti>(&$($field)*);
}
};
};
($t:ty, $i:tt, $ti:ty) => {
$crate::assert_field_type!(@internal v, $ti, $t, v.$i);
};
($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]);
}
};
$crate::assert_field_type!(@internal v, $ti, $t, v.$i[0]);
};
}

View file

@ -121,3 +121,5 @@ impl ClockType {
pub const CLOCK_VIRTUAL: ClockType = ClockType {
id: QEMUClockType::QEMU_CLOCK_VIRTUAL,
};
pub const NANOSECONDS_PER_SECOND: u64 = 1000000000;

View file

@ -200,13 +200,14 @@ pub const fn vmstate_varray_flag<T: VMState>(_: PhantomData<T>) -> VMStateFlags
/// and [`impl_vmstate_forward!`](crate::impl_vmstate_forward) help with this.
#[macro_export]
macro_rules! vmstate_of {
($struct_name:ty, $field_name:ident $([0 .. $num:ident $(* $factor:expr)?])? $(,)?) => {
($struct_name:ty, $field_name:ident $([0 .. $num:ident $(* $factor:expr)?])? $(, $test_fn:expr)? $(,)?) => {
$crate::bindings::VMStateField {
name: ::core::concat!(::core::stringify!($field_name), "\0")
.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),)?
$(field_exists: $crate::vmstate_exist_fn!($struct_name, $test_fn),)?
// 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!(
@ -435,6 +436,38 @@ macro_rules! vmstate_unused {
}};
}
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 {
// SAFETY: the opaque was passed as a reference to `T`.
let owner: &T = unsafe { &*(opaque.cast::<T>()) };
let version: u8 = version_id.try_into().unwrap();
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;
#[macro_export]
macro_rules! vmstate_exist_fn {
($struct_name:ty, $test_fn:expr) => {{
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)))
}};
}
// FIXME: including the `vmsd` field in a `const` is not possible without
// the const_refs_static feature (stabilized in Rust 1.83.0). Without it,
// it is not possible to use VMS_STRUCT in a transparent manner using
@ -445,7 +478,7 @@ macro_rules! vmstate_unused {
#[doc(alias = "VMSTATE_STRUCT")]
#[macro_export]
macro_rules! vmstate_struct {
($struct_name:ty, $field_name:ident $([0 .. $num:ident $(* $factor:expr)?])?, $vmsd:expr, $type:ty $(,)?) => {
($struct_name:ty, $field_name:ident $([0 .. $num:ident $(* $factor:expr)?])?, $vmsd:expr, $type:ty $(, $test_fn:expr)? $(,)?) => {
$crate::bindings::VMStateField {
name: ::core::concat!(::core::stringify!($field_name), "\0")
.as_bytes()
@ -458,6 +491,7 @@ macro_rules! vmstate_struct {
size: ::core::mem::size_of::<$type>(),
flags: $crate::bindings::VMStateFlags::VMS_STRUCT,
vmsd: $vmsd,
$(field_exists: $crate::vmstate_exist_fn!($struct_name, $test_fn),)?
..$crate::zeroable::Zeroable::ZERO
} $(.with_varray_flag_unchecked(
$crate::call_func_with_field!(
@ -473,7 +507,7 @@ macro_rules! vmstate_struct {
#[doc(alias = "VMSTATE_CLOCK")]
#[macro_export]
macro_rules! vmstate_clock {
($struct_name:ty, $field_name:ident) => {{
($struct_name:ty, $field_name:ident $([0 .. $num:ident $(* $factor:expr)?])?) => {{
$crate::bindings::VMStateField {
name: ::core::concat!(::core::stringify!($field_name), "\0")
.as_bytes()
@ -482,7 +516,7 @@ macro_rules! vmstate_clock {
$crate::assert_field_type!(
$struct_name,
$field_name,
$crate::qom::Owned<$crate::qdev::Clock>
$crate::qom::Owned<$crate::qdev::Clock> $(, num = $num)?
);
$crate::offset_of!($struct_name, $field_name)
},
@ -493,7 +527,14 @@ macro_rules! vmstate_clock {
),
vmsd: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_clock) },
..$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))?)?
}};
}
@ -514,43 +555,13 @@ 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)))
},
field_exists: $crate::vmstate_exist_fn!($struct_name, $test_fn),
flags: $crate::bindings::VMStateFlags(
$crate::bindings::VMStateFlags::VMS_MUST_EXIST.0
| $crate::bindings::VMStateFlags::VMS_ARRAY.0,

View file

@ -28,7 +28,7 @@ const FOO_ARRAY_MAX: usize = 3;
// - VMSTATE_VARRAY_UINT16_UNSAFE
// - VMSTATE_VARRAY_MULTIPLY
#[repr(C)]
#[derive(qemu_api_macros::offsets)]
#[derive(Default, qemu_api_macros::offsets)]
struct FooA {
arr: [u8; FOO_ARRAY_MAX],
num: u16,
@ -147,8 +147,9 @@ fn test_vmstate_varray_multiply() {
// - VMSTATE_STRUCT_VARRAY_UINT8
// - (no C version) MULTIPLY variant of VMSTATE_STRUCT_VARRAY_UINT32
// - VMSTATE_ARRAY
// - VMSTATE_STRUCT_VARRAY_UINT8 with BqlCell wrapper & test_fn
#[repr(C)]
#[derive(qemu_api_macros::offsets)]
#[derive(Default, qemu_api_macros::offsets)]
struct FooB {
arr_a: [FooA; FOO_ARRAY_MAX],
num_a: u8,
@ -158,6 +159,12 @@ struct FooB {
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],
arr_a_wrap: [FooA; FOO_ARRAY_MAX],
num_a_wrap: BqlCell<u32>,
}
fn validate_foob(_state: &FooB, _version_id: u8) -> bool {
true
}
static VMSTATE_FOOB: VMStateDescription = VMStateDescription {
@ -170,13 +177,14 @@ static VMSTATE_FOOB: VMStateDescription = VMStateDescription {
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),
vmstate_struct!(FooB, arr_a_wrap[0 .. num_a_wrap], &VMSTATE_FOOA, FooA, validate_foob),
},
..Zeroable::ZERO
};
#[test]
fn test_vmstate_bool_v() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 6) };
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) };
// 1st VMStateField ("val") in VMSTATE_FOOB (corresponding to VMSTATE_BOOL_V)
assert_eq!(
@ -196,7 +204,7 @@ fn test_vmstate_bool_v() {
#[test]
fn test_vmstate_uint64() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 6) };
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) };
// 2nd VMStateField ("wrap") in VMSTATE_FOOB (corresponding to VMSTATE_U64)
assert_eq!(
@ -216,7 +224,7 @@ fn test_vmstate_uint64() {
#[test]
fn test_vmstate_struct_varray_uint8() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 6) };
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) };
// 3rd VMStateField ("arr_a") in VMSTATE_FOOB (corresponding to
// VMSTATE_STRUCT_VARRAY_UINT8)
@ -240,7 +248,7 @@ fn test_vmstate_struct_varray_uint8() {
#[test]
fn test_vmstate_struct_varray_uint32_multiply() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 6) };
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) };
// 4th VMStateField ("arr_a_mul") in VMSTATE_FOOB (corresponding to
// (no C version) MULTIPLY variant of VMSTATE_STRUCT_VARRAY_UINT32)
@ -266,7 +274,7 @@ fn test_vmstate_struct_varray_uint32_multiply() {
#[test]
fn test_vmstate_macro_array() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 6) };
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) };
// 5th VMStateField ("arr_i64") in VMSTATE_FOOB (corresponding to
// VMSTATE_ARRAY)
@ -283,9 +291,26 @@ fn test_vmstate_macro_array() {
assert_eq!(foo_fields[4].flags, VMStateFlags::VMS_ARRAY);
assert!(foo_fields[4].vmsd.is_null());
assert!(foo_fields[4].field_exists.is_none());
}
#[test]
fn test_vmstate_struct_varray_uint8_wrapper() {
let foo_fields: &[VMStateField] = unsafe { slice::from_raw_parts(VMSTATE_FOOB.fields, 7) };
let mut foo_b: FooB = Default::default();
let foo_b_p = std::ptr::addr_of_mut!(foo_b).cast::<c_void>();
// 6th VMStateField ("arr_a_wrap") in VMSTATE_FOOB (corresponding to
// VMSTATE_STRUCT_VARRAY_UINT8). Other fields are checked in
// test_vmstate_struct_varray_uint8.
assert_eq!(
unsafe { CStr::from_ptr(foo_fields[5].name) }.to_bytes_with_nul(),
b"arr_a_wrap\0"
);
assert_eq!(foo_fields[5].num_offset, 228);
assert!(unsafe { foo_fields[5].field_exists.unwrap()(foo_b_p, 0) });
// The last VMStateField in VMSTATE_FOOB.
assert_eq!(foo_fields[5].flags, VMStateFlags::VMS_END);
assert_eq!(foo_fields[6].flags, VMStateFlags::VMS_END);
}
// =========================== Test VMSTATE_FOOC ===========================

View file

@ -1408,7 +1408,7 @@ struct decode_tbl _2op_inst[] = {
};
struct decode_x87_tbl invl_inst_x87 = {0x0, 0, 0, 0, 0, false, false, NULL,
NULL, decode_invalid, 0};
NULL, decode_invalid};
struct decode_x87_tbl _x87_inst[] = {
{0xd8, 0, 3, X86_DECODE_CMD_FADD, 10, false, false,
@ -1456,8 +1456,7 @@ struct decode_x87_tbl _x87_inst[] = {
decode_x87_modrm_st0, NULL, decode_d9_4},
{0xd9, 4, 0, X86_DECODE_CMD_INVL, 4, false, false,
decode_x87_modrm_bytep, NULL, NULL},
{0xd9, 5, 3, X86_DECODE_CMD_FLDxx, 10, false, false, NULL, NULL, NULL,
RFLAGS_MASK_NONE},
{0xd9, 5, 3, X86_DECODE_CMD_FLDxx, 10, false, false, NULL, NULL, NULL},
{0xd9, 5, 0, X86_DECODE_CMD_FLDCW, 2, false, false,
decode_x87_modrm_bytep, NULL, NULL},
@ -1478,20 +1477,17 @@ struct decode_x87_tbl _x87_inst[] = {
decode_x87_modrm_st0, NULL},
{0xda, 3, 3, X86_DECODE_CMD_FCMOV, 10, false, false, decode_x87_modrm_st0,
decode_x87_modrm_st0, NULL},
{0xda, 4, 3, X86_DECODE_CMD_INVL, 10, false, false, NULL, NULL, NULL,
RFLAGS_MASK_NONE},
{0xda, 4, 3, X86_DECODE_CMD_INVL, 10, false, false, NULL, NULL, NULL},
{0xda, 4, 0, X86_DECODE_CMD_FSUB, 4, false, false, decode_x87_modrm_st0,
decode_x87_modrm_intp, NULL},
{0xda, 5, 3, X86_DECODE_CMD_FUCOM, 10, false, true, decode_x87_modrm_st0,
decode_decode_x87_modrm_st0, NULL},
{0xda, 5, 0, X86_DECODE_CMD_FSUB, 4, true, false, decode_x87_modrm_st0,
decode_x87_modrm_intp, NULL},
{0xda, 6, 3, X86_DECODE_CMD_INVL, 10, false, false, NULL, NULL, NULL,
RFLAGS_MASK_NONE},
{0xda, 6, 3, X86_DECODE_CMD_INVL, 10, false, false, NULL, NULL, NULL},
{0xda, 6, 0, X86_DECODE_CMD_FDIV, 4, false, false, decode_x87_modrm_st0,
decode_x87_modrm_intp, NULL},
{0xda, 7, 3, X86_DECODE_CMD_INVL, 10, false, false, NULL, NULL, NULL,
RFLAGS_MASK_NONE},
{0xda, 7, 3, X86_DECODE_CMD_INVL, 10, false, false, NULL, NULL, NULL},
{0xda, 7, 0, X86_DECODE_CMD_FDIV, 4, true, false, decode_x87_modrm_st0,
decode_x87_modrm_intp, NULL},
@ -1511,8 +1507,7 @@ struct decode_x87_tbl _x87_inst[] = {
decode_x87_modrm_intp, NULL, NULL},
{0xdb, 4, 3, X86_DECODE_CMD_INVL, 10, false, false, NULL, NULL,
decode_db_4},
{0xdb, 4, 0, X86_DECODE_CMD_INVL, 10, false, false, NULL, NULL, NULL,
RFLAGS_MASK_NONE},
{0xdb, 4, 0, X86_DECODE_CMD_INVL, 10, false, false, NULL, NULL, NULL},
{0xdb, 5, 3, X86_DECODE_CMD_FUCOMI, 10, false, false,
decode_x87_modrm_st0, decode_x87_modrm_st0, NULL},
{0xdb, 5, 0, X86_DECODE_CMD_FLD, 10, false, false,

View file

@ -76,6 +76,7 @@
#include "qemu/main-loop.h"
#include "qemu/accel.h"
#include "target/i386/cpu.h"
#include "exec/target_page.h"
static Error *invtsc_mig_blocker;

View file

@ -342,7 +342,7 @@ static void gen_writeback(DisasContext *s, X86DecodedInsn *decode, int opn, TCGv
break;
case X86_OP_SEG:
/* Note that gen_movl_seg takes care of interrupt shadow and TF. */
gen_movl_seg(s, op->n, s->T0);
gen_movl_seg(s, op->n, v, op->n == R_SS);
break;
case X86_OP_INT:
if (op->has_ea) {
@ -2382,7 +2382,7 @@ static void gen_lxx_seg(DisasContext *s, X86DecodedInsn *decode, int seg)
gen_op_ld_v(s, MO_16, s->T1, s->A0);
/* load the segment here to handle exceptions properly */
gen_movl_seg(s, seg, s->T1);
gen_movl_seg(s, seg, s->T1, false);
}
static void gen_LDS(DisasContext *s, X86DecodedInsn *decode)

View file

@ -2026,27 +2026,36 @@ static void gen_op_movl_seg_real(DisasContext *s, X86Seg seg_reg, TCGv seg)
/* move SRC to seg_reg and compute if the CPU state may change. Never
call this function with seg_reg == R_CS */
static void gen_movl_seg(DisasContext *s, X86Seg seg_reg, TCGv src)
static void gen_movl_seg(DisasContext *s, X86Seg seg_reg, TCGv src, bool inhibit_irq)
{
if (PE(s) && !VM86(s)) {
TCGv_i32 sel = tcg_temp_new_i32();
tcg_gen_trunc_tl_i32(sel, src);
gen_helper_load_seg(tcg_env, tcg_constant_i32(seg_reg), sel);
/* abort translation because the addseg value may change or
because ss32 may change. For R_SS, translation must always
stop as a special handling must be done to disable hardware
interrupts for the next instruction */
if (seg_reg == R_SS) {
s->base.is_jmp = DISAS_EOB_INHIBIT_IRQ;
} else if (CODE32(s) && seg_reg < R_FS) {
/* For move to DS/ES/SS, the addseg or ss32 flags may change. */
if (CODE32(s) && seg_reg < R_FS) {
s->base.is_jmp = DISAS_EOB_NEXT;
}
} else {
gen_op_movl_seg_real(s, seg_reg, src);
if (seg_reg == R_SS) {
s->base.is_jmp = DISAS_EOB_INHIBIT_IRQ;
}
}
/*
* For MOV or POP to SS (but not LSS) translation must always
* stop as a special handling must be done to disable hardware
* interrupts for the next instruction.
*
* This is the last instruction, so it's okay to overwrite
* HF_TF_MASK; the next TB will start with the flag set.
*
* DISAS_EOB_INHIBIT_IRQ is a superset of DISAS_EOB_NEXT which
* might have been set above.
*/
if (inhibit_irq) {
s->base.is_jmp = DISAS_EOB_INHIBIT_IRQ;
s->flags &= ~HF_TF_MASK;
}
}
@ -2297,7 +2306,7 @@ gen_eob(DisasContext *s, int mode)
if (mode == DISAS_EOB_RECHECK_TF) {
gen_helper_rechecking_single_step(tcg_env);
tcg_gen_exit_tb(NULL, 0);
} else if ((s->flags & HF_TF_MASK) && mode != DISAS_EOB_INHIBIT_IRQ) {
} else if (s->flags & HF_TF_MASK) {
gen_helper_single_step(tcg_env);
} else if (mode == DISAS_JUMP &&
/* give irqs a chance to happen */