编译好了树莓派内核。在运行到arch/arm/mm.alignment.c的static int __init alignment_init(void)突然跑飞了。
定位到是在
if (cpu_is_v6_unaligned())
{
set_cr(__clear_cr(CR_A));
ai_usermode = safe_usermode(ai_usermode, false);
}
执行set_cr(__clear_cr(CR_A));的时候跑飞的。
static bool cpu_is_v6_unaligned(void)
{
return cpu_architecture() >= CPU_ARCH_ARMv6 && get_cr() & CR_U;
}
static inline unsigned long get_cr(void)
{
unsigned long val;
asm("mrc p15, 0, %0, c1, c0, 0 @ get CR" : "=r" (val) : : "cc");
return val;
}
static inline void set_cr(unsigned long val)
{
asm volatile("mcr p15, 0, %0, c1, c0, 0 @ set CR"
: : "r" (val) : "cc");
isb();
}
#define CR_A (1 << 1) /* Alignment abort enable */
#define CR_U (1 << 22) /* Unaligned access operation */
根据ARM1176-JZFS手册,以及协处理器操作。
由于cpu_is_v6_unaligned()返回true。也就是 cr1的第22bit CR_U为1。表明开启了支持非对齐的内存访问。包括混合的大端和小端操作。
该位相对于CR_A位来说优先级别较低,也就是先判定CR_A再判定CR_U。CR_A是第1bit,控制对齐错误检查,如果为1,那么开启内存对齐访问检查,如果为0则不开启,如果开启了了内存访问对齐检查,当访问内存地址没有对齐时,发生DataAbort中断。
unsigned long __init __clear_cr(unsigned long mask)
{
cr_alignment = cr_alignment & ~mask;
return cr_alignment;
}
__clear_cr很简单就是去掉cr_alignment的mask位。就是将CR_A位置0,不开启内存访问对齐检查。
cr_alignment的定义在entry-armv.S中。
比较一下通过get_cr()和cr_alignment比较检查一下是不是只有CR_A位不同
printk("get_cr()=0x%08x cr_alignment=0x%08x\n",get_cr(),cr_alignment);
打印的结果是
get_cr()=0x00c5387d cr_alignment=0x00000006
这里错误了。为什么要用cr_alignment不直接get_cr先得到cr1再处理呢?反正值为6肯定是错误的。我们先来检查一下这个值为什么不对
全局搜索 cr_alignment
一共有几处地方
在\arm\kernel\entry-armv.S中
line:1230
.globl cr_alignment
cr_alignment:
.space 4
line:311
.LCcralign:
.word cr_alignment
在\arm\kernel\entry-common.S中
__cr_alignment:
.word cr_alignment
在arm\kernel\head-common.S中
__mmap_switched_data:
.long __data_loc@ r4
.long _sdata@ r5
.long __bss_start@ r6
.long _end@ r7
.long processor_id@ r4
.long __machine_arch_type@ r5
.long __atags_pointer@ r6
#ifdef CONFIG_CPU_CP15
.long cr_alignment@ r7
#else
.long 0@ r7
#endif
.long init_thread_union + THREAD_START_SP @ sp
.size __mmap_switched_data, . - __mmap_switched_data
因此可以知道。实际的cr_alignment这个具体值保存在
\arm\kernel\entry-armv.S
.globl cr_alignment
cr_alignment:
.space 4
中。其他的地方用到的 .long cr_alignment表示这里是一个指针。指向这个 globl cr_alignment
实际来看比如
.init.text:C048E328 __mmap_switched_data DCD __init_end ; DATA XREF: _sinittexto
.init.text:C048E328 ; _sinittext+4o
.init.text:C048E32C DCD __init_end
.init.text:C048E330 DCD _edata
.init.text:C048E334 DCD NR_syscalls
.init.text:C048E338 DCD processor_id
.init.text:C048E33C DCD __machine_arch_type
.init.text:C048E340 DCD __atags_pointer
.init.text:C048E344 DCD cr_alignment
.init.text:C048E348 DCD unk_C04AFFF8
C048E344 就是 .longcr_alignment @ r7的地址。里面保存的值是 C048E344 【D8 2D 4B C0】是C04B2DD8
这个C04B2DD8就是.space 4的地址(也就是cr_alignment的标号)了。
再看这个地址里面的值。
.data:C04B2DD8 EXPORT cr_alignment
.data:C04B2DD8 cr_alignment DCD 0
明显看到初始值是0,那么一定有个地方初始化了。
跟踪源头找到
__mmap_switched:(跳转到这里的时候mmu已经开启了,所以是0XC开头的地址)
保存__mmap_switched_data地址到r3 C048E328
adr r3, __mmap_switched_data
从__mmap_switched_data取出前四个dword保存到r4r5r6r7.同时r3也增加了4*4(注意r3!末尾感叹号表示处理后自增),因此r3此时等于.longprocessor_id @ r4的地址,也就是C048E338
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5@ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0@ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
再次从r3依次取出数据到r4-r7 sp。把C048E344里面的值给了r7.cr_alignment的指针=r7=C04B2DD8
ARM( ldmia r3, {r4, r5, r6, r7, sp})
THUMB( ldmia r3, {r4, r5, r6, r7} )
THUMB( ldr sp, [r3, #16] )
str r9, [r4]@ Save processor ID
str r1, [r5]@ Save machine type
str r2, [r6]@ Save atags pointer
判断r7是不是0。如果不是0那么将r0的值放到r7指向的地址里面。也就是说cr_alignment的值就是这里r0的值。
cmp r7, #0
strne r0, [r7] @ Save control register values
b start_kernel
通过gbd查看
(gdb) x/1xw 0xc04b2dd8
0xc04b2dd8: 0x00000000
(gdb) si
0xc048e320 in ?? ()
(gdb) si
0xc048e324 in ?? ()
(gdb) x/1xw 0xc04b2dd8
0xc04b2dd8: 0x00c5387d
执行完
cmp r7, #0
strne r0, [r7] @ Save control register values
的确将cr_alignment的值改变了。
接下来只要追踪r0的值了。
回溯到arm/arch/kernel/head.S的__turn_mmu_on
ENTRY(__turn_mmu_on)
mov r0, r0
instr_sync
之前使用物理地址
mcr p15, 0, r0, c1, c0, 0@ write control reg 开启MMU了。因此turn_mmu_on物理和虚拟地址是一一对应的否则下面的语句无法执行
之后使用虚拟地址
mrc p15, 0, r3, c0, c0, 0@ read id reg
instr_sync
mov r3, r3
mov r3, r13
ret r3
__turn_mmu_on_end:
ENDPROC(__turn_mmu_on)
继续向上回溯
__enable_mmu:
bic r0, r0, #CR_A
这里操作的一次r0
.....
.....
b __turn_mmu_on
继续回溯
测试跟踪到
- ldr r13, =__mmap_switched @ address to jump to after
- @ mmu has been enabled
- adr lr, BSYM(1f) @ return (PIC) address
- mov r8, r4 @ set TTBR1 to swapper_pg_dir
- THUMB( add r12, r10, #PROCINFO_INITFUNC )
- THUMB( mov pc, r12 )
- 1: b __enable_mmu
ADD R12, R12, R10
MOV PC,R12
这两条执行后。r0变成了cp15的cr1寄存器的值。
对应的R12函数是和CPU相关的
也就是调到到对应的
- struct proc_info_list {
- unsigned int cpu_val;
- unsigned int cpu_mask;
- unsigned long __cpu_mm_mmu_flags; /* used by head.S */
- unsigned long __cpu_io_mmu_flags; /* used by head.S */
- unsigned long __cpu_flush; /* used by head.S */
- const char *arch_name;
也就是在
.head.text:C0008050 BL __vet_atags
.head.text:C0008054 BL __fixup_pv_table
.head.text:C0008058 BL __create_page_tables
.head.text:C000805C LDR SP, =_sinittext
.head.text:C0008060 ADR LR, loc_C0008074
.head.text:C0008064 MOV R8, R4
.head.text:C0008068 LDR R12, [R10,#0x10]
.head.text:C000806C ADD R12, R12, R10
.head.text:C0008070 MOV PC, R12
这里下断点。检查PC执行完R12以后r0的值。看看后面有没有变化。或者在那里又被修改了。之类的。回去用JLINK调试吧
最后发现是自己调试的时候修改了r0 r1的指令,后来没有及时恢复,晕死。。总之这里了解了cr_alignment的方式。后续再看问题吧