|  | // 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; | 
|  | } |