use std::{format, vec::Vec};
use fdt_edit::{Fdt, Node, NodeId};
use super::property::{prop_null, prop_string, prop_string_list, prop_u32, prop_u32_array};
use crate::{
AxVmResult,
arch::loongarch64::boot::GuestPlatform,
ax_err_type,
boot::fdt::device::{ResolvedFdtDevice, ResolvedFdtProperty},
};
const PHANDLE_CPU0: u32 = 0x8000;
const PHANDLE_CPUIC: u32 = 0x8001;
const PHANDLE_EIOINTC: u32 = 0x8002;
const PHANDLE_PCH_PIC: u32 = 0x8003;
const PHANDLE_PCH_MSI: u32 = 0x8004;
const PHANDLE_GED_SYSCON: u32 = 0x8005;
pub fn build(platform: &GuestPlatform) -> AxVmResult<Vec<u8>> {
let mut fdt = Fdt::new();
let root = fdt.root_id();
set_prop(&mut fdt, root, prop_u32("#address-cells", 2))?;
set_prop(&mut fdt, root, prop_u32("#size-cells", 2))?;
set_prop(
&mut fdt,
root,
prop_string("compatible", "linux,dummy-loongson3"),
)?;
add_chosen(&mut fdt, root, platform)?;
add_cpus(&mut fdt, root)?;
add_memory(&mut fdt, root, platform)?;
add_interrupt_controllers(&mut fdt, root, platform)?;
add_configured_devices(&mut fdt, root, platform)?;
add_platform_bus(&mut fdt, root, platform)?;
add_power(&mut fdt, root, platform)?;
add_rtc(&mut fdt, root, platform)?;
add_serial(&mut fdt, root, platform)?;
add_flash(&mut fdt, root, platform)?;
add_fw_cfg(&mut fdt, root, platform)?;
Ok(fdt.encode().as_ref().to_vec())
}
fn set_prop(fdt: &mut Fdt, node: NodeId, prop: fdt_edit::Property) -> AxVmResult {
fdt.node_mut(node)
.ok_or_else(|| ax_err_type!(InvalidData, "FDT node id is invalid"))?
.set_property(prop);
Ok(())
}
fn add_child(fdt: &mut Fdt, parent: NodeId, name: &str) -> NodeId {
fdt.add_node(parent, Node::new(name))
}
fn add_platform_bus(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult {
let (bus_base, bus_size) = platform_bus_range(platform);
let platform_bus = add_child(fdt, root, &format!("platform-bus@{bus_base:x}"));
set_prop(
fdt,
platform_bus,
prop_string_list("compatible", &["qemu,platform", "simple-bus"]),
)?;
set_prop(fdt, platform_bus, prop_u32("#address-cells", 1))?;
set_prop(fdt, platform_bus, prop_u32("#size-cells", 1))?;
set_prop(
fdt,
platform_bus,
prop_u32_array("ranges", &[0, 0, bus_base as u32, bus_size as u32]),
)?;
set_prop(
fdt,
platform_bus,
prop_u32("interrupt-parent", PHANDLE_PCH_PIC),
)
}
fn platform_bus_range(platform: &GuestPlatform) -> (u64, u64) {
const PAGE_SIZE: u64 = 0x1000;
let base = platform
.firmware_devices
.ged
.mmio
.base
.min(platform.firmware_devices.rtc.mmio.base)
& !(PAGE_SIZE - 1);
let end = platform
.firmware_devices
.ged
.mmio
.base
.saturating_add(platform.firmware_devices.ged.mmio.size)
.max(
platform
.firmware_devices
.rtc
.mmio
.base
.saturating_add(platform.firmware_devices.rtc.mmio.size),
)
.div_ceil(PAGE_SIZE)
* PAGE_SIZE;
(base, end.saturating_sub(base).max(PAGE_SIZE))
}
fn add_chosen(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult {
let chosen = add_child(fdt, root, "chosen");
set_prop(
fdt,
chosen,
prop_string(
"stdout-path",
&format!("/serial@{:x}", platform.serial.mmio.base),
),
)
}
fn add_cpus(fdt: &mut Fdt, root: NodeId) -> AxVmResult {
let cpus = add_child(fdt, root, "cpus");
set_prop(fdt, cpus, prop_u32("#address-cells", 1))?;
set_prop(fdt, cpus, prop_u32("#size-cells", 0))?;
let cpu_map = add_child(fdt, cpus, "cpu-map");
let socket0 = add_child(fdt, cpu_map, "socket0");
let core0 = add_child(fdt, socket0, "core0");
set_prop(fdt, core0, prop_u32("cpu", PHANDLE_CPU0))?;
let cpu = add_child(fdt, cpus, "cpu@0");
set_prop(fdt, cpu, prop_string("device_type", "cpu"))?;
set_prop(
fdt,
cpu,
prop_string("compatible", "loongarch,Loongson-3A5000"),
)?;
set_prop(fdt, cpu, prop_u32("reg", 0))?;
set_prop(fdt, cpu, prop_u32("phandle", PHANDLE_CPU0))
}
fn add_memory(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult {
for region in &platform.ram_regions {
let memory = add_child(fdt, root, &format!("memory@{:x}", region.base));
set_prop(fdt, memory, prop_string("device_type", "memory"))?;
prop_reg(fdt, memory, region.base, region.size)?;
}
Ok(())
}
fn add_interrupt_controllers(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult {
let cpuic = add_child(fdt, root, "cpuic");
set_prop(fdt, cpuic, prop_null("interrupt-controller"))?;
set_prop(fdt, cpuic, prop_u32("#interrupt-cells", 1))?;
set_prop(
fdt,
cpuic,
prop_string("compatible", "loongson,cpu-interrupt-controller"),
)?;
set_prop(fdt, cpuic, prop_u32("phandle", PHANDLE_CPUIC))?;
let eiointc = add_child(fdt, root, "eiointc@1400");
set_prop(
fdt,
eiointc,
prop_string("compatible", "loongson,ls2k2000-eiointc"),
)?;
set_prop(fdt, eiointc, prop_null("interrupt-controller"))?;
set_prop(fdt, eiointc, prop_u32("#interrupt-cells", 1))?;
set_prop(fdt, eiointc, prop_u32("interrupt-parent", PHANDLE_CPUIC))?;
set_prop(
fdt,
eiointc,
prop_u32_array("interrupts", &[platform.interrupt.eiointc_irq]),
)?;
set_prop(
fdt,
eiointc,
prop_u32_array("reg", &[0, 0x0000_1400, 0, 0x800]),
)?;
set_prop(fdt, eiointc, prop_u32("phandle", PHANDLE_EIOINTC))?;
let pch_pic = add_child(
fdt,
root,
&format!("platic@{:x}", platform.interrupt.pch_pic.base),
);
set_prop(
fdt,
pch_pic,
prop_string("compatible", "loongson,pch-pic-1.0"),
)?;
set_prop(fdt, pch_pic, prop_null("interrupt-controller"))?;
set_prop(fdt, pch_pic, prop_u32("#interrupt-cells", 2))?;
set_prop(fdt, pch_pic, prop_u32("interrupt-parent", PHANDLE_EIOINTC))?;
prop_reg(
fdt,
pch_pic,
platform.interrupt.pch_pic.base,
platform.interrupt.pch_pic.size,
)?;
set_prop(
fdt,
pch_pic,
prop_u32_array(
"loongson,pic-base-vec",
&[platform.interrupt.pch_pic_gsi_base],
),
)?;
set_prop(fdt, pch_pic, prop_u32("phandle", PHANDLE_PCH_PIC))?;
let msi = add_child(
fdt,
root,
&format!("msi@{:x}", platform.interrupt.pch_msi.base),
);
set_prop(fdt, msi, prop_string("compatible", "loongson,pch-msi-1.0"))?;
set_prop(fdt, msi, prop_null("interrupt-controller"))?;
set_prop(fdt, msi, prop_u32("interrupt-parent", PHANDLE_EIOINTC))?;
prop_reg(
fdt,
msi,
platform.interrupt.pch_msi.base,
platform.interrupt.pch_msi.size,
)?;
set_prop(
fdt,
msi,
prop_u32("loongson,msi-base-vec", platform.interrupt.pch_msi_start),
)?;
set_prop(
fdt,
msi,
prop_u32("loongson,msi-num-vecs", platform.interrupt.pch_msi_count),
)?;
set_prop(fdt, msi, prop_u32("phandle", PHANDLE_PCH_MSI))
}
fn add_configured_devices(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult {
for device in &platform.configured_fdt_devices {
let name = configured_device_node_name(device);
let node = add_child(fdt, root, &name);
let compatible = device
.compatible
.iter()
.map(String::as_str)
.collect::<Vec<_>>();
set_prop(fdt, node, prop_string_list("compatible", &compatible))?;
if !device.registers.is_empty() {
let mut registers = Vec::with_capacity(device.registers.len() * 4);
for (base, size) in &device.registers {
registers.extend_from_slice(&[
(base >> 32) as u32,
*base as u32,
(size >> 32) as u32,
*size as u32,
]);
}
set_prop(fdt, node, prop_u32_array("reg", ®isters))?;
}
if !device.interrupts.is_empty() {
let mut interrupts = Vec::with_capacity(device.interrupts.len() * 2);
for interrupt in &device.interrupts {
if interrupt.controller != platform.interrupt.controller {
return Err(crate::AxVmError::invalid_config(format!(
"device {} uses unsupported LoongArch interrupt controller {}",
device.id,
interrupt.controller.value()
)));
}
let flags = match interrupt.trigger {
axdevice_base::InterruptTrigger::EdgeTriggered => 1,
axdevice_base::InterruptTrigger::LevelTriggered => 4,
};
interrupts.extend_from_slice(&[interrupt.input, flags]);
}
set_prop(fdt, node, prop_u32("interrupt-parent", PHANDLE_PCH_PIC))?;
set_prop(fdt, node, prop_u32_array("interrupts", &interrupts))?;
}
for property in &device.properties {
let property = match property {
ResolvedFdtProperty::Empty(name) => prop_null(name),
ResolvedFdtProperty::U32(name, value) => prop_u32(name, *value),
ResolvedFdtProperty::String(name, value) => prop_string(name, value),
};
set_prop(fdt, node, property)?;
}
}
Ok(())
}
fn configured_device_node_name(device: &ResolvedFdtDevice) -> String {
device.registers.first().map_or_else(
|| format!("{}-{}", device.node_name, device.id),
|(base, _)| format!("{}@{base:x}", device.node_name),
)
}
fn add_power(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult {
let ged = platform.firmware_devices.ged;
let ged_node = add_child(fdt, root, &format!("ged@{:x}", ged.mmio.base));
set_prop(fdt, ged_node, prop_string("compatible", "syscon"))?;
prop_reg(fdt, ged_node, ged.mmio.base, ged.mmio.size)?;
set_prop(fdt, ged_node, prop_u32("reg-shift", 0))?;
set_prop(fdt, ged_node, prop_u32("reg-io-width", 1))?;
set_prop(fdt, ged_node, prop_u32("phandle", PHANDLE_GED_SYSCON))?;
let poweroff = add_child(fdt, root, "poweroff");
set_prop(fdt, poweroff, prop_string("compatible", "syscon-poweroff"))?;
set_prop(fdt, poweroff, prop_u32("regmap", PHANDLE_GED_SYSCON))?;
set_prop(fdt, poweroff, prop_u32("offset", ged.poweroff_offset))?;
set_prop(fdt, poweroff, prop_u32("value", ged.poweroff_value))?;
let reboot = add_child(fdt, root, "reboot");
set_prop(fdt, reboot, prop_string("compatible", "syscon-reboot"))?;
set_prop(fdt, reboot, prop_u32("regmap", PHANDLE_GED_SYSCON))?;
set_prop(fdt, reboot, prop_u32("offset", ged.reboot_offset))?;
set_prop(fdt, reboot, prop_u32("value", ged.reboot_value))
}
fn add_rtc(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult {
let rtc = platform.firmware_devices.rtc;
let rtc_node = add_child(fdt, root, &format!("rtc@{:x}", rtc.mmio.base));
set_prop(
fdt,
rtc_node,
prop_string("compatible", "loongson,ls7a-rtc"),
)?;
prop_reg(fdt, rtc_node, rtc.mmio.base, rtc.mmio.size)?;
set_prop(fdt, rtc_node, prop_u32("interrupt-parent", PHANDLE_PCH_PIC))?;
set_prop(fdt, rtc_node, prop_u32_array("interrupts", &[rtc.irq, 4]))
}
fn add_serial(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult {
let serial = add_child(
fdt,
root,
&format!("serial@{:x}", platform.serial.mmio.base),
);
set_prop(fdt, serial, prop_string("compatible", "ns16550a"))?;
prop_reg(
fdt,
serial,
platform.serial.mmio.base,
platform.serial.mmio.size,
)?;
set_prop(
fdt,
serial,
prop_u32("clock-frequency", platform.serial.clock_hz),
)?;
set_prop(
fdt,
serial,
prop_u32("reg-shift", u32::from(platform.serial.register_shift)),
)?;
set_prop(
fdt,
serial,
prop_u32(
"reg-io-width",
u32::try_from(platform.serial.register_width.size())
.expect("a serial access width is at most eight bytes"),
),
)?;
set_prop(fdt, serial, prop_u32("current-speed", platform.serial.baud))?;
set_prop(fdt, serial, prop_u32("interrupt-parent", PHANDLE_PCH_PIC))?;
set_prop(
fdt,
serial,
prop_u32_array("interrupts", &[platform.serial.irq, 4]),
)
}
fn add_flash(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult {
let flash = platform.firmware_devices.flash;
let flash_node = add_child(fdt, root, &format!("flash@{:x}", flash.banks[0].base));
set_prop(fdt, flash_node, prop_string("compatible", "cfi-flash"))?;
set_prop(fdt, flash_node, prop_u32("bank-width", flash.bank_width))?;
set_prop(
fdt,
flash_node,
prop_u32_array(
"reg",
&[
(flash.banks[0].base >> 32) as u32,
flash.banks[0].base as u32,
(flash.banks[0].size >> 32) as u32,
flash.banks[0].size as u32,
(flash.banks[1].base >> 32) as u32,
flash.banks[1].base as u32,
(flash.banks[1].size >> 32) as u32,
flash.banks[1].size as u32,
],
),
)
}
fn add_fw_cfg(fdt: &mut Fdt, root: NodeId, platform: &GuestPlatform) -> AxVmResult {
let fw_cfg = add_child(fdt, root, &format!("fw_cfg@{:x}", platform.fw_cfg.base));
set_prop(fdt, fw_cfg, prop_string("compatible", "qemu,fw-cfg-mmio"))?;
prop_reg(fdt, fw_cfg, platform.fw_cfg.base, platform.fw_cfg.size)?;
set_prop(fdt, fw_cfg, prop_null("dma-coherent"))
}
fn prop_reg(fdt: &mut Fdt, node: NodeId, base: u64, size: u64) -> AxVmResult {
set_prop(
fdt,
node,
prop_u32_array(
"reg",
&[
(base >> 32) as u32,
base as u32,
(size >> 32) as u32,
size as u32,
],
),
)
}
#[cfg(test)]
mod tests {
use fdt_edit::Fdt;
use super::*;
fn test_platform() -> GuestPlatform {
crate::arch::loongarch64::boot::probe::GuestPlatformBuilder::new(Vec::new(), None).build()
}
#[test]
fn loongarch_firmware_dtb_is_reparseable() {
let platform = test_platform();
let dtb = build(&platform).unwrap();
let fdt = Fdt::from_bytes(&dtb).unwrap();
assert!(fdt.get_by_path_id("/chosen").is_some());
assert!(fdt.get_by_path_id("/cpus/cpu@0").is_some());
let serial_path = format!("/serial@{:x}", platform.serial.mmio.base);
let serial = fdt.get_by_path(&serial_path).unwrap();
let regs = serial.regs();
assert_eq!(regs.len(), 1);
assert_eq!(regs[0].address, platform.serial.mmio.base);
assert_eq!(regs[0].size, Some(platform.serial.mmio.size));
assert!(
serial
.as_node()
.compatibles()
.any(|compatible| compatible == "ns16550a")
);
assert_eq!(
serial
.as_node()
.get_property("clock-frequency")
.unwrap()
.get_u32(),
Some(platform.serial.clock_hz)
);
assert_eq!(
serial
.as_node()
.get_property("current-speed")
.unwrap()
.get_u32(),
Some(platform.serial.baud)
);
assert_eq!(
serial
.as_node()
.get_property("interrupts")
.unwrap()
.get_u32_iter()
.collect::<Vec<_>>(),
[platform.serial.irq, 4]
);
assert_eq!(
fdt.get_by_path("/chosen")
.unwrap()
.as_node()
.get_property("stdout-path")
.unwrap()
.as_str(),
Some(serial_path.as_str())
);
}
#[test]
fn loongarch_firmware_dtb_encodes_resolved_devices() {
let mut platform = test_platform();
platform.configured_fdt_devices.push(ResolvedFdtDevice {
id: "virtio-blk0".into(),
node_name: "virtio_mmio".into(),
compatible: std::vec!["virtio,mmio".into()],
registers: std::vec![(0x0a00_0000, 0x200)],
interrupts: std::vec![crate::boot::fdt::device::ResolvedFdtInterrupt {
controller: axdevice_base::InterruptControllerId::new(0),
input: 48,
trigger: axdevice_base::InterruptTrigger::EdgeTriggered,
}],
properties: std::vec![ResolvedFdtProperty::Empty("dma-coherent".into())],
});
let dtb = build(&platform).unwrap();
let fdt = Fdt::from_bytes(&dtb).unwrap();
let device = fdt.get_by_path("/virtio_mmio@a000000").unwrap();
assert_eq!(device.regs()[0].address, 0x0a00_0000);
assert_eq!(device.regs()[0].size, Some(0x200));
assert_eq!(
device
.as_node()
.get_property("interrupts")
.unwrap()
.get_u32_iter()
.collect::<Vec<_>>(),
[48, 1]
);
assert!(device.as_node().get_property("dma-coherent").is_some());
}
}