cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

iova_domain.c (13136B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 * MMU-based software IOTLB.
      4 *
      5 * Copyright (C) 2020-2021 Bytedance Inc. and/or its affiliates. All rights reserved.
      6 *
      7 * Author: Xie Yongji <xieyongji@bytedance.com>
      8 *
      9 */
     10
     11#include <linux/slab.h>
     12#include <linux/file.h>
     13#include <linux/anon_inodes.h>
     14#include <linux/highmem.h>
     15#include <linux/vmalloc.h>
     16#include <linux/vdpa.h>
     17
     18#include "iova_domain.h"
     19
     20static int vduse_iotlb_add_range(struct vduse_iova_domain *domain,
     21				 u64 start, u64 last,
     22				 u64 addr, unsigned int perm,
     23				 struct file *file, u64 offset)
     24{
     25	struct vdpa_map_file *map_file;
     26	int ret;
     27
     28	map_file = kmalloc(sizeof(*map_file), GFP_ATOMIC);
     29	if (!map_file)
     30		return -ENOMEM;
     31
     32	map_file->file = get_file(file);
     33	map_file->offset = offset;
     34
     35	ret = vhost_iotlb_add_range_ctx(domain->iotlb, start, last,
     36					addr, perm, map_file);
     37	if (ret) {
     38		fput(map_file->file);
     39		kfree(map_file);
     40		return ret;
     41	}
     42	return 0;
     43}
     44
     45static void vduse_iotlb_del_range(struct vduse_iova_domain *domain,
     46				  u64 start, u64 last)
     47{
     48	struct vdpa_map_file *map_file;
     49	struct vhost_iotlb_map *map;
     50
     51	while ((map = vhost_iotlb_itree_first(domain->iotlb, start, last))) {
     52		map_file = (struct vdpa_map_file *)map->opaque;
     53		fput(map_file->file);
     54		kfree(map_file);
     55		vhost_iotlb_map_free(domain->iotlb, map);
     56	}
     57}
     58
     59int vduse_domain_set_map(struct vduse_iova_domain *domain,
     60			 struct vhost_iotlb *iotlb)
     61{
     62	struct vdpa_map_file *map_file;
     63	struct vhost_iotlb_map *map;
     64	u64 start = 0ULL, last = ULLONG_MAX;
     65	int ret;
     66
     67	spin_lock(&domain->iotlb_lock);
     68	vduse_iotlb_del_range(domain, start, last);
     69
     70	for (map = vhost_iotlb_itree_first(iotlb, start, last); map;
     71	     map = vhost_iotlb_itree_next(map, start, last)) {
     72		map_file = (struct vdpa_map_file *)map->opaque;
     73		ret = vduse_iotlb_add_range(domain, map->start, map->last,
     74					    map->addr, map->perm,
     75					    map_file->file,
     76					    map_file->offset);
     77		if (ret)
     78			goto err;
     79	}
     80	spin_unlock(&domain->iotlb_lock);
     81
     82	return 0;
     83err:
     84	vduse_iotlb_del_range(domain, start, last);
     85	spin_unlock(&domain->iotlb_lock);
     86	return ret;
     87}
     88
     89void vduse_domain_clear_map(struct vduse_iova_domain *domain,
     90			    struct vhost_iotlb *iotlb)
     91{
     92	struct vhost_iotlb_map *map;
     93	u64 start = 0ULL, last = ULLONG_MAX;
     94
     95	spin_lock(&domain->iotlb_lock);
     96	for (map = vhost_iotlb_itree_first(iotlb, start, last); map;
     97	     map = vhost_iotlb_itree_next(map, start, last)) {
     98		vduse_iotlb_del_range(domain, map->start, map->last);
     99	}
    100	spin_unlock(&domain->iotlb_lock);
    101}
    102
    103static int vduse_domain_map_bounce_page(struct vduse_iova_domain *domain,
    104					 u64 iova, u64 size, u64 paddr)
    105{
    106	struct vduse_bounce_map *map;
    107	u64 last = iova + size - 1;
    108
    109	while (iova <= last) {
    110		map = &domain->bounce_maps[iova >> PAGE_SHIFT];
    111		if (!map->bounce_page) {
    112			map->bounce_page = alloc_page(GFP_ATOMIC);
    113			if (!map->bounce_page)
    114				return -ENOMEM;
    115		}
    116		map->orig_phys = paddr;
    117		paddr += PAGE_SIZE;
    118		iova += PAGE_SIZE;
    119	}
    120	return 0;
    121}
    122
    123static void vduse_domain_unmap_bounce_page(struct vduse_iova_domain *domain,
    124					   u64 iova, u64 size)
    125{
    126	struct vduse_bounce_map *map;
    127	u64 last = iova + size - 1;
    128
    129	while (iova <= last) {
    130		map = &domain->bounce_maps[iova >> PAGE_SHIFT];
    131		map->orig_phys = INVALID_PHYS_ADDR;
    132		iova += PAGE_SIZE;
    133	}
    134}
    135
    136static void do_bounce(phys_addr_t orig, void *addr, size_t size,
    137		      enum dma_data_direction dir)
    138{
    139	unsigned long pfn = PFN_DOWN(orig);
    140	unsigned int offset = offset_in_page(orig);
    141	char *buffer;
    142	unsigned int sz = 0;
    143
    144	while (size) {
    145		sz = min_t(size_t, PAGE_SIZE - offset, size);
    146
    147		buffer = kmap_atomic(pfn_to_page(pfn));
    148		if (dir == DMA_TO_DEVICE)
    149			memcpy(addr, buffer + offset, sz);
    150		else
    151			memcpy(buffer + offset, addr, sz);
    152		kunmap_atomic(buffer);
    153
    154		size -= sz;
    155		pfn++;
    156		addr += sz;
    157		offset = 0;
    158	}
    159}
    160
    161static void vduse_domain_bounce(struct vduse_iova_domain *domain,
    162				dma_addr_t iova, size_t size,
    163				enum dma_data_direction dir)
    164{
    165	struct vduse_bounce_map *map;
    166	unsigned int offset;
    167	void *addr;
    168	size_t sz;
    169
    170	if (iova >= domain->bounce_size)
    171		return;
    172
    173	while (size) {
    174		map = &domain->bounce_maps[iova >> PAGE_SHIFT];
    175		offset = offset_in_page(iova);
    176		sz = min_t(size_t, PAGE_SIZE - offset, size);
    177
    178		if (WARN_ON(!map->bounce_page ||
    179			    map->orig_phys == INVALID_PHYS_ADDR))
    180			return;
    181
    182		addr = page_address(map->bounce_page) + offset;
    183		do_bounce(map->orig_phys + offset, addr, sz, dir);
    184		size -= sz;
    185		iova += sz;
    186	}
    187}
    188
    189static struct page *
    190vduse_domain_get_coherent_page(struct vduse_iova_domain *domain, u64 iova)
    191{
    192	u64 start = iova & PAGE_MASK;
    193	u64 last = start + PAGE_SIZE - 1;
    194	struct vhost_iotlb_map *map;
    195	struct page *page = NULL;
    196
    197	spin_lock(&domain->iotlb_lock);
    198	map = vhost_iotlb_itree_first(domain->iotlb, start, last);
    199	if (!map)
    200		goto out;
    201
    202	page = pfn_to_page((map->addr + iova - map->start) >> PAGE_SHIFT);
    203	get_page(page);
    204out:
    205	spin_unlock(&domain->iotlb_lock);
    206
    207	return page;
    208}
    209
    210static struct page *
    211vduse_domain_get_bounce_page(struct vduse_iova_domain *domain, u64 iova)
    212{
    213	struct vduse_bounce_map *map;
    214	struct page *page = NULL;
    215
    216	spin_lock(&domain->iotlb_lock);
    217	map = &domain->bounce_maps[iova >> PAGE_SHIFT];
    218	if (!map->bounce_page)
    219		goto out;
    220
    221	page = map->bounce_page;
    222	get_page(page);
    223out:
    224	spin_unlock(&domain->iotlb_lock);
    225
    226	return page;
    227}
    228
    229static void
    230vduse_domain_free_bounce_pages(struct vduse_iova_domain *domain)
    231{
    232	struct vduse_bounce_map *map;
    233	unsigned long pfn, bounce_pfns;
    234
    235	bounce_pfns = domain->bounce_size >> PAGE_SHIFT;
    236
    237	for (pfn = 0; pfn < bounce_pfns; pfn++) {
    238		map = &domain->bounce_maps[pfn];
    239		if (WARN_ON(map->orig_phys != INVALID_PHYS_ADDR))
    240			continue;
    241
    242		if (!map->bounce_page)
    243			continue;
    244
    245		__free_page(map->bounce_page);
    246		map->bounce_page = NULL;
    247	}
    248}
    249
    250void vduse_domain_reset_bounce_map(struct vduse_iova_domain *domain)
    251{
    252	if (!domain->bounce_map)
    253		return;
    254
    255	spin_lock(&domain->iotlb_lock);
    256	if (!domain->bounce_map)
    257		goto unlock;
    258
    259	vduse_iotlb_del_range(domain, 0, domain->bounce_size - 1);
    260	domain->bounce_map = 0;
    261unlock:
    262	spin_unlock(&domain->iotlb_lock);
    263}
    264
    265static int vduse_domain_init_bounce_map(struct vduse_iova_domain *domain)
    266{
    267	int ret = 0;
    268
    269	if (domain->bounce_map)
    270		return 0;
    271
    272	spin_lock(&domain->iotlb_lock);
    273	if (domain->bounce_map)
    274		goto unlock;
    275
    276	ret = vduse_iotlb_add_range(domain, 0, domain->bounce_size - 1,
    277				    0, VHOST_MAP_RW, domain->file, 0);
    278	if (ret)
    279		goto unlock;
    280
    281	domain->bounce_map = 1;
    282unlock:
    283	spin_unlock(&domain->iotlb_lock);
    284	return ret;
    285}
    286
    287static dma_addr_t
    288vduse_domain_alloc_iova(struct iova_domain *iovad,
    289			unsigned long size, unsigned long limit)
    290{
    291	unsigned long shift = iova_shift(iovad);
    292	unsigned long iova_len = iova_align(iovad, size) >> shift;
    293	unsigned long iova_pfn;
    294
    295	iova_pfn = alloc_iova_fast(iovad, iova_len, limit >> shift, true);
    296
    297	return (dma_addr_t)iova_pfn << shift;
    298}
    299
    300static void vduse_domain_free_iova(struct iova_domain *iovad,
    301				   dma_addr_t iova, size_t size)
    302{
    303	unsigned long shift = iova_shift(iovad);
    304	unsigned long iova_len = iova_align(iovad, size) >> shift;
    305
    306	free_iova_fast(iovad, iova >> shift, iova_len);
    307}
    308
    309dma_addr_t vduse_domain_map_page(struct vduse_iova_domain *domain,
    310				 struct page *page, unsigned long offset,
    311				 size_t size, enum dma_data_direction dir,
    312				 unsigned long attrs)
    313{
    314	struct iova_domain *iovad = &domain->stream_iovad;
    315	unsigned long limit = domain->bounce_size - 1;
    316	phys_addr_t pa = page_to_phys(page) + offset;
    317	dma_addr_t iova = vduse_domain_alloc_iova(iovad, size, limit);
    318
    319	if (!iova)
    320		return DMA_MAPPING_ERROR;
    321
    322	if (vduse_domain_init_bounce_map(domain))
    323		goto err;
    324
    325	if (vduse_domain_map_bounce_page(domain, (u64)iova, (u64)size, pa))
    326		goto err;
    327
    328	if (dir == DMA_TO_DEVICE || dir == DMA_BIDIRECTIONAL)
    329		vduse_domain_bounce(domain, iova, size, DMA_TO_DEVICE);
    330
    331	return iova;
    332err:
    333	vduse_domain_free_iova(iovad, iova, size);
    334	return DMA_MAPPING_ERROR;
    335}
    336
    337void vduse_domain_unmap_page(struct vduse_iova_domain *domain,
    338			     dma_addr_t dma_addr, size_t size,
    339			     enum dma_data_direction dir, unsigned long attrs)
    340{
    341	struct iova_domain *iovad = &domain->stream_iovad;
    342
    343	if (dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL)
    344		vduse_domain_bounce(domain, dma_addr, size, DMA_FROM_DEVICE);
    345
    346	vduse_domain_unmap_bounce_page(domain, (u64)dma_addr, (u64)size);
    347	vduse_domain_free_iova(iovad, dma_addr, size);
    348}
    349
    350void *vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
    351				  size_t size, dma_addr_t *dma_addr,
    352				  gfp_t flag, unsigned long attrs)
    353{
    354	struct iova_domain *iovad = &domain->consistent_iovad;
    355	unsigned long limit = domain->iova_limit;
    356	dma_addr_t iova = vduse_domain_alloc_iova(iovad, size, limit);
    357	void *orig = alloc_pages_exact(size, flag);
    358
    359	if (!iova || !orig)
    360		goto err;
    361
    362	spin_lock(&domain->iotlb_lock);
    363	if (vduse_iotlb_add_range(domain, (u64)iova, (u64)iova + size - 1,
    364				  virt_to_phys(orig), VHOST_MAP_RW,
    365				  domain->file, (u64)iova)) {
    366		spin_unlock(&domain->iotlb_lock);
    367		goto err;
    368	}
    369	spin_unlock(&domain->iotlb_lock);
    370
    371	*dma_addr = iova;
    372
    373	return orig;
    374err:
    375	*dma_addr = DMA_MAPPING_ERROR;
    376	if (orig)
    377		free_pages_exact(orig, size);
    378	if (iova)
    379		vduse_domain_free_iova(iovad, iova, size);
    380
    381	return NULL;
    382}
    383
    384void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
    385				void *vaddr, dma_addr_t dma_addr,
    386				unsigned long attrs)
    387{
    388	struct iova_domain *iovad = &domain->consistent_iovad;
    389	struct vhost_iotlb_map *map;
    390	struct vdpa_map_file *map_file;
    391	phys_addr_t pa;
    392
    393	spin_lock(&domain->iotlb_lock);
    394	map = vhost_iotlb_itree_first(domain->iotlb, (u64)dma_addr,
    395				      (u64)dma_addr + size - 1);
    396	if (WARN_ON(!map)) {
    397		spin_unlock(&domain->iotlb_lock);
    398		return;
    399	}
    400	map_file = (struct vdpa_map_file *)map->opaque;
    401	fput(map_file->file);
    402	kfree(map_file);
    403	pa = map->addr;
    404	vhost_iotlb_map_free(domain->iotlb, map);
    405	spin_unlock(&domain->iotlb_lock);
    406
    407	vduse_domain_free_iova(iovad, dma_addr, size);
    408	free_pages_exact(phys_to_virt(pa), size);
    409}
    410
    411static vm_fault_t vduse_domain_mmap_fault(struct vm_fault *vmf)
    412{
    413	struct vduse_iova_domain *domain = vmf->vma->vm_private_data;
    414	unsigned long iova = vmf->pgoff << PAGE_SHIFT;
    415	struct page *page;
    416
    417	if (!domain)
    418		return VM_FAULT_SIGBUS;
    419
    420	if (iova < domain->bounce_size)
    421		page = vduse_domain_get_bounce_page(domain, iova);
    422	else
    423		page = vduse_domain_get_coherent_page(domain, iova);
    424
    425	if (!page)
    426		return VM_FAULT_SIGBUS;
    427
    428	vmf->page = page;
    429
    430	return 0;
    431}
    432
    433static const struct vm_operations_struct vduse_domain_mmap_ops = {
    434	.fault = vduse_domain_mmap_fault,
    435};
    436
    437static int vduse_domain_mmap(struct file *file, struct vm_area_struct *vma)
    438{
    439	struct vduse_iova_domain *domain = file->private_data;
    440
    441	vma->vm_flags |= VM_DONTDUMP | VM_DONTEXPAND;
    442	vma->vm_private_data = domain;
    443	vma->vm_ops = &vduse_domain_mmap_ops;
    444
    445	return 0;
    446}
    447
    448static int vduse_domain_release(struct inode *inode, struct file *file)
    449{
    450	struct vduse_iova_domain *domain = file->private_data;
    451
    452	spin_lock(&domain->iotlb_lock);
    453	vduse_iotlb_del_range(domain, 0, ULLONG_MAX);
    454	vduse_domain_free_bounce_pages(domain);
    455	spin_unlock(&domain->iotlb_lock);
    456	put_iova_domain(&domain->stream_iovad);
    457	put_iova_domain(&domain->consistent_iovad);
    458	vhost_iotlb_free(domain->iotlb);
    459	vfree(domain->bounce_maps);
    460	kfree(domain);
    461
    462	return 0;
    463}
    464
    465static const struct file_operations vduse_domain_fops = {
    466	.owner = THIS_MODULE,
    467	.mmap = vduse_domain_mmap,
    468	.release = vduse_domain_release,
    469};
    470
    471void vduse_domain_destroy(struct vduse_iova_domain *domain)
    472{
    473	fput(domain->file);
    474}
    475
    476struct vduse_iova_domain *
    477vduse_domain_create(unsigned long iova_limit, size_t bounce_size)
    478{
    479	struct vduse_iova_domain *domain;
    480	struct file *file;
    481	struct vduse_bounce_map *map;
    482	unsigned long pfn, bounce_pfns;
    483	int ret;
    484
    485	bounce_pfns = PAGE_ALIGN(bounce_size) >> PAGE_SHIFT;
    486	if (iova_limit <= bounce_size)
    487		return NULL;
    488
    489	domain = kzalloc(sizeof(*domain), GFP_KERNEL);
    490	if (!domain)
    491		return NULL;
    492
    493	domain->iotlb = vhost_iotlb_alloc(0, 0);
    494	if (!domain->iotlb)
    495		goto err_iotlb;
    496
    497	domain->iova_limit = iova_limit;
    498	domain->bounce_size = PAGE_ALIGN(bounce_size);
    499	domain->bounce_maps = vzalloc(bounce_pfns *
    500				sizeof(struct vduse_bounce_map));
    501	if (!domain->bounce_maps)
    502		goto err_map;
    503
    504	for (pfn = 0; pfn < bounce_pfns; pfn++) {
    505		map = &domain->bounce_maps[pfn];
    506		map->orig_phys = INVALID_PHYS_ADDR;
    507	}
    508	file = anon_inode_getfile("[vduse-domain]", &vduse_domain_fops,
    509				domain, O_RDWR);
    510	if (IS_ERR(file))
    511		goto err_file;
    512
    513	domain->file = file;
    514	spin_lock_init(&domain->iotlb_lock);
    515	init_iova_domain(&domain->stream_iovad,
    516			PAGE_SIZE, IOVA_START_PFN);
    517	ret = iova_domain_init_rcaches(&domain->stream_iovad);
    518	if (ret)
    519		goto err_iovad_stream;
    520	init_iova_domain(&domain->consistent_iovad,
    521			PAGE_SIZE, bounce_pfns);
    522	ret = iova_domain_init_rcaches(&domain->consistent_iovad);
    523	if (ret)
    524		goto err_iovad_consistent;
    525
    526	return domain;
    527err_iovad_consistent:
    528	put_iova_domain(&domain->stream_iovad);
    529err_iovad_stream:
    530	fput(file);
    531err_file:
    532	vfree(domain->bounce_maps);
    533err_map:
    534	vhost_iotlb_free(domain->iotlb);
    535err_iotlb:
    536	kfree(domain);
    537	return NULL;
    538}
    539
    540int vduse_domain_init(void)
    541{
    542	return iova_cache_get();
    543}
    544
    545void vduse_domain_exit(void)
    546{
    547	iova_cache_put();
    548}