diff --git a/Cargo.lock b/Cargo.lock index e4f4454ec..5a78ae54c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -526,6 +526,7 @@ dependencies = [ "acpi_tables", "anyhow", "arch", + "bitfield-struct", "bitflags 2.9.0", "byteorder", "event_monitor", diff --git a/devices/Cargo.toml b/devices/Cargo.toml index c35f58b88..81776dbc3 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -8,6 +8,7 @@ version = "0.1.0" acpi_tables = { workspace = true } anyhow = { workspace = true } arch = { path = "../arch" } +bitfield-struct = { version = "0.10.1", optional = true } bitflags = { workspace = true } byteorder = { workspace = true } event_monitor = { path = "../event_monitor" } @@ -43,6 +44,6 @@ arch = { path = "../arch" } [features] default = [] -fw_cfg = ["linux-loader", "zerocopy"] +fw_cfg = ["bitfield-struct", "linux-loader", "zerocopy"] kvm = ["arch/kvm"] pvmemcontrol = [] diff --git a/devices/src/legacy/fw_cfg.rs b/devices/src/legacy/fw_cfg.rs index c12c50d5d..78bf87574 100644 --- a/devices/src/legacy/fw_cfg.rs +++ b/devices/src/legacy/fw_cfg.rs @@ -13,7 +13,7 @@ /// only firmware must implement mechanism to interact with this fw_cfg device use std::{ fs::File, - io::Result, + io::{ErrorKind, Read, Result, Seek, SeekFrom}, mem::offset_of, os::unix::fs::FileExt, sync::{Arc, Barrier}, @@ -30,16 +30,18 @@ use arch::layout::{ MEM_32BIT_RESERVED_START, PCI_MMCONFIG_SIZE, PCI_MMCONFIG_START, RAM_64BIT_START, }; use arch::RegionType; +use bitfield_struct::bitfield; #[cfg(target_arch = "x86_64")] use linux_loader::bootparam::boot_params; #[cfg(target_arch = "aarch64")] use linux_loader::loader::pe::arm64_image_header as boot_params; use vm_device::BusDevice; -use vm_memory::ByteValued; -#[cfg(target_arch = "x86_64")] -use vm_memory::GuestAddress; +use vm_memory::bitmap::AtomicBitmap; +use vm_memory::{ + ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, +}; use vmm_sys_util::sock_ctrl_msg::IntoIovec; -use zerocopy::{FromBytes, Immutable, IntoBytes}; +use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes}; #[cfg(target_arch = "x86_64")] // https://github.com/project-oak/oak/tree/main/stage0_bin#memory-layout @@ -114,6 +116,34 @@ pub enum FwCfgContent { U32(u32), } +struct FwCfgContentAccess<'a> { + content: &'a FwCfgContent, + offset: u32, +} + +impl Read for FwCfgContentAccess<'_> { + fn read(&mut self, buf: &mut [u8]) -> Result { + match self.content { + FwCfgContent::File(offset, f) => { + Seek::seek(&mut (&*f), SeekFrom::Start(offset + self.offset as u64))?; + Read::read(&mut (&*f), buf) + } + FwCfgContent::Bytes(b) => match b.get(self.offset as usize..) { + Some(mut s) => s.read(buf), + None => Err(ErrorKind::UnexpectedEof)?, + }, + FwCfgContent::Slice(b) => match b.get(self.offset as usize..) { + Some(mut s) => s.read(buf), + None => Err(ErrorKind::UnexpectedEof)?, + }, + FwCfgContent::U32(n) => match n.to_le_bytes().get(self.offset as usize..) { + Some(mut s) => s.read(buf), + None => Err(ErrorKind::UnexpectedEof)?, + }, + } + } +} + impl Default for FwCfgContent { fn default() -> Self { FwCfgContent::Slice(&[]) @@ -130,6 +160,12 @@ impl FwCfgContent { }; u32::try_from(ret).map_err(|_| std::io::ErrorKind::InvalidInput.into()) } + fn access(&self, offset: u32) -> FwCfgContentAccess<'_> { + FwCfgContentAccess { + content: self, + offset, + } + } } #[derive(Debug, Default)] @@ -139,12 +175,45 @@ pub struct FwCfgItem { } /// https://www.qemu.org/docs/master/specs/fw_cfg.html -#[derive(Debug, Default)] +#[derive(Debug)] pub struct FwCfg { selector: u16, data_offset: u32, + dma_address: u64, items: Vec, // 0x20 and above known_items: [FwCfgContent; FW_CFG_KNOWN_ITEMS], // 0x0 to 0x19 + memory: GuestMemoryAtomic>, +} + +#[repr(C)] +#[derive(Debug, IntoBytes, FromBytes)] +struct FwCfgDmaAccess { + control_be: u32, + length_be: u32, + address_be: u64, +} + +// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h#L67 +#[bitfield(u32)] +struct AccessControl { + // FW_CFG_DMA_CTL_ERROR = 0x01 + error: bool, + // FW_CFG_DMA_CTL_READ = 0x02 + read: bool, + #[bits(1)] + _unused2: u8, + // FW_CFG_DMA_CTL_SKIP = 0x04 + skip: bool, + #[bits(3)] + _unused3: u8, + // FW_CFG_DMA_CTL_ERROR = 0x08 + select: bool, + #[bits(7)] + _unused4: u8, + // FW_CFG_DMA_CTL_WRITE = 0x10 + write: bool, + #[bits(16)] + _unused: u32, } #[repr(C)] @@ -344,7 +413,7 @@ fn create_acpi_loader(acpi_table: AcpiTable) -> [FwCfgItem; 3] { } impl FwCfg { - pub fn new() -> FwCfg { + pub fn new(memory: GuestMemoryAtomic>) -> FwCfg { const DEFAULT_ITEM: FwCfgContent = FwCfgContent::Slice(&[]); let mut known_items = [DEFAULT_ITEM; FW_CFG_KNOWN_ITEMS]; known_items[FW_CFG_SIGNATURE as usize] = FwCfgContent::Slice(&FW_CFG_DMA_SIGNATURE); @@ -355,8 +424,10 @@ impl FwCfg { FwCfg { selector: 0, data_offset: 0, + dma_address: 0, items: vec![], known_items, + memory, } } @@ -448,6 +519,87 @@ impl FwCfg { Ok(()) } + fn dma_read_content( + &self, + content: &FwCfgContent, + offset: u32, + len: u32, + address: u64, + ) -> Result { + let content_size = content.size()?.saturating_sub(offset); + let op_size = std::cmp::min(content_size, len); + let mut access = content.access(offset); + let mut buf = vec![0u8; op_size as usize]; + access.read_exact(buf.as_mut_bytes())?; + let r = self + .memory + .memory() + .write(buf.as_bytes(), GuestAddress(address)); + match r { + Err(e) => { + error!("fw_cfg: dma read error: {e:x?}"); + Err(ErrorKind::InvalidInput.into()) + } + Ok(size) => Ok(size as u32), + } + } + + fn dma_read(&mut self, selector: u16, len: u32, address: u64) -> Result<()> { + let op_size = if let Some(content) = self.known_items.get(selector as usize) { + self.dma_read_content(content, self.data_offset, len, address) + } else if let Some(item) = self.items.get((selector - FW_CFG_FILE_FIRST) as usize) { + self.dma_read_content(&item.content, self.data_offset, len, address) + } else { + error!("fw_cfg: selector {selector:#x} does not exist."); + Err(ErrorKind::NotFound.into()) + }?; + self.data_offset += op_size; + Ok(()) + } + + fn do_dma(&mut self) { + let dma_address = self.dma_address; + let mut access = FwCfgDmaAccess::new_zeroed(); + let dma_access = match self + .memory + .memory() + .read(access.as_mut_bytes(), GuestAddress(dma_address)) + { + Ok(_) => access, + Err(e) => { + error!("fw_cfg: invalid address of dma access {dma_address:#x}: {e:?}"); + return; + } + }; + let control = AccessControl(u32::from_be(dma_access.control_be)); + if control.select() { + self.selector = control.select() as u16; + } + let len = u32::from_be(dma_access.length_be); + let addr = u64::from_be(dma_access.address_be); + let ret = if control.read() { + self.dma_read(self.selector, len, addr) + } else if control.write() { + Err(ErrorKind::InvalidInput.into()) + } else if control.skip() { + self.data_offset += len; + Ok(()) + } else { + Err(ErrorKind::InvalidData.into()) + }; + let mut access_resp = AccessControl(0); + if let Err(e) = ret { + error!("fw_cfg: dma operation {dma_access:x?}: {e:x?}"); + access_resp.set_error(true); + } + if let Err(e) = self.memory.memory().write( + &access_resp.0.to_be_bytes(), + GuestAddress(dma_address + core::mem::offset_of!(FwCfgDmaAccess, control_be) as u64), + ) { + error!("fw_cfg: finishing dma: {e:?}") + } + } + pub fn add_kernel_data(&mut self, file: &File) -> Result<()> { let mut buffer = vec![0u8; size_of::()]; file.read_exact_at(&mut buffer, 0)?; @@ -560,10 +712,14 @@ impl BusDevice for FwCfg { } (PORT_FW_CFG_DATA, _) => _ = self.read_data(data, size as u32), (PORT_FW_CFG_DMA_HI, 4) => { - unimplemented!() + let addr = self.dma_address; + let addr_hi = (addr >> 32) as u32; + data.copy_from_slice(&addr_hi.to_be_bytes()); } (PORT_FW_CFG_DMA_LO, 4) => { - unimplemented!() + let addr = self.dma_address; + let addr_lo = (addr & 0xffff_ffff) as u32; + data.copy_from_slice(&addr_lo.to_be_bytes()); } _ => { debug!("fw_cfg: read from unknown port {port:#x}: {size:#x} bytes and offset {offset:#x}."); @@ -587,10 +743,19 @@ impl BusDevice for FwCfg { } (PORT_FW_CFG_DATA, 1) => error!("fw_cfg: data register is read-only."), (PORT_FW_CFG_DMA_HI, 4) => { - unimplemented!() + let mut buf = [0u8; 4]; + buf[..size].copy_from_slice(&data[..size]); + let val = u32::from_be_bytes(buf); + self.dma_address &= 0xffff_ffff; + self.dma_address |= (val as u64) << 32; } (PORT_FW_CFG_DMA_LO, 4) => { - unimplemented!() + let mut buf = [0u8; 4]; + buf[..size].copy_from_slice(&data[..size]); + let val = u32::from_be_bytes(buf); + self.dma_address &= !0xffff_ffff; + self.dma_address |= val as u64; + self.do_dma(); } _ => debug!( "fw_cfg: write to unknown port {port:#x}: {size:#x} bytes and offset {offset:#x} ." @@ -617,10 +782,18 @@ mod tests { const DATA_OFFSET: u64 = 1; #[cfg(target_arch = "aarch64")] const DATA_OFFSET: u64 = 0; + #[cfg(target_arch = "x86_64")] + const DMA_OFFSET: u64 = 4; + #[cfg(target_arch = "aarch64")] + const DMA_OFFSET: u64 = 16; #[test] fn test_signature() { - let mut fw_cfg = FwCfg::new(); + let gm = GuestMemoryAtomic::new( + GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(), + ); + + let mut fw_cfg = FwCfg::new(gm); let mut data = vec![0u8]; @@ -637,7 +810,11 @@ mod tests { } #[test] fn test_kernel_cmdline() { - let mut fw_cfg = FwCfg::new(); + let gm = GuestMemoryAtomic::new( + GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(), + ); + + let mut fw_cfg = FwCfg::new(gm); let cmdline = *b"cmdline\0"; @@ -659,7 +836,11 @@ mod tests { #[test] fn test_initram_fs() { - let mut fw_cfg = FwCfg::new(); + let gm = GuestMemoryAtomic::new( + GuestMemoryMmap::from_ranges(&[(GuestAddress(0), RAM_64BIT_START.0 as usize)]).unwrap(), + ); + + let mut fw_cfg = FwCfg::new(gm); let temp = TempFile::new().unwrap(); let mut temp_file = temp.as_file(); @@ -682,4 +863,60 @@ mod tests { } } } + + #[test] + fn test_dma() { + let code = [ + 0xba, 0xf8, 0x03, 0x00, 0xd8, 0x04, b'0', 0xee, 0xb0, b'\n', 0xee, 0xf4, + ]; + + let content = FwCfgContent::Bytes(code.to_vec()); + + let mem_size = 0x1000; + let load_addr = GuestAddress(0x1000); + let mem: GuestMemoryMmap = + GuestMemoryMmap::from_ranges(&[(load_addr, mem_size)]).unwrap(); + + // Note: In firmware we would just allocate FwCfgDmaAccess struct + // and use address of struct (&) as dma address + let mut access_control = AccessControl(0); + // bit 1 = read access + access_control.set_read(true); + // length of data to access + let length_be = (code.len() as u32).to_be(); + // guest address for data + let code_address = 0x1900_u64; + let address_be = code_address.to_be(); + let mut access = FwCfgDmaAccess { + control_be: access_control.0.to_be(), // bit(1) = read bit + length_be, + address_be, + }; + // access address is where to put the code + let access_address = GuestAddress(load_addr.0); + let address_bytes = access_address.0.to_be_bytes(); + let dma_lo: [u8; 4] = address_bytes[0..4].try_into().unwrap(); + let dma_hi: [u8; 4] = address_bytes[4..8].try_into().unwrap(); + + // writing the FwCfgDmaAccess to mem (this would just be self.dma_access.as_ref() in guest) + let _ = mem.write(access.as_mut_bytes(), access_address); + let mem_m = GuestMemoryAtomic::new(mem.clone()); + let mut fw_cfg = FwCfg::new(mem_m); + let cfg_item = FwCfgItem { + name: "code".to_string(), + content, + }; + let _ = fw_cfg.add_item(cfg_item); + + let mut data = [0u8; 12]; + + let _ = mem.read(&mut data, GuestAddress(code_address)); + assert_ne!(data, code); + + fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_FILE_FIRST as u8, 0]); + fw_cfg.write(0, DMA_OFFSET, &dma_lo); + fw_cfg.write(0, DMA_OFFSET + 4, &dma_hi); + let _ = mem.read(&mut data, GuestAddress(code_address)); + assert_eq!(data, code); + } } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index afff34335..db9c0792b 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -1477,7 +1477,9 @@ impl DeviceManager { #[cfg(feature = "fw_cfg")] pub fn create_fw_cfg_device(&mut self) -> Result<(), DeviceManagerError> { - let fw_cfg = Arc::new(Mutex::new(devices::legacy::FwCfg::new())); + let fw_cfg = Arc::new(Mutex::new(devices::legacy::FwCfg::new( + self.memory_manager.lock().as_ref().unwrap().guest_memory(), + ))); self.fw_cfg = Some(fw_cfg.clone());