/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/*
 * File      : slab.c
 *
 * Change Logs:
 * Date           Author       Notes
 * 2008-07-12     Bernard      the first version
 * 2010-07-13     Bernard      fix RT_ALIGN issue found by kuronca
 * 2010-10-23     yi.qiu       add module memory allocator
 * 2010-12-18     yi.qiu       fix zone release bug
 */

/*
 * KERN_SLABALLOC.C - Kernel SLAB memory allocator
 *
 * Copyright (c) 2003,2004 The DragonFly Project.  All rights reserved.
 *
 * This code is derived from software contributed to The DragonFly Project
 * by Matthew Dillon <dillon@backplane.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of The DragonFly Project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific, prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <rthw.h>
#include <rtthread.h>

#if defined (RT_USING_SLAB)
/*
 * slab allocator implementation
 *
 * A slab allocator reserves a ZONE for each chunk size, then lays the
 * chunks out in an array within the zone.  Allocation and deallocation
 * is nearly instantanious, and fragmentation/overhead losses are limited
 * to a fixed worst-case amount.
 *
 * The downside of this slab implementation is in the chunk size
 * multiplied by the number of zones.  ~80 zones * 128K = 10MB of VM per cpu.
 * In a kernel implementation all this memory will be physical so
 * the zone size is adjusted downward on machines with less physical
 * memory.  The upside is that overhead is bounded... this is the *worst*
 * case overhead.
 *
 * Slab management is done on a per-cpu basis and no locking or mutexes
 * are required, only a critical section.  When one cpu frees memory
 * belonging to another cpu's slab manager an asynchronous IPI message
 * will be queued to execute the operation.   In addition, both the
 * high level slab allocator and the low level zone allocator optimize
 * M_ZERO requests, and the slab allocator does not have to pre initialize
 * the linked list of chunks.
 *
 * XXX Balancing is needed between cpus.  Balance will be handled through
 * asynchronous IPIs primarily by reassigning the z_Cpu ownership of chunks.
 *
 * XXX If we have to allocate a new zone and M_USE_RESERVE is set, use of
 * the new zone should be restricted to M_USE_RESERVE requests only.
 *
 *  Alloc Size  Chunking        Number of zones
 *  0-127       8               16
 *  128-255     16              8
 *  256-511     32              8
 *  512-1023    64              8
 *  1024-2047   128             8
 *  2048-4095   256             8
 *  4096-8191   512             8
 *  8192-16383  1024            8
 *  16384-32767 2048            8
 *  (if RT_MM_PAGE_SIZE is 4K the maximum zone allocation is 16383)
 *
 *  Allocations >= zone_limit go directly to kmem.
 *
 *          API REQUIREMENTS AND SIDE EFFECTS
 *
 *    To operate as a drop-in replacement to the FreeBSD-4.x malloc() we
 *    have remained compatible with the following API requirements:
 *
 *    + small power-of-2 sized allocations are power-of-2 aligned (kern_tty)
 *    + all power-of-2 sized allocations are power-of-2 aligned (twe)
 *    + malloc(0) is allowed and returns non-RT_NULL (ahc driver)
 *    + ability to allocate arbitrarily large chunks of memory
 */

#define ZALLOC_SLAB_MAGIC       0x51ab51ab
#define ZALLOC_ZONE_LIMIT       (16 * 1024)     /* max slab-managed alloc */
#define ZALLOC_MIN_ZONE_SIZE    (32 * 1024)     /* minimum zone size */
#define ZALLOC_MAX_ZONE_SIZE    (128 * 1024)    /* maximum zone size */
#define ZONE_RELEASE_THRESH     2               /* threshold number of zones */

/*
 * Misc constants.  Note that allocations that are exact multiples of
 * RT_MM_PAGE_SIZE, or exceed the zone limit, fall through to the kmem module.
 */
#define MIN_CHUNK_SIZE      8       /* in bytes */
#define MIN_CHUNK_MASK      (MIN_CHUNK_SIZE - 1)

/*
 * Array of descriptors that describe the contents of each page
 */
#define PAGE_TYPE_FREE      0x00
#define PAGE_TYPE_SMALL     0x01
#define PAGE_TYPE_LARGE     0x02

#define btokup(addr)    \
    (&slab->memusage[((rt_ubase_t)(addr) - slab->heap_start) >> RT_MM_PAGE_BITS])

/**
 * Base structure of slab memory object
 */

/*
 * The IN-BAND zone header is placed at the beginning of each zone.
 */
struct rt_slab_zone
{
    rt_uint32_t  z_magic;                    /**< magic number for sanity check */
    rt_uint32_t  z_nfree;                    /**< total free chunks / ualloc space in zone */
    rt_uint32_t  z_nmax;                     /**< maximum free chunks */
    struct rt_slab_zone *z_next;            /**< zoneary[] link if z_nfree non-zero */
    rt_uint8_t  *z_baseptr;                 /**< pointer to start of chunk array */

    rt_uint32_t  z_uindex;                   /**< current initial allocation index */
    rt_uint32_t  z_chunksize;                /**< chunk size for validation */

    rt_uint32_t  z_zoneindex;                /**< zone index */
    struct rt_slab_chunk  *z_freechunk;     /**< free chunk list */
};

/*
 * Chunk structure for free elements
 */
struct rt_slab_chunk
{
    struct rt_slab_chunk *c_next;
};

struct rt_slab_memusage
{
    rt_uint32_t     type: 2 ;               /**< page type */
    rt_uint32_t     size: 30;               /**< pages allocated or offset from zone */
};

/*
 * slab page allocator
 */
struct rt_slab_page
{
    struct rt_slab_page *next;      /**< next valid page */
    rt_size_t page;                 /**< number of page  */

    /* dummy */
    char dummy[RT_MM_PAGE_SIZE - (sizeof(struct rt_slab_page *) + sizeof(rt_size_t))];
};

#define RT_SLAB_NZONES                  72              /* number of zones */

/*
 * slab object
 */
struct rt_slab
{
    struct rt_memory            parent;                         /**< inherit from rt_memory */
    rt_ubase_t                  heap_start;                     /**< memory start address */
    rt_ubase_t                  heap_end;                       /**< memory end address */
    struct rt_slab_memusage    *memusage;
    struct rt_slab_zone        *zone_array[RT_SLAB_NZONES];     /* linked list of zones NFree > 0 */
    struct rt_slab_zone        *zone_free;                      /* whole zones that have become free */
    rt_uint32_t                 zone_free_cnt;
    rt_uint32_t                 zone_size;
    rt_uint32_t                 zone_limit;
    rt_uint32_t                 zone_page_cnt;
    struct rt_slab_page        *page_list;
};

/**
 * @brief Alloc memory size by page.
 *
 * @param slab the slab memory management object.
 *
 * @param npages the number of pages.
 */
void *rt_slab_page_alloc(rt_slab_t m, rt_size_t npages)
{
    struct rt_slab_page *b, *n;
    struct rt_slab_page **prev;
    struct rt_slab *slab = (struct rt_slab *)m;

    if (npages == 0)
        return RT_NULL;

    for (prev = &slab->page_list; (b = *prev) != RT_NULL; prev = &(b->next))
    {
        if (b->page > npages)
        {
            /* splite pages */
            n       = b + npages;
            n->next = b->next;
            n->page = b->page - npages;
            *prev   = n;
            break;
        }

        if (b->page == npages)
        {
            /* this node fit, remove this node */
            *prev = b->next;
            break;
        }
    }

    return b;
}

/**
 * @brief Free memory by page.
 *
 * @param slab the slab memory management object.
 *
 * @param addr is the head address of first page.
 *
 * @param npages is the number of pages.
 */
void rt_slab_page_free(rt_slab_t m, void *addr, rt_size_t npages)
{
    struct rt_slab_page *b, *n;
    struct rt_slab_page **prev;
    struct rt_slab *slab = (struct rt_slab *)m;

    RT_ASSERT(addr != RT_NULL);
    RT_ASSERT((rt_ubase_t)addr % RT_MM_PAGE_SIZE == 0);
    RT_ASSERT(npages != 0);

    n = (struct rt_slab_page *)addr;

    for (prev = &slab->page_list; (b = *prev) != RT_NULL; prev = &(b->next))
    {
        RT_ASSERT(b->page > 0);
        RT_ASSERT(b > n || b + b->page <= n);

        if (b + b->page == n)
        {
            if (b + (b->page += npages) == b->next)
            {
                b->page += b->next->page;
                b->next  = b->next->next;
            }
            return;
        }

        if (b == n + npages)
        {
            n->page = b->page + npages;
            n->next = b->next;
            *prev   = n;
            return;
        }

        if (b > n + npages)
            break;
    }

    n->page = npages;
    n->next = b;
    *prev   = n;
}

/*
 * Initialize the page allocator
 */
static void rt_slab_page_init(struct rt_slab *slab, void *addr, rt_size_t npages)
{
    RT_ASSERT(addr != RT_NULL);
    RT_ASSERT(npages != 0);

    slab->page_list = RT_NULL;
    rt_slab_page_free((rt_slab_t)(&slab->parent), addr, npages);
}

/**
 * @brief This function will init slab memory management algorithm
 *
 * @param slab the slab memory management object.
 *
 * @param name is the name of the slab memory management object.
 *
 * @param begin_addr the beginning address of system page.
 *
 * @param size is the size of the memory.
 *
 * @return Return a pointer to the slab memory object.
 */
rt_slab_t rt_slab_init(const char *name, void *begin_addr, rt_size_t size)
{
    rt_uint32_t limsize, npages;
    rt_ubase_t start_addr, begin_align, end_align;
    struct rt_slab *slab;

    slab = (struct rt_slab *)RT_ALIGN((rt_ubase_t)begin_addr, RT_ALIGN_SIZE);
    start_addr = (rt_ubase_t)slab + sizeof(*slab);
    /* align begin and end addr to page */
    begin_align = RT_ALIGN((rt_ubase_t)start_addr, RT_MM_PAGE_SIZE);
    end_align   = RT_ALIGN_DOWN((rt_ubase_t)begin_addr + size, RT_MM_PAGE_SIZE);
    if (begin_align >= end_align)
    {
        rt_kprintf("slab init errr. wrong address[0x%x - 0x%x]\n",
                   (rt_ubase_t)begin_addr, (rt_ubase_t)begin_addr + size);
        return RT_NULL;
    }

    limsize = end_align - begin_align;
    npages  = limsize / RT_MM_PAGE_SIZE;
    RT_DEBUG_LOG(RT_DEBUG_SLAB, ("heap[0x%x - 0x%x], size 0x%x, 0x%x pages\n",
                                begin_align, end_align, limsize, npages));

    rt_memset(slab, 0, sizeof(*slab));
    /* initialize slab memory object */
    rt_object_init(&(slab->parent.parent), RT_Object_Class_Memory, name);
    slab->parent.algorithm = "slab";
    slab->parent.address = begin_align;
    slab->parent.total = limsize;
    slab->parent.used = 0;
    slab->parent.max = 0;
    slab->heap_start = begin_align;
    slab->heap_end = end_align;

    /* init pages */
    rt_slab_page_init(slab, (void *)slab->heap_start, npages);

    /* calculate zone size */
    slab->zone_size = ZALLOC_MIN_ZONE_SIZE;
    while (slab->zone_size < ZALLOC_MAX_ZONE_SIZE && (slab->zone_size << 1) < (limsize / 1024))
        slab->zone_size <<= 1;

    slab->zone_limit = slab->zone_size / 4;
    if (slab->zone_limit > ZALLOC_ZONE_LIMIT)
        slab->zone_limit = ZALLOC_ZONE_LIMIT;

    slab->zone_page_cnt = slab->zone_size / RT_MM_PAGE_SIZE;

    RT_DEBUG_LOG(RT_DEBUG_SLAB, ("zone size 0x%x, zone page count 0x%x\n",
                                 slab->zone_size, slab->zone_page_cnt));

    /* allocate slab->memusage array */
    limsize  = npages * sizeof(struct rt_slab_memusage);
    limsize  = RT_ALIGN(limsize, RT_MM_PAGE_SIZE);
    slab->memusage = rt_slab_page_alloc((rt_slab_t)(&slab->parent), limsize / RT_MM_PAGE_SIZE);

    RT_DEBUG_LOG(RT_DEBUG_SLAB, ("slab->memusage 0x%x, size 0x%x\n",
                                 (rt_ubase_t)slab->memusage, limsize));
    return &slab->parent;
}
RTM_EXPORT(rt_slab_init);

/**
 * @brief This function will remove a slab object from the system.
 *
 * @param m the slab memory management object.
 *
 * @return RT_EOK
 */
rt_err_t rt_slab_detach(rt_slab_t m)
{
    struct rt_slab *slab = (struct rt_slab *)m;

    RT_ASSERT(slab != RT_NULL);
    RT_ASSERT(rt_object_get_type(&slab->parent.parent) == RT_Object_Class_Memory);
    RT_ASSERT(rt_object_is_systemobject(&slab->parent.parent));

    rt_object_detach(&(slab->parent.parent));

    return RT_EOK;
}
RTM_EXPORT(rt_slab_detach);

/*
 * Calculate the zone index for the allocation request size and set the
 * allocation request size to that particular zone's chunk size.
 */
rt_inline int zoneindex(rt_size_t *bytes)
{
    /* unsigned for shift opt */
    rt_ubase_t n = (rt_ubase_t)(*bytes);

    if (n < 128)
    {
        *bytes = n = (n + 7) & ~7;

        /* 8 byte chunks, 16 zones */
        return (n / 8 - 1);
    }
    if (n < 256)
    {
        *bytes = n = (n + 15) & ~15;

        return (n / 16 + 7);
    }
    if (n < 8192)
    {
        if (n < 512)
        {
            *bytes = n = (n + 31) & ~31;

            return (n / 32 + 15);
        }
        if (n < 1024)
        {
            *bytes = n = (n + 63) & ~63;

            return (n / 64 + 23);
        }
        if (n < 2048)
        {
            *bytes = n = (n + 127) & ~127;

            return (n / 128 + 31);
        }
        if (n < 4096)
        {
            *bytes = n = (n + 255) & ~255;

            return (n / 256 + 39);
        }
        *bytes = n = (n + 511) & ~511;

        return (n / 512 + 47);
    }
    if (n < 16384)
    {
        *bytes = n = (n + 1023) & ~1023;

        return (n / 1024 + 55);
    }

    rt_kprintf("Unexpected byte count %d", n);

    return 0;
}

/**
 * @addtogroup MM
 */

/**@{*/

/**
 * @brief This function will allocate a block from slab object.
 *
 * @note the RT_NULL is returned if
 *         - the nbytes is less than zero.
 *         - there is no nbytes sized memory valid in system.
 *
 * @param m the slab memory management object.
 *
 * @param size is the size of memory to be allocated.
 *
 * @return the allocated memory.
 */
void *rt_slab_alloc(rt_slab_t m, rt_size_t size)
{
    struct rt_slab_zone *z;
    rt_int32_t zi;
    struct rt_slab_chunk *chunk;
    struct rt_slab_memusage *kup;
    struct rt_slab *slab = (struct rt_slab *)m;

    /* zero size, return RT_NULL */
    if (size == 0)
        return RT_NULL;

    /*
     * Handle large allocations directly.  There should not be very many of
     * these so performance is not a big issue.
     */
    if (size >= slab->zone_limit)
    {
        size = RT_ALIGN(size, RT_MM_PAGE_SIZE);

        chunk = rt_slab_page_alloc(m, size >> RT_MM_PAGE_BITS);
        if (chunk == RT_NULL)
            return RT_NULL;

        /* set kup */
        kup = btokup(chunk);
        kup->type = PAGE_TYPE_LARGE;
        kup->size = size >> RT_MM_PAGE_BITS;

        RT_DEBUG_LOG(RT_DEBUG_SLAB,
                     ("alloc a large memory 0x%x, page cnt %d, kup %d\n",
                      size,
                      size >> RT_MM_PAGE_BITS,
                      ((rt_ubase_t)chunk - slab->heap_start) >> RT_MM_PAGE_BITS));
        /* mem stat */
        slab->parent.used += size;
        if (slab->parent.used > slab->parent.max)
            slab->parent.max = slab->parent.used;
        return chunk;
    }

    /*
     * Attempt to allocate out of an existing zone.  First try the free list,
     * then allocate out of unallocated space.  If we find a good zone move
     * it to the head of the list so later allocations find it quickly
     * (we might have thousands of zones in the list).
     *
     * Note: zoneindex() will panic of size is too large.
     */
    zi = zoneindex(&size);
    RT_ASSERT(zi < RT_SLAB_NZONES);

    RT_DEBUG_LOG(RT_DEBUG_SLAB, ("try to alloc 0x%x on zone: %d\n", size, zi));

    if ((z = slab->zone_array[zi]) != RT_NULL)
    {
        RT_ASSERT(z->z_nfree > 0);

        /* Remove us from the zone_array[] when we become full */
        if (--z->z_nfree == 0)
        {
            slab->zone_array[zi] = z->z_next;
            z->z_next = RT_NULL;
        }

        /*
         * No chunks are available but nfree said we had some memory, so
         * it must be available in the never-before-used-memory area
         * governed by uindex.  The consequences are very serious if our zone
         * got corrupted so we use an explicit rt_kprintf rather then a KASSERT.
         */
        if (z->z_uindex + 1 != z->z_nmax)
        {
            z->z_uindex = z->z_uindex + 1;
            chunk = (struct rt_slab_chunk *)(z->z_baseptr + z->z_uindex * size);
        }
        else
        {
            /* find on free chunk list */
            chunk = z->z_freechunk;

            /* remove this chunk from list */
            z->z_freechunk = z->z_freechunk->c_next;
        }
        /* mem stats */
        slab->parent.used += z->z_chunksize;
        if (slab->parent.used > slab->parent.max)
            slab->parent.max = slab->parent.used;

        return chunk;
    }

    /*
     * If all zones are exhausted we need to allocate a new zone for this
     * index.
     *
     * At least one subsystem, the tty code (see CROUND) expects power-of-2
     * allocations to be power-of-2 aligned.  We maintain compatibility by
     * adjusting the base offset below.
     */
    {
        rt_uint32_t off;

        if ((z = slab->zone_free) != RT_NULL)
        {
            /* remove zone from free zone list */
            slab->zone_free = z->z_next;
            -- slab->zone_free_cnt;
        }
        else
        {
            /* allocate a zone from page */
            z = rt_slab_page_alloc(m, slab->zone_size / RT_MM_PAGE_SIZE);
            if (z == RT_NULL)
            {
                return RT_NULL;
            }

            RT_DEBUG_LOG(RT_DEBUG_SLAB, ("alloc a new zone: 0x%x\n",
                                         (rt_ubase_t)z));

            /* set message usage */
            for (off = 0, kup = btokup(z); off < slab->zone_page_cnt; off ++)
            {
                kup->type = PAGE_TYPE_SMALL;
                kup->size = off;

                kup ++;
            }
        }

        /* clear to zero */
        rt_memset(z, 0, sizeof(struct rt_slab_zone));

        /* offset of slab zone struct in zone */
        off = sizeof(struct rt_slab_zone);

        /*
         * Guarentee power-of-2 alignment for power-of-2-sized chunks.
         * Otherwise just 8-byte align the data.
         */
        if ((size | (size - 1)) + 1 == (size << 1))
            off = (off + size - 1) & ~(size - 1);
        else
            off = (off + MIN_CHUNK_MASK) & ~MIN_CHUNK_MASK;

        z->z_magic     = ZALLOC_SLAB_MAGIC;
        z->z_zoneindex = zi;
        z->z_nmax      = (slab->zone_size - off) / size;
        z->z_nfree     = z->z_nmax - 1;
        z->z_baseptr   = (rt_uint8_t *)z + off;
        z->z_uindex    = 0;
        z->z_chunksize = size;

        chunk = (struct rt_slab_chunk *)(z->z_baseptr + z->z_uindex * size);

        /* link to zone array */
        z->z_next = slab->zone_array[zi];
        slab->zone_array[zi] = z;
        /* mem stats */
        slab->parent.used += z->z_chunksize;
        if (slab->parent.used > slab->parent.max)
            slab->parent.max = slab->parent.used;
    }

    return chunk;
}
RTM_EXPORT(rt_slab_alloc);

/**
 * @brief This function will change the size of previously allocated memory block.
 *
 * @param m the slab memory management object.
 *
 * @param ptr is the previously allocated memory block.
 *
 * @param size is the new size of memory block.
 *
 * @return the allocated memory.
 */
void *rt_slab_realloc(rt_slab_t m, void *ptr, rt_size_t size)
{
    void *nptr;
    struct rt_slab_zone *z;
    struct rt_slab_memusage *kup;
    struct rt_slab *slab = (struct rt_slab *)m;

    if (ptr == RT_NULL)
        return rt_slab_alloc(m, size);

    if (size == 0)
    {
        rt_slab_free(m, ptr);
        return RT_NULL;
    }

    /*
     * Get the original allocation's zone.  If the new request winds up
     * using the same chunk size we do not have to do anything.
     */
    kup = btokup((rt_ubase_t)ptr & ~RT_MM_PAGE_MASK);
    if (kup->type == PAGE_TYPE_LARGE)
    {
        rt_size_t osize;

        osize = kup->size << RT_MM_PAGE_BITS;
        if ((nptr = rt_slab_alloc(m, size)) == RT_NULL)
            return RT_NULL;
        rt_memcpy(nptr, ptr, size > osize ? osize : size);
        rt_slab_free(m, ptr);

        return nptr;
    }
    else if (kup->type == PAGE_TYPE_SMALL)
    {
        z = (struct rt_slab_zone *)(((rt_ubase_t)ptr & ~RT_MM_PAGE_MASK) -
                          kup->size * RT_MM_PAGE_SIZE);
        RT_ASSERT(z->z_magic == ZALLOC_SLAB_MAGIC);

        zoneindex(&size);
        if (z->z_chunksize == size)
            return (ptr); /* same chunk */

        /*
         * Allocate memory for the new request size.  Note that zoneindex has
         * already adjusted the request size to the appropriate chunk size, which
         * should optimize our bcopy().  Then copy and return the new pointer.
         */
        if ((nptr = rt_slab_alloc(m, size)) == RT_NULL)
            return RT_NULL;

        rt_memcpy(nptr, ptr, size > z->z_chunksize ? z->z_chunksize : size);
        rt_slab_free(m, ptr);

        return nptr;
    }

    return RT_NULL;
}
RTM_EXPORT(rt_slab_realloc);

/**
 * @brief This function will release the previous allocated memory block by rt_slab_alloc.
 *
 * @note The released memory block is taken back to system heap.
 *
 * @param m the slab memory management object.
 * @param ptr is the address of memory which will be released
 */
void rt_slab_free(rt_slab_t m, void *ptr)
{
    struct rt_slab_zone *z;
    struct rt_slab_chunk *chunk;
    struct rt_slab_memusage *kup;
    struct rt_slab *slab = (struct rt_slab *)m;

    /* free a RT_NULL pointer */
    if (ptr == RT_NULL)
        return ;

    /* get memory usage */
#if RT_DEBUG_SLAB
    {
        rt_ubase_t addr = ((rt_ubase_t)ptr & ~RT_MM_PAGE_MASK);
        RT_DEBUG_LOG(RT_DEBUG_SLAB,
                     ("free a memory 0x%x and align to 0x%x, kup index %d\n",
                      (rt_ubase_t)ptr,
                      (rt_ubase_t)addr,
                      ((rt_ubase_t)(addr) - slab->heap_start) >> RT_MM_PAGE_BITS));
    }
#endif /* RT_DEBUG_SLAB */

    kup = btokup((rt_ubase_t)ptr & ~RT_MM_PAGE_MASK);
    /* release large allocation */
    if (kup->type == PAGE_TYPE_LARGE)
    {
        rt_ubase_t size;

        /* clear page counter */
        size = kup->size;
        kup->size = 0;
        /* mem stats */
        slab->parent.used -= size * RT_MM_PAGE_SIZE;

        RT_DEBUG_LOG(RT_DEBUG_SLAB,
                     ("free large memory block 0x%x, page count %d\n",
                      (rt_ubase_t)ptr, size));

        /* free this page */
        rt_slab_page_free(m, ptr, size);

        return;
    }

    /* zone case. get out zone. */
    z = (struct rt_slab_zone *)(((rt_ubase_t)ptr & ~RT_MM_PAGE_MASK) -
                      kup->size * RT_MM_PAGE_SIZE);
    RT_ASSERT(z->z_magic == ZALLOC_SLAB_MAGIC);

    chunk          = (struct rt_slab_chunk *)ptr;
    chunk->c_next  = z->z_freechunk;
    z->z_freechunk = chunk;
    /* mem stats */
    slab->parent.used -= z->z_chunksize;

    /*
     * Bump the number of free chunks.  If it becomes non-zero the zone
     * must be added back onto the appropriate list.
     */
    if (z->z_nfree++ == 0)
    {
        z->z_next = slab->zone_array[z->z_zoneindex];
        slab->zone_array[z->z_zoneindex] = z;
    }

    /*
     * If the zone becomes totally free, and there are other zones we
     * can allocate from, move this zone to the FreeZones list.  Since
     * this code can be called from an IPI callback, do *NOT* try to mess
     * with kernel_map here.  Hysteresis will be performed at malloc() time.
     */
    if (z->z_nfree == z->z_nmax &&
        (z->z_next || slab->zone_array[z->z_zoneindex] != z))
    {
        struct rt_slab_zone **pz;

        RT_DEBUG_LOG(RT_DEBUG_SLAB, ("free zone 0x%x\n",
                                     (rt_ubase_t)z, z->z_zoneindex));

        /* remove zone from zone array list */
        for (pz = &slab->zone_array[z->z_zoneindex]; z != *pz; pz = &(*pz)->z_next)
            ;
        *pz = z->z_next;

        /* reset zone */
        z->z_magic = RT_UINT32_MAX;

        /* insert to free zone list */
        z->z_next = slab->zone_free;
        slab->zone_free = z;

        ++ slab->zone_free_cnt;

        /* release zone to page allocator */
        if (slab->zone_free_cnt > ZONE_RELEASE_THRESH)
        {
            register rt_uint32_t i;

            z         = slab->zone_free;
            slab->zone_free = z->z_next;
            -- slab->zone_free_cnt;

            /* set message usage */
            for (i = 0, kup = btokup(z); i < slab->zone_page_cnt; i ++)
            {
                kup->type = PAGE_TYPE_FREE;
                kup->size = 0;
                kup ++;
            }

            /* release pages */
            rt_slab_page_free(m, z, slab->zone_size / RT_MM_PAGE_SIZE);

            return;
        }
    }
}
RTM_EXPORT(rt_slab_free);

#endif /* defined (RT_USING_SLAB) */
