162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * MMU-based software IOTLB.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2020-2021 Bytedance Inc. and/or its affiliates. All rights reserved.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Author: Xie Yongji <xieyongji@bytedance.com>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/slab.h>
1262306a36Sopenharmony_ci#include <linux/file.h>
1362306a36Sopenharmony_ci#include <linux/anon_inodes.h>
1462306a36Sopenharmony_ci#include <linux/highmem.h>
1562306a36Sopenharmony_ci#include <linux/vmalloc.h>
1662306a36Sopenharmony_ci#include <linux/vdpa.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include "iova_domain.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic int vduse_iotlb_add_range(struct vduse_iova_domain *domain,
2162306a36Sopenharmony_ci				 u64 start, u64 last,
2262306a36Sopenharmony_ci				 u64 addr, unsigned int perm,
2362306a36Sopenharmony_ci				 struct file *file, u64 offset)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	struct vdpa_map_file *map_file;
2662306a36Sopenharmony_ci	int ret;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	map_file = kmalloc(sizeof(*map_file), GFP_ATOMIC);
2962306a36Sopenharmony_ci	if (!map_file)
3062306a36Sopenharmony_ci		return -ENOMEM;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	map_file->file = get_file(file);
3362306a36Sopenharmony_ci	map_file->offset = offset;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	ret = vhost_iotlb_add_range_ctx(domain->iotlb, start, last,
3662306a36Sopenharmony_ci					addr, perm, map_file);
3762306a36Sopenharmony_ci	if (ret) {
3862306a36Sopenharmony_ci		fput(map_file->file);
3962306a36Sopenharmony_ci		kfree(map_file);
4062306a36Sopenharmony_ci		return ret;
4162306a36Sopenharmony_ci	}
4262306a36Sopenharmony_ci	return 0;
4362306a36Sopenharmony_ci}
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistatic void vduse_iotlb_del_range(struct vduse_iova_domain *domain,
4662306a36Sopenharmony_ci				  u64 start, u64 last)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	struct vdpa_map_file *map_file;
4962306a36Sopenharmony_ci	struct vhost_iotlb_map *map;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	while ((map = vhost_iotlb_itree_first(domain->iotlb, start, last))) {
5262306a36Sopenharmony_ci		map_file = (struct vdpa_map_file *)map->opaque;
5362306a36Sopenharmony_ci		fput(map_file->file);
5462306a36Sopenharmony_ci		kfree(map_file);
5562306a36Sopenharmony_ci		vhost_iotlb_map_free(domain->iotlb, map);
5662306a36Sopenharmony_ci	}
5762306a36Sopenharmony_ci}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ciint vduse_domain_set_map(struct vduse_iova_domain *domain,
6062306a36Sopenharmony_ci			 struct vhost_iotlb *iotlb)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct vdpa_map_file *map_file;
6362306a36Sopenharmony_ci	struct vhost_iotlb_map *map;
6462306a36Sopenharmony_ci	u64 start = 0ULL, last = ULLONG_MAX;
6562306a36Sopenharmony_ci	int ret;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	spin_lock(&domain->iotlb_lock);
6862306a36Sopenharmony_ci	vduse_iotlb_del_range(domain, start, last);
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	for (map = vhost_iotlb_itree_first(iotlb, start, last); map;
7162306a36Sopenharmony_ci	     map = vhost_iotlb_itree_next(map, start, last)) {
7262306a36Sopenharmony_ci		map_file = (struct vdpa_map_file *)map->opaque;
7362306a36Sopenharmony_ci		ret = vduse_iotlb_add_range(domain, map->start, map->last,
7462306a36Sopenharmony_ci					    map->addr, map->perm,
7562306a36Sopenharmony_ci					    map_file->file,
7662306a36Sopenharmony_ci					    map_file->offset);
7762306a36Sopenharmony_ci		if (ret)
7862306a36Sopenharmony_ci			goto err;
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci	spin_unlock(&domain->iotlb_lock);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	return 0;
8362306a36Sopenharmony_cierr:
8462306a36Sopenharmony_ci	vduse_iotlb_del_range(domain, start, last);
8562306a36Sopenharmony_ci	spin_unlock(&domain->iotlb_lock);
8662306a36Sopenharmony_ci	return ret;
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_civoid vduse_domain_clear_map(struct vduse_iova_domain *domain,
9062306a36Sopenharmony_ci			    struct vhost_iotlb *iotlb)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	struct vhost_iotlb_map *map;
9362306a36Sopenharmony_ci	u64 start = 0ULL, last = ULLONG_MAX;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	spin_lock(&domain->iotlb_lock);
9662306a36Sopenharmony_ci	for (map = vhost_iotlb_itree_first(iotlb, start, last); map;
9762306a36Sopenharmony_ci	     map = vhost_iotlb_itree_next(map, start, last)) {
9862306a36Sopenharmony_ci		vduse_iotlb_del_range(domain, map->start, map->last);
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci	spin_unlock(&domain->iotlb_lock);
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic int vduse_domain_map_bounce_page(struct vduse_iova_domain *domain,
10462306a36Sopenharmony_ci					 u64 iova, u64 size, u64 paddr)
10562306a36Sopenharmony_ci{
10662306a36Sopenharmony_ci	struct vduse_bounce_map *map;
10762306a36Sopenharmony_ci	u64 last = iova + size - 1;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	while (iova <= last) {
11062306a36Sopenharmony_ci		map = &domain->bounce_maps[iova >> PAGE_SHIFT];
11162306a36Sopenharmony_ci		if (!map->bounce_page) {
11262306a36Sopenharmony_ci			map->bounce_page = alloc_page(GFP_ATOMIC);
11362306a36Sopenharmony_ci			if (!map->bounce_page)
11462306a36Sopenharmony_ci				return -ENOMEM;
11562306a36Sopenharmony_ci		}
11662306a36Sopenharmony_ci		map->orig_phys = paddr;
11762306a36Sopenharmony_ci		paddr += PAGE_SIZE;
11862306a36Sopenharmony_ci		iova += PAGE_SIZE;
11962306a36Sopenharmony_ci	}
12062306a36Sopenharmony_ci	return 0;
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cistatic void vduse_domain_unmap_bounce_page(struct vduse_iova_domain *domain,
12462306a36Sopenharmony_ci					   u64 iova, u64 size)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	struct vduse_bounce_map *map;
12762306a36Sopenharmony_ci	u64 last = iova + size - 1;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	while (iova <= last) {
13062306a36Sopenharmony_ci		map = &domain->bounce_maps[iova >> PAGE_SHIFT];
13162306a36Sopenharmony_ci		map->orig_phys = INVALID_PHYS_ADDR;
13262306a36Sopenharmony_ci		iova += PAGE_SIZE;
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistatic void do_bounce(phys_addr_t orig, void *addr, size_t size,
13762306a36Sopenharmony_ci		      enum dma_data_direction dir)
13862306a36Sopenharmony_ci{
13962306a36Sopenharmony_ci	unsigned long pfn = PFN_DOWN(orig);
14062306a36Sopenharmony_ci	unsigned int offset = offset_in_page(orig);
14162306a36Sopenharmony_ci	struct page *page;
14262306a36Sopenharmony_ci	unsigned int sz = 0;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	while (size) {
14562306a36Sopenharmony_ci		sz = min_t(size_t, PAGE_SIZE - offset, size);
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci		page = pfn_to_page(pfn);
14862306a36Sopenharmony_ci		if (dir == DMA_TO_DEVICE)
14962306a36Sopenharmony_ci			memcpy_from_page(addr, page, offset, sz);
15062306a36Sopenharmony_ci		else
15162306a36Sopenharmony_ci			memcpy_to_page(page, offset, addr, sz);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci		size -= sz;
15462306a36Sopenharmony_ci		pfn++;
15562306a36Sopenharmony_ci		addr += sz;
15662306a36Sopenharmony_ci		offset = 0;
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_cistatic void vduse_domain_bounce(struct vduse_iova_domain *domain,
16162306a36Sopenharmony_ci				dma_addr_t iova, size_t size,
16262306a36Sopenharmony_ci				enum dma_data_direction dir)
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	struct vduse_bounce_map *map;
16562306a36Sopenharmony_ci	unsigned int offset;
16662306a36Sopenharmony_ci	void *addr;
16762306a36Sopenharmony_ci	size_t sz;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	if (iova >= domain->bounce_size)
17062306a36Sopenharmony_ci		return;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	while (size) {
17362306a36Sopenharmony_ci		map = &domain->bounce_maps[iova >> PAGE_SHIFT];
17462306a36Sopenharmony_ci		offset = offset_in_page(iova);
17562306a36Sopenharmony_ci		sz = min_t(size_t, PAGE_SIZE - offset, size);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci		if (WARN_ON(!map->bounce_page ||
17862306a36Sopenharmony_ci			    map->orig_phys == INVALID_PHYS_ADDR))
17962306a36Sopenharmony_ci			return;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci		addr = kmap_local_page(map->bounce_page);
18262306a36Sopenharmony_ci		do_bounce(map->orig_phys + offset, addr + offset, sz, dir);
18362306a36Sopenharmony_ci		kunmap_local(addr);
18462306a36Sopenharmony_ci		size -= sz;
18562306a36Sopenharmony_ci		iova += sz;
18662306a36Sopenharmony_ci	}
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic struct page *
19062306a36Sopenharmony_civduse_domain_get_coherent_page(struct vduse_iova_domain *domain, u64 iova)
19162306a36Sopenharmony_ci{
19262306a36Sopenharmony_ci	u64 start = iova & PAGE_MASK;
19362306a36Sopenharmony_ci	u64 last = start + PAGE_SIZE - 1;
19462306a36Sopenharmony_ci	struct vhost_iotlb_map *map;
19562306a36Sopenharmony_ci	struct page *page = NULL;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	spin_lock(&domain->iotlb_lock);
19862306a36Sopenharmony_ci	map = vhost_iotlb_itree_first(domain->iotlb, start, last);
19962306a36Sopenharmony_ci	if (!map)
20062306a36Sopenharmony_ci		goto out;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	page = pfn_to_page((map->addr + iova - map->start) >> PAGE_SHIFT);
20362306a36Sopenharmony_ci	get_page(page);
20462306a36Sopenharmony_ciout:
20562306a36Sopenharmony_ci	spin_unlock(&domain->iotlb_lock);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	return page;
20862306a36Sopenharmony_ci}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cistatic struct page *
21162306a36Sopenharmony_civduse_domain_get_bounce_page(struct vduse_iova_domain *domain, u64 iova)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	struct vduse_bounce_map *map;
21462306a36Sopenharmony_ci	struct page *page = NULL;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	read_lock(&domain->bounce_lock);
21762306a36Sopenharmony_ci	map = &domain->bounce_maps[iova >> PAGE_SHIFT];
21862306a36Sopenharmony_ci	if (domain->user_bounce_pages || !map->bounce_page)
21962306a36Sopenharmony_ci		goto out;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	page = map->bounce_page;
22262306a36Sopenharmony_ci	get_page(page);
22362306a36Sopenharmony_ciout:
22462306a36Sopenharmony_ci	read_unlock(&domain->bounce_lock);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	return page;
22762306a36Sopenharmony_ci}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_cistatic void
23062306a36Sopenharmony_civduse_domain_free_kernel_bounce_pages(struct vduse_iova_domain *domain)
23162306a36Sopenharmony_ci{
23262306a36Sopenharmony_ci	struct vduse_bounce_map *map;
23362306a36Sopenharmony_ci	unsigned long pfn, bounce_pfns;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	bounce_pfns = domain->bounce_size >> PAGE_SHIFT;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	for (pfn = 0; pfn < bounce_pfns; pfn++) {
23862306a36Sopenharmony_ci		map = &domain->bounce_maps[pfn];
23962306a36Sopenharmony_ci		if (WARN_ON(map->orig_phys != INVALID_PHYS_ADDR))
24062306a36Sopenharmony_ci			continue;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci		if (!map->bounce_page)
24362306a36Sopenharmony_ci			continue;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci		__free_page(map->bounce_page);
24662306a36Sopenharmony_ci		map->bounce_page = NULL;
24762306a36Sopenharmony_ci	}
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ciint vduse_domain_add_user_bounce_pages(struct vduse_iova_domain *domain,
25162306a36Sopenharmony_ci				       struct page **pages, int count)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	struct vduse_bounce_map *map;
25462306a36Sopenharmony_ci	int i, ret;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	/* Now we don't support partial mapping */
25762306a36Sopenharmony_ci	if (count != (domain->bounce_size >> PAGE_SHIFT))
25862306a36Sopenharmony_ci		return -EINVAL;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	write_lock(&domain->bounce_lock);
26162306a36Sopenharmony_ci	ret = -EEXIST;
26262306a36Sopenharmony_ci	if (domain->user_bounce_pages)
26362306a36Sopenharmony_ci		goto out;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	for (i = 0; i < count; i++) {
26662306a36Sopenharmony_ci		map = &domain->bounce_maps[i];
26762306a36Sopenharmony_ci		if (map->bounce_page) {
26862306a36Sopenharmony_ci			/* Copy kernel page to user page if it's in use */
26962306a36Sopenharmony_ci			if (map->orig_phys != INVALID_PHYS_ADDR)
27062306a36Sopenharmony_ci				memcpy_to_page(pages[i], 0,
27162306a36Sopenharmony_ci					       page_address(map->bounce_page),
27262306a36Sopenharmony_ci					       PAGE_SIZE);
27362306a36Sopenharmony_ci			__free_page(map->bounce_page);
27462306a36Sopenharmony_ci		}
27562306a36Sopenharmony_ci		map->bounce_page = pages[i];
27662306a36Sopenharmony_ci		get_page(pages[i]);
27762306a36Sopenharmony_ci	}
27862306a36Sopenharmony_ci	domain->user_bounce_pages = true;
27962306a36Sopenharmony_ci	ret = 0;
28062306a36Sopenharmony_ciout:
28162306a36Sopenharmony_ci	write_unlock(&domain->bounce_lock);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	return ret;
28462306a36Sopenharmony_ci}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_civoid vduse_domain_remove_user_bounce_pages(struct vduse_iova_domain *domain)
28762306a36Sopenharmony_ci{
28862306a36Sopenharmony_ci	struct vduse_bounce_map *map;
28962306a36Sopenharmony_ci	unsigned long i, count;
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	write_lock(&domain->bounce_lock);
29262306a36Sopenharmony_ci	if (!domain->user_bounce_pages)
29362306a36Sopenharmony_ci		goto out;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	count = domain->bounce_size >> PAGE_SHIFT;
29662306a36Sopenharmony_ci	for (i = 0; i < count; i++) {
29762306a36Sopenharmony_ci		struct page *page = NULL;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci		map = &domain->bounce_maps[i];
30062306a36Sopenharmony_ci		if (WARN_ON(!map->bounce_page))
30162306a36Sopenharmony_ci			continue;
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci		/* Copy user page to kernel page if it's in use */
30462306a36Sopenharmony_ci		if (map->orig_phys != INVALID_PHYS_ADDR) {
30562306a36Sopenharmony_ci			page = alloc_page(GFP_ATOMIC | __GFP_NOFAIL);
30662306a36Sopenharmony_ci			memcpy_from_page(page_address(page),
30762306a36Sopenharmony_ci					 map->bounce_page, 0, PAGE_SIZE);
30862306a36Sopenharmony_ci		}
30962306a36Sopenharmony_ci		put_page(map->bounce_page);
31062306a36Sopenharmony_ci		map->bounce_page = page;
31162306a36Sopenharmony_ci	}
31262306a36Sopenharmony_ci	domain->user_bounce_pages = false;
31362306a36Sopenharmony_ciout:
31462306a36Sopenharmony_ci	write_unlock(&domain->bounce_lock);
31562306a36Sopenharmony_ci}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_civoid vduse_domain_reset_bounce_map(struct vduse_iova_domain *domain)
31862306a36Sopenharmony_ci{
31962306a36Sopenharmony_ci	if (!domain->bounce_map)
32062306a36Sopenharmony_ci		return;
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	spin_lock(&domain->iotlb_lock);
32362306a36Sopenharmony_ci	if (!domain->bounce_map)
32462306a36Sopenharmony_ci		goto unlock;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	vduse_iotlb_del_range(domain, 0, domain->bounce_size - 1);
32762306a36Sopenharmony_ci	domain->bounce_map = 0;
32862306a36Sopenharmony_ciunlock:
32962306a36Sopenharmony_ci	spin_unlock(&domain->iotlb_lock);
33062306a36Sopenharmony_ci}
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_cistatic int vduse_domain_init_bounce_map(struct vduse_iova_domain *domain)
33362306a36Sopenharmony_ci{
33462306a36Sopenharmony_ci	int ret = 0;
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	if (domain->bounce_map)
33762306a36Sopenharmony_ci		return 0;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	spin_lock(&domain->iotlb_lock);
34062306a36Sopenharmony_ci	if (domain->bounce_map)
34162306a36Sopenharmony_ci		goto unlock;
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	ret = vduse_iotlb_add_range(domain, 0, domain->bounce_size - 1,
34462306a36Sopenharmony_ci				    0, VHOST_MAP_RW, domain->file, 0);
34562306a36Sopenharmony_ci	if (ret)
34662306a36Sopenharmony_ci		goto unlock;
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	domain->bounce_map = 1;
34962306a36Sopenharmony_ciunlock:
35062306a36Sopenharmony_ci	spin_unlock(&domain->iotlb_lock);
35162306a36Sopenharmony_ci	return ret;
35262306a36Sopenharmony_ci}
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_cistatic dma_addr_t
35562306a36Sopenharmony_civduse_domain_alloc_iova(struct iova_domain *iovad,
35662306a36Sopenharmony_ci			unsigned long size, unsigned long limit)
35762306a36Sopenharmony_ci{
35862306a36Sopenharmony_ci	unsigned long shift = iova_shift(iovad);
35962306a36Sopenharmony_ci	unsigned long iova_len = iova_align(iovad, size) >> shift;
36062306a36Sopenharmony_ci	unsigned long iova_pfn;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	iova_pfn = alloc_iova_fast(iovad, iova_len, limit >> shift, true);
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	return (dma_addr_t)iova_pfn << shift;
36562306a36Sopenharmony_ci}
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_cistatic void vduse_domain_free_iova(struct iova_domain *iovad,
36862306a36Sopenharmony_ci				   dma_addr_t iova, size_t size)
36962306a36Sopenharmony_ci{
37062306a36Sopenharmony_ci	unsigned long shift = iova_shift(iovad);
37162306a36Sopenharmony_ci	unsigned long iova_len = iova_align(iovad, size) >> shift;
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	free_iova_fast(iovad, iova >> shift, iova_len);
37462306a36Sopenharmony_ci}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_cidma_addr_t vduse_domain_map_page(struct vduse_iova_domain *domain,
37762306a36Sopenharmony_ci				 struct page *page, unsigned long offset,
37862306a36Sopenharmony_ci				 size_t size, enum dma_data_direction dir,
37962306a36Sopenharmony_ci				 unsigned long attrs)
38062306a36Sopenharmony_ci{
38162306a36Sopenharmony_ci	struct iova_domain *iovad = &domain->stream_iovad;
38262306a36Sopenharmony_ci	unsigned long limit = domain->bounce_size - 1;
38362306a36Sopenharmony_ci	phys_addr_t pa = page_to_phys(page) + offset;
38462306a36Sopenharmony_ci	dma_addr_t iova = vduse_domain_alloc_iova(iovad, size, limit);
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	if (!iova)
38762306a36Sopenharmony_ci		return DMA_MAPPING_ERROR;
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	if (vduse_domain_init_bounce_map(domain))
39062306a36Sopenharmony_ci		goto err;
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	read_lock(&domain->bounce_lock);
39362306a36Sopenharmony_ci	if (vduse_domain_map_bounce_page(domain, (u64)iova, (u64)size, pa))
39462306a36Sopenharmony_ci		goto err_unlock;
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	if (dir == DMA_TO_DEVICE || dir == DMA_BIDIRECTIONAL)
39762306a36Sopenharmony_ci		vduse_domain_bounce(domain, iova, size, DMA_TO_DEVICE);
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	read_unlock(&domain->bounce_lock);
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	return iova;
40262306a36Sopenharmony_cierr_unlock:
40362306a36Sopenharmony_ci	read_unlock(&domain->bounce_lock);
40462306a36Sopenharmony_cierr:
40562306a36Sopenharmony_ci	vduse_domain_free_iova(iovad, iova, size);
40662306a36Sopenharmony_ci	return DMA_MAPPING_ERROR;
40762306a36Sopenharmony_ci}
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_civoid vduse_domain_unmap_page(struct vduse_iova_domain *domain,
41062306a36Sopenharmony_ci			     dma_addr_t dma_addr, size_t size,
41162306a36Sopenharmony_ci			     enum dma_data_direction dir, unsigned long attrs)
41262306a36Sopenharmony_ci{
41362306a36Sopenharmony_ci	struct iova_domain *iovad = &domain->stream_iovad;
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	read_lock(&domain->bounce_lock);
41662306a36Sopenharmony_ci	if (dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL)
41762306a36Sopenharmony_ci		vduse_domain_bounce(domain, dma_addr, size, DMA_FROM_DEVICE);
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	vduse_domain_unmap_bounce_page(domain, (u64)dma_addr, (u64)size);
42062306a36Sopenharmony_ci	read_unlock(&domain->bounce_lock);
42162306a36Sopenharmony_ci	vduse_domain_free_iova(iovad, dma_addr, size);
42262306a36Sopenharmony_ci}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_civoid *vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
42562306a36Sopenharmony_ci				  size_t size, dma_addr_t *dma_addr,
42662306a36Sopenharmony_ci				  gfp_t flag, unsigned long attrs)
42762306a36Sopenharmony_ci{
42862306a36Sopenharmony_ci	struct iova_domain *iovad = &domain->consistent_iovad;
42962306a36Sopenharmony_ci	unsigned long limit = domain->iova_limit;
43062306a36Sopenharmony_ci	dma_addr_t iova = vduse_domain_alloc_iova(iovad, size, limit);
43162306a36Sopenharmony_ci	void *orig = alloc_pages_exact(size, flag);
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	if (!iova || !orig)
43462306a36Sopenharmony_ci		goto err;
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	spin_lock(&domain->iotlb_lock);
43762306a36Sopenharmony_ci	if (vduse_iotlb_add_range(domain, (u64)iova, (u64)iova + size - 1,
43862306a36Sopenharmony_ci				  virt_to_phys(orig), VHOST_MAP_RW,
43962306a36Sopenharmony_ci				  domain->file, (u64)iova)) {
44062306a36Sopenharmony_ci		spin_unlock(&domain->iotlb_lock);
44162306a36Sopenharmony_ci		goto err;
44262306a36Sopenharmony_ci	}
44362306a36Sopenharmony_ci	spin_unlock(&domain->iotlb_lock);
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	*dma_addr = iova;
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	return orig;
44862306a36Sopenharmony_cierr:
44962306a36Sopenharmony_ci	*dma_addr = DMA_MAPPING_ERROR;
45062306a36Sopenharmony_ci	if (orig)
45162306a36Sopenharmony_ci		free_pages_exact(orig, size);
45262306a36Sopenharmony_ci	if (iova)
45362306a36Sopenharmony_ci		vduse_domain_free_iova(iovad, iova, size);
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	return NULL;
45662306a36Sopenharmony_ci}
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_civoid vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
45962306a36Sopenharmony_ci				void *vaddr, dma_addr_t dma_addr,
46062306a36Sopenharmony_ci				unsigned long attrs)
46162306a36Sopenharmony_ci{
46262306a36Sopenharmony_ci	struct iova_domain *iovad = &domain->consistent_iovad;
46362306a36Sopenharmony_ci	struct vhost_iotlb_map *map;
46462306a36Sopenharmony_ci	struct vdpa_map_file *map_file;
46562306a36Sopenharmony_ci	phys_addr_t pa;
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	spin_lock(&domain->iotlb_lock);
46862306a36Sopenharmony_ci	map = vhost_iotlb_itree_first(domain->iotlb, (u64)dma_addr,
46962306a36Sopenharmony_ci				      (u64)dma_addr + size - 1);
47062306a36Sopenharmony_ci	if (WARN_ON(!map)) {
47162306a36Sopenharmony_ci		spin_unlock(&domain->iotlb_lock);
47262306a36Sopenharmony_ci		return;
47362306a36Sopenharmony_ci	}
47462306a36Sopenharmony_ci	map_file = (struct vdpa_map_file *)map->opaque;
47562306a36Sopenharmony_ci	fput(map_file->file);
47662306a36Sopenharmony_ci	kfree(map_file);
47762306a36Sopenharmony_ci	pa = map->addr;
47862306a36Sopenharmony_ci	vhost_iotlb_map_free(domain->iotlb, map);
47962306a36Sopenharmony_ci	spin_unlock(&domain->iotlb_lock);
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ci	vduse_domain_free_iova(iovad, dma_addr, size);
48262306a36Sopenharmony_ci	free_pages_exact(phys_to_virt(pa), size);
48362306a36Sopenharmony_ci}
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_cistatic vm_fault_t vduse_domain_mmap_fault(struct vm_fault *vmf)
48662306a36Sopenharmony_ci{
48762306a36Sopenharmony_ci	struct vduse_iova_domain *domain = vmf->vma->vm_private_data;
48862306a36Sopenharmony_ci	unsigned long iova = vmf->pgoff << PAGE_SHIFT;
48962306a36Sopenharmony_ci	struct page *page;
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	if (!domain)
49262306a36Sopenharmony_ci		return VM_FAULT_SIGBUS;
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	if (iova < domain->bounce_size)
49562306a36Sopenharmony_ci		page = vduse_domain_get_bounce_page(domain, iova);
49662306a36Sopenharmony_ci	else
49762306a36Sopenharmony_ci		page = vduse_domain_get_coherent_page(domain, iova);
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	if (!page)
50062306a36Sopenharmony_ci		return VM_FAULT_SIGBUS;
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	vmf->page = page;
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci	return 0;
50562306a36Sopenharmony_ci}
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_cistatic const struct vm_operations_struct vduse_domain_mmap_ops = {
50862306a36Sopenharmony_ci	.fault = vduse_domain_mmap_fault,
50962306a36Sopenharmony_ci};
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_cistatic int vduse_domain_mmap(struct file *file, struct vm_area_struct *vma)
51262306a36Sopenharmony_ci{
51362306a36Sopenharmony_ci	struct vduse_iova_domain *domain = file->private_data;
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	vm_flags_set(vma, VM_DONTDUMP | VM_DONTEXPAND);
51662306a36Sopenharmony_ci	vma->vm_private_data = domain;
51762306a36Sopenharmony_ci	vma->vm_ops = &vduse_domain_mmap_ops;
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci	return 0;
52062306a36Sopenharmony_ci}
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_cistatic int vduse_domain_release(struct inode *inode, struct file *file)
52362306a36Sopenharmony_ci{
52462306a36Sopenharmony_ci	struct vduse_iova_domain *domain = file->private_data;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	spin_lock(&domain->iotlb_lock);
52762306a36Sopenharmony_ci	vduse_iotlb_del_range(domain, 0, ULLONG_MAX);
52862306a36Sopenharmony_ci	vduse_domain_remove_user_bounce_pages(domain);
52962306a36Sopenharmony_ci	vduse_domain_free_kernel_bounce_pages(domain);
53062306a36Sopenharmony_ci	spin_unlock(&domain->iotlb_lock);
53162306a36Sopenharmony_ci	put_iova_domain(&domain->stream_iovad);
53262306a36Sopenharmony_ci	put_iova_domain(&domain->consistent_iovad);
53362306a36Sopenharmony_ci	vhost_iotlb_free(domain->iotlb);
53462306a36Sopenharmony_ci	vfree(domain->bounce_maps);
53562306a36Sopenharmony_ci	kfree(domain);
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci	return 0;
53862306a36Sopenharmony_ci}
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_cistatic const struct file_operations vduse_domain_fops = {
54162306a36Sopenharmony_ci	.owner = THIS_MODULE,
54262306a36Sopenharmony_ci	.mmap = vduse_domain_mmap,
54362306a36Sopenharmony_ci	.release = vduse_domain_release,
54462306a36Sopenharmony_ci};
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_civoid vduse_domain_destroy(struct vduse_iova_domain *domain)
54762306a36Sopenharmony_ci{
54862306a36Sopenharmony_ci	fput(domain->file);
54962306a36Sopenharmony_ci}
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_cistruct vduse_iova_domain *
55262306a36Sopenharmony_civduse_domain_create(unsigned long iova_limit, size_t bounce_size)
55362306a36Sopenharmony_ci{
55462306a36Sopenharmony_ci	struct vduse_iova_domain *domain;
55562306a36Sopenharmony_ci	struct file *file;
55662306a36Sopenharmony_ci	struct vduse_bounce_map *map;
55762306a36Sopenharmony_ci	unsigned long pfn, bounce_pfns;
55862306a36Sopenharmony_ci	int ret;
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_ci	bounce_pfns = PAGE_ALIGN(bounce_size) >> PAGE_SHIFT;
56162306a36Sopenharmony_ci	if (iova_limit <= bounce_size)
56262306a36Sopenharmony_ci		return NULL;
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	domain = kzalloc(sizeof(*domain), GFP_KERNEL);
56562306a36Sopenharmony_ci	if (!domain)
56662306a36Sopenharmony_ci		return NULL;
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci	domain->iotlb = vhost_iotlb_alloc(0, 0);
56962306a36Sopenharmony_ci	if (!domain->iotlb)
57062306a36Sopenharmony_ci		goto err_iotlb;
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	domain->iova_limit = iova_limit;
57362306a36Sopenharmony_ci	domain->bounce_size = PAGE_ALIGN(bounce_size);
57462306a36Sopenharmony_ci	domain->bounce_maps = vzalloc(bounce_pfns *
57562306a36Sopenharmony_ci				sizeof(struct vduse_bounce_map));
57662306a36Sopenharmony_ci	if (!domain->bounce_maps)
57762306a36Sopenharmony_ci		goto err_map;
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_ci	for (pfn = 0; pfn < bounce_pfns; pfn++) {
58062306a36Sopenharmony_ci		map = &domain->bounce_maps[pfn];
58162306a36Sopenharmony_ci		map->orig_phys = INVALID_PHYS_ADDR;
58262306a36Sopenharmony_ci	}
58362306a36Sopenharmony_ci	file = anon_inode_getfile("[vduse-domain]", &vduse_domain_fops,
58462306a36Sopenharmony_ci				domain, O_RDWR);
58562306a36Sopenharmony_ci	if (IS_ERR(file))
58662306a36Sopenharmony_ci		goto err_file;
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_ci	domain->file = file;
58962306a36Sopenharmony_ci	rwlock_init(&domain->bounce_lock);
59062306a36Sopenharmony_ci	spin_lock_init(&domain->iotlb_lock);
59162306a36Sopenharmony_ci	init_iova_domain(&domain->stream_iovad,
59262306a36Sopenharmony_ci			PAGE_SIZE, IOVA_START_PFN);
59362306a36Sopenharmony_ci	ret = iova_domain_init_rcaches(&domain->stream_iovad);
59462306a36Sopenharmony_ci	if (ret)
59562306a36Sopenharmony_ci		goto err_iovad_stream;
59662306a36Sopenharmony_ci	init_iova_domain(&domain->consistent_iovad,
59762306a36Sopenharmony_ci			PAGE_SIZE, bounce_pfns);
59862306a36Sopenharmony_ci	ret = iova_domain_init_rcaches(&domain->consistent_iovad);
59962306a36Sopenharmony_ci	if (ret)
60062306a36Sopenharmony_ci		goto err_iovad_consistent;
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci	return domain;
60362306a36Sopenharmony_cierr_iovad_consistent:
60462306a36Sopenharmony_ci	put_iova_domain(&domain->stream_iovad);
60562306a36Sopenharmony_cierr_iovad_stream:
60662306a36Sopenharmony_ci	fput(file);
60762306a36Sopenharmony_cierr_file:
60862306a36Sopenharmony_ci	vfree(domain->bounce_maps);
60962306a36Sopenharmony_cierr_map:
61062306a36Sopenharmony_ci	vhost_iotlb_free(domain->iotlb);
61162306a36Sopenharmony_cierr_iotlb:
61262306a36Sopenharmony_ci	kfree(domain);
61362306a36Sopenharmony_ci	return NULL;
61462306a36Sopenharmony_ci}
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ciint vduse_domain_init(void)
61762306a36Sopenharmony_ci{
61862306a36Sopenharmony_ci	return iova_cache_get();
61962306a36Sopenharmony_ci}
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_civoid vduse_domain_exit(void)
62262306a36Sopenharmony_ci{
62362306a36Sopenharmony_ci	iova_cache_put();
62462306a36Sopenharmony_ci}
625