U-Boot (aarch64) on Qemu を動作させたい (10)
ほそぼそと開発を続けていたのですが、ようやく 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エミュレーションのコードを見ると、すべてのデバイスで CRC が crc = 0 /* FIXME */
とかになっているので、真面目に実装しようかなと思って調べた情報を記載しておく。
全体の仕様は、
https://www.sdcard.org/cht/developers/overview/sdio/sdio_spec/Simplified_SDIO_Card_Spec.pdf
を見れば良くて、細かな実装は、
を参考にできそう。
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)
結論からいうと、公式の 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 にアップしておく。
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 を確認すると、 DDR の SPD アクセスに使用していることがわかった。
DDR の情報が取得できないので、 Abort につながっているようだ。