# RISC-Vコア依存部 設計メモ
- 作成者: 本田晋也
- 最終更新: 2024年04月16日


# 略号
- ABI : Application Binary Interface
- CSR : Control and Status Register


# 参考文献
[1] TOPPERSプロジェクト 設計メモ「ARMのアーキテクチャに関するメモ」．


# RISC-Vコア依存部の位置づけ

RISC-Vコア依存部は，カーネルのターゲット依存部の中で，RV32I/RV64I/RV32Eに準拠したプロセッサコアを持つターゲットシステム（チップ）に共通に使用できる部分である．

ただし，これに該当するすべてのRISC-Vコアに対応できているわけではない．新しいチップにポーティングする際には，コア依存部に何らかの修正が必要になることも多いと思われる．

RISC-Vコアのプロセッサコア略称を"riscv"とする．GNU開発環境向けのRISC-Vコア依存部を置くディレクトリはarch/riscv_gcc/commonとなる．

RISC-Vの場合，一部の割込みのためのローカルの割込みコントローラ（CLIC）が定義されているがチップによっては無効となっている場合ある．そのため，ローカル割込みに関する記述はRISC-Vコア依存部に含めるが無効することを可能とする．

多くの外部割込みは外部割込みコントローラを経由して接続する．外部割込みコントローラは，任意のものを接続することが可能である．外部割込みコントローラに依存部依存する部分はRISC-Vコア依存部に含めないことを原則とするが，多くのチップに採用されている割込みコントローラであるPLIC（Platform-Level Interrupt Controller）に依存する部分（PLIC依存部）は，RISC-Vコア依存部のディレクトリに置いている．

今後，その他の標準化された外部割込みコントローラについては，RISC-Vコア依存部のディレクトリに置く可能性がある．

# 要検討事項

- SBIの使用
	- 需要は？
	- S-modeでOSを実行する場合はSBIを使用する方法がある．SBIには割込みの操作関数はない．

# 必須となる拡張

RISC-Vコア依存部では，Base Integer Instruction Setに加えて以下の拡張を必須とする．

- “Zicsr”, Control and Status Register (CSR) Instructions
	- CSR へのアクセスに使用する．
- “A” Standard Extension for Atomic Instructions
	- コア間の排他制御に使用

# 各拡張への対応

## RV32とRV64のコード共有

### __riscv_xlen マクロによる切り替え
- RV32とRV64でコードを切り分ける必要がある場合は，RISC-V C API Specification で定義された __riscv_xlen マクロで切り替える．
- RV32の場合は'32'に，RV64の場合は'64'に定義される．

### アセンブラマクロ
アセンブラに切り替えのifdefを減らすために，次のマクロの定義を切り替える．
- XREG_SIZE : 汎用レジスタのバイトサイズ
- INTPTR_T_SHIFT : intprt_t型のテーブルにアクセスする際のシフト数
- lx : 汎用レジスタのロード命令
- sx : 汎用レジスタのストア命令
- lwu : 32bitロードの0拡張

### Machine Timer のレジスタのアクセス

RV32では4byte単位，RV64では8byte単位でアクセスするよう切り替える．

### サイクルカウンタのアクセス

RV32では4byte単位，RV64では8byte単位でアクセスするよう切り替える．

### intprt_t 型変数のアライメントサイス

RV32では4byte単位，RV64では8byte単位でアクセスするよう切り替える．

### xlog_sysの出力フォーマと

RV32では "0x%08x" で，RV64では，"0x%016lx"で汎用レジスタの値を出力する．

## RV32E対応
- RV32E対応でコードを切り分ける必要がある場合は，RISC-V C API Specification で定義された __riscv_32e マクロで切り替える．	
- RV32Eの場合は'1'に定義される
- 現状の切り替え内容は次の通りである
	- CPU例外の情報を記憶しているメモリ領域の構造
		- t3- t6(x28-31),a6(x16),a7(x17) を含まない	
	- スタックサイズのアライン単位（CHECK_STKSZ_ALIGN）
		- RV32Eは4byteそれ以外は16byte
	- ディスパッチャや割込み・例外ハンドラ出入口処理
		- x16-x31 を保存復帰対象としない

### ディスパッチャや割込み・例外ハンドラ出入口処理でレジスタの保存・復帰数を定義するマクロ
- TNUM_RISCV_DISP_XREG
	- ディスパッチ時に保存する汎用レジスタ数
- TNUM_RISCV_INT_XREG
	- 割込み・例外ハンドラ入口処理で保存する汎用レジスタ数
- TNUM_RISCV_INT_DISP_XREG
	- 割込み・例外ハンドラからのディスパッチで保存する汎用レジスタ数

## F/D拡張対応
- F拡張及びD拡張対応でコードを切り分ける必要がある場合は，RISC-V C API Specification で定義された __riscv_flen マクロで切り替える．
- F拡張の場合は'32'に，D拡張の場合は'64'に定義される．

### アセンブラマクロ
- アセンブラに切り替えのifdefを減らすために，次のマクロの定義を切り替える．
	- FREG_SIZE : 浮動小数点数用汎用レジスタのバイトサイズ
	- lf : 浮動小数点数用汎用レジスタのロード命令
	- sf : 浮動小数点数用汎用レジスタのストア命令

###  CPU例外の情報を記憶しているメモリ領域の構造
- 浮動小数点数用汎用レジスタの型を FREG_T に定義する
- F/D拡張をサポートする際にはレジスタを追加

### ディスパッチャや割込み・例外ハンドラ出入口処理
- F/D拡張をサポートする際には浮動小数点数用汎用レジスタを保存復帰の対象とする．

### ディスパッチャや割込み・例外ハンドラ出入口処理でレジスタの保存・復帰数を定義するマクロ
- TNUM_RISCV_DISP_FREG
	- ディスパッチ時に保存する浮動小数点汎用レジスタ数
- TNUM_RISCV_INT_FREG
	- 割込み・例外ハンドラ入口処理で保存する浮動小数点汎用レジスタ数
- TNUM_RISCV_INT_DISP_FREG
	- 割込み・例外ハンドラからのディスパッチで保存する浮動小数点汎用レジスタ数

# システム状態の管理

## プロセッサモード

FMP3カーネルのRISC-Vコア依存部では，アプリケーションプログラムとカーネルの大部分を，マシンモードで実行する．

スーパーバイザーモードで動作させる方法もあるが，RTOSを動作させるレベルのチップではスーパーバイザーモードを持たない場合があることから，マシンモードでの実行とする．

RISC-Vでは現在の動作モードを取得する機能がないことから，FMP3カーネルのスタートアップコードを呼び出す時点でマシンモードとなっている必要がある．

## カーネル管理外の割込み

RISC-Vコア依存部の標準では，カーネル管理外の割込みは設定しない．ただし，ターゲット依存でカーネル管理外割込みを設定したい場合は，TOPPERS_SUPPORT_NONKERNELINT をマクロ定義することにより，CPUロック状態への遷移や解除関数を無効し，ターゲット依存部で設定することが可能である．

## 全割込みロック状態の管理

全割込みロック状態では，マシン割込みをマスクする．具体的にはmstatus.MIEをクリアする．

## コンテキストの管理

実行中のコンテキストをプロセッサモードで判断することができないため，例外（割込みハンドラおよびCPU例外ハンドラ）のネスト段数を変数（これを，例外ネストカウント(exncnt)と呼ぶ）で管理し，例外ネストカウントが0の時にタスクコンテキスト，0より大きい場合に非タスクコンテキストであると判断する．

## CPUロック状態の管理

標準では，CPUロック状態では，マシン割込みをマスクする．具体的にはmstatus.MIEをクリアする．
全割込みロック状態とCPUロック状態を区別できる必要はないことから，mstatus.MIEビットが立っていれば，CPUロック状態であると判断す
る．

## 割込み優先度マスクの管理

割込み優先度マスクの管理は，割込みコントローラによって行う．そのため，RISC-V依存部には含めない．

PLICの場合には，priority threshold レジスタにより，割込み優先度マスクを管理する．

## 割込み要求禁止フラグの管理

割込み要求禁止フラグの管理は，割込みコントローラによって行う．そのため，RISC-V依存部には含めない．

PLICの場合には，割込みを割り付けた Hart n M-mode enables レジスタにより，セット/クリアする．

## 割込みに関するその他の操作

割込みに関するその他の操作は，割込みコントローラによって実現する．そのため，RISC-V依存部には含めない．

PLICの場合には，割込み要求のプローブは，Interrupt Pending Register によって実現する．割込み要求のクリアはサポートしない．


# タスクコンテキストの保存復帰
RISC-Vは様々な拡張が用意されており，拡張によってはタスクコンテキストとして保存・復帰するレジスタが追加される．
RISC-Vコア依存部では汎用レジスタと浮動小数点汎用レジスタに対するコードのみを記載し，他の拡張に必要となるレジスタはターゲット依存部でマクロとして記載することにする．

コンテキスト保存時の保存の順序は，次の順序とする．このような順序にしているのは，浮動小数点を含む拡張の有無によらず，汎用レジスタがスタックのトップに保存するすることで，デバッグを容易化するためである．

- タスクコンテキスト保存追加マクロによる保存
- 浮動小数点汎用レジスタの保存
- 汎用レジスタの保存

## タスクコンテキスト保存復帰追加マクロ
タスクコンテキストを保存するコードの前と，コンテキストを復帰するコードの後にそれぞれ以下のマクロを呼び出す．ターゲット依存で追加のコンテキストを保存・復帰したい場合は各マクロの内容を記述すること．これらのマクロ内ではsp以外の汎用レジスタは使用できない．spの示すスタックに保存・復帰したのち，spの値を調整すること．

- save_additional_regs_disp
	- ディスパッチ時の追加コンテキストの保存
- restore_additional_regs_disp
	- ディスパッチ時に保存した追加コンテキストの復帰
- save_additional_regs_int
	- 割込み・例外ハンドラ入口処理での追加コンテキストの保存
- restore_additional_regs_int
	- 割込み・例外ハンドラ出口処理で保存した追加コンテキストの復帰
- save_additional_regs_int_disp
	- 割込み・例外ハンドラからのディスパッチ時の追加コンテキストの保存
- restore_additional_regs_int_disp
	- 割込み・例外ハンドラからのディスパッチ時に保存した追加コンテキストの復帰


# 例外（割込みハンドラおよびCPU例外ハンドラ）の出入口処理

割込みハンドラおよびCPU例外ハンドラの入口処理は別に用意する．

ベクタテーブルのダイレクトモードと個別ベクターモードの両方に対応する．ダイレクトモードを有効とする場合は， USE_RISCV_DIRECT_TRAP マクロを定義する．

ダイレクトモードでは，t0レジスタをmscratchに一旦保存してmcauseを読み込みMSBが'0'なら例外の入口処理を呼び出す．例外の入口処理ではmscratchの値をt0に戻す．

入口処理では次の保存処理を行う．保存するレジスタは次の通りである．

- 浮動小数点数汎用レジスタ
	-  Caller save レジスタ : f0-7, f10-11,f12-17,f28-31
- 汎用レジスタ
    -  Caller save レジスタ : x1, x5-x7, x10-11, x12-17, x28-31
	-  tp(x4) : 現状使用していないがタスクのコンテキストとして保存
- リターンアドレス : mpc
- ステータスレジスタ : mstatus
	- CPUロック状態等を元に戻すために保存

汎用レジスタの保存については，RV32Eが持つレジスタを保存したのち，mpc/mstatusを保存してから，RV32Eが持たない汎用レジスタを保存する．


```
#ifdef __riscv_flen
    /* RV32Dの場合はこの時点でスタックの調整が必要?(ToDo) */
    addi sp, sp, -(TNUM_RISCV_INT_FREG * FREG_SIZE)
    sf   ft0,   0*FREG_SIZE(sp)  /* f0  */
    sf   ft1,   1*FREG_SIZE(sp)  /* f1  */
    sf   ft2,   2*FREG_SIZE(sp)  /* f2  */
    sf   ft3,   3*FREG_SIZE(sp)  /* f3  */
    sf   ft4,   4*FREG_SIZE(sp)  /* f4  */
    sf   ft5,   5*FREG_SIZE(sp)  /* f5  */
    sf   ft6,   6*FREG_SIZE(sp)  /* f6  */
    sf   ft7,   7*FREG_SIZE(sp)  /* f7  */
    sf   fa0,   8*FREG_SIZE(sp)  /* f10 */
    sf   fa1,   9*FREG_SIZE(sp)  /* f11 */
    sf   fa2,  10*FREG_SIZE(sp)  /* f12 */
    sf   fs3,  11*FREG_SIZE(sp)  /* f13 */
    sf   fa4,  12*FREG_SIZE(sp)  /* f14 */
    sf   fa5,  13*FREG_SIZE(sp)  /* f15 */
    sf   fa6,  14*FREG_SIZE(sp)  /* f16 */
    sf   fa7,  15*FREG_SIZE(sp)  /* f17 */
    sf   ft8,  16*FREG_SIZE(sp)  /* f28 */
    sf   ft9,  17*FREG_SIZE(sp)  /* f29 */
    sf   ft10, 18*FREG_SIZE(sp)  /* f30 */
    sf   ft11, 19*FREG_SIZE(sp)  /* f31 */
#endif /* __riscv_flen */
    addi sp, sp, -(TNUM_RISCV_INT_XREG * XREG_SIZE)
    sx   ra,  0*XREG_SIZE(sp)  /* x1  */
    sx   tp,  1*XREG_SIZE(sp)  /* x4  */
    sx   t0,  2*XREG_SIZE(sp)  /* x5  */
    sx   t1,  3*XREG_SIZE(sp)  /* x6  */
    sx   t2,  4*XREG_SIZE(sp)  /* x7  */
    sx   a0,  5*XREG_SIZE(sp)  /* x10 */
    sx   a1,  6*XREG_SIZE(sp)  /* x11 */
    sx   a2,  7*XREG_SIZE(sp)  /* x12 */
    sx   a3,  8*XREG_SIZE(sp)  /* x13 */
    sx   a4,  9*XREG_SIZE(sp)  /* x14 */
    sx   a5, 10*XREG_SIZE(sp)  /* x15 */
    csrr t0, mepc
    sx   t0, 11*XREG_SIZE(sp)
    csrr t0, mstatus
    sx   t0, 12*XREG_SIZE(sp)
#ifndef __riscv_32e
    sx   t3, 13*XREG_SIZE(sp)  /* x28 */
    sx   t4, 14*XREG_SIZE(sp)  /* x29 */
    sx   t5, 15*XREG_SIZE(sp)  /* x30 */
    sx   t6, 16*XREG_SIZE(sp)  /* x31 */
    sx   a6, 17*XREG_SIZE(sp)  /* x16 */
    sx   a7, 18*XREG_SIZE(sp)  /* x17 */
#endif /* !__riscv_32e */
```

## スタックポインタの調整

RISC-Vでは，外部インタフェースにおいてスタックポインタを16バイト境界（RV32Eは4バイト）にアラインさせなければならないが，あくまで外部インタフェースにおける要求事項であるため，例外発生時にそれが満たされているとは限らない．そこで，割込みハンドラおよびCPU例外ハンドラの入口で，スタックポインタが16バイト境界にアラインしていない場合には，16バイト境界にアラインするようにスタックポインタを調整する．また，出口処理でスタックポインタを元に戻せるように，調整量を保存する．具体的なコードは次の通り．

RV32Eにおいては，16バイト境界にアラインしても問題ないため共通のコードとしている．

```
	/*
     *  スタックポインタの調整
     */
    andi t0, sp, 0xf
    sub  sp, sp, t0
    addi sp, sp, -16  /* スタックポインタの調整値を保存 */
    sx   t0, 0(sp)
```

このコードで，t0には，スタックポインタが16バイト境界にアラインしている場合には0が，アラインしていない場合には4から12が入る．スタックポインタをt0の分調整して16バイト境界になるようにし，t0をスタックに保存する．

割込みハンドラおよびCPU例外ハンドラの出口では，スタックポインタの調整量をスタックから復帰し，スタックポインタを元に戻す．具体的なコードは次の通り．

```
    lx   t0, 0(sp)   /* スタックポインタの調整を元に戻す */
    addi sp, sp, 16
    add  sp, sp, t0
```

# 割込みハンドラの出入口処理

FMP3カーネル ポーティングガイドに従って，割込みハンドラの出入口処理の実装方法を検討していく．

```
void
<割込みの出入口処理>(void)
{
	少なくともカーネル管理の割込みを禁止した状態にする … (*d)
	スクラッチレジスタをスタックに保存する … (*p)
```

RISC-Vコアは，例外を受け付けると，mstatus.MIEがクリアされ割込みを禁止するため，(*d)の処理は必要ない．スクラッチレジスタの保存は，例外の入口で実装済みである．

```
	if (割込みがタスクコンテキストで発生) {
```

まず，例外ネストカウントをインクリメントする．インクリメント後の値が1でない場合には，非タスクコンテキストでの割込み発生であるため，この後の{}内の処理をスキップする（core_int_entry_1に分岐）．

```
		スタックを非タスクコンテキスト用のスタックに切り換え，
								非タスクコンテキストに切り換える
	}
```

スタックを非タスクコンテキスト用のスタックに切り換え，切り換え前のスタックポインタを切り換え後のスタック上に保存する．これらの処理の後に，core_int_entry_1ラベルを置く．

```
	割込み優先度マスクを，受け付けた割込みの割込み優先度に設定する
	割込みハンドラの入口で必要なIRCの操作を行う（割込み要求ラインがエッ
				ジトリガである場合のトリガされた割込み要求のクリアなど）
```

割込み優先度マスクの管理は，チップ依存の割込みコントローラによって行う必要があるため，これらの処理は，チップ依存部のirc_begin_intで行う．すなわち，irc_begin_intは，割込み優先度マスクを受け付けた割込み要求の割込み優先度に設定し，割込みハンドラの入口で必要なIRCの操作を行う．

またirc_begin_intは，以下の情報をを返すようにする．

- a0:割込み番号
- a1:割込み前の優先度(内部表現)
- a2:割込みハンドラアドレス

スプリアス割込みの場合には，irc_begin_intは割込み番号0を返すものとし，その場合には，割込みハンドラの呼出し処理をスキップする（core_int_entry_2に分岐）．

```
    /*
     *  Spurious割込みなら何もしない
     */
    beqz a0, core_int_entry_2
```

この後の処理のために一旦irc_begin_intがリターンした値をスタックに格納する．

```
    /* 
     *  割込み関連の情報を保存
     */
    addi sp, sp, -(4*XREG_SIZE) /* XLEN=64でも32でも16byte境界となる */
    sx   a0, 0*XREG_SIZE(sp)    /* 割込み番号保存 */
    sx   a1, 1*XREG_SIZE(sp)    /* 割込み前の優先度(内部表現)保存 */
    sx   a2, 2*XREG_SIZE(sp)    /* 割込みハンドラアドレス保存 */
```


```
	CPUロック解除状態にする（受け付けた割込みよりも優先度の高い割込みを
												受け付けるようにする）
```

CPUロック解除状態にするために，次のマクロを呼び出す．カーネル管理外の割込みを設けない場合は，コア依存部の定義が有効となり，msatatus.MIEをセットする．
カーネル管理外の割込みを設ける場合は，ターゲット依存部でこのマクロを定義する．

```
    /*
     *  CPUロック解除状態 & 全割込みロック解除状態とするにする．
     */
    unlock_cpu_and_unlock_allint_asm  t0
```


```
#ifdef LOG_INH_ENTER
	log_inh_enter(割込みハンドラ番号);
#endif /* LOG_INH_ENTER */
```

LOG_INH_ENTERが定義されている場合には，割込みハンドラ番号をパラメータとして，log_inh_enterを呼び出す．

```
	割込みハンドラを呼び出す
```

割込みハンドラテーブル（inh_table）から割込みハンドラの番地を取り出して
呼び出す．

```
#ifdef LOG_INH_LEAVE
	log_inh_leave(割込みハンドラ番号);
#endif /* LOG_INH_LEAVE */
```

LOG_INH_LEAVEが定義されている場合には，割込みハンドラ番号をパラメータとして，log_inh_leaveを呼び出す．

```
	スピンロックを取得していれば解放する．
```

自プロセッサのPCBを引数に force_unlock_spin を呼び出す．

```
	少なくともカーネル管理の割込みを禁止した状態にする
```

カーネル管理の割込みを禁止するために，次のマクロを呼び出す．カーネル管理外の割込みを設けない場合は，コア依存部の定義が有効となり，msatatus.MIEをクリアする．
カーネル管理外の割込みを設ける場合は，ターゲット依存部でこのマクロを定義する．

```
    /*
     *  カーネル管理の割込みを禁止する．
     */
    lock_cpu_asm t0
```


```
	割込みハンドラの出口で必要なIRCの操作を行う（IRCに対する割込み処理
														の終了通知など）
	割込み優先度マスクを，割込みを受け付ける前の値に戻す … (*f)
```

これらの処理は，チップ依存部のirc_end_intで行う．すなわち，irc_end_intは，割込みハンドラの出口で必要なIRCの操作を行い，割込み優先度マスクを
割込み処理前の状態に戻す．

コア依存部からは，irc_end_intには次のパラメータを渡す．

- a0:割込み番号
- a1:割込み前の優先度(内部表現)

```
	if (割込みがタスクコンテキストで発生) {
```

例外ネストカウントをデクリメントする．デクリメント後の値が0でない場合には，非タスクコンテキストでの割込み発生であるため，{}内の処理をスキップする（core_int_entry_5に分岐）．

```
		スタックをタスク用のスタックに戻し，タスクコンテキストに切り換える
																… (*g)
```

タスクコンテキストのスタックポインタは，この時点で，スタックの先頭に保存されているため，スタックの先頭を取り出して，スタックポインタに設定する．

```
		if (p_runtsk == NULL) {
```

p_runtskがNULLでなければ，この後の{}内の処理をスキップする（core_int_entry_3に分岐）．

```
			タスクのスタックに保存したスクラッチレジスタ等を捨てる … (*o)
```

ここでは，スタックに保存したスクラッチレジスタを捨てる．また，スタックポインタの調整を元に戻すことも必要である．

```
			CPUロック状態にする … (*h)
			割込み優先度マスクは，全解除状態（TIPM_ENAALL）になって
												いなければならない … (*i)
			dispatcher_0に分岐する
		}
```

(*h)の処理は，irc_end_intを呼び出す前に行ったカーネル管理の割込みを禁止する処理で十分であるため，ここでは行わない．(*i)については，irc_end_intで割込み優先度マスクを割込み処理前の状態に戻すことで，満たされているはずである．そこで，単にdispatcher_0に分岐すればよい．

```
		if (p_runtsk != p_schedtsk) {
```

この処理の前に，core_int_entry_3ラベルを置く．p_runtskとp_schedtskが一致する時は，core_int_entry_4へ分岐する．

```
			CPUロック状態にする … (*h)
			割込み優先度マスクは，全解除状態（TIPM_ENAALL）になって
												いなければならない … (*i)
```

(*h)の処理は，irc_end_intを呼び出す前に行ったカーネル管理の割込みを禁止する処理で十分であるため，ここでは行わない．(*i)については，irc_end_intで割込み優先度マスクを割込み処理前の状態に戻すことで，満たされているはずである．

```
			スクラッチレジスタを除くすべてのレジスタをスタックに保存する
			スタックポインタを自タスク（p_runtsk）のTCBに保存する
			ret_int_rを，実行再開番地として自タスクのTCBに保存する … (*a)
			dispatcherに分岐する

		  ret_int_r:
			スクラッチレジスタを除くすべてのレジスタをスタックから復帰する
		}
```

すなおに実装する．


```
	}
	割込み処理からのリターンにより，CPUロック解除状態に戻るように準備する
	スクラッチレジスタをスタックから復帰する … (*p)
	割込み処理からのリターン
}
```

これらの処理の前に，core_int_entry_5のラベルを置く．これらの処理は，例外からの出口で行う．

割込み処理からのリターンにより，CPUロック解除状態に戻るように準備する以下のマクロを呼び出す．カーネル管理外の割込みを設けない場合は，コア依存部の定義が有効となり，何も処理を行わない．カーネル管理外の割込みを設ける場合は，ターゲット依存部でこのマクロを定義する．

```
    /*
     *  割込み処理からのリターンにより
     *  CPUロック解除状態になるように設定
     */
    ret_int_prepare_unlock_cpu_asm t0
```


# CPU例外ハンドラの出入口処理

FMP3カーネル ポーティングガイドに従って，CPU例外ハンドラの出入口処理の実装方法を検討していく．

```
void
<CPU例外の出入口処理>(void)
{
	スクラッチレジスタをスタックに保存する … (*p)
```

この処理は，例外の入口で実装済みである．

```
	CPU例外が発生した状況を判断するための追加情報をスタックに保存する … (*j)
	この時点でのスタックポインタ±nを，CPU例外の情報を記憶している領域の
										先頭番地とする（必要なら保存する）
```

exc_sense_intmaskを実現するために，CPU例外が発生した時点でのコンテキストと割込み優先度マスク（割込み優先度マスクは，CPU例外がタスクコンテキストで発生した場合のみ）が必要となる．そこで，現在の例外ネストカウント
と割込み優先度マスク（CPU例外の発生で割込み優先度マスクが変わることはないため，これで良い）をスタックに保存する．ここでスタック上に保存する割込み優先度マスクは，CPU例外がタスクコンテキストで発生した場合にのみ使用するので，非タスクコンテキストで発生した場合には正しい値でなくても良い．割込み優先度マスクは，チップ依存部のirc_get_intpriを呼び出すことで取得する．

また，CPU例外の情報を記憶している領域の先頭番地として，この時点でのスタックポインタを，レジスタに保存しておく．

```
	if (カーネル管理外のCPU例外) {
```

カーネル管理外のCPU例外は，カーネル実行中，全割込みロック状態，CPUロック状態，カーネル管理外の割込みハンドラ実行中に発生したCPU例外である．

次のマクロを呼び出すことで判断する．t0 にカーネル管理なら none 0, カーネル管理外なら 0 を格納する．
カーネル管理外の割込みを設けない場合は，コア依存部の定義が有効となり，msatatus.MPIE が0ならカーネル管理外とする．カーネル管理外の割込みを設ける場合は，ターゲット依存部でこのマクロを定義する．

```
is_kernel_exception_asm t0 t1
```

カーネル管理外のCPU例外の場合は，カーネル管理外のCPU例外の出入口処理（nk_exc_entry_1）に分岐する．

```
		if (CPU例外がタスクコンテキストで発生) {
			スタックを非タスクコンテキスト用のスタックに切り換え，
									非タスクコンテキストに切り換える … (*k)
		}
```

カーネル管理外のCPU例外の出入口処理（nk_exc_entry_1）では，まず，例外ネストカウントをインクリメントする．CPU例外発生前がタスクコンテキストなら，非タスクコンテキスト用のスタックに切り換え，切り換え前のスタックポ
インタを切り換え後のスタック上に保存する．

```
		システム状態（コンテキストは除く）を，CPU例外発生時の状態にする
```

システム状態（コンテキストは除く）をCPU例外発生時の状態にするために，次のマクロを呼び出す．

```
unlock_cpu_and_unlock_allint_asm  t0
```

```
		CPU例外ハンドラを，CPU例外の情報を記憶している領域の先頭番地を
									パラメータ（p_excinf）として呼び出す
```

CPU例外ハンドラテーブル（exc_table）からCPU例外ハンドラの番地を取り出し，CPU例外の情報を記憶している領域の先頭番地を第1パラメータ，CPU例外ハンドラ番号を第2パラメータとして呼び出す．第2パラメータにCPU例外ハンドラ番号を渡すのは，default_exc_handlerで割込みハンドラ番号を表示できるようにするためで，RISC-Vコア依存部の独自拡張である．

```
		if (CPU例外がタスクコンテキストで発生) {
			スタックをタスク用のスタックに戻し，
									タスクコンテキストに切り換える … (*k)
		}
		CPU例外処理からのリターン後に，CPU例外発生時のシステム状態に
													戻るように準備する
	}
	CPU例外が発生した状況を判断するための追加情報をスタック上から捨てる
	スクラッチレジスタをスタックから復帰する
	CPU例外処理からのリターン
}
```

例外ネストカウントをデクリメントし，0になった場合には，タスク用のスタックに戻す．その後，CPU例外が発生した状況を判断するための追加情報をスタック上から捨て，スクラッチレジスタを復帰した後，例外からリターンする．

```
	else {
		if (CPU例外がタスクコンテキストで発生) {
```

カーネル管理のCPU例外の出入口処理では，まず，例外ネストカウントをインクリメントする．CPU例外発生前が非タスクコンテキストなら，この後の{}内の処理をスキップする（core_exc_entry_2に分岐）．

```
			少なくともカーネル管理の割込みを禁止した状態にする
```

RISC-Vコアでは，例外の受付によりmstatus.MIEがクリアされるため，この処理は必要ない．

```
			スタックを非タスクコンテキスト用のスタックに切り換え，
									非タスクコンテキストに切り換える
		}
```

スタックを非タスクコンテキスト用のスタックに切り換え，切り換え前のスタックポインタを切り換え後のスタック上に保存する．これらの処理の後に，cor_exc_entry_2ラベルを置く．

```
		割込み優先度マスクを，CPU例外発生時の値に設定する … (*l)
```

チップ依存部のirc_begin_excを呼び出し，必要であれば，割込み優先度マスクをCPU例外発生時の値に設定する．また，irc_end_excで必要な情報をスタックに保存する．

```
		CPUロック解除状態にする（CPU例外発生時の割込み優先度マスクよりも
							優先度の高い割込みを受け付けるようにする）
```

割込みを許可して，CPUロック解除状態にする．

```
#ifdef LOG_EXC_ENTER
		log_exc_enter(CPU例外ハンドラ番号);
#endif /* LOG_EXC_ENTER */
```

LOG_EXC_ENTERが定義されている場合には，CPU例外ハンドラ番号をパラメータとして，log_exc_enterを呼び出す．

```
		CPU例外ハンドラを，CPU例外の情報を記憶している領域の先頭番地を
									パラメータ（p_excinf）として呼び出す
```

CPU例外ハンドラテーブル（exc_table）からCPU例外ハンドラの番地を取り出し，CPU例外の情報を記憶している領域の先頭番地を第1パラメータ，CPU例外ハンドラ番号を第2パラメータとして呼び出す．

```
#ifdef LOG_EXC_LEAVE
		log_exc_leave(CPU例外ハンドラ番号);
#endif /* LOG_EXC_LEAVE */
```

LOG_EXC_LEAVEが定義されている場合には，CPU例がハンドラ番号をパラメータとして，log_exc_leaveを呼び出す．

```
	スピンロックを取得していれば解放する．
```

force_unlock_spin を自プロセッサのPCBを引数に呼び出す．

```
		少なくともカーネル管理の割込みを禁止した状態にする … (*m)
```

次のマクロによりカーネル管理の割込みを禁止する．

```
lock_cpu_asm t0
```

```
		割込み優先度マスクを，CPU例外発生時の値に設定する … (*n)
```

チップ依存部のirc_end_excを呼び出し，割込み優先度マスクをCPU例外発生時の値に設定する．

```
		if (CPU例外がタスクコンテキストで発生) {
```

例外ネストカウントをデクリメントする．デクリメント後の値が0でない場合には，非タスクコンテキストでのCPU例外発生であるため，{}内の処理をスキップする（core_exc_entry_4に分岐）．

```
			スタックをタスク用のスタックに戻し，タスクコンテキストに切り換える
																	… (*g)
```

タスクコンテキストのスタックポインタは，この時点で，スタックの先頭に保存されているため，スタックの先頭を取り出して，スタックポインタに設定する．

```
			if (p_runtsk == NULL) {
```

p_runtskがNULLでなければ，この後の{}内の処理をスキップする（core_exc_entry_2に分岐）．

```
				タスクのスタックに保存したスクラッチレジスタ等を捨てる … (*o)
```

ここでは，スタックに保存したスクラッチレジスタとCPU例外が発生した状況を判断するための追加情報を捨てる．また，スタックポインタの調整を元に戻すことも必要である．

```
				CPUロック状態にする … (*h)
				割込み優先度マスクは，全解除状態（TIPM_ENAALL）になって
												いなければならない … (*i)
				dispatcher_0に分岐する
			}
```

(*h)の処理は，irc_end_excを呼び出す前に行ったカーネル管理の割込みを禁止する処理で十分であるため，ここでは行わない．(*i)については，irc_end_excで割込み優先度マスクを割込み処理前の状態に戻すことで，満たされているはずである．そこで，単にdispatcher_0に分岐すればよい．

```
			if (p_runtsk != p_schedtsk) {
```

この処理の前に，core_exc_entry_2ラベルを置く．p_runtskとp_schedtskが一致する時は，co_exc_entry_3へ分岐する．

```
				CPUロック状態にする … (*h)
				割込み優先度マスクは，全解除状態（TIPM_ENAALL）になって
												いなければならない … (*i)
```

(*h)の処理は，irc_end_excを呼び出す前に行ったカーネル管理の割込みを禁止する処理で十分であるため，ここでは行わない．(*i)については，irc_end_excで割込み優先度マスクを割込み処理前の状態に戻すことで，満たされているはずである．そこで，単にdispatcher_0に分岐すればよい．

```
				スクラッチレジスタを除くすべてのレジスタをスタックに保存する
				スタックポインタを自タスク（p_runtsk）のTCBに保存する
				ret_exc_rを，実行再開番地として自タスクのTCBに保存する … (*a)
				dispatcherに分岐する

			  ret_exc_r:
				スクラッチレジスタを除くすべてのレジスタをスタックから復帰する
			}
```

この辺りは素直に実装できる．

```
		}
		CPU例外処理からのリターン後に，CPUロック解除状態に戻るように準備する
	}
	CPU例外が発生した状況を判断するための追加情報をスタック上から捨てる
	スクラッチレジスタをスタックから復帰する … (*p)
	CPU例外処理からのリターン
}
```

これらの処理の前に，core_exc_entry_4のラベルを置く．「CPU例外が発生した状況を判断するための追加情報をスタック上から捨てる」以外の処理は，例外からの出口で行う．

## スタックポインタが不正の場合の対策

現状対策はしていない．


# FPUのサポート

コンパイルオプションで，"F"ないし"D"サポートが指定された阿相は，RISC-V C API Specification で定義された __riscv_flen マクロが定義されている．このマクロが定義されている場合は，タスクコンテキストとして浮動小数点汎用レジスタを扱う．すなわち，常にタスク切替時に浮動小数点汎用レジスタを保存復帰する．


以上
