// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2024 Ventana Micro Systems Inc. */ #include #include #include 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; }