MicroBlaze linux xmd でデバッグ tips

kernel の本家にマージされる前の MicroBlaze linux を作るのはけっこうステップが多くて面倒くさかった記憶があるが、Xilinx の git サーバで公開されている kernel と dts を組み合わせると驚くほど手間をかけずにとりあえず MicroBlazelinux が起動するようになった。気がする。
以前の kernel では platform_device というデータを arch/ 以下に作ってやり、レジスタのアドレスや割り込み番号などを格納して、それをドライバから使っていたが、dts を使うと of_platform という別のインターフェース(というかデータベースみたいなもの)を使ってリソースを取得するよう、ドライバの作りが変わってきている。ようだ。
しかし、適当なドライバを書いていると kernel panic が起きることは起きるので、xmd を使ったデバッグの tips をメモしておく。

デバッグには、MicroBlaze の仕様書と、xmd の使い方マニュアルを用意しておくと良い。

問題は I2C を使うドライバを書いてる時におきた。I2C コアのレジスタ空間を ioremap() してレジスタに値を書こうとしたところでエラーが起きた、という問題だ。
kernel 起動時のログ:

 [logi_touch_init()]: logi_touch test loaded
logi_touch_driver registed
 [logi_touch_probe()]: map 0x50006000 -> 0xC201C000
Data bus error exception in kernel mode.
Oops: bus exception, sig: 7
 Registers dump: mode=11021D10
 r1=00000000, r2=00000000, r3=00000001, r4=FFFFFFFF
 r5=C201C000, r6=C01DE294, r7=00000001, r8=00000A02
 r9=C025DC50, r10=00000A02, r11=00000008, r12=FFFFFFFB
 r13=00000000, r14=C0007564, r15=C0254BCC, r16=00000000
 r17=C0254BDC, r18=00000000, r19=C024194C, r20=00000000
 r21=00000000, r22=C201C000, r23=C0241908, r24=C01711A4
 r25=C0241954, r26=00000000, r27=C02402F4, r28=00000000
 r29=00000000, r30=00000000, r31=C101FC60, rPC=C0254BDC
 msr=000046A2, ear=FFFFFFED, esr=C0241908, fsr=C01711A4
Kernel panic - not syncing: Attempted to kill init!

起動ログにレジスタの値がダンプされているが、いまいちアテにならないことが後で分かったので、自分で直接調べる方法を書く。

まずは xmd から break point を設定して、エラーの起きる直前で MicroBlaze を止める。そのために、objdump で vmlinux をディスアセンブルし、該当するコードをアセンブラで見て、break point を設定したい番地を特定する必要がある。

hal@heavens-door:~/microBlaze/new_lc3/linux-2.6-xlnx
$ mb-linux-objdump -d vmlinux > /tmp/vm

問題の起きる関数は printk() でデバッグメッセージを入れるなどして、だいたい目星がついていたので、該当の関数のアセンブラをエディタで確認してみる:

c0254b8c :
c0254b8c:   b0005000    imm 20480
c0254b90:   30a06000    addik   r5, r0, 24576
c0254b94:   3021ffe0    addik   r1, r1, -32
c0254b98:   f9e10000    swi r15, r1, 0
c0254b9c:   fac1001c    swi r22, r1, 28
c0254ba0:   b000ffdb    imm -37
c0254ba4:   b9f42940    brlid   r15, 10560  // c00074e4 
c0254ba8:   30c00004    addik   r6, r0, 4
c0254bac:   12c30000    addk    r22, r3, r0
c0254bb0:   b000c01e    imm -16354
c0254bb4:   30c0b2dc    addik   r6, r0, -19748
c0254bb8:   b0005000    imm 20480
c0254bbc:   30e06000    addik   r7, r0, 24576
c0254bc0:   b000c01e    imm -16354
c0254bc4:   30a0b25c    addik   r5, r0, -19876
c0254bc8:   b000ffdb    imm -37
c0254bcc:   b9f4e894    brlid   r15, -5996  // c0013460 
c0254bd0:   11030000    addk    r8, r3, r0
c0254bd4:   10b60000    addk    r5, r22, r0
c0254bd8:   30600001    addik   r3, r0, 1
c0254bdc:   f8760000    swi r3, r22, 0
c0254be0:   b000ffdb    imm -37
c0254be4:   b9f428ac    brlid   r15, 10412  // c0007490 

0xc0254bdc 番地の swi 命令がたぶん、問題を起こしている原因のコードだと思われる。

xmd を起動し、break point を設定する。break point はソフトウェア bp ではなく、ハードウェア bp にしなければならない(けっこう忘れがちで、ハマる)。

$ xmd
XMD % connect mb mdm
XMD % stop
XMD % bps 0xc0254bd8 hw

XMD% bpl
0: HW BP: Address = 0xc0254bd8

bpl で設定した break point を確認出来る。ちなみに break point を消すには bpr all で良い。

break point を設定したので linux を起動してみる。

XMD % dow kernel

XMD% run

RUNNING> XMD%
XMD%

break したので、レジスタを見てみる。rrd と srrd でレジスタをダンプすることが出来る。

XMD% rrd
    r0: 00000000      r8: 00000a02     r16: 00000000     r24: c01711a4
    r1: c1021dc0      r9: c025dc50     r17: c016cb6c     r25: c0241954
    r2: 00000000     r10: 00000a02     r18: 00000000     r26: 00000000
    r3: 00000037     r11: 00000008     r19: c024194c     r27: c02402f4
    r4: ffffffff     r12: fffffffb     r20: 00000000     r28: 00000000
    r5: c201c000     r13: 00000000     r21: 00000000     r29: 00000000
    r6: c01de294     r14: c0013c2c     r22: c201c000     r30: 00000000
    r7: 00000001     r15: c0254bcc     r23: c0241908     r31: c101fc60
    pc: c0254bd8     msr: 000065a2
XMD% srrd
    pc: c0254bd8     msr: 000065a2     ear: c2008008     esr: 00000000
        ....
        ....    

次に stp でステップ実行して、問題を起こす。

XMD% stp
C0254BDC:   F8760000  swi      r3 , r22, 0

XMD% srrd
    pc: c0254bdc     msr: 000065a2     ear: c2008008     esr: 00000000

XMD% stp
      20:   B0001000  imm      4096
      24:   B8080FB0  brai     4016

XMD% srrd
    pc: 00000020     msr: 000046a2     ear: c201c000     esr: 00000c72

これで問題が起きた。pc が変わったのと、esr に値が書かれたことに注目する。
まず、esr ( 例外ステータスレジスタ)を確認する。値は 0x00000c72 ( MicroBlaze は big endian なので注意)なので、MicroBlaze の仕様書を見ながら例外の原因 EC と、例外ステータス ESS を突き止める:
ESS ( 例外ステータス ) => 110001
EC ( 例外の原因 ) => 10010

仕様書によると、例外の原因は データ TLB ミスによる例外であることがわかった。
TLB は変換 Look-Aside バッファの略らしく、データ TLB ミスとは、ようするに仮想アドレスを変換するための、変換用のテーブル(エントリというらしい)が無い、ということのようだ。

とりあえず xmd でのデバッグは、ここまで。
TLB を作るためのデータが多分カーネルに足りないか、与えてるつもりでもうまく与えられていないのだと思うが、あとは linux の話だ。

2/3 追記
TLB ミスによる例外が起きるのは正しいことが分かった。kernel はこのあと、MicroBlaze の UTLB に I2C レジスタ空間へアクセスするための情報を登録し、MicroBlaze は再度、例外の起きた swi 命令まで PC を戻して、swi 命令を実行する。
問題は UTLB に情報を登録したにも関わらず、2回目の swi 命令でバスエラーが起きることである。
この問題は暇な時にでも改めて調査しようと思う。