diff --git a/src/arch/src/lib.rs b/src/arch/src/lib.rs index c8b83b4db..4ab183b53 100644 --- a/src/arch/src/lib.rs +++ b/src/arch/src/lib.rs @@ -8,6 +8,10 @@ use std::result; #[derive(Default)] pub struct ArchMemoryInfo { + #[cfg(target_arch = "x86_64")] + pub ram_below_gap: u64, + #[cfg(target_arch = "x86_64")] + pub ram_above_gap: u64, pub ram_last_addr: u64, pub shm_start_addr: u64, pub page_size: usize, diff --git a/src/arch/src/x86_64/mod.rs b/src/arch/src/x86_64/mod.rs index 5e849f1fd..eaff9960f 100644 --- a/src/arch/src/x86_64/mod.rs +++ b/src/arch/src/x86_64/mod.rs @@ -71,78 +71,94 @@ pub fn arch_memory_regions( // It's safe to cast MMIO_MEM_START to usize because it fits in a u32 variable // (It points to an address in the 32 bit space). - let (ram_last_addr, shm_start_addr, regions, firmware_addr) = match size - .checked_sub(MMIO_MEM_START as usize) - { - // case1: guest memory fits before the gap - None | Some(0) => { - let shm_start_addr = FIRST_ADDR_PAST_32BITS; - - let (ram_last_addr, mut regions) = if let Some(kernel_load_addr) = kernel_load_addr { - if size < (kernel_load_addr + kernel_size as u64) as usize { - panic!("Kernel doesn't fit in RAM"); - } - - let ram_last_addr = kernel_load_addr + kernel_size as u64 + size as u64; + let (ram_below_gap, ram_above_gap, ram_last_addr, shm_start_addr, regions, firmware_addr) = + match size.checked_sub(MMIO_MEM_START as usize) { + // case1: guest memory fits before the gap + None | Some(0) => { + let shm_start_addr = FIRST_ADDR_PAST_32BITS; + + let (ram_last_addr, mut regions) = if let Some(kernel_load_addr) = kernel_load_addr + { + if size < (kernel_load_addr + kernel_size as u64) as usize { + panic!("Kernel doesn't fit in RAM"); + } + + let ram_last_addr = kernel_load_addr + kernel_size as u64 + size as u64; + ( + ram_last_addr, + vec![ + (GuestAddress(0), kernel_load_addr as usize), + (GuestAddress(kernel_load_addr + kernel_size as u64), size), + ], + ) + } else { + let ram_last_addr = size as u64; + (ram_last_addr, vec![(GuestAddress(0), size)]) + }; + + let firmware_addr = if let Some(firmware_size) = firmware_size { + let firmware_start = layout::FIRST_ADDR_PAST_32BITS - firmware_size as u64; + regions.push((GuestAddress(firmware_start), firmware_size)); + firmware_start + } else { + 0 + }; + ( + size as u64, + 0, ram_last_addr, - vec![ - (GuestAddress(0), kernel_load_addr as usize), - (GuestAddress(kernel_load_addr + kernel_size as u64), size), - ], + shm_start_addr, + regions, + firmware_addr, ) - } else { - let ram_last_addr = size as u64; - (ram_last_addr, vec![(GuestAddress(0), size)]) - }; - - let firmware_addr = if let Some(firmware_size) = firmware_size { - let firmware_start = layout::FIRST_ADDR_PAST_32BITS - firmware_size as u64; - regions.push((GuestAddress(firmware_start), firmware_size)); - firmware_start - } else { - 0 - }; - - (ram_last_addr, shm_start_addr, regions, firmware_addr) - } + } - // case2: guest memory extends beyond the gap - Some(remaining) => { - let ram_last_addr = FIRST_ADDR_PAST_32BITS + remaining as u64; - let shm_start_addr = ((ram_last_addr / 0x4000_0000) + 1) * 0x4000_0000; + // case2: guest memory extends beyond the gap + Some(remaining) => { + let ram_last_addr = FIRST_ADDR_PAST_32BITS + remaining as u64; + let shm_start_addr = ((ram_last_addr / 0x4000_0000) + 1) * 0x4000_0000; - let mut regions = if let Some(kernel_load_addr) = kernel_load_addr { - vec![ - (GuestAddress(0), kernel_load_addr as usize), - ( - GuestAddress(kernel_load_addr + kernel_size as u64), - (MMIO_MEM_START - (kernel_load_addr + kernel_size as u64)) as usize, - ), - (GuestAddress(FIRST_ADDR_PAST_32BITS), remaining), - ] - } else { - vec![ - (GuestAddress(0), MMIO_MEM_START as usize), - (GuestAddress(FIRST_ADDR_PAST_32BITS), remaining), - ] - }; - - let firmware_addr = if let Some(firmware_size) = firmware_size { - let firmware_start = layout::FIRST_ADDR_PAST_32BITS - firmware_size as u64; - regions.insert( - regions.len() - 2, - (GuestAddress(firmware_start), firmware_size), - ); - firmware_start - } else { - 0 - }; - - (ram_last_addr, shm_start_addr, regions, firmware_addr) - } - }; + let mut regions = if let Some(kernel_load_addr) = kernel_load_addr { + vec![ + (GuestAddress(0), kernel_load_addr as usize), + ( + GuestAddress(kernel_load_addr + kernel_size as u64), + (MMIO_MEM_START - (kernel_load_addr + kernel_size as u64)) as usize, + ), + (GuestAddress(FIRST_ADDR_PAST_32BITS), remaining), + ] + } else { + vec![ + (GuestAddress(0), MMIO_MEM_START as usize), + (GuestAddress(FIRST_ADDR_PAST_32BITS), remaining), + ] + }; + + let firmware_addr = if let Some(firmware_size) = firmware_size { + let firmware_start = layout::FIRST_ADDR_PAST_32BITS - firmware_size as u64; + regions.insert( + regions.len() - 1, + (GuestAddress(firmware_start), firmware_size), + ); + firmware_start + } else { + 0 + }; + + ( + MMIO_MEM_START, + remaining as u64, + ram_last_addr, + shm_start_addr, + regions, + firmware_addr, + ) + } + }; let info = ArchMemoryInfo { + ram_below_gap, + ram_above_gap, ram_last_addr, shm_start_addr, page_size, @@ -176,36 +192,43 @@ pub fn arch_memory_regions( // It's safe to cast MMIO_MEM_START to usize because it fits in a u32 variable // (It points to an address in the 32 bit space). - let (ram_last_addr, shm_start_addr, regions) = match size.checked_sub(MMIO_MEM_START as usize) { - // case1: guest memory fits before the gap - None | Some(0) => { - let ram_last_addr = size as u64; - let shm_start_addr = 0u64; - ( - ram_last_addr, - shm_start_addr, - vec![ - (GuestAddress(0), size), - (GuestAddress(FIRMWARE_START), FIRMWARE_SIZE as usize), - ], - ) - } - // case2: guest memory extends beyond the gap - Some(remaining) => { - let ram_last_addr = FIRST_ADDR_PAST_32BITS + remaining as u64; - let shm_start_addr = 0u64; - ( - ram_last_addr, - shm_start_addr, - vec![ - (GuestAddress(0), MMIO_MEM_START as usize), - (GuestAddress(FIRMWARE_START), FIRMWARE_SIZE as usize), - (GuestAddress(FIRST_ADDR_PAST_32BITS), remaining), - ], - ) - } - }; + let (ram_below_gap, ram_above_gap, ram_last_addr, shm_start_addr, regions) = + match size.checked_sub(MMIO_MEM_START as usize) { + // case1: guest memory fits before the gap + None | Some(0) => { + let ram_last_addr = size as u64; + let shm_start_addr = 0u64; + ( + size as u64, + 0, + ram_last_addr, + shm_start_addr, + vec![ + (GuestAddress(0), size), + (GuestAddress(FIRMWARE_START), FIRMWARE_SIZE as usize), + ], + ) + } + // case2: guest memory extends beyond the gap + Some(remaining) => { + let ram_last_addr = FIRST_ADDR_PAST_32BITS + remaining as u64; + let shm_start_addr = 0u64; + ( + MMIO_MEM_START, + remaining as u64, + ram_last_addr, + shm_start_addr, + vec![ + (GuestAddress(0), MMIO_MEM_START as usize), + (GuestAddress(FIRMWARE_START), FIRMWARE_SIZE as usize), + (GuestAddress(FIRST_ADDR_PAST_32BITS), remaining), + ], + ) + } + }; let info = ArchMemoryInfo { + ram_below_gap, + ram_above_gap, ram_last_addr, shm_start_addr, page_size, diff --git a/src/devices/src/legacy/mod.rs b/src/devices/src/legacy/mod.rs index 88c310f30..b311e902b 100644 --- a/src/devices/src/legacy/mod.rs +++ b/src/devices/src/legacy/mod.rs @@ -28,6 +28,8 @@ mod vcpu; #[cfg(target_arch = "x86_64")] mod x86_64; #[cfg(target_arch = "x86_64")] +use x86_64::cmos; +#[cfg(target_arch = "x86_64")] use x86_64::serial; #[cfg(target_arch = "aarch64")] mod aarch64; @@ -40,6 +42,8 @@ mod riscv64; #[cfg(target_arch = "riscv64")] use riscv64::serial; +#[cfg(target_arch = "x86_64")] +pub use self::cmos::Cmos; #[cfg(target_os = "macos")] pub use self::gicv3::GicV3; #[cfg(target_arch = "aarch64")] diff --git a/src/devices/src/legacy/x86_64/cmos.rs b/src/devices/src/legacy/x86_64/cmos.rs new file mode 100644 index 000000000..78b46dd94 --- /dev/null +++ b/src/devices/src/legacy/x86_64/cmos.rs @@ -0,0 +1,79 @@ +// Copyright 2025 Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::cmp::min; + +use crate::bus::BusDevice; + +const INDEX_MASK: u8 = 0x7f; +const INDEX_OFFSET: u64 = 0x0; +const DATA_OFFSET: u64 = 0x1; +const DATA_LEN: usize = 128; + +pub struct Cmos { + index: u8, + data: [u8; DATA_LEN], +} + +impl Cmos { + pub fn new(mem_below_4g: u64, mem_above_4g: u64) -> Cmos { + debug!("cmos: mem_below_4g={mem_below_4g} mem_above_4g={mem_above_4g}"); + + let mut data = [0u8; DATA_LEN]; + + // Extended memory from 16 MB to 4 GB in units of 64 KB + let ext_mem = min( + 0xFFFF, + mem_below_4g.saturating_sub(16 * 1024 * 1024) / (64 * 1024), + ); + data[0x34] = ext_mem as u8; + data[0x35] = (ext_mem >> 8) as u8; + + // High memory (> 4GB) in units of 64 KB + let high_mem = min(0xFFFFFF, mem_above_4g / (64 * 1024)); + data[0x5b] = high_mem as u8; + data[0x5c] = (high_mem >> 8) as u8; + data[0x5d] = (high_mem >> 16) as u8; + + Cmos { index: 0, data } + } +} + +impl BusDevice for Cmos { + fn read(&mut self, _vcpuid: u64, offset: u64, data: &mut [u8]) { + if data.len() != 1 { + error!("cmos: unsupported read length"); + return; + } + + data[0] = match offset { + INDEX_OFFSET => { + debug!("cmos: read index offset"); + self.index + } + DATA_OFFSET => { + debug!("cmos: read data offset from index={:x}", self.index); + self.data[(self.index & INDEX_MASK) as usize] + } + _ => { + debug!("cmos: unsupported read offset"); + 0 + } + }; + } + + fn write(&mut self, _vcpuid: u64, offset: u64, data: &[u8]) { + if data.len() != 1 { + error!("cmos: unsupported write length"); + return; + } + + match offset { + INDEX_OFFSET => { + debug!("cmos: update index"); + self.index = data[0] & INDEX_MASK; + } + _ => error!("cmos: unsupported write to CMOS"), + } + } +} diff --git a/src/devices/src/legacy/x86_64/mod.rs b/src/devices/src/legacy/x86_64/mod.rs index b1fc0cf1d..6e3f15a91 100644 --- a/src/devices/src/legacy/x86_64/mod.rs +++ b/src/devices/src/legacy/x86_64/mod.rs @@ -1 +1,2 @@ +pub mod cmos; pub mod serial; diff --git a/src/vmm/src/builder.rs b/src/vmm/src/builder.rs index b6b92e6eb..4f6f4bd46 100644 --- a/src/vmm/src/builder.rs +++ b/src/vmm/src/builder.rs @@ -28,6 +28,8 @@ use crate::resources::{ConsoleType, VmResources}; use crate::vmm_config::external_kernel::{ExternalKernel, KernelFormat}; #[cfg(feature = "net")] use crate::vmm_config::net::NetBuilder; +#[cfg(target_arch = "x86_64")] +use devices::legacy::Cmos; #[cfg(all(target_os = "linux", target_arch = "riscv64"))] use devices::legacy::KvmAia; #[cfg(all(target_os = "linux", target_arch = "aarch64"))] @@ -750,6 +752,10 @@ pub fn build_microvm( // Safe to unwrap 'serial_device' as it's always 'Some' on x86_64. // x86_64 uses the i8042 reset event as the Vmm exit event. let mut pio_device_manager = PortIODeviceManager::new( + Arc::new(Mutex::new(Cmos::new( + arch_memory_info.ram_below_gap, + arch_memory_info.ram_above_gap, + ))), serial_devices, exit_evt .try_clone() diff --git a/src/vmm/src/device_manager/legacy.rs b/src/vmm/src/device_manager/legacy.rs index b9398db91..c71144523 100644 --- a/src/vmm/src/device_manager/legacy.rs +++ b/src/vmm/src/device_manager/legacy.rs @@ -39,6 +39,7 @@ type Result = ::std::result::Result; /// The `LegacyDeviceManger` should be initialized only by using the constructor. pub struct PortIODeviceManager { pub io_bus: devices::Bus, + pub cmos: Arc>, pub stdio_serial: Vec>>, pub i8042: Arc>, @@ -52,6 +53,7 @@ pub struct PortIODeviceManager { impl PortIODeviceManager { /// Create a new DeviceManager handling legacy devices (uart, i8042). pub fn new( + cmos: Arc>, stdio_serial: Vec>>, i8042_reset_evfd: EventFd, ) -> Result { @@ -79,6 +81,7 @@ impl PortIODeviceManager { Ok(PortIODeviceManager { io_bus, + cmos, stdio_serial, i8042, com_evt_1: evts[0].try_clone().map_err(Error::EventFd)?, @@ -91,6 +94,10 @@ impl PortIODeviceManager { /// Register supported legacy devices. pub fn register_devices(&mut self) -> Result<()> { + self.io_bus + .insert(self.cmos.clone(), 0x70, 0x8) + .map_err(Error::BusError)?; + if let Some(serial) = self.stdio_serial.first() { self.io_bus .insert(serial.clone(), 0x3f8, 0x8)