Curve Curve

Linuxの起動 (Booting and Debugging Linux in Emulation with ZeBu)

このデモでは、組込みプロセッサコア上でLinuxを起動し、ZeBuエミュレータのソフトウェア、ハードウェアのビューを使ってデバッグに成功するところをご紹介します。

設定

SOCはTensilica Diamond 232Lプロセッサコアと、メモリ・サブシステムとUARTコントローラから構成されます。これはLinuxを起動するのに必要な最小構成です。ソフトウェアについては、Monta Vista社製のカーネル2.6ベースのLinuxディストリビューションを使用します。Linux起動時に必要な構成を作成するのにはinitramfsファイルシステムを利用し、コンパクトな形でLinuxコマンドを使うためにBusyBoxを採用します。

課題

プロセッサにLinuxイメージを読み込むと、コンソールの表示の通り、Linuxの起動が始まります。しかし、initプロセスがシェルを起動しようとする段階で、エラーメッセージが表示され、コンソールでの表示はそこで停止してしまいます。

ZeBu-Diamond Console

Linux version 2.6.10_mvl401-xt2000-xtensa_linux_le

(alain@treasure5.us.eve) (gcc version 3.4.3

(MontaVista 3.4.3-25.0.135.0702842 2007-03-23))

#76 Tue May 1 11:02:33 PDT 2007

On node 0 totalpages: 8192

DMA zone: 8192 pages, LIFO batch:2

Normal zone: 0 pages, LIFO batch:1

HighMem zone: 0 pages, LIFO batch:1

Built 1 zonelists

Kernel command line: console=ttyS0 mem=64M debug init=/sbin/init root=/dummy

PID hash table entries: 256 (order: 8, 4096 bytes)

Console: colour dummy device 80x25

Dentry cache hash table entries: 8192 (order: 3, 32768 bytes)

Inode-cache hash table entries: 4096 (order: 2, 16384 bytes)

Memory: 30936k/32768k available (606k kernel code, 1784k reserved,

80k data, 528k init 0k highmem)

Calibrating delay loop... 15.76 BogoMIPS (lpj=78848)

Mount-cache hash table entries: 512 (order: 0, 4096 bytes)

spawn_desched_task(00000000)

desched cpu_callback 3/00000000

ksoftirqd started up.

desched cpu_callback 2/00000000

desched thread 0 started up.

Linux NoNET1.0 for Linux 2.6

Initializing Cryptographic API

Serial: 8250/16550 driver $Revision: 1.90 $ 6 ports, IRQ sharing disabled

Registering platform device 'serial8250'. Parent at platform

ttyS0 at MMIO 0x0 (irq = 4) is a ST16650

ttyS1 at MMIO 0x0 (irq = 5) is a ST16650

io scheduler noop registered

io scheduler anticipatory registered

io scheduler deadline registered

io scheduler cfq registered

loop: loaded (max 8 devices)

mice: PS/2 mouse device common for all mice

Freeing unused kernel memory: 528k freed

Laun serial8250: too much work for irq4

ドライバのソースコードの確認

コンソールにエラーメッセージが表示されているので、ソースコードはすぐに見つかります。drivers/serial/8250.cには次のようなコードがあります。

static irqreturn_t serial8250_interrupt

   (int irq, void *dev_id,  struct pt_regs *regs)

   {
            struct irq_info *i  = dev_id;

         struct list_head  *l, *end = NULL;

         int pass_counter =  0, handled = 0; 

          DEBUG_INTR("serial8250_interrupt(%d)...", irq);

          spin_lock(&i->lock); 

         l = i->head;

         do {

                 struct  uart_8250_port *up;

                 unsigned  int iir; 

                 up =  list_entry(l, struct uart_8250_port, list); 

 #ifdef CONFIG_TSI108_BRIDGE /* for TSI108_REV_Z1 errata U2 */

                 /* read IIR  as part of 32-bit word */

                 iir = (in_be32((u32  *)(up->port.membase + UART_RX)) >> 8) & 0xff;

 #else

                 iir =  serial_in(up, UART_IIR);

 #endif

                 if (!(iir  & UART_IIR_NO_INT)) {

                          spin_lock(&up->port.lock);

                          serial8250_handle_port(up, regs);

                          spin_unlock(&up->port.lock);

                           handled = 1;

                          end  = NULL;

                 } else if  (end == NULL)

                         end  = l;

                  l =  l->next;

                 if (l ==  i->head && pass_counter++ > PASS_LIMIT) {

                         /*  If we hit this, we're dead. */

                          printk(KERN_ERR "serial8250: too much work for "

                                 "irq%d\n",  irq);

                          break;

                 }

         } while (l != end);

コードが示しているのは、ドライバは文字が送られる必要があるときには、それを扱うために割り込みが入るのをループしながら待つということです。デバイスからの割り込みがタイミング良く入らないと、ドライバはループを抜けて、さっきコンソールに表示されたエラーメッセージを出します。特に問題がありそうなのはUARTプロトコルの一部であるIIRレジスタ(割り込み要因レジスタ)です。

UARTの波形の抽出

次のステップとして、ハードウェアのデバッグに移り、ドライバからエラーが出された時点の周辺でのUARTコントローラの波形を抽出します。

ハードウェア・トリガ

最初の関門は、ハードウェア・サイクルの観点で、問題が発生する時期を合理的に推測することです。実際、Linux起動自体は10億サイクルの間続くので、その期間全体のDUTをトレースすることは合理的ではありません。問題の部分に素早く到達するために、ZeBuランタイム・ハードウェア環境の1機能であるハードウェア・トリガを使用します。エミュレータはプロセッサのPCが特定の値に等しくなるサイクル時点であればいつでも停止できます。ソフトウェアのブレークポイント(gdbなどのソフトウェアデバッガにある)とは違い、ハードウェア・トリガはプロセッサコアとそれ以外のSOC全体を両方とも停止させます。この機能はコアとそれ以外のSOC部位との間の割り込みや非同期イベントをデバッグするのに重要です。ハードウェア・トリガはVerilogシミュレータと同じように機能します。待機状態では時間が停止し、DUTの振舞いにまったく影響を与えません。

System.mapの使用

作業を簡単にするために、Linuxカーネルのコンパイル時に生成されるSystem.mapファイルを利用して、ドライバの関数名(serial8250_interrupt)とそのハードウェア・アドレスのマッピングを調べます。

	$ grep serial8250_interrupt System.map

      d008769c t serial8250_interrupt
      

次に、Diamondプロセッサを積んだPCがそのアドレスに到達したときにZeBuを停止するトリガを設定します。仕事をさらに簡単にするために、 zRunのGUIにTCLの行を少し追加し、関数名を直接入力できるようにして、さらにGUIが自動的にSystem.mapを検索して実際のハードウェアドレスを探し出すようにしました。

System.mapファイルを付随するアレイczrAddFromSymに解析したら、既存のzRunのTCLコードに次の2行(太字部分)を追加します。

proc getDynamicTrigger { } {

   global logFile

   global selectedTrig ret  prog

   set prog  [ZEBU_Trigger_getExpr $selectedTrig]

   set ret  1 

   set okCmd     { global ret

                   global  prog logFile

                   # use system.map

                   set simprog  "Diamond_PC\[31:0\]==$czrAddFromSym($prog)"

                   set ret  [ZEBU_Trigger_setExpr $selectedTrig "$simprog"]

                   destroy  .la.progDynaTrig

                 }
                 

zRun GUIにフィールドをもう1つ追加して実行中のLinuxカーネルの関数名が継続的に表示されるようにします。カーネルの動作にしたがって関数名が変化していくのを眺めるだけでも参考になります。

波形の取得

何か問題がありそうな場所でZeBuが停止したら、ダイナミックプローブを起動し、DUT内部の信号を標準の波形フォーマットにトレースします。今回のケースでは、UARTコントローラの信号をすべて、特にIIRレジスタと割り込みラインをトレースします。

波形を観ると、最初の数文字は実際正しくデータバスに送られていることがわかります。しかしその後、コントローラから割り込みが入らず、そのため文字が送られなくなります。これでドライバから出されたエラーメッセージの説明がつきます。

バグの修正

上記の情報から、UARTの仕様を再点検してみると、曖昧な点が見つかりました。UARTは自分のバッファが空のときにはいつでも割り込みを送るようになっています。しかし、バッファが空で、かつ空になった後にUARTが割り込みモードに入ったときに割り込みを発行するのかどうかが明確ではありませんでした。実際においては、英語の仕様書で、「バッファが空のときに割り込みを発行する」か、「バッファが空になったとき」なのかで、解釈の問題が発生していました。

期待されるソフトウェア・ドライバの動作と波形から考えて、バッファが空のときには毎回割り込みを発生させるようにUARTの動作を変更しました。

結果

UARTのロジックを変更した後には、コンソールには次のように表示されます。

ZeBu-Diamond Console

 Linux version 2.6.10_mvl401-xt2000-xtensa_linux_le  

 (alain@treasure5.us.eve) (gcc version 3.4.3 

 (MontaVista 3.4.3-25.0.135.0702842  2007-03-23)) 

 #76 Tue May 1 11:02:33 PDT 2007

 On node 0 totalpages: 8192

 DMA zone: 8192 pages,  LIFO batch:2

 Normal zone: 0 pages,  LIFO batch:1

 HighMem zone: 0 pages,  LIFO batch:1

 Built 1 zonelists

 Kernel command line: console=ttyS0 mem=64M debug init=/sbin/init  root=/dummy

 PID hash table entries: 256 (order: 8, 4096 bytes)

 Console: colour dummy device 80x25

 Dentry cache hash table entries: 8192 (order: 3, 32768 bytes)

 Inode-cache hash table entries: 4096 (order: 2, 16384 bytes)

 Memory: 30936k/32768k available 

 (606k kernel code, 1784k  reserved, 80k data, 528k init 0k highmem)

 Calibrating delay loop... 15.76 BogoMIPS (lpj=78848)

 Mount-cache hash table entries: 512 (order: 0, 4096 bytes)

 spawn_desched_task(00000000)

 desched cpu_callback 3/00000000

 ksoftirqd started up.

 desched cpu_callback 2/00000000

 desched thread 0 started up.

 Linux NoNET1.0 for Linux 2.6

 Initializing Cryptographic API

 Serial: 8250/16550 driver $Revision: 1.90 $ 6 ports, IRQ sharing  disabled

 Registering platform device 'serial8250'. Parent at platform

 ttyS0 at MMIO 0x0 (irq = 4) is a ST16650

 ttyS1 at MMIO 0x0 (irq = 5) is a ST16650

 io scheduler noop registered

 io scheduler anticipatory registered

 io scheduler deadline registered

 io scheduler cfq registered

 loop: loaded (max 8 devices)

 mice: PS/2 mouse device common for all mice

 Freeing unused kernel memory: 528k freed

 init started:  BusyBox  v1.4.2 (2007-04-26 04:18:24 PDT) multi-call binary

 Starting pid 15, console /dev/console: '/bin/hostname'

 Starting pid 16, console /dev/console: '/bin/login'

 zebudemo login: demo 

 BusyBox v1.4.2 (2007-04-26 04:18:24 PDT) Built-in shell (ash)

 Enter 'help' for a list of built-in commands.

 $ pwd

 /home/demo

 $
 

Linuxの起動はシェルまでずっと進行しました。カーネル自身は2秒以内でZeBu-UF 0.5に読み込まれました。それからカーネルがディスクイメージを解凍してinitプロセスを実行するのにおよそ10秒要し、そこからログインプロセスとシェルが起動します。

まとめ

なぜUARTが正しく動作しないせいで、Linuxの起動は完了に近いところまで進行しながら、そこで止まってしまったのでしょうか?Linuxは2つの UARTドライバを使っていて、その1つ(serial8250_early)は起動の前半に、割り込みを使わずに文字を表示させますが、これは安全でした。もう1つは「実際の」UARTドライバですが、カーネルが初期化されたときにのみ有効になります。Linuxの起動が完了する前にハードウェアテストを停止し、前者のドライバだけを見ていたら、この問題は時間内に発見できなかったでしょう。その場合、テープアウト後のソフトウェアの修正により、I/O 性能を犠牲にしてこの問題に対処していた可能性もあったでしょう。

Curve Curve