root/tools/testing/selftests/kvm/riscv/ebreak_test.c
// SPDX-License-Identifier: GPL-2.0
/*
 * RISC-V KVM ebreak test.
 *
 * Copyright 2024 Beijing ESWIN Computing Technology Co., Ltd.
 *
 */
#include "kvm_util.h"
#include "ucall_common.h"

#define LABEL_ADDRESS(v) ((uint64_t)&(v))

extern unsigned char sw_bp_1, sw_bp_2;
static uint64_t sw_bp_addr;

static void guest_code(void)
{
        asm volatile(
                ".option push\n"
                ".option norvc\n"
                "sw_bp_1: ebreak\n"
                "sw_bp_2: ebreak\n"
                ".option pop\n"
        );
        GUEST_ASSERT_EQ(READ_ONCE(sw_bp_addr), LABEL_ADDRESS(sw_bp_2));

        GUEST_DONE();
}

static void guest_breakpoint_handler(struct pt_regs *regs)
{
        WRITE_ONCE(sw_bp_addr, regs->epc);
        regs->epc += 4;
}

int main(void)
{
        struct kvm_vm *vm;
        struct kvm_vcpu *vcpu;
        uint64_t pc;
        struct kvm_guest_debug debug = {
                .control = KVM_GUESTDBG_ENABLE,
        };

        TEST_REQUIRE(kvm_has_cap(KVM_CAP_SET_GUEST_DEBUG));

        vm = vm_create_with_one_vcpu(&vcpu, guest_code);

        vm_init_vector_tables(vm);
        vcpu_init_vector_tables(vcpu);
        vm_install_exception_handler(vm, EXC_BREAKPOINT,
                                        guest_breakpoint_handler);

        /*
         * Enable the guest debug.
         * ebreak should exit to the VMM with KVM_EXIT_DEBUG reason.
         */
        vcpu_guest_debug_set(vcpu, &debug);
        vcpu_run(vcpu);

        TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_DEBUG);

        pc = vcpu_get_reg(vcpu, RISCV_CORE_REG(regs.pc));
        TEST_ASSERT_EQ(pc, LABEL_ADDRESS(sw_bp_1));

        /* skip sw_bp_1 */
        vcpu_set_reg(vcpu, RISCV_CORE_REG(regs.pc), pc + 4);

        /*
         * Disable all debug controls.
         * Guest should handle the ebreak without exiting to the VMM.
         */
        memset(&debug, 0, sizeof(debug));
        vcpu_guest_debug_set(vcpu, &debug);

        vcpu_run(vcpu);

        TEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_DONE);

        kvm_vm_free(vm);

        return 0;
}