您现在的位置是:首页 > 文章详情

Cortex-A7双核处理器的双核启动调试记录

日期:2020-04-23点击:434

davinci双核启动调试记录

第一阶段

双核启动原理概述:

达芬奇双核启动的原理和流程如下图所示:

 

 

由上图可知,CPU1跳转是通过CPU0触发的,CPU0为CPU1启动做的工作如下:

(1)将secondary CPU启动的地址填写到USB的一个寄存器中,因为目前没有一块可以使用的UBOOT和kernel通信的内存空间,所以暂时使用USB的寄存器作为跳转触发条件;

(2)设置identity mapping地址映射空间,这段地址空间是虚拟地址和物理地址一一映射的。

 

实验过程

实验前现象

通过实验结果可以确定的是,CPU0针对CPU1设置好启动的条件和环境之后,CPU0通过USB寄存器通知到CPU1开始从secondary_startup启动成功,CPU1进入到kernel的启动阶段的汇编启动阶段。但是在使能MMU后,并没有预期的跳转到汇编标__secondary_switched的地址开始运行。

 

实验及步骤

实验一确定CPU0设置的identity mapping地址映射空间是设置正确,且可以正常访问工作。

步骤一:由于内核的驱动模块是最后启动,所以在内核的tty驱动中添加identity mapping的地址的打印;

步骤二:在drivers/tty/serial/8250/8250_core.c中的static int __init serial8250_init(void)函数中添加如下代码:

 

            

步骤三:启动达芬奇板卡,通过串口,输出如下,由下图可知identity mapping地址空间的物理地址和虚拟地址是一一映射的。

            

步骤四:确认kernel的链接脚本中,针对identity mapping地址空间的划分,是在text段中的划分。定义如下:

 步骤五:在Jlink终端里,读取0xc0100000地址空间的数据,打印如下:

步骤六:反汇编vmlinux,找到0xc0100000地址位置,如下图所示。与步骤七里通过Jlink读取内存空间里的数据是一致的。

步骤七:在linux中,使用命令devmem访问identity mapping物理地址空间,打印如下:

    

实验一结论由实验一可以得出,CPU1在identity mapping内存空间里进行的MMU使能,并且使能MMU前后物理地址和虚拟地址确实时一一映射的。可以保证CPU1运行在identity mapping地址空间使能MMU后,下一条指令的物理地址和虚拟地址是一样的。

 

实验二:验证CPU1使能MMU后,确保下一条汇编指令地址,送入MMU的是identity mapping地址,且是与使能MMU之前的地址连续的

步骤一:在kernel源代码arch/arm/kernel/head.S中,在CPU1打开MMU的位置,添加如下图调试代码,在CPU1开始MMU前后设置地址标号。汇编标号:__continue_run_40,设置在CPU1开启MMU之前;汇编标号:__continue_run_44,设置在CPU1开始MMU之后第一条汇编指令的地址位置;汇编标号:__continue_run_48——__continue_run_54,每隔一条汇编指令,设置一个汇编地址标号。

步骤二:反汇编内核image,即vmlinux,CPU1打开MMU前后的反汇编代码如下图所示。由下图反汇编代码可知CPU1打开MMU时是使用的是identity mapping地址空间。而且CPU1打开MMU前后的反汇编代码的地址是连续的,且均在identity mapping地址空间。

从反汇编代码可知汇编地址标号__continue_run_40——__continue_run_54 的值也在identity mapping地址空间内。所以从反汇编代码可知所有指令的地址均在identity mapping地址空间内。

步骤三:使用Jlink工具读取identity mapping地址空间的内容,内容如下图所示,从Jlink打印的汇编地址编号__continue_run_40——__continue_run_54 数据可知,与步骤二的地址一致。

步骤四:在linux环境下使用devmem工具读取汇编地址编号__continue_run_40——__continue_run_54 的地址,地址如下图所示,地址与步骤二、三的地址一致。

实验二结论CPU1在打开MMU后第一条指令的地址,其地址值是物理内存和虚拟内存是同样数值。也就是说,打开MMU之后,CPU1发出的指令地址是与打开MMU之前地址连续的,物理地址和虚拟地址是同样的,且连续的。

 

实验二的现象CPU1并没有运行到预期的汇编地址位置,因此需要做实验三,查找CPU1开启MMU之后,CPU1发出的真实地址值是多少。

 

实验三:验证CPU1打开MMU之后第一条指令实际的地址值

步骤一:参考实验二的步骤一,在同样代码位置添加如下代码,汇编地址标__continue_run_44——__continue_run_54 设置在CPU1打开MMU之后的汇编代码位置。汇编地址标号__continue_run_48——__continue_run_54 的地址,在打开MMU之前,将汇编地址标号的地址存入地址0xc0100120——0xc010012c中。同样的地址标号__continue_run_48——__continue_run_54 的地址,在打开MMU之后,将汇编地址标号的地址存入地址0xc0100130——0xc010013c中。实验三的正确预期结果应该是:

地址0xc0100120内的数值 == 地址0xc0100130内的数值;

地址0xc0100124内的数值 == 地址0xc0100134内的数值;

地址0xc0100128内的数值 == 地址0xc0100138内的数值;

地址0xc010012c内的数值 == 地址0xc010013c内的数值。

步骤二:反汇编内核的image,即vmlinux,其反汇编的结果如下图所示。由下图可知CPU1在使能MMU前后,均运行在identity mapping地址空间,且地址空间是连续的,与实验二的步骤二的结果一致。

步骤三:步骤二之后Jlink打印的结果如下图1和图2所示,预期的CPU1开启MMU前后的指令地址的结果不一致。即如下:

地址0xc0100120内的数值  != 地址0xc0100130内的数值;

地址0xc0100124内的数值  != 地址0xc0100134内的数值;

地址0xc0100128内的数值  != 地址0xc0100138内的数值;

地址0xc010012c内的数值  != 地址0xc010013c内的数值。

 

图一

图二

图三

 

步骤四:为了确认0xc0100130——0xc010013c 地址内的内容没有其他程序或者操作会影响,在本实验三的步骤一代码基础上屏蔽掉对0xc0100130——0xc010013c 地址写入。代码修改如下图所示。其结果在Jlink里打印如上步骤三的图3所示。本步骤可以得出0xc0100130——0xc010013c 的地址只有下图屏蔽的代码修改。

                    

步骤五:使用devmem命令读取步骤二之后的物理内存0xc0100118——0xc010013c地址空间内的值,如下图所示。与步骤三和步骤一的对应地址结果一致。

实验三结果分析实验三的结果可以得出,在CPU1打开MMU之后,汇编指令的地址并不是程序要求的正确地址,CPU1的取指指令取到的地址并不是程序给的指令地址,程序给出了正确的指令地址,但是在CPU1里却变成了其他地址。另外,CPU1开启MMU之后的指令地址,即实验三的步骤三的0xc0100130——0xc010013c地址内的值,分别是 0xe1a0f003 、0xc010d5a0 、 0xee112f10 、 0xe3c22001。而这些数值其实质是0xc0100130——0xc010013c地址内本应该存储的ARM指令微码。也就是说CPU1开启MMU之后的指令都没有运行。也就是MMU映射的地址不正确。而往这四个字的地址空间内写入汇编指令地址标号的之前,就CPU1就将MMU关闭,所以写入的数据不存在写到其他地址的可能。而达芬奇的kernel的虚拟地址空间如下图1所示。而汇编执行的地址段,由实验一的步骤4可知在text段内,而CPU1打开MMU之后的四个汇编指令地址不连续,而且有三个地址超出了text段地址空间。下图2所示结果是identity mapping地址空间为0xc0100000 - 0xc0100154,0xc0100130——0xc010013c地址在其范围之内,所以可以确保是一一映射的。

 

图1

图2

图3

 

分析实验:打印出CPU1使能MMU前,CP15的C1寄存器的值,并且分析、验证各个寄存器设置无错误。(达芬奇设置MMU的代码是arm的特定指令集版本通用代码,其他cortex-A7芯片厂商均使用的是这段代码c1寄存器的值写入的是:0x10c5387d,通过对比分析手册文档《Cortex-A7_MPCore_Technical_Reference_Manual》里第4章节的寄存器的各个bit的意义,验证是设置无误的。

 

综上实验和分析得出的结论:CPU1使能MMU后,MMU映射的地址不正确。

        

第二阶段

在非kernel环境下,编写一个测试程序,对CPU1开启MMU后进行验证。因为一经上电,两个CPU core都会从0地址开始运行,而davinci的板卡设计是将Flash地址空间作为0地址,所以需要设计一个类似uboot一样的极简启动控制代码,目的是将两个core给hold住,loop一个指定的地址是否为0,非0则跳转到功能代码中。往指定地址写入非0数值的操作使用Jlink实现。极简的从Flash启动代码如下所示。0x4000BF00地址作为CPU0判断是否跳转到SRAM中运行的判断地址,0x4000BF04地址作为CPU1判断是否跳转到SRAM中运行的判断地址。

 

.text

.global __start

 

__start:

 

        ldr     r0, =0x40004000

        mov     r1, #0

        str     r1,[r0]

 

        ldr     r0, =0x4000BF10         @ setup CPU0 signal

        mov     r1, #0

        str     r1,[r0]

 

        ldr     r0, =0x4000BF14         @ plus 1 space

        mov     r1, #0

        str     r1,[r0]

 

        ldr     r0, =0x4000BF20         @ setup CPU1 signal

        mov     r1, #0

        str     r1,[r0]

 

        ldr     r0, =0x4000BF24         @ plus 1 space

        mov     r1, #0

        str     r1,[r0]

 

        mov     r5, #0

 

        MRC     p15, 0, r0, c0, c0, 5   @ Read MPIDR

        ANDS    r0, r0, #3

        cmp     r0, #0

        beq     cpu_0_wait

 

cpu_1_wait:

        ldr     r4, =0x4000BF24

        str     r5, [r4]

        add     r5, r5, #1

 

        ldr     r2, =0x4000BF04         @ stage test point

        ldr     r3, =0x22220808         @ test value

        str     r3, [r2]

 

        ldr     r0, =0x4000BF20         @ not 0 is signal for setup cpu1

        ldr     r1, [r0]

        cmp     r1, #0

        beq     cpu_1_wait

 

        ldr     r7, =0x22

        ldr     pc, =0x40004000         @ bug point

 

cpu_0_wait:

        ldr     r4, =0x4000BF14

        str     r5, [r4]

        add     r5, r5, #1

 

        ldr     r2, =0x4000BF00         @ stage test point

        ldr     r3, =0x11110808         @ test value

        str     r3, [r2]

 

        ldr     r0, =0x4000BF10         @ not 0 is signal for setup cpu0

        ldr     r1, [r0]

        cmp     r1, #0

        beq     cpu_0_wait

 

        ldr     r7, =0x11

        ldr     pc, =0x40004000         @ jump to SRAM text

 

loop:

        b loop

 

进入到SRAM中,CPU0和CPU1的运行代码如下:

.text

.global __start_ram

 

__start_ram:

        MRC     p15, 0, r0, c0, c0, 5   @ Read MPIDR

        ANDS    r0, r0, #3

        cmp     r0, #0

        beq     cpu_0_run

        b       cpu_1_run

 

        ldr     r0, =0x4000BF00

        ldr     r1, =0xEEEEEEEE

        str     r1, [r0]

 

ERR_loop:

        b       ERR_loop

 

cpu_0_run:

        ldr     sp, =0x4000B4FF

        ldr     r0, =0x4000BF00

        ldr     r1, =0x1234abcd

        str     r1, [r0]

 

        /* setup page table.*/

        ldr     r0, =0x40000400

        ldr     r1, =0x40000c1e

      str     r1, [r0]

 

        /* enable mmu.*/

//      mov     r0, #0

//      mcr     p15, 0, r0, c7, c1, 0           @ clean ICaches and DCaches

//

//      mcr     p15, 0, r0, c7, c10, 4          @ drain write buffer on v4

//

//      mcr     p15, 0, r0, c8, c7, 0

 

//      ldr     r5, =0xFFFFFFFF

//      mcr     p15, 0, r5, c3, c0, 0           @ load domain access register

 

//      ldr     r4, =0x40000000

//      mcr     p15, 0, r4, c2, c0, 0           @ load page table pointer

 

 

//      mrc     p15, 0, r0, c1, c0, 0

//      bic     r0, r0, #0x3000

//      bic     r0, r0, #0x0300

//      bic     r0, r0, #0x0087

//      orr     r0, r0, #0x0002

//      orr     r0, r0, #0x0004

//      orr     r0, r0, #0x1000

      orr     r0, r0, #0x0001

      mcr     p15, 0, r0, c1, c0, 0

 

        /* test CPU1 run after open MMU.*/

        ldr     r0, =0x4000BF30

        ldr     r1, =0xAAAAAAAA

        str     r1, [r0]

        ldr     r0, =0x4000BF34

        ldr     r1, =0xBBBBBBBB

        str     r1, [r0]

        ldr     r0, =0x4000BF38

        ldr     r1, =0xCCCCCCCC

        str     r1, [r0]

 

loop_cpu0:

        b       loop_cpu0

 

cpu_1_run:

        ldr     sp, =0x4000B8FF

        ldr     r0, =0x4000BF04

        ldr     r1, =0xabcd1234

        str     r1, [r0]

        /* enable mmu.*/

        mov     r0, r0

        mov     r0, r0

        mov     r0, r0

 

        /* setup page table.*/

        ldr     r0, =0x40000400

        ldr     r1, =0x40000c1e

        str     r1, [r0]

 

        /* enable mmu.*/

//      mov     r0, #0

//      mcr     p15, 0, r0, c7, c1, 0           @ clean ICaches and DCaches

//

//      mcr     p15, 0, r0, c7, c10, 4          @ drain write buffer on v4

//

//      mcr     p15, 0, r0, c8, c7, 0

 

        ldr     r5, =0xFFFFFFFF

        mcr     p15, 0, r5, c3, c0, 0           @ load domain access register

 

        ldr     r4, =0x40000000

        mcr     p15, 0, r4, c2, c0, 0           @ load page table pointer

 

 

        mrc     p15, 0, r0, c1, c0, 0

//      bic     r0, r0, #0x3000

//      bic     r0, r0, #0x0300

//      bic     r0, r0, #0x0087

//      orr     r0, r0, #0x0002

//      orr     r0, r0, #0x0004

//      orr     r0, r0, #0x1000

        orr     r0, r0, #0x0001

        mcr     p15, 0, r0, c1, c0, 0

 

loop_test_0:

        /* test CPU1 run after open MMU.*/

        ldr     r0, =0x4000BF40

        ldr     r1, =0x33333333

        str     r1, [r0]

        ldr     r0, =0x4000BF44

        ldr     r1, =0x55555555

        str     r1, [r0]

        ldr     r0, =0x4000BF48

        ldr     r1, =0x77777777

        str     r1, [r0]

        b       loop_test_0

 

        mov     r0, r0

        mov     r0, r0

        mov     r0, r0

 

loop_cpu1:

        b       loop_cpu1

loop:

        b       loop                   

 

实验的结果是CPU0能够在使能MMU后运行正常,CPU1运行不正常。

根据上述实验结果,需要排除CPU1、CP15和MMU三者硬件设计正确。

鉴于第一阶段得出MMU映射不正确的推断,那么引申出来两个可能的原因。第一个原因是MMU硬件工作不正常,内存的页表设置正确;第二个原因是MMU硬件工作正常,内存的页表设置不正确。鉴于CPU0和CPU1使用的是相同的内存页表,而CPU0在此内存页表设置下是工作正常的,所以内存的页表设置是正确的。所以第二个原因不成立。此时有一个问题,就是既然第二个原因不成立,也就是说MMU硬件是工作正常的,那么CPU0和CPU1均应该正常才是。这个问题的回答是:假设CPU使用MMU仅仅是CPU的core和MMU两者的关系,那么上述问题是真命题,但是CPU使能MMU是通过CP15协处理器实现的,那么就应该有必要确认CPU1、CPU1的CP15、MMU三者在硬件上是设置正确,且硬件正确的。

所以,协调硬件的同事,在PC上进行仿真验证。因为PC上验证的模拟硬件资源与davinci板卡的硬件资源不同,所以需要针对PC验证设计一个验证程序。

验证程序需要有如下几个限制:

  1. 使用MMU的分段功能,因为identity mapping空间使用的是分段;
  2. 使能MMU的时候,关闭cache等,仅仅使能MMU;
  3. 手动填入页表的表项,确保至少映射了两块,flash和SRAM。

验证程序如下:

 

.text

.global __start

 

__start:

         ldr     r0, =0x40004000

         mov     r1, #0

         str     r1,[r0]

         ldr     r0, =0x4000BF10         @ setup CPU0 signal

         mov     r1, #0

         str     r1,[r0]

         ldr     r0, =0x4000BF20         @ setup CPU1 signal

         mov     r1, #0

         str     r1,[r0]

         MRC     p15, 0, r0, c0, c0, 5   @ Read MPIDR

         ANDS    r0, r0, #3

         cmp     r0, #0

         beq     cpu_0_wait

 

cpu_1_wait:

         ldr     r2, =0x4000BF04         @ stage test point

         ldr     r3, =0x22220808         @ test value

         str     r3, [r2]

 

cpu_1_pause:

         ldr     r0, =0x4000BF20         @ not 0 is signal for setup cpu1

         ldr     r1, [r0]

         cmp     r1, #0

         beq     cpu_1_pause

 

         mov     r0, r0

         mov     r0, r0

         mov     r0, r0

         /* setup page table.*/

         ldr     r0, =0x40000000

         ldr     r1, =0x00000c1e

         str     r1, [r0]

         /* enable mmu.*/

 //      mov     r0, #0

 //      mcr     p15, 0, r0, c7, c1, 0           @ clean ICaches and DCaches

 //      mcr     p15, 0, r0, c7, c10, 4          @ drain write buffer on v4

 //      mcr     p15, 0, r0, c8, c7, 0

         ldr     r5, =0xFFFFFFFF

         mcr     p15, 0, r5, c3, c0, 0           @ load domain access register

         ldr     r2, =0x4000BF50

         str     r5, [r2]

         ldr     r4, =0x40000000

         mcr     p15, 0, r4, c2, c0, 0           @ load page table pointer

         ldr     r2, =0x4000BF54

         str     r4, [r2]

         mrc     p15, 0, r0, c1, c0, 0

 //      bic     r0, r0, #0x3000

 //      bic     r0, r0, #0x0300

 //      bic     r0, r0, #0x0087

 //      orr     r0, r0, #0x0002

 //      orr     r0, r0, #0x0004

 //      orr     r0, r0, #0x1000

         orr     r0, r0, #0x0001

         ldr     r2, =0x4000BF58

         ldr     r3, =0x58585858

         str     r3, [r2]

         mcr     p15, 0, r0, c1, c0, 0

 

 loop_test_1:

         /* test CPU1 run after open MMU.*/

         ldr     r0, =0x4000BF40

         ldr     r1, =0x33333333

         str     r1, [r0]

         ldr     r0, =0x4000BF44

         ldr     r1, =0x55555555

         str     r1, [r0]

         ldr     r0, =0x4000BF48

         ldr     r1, =0x77777777

         str     r1, [r0]

         b       loop_test_1

//      ldr     r7, =0x22

//      ldr     pc, =0x40004000         @ bug point

 

cpu_0_wait:

        ldr     r2, =0x4000BF00         @ stage test point

        ldr     r3, =0x11110808         @ test value

        str     r3, [r2]

//      ldr     r0, =0x4000BF10         @ not 0 is signal for setup cpu0

//      ldr     r1, [r0]

//      cmp     r1, #0

//      beq     cpu_0_wait

        ldr     r2, =0x4000BF20

        ldr     r3, =0x11111111

        str     r3, [r2]

        ldr     r0, =0x4000BF30

        ldr     r1, =0xAAAAAAAA

        str     r1, [r0]

        ldr     r0, =0x4000BF34

        ldr     r1, =0xBBBBBBBB

        str     r1, [r0]

        ldr     r0, =0x4000BF38

        ldr     r1, =0xCCCCCCCC

        str     r1, [r0]

cpu_0_loop:

        b       cpu_0_loop

        ldr     r7, =0x11

        ldr     pc, =0x40004000         @ jump to SRAM text

loop:

        b loop

 

PC上仿真的结果是CPU1能够在使能MMU的情况下能够工作正常。可以将这个结论作为一个不坚定的正确假设。以此为基础做进一步的实验验证。梳理arm官方的core手册,分析CP15相关设置是否正确,MMU机制和TLB设置是否正确。使用最新版本的core手册,发现CP15的C2寄存器的设计不同,按照最新的手册更改程序后,裸机验证程序下,CPU0和CPU1均能在使能MMU后工作正常。(注:验证程序在配套的程序包的git分支master中)

 

第三阶段

经过上述两个阶段的实验和分析,得出如下结论:

  1. Kernel启动后,CPU0工作正常,CPU1没有引导注册到SMP子系统中;
  2. Kernel中,经过实验验证得出kernel里的identity mapping正确;
  3. CPU1、CPU1的CP15、MMU三者的硬件可以确认是正确的;
  4. CPU1使用MMU分段能够寻址正确。

梳理上述四个结论,可以初步确定是kernel的问题。

回归到最初的问题现象,问题定位的代码如下图所示:

问题的现象,是使能MMU后,不能使用下图所示代码打印:

通过kernel的反汇编,如下图所示,可知__error_p汇编功能模块链接的地址不是处于identity mapping地址空间中。所以这部分代码其实是需要使用二级分页进行跳转的,如果跳转不成功,就是上述现象。

在kernel中,使能MMU这段代码是运行在identity mapping地址空间中,这部分与裸机验证程序的设计是相同的,都是使用的分段,所以可以确认只要kernel里打开MMU后,在identity mapping空间里运行,肯定是正确的。而使用分页机制,是否正确,有待商榷。通过反汇编代码(如下图1所示),以及kernel的identity mapping空间(下图2所示)可知0xc0100044~0xc0100060是cpu_ca9mp_reset函数地址空间,暂时启动过程中用不到reset功能,所以可以将这段空间作为测试空间。

图1

图2

将arch/arm/kernel/head.S文件中,针对secondary CPU的启动的调试信息,都使用内存读写指令写到0xc0100044~0xc0100060中,然后使用Jlink查看。经过实验,发现是CPU1没有读取到swapper_pg_dir的值、secondary_data.stack的值和translation table base的值。但是通过第一阶段CPU0的打印的信息可知CPU0设置了这些值,但是CPU1没有得到。分析原因有如下可能:

  1. swapper_pg_dir和secondary_data.stack的值在bss段,而CPU1运行的时候,bss段的数据改变了;
  2. swapper_pg_dir和secondary_data.stack的值存储的位置,CPU1没有权限访问;
  3. CPU0和CPU1协同工作不正确。

验证第一种可能,在kernel单核启动成功后,使用Jlink查看bss段的数据,数据并没有改变,所以可以排除第一种可能。验证第二种可能,是配置CPU1的MMU和CP15的domain时候,设置成全地址空间可读写权限,问题依旧,故可以排除。

至于第三种情况,双核启动的设计是CPU1等待CPU0的启动信号后再进入kernel,而再kernel中,使CPU0不启动CPU1,使用Jlink命令查看CPU1写identity mapping的地址,发现依然写入,所以可以断定CPU1并没有按照预期停住。

分析uboot代码,CPU1期望使用wfene指令停住,kernel使用sev指令使CPU1进入kernel,但是这两条指令没有起到预期效果。不适用这个指令,使用寄存器的特定值唤起设计,问题解决。

 

 

原文链接:https://my.oschina.net/u/4194484/blog/3434282
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章