本帖最后由 LINUX课程 于 2024-11-25 09:18 编辑
pmon启动流程概述
以龙芯处理器LS2K1000为例进行讲解
一 总体启动流程
- CPU执行start.S中的代码:ls2k1000 CPU开始执行Targets/LS2K/ls2k/start.S中的代码。这段代码是PMON启动的起点,它包含了一些宏定义、程序入口指示和CPU初始化所需的指令。
- 跳转到init_loongarch函数:然后,代码会跳转到zloader.ls2k/init_loongarch.c中的init_loongarch函数中执行。在这个函数中,PMON会将BIOS数据(biosdata)解压到特定的内存地址上。
- 调用realinit_loongarch函数:接下来调用zloader.ls2k/init_loongarch.c中的realinit_loongarch函数,跳转到Targets/LS2K/ls2k/tgt_machdep.c中的init_loongarch函数。在这个阶段,PMON会完成更多的初始化工作,并准备跳转到C语言编写的代码部分。
- 执行C语言代码:一旦PMON跳转到内存中的代码,它就可以使用栈和C语言编写的函数了。接下来的代码会继续进行硬件初始化、环境变量设置、PCI设备初始化等工作,并最终加载和执行操作系统内核。
为了更好的理解启动流程,以下是对pmon.bin.c生成过程的详细解释,以及它如何与biosdata和gzrom.bin相关联:
pmon.bin.c的生成过程
1.编译PMON源码:
- 首先,需要编译ls2k的PMON源码。PMON是一种固件,用于处理处理器的启动和电源管理。
- 在编译过程中,会生成多个目标文件(*.o),这些文件位于Targets/LS2K/compile/ls2k目录下。
2.链接生成elf文件:
- 使用链接脚本(如Targets/LS2K/conf/ld.script)将这些目标文件链接成一个elf格式的文件,即pmon。
- 这个elf文件包含了PMON的所有代码和数据。
3.去除符号表并压缩:
- elf文件通常包含符号表,这些符号表在最终的产品中是不需要的。因此,需要去除符号表,得到pmon.bin文件。
- 接着,使用gzip命令对pmon.bin文件进行压缩,得到pmon.bin.gz文件。
4.转换为C数组:
- 为了将pmon.bin.gz文件嵌入到C代码中,使用bin2c工具将其转换为一个C数组。这个数组就是biosdata,它保存在pmon.bin.c文件中。
- 在这个过程中,pmon.bin.gz文件的内容被读取并转换为一个字符数组的形式,这个数组在C代码中可以被直接访问和使用。
biosdata与gzrom.bin的关联
1.biosdata的作用:
- biosdata是一个在pmon.bin.c文件中定义的数组,它包含了PMON固件的所有代码和数据(经过压缩)。
- 在PMON的启动过程中,这个数组会被解压并加载到内存中,以执行PMON的代码。
2.gzrom.bin的生成:
- gzrom.bin文件是通过将zload.o和start.o等文件链接而成,并去除了符号表得到的。
- 其中,zload.o包含了initmips.c和pmon.bin.c等文件的编译结果,而pmon.bin.c文件又包含了biosdata数组。
- 因此,gzrom.bin文件间接地包含了biosdata数组的内容。
3.启动流程中的使用:
- 在龙芯处理器的启动过程中,gzrom.bin文件会被加载到内存中,并解压执行其中的代码。
- 这个过程中,会跳转到initmips函数中执行,而initmips函数又会使用到biosdata数组中的数据来初始化PMON固件。
二 start.S 启动概述
- .globl _start
- .globl start
- .globl __main
- _start:
- start:
- .globl stack
- stack = start + LOCK_CACHE_SIZE /* Place PMON stack in the end of 2M RAM */
复制代码……一系列初始化 - start_now:
- bnez s0, 1f
- li.w a0, 128
- la ra, init_loongarch #跳转到init_loongarch函数
- jirl zero, ra, 0
复制代码
三 在zloader.ls2k/init_loongarch.c中init_loongarch(…) 概述
- void realinit_loongarch();
- void init_loongarch(unsigned long long msize)
- {
- unsigned long i;
- char * biosdata_flash = (unsigned long long)biosdata & 0xfffffULL | 0x1c000000ULL | (0x900000000f010000 & (0xffULL << 56));
- char * biosdata_mem = (0x900000000f010000 - sizeof(biosdata)) & ~0xffULL;
- early_printf("Copy Bios to memory "); //Copy the biosdata from flash to the memory
- for (i = 0; i < sizeof(biosdata); i += sizeof(unsigned long)) {
- *(volatile unsigned long*)(biosdata_mem + i) = *(volatile unsigned long*)(biosdata_flash + i);
- }
- early_printf("OK, Uncompressing Bios");
- while(1) {
- if(run_unzip(biosdata_mem, 0x900000000f010000) >= 0)
- break;
- }
- memset((void *)0x900000000f27a228, 0, 0x900000000f2ec678 - 0x900000000f27a228); //clear bss
- memset((void *)0x900000000f010000 - 0x1000, 0, 0x1000); //0x900000000f010000-0x1000 for frame(registers),memset for pretty
- early_printf("OK, Booting Bios\r\n");
- realinit_loongarch(msize);
- }
- void realinit_loongarch(unsigned long long msize)
- {
- __asm__ ("li.d $r3,0x900000000f010000-0x4000;\n" \
- " li.d $r12,0x900000000f0af8f0;\n" \ //该地址就是Targets/LS2K/ls2k/tgt_machdep.c中的init_loongarch(...)函数地址
- " move $r4,%0;\n" \
- " jirl $r0,$r12, 0;\n" \
- :
- : "r" (msize)
- : "$r3", "$r12");
- }
复制代码 2.函数 realinit_loongarch(unsigned long long msize)
- 参数: msize - 内存大小,传递给BIOS初始化函数。
- 功能: 使用内联汇编跳转到BIOS的初始化函数。
四 在Targets/LS2K/ls2k/tgt_machdep.c中 init_loongarch(…)概述
- void init_loongarch(unsigned long long tgt_memsz)
- {
- unsigned int hi;
- unsigned short i;
- unlock_scache(LOCK_CACHE_BASE, LOCK_CACHE_SIZE);
- //core1 run wait_for_smp_call function in ram
- asm volatile("st.d %1,%0,0x20;"::"r"(0x800000001fe01100),"r"(&slave_main));
- mem_win_cfg(tgt_memsz);
- #ifdef CONFIG_UART0_SPLIT
- readq(LS2K1000_GENERAL_CFG1) |= 0xe;
- #endif
- /*enable float */
- tgt_fpuenable();
- get_memorysize(tgt_memsz);
- mul_pin_def_cfg();
- /*
- * Probe clock frequencys so delays will work properly.
- */
- ls2k_i2c_init(0, LS2K1000_I2C0_REG_BASE);
- ls2k_i2c_init(0, LS2K1000_I2C1_REG_BASE);
- tgt_cpufreq();
- SBD_DISPLAY("DONE", 0);
- /*
- * Init PMON and debug
- */
- cpuinfotab[0] = &DBGREG;
- dbginit(NULL);
- bcopy(LoongArchException, (char *)GEN_EXC_VEC, LoongArchExceptionEnd - LoongArchException);
- cpu_set_ebase();
- cpu_set_tlb_ebase();
- tgt_printf("set ebase done\n");
- /*
- * Set up exception vectors.
- */
- SBD_DISPLAY("BEV1", 0);
- printf("BEV in SR set to zero.\n");
- #if NNAND
- #ifdef CONFIG_LS2K_NAND
- readq(LS2K1000_GENERAL_CFG0) |= (1 << 9);
- ls2k_nand_init();
- #else
- /*nand pin as gpio*/
- readq(LS2K1000_GENERAL_CFG0) &= ~(1 << 9);
- #endif
- #if NSPINAND_MT29F || NSPINAND_LLD
- ls2k_spi_nand_probe();
- #endif
- #if NM25P80
- ls2k_m25p_probe();
- #endif
- #else
- /*nand pin as gpio*/
- readq(LS2K1000_GENERAL_CFG0) &= ~(1 << 9);
- #endif
- #ifdef DTB
- verify_dtb();
- #endif
- clear_pcie_inter_irq();
- /*
- * Launch!
- */
- main();
- }
复制代码 以下是对该函数主要操作的解释:
- 解锁缓存:通过调用unlock_scache函数,解锁指定基地址和大小的缓存区域。这是为了确保后续的内存访问不会受到缓存的限制。
- 核心间通信:使用内联汇编指令st.d,将slave_main函数的地址写入到一个特定的内存位置(0x800000001fe01100),这可能是为了通知另一个核心(如核心1)执行wait_for_smp_call函数。
- 内存窗口配置:调用mem_win_cfg函数,根据tgt_memsz参数配置内存窗口。
- UART配置:如果定义了CONFIG_UART0_SPLIT,则修改UART0的相关配置。
- 启用浮点运算:通过调用tgt_fpuenable函数,启用浮点运算单元。
- 获取内存大小:调用get_memorysize函数,根据tgt_memsz参数进一步处理或确认内存大小。
- 多引脚默认配置:通过mul_pin_def_cfg函数,设置多个引脚的默认配置。
- 时钟频率探测:初始化I2C接口,并调用tgt_cpufreq函数探测CPU时钟频率,确保延时操作能正确工作。
- 显示初始化状态:通过SBD_DISPLAY宏显示初始化过程中的一些状态信息。
- 调试和PMON初始化:设置调试寄存器信息,初始化调试功能,复制异常向量表到指定位置,设置异常基地址(ebase)。
- 异常向量设置:通过设置SR寄存器中的BEV位为0,使异常向量位于低地址空间。
- NAND和SPI NAND初始化:根据编译时的配置,初始化NAND闪存或SPI NAND闪存。如果未配置使用NAND,则将相关引脚配置为GPIO。
- 设备树验证:如果定义了DTB,则验证设备树。
- 清除PCIe中断:通过clear_pcie_inter_irq函数清除PCIe相关的中断。
- 启动主程序:最后,调用main函数启动主程序。
|
|