下面代码以高通智驾平台为例。
1 QNX应用程序编译原理
在高通提供的qnx开发包中,qnx的内核已经由qnx所提供,所以qnx的编译,其实就是大量应用程序的编译,以及最后利用buildfile文件,把内核,库文件以及应用程序打包在一起的过程。
1.1 qnx的工程目录
应用程序的编译,可以利用最常见的makefile规则,来指定生成目标所需要的依赖文件;也可以利用qnx提供的编译机制来生成对应的可执行文件或者库文件。文本着重介绍后者,如何利用qnx的编译机制来生成应用程序。
如果要利用qnx的编译机制,来生成可执行文件,首先要构建如下所示的文件目录结构:
qnx规则对文件目录进行了比较详细的规范,要求开发者尽量遵循上述规则来构建适应多os,多架构的构建。这样构建目录的好处是开发者可以尽可能把通用的代码放到上层目录,尽可能的减少代码的可重复性。同时,每一层级都会有makefile文件,用户可以在任何目录输入make命令,make 会递归每一级目录来编译相应的内容。根据开发经验,上述目录并不是必选的,但是CPU level和variant level是开发过程中需要构建的目录。
这边尤其要注意的是variant level目录的名字,决定着最后编译出来的文件是可执行文件,动态库,还是静态库。
该名字可以包含如下的一些组合,并用(.),(-)或者(/)来进行分割。
名字中包含a字符:说明该文件需要构建成静态库。
名字中包含so字符:说明该文件需要构建成动态库。
名字中包含g字符:说明该文件需要构建成debug的版本,该版本会包含调试信息,可以供gdb调试器使用。
名字中包含be, le字符:说明该目标文件是大端或者是小端。
比如说variant level的目录名为g.le,说明需要构建的目标文件是一个debug的版本的小端的可执行程序。不过有一点需要注意的是,向.a, .so, or .o这些选项最好不要放在名字的结尾,不然会和现有的文件类型产生混淆。
1.2 qnx的makefile
用qnx的规则来构建makefile,可以看到除了最底层variant level,其他层的makefile文件所包含的内容基本都是类似的:
LATE_DIRS=boards
include recurse.mk
recurse.mk 文件用来通知makefile向更底层的目录遍历。
在同级别如果有多个目录,并且不同目录中的文件构建有先后的依赖关系,我们可以使用EARLY_DIRS和LATE_DIRS来构建。需要更早构建的目录放在EARLY_DIRS,需要更晚构建的目录,放在LATE_DIRS中,EARLY_DIRS和LATE_DIRS中可以放置多个目录,但各个目录之间构建的时候没有先后关系。
而在最底层的variant level中,makefile文件的内容基本是这样的形式:
include ../../common.mk
common.mk文件通常放在project目录下面。common.mk文件中的内容是整个编译的核心,通常放着编译的标志,头文件位置,源文件位置,以及需要链接的库等。
common.mk中的内容大概如下:
ifndef QCONFIG
QCONFIG=qconfig.mk
endif
include $(QCONFIG)
# Preset make macros go here
include $(MKFILES_ROOT)/qtargets.mk
# Postset make macros go here
qconfig.mk宏通常包含着一些编译中使用的编译工具的宏定义,命令的宏定义,如(CP_HOST
,LN_HOST)qnx建议在编译的时候使用这些宏,而不是直接去引用某个绝对路径,或者某个host中的命令。在Preset 的位置,用户可以用来定义一些宏,比如如何进行链接,包含了哪些头文件或者源文件,以及目标文件的名字等。Postset可以用来定义一些哄,用来覆盖qtargets.mk中的定义。qtargets.mk用来包含安装路径以及文件链接。
qrules.mk用来放置编译过程中会用到的一些宏,如目标文件,源文件,编译标志,对于可写属性的宏,用户可以进行更改。
一些编译过程中会经常用到的宏:
INSTALLDIR:目标文件安装路径
CCFLAGS:编译标志位
LDFLAGS:链接标志位
LIBS:需要链接的库文件
NAME:指定目标文件的名字
EXTRA_INCVPATH:编译过程中需要搜索的头文件路径
EXTRA_SRCVPATH:编译过程中的源文件,通常源文件不在当前工程文件下时,需要设置该变量。
EXTRA_LIBVPATH:指定编译过程中搜索依赖库的路径
2 QNX image构建原理
在上述利用通用makefile规则或者qnx的makefile规则,编译出相应的可执行程序以后,就需要把这些应用程序打包,构建启动文件。qnx 利用buildfile文件来构建整个image。buildfile制定了qnx的启动文件,启动脚本,以及启动过程中需要打包的库文件以及应用程序。
可以先看一下qnx的启动时序:
最一开始的PLL是硬件相关的,这边不做讨论。
IPL阶段,全称是initial program loader,这是引导qnx系统的第一个阶段。通常用来做一些最基本的硬件初始化工作,当然也有可能会对startup程序进行内存拷贝(如果这一部分工作没有在bootloader完成的话)。
Startup阶段,完成后续的硬件初始化工作,包括内存页表映射,内核调用函数链接,然后把接下来的工作交给procnto(内核),拷贝os image到ram中。
Base system包含内核,以及系统启动的一些必要文件。
Boot script阶段,系统启动中优先级高的任务,可放在该阶段执行。
SLM阶段,一般的应用程序,可放在该阶段执行。
而由startup program,base system和boot script等组成的image,又称作Image Filesystem(IFS)
上述描述了整个qnx系统启动时的一个大概的时序,而buildfile所做的工作,基本能和上述所描述的元素所吻合。看一下buildfile的一些基本要素:
[virtual=x86_64,bios] .bootstrap = {
startup-x86
# PATH is the *safe* path for executables (confstr(_CS_PATH...))
# LD_LIBRARY_PATH is the *safe* path for libraries
# (confstr(_CS_LIBPATH)). That is, it's the path searched for libs
# in setuid/setgid executables.
# The module=aps enables the adaptive partitioning scheduler.
[module=aps] PATH=/proc/boot:/bin:/usr/bin:/sbin:/usr/sbin \
LD_LIBRARY_PATH=/proc/boot:/lib:/lib/dll:/usr/lib \
procnto-smp-instr
}
# Start-up script
[+script] .script = {
# Create some adaptive partitions during system startup:
# - IOPKT with a 20% budget
# - QCONN with a 20% budget
# NOTE: To specify a critical budget of 5 ms, use sched_aps as seen below
# when the filesystem on the disk is available.
sched_aps IOPKT 20 5
sched_aps QCONN 20 5
。。。。。。。
# Start the main shell
reopen /dev/con1
[+session] sh &
}
[perms=0777]
# Include the current "libc.so". It will be created as a real file
# using its internal "SONAME", with "libc.so" being a symlink to it.
# The symlink will point to the last "libc.so.*" so if an earlier
# libc is needed (e.g. libc.so.5) add it before the this line.
libc.so
libgcc_s.so.1
/usr/lib/ldqnx-64.so.2=ldqnx-64.so.2
libelfcore.so.1
libslog2.so
#libusbdi.so
devu-hcd-ehci.so
virtual=x86_64,bios描述的是IPL,startup-x86描述的是startup程序,procnto-smp-instr 是procnto程序,.script 中描述的是启动脚本需要完成的事情。最下面的内容是IFS中包含的一些系统启动所需要的库文件以及应用程序。通过buildfile文件,描述了整个IFS构建所需要的文件以及系统的启动流程。
3 以一个例子介绍高通基线应用程序的编译
这边以qcarcam_test为例,介绍一下高通如何利用qnx的规则,来生成对应的应用程序。
qcarcam_test 的目录结构如下所示:
源文件放在src目录中,build目录就是之前第一章所描述的,利用qnx的规则所创建的目录结构,build 目录可以理解为project level,aarch64目录和arm目录为cpu level,o-le和o-le-v7为variant level。
除了variant level的makefile中引用了common.mk:
其他的makefile文件都只是包含了recurse.mk,用来递归搜索下层目录:
目标文件编译规则由common.mk来描述:
ifndef QCONFIG
QCONFIG=qconfig.mk
endif
include $(QCONFIG)
include $(AMSS_ROOT)/amss_defs.mk
include ../../../../../build/qnx/overrides.mk
NAME=qcarcam_test
#===== INSTALLDIR - Subdirectory where the executable or library is to be installed.
INSTALLDIR=$(CAMERA_OUT_BIN)/$(NAME)
ifeq ($(CPULIST),aarch64)
cpulist=aarch64le
else
cpulist=armle-v7
endif
#===== USEFILE - the file containing the usage message for the application.
USEFILE=$(PROJECT_ROOT)/../src/qcarcam_test.use
#===== PINFO - the file containing the packaging information for the application.
define PINFO
PINFO DESCRIPTION=QCarCam test application
endef
#===== EXTRA_SRCVPATH - a space-separated list of directories to search for source files.
EXTRA_SRCVPATH+= \
$(PROJECT_ROOT)/../src \
$(PROJECT_ROOT)/../../test_util/src \
$(PROJECT_ROOT)/../../test_util/src/qnx
#===== EXTRA_INCVPATH - a space-separated list of directories to search for include files.
EXTRA_INCVPATH+= \
$(QCX_ROOT)/utils/inc \
$(QCX_ROOT)/utils/inc/os \
$(PROJECT_ROOT)/../../test_util/inc \
$(AMSS_INC) \
$(MULTIMEDIA_INC) \
$(INSTALL_ROOT_nto)/usr/include/amss \
$(INSTALL_ROOT_nto)/usr/include/amss/core \
$(CAMERA_OUT_HEADERS) \
$(INSTALL_ROOT_nto)/usr/include/amss/multimedia/display \
$(INSTALL_ROOT_nto)/usr/include/amss/core \
$(CAMERA_ROOT)/ext/system
#===== EXTRA_LIBVPATH - a space-separated list of directories to search for library files.
EXTRA_LIBVPATH+= $(INCLUDE_LIB_ASIC_8996) \
$(INSTALL_ROOT_nto)/$(cpulist)/$(INSTALLDIR_GFX_QC_BASE) \
$(INSTALL_ROOT_nto)/$(cpulist)/$(CAMERA_OUT_LIB)
#===== LIBS - a space-separated list of library items to be included in the link.
LIBS+= qcxclient qcxosal camera_metadata bmetrics \
OSAbstraction screen xml2 pmem_client fdt_utils gpio_client slog2
#===== VERSION_TAG_SO - version tag for SONAME. Use it only if you don't like SONAME_VERSION
override VERSION_TAG_SO=
#===== CCFLAGS - add the flags to the C compiler command line.
CCFLAGS += \
-Werror \
-DC2D_DISABLED \
-DQCX_TESTAPP_ENABLE_BMETRICS \
-DUSE_VENDOR_EXT_PARAMS
#===== CXXFLAGS - add the flags to the C++ compiler command line.
CXXFLAGS += $(CCFLAGS)
include $(MKFILES_ROOT)/qmacros.mk
ifndef QNX_INTERNAL
QNX_INTERNAL=$(BSP_ROOT)/.qnx_internal.mk
endif
include $(QNX_INTERNAL)
include $(MKFILES_ROOT)/qtargets.mk
OPTIMIZE_TYPE_g=none
OPTIMIZE_TYPE=$(OPTIMIZE_TYPE_$(filter g, $(VARIANTS)))
define install_extra
$(CP_HOST) -r $(PROJECT_ROOT)/../../test_util/config/*.xml $(INSTALL_DIRECTORY)/
$(CP_HOST) -r $(PROJECT_ROOT)/../../test_util/config/*.xsd $(INSTALL_DIRECTORY)/
$(CP_HOST) -r $(PROJECT_ROOT)/../../test_util/config/camxoverridesettings.txt $(INSTALL_DIRECTORY)/../
endef
在common.mk中,指定了生成的目标文件的名字为qcarcam_test。由于源文件不在build工程目录下,所以设置了EXTRA_SRCVPATH 变量来指定源文件路径。
设置了EXTRA_INCVPATH变量来指定需要搜索的头文件的路径。
设置了EXTRA_LIBVPATH变量指定了库文件搜索的路径。
LIBS 变量描述的是需要链接的库文件。
CCFLAGS是c语言编译标志位。
CXXFLAGS是c++编译标志位。
通过这些规则,就能编译出目标文件,下面编译一下,可以得到如下结果:
在o-le目录下面生成了qcarcam_test 可执行程序。这个结果也可以和第一章的variant level目录名的命名规则所对应,qcarcam_test 是一个小端的可执行程序。
4 高通基线qnx image的构建
先看一下app/qnx_app目录下面的顶层makefile文件:
这边主要关注all伪目标,其依赖于install和image
在install目标下面,可以看到其主要对AMSS 目录和boards目录利用-C 参数进行了构建,
然后执行install或者hinstall ,对应底层的makefile中的install或者hinstall 。qnx文档中解释一般hinstall 用来指代头文件的安装命令,而install用来指代应用程序的安装命令。
而image目标则是对target目录进行构建。
所以总结一下就是在app/qnx_app下面执行make命令,会对boards,AMSS ,target,src目录下面的makefile进行遍历,构建目标。
在target目录下面,会构建qnx IFS。
当make执行到target/hypervisor/host/Makefile中的时候,第一个目标为callit,所以执行
create_variant_images.sh 脚本,该脚本,是构建整个image的关键:
4.1 create_variant_images.sh
该文件在target/hypervisor/host/create_variant_images.sh 目录下,下面简单分析一下该脚本做的工作:
先来看一下LIST 和 LIST1 分别包含什么东西:
在variant_config目录下,包含.cfg和txt文件,它的命名规则如上图所示,cfg是soc命令,txt是soc下面的变体:
所以上面LIST 中是.cfg文件的集合,LIST1是txt文件的集合。
在create_variant_images.sh 脚本里做的事情就是遍历SOC 下面的变体,依次对这些变体调用create_image 构建 image。高通的qnx编译之所以很慢,就是在这边对多个SOC 的变体进行了构建,可以删除不需要的文件,以提高编译速度。构建的核心在create_image中:
SOC_VARIANT中存放的是.txt文件的路径,然后调用create_images.sh进行image构建,参数2为null。
4.2 create_images.sh
create_images.sh这个脚本是整个qnx image制作的核心,是相对比较复杂的,下面一步不解析该脚本大概做了哪些事情。
一开始先调用了set_env函数,以我们编译的lemans_qdrive板子为例,只列出核心部分:
set_env()
{
#qdt anti-rollback version
export rollback_version=1
. ${BSP_ROOT}/tools/build/image-builder.sh
if [ -d "${FILESETS}/override/${flavor}" ] //flavor在create_variant_images.sh脚本中进行赋值,值为 lemans_qdrive ,检查是否存在target/filesets/override/lemans_qdrive目录,如果存在,说明有些文件要使用override/lemans_qdrive下的文件而不是通用文件。
then
export LOCAL_FILESETS=${HYP_HOST}/${OUT_DIR}/${flavor}/filesets
echo ${LOCAL_FILESETS}
echo ${variant_name}
mkdir -p ${LOCAL_FILESETS}
cp -fr ${FILESETS}/launcher_scripts ${LOCAL_FILESETS}
cp -fr ${FILESETS}/secpol ${LOCAL_FILESETS}
cp -fr ${FILESETS}/override/${flavor}/secpol/* ${LOCAL_FILESETS}/secpol //覆盖原来的通用文件
cp -fr ${FILESETS}/override/${flavor}/launcher_scripts/* ${LOCAL_FILESETS}/launcher_scripts //覆盖原来的通用文件
//把target/filesets/launcher_scripts,target/filesets/secpol,target/filesets/override/lemans_qdrive/secpol,target/filesets/override/lemans_qdrive/launcher_scripts 目录下的文件复制到target/hypervisor/host/out_lemans/lemans_qdrive/filesets文件下面,lemans_qdrive 使用该文件夹作为编译源。
else
export LOCAL_FILESETS=${FILESETS}
fi
IFS2_LIST="ifs_coreservices\
ifs_display\
ifs_graphics\
ifs_audio\
ifs_video\
ifs_camera\
ifs_disk"
case $flavor in
lemans_qdrive | lemans_qdrive_iosock)
SECURE_BOOT_QVB=0
SECURE_BOOT_SECPOL=1
SECURE_BOOT_QTD=0
PERF_ENABLE=0
DEBUG_UEFI_REMOVAL=0
HLOS_SEC_DS_ENABLE=1
IFS2_ENABLE=1 //使能IFS2,该IFS2包含下面的一些image
IFS2_LIST="ifs_coreservices\
ifs_display\
ifs_graphics\
ifs_camera\
ifs_video\
ifs_disk"
;;
*)
echo "exiting since flavor $ext_name not recognised"
exit
;;
esac
}
编译qcpe
if [ -f "${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/QCPE_config.xml.tmpl" ]; then
rm -rf ./${OUT_DIR}/QCPE_config_${SOC_NAME}${ext_name}.xml
filepp -imacros $SOC_VARIANT -D__QNX_${version}__ -D__TOOLCHAIN_${qnx_toolchain}__ ${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/QCPE_config.xml.tmpl > ${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/QCPE_config_${SOC_NAME}${ext_name}.xml
fi
cp ${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/QCPE_config_${SOC_NAME}${ext_name}.xml ./${OUT_DIR}/QCPE_config_${SOC_NAME}${ext_name}.xml
python ${BSP_ROOT}/tools/build/qcpe_config_gen.py ${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/QCPE_config_${SOC_NAME}${ext_name}.xml ${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/qcpe_config.c ${SOC_NAME} ${ext_name}
if [ $? -ne 0 ]; then
echo "Couldn't create qcpe configurations"
exit 1;
fi
cp ${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/qcpe_config.c ./${OUT_DIR}/qcpe_config_${SOC_NAME}${ext_name}.c
make -C ${BSP_ROOT}/target/hypervisor/host/qcpe_config install
if [ $? -ne 0 ]; then
echo "Couldn't create qcpe configurations"
exit 1;
fi
qcpe是高通的串行口引擎,当配置io的时候,需要修改该文件,原始文件QCPE_config.xml.tmpl,这边编译比较有意思的是先把该文件生成.c文件,然后再把该.c文件编译成.so文件
# Generates devices names, used for IORT config generation.
if [[ ${SOC_NAME} == "8540" ]] || [[ ${SOC_NAME} == "lemans" ]] ; then
echo "Generating devices names list..."
SMMUMasters_h_path=${BSP_ROOT}/AMSS/security/tz_hlos_comm/qcpe_service/sources/ACv3.1/target/${SOC_NAME}/inc/SMMUMasters.h
devices_names_output_path=${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/devices_names.txt
python ${BSP_ROOT}/tools/build/generate_devices_names.py ${SMMUMasters_h_path} ${devices_names_output_path}
if [ $? -ne 0 ]; then
echo "Couldn't create devices names file"
exit 1;
fi
echo "Successfully generated devices names list"
fi
# Conversion script V2 is currently enabled only for 8540.
# Need to add attribute of device name to each sid in qcpe_config.xml in order to enable it for Lemans and Monaco.
echo "Generating IORT configuration..."
if [[ ${SOC_NAME} == "8540" ]] || [[ ${SOC_NAME} == "lemans" ]] ; then
python ${BSP_ROOT}/tools/build/conv_qcpe_to_iort_v2.py \
--qcpe_config_xml ${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/QCPE_config_${SOC_NAME}${ext_name}.xml \
--common_header ${BSP_ROOT}/target/hypervisor/host/qcpe_config/common/${SOC_NAME}.h \
--qcpe_target_h ${BSP_ROOT}/AMSS/security/tz_hlos_comm/qcpe_service/sources/qcpe/public/amss/core/qcpe/target/${SOC_NAME}/qcpe_target.h \
--devices_names_list ${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/devices_names.txt \
--iort_output_path ${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/iort_config.c
if [ $? -ne 0 ]; then
echo "Couldn't create IORT configurations"
exit 1;
fi
else
python ${BSP_ROOT}/tools/build/conv_qcpe_to_iort.py ${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/QCPE_config_${SOC_NAME}${ext_name}.xml ${BSP_ROOT}/target/hypervisor/host/qcpe_config/common/${SOC_NAME}.h ${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/mapping_config_${SOC_NAME}${mvariant}.txt ${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/iort_config.c
if [ $? -ne 0 ]; then
echo "Couldn't create IORT configurations"
exit 1;
fi
fi
cp ${BSP_ROOT}/target/hypervisor/host/qcpe_config/${SOC_NAME}/iort_config.c ./${OUT_DIR}/iort_config_${SOC_NAME}${ext_name}.c
make -C ${BSP_ROOT}/target/hypervisor/host/qcpe_config install
if [ $? -ne 0 ]; then
echo "Couldn't create IORT configurations"
exit 1;
fi
echo "Successfully generated IORT configuration"
利用之前生成的QCPE_config_lemans_qdrive.xml文件,devices_names.txt文件,qcpe_target.h文件,lemans.h文件,生成iort_config.c文件,并对其进行编译。
#generate_memory_map
if [ $IFS2_ENABLE == 1 ] ; then
build_ifs2
fi
build_ifs2()
{
# build the host component images
for build_file_tmpl in ${IFS2_LIST}; do
build_aifs ${build_file_tmpl} "-qvmhost" "" "${SOC_VARIANT}" "" "${BSP_ROOT}/target/hypervisor/host"
done
calc_size "__HOSTIFS2_RUNTIME_SIZE__" "131072" ${IFS2_LIST}
MKIFS_PATH=$MKIFS_PATH:./${OUT_DIR}
build_aifs ifs_startup "" "" "${SOC_VARIANT}" "" "${BSP_ROOT}/target/hypervisor/host"
build_aifs ifs2 "" "" "${SOC_VARIANT}" "${ext_name}" "${BSP_ROOT}/target/hypervisor/host"
}
构建ifs2分区,先遍历ifs_coreservices\
ifs_display\
ifs_graphics\
ifs_camera\
ifs_video\
ifs_disk
这些子目录,分别为这些子目录构建.img文件。
build_aifs在image-builder.sh 脚本中,分别对ifs_coreservices.build.tmpl,ifs_display.build.tmpl,ifs_graphics.build.tmpl,ifs_camera.build.tmpl,ifs_video.build.tmpl,ifs_disk.build.tmpl,然后分别根据这些文件,打包生成ifs_coreservices.img,fs_display.img,ifs_graphics.img,ifs_camera.img,ifs_video.img,ifs_disk.img, 最后调用ifs2.build.tmpl,对上面构建的img 进行打包,合成ifs2.img文件。
if [ -f "${BSP_ROOT}/target/hypervisor/host/fdt_config/public/amss/memory_host_map-${SOC_NAME}${ext_name}.h" ]; then
memmap_file=${BSP_ROOT}/target/hypervisor/host/fdt_config/public/amss/memory_host_map-${SOC_NAME}${ext_name}.h
else
memmap_file=${BSP_ROOT}/target/hypervisor/host/fdt_config/public/amss/memory_host_map-${SOC_NAME}${mvariant}.h
fi
python ${BSP_ROOT}/tools/build/generate_res_mem.py \
--host_maps ${FILESETS}/dtsi/${SOC_NAME}.h $memmap_file \
--common ${BSP_ROOT}/target/hypervisor/host/fdt_config/public/amss/memory_host_map_common.h \
--soc_variant ${SOC_VARIANT} \
--ifs2_hashes ${HASHFILE}
//把lemans根据ddr_map_list中的name相匹配,只要有name中包含lemans字符,就会生成对应的ddr layout的dts:
简单描述一下这个过程,
如果是lemons的版本,则根据lemans_4gb_2ch,lemans_4gb_4ch,lemans_24gb_4ch,lemans_24gb_6ch,lemans_36gb_6ch的memory
layout,对target/filesets/dtsi/lemans.h和target/hypervisor/host/fdt_config/public/amss/memory_host_map-lemans_qdrive.h 文件进行解析,会对两个.h文件中的内存的地址进行解析,匹配相应的memory layout,对每个不同的ddr layout,生成解析后的.h以及dts文件,如lemans_4gb_2ch,则生成memory_host_map_lemans_4gb_2ch.h和reserved_memory-host_lemans_4gb_2ch.dtsi,最终把这些生成的不同ddr 版本下的dts,合并成一个reserved_memory-host.dtsi,这个dts中会包含这些不同ddr size的memory节点,qnx启动的时候会根据ddr 的信息,自动决定使用哪种memory节点。
#Copy generated files to output directory
cp ./fdt_config/public/amss/out/reserved_memory-host.dtsi ./${OUT_DIR}/reserved_memory-host-${SOC_NAME}${ext_name}.dtsi
cp ./fdt_config/public/amss/out/secpol_range.txt ./${OUT_DIR}/secpol_range${ext_name}.txt
cp ./fdt_config/public/amss/out/range_declaration.txt ./${OUT_DIR}/range_declaration${ext_name}.txt
cp ./fdt_config/public/amss/out/secpol_memphys_subregion.txt ./${OUT_DIR}/secpol_memphys_subregion${ext_name}.txt
#Stage the memory map header
make -C ./fdt_config hinstall
#build_device_tree
if [ -f "${BSP_ROOT}/target/hypervisor/host/fdt_config/fdt.s.tmpl" ]; then
echo "Create fdt.s from fdt.s.tmpl"
rm -f ./fdt_config/fdt.s
filepp -imacros ${SOC_VARIANT} -D__QNX_${version}__ -D__TOOLCHAIN_${qnx_toolchain}__ -imacros ${SOC_CONFIG} ${BSP_ROOT}/target/hypervisor/host/fdt_config/fdt.s.tmpl > ${BSP_ROOT}/target/hypervisor/host/fdt_config/fdt.s
else
echo "There is no fdt.s.tmpl"
fi
#Generate device tree
make -C ./fdt_config install //把filesets/dtsi/lemans.dtsi和amss/out/reserved_memory-host.dtsi合并成一个merged_host.dts,
并生成host.dtb,其中lemans.dtsi是设备的dts,reserved_memory-host.dtsi是memory节点的dts,然后遍历target/hypervisor/host/fdt_config/dtb/overlay/lemans 目录下的dts文件,生成dtbo文件,然后利用fdt.s文件,链接成libmod_fdt_config.a文件,安装到aarch64le/boot/sys目录下,这个文件讲和IFS打包在一起,作为重要的启动文件而存在。
cp ./fdt_config/dtb/out/merged_host.dts ./${OUT_DIR}/merged_host_${SOC_NAME}${ext_name}.dts
if [ $? -ne 0 ]; then
echo "Couldn't create fdt lib"
exit 1;
fi
#Generate and store device tree crc
DT_HASHFILE=${INSTALL_ROOT_nto}/usr/include/amss/dt_crc.h
CRC=$(${BSP_ROOT}/tools/build/compute_dt_crc ./fdt_config/dtb/out/host.dtb)
echo "#define DT_CRC 0x${CRC}" > ${DT_HASHFILE}
make -C ${BSP_ROOT}/target/hypervisor/host/dt_crc install//根据dtb文件生成libmod_dt_crc.a文件,startup程序加载dtb的时候会用来做校验
if [ $? -ne 0 ]; then
echo "Couldn't create dt_crc lib"
exit 1;
fi
//放置在aarch64le/boot/sys目录,和startup程序组装在一起
cp ${BSP_ROOT}/target/hypervisor/host/dt_crc/aarch64/a-le/libmod_dt_crc.a ${INSTALL_ROOT_nto}/aarch64le/boot/sys
上面的代码是构建dtb文件以及dtb的crc,qnx startup启动的时候需要加载这两个文件来进行必要的初始化工作。
if [ $IFS2_ENABLE == 1 ] ; then
if [ -d "${BSP_ROOT}/target/hypervisor/host/startupmgr/${flavor}" ]; then
export STARTUPMGR_LOC=${flavor}
else
export STARTUPMGR_LOC=
fi
make -C ${BSP_ROOT}/target/hypervisor/host/startupmgr install
if [ $? -ne 0 ]; then
echo "Couldn't create startupmgr for host"
exit 1;
fi
fi
把launcher_scripts 目录下的启动程序和hypervisor/host/startupmgr/src/script.c编译成应用程序启动程序startupmgr
if [ ${ext_name} == "_qdrive_vp" ] ; then
make -C ${BSP_ROOT}/target/hypervisor/host/raw_boot_qc-vp clean
make -C ${BSP_ROOT}/target/hypervisor/host/raw_boot_qc-vp install
elif [ ${SOC_NAME} == "lemans" ] ; then
make -C ${BSP_ROOT}/target/hypervisor/host/raw_boot_qc-lemans clean
make -C ${BSP_ROOT}/target/hypervisor/host/raw_boot_qc-lemans install
elif [ ${SOC_NAME} == "monaco" ] ; then
make -C ${BSP_ROOT}/target/hypervisor/host/raw_boot_qc-monaco clean
make -C ${BSP_ROOT}/target/hypervisor/host/raw_boot_qc-monaco install
else
make -C ${BSP_ROOT}/target/hypervisor/host/raw_boot_qc clean
make -C ${BSP_ROOT}/target/hypervisor/host/raw_boot_qc install
fi
把bootfile.S 程序利用链接脚本bootfile.lds链接成raw-qc.boot程序,起始地址为0xA0000000,安装到aarch64le/boot/sys目录下。raw-qc.boot是qnx启动时执行的第一段代码,就是IPL 程序,高通的IPL程序中做的事情很简单,找到startup程序的开始位置,直接跳转到startup程序那边去执行。
ext_name_temp="${ext_name}_counter"
generate_filepp_imacro "boot_reset_counter" ${ext_name}
build_aifs mifs "-qvmhost" "" "mod_soc_variant.cfg" "${ext_name}" "${BSP_ROOT}/target/hypervisor/host" "_counter"
generate_compressed_boot_image mifs "${ext_name_temp}"
把一些宏定义写入到mod_soc_variant.cfg临时文件:
elif [ "boot_reset_counter" = "$1" ] ; then
cat ${SOC_VARIANT} >> mod_soc_variant.cfg
echo "#define IMAGE_TYPE raw-qc" >> mod_soc_variant.cfg
echo "#define LOAD_ADDRESS ${boot_load_addr}" >> mod_soc_variant.cfg
echo "#define QHEE_OPTS" >> mod_soc_variant.cfg
echo "#define RESET_CNT_OPTIONS -b 3" >> mod_soc_variant.cfg
在编译的时候利用这些宏定义的值替换原来的文件。
buidl_aifs是制作IFS,利用buildfile文件target/hypervisor/host/build_files/mifs.build.tmpl,制作成IFS 镜像。
先把target/hypervisor/host/build_files/mifs.build.tmpl进行合展开,生成target/hypervisor/host/out_lemans/mifs_qdrive_counter.build文件,最终通过mifs命令,生成./target/hypervisor/host/out_lemans/mifs_qdrive_counter.img文件,然后压缩,打包成./target/hypervisor/host/out_lemans/boot_qdrive_counter.img
#temporarily enabling this till the *secure* image are stable
SECURE_BOOT_SECPOL=1 SECURE_BOOT_QTD=1 build_aifs mifs "-qvmhost" "" "mod_soc_variant.cfg" "${ext_name}" "${BSP_ROOT}/target/hypervisor/host" "_counter.secure"
generate_compressed_boot_image mifs "${ext_name_temp}.secure"
和之前的操作一样,生成enable secure boot的IFS image target/hypervisor/host/out_lemans/boot_qdrive_counter.secure.img
generate_filepp_imacro "boot" ${ext_name}
# Enable QNX Security Policy by default for qdrive & _lv images.
build_aifs mifs "-qvmhost" "" "mod_soc_variant.cfg" "${ext_name}" "${BSP_ROOT}/target/hypervisor/host"
generate_compressed_boot_image mifs "${ext_name}"
enable QNX Security Policy 生成target/hypervisor/host/out_lemans/boot_qdrive.img
PERF_ENABLE=1 build_aifs mifs "-qvmhost" "" "mod_soc_variant.cfg" "${ext_name}" "${BSP_ROOT}/target/hypervisor/host" ".perf"
generate_compressed_boot_image mifs "${ext_name}.perf"
enable perf,生成target/hypervisor/host/out_lemans/boot_qdrive.iperf.img
DEBUG_UEFI_REMOVAL=0 SECURE_BOOT_QVB=0 SECURE_BOOT_SECPOL=1 SECURE_BOOT_QTD=1 PERF_ENABLE=1 build_aifs mifs "-qvmhost" "" "mod_soc_variant.cfg" "${ext_name}" "${BSP_ROOT}/target/hypervisor/host" ".perf.secure"
generate_compressed_boot_image mifs "${ext_name}.perf.secure"
enable perf和secure,生成target/hypervisor/host/out_lemans/boot_qdrive.iperf.secure.img
# Enable QNX Trusted Disk (QTD) for _lv and _qdrive images.
if [[ ${ext_name} == _lv* ]] || [[ ${ext_name} == _la* ]] || [[ ${ext_name} == _qdrive ]] || [[ ${ext_name} == _qdrive_iosock ]] || [[ ${ext_name} == _srv1m ]] || [[ ${ext_name} == _srv1m_iosock ]] || [[ ${ext_name} == _slt ]]; then
DEBUG_UEFI_REMOVAL=0 SECURE_BOOT_QVB=0 SECURE_BOOT_SECPOL=1 SECURE_BOOT_QTD=1 PERF_ENABLE=0 build_aifs mifs "-qvmhost" "" "mod_soc_variant.cfg" "${ext_name}" "${BSP_ROOT}/target/hypervisor/host" ".secure"
else
DEBUG_UEFI_REMOVAL=0 SECURE_BOOT_QVB=0 SECURE_BOOT_SECPOL=1 SECURE_BOOT_QTD=0 PERF_ENABLE=0 build_aifs mifs "-qvmhost" "" "mod_soc_variant.cfg" "${ext_name}" "${BSP_ROOT}/target/hypervisor/host" ".secure"
fi
generate_compressed_boot_image mifs "${ext_name}.secure"
生成target/hypervisor/host/out_lemans/boot_qdrive.secure.img
#disabling until mifs is not in boot chain
generate_filepp_imacro "hyp_reset_counter" ${ext_name}
build_mifs "_boot_counter"
generate_filepp_imacro "hyp" ${ext_name}
build_mifs
和上面分析类似,生成hypvisor相关的IFS image。上面生成了多种IFS boot.img,用户可以根据自己的需求,烧录不同的boot.img
if [[ ${SOC_NAME} == "lemans" ]] ; then
${SECTOOLS_V2_ROOT} secure-image ./abl-image-lemans/abl.elf --outfile ./abl-image-lemans/signed/abl.elf --image-id ABL --security-profile ${BSP_ROOT}/target/sectools/security/lemans_tz_security_profile.xml --sign --signing-mode TEST
${SECTOOLS_V2_ROOT} secure-image ./abl-image-lemans/abl_fastboot.elf --outfile ./abl-image-lemans/signed/abl_fastboot.elf --image-id ABL --security-profile ${BSP_ROOT}/target/sectools/security/lemans_tz_security_profile.xml --sign --signing-mode TEST
else
#Sign the abl.elf / abl_fastboot.elf with sectools v1 version
python ${SECTOOLS_ROOT}/sectools.py secimage -i ./abl-image/abl.elf -g abl -c ${SECTOOLS_ROOT}/config/integration/secimage_eccv3.xml --cfg_soc_hw_version 0x60140000 --cfg_soc_vers "0x6014" --cfg_in_use_soc_hw_version 1 -z -s -o ./abl-image/signed
python ${SECTOOLS_ROOT}/sectools.py secimage -i ./abl-image/abl_fastboot.elf -g abl -c ${SECTOOLS_ROOT}/config/integration/secimage_eccv3.xml --cfg_soc_hw_version 0x60140000 --cfg_soc_vers "0x6014" --cfg_in_use_soc_hw_version 1 -z -s -o ./abl-image/signed
fi
生成带签名的abl.elf 和abl_fastboot.elf
echo "Creating system partition"
img_name=system
if [ -f "${build_file_path}/build_files/${img_name}_${SOC_NAME}${variant}.build.tmpl" ]; then
build_file_tmpl=${img_name}_${SOC_NAME}${variant}.build.tmpl
sys_ext_name=
elif [ -f "${build_file_path}/build_files/${img_name}_${SOC_NAME}${mvariant}.build.tmpl" ]; then
build_file_tmpl=${img_name}_${SOC_NAME}${mvariant}.build.tmpl
sys_ext_name=
elif [ -f "${build_file_path}/build_files/${img_name}${variant}.build.tmpl" ]; then
build_file_tmpl=${img_name}${variant}.build.tmpl
sys_ext_name=
elif [ -f "${build_file_path}/build_files/${img_name}${mvariant}.build.tmpl" ]; then
build_file_tmpl=${img_name}${mvariant}.build.tmpl
sys_ext_name=
else
build_file_tmpl=${img_name}.build.tmpl
sys_ext_name=$ext_name
fi
设置 system buildfile文件为target/hypervisor/host/build_files/system.build.tmpl
img_name=startup
if [ -f "${BSP_ROOT}/target/hypervisor/host/${img_name}_${SOC_NAME}${variant}.qvmhost.tmpl" ]; then
startup_file=${img_name}_${SOC_NAME}${variant}
elif [ -f "${BSP_ROOT}/target/hypervisor/host/${img_name}_${SOC_NAME}${mvariant}.qvmhost.tmpl" ]; then
startup_file=${img_name}_${SOC_NAME}${mvariant}
elif [ -f "${BSP_ROOT}/target/hypervisor/host/${img_name}${variant}.qvmhost.tmpl" ]; then
startup_file=${img_name}${variant}
elif [ -f "${BSP_ROOT}/target/hypervisor/host/${img_name}${mvariant}.qvmhost.tmpl" ]; then
startup_file=${img_name}${mvariant}
else
startup_file=${img_name}
fi
设置 startup_file为target/hypervisor/host/startup.qvmhost.tmpl
filepp -I${FILESETS} -imacros ${FILESETS}/secpol/system_uids.txt -imacros ${FILESETS}/secpol/system_gids.txt -imacros ${SOC_CONFIG} ${SOC_VARIANT} -I${FILESETS} -D__TOOLCHAIN_${qnx_toolchain}__ -D__VARIANT_hyp__="qvmhost" ${BSP_ROOT}/target/hypervisor/host/$startup_file.qvmhost.tmpl > ./${OUT_DIR}/$startup_file-qvmhost.sh
cp -f ./${OUT_DIR}/$startup_file-qvmhost.sh ./${OUT_DIR}/startup-qvmhost.sh
对startup.qvmhost.tmpl 进行处理,生成startup-qvmhost.sh,该文件是个启动脚本,好多启动优先级不那么高的应用程序都在这里面启动。该文件在qnx系统中被链接成 startup.sh脚本
echo "Creating system partition"
if [ "noqtd" != "$img" ] ; then
echo "Creating optimal system image with room for qtd header"
build_aq6fs $build_file_tmpl "-qvmhost" 0 "system.img" "${SOC_VARIANT}" "${sys_ext_name}"
echo "Creating qtd image using the system image generated above"
build_qtdfs $build_file_tmpl "-qvmhost" 3221225472 "system.img.qtd" "${SOC_VARIANT}" "${sys_ext_name}" "${rollback_version}"
fi
echo "Create plain system image"
build_aq6fs $build_file_tmpl "-qvmhost" 3221225472 "system.img" "${SOC_VARIANT}" "${sys_ext_name}"
利用system.build.tmpl 生成 system_qdrive.img文件
至此qnx 这边生成了三个重要的img,分别是IFS,IFS2, 和 system fs。
echo "Create persist image"
build_aq6fs persist.build.tmpl "-qvmhost" 33554432 "persist.img" "${SOC_VARIANT}" ""
#Persist.img.sparse couldn’t be flashed through PCAT/QFIL and thus when we try to use persist.img to be flashed, its conflicting with the persisting.img in LA build.
echo "Creating a copy of persist.img.sparse/persist.img and renaming to persist_qnx.img.sparse/persist_qnx.img "
cp -r ./${OUT_DIR}/persist.img ./${OUT_DIR}/persist_qnx.img
cp -r ./${OUT_DIR}/persist.img.sparse ./${OUT_DIR}/persist_qnx.img.sparse
利用target/hypervisor/host/build_files/persist.build.tmpl 生成persist_qnx.img.sparse,至此,qnx启动所需要的必要文件,都已经生成。
5 高通基线qnx 启动流程
以buildfile文件,来简单了解一下启动过程中大概调用了哪些脚本和程序。
在上一章结中,经过处理以后生成的buildfile文件为mifs_qdrive.build:
该文件最开始的位置为raw-qc.boot,这是qnx IPL 程序,高通只是利用其做了简单的跳转,跳转搭到startup-sdx-perf。 真正复杂的工作是在startup-sdx-perf中做的,但是该部分的代码高通并没有开源,所以无从得知具体做了什么工作。在module 中分别设置了 fdt_config 和 dt_crc,这两个程序分别是dts和dts的crc校验程序,这两个程序会和startup-sdx-perf 打包到一起,startup-sdx-perf启动以后会利用dts 做相应的一些硬件的初始化工作。
在startup-sdx-perf 做完系统的必要的初始化工作以后,会跳转到 buildfile的script部分继续执行。
script 分为init_script,lemans_qdrive_script 和end_script 三部分。init_script 用来处理最重要的IFS 部分加载以后,需要处理的一些工作,优先级最高。lemans_qdrive_script 用来处理加载IFS2以后得一些工作,在lemans_qdrive_script 中,主要利用startupmgr程序进行处理:
简单看一下startupmgr程序做了哪些工作。根据之前的程序编译可知,startupmgr程序由script.c和launch_scripts目录下面的文件编译而来。
static const struct launcher_script scripts [] = {
/*host IFSs*/
{
.ifsname = "ifs_coreservices.img",
.actions = coreservices,
.sha256sum = __ifs_coreservices__hash__,
},
{
.ifsname = "ifs_audio.img",
.actions = audioservices,
.sha256sum = __ifs_audio__hash__,
.sleep_pre_dma = 0,
.sleep_pre_sha = 0,
.type = "IFS",
},
{
.ifsname = "ifs_display.img",
.actions = dispservices,
.sha256sum = __ifs_display__hash__,
.sleep_pre_dma = 0,
.sleep_pre_sha = 0,
.type = "IFS",
},
{
.ifsname = "ifs_graphics.img",
.actions = graphicsservices,
.sha256sum = __ifs_graphics__hash__,
.sleep_pre_dma = 0,
.sleep_pre_sha = 0,
.type = "IFS",
},
{
.ifsname = "ifs_camera.img",
.actions = cameraservices,
.sha256sum = __ifs_camera__hash__,
.sleep_pre_dma = 0,
.sleep_pre_sha = 0,
.type = "IFS",
},
{
.ifsname = "ifs_video.img",
.actions = videoservices,
.sha256sum = __ifs_video__hash__,
.sleep_pre_dma = 0,
.sleep_pre_sha = 0,
.type = "IFS",
},
{
.ifsname = "cdt.bin",
.lun_num_a = 3,
.lun_num_b = 3,
.lun_indicator = 0,
.type = "RAW",
.partition_name = "cdt"
},
{
.ifsname = "ifs_disk.img" ,
.actions = diskservices ,
.sha256sum = __ifs_disk__hash__ ,
.type = "IFS",
},
//Delimt with NULL,
{ NULL , NULL },
};
startupmgr 加载了scripts ,并依次执行里面的actions ,所以总结一下startupmgr 挂载了IFS2里面各个子系统,并且执行各个子系统里面的service。完成系统启动的第二阶段的程序的初始化。
在startupmgr 的最后阶段,会加载diskservices,在diskservices 的最后阶段,会加载
diskservices的最后阶段,会执行next_startup:
最终在startup.sh 中,会进行最后阶段的应用程序初始化工作。