明日にはでっかい太陽が昇るかもしれません。

「覚悟」とは!! 暗闇の荒野に!!進むべき道を切り開く事だッ!

Linux (aarch64) on Qemu を動作させたい (2)

Qemu への SEV 対応は、以下のような情報があり、 "パフォーマンスが悪いから nop にしているよ" みたいな感じに読めたけど、今回はパフォーマンスよりも動作の再現性を優先したいので、実装してみる。

Re: [Qemu-devel] implemetation of wfe and sev instructions on aarch64

diff --git a/target/arm/helper.h b/target/arm/helper.h
index df86bf71415..5cddcd0627f 100644
--- a/target/arm/helper.h
+++ b/target/arm/helper.h
@@ -50,6 +50,7 @@ DEF_HELPER_4(exception_with_syndrome, void, env, i32, i32, i32)
 DEF_HELPER_1(setend, void, env)
 DEF_HELPER_1(wfi, void, env)
 DEF_HELPER_1(wfe, void, env)
+DEF_HELPER_1(sev, void, env)
 DEF_HELPER_1(yield, void, env)
 DEF_HELPER_1(pre_hvc, void, env)
 DEF_HELPER_2(pre_smc, void, env, i32)
diff --git a/target/arm/op_helper.c b/target/arm/op_helper.c
index 2a856665797..dce0e11c011 100644
--- a/target/arm/op_helper.c
+++ b/target/arm/op_helper.c
@@ -445,6 +445,21 @@ void HELPER(wfe)(CPUARMState *env)
     HELPER(yield)(env);
 }
 
+void HELPER(sev)(CPUARMState *env)
+{
+    ARMCPU *cpu = arm_env_get_cpu(env);
+    CPUState *cs0 = CPU(cpu), *cs = cs0;
+
+    CPU_FOREACH(cs) {
+        if (cs->halted) {
+            fprintf(stderr, "%s: cpu%d halted (%p)\n", __func__, cs->cpu_index, cs->halt_cond);
+            qemu_cond_signal(cs->halt_cond);
+        }
+    }
+    //cs0->exception_index = EXCP_INTERRUPT;
+    cpu_loop_exit(cs0);
+}
+
 void HELPER(yield)(CPUARMState *env)
 {
     ARMCPU *cpu = arm_env_get_cpu(env);
diff --git a/target/arm/translate-a64.c b/target/arm/translate-a64.c
index cb44632d16a..d2b937a4343 100644
--- a/target/arm/translate-a64.c
+++ b/target/arm/translate-a64.c
@@ -1345,7 +1345,7 @@ static void handle_hint(DisasContext *s, uint32_t insn,
         return;
     case 4: /* SEV */
     case 5: /* SEVL */
-        /* we treat all as NOP at least for now */
+        s->is_jmp = DISAS_SEV;
         return;
     default:
         /* default specified as NOP equivalent */
@@ -11385,6 +11385,10 @@ void gen_intermediate_code_a64(CPUState *cs, TranslationBlock *tb)
             gen_a64_set_pc_im(dc->pc);
             gen_helper_wfe(cpu_env);
             break;
+        case DISAS_SEV:
+            gen_a64_set_pc_im(dc->pc);
+            gen_helper_sev(cpu_env);
+            break;
         case DISAS_YIELD:
             gen_a64_set_pc_im(dc->pc);
             gen_helper_yield(cpu_env);
diff --git a/target/arm/translate.h b/target/arm/translate.h
index 2fe144baa9b..c936488cdc3 100644
--- a/target/arm/translate.h
+++ b/target/arm/translate.h
@@ -132,6 +132,7 @@ static void disas_set_insn_syndrome(DisasContext *s, uint32_t syn)
 #define DISAS_EXC 6
 /* WFE */
 #define DISAS_WFE 7
+#define DISAS_SEV 13
 #define DISAS_HVC 8
 #define DISAS_SMC 9
 #define DISAS_YIELD 10

ls1046ardb 仮想ボード側も GIC を接続するバスが間違っていたので、そこを修正すると secondary core も wfe を突破することはできるようになった。

しかし、 wfe 後に EL3 → EL2 と動作環境を切り替える処理があり、そこでハングしてしまった。

現在 ls1046ardb 仮想ボードは EL1 で動作している (EL2, EL3 を有効にすると起動そうそうにハングしていたので、無効化している) ので、以下のように u-boot 側を変更して、 EL の切り替えを発生させないようにすると、 kernel コードへ移動することができるようになった。

diff --git a/arch/arm/cpu/armv8/fsl-layerscape/lowlevel.S b/arch/arm/cpu/armv8/fsl-layerscape/lowlevel.S
index 28a31b21a9..2558481c80 100644
--- a/arch/arm/cpu/armv8/fsl-layerscape/lowlevel.S
+++ b/arch/arm/cpu/armv8/fsl-layerscape/lowlevel.S
@@ -73,7 +73,7 @@ ENDPROC(smp_kick_all_cpus)
 ENTRY(lowlevel_init)
        mov     x29, lr                 /* Save LR */
 
-       switch_el x1, 1f, 100f, 100f    /* skip if not in EL3 */
+       switch_el x1, 1f, 100f, 101f    /* skip if not in EL3 */
 1:
 
 #if defined (CONFIG_SYS_FSL_HAS_CCN504)
@@ -181,6 +181,7 @@ ENTRY(lowlevel_init)
        str     w0, [x1, #0x10]
 #endif
 
+101:
        /* Initialize GIC Secure Bank Status */
 #if defined(CONFIG_GICV2) || defined(CONFIG_GICV3)
        branch_if_slave x0, 1f
@@ -487,7 +488,8 @@ slave_cpu:
        ldr     x0, [x11]
        cbz     x0, slave_cpu
 #ifndef CONFIG_ARMV8_SWITCH_TO_EL1
-       mrs     x1, sctlr_el2
+       //mrs     x1, sctlr_el2
+       mrs     x1, sctlr_el1
 #else
        mrs     x1, sctlr_el1
 #endif
@@ -521,7 +523,7 @@ ENDPROC(secondary_boot_func)
 
 ENTRY(secondary_switch_to_el2)
        switch_el x6, 1f, 0f, 0f
-0:     ret
+0:     br x4
 1:     armv8_switch_to_el2_m x4, x5, x6
 ENDPROC(secondary_switch_to_el2)
 

kernel に入ったあとは、 CPU Idle Governor の初期化でハングが発生 (全コアが idle に入ってしまう?) してしまったのでデバッグ中。

GDB で追いかけているけど、問題の箇所付近で全然違う処理に飛んでしまい、 GDB が有効に活用できなくなって苦戦中。。


追記 (2017-12-17 13:56)

CPU Idle Governor を無効にしてみたけど、やっぱりハングするので、原因は違うところっぽい。。どうやって調査しようか。。

Linux (aarch64) on Qemu を動作させたい (1)

U-Boot をひと通り動かすことができるようになったので、引き続き Linux を動作させていく。

Linux も、 NXP-SDK 付属のものを使用して、ターゲット向けのバイナリをそのまま動作させることを目指す。

FIT ファイルの作成

ビルドしたあと、 U-Boot からよみこむために FIT (Flattened uImage Tree) ファイルを作成する。

its ファイルは、 NXP-SDK 付属のものをベースにしている。

$ cat kernel.its
/dts-v1/;

/ {
        description = "Image file for the LS1046A Linux Kernel";
        #address-cells = <1>;

        images {
                kernel@1 {
                        description = "ARM64 Linux kernel";
                        data = /incbin/("path/to/arch/arm64/boot/Image.gz");
                        type = "kernel";
                        arch = "arm64";
                        os = "linux";
                        compression = "gzip";
                        load = <0x80080000>;
                        entry = <0x80080000>;
                };
                fdt@1 {
                        description = "Flattened Device Tree blob";
                        data = /incbin/("path/to/arch/arm64/boot/dts/freescale/fsl-ls1046a-rdb.dtb");
                        type = "flat_dt";
                        arch = "arm64";
                        compression = "none";
                        load = <0x90000000>;
                };
                ramdisk@1 {
                        description = "LS1046 Ramdisk";
                        data = /incbin/("path/to/fsl-image-minimal-ls1046ardb.ext2.gz");
                        type = "ramdisk";
                        arch = "arm64";
                        os = "linux";
                        compression = "gzip";
                };
        };

        configurations {
                default = "config@1";
                config@1 {
                        description = "Boot Linux kernel";
                        kernel = "kernel@1";
                        fdt = "fdt@1";
                        ramdisk = "ramdisk@1";
                };
        };
};

$ mkimage -f kernel.its kernel.itb

作成した itb ファイルは emmc イメージにコピーしておく。

Linux kernel の起動

U-Boot を起動したあと、

=> print load_addr
load_addr=0xa0000000
=> print emmc_bootcmd
emmc_bootcmd=echo Trying load from eMMC ..;mmcinfo; ext2load mmc 0:1 $load_addr /kernel.itb; bootm $load_addr
=> run emmc_bootcmd

Linux をロードできる。

で、動作はというと、2番目以降のコアを起動するところで、起動させることができずに起動待ちループで止まってしまった。

... 省略 ...
[    0.064447] Calibrating delay loop (skipped), value calculated using timer frequency.. 125.00 BogoMIPS (lpj=625000)
[    0.068584] pid_max: default: 32768 minimum: 301
[    0.078346] Security Framework initialized
[    0.092536] Mount-cache hash table entries: 4096 (order: 3, 32768 bytes)
[    0.093597] Mountpoint-cache hash table entries: 4096 (order: 3, 32768 bytes)
[    0.211129] Initializing cgroup subsys memory
[    0.217881] Initializing cgroup subsys hugetlb
[    0.320269] hw perfevents: enabled with arm/armv8-pmuv3 PMU driver, 1 counters available
[    0.324401] EFI services will not be available.
... ここで止まる ...

U-Boot のとき同様、 Qemu 経由で kernel に GDB を接続すると、以下のことがわかった。

  1. CPU の起動方法 ("enable-method") に "spin-table" が設定されている
  2. CPU の起動トリガ ("cpu-release-addr") にアドレスを書き込んだあとに SEV を呼び出している
  3. が、Qemu 側が SEV / WFE 命令に対応していないため、何も起こらない

という状況っぽい。

CPU 起動待ちのタイムアウトも発生していないのが気になるが、今は Qemu を SEV に対応させることを考えている。

でも、 Qemu の aarch64 が smp に対応していないということはないはずなので、何かが間違っているから動いていないだけの可能性もあるので、まずは Qemu の aarch64 で smp 可能なボードがどう動いているかを解析してみることにする。


追記 (2017-12-11 21:55)

とりあえず、 U-Boot のときも比較に使用していた Xilinx の Zynq MP ボードは CPU の起動方法が "psci" となっており、起動方法が異なるので参考にはならなさそう。

仮想ボードが virt の場合とかはどう動くんだろう?


追記 (2017-12-17 12:34)

仮想ボードが virt の場合も起動方法は "psci" だった。。

U-Boot (aarch64) on Qemu を動作させたい (10)

github.com

ほそぼそと開発を続けていたのですが、ようやく eMMC (実態は SDHCバイス) の実装がある程度まともに動作するようになり、 U-Boot から ext2 パーティションをマウントできるようになりました。

仕事で使用しているカスタムボードは eMMC から kernel をブートするので、 PCIe よりも優先して対応していました。

コード対応はコミットを見ればわかるけど、 qemu の起動引数での emmc ファイルイメージの指定方法や、そもそも emmc イメージファイルにパーティションファイルシステムを作成する手順をまとめておく。

emmc ファイルイメージファイルを作成する

### truncate コマンドを使用する場合
$ truncate -s 4GiB emmc.img

サイズは適当に。

イメージファイルにパーティションファイルシステムを作成する

### 開いているループバックデバイスを調べてファイルを割り当てる
$ sudo losetup -f
/dev/loop0
$ sudo losetup /dev/loop0 emmc.img 

### パーティションを作成する
$ sudo parted /dev/loop0
GNU Parted 2.3
/dev/loop0 を使用
GNU Parted へようこそ! コマンド一覧を見るには 'help' と入力してください。
(parted) mkpart primary ext2 1024KiB 100%                                                                                                                     
(parted) print                                                            
モデル: Loopback device (loop)
ディスク /dev/loop0: 4295MB
セクタサイズ (論理/物理): 512B/512B
パーティションテーブル: msdos

番号  開始    終了    サイズ  タイプ   ファイルシステム  フラグ
 1    1049kB  4295MB  4294MB  primary

(parted) q

### ext1 パーティションを作成
$ sudo mkfs.ext2 /dev/loop0p1

### ループバックデバイスの割り当てを解除
$ sudo losetup -d /dev/loop0

なんか、パーティションの開始セクタは開けておかないと 警告: 操作の結果できるパーティションはアライメントが正しくないためにパフォーマンスがでません。 という警告が出たので、 fdisk で初期化した時に作成される隙間と同じサイズを開けている。

一応、 parted コマンドでもファイルシステムを作れるけど、 mkfs サブコマンドを実行すると

警告: ファイルシステムに対して parted による操作(mkfs)を行おうとしています。
parted のファイルシステム操作コードは、e2fsprogs のようなファイルシステム専
用のものほど堅固に作られていません。可能なかぎり parted をパーティションテー
ブルの操作だけに用いることをお勧めします。ほとんどのファイルシステムに
対するほとんどの操作は今後のリリースで削除される予定です。

ってでるので、あとから mkfs コマンドを実行している。

fdisk を使わずに parted コマンドでパーティションを作成しているのは、 partedパーティション作成後に自動で /dev/loop0p1 デバイスファイルを作成してくれるから。 (fdisk 使っても手動でデバイスファイルは作れるんだろうけど、未調査)

仮想マシンにイメージファイルをマウントする

### drive オプション (種別は SD) でファイルをマウントする
$ qemu-system-aarch64 \
    -machine ls1046ardb \
    -smp 4 \
    -m 2046M \
    -kernel u-boot.elf \
    -drive if=sd,cache=unsafe,format=raw,file=emmc.img \
    -nographic \

仮想マシン上から読み込む

### ファイルの確認と、内容の確認
=> mmc dev 0
switch to partitions #0, OK
mmc0 is current device
=> mmc part

Partition Map for MMC device 0  --   Partition Type: DOS

Part    Start Sector    Num Sectors     UUID            Type
  1     2048            8386560         3bc514ae-01     83
=> ext2ls mmc 0:1 /
<DIR>       4096 .
<DIR>       4096 ..
<DIR>      16384 lost+found
               5 test.txt
=> ext2load mmc 0:1 80000000 /test.txt
5 bytes read in 26 ms (0 Bytes/s)
=> md.b 80000000 8
80000000: 74 65 73 74 0a 00 00 00                            test....

ホスト PC 上でイメージにファイルを追加する

開発は docker 上で行っているが、 docker コンテナで parted の自動デバイス割り当てが動作しなかった (udevadm が動作しない) ので、こちらの手順は fdisk で行っている。

### パーティションの位置を確認する
$ fdisk -l ../../emmc.img 
Disk ../../emmc.img: 4 GiB, 4294967296 bytes, 8388608 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x3bc514ae

Device          Boot Start     End Sectors Size Id Type
../../emmc.img1       2048 8388607 8386560   4G 83 Linux

### パーティションの先頭をオフセットにしてマウントする
$ sudo mount -o loop,offset=$((2048*512)) emmc.img /mnt

ということで、無事に emmc イメージファイル上のファイルを仮想マシン上で読み込むことができた。

次は kernel ブートを試してみる。

SDIO におけるコマンドの CRC について

Qemu の MMC / SDエミュレーションのコードを見ると、すべてのデバイスCRCcrc = 0 /* FIXME */ とかになっているので、真面目に実装しようかなと思って調べた情報を記載しておく。

全体の仕様は、

https://www.sdcard.org/cht/developers/overview/sdio/sdio_spec/Simplified_SDIO_Card_Spec.pdf

を見れば良くて、細かな実装は、

Seabright Technology

を参考にできそう。

docker コンテナ内で byobu (tmux) を利用する

Tmux and Screen | Intuitive documentation for using Docker containerization

最近は docker exec で直接コンテナ内に入っているけど、 ssh 経由の場合などと対処を混同しちゃうので、まとめておく。

$ docker exec -ti YOUR_CONTAINER_NAME script -q -c "/bin/bash" /dev/null

でコンテナ内に入って、

### or tmux
$ byobu 

とすれば、コンテナ内で tmux を正常に起動できる。

U-Boot (aarch64) on Qemu を動作させたい (9)

github.com

結論からいうと、公式の U-Boot をターゲット向けビルドのモジュールのまま Qemu 上で起動することができるようになった。

ここでの公式は、オリジナル U-Boot に NXP のカスタマイズが入った今回のターゲットボードである LS1046A RDB 向け U-Boot を指す。

前回 PTE の情報がなにかおかしそう、というところまでわかっていたが、リロケーション前に early_mmu_setup()set_ttbr_tcr_mair() で設定する TCR の値がリロケーション後のメモリマップと合っていないっぽいことがわかった。

get_tcr() で、メモリマップに定義されたアドレスの最大値からアドレス幅を求めて、 T0SZ の値を出しているのだが、リロケーション前のアドレスマップは LS1046A RDB で定義されている 480 GiB の DDR Region3 領域を使用しないようになっていたため、リロケーション前に設定した TCR (T0SZ) を使用したリロケーション後の PTE アクセスで不整合が発生していたように見える。

なので、リロケーション前のアドレスマップにも DDR Region3 領域を追加すると無事プロンプトが使えるところまで起動させることができるようになった。

... 省略 ...
DRAM:  Detected UDIMM                                                                                                                                           [1155/19018]
1.9 GiB (DDR4, 32-bit, CL=1, ECC off)
Waking secondary cores to start from fbd1f000
Not all cores (0xf) are up (0x1)
Did not wake secondary cores
Using SERDES1 Protocol: 4403 (0x1133)
Using SERDES2 Protocol: 21849 (0x5559)
ERROR: Stopped after 0 portals
ERROR: Stopped after 0 portals
NAND:  fsl_ifc_chip_init: address did not match any chip selects
0 MiB
MMC:   FSL_SDHC: 0
MMC: no card present
mmc_init: -123, time 1002
*** Warning - MMC init failed, using default environment

EEPROM: wait_for_sr_state: failed sr=a1 cr=f8 state=202
i2c_init_transfer: failed for chip 0x53 retry=0
wait_for_sr_state: failed sr=a1 cr=f8 state=202
i2c_init_transfer: failed for chip 0x53 retry=1
wait_for_sr_state: failed sr=a1 cr=f8 state=202
i2c_init_transfer: failed for chip 0x53 retry=2
i2c_init_transfer: give up i2c_regs=0x2180000
Read failed.
In:    serial
Out:   serial
Err:   serial
AHCI 0000.0000 1 slots 1 ports ? Gbps 0x0 impl SATA mode
flags: 
Found 0 device(s).
SCSI:  Net:   
MMC read: dev # 0, block # 18432, count 128 ...
MMC: no card present
mmc_init: -123, time 1001
MMC: block number 0x4880 exceeds max(0x0)
Fman1: Data at 00000000fbc25d30 is not a firmware
PCIe0: pcie@3400000 Endpoint: no link
PCIe1: pcie@3500000 Endpoint: no link
PCIe2: pcie@3600000 Endpoint: no link
No ethernet found.
Hit any key to stop autoboot:  0 
=> q
Unknown command 'q' - try 'help'

だが、 SDHCが未実装だったり、コアが 1 つしか動作していなかったりと、エミュレーションとしては不十分だが、 U-Boot の機能実装に対するテストが行えるようにはなったのではないかと思う。

と、ここまでは NXP の提供する NXP-SDK v2.0 update 17.03 に含まれる U-Boot で確認していたのだが、冒頭のリポジトリの最新コードを使用すると、リロケーション前のアドレスマップに対する修正なしに (Qemu で動かすための修正なしに!) すんなり起動してしまった。。

最新のコードでもアドレスマップに対する変更は入っていないし、(そもそもNXP-SDK v2.0 のコードも実機で動作しているものなのでアドレスマップが間違っているということはないと思うし、) どの差分が影響しているかは追っていないが、実機向けのバイナリがそのまま Qemu で起動できたのはとても嬉しかった。

最終目標は、仕事で使用しているカスタムボードのバイナリを動作させることだが、ベースシステムのバイナリを動作させられたという重要なマイルストーンを達成したので GitHub にアップしておく。

github.com

U-Boot (aarch64) on Qemu を動作させたい (8)

ハングの原因を追いかけると、リロケーション後に特定のメモリ領域 (キャッシュ関連) に値が入っていないため、例外が発行されていることがわかった。

U-Boot 的には final_mmu_setup() > enable_caches() 内の asm volatile ("msr tcr_el3, %0" : : "r" (tcr) : "memory") で例外が発生している。

Qemu 的には、 get_phys_addr_lpae() でエラーが発生している。

get_phys_addr_lpae() では、用途はわからないが特定のアドレス領域を読んでいて、そこから CPU の持つ機能を判断しているように見えたけど、そこで領域が 0 クリア状態のためエラーに流れていた。

さらなる調査のために、 gdb でメモリアドレスを監視する手段を確認すると、 watch コマンドで監視できることがわかった。

### アドレスは uint64_t として読み出されているため、監視する型を uint64_t にする
(gdb) watch *((uint64_t *)0x7fffd93f0000)
Hardware watchpoint 3: *((uint64_t*)0x7fffd93f0000)
(gdb) c
Continuing.


Hardware watchpoint 3: *((uint64_t*)0x7fffd93f0000)

Old value = 0
New value = 1006571523
0x00007fffdd42a8d0 in code_gen_buffer ()

調べを進めると、 PTE とプログラムの動きがあっていないような感じがする。ここからどう追うか。。