use std::io;
use vm_fdt::FdtWriter;
use super::layout;
const GIC_PHANDLE: u32 = 1;
const CLOCK_PHANDLE: u32 = 2;
const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
const IRQ_TYPE_EDGE_RISING: u32 = 1;
const IRQ_TYPE_LEVEL_HI: u32 = 4;
#[derive(Clone, Copy)]
pub struct VirtioMmioEntry {
pub base: u64,
pub irq: u32,
}
pub fn generate(
n_vcpus: usize,
mem_size: u64,
cmdline: &str,
initrd: Option<(u64, u64)>,
virtio_devs: &[VirtioMmioEntry],
) -> io::Result<Vec<u8>> {
let mut fdt =
FdtWriter::new().map_err(|e| io::Error::other(format!("FdtWriter::new: {e:?}")))?;
let root = fdt.begin_node("").map_err(map_fdt)?;
fdt.property_string("compatible", "linux,dummy-virt")
.map_err(map_fdt)?;
fdt.property_u32("#address-cells", 0x2).map_err(map_fdt)?;
fdt.property_u32("#size-cells", 0x2).map_err(map_fdt)?;
fdt.property_u32("interrupt-parent", GIC_PHANDLE)
.map_err(map_fdt)?;
let cpus = fdt.begin_node("cpus").map_err(map_fdt)?;
fdt.property_u32("#address-cells", 0x2).map_err(map_fdt)?;
fdt.property_u32("#size-cells", 0x0).map_err(map_fdt)?;
for i in 0..n_vcpus {
let cpu = fdt.begin_node(&format!("cpu@{i:x}")).map_err(map_fdt)?;
fdt.property_string("device_type", "cpu").map_err(map_fdt)?;
fdt.property_string("compatible", "arm,arm-v8")
.map_err(map_fdt)?;
if n_vcpus > 1 {
fdt.property_string("enable-method", "psci")
.map_err(map_fdt)?;
}
fdt.property_u64("reg", (i as u64) & 0x7fffff)
.map_err(map_fdt)?;
fdt.end_node(cpu).map_err(map_fdt)?;
}
fdt.end_node(cpus).map_err(map_fdt)?;
let mem = fdt.begin_node("memory").map_err(map_fdt)?;
fdt.property_string("device_type", "memory")
.map_err(map_fdt)?;
let mem_reg = be_u64_pair(layout::DRAM_MEM_START_KERNEL, mem_size);
fdt.property("reg", &mem_reg).map_err(map_fdt)?;
fdt.end_node(mem).map_err(map_fdt)?;
let chosen = fdt.begin_node("chosen").map_err(map_fdt)?;
fdt.property_string("bootargs", cmdline).map_err(map_fdt)?;
if let Some((start, size)) = initrd {
fdt.property_u64("linux,initrd-start", start)
.map_err(map_fdt)?;
fdt.property_u64("linux,initrd-end", start + size)
.map_err(map_fdt)?;
}
fdt.end_node(chosen).map_err(map_fdt)?;
let intc = fdt
.begin_node(&format!("intc@{:x}", layout::GICV3_DIST_BASE))
.map_err(map_fdt)?;
fdt.property_string("compatible", "arm,gic-v3")
.map_err(map_fdt)?;
fdt.property_null("interrupt-controller").map_err(map_fdt)?;
fdt.property_u32("#interrupt-cells", 3).map_err(map_fdt)?;
fdt.property_u32("#address-cells", 0x2).map_err(map_fdt)?;
fdt.property_u32("#size-cells", 0x2).map_err(map_fdt)?;
fdt.property_u32("phandle", GIC_PHANDLE).map_err(map_fdt)?;
let mut intc_reg = Vec::new();
intc_reg.extend_from_slice(&be_u64_pair(
layout::GICV3_DIST_BASE,
layout::GICV3_DIST_SIZE,
));
intc_reg.extend_from_slice(&be_u64_pair(
layout::GICV3_REDIST_BASE,
layout::GICV3_REDIST_STRIDE * (n_vcpus as u64),
));
fdt.property("reg", &intc_reg).map_err(map_fdt)?;
fdt.end_node(intc).map_err(map_fdt)?;
let timer = fdt.begin_node("timer").map_err(map_fdt)?;
fdt.property_string("compatible", "arm,armv8-timer")
.map_err(map_fdt)?;
fdt.property_null("always-on").map_err(map_fdt)?;
let timer_irqs = [
(13u32, IRQ_TYPE_LEVEL_HI),
(14, IRQ_TYPE_LEVEL_HI),
(11, IRQ_TYPE_LEVEL_HI),
(10, IRQ_TYPE_LEVEL_HI),
];
let mut tirqs: Vec<u8> = Vec::new();
for (intid, flags) in timer_irqs {
tirqs.extend_from_slice(&be_u32(GIC_FDT_IRQ_TYPE_PPI));
tirqs.extend_from_slice(&be_u32(intid));
tirqs.extend_from_slice(&be_u32(flags));
}
fdt.property("interrupts", &tirqs).map_err(map_fdt)?;
fdt.end_node(timer).map_err(map_fdt)?;
let serial = fdt
.begin_node(&format!("pl011@{:x}", layout::SERIAL_MMIO_BASE))
.map_err(map_fdt)?;
fdt.property_string_list(
"compatible",
vec!["arm,pl011".to_string(), "arm,primecell".to_string()],
)
.map_err(map_fdt)?;
let serial_reg = be_u64_pair(layout::SERIAL_MMIO_BASE, layout::SERIAL_MMIO_SIZE);
fdt.property("reg", &serial_reg).map_err(map_fdt)?;
let mut serial_irq = Vec::new();
serial_irq.extend_from_slice(&be_u32(GIC_FDT_IRQ_TYPE_SPI));
serial_irq.extend_from_slice(&be_u32(layout::SERIAL_IRQ - 32));
serial_irq.extend_from_slice(&be_u32(IRQ_TYPE_LEVEL_HI));
fdt.property("interrupts", &serial_irq).map_err(map_fdt)?;
fdt.property_u32("clocks", CLOCK_PHANDLE).map_err(map_fdt)?;
fdt.property_string("clock-names", "apb_pclk")
.map_err(map_fdt)?;
fdt.end_node(serial).map_err(map_fdt)?;
let clk = fdt.begin_node("apb-pclk").map_err(map_fdt)?;
fdt.property_string("compatible", "fixed-clock")
.map_err(map_fdt)?;
fdt.property_u32("#clock-cells", 0x0).map_err(map_fdt)?;
fdt.property_u32("clock-frequency", 24_000_000)
.map_err(map_fdt)?;
fdt.property_string("clock-output-names", "clk24mhz")
.map_err(map_fdt)?;
fdt.property_u32("phandle", CLOCK_PHANDLE)
.map_err(map_fdt)?;
fdt.end_node(clk).map_err(map_fdt)?;
let psci = fdt.begin_node("psci").map_err(map_fdt)?;
fdt.property_string("compatible", "arm,psci-0.2")
.map_err(map_fdt)?;
fdt.property_string("method", "hvc").map_err(map_fdt)?;
fdt.end_node(psci).map_err(map_fdt)?;
for ent in virtio_devs {
let n = fdt
.begin_node(&format!("virtio_mmio@{:x}", ent.base))
.map_err(map_fdt)?;
fdt.property_string("compatible", "virtio,mmio")
.map_err(map_fdt)?;
let reg = be_u64_pair(ent.base, 0x200);
fdt.property("reg", ®).map_err(map_fdt)?;
let mut irq = Vec::new();
irq.extend_from_slice(&be_u32(GIC_FDT_IRQ_TYPE_SPI));
irq.extend_from_slice(&be_u32(ent.irq - 32));
irq.extend_from_slice(&be_u32(IRQ_TYPE_EDGE_RISING));
fdt.property("interrupts", &irq).map_err(map_fdt)?;
fdt.end_node(n).map_err(map_fdt)?;
}
fdt.end_node(root).map_err(map_fdt)?;
fdt.finish().map_err(map_fdt)
}
fn map_fdt(e: vm_fdt::Error) -> io::Error {
io::Error::other(format!("FDT: {e:?}"))
}
fn be_u32(v: u32) -> [u8; 4] {
v.to_be_bytes()
}
fn be_u64_pair(a: u64, b: u64) -> [u8; 16] {
let mut o = [0u8; 16];
o[..8].copy_from_slice(&a.to_be_bytes());
o[8..].copy_from_slice(&b.to_be_bytes());
o
}