QEMU AARCH64 "virt" 机器 SMP CPU 开始于 "running" 与 "halted" 状态

QEMU AARCH64 "virt" Machine SMP CPUs Starting in "running" vs. "halted" State

我正在研究裸机。没有 Linux、库等。我在 ASM 中编写处理器启动代码并跳转到我编译的 C 代码。

我的命令行是:

% qemu-system-aarch64 \
   -s -S \
   -machine virt,secure=on,virtualization=on \
   -cpu cortex-a53 \
   -d int \
   -m 512M \
   -smp 4 \
   -display none \
   -nographic \
   -semihosting \
   -serial mon:stdio \
   -kernel my_file.elf \
   -device loader,addr=0x40004000,cpu-num=0 \
   -device loader,addr=0x40004000,cpu-num=1 \
   -device loader,addr=0x40004000,cpu-num=2 \
   -device loader,addr=0x40004000,cpu-num=3 \
   ;

一开始连接gcc时,可以看到:

(gdb) info threads
  Id   Target Id     Frame
 * 1   Thread 1.1 (CPU#0 [running]) _start () at .../start.S:20
   2   Thread 1.2 (CPU#1 [halted ]) _start () at .../start.S:20
   3   Thread 1.3 (CPU#2 [halted ]) _start () at .../start.S:20
   4   Thread 1.4 (CPU#3 [halted ]) _start () at .../start.S:20

我希望其他三个处理器以 "running" 状态启动,而不是 "halted"。怎么样?

请注意,我的 DTS 包含此部分:

psci {
    migrate = < 0xc4000005 >;
    cpu_on = < 0xc4000003 >;
    cpu_off = < 0x84000002 >;
    cpu_suspend = < 0xc4000001 >;
    method = "smc";
    compatible = "arm,psci-0.2[=15=]arm,psci";
};

但是,我不确定该怎么做。添加许多这种形式的不同行,似乎没有帮助:

-device loader,addr=0xc4000003,data=0x80000000,data-len=4

我不确定我在ARM PSCI这件事上是否走在正确的轨道上? ARM 的规范似乎定义了 "interface",而不是系统 "implementation"。但是,我没有将 PSCI 视为 "virt" documentation/source 中提到的 "real" 寄存器。 DTS 中没有提到 "SMC" 设备。

QEMU 如何决定 SMP 处理器在启动时是 "running" 还是 "halted",我如何影响它?


根据下面@Peter-Maydell 的回答,我需要做以下两件事之一...

  1. 将“-kernel”切换为“-bios”。我这样做了,但我的代码没有按预期加载。我的 *.elf 文件有几个部分;一些在 FLASH 中,一些在 DDR 中(高于 0x40000000)。也许这就是问题所在?
  2. 更改我的引导代码以设置并发出 SMC 指令,使 ARM PSCI "CPU_ON" 调用 QEMU 将识别并启动其他处理器。 像这样的代码运行但似乎"do"什么都没有...

    ldr   w0, =0xc4000003   // CPU_ON code from the DTS file
    mov   x1, 1             // CPU #1 in cluster zero (format of MPIDR register?)
    ldr   x2, _boot         // Jump address 0x40006000 (FYI)
    mov   x3, 1             // context ID (meaningful only to caller)
    smc   #0                // GO!
    // result is in x0 -> PSCI_RET_INVALID_PARAMS
    

这取决于电路板型号——通常我们遵循硬件的功能,有些电路板从通电开始所有 CPUs,有些则不然。对于 'virt' 板(特定于 QEMU),我们通常使用 PSCI,这是 Arm 标准固件接口,用于为 SMP CPU 上下供电(除其他外;您也可以例如,将其用于 'power down entire machine')。在启动时只有主 CPU 是 运行ning,来宾代码的工作是使用 PSCI API 启动辅助。这就是 DTS 中的 psci 节点告诉来宾的内容——它告诉来宾 PSCI ABI QEMU 实现的具体形式,特别是来宾是否应该使用 'hvc' 或 'smc' 指令来调用 PSCI 函数。 QEMU 在这里做的是模拟一个 "hardware + firmware" 组合——来宾执行一个 'smc' 指令,QEMU 执行在真实硬件上将由一些固件代码执行的操作 运行ning在 EL3.

virt 板也有另一种操作模式,当您想要 运行 本身就是 EL3 固件的来宾时(例如,如果您想要 运行 OVMF/UEFI 在 EL3)。如果您使用 -machine secure=true 启动 QEMU 以启用 EL3 仿真,并且您还通过 -bios 或 -drive if=pflash,... 提供来宾固件 blob,那么 QEMU 将假设您的固件想要 运行在 EL3 并自己提供 PSCI 服务,因此它将从所有 CPU 启动并让固件处理将它们分类出来。

进行 PSCI 调用以打开另一个 CPU 的简单示例(在本例中 cpu 第 4 个,共 8 个):

    .equ PSCI_0_2_FN64_CPU_ON, 0xc4000003
    ldr x0, =PSCI_0_2_FN64_CPU_ON
    ldr x1, =4         /* target CPU's MPIDR affinity */
    ldr x2, =0x10000   /* entry point */
    ldr x3, =0         /* context ID: put into target CPU's x0 */
    smc 0

使用 Peter Maydell 提供的回复,我在这里为可能感兴趣的人提供了一个最小的、可重现的示例。

Downloading/installing aarch64-elf 工具链:

wget "https://developer.arm.com/-/media/Files/downloads/gnu-a/8.3-2019.03/binrel/gcc-arm-8.3-2019.03-x86_64-aarch64-elf.tar.xz?revision=d678fd94-0ac4-485a-8054-1fbc60622a89&la=en"
mkdir -p /opt/arm
tar Jxf gcc-arm-8.3-2019.03-x86_64-aarch64-elf.tar.xz -C /opt/arm

示例文件:

loop.s:

                .title "loop.s"
                .arch armv8-a
                .text
                .global Reset_Handler
Reset_Handler:  mrs x0, mpidr_el1
                and x0,x0, 0b11
                cmp x0, #0
                b.eq Core0
                cmp x0, #1
                b.eq Core1
                cmp x0, #2
                b.eq Core2
                cmp x0, #3
                b.eq Core3
Error:          b .
Core0:          b .
Core1:          b .
Core2:          b .
Core3:          b .
               .end

build.sh:

#!/bin/bash
set -e

CROSS_COMPILE=/opt/arm/gcc-arm-8.3-2019.03-x86_64-aarch64-elf/bin/aarch64-elf-
AS=${CROSS_COMPILE}as
LD=${CROSS_COMPILE}ld
OBJCOPY=${CROSS_COMPILE}objcopy
OBJDUMP=${CROSS_COMPILE}objdump

${AS} -g -o loop.o loop.s 
${LD} -g -gc-sections -g -e Reset_Handler -Ttext-segment=0x40004000 -Map=loop.map -o loop.elf loop.o
${OBJDUMP} -d loop.elf

qemu.sh:

#!/bin/bash
set -e
QEMU_SYSTEM_AARCH64=qemu-system-aarch64
${QEMU_SYSTEM_AARCH64} \
   -s -S \
   -machine virt,secure=on,virtualization=on \
   -cpu cortex-a53 \
   -d int \
   -m 512M \
   -smp 4 \
   -display none \
   -nographic \
   -semihosting \
   -serial mon:stdio \
   -bios loop.elf \
   -device loader,addr=0x40004000,cpu-num=0 \
   -device loader,addr=0x40004000,cpu-num=1 \
   -device loader,addr=0x40004000,cpu-num=2 \
   -device loader,addr=0x40004000,cpu-num=3 \
   ;

loop.gdb:

target remote localhost:1234
file loop.elf
load loop.elf
disassemble Reset_Handler
info threads
continue

debug.sh:

#!/bin/bash
CROSS_COMPILE=/opt/arm/gcc-arm-8.3-2019.03-x86_64-aarch64-elf/bin/aarch64-elf-
GDB=${CROSS_COMPILE}gdb

${GDB} --command=loop.gdb

执行程序 - 需要两个控制台。

第一个控制台:

./build.sh 

输出应如下所示:

/opt/arm/gcc-arm-8.3-2019.03-x86_64-aarch64-elf/bin/aarch64-elf-ld: warning: address of `text-segment' isn't multiple of maximum page size

loop.elf:     file format elf64-littleaarch64


Disassembly of section .text:

0000000040004000 <Reset_Handler>:
    40004000:   d53800a0        mrs     x0, mpidr_el1
    40004004:   92400400        and     x0, x0, #0x3
    40004008:   f100001f        cmp     x0, #0x0
    4000400c:   54000100        b.eq    4000402c <Core0>  // b.none
    40004010:   f100041f        cmp     x0, #0x1
    40004014:   540000e0        b.eq    40004030 <Core1>  // b.none
    40004018:   f100081f        cmp     x0, #0x2
    4000401c:   540000c0        b.eq    40004034 <Core2>  // b.none
    40004020:   f1000c1f        cmp     x0, #0x3
    40004024:   540000a0        b.eq    40004038 <Core3>  // b.none

0000000040004028 <Error>:
    40004028:   14000000        b       40004028 <Error>

000000004000402c <Core0>:
    4000402c:   14000000        b       4000402c <Core0>

0000000040004030 <Core1>:
    40004030:   14000000        b       40004030 <Core1>

0000000040004034 <Core2>:
    40004034:   14000000        b       40004034 <Core2>

0000000040004038 <Core3>:
    40004038:   14000000        b       40004038 <Core3>

然后:

./qemu.sh

第二个控制台:

./debug.sh

输出应如下所示:

GNU gdb (GNU Toolchain for the A-profile Architecture 8.3-2019.03 (arm-rel-8.36)) 8.2.1.20190227-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=aarch64-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://bugs.linaro.org/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word".
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x0000000040004000 in ?? ()
Loading section .text, size 0x3c lma 0x40004000
Start address 0x40004000, load size 60
Transfer rate: 480 bits in <1 sec, 60 bytes/write.
Dump of assembler code for function Reset_Handler:
=> 0x0000000040004000 <+0>:     mrs     x0, mpidr_el1
   0x0000000040004004 <+4>:     and     x0, x0, #0x3
   0x0000000040004008 <+8>:     cmp     x0, #0x0
   0x000000004000400c <+12>:    b.eq    0x4000402c <Core0>  // b.none
   0x0000000040004010 <+16>:    cmp     x0, #0x1
   0x0000000040004014 <+20>:    b.eq    0x40004030 <Core1>  // b.none
   0x0000000040004018 <+24>:    cmp     x0, #0x2
   0x000000004000401c <+28>:    b.eq    0x40004034 <Core2>  // b.none
   0x0000000040004020 <+32>:    cmp     x0, #0x3
   0x0000000040004024 <+36>:    b.eq    0x40004038 <Core3>  // b.none
End of assembler dump.
  Id   Target Id                    Frame 
* 1    Thread 1.1 (CPU#0 [running]) Reset_Handler () at loop.s:5
  2    Thread 1.2 (CPU#1 [running]) Reset_Handler () at loop.s:5
  3    Thread 1.3 (CPU#2 [running]) Reset_Handler () at loop.s:5
  4    Thread 1.4 (CPU#3 [running]) Reset_Handler () at loop.s:5

所有四个内核都停止在地址 0x40004000/Reset_Handler,并由 loop.gdb 中的 continue 命令启动。 在第二个控制台按CTRL+C

^C
Thread 1 received signal SIGINT, Interrupt.
Core0 () at loop.s:16
16      Core0:                  b .
(gdb) 

Core #0 正在 Core0 标签处执行代码。 输入以下命令(仍在第二个控制台中):

(gdb) info threads
  Id   Target Id                    Frame 
* 1    Thread 1.1 (CPU#0 [running]) Core0 () at loop.s:16
  2    Thread 1.2 (CPU#1 [running]) Core1 () at loop.s:17
  3    Thread 1.3 (CPU#2 [running]) Core2 () at loop.s:18
  4    Thread 1.4 (CPU#3 [running]) Core3 () at loop.s:19
(gdb) 

核心 #1、#2 和 #3 在被 CTRL+C 停止之前正在各自的 Core1、Core2、Core3 标签上执行代码。

MPIDR_EL1 寄存器的描述可用hereMPIDR_EL1.Aff0 的最后两位被所有四个内核用来确定它们各自的内核编号。