devices: Implement DMA for fw_cfg device
We pass a reference to the guest memory when we create the device in DeviceManager. This allows us to access the guest memory for DMA. Signed-off-by: Alex Orozco <alexorozco@google.com>
This commit is contained in:
parent
1f51e4525b
commit
edee53ac1a
4 changed files with 257 additions and 16 deletions
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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<usize> {
|
||||
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<FwCfgItem>, // 0x20 and above
|
||||
known_items: [FwCfgContent; FW_CFG_KNOWN_ITEMS], // 0x0 to 0x19
|
||||
memory: GuestMemoryAtomic<GuestMemoryMmap<AtomicBitmap>>,
|
||||
}
|
||||
|
||||
#[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<GuestMemoryMmap<AtomicBitmap>>) -> 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<u32> {
|
||||
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::<boot_params>()];
|
||||
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<AtomicBitmap> =
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue