可以从http://sourceforge.net/projects/u-boot获得U-Boot的最新版本,如果使用过程中碰到问题或是发现Bug,可以通过邮件列表网站http://lists.sourceforge.net/lists/listinfo/u-boot-users/获得帮助。
本书在u-boot-1.1.6的基础上进行分析和移植,从sourceforge网站下载u-boot-1.1.6.tar.bz2后解压即得到全部源码。U-Boot源码目录结构比较简单、独立,目录结构也比较浅,很容易全部掌握。
目录 特性 解释说明
board 开发板相关 对应不同配置的电路板(即使CPU相同),比如smdk2410、sbc2410x
cpu 平台相关 对应不同的CPU,比如arm920t、arm925t、i386等;在它们的子目录下仍可以进一步细分,比如arm920t下就有at91rm9200、s3c24x0
lib_i386类似 某一架构下通用的文件
include 通用的函数 头文件和开发板配置文件,开发板的配置文件都放在include/configs目录下,U-Boot没有make menuconfig类似的莱单来进行可视化配置,需要手动地修改配置文件中的宏定义
lib_generic 通用的库函数,比如printf等
common 通用的函数,多是对下一层驱动程序的进一步封装
disk 通用的设备驱动程序 硬盘接口程序
drivers 各类具体设备的驱动程序,基本上可以通用,它们通过宏从外面引入平台/开发板相关的函数
dtt 数字温度测量器或者传感器的驱动
fs 文件系统
nand_spl U-Boot一般从ROM、NOR Flash等设备启动,现在开始支持从NAND Flash启动,但是支持的CPU种类还不多
net 各种网络协议
post 上电自检程序
rtc 实时时钟的驱动
doc 文档 开发、使用文档
examples 示例程序 一些测试程序,可以使用U-Boot下载后运行
tools 工具 制作S-Record、U-Boot格式映像的工具,比如mkimage
U-Boot中各目录间也是有层次结构的,虽然这种分法不是绝对的,但是在移植过程中可以提供一些指导意义,如图2所示。
2 U-Boot顶层目录的层次结构
比如common/cmd_nand.c文件提供了操作NAND Flash的各种命令,这些命令通过调用drivers/nand/nand_base.c中的擦除、读写函数来实现。这些函数针对NAND Flash的共性作了一些封装,将平台/开发板相关的代码用宏或外部函数来代替。而这些宏与外部函数,如果与平台相关,就要在下一层次的cpu /xxx(xxx表示某型号的CPU)中实现;如果与开发板相关,就要在下一层次的board/xxx目录(xxx表示某款开发板)中实现。本书移植的 U-Boot,就是在cpu/arm920t/s3c24x0目录下增加了一个nand_flash.c文件来实现这些函数。
以增加烧写yaffs文件系统映像的功能为例──就是在common目录下的 cmd_nand.c中增加命令,比如nand write.yaffs:这个命令要调用drivers/nand/nand_util.c中的相应函数,针对yaffs文件系统的特点依次调用擦除、烧 写函数。而这些函数依赖于drivers/nand/nand_base.c、cpu/arm920t/s3c24x0/nand_flash.c文件中 的相关函数。
目前u-boot-1.1.6支持10种架构──根目录下有10个类似lib_i386的目 录、31个型号(类型)的CPU──cpu目录下有31个子目录,214种开发板──board目录下有214个子目录,很容易从中找到与自己的板子相似 的配置,在上面稍作修改即可使用。
2.3 U-Boot的配置、编译、连接过程
1. U-Boot初体验
u-boot-1.1.6中有几千个文件,要想了解对于某款开发板,使用哪些文件、哪个文件首先执行、可执行文件占用内存的情况,最好的方法就是阅读它的Makefile。
根据顶层Readme文件的说明,可以知道如果要使用开发板board/,就先执行“make _config”命令进行配置,然后执行“make all”,就可以生成如下3个文件:
u-boot.bin:二进制可执行文件,它就是可以直接烧入ROM、NOR Flash的文件。
u-boot:ELF格式的可执行文件
u-boot.srec:Motorola S-Record格式的可执行文件
对于S3C2410的开发板,执行“make smdk2410_config”、“make all”后生成的u-boot.bin可以烧入NOR Flash中运行。启动后可以看到串口输出一些信息后进入控制界面,等待用户的输入。
对于S3C2440的开发板,烧入上面生成的u-boot.bin,串口无输出,需要修改代码。
在修改代码之前,先看看上面两个命令“make smdk2410_config”、“make all”做了什么事情,以了解程序的流程,知道要修改哪些文件。
另外,编译U-Boot成功后,还会在它的tools子目录下生成一些工具,比如mkimage等。将它们复制到/usr/local/bin目录下,以后就可以直接使用它们了,比如编译内核时,会使用mkimage来生成U-Boot格式的内核映像文件uImage。
2. U-Boot的配置过程
在顶层Makefile中可以看到如下代码:
SRCTREE:= $(CURDIR)
……
MKCONFIG:= $(SRCTREE)/mkconfig
……
smdk2410_config:unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
假定我们在u-boot-1.1.6的根目录下编译,则其中的MKCONFIG就是根目录下 的mkconfig文件。$(@:_config=)的结果就是将“smdk2410_config”中的“_config”去掉,结果为 “smdk2410”。所以“make smdk2410_config”实际上就是执行如下命令:
./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0
再来看看mkconfig的作用,在mkconfig文件开头第6行给出了它的用法:
06 # Parameters: Target Architecture CPU Board [VENDOR] [SOC]
这里解释一下概念,对于S3C2410、S3C2440,它们被称为SoC(System on Chip),上面除CPU外,还集成了包括UART、USB控制器、NAND Flash控制器等等设备(称为片内外设)。S3C2410/S3C2440中的CPU为arm920t。
以下,分步骤分析mkconfig的作用:
(1)确定开发板名称BOARD_NAME。
11 APPEND=no# Default: Create new config file
12 BOARD_NAME=""# Name to print in make output
13
14 while [ $# -gt 0 ] ; do
15 case "$1" in
16 --) shift ; break ;;
17 -a) shift ; APPEND=yes ;;
18 -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
19 *) break ;;
20 esac
21 done
22
23 [ "${BOARD_NAME}" ] || BOARD_NAME="$1"
对于“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令,其中没有“--”、“-a”、“-n”等符号,所以第14~22行没做任何事情。第11、12行两个变量仍维持原来的值。
执行完第23行后,BOARD_NAME的值等于第1个参数,即“smdk2410”。
(2)创建到平台/开发板相关的头文件的链接。
略过mkconfig文件中的一些没有起作用的行:
30 #
31 # Create link to architecture specific headers
32 #
33 if [ "$SRCTREE" != "$OBJTREE" ] ; then
……
45 else
46 cd ./include
47 rm -f asm
48 ln -s asm-$2 asm
49 fi
50
第33行判断源代码目录和目标文件目录是否一样,可以选择在其他目录下编译U-Boot,这可以令源代码目录保持干净,可以同时使用不同的配置进行编译。不过本书直接在源代码目录下编译的,第33行的条件不满足,将执行else分支的代码。
第46~48行进入include目录,删除asm文件(这是上一次配置时建立的链接文件),然后再次建立asm文件,并令它链接向asm-$2目录,即asm-arm。
继续往下看代码:
51 rm -f asm-$2/arch
52
53 if [ -z "$6" -o "$6" = "NULL" ] ; then
54 ln -s ${LNPREFIX}arch-$3 asm-$2/arch
55 else
56 ln -s ${LNPREFIX}arch-$6 asm-$2/arch
57 fi
58
59 if [ "$2" = "arm" ] ; then
60 rm -f asm-$2/proc
61 ln -s ${LNPREFIX}proc-armv asm-$2/proc
62 fi
63
第51行删除asm-$2/arch目录,即asm-arm/arch。
对于“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令,$6为“s3c24x0”,不为空,也不是“NULL”,所以第53行的条件不满足,将执行else分支。
第56行中,LNPREFIX为空,所以这个命令实际上就是:ln -s arch-$6 asm-$2/arch,即:ln -s arch-s3c24x0 asm-arm/arch。
第60、61行重新建立asm-arm/proc文件,并让它链接向proc-armv目录。
(3)创建顶层Makefile包含的文件include/config.mk。
对于“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令,上面几行代码创建的config.mk文件内容如下:
64 #
65 # Create include file for Make
66 #
67 echo "ARCH = $2" > config.mk
68 echo "CPU = $3" >> config.mk
69 echo "BOARD = $4" >> config.mk
70
71 [ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk
72
73 [ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk
74
ARCH = arm
CPU = arm920t
BOARD = smdk2410
SOC = s3c24x0
(4)创建开发板相关的头文件include/config.h。
75 #
76 # Create board specific header file
77 #
78 if [ "$APPEND" = "yes" ]# Append to existing config file
79 then
80 echo >> config.h
81 else
82 > config.h# Create new config file
83 fi
84 echo "/* Automatically generated - do not edit */" >>config.h
85 echo "#include " >>config.h
86
前面说过,APPEND维持原值“no”,所以config.h被重新建立,它的内容如下:
/* Automatically generated - do not edit */
#include "
现在总结一下,配置命令“make smdk2410_config”,实际的作用就是执行“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令。假设执行“./mkconfig $1 $2 $3 $4 $5 $6”命令,则将产生如下结果:
(1)开发板名称BOARD_NAME等于$1;
(2)创建到平台/开发板相关的头文件的链接:
ln -s asm-$2 asm
ln -s arch-$6 asm-$2/arch
ln -s proc-armv asm-$2/proc# 如果$2不是arm的话,此行没有
(3) 创建顶层Makefile包含的文件include/config.mk。
ARCH = $2
CPU = $3
BOARD = $4
VENDOR = $5# $5为空,或者是NULL的话,此行没有
SOC = $6# $6为空,或者是NULL的话,此行没有
(4)创建开发板相关的头文件include/config.h。
/* Automatically generated - do not edit */
#include "
从这4个结果可以知道,如果要在board目录下新建一个开发 板的目录,则在include/config目录下也要建立一个文件.h,里 面存放的就是开发板的配置信息。
U-Boot还没有类似Linux一样的可视化配置界面(比如使用make menuconfig来配置),要手动修改配置文件include/config/.h来裁减、设置U-Boot。
配置文件中有两类宏:
(1)一类是选项(Options),前缀为“CONFIG_”,它们用于选择CPU、SOC、开发板类型,设置系统时钟、选择设备驱动等。比如:
#define CONFIG_ARM920T1/* This is an ARM920T Core*/
#defineCONFIG_S3C24101/* in a SAMSUNG S3C2410 SoC */
#define CONFIG_SMDK24101/* on a SAMSUNG SMDK2410 Board */
#define CONFIG_SYS_CLK_FREQ12000000/* the SMDK2410 has 12MHz input clock */
#define CONFIG_DRIVER_CS89001/* we have a CS8900 on-board */
(2)另一类是参数(Setting),前缀为“CFG_”,它们用于设置malloc缓冲池的大小、U-Boot的提示符、U-Boot下载文件时的默认加载地址、Flash的起始地址等。比如:
#define CFG_MALLOC_LEN(CFG_ENV_SIZE + 128*1024)
#defineCFG_PROMPT"100ASK> "/* Monitor Command Prompt*/
#defineCFG_LOAD_ADDR0x33000000/* default load address*/
#define PHYS_FLASH_10x00000000 /* Flash Bank #1 */
从下面的编译、连接过程可知,U-Boot中几乎每个文件都被编译和连接,但是这些文件是否包含有效的代码,则由宏开关来设置。比如对于网卡驱动drivers/cs8900.c,它的格式为:
#include /* 将包含配置文件include/config/.h */
……
#ifdef CONFIG_DRIVER_CS8900
/* 实际的代码 */
……
#endif/* CONFIG_DRIVER_CS8900 */
如果定义了宏CONFIG_DRIVER_CS8900,则文件中包含有效的代码;否则,文件被注释为空。
可以这样粗糙地认为,“CONFIG_”除了设置一些参数外,主要用来设置U-Boot的功能、选择使用文件中的哪一部分;而“CFG_”用来设置更细节的参数。
3. U-Boot的编译、连接过程
配置完后,执行“make all”即可编译,从Makefile中可以了解U-Boot使用了哪些文件、哪个文件首先执行、可执行文件占用内存的情况。
先确定用到哪些文件,下面只摘取Makefile中与arm相关的部分:
117 include $(OBJTREE)/include/config.mk
118 exportARCH CPU BOARD VENDOR SOC
119
……
127 ifeq ($(ARCH),arm)
128 CROSS_COMPILE = arm-linux-
129 endif
……
163 # load other configuration
164 include $(TOPDIR)/config.mk
165
第117、164行用于包含其他的config.mk文件,第117行所要包含文件的就是在 上面的配置过程中制作出来的include/config.mk文件,其中定义了ARCH、CPU、BOARD、SOC等4个变量的值为arm、 arm920t、smdk2410、s3c24x0。
第164行包含顶层目录的config.mk文件,它根据上面4个变量的值确定了编译器、编译选项等。其中对我们理解编译过程有帮助的是BOARDDIR、LDFLAGS的值,config.mk中:
88 BOARDDIR = $(BOARD)
……
91 sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk# include board specific rules
……
143 LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds
……
189 LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
在board/smdk2410/config.mk中,定义了“TEXT_BASE = 0x33F80000”。所以,最终结果如下:BOARDDIR为smdk2410;LDFLAGS中有“-T board/smdk2410/u-boot.lds -Ttext 0x33F80000”字样。
继续往下看Makefile:
166 #########################################################################
167 # U-Boot objects....order is important (i.e. start must be first)
168
169 OBJS = cpu/$(CPU)/start.o
……
193 LIBS = lib_generic/libgeneric.a
194 LIBS += board/$(BOARDDIR)/lib$(BOARD).a
195 LIBS += cpu/$(CPU)/lib$(CPU).a
……
199 LIBS += lib_$(ARCH)/lib$(ARCH).a
200 LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a \
201 fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a
202 LIBS += net/libnet.a
……
212 LIBS += $(BOARDLIBS)
213
……
从第169行得知,OBJS的第一个值为“cpu/$(CPU)/start.o”,即“cpu/arm920t/start.o”。
第193~213行指定了LIBS变量就是平台/开发板相关的各个目录、通用目录下相应的 库,比如:lib_generic/libgeneric.a、board/smdk2410/libsmdk2410.a、cpu/arm920t /libarm920t.a、lib_arm/libarm.a、fs/cramfs/libcramfs.a fs/fat/libfat.a等。
OBJS、LIBS所代表的.o、.a文件就是U-Boot的构成,它们通过如下命令由相应的源文件(或相应子目录下的文件)编译得到。
268 $(OBJS):
269 $(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
270
271 $(LIBS):
272 $(MAKE) -C $(dir $(subst $(obj),,$@))
273
274 $(SUBDIRS):
275 $(MAKE) -C $@ all
276
第268、269两行的规则表示,对于OBJS中的每个成员,都将进入cpu/$(CPU)目录(即cpu/arm920t)编译它们。现在OBJS为cpu/arm920t/start.o,它将由cpu/arm920t/start.S编译得到。
第271、272两行的规则表示,对于LIBS中的每个成员,都将进入相应的子目录执行“make”命令。这些子目录中的Makefile,结构相似,它们将Makefle中指定的文件编译、连接成一个库文件。
当所有的OBJS、LIBS所表示的.o和.a文件都生成后,就剩最后的连接了,这对应Makefile中如下几行:
246 $(obj)u-boot.srec:$(obj)u-boot
247 $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
248
249 $(obj)u-boot.bin:$(obj)u-boot
250 $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
251
……
262 $(obj)u-boot:depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
263 UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
264 cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
265 --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
266 -Map u-boot.map -o u-boot
267
先使用第262~266的规则连接得到ELF格式的u-boot,最后转换为二进制格式u- boot.bin、S-Record格式u-boot.srec。LDFLAGS确定了连接方式,其中的“-T board/smdk2410/u-boot.lds -Ttext 0x33F80000”字样指定了程序的布局、地址。board/smdk2410/u-boot.lds文件如下:
28 SECTIONS
29 {
30 . = 0x00000000;
31
32 . = ALIGN(4);
33 .text :
34 {
35 cpu/arm920t/start.o(.text)
36 *(.text)
37 }
38
39 . = ALIGN(4);
40 .rodata : { *(.rodata) }
41
42 . = ALIGN(4);
43 .data : { *(.data) }
44
45 . = ALIGN(4);
46 .got : { *(.got) }
47
48 . = .;
49 __u_boot_cmd_start = .;
50 .u_boot_cmd : { *(.u_boot_cmd) }
51 __u_boot_cmd_end = .;
52
53 . = ALIGN(4);
54 __bss_start = .;
55 .bss : { *(.bss) }
56 _end = .;
57 }
从第35行可知,cpu/arm920t/start.o被放在程序的最前面,所以U-Boot的入口点在cpu/arm920t/start.S中。
现在来总结一下U-Boot的编译流程:
(1)首先编译cpu/$(CPU)/start.S,对于不同的CPU,还可能编译cpu/$(CPU)下的其他文件。
(2)然后,对于平台/开发板相关的每个目录、每个通用目录,都使用它们各自的Makefile生成相应的库。
(3)将1、2步骤生成的.o、.a文件按照board/$(BOARDDIR)/config.mk文件中指定的代码段起始地址、board/$(BOARDDIR)/u-boot.lds连接脚本进行连接。
(4)第3步得到的是ELF格式的U-Boot,后面Makefile还会将它转换为二进制格式、S-Record格式。
2.4 U-Boot的启动过程源码分析
首先强调,本书使用的U-Boot从NOR Flash启动,下面以开发板smdk2410的U-Boot为例。
U-Boot属于两阶段的Bootloader,第一阶段的文件为cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S,前者是平台相关,后者是开发板相关。
U-Boot第一阶段代码分析
它与1.2节中描述的Bootloader第一阶段所完成的功能可以一一对应:
(1)硬件设备初始化。
依次完成如下设置:将CPU的工作模式设为管理模式(svc),关闭WATCHDOG,设置FCLK、HCLK、PCLK的比例(即设置CLKDIVN寄存器),关闭MMU、CACHE。
代码都在cpu/arm920t/start.S中,注释也比较完善,读者有不明白的地方可以参考前面硬件实验的相关章节。
(2)为加载Bootloader的第二阶段代码准备RAM空间。
所谓准备RAM空间,就是初始化内存芯片,使它可用。对于S3C2410/S3C2440, 通过在start.S中调用lowlevel_init函数来设置存储控制器,使得外接的SDRAM可用。代码在board/smdk2410 /lowlevel_init.S中。
注意:lowlevel_init.S文件是开发板相关的,这表示如果外接的设备不一样,可以修改lowlevel_init.S文件中的相关宏。
lowlevel_init函数并不复杂,只是要注意这时的代码、数据都只保存在NOR Flash上,内存中还没有,所以读取数据时要变换地址。代码如下:
129 _TEXT_BASE:
130 .wordTEXT_BASE
131
132 .globl lowlevel_init
133 lowlevel_init:
134 /* memory control configuration */
135 /* make r0 relative the current location so that it */
136 /* reads SMRDATA out of FLASH rather than memory ! */
137 ldr r0, =SMRDATA
138 ldrr1, _TEXT_BASE
139 subr0, r0, r1
140 ldrr1, =BWSCON/* Bus Width Status Controller */
141 add r2, r0, #13*4
142 0:
143 ldr r3, [r0], #4
144 str r3, [r1], #4
145 cmp r2, r0
146 bne 0b
147
148 /* everything is fine now */
149 movpc, lr
150
151 .ltorg
152 /* the literal pools origin */
153
154 SMRDATA:/* 13个寄存器的值 */
155 .word ……
156 .word ……
第137~139行进行地址变换,因为这时候内存中还没有数据,不能使用连接程序时确定的地址来读取数据:
第137行中SMRDATA 表示这13个寄存器的值存放的开始地址(连接地址),值为0x33F8xxxx,处于内存中。
第138行获得代码段的起始地址,它就是第130行中的“TEXT_BASE”,其值在board/smdk2410/config.mk中定义:“TEXT_BASE = 0x33F80000”。
第139行将0x33F8xxxx与0x33F80000相减,这就是13个寄存器值在NOR Flash上存放的开始地址。
(3)拷贝Bootloader的第二阶段代码到 RAM 空间中。
这里将整个U-Boot的代码(包括第一、第二阶段)都复制到SDRAM中,这在cpu/arm920t/start.S中实现:
164 relocate:/* 将U-Boot复制到RAM中 */
165 adrr0, _start/* r0 = 当前代码的开始地址 */
166 ldrr1, _TEXT_BASE/* r1 = 代码段的连接地址 */
167 cmp r0, r1 /* 测试现在是在Flash中还是在RAM中 */
168 beq stack_setup/* 如果已经在RAM中(这通常是调试时,直接下载到RAM中),
* 则不需要复制
*/
169
170 ldrr2, _armboot_start/* _armboot_start在前面定义,是第一条指令的运行地址 */
171 ldrr3, _bss_start/* 在连接脚本u-boot.lds中定义,是代码段的结束地址 */
172 subr2, r3, r2/* r2 = 代码段长度 */
173 addr2, r0, r2/* r2 = NOR Flash上代码段的结束地址 */
174
175 copy_loop:
176 ldmiar0!, {r3-r10}/* 从地址[r0]处获得数据 */
177 stmiar1!, {r3-r10}/* 复制到地址[r1]处 */
178 cmpr0, r2/* 判断是否复制完毕 */
179 blecopy_loop/* 没复制完,则继续 */
(4)设置好栈。
栈的设置灵活性很大,只要让sp寄存器指向一段没有使用的内存即可。
182 /* Set up the stack */
183 stack_setup:
184 ldr r0, _TEXT_BASE /* _TEXT_BASE为代码段的开始地址,值为0x33F80000 */
185 sub r0, r0, #CFG_MALLOC_LEN /* 代码段下面,留出一段内存以实现malloc */
186 sub r0, r0, #CFG_GBL_DATA_SIZE /* 再留出一段内存,存一些全局参数 */
187 #ifdef CONFIG_USE_IRQ
188 sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) /* IRQ、FIQ模式的栈 */
189 #endif
190 sub sp, r0, #12 /* 最后,留出12字节的内存给abort异常,
* 往下的内存就都是栈了
*/
191
到了这一步,读者可以知道内存的使用情况了,如下图所示(图中与上面的划分稍有不同,这是因为在cpu/arm920t/cpu.c中的cpu_init函数中才真正为IRQ、FIQ模式划分了栈): [[Image:]]
图3 U-Boot内存使用情况
(5)跳转到第二阶段代码的C入口点。
在跳转之前,还要清除BSS段(初始值为0、无初始值的全局变量、静态变量放在BSS段),代码如下:
192 clear_bss:
193 ldrr0, _bss_start/* BSS段的开始地址,它的值在连接脚本u-boot.lds中确定 */
194 ldrr1, _bss_end/* BSS段的结束地址,它的值在连接脚本u-boot.lds中确定 */
195 mov r2, #0x00000000
196
197 clbss_l:strr2, [r0]/* 往BSS段中写入0值 */
198 addr0, r0, #4
199 cmpr0, r1
200 bleclbss_l
201
现在,C函数的运行环境已经完全准备好,通过如下命令直接跳转(这之后,程序才在内存中执行),它将调用lib_arm/board.c中的start_armboot函数,这是第二阶段的入口点:
223 ldrpc, _start_armboot
224
225 _start_armboot:.word start_armboot
226
U-Boot第二阶段代码分析
它与15.1.2节中描述的Bootloader第二阶段所完成的功能基本上一致,不过顺序有点小差别。另外,U-Boot在启动内核之前可以让用户决定是否进入下载模式,即进入U-Boot的控制界面。
第二阶段从lib_arm/board.c中的start_armboot函数开始,先看从这个函数开始的程序流程图。
图3 U-Boot第二阶段流程图
移植U-Boot的主要工作在于对硬件的初始化、驱动,所以下面讲解时将重点放在硬件的操作上。
(1)初始化本阶段要使用到的硬件设备。:最主要的是设置系统时钟、初始化串口,只要这两个设置好了,就可以从串口看到打印信息。
board_init函数设置MPLL、改变系统时钟,它是开发板相关的函数,在board/smdk2410/smdk2410.c中实现。值得注意的是,board_init函数中还保存了机器类型ID,这将在调用内核时传给内核,代码如下:
/* arch number of SMDK2410-Board */
gd->bd->bi_arch_number = MACH_TYPE_SMDK2410; /* 值为193 */
串口的初始化函数主要是serial_init,它设置UART控制器,是CPU相关的函数,在cpu/arm920t/s3c24x0/serial.c中实现。
(2)检测系统内存映射(memory map)。对于特定的开发板,其内存的分布是明确的,所以可以直接设置。board/smdk2410/smdk2410.c中的dram_init函数 指定了本开发板的内存起始地址为0x30000000,大小为0x4000000。代码如下:
int dram_init (void)
{
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;/* 即0x300000000 */
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;/* 即0x4000000 */
return 0;
}
这些设置的参数,将在后面向内核传递参数时用到。
(3)U-Boot命令的格式。
从图3可以知道,即使是内核的启动,也是通过U-Boot命令来实现的。U-Boot中每个命令都通过U_BOOT_CMD宏来定义,格式如下:
U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help")
各项参数的意义为:
① name:命令的名字,注意,它不是一个字符串(不要用双引号括起来)。
② maxargs:最大的参数个数
③ repeatable:命令是否可重复,可重复是指运行一个命令后,下次敲回车即可再次运行。
④ command:对应的函数指针,类型为(*cmd)(struct cmd_tbl_s *, int, int, char *[])。
⑤ usage:简短的使用说明,这是个字符串。
⑥ help:较详细的使用说明,这是个字符串。
宏U_BOOT_CMD在include/command.h中定义:
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
Struct_Section也是在include/command.h中定义:
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
比如对于bootm命令,它如此定义:
U_BOOT_CMD(
bootm,CFG_MAXARGS,1,do_bootm,
“string1”,
“string2”
);
宏U_BOOT_CMD扩展开后就是:
cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".u_boot_cmd"))) = {“bootm”, CFG_MAXARGS, 1, do_bootm, “string1”, “string2”};
对于每个使用U_BOOT_CMD宏来定义的命令,其实都是在".u_boot_cmd"段中定义一个cmd_tbl_t结构。连接脚本u-boot.lds中有这么一段:
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
程序中就是根据命令的名字在内存段__u_boot_cmd_start~__u_boot_cmd_end找到它的cmd_tbl_t结构,然后调用它的函数(请参考common/command.c中的find_cmd函数)。
内核的复制和启动,可以通过如下命令来完成:bootm从内存、ROM、NOR Flash中启动内核,bootp则通过网络来启动,而nboot从NAND Flash启动内核。它们都是先将内核映像从各种媒介中读出,存放在指定的位置;然后设置标记列表以给内核传递参数;最后跳到内核的入口点去执行。具体实 现的细节不再描述,有兴趣的读者可以阅读common/cmd_boot.c、common/cmd_net.c、common/cmd_nand.c来 了解它们的实现。
(4)为内核设置启动参数。
与15.1.2小节中《Bootloader与内核的交互》所描述的一样,U-Boot也是 通过标记列表向内核传递参数。并且,15.1.2小节中内存标记、命令行标记的示例代码就是取自U-Boot中的setup_memory_tags、 setup_commandline_tag函数,它们都是在lib_arm/armlinux.c中定义。一般而言,设置这两个标记就可以了,在配置文 件include/configs/smdk2410.h中增加如下两个配置项即可:
#define CONFIG_SETUP_MEMORY_TAGS 1
#define CONFIG_CMDLINE_TAG 1
对于ARM架构的CPU,都是通过lib_arm/armlinux.c中的 do_bootm_linux函数来启动内核。这个函数中,设置标记列表,最后通过“theKernel (0, bd->bi_arch_number, bd->bi_boot_params)”调用内核。其中,theKernel指向内核存放的地址(对于ARM架构的CPU,通常是 0x30008000),bd->bi_arch_number就是前面board_init函数设置的机器类型ID,而 bd->bi_boot_params就是标记列表的开始地址。
2.5 U-Boot的移植
开发板smdk2410的配置适用于大多数S3C2410单板,或是只需要极少的修改即可使用。但是目前U-Boot中没有对S3C2440的支持,需要我们自己移植。
本书基于的S3C2410、S3C2440两款开发板,它们的外接硬件相同:
BANK0外接容量为1MB,位宽为8的NOR Flash芯片AM29LV800
BANK3外接10M网卡芯片CS8900,位宽为16
BANK6外接两片容量为32MB、位宽为16的SDRAM芯片K4S561632,组成容量为64MB、位宽为32的内存
通过NAND Flash控制器外接容量为64MB,位宽为8的NAND Flash芯片K9S1208
对于NOR Flash和NAND Flash,如图15.4所示划分它们的使用区域。由于NAND Flash的“位反转”现象比较常见,为保证数据的正确,在读写数据时需要使用ECC较验。另外,NAND Flash在使用过程中、运输过程中还有可能出现坏块。所以本书选择在NOR Flash中保存U-Boot,在NAND Flash中保存内核和文件系统,并在使用U-Boot烧写内核、文件系统时,进行坏块检查、ECC较验。这样,即使NAND Flash出现坏块导致内核或文件系统不能使用,也可以通过NOR Flash中的U-Boot来重新烧写。 [[Image:]]
图15.4 开发板固态存储器分区划分
smdk2410开发板已经支持NOR Flash芯片AM29LV800,U-Boot本身也已经支持jffs2文件系统映像的烧写。下面一步一步移植U-Boot(所有的修改都在补丁文件 u-boot-1.1.6_100ask24x0.patch里,读者可以直接打补丁),增加如下新功能:
同时支持本书使用的S3C2410和S3C2440开发板
支持串口xmodem协议
支持网卡芯片CS8900
支持NAND Flash读写
支持烧写yaffs文件系统映像
1. 同时支持S3C2410和S3C2440
我们将在开发板smdk2410的基础上进行移植。
(1)新建一个开发板的相应目录和文件。
为了不破坏原来的代码,在board目录下将smdk2410复制为100ask24x0目录,并将board/100ask24x0/smdk2410.c改名为100ask24x0.c。
根据前面描述的配置过程可知,还要在include/configs目录下建立一个配置文件100ask24x0.h,可以将include/configs/smdk2410.h直接复制为100ask24x0.h。
还要修改两个Makefile,首先在顶层Makefile中增加如下两行:
100ask24x0_config:unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t 100ask24x0 NULL s3c24x0
然后在board/100ask24x0/Makefile中,如下修改(因为前面将smdk2410.c文件改名为100ask24x0.c了):
COBJS:= smdk2410.o flash.o
改为:
COBJS:= 100ask24x0.o flash.o
(2)修改SDRAM的配置。
SDRAM的初始化在U-Boot的第一阶段完成,就是在board/100ask24x0/lowlevel_init.S文件中设置存储控制器。
检查一下BANK6的设置:位宽为32──宏B6_BWSCON刚好为DW32(表示32位),无需改变;另外还要根据HCLK设置SDRAM的刷新参数,主要是REFCNT寄存器。
本书所用开发板的HCLK都设为100MHz,需要根据SDRAM芯片的具体参数重新计算REFCNT寄存器的值(请参考第6章)。代码修改如下:
126 #define REFCNT 1113/* period=15.6us, HCLK=60Mhz, (2048+1-15.6*60) */
改为
126 #define REFCNT 0x4f4/* period=7.8125us, HCLK=100Mhz, (2048+1-7.8125*100) */
对于其他BANK,比如网卡芯片CS8900所在的BANK2,原来的设置刚好匹配,无需更改;而对于BANK1、2、4、5、7,在U-Boot中并没有使用到它们外接的设备,也不需要理会。
(3)增加对S3C2440的支持。
S3C2440是S3C2410的改进版,它们的操作基本相似。不过在系统时钟的设置、 NAND Flash控制器的操作等方面,有一些小差别。它们的MPLL、UPLL计算公式不一样,FCLK、HCLK和PCLK的分频化设置也不一样,这在下面的 代码中可以看到。NAND Flash控制器的差别在增加对NAND Flash的支持时讲述。
本章的目标是令同一个U-Boot二进制代码既可以在S3C2410上运行,也可以在 S3C2440上运行。首先需要在代码中自动识别是S3C2410还是S3C2440,这可以通过读取GSTATUS1寄存器的值来分 辨:0x32410000表示S3C2410,0x32410002表示S3C2410A,0x32440000表示 S3C2440,0x32440001表示S3C2440A。S3C2410和S3C2410A、S3C2440和S3C2440A,对本书来说没有区 别。
对于S3C2410开发板,将FCLK设为200MHz,分频比为 FCLK:HCLK:PCLK=1:2:4;对于S3C2440开发板,将FCLK设为400MHz,分频比为 FCLK:HCLK:PCLK=1:4:8。还将UPLL设为48MHz,即UCLK为48MHz,以在内核中支持USB控制器。
首先修改board/100ask24x0/100ask24x0.c中的board_init函数,下面是修改后的代码:
33 /* S3C2440: MPLL = (2*m * Fin) / (p * 2^s), UPLL = (m * Fin) / (p * 2^s)
34 * m = M (the value for divider M)+ 8, p = P (the value for divider P) + 2
35 */
36 #define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01))
37 #define S3C2440_UPLL_48MHZ ((0x38<<12)|(0x02<<4)|(0x02))
38 #define S3C2440_CLKDIV 0x05 /* FCLK:HCLK:PCLK = 1:4:8, UCLK = UPLL */
39
40 /* S3C2410: Mpll,Upll = (m * Fin) / (p * 2^s)
41 * m = M (the value for divider M)+ 8, p = P (the value for divider P) + 2
42 */
43 #define S3C2410_MPLL_200MHZ ((0x5c<<12)|(0x04<<4)|(0x00))
44 #define S3C2410_UPLL_48MHZ ((0x28<<12)|(0x01<<4)|(0x02))
45 #define S3C2410_CLKDIV 0x03 /* FCLK:HCLK:PCLK = 1:2:4 */
46
上面几行针对S3C2410、S3C2440分别定义了MPLL、UPLL寄存器的值。开发 板输入时钟为12MHz(这在include/configs/100ask24x0.h中的宏CONFIG_SYS_CLK_FREQ中定义),读者可 以根据代码中的计算公式针对自己的开发板修改系统时钟。
下面是针对S3C2410、S3C2440,分别使用不同的宏设置系统时钟:
58 int board_init (void)
59 {
60 S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
61 S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();
62
63 /* 设置GPIO */
64 gpio->GPACON = 0x007FFFFF;
65 gpio->GPBCON = 0x00044555;
66 gpio->GPBUP = 0x000007FF;
67 gpio->GPCCON = 0xAAAAAAAA;
68 gpio->GPCUP = 0x0000FFFF;
69 gpio->GPDCON = 0xAAAAAAAA;
70 gpio->GPDUP = 0x0000FFFF;
71 gpio->GPECON = 0xAAAAAAAA;
72 gpio->GPEUP = 0x0000FFFF;
73 gpio->GPFCON = 0x000055AA;
74 gpio->GPFUP = 0x000000FF;
75 gpio->GPGCON = 0xFF95FFBA;
76 gpio->GPGUP = 0x0000FFFF;
77 gpio->GPHCON = 0x002AFAAA;
78 gpio->GPHUP = 0x000007FF;
79
80 /* 同时支持S3C2410和S3C2440, www.100ask.net */
81 if ((gpio->GSTATUS1 == 0x32410000) || (gpio->GSTATUS1 == 0x32410002))
82 {
83 /* FCLK:HCLK:PCLK = 1:2:4 */
84 clk_power->CLKDIVN = S3C2410_CLKDIV;
85
86 /* 修改为异步总线模式 */
87 __asm__( "mrc p15, 0, r1, c1, c0, 0\n" /* read ctrl register */
88 "orr r1, r1, #0xc0000000\n" /* Asynchronous */
89 "mcr p15, 0, r1, c1, c0, 0\n" /* write ctrl register */
90 :::"r1"
91 );
92
93 /* 设置PLL锁定时间 */
94 clk_power->LOCKTIME = 0xFFFFFF;
95
96 /* 配置MPLL */
97 clk_power->MPLLCON = S3C2410_MPLL_200MHZ;
98
99 /* 配置MPLL后,要延时一段时间再配置UPLL */
100 delay (4000);
101
102 /* 配置UPLL */
103 clk_power->UPLLCON = S3C2410_UPLL_48MHZ;
104
105 /* 再延时一会 */
106 delay (8000);
107
108 /* 机器类型ID,这在调用Linux内核时用到 */
109 gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
110 }
111 else
112 {
113 /* FCLK:HCLK:PCLK = 1:4:8 */
114 clk_power->CLKDIVN = S3C2440_CLKDIV;
115
116 /* 修改为异步总线模式 */
117 __asm__( "mrc p15, 0, r1, c1, c0, 0\n" /* read ctrl register */
118 "orr r1, r1, #0xc0000000\n" /* Asynchronous */
119 "mcr p15, 0, r1, c1, c0, 0\n" /* write ctrl register */
120 :::"r1"
121 );
122
123 /* 设置PLL锁定时间 */
124 clk_power->LOCKTIME = 0xFFFFFF;
125
126 /* 配置MPLL */
127 clk_power->MPLLCON = S3C2440_MPLL_400MHZ;
128
129 /* 配置MPLL后,要延时一段时间再配置UPLL */
130 delay (4000);
131
132 /* 配置UPLL */
133 clk_power->UPLLCON = S3C2440_UPLL_48MHZ;
134
135 /* 再延时一会 */
136 delay (8000);
137
138 /* 机器类型ID,这在调用Linux内核时用到,这个值要与内核相对应 */
139 gd->bd->bi_arch_number = MACH_TYPE_S3C2440;
140 }
141
142 /* 启动内核时,参数存放位置。这个值在构造标记列表时用到 */
143 gd->bd->bi_boot_params = 0x30000100;
144
145 icache_enable();
146 dcache_enable();
147
148 return 0;
149 }
150
最后一步:获取系统时钟的函数需要针对S3C2410、S3C2440的不同进行修改。
在后面设置串口波特率时需要获得系统时钟,就是在U-Boot的第二阶 段,lib_arm/board.c中start_armboot函数调用serial_init函数初始化串口时,会调用get_PCLK函数。它在 cpu/arm920t/s3c24x0/speed.c中定义,与它相关的还有get_HCLK、get_PLLCLK等函数。
前面的board_init函数在识别出S3C2410或S3C2440后,设置了机器类型 ID:gd->bd->bi_arch_number,后面的函数可以通过它来分辨是S3C2410还是S3C2440。首先要在程序的开头 增加如下一行,这样才可以使用gd变量:
DECLARE_GLOBAL_DATA_PTR;
S3C2410和S3C2440的MPLL、UPLL计算公式不一样,所以get_PLLCLK函数也需要修改:
56 static ulong get_PLLCLK(int pllreg)
57 {
58 S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
59 ulong r, m, p, s;
60
61 if (pllreg == MPLL)
62 r = clk_power->MPLLCON;
63 else if (pllreg == UPLL)
64 r = clk_power->UPLLCON;
65 else
66 hang();
67
68 m = ((r & 0xFF000) >> 12) + 8;
69 p = ((r & 0x003F0) >> 4) + 2;
70 s = r & 0x3;
71
72 /* 同时支持S3C2410和S3C2440, by www.100ask.net */
73 if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410)
74 return((CONFIG_SYS_CLK_FREQ * m) / (p << s));
75 else
76 return((CONFIG_SYS_CLK_FREQ * m * 2) / (p << s)); /* S3C2440 */
77 }
78
由于分频系数的设置方法也不一样,get_HCLK、get_PCLK也需要修改。对于S3C2410,沿用原来的计算方法,else分支中是S3C2440的代码:
85 /* for s3c2440 */
86 #define S3C2440_CLKDIVN_PDIVN (1<<0)
87 #define S3C2440_CLKDIVN_HDIVN_MASK (3<<1)
88 #define S3C2440_CLKDIVN_HDIVN_1 (0<<1)
89 #define S3C2440_CLKDIVN_HDIVN_2 (1<<1)
90 #define S3C2440_CLKDIVN_HDIVN_4_8 (2<<1)
91 #define S3C2440_CLKDIVN_HDIVN_3_6 (3<<1)
92 #define S3C2440_CLKDIVN_UCLK (1<<3)
93
94 #define S3C2440_CAMDIVN_CAMCLK_MASK (0xf<<0)
95 #define S3C2440_CAMDIVN_CAMCLK_SEL (1<<4)
96 #define S3C2440_CAMDIVN_HCLK3_HALF (1<<8)
97 #define S3C2440_CAMDIVN_HCLK4_HALF (1<<9)
98 #define S3C2440_CAMDIVN_DVSEN (1<<12)
99
100 /* return HCLK frequency */
101 ulong get_HCLK(void)
102 {
103 S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
104 unsigned long clkdiv;
105 unsigned long camdiv;
106 int hdiv = 1;
107
108 /* 同时支持S3C2410和S3C2440, by www.100ask.net */
109 if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410)
110 return((clk_power->CLKDIVN & 0x2) ? get_FCLK()/2 : get_FCLK());
111 else
112 {
113 clkdiv = clk_power->CLKDIVN;
114 camdiv = clk_power->CAMDIVN;
115
116 /* 计算分频比 */
117
118 switch (clkdiv & S3C2440_CLKDIVN_HDIVN_MASK) {
119 case S3C2440_CLKDIVN_HDIVN_1:
120 hdiv = 1;
121 break;
122
123 case S3C2440_CLKDIVN_HDIVN_2:
124 hdiv = 2;
125 break;
126
127 case S3C2440_CLKDIVN_HDIVN_4_8:
128 hdiv = (camdiv & S3C2440_CAMDIVN_HCLK4_HALF) ? 8 : 4;
129 break;
130
131 case S3C2440_CLKDIVN_HDIVN_3_6:
132 hdiv = (camdiv & S3C2440_CAMDIVN_HCLK3_HALF) ? 6 : 3;
133 break;
134 }
135
136 return get_FCLK() / hdiv;
137 }
138 }
139
140 /* return PCLK frequency */
141 ulong get_PCLK(void)
142 {
143 S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
144 unsigned long clkdiv;
145 unsigned long camdiv;
146 int hdiv = 1;
147
148 /* 同时支持S3C2410和S3C2440, by www.100ask.net */
149 if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410)
150 return((clk_power->CLKDIVN & 0x1) ? get_HCLK()/2 : get_HCLK());
151 else
152 {
153 clkdiv = clk_power->CLKDIVN;
154 camdiv = clk_power->CAMDIVN;
155
156 /* 计算分频比 */
157
158 switch (clkdiv & S3C2440_CLKDIVN_HDIVN_MASK) {
159 case S3C2440_CLKDIVN_HDIVN_1:
160 hdiv = 1;
161 break;
162
163 case S3C2440_CLKDIVN_HDIVN_2:
164 hdiv = 2;
165 break;
166
167 case S3C2440_CLKDIVN_HDIVN_4_8:
168 hdiv = (camdiv & S3C2440_CAMDIVN_HCLK4_HALF) ? 8 : 4;
169 break;
170
171 case S3C2440_CLKDIVN_HDIVN_3_6:
172 hdiv = (camdiv & S3C2440_CAMDIVN_HCLK3_HALF) ? 6 : 3;
173 break;
174 }
175
176 return get_FCLK() / hdiv / ((clkdiv & S3C2440_CLKDIVN_PDIVN)? 2:1);
177 }
178 }
179
现在重新执行“make 100ask24x0_config”和“make all”生成的u-boot.bin文件既可以运行于S3C2410开发板,也可以运行于S3C2440开发板。将它烧入NOR Flash后启动,就可以在串口工具(设置为115200,8N1)中看到提示信息,可以输入各种命令操作U-Boot了。
(4)选择NOR Flash的型号。
但是,现在还无法通过U-Boot命令烧写NOR Flash。本书所用开发板中的NOR Flash型号为AM29LV800,而配置文件include/configs/100ask24x0.h中的默认型号为AM29LV400。修改如下:
#define CONFIG_AMD_LV4001/* uncomment this if you have a LV400 flash */
#if 0
#define CONFIG_AMD_LV8001/* uncomment this if you have a LV800 flash */
#endif
改为:
#if 0
#define CONFIG_AMD_LV4001/* uncomment this if you have a LV400 flash */
#endif
#define CONFIG_AMD_LV8001/* uncomment this if you have a LV800 flash */
本例中NOR Flash的操作函数在board/100ask24x0/flash.c中实现,它支持AM29LV400y和AM29LV800。对于其他型号的 NOR Flash,如果符合CFI接口标准,则可以在使用drivers/cfi_flash.c中的接口函数;否则,只好自己编写了。如果要使用 cfi_flash.c,如下修改两个文件:
在include/configs/100ask24x0.h中增加以下一行:
#define CFG_FLASH_CFI_DRIVER 1
在board/100ask24x0/Makefile中去掉flash.o:
COBJS:= 100ask24x0.o flash.o
改为:
COBJS:= 100ask24x0.o
修改好对NOR Flash的支持后,重新编译U-Boot:make clean、make all。运行后可以在串口中看到如下字样:
Flash: 1 MB
现在可以使用loadb、loady等命令通过串口下载文件,然后使用erase、cp命令分别擦除、烧写NOR Flash了,它们的效率比JTAG快上好几倍。
2. 支持串口xmodem协议
上面的loadb命令需要配合Linux下的kermit工具来使用,loady命令通过串 口ymodem协议来传输文件。Windows下的超级终端虽然支持ymodem,但是它的使用界面实在不友好。而本书推荐使用的Windows工具 SecureCRT只支持xmodem和zmodem。为了方便在Windows下开发,现在修改代码增加对xmodem的支持,即增加一个命令 loadx。
依照loady的实现来编写代码,首先使用U_BOOT_CMD宏来增加loadx命令:
/* 支持xmodem, www.100ask.net */
U_BOOT_CMD(
loadx, 3, 0,do_load_serial_bin,
"loadx - load binary file over serial line (xmodem mode)\n",
"[ off ] [ baud ]\n"
" - load binary file over serial line"
" with offset 'off' and baudrate 'baud'\n"
);
其次,在do_load_serial_bin函数中增加对loadx命令的处理分支。也是依照loady来实现:
481 /* 支持xmodem, www.100ask.net */
482 if (strcmp(argv[0],"loadx")==0) {
483 printf ("## Ready for binary (xmodem) download "
484 "to 0x%08lX at %d bps...\n",
485 offset,
486 load_baudrate);
487
488 addr = load_serial_xmodem (offset);
489
490 } else if (strcmp(argv[0],"loady")==0) {
491 printf ("## Ready for binary (ymodem) download "
492 "to 0x%08lX at %d bps...\n",
……
第481~490行就是为loadx命令增加的代码。
在第288行调用load_serial_xmodem函数,它是依照load_serial_ymodem实现的一个新函数:
36 #if (CONFIG_COMMANDS & CFG_CMD_LOADB)
37 /* 支持xmodem, www.100ask.net */
38 static ulong load_serial_xmodem (ulong offset);
39 static ulong load_serial_ymodem (ulong offset);
40 #endif
……
995 /* 支持xmodem, www.100ask.net */
996 static ulong load_serial_xmodem (ulong offset)
997 {
……
1003 char xmodemBuf[1024];/* 原来是ymodemBuf,这只是为了与函数名称一致 */
……
1008 info.mode = xyzModem_xmodem;/* 原来是xyzModem_ymodem,对应ymodem */
……
首先在文件开头增加load_serial_xmodem函数的声明,然后复制load_serial_ymodem函数为load_serial_xmodem,稍作修改:
① 将局部数组ymodemBuf改名为xmodemBuf,并在后面使用到的地方统一修改。这只是为了与函数名称一致。
② info.mode的值从xyzModem_ymodem改为xyzModem_xmodem。
重新编译、烧写u-boot.bin后,就可以使用loadx命令下载文件了。
3. 支持网卡芯片CS8900
使用串口来传输文件的速率太低,现在增加对网卡芯片CS8900的支持。
本书使用开发板的网卡芯片CS8900的连接方式与smdk2410完全一样,所以现在的 U-Boot中已经支持CS8900了,它的驱动程序为drivers/cs8900.c。只要在U-Boot控制界面中稍加配置就可以使用网络功能。使 用网络之前,先设置开发板IP地址、MAC地址,服务器IP地址,比如可以在U-Boot中执行以下命令:
setenv ipaddr 192.168.1.17
setenv ethaddr 08:00:3e:26:0a:5b
setenv serverip 192.168.1.11
saveenv
然后就可以使用tftp或nfs命令下载文件了,注意:服务器上要开启tftp或nfs服务。比如可以使用如下命令将u-boot.bin文件下载到内存0x30000000中:
tftp 0x30000000 u-boot.bin
或
nfs 0x30000000 192.168.1.57:/work/nfs_root/u-boot.bin
可以修改配置文件,让网卡的各个默认值就是上面设置的值。在此之前,先了解网卡的相关文件,这有助于移植代码以支持其他连接方式的CS8900。
首先,CS8900接在S3C2410、S3C2440的BANK3,位宽为16,使用WAIT、nBE信号。在设置存储控制器时要设置好BANK3。代码在board/100ask24x0/lowlevel_init.S中:
#define B3_BWSCON (DW16 + WAIT + UBLB)
……
/* 时序参数 */
#define B3_Tacs 0x0/* 0clk */
#define B3_Tcos 0x3/* 4clk */
#define B3_Tacc 0x7/* 14clk */
#define B3_Tcoh 0x1/* 1clk */
#define B3_Tah 0x0/* 0clk */
#define B3_Tacp 0x3 /* 6clk */
#define B3_PMC 0x0/* normal */
接下来,还要确定CS8900的基地址。这在配置文件include/configs/100ask24x0.h中定义:
#define CONFIG_DRIVER_CS89001/* 使用CS8900 */
#define CS8900_BASE0x19000300/* 基地址 */
#define CS8900_BUS161 /* 位宽为16 */
从第6章可以知道网卡CS8900的访问基址为0x19000000,之所以再偏移0x300是由它的特性决定的。
最后,还是在配置文件include/configs/100ask24x0.h中定义CS8900的各个默认地址:
#define CONFIG_ETHADDR08:00:3e:26:0a:5b
#define CONFIG_NETMASK 255.255.255.0
#define CONFIG_IPADDR192.168.1.17
#define CONFIG_SERVERIP192.168.1.11
额外的,如果要增加ping命令,还可以在配置文件include/configs/100ask24x0.h的宏CONFIG_COMMANDS中增加CFG_CMD_PING,如下:
#define CONFIG_COMMANDS \
(CONFIG_CMD_DFL | \
CFG_CMD_CACHE | \
CFG_CMD_PING | \
……
4. 支持NAND Flash
U-Boot 1.1.6中对NAND Flash的支持有新旧两套代码,新代码在drivers/nand目录下,旧代码在drivers/nand_legacy目录下。文档doc /README.nand对这两套代码有所说明:使用旧代码需要定义更多的宏,而新代码移植自Linux内核2.6.12,它更加智能,可以自动识别更多 型号的NAND Flash。目前之所以还保留旧的代码,是因为两个目标板NETTA、NETTA_ISDN使用JFFS文件系统,它们还依赖于旧代码。当相关功能移植到 新代码之后,旧的代码将从U-Boot中去除。
要让U-Boot支持NAND Flash,首先在配置文件include/configs/100ask24x0.h的宏CONFIG_COMMANDS中增加CFG_CMD_NAND,如下:
#define CONFIG_COMMANDS \
(CONFIG_CMD_DFL | \
CFG_CMD_CACHE | \
CFG_CMD_PING | \
CFG_CMD_NAND| \
……
然后选择使用哪套代码:在配置文件中定义宏CFG_NAND_LEGACY则使用旧代码,否则使用新代码。
使用旧代码时,需要实现drivers/nand_legacy/nand_legacy.c中使用到的各种宏,比如:
#define NAND_WAIT_READY(nand)/* 等待Nand Flash的状态为“就绪”,代码依赖于具体的开发板 */
#define WRITE_NAND_COMMAND(d, adr)/* 写NAND Flash命令,代码依赖于具体的开发板 */
本书使用新代码,下面讲述移植过程。
代码的移植没有现成的文档,可以在配置文件include/configs/100ask24x0.h的宏CONFIG_COMMANDS中增加CFG_CMD_NAND后,就编译代码,然后一个一个地解决出现的错误。编译结果中出现的错误和警告如下:
nand.h:412: error: `NAND_MAX_CHIPS' undeclared here (not in a function)
nand.c:35: error: `CFG_MAX_NAND_DEVICE' undeclared here (not in a function)
nand.c:38: error: `CFG_NAND_BASE' undeclared here (not in a function)
nand.c:35: error: storage size of `nand_info' isn't known
nand.c:37: error: storage size of `nand_chip' isn't known
nand.c:38: error: storage size of `base_address' isn't known
nand.c:37: warning: 'nand_chip' defined but not used
nand.c:38: warning: 'base_address' defined but not used
在配置文件include/configs/100ask24x0.h中增加如下3个宏就可 以解决上述错误。在Flash的驱动程序中,设备是逻辑上的概念,表示一组相同结构、访问函数相同的Flash芯片。在本书所用开发板中,只有一个 NAND Flash芯片,所以设备数为1,芯片数也为1。
#define CFG_NAND_BASE 0/* 无实际意义:基地址,这在board_nand_init中重新指定 */
#define CFG_MAX_NAND_DEVICE 1/* NAND Flash“设备”的数目为1 */
#define NAND_MAX_CHIPS 1/* 每个NAND Flash“设备”由1个NAND Flash“芯片”组成 */
修改配置文件后再次编译,现在只有一个错误了,“board_nand_init函数未定义”:
nand.c:50: undefined reference to `board_nand_init'
调用board_nand_init函数的过程为:NAND Flash的初始化入口函数是nand_init,它在lib_arm/board.c的start_armboot函数中被调用;nand_init函 数在drivers/nand/nand.c中实现,它调用相同文件中的nand_init_chip函数;nand_init_chip函数首先调用 board_nand_init函数来初始化NAND Flash设备,最后才是统一的识别过程。
从board_nand_init函数的名称就可以知道它是平台/开发板相关的函数,需要自 己编写。本书在cpu/arm920t/s3c24x0目录下新建一个文件nand_flash.c,在里面针对S3C2410、S3C2440实现了统 一的board_nand_init函数。
在编写board_nand_init函数的之前,需要针对S3C2410、S3C2440 NAND Flash控制器的不同定义一些数据结构和函数:
(1)在include/s3c24x0.h文件中增加S3C2440_NAND数据结构。
/* NAND FLASH (see S3C2440 manual chapter 6, www.100ask.net) */
typedef struct {
S3C24X0_REG32 NFCONF;
S3C24X0_REG32 NFCONT;
S3C24X0_REG32 NFCMD;
S3C24X0_REG32 NFADDR;
S3C24X0_REG32 NFDATA;
S3C24X0_REG32 NFMECCD0;
S3C24X0_REG32 NFMECCD1;
S3C24X0_REG32 NFSECCD;
S3C24X0_REG32 NFSTAT;
S3C24X0_REG32 NFESTAT0;
S3C24X0_REG32 NFESTAT1;
S3C24X0_REG32 NFMECC0;
S3C24X0_REG32 NFMECC1;
S3C24X0_REG32 NFSECC;
S3C24X0_REG32 NFSBLK;
S3C24X0_REG32 NFEBLK;
} /*__attribute__((__packed__))*/ S3C2440_NAND;
(2)在include/s3c2410.h文件中仿照S3C2410_GetBase_NAND函数定义S3C2440_GetBase_NAND函数。
/* for s3c2440, www.100ask.net */
static inline S3C2440_NAND * const S3C2440_GetBase_NAND(void)
{
return (S3C2440_NAND * const)S3C2410_NAND_BASE;
}
既然新的NAND Flash代码是从Linux内核2.6.12中移植来的,那么cpu/arm920t/s3c24x0/nand_flash.c文件也可以仿照内核 中,对S3C2410、S3C2440的NAND Flash进行初始化的drivers/mtd/nand/s3c2410.c文件来编写。为了方便阅读,先把cpu/arm920t/s3c24x0 /nand_flash.c文件的代码全部列出来,再讲解:
01 /*
02 * s3c2410/s3c2440的NAND Flash控制器接口, www.100ask.net
03 * 修改自Linux内核2.6.13文件drivers/mtd/nand/s3c2410.c
04 */
05
06 #include
07
08 #if (CONFIG_COMMANDS & CFG_CMD_NAND) && !defined(CFG_NAND_LEGACY)
09 #include
10 #include
11
12 DECLARE_GLOBAL_DATA_PTR;
13
14 #define S3C2410_NFSTAT_READY (1<<0)
15 #define S3C2410_NFCONF_nFCE (1<<11)
16
17 #define S3C2440_NFSTAT_READY (1<<0)
18 #define S3C2440_NFCONT_nFCE (1<<1)
19
20
21 /* S3C2410:NAND Flash的片选函数 */
22 static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip)
23 {
24 S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND();
25
26 if (chip == -1) {
27 s3c2410nand->NFCONF |= S3C2410_NFCONF_nFCE;/* 禁止片选信号 */
28 } else {
29 s3c2410nand->NFCONF &= ~S3C2410_NFCONF_nFCE;/* 使能片选信号 */
30 }
31 }
32
33 /* S3C2410:命令和控制函数
34 *
35 * 注意,这个函数仅仅根据各种命令来修改“写地址”IO_ADDR_W 的值(这称为tglx方法),
36 * 这种方法使得平台/开发板相关的代码很简单。
37 * 真正发出命令是在上一层NAND Flash的统一的驱动中实现,
38 * 它首先调用这个函数修改“写地址”,然后才分别发出控制、地址、数据序列。
39 */
40 static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd)
41 {
42 S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND();
43 struct nand_chip *chip = mtd->priv;
44
45 switch (cmd) {
46 case NAND_CTL_SETNCE:
47 case NAND_CTL_CLRNCE:
48 printf("%s: called for NCE\n", __FUNCTION__);
49 break;
50
51 case NAND_CTL_SETCLE:
52 chip->IO_ADDR_W = (void *)&s3c2410nand->NFCMD;
53 break;
54
55 case NAND_CTL_SETALE:
56 chip->IO_ADDR_W = (void *)&s3c2410nand->NFADDR;
57 break;
58
59 /* NAND_CTL_CLRCLE: */
60 /* NAND_CTL_CLRALE: */
61 default:
62 chip->IO_ADDR_W = (void *)&s3c2410nand->NFDATA;
63 break;
64 }
65 }
66
67 /* S3C2410:查询NAND Flash状态
68 *
69 * 返回值:0 – 忙, 1 – 就绪
70 */
71 static int s3c2410_nand_devready(struct mtd_info *mtd)
72 {
73 S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND();
74
75 return (s3c2410nand->NFSTAT & S3C2410_NFSTAT_READY);
76 }
77
78
79 /* S3C2440:NAND Flash的片选函数 */
80 static void s3c2440_nand_select_chip(struct mtd_info *mtd, int chip)
81 {
82 S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
83
84 if (chip == -1) {
85 s3c2440nand->NFCONT |= S3C2440_NFCONT_nFCE;/* 禁止片选信号 */
86 } else {
87 s3c2440nand->NFCONT &= ~S3C2440_NFCONT_nFCE;/* 使能片选信号 */
88 }
89 }
90
91 /* S3C2440:命令和控制函数,与s3c2410_nand_hwcontrol函数类似 */
92 static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd)
93 {
94 S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
95 struct nand_chip *chip = mtd->priv;
96
97 switch (cmd) {
98 case NAND_CTL_SETNCE:
99 case NAND_CTL_CLRNCE:
100 printf("%s: called for NCE\n", __FUNCTION__);
101 break;
102
103 case NAND_CTL_SETCLE:
104 chip->IO_ADDR_W = (void *)&s3c2440nand->NFCMD;
105 break;
106
107 case NAND_CTL_SETALE:
108 chip->IO_ADDR_W = (void *)&s3c2440nand->NFADDR;
109 break;
110
111 /* NAND_CTL_CLRCLE: */
112 /* NAND_CTL_CLRALE: */
113 default:
114 chip->IO_ADDR_W = (void *)&s3c2440nand->NFDATA;
115 break;
116 }
117 }
118
119 /* S3C2440:查询NAND Flash状态
120 *
121 * 返回值:0 – 忙, 1 – 就绪
122 */
123 static int s3c2440_nand_devready(struct mtd_info *mtd)
124 {
125 S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
126
127 return (s3c2440nand->NFSTAT & S3C2440_NFSTAT_READY);
128 }
129
130 /*
131 * Nand flash硬件初始化:
132 * 设置NAND Flash的时序, 使能NAND Flash控制器
133 */
134 static void s3c24x0_nand_inithw(void)
135 {
136 S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND();
137 S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
138
139 #define TACLS 0
140 #define TWRPH0 4
141 #define TWRPH1 2
142
143 if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410)
144 {
145 /* 使能NAND Flash控制器,初始化ECC,使能片选信号,设置时序 */
146 s3c2410nand->NFCONF = (1<<15)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
147 }
148 else
149 {
150 /* 设置时序 */
151 s3c2440nand->NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
152 /* 初始化ECC,使能NAND Flash控制器,使能片选信号 */
153 s3c2440nand->NFCONT = (1<<4)|(0<<1)|(1<<0);
154 }
155 }
156
157 /*
158 * 被drivers/nand/nand.c调用, 初始化NAND Flash硬件,初始化访问接口函数
159 */
160 void board_nand_init(struct nand_chip *chip)
161 {
162 S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND();
163 S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();
164
165 s3c24x0_nand_inithw();/* Nand flash硬件初始化 */
166
167 if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410) {
168 chip->IO_ADDR_R = (void *)&s3c2410nand->NFDATA;
169 chip->IO_ADDR_W = (void *)&s3c2410nand->NFDATA;
170 chip->hwcontrol = s3c2410_nand_hwcontrol;
171 chip->dev_ready = s3c2410_nand_devready;
172 chip->select_chip = s3c2410_nand_select_chip;
173 chip->options = 0;/* 设置位宽等,位宽为8 */
174 } else {
175 chip->IO_ADDR_R = (void *)&s3c2440nand->NFDATA;
176 chip->IO_ADDR_W = (void *)&s3c2440nand->NFDATA;
177 chip->hwcontrol = s3c2440_nand_hwcontrol;
178 chip->dev_ready = s3c2440_nand_devready;
179 chip->select_chip = s3c2440_nand_select_chip;
180 chip->options = 0;/* 设置位宽等,位宽为8 */
181 }
182
183 chip->eccmode = NAND_ECC_SOFT;/* ECC较验方式:软件ECC */
184 }
185
186 #endif
文件中分别针对S3C2410、S3C2440实现了NAND Flash最底层访问函数,并进行了一些硬件的设置(比如时序、使能NAND Flash控制器等)。新的代码对NAND Flash的封装做得很好,只要向上提供底层初始化函数board_nand_init来设置好平台/开发板相关的初始化、提供底层接口即可。
最后,只要将新建的nand_flash.c文件编入U-Boot中就可以擦除、读写NAND Flash了。如下修改cpu/arm920t/s3c24x0/Makefile文件即可:
COBJS = i2c.o interrupts.o serial.o speed.o \
usb_ohci.o
改为:
COBJS = i2c.o interrupts.o serial.o speed.o \
usb_ohci.o nand_flash.o
现在,可以使用新编译的u-boot.bin烧写内核映像到NAND Flash去了,请参考15.2.6。
5. 支持烧写yaffs文件系统映像
在实际生产中,可以通过烧片器等手段将内核、文件系统映像烧入固态存储设备中,Bootloader不需要具备烧写功能。但为了方便开发,通常在Bootloader中增加烧写内核、文件系统映像文件的功能。
增加了NAND Flash功能的U-Boot 1.1.6已经可以通过“nand write ……”、“nand write.jffs2 ……”等命令来烧写内核,cramfs、jffs2文件系统映像文件。但是在NAND Flash上,yaffs文件系统的性能更佳,下面增加“nand write.yaffs ……”命令以烧写yaffs文件系统映像文件。
“nand write.yaffs ……”字样的命令中,“nand”是具体命令,“write.yaffs ……”是参数。nand命令在common/cmd_nand.c中实现:
U_BOOT_CMD(nand, 5, 1, do_nand,
"nand - NAND sub-system\n",
"info - show available NAND devices\n"
"nand device [dev] - show or set current device\n"
"nand read[.jffs2] - addr off|partition size\n"
"nand write[.jffs2] - addr off|partiton size - read/write `size' bytes starting\n"
" at offset `off' to/from memory address `addr'\n"
……
先在其中增加“nand write.yaffs ……”的使用说明:
U_BOOT_CMD(nand, 5, 1, do_nand,
"nand - NAND sub-system\n",
"info - show available NAND devices\n"
"nand device [dev] - show or set current device\n"
"nand read[.jffs2] - addr off|partition size\n"
"nand write[.jffs2] - addr off|partiton size - read/write `size' bytes starting\n"
" at offset `off' to/from memory address `addr'\n"
"nand read.yaffs addr off size - read the `size' byte yaffs image starting\n"
" at offset `off' to memory address `addr'\n"
"nand write.yaffs addr off size - write the `size' byte yaffs image starting\n"
" at offset `off' from memory address `addr'\n"
……
然后,在nand命令的处理函数do_nand中增加对“write.yaffs ……”的支持。do_nand函数仍在common/cmd_nand.c中实现,代码修改如下:
331 (!strcmp(s, ".jffs2") || !strcmp(s, ".e") || !strcmp(s, ".i"))) {
……
354 }else if ( s != NULL && !strcmp(s, ".yaffs")){
355 if (read) {
356 /* read */
357 nand_read_options_t opts;
358 memset(&opts, 0, sizeof(opts));
359 opts.buffer = (u_char*) addr;
360 opts.length = size;
361 opts.offset = off;
362 opts.readoob = 1;
363 opts.quiet = quiet;
364 ret = nand_read_opts(nand, &opts);
365 } else {
366 /* write */
367 nand_write_options_t opts;
368 memset(&opts, 0, sizeof(opts));
369 opts.buffer = (u_char*) addr;/* yaffs文件系统映像存放的地址 */
370 opts.length = size;/* 长度 */
371 opts.offset = off;/* 要烧写到的NAND Flash的偏移地址 */
372 /* opts.forceyaffs = 1; *//* 计算ECC码的方法,没有使用 */
373 opts.noecc = 1; /* 不需要计算ECC,yaffs映像中有OOB数据 */
374 opts.writeoob = 1;/* 写OOB区 */
375 opts.blockalign = 1;/* 每个“逻辑上的块”大小为1个“物理块” */
376 opts.quiet = quiet;/* 是否打印提示信息 */
377 opts.skipfirstblk = 1;/* 跳过第一个可用块 */
378 ret = nand_write_opts(nand, &opts);
379 }
380 } else {
……
385 }
386
第354~379行就是针对命令“nand read.yaffs ……”、“nand write.yaffs ……”增加的代码。有兴趣的读者可以自己分析“if (read)”分支的代码,下面只讲解“else”分支,即“nand write.yaffs ……”命令的实现。
NAND Flash每一页大小为(512+16)字节(还有其他格式的NAND Flash,比如每页大小为(256+8)、(2048+64)等),其中的512字节就是一般存储数据的区域,16字节称为OOB(Out Of Band)区。通常在OOB区存放坏块标记、前面512字节的ECC较验码等。
cramfs、jffs2文件系统映像文件中并没有OOB区的内容,如果将它们烧入NOR Flash中,则是简单的“平铺”关系;如果将它们烧入NAND Flash中,则NAND Flash的驱动程序首先根据OOB的标记略过坏块,然后将一页数据(512字节)写入后,还会计算这512字节的ECC较验码,最后将它写入OOB区, 如此循环。cramfs、jffs2文件系统映像文件的大小通常是512的整数倍。
而yaffs文件系统映像文件的格式则跟它们不同,文件本身就包含了OOB区的数据(里面有 坏块标记、ECC较验码、其他yaffs相关的信息)。所以烧写时,不需要再计算ECC值,首先检查是否坏块(是则跳过),然后写入512字节的数据,最 后写入16字节的OOB数据,如此循环。yaffs文件系统映像文件的大小是(512+16)的整数倍。
注意:烧写yaffs文件系统映像时,分区上第一个可用的(不是坏块)块也要跳过。
下面分析上面的代码。
第369~371行设置源地址、目的地址、长度。烧写yaffs文件系统映像前,一般通过网 络将它下载到内存某个地址处(比如0x30000000),然后通过类似“nand write.yaffs 0x30000000 0x00A00000 $(filesize)”的命令烧到NAND Flash的偏移地址0x00A00000处。对于这个命令,第369行中opts.buffer等于0x30000000,第370行中 opts.length等于$(filesize)的值,就是前面下载的文件的大小,第371行中的opts.offset等于0x00A00000。
这里列出不使用的第372行,是因为opts.forceyaffs这个名字很有欺骗性,它其实是指计算ECC较验码的一种方法。烧写yaffs文件系统映像时,不需要计算ECC较验码。
第373、374行指定烧写数据时不计算ECC较验码、而是烧入文件中的OOB数据。
第375行指定“逻辑块”的大小,“逻辑块”可以由多个“物理块”组成,在yaffs文件系统映像中,它们是1:1的关系。
第377行的opts.skipfirstblk是新加的项,nand_write_options_t结构中没有skipfirstblk成员。它表示烧写时跳过第一个可用的逻辑块──这是由yaffs文件系统的特性决定的。
既然skipfirstblk是在nand_write_options_t结构中新加的项,那么就要重新定义nand_write_options_t结构,并在下面调用的nand_write_opts函数中对它进行处理。
首先在include/nand.h中如下修改,增加skipfirstblk成员:
struct nand_write_options {
u_char *buffer;/* memory block containing image to write */
ulong length;/* number of bytes to write */
ulong offset;/* start address in NAND */
int quiet;/* don't display progress messages */
int autoplace;/* if true use auto oob layout */
int forcejffs2;/* force jffs2 oob layout */
int forceyaffs;/* force yaffs oob layout */
int noecc;/* write without ecc */
int writeoob;/* image contains oob data */
int pad;/* pad to page size */
int blockalign;/* 1|2|4 set multiple of eraseblocks to align to */
int skipfirstblk; /* 新加,烧写时跳过第一个可用的逻辑块 */
};
typedef struct nand_write_options nand_write_options_t;
然后,修改nand_write_opts函数增加对skipfirstblk成员的支持。它在drivers/nand/nand_util.c文件中,下面的第301、第430~435行是新加的:
285 int nand_write_opts(nand_info_t *meminfo, const nand_write_options_t *opts)
286 {
……
300 int result;
301 int skipfirstblk = opts->skipfirstblk;
……
430 /* skip the first good block when wirte yaffs image, by www.100ask.net */
431 if (skipfirstblk) {
432 mtdoffset += erasesize_blockalign;
433 skipfirstblk = 0;
434 continue;
435 }
……
进行了上面的移植后,U-Boot已经可以烧yaffs文件系统映像了。由于前面设置“opts.noecc = 1”不使用ECC较验码,在烧写过程中会出现很多的提示信息:
Writing data without ECC to NAND-FLASH is not recommended
可以修改drivers/nand/nand_base.c文件的nand_write_page函数将它去掉:
917 case NAND_ECC_NONE:
918 printk (KERN_WARNING "Writing data without ECC to NAND-FLASH is not recommended\n");
改为:
917 case NAND_ECC_NONE:
918 //printk (KERN_WARNING "Writing data without ECC to NAND-FLASH is not recommended\n");
6. 修改默认配置参数以方便使用
前面移植网卡芯片CS8900时,已经设置过默认IP地址等。为了使用U-Boot时减少一些设置,现在修改配置文件include/configs/100ask24x0.h增加默认配置参数,其中一些在移植过程中已经增加的选项这里也再次说明。
(1)Linux启动参数。
增加如下3个宏:
#define CONFIG_SETUP_MEMORY_TAGS 1/* 向内核传递内存分布信息 */
#define CONFIG_CMDLINE_TAG 1/* 向内核传递命令行参数 */
/* 默认命令行参数 */
#define CONFIG_BOOTARGS "noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0"
(2)自动启动命令。
增加如下2个宏:
/* 自动启动前延时3秒 */
#define CONFIG_BOOTDELAY3
/* 自动启动的命令 */
#define CONFIG_BOOTCOMMAND “nboot 0x32000000 0 0; bootm 0x32000000”
自动启动时(开机3秒内无输入),首先执行“nboot 0x32000000 0 0”命令将第0个NAND Flash偏移地址0上的映像文件复制到内存0x32000000中;然后执行“bootm 0x32000000”命令启动内存中的映像。
(3)默认网络设置。
根据具体网络环境增加、修改下面4个宏:
#define CONFIG_ETHADDR08:00:3e:26:0a:5b
#define CONFIG_NETMASK 255.255.255.0
#define CONFIG_IPADDR192.168.1.17
#define CONFIG_SERVERIP192.168.1.11
2.6 U-Boot的常用命令
1. U-Boot的常用命令的用法
进入U-Boot控制界面后,可以运行各种命令,比如下载文件到内存,擦除、读写Flash,运行内存、NOR Flash、NAND Flash中的程序,查看、修改、比较内存中的数据等。
使用各种命令时,可以使用其开头的若干个字母代替它。比如tftpboot命令,可以使用t、tf、tft、tftp等字母代替,只要其他命令不以这些字母开头即可。
当运行一个命令之后,如果它是可重复执行的(代码中使用U_BOOT_CMD定义这个命令时,第3个参数是1),若想再次运行可以直接输入回车。
U-Boot接受的数据都是16进制,输入时可以省略前缀0x、0X。
下面介绍常用的命令:
(1)帮助命令help。
运行help命令可以看到U-Boot中所有命令的作用,如果要查看某个命令的使用方法,运行“help 命令名”,比如“help bootm”。
可以使用“?”来代替“help”,比如直接输入“?”、“? bootm”。
(2)下载命令。
U-Boot支持串口下载、网络下载,相关命令有:loadb、loads、loadx、loady和tftpboot、nfs。
前几个串口下载命令使用方法相似,以loadx命令为例,它的用法为“loadx [ off ] [ baud ]”。中括号“[]”表示里面的参数可以省略,off表示文件下载后存放的内存地址,baud表示使用的波特率。如果baud参数省略,则使用当前的波特 率;如果off参数省略,存放的地址为配置文件中定义的宏CFG_LOAD_ADDR。
tftpboot命令使用TFTP协议从服务器下载文件,服务器的IP地址为环境变量 serverip。用法为“tftpboot [loadAddress] [bootfilename]”,loadAddress表示文件下载后存放的内存地址,bootfilename表示要下载的文件的名称。如果 loadAddress省略,存放的地址为配置文件中定义的宏CFG_LOAD_ADDR;如果bootfilename省略,则使用单板的IP地址构造 一个文件名,比如单板IP为192.168.1.17,则缺省的文件名为C0A80711.img。
nfs命令使用NFS协议下载文件,用法为“nfs [loadAddress] [host ip addr:bootfilename]”。loadAddress、bootfilename的意义与tftpboot命令一样,host ip addr表示服务器的IP地址,默认为环境变量serverip。
下载文件成功后,U-Boot会自动创建或更新环境变量filesize,它表示下载的文件的长度,可以在后续命令中使用“$(filesize)”来引用它。
(3)内存操作命令。
常用的命令有:查看内存命令md、修改内存命令md、填充内存命令mw、拷贝命令cp。这些 命令都可以带上后缀“.b”、“.w”或“.l”,表示以字节、字(2个字节)、双字(4个字节)为单位进行操作。比如“cp.l 30000000 31000000 2”将从开始地址0x30000000处,拷贝2个双字到开始地址为0x31000000的地方。
md命令用法为“md[.b, .w, .l] address [count]”,表示以字节、字或双字(默认为双字)为单位,显示从地址address开始的内存数据,显示的数据个数为count。
mm命令用法为“mm[.b, .w, .l] address”,表示以字节、字或双字(默认为双字)为单位,从地址address开始修改内存数据。执行mm命令后,输入新数据后回车,地址会自动增加,Ctrl+C退出。
mw命令用法为“mw[.b, .w, .l] address value [count]”,表示以字节、字或双字(默认为双字)为单位,往开始地址为address的内存中填充count个数据,数据值为value。
cp命令用法为“cp[.b, .w, .l] source target count”,表示以字节、字或双字(默认为双字)为单位,从源地址source的内存拷贝count个数据到目的地址的内存。
(4)NOR Flash操作命令。
常用的命令有查看Flash信息的flinfo命令、加/解写保护命令protect、擦除 命令erase。由于NOR Flash的接口与一般内存相似,所以一些内存命令可以在NOR Flash上使用,比如读NOR Flash时可以使用md、cp命令,写NOR Flash时可以使用cp命令(cp根据地址分辨出是NOR Flash,从而调用NOR Flash驱动完成写操作)。
直接运行“flinfo”即可看到NOR Flash的信息,有NOR Flash的型号、容量、各扇区的开始地址、是否只读等信息。比如对于本书基于的开发板,flinfo命令的结果如下:
Bank # 1: AMD: 1x Amd29LV800BB (8Mbit)
Size: 1 MB in 19 Sectors
Sector Start Addresses:
00000000 (RO) 00004000 (RO) 00006000 (RO) 00008000 (RO) 00010000 (RO)
00020000 (RO) 00030000 00040000 00050000 00060000
00070000 00080000 00090000 000A0000 000B0000
000C0000 000D0000 000E0000 000F0000 (RO)
其中的RO表示该扇区处于写保护状态,只读。
对于只读的扇区,在擦除、烧写它之前,要先解除写保护。最简单的命令为“protect off all”,解除所有NOR Flash的写保护。
erase命令常用的格式为“erase start end”──擦除的地址范围为start至end、“erase start +len”──擦除的地址范围为start至(start + len – 1),“erase all”──表示擦除所有NOR Flash。
注意:其中的地址范围,刚好是一个扇区的开始地址到另一个(或同一个)扇区的结束地址。比如要擦除Amd29LV800BB的前5个扇区,执行的命令为“erase 0 0x2ffff”,而非“erase 0 0x30000”。
(5)NAND Flash操作命令。
NAND Flash操作命令只有一个:nand,它根据不同的参数进行不同操作,比如擦除、读取、烧写等。
“nand info”查看NAND Flash信息。
“nand erase [clean] [off size]”擦除NAND Flash。加上“clean”时,表示在每个块的第一个扇区的OOB区加写入清除标记;off、size表示要擦除的开始偏移地址和长度,如果省略 off和size,表示要擦除整个NAND Flash。
“nand read[.jffs2] addr off size”从NAND Flash偏移地址off处读出size个字节的数据,存放到开始地址为addr的内存中。是否加后缀“.jffs”的差别只是读操作时的ECC较验方法不同。
“nand write[.jffs2] addr off size”把开始地址为addr的内存中的size个字节数据,写到NAND Flash的偏移地址off处。是否加后缀“.jffs”的差别只是写操作时的ECC较验方法不同。
“nand read.yaffs addr off size”从NAND Flash偏移地址off处读出size个字节的数据(包括OOB区域),存放到开始地址为addr的内存中。
“nand write.yaffs addr off size”把开始地址为addr的内存中的size个字节数据(其中有要写入OOB区域的数据),写到NAND Flash的偏移地址off处。
“nand dump off”,将NAND Flash偏移地址off的一个扇区的数据打印出来,包括OOB数据。
(6)环境变量命令。
“printenv”命令打印全部环境变量,“printenv name1 name2 ...”打印名字为name1、name2、……”的环境变量。
“setenv name value”设置名字为name的环境变量的值为value。
“setenv name”删除名字为name的环境变量。
上面的设置、删除操作只是在内存中进行,“saveenv”将更改后的所有环境变量写入NOR Flash中。
(7)启动命令。
不带参数的“boot”、“bootm”命令都是执行环境变量bootcmd所指定的命令。
“bootm [addr [arg ...]]”命令启动存放在地址addr处的U-Boot格式的映像文件(使用U-Boot目录tools下的mkimage工具制作得到),[arg ...]表示参数。如果addr参数省略,映像文件所在地址为配置文件中定义的宏CFG_LOAD_ADDR。
“go addr [arg ...]”与bootm命令类似,启动存放在地址addr处的二进制文件, [arg ...]表示参数。
“nboot [[[loadAddr] dev] offset]”命令将NAND Flash设备dev上偏移地址off处的映像文件复制到内存loadAddr处,然后,如果环境变量autostart的值为“yes”,就启动这个映 像。如果loadAddr参数省略,存放地址为配置文件中定义的宏CFG_LOAD_ADDR;如果dev参数省略,则它的取值为环境变量 bootdevice的值;如果offset参数省略,则默认为0。
2. U-Boot命令使用实例
下面通过一个例子来演示如何使用各种命令烧写内核映像文件、yaffs映像文件,并启动系统。
(1)制作内核映像文件。
对于本书使用的Linux 2.6.22.6版本,编译内核时可以直接生成U-Boot格式的映像文件uImage。
对于不能直接生成uImage的内核,制作方法在U-Boot根目录下的README文件中 有说明,假设已经编译好的内核文件为vmlinux,它是ELF格式的。mkimage是U-Boot目录tools下的工具,它在编译U-Boot时自 动生成。执行以下3个命令将内核文件vmlinux制作为U-Boot格式的映像文件uImage,它们首先将vmlinux转换为二进制格式,然后压 缩,最后构造头部信息(里面包含有文件名称、大小、类型、CRC较验码等):
① arm-linux-objcopy -O binary -R .note -R .comment -S vmlinux linux.bin
② gzip -9 linux.bin
③ mkimage -A arm -O linux -T kernel -C gzip -a 0x30008000 -e 0x30008000 -n "Linux Kernel Image" -d linux.bin.gz uImage
(2)烧写内核映像文件uImage。
首先将uImage放在主机上的tftp或nfs目录下,确保已经开启tftp或nfs服务。
然后运行如下命令下载文件,擦除、烧写NAND Flash:
① tftp 0x30000000 uImage 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/uImage
② nand erase 0x0 0x00200000
③ nand write.jffs2 0x30000000 0x0 $(filesize)
第3条命令之所以使用“nand write.jffs2”而不是“nand write”,是因为前者不要求文件的长度是页对齐的(512字节对齐)。也可以使用“nand write”,但是需要将命令中的长度参数改为$(filesize)向上进行512取整后的值。比如uImage的大小为1540883,向上进行 512取整后为1541120(即0x178400),可以使用命令“nand write 0x30000000 0x0 0x178400”进行烧写。
(3)烧写yaffs文件系统映像。
假设yaffs文件系统映像的文件名为yaffs.img,首先将它放在主机上的tftp或nfs目录下,确保已经开启tftp或nfs服务;然后执行如下命令下载、擦除、烧写:
① tftp 0x30000000 yaffs.img 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/yaffs.img
② nand erase 0xA00000 0x3600000
③ nand write.yaffs 0x30000000 0xA00000 $(filesize)
这时,重启系统,在U-Boot倒数3秒之后,就会自动启动Linux系统。
(4)烧写jffs2文件系统映像。
假设jffs2文件系统映像的文件名为jffs2.img,首先将它放在主机上的tftp或nfs目录下,确保已经开启tftp或nfs服务;然后执行如下命令下载、擦除、烧写:
① tftp 0x30000000 jffs2.img 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/jffs2.img
② nand erase 0x200000 0x800000
③ nand write.jffs2 0x30000000 0x200000 $(filesize)
系统启动后,就可以使用“mount -t jffs2 /dev/mtdblock1 /mnt”挂接jffs2文件系统。
2.7 使用U-Boot来执行程序
在前面的硬件实验中使用JTAG烧写程序到NAND Flash,烧写过程十分缓慢。如果使用U-Boot来烧写NAND Flash,效率会高很多。烧写二进制文件到NAND Flash中所使用的命令与上面烧写内核映像文件uImage的过程类似,只是不需要将二进制文件制作成U-Boot格式。
另外,可以将程序下载到内存中,然后使用go命令执行它。假设有一个程序的二进制可执行文件 test.bin,连接地址为0x30000000。首先将它放在主机上的tftp或nfs目录下,确保已经开启tftp或nfs服务;然后将它下载到内 存0x30000000处,最后使用go命令执行它:
① tftp 0x30000000 test.bin 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/test.bin
② go 0x30000000
转载于:https://www.cnblogs.com/yuanfang/archive/2011/04/14/2015919.html
你可能感兴趣的:(Bootloader之uBoot简介(转))
x86-64汇编语言训练程序与实战
十除以十等于一
本文还有配套的精品资源,点击获取简介:汇编语言是一种低级语言,与机器代码紧密相关,特别适用于编写系统级代码及性能要求高的应用。nasm编译器是针对x86和x86-64架构的汇编语言编译器,支持多种语法风格和指令集。项目Euler提供数学和计算机科学问题,鼓励编程技巧应用,前100个问题的答案可共享。x86-64架构扩展了寄存器数量并引入新指令,提升了数据处理效率。学习汇编语言能够深入理解计算机底层
三菱PLC全套学习资料及应用手册
good2know
本文还有配套的精品资源,点击获取简介:三菱PLC作为工业自动化领域的核心设备,其系列产品的学习和应用需要全面深入的知识。本次资料包为学习者提供从基础到进阶的全方位学习资源,包括各种型号PLC的操作手册、编程指南、软件操作教程以及实际案例分析,旨在帮助用户系统掌握PLC的编程语言、指令系统及在各类工业应用中的实施。1.三菱PLC基础知识入门1.1PLC的基本概念可编程逻辑控制器(PLC)是工业自动化
移动端城市区县二级联动选择功能实现包
good2know
本文还有配套的精品资源,点击获取简介:本项目是一套为移动端设计的jQuery实现方案,用于简化用户在选择城市和区县时的流程。它包括所有必需文件:HTML、JavaScript、CSS及图片资源。通过动态更新下拉菜单选项,实现城市到区县的联动效果,支持数据异步加载。开发者可以轻松集成此功能到移动网站或应用,并可基于需求进行扩展和优化。1.jQuery移动端解决方案概述jQuery技术简介jQuery
我不懂什么是爱,但我给你全部我拥有的
香尧
因为怕黑,所以愿意陪伴在夜中行走的人,给他一点点的安全感。因为渴望温柔与爱,所以愿意为别的孩子付出爱与温柔。因为曾遭受侮辱和伤害,所以不以同样的方式施于其他人。如果你向别人出之以利刃,对方还了你爱与包容,真的不要感激他,真的不要赞美他。每一个被人伤害过的人心里都留下了一颗仇恨的种子,他也会想要有一天以眼还眼,以牙还牙。但他未让那颗种子生根发芽,他用一把心剑又一次刺向他自己,用他血荐仇恨,开出一朵温
Spring进阶 - SpringMVC实现原理之DispatcherServlet处理请求的过程
倾听铃的声
后端 spring java mvc 开发语言 分布式
前文我们有了IOC的源码基础以及SpringMVC的基础,我们便可以进一步深入理解SpringMVC主要实现原理,包含DispatcherServlet的初始化过程和DispatcherServlet处理请求的过程的源码解析。本文是第二篇:DispatcherServlet处理请求的过程的源码解析。@pdaiSpring进阶-SpringMVC实现原理之DispatcherServlet处理请求的
MotionLCM 部署优化 踩坑解决bug
AI算法网奇
aigc与数字人 深度学习宝典 文生motion
目录依赖项windowstorchok:渲染黑白图问题解决:humanml3d:sentence-t5-large下载数据:报错:Nomodulenamed'sentence_transformers'继续报错:fromtransformers.integrationsimportCodeCarbonCallback解决方法:推理相关转mesh:module‘matplotlib.cm‘hasno
恩小希美食成长日记之118:“摘星女神”王亚平归来,她的婚姻,治愈了多少职场女性的痛
恩小希
上周最为热闹的消息,无疑是神舟13号的三位航天英雄回来了.其中,最为瞩目的就是王亚平.她是我国首位进行太空行走的女航天员,也是为自己小女儿“摘星星的妈妈”。作为最受关注的职场妈妈,王亚平之所以能够成就这样一番事业,跟背后默默支持她的丈夫--赵鹏分不开。01王亚平1980年出生于山东烟台。父母都是地地道道的农民。王亚平这个姑娘从小体质好,一直练习长跑。高中时,空军来家乡招收女飞行员,作为体育班里唯一
2019-07-30
西域社群
天地之间,分外热闹。望热浪涛涛,引无数帅哥竞开撩,夕窈窕淑女皆露腰。一代天骄,群里热闹。不见美女露妖娆,唯见帅哥手机忙,唐诗宋词,广为流传,惜字如金,声情并茂。君悉吾析,今日之局有套路,昨日之生惹人恋。聚往矣,数风流人物还看今朝!!
《路远连着天》 第二 章 在 路 上 7
亚宁
大路镇的街道两旁尽是店铺,气派者是红门柱子雕花门窗,一般则多为布匹小百货店,还有几家门面朝外的车马大店,和一家颇有气势的典当铺。街上来往人还真不少,有挑担叫卖水果的,有背篓子路过的,还有衣冠楚楚,悠哉悠哉,甩着双手散步的有钱爷。耿六想着先寻姑妈家,还是先到兵营看那几个土匪呢?也只是一转念,他选择了后者,跟在几个闲人后,就来到了在镇外山头上曾看到过的那处飘着晴天白日旗的兵营门外。这里,围观的人乱哄哄
【老房翻新】92平轻奢简约风,将和谐之美融入空间!
没人比我更懂装修
在客厅空间中,设计师于冷静的空间基调中选用了层次感丰富的黄蓝色作为主要跳色,搭配黑白纹理的地毯与单椅,为空间增加了时尚摩登的气息。艺术感的单品突出点亮了空间,绿植的点缀、留白的软饰则增强了空间的呼吸性。点击此处添加图片说明文字点击此处添加图片说明文字设计师力求使每一处的设立都在空间中达到相互间的呼应与制衡,将艺术的跃动之美赋于空间之上,也将空间的和谐之美融于生活之中。点击此处添加图片说明文字点击此
学生把我的课件换成小三认罪书(赵书晴宋诗月)全集阅读_学生把我的课件换成小三认罪书最新章节阅读_赵书晴宋诗月(学生把我的课件换成小三认罪书)全本免费在线阅读_(学生把我的课件换成小三认罪书)完结...
笔趣阁热门小说
学生把我的课件换成小三认罪书(赵书晴宋诗月)全集阅读_学生把我的课件换成小三认罪书最新章节阅读_赵书晴宋诗月(学生把我的课件换成小三认罪书)全本免费在线阅读_(学生把我的课件换成小三认罪书)完结版免费在线阅读_学生把我的课件换成小三认罪书(赵书晴宋诗月)完整版免费阅读_(学生把我的课件换成小三认罪书)全章节免费在线阅读主角配角:赵书晴宋诗月简介:我和赵京立去了民政局提交了申请因为离婚冷静期,还要再
二十四节气组诗 谷雨
离陌_6639
图片来源网络,若侵犯了你的权益,请联系我删除6.谷雨文/离陌背上行囊背上如行囊的我从此任行程马不停蹄今天家乡的田野春雨快马加鞭播下希望的种子观音不语目送着我和夏天一道在观音山出关图片来源网络,若侵犯了你的权益,请联系我删除你好啊,我是离陌,已然在懵懂中走过了16年的岁月,为了珍惜当下的每一秒,所以立志做一名终身学习者。文学对于我来说是一种信仰,诗歌是我的生命。人生之道,四通八达,即入文学,自当持之
最佳好女婿赵倩王城(精彩热门小说)最佳好女婿赵倩王城&全集目录免费阅读
海边书楼
最佳好女婿赵倩王城(精彩热门小说)最佳好女婿赵倩王城&全集目录免费阅读主角:赵倩王城简介:女人叫赵倩,三十八岁,很漂亮,----阅读全文小说内容请翻阅文章最底部---王城根本没有想到,女友的妈妈在自乐的时候,叫的竟然是自己的名字。女人叫赵倩,三十八岁,很漂亮,腰很细,腿很长,王城有些怪异赵倩为什么会放过自己,但赵倩没有发怒,却也让王城长长的舒了一口气,坐到沙发上点了根烟抽了起来。“王城,什么时候回
营销活动-大转盘
無缺520
写在前面最近,首先营销活动工具这块我是再熟悉不过了。曾经做了不下20个活动工具,然后通过监控活动数据反推活动的好坏。文中主要讲解幸运大转盘营销工具一.大转盘定义大转盘是比较常见的营销活动工具,它是通过消费者用户控制【开始/停止】操作获得奖品物品。用户在不知道自己能获得什么奖品的条件下,然后通过抽奖,大概率的获得未知的奖品。类似最近流行的盲盒玩法。二.为什么做大转盘大转盘是最常用的抽奖类的活动工具之
5G基站信号加速器!AD8021ARZ-REEL7亚德诺 超低噪声高速电压放大器 专利失真消除技术!
深圳市尚想信息技术有限公司
5G通信 高速运放 ADI黑科技 8K视频 医疗超声
AD8021ARZ-REEL7ADI:重新定义高速放大器的性能极限!一、产品简介AD8021ARZ-REEL7是ADI(亚德诺半导体)推出的超低噪声高速电压反馈放大器,采用XFCB工艺和专利失真消除技术,专为4K/8K视频处理、医疗成像、5G通信等超高频应用设计。以1.8GHz带宽和0.1nV/√Hz超低噪声,成为高速信号调理的终极解决方案!二、五大颠覆性优势军工级信号保真度1.8GHz-3dB带
STM32入门之TIM基本定时器
嵌入式白话
STM32入门学习 stm32 嵌入式硬件 单片机
一、定时器简介定时器是嵌入式系统中的关键外设之一,它可以用于生成精确的延时、周期性中断、PWM波形生成等功能。在STM32F1系列单片机中,定时器不仅能为系统提供精确的时钟,还支持外部事件的捕获以及信号输出。对于定时器的功能,我们可以通过一个生活中非常常见的例子来形象地描述:微波炉的定时器。想象你正在使用微波炉加热食物。在微波炉里,定时器的作用就是帮助你控制食物加热的时间。当你设置了加热时间后,定
收集落叶
申文秀
走啊,一起走,我们一起去收集落叶落叶的模样早已映入我的眼帘耳边已响起叶子落在地上的“沙沙”声走啊,去收集落叶,落叶是我们的回忆落叶的样子如此俏皮心形是写给秋姑娘的情书圆形是秋姑娘送给大地的礼物三角形是秋姑娘礼服上的碎珠片哟!秋姑娘举行的盛装宴会开始了你看!秋之宴会开始了松叶缓缓飘落在空中跳起了华尔兹枫叶跳着芭蕾兜着圈子盘旋而下银杏叶是杂技演员,正“七上八下”地翻跟头每个落叶都在宴会上展示自己最美妙
婴童医话(五百六十)
妙手柯楠
探天地清浊之源,察阴阳顺逆之机。“乳食过饱蓄胃中,乳片不化吐频频,身热面黄腹膨胀。”伤乳吐者,因乳食过饱,停蓄胃中,以致运化不及,吐多乳片,犹如物盛满而上溢也。其证身热面黄,肚腹膨胀。治宜化其宿乳,安胃和中,节其乳食,自然止也。“过食伤胃腹胀热,恶食口臭吐酸粘,眼胞虚浮身潮热。”伤食吐者,因小儿饮食无节,过食油腻、面食等物,以致壅塞中脘而成也,其证肚腹胀热,恶食口臭,频吐酸粘,眼胞虚浮,身体潮热。
【阳光️️☀️苑长 幸福365】7月9日《就此刻确知》
阳光苑长
音频文字原创|阳光苑长图片发自App一夜的梦境,情景相当的壮烈,那是前前后后多个情节的叠加,那是因为极其在意在正义的,完满的,在早已经确认——万古之先已经确认了一切美好的事物当中。当有假象,当有一些不好的势力,试图想去破坏夺取的时候,在梦境中一直得力的征战,为着身边最挚爱的朋友,为他,为所有美好的事业而守望。守望,英文叫做watcher,就是观望者。图片发自App在这个毫不奇怪,事实上一直发生的征
漫步,跳出藩篱
张巧金沙
最近的教学,倍感不爽。一为这国庆之假,把这课上得支离破碎的。放假前,上了四天课,但我却只上了三天,9月30日,我工作室在搞活动,全天的活动,课当然未能上。10月8日学生回校,上了两天课,学生又放回家了。就觉得学生刚有点状态,又回去逍遥去了。感觉吧,教学内容也不敢大胆甩开膀子去教学,所以呀,这教学内容还真上不走,而且学生学下来效果特差。这不,国庆放假前的一个周,测试了两次,均为第一单元,是自考试以来
用好考评指挥棒 答好时代新答卷
天才码字选手
民之所望,施政所向。而群众评议正是检验发展质量和人民获得感的试金石。究竟是“走过场”、“栽盆景”,还是俯下身子,甘当为民服务的“孺子牛”或撸起袖子,做担当作为的“拓荒牛”,群众自是看得清清楚楚,最有发言权。因而也有这样一句话:干部脚下沾多少泥土,群众心里就有多少感情。民意不可或缺,群众的意见不能少。新时代下,要最大程度发挥群众评议在激励干部担当作为中的效能,才能汇聚起推动高质量发展的磅礴之力,答好
在人间(阿伟林秀芳柳娇娇)全本免费在线阅读_人间乐事全文阅读《人间芳韵》
一米文库2
在人间(阿伟林秀芳柳娇娇)全本免费在线阅读_人间乐事全文阅读《人间芳韵》主角配角:阿伟林秀芳柳娇娇小说别名:在人间、人间乐事、人间芳韵简介:和美艳寂寞的小姨上山,不小心被她女儿看到……关注微信公众号【一米文库】回复书号【1017】即可阅读小说【在人间】全文内容!!!【戳我继续阅读】“嗯~~阿伟,你好强壮……”芳姨喝多了酒,被我搀扶着艰难的往卧室走去。她身上香喷喷的,温香软玉靠在我身上,性感的红唇几
总会有一道光之利刃 能划破暗夜长空!
一滴Sea
图片发自App一帆风顺的人生很难产生什么顿悟,只有扎扎心,人才会醒悟,所以人生路上偶尔的风浪冲突,都可以怡情,和着一杯清茶一品人生的滋味。图片发自App中国人的传统家教就是:优点不鼓励,心知肚明就好,有缺点一定要细细碎碎仔细倒嚼,然后编织成一张唠叨之网把家人孩子都包裹的严严实实,苦口婆心地说:“走大街的人,没有人像我这样说你,你该感恩才是!”图片发自App唉!真是让人一口长气无处抒发,多少孩子因为
老公的女朋友把我打成小三后,我杀疯了周昊净许青青小说完结推荐_最热门小说 老公的女朋友把我打成小三后,我杀疯了周昊净许青青
小富江呀
《老公的女朋友把我打成小三后,我杀疯了》主角:周昊净许青青简介:只因跟老公说了几句情话,就被老公的‘女朋友’当成小三。她带着一群自诩为“惩治小三联盟”的网络判官冲进了我家。“怎么,有脸当小三,没脸承认?”“从你当小三的那一刻起,就该想到会有被人收拾的一天!”“我们网络判官专治道德败坏的贱人!”这群人一边疯狂大骂,一边愤怒打砸。看着狼藉不堪的家,我面色平静地给公司法务部发去消息。“按照婚前协议,拟一
Pktgen-DPDK:开源网络测试工具的深度解析与应用
艾古力斯
本文还有配套的精品资源,点击获取简介:Pktgen-DPDK是基于DPDK的高性能流量生成工具,适用于网络性能测试、硬件验证及协议栈开发。它支持多种网络协议,能够模拟高吞吐量的数据包发送。本项目通过利用DPDK的高速数据包处理能力,允许用户自定义数据包内容,并实现高效的数据包管理与传输。文章将指导如何安装DPDK、编译Pktgen、配置工具以及使用方法,最终帮助开发者和网络管理员深入理解并优化网络
C++编程基础与面向对象概念解析
侯昂
面向对象编程 C++语法 函数 类与对象 继承与多态性
C++编程基础与面向对象概念解析背景简介C++是一种广泛使用的面向对象编程语言,它允许开发者创建高效、灵活且功能强大的程序。本文基于《C++Primer》一书的章节内容,深入解析C++的核心概念和面向对象编程原则,旨在帮助读者构建扎实的C++编程基础。面向对象编程的原则软件危机与进化介绍了软件危机的产生和软件进化的必要性,强调了面向对象编程(OOP)在应对这些问题中的优势。面向对象编程范式讨论了面
青云官道庄岩柳琴免费完结版小说_已完结小说推荐青云官道(庄岩柳琴)
d036fb3b3d05
《青云官道》主角:庄岩柳琴,简介:小科员庄岩,因一纸调研报告被副市长赏识,本以为能够就此走上人生巅峰,结果副市长就被双规!不过庄岩非但没有被牵连,反而拿着副市长留下的东西,不仅抱得美人,还平步青云,扶摇而上九万里!关注微信公众号【夏至文馆】去回复个书号【1190】即可阅读小说【青云官道】全文内容!!!小庄,现在几点了?”富丽堂皇的客厅里,一个身穿旗袍的美妇紧张的来回的踱步!“夫人,11点57。”站
替身贵妃将我扒光沉塘,暴君杀红了眼陆知白李双儿全本免费小说阅读_最新完本小说替身贵妃将我扒光沉塘,暴君杀红了眼(陆知白李双儿)
多多文馆
《替身贵妃将我扒光沉塘,暴君杀红了眼》主角:陆知白李双儿简介:我是贫门农女,也是暴君爱而不得的白月光。为逼我入宫,他将我满村屠尽!暴君在后宫建了一座水晶殿,将我禁锢其中。他日日宠幸我,直至寻到一个与我七分相似的替身,方才淡了兴致。他将替身封为贵妃,千娇万宠一言可戏诸侯。贵妃恃宠生娇,趁着暴君出宫巡视,带着一群人闯进水晶殿。“本宫马上就要受封为后,今天就来清理后宫!”她在我脸上刻奴字,挑断我的手筋脚
旋转
安静的影子
图片发自App傍晚你带我们去万象城说是试营业到了大厦底下好家伙这么壮观我望向楼顶直达蓝天白云啊整个大楼似乎在转圈我蒙了又很奇妙的感觉我透过大厦望天空就感觉到在旋转真的感觉到动态的效果很神奇我慌忙叫你看快看快看大厦好像会旋转你说我知道知道知道也不知道你是否感觉到了真的很美妙的体会是天空在转吗我感觉大厦也在旋转很美妙很美妙图片发自App图片发自App
今天的彭格列依然被世界游戏吓得瑟瑟发抖呢
云染舒倦
前言有一天,有一个白兰的大魔王想要毁灭世界,创世者非常不高兴,于是,创始者用吞噬一切,包容一切的大空(泽田纲吉)、捉摸不定的雾(六道骸)、孤高的浮云(云雀恭弥)、愤怒的忠岚(狱寺隼人)、晕染一切的雨(山本武)、守护之雷(蓝波)、热血之晴(晴川了平)创造了七位拯救世界的勇士(用属性制造勇士),让他们跟白兰对抗。。。[白兰的力量是游戏,于是要打败他,只要同样在世界布置的游戏中,赢过白兰就行了~]图片发
ztree设置禁用节点
3213213333332132
JavaScript ztree json setDisabledNode Ajax
ztree设置禁用节点的时候注意,当使用ajax后台请求数据,必须要设置为同步获取数据,否者会获取不到节点对象,导致设置禁用没有效果。
$(function(){
showTree();
setDisabledNode();
});
JVM patch by Taobao
bookjovi
java HotSpot
在网上无意中看到淘宝提交的hotspot patch,共四个,有意思,记录一下。
7050685:jsdbproc64.sh has a typo in the package name
7058036:FieldsAllocationStyle=2 does not work in 32-bit VM
7060619:C1 should respect inline and
将session存储到数据库中
dcj3sjt126com
sql PHP session
CREATE TABLE sessions (
id CHAR(32) NOT NULL,
data TEXT,
last_accessed TIMESTAMP NOT NULL,
PRIMARY KEY (id)
);
<?php
/**
* Created by PhpStorm.
* User: michaeldu
* Date
Vector
171815164
vector
public Vector<CartProduct> delCart(Vector<CartProduct> cart, String id) {
for (int i = 0; i < cart.size(); i++) {
if (cart.get(i).getId().equals(id)) {
cart.remove(i);
各连接池配置参数比较
g21121
连接池
排版真心费劲,大家凑合看下吧,见谅~
Druid
DBCP
C3P0
Proxool
数据库用户名称 Username Username User
数据库密码 Password Password Password
驱动名
[简单]mybatis insert语句添加动态字段
53873039oycg
mybatis
mysql数据库,id自增,配置如下:
<insert id="saveTestTb" useGeneratedKeys="true" keyProperty="id"
parameterType=&
struts2拦截器配置
云端月影
struts2拦截器
struts2拦截器interceptor的三种配置方法
方法1. 普通配置法
<struts>
<package name="struts2" extends="struts-default">
&
IE中页面不居中,火狐谷歌等正常
aijuans
IE中页面不居中
问题是首页在火狐、谷歌、所有IE中正常显示,列表页的页面在火狐谷歌中正常,在IE6、7、8中都不中,觉得可能那个地方设置的让IE系列都不认识,仔细查看后发现,列表页中没写HTML模板部分没有添加DTD定义,就是<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3
String,int,Integer,char 几个类型常见转换
antonyup_2006
html sql .net
如何将字串 String 转换成整数 int?
int i = Integer.valueOf(my_str).intValue();
int i=Integer.parseInt(str);
如何将字串 String 转换成Integer ?
Integer integer=Integer.valueOf(str);
如何将整数 int 转换成字串 String ?
1.
PL/SQL的游标类型
百合不是茶
显示游标(静态游标) 隐式游标 游标的更新和删除 %rowtype ref游标(动态游标)
游标是oracle中的一个结果集,用于存放查询的结果;
PL/SQL中游标的声明;
1,声明游标
2,打开游标(默认是关闭的);
3,提取数据
4,关闭游标
注意的要点:游标必须声明在declare中,使用open打开游标,fetch取游标中的数据,close关闭游标
隐式游标:主要是对DML数据的操作隐
JUnit4中@AfterClass @BeforeClass @after @before的区别对比
bijian1013
JUnit4 单元测试
一.基础知识
JUnit4使用Java5中的注解(annotation),以下是JUnit4常用的几个annotation: @Before:初始化方法 对于每一个测试方法都要执行一次(注意与BeforeClass区别,后者是对于所有方法执行一次)@After:释放资源 对于每一个测试方法都要执行一次(注意与AfterClass区别,后者是对于所有方法执行一次
精通Oracle10编程SQL(12)开发包
bijian1013
oracle 数据库 plsql
/*
*开发包
*包用于逻辑组合相关的PL/SQL类型(例如TABLE类型和RECORD类型)、PL/SQL项(例如游标和游标变量)和PL/SQL子程序(例如过程和函数)
*/
--包用于逻辑组合相关的PL/SQL类型、项和子程序,它由包规范和包体两部分组成
--建立包规范:包规范实际是包与应用程序之间的接口,它用于定义包的公用组件,包括常量、变量、游标、过程和函数等
--在包规
【EhCache二】ehcache.xml配置详解
bit1129
ehcache.xml
在ehcache官网上找了多次,终于找到ehcache.xml配置元素和属性的含义说明文档了,这个文档包含在ehcache.xml的注释中!
ehcache.xml : http://ehcache.org/ehcache.xml
ehcache.xsd : http://ehcache.org/ehcache.xsd
ehcache配置文件的根元素是ehcahe
ehcac
java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderL
白糖_
java eclipse spring tomcat Web
今天学习spring+cxf的时候遇到一个问题:在web.xml中配置了spring的上下文监听器:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
随后启动
angular.element
boyitech
AngularJS AngularJS API angular.element
angular.element
描述: 包裹着一部分DOM element或者是HTML字符串,把它作为一个jQuery元素来处理。(类似于jQuery的选择器啦) 如果jQuery被引入了,则angular.element就可以看作是jQuery选择器,选择的对象可以使用jQuery的函数;如果jQuery不可用,angular.e
java-给定两个已排序序列,找出共同的元素。
bylijinnan
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CommonItemInTwoSortedArray {
/**
* 题目:给定两个已排序序列,找出共同的元素。
* 1.定义两个指针分别指向序列的开始。
* 如果指向的两个元素
sftp 异常,有遇到的吗?求解
Chen.H
java jcraft auth jsch jschexception
com.jcraft.jsch.JSchException: Auth cancel
at com.jcraft.jsch.Session.connect(Session.java:460)
at com.jcraft.jsch.Session.connect(Session.java:154)
at cn.vivame.util.ftp.SftpServerAccess.connec
[生物智能与人工智能]神经元中的电化学结构代表什么?
comsci
人工智能
我这里做一个大胆的猜想,生物神经网络中的神经元中包含着一些化学和类似电路的结构,这些结构通常用来扮演类似我们在拓扑分析系统中的节点嵌入方程一样,使得我们的神经网络产生智能判断的能力,而这些嵌入到节点中的方程同时也扮演着"经验"的角色....
我们可以尝试一下...在某些神经
通过LAC和CID获取经纬度信息
dai_lm
lac cid
方法1:
用浏览器打开http://www.minigps.net/cellsearch.html,然后输入lac和cid信息(mcc和mnc可以填0),如果数据正确就可以获得相应的经纬度
方法2:
发送HTTP请求到http://www.open-electronics.org/celltrack/cell.php?hex=0&lac=<lac>&cid=&
JAVA的困难分析
datamachine
java
前段时间转了一篇SQL的文章(http://datamachine.iteye.com/blog/1971896),文章不复杂,但思想深刻,就顺便思考了一下java的不足,当砖头丢出来,希望引点和田玉。
-----------------------------------------------------------------------------------------
小学5年级英语单词背诵第二课
dcj3sjt126com
english word
money 钱
paper 纸
speak 讲,说
tell 告诉
remember 记得,想起
knock 敲,击,打
question 问题
number 数字,号码
learn 学会,学习
street 街道
carry 搬运,携带
send 发送,邮寄,发射
must 必须
light 灯,光线,轻的
front
linux下面没有tree命令
dcj3sjt126com
linux
centos p安装
yum -y install tree
mac os安装
brew install tree
首先来看tree的用法
tree 中文解释:tree
功能说明:以树状图列出目录的内容。
语 法:tree [-aACdDfFgilnNpqstux][-I <范本样式>][-P <范本样式
Map迭代方式,Map迭代,Map循环
蕃薯耀
Map循环 Map迭代 Map迭代方式
Map迭代方式,Map迭代,Map循环
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
蕃薯耀 2015年
Spring Cache注解+Redis
hanqunfeng
spring
Spring3.1 Cache注解
依赖jar包:
<!-- redis -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
Guava中针对集合的 filter和过滤功能
jackyrong
filter
在guava库中,自带了过滤器(filter)的功能,可以用来对collection 进行过滤,先看例子:
@Test
public void whenFilterWithIterables_thenFiltered() {
List<String> names = Lists.newArrayList("John"
学习编程那点事
lampcy
编程 android PHP html5
一年前的夏天,我还在纠结要不要改行,要不要去学php?能学到真本事吗?改行能成功吗?太多的问题,我终于不顾一切,下定决心,辞去了工作,来到传说中的帝都。老师给的乘车方式还算有效,很顺利的就到了学校,赶巧了,正好学校搬到了新校区。先安顿了下来,过了个轻松的周末,第一次到帝都,逛逛吧!
接下来的周一,是我噩梦的开始,学习内容对我这个零基础的人来说,除了勉强完成老师布置的作业外,我已经没有时间和精力去
架构师之流处理---------bytebuffer的mark,limit和flip
nannan408
ByteBuffer
1.前言。
如题,limit其实就是可以读取的字节长度的意思,flip是清空的意思,mark是标记的意思 。
2.例子.
例子代码:
String str = "helloWorld";
ByteBuffer buff = ByteBuffer.wrap(str.getBytes());
Sy
org.apache.el.parser.ParseException: Encountered " ":" ": "" at line 1, column 1
Everyday都不同
$ 转义 el表达式
最近在做Highcharts的过程中,在写js时,出现了以下异常:
严重: Servlet.service() for servlet jsp threw exception
org.apache.el.parser.ParseException: Encountered " ":" ": "" at line 1,
用Java实现发送邮件到163
tntxia
java实现
/*
在java版经常看到有人问如何用javamail发送邮件?如何接收邮件?如何访问多个文件夹等。问题零散,而历史的回复早已经淹没在问题的海洋之中。
本人之前所做过一个java项目,其中包含有WebMail功能,当初为用java实现而对javamail摸索了一段时间,总算有点收获。看到论坛中的经常有此方面的问题,因此把我的一些经验帖出来,希望对大家有些帮助。
此篇仅介绍用
探索实体类存在的真正意义
java小叶檀
POJO
一. 实体类简述
实体类其实就是俗称的POJO,这种类一般不实现特殊框架下的接口,在程序中仅作为数据容器用来持久化存储数据用的
POJO(Plain Old Java Objects)简单的Java对象
它的一般格式就是
public class A{
private String id;
public Str