2010年10月21日

Linux(r237)のテスト

ベースバージョンをr237に変更したが、そろそろテストを開始しようと思う。

まずは、ブートするまでの流れを おさらい。

  • メモリマップ:

    0x80000000
    割り込みベクタとか
    0x80002000
    (8KB) (1)xburst_stage1
    (stack)
    0x80004000
    0x80010000
    0x80100000 (offset 1MB) :
    (u-boot ) : (4)kernel(vmlinux.bin)
    : + initrd
    0x80600000 (offset 6MB) 0x80600000(max)
    (3)kernel (zImage)

    0x81c00000
    (4MB) (2)xburst_stage2
    (stack)
    0x82000000 (32MB の場合)

  • usbboot で boot コマンドを入力すると、xburst_stage1/2 をロードして 実行する。stage1 はメモリとかクロックとかハードウェアの初期化で、stage2 は、コマンド処理。stage1 は実行後不要になる。
  • kernel (zImage) は、0x80600000 にロードして同じアドレスから実行する。改造した usbboot では コマンドラインも渡せるようにしている。
  • kernel (zImage) は、0x80010000 から 内蔵したイメージを展開していく。イメージは、vmlinux.bin + initrd 。
    パラメータ(コマンドラインのアドレスとか) はセーブしていて、vmlinux.bin を実行するときに渡す。
  • vmlinux.bin の開始アドレスは、kernel_entry (arch/mips/kernel/head.S)
  • kernel_entry は start_kernel(init/main.c) を呼び出す。
  • start_kernel では、かなりの初期化を実行した後(printk すら 使えるようになった後)、setup_arch(arch/mips/kernel/setup.c) を呼び出す。ここから プログラムのメモリ(BSS) 以外のメモリの初期化とかが始まる。

次に、前回 (r108) でどこまで実行できたか

  • zImage をロードして制御を渡す 。OK
  • start_kernel 実行開始 OK 。
     - LCD のバックライトを点灯させることで確認
  • setup_arch 実行開始 NG 。
     - setup_arch まで行かない。


今回は、r237 ベースに変更してしきり直す。

今回からは、バックライトに加えて、赤黄の LED が使える。また、ある程度処理が進めば、
LCD へのメッセージ出力 も可能になる予定。

さて、前回と同じところまで行くものなのか確認 -- したら全然動かない。

困った。全く動かない。

過程を書くとちょっと込み入った話になるので、パスして結果だけ。

  • zImage が カーネル(kernel_entry)に処理を渡すのは確認できた。
  • だが、kernel_entry から startup_kernel に処理がわたらない。
  • 細かく調べると 冒頭の mtc0 が動かないことが判った。

      kernel_entry:
      800144f0: 40086000 mfc0 t0,c0_status
      800144f4: 3c011000 lui at,0x1000
      800144f8: 3421001f ori at,at,0x1f
      800144fc: 01014025 or t0,t0,at
      80014500: 3908001f xori t0,t0,0x1f
      80014504: 40886000 mtc0 t0,c0_status

    • c0_status というのは、カーネルモードとか割り込み禁止とかの CPU の動作モードや 割り込み原因などのステータスが格納されているレジスタ。
      このコードは、それのビットを立てたり落としたりしている。

      c0_status の意味:(一部)
    • bit4 UM
    • bit2 ERL (RESET/NMI 発生時に 1)
    • bit1 EXL (通常例外/割り込み発生時に 1)
        - UM=1 かつ ERL = 0 かつ EXL = 0 のときユーザモード。
    • bit0 IE (1: 割り込み許可 / 0: 割り込み禁止)

  • c0_status になにを設定すると動かないのか細かく調べたところ

    もとのデータは、0x1240CE16 が多いが このうち
    0x4 -- ERL ビットを 0 にすると 止まるか暴走する
    ことが判明。
    ちなみに、暴走した後放置しておくと、フラッシュから立ち上がった。

  • 調べると Reset や NMI で実行開始した場合 ERL bit が 1 になる。
    ERL を 0 にするには、

    (1) ERET で復帰
    (2) プログラムで 0 を書き込む

    の 2 通りがあるらしい。(2) では暴走するのだから (1) の方法を使うのか?
  • 頼りになるものは、u-boot のソースコードぐらいだが ... JzRISC では

    /* CU0=UM=EXL=IE=0, BEV=ERL=1, IP2^7 = 1 */
    li t0,0x0040FC04
    mtc0 t0,CP0_STATUS

    としていた。

ERL=1 だと割り込み許可ができない(or 無効) 。困ってしまったが、最初の割り込み許可までは、動作確認できるはず。
とりあえず
  • 先頭の CP0_STATUS の設定 (arch/mips/kernel/head.S)
  • raw_local_irq_disable (arch/mips/include/asm/irqflags.h)
  • raw_local_save_flags (arch/mips/include/asm/irqflags.h)
  • raw_local_irq_restore (arch/mips/include/asm/irqflags.h)
の 0x1f を 0x1b にすることで 先に進むことにした。

  • 大雑把にいうと動作モードには、ユーザモード/カーネルモードがあるが、カーネルモードにも種類があって、ERL=1 は NMI/Reset での実行状態/EXL=1 は 例外(システムコールとか)・割り込みが起きたときの実行状態/UM=ERL=RXL=0, は通常の実行状態。
    上記の対処は、ERL を変更しないようにしたということ。

こうすることで、setup_arch の直前まで来た。

さて、setup_arch の直前には printk が入っている。これをどう表示させるのだろう?

ちゃんと調べてみると arch/mips/kernel/early_printk.c というのがあってここで定義している。で、call する関数は prom_putchar() 。
そして、prom_putchar() は、(現config では) シリアルを使う版が arch/mips/jz4750l/board-a41.c に記述されていた。

ならば、prom_putchar() を LCD を使うように書き換えてみたところ .... 出た。



最後のメッセージは、NR_IRQS:256 で ここから先は、やはり ERL を 0 にしないと無理そうだ。

それはともかく、いくつか気になるところがある。

  • F4750L とあるが、もう分離しているのだから、A-41 / A-33 に変更しなくては。
  • メモリが 64MB 認識されてしまっている。

    usbboot -f /etc/xbufst-tools/a41-boot64M.cfg

    として起動しているので、64MB の RAM は実際にあるし使える状態。だが、

    usbboot> exec arch/mips/boot/compressed/zImage mem=28M

    として起動しているのだ。( exec は、新規で作成した コマンド )
    mem= を認識させないと stage2 が壊れてしまう。
    ... それにしてもなぜ 64MB と分かるのだろう? トータルのメモリサイズは渡していないのに。

      64MB は、JZ4750L では、ハードコーディングされていた。

      zImage だと 0x80600000 にロードして、先頭から実行すれば良いのだが、別の起動方法もある。
      objcopy で vmlinux を 変換し、0x80010000 にロード。
      実行は先頭ではなく、kernel_entry のアドレスを nm で調べて(いつも同じアドレスだが) そこに go する。具体的には、

      # mipsel-linux-objcopy -O binary vmlinux vmlinux.bin

      usbboot> load 0x80010000 vmlinux.bin 0
      usbboot> go 0x800144f0 0 mem=28M

      ... この方法だとテストプログラムで パラメーターを渡す実績もあるのだが.. linux で認識してくれなかった。... バグっているらしい。

テストその2(続き)


これで、どこまで行くか調べたところ、init_IRQ() (init/main.c) から call される arch_init_irq() (arch/mips/jz4750l/irq.c) までだった。

clear_c0_status(0xff04) /* clear ERL */

というのがあって、ここではじめて ERL を clear しているつもりらしい。意味がないので、0xff00 にしておいた。


    これで次に止まるのは、次のところと分かっている。
  • raw_local_irq_enable (arch/mips/include/asm/irqflags.h)
    -- 0x1f を 0x1b に 0x1e を 0x1a にしておく。


最後に止まるのは、calibrate_delay() -- 実際にタイマ割り込みが来ないと 先に進めないようだ。

  • ちなみに、calibrate_delay() が完了すれば、おなじみの BogoMips が表示される。


さてどうしたものか ...Linux カーネル Tree にマージ申請したときの紹介には、

  • The JZ4740 has a mostly MIPS32 4K compatible core (no on cpu timers)

と書いてあるのだが ...

とりあえず、MIPS 4K ファミリーの Software User's Manual というのを見てみる。CP0 のレジスターは、 $0-$31 まで定義されていて、

    $12 STATUS (上記の c0_status のこと)
    $14 EPC (通常の例外・割り込みからの復帰アドレス)
    $30 ErrorEPC (NMI/Reset からの復帰アドレス)

と書いてある。実際に値を読みだしてみると...

    $14 EPC 0xADD273F3 とか 0x8DDA73F3
    $30 ErrorEPC 0x94D5BFFF

なにか初期値がそのまま入っている感じで電源投入の毎に微妙に値が変わるのだが、起動後は、何回読みだしても同じ値。

これらに 適当な値を入れて ERET すると ... ユーザモードになって ... 結局アドレスエラーで 例外が起きそう。では、BEV を 0 にした上で、0x80000180 とか 0x80000200 の一般例外ベクタから 望むアドレスへの ジャンプ命令を書いておけば良いのだろうか?

で、それをどこでやるのが適切なのだろう? やはりブートローダからのカーネル実行?

  • ERET は、カーネルモード(通常 NMI や例外・割り込みで切り替わる)からの復帰命令。


    これをテストプログラムでやってみた。

    0x80000180からの例外ハンドラは、

    3c028000 lui v0,0x8000
    24420170 addiu v0,v0,0x170
    8c480000 lw t0,0(v0)
    8c440004 lw a0,4(v0)
    8c450008 lw a1,8(v0)
    8c46000c lw a2,12(v0)
    01000008 jr t0
    00000000 nop

    こんな命令列にする。その 16バイト前から ジャンプアドレスと パラメータを入れておく。なにか例外が起きれば、ここが実行されるわけだ。例外は故意に起こすが一旦ユーザモードに入らないといけないので、ERET を使う。( ErrorEPC にアドレスエラーを起こす値を設定しておく。)

    プログラムに実行が渡ったときの c0_status は、

    0x1200EE13 -- UM = 1 , EXL = 1, IE = 1

    状態としておかしくない。で、
  • IE を 0 / 1 にするのは OK 。
  • UM を 0 にするのは OK 。
  • EXL を 0 にすると ... 例外。
    という動作になった。... どうも ERL/EXL を変更するのはダメのようだ。

    それはともかくこれで割り込み禁止/許可ができる。

  • ブートローダ(usbboot) で 例外を起こして Linux 実行。
  • Linux では ERL/EXL を変更しない。
      0x1b を 0x19 に / 0x1a を 0x18 に

    という方針でやってみる。...

    ところでなぜこんな変なのか? 推測するしかないが、例えば UM/ERL/EXL の状態に応じたシャドーレジスタがあるとか
    ... 他のものがちゃんと動いているはずだし ... なにか設定があるのだろうが今のところ分からない。

で、やってみた。割り込みが許可できるようになったが、calibrate_delay() で止まる。

ちょっと見たところ jz4750l と jz4750 で time.c が全然違う。 jz4750 や jz4740 の time.c をコピーしてもビルドはできるが、やはり動かない。

    ちょっと CPU の違いをメモ

    OS Timer (32 bit) 16bit PWM
    Jz4740 x 8 8(?)
    Jz4750 o 6 6(0-5)
    Jz4725B o 5 5(0-3,5)
    Jz4755 o 6 4(1-3,5)

  • jz4750 では、タイマー割り込みには、timer5 を使用している。
  • OS Timer は、get_cycles 用に使用。

    jz4750 と互換性はありそうだが、PWM の都合上 timer0-2 にしないと困りそう。
    割り込みには、TCU0-2 の 3 つがあり timer5 は TCU1 ?
    jz4740 では、timer0 / timer1 / timer2-7 で 3 種類の割り込み。

    (linux の分類の) jz4750 と jz4750l では、レジスタの定義が一部違う。

    jz4750:
    #define IRQ_MSC1 13
    #define IRQ_MSC0 14
    jz4750l:
    #define IRQ_MSC1 24
    #define IRQ_MSC0 25

    jz4725B はいったいどっちなのか? それともまた別なのか?
    ちなみに DMA チャネルのの数

     jz4750 12
    jz4755 8
    jz4725B 6

    jz4750:
    #define MAX_DMA_NUM 12 /* max 12 channels */
    jz4750l:
    #define MAX_DMA_NUM 8 /* max 8 channels */


    jz4755/jz4725B は、jz4750l に近そうだし、CONFIG_FPGA で切り分けできるし、jz4750l を間借りして良かったとは思うのだが ... 情報がないし骨が折れそうだ。

    でも usbboot で テストプログラムを動かせるし、実際に割り込みが起きたらどのビットが立つのか調べることはできるから、あまり心配はしていない。

    もうすこし調べてみると、(INTC)割り込みコントローラと(CPM)クロックジェネレータがだいぶ違う。-- この違いは、付いているデバイスの種類と最大数の違いから来るものでやむを得ないようだ。
    あと (CIM)カメラインターフェイス と (IPU)イメージプロセッシングユニットが違う。-- これは、jz4750l が 設計中のもので変なのだろう。

    追記: なんと Jz4755_pm が入手できた。これを見ると 割り込み原因のビット割り当ては jz4750l に合致する。
    Jz4725B は、4755 を shrink したものだろうから たぶん jz4750l で良いはず。
    タイマーの割り込み割り当ては、timer0,3,4,5 が TCU1/timer 1,2 が TCU2 。
    クロック供給の設定も jz4750l に合致。

    ただし、
    #define INTC_ISSR (INTC_BASE + 0x18)
    #define INTC_ISCR (INTC_BASE + 0x1c)
    は存在せず、INTC_ICSSR が変なアドレス(0xB3016000)にある。

まぁ、ここらあたりで一旦整理しようと思う。
usbboot の 起動方法が変わったので いままでの テストプログラムの動作に問題が出るし。

    asis 版
  • linux-jz4725b-r237-wk.tar.bz2
  • usbboot-wk14.tar.gz

    usbboot は、1 回実行すると そこで終わりになった。割り込みをいじったりする場合は仕方ないとしても今までのプログラムも最後に暴走することになる。

    内部で状態を変更してからの実行にしたい。そのためには、少々面倒なことをしないと。
    引数が 使えるから、レジスタをセーブして、引数にセーブエリアをわたして、例外つかって jump 。... みたいな。

    追記:いろいろやってみたのだが、結局

    ERL = 1, EXL = 1 → ERL = 0, EXL = 1

    ならなんの問題もないことが判った。

テストその3(続き)


現在は、calibrate_delay() で止まっている。

タイマー割り込みの処理が実行されて jiffies がカウントUP されば、BogoMips が表示されて先に進むはずだが 割り込みの処理が実行されない。

で、local_irq_enable() call した後 (init/main.c) で、いろいろレジスタを表示させてみた。

    c0_status 10000403 (EXL = 1, IE = 1, only Hard Int 0 is enabled )
    c0_cause 30c00700 (Hard Int 0 is caused, Excepton code = Interrupt)
    CPM_CLKGR 00000000 (All Clock Gate are opened)
    INTC_ISR 00200040 (TCU2 , RTC are pending)
    INTC_IMR ff5fffff (TCU0, TCU2 are not masked)
    INTC_IPR 00200000 (TCU2 is active ?)
    TCU_TSTR 00000000
    TCU_TSR 00000000
    TCU_TER 00008004 (OS Timer , timer 2 are enabled)
    TCU_TFR 00000004 (timer 2 is FULL comparison matched)
    TCU_TMR 003f003b (Os Timer, timer 2 are not masked FULL comparison match intr)
    TCU_TCNT2 00000a42
    TCU_TCNT2 0000099f

なんだか正常そうなのだ。timer 2 を使って 周期的に割り込みを起こし、TCU2 の割り込みが発生する設定なのだが、timer 2は進んでいるし、TCU2 の割り込みも発生していて pending になっている。

割り込みも許可できているつもりだが ... 違った。ERL と EXL ともに 0 でないと割り込みは来ない。結局最初の問題に戻る。

いろいろやってみたら... write_c0_cause(0) をしてから EXL = 0 にすると ... 先に進んだ。

... で mtd が組み込まれてしまい。

bad eraseblock 210 at 697f000
Bad eraseblock 2598 at 5137f000
Bad eraseblock 2600 at 5147f000

こんなメッセージが ... まだオリジナルのファームウェアを壊すつもりはないので焦ったが、一応立ち上がった。

とりあえずメッセージが流れてしまったので、途中で止めて BogoMips をみてみた。

226.91 BogoMips 

だそうだ。CPUクロックを 228MHz にしているので、CPUクロックにほぼ一致する値になるらしい。

結局、arch_init_irq() (arch/mips/jz4750l/irq.c) の

clear_c0_status(0xff04) /* clear ERL */

write_c0_cause(0);
clear_c0_status(0xff1f) /* clear ERL */

に変更するだけで良かった。(ここまで ERL=1 / EXL=1 で来るので、割り込み禁止とかの変更はイキ)

次は、console とか パラメータとかをキチンとしよう。

    asis 版
  • linux-jz4725b-r237-wk2.tar.bz2
  • usbboot-wk15.tar.gz

    たいして変わっているわけではないのだが、ここまでの分のバックアップ。
    usbboot-wk15 は、wk13 ベースに戻した上で wk14 のテストコード追加。


追記: 結局 write_c0_cause(0)は、usbboot でもすることにした。これで、c0_status 周りを元に戻しても問題なくなった。.. のだが別に悪影響があるわけではないので当面すのままにしておく。

さて、今は usbboot での環境を整備しようかと思い、jz47xx の reg.h と ops.h を見ている。jz4740 や jz4750 といったチップ毎に reg.h ops.h があるのだが、どこまで同じで、なにが違うか分かるようにするため、チップ毎のファイルを コントローラ毎に してみている。

  • ひとつ重大なことを発見。.. jz4740 と jz4750l の rtc のコードにバグがあった。jz4750 だけ直してあった。

  • コントローラ毎に してみると ほぼ同じものもあるが、全然違うものもある。SLCD (Smart LCD) はあまり変わらないのだが、LCD (Generic LCD) は、機能が大幅に拡張されていて レジスタの数が 2倍ぐらいになっている。全然違うのは、他には CIM (カメラ) , ICDC (internal CODEC) 。あと SSI や MSC(SDカード), DMAC(DMA コントローラ)は、コントローラーが複数になっていて マクロが変更になっている。
  • INTC (割り込みコントローラ) , CPM(クロック) などは、周辺デバイスの構成が変わっているので チップ毎に違う部分がある。

  • ICDC (internal CODEC) は、Jz4740系 と Jz4750系ではまったく違う。Jz4740系 は、レジスタが普通にメモリにマッピングされているが、Jz4750系は、まるで別デバイスのようだ。
  • IPU のヘッダファイルは変。他のと違う。アプリケーションからとってきて突っ込んだ感じ。Jz4750 と Jz4755 も違うので困惑したが、デバイスそのものは同じだった。編集して 他のに合わせることにした。
    ついでに jz4740 の pm を見て比べたところ、互換性はだいぶあるが、随分拡張されている。
  • OTP(One Time Programmable Module というのがあって、そこにチップの情報が格納されている。
    プロダクトID やら ユニークな ID を作れる情報もある。

まぁこんな感じ。この作業を通じて だいぶ分かってきたような気がする。

Linux については、ちょっとお休み。パラメータはちゃんと渡っていることは確認できた。... が xburst_stage2 に callback すると panic することが分かった。ちゃんと panic まで来ているのだが、なぜなのかまでは分かっていない。これをどうにかしないと... 先に進めない。

ついでに書いておくが、手持ちのなかで Neo Slim 3000 だけが採用している HSYN/VSYNC インターフェイスの LCD は、なかなか使うのが難しいことが判った。DMA も必要だし 周期的に表示するため タイマも動いていないといけない。-- 要するに最初のデバッグに利用するのは困難。

posted by すz at 19:02| Comment(0) | TrackBack(0) | Jz47xx(Linux)