739 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			739 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|  | /*
 | ||
|  |  * Copyright (c) 2006-2023, RT-Thread Development Team | ||
|  |  * | ||
|  |  * SPDX-License-Identifier: Apache-2.0 | ||
|  |  * | ||
|  |  * Change Logs: | ||
|  |  * Date           Author       Notes | ||
|  |  * 2023-08-19     Shell        Support PRIVATE mapping and COW | ||
|  |  */ | ||
|  | 
 | ||
|  | #define DBG_TAG "mm.anon"
 | ||
|  | #define DBG_LVL DBG_INFO
 | ||
|  | #include <rtdbg.h>
 | ||
|  | 
 | ||
|  | #include <string.h>
 | ||
|  | #include "mm_private.h"
 | ||
|  | #include <mmu.h>
 | ||
|  | 
 | ||
|  | /**
 | ||
|  |  * Anonymous Object directly represent the mappings without backup files in the | ||
|  |  * aspace. Their only backup is in the aspace->pgtbl. | ||
|  |  */ | ||
|  | 
 | ||
|  | typedef struct rt_private_ctx { | ||
|  |     struct rt_mem_obj mem_obj; | ||
|  |     rt_aspace_t backup_aspace; | ||
|  |     /* both varea and aspace can holds a reference */ | ||
|  |     rt_atomic_t reference; | ||
|  |     /* readonly `private` is shared object */ | ||
|  |     long readonly; | ||
|  | } *rt_private_ctx_t; | ||
|  | 
 | ||
|  | rt_inline rt_aspace_t _anon_obj_get_backup(rt_mem_obj_t mobj) | ||
|  | { | ||
|  |     rt_private_ctx_t pctx; | ||
|  |     rt_aspace_t backup; | ||
|  |     pctx = rt_container_of(mobj, struct rt_private_ctx, mem_obj); | ||
|  |     backup = pctx->backup_aspace; | ||
|  |     return backup; | ||
|  | } | ||
|  | 
 | ||
|  | rt_inline rt_atomic_t *_anon_obj_get_reference(rt_mem_obj_t mobj) | ||
|  | { | ||
|  |     rt_private_ctx_t pctx; | ||
|  |     pctx = rt_container_of(mobj, struct rt_private_ctx, mem_obj); | ||
|  |     return &pctx->reference; | ||
|  | } | ||
|  | 
 | ||
|  | rt_inline rt_private_ctx_t _anon_mobj_to_pctx(rt_mem_obj_t mobj) | ||
|  | { | ||
|  |     return rt_container_of(mobj, struct rt_private_ctx, mem_obj); | ||
|  | } | ||
|  | 
 | ||
|  | static long rt_aspace_anon_ref_inc(rt_mem_obj_t aobj) | ||
|  | { | ||
|  |     long rc; | ||
|  |     if (aobj) | ||
|  |     { | ||
|  |         rc = rt_atomic_add(_anon_obj_get_reference(aobj), 1); | ||
|  |         LOG_D("%s(aobj=%p) Cur %ld", __func__, aobj, rc + 1); | ||
|  |     } | ||
|  |     else | ||
|  |         rc = -1; | ||
|  |     return rc; | ||
|  | } | ||
|  | 
 | ||
|  | rt_err_t rt_aspace_anon_ref_dec(rt_mem_obj_t aobj) | ||
|  | { | ||
|  |     rt_err_t rc; | ||
|  |     rt_aspace_t aspace; | ||
|  |     rt_private_ctx_t pctx; | ||
|  |     long former_reference; | ||
|  | 
 | ||
|  |     if (aobj) | ||
|  |     { | ||
|  |         pctx = _anon_mobj_to_pctx(aobj); | ||
|  |         RT_ASSERT(pctx); | ||
|  | 
 | ||
|  |         former_reference = rt_atomic_add(_anon_obj_get_reference(aobj), -1); | ||
|  |         LOG_D("%s(aobj=%p) Cur %ld", __func__, aobj, former_reference - 1); | ||
|  |         if (pctx->readonly) | ||
|  |         { | ||
|  |             if (former_reference - 1 <= pctx->readonly) | ||
|  |             { | ||
|  |                 void *pgtbl; | ||
|  |                 RT_ASSERT(former_reference - 1 == pctx->readonly); | ||
|  |                 aspace = _anon_obj_get_backup(aobj); | ||
|  | 
 | ||
|  |                 pctx->readonly = 0; | ||
|  |                 pgtbl = aspace->page_table; | ||
|  |                 rt_aspace_delete(aspace); | ||
|  |                 rt_hw_mmu_pgtbl_delete(pgtbl); | ||
|  |             } | ||
|  |         } | ||
|  |         else if (former_reference < 2) | ||
|  |         { | ||
|  |             aspace = _anon_obj_get_backup(aobj); | ||
|  |             aspace->private_object = RT_NULL; | ||
|  | 
 | ||
|  |             rt_free(pctx); | ||
|  |         } | ||
|  |         rc = RT_EOK; | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         rc = -RT_EINVAL; | ||
|  |     } | ||
|  | 
 | ||
|  |     return rc; | ||
|  | } | ||
|  | 
 | ||
|  | void rt_varea_pgmgr_insert(rt_varea_t varea, void *page_addr) | ||
|  | { | ||
|  |     /* each mapping of page frame in the varea is binding with a reference */ | ||
|  |     rt_page_ref_inc(page_addr, 0); | ||
|  | } | ||
|  | 
 | ||
|  | /**
 | ||
|  |  * Private unmapping of address space | ||
|  |  */ | ||
|  | static void _pgmgr_pop_all(rt_varea_t varea) | ||
|  | { | ||
|  |     rt_aspace_t aspace = varea->aspace; | ||
|  |     char *iter = varea->start; | ||
|  |     char *end_addr = iter + varea->size; | ||
|  | 
 | ||
|  |     RT_ASSERT(iter < end_addr); | ||
|  |     RT_ASSERT(!((long)iter & ARCH_PAGE_MASK)); | ||
|  |     RT_ASSERT(!((long)end_addr & ARCH_PAGE_MASK)); | ||
|  | 
 | ||
|  |     for (; iter != end_addr; iter += ARCH_PAGE_SIZE) | ||
|  |     { | ||
|  |         void *page_pa = rt_hw_mmu_v2p(aspace, iter); | ||
|  |         char *page_va = rt_kmem_p2v(page_pa); | ||
|  |         if (page_pa != ARCH_MAP_FAILED && page_va) | ||
|  |         { | ||
|  |             rt_hw_mmu_unmap(aspace, iter, ARCH_PAGE_SIZE); | ||
|  |             rt_pages_free(page_va, 0); | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | static void _pgmgr_pop_range(rt_varea_t varea, void *rm_start, void *rm_end) | ||
|  | { | ||
|  |     void *page_va; | ||
|  | 
 | ||
|  |     RT_ASSERT(!((rt_ubase_t)rm_start & ARCH_PAGE_MASK)); | ||
|  |     RT_ASSERT(!((rt_ubase_t)rm_end & ARCH_PAGE_MASK)); | ||
|  |     while (rm_start != rm_end) | ||
|  |     { | ||
|  |         page_va = rt_hw_mmu_v2p(varea->aspace, rm_start); | ||
|  | 
 | ||
|  |         if (page_va != ARCH_MAP_FAILED) | ||
|  |         { | ||
|  |             page_va -= PV_OFFSET; | ||
|  |             LOG_D("%s: free page %p", __func__, page_va); | ||
|  |             rt_varea_unmap_page(varea, rm_start); | ||
|  |             rt_pages_free(page_va, 0); | ||
|  |         } | ||
|  |         rm_start += ARCH_PAGE_SIZE; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | static const char *_anon_get_name(rt_varea_t varea) | ||
|  | { | ||
|  |     return varea->aspace == _anon_obj_get_backup(varea->mem_obj) ? "anonymous" : "reference"; | ||
|  | } | ||
|  | 
 | ||
|  | /**
 | ||
|  |  * Migration handler on varea re-construction | ||
|  |  */ | ||
|  | 
 | ||
|  | static void _anon_varea_open(struct rt_varea *varea) | ||
|  | { | ||
|  |     rt_aspace_anon_ref_inc(varea->mem_obj); | ||
|  | 
 | ||
|  |     if (varea->aspace == _anon_obj_get_backup(varea->mem_obj)) | ||
|  |         varea->offset = MM_PA_TO_OFF(varea->start); | ||
|  | 
 | ||
|  |     varea->data = NULL; | ||
|  | } | ||
|  | 
 | ||
|  | static void _anon_varea_close(struct rt_varea *varea) | ||
|  | { | ||
|  |     rt_aspace_anon_ref_dec(varea->mem_obj); | ||
|  | 
 | ||
|  |     /* unmap and dereference page frames in the varea region */ | ||
|  |     _pgmgr_pop_all(varea); | ||
|  | } | ||
|  | 
 | ||
|  | static rt_err_t _anon_varea_expand(struct rt_varea *varea, void *new_vaddr, rt_size_t size) | ||
|  | { | ||
|  |     return RT_EOK; | ||
|  | } | ||
|  | 
 | ||
|  | static rt_err_t _anon_varea_shrink(rt_varea_t varea, void *new_start, rt_size_t size) | ||
|  | { | ||
|  |     char *varea_start = varea->start; | ||
|  |     void *rm_start; | ||
|  |     void *rm_end; | ||
|  | 
 | ||
|  |     if (varea_start == (char *)new_start) | ||
|  |     { | ||
|  |         rm_start = varea_start + size; | ||
|  |         rm_end = varea_start + varea->size; | ||
|  |     } | ||
|  |     else /* if (varea_start < (char *)new_start) */ | ||
|  |     { | ||
|  |         RT_ASSERT(varea_start < (char *)new_start); | ||
|  |         rm_start = varea_start; | ||
|  |         rm_end = new_start; | ||
|  |     } | ||
|  | 
 | ||
|  |     _pgmgr_pop_range(varea, rm_start, rm_end); | ||
|  |     return RT_EOK; | ||
|  | } | ||
|  | 
 | ||
|  | static rt_err_t _anon_varea_split(struct rt_varea *existed, void *unmap_start, rt_size_t unmap_len, struct rt_varea *subset) | ||
|  | { | ||
|  |     /* remove the resource in the unmap region, and do nothing for the subset */ | ||
|  |     _pgmgr_pop_range(existed, unmap_start, (char *)unmap_start + unmap_len); | ||
|  | 
 | ||
|  |     _anon_varea_open(subset); | ||
|  |     return RT_EOK; | ||
|  | } | ||
|  | 
 | ||
|  | static rt_err_t _anon_varea_merge(struct rt_varea *merge_to, struct rt_varea *merge_from) | ||
|  | { | ||
|  |     /* do nothing for the varea merge */ | ||
|  |     return RT_EOK; | ||
|  | } | ||
|  | 
 | ||
|  | /**
 | ||
|  |  * Private mapping of address space | ||
|  |  */ | ||
|  | 
 | ||
|  | rt_inline void _map_page_in_varea(rt_aspace_t asapce, rt_varea_t varea, | ||
|  |                                   struct rt_aspace_fault_msg *msg, char *fault_addr) | ||
|  | { | ||
|  |     char *page_va = msg->response.vaddr; | ||
|  |     if (rt_varea_map_page(varea, fault_addr, page_va) == RT_EOK) | ||
|  |     { | ||
|  |         msg->response.status = MM_FAULT_STATUS_OK_MAPPED; | ||
|  |         rt_varea_pgmgr_insert(varea, page_va); | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         msg->response.status = MM_FAULT_STATUS_UNRECOVERABLE; | ||
|  |         LOG_W("%s: failed to map page into varea", __func__); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | /* page frame inquiry or allocation in backup address space */ | ||
|  | static void *_get_page_from_backup(rt_aspace_t backup, rt_base_t offset_in_mobj) | ||
|  | { | ||
|  |     void *frame_pa; | ||
|  |     char *backup_addr; | ||
|  |     rt_varea_t backup_varea; | ||
|  |     void *rc = RT_NULL; | ||
|  | 
 | ||
|  |     backup_addr = (char *)(offset_in_mobj << MM_PAGE_SHIFT); | ||
|  |     backup_varea = rt_aspace_query(backup, backup_addr); | ||
|  | 
 | ||
|  |     if (backup_varea) | ||
|  |     { | ||
|  |         /* synchronize between multiple request by aspace lock of backup */ | ||
|  |         WR_LOCK(backup); | ||
|  | 
 | ||
|  |         frame_pa = rt_hw_mmu_v2p(backup, backup_addr); | ||
|  |         if (frame_pa == ARCH_MAP_FAILED) | ||
|  |         { | ||
|  |             /* provide the page in backup varea */ | ||
|  |             struct rt_aspace_fault_msg msg; | ||
|  |             msg.fault_op = MM_FAULT_OP_WRITE; | ||
|  |             msg.fault_type = MM_FAULT_TYPE_PAGE_FAULT; | ||
|  |             msg.fault_vaddr = backup_addr; | ||
|  |             msg.off = offset_in_mobj; | ||
|  |             rt_mm_fault_res_init(&msg.response); | ||
|  | 
 | ||
|  |             rt_mm_dummy_mapper.on_page_fault(backup_varea, &msg); | ||
|  |             if (msg.response.status != MM_FAULT_STATUS_UNRECOVERABLE) | ||
|  |             { | ||
|  |                 _map_page_in_varea(backup, backup_varea, &msg, backup_addr); | ||
|  |                 if (msg.response.status == MM_FAULT_STATUS_OK_MAPPED) | ||
|  |                 { | ||
|  |                     rc = msg.response.vaddr; | ||
|  |                 } | ||
|  |                 rt_pages_free(msg.response.vaddr, 0); | ||
|  |             } | ||
|  |         } | ||
|  |         else | ||
|  |         { | ||
|  |             rc = rt_kmem_p2v(frame_pa); | ||
|  |             if (!rc) | ||
|  |                 RT_ASSERT(0 && "No kernel address of target page frame"); | ||
|  |         } | ||
|  |         WR_UNLOCK(backup); | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         /* out of range error */ | ||
|  |         LOG_E("(backup_addr=%p): Page request out of range", backup_addr); | ||
|  |     } | ||
|  | 
 | ||
|  |     return rc; | ||
|  | } | ||
|  | 
 | ||
|  | /* get the backup page in kernel for the address in user space */ | ||
|  | static void _fetch_page_for_varea(struct rt_varea *varea, struct rt_aspace_fault_msg *msg, rt_bool_t need_map) | ||
|  | { | ||
|  |     void *paddr; | ||
|  |     char *frame_ka; | ||
|  |     rt_aspace_t curr_aspace = varea->aspace; | ||
|  |     rt_aspace_t backup = _anon_obj_get_backup(varea->mem_obj); | ||
|  | 
 | ||
|  |     RDWR_LOCK(curr_aspace); | ||
|  | 
 | ||
|  |     /**
 | ||
|  |      * if the page is already mapped(this may caused by data race while other | ||
|  |      * thread success to take the lock and mapped the page before this), return okay | ||
|  |      */ | ||
|  |     paddr = rt_hw_mmu_v2p(curr_aspace, msg->fault_vaddr); | ||
|  |     if (paddr == ARCH_MAP_FAILED) | ||
|  |     { | ||
|  |         if (backup == curr_aspace) | ||
|  |         { | ||
|  |             rt_mm_dummy_mapper.on_page_fault(varea, msg); | ||
|  |             if (msg->response.status != MM_FAULT_STATUS_UNRECOVERABLE) | ||
|  |             { | ||
|  |                 /* if backup == curr_aspace, a page fetch always binding with a pte filling */ | ||
|  |                 _map_page_in_varea(backup, varea, msg, msg->fault_vaddr); | ||
|  |                 if (msg->response.status != MM_FAULT_STATUS_UNRECOVERABLE) | ||
|  |                 { | ||
|  |                     rt_pages_free(msg->response.vaddr, 0); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |         else | ||
|  |         { | ||
|  |             frame_ka = _get_page_from_backup(backup, msg->off); | ||
|  |             if (frame_ka) | ||
|  |             { | ||
|  |                 msg->response.vaddr = frame_ka; | ||
|  |                 msg->response.size = ARCH_PAGE_SIZE; | ||
|  |                 if (!need_map) | ||
|  |                 { | ||
|  |                     msg->response.status = MM_FAULT_STATUS_OK; | ||
|  |                 } | ||
|  |                 else | ||
|  |                 { | ||
|  |                     _map_page_in_varea(curr_aspace, varea, msg, msg->fault_vaddr); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         msg->response.status = MM_FAULT_STATUS_OK_MAPPED; | ||
|  |     } | ||
|  |     RDWR_UNLOCK(curr_aspace); | ||
|  | } | ||
|  | 
 | ||
|  | static void _anon_page_fault(struct rt_varea *varea, struct rt_aspace_fault_msg *msg) | ||
|  | { | ||
|  |     _fetch_page_for_varea(varea, msg, RT_TRUE); | ||
|  | } | ||
|  | 
 | ||
|  | static void read_by_mte(rt_aspace_t aspace, struct rt_aspace_io_msg *iomsg) | ||
|  | { | ||
|  |     if (rt_aspace_page_get_phy(aspace, iomsg->fault_vaddr, iomsg->buffer_vaddr) == RT_EOK) | ||
|  |         iomsg->response.status = MM_FAULT_STATUS_OK; | ||
|  | } | ||
|  | 
 | ||
|  | static void _anon_page_read(struct rt_varea *varea, struct rt_aspace_io_msg *iomsg) | ||
|  | { | ||
|  |     rt_aspace_t curr_aspace = varea->aspace; | ||
|  |     rt_aspace_t backup = _anon_obj_get_backup(varea->mem_obj); | ||
|  | 
 | ||
|  |     if (rt_hw_mmu_v2p(curr_aspace, iomsg->fault_vaddr) == ARCH_MAP_FAILED) | ||
|  |     { | ||
|  |         struct rt_aspace_fault_msg msg; | ||
|  |         msg.fault_op = MM_FAULT_OP_READ; | ||
|  |         msg.fault_type = MM_FAULT_TYPE_PAGE_FAULT; | ||
|  |         msg.fault_vaddr = iomsg->fault_vaddr; | ||
|  |         msg.off = iomsg->off; | ||
|  |         rt_mm_fault_res_init(&msg.response); | ||
|  | 
 | ||
|  |         _fetch_page_for_varea(varea, &msg, RT_FALSE); | ||
|  |         if (msg.response.status != MM_FAULT_STATUS_UNRECOVERABLE) | ||
|  |         { | ||
|  |             void *saved_fault_va = iomsg->fault_vaddr; | ||
|  |             iomsg->fault_vaddr = (void *)(iomsg->off << MM_PAGE_SHIFT); | ||
|  |             read_by_mte(backup, iomsg); | ||
|  |             iomsg->fault_vaddr = saved_fault_va; | ||
|  |         } | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         read_by_mte(curr_aspace, iomsg); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | static void write_by_mte(rt_aspace_t aspace, struct rt_aspace_io_msg *iomsg) | ||
|  | { | ||
|  |     if (rt_aspace_page_put_phy(aspace, iomsg->fault_vaddr, iomsg->buffer_vaddr) == RT_EOK) | ||
|  |         iomsg->response.status = MM_FAULT_STATUS_OK; | ||
|  | } | ||
|  | 
 | ||
|  | static void _anon_page_write(struct rt_varea *varea, struct rt_aspace_io_msg *iomsg) | ||
|  | { | ||
|  |     rt_aspace_t from_aspace = varea->aspace; | ||
|  |     rt_aspace_t backup = _anon_obj_get_backup(varea->mem_obj); | ||
|  | 
 | ||
|  |     if (from_aspace != backup) | ||
|  |     { | ||
|  |         /* varea in guest aspace cannot modify the page */ | ||
|  |         iomsg->response.status = MM_FAULT_STATUS_UNRECOVERABLE; | ||
|  |     } | ||
|  |     else if (rt_hw_mmu_v2p(from_aspace, iomsg->fault_vaddr) == ARCH_MAP_FAILED) | ||
|  |     { | ||
|  |         struct rt_aspace_fault_msg msg; | ||
|  |         msg.fault_op = MM_FAULT_OP_WRITE; | ||
|  |         msg.fault_type = MM_FAULT_TYPE_PAGE_FAULT; | ||
|  |         msg.fault_vaddr = iomsg->fault_vaddr; | ||
|  |         msg.off = iomsg->off; | ||
|  |         rt_mm_fault_res_init(&msg.response); | ||
|  | 
 | ||
|  |         _fetch_page_for_varea(varea, &msg, RT_TRUE); | ||
|  |         if (msg.response.status == MM_FAULT_STATUS_OK_MAPPED) | ||
|  |         { | ||
|  |             write_by_mte(backup, iomsg); | ||
|  |         } | ||
|  |         else | ||
|  |         { | ||
|  |             /* mapping failed, report an error */ | ||
|  |             iomsg->response.status = MM_FAULT_STATUS_UNRECOVERABLE; | ||
|  |         } | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         write_by_mte(backup, iomsg); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | static struct rt_private_ctx _priv_obj = { | ||
|  |     .mem_obj.get_name = _anon_get_name, | ||
|  |     .mem_obj.on_page_fault = _anon_page_fault, | ||
|  |     .mem_obj.hint_free = NULL, | ||
|  |     .mem_obj.on_varea_open = _anon_varea_open, | ||
|  |     .mem_obj.on_varea_close = _anon_varea_close, | ||
|  |     .mem_obj.on_varea_shrink = _anon_varea_shrink, | ||
|  |     .mem_obj.on_varea_split = _anon_varea_split, | ||
|  |     .mem_obj.on_varea_expand = _anon_varea_expand, | ||
|  |     .mem_obj.on_varea_merge = _anon_varea_merge, | ||
|  |     .mem_obj.page_read = _anon_page_read, | ||
|  |     .mem_obj.page_write = _anon_page_write, | ||
|  | }; | ||
|  | 
 | ||
|  | rt_inline rt_private_ctx_t rt_private_obj_create_n_bind(rt_aspace_t aspace) | ||
|  | { | ||
|  |     rt_private_ctx_t private_object; | ||
|  |     private_object = rt_malloc(sizeof(struct rt_private_ctx)); | ||
|  |     if (private_object) | ||
|  |     { | ||
|  |         memcpy(&private_object->mem_obj, &_priv_obj, sizeof(_priv_obj)); | ||
|  | 
 | ||
|  |         /* hold a init ref from backup aspace */ | ||
|  |         rt_atomic_store(&private_object->reference, 1); | ||
|  | 
 | ||
|  |         private_object->readonly = RT_FALSE; | ||
|  |         private_object->backup_aspace = aspace; | ||
|  |         aspace->private_object = &private_object->mem_obj; | ||
|  |     } | ||
|  | 
 | ||
|  |     return private_object; | ||
|  | } | ||
|  | 
 | ||
|  | rt_inline rt_mem_obj_t _get_private_obj(rt_aspace_t aspace) | ||
|  | { | ||
|  |     rt_private_ctx_t priv; | ||
|  |     rt_mem_obj_t rc; | ||
|  |     rc = aspace->private_object; | ||
|  |     if (!aspace->private_object) | ||
|  |     { | ||
|  |         priv = rt_private_obj_create_n_bind(aspace); | ||
|  |         if (priv) | ||
|  |         { | ||
|  |             rc = &priv->mem_obj; | ||
|  |             aspace->private_object = rc; | ||
|  |         } | ||
|  |     } | ||
|  |     return rc; | ||
|  | } | ||
|  | 
 | ||
|  | static int _override_map(rt_varea_t varea, rt_aspace_t aspace, void *fault_vaddr, struct rt_aspace_fault_msg *msg, void *page) | ||
|  | { | ||
|  |     int rc = MM_FAULT_FIXABLE_FALSE; | ||
|  |     rt_mem_obj_t private_object; | ||
|  |     rt_varea_t map_varea = RT_NULL; | ||
|  |     rt_err_t error; | ||
|  |     rt_size_t flags; | ||
|  |     rt_size_t attr; | ||
|  | 
 | ||
|  |     LOG_D("%s", __func__); | ||
|  | 
 | ||
|  |     private_object = _get_private_obj(aspace); | ||
|  | 
 | ||
|  |     if (private_object) | ||
|  |     { | ||
|  |         flags = varea->flag | MMF_MAP_FIXED; | ||
|  |         /* don't prefetch and do it latter */ | ||
|  |         flags &= ~MMF_PREFETCH; | ||
|  |         attr = rt_hw_mmu_attr_add_perm(varea->attr, RT_HW_MMU_PROT_USER | RT_HW_MMU_PROT_WRITE); | ||
|  | 
 | ||
|  |         /* override existing mapping at fault_vaddr */ | ||
|  |         error = _mm_aspace_map( | ||
|  |             aspace, &map_varea, &fault_vaddr, ARCH_PAGE_SIZE, attr, | ||
|  |             flags, private_object, MM_PA_TO_OFF(fault_vaddr)); | ||
|  | 
 | ||
|  |         if (error == RT_EOK) | ||
|  |         { | ||
|  |             msg->response.status = MM_FAULT_STATUS_OK; | ||
|  |             msg->response.vaddr = page; | ||
|  |             msg->response.size = ARCH_PAGE_SIZE; | ||
|  |             if (rt_varea_map_with_msg(map_varea, msg) != RT_EOK) | ||
|  |             { | ||
|  |                 LOG_E("%s: fault_va=%p,(priv_va=%p,priv_sz=0x%lx) at %s", __func__, msg->fault_vaddr, map_varea->start, map_varea->size, VAREA_NAME(map_varea)); | ||
|  |                 RT_ASSERT(0 && "should never failed"); | ||
|  |             } | ||
|  |             RT_ASSERT(rt_hw_mmu_v2p(aspace, msg->fault_vaddr) == (page + PV_OFFSET)); | ||
|  |             rc = MM_FAULT_FIXABLE_TRUE; | ||
|  |             rt_varea_pgmgr_insert(map_varea, page); | ||
|  |             rt_pages_free(page, 0); | ||
|  |         } | ||
|  |         else | ||
|  |         { | ||
|  |             /* private object will be release on destruction of aspace */ | ||
|  |             rt_free(map_varea); | ||
|  |         } | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         LOG_I("%s: out of memory", __func__); | ||
|  |         rc = MM_FAULT_FIXABLE_FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |     return rc; | ||
|  | } | ||
|  | 
 | ||
|  | /**
 | ||
|  |  * replace an existing mapping to a private one, this is identical to: | ||
|  |  * => aspace_unmap(ex_varea, ) | ||
|  |  * => aspace_map() | ||
|  |  */ | ||
|  | int rt_varea_fix_private_locked(rt_varea_t ex_varea, void *pa, | ||
|  |                                 struct rt_aspace_fault_msg *msg, | ||
|  |                                 rt_bool_t dont_copy) | ||
|  | { | ||
|  |     /**
 | ||
|  |      * todo: READ -> WRITE lock here | ||
|  |      */ | ||
|  |     void *page; | ||
|  |     void *fault_vaddr; | ||
|  |     rt_aspace_t aspace; | ||
|  |     rt_mem_obj_t ex_obj; | ||
|  |     int rc = MM_FAULT_FIXABLE_FALSE; | ||
|  |     ex_obj = ex_varea->mem_obj; | ||
|  | 
 | ||
|  |     if (ex_obj) | ||
|  |     { | ||
|  |         fault_vaddr = msg->fault_vaddr; | ||
|  |         aspace = ex_varea->aspace; | ||
|  |         RT_ASSERT(!!aspace); | ||
|  | 
 | ||
|  |         /**
 | ||
|  |          * todo: what if multiple pages are required? | ||
|  |          */ | ||
|  |         if (aspace->private_object == ex_obj) | ||
|  |         { | ||
|  |             RT_ASSERT(0 && "recursion"); | ||
|  |         } | ||
|  |         else if (ex_obj->page_read) | ||
|  |         { | ||
|  |             page = rt_pages_alloc_ext(0, PAGE_ANY_AVAILABLE); | ||
|  |             if (page) | ||
|  |             { | ||
|  |                 /** setup message & fetch the data from source object */ | ||
|  |                 if (!dont_copy) | ||
|  |                 { | ||
|  |                     struct rt_aspace_io_msg io_msg; | ||
|  |                     rt_mm_io_msg_init(&io_msg, msg->off, msg->fault_vaddr, page); | ||
|  |                     ex_obj->page_read(ex_varea, &io_msg); | ||
|  |                     /**
 | ||
|  |                      * Note: if ex_obj have mapped into varea, it's still okay since | ||
|  |                      * we will override it latter | ||
|  |                      */ | ||
|  |                     if (io_msg.response.status != MM_FAULT_STATUS_UNRECOVERABLE) | ||
|  |                     { | ||
|  |                         rc = _override_map(ex_varea, aspace, fault_vaddr, msg, page); | ||
|  |                     } | ||
|  |                     else | ||
|  |                     { | ||
|  |                         rt_pages_free(page, 0); | ||
|  |                         LOG_I("%s: page read(va=%p) fault from %s(start=%p,size=%p)", __func__, | ||
|  |                             msg->fault_vaddr, VAREA_NAME(ex_varea), ex_varea->start, ex_varea->size); | ||
|  |                     } | ||
|  |                 } | ||
|  |                 else | ||
|  |                 { | ||
|  |                     rc = _override_map(ex_varea, aspace, fault_vaddr, msg, page); | ||
|  |                 } | ||
|  |             } | ||
|  |             else | ||
|  |             { | ||
|  |                 LOG_I("%s: pages allocation failed", __func__); | ||
|  |             } | ||
|  |         } | ||
|  |         else | ||
|  |         { | ||
|  |             LOG_I("%s: no page read method provided from %s", __func__, VAREA_NAME(ex_varea)); | ||
|  |         } | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         LOG_I("%s: unavailable memory object", __func__); | ||
|  |     } | ||
|  | 
 | ||
|  |     return rc; | ||
|  | } | ||
|  | 
 | ||
|  | int rt_aspace_map_private(rt_aspace_t aspace, void **addr, rt_size_t length, | ||
|  |                           rt_size_t attr, mm_flag_t flags) | ||
|  | { | ||
|  |     int rc; | ||
|  |     rt_mem_obj_t priv_obj; | ||
|  | 
 | ||
|  |     if (flags & MMF_STATIC_ALLOC) | ||
|  |     { | ||
|  |         rc = -RT_EINVAL; | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         priv_obj = _get_private_obj(aspace); | ||
|  |         if (priv_obj) | ||
|  |         { | ||
|  |             flags |= MMF_MAP_PRIVATE; | ||
|  |             flags &= ~MMF_PREFETCH; | ||
|  |             rc = rt_aspace_map(aspace, addr, length, attr, flags, priv_obj, 0); | ||
|  |         } | ||
|  |         else | ||
|  |         { | ||
|  |             rc = -RT_ENOMEM; | ||
|  |         } | ||
|  |     } | ||
|  |     return rc; | ||
|  | } | ||
|  | 
 | ||
|  | static int _release_shared(rt_varea_t varea, void *arg) | ||
|  | { | ||
|  |     rt_aspace_t src = varea->aspace; | ||
|  |     rt_mem_obj_t mem_obj = varea->mem_obj; | ||
|  | 
 | ||
|  |     if (mem_obj != _get_private_obj(src)) | ||
|  |     { | ||
|  |         _varea_uninstall_locked(varea); | ||
|  |         if (VAREA_NOT_STATIC(varea)) | ||
|  |         { | ||
|  |             rt_free(varea); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static rt_err_t _convert_readonly(rt_aspace_t aspace, long base_reference) | ||
|  | { | ||
|  |     rt_mem_obj_t aobj; | ||
|  |     rt_private_ctx_t pctx; | ||
|  |     aobj = _get_private_obj(aspace); | ||
|  |     pctx = _anon_mobj_to_pctx(aobj); | ||
|  | 
 | ||
|  |     LOG_D("Ref(cur=%d,base=%d)", pctx->reference, base_reference); | ||
|  |     rt_aspace_traversal(aspace, _release_shared, 0); | ||
|  |     pctx->readonly = base_reference; | ||
|  |     return 0; | ||
|  | } | ||
|  | 
 | ||
|  | rt_inline void _switch_aspace(rt_aspace_t *pa, rt_aspace_t *pb) | ||
|  | { | ||
|  |     rt_aspace_t temp; | ||
|  |     temp = *pa; | ||
|  |     *pa = *pb; | ||
|  |     *pb = temp; | ||
|  | } | ||
|  | 
 | ||
|  | rt_err_t rt_aspace_fork(rt_aspace_t *psrc, rt_aspace_t *pdst) | ||
|  | { | ||
|  |     rt_err_t rc; | ||
|  |     void *pgtbl; | ||
|  |     rt_aspace_t backup; | ||
|  |     rt_aspace_t src = *psrc; | ||
|  |     rt_aspace_t dst = *pdst; | ||
|  |     long base_reference; | ||
|  | 
 | ||
|  |     pgtbl = rt_hw_mmu_pgtbl_create(); | ||
|  |     if (pgtbl) | ||
|  |     { | ||
|  |         backup = rt_aspace_create(src->start, src->size, pgtbl); | ||
|  |         if (backup) | ||
|  |         { | ||
|  |             WR_LOCK(src); | ||
|  |             base_reference = rt_atomic_load(_anon_obj_get_reference(src->private_object)); | ||
|  |             rc = rt_aspace_duplicate_locked(src, dst); | ||
|  |             WR_UNLOCK(src); | ||
|  | 
 | ||
|  |             if (!rc) | ||
|  |             { | ||
|  |                 /* WR_LOCK(dst) is not necessary since dst is not available currently */ | ||
|  |                 rc = rt_aspace_duplicate_locked(dst, backup); | ||
|  |                 if (!rc) | ||
|  |                 { | ||
|  |                     _switch_aspace(psrc, &backup); | ||
|  |                     _convert_readonly(backup, base_reference); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |         else | ||
|  |         { | ||
|  |             rc = -RT_ENOMEM; | ||
|  |         } | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |         rc = -RT_ENOMEM; | ||
|  |     } | ||
|  | 
 | ||
|  |     return rc; | ||
|  | } |