arm的协处理寄存器c1的对齐控制异常

编译好了树莓派内核。在运行到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

继续回溯

测试跟踪到

  1. ldr r13, =__mmap_switched       @ address to jump to after  
  2.                         @ mmu has been enabled  
  3.     adr lr, BSYM(1f)            @ return (PIC) address  
  4.     mov r8, r4              @ set TTBR1 to swapper_pg_dir  
  5.  THUMB( add r12, r10, #PROCINFO_INITFUNC    )  
  6.  THUMB( mov pc, r12             )  
  7. 1:  b   __enable_mmu  
也就是在执行了

ADD     R12, R12, R10

MOV     PC,R12

这两条执行后。r0变成了cp15的cr1寄存器的值。

对应的R12函数是和CPU相关的

也就是调到到对应的

  1. struct proc_info_list {  
  2.     unsigned int        cpu_val;  
  3.     unsigned int        cpu_mask;  
  4.     unsigned long       __cpu_mm_mmu_flags; /* used by head.S */  
  5.     unsigned long       __cpu_io_mmu_flags; /* used by head.S */  
  6.     unsigned long       __cpu_flush;        /* used by head.S */  
  7.     const char      *arch_name;  
的__cpu_flush函数中


也就是在

.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的方式。后续再看问题吧


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值