/* * Simple kernel memory allocator built on top of memcache * implementation. * * Copyright (C) 2007 Bahadir Balban */ #include #include #include #include #include #include #include INC_GLUE(memory.h) /* FIXME: * * mem_cache_alloc() now has an interruptible mutex. * All routines defined here should check returned errors. */ #define PGALLOC_PGD_CACHE 0 #define PGALLOC_PMD_CACHE 1 #define PGALLOC_PG_CACHE 2 #define PGALLOC_CACHE_TOTAL 3 /* The initial chunk of physical memory allocated before any pagers. */ #define PGALLOC_INIT_GRANT SZ_1MB /* Covers 3 main types of memory needed by the kernel. */ struct pgalloc { struct link cache_list[3]; }; static struct pgalloc pgalloc; void pgalloc_add_new_cache(struct mem_cache *cache, int cidx) { link_init(&cache->list); BUG_ON(cidx >= PGALLOC_CACHE_TOTAL || cidx < 0); list_insert(&cache->list, &pgalloc.cache_list[cidx]); } void print_kmem_grant_params(grant_kmem_usage_t *params) { printk("%s: %lu bytes physical memory granted.\n", __KERNELNAME__, params->total_size); printk("%s: Possible kmem usage on this memory grant:\n", __KERNELNAME__); printk("%s: PGDs: %lu, PMDs: %lu, TCBs: %lu, Extra: %lu bytes.\n", __KERNELNAME__, params->total_pgds, params->total_pmds, params->total_tcbs, params->extra); } #define TASK_AVERAGE_SIZE SZ_16MB #define TASK_AVERAGE_PMDS TASK_AVERAGE_SIZE / PMD_MAP_SIZE void calc_grant_kmem_usage(grant_kmem_usage_t *params, unsigned long total_size) { /* Kmem usage per task */ unsigned long task_avg_kmem_usage = PGD_SIZE + PMD_SIZE * 16 + PAGE_SIZE; unsigned long total_tasks = total_size / task_avg_kmem_usage; unsigned long extra = total_size - total_tasks * task_avg_kmem_usage; params->total_size = total_size; params->total_tasks = total_tasks; params->total_pgds = total_tasks; params->total_pmds = total_tasks * 16; params->total_tcbs = total_tasks; params->extra = extra; print_kmem_grant_params(params); } int pgalloc_add_new_grant(unsigned long pfn, int npages) { unsigned long physical = __pfn_to_addr(pfn); void *virtual = (void *)phys_to_virt(physical); struct mem_cache *pgd_cache, *pmd_cache, *pg_cache; grant_kmem_usage_t params; /* First map the whole grant */ add_mapping(physical, phys_to_virt(physical), __pfn_to_addr(npages), MAP_SVC_RW_FLAGS); /* Calculate how to divide buffer into different caches */ calc_grant_kmem_usage(¶ms, __pfn_to_addr(npages)); /* Create the caches, least alignment-needing, most, then others. */ pmd_cache = mem_cache_init(virtual, params.total_pmds * PMD_SIZE, PMD_SIZE, 1); virtual += params.total_pmds * PMD_SIZE; pgd_cache = mem_cache_init(virtual, params.total_pgds * PGD_SIZE, PGD_SIZE, 1); virtual += params.total_pgds * PGD_SIZE; pg_cache = mem_cache_init(virtual, params.total_tcbs * PAGE_SIZE + params.extra, PAGE_SIZE, 1); /* Add the caches */ pgalloc_add_new_cache(pgd_cache, PGALLOC_PGD_CACHE); pgalloc_add_new_cache(pmd_cache, PGALLOC_PMD_CACHE); pgalloc_add_new_cache(pg_cache, PGALLOC_PG_CACHE); return 0; } void init_pgalloc(void) { int initial_grant = PGALLOC_INIT_GRANT; for (int i = 0; i < PGALLOC_CACHE_TOTAL; i++) link_init(&pgalloc.cache_list[i]); /* Grant ourselves with an initial chunk of physical memory */ physmem.free_cur = page_align_up(physmem.free_cur); set_page_map(physmem.free_cur, __pfn(initial_grant), 1); pgalloc_add_new_grant(__pfn(physmem.free_cur), __pfn(initial_grant)); physmem.free_cur += initial_grant; /* Activate kmalloc */ init_kmalloc(); } void pgalloc_remove_cache(struct mem_cache *cache) { list_remove_init(&cache->list); } static inline void *pgalloc_from_cache(int cidx) { struct mem_cache *cache, *n; list_foreach_removable_struct(cache, n, &pgalloc.cache_list[cidx], list) if (mem_cache_total_empty(cache)) return mem_cache_zalloc(cache); return 0; } int kfree_to_cache(int cidx, void *virtual) { struct mem_cache *cache, *n; list_foreach_removable_struct(cache, n, &pgalloc.cache_list[cidx], list) if (mem_cache_free(cache, virtual) == 0) return 0; return -1; } void *alloc_page(void) { return pgalloc_from_cache(PGALLOC_PG_CACHE); } void *alloc_pmd(void) { pmd_table_t *pmd; if (!(pmd = alloc_boot_pmd())) pmd = pgalloc_from_cache(PGALLOC_PMD_CACHE); return pmd; } void *alloc_pgd(void) { return pgalloc_from_cache(PGALLOC_PGD_CACHE); } int free_page(void *v) { return kfree_to_cache(PGALLOC_PG_CACHE, v); } int free_pmd(void *v) { return kfree_to_cache(PGALLOC_PMD_CACHE, v); } int free_pgd(void *v) { return kfree_to_cache(PGALLOC_PGD_CACHE, v); } void *zalloc_page(void) { void *p; if (!(p = alloc_page())) return 0; memset(p, 0, PAGE_SIZE); return p; }