root/arch/riscv/kvm/nacl.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2024 Ventana Micro Systems Inc.
 */

#include <linux/kvm_host.h>
#include <linux/vmalloc.h>
#include <asm/kvm_nacl.h>

DEFINE_STATIC_KEY_FALSE(kvm_riscv_nacl_available);
DEFINE_STATIC_KEY_FALSE(kvm_riscv_nacl_sync_csr_available);
DEFINE_STATIC_KEY_FALSE(kvm_riscv_nacl_sync_hfence_available);
DEFINE_STATIC_KEY_FALSE(kvm_riscv_nacl_sync_sret_available);
DEFINE_STATIC_KEY_FALSE(kvm_riscv_nacl_autoswap_csr_available);
DEFINE_PER_CPU(struct kvm_riscv_nacl, kvm_riscv_nacl);

void __kvm_riscv_nacl_hfence(void *shmem,
                             unsigned long control,
                             unsigned long page_num,
                             unsigned long page_count)
{
        int i, ent = -1, try_count = 5;
        unsigned long *entp;

again:
        for (i = 0; i < SBI_NACL_SHMEM_HFENCE_ENTRY_MAX; i++) {
                entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_CONFIG(i);
                if (lelong_to_cpu(*entp) & SBI_NACL_SHMEM_HFENCE_CONFIG_PEND)
                        continue;

                ent = i;
                break;
        }

        if (ent < 0) {
                if (try_count) {
                        nacl_sync_hfence(-1UL);
                        goto again;
                } else {
                        pr_warn("KVM: No free entry in NACL shared memory\n");
                        return;
                }
        }

        entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_CONFIG(i);
        *entp = cpu_to_lelong(control);
        entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_PNUM(i);
        *entp = cpu_to_lelong(page_num);
        entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_PCOUNT(i);
        *entp = cpu_to_lelong(page_count);
}

int kvm_riscv_nacl_enable(void)
{
        int rc;
        struct sbiret ret;
        struct kvm_riscv_nacl *nacl;

        if (!kvm_riscv_nacl_available())
                return 0;
        nacl = this_cpu_ptr(&kvm_riscv_nacl);

        ret = sbi_ecall(SBI_EXT_NACL, SBI_EXT_NACL_SET_SHMEM,
                        nacl->shmem_phys, 0, 0, 0, 0, 0);
        rc = sbi_err_map_linux_errno(ret.error);
        if (rc)
                return rc;

        return 0;
}

void kvm_riscv_nacl_disable(void)
{
        if (!kvm_riscv_nacl_available())
                return;

        sbi_ecall(SBI_EXT_NACL, SBI_EXT_NACL_SET_SHMEM,
                  SBI_SHMEM_DISABLE, SBI_SHMEM_DISABLE, 0, 0, 0, 0);
}

void kvm_riscv_nacl_exit(void)
{
        int cpu;
        struct kvm_riscv_nacl *nacl;

        if (!kvm_riscv_nacl_available())
                return;

        /* Allocate per-CPU shared memory */
        for_each_possible_cpu(cpu) {
                nacl = per_cpu_ptr(&kvm_riscv_nacl, cpu);
                if (!nacl->shmem)
                        continue;

                free_pages((unsigned long)nacl->shmem,
                           get_order(SBI_NACL_SHMEM_SIZE));
                nacl->shmem = NULL;
                nacl->shmem_phys = 0;
        }
}

static long nacl_probe_feature(long feature_id)
{
        struct sbiret ret;

        if (!kvm_riscv_nacl_available())
                return 0;

        ret = sbi_ecall(SBI_EXT_NACL, SBI_EXT_NACL_PROBE_FEATURE,
                        feature_id, 0, 0, 0, 0, 0);
        return ret.value;
}

int kvm_riscv_nacl_init(void)
{
        int cpu;
        struct page *shmem_page;
        struct kvm_riscv_nacl *nacl;

        if (sbi_spec_version < sbi_mk_version(1, 0) ||
            sbi_probe_extension(SBI_EXT_NACL) <= 0)
                return -ENODEV;

        /* Enable NACL support */
        static_branch_enable(&kvm_riscv_nacl_available);

        /* Probe NACL features */
        if (nacl_probe_feature(SBI_NACL_FEAT_SYNC_CSR))
                static_branch_enable(&kvm_riscv_nacl_sync_csr_available);
        if (nacl_probe_feature(SBI_NACL_FEAT_SYNC_HFENCE))
                static_branch_enable(&kvm_riscv_nacl_sync_hfence_available);
        if (nacl_probe_feature(SBI_NACL_FEAT_SYNC_SRET))
                static_branch_enable(&kvm_riscv_nacl_sync_sret_available);
        if (nacl_probe_feature(SBI_NACL_FEAT_AUTOSWAP_CSR))
                static_branch_enable(&kvm_riscv_nacl_autoswap_csr_available);

        /* Allocate per-CPU shared memory */
        for_each_possible_cpu(cpu) {
                nacl = per_cpu_ptr(&kvm_riscv_nacl, cpu);

                shmem_page = alloc_pages(GFP_KERNEL | __GFP_ZERO,
                                         get_order(SBI_NACL_SHMEM_SIZE));
                if (!shmem_page) {
                        kvm_riscv_nacl_exit();
                        return -ENOMEM;
                }
                nacl->shmem = page_to_virt(shmem_page);
                nacl->shmem_phys = page_to_phys(shmem_page);
        }

        return 0;
}