目录
实现内容:在基于ARM处理器的开发板上安装Linux系统
Linux提供:进程管理 内存管理 网络接口 文件管理 设备管理
系统移植的目的
不同架构的处理器指令集不兼容,即便是相同的处理器架构,板卡不同驱动代码也不兼容(平台相关代码)
Linux是一个通用的内核,并不是为某一个特定的处理器架构或板卡设计的,所以从官方获取Linux源码后,我们要先经过相应的配置,使其与我们当前的硬件平台相匹配后,才能进行编译和安装。
系统移植过程
Windows装机
- 准备Windows系统镜像、U盘启动盘
- 进入BIOS(Basic Input Output System)选择启动方式(U盘启动)
- 通过U盘中的引导程序安装系统
- 安装Windows驱动程序
- 安装Windows应用程序
Linux系统移植
- 准备Linux内核镜像、SD卡启动盘
- 通过拨码开关选择启动方式(SD启动)
- 通过SD卡中的引导程序安装系统
- 安装Linux驱动程序
- 安装Linux应用程序
开发板启动过程
- 开发板上电后首先运行SOC内部iROM中固化的代码(BL0),这段代码先对基本的软硬件环境(时钟等...)进行初始化,然后再检测拨码开关位置获取启动方式(SD/EMMC),然后再将对应存储器中的uboot搬移到内存,然后跳转到uboot运行
- uboot开始运行后首先对开发板上的软硬件环境做进一步初始化,然后将linux内核、设备树(dtb)【Linux + 设备树 才是一个完整的Linux内核】、根文件系统(rootfs)从外部存储器(或网络)搬移到内存,然后跳转到linux运行
- linux开始运行后先对系统环境做初始化,当系统启动完成后,Linux再从内存中(或网络)挂载根文件系统
系统移植步骤
- uboot移植
- linux内核移植(包含设备树)
- 根文件系统移植
网络配置
tftp(主打传输)
tftp(Trivial File Transfer Protocol)即简单文件传输协议 是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。端口号为69
nfs (主打共享)
nfs(Network File System)即网络文件系统,其基于UDP/IP使用nfs能够在不同计算机之间通过网络进行文件共享,能使使用 者访问网络上其它计算机中的文件就像在访问自己的计算机一样。
uboot概述
Bootloader
在操作系统运行之前运行的一小段代码,用于将软硬件环境初始化到一个合适的状态,为操作系统的加载和运行做准备(其本身不是操作系统)
Bootloader基本功能
- 初始化软硬件环境
- 引导加载linux内核
- 给linux内核传参
- 执行用户命令
注:bootloader是启动引导程序的统称,嵌入式linux常用的bootloader是uboot
常见的Bootloader
SD卡存储结构
SD卡的存储以扇区为单位,每个扇区的大小为512Byte, 其中零扇区存储分区表(即分区信息),后续的扇区可自行分区和格式化;
若选择SD卡启动,处理器上电后从第一个扇区开始将其中的内容搬移到内存,所以我们把uboot放到从第一个扇区开始之后的空间, 之后的空间根据个人需求可进行分区和格式化
uboot的使用
uboot启动流程分析
启动流程
第一阶段:汇编阶段
- 设置CPU工作模式,关中断,关MMU,关看门狗....
- 初始化时钟,内存
- 自搬运
- 设置堆栈,清BSS段
- 跳转到C环境执行
第二阶段:C阶段
- 初始化GPIO
- 初始化用于调试的网口或串口等必要的硬件
- 执行CMD命令
- 加载内核
启动分析
【uboot代码流程 version:2013】
1、查找uboot入口函数
1)通过链接文件(lds)文件查找启动入口函数,路径在(arch\arm\cpu\u-boot.lds),查找到入口函数是_start (ENTRY(_start))
2)通过全局搜索(或者看链接文件里面的text断里面的start.o),找到_start在start.S下(arch\arm\cpu\armv7\start.S)
ps:这里之所以取armv7下面的start,主要是由于目标板cpu是armv7下面的cpu。
2、分析代码流程
_start
--reset
--设置cpu模式,关中断
--异常表基地址设置
--cpu_init_cp15(mmu关闭)
--cpu_init_crit
--lowlevel_init(board\samsung\origen\lowlevel_init.S)
--system_clock_init 时钟初始化(这里不执行)
--mem_ctrl_asm_init 内存初始化(这里不执行)
--uart_asm_init 串口初始化
--tzpc_init tzpc初始化
--_main(arch\arm\lib\crt0.S)
--为进入C做准备
--relocated(自搬移,uboot没有在内存里面运行)
--栈的初始化及 bss的清理
--board_init_r(arch\arm\lib\board.c)
--目标板硬件相关初始化
--main_loop(for循环进入等待处理命令,待输入加载内核命令后,uboot将操作权限交给内核)
【uboot代码流程version:2016】
找_start 在 \arch\arm\lib\vectors.S:定义异常向量表
找reset 在arch\arm\cpu\armv7\start.S 启动代码
ldr r0, =_start 获取uboot加载地址
mcr p15, 0, r0, c12, c0, 0 @设置vector向量表到start地址。
uboot模式
- 自启动模式 :uboot启动后若没有用户介入,倒计时结束后会自动执行自启动环境变量(bootcmd)中设置的命令(一般作加载和启动内核)
- 交互模式 :倒计时结束之前按下任意按键uboot会进入交互模式,交互模式下用户可输入uboot命令
uboot命令
帮助命令
help 查看uboot支持的所有命令
help [命令] 查看当前命令的使用方法
环境变量命令
printenv 打印uboot中所有的环境变量
setenv 设置指定的环境变量(保存在RAM中,断电会丢失,重启会重置)
setenv [环境变量] [环境变量的值]
saveenv 保存所有环境变量到EMMC中(会保存至外部存储器中,断电不丢失)
网络传输命令
loadb 通过Kermit协议下载文件到指定的内存地址 loadb [地址]
tftp 通过tftp协议下载文件到指定的内存地址 tftp [地址] [(服务器ip:)文件名]
例:tftp 0x41000000 interface.bin
注:使用tftp之前要配置好网络及tftp服务器 【网络:桥接模式 选择网卡(桥接到pcie)】
要在同一网段中,并且serverip就是Ubuntu的ip地址。如果未联通试试关闭防火墙
存储器访问命令(内存<=>外存)
mmc read
将EMMC中指定扇区中的内容读取到内存中指定的地址
mmc read <addr> <blk#> <cnt>
addr: 内存地址
blk#: EMMC中的扇区编号
cnt: 读取的扇区的个数
mmc write
将内存中指定地址中的内容写入到EMMC中指定的扇区
mmc write <addr> <blk#> <cnt>
uboot常用环境变量
- ipaddr uboot的IP地址
- serverip 服务器的IP地址(即ubuntu的IP)
- bootdelay 进入自启动模式之前倒计时的秒数
uboot自启动环境变量
bootcmd 自启动的环境变量
该环境变量可以设置成一到多个uboot命令的集合(若有多个使用\;分割)
自启动模式下uboot就会按照bootcmd中命令的顺序逐条执行 (无需干预自动执行)
eg: setenv bootcmd tftp 40008000 interface.bin\;go 40008000
saveenv
eg: setenv bootcmd mmc read 0 40008000 0x800 0x1\;go 40008000
saveenv
Linux内核的安装与加载
uboot内核启动命令
bootm 启动指定内存地址上的Linux内核并为内核传递参数
bootm kernel-addr ramdisk-addr dtb-addr
注:
- kernel-addr: 内核的下载地址
- ramdisk-addr: 根文件系统的下载地址
- dtb-addr: 设备树的下载地址
若不使用相应的地址,对应的位置写“-”
eg: bootm 0x41000000 - 0x42000000
uboot自启动参数环境变量
bootargs
是uboot用来给Linux内核传递参数的环境变量。
eg: setenv bootargs root=/dev/nfs nfsroot=xxx.xxx.xxx.xxx(Ubuntu的ip):/opt/4412/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=***.***.***.***(ipaddr)
setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/opt/4412/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=192.168.1.13
注:
- root 根文件系统类型(nfs)
- nfsroot 网络文件系统路径(xxx.xxx.xxx.xxx:/opt/4412/rootfs)
- rw 操作网络文件系统的权限(rw)
- console 控制台(使用串口2,波特率115200)
- init init进程的位置(/linuxrc)
- ip linux启动后自身的IP(***.***.***.***)
EMMC加载Linux内核及rootfs
# tftp 0x41000000 uImage
# mmc write 0 0x41000000 0x800 0x2000
下载设备树到内存中
# tftp 0x41000000 exynos4412-fs4412.dtb
# mmc write 0 0x41000000 0x2800 0x800
下载根文件系统镜像到内存中
# tftp 0x41000000 ramdisk.img
# mmc write 0 0x41000000 0x3000 0x2000
2.因为要从 EMMC 驱动,所以还要修改 uboot 的启动参数
# setenv bootcmd mmc read 0 0x41000000 0x800 0x2000\;mmc read 0 0x42000000 0x2800 0x800\;mmc read 0 0x43000000 0x3000 0x2000\;bootm 0x41000000 0x43000000
0x42000000
若设置无法保存可尝试如下格式
# setenv bootcmd 'mmc read 0 0x41000000 0x800 0x2000;mmc read 0 0x42000000 0x28000x800;mmc read 0 0x43000000 0x3000 0x2000;bootm 0x41000000 0x430000000x42000000'
设置完成后保存这些参数
# saveenv
3.给开发板重新上电,上电之后观察内核是否能通过 EMMC 加载和启动内核,启动完成
tftp加载Linux内核nfs挂载rootfs
$ sudo tar xvf rootfs.tar.xz
$ sudo rm rootfs.tar.xz
#setenv bootcmd tftp 0x41000000 uImage\;tftp 0x42000000 exynos4412-fs4412.dtb\;bootm 0x41000000 - 0x42000000
# saveenv
$ sudo service nfs-kernel-server restart
# tftp 0x41000000 u-boot-fs4412.bin
# emmc open 0# mmc write 0 0x41000000 0x0 0x800# emmc close 0
显示如下信息表示安装成功
3、 关闭开发板电源,调整拨码开关位置为 EMMC 启动(SD卡启动为反向)
↑此为EMMC启动
↑此为SD卡启动
因为启动的是 EMMC 中的 uboot 所以环境变量还需要重新设置
后续研究思路:开源->移植->编译
交叉编译工具链
编译原理
- 机器码(二进制)是处理器能直接识别的语言,不同的机器码代表不同的运算指令,处理器能够识别哪些机器码是由处理器的硬件设计所决定的,不同的处理器机器码不同,所以机器码不可移植
- 汇编语言是机器码的符号化,即汇编就是用一个符号来代替一条机器码,所以不同的处理器汇编也不一样,即汇编语言也不可移植
- C语言在编译时我们可以使用不同的编译器将C源码编译成不同架构处理器的汇编,所以C语言可以移植
GCC编译过程
预处理(.c ->.i):注释删除 宏定义替换之后删除 头文件内容展开
编译(.i->.s):把处理过的c语言 编译成汇编语言
汇编(.s ->.o):把汇编语言文件编译为目标文件
链接:将目标文件和其他附加文件(库)链接起来,生成可执行文件。
例:gcc tset.o v.o -o test
交叉编译
指程序的编译和运行不在同一台机器上
比如我们现在:在 ubuntu 上编辑、编译。 在 开发板 上运行。
交叉编译工具链
交叉编译工具链的获取:
1) 官网获取(不推荐,需要自己进行复杂配置与编译) http://ftp.gnu.org/gnu/gcc/
2) BSP板级开发支持包(推荐) samsung、全志...
交叉编译工具链的内容
1) 交叉编译工具 gcc/readelf/size/nm/strip/objcopy/objdump/addr2line
2) 库(lib) ARM架构的库
注:库是分平台的 具有平台属性
ELF文件格式
ELF
ELF格式是Linux平台上应用最广泛的二进制工业标准之一(可执行文件) 。
ELF格式的文件内包含了很多个段不同的段存储了不同的信息;因为ELF格式的文件要通过Linux系统的加载和管理才能运行,所以除了最基本的代码段和数据段之外,其中还存储了很多其它的信息,如符号表、调试信息等。
ELF文件相关命令
- file file + 文件名 查看文件的详细信息
- readelf
- readelf -h + 文件名 列出elf文件的头部信息
- readelf -a + 文件名 列出elf文件的所有信息
BIN文件格式
BIN文件一般是直接运行在CPU之上 的可执行文件,文件内只包含了CPU能够直接识别和运行的指令和数据,不包含其它系统相关的信息。
交叉编译工具链常用工具
size 列出目标文件每一段的大小以及总体的大小 size + 文件名
未初始化的全局变量存储在bss中 如 int a;(global) 后 bss=8
初始化的全局变量存储在data中 如若int a=100;(global) 则 data=280
局部变量不占用空间 ,但static int 同全局变量一样。未初始化的存储在bss中,初始化的存储在data中。
语句存放在text中。如 a++;
nm 列出目标文件中的.symtab符号表(标示符) nm + 文件名
strip(瘦身命令) 丢弃目标文件中的符号,但是并不影响程序正常执行 strip + 文件名
注:对于嵌入式开发,这个命令很重要,节省磁盘空间。
objdump 从目标文件中显示信息
eg: objdump -d + 文件名 将目标文件反汇编(机器码->汇编)
objcopy 对目标文件进行复制和转换
eg: objcopy --gap-fill=0xff -O binary a.out a.bin 将目标文件转换为bin格式
上述指令对于ARM架构来说,要使用交叉编译工具链中的 加个前缀arm-none-linux-gnueabi-***
uboot源码结构
uboot源码获取(开源)
- uboot源码下载 http://www.denx.de/wiki/U-Boot/
- uboot版本命名 前期:uboot-1.2.3 现在:uboot-2008.01
- uboot版本选择 支持对应的硬件平台、相对成熟的版本(资料多)
uboot特点
- 代码结构清晰
- 支持丰富的处理器与开发板,易于移植
- 支持丰富的用户命令
- 支持丰富的网络协议
- 支持丰富的文件系统
- 支持丰富的设备驱动
- 更新活跃、用户较多、资料丰富
- 开放源代码
- 较高的稳定性
- 不具有通用性(不同的处理器、开发板uboot不可通用)
uboot源码结构
平台相关代码
即与CPU架构或开发板硬件相关的源码,硬件的改动对应的代码也需要进行修改
arch: 与CPU架构相关的源代码
board:与开发板相关的源代码,包含各种官方评估板对应的源码
平台无关代码
- api: 应用接口
- common: uboot命令源码
- disk: 对磁盘设备的支持
- drivers: 设备驱动源码
- fs: 对文件系统的支持
- include: 头文件
- lib: 库
- net: 对网络协议的支持
- post: 上电自检程序
配置文件、帮助文档、示例程序、工具等:
- README: 说明文档
- doc: 帮助文档
- Makefile: 编译管理
- CREDITS: 开发者
- COPYING: 版权
- examples: 示例程序
- tools: 工具
uboot的配置与编译
uboot配置
- 指定当前使用的硬件平台
- 格式:make <board_name>_config
- 注1:<board_name>为当前使用的开发板的名字
- 注2:执行该命令的前提是uboot源码支持该开发板
- 注3:该命令必须在uboot源码的顶层目录下执行
- 指定编译uboot源码使用的编译器
- 在uboot源码顶层目录下的Makefile中指定(CROSS_COMPILE变量)
例:make origen_config
CROSS_COMPILE ?=arm-none-linux-gnueabi-
uboot编译
- 编译uboot
- make
- 注1:该命令必须在uboot源码的顶层目录下执行
- 注2:该命令执行后在uboot源码顶层目录下生成u-boot.bin
- 清除编译过程中生成的中间文件
- make clean(.o)
- make distclean
- 注1:该命令必须在uboot源码的顶层目录下执行
uboot 经过 arm-none-linux-gnueabi-objcopy 变成bin文件
uboot移植深化
一、建立自己的平台
1.下载uboot源码
在uboot官网下载uboot源码(这里我们选择u-boot-2013.01.tar.bz2)
2.解压uboot源码
拷贝uboot源码包到ubuntu的家目录下,解压并进入其顶层目录
$ tar xvf u-boot-2013.01.tar.bz2
$ cd u-boot-2013.01/
3.指定交叉编译工具信息
uboot源码并不知道我们使用的处理器架构及交叉编译工具是什么,这里我们需要自己 在Makefile中指定
$ vi Makefile
将
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif
修改为如下内容(注意后边不要有多余的空格),然后保存退出
ifeq (arm,arm)
CROSS_COMPILE ?= arm-none-linux-gnueabi-
endif
4.添加Board信息
因为uboot源码并不支持我们的开发板,这里我们需要从源码支持的开发板中找一个硬件与我们最类似的,在其基础上进行修改,这里我们参考的是samsung公司的origen
$ cp -rf board/samsung/origen/ board/samsung/fs4412
$ mv board/samsung/fs4412/origen.c board/samsung/fs4412/fs4412.c
因为修改了文件名,所以对应的Makefile也要修改
$ vi board/samsung/fs4412/Makefile
将
ifndef CONFIG_SPL_BUILD
COBJS += origen.o
endif
修改为如下内容,然后保存退出
ifndef CONFIG_SPL_BUILD
COBJS += fs4412.o
endif
拷贝origen相关的头文件并将其重命名
$ cp include/configs/origen.h include/configs/fs4412.h
修改文件中的信息
$ vi include/configs/fs4412.h
将
#define CONFIG_SYS_PROMPT "ORIGEN # "
修改为如下内容
#define CONFIG_SYS_PROMPT "fs4412 # "
再将
#define CONFIG_IDENT_STRING " for ORIGEN"
修改为如下内容,然后保存退出
#define CONFIG_IDENT_STRING " for fs4412"
打开uboot源码顶层目录下的boards.cfg文件
$ vi boards.cfg
在
origen arm armv7 origen samsung exynos
后添加如下内容(FS4412的相关信息),然后保存退出
fs4412 arm armv7 fs4412 samsung exynos
至此我们在uboot源码中给我们的板子添加了“档案”,源码就支持我们的开发板了
5.编译uboot
在uboot源码顶层目录下执行如下命令,指定当前使用的Board信息
$ make fs4412_config
编译uboot
$ make
编译完成后会在源码顶层目录下生成u-boot.bin文件,但该文件还不能在我们的开发板上运行,因为以上操作我们只是把origen相关的文件的名字改成了fs4412,使uboot能识别fs4412开发板,但文件中的代码还是origen的,和我们的开发板不匹配,所以我们还需要进一步进行修改和配置
二、uboot添加三星加密引导
考虑芯片启动的安全性,Exynos4412需要三星提供的初始引导加密后我们的u-boot才 能被引导运行,所以我们需要在uboot源码中添加三星提供的加密处理代码
1.添加三星加密引导方式
将资料中“移植相关文件”下的sdfuse_q和CodeSign4SecureBoot目录拷贝到uboot源 码的顶层目录下(这之后不要执行make clean或make distclean,这会将加密文件清除)
因为添加的加密文件也要编译,所以对应的Makefile也要修改
$ vi Makefile
在
$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
$(BOARD_SIZE_CHECK)
后添加如下内容(添加的内容需要tab键缩进,否则编译报错),然后保存退出
@#./mkuboot
@split -b 14336 u-boot.bin bl2
@+make -C sdfuse_q/
@#cp u-boot.bin u-boot-4212.bin
@#cp u-boot.bin u-boot-4412.bin
@#./sdfuse_q/add_sign
@./sdfuse_q/chksum
@./sdfuse_q/add_padding
@rm bl2a*
@echo
2.添加调试代码(点灯法)
很多时候我们不确定uboot是否已经在板子上运行,所以我们在uboot源码中添加一段 代码使板子上的LED点亮,这样如果看到LED亮的话就表示uboot已经在运行了
打开uboot启动后的第一段代码
$ vi arch/arm/cpu/armv7/start.S
在第134行后添加如下代码(即点亮LED2),然后保存退出
ldr r0, =0x11000c40
ldr r1, [r0]
bic r1, r1, #0xf0000000
orr r1, r1, #0x10000000
str r1, [r0]
ldr r0, =0x11000c44
mov r1, #0xff
str r1, [r0]
3.添加编译脚本
使用make命令编译时只链接uboot源码中的相关代码,而我们添加的初始引导加密的 代码不会被连接到u-boot.bin中,所以这里我们自己编写编译脚本build.sh,这个脚本 中除了对uboot源码进行配置和编译外还将初始引导加密代码链接到了u-boot.bin上, 最终生成一个完成的uboot镜像u-boot-fs4412.bin
将资料中“移植相关文件”下的build.sh拷贝到uboot源码的顶层目录下
给编译脚本添加可执行权限
$ chmod 777 build.sh
4.编译uboot
通过脚本编译uboot源码
$ ./build.sh
编译完成后在源码的顶层目录下会生成“u-boot-fs4412.bin”
5.测试uboot
参照之前的实验将生成的u-boot-fs4412.bin烧写到SD卡中,开发板选择SD卡启动, 然后上电查看现象,若LED2点亮则说明我自己移植的u-boot已经能够被加载运行
三、实现串口输出
虽然uboot已经能在开发板上加载运行,但是此时的uboot还不能在终端上打印信息, 原因在于uboot源码中对UART的配置与我们实际的硬件不匹配
1.修改UART源码
$ vi board/samsung/fs4412/lowlevel_init.S
在
lowlevel_init:
后添加如下内容(初始化临时栈)
ldr sp,=0x02060000
在
beq wakeup_reset
后添加如下内容(关闭看门狗)
#if 1
ldr r0, =0x1002330c
ldr r1, [r0]
orr r1, r1, #0x300
str r1, [r0]
ldr r0, =0x11000c08
ldr r1, =0x0
str r1, [r0]
/* Clear MASK_WDT_RESET_REQUEST */
ldr r0, =0x1002040c
ldr r1, =0x00
str r1, [r0]
#endif
在
uart_asm_init:
/* setup UART0-UART3 GPIOs (part1) */
mov r0, r7
ldr r1, =EXYNOS4_GPIO_A0_CON_VAL
str r1, [r0, #EXYNOS4_GPIO_A0_CON_OFFSET]
ldr r1, =EXYNOS4_GPIO_A1_CON_VAL
str r1, [r0, #EXYNOS4_GPIO_A1_CON_OFFSET]
后添加如下内容(UART初始化)
ldr r0, =0x10030000
ldr r1, =0x666666
ldr r2, =CLK_SRC_PERIL0_OFFSET
str r1, [r0, r2]
ldr r1, =0x777777
ldr r2, =CLK_DIV_PERIL0_OFFSET
str r1, [r0, r2]
注释掉
bl uart_asm_init
后的一条语句,然后保存退出
#if 0
bl tzpc_init
#endif
2.编译uboot
通过脚本编译uboot源码
$ ./build.sh
3.测试uboot
参照之前的实验将生成的u-boot-fs4412.bin烧写到SD卡中,开发板选择SD卡启动, 然后上电查看现象,若终端有打印信息则说明UART移植成功。
四、网卡移植
虽然可以通过终端输入命令,但此时的uboot还不能使用ping、tftp等命令,原因在于命令都是操作网络的,而uboot源码中网卡的相关配置与我们当前的板子不匹配,所以 我们还要对网卡进行移植
1.修改网络初始化代码
$ vi board/samsung/fs4412/fs4412.c
在
struct exynos4_gpio_part2 *gpio2;
后添加如下内容
#ifdef CONFIG_DRIVER_DM9000
#define EXYNOS4412_SROMC_BASE 0X12570000
#define DM9000_Tacs (0x1)
#define DM9000_Tcos (0x1)
#define DM9000_Tacc (0x5)
#define DM9000_Tcoh (0x1)
#define DM9000_Tah (0xC)
#define DM9000_Tacp (0x9)
#define DM9000_PMC (0x1)
struct exynos_sromc {
unsigned int bw;
unsigned int bc[6];
};
void exynos_config_sromc(u32 srom_bank, u32 srom_bw_conf, u32 srom_bc_conf)
{
unsigned int tmp;
struct exynos_sromc *srom = (struct exynos_sromc *)(EXYNOS4412_SROMC_BASE);
/* Configure SMC_BW register to handle proper SROMC bank */
tmp = srom->bw;
tmp &= ~(0xF << (srom_bank * 4));
tmp |= srom_bw_conf;
srom->bw = tmp;
/* Configure SMC_BC register */
srom->bc[srom_bank] = srom_bc_conf;
}
static void dm9000aep_pre_init(void)
{
unsigned int tmp;
unsigned char smc_bank_num = 1;
unsigned int smc_bw_conf=0;
unsigned int smc_bc_conf=0;
/* gpio configuration */
writel(0x00220020, 0x11000000 + 0x120);
writel(0x00002222, 0x11000000 + 0x140);
/* 16 Bit bus width */
writel(0x22222222, 0x11000000 + 0x180);
writel(0x0000FFFF, 0x11000000 + 0x188);
writel(0x22222222, 0x11000000 + 0x1C0);
writel(0x0000FFFF, 0x11000000 + 0x1C8);
writel(0x22222222, 0x11000000 + 0x1E0);
writel(0x0000FFFF, 0x11000000 + 0x1E8);
smc_bw_conf &= ~(0xf<<4);
smc_bw_conf |= (1<<7) | (1<<6) | (1<<5) | (1<<4);
smc_bc_conf = ((DM9000_Tacs << 28)
| (DM9000_Tcos << 24)
| (DM9000_Tacc << 16)
| (DM9000_Tcoh << 12)
| (DM9000_Tah << 8)
| (DM9000_Tacp << 4)
| (DM9000_PMC));
exynos_config_sromc(smc_bank_num,smc_bw_conf,smc_bc_conf);
}
#endif
在
gd->bd->bi_boot_params = (PHYS_SDRAM_1 + 0x100UL);
后添加如下内容
#ifdef CONFIG_DRIVER_DM9000
dm9000aep_pre_init();
#endif
在文件末尾添加如下内容,然后保存退出
#ifdef CONFIG_CMD_NET
int board_eth_init(bd_t *bis)
{
int rc = 0;
#ifdef CONFIG_DRIVER_DM9000
rc = dm9000_initialize(bis);
#endif
return rc;
}
#endif
2.修改网络配置代码
$ vi include/configs/fs4412.h
将
#undef CONFIG_CMD_PING
修改为
#define CONFIG_CMD_PING
再将
#undef CONFIG_CMD_NET
修改为
#define CONFIG_CMD_NET
在文件末尾
#endif /* __CONFIG_H */
前添加如下内容,然后保存退出
#ifdef CONFIG_CMD_NET
#define CONFIG_NET_MULTI
#define CONFIG_DRIVER_DM9000 1
#define CONFIG_DM9000_BASE 0x05000000
#define DM9000_IO CONFIG_DM9000_BASE
#define DM9000_DATA (CONFIG_DM9000_BASE + 4)
#define CONFIG_DM9000_USE_16BIT
#define CONFIG_DM9000_NO_SROM 1
#define CONFIG_ETHADDR 11:22:33:44:55:66
#define CONFIG_IPADDR 192.168.9.200
#define CONFIG_SERVERIP 192.168.9.120
#define CONFIG_GATEWAYIP 192.168.9.1
#define CONFIG_NETMASK 255.255.255.0
#endif
3.编译uboot
通过脚本编译uboot源码
$ ./build.sh
4.测试uboot
参照之前的实验将生成的u-boot-fs4412.bin烧写到SD卡中,开发板选择SD卡启动, 然后上电查看现象;设置好相关的环境变量,使用网线连接开发板与开发主机,使用 ping命令连接ubuntu,若显示“host xxx.xxx.xxx.xxx is alive”则表示网卡移植成功
五、EMMC移植
因为uboot源码中对EMMC的配置与我们的板子不匹配,这里还需要对EMMC相关的代码进行修改和配置
1.修改EMMC初始化代码
将资料中“移植相关文件”下的movi.c拷贝到uboot源码的arch/arm/cpu/armv7/exynos/ 目录下
因为添加的新文件也要编译,所以对应的Makefile也要修改
$ vi arch/arm/cpu/armv7/exynos/Makefile
将
COBJS += clock.o power.o soc.o system.o pinmux.o
修改为如下内容,然后保存退出
COBJS += clock.o power.o soc.o system.o pinmux.o movi.o
修改板级文件
$ vi board/samsung/fs4412/fs4412.c
在
#include <asm/arch/mmc.h>
后添加如下内容
#include <asm/arch/clk.h>
#include "origen_setup.h"
在
#ifdef CONFIG_GENERIC_MMC
后添加如下内容
u32 sclk_mmc4; /*clock source for emmc controller*/
#define __REGMY(x) (*((volatile u32 *)(x)))
#define CLK_SRC_FSYS __REGMY(EXYNOS4_CLOCK_BASE + CLK_SRC_FSYS_OFFSET)
#define CLK_DIV_FSYS3 __REGMY(EXYNOS4_CLOCK_BASE + CLK_DIV_FSYS3_OFFSET)
int emmc_init()
{
u32 tmp;
u32 clock;
u32 i;
/* setup_hsmmc_clock */
/* MMC4 clock src = SCLKMPLL */
tmp = CLK_SRC_FSYS & ~(0x000f0000);
CLK_SRC_FSYS = tmp | 0x00060000;
/* MMC4 clock div */
tmp = CLK_DIV_FSYS3 & ~(0x0000ff0f);
clock = get_pll_clk(MPLL)/1000000;
for(i=0 ; i<=0xf; i++) {
sclk_mmc4=(clock/(i+1));
if(sclk_mmc4 <= 160) //200
{
CLK_DIV_FSYS3 = tmp | (i<<0);
break;
}
}
emmcdbg("[mjdbg] sclk_mmc4:%d MHZ; mmc_ratio: %d\n",sclk_mmc4,i);
sclk_mmc4 *= 1000000;
/*
* MMC4 EMMC GPIO CONFIG
*
* GPK0[0] SD_4_CLK
* GPK0[1] SD_4_CMD
* GPK0[2] SD_4_CDn
* GPK0[3:6] SD_4_DATA[0:3]
*/
writel(readl(0x11000048)&~(0xf),0x11000048); //SD_4_CLK/SD_4_CMD pull-down enable
writel(readl(0x11000040)&~(0xff),0x11000040);//cdn set to be output
writel(readl(0x11000048)&~(3<<4),0x11000048); //cdn pull-down disable
writel(readl(0x11000044)&~(1<<2),0x11000044); //cdn output 0 to shutdown the emmc power
writel(readl(0x11000040)&~(0xf<<8)|(1<<8),0x11000040);//cdn set to be output
udelay(100*1000);
writel(readl(0x11000044)|(1<<2),0x11000044); //cdn output 1
writel(0x03333133, 0x11000040);
writel(0x00003FF0, 0x11000048);
writel(0x00002AAA, 0x1100004C);
#ifdef CONFIG_EMMC_8Bit
writel(0x04444000, 0x11000060);
writel(0x00003FC0, 0x11000068);
writel(0x00002AAA, 0x1100006C);
#endif
#ifdef USE_MMC4
smdk_s5p_mshc_init();
#endif
}
将board_mmc_init函数中的内容修改为(之前的内容删除即可)如下内容
int board_mmc_init(bd_t *bis)
{
int i, err;
#ifdef CONFIG_EMMC
err = emmc_init();
#endif
return err;
}
在文件的最末尾添加如下内容,然后保存退出
#ifdef CONFIG_BOARD_LATE_INIT
#include <movi.h>
int chk_bootdev(void)//mj for boot device check
{
char run_cmd[100];
struct mmc *mmc;
int boot_dev = 0;
int cmp_off = 0x10;
ulong start_blk, blkcnt;
mmc = find_mmc_device(0);
if (mmc == NULL)
{
printf("There is no eMMC card, Booting device is SD card\n");
boot_dev = 1;
return boot_dev;
}
start_blk = (24*1024/MOVI_BLKSIZE);
blkcnt = 0x10;
sprintf(run_cmd,"emmc open 0");
run_command(run_cmd, 0);
sprintf(run_cmd,"mmc read 0 %lx %lx %lx",CFG_PHY_KERNEL_BASE,start_blk,blkcnt);
run_command(run_cmd, 0);
/* switch mmc to normal paritition */
sprintf(run_cmd,"emmc close 0");
run_command(run_cmd, 0);
return 0;
}
int board_late_init (void)
{
int boot_dev =0 ;
char boot_cmd[100];
boot_dev = chk_bootdev();
if(!boot_dev)
{
printf("\n\nChecking Boot Mode ... EMMC4.41\n");
}
return 0;
}
#endif
2.添加EMMC命令
将资料中“移植相关文件”下的cmd_movi.c、cmd_mmc.c、cmd_mmc_fdisk.c拷贝到uboot 源码的common/目录下
因为添加的新文件也要编译,所以对应的Makefile也要修改
$ vi common/Makefile
在
COBJS-$(CONFIG_CMD_MMC) += cmd_mmc.o
后添加如下内容,然后保存退出
COBJS-$(CONFIG_CMD_MMC) += cmd_mmc_fdisk.o
COBJS-$(CONFIG_CMD_MOVINAND) += cmd_movi.o
将资料中“移植相关文件”下的mmc.c、s5p_mshc.c拷贝到uboot源码的drivers/mmc/ 目录下
将资料中“移植相关文件”下的mmc.h、movi.h、s5p_mshc.h拷贝到uboot源码的include/ 目录下
因为添加的新文件也要编译,所以对应的Makefile也要修改
$ vi drivers/mmc/Makefile
在
COBJS-$(CONFIG_S5P_SDHCI) += s5p_sdhci.o
后添加如下内容,然后保存退出
COBJS-$(CONFIG_S5P_MSHC) += s5p_mshc.o
3.修改EMMC配置代码
$ vi include/configs/fs4412.h
在文件的末尾
#endif /* __CONFIG_H */
前添加如下内容,然后保存退出
#define CONFIG_EVT1 1 /* EVT1 */
#ifdef CONFIG_EVT1
#define CONFIG_EMMC44_CH4 //eMMC44_CH4 (OMPIN[5:1] = 4)
#ifdef CONFIG_SDMMC_CH2
#define CONFIG_S3C_HSMMC
#undef DEBUG_S3C_HSMMC
#define USE_MMC2
#endif
#ifdef CONFIG_EMMC44_CH4
#define CONFIG_S5P_MSHC
#define CONFIG_EMMC 1
#define USE_MMC4
/* #define CONFIG_EMMC_8Bit */
#define CONFIG_EMMC_EMERGENCY
/*#define emmcdbg(fmt,args...) printf(fmt ,##args) */
#define emmcdbg(fmt,args...)
#endif
#endif /*end CONFIG_EVT1*/
#define CONFIG_CMD_MOVINAND
#define CONFIG_CLK_1000_400_200
#define CFG_PHY_UBOOT_BASE CONFIG_SYS_SDRAM_BASE + 0x3e00000
#define CFG_PHY_KERNEL_BASE CONFIG_SYS_SDRAM_BASE + 0x8000
#define BOOT_MMCSD 0x3
#define BOOT_EMMC43 0x6
#define BOOT_EMMC441 0x7
#define CONFIG_BOARD_LATE_INIT
4.编译uboot
通过脚本编译uboot源码
$ ./build.sh
5.测试uboot
参照之前的实验将生成的u-boot-fs4412.bin烧写到SD卡中,开发板选择SD卡启动, 然后上电查看现象;若显示EMMC的相关信息则表示EMMC移植成功
六、电源管理移植
因为uboot源码中对电源管理芯片的配置与我们的板子不匹配,后续有可能会导致内核 启动卡死,这里还需要对电源管理芯片相关的代码进行修改和配置
1.修改电源管理相关代码
将资料中“移植相关文件”下的pmic_s5m8767.c拷贝到uboot源码的drivers/power/pmic/ 目录下
因为添加的新文件也要编译,所以对应的Makefile也要修改
$ vi drivers/power/pmic/Makefile
在
COBJS-$(CONFIG_POWER_MAX77686) += pmic_max77686.o
后添加如下内容,然后保存退出
COBJS-$(CONFIG_POWER_S5M8767) += pmic_s5m8767.o
将添加的函数在头文件中声明
$ vi include/power/pmic.h
在
int pmic_set_output(struct pmic *p, u32 reg, int ldo, int on);
后添加如下内容,然后保存退出
void pmic_s5m8767_init(void);
修改配置文件
$ vi include/configs/fs4412.h
在文件末尾
#endif /* __CONFIG_H */
前添加如下内容,然后保存退出
#define CONFIG_POWER_S5M8767
修改板级文件
$ vi board/samsung/fs4412/fs4412.c
在board_init函数中
#ifdef CONFIG_DRIVER_DM9000
dm9000aep_pre_init();
#endif
后添加如下内容,然后保存退出
#ifdef CONFIG_POWER_S5M8767
pmic_s5m8767_init();
#endif
注释原有的代码
$ vi drivers/power/Makefile
将
COBJS-$(CONFIG_POWER) += power_core.o
修改为(即注释掉)
#COBJS-$(CONFIG_POWER) += power_core.o
修改架构文件
$ vi arch/arm/cpu/armv7/s5p-common/cpu_info.c
在
#include <asm/arch/clk.h>
后添加如下内容,然后保存退出
#include <power/pmic.h>
2.编译uboot
通过脚本编译uboot源码
$ ./build.sh
3、测试uboot
参照之前的实验将生成的u-boot-fs4412.bin烧写到SD卡中,开发板选择SD卡启动,然后上电查看现象
至此,uboot移植完成
Linux内核
内核与操作系统
- 内核 :内核是一个操作系统的核心,提供了操作系统最基本的功能,是操作系统工作的基础,决定着整个系统的性能和稳定性
- 操作系统 :操作系统是在内核的基础上添加了各种工具集、桌面管理器、库、shell、应用程序等
如我们平时用的Ubuntu是系统 Linux是内核。
系统调用是内核提供的功能,而调用库是来自于操作系统在内核的基础上添加的。
Linux层次结构
Linux内核特点
- Linux是微内核
-
代码结构清晰、模块化设计
-
支持丰富的硬件平台
-
较高的稳定性
-
轻量化及较强的裁剪性
-
开放源代码
-
更新活跃、用户较多、资料丰富
-
支持丰富的网络协议
Linux内核源码结构
Linux内核源码获取
- Linux内核源码下载 https://www.kernel.org/
- Linux内核版本命名 主版本号.次版本号.修订版本
- Linux内核版本选择
- 支持对应的硬件平台
- 相对成熟的版本(资料多)
- 稳定版本(次版本号为偶数的版本一般都是稳定版)
平台相关代码
arch:与CPU架构相关的源代码
平台无关代码
- block:磁盘设备的支持 crypto:加密相关
- drivers:设备驱动 firmware:固件
- fs:文件系统 include:头文件
- init:内核初始化 ipc:进程间通信
- kernel:内核核心调度机制等 lib:库
- mm:内存管理 net:网络协议
- scripts:工具、脚本等 security:安全
- usr:打包与压缩 virt:虚拟
帮助文档、示例程序、工具等
- COPYING: 版权
- CREDITS: 内核贡献者
- README: 说明文档
- Documentation: 帮助文档
- Makefile: 编译管理
- samples: 示例
- tools: 工具
Linux内核的配置与编译
Linux内核源码配置
指定处理器架构及编译工具
在Linux内核源码顶层目录下的Makefile中指定(ARCH、CROSS_COMPILE)
导入当前处理器的默认配置
make <soc_name>_defconfig
注1:soc_name为当前使用的处理器的名字
注2:内核源码的arch/arm/configs下对各个厂商的soc都有一个默认配置文件。执行该命令后就会将对应的配置文件中的信息导入到源码顶层目录下的.config。文件中CONFIG_xxx=y表示内核选中了该功能,内核编译时就会将该功能对应的代码编译,内核的体积也会增大。#CONFIG_xxx is not set表示内核没有选中该功能,内核编译时该功能对应的代码不会被编译,内核的体积也会减小。
修改配置
默认配置只能保证内核拥有最基本的功能,我们需要根据自己的实际需求对内核做进一步的配置
- 方法1:直接修改.config文件(不推荐)
- 方法2:make menuconfig
make menuconfig
修改配置
[ ] 有两种状态
- 输入Y,显示“*”,内核中该功能被选中,相关代码会被编译进内核
- 输入N,显示“ ”,内核中该功能不被选中,相关代码不会被编译进内核
< > 有三种状态
- 输入Y,显示“*”,内核中该功能被选中,相关代码会被编译进内核
- 输入N,显示“ ”,内核中该功能不被选中,相关代码不会被编译进内核
- 输入M,显示“M”,内核中该功能被选为模块(被编译为独立的模块)
注:使用make menuconfig配置的本质还是修改.config文件
Linux内核源码编译
内核编译(以下命令均在内核源码的顶层目录下执行)
- make uImage 编译内核(编译选为“*”的选项到内核)
- make modules 编译内核模块(编译选为“M”的选项为独立模块)
- make dtbs 编译设备树(将设备树源文件dts编译为二进制文件dtb)
- make clean 删除编译过程中产生的中间文件
Linux内核驱动移植
- 在make menuconfig界面中选中要安装的驱动
- 在设备树中添加/修改相应的设备信息
- 重新编译内核/设备树
设备树
设备树是一种描述硬件信息的数据结构,Linux内核运行时可以通过设备树将硬件信息直接传递给Linux内核,而不再需要在Linux内核中包含大量的冗余编码。
设备树文件
- dts 设备树源文件
- dtsi 类似于头文件,包含一些公共的信息,可被其它设备树文件引用
- dtb 编译后的设备树文件
设备树语法
设备树的语法为树状结构,由一系列的节点和属性组成,根节点下包含子节点。子节点下还可以包含子节点,节点内部包含了对应设备的属性。
/{//根节点的孩子结点;
memory{
0x40000000(起始地址) 0x40000000(空间大小)
};
dm9000{
0x05000000;//网卡地址
};
keypad{
up{
0x00000020;
0x10000020;
//两个可以控制上的引脚 也就是根结点的孙子结点
};
down;
};
}
根文件系统
根文件系统是内核启动后挂载的第一个文件系统系统。引导程序会在根文件系统挂载后从中把一些基本的初始化脚本和服务等加载到内存中去运行。
根文件系统内容
- bin shell命令(elf格式)(通过busybox编译生成) 内核不包含shell命令 想使用这些命令则放进了根文件系统
- dev 设备文件(内核启动后会将设备信息写入该目录)
- etc 内核配置文件(启动后要读取配置信息)
- lib 共享库(elf格式)(从交叉编译工具链中获取) 想在开发板上执行程序,就要把库放进根文件系统
- linuxrc 内核运行的第一个应用程序(通过busybox编译生成)
- mnt 挂载目录(非必要)
- proc 进程相关文件(内核启动后会将进程信息写入该目录)
- root 超级用户家目录(非必要)
- sbin 系统管理shell命令(elf格式)(通过busybox编译生成)(super bin)
- sys 驱动相关文件(内核启动后会将驱动信息写入该目录)
- usr shell命令(elf格式)(通过busybox编译生成)
BusyBox
BusyBox将很多常用的工具集成到一个很小的可执行文件中,为普通用户提供大多数常用的命令,BusyBox实现的命令都是精简版的,很多扩展都不支持。BusyBox被称为Linux工具里的瑞士军刀 。
BusyBox的获取 https://busybox.net/downloads/