设置tf-a u-boot 和Linux的开发和调试环境

在学习Linux的过程中,调试必不可少。下面的步骤就是一步一步如何使用Arm 的FVP平台来设置Linux的开发和调试环境。 如下所有的步骤最终都汇聚在我的github boot-tf-a-u-boot-linux ,如果仅仅是使用,不需要知道它的具体细节的,建议直接使用github仓库提供的Makefile。

开发环境准备

  1. 在Ububtu上安装必要的包
sudo apt-get install make autoconf build-essential git wget fuseext2 tmux
  1. 下载交叉编译工具

Arm gcc 可以在 arm-gnu-toolchain-downloads, 这里可以根据自己的系统来选择下载对应的gcc.

一般我们使用的是x86_64 Linux host, 所以下载 AArch64 GNU/Linux target for x86_64 Linux host

可以在Ubuntu 终端中,使用如下命令:

cd $(workspace)/tools
wget https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x86_64-aarch64-none-linux-gnu.tar.xz
tar -xvf arm-gnu-toolchain-13.2.rel1-x86_64-aarch64-none-linux-gnu.tar.xz
  1. 下载Arm Fixed Virtual Platform (FVP)

Arm Base FVP 平台现在是不需要License 就可以运行了,最新的FVP平台可以在 Arm的网站上 下载 , 这里使用 Armv-A Base RevC AEM FVP (x86 Linux)

在Ubuntu 终端中,使用如下命令:

cd $(workspace)/tools
wget https://armkeil.blob.core.windows.net/developer/Files/downloads/ecosystem-models/FM_11_25/FVP_Base_RevC-2xAEMvA_11.25_15_Linux64.tgz
tar -zxvf FVP_Base_RevC-2xAEMvA_11.25_15_Linux64.tgz
  1. 下载Arm DS

如果需要使用Arm DS来调试Linux的话,这就需要下载Arm DS,可以在 arm 的网站 下载 . 也可以申请试用的license。

下载代码和rootfs

  1. 下载 TF-A,u-boot和Linux

要把linux boot起来,这里需要使用到TF-A,u-boot和Linux,这里使用的都是最新的代码,如果在使用过程中发现问题,可能需要使用某个比较稳定的版本。

这里使用如下命令来下载代码:

cd $(workspace)/src
git clone https://git.denx.de/u-boot
git clone https://git.trustedfirmware.org/TF-A/trusted-firmware-a tf-a
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
  1. 下载rootfs

制作一个rootfs不难,这里使用准备好的一个rootfs,里面已经有一些必要的包,包括vi, gcc 和gdb,如果需要调试应用程序,这里rootfs也是可以调试的。

cd $(workspace)/rootfs
wget https://github.com/geesun/boot-tf-a-u-boot-linux/raw/master/rootfs/rootfs.tar.bz2

如果我们想往这个rootfs里面加入一些别的文件,在下面的步骤里面会详细说明如何做。

编译代码

  1. 编译u-boot代码

在编译u-boot代码之前,需要根据Linux kernel和rootfs的信息来配置u-boot的BOOTARGS和BOOTCOMMAND。 这里有几个比较关键的参数:

  • BOOTARGS中的root参数

这里使用了 root=/dev/vda1, 这是因为后面做rootfs的时候,使用了FVP的virtioblockdevice这个参数:

-C bp.virtioblockdevice.image_path=rootfs/grub-busybox.img

而制作rootfs 的时候,rootfs会放到第一个分区,所以就是vda1。

  • BOOTCOMMAND中kernel Image 和device tree的地址

这里使用了 booti 0x80080000 - 0x83000000,这个是因为在启动FVP的时候,有如下参数:

--data cluster0.cpu0=linux/arch/arm64/boot/Image@0x80080000
--data cluster0.cpu0=linux/arch/arm64/boot/dts/arm/fvp-base-revc.dtb@0x83000000

根据上面的描述,使用如下命令来配置u-boot:

cd $(workspace)/src/u-boot
echo "CONFIG_BOOTARGS=\"console=ttyAMA0 earlycon=pl011,0x1c090000 root=/dev/vda1 rw ip=dhcp debug user_debug=31 loglevel=9\"" > fvp.cfg
echo "CONFIG_BOOTCOMMAND=\"booti 0x80080000 - 0x83000000\"" >> fvp.cfg
export ARCH=aarch64 ;
export CROSS_COMPILE=$(CROSS_COMPILE) ;
make vexpress_aemv8a_semi_config;
scripts/kconfig/merge_config.sh -m -O ./ .config fvp.cfg;

这里的CROSS_COMPILE 可以根据前面下载的gcc来决定,这里可以把他设置成:

export CROSS_COMPILE=$(workspace)/tools/arm-gnu-toolchain-13.2.Rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-

配置完成之后,可以打开u-boot目录下的.config 来确保CONFIG_BOOTARGS 和 CONFIG_BOOTCOMMAND已经设置成期望的值。

CONFIG_BOOTARGS="console=ttyAMA0 earlycon=pl011,0x1c090000 root=/dev/vda1 rw ip=dhcp debug user_debug=31 loglevel=9"
CONFIG_BOOTCOMMAND="booti 0x80080000 - 0x83000000"

接下来使用如下命令来编译u-boot:

cd $(workspace)/src/u-boot
export ARCH=aarch64 ;
export CROSS_COMPILE=$(CROSS_COMPILE) ;
make

这一步做完,最终生成 src/u-boot/u-boot.bin.

  1. 编译tf-a代码

编译在tf-a的过程中,u-boot是作为tf-a的BL33的image,所以需要先编译前面的u-boot后,才能编译tf-a,使用如下命令:

export CROSS_COMPILE=$(CROSS_COMPILE)
cd $(workspace)/src/tf-a
make PLAT=fvp DEBUG=1 BL33=$(workspace)/src/u-boot/u-boot.bin all fip V=1

这里面仅仅是使用了tf-a的默认配置,如TTBR等feature 都有没有使能。 如果需要使能更多feature,可以根据自己的需求添加。

从上面的命令可以看出, u-boot 是被打包到fib.bin里面,所以如果更改了u-boot,必须重新运行tf-a的编译过程,把u-boot重新打包到fib.bin里面。

  1. 编译Linux kernel代码

编译Linux kernel,这里使用kernel中的默认配置,即defconfig。即使用如下命令来配置kernel:

cd $(workspace)/src/linux
make -C $(SRC_DIR)/linux ARCH=arm64 defconfig CROSS_COMPILE=$(CROSS_COMPILE)

如果想要对kernel 进行额外的配置,使用如下命令:

cd $(workspace)/src/linux
make -C $(SRC_DIR)/linux ARCH=arm64 menuconfig CROSS_COMPILE=$(CROSS_COMPILE)

配置完成之后,如下命令可以用来进行编译:

cd $(workspace)/src/linux
make -C $(SRC_DIR)/linux ARCH=arm64 -j 24 Image CROSS_COMPILE=$(CROSS_COMPILE) Image dtbs

制作rootfs

这里不想讲如何从头开始制作一个rootfs,其实用buildroot,busybox或者yocto制作出的rootfs是可以通用的,这里使用已经打包好的一个rootfs的tar包来重制rootfs。

这里主要的目的是要明白如何在已有tar基础上,如何添加一些文件到想要的rootfs里面去。

  1. 把rootfs解包

在准备阶段,已经在rootfs目录下载了rootfs.tar.bz2,这一步先把它解压一下:

mkdir -p $(workspace)/src/rootfs/tmp
cd $(workspace)/src/rootfs/tmp
tar -jxvf ../rootfs.tar.bz2
  1. 修改rootfs

在目录 $(workspace)/src/rootfs/tmp/rootfs ,可以根据自己的需求来增加或者删减文件。

这一步不是必须的,是根据需求来决定的,如果没有改动需求,这一步可以跳过。

  1. 生成rootfs的partition文件
cd $(workspace)/src/rootfs/tmp

export BLOCK_SIZE=512
export SEC_PER_MB=$((1024*2))
export EXT3_SIZE_MB=512
export PART_START=$((1*SEC_PER_MB))
export EXT3_SIZE=$((EXT3_SIZE_MB*SEC_PER_MB))
dd if=/dev/zero of=ext3_part bs=$BLOCK_SIZE count=$EXT3_SIZE
mkdir -p mnt
mkfs.ext3 -F ext3_part
fuse-ext2 ext3_part mnt -o rw+
cp -rf rootfs/* mnt/
sync
fusermount -u mnt
rm -rf mnt

这里rootfs的最大大小是512M,如果需要调整大小,可以调整EXT3_SIZE_MB=512的值。

完成这一步,就生成了一个ext3的rootfs partition文件ext3_part,下一步会把这个分区文件放在磁盘映像文件的第一个分区。

  1. 使用gdisk生成rootfs的磁盘映像文件
cd $(workspace)/src/rootfs/tmp

export BLOCK_SIZE=512
export SEC_PER_MB=$((1024*2))
export EXT3_SIZE_MB=512
export PART_START=$((1*SEC_PER_MB))
export EXT3_SIZE=$((EXT3_SIZE_MB*SEC_PER_MB))
export IMG_BB=../rootfs.img
dd if=/dev/zero of=part_table bs=$BLOCK_SIZE count=$PART_START

cat part_table > $IMG_BB
cat ext3_part >> $IMG_BB
cat part_table >> $IMG_BB
(echo n; echo 1; echo $PART_START; echo +$((EXT3_SIZE)); echo 8300; echo w; echo y) | gdisk $IMG_BB

这里就完成了把上一步生成的ext3_part放在$(workspace)/src/rootfs/rootfs.img 的第一个分区。

文件$(workspace)/src/rootfs/rootfs.img就是最终的要传给FVP的rootfs。

在FVP上运行Linux

在准备FVP那一节,FVP已经被解压到:

$(workspace)/tools/Base_RevC_AEMvA_pkg/models/Linux64_GCC-9.3/FVP_Base_RevC-2xAEMvA

确保前面几节已经准备了如下的images:

$(workspace)/src/tf-a/build/fvp/debug/bl1.bin
$(workspace)/src/tf-a/build/fvp/debug/fip.bin
$(workspace)/src/linux/arch/arm64/boot/Image
$(workspace)/src/linux/arch/arm64/boot/dts/arm/fvp-base-revc.dtb
$(workspace)/rootfs/rootfs.img

接下来就可以使用下面的命令来运行Linux:

cd $(workspace)
tools/Base_RevC_AEMvA_pkg/models/Linux64_GCC-9.3/FVP_Base_RevC-2xAEMvA \
-C cluster0.NUM_CORES=4 -C cluster1.NUM_CORES=4 \
-C cluster0.has_arm_v8-3=1 -C cluster1.has_arm_v8-3=1 \
-C cluster0.has_arm_v8-5=1 -C cluster1.has_arm_v8-5=1 \
-C cluster0.has_branch_target_exception=1 -C cluster1.has_branch_target_exception=1 \
-C cache_state_modelled=0 \
-C pctl.startup=0.0.0.0 \
-C bp.secure_memory=1   \
-C bp.ve_sysregs.exit_on_shutdown=1 \
-C bp.secureflashloader.fname=$(workspace)/src/tf-a/build/fvp/debug/bl1.bin \
-C bp.flashloader0.fname=$(workspace)/src/tf-a/build/fvp/debug/fip.bin \
--data cluster0.cpu0=$(workspace)/src/linux/arch/arm64/boot/Image@0x80080000  \
--data cluster0.cpu0=$(workspace)/src/linux/arch/arm64/boot/dts/arm/fvp-base-revc.dtb@0x83000000  \
-C bp.ve_sysregs.mmbSiteDefault=0    \
-C bp.virtioblockdevice.image_path=$(workspace)/rootfs/rootfs.img

如果是在ssh 的terminal里面运行FVP,因为没有图形界面,所以建议使用tmux 在增加如下参数运行FVP:

-C bp.terminal_3.terminal_command="tmux split-window -d telnet localhost %port" \
-C bp.terminal_0.terminal_command="tmux split-window -h telnet localhost %port" \

在Arm DS上调试Linux

在开始使用armdbg命令调试Linux之前,需要先去下载Arm DS和申请试用license。 这个过程这里就不赘述了。具体可以 参考 .

在安装好Arm DS之后,可以在图形界面来import base model 到Arm DS。 可以参考 Arm 网站

准备一个Arm DS的脚本ap.ds,来自动加载符号表,如下:

$ cat ap.ds

add-symbol-file "src/tf-a/build/fvp/debug/bl1/bl1.elf" EL3:0
add-symbol-file "src/tf-a/build/fvp/debug/bl2/bl2.elf" EL1S:0
add-symbol-file "src/tf-a/build/fvp/debug/bl31/bl31.elf" EL3:0

add-symbol-file "src/linux/vmlinux" EL2N:0x800000200000
add-symbol-file "src/linux/vmlinux" EL1N:0x800000200000
add-symbol-file "src/linux/vmlinux" EL1N:0
add-symbol-file "src/linux/vmlinux" EL2N:0

break bl31_main
break primary_entry
c

接下来就可以使用如下命令开始调试:

/opt/arm/developmentstudio_platinum-0.a/bin/armdbg \
        --cdb-entry="Imported::FVP_Base_RevC_2xAEMvA::Bare Metal Debug::Bare Metal Debug::ARM_AEM-A_MPx4 SMP Cluster 0" \
        --cdb-root ~/developmentstudio-workspace/RevC \
        -cdb-entry-param model_params=" \
        -C cluster0.NUM_CORES=4 -C cluster1.NUM_CORES=4 \
        -C cluster0.has_arm_v8-3=1 -C cluster1.has_arm_v8-3=1 \
        -C cluster0.has_arm_v8-5=1 -C cluster1.has_arm_v8-5=1 \
        -C cluster0.has_branch_target_exception=1 -C cluster1.has_branch_target_exception=1 \
        -C cache_state_modelled=0 \
        -C pctl.startup=0.0.0.0 \
        -C bp.secure_memory=1   \
        -C bp.ve_sysregs.exit_on_shutdown=1 \
        -C bp.secureflashloader.fname=$(workspace)/src/tf-a/build/fvp/debug/bl1.bin \
        -C bp.flashloader0.fname=$(workspace)/src/tf-a/build/fvp/debug/fip.bin \
        --data cluster0.cpu0=$(workspace)/src/linux/arch/arm64/boot/Image@0x80080000  \
        --data cluster0.cpu0=$(workspace)/src/linux/arch/arm64/boot/dts/arm/fvp-base-revc.dtb@0x83000000  \
        -C bp.ve_sysregs.mmbSiteDefault=0    \
        -C bp.virtioblockdevice.image_path=$(workspace)/rootfs/rootfs.img \
        -C bp.terminal_3.terminal_command=\"tmux split-window -d telnet localhost %port\" \
        -C bp.terminal_0.terminal_command=\"tmux split-window -h telnet localhost %port\" \
        " -s ap.ds --interactive

如果使用的是tmux,可以看到如下调试界面,armdbg的命令是跟gdb差不多:

EL3:0x020000000-0x02C1FFFFF | SP:0x020000000-0x02C1FFFFF | Device-nGnRE | RW | False | False | False      │INFO:    Entry point address = 0x4003000
EL3:0x02C200000-0x02EFFFFFF | <unmapped>                 |              |    |       |       |            │INFO:    SPSR = 0x3cd
EL3:0x02F000000-0x02F1FFFFF | SP:0x02F000000-0x02F1FFFFF | Device-nGnRE | RW | False | False | False      │INFO:    BL31 FCONF: FW_CONFIG address = 4001010
EL3:0x02F200000-0x0FFDFFFFF | <unmapped>                 |              |    |       |       |            │INFO:    FCONF: Reading FW_CONFIG firmware configuration file from: 0x4001010
EL3:0x0FFE00000-0x0FFFFFFFF | SP:0x0FFE00000-0x0FFFFFFFF | Normal       | RW | True  | True  | False      │INFO:    FCONF: Reading firmware configuration information for: dyn_cfg
EL3:0x100000000-0xFFFFFFFFF | <unmapped>                 |              |    |       |       |            │INFO:    FCONF: Reading HW_CONFIG firmware configuration file from: 0x7f00000
>c                                                                                                        │INFO:    FCONF: Reading firmware configuration information for: dram_layout
ERROR(?): [model] xterm: Xt error: Can't open display:                                                    │INFO:    FCONF: Reading firmware configuration information for: cpu_timer
ERROR(?): [model] xterm: DISPLAY is not set                                                               │INFO:    FCONF: Reading firmware configuration information for: uart_config
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0                                 │INFO:    FCONF: Reading firmware configuration information for: topology
On core ARM_AEM-A_MP_0 (ID 0)                                                                             │INFO:    FCONF: Reading firmware configuration information for: gicv3_config
In primary_entry (no debug info)                                                                          │NOTICE:  BL31: v2.11.0(debug):v2.11.0-2-g3e8f9fd87
EL2N:0x0000000081E200E0   BL       record_mmu_state ; 0x81E39CD8                                          │NOTICE:  BL31: Built : 13:24:53, May 29 2024
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0                                 │INFO:    GICv3 with legacy support detected.
On core ARM_AEM-A_MP_0 (ID 0)                                                                             │INFO:    ARM GICv3 driver initialized in EL3
EL2N:0x0000000081E200E0   BL       record_mmu_state ; 0x81E39CD8                                          │INFO:    Maximum SPI INTID supported: 255
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0                                 │INFO:    BL31: Initializing runtime services
On core ARM_AEM-A_MP_0 (ID 0)                                                                             │INFO:    BL31: Preparing for EL3 exit to normal world
EL2N:0x0000000081E200E0   BL       record_mmu_state ; 0x81E39CD8                                          │INFO:    Entry point address = 0x88000000
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0                                 │INFO:    SPSR = 0x3c9
On core ARM_AEM-A_MP_0 (ID 0)                                                                             │
EL2N:0x0000000081E200E0   BL       record_mmu_state ; 0x81E39CD8                                          │
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0                                 │U-Boot 2024.07-rc3-00013-g46ff00bea5 (May 29 2024 - 09:14:54 +0800) vexpress_aemv8a
On core ARM_AEM-A_MP_0 (ID 0)                                                                             │
EL2N:0x0000000081E200E0   BL       record_mmu_state ; 0x81E39CD8                                          │Model: FVP Base
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0                                 │DRAM:  2 GiB (effective 4 GiB)
On core ARM_AEM-A_MP_0 (ID 0)                                                                             │Core:  26 devices, 12 uclasses, devicetree: board
EL2N:0x0000000081E200E0   BL       record_mmu_state ; 0x81E39CD8                                          │Flash: 64 MiB
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0                                 │Loading Environment from Flash... *** Warning - bad CRC, using default environment
On core ARM_AEM-A_MP_0 (ID 0)                                                                             │
EL2N:0x0000000081E200E0   BL       record_mmu_state ; 0x81E39CD8                                          │In:    serial@90000
>p/x $x0                                                                                                  │Out:   serial@90000
$3 = 0xFD6BE000                                                                                           │Err:   serial@90000
>disass                                                                                                   │Net:   No ethernet found.
primary_entry:                                                                                            │
EL2N:0x0000000081E200E0 : BL       record_mmu_state ; 0x81E39CD8                                          │Hit any key to stop autoboot:  0
EL2N:0x0000000081E200E4 : BL       preserve_boot_args ; 0x81E39D2C                                        │Moving Image from 0x80080000 to 0x80200000, end=82db0000
EL2N:0x0000000081E200E8 : ADRP     x1,{pc}+0xf86000 ; 0x82DA60E8                                          │## Flattened Device Tree blob at 83000000
EL2N:0x0000000081E200EC : ADD      sp,x1,#0                                                                  Booting using the fdt blob at 0x83000000
EL2N:0x0000000081E200F0 : MOV      x29,xzr                                                                │Working FDT set to 83000000
EL2N:0x0000000081E200F4 : ADRP     x0,{pc}+0x110000 ; 0x81F300F4                                             Loading Device Tree to 00000000fd6be000, end 00000000fd6c3d78 ... OK
EL2N:0x0000000081E200F8 : MOV      x1,xzr                                                                 │Working FDT set to fd6be000
EL2N:0x0000000081E200FC : BL       __pi_create_init_idmap ; 0x81E37518                                    │
EL2N:0x0000000081E20100 : CBNZ     x19,primary_entry+64 ; 0x81E20120                                      │Starting kernel ...
EL2N:0x0000000081E20104 : DMB      SY                                                                     

Comments !