Manos Pitsidianakis <[email protected]> writes:
> This commit adds a re-implementation of hw/char/pl011.c in Rust.
>
> How to build:
>
> 1. Configure a QEMU build with:
> --enable-system --target-list=aarch64-softmmu --enable-rust
> 2. Launching a VM with qemu-system-aarch64 should use the Rust version
> of the pl011 device
>
> Co-authored-by: Junjie Mao <[email protected]>
> Co-authored-by: Paolo Bonzini <[email protected]>
> Signed-off-by: Junjie Mao <[email protected]>
> Signed-off-by: Paolo Bonzini <[email protected]>
> Signed-off-by: Manos Pitsidianakis <[email protected]>
> ---
[snip]
> diff --git a/rust/hw/char/pl011/src/device.rs
> b/rust/hw/char/pl011/src/device.rs
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..6bd121b83ae831e838b05d83b67c698474b00b4a
> --- /dev/null
> +++ b/rust/hw/char/pl011/src/device.rs
> @@ -0,0 +1,600 @@
> +// Copyright 2024, Linaro Limited
> +// Author(s): Manos Pitsidianakis <[email protected]>
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +use core::{
> + ffi::{c_int, c_uchar, c_uint, c_void, CStr},
> + ptr::{addr_of, addr_of_mut, NonNull},
> +};
> +
> +use qemu_api::{
> + bindings::{self, *},
> + definitions::ObjectImpl,
> +};
> +
> +use crate::{
> + memory_ops::PL011_OPS,
> + registers::{self, Interrupt},
> + RegisterOffset,
> +};
> +
> +static PL011_ID_ARM: [c_uchar; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0,
> 0x05, 0xb1];
> +
> +const DATA_BREAK: u32 = 1 << 10;
> +
> +/// QEMU sourced constant.
> +pub const PL011_FIFO_DEPTH: usize = 16_usize;
> +
> +#[repr(C)]
> +#[derive(Debug, qemu_api_macros::Object)]
> +/// PL011 Device Model in QEMU
> +pub struct PL011State {
> + pub parent_obj: SysBusDevice,
> + pub iomem: MemoryRegion,
> + #[doc(alias = "fr")]
> + pub flags: registers::Flags,
> + #[doc(alias = "lcr")]
> + pub line_control: registers::LineControl,
> + #[doc(alias = "rsr")]
> + pub receive_status_error_clear: registers::ReceiveStatusErrorClear,
> + #[doc(alias = "cr")]
> + pub control: registers::Control,
> + pub dmacr: u32,
> + pub int_enabled: u32,
> + pub int_level: u32,
> + pub read_fifo: [u32; PL011_FIFO_DEPTH],
> + pub ilpr: u32,
> + pub ibrd: u32,
> + pub fbrd: u32,
> + pub ifl: u32,
Some of the fields can be private to the implementation of PL011State.
> + pub read_pos: usize,
> + pub read_count: usize,
> + pub read_trigger: usize,
> + #[doc(alias = "chr")]
> + pub char_backend: CharBackend,
> + /// QEMU interrupts
> + ///
> + /// ```text
> + /// * sysbus MMIO region 0: device registers
> + /// * sysbus IRQ 0: `UARTINTR` (combined interrupt line)
> + /// * sysbus IRQ 1: `UARTRXINTR` (receive FIFO interrupt line)
> + /// * sysbus IRQ 2: `UARTTXINTR` (transmit FIFO interrupt line)
> + /// * sysbus IRQ 3: `UARTRTINTR` (receive timeout interrupt line)
> + /// * sysbus IRQ 4: `UARTMSINTR` (momem status interrupt line)
> + /// * sysbus IRQ 5: `UARTEINTR` (error interrupt line)
> + /// ```
> + #[doc(alias = "irq")]
> + pub interrupts: [qemu_irq; 6usize],
> + #[doc(alias = "clk")]
> + pub clock: NonNull<Clock>,
> + #[doc(alias = "migrate_clk")]
> + pub migrate_clock: bool,
> +}
> +
> +impl ObjectImpl for PL011State {
> + type Class = PL011Class;
> + const TYPE_INFO: qemu_api::bindings::TypeInfo = qemu_api::type_info! {
> Self };
> + const TYPE_NAME: &'static CStr = c"pl011";
Can use crate::definitions::TYPE_PL011 to avoid duplication.
> + const PARENT_TYPE_NAME: Option<&'static CStr> =
> Some(TYPE_SYS_BUS_DEVICE);
> + const ABSTRACT: bool = false;
> + const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> =
> Some(pl011_init);
> + const INSTANCE_POST_INIT: Option<unsafe extern "C" fn(obj: *mut Object)>
> = None;
> + const INSTANCE_FINALIZE: Option<unsafe extern "C" fn(obj: *mut Object)>
> = None;
> +}
> +
[snip]
> +
> +/// # Safety
> +///
> +/// We expect the FFI user of this function to pass a valid pointer, that has
> +/// the same size as [`PL011State`]. We also expect the device is
> +/// readable/writeable from one thread at any time.
> +#[no_mangle]
> +pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int {
> + unsafe {
> + assert!(!opaque.is_null());
> + let state = NonNull::new_unchecked(opaque.cast::<PL011State>());
Those two lines can be combined as:
let state = NonNull::new(opaque.cast::<PL011State>()).unwrap();
Alternatively, use debug_assert! if the assertion is only meant to be
active in debug builds.
> + state.as_ref().can_receive().into()
> + }
> +}
> +
> +/// # Safety
> +///
> +/// We expect the FFI user of this function to pass a valid pointer, that has
> +/// the same size as [`PL011State`]. We also expect the device is
> +/// readable/writeable from one thread at any time.
> +///
> +/// The buffer and size arguments must also be valid.
> +#[no_mangle]
> +pub unsafe extern "C" fn pl011_receive(
> + opaque: *mut core::ffi::c_void,
> + buf: *const u8,
> + size: core::ffi::c_int,
> +) {
> + unsafe {
> + assert!(!opaque.is_null());
> + let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
> + if state.as_ref().loopback_enabled() {
> + return;
> + }
> + if size > 0 {
> + assert!(!buf.is_null());
> + state.as_mut().put_fifo(c_uint::from(buf.read_volatile()))
> + }
> + }
> +}
> +
> +/// # Safety
> +///
> +/// We expect the FFI user of this function to pass a valid pointer, that has
> +/// the same size as [`PL011State`]. We also expect the device is
> +/// readable/writeable from one thread at any time.
> +#[no_mangle]
> +pub unsafe extern "C" fn pl011_event(opaque: *mut core::ffi::c_void, event:
> QEMUChrEvent) {
> + unsafe {
> + assert!(!opaque.is_null());
> + let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
> + state.as_mut().event(event)
> + }
> +}
> +
> +/// # Safety
> +///
> +/// We expect the FFI user of this function to pass a valid pointer for
> `chr`.
> +#[no_mangle]
> +pub unsafe extern "C" fn pl011_create(
> + addr: u64,
> + irq: qemu_irq,
> + chr: *mut Chardev,
> +) -> *mut DeviceState {
> + unsafe {
> + let dev: *mut DeviceState = qdev_new(PL011State::TYPE_INFO.name);
> + assert!(!dev.is_null());
qdev_new() aborts on error. So the assert! above is unnecessary.
> + let sysbus: *mut SysBusDevice = dev as *mut SysBusDevice;
In C such casts are typically done using the object checker generated by
the DECLARE_INSTANCE_CHECKER macro. With qom_cast_debug the checker is
able to capture casts to wrong types.
As a future work, similar mechanism should be available in Rust for such
casts.
> +
> + qdev_prop_set_chr(dev, bindings::TYPE_CHARDEV.as_ptr(), chr);
> + sysbus_realize_and_unref(sysbus, addr_of!(error_fatal) as *mut *mut
> Error);
> + sysbus_mmio_map(sysbus, 0, addr);
> + sysbus_connect_irq(sysbus, 0, irq);
> + dev
> + }
> +}
> +
> +/// # Safety
> +///
> +/// We expect the FFI user of this function to pass a valid pointer, that has
> +/// the same size as [`PL011State`]. We also expect the device is
> +/// readable/writeable from one thread at any time.
> +#[no_mangle]
I don't think #[no_mangle] is needed for functions being exposed to C
via function pointers. The annotated function has external linkage,
while the common practice is to make such callbacks static.
> +pub unsafe extern "C" fn pl011_init(obj: *mut Object) {
> + unsafe {
> + assert!(!obj.is_null());
> + let mut state = NonNull::new_unchecked(obj.cast::<PL011State>());
> + state.as_mut().init();
> + }
> +}
[snip]
> diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..541109d4d8f87a70748820cecee24656802a6350
> --- /dev/null
> +++ b/rust/hw/char/pl011/src/lib.rs
> @@ -0,0 +1,586 @@
> +// Copyright 2024, Linaro Limited
> +// Author(s): Manos Pitsidianakis <[email protected]>
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +//
> +// PL011 QEMU Device Model
> +//
> +// This library implements a device model for the PrimeCell® UART (PL011)
> +// device in QEMU.
> +//
> +#![doc = include_str!("../README.md")]
> +//! # Library crate
> +//!
> +//! See [`PL011State`](crate::device::PL011State) for the device model type
> and
> +//! the [`registers`] module for register types.
> +
> +#![deny(
> + unsafe_op_in_unsafe_fn,
Shall we make this builtin lint denied for all crates? That can be done
by adding '-D unsafe_op_in_unsafe_fn' to rustc argument.
> + rustdoc::broken_intra_doc_links,
> + rustdoc::redundant_explicit_links,
> + clippy::correctness,
> + clippy::suspicious,
> + clippy::complexity,
> + clippy::perf,
> + clippy::cargo,
> + clippy::nursery,
> + clippy::style,
> + // restriction group
> + clippy::dbg_macro,
> + clippy::as_underscore,
> + clippy::assertions_on_result_states,
> + // pedantic group
> + clippy::doc_markdown,
> + clippy::borrow_as_ptr,
> + clippy::cast_lossless,
> + clippy::option_if_let_else,
> + clippy::missing_const_for_fn,
> + clippy::cognitive_complexity,
> + clippy::missing_safety_doc,
> + )]
> +
--
Best Regards
Junjie Mao