实验八 分页内存管理
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 是页表基地址寄存器,地址转换的过程如下所示。
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:
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.
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.
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.
In the above Figure, the page table entry refers to a 512MB page (it is a block descriptor).
Bits [47:29] are taken from this page table entry and form bits [47:29] of the Physical Address.
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
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的配置有关)