| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2023 MediaTek Inc. |
| */ |
| |
| #include <linux/gzvm_drv.h> |
| |
| /** |
| * hva_to_pa_fast() - converts hva to pa in generic fast way |
| * @hva: Host virtual address. |
| * |
| * Return: GZVM_PA_ERR_BAD for translation error |
| */ |
| u64 hva_to_pa_fast(u64 hva) |
| { |
| struct page *page[1]; |
| u64 pfn; |
| |
| if (get_user_page_fast_only(hva, 0, page)) { |
| pfn = page_to_phys(page[0]); |
| put_page((struct page *)page); |
| return pfn; |
| } |
| return GZVM_PA_ERR_BAD; |
| } |
| |
| /** |
| * hva_to_pa_slow() - converts hva to pa in a slow way |
| * @hva: Host virtual address |
| * |
| * This function converts HVA to PA in a slow way because the target hva is not |
| * yet allocated and mapped in the host stage1 page table, we cannot find it |
| * directly from current page table. |
| * Thus, we have to allocate it and this operation is much slower than directly |
| * find via current page table. |
| * |
| * Context: This function may sleep |
| * Return: PA or GZVM_PA_ERR_BAD for translation error |
| */ |
| u64 hva_to_pa_slow(u64 hva) |
| { |
| struct page *page = NULL; |
| u64 pfn = 0; |
| int npages; |
| |
| npages = get_user_pages_unlocked(hva, 1, &page, 0); |
| if (npages != 1) |
| return GZVM_PA_ERR_BAD; |
| |
| if (page) { |
| pfn = page_to_phys(page); |
| put_page(page); |
| return pfn; |
| } |
| |
| return GZVM_PA_ERR_BAD; |
| } |
| |
| static u64 __gzvm_gfn_to_pfn_memslot(struct gzvm_memslot *memslot, u64 gfn) |
| { |
| u64 hva, pa; |
| |
| hva = gzvm_gfn_to_hva_memslot(memslot, gfn); |
| |
| pa = gzvm_hva_to_pa_arch(hva); |
| if (pa != GZVM_PA_ERR_BAD) |
| return PHYS_PFN(pa); |
| |
| pa = hva_to_pa_fast(hva); |
| if (pa != GZVM_PA_ERR_BAD) |
| return PHYS_PFN(pa); |
| |
| pa = hva_to_pa_slow(hva); |
| if (pa != GZVM_PA_ERR_BAD) |
| return PHYS_PFN(pa); |
| |
| return GZVM_PA_ERR_BAD; |
| } |
| |
| /** |
| * gzvm_gfn_to_pfn_memslot() - Translate gfn (guest ipa) to pfn (host pa), |
| * result is in @pfn |
| * @memslot: Pointer to struct gzvm_memslot. |
| * @gfn: Guest frame number. |
| * @pfn: Host page frame number. |
| * |
| * Return: |
| * * 0 - Succeed |
| * * -EFAULT - Failed to convert |
| */ |
| int gzvm_gfn_to_pfn_memslot(struct gzvm_memslot *memslot, u64 gfn, |
| u64 *pfn) |
| { |
| u64 __pfn; |
| |
| if (!memslot) |
| return -EFAULT; |
| |
| __pfn = __gzvm_gfn_to_pfn_memslot(memslot, gfn); |
| if (__pfn == GZVM_PA_ERR_BAD) { |
| *pfn = 0; |
| return -EFAULT; |
| } |
| |
| *pfn = __pfn; |
| |
| return 0; |
| } |
| |
| static int cmp_ppages(struct rb_node *node, const struct rb_node *parent) |
| { |
| struct gzvm_pinned_page *a = container_of(node, |
| struct gzvm_pinned_page, |
| node); |
| struct gzvm_pinned_page *b = container_of(parent, |
| struct gzvm_pinned_page, |
| node); |
| |
| if (a->ipa < b->ipa) |
| return -1; |
| if (a->ipa > b->ipa) |
| return 1; |
| return 0; |
| } |
| |
| static int rb_ppage_cmp(const void *key, const struct rb_node *node) |
| { |
| struct gzvm_pinned_page *p = container_of(node, |
| struct gzvm_pinned_page, |
| node); |
| phys_addr_t ipa = (phys_addr_t)key; |
| |
| return (ipa < p->ipa) ? -1 : (ipa > p->ipa); |
| } |
| |
| static int gzvm_insert_ppage(struct gzvm *vm, struct gzvm_pinned_page *ppage) |
| { |
| if (rb_find_add(&ppage->node, &vm->pinned_pages, cmp_ppages)) |
| return -EEXIST; |
| return 0; |
| } |
| |
| static int pin_one_page(struct gzvm *vm, unsigned long hva, u64 gpa) |
| { |
| unsigned int flags = FOLL_HWPOISON | FOLL_LONGTERM | FOLL_WRITE; |
| struct gzvm_pinned_page *ppage = NULL; |
| struct mm_struct *mm = current->mm; |
| struct page *page = NULL; |
| |
| ppage = kmalloc(sizeof(*ppage), GFP_KERNEL_ACCOUNT); |
| if (!ppage) |
| return -ENOMEM; |
| |
| mmap_read_lock(mm); |
| pin_user_pages(hva, 1, flags, &page, NULL); |
| mmap_read_unlock(mm); |
| |
| if (!page) { |
| kfree(ppage); |
| return -EFAULT; |
| } |
| |
| ppage->page = page; |
| ppage->ipa = gpa; |
| gzvm_insert_ppage(vm, ppage); |
| |
| return 0; |
| } |
| |
| /** |
| * gzvm_handle_relinquish() - Handle memory relinquish request from hypervisor |
| * |
| * @vcpu: Pointer to struct gzvm_vcpu_run in userspace |
| * @ipa: Start address(gpa) of a reclaimed page |
| * |
| * Return: Always return 0 because there are no cases of failure |
| */ |
| int gzvm_handle_relinquish(struct gzvm_vcpu *vcpu, phys_addr_t ipa) |
| { |
| struct gzvm_pinned_page *ppage; |
| struct rb_node *node; |
| struct gzvm *vm = vcpu->gzvm; |
| |
| node = rb_find((void *)ipa, &vm->pinned_pages, rb_ppage_cmp); |
| |
| if (node) |
| rb_erase(node, &vm->pinned_pages); |
| else |
| return 0; |
| |
| ppage = container_of(node, struct gzvm_pinned_page, node); |
| unpin_user_pages_dirty_lock(&ppage->page, 1, true); |
| kfree(ppage); |
| return 0; |
| } |
| |
| static int handle_block_demand_page(struct gzvm *vm, int memslot_id, u64 gfn) |
| { |
| unsigned long hva; |
| u64 pfn, __gfn; |
| int ret, i; |
| |
| u32 nr_entries = GZVM_BLOCK_BASED_DEMAND_PAGE_SIZE / PAGE_SIZE; |
| struct gzvm_memslot *memslot = &vm->memslot[memslot_id]; |
| u64 start_gfn = ALIGN_DOWN(gfn, nr_entries); |
| u32 total_pages = memslot->npages; |
| u64 base_gfn = memslot->base_gfn; |
| |
| /* if the demand region is less than a block, adjust the nr_entries */ |
| if (start_gfn + nr_entries > base_gfn + total_pages) |
| nr_entries = base_gfn + total_pages - start_gfn; |
| |
| mutex_lock(&vm->demand_paging_lock); |
| for (i = 0, __gfn = start_gfn; i < nr_entries; i++, __gfn++) { |
| ret = gzvm_gfn_to_pfn_memslot(&vm->memslot[memslot_id], __gfn, |
| &pfn); |
| if (unlikely(ret)) { |
| ret = -ERR_FAULT; |
| goto err_unlock; |
| } |
| vm->demand_page_buffer[i] = pfn; |
| |
| hva = gzvm_gfn_to_hva_memslot(&vm->memslot[memslot_id], __gfn); |
| ret = pin_one_page(vm, hva, PFN_PHYS(__gfn)); |
| if (ret) |
| goto err_unlock; |
| } |
| |
| ret = gzvm_arch_map_guest_block(vm->vm_id, memslot_id, start_gfn, |
| nr_entries); |
| if (unlikely(ret)) { |
| ret = -EFAULT; |
| goto err_unlock; |
| } |
| |
| err_unlock: |
| mutex_unlock(&vm->demand_paging_lock); |
| |
| return ret; |
| } |
| |
| static int handle_single_demand_page(struct gzvm *vm, int memslot_id, u64 gfn) |
| { |
| unsigned long hva; |
| int ret; |
| u64 pfn; |
| |
| ret = gzvm_gfn_to_pfn_memslot(&vm->memslot[memslot_id], gfn, &pfn); |
| if (unlikely(ret)) |
| return -EFAULT; |
| |
| ret = gzvm_arch_map_guest(vm->vm_id, memslot_id, pfn, gfn, 1); |
| if (unlikely(ret)) |
| return -EFAULT; |
| |
| hva = gzvm_gfn_to_hva_memslot(&vm->memslot[memslot_id], gfn); |
| return pin_one_page(vm, hva, PFN_PHYS(gfn)); |
| } |
| |
| /** |
| * gzvm_handle_page_fault() - Handle guest page fault, find corresponding page |
| * for the faulting gpa |
| * @vcpu: Pointer to struct gzvm_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 gzvm_handle_page_fault(struct gzvm_vcpu *vcpu) |
| { |
| struct gzvm *vm = vcpu->gzvm; |
| int memslot_id; |
| u64 gfn; |
| |
| gfn = PHYS_PFN(vcpu->run->exception.fault_gpa); |
| memslot_id = gzvm_find_memslot(vm, gfn); |
| if (unlikely(memslot_id < 0)) |
| return -EFAULT; |
| |
| if (vm->demand_page_gran == PAGE_SIZE) |
| return handle_single_demand_page(vm, memslot_id, gfn); |
| else |
| return handle_block_demand_page(vm, memslot_id, gfn); |
| } |