参考上一节的内容,建立了smdk6410的关联路径, 并把和路径关联的几个宏配置OK后,编译命令"make O=build smdk6410_defconfig;make O=build "就能通过了,这表示smdk6410项目框架搭起来了,后续就是要porting各块的代码。
Samsung支持smdk6410的uboot版本是uboot1.1.6 , 也就是飞凌资料包提供的,现在就是要把上面的相关代码移植到新的uboot上。
代码移植
代码Porting的大概次序是:
- arm平台相关的。比如cpu型号的配置,MMU/cache
- board初始化相关的。比如DDR,WDT,中断,CLOCK
- 串口功能的代码。为后续其他功能的移植提供调试手段
- 其他功能的驱动代码
1. 平台相关的代码
OK6410使用的SOC是内核为ARM1176JZF-S的S3C6410。在arch/arm/Kconfig里面定义config ARCH_S3C64XX一项中,需要选择CPU_ARM1176。uboot里面平台相关的代码都位于arch/目录下, arm1176的平台代码位于arch/arm/cpu/arm11(arm1176)和arch/arm/lib中,后者是arm平台的通用部分,不同arm核亦是相同;前者则存放arm1176核的平台差异代码,一般直接沿用uboot的代码即可。
arm1176平台的入口代码: arch/arm/cpu/arm1176/start.S,以下是只保留了关键部分的代码:
reset:
/* set the cpu to SVC32 mode
*/
mrs r0, cpsr
bic r0, r0, #0x3f
orr r0, r0, #0xd3
msr cpsr, r0
cpu_init_crit:
/* flush v4 I/D caches
*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 1 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
/* Prepare to disable the MMU */
adr r2, mmu_disable_phys
sub r2, r2, #(CONFIG_SYS_PHY_UBOOT_BASE - CONFIG_SYS_TEXT_BASE)
b mmu_disable
.align 5
/* Run in a single cache-line */
mmu_disable:
mcr p15, 0, r0, c1, c0, 0
nop
nop
mov pc, r2
mmu_disable_phys:
/* Go setup Memory and board specific bits prior to relocation.
*/
bl lowlevel_init /* go setup pll,mux,memory */
bl _main
mov pc, lr
从注释上可以看出来,这段代码的作用。如果要移植,需要关注两点:
1)配置宏 CONFIG_SYS_PHY_UBOOT_BASE、CONFIG_SYS_TEXT_BASE。这个值,即为uboot运行时的入口地址
2)lowlevel_init的实现。这个函数涉及系统时钟以及评估板相关的硬件初始化,将在下一步说下说明。
2. board初始化相关的代码
uboot将评估板启动初始化相关的代码放到了board/目录下,这一点上,不管是旧的版本还是新版本,都没有改变。 可以直接将原uboot1.1.6的board/samsung/smdk6410移植过来,前面提到的lowlevel_init ,就在其下的汇编文件lowlevel_init.S中。移植中,需要注意的是,头文件的引用位置 和 u-boot.lds文件的修改
1)头文件的存放位置和引用。默认的uboot头文件目录有:
./include
./arch/arm/include
./arch/arm/mach-xxxxx/include/mach
前两个和平常引用没有分别,需要注意的是最后一个的目录,在编译过程中,会临时创建一个软链接指向该目录,这个链接的路径: ./include/asm/arch ,引用头文件的方式类似:
#include <asm/arch/uart.h>
2)LDS文件的修改
LDS文件定义了uboot编译之后的连接过程,决定了一个可执行程序的各个段的存储位置。新版本由于代码结构和段落属性都有很大的改变,所以再沿用旧LDS文件不是很合适。
在顶层Makefile中,定义了LDSCRIPT值的选择顺序。可以看出board/samsung/smdk6410/u-boot.lds是第一优先级,若存在,将被选择。可以将arch/arm/cpu/arm1176/u-boot.lds复制到该位置。若以后要修改LDS文件,可将改动维持在项目相关的目录中,便于维护。
579 # If there is no specified link script, we look in a number of places for it
580 ifndef LDSCRIPT
581 ifeq ($(wildcard $(LDSCRIPT)),)
582 LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds
583 endif
584 ifeq ($(wildcard $(LDSCRIPT)),)
585 LDSCRIPT := $(srctree)/$(CPUDIR)/u-boot.lds
586 endif
587 ifeq ($(wildcard $(LDSCRIPT)),)
588 LDSCRIPT := $(srctree)/arch/$(ARCH)/cpu/u-boot.lds
589 endif
590 endif
3. 串口功能的代码
UART部分 在lowlevel_init.S 中也有初始化代码(uart_asm_init),默认设置uart0的波特率是115200,只是调试不方便,做不到运行时的顺序打印,只能显示最后一个字符。
在lowlevel_init.S 添加如下汇编代码,这样在早期就可以使用类似C中puts(char *string) 和int数值的十六进制打印功能,这两个函数做了寄存器的栈保存,所以也允许在C代码中被调用
extern void asm_puts(char *s);
extern void asm_print_hex(ulong val);
asm_putc:
ldr r1, =ELFIN_UART_BASE
1: ldr r2, [r1, #UTRSTAT_OFFSET]
tst r2, #2
beq 1b
str r0, [r1, #UTXH_OFFSET]
mov pc, lr
.globl asm_puts
asm_puts:
stmfd sp!, {r1-r3, lr}
mov r3, r0
0: ldrb r0, [r3]
cmp r0,#0
beq 2f
cmp r0, # '\n'
bne 1f
mov r0, # '\r'
bl asm_putc
mov r0, # '\n'
1:
bl asm_putc
add r3, r3, #1
b 0b
2:
ldmfd sp!, {r1-r3, lr}
mov pc, lr
.globl asm_print_hex
asm_print_hex:
stmfd sp!, {r1-r4, lr}
/* mov r5,lr */
mov r3,r0
mov r0,#'0'
bl asm_putc
mov r0,#'x'
bl asm_putc
mov r4,#8
1: mov r3,r3, ror #28
and r0, r3,#0x0F
cmp r0,#9
addls r0,r0,#'0'
addhi r0,r0,#55
bl asm_putc
subs r4, r4,#1
bne 1b
/* mov lr,r5 */
ldmfd sp!, {r1-r4, lr}
mov pc,lr
4. 其他功能的代码
在新版本的uboot中,引入了DM(Driver Model)的设备驱动模型。
下面关于DM的概念介绍, 引用自:https://www.cnblogs.com/wahaha02/p/5987350.html
uboot设备模型中udevice为核心对象,以树型模型组织(如下),其为dm的顶层结构
单个udevice建模如下,详细对象定义参见《附:核心数据结构》小节。
所有对象可以按udevice或者uclass进行遍历。
DM初始化流程包括:
- 模型初始化
- 静态对象初始化
- 运行态对象初始化
- 设备组公共初始化
- 设备初始化
DM初始化的总入口接口dm_init_and_scan(),其主要由以下三块组成:
- dm_init():创建udevice和uclass空链表,创建根设备(root device)
- dm_scan_platdata():扫描U_BOOT_DEVICE定义的设备,创建对应的udevice和uclass对象,查找并绑定相应driver,并调用probe流程。
- dm_scan_fdt():扫描由FDT设备树文件定义的设备,创建对应的udevice和uclass对象,查找并绑定相应driver,并调用probe流程。
这样,按DM的驱动模块,就可以添加uart、timer、sdhc、DMA、USB等驱动,具体的添加过程,这里就不在说明,以后视情况再专门介绍。
编译错误
编译uboot过程中, 有几个编译错误需要注意,花了些时间,最后才明白原因,有记录的价值:
编译错误1:lds文件添加指定文件后,引起的重复定义。
修改u-boot.lds文件, 新增第25行后
20 .text :
21 {
22 *(.__image_copy_start)
23 *(.vectors)
24 CPUDIR/start.o (.text*)
25 board/samsung/smdk6410/lowlevel_init.o (.text*)
26 }
出现如下编译错误:
board/samsung/smdk6410/built-in.o: In function `lowlevel_init':
/home/golden/workspace/uboot/uboot-201809/build/../board/samsung/smdk6410/lowlevel_init.S:46: multiple definition of `lowlevel_init'
board/samsung/smdk6410/lowlevel_init.o:/home/golden/workspace/uboot/uboot-201809/build/../board/samsung/smdk6410/lowlevel_init.S:46: first defined here
board/samsung/smdk6410/built-in.o: In function `test_uart_puthex':
/home/golden/workspace/uboot/uboot-201809/build/../board/samsung/smdk6410/lowlevel_init.S:390: multiple definition of `test_uart_puthex'
board/samsung/smdk6410/lowlevel_init.o:/home/golden/workspace/uboot/uboot-201809/build/../board/samsung/smdk6410/lowlevel_init.S:390: first defined here
board/samsung/smdk6410/built-in.o: In function `asm_puts':
/home/golden/workspace/uboot/uboot-201809/build/../board/samsung/smdk6410/lowlevel_init.S:370: multiple definition of `asm_puts'
解决方法: Makefile中将obj-y := lowlevel_init.o 改成extra-y := lowlevel_init.o
编译错误2: 提示定义冲突
In file included from ../include/common.h:519:0,
from include/asm/arch/cpu.h:35,
from ../include/configs/smdk6410.h:18,
from include/config.h:5,
from ../arch/arm/include/asm/string.h:4,
from ../include/linux/string.h:21,
from ../include/efi.h:19,
from ../arch/arm/lib/reloc_arm_efi.c:14:
../include/net.h: In function ‘net_read_ip’:
../include/net.h:723:2: warning: implicit declaration of function ‘memcpy’ [-Wimplicit-function-declaration]
memcpy((void *)&ip, (void *)from, sizeof(ip));
^~~~~~
In file included from ../include/linux/string.h:21:0,
from ../include/efi.h:19,
from ../arch/arm/lib/reloc_arm_efi.c:14:
../arch/arm/include/asm/string.h: At top level:
../arch/arm/include/asm/string.h:20:15: error: conflicting types for ‘memcpy’
extern void * memcpy(void *, const void *, __kernel_size_t);
^~~~~~
原因是:定义声明的头文件string.h首先被包含,然后逐层向下引用头文件。
而include/asm/arch/cpu.h 包含了的common.h,里面重复调用了string.h ,
由于string.h已被引用过,所以这时不会展开实际内容,而前一次的引用,这时还没展开,
导致common.h中引用net.h时,调用memcpy找不到它的外部声明。
解决办法: 去掉include/asm/arch/cpu.h的 common.h引用语句
SPL部分
与旧版本相比,新版本的uboot引入了u-boot-spl。编译结果或者过程中, 除了u-boot.bin的生成,还会有u-boot-spl.bin。开始时,我不是很明白这个东西和u-boot.bin的差别, 后来百度后,才明白它的含义。
SPL(Secondary Program Loader)指第二阶段程序加载器,这里所谓的第二阶段是相对于SOC中的BROM来说的。u-boot-spl相当于轻量级的bootloader,它的作用就是加载u-boot.bin, 是不是很疑惑这种设计? 如果结合SoC的启动过程,那就很好理解了。现在大多数SoC的存储介质是不支持片上引导的(Nandflash,eMMC等),所以需要加载到RAM中,才能运行,但在SoC上电启动后,外部的RAM或DDR在此时还未初始化,无法使用,这时SoC的IRAM(Internal RAM)就是程序运行介质。SoC开机后,片上的固化程序BROM会加载存储介质的执行程序到IRAM中, 但IRAM的大小有限,往往就只有几KB,无法容纳u-boot.bin,这时u-boot-spl轻量级就排上用场了,它先初始化DDR时序、系统时钟等,准备好外部的运行环境,再加载u-boot.bin,并将CPU的控制权交给u-boot.
接下来,介绍下u-boot-spl的smdk6410配置过程。
1.在defconfig 配置CONFIG_SPL
除了smdk6410_defconfig添加CONFIG_SPL之外,注意还需要在Kconfig 文件里面配置 CONFIG_SUPPORT_SPL
FILE: arch/arm/mach-s3c64xx/Kconfig
config TARGET_OK6410
default y
bool "Support smdk6410 board"
select OF_CONTROL
select SUPPORT_SPL
因为CONFIG_SPL依赖于CONFIG_SUPPORT_SPL,见
FILE: ./common/spl/Kconfig
config SPL
bool
depends on SUPPORT_SPL
prompt "Enable SPL"
2. 另外,代码的控制实际是通过 CONFIG_SPL_BUILD来控制。
见源码的宏控制分支,和各个Makefile中obj-$(CONFIG_SPL_BUILD)和宏控制分支
执行Makefiel,该宏定义来源于:
FILE: ./scripts/Makefile.autoconf
spl/u-boot.cfg: include/config.h FORCE
$(Q)mkdir -p $(dir $@)
$(call cmd,u_boot_cfg,-DCONFIG_SPL_BUILD)
tpl/u-boot.cfg: include/config.h FORCE
$(Q)mkdir -p $(dir $@)
$(call cmd,u_boot_cfg,-DCONFIG_SPL_BUILD -DCONFIG_TPL_BUILD)
编译C源码,该宏定义来源于 :
FILE: ./scripts/Makefile.spl
24
25 KBUILD_CPPFLAGS += -DCONFIG_SPL_BUILD
26 ifeq ($(CONFIG_TPL_BUILD),y)
27 KBUILD_CPPFLAGS += -DCONFIG_TPL_BUILD
28 endif
...
36 ifdef CONFIG_SPL_BUILD
37 SPL_ := SPL_
38 ifeq ($(CONFIG_TPL_BUILD),y)
39 SPL_TPL_ := TPL_
40 else
41 SPL_TPL_ := SPL_
42 endif
43 else
44 SPL_ :=
45 SPL_TPL_ :=
46 endif
上述文件中, SPL_ 和SPL_TPL_宏也在此定义. ,这样的宏被运用于生成u-boot-spl.bin 或 u-boot-tpl.bin的宏选择,
比如 drivers/Makfile : obj-$(CONFIG_$(SPL_TPL_)CLK) += mtd/nand/
但注意,这对应生成u-boot.bin并无影响, 因为u-boot.bin 的编译是由顶层Makefile直接定义的,不关心二级目录的Makefie.
比如 ./Makefile : libs-$(CONFIG_CMD_NAND) += drivers/mtd/nand/
FILE: ./Makefile
681 libs-y += lib/
682 libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/
683 libs-$(CONFIG_OF_EMBED) += dts/
684 libs-y += fs/
685 libs-y += net/
686 libs-y += disk/
687 libs-y += drivers/
688 libs-y += drivers/dma/
689 libs-y += drivers/gpio/
690 libs-y += drivers/i2c/
691 libs-y += drivers/mtd/
692 libs-$(CONFIG_CMD_NAND) += drivers/mtd/nand/
693 libs-y += drivers/mtd/onenand/
694 libs-$(CONFIG_CMD_UBI) += drivers/mtd/ubi/
所以u-boot.bin和u-boot-spl.bin、u-boot-tpl.bin是不同的编译过程,第一个是默认必选;后两者可选,分别由CONFIG_SPL、CONFIG_TPL 控制。
默认情况下,u-boot-spl.bin的text base 是基于0x000000。 如果SoC加载地址不是0x00000,那么编译时就需要根据实际加载情况配置CONFIG_SPL_TEXT_BASE。
在samsung S3C6410 SoC上, 有个叫‘Steppingstone’的内部SRAM,物理起始地址为0x0C00_0000。 SoC上电后,执行内部固化的ROM程序,会根据boot_config选择boot device,并将读取开头的部分代码到‘Steppingstone’,之后将CPU控制权交给这部分代码。
如果代码的执行是位置无关的,则没有问题,否则需要确保代码的TEXT_BASE和实际加载地址一致。 S3C6410上,根据添加的打印信息,默认将SDHC卡代码读取到SRAM的一个起始地址,故定义如下:
#define CONFIG_SPL_TEXT_BASE 0x0C000000
SPL代码里,可利用BROM提供的mmc接口函数CopyMovitoMem(),将u-boot.bin从SDHC卡中,移到DDR中。
修改arch/arm/cpu/arm1176/start.S, 并在smdk6410_defconfig中添加宏 CONFIG_SPL_BOOT_MMC
/*
* Go setup Memory and board specific bits prior to relocation.
*/
bl lowlevel_init /* go setup pll,mux,memory */
#if CONFIG_IS_ENABLED(BOOT_MMC)
ldr sp, =CONFIG_SYS_PHY_UBOOT_BASE
bl mmc_bl2_copy
cmp r0, #0
1: beq 1b
ldr pc, =CONFIG_SYS_PHY_UBOOT_BASE
#else
bl _main
#endif
添加board/samsumg/smdk6410/bl2_copy.c
#include <common.h>
#include <asm/io.h>
#include <asm/arch/sdhc.h>
#define HSMMC_CHANNEL 0
#define MOVI_INIT_REQUIRED 0
#define CopyMovitoMem(a,b,c,d,e) (((int(*)(int, uint, ushort, uint *, int))(*((uint *)(TCM_BASE + 0x8))))(a,b,c,d,e))
#define TCM_BASE 0x0C004000
#define BL2_BASE CONFIG_SYS_UBOOT_BASE
/* size information */
#define SS_SIZE (8 * 1024) //bl1 size
#define eFUSE_SIZE (1 * 1024) // 0.5k eFuse, 0.5k reserved`
/* movinand definitions */
#define MOVI_BLKSIZE 512
#define MOVI_TOTAL_BLKCNT *((volatile unsigned int*)(TCM_BASE - 0x4))
#define MOVI_HIGH_CAPACITY *((volatile unsigned int*)(TCM_BASE - 0x8))
/* partition information */
#define PART_SIZE_BL (512 * 1024)
#define PART_SIZE_KERNEL (4 * 1024 * 1024)
#define PART_SIZE_ROOTFS (8 * 1024 * 1024)
#define MOVI_LAST_BLKPOS (MOVI_TOTAL_BLKCNT - (eFUSE_SIZE / MOVI_BLKSIZE))
#define MOVI_BL1_BLKCNT (SS_SIZE / MOVI_BLKSIZE)
#define MOVI_ENV_BLKCNT (CONFIG_ENV_SIZE / MOVI_BLKSIZE)
#define MOVI_BL2_BLKCNT (PART_SIZE_BL / MOVI_BLKSIZE)
#define MOVI_ZIMAGE_BLKCNT (PART_SIZE_KERNEL / MOVI_BLKSIZE)
#define MOVI_BL2_POS (MOVI_LAST_BLKPOS - MOVI_BL1_BLKCNT - MOVI_BL2_BLKCNT - MOVI_ENV_BLKCNT)
#define MOVI_ROOTFS_BLKCNT (PART_SIZE_ROOTFS / MOVI_BLKSIZE)
int mmc_bl2_copy(void)
{
int ret;
/* Set current driver to 9mA */
writel(readl(HM_CONTROL4) | (0x3 << 16), HM_CONTROL4);
ret = CopyMovitoMem(HSMMC_CHANNEL, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
(uint *)BL2_BASE, MOVI_INIT_REQUIRED);
return ret;
}
代码烧写
最后就是SDHC卡的代码烧写。S3C6410的SD/MMC加载原理,这里就不多说了,网上有很多。
底下附上我自己写的烧录脚本,放到uboot代码的根目录下,注意,我的uboot 编译结果位于./build ,如果你不是,请修改脚本里面的文件路径。烧录方法,只要插上SDHC卡的U盘(一定要用U盘读卡器),执行这个脚本即可。
#!/bin/bash
#
# Created by Golden.Chen
# Date: 2018/12/23 15:56
#
SDCARD=/dev/sdb
SPL_BOOT=build/spl/u-boot-spl.bin
UBOOT=build/u-boot.bin
if [ `whoami` != "root" ];then
echo "Need root permision to execute the script!"
exit
fi
echo "SD Card Writer program V1.0"
echo
if [ "$1" != "" ]; then
SDCARD=$1
fi
if [ "$2" == "sd" ]; then
RES_SZ=1
else
RES_SZ=1025
fi
echo "sd/mmc: $SDCARD"
if [ ! -b "$SDCARD" ]; then
cat << EOF
no device found
Usage: $0 <sd-dev> [sd-type]
defalutly, <sd-dev> is /dev/sdb, sd-type is "sdhc" in ("sdhc","sd")
EOF
exit 1
fi
# Check SDCARD Sectors
# SDCARD_SECTORS=`fdisk -l -u $SDCARD | grep sectors | head -n 1 | \
# cut -d',' -f4 | cut -d' ' -f3`
SDCARD_SECTORS=`cat /sys/block/${SDCARD##*/}/size`
# unit: sectors -512 bytes
SECTOR_SIZE=512
# RES_SZ=1 or 1025 sectors
SIG_SZ=1
BL1_SZ=16
BL2_SZ=1024
ENV_SZ=32
SIZE_FAT=$(($SDCARD_SECTORS-$FL210_FREE-$FL210_FREE-2))
START_BL1_POS=$(($SDCARD_SECTORS-$RES_SZ-$SIG_SZ-$BL1_SZ))
START_BL2_POS=$(($START_BL1_POS-$ENV_SZ-$BL2_SZ))
print_success()
{
if [ "$1" == 0 ]; then
echo "success"
else
echo "failed"
exit -1
fi
}
# Ask for correct calculation
cat <<EOF
sd/mmc device : $SDCARD
byte per sector: $SECTOR_SIZE
total sectors : $SDCARD_SECTORS
fusing sd/mmc acticon in the unit: sector
EOF
#echo "Do you want to continue(yes/no): "
#read ANS
#if [ "$ANS" != "yes" ]; then
# exit -1
#fi
#size 1M
# echo -n "mmc.bin : "
# dd bs=512 seek=1 if=/dev/zero of=$SDCARD count=2048 > /dev/null 2>&1
# dd bs=512 seek=1 if=./mmc.bin of=$SDCARD > /dev/null 2>&1
echo -n "write bl1 to sd/mmc at offset: $START_BL1_POS size: $BL1_SZ ..."
dd bs=512 seek=$START_BL1_POS if=/dev/zero of=$SDCARD count=$BL1_SZ > /dev/null 2>&1
dd bs=512 seek=$START_BL1_POS if=$SPL_BOOT of=$SDCARD count=$BL1_SZ > /dev/null 2>&1
print_success "$?"
echo -n "write bl2 to sd/mmc at offset: $START_BL2_POS size: $BL2_SZ ..."
dd bs=512 seek=$START_BL2_POS if=/dev/zero of=$SDCARD count=$BL2_SZ > /dev/null 2>&1
dd bs=512 seek=$START_BL2_POS if=$UBOOT of=$SDCARD count=$BL2_SZ > /dev/null 2>&1
print_success "$?"
sync
exit 0
################ the end #########################