Raspberry Pi 4のUART出力のデバッグ
やること
現状の整理
QEMUでRPi4の環境構築
UART初期化処理の追加
実機検証
現状の整理
使用しているのはPL011
U-Bootやファームウェアの出力があるためUART出力自体は出来ている
U-Bootへの戻り値でどこまで到達したか検証が出来るという仕組みになっていて、DTBを読んでシリアルデバイスを見つけるところまで到達したところまでは到達していることが分かっている
つまりデバイスのアドレスに書き込んでも文字が出ないという状態
デバイスの初期化をしていないのが原因の可能性
QEMUでRPi4の環境構築
RPi4上の環境をQEMUで再現するようにしたい
QEMUのvirtマシン向けのコードはコメントアウトしていて/* QEMU: 0 */みたいなコメントになっている
virt向けでも動作検証ができるようにビルドの設定で変えられるようにしたい
変えられるようにしたけれど、引数で4とエラーが出てU-Bootに戻った
普通に引数の値を戻すとLinuxのブートまで行ってしまったが、ビルドのスクリプトの方を変えていないのかな
BuildじゃなくてRunの方で、QEMUでRPi4のマシンをエミュレートする方のRunスクリプトを作る必要がある
現在インストールされているのだとRPi4をQEMUがサポートしてないのでビルドをする
Runコマンド
% cargo run --config 'target.aarch64-unknown-none-softfloat.runner="tools/run_qemu_rpi4.sh"'
U-Bootのプロンプトでコマンドを打ち込むと失敗する
code:_
U-Boot> if fatload mmc 0:1 $kernel_addr_r min1.elf; then
bootelf $kernel_addr_r
else
echo "Unable to read min1.elf"
fi
Card did not respond to voltage select! : -110
** Bad device specification mmc 0 **
Couldn't find partition mmc 0:1
Can't set block device
Unable to read min1.elf
パーテイションが1だったので以下のようにする
code:_
setenv autostart yes
if fatload mmc 1:1 $kernel_addr_r min1.elf; then
bootelf $kernel_addr_r
else
echo "Unable to read min1.elf"
fi
ファイルが増えたので適切にignoreしてコミットしてもらう
./rpi4/create_image.shと./tools/launch_qemu_rpi4.shが連携するようにしたい
QEMUのRPi4では標準出力が無事出力されることが判明した
code:_
U-Boot> go 0x40417918 "0x3bb206f0"
## Starting application at 0x40417918 ...
INFO main Hello from main!
INFO main CurrentEL: 2
INFO setup_memory RAM is 0x0 ~ 0x3C000000
INFO setup_memory DTB is 0x3BB206F0 ~ 0x3BB40DBE
あとは起動スクリプトを打ち込むのが面倒なので自動化したい
ホストのコマンドでreadelf -h ./rpi4/min1.elfしてエントリーポイントを探して、U-Bootでecho $fdt_addrしてgoコマンドの引数に文字列として渡すのが大変すぎる
QEMUでは./scripts/boot.txtをboot.scrにしてコマンドを渡していた覚えがあるのでそれに準拠する
もとの./scripts/boot.txtを見るに、起動アドレスが$kernel_addr_rなのでリンカスクリプトをRPi4向けにしたらreadelfの結果を抽出するみたいなことをしなくてもいいという話がある
OpenCodeを使ってみる
もとの./scripts/boot.txtを見るに、起動アドレスが$kernel_addr_rなのでリンカスクリプトをRPi4向けにする、というのを忘れている可能性がある
QEMUとRPi4のアドレスの違いを見た方が良いですね
QEMUのU-Bootだとこうなる
code:_
=> echo $kernel_addr_r
0x40400000
RPi4だとこう
code:_
U-Boot> echo $kernel_addr_r
0x00080000
リンカスクリプトはこれに対応して書き直した
boot.txtのbootelfがどうもうまく行かないのでちょっとしたシェル芸をしてgoコマンドにelfのエントリポイントを埋め込みます
起動は安定して成功するようになった
code:_
U-Boot 2026.07-rc3-00014-g30b77f6aa146 (May 31 2026 - 11:15:28 +0900)
DRAM: 960 MiB
RPI 4 Model B (0xb03115)
Core: 212 devices, 14 uclasses, devicetree: board
MMC: mmcnr@7e300000: 1, mmc@7e340000: 0
Loading Environment from FAT... Card did not respond to voltage select! : -110
** Bad device specification mmc 0 **
In: serial,usbkbd
Out: serial,vidconsole
Err: serial,vidconsole
Net: No ethernet found.
starting USB...
No USB controllers found
scanning usb for storage devices... 0 Storage Device(s) found
Hit any key to stop autoboot: 0
Card did not respond to voltage select! : -110
Cannot persist EFI variables without system partition
Missing RNG device for EFI_RNG_PROTOCOL
** Booting bootflow '<NULL>' with efi_mgr
Loading Boot0000 'mmc 1' failed
EFI boot manager: Cannot load any image
Boot failed (err=-14)
** Booting bootflow 'mmcnr@7e300000.bootdev.part_1' with script
163784 bytes read in 92 ms (1.7 MiB/s)
## Starting application at 0x00082EE8 ...
INFO main Hello from main!
INFO main CurrentEL: 2
INFO setup_memory RAM is 0x0 ~ 0x3C000000
INFO setup_memory DTB is 0x3BB206F0 ~ 0x3BB40DBE
INFO setup_memory ELF is 0x80000 ~ 0x80238
INFO setup_memory ELF is 0x81000 ~ 0x93950
INFO setup_memory ELF is 0x94000 ~ 0x94001
INFO setup_memory ELF is 0x95000 ~ 0x9940C
INFO setup_memory ELF is 0x9A000 ~ 0x9B000
INFO setup_memory Reserve 0x3BB10000 ~ 0x3BB20000 for Stack
INFO setup_memory Initialize heap 0x9B000 ~ 0x3BB10000
次に、これで実装は問題ないことが確認できたはずなので、実機で動かしたら、やはりUART出力が出なかった
確認したこと
QEMUが1:1に対しパーティション0:1を使用すること
PL011がDTBで見つかっているところまで到達していることを戻り値で確認
怪しいのはスタックポインタの設定と、PL011の初期化
実機で動かしているコードと出力
code:rs
pub extern "C" fn main(argc: usize, argv: *const *const u8) -> usize {
let stack_pointer = crate::arch::aarch64::get_stack_pointer() as usize;
if argc != ARGC {
return 1;
}
let args = unsafe { slice::from_raw_parts(argv, argc) };
let Ok(dtb_addr_str) = unsafe { CStr::from_ptr(argsDTB_ARG_INDEX) }.to_str() else {
return 2;
};
let Some(dtb_address) = str_to_usize(dtb_addr_str) else {
return 3;
};
let Ok(dtb) = dtb::Dtb::new(dtb_address) else {
return 4;
};
if let Err(e) = init_pl011_serial_port(&dtb) {
return e;
};
return 8;
code:_
** Booting bootflow 'mmc@7e340000.bootdev.part_1' with script
38472 bytes read in 25 ms (1.5 MiB/s)
## Starting application at 0x000813B8 ...
## Application terminated, rc = 0x8
Boot failed (err=-14)
戻り値の8は自分の書いたコード由来なのは値や引数の数を変えて確認済み
err=-14はQEMUで失敗したときもそう出る
return 8は意図的に最初のprintln!マクロの前に配置していて、なぜなら最初のprintln!出力はできないし、その後の戻り値も捕捉できない(暴走か無限ループみたいな状態になっているかもしれない)
スタックポインタは戻り値が意図した値の時点で問題なさそう(それまでにDTB解析など何個も関数呼び出しがあるので)
ベースアドレスはしっかり取れている
code:_
## Starting application at 0x000813B8 ...
## Application terminated, rc = 0xFE201000
Boot failed (err=-14)
putcの検証
code:rs
use crate::serial::SerialDevice;
let r = PL011_DEVICE.lock().putc(b'H');
return if r.is_ok() { 0x10 } else { 0x20 };
見落としじゃなければ何も出ていないが、戻り値としては成功している
code:_
** Booting bootflow 'mmc@7e340000.bootdev.part_1' with script
38472 bytes read in 25 ms (1.5 MiB/s)
## Starting application at 0x000813B8 ...
## Application terminated, rc = 0x10
BCM2711 の PL011 は 32-bit アクセスが必要か?いまドライバを16bitで書いていたのだが移植でどうしても出力されなくてこれを疑っている
Claude.icon 結論から言うと、BCM2711のペリフェラルレジスタ(PL011を含む)は基本的に32-bitアクセスを前提として設計されており、16-bitアクセスのみで実装すると「設定が反映されない/出力されない」という現象が起こり得ます
32bitアクセスにして、QEMUで動作は確認したが、実機ではまだ出ない
Claude.icon もしUARTCLK=0のままIBRD/FBRDを(想定クロックで)計算して書き込んでいる場合、CR.TXE/RXEは正しく立っていてDRへの書き込み(FIFOへのpush)自体は成功する(=putcがOkを返す)一方、ボーレートジェネレータ/シフトレジスタが動作しないため、TX線にビット列が一切出てこない、という挙動になり得ます。これは「putcはOkを返すが何も出ない」という今回の症状とよく一致します。確認方法としては、Mailbox Property Interfaceの「Get Clock Rate」タグ(tag=0x00030002, clock id=2=UART)で現在のUARTCLKを取得し、0でないこと・期待値(多くの場合48MHz)であることを確認するのがよいと思います。必要であれば「Set Clock Rate」(tag=0x00038002)で明示的に設定してください。
クロックレート取得
code:QEMU
** Booting bootflow 'mmcnr@7e300000.bootdev.part_1' with script
38576 bytes read in 48 ms (784.2 KiB/s)
## Starting application at 0x00081460 ...
## Application terminated, rc = 0x2DC6C0
Boot failed (err=-14)
code:RPi4
** Booting bootflow 'mmc@7e340000.bootdev.part_1' with script
38576 bytes read in 25 ms (1.5 MiB/s)
## Starting application at 0x00081460 ...
## Application terminated, rc = 0xFFFFFFFF
Boot failed (err=-14)
config.txtを見ると、dtoverlay=disable-btが重複しているが、これが問題をきたす可能性は低いと考えられる
UARTのibrdとfbrdの設定を間違えている可能性はあって、115200に合わせる場合はどうするんでしたっけ?
Claude.icon RaspberryPi4のファームウェアはUARTCLKを48MHz固定にします(コアクロックのスケーリングに関係なく一定)。これを前提に115200bpsを計算すると、IBRD = 26、FBRD = round(0.0417 × 64) = 3となる
これまで、ibrd/fbrdが、3/0xbに設定されていて(RPi4のQEMUが3MHzだからかな、にしても逆だけど)、これに対しては出るはずがないと言えるのかな
U-Bootからgoコマンドで起動する場合、環境が大幅に変わっていることは考えづらいため、U-Bootのコマンドでメモリや使用しているデバイス、GPIOなどの情報を取得してみるといいという話がある
https://gyazo.com/0c66959b78a74baf7f0b29792dcba33c
おお
なんかpanicしているけど遂に自分の書いたプログラムの文字が出たぞ
mailboxは値を正しく取れていないので一旦無効にしておこう
あとは問題の切り分けをする
U-BootでGPIOとボーレートの二つを見てどちらが問題なのか判断する
まずはPL011のコントロールレジスタ
code:_
U-Boot> md.l fe201024 4
fe201024: 0000001a 00000003 00000070 00000301 ........p.......
// IBRD: 0x1a
// FBRD: 0x3
// LCR_H: 0x70
// CR: 0x301
GPIO
code:_
U-Boot> md.l fe200004 1
fe200004: 00012000
// 0x00012000 = 0b1_0010_0000_0000_0000
// GPIO14: 100
// GPIO15: 100
どっちも合っていてよく分からん
実はGPIOが間違え
片方ずつ該当コードを消して検証
1. baudrateのレジスタ(BRD)書き込みを消す
問題なく起動
2. GPIOの再設定を消す
文字が出なくなった!
これが原因かー
ところで何が起こっているのだろう?
code:_
INFO main gpfsel1_before: 12000
// 01_0010_0000_0000_0000
// ^^ ^^^^
// 010, 010
// U-BootではGPIO14,15はALT5に接続
INFO main gpfsel1_after: 24000
合ってると思いながら一応手動で設定してたところが実は前後で異なっていた
code:_
U-Boot> fdt print /chosen
chosen {
/* snip */
stdout-path = "serial0:115200n8";
U-Boot> fdt print /aliases
aliases {
serial0 = "/soc/serial@7e215040";
serial1 = "/soc/serial@7e201000";
ChatGPT.icon 通常 7e201000 = PL011 UART0, 7e215040 = mini UART (AUX UART) なのでmini UARTがserial0になっているから、GPIO14/15をALT0へ切り替える必要があった
#min1visor
#Raspberry_Pi