feat(pl011): implement an emuldated UART(PL011)

Signed-off-by: Changyuan Lyu <changyuanl@google.com>
This commit is contained in:
Changyuan Lyu 2024-06-22 12:22:07 -07:00 committed by Lencerf
parent 41eee098f6
commit 810211b228
6 changed files with 311 additions and 0 deletions

View file

@ -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)?;

View file

@ -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

View file

@ -97,6 +97,8 @@ where
pub payload: RwLock<Option<Payload>>,
pub mp_sync: Arc<(Mutex<u32>, Condvar)>,
pub io_devs: RwLock<Vec<(u16, Arc<dyn Mmio>)>>,
#[cfg(target_arch = "aarch64")]
pub mmio_devs: RwLock<Vec<(AddrOpt, Arc<MemRegion>)>>,
pub pci_bus: PciBus,
pub pci_devs: RwLock<Vec<PciDevice>>,
pub fw_cfg: Mutex<Option<Arc<Mutex<FwCfg>>>>,
@ -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)?;

View file

@ -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;

277
alioth/src/device/pl011.rs Normal file
View file

@ -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<u8>,
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<I> {
name: Arc<String>,
irq_line: Arc<I>,
reg: Arc<Mutex<Pl011Reg>>,
console: Console,
}
impl<I> Pl011<I>
where
I: IrqSender,
{
pub fn new(base_addr: u64, irq_line: I) -> io::Result<Self> {
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<I> Mmio for Pl011<I>
where
I: IrqSender,
{
fn size(&self) -> u64 {
0x1000
}
fn read(&self, offset: u64, _size: u8) -> mem::Result<u64> {
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(&reg)?;
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(&reg)?;
}
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<I: IrqSender> {
irq_line: Arc<I>,
reg: Arc<Mutex<Pl011Reg>>,
}
impl<I: IrqSender> UartRecv for Pl011Recv<I> {
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();
}
}
}

View file

@ -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())?;