xref: /freebsd/sys/compat/linuxkpi/common/src/linux_slab.c (revision a76de17715ab689b0b53a1012e22d8a08470b6e4)
114c5024dSHans Petter Selasky /*-
214c5024dSHans Petter Selasky  * Copyright (c) 2017 Mellanox Technologies, Ltd.
314c5024dSHans Petter Selasky  * All rights reserved.
414c5024dSHans Petter Selasky  *
514c5024dSHans Petter Selasky  * Redistribution and use in source and binary forms, with or without
614c5024dSHans Petter Selasky  * modification, are permitted provided that the following conditions
714c5024dSHans Petter Selasky  * are met:
814c5024dSHans Petter Selasky  * 1. Redistributions of source code must retain the above copyright
914c5024dSHans Petter Selasky  *    notice unmodified, this list of conditions, and the following
1014c5024dSHans Petter Selasky  *    disclaimer.
1114c5024dSHans Petter Selasky  * 2. Redistributions in binary form must reproduce the above copyright
1214c5024dSHans Petter Selasky  *    notice, this list of conditions and the following disclaimer in the
1314c5024dSHans Petter Selasky  *    documentation and/or other materials provided with the distribution.
1414c5024dSHans Petter Selasky  *
1514c5024dSHans Petter Selasky  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1614c5024dSHans Petter Selasky  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1714c5024dSHans Petter Selasky  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1814c5024dSHans Petter Selasky  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
1914c5024dSHans Petter Selasky  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2014c5024dSHans Petter Selasky  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2114c5024dSHans Petter Selasky  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2214c5024dSHans Petter Selasky  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2314c5024dSHans Petter Selasky  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2414c5024dSHans Petter Selasky  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2514c5024dSHans Petter Selasky  */
2614c5024dSHans Petter Selasky 
2714c5024dSHans Petter Selasky #include <sys/cdefs.h>
2814c5024dSHans Petter Selasky __FBSDID("$FreeBSD$");
2914c5024dSHans Petter Selasky 
3014c5024dSHans Petter Selasky #include <linux/slab.h>
3114c5024dSHans Petter Selasky #include <linux/rcupdate.h>
3214c5024dSHans Petter Selasky #include <linux/kernel.h>
33ec25b6faSVladimir Kondratyev #include <linux/irq_work.h>
34ec25b6faSVladimir Kondratyev #include <linux/llist.h>
35ec25b6faSVladimir Kondratyev 
36ec25b6faSVladimir Kondratyev #include <sys/param.h>
37ec25b6faSVladimir Kondratyev #include <sys/taskqueue.h>
38a2b83b59SVladimir Kondratyev #include <vm/uma.h>
3914c5024dSHans Petter Selasky 
4014c5024dSHans Petter Selasky struct linux_kmem_rcu {
4114c5024dSHans Petter Selasky 	struct rcu_head rcu_head;
4214c5024dSHans Petter Selasky 	struct linux_kmem_cache *cache;
4314c5024dSHans Petter Selasky };
4414c5024dSHans Petter Selasky 
45a2b83b59SVladimir Kondratyev struct linux_kmem_cache {
46a2b83b59SVladimir Kondratyev 	uma_zone_t cache_zone;
47a2b83b59SVladimir Kondratyev 	linux_kmem_ctor_t *cache_ctor;
48a2b83b59SVladimir Kondratyev 	unsigned cache_flags;
49a2b83b59SVladimir Kondratyev 	unsigned cache_size;
50a2b83b59SVladimir Kondratyev 	struct llist_head cache_items;
51a2b83b59SVladimir Kondratyev 	struct task cache_task;
52a2b83b59SVladimir Kondratyev };
53a2b83b59SVladimir Kondratyev 
5414c5024dSHans Petter Selasky #define	LINUX_KMEM_TO_RCU(c, m)					\
5514c5024dSHans Petter Selasky 	((struct linux_kmem_rcu *)((char *)(m) +		\
5614c5024dSHans Petter Selasky 	(c)->cache_size - sizeof(struct linux_kmem_rcu)))
5714c5024dSHans Petter Selasky 
5814c5024dSHans Petter Selasky #define	LINUX_RCU_TO_KMEM(r)					\
5914c5024dSHans Petter Selasky 	((void *)((char *)(r) + sizeof(struct linux_kmem_rcu) - \
6014c5024dSHans Petter Selasky 	(r)->cache->cache_size))
6114c5024dSHans Petter Selasky 
62ec25b6faSVladimir Kondratyev static LLIST_HEAD(linux_kfree_async_list);
63ec25b6faSVladimir Kondratyev 
64a2b83b59SVladimir Kondratyev static void	lkpi_kmem_cache_free_async_fn(void *, int);
65a2b83b59SVladimir Kondratyev 
66a2b83b59SVladimir Kondratyev void *
67a2b83b59SVladimir Kondratyev lkpi_kmem_cache_alloc(struct linux_kmem_cache *c, gfp_t flags)
68a2b83b59SVladimir Kondratyev {
69a2b83b59SVladimir Kondratyev 	return (uma_zalloc_arg(c->cache_zone, c,
70a2b83b59SVladimir Kondratyev 	    linux_check_m_flags(flags)));
71a2b83b59SVladimir Kondratyev }
72a2b83b59SVladimir Kondratyev 
73a2b83b59SVladimir Kondratyev void *
74a2b83b59SVladimir Kondratyev lkpi_kmem_cache_zalloc(struct linux_kmem_cache *c, gfp_t flags)
75a2b83b59SVladimir Kondratyev {
76a2b83b59SVladimir Kondratyev 	return (uma_zalloc_arg(c->cache_zone, c,
77a2b83b59SVladimir Kondratyev 	    linux_check_m_flags(flags | M_ZERO)));
78a2b83b59SVladimir Kondratyev }
79a2b83b59SVladimir Kondratyev 
8014c5024dSHans Petter Selasky static int
8114c5024dSHans Petter Selasky linux_kmem_ctor(void *mem, int size, void *arg, int flags)
8214c5024dSHans Petter Selasky {
8314c5024dSHans Petter Selasky 	struct linux_kmem_cache *c = arg;
8414c5024dSHans Petter Selasky 
85782a90d1SHans Petter Selasky 	if (unlikely(c->cache_flags & SLAB_TYPESAFE_BY_RCU)) {
8614c5024dSHans Petter Selasky 		struct linux_kmem_rcu *rcu = LINUX_KMEM_TO_RCU(c, mem);
8714c5024dSHans Petter Selasky 
8814c5024dSHans Petter Selasky 		/* duplicate cache pointer */
8914c5024dSHans Petter Selasky 		rcu->cache = c;
9014c5024dSHans Petter Selasky 	}
9114c5024dSHans Petter Selasky 
9214c5024dSHans Petter Selasky 	/* check for constructor */
9314c5024dSHans Petter Selasky 	if (likely(c->cache_ctor != NULL))
9414c5024dSHans Petter Selasky 		c->cache_ctor(mem);
9514c5024dSHans Petter Selasky 
9614c5024dSHans Petter Selasky 	return (0);
9714c5024dSHans Petter Selasky }
9814c5024dSHans Petter Selasky 
9914c5024dSHans Petter Selasky static void
10014c5024dSHans Petter Selasky linux_kmem_cache_free_rcu_callback(struct rcu_head *head)
10114c5024dSHans Petter Selasky {
10214c5024dSHans Petter Selasky 	struct linux_kmem_rcu *rcu =
10314c5024dSHans Petter Selasky 	    container_of(head, struct linux_kmem_rcu, rcu_head);
10414c5024dSHans Petter Selasky 
10514c5024dSHans Petter Selasky 	uma_zfree(rcu->cache->cache_zone, LINUX_RCU_TO_KMEM(rcu));
10614c5024dSHans Petter Selasky }
10714c5024dSHans Petter Selasky 
10814c5024dSHans Petter Selasky struct linux_kmem_cache *
10914c5024dSHans Petter Selasky linux_kmem_cache_create(const char *name, size_t size, size_t align,
11014c5024dSHans Petter Selasky     unsigned flags, linux_kmem_ctor_t *ctor)
11114c5024dSHans Petter Selasky {
11214c5024dSHans Petter Selasky 	struct linux_kmem_cache *c;
11314c5024dSHans Petter Selasky 
11414c5024dSHans Petter Selasky 	c = malloc(sizeof(*c), M_KMALLOC, M_WAITOK);
11514c5024dSHans Petter Selasky 
11614c5024dSHans Petter Selasky 	if (flags & SLAB_HWCACHE_ALIGN)
11714c5024dSHans Petter Selasky 		align = UMA_ALIGN_CACHE;
11814c5024dSHans Petter Selasky 	else if (align != 0)
11914c5024dSHans Petter Selasky 		align--;
12014c5024dSHans Petter Selasky 
121782a90d1SHans Petter Selasky 	if (flags & SLAB_TYPESAFE_BY_RCU) {
12214c5024dSHans Petter Selasky 		/* make room for RCU structure */
12314c5024dSHans Petter Selasky 		size = ALIGN(size, sizeof(void *));
12414c5024dSHans Petter Selasky 		size += sizeof(struct linux_kmem_rcu);
12514c5024dSHans Petter Selasky 
12614c5024dSHans Petter Selasky 		/* create cache_zone */
12714c5024dSHans Petter Selasky 		c->cache_zone = uma_zcreate(name, size,
12814c5024dSHans Petter Selasky 		    linux_kmem_ctor, NULL, NULL, NULL,
12914c5024dSHans Petter Selasky 		    align, UMA_ZONE_ZINIT);
13014c5024dSHans Petter Selasky 	} else {
131a2b83b59SVladimir Kondratyev 		/* make room for async task list items */
132a2b83b59SVladimir Kondratyev 		size = MAX(size, sizeof(struct llist_node));
133a2b83b59SVladimir Kondratyev 
13414c5024dSHans Petter Selasky 		/* create cache_zone */
13514c5024dSHans Petter Selasky 		c->cache_zone = uma_zcreate(name, size,
13614c5024dSHans Petter Selasky 		    ctor ? linux_kmem_ctor : NULL, NULL,
13714c5024dSHans Petter Selasky 		    NULL, NULL, align, 0);
13814c5024dSHans Petter Selasky 	}
13914c5024dSHans Petter Selasky 
14014c5024dSHans Petter Selasky 	c->cache_flags = flags;
14114c5024dSHans Petter Selasky 	c->cache_ctor = ctor;
14214c5024dSHans Petter Selasky 	c->cache_size = size;
143a2b83b59SVladimir Kondratyev 	init_llist_head(&c->cache_items);
144a2b83b59SVladimir Kondratyev 	TASK_INIT(&c->cache_task, 0, lkpi_kmem_cache_free_async_fn, c);
14514c5024dSHans Petter Selasky 	return (c);
14614c5024dSHans Petter Selasky }
14714c5024dSHans Petter Selasky 
148a2b83b59SVladimir Kondratyev static inline void
149a2b83b59SVladimir Kondratyev lkpi_kmem_cache_free_rcu(struct linux_kmem_cache *c, void *m)
15014c5024dSHans Petter Selasky {
15114c5024dSHans Petter Selasky 	struct linux_kmem_rcu *rcu = LINUX_KMEM_TO_RCU(c, m);
15214c5024dSHans Petter Selasky 
15314c5024dSHans Petter Selasky 	call_rcu(&rcu->rcu_head, linux_kmem_cache_free_rcu_callback);
15414c5024dSHans Petter Selasky }
15514c5024dSHans Petter Selasky 
156a2b83b59SVladimir Kondratyev static inline void
157a2b83b59SVladimir Kondratyev lkpi_kmem_cache_free_sync(struct linux_kmem_cache *c, void *m)
158a2b83b59SVladimir Kondratyev {
159a2b83b59SVladimir Kondratyev 	uma_zfree(c->cache_zone, m);
160a2b83b59SVladimir Kondratyev }
161a2b83b59SVladimir Kondratyev 
162a2b83b59SVladimir Kondratyev static void
163a2b83b59SVladimir Kondratyev lkpi_kmem_cache_free_async_fn(void *context, int pending)
164a2b83b59SVladimir Kondratyev {
165a2b83b59SVladimir Kondratyev 	struct linux_kmem_cache *c = context;
166a2b83b59SVladimir Kondratyev 	struct llist_node *freed, *next;
167a2b83b59SVladimir Kondratyev 
168a2b83b59SVladimir Kondratyev 	llist_for_each_safe(freed, next, llist_del_all(&c->cache_items))
169a2b83b59SVladimir Kondratyev 		lkpi_kmem_cache_free_sync(c, freed);
170a2b83b59SVladimir Kondratyev }
171a2b83b59SVladimir Kondratyev 
172a2b83b59SVladimir Kondratyev static inline void
173a2b83b59SVladimir Kondratyev lkpi_kmem_cache_free_async(struct linux_kmem_cache *c, void *m)
174a2b83b59SVladimir Kondratyev {
175a2b83b59SVladimir Kondratyev 	if (m == NULL)
176a2b83b59SVladimir Kondratyev 		return;
177a2b83b59SVladimir Kondratyev 
178a2b83b59SVladimir Kondratyev 	llist_add(m, &c->cache_items);
179a2b83b59SVladimir Kondratyev 	taskqueue_enqueue(linux_irq_work_tq, &c->cache_task);
180a2b83b59SVladimir Kondratyev }
181a2b83b59SVladimir Kondratyev 
182a2b83b59SVladimir Kondratyev void
183a2b83b59SVladimir Kondratyev lkpi_kmem_cache_free(struct linux_kmem_cache *c, void *m)
184a2b83b59SVladimir Kondratyev {
185a2b83b59SVladimir Kondratyev 	if (unlikely(c->cache_flags & SLAB_TYPESAFE_BY_RCU))
186a2b83b59SVladimir Kondratyev 		lkpi_kmem_cache_free_rcu(c, m);
187a2b83b59SVladimir Kondratyev 	else if (unlikely(curthread->td_critnest != 0))
188a2b83b59SVladimir Kondratyev 		lkpi_kmem_cache_free_async(c, m);
189a2b83b59SVladimir Kondratyev 	else
190a2b83b59SVladimir Kondratyev 		lkpi_kmem_cache_free_sync(c, m);
191a2b83b59SVladimir Kondratyev }
192a2b83b59SVladimir Kondratyev 
19314c5024dSHans Petter Selasky void
19414c5024dSHans Petter Selasky linux_kmem_cache_destroy(struct linux_kmem_cache *c)
19514c5024dSHans Petter Selasky {
196*a76de177SMark Johnston 	if (c == NULL)
197*a76de177SMark Johnston 		return;
198*a76de177SMark Johnston 
199782a90d1SHans Petter Selasky 	if (unlikely(c->cache_flags & SLAB_TYPESAFE_BY_RCU)) {
20014c5024dSHans Petter Selasky 		/* make sure all free callbacks have been called */
20114c5024dSHans Petter Selasky 		rcu_barrier();
20214c5024dSHans Petter Selasky 	}
20314c5024dSHans Petter Selasky 
204a2b83b59SVladimir Kondratyev 	if (!llist_empty(&c->cache_items))
205a2b83b59SVladimir Kondratyev 		taskqueue_enqueue(linux_irq_work_tq, &c->cache_task);
206a2b83b59SVladimir Kondratyev 	taskqueue_drain(linux_irq_work_tq, &c->cache_task);
20714c5024dSHans Petter Selasky 	uma_zdestroy(c->cache_zone);
20814c5024dSHans Petter Selasky 	free(c, M_KMALLOC);
20914c5024dSHans Petter Selasky }
210ec25b6faSVladimir Kondratyev 
211ec25b6faSVladimir Kondratyev static void
212ec25b6faSVladimir Kondratyev linux_kfree_async_fn(void *context, int pending)
213ec25b6faSVladimir Kondratyev {
214ec25b6faSVladimir Kondratyev 	struct llist_node *freed;
215ec25b6faSVladimir Kondratyev 
216ec25b6faSVladimir Kondratyev 	while((freed = llist_del_first(&linux_kfree_async_list)) != NULL)
217ec25b6faSVladimir Kondratyev 		kfree(freed);
218ec25b6faSVladimir Kondratyev }
219ec25b6faSVladimir Kondratyev static struct task linux_kfree_async_task =
220ec25b6faSVladimir Kondratyev     TASK_INITIALIZER(0, linux_kfree_async_fn, &linux_kfree_async_task);
221ec25b6faSVladimir Kondratyev 
222ec25b6faSVladimir Kondratyev void
223ec25b6faSVladimir Kondratyev linux_kfree_async(void *addr)
224ec25b6faSVladimir Kondratyev {
225ec25b6faSVladimir Kondratyev 	if (addr == NULL)
226ec25b6faSVladimir Kondratyev 		return;
227ec25b6faSVladimir Kondratyev 	llist_add(addr, &linux_kfree_async_list);
228ec25b6faSVladimir Kondratyev 	taskqueue_enqueue(linux_irq_work_tq, &linux_kfree_async_task);
229ec25b6faSVladimir Kondratyev }
230