在我们的最小实现中,我们不会启用 IRQ 或 FIQ。
此外,我们不会实施任何 EL0 应用程序或执行
`svc` 从我们的内核调用,结果所有 VBAR_EL1 条目都设置为
导致系统挂起(无限循环)。同样,对于 EL3,我们只期望
来自较低级别 AArch64 模式的同步异常。结果只有
相应的“vectors_el3”条目(+0x400)已设置,所有其他条目都会导致
系统挂起与 EL1 向量一样。异常处理程序保存当前
处理器状态(通用和状态寄存器)并调用
第二阶段处理程序。我们遵循`smc`调用约定[05],存储
W0 寄存器中的函数标识符和寄存器 X1-X6 中的参数
(即使我们只使用一个参数)。如果函数标识符是
未知,然后系统挂起,模糊测试中的一个重要决定
设置。
// framework/vectors.S
.align 11
.global vectors
vectors:
/*
* Current EL with SP0
*/
.align 7
b . /* Synchronous */
.align 7
b . /* IRQ/vIRQ */
...
.align 11
.global vectors_el3
vectors_el3:
...
/*
* Lower EL, aarch64
*/
.align 7
b el3_synch_low_64
...
el3_synch_low_64:
build_exception_frame
bl handle_interrupt_el3
cmp x0, #0
b.eq 1f
b .
1:
restore_exception_frame
eret
...
处理器在复位后进入 EL3,为了降低到较低的 EL,我们
必须初始化所需 EL 和控制寄存器的执行状态
并在所需的 EL 中构造一个假状态以通过“eret”返回。甚至
虽然我们将从 EL3 直接降到 EL1 以允许
专有的 EL2 实现来定义自己的状态,我们仍然必须
设置一些 EL2 状态寄存器值来初始化 EL1 执行状态。
不遵守最小配置会导致 `eret`
调用对执行异常级别没有影响(至少在
QEMU),换句话说,我们不能降到更低的 EL。
详细地说,要从 EL3 下降到 EL2,我们必须在 Secure 中定义 EL2 状态
配置寄存器 (SCR_EL3)。我们设置 SCR_EL3.NS(位 0)来指定
我们在普通世界,SCR_EL3.RW(位10)指定EL2是AArch64
以及任何需要的保留位。此外,我们将 SCR_EL3.HCE(第 8 位)设置为
在此处启用“hvc”指令,尽管这也可以在
后面的步骤。接下来,为了能够降到 EL1,我们修改 Hypervisor
配置寄存器 (HCR_EL2) 设置 HCR_EL2.RW (bit 31) 并指定
EL1 是 AArch64 和任何其他必需的保留位。尽可能接近
可能的原始设置我们在这里设置了更多的位,例如
HCR_EL2.SWIO(位 1),指示缓存失效行为。这些
我们可以通过上述预言机获得额外的价值
稍后将在文章中介绍。
// framework/boot64.S
.global _reset
_reset:
// setup EL3 stack
ldr x30, =stack_top_el3
mov sp, x30
// setup EL1 stack
ldr x30, =stack_top_el1
msr sp_el1, x30
...
// Setup exception vectors for EL1 and EL3 (EL2 is setup by vmm)
ldr x1, = vectors
msr vbar_el1, x1
ldr x1, = vectors_el3
msr vbar_el3, x1
...
// Initialize EL3 register values
ldr x0, =AARCH64_SCR_EL3_BOOT_VAL
msr scr_el3, x0
// Initialize required EL2 register values
mov x0, #( AARCH64_HCR_EL2_RW )
orr x0, x0,#( AARCH64_HCR_EL2_SWIO )
msr hcr_el2, x0
...
/*
* DROP TO EL1
*/
mov x0, #( AARCH64_SPSR_FROM_AARCH64 | AARCH64_SPSR_MODE_EL1 | \
AARCH64_SPSR_SP_SEL_N)
msr spsr_el3, x0
// drop to function start_el1
adr x0, start_el1
msr elr_el3, x0
eret
对于伪造的低级状态,异常链接寄存器 (ELR_EL3) 保存
异常返回地址,因此我们将其设置为所需的函数
(`start_el1()`)。保存的进程状态寄存器 (SPSR_EL3) 保存
异常之前的处理器状态 (PSTATE) 值,因此我们设置它的值
以便假异常来自 EL1 (SPSR_EL3.M bits[3:0]),使用
SP_EL1(SPSR_EL3.M 位 0)并在 AArch64 模式下执行(SPSR_EL3.M 位 4)。
`eret` 将我们带到 EL1 中的 `start_el1()`。最终登记册相关
exceptions 是保存信息的异常综合症寄存器 (ESR_ELx)
关于异常的性质(综合症信息),因此它
对返回的 EL 没有任何价值,可以忽略。
------[ 2.1.1 - EL1
如前所述,我们的目标是提供最小的设置。考虑到这一点,
还需要尽可能接近原始设置。
我们的 EL1 配置是根据这些要求定义的,并且
实现这一点,我们使用了来自两者的系统配置寄存器值
内核源代码和将在下面介绍的 EL2 预言机
部分,但现在我们可以放心地假设这些是任意选择的
价值观。我们将介绍一些关键系统的详细信息
寄存器值,但有关详细说明,请参阅 AARM 部分
“D13.2 通用系统控制寄存器”。
start_el1:
//初始化EL1所需的寄存器值
ldr x0, =AARCH64_TCR_EL1_BOOT_VAL
msr tcr_el1, x0
ldr x0, =AARCH64_SCTLR_EL1_BOOT_VAL
msr sctlr_el1, x0
...
#define AARCH64_TCR_EL1_BOOT_VAL (\
(AARCH64_TCR_IPS_1TB << AARCH64_TCR_EL1_IPS_SHIFT)| \
(AARCH64_TCR_TG1_4KB << AARCH64_TCR_EL1_TG1_SHIFT)| \
(AARCH64_TCR_TSZ_512G << AARCH64_TCR_EL1_T1SZ_SHIFT)| \
(AARCH64_TCR_TG0_4KB << AARCH64_TCR_EL1_TG0_SHIFT)| \
(AARCH64_TCR_TSZ_512G << AARCH64_TCR_EL1_T0SZ_SHIFT)| \
...
)
正如翻译控制寄存器 (TCR_EL1) 值所示,我们使用 40 位
1TB 大小的中间物理地址空间(TCR_EL1.IPS 位[34:32]),
对于 TTBR0_EL1 和 TTBR1_EL1 4kB 翻译颗粒大小 (TCR_EL1.TG1
位 [31:30] 和 TCR_EL1.TG0 [15:14] 分别)和 25 大小的偏移量
表示每个输入 VA 有 64-25=39 位或 512GB 区域
TTBRn_EL1(TCR_EL1.T1SZ 位[21:16] 和 TCR_EL1.T0SZ 位[5:0])。
通过使用 4kB 粒度,每个转换表大小为 4kB,每个条目
是一个 64 位的描述符,因此每个表有 512 个条目。所以在第 3 级,我们有
512 个条目,每个条目指向一个 4kB 页面,换句话说,我们可以映射一个 2MB
空间。同样,Level 2 有 512 个条目,每个条目指向 2MB 空间
总计 1GB 地址空间和 1 级条目指向 1GB 空间
总计有 512GB 的地址空间。在此设置中,有 39 位
输入 VA,我们不需要 0 级表,如翻译所示
图形。有关详细信息,请参阅 AARM 部分“D5.2 VMSAv8-64 地址
翻译系统”。
+---------+---------+---------+-----------+
| [38:30] | [29:21] | [20:12] | [11:0] | VA segmentation with
| | | | | 4kB Translation Granule
| Level 1 | Level 2 | Level 3 | Block off | 512GB input address space
+---------+---------+---------+-----------+
Physical Address
+-------------------------+-----------+
VA Translation | [39:12] | [11:0] |
demonstration with +-------------------------+-----------+
4kB Granule, ^ ^
512GB Input VA Space | |
1TB IPS | +----------+
+-------------------------+ |
| |
Level 1 tlb Level 2 tlb Level 3 tlb | |
+--------> +-----------+ +--->+-----------+ +-->+-----------+ | |
| | | | | | | | | | |
| +-----------+ | +-----------+ | | | | |
| | 1GB block | | | 2MB block | | | | | |
| | entry | | | entry | | | | | |
| +-----------+ | +-----------+ | | | | |
| | | | | | | | | | |
| +-----------+ | | | | | | | |
| +-->+ Tbl entry +---+ | | | | | | |
+---+---+ | +-----------+ +-----------+ | | | | |
| TTBRn | | | | +-->+ Tbl entry +--+ +-----------+ | |
+---+---+ | | | | +-----------+ +->+ Pg entry +--+ |
^ | | | | | | | +-----------+ |
| | | | | | | | | | |
+--+ | +-----------+ | +-----------+ | +-----------+ |
| | +------+ | |
| +----+ Index +----+ | +--+ +-----------+
| | | | |
+----+-+-+----+---------+----+----+----+----+----+----+------+----+
| | | | Level 0 | Level 1 | Level 2 | Level 3 | PA offset | VA
+----+---+----+---------+---------+---------+---------+-----------+
[55] [47:39] [38:30] [29:21] [20:12] [11:0]
TTBRn Select
对于 1 级和 2 级,每个条目都可以指向下一个翻译
表级(表项)或实际物理地址(块项)
有效地结束翻译。条目类型在 bits[1:0] 中定义,
其中第 0 位标识描述符是否有效(1 表示有效
描述符)和位 1 标识类型,值 0 用于块
条目和 1 用于表条目。结果条目类型值 3 标识
表条目和值 1 块条目。1级块条目指向1GB
使用 VA 位 [29:0] 作为 PA 偏移和级别 2 的内存区域
块条目指向 2MB 区域,其中 bits[20:0] 用作偏移量。最后的
但同样重要的是,3 级翻译表只能有页条目
(类似于块条目,但描述符类型值为 3,如前所述
级别表条目)。
61 51 11 2 1:0
+------------+-----------------------------+----------+------+ Block Entry
| Upper Attr | ... | Low Attr | Type | Stage 1
+------------+-----------------------------+----------+------+ Translation
| bits | Attr | Description |
---------------------------------------------------
| 4:2 | AttrIndex | MAIR_EL1 index |
| 7:6 | AP | Access permissions |
| 53 | PXN | Privileged execute never |
| 54 | (U)XN | (Unprivileged) execute never |
Block entry attributes
| AP | EL0 Access | EL1/2/3 Access | for Stage 1 translation
-------------------------------------
| 00 | None | Read Write |
| 01 | Read Write | Read Write |
| 10 | None | Read Only |
| 11 | Read Only | Read Only |
61 59 2 1:0
+--------+--------------------------------------------+------+ Table Entry
| Attr | ... | Type | Stage 1
+--------+--------------------------------------------+------+ Translation
| bits | Attr | Description |
---------------------------------------------
| 59 | PXN | Privileged execute never |
| 60 | U/XN | Unprivileged execute never |
| 62:61 | AP | Access permissions |
Table entry attributes
| AP | Effect in subsequent lookup levels | for Stage 1 translation
-------------------------------------------
| 00 | No effect |
| 01 | EL0 access not permitted |
| 10 | Write disabled |
| 11 | Write disabled, EL0 Read disabled |
在我们的设置中,我们使用 2MB 区域来映射内核并创建两个映射。
首先,恒等映射(VA 等于它们映射到的 PA)
设置为 TTBR0_EL1 主要在系统从不使用转换时使用
MMU 启用它。其次,在 PA 所在的 TTBR1_EL1 映射
映射到 VA_OFFSET + PA,这意味着从 TTBR1_EL1 获取 PA
VA 或反之亦然,只需减去或添加 VA_OFFSET
相应地。这在 RKP 初始化期间很重要。
#define VA_OFFSET 0xffffff8000000000
#define __pa(x) ((uint64_t)x - VA_OFFSET)
#define __va(x) ((uint64_t)x + VA_OFFSET)
创建页表和启用 MMU 的代码大量借鉴自
Linux内核实现。我们使用一个 1 级条目和所需的
两个表相邻的 2 级块条目的数量
预分配(在链接描述文件中定义)物理页。1级
条目由宏“create_table_entry”评估。首先,入口索引是
从 VA 位 [38:30] 中提取。入口值是下一个Level table PA
与有效的表条目值进行或运算。这也隐含地定义了
表条目属性,其中 (U)XN 被禁用,访问权限 (AP)
对后续级别的查找没有影响。有关其他详细信息
关于内存属性及其对内存的分层控制
访问请参阅 AARM 部分“D5.3.3 内存属性字段”
VMSAv8-64 转换表格式描述符”。
级别 2 遵循类似的过程,但在循环中映射所有必需的
宏“create_block_map”中的 VA。入口值就是我们要映射的PA
与 AARCH64_BLOCK_DEF_FLAGS 定义的块条目属性值进行或运算。
使用的标志值表示非安全内存区域,(U/P)XN 禁用,
内存属性间接寄存器中定义的普通内存
(MAIR_EL1) 和访问权限 (AP),允许读取/写入 EL1 并且不允许
访问 EL0。与表格条目一样,详细说明请参阅
AARM 部分“D5.3.3”。最后,MAIR_ELx 作为一个表来存放
内存区域的信息/属性,读者可以参考 AARM
“B2.7 内存类型和属性”部分了解更多信息。
// framework/aarch64.h
/*
* Block default flags for initial MMU setup
*
* block entry
* attr index 4
* NS = 0
* AP = 0 (EL0 no access, EL1 rw)
* (U/P)XN disabled
*/
#define AARCH64_BLOCK_DEF_FLAGS ( \
AARCH64_PGTBL_BLK_ENTRY | \
0x4 << AARCH64_PGTBL_BLK_ENT_STAGE1_LOW_ATTR_IDX_SHIFT | \
AARCH64_PGTBL_BLK_ENT_STAGE1_LOW_ATTR_AP_RW_ELHIGH << \
AARCH64_PGTBL_BLK_ENT_STAGE1_LOW_ATTR_AP_SHIFT | \
AARCH64_PGTBL_BLK_ENT_STAGE1_LOW_ATTR_SH_INN_SH << \
AARCH64_PGTBL_BLK_ENT_STAGE1_LOW_ATTR_SH_SHIFT | \
1 << AARCH64_PGTBL_BLK_ENT_STAGE1_LOW_ATTR_AF_SHIFT \
)
// framework/mmu.S
__enable_mmu:
...
bl __create_page_tables
isb
mrs x0, sctlr_el1
orr x0, x0, #(AARCH64_SCTLR_EL1_M)
msr sctlr_el1, x0
...
__create_page_tables:
mov x7, AARCH64_BLOCK_DEF_FLAGS
...
// x25 = swapper_pg_dir
u/ x20 = VA_OFFSET
mov x0, x25
adrp x1, _text
add x1, x1, x20
create_table_entry x0, x1, #(LEVEL1_4K_INDEX_SHIFT), \
#(PGTBL_ENTRIES), x4, x5
adrp x1, _text
add x2, x20, x1
adrp x3, _etext
add x3, x3, x20
create_block_map x0, x7, x1, x2, x3
...
.macro create_table_entry, tbl, virt, shift, ptrs, tmp1, tmp2
lsr \tmp1, \virt, \shift
and \tmp1, \tmp1, \ptrs - 1 // table entry index
add \tmp2, \tbl, #PAGE_SIZE // next page table PA
orr \tmp2, \tmp2, #AARCH64_PGTBL_TBL_ENTRY // valid table entry
str \tmp2, [\tbl, \tmp1, lsl #3] // store new entry
add \tbl, \tbl, #PAGE_SIZE // next level table page
.endm
.macro create_block_map, tbl, flags, phys, start, end
lsr \phys, \phys, #LEVEL2_4K_INDEX_SHIFT
lsr \start, \start, #LEVEL2_4K_INDEX_SHIFT
and \start, \start, #LEVEL_4K_INDEX_MASK // table index
orr \phys, \flags, \phys, lsl #LEVEL2_4K_INDEX_SHIFT // table entry
lsr \end, \end, #LEVEL2_4K_INDEX_SHIFT // block entries counter
and \end, \end, #LEVEL_4K_INDEX_MASK // table end index
1: str \phys, [\tbl, \start, lsl #3] // store the entry
add \start, \start, #1 // next entry
add \phys, \phys, #LEVEL2_4K_BLK_SIZE // next block
cmp \start, \end
b.ls 1b
.endm
...
作为演示,我们为 VA 0xffffff8080000000 执行手动表遍历
应该是函数`_reset()`的TTBR1_EL1 VA。1 级表
索引 (1) 为 2,条目值为 0x8008a003,表示有效
PA 0x8008a000 处的表描述符。2 级条目索引 (2) 为 0 并且
该条目的值为 0x80000711,表示物理上的块条目
地址 0x80000000。设置 PA 偏移的剩余 VA 位为零
并且检查生成的 PA 当然是功能的开始
`_reset()`。请注意,由于我们还没有启用 MMU(如图
在接下来的指令中执行的反汇编),所有内存
使用 gdb 访问指的是 PA,这就是我们可以直接检查页面的原因
表和结果 PA。在我们的设置中,即使使用 MMU 也是如此
由于身份映射而启用,但是,这不应该被假定为
适用于每个系统。
(gdb) disas
Dump of assembler code for function __enable_mmu:
0x00000000800401a0 <+0>: mov x28, x30
0x00000000800401a4 <+4>: adrp x25, 0x80089000 // TTBR1_EL1
0x00000000800401a8 <+8>: adrp x26, 0x8008c000
0x00000000800401ac <+12>: bl 0x80040058 <__create_page_tables>
=> 0x00000000800401b0 <+16>: isb
0x00000000800401b4 <+20>: mrs x0, sctlr_el1
0x00000000800401b8 <+24>: orr x0, x0, #0x1
End of assembler dump.
(gdb) p/x ((0xffffff8000000000 + 0x80000000) >> 30) & 0x1ff /* (1) */
$19 = 0x2
(gdb) x/gx ($TTBR1_EL1 + 2*8)
0x80089010: 0x000000008008a003
(gdb) p/x ((0xffffff8000000000 + 0x80000000) >> 21) & 0x1ff /* (2) */
$20 = 0x0
(gdb) x/gx 0x000000008008a000
0x8008a000: 0x0000000080000711
(gdb) x/10i 0x0000000080000000
0x80000000 <_reset>: ldr x30, 0x80040000
0x80000004 <_reset+4>: mov sp, x30
0x80000008 <_reset+8>: mrs x0, currentel
最后,启用 MMU 后,我们就可以启用 RKP。由于EL2
未设置异常向量表,唯一的方法是下降到
EL2 来自 EL3,就像我们对 EL1 所做的那样。我们用函数标识符调用`smc`
EL3 中断处理程序重定向到函数的 CINT_VMM_INIT
`_vmm_init_el3()`。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。