xref: /linux/drivers/accel/amdxdna/amdxdna_ubuf.c (revision ec2e0fb07d789976c601bec19ecced7a501c3705)
1*bd72d4acSLizhi Hou // SPDX-License-Identifier: GPL-2.0
2*bd72d4acSLizhi Hou /*
3*bd72d4acSLizhi Hou  * Copyright (C) 2025, Advanced Micro Devices, Inc.
4*bd72d4acSLizhi Hou  */
5*bd72d4acSLizhi Hou 
6*bd72d4acSLizhi Hou #include <drm/amdxdna_accel.h>
7*bd72d4acSLizhi Hou #include <drm/drm_device.h>
8*bd72d4acSLizhi Hou #include <drm/drm_print.h>
9*bd72d4acSLizhi Hou #include <linux/dma-buf.h>
10*bd72d4acSLizhi Hou #include <linux/pagemap.h>
11*bd72d4acSLizhi Hou #include <linux/vmalloc.h>
12*bd72d4acSLizhi Hou 
13*bd72d4acSLizhi Hou #include "amdxdna_pci_drv.h"
14*bd72d4acSLizhi Hou #include "amdxdna_ubuf.h"
15*bd72d4acSLizhi Hou 
16*bd72d4acSLizhi Hou struct amdxdna_ubuf_priv {
17*bd72d4acSLizhi Hou 	struct page **pages;
18*bd72d4acSLizhi Hou 	u64 nr_pages;
19*bd72d4acSLizhi Hou 	enum amdxdna_ubuf_flag flags;
20*bd72d4acSLizhi Hou 	struct mm_struct *mm;
21*bd72d4acSLizhi Hou };
22*bd72d4acSLizhi Hou 
23*bd72d4acSLizhi Hou static struct sg_table *amdxdna_ubuf_map(struct dma_buf_attachment *attach,
24*bd72d4acSLizhi Hou 					 enum dma_data_direction direction)
25*bd72d4acSLizhi Hou {
26*bd72d4acSLizhi Hou 	struct amdxdna_ubuf_priv *ubuf = attach->dmabuf->priv;
27*bd72d4acSLizhi Hou 	struct sg_table *sg;
28*bd72d4acSLizhi Hou 	int ret;
29*bd72d4acSLizhi Hou 
30*bd72d4acSLizhi Hou 	sg = kzalloc(sizeof(*sg), GFP_KERNEL);
31*bd72d4acSLizhi Hou 	if (!sg)
32*bd72d4acSLizhi Hou 		return ERR_PTR(-ENOMEM);
33*bd72d4acSLizhi Hou 
34*bd72d4acSLizhi Hou 	ret = sg_alloc_table_from_pages(sg, ubuf->pages, ubuf->nr_pages, 0,
35*bd72d4acSLizhi Hou 					ubuf->nr_pages << PAGE_SHIFT, GFP_KERNEL);
36*bd72d4acSLizhi Hou 	if (ret)
37*bd72d4acSLizhi Hou 		return ERR_PTR(ret);
38*bd72d4acSLizhi Hou 
39*bd72d4acSLizhi Hou 	if (ubuf->flags & AMDXDNA_UBUF_FLAG_MAP_DMA) {
40*bd72d4acSLizhi Hou 		ret = dma_map_sgtable(attach->dev, sg, direction, 0);
41*bd72d4acSLizhi Hou 		if (ret)
42*bd72d4acSLizhi Hou 			return ERR_PTR(ret);
43*bd72d4acSLizhi Hou 	}
44*bd72d4acSLizhi Hou 
45*bd72d4acSLizhi Hou 	return sg;
46*bd72d4acSLizhi Hou }
47*bd72d4acSLizhi Hou 
48*bd72d4acSLizhi Hou static void amdxdna_ubuf_unmap(struct dma_buf_attachment *attach,
49*bd72d4acSLizhi Hou 			       struct sg_table *sg,
50*bd72d4acSLizhi Hou 			       enum dma_data_direction direction)
51*bd72d4acSLizhi Hou {
52*bd72d4acSLizhi Hou 	struct amdxdna_ubuf_priv *ubuf = attach->dmabuf->priv;
53*bd72d4acSLizhi Hou 
54*bd72d4acSLizhi Hou 	if (ubuf->flags & AMDXDNA_UBUF_FLAG_MAP_DMA)
55*bd72d4acSLizhi Hou 		dma_unmap_sgtable(attach->dev, sg, direction, 0);
56*bd72d4acSLizhi Hou 
57*bd72d4acSLizhi Hou 	sg_free_table(sg);
58*bd72d4acSLizhi Hou 	kfree(sg);
59*bd72d4acSLizhi Hou }
60*bd72d4acSLizhi Hou 
61*bd72d4acSLizhi Hou static void amdxdna_ubuf_release(struct dma_buf *dbuf)
62*bd72d4acSLizhi Hou {
63*bd72d4acSLizhi Hou 	struct amdxdna_ubuf_priv *ubuf = dbuf->priv;
64*bd72d4acSLizhi Hou 
65*bd72d4acSLizhi Hou 	unpin_user_pages(ubuf->pages, ubuf->nr_pages);
66*bd72d4acSLizhi Hou 	kvfree(ubuf->pages);
67*bd72d4acSLizhi Hou 	atomic64_sub(ubuf->nr_pages, &ubuf->mm->pinned_vm);
68*bd72d4acSLizhi Hou 	mmdrop(ubuf->mm);
69*bd72d4acSLizhi Hou 	kfree(ubuf);
70*bd72d4acSLizhi Hou }
71*bd72d4acSLizhi Hou 
72*bd72d4acSLizhi Hou static vm_fault_t amdxdna_ubuf_vm_fault(struct vm_fault *vmf)
73*bd72d4acSLizhi Hou {
74*bd72d4acSLizhi Hou 	struct vm_area_struct *vma = vmf->vma;
75*bd72d4acSLizhi Hou 	struct amdxdna_ubuf_priv *ubuf;
76*bd72d4acSLizhi Hou 	unsigned long pfn;
77*bd72d4acSLizhi Hou 	pgoff_t pgoff;
78*bd72d4acSLizhi Hou 
79*bd72d4acSLizhi Hou 	ubuf = vma->vm_private_data;
80*bd72d4acSLizhi Hou 	pgoff = (vmf->address - vma->vm_start) >> PAGE_SHIFT;
81*bd72d4acSLizhi Hou 
82*bd72d4acSLizhi Hou 	pfn = page_to_pfn(ubuf->pages[pgoff]);
83*bd72d4acSLizhi Hou 	return vmf_insert_pfn(vma, vmf->address, pfn);
84*bd72d4acSLizhi Hou }
85*bd72d4acSLizhi Hou 
86*bd72d4acSLizhi Hou static const struct vm_operations_struct amdxdna_ubuf_vm_ops = {
87*bd72d4acSLizhi Hou 	.fault = amdxdna_ubuf_vm_fault,
88*bd72d4acSLizhi Hou };
89*bd72d4acSLizhi Hou 
90*bd72d4acSLizhi Hou static int amdxdna_ubuf_mmap(struct dma_buf *dbuf, struct vm_area_struct *vma)
91*bd72d4acSLizhi Hou {
92*bd72d4acSLizhi Hou 	struct amdxdna_ubuf_priv *ubuf = dbuf->priv;
93*bd72d4acSLizhi Hou 
94*bd72d4acSLizhi Hou 	vma->vm_ops = &amdxdna_ubuf_vm_ops;
95*bd72d4acSLizhi Hou 	vma->vm_private_data = ubuf;
96*bd72d4acSLizhi Hou 	vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
97*bd72d4acSLizhi Hou 
98*bd72d4acSLizhi Hou 	return 0;
99*bd72d4acSLizhi Hou }
100*bd72d4acSLizhi Hou 
101*bd72d4acSLizhi Hou static int amdxdna_ubuf_vmap(struct dma_buf *dbuf, struct iosys_map *map)
102*bd72d4acSLizhi Hou {
103*bd72d4acSLizhi Hou 	struct amdxdna_ubuf_priv *ubuf = dbuf->priv;
104*bd72d4acSLizhi Hou 	void *kva;
105*bd72d4acSLizhi Hou 
106*bd72d4acSLizhi Hou 	kva = vmap(ubuf->pages, ubuf->nr_pages, VM_MAP, PAGE_KERNEL);
107*bd72d4acSLizhi Hou 	if (!kva)
108*bd72d4acSLizhi Hou 		return -EINVAL;
109*bd72d4acSLizhi Hou 
110*bd72d4acSLizhi Hou 	iosys_map_set_vaddr(map, kva);
111*bd72d4acSLizhi Hou 	return 0;
112*bd72d4acSLizhi Hou }
113*bd72d4acSLizhi Hou 
114*bd72d4acSLizhi Hou static void amdxdna_ubuf_vunmap(struct dma_buf *dbuf, struct iosys_map *map)
115*bd72d4acSLizhi Hou {
116*bd72d4acSLizhi Hou 	vunmap(map->vaddr);
117*bd72d4acSLizhi Hou }
118*bd72d4acSLizhi Hou 
119*bd72d4acSLizhi Hou static const struct dma_buf_ops amdxdna_ubuf_dmabuf_ops = {
120*bd72d4acSLizhi Hou 	.map_dma_buf = amdxdna_ubuf_map,
121*bd72d4acSLizhi Hou 	.unmap_dma_buf = amdxdna_ubuf_unmap,
122*bd72d4acSLizhi Hou 	.release = amdxdna_ubuf_release,
123*bd72d4acSLizhi Hou 	.mmap = amdxdna_ubuf_mmap,
124*bd72d4acSLizhi Hou 	.vmap = amdxdna_ubuf_vmap,
125*bd72d4acSLizhi Hou 	.vunmap = amdxdna_ubuf_vunmap,
126*bd72d4acSLizhi Hou };
127*bd72d4acSLizhi Hou 
128*bd72d4acSLizhi Hou struct dma_buf *amdxdna_get_ubuf(struct drm_device *dev,
129*bd72d4acSLizhi Hou 				 enum amdxdna_ubuf_flag flags,
130*bd72d4acSLizhi Hou 				 u32 num_entries, void __user *va_entries)
131*bd72d4acSLizhi Hou {
132*bd72d4acSLizhi Hou 	struct amdxdna_dev *xdna = to_xdna_dev(dev);
133*bd72d4acSLizhi Hou 	unsigned long lock_limit, new_pinned;
134*bd72d4acSLizhi Hou 	struct amdxdna_drm_va_entry *va_ent;
135*bd72d4acSLizhi Hou 	struct amdxdna_ubuf_priv *ubuf;
136*bd72d4acSLizhi Hou 	u32 npages, start = 0;
137*bd72d4acSLizhi Hou 	struct dma_buf *dbuf;
138*bd72d4acSLizhi Hou 	int i, ret;
139*bd72d4acSLizhi Hou 	DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
140*bd72d4acSLizhi Hou 
141*bd72d4acSLizhi Hou 	if (!can_do_mlock())
142*bd72d4acSLizhi Hou 		return ERR_PTR(-EPERM);
143*bd72d4acSLizhi Hou 
144*bd72d4acSLizhi Hou 	ubuf = kzalloc(sizeof(*ubuf), GFP_KERNEL);
145*bd72d4acSLizhi Hou 	if (!ubuf)
146*bd72d4acSLizhi Hou 		return ERR_PTR(-ENOMEM);
147*bd72d4acSLizhi Hou 
148*bd72d4acSLizhi Hou 	ubuf->flags = flags;
149*bd72d4acSLizhi Hou 	ubuf->mm = current->mm;
150*bd72d4acSLizhi Hou 	mmgrab(ubuf->mm);
151*bd72d4acSLizhi Hou 
152*bd72d4acSLizhi Hou 	va_ent = kvcalloc(num_entries, sizeof(*va_ent), GFP_KERNEL);
153*bd72d4acSLizhi Hou 	if (!va_ent) {
154*bd72d4acSLizhi Hou 		ret = -ENOMEM;
155*bd72d4acSLizhi Hou 		goto free_ubuf;
156*bd72d4acSLizhi Hou 	}
157*bd72d4acSLizhi Hou 
158*bd72d4acSLizhi Hou 	if (copy_from_user(va_ent, va_entries, sizeof(*va_ent) * num_entries)) {
159*bd72d4acSLizhi Hou 		XDNA_DBG(xdna, "Access va entries failed");
160*bd72d4acSLizhi Hou 		ret = -EINVAL;
161*bd72d4acSLizhi Hou 		goto free_ent;
162*bd72d4acSLizhi Hou 	}
163*bd72d4acSLizhi Hou 
164*bd72d4acSLizhi Hou 	for (i = 0, exp_info.size = 0; i < num_entries; i++) {
165*bd72d4acSLizhi Hou 		if (!IS_ALIGNED(va_ent[i].vaddr, PAGE_SIZE) ||
166*bd72d4acSLizhi Hou 		    !IS_ALIGNED(va_ent[i].len, PAGE_SIZE)) {
167*bd72d4acSLizhi Hou 			XDNA_ERR(xdna, "Invalid address or len %llx, %llx",
168*bd72d4acSLizhi Hou 				 va_ent[i].vaddr, va_ent[i].len);
169*bd72d4acSLizhi Hou 			ret = -EINVAL;
170*bd72d4acSLizhi Hou 			goto free_ent;
171*bd72d4acSLizhi Hou 		}
172*bd72d4acSLizhi Hou 
173*bd72d4acSLizhi Hou 		exp_info.size += va_ent[i].len;
174*bd72d4acSLizhi Hou 	}
175*bd72d4acSLizhi Hou 
176*bd72d4acSLizhi Hou 	ubuf->nr_pages = exp_info.size >> PAGE_SHIFT;
177*bd72d4acSLizhi Hou 	lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
178*bd72d4acSLizhi Hou 	new_pinned = atomic64_add_return(ubuf->nr_pages, &ubuf->mm->pinned_vm);
179*bd72d4acSLizhi Hou 	if (new_pinned > lock_limit && !capable(CAP_IPC_LOCK)) {
180*bd72d4acSLizhi Hou 		XDNA_DBG(xdna, "New pin %ld, limit %ld, cap %d",
181*bd72d4acSLizhi Hou 			 new_pinned, lock_limit, capable(CAP_IPC_LOCK));
182*bd72d4acSLizhi Hou 		ret = -ENOMEM;
183*bd72d4acSLizhi Hou 		goto sub_pin_cnt;
184*bd72d4acSLizhi Hou 	}
185*bd72d4acSLizhi Hou 
186*bd72d4acSLizhi Hou 	ubuf->pages = kvmalloc_array(ubuf->nr_pages, sizeof(*ubuf->pages), GFP_KERNEL);
187*bd72d4acSLizhi Hou 	if (!ubuf->pages) {
188*bd72d4acSLizhi Hou 		ret = -ENOMEM;
189*bd72d4acSLizhi Hou 		goto sub_pin_cnt;
190*bd72d4acSLizhi Hou 	}
191*bd72d4acSLizhi Hou 
192*bd72d4acSLizhi Hou 	for (i = 0; i < num_entries; i++) {
193*bd72d4acSLizhi Hou 		npages = va_ent[i].len >> PAGE_SHIFT;
194*bd72d4acSLizhi Hou 
195*bd72d4acSLizhi Hou 		ret = pin_user_pages_fast(va_ent[i].vaddr, npages,
196*bd72d4acSLizhi Hou 					  FOLL_WRITE | FOLL_LONGTERM,
197*bd72d4acSLizhi Hou 					  &ubuf->pages[start]);
198*bd72d4acSLizhi Hou 		if (ret < 0 || ret != npages) {
199*bd72d4acSLizhi Hou 			ret = -ENOMEM;
200*bd72d4acSLizhi Hou 			XDNA_ERR(xdna, "Failed to pin pages ret %d", ret);
201*bd72d4acSLizhi Hou 			goto destroy_pages;
202*bd72d4acSLizhi Hou 		}
203*bd72d4acSLizhi Hou 
204*bd72d4acSLizhi Hou 		start += ret;
205*bd72d4acSLizhi Hou 	}
206*bd72d4acSLizhi Hou 
207*bd72d4acSLizhi Hou 	exp_info.ops = &amdxdna_ubuf_dmabuf_ops;
208*bd72d4acSLizhi Hou 	exp_info.priv = ubuf;
209*bd72d4acSLizhi Hou 	exp_info.flags = O_RDWR | O_CLOEXEC;
210*bd72d4acSLizhi Hou 
211*bd72d4acSLizhi Hou 	dbuf = dma_buf_export(&exp_info);
212*bd72d4acSLizhi Hou 	if (IS_ERR(dbuf)) {
213*bd72d4acSLizhi Hou 		ret = PTR_ERR(dbuf);
214*bd72d4acSLizhi Hou 		goto destroy_pages;
215*bd72d4acSLizhi Hou 	}
216*bd72d4acSLizhi Hou 	kvfree(va_ent);
217*bd72d4acSLizhi Hou 
218*bd72d4acSLizhi Hou 	return dbuf;
219*bd72d4acSLizhi Hou 
220*bd72d4acSLizhi Hou destroy_pages:
221*bd72d4acSLizhi Hou 	if (start)
222*bd72d4acSLizhi Hou 		unpin_user_pages(ubuf->pages, start);
223*bd72d4acSLizhi Hou 	kvfree(ubuf->pages);
224*bd72d4acSLizhi Hou sub_pin_cnt:
225*bd72d4acSLizhi Hou 	atomic64_sub(ubuf->nr_pages, &ubuf->mm->pinned_vm);
226*bd72d4acSLizhi Hou free_ent:
227*bd72d4acSLizhi Hou 	kvfree(va_ent);
228*bd72d4acSLizhi Hou free_ubuf:
229*bd72d4acSLizhi Hou 	mmdrop(ubuf->mm);
230*bd72d4acSLizhi Hou 	kfree(ubuf);
231*bd72d4acSLizhi Hou 	return ERR_PTR(ret);
232*bd72d4acSLizhi Hou }
233