Linux i、 MX35从IRAM暂停CPU和DDR2
我必须从Linux2.6.38将我的设备置于非常低的功耗模式,因此,有必要暂停所有组件,包括CPU和DDR2 到目前为止,我发现我必须将核心汇编函数复制到处理器的内存中,并从那里执行它。基本上,它看起来是这样的:Linux i、 MX35从IRAM暂停CPU和DDR2,linux,arm,cpu,ram,suspend,Linux,Arm,Cpu,Ram,Suspend,我必须从Linux2.6.38将我的设备置于非常低的功耗模式,因此,有必要暂停所有组件,包括CPU和DDR2 到目前为止,我发现我必须将核心汇编函数复制到处理器的内存中,并从那里执行它。基本上,它看起来是这样的: cpaddr = iram_alloc(SZ_1K, &iram_addr); if(!cpaddr) return -ENOMEM; suspend_iram_base = __arm_ioremap(iram_addr, SZ_1K, MT_HIGH_VECTORS); m
cpaddr = iram_alloc(SZ_1K, &iram_addr);
if(!cpaddr) return -ENOMEM;
suspend_iram_base = __arm_ioremap(iram_addr, SZ_1K, MT_HIGH_VECTORS);
memcpy(suspend_iram_base, cpu_v6_sdram_off, SZ_1K);
flush_icache_range(suspend_iram_base, suspend_iram_base + SZ_1K);
flush_cache_all();
__asm__ __volatile__(
"ldr r0, %0\n"
"ldr r1, %1\n"
"ldr r2, %2\n"
"blx r2\n"
"nop\n"
: : "m" (esdctl_addr),
"m" (csd0_addr),
"m" (suspend_iram_base));
ENTRY(cpu_v6_sdram_off)
@ r0: esdctl base address
@ r1: csd0 address with a10 high
cpsid if
@ disable I and D cache
mrc p15, 0, r2, c1, c0, 0
bic r2, r2, #0x00001000 @ disable I cache
bic r2, r2, #0x00000004 @ disable D cache
mcr p15, 0, r2, c1, c0, 0
@ invalidate I cache
mov r2, #0
mcr p15, 0, r2, c7, c5, 0
@ clear and invalidate D cache
mov r2, #0
mcr p15, 0, r2, c7, c14, 0
@ precharge power down mode
ldr r2, [r0]
bic r2, r2, #0xc00
orr r2, r2, #0x400
str r2, [r0]
@ precharge all command
mov r2, #0x92
lsl r2, #24
orr r2, r2, #0x228000
orr r2, r2, #0x0400
str r2, [r0]
mov r2, #0x12
lsl r2, #24
orr r2, r2, #0x340000
orr r2, r2, #0x5600
orr r2, r2, #0x78
str r2, [r1] @ dummy write access
@ execute wait for interrupt
mov r1, #0
mcr p15, 0, r1, c7, c10, 4
mcr p15, 0, r1, c7, c0, 4
cpsie if
bx lr
ENDPROC(cpu_v6_sdram_off)
ENTRY(cpu_v6_sdram_off)
@ r1: physical return address
@ precharge power down mode
ldr r0, =MX35_ESDCTL_BASE_ADDR
ldr r2, [r0]
bic r2, r2, #0xc00
orr r2, r2, #0x400
str r2, [r0]
@ precharge all command
mov r2, #0x92
lsl r2, #24
orr r2, r2, #0x228000
orr r2, r2, #0x0400
str r2, [r0]
ldr r0, =MX35_CSD0_BASE_ADDR
add r0, #0x400
mov r2, #0x12
lsl r2, #24
orr r2, r2, #0x340000
orr r2, r2, #0x5600
orr r2, r2, #0x78
str r2, [r0]
@ execute wait for interrupt
nop
mov r2, #0
mcr p15, 0, r2, c7, c10, 4
mcr p15, 0, r2, c7, c0, 4
nop
nop
nop
nop
nop
@ precharge all command
ldr r0, =MX35_ESDCTL_BASE_ADDR
mov r2, #0x92
lsl r2, #24
orr r2, r2, #0x228000
str r2, [r0]
@ set manual refresh mode
mov r2, #0xa2
lsl r2, #24
add r2, r2, #0x220000
str r2, [r0]
# access memory two times
ldr r0, =MX35_CSD0_BASE_ADDR
mov r2, #0x12
lsl r2, #24
orr r2, r2, #0x340000
orr r2, r2, #0x5600
orr r2, r2, #0x78
str r2, [r0]
nop
str r2, [r0]
@ enable auto-refresh
ldr r0, =MX35_ESDCTL_BASE_ADDR
mov r2, #0x82
lsl r2, #24
add r2, #0x228000
add r2, #0x80
str r2, [r0]
bx r1
ENDPROC(cpu_v6_sdram_off)
到目前为止,一切正常,我可以使用JTAG调试器从内部内存(在虚拟地址空间中)验证代码执行
如果我理解正确,我必须在IRAM功能中执行以下操作:
- 禁用中断和缓存
- 将SDRAM控制器设置为预充电断电模式
- 执行“全部预充电”命令,并以10高(例如0x400)访问内存,以有效关闭所有气缸组
- 通过执行WFI指令将CPU置于备用状态
- 之后重新启用所有内容(在下面的源代码中遗漏)
cpaddr = iram_alloc(SZ_1K, &iram_addr);
if(!cpaddr) return -ENOMEM;
suspend_iram_base = __arm_ioremap(iram_addr, SZ_1K, MT_HIGH_VECTORS);
memcpy(suspend_iram_base, cpu_v6_sdram_off, SZ_1K);
flush_icache_range(suspend_iram_base, suspend_iram_base + SZ_1K);
flush_cache_all();
__asm__ __volatile__(
"ldr r0, %0\n"
"ldr r1, %1\n"
"ldr r2, %2\n"
"blx r2\n"
"nop\n"
: : "m" (esdctl_addr),
"m" (csd0_addr),
"m" (suspend_iram_base));
ENTRY(cpu_v6_sdram_off)
@ r0: esdctl base address
@ r1: csd0 address with a10 high
cpsid if
@ disable I and D cache
mrc p15, 0, r2, c1, c0, 0
bic r2, r2, #0x00001000 @ disable I cache
bic r2, r2, #0x00000004 @ disable D cache
mcr p15, 0, r2, c1, c0, 0
@ invalidate I cache
mov r2, #0
mcr p15, 0, r2, c7, c5, 0
@ clear and invalidate D cache
mov r2, #0
mcr p15, 0, r2, c7, c14, 0
@ precharge power down mode
ldr r2, [r0]
bic r2, r2, #0xc00
orr r2, r2, #0x400
str r2, [r0]
@ precharge all command
mov r2, #0x92
lsl r2, #24
orr r2, r2, #0x228000
orr r2, r2, #0x0400
str r2, [r0]
mov r2, #0x12
lsl r2, #24
orr r2, r2, #0x340000
orr r2, r2, #0x5600
orr r2, r2, #0x78
str r2, [r1] @ dummy write access
@ execute wait for interrupt
mov r1, #0
mcr p15, 0, r1, c7, c10, 4
mcr p15, 0, r1, c7, c0, 4
cpsie if
bx lr
ENDPROC(cpu_v6_sdram_off)
ENTRY(cpu_v6_sdram_off)
@ r1: physical return address
@ precharge power down mode
ldr r0, =MX35_ESDCTL_BASE_ADDR
ldr r2, [r0]
bic r2, r2, #0xc00
orr r2, r2, #0x400
str r2, [r0]
@ precharge all command
mov r2, #0x92
lsl r2, #24
orr r2, r2, #0x228000
orr r2, r2, #0x0400
str r2, [r0]
ldr r0, =MX35_CSD0_BASE_ADDR
add r0, #0x400
mov r2, #0x12
lsl r2, #24
orr r2, r2, #0x340000
orr r2, r2, #0x5600
orr r2, r2, #0x78
str r2, [r0]
@ execute wait for interrupt
nop
mov r2, #0
mcr p15, 0, r2, c7, c10, 4
mcr p15, 0, r2, c7, c0, 4
nop
nop
nop
nop
nop
@ precharge all command
ldr r0, =MX35_ESDCTL_BASE_ADDR
mov r2, #0x92
lsl r2, #24
orr r2, r2, #0x228000
str r2, [r0]
@ set manual refresh mode
mov r2, #0xa2
lsl r2, #24
add r2, r2, #0x220000
str r2, [r0]
# access memory two times
ldr r0, =MX35_CSD0_BASE_ADDR
mov r2, #0x12
lsl r2, #24
orr r2, r2, #0x340000
orr r2, r2, #0x5600
orr r2, r2, #0x78
str r2, [r0]
nop
str r2, [r0]
@ enable auto-refresh
ldr r0, =MX35_ESDCTL_BASE_ADDR
mov r2, #0x82
lsl r2, #24
add r2, #0x228000
add r2, #0x80
str r2, [r0]
bx r1
ENDPROC(cpu_v6_sdram_off)
问题出现在使用伪写访问RAM的点上。它只会导致数据中止异常,然后CPU就会丢失。
如果我不考虑这一部分,DDR2似乎不会进入低功耗模式,因为电流消耗不会下降
现在我完全被困在这里,没有想法了。谁能给我一个提示,我做错了什么,或者我遗漏了什么?
或者是否有任何文档或源代码可用于演示Linux上i.MX35的整个过程?以及禁用icache和dcache,这需要耗尽所有缓冲区。我只在IMX25上实现了这一点;它是一个ARM926(armv5)。我现在正在为armv7开发,它看起来像是一个dcache刷新可能是合适的。即,确保CPU将所有内容转储到SDRAM
现在,您似乎还错过了关闭MMU的关键步骤。当您运行str r2,[r1]@dummy write access
时,您将获得TLB未命中,并尝试访问页面表,这些表可能位于SDRAM中。我看到一个问题;-)。幸运的是,你们有一个汇编程序,它与PC相关,可以随时随地运行
下面是一个示例函数,用于在物理调用例程之前禁用MMU。对于ARMV5,您需要将p15
值更新为CPU的功能等效值
static void phys_execute(void /*@unused@*/ (*function_pointer)(void))
{
__asm volatile (
" push {r4-r12,lr} \n" /* save everything */
"1: mrc p15, 0, r15, c7, c14, 3 \n" /* armv5 specific.. */
" bne 1b \n" /* dcache clean */
" mov r8, #0 \n"
" mcr p15, 0, r8, c7, c5, 0 \n" /* invalidate icache */
" mcr p15, 0, r8, c7, c10, 4 \n" /* drain wb armv5 */
" mrc p15, 0, r10, c1, c0, 0 \n" /* caches/mmu off */
" bic r8, r10, #0x5 \n"
" bic r8, r8, #0x1000 \n"
" mcr p15, 0, r8, c1, c0, 0 \n"
" blx r0 \n" /* Call r0 */
" mcr p15, 0, r10, c1, c0, 0 \n" /* caches on..*
"1: mrc p15, 0, r15, c7, c14, 3 \n" /* armv5 again */
" mov r8, #0 \n"
" bne 1b \n"
" mcr p15, 0, r8, c7, c5, 0 \n"
" mcr p15, 0, r8, c7, c10, 4 \n"
" pop {r4-r12,pc} \n"
);
}
r1
和r2
将通过物理ram调用例程。您可以将其重新调整为硬编码三个参数,然后使用函数指针将其放入r4
。然而,你的
@ r0: esdctl base address
@ r1: csd0 address with a10 high
必须更改为物理地址,以便在运行cpu\u v6\u sdram\u off
时,它将访问非虚拟地址。感谢您的帮助
嗯,不是很简单;-)
但是把我从你的答案中理解的所有内容放在一起,我最终得到了以下代码——当然不是很清晰,但至少是有效的代码:
__asm__ __volatile__(
"push {r4-r12, lr}\n"
"cpsid if\n"
"mov r0, #0\n"
"orr r0, r0, %0\n"
"mov r2, #0\n"
"mcr p15, 0, r2, c7, c14, 0\n" // clear and invalidate D cache
"mov r2, #0\n"
"mcr p15, 0, r2, c7, c5, 0\n" // invalidate I cache
"mov r2, #0\n"
"mcr p15, 0, r2, c7, c10, 4\n" // data synchronisation barrier (drain write buffer)
"mrc p15, 0, r2, c1, c0, 0\n"
"bic r2, r2, #0x00001000\n" // disable I cache
"bic r2, r2, #0x00000004\n" // disable D cache
"bic r2, r2, #0x00000001\n" // disable MMU
"mcr p15, 0, r2, c1, c0, 0\n"
"add r1, pc, #8\n"
"sub r1, #0xc0000000\n"
"add r1, #0x80000000\n"
"blx r0\n"
"nop \n"
"add r1, pc, #28\n"
"sub r1, #0x80000000\n"
"add r1, #0xc0000000\n"
"mrc p15, 0, r2, c1, c0, 0\n"
"orr r2, r2, #0x00001000\n" // enable I cache
"orr r2, r2, #0x00000004\n" // enable D cache
"orr r2, r2, #0x00000001\n" // enable MMU
"mcr p15, 0, r2, c1, c0, 0\n"
"bx r1\n"
"nop \n"
"cpsie if\n"
"pop {r4-r12, pc}\n"
: : "r" (asm_func));
根据ARM1136技术参考手册,在ARMv6上应该使用“数据同步屏障”而不是“漏写缓冲区”,所以我选择了这个
更改地址空间时,两个nop命令标记跳转目的地。寄存器r0包含IRAM中cpu_v6_sdram_off的物理代码位置
整个挂起/恢复代码现在如下所示:
cpaddr = iram_alloc(SZ_1K, &iram_addr);
if(!cpaddr) return -ENOMEM;
suspend_iram_base = __arm_ioremap(iram_addr, SZ_1K, MT_HIGH_VECTORS);
memcpy(suspend_iram_base, cpu_v6_sdram_off, SZ_1K);
flush_icache_range(suspend_iram_base, suspend_iram_base + SZ_1K);
flush_cache_all();
__asm__ __volatile__(
"ldr r0, %0\n"
"ldr r1, %1\n"
"ldr r2, %2\n"
"blx r2\n"
"nop\n"
: : "m" (esdctl_addr),
"m" (csd0_addr),
"m" (suspend_iram_base));
ENTRY(cpu_v6_sdram_off)
@ r0: esdctl base address
@ r1: csd0 address with a10 high
cpsid if
@ disable I and D cache
mrc p15, 0, r2, c1, c0, 0
bic r2, r2, #0x00001000 @ disable I cache
bic r2, r2, #0x00000004 @ disable D cache
mcr p15, 0, r2, c1, c0, 0
@ invalidate I cache
mov r2, #0
mcr p15, 0, r2, c7, c5, 0
@ clear and invalidate D cache
mov r2, #0
mcr p15, 0, r2, c7, c14, 0
@ precharge power down mode
ldr r2, [r0]
bic r2, r2, #0xc00
orr r2, r2, #0x400
str r2, [r0]
@ precharge all command
mov r2, #0x92
lsl r2, #24
orr r2, r2, #0x228000
orr r2, r2, #0x0400
str r2, [r0]
mov r2, #0x12
lsl r2, #24
orr r2, r2, #0x340000
orr r2, r2, #0x5600
orr r2, r2, #0x78
str r2, [r1] @ dummy write access
@ execute wait for interrupt
mov r1, #0
mcr p15, 0, r1, c7, c10, 4
mcr p15, 0, r1, c7, c0, 4
cpsie if
bx lr
ENDPROC(cpu_v6_sdram_off)
ENTRY(cpu_v6_sdram_off)
@ r1: physical return address
@ precharge power down mode
ldr r0, =MX35_ESDCTL_BASE_ADDR
ldr r2, [r0]
bic r2, r2, #0xc00
orr r2, r2, #0x400
str r2, [r0]
@ precharge all command
mov r2, #0x92
lsl r2, #24
orr r2, r2, #0x228000
orr r2, r2, #0x0400
str r2, [r0]
ldr r0, =MX35_CSD0_BASE_ADDR
add r0, #0x400
mov r2, #0x12
lsl r2, #24
orr r2, r2, #0x340000
orr r2, r2, #0x5600
orr r2, r2, #0x78
str r2, [r0]
@ execute wait for interrupt
nop
mov r2, #0
mcr p15, 0, r2, c7, c10, 4
mcr p15, 0, r2, c7, c0, 4
nop
nop
nop
nop
nop
@ precharge all command
ldr r0, =MX35_ESDCTL_BASE_ADDR
mov r2, #0x92
lsl r2, #24
orr r2, r2, #0x228000
str r2, [r0]
@ set manual refresh mode
mov r2, #0xa2
lsl r2, #24
add r2, r2, #0x220000
str r2, [r0]
# access memory two times
ldr r0, =MX35_CSD0_BASE_ADDR
mov r2, #0x12
lsl r2, #24
orr r2, r2, #0x340000
orr r2, r2, #0x5600
orr r2, r2, #0x78
str r2, [r0]
nop
str r2, [r0]
@ enable auto-refresh
ldr r0, =MX35_ESDCTL_BASE_ADDR
mov r2, #0x82
lsl r2, #24
add r2, #0x228000
add r2, #0x80
str r2, [r0]
bx r1
ENDPROC(cpu_v6_sdram_off)
如果有人想更正或优化此代码,请随时通知我。谢谢
phys\u execute
(或类似的东西)应该刷新缓存,否则它需要使用phys==virt执行,以便在禁用MMU时,最终的“bx”返回到正确的地址。将cpu\u v6\u sdram\u关闭时的缓存保持活动状态
可以将phys\u execute
的“tail”指令保留在icache中以避免出现这种情况。如果选择phys==virt,则在禁用MMU之前,需要进行此映射并跳转到phys范围phys\u execute
;所有这些都很简单:)另外,如果你从睡眠中醒来时出现问题,一些飞思卡尔SOC会延迟访问SDRAM,允许时钟在醒来后第一次使用前锁定。我比耳语要好,你感觉到睡眠,非常睡眠。是的,董事会终于播放了《熟睡的贝蒂》:-)我这边的两个小更正:必须用stmfd/ldmfd替换push/pop命令,否则事情会变得一团糟。与向程序计数器添加偏移量不同,为跳转目标地址使用标签更安全。