本文节选自《实验指导手册》第二版第16.4章
实验指导手册是奔跑吧Linux内核入门篇第二版配套实验书,pdf版本已经release,可以免费下载和自由打印!
下载方法:
登陆“奔跑吧linux社区”微信公众号,输入“奔跑吧2”获取下载地址。
本文是《奔跑吧Linux内核 入门篇》第16章中的实验16-2:切换异常等级。
1. 实验目的
1)了解和熟悉ARM64汇编语言。
2)了解和熟悉ARM64的异常等级。
2. 实验要求
1)在实验16-1的基础上输出当前的异常等级。
2)在跳转到C语言之前切换异常等级到EL1。
3. 实验讲解
这个实验有如下几个难点:
要在汇编代码里实现串口打印功能。
在汇编语言里实现异常等级的切换。
写个测试代码,测试当前的异常等级。
3.1串口的初始化
我们需要在汇编代码里对串口进行初始化,本实验使用PL011串口设备。我们可以对arch/arm64/mach-rpi/pl_uart.c文件转换成相应的汇编语言即可。
下面是__init_uart汇编函数的参考代码,在arch/arm64/mach-rpi/early_uart.S文件里。
__init_uart:
/* GPIO */
ldr x1, =GPFSEL1
ldr w0, [x1]
and w0, w0, #0xffff8fff /* selector &= ~(7<<12) */
orr w0, w0, #0x4000 /* selector |= 4<<12; */
and w0, w0, #0xfffc7fff /* selector &= ~(7<<15);*/
orr w0, w0, #0x20000 /* selector |= 4<<15;*/
str w0, [x1]
ldr x1, =GPPUD
str wzr,[x1]
/* delay */
mov x0, #150
1:
sub x0, x0, #1
cmp x0, #0
bne 1b
ldr x1, =GPPUDCLK0
ldr w2, #0xc000
str w2, [x1]
/* delay */
mov x0, #150
2:
sub x0, x0, #1
cmp x0, #0
bne 2b
ldr x1, =GPPUDCLK0
str wzr, [x1]
isb
/* Disable UART */
ldr x1, =U_CR_REG
str wzr, [x1]
/* set BRD */
ldr x1, =U_IBRD_REG
mov w2, #26
str w2, [x1]
ldr x1, =U_FBRD_REG
mov w2, #3
str w2, [x1]
ldr x1, =U_LCRH_REG
mov w2, #0x70 //(1<<4) | (3<<5)
str w2, [x1]
ldr x1, =U_IMSC_REG
str wzr, [x1]
ldr x1, =U_CR_REG
mov w2, #0x301 //1 | (1<<8) | (1<<9)
str w2, [x1]
isb
ret
提示:树莓派的外设寄存器的位宽都是32bit的,如果我们在汇编代码里使用了Xn寄存器,那么有可能出错。例如下面的伪代码,第一行把register的地址加载到x1寄存器,然后把x0寄存器的值写入到register地址处,这里会写入64bit的宽度,这样会把旁边寄存器的值也误写了。
ldr x1, =register
str x0, [x1]
正确的做法是,使用32位宽的w0寄存器。
ldr x1, =register
str w0, [x1]
3.2 串口的初始化
往串口里打印一个字符的函数为uart_send(),代码是在arch/arm64/mach-rpi/pl_uart.c文件,我们需要把这个函数转换成汇编函数。
void uart_send(char c)
{
/* wait for transmit FIFO to have an available slot*/
while (readl(U_FR_REG) & (1<<5))
;
writel(c, U_DATA_REG);
}
转换成汇编函数put_uart。
put_uart:
ldr x1, =U_FR_REG
1:
ldr w2, [x1]
and w2, w2, #0x20
cmp w2, #0x0
b.ne 1b
ldr x1, =U_DATA_REG
str w0, [x1]
ret
如果我们需要打印一个字符串的话,我们需要实现另外一个汇编函数。put_string_uart汇编函数,用来打印一个字符串。
.globl put_string_uart
put_string_uart:
mov x4, x0
/* save lr register */
mov x6, x30
1:
ldrb w0, [x4]
bl put_uart
add x4, x4, 1
cmp w0, #0
bne 1b
/* restore lr and return*/
mov x30, x6
ret
提示:这里需要注意,put_string_uart函数调用了put_uart子函数,在调用子函数之后,lr寄存器的内容会被改写。从put_uart子函数返回之后,由于lr寄存器内容被改写,会导致put_string_uart汇编函数返回不了上一级的函数里。因此,在调用put_uart子函数之前,需要把lr寄存器的值临时保存下来,例如这里的“mov x6, x30”语句。put_string_uart汇编函数返回之前,再从x6寄存器里恢复lr寄存器的内容。
想在汇编函数里打印“Booting at EL”的字符串,可以使用位操作命令.string来定义一个字符串。
.section .rodata
.align 3
.globl string1
string1:
.string "Booting at EL"
然后再打印。
/* print EL */
ldr x0, =string1
bl put_string_uart
3.3 切换异常等级
一般来说,ARM64处理器在复位上电之后首先运行在最高的异常等级EL3。树莓派固件有可能会被异常等级切换到EL2,因此我们的代码里需要在入口处判断当前的异常等级是EL3还是EL2。
mrs x5, CurrentEL
cmp x5, #CurrentEL_EL3
b.eq el3_entry
b el2_entry
如当前异常等级是EL3,那么跳转到el3_entry,否则跳转到el2_entry里。
el2_entry:
ifdef CONFIG_DEBUG_ON_EARLY_ASM
bl print_el
endif
ldr x0, =SCTLR_EL2_VALUE_MMU_DISABLED
msr sctlr_el2, x0
/* The Execution state for EL1 is AArch64 */
ldr x0, =HCR_HOST_NVHE_FLAGS
msr hcr_el2, x0
ldr x0, =SCTLR_EL1_VALUE_MMU_DISABLED
msr sctlr_el1, x0
ldr x0, =SPSR_EL1
msr spsr_el2, x0
adr x0, el1_entry
msr elr_el2, x0
eret
下面是从EL2 切换到 EL1, 需要做如下几件事情:
设置HCR_EL2寄存器,最重要的是Bit 31的RW域,表示EL1要运行在哪个执行环境里?
设置SCTLR_EL1寄存器,需要设置 大小端和关闭MMU。
设置SPSR_EL2寄存器,设置模式M域为 EL1h,另外需要关闭 所有的DAIF。
设置异常返回寄存器elr_el2,让其返回到 el1_entry汇编函数里。
执行eret
提示:
当从EL2切换到EL1的时候,我们需要设置hcr_el2寄存器的RW域,确保EL1里的运行环境为aarch64,否则就出错了。
3.4 获取当前异常等级
我们可以通过读取CurrentEL寄存器来获取当前异常等级,下面通过get_currentel()宏来实现。
4. 实验步骤
在Ubuntu Linux主机中,进入参考实验代码目录。
rlk@rlk :$ cd /home/rlk/rlk/runninglinuxkernel_5.0/kmodules/rlk_lab/rlk_basic/chapter_16_benos/lab01_kbuild
进入make menuconfig菜单。
rlk@master:lab01_kbuild$ make menuconfig
我们可以选在树莓派3B或者树莓派4B。
编译BenOS。
rlk@master:lab02_setting_el$ make
编译完成之后,可以先在QEMU上运行。
从上面log可以看出,我们的BenOS在汇编入口处是运行在EL2,然后切换到EL1。
综合能力训练:动手写一个小OS(1)
综合能力训练:动手写一个小OS(2)
奔跑吧Linux内核,一位老FAE的独白和分享,不求回报!
奔2读者交流微信群
关于vim看linux 内核源代码时的代码补全
一个有趣的文件系统实验
奔跑吧四剑客
奔二卷2vmcore实验素材已经上传网盘
一本为您匠心打造的入门书
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。