void kmem_cache_free(struct kmem_cache *s, void *objp);
 
+kmem_buckets *kmem_buckets_create(const char *name, slab_flags_t flags,
+                                 unsigned int useroffset, unsigned int usersize,
+                                 void (*ctor)(void *));
+
 /*
  * Bulk allocation and freeing operations. These are accelerated in an
  * allocator specific way to avoid taking locks repeatedly or building
 }
 #define kmalloc(...)                           alloc_hooks(kmalloc_noprof(__VA_ARGS__))
 
+#define kmem_buckets_alloc(_b, _size, _flags)  \
+       alloc_hooks(__kmalloc_node_noprof(PASS_BUCKET_PARAMS(_size, _b), _flags, NUMA_NO_NODE))
+
+#define kmem_buckets_alloc_track_caller(_b, _size, _flags)     \
+       alloc_hooks(__kmalloc_node_track_caller_noprof(PASS_BUCKET_PARAMS(_size, _b), _flags, NUMA_NO_NODE, _RET_IP_))
+
 static __always_inline __alloc_size(1) void *kmalloc_node_noprof(size_t size, gfp_t flags, int node)
 {
        if (__builtin_constant_p(size) && size) {
 #define kvzalloc(_size, _flags)                        kvmalloc(_size, (_flags)|__GFP_ZERO)
 
 #define kvzalloc_node(_size, _flags, _node)    kvmalloc_node(_size, (_flags)|__GFP_ZERO, _node)
+#define kmem_buckets_valloc(_b, _size, _flags) \
+       alloc_hooks(__kvmalloc_node_noprof(PASS_BUCKET_PARAMS(_size, _b), _flags, NUMA_NO_NODE))
 
 static inline __alloc_size(1, 2) void *
 kvmalloc_array_node_noprof(size_t n, size_t size, gfp_t flags, int node)
 
 }
 EXPORT_SYMBOL(kmem_cache_create);
 
+static struct kmem_cache *kmem_buckets_cache __ro_after_init;
+
+/**
+ * kmem_buckets_create - Create a set of caches that handle dynamic sized
+ *                      allocations via kmem_buckets_alloc()
+ * @name: A prefix string which is used in /proc/slabinfo to identify this
+ *       cache. The individual caches with have their sizes as the suffix.
+ * @flags: SLAB flags (see kmem_cache_create() for details).
+ * @useroffset: Starting offset within an allocation that may be copied
+ *             to/from userspace.
+ * @usersize: How many bytes, starting at @useroffset, may be copied
+ *             to/from userspace.
+ * @ctor: A constructor for the objects, run when new allocations are made.
+ *
+ * Cannot be called within an interrupt, but can be interrupted.
+ *
+ * Return: a pointer to the cache on success, NULL on failure. When
+ * CONFIG_SLAB_BUCKETS is not enabled, ZERO_SIZE_PTR is returned, and
+ * subsequent calls to kmem_buckets_alloc() will fall back to kmalloc().
+ * (i.e. callers only need to check for NULL on failure.)
+ */
+kmem_buckets *kmem_buckets_create(const char *name, slab_flags_t flags,
+                                 unsigned int useroffset,
+                                 unsigned int usersize,
+                                 void (*ctor)(void *))
+{
+       kmem_buckets *b;
+       int idx;
+
+       /*
+        * When the separate buckets API is not built in, just return
+        * a non-NULL value for the kmem_buckets pointer, which will be
+        * unused when performing allocations.
+        */
+       if (!IS_ENABLED(CONFIG_SLAB_BUCKETS))
+               return ZERO_SIZE_PTR;
+
+       if (WARN_ON(!kmem_buckets_cache))
+               return NULL;
+
+       b = kmem_cache_alloc(kmem_buckets_cache, GFP_KERNEL|__GFP_ZERO);
+       if (WARN_ON(!b))
+               return NULL;
+
+       flags |= SLAB_NO_MERGE;
+
+       for (idx = 0; idx < ARRAY_SIZE(kmalloc_caches[KMALLOC_NORMAL]); idx++) {
+               char *short_size, *cache_name;
+               unsigned int cache_useroffset, cache_usersize;
+               unsigned int size;
+
+               if (!kmalloc_caches[KMALLOC_NORMAL][idx])
+                       continue;
+
+               size = kmalloc_caches[KMALLOC_NORMAL][idx]->object_size;
+               if (!size)
+                       continue;
+
+               short_size = strchr(kmalloc_caches[KMALLOC_NORMAL][idx]->name, '-');
+               if (WARN_ON(!short_size))
+                       goto fail;
+
+               cache_name = kasprintf(GFP_KERNEL, "%s-%s", name, short_size + 1);
+               if (WARN_ON(!cache_name))
+                       goto fail;
+
+               if (useroffset >= size) {
+                       cache_useroffset = 0;
+                       cache_usersize = 0;
+               } else {
+                       cache_useroffset = useroffset;
+                       cache_usersize = min(size - cache_useroffset, usersize);
+               }
+               (*b)[idx] = kmem_cache_create_usercopy(cache_name, size,
+                                       0, flags, cache_useroffset,
+                                       cache_usersize, ctor);
+               kfree(cache_name);
+               if (WARN_ON(!(*b)[idx]))
+                       goto fail;
+       }
+
+       return b;
+
+fail:
+       for (idx = 0; idx < ARRAY_SIZE(kmalloc_caches[KMALLOC_NORMAL]); idx++)
+               kmem_cache_destroy((*b)[idx]);
+       kfree(b);
+
+       return NULL;
+}
+EXPORT_SYMBOL(kmem_buckets_create);
+
 #ifdef SLAB_SUPPORTS_SYSFS
 /*
  * For a given kmem_cache, kmem_cache_destroy() should only be called
 
        /* Kmalloc array is now usable */
        slab_state = UP;
+
+       if (IS_ENABLED(CONFIG_SLAB_BUCKETS))
+               kmem_buckets_cache = kmem_cache_create("kmalloc_buckets",
+                                                      sizeof(kmem_buckets),
+                                                      0, SLAB_NO_MERGE, NULL);
 }
 
 /**