uboot的移植——移植uboot官方的uboot到x210开发板(2)

2024-03-27 09:59

本文主要是介绍uboot的移植——移植uboot官方的uboot到x210开发板(2),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

参考内容

1、uboot的移植——移植uboot官方的uboot到x210开发板(1)

2、uboot启动流程——C阶段的start_armboot函数-CSDN博客

3、以官方uboot移植uboot(SD卡驱动移植)-CSDN博客

八、第二阶段总结

博客移植uboot官方的uboot到x210开发板(1)完成了第一阶段的移植工作(主要工作是DDR的初始化、uboot的重定位与清BSS段)。第一阶段移植工作结束后,运行现象如下:

第二阶段的入口是/arch/arm/lib/crt0.S文件中的_main函数,如下所示:

/*
第一阶段主要调用/board/samsung/goni/lowlevel_init.S文件中的lowlevel_init函数,
以完成DDR的初始化。然后以非函数的形式完成uboot的的重定位、BSS段的清零。
*///进入第二阶段ldr	pc, __main__main:.word _main

 _main函数主要完成以下工作:

ENTRY(_main)//设置栈//调用 board_init_f 函数//uboot的重定位与清BSS段//调用 board_init_r 函数
ENDPROC(_main)

(1)首先设置栈,即把sp指向DDR中的某个地址。

(2)然后调用/arch/arm/lib/board.c文件中的 board_init_f 函数进行板级初始化,如下所示:

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)ldr	sp, =(CONFIG_SPL_STACK)
#elseldr	sp, =(CONFIG_SYS_INIT_SP_ADDR) //设置栈
#endifbic	sp, sp, #7	/* 8-byte alignment for ABI compliance */sub	sp, #GD_SIZE	/* allocate one GD above SP */bic	sp, sp, #7	/* 8-byte alignment for ABI compliance */mov	r9, sp		/* GD is above SP */mov	r0, #0bl	board_init_f 

board_init_f函数在/common/board_f.c、/drivers/video/exynos_fb.c、/arch/arm/lib/board.c等文件中都有定义,那这里为何是/arch/arm/lib/board.c文件中的board_init_f函数?我们可以从执行make时是否生成.o文件来判断到底选择哪个文件中的board_init_f函数,又或者通过分析这些文件所在文件夹中的Makefile文件(是否编译某个文件)来判断。

另外board_init_f函数是C语言函数,那汇编代码中是如何给C语言函数传递参数的?(未解)

void board_init_f(ulong bootflag) {//省略代码}

(3)然后进行uboot的重定位与清BSS段(由于该部分工作已经放在第一阶段完成,这里要删除这部分的代码)。

(4)最后调用/arch/arm/lib/board.c文件中的 board_init_r 函数,如下所示:

	/* call board_init_r(gd_t *id, ulong dest_addr) */mov     r0, r9                  /* gd_t */ldr	r1, [r9, #GD_RELOCADDR]	/* dest_addr *//* call board_init_r */ldr	pc, =board_init_r	/* this is auto-relocated! */

该函数内部进行了一些列初始化,比如SD/MMC的初始化、网卡的初始化、串口初始化、环境变量的重定位、控制台初始化等工作,最终调用main_loop函数以进入uboot命令行或启动内核。 

board_init_r函数是C语言函数,那汇编代码中是如何给C语言函数传递参数的?从上面代码解释可以看出,r0中存储着board_init_r函数的传参1,r1中存储着board_init_r函数的传参2。但是该函数怎么知道去寄存器r1和r2中找它的参数的?(未解)

void board_init_r(gd_t *id, ulong dest_addr){//省略代码}

九、分析board_init_f函数

在开始进行移植工作前,我们先分析/arch/arm/lib/board.c文件中board_init_f函数的内容。

首先,board_init_f函数填充了gd这个全局变量。

然后,通过一个for循环顺序执行init_sequence这个函数指针数组中的函数:

	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {if ((*init_fnc_ptr)() != 0) {hang ();}}

该函数指针数组的定义如下:

init_fnc_t *init_sequence[] = {arch_cpu_init,		/* basic arch cpu dependent setup */mark_bootstage,
#ifdef CONFIG_OF_CONTROLfdtdec_check_fdt,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)board_early_init_f,
#endiftimer_init,		/* initialize timer */
#ifdef CONFIG_BOARD_POSTCLK_INITboard_postclk_init,
#endif
#ifdef CONFIG_FSL_ESDHCget_clocks,
#endifenv_init,		/* initialize environment */init_baudrate,		/* initialze baudrate settings */serial_init,		/* serial communications setup */console_init_f,		/* stage 1 init of console */display_banner,		/* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)print_cpuinfo,		/* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)checkboard,		/* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)init_func_i2c,
#endifdram_init,		/* configure available RAM banks */NULL,
};

其中与我们移植工作有关的函数包括:

(1)display_banner函数:打印“U-Boot 2013.10(Mar 12 2024 - 15:35:41)”这个内容。

(2)print_cpuinfo函数:打印“CPU:    s5pc110@400MHz” 这个内容。

(3)dram_init函数:给gd->ram_size赋值,即三块DDR bank合起来的大小。

(4)checkboard函数:打印“Board: Goni”这个内容。

(5)init_func_i2c函数:对于X210开发板,uboot中的I2C部分是没有用的,因此可以不执行这个函数,我们可以将定义在s5p_goni文件中的宏CONFIG_SYS_I2C注释掉。但是课程提到(我没有实践过),注释掉这个宏将导致编译报错(因为其他文件中用到这个宏),因此这里选择不管它,所以接下来的移植实验里SCRT中一直显示“I2C:ready”。

接着,board_init_f 函数继续执行下面的函数:

(1)lcd_setmem函数:由于条件编译,该函数不会被执行。

(2)interrupt_init函数:不用改动。

(3)dram_init_banksize函数:初始化每块DDR bank的起始地址、大小。

(4)display_dram_config函数:打印“DRAM:464MiB”这个内容。

理清代码执行顺序后,接下来我们将进行第二阶段的移植。

十、CPU信息显示移植

第八节的图片显示的信息中,有一些信息是错误的:比如X210开发板的CPU的主频率是1000MHz 而非400MHz;DRAM的大小是512MB而非464MiB;开发板的名字是“X210”而非“Goni”等等。因此我们需要进行移植。

这里说的“CPU信息显示移植”,指的是修改 “CPU:    S5PC110@400MHz” 这个内容。

10.1 补全banner信息

也就是在“U-Boot 2013.10(Mar 12 2024 - 15:35:41)”这个输出的末尾添加“for X210”。

它表明这个uboot是为哪个开发板移植的,是个可选的移植操作。

“U-Boot 2013.10(Mar 12 2024 - 15:35:41)”这个信息是由display_banner函数打印的,该函数内部关键代码如下:

printf("\n\n%s\n\n", version_string);

可知它打印的是 version_string 这个字符数组,该字符数组的定义如下:

const char __weak version_string[] = U_BOOT_VERSION_STRING;

而U_BOOT_VERSION_STRING 这个宏的定义如下:

#define U_BOOT_VERSION_STRING U_BOOT_VERSION " (" U_BOOT_DATE " - " \U_BOOT_TIME ")" CONFIG_IDENT_STRING

其中 U_BOOT_VERSION 、U_BOOT_DATE 、U_BOOT_TIME 在/Makefile中有相关定义。

我们关注一下CONFIG_IDENT_STRING,这个宏在/include/configs/s5p_goni.h文件中没有定义,所以“U-Boot 2013.10(Mar 12 2024 - 15:35:41)”的后面没有类似“for X210”这样的信息。我们在s5p_goni.h文件中中定义这个宏如下:

#define  CONFIG_IDENT_STRING "for X210"

 重新编译与烧录,运行结果如下所示:

10.2 修改CPU的ID

也就是修改 “CPU:    s5pc110@400MHz”中的“s5pc110”,这里应该修改为“s5pv210”。

“CPU:  s5pc110@400MHz”是由/arch/arm/cpu/armv7/s5p-common/cpu_info.c文件中的print_cpuinfo函数打印的,该函数内容如下:

int print_cpuinfo(void)
{char buf[32];printf("CPU:\t%s%X@%sMHz\n",s5p_get_cpu_name(), s5p_cpu_id,strmhz(buf, get_arm_clk()));return 0;
}

代码中的变量s5p_cpu_id定义如下所示:

unsigned int s5p_cpu_id = 0xC100;

从中可以得知,s5p_cpu_id是“S5PC110”这个字符串的一部分,即“C110”。

如果想改为S5PV210,由于字母V不是16进制里的字母,而s5p_cpu_id是整数类型而非字符数组类型变量,因此不能将s5p_cpu_id这个变量赋值为 “V210”。我们不修改 s5p_cpu_id 这个变量,而是选直接输出,如下所示:

int print_cpuinfo(void)
{char buf[32];/*	printf("CPU:\t%s%X@%sMHz\n",s5p_get_cpu_name(), s5p_cpu_id,strmhz(buf, get_arm_clk()));
*/printf("\nCPU:  S5PV210@%sMHz(OK)\n", strmhz(buf, get_arm_clk()));return 0;
}

这里的 strmhz(buf, get_arm_clk())函数,是通过get_arm_clk函数获取ARMCLK的频率,然后用strmhz函数转换为以MHz单位的数值。

我们分析一下get_arm_clk函数,它位于/arch/arm/cpu/armv7/s5pc1xx/clock.c文件中,内容如下:

unsigned long get_arm_clk(void)
{if (cpu_is_s5pc110()) //我们选择这条路线return s5pc110_get_arm_clk();elsereturn s5pc100_get_arm_clk();
}

其中 cpu_is_s5pc110 函数在SI工程中搜索不到(之前也遇到过这种情形,一种可能就是以宏定义的形式给出,涉及##这种替换;另外一种就是可能SI工程没有包含某些格式的文件),这里是因为它是以宏定义的形式定义的(在/arch/arm/include/asm/arch-s5pc1xx/cpu.h文件中定义):

#define IS_SAMSUNG_TYPE(type, id)			\
static inline int cpu_is_##type(void)			\
{							\return s5p_cpu_id == id ? 1 : 0;		\
}IS_SAMSUNG_TYPE(s5pc110, 0xc110)

则IS_SAMSUNG_TYPE(s5pc110, 0xc110)就相当于定义了以下一个函数:

static inline int cpu_is_s5pc110(void)
{return s5p_cpu_id == 0xc110 ? 1 : 0;
}

由于我们没有修改s5p_cpu_id 这个变量,所以它的值就是0xc110,因此cpu_is_s5pc110函数返回值为1,所以get_arm_clk函数会执行s5pc110_get_arm_clk函数。这个函数会计算得到ARMCLK的时钟值,即400MHz(但这个值是不对的,原因见下面)。

修改后的运行结果如下:

10.3 修改CPU的频率

10.3.1 分析问题

也就是根据实际情况修改 “CPU:   S5PC110@400MHz”中的“400MHz”。

这里显示的“400MHz”,根据/arch/arm/cpu/armv7/s5p-common/cpu_info.c文件中的print_cpuinfo函数代码,可知它是ARMCLK的时钟,但实际上ARMCLK的时钟究竟是多少,有待研究。

我们在print_cpuinfo函数中将APLL、MPLL等时钟打印出来(这段代码是从三星版本uboot的print_cpuinfo函数中拷贝的),如下所示:

int print_cpuinfo(void)
{char buf[32];/*	printf("CPU:\t%s%X@%sMHz\n",s5p_get_cpu_name(), s5p_cpu_id,strmhz(buf, get_arm_clk()));
*/printf("\nCPU:  S5PV210@%sMHz(OK)\n", strmhz(buf, get_arm_clk()));printf("        APLL = %ldMHz, HclkMsys = %ldMHz, PclkMsys = %ldMHz\n",get_FCLK()/1000000, get_HCLK()/1000000, get_PCLK()/1000000);printf("	MPLL = %ldMHz, EPLL = %ldMHz\n",get_MPLL_CLK()/1000000, get_PLLCLK(EPLL)/1000000);printf("			   HclkDsys = %ldMHz, PclkDsys = %ldMHz\n",get_HCLKD()/1000000, get_PCLKD()/1000000);printf("			   HclkPsys = %ldMHz, PclkPsys = %ldMHz\n",get_HCLKP()/1000000, get_PCLKP()/1000000);printf("			   SCLKA2M	= %ldMHz\n", get_SCLKA2M()/1000000);return 0;
}

注意这里调用了 get_FCLK、get_HCLK等函数(有时间看一下这些函数的实现),我们需要先移植这些函数:

(1)首先将这些函数的实现(在三星版本uboot的/cpu/s5pc11x/s5pc110/speed.c文件中)拷贝到print_cpuinfo函数所在的cpu_info.c文件的前面(注意speed.c文件开头的三个变量宏定义APLL、MPLL和EPLL也要拷贝过去)。

(2)然后在 /include/s5pc110.h文件中补上这些函数使用到的宏(好像都已经定义了)。

(3)因为get_PLLCLK函数使用的是CONFIG_SYS_CLK_FREQ这个宏,它与s5p_goni.h文件中的CONFIG_SYS_CLK_FREQ_C100同义,所以要将CONFIG_SYS_CLK_FREQ_C100改为CONFIG_SYS_CLK_FREQ。

(4)最后在 cpu_info.c 文件的开头添加头文件包含语句“#include <s5pc110.h>”。

经过上述操作,我们重新编译并烧录运行,发现它们的值和九鼎版本uboot所设置的时钟值不同:

这是因为2013.10版本uboot并未对SoC的时钟进行设置(uboot中虽然有时钟初始化函数,但是没有被调用,见参考内容1的4.1【4】的描述),因此SoC的时钟是由iROM默认设置的。

iROM中默认设置的时钟是:APLL是800MHz(我原本以为iROM中将APLL设置为1000MHz了,因此uboot中是否有时钟设置并不重要,但实际并非如此),ARMCLK是400MHz。因此uboot中如果不设置时钟,则实际得到的就是这个时钟,所以显示400MHz。

所以要解决这个时钟不对的问题,在lowlevel_init.S文件中添加上时钟初始化的代码即可。

10.3.2 解决问题 

我们的解决思路是,我们首先在2013.10版本uboot的/board/samsung/goni/lowlevel_init.S文件中移植system_clock_init函数,并且在/include/configs/s5p_goni.h文件中添加相关的宏定义,然后在lowlevel_init.S文件中的lowlevel_init函数中调用system_clock_init函数。

具体步骤如下:

(1)lowlevel_init函数中调用system_clock_init函数的位置如下所示:

	// for clock initbl	system_clock_init  //在这里调用/* for UART */bl	uart_asm_init// DDR initbl	mem_ctrl_asm_init

(2)在lowlevel_init.S文件中重写system_clock_init函数。该函数的代码见系统时钟初始化函数system_clock_init。如果直接拷贝这个链接里的函数,需要把该函数中的一些条件编译处理一下;另外在进行第(3)操作时要参考x210_sd.h文件而非smdkv210single.h来添加宏定义。我这里直接拷贝课程中的system_clock_init函数代码,因此在进行第(3)操作时参考的是smdkv210single.h文件。

(3)在 /include/configs/s5p_goni.h文件中添加相关的宏定义参数(参考三星的smdkv210single.h文件):

// clock
#define APLL_MDIV       0x7d
#define APLL_PDIV       0x3
#define APLL_SDIV       0x1#define APLL_LOCKTIME_VAL	0x2cf#if defined(CONFIG_EVT1)
/* Set AFC value */
#define AFC_ON		0x00000000
#define AFC_OFF		0x10000010
#endif#define MPLL_MDIV	0x29b
#define MPLL_PDIV	0xc
#define MPLL_SDIV	0x1#define EPLL_MDIV	0x60
#define EPLL_PDIV	0x6
#define EPLL_SDIV	0x2#define VPLL_MDIV	0x6c
#define VPLL_PDIV	0x6
#define VPLL_SDIV	0x3/* CLK_DIV0 */
#define APLL_RATIO	0
#define A2M_RATIO	4
#define HCLK_MSYS_RATIO	8
#define PCLK_MSYS_RATIO	12
#define HCLK_DSYS_RATIO	16
#define PCLK_DSYS_RATIO 20
#define HCLK_PSYS_RATIO	24
#define PCLK_PSYS_RATIO 28#define CLK_DIV0_MASK	0x7fffffff#define set_pll(mdiv, pdiv, sdiv)	(1<<31 | mdiv<<16 | pdiv<<8 | sdiv)#define APLL_VAL	set_pll(APLL_MDIV,APLL_PDIV,APLL_SDIV)
#define MPLL_VAL	set_pll(MPLL_MDIV,MPLL_PDIV,MPLL_SDIV)
#define EPLL_VAL	set_pll(EPLL_MDIV,EPLL_PDIV,EPLL_SDIV)
#define VPLL_VAL	set_pll(VPLL_MDIV,VPLL_PDIV,VPLL_SDIV)#define CLK_DIV0_VAL    ((0<<APLL_RATIO)|(4<<A2M_RATIO)|(4<<HCLK_MSYS_RATIO)|(1<<PCLK_MSYS_RATIO)\|(3<<HCLK_DSYS_RATIO)|(1<<PCLK_DSYS_RATIO)|(4<<HCLK_PSYS_RATIO)|(1<<PCLK_PSYS_RATIO))
#define CLK_DIV1_VAL	((1<<16)|(1<<12)|(1<<8)|(1<<4))
#define CLK_DIV2_VAL	(1<<0)

10.3.3 运行现象

重新编译时报错如下:

lowlevel_init.S: Assembler messages:
lowlevel_init.S:271: Error: internal_relocation (type: OFFSET_IMM) not fixed up
lowlevel_init.S:274: Error: internal_relocation (type: OFFSET_IMM) not fixed up
lowlevel_init.S:279: Error: internal_relocation (type: OFFSET_IMM) not fixed up
#省略部分输出
lowlevel_init.S:339: Error: internal_relocation (type: OFFSET_IMM) not fixed up
make[1]: *** [lowlevel_init.o] 错误 1
make[1]:正在离开目录 `/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/board/samsung/goni'
make: *** [board/samsung/goni/libgoni.o] 错误 2

​查看lowlevel_init.S文件,发现报错的行数都是与一些宏有关的,如下所示:

这些宏被定义在/include/s5pc110.h文件中,现在报错可能是因为没有包含这个头文件。我们查看lowlevel_init.S文件开头,果然缺失了这句代码 “#include <s5pc110.h>”,我们补上即可。

但是重新编译时继续报错,原因是没有定义PHYS_SDRAM_3_SIZE这个宏,如下所示:

goni.c: In function 'dram_init':
goni.c:49: error: 'PHYS_SDRAM_3_SIZE' undeclared (first use in this function)
goni.c:49: error: (Each undeclared identifier is reported only once
goni.c:49: error: for each function it appears in.)
goni.c: In function 'dram_init_banksize':
goni.c:60: error: 'PHYS_SDRAM_3' undeclared (first use in this function)
goni.c:61: error: 'PHYS_SDRAM_3_SIZE' undeclared (first use in this function)
make[1]: *** [goni.o] 错误 1
make[1]:正在离开目录 `/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/board/samsung/goni'
make: *** [board/samsung/goni/libgoni.o] 错误 2

报错原因是我为了省事,在9.3.2(3)的操作中,将课程中移植好的/include/configs/s5p_goni.h文件直接覆盖原来的s5p_goni.h文件。原来的s5p_goni.h文件有PHYS_SDRAM_3_SIZE这个宏的定义,dram_init函数内部计算的是3块DDR bank的信息。但是课程中已经移植好的s5p_goni.h文件将这个宏删除了,而我这里又没有修改dram_init函数,所以会报错。

为了体现一步步移植的逻辑,我这里不再使用课程移植好的s5p_goni.h文件,而是自己移植。将之前关于的s5p_goni.h文件的操作重新进行一遍,得到该步骤对应的s5p_goni.h文件(见链接)。

重新编译可以通过,烧录运行结果如下:

可见这里参考三星的smdkv210single.h文件进行时钟设置的运行结果,与九鼎版本uoot的运行结果,两者除了EPLL的值不同外,其他时钟的值是一样的。这里是80MHz(和移植三星官方的uboot到X210开发板的结果一样),而九鼎uboot设置的是96MHz。

十一、board与DDR信息显示移植

11.1 更改board的名称

这里指的是更改“Board:  Goni”中的“Goni”,将它改为“X210”,表示开发板的名字是“X210”。

“Board: Goni”这个内容,是由/board/samsung/goni/goni.c文件中的checkboard函数中打印的。该函数内容如下:

#ifdef CONFIG_DISPLAY_BOARDINFO
int checkboard(void)
{puts("Board:\tGoni\n");//puts("Board:\tX210\n");return 0;
}
#endif

所以更改为“Board:  X210”非常简单,改为注释的那条语句即可,但这里我没有修改(懒得改)。

11.2 更改DDR的显示信息

涉及dram_init函数、dram_init_banksize函数、display_dram_config函数。

dram_init函数定义在/board/samsung/goni/goni.c文件中,定义如下:

int dram_init(void)
{gd->ram_size = PHYS_SDRAM_1_SIZE + PHYS_SDRAM_2_SIZE +PHYS_SDRAM_3_SIZE;return 0;
}

dram_init_banksize函数定义在/board/samsung/goni/goni.c文件中,定义如下:

void dram_init_banksize(void)
{gd->bd->bi_dram[0].start = PHYS_SDRAM_1;gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;gd->bd->bi_dram[1].start = PHYS_SDRAM_2;gd->bd->bi_dram[1].size = PHYS_SDRAM_2_SIZE;gd->bd->bi_dram[2].start = PHYS_SDRAM_3;gd->bd->bi_dram[2].size = PHYS_SDRAM_3_SIZE;
}

display_dram_config函数定义在 /arch/arm/lib/board.c文件中,内容如下:

static int display_dram_config(void)
{int i;ulong size = 0;for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++)size += gd->bd->bi_dram[i].size;puts("DRAM:  ");print_size(size, "\n");return (0);
}

由上面函数内容可知,Goni这个开发板配置有3块DDR bank,但是X210开发板只配置了2块DDR bank。因此需要修改这些函数的内容,以及一些宏的定义。

(1)修改/include/configs/s5p_goni.h文件中的宏CONFIG_NR_DRAM_BANKS。

该宏定义表示开发板板载了多少块DDR bank,我们将CONFIG_NR_DRAM_BANKS 的值改为2。

(2)注释掉/include/configs/s5p_goni.h中与第3块DDR bank有关的宏。

/* Goni has 3 banks of DRAM, but swap the bank */
//#define CONFIG_NR_DRAM_BANKS  3
#define CONFIG_NR_DRAM_BANKS    2#define PHYS_SDRAM_1            CONFIG_SYS_SDRAM_BASE   /* OneDRAM Bank #0 */
#define PHYS_SDRAM_1_SIZE       (80 << 20)              /* 80 MB in Bank #0 */#define PHYS_SDRAM_2            0x40000000              /* mDDR DMC1 Bank #1 */
#define PHYS_SDRAM_2_SIZE       (256 << 20)             /* 256 MB in Bank #1 *///注释掉第3块DDR bank的内容
//#define PHYS_SDRAM_3            0x50000000              /* mDDR DMC2 Bank #2 */
//#define PHYS_SDRAM_3_SIZE       (128 << 20)             /* 128 MB in Bank #2 */

 (3)修改每块DDR bank的信息(大小与起始地址)

/* Goni has 3 banks of DRAM, but swap the bank */
//#define CONFIG_NR_DRAM_BANKS  3
#define CONFIG_NR_DRAM_BANKS    2//修改每块DDR bank的信息(大小与起始地址)
#define PHYS_SDRAM_1            CONFIG_SYS_SDRAM_BASE   /* OneDRAM Bank #0 */
//#define PHYS_SDRAM_1_SIZE       (80 << 20)              /* 80 MB in Bank #0 */
#define PHYS_SDRAM_1_SIZE       (256 << 20)              /* 256 MB in Bank #0 */#define PHYS_SDRAM_2            0x40000000              /* mDDR DMC1 Bank #1 */
#define PHYS_SDRAM_2_SIZE       (256 << 20)             /* 256 MB in Bank #1 *///注释掉第3块DDR bank的内容
//#define PHYS_SDRAM_3            0x50000000              /* mDDR DMC2 Bank #2 */
//#define PHYS_SDRAM_3_SIZE       (128 << 20)             /* 128 MB in Bank #2 */

(4)修改 dram_init_banksize 函数。

void dram_init_banksize(void)
{gd->bd->bi_dram[0].start = PHYS_SDRAM_1;gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;gd->bd->bi_dram[1].start = PHYS_SDRAM_2;gd->bd->bi_dram[1].size = PHYS_SDRAM_2_SIZE;//gd->bd->bi_dram[2].start = PHYS_SDRAM_3;//gd->bd->bi_dram[2].size = PHYS_SDRAM_3_SIZE;
}

(5)修改 dram_init 函数。

​
int dram_init(void)
{//gd->ram_size = PHYS_SDRAM_1_SIZE + PHYS_SDRAM_2_SIZE + PHYS_SDRAM_3_SIZE;gd->ram_size = PHYS_SDRAM_1_SIZE + PHYS_SDRAM_2_SIZE;return 0;
}

 完成上面的操作之后,重新编译与烧录运行,运行现象如下:

这里显示“DRAM:512MiB”而非“DRAM:512MB”,这是display_dram_config函数所调用的print_size函数写错的缘故(这就很可笑,我以为这个MiB是某个我不认识的单位,因此上网查询,居然真的有这个单位。但这里应该是上传这个2013.10版本uboot的人写错了而已,我们删除字母i即可):

void print_size (phys_size_t size, const char *s)
{//省略其他代码//printf (" %ciB%s", c, s);printf (" %cB%s", c, s);
}

十二、分析board_init_r函数

移植完/arch/arm/lib/board.c文件中的board_init_f函数相关内容后,烧录运行结果如下:

接下来移植/arch/arm/lib/board.c文件中的board_init_r函数相关的内容。

先分析一下该函数的内容,它主要涉及以下函数:

(1)enable_caches函数:输出“WARNING: Caches not enabled”而已,可以注释掉这个函数。

(2)board_init函数:设置开发板的机器码,以及传递给内核的启动参数的存放地址。

(3)serial_initialize函数:暂时不管它。

(4)mem_malloc_init函数:初始化堆空间(堆空间的起始地址、堆空间的大小)。

(5)power_init_board函数:输出“Board PMIC init”。因为没有电源管理模块,可以注释掉。

(6)flash_init函数:条件编译不执行。

(7)nand_init函数:条件编译不执行。

(8)onenand_init函数:打印出“Muxed OneNAND 16MB 2.65/3.3V 16-bit (0xf001)”、“No OOB scheme defined for oobsize 3840”。

(9)mmc_initialize函数:初始化SD/MMC(包括初始化SD/MMC控制器,以及注册SD/MMC设备驱动)。

(10)env_relocate函数:暂时不管它。

(11)stdio_init函数:暂时不管它。

(12)jumptable_init函数:暂时不管它。

(13)console_init_r函数:暂时不管它。

(14)enable_interrupts函数:暂时不管它。

(15)eth_initialize函数:这是初始化网卡的函数,原来代码由于条件编译不会执行,但后续我们会移植。

(16)main_loop函数:进入控制台(输出命令提示符“Goni #:”)或者执行启动命令。

经过简单分析与初步修改之后,重新编译与烧录运行,运行现象如下:

十三、更改MACH_TYPE

在board_init_f函数中有以下代码(这是以前版本比如1.3.4版本uboot定义机器码的方式):

#ifdef CONFIG_MACH_TYPEgd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */
#endif

后来在board_init_r函数中的board_init函数(位于/board/samsung/goni/goni.c文件)的代码又覆盖上面的定义,这说明Goni这个开发板的机器码最终被赋予 MACH_TYPE_GONI 这个宏的值。

int board_init(void)
{/* Set Initial global variables */s5pc110_gpio = (struct s5pc110_gpio *)S5PC110_GPIO_BASE;gd->bd->bi_arch_number = MACH_TYPE_GONI;gd->bd->bi_boot_params = PHYS_SDRAM_1 + 0x100;return 0;
}

关于MACH_TYPE的定义问题,2013.10版本uboot与九鼎1.3.4版本uboot的设计有所不同。

比如2013.10版本uboot中,宏 MACH_TYPE_GONI定义在/arch/arm/include/asm/mach-types.h文件里。这个文件集中定义了开发板的机器码。这种集中定义的方法是uboot从Linux内核中学来的(在Linux内核中,MACH_TYPE就集中定义在某个文件中)。这种集中定义的方法好处就是方便查阅、不容易重复定义。

#define MACH_TYPE_HTC_SPV_M700         2860
#define MACH_TYPE_MX257SX              2861
#define MACH_TYPE_GONI                 2862  //这里
#define MACH_TYPE_MSM8X55_SVLTE_FFA    2863
#define MACH_TYPE_MSM8X55_SVLTE_SURF   2864

而1.3.4版本的uboot中,MACH_TYPE分散定义在各个配置头文件中,比如九鼎X210开发板的机器码就定义在x210_sd.h文件中。

/** Architecture magic and machine type*/#define MACH_TYPE               2456   //这里#define UBOOT_MAGIC             (0x43090000 | MACH_TYPE)/* Power Management is enabled */
#define CONFIG_PM
#define CONFIG_DISPLAY_CPUINFO

原则上每一个型号的开发板都有一个MACH_TYPE,这个MACH_TYPE由Linux内核管理者来统一分配,如果需要使用则先应该申请。上面的MACH_TYPE_GONI(2862)是Goni开发板对应的机器码;我们的开发板是X210,它也要对应着一个机器码。至于这个机器码是多少,原则上需要向内核管理者申请,但实际上它只是一个标号而已。只要uboot中设置的机器码与内核中设置的机器码一样即可。

我原本想和九鼎一样,为X210开发板设置机器码为“2456”,但是在/arch/arm/include/asm/mach-types.h文件里搜寻“2456”,不幸地发现这个机器码已经被一款名为SMDKV210的开发板占用了。

#define MACH_TYPE_TNETV107X            2418
#define MACH_TYPE_SMDKV210             2456  //被占用了
#define MACH_TYPE_OMAP_ZOOM3           2464
#define MACH_TYPE_OMAP_3630SDP         2465

此时可以在/arch/arm/include/asm/mach-types.h文件中,定义X210开发板的机器码为2460:

#define MACH_TYPE_TNETV107X            2418
#define MACH_TYPE_SMDKV210             2456
//定义X210开发板的机器码为2460
#define MACH_TYPE_X210                 2460
#define MACH_TYPE_OMAP_ZOOM3           2464
#define MACH_TYPE_OMAP_3630SDP         2465

然后修改board_init_r函数中的board_init函数,如下所示:

​
int board_init(void)
{/* Set Initial global variables */s5pc110_gpio = (struct s5pc110_gpio *)S5PC110_GPIO_BASE;//gd->bd->bi_arch_number = MACH_TYPE_GONI;gd->bd->bi_arch_number = MACH_TYPE_X210;//修改为这个gd->bd->bi_boot_params = PHYS_SDRAM_1 + 0x100;return 0;
}

上述修改之后,如果编译通过则说明修改机器码成功了,这个暂时没有什么运行现象。但是如果使用这个移植后的uboot去启动内核,则一定要在内核相关文件中将机器码修改为“2460”,否则Linux内核将启动不起来。

十四、去掉oneNand支持

关于oneNand的介绍见博客OneNand、Nand和Nor三种Flash的区别。

简而言之,oneNand采用了Nand逻辑结构的存储内核和Nor的控制接口,并在系统内设置一定容量的SRAM作为高速缓冲区。

但是X210开发板板载的外部存储器是iNand和SD卡,并非是oneNand,所以我们应该去掉uboot中关于支持oneNand的代码,也就是注释掉onenand_init函数,否则会输出“Muxed OneNAND 16MB 2.65/3.3V 16-bit (0xf001)”、“No OOB scheme defined for oobsize 3840”这些误导性的错误信息。

(1)该函数是否执行,受控于/include/configs/s5p_goni.h文件中的宏CONFIG_CMD_ONENAND,因此理论上我们只需要注释掉这个宏即可。但是注释掉这个宏后重新编译会报错,如下所示:

common/libcommon.o: In function `saveenv':
/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/common/env_onenand.c:112: undefined reference to `onenand_mtd'
common/libcommon.o: In function `env_relocate_spec':
/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/common/env_onenand.c:64: undefined reference to `onenand_mtd'
make: *** [u-boot] 错误 1
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10#

可知出错原因是 env_onenand.c 文件直接或间接地与这个宏有关。

实际上X210开发板没有使用onenand,因此不会涉及这个文件。这里之所以会涉及env_onenand.c这个文件,是因为/common/Makefile文件中有如下代码,而在s5p_goni.h文件中又定义了CONFIG_ENV_IS_IN_ONENAND这个宏。

COBJS-$(CONFIG_ENV_IS_IN_FAT) += env_fat.o
COBJS-$(CONFIG_ENV_IS_IN_NAND) += env_nand.o
COBJS-$(CONFIG_ENV_IS_IN_NVRAM) += env_nvram.o
COBJS-$(CONFIG_ENV_IS_IN_ONENAND) += env_onenand.o //这里
COBJS-$(CONFIG_ENV_IS_IN_SPI_FLASH) += env_sf.o
COBJS-$(CONFIG_ENV_IS_IN_REMOTE) += env_remote.o

因此我们将s5p_goni.h文件中将这个宏注释掉(顺便将oneNand有关的宏注释掉):

/* FLASH and environment organization *///(1)注释掉CONFIG_ENV_IS_IN_ONENAND这个宏
//#define CONFIG_ENV_IS_IN_ONENAND      1//(1)修改CONFIG_ENV_SIZE这个宏的值(因为它设置的不对)
//#define CONFIG_ENV_SIZE         (256 << 10)     /* 256 KiB, 0x40000 */
#define CONFIG_ENV_SIZE           CFG_ENV_SIZE  //#define CFG_ENV_SIZE   0x4000//(1)注释掉一些与oneNand有关的宏
//#define CONFIG_ENV_ADDR                       (1 << 20)       /* 1 MB, 0x100000 */
//#define CONFIG_USE_ONENAND_BOARD_INIT
//#define CONFIG_SAMSUNG_ONENAND                1
//#define CONFIG_SYS_ONENAND_BASE               0xB0000000

(2)此时编译还是报错如下:

cmd_nvedit.c:51: error: #error Define one of CONFIG_ENV_IS_IN_{EEPROM|FLASH|DATAFLASH|ONENAND|SPI_FLASH|NVRAM|MMC|FAT|REMOTE|UBI} or CONFIG_ENV_IS_NOWHERE
make[1]: *** [cmd_nvedit.o] 错误 1
make[1]:正在离开目录 `/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/common'
make: *** [common/libcommon.o] 错误 2
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10# 

该错误提示告知需要定义一个CONFIG_ENV_IS_IN_xxx宏,由于我们开发板是iNand或SD卡,因此这里的xxx是MMC。所以我们在s5p_goni.h文件中添加一个CONFIG_ENV_IS_IN_MMC宏:

/* FLASH and environment organization *///(1)注释掉CONFIG_ENV_IS_IN_ONENAND这个宏
//#define CONFIG_ENV_IS_IN_ONENAND      1//(1)修改CONFIG_ENV_SIZE这个宏的值(因为它设置的不对)
//#define CONFIG_ENV_SIZE         (256 << 10)     /* 256 KiB, 0x40000 */
#define CONFIG_ENV_SIZE           CFG_ENV_SIZE  //#define CFG_ENV_SIZE   0x4000//(2)定义一个CONFIG_ENV_IS_IN_xxx宏
#define  CONFIG_ENV_IS_IN_MMC  1//(1)注释掉一些与oneNand有关的宏
//#define CONFIG_ENV_ADDR                       (1 << 20)       /* 1 MB, 0x100000 */
//#define CONFIG_USE_ONENAND_BOARD_INIT
//#define CONFIG_SAMSUNG_ONENAND                1
//#define CONFIG_SYS_ONENAND_BASE               0xB0000000

(3)重新编译还是报错如下:

env_mmc.c: In function 'write_env':
env_mmc.c:108: error: 'CONFIG_SYS_MMC_ENV_DEV' undeclared (first use in this function)
env_mmc.c:108: error: (Each undeclared identifier is reported only once
env_mmc.c:108: error: for each function it appears in.)
env_mmc.c: In function 'saveenv':
env_mmc.c:123: error: 'CONFIG_SYS_MMC_ENV_DEV' undeclared (first use in this function)
env_mmc.c: In function 'read_env':
env_mmc.c:181: error: 'CONFIG_SYS_MMC_ENV_DEV' undeclared (first use in this function)
env_mmc.c: In function 'env_relocate_spec':
env_mmc.c:277: error: 'CONFIG_SYS_MMC_ENV_DEV' undeclared (first use in this function)
make[1]: *** [env_mmc.o] 错误 1
make[1]:正在离开目录 `/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/common'
make: *** [common/libcommon.o] 错误 2

这些错误提示缺乏CONFIG_SYS_MMC_ENV_DEV这个宏定义。由saveenv函数可知,这个宏表示外部存储器的哪个通道。如果定义为0则表示iNand,如果定义为1则表示SD卡。这里定义为0,表示内部的iNand(其实定义为1表示放在外部SD卡也是可以的)。

/* FLASH and environment organization *///(1)注释掉CONFIG_ENV_IS_IN_ONENAND这个宏
//#define CONFIG_ENV_IS_IN_ONENAND      1//(1)修改CONFIG_ENV_SIZE这个宏的值(因为它设置的不对)
//#define CONFIG_ENV_SIZE         (256 << 10)     /* 256 KiB, 0x40000 */
#define CONFIG_ENV_SIZE           CFG_ENV_SIZE    //#define CFG_ENV_SIZE   0x4000//(2)定义一个CONFIG_ENV_IS_IN_xxx宏
#define CONFIG_ENV_IS_IN_MMC 1//(3)定义CONFIG_SYS_MMC_ENV_DEV
#define CONFIG_SYS_MMC_ENV_DEV  0  //表示内部的iNand//(1)注释掉一些与oneNand有关的宏
//#define CONFIG_ENV_ADDR                       (1 << 20)       /* 1 MB, 0x100000 */
//#define CONFIG_USE_ONENAND_BOARD_INIT
//#define CONFIG_SAMSUNG_ONENAND                1
//#define CONFIG_SYS_ONENAND_BASE               0xB0000000

注意直接复制上面的代码到s5p_goni.h文件中可能会出现一些错误(error: stray '\302' in program),这是因为其中有中文空格,此时在英文输入法下重新组织一下就好。 

(4)此时重新编译还是报错,如下所示(错误与课程的不同):

onenand_uboot.c: In function 'onenand_init':
onenand_uboot.c:38: error: 'CONFIG_SYS_ONENAND_BASE' undeclared (first use in this function)
onenand_uboot.c:38: error: (Each undeclared identifier is reported only once
onenand_uboot.c:38: error: for each function it appears in.)
make[1]: *** [onenand_uboot.o] 错误 1
make[1]:正在离开目录 `/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/drivers/mtd/onenand'
make: *** [drivers/mtd/onenand/libonenand.o] 错误 2
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10# 

该错误提示缺乏CONFIG_SYS_ONENAND_BASE这个宏的定义。

我们并不需要编译onenand_uboot.c这个文件,所以修改与onenand_uboot.c这个文件同一目录下的Makefile文件,将onenand_uboot.o去掉。

ifndef	CONFIG_SPL_BUILD
#COBJS-$(CONFIG_CMD_ONENAND)	:= onenand_uboot.o onenand_base.o onenand_bbt.o
COBJS-$(CONFIG_CMD_ONENAND)	:=  onenand_base.o onenand_bbt.o
COBJS-$(CONFIG_SAMSUNG_ONENAND)	+= samsung.o
else
COBJS-y				:= onenand_spl.o
endif

写到这里我才发现,onenand_uboot.c这个文件是否编译,与是否定义CONFIG_CMD_ONENAND这个宏有关,而这个宏之前我并没有注释掉,所以才会出现与课程不同的错误,因此这里注释掉这个宏即可。

(5)重新编译出现错误:

onenand.c: In function 'onenand_board_init':
onenand.c:18: error: 'CONFIG_SYS_ONENAND_BASE' undeclared (first use in this function)
onenand.c:18: error: (Each undeclared identifier is reported only once
onenand.c:18: error: for each function it appears in.)
make[1]: *** [onenand.o] 错误 1
make[1]:正在离开目录 `/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/board/samsung/goni'
make: *** [board/samsung/goni/libgoni.o] 错误 2
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10# 

该错误提示没有定义CONFIG_SYS_ONENAND_BASE这个宏。之所以需要定义这个宏,是因为onenand.c文件的编译与这个宏有关。那么我们只要不编译onenand.c这个文件(本来我们就不需要这个文件),就不会与CONFIG_SYS_ONENAND_BASE这个宏扯上关系。所以解决方法是修改与onenand.c这个文件同一目录下的Makefile文件(/board/samsung/goni/Makefile):

include $(TOPDIR)/config.mkLIB     = $(obj)lib$(BOARD).o#COBJS-y := goni.o onenand.o
COBJS-y := goni.o

(6)重新编译可以通过,烧录运行现象如下(可能与课程现象有差异,与板子具体状况有关):

这说明我们已经进入uboot的命令行了。

但是在uboot命令行下通过set命令定义一个环境变量后,使用save命令时会报错:

Goni # set abc 123
Goni # save
Saving Environment to MMC...
Writing to MMC(0)... Error detected in status(0x208010)!
mmc write failed
failed
Goni # 

另外使用ping命令时,会提示不识别ping命令:

Goni # ping 192.168.1.141
Unknown command 'ping' - try 'help'
Goni #

这说明SD/MMC卡没有被正确地初始化(SD/MMC控制器没有被正确地初始化,或者SD/MMC设备驱动没有被注册),网卡也没有被正确地初始化(SROM控制器没有被正确地初始化,或者网卡设备驱动没有被注册);又或者它们的驱动没有被正确地移植。这两个说法含义不同,需要注意区分:想要SD/MMC卡与网卡正常工作,需要uboot提供关于SD/MMC卡与网卡的初始化代码,另外也需要/driver目录中的驱动。

十五、移植SD/MMC驱动

15.1 概览SD/MMC驱动

对于X210开发板,SD指外部的SD卡,MMC指内部的eMMC即iNand,SoC都是通过SD/MMC控制器对两者进行访问的。

这里可以简单理解为:SD卡驱动、MMC驱动、SD\MMC驱动,三者同义。

下面这段代码的mmc_initialize函数,对SD/MMC控制器进行了初始化:

#ifdef CONFIG_GENERIC_MMC //这个宏已经在s5p_goni.h中定义了puts("MMC:   ");mmc_initialize(gd->bd);
#endif

其中宏CONFIG_GENERIC_MM已经在s5p_goni.h文件中定义了(在第十一节中SCRT之所以没有显示“MMC:   ”的字样,是因为卡在了第十二节中提到的onenand_init函数里,所以第十二节中解决这个问题后才会显示这个字样)。

我们根据“Error detected in status”这些关键词反过来跟踪一下mmc_initialize函数:

|-board_init_r                 // 在/arch/arm/lib/board.c文件中
|----mmc_initialize            // 在/drivers/mmc/mmc.c文件中
|-------board_mmc_init         // 在/board/samsung/goni/goni.c文件中
|----------s5p_mmc_init        // 在/arch/arm/include/asm/arch-s5pc1xx/mmc.h
|-------------s5p_sdhci_init   // 在/drivers/mmc/s5p_sdhci.c文件中
|-------print_mmc_devices      // 在/drivers/mmc/mmc.c文件中
|-------do_preinit                         // 在/drivers/mmc/mmc.c文件中
|----------mmc_start_init                  // 在/drivers/mmc/mmc.c文件中
|-------------mmc_go_idle                  // 在/drivers/mmc/mmc.c文件中
|----------------mmc_send_cmd              // 在/drivers/mmc/mmc.c文件中
|-------------------sdhci_send_command     // 在/drivers/mmc/sdhci.c文件中
|----------------------sdhci_transfer_data // 在/drivers/mmc/sdhci.c文件中

该函数涉及/drivers/mmc/mmc.c、/drivers/mmc/sdhci.c、/arch/arm/include/asm/arch-s5pc1xx/mmc.h、/board/samsung/goni/goni.c

其中/drivers/mmc/sdhci.c文件中的所有函数,就构成了S5PV210的SD/MMC控制器的驱动。这个文件里的函数是三星公司的工程师写的,用以控制S5PV210的SD/MMC控制器与外部的SD/MMC进行通信的。这就是所谓的驱动。

(1)我们分析一下/board/samsung/goni/goni.c文件中的board_mmc_init函数。

该函数首先初始化一些GPIO引脚,然后调用s5p_mmc_init函数初始化SD/MMC控制器。对于通道2也就是SD卡,该函数首先判断有没有插SD卡,如果有才进行初始化。这里把判断部分的代码删除,表示无论是否插进SD卡,都初始化SD/MMC控制器:

#ifdef CONFIG_GENERIC_MMC
int board_mmc_init(bd_t *bis)
{int i, ret, ret_sd = 0;/*SD卡通道0的初始化(也就是iNand)*//* MASSMEMORY_EN: XMSMDATA7: GPJ2[7] output high */s5p_gpio_direction_output(&s5pc110_gpio->j2, 7, 1);/** MMC0 GPIO* GPG0[0]	SD_0_CLK* GPG0[1]	SD_0_CMD* GPG0[2]	SD_0_CDn	-> Not used* GPG0[3:6]	SD_0_DATA[0:3]*/for (i = 0; i < 7; i++) {if (i == 2)continue;/* GPG0[0:6] special function 2 */s5p_gpio_cfg_pin(&s5pc110_gpio->g0, i, 0x2);/* GPG0[0:6] pull disable */s5p_gpio_set_pull(&s5pc110_gpio->g0, i, GPIO_PULL_NONE);/* GPG0[0:6] drv 4x */s5p_gpio_set_drv(&s5pc110_gpio->g0, i, GPIO_DRV_4X);}ret = s5p_mmc_init(0, 4);if (ret)error("MMC: Failed to init MMC:0.\n");/*SD卡通道2的初始化(也就是SD卡)*/#if 0  //注释掉原来的/** SD card (T_FLASH) detect and init* T_FLASH_DETECT: EINT28: GPH3[4] input mode*/s5p_gpio_cfg_pin(&s5pc110_gpio->h3, 4, GPIO_INPUT);s5p_gpio_set_pull(&s5pc110_gpio->h3, 4, GPIO_PULL_UP);if (!s5p_gpio_get_value(&s5pc110_gpio->h3, 4)) {for (i = 0; i < 7; i++) {if (i == 2)continue;/* GPG2[0:6] special function 2 */s5p_gpio_cfg_pin(&s5pc110_gpio->g2, i, 0x2);/* GPG2[0:6] pull disable */s5p_gpio_set_pull(&s5pc110_gpio->g2, i, GPIO_PULL_NONE);/* GPG2[0:6] drv 4x */s5p_gpio_set_drv(&s5pc110_gpio->g2, i, GPIO_DRV_4X);}ret_sd = s5p_mmc_init(2, 4);if (ret_sd)error("MMC: Failed to init SD card (MMC:2).\n");}
#endif
//重新写这个for (i = 0; i < 7; i++) {if (i == 2)continue;/* GPG2[0:6] special function 2 */s5p_gpio_cfg_pin(&s5pc110_gpio->g2, i, 0x2);/* GPG2[0:6] pull disable */s5p_gpio_set_pull(&s5pc110_gpio->g2, i, GPIO_PULL_NONE);/* GPG2[0:6] drv 4x */s5p_gpio_set_drv(&s5pc110_gpio->g2, i, GPIO_DRV_4X);}ret_sd = s5p_mmc_init(2, 4);if (ret_sd)error("MMC: Failed to init SD card (MMC:2).\n");return ret & ret_sd;
}
#endif

重新编译与烧录运行,运行现象如下:

(2)我们接着分析在/drivers/mmc/mmc.c文件中的print_mmc_devices函数。

该函数用以打印 “SAMSUNG SDHCI: 0, SAMSUNG SDHCI: 1”,如下所示:

void print_mmc_devices(char separator)
{struct mmc *m;struct list_head *entry;list_for_each(entry, &mmc_devices) {m = list_entry(entry, struct mmc, link);//打印 “SAMSUNG SDHCI: 0, SAMSUNG SDHCI: 1”printf("%s: %d", m->name, m->block_dev.dev);if (entry->next != &mmc_devices)printf("%c ", separator);}printf("\n");
}

(3)我们接着分析在/drivers/mmc/sdhci.c文件中的 sdhci_transfer_data 函数。

经过搜寻错误提示的关键词“Error detected in status”,可知它出现在sdhci_transfer_data​​​函数中:

static int sdhci_transfer_data(struct sdhci_host *host, struct mmc_data *data,unsigned int start_addr)
{unsigned int stat, rdy, mask, timeout, block = 0;
#ifdef CONFIG_MMC_SDMAunsigned char ctrl;ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);ctrl &= ~SDHCI_CTRL_DMA_MASK;sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
#endiftimeout = 1000000;rdy = SDHCI_INT_SPACE_AVAIL | SDHCI_INT_DATA_AVAIL;mask = SDHCI_DATA_AVAILABLE | SDHCI_SPACE_AVAILABLE;do {stat = sdhci_readl(host, SDHCI_INT_STATUS);if (stat & SDHCI_INT_ERROR) {printf("Error detected in status(0x%X)!\n", stat);//出现在这里return -1;}if (stat & rdy) {if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & mask))continue;sdhci_writel(host, rdy, SDHCI_INT_STATUS);sdhci_transfer_pio(host, data);data->dest += data->blocksize;if (++block >= data->blocks)break;}
#ifdef CONFIG_MMC_SDMAif (stat & SDHCI_INT_DMA_END) {sdhci_writel(host, SDHCI_INT_DMA_END, SDHCI_INT_STATUS);start_addr &= ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1);start_addr += SDHCI_DEFAULT_BOUNDARY_SIZE;sdhci_writel(host, start_addr, SDHCI_DMA_ADDRESS);}
#endifif (timeout-- > 0)udelay(10);else {printf("Transfer data timeout\n");return -1;}} while (!(stat & SDHCI_INT_DATA_END));return 0;
}

这个函数出错,说明SoC的SD/MMC控制器和外部的SD或内部的iNand之间的数据传输出现了问题。

从代码的判断语句可知,之所以输出这个错误提示,是因为控制器内部有一个中断状态错误标志被置位了。

15.2 移植SD/MMC驱动

移植SD/MMC驱动,这里有三种解决方案:

第一种,逐行分析SD/MMC驱动的实现,发现错误所在并修改代码以解决问题。这种方法要求移植人员对SD/MMC通信协议与SD/MMC控制器非常熟悉。这种方法可以用以学习SD/MMC驱动。

第二种,将三星官方uboot的移植版本中的SD/MMC驱动,经过简单移植后,替换掉2013.10版本uboot的MMC驱动。这种方法适合用以快速解决问题,属于投机取巧的方法。

第三种,参考三星官方uboot的移植版本中的MMC驱动,来修改2013.10版本uboot的MMC驱动。这种方法综合了第一种和第二种方法,既能解决问题,也能学习SD/MMC驱动。

为了简单与快速解决问题,这里我们选择第二种方案。

15.2.1 分析不同版本uboot的SD/MMC驱动差异

2013.10版本的uboot中(以下简称“2013.10版本”),与SD/MMC驱动相关的文件主要有:

  • /drivers/mmc/mmc.c(驱动文件)
  • /drivers/mmc/sdhci.c(驱动文件)
  • /drivers/mmc/s5p_sdhci.c(驱动文件)
  • /board/samsung/goni/goni.c(uboot提供的SD/MMC控制器初始化代码文件)

三星官方uboot的移植版本中(以下简称“三星版本”),与SD/MMC驱动相关的文件主要有:

  • /drivers/mmc/mmc.c(驱动文件)
  • /drivers/mmc/s3c_hsmmc.c(驱动文件)
  • /cpu/s5pc11x/cpu.c(uboot提供的SD/MMC控制器初始化代码文件)
  • /cpu/s5pc11x/setup_hsmmc.c(uboot提供的SD/MMC控制器初始化代码文件)

经过分析发现,SD/MMC驱动要工作需要包含两部分内容:一部分是uboot提供的SD/MMC控制器初始化代码文件,比如GPIO初始化、时钟初始化(因为SD/MMC控制器为SD/MMC卡的操作提供时钟,在红色文件中);另一部分是/drivers/mmc目录下的驱动文件,即上面黑色的文件。

15.2.2 移植工作

如何解决每次编译时间很长这个问题?当我们只是修改普通的代码文件(比如.c文件),没有修改s5p_goni.h、Makefile文件或者其他项目配置文件时,编译时不需要“make distclean”,直接“make”就好。

步骤1:将三星版本的SD/MMC驱动文件拷贝到2013.10版本的SD/MMC驱动文件目录下,并修改该目录下的Makefile文件。

(1)首先,将三星版本的/drivers/mmc/mmc.c、s3c_hsmmc.c文件,拷贝或替换到2013.10版本的/drivers/mmc/目录中。

(2)然后,修改2013.10版本的/drivers/mmc/Makefile文件。主要工作是:在该Makefile文件中去掉2013.10版本原来的SD/MMC驱动文件有关的条目,然后添加那些复制文件的条目。

一种比较简单粗暴的修改方法如下所示:

#在/drivers/mmc/Makefile文件中#注释掉原来的
#COBJS-$(CONFIG_GENERIC_MMC) += mmc.o
#COBJS-$(CONFIG_SDHCI) += sdhci.o
#COBJS-$(CONFIG_S5P_SDHCI) += s5p_sdhci.o#新添加的内容
COBJS-$(CONFIG_GENERIC_MMC) += mmc.o
COBJS-$(CONFIG_S3C_HSMMC) += s3c_hsmmc.o
//在s5p_goni.h文件中//新添加一个宏定义
#define CONFIG_S3C_HSMMC 

 正式的修改方法如下所示:

#在/drivers/mmc/Makefile文件中#不用注释掉原来的
COBJS-$(CONFIG_GENERIC_MMC) += mmc.o
COBJS-$(CONFIG_SDHCI) += sdhci.o
COBJS-$(CONFIG_S5P_SDHCI) += s5p_sdhci.o#新添加的内容
#COBJS-$(CONFIG_GENERIC_MMC) += mmc.o
COBJS-$(CONFIG_S3C_HSMMC) += s3c_hsmmc.o
//在s5p_goni.h文件中/* MMC */
#define CONFIG_GENERIC_MMC
#define CONFIG_MMC//关闭宏
//#define CONFIG_SDHCI
//#define CONFIG_S5P_SDHCI//添加宏定义
#define CONFIG_S3C_HSMMC 

注意上述操作之后的2013.10版本的SD/MMC驱动文件包括:

  • /drivers/mmc/mmc.c(新的)
  • /drivers/mmc/s3c_hsmmc.c(新的)
  • /drivers/mmc/sdhci.c(原来的驱动文件,因为不被编译而可以删掉)
  • /drivers/mmc/s5p_sdhci.c(原来的驱动文件,因为不被编译而可以删掉)

步骤2:另外将三星版本的smdkv210single.h文件中与SD/MMC有关的宏定义,拷贝到2013.10版本的s5p_goni.h文件中:

/* IROM specific data */
#define SDMMC_BLK_SIZE        (0xD003A500)
#define COPY_SDMMC_TO_MEM     (0xD003E008)/* The macro for MMC channel 0 is defined by default and can't be undefined */
#define USE_MMC0
#define USE_MMC2
#define MMC_MAX_CHANNEL		4

步骤3:修改2013.10版本的SD/MMC控制器初始化代码文件,即/board/samsung/goni/goni.c文件。

2013.10版本的/drivers/mmc/mmc.c文件(新的)中的mmc_initialize函数,包含下面的代码:

	if (board_mmc_init(bis) < 0)cpu_mmc_init(bis);

其中board_mmc_init函数(位于/board/samsung/goni/goni.c文件)有实质内容,而cpu_mmc_init函数(位于/drivers/mmc/mmc.c文件)只是单纯地返回-1。

三星版本/drivers/mmc/mmc.c文件中的mmc_initialize函数刚好反过来,board_mmc_init函数单纯地返回-1(从而可以执行cpu_mmc_init函数),而cpu_mmc_init函数有实质内容。

与上面表述有关的内容,见Linux内核强符号和弱符号讲解 - 嵌入式技术 - 电子发烧友网。

所以我们注释掉2013.10版本的board_mmc_init函数的内部代码,将三星版本的cpu_mmc_init函数的内部代码,拷贝到2013.10版本的board_mmc_init函数中。

#ifdef CONFIG_GENERIC_MMC
int board_mmc_init(bd_t *bis)
{
#if 0	//原来的代码内容
#endif#ifdef CONFIG_S3C_HSMMCsetup_hsmmc_clock();setup_hsmmc0_cfg_gpio();return smdk_s3c_hsmmc_init();
#elsereturn 0;
#endif
}

步骤4:将三星版本的SD/MMC控制器初始化代码,拷贝到2013.10版本中,并修改相应的 Makefile 文件。

(1)首先,将三星版本的/cpu/s5pc11x/setup_hsmmc.c文件,拷贝到2013.10版本的/board/samsung/goni/目录下。

(2)然后,修改2013.10版本的/board/samsung/goni/Makefile文件。

#COBJS-y := goni.o onenand.o
#COBJS-y := goni.o
COBJS-y := goni.o setup_hsmmc.o

步骤5:代码浏览及修补

经过上面修改之后,不用急着编译,因为目前只是整体复制与修改而已,很多细节没有考虑到,因此此时编译的话会有很多错误的。

我们按照代码运行时的流程来逐步浏览代码,看哪里需要修补(主要是看那些复制过来的文件,哪里是黑色的,因为黑色表示没定义)。

(1)修改2013.10版本/board/samsung/goni/setup_hsmmc.c文件中的setup_hsmmc_clock函数,将代码中的get_MPLL_CLK()改为get_pll_clk(MPLL)(其实好像不修改也行,因为之前我曾移植过这个函数):

    /* MMC0 clock div */tmp = CLK_DIV4_REG & ~(0x0000000f);//clock = get_MPLL_CLK()/1000000;clock = get_MPLL_CLK(MPLL)/1000000;

(2)将三星版本的/include/mmc.h替换掉2013.10版本的/include/mmc.h(因为2013.10版本的SD/MMC驱动文件缺少一些宏,而这些宏本该定义在2013.10版本的/include/mmc.h文件中的)。

(3)将三星版本的/include/s3c_hsmmc.h文件复制到2013.10版本的/include/目录下。

步骤6:编译与解决报错

经过上面的修补之后,基本可以进行编译了,编译出错再去解决。

(1)重新编译,报错如下:

cmd_mmc.c: In function 'do_mmcinfo':
cmd_mmc.c:107: warning: implicit declaration of function 'get_mmc_num'
cmd_mmc.c: In function 'do_mmcops':
cmd_mmc.c:192: error: 'struct mmc' has no member named 'has_init'
cmd_mmc.c:236: error: 'PART_ACCESS_MASK' undeclared (first use in this function)
cmd_mmc.c:236: error: (Each undeclared identifier is reported only once
cmd_mmc.c:236: error: for each function it appears in.)
cmd_mmc.c:253: error: 'struct mmc' has no member named 'part_config'
cmd_mmc.c:253: error: 'MMCPART_NOAVAILABLE' undeclared (first use in this function)
cmd_mmc.c:258: error: 'struct mmc' has no member named 'part_num'
cmd_mmc.c:259: warning: implicit declaration of function 'mmc_switch_part'
cmd_mmc.c:261: error: 'struct mmc' has no member named 'part_num'
cmd_mmc.c:268: error: 'struct mmc' has no member named 'part_config'
cmd_mmc.c:272: error: 'struct mmc' has no member named 'part_num'
cmd_mmc.c:376: warning: implicit declaration of function 'mmc_getwp'
make[1]: *** [cmd_mmc.o] 错误 1
make[1]:正在离开目录 `/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/common'
make: *** [common/libcommon.o] 错误 2
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10# 

报错与cmd_mmc.c文件有关,这是因为cmd_mmc.c和MMC驱动密切相关,因此修改驱动后这个文件的内容也要跟着修改。

这里的解决方法,是将三星版本的/common/cmd_mmc.c文件,替换掉2013.10版本的/common/cmd_mmc.c文件。

(2)重新编译,报错如下:

s3c_hsmmc.c:35: fatal error: regs.h: No such file or directory
compilation terminated.
make[1]: *** 没有规则可以创建“libmmc.o”需要的目标“.depend”。 停止。
make[1]:正在离开目录 `/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/drivers/mmc'
make: *** [drivers/mmc/libmmc.o] 错误 2
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10# 

这里的解决方法是,将/drivers/mmc/s3c_hsmmc.c文件中的 #include<regs.h>注释掉,然后添加#include <s5pc110.h>(如果不添加这一行,后面会把报错缺乏一些宏定义,而那些宏定义就定义在s5pc110.h文件中,这里为了简单起见,没把这过程体现出来)。

#include <config.h>
#include <common.h>
#include <command.h>
#include <mmc.h>
#include <part.h>
#include <malloc.h>
#include <asm/io.h>
//#include <regs.h>  //注释掉
#include <s5pc110.h> //新添加

(3)重新编译,报错如下:

mmc_write.c: In function 'mmc_erase_t':
mmc_write.c:29: error: 'SD_CMD_ERASE_WR_BLK_START' undeclared (first use in this function)
mmc_write.c:29: error: (Each undeclared identifier is reported only once
mmc_write.c:29: error: for each function it appears in.)
mmc_write.c:30: error: 'SD_CMD_ERASE_WR_BLK_END' undeclared (first use in this function)
mmc_write.c:32: error: 'MMC_CMD_ERASE_GROUP_START' undeclared (first use in this function)
mmc_write.c:33: error: 'MMC_CMD_ERASE_GROUP_END' undeclared (first use in this function)
mmc_write.c:36: error: 'struct mmc_cmd' has no member named 'cmdidx'
mmc_write.c:37: error: 'struct mmc_cmd' has no member named 'cmdarg'
mmc_write.c:44: error: 'struct mmc_cmd' has no member named 'cmdidx'
mmc_write.c:45: error: 'struct mmc_cmd' has no member named 'cmdarg'
mmc_write.c:51: error: 'struct mmc_cmd' has no member named 'cmdidx'
mmc_write.c:51: error: 'MMC_CMD_ERASE' undeclared (first use in this function)
mmc_write.c:52: error: 'struct mmc_cmd' has no member named 'cmdarg'
mmc_write.c:52: error: 'SECURE_ERASE' undeclared (first use in this function)
mmc_write.c: In function 'mmc_berase':
mmc_write.c:76: error: 'struct mmc' has no member named 'erase_grp_size'
mmc_write.c:76: error: 'struct mmc' has no member named 'erase_grp_size'
mmc_write.c:80: error: 'struct mmc' has no member named 'erase_grp_size'
mmc_write.c:80: error: 'struct mmc' has no member named 'erase_grp_size'
mmc_write.c:81: error: 'struct mmc' has no member named 'erase_grp_size'
mmc_write.c:82: error: 'struct mmc' has no member named 'erase_grp_size'
mmc_write.c:85: error: 'struct mmc' has no member named 'erase_grp_size'
mmc_write.c:86: error: 'struct mmc' has no member named 'erase_grp_size'
mmc_write.c: In function 'mmc_write_blocks':
mmc_write.c:117: error: 'struct mmc_cmd' has no member named 'cmdidx'
mmc_write.c:119: error: 'struct mmc_cmd' has no member named 'cmdidx'
mmc_write.c:122: error: 'struct mmc_cmd' has no member named 'cmdarg'
mmc_write.c:124: error: 'struct mmc_cmd' has no member named 'cmdarg'
mmc_write.c:141: warning: implicit declaration of function 'mmc_host_is_spi'
mmc_write.c:142: error: 'struct mmc_cmd' has no member named 'cmdidx'
mmc_write.c:143: error: 'struct mmc_cmd' has no member named 'cmdarg'
mmc_write.c: In function 'mmc_bwrite':
mmc_write.c:170: error: 'struct mmc' has no member named 'b_max'
mmc_write.c:170: error: 'struct mmc' has no member named 'b_max'
make[1]: *** [mmc_write.o] 错误 1
make[1]:正在离开目录 `/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/drivers/mmc'
make: *** [drivers/mmc/libmmc.o] 错误 2
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10# 

报错与/drivers/mmc/mmc_write.c这个文件有关。

这是因为mmc_write.c这个文件和替换之前的mmc.c文件相关。我们在13.3.2的(1)中将mmc.c替换掉了,所以这个文件编译报错。

这里的解决方案就是修改/drivers/mmc/Makefile文件,即去掉这个文件的依赖,使它不被编译。

ifdef CONFIG_SPL_BUILD
COBJS-$(CONFIG_SPL_MMC_BOOT) += fsl_esdhc_spl.o
else
#注释掉
#COBJS-$(CONFIG_GENERIC_MMC) += mmc_write.o
endif

(4)重新编译,报错如下:

setup_hsmmc.c:2: fatal error: regs.h: No such file or directory
compilation terminated.
make[1]: *** 没有规则可以创建“all”需要的目标“.depend”。 停止。
make[1]:正在离开目录 `/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/board/samsung/goni'
make: *** [board/samsung/goni/libgoni.o] 错误 2
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10# 

这里的解决方法是,将/board/samsung/goni/setup_hsmmc.c文件中的 #include<regs.h>注释掉,然后添加#include <s5pc110.h>(如果不添加这一行,后面会把报错缺乏一些宏定义,而那些宏定义就定义在s5pc110.h文件中,这里为了简单起见,没把这过程体现出来)。

#include <common.h>
//#include <regs.h>  //注释掉
#include <s5pc110.h> //新添加
#include <asm/io.h>
#include <mmc.h>
#include <s3c_hsmmc.h>

(5)重新编译,报错如下:

setup_hsmmc.c: In function 'setup_hsmmc_clock':
setup_hsmmc.c:20: warning: implicit declaration of function 'get_MPLL_CLK'
setup_hsmmc.c:20: error: 'MPLL' undeclared (first use in this function)
setup_hsmmc.c:20: error: (Each undeclared identifier is reported only once
setup_hsmmc.c:20: error: for each function it appears in.)
make[1]: *** [setup_hsmmc.o] 错误 1
make[1]:正在离开目录 `/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/board/samsung/goni'
make: *** [board/samsung/goni/libgoni.o] 错误 2
root@ubuntu:/home/xjh/iot/embedded_basic/uboot/u-boot-2013.10# 

这里出错是因为MPLL这个宏没有定义。MPLL其实在/arch/arm/include/asm/arch-s5pc1xx/clk.h文件中有定义的,只是setup_hsmmc.c文件中没有把这个文件包含进来。

解决方法是,在/board/samsung/goni/setup_hsmmc.c文件中添加头文件clk.h,如下所示:

#include <common.h>
//#include <regs.h>
#include <s5pc110.h>
#include <asm/io.h>
#include <mmc.h>
#include <s3c_hsmmc.h>
#include <asm/arch/clk.h> //添加这个,注意格式

(6)重新编译,报错如下:

home/xjh/iot/embedded_basic/uboot/u-boot-2013.10/board/samsung/goni/goni.c:151: undefined reference to `setup_hsmmc0_cfg_gpio'

这个错误提示找不到setup_hsmmc0_cfg_gpio这个函数。问题是这个函数位于setup_hsmmc.c文件中,我已经将这个文件拷贝到/boar/samsung/goni/目录中了,为何提示找不到呢?折腾了很久,后来我把/board/samsung/goni/goni.c文件中该函数的函数名改为hhh(),以及把setup_hsmmc.c文件中该函数的函数名改为hhh(),然后居然就编译通过了!什么鬼!后来我一想,是不是两个文件中的函数名不同导致的错误?一看,果然如此!

setup_hsmmc_cfg_gpio //board/samsung/goni/setup_hsmmc.c文件中的
setup_hsmmc0_cfg_gpio //board/samsung/goni/goni.c文件中的

在/board/samsung/goni/goni.c文件中,将setup_hsmmc0_cfg_gpio 改为setup_hsmmc_cfg_gpio 就好。

(7)重新编译与烧录运行,运行现象如下(在(6)中我把各时钟频率的打印代码删除了):

 

关于“MMC:   unrecognised EXT_CSD structure version 7”的解释与解决方法,见移植三星官方的uboot到X210开发板的第二节第7点。

(8)重新编译与烧录运行,运行现象如下:

15.2.3 测试与验证

接下来测试SD/MMC驱动是否能正常工作。

(1)列出MMC设备:mmc list

Goni # help mmc       
mmc - MMC sub systemUsage:
mmc read <device num> addr blk# cnt
mmc write <device num> addr blk# cnt
mmc rescan <device num>
mmc list - list available devicesGoni # mmc list
S3C_HSMMC0_dev0S3C_HSMMC2_dev1
Goni #

(2)测试读SD卡(或iNand)功能

首先,使用命令“mmc read 1 30000000 1# 1”读取SD卡扇区1的数据到内存地址0x30000000处。

  • mmc read 1,其中1表示外部SD卡,如果是0则表示内部的iNand。
  • 30000000,表示读取到内存地址30000000。
  • 1#,表示读取的起始扇区编号为1。
  • 1表示读取1个扇区的数据。
Goni # mmc read 1 30000000 1# 1MMC read: dev # 1, block # 1, count 1 ...1 blocks read: OK
Goni #

然后,使用命令“md 30000000”查看内存地址0x300000的数据。

Goni # md 30000000
30000000: 00002000 00000000 000da3d6 00000000    . ..............
30000010: ea000015 e59ff014 e59ff014 e59ff014    ................
30000020: e59ff014 e59ff014 e59ff014 e59ff014    ................
30000030: 34800180 348001e0 34800240 348002a0    ...4...4@..4...4
30000040: 34800300 34800360 348003c0 12345678    ...4`..4...4xV4.
30000050: 34800000 0001eddc 000214a0 00022624    ...4........$&..
30000060: 3481edec 00000000 0badc0de eb000031    ...4........1...
30000070: e10f0000 e200101f e331001a 13c0001f    ..........1.....
30000080: 13800013 e38000c0 e129f000 ee110f10    ..........).....
30000090: e3c00a02 ee010f10 e59f0368 ee0c0f10    ........h.......
300000a0: eb000025 eb000032 eb000023 eb000030    %...2...#...0...
300000b0: e59f1354 e59f2354 e5821000 e51fd074    T...T#......t...
300000c0: e24dd00c e3a0b000 e59f0344 e1cf1000    ..M.....D.......
300000d0: e51f2088 e1c22000 e1510002 0a000006    . ... ....Q.....
300000e0: e59f0330 e5901000 e59f232c e1510002    0.......,#....Q.
300000f0: 0affffff eb0002d8 eaffffff e51f00a4    ................
Goni # 

最后,我们使用winhex打开u-boot.bin:

通过观察可知两者显示的内容相同(第一行不同,为何),这说明读SD卡功能正常。

(3)测试写SD卡(或iNand)功能

首先在内存中构建一段数据,然后写入SD卡中。

Goni # help mmc
mmc - MMC sub systemUsage:
mmc read <device num> addr blk# cnt
mmc write <device num> addr blk# cnt
mmc rescan <device num>
mmc list - list available devicesGoni # mmc read 0 31000000 1# 1
MMC read: dev # 0, block # 1, count 1 ...1 blocks read: OK
Goni # md 31000000
31000000: 6f620030 7274746f 3d656361 65746573    0.boottrace=sete
31000010: 6f20766e 20737470 74696e69 6c6c6163    nv opts initcall
31000020: 6265645f 203b6775 206e7572 746f6f62    _debug; run boot
31000030: 00646d63 736e6f63 3d656c6f 736e6f63    cmd.console=cons
31000040: 3d656c6f 53797474 2c324341 32353131    ole=ttySAC2,1152
31000050: 386e3030 616c6600 6f626873 733d746f    00n8.flashboot=s
31000060: 62207465 61746f6f 20736772 746f6f72    et bootargs root
31000070: 65642f3d 746d2f76 6f6c6264 7b246b63    =/dev/mtdblock${
31000080: 746f6f62 636f6c62 72207d6b 66746f6f    bootblock} rootf
31000090: 70797473 7b243d65 746f6f72 79747366    stype=${rootfsty
310000a0: 207d6570 2e696275 3d64746d 62757b24    pe} ubi.mtd=${ub
310000b0: 6f6c6269 207d6b63 2e696275 3d64746d    iblock} ubi.mtd=
310000c0: 62752033 746d2e69 20363d64 706f7b24    3 ubi.mtd=6 ${op
310000d0: 207d7374 636c7b24 666e6964 24207d6f    ts} ${lcdinfo} $
310000e0: 6e6f637b 656c6f73 7b24207d 696d656d    {console} ${memi
310000f0: 7d6f666e 6d7b2420 61706474 7d737472    nfo} ${mtdparts}
Goni # help mw
mw - memory write (fill)Usage:
mw [.b, .w, .l] address value [count]Goni # mw.l 31000000 12345678
Goni # mw.l 31000004 87654321   
Goni # md 31000000
31000000: 12345678 87654321 3d656361 65746573    xV4.!Ce.ace=sete
31000010: 6f20766e 20737470 74696e69 6c6c6163    nv opts initcall
31000020: 6265645f 203b6775 206e7572 746f6f62    _debug; run boot
31000030: 00646d63 736e6f63 3d656c6f 736e6f63    cmd.console=cons
31000040: 3d656c6f 53797474 2c324341 32353131    ole=ttySAC2,1152
31000050: 386e3030 616c6600 6f626873 733d746f    00n8.flashboot=s
31000060: 62207465 61746f6f 20736772 746f6f72    et bootargs root
31000070: 65642f3d 746d2f76 6f6c6264 7b246b63    =/dev/mtdblock${
31000080: 746f6f62 636f6c62 72207d6b 66746f6f    bootblock} rootf
31000090: 70797473 7b243d65 746f6f72 79747366    stype=${rootfsty
310000a0: 207d6570 2e696275 3d64746d 62757b24    pe} ubi.mtd=${ub
310000b0: 6f6c6269 207d6b63 2e696275 3d64746d    iblock} ubi.mtd=
310000c0: 62752033 746d2e69 20363d64 706f7b24    3 ubi.mtd=6 ${op
310000d0: 207d7374 636c7b24 666e6964 24207d6f    ts} ${lcdinfo} $
310000e0: 6e6f637b 656c6f73 7b24207d 696d656d    {console} ${memi
310000f0: 7d6f666e 6d7b2420 61706474 7d737472    nfo} ${mtdparts}Goni # mmc write 0 31000000 1# 1
MMC write: dev # 0, block # 1, count 1 ... 1 blocks written: OK
Goni # 

接着读到内存中,并和原来构造的数据比对。

Goni # mmc read 0 31000000 1# 1
MMC read: dev # 0, block # 1, count 1 ...1 blocks read: OK
Goni # md 31000000
31000000: 12345678 87654321 3d656361 65746573    xV4.!Ce.ace=sete
31000010: 6f20766e 20737470 74696e69 6c6c6163    nv opts initcall
31000020: 6265645f 203b6775 206e7572 746f6f62    _debug; run boot
31000030: 00646d63 736e6f63 3d656c6f 736e6f63    cmd.console=cons
31000040: 3d656c6f 53797474 2c324341 32353131    ole=ttySAC2,1152
31000050: 386e3030 616c6600 6f626873 733d746f    00n8.flashboot=s
31000060: 62207465 61746f6f 20736772 746f6f72    et bootargs root
31000070: 65642f3d 746d2f76 6f6c6264 7b246b63    =/dev/mtdblock${
31000080: 746f6f62 636f6c62 72207d6b 66746f6f    bootblock} rootf
31000090: 70797473 7b243d65 746f6f72 79747366    stype=${rootfsty
310000a0: 207d6570 2e696275 3d64746d 62757b24    pe} ubi.mtd=${ub
310000b0: 6f6c6269 207d6b63 2e696275 3d64746d    iblock} ubi.mtd=
310000c0: 62752033 746d2e69 20363d64 706f7b24    3 ubi.mtd=6 ${op
310000d0: 207d7374 636c7b24 666e6964 24207d6f    ts} ${lcdinfo} $
310000e0: 6e6f637b 656c6f73 7b24207d 696d656d    {console} ${memi
310000f0: 7d6f666e 6d7b2420 61706474 7d737472    nfo} ${mtdparts}
Goni # 

可见两者相同,这说明写SD卡功能正常。

由上面的测试结果可知,内部的iNand和外部的SD卡均能顺利读写,SD/MMC驱动移植成功。

上面的移植过程中,我没有关注代码的细节。后期可以看看相关代码,比如它具体是怎么初始化的,是不是同时初始化了SD与iNand两个通道,利用save保存环境变量时,是保存到SD还是iNand或两者都保存有呢?

十六、移植环境变量

16.1 环境变量的存储

16.1.1 测试环境变量的保存

首先我们来测试一下环境变量是否可以保存。

开机后通过set设置环境变量然后save,接着关机重启通过print来测试环境变量是否保存成功。

Goni # set test_varialbe abc123
Goni # save
Saving Environment to MMC...
Writing to MMC(0)... done      #从这里可知,它默认写到iNand中?
Goni # #开机重启后Goni # print#省略部分输出
test_varialbe=abc123#省略部分输出
Goni #

16.1.2 分析与保存环境变量有关的代码 

从上面执行save命令时的输出内容可知,环境变量ENV是保存到内部iNand中的。

这里为何不是保存到SD卡?这是因为save命令对应的env_mmc.c文件里的saveenv函数中,有这么一条代码:

struct mmc *mmc = find_mmc_device(CONFIG_SYS_MMC_ENV_DEV);

其中CONFIG_SYS_MMC_ENV_DEV这个宏的值被定义为0,所以它操作的是内部的iNand,将来保存环境变量时也是保存到iNand中。

那它具体保存在iNand中哪个位置呢?这个需要分析与环境变量保存的有关代码才可以得知。 

值得一提的是,虽然目前无法确定ENV一定要放在iNand中哪个位置,但有个原则必须遵守,就是iNand中同一个扇区只能放一种东西,不能叠加其他东西,否则会被覆盖。

以九鼎版本uboot为例进行说明。

九鼎版本uboot中,当利用fastboot命令烧录uboot到iNand中时,所遵循的形式与利用sd_fusing文件夹烧录uboot到SD卡一样,都是扇区0空闲,扇区1~16被uboot的BL1占用,扇区17-48空闲,扇区49~x被uboot的BL2占用。因此ENV不能放在iNand中的uboot分区(扇区1~16或者49~x)以及iNand中的kernel分区、rfs分区,其他扇区都可以商量。

九鼎版本uboot最终将iNand的扇区17~48作为环境变量分区,共32个扇区16KB,与uboot源码中定义的环境变量分区大小(x210_sd.h文件中的CFG_ENV_SIZE宏)相同,如下图左侧表格所示:

我们回到2013.10版本uoot的移植实验中。

目前2013.10版本uoot放在外部SD卡(SD2通道)中,而根据执行save时的显示可知ENV保存在内部iNand(SD0通道)中。因此两者井水不犯河水,即不管将ENV放在 iNand 中的哪个扇区,都不会破坏放在SD卡中的uboot,因此uboot都能工作。

但我们还是需要找到2013.10版本uoot所设置的ENV分区在iNand中的扇区位置,以便查看它所设置的ENV分区的扇区位置,与将来烧录uboot、kernel、rootfs到iNand中的扇区位置,是否发生冲突。如果发生冲突,则需要修改2013.10版本uboot中关于ENV分区的源码(其实根据分析没有所谓的ENV分区,只是save命令内部所调用的write_env函数指定了写在哪个扇区而已)。

我们在SI中搜索“saveenv”,发现该命令定义在cmd_nvedit.c文件中。一路追踪,在env_mmc.c文件中的saveenv函数中,有以下代码:

	printf("Writing to %sMMC(%d)... ", copy ? "redundant " : "",CONFIG_SYS_MMC_ENV_DEV);if (write_env(mmc, CONFIG_ENV_SIZE, offset, (u_char *)env_new)) {puts("failed\n");ret = 1;goto fini;}puts("done\n");

我们关注一下其中的write_env函数,其定义如下:

  • mmc,表示要往哪个mmc设备写入数据。
  • size,表示写入数据的大小。
  • offset,表示要将数据写入到哪里(相对于扇区0的偏移量,以Byte为单位)。
  • buffer,表示待写入的数据。
static inline int write_env(struct mmc *mmc, unsigned long size,unsigned long offset, const void *buffer)
{uint blk_start, blk_cnt, n;blk_start	= ALIGN(offset, mmc->write_bl_len) / mmc->write_bl_len;blk_cnt		= ALIGN(size, mmc->write_bl_len) / mmc->write_bl_len;n = mmc->block_dev.block_write(CONFIG_SYS_MMC_ENV_DEV, blk_start,blk_cnt, (u_char *)buffer);return (n == blk_cnt) ? 0 : -1;
}

我们追踪上面代码的变量offset,发现env_mmc.c文件的saveenv函数中的mmc_get_env_addr函数(该函数位于/common/env_mmc.c文件中)将它赋值为CONFIG_ENV_OFFSET这个宏(定义在/common/env_mmc.c文件中)。这个宏定义在的值为0,因此执行save命令时,ENV会被写如iNand的0扇区开始的32个扇区中。

16.2 移植工作

16.2.1 修改环境变量分区起始地址

上面分析可知,执行save命令时,ENV会被写如iNand的0扇区开始的32个扇区中。

但是将ENV写到这个扇区位置肯定是不行的,因为和uboot的BL1冲突了。

解决方案是修改/common/env_mmc.c文件中CONFIG_ENV_OFFSET的值,表示将ENV写到其他空闲扇区中。

一种可行的设置方案如下:

// 在/common/env_mmc.c文件中修改CONFIG_ENV_OFFSET的值
#if !defined(CONFIG_ENV_OFFSET)
//#define CONFIG_ENV_OFFSET 0
#defile CONFIG_ENV_OFFSET 17*512
#endif

这表示将环境变量写到扇区17开始的32个扇区内(也就是说iNand中的环境变量分区的扇区位置是17~48)。

这种设置方法,也是三星版本uboot源码中关于在SD卡(或iNand)中设置环境变量分区的方法,我们就是参考三星版本uboot源码才将 CONFIG_ENV_OFFSET 这个宏设置为17的。但是设置方法并不是唯一的。

三星版本uboot源码中关于SD卡(或iNand)的分区体现,在下面这行代码中:

#define MOVI_BL2_POS ((eFUSE_SIZE / MOVI_BLKSIZE) + MOVI_BL1_BLKCNT + MOVI_ENV_BLKCNT)//1               +       16        +       32 = 49

16.2.2 增删一些环境变量

(1)删除或修改一些环境变量

这步是可选操作,不删除也行,只是使用print打印环境变量时,显示的环境变量很多很繁杂。

s5p_goni.h文件中定义了这些环境变量对应的宏,因为我们在该文件中,将一些不常用的环境变量注释掉(直接#if 0 …#endif 注释掉,然后再从中挑选有用的),保留并修改以下内容:

#if 0/* Actual modem binary size is 16MiB. Add 2MiB for bad block handling */
#define MTDIDS_DEFAULT          "onenand0=samsung-onenand"
//省略一坨"opts=always_resume=1"
#endif//挑选有用的
#define CONFIG_ENV_OVERWRITE
#define CONFIG_SYS_CONSOLE_IS_IN_ENV#define CONFIG_BOOTCOMMAND "tftp 0x30008000 zImage-qt;bootm 0x30008000"
#define CONFIG_BOOTARGS "console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3"

(2)增加一些环境变量 

从三星smdkv210single.h文件中(因为它是参考)拷贝一些宏到s5p_goni.h文件中:

/*#define CONFIG_BOOTARGS    	"root=ramfs devfs=mount console=ttySA0,9600" */
#define CONFIG_ETHADDR		00:40:5c:26:0a:5b
#define CONFIG_NETMASK          255.255.255.0
#define CONFIG_IPADDR		192.168.0.20
#define CONFIG_SERVERIP		192.168.0.10
#define CONFIG_GATEWAYIP	192.168.0.1

16.3 测试与验证

经过上面修改后,重新编译与烧录运行。

接下来测试环境变量是否的确保存在iNand的17~48扇区,以及是否保存正确。

(1)首先要确保 iNand 中本来有没有环境变量。为了保险起见,对iNand的前49个扇区(0~48扇区)进行擦除,这样就可以确保里面没有之前保存过的环境变量。

Goni # mw.b 30000000 aa 4000
Goni # md 30000000
30000000: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
30000010: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
30000020: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
30000030: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
30000040: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
30000050: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
30000060: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
30000070: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
30000080: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
30000090: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
300000a0: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
300000b0: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
300000c0: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
300000d0: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
300000e0: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
300000f0: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................Goni # mmc write 0 30000000 0# 49 MMC write: dev # 0, block # 0, count 49 ... 49 blocks written: OK
Goni #
Goni # mmc read 0 34000000 17# 32MMC read: dev # 0, block # 17, count 32 ...32 blocks read: OK
Goni # md 34000000
34000000: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
34000010: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
34000020: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
34000030: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
34000040: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
34000050: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
34000060: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
34000070: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
34000080: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
34000090: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
340000a0: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
340000b0: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
340000c0: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
340000d0: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
340000e0: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
340000f0: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa    ................
Goni # 

(2)重启(如果不重启,之前那份已经被加载到内存中运行了),然后通过set命令随意修改一个环境变量(比如“set bootdelay 321”)作为标记,然后saveenv。

Goni # set bootdelay 321
Goni # save
Saving Environment to MMC...
Writing to MMC(0)... done
Goni # 

(3)重启,然后使用“mmc read 0 30000000 17# 32”命令,将iNand的扇区17开始的32个扇区读到内存30000000处,然后使用“md 30000000”命令查看显示区域的内容(如果不能显示全部,则可以将30000000改为更大一些的数字,或者“md 30000000 100”看100行的内容)。找到刚才修改的那个环境变量,看看它的值是否等于刚才修改的值。

由下面的测试结果可知,环境变量的确保存在iNand的17~48扇区,且保存正确。

(4)另外我觉得关于环境变量的使用,应该有这样的一个规则:如果iNand中的环境变量分区的环境变量可以使用,则优先使用iNand中的环境变量;如果iNand中没有或者校验和不通过,则使用SD卡环境变量分区的环境变量;如果连SD卡中也没有或者校验和不通过,则使用写死在uboot源码中的环境变量。为了验证这一点,在上面操作之后,我把iNand中的0~48扇区全部擦除,然后再以SD卡启动,然后打印bootdelay的值,看看它是否还是321。如果是,则说明保存时也会保存到SD卡的环境变量分区;如果不是,则说明只保存到iNand的环境变量分区。

测试表明,bootdelay的值不再是321而是1,这说明执行save时只会保存到iNand的环境变量分区中而不会保存到SD卡的环境变量分区中(其实这点通过分析代码也可以得知),另外也说明如果iNand中没有环境变量则会使用SD卡中的环境变量(这里是uboot源码中写死的环境变量)。

(6)经过上述所有操作之后,最后移植效果如下所示(iNand中0~48扇区已经被擦除,所以才会出现“…bad CRC…”的提示):

十七、移植网卡驱动

经过上面的移植,我们在uboot命令行下执行ping命令时,会提示找不到ping命令:

Goni # ping 192.168.1.141
Unknown command 'ping' - try 'help'
Goni # 

 这是因为还没有进行网卡相关的移植工作。 

17.1 添加网络支持

我们知道uboot可以通过条件编译来实现可配置、可裁剪。

比如在2013.10版本uboot的/arch/arm/lib/board.c文件中有以下代码:

#if defined(CONFIG_CMD_NET)puts("Net:   ");eth_initialize(gd->bd);
#if defined(CONFIG_RESET_PHY_R)debug("Reset Ethernet PHY\n");reset_phy();
#endif
#endif

在开发板配置文件s5p_goni.h文件中有如下代码,这说明2013.10版本uboot默认不支持网络:

/* Command definition */
#include <config_cmd_default.h>#undef CONFIG_CMD_FPGA
#undef CONFIG_CMD_MISC
//#undef CONFIG_CMD_NET    //注意这里是#undef,注释掉
#undef CONFIG_CMD_NFS
#undef CONFIG_CMD_XIMG

为了支持网络,我们需要将“#undef CONFIG_CMD_NET”改为“#define CONFIG_CMD_NET”。修改之后,在uboot初始化时就会执行eth_initialize函数,从而网络相关代码初始化就会被执行,将来就有可能能够使用网络(其他的细节没有注意则也使用不了)。后来发现config_cmd_default.h文件中已经有“#define CONFIG_CMD_NET”,所以最终修改是将#undef CONFIG_CMD_NET这行代码注释掉。

17.2 添加ping和tftp命令支持

在Linux系统中,网络底层驱动被上层应用调用的接口是socket,是一个典型的分层结构,底层和上层是完全被socket接口隔离的。在uboot中,网络底层驱动和上层应用是不分层的,即上层网络的每一个应用都是自己去调用底层驱动中的操作硬件的代码来实现的。

uboot中有很多预先设计好的与网络有关的命令,比如ping和tftp这两个命令。

如果想在uboot中使用这两个命令,则需要打开相应的宏开关。经过代码检查(比如我们已知ping命令对应的函数为do_ping,在SI中搜索do_ping函数,发现该函数前面有“#if defined(CONFIG_CMD_PING)”),得知ping命令开关宏为CONFIG_CMD_PING,而tftp命令的开关为CONFIG_CMD_NET。

因此我们在s5p_goni.h文件中添加CONFIG_CMD_PING这个宏定义:

#define CONFIG_CMD_PING
//#define CONFIG_CMD_NET //这个在上面已经有

经过上面的操作之后,我们重新编译与烧录运行,运行结果如下:

 

从上面可知ping和tftp命令都已经被识别,但是都提示“no ethernet found……”,也就是网络不通。 

17.3 移植工作 

17.3.1 分析问题 

我们根据错误提示“Net Initialization Skipped”与“No ethernet found”,追踪到/net/eth.c文件中的eth_initialize函数里的代码:

/** If board-specific initialization exists, call it.* If not, call a CPU-specific one*/if (board_eth_init != __def_eth_init) {if (board_eth_init(bis) < 0)printf("Board Net Initialization Failed\n");}else if (cpu_eth_init != __def_eth_init){if (cpu_eth_init(bis) < 0)printf("CPU Net Initialization Failed\n");}elseprintf("Net Initialization Skipped\n");if (!eth_devices) {puts("No ethernet found.\n");bootstage_error(BOOTSTAGE_ID_NET_ETH_START);} else{ //省略部分代码}

运行现象表明,这里的 board_eth_init 函数、cpu_eth_init 函数,其实都是 __def_eth_init(该函数内部单纯地返回-1而已),所以才会打印上面的提示信息。换句话说,因为没有自定义的网卡初始化函数(board_eth_init或者cpu_eth_init),所以 uboot 启动过程中初始化网卡时会打印 “Net:   Net Initialization Skipped”。

广义的网卡驱动移植,包括网卡初始化移植、网卡驱动的移植。其中网卡初始化的移植工作,又包括SROM控制器的初始化和(利用网卡驱动提供的接口进行)网卡的注册;网卡驱动的移植,其实就是找到某个网卡对应的驱动(一般内核或者uboot的/drivers目录中都有)的这个过程,因为这些驱动文件一般不需要改动源码。

上面的代码在判断eth_devices是否为NULL之前并没有完成网卡的初始化(包括SROM控制器的初始化和(利用网卡驱动提供的接口进行)网卡的注册),因此eth_devices为NULL而打印出“No ethernet found.”。

所以解决方法是在判断eth_devices是否为NULL之前完成网卡的初始化。

17.3.2 移植“SROM控制器初始化”代码

关于这部分的理论,见博客uboot的移植——DM9000移植的理论基础-CSDN博客。

(1)由上面博客的链接可知,网卡的初始化函数(这里特指SROM控制器初始化)一般放在第二阶段的board_init函数中的dm9000_pre_init函数中。2013.10版本的board_init函数位于/board/samsung/goni/goni.c文件中,我们在该函数内部添加dm9000_pre_init函数的调用(参考三星版本的board_init函数写法),如下所示:

int board_init(void)
{/* Set Initial global variables */s5pc110_gpio = (struct s5pc110_gpio *)S5PC110_GPIO_BASE;gd->bd->bi_arch_number = MACH_TYPE_GONI;gd->bd->bi_boot_params = PHYS_SDRAM_1 + 0x100;#ifdef CONFIG_DRIVER_DM9000dm9000_pre_init();
#endifreturn 0;
}

(2)然后将三星版本/board/samsung/smdkc110/smdkc110.c文件中的dm9000_pre_init函数的实现,拷贝到2013.10版本的/board/samsung/goni/goni.c文件中的board_init函数的前面, 然后参考uboot的移植——DM9000移植的理论基础进行移植即可,移植后的版本如下:

static void dm9000_pre_init(void)
{unsigned int tmp;#if defined(DM9000_16BIT_DATA)
//	SROM_BW_REG &= ~(0xf << 20);
//	SROM_BW_REG |= (0<<23) | (0<<22) | (0<<21) | (1<<20);SROM_BW_REG &= ~(0xf << 4);SROM_BW_REG |= (1<<7) | (1<<6) | (1<<5) | (1<<4);#else	SROM_BW_REG &= ~(0xf << 20);SROM_BW_REG |= (0<<19) | (0<<18) | (0<<16);
#endif
//	SROM_BC5_REG = ((0<<28)|(1<<24)|(5<<16)|(1<<12)|(4<<8)|(6<<4)|(0<<0));SROM_BC1_REG = ((0<<28)|(1<<24)|(5<<16)|(1<<12)|(4<<8)|(6<<4)|(0<<0));tmp = MP01CON_REG;
//	tmp &=~(0xf<<20);
//	tmp |=(2<<20);tmp &=~(0xf<<4);tmp |=(2<<4);MP01CON_REG = tmp;
}

(3) 然后我们把三星uboot中smdkv210singl.h文件中与网卡有关的宏,全部复制到s5p_goni.h文件中,然后参考uboot的移植——DM9000移植的理论基础进行移植。移植后的版本如下: 

/** Hardware drivers*/
#define DM9000_16BIT_DATA#define CONFIG_DRIVER_DM9000	1#ifdef CONFIG_DRIVER_DM9000
//#define CONFIG_DM9000_BASE		(0xA8000000)
#define CONFIG_DM9000_BASE		    (0x88000300)
#define DM9000_IO			(CONFIG_DM9000_BASE)
#if defined(DM9000_16BIT_DATA)
//#define DM9000_DATA			(CONFIG_DM9000_BASE+2)
#define DM9000_DATA			(CONFIG_DM9000_BASE+4)
#else
#define DM9000_DATA			(CONFIG_DM9000_BASE+1)
#endif
#endif

(4)最后在/board/samsung/goni/goni.c文件中的开始处,添加头文件包含“#include<s5pc110.h>”。

(5)重新编译与烧录运行,运行现象如下:

可见网卡还没有初始化,为何?上面提到“网卡的初始化,包括SROM控制器的初始化,以及利用网卡驱动提供的接口进行网卡的注册”,这里只完成了“SROM控制器的初始化”移植,还没有完成“网卡的注册”移植,所以网卡不能工作。 

17.3.2 移植“注册网卡驱动”代码 

接下来移植“网卡驱动的注册”。

在Linux的网卡驱动体系中,有一个结构体“struct eth_device”,它用来表示(或者叫封装)一个网卡的所有信息。我们关注一下代码中的eth_devices、eth_current 这两个变量,它们都是struct eth_device* 类型的指针,其中 eth_devices 指针指向一个保存着当前系统中所有的网卡信息的链表,而 eth_current指针指向当前我们正在操作的那个网卡对应的结构体。

向系统注册一个网卡,就是要建立一个这个结构体的实例,然后填充这个实例中的各个元素,最后将这个结构体实例加入到 eth_devices 这个链表上,从而完成网卡注册。因此网卡在初始化时,必须将自己注册到Linux系统的网卡驱动体系中,即把自己的 eth_device 结构体实例添加到eth_devices链表中,否则会出现“No ethernet found.”。

根据17.3.1的代码分析可以发现,/net/eth.c文件中有2个预留的函数(即 board_eth_init 和 cpu_eth_init)可以用来完成网卡的注册,但这两个函数都是空的,并没有完成网卡的注册工作。

但是注册网卡驱动的代码不能随便乱写,一定要遵守Linux网卡驱动架构的要求。但是这部分代码一般属于网卡驱动的一部分,不需要我们自己写,比如/drivers/net/dm9000x.c文件的最后一个函数int dm9000_initialize(bd_t *bis),就是用来注册DM9000网卡驱动的。

因此我们可以直接调用该函数来进行网卡的注册。我们注释掉原来的board_eth_init函数,重新写一个board_eth_init函数:

int board_eth_init(bd_t *bis){return dm9000_initialize(bis);
}

然后重新编译与烧录运行,运行现象如下:

目前为止的移植工作所得到的uboot源码见链接。

十八、uboot启动内核的移植

接下来进入到board_init_r函数最后的main_loop函数(该函数位于/common/main.c文件中):

	/* main_loop() can return to retry autoboot, if so just run it again. */for (;;) {main_loop();}

关于main_loop函数的理论分析,见博客uboot启动流程——main_loop函数分析。

18.1 修改命令提示符

这里只需要修改s5p_goni.h文件中的“ #define CONFIG_SYS_PROMPT  "Goni # " ”即可:

//#define CONFIG_SYS_PROMPT    "Goni # "
#define CONFIG_SYS_PROMPT    "x210 # "

重新编译与烧录运行,运行现象如下:

18.2 uboot启动内核的移植

18.2.1 尝试启动内核 

uboot的最终目的是启动Llinux内核,为了测试移植后的uboot能否启动内核,这里进行测试。

由于移植的uboot支持tftp命令,我们使用tftp命令从服务器中下载内核镜像zImage。

(1)首先确认bootcmd,以及与网络有关的环境变量是否设置正确。

x210 # print
baudrate=115200
bootargs=console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3
bootcmd=tftp 0x30008000 zImage;bootm 0x30008000
bootdelay=3
ethact=dm9000
ethaddr=00:40:5c:26:0a:5b
gatewayip=192.168.1.1
ipaddr=192.168.1.22
netmask=255.255.255.0
serverip=192.168.1.141Environment size: 312/16380 bytes
x210 #

(2)然后确认虚拟机Linux系统关于网络的设置是否正确。

(3)确认内核镜像zImage位于tftp服务器目录中(并将它改名为zImage-qt)。

root@ubuntu:/home/xjh/iot/embedded_basic/kernel/x210_kernel# cd arch/arm/boot/
root@ubuntu:/home/xjh/iot/embedded_basic/kernel/x210_kernel/arch/arm/boot# ls
bootp  compressed  Image  install.sh  Makefile  test  test2  zImage
root@ubuntu:/home/xjh/iot/embedded_basic/kernel/x210_kernel/arch/arm/boot# cp zImage /tftpboot/
root@ubuntu:/home/xjh/iot/embedded_basic/kernel/x210_kernel/arch/arm/boot# ls /tftpboot/zI*
/tftpboot/zImage
root@ubuntu:/home/xjh/iot/embedded_basic/kernel/x210_kernel/arch/arm/boot# mv /tftpboot/zImage /tftpboot/zImage-qt

(4)确认开发板能够ping通虚拟机Linux系统。

x210 # ping 192.168.1.141
dm9000 i/o: 0x88000300, id: 0x90000a46 
DM9000: running in 16 bit mode
MAC: 00:40:5c:26:0a:5b
operating at 100M full duplex mode
Using dm9000 device
host 192.168.1.141 is alive
x210 # 

(5)重启开发板,不打断启动流程而让它完全启动,或者手动输入bootcmd对应的命令。

x210 # tftp 0x30008000 zImage-qt
dm9000 i/o: 0x88000300, id: 0x90000a46 
DM9000: running in 16 bit mode
MAC: 00:40:5c:26:0a:5b
operating at 100M full duplex mode
Using dm9000 device
TFTP from server 192.168.1.141; our IP address is 192.168.1.22
Filename 'zImage-qt'.
Load address: 0x30008000
Loading: #########T ##############################################################################################################################################################################################################################################353.5 KB/s
done
Bytes transferred = 3615284 (372a34 hex)
x210 #
x210 # bootm 0x30008000
Wrong Image Format for bootm command
ERROR: can't get kernel image!
x210 #

可以按到,执行“bootm 0x30008000”时提示镜像格式错误。

18.2.2 错误分析

在uboot启动流程——main_loop函数分析中提到,执行“ bootm 30008000 ”实际执行 do_bootm 函数,因此需要分析2013.10版本的 do_bootm函数(该函数位于/common/cmd_bootm.c文件)。另外我们以错误信息关键词“Wrong Image Format”在SI中搜寻,得知它在 boot_get_kernel 这个函数中,而这个函数又间接地被do_bootm函数调用。

分析得知do_bootm函数调用的函数关系如下:

//以下函数均位于/common/cmd_bootm.c文件
|---do_bootm
|------do_bootm_subcommand //做标号推出来是调用这个
|---------do_bootm_states
|------------bootm_find_os //输出Wrong Image Format for bootm command
|---------------boot_get_kernel //输出“Wrong Image Format”
|------------bootm_load_os

为何是调用do_bootm_subcommand 函数呢?我在该函数中通过添加print语句而得知:

x210 # bootm 0x30008000
this is a mark!
Wrong Image Format for bootm command
ERROR: can't get kernel image!
x210 # 

18.2.3 移植工作

这里的移植过程暂无,但有两种后续工作思路:第一种思路是读懂2013.10版本uboot的启动内核这部分的代码,然后进行修改移植;第二种思路是移植三星版本uboot的启动内核这部分的代码。

其中第二种思路实现起来比较简单,后面可能就以第二种思路进行移植。

关于这部分的理论,还是要看uboot启动流程——main_loop函数分析这博客的内容。

18.3 测试最终移植效果 

到目前为止,uboot的移植工作基本完成(后续还可以进行修改,比如初始化LCD等工作)。

接下来测试最终移植效果,测试过程同18.2中的(1)~(5),效果如下:

暂无。

可见移植后的uboot可以顺利下载到DDR中,并且启动了内核。至于后面内核试图挂载根文件系时出现错误,那可能与环境变量bootargs以及与根文件系统本身有关,不是uboot移植的问题。

至此,整个uboot移植实验完成。

附录: /mkconfig脚本的分析

在命令行配置uboot,即执行“make s5p_goni_config”时,对应Makefile中的这个目标:

%_config::	unconfig@$(MKCONFIG) -A $(@:_config=)
  • $(MKCONFIG),表示/mkconfig脚本。
  • 该脚本接受两个参数,即-A和s5p_goni。
  • 该脚本完成一些符号连接的创建,以及创建头文件include/config.h
  • include/config.h文件中仅有一行代码,即“ #include<include/configs/s5p_goni.h> ”。
  • 配置前,configs文件夹就已经位于/include文件夹下,它的每个文件对应一个开发板的头文件。这些头文件都是一些宏定义配置文件,是移植时最主要的文件。

下面我们来分析/mkconfig脚本。

(1)下面是/mkconfig脚本24至35行代码。

其使用awk正则表达式,将boards.cfg文件中与刚才$2(s5p_goni)能够匹配上的那一行截取出来赋值给变量line,然后将line的内容以空格为间隔依次分开,分别赋值给$1、$2…$7、$8。

在解析完boards.cfg之后,$1到$8重新赋值如下:$1 = Active,$2 = arm,$3 = armv7,$4 = s5pc1xx,$5 = samsung,$6 = goni,$7 = s5p_goni,$8 = -。

if [ \( $# -eq 2 \) -a \( "$1" = "-A" \) ] ; then# Automatic modeline=`awk '($0 !~ /^#/ && $7 ~ /^'"$2"'$/) { print $1, $2, $3, $4, $5, $6, $7, $8 }' boards.cfg`if [ -z "$line" ] ; thenecho "make: *** No rule to make target \`$2_config'.  Stop." >&2exit 1fiset ${line}# add default board name if needed[ $# = 3 ] && set ${line} ${1}
fi

(2)下面是/mkconfig脚本55至64行代码。

从此段代码得到arch=arm,cpu=armv7,vendor=samsung,soc=s5pc1xx。

arch="$2"
cpu=`echo $3 | awk 'BEGIN {FS = ":"} ; {print $1}'`
spl_cpu=`echo $3 | awk 'BEGIN {FS = ":"} ; {print $2}'`
if [ "$6" = "-" ] ; thenboard=${BOARD_NAME}
elseboard="$6"
fi
[ "$5" != "-" ] && vendor="$5"
[ "$4" != "-" ] && soc="$4"

(3)下面是/mkconfig脚本94至123行代码。

此处在创建符号连接,即:include/asm—>arch/arm/include/asm,include/asm/arch—>include/asm/arch-s5pc1xx,include/asm/proc —> include/asm/proc-armv。

#
# Create link to architecture specific headers
#
if [ "$SRCTREE" != "$OBJTREE" ] ; thenmkdir -p ${OBJTREE}/includemkdir -p ${OBJTREE}/include2cd ${OBJTREE}/include2rm -f asmln -s ${SRCTREE}/arch/${arch}/include/asm asmLNPREFIX=${SRCTREE}/arch/${arch}/include/asm/cd ../includemkdir -p asm
elsecd ./includerm -f asmln -s ../arch/${arch}/include/asm asm
firm -f asm/archif [ -z "${soc}" ] ; thenln -s ${LNPREFIX}arch-${cpu} asm/arch
elseln -s ${LNPREFIX}arch-${soc} asm/arch
fiif [ "${arch}" = "arm" ] ; thenrm -f asm/procln -s ${LNPREFIX}proc-armv asm/proc
fi

(4)下面是/mkconfig脚本151至185行代码。

​​​此处创建include/config.h文件。

#
# Create board specific header file
#
if [ "$APPEND" = "yes" ]	# Append to existing config file
thenecho >> config.h
else> config.h		# Create new config file
fi
echo "/* Automatically generated - do not edit */" >>config.hfor i in ${TARGETS} ; doi="`echo ${i} | sed '/=/ {s/=/	/;q; } ; { s/$/	1/; }'`"echo "#define CONFIG_${i}" >>config.h ;
doneecho "#define CONFIG_SYS_ARCH  \"${arch}\""  >> config.h
echo "#define CONFIG_SYS_CPU   \"${cpu}\""   >> config.h
echo "#define CONFIG_SYS_BOARD \"${board}\"" >> config.h[ "${vendor}" ] && echo "#define CONFIG_SYS_VENDOR \"${vendor}\"" >> config.h[ "${soc}"    ] && echo "#define CONFIG_SYS_SOC    \"${soc}\""    >> config.hcat << EOF >> config.h
#define CONFIG_BOARDDIR board/$BOARDDIR
#include <config_cmd_defaults.h>
#include <config_defaults.h>
#include <configs/${CONFIG_NAME}.h>
#include <asm/config.h>
#include <config_fallbacks.h>
#include <config_uncmd_spl.h>
EOFexit 0

这篇关于uboot的移植——移植uboot官方的uboot到x210开发板(2)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/851730

相关文章

ubuntu系统使用官方操作命令升级Dify指南

《ubuntu系统使用官方操作命令升级Dify指南》Dify支持自动化执行、日志记录和结果管理,适用于数据处理、模型训练和部署等场景,今天我们就来看看ubuntu系统中使用官方操作命令升级Dify的方... Dify 是一个基于 docker 的工作流管理工具,旨在简化机器学习和数据科学领域的多步骤工作流。

QT移植到RK3568开发板的方法步骤

《QT移植到RK3568开发板的方法步骤》本文主要介绍了QT移植到RK3568开发板的方法步骤,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录前言一、获取SDK1. 安装依赖2. 获取SDK资源包3. SDK工程目录介绍4. 获取补丁包二

Mybatis官方生成器的使用方式

《Mybatis官方生成器的使用方式》本文详细介绍了MyBatisGenerator(MBG)的使用方法,通过实际代码示例展示了如何配置Maven插件来自动化生成MyBatis项目所需的实体类、Map... 目录1. MyBATis Generator 简介2. MyBatis Generator 的功能3

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

FreeRTOS-基本介绍和移植STM32

FreeRTOS-基本介绍和STM32移植 一、裸机开发和操作系统开发介绍二、任务调度和任务状态介绍2.1 任务调度2.1.1 抢占式调度2.1.2 时间片调度 2.2 任务状态 三、FreeRTOS源码和移植STM323.1 FreeRTOS源码3.2 FreeRTOS移植STM323.2.1 代码移植3.2.2 时钟中断配置 一、裸机开发和操作系统开发介绍 裸机:前后台系

Adblock Plus官方规则Easylist China说明与反馈贴(2015.12.15)

-------------------------------特别说明--------------------------------------- 视频广告问题:因Adblock Plus的局限,存在以下现象,优酷、搜狐、17173黑屏并倒数;乐视、爱奇艺播放广告。因为这些视频网站的Flash播放器被植入了检测代码,而Adblock Plus无法修改播放器。 如需同时使用ads

开发板NFS挂载文件目录

文章目录 序NFS1. 安装 NFS 服务器和客户端在服务器上(NFS 服务器端)在客户端上(NFS 客户端) 2. 配置 NFS 服务器创建共享目录编辑 `/etc/exports` 文件启动 NFS 服务 3. 在客户端挂载 NFS 共享创建挂载点挂载 NFS 共享验证挂载 4. 设置开机自动挂载5. 解决权限问题 序 本节主要实现虚拟机(服务器)与开发板(客户端)通过N

Temu官方宣导务必将所有的点位材料进行检测-RSL资质检测

关于饰品类产品合规问题宣导: 产品法规RSL要求 RSL测试是根据REACH法规及附录17的要求进行测试。REACH法规是欧洲一项重要的法规,其中包含许多对化学物质进行限制的规定和高度关注物质。 为了确保珠宝首饰的安全性,欧盟REACH法规规定,珠宝首饰上架各大电商平台前必须进行RSLReport(欧盟禁限用化学物质检测报告)资质认证,以确保产品不含对人体有害的化学物质。 RSL-铅,

Cortex-A7:ARM官方推荐的嵌套中断实现机制

0 参考资料 ARM Cortex-A(armV7)编程手册V4.0.pdf ARM体系结构与编程第2版 1 前言 Cortex-M系列内核MCU中断硬件原生支持嵌套中断,开发者不需要为了实现嵌套中断而进行额外的工作。但在Cortex-A7中,硬件原生是不支持嵌套中断的,这从Cortex-A7中断向量表中仅为外部中断设置了一个中断向量可以看出。本文介绍ARM官方推荐使用的嵌套中断实现机

长虹官方刷机包和刷机教程

为了解决部分朋友因应用引起的电视死机、无法开机、系统被破坏等情形,长虹电视团队特开此帖为朋友们提供刷机方法,但刷机有风险,如完全不懂刷机技巧的朋友需要谨慎操作哦,如有疑问可以微信留言给我们。       下面是按照电视机型、刷机方式、刷机包链接这样的方式提供的(目前暂放热门型号刷机教程)。