外设建模
使用 Rust 建模的流程与 C 的区别不大,只不过需要遵循 Rust 的规范,另外需要处理 FFI 相关的内容。
我们仍然从 QOM 的角度出发,将创建一个 Rust 版本的外设模型分为以下几个部分:
- Class 的创建:方法、属性
- Object 的创建:初始化、实例化
- read/write 的实现:寄存器副作用
下面我们以 pl011 为例,进行讲解。
Class 的创建
我们想给出 C 语言版本:
static const TypeInfo pl011_arm_info = {
.name = TYPE_PL011,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(PL011State),
.instance_init = pl011_init,
.class_init = pl011_class_init,
};
static void pl011_luminary_init(Object *obj)
{
PL011State *s = PL011(obj);
s->id = pl011_id_luminary;
}
static const TypeInfo pl011_luminary_info = {
.name = TYPE_PL011_LUMINARY,
.parent = TYPE_PL011,
.instance_init = pl011_luminary_init,
};
static void pl011_register_types(void)
{
type_register_static(&pl011_arm_info);
type_register_static(&pl011_luminary_info);
}
type_init(pl011_register_types)
与 C 定义 Typeinfo 类似,我们需要初始化这个 Class 的 type,指明它的 parent,并且注册 class_init 和 instance_init 相关的方法。
#[repr(C)]
pub struct PL011Class {
parent_class: <SysBusDevice as ObjectType>::Class,
/// The byte string that identifies the device.
device_id: DeviceId,
}
trait PL011Impl: SysBusDeviceImpl + IsA<PL011State> {
const DEVICE_ID: DeviceId;
}
impl PL011Class {
fn class_init<T: PL011Impl>(&mut self) {
self.device_id = T::DEVICE_ID;
self.parent_class.class_init::<T>();
}
}
unsafe impl ObjectType for PL011State {
type Class = PL011Class;
const TYPE_NAME: &'static CStr = crate::TYPE_PL011;
}
impl PL011Impl for PL011State {
const DEVICE_ID: DeviceId = DeviceId(&[0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1]);
}
impl ObjectImpl for PL011State {
type ParentType = SysBusDevice;
const INSTANCE_INIT: Option<unsafe fn(ParentInit<Self>)> = Some(Self::init);
const INSTANCE_POST_INIT: Option<fn(&Self)> = Some(Self::post_init);
const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::<Self>;
}
impl DeviceImpl for PL011State {
fn properties() -> &'static [Property] {
&PL011_PROPERTIES
}
fn vmsd() -> Option<&'static VMStateDescription> {
Some(&VMSTATE_PL011)
}
const REALIZE: Option<fn(&Self) -> qemu_api::Result<()>> = Some(Self::realize);
}
impl ResettablePhasesImpl for PL011State {
const HOLD: Option<fn(&Self, ResetType)> = Some(Self::reset_hold);
}
impl SysBusDeviceImpl for PL011State {}
Object 的创建
对于 C 来说,主要是定义 pl011 的 state 结构体,这一点在 Rust 当中是一样的。
我们先给出 C 的定义:
struct PL011State {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t flags;
uint32_t lcr;
uint32_t rsr;
uint32_t cr;
uint32_t dmacr;
uint32_t int_enabled;
uint32_t int_level;
uint32_t read_fifo[PL011_FIFO_DEPTH];
uint32_t ilpr;
uint32_t ibrd;
uint32_t fbrd;
uint32_t ifl;
int read_pos;
int read_count;
int read_trigger;
CharBackend chr;
qemu_irq irq[6];
Clock *clk;
bool migrate_clk;
const unsigned char *id;
/*
* Since some users embed this struct directly, we must
* ensure that the C struct is at least as big as the Rust one.
*/
uint8_t padding_for_rust[16];
};
对应 Rust 的定义:
#[repr(C)]
#[derive(qemu_api_macros::Object)]
/// PL011 Device Model in QEMU
pub struct PL011State {
pub parent_obj: ParentField<SysBusDevice>,
pub iomem: MemoryRegion,
#[doc(alias = "chr")]
pub char_backend: CharBackend,
pub regs: BqlRefCell<PL011Registers>,
/// 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: [InterruptSource; IRQMASK.len()],
#[doc(alias = "clk")]
pub clock: Owned<Clock>,
#[doc(alias = "migrate_clk")]
pub migrate_clock: bool,
}
// Some C users of this device embed its state struct into their own
// structs, so the size of the Rust version must not be any larger
// than the size of the C one. If this assert triggers you need to
// expand the padding_for_rust[] array in the C PL011State struct.
static_assert!(size_of::<PL011State>() <= size_of::<qemu_api::bindings::PL011State>());
qom_isa!(PL011State : SysBusDevice, DeviceState, Object);
值得注意的是,C 当中进行类型转换是很方便的,但是在 Rust 中却不行。
因此 QEMU Rust 实现了一些 trait 和 宏,来安全的实现这一功能,对应 qom_isa!
,我们简单看看它的实现:
首先是 IsA<P> Trait
:
/// Marker trait: `Self` can be statically upcasted to `P` (i.e. `P` is a direct
/// or indirect parent of `Self`).
///
/// # Safety
///
/// The struct `Self` must be `#[repr(C)]` and must begin, directly or
/// indirectly, with a field of type `P`. This ensures that invalid casts,
/// which rely on `IsA<>` for static checking, are rejected at compile time.
pub unsafe trait IsA<P: ObjectType>: ObjectType {}
这是一个标记性 trait(marker trait),表示类型 Self 可以“向上转型”为类型 P,即 P 是 Self 的父类(或祖先类)。
unsafe trait
表示实现这个 trait 是 unsafe 的,因为如果错误地实现,可能导致未定义行为(UB)。
然后是IsA
的自动实现(对自身):
// SAFETY: it is always safe to cast to your own type
unsafe impl<T: ObjectType> IsA<T> for T {}
任何类型 T 都可以“向上转型”为自己(恒等转换),这是合理的,且是安全的,因为指针到自身的转换不会改变地址或破坏类型。
最后是 qom_isa!
宏的实现:
#[macro_export]
macro_rules! qom_isa {
($struct:ty : $($parent:ty),* ) => {
$(
// SAFETY: it is the caller responsibility to have $parent as the
// first field
unsafe impl $crate::qom::IsA<$parent> for $struct {}
impl AsRef<$parent> for $struct {
fn as_ref(&self) -> &$parent {
// SAFETY: follows the same rules as for IsA<U>, which is
// declared above.
let ptr: *const Self = self;
unsafe { &*ptr.cast::<$parent>() }
}
}
)*
};
}
这是个宏,用于方便地为某个结构体声明其“父类”。
当然,实现以上所有的前提是 #[repr(C)]
, 它保证了结构体起始地址就是第一个字段的地址,与 C 是对齐的。
现在我们来看看初始化的过程:
static void pl011_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
PL011State *s = PL011(obj);
int i;
memory_region_init_io(&s->iomem, OBJECT(s), &pl011_ops, s, "pl011", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
for (i = 0; i < ARRAY_SIZE(s->irq); i++) {
sysbus_init_irq(sbd, &s->irq[i]);
}
s->clk = qdev_init_clock_in(DEVICE(obj), "clk", pl011_clock_update, s,
ClockUpdate);
s->id = pl011_id_arm;
}
static void pl011_realize(DeviceState *dev, Error **errp)
{
PL011State *s = PL011(dev);
qemu_chr_fe_set_handlers(&s->chr, pl011_can_receive, pl011_receive,
pl011_event, NULL, s, NULL, true);
}
对应 Rust 的实现:
impl PL011State {
/// Initializes a pre-allocated, uninitialized instance of `PL011State`.
///
/// # Safety
///
/// `self` must point to a correctly sized and aligned location for the
/// `PL011State` type. It must not be called more than once on the same
/// location/instance. All its fields are expected to hold uninitialized
/// values with the sole exception of `parent_obj`.
unsafe fn init(mut this: ParentInit<Self>) {
static PL011_OPS: MemoryRegionOps<PL011State> = MemoryRegionOpsBuilder::<PL011State>::new()
.read(&PL011State::read)
.write(&PL011State::write)
.native_endian()
.impl_sizes(4, 4)
.build();
// SAFETY: this and this.iomem are guaranteed to be valid at this point
MemoryRegion::init_io(
&mut uninit_field_mut!(*this, iomem),
&PL011_OPS,
"pl011",
0x1000,
);
uninit_field_mut!(*this, regs).write(Default::default());
let clock = DeviceState::init_clock_in(
&mut this,
"clk",
&Self::clock_update,
ClockEvent::ClockUpdate,
);
uninit_field_mut!(*this, clock).write(clock);
}
fn realize(&self) -> qemu_api::Result<()> {
self.char_backend
.enable_handlers(self, Self::can_receive, Self::receive, Self::event);
Ok(())
}
}
它们的做的事情基本一致,不同的是 Rust 对 init 的实现,要求更为严格。重点在 init() 的 ParentInit<Self>
。
QEMU Rust 为了保证代码是 safe 的,对于 init 阶段,会认为 object 的非 parent 字段,可能是未初始化的。这提醒我们,要在 init 阶段为关键字段初始化,设置 default 值。
一个关键特点,访问 object 的字段,需要使用 uninit_field_mut!
。
read/write 的实现
实现外设的寄存器功能,基本就不依赖 C 的接口了,可以使用 Rust 进行编程,除非涉及到调用 qemu 的一些功能,但是这部分会逐渐被 Rust 封装起来,提供安全的接口。
我们来看一下 C 的实现:
static uint64_t pl011_read(void *opaque, hwaddr offset,
unsigned size)
{
PL011State *s = (PL011State *)opaque;
uint64_t r;
switch (offset >> 2) {
case 0: /* UARTDR */
r = pl011_read_rxdata(s);
break;
case 1: /* UARTRSR */
r = s->rsr;
break;
case 6: /* UARTFR */
r = s->flags;
break;
case 8: /* UARTILPR */
r = s->ilpr;
break;
case 9: /* UARTIBRD */
r = s->ibrd;
break;
case 10: /* UARTFBRD */
r = s->fbrd;
break;
case 11: /* UARTLCR_H */
r = s->lcr;
break;
case 12: /* UARTCR */
r = s->cr;
break;
case 13: /* UARTIFLS */
r = s->ifl;
break;
case 14: /* UARTIMSC */
r = s->int_enabled;
break;
case 15: /* UARTRIS */
r = s->int_level;
break;
case 16: /* UARTMIS */
r = s->int_level & s->int_enabled;
break;
case 18: /* UARTDMACR */
r = s->dmacr;
break;
case 0x3f8 ... 0x400:
r = s->id[(offset - 0xfe0) >> 2];
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"pl011_read: Bad offset 0x%x\n", (int)offset);
r = 0;
break;
}
trace_pl011_read(offset, r, pl011_regname(offset));
return r;
}
再来看看 Rust 的实现:
pub(self) fn read(&mut self, offset: RegisterOffset) -> (bool, u32) {
use RegisterOffset::*;
let mut update = false;
let result = match offset {
DR => self.read_data_register(&mut update),
RSR => u32::from(self.receive_status_error_clear),
FR => u32::from(self.flags),
FBRD => self.fbrd,
ILPR => self.ilpr,
IBRD => self.ibrd,
LCR_H => u32::from(self.line_control),
CR => u32::from(self.control),
FLS => self.ifl,
IMSC => u32::from(self.int_enabled),
RIS => u32::from(self.int_level),
MIS => u32::from(self.int_level & self.int_enabled),
ICR => {
// "The UARTICR Register is the interrupt clear register and is write-only"
// Source: ARM DDI 0183G 3.3.13 Interrupt Clear Register, UARTICR
0
}
DMACR => self.dmacr,
};
(update, result)
}
可以看到,基本逻辑是一致的。