279 lines
5.8 KiB
C
279 lines
5.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2025 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com/
|
|
*/
|
|
|
|
#include <linux/gfp.h>
|
|
|
|
#include "exynos-hvc.h"
|
|
#include "hvm_drv.h"
|
|
|
|
static int hvm_gfn_to_hva_memslot(struct hvm_memslot *memslot,
|
|
u64 gfn,
|
|
u64 *hva_memslot)
|
|
{
|
|
u64 offset;
|
|
|
|
if (gfn < memslot->base_gfn)
|
|
return -EINVAL;
|
|
|
|
offset = gfn - memslot->base_gfn;
|
|
*hva_memslot = memslot->userspace_addr + offset * PAGE_SIZE;
|
|
return 0;
|
|
}
|
|
|
|
static int cmp_ppages(struct rb_node *node, const struct rb_node *parent)
|
|
{
|
|
struct hvm_pinned_page *a = container_of(node,
|
|
struct hvm_pinned_page,
|
|
node);
|
|
struct hvm_pinned_page *b = container_of(parent,
|
|
struct hvm_pinned_page,
|
|
node);
|
|
|
|
if (a->ipa < b->ipa)
|
|
return -1;
|
|
if (a->ipa > b->ipa)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Invoker of this function is responsible for locking */
|
|
static int hvm_insert_ppage(struct hvm *vm, struct hvm_pinned_page *ppage)
|
|
{
|
|
if (rb_find_add(&ppage->node, &vm->pinned_pages, cmp_ppages))
|
|
return -EEXIST;
|
|
return 0;
|
|
}
|
|
|
|
static int pin_one_page(struct hvm *vm, unsigned long hva, u64 gpa,
|
|
struct page **out_page)
|
|
{
|
|
u32 flags = FOLL_HWPOISON | FOLL_LONGTERM | FOLL_WRITE;
|
|
struct hvm_pinned_page *ppage = NULL;
|
|
struct mm_struct *mm = current->mm;
|
|
struct page *page = NULL;
|
|
int ret;
|
|
|
|
ppage = kmalloc(sizeof(*ppage), GFP_KERNEL_ACCOUNT);
|
|
if (!ppage)
|
|
return -ENOMEM;
|
|
|
|
mmap_read_lock(mm);
|
|
ret = pin_user_pages(hva, 1, flags, &page);
|
|
mmap_read_unlock(mm);
|
|
|
|
if (ret != 1 || !page) {
|
|
kfree(ppage);
|
|
return -EFAULT;
|
|
}
|
|
|
|
ppage->page = page;
|
|
ppage->ipa = gpa;
|
|
|
|
mutex_lock(&vm->mem_lock);
|
|
ret = hvm_insert_ppage(vm, ppage);
|
|
|
|
/**
|
|
* The return of -EEXIST from hvm_insert_ppage is considered an
|
|
* expected behavior in this context.
|
|
* This situation arises when two or more VCPUs are concurrently
|
|
* engaged in demand paging handling. The initial VCPU has already
|
|
* allocated and pinned a page, while the subsequent VCPU attempts
|
|
* to pin the same page again. As a result, we prompt the unpinning
|
|
* and release of the allocated structure, followed by a return -EAGAIN.
|
|
*/
|
|
if (ret == -EEXIST) {
|
|
kfree(ppage);
|
|
unpin_user_pages(&page, 1);
|
|
mutex_unlock(&vm->mem_lock);
|
|
return -EAGAIN;
|
|
}
|
|
mutex_unlock(&vm->mem_lock);
|
|
*out_page = page;
|
|
|
|
return ret;
|
|
}
|
|
|
|
void *hvm_alloc_pages(size_t size)
|
|
{
|
|
return alloc_pages_exact(size, GFP_KERNEL | __GFP_ZERO);
|
|
}
|
|
|
|
void hvm_free_pages(void *virt, size_t size)
|
|
{
|
|
free_pages_exact(virt, size);
|
|
}
|
|
|
|
static int hvm_register_vm_pgtable(struct hvm *hvm)
|
|
{
|
|
unsigned long *buf;
|
|
unsigned long buf_pa;
|
|
size_t size = PAGE_SIZE * 2;
|
|
unsigned long ret;
|
|
|
|
buf = hvm_alloc_pages(size);
|
|
if (buf == NULL)
|
|
return -ENOMEM;
|
|
|
|
buf_pa = virt_to_phys(buf);
|
|
|
|
ret = exynos_hvc(HVC_FID_HVM_REGISTER_VM_PGTABLE,
|
|
hvm->vm_id, buf_pa, size, 0);
|
|
if (ret) {
|
|
hvm_free_pages(buf, size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
hvm->vm_pgtable[hvm->pgtable_num++] = buf;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int hvm_init_vm_stage2_mmu(struct hvm *hvm)
|
|
{
|
|
unsigned long *buf;
|
|
unsigned long buf_pa;
|
|
unsigned long ret;
|
|
u16 vm_id = hvm->vm_id;
|
|
|
|
buf = hvm_alloc_pages(PAGE_SIZE);
|
|
if (buf == NULL)
|
|
return -ENOMEM;
|
|
|
|
buf_pa = virt_to_phys(buf);
|
|
|
|
ret = exynos_hvc(HVC_FID_HVM_INIT_STAGE2_MMU,
|
|
vm_id, buf_pa, PAGE_SIZE, 0);
|
|
if (ret) {
|
|
hvm_free_pages(buf, PAGE_SIZE);
|
|
return -EINVAL;
|
|
}
|
|
|
|
hvm->s2table_buf = buf;
|
|
hvm->pgtable_num = 0;
|
|
|
|
return hvm_register_vm_pgtable(hvm);
|
|
}
|
|
|
|
static int hvm_vm_allocate_guest_page(struct hvm *vm, struct hvm_memslot *slot,
|
|
u64 gfn, u64 *pfn)
|
|
{
|
|
struct page *page = NULL;
|
|
unsigned long hva;
|
|
int ret;
|
|
|
|
if (hvm_gfn_to_hva_memslot(slot, gfn, (u64 *)&hva) != 0)
|
|
return -EINVAL;
|
|
|
|
ret = pin_one_page(vm, hva, PFN_PHYS(gfn), &page);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
if (page == NULL)
|
|
return -EFAULT;
|
|
/**
|
|
* As `pin_user_pages` already gets the page struct, we don't need to
|
|
* call other APIs to reduce function call overhead.
|
|
*/
|
|
*pfn = page_to_pfn(page);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hvm_alloc_vm_pgtable(struct hvm *hvm)
|
|
{
|
|
if (!exynos_hvc(HVC_FID_HVM_NEED_VM_PGTABLE,
|
|
hvm->vm_id, 0, 0, 0))
|
|
return 0;
|
|
|
|
return hvm_register_vm_pgtable(hvm);
|
|
}
|
|
|
|
static int hvm_map_guest(struct hvm *hvm, int memslot_id,
|
|
u64 pfn, u64 gfn, u64 nr_pages)
|
|
{
|
|
unsigned long ret;
|
|
u64 ids;
|
|
|
|
ret = hvm_alloc_vm_pgtable(hvm);
|
|
if (ret)
|
|
return (int)ret;
|
|
|
|
ids = (u32)memslot_id;
|
|
ids = (ids << 32) | hvm->vm_id;
|
|
|
|
ret = exynos_hvc(HVC_FID_HVM_MAP_GUEST, ids, pfn, gfn, nr_pages);
|
|
|
|
return (int)ret;
|
|
}
|
|
|
|
static int handle_demand_page(struct hvm *vm, int memslot_id, u64 gfn)
|
|
{
|
|
int ret;
|
|
u64 pfn;
|
|
|
|
ret = hvm_vm_allocate_guest_page(vm, &vm->memslot[memslot_id], gfn, &pfn);
|
|
if (unlikely(ret)) {
|
|
if (ret == -EAGAIN)
|
|
return 0;
|
|
else
|
|
return -EFAULT;
|
|
}
|
|
|
|
ret = hvm_map_guest(vm, memslot_id, pfn, gfn, 1);
|
|
if (unlikely(ret))
|
|
return -EFAULT;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* hvm_find_memslot() - Find memslot containing this @gpa
|
|
* @vm: Pointer to struct hvm
|
|
* @gfn: Guest frame number
|
|
*
|
|
* Return:
|
|
* * >=0 - Index of memslot
|
|
* * -EFAULT - Not found
|
|
*/
|
|
static int hvm_find_memslot(struct hvm *vm, u64 gfn)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < HVM_MAX_MEM_REGION; i++) {
|
|
if (vm->memslot[i].npages == 0)
|
|
continue;
|
|
|
|
if (gfn >= vm->memslot[i].base_gfn &&
|
|
gfn < vm->memslot[i].base_gfn + vm->memslot[i].npages)
|
|
return i;
|
|
}
|
|
|
|
return -EFAULT;
|
|
}
|
|
|
|
/**
|
|
* hvm_handle_page_fault() - Handle guest page fault, find corresponding page
|
|
* for the faulting gpa
|
|
* @vcpu: Pointer to struct hvm_vcpu_run of the faulting vcpu
|
|
*
|
|
* Return:
|
|
* * 0 - Success to handle guest page fault
|
|
* * -EFAULT - Failed to map phys addr to guest's GPA
|
|
*/
|
|
int hvm_handle_page_fault(struct hvm_vcpu *vcpu)
|
|
{
|
|
struct hvm *vm = vcpu->hvm;
|
|
int memslot_id;
|
|
u64 gfn;
|
|
|
|
gfn = PHYS_PFN(vcpu->run->exception.fault_gpa);
|
|
memslot_id = hvm_find_memslot(vm, gfn);
|
|
if (unlikely(memslot_id < 0))
|
|
return -EFAULT;
|
|
|
|
return handle_demand_page(vm, memslot_id, gfn);
|
|
}
|