arm_vcpu 0.5.21

OS-neutral AArch64 vCPU core
Documentation
const EXCEPTION_ASSEMBLY: &str = include_str!("architecture/exception.S");
const CONTEXT_FRAME: &str = include_str!("architecture/context_frame.rs");
const VCPU: &str = include_str!("architecture/vcpu.rs");

fn section<'a>(source: &'a str, start: &str, end: &str) -> &'a str {
    source
        .split_once(start)
        .unwrap_or_else(|| panic!("missing section start {start:?}"))
        .1
        .split_once(end)
        .unwrap_or_else(|| panic!("missing section end {end:?}"))
        .0
}

fn assert_in_order(source: &str, operations: &[&str]) {
    let mut cursor = 0;
    for operation in operations {
        let offset = source[cursor..]
            .find(operation)
            .unwrap_or_else(|| panic!("missing ordered operation {operation:?}"));
        cursor += offset + operation.len();
    }
}

#[test]
fn timer_world_switch_keeps_guest_and_host_counter_domains_transactional() {
    let exit = section(
        EXCEPTION_ASSEMBLY,
        ".macro SAVE_VCPU_RUNTIME_FROM_EL1",
        ".endm",
    );
    assert_in_order(
        exit,
        &[
            "mrs     x9, cntv_ctl_el0",
            "mrs     x9, cntv_cval_el0",
            "mrs     x9, cntkctl_el1",
            "msr     cntv_ctl_el0, xzr",
            "isb",
            "msr     cntvoff_el2, xzr",
            "msr     cnthctl_el2, x9",
            "msr     cntkctl_el1, x9",
            "strb    wzr, [sp, {timer_loaded_offset}]",
            "isb",
        ],
    );

    let entry = section(
        EXCEPTION_ASSEMBLY,
        ".Lexception_return_guest_el1:",
        ".Lexception_return_current_el2:",
    );
    assert_in_order(
        entry,
        &[
            "mrs     x9, cnthctl_el2",
            "mrs     x9, cntkctl_el1",
            "msr     cntv_ctl_el0, xzr",
            "isb",
            "msr     cntvoff_el2, x9",
            "msr     cnthctl_el2, x9",
            "msr     cntkctl_el1, x9",
            "msr     cntv_cval_el0, x9",
            "isb",
            "msr     cntv_ctl_el0, x9",
            "strb    w9, [sp, {timer_loaded_offset}]",
            "isb",
            "eret",
        ],
    );

    let lower_irq = section(EXCEPTION_ASSEMBLY, ".macro HANDLE_LOWER_IRQ_VCPU", ".endm");
    assert_in_order(
        lower_irq,
        &[
            "SAVE_REGS_FROM_EL1",
            "ACK_PENDING_HOST_IRQ",
            "SAVE_VCPU_RUNTIME_FROM_EL1",
            "bl      vmexit_trampoline",
        ],
    );

    let acknowledge = section(EXCEPTION_ASSEMBLY, ".macro ACK_PENDING_HOST_IRQ", ".endm");
    assert_in_order(
        acknowledge,
        &[
            "mov     w9, #-1",
            "str     w9, [sp, {host_pending_irq_ack_offset}]",
            "ldr     x9, [sp, {host_irq_interface_offset}]",
            "mrs     x9, ICC_IAR1_EL1",
            "str     w9, [sp, {host_pending_irq_ack_offset}]",
            "dsb     sy",
        ],
    );
    assert_in_order(
        acknowledge,
        &[
            ".Lack_host_irq_gicv2\\@:",
            "ldr     x10, [sp, {host_irq_cpu_interface_base_offset}]",
            "ldr     w9, [x10, #0xc]",
            "str     w9, [sp, {host_pending_irq_ack_offset}]",
            "dsb     sy",
        ],
    );
}

#[test]
fn exception_vector_table_preserves_the_architectural_slot_layout() {
    let vector_table = section(
        EXCEPTION_ASSEMBLY,
        "exception_vector_base_vcpu:",
        ".global context_vm_entry",
    );
    assert_eq!(vector_table.matches("VECTOR_SLOT ").count(), 16);
    assert!(!vector_table.contains("SAVE_REGS_FROM_EL1"));
    assert!(!vector_table.contains("SAVE_VCPU_REGS_FROM_EL1"));

    let slot_macro = section(EXCEPTION_ASSEMBLY, ".macro VECTOR_SLOT", ".endm");
    assert_in_order(slot_macro, &["b       \\handler", ".space  0x80 - 4"]);
}

#[test]
fn tls_switch_occurs_only_inside_the_final_assembly_windows() {
    let restore = section(
        CONTEXT_FRAME,
        "    pub unsafe fn restore(&self)",
        "    }\n}",
    );
    let store = section(
        CONTEXT_FRAME,
        "    pub unsafe fn store(&mut self)",
        "    /// Restores the values",
    );
    assert!(!restore.contains("msr TPIDR_EL0"));
    assert!(!store.contains("mrs {0}, TPIDR_EL0"));

    let exit = section(
        EXCEPTION_ASSEMBLY,
        ".macro SAVE_VCPU_RUNTIME_FROM_EL1",
        ".endm",
    );
    assert_in_order(
        exit,
        &[
            "mrs     x9, tpidr_el0",
            "str     x9, [sp, {guest_tpidr_el0_offset}]",
            "ldr     x9, [sp, {host_tpidr_el0_offset}]",
            "msr     tpidr_el0, x9",
        ],
    );
    assert!(!exit.contains("bl      "));

    let entry = section(
        EXCEPTION_ASSEMBLY,
        ".macro RESTORE_GUEST_REGS_INTO_EL1",
        ".endm",
    );
    assert_in_order(
        entry,
        &[
            "ldr     x9, [sp, {guest_tpidr_el0_offset}]",
            "msr     tpidr_el0, x9",
            "ldp     x8, x9, [sp, 8 * 8]",
        ],
    );
    assert!(!entry.contains("bl      "));
    assert!(VCPU.contains("offset_of!(HostRuntimeContext, tpidr_el0)"));
    assert!(CONTEXT_FRAME.contains("offset_of!(GuestSystemRegisters, tpidr_el0)"));
}