devices: Add fw_cfg device
Here we add the fw_cfg device as a legacy device to the device manager. It is guarded behind a fw_cfg flag in vmm at creation of the DeviceManager. In this cl we implement the fw_cfg device with one function (signature). Signed-off-by: Alex Orozco <alexorozco@google.com>
This commit is contained in:
parent
5226ceb974
commit
777b7ee11e
11 changed files with 455 additions and 0 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -542,6 +542,7 @@ dependencies = [
|
|||
"vm-memory",
|
||||
"vm-migration",
|
||||
"vmm-sys-util",
|
||||
"zerocopy 0.8.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ wait-timeout = { workspace = true }
|
|||
dbus_api = ["vmm/dbus_api", "zbus"]
|
||||
default = ["io_uring", "kvm"]
|
||||
dhat-heap = ["dhat", "vmm/dhat-heap"] # For heap profiling
|
||||
fw_cfg = ["vmm/fw_cfg"]
|
||||
guest_debug = ["vmm/guest_debug"]
|
||||
igvm = ["mshv", "vmm/igvm"]
|
||||
io_uring = ["vmm/io_uring"]
|
||||
|
|
|
|||
|
|
@ -28,11 +28,16 @@ vm-memory = { workspace = true, features = [
|
|||
] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = { workspace = true }
|
||||
zerocopy = { version = "0.8.26", features = [
|
||||
"alloc",
|
||||
"derive",
|
||||
], optional = true }
|
||||
|
||||
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
|
||||
arch = { path = "../arch" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
fw_cfg = ["zerocopy"]
|
||||
kvm = ["arch/kvm"]
|
||||
pvmemcontrol = []
|
||||
|
|
|
|||
309
devices/src/legacy/fw_cfg.rs
Normal file
309
devices/src/legacy/fw_cfg.rs
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
// Copyright 2025 Google LLC.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
/// Cloud Hypervisor implementation of Qemu's fw_cfg spec
|
||||
/// https://www.qemu.org/docs/master/specs/fw_cfg.html
|
||||
/// Linux kernel fw_cfg driver header
|
||||
/// https://github.com/torvalds/linux/blob/master/include/uapi/linux/qemu_fw_cfg.h
|
||||
/// Uploading files to the guest via fw_cfg is supported for all kernels 4.6+ w/ CONFIG_FW_CFG_SYSFS enabled
|
||||
/// https://cateee.net/lkddb/web-lkddb/FW_CFG_SYSFS.html
|
||||
/// No kernel requirement if above functionality is not required,
|
||||
/// only firmware must implement mechanism to interact with this fw_cfg device
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Result,
|
||||
mem::size_of_val,
|
||||
os::unix::fs::FileExt,
|
||||
sync::{Arc, Barrier},
|
||||
};
|
||||
|
||||
use vm_device::BusDevice;
|
||||
use vmm_sys_util::sock_ctrl_msg::IntoIovec;
|
||||
use zerocopy::{FromBytes, IntoBytes};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const PORT_FW_CFG_SELECTOR: u64 = 0x510;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const PORT_FW_CFG_DATA: u64 = 0x511;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const PORT_FW_CFG_DMA_HI: u64 = 0x514;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const PORT_FW_CFG_DMA_LO: u64 = 0x518;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub const PORT_FW_CFG_BASE: u64 = 0x510;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub const PORT_FW_CFG_WIDTH: u64 = 0xc;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PORT_FW_CFG_SELECTOR: u64 = 0x9030008;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PORT_FW_CFG_DATA: u64 = 0x9030000;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PORT_FW_CFG_DMA_HI: u64 = 0x9030010;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PORT_FW_CFG_DMA_LO: u64 = 0x9030014;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub const PORT_FW_CFG_BASE: u64 = 0x9030000;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub const PORT_FW_CFG_WIDTH: u64 = 0x10;
|
||||
|
||||
const FW_CFG_SIGNATURE: u16 = 0x00;
|
||||
const FW_CFG_ID: u16 = 0x01;
|
||||
const FW_CFG_FILE_DIR: u16 = 0x19;
|
||||
const FW_CFG_KNOWN_ITEMS: usize = 0x20;
|
||||
|
||||
pub const FW_CFG_FILE_FIRST: u16 = 0x20;
|
||||
pub const FW_CFG_DMA_SIGNATURE: [u8; 8] = *b"QEMU CFG";
|
||||
// Reserved (must be enabled)
|
||||
const FW_CFG_F_RESERVED: u8 = 1 << 0;
|
||||
// DMA Toggle Bit (enabled by default)
|
||||
const FW_CFG_F_DMA: u8 = 1 << 1;
|
||||
pub const FW_CFG_FEATURE: [u8; 4] = [FW_CFG_F_RESERVED | FW_CFG_F_DMA, 0, 0, 0];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FwCfgContent {
|
||||
Bytes(Vec<u8>),
|
||||
Slice(&'static [u8]),
|
||||
File(u64, File),
|
||||
U32(u32),
|
||||
}
|
||||
|
||||
impl Default for FwCfgContent {
|
||||
fn default() -> Self {
|
||||
FwCfgContent::Slice(&[])
|
||||
}
|
||||
}
|
||||
|
||||
impl FwCfgContent {
|
||||
fn size(&self) -> Result<u32> {
|
||||
let ret = match self {
|
||||
FwCfgContent::Bytes(v) => v.len(),
|
||||
FwCfgContent::File(offset, f) => (f.metadata()?.len() - offset) as usize,
|
||||
FwCfgContent::Slice(s) => s.len(),
|
||||
FwCfgContent::U32(n) => size_of_val(n),
|
||||
};
|
||||
u32::try_from(ret).map_err(|_| std::io::ErrorKind::InvalidInput.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FwCfgItem {
|
||||
pub name: String,
|
||||
pub content: FwCfgContent,
|
||||
}
|
||||
|
||||
/// https://www.qemu.org/docs/master/specs/fw_cfg.html
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FwCfg {
|
||||
selector: u16,
|
||||
data_offset: u32,
|
||||
items: Vec<FwCfgItem>, // 0x20 and above
|
||||
known_items: [FwCfgContent; FW_CFG_KNOWN_ITEMS], // 0x0 to 0x19
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, IntoBytes, FromBytes)]
|
||||
struct FwCfgFilesHeader {
|
||||
count_be: u32,
|
||||
}
|
||||
|
||||
pub const FILE_NAME_SIZE: usize = 56;
|
||||
|
||||
pub fn create_file_name(name: &str) -> [u8; FILE_NAME_SIZE] {
|
||||
let mut c_name = [0u8; FILE_NAME_SIZE];
|
||||
let c_len = std::cmp::min(FILE_NAME_SIZE - 1, name.len());
|
||||
c_name[0..c_len].copy_from_slice(&name.as_bytes()[0..c_len]);
|
||||
c_name
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, IntoBytes, FromBytes, Clone, Copy)]
|
||||
struct BootE820Entry {
|
||||
addr: u64,
|
||||
size: u64,
|
||||
type_: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, IntoBytes, FromBytes)]
|
||||
struct FwCfgFile {
|
||||
size_be: u32,
|
||||
select_be: u16,
|
||||
_reserved: u16,
|
||||
name: [u8; FILE_NAME_SIZE],
|
||||
}
|
||||
|
||||
impl FwCfg {
|
||||
pub fn new() -> 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);
|
||||
known_items[FW_CFG_ID as usize] = FwCfgContent::Slice(&FW_CFG_FEATURE);
|
||||
let file_buf = Vec::from(FwCfgFilesHeader { count_be: 0 }.as_mut_bytes());
|
||||
known_items[FW_CFG_FILE_DIR as usize] = FwCfgContent::Bytes(file_buf);
|
||||
|
||||
FwCfg {
|
||||
selector: 0,
|
||||
data_offset: 0,
|
||||
items: vec![],
|
||||
known_items,
|
||||
}
|
||||
}
|
||||
|
||||
fn file_dir_mut(&mut self) -> &mut Vec<u8> {
|
||||
let FwCfgContent::Bytes(file_buf) = &mut self.known_items[FW_CFG_FILE_DIR as usize] else {
|
||||
unreachable!("fw_cfg: selector {FW_CFG_FILE_DIR:#x} should be FwCfgContent::Byte!")
|
||||
};
|
||||
file_buf
|
||||
}
|
||||
|
||||
fn update_count(&mut self) {
|
||||
let mut header = FwCfgFilesHeader {
|
||||
count_be: (self.items.len() as u32).to_be(),
|
||||
};
|
||||
self.file_dir_mut()[0..4].copy_from_slice(header.as_mut_bytes());
|
||||
}
|
||||
|
||||
pub fn add_item(&mut self, item: FwCfgItem) -> Result<()> {
|
||||
let index = self.items.len();
|
||||
let c_name = create_file_name(&item.name);
|
||||
let size = item.content.size()?;
|
||||
let mut cfg_file = FwCfgFile {
|
||||
size_be: size.to_be(),
|
||||
select_be: (FW_CFG_FILE_FIRST + index as u16).to_be(),
|
||||
_reserved: 0,
|
||||
name: c_name,
|
||||
};
|
||||
self.file_dir_mut()
|
||||
.extend_from_slice(cfg_file.as_mut_bytes());
|
||||
self.items.push(item);
|
||||
self.update_count();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_content(content: &FwCfgContent, offset: u32, data: &mut [u8], size: u32) -> Option<u8> {
|
||||
let start = offset as usize;
|
||||
let end = start + size as usize;
|
||||
match content {
|
||||
FwCfgContent::Bytes(b) => {
|
||||
if b.len() >= size as usize {
|
||||
data.copy_from_slice(&b[start..end]);
|
||||
}
|
||||
}
|
||||
FwCfgContent::Slice(s) => {
|
||||
if s.len() >= size as usize {
|
||||
data.copy_from_slice(&s[start..end]);
|
||||
}
|
||||
}
|
||||
FwCfgContent::File(o, f) => {
|
||||
f.read_exact_at(data, o + offset as u64).ok()?;
|
||||
}
|
||||
FwCfgContent::U32(n) => {
|
||||
let bytes = n.to_le_bytes();
|
||||
data.copy_from_slice(&bytes[start..end]);
|
||||
}
|
||||
};
|
||||
Some(size as u8)
|
||||
}
|
||||
|
||||
fn read_data(&mut self, data: &mut [u8], size: u32) -> u8 {
|
||||
let ret = if let Some(content) = self.known_items.get(self.selector as usize) {
|
||||
Self::read_content(content, self.data_offset, data, size)
|
||||
} else if let Some(item) = self.items.get((self.selector - FW_CFG_FILE_FIRST) as usize) {
|
||||
Self::read_content(&item.content, self.data_offset, data, size)
|
||||
} else {
|
||||
error!("fw_cfg: selector {:#x} does not exist.", self.selector);
|
||||
None
|
||||
};
|
||||
if let Some(val) = ret {
|
||||
self.data_offset += size;
|
||||
val
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for FwCfg {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
let port = offset + PORT_FW_CFG_BASE;
|
||||
let size = data.len();
|
||||
match (port, size) {
|
||||
(PORT_FW_CFG_SELECTOR, _) => {
|
||||
error!("fw_cfg: selector register is write-only.");
|
||||
}
|
||||
(PORT_FW_CFG_DATA, _) => _ = self.read_data(data, size as u32),
|
||||
(PORT_FW_CFG_DMA_HI, 4) => {
|
||||
unimplemented!()
|
||||
}
|
||||
(PORT_FW_CFG_DMA_LO, 4) => {
|
||||
unimplemented!()
|
||||
}
|
||||
_ => {
|
||||
debug!("fw_cfg: read from unknown port {port:#x}: {size:#x} bytes and offset {offset:#x}.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
let port = offset + PORT_FW_CFG_BASE;
|
||||
let size = data.size();
|
||||
match (port, size) {
|
||||
(PORT_FW_CFG_SELECTOR, 2) => {
|
||||
let mut buf = [0u8; 2];
|
||||
buf[..size].copy_from_slice(&data[..size]);
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
let val = u16::from_le_bytes(buf);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
let val = u16::from_be_bytes(buf);
|
||||
self.selector = val;
|
||||
self.data_offset = 0;
|
||||
}
|
||||
(PORT_FW_CFG_DATA, 1) => error!("fw_cfg: data register is read-only."),
|
||||
(PORT_FW_CFG_DMA_HI, 4) => {
|
||||
unimplemented!()
|
||||
}
|
||||
(PORT_FW_CFG_DMA_LO, 4) => {
|
||||
unimplemented!()
|
||||
}
|
||||
_ => debug!(
|
||||
"fw_cfg: write to unknown port {port:#x}: {size:#x} bytes and offset {offset:#x} ."
|
||||
),
|
||||
};
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const SELECTOR_OFFSET: u64 = 0;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const SELECTOR_OFFSET: u64 = 8;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const DATA_OFFSET: u64 = 1;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const DATA_OFFSET: u64 = 0;
|
||||
|
||||
#[test]
|
||||
fn test_signature() {
|
||||
let mut fw_cfg = FwCfg::new();
|
||||
|
||||
let mut data = vec![0u8];
|
||||
|
||||
let mut sig_iter = FW_CFG_DMA_SIGNATURE.into_iter();
|
||||
fw_cfg.write(0, SELECTOR_OFFSET, &[FW_CFG_SIGNATURE as u8, 0]);
|
||||
loop {
|
||||
if let Some(char) = sig_iter.next() {
|
||||
fw_cfg.read(0, DATA_OFFSET, &mut data);
|
||||
assert_eq!(data[0], char);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@
|
|||
mod cmos;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod debug_port;
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
pub mod fw_cfg;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod fwdebug;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
|
|
@ -22,6 +24,8 @@ mod uart_pl011;
|
|||
pub use self::cmos::Cmos;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use self::debug_port::DebugPort;
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
pub use self::fw_cfg::FwCfg;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use self::fwdebug::FwDebugDevice;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
|
|
|
|||
77
docs/fw_cfg.md
Normal file
77
docs/fw_cfg.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# Firmware Configuration (fw_cfg) Device
|
||||
|
||||
The `fw_cfg` device is a QEMU-compatible device that allows the hypervisor to pass configuration and data to the guest operating system. This is particularly useful for firmware to access information like ACPI tables, kernel images, initramfs, kernel command lines, and other arbitrary data blobs.
|
||||
|
||||
Cloud Hypervisor implements the `fw_cfg` device with DMA-enabled access.
|
||||
|
||||
## Purpose
|
||||
|
||||
The `fw_cfg` device serves as a generic information channel between the VMM and the guest. It can be used to:
|
||||
|
||||
* Load the kernel, initramfs, and kernel command line for direct kernel boot with firmware.
|
||||
* Provide ACPI tables to the guest firmware or OS.
|
||||
* Pass custom configuration files or data blobs (e.g., attestation data, SEV-SNP launch secrets) to the guest.
|
||||
* Supply an E820 memory map to the guest.
|
||||
|
||||
## Enabling `fw_cfg`
|
||||
|
||||
The `fw_cfg` device is enabled via the `fw_cfg` feature flag when building Cloud Hypervisor:
|
||||
|
||||
```bash
|
||||
cargo build --features fw_cfg
|
||||
```
|
||||
|
||||
## Guest Kernel Configuration
|
||||
|
||||
For the guest Linux kernel to recognize and use the `fw_cfg` device via sysfs, the following kernel configuration option must be enabled:
|
||||
|
||||
* `CONFIG_FW_CFG_SYSFS=y`
|
||||
|
||||
This option allows the kernel to expose `fw_cfg` entries under `/sys/firmware/qemu_fw_cfg/by_name/`.
|
||||
|
||||
## Command Line Options
|
||||
|
||||
The `fw_cfg` device is configured using the `--fw-cfg-config` command-line option.
|
||||
|
||||
**Parameters:**
|
||||
* `e820=on|off`: (Default: `on`) Whether to add an E820 memory map entry to `fw_cfg`.
|
||||
* `kernel=on|off`: (Default: `on`) Whether to add the kernel image (specified by `--kernel`) to `fw_cfg`.
|
||||
* `cmdline=on|off`: (Default: `on`) Whether to add the kernel command line (specified by `--cmdline`) to `fw_cfg`.
|
||||
* `initramfs=on|off`: (Default: `on`) Whether to add the initramfs image (specified by `--initramfs`) to `fw_cfg`.
|
||||
* `acpi_table=on|off`: (Default: `on`) Whether to add generated ACPI tables to `fw_cfg`.
|
||||
* `items=[... : ...]`: A list of custom key-value pairs to be exposed via `fw_cfg`.
|
||||
* `name=<guest_sysfs_path>`: The path under which the item will appear in the guest's sysfs (e.g., `opt/org.example/my-data`).
|
||||
* `file=<host_file_path>`: The path to the file on the host whose content will be provided to the guest for this item.
|
||||
|
||||
**Example Usage:**
|
||||
|
||||
1. **Direct kernel boot with custom `fw_cfg` entries:**
|
||||
|
||||
```bash
|
||||
cloud-hypervisor \
|
||||
--kernel /path/to/vmlinux \
|
||||
--cmdline "console=hvc0 root=/dev/vda1" \
|
||||
--disk path=/path/to/rootfs.img \
|
||||
--fw-cfg-config initramfs=off,items=[name=opt/org.mycorp/setup_info,file=/tmp/guest_setup.txt] \
|
||||
...
|
||||
```
|
||||
In the guest, `/tmp/guest_setup.txt` from the host will be accessible at `/sys/firmware/qemu_fw_cfg/by_name/opt/org.mycorp/setup_info/raw`.
|
||||
|
||||
2. **Disabling `fw_cfg` explicitly:**
|
||||
|
||||
```bash
|
||||
cloud-hypervisor \
|
||||
--fw-cfg-config disable \
|
||||
...
|
||||
```
|
||||
|
||||
## Accessing `fw_cfg` Items in the Guest
|
||||
|
||||
If `CONFIG_FW_CFG_SYSFS` is enabled in the guest kernel, items added to `fw_cfg` can be accessed via sysfs.
|
||||
|
||||
For example, an item added with `name=opt/org.example/my-data` will be available at:
|
||||
`/sys/firmware/qemu_fw_cfg/by_name/opt/org.example/my-data/raw`
|
||||
|
||||
The `raw` file contains the binary content of the host file provided.
|
||||
|
||||
Standard items like kernel, initramfs, cmdline, and ACPI tables also have predefined names (e.g., `etc/kernel`, `etc/cmdline`) if they are enabled to be passed via `fw_cfg`.
|
||||
|
|
@ -853,6 +853,8 @@ fn main() {
|
|||
compile_error!("Feature 'tdx' and 'sev_snp' are mutually exclusive.");
|
||||
#[cfg(all(feature = "sev_snp", not(target_arch = "x86_64")))]
|
||||
compile_error!("Feature 'sev_snp' needs target 'x86_64'");
|
||||
#[cfg(all(feature = "fw_cfg", target_arch = "riscv64"))]
|
||||
compile_error!("Feature 'fw_cfg' needs targets 'x86_64' or 'aarch64'");
|
||||
|
||||
#[cfg(feature = "dhat-heap")]
|
||||
let _profiler = dhat::Profiler::new_heap();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ version = "0.1.0"
|
|||
dbus_api = ["blocking", "futures", "zbus"]
|
||||
default = []
|
||||
dhat-heap = ["dhat"] # For heap profiling
|
||||
fw_cfg = ["devices/fw_cfg"]
|
||||
guest_debug = ["gdbstub", "gdbstub_arch", "kvm"]
|
||||
igvm = ["dep:igvm", "hex", "igvm_defs", "mshv-bindings", "range_map_vec"]
|
||||
io_uring = ["block/io_uring"]
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@ use devices::ioapic;
|
|||
use devices::legacy::Pl011;
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))]
|
||||
use devices::legacy::Serial;
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
use devices::legacy::{
|
||||
fw_cfg::{PORT_FW_CFG_BASE, PORT_FW_CFG_WIDTH},
|
||||
FwCfg,
|
||||
};
|
||||
#[cfg(feature = "pvmemcontrol")]
|
||||
use devices::pvmemcontrol::{PvmemcontrolBusDevice, PvmemcontrolPciDevice};
|
||||
use devices::{interrupt_controller, AcpiNotificationFlags};
|
||||
|
|
@ -644,6 +649,11 @@ pub enum DeviceManagerError {
|
|||
/// Cannot lock images of all block devices.
|
||||
#[error("Cannot lock images of all block devices")]
|
||||
DiskLockError(#[source] virtio_devices::block::Error),
|
||||
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
/// Error adding fw_cfg to bus.
|
||||
#[error("Error adding fw_cfg to bus")]
|
||||
ErrorAddingFwCfgToBus(#[source] vm_device::BusError),
|
||||
}
|
||||
|
||||
pub type DeviceManagerResult<T> = result::Result<T, DeviceManagerError>;
|
||||
|
|
@ -1070,6 +1080,9 @@ pub struct DeviceManager {
|
|||
rate_limit_groups: HashMap<String, Arc<RateLimiterGroup>>,
|
||||
|
||||
mmio_regions: Arc<Mutex<Vec<MmioRegion>>>,
|
||||
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
fw_cfg: Option<Arc<Mutex<FwCfg>>>,
|
||||
}
|
||||
|
||||
fn create_mmio_allocators(
|
||||
|
|
@ -1334,6 +1347,8 @@ impl DeviceManager {
|
|||
snapshot,
|
||||
rate_limit_groups,
|
||||
mmio_regions: Arc::new(Mutex::new(Vec::new())),
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
fw_cfg: None,
|
||||
};
|
||||
|
||||
let device_manager = Arc::new(Mutex::new(device_manager));
|
||||
|
|
@ -1460,6 +1475,32 @@ impl DeviceManager {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[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()));
|
||||
|
||||
self.fw_cfg = Some(fw_cfg.clone());
|
||||
|
||||
self.bus_devices
|
||||
.push(Arc::clone(&fw_cfg) as Arc<dyn BusDeviceSync>);
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
self.address_manager
|
||||
.io_bus
|
||||
.insert(fw_cfg, PORT_FW_CFG_BASE, PORT_FW_CFG_WIDTH)
|
||||
.map_err(DeviceManagerError::ErrorAddingFwCfgToBus)?;
|
||||
|
||||
// default address for fw_cfg on arm via mmio
|
||||
// https://github.com/torvalds/linux/blob/master/drivers/firmware/qemu_fw_cfg.c#L27
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
self.address_manager
|
||||
.mmio_bus
|
||||
.insert(fw_cfg.clone(), PORT_FW_CFG_BASE, PORT_FW_CFG_WIDTH)
|
||||
.map_err(DeviceManagerError::ErrorAddingFwCfgToBus)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn state(&self) -> DeviceManagerState {
|
||||
DeviceManagerState {
|
||||
device_tree: self.device_tree.lock().unwrap().clone(),
|
||||
|
|
@ -4181,6 +4222,11 @@ impl DeviceManager {
|
|||
&self.address_manager.mmio_bus
|
||||
}
|
||||
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
pub fn fw_cfg(&self) -> Option<&Arc<Mutex<FwCfg>>> {
|
||||
self.fw_cfg.as_ref()
|
||||
}
|
||||
|
||||
pub fn allocator(&self) -> &Arc<Mutex<SystemAllocator>> {
|
||||
&self.address_manager.allocator
|
||||
}
|
||||
|
|
|
|||
|
|
@ -405,6 +405,8 @@ pub fn feature_list() -> Vec<String> {
|
|||
"dbus_api".to_string(),
|
||||
#[cfg(feature = "dhat-heap")]
|
||||
"dhat-heap".to_string(),
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
"fw_cfg".to_string(),
|
||||
#[cfg(feature = "guest_debug")]
|
||||
"guest_debug".to_string(),
|
||||
#[cfg(feature = "igvm")]
|
||||
|
|
|
|||
|
|
@ -720,6 +720,13 @@ impl Vm {
|
|||
vm.sev_snp_init().map_err(Error::InitializeSevSnpVm)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
device_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.create_fw_cfg_device()
|
||||
.map_err(Error::DeviceManager)?;
|
||||
|
||||
#[cfg(feature = "tdx")]
|
||||
let kernel = config
|
||||
.lock()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue