0.约定

为了便于理解,根据最新版的《龙芯3A处理器用户手册》约定以下术语:

处理器节点:包含4个GS464核的1个3A处理器称为1个处理器节点

处理器核: 3A中的每个GS464核就是一个处理器核

主处理器核:4个GS464中最先进入内核并负责初始化外设和引导从处理器核的处理器核

从处理器核:被主处理器核引导的处理器核

1.BIOS的引导

操作系统一般都是要由BIOS启动。龙芯的处理器芯片同样要先进入相应的BIOS也即PMON。在BIOS这一层面上的多核系统中,主处理器核的启动和从处理器核的启动有所不同。主处理器核的启动流程,跟单核系统的启动过程基本一样,主要完成处理器核、TLB、MMU及相关外设的启动。而从处理器则根据自身的Id号码,执行各自的轮询循环,直到某个寄存器值已经改变,跳到各自的内核代码人口。

系统重启后,所有的的处理器核都会进入pmon中位于boot.S中的reset_exception执行,代码如下:

reset_exception:

22     .set mips64

23     mfc0   t0, $15, 1  #mfc0, t0, CP0_PRID, 1

24     andi   t0, 0x3ff   #clear bit [31]~bit[12]

25     .set mips3

26 1:

27     bnez   t0, slave_main  #slave cpu

28     nop

代码中23行的数字1表示是mips64 中的EBase Rigister selector 1寄存器,该寄存器的格式如下:

上面CPUNum字段的值在处理器出厂时由硬件固定。在单核系统中,CPUNum始终为0;在多核系统中,该字段用来区别不同的处理器核。代码中的23~24行读出当前处理器核(执行到这段代码的处理器核)的处理器号到t0中,如果t0为0则表示当前的是主处理器核,接着往下执行;否则表示当前是从处理器核,则调用slave_main执行。

1.1主处理器核的引导

先看主处理器核的启动过程,它会接着执行:

32         li     t0, 0xbfe00100   #it meansGODSON_CONFREG_ADDR

33         lb     t2, 0x02(t0)

34         srl    t2, t2, 5

35         li     t3, 0x3

36         and    t2, t2, t3

37         li     t1, 0x0483    #'b (0) (0_0100)_(1) (000_00) (11)   liocfg

38         addiu  t3, $0, 0

39 1:

40         addiu  t3, t3, 2     # 00->1  01->3 10->5  11->7   +1 (60ns+1cyc)

41         bnez   t2, 1b

42         addiu  t2, t2, -1

43

44         sll    t3, t3, 2

45         or     t1, t3, t1

46 1:      sw     t1, 0x8(t0)     # speed up localio

47 */

48         beq    $0, $0, cp0_main

49         addu   k0, $0, $0

它首先初始化cpi0的配置寄存器、时钟频率、localio寄存器,然后跳转到cp0_main,去执行主处理器核的初始化工作,包括设置中断、初始化DDR控制寄存器、LPC、SCache、TLB等,完成这些准备工作之后,执行如下的代码进入内核。

270    ## v0 -- kernel entry

271     dli    v0, KERNEL_ENTRY   # define inlinux.h 0xffffffff80314000

272     subu   sp, 32

273     sd $0, 24(sp)

274     sd $0, 16(sp)

275     sd $0, 8(sp)

276     sd $0, 0(sp)

277     move   a0, zero

278     move   v1, sp

279     addiu  a1, v1, 24

280     addiu  a2, v1, 16

281     addiu  a3, v1, 8

282     jr v0

在上面的这段代码中,v0指向代码入口,它的值是KERNEL_ENTRY,也就是编译支持多核的linux 内核的入口地址,通过反汇编vmlinux.32可以看到:

540137 ffffffff80314000 <kernel_entry>:

540138 _sinittext():

540139 ffffffff80314000:   400c6000   mfc0    $t4,$12

540140 ffffffff80314004:   3c015000   lui $at,0x5000

540141 ffffffff80314008:   3421009f   ori $at,$at,0x9f

540142 ffffffff8031400c:   01816025   or  $t4,$t4,$at

540143 ffffffff80314010:   398c001f   xori    $t4,$t4,0x1f

..................................

1.2从处理器核的引导

完成上面这些步骤后,主处理器核在pmon中的工作完成了,而从处理器核则从slave_main的代码开始,代码如下:

296     mfc0   t0, CP0_CONFIG

297     ori    t0, t0, 7 # set bit[0~2] to be '1'

298     xori   t0, t0, 4 # set bit[2] to be '0' 011 K0=11b means cached kseg0,

299     mtc0   t0, CP0_CONFIG

300

301     la t0, next

302     dli t1, 0xfffff

303     and    t0, t1      # set bit[64~32] to be'0'

304     dli t1, 0xffffffff9fc00000 #set virtualbit address of 'next',why plus 0x9fc0?

305     or t0, t1

306

307     jr t0

308     nop

首先还是从初始化从处理核的配置寄存器,设置处理器缓存一致性协议和KSEG0段访问的缓存行为,接着取得标号为”next”的代码的虚拟地址,然后跳到该地址去。标号为next处的代码首先设置KSEG0段可以缓存,然后初始化MMU和TLB相关的寄存器,完了之后执行如下的代码:

366     mfc0   t2, $15, 1  # mfc0, t0, CP0_PRID,1

367     andi   t2, 0x3ff

368     .set mips3

375     dli    t0, 0x900000003ff01000 # IPI_Status Base

376     andi   t3, t2, 0x3  #local cpuid

377     sll    t3, 8

378     or     t0, t0, t3

379

380     andi   t4, t2, 0xc  #node id

381     dsll    t4, 42

382     or     t0, t0, t4

执行366/367/375行可以得到当前从处理器核的id号,该id号码的范围是0~3(对单个节点的3A处理器而言),375~378行实现把当前的id左移8位然后加上 0x900000003ff01000的值,这样刚好得到当前从处理器核对应的IPI_Status的地址,该地址保存在t0中。接着执行:

383waitforinit:

384     li     a0, 0x1000

385idle1000:

386     addiu  a0, -1

387     bnez   a0, idle1000

388     nop

389

390     lw     v0, FN_OFF(t0) #FN_OFF = 0x020,from mailbox

391     beqz   v0, waitforinit   #v0 is zero orpointer to a function

392     nop

383~388行执行一段循环等待,然后检查自己对应的邮箱里的值是否为0,如果是则跳到waitforint继续等待,否则接着往下这行。硬件重启后邮箱里面的值会自动为0,boot.S代码里面并没有哪个去设置它,那它什么时候变为非0的呢?前面提到主处理器核完成pmon里面的任务之后进入内核,它会在完成相关的初始化并及从处理器核的启动准备后,再回头给各个从处理器核的Mail_Box寄存器发送一个函数的地址,告诉它们不用等待而可以跳到这个函数去执行。具体的过程在内核的启动过程中会详细描述。从处理器跳到那个函数入口的过程如下:

394     dli     t1, 0xffffffff00000000

395     or      v0, t1

396

397     dli     t1, 0x9800000000000000

398     lw     sp, SP_OFF(t0)

399     or     sp, t1

400     lw     gp, GP_OFF(t0)

401     or     gp, t1

402     lw     a1, A1_OFF(t0)

403

404     jalr   v0  #byebye

405     nop

...............

首先394~395把v0里的32位函数地址扩展到64位地址的扩展,然后接着设置堆栈sp和gp的指针(位什么要这样设置呢?),完成这些操作之后执行跳转指令jalr v0跳到那个函数。这样从处理器核就从bios/pmon中进入了kernel。

2.Kernel的启动

多核系统的Kernel启动过程,同样可以分为主处理器核的启动和从处理器核的启动这两个阶段,因此可以按照先后顺序分别描述。

2.1主处理核的启动

主处理器核从kernel_entry开始执行,完成cpu自身的初始化及MMU/TLB和外设的初始化,之后的初始化过程中会调用__smp_init这一函数。在单核的系统引导过称中并没有这一过程,因此这是单核和多核引导的主要区别。该函数的内容如下:

355 /*Called by boot processor to activate the rest. */

356 staticvoid __init smp_init(void)

357 {

......................

358     for_each_present_cpu(i) {

363         if (num_online_cpus() >= max_cpus)

364             break;

365         if (!cpu_online(i))    //TODO tmp for init

366             cpu_up(i);

367     }

368

369     /* Any cleanup work */

370     printk(KERN_INFO "Brought up %ldCPUs\n", (long)num_online_cpus());

371     smp_cpus_done(max_cpus);

372         printk(KERN_INFO "smp_init:all_cpus_done \n");

.................

上面主要的工作在for_each_present_cpu(i)循环中进行,366~367行检查当前要初始化的处理器核Id号码是否越界,没有的话继续判断ID号为i的处理器核是否已经初始化,如果没有则调用cpu_up(i)把它初始化。cpu_up代码的主要部分如下:

200     ret =blocking_notifier_call_chain(&cpu_chain, CPU_UP_PREPARE, hcpu);

201     if (ret == NOTIFY_BAD) {

202         printk("%s: attempt to bring upCPU %u failed\n",

203                 __FUNCTION__, cpu);

204         ret = -EINVAL;

205        goto out_notify;

206     }

207

208     /* Arch-specific enabling code. */

209     mutex_lock(&cpu_bitmask_lock);

210     ret = __cpu_up(cpu);

211     mutex_unlock(&cpu_bitmask_lock);

212     if (ret != 0)

213         goto out_notify;

214     BUG_ON(!cpu_online(cpu));

215

216     /* Now call notifier in preparation. */

217     prom_printf("CPU_ONLINE(%d)\n",cpu);

218    blocking_notifier_call_chain(&cpu_chain, CPU_ONLINE, hcpu);

...............

首先会调用blocking_notifier_call_chain(&cpu_chain,CPU_UP_PREPARE, hcpu),CPU_UP_PREPAR最终会传递给kernel/softirq.c文件中cpu_callback函数的参数,它为从CPU生成ksoftirqd等线程。而218行的blocking_notifier_call_chain(&cpu_chain,CPU_ONLINE, hcpu)的作用是通过CPU_ONLINE 标志,唤醒从处理器核上的ksoftirqd线程。为了避免外设中断的影响,在启动从处理器核之前需要对临界代码__cpu_up(cpu)上锁,启动完成之后需要解锁。关键代码如下所示:

276 int__devinit __cpu_up(unsigned int cpu)

277 {

278     struct task_struct *idle;

279

280     /*

281      * Processor goes to start_secondary(),sets online flag

282     * The following code is purely to make sure

283      * Linux can schedule processes on thisslave.

284      */

285     idle = fork_idle(cpu);

286     if (IS_ERR(idle))

287         panic(KERN_ERR "Fork failed forCPU %d", cpu);

288

289     prom_boot_secondary(cpu, idle);

...............

第285行首先调用fork_idle为ID为参数cpu的处理器核创建一个空闲的idle的任务结构,以备从处理器启动后执行。该函数首先调用 copy_process()在主处理器核0号进程(也就是idle进程)的基础上复制一个进程,只是还没有让它运行起来。接着调用 init_idle()函数把新创建的进程跟要被初始化的从处理器核关联起来。fork_idle()执行完成之后,接着调用prom_boot_secondary():

294 voidprom_boot_secondary(int cpu, struct task_struct *idle)

295 {

296     int retval;

297

298     printk("\n BOOT CPU#%d...\n",cpu);

299     retval =godson3_cpu_start(cpu_logical_map(cpu), &smp_bootstrap,

300                    __KSTK_TOS(idle),

301                    (unsignedlong)task_thread_info(idle), 0);

302     if (retval != 0)

303      printk("godson3_start_cpu(%i)returned err%i \n", cpu,retval);

304 }

可以看到:主要是 godson3_cpu_start()完成从处理器核的引导工作,这个函数的定义如下:

234 intgodson3_cpu_start(int cpu, void(*fn)(void), long sp, long gp, long a1)

235 {

236     int res;

237     unsigned long long startargs[4];

238

239     startargs[0] = (long)fn;

240     startargs[1] = sp;

241     startargs[2] = gp;

242     startargs[3] = a1;

.................

251

252     godson3_raw_writeq(startargs[3],mailbox_buf[cpu]+0x18);

253     godson3_raw_writeq(startargs[2],mailbox_buf[cpu]+0x10);

254     godson3_raw_writeq(startargs[1],mailbox_buf[cpu]+0x8);

255     godson3_raw_writeq(startargs[0],mailbox_buf[cpu]+0x0);

256

257     res = 0;

258

259     return res;

260 }

根据函数的参数不难想象该函数可能是来设置从处理器核进入到内核后的地址的。事实正是如此,它通过调用godson3_raw_writeq来把从处理器核启动后执行对mailbox_buf的写的。

上面用到的 godson3_raw_writeq函数定义如下:

26 voidgodson3_raw_writeq(unsigned int action, void * addr)

27 {                                                   // the value is action

28     *((unsigned int *)addr) = action;

29 };

而mailbox_buf是一组处理器核的寄存器地址的集合,如下:

113 static void *mailbox_buf[] = {                                                                                       114     (void*)(smp_core_group0_base + smp_core0_offset + BUF),                                                                      115     (void *)(smp_core_group0_base +smp_core1_offset + BUF),                                                                    116     (void*)(smp_core_group0_base + smp_core2_offset + BUF),                                                              117     (void *)(smp_core_group0_base +smp_core3_offset + BUF),

.........

}

其中相关定义在头文件arch/mips/godson/godson3/smp.h中:

#defineBUF 0x20

3 #define smp_core_group0_base   0x900000003ff01000

9 #define smp_core0_offset  0x0

10#define  smp_core1_offset  0x100

11#define  smp_core2_offset  0x200

12#define  smp_core3_offset  0x300

...............

所以 godson3_raw_writeq(startargs[0],mailbox_buf[cpu]+0x0)实际上就可以替代为:

godson3_raw_writeq(fn,0x900000003ff01020+cpu*0x100),而 0x900000003ff01020+cpu*0x100正好对应ID为cpu的处理器核的CoreN_MailBox0寄存器的物理地址,因此该函数也就是往ID为cpu的处理器核的四个Mail_Box寄存器里面写入gp、sp、al和一个函数的地址fn的。一旦fn写入到从处理器核的CoreN_MailBox0,还在pmon中循环查询的从处理器核就可以跳出循环,进入该函数执行。

2.2从处理器核的启动

从处理器核就从pmon中直接进入了内核,会都跳到smp_bootstrap执行。smp_bootstrap并非一个C函数,而是一个在arch/mips/kernel/head.S里定义的一个具有函数类型的入口标志,如下:

208NESTED(smp_bootstrap, 16, sp)

209 #ifdefCONFIG_MIPS_MT_SMTC

210     /*

211      * Read-modify-writes of Status must beatomic, and this

212      * is one case where CLI is invokedwithout EXL being

213      * necessarily set. The CLI andsetup_c0_status will

214      * in fact be redundant for all but thefirst TC of

215      * each VPE being booted.

216      */

217     DMT 10 # dmt t2 /* t0, t1 are used by CLI and setup_c0_status() */

218     jal mips_ihb

219 #endif/* CONFIG_MIPS_MT_SMTC */

220     setup_c0_status_sec

221     smp_slave_setup

222 #ifdefCONFIG_MIPS_MT_SMTC

223     andi   t2, t2, VPECONTROL_TE

224     beqz   t2, 2f

225     EMT    # emt

226 2:

227 #endif/* CONFIG_MIPS_MT_SMTC */

228     j  start_secondary

...............

在这段代码里面,主要是调用setup_c0_status_sec去初始化状态寄存器,然后跳转到start_secondary去执行。

88 asmlinkagevoid start_secondary(void)

89 {

90     unsigned int cpu;

91

92     printk("Slave CPU#%d: I'mcoming!!!!!\n", smp_processor_id());

93

94 #ifdefCONFIG_MIPS_MT_SMTC

95     /* Only do cpu_probe for first TC of CPU*/

96    if ((read_c0_tcbind() & TCBIND_CURTC) == 0)

97 #endif/* CONFIG_MIPS_MT_SMTC */

98     cpu_probe(); //检测CPU类型、处理器id、fpu类型

99     cpu_report(); //打印cpuversion

100     per_cpu_trap_init();//初始化MMU和TLB

101     prom_init_secondary();//实际没有做任何事情

102

103     /*

104      * XXX parity protection should be foldedin here when it's converted

105      * to an option instead of something basedon .cputype

106      */

108     calibrate_delay();//测试并设置时钟滴答

109     preempt_disable();//禁止可抢占

110     cpu = smp_processor_id();

111     cpu_data[cpu].udelay_val =loops_per_jiffy;

112

113     prom_smp_finish();//调用godson3_smp_finish(void)开中断

114

115     cpu_set(cpu, cpu_callin_map);//在位图中设置id为cpu的处理器核启动标志

116

117     cpu_idle();// 调用schedule();进入之前创建的idle进程

118 }

该函数中间调用的一些函数的功能可以参考注释。执行到cpu_idle()后从处理器核也完全启动了。至此,整个多处理器核的引导完成。

3.参考文献

1.MIPS TECHNOLOGIES Corp.MIPS64@Architecture For ProgrammersVoloum III: The MIPS64

@Privilged Resource Architecture.2005.1:112

2.北京中科龙芯技术服务中心有限公司.龙芯3A处理器用户手册.2009.05

3.毛德操,胡希明.Linux内核源代码分析(下).浙江大学出版社:607~642.

4.Dominic Sweetman.MIPS 处理器设计透视.赵俊良,张福新译.北京航空航天大学出版社.2005.6

本文转自存储之厨51CTO博客,原文链接: http://blog.51cto.com/xiamachao/1680935,如需转载请自行联系原作者

龙芯多核处理器启动概要相关推荐

  1. 龙芯服务器如何设置共享文件夹,龙芯多核处理器及虚拟机架构图解

    龙芯处理器架构秉承最佳的性能功耗比,以最小的功耗提供最高的性能一直是设计的目标.依此准则设计了一系列龙芯处理器,从32位计算技术到64位计算技术,从单发射到四发射,从简单的访存管理到多级CACHE管理 ...

  2. 龙芯3A4000处理器解读①

    龙芯3A4000处理器解读 关于3A4000的处理器结构简单做一下自己的相关分析,希望可能通过此次深入分析,更加了解处理器的结构. 文章目录 龙芯3A4000处理器解读 简介 3号(3A)硬件结构 简 ...

  3. 龙芯3A4000+龙芯3B4000处理器数据手册

    龙芯3A4000+龙芯3B4000是龙芯3A3000/3B3000 四核处理器的微结构升级版本,采用相同的28nm FD-SOI 工艺,封装引脚全新定义.龙芯3A4000/3B4000是一个配置为单节 ...

  4. 龙芯3A4000处理器解读 ②

    龙芯3A4000处理器解读 2 第一章简述了3A4000的芯片结构,并对照结构图举例了访问7A的通道,那么本章节主要梳理CPU如何获取到一条分配给到7A的地址如何通过窗口命中正确访问到有效数据. 文章 ...

  5. 龙芯2K1000运行linux,对龙芯2K1000处理器的支持补丁已经合并进Linux 5.

    对龙芯Loongson 2K1000的支持工作终于在即将到来的Linux 5.13内核中被合并.虽然基于MIPS的Loongson处理器以对开源社区极为友好而闻名,并一度被Stallman提及,但在过 ...

  6. ORI-621龙芯3A处理器CPCI刀片计算机

    ORI-621龙芯3A处理器CPCI刀片计算机 一.产品简介 ORI -621是一款基于龙芯3A国产CPU处理器的特种CPCI刀片计算机.该产品成功地实现了服务器NUMA架构在国产特种计算机中的应用, ...

  7. 龙芯PMON(2K1000)启动流程(三、C语言部分③)

    3.6 NAND初始化 dbginit(NULL)执行完成后,说明重要的核心设备初始化过程已经告一段落.接下来设置把BEV清零. BEV1 BEV0 BEV in SR set to zero. (内 ...

  8. 龙芯3A4000处理器实测:28nm工艺不变,性能可提升100%以上

    龙芯是中科院下属的计算机所研发的自主产权国产处理器,现在已经由中科龙芯公司商业化,再过几天他们又要发布新一代龙芯处理器--龙芯3A4000系列了,这是在现有的龙芯3A3000系列上的改进版. 根据之前 ...

  9. 龙芯2K1000LA处理器流片成功,龙芯业务全面转向LoongArch架构

    2022年4月底,龙芯2K1000处理器完成了改版芯片(代号龙芯2K1000LA)的功能和性能测试,正在开展用户试用.龙芯2K1000LA在实现与原有版本2K1000引脚和接口兼容的基础上,处理器核更 ...

最新文章

  1. 激活函数之logistic sigmoid函数介绍及C++实现
  2. 邮件内容被分析抛售,你的企业邮箱安全吗?
  3. Uncaught TypeError: Cannot read property 'style' of null
  4. 【IT笔试面试题整理】寻找二叉树两节点的最近的公共祖先
  5. 【三次握手、四次挥手流程】及【长短链接区别】
  6. LETTers比赛第三场 --1002 Ignatius and the Princess III解题报告
  7. 大话存储系列21——存储系统内部IO 上
  8. EasyUI的DataGrid 打印导出
  9. mysql client 升级_解决consider upgrading MySQL client问题
  10. 一种雷达和电子海图图像叠加方法
  11. 决策树_Python3实现代码及注释
  12. 计算机软件后缀名,如何显示文件后缀名
  13. 【Java入门练习100例】03.短路与和逻辑与的区别——老实人和机灵鬼
  14. [LCT刷题][连通性维护] P3950 部落冲突
  15. 《人力资源视角下的企业数字化转型》高峰圆桌论坛圆满闭幕
  16. tx2串口与can通信控制c620电机(使用usb转can模块)
  17. fastadmin列表自动刷新功能
  18. android优化启动时间
  19. 【NoteBook】刘润:新零售:低价高效的数据赋能之路
  20. Java进阶篇设计模式之十二 ---- 备忘录模式和状态模式

热门文章

  1. C语言for语句简单打印心形。
  2. 含有DBCO和马来酰亚胺基团Mal-PEG2-DBCO,2698339-31-8,DBCO-PEG2-Maleimide
  3. 小米面试总结(附答案)
  4. R语言|回归分析(一) ———R语言数据分析系列(一)
  5. 提车自检手册(3系,其他车辆类似)
  6. 苹果手机还原网络设置会怎样_iPhone手机信号不好?一键这样设置网速提升3倍不止,果粉:真强...
  7. 好脾气坏脾气,职场上谁走的更远?
  8. ios Xib的几种用法[转]
  9. 网易视频云助力网易新闻直播,凸显内容价值
  10. AD18在拉线时,如何保证线与线的间距?