上上篇:讯为4412开发板嵌入式学习(九)uboot启动过程
上一篇:讯为4412开发板嵌入式学习(十)uboot源代码结构
4412的主板为arm架构,cortex-A9内核。假如指令为itop_4412_android_config_scp_1GDDR,我们分析Makefile文件。
首先删除不相关的架构配置和信息。
删除行数范围为3264~3810的数据
删除行数范围为530~3201的数据
由于makefile之前都需要先进行配置,所以先讲配置部分的代码。
下面分析Makefile中配置(mkconfig)部分。
itop_4412_android_config_scp_1GDDR: unconfig
@$(MKCONFIG) $(@:_config=) arm arm_cortexa9 smdkc210 samsung s5pc210 SCP_1GDDR
MKCONFIG := $(SRCTREE)/mkconfig
SRCTREE := $(CURDIR)
CURDIR是make的内嵌变量,代表的是当前目录./
。
所以 @$(MKCONFIG) 代表的是 ./mkconfig 。 @ 表示不输出该命令行。
这是Makefile中常用的字符串处理方式,$(@)的意思是目标文件集,而输入的文件集就是itop_4412_android_config_scp_1GDDR,后面的 :_config= 指的是用目标的字符串 _config 之前的字符串代替它,并且后面的 = 意思是省略其后面的字段。
所以 $(@:_config=) 代表的是 itop_4412_android 。
综上所述,整句话的意思如下:
./mkconfig itop_4412_android arm arm_cortexa9 smdkc210 samsung s5pc210 SCP_1GDDR
由上述可知,传入mkconfig的完整参数表如下:
$0 | $1 | $2 | $3 | $4 | $5 | $6 | $7 |
---|---|---|---|---|---|---|---|
mkconfig | itop_4412_android | arm | arm_cortexa9 | smdkc210 | samsung | s5pc210 | SCP_1GDDR |
#!/bin/sh -e
#指定解释器路径,同时`-e`表示的是程序会自动根据指令返回值$?是否为零判断是否执行正确指令,非零则自动退出脚本。
# Script to create header files and links to configure 文件的说明,意思是该脚本的作用是创建要配置的头文件和链接。
# U-Boot for a specific board. 为特定板子服务的U-boot。
#
# Parameters: Target Architecture CPU Board [VENDOR] [SOC] 输入参数为Target(目标) Architecture(架构) CPU Board(开发板) \[VENDOR\](供应商) \[SOC\](片上系统)
#
# (C) 2002-2006 DENX Software Engineering, Wolfgang Denk 代码编写/维护者
#
APPEND=no # Default: Create new config file 默认创建一个新的配置文件
#BOARD_NAME="" # Name to print in make output make要输出的名字
TARGETS="" #目标名
echo "CoreBoard is $7...... " #根据输入参数$7输出核心板信息(此处$7是SCP_1GDDR)
# 根据$7选择板子名并输出核心板运行的OS信息
if [ "$7" = "SCP_1GDDR" ] || [ "$7" = "SCP_2GDDR" ] || [ "$7" = "POP_1GDDR" ] || [ "$7" = "POP_2GDDR" ]
then
BOARD_NAME="itop_4412_android"
echo "CoreBoard OS is android or linux...... "
elif [ "$7" = "SCP_1GDDR_Ubuntu" ] || [ "$7" = "SCP_2GDDR_Ubuntu" ] || [ "$7" = "POP_1GDDR_Ubuntu" ] || [ "$7" = "POP_2GDDR_Ubuntu" ]
then
BOARD_NAME="itop_4412_ubuntu"
echo "CoreBoard OS is Ubuntu...... "
else
echo "unknown coreboard type and os type......"
fi
#传给脚本的参数数量(不算上$0)大于0则进入while循环
while [ $# -gt 0 ] ; do
#判断参数$1是否为 -- -a -t 或是其他输入并执行相应动作。
case "$1" in
--) shift ; break ;;
-a) shift ; APPEND=yes ;;
# -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
#获取参数$1 字符'_'替换为空格并将结果赋值给TARGETS
-t) shift ; TARGETS="`echo $1 | sed 's:_: :g'` ${TARGETS}" ; shift ;;
*) break ;;
esac
done
#判断参数BOARD_NAME是否为空,为空的话则将输入参数$1赋值给BOARD_NAME。(使用逻辑或的短路特性)
[ "${BOARD_NAME}" ] || BOARD_NAME="$1"
#如果输入参数数量小于4或大于7则退出脚本,即输入参数数量要 4<=$#<=7
[ $# -lt 4 ] && exit 1
[ $# -gt 7 ] && exit 1
#判断是否为ARM架构
if [ "${ARCH}" -a "${ARCH}" != "$2" ]; then
#不是则输出错误信息,并将标准输出至通道2
echo "Failed: \$ARCH=${ARCH}, should be '$2' for ${BOARD_NAME}" 1>&2
#退出脚本
exit 1
fi
#输出板子开始配置的信息
echo "Configuring for ${BOARD_NAME} board..."
#
# Create link to architecture specific headers 创建架构特定的头文件链接
#
#在Makefile中有以下两句定义
#如果$BUILD_DIR不为空的话,则将$BUILD_DIR赋值给$OBJTREE,否则将$CURDIR赋值给$OBJTREE
#OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
#SRCTREE := $(CURDIR) #当前目录
#
if [ "$SRCTREE" != "$OBJTREE" ] ; then
#OBJTREE不是当前目录路径,则新建文件夹,将asm-arm和asm链接起来
mkdir -p ${OBJTREE}/include
mkdir -p ${OBJTREE}/include2
cd ${OBJTREE}/include2
rm -f asm
ln -s ${SRCTREE}/include/asm-$2 asm
LNPREFIX="../../include2/asm/"
cd ../include
rm -rf asm-$2
rm -f asm
mkdir asm-$2
ln -s asm-$2 asm
else
#OBJTREE是当前目录路径,直接将asm-arm和asm链接起来,成立
cd ./include
rm -f asm
#等价于 ln -s asm-arm asm
ln -s asm-$2 asm
fi
直接将asm->asm-arm链接起来便于不同平台的移植。在代码内部直接使用#include
rm -f asm-$2/arch # 删除与架构相关的目录asm-arm/arch
#判断$6(s5pc210)是否为0或者是否为空
if [ -z "$6" -o "$6" = "NULL" ] ; then
#成立则将arch-arm_cortexa9和asm-arm/arch链接起来(${LNPREFIX}未定义,为空)
ln -s ${LNPREFIX}arch-$3 asm-$2/arch
else
#否则将arch-s5pc210和asm-arm/arch链接起来,成立
ln -s ${LNPREFIX}arch-$6 asm-$2/arch
fi
#判断参数$2是否等于arm
if [ "$2" = "arm" ] ; then
#删除原先的asm-arm/proc,将proc-armv和asm-arm/proc链接起来,成立
rm -f asm-$2/proc
ln -s ${LNPREFIX}proc-armv asm-$2/proc
fi
此处将arch-s5pc210和asm-arm/arch链接起来了。
此处将proc-armv和asm-arm/proc链接起来了。
#
# Create include file for Make 为make创建包含文件
#
#将这三个参数打印到config.mk文件 "ARCH=arm" "CPU=arm_cortexa9" "BOARD=smdkc210"
echo "ARCH = $2" > config.mk #一个'>'表示新建一个config.mk并把数据添加进去
echo "CPU = $3" >> config.mk #两个'>'表示在后面追加数据
echo "BOARD = $4" >> config.mk
#参数$5存在且不等于NULL,然后在config.mk后追加"VENDOR=samsung"
[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk
#参数$6存在且不等于NULL,然后在config.mk后追加"SOC=s5pc210"
[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk
# Assign board directory to BOARDIR variable 为BOARDIR变量分配板子相关信息的目录
#判断参数$5(samsung)是否等于0且为空
if [ -z "$5" -o "$5" = "NULL" ] ; then
#BOARDDIR设置为smdkc210
BOARDDIR=$4
else
#BOARDDIR设置为samsung/smdkc210,成立
BOARDDIR=$5/$4
fi
#add by dg for kinds of coreboard 添加各种核心板
#判断参数$7(SCP_1GDDR)是否为几种SCP_系列的核心板,成立
if [ "$7" = "SCP_1GDDR" ] || [ "$7" = "SCP_2GDDR" ] || [ "$7" = "SCP_1GDDR_Ubuntu" ] || [ "$7" = "SCP_2GDDR_Ubuntu" ]
then
# 在文件config.mk后追加"CORE = SCP"
echo "CORE = SCP" >> config.mk
#将板子和CPU相关的启动文件链接起来
ln -sf ${SRCTREE}/board/samsung/smdkc210/lowlevel_init_SCP.S ${SRCTREE}/board/samsung/smdkc210/lowlevel_init.S
ln -sf ${SRCTREE}/cpu/arm_cortexa9/s5pc210/cpu_init_SCP.S ${SRCTREE}/cpu/arm_cortexa9/s5pc210/cpu_init.S
#同理可得
elif [ "$7" = "POP_1GDDR" ] || [ "$7" = "POP_2GDDR" ] || [ "$7" = "POP_1GDDR_Ubuntu" ] || [ "$7" = "POP_2GDDR_Ubuntu" ]
then
echo "CORE = POP" >> config.mk
ln -sf ${SRCTREE}/board/samsung/smdkc210/lowlevel_init_POP.S ${SRCTREE}/board/samsung/smdkc210/lowlevel_init.S
ln -sf ${SRCTREE}/cpu/arm_cortexa9/s5pc210/cpu_init_POP.S ${SRCTREE}/cpu/arm_cortexa9/s5pc210/cpu_init.S
else
echo "make config error,please use correct params......"
exit 0
fi
#
# Create board specific header file 创建板子特定的头文件
#
#如果参数$APPEND为yes则往现有的config.h文件追加信息
if [ "$APPEND" = "yes" ] # Append to existing config file
then
echo >> config.h
else
> config.h # Create new config file 否则创建新的config头文件,成立
fi
#往config.h后追加信息
echo "/* Automatically generated - do not edit */" >>config.h
#依次将参数$TARGETS内的参数和#define CONFIG_MK_${i} 1整合起来追加到config.h后面,此处$TARGETS为空,不执行
for i in ${TARGETS} ; do
echo "#define CONFIG_MK_${i} 1" >>config.h ;
done
#add by dg for all itop4412 type boards 添加应用于全部itop4412类型板子的信息
#判断参数$7(SCP_1GDDR)是否存在且不为空,成立则并将相关信息追加到config.h
[ "$7" ] && [ "$7" != "NULL" ] && echo "#define CONFIG_$7" >> config.h
#将下面EOF前面的数据追加到文件config.h
cat << EOF >> config.h
#define CONFIG_BOARDDIR board/$BOARDDIR
#include
#include
#include
EOF
exit 0
查看config.h的内部信息
综上所述,Makefile配置文件mkconfig主要实现的作用有:
有关贡献人员和代码分享相关的GNU通用公共许可证(GNU General Public Licence)的说明。
VERSION = 2010
PATCHLEVEL = 03
SUBLEVEL =
EXTRAVERSION =
ifneq "$(SUBLEVEL)" ""
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
else
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL)$(EXTRAVERSION)
endif
TIMESTAMP_FILE = $(obj)include/timestamp_autogenerated.h
VERSION_FILE = $(obj)include/version_autogenerated.h
版本相关的信息。
#获取主机平台
HOSTARCH := $(shell uname -m | \
sed -e s/i.86/i386/ \
-e s/sun4u/sparc64/ \
-e s/arm.*/arm/ \
-e s/sa110/arm/ \
-e s/powerpc/ppc/ \
-e s/ppc64/ppc/ \
-e s/macppc/ppc/)
#获取主机操作系统
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
sed -e 's/\(cygwin\).*/cygwin/')
# Set shell to bash if possible, otherwise fall back to sh 可能的话将shell设置为bash,否则回退到sh
#获取shell路径
SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
else if [ -x /bin/bash ]; then echo /bin/bash; \
else echo sh; fi; fi)
#设置环境变量
export HOSTARCH HOSTOS SHELL
# Deal with colliding definitions from tcsh etc. 定义一个变量用于处理tcsh等的冲突定义。
VENDOR=
获取主机平台(ubuntu中执行uname -m即可,但这里是为了尽可能匹配所有设备)。
获取主机操作系统(ubuntu中执行uname -s即可)。
获取shell路径。
综上有如下结果:
HOSTARCH = x86_64
HOSTOS = Linux
SHELL = /bin/bash
VENDOR=
#########################################################################
# Allow for silent builds 允许在编译的时候不输出信息
#在变量MAKEFLAGS中查找是否存在s字符串,查找到则返回s,否则返回NULL。然后判断NULL和结果是否相同,从而通过变量XECHO决定是否输出编译信息。
ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
else
XECHO = :
endif
在编译的时候可使用命令行输入参数(在命令行使用make O=),从而设置BUILD_DIR的路径。
ifdef O
ifeq ("$(origin O)", "command line")
BUILD_DIR := $(O)
endif
endif
#如果BUILD_DIR不为空,则将saved-output设置为BUILD_DIR
ifneq ($(BUILD_DIR),)
saved-output := $(BUILD_DIR)
具体操作可参考$(origin variable)详解
# Attempt to create a output directory.尝试创建一个新的输出文件夹
#-d(directory)判断路径BUILD_DIR是否为文件夹,是则返回0并退出,否则在目录下递归创建新的文件夹。这里利用到||的短路特性
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})
# Verify if it was successful. 验证是否正确
# 如果BUILD_DIR用的是相对路径,则将它转换为绝对路径(pwd就是打印当前的绝对目录的绝对路径),这里利用到&&的断路特性
BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd)
#如果BUILD_DIR为空,则输出错误信息
$(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist))
endif # ifneq ($(BUILD_DIR),)
#各种路径变量
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE := $(CURDIR)
TOPDIR := $(SRCTREE)
LNDIR := $(OBJTREE)
#设置环境变量
export TOPDIR SRCTREE OBJTREE
#设置mkconfig的路径并设置环境变量
MKCONFIG := $(SRCTREE)/mkconfig
export MKCONFIG
#如果OBJTREE和SRCTREE不一样,则设置环境变量REMOTE_BUILD为1,说明是在外部编译
ifneq ($(OBJTREE),$(SRCTREE))
REMOTE_BUILD := 1
export REMOTE_BUILD
endif
#obj和src定义在config.mk,但在这里的这两个变量在makefile文件中。
#同时以防一些复位清理的操作,在config.mk被包含之前我们需要使用它们。
# $(obj) and (src) are defined in config.mk but here in main Makefile
# we also need them before config.mk is included which is the case for
# some targets like unconfig, clean, clobber, distclean, etc.
#如果OBJTREE和SRCTREE不相等,分别将其赋值,否则清零。并设置环境变量
ifneq ($(OBJTREE),$(SRCTREE))
obj := $(OBJTREE)/
src := $(SRCTREE)/
else
obj :=
src :=
endif
export obj src
#确保CDPATH不被干涉,取消这个环境变量
# Make sure CDPATH settings don't interfere
unexport CDPATH
#########################################################################
#如果架构为powerpc则将其更改为ppc(Performance Optimization With Enhanced RISC-Performance Computing,是一种精简指令集RISC的CPU)
ifeq ($(ARCH),powerpc)
ARCH = ppc
endif
#判断是否存在config.mk,wildcard的作用是找出和规则匹配的项。
ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk))
#在执行config.mk之前先让模块包含autoconf.mk,这样可以使配置信息对全部上层编译文件有效。
# Include autoconf.mk before config.mk so that the config options are available
# to all top level build files. We need the dummy all: target to prevent the
# dependency target in autoconf.mk.dep from being the default.
all:
#通常我们在Makefile中可使用“-include”来代替“include”,来忽略由于包含文件不存在或者无法创建时的错误提示(“-”的意思是告诉make,忽略此操作的错误。make继续执行)
#使全部模块包含自动配置的依赖包和配置文件
sinclude $(obj)include/autoconf.mk.dep
sinclude $(obj)include/autoconf.mk
# load ARCH, BOARD, and CPU configuration 加载配置并设置环境变量
include $(obj)include/config.mk
export ARCH CPU BOARD VENDOR SOC
yixiasinclude/autoconfig.mk文件内容,由CONFIG_xx组成的宏定义。
以下是include/autoconfig.mk.dep文件内容,包含include目录下(包括子目录)所有头文件。
以下是include/config.mk文件内容,这部分内容在之前mkconfig中生成。
根据架构选择编译器(如arm架构使用编译器前缀为arm-none-linux-gnueabi-)并设置环境变量
加载其他配置
# load other configuration 加载顶层的config.mk
include $(TOPDIR)/config.mk
#########################################################################
# U-Boot objects....order is important (i.e. start must be first) U-Boot目标运行的次序很重要,也就是说必须先运行start
#根据特定CPU添加启动程序,其中resetvec.o是通过使用复位向量是程序跳转到启动位置
OBJS = cpu/$(CPU)/start.o
ifeq ($(CPU),i386)
OBJS += cpu/$(CPU)/start16.o
OBJS += cpu/$(CPU)/resetvec.o
endif
ifeq ($(CPU),ppc4xx)
OBJS += cpu/$(CPU)/resetvec.o
endif
ifeq ($(CPU),mpc85xx)
OBJS += cpu/$(CPU)/resetvec.o
endif
#将前缀$obj(即路径)添加到OBJS
OBJS := $(addprefix $(obj),$(OBJS))
resetvec.S源码
加载各种库,如通用库、原厂库、CPU库、文件系统库、磁盘相关库、驱动库、接口库和GCC库等。
LIBS = lib_generic/libgeneric.a
#......中间还有很多
LIBS += libfdt/libfdt.a
LIBS += api/libapi.a
LIBS += post/libpost.a
#为LIBS添加前缀$obj(即路径)
LIBS := $(addprefix $(obj),$(LIBS))
#将待生成的库文件当成伪目标来处理
.PHONY : $(LIBS) $(TIMESTAMP_FILE) $(VERSION_FILE)
#和板子相关的库,并添加路径
LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a
LIBBOARD := $(addprefix $(obj),$(LIBBOARD))
# Special flags for CPP when processing the linker script. 当处理链接脚本时CPP特殊标志位
# Pass the version down so we can handle backwards compatibility 为了能够处理向后兼容而传递版本号
# on the fly.
#...
#subst是替换字符串函数,去除OBJS中的字串$obj
__OBJS := $(subst $(obj),,$(OBJS))
__LIBS := $(subst $(obj),,$(LIBS)) $(subst $(obj),,$(LIBBOARD))
使用所提供的依赖编译出指定类型文件,看到最下面编译u-boot时使用到链接文件u-boot.lds,我们先对其进行分析。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") #输出文件格式为小端模式的elf32位文件
OUTPUT_ARCH(arm) #输出架构为arm
ENTRY(_start) #进入起始入口
SECTIONS #段定义
{
. = 0x00000000; #u-boot的起始地址,main的起始地址在此基础上还要加上偏移0xc3e00000
. = ALIGN(4); #4字节对齐
.text : #代码段,指定路径的文件按照顺序摆放
{
cpu/arm_cortexa9/start.o (.text) #startup启动代码
cpu/arm_cortexa9/s5pc210/cpu_init.o (.text) #cpu初始化代码
board/samsung/smdkc210/lowlevel_init.o (.text) #底层初始化代码
common/ace_sha1.o (.text) #sha1加密代码
*(.text) #剩余的代码
}
. = ALIGN(4); #4字节对齐
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } #只读数据段RO,数据按照名字进行排列
. = ALIGN(4); #4字节对齐
.data : { *(.data) } #读写数据段RW
. = ALIGN(4); #4字节对齐
.got : { *(.got) } #uboot自定义的数据段got
__u_boot_cmd_start = .; #把当前位置设为uboot命令段起始
.u_boot_cmd : { *(.u_boot_cmd) } #uboot cmd命令段
__u_boot_cmd_end = .; #uboot命令段末尾设为当前位置
. = ALIGN(4); #4字节对齐
__bss_start = .; #把当前位置设为bss段起始
.bss : { *(.bss) }#bss段(Block Started by Symbol,存储未被初始化的静态和全局变量)
_end = .; #把当前位置设为bss段末尾
}
由于cpu最先执行的文件是start.S,我们先对其进行分析。
BL1前16字节预留给iROM用于标识BL1。
中断向量的设置,58行跳转(b,不返回跳转)到184行的reset。
设置CPU工作模式为SVC,拥有最高的权限修改底层。
cache缓存初始化,关闭缓冲器(TLBs)和内部缓存(icache),关闭内存管理单元(mmu)。
测试LED
读取启动信息,即OM寄存器的状态。
查看源码可得知如下两个宏定义的具体值:
#define POWER_BASE 0x10020000
#define OMR_OFFSET 0x0
电源管理单元PMU基址
PMU内部寄存器偏移及说明
接下来根据读取到的OM状态值r2选择启动设备,并将对应的设备值存储到r0。
作为子程序跳转(bl)到底层初始化程序(board/samsung/smdkc210/lowlevel_init.o),初始化锁相环、多路复用器和存储器等(pll,mux,memory)。
上电冷启动使PS_HOLD引脚输出高电平,和手机开机长按电源键一个道理,使电源管理芯片开始工作。
准备调用C函数,设置栈指针。
判断程序是在eMMC还是在内存。
当程序已经在ram中运行时,我们不需要重新定位到U-Boot。实际上,在U-Boot在ram中运行之前必须配置内存控制器(memory controller)。
置位指定IO口,点亮两盏灯。延时一段时间。
根据保存起来的OM值跳转到对应启动设备标签的代码位置。这里用的是eMMC441作为启动设备,故跳转到emmc441_boot,即第418行。
配置时钟,然后返回跳转到emmc441_uboot_copy,将uboot从eMMC拷贝到内存,然后返回到原来调用的位置(此时mmu还是关闭的,内存还不能工作)。
其中emmc441_uboot_copy函数在cpu/arm_cortexa9/s5pc210/movi.c文件中。它的作用是复制BL2。
然后判断拷贝是否成功,否则进入TF卡拷贝。
一切正常后跳转到after_copy标签。
使能MMU和开启MMU,然后进行堆栈设置,清空BSS段,最后启动C语言相关start_armboot函数,它被定义在lib_arm\board.c中,至此,汇编部分的代码到此结束。
由于NandFlash工艺问题会存在坏块,需要ECC(Error Checking and Correcting,错误检查和纠正)进行纠错,4412内部具有16bit的纠错能力。
各存储器内部组成:
通过三星原厂提供的文档进行烧写,文件名为:SEC_Exynos4x12_[SSCR][TC4]ICS_Installation_Guide_RTM1.0.2
假如要读取abc.bin这个二进制文件,则可以如下操作:
1、以二进制形式打开文件
vim -b abc.bin
2、转换字符为16进制
:%!xxd
3、当查看完成,输入
%!xxd -r
或者直接输入q!
退出即可。
假如要删除文件abc之外的文件,可通过指令
sudo rm -rf `ls | grep -v "^abc$"`
又或者要删除文件aa和bb之外的文件,可通过指令
sudo rm -rf `ls | grep -v "^aa$" | grep -v "^bb$"`
参数说明:
所以^aa$的意思是全文匹配。
1、明确知道行的范围进行删除
假如要删除m行到n行,输入指令:m,n d
2、从当前行m开始往n行删除
mg
(m是数字,指定的行数)将光标移动到m行。:.,nd
删除第m行到第n行(m撤销:u(undo)
重做:ctrl+r(redo)
出现如下错误:
zsh: no matches found: s/arm.*/arm/
1、在~/.zshrc添加
setopt no_nomatch
2、执行
source ~/.zshrc
参考资料:
ARM 中断状态和SVC状态的堆栈切换 (异常)
ARM官方手册
IRAM IROM 区别
Linux grep 命令
Linux awk 命令
makefile下$^,$@,$?定义使用详解
Linux sed 命令
Shell 传递参数
makefile中的:BUILD_DIR解释
cat < Tcsh脚本编程 makefile之findstring函数 $(origin variable)详解 uboot笔记之makefile分析 u-boot.lds链接文件详解 x86 Assembly Language Reference Manual ARM Assembly Language Programming