This commit is contained in:
2024-08-05 20:57:09 +08:00
commit 46d9ee7795
3020 changed files with 1725767 additions and 0 deletions

View 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

View 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')

View 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);
}

View 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__ */

View 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);
}

View 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__*/

View 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;
}

File diff suppressed because it is too large Load Diff

View 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__ */

View 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 */

View 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__ */

View 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__ */

View 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;
}

View 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, &reg.start, &reg.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 */

View 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__ */

View 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,
};

View 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;
}

View 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__ */

View 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__ */