实验八 分页内存管理

Armv8的地址转换

ARM Cortex-A Series Programmer’s Guide for ARMv8-A 中提到:For EL0 and EL1, there are two translation tables. TTBR0_EL1 provides translations for the bottom of Virtual Address space, which is typically application space and TTBR1_EL1 covers the top of Virtual Address space, typically kernel space. This split means that the OS mappings do not have to be replicated in the translation tables of each task. 即TTBR0指向整个虚拟空间下半部分通常用于应用程序的空间,TTBR1指向虚拟空间的上半部分通常用于内核的空间。其中TTBR0除了在EL1中存在外,也在EL2 and EL3中存在,但TTBR1只在EL1中存在。

TTBR0_ELn 和 TTBR1_ELn 是页表基地址寄存器,地址转换的过程如下所示。

../_images/v2p-translate.svg

In a simple address translation involving only one level of look-up. It assumes we are using a 64KB granule with a 42-bit Virtual Address. The MMU translates a Virtual Address as follows:

  1. If VA[63:42] = 1 then TTBR1 is used for the base address for the first page table. When VA[63:42] = 0, TTBR0 is used for the base address for the first page table.

  2. The page table contains 8192 64-bit page table entries, and is indexed using VA[41:29]. The MMU reads the pertinent level 2 page table entry from the table.

  3. The MMU checks the page table entry for validity and whether or not the requested memory access is allowed. Assuming it is valid, the memory access is allowed.

  4. In the above Figure, the page table entry refers to a 512MB page (it is a block descriptor).

  5. Bits [47:29] are taken from this page table entry and form bits [47:29] of the Physical Address.

  6. Because we have a 512MB page, bits [28:0] of the VA are taken to form PA[28:0]. See Effect of granule sizes on translation tables

  7. The full PA[47:0] is returned, along with additional information from the page table entry.

In practice, such a simple translation process severely limits how finely you can divide up your address space. Instead of using only this first-level translation table, a first-level table entry can also point to a second-level page table.

mmu管理

新建 src/bsp/mmu.c 文件

  1#include "prt_typedef.h"
  2#include "prt_module.h"
  3#include "prt_errno.h"
  4#include "mmu.h"
  5#include "prt_task.h"
  6
  7extern U64 g_mmu_page_begin;
  8extern U64 g_mmu_page_end;
  9
 10extern void os_asm_invalidate_dcache_all(void);
 11extern void os_asm_invalidate_icache_all(void);
 12extern void os_asm_invalidate_tlb_all(void);
 13
 14static mmu_mmap_region_s g_mem_map_info[] = {
 15    {
 16        .virt      = 0x0,
 17        .phys      = 0x0,
 18        .size      = 0x40000000, // 1G size
 19        .max_level = 0x2,  // 不应大于3
 20        .attrs     = MMU_ATTR_DEVICE_NGNRNE | MMU_ACCESS_RWX, // 设备
 21    }, {
 22        .virt      = 0x40000000,
 23        .phys      = 0x40000000,
 24        .size      = 0x40000000, // 1G size
 25        .max_level = 0x2, // // 不应大于3
 26        .attrs     = MMU_ATTR_CACHE_SHARE | MMU_ACCESS_RWX, // 内存
 27    }
 28};
 29
 30static mmu_ctrl_s g_mmu_ctrl = { 0 };
 31
 32// 依据实际情况生成tcr的值,pva_bits返回虚拟地址位数。Translation Control Register (tcr)
 33static U64 mmu_get_tcr(U32 *pips, U32 *pva_bits)
 34{
 35    U64 max_addr = 0;
 36    U64 ips, va_bits;
 37    U64 tcr;
 38    U32 i;
 39    U32 mmu_table_num = sizeof(g_mem_map_info) / sizeof(mmu_mmap_region_s);
 40
 41    // 根据g_mem_map_info表计算所使用的虚拟地址的最大值
 42    for (i = 0; i < mmu_table_num; ++i) {
 43        max_addr = MAX(max_addr, g_mem_map_info[i].virt + g_mem_map_info[i].size);
 44    }
 45
 46    // 依据虚拟地址最大值计算虚拟地址所需的位数,
 47    // 实际上应该分别计算物理地址的ips和虚拟地址的va_bits,而不是如下同时进行。
 48    if (max_addr > (1ULL << MMU_BITS_44)) {
 49        ips = MMU_PHY_ADDR_LEVEL_5;
 50        va_bits = MMU_BITS_48;
 51    } else if (max_addr > (1ULL << MMU_BITS_42)) {
 52        ips = MMU_PHY_ADDR_LEVEL_4;
 53        va_bits = MMU_BITS_44;
 54    } else if (max_addr > (1ULL << MMU_BITS_40)) {
 55        ips = MMU_PHY_ADDR_LEVEL_3;
 56        va_bits = MMU_BITS_42;
 57    } else if (max_addr > (1ULL << MMU_BITS_36)) {
 58        ips = MMU_PHY_ADDR_LEVEL_2;
 59        va_bits = MMU_BITS_40;
 60    } else if (max_addr > (1ULL << MMU_BITS_32)) {
 61        ips = MMU_PHY_ADDR_LEVEL_1;
 62        va_bits = MMU_BITS_36;
 63    } else {
 64        ips = MMU_PHY_ADDR_LEVEL_0;
 65        va_bits = MMU_BITS_32;
 66    }
 67
 68    // 构建Translation Control Register寄存器的值,tcr可控制TTBR0_EL1和TTBR1_EL1的影响
 69    tcr = TCR_EL1_RSVD | TCR_IPS(ips);
 70
 71    if (g_mmu_ctrl.granule == MMU_GRANULE_4K) {
 72        tcr |= TCR_TG0_4K | TCR_SHARED_INNER | TCR_ORGN_WBWA | TCR_IRGN_WBWA;
 73    } else {
 74        tcr |= TCR_TG0_64K | TCR_SHARED_INNER | TCR_ORGN_WBWA | TCR_IRGN_WBWA;
 75    }
 76
 77    tcr |= TCR_T0SZ(va_bits);   // Memory region 2^(64-T0SZ)
 78
 79    if (pips != NULL) {
 80        *pips = ips;
 81    }
 82
 83    if (pva_bits != NULL) {
 84        *pva_bits = va_bits;
 85    }
 86
 87    return tcr;
 88}
 89
 90static U32 mmu_get_pte_type(U64 const *pte)
 91{
 92    return (U32)(*pte & PTE_TYPE_MASK);
 93}
 94
 95// 根据页表项级别计算当个页表项表示的范围(位数)
 96static U32 mmu_level2shift(U32 level)
 97{
 98    if (g_mmu_ctrl.granule == MMU_GRANULE_4K) {
 99        return (U32)(MMU_BITS_12 + MMU_BITS_9 * (MMU_LEVEL_3 - level));
100    } else {
101        return (U32)(MMU_BITS_16 + MMU_BITS_13 * (MMU_LEVEL_3 - level));
102    }
103}
104
105// 根据虚拟地址找到对应级别的页表项
106static U64 *mmu_find_pte(U64 addr, U32 level)
107{
108    U64 *pte = NULL;
109    U64 idx;
110    U32 i;
111
112    if (level < g_mmu_ctrl.start_level) {
113        return NULL;
114    }
115
116    pte = (U64 *)g_mmu_ctrl.tlb_addr;
117
118    // 从顶级页表开始,直到找到所需level级别的页表项或返回NULL
119    for (i = g_mmu_ctrl.start_level; i < MMU_LEVEL_MAX; ++i) {
120        // 依据级别i计算页表项在页表中的索引idx
121        if (g_mmu_ctrl.granule == MMU_GRANULE_4K) {
122            idx = (addr >> mmu_level2shift(i)) & 0x1FF;
123        } else {
124            idx = (addr >> mmu_level2shift(i)) & 0x1FFF;
125        }
126
127        // 找到对应的页表项
128        pte += idx;
129
130        // 如果是需要level级别的页表项则返回
131        if (i == level) {
132            return pte;
133        }
134
135        // 从顶级页表开始找,
136        // 找到当前级别页表项不是有效的(无效或是block entry)直接返回NULL
137        if (mmu_get_pte_type(pte) != PTE_TYPE_TABLE) {
138            return NULL;
139        }
140
141        // 不是所需级别但pte指向有效,依据页表粒度准备访问下级页表
142        if (g_mmu_ctrl.granule == MMU_GRANULE_4K) {
143            pte = (U64 *)(*pte & PTE_TABLE_ADDR_MARK_4K);
144        } else {
145            pte = (U64 *)(*pte & PTE_TABLE_ADDR_MARK_64K);
146        }
147    }
148
149    return NULL;
150}
151
152// 根据页表粒度在页表区域新建一个页表,返回页表起始位置
153static U64 *mmu_create_table(void)
154{
155    U32 pt_len;
156    U64 *new_table = (U64 *)g_mmu_ctrl.tlb_fillptr;
157
158    if (g_mmu_ctrl.granule == MMU_GRANULE_4K) {
159        pt_len = MAX_PTE_ENTRIES_4K * sizeof(U64);
160    } else {
161        pt_len = MAX_PTE_ENTRIES_64K * sizeof(U64);
162    }
163
164    // 根据页表粒度在页表区域新建一个页表(4K或64K)
165    g_mmu_ctrl.tlb_fillptr += pt_len;
166
167    if (g_mmu_ctrl.tlb_fillptr - g_mmu_ctrl.tlb_addr > g_mmu_ctrl.tlb_size) {
168        return NULL;
169    }
170
171    // 初始化页表全为0,因此该页表所有的页表项初始都是PTE_TYPE_FAULT
172    // (void)memset_s((void *)new_table, MAX_PTE_ENTRIES_64K * sizeof(U64), 0, pt_len);
173    U64 *tmp = new_table;
174    for(int i = 0; i < pt_len; i+=sizeof(U64)){
175        *tmp = 0;
176        tmp++;
177    }
178
179    return new_table;
180}
181
182static void mmu_set_pte_table(U64 *pte, U64 *table)
183{
184    // https://developer.arm.com/documentation/den0024/a/The-Memory-Management-Unit/Translation-tables-in-ARMv8-A/AArch64-descriptor-format
185    *pte = PTE_TYPE_TABLE | (U64)table;
186}
187
188// 依据mmu_mmap_region_s填充pte
189static S32 mmu_add_map_pte_process(mmu_mmap_region_s const *map, U64 *pte, U64 phys, U32 level)
190{
191    U64 *new_table = NULL;
192
193    // 属于上级页表项
194    if (level < map->max_level) {
195        // 如果页表项指向无效,新建一个页表且pte指向该页表
196        if (mmu_get_pte_type(pte) == PTE_TYPE_FAULT) {
197            // 新建一个页表
198            new_table = mmu_create_table();
199            if (new_table == NULL) {
200                return -1;
201            }
202            // pte指向下级页表
203            mmu_set_pte_table(pte, new_table);
204        } //else: 如果页表项指向有效,不做任何处理。
205    } else if (level == MMU_LEVEL_3) { // 最多4级页表(0,1,2,3),这是最后一级页表项,最后L3级页表项定义略有不同
206        *pte = phys | map->attrs | PTE_TYPE_PAGE;
207    } else {
208        // 这里的情况:等于map->max_level且不到最后L3级页表,依据mmu_mmap_region_s的配置作为block entry类型直接指向物理区域
209        *pte = phys | map->attrs | PTE_TYPE_BLOCK;
210    }
211
212    return 0;
213}
214
215// 依据 mmu_mmap_region_s 的定义,生成 mmu 映射
216static S32 mmu_add_map(mmu_mmap_region_s const *map)
217{
218    U64 virt = map->virt;
219    U64 phys = map->phys;
220    U64 max_level = map->max_level;
221    U64 start_level = g_mmu_ctrl.start_level;
222    U64 block_size = 0;
223    U64 map_size = 0;
224    U32 level;
225    U64 *pte = NULL;
226    S32 ret;
227
228    if (map->max_level <= start_level) {
229        return -2;
230    }
231
232    while (map_size < map->size) {
233        // 从起始级别start_level开始遍历页表。注意起始级别页表肯定存在
234        for (level = start_level; level <= max_level; ++level) {
235            // 找到对应level的页表项
236            pte = mmu_find_pte(virt, level);
237            if (pte == NULL) {
238                return -3;
239            }
240
241            // 如果为上级页表项且pte指向无效,新建下级页表且pte指向该新建的页表
242            // 如果为最低页表项或到达设定级别页表项,直接设置页表项的值
243            ret = mmu_add_map_pte_process(map, pte, phys, level);
244            if (ret) {
245                return ret;
246            }
247
248            if (level != start_level) {
249                block_size = 1ULL << mmu_level2shift(level);
250            }
251        }
252
253        virt += block_size;
254        phys += block_size;
255        map_size += block_size;
256    }
257
258    return 0;
259}
260
261static inline void mmu_set_ttbr_tcr_mair(U64 table, U64 tcr, U64 attr)
262{
263    OS_EMBED_ASM("dsb sy");
264
265    OS_EMBED_ASM("msr ttbr0_el1, %0" : : "r" (table) : "memory");
266    // OS_EMBED_ASM("msr ttbr1_el1, %0" : : "r" (table) : "memory");
267    OS_EMBED_ASM("msr tcr_el1, %0" : : "r" (tcr) : "memory");
268    OS_EMBED_ASM("msr mair_el1, %0" : : "r" (attr) : "memory");
269
270    OS_EMBED_ASM("isb");
271}
272
273static U32 mmu_setup_pgtables(mmu_mmap_region_s *mem_map, U32 mem_region_num, U64 tlb_addr, U64 tlb_len, U32 granule)
274{
275    U32 i;
276    U32 ret;
277    U64 tcr;
278    U64 *new_table = NULL;
279
280    g_mmu_ctrl.tlb_addr = tlb_addr;
281    g_mmu_ctrl.tlb_size = tlb_len;
282    g_mmu_ctrl.tlb_fillptr = tlb_addr;
283    g_mmu_ctrl.granule = granule;
284    g_mmu_ctrl.start_level = 0;
285
286    tcr = mmu_get_tcr(NULL, &g_mmu_ctrl.va_bits);
287
288    // 依据页表粒度和虚拟地址位数计算地址转换起始级别
289    if (g_mmu_ctrl.granule == MMU_GRANULE_4K) {
290        if (g_mmu_ctrl.va_bits < MMU_BITS_39) {
291            g_mmu_ctrl.start_level = MMU_LEVEL_1;
292        } else {
293            g_mmu_ctrl.start_level = MMU_LEVEL_0;
294        }
295    } else {
296        if (g_mmu_ctrl.va_bits <= MMU_BITS_36) {
297            g_mmu_ctrl.start_level = MMU_LEVEL_2;
298        } else {
299            g_mmu_ctrl.start_level = MMU_LEVEL_1;
300            return 3;
301        }
302    }
303
304    // 创建一个顶级页表,不一定是L0
305    new_table = mmu_create_table();
306    if (new_table == NULL) {
307        return 1;
308    }
309
310    for (i = 0; i < mem_region_num; ++i) {
311        ret = mmu_add_map(&mem_map[i]);
312        if (ret) {
313            return ret;
314        }
315    }
316
317    mmu_set_ttbr_tcr_mair(g_mmu_ctrl.tlb_addr, tcr, MEMORY_ATTRIBUTES);
318
319    return 0;
320}
321
322static S32 mmu_setup(void)
323{
324    S32 ret;
325    U64 page_addr;
326    U64 page_len;
327
328    page_addr = (U64)&g_mmu_page_begin;
329    page_len = (U64)&g_mmu_page_end - (U64)&g_mmu_page_begin;
330
331    ret = mmu_setup_pgtables(g_mem_map_info, (sizeof(g_mem_map_info) / sizeof(mmu_mmap_region_s)),
332                            page_addr, page_len, MMU_GRANULE_4K);
333    if (ret) {
334        return ret;
335    }
336
337    return 0;
338}
339
340
341
342S32 mmu_init(void)
343{
344    S32 ret;
345
346    ret = mmu_setup();
347    if (ret) {
348        return ret;
349    }
350
351    os_asm_invalidate_dcache_all();
352    os_asm_invalidate_icache_all();
353    os_asm_invalidate_tlb_all();
354
355    set_sctlr(get_sctlr() | CR_C | CR_M | CR_I);
356
357    return 0;
358}

新建 src/bsp/mmu.h, 该文件可从 这里 下载

新建 src/bsp/cache_asm.S, 该文件可从 这里 下载

启用 mmu

start.S 中在 B OsEnterMain 之前启用 MMU

1// 启用 MMU
2BL     mmu_init
3// 进入 main 函数
4B      OsEnterMain

提示

将新增文件加入构建系统

提示

通过调试确保你真的启动了 MMU

lab8 作业

作业1

启用 TTBR1 ,将地址映射到虚拟地址的高半部分,使用高地址访问串口 修改后:(1)src/bsp/print.c中

#define UART_0_REG_BASE (0xffffffff00000000 + 0x09000000)

(2)src/bsp/hwi_init.c 中

#define GIC_DIST_BASE              (0xffffffff00000000 + 0x08000000)
#define GIC_CPU_BASE               (0xffffffff00000000 + 0x08010000)

程序可以正常运行。(GIC_DIST_BASE 和 GIC_CPU_BASE 的高位多少个f与你对MMU的配置有关)