西永俊文氏:それでは「JTAGでarmプロセッサをデバッグする話」をします。tnishinagaです。組み込みが好きです。キャラクターボイスはついなちゃんです。今回は音声読み上げソフトを試験的に導入しています。聞き取りづらい方のために発表テキストを公開するので、ぜひご利用ください。URLはTwitterで公開します。

(スライドを示して)今回の発表でお世話になった方々です。レビューにご協力いただいたretrageさん、bingenというcrateを作ってくれたhikaliumさん、そしてJTAGに関するさまざまな情報を日本語で公開してくれている、なひたふさんに感謝します。

最初に、「JTAGとは何か」から紹介します。Wikipediaの引用ですが、JTAGとは電子回路やCPUデバッグ等に使えるバウンダリスキャンテストやテストアクセスポートの標準、IEEE1149.1の総称です。ここで出てくるバウンダリスキャンとは、ICのピン状態を取得したり制御したりする技術です。ICのIOピンの手前にシフトレジスタが入っていて、このシフトレジスタを使ってピン状態を取得したり制御したりしているようです。

JTAGはCPUデバッグにも使えます。この発表を見ているみなさんは、こちらの使い方のほうをよくご存じでしょう。JTAG Interfaceと呼ばれる機材を、ターゲットのJTAGポートにつなぎ、OpenOCD等のソフトを起動すると、gdb経由でCPUデバッグが行えます。OS開発などベアメタル環境での開発には必須の装備でしょう。

さて、ここまでの話はつい半年前の自分も知っていた内容です。ただし、当時は両者のイメージが乖離していました。ICの状態を調べる技術で、具体的にどうやってCPUデバッグを行えるのか、理解できていなかったのです。先ほどの解説だと、OpenOCDから先の部分で何をやっているのか、自分で制御できるレベルでわかっていませんでした。

そこで、JTAGを使ってCPUデバッグする仕組みを調べ、この自分の中の乖離を縮めていこうと考えたのが今回の発表の発端です。

本日のアジェンダ

さて、ここからの発表の進め方を紹介します。本発表は、JTAGでCPUデバッグを行ううえで作者が疑問に思ったことの解決を目標に、調査を行っていきます。CPUデバッグを行う文脈での調査なので、その文脈外のことや細かい部分の説明は行いません。予めご了承ください。私はプログラムを作りながら調べる方法が一番理解が深まるので、調査が一段落するたびにRustでコードを書いて動作確認していきます。

(スライドを示して)こちらに列挙した疑問を解消するのが本日の目標です。最初に、物理層の理解を深めるため、JTAGの通信がどうなっているのかを調べてから、JTAGインターフェイスの中身や必要な機能について調べます。物理層を理解したあとは、CPUデバッグの方法について調べていきます。ただし、CPUデバッグは、レジスタ取得、メモリ読み書き、breakpoint、step実行に分けて調査を行っていく予定でしたが、進捗の都合上レジスタ取得までしか行えていません。

CPUデバッグでレジスタを取得する仕組みと通信の概要

本題に入る前に、全体像を把握するための概要を話しておきましょう。(スライドを示して)こちらがCPUデバッグでレジスタを取得するまでの仕組みと通信の概要です。

まず、JTAGインターフェイスからTAPまでの間は、JTAGの信号線を使って通信します。物理的な通信はこのTAPまでです。矢印の間にあるのはJTAGの信号線です。詳細は後ほど説明します。

TAPからはDAPとJTAGのプライベート命令を使って通信します。矢印の間にあるのはプライベート命令の一部です。こちらも詳細は後ほど説明します。DAPからはMEM-APと呼ばれるポートを使い、メモリマップされたプロセッサのデバッグユニットとCross Trigger Interfaceにアクセスします。

Cross Trigger InterfaceからプロセッサをHaltしてデバッグステートに移行したあと、デバッグユニットからA64命令のopcodeを発行して、レジスタ値をデバッガとプロセッサ両方から読み書きできるシステムレジスタに格納します。

最後に、デバッガからそのシステムレジスタ値を取得すれば、JTAGからCPUレジスタ値の取得が完了します。

CPUレジスタ取得までの手順が書かれた資料は、大まかに3つに分かれています。最初のTAPまでの部分はIEEE1149.1の規格書に書かれているそうですが、この資料は有料で1万5,000円ほどするので、私は買えていません。内容は別の資料や、MITOUJTAGを作ったなひたふさんのサイトに書かれているので、今回はそちらを参照しています。

DAPからMEM-APによるメモリマップドレジスタへのアクセス方法までの仕様は、『Arm Debug Interface Architecture Specification』という資料に書かれています。こちらはArmのサイトから無料でダウンロードできます。デバッグユニットやCross Trigger Interfaceの仕様等については、ARMv8のアーキテクチャマニュアルに書かれています。こちらもArmのサイトから無料でダウンロードできます。

(スライドを示して)先ほど紹介した各資料の名前とURLです。以降はこれらの資料を参照しながら解説を行っていきます。ここからはちょっと復習が入りますが、JTAGについて説明します。この節で説明するのは、要約の図のJTAG InterfaceとTAPの間の部分です。

JTAG対応ICの概要と信号線

再掲ですが、JTAGとは電子回路やCPUデバッグ等に使えるバウンダリスキャンテストや、テストアクセスポートの標準、IEEE1149.1の総称です。

JTAG対応ICには外部のインターフェイスとして、TDI等のテスト信号接続用のポートが付いています。IC内部には、制御用のTAPコントローラーと、JTAGテストやバウンダリスキャン用のシフトレジスタが入っています。

(スライドを示して)TAPの信号線は主に以下の4本になります。TMSはTAPコントローラーの持っているステートマシンの状態遷移を行うための線です。ステートマシンの詳細はこのあとに説明します。TCKはクロック線です。TMSの信号を確定したり、シフトレジスタのシフトを行ったりするのに使います。エッジは立ち上がりエッジです。

TDIはJTAGインターフェイス側からデバイス側に送る信号線です。TDOはTDIの逆で、デバイス側から出てきた信号を、インターフェイスで受け取るための信号線です。

(スライドを示して)先ほど紹介した信号線の他に、オプションの信号線があります。TRSTとSRSTはリセットを行うための線です。TRSTはステートマシンのリセット、SRSTはシステムリセットを行います。

RTCKはARMコア専用のピンです。Adaptive clockという機能で、TCKの周波数を自動でターゲットの動作周波数に合わせる機能があるそうですが、今のところ使ったことがないので詳細不明です。

信号線ではないですが、おまけとしてVrefを紹介します。Vrefはターゲットの電源電圧を取得するためのピンで、信号電圧の決定等に使います。配線を忘れると動かないデバイスもあるので注意してください。

次に、テスト用レジスタとの値の交換方法を紹介します。JTAG対応ICは、内部にシフトレジスタを持っていて、このレジスタを用いて、アダプタと命令やデータのやり取りを行います。レジスタ長やレジスタ値の初期値は、TAPコントローラーの状態やセットした命令によって変わります。入力はTDI、出力はTDO、トリガーにはTCKがつながっていて、(TCKから)クロックを与えるとTDIの状態がシフトレジスタにセットされ、レジスタ内の状態はTDOに出てきます。

TDIとTDOをつなぐと複数デバイスを一度に制御することも可能ですが、今回使用するRaspberry Piはデバイスが1つしかつながっていないので考慮していません。

JTAGテストの通信概要

JTAGテストの通信は、主に命令をセットする操作と、データの読み書きを行う操作の繰り返しになっています。各操作はTAPステートマシンを制御して行います。

(スライドを示して)TAPステートマシンは、TAPコントローラーの持つステートマシンです。このステートマシンは16の状態を持ち、TMSの信号で制御されます。ステートは主にIRで終わる命令系と、DRで終わるデータ系に分かれています。どちらもShiftステートでJTAGインターフェイスと値を交換し、Updateステートでレジスタの入力を確定します。TMSをHighにして5回以上クロックを与えると、必ずResetにたどり着くようになっているので、これを用いてステートの初期化を行います。

IRは命令を指定するためのレジスタです。レジスタ長はデバイスごとに異なり、Raspberry Pi 3B+では4bitです。命令にはパブリック命令とプライベート命令の2種類があります。パブリック命令はIEEE1149.1で決められている命令で、どのデバイスも絶対に使える必須命令と、実装依存のオプション命令があります。

プライベート命令はチップの設計者が独自に作った命令です。CPUデバッグは主にこのプライベート命令を使って行います。各命令のビット列は、一部を除き設計依存となっています。

DRはデータの入っているレジスタです。TAPコントローラーのDR系に行くと読み書きできます。レジスタ長はIRで設定した命令依存になります。

パブリック命令の例を紹介します。(スライドを示して)BYPASS命令は命令ビット列まで決められている必須命令の1つです。(BYPASS命令は)DRのレジスタ長を1bitにして、TDIの入力をTDOにバイパスする機能を持っています。複数デバイス接続している経路上では、ターゲット以外にこのBYPASS命令をセットしておくと(それらデバイスの状態が)変なことにならず安全です。

IDCODE命令は、TAP識別用のIDを取得するためのオプション命令です。この命令はTAPステートをResetにすると自動で発行されるので、命令ビット列を知らなくても発行できます。

ステートマシン制御の例として、IRで命令を発行する操作をしてみます。(スライドを示して)まず、TMSに0,1,1,0,0を入れてResetからShift-IRステートに移行します。Shift-IRではTMSを0にしたままクロックを送って、TDIからビット列を送り込みます。最後の1bitを送る際はTMSを1にしてクロックを発行し、Exit-IRステートに移行します。この操作をしないと、命令の最下位ビットが落ちてしまうので注意してください。

あとは1,0をTMSに送ってUpdate-IRで命令を確定して、Runステートに戻します。データを交換する命令では、そのままDR系に移行して、JTAGインターフェイスとデータを交換します。なお、命令発行後にステートをResetにまで戻してしまうと、命令の実行が中断されてデータが読み出せなくなるので、気をつけてください。

もう1件の制御例として、IDCODE取得を行ってみましょう。(スライドを示して)やることは、IRへのIDCODE命令のセットと、DRから32bit読み出す作業の2つだけです。

IRへのIDCODEセットは以下の手順で行います。まず、Shift-IRステートに進めて、TDIから0b1110をIRにセットします。次に、Update-IRを経由してRunステートに戻ると命令がセットされ、DRにIDCODEがセットされます。

テスト環境と動作確認

IDCODE命令実行から読み出しまでの動作確認を実機で行ってみましょう。テスト環境のJTAG InterfaceにはOlimexのARM-USB-TINY-Hを利用し、ターゲットにはRaspberry Pi 3B+を利用しました。特に断りがない場合は、今後のテストでもここで紹介した環境を利用しています。

(スライドを示して)JTAG InterfaceとRaspberry Piの接続は次のようになっています。接続の詳細はRaspberry Piのペリフェラルマニュアルを参照してください。

Raspberry PiでJTAGを使うには、config.txtの設定が必要なので追記します。加えて、A64モードのデバッグを行うために64bit化も行います。

話を戻して、実際にIDCODE命令をIRにセットするまでの波形を見てみましょう。(スライドを示して)この波形を見ると、立ち上がりエッジでステートが進むことや、TDIの出力が最下位ビットから出ているのが見て取れます。

命令発行が完了したら、DRからIDCODEを読み出します。IRと同様にShift-DRに進めたあと、クロックを与えながらTDOから32bit読み出します。読み出しが終わったらUpdate-DRを経由してRunステートに戻ります。

実際にDR読み込み時の波形を見てみましょう。(スライドを示して)細かくて見づらいですが、DRの値が32bit読み出されている様子が見て取れます。今回は読み出しを行っているので、TDOのところに0x4baから始まるIDCODEが確認できます。以上でJTAGの説明を終わります。

この節では、「JTAGはIEEE1149.1の総称である」こと、「JTAGの信号線が8本ある」こと、「TAPコントローラーがステートマシンを持つ」こと、「IRで命令発行、DRでデータのやり取りを行う」こと、「命令にはパブリック命令とプライベート命令があり、CPUデバッグは主にプライベート命令を使う」ことを学びました。この節の内容は、JTAGの使えるデバイスで共通の知識が多いので、覚えておくと何か役に立つことがあるかもしれません。

JTAGインターフェイスとは何か

ここからはJTAGインターフェイスと、その中に入っているICについて紹介したあと、実際にJTAGインターフェイスを利用する方法を調べていきます。この節で説明するのは、要約の図のJTAG Interfaceの部分になります。

JTAGインターフェイスとは、JTAGの信号をやり取りするための物理インターフェイスです。通信速度を気にしなければ、GPIOが7本程度生えていて、パソコンと通信ができるデバイスであれば、どんなデバイスでもJTAGインターフェイスとして利用できます。JTAG専用デバイスとして売られているものは、数十MHz程度以上の高速通信が行えたり、幅広い信号電圧に対応したりしています。

(スライドを示して)JTAGインターフェイスの1つに、Olimex社の出しているARM-USB-TINY-Hというデバイスがあります。価格は5,000円です。このデバイスの中には、FTDI社のFT2232HというICが乗っています。このFT2232Hは、USBシリアル・パラレル変換回路を2つ積んだICです。それぞれの回路にはモードがあり、各ピンの機能を変えられるようになっています。チップの価格は700円程度と安価ですが、昨今の半導体不足の影響で買えません。

SyncBitBangモードとMPSSEモードの紹介

FT2232のモード例を2つ紹介します。1つ目はSyncBitBangモードです。こちらは一部のピンをGPIOとして使えるモードで、各ピンに対応した8bitの値を書き込むと、ピン状態が変化します。ピン状態を書き込む時に自動で各ピンの状態が読み込まれ、バッファに格納されます。後述のMPSSEと比較して、低速ですが制御が簡単です。

2つ目はMPSSEモードです。こちらはBitBangモードと異なり、コマンドを送信してSPIやJTAG等の通信を半自動で行うモードです。MPSSEは使いこなせると高速なのですが、まだうまく制御できていないため、今回はSyncBitBangモードを使用しました。

(スライドを示して)FT2232HのピンはA回路とB回路の2回路に分かれており、それぞれの回路にピンが16本ずつ割り当てられています。さらに、その16ピンは8ピンごとにDBUSとCBUSの2つに分かれています。各バスはモードによって機能が変わるようになっており、SyncBitBangモードでは、DBUSのみGPIOとして利用できます。MPSSEモードでは機能ごとに使えるピンが固定されています。

safe-ftdiを使った、ARM-USB-TINY-H制御サンプル

(スライドを示して)実際に、ARM-USB-TINY-HのFT2232のピンとJTAG信号線の対応を見ていきましょう。主要な信号線はすべてADBUSにまとまっているため、ADBUSしか使えないSyncBitBangモードを使っても、JTAGの通信ができることが、この表からわかります。ハード側の仕様がわかったので、今度は制御ソフト側を見ていきます。

今回利用したのはsafe-ftdiという、libftdiのラッパーライブラリです。

SyncBitBangとMPSSEのどちらも対応していたので選択しました。(スライドを示して)このsafe-ftdiを使ってARM-USB-TINY-Hを制御するサンプルがこちらです。vidとpidを指定してデバイスを開いたあとにボーレートの設定を行い、各ビットの入出力とモードの設定を行っています。初期設定が終わったあとはTCKを点滅させています。

(スライドを示して)先ほどのコードを実行した結果がこちらです。コードが正しく動いていてTCKが点滅している様子が見られます。

この節のまとめに入ります。JTAGインターフェイスはJTAG信号をやり取りするためのインターフェイスです。パソコンと通信してJTAG信号をやり取りできれば、普通のマイコンでもFT2232のように専用機能を持ったICを使っても、JTAGインターフェイスは作れます。制御用ソフトをRustで開発するのであれば、safe-ftdi経由でlibftdiを使うのが、今のところおすすめの方法です。

(次回へつづく)