diff options
| author | geniusgogo <[email protected]> | 2023-10-17 13:07:59 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-10-17 13:07:59 +0800 |
| commit | ecd29fda6005ef19c625f194f9311259ba907a78 (patch) | |
| tree | d0e272f5783ac4a3048238e14a37bcd88b5dc3e6 /components/mm | |
| parent | 7d64cdcf58969b1b2b7e9cfb3aac9e46cb456ae3 (diff) | |
Sync dfs lwp (#8123)
Diffstat (limited to 'components/mm')
| -rw-r--r-- | components/mm/SConscript | 10 | ||||
| -rw-r--r-- | components/mm/mm_anon.c | 620 | ||||
| -rw-r--r-- | components/mm/mm_aspace.c | 984 | ||||
| -rw-r--r-- | components/mm/mm_aspace.h | 104 | ||||
| -rw-r--r-- | components/mm/mm_fault.c | 91 | ||||
| -rw-r--r-- | components/mm/mm_fault.h | 69 | ||||
| -rw-r--r-- | components/mm/mm_flag.h | 15 | ||||
| -rw-r--r-- | components/mm/mm_kmem.c | 28 | ||||
| -rw-r--r-- | components/mm/mm_object.c | 206 | ||||
| -rw-r--r-- | components/mm/mm_page.c | 56 | ||||
| -rw-r--r-- | components/mm/mm_page.h | 4 | ||||
| -rw-r--r-- | components/mm/mm_private.h | 40 |
12 files changed, 2032 insertions, 195 deletions
diff --git a/components/mm/SConscript b/components/mm/SConscript index e42fa9f0c8..9c76dd8b41 100644 --- a/components/mm/SConscript +++ b/components/mm/SConscript @@ -1,14 +1,14 @@ import os from building import * -objs = [] -src = [] +objs = [] if GetDepend('ARCH_ARM_CORTEX_A') or GetDepend('ARCH_ARMV8') or GetDepend('ARCH_RISCV64'): cwd = GetCurrentDir() - src += ['avl_adpt.c', 'ioremap.c', 'mm_aspace.c', 'mm_fault.c', 'mm_kmem.c', 'mm_object.c', 'mm_page.c'] - if GetDepend('RT_USING_MEMBLOCK'): - src += ['mm_memblock.c'] + 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) diff --git a/components/mm/mm_anon.c b/components/mm/mm_anon.c new file mode 100644 index 0000000000..e14300a08f --- /dev/null +++ b/components/mm/mm_anon.c @@ -0,0 +1,620 @@ +/* + * 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; +} + +static const char *_anon_get_name(rt_varea_t varea) +{ + return varea->aspace == _anon_obj_get_backup(varea->mem_obj) ? "anonymous" : "reference"; +} + +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); +} + +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) +{ + return rt_mm_dummy_mapper.on_varea_shrink(varea, new_start, size); +} + +static rt_err_t _anon_varea_split(struct rt_varea *existed, void *unmap_start, rt_size_t unmap_len, struct rt_varea *subset) +{ + _anon_varea_open(subset); + return rt_mm_dummy_mapper.on_varea_split(existed, unmap_start, unmap_len, subset); +} + +static rt_err_t _anon_varea_merge(struct rt_varea *merge_to, struct rt_varea *merge_from) +{ + _anon_varea_close(merge_from); + return rt_mm_dummy_mapper.on_varea_merge(merge_to, merge_from); +} + +rt_inline void _map_page_in_varea(rt_aspace_t asapce, rt_varea_t varea, + struct rt_aspace_fault_msg *msg, char *fault_addr) +{ + if (rt_varea_map_page(varea, fault_addr, msg->response.vaddr) == RT_EOK) + { + msg->response.status = MM_FAULT_STATUS_OK_MAPPED; + } + else + { + /* revoke the allocated page */ + rt_varea_pgmgr_pop(varea, msg->response.vaddr, ARCH_PAGE_SIZE); + msg->response.status = MM_FAULT_STATUS_UNRECOVERABLE; + LOG_W("%s: failed to map page into varea", __func__); + } +} + +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; + } + } + } + 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 _anon_page_fault(struct rt_varea *varea, struct rt_aspace_fault_msg *msg) +{ + void *paddr; + char *frame_ka; + rt_aspace_t from_aspace = varea->aspace; + rt_aspace_t backup = _anon_obj_get_backup(varea->mem_obj); + + RDWR_LOCK(from_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(from_aspace, msg->fault_vaddr); + if (paddr == ARCH_MAP_FAILED) + { + if (backup == from_aspace) + { + rt_mm_dummy_mapper.on_page_fault(varea, msg); + if (msg->response.status != MM_FAULT_STATUS_UNRECOVERABLE) + { + _map_page_in_varea(backup, varea, msg, msg->fault_vaddr); + } + } + else + { + frame_ka = _get_page_from_backup(backup, msg->off); + if (frame_ka) + { + msg->response.vaddr = frame_ka; + msg->response.size = ARCH_PAGE_SIZE; + _map_page_in_varea(from_aspace, varea, msg, msg->fault_vaddr); + } + } + } + else + { + msg->response.status = MM_FAULT_STATUS_OK_MAPPED; + } + RDWR_UNLOCK(from_aspace); +} + +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 from_aspace = varea->aspace; + + 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_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); + + _anon_page_fault(varea, &msg); + if (msg.response.status != MM_FAULT_STATUS_UNRECOVERABLE) + { + read_by_mte(from_aspace, iomsg); + } + } + else + { + read_by_mte(from_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; + + 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); + + _anon_page_fault(varea, &msg); + if (msg.response.status != MM_FAULT_STATUS_UNRECOVERABLE) + { + write_by_mte(from_aspace, iomsg); + } + } + else + { + write_by_mte(from_aspace, 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); + } + 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; +} diff --git a/components/mm/mm_aspace.c b/components/mm/mm_aspace.c index 6249e44ac3..42278e684f 100644 --- a/components/mm/mm_aspace.c +++ b/components/mm/mm_aspace.c @@ -1,40 +1,41 @@ /* - * Copyright (c) 2006-2022, RT-Thread Development Team + * 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 + * Support MAP_FIXED in aspace_map(), and + * Add better support of permission in mmap */ /** * @brief Virtual Address Space */ -#include <rtdef.h> #include <rtthread.h> -#include <stddef.h> -#include <stdint.h> #define DBG_TAG "mm.aspace" #define DBG_LVL DBG_INFO #include <rtdbg.h> #include "avl_adpt.h" -#include "mm_aspace.h" -#include "mm_fault.h" -#include "mm_flag.h" -#include "mm_page.h" #include "mm_private.h" #include <mmu.h> #include <tlb.h> +#include <stddef.h> +#include <stdint.h> + +#define ALIGNED(addr) (!((rt_size_t)(addr) & ARCH_PAGE_MASK)) + static void *_find_free(rt_aspace_t aspace, void *prefer, rt_size_t req_size, void *limit_start, rt_size_t limit_size, mm_flag_t flags); -static void _varea_uninstall(rt_varea_t varea); +static int _unmap_range_locked(rt_aspace_t aspace, void *addr, size_t length); struct rt_aspace rt_kernel_space; @@ -56,6 +57,7 @@ rt_err_t rt_aspace_init(rt_aspace_t aspace, void *start, rt_size_t length, void aspace->page_table = pgtbl; aspace->start = start; aspace->size = length; + aspace->private_object = RT_NULL; err = _aspace_bst_init(aspace); if (err == RT_EOK) @@ -102,21 +104,25 @@ rt_aspace_t rt_aspace_create(void *start, rt_size_t length, void *pgtbl) void rt_aspace_detach(rt_aspace_t aspace) { + rt_varea_t varea; + WR_LOCK(aspace); - rt_varea_t varea = ASPACE_VAREA_FIRST(aspace); + varea = ASPACE_VAREA_FIRST(aspace); while (varea) { rt_varea_t prev = varea; - _varea_uninstall(varea); - varea = ASPACE_VAREA_NEXT(varea); - if (!(prev->flag & MMF_STATIC_ALLOC)) + + _varea_uninstall_locked(prev); + if (VAREA_NOT_STATIC(prev)) { rt_free(prev); } } WR_UNLOCK(aspace); + rt_aspace_anon_ref_dec(aspace->private_object); + rt_mutex_detach(&aspace->bst_lock); } @@ -127,16 +133,29 @@ void rt_aspace_delete(rt_aspace_t aspace) rt_free(aspace); } -static int _do_named_map(rt_aspace_t aspace, void *vaddr, rt_size_t length, - rt_size_t offset, rt_size_t attr) +rt_inline rt_size_t _get_effect_attr(rt_aspace_t aspace, rt_varea_t varea) +{ + rt_size_t attr = varea->attr; + + /* not write permission for user on private mapping */ + if (rt_varea_is_private_locked(varea)) + attr = rt_hw_mmu_attr_rm_perm(attr, RT_HW_MMU_PROT_USER | RT_HW_MMU_PROT_WRITE); + + return attr; +} + +static int _do_named_map(rt_aspace_t aspace, rt_varea_t varea, void *vaddr, + rt_size_t length, rt_size_t offset, rt_size_t attr) { LOG_D("%s: va %p length %p", __func__, vaddr, length); int err = RT_EOK; /* it's ensured by caller that (void*)end will not overflow */ void *phyaddr = (void *)(offset << MM_PAGE_SHIFT); + void *ret; - void *ret = rt_hw_mmu_map(aspace, vaddr, phyaddr, length, attr); + attr = _get_effect_attr(aspace, varea); + ret = rt_hw_mmu_map(aspace, vaddr, phyaddr, length, attr); if (ret == RT_NULL) { err = -RT_ERROR; @@ -160,11 +179,13 @@ rt_inline void _do_page_fault(struct rt_aspace_fault_msg *msg, rt_size_t off, msg->response.vaddr = 0; msg->response.size = 0; + RT_ASSERT(mem_obj->on_page_fault); mem_obj->on_page_fault(varea, msg); } -int _varea_map_with_msg(rt_varea_t varea, struct rt_aspace_fault_msg *msg) +int rt_varea_map_with_msg(rt_varea_t varea, struct rt_aspace_fault_msg *msg) { + rt_aspace_t aspace; int err = -RT_ERROR; if (msg->response.status == MM_FAULT_STATUS_OK) { @@ -182,9 +203,15 @@ int _varea_map_with_msg(rt_varea_t varea, struct rt_aspace_fault_msg *msg) else { void *map; + rt_size_t attr; void *v_addr = msg->fault_vaddr; void *p_addr = store + PV_OFFSET; - map = rt_hw_mmu_map(varea->aspace, v_addr, p_addr, store_sz, varea->attr); + + aspace = varea->aspace; + RT_ASSERT(aspace); + + attr = _get_effect_attr(aspace, varea); + map = rt_hw_mmu_map(aspace, v_addr, p_addr, store_sz, attr); if (!map) { @@ -202,12 +229,9 @@ int _varea_map_with_msg(rt_varea_t varea, struct rt_aspace_fault_msg *msg) { if (rt_hw_mmu_v2p(varea->aspace, msg->fault_vaddr) == ARCH_MAP_FAILED) { - LOG_W("%s: no page is mapped on %p", __func__, msg->fault_vaddr); - } - else - { - err = RT_EOK; + LOG_D("%s: no page is mapped on %p", __func__, msg->fault_vaddr); } + err = RT_EOK; } else { @@ -234,7 +258,7 @@ static int _do_prefetch(rt_aspace_t aspace, rt_varea_t varea, void *start, struct rt_aspace_fault_msg msg; _do_page_fault(&msg, off, vaddr, varea->mem_obj, varea); - if (_varea_map_with_msg(varea, &msg)) + if (rt_varea_map_with_msg(varea, &msg)) { err = -RT_ENOMEM; break; @@ -253,30 +277,210 @@ static int _do_prefetch(rt_aspace_t aspace, rt_varea_t varea, void *start, return err; } -/* caller must hold the aspace lock */ -static int _varea_install(rt_aspace_t aspace, rt_varea_t varea, rt_mm_va_hint_t hint) +static rt_err_t _expand_varea(rt_varea_t varea, void *new_va, rt_size_t size) { - void *alloc_va; - int err = RT_EOK; + rt_err_t error; + rt_aspace_t aspace; + void *old_va; - /** - * find a suitable va range. - * even though this is sleepable, it's still ok for startup routine - */ - alloc_va = - _find_free(aspace, hint->prefer, hint->map_size, hint->limit_start, - hint->limit_range_size, hint->flags); + if (varea->mem_obj && varea->mem_obj->on_varea_expand) + error = varea->mem_obj->on_varea_expand(varea, new_va, size); + else + error = -RT_EPERM; - /* TODO try merge surrounding regions to optimize memory footprint */ + if (error == RT_EOK) + { + aspace = varea->aspace; + old_va = varea->start; + varea->size = size; - if (alloc_va != RT_NULL) + if (old_va != new_va) + { + varea->start = new_va; + varea->offset += ((long)new_va - (long)old_va) >> MM_PAGE_SHIFT; + _aspace_bst_remove(aspace, varea); + _aspace_bst_insert(aspace, varea); + } + } + return error; +} + +struct _mapping_property { + rt_size_t attr; + rt_size_t flags; + rt_size_t offset; + struct rt_mem_obj *mem_obj; +}; + +#define INIT_PROP(obj,off,fl,attr) \ + {.mem_obj = (obj), \ + .offset = (off), \ + .flags = (fl), \ + .attr = (attr),} + +static rt_bool_t _contiguous_offset(rt_varea_t neighbour, rt_size_t map_size, + struct _mapping_property *prop) +{ + rt_size_t n_off = neighbour->offset; + rt_size_t map_off = prop->offset; + return n_off < map_off ? + n_off + (neighbour->size >> MM_PAGE_SHIFT) == map_off : + map_off + (map_size >> MM_PAGE_SHIFT) == n_off; +} + +static rt_bool_t _compatible(rt_varea_t neighbour, rt_size_t map_size, + struct _mapping_property *prop) +{ + return (prop->attr == neighbour->attr && prop->flags == neighbour->flag && + prop->mem_obj == neighbour->mem_obj && + _contiguous_offset(neighbour, map_size, prop)); +} + +rt_inline rt_err_t _migrate_and_release_varea(rt_aspace_t aspace, rt_varea_t to, rt_varea_t from, + rt_err_t (*on_varea_merge)(struct rt_varea *to, struct rt_varea *from)) +{ + rt_err_t error; + error = on_varea_merge(to, from); + if (error == RT_EOK) + { + /* uninstall operand & release the varea */ + _aspace_bst_remove(aspace, from); + if (!(from->flag & MMF_STATIC_ALLOC)) + rt_free(from); + + to->size += from->size; + } + return error; +} + +static rt_varea_t _merge_surrounding(rt_aspace_t aspace, rt_varea_t operand, + struct _mapping_property *prop) +{ + int again; + rt_err_t error; + int can_merge_fw; + int can_merge_bw; + rt_varea_t neighbour; + char *operand_start; + size_t operand_size; + rt_err_t (*on_varea_merge)(struct rt_varea *to, struct rt_varea *from); + + if (operand->mem_obj && operand->mem_obj->on_varea_merge) + { + on_varea_merge = operand->mem_obj->on_varea_merge; + do { + operand_start = operand->start; + operand_size = operand->size; + LOG_D("search op_start=%p,op_size=0x%lx", operand_start, operand_size); + + /* find a compatible neighbour if any and setup direction */ + can_merge_fw = can_merge_bw = 0; + neighbour = _aspace_bst_search(aspace, operand_start - 1); + if (!neighbour || !_compatible(neighbour, operand_size, prop)) + { + neighbour = _aspace_bst_search(aspace, operand_start + operand_size); + if (neighbour && _compatible(neighbour, operand_size, prop)) + can_merge_bw = 1; + } + else + can_merge_fw = 1; + + if (can_merge_fw || can_merge_bw) + { + /* merge operand with its predecessor or successor */ + if (can_merge_fw) + { + error = _migrate_and_release_varea(aspace, neighbour, operand, on_varea_merge); + operand = neighbour; + } + else + error = _migrate_and_release_varea(aspace, operand, neighbour, on_varea_merge); + + if (error == RT_EOK) + again = 1; + } + else + again = 0; + + } while (again); + } + return operand; +} + +/** + * Brief: expand and merge surrounding until not possible and + * setup the pvarea if new virt address region is installed + */ +static rt_bool_t _try_expand_and_merge_okay(rt_aspace_t aspace, rt_varea_t *pvarea, + void *alloc_va, rt_mm_va_hint_t hint, + struct _mapping_property *prop) +{ + int can_expand_fw; + int can_expand_bw; + rt_varea_t neighbour; + rt_varea_t new_region_at = RT_NULL; + rt_bool_t install_ok = RT_FALSE; + char *operand_start = alloc_va; + size_t operand_size = hint->map_size; + + /* find a compatible neighbour if any and setup direction */ + LOG_D("search op_start=%p,op_size=0x%lx", operand_start, operand_size); + can_expand_fw = can_expand_bw = 0; + neighbour = _aspace_bst_search(aspace, operand_start - 1); + if (!neighbour || !_compatible(neighbour, operand_size, prop)) + { + neighbour = _aspace_bst_search(aspace, operand_start + operand_size); + if (neighbour && _compatible(neighbour, operand_size, prop)) + can_expand_bw = 1; + } + else + can_expand_fw = 1; + + if (can_expand_fw || can_expand_bw) + { + /* expand varea at head or tailing */ + if (can_expand_fw) + operand_start = neighbour->start; + operand_size += neighbour->size; + + LOG_D("expand op_start=%p,op_size=0x%lx", operand_start, operand_size); + + if (_expand_varea(neighbour, operand_start, operand_size) == RT_EOK) + { + new_region_at = _merge_surrounding(aspace, neighbour, prop); + *pvarea = new_region_at; + install_ok = RT_TRUE; + } + } + + return install_ok; +} + +static rt_varea_t _varea_create(void *start, rt_size_t size); + +static int _insert_new_varea(rt_aspace_t aspace, rt_varea_t *pvarea, + void *alloc_va, rt_mm_va_hint_t hint) +{ + int err; + rt_varea_t varea = *pvarea; + if (varea == RT_NULL) + { + /* no preallocate buffer is provided, then create one */ + varea = _varea_create(hint->prefer, hint->map_size); + hint->flags &= ~MMF_STATIC_ALLOC; + *pvarea = varea; + } + + if (varea) { varea->start = alloc_va; _aspace_bst_insert(aspace, varea); + err = RT_EOK; } else { - err = -RT_ENOSPC; + LOG_W("%s: Out of memory", __func__); + err = -RT_ENOMEM; } return err; @@ -298,10 +502,64 @@ static inline void _varea_post_install(rt_varea_t varea, rt_aspace_t aspace, } /** + * Install new virtual address region into address space + * The pvarea will be set to the varea where new virt memory installed which can + * be a newly created or existed one. + * + * Note: caller must hold the aspace lock + */ +static int _varea_install(rt_aspace_t aspace, rt_varea_t *pvarea, + rt_mm_va_hint_t hint, struct _mapping_property *prop, + void **pva) +{ + void *alloc_va; + int err = RT_EOK; + + if (hint->flags & MMF_MAP_FIXED) + { + alloc_va = hint->prefer; + err = _unmap_range_locked(aspace, alloc_va, hint->map_size); + if (err != RT_EOK) + { + /* Note: MAP_FIXED must failed if unable to unmap existing mapping */ + LOG_I("%s: unmap range failed in %p with size 0x%lx, error=%d", __func__, alloc_va, hint->map_size, err); + } + } + else + { + alloc_va = + _find_free(aspace, hint->prefer, hint->map_size, hint->limit_start, + hint->limit_range_size, hint->flags); + if (alloc_va == RT_NULL) + err = -RT_ENOSPC; + } + + if (alloc_va != RT_NULL) + { + /* TODO: fix to private mapping directly */ + if (!_try_expand_and_merge_okay(aspace, pvarea, alloc_va, hint, prop)) + { + err = _insert_new_varea(aspace, pvarea, alloc_va, hint); + + if (err == RT_EOK) + _varea_post_install(*pvarea, aspace, prop->attr, prop->flags, + prop->mem_obj, prop->offset); + } + + if (err == RT_EOK) + { + RT_ASSERT(*pvarea); + *pva = alloc_va; + } + } + + return err; +} + +/** * restore context modified by varea install - * caller must NOT hold the aspace lock */ -static void _varea_uninstall(rt_varea_t varea) +void _varea_uninstall_locked(rt_varea_t varea) { rt_aspace_t aspace = varea->aspace; @@ -313,16 +571,16 @@ static void _varea_uninstall(rt_varea_t varea) rt_varea_pgmgr_pop_all(varea); - WR_LOCK(aspace); _aspace_bst_remove(aspace, varea); - WR_UNLOCK(aspace); } -static int _mm_aspace_map(rt_aspace_t aspace, rt_varea_t varea, rt_size_t attr, - mm_flag_t flags, rt_mem_obj_t mem_obj, - rt_size_t offset) +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) { int err = RT_EOK; + rt_varea_t varea; + struct _mapping_property prop = INIT_PROP(mem_obj, offset, flags, attr); WR_LOCK(aspace); @@ -331,8 +589,8 @@ static int _mm_aspace_map(rt_aspace_t aspace, rt_varea_t varea, rt_size_t attr, * .limit_start & .limit_range_size have default to be in range of aspace * .flags is from parameter, and will be fill in varea if install successfully */ - struct rt_mm_va_hint hint = {.prefer = varea->start, - .map_size = varea->size, + struct rt_mm_va_hint hint = {.prefer = *addr, + .map_size = length, .limit_start = aspace->start, .limit_range_size = aspace->size, .flags = flags}; @@ -344,30 +602,34 @@ static int _mm_aspace_map(rt_aspace_t aspace, rt_varea_t varea, rt_size_t attr, } /* try to allocate a virtual address region for varea */ - err = _varea_install(aspace, varea, &hint); - WR_UNLOCK(aspace); + err = _varea_install(aspace, pvarea, &hint, &prop, addr); if (err == RT_EOK) { - /* fill in varea data */ - _varea_post_install(varea, aspace, attr, flags, mem_obj, offset); - + varea = *pvarea; if (MMF_TEST_CNTL(flags, MMF_PREFETCH)) { /* do the MMU & TLB business */ err = _do_prefetch(aspace, varea, varea->start, varea->size); if (err) { + LOG_I("%s: failed to prefetch page into varea(%s)", + __func__, VAREA_NAME(varea)); + /* restore data structure and MMU */ - _varea_uninstall(varea); + _varea_uninstall_locked(varea); + if (!(varea->flag & MMF_STATIC_ALLOC)) + rt_free(varea); } } } + WR_UNLOCK(aspace); + return err; } -rt_varea_t _varea_create(void *start, rt_size_t size) +static rt_varea_t _varea_create(void *start, rt_size_t size) { rt_varea_t varea; varea = (rt_varea_t)rt_malloc(sizeof(struct rt_varea)); @@ -385,13 +647,14 @@ rt_varea_t _varea_create(void *start, rt_size_t size) static inline int _not_in_range(void *start, rt_size_t length, void *limit_start, rt_size_t limit_size) { - if (start != RT_NULL) - LOG_D("%s: [%p : %p] [%p : %p]", __func__, start, length, limit_start, limit_size); /* assuming (base + length) will not overflow except (0) */ - return start != RT_NULL + int rc = start != RT_NULL ? (_IS_OVERFLOW(start, length) || start < limit_start || _IS_OVERSIZE(start, length, limit_start, limit_size)) : length > limit_size; + if (rc) + LOG_D("%s: [%p : %p] [%p : %p]", __func__, start, length, limit_start, limit_size); + return rc; } static inline int _not_align(void *start, rt_size_t length, rt_size_t mask) @@ -411,9 +674,10 @@ 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) { - /* TODO check not in atomic context: irq, spinlock, local intr disable... */ int err; - rt_varea_t varea; + rt_varea_t varea = RT_NULL; + + RT_DEBUG_SCHEDULER_AVAILABLE(1); if (!aspace || !addr || !mem_obj || length == 0) { @@ -433,26 +697,9 @@ int rt_aspace_map(rt_aspace_t aspace, void **addr, rt_size_t length, } else { - /* allocate the varea and fill in start and size */ - varea = _varea_create(*addr, length); - - if (varea) - { - err = _mm_aspace_map(aspace, varea, attr, flags, mem_obj, offset); - if (err != RT_EOK) - { - rt_free(varea); - } - else - { - *addr = varea->start; - } - } - else - { - LOG_W("%s: memory allocation failed", __func__); - err = -RT_ENOMEM; - } + RT_ASSERT((length & ARCH_PAGE_MASK) == 0); + RT_ASSERT(((long)*addr & ARCH_PAGE_MASK) == 0); + err = _mm_aspace_map(aspace, &varea, addr, length, attr, flags, mem_obj, offset); } if (err != RT_EOK) @@ -485,7 +732,11 @@ int rt_aspace_map_static(rt_aspace_t aspace, rt_varea_t varea, void **addr, varea->size = length; varea->start = *addr; flags |= MMF_STATIC_ALLOC; - err = _mm_aspace_map(aspace, varea, attr, flags, mem_obj, offset); + + /** + * todo: fix if mapping expand, the static varea is not used at all + */ + err = _mm_aspace_map(aspace, &varea, addr, length, attr, flags, mem_obj, offset); } if (err != RT_EOK) @@ -526,24 +777,22 @@ int _mm_aspace_map_phy(rt_aspace_t aspace, rt_varea_t varea, } else { - WR_LOCK(aspace); - err = _varea_install(aspace, varea, hint); - WR_UNLOCK(aspace); + struct _mapping_property prop = INIT_PROP(0, pa_off, hint->flags, attr); + WR_LOCK(aspace); + err = _varea_install(aspace, &varea, hint, &prop, &vaddr); if (err == RT_EOK) { - _varea_post_install(varea, aspace, attr, hint->flags, NULL, pa_off); - - vaddr = varea->start; - - err = _do_named_map(aspace, varea->start, varea->size, + err = _do_named_map(aspace, varea, varea->start, varea->size, (rt_size_t)pa_off, attr); if (err != RT_EOK) { - _varea_uninstall(varea); + _varea_uninstall_locked(varea); } } + + WR_UNLOCK(aspace); } if (ret_va) @@ -567,6 +816,7 @@ int rt_aspace_map_phy(rt_aspace_t aspace, rt_mm_va_hint_t hint, rt_size_t attr, rt_varea_t varea = _varea_create(hint->prefer, hint->map_size); if (varea) { + hint->flags &= ~MMF_STATIC_ALLOC; err = _mm_aspace_map_phy(aspace, varea, hint, attr, pa_off, ret_va); if (err != RT_EOK) { @@ -596,7 +846,7 @@ int rt_aspace_map_phy_static(rt_aspace_t aspace, rt_varea_t varea, { varea->start = hint->prefer; varea->size = hint->map_size; - hint->flags |= (MMF_MAP_FIXED | MMF_STATIC_ALLOC); + hint->flags |= (MMF_STATIC_ALLOC); LOG_D("%s: start %p size %p phy at %p", __func__, varea->start, varea->size, pa_off << MM_PAGE_SHIFT); err = _mm_aspace_map_phy(aspace, varea, hint, attr, pa_off, ret_va); } @@ -608,42 +858,216 @@ int rt_aspace_map_phy_static(rt_aspace_t aspace, rt_varea_t varea, return err; } -void _aspace_unmap(rt_aspace_t aspace, void *addr) +int _aspace_unmap(rt_aspace_t aspace, void *addr) { + int error; + rt_varea_t varea; + WR_LOCK(aspace); - rt_varea_t varea = _aspace_bst_search(aspace, addr); - WR_UNLOCK(aspace); + varea = _aspace_bst_search(aspace, addr); if (varea == RT_NULL) { - LOG_I("%s: No such entry found at %p\n", __func__, addr); + LOG_D("%s: No such entry found at %p\n", __func__, addr); + error = -RT_ENOENT; } - - _varea_uninstall(varea); - if (!(varea->flag & MMF_STATIC_ALLOC)) + else { - rt_free(varea); + _varea_uninstall_locked(varea); + if (!(varea->flag & MMF_STATIC_ALLOC)) + { + rt_free(varea); + } + error = RT_EOK; } + + WR_UNLOCK(aspace); + return error; } int rt_aspace_unmap(rt_aspace_t aspace, void *addr) { + int error; if (!aspace) { LOG_I("%s: Invalid input", __func__); - return -RT_EINVAL; + error = -RT_EINVAL; + } + else if (_not_in_range(addr, 1, aspace->start, aspace->size)) + { + LOG_I("%s: %lx not in range of aspace[%lx:%lx]", __func__, addr, + aspace->start, (char *)aspace->start + aspace->size); + error = -RT_EINVAL; + } + else + { + error = _aspace_unmap(aspace, addr); } - if (_not_in_range(addr, 1, aspace->start, aspace->size)) + return error; +} + +/** + * modify the property of existed varea by shrink its size. Mem_obj is + * notified to released the resource. + */ +static rt_err_t _shrink_varea(rt_varea_t varea, void *new_va, rt_size_t size) +{ + rt_err_t error; + rt_aspace_t aspace; + void *old_va; + + if (varea->mem_obj && varea->mem_obj->on_varea_shrink) + error = varea->mem_obj->on_varea_shrink(varea, new_va, size); + else + error = -RT_EPERM; + + if (error == RT_EOK) + { + aspace = varea->aspace; + old_va = varea->start; + varea->size = size; + + if (old_va != new_va) + { + varea->start = new_va; + varea->offset += ((long)new_va - (long)old_va) >> MM_PAGE_SHIFT; + _aspace_bst_remove(aspace, varea); + _aspace_bst_insert(aspace, varea); + } + } + return error; +} + +static rt_err_t _split_varea(rt_varea_t existed, char *ex_end, char *unmap_start, char *unmap_end, rt_size_t unmap_len) +{ + int error; + size_t rela_offset; + rt_varea_t subset; + char *subset_start; + size_t subset_size; + + if (existed->mem_obj && existed->mem_obj->on_varea_split) + { + subset_start = unmap_end; + subset_size = ex_end - subset_start; + subset = _varea_create(subset_start, subset_size); + if (subset) + { + rela_offset = MM_PA_TO_OFF(subset_start - (char *)existed->start); + subset->aspace = existed->aspace; + subset->attr = existed->attr; + subset->mem_obj = existed->mem_obj; + subset->flag = existed->flag & ~MMF_STATIC_ALLOC; + subset->offset = existed->offset + rela_offset; + subset->frames = NULL; + + error = existed->mem_obj->on_varea_split(existed, unmap_start, unmap_len, subset); + if (error == RT_EOK) + { + existed->size = unmap_start - (char *)existed->start; + _aspace_bst_insert(existed->aspace, subset); + } + + if (error != RT_EOK) + rt_free(subset); + } + else + error = -RT_ENOMEM; + } + else + error = -RT_EPERM; + + return error; +} + +/* remove overlapped pages from varea */ +static int _remove_overlapped_varea(rt_varea_t existed, char *unmap_start, rt_size_t unmap_len) +{ + int error; + char *ex_start = existed->start; + char *ex_end = ex_start + existed->size; + char *unmap_end = unmap_start + unmap_len; + + if (ex_start < unmap_start) + { + if (ex_end > unmap_end) + error = _split_varea(existed, ex_end, unmap_start, unmap_end, unmap_len); + else + error = _shrink_varea(existed, ex_start, unmap_start - ex_start); + } + else if (ex_end > unmap_end) + error = _shrink_varea(existed, unmap_end, ex_end - unmap_end); + else + { + _varea_uninstall_locked(existed); + if (VAREA_NOT_STATIC(existed)) + { + rt_free(existed); + } + error = RT_EOK; + } + + return error; +} + +static int _unmap_range_locked(rt_aspace_t aspace, void *addr, size_t length) +{ + int error = RT_EOK; + rt_varea_t existed; + struct _mm_range unmap_range; + + unmap_range.start = addr; + unmap_range.end = addr + length - 1; + + existed = _aspace_bst_search_overlap(aspace, unmap_range); + while (existed) + { + error = _remove_overlapped_varea(existed, addr, length); + + if (error == RT_EOK) + existed = _aspace_bst_search_overlap(aspace, unmap_range); + else + break; + } + + return error; +} + +int rt_aspace_unmap_range(rt_aspace_t aspace, void *addr, size_t length) +{ + int error; + + if (!aspace) + { + LOG_I("%s: Invalid input", __func__); + error = -RT_EINVAL; + } + else if (_not_in_range(addr, length, aspace->start, aspace->size)) { LOG_I("%s: %lx not in range of aspace[%lx:%lx]", __func__, addr, aspace->start, (char *)aspace->start + aspace->size); - return -RT_EINVAL; + error = -RT_EINVAL; } + else if (!ALIGNED(addr)) + { + LOG_I("%s(addr=%p): Unaligned address", __func__, addr); + error = -RT_EINVAL; + } + else + { + /** + * Brief: re-arrange the address space to remove existing pages mapping + * in [unmap_start, unmap_start + unmap_len) + */ + length = RT_ALIGN(length, ARCH_PAGE_SIZE); - _aspace_unmap(aspace, addr); + WR_LOCK(aspace); + error = _unmap_range_locked(aspace, addr, length); + WR_UNLOCK(aspace); + } - return RT_EOK; + return error; } static inline void *_lower(void *a, void *b) @@ -843,6 +1267,7 @@ int rt_varea_map_page(rt_varea_t varea, void *vaddr, void *page) { err = _do_named_map( varea->aspace, + varea, vaddr, ARCH_PAGE_SIZE, MM_PA_TO_OFF(page_pa), @@ -853,8 +1278,6 @@ int rt_varea_map_page(rt_varea_t varea, void *vaddr, void *page) return err; } -#define ALIGNED(addr) (!((rt_size_t)(addr) & ARCH_PAGE_MASK)) - int rt_varea_unmap_page(rt_varea_t varea, void *vaddr) { void *va_aligned = (void *)RT_ALIGN_DOWN((rt_base_t)vaddr, ARCH_PAGE_SIZE); @@ -883,6 +1306,7 @@ int rt_varea_map_range(rt_varea_t varea, void *vaddr, void *paddr, rt_size_t len { err = _do_named_map( varea->aspace, + varea, vaddr, length, MM_PA_TO_OFF(paddr), @@ -986,3 +1410,353 @@ void rt_aspace_print_all(rt_aspace_t aspace) { rt_aspace_traversal(aspace, _dump, NULL); } + +static int _count_vsz(rt_varea_t varea, void *arg) +{ + rt_base_t *pvsz = arg; + RT_ASSERT(varea); + *pvsz = *pvsz + varea->size; + return 0; +} + +rt_base_t rt_aspace_count_vsz(rt_aspace_t aspace) +{ + rt_base_t vsz = 0; + rt_aspace_traversal(aspace, _count_vsz, &vsz); + return vsz; +} + +static int _dup_varea(rt_varea_t src_varea, void *arg) +{ + int err; + rt_aspace_t dst = arg; + rt_aspace_t src = src_varea->aspace; + + void *pa = RT_NULL; + void *va = RT_NULL; + rt_mem_obj_t mem_obj = src_varea->mem_obj; + + if (!mem_obj) + { + /* duplicate a physical mapping */ + pa = rt_hw_mmu_v2p(src, (void *)src_varea->start); + RT_ASSERT(pa != ARCH_MAP_FAILED); + struct rt_mm_va_hint hint = {.flags = src_varea->flag, + .limit_range_size = dst->size, + .limit_start = dst->start, + .prefer = src_varea->start, + .map_size = src_varea->size}; + err = rt_aspace_map_phy(dst, &hint, src_varea->attr, + MM_PA_TO_OFF(pa), &va); + if (err != RT_EOK) + { + LOG_W("%s: aspace map failed at %p with size %p", __func__, + src_varea->start, src_varea->size); + } + } + else + { + /* duplicate a mem_obj backing mapping */ + rt_base_t flags = src_varea->flag | MMF_MAP_FIXED; + flags &= ~MMF_STATIC_ALLOC; + flags &= ~MMF_PREFETCH; + va = src_varea->start; + + err = rt_aspace_map(dst, &va, src_varea->size, src_varea->attr, + flags, mem_obj, src_varea->offset); + if (err != RT_EOK) + { + LOG_W("%s: aspace map failed at %p with size %p", __func__, + src_varea->start, src_varea->size); + } + } + + if (va != (void *)src_varea->start) + { + return -1; + } + return 0; +} + +struct _compare_param { + rt_aspace_t dst; + int rc; +}; + +rt_err_t rt_aspace_duplicate_locked(rt_aspace_t src, rt_aspace_t dst) +{ + return rt_aspace_traversal(src, _dup_varea, dst); +} + +rt_inline int _varea_same(rt_varea_t a, rt_varea_t b) +{ + return a->attr == b->attr && a->flag == b->flag && a->mem_obj == b->mem_obj; +} + +rt_inline void _dump_varea(rt_varea_t varea) +{ + LOG_W("%s(attr=0x%lx, flags=0x%lx, start=0x%lx, size=0x%lx, mem_obj=%p)", VAREA_NAME(varea), varea->attr, varea->flag, varea->start, varea->size, varea->mem_obj); +} + +static int _compare_varea(rt_varea_t src_varea, void *arg) +{ + struct _compare_param *param = arg; + rt_varea_t dst_varea; + rt_aspace_t dst = param->dst; + rt_aspace_t src = src_varea->aspace; + + dst_varea = _aspace_bst_search(dst, src_varea->start); + if (dst_varea) + { + char *buf1 = rt_pages_alloc_ext(0, PAGE_ANY_AVAILABLE); + char *buf2 = rt_pages_alloc_ext(0, PAGE_ANY_AVAILABLE); + char *vend = src_varea->start + src_varea->size; + for (char *i = src_varea->start; i < vend; i += ARCH_PAGE_SIZE) + { + int rc; + rt_aspace_page_get(src, i, buf1); + rt_aspace_page_get(dst, i, buf2); + rc = memcmp(buf1, buf2, ARCH_PAGE_SIZE); + if (rc) + { + if (param->rc == 0) + param->rc = rc; + LOG_E("%s(a_varea=%s, b_varea=%s)", __func__, VAREA_NAME(src_varea), VAREA_NAME(dst_varea)); + _dump_varea(src_varea); + _dump_varea(dst_varea); + RT_ASSERT(0); + } + } + + rt_pages_free(buf1, 0); + rt_pages_free(buf2, 0); + } + else + { + param->rc = -RT_ENOENT; + } + + return 0; +} + +rt_err_t rt_aspace_compare(rt_aspace_t src, rt_aspace_t dst) +{ + struct _compare_param param = {.rc = 0, .dst = dst}; + rt_aspace_traversal(src, _compare_varea, ¶m); + return param.rc; +} + +/* dst are page aligned */ +rt_inline rt_err_t _page_put(rt_varea_t varea, void *page_va, void *buffer) +{ + struct rt_aspace_io_msg iomsg; + rt_err_t rc; + + rt_mm_io_msg_init(&iomsg, VAREA_VA_TO_OFFSET(varea, page_va), page_va, buffer); + varea->mem_obj->page_write(varea, &iomsg); + + if (iomsg.response.status == MM_FAULT_STATUS_UNRECOVERABLE) + rc = -RT_ERROR; + else + rc = RT_EOK; + return rc; +} + +/* dst are page aligned */ +rt_inline rt_err_t _page_get(rt_varea_t varea, void *page_va, void *buffer) +{ + struct rt_aspace_io_msg iomsg; + rt_err_t rc; + + rt_mm_io_msg_init(&iomsg, VAREA_VA_TO_OFFSET(varea, page_va), page_va, buffer); + varea->mem_obj->page_read(varea, &iomsg); + + if (iomsg.response.status == MM_FAULT_STATUS_UNRECOVERABLE) + rc = -RT_ERROR; + else + rc = RT_EOK; + return rc; +} + +#ifdef RT_USING_SMART +#include "lwp.h" +rt_inline rt_aspace_t _current_uspace(void) +{ + rt_lwp_t this_proc = lwp_self(); + return this_proc ? this_proc->aspace : RT_NULL; +} +#else +rt_inline rt_aspace_t _current_uspace(void) +{ + return RT_NULL; +} +#endif + +rt_err_t rt_aspace_page_get_phy(rt_aspace_t aspace, void *page_va, void *buffer) +{ + rt_err_t rc = -RT_ERROR; + + char *frame_ka = rt_hw_mmu_v2p(aspace, page_va); + if (frame_ka != ARCH_MAP_FAILED) + { + frame_ka = rt_kmem_p2v(frame_ka); + if (frame_ka) + { + rt_memcpy(buffer, frame_ka, ARCH_PAGE_SIZE); + rc = RT_EOK; + } + else if (aspace == _current_uspace() || aspace == &rt_kernel_space) + { + /* direct IO */ + rt_memcpy(buffer, page_va, ARCH_PAGE_SIZE); + rc = RT_EOK; + } + else + { + /* user memory region remap ? */ + LOG_W("%s(aspace=0x%lx,va=%p): Operation not support", + __func__, aspace, page_va); + rc = -RT_ENOSYS; + } + } + else + { + LOG_W("%s(aspace=0x%lx,va=%p): PTE not existed", + __func__, aspace, page_va); + rc = -RT_ENOENT; + } + return rc; +} + +rt_err_t rt_aspace_page_put_phy(rt_aspace_t aspace, void *page_va, void *buffer) +{ + rt_err_t rc = -RT_ERROR; + + char *frame_ka = rt_hw_mmu_v2p(aspace, page_va); + if (frame_ka != ARCH_MAP_FAILED) + { + frame_ka = rt_kmem_p2v(frame_ka); + if (frame_ka) + { + rt_memcpy(frame_ka, buffer, ARCH_PAGE_SIZE); + rc = RT_EOK; + } + else if (aspace == _current_uspace() || aspace == &rt_kernel_space) + { + /* direct IO */ + rt_memcpy(page_va, buffer, ARCH_PAGE_SIZE); + rc = RT_EOK; + } + else + { + /* user memory region remap ? */ + LOG_W("%s(aspace=0x%lx,va=%p): Operation not support", + __func__, aspace, page_va); + rc = -RT_ENOSYS; + } + } + else + { + LOG_W("%s(aspace=0x%lx,va=%p): PTE not existed", + __func__, aspace, page_va); + rc = -RT_ENOENT; + } + + return rc; +} + +rt_err_t rt_aspace_page_put(rt_aspace_t aspace, void *page_va, void *buffer) +{ + rt_err_t rc = -RT_ERROR; + rt_varea_t varea; + + RT_ASSERT(aspace); + RD_LOCK(aspace); + varea = _aspace_bst_search(aspace, page_va); + if (varea && ALIGNED(page_va)) + { + if (varea->mem_obj) + { + if (varea->mem_obj->page_write) + { + if (rt_varea_is_private_locked(varea)) + { + RDWR_LOCK(aspace); + struct rt_aspace_fault_msg msg; + msg.fault_op = MM_FAULT_OP_WRITE; + msg.fault_type = MM_FAULT_TYPE_ACCESS_FAULT; + msg.fault_vaddr = page_va; + rc = rt_varea_fix_private_locked(varea, rt_hw_mmu_v2p(aspace, page_va), + &msg, RT_TRUE); + RDWR_UNLOCK(aspace); + if (rc == MM_FAULT_FIXABLE_TRUE) + { + varea = _aspace_bst_search(aspace, page_va); + rc = _page_put(varea, page_va, buffer); + } + else + rc = -RT_ERROR; + } + else + rc = _page_put(varea, page_va, buffer); + } + else + { + rc = -RT_EINVAL; + LOG_I("%s: Operation not allowed", __func__); + } + } + else + { + rc = rt_aspace_page_put_phy(aspace, page_va, buffer); + } + } + else + rc = -RT_EINVAL; + RD_UNLOCK(aspace); + + return rc; +} + +rt_err_t rt_aspace_page_get(rt_aspace_t aspace, void *page_va, void *buffer) +{ + rt_err_t rc = -RT_ERROR; + rt_varea_t varea; + + /* TODO: cache the last search item */ + RT_ASSERT(aspace); + RD_LOCK(aspace); + varea = _aspace_bst_search(aspace, page_va); + if (varea && ALIGNED(page_va)) + { + if (varea->mem_obj) + { + if (varea->mem_obj->page_read) + { + rc = _page_get(varea, page_va, buffer); + } + else + { + LOG_I("%s: Operation not allowed", __func__); + } + } + else + { + rc = rt_aspace_page_get_phy(aspace, page_va, buffer); + } + } + else + { + rc = -RT_EINVAL; + LOG_D("%s(va=%p,varea=0x%lx): Invalid address", + __func__, page_va, varea); + } + RD_UNLOCK(aspace); + + return rc; +} + +rt_varea_t rt_aspace_query(rt_aspace_t aspace, void *vaddr) +{ + return _aspace_bst_search(aspace, vaddr); +} diff --git a/components/mm/mm_aspace.h b/components/mm/mm_aspace.h index 6c24040368..5748647903 100644 --- a/components/mm/mm_aspace.h +++ b/components/mm/mm_aspace.h @@ -1,23 +1,26 @@ /* - * Copyright (c) 2006-2022, RT-Thread Development Team + * 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 <stddef.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()) @@ -55,6 +58,7 @@ typedef struct rt_aspace struct _aspace_tree tree; struct rt_mutex bst_lock; + struct rt_mem_obj *private_object; rt_uint64_t asid; } *rt_aspace_t; @@ -97,7 +101,25 @@ typedef struct rt_mem_obj /* do post close bushiness like def a ref */ void (*on_varea_close)(struct rt_varea *varea); - void (*on_page_offload)(struct rt_varea *varea, void *vaddr, rt_size_t size); + /* 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); } *rt_mem_obj_t; @@ -110,6 +132,8 @@ enum rt_mmu_cntl MMU_CNTL_CACHE, MMU_CNTL_READONLY, MMU_CNTL_READWRITE, + MMU_CNTL_OFFLOAD, + MMU_CNTL_INSTALL, MMU_CNTL_DUMMY_END, }; @@ -122,8 +146,11 @@ enum rt_mmu_cntl #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); @@ -176,14 +203,50 @@ 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 any mappings overlap the range [addr, addr + bytes) + * @brief Remove mappings containing address specified by addr * - * @param aspace + * @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); + +/** + * @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); + +/** + * @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 * @return int */ -int rt_aspace_unmap(rt_aspace_t aspace, void *addr); +int rt_aspace_unmap_range(rt_aspace_t aspace, void *addr, size_t length); int rt_aspace_control(rt_aspace_t aspace, void *addr, enum rt_mmu_cntl cmd); @@ -191,14 +254,28 @@ 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 @@ -209,6 +286,8 @@ 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 @@ -252,7 +331,16 @@ int rt_varea_unmap_range(rt_varea_t varea, void *vaddr, rt_size_t length); */ void rt_varea_pgmgr_insert(rt_varea_t varea, void *page_addr); -rt_ubase_t rt_kmem_pvoff(void); +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); @@ -260,6 +348,8 @@ 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__ */ diff --git a/components/mm/mm_fault.c b/components/mm/mm_fault.c index a73c173acf..4dcef168a5 100644 --- a/components/mm/mm_fault.c +++ b/components/mm/mm_fault.c @@ -6,6 +6,7 @@ * Change Logs: * Date Author Notes * 2022-12-06 WangXiaoyao the first version + * 2023-08-19 Shell Support PRIVATE mapping and COW */ #include <rtthread.h> @@ -23,27 +24,21 @@ #include <mmu.h> #include <tlb.h> -#define UNRECOVERABLE 0 -#define RECOVERABLE 1 - static int _fetch_page(rt_varea_t varea, struct rt_aspace_fault_msg *msg) { - int err = UNRECOVERABLE; - msg->response.status = MM_FAULT_STATUS_UNRECOVERABLE; - msg->response.vaddr = 0; - msg->response.size = 0; + 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 = _varea_map_with_msg(varea, msg); - err = (err == RT_EOK ? RECOVERABLE : UNRECOVERABLE); + 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 = UNRECOVERABLE; + int err = MM_FAULT_FIXABLE_FALSE; if (msg->fault_type == MM_FAULT_TYPE_PAGE_FAULT) { RT_ASSERT(pa == ARCH_MAP_FAILED); @@ -59,19 +54,37 @@ static int _read_fault(rt_varea_t varea, void *pa, struct rt_aspace_fault_msg *m static int _write_fault(rt_varea_t varea, void *pa, struct rt_aspace_fault_msg *msg) { - int err = UNRECOVERABLE; - if (msg->fault_type == MM_FAULT_TYPE_PAGE_FAULT) + 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_ACCESS_FAULT || + 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)", __func__, VAREA_NAME(varea), varea->attr); + } + } + 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); - } - else if (msg->fault_type == MM_FAULT_TYPE_ACCESS_FAULT && - varea->flag & MMF_COW) - { + 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; @@ -79,7 +92,7 @@ static int _write_fault(rt_varea_t varea, void *pa, struct rt_aspace_fault_msg * static int _exec_fault(rt_varea_t varea, void *pa, struct rt_aspace_fault_msg *msg) { - int err = UNRECOVERABLE; + int err = MM_FAULT_FIXABLE_FALSE; if (msg->fault_type == MM_FAULT_TYPE_PAGE_FAULT) { RT_ASSERT(pa == ARCH_MAP_FAILED); @@ -91,10 +104,13 @@ static int _exec_fault(rt_varea_t varea, void *pa, struct rt_aspace_fault_msg *m int rt_aspace_fault_try_fix(rt_aspace_t aspace, struct rt_aspace_fault_msg *msg) { - int err = UNRECOVERABLE; + 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) { @@ -105,21 +121,34 @@ int rt_aspace_fault_try_fix(rt_aspace_t aspace, struct rt_aspace_fault_msg *msg) if (varea) { void *pa = rt_hw_mmu_v2p(aspace, msg->fault_vaddr); - msg->off = ((char *)msg->fault_vaddr - (char *)varea->start) >> ARCH_PAGE_SHIFT; - - /* permission checked by fault op */ - switch (msg->fault_op) + if (pa != ARCH_MAP_FAILED && msg->fault_type == MM_FAULT_TYPE_PAGE_FAULT) { - 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; + 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); } diff --git a/components/mm/mm_fault.h b/components/mm/mm_fault.h index eeb413025f..28671aef74 100644 --- a/components/mm/mm_fault.h +++ b/components/mm/mm_fault.h @@ -20,12 +20,8 @@ #define MM_FAULT_STATUS_OK_MAPPED 1 #define MM_FAULT_STATUS_UNRECOVERABLE 4 -struct rt_mm_fault_res -{ - void *vaddr; - rt_size_t size; - int status; -}; +#define MM_FAULT_FIXABLE_FALSE 0 +#define MM_FAULT_FIXABLE_TRUE 1 enum rt_mm_fault_op { @@ -36,10 +32,41 @@ enum rt_mm_fault_op enum rt_mm_fault_type { + /** + * Occurs when an instruction attempts to access a memory address that it + * does not have permission to access + */ MM_FAULT_TYPE_ACCESS_FAULT, + + /** + * 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, + 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 @@ -52,8 +79,36 @@ struct rt_aspace_fault_msg 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, return 1 is fixable */ +/* 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__ */ diff --git a/components/mm/mm_flag.h b/components/mm/mm_flag.h index 4104a60b8f..6d58027b36 100644 --- a/components/mm/mm_flag.h +++ b/components/mm/mm_flag.h @@ -10,6 +10,8 @@ #ifndef __MM_FLAG_H__ #define __MM_FLAG_H__ +#include <rtthread.h> + /** * @brief mm_flag_t * |max ------- 7|6 ----- 0| @@ -27,10 +29,19 @@ typedef unsigned long mm_flag_t; enum mm_flag_cntl { /** - * @brief Indicate a possible COW mapping + * @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), - MMF_COW = _DEF_FLAG(1), + + /** + * @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 diff --git a/components/mm/mm_kmem.c b/components/mm/mm_kmem.c index f981b36a83..7299435cb3 100644 --- a/components/mm/mm_kmem.c +++ b/components/mm/mm_kmem.c @@ -27,7 +27,7 @@ void rt_kmem_list(void) __attribute__((alias("list_kmem"))); static rt_ubase_t rt_pv_offset; -rt_ubase_t rt_kmem_pvoff(void) +const rt_ubase_t rt_kmem_pvoff(void) { return rt_pv_offset; } @@ -78,3 +78,29 @@ 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; +} diff --git a/components/mm/mm_object.c b/components/mm/mm_object.c index 3b4ccddc89..f26b3173f5 100644 --- a/components/mm/mm_object.c +++ b/components/mm/mm_object.c @@ -6,8 +6,13 @@ * Change Logs: * Date Author Notes * 2022-11-30 WangXiaoyao the first version + * 2023-08-19 Shell Support varea modification handler */ +#define DBG_TAG "mm.object" +#define DBG_LVL DBG_INFO +#include "rtdbg.h" + #include <rtthread.h> #include "mm_aspace.h" @@ -15,15 +20,38 @@ #include "mm_page.h" #include <mmu.h> -#define DBG_TAG "mm.object" -#define DBG_LVL DBG_INFO -#include "rtdbg.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 rt_bool_t _varea_pgmgr_frame_is_member(rt_varea_t varea, rt_page_t frame) +{ + rt_page_t iter; + rt_bool_t rc = RT_FALSE; + + if (varea->frames) + { + iter = varea->frames; + do + { + if (iter == frame) + { + rc = RT_TRUE; + break; + } + iter = iter->next; + } while (iter); + } + + return rc; +} + void rt_varea_pgmgr_insert(rt_varea_t varea, void *page_addr) { rt_page_t page = rt_page_addr2page(page_addr); @@ -31,10 +59,12 @@ void rt_varea_pgmgr_insert(rt_varea_t varea, void *page_addr) if (varea->frames == NULL) { varea->frames = page; - page->next = NULL; + page->pre = RT_NULL; + page->next = RT_NULL; } else { + page->pre = RT_NULL; varea->frames->pre = page; page->next = varea->frames; varea->frames = page; @@ -52,6 +82,8 @@ void rt_varea_pgmgr_pop_all(rt_varea_t varea) rt_pages_free(pg_va, 0); page = next; } + + varea->frames = RT_NULL; } void rt_varea_pgmgr_pop(rt_varea_t varea, void *vaddr, rt_size_t size) @@ -60,9 +92,16 @@ void rt_varea_pgmgr_pop(rt_varea_t varea, void *vaddr, rt_size_t size) while (vaddr != vend) { rt_page_t page = rt_page_addr2page(vaddr); - page->pre->next = page->next; - page->next->pre = page->pre; - rt_pages_free(vaddr, 0); + if (_varea_pgmgr_frame_is_member(varea, page)) + { + if (page->pre) + page->pre->next = page->next; + if (page->next) + page->next->pre = page->pre; + if (varea->frames == page) + varea->frames = page->next; + rt_pages_free(vaddr, 0); + } vaddr = (char *)vaddr + ARCH_PAGE_SIZE; } } @@ -94,9 +133,149 @@ static void on_varea_close(struct rt_varea *varea) { } -static void on_page_offload(rt_varea_t varea, void *vaddr, rt_size_t size) +static rt_err_t on_varea_expand(struct rt_varea *varea, void *new_vaddr, rt_size_t size) +{ + return RT_EOK; +} + +static void _remove_pages(rt_varea_t varea, void *rm_start, void *rm_end) { - rt_varea_pgmgr_pop(varea, vaddr, size); + 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_varea_pgmgr_pop(varea, page_va, ARCH_PAGE_SIZE); + } + rm_start += ARCH_PAGE_SIZE; + } +} + +static rt_err_t on_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; + } + + _remove_pages(varea, rm_start, rm_end); + 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) +{ + void *sub_start = subset->start; + void *sub_end = sub_start + subset->size; + void *page_va; + + _remove_pages(existed, unmap_start, (char *)unmap_start + unmap_len); + + RT_ASSERT(!((rt_ubase_t)sub_start & ARCH_PAGE_MASK)); + RT_ASSERT(!((rt_ubase_t)sub_end & ARCH_PAGE_MASK)); + while (sub_start != sub_end) + { + page_va = rt_hw_mmu_v2p(existed->aspace, sub_start); + + if (page_va != ARCH_MAP_FAILED) + { + rt_page_t frame; + page_va = rt_kmem_p2v(page_va); + if (page_va) + { + frame = rt_page_addr2page(page_va); + if (frame && _varea_pgmgr_frame_is_member(existed, frame)) + { + LOG_D("%s: free page %p", __func__, page_va); + rt_page_ref_inc(page_va, 0); + rt_varea_pgmgr_pop(existed, page_va, ARCH_PAGE_SIZE); + rt_varea_pgmgr_insert(subset, page_va); + } + } + } + sub_start += ARCH_PAGE_SIZE; + } + + return RT_EOK; +} + +static rt_err_t on_varea_merge(struct rt_varea *merge_to, struct rt_varea *merge_from) +{ + /* transport page */ + void *mr_start = merge_from->start; + void *mr_end = mr_start + merge_from->size; + void *page_va; + + RT_ASSERT(!((rt_ubase_t)mr_start & ARCH_PAGE_MASK)); + RT_ASSERT(!((rt_ubase_t)mr_end & ARCH_PAGE_MASK)); + while (mr_start != mr_end) + { + page_va = rt_hw_mmu_v2p(merge_from->aspace, mr_start); + if (page_va != ARCH_MAP_FAILED) + { + rt_page_t frame; + page_va = rt_kmem_p2v(page_va); + if (page_va) + { + frame = rt_page_addr2page(page_va); + if (frame && _varea_pgmgr_frame_is_member(merge_from, frame)) + { + LOG_D("%s: free page %p", __func__, page_va); + rt_page_ref_inc(page_va, 0); + rt_varea_pgmgr_pop(merge_from, page_va, ARCH_PAGE_SIZE); + rt_varea_pgmgr_insert(merge_to, page_va); + } + } + } + mr_start += ARCH_PAGE_SIZE; + } + 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 = { @@ -105,5 +284,12 @@ struct rt_mem_obj rt_mm_dummy_mapper = { .hint_free = NULL, .on_varea_open = on_varea_open, .on_varea_close = on_varea_close, - .on_page_offload = on_page_offload, + + .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, }; diff --git a/components/mm/mm_page.c b/components/mm/mm_page.c index 99110ab435..0ddb392b63 100644 --- a/components/mm/mm_page.c +++ b/components/mm/mm_page.c @@ -91,10 +91,13 @@ 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); @@ -104,16 +107,18 @@ static void _collect() rt_page_t page = _trace_head; if (!page) { - rt_kputs("ok!\n"); + LOG_RAW("ok! ALLOC CNT %ld\n", _alloc_cnt); } - - while (page) + else { - rt_page_t next = page->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; + 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; + } } } @@ -129,11 +134,13 @@ static void _trace_alloc(rt_page_t page, void *caller, size_t size_bits) { if (enable) { + char *page_va = rt_page_page2addr(page); 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; @@ -147,12 +154,12 @@ static void _trace_alloc(rt_page_t page, void *caller, size_t size_bits) } } -void _report(rt_page_t page, size_bits, char *msg) +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_kputs("backtrace\n"); - rt_hw_backtrace(0, 0); + LOG_RAW("backtrace\n"); + rt_backtrace(); } static void _trace_free(rt_page_t page, void *caller, size_t size_bits) @@ -162,25 +169,26 @@ static void _trace_free(rt_page_t page, void *caller, size_t size_bits) /* free after free */ if (page->trace_size == 0xabadcafe) { - _report("free after free") + _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("incompatible size bits parameter"); + LOG_RAW("free with size bits %lx\n", size_bits); + _report(page, size_bits, "incompatible size bits parameter"); return ; } - if (page->ref_cnt == 1) + 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->next; + _trace_head = page->tl_next; page->tl_prev = NULL; page->tl_next = NULL; @@ -531,7 +539,7 @@ static rt_page_t *_flag_to_page_list(size_t flags) return page_list; } -static void *_do_pages_alloc(rt_uint32_t size_bits, size_t flags) +rt_inline void *_do_pages_alloc(rt_uint32_t size_bits, size_t flags) { void *alloc_buf = RT_NULL; struct rt_page *p; @@ -613,27 +621,27 @@ void list_page(void) struct rt_page *lp = page_list_low[i]; struct rt_page *hp = page_list_high[i]; - rt_kprintf("level %d ", i); + LOG_RAW("level %d ", i); while (lp) { free += (1UL << i); - rt_kprintf("[0x%08p]", rt_page_page2addr(lp)); + LOG_RAW("[0x%08p]", rt_page_page2addr(lp)); lp = lp->next; } while (hp) { free += (1UL << i); - rt_kprintf("[0x%08p]", rt_page_page2addr(hp)); + LOG_RAW("[0x%08p]", rt_page_page2addr(hp)); hp = hp->next; } - rt_kprintf("\n"); + LOG_RAW("\n"); } rt_hw_interrupt_enable(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"); + LOG_RAW("-------------------------------\n"); + LOG_RAW("Page Summary:\n => free/installed: 0x%lx/0x%lx (%ld/%ld KB)\n", free, installed, PGNR2SIZE(free), PGNR2SIZE(installed)); + LOG_RAW("-------------------------------\n"); } MSH_CMD_EXPORT(list_page, show page info); diff --git a/components/mm/mm_page.h b/components/mm/mm_page.h index a0171d6672..e1557b923d 100644 --- a/components/mm/mm_page.h +++ b/components/mm/mm_page.h @@ -35,7 +35,7 @@ #ifdef RT_DEBUGING_PAGE_LEAK -#define DEBUG_FIELD { \ +#define DEBUG_FIELD struct { \ /* trace list */ \ struct rt_page *tl_next; \ struct rt_page *tl_prev; \ @@ -50,7 +50,7 @@ DEF_PAGE_T( struct rt_page *next; /* same level next */ struct rt_page *pre; /* same level pre */ - DEBUG_FIELD + 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 */ diff --git a/components/mm/mm_private.h b/components/mm/mm_private.h index 364ddab717..3fd3b88f86 100644 --- a/components/mm/mm_private.h +++ b/components/mm/mm_private.h @@ -11,6 +11,10 @@ #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> @@ -29,6 +33,17 @@ * 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)) +#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; @@ -94,6 +109,29 @@ void rt_varea_pgmgr_pop(rt_varea_t varea, void *vaddr, rt_size_t size); void rt_varea_pgmgr_pop_all(rt_varea_t varea); -int _varea_map_with_msg(rt_varea_t varea, struct rt_aspace_fault_msg *msg); +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__ */ |
