Alioth Code Coverage

board_aarch64.rs1.89%

1// Copyright 2024 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
15use std::collections::HashMap;
16use std::path::Path;
17use std::sync::Arc;
18
19use crate::arch::layout::{
20 DEVICE_TREE_LIMIT, DEVICE_TREE_START, GIC_DIST_START, GIC_MSI_START,
21 GIC_V2_CPU_INTERFACE_START, GIC_V3_REDIST_START, IO_END, IO_START, MEM_64_START,
22 PCIE_CONFIG_START, PCIE_MMIO_32_NON_PREFETCHABLE_END, PCIE_MMIO_32_NON_PREFETCHABLE_START,
23 PCIE_MMIO_32_PREFETCHABLE_END, PCIE_MMIO_32_PREFETCHABLE_START, PL011_START, PL031_START,
24 RAM_32_SIZE, RAM_32_START,
25};
26use crate::arch::reg::MpidrEl1;
27use crate::board::{Board, BoardConfig, CpuTopology, PCIE_MMIO_64_SIZE, Result, VcpuGuard};
28use crate::firmware::dt::{DeviceTree, Node, PropVal};
29use crate::hv::{GicV2, GicV2m, GicV3, Hypervisor, Its, Vcpu, Vm};
30use crate::loader::{Executable, InitState, Payload};
31use crate::mem::{MemRegion, MemRegionType};
32
33enum Gic<V>
34where
35 V: Vm,
36{
37 V2(V::GicV2),
38 V3(V::GicV3),
39}
40
41enum Msi<V>
42where
43 V: Vm,
44{
45 V2m(V::GicV2m),
46 Its(V::Its),
47}
48
49pub struct ArchBoard<V>
50where
51 V: Vm,
52{
53 gic: Gic<V>,
54 msi: Option<Msi<V>>,
55}
56
57impl<V: Vm> ArchBoard<V> {
58 pub fn new<H>(_hv: &H, vm: &V, config: &BoardConfig) -> Result<Self>
59 where
60 H: Hypervisor<Vm = V>,
61 {
62 let gic = match vm.create_gic_v3(GIC_DIST_START, GIC_V3_REDIST_START, config.cpu.count) {
63 Ok(v3) => Gic::V3(v3),
64 Err(e) => {
65 log::error!("Cannot create GIC v3: {e:?}trying v2...");
66 Gic::V2(vm.create_gic_v2(GIC_DIST_START, GIC_V2_CPU_INTERFACE_START)?)
67 }
68 };
69
70 let create_gic_v2m = || match vm.create_gic_v2m(GIC_MSI_START) {
71 Ok(v2m) => Some(Msi::V2m(v2m)),
72 Err(e) => {
73 log::error!("Cannot create GIC v2m: {e:?}");
74 None
75 }
76 };
77
78 let msi = if matches!(gic, Gic::V3(_)) {
79 match vm.create_its(GIC_MSI_START) {
80 Ok(its) => Some(Msi::Its(its)),
81 Err(e) => {
82 log::error!("Cannot create ITS: {e:?}trying v2m...");
83 create_gic_v2m()
84 }
85 }
86 } else {
87 create_gic_v2m()
88 };
89
90 Ok(ArchBoard { gic, msi })
91 }
92}
93
94fn encode_mpidr(topology: &CpuTopology, index: u16) -> MpidrEl1 {6x
95 let (socket_id, core_id, thread_id) = topology.encode(index);6x
96 let mut mpidr = MpidrEl1(0);6x
97 mpidr.set_aff0(thread_id);6x
98 mpidr.set_aff1(core_id as u8);6x
99 mpidr.set_aff2(socket_id);6x
100 mpidr6x
101}6x
102
103impl<V> Board<V>
104where
105 V: Vm,
106{
107 pub fn encode_cpu_identity(&self, index: u16) -> u64 {
108 encode_mpidr(&self.config.cpu.topology, index).0
109 }
110
111 pub fn setup_firmware(&self, _: &Path, _: &Payload, _: &V::Vcpu) -> Result<InitState> {
112 unimplemented!()
113 }
114
115 pub fn init_ap(&self, _id: u16, _vcpu: &mut V::Vcpu, _vcpus: &VcpuGuard) -> Result<()> {
116 Ok(())
117 }
118
119 pub fn init_boot_vcpu(&self, vcpu: &mut V::Vcpu, init_state: &InitState) -> Result<()> {
120 vcpu.set_regs(&init_state.regs)?;
121 vcpu.set_sregs(&init_state.sregs)?;
122 Ok(())
123 }
124
125 pub fn init_vcpu(&self, index: u16, vcpu: &mut V::Vcpu) -> Result<()> {
126 self.reset_vcpu(index, vcpu)
127 }
128
129 pub fn reset_vcpu(&self, index: u16, vcpu: &mut V::Vcpu) -> Result<()> {
130 vcpu.reset(index == 0)?;
131 Ok(())
132 }
133
134 pub fn create_ram(&self) -> Result<()> {
135 let mem_size = self.config.mem.size;
136 let memory = &self.memory;
137
138 let low_mem_size = std::cmp::min(mem_size, RAM_32_SIZE);
139 let pages_low = self.create_ram_pages(low_mem_size, c"ram-low")?;
140 memory.add_region(
141 RAM_32_START,
142 Arc::new(MemRegion::with_ram(pages_low, MemRegionType::Ram)),
143 )?;
144
145 let high_mem_size = mem_size.saturating_sub(RAM_32_SIZE);
146 if high_mem_size > 0 {
147 let pages_high = self.create_ram_pages(high_mem_size, c"ram-high")?;
148 memory.add_region(
149 MEM_64_START,
150 Arc::new(MemRegion::with_ram(pages_high, MemRegionType::Ram)),
151 )?;
152 }
153
154 Ok(())
155 }
156
157 pub fn coco_init(&self, _: Arc<V::Memory>) -> Result<()> {
158 Ok(())
159 }
160
161 pub fn coco_finalize(&self, _id: u16, _vcpus: &VcpuGuard) -> Result<()> {
162 Ok(())
163 }
164
165 pub fn arch_init(&self) -> Result<()> {
166 match &self.arch.gic {
167 Gic::V2(v2) => v2.init(),
168 Gic::V3(v3) => v3.init(),
169 }?;
170 match &self.arch.msi {
171 Some(Msi::V2m(v2m)) => v2m.init(),
172 Some(Msi::Its(its)) => its.init(),
173 None => Ok(()),
174 }?;
175 Ok(())
176 }
177
178 fn create_chosen_node(&self, init_state: &InitState, root: &mut Node) {
179 let payload = self.payload.read();
180 let Some(payload) = payload.as_ref() else {
181 return;
182 };
183 if !matches!(payload.executable, Some(Executable::Linux(_))) {
184 return;
185 }
186 let mut node = Node::default();
187 if let Some(cmdline) = &payload.cmdline {
188 let bytes = cmdline.as_bytes_with_nul().to_owned();
189 node.props.insert("bootargs", PropVal::Bytes(bytes));
190 }
191 if let Some(initramfs_range) = &init_state.initramfs {
192 node.props.insert(
193 "linux,initrd-start",
194 PropVal::U32(initramfs_range.start as u32),
195 );
196 node.props
197 .insert("linux,initrd-end", PropVal::U32(initramfs_range.end as u32));
198 }
199 node.props.insert(
200 "stdout-path",
201 PropVal::String(format!("/pl011@{PL011_START:x}")),
202 );
203 root.nodes.push(("chosen".to_owned(), node));
204 }
205
206 pub fn create_memory_node(&self, root: &mut Node) {
207 let regions = self.memory.mem_region_entries();
208 for (start, region) in regions {
209 if region.type_ != MemRegionType::Ram {
210 continue;
211 };
212 let node = Node {
213 props: HashMap::from([
214 ("device_type", PropVal::Str("memory")),
215 ("reg", PropVal::U64List(vec![start, region.size])),
216 ]),
217 nodes: Vec::new(),
218 };
219 root.nodes.push((format!("memory@{start:x}"), node));
220 }
221 }
222
223 pub fn create_cpu_nodes(&self, root: &mut Node) {
224 let topology = &self.config.cpu.topology;
225
226 let thread_node = |socket_id: u8, core_id: u16, thread_id: u8| {
227 let phandle = PHANDLE_CPU | topology.decode(socket_id, core_id, thread_id) as u32;
228 Node {
229 props: HashMap::from([("cpu", PropVal::PHandle(phandle))]),
230 nodes: Vec::new(),
231 }
232 };
233 let core_node = |socket_id: u8, core_id: u16| {
234 if topology.smt {
235 Node {
236 props: HashMap::new(),
237 nodes: vec![
238 ("thread0".to_owned(), thread_node(socket_id, core_id, 0)),
239 ("thread1".to_owned(), thread_node(socket_id, core_id, 1)),
240 ],
241 }
242 } else {
243 thread_node(socket_id, core_id, 0)
244 }
245 };
246 let socket_node = |socket_id: u8| Node {
247 props: HashMap::new(),
248 nodes: vec![(
249 "cluster0".to_owned(),
250 Node {
251 props: HashMap::new(),
252 nodes: (0..topology.cores)
253 .map(|core_id| (format!("core{core_id}"), core_node(socket_id, core_id)))
254 .collect(),
255 },
256 )],
257 };
258 let cpu_map_node = Node {
259 props: HashMap::new(),
260 nodes: (0..topology.sockets)
261 .map(|socket_id| (format!("socket{socket_id}"), socket_node(socket_id)))
262 .collect(),
263 };
264
265 let cpus_nodes = (0..(self.config.cpu.count))
266 .map(|index| {
267 let mpidr = self.encode_cpu_identity(index);
268 (
269 format!("cpu@{mpidr:x}"),
270 Node {
271 props: HashMap::from([
272 ("device_type", PropVal::Str("cpu")),
273 ("compatible", PropVal::Str("arm,arm-v8")),
274 ("enable-method", PropVal::Str("psci")),
275 ("reg", PropVal::U64(mpidr)),
276 ("phandle", PropVal::PHandle(PHANDLE_CPU | index as u32)),
277 ]),
278 nodes: Vec::new(),
279 },
280 )
281 })
282 .chain([("cpu-map".to_owned(), cpu_map_node)])
283 .collect();
284
285 let cpus = Node {
286 props: HashMap::from([
287 ("#address-cells", PropVal::U32(2)),
288 ("#size-cells", PropVal::U32(0)),
289 ]),
290 nodes: cpus_nodes,
291 };
292 root.nodes.push(("cpus".to_owned(), cpus));
293 }
294
295 fn create_clock_node(&self, root: &mut Node) {
296 let node = Node {
297 props: HashMap::from([
298 ("compatible", PropVal::Str("fixed-clock")),
299 ("clock-frequency", PropVal::U32(24000000)),
300 ("clock-output-names", PropVal::Str("clk24mhz")),
301 ("phandle", PropVal::PHandle(PHANDLE_CLOCK)),
302 ("#clock-cells", PropVal::U32(0)),
303 ]),
304 nodes: Vec::new(),
305 };
306 root.nodes.push(("apb-pclk".to_owned(), node));
307 }
308
309 fn create_pl011_node(&self, root: &mut Node) {
310 let pin = 1;
311 let edge_trigger = 1;
312 let spi = 0;
313 let node = Node {
314 props: HashMap::from([
315 ("compatible", PropVal::Str("arm,primecell\0arm,pl011")),
316 ("reg", PropVal::U64List(vec![PL011_START, 0x1000])),
317 ("interrupts", PropVal::U32List(vec![spi, pin, edge_trigger])),
318 ("clock-names", PropVal::Str("uartclk\0apb_pclk")),
319 (
320 "clocks",
321 PropVal::U32List(vec![PHANDLE_CLOCK, PHANDLE_CLOCK]),
322 ),
323 ]),
324 nodes: Vec::new(),
325 };
326 root.nodes.push((format!("pl011@{PL011_START:x}"), node));
327 }
328
329 fn create_pl031_node(&self, root: &mut Node) {
330 let node = Node {
331 props: HashMap::from([
332 ("compatible", PropVal::Str("arm,primecell\0arm,pl031")),
333 ("reg", PropVal::U64List(vec![PL031_START, 0x1000])),
334 ("clock-names", PropVal::Str("apb_pclk")),
335 ("clocks", PropVal::U32List(vec![PHANDLE_CLOCK])),
336 ]),
337 nodes: Vec::new(),
338 };
339 root.nodes.push((format!("pl031@{PL031_START:x}"), node));
340 }
341
342 // Documentation/devicetree/bindings/timer/arm,arch_timer.yaml
343 fn create_timer_node(&self, root: &mut Node) {
344 let mut interrupts = vec![];
345 let irq_pins = [13, 14, 11, 10];
346 let ppi = 1;
347 let level_trigger = 4;
348 let cpu_mask = match self.arch.gic {
349 Gic::V2(_) => (1 << self.config.cpu.count) - 1,
350 Gic::V3 { .. } => 0,
351 };
352 for pin in irq_pins {
353 interrupts.extend([ppi, pin, (cpu_mask << 8) | level_trigger]);
354 }
355 let node = Node {
356 props: HashMap::from([
357 ("compatible", PropVal::Str("arm,armv8-timer")),
358 ("interrupts", PropVal::U32List(interrupts)),
359 ("always-on", PropVal::Empty),
360 ]),
361 nodes: Vec::new(),
362 };
363 root.nodes.push(("timer".to_owned(), node));
364 }
365
366 fn create_gic_msi_node(&self) -> Vec<(String, Node)> {
367 let Some(msi) = &self.arch.msi else {
368 return Vec::new();
369 };
370 match msi {
371 Msi::Its(_) => {
372 let node = Node {
373 props: HashMap::from([
374 ("compatible", PropVal::Str("arm,gic-v3-its")),
375 ("msi-controller", PropVal::Empty),
376 ("#msi-cells", PropVal::U32(1)),
377 ("reg", PropVal::U64List(vec![GIC_MSI_START, 128 << 10])),
378 ("phandle", PropVal::PHandle(PHANDLE_MSI)),
379 ]),
380 nodes: Vec::new(),
381 };
382 vec![(format!("its@{GIC_MSI_START:x}"), node)]
383 }
384 Msi::V2m(_) => {
385 let node = Node {
386 props: HashMap::from([
387 ("compatible", PropVal::Str("arm,gic-v2m-frame")),
388 ("msi-controller", PropVal::Empty),
389 ("reg", PropVal::U64List(vec![GIC_MSI_START, 64 << 10])),
390 ("phandle", PropVal::PHandle(PHANDLE_MSI)),
391 ]),
392 nodes: Vec::new(),
393 };
394 vec![(format!("v2m@{GIC_MSI_START:x}"), node)]
395 }
396 }
397 }
398
399 fn create_gic_node(&self, root: &mut Node) {
400 let msi = self.create_gic_msi_node();
401 let node = match self.arch.gic {
402 // Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml
403 Gic::V2(_) => Node {
404 props: HashMap::from([
405 ("compatible", PropVal::Str("arm,cortex-a15-gic")),
406 ("#interrupt-cells", PropVal::U32(3)),
407 (
408 "reg",
409 PropVal::U64List(vec![
410 GIC_DIST_START,
411 0x1000,
412 GIC_V2_CPU_INTERFACE_START,
413 0x2000,
414 ]),
415 ),
416 ("phandle", PropVal::U32(PHANDLE_GIC)),
417 ("interrupt-controller", PropVal::Empty),
418 ]),
419 nodes: msi,
420 },
421 // Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml
422 Gic::V3(_) => Node {
423 props: HashMap::from([
424 ("compatible", PropVal::Str("arm,gic-v3")),
425 ("#interrupt-cells", PropVal::U32(3)),
426 ("#address-cells", PropVal::U32(2)),
427 ("#size-cells", PropVal::U32(2)),
428 ("interrupt-controller", PropVal::Empty),
429 ("ranges", PropVal::Empty),
430 (
431 "reg",
432 PropVal::U64List(vec![
433 GIC_DIST_START,
434 64 << 10,
435 GIC_V3_REDIST_START,
436 self.config.cpu.count as u64 * (128 << 10),
437 ]),
438 ),
439 ("phandle", PropVal::U32(PHANDLE_GIC)),
440 ]),
441 nodes: msi,
442 },
443 };
444 root.nodes.push((format!("intc@{GIC_DIST_START:x}"), node));
445 }
446
447 // Documentation/devicetree/bindings/arm/psci.yaml
448 fn create_psci_node(&self, root: &mut Node) {
449 let node = Node {
450 props: HashMap::from([
451 ("method", PropVal::Str("hvc")),
452 ("compatible", PropVal::Str("arm,psci-0.2\0arm,psci")),
453 ]),
454 nodes: Vec::new(),
455 };
456 root.nodes.push(("psci".to_owned(), node));
457 }
458
459 // https://elinux.org/Device_Tree_Usage#PCI_Host_Bridge
460 // Documentation/devicetree/bindings/pci/host-generic-pci.yaml
461 // IEEE Std 1275-1994
462 fn create_pci_bridge_node(&self, root: &mut Node) {
463 let Some(max_bus) = self.pci_bus.segment.max_bus() else {
464 return;
465 };
466 let pcie_mmio_64_start = self.config.pcie_mmio_64_start();
467 let prefetchable = 1 << 30;
468 let io = 0b01 << 24;
469 let mem_32 = 0b10 << 24;
470 let mem_64 = 0b11 << 24;
471 let node = Node {
472 props: HashMap::from([
473 ("compatible", PropVal::Str("pci-host-ecam-generic")),
474 ("device_type", PropVal::Str("pci")),
475 ("reg", PropVal::U64List(vec![PCIE_CONFIG_START, 256 << 20])),
476 ("bus-range", PropVal::U32List(vec![0, max_bus as u32])),
477 ("#address-cells", PropVal::U32(3)),
478 ("#size-cells", PropVal::U32(2)),
479 (
480 "ranges",
481 PropVal::U32List(vec![
482 io,
483 0,
484 0,
485 0,
486 IO_START as u32,
487 0,
488 (IO_END - IO_START) as u32,
489 mem_32 | prefetchable,
490 0,
491 PCIE_MMIO_32_PREFETCHABLE_START as u32,
492 0,
493 PCIE_MMIO_32_PREFETCHABLE_START as u32,
494 0,
495 (PCIE_MMIO_32_PREFETCHABLE_END - PCIE_MMIO_32_PREFETCHABLE_START) as u32,
496 mem_32,
497 0,
498 PCIE_MMIO_32_NON_PREFETCHABLE_START as u32,
499 0,
500 PCIE_MMIO_32_NON_PREFETCHABLE_START as u32,
501 0,
502 (PCIE_MMIO_32_NON_PREFETCHABLE_END - PCIE_MMIO_32_NON_PREFETCHABLE_START)
503 as u32,
504 mem_64 | prefetchable,
505 (pcie_mmio_64_start >> 32) as u32,
506 pcie_mmio_64_start as u32,
507 (pcie_mmio_64_start >> 32) as u32,
508 pcie_mmio_64_start as u32,
509 (PCIE_MMIO_64_SIZE >> 32) as u32,
510 PCIE_MMIO_64_SIZE as u32,
511 ]),
512 ),
513 (
514 "msi-map",
515 // Identity map from RID (BDF) to msi-specifier.
516 // Documentation/devicetree/bindings/pci/pci-msi.txt
517 PropVal::U32List(vec![0, PHANDLE_MSI, 0, 0x10000]),
518 ),
519 ]),
520 nodes: Vec::new(),
521 };
522 root.nodes
523 .push((format!("pci@{PCIE_CONFIG_START:x}"), node));
524 }
525
526 pub fn create_firmware_data(&self, init_state: &InitState) -> Result<()> {
527 let mut device_tree = DeviceTree::new();
528 let root = &mut device_tree.root;
529 root.props.insert("#address-cells", PropVal::U32(2));
530 root.props.insert("#size-cells", PropVal::U32(2));
531 root.props.insert("model", PropVal::Str("linux,dummy-virt"));
532 root.props
533 .insert("compatible", PropVal::Str("linux,dummy-virt"));
534 root.props
535 .insert("interrupt-parent", PropVal::PHandle(PHANDLE_GIC));
536
537 self.create_chosen_node(init_state, root);
538 self.create_pl011_node(root);
539 self.create_pl031_node(root);
540 self.create_memory_node(root);
541 self.create_cpu_nodes(root);
542 self.create_gic_node(root);
543 if self.arch.msi.is_some() {
544 self.create_pci_bridge_node(root);
545 }
546 self.create_clock_node(root);
547 self.create_timer_node(root);
548 self.create_psci_node(root);
549 log::debug!("device tree: {device_tree:#x?}");
550 let blob = device_tree.to_blob();
551 let ram = self.memory.ram_bus();
552 assert!(blob.len() as u64 <= DEVICE_TREE_LIMIT);
553 ram.write_range(DEVICE_TREE_START, blob.len() as u64, &*blob)?;
554 Ok(())
555 }
556}
557
558const PHANDLE_GIC: u32 = 1;
559const PHANDLE_CLOCK: u32 = 2;
560const PHANDLE_MSI: u32 = 3;
561const PHANDLE_CPU: u32 = 1 << 31;
562
563#[cfg(test)]
564#[path = "board_aarch64_test.rs"]
565mod tests;
566