first
This commit is contained in:
19
rt-thread/components/mm/Kconfig
Normal file
19
rt-thread/components/mm/Kconfig
Normal file
@@ -0,0 +1,19 @@
|
||||
menu "Memory management"
|
||||
|
||||
config RT_USING_MEMBLOCK
|
||||
bool "Using memblock"
|
||||
default n
|
||||
help
|
||||
Using memblock to record memory infomation in init time
|
||||
|
||||
config RT_INIT_MEMORY_REGIONS
|
||||
int "The max number of memory block regions in init time"
|
||||
depends on RT_USING_MEMBLOCK
|
||||
range 1 1024
|
||||
default 128
|
||||
help
|
||||
During the system initialization phase, the kernel divides
|
||||
memory into different types of regions. This variable specifies
|
||||
the maximum number of regions supported by the system.
|
||||
|
||||
endmenu
|
16
rt-thread/components/mm/SConscript
Normal file
16
rt-thread/components/mm/SConscript
Normal file
@@ -0,0 +1,16 @@
|
||||
import os
|
||||
from building import *
|
||||
|
||||
group = []
|
||||
|
||||
if GetDepend('ARCH_ARM_CORTEX_A') or GetDepend('ARCH_ARMV8') or GetDepend('ARCH_RISCV64'):
|
||||
cwd = GetCurrentDir()
|
||||
src = Glob('*.c') + Glob('*_gcc.S')
|
||||
if not GetDepend('RT_USING_MEMBLOCK'):
|
||||
SrcRemove(src, ['mm_memblock.c'])
|
||||
|
||||
CPPPATH = [cwd]
|
||||
|
||||
group = DefineGroup('mm', src, depend = ['ARCH_MM_MMU'], CPPPATH = CPPPATH)
|
||||
|
||||
Return('group')
|
180
rt-thread/components/mm/avl_adpt.c
Normal file
180
rt-thread/components/mm/avl_adpt.c
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2022, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2022-11-14 WangXiaoyao the first version
|
||||
*/
|
||||
#include <rtdef.h>
|
||||
|
||||
#include <avl.h>
|
||||
#include "avl_adpt.h"
|
||||
#include "mm_aspace.h"
|
||||
#include "mm_private.h"
|
||||
|
||||
#define DBG_TAG "MM"
|
||||
#define DBG_LVL DBG_INFO
|
||||
#include <rtdbg.h>
|
||||
|
||||
/**
|
||||
* @brief Adapter Layer for lwp AVL BST
|
||||
*/
|
||||
|
||||
rt_err_t _aspace_bst_init(struct rt_aspace *aspace)
|
||||
{
|
||||
aspace->tree.tree.root_node = AVL_ROOT;
|
||||
return RT_EOK;
|
||||
}
|
||||
|
||||
static int compare_overlap(void *as, void *ae, void *bs, void *be)
|
||||
{
|
||||
LOG_D("as %lx, ae %lx, bs %lx, be %lx", as, ae, bs, be);
|
||||
int cmp;
|
||||
if (as > be)
|
||||
{
|
||||
cmp = 1;
|
||||
}
|
||||
else if (ae < bs)
|
||||
{
|
||||
cmp = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
cmp = 0;
|
||||
}
|
||||
LOG_D("ret %d", cmp);
|
||||
return cmp;
|
||||
}
|
||||
|
||||
static int compare_exceed(void *as, void *ae, void *bs, void *be)
|
||||
{
|
||||
LOG_D("as %lx, ae %lx, bs %lx, be %lx", as, ae, bs, be);
|
||||
int cmp;
|
||||
if (as > bs)
|
||||
{
|
||||
cmp = 1;
|
||||
}
|
||||
else if (as < bs)
|
||||
{
|
||||
cmp = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
cmp = 0;
|
||||
}
|
||||
LOG_D("ret %d", cmp);
|
||||
return cmp;
|
||||
}
|
||||
|
||||
static struct rt_varea *search(struct util_avl_root *root,
|
||||
struct _mm_range range,
|
||||
int (*compare)(void *as, void *ae, void *bs,
|
||||
void *be))
|
||||
{
|
||||
struct util_avl_struct *node = root->root_node;
|
||||
while (node)
|
||||
{
|
||||
rt_varea_t varea = VAREA_ENTRY(node);
|
||||
int cmp = compare(range.start, range.end, varea->start,
|
||||
(char *)varea->start + varea->size - 1);
|
||||
|
||||
if (cmp < 0)
|
||||
{
|
||||
node = node->avl_left;
|
||||
}
|
||||
else if (cmp > 0)
|
||||
{
|
||||
node = node->avl_right;
|
||||
}
|
||||
else
|
||||
{
|
||||
return varea;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct rt_varea *_aspace_bst_search(struct rt_aspace *aspace, void *key)
|
||||
{
|
||||
struct util_avl_root *root = &aspace->tree.tree;
|
||||
struct _mm_range range = {key, key};
|
||||
return search(root, range, compare_overlap);
|
||||
}
|
||||
|
||||
rt_varea_t _aspace_bst_search_exceed(struct rt_aspace *aspace, void *start)
|
||||
{
|
||||
struct util_avl_root *root = &aspace->tree.tree;
|
||||
struct util_avl_struct *node = root->root_node;
|
||||
rt_varea_t closest = NULL;
|
||||
ptrdiff_t min_off = PTRDIFF_MAX;
|
||||
while (node)
|
||||
{
|
||||
rt_varea_t varea = VAREA_ENTRY(node);
|
||||
void *va_s = varea->start;
|
||||
int cmp = compare_exceed(start, start, va_s, va_s);
|
||||
|
||||
if (cmp < 0)
|
||||
{
|
||||
/* varae exceed start */
|
||||
ptrdiff_t off = (char *)va_s - (char *)start;
|
||||
if (off < min_off)
|
||||
{
|
||||
min_off = off;
|
||||
closest = varea;
|
||||
}
|
||||
node = node->avl_left;
|
||||
}
|
||||
else if (cmp > 0)
|
||||
{
|
||||
/* find the next huger varea */
|
||||
node = node->avl_right;
|
||||
}
|
||||
else
|
||||
{
|
||||
return varea;
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
struct rt_varea *_aspace_bst_search_overlap(struct rt_aspace *aspace,
|
||||
struct _mm_range range)
|
||||
{
|
||||
struct util_avl_root *root = &aspace->tree.tree;
|
||||
return search(root, range, compare_overlap);
|
||||
}
|
||||
|
||||
void _aspace_bst_insert(struct rt_aspace *aspace, struct rt_varea *varea)
|
||||
{
|
||||
struct util_avl_root *root = &aspace->tree.tree;
|
||||
struct util_avl_struct *current = NULL;
|
||||
struct util_avl_struct **next = &(root->root_node);
|
||||
rt_ubase_t key = (rt_ubase_t)varea->start;
|
||||
|
||||
/* Figure out where to put new node */
|
||||
while (*next)
|
||||
{
|
||||
current = *next;
|
||||
struct rt_varea *data = VAREA_ENTRY(current);
|
||||
|
||||
if (key < (rt_ubase_t)data->start)
|
||||
next = &(current->avl_left);
|
||||
else if (key > (rt_ubase_t)data->start)
|
||||
next = &(current->avl_right);
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
/* Add new node and rebalance tree. */
|
||||
util_avl_link(&varea->node.node, current, next);
|
||||
util_avl_rebalance(current, root);
|
||||
return;
|
||||
}
|
||||
|
||||
void _aspace_bst_remove(struct rt_aspace *aspace, struct rt_varea *varea)
|
||||
{
|
||||
struct util_avl_struct *node = &varea->node.node;
|
||||
util_avl_remove(node, &aspace->tree.tree);
|
||||
}
|
40
rt-thread/components/mm/avl_adpt.h
Normal file
40
rt-thread/components/mm/avl_adpt.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2022, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2022-11-14 WangXiaoyao the first version
|
||||
*/
|
||||
|
||||
#ifndef __MM_AVL_ADPT_H__
|
||||
#define __MM_AVL_ADPT_H__
|
||||
|
||||
#include <avl.h>
|
||||
#include <rtdef.h>
|
||||
#include <rtthread.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define VAREA_ENTRY(pnode) \
|
||||
(pnode) \
|
||||
? rt_container_of(rt_container_of(pnode, struct _aspace_node, node), \
|
||||
struct rt_varea, node) \
|
||||
: 0
|
||||
|
||||
#define ASPACE_VAREA_NEXT(pva) (VAREA_ENTRY(util_avl_next(&pva->node.node)))
|
||||
#define ASPACE_VAREA_FIRST(aspace) (VAREA_ENTRY(util_avl_first(&aspace->tree.tree)))
|
||||
#define ASPACE_VAREA_LAST(aspace) (VAREA_ENTRY(util_avl_last(&aspace->tree.tree)))
|
||||
#define ASPACE_VAREA_PREV(pva) (VAREA_ENTRY(util_avl_prev(&pva->node.node)))
|
||||
|
||||
typedef struct _aspace_node
|
||||
{
|
||||
struct util_avl_struct node;
|
||||
} *_aspace_node_t;
|
||||
|
||||
typedef struct _aspace_tree
|
||||
{
|
||||
struct util_avl_root tree;
|
||||
} *_aspace_tree_t;
|
||||
|
||||
#endif /* __MM_AVL_ADPT_H__ */
|
114
rt-thread/components/mm/ioremap.c
Normal file
114
rt-thread/components/mm/ioremap.c
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2018, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2021-05-06 Jesven first version
|
||||
*/
|
||||
#include <rthw.h>
|
||||
#include <rtthread.h>
|
||||
|
||||
#include <mmu.h>
|
||||
#include <mm_aspace.h>
|
||||
#include <ioremap.h>
|
||||
|
||||
void *rt_ioremap_start;
|
||||
size_t rt_ioremap_size;
|
||||
|
||||
#ifdef RT_USING_SMART
|
||||
#include <lwp_mm.h>
|
||||
#endif
|
||||
|
||||
#define DBG_TAG "mm.ioremap"
|
||||
#define DBG_LVL DBG_LOG
|
||||
#include <rtdbg.h>
|
||||
|
||||
enum ioremap_type
|
||||
{
|
||||
MM_AREA_TYPE_PHY,
|
||||
MM_AREA_TYPE_PHY_WT,
|
||||
MM_AREA_TYPE_PHY_CACHED
|
||||
};
|
||||
|
||||
static void *_ioremap_type(void *paddr, size_t size, enum ioremap_type type)
|
||||
{
|
||||
char *v_addr = NULL;
|
||||
size_t attr;
|
||||
size_t lo_off;
|
||||
int err;
|
||||
|
||||
lo_off = (rt_ubase_t)paddr & ARCH_PAGE_MASK;
|
||||
|
||||
struct rt_mm_va_hint hint = {
|
||||
.prefer = RT_NULL,
|
||||
.map_size = RT_ALIGN(size + lo_off, ARCH_PAGE_SIZE),
|
||||
.flags = 0,
|
||||
.limit_start = rt_ioremap_start,
|
||||
.limit_range_size = rt_ioremap_size,
|
||||
};
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MM_AREA_TYPE_PHY:
|
||||
attr = MMU_MAP_K_DEVICE;
|
||||
break;
|
||||
case MM_AREA_TYPE_PHY_WT:
|
||||
attr = MMU_MAP_K_RW;
|
||||
break;
|
||||
case MM_AREA_TYPE_PHY_CACHED:
|
||||
attr = MMU_MAP_K_RWCB;
|
||||
break;
|
||||
default:
|
||||
return v_addr;
|
||||
}
|
||||
err = rt_aspace_map_phy(&rt_kernel_space, &hint, attr, MM_PA_TO_OFF(paddr), (void **)&v_addr);
|
||||
|
||||
if (err)
|
||||
{
|
||||
LOG_W("IOREMAP 0x%lx failed %d\n", paddr, err);
|
||||
v_addr = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
v_addr = v_addr + lo_off;
|
||||
}
|
||||
return v_addr;
|
||||
}
|
||||
|
||||
rt_weak void *rt_ioremap_early(void *paddr, size_t size)
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
return RT_NULL;
|
||||
}
|
||||
|
||||
return paddr;
|
||||
}
|
||||
|
||||
void *rt_ioremap(void *paddr, size_t size)
|
||||
{
|
||||
return _ioremap_type(paddr, size, MM_AREA_TYPE_PHY);
|
||||
}
|
||||
|
||||
void *rt_ioremap_nocache(void *paddr, size_t size)
|
||||
{
|
||||
return _ioremap_type(paddr, size, MM_AREA_TYPE_PHY);
|
||||
}
|
||||
|
||||
void *rt_ioremap_wt(void *paddr, size_t size)
|
||||
{
|
||||
return _ioremap_type(paddr, size, MM_AREA_TYPE_PHY_WT);
|
||||
}
|
||||
|
||||
void *rt_ioremap_cached(void *paddr, size_t size)
|
||||
{
|
||||
return _ioremap_type(paddr, size, MM_AREA_TYPE_PHY_CACHED);
|
||||
}
|
||||
|
||||
void rt_iounmap(volatile void *vaddr)
|
||||
{
|
||||
rt_aspace_unmap(&rt_kernel_space, (void *)vaddr);
|
||||
}
|
||||
|
47
rt-thread/components/mm/ioremap.h
Normal file
47
rt-thread/components/mm/ioremap.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2020, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2021-05-06 Jesven first version
|
||||
*/
|
||||
#ifndef __IOREMAP_H__
|
||||
#define __IOREMAP_H__
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* IOREMAP family
|
||||
* `rt_ioremap` default to map physical memory in MMIO region as DEVICE memory
|
||||
* to kernel space. And there are 3 variants currently supported.
|
||||
*
|
||||
* name | attribution
|
||||
* ------------------ | -----------
|
||||
* rt_ioremap_nocache | Device (MMU_MAP_K_DEVICE)
|
||||
* rt_ioremap_cache | Normal memory (MMU_MAP_K_RWCB)
|
||||
* rt_ioremap_wt | Normal memory but guarantee that
|
||||
* | Each write access should go to system memory directly
|
||||
* | Currently as non-cacheable
|
||||
*/
|
||||
|
||||
void *rt_ioremap_early(void *paddr, size_t size);
|
||||
void *rt_ioremap(void *paddr, size_t size);
|
||||
void *rt_ioremap_nocache(void *paddr, size_t size);
|
||||
void *rt_ioremap_cached(void *paddr, size_t size);
|
||||
void *rt_ioremap_wt(void *paddr, size_t size);
|
||||
void rt_iounmap(volatile void *addr);
|
||||
|
||||
extern void *rt_ioremap_start;
|
||||
extern size_t rt_ioremap_size;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /*__LWP_IOREMAP_H__*/
|
738
rt-thread/components/mm/mm_anon.c
Normal file
738
rt-thread/components/mm/mm_anon.c
Normal file
@@ -0,0 +1,738 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2023, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2023-08-19 Shell Support PRIVATE mapping and COW
|
||||
*/
|
||||
|
||||
#define DBG_TAG "mm.anon"
|
||||
#define DBG_LVL DBG_INFO
|
||||
#include <rtdbg.h>
|
||||
|
||||
#include <string.h>
|
||||
#include "mm_private.h"
|
||||
#include <mmu.h>
|
||||
|
||||
/**
|
||||
* Anonymous Object directly represent the mappings without backup files in the
|
||||
* aspace. Their only backup is in the aspace->pgtbl.
|
||||
*/
|
||||
|
||||
typedef struct rt_private_ctx {
|
||||
struct rt_mem_obj mem_obj;
|
||||
rt_aspace_t backup_aspace;
|
||||
/* both varea and aspace can holds a reference */
|
||||
rt_atomic_t reference;
|
||||
/* readonly `private` is shared object */
|
||||
long readonly;
|
||||
} *rt_private_ctx_t;
|
||||
|
||||
rt_inline rt_aspace_t _anon_obj_get_backup(rt_mem_obj_t mobj)
|
||||
{
|
||||
rt_private_ctx_t pctx;
|
||||
rt_aspace_t backup;
|
||||
pctx = rt_container_of(mobj, struct rt_private_ctx, mem_obj);
|
||||
backup = pctx->backup_aspace;
|
||||
return backup;
|
||||
}
|
||||
|
||||
rt_inline rt_atomic_t *_anon_obj_get_reference(rt_mem_obj_t mobj)
|
||||
{
|
||||
rt_private_ctx_t pctx;
|
||||
pctx = rt_container_of(mobj, struct rt_private_ctx, mem_obj);
|
||||
return &pctx->reference;
|
||||
}
|
||||
|
||||
rt_inline rt_private_ctx_t _anon_mobj_to_pctx(rt_mem_obj_t mobj)
|
||||
{
|
||||
return rt_container_of(mobj, struct rt_private_ctx, mem_obj);
|
||||
}
|
||||
|
||||
static long rt_aspace_anon_ref_inc(rt_mem_obj_t aobj)
|
||||
{
|
||||
long rc;
|
||||
if (aobj)
|
||||
{
|
||||
rc = rt_atomic_add(_anon_obj_get_reference(aobj), 1);
|
||||
LOG_D("%s(aobj=%p) Cur %ld", __func__, aobj, rc + 1);
|
||||
}
|
||||
else
|
||||
rc = -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
rt_err_t rt_aspace_anon_ref_dec(rt_mem_obj_t aobj)
|
||||
{
|
||||
rt_err_t rc;
|
||||
rt_aspace_t aspace;
|
||||
rt_private_ctx_t pctx;
|
||||
long former_reference;
|
||||
|
||||
if (aobj)
|
||||
{
|
||||
pctx = _anon_mobj_to_pctx(aobj);
|
||||
RT_ASSERT(pctx);
|
||||
|
||||
former_reference = rt_atomic_add(_anon_obj_get_reference(aobj), -1);
|
||||
LOG_D("%s(aobj=%p) Cur %ld", __func__, aobj, former_reference - 1);
|
||||
if (pctx->readonly)
|
||||
{
|
||||
if (former_reference - 1 <= pctx->readonly)
|
||||
{
|
||||
void *pgtbl;
|
||||
RT_ASSERT(former_reference - 1 == pctx->readonly);
|
||||
aspace = _anon_obj_get_backup(aobj);
|
||||
|
||||
pctx->readonly = 0;
|
||||
pgtbl = aspace->page_table;
|
||||
rt_aspace_delete(aspace);
|
||||
rt_hw_mmu_pgtbl_delete(pgtbl);
|
||||
}
|
||||
}
|
||||
else if (former_reference < 2)
|
||||
{
|
||||
aspace = _anon_obj_get_backup(aobj);
|
||||
aspace->private_object = RT_NULL;
|
||||
|
||||
rt_free(pctx);
|
||||
}
|
||||
rc = RT_EOK;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = -RT_EINVAL;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void rt_varea_pgmgr_insert(rt_varea_t varea, void *page_addr)
|
||||
{
|
||||
/* each mapping of page frame in the varea is binding with a reference */
|
||||
rt_page_ref_inc(page_addr, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private unmapping of address space
|
||||
*/
|
||||
static void _pgmgr_pop_all(rt_varea_t varea)
|
||||
{
|
||||
rt_aspace_t aspace = varea->aspace;
|
||||
char *iter = varea->start;
|
||||
char *end_addr = iter + varea->size;
|
||||
|
||||
RT_ASSERT(iter < end_addr);
|
||||
RT_ASSERT(!((long)iter & ARCH_PAGE_MASK));
|
||||
RT_ASSERT(!((long)end_addr & ARCH_PAGE_MASK));
|
||||
|
||||
for (; iter != end_addr; iter += ARCH_PAGE_SIZE)
|
||||
{
|
||||
void *page_pa = rt_hw_mmu_v2p(aspace, iter);
|
||||
char *page_va = rt_kmem_p2v(page_pa);
|
||||
if (page_pa != ARCH_MAP_FAILED && page_va)
|
||||
{
|
||||
rt_hw_mmu_unmap(aspace, iter, ARCH_PAGE_SIZE);
|
||||
rt_pages_free(page_va, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _pgmgr_pop_range(rt_varea_t varea, void *rm_start, void *rm_end)
|
||||
{
|
||||
void *page_va;
|
||||
|
||||
RT_ASSERT(!((rt_ubase_t)rm_start & ARCH_PAGE_MASK));
|
||||
RT_ASSERT(!((rt_ubase_t)rm_end & ARCH_PAGE_MASK));
|
||||
while (rm_start != rm_end)
|
||||
{
|
||||
page_va = rt_hw_mmu_v2p(varea->aspace, rm_start);
|
||||
|
||||
if (page_va != ARCH_MAP_FAILED)
|
||||
{
|
||||
page_va -= PV_OFFSET;
|
||||
LOG_D("%s: free page %p", __func__, page_va);
|
||||
rt_varea_unmap_page(varea, rm_start);
|
||||
rt_pages_free(page_va, 0);
|
||||
}
|
||||
rm_start += ARCH_PAGE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *_anon_get_name(rt_varea_t varea)
|
||||
{
|
||||
return varea->aspace == _anon_obj_get_backup(varea->mem_obj) ? "anonymous" : "reference";
|
||||
}
|
||||
|
||||
/**
|
||||
* Migration handler on varea re-construction
|
||||
*/
|
||||
|
||||
static void _anon_varea_open(struct rt_varea *varea)
|
||||
{
|
||||
rt_aspace_anon_ref_inc(varea->mem_obj);
|
||||
|
||||
if (varea->aspace == _anon_obj_get_backup(varea->mem_obj))
|
||||
varea->offset = MM_PA_TO_OFF(varea->start);
|
||||
|
||||
varea->data = NULL;
|
||||
}
|
||||
|
||||
static void _anon_varea_close(struct rt_varea *varea)
|
||||
{
|
||||
rt_aspace_anon_ref_dec(varea->mem_obj);
|
||||
|
||||
/* unmap and dereference page frames in the varea region */
|
||||
_pgmgr_pop_all(varea);
|
||||
}
|
||||
|
||||
static rt_err_t _anon_varea_expand(struct rt_varea *varea, void *new_vaddr, rt_size_t size)
|
||||
{
|
||||
return RT_EOK;
|
||||
}
|
||||
|
||||
static rt_err_t _anon_varea_shrink(rt_varea_t varea, void *new_start, rt_size_t size)
|
||||
{
|
||||
char *varea_start = varea->start;
|
||||
void *rm_start;
|
||||
void *rm_end;
|
||||
|
||||
if (varea_start == (char *)new_start)
|
||||
{
|
||||
rm_start = varea_start + size;
|
||||
rm_end = varea_start + varea->size;
|
||||
}
|
||||
else /* if (varea_start < (char *)new_start) */
|
||||
{
|
||||
RT_ASSERT(varea_start < (char *)new_start);
|
||||
rm_start = varea_start;
|
||||
rm_end = new_start;
|
||||
}
|
||||
|
||||
_pgmgr_pop_range(varea, rm_start, rm_end);
|
||||
return RT_EOK;
|
||||
}
|
||||
|
||||
static rt_err_t _anon_varea_split(struct rt_varea *existed, void *unmap_start, rt_size_t unmap_len, struct rt_varea *subset)
|
||||
{
|
||||
/* remove the resource in the unmap region, and do nothing for the subset */
|
||||
_pgmgr_pop_range(existed, unmap_start, (char *)unmap_start + unmap_len);
|
||||
|
||||
_anon_varea_open(subset);
|
||||
return RT_EOK;
|
||||
}
|
||||
|
||||
static rt_err_t _anon_varea_merge(struct rt_varea *merge_to, struct rt_varea *merge_from)
|
||||
{
|
||||
/* do nothing for the varea merge */
|
||||
return RT_EOK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private mapping of address space
|
||||
*/
|
||||
|
||||
rt_inline void _map_page_in_varea(rt_aspace_t asapce, rt_varea_t varea,
|
||||
struct rt_aspace_fault_msg *msg, char *fault_addr)
|
||||
{
|
||||
char *page_va = msg->response.vaddr;
|
||||
if (rt_varea_map_page(varea, fault_addr, page_va) == RT_EOK)
|
||||
{
|
||||
msg->response.status = MM_FAULT_STATUS_OK_MAPPED;
|
||||
rt_varea_pgmgr_insert(varea, page_va);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg->response.status = MM_FAULT_STATUS_UNRECOVERABLE;
|
||||
LOG_W("%s: failed to map page into varea", __func__);
|
||||
}
|
||||
}
|
||||
|
||||
/* page frame inquiry or allocation in backup address space */
|
||||
static void *_get_page_from_backup(rt_aspace_t backup, rt_base_t offset_in_mobj)
|
||||
{
|
||||
void *frame_pa;
|
||||
char *backup_addr;
|
||||
rt_varea_t backup_varea;
|
||||
void *rc = RT_NULL;
|
||||
|
||||
backup_addr = (char *)(offset_in_mobj << MM_PAGE_SHIFT);
|
||||
backup_varea = rt_aspace_query(backup, backup_addr);
|
||||
|
||||
if (backup_varea)
|
||||
{
|
||||
/* synchronize between multiple request by aspace lock of backup */
|
||||
WR_LOCK(backup);
|
||||
|
||||
frame_pa = rt_hw_mmu_v2p(backup, backup_addr);
|
||||
if (frame_pa == ARCH_MAP_FAILED)
|
||||
{
|
||||
/* provide the page in backup varea */
|
||||
struct rt_aspace_fault_msg msg;
|
||||
msg.fault_op = MM_FAULT_OP_WRITE;
|
||||
msg.fault_type = MM_FAULT_TYPE_PAGE_FAULT;
|
||||
msg.fault_vaddr = backup_addr;
|
||||
msg.off = offset_in_mobj;
|
||||
rt_mm_fault_res_init(&msg.response);
|
||||
|
||||
rt_mm_dummy_mapper.on_page_fault(backup_varea, &msg);
|
||||
if (msg.response.status != MM_FAULT_STATUS_UNRECOVERABLE)
|
||||
{
|
||||
_map_page_in_varea(backup, backup_varea, &msg, backup_addr);
|
||||
if (msg.response.status == MM_FAULT_STATUS_OK_MAPPED)
|
||||
{
|
||||
rc = msg.response.vaddr;
|
||||
}
|
||||
rt_pages_free(msg.response.vaddr, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = rt_kmem_p2v(frame_pa);
|
||||
if (!rc)
|
||||
RT_ASSERT(0 && "No kernel address of target page frame");
|
||||
}
|
||||
WR_UNLOCK(backup);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* out of range error */
|
||||
LOG_E("(backup_addr=%p): Page request out of range", backup_addr);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* get the backup page in kernel for the address in user space */
|
||||
static void _fetch_page_for_varea(struct rt_varea *varea, struct rt_aspace_fault_msg *msg, rt_bool_t need_map)
|
||||
{
|
||||
void *paddr;
|
||||
char *frame_ka;
|
||||
rt_aspace_t curr_aspace = varea->aspace;
|
||||
rt_aspace_t backup = _anon_obj_get_backup(varea->mem_obj);
|
||||
|
||||
RDWR_LOCK(curr_aspace);
|
||||
|
||||
/**
|
||||
* if the page is already mapped(this may caused by data race while other
|
||||
* thread success to take the lock and mapped the page before this), return okay
|
||||
*/
|
||||
paddr = rt_hw_mmu_v2p(curr_aspace, msg->fault_vaddr);
|
||||
if (paddr == ARCH_MAP_FAILED)
|
||||
{
|
||||
if (backup == curr_aspace)
|
||||
{
|
||||
rt_mm_dummy_mapper.on_page_fault(varea, msg);
|
||||
if (msg->response.status != MM_FAULT_STATUS_UNRECOVERABLE)
|
||||
{
|
||||
/* if backup == curr_aspace, a page fetch always binding with a pte filling */
|
||||
_map_page_in_varea(backup, varea, msg, msg->fault_vaddr);
|
||||
if (msg->response.status != MM_FAULT_STATUS_UNRECOVERABLE)
|
||||
{
|
||||
rt_pages_free(msg->response.vaddr, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
frame_ka = _get_page_from_backup(backup, msg->off);
|
||||
if (frame_ka)
|
||||
{
|
||||
msg->response.vaddr = frame_ka;
|
||||
msg->response.size = ARCH_PAGE_SIZE;
|
||||
if (!need_map)
|
||||
{
|
||||
msg->response.status = MM_FAULT_STATUS_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
_map_page_in_varea(curr_aspace, varea, msg, msg->fault_vaddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
msg->response.status = MM_FAULT_STATUS_OK_MAPPED;
|
||||
}
|
||||
RDWR_UNLOCK(curr_aspace);
|
||||
}
|
||||
|
||||
static void _anon_page_fault(struct rt_varea *varea, struct rt_aspace_fault_msg *msg)
|
||||
{
|
||||
_fetch_page_for_varea(varea, msg, RT_TRUE);
|
||||
}
|
||||
|
||||
static void read_by_mte(rt_aspace_t aspace, struct rt_aspace_io_msg *iomsg)
|
||||
{
|
||||
if (rt_aspace_page_get_phy(aspace, iomsg->fault_vaddr, iomsg->buffer_vaddr) == RT_EOK)
|
||||
iomsg->response.status = MM_FAULT_STATUS_OK;
|
||||
}
|
||||
|
||||
static void _anon_page_read(struct rt_varea *varea, struct rt_aspace_io_msg *iomsg)
|
||||
{
|
||||
rt_aspace_t curr_aspace = varea->aspace;
|
||||
rt_aspace_t backup = _anon_obj_get_backup(varea->mem_obj);
|
||||
|
||||
if (rt_hw_mmu_v2p(curr_aspace, iomsg->fault_vaddr) == ARCH_MAP_FAILED)
|
||||
{
|
||||
struct rt_aspace_fault_msg msg;
|
||||
msg.fault_op = MM_FAULT_OP_READ;
|
||||
msg.fault_type = MM_FAULT_TYPE_PAGE_FAULT;
|
||||
msg.fault_vaddr = iomsg->fault_vaddr;
|
||||
msg.off = iomsg->off;
|
||||
rt_mm_fault_res_init(&msg.response);
|
||||
|
||||
_fetch_page_for_varea(varea, &msg, RT_FALSE);
|
||||
if (msg.response.status != MM_FAULT_STATUS_UNRECOVERABLE)
|
||||
{
|
||||
void *saved_fault_va = iomsg->fault_vaddr;
|
||||
iomsg->fault_vaddr = (void *)(iomsg->off << MM_PAGE_SHIFT);
|
||||
read_by_mte(backup, iomsg);
|
||||
iomsg->fault_vaddr = saved_fault_va;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
read_by_mte(curr_aspace, iomsg);
|
||||
}
|
||||
}
|
||||
|
||||
static void write_by_mte(rt_aspace_t aspace, struct rt_aspace_io_msg *iomsg)
|
||||
{
|
||||
if (rt_aspace_page_put_phy(aspace, iomsg->fault_vaddr, iomsg->buffer_vaddr) == RT_EOK)
|
||||
iomsg->response.status = MM_FAULT_STATUS_OK;
|
||||
}
|
||||
|
||||
static void _anon_page_write(struct rt_varea *varea, struct rt_aspace_io_msg *iomsg)
|
||||
{
|
||||
rt_aspace_t from_aspace = varea->aspace;
|
||||
rt_aspace_t backup = _anon_obj_get_backup(varea->mem_obj);
|
||||
|
||||
if (from_aspace != backup)
|
||||
{
|
||||
/* varea in guest aspace cannot modify the page */
|
||||
iomsg->response.status = MM_FAULT_STATUS_UNRECOVERABLE;
|
||||
}
|
||||
else if (rt_hw_mmu_v2p(from_aspace, iomsg->fault_vaddr) == ARCH_MAP_FAILED)
|
||||
{
|
||||
struct rt_aspace_fault_msg msg;
|
||||
msg.fault_op = MM_FAULT_OP_WRITE;
|
||||
msg.fault_type = MM_FAULT_TYPE_PAGE_FAULT;
|
||||
msg.fault_vaddr = iomsg->fault_vaddr;
|
||||
msg.off = iomsg->off;
|
||||
rt_mm_fault_res_init(&msg.response);
|
||||
|
||||
_fetch_page_for_varea(varea, &msg, RT_TRUE);
|
||||
if (msg.response.status == MM_FAULT_STATUS_OK_MAPPED)
|
||||
{
|
||||
write_by_mte(backup, iomsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* mapping failed, report an error */
|
||||
iomsg->response.status = MM_FAULT_STATUS_UNRECOVERABLE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
write_by_mte(backup, iomsg);
|
||||
}
|
||||
}
|
||||
|
||||
static struct rt_private_ctx _priv_obj = {
|
||||
.mem_obj.get_name = _anon_get_name,
|
||||
.mem_obj.on_page_fault = _anon_page_fault,
|
||||
.mem_obj.hint_free = NULL,
|
||||
.mem_obj.on_varea_open = _anon_varea_open,
|
||||
.mem_obj.on_varea_close = _anon_varea_close,
|
||||
.mem_obj.on_varea_shrink = _anon_varea_shrink,
|
||||
.mem_obj.on_varea_split = _anon_varea_split,
|
||||
.mem_obj.on_varea_expand = _anon_varea_expand,
|
||||
.mem_obj.on_varea_merge = _anon_varea_merge,
|
||||
.mem_obj.page_read = _anon_page_read,
|
||||
.mem_obj.page_write = _anon_page_write,
|
||||
};
|
||||
|
||||
rt_inline rt_private_ctx_t rt_private_obj_create_n_bind(rt_aspace_t aspace)
|
||||
{
|
||||
rt_private_ctx_t private_object;
|
||||
private_object = rt_malloc(sizeof(struct rt_private_ctx));
|
||||
if (private_object)
|
||||
{
|
||||
memcpy(&private_object->mem_obj, &_priv_obj, sizeof(_priv_obj));
|
||||
|
||||
/* hold a init ref from backup aspace */
|
||||
rt_atomic_store(&private_object->reference, 1);
|
||||
|
||||
private_object->readonly = RT_FALSE;
|
||||
private_object->backup_aspace = aspace;
|
||||
aspace->private_object = &private_object->mem_obj;
|
||||
}
|
||||
|
||||
return private_object;
|
||||
}
|
||||
|
||||
rt_inline rt_mem_obj_t _get_private_obj(rt_aspace_t aspace)
|
||||
{
|
||||
rt_private_ctx_t priv;
|
||||
rt_mem_obj_t rc;
|
||||
rc = aspace->private_object;
|
||||
if (!aspace->private_object)
|
||||
{
|
||||
priv = rt_private_obj_create_n_bind(aspace);
|
||||
if (priv)
|
||||
{
|
||||
rc = &priv->mem_obj;
|
||||
aspace->private_object = rc;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int _override_map(rt_varea_t varea, rt_aspace_t aspace, void *fault_vaddr, struct rt_aspace_fault_msg *msg, void *page)
|
||||
{
|
||||
int rc = MM_FAULT_FIXABLE_FALSE;
|
||||
rt_mem_obj_t private_object;
|
||||
rt_varea_t map_varea = RT_NULL;
|
||||
rt_err_t error;
|
||||
rt_size_t flags;
|
||||
rt_size_t attr;
|
||||
|
||||
LOG_D("%s", __func__);
|
||||
|
||||
private_object = _get_private_obj(aspace);
|
||||
|
||||
if (private_object)
|
||||
{
|
||||
flags = varea->flag | MMF_MAP_FIXED;
|
||||
/* don't prefetch and do it latter */
|
||||
flags &= ~MMF_PREFETCH;
|
||||
attr = rt_hw_mmu_attr_add_perm(varea->attr, RT_HW_MMU_PROT_USER | RT_HW_MMU_PROT_WRITE);
|
||||
|
||||
/* override existing mapping at fault_vaddr */
|
||||
error = _mm_aspace_map(
|
||||
aspace, &map_varea, &fault_vaddr, ARCH_PAGE_SIZE, attr,
|
||||
flags, private_object, MM_PA_TO_OFF(fault_vaddr));
|
||||
|
||||
if (error == RT_EOK)
|
||||
{
|
||||
msg->response.status = MM_FAULT_STATUS_OK;
|
||||
msg->response.vaddr = page;
|
||||
msg->response.size = ARCH_PAGE_SIZE;
|
||||
if (rt_varea_map_with_msg(map_varea, msg) != RT_EOK)
|
||||
{
|
||||
LOG_E("%s: fault_va=%p,(priv_va=%p,priv_sz=0x%lx) at %s", __func__, msg->fault_vaddr, map_varea->start, map_varea->size, VAREA_NAME(map_varea));
|
||||
RT_ASSERT(0 && "should never failed");
|
||||
}
|
||||
RT_ASSERT(rt_hw_mmu_v2p(aspace, msg->fault_vaddr) == (page + PV_OFFSET));
|
||||
rc = MM_FAULT_FIXABLE_TRUE;
|
||||
rt_varea_pgmgr_insert(map_varea, page);
|
||||
rt_pages_free(page, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* private object will be release on destruction of aspace */
|
||||
rt_free(map_varea);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_I("%s: out of memory", __func__);
|
||||
rc = MM_FAULT_FIXABLE_FALSE;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* replace an existing mapping to a private one, this is identical to:
|
||||
* => aspace_unmap(ex_varea, )
|
||||
* => aspace_map()
|
||||
*/
|
||||
int rt_varea_fix_private_locked(rt_varea_t ex_varea, void *pa,
|
||||
struct rt_aspace_fault_msg *msg,
|
||||
rt_bool_t dont_copy)
|
||||
{
|
||||
/**
|
||||
* todo: READ -> WRITE lock here
|
||||
*/
|
||||
void *page;
|
||||
void *fault_vaddr;
|
||||
rt_aspace_t aspace;
|
||||
rt_mem_obj_t ex_obj;
|
||||
int rc = MM_FAULT_FIXABLE_FALSE;
|
||||
ex_obj = ex_varea->mem_obj;
|
||||
|
||||
if (ex_obj)
|
||||
{
|
||||
fault_vaddr = msg->fault_vaddr;
|
||||
aspace = ex_varea->aspace;
|
||||
RT_ASSERT(!!aspace);
|
||||
|
||||
/**
|
||||
* todo: what if multiple pages are required?
|
||||
*/
|
||||
if (aspace->private_object == ex_obj)
|
||||
{
|
||||
RT_ASSERT(0 && "recursion");
|
||||
}
|
||||
else if (ex_obj->page_read)
|
||||
{
|
||||
page = rt_pages_alloc_ext(0, PAGE_ANY_AVAILABLE);
|
||||
if (page)
|
||||
{
|
||||
/** setup message & fetch the data from source object */
|
||||
if (!dont_copy)
|
||||
{
|
||||
struct rt_aspace_io_msg io_msg;
|
||||
rt_mm_io_msg_init(&io_msg, msg->off, msg->fault_vaddr, page);
|
||||
ex_obj->page_read(ex_varea, &io_msg);
|
||||
/**
|
||||
* Note: if ex_obj have mapped into varea, it's still okay since
|
||||
* we will override it latter
|
||||
*/
|
||||
if (io_msg.response.status != MM_FAULT_STATUS_UNRECOVERABLE)
|
||||
{
|
||||
rc = _override_map(ex_varea, aspace, fault_vaddr, msg, page);
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_pages_free(page, 0);
|
||||
LOG_I("%s: page read(va=%p) fault from %s(start=%p,size=%p)", __func__,
|
||||
msg->fault_vaddr, VAREA_NAME(ex_varea), ex_varea->start, ex_varea->size);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = _override_map(ex_varea, aspace, fault_vaddr, msg, page);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_I("%s: pages allocation failed", __func__);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_I("%s: no page read method provided from %s", __func__, VAREA_NAME(ex_varea));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_I("%s: unavailable memory object", __func__);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int rt_aspace_map_private(rt_aspace_t aspace, void **addr, rt_size_t length,
|
||||
rt_size_t attr, mm_flag_t flags)
|
||||
{
|
||||
int rc;
|
||||
rt_mem_obj_t priv_obj;
|
||||
|
||||
if (flags & MMF_STATIC_ALLOC)
|
||||
{
|
||||
rc = -RT_EINVAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
priv_obj = _get_private_obj(aspace);
|
||||
if (priv_obj)
|
||||
{
|
||||
flags |= MMF_MAP_PRIVATE;
|
||||
flags &= ~MMF_PREFETCH;
|
||||
rc = rt_aspace_map(aspace, addr, length, attr, flags, priv_obj, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = -RT_ENOMEM;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int _release_shared(rt_varea_t varea, void *arg)
|
||||
{
|
||||
rt_aspace_t src = varea->aspace;
|
||||
rt_mem_obj_t mem_obj = varea->mem_obj;
|
||||
|
||||
if (mem_obj != _get_private_obj(src))
|
||||
{
|
||||
_varea_uninstall_locked(varea);
|
||||
if (VAREA_NOT_STATIC(varea))
|
||||
{
|
||||
rt_free(varea);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static rt_err_t _convert_readonly(rt_aspace_t aspace, long base_reference)
|
||||
{
|
||||
rt_mem_obj_t aobj;
|
||||
rt_private_ctx_t pctx;
|
||||
aobj = _get_private_obj(aspace);
|
||||
pctx = _anon_mobj_to_pctx(aobj);
|
||||
|
||||
LOG_D("Ref(cur=%d,base=%d)", pctx->reference, base_reference);
|
||||
rt_aspace_traversal(aspace, _release_shared, 0);
|
||||
pctx->readonly = base_reference;
|
||||
return 0;
|
||||
}
|
||||
|
||||
rt_inline void _switch_aspace(rt_aspace_t *pa, rt_aspace_t *pb)
|
||||
{
|
||||
rt_aspace_t temp;
|
||||
temp = *pa;
|
||||
*pa = *pb;
|
||||
*pb = temp;
|
||||
}
|
||||
|
||||
rt_err_t rt_aspace_fork(rt_aspace_t *psrc, rt_aspace_t *pdst)
|
||||
{
|
||||
rt_err_t rc;
|
||||
void *pgtbl;
|
||||
rt_aspace_t backup;
|
||||
rt_aspace_t src = *psrc;
|
||||
rt_aspace_t dst = *pdst;
|
||||
long base_reference;
|
||||
|
||||
pgtbl = rt_hw_mmu_pgtbl_create();
|
||||
if (pgtbl)
|
||||
{
|
||||
backup = rt_aspace_create(src->start, src->size, pgtbl);
|
||||
if (backup)
|
||||
{
|
||||
WR_LOCK(src);
|
||||
base_reference = rt_atomic_load(_anon_obj_get_reference(src->private_object));
|
||||
rc = rt_aspace_duplicate_locked(src, dst);
|
||||
WR_UNLOCK(src);
|
||||
|
||||
if (!rc)
|
||||
{
|
||||
/* WR_LOCK(dst) is not necessary since dst is not available currently */
|
||||
rc = rt_aspace_duplicate_locked(dst, backup);
|
||||
if (!rc)
|
||||
{
|
||||
_switch_aspace(psrc, &backup);
|
||||
_convert_readonly(backup, base_reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = -RT_ENOMEM;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = -RT_ENOMEM;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
1821
rt-thread/components/mm/mm_aspace.c
Normal file
1821
rt-thread/components/mm/mm_aspace.c
Normal file
File diff suppressed because it is too large
Load Diff
332
rt-thread/components/mm/mm_aspace.h
Normal file
332
rt-thread/components/mm/mm_aspace.h
Normal file
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2023, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2022-11-14 WangXiaoyao the first version
|
||||
* 2023-08-17 Shell Add unmap_range for MAP_PRIVATE
|
||||
*/
|
||||
#ifndef __MM_ASPACE_H__
|
||||
#define __MM_ASPACE_H__
|
||||
|
||||
#include <rthw.h>
|
||||
#include <rtthread.h>
|
||||
|
||||
#include "avl_adpt.h"
|
||||
#include "mm_fault.h"
|
||||
#include "mm_flag.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MM_PAGE_SHIFT 12
|
||||
#define MM_PA_TO_OFF(pa) ((uintptr_t)(pa) >> MM_PAGE_SHIFT)
|
||||
#define PV_OFFSET (rt_kmem_pvoff())
|
||||
|
||||
typedef struct rt_spinlock mm_spinlock_t;
|
||||
|
||||
#define MM_PGTBL_LOCK_INIT(aspace) (rt_spin_lock_init(&((aspace)->pgtbl_lock)))
|
||||
#define MM_PGTBL_LOCK(aspace) (rt_spin_lock(&((aspace)->pgtbl_lock)))
|
||||
#define MM_PGTBL_UNLOCK(aspace) (rt_spin_unlock(&((aspace)->pgtbl_lock)))
|
||||
|
||||
struct rt_aspace;
|
||||
struct rt_varea;
|
||||
struct rt_mem_obj;
|
||||
|
||||
extern struct rt_aspace rt_kernel_space;
|
||||
|
||||
typedef struct rt_aspace
|
||||
{
|
||||
void *start;
|
||||
rt_size_t size;
|
||||
|
||||
void *page_table;
|
||||
mm_spinlock_t pgtbl_lock;
|
||||
|
||||
struct _aspace_tree tree;
|
||||
struct rt_mutex bst_lock;
|
||||
|
||||
struct rt_mem_obj *private_object;
|
||||
|
||||
#ifdef ARCH_USING_ASID
|
||||
rt_uint64_t asid;
|
||||
#endif /* ARCH_USING_ASID */
|
||||
|
||||
} *rt_aspace_t;
|
||||
|
||||
typedef struct rt_varea
|
||||
{
|
||||
void *start;
|
||||
rt_size_t size;
|
||||
rt_size_t offset;
|
||||
|
||||
rt_size_t attr;
|
||||
rt_size_t flag;
|
||||
|
||||
struct rt_aspace *aspace;
|
||||
struct rt_mem_obj *mem_obj;
|
||||
|
||||
struct _aspace_node node;
|
||||
|
||||
void *data;
|
||||
} *rt_varea_t;
|
||||
|
||||
typedef struct rt_mm_va_hint
|
||||
{
|
||||
void *limit_start;
|
||||
rt_size_t limit_range_size;
|
||||
|
||||
void *prefer;
|
||||
const rt_size_t map_size;
|
||||
|
||||
mm_flag_t flags;
|
||||
} *rt_mm_va_hint_t;
|
||||
|
||||
typedef struct rt_mem_obj
|
||||
{
|
||||
void (*hint_free)(rt_mm_va_hint_t hint);
|
||||
void (*on_page_fault)(struct rt_varea *varea, struct rt_aspace_fault_msg *msg);
|
||||
|
||||
/* do pre open bushiness like inc a ref */
|
||||
void (*on_varea_open)(struct rt_varea *varea);
|
||||
/* do post close bushiness like def a ref */
|
||||
void (*on_varea_close)(struct rt_varea *varea);
|
||||
|
||||
/* do preparation for address space modification of varea */
|
||||
rt_err_t (*on_varea_shrink)(struct rt_varea *varea, void *new_vaddr, rt_size_t size);
|
||||
/* do preparation for address space modification of varea */
|
||||
rt_err_t (*on_varea_expand)(struct rt_varea *varea, void *new_vaddr, rt_size_t size);
|
||||
/**
|
||||
* this is like an on_varea_open() to `subset`, and an on_varea_shrink() to `existed`
|
||||
* while resource can migrate from `existed` to `subset` at the same time
|
||||
*/
|
||||
rt_err_t (*on_varea_split)(struct rt_varea *existed, void *unmap_start,
|
||||
rt_size_t unmap_len, struct rt_varea *subset);
|
||||
/**
|
||||
* this is like a on_varea_expand() to `merge_to` and on_varea_close() to `merge_from`
|
||||
* while resource can migrate from `merge_from` to `merge_to` at the same time
|
||||
*/
|
||||
rt_err_t (*on_varea_merge)(struct rt_varea *merge_to, struct rt_varea *merge_from);
|
||||
|
||||
/* dynamic mem_obj API */
|
||||
void (*page_read)(struct rt_varea *varea, struct rt_aspace_io_msg *msg);
|
||||
void (*page_write)(struct rt_varea *varea, struct rt_aspace_io_msg *msg);
|
||||
|
||||
const char *(*get_name)(rt_varea_t varea);
|
||||
|
||||
void *(*on_varea_mremap)(struct rt_varea *varea, rt_size_t new_size, int flags, void *new_address);
|
||||
} *rt_mem_obj_t;
|
||||
|
||||
extern struct rt_mem_obj rt_mm_dummy_mapper;
|
||||
|
||||
enum rt_mmu_cntl
|
||||
{
|
||||
MMU_CNTL_NONCACHE,
|
||||
MMU_CNTL_CACHE,
|
||||
MMU_CNTL_READONLY,
|
||||
MMU_CNTL_READWRITE,
|
||||
MMU_CNTL_OFFLOAD,
|
||||
MMU_CNTL_INSTALL,
|
||||
MMU_CNTL_DUMMY_END,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Lock to access page table of address space
|
||||
*/
|
||||
#define WR_LOCK(aspace) \
|
||||
rt_thread_self() ? rt_mutex_take(&(aspace)->bst_lock, RT_WAITING_FOREVER) \
|
||||
: 0
|
||||
#define WR_UNLOCK(aspace) \
|
||||
rt_thread_self() ? rt_mutex_release(&(aspace)->bst_lock) : 0
|
||||
|
||||
/* FIXME: fix rd_lock */
|
||||
#define RD_LOCK(aspace) WR_LOCK(aspace)
|
||||
#define RD_UNLOCK(aspace) WR_UNLOCK(aspace)
|
||||
#define RDWR_LOCK(aspace) ((void)aspace)
|
||||
#define RDWR_UNLOCK(aspace) ((void)aspace)
|
||||
|
||||
rt_aspace_t rt_aspace_create(void *start, rt_size_t length, void *pgtbl);
|
||||
|
||||
rt_err_t rt_aspace_init(rt_aspace_t aspace, void *start, rt_size_t length, void *pgtbl);
|
||||
|
||||
void rt_aspace_delete(rt_aspace_t aspace);
|
||||
|
||||
void rt_aspace_detach(rt_aspace_t aspace);
|
||||
|
||||
/**
|
||||
* @brief Memory Map on Virtual Address Space to Mappable Object
|
||||
* *INFO There is no restriction to use NULL address(physical/virtual).
|
||||
* Vaddr passing in addr must be page aligned. If vaddr is RT_NULL,
|
||||
* a suitable address will be chose automatically.
|
||||
*
|
||||
* @param aspace target virtual address space
|
||||
* @param addr virtual address of the mapping
|
||||
* @param length length of mapping region
|
||||
* @param attr MMU attribution
|
||||
* @param flags desired memory protection and behaviour of the mapping
|
||||
* @param mem_obj memory map backing store object
|
||||
* @param offset offset of mapping in 4KB page for mem_obj
|
||||
* @return int E_OK on success, with addr set to vaddr of mapping
|
||||
* E_INVAL
|
||||
*/
|
||||
int rt_aspace_map(rt_aspace_t aspace, void **addr, rt_size_t length, rt_size_t attr,
|
||||
mm_flag_t flags, rt_mem_obj_t mem_obj, rt_size_t offset);
|
||||
|
||||
/** no malloc routines call */
|
||||
int rt_aspace_map_static(rt_aspace_t aspace, rt_varea_t varea, void **addr,
|
||||
rt_size_t length, rt_size_t attr, mm_flag_t flags,
|
||||
rt_mem_obj_t mem_obj, rt_size_t offset);
|
||||
|
||||
/**
|
||||
* @brief Memory Map on Virtual Address Space to Physical Memory
|
||||
*
|
||||
* @param aspace target virtual address space
|
||||
* @param hint hint of mapping va
|
||||
* @param attr MMU attribution
|
||||
* @param pa_off (physical address >> 12)
|
||||
* @param ret_va pointer to the location to store va
|
||||
* @return int E_OK on success, with ret_va set to vaddr of mapping
|
||||
* E_INVAL
|
||||
*/
|
||||
int rt_aspace_map_phy(rt_aspace_t aspace, rt_mm_va_hint_t hint, rt_size_t attr,
|
||||
rt_size_t pa_off, void **ret_va);
|
||||
|
||||
/** no malloc routines call */
|
||||
int rt_aspace_map_phy_static(rt_aspace_t aspace, rt_varea_t varea,
|
||||
rt_mm_va_hint_t hint, rt_size_t attr, rt_size_t pa_off,
|
||||
void **ret_va);
|
||||
|
||||
/** map a private memory region to aspace */
|
||||
int rt_aspace_map_private(rt_aspace_t aspace, void **addr, rt_size_t length,
|
||||
rt_size_t attr, mm_flag_t flags);
|
||||
|
||||
/**
|
||||
* @brief Remove mappings containing address specified by addr
|
||||
*
|
||||
* @param aspace target virtual address space
|
||||
* @param addr addresses that mapping to be removed contains
|
||||
* @return int rt errno
|
||||
*/
|
||||
int rt_aspace_unmap(rt_aspace_t aspace, void *addr);
|
||||
|
||||
/**
|
||||
* @brief Remove pages of existed mappings in the range [addr, addr+length)
|
||||
* Length is automatically rounded up to the next multiple of the page size.
|
||||
*
|
||||
* @param aspace target virtual address space
|
||||
* @param addr the beginning of the range of pages to be unmapped
|
||||
* @param length length of range in bytes
|
||||
* @return int rt errno
|
||||
*/
|
||||
int rt_aspace_unmap_range(rt_aspace_t aspace, void *addr, size_t length);
|
||||
|
||||
void *rt_aspace_mremap_range(rt_aspace_t aspace, void *old_address, size_t old_size,
|
||||
size_t new_size, int flags, void *new_address);
|
||||
|
||||
int rt_aspace_control(rt_aspace_t aspace, void *addr, enum rt_mmu_cntl cmd);
|
||||
|
||||
int rt_aspace_load_page(rt_aspace_t aspace, void *addr, rt_size_t npage);
|
||||
|
||||
int rt_aspace_offload_page(rt_aspace_t aspace, void *addr, rt_size_t npage);
|
||||
|
||||
rt_err_t rt_aspace_page_put(rt_aspace_t aspace, void *page_va, void *buffer);
|
||||
|
||||
rt_err_t rt_aspace_page_get(rt_aspace_t aspace, void *page_va, void *buffer);
|
||||
|
||||
int rt_aspace_traversal(rt_aspace_t aspace,
|
||||
int (*fn)(rt_varea_t varea, void *arg), void *arg);
|
||||
|
||||
void rt_aspace_print_all(rt_aspace_t aspace);
|
||||
|
||||
rt_base_t rt_aspace_count_vsz(rt_aspace_t aspace);
|
||||
|
||||
rt_varea_t rt_aspace_query(rt_aspace_t aspace, void *vaddr);
|
||||
|
||||
rt_err_t rt_aspace_duplicate_locked(rt_aspace_t src, rt_aspace_t dst);
|
||||
rt_err_t rt_aspace_fork(rt_aspace_t *psrc, rt_aspace_t *pdst);
|
||||
rt_err_t rt_aspace_compare(rt_aspace_t src, rt_aspace_t dst);
|
||||
|
||||
/**
|
||||
* @brief Map one page to varea
|
||||
*
|
||||
* @note caller should take the read/write lock
|
||||
*
|
||||
* @param varea target varea
|
||||
* @param addr user address
|
||||
* @param page the page frame to be mapped
|
||||
* @return int
|
||||
*/
|
||||
int rt_varea_map_page(rt_varea_t varea, void *vaddr, void *page);
|
||||
|
||||
/**
|
||||
* @brief Unmap one page in varea
|
||||
*
|
||||
* @note caller should take the read/write lock
|
||||
*
|
||||
* @param varea target varea
|
||||
* @param addr user address
|
||||
* @param page the page frame to be mapped
|
||||
* @return int
|
||||
*/
|
||||
int rt_varea_unmap_page(rt_varea_t varea, void *vaddr);
|
||||
|
||||
/**
|
||||
* @brief Map a range of physical address to varea
|
||||
*
|
||||
* @warning Caller should take care of synchronization of its varea among all
|
||||
* the map/unmap operation
|
||||
*
|
||||
* @param varea target varea
|
||||
* @param vaddr user address
|
||||
* @param paddr physical address
|
||||
* @param length map range
|
||||
* @return int
|
||||
*/
|
||||
int rt_varea_map_range(rt_varea_t varea, void *vaddr, void *paddr, rt_size_t length);
|
||||
|
||||
/**
|
||||
* @brief Unmap a range of physical address in varea
|
||||
*
|
||||
* @warning Caller should take care of synchronization of its varea among all
|
||||
* the map/unmap operation
|
||||
*
|
||||
* @param varea target varea
|
||||
* @param vaddr user address
|
||||
* @param length map range
|
||||
* @return int
|
||||
*/
|
||||
int rt_varea_unmap_range(rt_varea_t varea, void *vaddr, rt_size_t length);
|
||||
|
||||
/**
|
||||
* @brief Insert page to page manager of varea
|
||||
* The page will be freed by varea on uninstall automatically
|
||||
*
|
||||
* @param varea target varea
|
||||
* @param page_addr the page frame to be added
|
||||
*/
|
||||
void rt_varea_pgmgr_insert(rt_varea_t varea, void *page_addr);
|
||||
|
||||
rt_inline rt_mem_obj_t rt_mem_obj_create(rt_mem_obj_t source)
|
||||
{
|
||||
rt_mem_obj_t target;
|
||||
target = rt_malloc(sizeof(*target));
|
||||
if (target)
|
||||
memcpy(target, source, sizeof(*target));
|
||||
return target;
|
||||
}
|
||||
|
||||
const rt_ubase_t rt_kmem_pvoff(void);
|
||||
|
||||
void rt_kmem_pvoff_set(rt_ubase_t pvoff);
|
||||
|
||||
int rt_kmem_map_phy(void *va, void *pa, rt_size_t length, rt_size_t attr);
|
||||
|
||||
void *rt_kmem_v2p(void *vaddr);
|
||||
|
||||
void *rt_kmem_p2v(void *paddr);
|
||||
|
||||
void rt_kmem_list(void);
|
||||
|
||||
#endif /* __MM_ASPACE_H__ */
|
201
rt-thread/components/mm/mm_fault.c
Normal file
201
rt-thread/components/mm/mm_fault.c
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2022, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2022-12-06 WangXiaoyao the first version
|
||||
* 2023-08-19 Shell Support PRIVATE mapping and COW
|
||||
*/
|
||||
#include <rtthread.h>
|
||||
|
||||
#ifdef RT_USING_SMART
|
||||
#define DBG_TAG "mm.fault"
|
||||
#define DBG_LVL DBG_INFO
|
||||
#include <rtdbg.h>
|
||||
|
||||
#include <lwp.h>
|
||||
#include <lwp_syscall.h>
|
||||
#include "mm_aspace.h"
|
||||
#include "mm_fault.h"
|
||||
#include "mm_flag.h"
|
||||
#include "mm_private.h"
|
||||
#include <mmu.h>
|
||||
#include <tlb.h>
|
||||
|
||||
static int _fetch_page(rt_varea_t varea, struct rt_aspace_fault_msg *msg)
|
||||
{
|
||||
int err = MM_FAULT_FIXABLE_FALSE;
|
||||
if (varea->mem_obj && varea->mem_obj->on_page_fault)
|
||||
{
|
||||
varea->mem_obj->on_page_fault(varea, msg);
|
||||
err = rt_varea_map_with_msg(varea, msg);
|
||||
err = (err == RT_EOK ? MM_FAULT_FIXABLE_TRUE : MM_FAULT_FIXABLE_FALSE);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int _read_fault(rt_varea_t varea, void *pa, struct rt_aspace_fault_msg *msg)
|
||||
{
|
||||
int err = MM_FAULT_FIXABLE_FALSE;
|
||||
if (msg->fault_type == MM_FAULT_TYPE_PAGE_FAULT)
|
||||
{
|
||||
RT_ASSERT(pa == ARCH_MAP_FAILED);
|
||||
RT_ASSERT(!(varea->flag & MMF_PREFETCH));
|
||||
err = _fetch_page(varea, msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* signal a fault to user? */
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int _write_fault(rt_varea_t varea, void *pa, struct rt_aspace_fault_msg *msg)
|
||||
{
|
||||
rt_aspace_t aspace = varea->aspace;
|
||||
int err = MM_FAULT_FIXABLE_FALSE;
|
||||
|
||||
if (rt_varea_is_private_locked(varea))
|
||||
{
|
||||
if (VAREA_IS_WRITABLE(varea) && (
|
||||
msg->fault_type == MM_FAULT_TYPE_RWX_PERM ||
|
||||
msg->fault_type == MM_FAULT_TYPE_PAGE_FAULT))
|
||||
{
|
||||
RDWR_LOCK(aspace);
|
||||
err = rt_varea_fix_private_locked(varea, pa, msg, RT_FALSE);
|
||||
RDWR_UNLOCK(aspace);
|
||||
if (err == MM_FAULT_FIXABLE_FALSE)
|
||||
LOG_I("%s: fix private failure", __func__);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_I("%s: No permission on %s(attr=0x%lx,writable=%s,fault_type=%d)",
|
||||
__func__, VAREA_NAME(varea), varea->attr,
|
||||
VAREA_IS_WRITABLE(varea) ? "True" : "False", msg->fault_type);
|
||||
}
|
||||
}
|
||||
else if (msg->fault_type == MM_FAULT_TYPE_PAGE_FAULT)
|
||||
{
|
||||
RT_ASSERT(pa == ARCH_MAP_FAILED);
|
||||
RT_ASSERT(!(varea->flag & MMF_PREFETCH));
|
||||
err = _fetch_page(varea, msg);
|
||||
if (err == MM_FAULT_FIXABLE_FALSE)
|
||||
LOG_I("%s: page fault failure", __func__);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_D("%s: can not fix", __func__);
|
||||
/* signal a fault to user? */
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int _exec_fault(rt_varea_t varea, void *pa, struct rt_aspace_fault_msg *msg)
|
||||
{
|
||||
int err = MM_FAULT_FIXABLE_FALSE;
|
||||
if (msg->fault_type == MM_FAULT_TYPE_PAGE_FAULT)
|
||||
{
|
||||
RT_ASSERT(pa == ARCH_MAP_FAILED);
|
||||
RT_ASSERT(!(varea->flag & MMF_PREFETCH));
|
||||
err = _fetch_page(varea, msg);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static void _determine_precise_fault_type(struct rt_aspace_fault_msg *msg, rt_ubase_t pa, rt_varea_t varea)
|
||||
{
|
||||
if (msg->fault_type == MM_FAULT_TYPE_GENERIC_MMU)
|
||||
{
|
||||
rt_base_t requesting_perm;
|
||||
switch (msg->fault_op)
|
||||
{
|
||||
case MM_FAULT_OP_READ:
|
||||
requesting_perm = RT_HW_MMU_PROT_READ | RT_HW_MMU_PROT_USER;
|
||||
break;
|
||||
case MM_FAULT_OP_WRITE:
|
||||
requesting_perm = RT_HW_MMU_PROT_WRITE | RT_HW_MMU_PROT_USER;
|
||||
break;
|
||||
case MM_FAULT_OP_EXECUTE:
|
||||
requesting_perm = RT_HW_MMU_PROT_EXECUTE | RT_HW_MMU_PROT_USER;
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* always checking the user privileges since dynamic permission is not
|
||||
* supported in kernel. So those faults are never fixable. Hence, adding
|
||||
* permission check never changes the result of checking. In other
|
||||
* words, { 0 && (expr) } is always false.
|
||||
*/
|
||||
if (rt_hw_mmu_attr_test_perm(varea->attr, requesting_perm))
|
||||
{
|
||||
if (pa == (rt_ubase_t)ARCH_MAP_FAILED)
|
||||
{
|
||||
msg->fault_type = MM_FAULT_TYPE_PAGE_FAULT;
|
||||
}
|
||||
else
|
||||
{
|
||||
msg->fault_type = MM_FAULT_TYPE_RWX_PERM;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int rt_aspace_fault_try_fix(rt_aspace_t aspace, struct rt_aspace_fault_msg *msg)
|
||||
{
|
||||
int err = MM_FAULT_FIXABLE_FALSE;
|
||||
uintptr_t va = (uintptr_t)msg->fault_vaddr;
|
||||
va &= ~ARCH_PAGE_MASK;
|
||||
msg->fault_vaddr = (void *)va;
|
||||
rt_mm_fault_res_init(&msg->response);
|
||||
|
||||
RT_DEBUG_SCHEDULER_AVAILABLE(1);
|
||||
|
||||
if (aspace)
|
||||
{
|
||||
rt_varea_t varea;
|
||||
|
||||
RD_LOCK(aspace);
|
||||
varea = _aspace_bst_search(aspace, msg->fault_vaddr);
|
||||
if (varea)
|
||||
{
|
||||
void *pa = rt_hw_mmu_v2p(aspace, msg->fault_vaddr);
|
||||
_determine_precise_fault_type(msg, (rt_ubase_t)pa, varea);
|
||||
|
||||
if (pa != ARCH_MAP_FAILED && msg->fault_type == MM_FAULT_TYPE_PAGE_FAULT)
|
||||
{
|
||||
LOG_D("%s(fault=%p) has already fixed", __func__, msg->fault_vaddr);
|
||||
err = MM_FAULT_FIXABLE_TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_D("%s(varea=%s,fault=%p,fault_op=%d,phy=%p)", __func__, VAREA_NAME(varea), msg->fault_vaddr, msg->fault_op, pa);
|
||||
msg->off = varea->offset + ((long)msg->fault_vaddr - (long)varea->start) / ARCH_PAGE_SIZE;
|
||||
|
||||
/* permission checked by fault op */
|
||||
switch (msg->fault_op)
|
||||
{
|
||||
case MM_FAULT_OP_READ:
|
||||
err = _read_fault(varea, pa, msg);
|
||||
break;
|
||||
case MM_FAULT_OP_WRITE:
|
||||
err = _write_fault(varea, pa, msg);
|
||||
break;
|
||||
case MM_FAULT_OP_EXECUTE:
|
||||
err = _exec_fault(varea, pa, msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_I("%s: varea not found at 0x%lx", __func__, msg->fault_vaddr);
|
||||
}
|
||||
RD_UNLOCK(aspace);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#endif /* RT_USING_SMART */
|
123
rt-thread/components/mm/mm_fault.h
Normal file
123
rt-thread/components/mm/mm_fault.h
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2022, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2022-12-06 WangXiaoyao the first version
|
||||
*/
|
||||
#ifndef __MM_FAULT_H__
|
||||
#define __MM_FAULT_H__
|
||||
|
||||
#include <rtthread.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* fast path fault handler, a page frame on kernel space is returned */
|
||||
#define MM_FAULT_STATUS_OK 0
|
||||
/* customized fault handler, done by using rt_varea_map_* */
|
||||
#define MM_FAULT_STATUS_OK_MAPPED 1
|
||||
#define MM_FAULT_STATUS_UNRECOVERABLE 4
|
||||
|
||||
#define MM_FAULT_FIXABLE_FALSE 0
|
||||
#define MM_FAULT_FIXABLE_TRUE 1
|
||||
|
||||
enum rt_mm_fault_op
|
||||
{
|
||||
MM_FAULT_OP_READ = 1,
|
||||
MM_FAULT_OP_WRITE,
|
||||
MM_FAULT_OP_EXECUTE,
|
||||
};
|
||||
|
||||
enum rt_mm_fault_type
|
||||
{
|
||||
/**
|
||||
* Occurs when an instruction attempts to access a memory address that it
|
||||
* does not have R/W/X permission to access
|
||||
*/
|
||||
MM_FAULT_TYPE_RWX_PERM,
|
||||
|
||||
/* Without privileges to access (e.g. user accessing kernel) */
|
||||
MM_FAULT_TYPE_NO_PRIVILEGES,
|
||||
|
||||
/**
|
||||
* Occurs when a load or store instruction accesses a virtual memory
|
||||
* address that is not currently mapped to a physical memory page
|
||||
*/
|
||||
MM_FAULT_TYPE_PAGE_FAULT,
|
||||
|
||||
/**
|
||||
* Occurs like a SIGBUS
|
||||
*/
|
||||
MM_FAULT_TYPE_BUS_ERROR,
|
||||
|
||||
/**
|
||||
* Occurs when page table walk failed, permission failed, writings on
|
||||
* non-dirty page.
|
||||
*/
|
||||
MM_FAULT_TYPE_GENERIC_MMU,
|
||||
|
||||
MM_FAULT_TYPE_GENERIC,
|
||||
__PRIVATE_PAGE_INSERT,
|
||||
};
|
||||
|
||||
enum rt_mm_hint_prefetch
|
||||
{
|
||||
MM_FAULT_HINT_PREFETCH_NONE,
|
||||
MM_FAULT_HINT_PREFETCH_READY,
|
||||
};
|
||||
|
||||
struct rt_mm_fault_res
|
||||
{
|
||||
void *vaddr;
|
||||
rt_size_t size;
|
||||
int status;
|
||||
|
||||
/* hint for prefetch strategy */
|
||||
enum rt_mm_hint_prefetch hint;
|
||||
};
|
||||
|
||||
struct rt_aspace_fault_msg
|
||||
{
|
||||
enum rt_mm_fault_op fault_op;
|
||||
enum rt_mm_fault_type fault_type;
|
||||
rt_size_t off;
|
||||
void *fault_vaddr;
|
||||
|
||||
struct rt_mm_fault_res response;
|
||||
};
|
||||
|
||||
struct rt_aspace_io_msg
|
||||
{
|
||||
/* offset in varea */
|
||||
rt_size_t off;
|
||||
/* fault address in target address space */
|
||||
void *fault_vaddr;
|
||||
/* read/write buffer in kernel space */
|
||||
void *buffer_vaddr;
|
||||
|
||||
struct rt_mm_fault_res response;
|
||||
};
|
||||
|
||||
rt_inline void rt_mm_fault_res_init(struct rt_mm_fault_res *res)
|
||||
{
|
||||
res->vaddr = RT_NULL;
|
||||
res->size = 0;
|
||||
res->hint = MM_FAULT_HINT_PREFETCH_NONE;
|
||||
res->status = MM_FAULT_STATUS_UNRECOVERABLE;
|
||||
}
|
||||
|
||||
rt_inline void rt_mm_io_msg_init(struct rt_aspace_io_msg *io, rt_size_t off, void *fault_vaddr, void *buffer_vaddr)
|
||||
{
|
||||
io->off = off;
|
||||
io->fault_vaddr = fault_vaddr;
|
||||
io->buffer_vaddr = buffer_vaddr;
|
||||
rt_mm_fault_res_init(&io->response);
|
||||
}
|
||||
|
||||
struct rt_aspace;
|
||||
/* MMU base page fault handler, MM_FAULT_FIXABLE_TRUE/MM_FAULT_FIXABLE_FALSE will be returned */
|
||||
int rt_aspace_fault_try_fix(struct rt_aspace *aspace, struct rt_aspace_fault_msg *msg);
|
||||
|
||||
#endif /* __MM_FAULT_H__ */
|
116
rt-thread/components/mm/mm_flag.h
Normal file
116
rt-thread/components/mm/mm_flag.h
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2022, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2022-11-23 WangXiaoyao the first version
|
||||
*/
|
||||
#ifndef __MM_FLAG_H__
|
||||
#define __MM_FLAG_H__
|
||||
|
||||
#include <rtthread.h>
|
||||
|
||||
/**
|
||||
* @brief mm_flag_t
|
||||
* |max ------- 7|6 ----- 0|
|
||||
* | control | align |
|
||||
*
|
||||
* there should be no more than 25 flags
|
||||
*/
|
||||
typedef unsigned long mm_flag_t;
|
||||
|
||||
#define _MMF_CNTL_SHIFT 7
|
||||
#define _MMF_ALIGN_MASK 0x7f
|
||||
#define _MMF_CNTL_MASK (~((1 << _MMF_CNTL_SHIFT) - 1))
|
||||
#define _DEF_FLAG(index) (1 << (_MMF_CNTL_SHIFT + (index)))
|
||||
|
||||
enum mm_flag_cntl
|
||||
{
|
||||
/**
|
||||
* @brief Modifications to the mapped data shall be visible only to the
|
||||
* aspace only and shall not change the underlying object. It is
|
||||
* unspecified whether modifications to the underlying object done after
|
||||
* the MAP_PRIVATE mapping is established are visible through the
|
||||
* MAP_PRIVATE mapping.
|
||||
*/
|
||||
MMF_MAP_PRIVATE = _DEF_FLAG(0),
|
||||
|
||||
/**
|
||||
* @brief Same as MMF_MAP_PRIVATE, except the modification after mapping is
|
||||
* invisible to the varea
|
||||
*/
|
||||
MMF_MAP_PRIVATE_DONT_SYNC = _DEF_FLAG(1),
|
||||
|
||||
/**
|
||||
* @brief [POSIX MAP_FIXED] When MAP_FIXED is set in the flags argument, the
|
||||
* implementation is informed that the value of pa shall be addr, exactly.
|
||||
* If a MAP_FIXED request is successful, the mapping established
|
||||
* by mmap() replaces any previous mappings for the pages in the range
|
||||
* [pa,pa+len) of the process.
|
||||
*/
|
||||
MMF_MAP_FIXED = _DEF_FLAG(2),
|
||||
|
||||
/**
|
||||
* @brief The backup page frame is allocated and setted only until it is
|
||||
* truly necessary by the user
|
||||
*/
|
||||
MMF_PREFETCH = _DEF_FLAG(3),
|
||||
|
||||
/**
|
||||
* @brief Allocate the mapping using "huge" pages
|
||||
*/
|
||||
MMF_HUGEPAGE = _DEF_FLAG(4),
|
||||
|
||||
/** internal reserved flags */
|
||||
MMF_TEXT = _DEF_FLAG(5),
|
||||
|
||||
/** internal reserved flags */
|
||||
MMF_STATIC_ALLOC = _DEF_FLAG(6),
|
||||
|
||||
/**
|
||||
* @brief Shared mapping. Updates to the mapping are visible to other
|
||||
* processes mapping the same region, and are carried through to the
|
||||
* underlying file.
|
||||
*/
|
||||
MMF_MAP_SHARED = _DEF_FLAG(7),
|
||||
|
||||
/**
|
||||
* @brief a non-locked memory can be swapped out when required, this is
|
||||
* reserved for future
|
||||
*/
|
||||
MMF_NONLOCKED = _DEF_FLAG(8),
|
||||
|
||||
/**
|
||||
* @brief An alignment is specified in flags that the mapping must admit
|
||||
*/
|
||||
MMF_REQUEST_ALIGN = _DEF_FLAG(9),
|
||||
|
||||
__MMF_INVALID,
|
||||
};
|
||||
|
||||
#define MMF_GET_ALIGN(src) ((src & _MMF_ALIGN_MASK))
|
||||
#define MMF_SET_ALIGN(src, align) \
|
||||
((src & ~_MMF_ALIGN_MASK) | (__builtin_ffsl(align) - 1))
|
||||
|
||||
#define MMF_GET_CNTL(src) (src & _MMF_CNTL_MASK)
|
||||
#define MMF_TEST_CNTL(src, flag) (src & flag)
|
||||
#define MMF_SET_CNTL(src, flag) ((src) | (flag))
|
||||
#define MMF_CLEAR_CNTL(src, flag) ((src) & ~(flag))
|
||||
|
||||
/**
|
||||
* @brief Create Flags
|
||||
*
|
||||
* example: MMF_CREATE(0, 0)
|
||||
* MMF_CREATE(MMF_MAP_FIXED, 0x2000)
|
||||
*
|
||||
* Direct use of flag is also acceptable: (MMF_MAP_FIXED | MMF_PREFETCH)
|
||||
*/
|
||||
#define MMF_CREATE(cntl, align) \
|
||||
((align) ? (MMF_SET_CNTL((mm_flag_t)0, (cntl) | MMF_REQUEST_ALIGN) | \
|
||||
MMF_SET_ALIGN((mm_flag_t)0, (align))) \
|
||||
: (MMF_SET_CNTL((mm_flag_t)0, (cntl) & ~MMF_REQUEST_ALIGN)))
|
||||
|
||||
#undef _DEF_FLAG
|
||||
#endif /* __MM_FLAG_H__ */
|
106
rt-thread/components/mm/mm_kmem.c
Normal file
106
rt-thread/components/mm/mm_kmem.c
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2022, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2022-11-14 WangXiaoyao the first version
|
||||
*/
|
||||
|
||||
#include <rtthread.h>
|
||||
#define DBG_TAG "mm.kmem"
|
||||
#define DBG_LVL DBG_INFO
|
||||
#include <rtdbg.h>
|
||||
|
||||
#include "mm_aspace.h"
|
||||
#include "mm_private.h"
|
||||
#include <mmu.h>
|
||||
|
||||
static void list_kmem(void)
|
||||
{
|
||||
rt_aspace_print_all(&rt_kernel_space);
|
||||
}
|
||||
MSH_CMD_EXPORT(list_kmem, List varea in kernel virtual memory space);
|
||||
|
||||
void rt_kmem_list(void) __attribute__((alias("list_kmem")));
|
||||
|
||||
static rt_ubase_t rt_pv_offset;
|
||||
|
||||
const rt_ubase_t rt_kmem_pvoff(void)
|
||||
{
|
||||
return rt_pv_offset;
|
||||
}
|
||||
|
||||
void rt_kmem_pvoff_set(rt_ubase_t pvoff)
|
||||
{
|
||||
rt_pv_offset = pvoff;
|
||||
}
|
||||
|
||||
#define _KMEM_LO_OFF(addr) ((rt_ubase_t)(addr) & ARCH_PAGE_MASK)
|
||||
|
||||
int rt_kmem_map_phy(void *va, void *pa, rt_size_t length, rt_size_t attr)
|
||||
{
|
||||
int err;
|
||||
size_t lo_off;
|
||||
lo_off = _KMEM_LO_OFF(pa);
|
||||
|
||||
if (va == RT_NULL)
|
||||
{
|
||||
LOG_E("%s: va NULL is not a valid input", __func__);
|
||||
err = -RT_EINVAL;
|
||||
}
|
||||
else if (_KMEM_LO_OFF(pa) != _KMEM_LO_OFF(va))
|
||||
{
|
||||
LOG_E("%s: misaligned PA(%p) to VA(%p)", __func__, pa, va);
|
||||
err = -RT_EINVAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
struct rt_mm_va_hint hint = {.flags = MMF_MAP_FIXED,
|
||||
.limit_range_size = rt_kernel_space.size,
|
||||
.limit_start = rt_kernel_space.start,
|
||||
.prefer = va,
|
||||
.map_size = RT_ALIGN(length + lo_off, ARCH_PAGE_SIZE)};
|
||||
|
||||
err = rt_aspace_map_phy(&rt_kernel_space, &hint, attr, MM_PA_TO_OFF(pa), &va);
|
||||
|
||||
if (err)
|
||||
{
|
||||
LOG_W("%s: map %p to %p (%p bytes) failed(err %d)", __func__, pa, va, length, err);
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void *rt_kmem_v2p(void *vaddr)
|
||||
{
|
||||
return rt_hw_mmu_v2p(&rt_kernel_space, vaddr);
|
||||
}
|
||||
|
||||
void *rt_kmem_p2v(void *paddr)
|
||||
{
|
||||
char *rc;
|
||||
char *linear_va;
|
||||
char *linear_pa;
|
||||
if (paddr != ARCH_MAP_FAILED)
|
||||
{
|
||||
linear_va = (char *)paddr - PV_OFFSET;
|
||||
linear_pa = rt_kmem_v2p(linear_va);
|
||||
if (linear_pa != paddr)
|
||||
{
|
||||
rc = RT_NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = linear_va;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = RT_NULL;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
420
rt-thread/components/mm/mm_memblock.c
Normal file
420
rt-thread/components/mm/mm_memblock.c
Normal file
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2023, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2023-09-07 zmshahaha the first version
|
||||
*/
|
||||
|
||||
#include "mm_memblock.h"
|
||||
#include "mm_page.h"
|
||||
#include "mm_aspace.h"
|
||||
#include <mmu.h>
|
||||
|
||||
#define DBG_TAG "mm.memblock"
|
||||
#define DBG_LVL DBG_INFO
|
||||
#include <rtdbg.h>
|
||||
|
||||
#define PHYS_ADDR_MAX (~((rt_size_t)0))
|
||||
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
#ifndef RT_INIT_MEMORY_REGIONS
|
||||
#define RT_INIT_MEMORY_REGIONS 128
|
||||
#endif
|
||||
|
||||
static struct rt_mmblk_reg _regions[RT_INIT_MEMORY_REGIONS];
|
||||
static int _hint_idx;
|
||||
|
||||
static struct rt_memblock mmblk_memory;
|
||||
static struct rt_memblock mmblk_reserved;
|
||||
|
||||
struct rt_memblock *rt_memblock_get_memory(void)
|
||||
{
|
||||
return &mmblk_memory;
|
||||
}
|
||||
|
||||
struct rt_memblock *rt_memblock_get_reserved(void)
|
||||
{
|
||||
return &mmblk_reserved;
|
||||
}
|
||||
|
||||
rt_inline struct rt_mmblk_reg *_next_region(struct rt_mmblk_reg *prev)
|
||||
{
|
||||
if (prev && prev->node.next)
|
||||
{
|
||||
return rt_slist_entry(prev->node.next, struct rt_mmblk_reg, node);
|
||||
}
|
||||
else
|
||||
{
|
||||
return RT_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static struct rt_mmblk_reg *_alloc_memreg(struct rt_mmblk_reg *prev)
|
||||
{
|
||||
for (int i =_hint_idx; i < RT_INIT_MEMORY_REGIONS; i++)
|
||||
{
|
||||
if (_regions[i].alloc == RT_FALSE)
|
||||
{
|
||||
rt_slist_insert(&(prev->node), &(_regions[i].node));
|
||||
_regions[i].alloc = RT_TRUE;
|
||||
_hint_idx = i + 1;
|
||||
return &_regions[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _hint_idx; i++)
|
||||
{
|
||||
if (_regions[i].alloc == RT_FALSE)
|
||||
{
|
||||
rt_slist_insert(&(prev->node), &(_regions[i].node));
|
||||
_regions[i].alloc = RT_TRUE;
|
||||
_hint_idx = i + 1;
|
||||
return &_regions[i];
|
||||
}
|
||||
}
|
||||
|
||||
return RT_NULL;
|
||||
}
|
||||
|
||||
static void _free_memreg(struct rt_mmblk_reg *prev)
|
||||
{
|
||||
struct rt_mmblk_reg *next = _next_region(prev);
|
||||
|
||||
next->alloc = RT_FALSE;
|
||||
rt_slist_remove(&(prev->node), prev->node.next);
|
||||
}
|
||||
|
||||
static rt_err_t _reg_insert_after(struct rt_mmblk_reg *prev, rt_region_t *reg,
|
||||
mmblk_flag_t flags)
|
||||
{
|
||||
struct rt_mmblk_reg *new_reg = _alloc_memreg(prev);
|
||||
|
||||
if (!new_reg)
|
||||
{
|
||||
LOG_E("No enough space");
|
||||
return -RT_ENOMEM;
|
||||
}
|
||||
|
||||
rt_memcpy(&(new_reg->memreg), reg, sizeof(*reg));
|
||||
new_reg->flags = flags;
|
||||
return RT_EOK;
|
||||
}
|
||||
|
||||
rt_inline void _reg_remove_after(struct rt_mmblk_reg *prev)
|
||||
{
|
||||
_free_memreg(prev);
|
||||
}
|
||||
|
||||
/* adding overlapped regions is banned */
|
||||
static rt_err_t _memblock_add_range(struct rt_memblock *memblock,
|
||||
const char *name, rt_size_t start, rt_size_t end, mm_flag_t flag)
|
||||
{
|
||||
struct rt_mmblk_reg *reg = RT_NULL, *reg_next = RT_NULL;
|
||||
rt_slist_t sentinel;
|
||||
rt_region_t new_region;
|
||||
|
||||
if (start >= end)
|
||||
return -RT_EINVAL;
|
||||
|
||||
sentinel.next = &(memblock->reg_list);
|
||||
|
||||
/* find suitable place */
|
||||
rt_slist_for_each_entry(reg, &sentinel, node)
|
||||
{
|
||||
reg_next = _next_region(reg);
|
||||
|
||||
if (reg_next == RT_NULL)
|
||||
break;
|
||||
|
||||
rt_size_t rstart = reg_next->memreg.start;
|
||||
rt_size_t rend = reg_next->memreg.end;
|
||||
|
||||
/* not overlap */
|
||||
if (rstart >= end)
|
||||
break;
|
||||
if (rend <= start)
|
||||
continue;
|
||||
|
||||
/* overlap */
|
||||
LOG_E("region to add %s: [%p-%p) overlap with existing region %s: [%p-%p)",\
|
||||
name, start, end, reg_next->memreg.name, rstart, rend);
|
||||
return -RT_EINVAL;
|
||||
}
|
||||
|
||||
/* insert the region */
|
||||
new_region.name = name;
|
||||
new_region.start = start;
|
||||
new_region.end = end;
|
||||
return _reg_insert_after(reg, &new_region, flag);
|
||||
}
|
||||
|
||||
rt_err_t rt_memblock_add_memory(const char *name, rt_size_t start, rt_size_t end, mmblk_flag_t flags)
|
||||
{
|
||||
LOG_D("add physical address range [%p-%p) with flag 0x%x" \
|
||||
" to overall memory regions\n", base, base + size, flag);
|
||||
|
||||
return _memblock_add_range(&mmblk_memory, name, start, end, flags);
|
||||
}
|
||||
|
||||
rt_err_t rt_memblock_reserve_memory(const char *name, rt_size_t start, rt_size_t end, mmblk_flag_t flags)
|
||||
{
|
||||
LOG_D("add physical address range [%p-%p) to reserved memory regions\n",\
|
||||
base, base + size);
|
||||
|
||||
return _memblock_add_range(&mmblk_reserved, name, start, end, flags);
|
||||
}
|
||||
|
||||
/* [*start_reg, *end_reg) is the isolated range */
|
||||
static rt_err_t _memblock_separate_range(struct rt_memblock *memblock,
|
||||
rt_size_t start, rt_size_t end,
|
||||
struct rt_mmblk_reg **start_reg, struct rt_mmblk_reg **end_reg)
|
||||
{
|
||||
struct rt_mmblk_reg *reg = RT_NULL;
|
||||
rt_region_t new_region;
|
||||
rt_err_t err = RT_EOK;
|
||||
|
||||
*start_reg = *end_reg = RT_NULL;
|
||||
|
||||
rt_slist_for_each_entry(reg, &(memblock->reg_list), node)
|
||||
{
|
||||
rt_size_t rstart = reg->memreg.start;
|
||||
rt_size_t rend = reg->memreg.end;
|
||||
|
||||
if (rstart >= end)
|
||||
break;
|
||||
if (rend <= start)
|
||||
continue;
|
||||
|
||||
/* the beginning of the range separates its respective region */
|
||||
if (rstart < start)
|
||||
{
|
||||
new_region.start = start;
|
||||
new_region.end = rend;
|
||||
new_region.name = reg->memreg.name;
|
||||
err = _reg_insert_after(reg, &new_region, reg->flags);
|
||||
|
||||
if (err != RT_EOK)
|
||||
return err;
|
||||
|
||||
reg->memreg.end = start;
|
||||
|
||||
*start_reg = _next_region(reg);
|
||||
*end_reg = _next_region(*start_reg);
|
||||
}
|
||||
/* the endpoint of the range separates its respective region */
|
||||
else if (rend > end)
|
||||
{
|
||||
new_region.start = end;
|
||||
new_region.end = rend;
|
||||
new_region.name = reg->memreg.name;
|
||||
err = _reg_insert_after(reg, &new_region, reg->flags);
|
||||
|
||||
if (err != RT_EOK)
|
||||
return err;
|
||||
|
||||
reg->memreg.end = end;
|
||||
|
||||
*end_reg = _next_region(reg);
|
||||
break;
|
||||
}
|
||||
/* reg->next is fully contained in range */
|
||||
else
|
||||
{
|
||||
if (!*end_reg)
|
||||
*start_reg = reg;
|
||||
*end_reg = _next_region(reg);
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void _memblock_set_flag(struct rt_mmblk_reg *start_reg, struct rt_mmblk_reg *end_reg, \
|
||||
mmblk_flag_t flags)
|
||||
{
|
||||
if (start_reg == RT_NULL)
|
||||
return;
|
||||
|
||||
for (struct rt_mmblk_reg *iter = start_reg; iter != end_reg; iter = _next_region(iter)) {
|
||||
iter->flags |= flags;
|
||||
}
|
||||
}
|
||||
|
||||
static void _next_free_region(struct rt_mmblk_reg **m, struct rt_mmblk_reg **r, mmblk_flag_t flags,
|
||||
rt_size_t *out_start, rt_size_t *out_end)
|
||||
{
|
||||
/* memory related data */
|
||||
rt_size_t m_start = 0;
|
||||
rt_size_t m_end = 0;
|
||||
|
||||
/* reserved related data */
|
||||
rt_size_t r_start = 0;
|
||||
rt_size_t r_end = 0;
|
||||
struct rt_mmblk_reg *r_sentinel = rt_slist_entry(&(mmblk_reserved.reg_list), struct rt_mmblk_reg, node);
|
||||
|
||||
for (; *m != RT_NULL; *m = _next_region(*m))
|
||||
{
|
||||
if ((*m)->flags != flags)
|
||||
continue;
|
||||
|
||||
m_start = (*m)->memreg.start;
|
||||
m_end = (*m)->memreg.end;
|
||||
|
||||
for (; *r != RT_NULL; *r = _next_region(*r))
|
||||
{
|
||||
/*
|
||||
* r started with _resreg_guard
|
||||
* Find the complement of reserved memblock.
|
||||
* For example, if reserved memblock is following:
|
||||
*
|
||||
* 0:[8-16), 1:[32-48), 2:[128-130)
|
||||
*
|
||||
* The upper 32bit indexes the following regions.
|
||||
*
|
||||
* 0:[0-8), 1:[16-32), 2:[48-128), 3:[130-MAX)
|
||||
*
|
||||
* So we can find intersecting region other than excluding.
|
||||
*/
|
||||
r_start = (*r == r_sentinel) ? 0 : (*r)->memreg.end;
|
||||
r_end = (_next_region(*r)) ? _next_region(*r)->memreg.start : PHYS_ADDR_MAX;
|
||||
|
||||
/* two reserved region are adjacent */
|
||||
if (r_start == r_end)
|
||||
continue;
|
||||
|
||||
if (r_start >= m_end)
|
||||
break;
|
||||
|
||||
if (m_start < r_end)
|
||||
{
|
||||
*out_start = MAX(m_start, r_start);
|
||||
*out_end = MIN(m_end, r_end);
|
||||
|
||||
if (m_end <= r_end)
|
||||
*m = _next_region(*m);
|
||||
else
|
||||
*r = _next_region(*r);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* all regions found */
|
||||
*m = rt_slist_entry(&(mmblk_memory.reg_list), struct rt_mmblk_reg, node);
|
||||
}
|
||||
|
||||
/* for each region in memory with flags and not reserved */
|
||||
#define for_each_free_region(m, r, flags, p_start, p_end) \
|
||||
m = rt_slist_entry(&(mmblk_memory.reg_list.next), struct rt_mmblk_reg, node); \
|
||||
r = rt_slist_entry(&(mmblk_reserved.reg_list), struct rt_mmblk_reg, node); \
|
||||
for (_next_free_region(&m, &r, flags, p_start, p_end); \
|
||||
m != rt_slist_entry(&(mmblk_memory.reg_list), struct rt_mmblk_reg, node); \
|
||||
_next_free_region(&m, &r, flags, p_start, p_end))
|
||||
|
||||
/* merge normal memory regions */
|
||||
static void _memblock_merge_memory(void)
|
||||
{
|
||||
struct rt_mmblk_reg *reg = RT_NULL;
|
||||
|
||||
rt_slist_for_each_entry(reg, &(mmblk_memory.reg_list), node)
|
||||
{
|
||||
while (_next_region(reg) &&
|
||||
reg->flags == _next_region(reg)->flags &&
|
||||
reg->memreg.end == _next_region(reg)->memreg.start)
|
||||
{
|
||||
reg->memreg.end = _next_region(reg)->memreg.end;
|
||||
_reg_remove_after(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rt_memblock_setup_memory_environment(void)
|
||||
{
|
||||
struct rt_mmblk_reg *iter = RT_NULL, *start_reg = RT_NULL, *end_reg = RT_NULL;
|
||||
rt_region_t reg = {0};
|
||||
rt_size_t mem = 0;
|
||||
struct rt_mmblk_reg *m, *r;
|
||||
void *err;
|
||||
|
||||
_memblock_merge_memory();
|
||||
|
||||
LOG_I("System memory:");
|
||||
|
||||
rt_slist_for_each_entry(iter, &(mmblk_memory.reg_list), node)
|
||||
{
|
||||
LOG_I(" %-*.s [%p, %p]", RT_NAME_MAX, iter->memreg.name, iter->memreg.start, iter->memreg.end);
|
||||
}
|
||||
|
||||
LOG_I("Reserved memory:");
|
||||
|
||||
rt_slist_for_each_entry(iter, &(mmblk_reserved.reg_list), node)
|
||||
{
|
||||
LOG_I(" %-*.s [%p, %p]", RT_NAME_MAX, iter->memreg.name, iter->memreg.start, iter->memreg.end);
|
||||
|
||||
if (iter->flags != MEMBLOCK_NONE)
|
||||
{
|
||||
_memblock_separate_range(&mmblk_memory, iter->memreg.start, iter->memreg.end, &start_reg, &end_reg);
|
||||
_memblock_set_flag(start_reg, end_reg, iter->flags);
|
||||
}
|
||||
}
|
||||
|
||||
/* install usable memory to system page */
|
||||
for_each_free_region(m, r, MEMBLOCK_NONE, ®.start, ®.end)
|
||||
{
|
||||
reg.start = RT_ALIGN(reg.start, ARCH_PAGE_SIZE);
|
||||
reg.end = RT_ALIGN_DOWN(reg.end, ARCH_PAGE_SIZE);
|
||||
|
||||
if (reg.start >= reg.end)
|
||||
continue;
|
||||
|
||||
LOG_I("physical memory region [%p-%p] installed to system page", reg.start, reg.end);
|
||||
|
||||
reg.start -= PV_OFFSET;
|
||||
reg.end -= PV_OFFSET;
|
||||
|
||||
struct rt_mm_va_hint hint = {.flags = MMF_MAP_FIXED,
|
||||
.limit_start = rt_kernel_space.start,
|
||||
.limit_range_size = rt_kernel_space.size,
|
||||
.map_size = reg.end - reg.start,
|
||||
.prefer = (void *)reg.start};
|
||||
|
||||
rt_aspace_map_phy(&rt_kernel_space, &hint, MMU_MAP_K_RWCB, (reg.start + PV_OFFSET) >> MM_PAGE_SHIFT, &err);
|
||||
rt_page_install(reg);
|
||||
mem += reg.end - reg.start;
|
||||
}
|
||||
|
||||
LOG_I("%ld MB memory installed to system page", mem/1000000);
|
||||
}
|
||||
|
||||
#ifdef UTEST_MM_API_TC
|
||||
/* functions below are only used for utest */
|
||||
void rt_memblock_merge(void)
|
||||
{
|
||||
_memblock_merge_memory();
|
||||
}
|
||||
|
||||
static struct rt_mmblk_reg *mem;
|
||||
static struct rt_mmblk_reg *res;
|
||||
|
||||
void rt_memblock_next_free_region_init(void)
|
||||
{
|
||||
mem = rt_slist_entry(&(mmblk_memory.reg_list.next), struct rt_mmblk_reg, node);
|
||||
res = rt_slist_entry(&(mmblk_reserved.reg_list), struct rt_mmblk_reg, node);
|
||||
}
|
||||
|
||||
void rt_memblock_next_free_region(mmblk_flag_t flags, rt_size_t *out_start, rt_size_t *out_end)
|
||||
{
|
||||
_next_free_region(&mem, &res, flags, out_start, out_end);
|
||||
}
|
||||
|
||||
rt_bool_t rt_memblock_is_last_free(void)
|
||||
{
|
||||
return mem == rt_slist_entry(&(mmblk_memory.reg_list), struct rt_mmblk_reg, node);
|
||||
}
|
||||
|
||||
#endif /* UTEST_MM_API_TC */
|
95
rt-thread/components/mm/mm_memblock.h
Normal file
95
rt-thread/components/mm/mm_memblock.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2023, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2023-09-07 zmshahaha the first version
|
||||
*/
|
||||
|
||||
#ifndef __MM_MEMBLOCK_H__
|
||||
#define __MM_MEMBLOCK_H__
|
||||
|
||||
#include "mm_page.h"
|
||||
#include <rtdef.h>
|
||||
|
||||
enum mmblk_flag
|
||||
{
|
||||
MEMBLOCK_NONE = 0x0, /**< normal region */
|
||||
MEMBLOCK_HOTPLUG = 0x1, /**< hotpluggable region */
|
||||
MEMBLOCK_NOMAP = 0x2, /**< don't add to kernel direct mapping */
|
||||
};
|
||||
|
||||
typedef rt_uint32_t mmblk_flag_t;
|
||||
|
||||
/**
|
||||
* @struct rt_mmblk_reg
|
||||
*
|
||||
* @brief A structure representing a region in rt_memblock
|
||||
*/
|
||||
struct rt_mmblk_reg
|
||||
{
|
||||
rt_region_t memreg; /**< used to indicate the extent of this area */
|
||||
mmblk_flag_t flags; /**< the flag of the region */
|
||||
rt_bool_t alloc; /**< is the node allocated */
|
||||
rt_slist_t node; /**< hook on rt_memblock */
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct rt_memblock
|
||||
*
|
||||
* @brief A structure representing physical memory block
|
||||
*/
|
||||
struct rt_memblock
|
||||
{
|
||||
rt_slist_t reg_list; /**< store the regions of the memory block */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Add a physical address range to the overall memory region
|
||||
*
|
||||
* @note The newly added region is strictly prohibited from overlapping with existing regions.
|
||||
*
|
||||
* @param name the name of the region
|
||||
* @param start the beginning of the physical address range
|
||||
* @param end the size of the physical address range
|
||||
* @param flags the flags of the region
|
||||
*/
|
||||
rt_err_t rt_memblock_add_memory(const char *name, rt_size_t start, rt_size_t end, mmblk_flag_t flags);
|
||||
|
||||
/**
|
||||
* @brief Add a physical address range to the reserved memory region
|
||||
*
|
||||
* @note The newly added region is strictly prohibited from overlapping with existing regions.
|
||||
*
|
||||
* @param name the name of the reseved region
|
||||
* @param start the beginning of the physical address range
|
||||
* @param end the size of the physical address range
|
||||
* @param flags the flags of the region
|
||||
*/
|
||||
rt_err_t rt_memblock_reserve_memory(const char *name, rt_size_t start, rt_size_t end, mmblk_flag_t flags);
|
||||
|
||||
/**
|
||||
* @brief To conclude the management of memory by the memblock.
|
||||
*
|
||||
* @note This function will free all usable to buddy system and map all memory without
|
||||
* specified MEMBLOCK_NOMAP.
|
||||
*/
|
||||
void rt_memblock_setup_memory_environment(void);
|
||||
|
||||
/**
|
||||
* @brief Get the memory memblock.
|
||||
*
|
||||
* @return Overall memory memblock.
|
||||
*/
|
||||
struct rt_memblock *rt_memblock_get_memory(void);
|
||||
|
||||
/**
|
||||
* @brief Get the reserved memory memblock.
|
||||
*
|
||||
* @return Reserved memory memblock.
|
||||
*/
|
||||
struct rt_memblock *rt_memblock_get_reserved(void);
|
||||
|
||||
#endif /* __MM_MEMBLOCK_H__ */
|
121
rt-thread/components/mm/mm_object.c
Normal file
121
rt-thread/components/mm/mm_object.c
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2023, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2022-11-30 WangXiaoyao the first version
|
||||
* 2023-08-19 Shell Support varea modification handler
|
||||
* 2023-10-13 Shell Replace the page management algorithm of pgmgr
|
||||
*/
|
||||
|
||||
#define DBG_TAG "mm.object"
|
||||
#define DBG_LVL DBG_INFO
|
||||
#include <rtdbg.h>
|
||||
|
||||
#include <rtthread.h>
|
||||
|
||||
#include "mm_aspace.h"
|
||||
#include "mm_fault.h"
|
||||
#include "mm_page.h"
|
||||
#include <mmu.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/** varea based dummy memory object whose data comes directly from page frame */
|
||||
|
||||
static const char *get_name(rt_varea_t varea)
|
||||
{
|
||||
return "dummy-mapper";
|
||||
}
|
||||
|
||||
static void on_page_fault(struct rt_varea *varea, struct rt_aspace_fault_msg *msg)
|
||||
{
|
||||
void *page;
|
||||
page = rt_pages_alloc_ext(0, PAGE_ANY_AVAILABLE);
|
||||
|
||||
if (!page)
|
||||
{
|
||||
LOG_W("%s: page alloc failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
msg->response.status = MM_FAULT_STATUS_OK;
|
||||
msg->response.size = ARCH_PAGE_SIZE;
|
||||
msg->response.vaddr = page;
|
||||
}
|
||||
|
||||
static void on_varea_open(struct rt_varea *varea)
|
||||
{
|
||||
varea->data = NULL;
|
||||
}
|
||||
|
||||
static void on_varea_close(struct rt_varea *varea)
|
||||
{
|
||||
}
|
||||
|
||||
static rt_err_t on_varea_expand(struct rt_varea *varea, void *new_vaddr, rt_size_t size)
|
||||
{
|
||||
return RT_EOK;
|
||||
}
|
||||
|
||||
static rt_err_t on_varea_shrink(rt_varea_t varea, void *new_start, rt_size_t size)
|
||||
{
|
||||
return RT_EOK;
|
||||
}
|
||||
|
||||
static rt_err_t on_varea_split(struct rt_varea *existed, void *unmap_start, rt_size_t unmap_len, struct rt_varea *subset)
|
||||
{
|
||||
return RT_EOK;
|
||||
}
|
||||
|
||||
static rt_err_t on_varea_merge(struct rt_varea *merge_to, struct rt_varea *merge_from)
|
||||
{
|
||||
return RT_EOK;
|
||||
}
|
||||
|
||||
static void page_read(struct rt_varea *varea, struct rt_aspace_io_msg *msg)
|
||||
{
|
||||
char *dst_k;
|
||||
rt_aspace_t aspace = varea->aspace;
|
||||
dst_k = rt_hw_mmu_v2p(aspace, msg->fault_vaddr);
|
||||
if (dst_k != ARCH_MAP_FAILED)
|
||||
{
|
||||
RT_ASSERT(!((long)dst_k & ARCH_PAGE_MASK));
|
||||
dst_k = (void *)((char *)dst_k - PV_OFFSET);
|
||||
memcpy(msg->buffer_vaddr, dst_k, ARCH_PAGE_SIZE);
|
||||
msg->response.status = MM_FAULT_STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
static void page_write(struct rt_varea *varea, struct rt_aspace_io_msg *msg)
|
||||
{
|
||||
void *dst_k;
|
||||
rt_aspace_t aspace = varea->aspace;
|
||||
dst_k = rt_hw_mmu_v2p(aspace, msg->fault_vaddr);
|
||||
if (dst_k != ARCH_MAP_FAILED)
|
||||
{
|
||||
RT_ASSERT(!((long)dst_k & ARCH_PAGE_MASK));
|
||||
dst_k = (void *)((char *)dst_k - PV_OFFSET);
|
||||
memcpy(dst_k, msg->buffer_vaddr, ARCH_PAGE_SIZE);
|
||||
msg->response.status = MM_FAULT_STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
struct rt_mem_obj rt_mm_dummy_mapper = {
|
||||
.get_name = get_name,
|
||||
.on_page_fault = on_page_fault,
|
||||
.hint_free = NULL,
|
||||
.on_varea_open = on_varea_open,
|
||||
.on_varea_close = on_varea_close,
|
||||
|
||||
.on_varea_shrink = on_varea_shrink,
|
||||
.on_varea_split = on_varea_split,
|
||||
.on_varea_expand = on_varea_expand,
|
||||
.on_varea_merge = on_varea_merge,
|
||||
|
||||
.page_write = page_write,
|
||||
.page_read = page_read,
|
||||
};
|
893
rt-thread/components/mm/mm_page.c
Normal file
893
rt-thread/components/mm/mm_page.c
Normal file
@@ -0,0 +1,893 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2022, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2019-11-01 Jesven The first version
|
||||
* 2022-12-13 WangXiaoyao Hot-pluggable, extensible
|
||||
* page management algorithm
|
||||
* 2023-02-20 WangXiaoyao Multi-list page-management
|
||||
* 2023-11-28 Shell Bugs fix for page_install on shadow region
|
||||
*/
|
||||
#include <rtthread.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mm_fault.h"
|
||||
#include "mm_private.h"
|
||||
#include "mm_aspace.h"
|
||||
#include "mm_flag.h"
|
||||
#include "mm_page.h"
|
||||
#include <mmu.h>
|
||||
|
||||
#define DBG_TAG "mm.page"
|
||||
#define DBG_LVL DBG_WARNING
|
||||
#include <rtdbg.h>
|
||||
|
||||
RT_STATIC_ASSERT(order_huge_pg, RT_PAGE_MAX_ORDER > ARCH_PAGE_SHIFT - 2);
|
||||
RT_STATIC_ASSERT(size_width, sizeof(rt_size_t) == sizeof(void *));
|
||||
|
||||
#ifdef RT_USING_SMART
|
||||
#include "lwp_arch_comm.h"
|
||||
#endif /* RT_USING_SMART */
|
||||
|
||||
static rt_size_t init_mpr_align_start;
|
||||
static rt_size_t init_mpr_align_end;
|
||||
static void *init_mpr_cont_start;
|
||||
|
||||
static struct rt_varea mpr_varea;
|
||||
|
||||
static struct rt_page *page_list_low[RT_PAGE_MAX_ORDER];
|
||||
static struct rt_page *page_list_high[RT_PAGE_MAX_ORDER];
|
||||
static RT_DEFINE_SPINLOCK(_spinlock);
|
||||
|
||||
#define page_start ((rt_page_t)rt_mpr_start)
|
||||
|
||||
static rt_size_t page_nr;
|
||||
static rt_size_t _high_pages_nr;
|
||||
static rt_size_t early_offset;
|
||||
|
||||
static const char *get_name(rt_varea_t varea)
|
||||
{
|
||||
return "master-page-record";
|
||||
}
|
||||
|
||||
static void hint_free(rt_mm_va_hint_t hint)
|
||||
{
|
||||
hint->flags = MMF_MAP_FIXED;
|
||||
hint->limit_start = rt_kernel_space.start;
|
||||
hint->limit_range_size = rt_kernel_space.size;
|
||||
hint->prefer = rt_mpr_start;
|
||||
}
|
||||
|
||||
static void on_page_fault(struct rt_varea *varea, struct rt_aspace_fault_msg *msg)
|
||||
{
|
||||
char *init_start = (void *)init_mpr_align_start;
|
||||
char *init_end = (void *)init_mpr_align_end;
|
||||
if ((char *)msg->fault_vaddr < init_end && (char *)msg->fault_vaddr >= init_start)
|
||||
{
|
||||
rt_size_t offset = (char *)msg->fault_vaddr - init_start;
|
||||
msg->response.status = MM_FAULT_STATUS_OK;
|
||||
msg->response.vaddr = (char *)init_mpr_cont_start + offset;
|
||||
msg->response.size = ARCH_PAGE_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_mm_dummy_mapper.on_page_fault(varea, msg);
|
||||
}
|
||||
}
|
||||
|
||||
static struct rt_mem_obj mm_page_mapper = {
|
||||
.get_name = get_name,
|
||||
.on_page_fault = on_page_fault,
|
||||
.hint_free = hint_free,
|
||||
};
|
||||
|
||||
#ifdef RT_DEBUGING_PAGE_LEAK
|
||||
static volatile int enable;
|
||||
static rt_page_t _trace_head;
|
||||
#define TRACE_ALLOC(pg, size) _trace_alloc(pg, __builtin_return_address(0), size)
|
||||
#define TRACE_FREE(pgaddr, size) _trace_free(pgaddr, __builtin_return_address(0), size)
|
||||
|
||||
static long _alloc_cnt;
|
||||
|
||||
void rt_page_leak_trace_start()
|
||||
{
|
||||
// TODO multicore safety
|
||||
_trace_head = NULL;
|
||||
_alloc_cnt = 0;
|
||||
enable = 1;
|
||||
}
|
||||
MSH_CMD_EXPORT(rt_page_leak_trace_start, start page leak tracer);
|
||||
|
||||
static void _collect()
|
||||
{
|
||||
rt_page_t page = _trace_head;
|
||||
if (!page)
|
||||
{
|
||||
rt_kprintf("ok! ALLOC CNT %ld\n", _alloc_cnt);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (page)
|
||||
{
|
||||
rt_page_t next = page->tl_next;
|
||||
void *pg_va = rt_page_page2addr(page);
|
||||
LOG_W("LEAK: %p, allocator: %p, size bits: %lx", pg_va, page->caller, page->trace_size);
|
||||
rt_pages_free(pg_va, page->trace_size);
|
||||
page = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rt_page_leak_trace_stop()
|
||||
{
|
||||
// TODO multicore safety
|
||||
enable = 0;
|
||||
_collect();
|
||||
}
|
||||
MSH_CMD_EXPORT(rt_page_leak_trace_stop, stop page leak tracer);
|
||||
|
||||
static void _trace_alloc(rt_page_t page, void *caller, size_t size_bits)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
page->caller = caller;
|
||||
page->trace_size = size_bits;
|
||||
page->tl_prev = NULL;
|
||||
page->tl_next = NULL;
|
||||
|
||||
_alloc_cnt++;
|
||||
if (_trace_head == NULL)
|
||||
{
|
||||
_trace_head = page;
|
||||
}
|
||||
else
|
||||
{
|
||||
_trace_head->tl_prev = page;
|
||||
page->tl_next = _trace_head;
|
||||
_trace_head = page;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _report(rt_page_t page, size_t size_bits, char *msg)
|
||||
{
|
||||
void *pg_va = rt_page_page2addr(page);
|
||||
LOG_W("%s: %p, allocator: %p, size bits: %lx", msg, pg_va, page->caller, page->trace_size);
|
||||
rt_kprintf("backtrace\n");
|
||||
rt_backtrace();
|
||||
}
|
||||
|
||||
static void _trace_free(rt_page_t page, void *caller, size_t size_bits)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
/* free after free */
|
||||
if (page->trace_size == 0xabadcafe)
|
||||
{
|
||||
_report(page, size_bits, "free after free");
|
||||
return ;
|
||||
}
|
||||
else if (page->trace_size != size_bits)
|
||||
{
|
||||
rt_kprintf("free with size bits %lx\n", size_bits);
|
||||
_report(page, size_bits, "incompatible size bits parameter");
|
||||
return ;
|
||||
}
|
||||
|
||||
if (page->ref_cnt == 0)
|
||||
{
|
||||
_alloc_cnt--;
|
||||
if (page->tl_prev)
|
||||
page->tl_prev->tl_next = page->tl_next;
|
||||
if (page->tl_next)
|
||||
page->tl_next->tl_prev = page->tl_prev;
|
||||
|
||||
if (page == _trace_head)
|
||||
_trace_head = page->tl_next;
|
||||
|
||||
page->tl_prev = NULL;
|
||||
page->tl_next = NULL;
|
||||
page->trace_size = 0xabadcafe;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define TRACE_ALLOC(x, y)
|
||||
#define TRACE_FREE(x, y)
|
||||
#endif
|
||||
|
||||
static inline void *page_to_addr(rt_page_t page)
|
||||
{
|
||||
return (void *)(((page - page_start) << ARCH_PAGE_SHIFT) - PV_OFFSET);
|
||||
}
|
||||
|
||||
static inline rt_page_t addr_to_page(rt_page_t pg_start, void *addr)
|
||||
{
|
||||
addr = (char *)addr + PV_OFFSET;
|
||||
return &pg_start[((rt_ubase_t)addr >> ARCH_PAGE_SHIFT)];
|
||||
}
|
||||
|
||||
#define FLOOR(val, align) (((rt_size_t)(val) + (align)-1) & ~((align)-1))
|
||||
|
||||
const rt_size_t shadow_mask =
|
||||
((1ul << (RT_PAGE_MAX_ORDER + ARCH_PAGE_SHIFT - 1)) - 1);
|
||||
|
||||
const rt_size_t rt_mpr_size = FLOOR(
|
||||
((1ul << (ARCH_VADDR_WIDTH - ARCH_PAGE_SHIFT))) * sizeof(struct rt_page),
|
||||
ARCH_PAGE_SIZE);
|
||||
|
||||
void *rt_mpr_start;
|
||||
|
||||
rt_weak int rt_hw_clz(unsigned long n)
|
||||
{
|
||||
return __builtin_clzl(n);
|
||||
}
|
||||
|
||||
rt_weak int rt_hw_ctz(unsigned long n)
|
||||
{
|
||||
return __builtin_ctzl(n);
|
||||
}
|
||||
|
||||
rt_size_t rt_page_bits(rt_size_t size)
|
||||
{
|
||||
int bit = sizeof(rt_size_t) * 8 - rt_hw_clz(size) - 1;
|
||||
|
||||
if ((size ^ (1UL << bit)) != 0)
|
||||
{
|
||||
bit++;
|
||||
}
|
||||
bit -= ARCH_PAGE_SHIFT;
|
||||
if (bit < 0)
|
||||
{
|
||||
bit = 0;
|
||||
}
|
||||
return bit;
|
||||
}
|
||||
|
||||
struct rt_page *rt_page_addr2page(void *addr)
|
||||
{
|
||||
return addr_to_page(page_start, addr);
|
||||
}
|
||||
|
||||
void *rt_page_page2addr(struct rt_page *p)
|
||||
{
|
||||
return page_to_addr(p);
|
||||
}
|
||||
|
||||
static inline struct rt_page *_buddy_get(struct rt_page *p,
|
||||
rt_uint32_t size_bits)
|
||||
{
|
||||
rt_size_t addr;
|
||||
|
||||
addr = (rt_size_t)rt_page_page2addr(p);
|
||||
addr ^= (1UL << (size_bits + ARCH_PAGE_SHIFT));
|
||||
return rt_page_addr2page((void *)addr);
|
||||
}
|
||||
|
||||
static void _page_remove(rt_page_t page_list[], struct rt_page *p, rt_uint32_t size_bits)
|
||||
{
|
||||
if (p->pre)
|
||||
{
|
||||
p->pre->next = p->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
page_list[size_bits] = p->next;
|
||||
}
|
||||
|
||||
if (p->next)
|
||||
{
|
||||
p->next->pre = p->pre;
|
||||
}
|
||||
|
||||
p->size_bits = ARCH_ADDRESS_WIDTH_BITS;
|
||||
}
|
||||
|
||||
static void _page_insert(rt_page_t page_list[], struct rt_page *p, rt_uint32_t size_bits)
|
||||
{
|
||||
p->next = page_list[size_bits];
|
||||
if (p->next)
|
||||
{
|
||||
p->next->pre = p;
|
||||
}
|
||||
p->pre = 0;
|
||||
page_list[size_bits] = p;
|
||||
p->size_bits = size_bits;
|
||||
}
|
||||
|
||||
static void _pages_ref_inc(struct rt_page *p, rt_uint32_t size_bits)
|
||||
{
|
||||
struct rt_page *page_head;
|
||||
int idx;
|
||||
|
||||
/* find page group head */
|
||||
idx = p - page_start;
|
||||
idx = idx & ~((1UL << size_bits) - 1);
|
||||
|
||||
page_head = page_start + idx;
|
||||
page_head = (void *)((char *)page_head + early_offset);
|
||||
page_head->ref_cnt++;
|
||||
}
|
||||
|
||||
static int _pages_ref_get(struct rt_page *p, rt_uint32_t size_bits)
|
||||
{
|
||||
struct rt_page *page_head;
|
||||
int idx;
|
||||
|
||||
/* find page group head */
|
||||
idx = p - page_start;
|
||||
idx = idx & ~((1UL << size_bits) - 1);
|
||||
|
||||
page_head = page_start + idx;
|
||||
return page_head->ref_cnt;
|
||||
}
|
||||
|
||||
static int _pages_free(rt_page_t page_list[], struct rt_page *p, rt_uint32_t size_bits)
|
||||
{
|
||||
rt_uint32_t level = size_bits;
|
||||
struct rt_page *buddy;
|
||||
|
||||
RT_ASSERT(p >= page_start);
|
||||
RT_ASSERT((char *)p < (char *)rt_mpr_start + rt_mpr_size);
|
||||
RT_ASSERT(rt_kmem_v2p(p));
|
||||
RT_ASSERT(p->ref_cnt > 0);
|
||||
RT_ASSERT(p->size_bits == ARCH_ADDRESS_WIDTH_BITS);
|
||||
RT_ASSERT(size_bits < RT_PAGE_MAX_ORDER);
|
||||
|
||||
p->ref_cnt--;
|
||||
if (p->ref_cnt != 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (level < RT_PAGE_MAX_ORDER - 1)
|
||||
{
|
||||
buddy = _buddy_get(p, level);
|
||||
if (buddy && buddy->size_bits == level)
|
||||
{
|
||||
_page_remove(page_list, buddy, level);
|
||||
p = (p < buddy) ? p : buddy;
|
||||
level++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
_page_insert(page_list, p, level);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct rt_page *_pages_alloc(rt_page_t page_list[], rt_uint32_t size_bits)
|
||||
{
|
||||
struct rt_page *p;
|
||||
|
||||
if (page_list[size_bits])
|
||||
{
|
||||
p = page_list[size_bits];
|
||||
_page_remove(page_list, p, size_bits);
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_uint32_t level;
|
||||
|
||||
for (level = size_bits + 1; level < RT_PAGE_MAX_ORDER; level++)
|
||||
{
|
||||
if (page_list[level])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (level == RT_PAGE_MAX_ORDER)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
p = page_list[level];
|
||||
_page_remove(page_list, p, level);
|
||||
while (level > size_bits)
|
||||
{
|
||||
_page_insert(page_list, p, level - 1);
|
||||
p = _buddy_get(p, level - 1);
|
||||
level--;
|
||||
}
|
||||
}
|
||||
p->size_bits = ARCH_ADDRESS_WIDTH_BITS;
|
||||
p->ref_cnt = 1;
|
||||
return p;
|
||||
}
|
||||
|
||||
static void _early_page_remove(rt_page_t page_list[], rt_page_t page, rt_uint32_t size_bits)
|
||||
{
|
||||
rt_page_t page_cont = (rt_page_t)((char *)page + early_offset);
|
||||
if (page_cont->pre)
|
||||
{
|
||||
rt_page_t pre_cont = (rt_page_t)((char *)page_cont->pre + early_offset);
|
||||
pre_cont->next = page_cont->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
page_list[size_bits] = page_cont->next;
|
||||
}
|
||||
|
||||
if (page_cont->next)
|
||||
{
|
||||
rt_page_t next_cont = (rt_page_t)((char *)page_cont->next + early_offset);
|
||||
next_cont->pre = page_cont->pre;
|
||||
}
|
||||
|
||||
page_cont->size_bits = ARCH_ADDRESS_WIDTH_BITS;
|
||||
}
|
||||
|
||||
static void _early_page_insert(rt_page_t page_list[], rt_page_t page, int size_bits)
|
||||
{
|
||||
RT_ASSERT((void *)page >= rt_mpr_start &&
|
||||
((char *)page - (char *)rt_mpr_start) < rt_mpr_size);
|
||||
rt_page_t page_cont = (rt_page_t)((char *)page + early_offset);
|
||||
|
||||
page_cont->next = page_list[size_bits];
|
||||
if (page_cont->next)
|
||||
{
|
||||
rt_page_t next_cont = (rt_page_t)((char *)page_cont->next + early_offset);
|
||||
next_cont->pre = page;
|
||||
}
|
||||
page_cont->pre = 0;
|
||||
page_list[size_bits] = page;
|
||||
page_cont->size_bits = size_bits;
|
||||
}
|
||||
|
||||
static struct rt_page *_early_pages_alloc(rt_page_t page_list[], rt_uint32_t size_bits)
|
||||
{
|
||||
struct rt_page *p;
|
||||
|
||||
if (page_list[size_bits])
|
||||
{
|
||||
p = page_list[size_bits];
|
||||
_early_page_remove(page_list, p, size_bits);
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_uint32_t level;
|
||||
|
||||
for (level = size_bits + 1; level < RT_PAGE_MAX_ORDER; level++)
|
||||
{
|
||||
if (page_list[level])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (level == RT_PAGE_MAX_ORDER)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
p = page_list[level];
|
||||
_early_page_remove(page_list, p, level);
|
||||
while (level > size_bits)
|
||||
{
|
||||
_early_page_insert(page_list, p, level - 1);
|
||||
p = _buddy_get(p, level - 1);
|
||||
level--;
|
||||
}
|
||||
}
|
||||
rt_page_t page_cont = (rt_page_t)((char *)p + early_offset);
|
||||
page_cont->size_bits = ARCH_ADDRESS_WIDTH_BITS;
|
||||
page_cont->ref_cnt = 1;
|
||||
return p;
|
||||
}
|
||||
|
||||
static rt_page_t *_get_page_list(void *vaddr)
|
||||
{
|
||||
rt_ubase_t pa_int = (rt_ubase_t)vaddr + PV_OFFSET;
|
||||
rt_page_t *list;
|
||||
if (pa_int > UINT32_MAX)
|
||||
{
|
||||
list = page_list_high;
|
||||
}
|
||||
else
|
||||
{
|
||||
list = page_list_low;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
int rt_page_ref_get(void *addr, rt_uint32_t size_bits)
|
||||
{
|
||||
struct rt_page *p;
|
||||
rt_base_t level;
|
||||
int ref;
|
||||
|
||||
p = rt_page_addr2page(addr);
|
||||
level = rt_spin_lock_irqsave(&_spinlock);
|
||||
ref = _pages_ref_get(p, size_bits);
|
||||
rt_spin_unlock_irqrestore(&_spinlock, level);
|
||||
return ref;
|
||||
}
|
||||
|
||||
void rt_page_ref_inc(void *addr, rt_uint32_t size_bits)
|
||||
{
|
||||
struct rt_page *p;
|
||||
rt_base_t level;
|
||||
|
||||
p = rt_page_addr2page(addr);
|
||||
level = rt_spin_lock_irqsave(&_spinlock);
|
||||
_pages_ref_inc(p, size_bits);
|
||||
rt_spin_unlock_irqrestore(&_spinlock, level);
|
||||
}
|
||||
|
||||
static rt_page_t (*pages_alloc_handler)(rt_page_t page_list[], rt_uint32_t size_bits);
|
||||
|
||||
/* if not, we skip the finding on page_list_high */
|
||||
static size_t _high_page_configured = 0;
|
||||
|
||||
static rt_page_t *_flag_to_page_list(size_t flags)
|
||||
{
|
||||
rt_page_t *page_list;
|
||||
if (_high_page_configured && (flags & PAGE_ANY_AVAILABLE))
|
||||
{
|
||||
page_list = page_list_high;
|
||||
}
|
||||
else
|
||||
{
|
||||
page_list = page_list_low;
|
||||
}
|
||||
return page_list;
|
||||
}
|
||||
|
||||
rt_inline void *_do_pages_alloc(rt_uint32_t size_bits, size_t flags)
|
||||
{
|
||||
void *alloc_buf = RT_NULL;
|
||||
struct rt_page *p;
|
||||
rt_base_t level;
|
||||
rt_page_t *page_list = _flag_to_page_list(flags);
|
||||
|
||||
level = rt_spin_lock_irqsave(&_spinlock);
|
||||
p = pages_alloc_handler(page_list, size_bits);
|
||||
rt_spin_unlock_irqrestore(&_spinlock, level);
|
||||
|
||||
if (!p && page_list != page_list_low)
|
||||
{
|
||||
/* fall back */
|
||||
page_list = page_list_low;
|
||||
|
||||
level = rt_spin_lock_irqsave(&_spinlock);
|
||||
p = pages_alloc_handler(page_list, size_bits);
|
||||
rt_spin_unlock_irqrestore(&_spinlock, level);
|
||||
}
|
||||
|
||||
if (p)
|
||||
{
|
||||
alloc_buf = page_to_addr(p);
|
||||
|
||||
#ifdef RT_DEBUGING_PAGE_LEAK
|
||||
level = rt_spin_lock_irqsave(&_spinlock);
|
||||
TRACE_ALLOC(p, size_bits);
|
||||
rt_spin_unlock_irqrestore(&_spinlock, level);
|
||||
#endif
|
||||
}
|
||||
return alloc_buf;
|
||||
}
|
||||
|
||||
void *rt_pages_alloc(rt_uint32_t size_bits)
|
||||
{
|
||||
return _do_pages_alloc(size_bits, 0);
|
||||
}
|
||||
|
||||
void *rt_pages_alloc_ext(rt_uint32_t size_bits, size_t flags)
|
||||
{
|
||||
return _do_pages_alloc(size_bits, flags);
|
||||
}
|
||||
|
||||
int rt_pages_free(void *addr, rt_uint32_t size_bits)
|
||||
{
|
||||
struct rt_page *p;
|
||||
rt_page_t *page_list = _get_page_list(addr);
|
||||
int real_free = 0;
|
||||
|
||||
p = rt_page_addr2page(addr);
|
||||
if (p)
|
||||
{
|
||||
rt_base_t level;
|
||||
level = rt_spin_lock_irqsave(&_spinlock);
|
||||
real_free = _pages_free(page_list, p, size_bits);
|
||||
if (real_free)
|
||||
TRACE_FREE(p, size_bits);
|
||||
rt_spin_unlock_irqrestore(&_spinlock, level);
|
||||
}
|
||||
|
||||
return real_free;
|
||||
}
|
||||
|
||||
void rt_page_list(void) __attribute__((alias("list_page")));
|
||||
|
||||
#define PGNR2SIZE(nr) ((nr) * ARCH_PAGE_SIZE / 1024)
|
||||
|
||||
void list_page(void)
|
||||
{
|
||||
int i;
|
||||
rt_size_t free = 0;
|
||||
rt_size_t installed = page_nr;
|
||||
|
||||
rt_base_t level;
|
||||
level = rt_spin_lock_irqsave(&_spinlock);
|
||||
|
||||
for (i = 0; i < RT_PAGE_MAX_ORDER; i++)
|
||||
{
|
||||
struct rt_page *lp = page_list_low[i];
|
||||
struct rt_page *hp = page_list_high[i];
|
||||
|
||||
rt_kprintf("level %d ", i);
|
||||
|
||||
while (lp)
|
||||
{
|
||||
free += (1UL << i);
|
||||
rt_kprintf("[0x%08p]", rt_page_page2addr(lp));
|
||||
lp = lp->next;
|
||||
}
|
||||
while (hp)
|
||||
{
|
||||
free += (1UL << i);
|
||||
rt_kprintf("[0x%08p]", rt_page_page2addr(hp));
|
||||
hp = hp->next;
|
||||
}
|
||||
rt_kprintf("\n");
|
||||
}
|
||||
|
||||
rt_spin_unlock_irqrestore(&_spinlock, level);
|
||||
rt_kprintf("-------------------------------\n");
|
||||
rt_kprintf("Page Summary:\n => free/installed: 0x%lx/0x%lx (%ld/%ld KB)\n", free, installed, PGNR2SIZE(free), PGNR2SIZE(installed));
|
||||
rt_kprintf("-------------------------------\n");
|
||||
}
|
||||
MSH_CMD_EXPORT(list_page, show page info);
|
||||
|
||||
void rt_page_get_info(rt_size_t *total_nr, rt_size_t *free_nr)
|
||||
{
|
||||
int i;
|
||||
rt_size_t total_free = 0;
|
||||
rt_base_t level;
|
||||
|
||||
level = rt_spin_lock_irqsave(&_spinlock);
|
||||
for (i = 0; i < RT_PAGE_MAX_ORDER; i++)
|
||||
{
|
||||
struct rt_page *p = page_list_low[i];
|
||||
|
||||
while (p)
|
||||
{
|
||||
total_free += (1UL << i);
|
||||
p = p->next;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < RT_PAGE_MAX_ORDER; i++)
|
||||
{
|
||||
struct rt_page *p = page_list_high[i];
|
||||
|
||||
while (p)
|
||||
{
|
||||
total_free += (1UL << i);
|
||||
p = p->next;
|
||||
}
|
||||
}
|
||||
rt_spin_unlock_irqrestore(&_spinlock, level);
|
||||
*total_nr = page_nr;
|
||||
*free_nr = total_free;
|
||||
}
|
||||
|
||||
void rt_page_high_get_info(rt_size_t *total_nr, rt_size_t *free_nr)
|
||||
{
|
||||
int i;
|
||||
rt_size_t total_free = 0;
|
||||
rt_base_t level;
|
||||
|
||||
level = rt_spin_lock_irqsave(&_spinlock);
|
||||
for (i = 0; i < RT_PAGE_MAX_ORDER; i++)
|
||||
{
|
||||
struct rt_page *p = page_list_high[i];
|
||||
|
||||
while (p)
|
||||
{
|
||||
total_free += (1UL << i);
|
||||
p = p->next;
|
||||
}
|
||||
}
|
||||
rt_spin_unlock_irqrestore(&_spinlock, level);
|
||||
*total_nr = _high_pages_nr;
|
||||
*free_nr = total_free;
|
||||
}
|
||||
|
||||
static void _install_page(rt_page_t mpr_head, rt_region_t region, void *insert_handler)
|
||||
{
|
||||
void (*insert)(rt_page_t *page_list, rt_page_t page, int size_bits) = insert_handler;
|
||||
rt_region_t shadow;
|
||||
shadow.start = region.start & ~shadow_mask;
|
||||
shadow.end = FLOOR(region.end, shadow_mask + 1);
|
||||
|
||||
if (shadow.end + PV_OFFSET > UINT32_MAX)
|
||||
_high_page_configured = 1;
|
||||
|
||||
rt_page_t shad_head = addr_to_page(mpr_head, (void *)shadow.start);
|
||||
rt_page_t shad_tail = addr_to_page(mpr_head, (void *)shadow.end);
|
||||
rt_page_t head = addr_to_page(mpr_head, (void *)region.start);
|
||||
rt_page_t tail = addr_to_page(mpr_head, (void *)region.end);
|
||||
|
||||
/* mark shadow pages as illegal */
|
||||
for (rt_page_t iter = shad_head; iter < head; iter++)
|
||||
{
|
||||
iter->size_bits = ARCH_ADDRESS_WIDTH_BITS;
|
||||
}
|
||||
for (rt_page_t iter = tail; iter < shad_tail; iter++)
|
||||
{
|
||||
iter->size_bits = ARCH_ADDRESS_WIDTH_BITS;
|
||||
}
|
||||
|
||||
/* insert reserved pages to list */
|
||||
const int max_order = RT_PAGE_MAX_ORDER + ARCH_PAGE_SHIFT - 1;
|
||||
while (region.start != region.end)
|
||||
{
|
||||
struct rt_page *p;
|
||||
int align_bits;
|
||||
int size_bits;
|
||||
|
||||
size_bits =
|
||||
ARCH_ADDRESS_WIDTH_BITS - 1 - rt_hw_clz(region.end - region.start);
|
||||
align_bits = rt_hw_ctz(region.start);
|
||||
if (align_bits < size_bits)
|
||||
{
|
||||
size_bits = align_bits;
|
||||
}
|
||||
if (size_bits > max_order)
|
||||
{
|
||||
size_bits = max_order;
|
||||
}
|
||||
|
||||
p = addr_to_page(mpr_head, (void *)region.start);
|
||||
p->size_bits = ARCH_ADDRESS_WIDTH_BITS;
|
||||
p->ref_cnt = 0;
|
||||
|
||||
/* insert to list */
|
||||
rt_page_t *page_list = _get_page_list((void *)region.start);
|
||||
if (page_list == page_list_high)
|
||||
{
|
||||
_high_pages_nr += 1 << (size_bits - ARCH_PAGE_SHIFT);
|
||||
}
|
||||
insert(page_list, (rt_page_t)((char *)p - early_offset), size_bits - ARCH_PAGE_SHIFT);
|
||||
region.start += (1UL << size_bits);
|
||||
}
|
||||
}
|
||||
|
||||
void rt_page_init(rt_region_t reg)
|
||||
{
|
||||
int i;
|
||||
rt_region_t shadow;
|
||||
|
||||
/* inclusive start, exclusive end */
|
||||
reg.start += ARCH_PAGE_MASK;
|
||||
reg.start &= ~ARCH_PAGE_MASK;
|
||||
reg.end &= ~ARCH_PAGE_MASK;
|
||||
if (reg.end <= reg.start)
|
||||
{
|
||||
LOG_E("region end(%p) must greater than start(%p)", reg.start, reg.end);
|
||||
RT_ASSERT(0);
|
||||
}
|
||||
page_nr = ((reg.end - reg.start) >> ARCH_PAGE_SHIFT);
|
||||
shadow.start = reg.start & ~shadow_mask;
|
||||
shadow.end = FLOOR(reg.end, shadow_mask + 1);
|
||||
LOG_D("[Init page] start: 0x%lx, end: 0x%lx, total: 0x%lx", reg.start,
|
||||
reg.end, page_nr);
|
||||
|
||||
int err;
|
||||
|
||||
/* init free list */
|
||||
for (i = 0; i < RT_PAGE_MAX_ORDER; i++)
|
||||
{
|
||||
page_list_low[i] = 0;
|
||||
page_list_high[i] = 0;
|
||||
}
|
||||
|
||||
/* map MPR area */
|
||||
err = rt_aspace_map_static(&rt_kernel_space, &mpr_varea, &rt_mpr_start,
|
||||
rt_mpr_size, MMU_MAP_K_RWCB, MMF_MAP_FIXED,
|
||||
&mm_page_mapper, 0);
|
||||
|
||||
if (err != RT_EOK)
|
||||
{
|
||||
LOG_E("MPR map failed with size %lx at %p", rt_mpr_size, rt_mpr_start);
|
||||
RT_ASSERT(0);
|
||||
}
|
||||
|
||||
/* calculate footprint */
|
||||
init_mpr_align_start =
|
||||
(rt_size_t)addr_to_page(page_start, (void *)shadow.start) &
|
||||
~ARCH_PAGE_MASK;
|
||||
init_mpr_align_end =
|
||||
FLOOR(addr_to_page(page_start, (void *)shadow.end), ARCH_PAGE_SIZE);
|
||||
rt_size_t init_mpr_size = init_mpr_align_end - init_mpr_align_start;
|
||||
rt_size_t init_mpr_npage = init_mpr_size >> ARCH_PAGE_SHIFT;
|
||||
|
||||
init_mpr_cont_start = (void *)reg.start;
|
||||
rt_size_t init_mpr_cont_end = (rt_size_t)init_mpr_cont_start + init_mpr_size;
|
||||
early_offset = (rt_size_t)init_mpr_cont_start - init_mpr_align_start;
|
||||
rt_page_t mpr_cont = (void *)((char *)rt_mpr_start + early_offset);
|
||||
|
||||
/* mark init mpr pages as illegal */
|
||||
rt_page_t head_cont = addr_to_page(mpr_cont, (void *)reg.start);
|
||||
rt_page_t tail_cont = addr_to_page(mpr_cont, (void *)reg.end);
|
||||
for (rt_page_t iter = head_cont; iter < tail_cont; iter++)
|
||||
{
|
||||
iter->size_bits = ARCH_ADDRESS_WIDTH_BITS;
|
||||
}
|
||||
|
||||
reg.start = init_mpr_cont_end;
|
||||
_install_page(mpr_cont, reg, _early_page_insert);
|
||||
|
||||
pages_alloc_handler = _early_pages_alloc;
|
||||
/* doing the page table bushiness */
|
||||
if (rt_aspace_load_page(&rt_kernel_space, (void *)init_mpr_align_start, init_mpr_npage))
|
||||
{
|
||||
LOG_E("%s: failed to load pages", __func__);
|
||||
RT_ASSERT(0);
|
||||
}
|
||||
|
||||
if (rt_hw_mmu_tbl_get() == rt_kernel_space.page_table)
|
||||
rt_page_cleanup();
|
||||
}
|
||||
|
||||
static int _load_mpr_area(void *head, void *tail)
|
||||
{
|
||||
int err = 0;
|
||||
char *iter = (char *)((rt_ubase_t)head & ~ARCH_PAGE_MASK);
|
||||
tail = (void *)FLOOR(tail, ARCH_PAGE_SIZE);
|
||||
|
||||
while (iter != tail)
|
||||
{
|
||||
void *paddr = rt_kmem_v2p(iter);
|
||||
if (paddr == ARCH_MAP_FAILED)
|
||||
{
|
||||
err = rt_aspace_load_page(&rt_kernel_space, iter, 1);
|
||||
if (err != RT_EOK)
|
||||
{
|
||||
LOG_E("%s: failed to load page", __func__);
|
||||
break;
|
||||
}
|
||||
}
|
||||
iter += ARCH_PAGE_SIZE;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
int rt_page_install(rt_region_t region)
|
||||
{
|
||||
int err = -RT_EINVAL;
|
||||
rt_region_t shadow;
|
||||
void *head, *tail;
|
||||
|
||||
if (region.end != region.start && !(region.start & ARCH_PAGE_MASK) &&
|
||||
!(region.end & ARCH_PAGE_MASK))
|
||||
{
|
||||
shadow.start = region.start & ~shadow_mask;
|
||||
shadow.end = FLOOR(region.end, shadow_mask + 1);
|
||||
head = addr_to_page(page_start, (void *)shadow.start);
|
||||
tail = addr_to_page(page_start, (void *)shadow.end);
|
||||
|
||||
page_nr += ((region.end - region.start) >> ARCH_PAGE_SHIFT);
|
||||
|
||||
err = _load_mpr_area(head, tail);
|
||||
|
||||
if (err == RT_EOK)
|
||||
{
|
||||
_install_page(rt_mpr_start, region, _page_insert);
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void rt_page_cleanup(void)
|
||||
{
|
||||
early_offset = 0;
|
||||
pages_alloc_handler = _pages_alloc;
|
||||
}
|
115
rt-thread/components/mm/mm_page.h
Normal file
115
rt-thread/components/mm/mm_page.h
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2019, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2019-11-01 Jesven The first version
|
||||
* 2022-12-13 WangXiaoyao Hot-pluggable, extensible
|
||||
* page management algorithm
|
||||
*/
|
||||
#ifndef __MM_PAGE_H__
|
||||
#define __MM_PAGE_H__
|
||||
|
||||
#include <rthw.h>
|
||||
#include <rtthread.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define GET_FLOOR(type) \
|
||||
(1ul << (8 * sizeof(rt_size_t) - __builtin_clzl(2 * sizeof(type) - 1) - 1))
|
||||
#define DEF_PAGE_T(fields) \
|
||||
typedef struct rt_page {\
|
||||
union {struct {fields}; char _padding[GET_FLOOR(struct {fields})];};\
|
||||
} *rt_page_t
|
||||
|
||||
/**
|
||||
* @brief PAGE ALLOC FLAGS
|
||||
*
|
||||
* @info PAGE_ANY_AVAILABLE
|
||||
* page allocation default to use lower region, this behavior can change by setting
|
||||
* PAGE_ANY_AVAILABLE
|
||||
*/
|
||||
|
||||
#define PAGE_ANY_AVAILABLE 0x1ul
|
||||
|
||||
#ifdef RT_DEBUGING_PAGE_LEAK
|
||||
#define DEBUG_FIELD struct { \
|
||||
/* trace list */ \
|
||||
struct rt_page *tl_next; \
|
||||
struct rt_page *tl_prev; \
|
||||
void *caller; \
|
||||
size_t trace_size; \
|
||||
}
|
||||
#else
|
||||
#define DEBUG_FIELD
|
||||
#endif
|
||||
|
||||
DEF_PAGE_T(
|
||||
struct rt_page *next; /* same level next */
|
||||
struct rt_page *pre; /* same level pre */
|
||||
|
||||
DEBUG_FIELD;
|
||||
|
||||
rt_uint32_t size_bits; /* if is ARCH_ADDRESS_WIDTH_BITS, means not free */
|
||||
rt_uint32_t ref_cnt; /* page group ref count */
|
||||
);
|
||||
|
||||
#undef GET_FLOOR
|
||||
#undef DEF_PAGE_T
|
||||
|
||||
typedef struct tag_region
|
||||
{
|
||||
rt_size_t start;
|
||||
rt_size_t end;
|
||||
|
||||
const char *name;
|
||||
} rt_region_t;
|
||||
|
||||
extern const rt_size_t rt_mpr_size;
|
||||
extern void *rt_mpr_start;
|
||||
|
||||
void rt_page_init(rt_region_t reg);
|
||||
|
||||
void rt_page_cleanup(void);
|
||||
|
||||
void *rt_pages_alloc(rt_uint32_t size_bits);
|
||||
|
||||
void *rt_pages_alloc_ext(rt_uint32_t size_bits, size_t flags);
|
||||
|
||||
void rt_page_ref_inc(void *addr, rt_uint32_t size_bits);
|
||||
|
||||
int rt_page_ref_get(void *addr, rt_uint32_t size_bits);
|
||||
|
||||
int rt_pages_free(void *addr, rt_uint32_t size_bits);
|
||||
|
||||
void rt_page_list(void);
|
||||
|
||||
rt_size_t rt_page_bits(rt_size_t size);
|
||||
|
||||
void rt_page_get_info(rt_size_t *total_nr, rt_size_t *free_nr);
|
||||
|
||||
void rt_page_high_get_info(rt_size_t *total_nr, rt_size_t *free_nr);
|
||||
|
||||
void *rt_page_page2addr(struct rt_page *p);
|
||||
|
||||
struct rt_page *rt_page_addr2page(void *addr);
|
||||
|
||||
/**
|
||||
* @brief Install page frames at run-time
|
||||
* Region size must be aligned to 2^(RT_PAGE_MAX_ORDER + ARCH_PAGE_SHIFT - 1)
|
||||
* bytes currently (typically 2 MB).
|
||||
*
|
||||
* !WARNING this API will NOT check whether region is valid or not in list
|
||||
*
|
||||
* @param region region.start as first page frame(inclusive),
|
||||
* region.end as first page frame after free region
|
||||
* @return int 0 on success
|
||||
*/
|
||||
int rt_page_install(rt_region_t region);
|
||||
|
||||
void rt_page_leak_trace_start(void);
|
||||
|
||||
void rt_page_leak_trace_stop(void);
|
||||
|
||||
#endif /* __MM_PAGE_H__ */
|
135
rt-thread/components/mm/mm_private.h
Normal file
135
rt-thread/components/mm/mm_private.h
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2022, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2022-11-14 WangXiaoyao the first version
|
||||
*/
|
||||
#ifndef __MM_PRIVATE_H__
|
||||
#define __MM_PRIVATE_H__
|
||||
|
||||
#include "mm_aspace.h"
|
||||
#include "mm_fault.h"
|
||||
#include "mm_flag.h"
|
||||
#include "mm_page.h"
|
||||
|
||||
#include <rtdef.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* @brief DATA STRUCTURE & API USED INTERNALLY
|
||||
*
|
||||
* This is mainly a wrapper layer to actual data structure.
|
||||
* In this way, we can switch to any BST we like by adding new
|
||||
* wrapper code.
|
||||
* Every BST must satisfy the API to support MM
|
||||
*
|
||||
* *INFO: varea range convention
|
||||
* For API, a range is specified by a base and its length.
|
||||
* This provides a clear interface without ambiguity.
|
||||
* For implementation, a range is specified by [start, end] tuple
|
||||
* where both start and end are inclusive.
|
||||
*/
|
||||
|
||||
#define VAREA_NOT_STATIC(varea) (!((varea)->flag & MMF_STATIC_ALLOC))
|
||||
#define VAREA_NAME(varea) \
|
||||
((!varea->mem_obj || !varea->mem_obj->get_name) \
|
||||
? "unknow" \
|
||||
: varea->mem_obj->get_name(varea))
|
||||
|
||||
/* only user address use COW technique, so user permission is always checked */
|
||||
#define VAREA_IS_WRITABLE(varea) \
|
||||
(rt_hw_mmu_attr_test_perm(varea->attr, \
|
||||
RT_HW_MMU_PROT_USER | RT_HW_MMU_PROT_WRITE))
|
||||
#define VAREA_VA_TO_OFFSET(varea, va) \
|
||||
((varea)->offset + MM_PA_TO_OFF((long)(va) - (long)(varea)->start))
|
||||
|
||||
struct _mm_range
|
||||
{
|
||||
void *start;
|
||||
void *end;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param aspace
|
||||
* @return rt_err_t
|
||||
*/
|
||||
rt_err_t _aspace_bst_init(struct rt_aspace *aspace);
|
||||
|
||||
/**
|
||||
* @brief Retrieve any varea if start in [varea->start, varea->end]
|
||||
*
|
||||
* @param aspace
|
||||
* @param start
|
||||
* @return struct rt_varea*
|
||||
*/
|
||||
struct rt_varea *_aspace_bst_search(struct rt_aspace *aspace, void *start);
|
||||
|
||||
/**
|
||||
* @brief Retrieve lowest varea satisfies (varea->start >= start)
|
||||
*
|
||||
* @param aspace
|
||||
* @param length
|
||||
* @param struct _mm_range
|
||||
* @return struct rt_varea*
|
||||
*/
|
||||
struct rt_varea *_aspace_bst_search_exceed(struct rt_aspace *aspace,
|
||||
void *start);
|
||||
|
||||
/**
|
||||
* @brief Retrieve any varea overlaps a specified address range
|
||||
*
|
||||
* @param aspace
|
||||
* @param start
|
||||
* @param length
|
||||
* @return struct rt_varea*
|
||||
*/
|
||||
struct rt_varea *_aspace_bst_search_overlap(struct rt_aspace *aspace,
|
||||
struct _mm_range range);
|
||||
|
||||
/**
|
||||
* @brief Insert a varea into the bst
|
||||
*
|
||||
* @param aspace
|
||||
* @param varea
|
||||
*/
|
||||
void _aspace_bst_insert(struct rt_aspace *aspace, struct rt_varea *varea);
|
||||
|
||||
/**
|
||||
* @brief Remove a varea from the bst
|
||||
*
|
||||
* @param aspace
|
||||
* @param varea
|
||||
*/
|
||||
void _aspace_bst_remove(struct rt_aspace *aspace, struct rt_varea *varea);
|
||||
|
||||
int rt_varea_fix_private_locked(rt_varea_t ex_varea, void *pa,
|
||||
struct rt_aspace_fault_msg *msg,
|
||||
rt_bool_t dont_copy);
|
||||
|
||||
int rt_varea_map_with_msg(rt_varea_t varea, struct rt_aspace_fault_msg *msg);
|
||||
|
||||
void _varea_uninstall_locked(rt_varea_t varea);
|
||||
|
||||
int _mm_aspace_map(rt_aspace_t aspace, rt_varea_t *pvarea, void **addr,
|
||||
rt_size_t length, rt_size_t attr, mm_flag_t flags,
|
||||
rt_mem_obj_t mem_obj, rt_size_t offset);
|
||||
|
||||
rt_inline rt_bool_t rt_varea_is_private_locked(rt_varea_t varea)
|
||||
{
|
||||
rt_base_t flags = varea->flag;
|
||||
return !!(
|
||||
(flags & (MMF_MAP_PRIVATE | MMF_MAP_PRIVATE_DONT_SYNC))
|
||||
&& (varea->aspace->private_object != varea->mem_obj)
|
||||
);
|
||||
}
|
||||
|
||||
rt_err_t rt_aspace_anon_ref_dec(rt_mem_obj_t aobj);
|
||||
rt_err_t rt_aspace_page_get_phy(rt_aspace_t aspace, void *page_va, void *buffer);
|
||||
rt_err_t rt_aspace_page_put_phy(rt_aspace_t aspace, void *page_va, void *buffer);
|
||||
|
||||
#endif /* __MM_PRIVATE_H__ */
|
Reference in New Issue
Block a user