Skip to content

Commit 198cab8

Browse files
committed
feat(cpu): introduce CPU topology configuration
This commit adds option `topology` to flag --cpu to allow users to specify CPU topology. On x86_64, the CPU topology is encoded in CPU x2APIC ID, as well CPUID leaf 0xb, 0x1f (Intel Only), and 0x8000_0026 (AMD only). On aarch64, the CPU topology is encoded in CPU MPIDR and the devicetree. Signed-off-by: Changyuan Lyu <changyuanl@google.com>
1 parent 6fa79e4 commit 198cab8

File tree

9 files changed

+298
-48
lines changed

9 files changed

+298
-48
lines changed

alioth-cli/src/boot.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ pub fn boot(args: BootArgs) -> Result<(), Error> {
390390
eprintln!("Please update the cmd line to --cpu count={}", args.num_cpu);
391391
CpuConfig {
392392
count: args.num_cpu,
393+
..Default::default()
393394
}
394395
};
395396
let board_config = BoardConfig {

alioth/src/arch/aarch64/reg.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,10 @@ bitfield! {
174174
#[derive(Copy, Clone, Default, PartialEq, Eq, Hash)]
175175
pub struct MpidrEl1(u64);
176176
impl Debug;
177-
pub aff3, set_aff3: 39, 32;
177+
pub u8, aff3, set_aff3: 39, 32;
178178
pub u, set_u: 30;
179179
pub mt, set_mt: 24;
180-
pub aff2, set_aff2: 23, 16;
181-
pub aff1, set_aff1: 15, 8;
182-
pub aff0, set_aff0: 7, 0;
180+
pub u8, aff2, set_aff2: 23, 16;
181+
pub u8, aff1, set_aff1: 15, 8;
182+
pub u8, aff0, set_aff0: 7, 0;
183183
}

alioth/src/board/board.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ pub enum Error {
7070
Memory { source: Box<crate::mem::Error> },
7171
#[snafu(display("Failed to load payload"), context(false))]
7272
Loader { source: Box<crate::loader::Error> },
73+
#[snafu(display("Invalid CPU topology"))]
74+
InvalidCpuTopology,
7375
#[snafu(display("Failed to create VCPU-{index}"))]
7476
CreateVcpu {
7577
index: u16,
@@ -99,6 +101,29 @@ pub enum Error {
99101

100102
type Result<T, E = Error> = std::result::Result<T, E>;
101103

104+
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Default, Help)]
105+
pub struct CpuTopology {
106+
#[serde(default)]
107+
/// Enable SMT (Hyperthreading)
108+
pub smt: bool,
109+
#[serde(default)]
110+
/// Number of cores per socket.
111+
pub cores: u16,
112+
#[serde(default)]
113+
/// Number of sockets.
114+
pub sockets: u8,
115+
}
116+
117+
impl CpuTopology {
118+
pub fn encode(&self, index: u16) -> (u8, u16, u8) {
119+
let total_cores = self.cores * self.sockets as u16;
120+
let thread_id = index / total_cores;
121+
let core_id = index % total_cores % self.cores;
122+
let socket_id = index % total_cores / self.cores;
123+
(thread_id as u8, core_id, socket_id as u8)
124+
}
125+
}
126+
102127
const fn default_cpu_count() -> u16 {
103128
1
104129
}
@@ -108,6 +133,27 @@ pub struct CpuConfig {
108133
/// Number of VCPUs assigned to the guest. [default: 1]
109134
#[serde(default = "default_cpu_count")]
110135
pub count: u16,
136+
/// Architecture specific CPU topology
137+
#[serde(default)]
138+
pub topology: CpuTopology,
139+
}
140+
141+
impl CpuConfig {
142+
pub fn fixup(&mut self) -> Result<()> {
143+
if self.topology.sockets == 0 {
144+
self.topology.sockets = 1;
145+
}
146+
let vcpus_per_core = 1 + self.topology.smt as u16;
147+
if self.topology.cores == 0 {
148+
self.topology.cores = self.count / self.topology.sockets as u16 / vcpus_per_core;
149+
}
150+
let vcpus_per_socket = self.topology.cores * vcpus_per_core;
151+
let count = self.topology.sockets as u16 * vcpus_per_socket;
152+
if count != self.count {
153+
return error::InvalidCpuTopology.fail();
154+
}
155+
Ok(())
156+
}
111157
}
112158

113159
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -137,6 +183,10 @@ impl BoardConfig {
137183
pub fn pcie_mmio_64_start(&self) -> u64 {
138184
(self.mem.size.saturating_sub(RAM_32_SIZE) + MEM_64_START).next_power_of_two()
139185
}
186+
187+
pub fn config_fixup(&mut self) -> Result<()> {
188+
self.cpu.fixup()
189+
}
140190
}
141191

142192
type VcpuGuard<'a> = RwLockReadGuard<'a, Vec<(JoinHandle<Result<()>>, Sender<()>)>>;
@@ -457,3 +507,7 @@ where
457507
Ok(pages)
458508
}
459509
}
510+
511+
#[cfg(test)]
512+
#[path = "board_test.rs"]
513+
mod tests;

alioth/src/board/board_aarch64.rs

Lines changed: 72 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::arch::layout::{
2424
RAM_32_SIZE, RAM_32_START,
2525
};
2626
use crate::arch::reg::MpidrEl1;
27-
use crate::board::{Board, BoardConfig, PCIE_MMIO_64_SIZE, Result, VcpuGuard};
27+
use crate::board::{Board, BoardConfig, CpuTopology, PCIE_MMIO_64_SIZE, Result, VcpuGuard};
2828
use crate::firmware::dt::{DeviceTree, Node, PropVal};
2929
use crate::hv::{GicV2, GicV2m, GicV3, Hypervisor, Its, Vcpu, Vm};
3030
use crate::loader::{Executable, InitState, Payload};
@@ -91,22 +91,59 @@ impl<V: Vm> ArchBoard<V> {
9191
}
9292
}
9393

94-
fn encode_mpidr(index: u16) -> MpidrEl1 {
94+
fn encode_mpidr(topology: &CpuTopology, index: u16) -> MpidrEl1 {
95+
let (thread_id, core_id, socket_id) = topology.encode(index);
9596
let mut mpidr = MpidrEl1(0);
96-
let index = index as u64;
97-
mpidr.set_aff0(index & 0xf);
98-
mpidr.set_aff1(index >> 4);
99-
mpidr.set_aff2(index >> 12);
100-
mpidr.set_aff3(index >> 20);
97+
mpidr.set_aff0(thread_id);
98+
mpidr.set_aff1(core_id as u8);
99+
mpidr.set_aff2(socket_id);
101100
mpidr
102101
}
103102

103+
fn dt_cpu_node(topology: &CpuTopology, socket: u8, core: u16) -> Node {
104+
let mut mpidr = MpidrEl1(0);
105+
mpidr.set_aff1(core as u8);
106+
mpidr.set_aff2(socket);
107+
if topology.smt {
108+
Node {
109+
props: HashMap::new(),
110+
nodes: vec![
111+
(
112+
"thread0".to_owned(),
113+
Node {
114+
props: HashMap::from([(
115+
"cpu",
116+
PropVal::PHandle(PHANDLE_CPU | mpidr.0 as u32),
117+
)]),
118+
nodes: Vec::new(),
119+
},
120+
),
121+
(
122+
"thread1".to_owned(),
123+
Node {
124+
props: HashMap::from([(
125+
"cpu",
126+
PropVal::PHandle(PHANDLE_CPU | mpidr.0 as u32 | 1),
127+
)]),
128+
nodes: Vec::new(),
129+
},
130+
),
131+
],
132+
}
133+
} else {
134+
Node {
135+
nodes: Vec::new(),
136+
props: HashMap::from([("cpu", PropVal::PHandle(PHANDLE_CPU | mpidr.0 as u32))]),
137+
}
138+
}
139+
}
140+
104141
impl<V> Board<V>
105142
where
106143
V: Vm,
107144
{
108145
pub fn encode_cpu_identity(&self, index: u16) -> u64 {
109-
encode_mpidr(index).0
146+
encode_mpidr(&self.config.cpu.topology, index).0
110147
}
111148

112149
pub fn setup_firmware(&self, _: &Path, _: &Payload) -> Result<InitState> {
@@ -233,42 +270,44 @@ where
233270
("compatible", PropVal::Str("arm,arm-v8")),
234271
("enable-method", PropVal::Str("psci")),
235272
("reg", PropVal::U64(mpidr)),
236-
("phandle", PropVal::PHandle(PHANDLE_CPU | index as u32)),
273+
("phandle", PropVal::PHandle(PHANDLE_CPU | mpidr as u32)),
237274
]),
238275
nodes: Vec::new(),
239276
},
240277
)
241278
})
242279
.collect();
243-
let cores = (0..(self.config.cpu.count))
244-
.map(|index| {
245-
(
246-
format!("core{index}"),
247-
Node {
248-
props: HashMap::from([(
249-
"cpu",
250-
PropVal::PHandle(PHANDLE_CPU | index as u32),
251-
)]),
252-
nodes: Vec::new(),
253-
},
254-
)
255-
})
256-
.collect();
280+
257281
let cpu_map = Node {
258282
props: HashMap::new(),
259-
nodes: vec![(
260-
"socket0".to_owned(),
261-
Node {
262-
props: HashMap::new(),
263-
nodes: vec![(
264-
"cluster0".to_owned(),
283+
nodes: (0..self.config.cpu.topology.sockets)
284+
.map(|socket| {
285+
(
286+
format!("socket{socket}",),
265287
Node {
266288
props: HashMap::new(),
267-
nodes: cores,
289+
nodes: vec![(
290+
"cluster0".to_owned(),
291+
Node {
292+
props: HashMap::new(),
293+
nodes: (0..self.config.cpu.topology.cores)
294+
.map(|core| {
295+
(
296+
format!("core{core}"),
297+
dt_cpu_node(
298+
&self.config.cpu.topology,
299+
socket,
300+
core,
301+
),
302+
)
303+
})
304+
.collect(),
305+
},
306+
)],
268307
},
269-
)],
270-
},
271-
)],
308+
)
309+
})
310+
.collect(),
272311
};
273312
cpu_nodes.push(("cpu-map".to_owned(), cpu_map));
274313
let cpus = Node {

alioth/src/board/board_aarch64_test.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
use rstest::rstest;
1616

1717
use crate::arch::reg::MpidrEl1;
18+
use crate::board::CpuTopology;
1819
use crate::board::aarch64::encode_mpidr;
1920

2021
#[rstest]
21-
#[case(1, 1)]
22-
#[case(8, 8)]
23-
#[case(23, (1 << 8) | 7)]
24-
fn test_encode_mpidr(#[case] index: u16, #[case] mpidr: u64) {
25-
assert_eq!(encode_mpidr(index), MpidrEl1(mpidr));
22+
#[case(CpuTopology{smt: false, cores: 1, sockets: 1}, 1, 1)]
23+
#[case(CpuTopology{smt: true, cores: 8, sockets: 1}, 8, 1)]
24+
#[case(CpuTopology{smt: true, cores: 8, sockets: 4}, 45, (1 << 16) | (5 << 8) | 1)]
25+
fn test_encode_mpidr(#[case] topology: CpuTopology, #[case] index: u16, #[case] mpidr: u64) {
26+
assert_eq!(encode_mpidr(&topology, index), MpidrEl1(mpidr));
2627
}

alioth/src/board/board_test.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use assert_matches::assert_matches;
16+
17+
use crate::board::{CpuConfig, CpuTopology, Error};
18+
19+
#[test]
20+
fn test_cpu_topology_fixup() {
21+
let mut empty = CpuConfig {
22+
count: 2,
23+
topology: CpuTopology::default(),
24+
};
25+
empty.fixup().unwrap();
26+
assert_matches!(
27+
empty,
28+
CpuConfig {
29+
count: 2,
30+
topology: CpuTopology {
31+
smt: false,
32+
cores: 2,
33+
sockets: 1
34+
}
35+
}
36+
);
37+
38+
let mut invalid = CpuConfig {
39+
count: 2,
40+
topology: CpuTopology {
41+
smt: true,
42+
cores: 2,
43+
sockets: 1,
44+
},
45+
};
46+
assert_matches!(invalid.fixup(), Err(Error::InvalidCpuTopology { .. }))
47+
}

0 commit comments

Comments
 (0)