The aarch64 platform uses a virt machine type similar with Qemu does. UEFI requires this pl011 device, so let's add it into legacy.
Signed-off-by: Ying Fang fangying1@huawei.com --- boot_loader/src/aarch64/mod.rs | 7 +- device_model/src/legacy/mod.rs | 3 + device_model/src/legacy/pl011.rs | 464 +++++++++++++++++++++++++++++++ device_model/src/micro_vm/mod.rs | 30 +- device_model/src/mmio/mod.rs | 2 +- 5 files changed, 495 insertions(+), 11 deletions(-) create mode 100644 device_model/src/legacy/pl011.rs
diff --git a/boot_loader/src/aarch64/mod.rs b/boot_loader/src/aarch64/mod.rs index b9fa3d3..1dc3c95 100644 --- a/boot_loader/src/aarch64/mod.rs +++ b/boot_loader/src/aarch64/mod.rs @@ -74,6 +74,7 @@ pub fn linux_bootloader( sys_mem: &Arc<AddressSpace>, ) -> Result<AArch64BootLoader> {
+ /* let dtb_addr = if sys_mem.memory_end_address().raw_value() > u64::from(device_tree::FDT_MAX_SIZE) { if let Some(addr) = sys_mem @@ -95,9 +96,9 @@ pub fn linux_bootloader(
if dtb_addr == 0 { return Err(ErrorKind::DTBOverflow(sys_mem.memory_end_address().raw_value()).into()); - } + }*/
- let dbt_addr: u64 = 1 << 30; + let dtb_addr: u64 = 1 << 30;
let mut initrd_addr = 0; if config.initrd_size > 0 { @@ -115,7 +116,7 @@ pub fn linux_bootloader( }
Ok(AArch64BootLoader { - kernel_start: 0x00, + kernel_start: 0x0000, vmlinux_start: config.mem_start + AARCH64_KERNEL_OFFSET, initrd_start: initrd_addr, dtb_start: dtb_addr, diff --git a/device_model/src/legacy/mod.rs b/device_model/src/legacy/mod.rs index bd8970e..0df5ae3 100644 --- a/device_model/src/legacy/mod.rs +++ b/device_model/src/legacy/mod.rs @@ -42,3 +42,6 @@ pub use self::fw_cfg::FWCfgMem; #[cfg(target_arch = "aarch64")] pub use self::fw_cfg::FWCfgConfig;
+mod pl011; +#[cfg(target_arch = "aarch64")] +pub use self::pl011::PL011; diff --git a/device_model/src/legacy/pl011.rs b/device_model/src/legacy/pl011.rs new file mode 100644 index 0000000..9f435c7 --- /dev/null +++ b/device_model/src/legacy/pl011.rs @@ -0,0 +1,464 @@ +// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::io; +use std::os::unix::io::RawFd; +use std::sync::{Arc, Mutex}; + +use kvm_ioctls::VmFd; +use address_space::GuestAddress; +use byteorder::{ByteOrder, LittleEndian}; + +use util::epoll_context::{EventNotifier, EventNotifierHelper, NotifierOperation}; +use vmm_sys_util::{epoll::EventSet, eventfd::EventFd, terminal::Terminal}; + +use super::super::mmio::{DeviceResource, DeviceType, MmioDeviceOps}; +use super::super::DeviceOps; +use super::super::mmio::errors::{Result, ResultExt}; + + +const PL011_INT_TX: u8 = 0x20; +const PL011_INT_RX: u8 = 0x10; + +const PL011_FLAG_RXFF: u8 = 0x40; +const PL011_FLAG_RXFE: u8 = 0x10; + + +/// Interrupt status bits in UARTRIS, UARTMIS and UARTIMSC +const INT_OE: u32 = 1 << 10; +const INT_BE: u32 = 1 << 9; +const INT_PE: u32 = 1 << 8; +const INT_FE: u32 = 1 << 7; +const INT_RT: u32 = 1 << 6; +const INT_TX: u32 = 1 << 5; +const INT_RX: u32 = 1 << 4; +const INT_DSR: u32 = 1 << 3; +const INT_DCD: u32 = 1 << 2; +const INT_CTS: u32 = 1 << 1; +const INT_RI: u32 = 1 << 0; +const INT_E: u32 = INT_OE | INT_BE | INT_PE | INT_FE; +const INT_MS: u32 = INT_RI | INT_DSR | INT_DCD | INT_CTS; + +pub struct PL011 { + /// Read FIFO. + rfifo: Vec<u32>, + /// Flag Register. + flags: u32, + /// Line Control Register. + lcr: u32, + /// Receive Status Register. + rsr: u32, + /// Control Register. + cr: u32, + /// DMA Control Register. + dmacr: u32, + /// IrDA Low-Power Counter Register. + ilpr: u32, + /// Integer Baud Rate Register. + ibrd: u32, + /// Fractional Baud Rate Register. + fbrd: u32, + /// Interrut FIFO Level Select Register. + ifl: u32, + /// Identifier Register. + id: Vec<u8>, + /// FIFO Status. + read_pos: i32, + read_count: i32, + read_trigger: i32, + /// Raw Interrupt Status Register. + int_level: u32, + /// Interrupt Mask Set/Clear Register. + int_enabled: u32, + /// Interrupt event file descriptor. + interrupt_evt: Option<EventFd>, + /// Operation methods. + output: Option<Box<dyn io::Write + Send + Sync>>, +} + +impl PL011 { + /// Create a new `PL011` instance with default parameters. + pub fn new() -> Self { + PL011 { + rfifo: vec![0; 16], + flags: 0x90, + lcr: 0, + rsr: 0, + cr: 0x300, + dmacr: 0, + ilpr: 0, + ibrd: 0, + fbrd: 0, + ifl: 0x12, + id: vec![0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1], + read_pos: 0, + read_count: 0, + read_trigger: 1, + int_level: 0, + int_enabled: 0, + interrupt_evt: None, + output: None, + } + } + + /// Set EventFd for PL011 + /// + /// # Errors + /// + /// Return Error if + /// * fail to write EventFd. + /// * fail to get an interrupt event fd. + fn interrupt(&self) -> Result<()> { + info!("interrupt once"); + match &self.interrupt_evt { + Some(evt) => evt.write(1).chain_err(|| "Failed to write fd")?, + None => bail!("Failed to get an interrupt event fd"), + }; + + Ok(()) + } + + /// Update interrupt + /// Call this function to send irq to guest. + fn update(&self) -> Result<()> { + let irq_mask = [INT_E | INT_MS | INT_RT | INT_TX | INT_RX, + INT_RX, INT_TX, INT_RT, INT_MS, INT_E]; + let flags = self.int_level & self.int_enabled; + debug!("update int_level 0x{:x} int_enabled 0x{:x}", self.int_level, self.int_enabled); + debug!("pl011_irq_state irq state {}", flags != 0); + for i in 0..irq_mask.len() { + if flags & irq_mask[i as usize] != 0 { + self.interrupt()?; + } + } + Ok(()) + } + /// Append `data` to receiver buffer register, and update interrupt. + /// + /// # Arguments + /// + /// * `value` - A u8-type array. + pub fn receive(&mut self, data: &[u8]) -> Result<()> { + debug!("pl011 receive read_pos 0x{:x} read_count 0x{:x}", self.read_pos, self.read_count); + let value = data[0] as u32; + let mut slot = self.read_pos + self.read_count; + if slot >= 16 { + slot -= 16; + } + self.rfifo[slot as usize] = value; + self.read_count += 1; + self.flags &= !PL011_FLAG_RXFE as u32; + if ((self.lcr & 0x10) == 0) || (self.read_count == 16) { + self.flags |= PL011_FLAG_RXFF as u32; + } + debug!("slot is {} read_count {} read_trigger {}", slot, self.read_count, self.read_trigger); + if self.read_count == self.read_trigger { + self.int_level |= PL011_INT_RX as u32; + if self.update().is_err() { + error!("Failed to update interrupt."); + } + } + + Ok(()) + } +} + +impl DeviceOps for PL011 { + /// Read data from a certain register selected by `offset`. + /// + /// # Arguments + /// + /// * `offset` - Used to select a register. + /// + /// # Errors + /// + /// Return Error if fail to update interrupt. + fn read(&mut self, data: &mut [u8], _base: GuestAddress, offset: u64) -> bool { + let mut ret: u32 = 0; + + match offset >> 2 { + 0 => { + self.flags &= !(PL011_FLAG_RXFF as u32); + let c = self.rfifo[self.read_pos as usize]; + + if self.read_count > 0 { + self.read_count -= 1; + self.read_pos += 1; + if self.read_pos == 16 { + self.read_pos = 0; + } + } + if self.read_count == 0 { + self.flags |= PL011_FLAG_RXFE as u32; + } + if self.read_count == self.read_trigger - 1 { + self.int_level &= !(PL011_INT_RX as u32); + } + self.rsr = c >> 8; + if self.update().is_err() { + error!("Failed to update interrupt."); + } + ret = c; + } + 1 => { // UARTRSR + ret = self.rsr; + } + 6 => { // UARTFR + ret = self.flags; + } + 8 => { // UARTILPR + ret = self.ilpr; + } + 9 => { // UARTIBRD + ret = self.ibrd; + } + 10 => { // UARTFBRD + ret = self.fbrd; + } + 11 => { // UARTLCR_H + ret = self.lcr; + } + 12 => { // UARTCR + ret = self.cr; + } + 13 => { // UARTIFLS + ret = self.ifl; + } + 14 => { // UARTIMSC + ret = self.int_enabled; + } + 15 => { // UARTRIS + ret = self.int_level; + } + 16 => { // UARTMIS + ret = self.int_level & self.int_enabled; + } + 18 => { // UARTDMACR + ret = self.dmacr; + } + 0x3f8..=0x400 => { + ret = *self.id.get(((offset - 0xfe0) >> 2) as usize).unwrap() as u32; + } + _ => { + ret = 0; + debug!("read bad ofset 0x{:x}", offset) + } + } + debug!("pl011 read addr 0x{:x} value 0x{:x}", offset, ret); + use crate::util::byte_code::ByteCode; + data.copy_from_slice(&ret.as_bytes()[0..data.len()]); + + true + } + + /// Write one byte data to a certain register selected by `offset`. + /// + /// # Arguments + /// + /// * `offset` - Used to select a register. + /// * `data` - A u8-type data, which will be written to the register. + /// + /// # Errors + /// + /// Return Error if + /// * fail to get output file descriptor. + /// * fail to write pl011. + /// * fail to flush pl011. + fn write(&mut self, data: &[u8], _base: GuestAddress, offset: u64) -> bool { + let value = if data.len() == 1 { + data[0] as u16 + } else if data.len() == 2 { + LittleEndian::read_u16(data) + } else { 0 } as u32; + + debug!("pl011 write data.len={} data=0x{:x}", data.len(), data[0]); + debug!("pl011 write addr 0x{:x} value=0x{:x}", offset, value); + match offset >> 2 { + 0 => { + let ch = value as u8; + + let output = match &mut self.output { + Some(output_) => output_, + None => { + error!("Failed to get output fd."); + return true; + } + }; + output.write_all(&[ch]).unwrap(); + output.flush().unwrap(); + + self.int_level |= PL011_INT_TX as u32; + if self.update().is_err() { + error!("Failed to update interrupt."); + } + } + 1 => { // UARTRSR/UARTECR + self.rsr = 0; + } + 6 => { // UARTFR: write to flag register are ignored + } + 8 => { // UARTUARTILPR + self.ilpr = value; + } + 9 => { // UARTIBRD + self.ibrd = value; + } + 10 => { // UARTFBRD + self.fbrd = value; + } + 11 => { // UARTLCR_H + if (self.lcr ^ value) & 0x10 != 0 { + self.read_count = 0; + self.read_pos = 0; + } + self.lcr = value; + self.read_trigger = 1; + info!("write lcr 0x{:x}", self.lcr); + } + 12 => { // UARTCR + self.cr = value; + } + 13 => { // UARTIFS + self.ifl = value; + self.read_trigger = 1; + } + 14 => { // UARTIMSC + self.int_enabled = value; + if self.update().is_err() { + error!("Failed to update interrupt."); + } + } + 17 => { // UARTICR + self.int_level &= !value; + if self.update().is_err() { + error!("Failed to update interrupt."); + } + } + 18 => { // UARTDMACR + self.dmacr = value; + if value & 3 != 0 { + error!("pl011: DMA not implemented"); + } + } + _ => { + error!("pl011_write: Bad offset 0x{:x}", offset); + } + } + + true + } +} + +impl MmioDeviceOps for PL011 { + /// Realize a PL011 serial port for VM. + /// * Create a new output component. + /// * Register DeviceResource IRQ to VM. + /// * Set interrupt_evt component. + /// + /// # Arguments + /// + /// * `vm_fd` - File descriptor of VM. + /// * `resource` - Device resource. + /// + /// # Errors + /// + /// Return Error if + /// * fail to register. + /// * fail to create a new EventFd. + fn realize(&mut self, vm_fd: &VmFd, resource: DeviceResource) -> Result<()> { + self.output = Some(Box::new(std::io::stdout())); + + match EventFd::new(libc::EFD_NONBLOCK) { + Ok(evt) => { + vm_fd + .register_irqfd(&evt, resource.irq) + .chain_err(|| "Failed to register irqfd")?; + self.interrupt_evt = Some(evt); + + Ok(()) + } + Err(_) => Err("Failed to create new EventFd".into()), + } + } + + /// Get type of Device. + fn get_type(&self) -> DeviceType { + DeviceType::SERIAL + } +} + + +impl EventNotifierHelper for PL011 { + /// Add PL011 to `EventNotifier`. + /// + /// # Arguments + /// + /// * `pl011` - PL011 Serial instance. + fn internal_notifiers(pl011: Arc<Mutex<Self>>) -> Vec<EventNotifier> { + let mut notifiers = Vec::new(); + + let mut handlers = Vec::new(); + let handler: Box<dyn Fn(EventSet, RawFd) -> Option<Vec<EventNotifier>>> = + Box::new(move |_, _| { + let mut out = [0_u8; 64]; + if let Ok(count) = std::io::stdin().lock().read_raw(&mut out) { + info!("read {} bytes from stdin", count); + let _ = pl011.lock().unwrap().receive(&out[..count]); + info!("receive done here"); + } + None + }); + + handlers.push(Arc::new(Mutex::new(handler))); + + let notifier = EventNotifier::new( + NotifierOperation::AddShared, + libc::STDIN_FILENO, + None, + EventSet::IN, + handlers, + ); + + notifiers.push(notifier); + notifiers + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_methods_of_pl011() { + // test new method + let mut pl011 = PL011::new(); + assert_eq!(pl011.ifl, 0x12); + assert_eq!(pl011.cr, 1); + assert_eq!(pl011.flags, 0x90); + assert_eq!(pl011.read_trigger, 1); + + // test interrupt method + // for interrupt method to work, + // you need to set interrupt_evt at first + assert!(pl011.interrupt().is_err()); + + let evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + pl011.interrupt_evt = Some(evt); + assert!(pl011.interrupt().is_ok()); + + // test receive method + let data = [0x01, 0x02]; + assert!(pl011.receive(&data).is_ok()); + assert!(pl011.read_count, 1); + assert!(pl011.flags, 0x90 & !PL011_FLAG_RXFE); + } +} diff --git a/device_model/src/micro_vm/mod.rs b/device_model/src/micro_vm/mod.rs index e99d8fb..4905513 100644 --- a/device_model/src/micro_vm/mod.rs +++ b/device_model/src/micro_vm/mod.rs @@ -81,8 +81,11 @@ use crate::legacy::PL031; #[cfg(target_arch = "aarch64")] use crate::mmio::DeviceResource; use crate::MainLoop; +#[cfg(target_arh = "x86_64")] +use crate::legacy::Serial; +#[cfg(target_arch = "aarch64")] +use crate::{legacy::PL011, legacy::FWCfgMem}; use crate::{ - legacy::Serial, mmio::{Bus, DeviceType, VirtioMmioDevice}, virtio::{vhost, Console}, }; @@ -154,7 +157,7 @@ impl ConfigDevBuilder for VsockConfig {
impl ConfigDevBuilder for SerialConfig { fn build_dev(&self, _sys_mem: Arc<AddressSpace>, bus: &mut Bus) -> Result<()> { - let serial = Arc::new(Mutex::new(Serial::new())); + let serial = Arc::new(Mutex::new(PL011::new())); bus.attach_device(serial.clone()) .chain_err(|| "build dev from config failed")?;
@@ -558,6 +561,11 @@ impl LightMachine { self.bus .attach_device(rtc) .chain_err(|| "add rtc to bus failed")?; + + let fwcfg = Arc::new(Mutex::new(FWCfgMem::new(self.sys_mem.clone()))); + self.bus + .attach_device(fwcfg) + .chain_err(|| "add fwcfg to bus failed")?; }
if let Some(serial) = vm_config.serial { @@ -1021,7 +1029,6 @@ fn generate_fwcfg_device_node( Ok(()) }
- /// Function that helps to generate serial node in device-tree. /// /// # Arguments @@ -1033,11 +1040,11 @@ fn generate_serial_device_node( dev_info: &DeviceResource, fdt: &mut Vec<u8>, ) -> util::errors::Result<()> { - let node = format!("/uart@{:x}", dev_info.addr); + let node = format!("/pl011@{:x}", dev_info.addr); device_tree::add_sub_node(fdt, &node)?; - device_tree::set_property_string(fdt, &node, "compatible", "ns16550a")?; - device_tree::set_property_string(fdt, &node, "clock-names", "apb_pclk")?; - device_tree::set_property_u32(fdt, &node, "clocks", device_tree::CLK_PHANDLE)?; + device_tree::set_property_string(fdt, &node, "compatible", "arm,pl011\0arm,primecell")?; + device_tree::set_property_string(fdt, &node, "clock-names", "uartclk\0apb_pclk")?; + device_tree::set_property_array_u32(fdt, &node, "clocks", &[device_tree::CLK_PHANDLE, device_tree::CLK_PHANDLE])?; device_tree::set_property_array_u64(fdt, &node, "reg", &[dev_info.addr, dev_info.size])?; device_tree::set_property_array_u32( fdt, @@ -1285,6 +1292,14 @@ impl CompileFDTHelper for LightMachine { let cmdline = &boot_source.kernel_cmdline.to_string(); device_tree::set_property_string(fdt, node, "bootargs", cmdline.as_str())?;
+ let mut prop_string = String::new(); + for dev_info in self.bus.get_devices_info().iter().rev() { + if dev_info.dev_type == DeviceType::SERIAL { + prop_string = format!("/pl011@{:x}", dev_info.addr); + } + } + device_tree::set_property_string(fdt, node, "stdout-path", &prop_string)?; + match &boot_source.initrd { Some(initrd) => { device_tree::set_property_u64( @@ -1323,6 +1338,7 @@ impl device_tree::CompileFDT for LightMachine { self.generate_chosen_node(fdt)?; self.irq_chip.generate_fdt_node(fdt)?;
+ util::device_tree::dump_dtb(fdt, "/tmp/dtb"); Ok(()) } } diff --git a/device_model/src/mmio/mod.rs b/device_model/src/mmio/mod.rs index 4ac532f..c29412b 100644 --- a/device_model/src/mmio/mod.rs +++ b/device_model/src/mmio/mod.rs @@ -154,7 +154,7 @@ impl MmioDevice { #[cfg(target_arch = "aarch64")] cmdline.push(Param { param_type: "earlycon".to_string(), - value: format!("uart,mmio,0x{:08x}", self.resource.addr), + value: format!("pl011,mmio,0x{:08x}", self.resource.addr), }); } else { #[cfg(target_arch = "x86_64")]