0
点赞
收藏
分享

微信扫一扫

综合能力训练:在树莓派上动手写一个小OS(3):实验16-2:切换异常等级

本文节选自《实验指导手册》第二版第16.4章

实验指导手册是奔跑吧Linux内核入门篇第二版配套实验书,pdf版本已经release,可以免费下载和自由打印!

下载方法:

登陆“奔跑吧linux社区”微信公众号,输入“奔跑吧2”获取下载地址。

本文是《奔跑吧Linux内核 入门篇》第16章中的实验16-2:切换异常等级。

1. 实验目的

1)了解和熟悉ARM64汇编语言。

2)了解和熟悉ARM64的异常等级。

2. 实验要求

1)在实验16-1的基础上输出当前的异常等级。

2)在跳转到C语言之前切换异常等级到EL1。

3. 实验讲解

这个实验有如下几个难点:

  1. 要在汇编代码里实现串口打印功能。
  2. 在汇编语言里实现异常等级的切换。
  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,否则就出错了。

  • 综合能力训练:在树莓派上动手写一个小OS(3):实验16-2:切换异常等级_串口

3.4 获取当前异常等级

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

综合能力训练:在树莓派上动手写一个小OS(3):实验16-2:切换异常等级_字符串_02

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。

综合能力训练:在树莓派上动手写一个小OS(3):实验16-2:切换异常等级_寄存器_03

编译BenOS。

rlk@master:lab02_setting_el$ make

编译完成之后,可以先在QEMU上运行。

综合能力训练:在树莓派上动手写一个小OS(3):实验16-2:切换异常等级_串口_04

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

综合能力训练:在树莓派上动手写一个小OS(3):实验16-2:切换异常等级_串口_05

综合能力训练:在树莓派上动手写一个小OS(3):实验16-2:切换异常等级_字符串_06


举报

相关推荐

0 条评论