Skip to content

Commit 20d1a34

Browse files
alicedreamedslp
authored andcommitted
devices: Introduce RISC-V 64-bit FDT
Intruduce a minimum FDT setup for booting RISC-V 64-bit linux, and turn on vm-fdt dependency for riscv64. Signed-off-by: Bingxin Li <[email protected]>
1 parent 6dca22d commit 20d1a34

File tree

4 files changed

+294
-3
lines changed

4 files changed

+294
-3
lines changed

src/devices/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,5 @@ caps = "0.5.5"
4646
kvm-bindings = { version = ">=0.11", features = ["fam-wrappers"] }
4747
kvm-ioctls = ">=0.21"
4848

49-
[target.'cfg(target_arch = "aarch64")'.dependencies]
49+
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
5050
vm-fdt = ">= 0.2.0"

src/devices/src/fdt/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@
55
pub mod aarch64;
66
#[cfg(target_arch = "aarch64")]
77
pub use aarch64::*;
8+
9+
#[cfg(target_arch = "riscv64")]
10+
pub mod riscv64;
11+
#[cfg(target_arch = "riscv64")]
12+
pub use riscv64::*;

src/devices/src/fdt/riscv64.rs

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
// Copyright 2025 The libkrun Authors. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use std::collections::HashMap;
5+
use std::fmt::Debug;
6+
use std::{io, result};
7+
8+
use crate::legacy::aia::AIADevice;
9+
use crate::legacy::IrqChip;
10+
use crate::DeviceType;
11+
use arch::riscv64::get_fdt_addr;
12+
use arch::riscv64::layout::IRQ_BASE;
13+
use arch::{ArchMemoryInfo, InitrdConfig};
14+
use vm_fdt::{Error as FdtError, FdtWriter};
15+
use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap};
16+
17+
const AIA_APLIC_PHANDLE: u32 = 1;
18+
const AIA_IMSIC_PHANDLE: u32 = 2;
19+
const CPU_INTC_BASE_PHANDLE: u32 = 3;
20+
const CPU_BASE_PHANDLE: u32 = 256 + CPU_INTC_BASE_PHANDLE;
21+
// Read the documentation specified when appending the root node to the FDT.
22+
const ADDRESS_CELLS: u32 = 0x2;
23+
const SIZE_CELLS: u32 = 0x2;
24+
25+
// From https://elixir.bootlin.com/linux/v6.10/source/include/dt-bindings/interrupt-controller/irq.h#L14
26+
const _IRQ_TYPE_EDGE_RISING: u32 = 1;
27+
const IRQ_TYPE_LEVEL_HI: u32 = 4;
28+
29+
const S_MODE_EXT_IRQ: u32 = 9;
30+
31+
/// Trait for devices to be added to the Flattened Device Tree.
32+
pub trait DeviceInfoForFDT {
33+
/// Returns the address where this device will be loaded.
34+
fn addr(&self) -> u64;
35+
/// Returns the associated interrupt for this device.
36+
fn irq(&self) -> u32;
37+
/// Returns the amount of memory that needs to be reserved for this device.
38+
fn length(&self) -> u64;
39+
}
40+
41+
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
42+
#[derive(Debug)]
43+
pub enum Error {
44+
/// Creating FDT failed.
45+
CreateFDT(FdtError),
46+
/// Failure in calling syscall for terminating this FDT.
47+
FinishFDTReserveMap(io::Error),
48+
/// Failure in writing FDT in memory.
49+
WriteFDTToMemory(GuestMemoryError),
50+
}
51+
type Result<T> = result::Result<T, Error>;
52+
53+
impl From<FdtError> for Error {
54+
fn from(item: FdtError) -> Self {
55+
Error::CreateFDT(item)
56+
}
57+
}
58+
59+
/// Creates the flattened device tree for this riscv64 VM.
60+
pub fn create_fdt<T: DeviceInfoForFDT + Clone + Debug>(
61+
guest_mem: &GuestMemoryMmap,
62+
arch_memory_info: &ArchMemoryInfo,
63+
num_vcpu: u32,
64+
cmdline: &str,
65+
device_info: &HashMap<(DeviceType, String), T>,
66+
aia_device: &IrqChip,
67+
initrd: &Option<InitrdConfig>,
68+
) -> Result<Vec<u8>> {
69+
// Allocate stuff necessary for the holding the blob.
70+
let mut fdt = FdtWriter::new()?;
71+
72+
// For an explanation why these nodes were introduced in the blob take a look at
73+
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L845
74+
// Look for "Required nodes and properties".
75+
76+
// Header or the root node as per above mentioned documentation.
77+
let root_node = fdt.begin_node("root")?;
78+
fdt.property_string("compatible", "linux,dummy-virt")?;
79+
// For info on #address-cells and size-cells resort to Table 3.1 Root Node
80+
// Properties
81+
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
82+
fdt.property_u32("#size-cells", SIZE_CELLS)?;
83+
create_cpu_nodes(&mut fdt, num_vcpu)?;
84+
create_memory_node(&mut fdt, guest_mem, arch_memory_info)?;
85+
create_chosen_node(&mut fdt, cmdline, initrd)?;
86+
create_aia_node(&mut fdt, aia_device)?;
87+
create_devices_node(&mut fdt, device_info)?;
88+
89+
// End Header node.
90+
fdt.end_node(root_node)?;
91+
92+
// Allocate another buffer so we can format and then write fdt to guest.
93+
let fdt_final = fdt.finish()?;
94+
95+
// Write FDT to memory.
96+
let fdt_address = GuestAddress(get_fdt_addr(guest_mem));
97+
guest_mem
98+
.write_slice(fdt_final.as_slice(), fdt_address)
99+
.map_err(Error::WriteFDTToMemory)?;
100+
Ok(fdt_final)
101+
}
102+
103+
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
104+
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> Result<()> {
105+
// See https://elixir.bootlin.com/linux/v6.10/source/Documentation/devicetree/bindings/riscv/cpus.yaml
106+
let cpus = fdt.begin_node("cpus")?;
107+
// As per documentation, on RISC-V 64-bit systems value should be set to 1.
108+
fdt.property_u32("#address-cells", 0x01)?;
109+
fdt.property_u32("#size-cells", 0x0)?;
110+
fdt.property_u32("timebase-frequency", 0x989680)?;
111+
112+
for cpu_index in 0..num_cpus {
113+
let cpu = fdt.begin_node(&format!("cpu@{cpu_index:x}"))?;
114+
fdt.property_string("device_type", "cpu")?;
115+
fdt.property_string("compatible", "riscv")?;
116+
fdt.property_string("mmu-type", "sv48")?;
117+
fdt.property_string("riscv,isa", "rv64imafdc_smaia_ssaia")?;
118+
fdt.property_string("status", "okay")?;
119+
fdt.property_u32("reg", cpu_index)?;
120+
fdt.property_u32("phandle", CPU_BASE_PHANDLE + cpu_index)?;
121+
122+
// interrupt controller node
123+
let intc_node = fdt.begin_node("interrupt-controller")?;
124+
fdt.property_string("compatible", "riscv,cpu-intc")?;
125+
fdt.property_u32("#interrupt-cells", 1u32)?;
126+
fdt.property_null("interrupt-controller")?;
127+
fdt.property_u32("phandle", CPU_INTC_BASE_PHANDLE + cpu_index)?;
128+
fdt.end_node(intc_node)?;
129+
130+
fdt.end_node(cpu)?;
131+
}
132+
fdt.end_node(cpus)?;
133+
Ok(())
134+
}
135+
136+
fn create_memory_node(
137+
fdt: &mut FdtWriter,
138+
_guest_mem: &GuestMemoryMmap,
139+
arch_memory_info: &ArchMemoryInfo,
140+
) -> Result<()> {
141+
let mem_size = arch_memory_info.ram_last_addr - arch::riscv64::layout::DRAM_MEM_START;
142+
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L960
143+
// for an explanation of this.
144+
let mem_reg_prop = [arch::riscv64::layout::DRAM_MEM_START, mem_size];
145+
146+
let mem_node = fdt.begin_node("memory")?;
147+
fdt.property_string("device_type", "memory")?;
148+
fdt.property_array_u64("reg", &mem_reg_prop)?;
149+
fdt.end_node(mem_node)?;
150+
Ok(())
151+
}
152+
153+
fn create_chosen_node(
154+
fdt: &mut FdtWriter,
155+
cmdline: &str,
156+
initrd: &Option<InitrdConfig>,
157+
) -> Result<()> {
158+
let chosen_node = fdt.begin_node("chosen")?;
159+
fdt.property_string("bootargs", cmdline)?;
160+
161+
if let Some(initrd_config) = initrd {
162+
fdt.property_u64("linux,initrd-start", initrd_config.address.raw_value())?;
163+
fdt.property_u64(
164+
"linux,initrd-end",
165+
initrd_config.address.raw_value() + initrd_config.size as u64,
166+
)?;
167+
}
168+
169+
fdt.end_node(chosen_node)?;
170+
171+
Ok(())
172+
}
173+
174+
fn create_aia_node(fdt: &mut FdtWriter, aia_device: &IrqChip) -> Result<()> {
175+
// IMSIC
176+
if aia_device.lock().unwrap().msi_compatible() {
177+
use arch::riscv64::layout::IMSIC_START;
178+
let imsic_name = format!("imsics@{IMSIC_START:x}");
179+
let imsic_node = fdt.begin_node(&imsic_name)?;
180+
181+
fdt.property_string(
182+
"compatible",
183+
aia_device.lock().unwrap().imsic_compatibility(),
184+
)?;
185+
let imsic_reg_prop = aia_device.lock().unwrap().imsic_properties();
186+
fdt.property_array_u32("reg", &imsic_reg_prop)?;
187+
fdt.property_u32("#interrupt-cells", 0u32)?;
188+
fdt.property_null("interrupt-controller")?;
189+
fdt.property_null("msi-controller")?;
190+
// TODO complete num-ids
191+
fdt.property_u32("riscv,num-ids", 2047u32)?;
192+
fdt.property_u32("phandle", AIA_IMSIC_PHANDLE)?;
193+
194+
let mut irq_cells = Vec::new();
195+
let num_cpus = aia_device.lock().unwrap().vcpu_count();
196+
for i in 0..num_cpus {
197+
irq_cells.push(CPU_INTC_BASE_PHANDLE + i);
198+
irq_cells.push(S_MODE_EXT_IRQ);
199+
}
200+
fdt.property_array_u32("interrupts-extended", &irq_cells)?;
201+
202+
fdt.end_node(imsic_node)?;
203+
}
204+
205+
// APLIC
206+
use arch::riscv64::layout::APLIC_START;
207+
let aplic_name = format!("aplic@{APLIC_START:x}");
208+
let aplic_node = fdt.begin_node(&aplic_name)?;
209+
210+
fdt.property_string(
211+
"compatible",
212+
aia_device.lock().unwrap().aplic_compatibility(),
213+
)?;
214+
let reg_cells = aia_device.lock().unwrap().aplic_properties();
215+
fdt.property_array_u32("reg", &reg_cells)?;
216+
fdt.property_u32("#interrupt-cells", 2u32)?;
217+
fdt.property_null("interrupt-controller")?;
218+
fdt.property_u32("riscv,num-sources", 96u32)?;
219+
fdt.property_u32("phandle", AIA_APLIC_PHANDLE)?;
220+
fdt.property_u32("msi-parent", AIA_IMSIC_PHANDLE)?;
221+
222+
fdt.end_node(aplic_node)?;
223+
224+
Ok(())
225+
}
226+
227+
fn create_virtio_node<T: DeviceInfoForFDT + Clone + Debug>(
228+
fdt: &mut FdtWriter,
229+
dev_info: &T,
230+
) -> Result<()> {
231+
let device_reg_prop = [dev_info.addr(), dev_info.length()];
232+
#[cfg(target_os = "linux")]
233+
let irq = [dev_info.irq() - IRQ_BASE, IRQ_TYPE_LEVEL_HI];
234+
235+
let virtio_node = fdt.begin_node(&format!("virtio_mmio@{:x}", dev_info.addr()))?;
236+
fdt.property_string("compatible", "virtio,mmio")?;
237+
fdt.property_array_u64("reg", &device_reg_prop)?;
238+
fdt.property_array_u32("interrupts", &irq)?;
239+
fdt.property_u32("interrupt-parent", AIA_APLIC_PHANDLE)?;
240+
fdt.end_node(virtio_node)?;
241+
242+
Ok(())
243+
}
244+
245+
fn create_serial_node<T: DeviceInfoForFDT + Clone + Debug>(
246+
fdt: &mut FdtWriter,
247+
dev_info: &T,
248+
) -> Result<()> {
249+
let serial_reg_prop = [dev_info.addr(), dev_info.length()];
250+
let irq = [dev_info.irq() - IRQ_BASE, IRQ_TYPE_LEVEL_HI];
251+
252+
let serial_node = fdt.begin_node(&format!("serial@{:x}", dev_info.addr()))?;
253+
fdt.property_string("compatible", "ns16550a")?;
254+
fdt.property_array_u64("reg", &serial_reg_prop)?;
255+
fdt.property_u32("clock-frequency", 3686400)?;
256+
fdt.property_u32("interrupt-parent", AIA_APLIC_PHANDLE)?;
257+
fdt.property_array_u32("interrupts", &irq)?;
258+
fdt.end_node(serial_node)?;
259+
260+
Ok(())
261+
}
262+
263+
fn create_devices_node<T: DeviceInfoForFDT + Clone + Debug>(
264+
fdt: &mut FdtWriter,
265+
dev_info: &HashMap<(DeviceType, String), T>,
266+
) -> Result<()> {
267+
// Create one temp Vec to store all virtio devices
268+
let mut ordered_virtio_device: Vec<&T> = Vec::new();
269+
270+
for ((device_type, _device_id), info) in dev_info {
271+
match device_type {
272+
DeviceType::Serial => create_serial_node(fdt, info)?,
273+
DeviceType::Virtio(_) => {
274+
ordered_virtio_device.push(info);
275+
}
276+
}
277+
}
278+
279+
// Sort out virtio devices by address from low to high and insert them into fdt table.
280+
ordered_virtio_device.sort_by_key(|a| a.addr());
281+
for ordered_device_info in ordered_virtio_device.drain(..) {
282+
create_virtio_node(fdt, ordered_device_info)?;
283+
}
284+
285+
Ok(())
286+
}

src/devices/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::fmt;
1414
use std::io;
1515

1616
mod bus;
17-
#[cfg(target_arch = "aarch64")]
17+
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
1818
pub mod fdt;
1919
pub mod legacy;
2020
pub mod virtio;
@@ -44,7 +44,7 @@ pub enum DeviceType {
4444
#[cfg(target_arch = "aarch64")]
4545
Gpio,
4646
/// Device Type: Serial.
47-
#[cfg(target_arch = "aarch64")]
47+
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
4848
Serial,
4949
/// Device Type: RTC.
5050
#[cfg(target_arch = "aarch64")]

0 commit comments

Comments
 (0)