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

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

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 とプログラムの動きがあっていないような感じがする。ここからどう追うか。。

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

なんとか、 DDR の SDP も実装し、 board_f.c の init_sequence_f の初期化シーケンスについては抜けられるようになった。

だが、どこかでハングが発生して、プロンプトまでは到達していない。

u-boot の処理では、プログラムのリロケーションが実行されており、ブレイクポイントは張れなくなっている。

調べると、 gdb ではリロケーション後のシンボル読み込みにも対応していることがわかった。

### リロケーション後のアドレスを確認する
(gdb) p/x gd->relocaddr
$1 = 0xffcfb000

### 現在のシンボルの破棄
(gdb) symbol-file
Discard symbol table from `/path/to/u-boot-qoriq-2016.09+fslgit-r0/build_ls1046ardb_emmc/u-boot'? (y or n) y
No symbol file now.

### リロケーション後アドレスにシンボルを読み込み
(gdb) add-symbol-file u-boot 0xffcfb000
add symbol table from file "u-boot" at
        .text_addr = 0xffcfb000
(y or n) y
Reading symbols from u-boot...done.

### ブレイクポイントの貼り直し
(gdb) b ../arch/arm/lib/crt0_64.S:110
Breakpoint 6 at 0xffd00c90: file ../arch/arm/lib/crt0_64.S, line 110.

### ブレイクポイントの動作確認
(gdb) c
Continuing.

Breakpoint 6, _main () at ../arch/arm/lib/crt0_64.S:110
110             bl      c_runtime_cpu_setup             /* still call old routine */

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

現在は、仕事で触れている freescale の ls1046a Reference Design Board の U-Boot を Qemu で動作させるべく、仮想ボードを作成中。

CPU, RAM, UART と何とか整えてきて、i2c の調整を行っています。

U-Boot 2016.092.0+ga06b20925c (Oct 22 2017 - 21:04:46 +0900)

SoC:  LS1046AE Rev1.0 (0x87070010)
Clock Configuration:
       CPU0(   ):800  MHz  CPU1(   ):800  MHz  CPU2(   ):800  MHz  
       CPU3(   ):800  MHz  
       Bus:      600  MHz  DDR:      2100 MT/s  FMAN:     800  MHz
Reset Configuration Word (RCW):
       00000000: 0c150010 0e000000 00000000 00000000
       00000010: 11335559 40000012 60040000 c1000000
       00000020: 00000000 00000000 00000000 00238800
       00000030: 20124000 00003000 00000096 00000001
Model: LS1046A RDB Board
Board: LS1046ARDB, boot from Invalid setting of SW5
CPLD:  V0.0
PCBA:  V0.0
SERDES Reference Clocks:
SD1_CLK1 = 100.00MHZ, SD1_CLK2 = 100.00MHZ
I2C:   ready
DRAM:  wait_for_sr_state: failed sr=a1 cr=38 state=202
i2c_init_transfer: failed for chip 0x36 retry=0
wait_for_sr_state: failed sr=a1 cr=38 state=202
i2c_init_transfer: failed for chip 0x36 retry=1
wait_for_sr_state: failed sr=a1 cr=38 state=202
i2c_init_transfer: failed for chip 0x36 retry=2
i2c_init_transfer: give up i2c_regs=0x2180000
wait_for_sr_state: failed sr=a1 cr=38 state=202
i2c_init_transfer: failed for chip 0x51 retry=0
wait_for_sr_state: failed sr=a1 cr=38 state=202
i2c_init_transfer: failed for chip 0x51 retry=1
wait_for_sr_state: failed sr=a1 cr=38 state=202
i2c_init_transfer: failed for chip 0x51 retry=2
i2c_init_transfer: give up i2c_regs=0x2180000
DDR: failed to read SPD from address 81
Error: No valid SPD detected.
16 EiB (DDR not enabled)

"Synchronous Abort" handler, esr 0x96000045
ELR:     fff638e8
LR:      ffefd214
x0 : 00000007ffe00000 x1 : 0000000000000000
x2 : 0000000000001000 x3 : 00000007ffe01000
x4 : 00000007ffe00000 x5 : 0000000000000000
x6 : 00000000000001e8 x7 : 0000000000000001
x8 : 0000000082070550 x9 : 000000007defb000
x10: 0000000000000403 x11: 0000000082059274
x12: 0000000000000002 x13: 0000000000000000
x14: 0000000000000000 x15: 0000000082003228
x16: 0000000000000000 x17: 0000000000000000
x18: 00000000ffdf8d70 x19: 00000007ffe00000
x20: 00000007ffe00000 x21: 0000000000000000
x22: 00000000fff89490 x23: 0000000000000000
x24: 0000000000000000 x25: 0000000000000000
x26: 0000000000000000 x27: 0000000000000000
x28: 0000000082001cd8 x29: 00000000ffdf6680

Resetting CPU ...

resetting ...
guts: Unknown register read: b0
guts: Unknown register write: b0 = 2

最終的には Abort してしまっているけど、原因がどこかは現時点では未調査。

タイムアウトの原因を GDB で追いかけると、 i2c_init_transfer_() から tx_byte() に入り、 I2DR にスレーブアドレスを書き込んだあとに、 I2SR_IIF ビット (割り込み) が立たないため、タイムアウトが発生していた。

Qemu 側も GDB で追いかけると、imx_i2c.c から i2c/core.c の i2c_start_transfer() を呼び出したところで QTAILQ_FOREACH() マクロの処理が行われていないため、 I2SR_IIF ビットを立てる処理が実施されていないことがわかった。

QTAILQ_FOREACH() マクロは、 &bus->qbus.children.tqh_first にデータがある場合に実行される処理で、タイムアウトの場合は NULL となっていたため、 bus->current_devs.lh_first も空のままになり、下の QLIST_EMPTY で return 1 となっていた。

    if (QLIST_EMPTY(&bus->current_devs)) {
        QTAILQ_FOREACH(kid, &bus->qbus.children, sibling) {
            DeviceState *qdev = kid->child;
            I2CSlave *candidate = I2C_SLAVE(qdev);
            if ((candidate->address == address) || (bus->broadcast)) {
                node = g_malloc(sizeof(struct I2CNode));
                node->elt = candidate;
                QLIST_INSERT_HEAD(&bus->current_devs, node, next);
                if (!bus->broadcast) {
                    break;
                }
            }
        }
        bus_scanned = true;
    }

    if (QLIST_EMPTY(&bus->current_devs)) {
        return 1;
    }

bus->qbus.children.tqh_first にデータが入る条件を調べると、 qdev_set_parent_bus() が呼ばれた場合であることがわかった。

qdev_set_parent_bus() は、システムバズに仮想デバイスをつなぐ場合に呼び出している処理で、特定のバスに仮想デバイスを接続する機能を持っている。

ということは、現在は i2c バスにデバイスが接続されていないから、スレーブアドレスの書き込みでタイムアウトになっている、ということ?

backtrace を確認すると、 DDRSPD アクセスに使用していることがわかった。

DDR の情報が取得できないので、 Abort につながっているようだ。