162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * ACRN: Memory mapping management
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2020 Intel Corporation. All rights reserved.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Authors:
862306a36Sopenharmony_ci *	Fei Li <lei1.li@intel.com>
962306a36Sopenharmony_ci *	Shuo Liu <shuo.a.liu@intel.com>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/io.h>
1362306a36Sopenharmony_ci#include <linux/mm.h>
1462306a36Sopenharmony_ci#include <linux/slab.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include "acrn_drv.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistatic int modify_region(struct acrn_vm *vm, struct vm_memory_region_op *region)
1962306a36Sopenharmony_ci{
2062306a36Sopenharmony_ci	struct vm_memory_region_batch *regions;
2162306a36Sopenharmony_ci	int ret;
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci	regions = kzalloc(sizeof(*regions), GFP_KERNEL);
2462306a36Sopenharmony_ci	if (!regions)
2562306a36Sopenharmony_ci		return -ENOMEM;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	regions->vmid = vm->vmid;
2862306a36Sopenharmony_ci	regions->regions_num = 1;
2962306a36Sopenharmony_ci	regions->regions_gpa = virt_to_phys(region);
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	ret = hcall_set_memory_regions(virt_to_phys(regions));
3262306a36Sopenharmony_ci	if (ret < 0)
3362306a36Sopenharmony_ci		dev_dbg(acrn_dev.this_device,
3462306a36Sopenharmony_ci			"Failed to set memory region for VM[%u]!\n", vm->vmid);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	kfree(regions);
3762306a36Sopenharmony_ci	return ret;
3862306a36Sopenharmony_ci}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci/**
4162306a36Sopenharmony_ci * acrn_mm_region_add() - Set up the EPT mapping of a memory region.
4262306a36Sopenharmony_ci * @vm:			User VM.
4362306a36Sopenharmony_ci * @user_gpa:		A GPA of User VM.
4462306a36Sopenharmony_ci * @service_gpa:	A GPA of Service VM.
4562306a36Sopenharmony_ci * @size:		Size of the region.
4662306a36Sopenharmony_ci * @mem_type:		Combination of ACRN_MEM_TYPE_*.
4762306a36Sopenharmony_ci * @mem_access_right:	Combination of ACRN_MEM_ACCESS_*.
4862306a36Sopenharmony_ci *
4962306a36Sopenharmony_ci * Return: 0 on success, <0 on error.
5062306a36Sopenharmony_ci */
5162306a36Sopenharmony_ciint acrn_mm_region_add(struct acrn_vm *vm, u64 user_gpa, u64 service_gpa,
5262306a36Sopenharmony_ci		       u64 size, u32 mem_type, u32 mem_access_right)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	struct vm_memory_region_op *region;
5562306a36Sopenharmony_ci	int ret = 0;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	region = kzalloc(sizeof(*region), GFP_KERNEL);
5862306a36Sopenharmony_ci	if (!region)
5962306a36Sopenharmony_ci		return -ENOMEM;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	region->type = ACRN_MEM_REGION_ADD;
6262306a36Sopenharmony_ci	region->user_vm_pa = user_gpa;
6362306a36Sopenharmony_ci	region->service_vm_pa = service_gpa;
6462306a36Sopenharmony_ci	region->size = size;
6562306a36Sopenharmony_ci	region->attr = ((mem_type & ACRN_MEM_TYPE_MASK) |
6662306a36Sopenharmony_ci			(mem_access_right & ACRN_MEM_ACCESS_RIGHT_MASK));
6762306a36Sopenharmony_ci	ret = modify_region(vm, region);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	dev_dbg(acrn_dev.this_device,
7062306a36Sopenharmony_ci		"%s: user-GPA[%pK] service-GPA[%pK] size[0x%llx].\n",
7162306a36Sopenharmony_ci		__func__, (void *)user_gpa, (void *)service_gpa, size);
7262306a36Sopenharmony_ci	kfree(region);
7362306a36Sopenharmony_ci	return ret;
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci/**
7762306a36Sopenharmony_ci * acrn_mm_region_del() - Del the EPT mapping of a memory region.
7862306a36Sopenharmony_ci * @vm:		User VM.
7962306a36Sopenharmony_ci * @user_gpa:	A GPA of the User VM.
8062306a36Sopenharmony_ci * @size:	Size of the region.
8162306a36Sopenharmony_ci *
8262306a36Sopenharmony_ci * Return: 0 on success, <0 for error.
8362306a36Sopenharmony_ci */
8462306a36Sopenharmony_ciint acrn_mm_region_del(struct acrn_vm *vm, u64 user_gpa, u64 size)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	struct vm_memory_region_op *region;
8762306a36Sopenharmony_ci	int ret = 0;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	region = kzalloc(sizeof(*region), GFP_KERNEL);
9062306a36Sopenharmony_ci	if (!region)
9162306a36Sopenharmony_ci		return -ENOMEM;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	region->type = ACRN_MEM_REGION_DEL;
9462306a36Sopenharmony_ci	region->user_vm_pa = user_gpa;
9562306a36Sopenharmony_ci	region->service_vm_pa = 0UL;
9662306a36Sopenharmony_ci	region->size = size;
9762306a36Sopenharmony_ci	region->attr = 0U;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	ret = modify_region(vm, region);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	dev_dbg(acrn_dev.this_device, "%s: user-GPA[%pK] size[0x%llx].\n",
10262306a36Sopenharmony_ci		__func__, (void *)user_gpa, size);
10362306a36Sopenharmony_ci	kfree(region);
10462306a36Sopenharmony_ci	return ret;
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ciint acrn_vm_memseg_map(struct acrn_vm *vm, struct acrn_vm_memmap *memmap)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	int ret;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	if (memmap->type == ACRN_MEMMAP_RAM)
11262306a36Sopenharmony_ci		return acrn_vm_ram_map(vm, memmap);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	if (memmap->type != ACRN_MEMMAP_MMIO) {
11562306a36Sopenharmony_ci		dev_dbg(acrn_dev.this_device,
11662306a36Sopenharmony_ci			"Invalid memmap type: %u\n", memmap->type);
11762306a36Sopenharmony_ci		return -EINVAL;
11862306a36Sopenharmony_ci	}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	ret = acrn_mm_region_add(vm, memmap->user_vm_pa,
12162306a36Sopenharmony_ci				 memmap->service_vm_pa, memmap->len,
12262306a36Sopenharmony_ci				 ACRN_MEM_TYPE_UC, memmap->attr);
12362306a36Sopenharmony_ci	if (ret < 0)
12462306a36Sopenharmony_ci		dev_dbg(acrn_dev.this_device,
12562306a36Sopenharmony_ci			"Add memory region failed, VM[%u]!\n", vm->vmid);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	return ret;
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ciint acrn_vm_memseg_unmap(struct acrn_vm *vm, struct acrn_vm_memmap *memmap)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	int ret;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	if (memmap->type != ACRN_MEMMAP_MMIO) {
13562306a36Sopenharmony_ci		dev_dbg(acrn_dev.this_device,
13662306a36Sopenharmony_ci			"Invalid memmap type: %u\n", memmap->type);
13762306a36Sopenharmony_ci		return -EINVAL;
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	ret = acrn_mm_region_del(vm, memmap->user_vm_pa, memmap->len);
14162306a36Sopenharmony_ci	if (ret < 0)
14262306a36Sopenharmony_ci		dev_dbg(acrn_dev.this_device,
14362306a36Sopenharmony_ci			"Del memory region failed, VM[%u]!\n", vm->vmid);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	return ret;
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci/**
14962306a36Sopenharmony_ci * acrn_vm_ram_map() - Create a RAM EPT mapping of User VM.
15062306a36Sopenharmony_ci * @vm:		The User VM pointer
15162306a36Sopenharmony_ci * @memmap:	Info of the EPT mapping
15262306a36Sopenharmony_ci *
15362306a36Sopenharmony_ci * Return: 0 on success, <0 for error.
15462306a36Sopenharmony_ci */
15562306a36Sopenharmony_ciint acrn_vm_ram_map(struct acrn_vm *vm, struct acrn_vm_memmap *memmap)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	struct vm_memory_region_batch *regions_info;
15862306a36Sopenharmony_ci	int nr_pages, i = 0, order, nr_regions = 0;
15962306a36Sopenharmony_ci	struct vm_memory_mapping *region_mapping;
16062306a36Sopenharmony_ci	struct vm_memory_region_op *vm_region;
16162306a36Sopenharmony_ci	struct page **pages = NULL, *page;
16262306a36Sopenharmony_ci	void *remap_vaddr;
16362306a36Sopenharmony_ci	int ret, pinned;
16462306a36Sopenharmony_ci	u64 user_vm_pa;
16562306a36Sopenharmony_ci	unsigned long pfn;
16662306a36Sopenharmony_ci	struct vm_area_struct *vma;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	if (!vm || !memmap)
16962306a36Sopenharmony_ci		return -EINVAL;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	mmap_read_lock(current->mm);
17262306a36Sopenharmony_ci	vma = vma_lookup(current->mm, memmap->vma_base);
17362306a36Sopenharmony_ci	if (vma && ((vma->vm_flags & VM_PFNMAP) != 0)) {
17462306a36Sopenharmony_ci		if ((memmap->vma_base + memmap->len) > vma->vm_end) {
17562306a36Sopenharmony_ci			mmap_read_unlock(current->mm);
17662306a36Sopenharmony_ci			return -EINVAL;
17762306a36Sopenharmony_ci		}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci		ret = follow_pfn(vma, memmap->vma_base, &pfn);
18062306a36Sopenharmony_ci		mmap_read_unlock(current->mm);
18162306a36Sopenharmony_ci		if (ret < 0) {
18262306a36Sopenharmony_ci			dev_dbg(acrn_dev.this_device,
18362306a36Sopenharmony_ci				"Failed to lookup PFN at VMA:%pK.\n", (void *)memmap->vma_base);
18462306a36Sopenharmony_ci			return ret;
18562306a36Sopenharmony_ci		}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci		return acrn_mm_region_add(vm, memmap->user_vm_pa,
18862306a36Sopenharmony_ci			 PFN_PHYS(pfn), memmap->len,
18962306a36Sopenharmony_ci			 ACRN_MEM_TYPE_WB, memmap->attr);
19062306a36Sopenharmony_ci	}
19162306a36Sopenharmony_ci	mmap_read_unlock(current->mm);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	/* Get the page number of the map region */
19462306a36Sopenharmony_ci	nr_pages = memmap->len >> PAGE_SHIFT;
19562306a36Sopenharmony_ci	pages = vzalloc(array_size(nr_pages, sizeof(*pages)));
19662306a36Sopenharmony_ci	if (!pages)
19762306a36Sopenharmony_ci		return -ENOMEM;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	/* Lock the pages of user memory map region */
20062306a36Sopenharmony_ci	pinned = pin_user_pages_fast(memmap->vma_base,
20162306a36Sopenharmony_ci				     nr_pages, FOLL_WRITE | FOLL_LONGTERM,
20262306a36Sopenharmony_ci				     pages);
20362306a36Sopenharmony_ci	if (pinned < 0) {
20462306a36Sopenharmony_ci		ret = pinned;
20562306a36Sopenharmony_ci		goto free_pages;
20662306a36Sopenharmony_ci	} else if (pinned != nr_pages) {
20762306a36Sopenharmony_ci		ret = -EFAULT;
20862306a36Sopenharmony_ci		goto put_pages;
20962306a36Sopenharmony_ci	}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	/* Create a kernel map for the map region */
21262306a36Sopenharmony_ci	remap_vaddr = vmap(pages, nr_pages, VM_MAP, PAGE_KERNEL);
21362306a36Sopenharmony_ci	if (!remap_vaddr) {
21462306a36Sopenharmony_ci		ret = -ENOMEM;
21562306a36Sopenharmony_ci		goto put_pages;
21662306a36Sopenharmony_ci	}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	/* Record Service VM va <-> User VM pa mapping */
21962306a36Sopenharmony_ci	mutex_lock(&vm->regions_mapping_lock);
22062306a36Sopenharmony_ci	region_mapping = &vm->regions_mapping[vm->regions_mapping_count];
22162306a36Sopenharmony_ci	if (vm->regions_mapping_count < ACRN_MEM_MAPPING_MAX) {
22262306a36Sopenharmony_ci		region_mapping->pages = pages;
22362306a36Sopenharmony_ci		region_mapping->npages = nr_pages;
22462306a36Sopenharmony_ci		region_mapping->size = memmap->len;
22562306a36Sopenharmony_ci		region_mapping->service_vm_va = remap_vaddr;
22662306a36Sopenharmony_ci		region_mapping->user_vm_pa = memmap->user_vm_pa;
22762306a36Sopenharmony_ci		vm->regions_mapping_count++;
22862306a36Sopenharmony_ci	} else {
22962306a36Sopenharmony_ci		dev_warn(acrn_dev.this_device,
23062306a36Sopenharmony_ci			"Run out of memory mapping slots!\n");
23162306a36Sopenharmony_ci		ret = -ENOMEM;
23262306a36Sopenharmony_ci		mutex_unlock(&vm->regions_mapping_lock);
23362306a36Sopenharmony_ci		goto unmap_no_count;
23462306a36Sopenharmony_ci	}
23562306a36Sopenharmony_ci	mutex_unlock(&vm->regions_mapping_lock);
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	/* Calculate count of vm_memory_region_op */
23862306a36Sopenharmony_ci	while (i < nr_pages) {
23962306a36Sopenharmony_ci		page = pages[i];
24062306a36Sopenharmony_ci		VM_BUG_ON_PAGE(PageTail(page), page);
24162306a36Sopenharmony_ci		order = compound_order(page);
24262306a36Sopenharmony_ci		nr_regions++;
24362306a36Sopenharmony_ci		i += 1 << order;
24462306a36Sopenharmony_ci	}
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	/* Prepare the vm_memory_region_batch */
24762306a36Sopenharmony_ci	regions_info = kzalloc(struct_size(regions_info, regions_op,
24862306a36Sopenharmony_ci					   nr_regions), GFP_KERNEL);
24962306a36Sopenharmony_ci	if (!regions_info) {
25062306a36Sopenharmony_ci		ret = -ENOMEM;
25162306a36Sopenharmony_ci		goto unmap_kernel_map;
25262306a36Sopenharmony_ci	}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	/* Fill each vm_memory_region_op */
25562306a36Sopenharmony_ci	vm_region = regions_info->regions_op;
25662306a36Sopenharmony_ci	regions_info->vmid = vm->vmid;
25762306a36Sopenharmony_ci	regions_info->regions_num = nr_regions;
25862306a36Sopenharmony_ci	regions_info->regions_gpa = virt_to_phys(vm_region);
25962306a36Sopenharmony_ci	user_vm_pa = memmap->user_vm_pa;
26062306a36Sopenharmony_ci	i = 0;
26162306a36Sopenharmony_ci	while (i < nr_pages) {
26262306a36Sopenharmony_ci		u32 region_size;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci		page = pages[i];
26562306a36Sopenharmony_ci		VM_BUG_ON_PAGE(PageTail(page), page);
26662306a36Sopenharmony_ci		order = compound_order(page);
26762306a36Sopenharmony_ci		region_size = PAGE_SIZE << order;
26862306a36Sopenharmony_ci		vm_region->type = ACRN_MEM_REGION_ADD;
26962306a36Sopenharmony_ci		vm_region->user_vm_pa = user_vm_pa;
27062306a36Sopenharmony_ci		vm_region->service_vm_pa = page_to_phys(page);
27162306a36Sopenharmony_ci		vm_region->size = region_size;
27262306a36Sopenharmony_ci		vm_region->attr = (ACRN_MEM_TYPE_WB & ACRN_MEM_TYPE_MASK) |
27362306a36Sopenharmony_ci				  (memmap->attr & ACRN_MEM_ACCESS_RIGHT_MASK);
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci		vm_region++;
27662306a36Sopenharmony_ci		user_vm_pa += region_size;
27762306a36Sopenharmony_ci		i += 1 << order;
27862306a36Sopenharmony_ci	}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	/* Inform the ACRN Hypervisor to set up EPT mappings */
28162306a36Sopenharmony_ci	ret = hcall_set_memory_regions(virt_to_phys(regions_info));
28262306a36Sopenharmony_ci	if (ret < 0) {
28362306a36Sopenharmony_ci		dev_dbg(acrn_dev.this_device,
28462306a36Sopenharmony_ci			"Failed to set regions, VM[%u]!\n", vm->vmid);
28562306a36Sopenharmony_ci		goto unset_region;
28662306a36Sopenharmony_ci	}
28762306a36Sopenharmony_ci	kfree(regions_info);
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	dev_dbg(acrn_dev.this_device,
29062306a36Sopenharmony_ci		"%s: VM[%u] service-GVA[%pK] user-GPA[%pK] size[0x%llx]\n",
29162306a36Sopenharmony_ci		__func__, vm->vmid,
29262306a36Sopenharmony_ci		remap_vaddr, (void *)memmap->user_vm_pa, memmap->len);
29362306a36Sopenharmony_ci	return ret;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ciunset_region:
29662306a36Sopenharmony_ci	kfree(regions_info);
29762306a36Sopenharmony_ciunmap_kernel_map:
29862306a36Sopenharmony_ci	mutex_lock(&vm->regions_mapping_lock);
29962306a36Sopenharmony_ci	vm->regions_mapping_count--;
30062306a36Sopenharmony_ci	mutex_unlock(&vm->regions_mapping_lock);
30162306a36Sopenharmony_ciunmap_no_count:
30262306a36Sopenharmony_ci	vunmap(remap_vaddr);
30362306a36Sopenharmony_ciput_pages:
30462306a36Sopenharmony_ci	for (i = 0; i < pinned; i++)
30562306a36Sopenharmony_ci		unpin_user_page(pages[i]);
30662306a36Sopenharmony_cifree_pages:
30762306a36Sopenharmony_ci	vfree(pages);
30862306a36Sopenharmony_ci	return ret;
30962306a36Sopenharmony_ci}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci/**
31262306a36Sopenharmony_ci * acrn_vm_all_ram_unmap() - Destroy a RAM EPT mapping of User VM.
31362306a36Sopenharmony_ci * @vm:	The User VM
31462306a36Sopenharmony_ci */
31562306a36Sopenharmony_civoid acrn_vm_all_ram_unmap(struct acrn_vm *vm)
31662306a36Sopenharmony_ci{
31762306a36Sopenharmony_ci	struct vm_memory_mapping *region_mapping;
31862306a36Sopenharmony_ci	int i, j;
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	mutex_lock(&vm->regions_mapping_lock);
32162306a36Sopenharmony_ci	for (i = 0; i < vm->regions_mapping_count; i++) {
32262306a36Sopenharmony_ci		region_mapping = &vm->regions_mapping[i];
32362306a36Sopenharmony_ci		vunmap(region_mapping->service_vm_va);
32462306a36Sopenharmony_ci		for (j = 0; j < region_mapping->npages; j++)
32562306a36Sopenharmony_ci			unpin_user_page(region_mapping->pages[j]);
32662306a36Sopenharmony_ci		vfree(region_mapping->pages);
32762306a36Sopenharmony_ci	}
32862306a36Sopenharmony_ci	mutex_unlock(&vm->regions_mapping_lock);
32962306a36Sopenharmony_ci}
330