axvm 0.5.18

Virtual Machine resource management crate for ArceOS's hypervisor variant.
use alloc::{format, vec, vec::Vec};

use ax_errno::{AxError, AxResult, ax_err_type};

use crate::boot::{fdt::vm_fdt::FdtWriter, guest_platform::loongarch64::GuestPlatform};

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) -> AxResult<Vec<u8>> {
    let mut fdt = FdtWriter::new().map_err(fdt_err)?;
    let root = fdt.begin_node("").map_err(fdt_err)?;
    fdt.property_u32("#address-cells", 2).map_err(fdt_err)?;
    fdt.property_u32("#size-cells", 2).map_err(fdt_err)?;
    fdt.property_string("compatible", "linux,dummy-loongson3")
        .map_err(fdt_err)?;

    add_chosen(&mut fdt, platform)?;
    add_cpus(&mut fdt)?;
    add_memory(&mut fdt, platform)?;
    add_interrupt_controllers(&mut fdt, platform)?;
    add_platform_bus(&mut fdt, platform)?;
    add_power(&mut fdt, platform)?;
    add_rtc(&mut fdt, platform)?;
    add_serial(&mut fdt, platform)?;
    add_flash(&mut fdt, platform)?;
    add_fw_cfg(&mut fdt, platform)?;

    fdt.end_node(root).map_err(fdt_err)?;
    fdt.finish().map_err(fdt_err)
}

fn fdt_err(err: impl core::fmt::Debug) -> AxError {
    ax_err_type!(
        InvalidData,
        format!("failed to build LoongArch UEFI firmware FDT: {err:?}")
    )
}

fn add_platform_bus(fdt: &mut FdtWriter, platform: &GuestPlatform) -> AxResult {
    let (bus_base, bus_size) = platform_bus_range(platform);
    let platform_bus = fdt
        .begin_node(&format!("platform-bus@{bus_base:x}"))
        .map_err(fdt_err)?;
    fdt.property_string_list(
        "compatible",
        vec!["qemu,platform".into(), "simple-bus".into()],
    )
    .map_err(fdt_err)?;
    fdt.property_u32("#address-cells", 1).map_err(fdt_err)?;
    fdt.property_u32("#size-cells", 1).map_err(fdt_err)?;
    fdt.property_array_u32("ranges", &[0, 0, bus_base as u32, bus_size as u32])
        .map_err(fdt_err)?;
    fdt.property_u32("interrupt-parent", PHANDLE_PCH_PIC)
        .map_err(fdt_err)?;
    fdt.end_node(platform_bus).map_err(fdt_err)?;
    Ok(())
}

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 FdtWriter, platform: &GuestPlatform) -> AxResult {
    let chosen = fdt.begin_node("chosen").map_err(fdt_err)?;
    fdt.property_string(
        "stdout-path",
        &format!("/serial@{:x}", platform.serial.mmio.base),
    )
    .map_err(fdt_err)?;
    fdt.end_node(chosen).map_err(fdt_err)?;
    Ok(())
}

fn add_cpus(fdt: &mut FdtWriter) -> AxResult {
    let cpus = fdt.begin_node("cpus").map_err(fdt_err)?;
    fdt.property_u32("#address-cells", 1).map_err(fdt_err)?;
    fdt.property_u32("#size-cells", 0).map_err(fdt_err)?;
    let cpu_map = fdt.begin_node("cpu-map").map_err(fdt_err)?;
    let socket0 = fdt.begin_node("socket0").map_err(fdt_err)?;
    let core0 = fdt.begin_node("core0").map_err(fdt_err)?;
    fdt.property_u32("cpu", PHANDLE_CPU0).map_err(fdt_err)?;
    fdt.end_node(core0).map_err(fdt_err)?;
    fdt.end_node(socket0).map_err(fdt_err)?;
    fdt.end_node(cpu_map).map_err(fdt_err)?;
    let cpu = fdt.begin_node("cpu@0").map_err(fdt_err)?;
    fdt.property_string("device_type", "cpu").map_err(fdt_err)?;
    fdt.property_string("compatible", "loongarch,Loongson-3A5000")
        .map_err(fdt_err)?;
    fdt.property_u32("reg", 0).map_err(fdt_err)?;
    fdt.property_phandle(PHANDLE_CPU0).map_err(fdt_err)?;
    fdt.end_node(cpu).map_err(fdt_err)?;
    fdt.end_node(cpus).map_err(fdt_err)?;
    Ok(())
}

fn add_memory(fdt: &mut FdtWriter, platform: &GuestPlatform) -> AxResult {
    for region in &platform.ram_regions {
        let memory = fdt
            .begin_node(&format!("memory@{:x}", region.base))
            .map_err(fdt_err)?;
        fdt.property_string("device_type", "memory")
            .map_err(fdt_err)?;
        fdt.property_array_u32(
            "reg",
            &[
                (region.base >> 32) as u32,
                region.base as u32,
                (region.size >> 32) as u32,
                region.size as u32,
            ],
        )
        .map_err(fdt_err)?;
        fdt.end_node(memory).map_err(fdt_err)?;
    }
    Ok(())
}

fn add_interrupt_controllers(fdt: &mut FdtWriter, platform: &GuestPlatform) -> AxResult {
    let cpuic = fdt.begin_node("cpuic").map_err(fdt_err)?;
    fdt.property_null("interrupt-controller").map_err(fdt_err)?;
    fdt.property_u32("#interrupt-cells", 1).map_err(fdt_err)?;
    fdt.property_string("compatible", "loongson,cpu-interrupt-controller")
        .map_err(fdt_err)?;
    fdt.property_phandle(PHANDLE_CPUIC).map_err(fdt_err)?;
    fdt.end_node(cpuic).map_err(fdt_err)?;

    let eiointc = fdt.begin_node("eiointc@1400").map_err(fdt_err)?;
    fdt.property_string("compatible", "loongson,ls2k2000-eiointc")
        .map_err(fdt_err)?;
    fdt.property_null("interrupt-controller").map_err(fdt_err)?;
    fdt.property_u32("#interrupt-cells", 1).map_err(fdt_err)?;
    fdt.property_u32("interrupt-parent", PHANDLE_CPUIC)
        .map_err(fdt_err)?;
    fdt.property_array_u32("interrupts", &[platform.interrupt.eiointc_irq])
        .map_err(fdt_err)?;
    fdt.property_array_u32("reg", &[0, 0x0000_1400, 0, 0x800])
        .map_err(fdt_err)?;
    fdt.property_phandle(PHANDLE_EIOINTC).map_err(fdt_err)?;
    fdt.end_node(eiointc).map_err(fdt_err)?;

    let pch_pic = fdt
        .begin_node(&format!("platic@{:x}", platform.interrupt.pch_pic.base))
        .map_err(fdt_err)?;
    fdt.property_string("compatible", "loongson,pch-pic-1.0")
        .map_err(fdt_err)?;
    fdt.property_null("interrupt-controller").map_err(fdt_err)?;
    fdt.property_u32("#interrupt-cells", 2).map_err(fdt_err)?;
    fdt.property_u32("interrupt-parent", PHANDLE_EIOINTC)
        .map_err(fdt_err)?;
    prop_reg(
        fdt,
        platform.interrupt.pch_pic.base,
        platform.interrupt.pch_pic.size,
    )?;
    fdt.property_array_u32(
        "loongson,pic-base-vec",
        &[platform.interrupt.pch_pic_gsi_base],
    )
    .map_err(fdt_err)?;
    fdt.property_phandle(PHANDLE_PCH_PIC).map_err(fdt_err)?;
    fdt.end_node(pch_pic).map_err(fdt_err)?;

    let msi = fdt
        .begin_node(&format!("msi@{:x}", platform.interrupt.pch_msi.base))
        .map_err(fdt_err)?;
    fdt.property_string("compatible", "loongson,pch-msi-1.0")
        .map_err(fdt_err)?;
    fdt.property_null("interrupt-controller").map_err(fdt_err)?;
    fdt.property_u32("interrupt-parent", PHANDLE_EIOINTC)
        .map_err(fdt_err)?;
    prop_reg(
        fdt,
        platform.interrupt.pch_msi.base,
        platform.interrupt.pch_msi.size,
    )?;
    fdt.property_u32("loongson,msi-base-vec", platform.interrupt.pch_msi_start)
        .map_err(fdt_err)?;
    fdt.property_u32("loongson,msi-num-vecs", platform.interrupt.pch_msi_count)
        .map_err(fdt_err)?;
    fdt.property_phandle(PHANDLE_PCH_MSI).map_err(fdt_err)?;
    fdt.end_node(msi).map_err(fdt_err)?;
    Ok(())
}

fn add_power(fdt: &mut FdtWriter, platform: &GuestPlatform) -> AxResult {
    let ged = platform.firmware_devices.ged;
    let ged_node = fdt
        .begin_node(&format!("ged@{:x}", ged.mmio.base))
        .map_err(fdt_err)?;
    fdt.property_string("compatible", "syscon")
        .map_err(fdt_err)?;
    prop_reg(fdt, ged.mmio.base, ged.mmio.size)?;
    fdt.property_u32("reg-shift", 0).map_err(fdt_err)?;
    fdt.property_u32("reg-io-width", 1).map_err(fdt_err)?;
    fdt.property_phandle(PHANDLE_GED_SYSCON).map_err(fdt_err)?;
    fdt.end_node(ged_node).map_err(fdt_err)?;

    let poweroff = fdt.begin_node("poweroff").map_err(fdt_err)?;
    fdt.property_string("compatible", "syscon-poweroff")
        .map_err(fdt_err)?;
    fdt.property_u32("regmap", PHANDLE_GED_SYSCON)
        .map_err(fdt_err)?;
    fdt.property_u32("offset", ged.poweroff_offset)
        .map_err(fdt_err)?;
    fdt.property_u32("value", ged.poweroff_value)
        .map_err(fdt_err)?;
    fdt.end_node(poweroff).map_err(fdt_err)?;

    let reboot = fdt.begin_node("reboot").map_err(fdt_err)?;
    fdt.property_string("compatible", "syscon-reboot")
        .map_err(fdt_err)?;
    fdt.property_u32("regmap", PHANDLE_GED_SYSCON)
        .map_err(fdt_err)?;
    fdt.property_u32("offset", ged.reboot_offset)
        .map_err(fdt_err)?;
    fdt.property_u32("value", ged.reboot_value)
        .map_err(fdt_err)?;
    fdt.end_node(reboot).map_err(fdt_err)?;
    Ok(())
}

fn add_rtc(fdt: &mut FdtWriter, platform: &GuestPlatform) -> AxResult {
    let rtc = platform.firmware_devices.rtc;
    let rtc_node = fdt
        .begin_node(&format!("rtc@{:x}", rtc.mmio.base))
        .map_err(fdt_err)?;
    fdt.property_string("compatible", "loongson,ls7a-rtc")
        .map_err(fdt_err)?;
    prop_reg(fdt, rtc.mmio.base, rtc.mmio.size)?;
    fdt.property_u32("interrupt-parent", PHANDLE_PCH_PIC)
        .map_err(fdt_err)?;
    fdt.property_array_u32("interrupts", &[rtc.irq, 4])
        .map_err(fdt_err)?;
    fdt.end_node(rtc_node).map_err(fdt_err)?;
    Ok(())
}

fn add_serial(fdt: &mut FdtWriter, platform: &GuestPlatform) -> AxResult {
    let serial = fdt
        .begin_node(&format!("serial@{:x}", platform.serial.mmio.base))
        .map_err(fdt_err)?;
    fdt.property_string("compatible", "ns16550a")
        .map_err(fdt_err)?;
    prop_reg(fdt, platform.serial.mmio.base, platform.serial.mmio.size)?;
    fdt.property_u32("clock-frequency", platform.serial.clock_hz)
        .map_err(fdt_err)?;
    fdt.property_u32("interrupt-parent", PHANDLE_PCH_PIC)
        .map_err(fdt_err)?;
    fdt.property_array_u32("interrupts", &[platform.serial.irq, 4])
        .map_err(fdt_err)?;
    fdt.end_node(serial).map_err(fdt_err)?;
    Ok(())
}

fn add_flash(fdt: &mut FdtWriter, platform: &GuestPlatform) -> AxResult {
    let flash = platform.firmware_devices.flash;
    let flash_node = fdt
        .begin_node(&format!("flash@{:x}", flash.banks[0].base))
        .map_err(fdt_err)?;
    fdt.property_string("compatible", "cfi-flash")
        .map_err(fdt_err)?;
    fdt.property_u32("bank-width", flash.bank_width)
        .map_err(fdt_err)?;
    fdt.property_array_u32(
        "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,
        ],
    )
    .map_err(fdt_err)?;
    fdt.end_node(flash_node).map_err(fdt_err)?;
    Ok(())
}

fn add_fw_cfg(fdt: &mut FdtWriter, platform: &GuestPlatform) -> AxResult {
    let fw_cfg = fdt
        .begin_node(&format!("fw_cfg@{:x}", platform.fw_cfg.base))
        .map_err(fdt_err)?;
    fdt.property_string("compatible", "qemu,fw-cfg-mmio")
        .map_err(fdt_err)?;
    prop_reg(fdt, platform.fw_cfg.base, platform.fw_cfg.size)?;
    fdt.property_null("dma-coherent").map_err(fdt_err)?;
    fdt.end_node(fw_cfg).map_err(fdt_err)?;
    Ok(())
}

fn prop_reg(fdt: &mut FdtWriter, base: u64, size: u64) -> AxResult {
    fdt.property_array_u32(
        "reg",
        &[
            (base >> 32) as u32,
            base as u32,
            (size >> 32) as u32,
            size as u32,
        ],
    )
    .map_err(fdt_err)
}