STM32マイコンでArm v7-Mの特権状態まわりについて勉強した【前編】

※この記事は以下の記事に続きます
[STM32マイコンでArm v7-Mの特権状態まわりについて勉強した【後編】](https://qiita.com/Ke_N_551/items/a65fe4c5c42cccb52694)

※最初、「特権モード」「非特権モード」と書いていたのですが、これとは別の概念の「スレッドモード」「ハンドラモード」と混同してしまいかねず、不適切な表記だったと思ったので、「特権/非特権状態」に修正しました。
紛らわしいことを書いてすみませんでした。

## 概要
以下の本を読み進めて、5章プロセス切り替えでカーネル（特権状態）←→アプリケーション（非特権状態）の切り替えを行うところまで実装しました。
（めちゃくちゃ参考にさせていただいております、ありがとうございます）
[Rustで始める自作組込みOS入門](https://garasubo.github.io/embedded-book/1_intro/index.html)

本の中に「特権状態」「非特権状態」やSVCallといったOSっぽい（？）概念が出てきたので、ついでにそこを深く掘り下げてドキュメントを読み、実装することで勉強しようと思いました。

目指すゴールとしては

* 非特権状態で実行するアプリケーションからはGPIO操作をできない（＝Lチカができない）ようにする
* アプリケーションからGPIOを操作する（＝LEDをつける）場合は、SVCallでカーネルにそ操作してもらう

というように、非特権状態のアプリケーションからSVCallをしてLチカをすることとしました。

やってみると結構色々やらないといけないことがあったので、備忘録かねて分割して記事を書くことにしました。
本記事では【前編】として、「非特権状態で実行するアプリケーションからはGPIO操作をできない」ようにすることとしました。
ですので、主に特権状態そのものについてや、メモリアクセス制限をかけるためのMPU（Memory Protection Unit）について書きます。

###環境

* パソコン：MacOS Mojave 10.14.6
* 使用言語：Rust
* ボード：STM32 NUCLEO-F446RE
* エディタ:VSCode
* デバッグ環境: VSCode（Cortex-Debugプラグイン導入）
背景知識：やや組込み初心者（基礎の基礎は知ってるものの基礎以上はわからない）

以前に書いた以下の記事と同じ環境です。
[STM32マイコンのレジスタを叩いてLチカしようとしたらどハマりした](https://qiita.com/Ke_N_551/items/91b991d61519b174a16b)
かつ、LEDの点灯にはにはこの記事で紹介した`led.rs`を使用します。

##学んだこと
特権状態（privileged mode）、およびそれを使う上で必要なMPU(Memory Protection Unit)についてマニュアルを中心に資料を読んで調べました。
今回は主にarm v7-Mのドキュメントを中心に読み込みます。
[Arm®v7-M Architecture Reference Manual](https://developer.arm.com/docs/ddi0403/ed/armv7-m-architecture-reference-manual)

###特権状態について
まずは特権状態（privileged mode)と非特権状態（unprivileged mode)について調べます。

arm-v7のドキュメントで見つけた、「A3.6.1 Privilege level access controls for data access」という項目にたどり着いたのでそこを読み進めました。
軽くざっくりと理解すると

1. 各メモリ領域について
    * 全アクセス拒否
    * 特権状態でのアクセスのみ許可
    * 全アクセス許可  
を定めることができる
1. アクセス違反は（MemManage）例外を引き起こす
1. **MPU(Memory Protection Unit)が実装されており有効になっている**時のみ、特権/非特権によるアクセス制限をかけられる  
(Noteの部分に書いてあります）

という感じでした。
（もっと細かく書いてあったのですが必要部分以外読み飛ばしています・・・後日読んで理解し次第追記します）

3.にある通り、特権状態を利用してアクセス保護を行うにはMPUを有効化しないとダメそうでした。
「実装されている」に関しては、使用するF446REボードのマニュアルを読み、MPUが実装されていることを確認しました。
次に「有効にする」必要があるため、Note部分に貼ってあるリンクから「B3.5 Protected Memory System Architecture, PMSAv7」に飛んでMPUについて理解します。

###MPUについて

というわけでMPU(Memory Protection Unit)を有効にし、GPIOの操作をするメモリ領域を特権状態からしかアクセスできないようにします。

（理解しないといけない項目が多すぎたので、目標を達成することを優先しざっくりと理解することとしました。
ので間違った内容・理解があると思います・・・ご指摘いただけると非常にありがたいです）

Arm v7-MのMPUについてはマニュアルの「Protected Memory System Architecture, PMSAv7」に書いてあります。
このマニュアルだけではほとんど理解できいなかったのですが、以下のWebページは具体的なコードもあり、参考になりました。
[Setting up the Cortex-M3/4 (ARMv7-M) Memory Protection Unit (MPU) - Stickey Bits -Powered by Feabhas](https://blog.feabhas.com/2013/02/setting-up-the-cortex-m34-armv7-m-memory-protection-unit-mpu/)

ざっくり理解すると

* メモリ空間の一部をregionとして設定し、それぞれのregionについてアクセス権などの設定が行える
* regionは**こちらで指定してやる必要がある**
* MPUを実装しているか否か、およびMPUで管理できるregionの数はプロセッサの実装によって異なる  
(ただしArm v7-Mの場合8regionのようです)

また、「B3.5.4 Register support for PMSAv7 in the SCS」にMPUの操作を行うレジスタのアドレスとその詳細が記載されています。

| アドレス       |名前|  読み書き属性 | 説明     |
|:-----------------|:------------:|:-----|:----------------|
| 0xE000ED90|MPU_TYPE|**RO**|実装されているMPUの情報を返す|
| 0xE000ED94|MPU_CTRL|RW|MPUの設定をする|
| 0xE000ED98|MPU_RNR|RW|**RBAR/RASRレジスタで設定するresionの番号を指定する**|
|0xE000ED9C|MPU_RBAR|RW|regionの開始アドレスを設定する|
| 0xE000EDA0|MPU_RASR|RW|regionのサイズやアクセス権限を設定する|
（エイリアス？についても書いてあったのですが省略しています・・・・）

それぞれ軽く説明すると
####MPU_TYPE
実装されているMPUの情報を返します。
特に、readした時に[15:8]のビットに入っている値はMPUが管理可能なregion数を表しており、**MPUがプロセッサに実装されていない場合ここに0が入ります。**
Cortex-M4を採用しているボードであれば大丈夫？だとは思うのですが、念の為以下のようなコードで値を読み出して0出ないことを確認しておくと安心です。

```rust
let mpu_type_value = read_volatile(0xE000_ED90 as *const u32);
hprintln!("value: {:b}",mpu_type_value);
```
二進数であれば`10000000000`,10進数であれば`2048`になるはずです。

#### MPU_CTRL
MPUの設定を行います。特に有効/無効の切り替えはこのレジスタの[0]ビットに値を書き込んで行います。
ただし、マニュアルのP.B3-636のNoteにある通り、こいつをenableする前にMPU_RASRをちゃんと初期化してあげないといけないみたいです。
（始めこれを正しく指定せずMPUを有効していたのですが、変に制限がかかるらしく非特権状態への切り替え時にHardFaultが発生しました）

#### MPU_RNR (Region Number Register)
以下二つのレジスタで指定するregionの番号をここで指定します。
例えば、番号3のregionについて領域や権限を設定したい場合、まずこのレジスタの[7:0]に3を書き込み、その後`MPU_RBAR`と`MPU_RASR`への値の書き込みを行います。

#### MPU_RBAR (Region Base Address Register)
`MPU_RNR`で指定した番号のregionに割り当てるメモリ領域の開始アドレスを、region番号とともに指定します。
入力する値は、[31:5]に開始アドレス（32bit)の下5btを削ったもの、[4:0]に（`MPU_RNR`に指定したのと同じ）region番号です。

#### MPU_RASR (Region Attribute and Size Register)
`MPU_RNR`で指定した番号のregionに割り当てるメモリのサイズや、そこのアクセス権限などについて指定します。
指定するregionに対し、非特権状態のアクセスを許すか許さないかの設定もこのレジスタへの書き込みによって行います。
サイズは[5:1]に5ビット数で指定し、2^(指定した数+1)Byteがregionに対し割り当てられます。

これより、今回実装する必要のある操作としては

* CPUのメモリマップを見つつ、regionにメモリ領域を割り当てる
(`MPU_RBAR`にその領域の開始アドレスを、`MPU_RASR`にその領域のサイズを指定してやる必要があります。)
* それぞれのregionに対し、アクセス権を決める
* MPUを有効にする

という感じになります。

##実装

###実装
[Rustで始める自作組込みOS入門](https://garasubo.github.io/embedded-book/1_intro/index.html)の5章で特権/非特権の切り替えは実装しているので、実装する必要があるのはMPUを有効にするコードのみです。

`led.rs`と同様、ファイルを分けて実装します。
ということで`mpu.rs`を作り、以下のように実装します。

```rust
use core::ptr::{write_volatile, read_volatile};
use cortex_m_semihosting::hprintln;

const MPU_TYPE_ADDR : usize = 0xE000_ED90;
const MPU_CTRL_ADDR : usize = 0xE000_ED94;
const MPU_RNR_ADDR : usize = 0xE000_ED98;
const MPU_RASR_ADDR : usize = 0xE000_EDA0;
const MPU_RBAR_ADDR : usize = 0xE000_ED9C;

pub fn init() {
    hprintln!("enabling MPU");
    unsafe {
        //disable MPU
        write_volatile(MPU_CTRL_ADDR as *mut u32, 0x0000_0000);

        // setting on regions ( 8 regions )
        write_volatile(MPU_RNR_ADDR as *mut u32, 0x0000_0000);
        write_volatile(MPU_RBAR_ADDR as *mut u32, 0x0000_0010);
                                            //          XN  AP     TEX S C B SRD         SIZE  EN
        write_volatile(MPU_RASR_ADDR as *mut u32, 0b000_0_0_011_00_001_0_0_0_00000000_00_11100_1);

        write_volatile(MPU_RNR_ADDR as *mut u32, 0x0000_0001);
        write_volatile(MPU_RBAR_ADDR as *mut u32, 0x2000_0011);
                                            //          XN  AP     TEX S C B SRD         SIZE  EN
        write_volatile(MPU_RASR_ADDR as *mut u32, 0b000_0_0_011_00_001_0_0_0_00000000_00_11100_1);

        write_volatile(MPU_RNR_ADDR as *mut u32, 0x0000_0002);
        write_volatile(MPU_RBAR_ADDR as *mut u32, 0x4000_0012);
                                            //          XN  AP     TEX S C B SRD         SIZE  EN
        write_volatile(MPU_RASR_ADDR as *mut u32, 0b000_0_0_001_00_001_0_0_0_00000000_00_11100_1);

        write_volatile(MPU_RNR_ADDR as *mut u32, 0x0000_0003);
        write_volatile(MPU_RBAR_ADDR as *mut u32, 0x6000_0013);
                                            //          XN  AP     TEX S C B SRD         SIZE  EN
        write_volatile(MPU_RASR_ADDR as *mut u32, 0b000_0_0_011_00_001_0_0_0_00000000_00_11100_1);

        write_volatile(MPU_RNR_ADDR as *mut u32, 0x0000_0004);
        write_volatile(MPU_RBAR_ADDR as *mut u32, 0x8000_0014);
                                            //          XN  AP     TEX S C B SRD         SIZE  EN
        write_volatile(MPU_RASR_ADDR as *mut u32, 0b000_0_0_011_00_001_0_0_0_00000000_00_11100_1);

        write_volatile(MPU_RNR_ADDR as *mut u32, 0x0000_0005);
        write_volatile(MPU_RBAR_ADDR as *mut u32, 0xA000_0015);
                                            //          XN  AP     TEX S C B SRD         SIZE  EN
        write_volatile(MPU_RASR_ADDR as *mut u32, 0b000_0_0_011_00_001_0_0_0_00000000_00_11100_1);

        write_volatile(MPU_RNR_ADDR as *mut u32, 0x0000_0006);
        write_volatile(MPU_RBAR_ADDR as *mut u32, 0xD000_0016);
                                            //          XN  AP     TEX S C B SRD         SIZE  EN
        write_volatile(MPU_RASR_ADDR as *mut u32, 0b000_0_0_011_00_001_0_0_0_00000000_00_11100_1);

        write_volatile(MPU_RNR_ADDR as *mut u32, 0x0000_0007);
        write_volatile(MPU_RBAR_ADDR as *mut u32, 0xE000_0017);
                                            //          XN  AP     TEX S C B SRD         SIZE  EN
        write_volatile(MPU_RASR_ADDR as *mut u32, 0b000_0_0_011_00_001_0_0_0_00000000_00_11100_1);

        //enable MPU
        write_volatile(MPU_CTRL_ADDR as *mut u32, 0x0000_0001);
    }
}
```
ざっくり何を書いたかを説明すると

```rust
write_volatile(MPU_CTRL_ADDR as *mut u32, 0x0000_0001);
と
write_volatile(MPU_CTRL_ADDR as *mut u32, 0x0000_0001);
```
各種設定前にMPUを無効化し、設定完了後に有効化しています。
`MPU_CTRL`の[0]がenable/disableを設定するビットなので、ここに1を書いてやっています。
[2]と[1]にも設定項目があり、権限周りの設定が変わるのですが、今回は省略します。

```rust
write_volatile(MPU_RNR_ADDR as *mut u32, 0x0000_0002);
write_volatile(MPU_RBAR_ADDR as *mut u32, 0x4000_0012);
                                    //          XN  AP     TEX S C B SRD         SIZE  EN
write_volatile(MPU_RASR_ADDR as *mut u32, 0b000_0_0_001_00_001_0_0_0_00000000_00_11100_1);
```
このひとかたまりで1regionについての設定を行なっており、`mpu.rs`ではこれを8regionに対して行なっています。
わかりやすいよう、region2に関して設定しているコードを抜き出してきました。また`MPU_RASR`に入れる値がわかりやすいよう、ここだけ2進数にし、コメントにフィールド名を振っています。

8つのregionは使用しているF446REボードのリファレンスマニュアル↓P.65にあるメモリマップを元に、8つある512MBの領域をそれぞれ割り当てることにしました。
[リファレンスマニュアル](https://www.st.com/resource/en/reference_manual/dm00135183-stm32f446xx-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf)
SIZEは全て512MBなので512 * 1024 * 1024 = 2^29から28(11100)を指定しています。
今回はGPIOへのアクセスを禁止したいので、0x4000_0000-0x5FFF_FFFFの領域へのアクセスを特権状態での実行時のみに制限します。

上のコードにある通り、region2にはペリフェラル領域が割り当たっているので、非特権状態からのアクセスを禁止するように指定します。
アクセス制限は`MPU_RASR`の`AP`フィールド
全ての値の意味はArm v7-MのマニュアルのP.B3-642に書いてありますが、一部抜粋すると`000`でアクセス全拒否、`001`で特権状態のみ読み書き許可、`011`で特権/非特権関わらず読み書き許可、になります。
このため、ペリフェラル領域のregionについては`001`を指定して非特権状態からのアクセスを拒否し、他のregionは`011`にしてアクセス拒否をしないようにしてやっています。
（それってどうやのって感じではありますが・・・）

その他の項目については先ほども紹介した以下のサイトに掲載されていた指定に倣っています。
[Setting up the Cortex-M3/4 (ARMv7-M) Memory Protection Unit (MPU) - Stickey Bits -Powered by Feabhas](https://blog.feabhas.com/2013/02/setting-up-the-cortex-m34-armv7-m-memory-protection-unit-mpu/)

あとは`main.rs`の中に、この`mpu.rs`を利用するコードを書きます

```rust
...
mod mpu;
...
#[no_mangle]
pub unsafe extern "C" fn Reset() -> ! {
...
  mpu::init();
...
```

###実行・検証

さてこれで非特権状態で実行するプログラムからGPIO操作ができなくなったのでしょうか。

検証のため、特権状態で実行する`Reset()`と、非特権状態で実行する`app_main()`について以下のようにしました。
（`SVCall()`など省略している部分は「[Rustで始める自作組込みOS入門](https://garasubo.github.io/embedded-book/1_intro/index.html) 5章の成果物とほぼ同じです)

```rust
...
#[no_mangle]
pub unsafe extern "C" fn Reset() -> ! {
...
    mpu::init();
    asm!(
        "
        msr psp, r0
        svc 0
        "
        ::"{r0}"(ptr):"r4", "r5", "r6", "r8", "r9", "r10", "r11":"volatile");
    hprintln!("Kernel").unwrap();
    led::init();
    led::turn_on();
    loop {}
}
extern "C" fn app_main() -> ! {
    hprintln!("App!").unwrap();
    led::init();
    led::turn_on();
    unsafe { asm!("svc 0"::::"volatile"); }
    loop {}
}
```
このコードで実行したところ、LEDは光らず、デバッガで確認するとHardFaultが発生していました。

続いて、`app_main()`内のled点灯処理を行う関数をコメントアウトします

```rust
...
extern "C" fn app_main() -> ! {
    hprintln!("App!").unwrap();
    //led::init();
    //led::turn_on();
    unsafe { asm!("svc 0"::::"volatile"); }
    loop {}
}
```

実行したところ、`Reset()`に実行権が戻ってきたあと無事LEDが点灯しました。
ということで、目標であった非特権状態でのGPIOレジスタへのアクセス制限をかけることができました、めでたしめでたし
##〆
今後やること：
* 特権状態についてもっと理解する（詳細を読み飛ばしたので）
* MPUについてもっと理解する（詳細を読み飛ばしたので）
* SVCallでGPIOをいじれるようにする

OSに共通する勉強をしようと思ったら結局Arm特有の問題になってしまった
後編書けるんか・・・・？

##参考文献

1. [Rustで始める自作組込みOS入門](https://garasubo.github.io/embedded-book/1_intro/index.html) 
1. [Setting up the Cortex-M3/4 (ARMv7-M) Memory Protection Unit (MPU) - Stickey Bits -Powered by Feabhas](https://blog.feabhas.com/2013/02/setting-up-the-cortex-m34-armv7-m-memory-protection-unit-mpu/) 
2. [Arm®v7-M Architecture Reference Manual](https://developer.arm.com/docs/ddi0403/ed/armv7-m-architecture-reference-manual)
3. [STM32F446xC/E リファレンスマニュアル](https://www.st.com/resource/en/reference_manual/dm00135183-stm32f446xx-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf)