axvm 0.5.26

Virtual Machine resource management crate for ArceOS's hypervisor variant.
//! LoongArch platform AML generation.

use std::{format, vec, vec::Vec};

use super::config::{LoongArchFwCfgPciConfig, LoongArchFwCfgSerialConfig};

pub(super) fn build_loongarch_dsdt_aml(
    serial: &LoongArchFwCfgSerialConfig,
    pci: &LoongArchFwCfgPciConfig,
) -> Vec<u8> {
    let mut scope_body = Vec::new();
    scope_body.extend(aml_device("COMA", build_coma_aml(serial)));
    scope_body.extend(aml_device("PCI0", build_pci0_aml(pci)));

    let mut aml = Vec::new();
    aml.extend(aml_scope("_SB_", scope_body));
    aml.extend(aml_name_decl(
        "_S5_",
        aml_package(&[aml_int(5), aml_int(0), aml_int(0), aml_int(0)]),
    ));
    aml
}

fn build_coma_aml(serial: &LoongArchFwCfgSerialConfig) -> Vec<u8> {
    let mut body = Vec::new();
    body.extend(aml_name_decl("_HID", aml_string("PNP0501")));
    body.extend(aml_name_decl("_UID", aml_int(0)));
    body.extend(aml_name_decl("_CCA", aml_int(1)));
    body.extend(aml_name_decl("_CRS", serial_crs_aml(serial)));
    body.extend_from_slice(&[
        0x08, 0x5f, 0x44, 0x53, 0x44, 0x12, 0x32, 0x02, 0x11, 0x13, 0x0a, 0x10, 0x14, 0xd8, 0xff,
        0xda, 0xba, 0x6e, 0x8c, 0x4d, 0x8a, 0x91, 0xbc, 0x9b, 0xbf, 0x4a, 0xa3, 0x01, 0x12, 0x1b,
        0x01, 0x12, 0x18, 0x02, 0x0d, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x2d, 0x66, 0x72, 0x65, 0x71,
        0x75, 0x65, 0x6e, 0x63, 0x79, 0x00, 0x0c, 0x00, 0xe1, 0xf5, 0x05,
    ]);
    body
}

fn build_pci0_aml(pci: &LoongArchFwCfgPciConfig) -> Vec<u8> {
    let mut body = Vec::new();
    body.extend(aml_name_decl("_HID", aml_string("PNP0A08")));
    body.extend(aml_name_decl("_CID", aml_string("PNP0A03")));
    body.extend(aml_name_decl("_SEG", aml_int(0)));
    body.extend(aml_name_decl("_BBN", aml_int(0)));
    body.extend(aml_name_decl("_UID", aml_int(0)));
    body.extend(aml_name_decl("_CCA", aml_int(1)));
    body.extend(aml_name_decl("_PRT", pci_route_package_aml()));

    for gsi in 0..4 {
        body.extend(aml_device(
            &format!("GSI{gsi}"),
            build_gsi_link_aml(pci, gsi),
        ));
    }

    body.extend(aml_method("_CBA", 0, aml_return(aml_int(pci.ecam_base))));
    body.extend(aml_name_decl("_CRS", pci_crs_aml(pci)));
    body.extend(aml_device("RES0", build_pci_res0_aml(pci)));
    body
}

fn pci_route_package_aml() -> Vec<u8> {
    const PCI_SLOT_MAX: usize = 32;
    const PCI_NUM_PINS: usize = 4;

    let mut entries = Vec::new();
    for slot in 0..PCI_SLOT_MAX {
        for pin in 0..PCI_NUM_PINS {
            let gsi = (pin + slot) % PCI_NUM_PINS;
            let address = ((slot as u64) << 16) | 0xffff;
            entries.push(aml_package(&[
                aml_int(address),
                aml_int(pin as u64),
                aml_name_ref(&format!("GSI{gsi}")),
                aml_int(0),
            ]));
        }
    }
    aml_package_with_count(entries, (PCI_SLOT_MAX * PCI_NUM_PINS) as u8)
}

fn build_gsi_link_aml(pci: &LoongArchFwCfgPciConfig, gsi: usize) -> Vec<u8> {
    let irq = pci.intx_base + gsi as u8;
    let mut body = Vec::new();
    body.extend(aml_name_decl("_HID", aml_string("PNP0C0F")));
    body.extend(aml_name_decl("_UID", aml_int(gsi as u64)));
    body.extend(aml_name_decl("_PRS", interrupt_crs_aml(irq, false)));
    body.extend(aml_name_decl("_CRS", interrupt_crs_aml(irq, false)));
    body.extend(aml_method("_SRS", 1, Vec::new()));
    body
}

fn build_pci_res0_aml(pci: &LoongArchFwCfgPciConfig) -> Vec<u8> {
    let mut body = Vec::new();
    body.extend(aml_name_decl("_HID", aml_string("PNP0C02")));
    body.extend(aml_name_decl("_CRS", pci_res0_crs_aml(pci)));
    body
}

fn aml_scope(name: &str, body: Vec<u8>) -> Vec<u8> {
    let mut content = aml_name_ref(name);
    content.extend(body);
    aml_pkg_op(&[0x10], content)
}

fn aml_device(name: &str, body: Vec<u8>) -> Vec<u8> {
    let mut content = aml_name_ref(name);
    content.extend(body);
    aml_pkg_op(&[0x5b, 0x82], content)
}

fn aml_method(name: &str, arg_count: u8, body: Vec<u8>) -> Vec<u8> {
    let mut content = aml_name_ref(name);
    content.push(arg_count & 0x7);
    content.extend(body);
    aml_pkg_op(&[0x14], content)
}

fn aml_pkg_op(opcode: &[u8], content: Vec<u8>) -> Vec<u8> {
    let mut out = Vec::new();
    out.extend_from_slice(opcode);
    out.extend(aml_pkg_len(content.len()));
    out.extend(content);
    out
}

fn aml_pkg_len(content_len: usize) -> Vec<u8> {
    for len_len in 1..=4 {
        let total_len = content_len + len_len;
        let max_len = 1usize << (4 + 8 * (len_len - 1));
        if total_len < max_len {
            if len_len == 1 {
                return vec![total_len as u8];
            }
            let mut bytes = Vec::with_capacity(len_len);
            bytes.push((((len_len - 1) as u8) << 6) | ((total_len as u8) & 0x0f));
            let mut remaining = total_len >> 4;
            for _ in 1..len_len {
                bytes.push((remaining & 0xff) as u8);
                remaining >>= 8;
            }
            return bytes;
        }
    }
    unreachable!("AML package is too large")
}

fn aml_name_decl(name: &str, value: Vec<u8>) -> Vec<u8> {
    let mut out = Vec::new();
    out.push(0x08);
    out.extend(aml_name_ref(name));
    out.extend(value);
    out
}

fn aml_name_ref(name: &str) -> Vec<u8> {
    if name == "\\" {
        return vec![0x5c];
    }
    let bytes = name.as_bytes();
    assert_eq!(bytes.len(), 4, "AML short names must be 4 bytes");
    bytes.to_vec()
}

fn aml_string(value: &str) -> Vec<u8> {
    let mut out = Vec::with_capacity(value.len() + 2);
    out.push(0x0d);
    out.extend_from_slice(value.as_bytes());
    out.push(0);
    out
}

fn aml_int(value: u64) -> Vec<u8> {
    match value {
        0 => vec![0x00],
        1 => vec![0x01],
        2..=0xff => vec![0x0a, value as u8],
        0x100..=0xffff => {
            let mut out = vec![0x0b];
            out.extend_from_slice(&(value as u16).to_le_bytes());
            out
        }
        0x1_0000..=0xffff_ffff => {
            let mut out = vec![0x0c];
            out.extend_from_slice(&(value as u32).to_le_bytes());
            out
        }
        _ => {
            let mut out = vec![0x0e];
            out.extend_from_slice(&value.to_le_bytes());
            out
        }
    }
}

fn aml_package(elements: &[Vec<u8>]) -> Vec<u8> {
    aml_package_with_count(elements.to_vec(), elements.len() as u8)
}

fn aml_package_with_count(elements: Vec<Vec<u8>>, count: u8) -> Vec<u8> {
    let mut content = vec![count];
    for element in elements {
        content.extend(element);
    }
    aml_pkg_op(&[0x12], content)
}

fn aml_return(value: Vec<u8>) -> Vec<u8> {
    let mut out = vec![0xa4];
    out.extend(value);
    out
}

fn aml_buffer(bytes: &[u8]) -> Vec<u8> {
    let mut content = Vec::with_capacity(1 + bytes.len());
    content.extend(aml_int(bytes.len() as u64));
    content.extend_from_slice(bytes);
    aml_pkg_op(&[0x11], content)
}

fn aml_resource_template(resources: &[Vec<u8>]) -> Vec<u8> {
    let mut bytes = Vec::new();
    for resource in resources {
        bytes.extend_from_slice(resource);
    }
    bytes.extend_from_slice(&[0x79, 0x00]);
    aml_buffer(&bytes)
}

fn word_bus_number_resource(min: u16, max: u16) -> Vec<u8> {
    let length = max.saturating_sub(min).saturating_add(1);
    let mut out = vec![0x88, 0x0d, 0x00, 0x02, 0x0c, 0x00, 0x00, 0x00];
    out.extend_from_slice(&min.to_le_bytes());
    out.extend_from_slice(&max.to_le_bytes());
    out.extend_from_slice(&0u16.to_le_bytes());
    out.extend_from_slice(&length.to_le_bytes());
    out
}

fn dword_io_resource(base: u64, size: u32) -> Vec<u8> {
    let max = size.saturating_sub(1);
    let mut out = vec![0x87, 0x17, 0x00, 0x01, 0x0c, 0x03];
    out.extend_from_slice(&0u32.to_le_bytes());
    out.extend_from_slice(&0u32.to_le_bytes());
    out.extend_from_slice(&max.to_le_bytes());
    out.extend_from_slice(&(base as u32).to_le_bytes());
    out.extend_from_slice(&size.to_le_bytes());
    out
}

fn qword_memory_resource(base: u64, size: u64, prefetchable: bool, fixed: bool) -> Vec<u8> {
    let max = base.saturating_add(size).saturating_sub(1);
    let mut out = vec![
        0x8a,
        0x2b,
        0x00,
        0x00,
        if fixed { 0x0d } else { 0x0c },
        if prefetchable { 0x03 } else { 0x01 },
    ];
    out.extend_from_slice(&0u64.to_le_bytes());
    out.extend_from_slice(&base.to_le_bytes());
    out.extend_from_slice(&max.to_le_bytes());
    out.extend_from_slice(&0u64.to_le_bytes());
    out.extend_from_slice(&size.to_le_bytes());
    out
}

fn extended_interrupt_resource(irqs: &[u32], consumer: bool) -> Vec<u8> {
    let payload_len = 2 + core::mem::size_of_val(irqs);
    let mut out = vec![0x89];
    out.extend_from_slice(&(payload_len as u16).to_le_bytes());
    out.push(if consumer { 0x09 } else { 0x01 });
    out.push(irqs.len() as u8);
    for irq in irqs {
        out.extend_from_slice(&irq.to_le_bytes());
    }
    out
}

fn serial_crs_aml(serial: &LoongArchFwCfgSerialConfig) -> Vec<u8> {
    aml_resource_template(&[
        qword_memory_resource(serial.base, serial.size, false, true),
        extended_interrupt_resource(&[serial.irq as u32], true),
    ])
}

fn interrupt_crs_aml(irq: u8, shared: bool) -> Vec<u8> {
    vec![
        0x11,
        0x0e,
        0x0a,
        0x0b,
        0x89,
        0x06,
        0x00,
        0x01,
        if shared { 0x09 } else { 0x01 },
        irq,
        0x00,
        0x00,
        0x00,
        0x79,
        0x00,
    ]
}

fn pci_crs_aml(pci: &LoongArchFwCfgPciConfig) -> Vec<u8> {
    aml_resource_template(&[
        word_bus_number_resource(0, ((pci.ecam_size - 1) >> 20) as u16),
        dword_io_resource(pci.io_base, pci.io_size),
        qword_memory_resource(pci.mmio_base, pci.mmio_size, false, false),
    ])
}

fn pci_res0_crs_aml(pci: &LoongArchFwCfgPciConfig) -> Vec<u8> {
    aml_resource_template(&[qword_memory_resource(
        pci.ecam_base,
        pci.ecam_size,
        false,
        true,
    )])
}