ARM:禁用 MMU 并更新 PC
ARM: Disabling MMU and updating PC
简而言之,出于调试目的,我想关闭 Linux 上下文(从内核内部)中的所有 MMU(和缓存)操作,只是为了 运行 一些测试。明确地说,我不打算在那之后我的系统仍然可以运行。
关于我的设置:我目前正在摆弄集成了 Cortex A5 的 Freescale Vybrid (VF610) 及其低功耗模式。由于我正在试验一些 可疑的本地 内存损坏,而芯片处于 "Low Power Stop" 模式并且我的 DDR3 处于自刷新状态,我试图一点一点地移动操作,现在执行所有 suspend/resume 步骤而不实际执行 WFI。因为在这条指令之前我 运行 有地址转换,之后没有(它本质上是一个重置),我想 "simulate" 通过 "manually" 关闭 MMU。
(我 目前 没有 JTAG 或任何其他调试访问我的芯片。我通过 MMC/TFTP/NFS 加载它,并用 LED 调试它。)
到目前为止我尝试过的:
/* disable the Icache, Dcache and branch prediction */
mrc p15, 0, r6, c1, c0, 0
ldr r7, =0x1804
bic r6, r6, r7
mcr p15, 0, r6, c1, c0, 0
isb
/* disable the MMU and TEX */
bic r7, r6, r7
isb
mcr p15, 0, r6, c1, c0, 0 @ turn on MMU, I-cache, etc
mrc p15, 0, r6, c0, c0, 0 @ read id reg
isb
dsb
dmb
以及具有相同效果的其他变体。
我观察到的:
在 MMU 块之前,我可以点亮一个 LED(3 个汇编指令,没有分支,没有什么花哨的,也没有访问我的 DDR,它已经处于自刷新状态 - GPIO 端口的虚拟地址存储在在那之前注册)。
在 MMU 块之后,我无法再尝试物理地址或虚拟地址。
我认为问题可能与我的电脑有关,它保留了一个过时的虚拟地址。查看内核其他地方的工作方式,但反过来(即启用翻译时):
ldr r3, =cpu_resume_after_mmu
instr_sync
mcr p15, 0, r0, c1, c0, 0 @ turn on MMU, I-cache, etc
mrc p15, 0, r0, c0, c0, 0 @ read id reg
instr_sync
mov r0, r0
mov r0, r0
ret r3 @ jump to virtual address
ENDPROC(cpu_resume_mmu)
.popsection
cpu_resume_after_mmu:
(来自arch/arm/kernel/sleep.S, cpu_resume_mmu)
我想知道这 2 条指令的延迟与什么有关,以及记录在何处。我在这个问题上一无所获。我试过类似的东西,但没有成功:
adr lr, BSYM(phys_block)
/* disable the Icache, Dcache and branch prediction */
mrc p15, 0, r6, c1, c0, 0
ldr r7, =0x1804
bic r6, r6, r7
mcr p15, 0, r6, c1, c0, 0
isb
/* disable the MMU and TEX */
bic r7, r6, r7
isb
mcr p15, 0, r6, c1, c0, 0 @ turn on MMU, I-cache, etc
mrc p15, 0, r6, c0, c0, 0 @ read id reg
isb
dsb
msb
mov r0, r0
mov r0, r0
ret lr
phys_block:
blue_light
loop
感谢任何有线索或指点的人!
由于 Jacen 和 dwelch 都通过评论(各自)提供了我需要的答案,为了清楚起见,我将在这里回答我自己的问题:
诀窍是简单地添加身份映射 from/to 进行转换的页面,允许我们使用 "physical"(尽管实际上是虚拟的)PC 跳转到它,然后禁用 MMU。
这是最终代码(有点具体,但已注释):
/* Duplicate mapping to here */
mrc p15, 0, r4, c2, c0, 0 // Get TTRB0
ldr r10, =0x00003fff
bic r4, r10 // Extract page table physical base address
orr r4, #0xc0000000 // Nastily "translate" it to the virtual one
/*
* Here r8 holds vf_suspend's physical address. I had no way of
* doing this more "locally", since both physical and virtual
* space for my code are runtime-allocated.
*/
add lr, r8, #(phys_block-vf_suspend) // -> phys_block physical address
lsr r9, lr, #20 // SECTION_SHIFT -> Page index
add r7, r4, r9, lsl #2 // PMD_ORDER -> Entry address
ldr r10, =0x00000c0e // Flags
orr r9, r10, r9, lsl #20 // SECTION_SHIFT -> Entry value
str r9, [r7] // Write entry
ret lr // Jump / transition to virtual addressing
phys_block:
/* disable the MMU and TEX */
isb
mrc p15, 0, r6, c1, c0, 0
ldr r7, =0x10000001
bic r6, r6, r7
mcr p15, 0, r6, c1, c0, 0 @ turn on MMU, I-cache, etc
mrc p15, 0, r6, c0, c0, 0 @ read id reg
isb
dsb
dmb
/* disable the Icache, Dcache and branch prediction */
mrc p15, 0, r6, c1, c0, 0
ldr r7, =0x1804
bic r6, r6, r7
mcr p15, 0, r6, c1, c0, 0
isb
// Done !
为了解决问题的 "what this 2-instruction delay is" 部分,与 /arch/arm 的大部分内容一样,它主要只是遗留下来的废话*.
早在任何类型的屏障指令之前的日子里,你必须考虑这样一个事实,即在你切换 MMU 时,管道包含在切换之前已经获取和解码的指令,所以有任何像如果地址 space 在它执行时已经改变,那么其中的分支或内存访问将出现可怕的错误。 ARMv4 Architecture Reference Manual 做出了精彩的陈述 "The correct code sequence for enabling and disabling the MMU is IMPLEMENTATION DEFINED" - 实际上,这主要意味着您知道您的管道有 3 个阶段长,因此插入两个 NOP 以安全地填充它。或者 took full advantage of the fact 做一些可怕的事情,比如安排直接跳转到翻译后的 VA,而不通过恒等映射(哎呀!)。
来自对旧微体系结构手册的有趣拖网,3 NOPs are needed for StrongARM (compared to 2 for the 3-stage ARM7 pipeline), and reading CP15 with a data dependency on the result is the recommended self-synchronising sequence for XScale,这解释了对主 ID 寄存器显然毫无意义的读取。
然而,在现代的东西(ARMv6 或更高版本)上,none 应该是必需的,因为你已经构建了障碍,所以你只需按下开关然后发出一个 isb
来刷新管道,这就是 instr_sync
宏在为此类架构构建时扩展到的内容。
* 或 Linux "works on everything" 方法的一个很好的例子,取决于您的观点...
简而言之,出于调试目的,我想关闭 Linux 上下文(从内核内部)中的所有 MMU(和缓存)操作,只是为了 运行 一些测试。明确地说,我不打算在那之后我的系统仍然可以运行。
关于我的设置:我目前正在摆弄集成了 Cortex A5 的 Freescale Vybrid (VF610) 及其低功耗模式。由于我正在试验一些 可疑的本地 内存损坏,而芯片处于 "Low Power Stop" 模式并且我的 DDR3 处于自刷新状态,我试图一点一点地移动操作,现在执行所有 suspend/resume 步骤而不实际执行 WFI。因为在这条指令之前我 运行 有地址转换,之后没有(它本质上是一个重置),我想 "simulate" 通过 "manually" 关闭 MMU。
(我 目前 没有 JTAG 或任何其他调试访问我的芯片。我通过 MMC/TFTP/NFS 加载它,并用 LED 调试它。)
到目前为止我尝试过的:
/* disable the Icache, Dcache and branch prediction */
mrc p15, 0, r6, c1, c0, 0
ldr r7, =0x1804
bic r6, r6, r7
mcr p15, 0, r6, c1, c0, 0
isb
/* disable the MMU and TEX */
bic r7, r6, r7
isb
mcr p15, 0, r6, c1, c0, 0 @ turn on MMU, I-cache, etc
mrc p15, 0, r6, c0, c0, 0 @ read id reg
isb
dsb
dmb
以及具有相同效果的其他变体。
我观察到的:
在 MMU 块之前,我可以点亮一个 LED(3 个汇编指令,没有分支,没有什么花哨的,也没有访问我的 DDR,它已经处于自刷新状态 - GPIO 端口的虚拟地址存储在在那之前注册)。
在 MMU 块之后,我无法再尝试物理地址或虚拟地址。
我认为问题可能与我的电脑有关,它保留了一个过时的虚拟地址。查看内核其他地方的工作方式,但反过来(即启用翻译时):
ldr r3, =cpu_resume_after_mmu
instr_sync
mcr p15, 0, r0, c1, c0, 0 @ turn on MMU, I-cache, etc
mrc p15, 0, r0, c0, c0, 0 @ read id reg
instr_sync
mov r0, r0
mov r0, r0
ret r3 @ jump to virtual address
ENDPROC(cpu_resume_mmu)
.popsection
cpu_resume_after_mmu:
(来自arch/arm/kernel/sleep.S, cpu_resume_mmu)
我想知道这 2 条指令的延迟与什么有关,以及记录在何处。我在这个问题上一无所获。我试过类似的东西,但没有成功:
adr lr, BSYM(phys_block)
/* disable the Icache, Dcache and branch prediction */
mrc p15, 0, r6, c1, c0, 0
ldr r7, =0x1804
bic r6, r6, r7
mcr p15, 0, r6, c1, c0, 0
isb
/* disable the MMU and TEX */
bic r7, r6, r7
isb
mcr p15, 0, r6, c1, c0, 0 @ turn on MMU, I-cache, etc
mrc p15, 0, r6, c0, c0, 0 @ read id reg
isb
dsb
msb
mov r0, r0
mov r0, r0
ret lr
phys_block:
blue_light
loop
感谢任何有线索或指点的人!
由于 Jacen 和 dwelch 都通过评论(各自)提供了我需要的答案,为了清楚起见,我将在这里回答我自己的问题:
诀窍是简单地添加身份映射 from/to 进行转换的页面,允许我们使用 "physical"(尽管实际上是虚拟的)PC 跳转到它,然后禁用 MMU。
这是最终代码(有点具体,但已注释):
/* Duplicate mapping to here */
mrc p15, 0, r4, c2, c0, 0 // Get TTRB0
ldr r10, =0x00003fff
bic r4, r10 // Extract page table physical base address
orr r4, #0xc0000000 // Nastily "translate" it to the virtual one
/*
* Here r8 holds vf_suspend's physical address. I had no way of
* doing this more "locally", since both physical and virtual
* space for my code are runtime-allocated.
*/
add lr, r8, #(phys_block-vf_suspend) // -> phys_block physical address
lsr r9, lr, #20 // SECTION_SHIFT -> Page index
add r7, r4, r9, lsl #2 // PMD_ORDER -> Entry address
ldr r10, =0x00000c0e // Flags
orr r9, r10, r9, lsl #20 // SECTION_SHIFT -> Entry value
str r9, [r7] // Write entry
ret lr // Jump / transition to virtual addressing
phys_block:
/* disable the MMU and TEX */
isb
mrc p15, 0, r6, c1, c0, 0
ldr r7, =0x10000001
bic r6, r6, r7
mcr p15, 0, r6, c1, c0, 0 @ turn on MMU, I-cache, etc
mrc p15, 0, r6, c0, c0, 0 @ read id reg
isb
dsb
dmb
/* disable the Icache, Dcache and branch prediction */
mrc p15, 0, r6, c1, c0, 0
ldr r7, =0x1804
bic r6, r6, r7
mcr p15, 0, r6, c1, c0, 0
isb
// Done !
为了解决问题的 "what this 2-instruction delay is" 部分,与 /arch/arm 的大部分内容一样,它主要只是遗留下来的废话*.
早在任何类型的屏障指令之前的日子里,你必须考虑这样一个事实,即在你切换 MMU 时,管道包含在切换之前已经获取和解码的指令,所以有任何像如果地址 space 在它执行时已经改变,那么其中的分支或内存访问将出现可怕的错误。 ARMv4 Architecture Reference Manual 做出了精彩的陈述 "The correct code sequence for enabling and disabling the MMU is IMPLEMENTATION DEFINED" - 实际上,这主要意味着您知道您的管道有 3 个阶段长,因此插入两个 NOP 以安全地填充它。或者 took full advantage of the fact 做一些可怕的事情,比如安排直接跳转到翻译后的 VA,而不通过恒等映射(哎呀!)。
来自对旧微体系结构手册的有趣拖网,3 NOPs are needed for StrongARM (compared to 2 for the 3-stage ARM7 pipeline), and reading CP15 with a data dependency on the result is the recommended self-synchronising sequence for XScale,这解释了对主 ID 寄存器显然毫无意义的读取。
然而,在现代的东西(ARMv6 或更高版本)上,none 应该是必需的,因为你已经构建了障碍,所以你只需按下开关然后发出一个 isb
来刷新管道,这就是 instr_sync
宏在为此类架构构建时扩展到的内容。
* 或 Linux "works on everything" 方法的一个很好的例子,取决于您的观点...