このデモでは(フラッシュバージョンはこちら)、組込みプロセッサコア上でLinuxを起動し、ZeBuエミュレータのソフトウェア、ハードウェアのビューを使ってデバッグに成功するところをご紹介します。
SOCはTensilica Diamond 232Lプロセッサコアと、メモリ・サブシステムとUARTコントローラから構成されます。これはLinuxを起動するのに必要な最小構成です。ソフトウェアについては、Monta Vista社製のカーネル2.6ベースのLinuxディストリビューションを使用します。Linux起動時に必要な構成を作成するのにはinitramfsファイルシステムを利用し、コンパクトな形でLinuxコマンドを使うためにBusyBoxを採用します。
SOC全体はZeBu-UF 0.5でエミュレートします。
プロセッサに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コントローラの波形を抽出します。
最初の関門は、ハードウェア・サイクルの観点で、問題が発生する時期を合理的に推測することです。実際、Linux起動自体は10億サイクルの間続くので、その期間全体のDUTをトレースすることは合理的ではありません。問題の部分に素早く到達するために、ZeBuランタイム・ハードウェア環境の1機能であるハードウェア・トリガを使用します。エミュレータはプロセッサのPCが特定の値に等しくなるサイクル時点であればいつでも停止できます。ソフトウェアのブレークポイント(gdbなどのソフトウェアデバッガにある)とは違い、ハードウェア・トリガはプロセッサコアとそれ以外のSOC全体を両方とも停止させます。この機能はコアとそれ以外のSOC部位との間の割り込みや非同期イベントをデバッグするのに重要です。ハードウェア・トリガはVerilogシミュレータと同じように機能します。待機状態では時間が停止し、DUTの振舞いにまったく影響を与えません。
作業を簡単にするために、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性能を犠牲にしてこの問題に対処していた可能性もあったでしょう。