xref: /openbmc/linux/net/xdp/xdp_umem.c (revision 965a990984432cd01a9eb3514c64d86f56704295)
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  * This program is free software; you can redistribute it and/or modify it
6c0c77d8fSBjörn Töpel  * under the terms and conditions of the GNU General Public License,
7c0c77d8fSBjörn Töpel  * version 2, as published by the Free Software Foundation.
8c0c77d8fSBjörn Töpel  *
9c0c77d8fSBjörn Töpel  * This program is distributed in the hope it will be useful, but WITHOUT
10c0c77d8fSBjörn Töpel  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11c0c77d8fSBjörn Töpel  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
12c0c77d8fSBjörn Töpel  * more details.
13c0c77d8fSBjörn Töpel  */
14c0c77d8fSBjörn Töpel 
15c0c77d8fSBjörn Töpel #include <linux/init.h>
16c0c77d8fSBjörn Töpel #include <linux/sched/mm.h>
17c0c77d8fSBjörn Töpel #include <linux/sched/signal.h>
18c0c77d8fSBjörn Töpel #include <linux/sched/task.h>
19c0c77d8fSBjörn Töpel #include <linux/uaccess.h>
20c0c77d8fSBjörn Töpel #include <linux/slab.h>
21c0c77d8fSBjörn Töpel #include <linux/bpf.h>
22c0c77d8fSBjörn Töpel #include <linux/mm.h>
23c0c77d8fSBjörn Töpel 
24c0c77d8fSBjörn Töpel #include "xdp_umem.h"
25c0c77d8fSBjörn Töpel 
26c0c77d8fSBjörn Töpel #define XDP_UMEM_MIN_FRAME_SIZE 2048
27c0c77d8fSBjörn Töpel 
28c0c77d8fSBjörn Töpel int xdp_umem_create(struct xdp_umem **umem)
29c0c77d8fSBjörn Töpel {
30c0c77d8fSBjörn Töpel 	*umem = kzalloc(sizeof(**umem), GFP_KERNEL);
31c0c77d8fSBjörn Töpel 
32c0c77d8fSBjörn Töpel 	if (!(*umem))
33c0c77d8fSBjörn Töpel 		return -ENOMEM;
34c0c77d8fSBjörn Töpel 
35c0c77d8fSBjörn Töpel 	return 0;
36c0c77d8fSBjörn Töpel }
37c0c77d8fSBjörn Töpel 
38c0c77d8fSBjörn Töpel static void xdp_umem_unpin_pages(struct xdp_umem *umem)
39c0c77d8fSBjörn Töpel {
40c0c77d8fSBjörn Töpel 	unsigned int i;
41c0c77d8fSBjörn Töpel 
42c0c77d8fSBjörn Töpel 	if (umem->pgs) {
43c0c77d8fSBjörn Töpel 		for (i = 0; i < umem->npgs; i++) {
44c0c77d8fSBjörn Töpel 			struct page *page = umem->pgs[i];
45c0c77d8fSBjörn Töpel 
46c0c77d8fSBjörn Töpel 			set_page_dirty_lock(page);
47c0c77d8fSBjörn Töpel 			put_page(page);
48c0c77d8fSBjörn Töpel 		}
49c0c77d8fSBjörn Töpel 
50c0c77d8fSBjörn Töpel 		kfree(umem->pgs);
51c0c77d8fSBjörn Töpel 		umem->pgs = NULL;
52c0c77d8fSBjörn Töpel 	}
53c0c77d8fSBjörn Töpel }
54c0c77d8fSBjörn Töpel 
55c0c77d8fSBjörn Töpel static void xdp_umem_unaccount_pages(struct xdp_umem *umem)
56c0c77d8fSBjörn Töpel {
57c0c77d8fSBjörn Töpel 	if (umem->user) {
58c0c77d8fSBjörn Töpel 		atomic_long_sub(umem->npgs, &umem->user->locked_vm);
59c0c77d8fSBjörn Töpel 		free_uid(umem->user);
60c0c77d8fSBjörn Töpel 	}
61c0c77d8fSBjörn Töpel }
62c0c77d8fSBjörn Töpel 
63c0c77d8fSBjörn Töpel static void xdp_umem_release(struct xdp_umem *umem)
64c0c77d8fSBjörn Töpel {
65c0c77d8fSBjörn Töpel 	struct task_struct *task;
66c0c77d8fSBjörn Töpel 	struct mm_struct *mm;
67c0c77d8fSBjörn Töpel 
68423f3832SMagnus Karlsson 	if (umem->fq) {
69423f3832SMagnus Karlsson 		xskq_destroy(umem->fq);
70423f3832SMagnus Karlsson 		umem->fq = NULL;
71423f3832SMagnus Karlsson 	}
72423f3832SMagnus Karlsson 
73c0c77d8fSBjörn Töpel 	if (umem->pgs) {
74c0c77d8fSBjörn Töpel 		xdp_umem_unpin_pages(umem);
75c0c77d8fSBjörn Töpel 
76c0c77d8fSBjörn Töpel 		task = get_pid_task(umem->pid, PIDTYPE_PID);
77c0c77d8fSBjörn Töpel 		put_pid(umem->pid);
78c0c77d8fSBjörn Töpel 		if (!task)
79c0c77d8fSBjörn Töpel 			goto out;
80c0c77d8fSBjörn Töpel 		mm = get_task_mm(task);
81c0c77d8fSBjörn Töpel 		put_task_struct(task);
82c0c77d8fSBjörn Töpel 		if (!mm)
83c0c77d8fSBjörn Töpel 			goto out;
84c0c77d8fSBjörn Töpel 
85c0c77d8fSBjörn Töpel 		mmput(mm);
86c0c77d8fSBjörn Töpel 		umem->pgs = NULL;
87c0c77d8fSBjörn Töpel 	}
88c0c77d8fSBjörn Töpel 
89c0c77d8fSBjörn Töpel 	xdp_umem_unaccount_pages(umem);
90c0c77d8fSBjörn Töpel out:
91c0c77d8fSBjörn Töpel 	kfree(umem);
92c0c77d8fSBjörn Töpel }
93c0c77d8fSBjörn Töpel 
94c0c77d8fSBjörn Töpel static void xdp_umem_release_deferred(struct work_struct *work)
95c0c77d8fSBjörn Töpel {
96c0c77d8fSBjörn Töpel 	struct xdp_umem *umem = container_of(work, struct xdp_umem, work);
97c0c77d8fSBjörn Töpel 
98c0c77d8fSBjörn Töpel 	xdp_umem_release(umem);
99c0c77d8fSBjörn Töpel }
100c0c77d8fSBjörn Töpel 
101c0c77d8fSBjörn Töpel void xdp_get_umem(struct xdp_umem *umem)
102c0c77d8fSBjörn Töpel {
103c0c77d8fSBjörn Töpel 	atomic_inc(&umem->users);
104c0c77d8fSBjörn Töpel }
105c0c77d8fSBjörn Töpel 
106c0c77d8fSBjörn Töpel void xdp_put_umem(struct xdp_umem *umem)
107c0c77d8fSBjörn Töpel {
108c0c77d8fSBjörn Töpel 	if (!umem)
109c0c77d8fSBjörn Töpel 		return;
110c0c77d8fSBjörn Töpel 
111c0c77d8fSBjörn Töpel 	if (atomic_dec_and_test(&umem->users)) {
112c0c77d8fSBjörn Töpel 		INIT_WORK(&umem->work, xdp_umem_release_deferred);
113c0c77d8fSBjörn Töpel 		schedule_work(&umem->work);
114c0c77d8fSBjörn Töpel 	}
115c0c77d8fSBjörn Töpel }
116c0c77d8fSBjörn Töpel 
117c0c77d8fSBjörn Töpel static int xdp_umem_pin_pages(struct xdp_umem *umem)
118c0c77d8fSBjörn Töpel {
119c0c77d8fSBjörn Töpel 	unsigned int gup_flags = FOLL_WRITE;
120c0c77d8fSBjörn Töpel 	long npgs;
121c0c77d8fSBjörn Töpel 	int err;
122c0c77d8fSBjörn Töpel 
123c0c77d8fSBjörn Töpel 	umem->pgs = kcalloc(umem->npgs, sizeof(*umem->pgs), GFP_KERNEL);
124c0c77d8fSBjörn Töpel 	if (!umem->pgs)
125c0c77d8fSBjörn Töpel 		return -ENOMEM;
126c0c77d8fSBjörn Töpel 
127c0c77d8fSBjörn Töpel 	down_write(&current->mm->mmap_sem);
128c0c77d8fSBjörn Töpel 	npgs = get_user_pages(umem->address, umem->npgs,
129c0c77d8fSBjörn Töpel 			      gup_flags, &umem->pgs[0], NULL);
130c0c77d8fSBjörn Töpel 	up_write(&current->mm->mmap_sem);
131c0c77d8fSBjörn Töpel 
132c0c77d8fSBjörn Töpel 	if (npgs != umem->npgs) {
133c0c77d8fSBjörn Töpel 		if (npgs >= 0) {
134c0c77d8fSBjörn Töpel 			umem->npgs = npgs;
135c0c77d8fSBjörn Töpel 			err = -ENOMEM;
136c0c77d8fSBjörn Töpel 			goto out_pin;
137c0c77d8fSBjörn Töpel 		}
138c0c77d8fSBjörn Töpel 		err = npgs;
139c0c77d8fSBjörn Töpel 		goto out_pgs;
140c0c77d8fSBjörn Töpel 	}
141c0c77d8fSBjörn Töpel 	return 0;
142c0c77d8fSBjörn Töpel 
143c0c77d8fSBjörn Töpel out_pin:
144c0c77d8fSBjörn Töpel 	xdp_umem_unpin_pages(umem);
145c0c77d8fSBjörn Töpel out_pgs:
146c0c77d8fSBjörn Töpel 	kfree(umem->pgs);
147c0c77d8fSBjörn Töpel 	umem->pgs = NULL;
148c0c77d8fSBjörn Töpel 	return err;
149c0c77d8fSBjörn Töpel }
150c0c77d8fSBjörn Töpel 
151c0c77d8fSBjörn Töpel static int xdp_umem_account_pages(struct xdp_umem *umem)
152c0c77d8fSBjörn Töpel {
153c0c77d8fSBjörn Töpel 	unsigned long lock_limit, new_npgs, old_npgs;
154c0c77d8fSBjörn Töpel 
155c0c77d8fSBjörn Töpel 	if (capable(CAP_IPC_LOCK))
156c0c77d8fSBjörn Töpel 		return 0;
157c0c77d8fSBjörn Töpel 
158c0c77d8fSBjörn Töpel 	lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
159c0c77d8fSBjörn Töpel 	umem->user = get_uid(current_user());
160c0c77d8fSBjörn Töpel 
161c0c77d8fSBjörn Töpel 	do {
162c0c77d8fSBjörn Töpel 		old_npgs = atomic_long_read(&umem->user->locked_vm);
163c0c77d8fSBjörn Töpel 		new_npgs = old_npgs + umem->npgs;
164c0c77d8fSBjörn Töpel 		if (new_npgs > lock_limit) {
165c0c77d8fSBjörn Töpel 			free_uid(umem->user);
166c0c77d8fSBjörn Töpel 			umem->user = NULL;
167c0c77d8fSBjörn Töpel 			return -ENOBUFS;
168c0c77d8fSBjörn Töpel 		}
169c0c77d8fSBjörn Töpel 	} while (atomic_long_cmpxchg(&umem->user->locked_vm, old_npgs,
170c0c77d8fSBjörn Töpel 				     new_npgs) != old_npgs);
171c0c77d8fSBjörn Töpel 	return 0;
172c0c77d8fSBjörn Töpel }
173c0c77d8fSBjörn Töpel 
174c0c77d8fSBjörn Töpel int xdp_umem_reg(struct xdp_umem *umem, struct xdp_umem_reg *mr)
175c0c77d8fSBjörn Töpel {
176c0c77d8fSBjörn Töpel 	u32 frame_size = mr->frame_size, frame_headroom = mr->frame_headroom;
177c0c77d8fSBjörn Töpel 	u64 addr = mr->addr, size = mr->len;
178c0c77d8fSBjörn Töpel 	unsigned int nframes, nfpp;
179c0c77d8fSBjörn Töpel 	int size_chk, err;
180c0c77d8fSBjörn Töpel 
181c0c77d8fSBjörn Töpel 	if (!umem)
182c0c77d8fSBjörn Töpel 		return -EINVAL;
183c0c77d8fSBjörn Töpel 
184c0c77d8fSBjörn Töpel 	if (frame_size < XDP_UMEM_MIN_FRAME_SIZE || frame_size > PAGE_SIZE) {
185c0c77d8fSBjörn Töpel 		/* Strictly speaking we could support this, if:
186c0c77d8fSBjörn Töpel 		 * - huge pages, or*
187c0c77d8fSBjörn Töpel 		 * - using an IOMMU, or
188c0c77d8fSBjörn Töpel 		 * - making sure the memory area is consecutive
189c0c77d8fSBjörn Töpel 		 * but for now, we simply say "computer says no".
190c0c77d8fSBjörn Töpel 		 */
191c0c77d8fSBjörn Töpel 		return -EINVAL;
192c0c77d8fSBjörn Töpel 	}
193c0c77d8fSBjörn Töpel 
194c0c77d8fSBjörn Töpel 	if (!is_power_of_2(frame_size))
195c0c77d8fSBjörn Töpel 		return -EINVAL;
196c0c77d8fSBjörn Töpel 
197c0c77d8fSBjörn Töpel 	if (!PAGE_ALIGNED(addr)) {
198c0c77d8fSBjörn Töpel 		/* Memory area has to be page size aligned. For
199c0c77d8fSBjörn Töpel 		 * simplicity, this might change.
200c0c77d8fSBjörn Töpel 		 */
201c0c77d8fSBjörn Töpel 		return -EINVAL;
202c0c77d8fSBjörn Töpel 	}
203c0c77d8fSBjörn Töpel 
204c0c77d8fSBjörn Töpel 	if ((addr + size) < addr)
205c0c77d8fSBjörn Töpel 		return -EINVAL;
206c0c77d8fSBjörn Töpel 
207c0c77d8fSBjörn Töpel 	nframes = size / frame_size;
208c0c77d8fSBjörn Töpel 	if (nframes == 0 || nframes > UINT_MAX)
209c0c77d8fSBjörn Töpel 		return -EINVAL;
210c0c77d8fSBjörn Töpel 
211c0c77d8fSBjörn Töpel 	nfpp = PAGE_SIZE / frame_size;
212c0c77d8fSBjörn Töpel 	if (nframes < nfpp || nframes % nfpp)
213c0c77d8fSBjörn Töpel 		return -EINVAL;
214c0c77d8fSBjörn Töpel 
215c0c77d8fSBjörn Töpel 	frame_headroom = ALIGN(frame_headroom, 64);
216c0c77d8fSBjörn Töpel 
217c0c77d8fSBjörn Töpel 	size_chk = frame_size - frame_headroom - XDP_PACKET_HEADROOM;
218c0c77d8fSBjörn Töpel 	if (size_chk < 0)
219c0c77d8fSBjörn Töpel 		return -EINVAL;
220c0c77d8fSBjörn Töpel 
221c0c77d8fSBjörn Töpel 	umem->pid = get_task_pid(current, PIDTYPE_PID);
222c0c77d8fSBjörn Töpel 	umem->size = (size_t)size;
223c0c77d8fSBjörn Töpel 	umem->address = (unsigned long)addr;
224c0c77d8fSBjörn Töpel 	umem->props.frame_size = frame_size;
225c0c77d8fSBjörn Töpel 	umem->props.nframes = nframes;
226c0c77d8fSBjörn Töpel 	umem->frame_headroom = frame_headroom;
227c0c77d8fSBjörn Töpel 	umem->npgs = size / PAGE_SIZE;
228c0c77d8fSBjörn Töpel 	umem->pgs = NULL;
229c0c77d8fSBjörn Töpel 	umem->user = NULL;
230c0c77d8fSBjörn Töpel 
231c0c77d8fSBjörn Töpel 	umem->frame_size_log2 = ilog2(frame_size);
232c0c77d8fSBjörn Töpel 	umem->nfpp_mask = nfpp - 1;
233c0c77d8fSBjörn Töpel 	umem->nfpplog2 = ilog2(nfpp);
234c0c77d8fSBjörn Töpel 	atomic_set(&umem->users, 1);
235c0c77d8fSBjörn Töpel 
236c0c77d8fSBjörn Töpel 	err = xdp_umem_account_pages(umem);
237c0c77d8fSBjörn Töpel 	if (err)
238c0c77d8fSBjörn Töpel 		goto out;
239c0c77d8fSBjörn Töpel 
240c0c77d8fSBjörn Töpel 	err = xdp_umem_pin_pages(umem);
241c0c77d8fSBjörn Töpel 	if (err)
242c0c77d8fSBjörn Töpel 		goto out_account;
243c0c77d8fSBjörn Töpel 	return 0;
244c0c77d8fSBjörn Töpel 
245c0c77d8fSBjörn Töpel out_account:
246c0c77d8fSBjörn Töpel 	xdp_umem_unaccount_pages(umem);
247c0c77d8fSBjörn Töpel out:
248c0c77d8fSBjörn Töpel 	put_pid(umem->pid);
249c0c77d8fSBjörn Töpel 	return err;
250c0c77d8fSBjörn Töpel }
251*965a9909SMagnus Karlsson 
252*965a9909SMagnus Karlsson bool xdp_umem_validate_queues(struct xdp_umem *umem)
253*965a9909SMagnus Karlsson {
254*965a9909SMagnus Karlsson 	return umem->fq;
255*965a9909SMagnus Karlsson }
256