On Wed, Feb 19, 2025 at 10:25:40AM +0100, Paolo Bonzini wrote: > Date: Wed, 19 Feb 2025 10:25:40 +0100 > From: Paolo Bonzini <pbonz...@redhat.com> > Subject: Re: [PATCH] hw/timer/hpet: Detect invalid access to TN registers > > On 2/18/25 10:07, Philippe Mathieu-Daudé wrote: > > On 18/2/25 09:53, Paolo Bonzini wrote: > > > On 2/18/25 08:37, Zhao Liu wrote: > > > > "addr & 0x18" ignores invalid address, so that the trace in default > > > > branch (trace_hpet_ram_{read|write}_invalid()) doesn't work. > > > > > > > > Mask addr by "0x1f & ~4", in which 0x1f means to get the complete TN > > > > registers access and ~4 means to keep any invalid address offset. > > > > > > I think this is less readable. > > > > > > The reason to use !4 in the Rust code is because the initial AND is done > > > in a separate function, timer_and_addr(). > > > > Having a quick look at the model without looking at the specs: > > > > include/hw/timer/hpet.h:20:#define HPET_LEN 0x400 > > > > hw/timer/hpet.c:439:static uint64_t hpet_ram_read(..., > > hw/timer/hpet.c-441-{ > > hw/timer/hpet.c-448- /*address range of all TN regs*/ > > hw/timer/hpet.c-449- if (addr >= 0x100 && addr <= 0x3ff) { > > hw/timer/hpet.c-450- uint8_t timer_id = (addr - 0x100) / 0x20; > > ... > > hw/timer/hpet.c-469- } else { > > hw/timer/hpet.c-470- switch (addr & ~4) { > > ... > > hw/timer/hpet.c-488- } > > hw/timer/hpet.c-489- } > > hw/timer/hpet.c-490- return 0; > > hw/timer/hpet.c-491-} > > > > hw/timer/hpet.c:699: memory_region_init_io(&s->iomem, obj, > > &hpet_ram_ops, s, > > "hpet", HPET_LEN); > > > > I suppose we want to register multiple timers of I/O size 0x20 at 0x100, > > and the I/O size of 0x20 at 0x000 is a generic control region. > > > > Maybe split hpet_ram_ops in 2 (hpet_cfg_ops and hpet_tmr_ops), mapping > > the first one once at 0x000 and the other 24 times at 0x100-0x3ff? > > You would have to come up with a way to get the index though. It seems to > be adding churn for no particular reason. > > I'd rather look into how to make decoding code *easy* without making > everything MemoryRegions.
I aslo met an register space implementation [1] which stores registers with range (I guess for QEMU, it could map each register to a memory region) and register specific callbacks. I didn't choose this way since it's too complex to quickly develop... [1]: https://github.com/google/crosvm/blob/main/devices/src/register_space/mod.rs > As I explained yesterday, while I'm not yet sure > that Rust is going to stay in QEMU, I'd like to have as many examples as > possible to help tilting the balance one way or the other. And indeed in the > Rust version of HPET, timer_and_addr() could be extended to something like > this: > > // Start with the same "enum for registers" pattern that PL011 uses: > #[derive(qemu_api_macros::TryInto)] > #[repr(u64)] > enum TimerRegister { > CFG = 0, > CMP = 8, > ROUTE = 16, > } > > #[derive(qemu_api_macros::TryInto)] > #[repr(u64)] > enum GlobalRegister { > CAP = 0, > CFG = 0x10, > INT_STATUS = 0x20, > COUNTER = 0xF0, > } > > // Go one step further and define types for all possible outcomes: > #[derive(Copy)] > enum HPETRegister { > Timer(&BqlRefCell<HPETTimer>, TimerRegister), > Global(GlobalRegister), > Unknown(hwaddr), > } > > struct HPETAddrDecode { > u32 shift, > u32 len, > HPETRegister reg, > } > > fn decode(&self, addr: hwaddr, size: u32) -> HPETAddrDecode { > let shift = ((addr & 4) * 8) as u32; > let len = std::cmp::min(size * 8, 64 - shift); > > addr &= !4; > let reg = if (0x100..=0x3ff).contains(&addr) { > let timer_id: usize = ((addr - 0x100) / 0x20) as usize; > TimerRegister::try_from(addr) > .map(|reg| HPETRegister::Timer(&self.timers[timer_id], reg)) > } else { > GlobalRegister::try_from(addr) > .map(HPETRegister::Global) > } > > // reg is now a Result<HPETRegister, hwaddr> > // convert the Err case into HPETRegister as well > let reg = reg.unwrap_or_else(HPETRegister::Unknown); > HPETAddrDecode { shift, len, reg } > } > > (untested). The read and write functions then can do something like > > let val = match decoded.reg { > Timer(timer, reg) => timer.borrow_mut().read(decoded), > Global(GlobalRegister::CAP) => self.capability.get(), > Global(GlobalRegister::CFG) => self.config.get(), > ... > } > val >> decoded.shift > > and for write: > > match decoded.reg { > Timer(timer, reg) => timer.borrow_mut().write(decoded, value), > Global(GlobalRegister::CAP) => {}, // read-only > Global(GlobalRegister::CFG) => self.set_cfg_reg(decoded, value), > ... > } > > > The above could be a scheme that new devices could copy. Overall I think it > would be shorter code than what is there in rust/hw/timer/hpet (which is IMO > already better than C, mind!). Yes, I also like your way. It's a bit more abstract than current hpet, but is also more simple than pl011 without bilge. I also found that there's another example to abstract register fields without bilge [2]. However, examples such as hpet without any abstraction also exit [3]. >From the experiences of other Rust VMMs, the handling of registers varies greatly :-(, and there doesn't seem to be a unified approach. Developers create abstractions according to their preferences, which I think is quite different from C devices (in C, most people will hardcode everything like hpet). [2]: https://github.com/cloud-hypervisor/cloud-hypervisor/blob/main/devices/src/tpm.rs [3]: https://github.com/google/crosvm/blob/main/devices/src/cmos.rs > The honest question for people with less experience is whether this is > readable at all; whether seeing it helps you learn the language or > discourages you. If you think this conversion is not urgent, perhaps this case could become as a "good first issue" to encourage people practice and get familiar with RustInQemu. Regards, Zhao