原始版本

This commit is contained in:
冯佳
2025-06-19 21:56:46 +08:00
parent fe98e5f010
commit a4841450cf
4152 changed files with 1910684 additions and 0 deletions

View File

@ -0,0 +1,52 @@
menu "Memory management"
config RT_PAGE_AFFINITY_BLOCK_SIZE
hex "Affinity block size in bytes for page management"
default 0x1000
help
Page affinity block can be used to resolve the VIPT aliasing problem.
It should be set to `1ul << ((index + block) - page_offset)` in this case.
You could also exploit this as a tunning for cache coloring.
config RT_PAGE_MAX_ORDER
int "Max order of pages allocatable by page allocator"
default 11
depends on ARCH_MM_MMU
help
For example, A value of 11 means the maximum chunk of contiguous memory
allocatable by page system is 2^(11 + ARCH_PAGE_BITS - 1) Bytes.
Large memory requirement can consume all system resource, and should
consider reserved memory instead to enhance system endurance.
Max order should at least satisfied usage by huge page.
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.
menu "Debugging"
config RT_DEBUGGING_ALIASING
bool "Using aliasing paging debugger"
default n
config RT_DEBUGING_PAGE_LEAK
bool "Using page leaking tracer"
default n
config RT_DEBUGGING_PAGE_POISON
bool "Using page poisoner to detect illegal usage"
default n
endmenu
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,116 @@
/*
* 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;
size_t pa_off = (rt_ubase_t)paddr & ~(RT_PAGE_AFFINITY_BLOCK_SIZE - 1);
lo_off = (rt_ubase_t)paddr - pa_off;
pa_off = MM_PA_TO_OFF(pa_off);
struct rt_mm_va_hint hint = {
.prefer = RT_NULL,
.map_size = RT_ALIGN(size + lo_off, RT_PAGE_AFFINITY_BLOCK_SIZE),
.flags = MMF_CREATE(0, RT_PAGE_AFFINITY_BLOCK_SIZE),
.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, pa_off, (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_varea_unmap_page(varea, iter);
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_tagged(0, RT_PAGE_PICK_AFFID(fault_vaddr), 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,208 @@
/*
* 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;
default:
LOG_D("Unhandle exception");
break;
}
}
}
else
{
LOG_W("%s: varea not found at 0x%lx", __func__, msg->fault_vaddr);
}
RD_UNLOCK(aspace);
}
else
{
LOG_W("No aspace found");
}
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,426 @@
/*
* 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))
#ifdef ARCH_CPU_64BIT
#define MIN_BIT 16
#else
#define MIN_BIT 8
#endif
#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 [0x%.*lx-0x%.*lx) with flag 0x%x" \
" to overall memory regions\n", MIN_BIT, base, MIN_BIT, 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 %s [0x%.*lx-0x%.*lx) to reserved memory regions\n",
name, MIN_BIT, start, MIN_BIT, end);
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 [0x%.*lx, 0x%.*lx]", RT_NAME_MAX, iter->memreg.name, MIN_BIT, iter->memreg.start, MIN_BIT, iter->memreg.end);
}
LOG_I("Reserved memory:");
rt_slist_for_each_entry(iter, &(mmblk_reserved.reg_list), node)
{
LOG_I(" %-*.s [0x%.*lx, 0x%.*lx]", RT_NAME_MAX, iter->memreg.name, MIN_BIT, iter->memreg.start, MIN_BIT, 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,122 @@
/*
* 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;
int affid = RT_PAGE_PICK_AFFID(msg->fault_vaddr);
page = rt_pages_alloc_tagged(0, affid, 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,
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,123 @@
/*
* 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
#define RT_PAGE_PICK_AFFID(ptr) \
((((long)ptr) & (RT_PAGE_AFFINITY_BLOCK_SIZE - 1)) / ARCH_PAGE_SIZE)
#ifdef RT_DEBUGGING_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
#undef DEBUG_FIELD
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_pages_alloc_tagged(rt_uint32_t size_bits, long tag, size_t flags);
rt_bool_t rt_page_is_member(rt_base_t page_pa);
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);
int 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__ */