| // 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_obj(*regions); | |
| 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_obj(*region); | |
| 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[%p] service-GPA[%p] 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_obj(*region); | |
| 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[%p] 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:%p.\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_flex(*regions_info, regions_op, nr_regions); | |
| 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[%p] user-GPA[%p] 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); | |
| } |