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