#はじめに
Arm Cortex-Aシリーズの割込みコントローラであるGICの機能に、PCIなどの割込みに使用されるLPI(Locality-specific Peripheral Interrupts)と、ITS(Interrupt Translation Service)という機能があります。これらLPIとITSの理解を深めるために、u-boot上で動作するプログラムを作成して、LPI+ITS割込みを発生させたときの備忘録です。
#環境
- CPU:NXP社製 LS2088A(Cortex-A72, GIC500)
- 評価ボード:NXP社製 LS2088ARDB
- u-boot:U-Boot 2016.092.0+ga06b209
#LPIとは
PCIなどで使用されるMSI(Message Signaled Interrupts)のための機能で、SDRAMなどのメモリ領域に割り当てたLPIテーブルに値を書き込むことで、8192番以降の割込みを発生させる機能です。
LPIはGIC内にコアの個数分(LS2088Aでは8個)存在します。
LS2088Aでは1コアあたり最大(65536-8192)個の割込みを登録することができます。
##LPIテーブル
LPIテーブルには以下の2つのテーブルがあります。
・LPI構成テーブル(LPI Configuration tables)
・LPI保留テーブル(LPI Pending tables)
#####LPI構成テーブル
各割込みの優先度と有効/無効を設定するためのテーブルです。1つの割込みエントリあたり8bitで構成され(下図参照)、割込みの個数分配列構造になっています。
#####LPI保留テーブル
各割込みの保留状態を示すテーブルです。1つの割込みエントリあたり1bitで構成され、
割込みの個数分並べられた構造になっています。ただし、テーブルの先頭から1Kbyte分は未使用領域とされており、0クリアしておく必要があります。
#ITSとは
LPIを利用しやすいように補足された機能で、このデバイスのこのイベントが発生したという表現でLPI割込みを発生させられるように、DeviceIDおよびEventIDという識別子と発生させたいLPI割込みを関連付ける機能です。
ITSはGIC内に1つ存在します。
##ITSテーブル
この記事で登場するテーブルとして以下の3つのテーブルがあります。
・デバイステーブル(Device table)
・ITT(Interrupt Translation table)
・コマンドテーブル(Command Queue)
この他にCollection tableや仮想化で使用するテーブルがありますが、この記事では設定していません。
#####デバイステーブル
デバイス毎にITTが存在するため、DeviceIDと対応するITTのベースアドレスを関連付けるためのテーブルです。テーブルの1エントリのサイズは、GITS_BASERのEntry_Sizeから読み取ることができ、エントリの個数分を掛け合わせることで、テーブルに必要なサイズを求めることができます。
LS2088AではGITS_BASERのEntry_Sizeは7という値が設定されており、GICの資料(参考資料1)のGITS_BASERの説明からEntry_Sizeはマイナス1された値を示すので、エントリサイズは8byteであることが分かります。
#####ITT
デバイスに複数のイベントを持たせ、イベント毎にLPI割込みの番号を設定できます。ITTはEventIDと対応するLPI割込み番号を関連付けるためのテーブルです。テーブルの1エントリのサイズは、GITS_TYPERのITT_entry_sizeから読み取ることができ、エントリの個数分を掛け合わせることで、テーブルに必要なサイズを求めることができます。
LS2088AではGITS_TYPERのITT_entry_sizeは3という値が設定されており、GICの資料(参考資料1)のGITS_TYPERの説明からITT_entry_sizeはマイナス1された値を示すので、エントリサイズは4byteであることが分かります。
#####コマンドテーブル
ITSテーブルに値を設定していくためのコマンド群を記述するテーブルです。ITSコマンドを実行させることでITSテーブルを設定していきます。GICの資料(参考資料1)ではCommand queueと記述されていますが、ここではコマンドテーブルと記述しています。
#メモリマップ
以下のメモリマップでテーブルを配置して、割込み発生の実験を行いました。
テーブル | アドレス |
---|---|
LPI構成テーブル | 0xa0000000-0xa000ffff |
LPI保留テーブル | 0xa0010000-0xa0011fff |
デバイステーブル | 0xa1000000-0xa1000fff |
ITT | 0xa1001000-0xa100103f |
コマンドテーブル | 0xa1010000-0xa1010fff |
#LPIの設定と有効化
LPIテーブルの初期化およびLPI関連のレジスタを設定して、LPIを有効化します。
実験で使用したLPI設定のソースの一部を以下に示します。(gic_write、gic_write64関数はGICのレジスタに値を書き込む関数)
1 #define LPI_CFG_TABLE 0x00000000a0000000ULL
2 #define LPI_CFG_TABLE_SZ (65536 * 1)
3 #define LPI_PEND_TABLE 0x00000000a0010000ULL
4 #define LPI_PEND_TABLE_SZ (65536 / 8)
5
6 /* LPI構成テーブル(64Kbyte)の0クリア */
7 adr = (volatile uint64_t *)LPI_CFG_TABLE;
8 for(i = 0; i < LPI_CFG_TABLE_SZ/8; i++){
9 *(adr + i) = 0;
10 }
11 /* 8194番目の割込みを優先度0xa0で有効化 */
12 *adr = 0x0000000000a30000;
13
14 /* LPI保留テーブル(8Kbyte)の0クリア */
15 adr = (volatile uint64_t *)LPI_PEND_TABLE;
16 for(i = 0; i < LPI_PEND_TABLE_SZ/8; i++){
17 *(adr + i) = 0;
18 }
19
20 /* LPI構成テーブルのGICR_PROPBASERへの登録 */
21 gic_write64(GICR_PROPBASER, 0x000000000000038f + LPI_CFG_TABLE);
22 /* LPI保留テーブルのGICR_PENDBASERへの登録 */
23 gic_write64(GICR_PENDBASER, 0x0000000000000380 + LPI_PEND_TABLE);
24
25 /* LPI有効 */
26 gic_write(GICR_CTLR, 0x00000001);
6-12行目でLPI構成テーブルを65536個分クリアして、実験で使用する8194番目の割込みのみを優先度0xa0で有効化します。
14-18行目でLPI保留テーブルを65536個分クリアします。
21行目でGICR_PROPBASERにLPI構成テーブルのアドレス(LPI_CFG_TABLE)とキャッシュ有効なノーマルメモリ属性を設定します。また、IDbitsにLPI割込みの個数が65536個になるように設定します。GICの資料(参考資料1)のGICR_PROPBASERの説明から、65536個の割込みは2の16乗なので、16から1引いた値をIDbitsに設定します。以下にGICR_PROPBASERの構成を示します。
23行目でGICR_PENDBASERにLPI保留テーブルのアドレス(LPI_PEND_TABLE)とキャッシュ有効なノーマルメモリ属性を設定しています。以下にGICR_PENDBASERの構成を示します。
26行目でLPIテーブルの設定が終わったので、GICR_CTLRのEnable_LPIsを有効にして、LPI機能を有効にします。実験ではコア0用のLPIのみ有効にします。
#ITSの設定と有効化
ITSテーブルの初期化およびITS関連のレジスタを設定して、ITSを有効化します。
実験で使用したITS設定のソースの一部を以下に示します。
1 #define DEVICE_TABLE 0x00000000a1000000ULL
2 #define DEVICE_TABLE_SZ (4096 * 1)
3 #define ITT_ADDRESS 0x00000000a1001000ULL
4 #define ITT_SZ (16 * 4)
5 #define CMD_QUEUE 0x00000000a1010000ULL
6 #define CMD_QUEUE_SZ (4096 * 1)
7
8 /* デバイステーブル(4Kbyte * 1block)の0クリア */
9 adr = (volatile uint64_t *)DEVICE_TABLE;
10 for(i = 0; i < DEVICE_TABLE_SZ/8; i++){
11 *(adr + i) = 0;
12 }
13
14 /* ITT(16entry * 4byte)の0クリア */
15 adr = (volatile uint64_t *)ITT_ADDRESS;
16 for(i = 0; i < ITT_SZ/8; i++){
17 *(adr + i) = 0;
18 }
19
20 /* デバイステーブルのGITS_BASERへの登録 */
20 gic_write64(GITS_BASER, 0xb800000000000000 + DEVICE_TABLE);
21
22 /* コマンドテーブル(4Kbyte * 1block)の0クリア */
23 adr = (volatile uint64_t *)CMD_QUEUE;
24 for(i = 0; i < CMD_QUEUE_SZ/8; i++){
25 *(adr + i) = 0;
26 }
27
28 /* コマンドテーブルのGITS_CBASERへの登録 */
29 gic_write64(GITS_CBASER, 0xb800000000000000 + CMD_QUEUE);
30 /* 書き込み位置を0クリア */
30 gic_write64(GITS_CWRITER, 0x0000000000000000);
31
32 /* ITS有効 */
33 gic_write(GITS_CTLR, 0x00000001);
8-12行目でデバイステーブルを0クリアします。実験では最小単位の4Kbyteを1block割り当てます。
14-18行目でITTを0クリアします。実験では16entryとし、エントリサイズはLS2088Aでは4byteです。
20行目でGITS_BASERにデバイステーブルのアドレス(DEVICE_TABLE)とキャッシュ有効なノーマルメモリ属性を設定します。また、Page_Sizeは4Kbyte(00b)、16Kbyte(01b)、64Kbyte(10b)からの選択で4Kbyteを選択し、その個数を1blockとするために、Sizeにはマイナス1した0を設定します。以下にGITS_BASERの構成を示します。詳細はGICの資料(参考資料1)を参照して下さい。
21-26行目でコマンドテーブルを0クリアします。実験では最小単位の4Kbyteを1block割り当てます。
29行目でGITS_CBASERにコマンドテーブルのアドレス(CMD_QUEUE)とキャッシュ有効なノーマルメモリ属性を設定します。また、Sizeは1blockとするため、GICの資料(参考資料1)のGITS_CBASERの説明から、Sizeにはマイナス1した0を設定します。以下にGITS_CBASERの構成を示します。
30行目でGITS_CWRITERに0を書き込み、書き込み位置をコマンドテーブルの先頭にします。
33行目でGITS_CTLRのEnabledを有効にして、ITS機能を有効にします。
#ITSコマンドの実行
LPIテーブルやITSテーブルに設定値を書き込むために、ITSコマンドを実行します。
実験では、DeviceID=1、EventID=2のイベントが発生したとき、コア0に8194番のLPI割込みが発生するように設定します。
実験で使用したITSコマンド実行のソースの一部を以下に示します。
1 adr = (volatile uint64_t *)CMD_QUEUE;
2 /* MAPD DeviceID=1, ITT_ADDRESS, size=3+1 */
3 *adr++ = 0x0000000100000008;
4 *adr++ = 0x0000000000000003;
5 *adr++ = 0x8000000000000000 + ITT_ADDRESS;
6 *adr++ = 0x0000000000000000;
7 /* MAPTI DeviceID=1, INTNO=0x2002, EventID=2, ICID=0 */
8 *adr++ = 0x000000010000000a;
9 *adr++ = 0x0000200200000002;
10 *adr++ = 0x0000000000000000;
11 *adr++ = 0x0000000000000000;
12 /* MAPC RDBase=0, ICID=0 */
13 *adr++ = 0x0000000000000009;
14 *adr++ = 0x0000000000000000;
15 *adr++ = 0x8000000000000000;
16 *adr++ = 0x0000000000000000;
17 /* SYNC RDBase=0 */
18 *adr++ = 0x0000000000000005;
19 *adr++ = 0x0000000000000000;
20 *adr++ = 0x0000000000000000;
21 *adr++ = 0x0000000000000000;
22
23 /* ITSコマンド実行 */
24 gic_write64(GITS_CWRITER, (uint64_t)adr - CMD_QUEUE);
25
26 /* 読み込み位置が書き込み位置に来るまで待つ */
26 val = gic_read64(GITS_CWRITER);
27 while(val != gic_read64(GITS_CREADR));
2-6行目でMAPDコマンドを設定します。デバイステーブルにDeviceID=1のITTのアドレス(ITT_ADDRESS)と、エントリ数を設定します。
実験ではITTのエントリ数を16entryにするため、GICの資料(参考資料1)のMAPDコマンドの説明から2の4乗の4という値からマイナス1した3をSizeに設定します。以下にMAPDコマンドの構成を示します。
7-11行目でMAPTIコマンドを設定します。ITTにEventID=2の割込み番号は8194番であることを設定します。ICIDには0を設定します。以下にMAPTIコマンドの構成を示します。
12-16行目でMAPCコマンドを設定します。ICID=0と関係のあるコア番号(RDBase)を設定します。以下にMAPCコマンドの構成を示します。
17-21行目でSYNCコマンドを設定します。ITSの設定内容を指定コアのLPIへ通知し同期をとります。以下にSYNCコマンドの構成を示します。
24行目でGITS_CWRITERを更新して、書き込み位置を移動させ、27行目で読み込み位置が書き込み位置と同じになるのを待つことで、コマンドが実行されたことを確認します。
この時点でのデバイステーブルとITTのメモリ内容を、u-bootで見てみると以下のようになっています。
=> md.q 0xa1000000 10
a1000000: 0000000000000000 80030000a1001000 ................
a1000010: 0000000000000000 0000000000000000 ................
a1000020: 0000000000000000 0000000000000000 ................
a1000030: 0000000000000000 0000000000000000 ................
a1000040: 0000000000000000 0000000000000000 ................
a1000050: 0000000000000000 0000000000000000 ................
a1000060: 0000000000000000 0000000000000000 ................
a1000070: 0000000000000000 0000000000000000 ................
=> md.l 0xa1001000 10
a1001000: 00000000 00000000 80002002 00000000 ......... ......
a1001010: 00000000 00000000 00000000 00000000 ................
a1001020: 00000000 00000000 00000000 00000000 ................
a1001030: 00000000 00000000 00000000 00000000 ................
=>
#割込みの発生と確認
LPI割込みを受信できる条件に整えてLPI割込みを発生させ、割込み発生時の割込み番号を確認します。割込み発生時の割込み番号を確認するためにベクタテーブルを書き換えて確認します。
実験で使用した割込み発生のソースの一部を以下に示します。(v8sys_write関数はシステムレジスタに値を書き込む関数)
1 /* ベクタテーブルを書き換える */
2 v8sys_write("VBAR_EL3", (uint64_t)vector_table);
3
4 /* Non-secure group1割込みを有効にする */
5 gic_write(GICD_CTLR, 0x00000037);
6
7 /* 割込みマスクレベルを0xa0より低くする */
8 v8sys_write("ICC_PMR", 0xf0);
9
10 /* CPUの割込みを許可状態にする */
11 v8sys_write("DAIF", 0x0000);
12
13 /* INT DeviceID=1, EventID=2 */
14 *adr++ = 0x0000000100000003;
15 *adr++ = 0x0000000000000002;
16 *adr++ = 0x0000000000000000;
17 *adr++ = 0x0000000000000000;
18 gic_write64(GITS_CWRITER, (uint64_t)adr - CMD_QUEUE);
2行目で割込み発生時の割込み番号を確認するためにベクタテーブルを書き換えます。(u-bootには戻れなくなります)
5行目でLPI割込みはNon-secureのgroup1に属する割込みなので、Non-secure group1の割込みを有効にします。
8行目で割込みの優先度を0xf0に設定します。LPI割込みの優先度(0xa0)より低い優先度にしてLPI割込みを受信できる状態にします。
11行目でCPU側のIRQ割込みを受付許可状態にします。
13-18行目でITSコマンドのINTコマンドを使用して、DeviceID=1、EventID=2の割込みをかけます。以下にINTコマンドの構成を示します。
割込みをかければ、vector_tableから0x280(Current Exception level with SP_ELxのIRQ)オフセットした場所に飛び、ICC_IAR1レジスタを確認すると、8194番が記されていることが確認できます。
#####最後に
低レベルな操作でLPI+ITS機能を使用する方は少ないと思いますが、LPI+ITSを理解したいと思っている方に少しでも参考になれば幸いです。
##参考資料
参考資料1: ARM® Generic Interrupt Controller Architecture Specification (GIC architecture version 3.0 and version 4.0) [ARM IHI 0069C]
参考資料2: GICv3 and GICv4 Software Overview [ARM DAI 0492B]
参考資料3: Arm® Architecture Reference Manual (Armv8, for Armv8-A architecture profile) [ARM DDI 0487E.a]
参考資料4: QorIQ LS2088A Reference Manual [Rev.0.1,02/2020]
『各種製品名は、各社の製品名称、商標または登録商標です。本記事に記載されているシステム名、製品名には、必ずしも商標表示((R)、TM)を付記していません。』