root/tools/testing/selftests/kvm/arm64/hypercalls.c
// SPDX-License-Identifier: GPL-2.0-only

/* hypercalls: Check the ARM64's psuedo-firmware bitmap register interface.
 *
 * The test validates the basic hypercall functionalities that are exposed
 * via the psuedo-firmware bitmap register. This includes the registers'
 * read/write behavior before and after the VM has started, and if the
 * hypercalls are properly masked or unmasked to the guest when disabled or
 * enabled from the KVM userspace, respectively.
 */
#include <errno.h>
#include <linux/arm-smccc.h>
#include <asm/kvm.h>
#include <kvm_util.h>

#include "processor.h"

#define FW_REG_ULIMIT_VAL(max_feat_bit) (GENMASK(max_feat_bit, 0))

/* Last valid bits of the bitmapped firmware registers */
#define KVM_REG_ARM_STD_BMAP_BIT_MAX            0
#define KVM_REG_ARM_STD_HYP_BMAP_BIT_MAX        0
#define KVM_REG_ARM_VENDOR_HYP_BMAP_BIT_MAX     1
#define KVM_REG_ARM_VENDOR_HYP_BMAP_2_BIT_MAX   1

#define KVM_REG_ARM_STD_BMAP_RESET_VAL          FW_REG_ULIMIT_VAL(KVM_REG_ARM_STD_BMAP_BIT_MAX)
#define KVM_REG_ARM_STD_HYP_BMAP_RESET_VAL      FW_REG_ULIMIT_VAL(KVM_REG_ARM_STD_HYP_BMAP_BIT_MAX)
#define KVM_REG_ARM_VENDOR_HYP_BMAP_RESET_VAL   FW_REG_ULIMIT_VAL(KVM_REG_ARM_VENDOR_HYP_BMAP_BIT_MAX)
#define KVM_REG_ARM_VENDOR_HYP_BMAP_2_RESET_VAL 0

struct kvm_fw_reg_info {
        uint64_t reg;           /* Register definition */
        uint64_t max_feat_bit;  /* Bit that represents the upper limit of the feature-map */
        uint64_t reset_val;     /* Reset value for the register */
};

#define FW_REG_INFO(r)                  \
        {                                       \
                .reg = r,                       \
                .max_feat_bit = r##_BIT_MAX,    \
                .reset_val = r##_RESET_VAL      \
        }

static const struct kvm_fw_reg_info fw_reg_info[] = {
        FW_REG_INFO(KVM_REG_ARM_STD_BMAP),
        FW_REG_INFO(KVM_REG_ARM_STD_HYP_BMAP),
        FW_REG_INFO(KVM_REG_ARM_VENDOR_HYP_BMAP),
        FW_REG_INFO(KVM_REG_ARM_VENDOR_HYP_BMAP_2),
};

enum test_stage {
        TEST_STAGE_REG_IFACE,
        TEST_STAGE_HVC_IFACE_FEAT_DISABLED,
        TEST_STAGE_HVC_IFACE_FEAT_ENABLED,
        TEST_STAGE_HVC_IFACE_FALSE_INFO,
        TEST_STAGE_END,
};

static int stage = TEST_STAGE_REG_IFACE;

struct test_hvc_info {
        uint32_t func_id;
        uint64_t arg1;
};

#define TEST_HVC_INFO(f, a1)    \
        {                       \
                .func_id = f,   \
                .arg1 = a1,     \
        }

static const struct test_hvc_info hvc_info[] = {
        /* KVM_REG_ARM_STD_BMAP */
        TEST_HVC_INFO(ARM_SMCCC_TRNG_VERSION, 0),
        TEST_HVC_INFO(ARM_SMCCC_TRNG_FEATURES, ARM_SMCCC_TRNG_RND64),
        TEST_HVC_INFO(ARM_SMCCC_TRNG_GET_UUID, 0),
        TEST_HVC_INFO(ARM_SMCCC_TRNG_RND32, 0),
        TEST_HVC_INFO(ARM_SMCCC_TRNG_RND64, 0),

        /* KVM_REG_ARM_STD_HYP_BMAP */
        TEST_HVC_INFO(ARM_SMCCC_ARCH_FEATURES_FUNC_ID, ARM_SMCCC_HV_PV_TIME_FEATURES),
        TEST_HVC_INFO(ARM_SMCCC_HV_PV_TIME_FEATURES, ARM_SMCCC_HV_PV_TIME_ST),
        TEST_HVC_INFO(ARM_SMCCC_HV_PV_TIME_ST, 0),

        /* KVM_REG_ARM_VENDOR_HYP_BMAP */
        TEST_HVC_INFO(ARM_SMCCC_VENDOR_HYP_KVM_FEATURES_FUNC_ID,
                        ARM_SMCCC_VENDOR_HYP_KVM_PTP_FUNC_ID),
        TEST_HVC_INFO(ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID, 0),
        TEST_HVC_INFO(ARM_SMCCC_VENDOR_HYP_KVM_PTP_FUNC_ID, KVM_PTP_VIRT_COUNTER),
};

/* Feed false hypercall info to test the KVM behavior */
static const struct test_hvc_info false_hvc_info[] = {
        /* Feature support check against a different family of hypercalls */
        TEST_HVC_INFO(ARM_SMCCC_TRNG_FEATURES, ARM_SMCCC_VENDOR_HYP_KVM_PTP_FUNC_ID),
        TEST_HVC_INFO(ARM_SMCCC_ARCH_FEATURES_FUNC_ID, ARM_SMCCC_TRNG_RND64),
        TEST_HVC_INFO(ARM_SMCCC_HV_PV_TIME_FEATURES, ARM_SMCCC_TRNG_RND64),
};

static void guest_test_hvc(const struct test_hvc_info *hc_info)
{
        unsigned int i;
        struct arm_smccc_res res;
        unsigned int hvc_info_arr_sz;

        hvc_info_arr_sz =
        hc_info == hvc_info ? ARRAY_SIZE(hvc_info) : ARRAY_SIZE(false_hvc_info);

        for (i = 0; i < hvc_info_arr_sz; i++, hc_info++) {
                memset(&res, 0, sizeof(res));
                do_smccc(hc_info->func_id, hc_info->arg1, 0, 0, 0, 0, 0, 0, &res);

                switch (stage) {
                case TEST_STAGE_HVC_IFACE_FEAT_DISABLED:
                case TEST_STAGE_HVC_IFACE_FALSE_INFO:
                        __GUEST_ASSERT(res.a0 == SMCCC_RET_NOT_SUPPORTED,
                                       "a0 = 0x%lx, func_id = 0x%x, arg1 = 0x%lx, stage = %u",
                                        res.a0, hc_info->func_id, hc_info->arg1, stage);
                        break;
                case TEST_STAGE_HVC_IFACE_FEAT_ENABLED:
                        __GUEST_ASSERT(res.a0 != SMCCC_RET_NOT_SUPPORTED,
                                       "a0 = 0x%lx, func_id = 0x%x, arg1 = 0x%lx, stage = %u",
                                        res.a0, hc_info->func_id, hc_info->arg1, stage);
                        break;
                default:
                        GUEST_FAIL("Unexpected stage = %u", stage);
                }
        }
}

static void guest_code(void)
{
        while (stage != TEST_STAGE_END) {
                switch (stage) {
                case TEST_STAGE_REG_IFACE:
                        break;
                case TEST_STAGE_HVC_IFACE_FEAT_DISABLED:
                case TEST_STAGE_HVC_IFACE_FEAT_ENABLED:
                        guest_test_hvc(hvc_info);
                        break;
                case TEST_STAGE_HVC_IFACE_FALSE_INFO:
                        guest_test_hvc(false_hvc_info);
                        break;
                default:
                        GUEST_FAIL("Unexpected stage = %u", stage);
                }

                GUEST_SYNC(stage);
        }

        GUEST_DONE();
}

struct st_time {
        uint32_t rev;
        uint32_t attr;
        uint64_t st_time;
};

#define STEAL_TIME_SIZE         ((sizeof(struct st_time) + 63) & ~63)
#define ST_GPA_BASE             (1 << 30)

static void steal_time_init(struct kvm_vcpu *vcpu)
{
        uint64_t st_ipa = (ulong)ST_GPA_BASE;
        unsigned int gpages;

        gpages = vm_calc_num_guest_pages(VM_MODE_DEFAULT, STEAL_TIME_SIZE);
        vm_userspace_mem_region_add(vcpu->vm, VM_MEM_SRC_ANONYMOUS, ST_GPA_BASE, 1, gpages, 0);

        vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PVTIME_CTRL,
                             KVM_ARM_VCPU_PVTIME_IPA, &st_ipa);
}

static void test_fw_regs_before_vm_start(struct kvm_vcpu *vcpu)
{
        uint64_t val;
        unsigned int i;
        int ret;

        for (i = 0; i < ARRAY_SIZE(fw_reg_info); i++) {
                const struct kvm_fw_reg_info *reg_info = &fw_reg_info[i];
                uint64_t set_val;

                /* First 'read' should be the reset value for the reg  */
                val = vcpu_get_reg(vcpu, reg_info->reg);
                TEST_ASSERT(val == reg_info->reset_val,
                        "Unexpected reset value for reg: 0x%lx; expected: 0x%lx; read: 0x%lx",
                        reg_info->reg, reg_info->reset_val, val);

                if (reg_info->reset_val)
                        set_val = 0;
                else
                        set_val = FW_REG_ULIMIT_VAL(reg_info->max_feat_bit);

                ret = __vcpu_set_reg(vcpu, reg_info->reg, set_val);
                TEST_ASSERT(ret == 0,
                        "Failed to %s all the features of reg: 0x%lx; ret: %d",
                        (set_val ? "set" : "clear"), reg_info->reg, errno);

                val = vcpu_get_reg(vcpu, reg_info->reg);
                TEST_ASSERT(val == set_val,
                        "Expected all the features to be %s for reg: 0x%lx",
                        (set_val ? "set" : "cleared"), reg_info->reg);

                /*
                 * If the reg has been set, clear it as test_fw_regs_after_vm_start()
                 * expects it to be cleared.
                 */
                if (set_val) {
                        ret = __vcpu_set_reg(vcpu, reg_info->reg, 0);
                        TEST_ASSERT(ret == 0,
                        "Failed to clear all the features of reg: 0x%lx; ret: %d",
                        reg_info->reg, errno);
                }

                /*
                 * Test enabling a feature that's not supported.
                 * Avoid this check if all the bits are occupied.
                 */
                if (reg_info->max_feat_bit < 63) {
                        ret = __vcpu_set_reg(vcpu, reg_info->reg, BIT(reg_info->max_feat_bit + 1));
                        TEST_ASSERT(ret != 0 && errno == EINVAL,
                        "Unexpected behavior or return value (%d) while setting an unsupported feature for reg: 0x%lx",
                        errno, reg_info->reg);
                }
        }
}

static void test_fw_regs_after_vm_start(struct kvm_vcpu *vcpu)
{
        uint64_t val;
        unsigned int i;
        int ret;

        for (i = 0; i < ARRAY_SIZE(fw_reg_info); i++) {
                const struct kvm_fw_reg_info *reg_info = &fw_reg_info[i];

                /*
                 * Before starting the VM, the test clears all the bits.
                 * Check if that's still the case.
                 */
                val = vcpu_get_reg(vcpu, reg_info->reg);
                TEST_ASSERT(val == 0,
                        "Expected all the features to be cleared for reg: 0x%lx",
                        reg_info->reg);

                /*
                 * Since the VM has run at least once, KVM shouldn't allow modification of
                 * the registers and should return EBUSY. Set the registers and check for
                 * the expected errno.
                 */
                ret = __vcpu_set_reg(vcpu, reg_info->reg, FW_REG_ULIMIT_VAL(reg_info->max_feat_bit));
                TEST_ASSERT(ret != 0 && errno == EBUSY,
                "Unexpected behavior or return value (%d) while setting a feature while VM is running for reg: 0x%lx",
                errno, reg_info->reg);
        }
}

static struct kvm_vm *test_vm_create(struct kvm_vcpu **vcpu)
{
        struct kvm_vm *vm;

        vm = vm_create_with_one_vcpu(vcpu, guest_code);

        steal_time_init(*vcpu);

        return vm;
}

static void test_guest_stage(struct kvm_vm **vm, struct kvm_vcpu **vcpu)
{
        int prev_stage = stage;

        pr_debug("Stage: %d\n", prev_stage);

        /* Sync the stage early, the VM might be freed below. */
        stage++;
        sync_global_to_guest(*vm, stage);

        switch (prev_stage) {
        case TEST_STAGE_REG_IFACE:
                test_fw_regs_after_vm_start(*vcpu);
                break;
        case TEST_STAGE_HVC_IFACE_FEAT_DISABLED:
                /* Start a new VM so that all the features are now enabled by default */
                kvm_vm_free(*vm);
                *vm = test_vm_create(vcpu);
                break;
        case TEST_STAGE_HVC_IFACE_FEAT_ENABLED:
        case TEST_STAGE_HVC_IFACE_FALSE_INFO:
                break;
        default:
                TEST_FAIL("Unknown test stage: %d", prev_stage);
        }
}

static void test_run(void)
{
        struct kvm_vcpu *vcpu;
        struct kvm_vm *vm;
        struct ucall uc;
        bool guest_done = false;

        vm = test_vm_create(&vcpu);

        test_fw_regs_before_vm_start(vcpu);

        while (!guest_done) {
                vcpu_run(vcpu);

                switch (get_ucall(vcpu, &uc)) {
                case UCALL_SYNC:
                        test_guest_stage(&vm, &vcpu);
                        break;
                case UCALL_DONE:
                        guest_done = true;
                        break;
                case UCALL_ABORT:
                        REPORT_GUEST_ASSERT(uc);
                        break;
                default:
                        TEST_FAIL("Unexpected guest exit");
                }
        }

        kvm_vm_free(vm);
}

int main(void)
{
        test_run();
        return 0;
}