forked from Minki/linux
88f537d5e8
The HSM provides hypervisor services to the ACRN userspace. While launching a User VM, ACRN userspace needs to allocate memory and request the ACRN Hypervisor to set up the EPT mapping for the VM. A mapping cache is introduced for accelerating the translation between the Service VM kernel virtual address and User VM physical address. >From the perspective of the hypervisor, the types of GPA of User VM can be listed as following: 1) RAM region, which is used by User VM as system ram. 2) MMIO region, which is recognized by User VM as MMIO. MMIO region is used to be utilized for devices emulation. Generally, User VM RAM regions mapping is set up before VM started and is released in the User VM destruction. MMIO regions mapping may be set and unset dynamically during User VM running. To achieve this, ioctls ACRN_IOCTL_SET_MEMSEG and ACRN_IOCTL_UNSET_MEMSEG are introduced in HSM. Cc: Zhi Wang <zhi.a.wang@intel.com> Cc: Zhenyu Wang <zhenyuw@linux.intel.com> Cc: Yu Wang <yu1.wang@intel.com> Cc: Reinette Chatre <reinette.chatre@intel.com> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Reviewed-by: Zhi Wang <zhi.a.wang@intel.com> Reviewed-by: Reinette Chatre <reinette.chatre@intel.com> Signed-off-by: Shuo Liu <shuo.a.liu@intel.com> Link: https://lore.kernel.org/r/20210207031040.49576-9-shuo.a.liu@intel.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
307 lines
7.7 KiB
C
307 lines
7.7 KiB
C
// 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 "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 = 0, 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;
|
|
|
|
if (!vm || !memmap)
|
|
return -EINVAL;
|
|
|
|
/* Get the page number of the map region */
|
|
nr_pages = memmap->len >> PAGE_SHIFT;
|
|
pages = vzalloc(nr_pages * sizeof(struct page *));
|
|
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 */
|
|
while (i < nr_pages) {
|
|
page = pages[i];
|
|
VM_BUG_ON_PAGE(PageTail(page), page);
|
|
order = compound_order(page);
|
|
nr_regions++;
|
|
i += 1 << order;
|
|
}
|
|
|
|
/* Prepare the vm_memory_region_batch */
|
|
regions_info = kzalloc(sizeof(*regions_info) +
|
|
sizeof(*vm_region) * nr_regions,
|
|
GFP_KERNEL);
|
|
if (!regions_info) {
|
|
ret = -ENOMEM;
|
|
goto unmap_kernel_map;
|
|
}
|
|
|
|
/* Fill each vm_memory_region_op */
|
|
vm_region = (struct vm_memory_region_op *)(regions_info + 1);
|
|
regions_info->vmid = vm->vmid;
|
|
regions_info->regions_num = nr_regions;
|
|
regions_info->regions_gpa = virt_to_phys(vm_region);
|
|
user_vm_pa = memmap->user_vm_pa;
|
|
i = 0;
|
|
while (i < nr_pages) {
|
|
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;
|
|
i += 1 << order;
|
|
}
|
|
|
|
/* 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);
|
|
}
|