Skip to content

Commit 6141978

Browse files
slpjakecorrenti
authored andcommitted
devices/x86_64: add cmos legacy device
u-boot uses a legacy CMOS device to obtain the ram size, so do a minimal implementation ourselves. Signed-off-by: Sergio Lopez <slp@redhat.com>
1 parent 7527719 commit 6141978

File tree

5 files changed

+99
-0
lines changed

5 files changed

+99
-0
lines changed

src/devices/src/legacy/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ mod vcpu;
3030
#[cfg(target_arch = "x86_64")]
3131
mod x86_64;
3232
#[cfg(target_arch = "x86_64")]
33+
use x86_64::cmos;
34+
#[cfg(target_arch = "x86_64")]
3335
use x86_64::serial;
3436
#[cfg(target_arch = "aarch64")]
3537
mod aarch64;
@@ -42,6 +44,8 @@ mod riscv64;
4244
#[cfg(target_arch = "riscv64")]
4345
use riscv64::serial;
4446

47+
#[cfg(target_arch = "x86_64")]
48+
pub use self::cmos::Cmos;
4549
#[cfg(target_os = "macos")]
4650
pub use self::gicv3::GicV3;
4751
#[cfg(target_arch = "aarch64")]
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2025 Red Hat, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use std::cmp::min;
5+
6+
use crate::bus::BusDevice;
7+
8+
const INDEX_MASK: u8 = 0x7f;
9+
const INDEX_OFFSET: u64 = 0x0;
10+
const DATA_OFFSET: u64 = 0x1;
11+
const DATA_LEN: usize = 128;
12+
13+
pub struct Cmos {
14+
index: u8,
15+
data: [u8; DATA_LEN],
16+
}
17+
18+
impl Cmos {
19+
pub fn new(mem_below_4g: u64, mem_above_4g: u64) -> Cmos {
20+
debug!("cmos: mem_below_4g={mem_below_4g} mem_above_4g={mem_above_4g}");
21+
22+
let mut data = [0u8; DATA_LEN];
23+
24+
// Extended memory from 16 MB to 4 GB in units of 64 KB
25+
let ext_mem = min(
26+
0xFFFF,
27+
mem_below_4g.saturating_sub(16 * 1024 * 1024) / (64 * 1024),
28+
);
29+
data[0x34] = ext_mem as u8;
30+
data[0x35] = (ext_mem >> 8) as u8;
31+
32+
// High memory (> 4GB) in units of 64 KB
33+
let high_mem = min(0xFFFFFF, mem_above_4g / (64 * 1024));
34+
data[0x5b] = high_mem as u8;
35+
data[0x5c] = (high_mem >> 8) as u8;
36+
data[0x5d] = (high_mem >> 16) as u8;
37+
38+
Cmos { index: 0, data }
39+
}
40+
}
41+
42+
impl BusDevice for Cmos {
43+
fn read(&mut self, _vcpuid: u64, offset: u64, data: &mut [u8]) {
44+
if data.len() != 1 {
45+
error!("cmos: unsupported read length");
46+
return;
47+
}
48+
49+
data[0] = match offset {
50+
INDEX_OFFSET => {
51+
debug!("cmos: read index offset");
52+
self.index
53+
}
54+
DATA_OFFSET => {
55+
debug!("cmos: read data offset from index={:x}", self.index);
56+
self.data[(self.index & INDEX_MASK) as usize]
57+
}
58+
_ => {
59+
debug!("cmos: unsupported read offset");
60+
0
61+
}
62+
};
63+
}
64+
65+
fn write(&mut self, _vcpuid: u64, offset: u64, data: &[u8]) {
66+
if data.len() != 1 {
67+
error!("cmos: unsupported write length");
68+
return;
69+
}
70+
71+
match offset {
72+
INDEX_OFFSET => {
73+
debug!("cmos: update index");
74+
self.index = data[0] & INDEX_MASK;
75+
}
76+
_ => debug!("cmos: ignoring unsupported write to CMOS"),
77+
}
78+
}
79+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod cmos;
12
pub mod serial;

src/vmm/src/builder.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ use crate::resources::{
2929
use crate::vmm_config::external_kernel::{ExternalKernel, KernelFormat};
3030
#[cfg(feature = "net")]
3131
use crate::vmm_config::net::NetBuilder;
32+
#[cfg(target_arch = "x86_64")]
33+
use devices::legacy::Cmos;
3234
#[cfg(all(target_os = "linux", target_arch = "riscv64"))]
3335
use devices::legacy::KvmAia;
3436
#[cfg(target_arch = "x86_64")]
@@ -757,6 +759,10 @@ pub fn build_microvm(
757759
// Safe to unwrap 'serial_device' as it's always 'Some' on x86_64.
758760
// x86_64 uses the i8042 reset event as the Vmm exit event.
759761
let mut pio_device_manager = PortIODeviceManager::new(
762+
Arc::new(Mutex::new(Cmos::new(
763+
arch_memory_info.ram_below_gap,
764+
arch_memory_info.ram_above_gap,
765+
))),
760766
serial_devices,
761767
exit_evt
762768
.try_clone()

src/vmm/src/device_manager/legacy.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type Result<T> = ::std::result::Result<T, Error>;
3939
/// The `LegacyDeviceManger` should be initialized only by using the constructor.
4040
pub struct PortIODeviceManager {
4141
pub io_bus: devices::Bus,
42+
pub cmos: Arc<Mutex<devices::legacy::Cmos>>,
4243
pub stdio_serial: Vec<Arc<Mutex<devices::legacy::Serial>>>,
4344
pub i8042: Arc<Mutex<devices::legacy::I8042Device>>,
4445

@@ -52,6 +53,7 @@ pub struct PortIODeviceManager {
5253
impl PortIODeviceManager {
5354
/// Create a new DeviceManager handling legacy devices (uart, i8042).
5455
pub fn new(
56+
cmos: Arc<Mutex<devices::legacy::Cmos>>,
5557
stdio_serial: Vec<Arc<Mutex<devices::legacy::Serial>>>,
5658
i8042_reset_evfd: EventFd,
5759
) -> Result<Self> {
@@ -79,6 +81,7 @@ impl PortIODeviceManager {
7981

8082
Ok(PortIODeviceManager {
8183
io_bus,
84+
cmos,
8285
stdio_serial,
8386
i8042,
8487
com_evt_1: evts[0].try_clone().map_err(Error::EventFd)?,
@@ -91,6 +94,10 @@ impl PortIODeviceManager {
9194

9295
/// Register supported legacy devices.
9396
pub fn register_devices(&mut self) -> Result<()> {
97+
self.io_bus
98+
.insert(self.cmos.clone(), 0x70, 0x8)
99+
.map_err(Error::BusError)?;
100+
94101
if let Some(serial) = self.stdio_serial.first() {
95102
self.io_bus
96103
.insert(serial.clone(), 0x3f8, 0x8)
@@ -147,7 +154,9 @@ mod tests {
147154
fn test_register_legacy_devices() {
148155
let serial =
149156
devices::legacy::Serial::new_sink(EventFd::new(utils::eventfd::EFD_NONBLOCK).unwrap());
157+
let cmos = devices::legacy::Cmos::new(0, 0);
150158
let ldm = PortIODeviceManager::new(
159+
Arc::new(Mutex::new(cmos)),
151160
vec![Arc::new(Mutex::new(serial))],
152161
EventFd::new(utils::eventfd::EFD_NONBLOCK).unwrap(),
153162
);

0 commit comments

Comments
 (0)