xref: /openbmc/linux/net/xdp/xdp_umem.c (revision bbff2f321a864ee07c9d3d1245af498023146951)
1c0c77d8fSBjörn Töpel // SPDX-License-Identifier: GPL-2.0
2c0c77d8fSBjörn Töpel /* XDP user-space packet buffer
3c0c77d8fSBjörn Töpel  * Copyright(c) 2018 Intel Corporation.
4c0c77d8fSBjörn Töpel  */
5c0c77d8fSBjörn Töpel 
6c0c77d8fSBjörn Töpel #include <linux/init.h>
7c0c77d8fSBjörn Töpel #include <linux/sched/mm.h>
8c0c77d8fSBjörn Töpel #include <linux/sched/signal.h>
9c0c77d8fSBjörn Töpel #include <linux/sched/task.h>
10c0c77d8fSBjörn Töpel #include <linux/uaccess.h>
11c0c77d8fSBjörn Töpel #include <linux/slab.h>
12c0c77d8fSBjörn Töpel #include <linux/bpf.h>
13c0c77d8fSBjörn Töpel #include <linux/mm.h>
14c0c77d8fSBjörn Töpel 
15c0c77d8fSBjörn Töpel #include "xdp_umem.h"
16c0c77d8fSBjörn Töpel 
17*bbff2f32SBjörn Töpel #define XDP_UMEM_MIN_CHUNK_SIZE 2048
18c0c77d8fSBjörn Töpel 
19c0c77d8fSBjörn Töpel static void xdp_umem_unpin_pages(struct xdp_umem *umem)
20c0c77d8fSBjörn Töpel {
21c0c77d8fSBjörn Töpel 	unsigned int i;
22c0c77d8fSBjörn Töpel 
23c0c77d8fSBjörn Töpel 	for (i = 0; i < umem->npgs; i++) {
24c0c77d8fSBjörn Töpel 		struct page *page = umem->pgs[i];
25c0c77d8fSBjörn Töpel 
26c0c77d8fSBjörn Töpel 		set_page_dirty_lock(page);
27c0c77d8fSBjörn Töpel 		put_page(page);
28c0c77d8fSBjörn Töpel 	}
29c0c77d8fSBjörn Töpel 
30c0c77d8fSBjörn Töpel 	kfree(umem->pgs);
31c0c77d8fSBjörn Töpel 	umem->pgs = NULL;
32c0c77d8fSBjörn Töpel }
33c0c77d8fSBjörn Töpel 
34c0c77d8fSBjörn Töpel static void xdp_umem_unaccount_pages(struct xdp_umem *umem)
35c0c77d8fSBjörn Töpel {
36c0c77d8fSBjörn Töpel 	atomic_long_sub(umem->npgs, &umem->user->locked_vm);
37c0c77d8fSBjörn Töpel 	free_uid(umem->user);
38c0c77d8fSBjörn Töpel }
39c0c77d8fSBjörn Töpel 
40c0c77d8fSBjörn Töpel static void xdp_umem_release(struct xdp_umem *umem)
41c0c77d8fSBjörn Töpel {
42c0c77d8fSBjörn Töpel 	struct task_struct *task;
43c0c77d8fSBjörn Töpel 	struct mm_struct *mm;
44c0c77d8fSBjörn Töpel 
45423f3832SMagnus Karlsson 	if (umem->fq) {
46423f3832SMagnus Karlsson 		xskq_destroy(umem->fq);
47423f3832SMagnus Karlsson 		umem->fq = NULL;
48423f3832SMagnus Karlsson 	}
49423f3832SMagnus Karlsson 
50fe230832SMagnus Karlsson 	if (umem->cq) {
51fe230832SMagnus Karlsson 		xskq_destroy(umem->cq);
52fe230832SMagnus Karlsson 		umem->cq = NULL;
53fe230832SMagnus Karlsson 	}
54fe230832SMagnus Karlsson 
55c0c77d8fSBjörn Töpel 	xdp_umem_unpin_pages(umem);
56c0c77d8fSBjörn Töpel 
57c0c77d8fSBjörn Töpel 	task = get_pid_task(umem->pid, PIDTYPE_PID);
58c0c77d8fSBjörn Töpel 	put_pid(umem->pid);
59c0c77d8fSBjörn Töpel 	if (!task)
60c0c77d8fSBjörn Töpel 		goto out;
61c0c77d8fSBjörn Töpel 	mm = get_task_mm(task);
62c0c77d8fSBjörn Töpel 	put_task_struct(task);
63c0c77d8fSBjörn Töpel 	if (!mm)
64c0c77d8fSBjörn Töpel 		goto out;
65c0c77d8fSBjörn Töpel 
66c0c77d8fSBjörn Töpel 	mmput(mm);
67c0c77d8fSBjörn Töpel 	xdp_umem_unaccount_pages(umem);
68c0c77d8fSBjörn Töpel out:
69c0c77d8fSBjörn Töpel 	kfree(umem);
70c0c77d8fSBjörn Töpel }
71c0c77d8fSBjörn Töpel 
72c0c77d8fSBjörn Töpel static void xdp_umem_release_deferred(struct work_struct *work)
73c0c77d8fSBjörn Töpel {
74c0c77d8fSBjörn Töpel 	struct xdp_umem *umem = container_of(work, struct xdp_umem, work);
75c0c77d8fSBjörn Töpel 
76c0c77d8fSBjörn Töpel 	xdp_umem_release(umem);
77c0c77d8fSBjörn Töpel }
78c0c77d8fSBjörn Töpel 
79c0c77d8fSBjörn Töpel void xdp_get_umem(struct xdp_umem *umem)
80c0c77d8fSBjörn Töpel {
81d3b42f14SBjörn Töpel 	refcount_inc(&umem->users);
82c0c77d8fSBjörn Töpel }
83c0c77d8fSBjörn Töpel 
84c0c77d8fSBjörn Töpel void xdp_put_umem(struct xdp_umem *umem)
85c0c77d8fSBjörn Töpel {
86c0c77d8fSBjörn Töpel 	if (!umem)
87c0c77d8fSBjörn Töpel 		return;
88c0c77d8fSBjörn Töpel 
89d3b42f14SBjörn Töpel 	if (refcount_dec_and_test(&umem->users)) {
90c0c77d8fSBjörn Töpel 		INIT_WORK(&umem->work, xdp_umem_release_deferred);
91c0c77d8fSBjörn Töpel 		schedule_work(&umem->work);
92c0c77d8fSBjörn Töpel 	}
93c0c77d8fSBjörn Töpel }
94c0c77d8fSBjörn Töpel 
95c0c77d8fSBjörn Töpel static int xdp_umem_pin_pages(struct xdp_umem *umem)
96c0c77d8fSBjörn Töpel {
97c0c77d8fSBjörn Töpel 	unsigned int gup_flags = FOLL_WRITE;
98c0c77d8fSBjörn Töpel 	long npgs;
99c0c77d8fSBjörn Töpel 	int err;
100c0c77d8fSBjörn Töpel 
101c0c77d8fSBjörn Töpel 	umem->pgs = kcalloc(umem->npgs, sizeof(*umem->pgs), GFP_KERNEL);
102c0c77d8fSBjörn Töpel 	if (!umem->pgs)
103c0c77d8fSBjörn Töpel 		return -ENOMEM;
104c0c77d8fSBjörn Töpel 
105c0c77d8fSBjörn Töpel 	down_write(&current->mm->mmap_sem);
106c0c77d8fSBjörn Töpel 	npgs = get_user_pages(umem->address, umem->npgs,
107c0c77d8fSBjörn Töpel 			      gup_flags, &umem->pgs[0], NULL);
108c0c77d8fSBjörn Töpel 	up_write(&current->mm->mmap_sem);
109c0c77d8fSBjörn Töpel 
110c0c77d8fSBjörn Töpel 	if (npgs != umem->npgs) {
111c0c77d8fSBjörn Töpel 		if (npgs >= 0) {
112c0c77d8fSBjörn Töpel 			umem->npgs = npgs;
113c0c77d8fSBjörn Töpel 			err = -ENOMEM;
114c0c77d8fSBjörn Töpel 			goto out_pin;
115c0c77d8fSBjörn Töpel 		}
116c0c77d8fSBjörn Töpel 		err = npgs;
117c0c77d8fSBjörn Töpel 		goto out_pgs;
118c0c77d8fSBjörn Töpel 	}
119c0c77d8fSBjörn Töpel 	return 0;
120c0c77d8fSBjörn Töpel 
121c0c77d8fSBjörn Töpel out_pin:
122c0c77d8fSBjörn Töpel 	xdp_umem_unpin_pages(umem);
123c0c77d8fSBjörn Töpel out_pgs:
124c0c77d8fSBjörn Töpel 	kfree(umem->pgs);
125c0c77d8fSBjörn Töpel 	umem->pgs = NULL;
126c0c77d8fSBjörn Töpel 	return err;
127c0c77d8fSBjörn Töpel }
128c0c77d8fSBjörn Töpel 
129c0c77d8fSBjörn Töpel static int xdp_umem_account_pages(struct xdp_umem *umem)
130c0c77d8fSBjörn Töpel {
131c0c77d8fSBjörn Töpel 	unsigned long lock_limit, new_npgs, old_npgs;
132c0c77d8fSBjörn Töpel 
133c0c77d8fSBjörn Töpel 	if (capable(CAP_IPC_LOCK))
134c0c77d8fSBjörn Töpel 		return 0;
135c0c77d8fSBjörn Töpel 
136c0c77d8fSBjörn Töpel 	lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
137c0c77d8fSBjörn Töpel 	umem->user = get_uid(current_user());
138c0c77d8fSBjörn Töpel 
139c0c77d8fSBjörn Töpel 	do {
140c0c77d8fSBjörn Töpel 		old_npgs = atomic_long_read(&umem->user->locked_vm);
141c0c77d8fSBjörn Töpel 		new_npgs = old_npgs + umem->npgs;
142c0c77d8fSBjörn Töpel 		if (new_npgs > lock_limit) {
143c0c77d8fSBjörn Töpel 			free_uid(umem->user);
144c0c77d8fSBjörn Töpel 			umem->user = NULL;
145c0c77d8fSBjörn Töpel 			return -ENOBUFS;
146c0c77d8fSBjörn Töpel 		}
147c0c77d8fSBjörn Töpel 	} while (atomic_long_cmpxchg(&umem->user->locked_vm, old_npgs,
148c0c77d8fSBjörn Töpel 				     new_npgs) != old_npgs);
149c0c77d8fSBjörn Töpel 	return 0;
150c0c77d8fSBjörn Töpel }
151c0c77d8fSBjörn Töpel 
152a49049eaSBjörn Töpel static int xdp_umem_reg(struct xdp_umem *umem, struct xdp_umem_reg *mr)
153c0c77d8fSBjörn Töpel {
154*bbff2f32SBjörn Töpel 	u32 chunk_size = mr->chunk_size, headroom = mr->headroom;
155*bbff2f32SBjörn Töpel 	unsigned int chunks, chunks_per_page;
156c0c77d8fSBjörn Töpel 	u64 addr = mr->addr, size = mr->len;
157c0c77d8fSBjörn Töpel 	int size_chk, err;
158c0c77d8fSBjörn Töpel 
159*bbff2f32SBjörn Töpel 	if (chunk_size < XDP_UMEM_MIN_CHUNK_SIZE || chunk_size > PAGE_SIZE) {
160c0c77d8fSBjörn Töpel 		/* Strictly speaking we could support this, if:
161c0c77d8fSBjörn Töpel 		 * - huge pages, or*
162c0c77d8fSBjörn Töpel 		 * - using an IOMMU, or
163c0c77d8fSBjörn Töpel 		 * - making sure the memory area is consecutive
164c0c77d8fSBjörn Töpel 		 * but for now, we simply say "computer says no".
165c0c77d8fSBjörn Töpel 		 */
166c0c77d8fSBjörn Töpel 		return -EINVAL;
167c0c77d8fSBjörn Töpel 	}
168c0c77d8fSBjörn Töpel 
169*bbff2f32SBjörn Töpel 	if (!is_power_of_2(chunk_size))
170c0c77d8fSBjörn Töpel 		return -EINVAL;
171c0c77d8fSBjörn Töpel 
172c0c77d8fSBjörn Töpel 	if (!PAGE_ALIGNED(addr)) {
173c0c77d8fSBjörn Töpel 		/* Memory area has to be page size aligned. For
174c0c77d8fSBjörn Töpel 		 * simplicity, this might change.
175c0c77d8fSBjörn Töpel 		 */
176c0c77d8fSBjörn Töpel 		return -EINVAL;
177c0c77d8fSBjörn Töpel 	}
178c0c77d8fSBjörn Töpel 
179c0c77d8fSBjörn Töpel 	if ((addr + size) < addr)
180c0c77d8fSBjörn Töpel 		return -EINVAL;
181c0c77d8fSBjörn Töpel 
182*bbff2f32SBjörn Töpel 	chunks = (unsigned int)div_u64(size, chunk_size);
183*bbff2f32SBjörn Töpel 	if (chunks == 0)
184c0c77d8fSBjörn Töpel 		return -EINVAL;
185c0c77d8fSBjörn Töpel 
186*bbff2f32SBjörn Töpel 	chunks_per_page = PAGE_SIZE / chunk_size;
187*bbff2f32SBjörn Töpel 	if (chunks < chunks_per_page || chunks % chunks_per_page)
188c0c77d8fSBjörn Töpel 		return -EINVAL;
189c0c77d8fSBjörn Töpel 
190*bbff2f32SBjörn Töpel 	headroom = ALIGN(headroom, 64);
191c0c77d8fSBjörn Töpel 
192*bbff2f32SBjörn Töpel 	size_chk = chunk_size - headroom - XDP_PACKET_HEADROOM;
193c0c77d8fSBjörn Töpel 	if (size_chk < 0)
194c0c77d8fSBjörn Töpel 		return -EINVAL;
195c0c77d8fSBjörn Töpel 
196c0c77d8fSBjörn Töpel 	umem->pid = get_task_pid(current, PIDTYPE_PID);
197c0c77d8fSBjörn Töpel 	umem->address = (unsigned long)addr;
198*bbff2f32SBjörn Töpel 	umem->props.chunk_mask = ~((u64)chunk_size - 1);
199*bbff2f32SBjörn Töpel 	umem->props.size = size;
200*bbff2f32SBjörn Töpel 	umem->headroom = headroom;
201*bbff2f32SBjörn Töpel 	umem->chunk_size_nohr = chunk_size - headroom;
202c0c77d8fSBjörn Töpel 	umem->npgs = size / PAGE_SIZE;
203c0c77d8fSBjörn Töpel 	umem->pgs = NULL;
204c0c77d8fSBjörn Töpel 	umem->user = NULL;
205c0c77d8fSBjörn Töpel 
206d3b42f14SBjörn Töpel 	refcount_set(&umem->users, 1);
207c0c77d8fSBjörn Töpel 
208c0c77d8fSBjörn Töpel 	err = xdp_umem_account_pages(umem);
209c0c77d8fSBjörn Töpel 	if (err)
210c0c77d8fSBjörn Töpel 		goto out;
211c0c77d8fSBjörn Töpel 
212c0c77d8fSBjörn Töpel 	err = xdp_umem_pin_pages(umem);
213c0c77d8fSBjörn Töpel 	if (err)
214c0c77d8fSBjörn Töpel 		goto out_account;
215c0c77d8fSBjörn Töpel 	return 0;
216c0c77d8fSBjörn Töpel 
217c0c77d8fSBjörn Töpel out_account:
218c0c77d8fSBjörn Töpel 	xdp_umem_unaccount_pages(umem);
219c0c77d8fSBjörn Töpel out:
220c0c77d8fSBjörn Töpel 	put_pid(umem->pid);
221c0c77d8fSBjörn Töpel 	return err;
222c0c77d8fSBjörn Töpel }
223965a9909SMagnus Karlsson 
224a49049eaSBjörn Töpel struct xdp_umem *xdp_umem_create(struct xdp_umem_reg *mr)
225a49049eaSBjörn Töpel {
226a49049eaSBjörn Töpel 	struct xdp_umem *umem;
227a49049eaSBjörn Töpel 	int err;
228a49049eaSBjörn Töpel 
229a49049eaSBjörn Töpel 	umem = kzalloc(sizeof(*umem), GFP_KERNEL);
230a49049eaSBjörn Töpel 	if (!umem)
231a49049eaSBjörn Töpel 		return ERR_PTR(-ENOMEM);
232a49049eaSBjörn Töpel 
233a49049eaSBjörn Töpel 	err = xdp_umem_reg(umem, mr);
234a49049eaSBjörn Töpel 	if (err) {
235a49049eaSBjörn Töpel 		kfree(umem);
236a49049eaSBjörn Töpel 		return ERR_PTR(err);
237a49049eaSBjörn Töpel 	}
238a49049eaSBjörn Töpel 
239a49049eaSBjörn Töpel 	return umem;
240a49049eaSBjörn Töpel }
241a49049eaSBjörn Töpel 
242965a9909SMagnus Karlsson bool xdp_umem_validate_queues(struct xdp_umem *umem)
243965a9909SMagnus Karlsson {
244da60cf00SBjörn Töpel 	return umem->fq && umem->cq;
245965a9909SMagnus Karlsson }
246