xref: /openbmc/linux/net/xdp/xdp_umem.c (revision c2d3d6a474629e30428b1622af3d551f560cd1d8)
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>
1484c6b868SJakub Kicinski #include <linux/netdevice.h>
1584c6b868SJakub Kicinski #include <linux/rtnetlink.h>
1650e74c01SBjörn Töpel #include <linux/idr.h>
17624676e7SIvan Khoronzhuk #include <linux/vmalloc.h>
18c0c77d8fSBjörn Töpel 
19c0c77d8fSBjörn Töpel #include "xdp_umem.h"
20e61e62b9SBjörn Töpel #include "xsk_queue.h"
21c0c77d8fSBjörn Töpel 
22bbff2f32SBjörn Töpel #define XDP_UMEM_MIN_CHUNK_SIZE 2048
23c0c77d8fSBjörn Töpel 
2450e74c01SBjörn Töpel static DEFINE_IDA(umem_ida);
2550e74c01SBjörn Töpel 
26ac98d8aaSMagnus Karlsson void xdp_add_sk_umem(struct xdp_umem *umem, struct xdp_sock *xs)
27ac98d8aaSMagnus Karlsson {
28ac98d8aaSMagnus Karlsson 	unsigned long flags;
29ac98d8aaSMagnus Karlsson 
302afd23f7SMagnus Karlsson 	if (!xs->tx)
312afd23f7SMagnus Karlsson 		return;
322afd23f7SMagnus Karlsson 
33e4e5aefcSMagnus Karlsson 	spin_lock_irqsave(&umem->xsk_tx_list_lock, flags);
34e4e5aefcSMagnus Karlsson 	list_add_rcu(&xs->list, &umem->xsk_tx_list);
35e4e5aefcSMagnus Karlsson 	spin_unlock_irqrestore(&umem->xsk_tx_list_lock, flags);
36ac98d8aaSMagnus Karlsson }
37ac98d8aaSMagnus Karlsson 
38ac98d8aaSMagnus Karlsson void xdp_del_sk_umem(struct xdp_umem *umem, struct xdp_sock *xs)
39ac98d8aaSMagnus Karlsson {
40ac98d8aaSMagnus Karlsson 	unsigned long flags;
41ac98d8aaSMagnus Karlsson 
422afd23f7SMagnus Karlsson 	if (!xs->tx)
432afd23f7SMagnus Karlsson 		return;
442afd23f7SMagnus Karlsson 
45e4e5aefcSMagnus Karlsson 	spin_lock_irqsave(&umem->xsk_tx_list_lock, flags);
46ac98d8aaSMagnus Karlsson 	list_del_rcu(&xs->list);
47e4e5aefcSMagnus Karlsson 	spin_unlock_irqrestore(&umem->xsk_tx_list_lock, flags);
48ac98d8aaSMagnus Karlsson }
49ac98d8aaSMagnus Karlsson 
50c0c77d8fSBjörn Töpel static void xdp_umem_unpin_pages(struct xdp_umem *umem)
51c0c77d8fSBjörn Töpel {
52f1f6a7ddSJohn Hubbard 	unpin_user_pages_dirty_lock(umem->pgs, umem->npgs, true);
53c0c77d8fSBjörn Töpel 
54c0c77d8fSBjörn Töpel 	kfree(umem->pgs);
55c0c77d8fSBjörn Töpel 	umem->pgs = NULL;
56c0c77d8fSBjörn Töpel }
57c0c77d8fSBjörn Töpel 
58c0c77d8fSBjörn Töpel static void xdp_umem_unaccount_pages(struct xdp_umem *umem)
59c0c77d8fSBjörn Töpel {
60c09290c5SDaniel Borkmann 	if (umem->user) {
61c0c77d8fSBjörn Töpel 		atomic_long_sub(umem->npgs, &umem->user->locked_vm);
62c0c77d8fSBjörn Töpel 		free_uid(umem->user);
63c0c77d8fSBjörn Töpel 	}
64c09290c5SDaniel Borkmann }
65c0c77d8fSBjörn Töpel 
66c0c77d8fSBjörn Töpel static void xdp_umem_release(struct xdp_umem *umem)
67c0c77d8fSBjörn Töpel {
68*c2d3d6a4SMagnus Karlsson 	umem->zc = false;
6950e74c01SBjörn Töpel 	ida_simple_remove(&umem_ida, umem->id);
7050e74c01SBjörn Töpel 
71c0c77d8fSBjörn Töpel 	xdp_umem_unpin_pages(umem);
72c0c77d8fSBjörn Töpel 
73c0c77d8fSBjörn Töpel 	xdp_umem_unaccount_pages(umem);
74c0c77d8fSBjörn Töpel 	kfree(umem);
75c0c77d8fSBjörn Töpel }
76c0c77d8fSBjörn Töpel 
77c0c77d8fSBjörn Töpel void xdp_get_umem(struct xdp_umem *umem)
78c0c77d8fSBjörn Töpel {
79d3b42f14SBjörn Töpel 	refcount_inc(&umem->users);
80c0c77d8fSBjörn Töpel }
81c0c77d8fSBjörn Töpel 
82c0c77d8fSBjörn Töpel void xdp_put_umem(struct xdp_umem *umem)
83c0c77d8fSBjörn Töpel {
84c0c77d8fSBjörn Töpel 	if (!umem)
85c0c77d8fSBjörn Töpel 		return;
86c0c77d8fSBjörn Töpel 
871c1efc2aSMagnus Karlsson 	if (refcount_dec_and_test(&umem->users))
881c1efc2aSMagnus Karlsson 		xdp_umem_release(umem);
89c0c77d8fSBjörn Töpel }
90c0c77d8fSBjörn Töpel 
9107bf2d97SMagnus Karlsson static int xdp_umem_pin_pages(struct xdp_umem *umem, unsigned long address)
92c0c77d8fSBjörn Töpel {
93c0c77d8fSBjörn Töpel 	unsigned int gup_flags = FOLL_WRITE;
94c0c77d8fSBjörn Töpel 	long npgs;
95c0c77d8fSBjörn Töpel 	int err;
96c0c77d8fSBjörn Töpel 
97a343993cSBjörn Töpel 	umem->pgs = kcalloc(umem->npgs, sizeof(*umem->pgs),
98a343993cSBjörn Töpel 			    GFP_KERNEL | __GFP_NOWARN);
99c0c77d8fSBjörn Töpel 	if (!umem->pgs)
100c0c77d8fSBjörn Töpel 		return -ENOMEM;
101c0c77d8fSBjörn Töpel 
102d8ed45c5SMichel Lespinasse 	mmap_read_lock(current->mm);
10307bf2d97SMagnus Karlsson 	npgs = pin_user_pages(address, umem->npgs,
104932f4a63SIra Weiny 			      gup_flags | FOLL_LONGTERM, &umem->pgs[0], NULL);
105d8ed45c5SMichel Lespinasse 	mmap_read_unlock(current->mm);
106c0c77d8fSBjörn Töpel 
107c0c77d8fSBjörn Töpel 	if (npgs != umem->npgs) {
108c0c77d8fSBjörn Töpel 		if (npgs >= 0) {
109c0c77d8fSBjörn Töpel 			umem->npgs = npgs;
110c0c77d8fSBjörn Töpel 			err = -ENOMEM;
111c0c77d8fSBjörn Töpel 			goto out_pin;
112c0c77d8fSBjörn Töpel 		}
113c0c77d8fSBjörn Töpel 		err = npgs;
114c0c77d8fSBjörn Töpel 		goto out_pgs;
115c0c77d8fSBjörn Töpel 	}
116c0c77d8fSBjörn Töpel 	return 0;
117c0c77d8fSBjörn Töpel 
118c0c77d8fSBjörn Töpel out_pin:
119c0c77d8fSBjörn Töpel 	xdp_umem_unpin_pages(umem);
120c0c77d8fSBjörn Töpel out_pgs:
121c0c77d8fSBjörn Töpel 	kfree(umem->pgs);
122c0c77d8fSBjörn Töpel 	umem->pgs = NULL;
123c0c77d8fSBjörn Töpel 	return err;
124c0c77d8fSBjörn Töpel }
125c0c77d8fSBjörn Töpel 
126c0c77d8fSBjörn Töpel static int xdp_umem_account_pages(struct xdp_umem *umem)
127c0c77d8fSBjörn Töpel {
128c0c77d8fSBjörn Töpel 	unsigned long lock_limit, new_npgs, old_npgs;
129c0c77d8fSBjörn Töpel 
130c0c77d8fSBjörn Töpel 	if (capable(CAP_IPC_LOCK))
131c0c77d8fSBjörn Töpel 		return 0;
132c0c77d8fSBjörn Töpel 
133c0c77d8fSBjörn Töpel 	lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
134c0c77d8fSBjörn Töpel 	umem->user = get_uid(current_user());
135c0c77d8fSBjörn Töpel 
136c0c77d8fSBjörn Töpel 	do {
137c0c77d8fSBjörn Töpel 		old_npgs = atomic_long_read(&umem->user->locked_vm);
138c0c77d8fSBjörn Töpel 		new_npgs = old_npgs + umem->npgs;
139c0c77d8fSBjörn Töpel 		if (new_npgs > lock_limit) {
140c0c77d8fSBjörn Töpel 			free_uid(umem->user);
141c0c77d8fSBjörn Töpel 			umem->user = NULL;
142c0c77d8fSBjörn Töpel 			return -ENOBUFS;
143c0c77d8fSBjörn Töpel 		}
144c0c77d8fSBjörn Töpel 	} while (atomic_long_cmpxchg(&umem->user->locked_vm, old_npgs,
145c0c77d8fSBjörn Töpel 				     new_npgs) != old_npgs);
146c0c77d8fSBjörn Töpel 	return 0;
147c0c77d8fSBjörn Töpel }
148c0c77d8fSBjörn Töpel 
149a49049eaSBjörn Töpel static int xdp_umem_reg(struct xdp_umem *umem, struct xdp_umem_reg *mr)
150c0c77d8fSBjörn Töpel {
151c05cd364SKevin Laatz 	bool unaligned_chunks = mr->flags & XDP_UMEM_UNALIGNED_CHUNK_FLAG;
152bbff2f32SBjörn Töpel 	u32 chunk_size = mr->chunk_size, headroom = mr->headroom;
153b16a87d0SBjörn Töpel 	u64 npgs, addr = mr->addr, size = mr->len;
154bbff2f32SBjörn Töpel 	unsigned int chunks, chunks_per_page;
15599e3a236SMagnus Karlsson 	int err;
156c0c77d8fSBjörn Töpel 
157bbff2f32SBjörn Töpel 	if (chunk_size < XDP_UMEM_MIN_CHUNK_SIZE || chunk_size > PAGE_SIZE) {
158c0c77d8fSBjörn Töpel 		/* Strictly speaking we could support this, if:
159c0c77d8fSBjörn Töpel 		 * - huge pages, or*
160c0c77d8fSBjörn Töpel 		 * - using an IOMMU, or
161c0c77d8fSBjörn Töpel 		 * - making sure the memory area is consecutive
162c0c77d8fSBjörn Töpel 		 * but for now, we simply say "computer says no".
163c0c77d8fSBjörn Töpel 		 */
164c0c77d8fSBjörn Töpel 		return -EINVAL;
165c0c77d8fSBjörn Töpel 	}
166c0c77d8fSBjörn Töpel 
167*c2d3d6a4SMagnus Karlsson 	if (mr->flags & ~XDP_UMEM_UNALIGNED_CHUNK_FLAG)
168c05cd364SKevin Laatz 		return -EINVAL;
169c05cd364SKevin Laatz 
170c05cd364SKevin Laatz 	if (!unaligned_chunks && !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 
1837d877c35SPavel Machek 	npgs = size >> PAGE_SHIFT;
184b16a87d0SBjörn Töpel 	if (npgs > U32_MAX)
185b16a87d0SBjörn Töpel 		return -EINVAL;
186b16a87d0SBjörn Töpel 
187bbff2f32SBjörn Töpel 	chunks = (unsigned int)div_u64(size, chunk_size);
188bbff2f32SBjörn Töpel 	if (chunks == 0)
189c0c77d8fSBjörn Töpel 		return -EINVAL;
190c0c77d8fSBjörn Töpel 
191c05cd364SKevin Laatz 	if (!unaligned_chunks) {
192bbff2f32SBjörn Töpel 		chunks_per_page = PAGE_SIZE / chunk_size;
193bbff2f32SBjörn Töpel 		if (chunks < chunks_per_page || chunks % chunks_per_page)
194c0c77d8fSBjörn Töpel 			return -EINVAL;
195c05cd364SKevin Laatz 	}
196c0c77d8fSBjörn Töpel 
19799e3a236SMagnus Karlsson 	if (headroom >= chunk_size - XDP_PACKET_HEADROOM)
198c0c77d8fSBjörn Töpel 		return -EINVAL;
199c0c77d8fSBjörn Töpel 
20093ee30f3SMagnus Karlsson 	umem->size = size;
201bbff2f32SBjörn Töpel 	umem->headroom = headroom;
2022b43470aSBjörn Töpel 	umem->chunk_size = chunk_size;
2031c1efc2aSMagnus Karlsson 	umem->chunks = chunks;
204b16a87d0SBjörn Töpel 	umem->npgs = (u32)npgs;
205c0c77d8fSBjörn Töpel 	umem->pgs = NULL;
206c0c77d8fSBjörn Töpel 	umem->user = NULL;
207c05cd364SKevin Laatz 	umem->flags = mr->flags;
208e4e5aefcSMagnus Karlsson 	INIT_LIST_HEAD(&umem->xsk_tx_list);
209e4e5aefcSMagnus Karlsson 	spin_lock_init(&umem->xsk_tx_list_lock);
210c0c77d8fSBjörn Töpel 
211d3b42f14SBjörn Töpel 	refcount_set(&umem->users, 1);
212c0c77d8fSBjörn Töpel 
213c0c77d8fSBjörn Töpel 	err = xdp_umem_account_pages(umem);
214c0c77d8fSBjörn Töpel 	if (err)
215044175a0SBjörn Töpel 		return err;
216c0c77d8fSBjörn Töpel 
21707bf2d97SMagnus Karlsson 	err = xdp_umem_pin_pages(umem, (unsigned long)addr);
218c0c77d8fSBjörn Töpel 	if (err)
219c0c77d8fSBjörn Töpel 		goto out_account;
2208aef7340SBjörn Töpel 
221c0c77d8fSBjörn Töpel 	return 0;
222c0c77d8fSBjörn Töpel 
223c0c77d8fSBjörn Töpel out_account:
224c0c77d8fSBjörn Töpel 	xdp_umem_unaccount_pages(umem);
225c0c77d8fSBjörn Töpel 	return err;
226c0c77d8fSBjörn Töpel }
227965a9909SMagnus Karlsson 
228a49049eaSBjörn Töpel struct xdp_umem *xdp_umem_create(struct xdp_umem_reg *mr)
229a49049eaSBjörn Töpel {
230a49049eaSBjörn Töpel 	struct xdp_umem *umem;
231a49049eaSBjörn Töpel 	int err;
232a49049eaSBjörn Töpel 
233a49049eaSBjörn Töpel 	umem = kzalloc(sizeof(*umem), GFP_KERNEL);
234a49049eaSBjörn Töpel 	if (!umem)
235a49049eaSBjörn Töpel 		return ERR_PTR(-ENOMEM);
236a49049eaSBjörn Töpel 
23750e74c01SBjörn Töpel 	err = ida_simple_get(&umem_ida, 0, 0, GFP_KERNEL);
23850e74c01SBjörn Töpel 	if (err < 0) {
23950e74c01SBjörn Töpel 		kfree(umem);
24050e74c01SBjörn Töpel 		return ERR_PTR(err);
24150e74c01SBjörn Töpel 	}
24250e74c01SBjörn Töpel 	umem->id = err;
24350e74c01SBjörn Töpel 
244a49049eaSBjörn Töpel 	err = xdp_umem_reg(umem, mr);
245a49049eaSBjörn Töpel 	if (err) {
24650e74c01SBjörn Töpel 		ida_simple_remove(&umem_ida, umem->id);
247a49049eaSBjörn Töpel 		kfree(umem);
248a49049eaSBjörn Töpel 		return ERR_PTR(err);
249a49049eaSBjörn Töpel 	}
250a49049eaSBjörn Töpel 
251a49049eaSBjörn Töpel 	return umem;
252a49049eaSBjörn Töpel }
253