目的は、Linux カーネルの直接ブート と (カーネルにドライバを入れることでの)カーネルのメッセージの取得。
そのための機能は、一応仕込んだ。これからテストしてみようと思う。
プログラム:
SDRAM へのプログラム(ファイル) のロード:
オリジナルは、1 回の転送サイズ (MAX_LOAD_SIZE) を 12KB に制限していて とても遅いので、とりあえず 256KB に変更。
サンプルとして DINGUX の dual_boot インストーラの zImage を ロードしてみた。
ちゃんと ロードできたかどうかは、SDRAM のメモリをダンプする mread というコマンドを作成し xburst_stage2 にもプリミティブを作ってテストした。ちなみに ファイルに書き込む mdump というコマンドも作る予定だが、まだ作っていない。
usbboot :> load
Usage: load (1) (2) (3)
1:SDRAM start address
2:image file name
3:device index number
usbboot :> load 0x80600000 zImage.bin 0
start:::::: 0x80600000
Total size to send in byte is :3256320
Loading data to SDRAM :
CPU data: Boot4750
Load last address at 0x80600000
0x80640000
CPU data: Boot4750
Load last address at 0x80640000
CPU data: Boot4750
Load last address at 0x80680000
CPU data: Boot4750
Load last address at 0x806c0000
:
CPU data: Boot4750
Load last address at 0x80900000
usbboot :> mread
Usage: mread (1) (2)
1:start address
2:length in byte
usbboot :> mread 0x80600000 256
CPU data: Boot4750
Reading from 0x80600000 length 0x100....
0x80600000 : 21 80 80 00 21 88 a0 00 21 90 c0 00 21 98 e0 00
0x80600010 : 92 80 04 3c 00 b0 84 24 92 80 05 3c 40 54 a5 24
0x80600020 : 00 00 80 ac fe ff a4 14 04 00 84 24 92 80 1d 3c
0x80600030 : 40 54 bd 27 60 80 04 3c 20 40 84 24 31 00 05 3c
0x80600040 : 20 6f a5 34 01 80 06 3c 60 80 1f 3c 60 00 ff 27
0x80600050 : 60 80 1a 3c 98 19 5a 27 08 00 40 03 00 00 00 00
0x80600060 : 21 20 00 02 21 28 20 02 21 30 40 02 21 38 60 02
0x80600070 : 18 80 1a 3c 10 0b 5a 37 08 00 40 03 00 00 00 00
0x80600080 : 08 00 00 10 60 80 08 3c 9c 00 08 25 00 a0 09 3c
0x80600090 : 25 40 09 01 08 00 00 01 00 00 00 00 00 80 1a 3c
0x806000a0 : 00 80 1b 3c 00 40 7b 37 80 ff 7b 27 00 00 41 bf
0x806000b0 : 20 00 41 bf 40 00 41 bf 60 00 41 bf 00 00 40 bf
0x806000c0 : 20 00 40 bf 40 00 40 bf 60 00 40 bf f7 ff 5b 17
0x806000d0 : 80 00 5a 27 60 80 08 3c e4 00 08 25 08 00 00 01
0x806000e0 : 00 00 00 00 08 00 e0 03 00 00 00 00 00 00 00 00
0x806000f0 : 08 00 e0 03 00 00 00 00 08 00 e0 03 00 00 00 00
usbboot :>
ロードしたプログラムの実行:
オリジナルでも go コマンドがある。これは引数なしで、アドレスを call する。
Linux の boot を考えると 引数 と コマンドラインが必要。不定値では都合が悪い。go コマンドを改造し コマンドラインの設定と 引数として渡すようにした。
詳しく書いておくと コマンドラインを含む通信エリアというのを定義していて、通信エリアの先頭が argv になるようにしている。
プログラムが 対応していれば、USBBOOT のプロトコルを使って メッセージを返せるようにしている。
これをテストするプログラムを xburst_stage1/2 を参考に作ることにした。
target.ld:
これはプログラムの アドレスを決めるもの ... と思って良い。
stage1/2 の違いをみると MEMORY のところだけなので、開始アドレスを 0x80600000 にした。サイズは適当で良さそうだが、最低 16MB の RAM があり後ろ 4MB に stage2 があるとして 6MB にした。
UTPUT_ARCH(mips)
ENTRY(_start)
MEMORY
{
ram : ORIGIN = 0x80600000 , LENGTH = 6M
}
(後略)
head.S:
これはスタートアップを定義するもの。c_main に jump するのだが、c_main が return すれば、USBBOOT に制御が戻るはず。とりあえずは、stack を設定しないバージョン。
.globl _start
.set noreorder
_start:
#if 0
/*
* setup stack, jump to C code
*/
la $29, 0x80bffff0 /* sp */
#endif
j c_main
nop
ビルド:
xburst_stage1/2 の Makefile を変更しただけ。こんな風にビルドしている。
ipsel-linux-gcc -O2 -fno-unit-at-a-time -fno-zero-initialized-in-bss -mips32 -fno-pic -mno-abicalls -c main.c -o main.o
mipsel-linux-ld -nostdlib -EL -T target.ld head.o main.o msg.o -o xburst_hello.elf
mipsel-linux-objcopy -O binary xburst_hello.elf xburst_hello.bin
mipsel-linux-objdump -D xburst_hello.elf < xburst_hello.dump
mipsel-linux-objdump -h xburst_hello.elf < xburst_hello.map
mipsel-linux-nm -n xburst_hello.elf < System.map
確認:
objdump を使って xburst_hello.elf を見てみた。
mipsel-linux-objdump -d xburst_hello.elf
Disassembly of section .text:
80600000 <_start>:
80600000: 04 00 18 08 00 00 00 00 00 00 00 00 00 00 00 00 ............
....
80600010 <c_main>:
80600010: 0818003f j 806000fc
80600014: 00a02021 move a0,a1
...
806000fc <msg_init>:
806000fc: 3c028060 lui v0,0x8060
80600100: 03e00008 jr ra
80600104: ac4401a0 sw a0,416(v0)
ちゃんとロードアドレスが、0x80600000 になっていて、その先頭が entry になっている。
_start が ディスアセンブルされていないが、
.type _start, @function
:
.end _start
を入れることで ディスアセンブルされるようになる。
それはともかく、これが動くかというと ... 制御が渡った後に戻ってこない。
usbboot :> go
Usage: go (1) (2) (3)
1:start SDRAM address
2:device index number
3:command_line
usbboot :> go 0x80600000 0
CPU data: Boot4750
Load last address at 0x81d08130
Executing No.0 device at address 0x80600000
usbboot :>
usbboot :> boot
Error - can't retrieve XBurst CPU information: -19
ほとんどなにもしていないのにもかかわらず戻ってこない。.. メモリに書き込んでいるぐらい。
次のステップとして
- メッセージを追加しての USBBOOT への return 。
- ポーリングすることで、メッセージを出力。
を考えているのだが ...
原因を調べて直さないと次のステップに行けない。
それはともかく ... こういう風に USBBOOT を使って任意のプログラムを動かせるようになれば、ちょっと大規模な マイクロコントローラのような使い方もできるようになる。ライブラリがないのがつらいところだが、Cortex-M3 とか SH-2A とかのボードを使う感覚でプログラムを作ってみるのも、面白いかもしれない。
なにしろ、LCD や サウンド、microSD(or SD) 、バッテリーが最初から付いているのだ。メモリも最低クラスが 16MB -- 十分ではないか。
もうひとつ嬉しいのは、ボタンとか LCD などデバイスの使い方をさがすのに USBBOOT の拡張に頼らなくて済むという点。プログラムを書いて動かしてみれば良い。ちゃんと return してくるなら reboot もしなくて済む。
このあたりの目的には ロード + 実行のコマンドを作って使いやすくしておきたいところ。検討しておこう。
プログラム:
- WK6: usbboot-wk6.tar.gz
動かない原因を調べるために。stage2 で 実際に実行しているところを コメント化して 実行直前の データを取ることにした 。
ところがこれも動かない。いくつかの処理を call しているので どれが関係するのか調べていったところ ...
jz_writeb(USB_REG_POWER,0x0); // High speed
このコードを入れると動かなくなることが分かった。
UDC (USB Device Controller) の Power Manegement レジスタだそうだが、何をしようとしているか不明。
したいことは、UDC の状態を変更せず 戻れることだから、不要そうではある。
Execute program at 0x80600000
Command line: aaa bbb ccc ddd eee fff
さて、stage2 での "go" 直前の状態。-- アドレスと Command line の設定はうまく行っている。
これで 実際に実行するように変更する。そして stage2 内に仕込んだ hello() を実行してみる。
32MB だと stage2 の先頭アドレスは 0x81c00000 になる。hello のアドレスを mipsel-linux-nm で調べてみると 0x80001610 。
go 0x81c01610 0
とすると
Execute program at 0x81C01610
Command line:
hello world
なんて表示された。今度は 実際のプログラム... の前に バイナリの作成について ..
stage1 は、作成時に決めた固定のアドレスにロードするが、stage2 はリロケータブル。リンクのオプションは同じだが、target.ld が違う。
stage1:
_gp = ABSOLUTE(.);
stage2:
_gp = ALIGN(16);
_got_end = ABSOLUTE(.);
の違いがある。で、stage2 では、c_main でなにかおまじないをしている。
リロケータブルなバイナリは作ってみたいとは思うが最初はパスして、stage1 流で行く。
usbboot :> msg
usbboot :> load 0x80600000 hello.bin
usbboot :> go 0x80600000 0
Execute program at 0x80600000
Command line:
comm_area addr:0x81D070E0
proc_func addr:0x30783009
XXXXXXX hello world XXXXXXX
argc = 0x00000002
argv[1] :
さて、hello.bin を動かしていて、気がついたことがある。
ロードしなおしても前のプログラムが動いてしまうのだ。
原因は、icache の flush 忘れ。もともと 2 回以上実行することを想定していないのかも知れない。
とりあえず。usb_main の先頭と同様に __icache_invalidate_all() を入れることで 解決した。
次の課題は、hello.bin からのポーリング。果たして動くのか ...
というと動かなかった。
リロケータブルなバイナリの call はみな t9 にアドレスを入れてそこに call している。
806000a0 <msg_proc>:
806000a0: 3c028060 lui v0,0x8060
806000a4: 8c420290 lw v0,656(v0)
806000a8: 10400006 beqz v0,806000c4 <msg_proc+0x24>
806000ac: 00000000 nop
806000b0: 8c590010 lw t9,16(v0)
806000b4: 13200003 beqz t9,806000c4 <msg_proc+0x24>
806000b8: 00000000 nop
806000bc: 03200008 jr t9
806000c0: 00000000 nop
806000c4: 03e00008 jr ra
806000c8: 00000000 nop
この msg_proc は、関数ポインタとして 登録されている udc4740Proc() を call するもの。
関数ポインタを使った call は t9 を使って call することはクリアしている。
うーん何が悪いのだろうか?
Execute program at 0x80600000
Command line:
comm_area addr:0x81D070F0
proc_func addr:0x47200A0D
XXXXXXX hello world XXXXXXX
argc = 0x00000002
argv :0x81D070F0
proc_func addr :0x47200A0D
関数ポインタの値を表示したところ、答えが出ていた。不定値が入っている。よくよく見れば。関数ポインタに代入していなかった。
これで、hello.bin の中から 10 回 udcProc を call して return しても正しく動くようになった。
これで hello world は、一旦の完成だが、 Jz47xx で動かした プログラムについてのメモを載せる予定。
一応 Linux を移植しようと思っているので、この状態で実用的なものを 自作しようとはお持っていない。基本的に テストプログラムを走らせるつもり。
ただテストといっても いろいろ考えられる。装置の解析が第一としても、XMU を使ったプログラムを動かして動作を確認するとか、オーバクロックしてどこまで動くか調べるとか 。
プログラム:
- WK7: usbboot-wk7.tar.gz
テストプログラムの追加。tests に 01_hello / 02_backlight / 03_portout を作成した。
01_hello は今まで作ってきた hello world 。
02_backlight は、PWM のポート に対して 出力に設定するもの H と L が出力できる。( パラメータで指定 )
03_portout は、02_backlight を汎用化したもの。PA0 〜 PD31 までの範囲の ポートに対して 出力 (H/L) と 入力(Z) を設定できる。 - WK8: usbboot-wk8.tar.gz
ECC の仕組みが多少分かったので、cfg を修正。あと、32M/64M の改造をしたのでそれ用の cfg も追加。
周波数を確認するために、tests/04_hz を追加。portout ベース。
228MHz で動作している場合、port を 1Hz の周期でブリンクする(はず)。 - WK10: usbboot-wk10.tar.gz
tests/05_memtest/ -- メモリテストのプログラムを入れた。- SDRAM のデータを ファイルに書き込む mdump を作った。
メモリがちゃんと読み書きできるかのテスト。0x8001000 から stage2 まで (END - 4MB) の領域のうち、プログラム用のエリア (0x80600000 - 6MB) を除いた部分をテストする 。 - SDRAM のデータを ファイルに書き込む mdump を作った。
- WK11: usbboot-wk11.tar.gz
A-41 の LCD にアクセスするプログラムをいれた.