bc2a13136a
BCMSAI 4.3.0.10, 6.5.21 SDK release with enhancements and fixes for vxlan, TD3 MMU, TD4-X9 EA support, etc.
473 lines
14 KiB
C
473 lines
14 KiB
C
/*! \file ngbde_pgmem.c
|
|
*
|
|
* \brief PGMEM allocator.
|
|
*
|
|
* This module is used to allocate large physically contiguous memory
|
|
* blocks using the Linux kernel page allocator.
|
|
*
|
|
* The Linux page allocator can allocate contiguous memory up until a
|
|
* certain size, which depends on the kernel version and the CPU
|
|
* architecture.
|
|
*
|
|
* If a larger contiguous memory block is requested, then we need to
|
|
* allocate multiple blocks from the Linux page allocator and then
|
|
* check if which ones are contiguous.
|
|
*
|
|
* The smaller memory blocks from which the larger block is assembled
|
|
* are referred to as "chunks".
|
|
*
|
|
* The PGMEM allocator will continue to allocate chunks from the Linux
|
|
* page allocator, until a contiguous memory block of the requested
|
|
* size has been assembled, or until a predefined maximum number of
|
|
* chunks have been allocated. Obviously the process is also stopped
|
|
* if the Linux page allocator returns an error.
|
|
*
|
|
* A physically contiguous memory block assembled from smaller memory
|
|
* chunks are referred to as "cmblocks".
|
|
*
|
|
* The chance of success depends on the requested memory block size as
|
|
* well as the fragmentation level of the system memory, i.e. the
|
|
* sooner after system boot these memory block are requested, the more
|
|
* likely these requests are to succeed.
|
|
*/
|
|
/*
|
|
* $Copyright: Copyright 2018-2020 Broadcom. All rights reserved.
|
|
* The term 'Broadcom' refers to Broadcom Inc. and/or its subsidiaries.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* A copy of the GNU General Public License version 2 (GPLv2) can
|
|
* be found in the LICENSES folder.$
|
|
*/
|
|
|
|
#include <ngbde.h>
|
|
|
|
|
|
/*******************************************************************************
|
|
* Local definitions
|
|
******************************************************************************/
|
|
|
|
/*! Maximum size the kernel can allocate in a single allocation. */
|
|
#define MEM_CHUNK_SIZE_MAX (1 << (MAX_ORDER - 1 + PAGE_SHIFT))
|
|
|
|
/*! Default block size we wil request from the kernel. */
|
|
#define MEM_CHUNK_SIZE_DEFAULT (512 * ONE_KB)
|
|
|
|
/*! \cond */
|
|
static int pgmem_chunk_size = 0;
|
|
module_param(pgmem_chunk_size, int, 0);
|
|
MODULE_PARM_DESC(pgmem_chunk_size,
|
|
"Memory chunk size in KB used by page allocator (default auto).");
|
|
/*! \endcond */
|
|
|
|
/*! \cond */
|
|
static int pgmem_debug = 0;
|
|
module_param(pgmem_debug, int, 0);
|
|
MODULE_PARM_DESC(pgmem_debug,
|
|
"Enable page memory allocator debug output (default 0).");
|
|
/*! \endcond */
|
|
|
|
/*! Helper macro for debug trace output. */
|
|
#define PGMEM_TRACE(_s) \
|
|
do { \
|
|
if (pgmem_debug) { \
|
|
printk(_s); \
|
|
} \
|
|
} while (0)
|
|
|
|
/*!
|
|
* Chunk memory block descriptor.
|
|
*/
|
|
typedef struct cmblock_desc_s {
|
|
|
|
/*! Linked-list handle. */
|
|
struct list_head list;
|
|
|
|
/*! Requested cmblock size. */
|
|
unsigned long req_size;
|
|
|
|
/*! Memory chunk size. */
|
|
unsigned long chunk_size;
|
|
|
|
/*! Memory chunk size in alternate format (2^x). */
|
|
unsigned long chunk_order;
|
|
|
|
/*! Current cmblock size. */
|
|
unsigned long cmblk_size;
|
|
|
|
/*! Logical address of cmblock. */
|
|
unsigned long cmblk_begin;
|
|
|
|
/*! Logical end address of cmblock. */
|
|
unsigned long cmblk_end;
|
|
|
|
/*! Array of logical chunk addresses. */
|
|
unsigned long *chunk_ptr;
|
|
|
|
/*! Maximum number of chunks to allocate. */
|
|
int chunk_cnt_max;
|
|
|
|
/*! Current number of chunks allocated. */
|
|
int chunk_cnt;
|
|
|
|
} cmblock_desc_t;
|
|
|
|
static LIST_HEAD(cmblocks_list);
|
|
|
|
|
|
/*!
|
|
* \name Chunk tag mask.
|
|
* \anchor CT_xxx
|
|
*
|
|
* The lower two bits of the chunk address is used to tag the chunk
|
|
* with its current state.
|
|
*/
|
|
#define CT_MASK 0x3
|
|
|
|
/*! Chunk is untagged. */
|
|
#define CT_UNTAGGED 0
|
|
|
|
/*! Chunk was discarded. */
|
|
#define CT_DISCARDED 1
|
|
|
|
/*! Chunk is part of largest cmblock. */
|
|
#define CT_LARGEST 2
|
|
|
|
/*! Chunk is part of current cmblock. */
|
|
#define CT_CURRENT 3
|
|
|
|
/*! Set block as untagged. */
|
|
#define CTAG_SET(_a, _t) \
|
|
do { \
|
|
(_a) &= ~CT_MASK; \
|
|
(_a) |= _t; \
|
|
} while (0)
|
|
|
|
/*! Set block as untagged. */
|
|
#define CTAG_GET(_a) \
|
|
((_a) & CT_MASK)
|
|
|
|
|
|
/*******************************************************************************
|
|
* Private Functions
|
|
******************************************************************************/
|
|
|
|
/*!
|
|
* \brief Find largest contiguous memory block.
|
|
*
|
|
* Find largest contiguous memory block from a pool of memory chunks.
|
|
*
|
|
* Assembly stops if a cmblock of the requested cmblock size has been
|
|
* obtained.
|
|
*
|
|
* The lower two address bits of the memory chunks are encoded as a
|
|
* tag according to \ref CT_xxx.
|
|
*
|
|
* \param [in] cmbd cmblock descriptor.
|
|
*
|
|
* \return Always 0.
|
|
*/
|
|
static int
|
|
find_largest_cmblock(cmblock_desc_t *cmbd)
|
|
{
|
|
int i, j, chunks, found;
|
|
unsigned long b, e, a;
|
|
unsigned long *cptr;
|
|
|
|
/* Convenience variable */
|
|
chunks = cmbd->chunk_cnt;
|
|
cptr = cmbd->chunk_ptr;
|
|
|
|
/* Clear all chunk tags */
|
|
for (i = 0; i < chunks; i++) {
|
|
CTAG_SET(cptr[i], CT_UNTAGGED);
|
|
}
|
|
for (i = 0; i < chunks && cmbd->cmblk_size < cmbd->req_size; i++) {
|
|
/* First chunk must be an untagged chunk */
|
|
if (CTAG_GET(cptr[i]) == CT_UNTAGGED) {
|
|
/* Initial cmblock size is the chunk size */
|
|
b = cptr[i];
|
|
e = b + cmbd->chunk_size;
|
|
CTAG_SET(cptr[i], CT_CURRENT);
|
|
/* Loop looking for adjacent chunks */
|
|
do {
|
|
found = 0;
|
|
for (j = i + 1; j < chunks && (e - b) < cmbd->req_size; j++) {
|
|
a = cptr[j];
|
|
/* Check untagged chunks only */
|
|
if (CTAG_GET(a) == CT_UNTAGGED) {
|
|
if (a == (b - cmbd->chunk_size)) {
|
|
/* Found adjacent chunk below current cmblock */
|
|
CTAG_SET(cptr[j], CT_CURRENT);
|
|
b = a;
|
|
found = 1;
|
|
} else if (a == e) {
|
|
/* Found adjacent chunk above current cmblock */
|
|
CTAG_SET(cptr[j], CT_CURRENT);
|
|
e += cmbd->chunk_size;
|
|
found = 1;
|
|
}
|
|
}
|
|
}
|
|
} while (found);
|
|
/* Now check the size of the assembled memory block */
|
|
if ((e - b) > cmbd->cmblk_size) {
|
|
/* The current block is largest so far */
|
|
cmbd->cmblk_begin = b;
|
|
cmbd->cmblk_end = e;
|
|
cmbd->cmblk_size = e - b;
|
|
/* Re-tag current and previous largest cmblock */
|
|
for (j = 0; j < chunks; j++) {
|
|
if (CTAG_GET(cptr[j]) == CT_CURRENT) {
|
|
/* Tag current cmblock as the largest */
|
|
CTAG_SET(cptr[j], CT_LARGEST);
|
|
} else if (CTAG_GET(cptr[j]) == CT_LARGEST) {
|
|
/* Discard previous largest cmblock */
|
|
CTAG_SET(cptr[j], CT_DISCARDED);
|
|
}
|
|
}
|
|
} else {
|
|
/* Discard all chunks in current cmblock */
|
|
for (j = 0; j < chunks; j++) {
|
|
if (CTAG_GET(cptr[j]) == CT_CURRENT) {
|
|
CTAG_SET(cptr[j], CT_DISCARDED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
* \brief Allocate memory chunks and add them to the pool.
|
|
*
|
|
* Memory chunks are allocated using the kernel page allocator.
|
|
*
|
|
* \param [in] cmbd - cmblock descriptor.
|
|
* \param [in] chunks - Number of memory chunks to allocate.
|
|
*
|
|
* \return 0 if no errors, otherwise -1.
|
|
*/
|
|
static int
|
|
alloc_mem_chunks(cmblock_desc_t *cmbd, int chunks)
|
|
{
|
|
int i, start;
|
|
unsigned long addr;
|
|
|
|
if (cmbd->chunk_cnt + chunks > cmbd->chunk_cnt_max) {
|
|
printk("PGMEM: No more memory chunks\n");
|
|
return -1;
|
|
}
|
|
start = cmbd->chunk_cnt;
|
|
cmbd->chunk_cnt += chunks;
|
|
for (i = start; i < cmbd->chunk_cnt; i++) {
|
|
/* Get chunk from kernel allocator */
|
|
addr = __get_free_pages(GFP_KERNEL | GFP_DMA32, cmbd->chunk_order);
|
|
PGMEM_TRACE(".");
|
|
if (addr) {
|
|
cmbd->chunk_ptr[i] = addr;
|
|
} else {
|
|
printk("PGMEM: Page memory allocation failed\n");
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
* \brief Allocate large physically contiguous memory block.
|
|
*
|
|
* If we cannot allocate a sufficiently large block of contiguous
|
|
* memory from the kernel, then we simply keep allocating smaller
|
|
* chunks until we can assemble a contiguous block of the desired
|
|
* size.
|
|
*
|
|
* When maximum amount of system memory has been allocated without the
|
|
* successful assembly of a contiguous memory block, the allocation
|
|
* function will return the largest contiguous block found so far. It
|
|
* is then up to the calling function to decide whether this amount is
|
|
* sufficient to proceed.
|
|
*
|
|
* \param [in] size Requested memory block size.
|
|
* \param [in] chunk_size Assemble cmblock from chunks of this size.
|
|
*
|
|
* \return Pointer to cmblock descriptor, or NULL if error.
|
|
*/
|
|
static cmblock_desc_t *
|
|
cmblock_alloc(size_t size, size_t chunk_size)
|
|
{
|
|
cmblock_desc_t *cmbd;
|
|
int i, chunk_ptr_size;
|
|
unsigned long page_addr;
|
|
struct sysinfo si;
|
|
|
|
/* Sanity check */
|
|
if (size == 0 || chunk_size == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate an initialize memory cmblock descriptor */
|
|
if ((cmbd = kmalloc(sizeof(cmblock_desc_t), GFP_KERNEL)) == NULL) {
|
|
return NULL;
|
|
}
|
|
memset(cmbd, 0, sizeof(*cmbd));
|
|
cmbd->req_size = size;
|
|
cmbd->chunk_size = PAGE_ALIGN(chunk_size);
|
|
while ((PAGE_SIZE << cmbd->chunk_order) < cmbd->chunk_size) {
|
|
cmbd->chunk_order++;
|
|
}
|
|
|
|
/* Determine the maximum possible number of memory chunks */
|
|
si_meminfo(&si);
|
|
cmbd->chunk_cnt_max = (si.totalram << PAGE_SHIFT) / cmbd->chunk_size;
|
|
chunk_ptr_size = cmbd->chunk_cnt_max * sizeof(unsigned long);
|
|
|
|
/* Allocate an initialize memory chunk pool */
|
|
cmbd->chunk_ptr = kmalloc(chunk_ptr_size, GFP_KERNEL);
|
|
if (cmbd->chunk_ptr == NULL) {
|
|
kfree(cmbd);
|
|
return NULL;
|
|
}
|
|
memset(cmbd->chunk_ptr, 0, chunk_ptr_size);
|
|
|
|
/* Allocate minimum number of memory chunks */
|
|
(void)alloc_mem_chunks(cmbd, cmbd->req_size / cmbd->chunk_size);
|
|
|
|
/* Allocate more chunks until we have a complete cmblock */
|
|
do {
|
|
find_largest_cmblock(cmbd);
|
|
PGMEM_TRACE("o");
|
|
if (cmbd->cmblk_size >= cmbd->req_size) {
|
|
break;
|
|
}
|
|
} while (alloc_mem_chunks(cmbd, 8) == 0);
|
|
|
|
/* Reserve all pages in the cmblock and free unused chunks */
|
|
for (i = 0; i < cmbd->chunk_cnt; i++) {
|
|
if (CTAG_GET(cmbd->chunk_ptr[i]) == CT_LARGEST) {
|
|
CTAG_SET(cmbd->chunk_ptr[i], CT_UNTAGGED);
|
|
for (page_addr = cmbd->chunk_ptr[i];
|
|
page_addr < cmbd->chunk_ptr[i] + cmbd->chunk_size;
|
|
page_addr += PAGE_SIZE) {
|
|
SetPageReserved(virt_to_page((void *)page_addr));
|
|
}
|
|
} else if (cmbd->chunk_ptr[i]) {
|
|
CTAG_SET(cmbd->chunk_ptr[i], CT_UNTAGGED);
|
|
free_pages(cmbd->chunk_ptr[i], cmbd->chunk_order);
|
|
PGMEM_TRACE("x");
|
|
cmbd->chunk_ptr[i] = 0;
|
|
}
|
|
}
|
|
PGMEM_TRACE("O\n");
|
|
return cmbd;
|
|
}
|
|
|
|
/*!
|
|
* \brief Free cmblock and associated resources.
|
|
*
|
|
* Free all memory chunks and other associated resources associated
|
|
* with a contiguous memory block.
|
|
*
|
|
* See alse \ref cmblock_alloc.
|
|
*
|
|
* \param [in] cmbd Command block descriptor to free.
|
|
*
|
|
* \return Nothing.
|
|
*/
|
|
static void
|
|
cmblock_free(cmblock_desc_t *cmbd)
|
|
{
|
|
int i;
|
|
unsigned long page_addr;
|
|
|
|
if (cmbd->chunk_ptr) {
|
|
for (i = 0; i < cmbd->chunk_cnt; i++) {
|
|
if (cmbd->chunk_ptr[i]) {
|
|
for (page_addr = cmbd->chunk_ptr[i];
|
|
page_addr < cmbd->chunk_ptr[i] + cmbd->chunk_size;
|
|
page_addr += PAGE_SIZE) {
|
|
ClearPageReserved(virt_to_page((void *)page_addr));
|
|
}
|
|
free_pages(cmbd->chunk_ptr[i], cmbd->chunk_order);
|
|
PGMEM_TRACE("X");
|
|
}
|
|
}
|
|
kfree(cmbd->chunk_ptr);
|
|
kfree(cmbd);
|
|
}
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Public Functions
|
|
******************************************************************************/
|
|
|
|
void *
|
|
ngbde_pgmem_alloc(size_t size, gfp_t flags)
|
|
{
|
|
cmblock_desc_t *cmbd;
|
|
size_t chunk_size;
|
|
|
|
chunk_size = size;
|
|
|
|
if (pgmem_chunk_size > 0) {
|
|
chunk_size = pgmem_chunk_size * ONE_KB;
|
|
}
|
|
|
|
if (chunk_size > MEM_CHUNK_SIZE_MAX) {
|
|
chunk_size = MEM_CHUNK_SIZE_DEFAULT;
|
|
}
|
|
|
|
if (pgmem_debug) {
|
|
printk("PGMEM: Allocate %d MB in %d KB chunks\n",
|
|
(int)(size / ONE_MB), (int)(chunk_size / ONE_KB));
|
|
}
|
|
|
|
if ((cmbd = cmblock_alloc(size, chunk_size)) == NULL) {
|
|
return NULL;
|
|
}
|
|
if (cmbd->cmblk_size < size) {
|
|
/* If we didn't get the full size then forget it */
|
|
cmblock_free(cmbd);
|
|
return NULL;
|
|
}
|
|
list_add(&cmbd->list, &cmblocks_list);
|
|
return (void *)cmbd->cmblk_begin;
|
|
}
|
|
|
|
int
|
|
ngbde_pgmem_free(void *ptr)
|
|
{
|
|
struct list_head *pos;
|
|
|
|
list_for_each(pos, &cmblocks_list) {
|
|
cmblock_desc_t *cmbd = list_entry(pos, cmblock_desc_t, list);
|
|
if (ptr == (void *)cmbd->cmblk_begin) {
|
|
list_del(&cmbd->list);
|
|
cmblock_free(cmbd);
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
ngbde_pgmem_free_all(void)
|
|
{
|
|
struct list_head *pos, *tmp;
|
|
|
|
list_for_each_safe(pos, tmp, &cmblocks_list) {
|
|
cmblock_desc_t *cmbd = list_entry(pos, cmblock_desc_t, list);
|
|
list_del(&cmbd->list);
|
|
cmblock_free(cmbd);
|
|
}
|
|
}
|