Manos Pitsidianakis <manos.pitsidiana...@linaro.org> 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 <junjie....@intel.com> > Co-authored-by: Paolo Bonzini <pbonz...@redhat.com> > Signed-off-by: Junjie Mao <junjie....@intel.com> > Signed-off-by: Paolo Bonzini <pbonz...@redhat.com> > Signed-off-by: Manos Pitsidianakis <manos.pitsidiana...@linaro.org> > --- [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 <manos.pitsidiana...@linaro.org> > +// SPDX-License-Identifier: GPL-2.0-or-later > + > +use core::{ > + ffi::{c_int, c_uchar, c_uint, c_void, CStr}, > + ptr::{addr_of, addr_of_mut, NonNull}, > +}; > + > +use 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 <manos.pitsidiana...@linaro.org> > +// 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