uboot do_bootm函数详解

先来引用一下这篇介绍“ARM Linux内核启动要求”的文章ARM Linux Kernel Boot Requirements,是ARM Linux内核的维护者Russell King写的。
引用:
    * CPU register settings
          o r0 = 0.
          o r1 = machine type number.
          o r2 = physical address of tagged list in system RAM. 
    * CPU mode
          o All forms of interrupts must be disabled (IRQs and FIQs.)
          o The CPU must be in SVC mode. (A special exception exists for Angel.) 
    * Caches, MMUs
          o The MMU must be off.
          o Instruction cache may be on or off.
          o Data cache must be off and must not contain any stale data. 
    * Devices
          o DMA to/from devices should be quiesced. 
    * The boot loader is expected to call the kernel image by jumping directly to the first instruction of the kernel image.



大致就是以上条件了,请特别关注一下第一条,这个基本上就是U-Boot的go命令和bootm命令之间的本质区别所在了。先来看看bootm命令的实现,在common/cmd_bootm.c的第119行开始有:
#ifdef CONFIG_PPC

static boot_os_Fcn do_bootm_linux;

#else

extern boot_os_Fcn do_bootm_linux;

#endif

这里的预编译宏说明了,非 PPC体系结构的CPU的do_bootm_linux()函数都不是在这个文件内实现的(extern)。可想而知,这个函数的实现应该是和体系结构相关的,具体到arm体系结构的实现就是在lib_arm/armlinux.c这个文件当中。可以看到从lib_arm/armlinux.c中的第77 行开始就是do_bootm_linux()函数的实现。
 
其中第85行声明了这样一个函数指针theKernel:

void (*theKernel)(int zero, int arch, uint params);

看看它的名字和参数的命名我们也可以猜到这个其实就是内核的入口函数的指针了。几个参数的命名也说明了上文提到的ARM Linux内核启动要求的第一条,因为根据ACPS(ARM/Thumb Procedure Call Standard)的规定,这三个参数就是依次使用r0,r1和r2来传递的。
 
接下来第93行就是给这个函数指针赋值:

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

可以看到theKernel被赋值为hdr->ih_ep,这个hdr是指使用tools/mkimage工具程序制作uImage时加在linux.bin.gz前面的一个头部,而ih_ep结构体成员保存的就是使用mkimage时指定的-e参数的值,即内核的入口点(Entry Point)。知道了hdr->ih_ep的意义之后,给theKernel赋这个值也就是理所当然的了。
 
最后是对内核入口函数的调用,发生在第270行:

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

调用的时候对参数进行赋值,r0=0,r1=bd->bi_arch_number,r2=bd->bi_boot_params,一个都不少。至此U-Boot的使命完成,开始进入ARM Linux的美丽新世界。
 
====================================================================
 
要知道哪个地址是启动内核,哪个地址启动文件系统,要分析common/cmd_bootm.c中的函数do_bootm,因为引导kernel就是bootm这条命令的工作,do_bootm是命令bootm的执行函数。

现在我们来分析一下common/cmd_bootm.c中的函数do_bootm,这是bootm命令的处理函数。
……



image_header_t header;



ulong load_addr = CFG_LOAD_ADDR; /* Default Load Address */



int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

ulong iflag;

ulong addr;

ulong data, len, checksum;

ulong *len_ptr;

uint unc_len = 0x400000;

int i, verify;

char *name, *s;

int (*appl)(int, char *[]);

image_header_t *hdr = &header;


读取uboot的环境变量verify,如果环境变量verify等于’n’,则局部变量verify赋值成为0;如果环境变量verify为空(即没有定义环境变量verify)或者环境变量verify不等于’n’,则局部变量verify赋值成为1。
s = getenv ("verify");

verify = (s && (*s == 'n')) ? 0 : 1;


如果参数个数小于2(即只是输入了bootm),使用缺省加载地址CFG_LOAD_ADDR;否则使用第二个参数作为加载地址。
if (argc < 2) {

addr = load_addr;

} else {

addr = simple_strtoul(argv[1], NULL, 16);

}



SHOW_BOOT_PROGRESS (1);

printf ("## Booting image at %08lx ...\n", addr);


将mkimage添加到映象文件头部的64字节提取到image_header_t 结构变量header中。
/* Copy header so we can blank CRC field for re-calculation */
定义了CONFIG_HAS_DATAFLASH,表示系统中存在ATMEL的数据Flash。
#ifdef CONFIG_HAS_DATAFLASH

if (addr_dataflash(addr)){

read_dataflash(addr, sizeof(image_header_t), (char *)&header);

} else

#endif

memmove (&header, (char *)addr, sizeof(image_header_t));


判断image header的magic是否匹配,如果不匹配,说明下载过程中发生了错误。
if (ntohl(hdr->ih_magic) != IH_MAGIC) {

#ifdef __I386__ /* correct image format not implemented yet - fake it */

if (fake_header(hdr, (void*)addr, -1) != NULL) {

/* to compensate for the addition below */

addr -= sizeof(image_header_t);

/* turnof verify,

* fake_header() does not fake the data crc

*/

verify = 0;

} else

#endif /* __I386__ */

{

puts ("Bad Magic Number\n");

SHOW_BOOT_PROGRESS (-1);

return 1;

}

}

SHOW_BOOT_PROGRESS (2);


校验image header的CRC以及image data的CRC,如果校验不匹配,说明下载过程中发生了错误。
data = (ulong)&header;

len = sizeof(image_header_t);



checksum = ntohl(hdr->ih_hcrc);

hdr->ih_hcrc = 0;



if (crc32 (0, (char *)data, len) != checksum) {

puts ("Bad Header Checksum\n");

SHOW_BOOT_PROGRESS (-2);

return 1;

}

SHOW_BOOT_PROGRESS (3);



/* for multi-file images we need the data part, too */

print_image_hdr ((image_header_t *)addr);



data = addr + sizeof(image_header_t);

len = ntohl(hdr->ih_size);



#ifdef CONFIG_HAS_DATAFLASH

if (addr_dataflash(addr)){

read_dataflash(data, len, (char *)CFG_LOAD_ADDR);

data = CFG_LOAD_ADDR;

}

#endif



if (verify) {

puts (" Verifying Checksum ... ");

if (crc32 (0, (char *)data, len) != ntohl(hdr->ih_dcrc)) {

printf ("Bad Data CRC\n");

SHOW_BOOT_PROGRESS (-3);

return 1;

}

puts ("OK\n");

}

SHOW_BOOT_PROGRESS (4);


判断体系结构。
len_ptr = (ulong *)data;



#if defined(__PPC__)

if (hdr->ih_arch != IH_CPU_PPC)

#elif defined(__ARM__)

if (hdr->ih_arch != IH_CPU_ARM)

#elif defined(__I386__)

if (hdr->ih_arch != IH_CPU_I386)

#elif defined(__mips__)

if (hdr->ih_arch != IH_CPU_MIPS)

#elif defined(__nios__)

if (hdr->ih_arch != IH_CPU_NIOS)

#elif defined(__M68K__)

if (hdr->ih_arch != IH_CPU_M68K)

#elif defined(__microblaze__)

if (hdr->ih_arch != IH_CPU_MICROBLAZE)

#else

# error Unknown CPU type

#endif

{

printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);

SHOW_BOOT_PROGRESS (-4);

return 1;

}

SHOW_BOOT_PROGRESS (5);


判断image类型。
switch (hdr->ih_type) {

case IH_TYPE_STANDALONE:

name = "Standalone Application";

/* A second argument overwrites the load address */

if (argc > 2) {

hdr->ih_load = simple_strtoul(argv[2], NULL, 16);

}

break;

case IH_TYPE_KERNEL:

name = "Kernel Image";

break;

case IH_TYPE_MULTI:

name = "Multi-File Image";

len = ntohl(len_ptr[0]);

/* OS kernel is always the first image */

data += 8; /* kernel_len + terminator */

for (i=1; len_ptr; ++i)

data += 4;

break;

default: printf ("Wrong Image Type for %s command\n", cmdtp->name);

SHOW_BOOT_PROGRESS (-5);

return 1;

}

SHOW_BOOT_PROGRESS (6);



/*

* We have reached the point of no return: we are going to

* overwrite all exception vector code, so we cannot easily

* recover from any failures any more...

*/



iflag = disable_interrupts();



#ifdef CONFIG_AMIGAONEG3SE

/*

* We've possible left the caches enabled during

* bios emulation, so turn them off again

*/

icache_disable();

invalidate_l1_instruction_cache();

flush_data_cache();

dcache_disable();

#endif


判断image压缩类型
switch (hdr->ih_comp) {

case IH_COMP_NONE: 没有压缩

if(ntohl(hdr->ih_load) == addr) { 如果image header中指示的加载地址和bootm命令中参数2指定的地址相同,则表示不需要copy,可以就地执行。

printf (" XIP %s ... ", name);

} else {

#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)

size_t l = len;

void *to = (void *)ntohl(hdr->ih_load);

void *from = (void *)data;



printf (" Loading %s ... ", name);



while (l > 0) {

size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;

WATCHDOG_RESET();

memmove (to, from, tail);

to += tail;

from += tail;

l -= tail;

}

#else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */

如果image header中指示的加载地址和bootm命令中参数2指定的地址不相同,则表示要从image header中指示的加载地址处把image data copy到bootm命令中参数2指定的地址处,然后再执行。
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);

#endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */

}

break;

case IH_COMP_GZIP:

printf (" Uncompressing %s ... ", name);

if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,

(uchar *)data, (int *)&len) != 0) {

puts ("GUNZIP ERROR - must RESET board to recover\n");

SHOW_BOOT_PROGRESS (-6);

do_reset (cmdtp, flag, argc, argv);

}

break;

#ifdef CONFIG_BZIP2

case IH_COMP_BZIP2:

printf (" Uncompressing %s ... ", name);

/*

* If we've got less than 4 MB of malloc() space,

* use slower decompression algorithm which requires

* at most 2300 KB of memory.

*/

i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),

&unc_len, (char *)data, len,

CFG_MALLOC_LEN < (4096 * 1024), 0);

if (i != BZ_OK) {

printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);

SHOW_BOOT_PROGRESS (-6);

udelay(100000);

do_reset (cmdtp, flag, argc, argv);

}

break;

#endif /* CONFIG_BZIP2 */

default:

if (iflag)

enable_interrupts();

printf ("Unimplemented compression type %d\n", hdr->ih_comp);

SHOW_BOOT_PROGRESS (-7);

return 1;

}

puts ("OK\n");

SHOW_BOOT_PROGRESS (7);


根据image 执行type来决定如何引导。

switch (hdr->ih_type) {

case IH_TYPE_STANDALONE:

if (iflag)

enable_interrupts();



/* load (and uncompress), but don't start if "autostart"

* is set to "no"

*/

if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {

char buf[32];

sprintf(buf, "%lX", len);

setenv("filesize", buf);

return 0;

}

appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);

(*appl)(argc-1, &argv[1]);

return 0;

case IH_TYPE_KERNEL:

case IH_TYPE_MULTI:

/* handled below */

break; 下面将有代码专门处理这两种image类型

default:

if (iflag)

enable_interrupts();

printf ("Can't boot image type %d\n", hdr->ih_type);

SHOW_BOOT_PROGRESS (-8);

return 1;

}

SHOW_BOOT_PROGRESS (8);


根据image 的OS type来决定如何引导
switch (hdr->ih_os) {

default: /* handled by (original) Linux case */

case IH_OS_LINUX:

#ifdef CONFIG_SILENT_CONSOLE

fixup_silent_linux();

#endif

do_bootm_linux (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;

case IH_OS_NETBSD:

do_bootm_netbsd (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;



#ifdef CONFIG_LYNXKDI

case IH_OS_LYNXOS:

do_bootm_lynxkdi (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;

#endif



case IH_OS_RTEMS:

do_bootm_rtems (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;



#if (CONFIG_COMMANDS & CFG_CMD_ELF)

case IH_OS_VXWORKS:

do_bootm_vxworks (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;

case IH_OS_QNX:

do_bootm_qnxelf (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;

#endif /* CFG_CMD_ELF */

#ifdef CONFIG_ARTOS

case IH_OS_ARTOS:

do_bootm_artos (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;

#endif

}



SHOW_BOOT_PROGRESS (-9);

#ifdef DEBUG

puts ("\n## Control returned to monitor - resetting...\n");

do_reset (cmdtp, flag, argc, argv);

#endif

return 1;

}

bootm命令是用来引导经过u-boot的工具mkimage打包后的kernel image的,什么叫做经过u-boot的工具mkimage打包后的kernel image,这个就要看mkimage的代码,看看它做了些什么,虽然我很希望大家不要偷懒,认真地去看看,但是我知道还是有很多人懒得去做这件,那么我就j将分析mkimage代码后得到的总结告诉大家,mkimage做了些什么,怎么用这个工具。

mkimage的用法
uboot源代码的tools/目录下有mkimage工具,这个工具可以用来制作不压缩或者压缩的多种可启动映象文件。

mkimage在制作映象文件的时候,是在原来的可执行映象文件的前面加上一个0x40字节的头,记录参数所指定的信息,这样uboot才能识别这个映象是针对哪个CPU体系结构的,哪个OS的,哪种类型,加载内存中的哪个位置, 入口点在内存的那个位置以及映象名是什么
引用:
root@Glym:/tftpboot# ./mkimage
Usage: ./mkimage -l image
-l ==> list image header information
./mkimage -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
-A ==> set architecture to 'arch'
-O ==> set operating system to 'os'
-T ==> set image type to 'type'
-C ==> set compression type 'comp'
-a ==> set load address to 'addr' (hex)
-e ==> set entry point to 'ep' (hex)
-n ==> set image name to 'name'
-d ==> use image data from 'datafile'
-x ==> set XIP (execute in place)


参数说明:

-A 指定CPU的体系结构:

取值 表示的体系结构
alpha Alpha
arm A RM
x86 Intel x86
ia64 IA64
mips MIPS
mips64 MIPS 64 Bit
ppc PowerPC
s390 IBM S390
sh SuperH
sparc SPARC
sparc64 SPARC 64 Bit
m68k MC68000

-O 指定操作系统类型,可以取以下值:
openbsd、netbsd、freebsd、4_4bsd、linux、svr4、esix、solaris、irix、sco、dell、ncr、lynxos、vxworks、psos、qnx、u-boot、rtems、artos

-T 指定映象类型,可以取以下值:
standalone、kernel、ramdisk、multi、firmware、script、filesystem

-C 指定映象压缩方式,可以取以下值:
none 不压缩
gzip 用gzip的压缩方式
bzip2 用bzip2的压缩方式

-a 指定映象在内存中的加载地址,映象下载到内存中时,要按照用mkimage制作映象时,这个参数所指定的地址值来下载

-e 指定映象运行的入口点地址,这个地址就是-a参数指定的值加上0x40(因为前面有个mkimage添加的0x40个字节的头)

-n 指定映象名

-d 指定制作映象的源文件
<think>好的,用户现在问的是关于U-Boot中使用spi_flash_probe函数的问题。首先,我需要回顾之前的对话历史,用户之前遇到了SPI_MODE_3未声明的错误,我给出了相应的解决方案。现在用户的问题转向了U-Boot中如何使用这个函数,说明他们可能在移植或开发U-Boot的驱动,或者在调试SPI Flash相关的问题。 首先,我需要确认用户的使用场景。可能用户正在开发嵌入式系统,使用U-Boot作为引导加载程序,需要初始化或操作SPI Flash设备。他们的需求可能是了解如何正确调用spi_flash_probe函数,或者遇到了相关的问题,比如设备无法识别、初始化失败等。 接下来,我需要回忆U-Boot中关于SPI Flash的代码结构。在U-Boot中,spi_flash_probe函数通常用于探测并初始化SPI Flash设备,返回一个struct spi_flash指针,供后续操作使用。这个函数的参数可能包括SPI总线号、片选号、速度、模式等,但具体参数可能因版本不同而有所变化。例如,有些版本可能不需要显式传递模式,而是通过设备树配置。 然后,我需要考虑用户可能遇到的问题。比如,他们是否包含了正确的头文件,比如#include <spi_flash.h>?是否在编译时启用了相关的配置选项,比如CONFIG_DM_SPI和CONFIG_SPI_FLASH?此外,设备树的配置是否正确,比如SPI控制器的节点、片选引脚、频率、模式等是否设置正确? 还要考虑到U-Boot的版本差异。例如,较新的U-Boot版本可能采用了设备树(Device Tree)来配置硬件,而旧版本可能使用硬编码的配置。如果用户使用的是新版本,可能需要检查设备树的配置是否正确,而旧版本可能需要修改板级配置文件或头文件。 另外,用户可能在调用spi_flash_probe时参数传递有误。比如,总线号和片选号是否正确,SPI模式是否与硬件匹配,频率是否在设备支持的范围内。需要提醒用户检查这些参数,并参考具体硬件的数据手册。 还需要考虑可能的错误处理。例如,函数返回NULL时,如何排查问题,比如SPI控制器驱动是否初始化,片选信号是否正确,是否有电气连接问题等。建议用户使用调试输出,如printf或者U-Boot的debug宏,来跟踪执行流程。 最后,总结回答的结构,分步骤介绍函数的作用、参数、使用流程、配置选项、常见问题及示例代码,确保用户能够逐步理解和应用。同时,提醒用户注意版本差异和设备树配置的重要性,以及如何通过调试来解决问题。</think>在 U-Boot 中,`spi_flash_probe()` 是用于初始化并探测 SPI Flash 设备的函数,常用于访问 SPI NOR Flash(如 Winbond、GD25 等芯片)。以下是该函数的关键点和使用方法: --- ### **1. 函数作用** - **探测 SPI Flash**:根据总线、片选(CS)和参数,初始化 SPI 控制器并识别连接的 Flash 芯片。 - **返回操作句柄**:返回 `struct spi_flash` 指针,用于后续的读写擦除操作。 --- ### **2. 函数原型** 在 U-Boot 源码中,函数通常定义如下(具体参数可能因版本不同略有差异): ```c struct spi_flash *spi_flash_probe(unsigned int bus, unsigned int cs, unsigned int max_hz, unsigned int spi_mode); ``` - **参数说明**: - `bus`: SPI 总线编号(如 `0` 表示第一个 SPI 控制器)。 - `cs`: 片选信号编号(如 `0` 表示第一个片选引脚)。 - `max_hz`: SPI 通信的最大频率(如 `1000000` 表示 1MHz)。 - `spi_mode`: SPI 模式(如 `SPI_MODE_0` 到 `SPI_MODE_3`,需与 Flash 芯片匹配)。 - **返回值**:成功返回 `struct spi_flash` 指针,失败返回 `NULL`。 --- ### **3. 使用流程** #### **(1) 包含头文件** 确保代码中已包含 SPI Flash 相关头文件: ```c #include <spi.h> #include <spi_flash.h> ``` #### **(2) 调用示例** ```c struct spi_flash *flash; // 探测 SPI Flash(总线 0,片选 0,频率 1MHz,SPI 模式 3) flash = spi_flash_probe(0, 0, 1000000, SPI_MODE_3); if (!flash) { printf("SPI Flash 探测失败!\n"); return -1; } // 后续操作:读取 Flash ID、擦除、写入、读取等 ``` --- ### **4. 关键配置** #### **(1) 启用 SPI 驱动** 在 U-Boot 的配置文件(如 `defconfig` 或 Kconfig)中确保以下配置已启用: ```makefile CONFIG_DM_SPI=y # 启用 SPI 设备模型 CONFIG_SPI_FLASH=y # 启用 SPI Flash 支持 CONFIG_SPI=y # 启用 SPI 控制器驱动 ``` #### **(2) 设备树配置(DTS)** 在设备树中需定义 SPI 控制器和 Flash 节点(以 U-Boot 设备树为例): ```dts &spi0 { status = "okay"; flash@0 { compatible = "jedec,spi-nor"; reg = <0>; // 片选 0 spi-max-frequency = <1000000>; spi-cpol; // CPOL=1 spi-cpha; // CPHA=1(SPI_MODE_3) }; }; ``` --- ### **5. 常见问题** #### **(1) 探测失败的可能原因** - **SPI 控制器未初始化**:检查是否在板级代码中调用了 `spi_init()`。 - **设备树配置错误**:确认 DTS 中的 SPI 节点和 Flash 子节点配置正确。 - **硬件连接问题**:检查片选(CS)、时钟(SCK)、数据线(MOSI/MISO)是否正常。 #### **(2) SPI 模式不匹配** - Flash 芯片的 SPI 模式需与代码中 `spi_mode` 参数一致(常见为 `SPI_MODE_3`)。 - 若出现 `SPI_MODE_3 undeclared` 错误,需检查头文件是否包含 `<spi.h>` 或 `<spi-mem.h>`。 --- ### **6. 扩展操作** #### **(1) 读写 Flash** ```c u8 buf[256]; u32 addr = 0x1000; // 操作地址 // 读取 256 字节 spi_flash_read(flash, addr, sizeof(buf), buf); // 写入前需先擦除 spi_flash_erase(flash, addr, sizeof(buf)); spi_flash_write(flash, addr, sizeof(buf), buf); ``` #### **(2) 获取 Flash 信息** ```c printf("Flash 名称: %s\n", flash->name); printf("Flash 大小: %u MB\n", flash->size >> 20); ``` --- ### **7. 示例代码(U-Boot 命令行)** 在 U-Boot 命令行中可通过 `sf` 命令操作 SPI Flash(需启用 `CONFIG_CMD_SF`): ```bash # 探测 Flash sf probe 0:0 # 总线 0,片选 0 # 读取 Flash ID sf id # 擦除并写入数据 sf erase 0x1000 0x100 # 擦除 256 字节 sf write 0x20000000 0x1000 0x100 # 将内存地址 0x20000000 的数据写入 Flash ``` --- ### **总结** - `spi_flash_probe()` 是 U-Boot 中操作 SPI Flash 的核心入口函数。 - 需确保 **SPI 控制器初始化**、**设备树配置** 和 **硬件连接** 正确。 - 若需更深入调试,可通过 `CONFIG_LOGLEVEL=8` 启用 U-Boot 的详细日志输出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值