linux内核cpu初始化,基于MPC8641D处理器的Linux SMP系统初始化过程分析 七

li

r3,0

<3,7>:mtspr

SPRN_SPRG_RTAS,r3 // 0 =>

not in RTAS

SPRN_SPRG_RTAS宏代表的是SPRG2寄存器,<3,7>初始化该寄存器值为0。在我们的MPC8641HPCN平台的.config文件中没有配置CONFIG_PPC_RTAS,所以我们不会使用SPRG2寄存器中的这个值。

<3,8>:lis

r1,init_thread_union@ha

<3,9>:addi

r1,r1,init_thread_union@l

解释:

init_task进程使用init_thread_union数据结构描述的内存区域作为该进程的堆栈空间,并且和自身的thread_info参数公用这一内存空间空间,其数据结构的定义如下(linux-

2.6.38/include/linux/sched.h)

union thread_union {

struct thread_info thread_info;

unsigned long stack[THREAD_SIZE/sizeof(long)];

};

init_thread_union变量在arch/powerpc/kernel/init_task.c中定义,代码如下:

union thread_union init_thread_union

__init_task_data

=

{ INIT_THREAD_INFO(init_task) };

该变量用INIT_THREAD_INFO(init_task)把init_thread_union变量初始化为init_task进程描述符的thread_info。

备注:__init_task_data并不是一个变量,它只是一个宏。该宏在include/linux/init_task.h中定义:

#define __init_task_data

__attribute__((__section__(".data..init_task")))

该宏把init_thread_union变量放入.data..init_task段中。

INIT_THREAD_INFO在arch/powerpc/include/asm/thread_info.h中定义,其代码如下:

#define INIT_THREAD_INFO(tsk)

\

{

\

.task =

&tsk,

\

<3,9,1>

.exec_domain =

&default_exec_domain, \

.cpu =

0,

\

<3,9,2>

.preempt_count = INIT_PREEMPT_COUNT,

\

.restart_block = {

\

.fn = do_no_restart_syscall, \

},

\

.flags =

0,

\

}

所以0号进程init_task的init_thread_union 描述符中的tsk成员指向init_task进程描述符,cpu成员赋值为0,表示指出init_task进程目前在MPC8641D的core 0上运行。

thread_union结构体定义在include/linux/sched.h文件中,代码如下:

union thread_union

{

struct thread_info thread_info;

unsigned long stack[THREAD_SIZE/sizeof(long)];

};

#define init_thread_info

(init_thread_union.thread_info)

#define init_stack

(init_thread_union.stack)

从代码中我们可以看出为了方便使用,PowerPC

linux定义了两个宏:init_thread_info和init_stack,分别指向init_task进程的thread_info和stack,针对我们的MPC8641D平台,THREAD_SIZE为8K

<3,10>:li

r0,0

解释:将r0寄存器初始化为0

<3,11>:stwu

r0, THREAD_SIZE-STACK_FRAME_OVERHEAD(r1)

解释:<3,9>和<3,10>是为init_task进程创建堆栈空间。stwu为init_task分配一个最小的栈帧(即16个字节),因为init_task进程的函数体不带参数且不返回,所以最小栈帧就足够用的了。

备注:r1指针是PowerPC linux中的栈指针,在执行

stwu

r0, THREAD_SIZE-STACK_FRAME_OVERHEAD(r1)

之前,r1指向8K栈空间的基地址addr(为了方便描述,假设为addr);当执行完这条指令后r1指向addr+8K-16,并且addr+8K-16指向的内存值为0。

执行到此处init_task进程的进程描述符,代码段,数据段和堆栈段已经建立起来了,但是init_task的地址空间此刻还没有建立,接下来的代码一个非常重要的功能就是创建init_task进程的地址空间。下面我们将依次分析machine_init()、_save_cpu_setup()以及MMU_init()函数。

<3,12>:mr

r3,r31

<3,13>:mr

r4,r30

解释:

首先我们看一下_start函数的入口处的指令序列:

__start:

cmpwi

0,r5,0

beq

1f

1:

mr r31,

r3

mr r30,

r4

。。。。。。

通用寄存器r3:指向OF Tree的物理地址

通用寄存器r4:指令Linux内核所在的物理地址

这两个值都是UBoot传递给Linux-smp内核的。

从上面的代码我们可以看出:

这两个值均备份在r31,r30寄存器中,现在要恢复r3,r4的值,其中r3的值是作为machine_init()函数的参数!

下面我们将详细的分析这些过程:

<3,14>:bl

machine_init

输入参数:r3中的值(OF Tree的物理地址)

该值是有boot

loader(比如在MPC8641HPCN开发板,我使用的是U-boot)传过来的OF设备树的地址。

该函数在arch/powerpc/kernel/setup_32.c中定义,主要实现两个功能:

一:分析OF树的结构,获得当前处理器的内存使用情况,创建LMB结构,同时获得当前CPU在OF树中的硬件信息;

二:确认当前处理器系统的ppc_md结构,ppc_md结构定义了当前linux-smp的一系列钩子函数,设置ppc_md函数的目的是为了进一步优化linux-smp系统源代码结构。

machine_init()函数的代码如下:

notrace void __init machine_init(unsigned long

dt_ptr)

{

lockdep_init();

<3,13,1>

//Enable early debugging if any specified (see udbg.h)

udbg_early_init();

<3,13,2>

//Do some early initialization based on the flat device

tree

early_init_devtree(__va(dt_ptr)); <3,13,3>

probe_machine();

<3,13,4>

setup_kdump_trampoline();<3,13,5>

#ifdef CONFIG_6xx

if (cpu_has_feature(CPU_FTR_CAN_DOZE) ||

cpu_has_feature(CPU_FTR_CAN_NAP))

ppc_md.power_save = ppc6xx_idle;<3,13,6>

#endif

#ifdef CONFIG_E500<3,13,7>

if (cpu_has_feature(CPU_FTR_CAN_DOZE) ||

cpu_has_feature(CPU_FTR_CAN_NAP))

ppc_md.power_save = e500_idle;

#endif

if (ppc_md.progress)

ppc_md.progress("id mach(): done", 0x200);

<3,13,8>

}

分析:

<3,13,1>:lockdep_init()

在我们的MPC8641HPCN平台lockdep_init()函数是一个宏函数,在include/lockdep.h中被定义为

# define

lockdep_init()

do { } while (0)

<3,13,2>:udbg_early_init()

在我们的MPC8641HPCN平台是一个空函数。

备注:

关于lockdep_init(),udbg_early_init()这两个函数,前者用于启动Lock Dependency Validator(内核依赖的关系表),本质上就是建立两个散列表calsshash_table和chainhash_table,并初始化全局变量lockdep_initialized,标志已初始化完成。后者用于初始化早期调试输出,可以通过配置config文件使能其中的一个,一般都是NS16550的串口打印调试。这两个函数在编译内核是都是可配置的,我在编译linux-smp内核映象均没有配置,所以将不分析这两个函数。

<3,13,3>:early_init_devtree(__va(dt_ptr))

该函数就是我们上面所说的machine_init()函数的第一个功能,即分析OF树的结构,获得当前处理器的内存使用情况,创建LMB结构,同时获得当前CPU在OF树中的硬件信息,比如CPU主频、内部寄存器级基地址等等。

这里需要说明一下__va(dt_ptr)函数,既然dt_ptr中存放着设备树所在的物理地址处,为啥要将这个地址再转换成有效地址EA呢?因为我们的MMU已经开启了,MMU硬件会自动把有效地址EA映射到物理地址PA。

如下图所示r31中存放设备树物理地址,__va()会把它转换到虚拟地址,放在r3寄存器中。

a4c26d1e5885305701be709a3d33442f.png

为了能清楚的说明Linux-smp如何利用OF设备树获取MPC8641HPCN的硬件信息(比如CPU主频、内部寄存器基地址等等),我不得不介绍一下扁平设备树的来历:

设备树DT(Device

Tree)是描述目标板硬件的数据结构,我们可以简单的设想一下如果我们在Linux kernel中硬编码硬件的基本信息,那么如果目标板做出更改的话,我们必须重新编译整个Linux kernel,才能把修改的信息通知Linux。如果我们把对硬件的描述信息存放在设备树DT中,我们只需要修改DT,然后把DT传递给Linux即可,这样做的使得Linux尽可能地降低硬件依赖,从而利于加速支持包的开发,降低硬件带来的变化需求和成本,降低对内核设计和编译的要求。

关于DT的详细信息我们可以参考:

IBM、Sun 等厂家的服务器最初都采用了Firmware(一种嵌入到硬件设备中的程序,用于提供软件和硬件之间的接口,类似于X86上的BIOS),用于初始化系统配置,提供操作系统软件和硬件之间的接口,启动和运行系统。后来为了标准化和兼容性,IBM、Sun 等联合推出了固件接口IEEE

1275标准,让他们的服务器如IBM PowerPC pSeries,Apple PowerPC,Sun SPARC 等均采用OpenFirmware,在运行时构建系统硬件的设备树信息传递给内核,进行系统的启动运行。从而减少内核对系统硬件的严重依赖,利于加速支持包的开发,降低硬件带来的变化需求和成本,降低对内核设计和编译的要求。

随着Linux/ppc64

内核的发展,内核代码从原来的arch/ppc32 和arch/ppc64

逐渐迁移到统一的arch/powerpc 目录(例如我们使用的Linux-2.6.38内核就是如此,基于PowerPC的平台代码都集中在arch/powerpc目录下),并在内核代码引入Open Firmware API

以使用标准固件接口(参考:Power.org(TM) Standard for Embedded Power Architecture(TM)

Platform Requirements (ePAPR))。Linux 内核在运行时需要知道硬件的一些相关信息,当我们使用ARCH=powerpc 参数编译的内核镜像,这些信息基于Open

Firmware 规范,以设备树的形式存在(我编译的基于PowerPC的linux-2.6.38-smp内核映像就使用了这个参数)。这样内核在启动时扫描Open Firmware 提供的设备树,从而读取平台的硬件设备信息,搜索匹配的设备驱动程序并将该驱动程序绑定到设备

在嵌入式PowerPC

中,一般使用U-Boot来引导系统。早期的U-Boot的内核不支持Open Firmware,只有使用include/asm-ppc/u-boot.h 中的静态数据结构struct

bd_t 将板子基本信息传递给内核,其余的由内核处理。这样的接口不够灵活,硬件发生变化就需要重新定制编译烧写UBoot代码和内核,为了适应内核的发展及嵌入式PowerPC平台的千变万化,吸收标准Open Firmware

的优点,U-Boot 引入了扁平设备树FDT((Flatted Device Tree)这样的一个动态接口,使用一个单独的FDT blob(二进制大对象,是一个可以存储二进制文件的容器)存储传递给内核的参数。一些确定信息,例如cache 大小、中断路由等直接由设备树提供,而其他的信息,例如eTSEC 的MAC 地址、频率、PCI

总线数目等由U-Boot

在运行时修改。U-Boot

使用扁平设备树取代了bd_t,而且也不再保证对bd_t 的后向兼容。

大家应该记得在我们的MPC8641HPCN平台,U-boot使用通用寄存器r3,r4,r5,r6,r7传递参数给linux-smp内核映象的入口__start()函数

由于早期的U-Boot的内核不支持Open Firmware,只有使用include/asm-ppc/u-boot.h 中的静态数据结构struct

bd_t 将板子基本信息传递给内核,其余的由内核处理。这样的话r3至r7寄存器传递的数值如下:

r3:bd_info的地址

r4:initrd的起始地址

r5:initrd的结束地址

r6:命令行参数的起始地址

r7:命令行参数的结束地址

而现在的U-boot,比如我用来引导Linux-2.6.38内核的UBoot-2010.09版本,不但支持Open Firmware,而且引入了扁平设备树FDT((Flatted Device Tree)这样的一个动态接口,使用一个单独的FDT blob(二进制大对象,是一个可以存储二进制文件的容器)存储传递给内核的参数,这样的话r3至r7寄存器传递的数值如下:

r3:指向dtb(设备树二进制文件,也成为OF设备树)的物理地址

r4:指向linux-2.6.38-smp内核映象所在的物理地址

r5:空值NULL

r6:空值NULL

r7:空值NULL

正因为如此我们才可以在_start函数的<2>处,根据r5寄存器的值来判断U-boot传递给linux内核的参数类型,在MPC8641D平台,U-boot支持OF结构,所有只使用了r3,r4寄存器来传递参数,r5至r7均为NULL。所有CPU会跳转到标签“1:”处运行。

<3,13,3>

early_init_devtree(__va(dt_ptr))

扁平设备树FDT(Flatted Device Tree)的构成

我们在本文的开始处已经说过:U-boot把Linux-smp映象和目标板的OF设备树加载到内存的不同位置,并把两个位置通过r3,r4寄存器传递给linux-smp的__start()函数,最后把CPU的控制权交给了linux-smp。

例如:针对我们的MPC8641HPCN目标板来说,U-boot把linux-smp映象加载内内存0x0000 0000起始的内存区域,然后把MPC8641d的OF设备树加载的内存0x007 fa000;然后通过r3和r4寄存器,U-boot分别把0x007fa000和0x0000 0000分别传递给linux-2.6.38-smp内核。

这里的OF设备树描述了MPC8641HPCN开发板的信息,我们称之为FDT(Flattened Device Tree)

或者DTB(Device Tree

Blob),FDT是一个单一线性数据结构,主要包含四个部分:

FDT头结构(FDT header)、内存保留块(Memory Reservation BlockStructure Block)以及字符块(Strings Block)。

示意图如下:

a4c26d1e5885305701be709a3d33442f.png

第一个组件:头结构

FDT头结构:描述设备树的一些基本信息,例如设备树大小,结构块偏移地址,字符串块偏移地址等,在Linux-2.6.38中linux-2.6.38/include/linux/of_fdt.h相关代码如下:

struct fdt_header {

__be32

magic;

__be32

totalsize;

__be32

off_dt_struct;

__be32

off_dt_strings;

__be32

off_mem_rsvmap;

__be32

version;

__be32

last_comp_version;

__be32

boot_cpuid_phys;

__be32

dt_strings_size;

__be32

dt_struct_size;

};

解释:

magic:FDT设备树幻数标识,FDT采用大端法存储,其幻数标识为:0xd00dfeed

totalsize:这个FDT设备树的大小,如上图所示包含FDT头结构(FDT header)、内存保留块(Memory Reservation Block)、结构块(Structure Block)、字符块(Strings Block)以及每个块前后的空闲空间(Free Space)

off_dt_struct:保存结构块(Structure Block)从FDT头结构所在的地址处到结构块(Structure Block)自身所在位置的偏移量,以字节位单位

off_dt_strings:保存字符块(Strings Block)从FDT头结构所在的地址处到字符块(Strings Block)自身所在位置的偏移量,以字节位单位

off_mem_rsvmap:保存内存保留块(Memory Reservation Block)从FDT头结构所在的地址处到内存保留块(Memory Reservation Block)自身所在位置的偏移量,以字节位单位

version:保存设备树版本

last_comp_version:该设备树版本向下兼容的最低版本号

boot_cpuid_phys:在Linux SMP中有意义,Linux SMP将CPU分成BSP(Boot Stap CPU)和AP(Application CPU),其中BSP用于Linux SMP的初始化和引导其他的AP,该编号和FDT中的CPU的reg号一致。比如MPC8641HPCN平台上,引导CPU是core 0,而core 0的reg 为<0>,所以boot_cpuid_phys的值为0

dt_strings_size:FDT中的字符块(Strings Block)的长度

dt_struct_size:FDT设备树的大小,以字节位单位

第二个组件:内存保留块(Memory

Reservation Block)

内存保留块给操作系统提供了一条内存保留块的链表,该链表所指向的内存块不作为普通的内存分配使用,该区域用来保存一个关键的数据结构不会被操作系统的所修改。

内存保留块链表的结构体类型如下:

struct

fdt_reserve_entry {

uint64_t address;

uint64_t size;

};

address:指向了内存保留区块的首地址

size:支持内存保留区块的尺寸

需要注意的是在内存区块链表中,需要用一个单元来表示链表的结束标示,只需将该单元的address和size均设置为0即可

第三个组件:结构块(Structure

Block)

扁平设备树结构块是一个线性化的结构体,正如上图所示FDT是一个线性化的结构体,是由一系列的片段构成,这些片段组织成一个线性的树状结构体,是设备树的主体。FDT以节点(node)的形式保存了目标单板上的设备信息。下面介绍这些节点信息在扁平设备树中的存储格式。

在结构块中以宏FDT_BEGIN_NODE和FDT_END_NODE标识一个节点的开始和结束,具体的说由以下几个部分组成:

section1:节点开始标志FDT_BEGIN_NODE

section2:节点路径或者节点的单元名

section3:填充字段(对齐到四字节)

section4:节点属性。每个属性以宏OF_DT_PROP开始,

后面依次为该属性的属性名称长度、属性名称在字符

块中的偏移量、属性值和填充

section5如果存在子节点,则定义子节点

section6:节点结束标志FDT_END_NODE

FDT(Flattened Device Tree)或者DTB(Device Tree Blob)中设备树由device node组成,这些device node又可以由更多的子device node组成!OF使用device tree对一个处理器系统进行描述,我们选取linux-2.6.38/arch/powerpc/boot/dts/

mpc8641_hpcn.dts文件中的部分内容进行描述,内容如下:

/ {

model = "MPC8641HPCN";

compatible = "fsl,mpc8641hpcn";

#address-cells =

<1>;

#size-cells = <1>;

cpus {

#address-cells =

<1>;

#size-cells = <0>;

PowerPC,8641@0 {

device_type = "cpu";

reg = <0>;

d-cache-line-size =

<32>;

i-cache-line-size =

<32>;

d-cache-size = <32768>;

// L1

i-cache-size = <32768>;

// L1

timebase-frequency = <0>;

// From uboot

bus-frequency = <0>;

// From uboot

clock-frequency = <0>;

// From uboot

};

PowerPC,8641@1 {

device_type = "cpu";

reg = <1>;

d-cache-line-size =

<32>;

i-cache-line-size =

<32>;

d-cache-size =

<32768>;

i-cache-size =

<32768>;

timebase-frequency = <0>;

// From uboot

bus-frequency = <0>;

// From uboot

clock-frequency = <0>;

// From uboot

};

};

memory {

device_type = "memory";

reg = <0x00000000

0x40000000>;

// 1G at 0x0

};

以上的device

tree结构分别描述处理器系统中的CPU和Memory,该设备树中包含唯一的一个根节点/,在根节点中包含cpus和memory节点,cpus又包含两个子节点PowerPC,8641@0和PowerPC,8641@1。

待续。。。。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值