|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * ACRN: Memory mapping management | 
|  | * | 
|  | * Copyright (C) 2020 Intel Corporation. All rights reserved. | 
|  | * | 
|  | * Authors: | 
|  | *	Fei Li <lei1.li@intel.com> | 
|  | *	Shuo Liu <shuo.a.liu@intel.com> | 
|  | */ | 
|  |  | 
|  | #include <linux/io.h> | 
|  | #include <linux/mm.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/vmalloc.h> | 
|  |  | 
|  | #include "acrn_drv.h" | 
|  |  | 
|  | static int modify_region(struct acrn_vm *vm, struct vm_memory_region_op *region) | 
|  | { | 
|  | struct vm_memory_region_batch *regions; | 
|  | int ret; | 
|  |  | 
|  | regions = kzalloc(sizeof(*regions), GFP_KERNEL); | 
|  | if (!regions) | 
|  | return -ENOMEM; | 
|  |  | 
|  | regions->vmid = vm->vmid; | 
|  | regions->regions_num = 1; | 
|  | regions->regions_gpa = virt_to_phys(region); | 
|  |  | 
|  | ret = hcall_set_memory_regions(virt_to_phys(regions)); | 
|  | if (ret < 0) | 
|  | dev_dbg(acrn_dev.this_device, | 
|  | "Failed to set memory region for VM[%u]!\n", vm->vmid); | 
|  |  | 
|  | kfree(regions); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * acrn_mm_region_add() - Set up the EPT mapping of a memory region. | 
|  | * @vm:			User VM. | 
|  | * @user_gpa:		A GPA of User VM. | 
|  | * @service_gpa:	A GPA of Service VM. | 
|  | * @size:		Size of the region. | 
|  | * @mem_type:		Combination of ACRN_MEM_TYPE_*. | 
|  | * @mem_access_right:	Combination of ACRN_MEM_ACCESS_*. | 
|  | * | 
|  | * Return: 0 on success, <0 on error. | 
|  | */ | 
|  | int acrn_mm_region_add(struct acrn_vm *vm, u64 user_gpa, u64 service_gpa, | 
|  | u64 size, u32 mem_type, u32 mem_access_right) | 
|  | { | 
|  | struct vm_memory_region_op *region; | 
|  | int ret = 0; | 
|  |  | 
|  | region = kzalloc(sizeof(*region), GFP_KERNEL); | 
|  | if (!region) | 
|  | return -ENOMEM; | 
|  |  | 
|  | region->type = ACRN_MEM_REGION_ADD; | 
|  | region->user_vm_pa = user_gpa; | 
|  | region->service_vm_pa = service_gpa; | 
|  | region->size = size; | 
|  | region->attr = ((mem_type & ACRN_MEM_TYPE_MASK) | | 
|  | (mem_access_right & ACRN_MEM_ACCESS_RIGHT_MASK)); | 
|  | ret = modify_region(vm, region); | 
|  |  | 
|  | dev_dbg(acrn_dev.this_device, | 
|  | "%s: user-GPA[%pK] service-GPA[%pK] size[0x%llx].\n", | 
|  | __func__, (void *)user_gpa, (void *)service_gpa, size); | 
|  | kfree(region); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * acrn_mm_region_del() - Del the EPT mapping of a memory region. | 
|  | * @vm:		User VM. | 
|  | * @user_gpa:	A GPA of the User VM. | 
|  | * @size:	Size of the region. | 
|  | * | 
|  | * Return: 0 on success, <0 for error. | 
|  | */ | 
|  | int acrn_mm_region_del(struct acrn_vm *vm, u64 user_gpa, u64 size) | 
|  | { | 
|  | struct vm_memory_region_op *region; | 
|  | int ret = 0; | 
|  |  | 
|  | region = kzalloc(sizeof(*region), GFP_KERNEL); | 
|  | if (!region) | 
|  | return -ENOMEM; | 
|  |  | 
|  | region->type = ACRN_MEM_REGION_DEL; | 
|  | region->user_vm_pa = user_gpa; | 
|  | region->service_vm_pa = 0UL; | 
|  | region->size = size; | 
|  | region->attr = 0U; | 
|  |  | 
|  | ret = modify_region(vm, region); | 
|  |  | 
|  | dev_dbg(acrn_dev.this_device, "%s: user-GPA[%pK] size[0x%llx].\n", | 
|  | __func__, (void *)user_gpa, size); | 
|  | kfree(region); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int acrn_vm_memseg_map(struct acrn_vm *vm, struct acrn_vm_memmap *memmap) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | if (memmap->type == ACRN_MEMMAP_RAM) | 
|  | return acrn_vm_ram_map(vm, memmap); | 
|  |  | 
|  | if (memmap->type != ACRN_MEMMAP_MMIO) { | 
|  | dev_dbg(acrn_dev.this_device, | 
|  | "Invalid memmap type: %u\n", memmap->type); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | ret = acrn_mm_region_add(vm, memmap->user_vm_pa, | 
|  | memmap->service_vm_pa, memmap->len, | 
|  | ACRN_MEM_TYPE_UC, memmap->attr); | 
|  | if (ret < 0) | 
|  | dev_dbg(acrn_dev.this_device, | 
|  | "Add memory region failed, VM[%u]!\n", vm->vmid); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int acrn_vm_memseg_unmap(struct acrn_vm *vm, struct acrn_vm_memmap *memmap) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | if (memmap->type != ACRN_MEMMAP_MMIO) { | 
|  | dev_dbg(acrn_dev.this_device, | 
|  | "Invalid memmap type: %u\n", memmap->type); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | ret = acrn_mm_region_del(vm, memmap->user_vm_pa, memmap->len); | 
|  | if (ret < 0) | 
|  | dev_dbg(acrn_dev.this_device, | 
|  | "Del memory region failed, VM[%u]!\n", vm->vmid); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * acrn_vm_ram_map() - Create a RAM EPT mapping of User VM. | 
|  | * @vm:		The User VM pointer | 
|  | * @memmap:	Info of the EPT mapping | 
|  | * | 
|  | * Return: 0 on success, <0 for error. | 
|  | */ | 
|  | int acrn_vm_ram_map(struct acrn_vm *vm, struct acrn_vm_memmap *memmap) | 
|  | { | 
|  | struct vm_memory_region_batch *regions_info; | 
|  | int nr_pages, i, order, nr_regions = 0; | 
|  | struct vm_memory_mapping *region_mapping; | 
|  | struct vm_memory_region_op *vm_region; | 
|  | struct page **pages = NULL, *page; | 
|  | void *remap_vaddr; | 
|  | int ret, pinned; | 
|  | u64 user_vm_pa; | 
|  | struct vm_area_struct *vma; | 
|  |  | 
|  | if (!vm || !memmap) | 
|  | return -EINVAL; | 
|  |  | 
|  | /* Get the page number of the map region */ | 
|  | nr_pages = memmap->len >> PAGE_SHIFT; | 
|  | if (!nr_pages) | 
|  | return -EINVAL; | 
|  |  | 
|  | mmap_read_lock(current->mm); | 
|  | vma = vma_lookup(current->mm, memmap->vma_base); | 
|  | if (vma && ((vma->vm_flags & VM_PFNMAP) != 0)) { | 
|  | unsigned long start_pfn, cur_pfn; | 
|  | bool writable; | 
|  |  | 
|  | if ((memmap->vma_base + memmap->len) > vma->vm_end) { | 
|  | mmap_read_unlock(current->mm); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < nr_pages; i++) { | 
|  | struct follow_pfnmap_args args = { | 
|  | .vma = vma, | 
|  | .address = memmap->vma_base + i * PAGE_SIZE, | 
|  | }; | 
|  |  | 
|  | ret = follow_pfnmap_start(&args); | 
|  | if (ret) | 
|  | break; | 
|  |  | 
|  | cur_pfn = args.pfn; | 
|  | if (i == 0) | 
|  | start_pfn = cur_pfn; | 
|  | writable = args.writable; | 
|  | follow_pfnmap_end(&args); | 
|  |  | 
|  | /* Disallow write access if the PTE is not writable. */ | 
|  | if (!writable && | 
|  | (memmap->attr & ACRN_MEM_ACCESS_WRITE)) { | 
|  | ret = -EFAULT; | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* Disallow refcounted pages. */ | 
|  | if (pfn_valid(cur_pfn) && | 
|  | !PageReserved(pfn_to_page(cur_pfn))) { | 
|  | ret = -EFAULT; | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* Disallow non-contiguous ranges. */ | 
|  | if (cur_pfn != start_pfn + i) { | 
|  | ret = -EINVAL; | 
|  | break; | 
|  | } | 
|  | } | 
|  | mmap_read_unlock(current->mm); | 
|  |  | 
|  | if (ret) { | 
|  | dev_dbg(acrn_dev.this_device, | 
|  | "Failed to lookup PFN at VMA:%pK.\n", (void *)memmap->vma_base); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return acrn_mm_region_add(vm, memmap->user_vm_pa, | 
|  | PFN_PHYS(start_pfn), memmap->len, | 
|  | ACRN_MEM_TYPE_WB, memmap->attr); | 
|  | } | 
|  | mmap_read_unlock(current->mm); | 
|  |  | 
|  | pages = vzalloc(array_size(nr_pages, sizeof(*pages))); | 
|  | if (!pages) | 
|  | return -ENOMEM; | 
|  |  | 
|  | /* Lock the pages of user memory map region */ | 
|  | pinned = pin_user_pages_fast(memmap->vma_base, | 
|  | nr_pages, FOLL_WRITE | FOLL_LONGTERM, | 
|  | pages); | 
|  | if (pinned < 0) { | 
|  | ret = pinned; | 
|  | goto free_pages; | 
|  | } else if (pinned != nr_pages) { | 
|  | ret = -EFAULT; | 
|  | goto put_pages; | 
|  | } | 
|  |  | 
|  | /* Create a kernel map for the map region */ | 
|  | remap_vaddr = vmap(pages, nr_pages, VM_MAP, PAGE_KERNEL); | 
|  | if (!remap_vaddr) { | 
|  | ret = -ENOMEM; | 
|  | goto put_pages; | 
|  | } | 
|  |  | 
|  | /* Record Service VM va <-> User VM pa mapping */ | 
|  | mutex_lock(&vm->regions_mapping_lock); | 
|  | region_mapping = &vm->regions_mapping[vm->regions_mapping_count]; | 
|  | if (vm->regions_mapping_count < ACRN_MEM_MAPPING_MAX) { | 
|  | region_mapping->pages = pages; | 
|  | region_mapping->npages = nr_pages; | 
|  | region_mapping->size = memmap->len; | 
|  | region_mapping->service_vm_va = remap_vaddr; | 
|  | region_mapping->user_vm_pa = memmap->user_vm_pa; | 
|  | vm->regions_mapping_count++; | 
|  | } else { | 
|  | dev_warn(acrn_dev.this_device, | 
|  | "Run out of memory mapping slots!\n"); | 
|  | ret = -ENOMEM; | 
|  | mutex_unlock(&vm->regions_mapping_lock); | 
|  | goto unmap_no_count; | 
|  | } | 
|  | mutex_unlock(&vm->regions_mapping_lock); | 
|  |  | 
|  | /* Calculate count of vm_memory_region_op */ | 
|  | for (i = 0; i < nr_pages; i += 1 << order) { | 
|  | page = pages[i]; | 
|  | VM_BUG_ON_PAGE(PageTail(page), page); | 
|  | order = compound_order(page); | 
|  | nr_regions++; | 
|  | } | 
|  |  | 
|  | /* Prepare the vm_memory_region_batch */ | 
|  | regions_info = kzalloc(struct_size(regions_info, regions_op, | 
|  | nr_regions), GFP_KERNEL); | 
|  | if (!regions_info) { | 
|  | ret = -ENOMEM; | 
|  | goto unmap_kernel_map; | 
|  | } | 
|  | regions_info->regions_num = nr_regions; | 
|  |  | 
|  | /* Fill each vm_memory_region_op */ | 
|  | vm_region = regions_info->regions_op; | 
|  | regions_info->vmid = vm->vmid; | 
|  | regions_info->regions_gpa = virt_to_phys(vm_region); | 
|  | user_vm_pa = memmap->user_vm_pa; | 
|  | for (i = 0; i < nr_pages; i += 1 << order) { | 
|  | u32 region_size; | 
|  |  | 
|  | page = pages[i]; | 
|  | VM_BUG_ON_PAGE(PageTail(page), page); | 
|  | order = compound_order(page); | 
|  | region_size = PAGE_SIZE << order; | 
|  | vm_region->type = ACRN_MEM_REGION_ADD; | 
|  | vm_region->user_vm_pa = user_vm_pa; | 
|  | vm_region->service_vm_pa = page_to_phys(page); | 
|  | vm_region->size = region_size; | 
|  | vm_region->attr = (ACRN_MEM_TYPE_WB & ACRN_MEM_TYPE_MASK) | | 
|  | (memmap->attr & ACRN_MEM_ACCESS_RIGHT_MASK); | 
|  |  | 
|  | vm_region++; | 
|  | user_vm_pa += region_size; | 
|  | } | 
|  |  | 
|  | /* Inform the ACRN Hypervisor to set up EPT mappings */ | 
|  | ret = hcall_set_memory_regions(virt_to_phys(regions_info)); | 
|  | if (ret < 0) { | 
|  | dev_dbg(acrn_dev.this_device, | 
|  | "Failed to set regions, VM[%u]!\n", vm->vmid); | 
|  | goto unset_region; | 
|  | } | 
|  | kfree(regions_info); | 
|  |  | 
|  | dev_dbg(acrn_dev.this_device, | 
|  | "%s: VM[%u] service-GVA[%pK] user-GPA[%pK] size[0x%llx]\n", | 
|  | __func__, vm->vmid, | 
|  | remap_vaddr, (void *)memmap->user_vm_pa, memmap->len); | 
|  | return ret; | 
|  |  | 
|  | unset_region: | 
|  | kfree(regions_info); | 
|  | unmap_kernel_map: | 
|  | mutex_lock(&vm->regions_mapping_lock); | 
|  | vm->regions_mapping_count--; | 
|  | mutex_unlock(&vm->regions_mapping_lock); | 
|  | unmap_no_count: | 
|  | vunmap(remap_vaddr); | 
|  | put_pages: | 
|  | for (i = 0; i < pinned; i++) | 
|  | unpin_user_page(pages[i]); | 
|  | free_pages: | 
|  | vfree(pages); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * acrn_vm_all_ram_unmap() - Destroy a RAM EPT mapping of User VM. | 
|  | * @vm:	The User VM | 
|  | */ | 
|  | void acrn_vm_all_ram_unmap(struct acrn_vm *vm) | 
|  | { | 
|  | struct vm_memory_mapping *region_mapping; | 
|  | int i, j; | 
|  |  | 
|  | mutex_lock(&vm->regions_mapping_lock); | 
|  | for (i = 0; i < vm->regions_mapping_count; i++) { | 
|  | region_mapping = &vm->regions_mapping[i]; | 
|  | vunmap(region_mapping->service_vm_va); | 
|  | for (j = 0; j < region_mapping->npages; j++) | 
|  | unpin_user_page(region_mapping->pages[j]); | 
|  | vfree(region_mapping->pages); | 
|  | } | 
|  | mutex_unlock(&vm->regions_mapping_lock); | 
|  | } |