低レベルな操作によるArmのLPI+ITS割込みの発生方法

#はじめに
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構成テーブル
各割込みの優先度と有効/無効を設定するためのテーブルです。１つの割込みエントリあたり8bitで構成され(下図参照)、割込みの個数分配列構造になっています。
![LPI_config_enrty2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384597/c292ede6-1546-1f59-5b07-54a6042faaf5.png)

#####LPI保留テーブル
各割込みの保留状態を示すテーブルです。１つの割込みエントリあたり1bitで構成され、
割込みの個数分並べられた構造になっています。ただし、テーブルの先頭から1Kbyte分は未使用領域とされており、0クリアしておく必要があります。

#ITSとは
LPIを利用しやすいように補足された機能で、このデバイスのこのイベントが発生したという表現でLPI割込みを発生させられるように、DeviceIDおよびEventIDという識別子と発生させたいLPI割込みを関連付ける機能です。
ITSはGIC内に１つ存在します。

##ITSテーブル
この記事で登場するテーブルとして以下の３つのテーブルがあります。
・デバイステーブル(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構成テーブル |
| LPI保留テーブル | 0xa0010000-0xa0011fff | LPI保留テーブル |
| デバイステーブル | 0xa1000000-0xa1000fff | デバイステーブル |
| ITT | 0xa1001000-0xa100103f | ITT |
| コマンドテーブル | 0xa1010000-0xa1010fff | コマンドテーブル |

#LPIの設定と有効化
LPIテーブルの初期化およびLPI関連のレジスタを設定して、LPIを有効化します。
実験で使用したLPI設定のソースの一部を以下に示します。(gic_write、gic_write64関数はGICのレジスタに値を書き込む関数)

```c
 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の構成を示します。
![GICR_PROPBASER2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384597/daf0696d-cd67-cc43-3124-d2de53175803.png)

23行目でGICR_PENDBASERにLPI保留テーブルのアドレス(LPI_PEND_TABLE)とキャッシュ有効なノーマルメモリ属性を設定しています。以下にGICR_PENDBASERの構成を示します。
![GICR_PENDBASER2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384597/77534ca8-0c1b-de6f-271c-ba3c259f8d64.png)

26行目でLPIテーブルの設定が終わったので、GICR_CTLRのEnable_LPIsを有効にして、LPI機能を有効にします。実験ではコア0用のLPIのみ有効にします。

#ITSの設定と有効化
ITSテーブルの初期化およびITS関連のレジスタを設定して、ITSを有効化します。
実験で使用したITS設定のソースの一部を以下に示します。

```c
 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)を参照して下さい。

![GITS_BASER2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384597/0873ffab-475b-2681-2a88-7e9f4d9f2b6f.png)

21-26行目でコマンドテーブルを0クリアします。実験では最小単位の4Kbyteを1block割り当てます。

29行目でGITS_CBASERにコマンドテーブルのアドレス(CMD_QUEUE)とキャッシュ有効なノーマルメモリ属性を設定します。また、Sizeは1blockとするため、GICの資料(参考資料1)のGITS_CBASERの説明から、Sizeにはマイナス1した0を設定します。以下にGITS_CBASERの構成を示します。
![GITS_CBASER2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384597/83c02e33-0a1f-0a4d-320a-80df9cbd4fb9.png)

30行目でGITS_CWRITERに0を書き込み、書き込み位置をコマンドテーブルの先頭にします。

33行目でGITS_CTLRのEnabledを有効にして、ITS機能を有効にします。

#ITSコマンドの実行
LPIテーブルやITSテーブルに設定値を書き込むために、ITSコマンドを実行します。
実験では、DeviceID=1、EventID=2のイベントが発生したとき、コア0に8194番のLPI割込みが発生するように設定します。
実験で使用したITSコマンド実行のソースの一部を以下に示します。

```c
 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コマンドの構成を示します。
![MAPD_command2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384597/48593268-bb1a-4993-1bee-8c99d3715394.png)
7-11行目でMAPTIコマンドを設定します。ITTにEventID=2の割込み番号は8194番であることを設定します。ICIDには0を設定します。以下にMAPTIコマンドの構成を示します。
![MAPTI_command2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384597/37b62bed-f27f-ac9f-6fab-0a9508741d4d.png)
12-16行目でMAPCコマンドを設定します。ICID=0と関係のあるコア番号(RDBase)を設定します。以下にMAPCコマンドの構成を示します。
![MAPC_command2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384597/a79bfb03-e476-f6eb-81a3-248bf169bbd4.png)
17-21行目でSYNCコマンドを設定します。ITSの設定内容を指定コアのLPIへ通知し同期をとります。以下にSYNCコマンドの構成を示します。
![SYNC_command2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384597/b8a2a4e8-cfe9-d116-53e8-294e1fd805ee.png)
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関数はシステムレジスタに値を書き込む関数)

```c
 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コマンドの構成を示します。
![INT_command2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384597/866ee73e-45c3-7590-0aa5-75219136e314.png)
割込みをかければ、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）を付記していません。』