* rust: more qdev bindings

* rust: HPET device model with timer and GPIO bindings
 * rust: small cleanups and fixes; run doctests during CI
 * ui/sdl2: reenable the SDL2 Windows keyboard hook procedure
 -----BEGIN PGP SIGNATURE-----
 
 iQFIBAABCAAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmet6qkUHHBib256aW5p
 QHJlZGhhdC5jb20ACgkQv/vSX3jHroO4yQgAjSpJ8DChoEVrm6xgCUGPkC7VlI0A
 3WimcgiTUCUVqiywvLmObHRv9ld/b9mJ+2v/actDy39qioN3i3+RGpyeSRcysITd
 2AWQVOe6JuVfEyN+ihYq3yS3v1meDhzZbOzRNHgbTX20rMy/HWJFIvQbK4abQaVI
 j8zaPYIjcfcH/ScEmmha88l6PJDMPy7fCEzQWx41oHKkQ8w4rhmarn9f3WcXB/SN
 bCvm2NmkJFPsU/TCynWz7YSjrLWCsWjiDgxoDD1295QoeEvfcuD8Z6vPIA9BttGx
 MUgcrXi4KnJI8W9gm5jAiKq+DSxFX6f7AwUDfb2l+Vrkq84s7bu9UVNQqA==
 =/vpW
 -----END PGP SIGNATURE-----

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

* rust: more qdev bindings
* rust: HPET device model with timer and GPIO bindings
* rust: small cleanups and fixes; run doctests during CI
* ui/sdl2: reenable the SDL2 Windows keyboard hook procedure

# -----BEGIN PGP SIGNATURE-----
#
# iQFIBAABCAAyFiEE8TM4V0tmI4mGbHaCv/vSX3jHroMFAmet6qkUHHBib256aW5p
# QHJlZGhhdC5jb20ACgkQv/vSX3jHroO4yQgAjSpJ8DChoEVrm6xgCUGPkC7VlI0A
# 3WimcgiTUCUVqiywvLmObHRv9ld/b9mJ+2v/actDy39qioN3i3+RGpyeSRcysITd
# 2AWQVOe6JuVfEyN+ihYq3yS3v1meDhzZbOzRNHgbTX20rMy/HWJFIvQbK4abQaVI
# j8zaPYIjcfcH/ScEmmha88l6PJDMPy7fCEzQWx41oHKkQ8w4rhmarn9f3WcXB/SN
# bCvm2NmkJFPsU/TCynWz7YSjrLWCsWjiDgxoDD1295QoeEvfcuD8Z6vPIA9BttGx
# MUgcrXi4KnJI8W9gm5jAiKq+DSxFX6f7AwUDfb2l+Vrkq84s7bu9UVNQqA==
# =/vpW
# -----END PGP SIGNATURE-----
# gpg: Signature made Thu 13 Feb 2025 07:50:49 EST
# 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: (27 commits)
  ui/sdl2: reenable the SDL2 Windows keyboard hook procedure
  rust: fix doctests
  rust: vmstate: remove redundant link targets
  rust: qemu_api: add a documentation header for all modules
  i386: enable rust hpet for pc when rust is enabled
  rust/timer/hpet: add qom and qdev APIs support
  rust/timer/hpet: add basic HPET timer and HPETState
  rust/timer/hpet: define hpet_fw_cfg
  rust: add bindings for timer
  rust: add bindings for memattrs
  rust: add bindings for gpio_{in|out} initialization
  rust/irq: Add a helper to convert [InterruptSource] to pointer
  rust/qdev: add the macro to define bit property
  i386/fw_cfg: move hpet_cfg definition to hpet.c
  rust: pl011: convert pl011_create to safe Rust
  rust: chardev, qdev: add bindings to qdev_prop_set_chr
  rust: irq: define ObjectType for IRQState
  rust: bindings for MemoryRegionOps
  rust: bindings: add Send and Sync markers for types that have bindings
  rust: qdev: switch from legacy reset to Resettable
  ...

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi 2025-02-14 08:18:56 -05:00
commit b4b0880c3a
45 changed files with 2232 additions and 204 deletions

View File

@ -131,6 +131,12 @@ build-system-fedora-rust-nightly:
CONFIGURE_ARGS: --disable-docs --enable-rust --enable-strict-rust-lints
TARGETS: aarch64-softmmu
MAKE_CHECK_ARGS: check-build
after_script:
- source scripts/ci/gitlab-ci-section
- section_start test "Running Rust doctests"
- cd build
- pyvenv/bin/meson devenv -w ../rust ${CARGO-cargo} test --doc -p qemu_api
allow_failure: true
check-system-fedora:

View File

@ -6,6 +6,7 @@
#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

@ -180,11 +180,13 @@ module status
``cell`` stable
``c_str`` complete
``irq`` complete
``memory`` stable
``module`` complete
``offset_of`` stable
``qdev`` stable
``qom`` stable
``sysbus`` stable
``timer`` stable
``vmstate`` proof of concept
``zeroable`` stable
================ ======================
@ -194,6 +196,50 @@ module status
interface either. Also, ``unsafe`` interfaces may be replaced by safe interfaces
later.
Naming convention
'''''''''''''''''
C function names usually are prefixed according to the data type that they
apply to, for example ``timer_mod`` or ``sysbus_connect_irq``. Furthermore,
both function and structs sometimes have a ``qemu_`` or ``QEMU`` prefix.
Generally speaking, these are all removed in the corresponding Rust functions:
``QEMUTimer`` becomes ``timer::Timer``, ``timer_mod`` becomes ``Timer::modify``,
``sysbus_connect_irq`` becomes ``SysBusDeviceMethods::connect_irq``.
Sometimes however a name appears multiple times in the QOM class hierarchy,
and the only difference is in the prefix. An example is ``qdev_realize`` and
``sysbus_realize``. In such cases, whenever a name is not unique in
the hierarchy, always add the prefix to the classes that are lower in
the hierarchy; for the top class, decide on a case by case basis.
For example:
========================== =========================================
``device_cold_reset()`` ``DeviceMethods::cold_reset()``
``pci_device_reset()`` ``PciDeviceMethods::pci_device_reset()``
``pci_bridge_reset()`` ``PciBridgeMethods::pci_bridge_reset()``
========================== =========================================
Here, the name is not exactly the same, but nevertheless ``PciDeviceMethods``
adds the prefix to avoid confusion, because the functionality of
``device_cold_reset()`` and ``pci_device_reset()`` is subtly different.
In this case, however, no prefix is needed:
========================== =========================================
``device_realize()`` ``DeviceMethods::realize()``
``sysbus_realize()`` ``SysbusDeviceMethods::sysbus_realize()``
``pci_realize()`` ``PciDeviceMethods::pci_realize()``
========================== =========================================
Here, the lower classes do not add any functionality, and mostly
provide extra compile-time checking; the basic *realize* functionality
is the same for all devices. Therefore, ``DeviceMethods`` does not
add the prefix.
Whenever a name is unique in the hierarchy, instead, you should
always remove the class name prefix.
Common pitfalls
'''''''''''''''

View File

@ -26,7 +26,9 @@
#include CONFIG_DEVICES
#include "target/i386/cpu.h"
struct hpet_fw_config hpet_cfg = {.count = UINT8_MAX};
#if !defined(CONFIG_HPET) && !defined(CONFIG_X_HPET_RUST)
struct hpet_fw_config hpet_fw_cfg = {.count = UINT8_MAX};
#endif
const char *fw_cfg_arch_key_name(uint16_t key)
{
@ -149,7 +151,7 @@ FWCfgState *fw_cfg_arch_create(MachineState *ms,
#endif
fw_cfg_add_i32(fw_cfg, FW_CFG_IRQ0_OVERRIDE, 1);
fw_cfg_add_bytes(fw_cfg, FW_CFG_HPET, &hpet_cfg, sizeof(hpet_cfg));
fw_cfg_add_bytes(fw_cfg, FW_CFG_HPET, &hpet_fw_cfg, sizeof(hpet_fw_cfg));
/* allocate memory for the NUMA channel: one (64bit) word for the number
* of nodes, one word for each VCPU->node and one word for each node to
* hold the amount of memory.

View File

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

View File

@ -11,7 +11,7 @@ config A9_GTIMER
config HPET
bool
default y if PC
default y if PC && !HAVE_RUST
config I8254
bool

View File

@ -40,6 +40,8 @@
#include "qom/object.h"
#include "trace.h"
struct hpet_fw_config hpet_fw_cfg = {.count = UINT8_MAX};
#define HPET_MSI_SUPPORT 0
OBJECT_DECLARE_SIMPLE_TYPE(HPETState, HPET)
@ -278,7 +280,7 @@ static int hpet_post_load(void *opaque, int version_id)
/* Push number of timers into capability returned via HPET_ID */
s->capability &= ~HPET_ID_NUM_TIM_MASK;
s->capability |= (s->num_timers - 1) << HPET_ID_NUM_TIM_SHIFT;
hpet_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability;
hpet_fw_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability;
/* Derive HPET_MSI_SUPPORT from the capability of the first timer. */
s->flags &= ~(1 << HPET_MSI_SUPPORT);
@ -665,8 +667,8 @@ static void hpet_reset(DeviceState *d)
s->hpet_counter = 0ULL;
s->hpet_offset = 0ULL;
s->config = 0ULL;
hpet_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability;
hpet_cfg.hpet[s->hpet_id].address = sbd->mmio[0].addr;
hpet_fw_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability;
hpet_fw_cfg.hpet[s->hpet_id].address = sbd->mmio[0].addr;
/* to document that the RTC lowers its output on reset as well */
s->rtc_irq_level = 0;
@ -708,17 +710,17 @@ static void hpet_realize(DeviceState *dev, Error **errp)
if (!s->intcap) {
warn_report("Hpet's intcap not initialized");
}
if (hpet_cfg.count == UINT8_MAX) {
if (hpet_fw_cfg.count == UINT8_MAX) {
/* first instance */
hpet_cfg.count = 0;
hpet_fw_cfg.count = 0;
}
if (hpet_cfg.count == 8) {
if (hpet_fw_cfg.count == 8) {
error_setg(errp, "Only 8 instances of HPET is allowed");
return;
}
s->hpet_id = hpet_cfg.count++;
s->hpet_id = hpet_fw_cfg.count++;
for (i = 0; i < HPET_NUM_IRQ_ROUTES; i++) {
sysbus_init_irq(sbd, &s->irqs[i]);

View File

@ -73,7 +73,7 @@ struct hpet_fw_config
struct hpet_fw_entry hpet[8];
} QEMU_PACKED;
extern struct hpet_fw_config hpet_cfg;
extern struct hpet_fw_config hpet_fw_cfg;
#define TYPE_HPET "hpet"

View File

@ -4073,6 +4073,7 @@ if have_rust
'MigrationPriority',
'QEMUChrEvent',
'QEMUClockType',
'ResetType',
'device_endian',
'module_init_type',
]
@ -4086,6 +4087,13 @@ if have_rust
foreach enum : c_bitfields
bindgen_args += ['--bitfield-enum', enum]
endforeach
c_nocopy = [
'QEMUTimer',
]
# Used to customize Drop trait
foreach struct : c_nocopy
bindgen_args += ['--no-copy', struct]
endforeach
# TODO: Remove this comment when the clang/libclang mismatch issue is solved.
#

8
rust/Cargo.lock generated
View File

@ -37,6 +37,14 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
[[package]]
name = "hpet"
version = "0.1.0"
dependencies = [
"qemu_api",
"qemu_api_macros",
]
[[package]]
name = "itertools"
version = "0.11.0"

View File

@ -4,6 +4,7 @@ members = [
"qemu-api-macros",
"qemu-api",
"hw/char/pl011",
"hw/timer/hpet",
]
[workspace.lints.rust]

View File

@ -1,2 +1,3 @@
# devices Kconfig
source char/Kconfig
source timer/Kconfig

View File

@ -10,24 +10,22 @@ use std::{
use qemu_api::{
bindings::{
error_fatal, hwaddr, memory_region_init_io, qdev_init_clock_in, qdev_new,
qdev_prop_set_chr, qemu_chr_fe_accept_input, qemu_chr_fe_ioctl, qemu_chr_fe_set_handlers,
qemu_chr_fe_write_all, qemu_irq, sysbus_connect_irq, sysbus_mmio_map,
sysbus_realize_and_unref, CharBackend, Chardev, Clock, ClockEvent, MemoryRegion,
QEMUChrEvent, CHR_IOCTL_SERIAL_SET_BREAK,
qemu_chr_fe_accept_input, qemu_chr_fe_ioctl, qemu_chr_fe_set_handlers,
qemu_chr_fe_write_all, CharBackend, QEMUChrEvent, CHR_IOCTL_SERIAL_SET_BREAK,
},
c_str, impl_vmstate_forward,
irq::InterruptSource,
chardev::Chardev,
impl_vmstate_forward,
irq::{IRQState, InterruptSource},
memory::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder},
prelude::*,
qdev::{DeviceImpl, DeviceState, Property},
qom::{ClassInitImpl, ObjectImpl, ParentField},
qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl},
qom::{ClassInitImpl, ObjectImpl, Owned, ParentField},
sysbus::{SysBusDevice, SysBusDeviceClass},
vmstate::VMStateDescription,
};
use crate::{
device_class,
memory_ops::PL011_OPS,
registers::{self, Interrupt},
RegisterOffset,
};
@ -131,7 +129,7 @@ pub struct PL011State {
#[doc(alias = "irq")]
pub interrupts: [InterruptSource; IRQMASK.len()],
#[doc(alias = "clk")]
pub clock: NonNull<Clock>,
pub clock: Owned<Clock>,
#[doc(alias = "migrate_clk")]
pub migrate_clock: bool,
}
@ -172,7 +170,10 @@ impl DeviceImpl for PL011State {
Some(&device_class::VMSTATE_PL011)
}
const REALIZE: Option<fn(&Self)> = Some(Self::realize);
const RESET: Option<fn(&Self)> = Some(Self::reset);
}
impl ResettablePhasesImpl for PL011State {
const HOLD: Option<fn(&Self, ResetType)> = Some(Self::reset_hold);
}
impl PL011Registers {
@ -485,43 +486,39 @@ impl PL011State {
/// location/instance. All its fields are expected to hold unitialized
/// values with the sole exception of `parent_obj`.
unsafe fn init(&mut self) {
const CLK_NAME: &CStr = c_str!("clk");
static PL011_OPS: MemoryRegionOps<PL011State> = MemoryRegionOpsBuilder::<PL011State>::new()
.read(&PL011State::read)
.write(&PL011State::write)
.native_endian()
.impl_sizes(4, 4)
.build();
// SAFETY:
//
// self and self.iomem are guaranteed to be valid at this point since callers
// must make sure the `self` reference is valid.
unsafe {
memory_region_init_io(
addr_of_mut!(self.iomem),
addr_of_mut!(*self).cast::<Object>(),
&PL011_OPS,
addr_of_mut!(*self).cast::<c_void>(),
Self::TYPE_NAME.as_ptr(),
0x1000,
);
}
MemoryRegion::init_io(
unsafe { &mut *addr_of_mut!(self.iomem) },
addr_of_mut!(*self),
&PL011_OPS,
"pl011",
0x1000,
);
self.regs = Default::default();
// SAFETY:
//
// self.clock is not initialized at this point; but since `NonNull<_>` is Copy,
// we can overwrite the undefined value without side effects. This is
// safe since all PL011State instances are created by QOM code which
// calls this function to initialize the fields; therefore no code is
// able to access an invalid self.clock value.
unsafe {
let dev: &mut DeviceState = self.upcast_mut();
self.clock = NonNull::new(qdev_init_clock_in(
dev,
CLK_NAME.as_ptr(),
None, /* pl011_clock_update */
addr_of_mut!(*self).cast::<c_void>(),
ClockEvent::ClockUpdate.0,
))
.unwrap();
}
// self.clock is not initialized at this point; but since `Owned<_>` is
// not Drop, we can overwrite the undefined value without side effects;
// it's not sound but, because for all PL011State instances are created
// by QOM code which calls this function to initialize the fields, at
// leastno code is able to access an invalid self.clock value.
self.clock = self.init_clock_in("clk", &Self::clock_update, ClockEvent::ClockUpdate);
}
const fn clock_update(&self, _event: ClockEvent) {
/* pl011_trace_baudrate_change(s); */
}
fn post_init(&self) {
@ -531,7 +528,7 @@ impl PL011State {
}
}
pub fn read(&mut self, offset: hwaddr, _size: u32) -> u64 {
pub fn read(&self, offset: hwaddr, _size: u32) -> u64 {
match RegisterOffset::try_from(offset) {
Err(v) if (0x3f8..0x400).contains(&(v >> 2)) => {
let device_id = self.get_class().device_id;
@ -546,7 +543,7 @@ impl PL011State {
if update_irq {
self.update();
unsafe {
qemu_chr_fe_accept_input(&mut self.char_backend);
qemu_chr_fe_accept_input(addr_of!(self.char_backend) as *mut _);
}
}
result.into()
@ -554,7 +551,7 @@ impl PL011State {
}
}
pub fn write(&mut self, offset: hwaddr, value: u64) {
pub fn write(&self, offset: hwaddr, value: u64, _size: u32) {
let mut update_irq = false;
if let Ok(field) = RegisterOffset::try_from(offset) {
// qemu_chr_fe_write_all() calls into the can_receive
@ -567,14 +564,15 @@ impl PL011State {
// XXX this blocks entire thread. Rewrite to use
// qemu_chr_fe_write and background I/O callbacks
unsafe {
qemu_chr_fe_write_all(&mut self.char_backend, &ch, 1);
qemu_chr_fe_write_all(addr_of!(self.char_backend) as *mut _, &ch, 1);
}
}
update_irq = self
.regs
.borrow_mut()
.write(field, value as u32, &mut self.char_backend);
update_irq = self.regs.borrow_mut().write(
field,
value as u32,
addr_of!(self.char_backend) as *mut _,
);
} else {
eprintln!("write bad offset {offset} value {value}");
}
@ -631,7 +629,7 @@ impl PL011State {
}
}
pub fn reset(&self) {
pub fn reset_hold(&self, _type: ResetType) {
self.regs.borrow_mut().reset();
}
@ -698,23 +696,27 @@ pub unsafe extern "C" fn pl011_event(opaque: *mut c_void, event: QEMUChrEvent) {
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer for `chr`.
/// We expect the FFI user of this function to pass a valid pointer for `chr`
/// and `irq`.
#[no_mangle]
pub unsafe extern "C" fn pl011_create(
addr: u64,
irq: qemu_irq,
irq: *mut IRQState,
chr: *mut Chardev,
) -> *mut DeviceState {
unsafe {
let dev: *mut DeviceState = qdev_new(PL011State::TYPE_NAME.as_ptr());
let sysbus: *mut SysBusDevice = dev.cast::<SysBusDevice>();
// SAFETY: The callers promise that they have owned references.
// They do not gift them to pl011_create, so use `Owned::from`.
let irq = unsafe { Owned::<IRQState>::from(&*irq) };
let chr = unsafe { Owned::<Chardev>::from(&*chr) };
qdev_prop_set_chr(dev, c_str!("chardev").as_ptr(), chr);
sysbus_realize_and_unref(sysbus, addr_of_mut!(error_fatal));
sysbus_mmio_map(sysbus, 0, addr);
sysbus_connect_irq(sysbus, 0, irq);
dev
}
let dev = PL011State::new();
dev.prop_set_chr("chardev", &chr);
dev.sysbus_realize();
dev.mmio_map(0, addr);
dev.connect_irq(0, &irq);
// The pointer is kept alive by the QOM tree; drop the owned ref
dev.as_mut_ptr()
}
#[repr(C)]
@ -743,3 +745,4 @@ impl ObjectImpl for PL011Luminary {
}
impl DeviceImpl for PL011Luminary {}
impl ResettablePhasesImpl for PL011Luminary {}

View File

@ -18,7 +18,6 @@ use qemu_api::c_str;
mod device;
mod device_class;
mod memory_ops;
pub use device::pl011_create;

View File

@ -1,34 +0,0 @@
// Copyright 2024, Linaro Limited
// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: GPL-2.0-or-later
use core::ptr::NonNull;
use std::os::raw::{c_uint, c_void};
use qemu_api::{bindings::*, zeroable::Zeroable};
use crate::device::PL011State;
pub static PL011_OPS: MemoryRegionOps = MemoryRegionOps {
read: Some(pl011_read),
write: Some(pl011_write),
read_with_attrs: None,
write_with_attrs: None,
endianness: device_endian::DEVICE_NATIVE_ENDIAN,
valid: Zeroable::ZERO,
impl_: MemoryRegionOps__bindgen_ty_2 {
min_access_size: 4,
max_access_size: 4,
..Zeroable::ZERO
},
};
unsafe extern "C" fn pl011_read(opaque: *mut c_void, addr: hwaddr, size: c_uint) -> u64 {
let mut state = NonNull::new(opaque).unwrap().cast::<PL011State>();
unsafe { state.as_mut() }.read(addr, size)
}
unsafe extern "C" fn pl011_write(opaque: *mut c_void, addr: hwaddr, data: u64, _size: c_uint) {
let mut state = NonNull::new(opaque).unwrap().cast::<PL011State>();
unsafe { state.as_mut() }.write(addr, data);
}

View File

@ -1 +1,2 @@
subdir('char')
subdir('timer')

2
rust/hw/timer/Kconfig Normal file
View File

@ -0,0 +1,2 @@
config X_HPET_RUST
bool

View File

@ -0,0 +1,18 @@
[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"
[lib]
crate-type = ["staticlib"]
[dependencies]
qemu_api = { path = "../../../qemu-api" }
qemu_api_macros = { path = "../../../qemu-api-macros" }
[lints]
workspace = true

View File

@ -0,0 +1,18 @@
_libhpet_rs = static_library(
'hpet',
files('src/lib.rs'),
override_options: ['rust_std=2021', 'build.rust_std=2021'],
rust_abi: 'rust',
dependencies: [
qemu_api,
qemu_api_macros,
],
)
rust_devices_ss.add(when: 'CONFIG_X_HPET_RUST', if_true: [declare_dependency(
link_whole: [_libhpet_rs],
# Putting proc macro crates in `dependencies` is necessary for Meson to find
# them when compiling the root per-target static rust lib.
dependencies: [qemu_api_macros],
variables: {'crate': 'hpet'},
)])

View File

@ -0,0 +1,69 @@
// Copyright (C) 2024 Intel Corporation.
// Author(s): Zhao Liu <zhai1.liu@intel.com>
// SPDX-License-Identifier: GPL-2.0-or-later
use std::ptr::addr_of_mut;
use qemu_api::{cell::bql_locked, impl_zeroable, zeroable::Zeroable};
/// Each `HPETState` represents a Event Timer Block. The v1 spec supports
/// up to 8 blocks. QEMU only uses 1 block (in PC machine).
const HPET_MAX_NUM_EVENT_TIMER_BLOCK: usize = 8;
#[repr(C, packed)]
#[derive(Copy, Clone, Default)]
pub struct HPETFwEntry {
pub event_timer_block_id: u32,
pub address: u64,
pub min_tick: u16,
pub page_prot: u8,
}
impl_zeroable!(HPETFwEntry);
#[repr(C, packed)]
#[derive(Copy, Clone, Default)]
pub struct HPETFwConfig {
pub count: u8,
pub hpet: [HPETFwEntry; HPET_MAX_NUM_EVENT_TIMER_BLOCK],
}
impl_zeroable!(HPETFwConfig);
#[allow(non_upper_case_globals)]
#[no_mangle]
pub static mut hpet_fw_cfg: HPETFwConfig = HPETFwConfig {
count: u8::MAX,
..Zeroable::ZERO
};
impl HPETFwConfig {
pub(crate) fn assign_hpet_id() -> usize {
assert!(bql_locked());
// SAFETY: all accesses go through these methods, which guarantee
// that the accesses are protected by the BQL.
let mut fw_cfg = unsafe { *addr_of_mut!(hpet_fw_cfg) };
if fw_cfg.count == u8::MAX {
// first instance
fw_cfg.count = 0;
}
if fw_cfg.count == 8 {
// TODO: Add error binding: error_setg()
panic!("Only 8 instances of HPET is allowed");
}
let id: usize = fw_cfg.count.into();
fw_cfg.count += 1;
id
}
pub(crate) fn update_hpet_cfg(hpet_id: usize, timer_block_id: u32, address: u64) {
assert!(bql_locked());
// SAFETY: all accesses go through these methods, which guarantee
// that the accesses are protected by the BQL.
let mut fw_cfg = unsafe { *addr_of_mut!(hpet_fw_cfg) };
fw_cfg.hpet[hpet_id].event_timer_block_id = timer_block_id;
fw_cfg.hpet[hpet_id].address = address;
}
}

View File

@ -0,0 +1,889 @@
// Copyright (C) 2024 Intel Corporation.
// Author(s): Zhao Liu <zhai1.liu@intel.com>
// SPDX-License-Identifier: GPL-2.0-or-later
use std::{
ffi::CStr,
ptr::{addr_of_mut, null_mut, NonNull},
slice::from_ref,
};
use qemu_api::{
bindings::{
address_space_memory, address_space_stl_le, qdev_prop_bit, qdev_prop_bool,
qdev_prop_uint32, qdev_prop_uint8,
},
c_str,
cell::{BqlCell, BqlRefCell},
irq::InterruptSource,
memory::{
hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder, MEMTXATTRS_UNSPECIFIED,
},
prelude::*,
qdev::{DeviceImpl, DeviceMethods, DeviceState, Property, ResetType, ResettablePhasesImpl},
qom::{ObjectImpl, ObjectType, ParentField},
qom_isa,
sysbus::SysBusDevice,
timer::{Timer, CLOCK_VIRTUAL},
};
use crate::fw_cfg::HPETFwConfig;
/// Register space for each timer block (`HPET_BASE` is defined in hpet.h).
const HPET_REG_SPACE_LEN: u64 = 0x400; // 1024 bytes
/// Minimum recommended hardware implementation.
const HPET_MIN_TIMERS: usize = 3;
/// Maximum timers in each timer block.
const HPET_MAX_TIMERS: usize = 32;
/// Flags that HPETState.flags supports.
const HPET_FLAG_MSI_SUPPORT_SHIFT: usize = 0;
const HPET_NUM_IRQ_ROUTES: usize = 32;
const HPET_LEGACY_PIT_INT: u32 = 0; // HPET_LEGACY_RTC_INT isn't defined here.
const RTC_ISA_IRQ: usize = 8;
const HPET_CLK_PERIOD: u64 = 10; // 10 ns
const FS_PER_NS: u64 = 1000000; // 1000000 femtoseconds == 1 ns
/// General Capabilities and ID Register
const HPET_CAP_REG: u64 = 0x000;
/// Revision ID (bits 0:7). Revision 1 is implemented (refer to v1.0a spec).
const HPET_CAP_REV_ID_VALUE: u64 = 0x1;
const HPET_CAP_REV_ID_SHIFT: usize = 0;
/// Number of Timers (bits 8:12)
const HPET_CAP_NUM_TIM_SHIFT: usize = 8;
/// Counter Size (bit 13)
const HPET_CAP_COUNT_SIZE_CAP_SHIFT: usize = 13;
/// Legacy Replacement Route Capable (bit 15)
const HPET_CAP_LEG_RT_CAP_SHIFT: usize = 15;
/// Vendor ID (bits 16:31)
const HPET_CAP_VENDER_ID_VALUE: u64 = 0x8086;
const HPET_CAP_VENDER_ID_SHIFT: usize = 16;
/// Main Counter Tick Period (bits 32:63)
const HPET_CAP_CNT_CLK_PERIOD_SHIFT: usize = 32;
/// General Configuration Register
const HPET_CFG_REG: u64 = 0x010;
/// Overall Enable (bit 0)
const HPET_CFG_ENABLE_SHIFT: usize = 0;
/// Legacy Replacement Route (bit 1)
const HPET_CFG_LEG_RT_SHIFT: usize = 1;
/// Other bits are reserved.
const HPET_CFG_WRITE_MASK: u64 = 0x003;
/// General Interrupt Status Register
const HPET_INT_STATUS_REG: u64 = 0x020;
/// Main Counter Value Register
const HPET_COUNTER_REG: u64 = 0x0f0;
/// Timer N Configuration and Capability Register (masked by 0x18)
const HPET_TN_CFG_REG: u64 = 0x000;
/// bit 0, 7, and bits 16:31 are reserved.
/// bit 4, 5, 15, and bits 32:64 are read-only.
const HPET_TN_CFG_WRITE_MASK: u64 = 0x7f4e;
/// Timer N Interrupt Type (bit 1)
const HPET_TN_CFG_INT_TYPE_SHIFT: usize = 1;
/// Timer N Interrupt Enable (bit 2)
const HPET_TN_CFG_INT_ENABLE_SHIFT: usize = 2;
/// Timer N Type (Periodic enabled or not, bit 3)
const HPET_TN_CFG_PERIODIC_SHIFT: usize = 3;
/// Timer N Periodic Interrupt Capable (support Periodic or not, bit 4)
const HPET_TN_CFG_PERIODIC_CAP_SHIFT: usize = 4;
/// Timer N Size (timer size is 64-bits or 32 bits, bit 5)
const HPET_TN_CFG_SIZE_CAP_SHIFT: usize = 5;
/// Timer N Value Set (bit 6)
const HPET_TN_CFG_SETVAL_SHIFT: usize = 6;
/// Timer N 32-bit Mode (bit 8)
const HPET_TN_CFG_32BIT_SHIFT: usize = 8;
/// Timer N Interrupt Rout (bits 9:13)
const HPET_TN_CFG_INT_ROUTE_MASK: u64 = 0x3e00;
const HPET_TN_CFG_INT_ROUTE_SHIFT: usize = 9;
/// Timer N FSB Interrupt Enable (bit 14)
const HPET_TN_CFG_FSB_ENABLE_SHIFT: usize = 14;
/// Timer N FSB Interrupt Delivery (bit 15)
const HPET_TN_CFG_FSB_CAP_SHIFT: usize = 15;
/// Timer N Interrupt Routing Capability (bits 32:63)
const HPET_TN_CFG_INT_ROUTE_CAP_SHIFT: usize = 32;
/// Timer N Comparator Value Register (masked by 0x18)
const HPET_TN_CMP_REG: u64 = 0x008;
/// Timer N FSB Interrupt Route Register (masked by 0x18)
const HPET_TN_FSB_ROUTE_REG: u64 = 0x010;
const fn hpet_next_wrap(cur_tick: u64) -> u64 {
(cur_tick | 0xffffffff) + 1
}
const fn hpet_time_after(a: u64, b: u64) -> bool {
((b - a) as i64) < 0
}
const fn ticks_to_ns(value: u64) -> u64 {
value * HPET_CLK_PERIOD
}
const fn ns_to_ticks(value: u64) -> u64 {
value / HPET_CLK_PERIOD
}
// Avoid touching the bits that cannot be written.
const fn hpet_fixup_reg(new: u64, old: u64, mask: u64) -> u64 {
(new & mask) | (old & !mask)
}
const fn activating_bit(old: u64, new: u64, shift: usize) -> bool {
let mask: u64 = 1 << shift;
(old & mask == 0) && (new & mask != 0)
}
const fn deactivating_bit(old: u64, new: u64, shift: usize) -> bool {
let mask: u64 = 1 << shift;
(old & mask != 0) && (new & mask == 0)
}
fn timer_handler(timer_cell: &BqlRefCell<HPETTimer>) {
timer_cell.borrow_mut().callback()
}
/// HPET Timer Abstraction
#[repr(C)]
#[derive(Debug, Default, qemu_api_macros::offsets)]
pub struct HPETTimer {
/// timer N index within the timer block (`HPETState`)
#[doc(alias = "tn")]
index: usize,
qemu_timer: Option<Box<Timer>>,
/// timer block abstraction containing this timer
state: Option<NonNull<HPETState>>,
// Memory-mapped, software visible timer registers
/// Timer N Configuration and Capability Register
config: u64,
/// Timer N Comparator Value Register
cmp: u64,
/// Timer N FSB Interrupt Route Register
fsb: u64,
// Hidden register state
/// comparator (extended to counter width)
cmp64: u64,
/// Last value written to comparator
period: u64,
/// timer pop will indicate wrap for one-shot 32-bit
/// mode. Next pop will be actual timer expiration.
wrap_flag: u8,
/// last value armed, to avoid timer storms
last: u64,
}
impl HPETTimer {
fn init(&mut self, index: usize, state_ptr: *mut HPETState) -> &mut Self {
*self = HPETTimer::default();
self.index = index;
self.state = NonNull::new(state_ptr);
self
}
fn init_timer_with_state(&mut self) {
self.qemu_timer = Some(Box::new({
let mut t = Timer::new();
t.init_full(
None,
CLOCK_VIRTUAL,
Timer::NS,
0,
timer_handler,
&self.get_state().timers[self.index],
);
t
}));
}
fn get_state(&self) -> &HPETState {
// SAFETY:
// the pointer is convertible to a reference
unsafe { self.state.unwrap().as_ref() }
}
fn is_int_active(&self) -> bool {
self.get_state().is_timer_int_active(self.index)
}
const fn is_fsb_route_enabled(&self) -> bool {
self.config & (1 << HPET_TN_CFG_FSB_ENABLE_SHIFT) != 0
}
const fn is_periodic(&self) -> bool {
self.config & (1 << HPET_TN_CFG_PERIODIC_SHIFT) != 0
}
const fn is_int_enabled(&self) -> bool {
self.config & (1 << HPET_TN_CFG_INT_ENABLE_SHIFT) != 0
}
const fn is_32bit_mod(&self) -> bool {
self.config & (1 << HPET_TN_CFG_32BIT_SHIFT) != 0
}
const fn is_valset_enabled(&self) -> bool {
self.config & (1 << HPET_TN_CFG_SETVAL_SHIFT) != 0
}
fn clear_valset(&mut self) {
self.config &= !(1 << HPET_TN_CFG_SETVAL_SHIFT);
}
/// True if timer interrupt is level triggered; otherwise, edge triggered.
const fn is_int_level_triggered(&self) -> bool {
self.config & (1 << HPET_TN_CFG_INT_TYPE_SHIFT) != 0
}
/// calculate next value of the general counter that matches the
/// target (either entirely, or the low 32-bit only depending on
/// the timer mode).
fn calculate_cmp64(&self, cur_tick: u64, target: u64) -> u64 {
if self.is_32bit_mod() {
let mut result: u64 = cur_tick.deposit(0, 32, target);
if result < cur_tick {
result += 0x100000000;
}
result
} else {
target
}
}
const fn get_individual_route(&self) -> usize {
((self.config & HPET_TN_CFG_INT_ROUTE_MASK) >> HPET_TN_CFG_INT_ROUTE_SHIFT) as usize
}
fn get_int_route(&self) -> usize {
if self.index <= 1 && self.get_state().is_legacy_mode() {
// If LegacyReplacement Route bit is set, HPET specification requires
// timer0 be routed to IRQ0 in NON-APIC or IRQ2 in the I/O APIC,
// timer1 be routed to IRQ8 in NON-APIC or IRQ8 in the I/O APIC.
//
// If the LegacyReplacement Route bit is set, the individual routing
// bits for timers 0 and 1 (APIC or FSB) will have no impact.
//
// FIXME: Consider I/O APIC case.
if self.index == 0 {
0
} else {
RTC_ISA_IRQ
}
} else {
// (If the LegacyReplacement Route bit is set) Timer 2-n will be
// routed as per the routing in the timer n config registers.
// ...
// If the LegacyReplacement Route bit is not set, the individual
// routing bits for each of the timers are used.
self.get_individual_route()
}
}
fn set_irq(&mut self, set: bool) {
let route = self.get_int_route();
if set && self.is_int_enabled() && self.get_state().is_hpet_enabled() {
if self.is_fsb_route_enabled() {
// SAFETY:
// the parameters are valid.
unsafe {
address_space_stl_le(
addr_of_mut!(address_space_memory),
self.fsb >> 32, // Timer N FSB int addr
self.fsb as u32, // Timer N FSB int value, truncate!
MEMTXATTRS_UNSPECIFIED,
null_mut(),
);
}
} else if self.is_int_level_triggered() {
self.get_state().irqs[route].raise();
} else {
self.get_state().irqs[route].pulse();
}
} else if !self.is_fsb_route_enabled() {
self.get_state().irqs[route].lower();
}
}
fn update_irq(&mut self, set: bool) {
// If Timer N Interrupt Enable bit is 0, "the timer will
// still operate and generate appropriate status bits, but
// will not cause an interrupt"
self.get_state()
.update_int_status(self.index as u32, set && self.is_int_level_triggered());
self.set_irq(set);
}
fn arm_timer(&mut self, tick: u64) {
let mut ns = self.get_state().get_ns(tick);
// Clamp period to reasonable min value (1 us)
if self.is_periodic() && ns - self.last < 1000 {
ns = self.last + 1000;
}
self.last = ns;
self.qemu_timer.as_ref().unwrap().modify(self.last);
}
fn set_timer(&mut self) {
let cur_tick: u64 = self.get_state().get_ticks();
self.wrap_flag = 0;
self.cmp64 = self.calculate_cmp64(cur_tick, self.cmp);
if self.is_32bit_mod() {
// HPET spec says in one-shot 32-bit mode, generate an interrupt when
// counter wraps in addition to an interrupt with comparator match.
if !self.is_periodic() && self.cmp64 > hpet_next_wrap(cur_tick) {
self.wrap_flag = 1;
self.arm_timer(hpet_next_wrap(cur_tick));
return;
}
}
self.arm_timer(self.cmp64);
}
fn del_timer(&mut self) {
// Just remove the timer from the timer_list without destroying
// this timer instance.
self.qemu_timer.as_ref().unwrap().delete();
if self.is_int_active() {
// For level-triggered interrupt, this leaves interrupt status
// register set but lowers irq.
self.update_irq(true);
}
}
/// Configuration and Capability Register
fn set_tn_cfg_reg(&mut self, shift: u32, len: u32, val: u64) {
// TODO: Add trace point - trace_hpet_ram_write_tn_cfg(addr & 4)
let old_val: u64 = self.config;
let mut new_val: u64 = old_val.deposit(shift, len, val);
new_val = hpet_fixup_reg(new_val, old_val, HPET_TN_CFG_WRITE_MASK);
// Switch level-type interrupt to edge-type.
if deactivating_bit(old_val, new_val, HPET_TN_CFG_INT_TYPE_SHIFT) {
// Do this before changing timer.config; otherwise, if
// HPET_TN_FSB is set, update_irq will not lower the qemu_irq.
self.update_irq(false);
}
self.config = new_val;
if activating_bit(old_val, new_val, HPET_TN_CFG_INT_ENABLE_SHIFT) && self.is_int_active() {
self.update_irq(true);
}
if self.is_32bit_mod() {
self.cmp = u64::from(self.cmp as u32); // truncate!
self.period = u64::from(self.period as u32); // truncate!
}
if self.get_state().is_hpet_enabled() {
self.set_timer();
}
}
/// Comparator Value Register
fn set_tn_cmp_reg(&mut self, shift: u32, len: u32, val: u64) {
let mut length = len;
let mut value = val;
// TODO: Add trace point - trace_hpet_ram_write_tn_cmp(addr & 4)
if self.is_32bit_mod() {
// High 32-bits are zero, leave them untouched.
if shift != 0 {
// TODO: Add trace point - trace_hpet_ram_write_invalid_tn_cmp()
return;
}
length = 64;
value = u64::from(value as u32); // truncate!
}
if !self.is_periodic() || self.is_valset_enabled() {
self.cmp = self.cmp.deposit(shift, length, value);
}
if self.is_periodic() {
self.period = self.period.deposit(shift, length, value);
}
self.clear_valset();
if self.get_state().is_hpet_enabled() {
self.set_timer();
}
}
/// FSB Interrupt Route Register
fn set_tn_fsb_route_reg(&mut self, shift: u32, len: u32, val: u64) {
self.fsb = self.fsb.deposit(shift, len, val);
}
fn reset(&mut self) {
self.del_timer();
self.cmp = u64::MAX; // Comparator Match Registers reset to all 1's.
self.config = (1 << HPET_TN_CFG_PERIODIC_CAP_SHIFT) | (1 << HPET_TN_CFG_SIZE_CAP_SHIFT);
if self.get_state().has_msi_flag() {
self.config |= 1 << HPET_TN_CFG_FSB_CAP_SHIFT;
}
// advertise availability of ioapic int
self.config |=
(u64::from(self.get_state().int_route_cap)) << HPET_TN_CFG_INT_ROUTE_CAP_SHIFT;
self.period = 0;
self.wrap_flag = 0;
}
/// timer expiration callback
fn callback(&mut self) {
let period: u64 = self.period;
let cur_tick: u64 = self.get_state().get_ticks();
if self.is_periodic() && period != 0 {
while hpet_time_after(cur_tick, self.cmp64) {
self.cmp64 += period;
}
if self.is_32bit_mod() {
self.cmp = u64::from(self.cmp64 as u32); // truncate!
} else {
self.cmp = self.cmp64;
}
self.arm_timer(self.cmp64);
} else if self.wrap_flag != 0 {
self.wrap_flag = 0;
self.arm_timer(self.cmp64);
}
self.update_irq(true);
}
const fn read(&self, addr: hwaddr, _size: u32) -> u64 {
let shift: u64 = (addr & 4) * 8;
match addr & !4 {
HPET_TN_CFG_REG => self.config >> shift, // including interrupt capabilities
HPET_TN_CMP_REG => self.cmp >> shift, // comparator register
HPET_TN_FSB_ROUTE_REG => self.fsb >> shift,
_ => {
// TODO: Add trace point - trace_hpet_ram_read_invalid()
// Reserved.
0
}
}
}
fn write(&mut self, addr: hwaddr, value: u64, size: u32) {
let shift = ((addr & 4) * 8) as u32;
let len = std::cmp::min(size * 8, 64 - shift);
match addr & !4 {
HPET_TN_CFG_REG => self.set_tn_cfg_reg(shift, len, value),
HPET_TN_CMP_REG => self.set_tn_cmp_reg(shift, len, value),
HPET_TN_FSB_ROUTE_REG => self.set_tn_fsb_route_reg(shift, len, value),
_ => {
// TODO: Add trace point - trace_hpet_ram_write_invalid()
// Reserved.
}
}
}
}
/// HPET Event Timer Block Abstraction
#[repr(C)]
#[derive(qemu_api_macros::Object, qemu_api_macros::offsets)]
pub struct HPETState {
parent_obj: ParentField<SysBusDevice>,
iomem: MemoryRegion,
// HPET block Registers: Memory-mapped, software visible registers
/// General Capabilities and ID Register
capability: BqlCell<u64>,
/// General Configuration Register
config: BqlCell<u64>,
/// General Interrupt Status Register
#[doc(alias = "isr")]
int_status: BqlCell<u64>,
/// Main Counter Value Register
#[doc(alias = "hpet_counter")]
counter: BqlCell<u64>,
// Internal state
/// Capabilities that QEMU HPET supports.
/// bit 0: MSI (or FSB) support.
flags: u32,
/// Offset of main counter relative to qemu clock.
hpet_offset: BqlCell<u64>,
hpet_offset_saved: bool,
irqs: [InterruptSource; HPET_NUM_IRQ_ROUTES],
rtc_irq_level: BqlCell<u32>,
pit_enabled: InterruptSource,
/// Interrupt Routing Capability.
/// This field indicates to which interrupts in the I/O (x) APIC
/// the timers' interrupt can be routed, and is encoded in the
/// bits 32:64 of timer N's config register:
#[doc(alias = "intcap")]
int_route_cap: u32,
/// HPET timer array managed by this timer block.
#[doc(alias = "timer")]
timers: [BqlRefCell<HPETTimer>; HPET_MAX_TIMERS],
num_timers: BqlCell<usize>,
/// Instance id (HPET timer block ID).
hpet_id: BqlCell<usize>,
}
impl HPETState {
const fn has_msi_flag(&self) -> bool {
self.flags & (1 << HPET_FLAG_MSI_SUPPORT_SHIFT) != 0
}
fn is_legacy_mode(&self) -> bool {
self.config.get() & (1 << HPET_CFG_LEG_RT_SHIFT) != 0
}
fn is_hpet_enabled(&self) -> bool {
self.config.get() & (1 << HPET_CFG_ENABLE_SHIFT) != 0
}
fn is_timer_int_active(&self, index: usize) -> bool {
self.int_status.get() & (1 << index) != 0
}
fn get_ticks(&self) -> u64 {
ns_to_ticks(CLOCK_VIRTUAL.get_ns() + self.hpet_offset.get())
}
fn get_ns(&self, tick: u64) -> u64 {
ticks_to_ns(tick) - self.hpet_offset.get()
}
fn handle_legacy_irq(&self, irq: u32, level: u32) {
if irq == HPET_LEGACY_PIT_INT {
if !self.is_legacy_mode() {
self.irqs[0].set(level != 0);
}
} else {
self.rtc_irq_level.set(level);
if !self.is_legacy_mode() {
self.irqs[RTC_ISA_IRQ].set(level != 0);
}
}
}
fn init_timer(&self) {
let raw_ptr: *mut HPETState = self as *const HPETState as *mut HPETState;
for (index, timer) in self.timers.iter().enumerate() {
timer
.borrow_mut()
.init(index, raw_ptr)
.init_timer_with_state();
}
}
fn update_int_status(&self, index: u32, level: bool) {
self.int_status
.set(self.int_status.get().deposit(index, 1, u64::from(level)));
}
/// General Configuration Register
fn set_cfg_reg(&self, shift: u32, len: u32, val: u64) {
let old_val = self.config.get();
let mut new_val = old_val.deposit(shift, len, val);
new_val = hpet_fixup_reg(new_val, old_val, HPET_CFG_WRITE_MASK);
self.config.set(new_val);
if activating_bit(old_val, new_val, HPET_CFG_ENABLE_SHIFT) {
// Enable main counter and interrupt generation.
self.hpet_offset
.set(ticks_to_ns(self.counter.get()) - CLOCK_VIRTUAL.get_ns());
for timer in self.timers.iter().take(self.num_timers.get()) {
let mut t = timer.borrow_mut();
if t.is_int_enabled() && t.is_int_active() {
t.update_irq(true);
}
t.set_timer();
}
} else if deactivating_bit(old_val, new_val, HPET_CFG_ENABLE_SHIFT) {
// Halt main counter and disable interrupt generation.
self.counter.set(self.get_ticks());
for timer in self.timers.iter().take(self.num_timers.get()) {
timer.borrow_mut().del_timer();
}
}
// i8254 and RTC output pins are disabled when HPET is in legacy mode
if activating_bit(old_val, new_val, HPET_CFG_LEG_RT_SHIFT) {
self.pit_enabled.set(false);
self.irqs[0].lower();
self.irqs[RTC_ISA_IRQ].lower();
} else if deactivating_bit(old_val, new_val, HPET_CFG_LEG_RT_SHIFT) {
// TODO: Add irq binding: qemu_irq_lower(s->irqs[0])
self.irqs[0].lower();
self.pit_enabled.set(true);
self.irqs[RTC_ISA_IRQ].set(self.rtc_irq_level.get() != 0);
}
}
/// General Interrupt Status Register: Read/Write Clear
fn set_int_status_reg(&self, shift: u32, _len: u32, val: u64) {
let new_val = val << shift;
let cleared = new_val & self.int_status.get();
for (index, timer) in self.timers.iter().take(self.num_timers.get()).enumerate() {
if cleared & (1 << index) != 0 {
timer.borrow_mut().update_irq(false);
}
}
}
/// Main Counter Value Register
fn set_counter_reg(&self, shift: u32, len: u32, val: u64) {
if self.is_hpet_enabled() {
// TODO: Add trace point -
// trace_hpet_ram_write_counter_write_while_enabled()
//
// HPET spec says that writes to this register should only be
// done while the counter is halted. So this is an undefined
// behavior. There's no need to forbid it, but when HPET is
// enabled, the changed counter value will not affect the
// tick count (i.e., the previously calculated offset will
// not be changed as well).
}
self.counter
.set(self.counter.get().deposit(shift, len, val));
}
unsafe fn init(&mut self) {
static HPET_RAM_OPS: MemoryRegionOps<HPETState> =
MemoryRegionOpsBuilder::<HPETState>::new()
.read(&HPETState::read)
.write(&HPETState::write)
.native_endian()
.valid_sizes(4, 8)
.impl_sizes(4, 8)
.build();
// SAFETY:
// self and self.iomem are guaranteed to be valid at this point since callers
// must make sure the `self` reference is valid.
MemoryRegion::init_io(
unsafe { &mut *addr_of_mut!(self.iomem) },
addr_of_mut!(*self),
&HPET_RAM_OPS,
"hpet",
HPET_REG_SPACE_LEN,
);
}
fn post_init(&self) {
self.init_mmio(&self.iomem);
for irq in self.irqs.iter() {
self.init_irq(irq);
}
}
fn realize(&self) {
if self.int_route_cap == 0 {
// TODO: Add error binding: warn_report()
println!("Hpet's hpet-intcap property not initialized");
}
self.hpet_id.set(HPETFwConfig::assign_hpet_id());
if self.num_timers.get() < HPET_MIN_TIMERS {
self.num_timers.set(HPET_MIN_TIMERS);
} else if self.num_timers.get() > HPET_MAX_TIMERS {
self.num_timers.set(HPET_MAX_TIMERS);
}
self.init_timer();
// 64-bit General Capabilities and ID Register; LegacyReplacementRoute.
self.capability.set(
HPET_CAP_REV_ID_VALUE << HPET_CAP_REV_ID_SHIFT |
1 << HPET_CAP_COUNT_SIZE_CAP_SHIFT |
1 << HPET_CAP_LEG_RT_CAP_SHIFT |
HPET_CAP_VENDER_ID_VALUE << HPET_CAP_VENDER_ID_SHIFT |
((self.num_timers.get() - 1) as u64) << HPET_CAP_NUM_TIM_SHIFT | // indicate the last timer
(HPET_CLK_PERIOD * FS_PER_NS) << HPET_CAP_CNT_CLK_PERIOD_SHIFT, // 10 ns
);
self.init_gpio_in(2, HPETState::handle_legacy_irq);
self.init_gpio_out(from_ref(&self.pit_enabled));
}
fn reset_hold(&self, _type: ResetType) {
let sbd = self.upcast::<SysBusDevice>();
for timer in self.timers.iter().take(self.num_timers.get()) {
timer.borrow_mut().reset();
}
self.counter.set(0);
self.config.set(0);
self.pit_enabled.set(true);
self.hpet_offset.set(0);
HPETFwConfig::update_hpet_cfg(
self.hpet_id.get(),
self.capability.get() as u32,
sbd.mmio[0].addr,
);
// to document that the RTC lowers its output on reset as well
self.rtc_irq_level.set(0);
}
fn timer_and_addr(&self, addr: hwaddr) -> Option<(&BqlRefCell<HPETTimer>, hwaddr)> {
let timer_id: usize = ((addr - 0x100) / 0x20) as usize;
// TODO: Add trace point - trace_hpet_ram_[read|write]_timer_id(timer_id)
if timer_id > self.num_timers.get() {
// TODO: Add trace point - trace_hpet_timer_id_out_of_range(timer_id)
None
} else {
// Keep the complete address so that HPETTimer's read and write could
// detect the invalid access.
Some((&self.timers[timer_id], addr & 0x1F))
}
}
fn read(&self, addr: hwaddr, size: u32) -> u64 {
let shift: u64 = (addr & 4) * 8;
// address range of all TN regs
// TODO: Add trace point - trace_hpet_ram_read(addr)
if (0x100..=0x3ff).contains(&addr) {
match self.timer_and_addr(addr) {
None => 0, // Reserved,
Some((timer, tn_addr)) => timer.borrow_mut().read(tn_addr, size),
}
} else {
match addr & !4 {
HPET_CAP_REG => self.capability.get() >> shift, /* including HPET_PERIOD 0x004 */
// (CNT_CLK_PERIOD field)
HPET_CFG_REG => self.config.get() >> shift,
HPET_COUNTER_REG => {
let cur_tick: u64 = if self.is_hpet_enabled() {
self.get_ticks()
} else {
self.counter.get()
};
// TODO: Add trace point - trace_hpet_ram_read_reading_counter(addr & 4,
// cur_tick)
cur_tick >> shift
}
HPET_INT_STATUS_REG => self.int_status.get() >> shift,
_ => {
// TODO: Add trace point- trace_hpet_ram_read_invalid()
// Reserved.
0
}
}
}
}
fn write(&self, addr: hwaddr, value: u64, size: u32) {
let shift = ((addr & 4) * 8) as u32;
let len = std::cmp::min(size * 8, 64 - shift);
// TODO: Add trace point - trace_hpet_ram_write(addr, value)
if (0x100..=0x3ff).contains(&addr) {
match self.timer_and_addr(addr) {
None => (), // Reserved.
Some((timer, tn_addr)) => timer.borrow_mut().write(tn_addr, value, size),
}
} else {
match addr & !0x4 {
HPET_CAP_REG => {} // General Capabilities and ID Register: Read Only
HPET_CFG_REG => self.set_cfg_reg(shift, len, value),
HPET_INT_STATUS_REG => self.set_int_status_reg(shift, len, value),
HPET_COUNTER_REG => self.set_counter_reg(shift, len, value),
_ => {
// TODO: Add trace point - trace_hpet_ram_write_invalid()
// Reserved.
}
}
}
}
}
qom_isa!(HPETState: SysBusDevice, DeviceState, Object);
unsafe impl ObjectType for HPETState {
// No need for HPETClass. Just like OBJECT_DECLARE_SIMPLE_TYPE in C.
type Class = <SysBusDevice as ObjectType>::Class;
const TYPE_NAME: &'static CStr = crate::TYPE_HPET;
}
impl ObjectImpl for HPETState {
type ParentType = SysBusDevice;
const INSTANCE_INIT: Option<unsafe fn(&mut Self)> = Some(Self::init);
const INSTANCE_POST_INIT: Option<fn(&Self)> = Some(Self::post_init);
}
// TODO: Make these properties user-configurable!
qemu_api::declare_properties! {
HPET_PROPERTIES,
qemu_api::define_property!(
c_str!("timers"),
HPETState,
num_timers,
unsafe { &qdev_prop_uint8 },
u8,
default = HPET_MIN_TIMERS
),
qemu_api::define_property!(
c_str!("msi"),
HPETState,
flags,
unsafe { &qdev_prop_bit },
u32,
bit = HPET_FLAG_MSI_SUPPORT_SHIFT as u8,
default = false,
),
qemu_api::define_property!(
c_str!("hpet-intcap"),
HPETState,
int_route_cap,
unsafe { &qdev_prop_uint32 },
u32,
default = 0
),
qemu_api::define_property!(
c_str!("hpet-offset-saved"),
HPETState,
hpet_offset_saved,
unsafe { &qdev_prop_bool },
bool,
default = true
),
}
impl DeviceImpl for HPETState {
fn properties() -> &'static [Property] {
&HPET_PROPERTIES
}
const REALIZE: Option<fn(&Self)> = Some(Self::realize);
}
impl ResettablePhasesImpl for HPETState {
const HOLD: Option<fn(&Self, ResetType)> = Some(Self::reset_hold);
}

View File

@ -0,0 +1,15 @@
// Copyright (C) 2024 Intel Corporation.
// Author(s): Zhao Liu <zhai1.liu@intel.com>
// SPDX-License-Identifier: GPL-2.0-or-later
//! # HPET QEMU Device Model
//!
//! This library implements a device model for the IA-PC HPET (High
//! Precision Event Timers) device in QEMU.
use qemu_api::c_str;
pub mod fw_cfg;
pub mod hpet;
pub const TYPE_HPET: &::std::ffi::CStr = c_str!("hpet");

View File

@ -0,0 +1 @@
subdir('hpet')

View File

@ -20,14 +20,17 @@ _qemu_api_rs = static_library(
'src/bitops.rs',
'src/callbacks.rs',
'src/cell.rs',
'src/chardev.rs',
'src/c_str.rs',
'src/irq.rs',
'src/memory.rs',
'src/module.rs',
'src/offset_of.rs',
'src/prelude.rs',
'src/qdev.rs',
'src/qom.rs',
'src/sysbus.rs',
'src/timer.rs',
'src/vmstate.rs',
'src/zeroable.rs',
],

View File

@ -2,9 +2,13 @@
// Author(s): Paolo Bonzini <pbonzini@redhat.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#![doc(hidden)]
//! This module provides macros to check the equality of types and
//! the type of `struct` fields. This can be useful to ensure that
//! types match the expectations of C code.
//!
//! Documentation is hidden because it only exposes macros, which
//! are exported directly from `qemu_api`.
// Based on https://stackoverflow.com/questions/64251852/x/70978292#70978292
// (stackoverflow answers are released under MIT license).

View File

@ -15,15 +15,63 @@
clippy::missing_safety_doc
)]
//! `bindgen`-generated declarations.
#[cfg(MESON)]
include!("bindings.inc.rs");
#[cfg(not(MESON))]
include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs"));
// SAFETY: these are implemented in C; the bindings need to assert that the
// BQL is taken, either directly or via `BqlCell` and `BqlRefCell`.
unsafe impl Send for BusState {}
unsafe impl Sync for BusState {}
unsafe impl Send for CharBackend {}
unsafe impl Sync for CharBackend {}
unsafe impl Send for Chardev {}
unsafe impl Sync for Chardev {}
unsafe impl Send for Clock {}
unsafe impl Sync for Clock {}
unsafe impl Send for DeviceState {}
unsafe impl Sync for DeviceState {}
unsafe impl Send for MemoryRegion {}
unsafe impl Sync for MemoryRegion {}
unsafe impl Send for ObjectClass {}
unsafe impl Sync for ObjectClass {}
unsafe impl Send for Object {}
unsafe impl Sync for Object {}
unsafe impl Send for SysBusDevice {}
unsafe impl Sync for SysBusDevice {}
// SAFETY: this is a pure data struct
unsafe impl Send for CoalescedMemoryRange {}
unsafe impl Sync for CoalescedMemoryRange {}
// SAFETY: these are constants and vtables; the Send and Sync requirements
// are deferred to the unsafe callbacks that they contain
unsafe impl Send for MemoryRegionOps {}
unsafe impl Sync for MemoryRegionOps {}
unsafe impl Send for Property {}
unsafe impl Sync for Property {}
unsafe impl Send for TypeInfo {}
unsafe impl Sync for TypeInfo {}
unsafe impl Send for VMStateDescription {}
unsafe impl Sync for VMStateDescription {}
unsafe impl Send for VMStateField {}
unsafe impl Sync for VMStateField {}
unsafe impl Send for VMStateInfo {}
unsafe impl Sync for VMStateInfo {}

View File

@ -2,6 +2,14 @@
// Author(s): Paolo Bonzini <pbonzini@redhat.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#![doc(hidden)]
//! This module provides a macro to define a constant of type
//! [`CStr`](std::ffi::CStr), for compatibility with versions of
//! Rust that lack `c""` literals.
//!
//! Documentation is hidden because it only exposes macros, which
//! are exported directly from `qemu_api`.
#[macro_export]
/// Given a string constant _without_ embedded or trailing NULs, return
/// a `CStr`.

View File

@ -79,6 +79,31 @@ use std::{mem, ptr::NonNull};
/// call_it(&move |_| String::from(x), "hello workd");
/// ```
///
/// `()` can be used to indicate "no function":
///
/// ```
/// # use qemu_api::callbacks::FnCall;
/// fn optional<F: for<'a> FnCall<(&'a str,), String>>(_f: &F, s: &str) -> Option<String> {
/// if F::IS_SOME {
/// Some(F::call((s,)))
/// } else {
/// None
/// }
/// }
///
/// assert!(optional(&(), "hello world").is_none());
/// ```
///
/// Invoking `F::call` will then be a run-time error.
///
/// ```should_panic
/// # use qemu_api::callbacks::FnCall;
/// # fn call_it<F: for<'a> FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String {
/// # F::call((s,))
/// # }
/// let s: String = call_it(&(), "hello world"); // panics
/// ```
///
/// # Safety
///
/// Because `Self` is a zero-sized type, all instances of the type are
@ -93,10 +118,70 @@ pub unsafe trait FnCall<Args, R = ()>: 'static + Sync + Sized {
/// Rust 1.79.0+.
const ASSERT_ZERO_SIZED: () = { assert!(mem::size_of::<Self>() == 0) };
/// Referring to this constant asserts that the `Self` type is an actual
/// function type, which can be used to catch incorrect use of `()`
/// at compile time.
///
/// # Examples
///
/// ```compile_fail
/// # use qemu_api::callbacks::FnCall;
/// fn call_it<F: for<'a> FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String {
/// let _: () = F::ASSERT_IS_SOME;
/// F::call((s,))
/// }
///
/// let s: String = call_it((), "hello world"); // does not compile
/// ```
///
/// Note that this can be more simply `const { assert!(F::IS_SOME) }` in
/// Rust 1.79.0 or newer.
const ASSERT_IS_SOME: () = { assert!(Self::IS_SOME) };
/// `true` if `Self` is an actual function type and not `()`.
///
/// # Examples
///
/// You can use `IS_SOME` to catch this at compile time:
///
/// ```compile_fail
/// # use qemu_api::callbacks::FnCall;
/// fn call_it<F: for<'a> FnCall<(&'a str,), String>>(_f: &F, s: &str) -> String {
/// const { assert!(F::IS_SOME) }
/// F::call((s,))
/// }
///
/// let s: String = call_it((), "hello world"); // does not compile
/// ```
const IS_SOME: bool;
/// `false` if `Self` is an actual function type, `true` if it is `()`.
fn is_none() -> bool {
!Self::IS_SOME
}
/// `true` if `Self` is an actual function type, `false` if it is `()`.
fn is_some() -> bool {
Self::IS_SOME
}
/// Call the function with the arguments in args.
fn call(a: Args) -> R;
}
/// `()` acts as a "null" callback. Using `()` and `function` is nicer
/// than `None` and `Some(function)`, because the compiler is unable to
/// infer the type of just `None`. Therefore, the trait itself acts as the
/// option type, with functions [`FnCall::is_some`] and [`FnCall::is_none`].
unsafe impl<Args, R> FnCall<Args, R> for () {
const IS_SOME: bool = false;
/// Call the function with the arguments in args.
fn call(_a: Args) -> R {
panic!("callback not specified")
}
}
macro_rules! impl_call {
($($args:ident,)* ) => (
// SAFETY: because each function is treated as a separate type,
@ -106,6 +191,8 @@ macro_rules! impl_call {
where
F: 'static + Sync + Sized + Fn($($args, )*) -> R,
{
const IS_SOME: bool = true;
#[inline(always)]
fn call(a: ($($args,)*)) -> R {
let _: () = Self::ASSERT_ZERO_SIZED;
@ -141,4 +228,14 @@ mod tests {
fn test_call() {
assert_eq!(do_test_call(&str::to_owned), "hello world")
}
// The `_f` parameter is unused but it helps the compiler infer `F`.
fn do_test_is_some<'a, F: FnCall<(&'a str,), String>>(_f: &F) {
assert!(F::is_some());
}
#[test]
fn test_is_some() {
do_test_is_some(&str::to_owned);
}
}

View File

@ -0,0 +1,19 @@
// Copyright 2024 Red Hat, Inc.
// Author(s): Paolo Bonzini <pbonzini@redhat.com>
// SPDX-License-Identifier: GPL-2.0-or-later
//! Bindings for character devices
use std::ffi::CStr;
use crate::{bindings, prelude::*};
pub type Chardev = bindings::Chardev;
pub type ChardevClass = bindings::ChardevClass;
unsafe impl ObjectType for Chardev {
type Class = ChardevClass;
const TYPE_NAME: &'static CStr =
unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_CHARDEV) };
}
qom_isa!(Chardev: Object);

View File

@ -5,11 +5,12 @@
//! Bindings for interrupt sources
use core::ptr;
use std::{marker::PhantomData, os::raw::c_int};
use std::{ffi::CStr, marker::PhantomData, os::raw::c_int};
use crate::{
bindings::{qemu_set_irq, IRQState},
bindings::{self, qemu_set_irq},
prelude::*,
qom::ObjectClass,
};
/// Interrupt sources are used by devices to pass changes to a value (typically
@ -21,7 +22,8 @@ use crate::{
/// method sends a `true` value to the sink. If the guest has to see a
/// different polarity, that change is performed by the board between the
/// device and the interrupt controller.
///
pub type IRQState = bindings::IRQState;
/// Interrupts are implemented as a pointer to the interrupt "sink", which has
/// type [`IRQState`]. A device exposes its source as a QOM link property using
/// a function such as [`SysBusDeviceMethods::init_irq`], and
@ -43,6 +45,9 @@ where
_marker: PhantomData<T>,
}
// SAFETY: the implementation asserts via `BqlCell` that the BQL is taken
unsafe impl<T> Sync for InterruptSource<T> where c_int: From<T> {}
impl InterruptSource<bool> {
/// Send a low (`false`) value to the interrupt sink.
pub fn lower(&self) {
@ -78,6 +83,11 @@ where
pub(crate) const fn as_ptr(&self) -> *mut *mut IRQState {
self.cell.as_ptr()
}
pub(crate) const fn slice_as_ptr(slice: &[Self]) -> *mut *mut IRQState {
assert!(!slice.is_empty());
slice[0].as_ptr()
}
}
impl Default for InterruptSource {
@ -88,3 +98,10 @@ impl Default for InterruptSource {
}
}
}
unsafe impl ObjectType for IRQState {
type Class = ObjectClass;
const TYPE_NAME: &'static CStr =
unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_IRQ) };
}
qom_isa!(IRQState: Object);

View File

@ -18,12 +18,15 @@ pub mod bitops;
pub mod c_str;
pub mod callbacks;
pub mod cell;
pub mod chardev;
pub mod irq;
pub mod memory;
pub mod module;
pub mod offset_of;
pub mod qdev;
pub mod qom;
pub mod sysbus;
pub mod timer;
pub mod vmstate;
pub mod zeroable;

203
rust/qemu-api/src/memory.rs Normal file
View File

@ -0,0 +1,203 @@
// Copyright 2024 Red Hat, Inc.
// Author(s): Paolo Bonzini <pbonzini@redhat.com>
// SPDX-License-Identifier: GPL-2.0-or-later
//! Bindings for `MemoryRegion`, `MemoryRegionOps` and `MemTxAttrs`
use std::{
ffi::{CStr, CString},
marker::{PhantomData, PhantomPinned},
os::raw::{c_uint, c_void},
ptr::addr_of,
};
pub use bindings::{hwaddr, MemTxAttrs};
use crate::{
bindings::{self, device_endian, memory_region_init_io},
callbacks::FnCall,
prelude::*,
zeroable::Zeroable,
};
pub struct MemoryRegionOps<T>(
bindings::MemoryRegionOps,
// Note: quite often you'll see PhantomData<fn(&T)> mentioned when discussing
// covariance and contravariance; you don't need any of those to understand
// this usage of PhantomData. Quite simply, MemoryRegionOps<T> *logically*
// holds callbacks that take an argument of type &T, except the type is erased
// before the callback is stored in the bindings::MemoryRegionOps field.
// The argument of PhantomData is a function pointer in order to represent
// that relationship; while that will also provide desirable and safe variance
// for T, variance is not the point but just a consequence.
PhantomData<fn(&T)>,
);
// SAFETY: When a *const T is passed to the callbacks, the call itself
// is done in a thread-safe manner. The invocation is okay as long as
// T itself is `Sync`.
unsafe impl<T: Sync> Sync for MemoryRegionOps<T> {}
#[derive(Clone)]
pub struct MemoryRegionOpsBuilder<T>(bindings::MemoryRegionOps, PhantomData<fn(&T)>);
unsafe extern "C" fn memory_region_ops_read_cb<T, F: for<'a> FnCall<(&'a T, hwaddr, u32), u64>>(
opaque: *mut c_void,
addr: hwaddr,
size: c_uint,
) -> u64 {
F::call((unsafe { &*(opaque.cast::<T>()) }, addr, size))
}
unsafe extern "C" fn memory_region_ops_write_cb<T, F: for<'a> FnCall<(&'a T, hwaddr, u64, u32)>>(
opaque: *mut c_void,
addr: hwaddr,
data: u64,
size: c_uint,
) {
F::call((unsafe { &*(opaque.cast::<T>()) }, addr, data, size))
}
impl<T> MemoryRegionOpsBuilder<T> {
#[must_use]
pub const fn read<F: for<'a> FnCall<(&'a T, hwaddr, u32), u64>>(mut self, _f: &F) -> Self {
self.0.read = Some(memory_region_ops_read_cb::<T, F>);
self
}
#[must_use]
pub const fn write<F: for<'a> FnCall<(&'a T, hwaddr, u64, u32)>>(mut self, _f: &F) -> Self {
self.0.write = Some(memory_region_ops_write_cb::<T, F>);
self
}
#[must_use]
pub const fn big_endian(mut self) -> Self {
self.0.endianness = device_endian::DEVICE_BIG_ENDIAN;
self
}
#[must_use]
pub const fn little_endian(mut self) -> Self {
self.0.endianness = device_endian::DEVICE_LITTLE_ENDIAN;
self
}
#[must_use]
pub const fn native_endian(mut self) -> Self {
self.0.endianness = device_endian::DEVICE_NATIVE_ENDIAN;
self
}
#[must_use]
pub const fn valid_sizes(mut self, min: u32, max: u32) -> Self {
self.0.valid.min_access_size = min;
self.0.valid.max_access_size = max;
self
}
#[must_use]
pub const fn valid_unaligned(mut self) -> Self {
self.0.valid.unaligned = true;
self
}
#[must_use]
pub const fn impl_sizes(mut self, min: u32, max: u32) -> Self {
self.0.impl_.min_access_size = min;
self.0.impl_.max_access_size = max;
self
}
#[must_use]
pub const fn impl_unaligned(mut self) -> Self {
self.0.impl_.unaligned = true;
self
}
#[must_use]
pub const fn build(self) -> MemoryRegionOps<T> {
MemoryRegionOps::<T>(self.0, PhantomData)
}
#[must_use]
pub const fn new() -> Self {
Self(bindings::MemoryRegionOps::ZERO, PhantomData)
}
}
impl<T> Default for MemoryRegionOpsBuilder<T> {
fn default() -> Self {
Self::new()
}
}
/// A safe wrapper around [`bindings::MemoryRegion`]. Compared to the
/// underlying C struct it is marked as pinned because the QOM tree
/// contains a pointer to it.
pub struct MemoryRegion {
inner: bindings::MemoryRegion,
_pin: PhantomPinned,
}
impl MemoryRegion {
// inline to ensure that it is not included in tests, which only
// link to hwcore and qom. FIXME: inlining is actually the opposite
// of what we want, since this is the type-erased version of the
// init_io function below. Look into splitting the qemu_api crate.
#[inline(always)]
unsafe fn do_init_io(
slot: *mut bindings::MemoryRegion,
owner: *mut Object,
ops: &'static bindings::MemoryRegionOps,
name: &'static str,
size: u64,
) {
unsafe {
let cstr = CString::new(name).unwrap();
memory_region_init_io(
slot,
owner.cast::<Object>(),
ops,
owner.cast::<c_void>(),
cstr.as_ptr(),
size,
);
}
}
pub fn init_io<T: IsA<Object>>(
&mut self,
owner: *mut T,
ops: &'static MemoryRegionOps<T>,
name: &'static str,
size: u64,
) {
unsafe {
Self::do_init_io(&mut self.inner, owner.cast::<Object>(), &ops.0, name, size);
}
}
pub(crate) const fn as_mut_ptr(&self) -> *mut bindings::MemoryRegion {
addr_of!(self.inner) as *mut _
}
}
unsafe impl ObjectType for MemoryRegion {
type Class = bindings::MemoryRegionClass;
const TYPE_NAME: &'static CStr =
unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_MEMORY_REGION) };
}
qom_isa!(MemoryRegion: Object);
/// A special `MemTxAttrs` constant, used to indicate that no memory
/// attributes are specified.
///
/// Bus masters which don't specify any attributes will get this,
/// which has all attribute bits clear except the topmost one
/// (so that we can distinguish "all attributes deliberately clear"
/// from "didn't specify" if necessary).
pub const MEMTXATTRS_UNSPECIFIED: MemTxAttrs = MemTxAttrs {
unspecified: true,
..Zeroable::ZERO
};

View File

@ -1,5 +1,12 @@
// SPDX-License-Identifier: MIT
#![doc(hidden)]
//! This module provides macros that emulate the functionality of
//! `core::mem::offset_of` on older versions of Rust.
//!
//! Documentation is hidden because it only exposes macros, which
//! are exported directly from `qemu_api`.
/// 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! { }`.

View File

@ -2,16 +2,22 @@
// Author(s): Paolo Bonzini <pbonzini@redhat.com>
// SPDX-License-Identifier: GPL-2.0-or-later
//! Commonly used traits and types for QEMU.
pub use crate::bitops::IntegerExt;
pub use crate::cell::BqlCell;
pub use crate::cell::BqlRefCell;
pub use crate::qdev::DeviceMethods;
pub use crate::qom::InterfaceType;
pub use crate::qom::IsA;
pub use crate::qom::Object;
pub use crate::qom::ObjectCast;
pub use crate::qom::ObjectCastMut;
pub use crate::qom::ObjectDeref;
pub use crate::qom::ObjectClassMethods;
pub use crate::qom::ObjectMethods;
pub use crate::qom::ObjectType;

View File

@ -4,19 +4,89 @@
//! Bindings to create devices and access device functionality from Rust.
use std::{ffi::CStr, ptr::NonNull};
use std::{
ffi::{CStr, CString},
os::raw::{c_int, c_void},
ptr::NonNull,
};
pub use bindings::{DeviceClass, DeviceState, Property};
pub use bindings::{Clock, ClockEvent, DeviceClass, DeviceState, Property, ResetType};
use crate::{
bindings::{self, Error},
bindings::{self, qdev_init_gpio_in, qdev_init_gpio_out, Error, ResettableClass},
callbacks::FnCall,
cell::bql_locked,
chardev::Chardev,
irq::InterruptSource,
prelude::*,
qom::{ClassInitImpl, ObjectClass},
qom::{ClassInitImpl, ObjectClass, ObjectImpl, Owned},
vmstate::VMStateDescription,
};
/// Trait providing the contents of the `ResettablePhases` struct,
/// which is part of the QOM `Resettable` interface.
pub trait ResettablePhasesImpl {
/// If not None, this is called when the object enters reset. It
/// can reset local state of the object, but it must not do anything that
/// has a side-effect on other objects, such as raising or lowering an
/// [`InterruptSource`], or reading or writing guest memory. It takes the
/// reset's type as argument.
const ENTER: Option<fn(&Self, ResetType)> = None;
/// If not None, this is called when the object for entry into reset, once
/// every object in the system which is being reset has had its
/// `ResettablePhasesImpl::ENTER` method called. At this point devices
/// can do actions that affect other objects.
///
/// If in doubt, implement this method.
const HOLD: Option<fn(&Self, ResetType)> = None;
/// If not None, this phase is called when the object leaves the reset
/// state. Actions affecting other objects are permitted.
const EXIT: Option<fn(&Self, ResetType)> = None;
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer that
/// can be downcasted to type `T`. We also expect the device is
/// readable/writeable from one thread at any time.
unsafe extern "C" fn rust_resettable_enter_fn<T: ResettablePhasesImpl>(
obj: *mut Object,
typ: ResetType,
) {
let state = NonNull::new(obj).unwrap().cast::<T>();
T::ENTER.unwrap()(unsafe { state.as_ref() }, typ);
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer that
/// can be downcasted to type `T`. We also expect the device is
/// readable/writeable from one thread at any time.
unsafe extern "C" fn rust_resettable_hold_fn<T: ResettablePhasesImpl>(
obj: *mut Object,
typ: ResetType,
) {
let state = NonNull::new(obj).unwrap().cast::<T>();
T::HOLD.unwrap()(unsafe { state.as_ref() }, typ);
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer that
/// can be downcasted to type `T`. We also expect the device is
/// readable/writeable from one thread at any time.
unsafe extern "C" fn rust_resettable_exit_fn<T: ResettablePhasesImpl>(
obj: *mut Object,
typ: ResetType,
) {
let state = NonNull::new(obj).unwrap().cast::<T>();
T::EXIT.unwrap()(unsafe { state.as_ref() }, typ);
}
/// Trait providing the contents of [`DeviceClass`].
pub trait DeviceImpl {
pub trait DeviceImpl: ObjectImpl + ResettablePhasesImpl {
/// _Realization_ is the second stage of device creation. It contains
/// all operations that depend on device properties and can fail (note:
/// this is not yet supported for Rust devices).
@ -25,13 +95,6 @@ pub trait DeviceImpl {
/// with the function pointed to by `REALIZE`.
const REALIZE: Option<fn(&Self)> = None;
/// If not `None`, the parent class's `reset` method is overridden
/// with the function pointed to by `RESET`.
///
/// Rust does not yet support the three-phase reset protocol; this is
/// usually okay for leaf classes.
const RESET: Option<fn(&Self)> = None;
/// An array providing the properties that the user can set on the
/// device. Not a `const` because referencing statics in constants
/// is unstable until Rust 1.83.0.
@ -59,29 +122,36 @@ unsafe extern "C" fn rust_realize_fn<T: DeviceImpl>(dev: *mut DeviceState, _errp
T::REALIZE.unwrap()(unsafe { state.as_ref() });
}
/// # Safety
///
/// We expect the FFI user of this function to pass a valid pointer that
/// can be downcasted to type `T`. We also expect the device is
/// readable/writeable from one thread at any time.
unsafe extern "C" fn rust_reset_fn<T: DeviceImpl>(dev: *mut DeviceState) {
let mut state = NonNull::new(dev).unwrap().cast::<T>();
T::RESET.unwrap()(unsafe { state.as_mut() });
unsafe impl InterfaceType for ResettableClass {
const TYPE_NAME: &'static CStr =
unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_RESETTABLE_INTERFACE) };
}
impl<T> ClassInitImpl<ResettableClass> for T
where
T: ResettablePhasesImpl,
{
fn class_init(rc: &mut ResettableClass) {
if <T as ResettablePhasesImpl>::ENTER.is_some() {
rc.phases.enter = Some(rust_resettable_enter_fn::<T>);
}
if <T as ResettablePhasesImpl>::HOLD.is_some() {
rc.phases.hold = Some(rust_resettable_hold_fn::<T>);
}
if <T as ResettablePhasesImpl>::EXIT.is_some() {
rc.phases.exit = Some(rust_resettable_exit_fn::<T>);
}
}
}
impl<T> ClassInitImpl<DeviceClass> for T
where
T: ClassInitImpl<ObjectClass> + DeviceImpl,
T: ClassInitImpl<ObjectClass> + ClassInitImpl<ResettableClass> + DeviceImpl,
{
fn class_init(dc: &mut DeviceClass) {
if <T as DeviceImpl>::REALIZE.is_some() {
dc.realize = Some(rust_realize_fn::<T>);
}
if <T as DeviceImpl>::RESET.is_some() {
unsafe {
bindings::device_class_set_legacy_reset(dc, Some(rust_reset_fn::<T>));
}
}
if let Some(vmsd) = <T as DeviceImpl>::vmsd() {
dc.vmsd = vmsd;
}
@ -92,12 +162,25 @@ where
}
}
ResettableClass::interface_init::<T, DeviceState>(dc);
<T as ClassInitImpl<ObjectClass>>::class_init(&mut dc.parent_class);
}
}
#[macro_export]
macro_rules! define_property {
($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty, bit = $bitnr:expr, default = $defval:expr$(,)*) => {
$crate::bindings::Property {
// use associated function syntax for type checking
name: ::std::ffi::CStr::as_ptr($name),
info: $prop,
offset: $crate::offset_of!($state, $field) as isize,
bitnr: $bitnr,
set_default: true,
defval: $crate::bindings::Property__bindgen_ty_1 { u: $defval as u64 },
..$crate::zeroable::Zeroable::ZERO
}
};
($name:expr, $state:ty, $field:ident, $prop:expr, $type:ty, default = $defval:expr$(,)*) => {
$crate::bindings::Property {
// use associated function syntax for type checking
@ -143,3 +226,144 @@ unsafe impl ObjectType for DeviceState {
unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_DEVICE) };
}
qom_isa!(DeviceState: Object);
/// Trait for methods exposed by the [`DeviceState`] class. The methods can be
/// called on all objects that have the trait `IsA<DeviceState>`.
///
/// The trait should only be used through the blanket implementation,
/// which guarantees safety via `IsA`.
pub trait DeviceMethods: ObjectDeref
where
Self::Target: IsA<DeviceState>,
{
/// Add an input clock named `name`. Invoke the callback with
/// `self` as the first parameter for the events that are requested.
///
/// The resulting clock is added as a child of `self`, but it also
/// stays alive until after `Drop::drop` is called because C code
/// keeps an extra reference to it until `device_finalize()` calls
/// `qdev_finalize_clocklist()`. Therefore (unlike most cases in
/// which Rust code has a reference to a child object) it would be
/// possible for this function to return a `&Clock` too.
#[inline]
fn init_clock_in<F: for<'a> FnCall<(&'a Self::Target, ClockEvent)>>(
&self,
name: &str,
_cb: &F,
events: ClockEvent,
) -> Owned<Clock> {
fn do_init_clock_in(
dev: *mut DeviceState,
name: &str,
cb: Option<unsafe extern "C" fn(*mut c_void, ClockEvent)>,
events: ClockEvent,
) -> Owned<Clock> {
assert!(bql_locked());
// SAFETY: the clock is heap allocated, but qdev_init_clock_in()
// does not gift the reference to its caller; so use Owned::from to
// add one. The callback is disabled automatically when the clock
// is unparented, which happens before the device is finalized.
unsafe {
let cstr = CString::new(name).unwrap();
let clk = bindings::qdev_init_clock_in(
dev,
cstr.as_ptr(),
cb,
dev.cast::<c_void>(),
events.0,
);
Owned::from(&*clk)
}
}
let cb: Option<unsafe extern "C" fn(*mut c_void, ClockEvent)> = if F::is_some() {
unsafe extern "C" fn rust_clock_cb<T, F: for<'a> FnCall<(&'a T, ClockEvent)>>(
opaque: *mut c_void,
event: ClockEvent,
) {
// SAFETY: the opaque is "this", which is indeed a pointer to T
F::call((unsafe { &*(opaque.cast::<T>()) }, event))
}
Some(rust_clock_cb::<Self::Target, F>)
} else {
None
};
do_init_clock_in(self.as_mut_ptr(), name, cb, events)
}
/// Add an output clock named `name`.
///
/// The resulting clock is added as a child of `self`, but it also
/// stays alive until after `Drop::drop` is called because C code
/// keeps an extra reference to it until `device_finalize()` calls
/// `qdev_finalize_clocklist()`. Therefore (unlike most cases in
/// which Rust code has a reference to a child object) it would be
/// possible for this function to return a `&Clock` too.
#[inline]
fn init_clock_out(&self, name: &str) -> Owned<Clock> {
unsafe {
let cstr = CString::new(name).unwrap();
let clk = bindings::qdev_init_clock_out(self.as_mut_ptr(), cstr.as_ptr());
Owned::from(&*clk)
}
}
fn prop_set_chr(&self, propname: &str, chr: &Owned<Chardev>) {
assert!(bql_locked());
let c_propname = CString::new(propname).unwrap();
unsafe {
bindings::qdev_prop_set_chr(self.as_mut_ptr(), c_propname.as_ptr(), chr.as_mut_ptr());
}
}
fn init_gpio_in<F: for<'a> FnCall<(&'a Self::Target, u32, u32)>>(
&self,
num_lines: u32,
_cb: F,
) {
let _: () = F::ASSERT_IS_SOME;
unsafe extern "C" fn rust_irq_handler<T, F: for<'a> FnCall<(&'a T, u32, u32)>>(
opaque: *mut c_void,
line: c_int,
level: c_int,
) {
// SAFETY: the opaque was passed as a reference to `T`
F::call((unsafe { &*(opaque.cast::<T>()) }, line as u32, level as u32))
}
let gpio_in_cb: unsafe extern "C" fn(*mut c_void, c_int, c_int) =
rust_irq_handler::<Self::Target, F>;
unsafe {
qdev_init_gpio_in(
self.as_mut_ptr::<DeviceState>(),
Some(gpio_in_cb),
num_lines as c_int,
);
}
}
fn init_gpio_out(&self, pins: &[InterruptSource]) {
unsafe {
qdev_init_gpio_out(
self.as_mut_ptr::<DeviceState>(),
InterruptSource::slice_as_ptr(pins),
pins.len() as c_int,
);
}
}
}
impl<R: ObjectDeref> DeviceMethods for R where R::Target: IsA<DeviceState> {}
unsafe impl ObjectType for Clock {
type Class = ObjectClass;
const TYPE_NAME: &'static CStr =
unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_CLOCK) };
}
qom_isa!(Clock: Object);

View File

@ -56,6 +56,7 @@
use std::{
ffi::CStr,
fmt,
mem::ManuallyDrop,
ops::{Deref, DerefMut},
os::raw::c_void,
ptr::NonNull,
@ -63,7 +64,13 @@ use std::{
pub use bindings::{Object, ObjectClass};
use crate::bindings::{self, object_dynamic_cast, object_get_class, object_get_typename, TypeInfo};
use crate::{
bindings::{
self, object_class_dynamic_cast, object_dynamic_cast, object_get_class,
object_get_typename, object_new, object_ref, object_unref, TypeInfo,
},
cell::bql_locked,
};
/// Marker trait: `Self` can be statically upcasted to `P` (i.e. `P` is a direct
/// or indirect parent of `Self`).
@ -256,6 +263,47 @@ pub unsafe trait ObjectType: Sized {
}
}
/// Trait exposed by all structs corresponding to QOM interfaces.
/// Unlike `ObjectType`, it is implemented on the class type (which provides
/// the vtable for the interfaces).
///
/// # Safety
///
/// `TYPE` must match the contents of the `TypeInfo` as found in the C code;
/// right now, interfaces can only be declared in C.
pub unsafe trait InterfaceType: Sized {
/// The name of the type, which can be passed to
/// `object_class_dynamic_cast()` to obtain the pointer to the vtable
/// for this interface.
const TYPE_NAME: &'static CStr;
/// Initialize the vtable for the interface; the generic argument `T` is the
/// type being initialized, while the generic argument `U` is the type that
/// lists the interface in its `TypeInfo`.
///
/// # Panics
///
/// Panic if the incoming argument if `T` does not implement the interface.
fn interface_init<
T: ObjectType + ClassInitImpl<Self> + ClassInitImpl<U::Class>,
U: ObjectType,
>(
klass: &mut U::Class,
) {
unsafe {
// SAFETY: upcasting to ObjectClass is always valid, and the
// return type is either NULL or the argument itself
let result: *mut Self = object_class_dynamic_cast(
(klass as *mut U::Class).cast(),
Self::TYPE_NAME.as_ptr(),
)
.cast();
<T as ClassInitImpl<Self>>::class_init(result.as_mut().unwrap())
}
}
}
/// This trait provides safe casting operations for QOM objects to raw pointers,
/// to be used for example for FFI. The trait can be applied to any kind of
/// reference or smart pointers, and enforces correctness through the [`IsA`]
@ -280,10 +328,10 @@ where
///
/// # Safety
///
/// This method is unsafe because it overrides const-ness of `&self`.
/// Bindings to C APIs will use it a lot, but otherwise it should not
/// be necessary.
unsafe fn as_mut_ptr<U: ObjectType>(&self) -> *mut U
/// This method is safe because only the actual dereference of the pointer
/// has to be unsafe. Bindings to C APIs will use it a lot, but care has
/// to be taken because it overrides the const-ness of `&self`.
fn as_mut_ptr<U: ObjectType>(&self) -> *mut U
where
Self::Target: IsA<U>,
{
@ -610,6 +658,166 @@ unsafe impl ObjectType for Object {
unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_OBJECT) };
}
/// A reference-counted pointer to a QOM object.
///
/// `Owned<T>` wraps `T` with automatic reference counting. It increases the
/// reference count when created via [`Owned::from`] or cloned, and decreases
/// it when dropped. This ensures that the reference count remains elevated
/// as long as any `Owned<T>` references to it exist.
///
/// `Owned<T>` can be used for two reasons:
/// * because the lifetime of the QOM object is unknown and someone else could
/// take a reference (similar to `Arc<T>`, for example): in this case, the
/// object can escape and outlive the Rust struct that contains the `Owned<T>`
/// field;
///
/// * to ensure that the object stays alive until after `Drop::drop` is called
/// on the Rust struct: in this case, the object will always die together with
/// the Rust struct that contains the `Owned<T>` field.
///
/// Child properties are an example of the second case: in C, an object that
/// is created with `object_initialize_child` will die *before*
/// `instance_finalize` is called, whereas Rust expects the struct to have valid
/// contents when `Drop::drop` is called. Therefore Rust structs that have
/// child properties need to keep a reference to the child object. Right now
/// this can be done with `Owned<T>`; in the future one might have a separate
/// `Child<'parent, T>` smart pointer that keeps a reference to a `T`, like
/// `Owned`, but does not allow cloning.
///
/// Note that dropping an `Owned<T>` requires the big QEMU lock to be taken.
#[repr(transparent)]
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Owned<T: ObjectType>(NonNull<T>);
// The following rationale for safety is taken from Linux's kernel::sync::Arc.
// SAFETY: It is safe to send `Owned<T>` to another thread when the underlying
// `T` is `Sync` because it effectively means sharing `&T` (which is safe
// because `T` is `Sync`); additionally, it needs `T` to be `Send` because any
// thread that has an `Owned<T>` may ultimately access `T` using a
// mutable reference when the reference count reaches zero and `T` is dropped.
unsafe impl<T: ObjectType + Send + Sync> Send for Owned<T> {}
// SAFETY: It is safe to send `&Owned<T>` to another thread when the underlying
// `T` is `Sync` because it effectively means sharing `&T` (which is safe
// because `T` is `Sync`); additionally, it needs `T` to be `Send` because any
// thread that has a `&Owned<T>` may clone it and get an `Owned<T>` on that
// thread, so the thread may ultimately access `T` using a mutable reference
// when the reference count reaches zero and `T` is dropped.
unsafe impl<T: ObjectType + Sync + Send> Sync for Owned<T> {}
impl<T: ObjectType> Owned<T> {
/// Convert a raw C pointer into an owned reference to the QOM
/// object it points to. The object's reference count will be
/// decreased when the `Owned` is dropped.
///
/// # Panics
///
/// Panics if `ptr` is NULL.
///
/// # Safety
///
/// The caller must indeed own a reference to the QOM object.
/// The object must not be embedded in another unless the outer
/// object is guaranteed to have a longer lifetime.
///
/// A raw pointer obtained via [`Owned::into_raw()`] can always be passed
/// back to `from_raw()` (assuming the original `Owned` was valid!),
/// since the owned reference remains there between the calls to
/// `into_raw()` and `from_raw()`.
pub unsafe fn from_raw(ptr: *const T) -> Self {
// SAFETY NOTE: while NonNull requires a mutable pointer, only
// Deref is implemented so the pointer passed to from_raw
// remains const
Owned(NonNull::new(ptr as *mut T).unwrap())
}
/// Obtain a raw C pointer from a reference. `src` is consumed
/// and the reference is leaked.
#[allow(clippy::missing_const_for_fn)]
pub fn into_raw(src: Owned<T>) -> *mut T {
let src = ManuallyDrop::new(src);
src.0.as_ptr()
}
/// Increase the reference count of a QOM object and return
/// a new owned reference to it.
///
/// # Safety
///
/// The object must not be embedded in another, unless the outer
/// object is guaranteed to have a longer lifetime.
pub unsafe fn from(obj: &T) -> Self {
unsafe {
object_ref(obj.as_object_mut_ptr().cast::<c_void>());
// SAFETY NOTE: while NonNull requires a mutable pointer, only
// Deref is implemented so the reference passed to from_raw
// remains shared
Owned(NonNull::new_unchecked(obj.as_mut_ptr()))
}
}
}
impl<T: ObjectType> Clone for Owned<T> {
fn clone(&self) -> Self {
// SAFETY: creation method is unsafe; whoever calls it has
// responsibility that the pointer is valid, and remains valid
// throughout the lifetime of the `Owned<T>` and its clones.
unsafe { Owned::from(self.deref()) }
}
}
impl<T: ObjectType> Deref for Owned<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
// SAFETY: creation method is unsafe; whoever calls it has
// responsibility that the pointer is valid, and remains valid
// throughout the lifetime of the `Owned<T>` and its clones.
// With that guarantee, reference counting ensures that
// the object remains alive.
unsafe { &*self.0.as_ptr() }
}
}
impl<T: ObjectType> ObjectDeref for Owned<T> {}
impl<T: ObjectType> Drop for Owned<T> {
fn drop(&mut self) {
assert!(bql_locked());
// SAFETY: creation method is unsafe, and whoever calls it has
// responsibility that the pointer is valid, and remains valid
// throughout the lifetime of the `Owned<T>` and its clones.
unsafe {
object_unref(self.as_object_mut_ptr().cast::<c_void>());
}
}
}
impl<T: IsA<Object>> fmt::Debug for Owned<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.deref().debug_fmt(f)
}
}
/// Trait for class methods exposed by the Object class. The methods can be
/// called on all objects that have the trait `IsA<Object>`.
///
/// The trait should only be used through the blanket implementation,
/// which guarantees safety via `IsA`
pub trait ObjectClassMethods: IsA<Object> {
/// Return a new reference counted instance of this class
fn new() -> Owned<Self> {
assert!(bql_locked());
// SAFETY: the object created by object_new is allocated on
// the heap and has a reference count of 1
unsafe {
let obj = &*object_new(Self::TYPE_NAME.as_ptr());
Owned::from_raw(obj.unsafe_cast::<Self>())
}
}
}
/// Trait for methods exposed by the Object class. The methods can be
/// called on all objects that have the trait `IsA<Object>`.
///
@ -641,6 +849,14 @@ where
klass
}
/// Convenience function for implementing the Debug trait
fn debug_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple(&self.typename())
.field(&(self as *const Self))
.finish()
}
}
impl<T> ObjectClassMethods for T where T: IsA<Object> {}
impl<R: ObjectDeref> ObjectMethods for R where R::Target: IsA<Object> {}

View File

@ -2,17 +2,20 @@
// Author(s): Paolo Bonzini <pbonzini@redhat.com>
// SPDX-License-Identifier: GPL-2.0-or-later
use std::{ffi::CStr, ptr::addr_of};
//! Bindings to access `sysbus` functionality from Rust.
use std::{ffi::CStr, ptr::addr_of_mut};
pub use bindings::{SysBusDevice, SysBusDeviceClass};
use crate::{
bindings,
cell::bql_locked,
irq::InterruptSource,
irq::{IRQState, InterruptSource},
memory::MemoryRegion,
prelude::*,
qdev::{DeviceClass, DeviceState},
qom::ClassInitImpl,
qom::{ClassInitImpl, Owned},
};
unsafe impl ObjectType for SysBusDevice {
@ -42,10 +45,10 @@ where
/// important, since whoever creates the sysbus device will refer to the
/// region with a number that corresponds to the order of calls to
/// `init_mmio`.
fn init_mmio(&self, iomem: &bindings::MemoryRegion) {
fn init_mmio(&self, iomem: &MemoryRegion) {
assert!(bql_locked());
unsafe {
bindings::sysbus_init_mmio(self.as_mut_ptr(), addr_of!(*iomem) as *mut _);
bindings::sysbus_init_mmio(self.as_mut_ptr(), iomem.as_mut_ptr());
}
}
@ -59,6 +62,34 @@ where
bindings::sysbus_init_irq(self.as_mut_ptr(), irq.as_ptr());
}
}
// TODO: do we want a type like GuestAddress here?
fn mmio_map(&self, id: u32, addr: u64) {
assert!(bql_locked());
let id: i32 = id.try_into().unwrap();
unsafe {
bindings::sysbus_mmio_map(self.as_mut_ptr(), id, addr);
}
}
// Owned<> is used here because sysbus_connect_irq (via
// object_property_set_link) adds a reference to the IRQState,
// which can prolong its life
fn connect_irq(&self, id: u32, irq: &Owned<IRQState>) {
assert!(bql_locked());
let id: i32 = id.try_into().unwrap();
unsafe {
bindings::sysbus_connect_irq(self.as_mut_ptr(), id, irq.as_mut_ptr());
}
}
fn sysbus_realize(&self) {
// TODO: return an Error
assert!(bql_locked());
unsafe {
bindings::sysbus_realize(self.as_mut_ptr(), addr_of_mut!(bindings::error_fatal));
}
}
}
impl<R: ObjectDeref> SysBusDeviceMethods for R where R::Target: IsA<SysBusDevice> {}

View File

@ -0,0 +1,98 @@
// Copyright (C) 2024 Intel Corporation.
// Author(s): Zhao Liu <zhai1.liu@intel.com>
// SPDX-License-Identifier: GPL-2.0-or-later
use std::os::raw::{c_int, c_void};
use crate::{
bindings::{self, qemu_clock_get_ns, timer_del, timer_init_full, timer_mod, QEMUClockType},
callbacks::FnCall,
};
pub type Timer = bindings::QEMUTimer;
pub type TimerListGroup = bindings::QEMUTimerListGroup;
impl Timer {
pub const MS: u32 = bindings::SCALE_MS;
pub const US: u32 = bindings::SCALE_US;
pub const NS: u32 = bindings::SCALE_NS;
pub fn new() -> Self {
Default::default()
}
const fn as_mut_ptr(&self) -> *mut Self {
self as *const Timer as *mut _
}
pub fn init_full<'timer, 'opaque: 'timer, T, F>(
&'timer mut self,
timer_list_group: Option<&TimerListGroup>,
clk_type: ClockType,
scale: u32,
attributes: u32,
_cb: F,
opaque: &'opaque T,
) where
F: for<'a> FnCall<(&'a T,)>,
{
let _: () = F::ASSERT_IS_SOME;
/// timer expiration callback
unsafe extern "C" fn rust_timer_handler<T, F: for<'a> FnCall<(&'a T,)>>(
opaque: *mut c_void,
) {
// SAFETY: the opaque was passed as a reference to `T`.
F::call((unsafe { &*(opaque.cast::<T>()) },))
}
let timer_cb: unsafe extern "C" fn(*mut c_void) = rust_timer_handler::<T, F>;
// SAFETY: the opaque outlives the timer
unsafe {
timer_init_full(
self,
if let Some(g) = timer_list_group {
g as *const TimerListGroup as *mut _
} else {
::core::ptr::null_mut()
},
clk_type.id,
scale as c_int,
attributes as c_int,
Some(timer_cb),
(opaque as *const T).cast::<c_void>() as *mut c_void,
)
}
}
pub fn modify(&self, expire_time: u64) {
unsafe { timer_mod(self.as_mut_ptr(), expire_time as i64) }
}
pub fn delete(&self) {
unsafe { timer_del(self.as_mut_ptr()) }
}
}
impl Drop for Timer {
fn drop(&mut self) {
self.delete()
}
}
pub struct ClockType {
id: QEMUClockType,
}
impl ClockType {
pub fn get_ns(&self) -> u64 {
// SAFETY: cannot be created outside this module, therefore id
// is valid
(unsafe { qemu_clock_get_ns(self.id) }) as u64
}
}
pub const CLOCK_VIRTUAL: ClockType = ClockType {
id: QEMUClockType::QEMU_CLOCK_VIRTUAL,
};

View File

@ -29,6 +29,8 @@ use core::{marker::PhantomData, mem, ptr::NonNull};
pub use crate::bindings::{VMStateDescription, VMStateField};
use crate::{
bindings::{self, VMStateFlags},
prelude::*,
qom::Owned,
zeroable::Zeroable,
};
@ -189,9 +191,9 @@ pub const fn vmstate_varray_flag<T: VMState>(_: PhantomData<T>) -> VMStateFlags
/// * scalar types (integer and `bool`)
/// * the C struct `QEMUTimer`
/// * a transparent wrapper for any of the above (`Cell`, `UnsafeCell`,
/// [`BqlCell`](crate::cell::BqlCell), [`BqlRefCell`](crate::cell::BqlRefCell)
/// [`BqlCell`], [`BqlRefCell`]
/// * a raw pointer to any of the above
/// * a `NonNull` pointer or a `Box` for any of the above
/// * a `NonNull` pointer, a `Box` or an [`Owned`] for any of the above
/// * an array of any of the above
///
/// In order to support other types, the trait `VMState` must be implemented
@ -292,7 +294,7 @@ impl VMStateField {
/// # Examples
///
/// ```
/// # use qemu_api::vmstate::impl_vmstate_forward;
/// # use qemu_api::impl_vmstate_forward;
/// pub struct Fifo([u8; 16]);
/// impl_vmstate_forward!(Fifo);
/// ```
@ -398,6 +400,7 @@ impl_vmstate_pointer!(NonNull<T> where T: VMState);
// Unlike C pointers, Box is always non-null therefore there is no need
// to specify VMS_ALLOC.
impl_vmstate_pointer!(Box<T> where T: VMState);
impl_vmstate_pointer!(Owned<T> where T: VMState + ObjectType);
// Arrays using the underlying type's VMState plus
// VMS_ARRAY/VMS_ARRAY_OF_POINTER
@ -466,11 +469,11 @@ macro_rules! vmstate_clock {
$crate::assert_field_type!(
$struct_name,
$field_name,
core::ptr::NonNull<$crate::bindings::Clock>
$crate::qom::Owned<$crate::bindings::Clock>
);
$crate::offset_of!($struct_name, $field_name)
},
size: ::core::mem::size_of::<*const $crate::bindings::Clock>(),
size: ::core::mem::size_of::<*const $crate::qdev::Clock>(),
flags: VMStateFlags(VMStateFlags::VMS_STRUCT.0 | VMStateFlags::VMS_POINTER.0),
vmsd: unsafe { ::core::ptr::addr_of!($crate::bindings::vmstate_clock) },
..$crate::zeroable::Zeroable::ZERO

View File

@ -1,11 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later
//! Defines a trait for structs that can be safely initialized with zero bytes.
/// Encapsulates the requirement that
/// `MaybeUninit::<Self>::zeroed().assume_init()` does not cause undefined
/// behavior. This trait in principle could be implemented as just:
///
/// ```
/// pub unsafe trait Zeroable {
/// pub unsafe trait Zeroable: Default {
/// const ZERO: Self = unsafe { ::core::mem::MaybeUninit::<Self>::zeroed().assume_init() };
/// }
/// ```
@ -56,6 +58,7 @@ pub unsafe trait Zeroable: Default {
/// ## Differences with `core::mem::zeroed`
///
/// `const_zero` zeroes padding bits, while `core::mem::zeroed` doesn't
#[macro_export]
macro_rules! const_zero {
// This macro to produce a type-generic zero constant is taken from the
// const_zero crate (v0.1.1):
@ -77,10 +80,11 @@ macro_rules! const_zero {
}
/// A wrapper to implement the `Zeroable` trait through the `const_zero` macro.
#[macro_export]
macro_rules! impl_zeroable {
($type:ty) => {
unsafe impl Zeroable for $type {
const ZERO: Self = unsafe { const_zero!($type) };
unsafe impl $crate::zeroable::Zeroable for $type {
const ZERO: Self = unsafe { $crate::const_zero!($type) };
}
};
}
@ -100,3 +104,5 @@ impl_zeroable!(crate::bindings::VMStateField);
impl_zeroable!(crate::bindings::VMStateDescription);
impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_1);
impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_2);
impl_zeroable!(crate::bindings::MemoryRegionOps);
impl_zeroable!(crate::bindings::MemTxAttrs);

View File

@ -3,8 +3,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
use std::{
ffi::CStr,
os::raw::c_void,
ffi::{c_void, CStr},
ptr::{addr_of, addr_of_mut},
};
@ -14,7 +13,7 @@ use qemu_api::{
cell::{self, BqlCell},
declare_properties, define_property,
prelude::*,
qdev::{DeviceClass, DeviceImpl, DeviceState, Property},
qdev::{DeviceClass, DeviceImpl, DeviceState, Property, ResettablePhasesImpl},
qom::{ClassInitImpl, ObjectImpl, ParentField},
vmstate::VMStateDescription,
zeroable::Zeroable,
@ -62,6 +61,8 @@ impl ObjectImpl for DummyState {
const ABSTRACT: bool = false;
}
impl ResettablePhasesImpl for DummyState {}
impl DeviceImpl for DummyState {
fn properties() -> &'static [Property] {
&DUMMY_PROPERTIES
@ -102,6 +103,7 @@ impl ObjectImpl for DummyChildState {
const ABSTRACT: bool = false;
}
impl ResettablePhasesImpl for DummyChildState {}
impl DeviceImpl for DummyChildState {}
impl ClassInitImpl<DummyClass> for DummyChildState {
@ -132,22 +134,26 @@ fn init_qom() {
/// Create and immediately drop an instance.
fn test_object_new() {
init_qom();
unsafe {
object_unref(object_new(DummyState::TYPE_NAME.as_ptr()).cast());
object_unref(object_new(DummyChildState::TYPE_NAME.as_ptr()).cast());
}
drop(DummyState::new());
drop(DummyChildState::new());
}
#[test]
#[allow(clippy::redundant_clone)]
/// Create, clone and then drop an instance.
fn test_clone() {
init_qom();
let p = DummyState::new();
assert_eq!(p.clone().typename(), "dummy");
drop(p);
}
#[test]
/// Try invoking a method on an object.
fn test_typename() {
init_qom();
let p: *mut DummyState = unsafe { object_new(DummyState::TYPE_NAME.as_ptr()).cast() };
let p_ref: &DummyState = unsafe { &*p };
assert_eq!(p_ref.typename(), "dummy");
unsafe {
object_unref(p_ref.as_object_mut_ptr().cast::<c_void>());
}
let p = DummyState::new();
assert_eq!(p.typename(), "dummy");
}
// a note on all "cast" tests: usually, especially for downcasts the desired
@ -162,24 +168,23 @@ fn test_typename() {
/// Test casts on shared references.
fn test_cast() {
init_qom();
let p: *mut DummyState = unsafe { object_new(DummyState::TYPE_NAME.as_ptr()).cast() };
let p = DummyState::new();
let p_ptr: *mut DummyState = p.as_mut_ptr();
let p_ref: &mut DummyState = unsafe { &mut *p_ptr };
let p_ref: &DummyState = unsafe { &*p };
let obj_ref: &Object = p_ref.upcast();
assert_eq!(addr_of!(*obj_ref), p.cast());
assert_eq!(addr_of!(*obj_ref), p_ptr.cast());
let sbd_ref: Option<&SysBusDevice> = obj_ref.dynamic_cast();
assert!(sbd_ref.is_none());
let dev_ref: Option<&DeviceState> = obj_ref.downcast();
assert_eq!(addr_of!(*dev_ref.unwrap()), p.cast());
assert_eq!(addr_of!(*dev_ref.unwrap()), p_ptr.cast());
// SAFETY: the cast is wrong, but the value is only used for comparison
unsafe {
let sbd_ref: &SysBusDevice = obj_ref.unsafe_cast();
assert_eq!(addr_of!(*sbd_ref), p.cast());
object_unref(p_ref.as_object_mut_ptr().cast::<c_void>());
assert_eq!(addr_of!(*sbd_ref), p_ptr.cast());
}
}

View File

@ -62,3 +62,6 @@ typedef enum memory_order {
#include "qapi/error.h"
#include "migration/vmstate.h"
#include "chardev/char-serial.h"
#include "exec/memattrs.h"
#include "qemu/timer.h"
#include "exec/address-spaces.h"

View File

@ -103,7 +103,8 @@ 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') 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_PARALLEL') ? ['bios-tables-test'] : []) + \
qtests_pci + \
qtests_cxl + \

View File

@ -120,10 +120,6 @@ if gtk.found()
endif
if sdl.found()
if host_os == 'windows'
system_ss.add(files('win32-kbd-hook.c'))
endif
sdl_ss = ss.source_set()
sdl_ss.add(sdl, sdl_image, pixman, glib, files(
'sdl2-2d.c',

View File

@ -32,7 +32,6 @@
#include "system/runstate.h"
#include "system/runstate-action.h"
#include "system/system.h"
#include "ui/win32-kbd-hook.h"
#include "qemu/log.h"
#include "qemu-main.h"
@ -263,7 +262,6 @@ static void sdl_grab_start(struct sdl2_console *scon)
}
SDL_SetWindowGrab(scon->real_window, SDL_TRUE);
gui_grab = 1;
win32_kbd_set_grab(true);
sdl_update_caption(scon);
}
@ -271,7 +269,6 @@ static void sdl_grab_end(struct sdl2_console *scon)
{
SDL_SetWindowGrab(scon->real_window, SDL_FALSE);
gui_grab = 0;
win32_kbd_set_grab(false);
sdl_show_cursor(scon);
sdl_update_caption(scon);
}
@ -372,19 +369,6 @@ static int get_mod_state(void)
}
}
static void *sdl2_win32_get_hwnd(struct sdl2_console *scon)
{
#ifdef CONFIG_WIN32
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
if (SDL_GetWindowWMInfo(scon->real_window, &info)) {
return info.info.win.window;
}
#endif
return NULL;
}
static void handle_keydown(SDL_Event *ev)
{
int win;
@ -609,10 +593,6 @@ static void handle_windowevent(SDL_Event *ev)
sdl2_redraw(scon);
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
win32_kbd_set_grab(gui_grab);
if (qemu_console_is_graphic(scon->dcl.con)) {
win32_kbd_set_window(sdl2_win32_get_hwnd(scon));
}
/* fall through */
case SDL_WINDOWEVENT_ENTER:
if (!gui_grab && (qemu_input_is_absolute(scon->dcl.con) || absolute_enabled)) {
@ -628,9 +608,6 @@ static void handle_windowevent(SDL_Event *ev)
scon->ignore_hotkeys = get_mod_state();
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
if (qemu_console_is_graphic(scon->dcl.con)) {
win32_kbd_set_window(NULL);
}
if (gui_grab && !gui_fullscreen) {
sdl_grab_end(scon);
}
@ -870,10 +847,7 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR /* only available since SDL 2.0.8 */
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
#endif
#ifndef CONFIG_WIN32
/* QEMU uses its own low level keyboard hook procedure on Windows */
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
#endif
#ifdef SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED
SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0");
#endif