/* * Copyright (c) 2006-2023, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2023-09-07 zmshahaha the first version */ #include "mm_memblock.h" #include "mm_page.h" #include "mm_aspace.h" #include #define DBG_TAG "mm.memblock" #define DBG_LVL DBG_INFO #include #define PHYS_ADDR_MAX (~((rt_size_t)0)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #ifndef RT_INIT_MEMORY_REGIONS #define RT_INIT_MEMORY_REGIONS 128 #endif static struct rt_mmblk_reg _regions[RT_INIT_MEMORY_REGIONS]; static int _hint_idx; static struct rt_memblock mmblk_memory; static struct rt_memblock mmblk_reserved; struct rt_memblock *rt_memblock_get_memory(void) { return &mmblk_memory; } struct rt_memblock *rt_memblock_get_reserved(void) { return &mmblk_reserved; } rt_inline struct rt_mmblk_reg *_next_region(struct rt_mmblk_reg *prev) { if (prev && prev->node.next) { return rt_slist_entry(prev->node.next, struct rt_mmblk_reg, node); } else { return RT_NULL; } } static struct rt_mmblk_reg *_alloc_memreg(struct rt_mmblk_reg *prev) { for (int i =_hint_idx; i < RT_INIT_MEMORY_REGIONS; i++) { if (_regions[i].alloc == RT_FALSE) { rt_slist_insert(&(prev->node), &(_regions[i].node)); _regions[i].alloc = RT_TRUE; _hint_idx = i + 1; return &_regions[i]; } } for (int i = 0; i < _hint_idx; i++) { if (_regions[i].alloc == RT_FALSE) { rt_slist_insert(&(prev->node), &(_regions[i].node)); _regions[i].alloc = RT_TRUE; _hint_idx = i + 1; return &_regions[i]; } } return RT_NULL; } static void _free_memreg(struct rt_mmblk_reg *prev) { struct rt_mmblk_reg *next = _next_region(prev); next->alloc = RT_FALSE; rt_slist_remove(&(prev->node), prev->node.next); } static rt_err_t _reg_insert_after(struct rt_mmblk_reg *prev, rt_region_t *reg, mmblk_flag_t flags) { struct rt_mmblk_reg *new_reg = _alloc_memreg(prev); if (!new_reg) { LOG_E("No enough space"); return -RT_ENOMEM; } rt_memcpy(&(new_reg->memreg), reg, sizeof(*reg)); new_reg->flags = flags; return RT_EOK; } rt_inline void _reg_remove_after(struct rt_mmblk_reg *prev) { _free_memreg(prev); } /* adding overlapped regions is banned */ static rt_err_t _memblock_add_range(struct rt_memblock *memblock, const char *name, rt_size_t start, rt_size_t end, mm_flag_t flag) { struct rt_mmblk_reg *reg = RT_NULL, *reg_next = RT_NULL; rt_slist_t sentinel; rt_region_t new_region; if (start >= end) return -RT_EINVAL; sentinel.next = &(memblock->reg_list); /* find suitable place */ rt_slist_for_each_entry(reg, &sentinel, node) { reg_next = _next_region(reg); if (reg_next == RT_NULL) break; rt_size_t rstart = reg_next->memreg.start; rt_size_t rend = reg_next->memreg.end; /* not overlap */ if (rstart >= end) break; if (rend <= start) continue; /* overlap */ LOG_E("region to add %s: [%p-%p) overlap with existing region %s: [%p-%p)",\ name, start, end, reg_next->memreg.name, rstart, rend); return -RT_EINVAL; } /* insert the region */ new_region.name = name; new_region.start = start; new_region.end = end; return _reg_insert_after(reg, &new_region, flag); } rt_err_t rt_memblock_add_memory(const char *name, rt_size_t start, rt_size_t end, mmblk_flag_t flags) { LOG_D("add physical address range [%p-%p) with flag 0x%x" \ " to overall memory regions\n", base, base + size, flag); return _memblock_add_range(&mmblk_memory, name, start, end, flags); } rt_err_t rt_memblock_reserve_memory(const char *name, rt_size_t start, rt_size_t end, mmblk_flag_t flags) { LOG_D("add physical address range [%p-%p) to reserved memory regions\n",\ base, base + size); return _memblock_add_range(&mmblk_reserved, name, start, end, flags); } /* [*start_reg, *end_reg) is the isolated range */ static rt_err_t _memblock_separate_range(struct rt_memblock *memblock, rt_size_t start, rt_size_t end, struct rt_mmblk_reg **start_reg, struct rt_mmblk_reg **end_reg) { struct rt_mmblk_reg *reg = RT_NULL; rt_region_t new_region; rt_err_t err = RT_EOK; *start_reg = *end_reg = RT_NULL; rt_slist_for_each_entry(reg, &(memblock->reg_list), node) { rt_size_t rstart = reg->memreg.start; rt_size_t rend = reg->memreg.end; if (rstart >= end) break; if (rend <= start) continue; /* the beginning of the range separates its respective region */ if (rstart < start) { new_region.start = start; new_region.end = rend; new_region.name = reg->memreg.name; err = _reg_insert_after(reg, &new_region, reg->flags); if (err != RT_EOK) return err; reg->memreg.end = start; *start_reg = _next_region(reg); *end_reg = _next_region(*start_reg); } /* the endpoint of the range separates its respective region */ else if (rend > end) { new_region.start = end; new_region.end = rend; new_region.name = reg->memreg.name; err = _reg_insert_after(reg, &new_region, reg->flags); if (err != RT_EOK) return err; reg->memreg.end = end; *end_reg = _next_region(reg); break; } /* reg->next is fully contained in range */ else { if (!*end_reg) *start_reg = reg; *end_reg = _next_region(reg); } } return err; } static void _memblock_set_flag(struct rt_mmblk_reg *start_reg, struct rt_mmblk_reg *end_reg, \ mmblk_flag_t flags) { if (start_reg == RT_NULL) return; for (struct rt_mmblk_reg *iter = start_reg; iter != end_reg; iter = _next_region(iter)) { iter->flags |= flags; } } static void _next_free_region(struct rt_mmblk_reg **m, struct rt_mmblk_reg **r, mmblk_flag_t flags, rt_size_t *out_start, rt_size_t *out_end) { /* memory related data */ rt_size_t m_start = 0; rt_size_t m_end = 0; /* reserved related data */ rt_size_t r_start = 0; rt_size_t r_end = 0; struct rt_mmblk_reg *r_sentinel = rt_slist_entry(&(mmblk_reserved.reg_list), struct rt_mmblk_reg, node); for (; *m != RT_NULL; *m = _next_region(*m)) { if ((*m)->flags != flags) continue; m_start = (*m)->memreg.start; m_end = (*m)->memreg.end; for (; *r != RT_NULL; *r = _next_region(*r)) { /* * r started with _resreg_guard * Find the complement of reserved memblock. * For example, if reserved memblock is following: * * 0:[8-16), 1:[32-48), 2:[128-130) * * The upper 32bit indexes the following regions. * * 0:[0-8), 1:[16-32), 2:[48-128), 3:[130-MAX) * * So we can find intersecting region other than excluding. */ r_start = (*r == r_sentinel) ? 0 : (*r)->memreg.end; r_end = (_next_region(*r)) ? _next_region(*r)->memreg.start : PHYS_ADDR_MAX; /* two reserved region are adjacent */ if (r_start == r_end) continue; if (r_start >= m_end) break; if (m_start < r_end) { *out_start = MAX(m_start, r_start); *out_end = MIN(m_end, r_end); if (m_end <= r_end) *m = _next_region(*m); else *r = _next_region(*r); return; } } } /* all regions found */ *m = rt_slist_entry(&(mmblk_memory.reg_list), struct rt_mmblk_reg, node); } /* for each region in memory with flags and not reserved */ #define for_each_free_region(m, r, flags, p_start, p_end) \ m = rt_slist_entry(&(mmblk_memory.reg_list.next), struct rt_mmblk_reg, node); \ r = rt_slist_entry(&(mmblk_reserved.reg_list), struct rt_mmblk_reg, node); \ for (_next_free_region(&m, &r, flags, p_start, p_end); \ m != rt_slist_entry(&(mmblk_memory.reg_list), struct rt_mmblk_reg, node); \ _next_free_region(&m, &r, flags, p_start, p_end)) /* merge normal memory regions */ static void _memblock_merge_memory(void) { struct rt_mmblk_reg *reg = RT_NULL; rt_slist_for_each_entry(reg, &(mmblk_memory.reg_list), node) { while (_next_region(reg) && reg->flags == _next_region(reg)->flags && reg->memreg.end == _next_region(reg)->memreg.start) { reg->memreg.end = _next_region(reg)->memreg.end; _reg_remove_after(reg); } } } void rt_memblock_setup_memory_environment(void) { struct rt_mmblk_reg *iter = RT_NULL, *start_reg = RT_NULL, *end_reg = RT_NULL; rt_region_t reg = {0}; rt_size_t mem = 0; struct rt_mmblk_reg *m, *r; void *err; _memblock_merge_memory(); LOG_I("System memory:"); rt_slist_for_each_entry(iter, &(mmblk_memory.reg_list), node) { LOG_I(" %-*.s [%p, %p]", RT_NAME_MAX, iter->memreg.name, iter->memreg.start, iter->memreg.end); } LOG_I("Reserved memory:"); rt_slist_for_each_entry(iter, &(mmblk_reserved.reg_list), node) { LOG_I(" %-*.s [%p, %p]", RT_NAME_MAX, iter->memreg.name, iter->memreg.start, iter->memreg.end); if (iter->flags != MEMBLOCK_NONE) { _memblock_separate_range(&mmblk_memory, iter->memreg.start, iter->memreg.end, &start_reg, &end_reg); _memblock_set_flag(start_reg, end_reg, iter->flags); } } /* install usable memory to system page */ for_each_free_region(m, r, MEMBLOCK_NONE, ®.start, ®.end) { reg.start = RT_ALIGN(reg.start, ARCH_PAGE_SIZE); reg.end = RT_ALIGN_DOWN(reg.end, ARCH_PAGE_SIZE); if (reg.start >= reg.end) continue; LOG_I("physical memory region [%p-%p] installed to system page", reg.start, reg.end); reg.start -= PV_OFFSET; reg.end -= PV_OFFSET; struct rt_mm_va_hint hint = {.flags = MMF_MAP_FIXED, .limit_start = rt_kernel_space.start, .limit_range_size = rt_kernel_space.size, .map_size = reg.end - reg.start, .prefer = (void *)reg.start}; rt_aspace_map_phy(&rt_kernel_space, &hint, MMU_MAP_K_RWCB, (reg.start + PV_OFFSET) >> MM_PAGE_SHIFT, &err); rt_page_install(reg); mem += reg.end - reg.start; } LOG_I("%ld MB memory installed to system page", mem/1000000); } #ifdef UTEST_MM_API_TC /* functions below are only used for utest */ void rt_memblock_merge(void) { _memblock_merge_memory(); } static struct rt_mmblk_reg *mem; static struct rt_mmblk_reg *res; void rt_memblock_next_free_region_init(void) { mem = rt_slist_entry(&(mmblk_memory.reg_list.next), struct rt_mmblk_reg, node); res = rt_slist_entry(&(mmblk_reserved.reg_list), struct rt_mmblk_reg, node); } void rt_memblock_next_free_region(mmblk_flag_t flags, rt_size_t *out_start, rt_size_t *out_end) { _next_free_region(&mem, &res, flags, out_start, out_end); } rt_bool_t rt_memblock_is_last_free(void) { return mem == rt_slist_entry(&(mmblk_memory.reg_list), struct rt_mmblk_reg, node); } #endif /* UTEST_MM_API_TC */