From 810211b228509b2e70c9a62397388fad905adeb3 Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sat, 22 Jun 2024 12:22:07 -0700 Subject: [PATCH] feat(pl011): implement an emuldated UART(PL011) Signed-off-by: Changyuan Lyu --- alioth-cli/src/main.rs | 2 + alioth/src/arch/aarch64/layout.rs | 2 + alioth/src/board/board.rs | 6 + alioth/src/device/device.rs | 2 + alioth/src/device/pl011.rs | 277 ++++++++++++++++++++++++++++++ alioth/src/vm.rs | 22 +++ 6 files changed, 311 insertions(+) create mode 100644 alioth/src/device/pl011.rs diff --git a/alioth-cli/src/main.rs b/alioth-cli/src/main.rs index ed405ae..2e1d441 100644 --- a/alioth-cli/src/main.rs +++ b/alioth-cli/src/main.rs @@ -204,6 +204,8 @@ fn main_run(args: RunArgs) -> Result<(), Error> { let mut vm = Machine::new(hypervisor, board_config).context(error::CreateVm)?; #[cfg(target_arch = "x86_64")] vm.add_com1().context(error::CreateDevice)?; + #[cfg(target_arch = "aarch64")] + vm.add_pl011().context(error::CreateDevice)?; if args.pvpanic { vm.add_pvpanic().context(error::CreateDevice)?; diff --git a/alioth/src/arch/aarch64/layout.rs b/alioth/src/arch/aarch64/layout.rs index cf983bd..78a14d3 100644 --- a/alioth/src/arch/aarch64/layout.rs +++ b/alioth/src/arch/aarch64/layout.rs @@ -17,6 +17,8 @@ pub const MMIO_32_START: u64 = 0x1000_0000; // 256 MiB pub const GIC_V2_DIST_START: u64 = 0x1000_0000; // size 4 KiB pub const GIC_V2_CPU_INTERFACE_START: u64 = 0x1000_1000; // size 8 KiB +pub const PL011_START: u64 = 0x2fff_f000; + pub const MMIO_32_END: u64 = 0x3000_0000; // 768 MiB, size = 512 MiB pub const PCIE_CONFIG_START: u64 = 0x3000_0000; // 768 MiB diff --git a/alioth/src/board/board.rs b/alioth/src/board/board.rs index 87cfdfe..55f2bea 100644 --- a/alioth/src/board/board.rs +++ b/alioth/src/board/board.rs @@ -97,6 +97,8 @@ where pub payload: RwLock>, pub mp_sync: Arc<(Mutex, Condvar)>, pub io_devs: RwLock)>>, + #[cfg(target_arch = "aarch64")] + pub mmio_devs: RwLock)>>, pub pci_bus: PciBus, pub pci_devs: RwLock>, pub fw_cfg: Mutex>>>, @@ -238,6 +240,10 @@ where for (port, dev) in self.io_devs.read().iter() { self.memory.add_io_dev(Some(*port), dev.clone())?; } + #[cfg(target_arch = "aarch64")] + for (addr, dev) in self.mmio_devs.read().iter() { + self.memory.add_region(*addr, dev.clone())?; + } self.add_pci_devs()?; let init_state = self.load_payload()?; self.init_boot_vcpu(&mut vcpu, &init_state)?; diff --git a/alioth/src/device/device.rs b/alioth/src/device/device.rs index 3c168d0..d57b66e 100644 --- a/alioth/src/device/device.rs +++ b/alioth/src/device/device.rs @@ -15,6 +15,8 @@ pub mod console; #[path = "fw_cfg/fw_cfg.rs"] pub mod fw_cfg; +#[cfg(target_arch = "aarch64")] +pub mod pl011; pub mod pvpanic; #[cfg(target_arch = "x86_64")] pub mod serial; diff --git a/alioth/src/device/pl011.rs b/alioth/src/device/pl011.rs new file mode 100644 index 0000000..da3eeb7 --- /dev/null +++ b/alioth/src/device/pl011.rs @@ -0,0 +1,277 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::VecDeque; +use std::io; +use std::sync::Arc; + +use bitflags::bitflags; +use parking_lot::Mutex; + +use crate::device::console::{Console, UartRecv}; +use crate::hv::IrqSender; +use crate::mem::emulated::Mmio; +use crate::{hv, mem}; + +/// RW width 12/8 Data Register +const UART_DR: u64 = 0x0; +/// RO width 4 Receive Status Register +const UART_RSR: u64 = 0x4; +/// WO width 0  Error Clear Register +const UART_ECR: u64 = 0x4; +/// RO width 9 Flag Register +const UART_FR: u64 = 0x18; +/// RW width 8 IrDA Low-Power Counter Register +const UART_ILPR: u64 = 0x20; +/// RW width 16 Integer Baud Rate Register +const UART_IBRD: u64 = 0x24; +/// RW width 6 Fractional Baud Rate Register +const UART_FBRD: u64 = 0x28; +/// RW width 8 Line Control Register +const UART_LCR_H: u64 = 0x2C; +/// RW width 16 Control Register +const UART_CR: u64 = 0x30; +/// RW width 6 Interrupt FIFO Level Select Register +const UART_IFLS: u64 = 0x34; +/// RW width 11 Interrupt Mask Set/Clear Register +const UART_IMSC: u64 = 0x38; +/// RO width 11 Raw Interrupt Status Register +const UART_RIS: u64 = 0x3C; +/// RO width 11 Masked Interrupt Status Register +const UART_MIS: u64 = 0x40; +/// WO width 11 Interrupt Clear Register +const UART_ICR: u64 = 0x44; +/// RW width 3 DMA Control Register +const UART_DMACR: u64 = 0x48; +/// RO width 8 UARTPeriphID0 Register +const UART_PERIPH_ID0: u64 = 0xFE0; +/// RO width 8 UARTPeriphID1 Register +const UART_PERIPH_ID1: u64 = 0xFE4; +/// RO width 8 UARTPeriphID2 Register +const UART_PERIPH_ID2: u64 = 0xFE8; +/// RO width 8 UARTPeriphID3 Register +const UART_PERIPH_ID3: u64 = 0xFEC; +/// RO width 8 UARTPCellID0 Register +const UART_PCELL_ID0: u64 = 0xFF0; +/// RO width 8 UARTPCellID1 Register +const UART_PCELL_ID1: u64 = 0xFF4; +/// RO width 8 UARTPCellID2 Register +const UART_PCELL_ID2: u64 = 0xFF8; +/// RO width 8 UARTPCellID3 Register +const UART_PCELL_ID3: u64 = 0xFFC; + +// https://developer.arm.com/documentation/ddi0183/g/programmers-model/register-descriptions/peripheral-identification-registers--uartperiphid0-3 +const PERIPH_ID: [u32; 4] = [0x11, 0x10, 0x14, 0x00]; + +// https://developer.arm.com/documentation/ddi0183/g/programmers-model/register-descriptions/primecell-identification-registers--uartpcellid0-3 +const PCELL_ID: [u32; 4] = [0x0d, 0xf0, 0x05, 0xb1]; + +bitflags! { + #[derive(Default, Debug, Clone, Copy)] + pub struct Flag: u16 { + const RI = 1 << 8; + /// Transmit FIFO empty + const TXFE = 1 << 7; + /// Receive FIFO full + const RXFF = 1 << 6; + /// Transmit FIFO full. + const TXFF = 1 << 5; + /// Receive FIFO empty + const RXFE = 1 << 4; + const BUSY = 1 << 3; + const DCD = 1 << 2; + const DSR = 1 << 1; + const CTS = 1 << 0; + } +} + +bitflags! { + #[derive(Default, Debug, Clone, Copy)] + pub struct Interrupt: u16 { + /// Overrun error interrupt status. + const OERIS = 1 << 10; + /// Break error interrupt status. + const BERIS = 1 << 9; + /// Parity error interrupt status. + const PERIS = 1 << 8; + /// Framing error interrupt status. + const FERIS = 1 << 7; + /// Receive timeout interrupt status. + const RTRIS = 1 << 6; + /// Transmit interrupt status. + const TXRIS = 1 << 5; + /// Receive interrupt status. + const RXRIS = 1 << 4; + /// nUARTDSR modem interrupt status. + const DSRRMIS = 1 << 3; + /// nUARTDCD modem interrupt status. + const DCDRMIS = 1 << 2; + /// nUARTCTS modem interrupt status. + const CTSRMIS = 1 << 1; + /// nUARTRI modem interrupt status. + const RIRMIS = 1 << 0; + } +} + +#[derive(Debug, Default)] +struct Pl011Reg { + data: VecDeque, + flag: Flag, + lcr: u32, + rsr: u32, + cr: u32, + dmacr: u32, + ilpr: u32, + ibrd: u32, + fbrd: u32, + ifl: u32, + interrupt_mask: Interrupt, + interrupt_status: Interrupt, +} + +/// https://developer.arm.com/documentation/ddi0183/g +#[derive(Debug)] +pub struct Pl011 { + name: Arc, + irq_line: Arc, + reg: Arc>, + console: Console, +} + +impl Pl011 +where + I: IrqSender, +{ + pub fn new(base_addr: u64, irq_line: I) -> io::Result { + let irq_line = Arc::new(irq_line); + let reg = Arc::new(Mutex::new(Pl011Reg::default())); + let name = Arc::new(format!("pl011@{base_addr:#x}")); + let pl011_recv = Pl011Recv { + irq_line: irq_line.clone(), + reg: reg.clone(), + }; + let console = Console::new(name.clone(), pl011_recv)?; + let pl011 = Pl011 { + name, + irq_line, + reg, + console, + }; + Ok(pl011) + } + + fn update_interrupt(&self, reg: &Pl011Reg) -> Result<(), hv::Error> { + if (reg.interrupt_status & reg.interrupt_mask).bits() != 0 { + self.irq_line.send().unwrap(); + } + Ok(()) + } +} + +impl Mmio for Pl011 +where + I: IrqSender, +{ + fn size(&self) -> u64 { + 0x1000 + } + + fn read(&self, offset: u64, _size: u8) -> mem::Result { + let mut reg = self.reg.lock(); + let ret = match offset { + UART_DR => { + let byte = reg.data.pop_front().unwrap_or(0); + if reg.data.is_empty() { + reg.flag.insert(Flag::RXFE); + reg.interrupt_status.remove(Interrupt::RXRIS); + } + self.update_interrupt(®)?; + byte as u32 + } + UART_RSR => reg.rsr, + UART_FR => reg.flag.bits() as u32, + UART_ILPR => reg.ilpr, + UART_IBRD => reg.ibrd, + UART_FBRD => reg.fbrd, + UART_LCR_H => reg.lcr, + UART_CR => reg.cr, + UART_IFLS => reg.ifl, + UART_IMSC => reg.interrupt_mask.bits() as u32, + UART_RIS => reg.interrupt_status.bits() as u32, + UART_MIS => (reg.interrupt_mask & reg.interrupt_status).bits() as u32, + UART_ICR => { + log::error!("{}: UART_ICR is write only", self.name); + 0 + } + UART_DMACR => reg.dmacr, + UART_PERIPH_ID0 => PERIPH_ID[0], + UART_PERIPH_ID1 => PERIPH_ID[1], + UART_PERIPH_ID2 => PERIPH_ID[2], + UART_PERIPH_ID3 => PERIPH_ID[3], + UART_PCELL_ID0 => PCELL_ID[0], + UART_PCELL_ID1 => PCELL_ID[1], + UART_PCELL_ID2 => PCELL_ID[2], + UART_PCELL_ID3 => PCELL_ID[3], + _ => 0, + }; + Ok(ret as u64) + } + + fn write(&self, offset: u64, _size: u8, val: u64) -> mem::Result<()> { + let mut reg = self.reg.lock(); + match offset { + UART_DR => { + self.console.transmit(&[val as u8]); + reg.interrupt_status.insert(Interrupt::TXRIS); + reg.flag.insert(Flag::TXFE); + self.update_interrupt(®)?; + } + UART_ECR => reg.rsr = 0, + UART_FR => log::error!("{}: UART_FR is read only", self.name), + UART_ILPR => reg.ilpr = val as u32, + UART_IBRD => reg.ibrd = val as u32, + UART_FBRD => reg.fbrd = val as u32, + UART_LCR_H => reg.lcr = val as u32, + UART_CR => reg.cr = val as u32, + UART_IFLS => reg.ifl = val as u32, + UART_IMSC => { + reg.interrupt_mask = Interrupt::from_bits_truncate(val as u16); + } + UART_RIS => log::error!("{}, UART_RIS is read only", self.name), + UART_MIS => log::error!("{}, UART_MIS is read only", self.name), + UART_ICR => reg.interrupt_status &= !Interrupt::from_bits_truncate(val as u16), + UART_DMACR => reg.dmacr = val as u32, + _ => {} + } + Ok(()) + } +} + +struct Pl011Recv { + irq_line: Arc, + reg: Arc>, +} + +impl UartRecv for Pl011Recv { + fn receive(&self, bytes: &[u8]) { + let mut reg = self.reg.lock(); + reg.data.extend(bytes); + reg.interrupt_status.insert(Interrupt::RXRIS); + reg.flag.remove(Flag::RXFE); + + if (reg.interrupt_status & reg.interrupt_mask).bits() != 0 { + self.irq_line.send().unwrap(); + } + } +} diff --git a/alioth/src/vm.rs b/alioth/src/vm.rs index a0f544c..901a9f8 100644 --- a/alioth/src/vm.rs +++ b/alioth/src/vm.rs @@ -21,14 +21,20 @@ use std::thread; use parking_lot::{Condvar, Mutex, RwLock}; use thiserror::Error; +#[cfg(target_arch = "aarch64")] +use crate::arch::layout::PL011_START; use crate::board::{self, ArchBoard, Board, BoardConfig, STATE_CREATED, STATE_RUNNING}; use crate::device::fw_cfg::{FwCfg, FwCfgItemParam, PORT_SELECTOR}; +#[cfg(target_arch = "aarch64")] +use crate::device::pl011::Pl011; use crate::device::pvpanic::PvPanic; #[cfg(target_arch = "x86_64")] use crate::device::serial::Serial; use crate::hv::{self, Hypervisor, IoeventFdRegistry, Vm, VmConfig}; use crate::loader::{self, Payload}; use crate::mem::Memory; +#[cfg(target_arch = "aarch64")] +use crate::mem::{AddrOpt, MemRegion, MemRegionType}; use crate::pci::bus::PciBus; use crate::pci::PciDevice; use crate::virtio::dev::{DevParam, Virtio, VirtioDevice}; @@ -99,6 +105,8 @@ where vcpus: Arc::new(RwLock::new(Vec::new())), mp_sync: Arc::new((Mutex::new(0), Condvar::new())), io_devs: RwLock::new(Vec::new()), + #[cfg(target_arch = "aarch64")] + mmio_devs: RwLock::new(Vec::new()), pci_bus: PciBus::new(), pci_devs: RwLock::new(Vec::new()), fw_cfg: Mutex::new(None), @@ -138,6 +146,20 @@ where Ok(()) } + #[cfg(target_arch = "aarch64")] + pub fn add_pl011(&self) -> Result<(), Error> { + let irq_line = self.board.vm.create_irq_sender(1)?; + let pl011_dev = Pl011::new(PL011_START, irq_line)?; + self.board.mmio_devs.write().push(( + AddrOpt::Fixed(PL011_START), + Arc::new(MemRegion::with_emulated( + Arc::new(pl011_dev), + MemRegionType::Hidden, + )), + )); + Ok(()) + } + pub fn add_pci_dev(&mut self, dev: PciDevice) -> Result<(), Error> { let config = dev.dev.config(); let bdf = self.board.pci_bus.add(None, config.clone())?;