162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2020 Google LLC 462306a36Sopenharmony_ci * Author: Quentin Perret <qperret@google.com> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/kvm_host.h> 862306a36Sopenharmony_ci#include <asm/kvm_hyp.h> 962306a36Sopenharmony_ci#include <asm/kvm_mmu.h> 1062306a36Sopenharmony_ci#include <asm/kvm_pgtable.h> 1162306a36Sopenharmony_ci#include <asm/kvm_pkvm.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <nvhe/early_alloc.h> 1462306a36Sopenharmony_ci#include <nvhe/ffa.h> 1562306a36Sopenharmony_ci#include <nvhe/fixed_config.h> 1662306a36Sopenharmony_ci#include <nvhe/gfp.h> 1762306a36Sopenharmony_ci#include <nvhe/memory.h> 1862306a36Sopenharmony_ci#include <nvhe/mem_protect.h> 1962306a36Sopenharmony_ci#include <nvhe/mm.h> 2062306a36Sopenharmony_ci#include <nvhe/pkvm.h> 2162306a36Sopenharmony_ci#include <nvhe/trap_handler.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ciunsigned long hyp_nr_cpus; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define hyp_percpu_size ((unsigned long)__per_cpu_end - \ 2662306a36Sopenharmony_ci (unsigned long)__per_cpu_start) 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic void *vmemmap_base; 2962306a36Sopenharmony_cistatic void *vm_table_base; 3062306a36Sopenharmony_cistatic void *hyp_pgt_base; 3162306a36Sopenharmony_cistatic void *host_s2_pgt_base; 3262306a36Sopenharmony_cistatic void *ffa_proxy_pages; 3362306a36Sopenharmony_cistatic struct kvm_pgtable_mm_ops pkvm_pgtable_mm_ops; 3462306a36Sopenharmony_cistatic struct hyp_pool hpool; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistatic int divide_memory_pool(void *virt, unsigned long size) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci unsigned long nr_pages; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci hyp_early_alloc_init(virt, size); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci nr_pages = hyp_vmemmap_pages(sizeof(struct hyp_page)); 4362306a36Sopenharmony_ci vmemmap_base = hyp_early_alloc_contig(nr_pages); 4462306a36Sopenharmony_ci if (!vmemmap_base) 4562306a36Sopenharmony_ci return -ENOMEM; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci nr_pages = hyp_vm_table_pages(); 4862306a36Sopenharmony_ci vm_table_base = hyp_early_alloc_contig(nr_pages); 4962306a36Sopenharmony_ci if (!vm_table_base) 5062306a36Sopenharmony_ci return -ENOMEM; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci nr_pages = hyp_s1_pgtable_pages(); 5362306a36Sopenharmony_ci hyp_pgt_base = hyp_early_alloc_contig(nr_pages); 5462306a36Sopenharmony_ci if (!hyp_pgt_base) 5562306a36Sopenharmony_ci return -ENOMEM; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci nr_pages = host_s2_pgtable_pages(); 5862306a36Sopenharmony_ci host_s2_pgt_base = hyp_early_alloc_contig(nr_pages); 5962306a36Sopenharmony_ci if (!host_s2_pgt_base) 6062306a36Sopenharmony_ci return -ENOMEM; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci nr_pages = hyp_ffa_proxy_pages(); 6362306a36Sopenharmony_ci ffa_proxy_pages = hyp_early_alloc_contig(nr_pages); 6462306a36Sopenharmony_ci if (!ffa_proxy_pages) 6562306a36Sopenharmony_ci return -ENOMEM; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci return 0; 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic int recreate_hyp_mappings(phys_addr_t phys, unsigned long size, 7162306a36Sopenharmony_ci unsigned long *per_cpu_base, 7262306a36Sopenharmony_ci u32 hyp_va_bits) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci void *start, *end, *virt = hyp_phys_to_virt(phys); 7562306a36Sopenharmony_ci unsigned long pgt_size = hyp_s1_pgtable_pages() << PAGE_SHIFT; 7662306a36Sopenharmony_ci enum kvm_pgtable_prot prot; 7762306a36Sopenharmony_ci int ret, i; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci /* Recreate the hyp page-table using the early page allocator */ 8062306a36Sopenharmony_ci hyp_early_alloc_init(hyp_pgt_base, pgt_size); 8162306a36Sopenharmony_ci ret = kvm_pgtable_hyp_init(&pkvm_pgtable, hyp_va_bits, 8262306a36Sopenharmony_ci &hyp_early_alloc_mm_ops); 8362306a36Sopenharmony_ci if (ret) 8462306a36Sopenharmony_ci return ret; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci ret = hyp_create_idmap(hyp_va_bits); 8762306a36Sopenharmony_ci if (ret) 8862306a36Sopenharmony_ci return ret; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci ret = hyp_map_vectors(); 9162306a36Sopenharmony_ci if (ret) 9262306a36Sopenharmony_ci return ret; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci ret = hyp_back_vmemmap(hyp_virt_to_phys(vmemmap_base)); 9562306a36Sopenharmony_ci if (ret) 9662306a36Sopenharmony_ci return ret; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci ret = pkvm_create_mappings(__hyp_text_start, __hyp_text_end, PAGE_HYP_EXEC); 9962306a36Sopenharmony_ci if (ret) 10062306a36Sopenharmony_ci return ret; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci ret = pkvm_create_mappings(__hyp_rodata_start, __hyp_rodata_end, PAGE_HYP_RO); 10362306a36Sopenharmony_ci if (ret) 10462306a36Sopenharmony_ci return ret; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci ret = pkvm_create_mappings(__hyp_bss_start, __hyp_bss_end, PAGE_HYP); 10762306a36Sopenharmony_ci if (ret) 10862306a36Sopenharmony_ci return ret; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci ret = pkvm_create_mappings(virt, virt + size, PAGE_HYP); 11162306a36Sopenharmony_ci if (ret) 11262306a36Sopenharmony_ci return ret; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci for (i = 0; i < hyp_nr_cpus; i++) { 11562306a36Sopenharmony_ci struct kvm_nvhe_init_params *params = per_cpu_ptr(&kvm_init_params, i); 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci start = (void *)kern_hyp_va(per_cpu_base[i]); 11862306a36Sopenharmony_ci end = start + PAGE_ALIGN(hyp_percpu_size); 11962306a36Sopenharmony_ci ret = pkvm_create_mappings(start, end, PAGE_HYP); 12062306a36Sopenharmony_ci if (ret) 12162306a36Sopenharmony_ci return ret; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci ret = pkvm_create_stack(params->stack_pa, ¶ms->stack_hyp_va); 12462306a36Sopenharmony_ci if (ret) 12562306a36Sopenharmony_ci return ret; 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci /* 12962306a36Sopenharmony_ci * Map the host sections RO in the hypervisor, but transfer the 13062306a36Sopenharmony_ci * ownership from the host to the hypervisor itself to make sure they 13162306a36Sopenharmony_ci * can't be donated or shared with another entity. 13262306a36Sopenharmony_ci * 13362306a36Sopenharmony_ci * The ownership transition requires matching changes in the host 13462306a36Sopenharmony_ci * stage-2. This will be done later (see finalize_host_mappings()) once 13562306a36Sopenharmony_ci * the hyp_vmemmap is addressable. 13662306a36Sopenharmony_ci */ 13762306a36Sopenharmony_ci prot = pkvm_mkstate(PAGE_HYP_RO, PKVM_PAGE_SHARED_OWNED); 13862306a36Sopenharmony_ci ret = pkvm_create_mappings(&kvm_vgic_global_state, 13962306a36Sopenharmony_ci &kvm_vgic_global_state + 1, prot); 14062306a36Sopenharmony_ci if (ret) 14162306a36Sopenharmony_ci return ret; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci return 0; 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic void update_nvhe_init_params(void) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci struct kvm_nvhe_init_params *params; 14962306a36Sopenharmony_ci unsigned long i; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci for (i = 0; i < hyp_nr_cpus; i++) { 15262306a36Sopenharmony_ci params = per_cpu_ptr(&kvm_init_params, i); 15362306a36Sopenharmony_ci params->pgd_pa = __hyp_pa(pkvm_pgtable.pgd); 15462306a36Sopenharmony_ci dcache_clean_inval_poc((unsigned long)params, 15562306a36Sopenharmony_ci (unsigned long)params + sizeof(*params)); 15662306a36Sopenharmony_ci } 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_cistatic void *hyp_zalloc_hyp_page(void *arg) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci return hyp_alloc_pages(&hpool, 0); 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic void hpool_get_page(void *addr) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci hyp_get_page(&hpool, addr); 16762306a36Sopenharmony_ci} 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_cistatic void hpool_put_page(void *addr) 17062306a36Sopenharmony_ci{ 17162306a36Sopenharmony_ci hyp_put_page(&hpool, addr); 17262306a36Sopenharmony_ci} 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cistatic int fix_host_ownership_walker(const struct kvm_pgtable_visit_ctx *ctx, 17562306a36Sopenharmony_ci enum kvm_pgtable_walk_flags visit) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci enum kvm_pgtable_prot prot; 17862306a36Sopenharmony_ci enum pkvm_page_state state; 17962306a36Sopenharmony_ci phys_addr_t phys; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (!kvm_pte_valid(ctx->old)) 18262306a36Sopenharmony_ci return 0; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci if (ctx->level != (KVM_PGTABLE_MAX_LEVELS - 1)) 18562306a36Sopenharmony_ci return -EINVAL; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci phys = kvm_pte_to_phys(ctx->old); 18862306a36Sopenharmony_ci if (!addr_is_memory(phys)) 18962306a36Sopenharmony_ci return -EINVAL; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci /* 19262306a36Sopenharmony_ci * Adjust the host stage-2 mappings to match the ownership attributes 19362306a36Sopenharmony_ci * configured in the hypervisor stage-1. 19462306a36Sopenharmony_ci */ 19562306a36Sopenharmony_ci state = pkvm_getstate(kvm_pgtable_hyp_pte_prot(ctx->old)); 19662306a36Sopenharmony_ci switch (state) { 19762306a36Sopenharmony_ci case PKVM_PAGE_OWNED: 19862306a36Sopenharmony_ci return host_stage2_set_owner_locked(phys, PAGE_SIZE, PKVM_ID_HYP); 19962306a36Sopenharmony_ci case PKVM_PAGE_SHARED_OWNED: 20062306a36Sopenharmony_ci prot = pkvm_mkstate(PKVM_HOST_MEM_PROT, PKVM_PAGE_SHARED_BORROWED); 20162306a36Sopenharmony_ci break; 20262306a36Sopenharmony_ci case PKVM_PAGE_SHARED_BORROWED: 20362306a36Sopenharmony_ci prot = pkvm_mkstate(PKVM_HOST_MEM_PROT, PKVM_PAGE_SHARED_OWNED); 20462306a36Sopenharmony_ci break; 20562306a36Sopenharmony_ci default: 20662306a36Sopenharmony_ci return -EINVAL; 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci return host_stage2_idmap_locked(phys, PAGE_SIZE, prot); 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic int fix_hyp_pgtable_refcnt_walker(const struct kvm_pgtable_visit_ctx *ctx, 21362306a36Sopenharmony_ci enum kvm_pgtable_walk_flags visit) 21462306a36Sopenharmony_ci{ 21562306a36Sopenharmony_ci /* 21662306a36Sopenharmony_ci * Fix-up the refcount for the page-table pages as the early allocator 21762306a36Sopenharmony_ci * was unable to access the hyp_vmemmap and so the buddy allocator has 21862306a36Sopenharmony_ci * initialised the refcount to '1'. 21962306a36Sopenharmony_ci */ 22062306a36Sopenharmony_ci if (kvm_pte_valid(ctx->old)) 22162306a36Sopenharmony_ci ctx->mm_ops->get_page(ctx->ptep); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci return 0; 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_cistatic int fix_host_ownership(void) 22762306a36Sopenharmony_ci{ 22862306a36Sopenharmony_ci struct kvm_pgtable_walker walker = { 22962306a36Sopenharmony_ci .cb = fix_host_ownership_walker, 23062306a36Sopenharmony_ci .flags = KVM_PGTABLE_WALK_LEAF, 23162306a36Sopenharmony_ci }; 23262306a36Sopenharmony_ci int i, ret; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci for (i = 0; i < hyp_memblock_nr; i++) { 23562306a36Sopenharmony_ci struct memblock_region *reg = &hyp_memory[i]; 23662306a36Sopenharmony_ci u64 start = (u64)hyp_phys_to_virt(reg->base); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci ret = kvm_pgtable_walk(&pkvm_pgtable, start, reg->size, &walker); 23962306a36Sopenharmony_ci if (ret) 24062306a36Sopenharmony_ci return ret; 24162306a36Sopenharmony_ci } 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci return 0; 24462306a36Sopenharmony_ci} 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_cistatic int fix_hyp_pgtable_refcnt(void) 24762306a36Sopenharmony_ci{ 24862306a36Sopenharmony_ci struct kvm_pgtable_walker walker = { 24962306a36Sopenharmony_ci .cb = fix_hyp_pgtable_refcnt_walker, 25062306a36Sopenharmony_ci .flags = KVM_PGTABLE_WALK_LEAF | KVM_PGTABLE_WALK_TABLE_POST, 25162306a36Sopenharmony_ci .arg = pkvm_pgtable.mm_ops, 25262306a36Sopenharmony_ci }; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci return kvm_pgtable_walk(&pkvm_pgtable, 0, BIT(pkvm_pgtable.ia_bits), 25562306a36Sopenharmony_ci &walker); 25662306a36Sopenharmony_ci} 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_civoid __noreturn __pkvm_init_finalise(void) 25962306a36Sopenharmony_ci{ 26062306a36Sopenharmony_ci struct kvm_host_data *host_data = this_cpu_ptr(&kvm_host_data); 26162306a36Sopenharmony_ci struct kvm_cpu_context *host_ctxt = &host_data->host_ctxt; 26262306a36Sopenharmony_ci unsigned long nr_pages, reserved_pages, pfn; 26362306a36Sopenharmony_ci int ret; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci /* Now that the vmemmap is backed, install the full-fledged allocator */ 26662306a36Sopenharmony_ci pfn = hyp_virt_to_pfn(hyp_pgt_base); 26762306a36Sopenharmony_ci nr_pages = hyp_s1_pgtable_pages(); 26862306a36Sopenharmony_ci reserved_pages = hyp_early_alloc_nr_used_pages(); 26962306a36Sopenharmony_ci ret = hyp_pool_init(&hpool, pfn, nr_pages, reserved_pages); 27062306a36Sopenharmony_ci if (ret) 27162306a36Sopenharmony_ci goto out; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci ret = kvm_host_prepare_stage2(host_s2_pgt_base); 27462306a36Sopenharmony_ci if (ret) 27562306a36Sopenharmony_ci goto out; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci pkvm_pgtable_mm_ops = (struct kvm_pgtable_mm_ops) { 27862306a36Sopenharmony_ci .zalloc_page = hyp_zalloc_hyp_page, 27962306a36Sopenharmony_ci .phys_to_virt = hyp_phys_to_virt, 28062306a36Sopenharmony_ci .virt_to_phys = hyp_virt_to_phys, 28162306a36Sopenharmony_ci .get_page = hpool_get_page, 28262306a36Sopenharmony_ci .put_page = hpool_put_page, 28362306a36Sopenharmony_ci .page_count = hyp_page_count, 28462306a36Sopenharmony_ci }; 28562306a36Sopenharmony_ci pkvm_pgtable.mm_ops = &pkvm_pgtable_mm_ops; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci ret = fix_host_ownership(); 28862306a36Sopenharmony_ci if (ret) 28962306a36Sopenharmony_ci goto out; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci ret = fix_hyp_pgtable_refcnt(); 29262306a36Sopenharmony_ci if (ret) 29362306a36Sopenharmony_ci goto out; 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci ret = hyp_create_pcpu_fixmap(); 29662306a36Sopenharmony_ci if (ret) 29762306a36Sopenharmony_ci goto out; 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci ret = hyp_ffa_init(ffa_proxy_pages); 30062306a36Sopenharmony_ci if (ret) 30162306a36Sopenharmony_ci goto out; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci pkvm_hyp_vm_table_init(vm_table_base); 30462306a36Sopenharmony_ciout: 30562306a36Sopenharmony_ci /* 30662306a36Sopenharmony_ci * We tail-called to here from handle___pkvm_init() and will not return, 30762306a36Sopenharmony_ci * so make sure to propagate the return value to the host. 30862306a36Sopenharmony_ci */ 30962306a36Sopenharmony_ci cpu_reg(host_ctxt, 1) = ret; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci __host_enter(host_ctxt); 31262306a36Sopenharmony_ci} 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ciint __pkvm_init(phys_addr_t phys, unsigned long size, unsigned long nr_cpus, 31562306a36Sopenharmony_ci unsigned long *per_cpu_base, u32 hyp_va_bits) 31662306a36Sopenharmony_ci{ 31762306a36Sopenharmony_ci struct kvm_nvhe_init_params *params; 31862306a36Sopenharmony_ci void *virt = hyp_phys_to_virt(phys); 31962306a36Sopenharmony_ci void (*fn)(phys_addr_t params_pa, void *finalize_fn_va); 32062306a36Sopenharmony_ci int ret; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci BUG_ON(kvm_check_pvm_sysreg_table()); 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci if (!PAGE_ALIGNED(phys) || !PAGE_ALIGNED(size)) 32562306a36Sopenharmony_ci return -EINVAL; 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci hyp_spin_lock_init(&pkvm_pgd_lock); 32862306a36Sopenharmony_ci hyp_nr_cpus = nr_cpus; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci ret = divide_memory_pool(virt, size); 33162306a36Sopenharmony_ci if (ret) 33262306a36Sopenharmony_ci return ret; 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci ret = recreate_hyp_mappings(phys, size, per_cpu_base, hyp_va_bits); 33562306a36Sopenharmony_ci if (ret) 33662306a36Sopenharmony_ci return ret; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci update_nvhe_init_params(); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci /* Jump in the idmap page to switch to the new page-tables */ 34162306a36Sopenharmony_ci params = this_cpu_ptr(&kvm_init_params); 34262306a36Sopenharmony_ci fn = (typeof(fn))__hyp_pa(__pkvm_init_switch_pgd); 34362306a36Sopenharmony_ci fn(__hyp_pa(params), __pkvm_init_finalise); 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci unreachable(); 34662306a36Sopenharmony_ci} 347