QEMU の aarch64 標準モデル virt の中身を guest kernel から探ってみます。ここではソースではなく、userland で得られる情報から見てみます。特に QEMU では virt モデルを使用する場合は devicetree を指定することがないため、解読することでモデルの構造が見えてきます。
QEMU を以下のコマンドラインで実行した場合の状況を確認します。
qemu-system-aarch64 -M virt -cpu cortex-a72 -m 1024 -smp 2 -nographic \
-kernel Image.gz -initrd rootfs.cpio.gz \
-append "rdinit=/bin/sh" \
-device e1000,netdev=net0 -netdev user,id=net0
使用する QEMU は以下のバージョンです。
QEMU emulator version 5.1.50 Copyright (c) 2003-2020 Fabrice Bellard and the QEMU Project developers
devicetree の解読
/sys/firmware/devicetree 以下を見ると devicetree の構造を確認することができます。これをテキストに起こしてパートごとに見てみます。
まず モデルの compatible 文字列とアドレス/サイズの幅(2=64bit)、最上位の割込みコントローラ指定があります。コントローラの ”gic" ラベルはこちらで付けたもので、本体の記述は後で出てきます。
base {
compatible = "linux,dummy-virt"
#address-cells = <2>;
#size-cells = <2>;
interrupt-parent = <&gic>;
"stdout-path" では標準出力として使用するドライバパスが指定されています。"bootargs" ではコマンドラインで指定した文字列がそのまま反映されています。linux,initrd-start/end は QEMU が initrd を配置したアドレスを示しています。
chosen {
stdout-path = "/pl011@9000000";
bootargs = "rdinit=/bin/sh";
linux,initrd-start = <0x48000000>;
linux,initrd-end = <0x497acfbe>;
};
CPU の記述があります。"-cpu cortex-a72 -smp 2" の指定により、CA72 2core の構成が現れます。
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatbile = "arm,cortex-a72";
device_type = "cpu";
reg = <0>;
};
cpu@1 {
compatbile = "arm,cortex-a72";
device_type = "cpu";
reg = <1>;
};
};
PSCI の記述があります。"hvc" は Hypervisor Call 命令で、guest 上で命令が発行されると QEMU にフックされ実行されます。実行されるコマンド "cpu_on", "cpu_off", "cpu_suspend", "migrate" に対応する値がそれぞれ記述されています(PSCI 仕様で決まっています)。
psci {
compatible = "arm,psci-0.2", "arm,psci";
method = "hvc";
cpu_on = <0xc4000003>;
cpu_off = <0x84000002>;
cpu_suspend = <0xc4000001>;
migrate = <0xc4000005>;
};
Armv8 CPU に実装されている Arch Timer が記述されています。割込み番号はアーキテクチャで決まっています。
timer {
compatible = "arm,armv8-timer", "arm,armv7-timer";
interrupts = <1 13 0x104>,
<1 14 0x104>,
<1 11 0x104>,
<1 10 0x104>;
always-on;
};
Armv8 CPU に実装されている PMU (Performance Monitor) が指定されています。通常はコア種別ごとに指定ですが、ここでは QEMU 向けのソフト実装 "armv8-pmuv3" が指定されています。
pmu {
compatible = "arm,armv8-pmuv3";
interrupts = <1 7 0x104>;
};
周辺バス(APB) 用のクロックが記述されています。クロックゲートなどの機能がない固定周波数クロック "fixed-clock" のため、各ドライバで参照される際に API でレート参照はできるものの、他の操作はスルーされます。
clk: apb-pclk {
compatible = "fixed-clock";
clock-freqyency = <24000000>;
clock-output-names = "clk24mhz";
#clock-cells = <0>;
};
パワーオフキーについて記述されています。キーコードの "linux,code" と GPIO の接続元 "gpios" が指定されています。
gpio-keys {
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
poweroff {
label = "GPIO Key Poweroff";
linux,code = <0x00000074>;
gpios = <&gpio 3 0>;
};
};
内蔵フラッシュメモリが 64MB x 2バンク分記述されています。MTD デバイスとして現れますが、どのように使われるかは今のところ分かりません。
flash@0 {
compatible = "cfi-flash";
reg = <0 0x00000000 0 0x04000000>,
<0 0x04000000 0 0x04000000>;
bank-width = <4>;
};
割込みコントローラとして "A15 GIC" が記述されています。さらに PCIe の割込みに使用される MSI のための拡張 (V2M) が記述されています。
gic: intc@8000000 {
compatible = "arm,cortex-a15-gic";
reg = <0 0x08000000 0 0x10000>,
<0 0x08010000 0 0x10000>;
#address-cells = <2>;
#size-cells = <2>;
#interrupt-cells = <3>;
ranges;
gic_v2m: v2m@8020000 {
compatible = "arm,gic-v2m-frame";
reg = <0 0x08020000 0 0x1000>;
msi-controller;
};
};
QEMU に搭載されているシリアルデバイス "PL011" が記述されています。clock は ボーレートの基準クロック "uartclk" と PL011 を動作させるための "apb_pclk" が指定されています。
pl011@9000000 {
compatible = "arm,pl011", "arm,primecell";
reg = <0 0x09000000 0x1000>;
interrupts = <0 1 4>;
clock-names = "uartclk", "apb_pclk";
clocks = <&clk>, <&clk>;
};
QEMU に搭載されているリアルタイムクロック "PL031" が記述されています。
pl031@9010000 {
compatible = "arm,pl031", "arm,primecell";
reg = <0 0x09010000 0x1000>;
interrupts = <0 2 4>;
clock-names = "apb_pclk";
clocks = <&clk>;
};
QEMU でエミュレーションしている firmware を操作するレジスタが記述されています。
fw-cfg@9020000 {
compatible = "qemu,fw-cfg-mmio";
reg = <0 0x09020000 0 0x18>;
dma-coherent;
};
QEMU に搭載されている GPIO コントローラ "PL061" が記載されています。先に記載されていた gpio-keys はこのコントローラを参照しています。
gpio: pl061@9030000 {
compatible = "arm,pl061", "arm,primecell";
reg = <0 0x09030000 0x1000>;
interrupts = <0 7 4>;
clock-names = "apb_pclk";
clocks = <&clk>;
gpio-controller;
#gpio-cells = <2>;
};
QEMU で準仮想化デバイスのための MMIO 領域が 32 個記述されています。
virtio_mmio@a000000 {
compatible = "virtio,mmio";
reg = <0 0x0a000000 0x200>;
dma-cohenrent;
};
:
:
virtio_mmio@a003e00 {
compatible = "virtio,mmio";
reg = <0 0x0a003e00 0x200>;
dma-cohenrent;
};
QEMU のバスが記述されています。このバスに接続されたデバイスから領域と割込みが参照されますが、今回の例では接続はありません。
platform@c000000 {
compatible = "qemu,platform", "simple-bus";
ranges = <0 0 0x0c000000 0x02000000>;
interrupt-parent = <&gic>;
#address-cells = <1>;
#size-cells = <1>;
};
PCI-express のコントローラとバス設定が記述されています。ハードウェア固有になる config のアクセス手段は QEMUによって ECAM(Enhanced configuration access mechanism) で共通化されているため、kernel は generic なハードとして "pci-host-ecam-generic" が指定されています。また MSI 割込みは先の GIC V2M が指定されています。
pcie@10000000 {
compatible = "pci-host-ecam-generic";
reg = <0x00000040 0x10000000 0 0x10000000>;
device_type = "pci";
bus-range = <0 0xff>;
msi-parent = <&gic_v2m>;
dma-coherent;
linux,pci-domain = <0>;
#address-cells = <3>;
#size-cells = <2>;
PCI バス領域は "ranges" に記述されています。最初の 1 word (32bit) が属性、次の 2 word (64bit) が PCI バスの先頭アドレス、次の 2 word (64bit) が物理アドレス、最後の 2 word(64bit) がサイズを示しています。これは devicetree の仕様で決まっていますが、eLinux の解説に詳しいビットの意味が書かれています。
ranges =
/* 00_3eff0000 - 00_3effffff -> 00_00000000 non-reloc I/O */
<0x01000000 0x00 0x00000000 0x00 0x3eff0000 0x00 0x00010000>,
/* 00_10000000 - 00_3eefffff -> 00_10000000 non-reloc non-prefech 32bit MEM */
<0x02000000 0x00 0x10000000 0x00 0x10000000 0x00 0x2eff0000>,
/* 80_00000000 - ff_ffffffff -> 80_00000000 non-reloc non-prefech 64bit MEM */
<0x03000000 0x80 0x00000000 0x80 0x00000000 0x80 0x00000000>;
PCI 割込みは Legacy 割込みとすでに GIC V2M への参照が記載された MSI があります。Legacy 割込みは INTA,INTB,INTC,INTD の4つの割込みがあり、これらを上位のコントローラのどの番号に割り当てるかを指定します。詳細は不明ですがスロットが4つある実装になっているようなので、4x4=16 行の記載があるようです。
#interrupt-cells = <1>;
interrupt-map-mask = <0x1800 0 0 7>;
interrupt-map =
/* slot 1 */
<0x0000 0 0 1 &gic 0 0 0 3 4>,
<0x0000 0 0 2 &gic 0 0 0 4 4>,
<0x0000 0 0 3 &gic 0 0 0 5 4>,
<0x0000 0 0 4 &gic 0 0 0 6 4>,
/* slot 2 */
<0x0800 0 0 1 &gic 0 0 0 4 4>,
<0x0800 0 0 2 &gic 0 0 0 5 4>,
<0x0800 0 0 3 &gic 0 0 0 6 4>,
<0x0800 0 0 4 &gic 0 0 0 3 4>,
/* slot 3 */
<0x1000 0 0 1 &gic 0 0 0 5 4>,
<0x1000 0 0 2 &gic 0 0 0 6 4>,
<0x1000 0 0 3 &gic 0 0 0 3 4>,
<0x1000 0 0 4 &gic 0 0 0 4 4>,
/* slot 4 */
<0x1800 0 0 1 &gic 0 0 0 6 4>,
<0x1800 0 0 2 &gic 0 0 0 3 4>,
<0x1800 0 0 3 &gic 0 0 0 4 4>,
<0x1800 0 0 4 &gic 0 0 0 5 4>;
};
最後に DRAM メモリ領域が記述されています。"-m" で指定したメモリサイズが反映されます。
memory@40000000 {
device_type = "memory";
reg = <0 0x40000000 0 0x40000000>;
};
};
memory map
devicetree から virt モデルは次のようなマップになっているようです。もちろん QEMU:hw/arm/virt.c を見れば詳細が定義されてるのが分かります。
| 00 00000000 | flash@0 |
| 00 04000000 | flash@1 |
| 00 08000000 | Arm GIC (cortex-a15 GIC) |
| 00 08020000 | Arm GIC v2m |
| 00 09000000 | Arm PL011 Serial |
| 00 09010000 | Arm PL031 RTC |
| 00 09020000 | QEMU firmware config |
| 00 09030000 | Arm PL061 GPIO |
| 00 0a000000 | QEMU virtio |
| 00 0c000000 | QEMU platform bus |
| 00 10000000 | ECAM PCI-express (32bit MEM) |
| 00 3eff0000 | ECAM PCI-express (I/O) |
| 00 40000000 | DRAM |
| 40 10000000 | ECAM PCI-exress |
| 80 00000000 | ECAM PCI-express (64bit MEM) |
割込み番号
devicetree に記述された割込みは以下のように反映されています。PCIe のデバイスが接続された構成では、さらに割込みが追加されます。Ethernet (eth0) は Legacy 割込みを使用していて、GIC に直結しているため "GIC-0" として現れています。電源 off キーは PL061 を割込みコントローラの割込みとして現れています。
# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
11: 115 255 265 456 GIC-0 27 Level arch_timer
46: 0 0 0 0 GIC-0 34 Level rtc-pl031
47: 46 0 0 0 GIC-0 33 Level uart-pl011
48: 0 0 0 0 GIC-0 23 Level arm-pmu
49: 9 0 0 0 GIC-0 36 Level eth0
50: 0 0 0 0 9030000.pl061 3 Edge GPIO Key Poweroff
IPI0: 117 218 306 283 Rescheduling interrupts
IPI1: 44 7 6 6 Function call interrupts
IPI2: 0 0 0 0 CPU stop interrupts
IPI3: 0 0 0 0 CPU stop (for crash dump) interrupts
IPI4: 0 0 0 0 Timer broadcast interrupts
IPI5: 0 0 0 0 IRQ work interrupts
IPI6: 0 0 0 0 CPU wake-up interrupts
Err: 0
まとめ
QEMU で virt モデルを使用する場合、devicetree を指定する必要がないので QEMU で生成されているのだろうと思い、展開して本記事にて眺めてみました。devicetree を見ることでハードウェアやモデルの構成、接続の仕方などが見えてくるので、ドライバ作成の参考になるかと思います。

コメント