一个简易bootloader框架

本文

主要介绍一个基于ARM的bootloader程序框架,其结构和功能都比较简单,主要作为CPU验证中裸机程序代码框架使用。

版本 说明
0.1 初版发布

背景

在CPU验证中,出于各种原因,经常需要通过汇编程序来实现定向测试,不管是TOP验证环境还是UT验证环境,都有这样的需求。

TOP环境中,需要提供elf文件并生成二进制码存入指令存储器,然后指定复位地址,启动后整个CPU就可以运行起来了。通过编写汇编程序并编译为elf文件,可实现CPU的定向测试。有了elf文件只是解决了激励的问题,关于功能正确性的保证还需要ARM提供的仿真模型fastmodel。

UT环境中,有些dut是与指令耦合度比较高的模块,使用汇编程序的方式实现定向测试也是非常必要的。这里与指令耦合度高是指,容易从指令码转换为模块接口信号,比如DPU模块。DPU前接IFU模块,IFU对指令码进行预译码后交给DPU,所以只需要将IFU的预译码逻辑植入DPU验证环境,就可以实现将指令码转为DPU的接口信号,从而实现以汇编程序实现DPU的定向测试。

编译工具

  • A64:aarch64-linux-gnu
  • A32:armv8l-linux-gnueabihf

下载网址: https://releases.linaro.org/components/toolchain/binaries/

Bootloader需要做什么

我对Linux Kernel的了解比较粗浅,所以下文内容仅供参考。

  1. 初始化系统寄存器
  2. 设置异常向量表
  3. 初始化栈和堆
  4. 检查和初始化外设
  5. 初始化内存管理、开启mmu和cache
  6. ……

此bootloader框架主要作为CPU验证中裸机程序代码框架使用,所以只需要完成系统寄存器的初始化和异常向量表,如果会用到c程序则需要初始化栈,其他功能在此bootloader框架中并未实现。

文件结构

simple_bootloader
├── a32_test
│   ├── a32_test.s
│   ├── link.lds
│   ├── Makefile
│   ├── your_testcode.s
│   └── thumb_test.inc
├── a64_test
│   ├── a64_test.s
│   ├── link.lds
│   ├── Makefile
│   └── your_testcode.s
├── link.lds
├── Makefile
├── Makefile.def
└── system
    ├── asmlib.inc
    ├── evt.inc
    ├── Makefile
    ├── start.s
    └── sysregs_init.inc

整个代码环境分为三个部分:System、A64_test、A32_test。

  • System:整个程序执行流程的控制。
  • A64_test:A64指令类型的测试程序。
  • A32_test:A32以及Thumb类型的测试程序。

AArch64与AArch32混合编程

ARMv8中,AArch64和AArch32是通过异常进行切换的。而A32和T32是通过bx指令进行切换的。如下图:

也就是CPU启动是在EL3,处于AArch64,在EL3设置EL2的架构为AArch32,设置好返回地址,通过ERET指令,切换到EL2,就可以执行A32指令了。

这里会有一个问题,AArch64和AArch32的编译链接工具不同,无法进行混合的链接。解决办法是将A32测试程序生成elf文件再生成bin文件,在链接时链接A32的bin文件。这里干脆把A64的测试程序也使用bin文件链接的方式,而顶层只保留系统文件,这样结够更加清晰。另外A64测试程序、A32测试程序、系统程序相互独立,可以在单独的环境进行编译调试。

system目录

system目录下的文件主要实现对指令执行流程的控制、系统寄存器的配置、异常向量表和异常等级切换、A64和A32执行状态切换等内容。

start.s文件

start.s是主程序,也是整个程序的入口,主要功能是整个程序执行流程的控制,执行流程如下:

  1. 程序启动时处于A64状态,在EL3异常等级。
  2. 关闭多核,仅开启cpu0运行。
  3. 系统寄存器配置。
  4. 设置栈空间和进行栈指针初始化。
  5. 切换至EL0状态。
  6. (可配置编译选项)在EL0下执行A64测试程序,并打印执行结果(TEST PASSED / FAILED)。
  7. 通过SVC指令切换至EL1状态。
  8. (可配置编译选项)在EL1下执行A64测试程序,并打印执行结果(TEST PASSED / FAILED)。
  9. 通过HVC指令切换至EL2状态。
  10. (可配置编译选项)在EL2下执行A64测试程序,并打印执行结果(TEST PASSED / FAILED)。
  11. 通过SMC指令切换至EL3状态。
  12. (可配置编译选项)在EL3下执行A64测试程序,并打印执行结果(TEST PASSED / FAILED)。
  13. (可配置编译选项)设置scr_el3,通过异常返回,切换至A32状态,并执行A32测试程序以及Thumb测试程序。
  14. 程序执行结束。

以下是代码内容:

  • global声明标号为全局,这样其他文件程序可以直接使用该标号
  • 这里通过svc hvc smc来产生异常,改变exception level,来实现在不同level下测试程序,注意这里exception level的改变需要配合异常处理程序来实现。
  • start程序就是整个程序的入口,并在这里调用其他子程序。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
        .global _start

_start:
        bl _disable_mp
        bl _sysregs_init
        bl _stack_init
        bl _el3_to_el0_ns

        //----------------------------
        .global _sys_a64_test
_sys_a64_test:
        //el0 test
        .if TEST_A64_EL0==1
        bl _a64_test
        bl _print_result
        .endif

        //el1 test
        svc #0x0
        .if TEST_A64_EL1==1
        bl _main_test
        bl _print_result
        .endif

        //el2 test
        hvc #0x0
        .if TEST_A64_EL2==1
        bl _main_test
        bl _print_result
        .endif

        //el3 test
        smc #0x0
        .if TEST_A64_EL3==1
        bl _main_test
        bl _print_result
        .endif
        //----------------------------


        //----------------------------
        .if TEST_A32==1
        //el3 to usermode
        mov x9, #0x30          //RES1
        orr x9, x9, #(1 << 0)  //non-secure
        orr x9, x9, #(1 << 8)  //HVC enable
        orr x9, x9, #(1 << 10) //set aarch32 el2
        msr scr_el3, x9
        mov x9, #0x1d0
        msr spsr_el3, x9
        adr x9, _a32_test
        msr elr_el3, x9
        eret
        //run a32 test
        .endif

        .global _back_from_a32
_back_from_a32:
        bl _print_result
        //----------------------------

        bl _end_cpu
        b .

        .include "./asmlib.inc"
        .include "./sysregs_init.inc"
        .include "./evt.inc"

asmlib.inc文件

asmlib.inc是各个子程序的集合,通过start.s中bl实现子程序调用,包含如下:

  1. 关闭多核,仅开启cpu0运行(目的是使fastmodel的trace信息中只保留主核)。
  2. 栈的初始化(初始化栈之后才可以使用c语言)。
  3. 打印pass或fail(通过x0来判断是否fail)。
  4. 结束程序执行。
  5. el3切换到el0。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
        //{{{ 关闭非主核
        .global _disable_mp
_disable_mp:
        mrs x9,  mpidr_el1
        and x9,  x9, #0xff
        cbnz x9,  _cpu_sleep
        ret
_cpu_sleep:
        wfi
        b _cpu_sleep
        //}}}


        //{{{ 初始化栈
        .global _stack_init
_stack_init:
        //STACK_TOP_ADDR  = BOARD_BASE_ADDR + BOARD_MEM_SIZE - RESERVE_MEM_TOP
        //                = RVBAR + 2G - 1M
        //EACH_STACK_SIZE = 64M
        ldr x9, =RVBAR
        ldr x10, =0x7ff00000
        add x0, x9, x10      //x9: STACK_TOP_ADDR
        ldr x1, =0x04000000  //x1: EACH_STACK_SIZE
        mrs x9, CurrentEL
        cmp x9, #(0b11 << 2)
        b.eq 3f
        cmp x9, #(0b10 << 2)
        b.eq 2f
        cmp x9, #(0b01 << 2)
        b.eq 1f
        b .  //系统启动不会在el0状态,这里设置个死循环

3:
        mov sp, x0 //sp_el3
        sub x0, x0, x1
2:
        msr sp_el2, x0
        sub x0, x0, x1
1:
        msr sp_el1, x0
        sub x0, x0, x1
0:
        msr sp_el0, x0

        ret
        //}}}

        //{{{ 打印测试结果
        .global _print_result
_print_result:
        .if PRINT_RESULT==1
        and x9, x0, #0xff
        sub x9, x9, #0xff
        cbz x9, _test_fail
        b _test_pass
        .else
        ret
        .endif

_test_pass:
        ldr x9, =0x13000000 //Tube address
        adr x10, _pass_message_
        ldrb w11, [x10], #1
_loop_print_pass:
        strb w11, [x9]
        ldrb w11, [x10], #1
        cbnz w11, _loop_print_pass
        ret

_test_fail:
        ldr x9, =0x13000000 //Tube address
        adr x10, _fail_message_
        ldrb w11, [x10], #1
_loop_print_fail:
        strb w11, [x9]
        ldrb w11, [x10], #1
        cbnz w11, _loop_print_fail
        ret

        .align 2
_pass_message_:   .asciz "*** TEST PASSED ***\n"
_fail_message_:   .asciz "*** TEST FAILED ***\n"
        //}}}
        .align 2

        //{{{ 结束CPU
        .global _end_cpu
_end_cpu:
        mov w9, #0x04
        ldr x10, =0x13000000 //Tube address
        strb w9, [x10]
        dsb sy
        ret
        //}}}

        //{{{ el3切换到el0_ns
        .global _el3_to_el0_ns
_el3_to_el0_ns:
        ldr x9, =0x000002c0
        msr spsr_el3, x9
        adr x9, _sys_a64_test
        msr elr_el3, x9
        eret
        //}}}

sysregs_init.inc文件

sysregs_init.inc文件主要是对系统寄存器进行初始化配置,这里要结合设计进行适配,使设计和程序运行状态保持一致。具体相关寄存器的含义请查看ARM手册。

以下是代码内容:

  • 注意:这里 “b.eq 1f” 的用法,1是指标号,f代表向下寻找,这属于常用的汇编技巧。
  • 注意:系统寄存器配置后需要使用isb指令隔离命令,是因为系统寄存器可能存在多个备份来解决负载问题,使用isb指令可以实现各个副本的更新,使其保持一致。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
        //{{{ 初始化系统寄存器
        .global _sysregs_init
_sysregs_init:
        mrs x9, CurrentEL
        cmp x9, #(0b11 << 2)
        b.eq 3f
        cmp x9, #(0b10 << 2)
        b.eq 2f
        cmp x9, #(0b01 << 2)
        b.eq 1f
        b .  //系统启动不会在el0状态,这里设置个死循环

3:
        //scr_el3
        ldr x9, =0x0000000000000430 //rtl默认的初始值
        msr scr_el3, x9
        mov x10, #0x1
        mov x11, #0x0
        mrs x9, scr_el3
        bfi x9, x10, #8, #1  // set scr_el3.hce to 1
        bfi x9, x10, #0, #1  // set scr_el3.ns to 1
        bfi x9, x10, #10, #1 // set scr_el3.rw to 1
        msr scr_el3, x9

        //cptr_el3
        ldr x9, =0x0000000000000000 //rtl默认的初始值
        msr cptr_el3, x9
        mov x10, #0x1
        mov x11, #0x0
        mrs x9, cptr_el3
        bfi x9, x11, #10, #1 // set scr_el3.tfp to 0
        msr scr_el3, x9

        //sctlr_el3
        ldr x9, =0x0000000000c50838 //rtl默认的初始值
        msr sctlr_el3, x9

        // exception entry vector addr
        ldr x9, =_vector_table_route_to_el3
        msr vbar_el3, x9

2:
        //hcr_el2
        ldr x9, =0x0000000080000002 //rtl默认的初始值
        msr hcr_el2, x9
        mov x10, #0x1
        mov x11, #0x0
        mrs x9, hcr_el2
        bfi x9, x10, #34, #1  // set hcr_el2.e2h to 1
        bfi x9, x11, #27, #1  // set hcr_el2.tge to 0
        bfi x9, x10, #31, #1  // set hcr_el2.rw to 1
        msr hcr_el2, x9

        //cptr_el2
        ldr x9, =0x00000000000033ff //rtl默认的初始值
        msr cptr_el2, x9
        mov x10, #0x3
        mov x11, #0x0
        mrs x9, cptr_el2
        bfi x9, x10, #20, #2  // set cptr_el2.fpen to 11
        bfi x9, x11, #10, #1  // set cptr_el2.tfp to 0
        msr cptr_el2, x9

        // exception entry vector addr
        ldr x9, =_vector_table_route_to_el2
        msr vbar_el2, x9

1:
        //cpacr_el1
        ldr x9, =0x0000000000000000 //rtl默认的初始值
        msr cpacr_el1, x9
        mov x10, #0x3
        mov x11, #0x0
        mrs x9, cpacr_el1
        bfi x9, x10, #20, #2  // set cpacr_el1.fpen to 11
        msr cpacr_el1, x9

        // exception entry vector addr
        ldr x9, =_vector_table_route_to_el1
        msr vbar_el1, x9

        //barrier instr and Synchronize the sysregs
        isb
        dsb sy
        ret
        //}}}

evt.inc文件

evt.inc文件主要设置异常向量表,以及设置一些异常处理行为这些异常处理行为仅仅为了实现此测试环境的程序执行控制,并没有具体实际含义。关于异常向量表的详细描述,可以浏览之前的博客《AArch64之异常处理》,以及ARM手册。

以下是代码内容:

  • 注意:每一级异常向量要2K对齐,因为VBAR_EL3/2/1寄存器低11bit是reserve的。
  • 注意:sync异常处理没有进行异常返回,而是使用ret返回elr,这样配合svc hvc smc指令来实现el0 -> el1 -> el2 -> el3。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
/* Vector offsets from vector table base address
|--------------------------------------------------------------------------------------------------|
| Exception taken from                | Offset for exception type                                  |
|                                     | Synchronous | IRQ or vIRQ | FIQ or vFIQ | SError or vSError|
|*************************************|*************|*************|*************|******************|
| Current Exception level with SP_EL0.| 0x000       | 0x080       | 0x100       | 0x180            |
|-------------------------------------|-------------|-------------|-------------|------------------|
| Current Exception level with SP_ELx,| 0x200       | 0x280       | 0x300       | 0x380            |
| x>0.                                |             |             |             |                  |
|-------------------------------------|-------------|-------------|-------------|------------------|
| Lower Exception level, where the    | 0x400       | 0x480       | 0x500       | 0x580            |
| implemented level immediately lower |             |             |             |                  |
| than the target level is using      |             |             |             |                  |
| AArch64.                            |             |             |             |                  |
|-------------------------------------|-------------|-------------|-------------|------------------|
| Lower Exception level, where the    | 0x600       | 0x680       | 0x700       | 0x780            |
| implemented level immediately lower |             |             |             |                  |
| than the target level is using      |             |             |             |                  |
| AArch32.                            |             |             |             |                  |
|--------------------------------------------------------------------------------------------------|
*/

        //{{{异常路由至el1
        .global _vector_table_route_to_el1
        .balign    0x1000 //2K对齐,因为VBAR_EL1寄存器低11bit是reserve的
_vector_table_route_to_el1:
        //offset: 0x000
        //route_to_el1 & Current & with SP_EL0 & Sync
        .balign    0x80
        b _rt2el1_current_withspel0_sync

        //offset: 0x080
        //route_to_el1 & Current & with SP_EL0 & IRQ
        .balign    0x80
        b _rt2el1_current_withspel0_irq

        //offset: 0x100
        //route_to_el1 & Current & with SP_EL0 & FIQ
        .balign    0x80
        b _rt2el1_current_withspel0_fiq

        //offset: 0x180
        //route_to_el1 & Current & with SP_EL0 & SERROR
        .balign    0x80
        b _rt2el1_current_withspel0_serror

        //offset: 0x200
        //route_to_el1 & Current & with SP_ELx & Sync
        .balign    0x80
        b _rt2el1_current_withspelx_sync

        //offset: 0x280
        //route_to_el1 & Current & with SP_ELx & IRQ
        .balign    0x80
        b _rt2el1_current_withspelx_irq

        //offset: 0x300
        //route_to_el1 & Current & with SP_ELx & FIQ
        .balign    0x80
        b _rt2el1_current_withspelx_fiq

        //offset: 0x380
        //route_to_el1 & Current & with SP_ELx & SERROR
        .balign    0x80
        b _rt2el1_current_withspelx_serror

        //offset: 0x400
        //route_to_el1 & LoA64toHi & Sync
        .balign    0x80
        b _rt2el1_LoA64toHi_sync

        //offset: 0x480
        //route_to_el1 & LoA64toHi & IRQ
        .balign    0x80
        b _rt2el1_LoA64toHi_irq

        //offset: 0x500
        //route_to_el1 & LoA64toHi & FIQ
        .balign    0x80
        b _rt2el1_LoA64toHi_fiq

        //offset: 0x580
        //route_to_el1 & LoA64toHi & SERROR
        .balign    0x80
        b _rt2el1_LoA64toHi_serror

        //offset: 0x600
        //route_to_el1 & LoA32toHi & Sync
        .balign    0x80
        b _rt2el1_LoA32toHi_sync

        //offset: 0x680
        //route_to_el1 & LoA32toHi & IRQ
        .balign    0x80
        b _rt2el1_LoA32toHi_irq

        //offset: 0x700
        //route_to_el1 & LoA32toHi & FIQ
        .balign    0x80
        b _rt2el1_LoA32toHi_fiq

        //offset: 0x780
        //route_to_el1 & LoA32toHi & SERROR
        .balign    0x80
        b _rt2el1_LoA32toHi_serror
        //}}}

        //{{{异常路由至el2
        .global _vector_table_route_to_el2
        .balign    0x1000 //2K对齐,因为VBAR_EL2寄存器低11bit是reserve的
_vector_table_route_to_el2:
        //offset: 0x000
        //route_to_el2 & Current & with SP_EL0 & Sync
        .balign    0x80
        b _rt2el2_current_withspel0_sync

        //offset: 0x080
        //route_to_el2 & Current & with SP_EL0 & IRQ
        .balign    0x80
        b _rt2el2_current_withspel0_irq

        //offset: 0x100
        //route_to_el2 & Current & with SP_EL0 & FIQ
        .balign    0x80
        b _rt2el2_current_withspel0_fiq

        //offset: 0x180
        //route_to_el2 & Current & with SP_EL0 & SERROR
        .balign    0x80
        b _rt2el2_current_withspel0_serror

        //offset: 0x200
        //route_to_el2 & Current & with SP_ELx & Sync
        .balign    0x80
        b _rt2el2_current_withspelx_sync

        //offset: 0x280
        //route_to_el2 & Current & with SP_ELx & IRQ
        .balign    0x80
        b _rt2el2_current_withspelx_irq

        //offset: 0x300
        //route_to_el2 & Current & with SP_ELx & FIQ
        .balign    0x80
        b _rt2el2_current_withspelx_fiq

        //offset: 0x380
        //route_to_el2 & Current & with SP_ELx & SERROR
        .balign    0x80
        b _rt2el2_current_withspelx_serror

        //offset: 0x400
        //route_to_el2 & LoA64toHi & Sync
        .balign    0x80
        b _rt2el2_LoA64toHi_sync

        //offset: 0x480
        //route_to_el2 & LoA64toHi & IRQ
        .balign    0x80
        b _rt2el2_LoA64toHi_irq

        //offset: 0x500
        //route_to_el2 & LoA64toHi & FIQ
        .balign    0x80
        b _rt2el2_LoA64toHi_fiq

        //offset: 0x580
        //route_to_el2 & LoA64toHi & SERROR
        .balign    0x80
        b _rt2el2_LoA64toHi_serror

        //offset: 0x600
        //route_to_el2 & LoA32toHi & Sync
        .balign    0x80
        b _rt2el2_LoA32toHi_sync

        //offset: 0x680
        //route_to_el2 & LoA32toHi & IRQ
        .balign    0x80
        b _rt2el2_LoA32toHi_irq

        //offset: 0x700
        //route_to_el2 & LoA32toHi & FIQ
        .balign    0x80
        b _rt2el2_LoA32toHi_fiq

        //offset: 0x780
        //route_to_el2 & LoA32toHi & SERROR
        .balign    0x80
        b _rt2el2_LoA32toHi_serror
        //}}}

        //{{{异常路由至el3
        .global _vector_table_route_to_el3
        .balign    0x1000 //2K对齐,因为VBAR_EL3寄存器低11bit是reserve的
_vector_table_route_to_el3:
        //offset: 0x000
        //route_to_el3 & Current & with SP_EL0 & Sync
        .balign    0x80
        b _rt2el3_current_withspel0_sync

        //offset: 0x080
        //route_to_el3 & Current & with SP_EL0 & IRQ
        .balign    0x80
        b _rt2el3_current_withspel0_irq

        //offset: 0x100
        //route_to_el3 & Current & with SP_EL0 & FIQ
        .balign    0x80
        b _rt2el3_current_withspel0_fiq

        //offset: 0x180
        //route_to_el3 & Current & with SP_EL0 & SERROR
        .balign    0x80
        b _rt2el3_current_withspel0_serror

        //offset: 0x200
        //route_to_el3 & Current & with SP_ELx & Sync
        .balign    0x80
        b _rt2el3_current_withspelx_sync

        //offset: 0x280
        //route_to_el3 & Current & with SP_ELx & IRQ
        .balign    0x80
        b _rt2el3_current_withspelx_irq

        //offset: 0x300
        //route_to_el3 & Current & with SP_ELx & FIQ
        .balign    0x80
        b _rt2el3_current_withspelx_fiq

        //offset: 0x380
        //route_to_el3 & Current & with SP_ELx & SERROR
        .balign    0x80
        b _rt2el3_current_withspelx_serror

        //offset: 0x400
        //route_to_el3 & LoA64toHi & Sync
        .balign    0x80
        b _rt2el3_LoA64toHi_sync

        //offset: 0x480
        //route_to_el3 & LoA64toHi & IRQ
        .balign    0x80
        b _rt2el3_LoA64toHi_irq

        //offset: 0x500
        //route_to_el3 & LoA64toHi & FIQ
        .balign    0x80
        b _rt2el3_LoA64toHi_fiq

        //offset: 0x580
        //route_to_el3 & LoA64toHi & SERROR
        .balign    0x80
        b _rt2el3_LoA64toHi_serror

        //offset: 0x600
        //route_to_el3 & LoA32toHi & Sync
        .balign    0x80
        b _rt2el3_LoA32toHi_sync

        //offset: 0x680
        //route_to_el3 & LoA32toHi & IRQ
        .balign    0x80
        b _rt2el3_LoA32toHi_irq

        //offset: 0x700
        //route_to_el3 & LoA32toHi & FIQ
        .balign    0x80
        b _rt2el3_LoA32toHi_fiq

        //offset: 0x780
        //route_to_el3 & LoA32toHi & SERROR
        .balign    0x80
        b _rt2el3_LoA32toHi_serror
        //}}}

        //{{{异常路由至el1-子程序
//--------------------------------------------------------
//rt2el1_current_withspel0
_rt2el1_current_withspel0_sync:
        //        eret
        and x9, x1, #0xff
        sub x9, x9, #0xff
        cbz x9, 1f
        mrs x30, elr_el1
        ret
1:
        adr x10, _back_from_a32
        mov x30, x10
        ret

_rt2el1_current_withspel0_irq:
        eret

_rt2el1_current_withspel0_fiq:
        eret

_rt2el1_current_withspel0_serror:
        eret

//rt2el1_current_withspelx
_rt2el1_current_withspelx_sync:
        //        eret
        and x9, x1, #0xff
        sub x9, x9, #0xff
        cbz x9, 1f
        mrs x30, elr_el1
        ret
1:
        adr x10, _back_from_a32
        mov x30, x10
        ret

_rt2el1_current_withspelx_irq:
        eret

_rt2el1_current_withspelx_fiq:
        eret

_rt2el1_current_withspelx_serror:
        eret

//rt2el1_LoA64toHi
_rt2el1_LoA64toHi_sync:
        //        eret
        and x9, x1, #0xff
        sub x9, x9, #0xff
        cbz x9, 1f
        mrs x30, elr_el1
        ret
1:
        adr x10, _back_from_a32
        mov x30, x10
        ret

_rt2el1_LoA64toHi_irq:
        eret

_rt2el1_LoA64toHi_fiq:
        eret

_rt2el1_LoA64toHi_serror:
        eret

//rt2el1_LoA32toHi
_rt2el1_LoA32toHi_sync:
        //        eret
        and x9, x1, #0xff
        sub x9, x9, #0xff
        cbz x9, 1f
        mrs x30, elr_el1
        ret
1:
        adr x10, _back_from_a32
        mov x30, x10
        ret

_rt2el1_LoA32toHi_irq:
        eret

_rt2el1_LoA32toHi_fiq:
        eret

_rt2el1_LoA32toHi_serror:
        eret
        //}}}

        //{{{异常路由至el2-子程序
//rt2el2_current_withspel0
_rt2el2_current_withspel0_sync:
//        eret
        mrs x30, elr_el2
        ret

_rt2el2_current_withspel0_irq:
        eret

_rt2el2_current_withspel0_fiq:
        eret

_rt2el2_current_withspel0_serror:
        eret

//rt2el2_current_withspelx
_rt2el2_current_withspelx_sync:
//        eret
        mrs x30, elr_el2
        ret

_rt2el2_current_withspelx_irq:
        eret

_rt2el2_current_withspelx_fiq:
        eret

_rt2el2_current_withspelx_serror:
        eret

//rt2el2_LoA64toHi
_rt2el2_LoA64toHi_sync:
//        eret
        mrs x30, elr_el2
        ret

_rt2el2_LoA64toHi_irq:
        eret

_rt2el2_LoA64toHi_fiq:
        eret

_rt2el2_LoA64toHi_serror:
        eret

//rt2el2_LoA32toHi
_rt2el2_LoA32toHi_sync:
//        eret
        mrs x30, elr_el2
        ret

_rt2el2_LoA32toHi_irq:
        eret

_rt2el2_LoA32toHi_fiq:
        eret

_rt2el2_LoA32toHi_serror:
        eret
        //}}}

        //{{{异常路由至el3-子程序
//rt2el3_current_withspel0
_rt2el3_current_withspel0_sync:
//        eret
        mrs x30, elr_el3
        ret

_rt2el3_current_withspel0_irq:
        eret

_rt2el3_current_withspel0_fiq:
        eret

_rt2el3_current_withspel0_serror:
        eret

//rt2el3_current_withspelx
_rt2el3_current_withspelx_sync:
//        eret
        mrs x30, elr_el3
        ret

_rt2el3_current_withspelx_irq:
        eret

_rt2el3_current_withspelx_fiq:
        eret

_rt2el3_current_withspelx_serror:
        eret

//rt2el3_LoA64toHi
_rt2el3_LoA64toHi_sync:
//        eret
        mrs x30, elr_el3
        ret

_rt2el3_LoA64toHi_irq:
        eret

_rt2el3_LoA64toHi_fiq:
        eret

_rt2el3_LoA64toHi_serror:
        eret

//rt2el3_LoA32toHi
_rt2el3_LoA32toHi_sync:
//        eret
        mrs x30, elr_el3
        ret

_rt2el3_LoA32toHi_irq:
        eret

_rt2el3_LoA32toHi_fiq:
        eret

_rt2el3_LoA32toHi_serror:
        eret
        //}}}

Makefile

Makefile定义了System目录下文件的编译规则,目的是得到start.o文件。其实这里就只有start.s文件,其他文件通过include方式添加到start.s文件。

Makefile.def是一些编译参数的定义。

1
2
3
4
5
6
7
include ../Makefile.def

build:
        $(ASM_A64) -march=$(MARCH) $(DEFINES) -c start.s -o start.o

clean:
        rm -f *.o

a64_test目录

a64_test目录下的文件主要是a64类型测试程序,如果需要,这里可以一同编译没有外部依赖的c程序。

a64_test.s文件

a64_test.s是a64类型测试程序,可以跳转至c函数,也可以嵌入your_testcode.s测试程序。check code是程序自检的一种实现,设置x0为ff时,将print出"TEST FAILED”,设置x0为其他值时,将print出"TEST PASSED”。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
        .global _a64_test
_a64_test:
        stp x29, x30, [sp, #-16]! //Push lr to stack

        //test code is here
        //----------------------------------
        //bl main // c function
        .include "./your_testcode.s"
        //----------------------------------

        //check code is here
        //----------------------------------
        // pass : x0=0x11
        // fail : x0=0xff

        mov x0, #0xff
        mov x0, #0x11
        //----------------------------------

        ldp x29, x30, [sp], #16 //Pop lr from stack

        ret

link.lds是链接脚本,编译是文件各自独立的,编译后还需要将各个编译后的文件链接在一起,链接脚本就是用来指定链接规则,或者说为各个编译后的文件指定存储位置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
OUTPUT_FORMAT("elf64-littleaarch64")
OUTPUT_ARCH(aarch64)

SECTIONS
{
        . = 0x0;

        .setup : {
               a64_test.o
        }

        .text : {
                * (.text)
        }

        .data : {
                * (.data)
        }

        .bss : {
                * (.bss)
        }
}

your_testcode.s文件

your_testcode.s文件是自定义的a64类型测试程序,将其独立出来,为了避免使用者不小心更改Bootloader程序框架。

Makefile

Makefile定义了a64_test目录下文件的编译规则,目的是得到a64_test.bin文件。主要经过以下几步:

  • 将a64_test目录下所有.s和.c文件编译为.o文件。
  • 根据link.lds描述的链接规则,链接为elf文件。
  • 将elf文件转换为bin文件。

Makefile.def是一些编译参数的定义。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
include ../Makefile.def

SRC_C = $(wildcard *.c)
SRC_S = $(wildcard *.s)
OBJ_C = $(patsubst %.c, %.o, $(notdir $(SRC_C)))
OBJ_S = $(patsubst %.s, %.o, $(notdir $(SRC_S)))

build: $(OBJ_S) $(OBJ_C)
        $(LD_A64) -T link.lds $(DEFINES) $^ -o a64_test.elf
        $(OBJCOPY_A64) -O binary a64_test.elf  a64_test.bin
        $(OBJDUMP_A64) -D a64_test.elf > a64_test.dis

%.o: %.s
        $(ASM_A64) -march=$(MARCH) $(DEFINES) -c $^ -o $@

%.o: %.c
        $(CC_A64) -c $^ -o $@ -nostdlib

clean:
        rm -f *.o *.dis *.bin *.elf

a32_test目录

a32_test目录下的文件主要是a32类型测试程序,如果需要,这里可以一同编译没有外部依赖的c程序。

a32_test.s文件

a32_test.s是a32类型测试程序,可以跳转至c函数,也可以嵌入your_testcode.s测试程序。check code是程序自检的一种实现,设置r0为ff时,将print出"TEST FAILED”,设置r0为其他值时,将print出"TEST PASSED”。

  • “.code 32"声明为a32类型程序。
  • “.text"声明为程序段。
  • 通过跳转至”_t32_test +1"的地址,可以切换至Thumb状态。
  • 从Thumb状态返回,是通过异常返回实现的,这里设置r1为ff,是配合异常处理程序完成程序执行控制。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
        .code 32
        .text

        .global _a32_test
_a32_test:
        //Push stack for lr
        push {r7, lr}
        sub sp, #8
        add r7, sp, #0

        //=====================================
        //add your test code
        //bl main //c function
        .include "./your_testcode.s"

        //add your check code
        mov r0, #0x11
        mov r0, #0xff
        //=====================================

        //Pop stack for lr
        adds r7, #8
        mov sp, r7
        pop {r7, pc}

        //a32 to thumb
        //=====================================
        .if TEST_THUMB==1
        adr r8, _t32_test
        add r8, r8, #0x1
        blx r8
        .endif
        //=====================================

        //usermode to el1
        //=====================================
        mov r1, #0xff //for evt Judgemen
        eret
        //=====================================

        .include "./thumb_test.inc"

thumb_test.inc文件

thumb_test.inc文件是thumb类型执行程序,

  • “.code 16"声明为thumb类型程序。
  • “.syntax unified"是一个指示,说明下面的指令是ARM和THUMB通用格式的8字节对齐,可以提高访问速度。
  • 通过跳转至”_t32_test +1"的地址,可以切换至Thumb状态;通过"bx lr"返回a32状态。
  • 为了避免使用者不小心更改bootloader程序框架,也可以在此通过include方式添加独立的thumb测试程序。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
        .code 16
        .syntax unified

        .global _t32_test
_t32_test:
        //================================
        // add your test code
        moval r5, #0
        //================================

        //================================
_t16_test:
        // add your test code
        movs r5, #0
        //================================

        bx lr

link.lds是链接脚本,编译是文件各自独立的,编译后还需要将各个编译后的文件链接在一起,链接脚本就是用来指定链接规则,或者说为各个编译后的文件指定存储位置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
OUTPUT_FORMAT("elf32-littlearm")
OUTPUT_ARCH(arm)

SECTIONS
{
        . = 0x0;

        .setup : {
               a32_test.o
        }

        .text : {
                * (.text)
        }

        .data : {
                * (.data)
        }

        .bss : {
                * (.bss)
        }
}

your_testcode.s文件

your_testcode.s文件是自定义的a32类型测试程序,将其独立出来,为了避免使用者不小心更改Bootloader程序框架。

Makefile

Makefile定义了a32_test目录下文件的编译规则,目的是得到a32_test.bin文件。主要经过以下几步:

  • 将a64_test目录下所有.s和.c文件编译为.o文件。
  • 根据link.lds描述的链接规则,链接为elf文件。
  • 将elf文件转换为bin文件。

Makefile.def是一些编译参数的定义。注意,这里使用的编译工具与a64_test下的不同,具体可查看Makefile.def。

注意:a32 编译支持浮点和SIMD,需要添加编译参数 “-mfpu=neon-fp-armv8”。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
include ../Makefile.def

SRC_C = $(wildcard *.c)
SRC_S = $(wildcard *.s)
OBJ_C = $(patsubst %.c, %.o, $(notdir $(SRC_C)))
OBJ_S = $(patsubst %.s, %.o, $(notdir $(SRC_S)))

build: $(OBJ_S) $(OBJ_C)
        $(LD_A64) -T link.lds $(DEFINES) $^ -o a64_test.elf
        $(OBJCOPY_A64) -O binary a64_test.elf  a64_test.bin
        $(OBJDUMP_A64) -D a64_test.elf > a64_test.dis

%.o: %.s
        $(ASM_A64) -march=$(MARCH) $(DEFINES) -mfpu=neon-fp-armv8 -c $^ -o $@

%.o: %.c
        $(CC_A64) -c $^ -o $@ -nostdlib

clean:
        rm -f *.o *.dis *.bin *.elf

simple_bootloader目录

simple_bootloader目录是整个程序框架的根目录,主要内容是通过Makefile和link.lds将system、a64_test、a32_test下生成的bin文件,链接到一起,合并成一个elf文件,并通过Makefile.def实现环境的参数化。

  • “ENTRY(_start)",将_start设置为程序入口,_start在start.s内定义。
  • 指定链接顺序,start.o在最前,其次是a64_test.bin,最后是a32_test.bin。
  • “. = RVBAR;“设置reset地址为当前地址,也就是最开始的_start位置。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
OUTPUT_FORMAT("elf64_littleaarch64")
OUTPUT_ARCH(aarch64)
TARGET(binary)

ENTRY(_start)

INPUT(./a64_test.bin)
INPUT(./a32_test.bin)

SECTIONS
{
        . = RVBAR;

        .setup : {
                start.o
        }

        .text : {
                * (.text)
        }

        _a64_test = .;
        .a64_test : {
                  a64_test.bin
        }

        _a32_test = .;
        .a32_test : {
                  a32_test.bin
        }

        .data : {
              * (.data)
        }

        .bss : {
             * (.bss)
        }
}

Makefile.def

Makefile.def定义了环境的编译参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#===============================================
RVBAR = 0x0
MARCH = armv8.2-a+crypto+fp16+dot
COMPILER_A64 = "aarch64-linux-gnu"
COMPILER_A32 = "aarch32-linux-gnu"
FNAME = your_testcode
DEFINES += --defsym RVBAR=$(RVBAR)
DEFINES += --defsym TEST_A64_EL0=1
DEFINES += --defsym TEST_A64_EL1=1
DEFINES += --defsym TEST_A64_EL2=1
DEFINES += --defsym TEST_A64_EL3=1
DEFINES += --defsym TEST_A32=1
DEFINES += --defsym TEST_THUMB=1
DEFINES += --defsym PRINT_RESULT=1
#===============================================

#===============================================
ASM_A64 = $(COMPILER_A64)-as
CC_A64 = $(COMPILER_A64)-gcc
LD_A64 = $(COMPILER_A64)-ld
OBJDUMP_A64 = $(COMPILER_A64)-objdump
OBJCOPY_A64 = $(COMPILER_A64)-objcopy

ASM_A32 = $(COMPILER_A32)-as
CC_A32 = $(COMPILER_A32)-gcc
LD_A32 = $(COMPILER_A32)-ld
OBJDUMP_A32 = $(COMPILER_A32)-objdump
OBJCOPY_A32 = $(COMPILER_A32)-objcopy

PROJ_PATH = $(shell pwd)
SYS = $(PROJ_PATH)/system/
A64 = $(PROJ_PATH)/a64_test/
A32 = $(PROJ_PATH)/a32_test/
#===============================================

Makefile文件

Makefile文件主要调用system、a64_test、a32_test下的Makefile分别进行编译,最后根据总的link.lds进行链接,合成总的elf文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
include ./Makefile.def

#=======================================

build: sys a64 a32
        $(LD_A64) -T link.lds $(DEFINES) start.o a32_test.bin a64_test.bin -o $(FNAME).elf
        $(OBJCOPY_A64) -O binary $(FNAME).elf  $(FNAME).bin
        $(OBJDUMP_A64) -D $(FNAME).elf > $(FNAME).dis

a64:
        make -C $(A64) build
        cp $(A64)/a64_test.bin $(PROJ_PATH)/

a32:
        make -C $(A32) build
        cp $(A32)/a32_test.bin $(PROJ_PATH)/

sys:
        make -C $(SYS) build
        cp $(SYS)/start.o $(PROJ_PATH)/

clean:
        make -C $(A64) clean
        make -C $(A32) clean
        make -C $(SYS) clean
        rm *.o *.dis *.bin *.elf -f

文章原创,可能存在部分错误,欢迎指正,联系邮箱 cao_arvin@163.com。