本文节选自《实验指导手册》第二版第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,否则就出错了。

640 (3).png

3.4 获取当前异常等级

我们可以通过读取CurrentEL寄存器来获取当前异常等级,下面通过get_currentel()宏来实现。

640.png

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。

640 (1).png

编译BenOS。

rlk@master:lab02_setting_el$ make
编译完成之后,可以先在QEMU上运行。

640 (2).png

从上面log可以看出,我们的BenOS在汇编入口处是运行在EL2,然后切换到EL1。

640.jpg
640 (1).jpg

综合能力训练:动手写一个小OS(1)

综合能力训练:动手写一个小OS(2)

奔跑吧Linux内核,一位老FAE的独白和分享,不求回报!

奔2读者交流微信群

关于vim看linux 内核源代码时的代码补全

一个有趣的文件系统实验

奔跑吧四剑客

奔二卷2vmcore实验素材已经上传网盘

一本为您匠心打造的入门书


奔跑吧Linux社区
4 声望4 粉丝

奔跑吧Linux社区,为广大小伙伴布道Linux开源!


引用和评论

0 条评论