358 lines
11 KiB
C
358 lines
11 KiB
C
/// @file mouse.h
|
|
/// @brief Driver for *PS2* Mouses.
|
|
/// @copyright (c) 2014-2022 This file is distributed under the MIT License.
|
|
/// See LICENSE.md for details.
|
|
|
|
// Include the kernel log levels.
|
|
#include "sys/kernel_levels.h"
|
|
/// Change the header.
|
|
#define __DEBUG_HEADER__ "[SLAB ]"
|
|
/// Set the log level.
|
|
#define __DEBUG_LEVEL__ LOGLEVEL_NOTICE
|
|
|
|
#include "mem/zone_allocator.h"
|
|
#include "mem/paging.h"
|
|
#include "assert.h"
|
|
#include "io/debug.h"
|
|
#include "mem/slab.h"
|
|
|
|
/// @brief Use it to manage cached pages.
|
|
typedef struct kmem_obj {
|
|
/// The list_head for this object.
|
|
list_head objlist;
|
|
} kmem_obj;
|
|
|
|
/// Max order of kmalloc cache allocations, if greater raw page allocation is done.
|
|
#define MAX_KMALLOC_CACHE_ORDER 12
|
|
|
|
#define KMEM_OBJ_OVERHEAD sizeof(kmem_obj)
|
|
#define KMEM_START_OBJ_COUNT 8
|
|
#define KMEM_MAX_REFILL_OBJ_COUNT 64
|
|
#define KMEM_OBJ(cachep, addr) ((kmem_obj *)(addr))
|
|
#define ADDR_FROM_KMEM_OBJ(cachep, kmem_obj) ((void *)(kmem_obj))
|
|
|
|
// The list of caches.
|
|
static list_head kmem_caches_list;
|
|
// Cache where we will store the data about caches.
|
|
static kmem_cache_t kmem_cache;
|
|
// Caches for each order of the malloc.
|
|
static kmem_cache_t *malloc_blocks[MAX_KMALLOC_CACHE_ORDER];
|
|
|
|
static int __alloc_slab_page(kmem_cache_t *cachep, gfp_t flags)
|
|
{
|
|
page_t *page = _alloc_pages(flags, cachep->gfp_order);
|
|
if (!page) {
|
|
pr_crit("Failed to allocate a new page from slab.\n");
|
|
return -1;
|
|
}
|
|
|
|
list_head_init(&page->slabs);
|
|
|
|
// Save in the root page the kmem_cache_t pointer,
|
|
// to allow freeing arbitrary pointers
|
|
page[0].container.slab_cache = cachep;
|
|
|
|
// Update slab main pages of all child pages, to allow
|
|
// reconstructing which page handles a specified address
|
|
for (unsigned int i = 1; i < (1U << cachep->gfp_order); i++) {
|
|
page[i].container.slab_main_page = page;
|
|
}
|
|
|
|
unsigned int slab_size = PAGE_SIZE * (1U << cachep->gfp_order);
|
|
|
|
// Update the page objects counters
|
|
page->slab_objcnt = slab_size / cachep->size;
|
|
page->slab_objfree = page->slab_objcnt;
|
|
|
|
unsigned int pg_addr = get_lowmem_address_from_page(page);
|
|
|
|
list_head_init(&page->slab_freelist);
|
|
|
|
// Build the objects structures
|
|
for (unsigned int i = 0; i < page->slab_objcnt; i++) {
|
|
kmem_obj *obj = KMEM_OBJ(cachep, pg_addr + cachep->size * i);
|
|
list_head_add(&obj->objlist, &page->slab_freelist);
|
|
}
|
|
|
|
// Add the page to the slab list and update the counters
|
|
list_head_add(&page->slabs, &cachep->slabs_free);
|
|
cachep->total_num += page->slab_objcnt;
|
|
cachep->free_num += page->slab_objcnt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __kmem_cache_refill(kmem_cache_t *cachep, unsigned int free_num, gfp_t flags)
|
|
{
|
|
while (cachep->free_num < free_num) {
|
|
if (__alloc_slab_page(cachep, flags) < 0) {
|
|
pr_warning("Cannot allocate a page, abort refill\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static unsigned int __find_next_alignment(unsigned int size, unsigned int align)
|
|
{
|
|
return (size / align + (size % align ? 1 : 0)) * align;
|
|
}
|
|
|
|
static void __compute_size_and_order(kmem_cache_t *cachep)
|
|
{
|
|
// Align the whole object to the required padding
|
|
cachep->size = __find_next_alignment(
|
|
max(cachep->object_size, KMEM_OBJ_OVERHEAD),
|
|
max(8, cachep->align));
|
|
|
|
// Compute the gfp order
|
|
unsigned int size = __find_next_alignment(cachep->size, PAGE_SIZE) / PAGE_SIZE;
|
|
while ((size /= 2) > 0) {
|
|
cachep->gfp_order++;
|
|
}
|
|
}
|
|
|
|
static void __kmem_cache_create(kmem_cache_t *cachep, const char *name, unsigned int size, unsigned int align, slab_flags_t flags, void (*ctor)(void *), void (*dtor)(void *), unsigned int start_count)
|
|
{
|
|
pr_info("Creating new cache `%s` with objects of size `%d`.\n", name, size);
|
|
|
|
*cachep = (kmem_cache_t){
|
|
.name = name,
|
|
.object_size = size,
|
|
.align = align,
|
|
.flags = flags,
|
|
.ctor = ctor,
|
|
.dtor = dtor
|
|
};
|
|
|
|
list_head_init(&cachep->slabs_free);
|
|
list_head_init(&cachep->slabs_partial);
|
|
list_head_init(&cachep->slabs_full);
|
|
|
|
__compute_size_and_order(cachep);
|
|
|
|
__kmem_cache_refill(cachep, start_count, flags);
|
|
|
|
list_head_add(&cachep->cache_list, &kmem_caches_list);
|
|
}
|
|
|
|
static inline void *__kmem_cache_alloc_slab(kmem_cache_t *cachep, page_t *slab_page)
|
|
{
|
|
list_head *elem_listp = list_head_pop(&slab_page->slab_freelist);
|
|
if (!elem_listp) {
|
|
pr_warning("There are no FREE element inside the slab_freelist\n");
|
|
return NULL;
|
|
}
|
|
slab_page->slab_objfree--;
|
|
cachep->free_num--;
|
|
|
|
kmem_obj *obj = list_entry(elem_listp, kmem_obj, objlist);
|
|
|
|
// Get the element from the kmem_obj object
|
|
void *elem = ADDR_FROM_KMEM_OBJ(cachep, obj);
|
|
|
|
if (cachep->ctor)
|
|
cachep->ctor(elem);
|
|
|
|
return elem;
|
|
}
|
|
|
|
static inline void __kmem_cache_free_slab(kmem_cache_t *cachep, page_t *slab_page)
|
|
{
|
|
cachep->free_num -= slab_page->slab_objfree;
|
|
cachep->total_num -= slab_page->slab_objcnt;
|
|
// Clear objcnt, used as a flag to check if the page belongs to the slab
|
|
slab_page->slab_objcnt = 0;
|
|
slab_page->container.slab_main_page = NULL;
|
|
|
|
// Reset all non-root slab pages
|
|
for (unsigned int i = 1; i < (1U << cachep->gfp_order); i++) {
|
|
(slab_page + i)->container.slab_main_page = NULL;
|
|
}
|
|
|
|
__free_pages(slab_page);
|
|
}
|
|
|
|
void kmem_cache_init()
|
|
{
|
|
// Initialize the list of caches.
|
|
list_head_init(&kmem_caches_list);
|
|
// Create a cache to store the data about caches.
|
|
__kmem_cache_create(
|
|
&kmem_cache,
|
|
"kmem_cache_t",
|
|
sizeof(kmem_cache_t),
|
|
alignof(kmem_cache_t),
|
|
GFP_KERNEL,
|
|
NULL,
|
|
NULL, 32);
|
|
for (unsigned int i = 0; i < MAX_KMALLOC_CACHE_ORDER; i++) {
|
|
malloc_blocks[i] = kmem_cache_create(
|
|
"kmalloc",
|
|
1u << i,
|
|
1u << i,
|
|
GFP_KERNEL,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
kmem_cache_t *kmem_cache_create(const char *name, unsigned int size, unsigned int align, slab_flags_t flags, void (*ctor)(void *), void (*dtor)(void *))
|
|
{
|
|
kmem_cache_t *cachep = (kmem_cache_t *)kmem_cache_alloc(&kmem_cache, GFP_KERNEL);
|
|
if (!cachep)
|
|
return cachep;
|
|
|
|
__kmem_cache_create(cachep, name, size, align, flags, ctor, dtor, KMEM_START_OBJ_COUNT);
|
|
|
|
return cachep;
|
|
}
|
|
|
|
void kmem_cache_destroy(kmem_cache_t *cachep)
|
|
{
|
|
while (!list_head_empty(&cachep->slabs_free)) {
|
|
list_head *slab_list = list_head_pop(&cachep->slabs_free);
|
|
__kmem_cache_free_slab(cachep, list_entry(slab_list, page_t, slabs));
|
|
}
|
|
|
|
while (!list_head_empty(&cachep->slabs_partial)) {
|
|
list_head *slab_list = list_head_pop(&cachep->slabs_partial);
|
|
__kmem_cache_free_slab(cachep, list_entry(slab_list, page_t, slabs));
|
|
}
|
|
|
|
while (!list_head_empty(&cachep->slabs_full)) {
|
|
list_head *slab_list = list_head_pop(&cachep->slabs_full);
|
|
__kmem_cache_free_slab(cachep, list_entry(slab_list, page_t, slabs));
|
|
}
|
|
|
|
kmem_cache_free(cachep);
|
|
list_head_del(&cachep->cache_list);
|
|
}
|
|
|
|
#ifdef ENABLE_CACHE_TRACE
|
|
void *pr_kmem_cache_alloc(const char *file, const char *fun, int line, kmem_cache_t *cachep, gfp_t flags)
|
|
#else
|
|
void *kmem_cache_alloc(kmem_cache_t *cachep, gfp_t flags)
|
|
#endif
|
|
{
|
|
if (list_head_empty(&cachep->slabs_partial)) {
|
|
if (list_head_empty(&cachep->slabs_free)) {
|
|
if (flags == 0)
|
|
flags = cachep->flags;
|
|
|
|
// Refill the cache in an exponential fashion, capping at KMEM_MAX_REFILL_OBJ_COUNT to avoid
|
|
// too big allocations
|
|
__kmem_cache_refill(cachep, min(cachep->total_num, KMEM_MAX_REFILL_OBJ_COUNT), flags);
|
|
if (list_head_empty(&cachep->slabs_free)) {
|
|
pr_crit("Cannot allocate more slabs in `%s`\n", cachep->name);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// Add a free slab to partial list because in any case an element will
|
|
// be removed before the function returns
|
|
list_head *free_slab = list_head_pop(&cachep->slabs_free);
|
|
list_head_add(free_slab, &cachep->slabs_partial);
|
|
}
|
|
|
|
page_t *slab_page = list_entry(list_head_front(&cachep->slabs_partial), page_t, slabs);
|
|
void *ptr = __kmem_cache_alloc_slab(cachep, slab_page);
|
|
|
|
// If the slab is now full, add it to the full slabs list
|
|
if (slab_page->slab_objfree == 0) {
|
|
list_head *slab_full_elem = list_head_pop(&cachep->slabs_partial);
|
|
list_head_add(slab_full_elem, &cachep->slabs_full);
|
|
}
|
|
#ifdef ENABLE_CACHE_TRACE
|
|
pr_notice("kmem_cache_alloc : (%-16s:%3d)[%-16s] : 0x%p\n", file, line, cachep->name, ptr);
|
|
#endif
|
|
return ptr;
|
|
}
|
|
|
|
#ifdef ENABLE_CACHE_TRACE
|
|
void pr_kmem_cache_free(const char *file, const char *fun, int line, void *ptr)
|
|
#else
|
|
void kmem_cache_free(void *ptr)
|
|
#endif
|
|
{
|
|
page_t *slab_page = get_lowmem_page_from_address((uint32_t)ptr);
|
|
|
|
// If the slab main page is a lowmem page, change to it as it's the root page
|
|
if (is_lowmem_page_struct(slab_page->container.slab_main_page)) {
|
|
slab_page = slab_page->container.slab_main_page;
|
|
}
|
|
|
|
kmem_cache_t *cachep = slab_page->container.slab_cache;
|
|
|
|
#ifdef ENABLE_CACHE_TRACE
|
|
pr_notice("kmem_cache_free : (%-16s:%3d)[%-16s] : 0x%p\n", file, line, cachep->name, ptr);
|
|
#endif
|
|
if (cachep->dtor)
|
|
cachep->dtor(ptr);
|
|
|
|
kmem_obj *obj = KMEM_OBJ(cachep, ptr);
|
|
|
|
// Add object to the free list
|
|
list_head_add(&obj->objlist, &slab_page->slab_freelist);
|
|
slab_page->slab_objfree++;
|
|
cachep->free_num++;
|
|
|
|
// Now page is completely free
|
|
if (slab_page->slab_objfree == slab_page->slab_objcnt) {
|
|
// Remove page from partial list
|
|
list_head_del(&slab_page->slabs);
|
|
// Add page to free list
|
|
list_head_add(&slab_page->slabs, &cachep->slabs_free);
|
|
}
|
|
// Now page is not full, so change its list
|
|
else if (slab_page->slab_objfree == 1) {
|
|
// Remove page from full list
|
|
list_head_del(&slab_page->slabs);
|
|
// Add page to partial list
|
|
list_head_add(&slab_page->slabs, &cachep->slabs_partial);
|
|
}
|
|
}
|
|
|
|
#ifdef ENABLE_ALLOC_TRACE
|
|
void *pr_kmalloc(const char *file, const char *fun, int line, unsigned int size)
|
|
#else
|
|
void *kmalloc(unsigned int size)
|
|
#endif
|
|
{
|
|
unsigned int order = 0;
|
|
while (size != 0) {
|
|
order++;
|
|
size /= 2;
|
|
}
|
|
|
|
// If size does not fit in the maximum cache order, allocate raw pages
|
|
void *ptr;
|
|
if (order >= MAX_KMALLOC_CACHE_ORDER) {
|
|
ptr = (void *)__alloc_pages_lowmem(GFP_KERNEL, order - 12);
|
|
} else {
|
|
ptr = kmem_cache_alloc(malloc_blocks[order], GFP_KERNEL);
|
|
}
|
|
#ifdef ENABLE_ALLOC_TRACE
|
|
pr_notice("kmalloc : (%-16s:%3d) : 0x%p\n", file, line, ptr);
|
|
#endif
|
|
return ptr;
|
|
}
|
|
|
|
#ifdef ENABLE_ALLOC_TRACE
|
|
void pr_kfree(const char *file, const char *fun, int line, void *ptr)
|
|
#else
|
|
void kfree(void *ptr)
|
|
#endif
|
|
{
|
|
#ifdef ENABLE_ALLOC_TRACE
|
|
pr_notice("kfree : (%-16s:%3d) : 0x%p\n", file, line, ptr);
|
|
#endif
|
|
page_t *page = get_lowmem_page_from_address((uint32_t)ptr);
|
|
|
|
// If the address is part of the cache
|
|
if (page->container.slab_main_page) {
|
|
kmem_cache_free(ptr);
|
|
} else {
|
|
free_pages_lowmem((uint32_t)ptr);
|
|
}
|
|
}
|