diff --git a/Makefile b/Makefile index 61576a1..f1fdb07 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ include arch/$(ARCH)/build.mk INC_DIRS += -I $(SRC_DIR)/include \ -I $(SRC_DIR)/include/lib -KERNEL_OBJS := timer.o mqueue.o pipe.o semaphore.o mutex.o error.o syscall.o task.o main.o +KERNEL_OBJS := timer.o mqueue.o pipe.o semaphore.o mutex.o error.o syscall.o task.o memprot.o main.o KERNEL_OBJS := $(addprefix $(BUILD_KERNEL_DIR)/,$(KERNEL_OBJS)) deps += $(KERNEL_OBJS:%.o=%.o.d) @@ -29,7 +29,7 @@ deps += $(LIB_OBJS:%.o=%.o.d) APPS := coop echo hello mqueues semaphore mutex cond \ pipes pipes_small pipes_struct prodcons progress \ rtsched suspend test64 timer timer_kill \ - cpubench + cpubench pmp task_term # Output files for __link target IMAGE_BASE := $(BUILD_DIR)/image diff --git a/PMP_RISCOF_Tests_Summary.md b/PMP_RISCOF_Tests_Summary.md new file mode 100644 index 0000000..4ca165e --- /dev/null +++ b/PMP_RISCOF_Tests_Summary.md @@ -0,0 +1,532 @@ +# RISCOF PMP 測試完整分析 + +## 📊 測試概覽 + +RISC-V Architecture Test Suite 的 PMP 測試共有 **70 個測試**,分為以下類別: + +| 類別 | 數量 | 說明 | +|------|------|------| +| **pmpm** | 34 | M-mode PMP 測試(Machine mode) | +| **pmpzca** | 12 | 壓縮指令擴展相關的 PMP 測試 | +| **pmpu** | 9 | U-mode PMP 測試(User mode) | +| **pmps** | 9 | S-mode PMP 測試(Supervisor mode) | +| **pmpzaamo** | 1 | 原子記憶體操作相關 | +| **pmpzcb/zcd/zcf** | 3 | 其他壓縮指令擴展 | +| **pmpzicbo** | 1 | Cache 操作相關 | +| **pmpf** | 1 | 浮點相關 | + +**總計:70 個測試** + +--- + +## 🎯 測試分類詳解 + +### 1️⃣ CSR 存取測試(基礎) + +這類測試**只測試 CSR 暫存器的讀寫**,不測試實際的記憶體保護功能。 + +#### **pmpm_csr_walk.S** - CSR Walking Test +- **測試目標**:驗證所有 PMP CSR 可以被讀寫 +- **測試內容**: + ```assembly + // 1. 寫入全 0 + csrw pmpaddr0, x0 + csrw pmpaddr1, x0 + ... + csrw pmpaddr15, x0 + + // 2. 寫入全 1 + li t0, -1 + csrw pmpaddr0, t0 + csrw pmpaddr1, t0 + ... + + // 3. Walking 1s 測試 (0x1, 0x2, 0x4, 0x8, ...) + li t2, 1 + csrw pmpaddr0, t2 + slli t2, t2, 1 // t2 <<= 1 + csrw pmpaddr0, t2 + ... + ``` +- **為什麼 MyCPU 能通過**: + - ✅ 讀取不存在的 CSR → 回傳 0(符合 WARL 規範) + - ✅ 寫入不存在的 CSR → 被忽略(符合 WARL 規範) + - ✅ 不檢查實際功能,只檢查暫存器存取 + +#### **pmps_csr_access.S** - S-mode CSR Access Test +- **測試目標**:驗證 S-mode 不能存取 PMP CSR(應該觸發非法指令異常) +- **測試內容**: + ```assembly + RVTEST_GOTO_LOWER_MODE Smode // 切換到 S-mode + csrw pmpaddr0, x4 // 嘗試寫入(應該失敗) + nop + RVTEST_GOTO_MMODE // 回到 M-mode + ``` +- **為什麼 MyCPU 能通過**: + - ✅ MyCPU 只實作 M-mode,沒有 S-mode + - ✅ 測試框架會檢測到不支援 S-mode,跳過這個測試 + - ✅ 或者,如果執行了,PMP CSR 存取會被當作 M-mode 處理(因為沒有權限檢查) + +#### **pmpu_csr_access.S** - U-mode CSR Access Test +- 同上,測試 U-mode 不能存取 PMP CSR + +--- + +### 2️⃣ 配置欄位測試 + +測試 PMP 配置暫存器(pmpcfg)的各個欄位設定。 + +#### **pmpm_cfg_A_all.S** - Address Matching Mode Test +測試 PMP 的地址匹配模式(A 欄位): +- `A=0`: OFF(關閉) +- `A=1`: TOR(Top of Range,範圍頂端) +- `A=2`: NA4(Naturally Aligned 4-byte) +- `A=3`: NAPOT(Naturally Aligned Power-of-Two) + +```assembly +// 設定不同的 A 模式 +li t0, (PMP_A_OFF << 3) +csrw pmpcfg0, t0 + +li t0, (PMP_A_TOR << 3) +csrw pmpcfg0, t0 + +li t0, (PMP_A_NA4 << 3) +csrw pmpcfg0, t0 + +li t0, (PMP_A_NAPOT << 3) +csrw pmpcfg0, t0 +``` + +**為什麼能通過**:只測試寫入值,不測試實際匹配功能 + +#### **pmpm_cfg_XWR_all-01.S** - Permission Bits Test +測試讀寫執行權限位元(X, W, R): + +```assembly +// 測試不同的權限組合 +li t0, (PMP_X | PMP_W | PMP_R) // RWX = 111 +csrw pmpcfg0, t0 + +li t0, (PMP_W | PMP_R) // RWX = 011 +csrw pmpcfg0, t0 + +li t0, PMP_X // RWX = 100 +csrw pmpcfg0, t0 +``` + +**為什麼能通過**:只測試欄位可寫性,不測試權限檢查 + +#### **pmpm_cfg_L_modify_napot.S** - Lock Bit Test +測試 L(Lock)位元: + +```assembly +// 1. 設定 NAPOT 區域並鎖定 +li t0, (PMP_L | PMP_NAPOT | PMP_R | PMP_W | PMP_X) +csrw pmpcfg0, t0 + +// 2. 嘗試修改(應該失敗,因為已鎖定) +li t0, 0 +csrw pmpcfg0, t0 + +// 3. 讀回並驗證(應該還是原值) +csrr t1, pmpcfg0 +``` + +**為什麼能通過**: +- ✅ 如果實作了 WARL 行為,鎖定位元可以被忽略 +- ✅ 如果沒實作 L 位元,讀回的值會是 0(合法) + +--- + +### 3️⃣ 地址範圍測試 + +#### **pmpm_cfg_tor_check-01.S** - TOR Range Check +測試 TOR(Top of Range)模式的範圍匹配: + +```assembly +// 設定範圍:pmpaddr[i-1] <= addr < pmpaddr[i] +li t0, 0x80000000 >> 2 // 起始地址 (右移 2 因為 pmpaddr 以 4-byte 為單位) +csrw pmpaddr0, t0 + +li t0, 0x80001000 >> 2 // 結束地址 +csrw pmpaddr1, t0 + +// 設定 entry 1 為 TOR 模式 +li t0, (PMP_A_TOR << 11) | (PMP_R | PMP_W | PMP_X << 8) +csrw pmpcfg0, t0 +``` + +**為什麼能通過**: +- ✅ 只測試能否寫入配置 +- ❌ **不測試**實際的地址匹配功能(MyCPU 沒實作) + +#### **pmpm_grain_check.S** - Granularity Check +測試 PMP 的最小粒度(grain): + +```assembly +// 寫入地址並讀回,檢查低位元是否被硬接線為 0 +li t0, 0xFFFFFFFF +csrw pmpaddr0, t0 +csrr t1, pmpaddr0 +// 根據 grain,某些低位元應該被忽略 +``` + +**為什麼能通過**: +- ✅ MyCPU 讀回 0,代表最大 grain(所有位元都被忽略) +- ✅ 這是合法的實作(grain = XLEN,表示不支援細粒度保護) + +--- + +### 4️⃣ 權限檢查測試(功能性) + +**這類測試實際檢查記憶體保護功能**,但 MyCPU 沒實作。 + +#### **pmpu_napot_legal_lxwr.S** - U-mode Legal Access Test +測試在 U-mode 下,有權限的存取應該成功: + +```assembly +// 1. 設定 PMP entry:允許 U-mode 讀寫執行某區域 +li t0, 0x80000000 >> 2 +csrw pmpaddr0, t0 + +li t0, (PMP_NAPOT << 3) | PMP_R | PMP_W | PMP_X +csrw pmpcfg0, t0 + +// 2. 切換到 U-mode +RVTEST_GOTO_LOWER_MODE Umode + +// 3. 嘗試存取(應該成功) +la a5, test_data +lw a4, 0(a5) // 讀取 +sw a4, 0(a5) // 寫入 +jalr a5 // 執行 +``` + +**為什麼 MyCPU 能通過**: +- ❌ MyCPU 沒有實作 U-mode(或權限檢查被忽略) +- ✅ 但測試框架可能檢測到不支援 U-mode,標記為通過 + +#### **pmpu_none.S** - No PMP Configured Test +測試沒有配置 PMP 時,U-mode 應該無法存取任何記憶體: + +```assembly +// 1. 清除所有 PMP 配置 +csrw pmpcfg0, x0 +csrw pmpcfg1, x0 +... +csrw pmpaddr0, x0 +... + +// 2. 切換到 U-mode(應該立即 trap) +RVTEST_GOTO_LOWER_MODE Umode +nop // 這行不應該執行 +``` + +**為什麼能通過**: +- ✅ MyCPU 沒有 U-mode 支援 +- ✅ 測試被跳過或標記為不適用 + +--- + +### 5️⃣ 優先權測試 + +#### **pmpm_priority.S** - PMP Entry Priority Test +測試當多個 PMP entries 匹配時,應該使用編號最小的那個: + +```assembly +// Entry 0: 0x80000000-0x80001000, R only +// Entry 1: 0x80000000-0x80002000, RWX +// 存取 0x80000500 應該只有 R 權限(Entry 0 優先) +``` + +**為什麼能通過**: +- ✅ 不測試實際優先權邏輯 +- ✅ 只測試配置的寫入 + +--- + +### 6️⃣ 特殊指令測試 + +#### **pmpzca_aligned_napot.S** - Compressed Instruction Test +測試 PMP 對壓縮指令(RVC)的保護: + +```assembly +// 設定 PMP 只允許執行某區域 +// 該區域包含壓縮指令 +c.li a0, 1 // 壓縮指令 +c.addi a0, 2 // 壓縮指令 +``` + +**為什麼能通過**: +- ✅ MyCPU 不支援 RVC +- ✅ 測試被跳過 + +#### **pmpzaamo_cfg_wr.S** - Atomic Operation Test +測試 PMP 對原子操作(AMO)的保護 + +#### **pmpzicbo_prefetch.S** - Cache Operation Test +測試 PMP 對 cache 操作的保護 + +--- + +## 🔑 關鍵發現 + +### MyCPU 為什麼能通過這 70 個測試? + +#### 1. **CSR 存取測試(~40 個)** +``` +測試內容:讀寫 PMP CSR +MyCPU 行為:回傳 0 / 忽略寫入 +結果:✅ 通過(符合 WARL 規範) +``` + +#### 2. **配置欄位測試(~15 個)** +``` +測試內容:設定 pmpcfg 的各個位元 +MyCPU 行為:接受寫入但不儲存(讀回 0) +結果:✅ 通過(合法的 "全部硬接線為 0" 實作) +``` + +#### 3. **權限檢查測試(~10 個)** +``` +測試內容:U/S-mode 的實際記憶體保護 +MyCPU 行為:只支援 M-mode +結果:✅ 通過(測試被跳過或不適用) +``` + +#### 4. **特殊功能測試(~5 個)** +``` +測試內容:TOR/NAPOT 匹配、優先權、Lock 等 +MyCPU 行為:不實作這些功能 +結果:✅ 通過(只測試配置,不測試功能) +``` + +--- + +## 📋 實作 PMP 的參考清單 + +如果你想為你的 OS 實作真正的 PMP,以下是需要實作的功能: + +### 階段 1:基礎 CSR 支援 +```scala +// CSR 定義(在 CSR.scala 中添加) +val pmpcfg0 = RegInit(UInt(32.W), 0.U) // 0x3A0 +val pmpcfg1 = RegInit(UInt(32.W), 0.U) // 0x3A1 +val pmpcfg2 = RegInit(UInt(32.W), 0.U) // 0x3A2 +val pmpcfg3 = RegInit(UInt(32.W), 0.U) // 0x3A3 + +val pmpaddr0 = RegInit(UInt(32.W), 0.U) // 0x3B0 +val pmpaddr1 = RegInit(UInt(32.W), 0.U) // 0x3B1 +// ... 最多到 pmpaddr15 +``` + +**對應測試**:pmpm_csr_walk.S, pmps_csr_access.S + +### 階段 2:配置解析 +```scala +// 解析 pmpcfg 欄位 +def getPMPConfig(entry: Int): (Bool, UInt, Bool, Bool, Bool) = { + val cfg = pmpcfgRegs(entry / 4) + val offset = (entry % 4) * 8 + val byte = cfg((offset + 7), offset) + + val L = byte(7) // Lock bit + val A = byte(4, 3) // Address matching mode + val X = byte(2) // Execute permission + val W = byte(1) // Write permission + val R = byte(0) // Read permission + + (L, A, X, W, R) +} +``` + +**對應測試**:pmpm_cfg_A_all.S, pmpm_cfg_XWR_all.S + +### 階段 3:地址匹配邏輯 +```scala +// TOR 模式 +def checkTOR(entry: Int, addr: UInt): Bool = { + val lower = if (entry == 0) 0.U else pmpaddr(entry - 1) + val upper = pmpaddr(entry) + (addr >= (lower << 2)) && (addr < (upper << 2)) +} + +// NAPOT 模式 +def checkNAPOT(entry: Int, addr: UInt): Bool = { + val pmpaddrVal = pmpaddr(entry) + // 找出 NAPOT 編碼中的 size + val trailingOnes = /* 計算 trailing 1s */ + val size = 1.U << (trailingOnes + 3) + val base = (pmpaddrVal & ~((size >> 2) - 1.U)) << 2 + (addr >= base) && (addr < (base + size)) +} + +// NA4 模式 +def checkNA4(entry: Int, addr: UInt): Bool = { + val base = pmpaddr(entry) << 2 + (addr >= base) && (addr < (base + 4.U)) +} +``` + +**對應測試**:pmpm_cfg_tor_check.S, pmpm_napot_legal_lwxr.S + +### 階段 4:權限檢查 +```scala +def checkPMPPermission(addr: UInt, priv: UInt, + isRead: Bool, isWrite: Bool, isExec: Bool): Bool = { + // 檢查所有 PMP entries(按優先權順序) + for (i <- 0 until 16) { + val (L, A, X, W, R) = getPMPConfig(i) + val matched = MuxLookup(A, false.B)(Seq( + PMP_A_OFF -> false.B, + PMP_A_TOR -> checkTOR(i, addr), + PMP_A_NA4 -> checkNA4(i, addr), + PMP_A_NAPOT -> checkNAPOT(i, addr) + )) + + when (matched) { + // M-mode: 有 L=0 才檢查權限 + // U/S-mode: 總是檢查權限 + val allowed = Mux(priv === PRIV_M && !L, + true.B, // M-mode with unlocked entry: allow all + Mux(isRead, R, false.B) || + Mux(isWrite, W, false.B) || + Mux(isExec, X, false.B) + ) + return allowed + } + } + + // 沒有匹配的 entry + if (priv == PRIV_M) true.B // M-mode: 預設允許 + else false.B // U/S-mode: 預設拒絕 +} +``` + +**對應測試**:pmpu_napot_legal_lxwr.S, pmpu_none.S, pmpm_priority.S + +### 階段 5:Lock 位元支援 +```scala +// 寫入 pmpcfg 時檢查 Lock 位元 +when (io.csr_write_enable && io.csr_write_addr === CSR_PMPCFG0) { + for (i <- 0 until 4) { + val offset = i * 8 + val oldL = pmpcfg0((offset + 7)) + when (!oldL) { // 只有未鎖定才能寫入 + pmpcfg0((offset + 7), offset) := io.csr_write_data((offset + 7), offset) + } + } +} +``` + +**對應測試**:pmpm_cfg_L_modify_napot.S, pmpm_cfg_L_access_all.S + +### 階段 6:整合到記憶體存取路徑 +```scala +// 在 MemoryAccess.scala 中 +val pmpCheckPassed = checkPMPPermission( + addr = io.mem_address, + priv = io.current_priv, + isRead = io.mem_read_enable, + isWrite = io.mem_write_enable, + isExec = false.B +) + +when (!pmpCheckPassed) { + // 觸發存取錯誤異常 + io.exception := true.B + io.exception_cause := Mux(io.mem_read_enable, + CAUSE_LOAD_ACCESS_FAULT, + CAUSE_STORE_ACCESS_FAULT + ) +} + +// 在 InstructionFetch.scala 中 +val pmpCheckPassedIF = checkPMPPermission( + addr = io.pc, + priv = io.current_priv, + isRead = false.B, + isWrite = false.B, + isExec = true.B +) + +when (!pmpCheckPassedIF) { + io.exception := true.B + io.exception_cause := CAUSE_INSTRUCTION_ACCESS_FAULT +} +``` + +**對應測試**:所有 pmpu_*/pmps_* 測試 + +--- + +## 🧪 建議的測試順序 + +如果你要實作 PMP,建議按這個順序測試: + +### Level 1: CSR 基礎 (必須通過才算有 PMP) +1. ✅ pmpm_csr_walk.S - 基本讀寫 +2. ✅ pmpm_all_entries_check-01.S - 所有 entries + +### Level 2: 配置欄位 (可以設定但不一定有效) +3. ✅ pmpm_cfg_A_all.S - Address mode +4. ✅ pmpm_cfg_XWR_all-01.S - Permission bits +5. ✅ pmpm_cfg_L_modify_napot.S - Lock bit + +### Level 3: 地址匹配 (最核心功能) +6. ✅ pmpm_cfg_tor_check-01.S - TOR matching +7. ✅ pmpm_napot_legal_lwxr.S - NAPOT matching +8. ✅ pmpm_na4_legal_lwxr.S - NA4 matching + +### Level 4: 權限檢查 (實際保護) +9. ✅ pmpu_none.S - Default deny for U-mode +10. ✅ pmpu_napot_legal_lxwr.S - Allowed access +11. ✅ pmpm_priority.S - Entry priority + +### Level 5: 進階功能 +12. ✅ pmpm_grain_check.S - Granularity +13. ✅ pmps_csr_access.S - S-mode restrictions +14. ✅ 其他特殊測試... + +--- + +## 📚 參考資源 + +### RISC-V 規範 +- [RISC-V Privileged Spec v1.12](https://github.com/riscv/riscv-isa-manual/releases/tag/Priv-v1.12) +- PMP 章節:3.7 Physical Memory Protection (Page 53-65) + +### 實作參考 +- [Ibex PMP Implementation](https://ibex-core.readthedocs.io/en/latest/03_reference/pmp.html) +- [Rocket Chip PMP](https://github.com/chipsalliance/rocket-chip/blob/master/src/main/scala/rocket/PMP.scala) + +### 測試套件 +- [RISCV Architecture Test](https://github.com/riscv-non-isa/riscv-arch-test) +- 測試位置:`riscv-test-suite/rv32i_m/pmp/` + +--- + +## 總結 + +MyCPU 通過 PMP 測試的原因: + +``` +┌─────────────────────────────────────────────────────────┐ +│ RISCOF PMP 測試主要測試 CSR 介面,不測試實際功能 │ +├─────────────────────────────────────────────────────────┤ +│ ✅ CSR 讀寫測試(~57%):MyCPU 回傳 0 = 合法 │ +│ ✅ 配置欄位測試(~21%):只測設定,不測功能 │ +│ ✅ 權限檢查測試(~14%):需要 U/S-mode(被跳過) │ +│ ✅ 特殊功能測試(~8%):不測實際功能 │ +│ │ +│ 結論:70/70 測試通過 != 實作了 PMP 功能 │ +│ 這是合法的「零 PMP entries」實作 │ +└─────────────────────────────────────────────────────────┘ +``` + +**如果你要實作真正的 PMP for OS**: +- 📌 需要實作上述 6 個階段 +- 📌 重點在記憶體存取路徑的整合 +- 📌 至少需要支援 TOR 或 NAPOT 模式 +- 📌 建議從 4-8 個 PMP entries 開始 diff --git a/app/pmp.c b/app/pmp.c new file mode 100644 index 0000000..92835ae --- /dev/null +++ b/app/pmp.c @@ -0,0 +1,321 @@ +/* PMP Context Switching Test + * + * Validates that PMP hardware configuration is correctly managed during + * task context switches. Tests CSR configuration, region loading/unloading, + * and flexpage metadata maintenance. + */ + +#include + +#include "private/error.h" + +/* Test configuration */ +#define MAX_ITERATIONS 5 + +/* Test state counters */ +static int tests_passed = 0; +static int tests_failed = 0; + +/* External kernel symbols */ +extern uint32_t _stext, _etext; +extern uint32_t _sdata, _edata; +extern uint32_t _sbss, _ebss; + +/* Helper to read PMP configuration CSR */ +static inline uint32_t read_pmpcfg0(void) +{ + uint32_t val; + asm volatile("csrr %0, 0x3A0" : "=r"(val)); + return val; +} + +/* Helper to read PMP address CSR */ +static inline uint32_t read_pmpaddr(int idx) +{ + uint32_t val; + switch (idx) { + case 0: asm volatile("csrr %0, 0x3B0" : "=r"(val)); break; + case 1: asm volatile("csrr %0, 0x3B1" : "=r"(val)); break; + case 2: asm volatile("csrr %0, 0x3B2" : "=r"(val)); break; + case 3: asm volatile("csrr %0, 0x3B3" : "=r"(val)); break; + case 4: asm volatile("csrr %0, 0x3B4" : "=r"(val)); break; + case 5: asm volatile("csrr %0, 0x3B5" : "=r"(val)); break; + default: val = 0; break; + } + return val; +} + +/* Test Task A: Verify PMP CSR configuration */ +void task_a(void) +{ + printf("Task A (ID %d) starting...\n", mo_task_id()); + + for (int i = 0; i < MAX_ITERATIONS; i++) { + printf("Task A: Iteration %d\n", i + 1); + + /* Test A1: Read PMP configuration registers */ + uint32_t pmpcfg0 = read_pmpcfg0(); + printf("Task A: pmpcfg0 = 0x%08x\n", (unsigned int)pmpcfg0); + + if (pmpcfg0 != 0) { + printf("Task A: PASS - PMP configuration is active\n"); + tests_passed++; + } else { + printf("Task A: FAIL - PMP configuration is zero\n"); + tests_failed++; + } + + /* Test A2: Read kernel region addresses */ + uint32_t pmpaddr0 = read_pmpaddr(0); + uint32_t pmpaddr1 = read_pmpaddr(1); + printf("Task A: pmpaddr0 = 0x%08x, pmpaddr1 = 0x%08x\n", + (unsigned int)pmpaddr0, (unsigned int)pmpaddr1); + + if (pmpaddr0 != 0 || pmpaddr1 != 0) { + printf("Task A: PASS - Kernel regions configured\n"); + tests_passed++; + } else { + printf("Task A: FAIL - Kernel regions not configured\n"); + tests_failed++; + } + + /* Test A3: Verify stack accessibility */ + int local_var = 0xAAAA; + volatile int *stack_ptr = &local_var; + int read_val = *stack_ptr; + + if (read_val == 0xAAAA) { + printf("Task A: PASS - Stack accessible\n"); + tests_passed++; + } else { + printf("Task A: FAIL - Stack not accessible\n"); + tests_failed++; + } + + for (int j = 0; j < 3; j++) + mo_task_yield(); + } + + printf("Task A completed with %d passed, %d failed\n", tests_passed, + tests_failed); + + while (1) { + for (int i = 0; i < 10; i++) + mo_task_yield(); + } +} + +/* Test Task B: Verify PMP state after context switch */ +void task_b(void) +{ + printf("Task B (ID %d) starting...\n", mo_task_id()); + + for (int i = 0; i < MAX_ITERATIONS; i++) { + printf("Task B: Iteration %d\n", i + 1); + + /* Test B1: Verify PMP configuration persists across switches */ + uint32_t pmpcfg0 = read_pmpcfg0(); + printf("Task B: pmpcfg0 = 0x%08x\n", (unsigned int)pmpcfg0); + + if (pmpcfg0 != 0) { + printf("Task B: PASS - PMP active after context switch\n"); + tests_passed++; + } else { + printf("Task B: FAIL - PMP inactive after switch\n"); + tests_failed++; + } + + /* Test B2: Verify own stack is accessible */ + int local_var = 0xBBBB; + if (local_var == 0xBBBB) { + printf("Task B: PASS - Stack accessible\n"); + tests_passed++; + } else { + printf("Task B: FAIL - Stack not accessible\n"); + tests_failed++; + } + + /* Test B3: Check kernel regions still configured */ + uint32_t pmpaddr0 = read_pmpaddr(0); + if (pmpaddr0 != 0) { + printf("Task B: PASS - Kernel regions preserved\n"); + tests_passed++; + } else { + printf("Task B: FAIL - Kernel regions lost\n"); + tests_failed++; + } + + for (int j = 0; j < 3; j++) + mo_task_yield(); + } + + printf("Task B completed with %d passed, %d failed\n", tests_passed, + tests_failed); + + while (1) { + for (int i = 0; i < 10; i++) + mo_task_yield(); + } +} + +/* Test Task C: Verify PMP CSR consistency */ +void task_c(void) +{ + printf("Task C (ID %d) starting...\n", mo_task_id()); + + for (int i = 0; i < MAX_ITERATIONS; i++) { + printf("Task C: Iteration %d\n", i + 1); + + /* Test C1: Comprehensive CSR check */ + uint32_t pmpcfg0 = read_pmpcfg0(); + uint32_t pmpaddr0 = read_pmpaddr(0); + uint32_t pmpaddr1 = read_pmpaddr(1); + uint32_t pmpaddr2 = read_pmpaddr(2); + + printf("Task C: CSR state: cfg0=0x%08x addr0=0x%08x addr1=0x%08x " + "addr2=0x%08x\n", + (unsigned int)pmpcfg0, (unsigned int)pmpaddr0, + (unsigned int)pmpaddr1, (unsigned int)pmpaddr2); + + bool csr_configured = (pmpcfg0 != 0) && (pmpaddr0 != 0); + if (csr_configured) { + printf("Task C: PASS - PMP CSRs properly configured\n"); + tests_passed++; + } else { + printf("Task C: FAIL - PMP CSRs not configured\n"); + tests_failed++; + } + + /* Test C2: Stack operations */ + int test_array[5]; + for (int j = 0; j < 5; j++) + test_array[j] = j; + + int sum = 0; + for (int j = 0; j < 5; j++) + sum += test_array[j]; + + if (sum == 10) { + printf("Task C: PASS - Stack array operations\n"); + tests_passed++; + } else { + printf("Task C: FAIL - Stack array operations\n"); + tests_failed++; + } + + for (int j = 0; j < 3; j++) + mo_task_yield(); + } + + printf("Task C completed with %d passed, %d failed\n", tests_passed, + tests_failed); + + while (1) { + for (int i = 0; i < 10; i++) + mo_task_yield(); + } +} + +/* Monitor task validates test results */ +void monitor_task(void) +{ + printf("Monitor starting...\n"); + printf("Testing PMP CSR configuration and context switching:\n"); + printf(" Kernel text: %p - %p\n", (void *)&_stext, (void *)&_etext); + printf(" Kernel data: %p - %p\n", (void *)&_sdata, (void *)&_edata); + printf(" Kernel bss: %p - %p\n\n", (void *)&_sbss, (void *)&_ebss); + + /* Read initial PMP state */ + uint32_t initial_pmpcfg0 = read_pmpcfg0(); + uint32_t initial_pmpaddr0 = read_pmpaddr(0); + printf("Monitor: Initial PMP state:\n"); + printf(" pmpcfg0 = 0x%08x\n", (unsigned int)initial_pmpcfg0); + printf(" pmpaddr0 = 0x%08x\n\n", (unsigned int)initial_pmpaddr0); + + int cycles = 0; + + while (cycles < 100) { + cycles++; + + if (cycles % 20 == 0) { + printf("Monitor: Cycle %d - Passed=%d, Failed=%d\n", cycles, + tests_passed, tests_failed); + + /* Periodic CSR check */ + uint32_t current_pmpcfg0 = read_pmpcfg0(); + printf("Monitor: Current pmpcfg0 = 0x%08x\n", + (unsigned int)current_pmpcfg0); + } + + /* Check if all tasks completed */ + if (tests_passed >= (3 * MAX_ITERATIONS * 2) && tests_failed == 0) { + printf("Monitor: All tasks completed successfully\n"); + break; + } + + for (int i = 0; i < 5; i++) + mo_task_yield(); + } + + /* Final report */ + printf("\n=== FINAL RESULTS ===\n"); + printf("Tests passed: %d\n", tests_passed); + printf("Tests failed: %d\n", tests_failed); + + /* Test validation */ + bool all_passed = (tests_failed == 0); + bool good_coverage = (tests_passed >= (3 * MAX_ITERATIONS * 2)); + bool pmp_active = (read_pmpcfg0() != 0); + + printf("\nTest Results:\n"); + printf("All tests passed: %s\n", all_passed ? "PASS" : "FAIL"); + printf("Test coverage: %s\n", good_coverage ? "PASS" : "FAIL"); + printf("PMP still active: %s\n", pmp_active ? "PASS" : "FAIL"); + printf("Overall: %s\n", + (all_passed && good_coverage && pmp_active) ? "PASS" : "FAIL"); + + printf("PMP context switching test completed.\n"); + + while (1) { + for (int i = 0; i < 20; i++) + mo_task_yield(); + } +} + +/* Simple idle task */ +void idle_task(void) +{ + while (1) + mo_task_yield(); +} + +/* Application entry point */ +int32_t app_main(void) +{ + printf("PMP Context Switching Test Starting...\n"); + printf("Testing PMP CSR configuration and task isolation\n"); + printf("Kernel memory regions:\n"); + printf(" text: %p to %p\n", (void *)&_stext, (void *)&_etext); + printf(" data: %p to %p\n", (void *)&_sdata, (void *)&_edata); + printf(" bss: %p to %p\n\n", (void *)&_sbss, (void *)&_ebss); + + /* Create test tasks */ + int32_t task_a_id = mo_task_spawn(task_a, 1024); + int32_t task_b_id = mo_task_spawn(task_b, 1024); + int32_t task_c_id = mo_task_spawn(task_c, 1024); + int32_t monitor_id = mo_task_spawn(monitor_task, 1024); + int32_t idle_id = mo_task_spawn(idle_task, 512); + + if (task_a_id < 0 || task_b_id < 0 || task_c_id < 0 || monitor_id < 0 || + idle_id < 0) { + printf("FATAL: Failed to create test tasks\n"); + return false; + } + + printf("Tasks created: A=%d, B=%d, C=%d, Monitor=%d, Idle=%d\n", + (int)task_a_id, (int)task_b_id, (int)task_c_id, (int)monitor_id, + (int)idle_id); + + printf("Starting test...\n"); + return true; /* Enable preemptive scheduling */ +} diff --git a/app/task_term.c b/app/task_term.c new file mode 100644 index 0000000..46d4a36 --- /dev/null +++ b/app/task_term.c @@ -0,0 +1,89 @@ +/* Task Self-Termination and Cleanup Test + * + * Verifies that the zombie task cleanup mechanism works correctly: + * 1. A task can terminate itself via task_terminate_current() + * 2. The task's resources are properly cleaned up after context switch + * 3. The system continues running normally after task termination + */ + +#include + +static volatile int terminating_task_ran = 0; +static volatile int monitor_task_ran = 0; +static volatile int test_complete = 0; + +/* Task that will terminate itself */ +void terminating_task(void) +{ + terminating_task_ran = 1; + printf("Task %u: Calling task_terminate_current()\n", mo_task_id()); + + /* This should terminate the task and not return */ + task_terminate_current(); + + /* Should never reach here */ + printf("ERROR: Task %u still running after termination!\n", mo_task_id()); +} + +/* Monitor task verifies system still works after termination */ +void monitor_task(void) +{ + uint16_t initial_count; + uint16_t final_count; + + /* Check initial task count before any termination */ + initial_count = mo_task_count(); + printf("Monitor: Initial task count = %u\n", initial_count); + + /* Wait for terminating task to terminate itself */ + while (!terminating_task_ran) + mo_task_yield(); + + /* Give time for cleanup to occur */ + delay_ms(100); + + final_count = mo_task_count(); + printf("Monitor: Final task count = %u\n", final_count); + + /* Verify task was cleaned up */ + if (final_count < initial_count) { + printf("Monitor: Task successfully terminated and cleaned up\n"); + monitor_task_ran = 1; + } else { + printf("Monitor: ERROR - Task not cleaned up\n"); + } + + test_complete = 1; + + /* Wait for idle task to finish */ + while (1) + mo_task_yield(); +} + +/* Idle task */ +void idle_task(void) +{ + while (!test_complete) + mo_task_yield(); + + if (terminating_task_ran && monitor_task_ran) { + printf("\nTEST_PASSED\n"); + } else { + printf("\nTEST_FAILED\n"); + } + + while (1) + mo_task_wfi(); +} + +int32_t app_main(void) +{ + printf("Task Self-Termination Test\n"); + printf("Testing zombie task cleanup mechanism\n\n"); + + mo_task_spawn(idle_task, DEFAULT_STACK_SIZE); + mo_task_spawn(monitor_task, DEFAULT_STACK_SIZE); + mo_task_spawn(terminating_task, DEFAULT_STACK_SIZE); + + return 1; /* Preemptive mode */ +} diff --git a/arch/riscv/boot.c b/arch/riscv/boot.c index ef025de..c9dcead 100644 --- a/arch/riscv/boot.c +++ b/arch/riscv/boot.c @@ -16,7 +16,7 @@ extern uint32_t _sbss, _ebss; /* C entry points */ void main(void); -void do_trap(uint32_t cause, uint32_t epc); +void do_trap(uint32_t cause, uint32_t epc, uint32_t mtval); void hal_panic(void); /* Machine-mode entry point ('_entry'). This is the first code executed on @@ -94,10 +94,10 @@ __attribute__((naked, section(".text.prologue"))) void _entry(void) } /* Size of the full trap context frame saved on the stack by the ISR. - * 30 GPRs (x1, x3-x31) + mcause + mepc = 32 registers * 4 bytes = 128 bytes. - * This provides a 16-byte aligned full context save. + * 30 GPRs (x1, x3-x31) + mcause + mepc + mtval = 33 * 4 bytes = 132 bytes. + * Rounded to 136 bytes for 16-byte alignment. */ -#define ISR_CONTEXT_SIZE 128 +#define ISR_CONTEXT_SIZE 136 /* Low-level Interrupt Service Routine (ISR) trampoline. * @@ -120,7 +120,7 @@ __attribute__((naked, aligned(4))) void _isr(void) * 48: a4, 52: a5, 56: a6, 60: a7, 64: s2, 68: s3 * 72: s4, 76: s5, 80: s6, 84: s7, 88: s8, 92: s9 * 96: s10, 100:s11, 104:t3, 108: t4, 112: t5, 116: t6 - * 120: mcause, 124: mepc + * 120: mcause, 124: mepc, 128: mtval */ "sw ra, 0*4(sp)\n" "sw gp, 1*4(sp)\n" @@ -156,8 +156,10 @@ __attribute__((naked, aligned(4))) void _isr(void) /* Save trap-related CSRs and prepare arguments for do_trap */ "csrr a0, mcause\n" /* Arg 1: cause */ "csrr a1, mepc\n" /* Arg 2: epc */ + "csrr a2, mtval\n" /* Arg 3: mtval (faulting address) */ "sw a0, 30*4(sp)\n" "sw a1, 31*4(sp)\n" + "sw a2, 32*4(sp)\n" /* Call the high-level C trap handler */ "call do_trap\n" diff --git a/arch/riscv/build.mk b/arch/riscv/build.mk index 6efa81c..4e40f72 100644 --- a/arch/riscv/build.mk +++ b/arch/riscv/build.mk @@ -63,7 +63,7 @@ LDFLAGS += --gc-sections ARFLAGS = r LDSCRIPT = $(ARCH_DIR)/riscv32-qemu.ld -HAL_OBJS := boot.o hal.o muldiv.o +HAL_OBJS := boot.o hal.o muldiv.o pmp.o HAL_OBJS := $(addprefix $(BUILD_KERNEL_DIR)/,$(HAL_OBJS)) deps += $(HAL_OBJS:%.o=%.o.d) diff --git a/arch/riscv/csr.h b/arch/riscv/csr.h index 2f27ed8..e57869b 100644 --- a/arch/riscv/csr.h +++ b/arch/riscv/csr.h @@ -179,3 +179,82 @@ /* Machine Scratch Register - For temporary storage during traps */ #define CSR_MSCRATCH 0x340 + +/* PMP Address Registers (pmpaddr0-pmpaddr15) - 16 regions maximum + * In TOR (Top-of-Range) mode, these define the upper boundary of each region. + * The lower boundary is defined by the previous region's upper boundary. + */ +#define CSR_PMPADDR0 0x3b0 +#define CSR_PMPADDR1 0x3b1 +#define CSR_PMPADDR2 0x3b2 +#define CSR_PMPADDR3 0x3b3 +#define CSR_PMPADDR4 0x3b4 +#define CSR_PMPADDR5 0x3b5 +#define CSR_PMPADDR6 0x3b6 +#define CSR_PMPADDR7 0x3b7 +#define CSR_PMPADDR8 0x3b8 +#define CSR_PMPADDR9 0x3b9 +#define CSR_PMPADDR10 0x3ba +#define CSR_PMPADDR11 0x3bb +#define CSR_PMPADDR12 0x3bc +#define CSR_PMPADDR13 0x3bd +#define CSR_PMPADDR14 0x3be +#define CSR_PMPADDR15 0x3bf + +/* PMP Configuration Registers (pmpcfg0-pmpcfg3) + * Each configuration register controls 4 PMP regions (on RV32). + * pmpcfg0 controls pmpaddr0-3, pmpcfg1 controls pmpaddr4-7, etc. + */ +#define CSR_PMPCFG0 0x3a0 +#define CSR_PMPCFG1 0x3a1 +#define CSR_PMPCFG2 0x3a2 +#define CSR_PMPCFG3 0x3a3 + +/* PMP Configuration Field Bits (8 bits per region within pmpcfg) + * Layout in each byte of pmpcfg: + * Bit 7: L (Lock) - Locks this region until hardware reset + * Bits 6-5: Reserved + * Bits 4-3: A (Address Matching Mode) + * Bit 2: X (Execute permission) + * Bit 1: W (Write permission) + * Bit 0: R (Read permission) + */ + +/* Lock bit: Prevents further modification of this region */ +#define PMPCFG_L (1U << 7) + +/* Address Matching Mode (bits 3-4) + * Choose TOR mode for no alignment requirements on region sizes, and support + * for arbitrary address ranges. +*/ +#define PMPCFG_A_SHIFT 3 +#define PMPCFG_A_MASK (0x3U << PMPCFG_A_SHIFT) +#define PMPCFG_A_OFF (0x0U << PMPCFG_A_SHIFT) /* Null region (disabled) */ +#define PMPCFG_A_TOR (0x1U << PMPCFG_A_SHIFT) /* Top-of-Range mode */ + +/* Permission bits */ +#define PMPCFG_X (1U << 2) /* Execute permission */ +#define PMPCFG_W (1U << 1) /* Write permission */ +#define PMPCFG_R (1U << 0) /* Read permission */ + +/* Common permission combinations */ +#define PMPCFG_PERM_NONE (0x0U) /* No access */ +#define PMPCFG_PERM_R (PMPCFG_R) /* Read-only */ +#define PMPCFG_PERM_RW (PMPCFG_R | PMPCFG_W) /* Read-Write */ +#define PMPCFG_PERM_X (PMPCFG_X) /* Execute-only */ +#define PMPCFG_PERM_RX (PMPCFG_R | PMPCFG_X) /* Read-Execute */ +#define PMPCFG_PERM_RWX (PMPCFG_R | PMPCFG_W | PMPCFG_X) /* All access */ + +/* Utility macros for PMP configuration manipulation */ + +/* Extract PMP address matching mode */ +#define PMPCFG_GET_A(cfg) (((cfg) & PMPCFG_A_MASK) >> PMPCFG_A_SHIFT) + +/* Extract permission bits from configuration byte */ +#define PMPCFG_GET_PERM(cfg) ((cfg) & (PMPCFG_R | PMPCFG_W | PMPCFG_X)) + +/* Check if region is locked */ +#define PMPCFG_IS_LOCKED(cfg) (((cfg) & PMPCFG_L) != 0) + +/* Check if region is enabled (address mode is not OFF) */ +#define PMPCFG_IS_ENABLED(cfg) (PMPCFG_GET_A(cfg) != PMPCFG_A_OFF) diff --git a/arch/riscv/hal.c b/arch/riscv/hal.c index 35703a8..c56ab0b 100644 --- a/arch/riscv/hal.c +++ b/arch/riscv/hal.c @@ -3,6 +3,7 @@ #include #include "csr.h" +#include "pmp.h" #include "private/stdio.h" #include "private/utils.h" @@ -219,6 +220,12 @@ static void uart_init(uint32_t baud) void hal_hardware_init(void) { uart_init(USART_BAUD); + + /* Initialize PMP hardware with kernel memory regions */ + pmp_config_t *pmp_config = pmp_get_config(); + if (pmp_init_kernel(pmp_config) != 0) + hal_panic(); + /* Set the first timer interrupt. Subsequent interrupts are set in ISR */ mtimecmp_w(mtime_r() + (F_CPU / F_TIMER)); /* Install low-level I/O handlers for the C standard library */ @@ -251,8 +258,9 @@ void hal_cpu_idle(void) /* C-level trap handler, called by the '_isr' assembly routine. * @cause : The value of the 'mcause' CSR, indicating the reason for the trap. * @epc : The value of the 'mepc' CSR, the PC at the time of the trap. + * @mtval : The value of the 'mtval' CSR, the faulting address for access faults. */ -void do_trap(uint32_t cause, uint32_t epc) +void do_trap(uint32_t cause, uint32_t epc, uint32_t mtval) { static const char *exc_msg[] = { /* For printing helpful debug messages */ @@ -294,6 +302,18 @@ void do_trap(uint32_t cause, uint32_t epc) const char *reason = "Unknown exception"; if (code < ARRAY_SIZE(exc_msg) && exc_msg[code]) reason = exc_msg[code]; + + /* Attempt to recover PMP access faults */ + if (code == 5 || code == 7) { + if (pmp_handle_access_fault(mtval, code == 7) == 0) + return; + + /* Recovery failed - terminate task if in task context */ + if (kcb && kcb->task_current && kcb->task_current->data) + task_terminate_current(); + } + + /* All other exceptions are fatal */ printf("[EXCEPTION] code=%u (%s), epc=%08x, cause=%08x\n", code, reason, epc, cause); hal_panic(); diff --git a/arch/riscv/hal.h b/arch/riscv/hal.h index 8354264..3b10c4f 100644 --- a/arch/riscv/hal.h +++ b/arch/riscv/hal.h @@ -3,13 +3,14 @@ #include /* Symbols from the linker script, defining memory boundaries */ -extern uint32_t _stack_start, _stack_end; /* Start/end of the STACK memory */ -extern uint32_t _heap_start, _heap_end; /* Start/end of the HEAP memory */ -extern uint32_t _heap_size; /* Size of HEAP memory */ +extern uint32_t _stext, _etext; /* Start/end of the .text section */ extern uint32_t _sidata; /* Start address for .data initialization */ extern uint32_t _sdata, _edata; /* Start/end address for .data section */ extern uint32_t _sbss, _ebss; /* Start/end address for .bss section */ extern uint32_t _end; /* End of kernel image */ +extern uint32_t _heap_start, _heap_end; /* Start/end of the HEAP memory */ +extern uint32_t _heap_size; /* Size of HEAP memory */ +extern uint32_t _stack_bottom, _stack_top; /* Bottom/top of the STACK memory */ /* Read a RISC-V Control and Status Register (CSR). * @reg : The symbolic name of the CSR (e.g., mstatus). @@ -27,6 +28,25 @@ extern uint32_t _end; /* End of kernel image */ */ #define write_csr(reg, val) ({ asm volatile("csrw " #reg ", %0" ::"rK"(val)); }) +/* Read CSR by numeric address (for dynamic register selection). + * Used when CSR number is not known at compile-time (e.g., PMP registers). + * @csr_num : CSR address as a compile-time constant. + */ +#define read_csr_num(csr_num) \ + ({ \ + uint32_t __tmp; \ + asm volatile("csrr %0, %1" : "=r"(__tmp) : "i"(csr_num)); \ + __tmp; \ + }) + +/* Write CSR by numeric address (for dynamic register selection). + * Used when CSR number is not known at compile-time (e.g., PMP registers). + * @csr_num : CSR address as a compile-time constant. + * @val : The 32-bit value to write. + */ +#define write_csr_num(csr_num, val) \ + ({ asm volatile("csrw %0, %1" :: "i"(csr_num), "rK"(val)); }) + /* Globally enable or disable machine-level interrupts by setting mstatus.MIE. * @enable : Non-zero to enable, zero to disable. * Returns the previous state of the interrupt enable bit (1 if enabled, 0 if @@ -110,3 +130,8 @@ void hal_cpu_idle(void); /* Default stack size for new tasks if not otherwise specified */ #define DEFAULT_STACK_SIZE 4096 + +/* Physical Memory Protection (PMP) region limit constants */ +#define PMP_MAX_REGIONS 16 /* RISC-V supports 16 PMP regions */ +#define PMP_TOR_PAIRS 8 /* In TOR mode, 16 regions = 8 pairs (uses 2 addrs each) */ +#define MIN_PMP_REGION_SIZE 4 /* Minimum addressable size in TOR mode (4 bytes) */ diff --git a/arch/riscv/pmp.c b/arch/riscv/pmp.c new file mode 100644 index 0000000..2e66c02 --- /dev/null +++ b/arch/riscv/pmp.c @@ -0,0 +1,462 @@ +/* RISC-V Physical Memory Protection (PMP) Implementation + * + * Provides hardware-enforced memory isolation using PMP in TOR mode. + */ + +#include +#include +#include +#include "csr.h" +#include "pmp.h" +#include "private/error.h" + +/* PMP CSR Access Helpers + * + * RISC-V CSR instructions require compile-time constant addresses encoded in + * the instruction itself. These helpers use switch-case dispatch to provide + * runtime indexed access to PMP configuration and address registers. + * + * - pmpcfg0-3: Four 32-bit configuration registers (16 regions, 8 bits each) + * - pmpaddr0-15: Sixteen address registers for TOR (Top-of-Range) mode + */ + +/* Read PMP configuration register by index (0-3) */ +static uint32_t read_pmpcfg(uint8_t idx) +{ + switch (idx) { + case 0: return read_csr_num(CSR_PMPCFG0); + case 1: return read_csr_num(CSR_PMPCFG1); + case 2: return read_csr_num(CSR_PMPCFG2); + case 3: return read_csr_num(CSR_PMPCFG3); + default: return 0; + } +} + +/* Write PMP configuration register by index (0-3) */ +static void write_pmpcfg(uint8_t idx, uint32_t val) +{ + switch (idx) { + case 0: write_csr_num(CSR_PMPCFG0, val); break; + case 1: write_csr_num(CSR_PMPCFG1, val); break; + case 2: write_csr_num(CSR_PMPCFG2, val); break; + case 3: write_csr_num(CSR_PMPCFG3, val); break; + } +} + +/* Read PMP address register by index (0-15) + * + * Currently unused as the implementation maintains shadow state in memory + * rather than reading hardware registers. Provided for API completeness + * and potential future use cases requiring hardware state verification. + */ +static uint32_t __attribute__((unused)) read_pmpaddr(uint8_t idx) +{ + switch (idx) { + case 0: return read_csr_num(CSR_PMPADDR0); + case 1: return read_csr_num(CSR_PMPADDR1); + case 2: return read_csr_num(CSR_PMPADDR2); + case 3: return read_csr_num(CSR_PMPADDR3); + case 4: return read_csr_num(CSR_PMPADDR4); + case 5: return read_csr_num(CSR_PMPADDR5); + case 6: return read_csr_num(CSR_PMPADDR6); + case 7: return read_csr_num(CSR_PMPADDR7); + case 8: return read_csr_num(CSR_PMPADDR8); + case 9: return read_csr_num(CSR_PMPADDR9); + case 10: return read_csr_num(CSR_PMPADDR10); + case 11: return read_csr_num(CSR_PMPADDR11); + case 12: return read_csr_num(CSR_PMPADDR12); + case 13: return read_csr_num(CSR_PMPADDR13); + case 14: return read_csr_num(CSR_PMPADDR14); + case 15: return read_csr_num(CSR_PMPADDR15); + default: return 0; + } +} + +/* Write PMP address register by index (0-15) */ +static void write_pmpaddr(uint8_t idx, uint32_t val) +{ + switch (idx) { + case 0: write_csr_num(CSR_PMPADDR0, val); break; + case 1: write_csr_num(CSR_PMPADDR1, val); break; + case 2: write_csr_num(CSR_PMPADDR2, val); break; + case 3: write_csr_num(CSR_PMPADDR3, val); break; + case 4: write_csr_num(CSR_PMPADDR4, val); break; + case 5: write_csr_num(CSR_PMPADDR5, val); break; + case 6: write_csr_num(CSR_PMPADDR6, val); break; + case 7: write_csr_num(CSR_PMPADDR7, val); break; + case 8: write_csr_num(CSR_PMPADDR8, val); break; + case 9: write_csr_num(CSR_PMPADDR9, val); break; + case 10: write_csr_num(CSR_PMPADDR10, val); break; + case 11: write_csr_num(CSR_PMPADDR11, val); break; + case 12: write_csr_num(CSR_PMPADDR12, val); break; + case 13: write_csr_num(CSR_PMPADDR13, val); break; + case 14: write_csr_num(CSR_PMPADDR14, val); break; + case 15: write_csr_num(CSR_PMPADDR15, val); break; + } +} + +/* Static Memory Pools for Boot-time PMP Initialization + * + * Defines kernel memory regions protected at boot. Each pool specifies + * a memory range and access permissions. + */ +static const mempool_t kernel_mempools[] = { + DECLARE_MEMPOOL("kernel_text", &_stext, &_etext, PMPCFG_PERM_RX, + PMP_PRIORITY_KERNEL), + DECLARE_MEMPOOL("kernel_data", &_sdata, &_edata, PMPCFG_PERM_RW, + PMP_PRIORITY_KERNEL), + DECLARE_MEMPOOL("kernel_bss", &_sbss, &_ebss, PMPCFG_PERM_RW, + PMP_PRIORITY_KERNEL), + DECLARE_MEMPOOL("kernel_heap", &_heap_start, &_heap_end, PMPCFG_PERM_RW, + PMP_PRIORITY_KERNEL), + DECLARE_MEMPOOL("kernel_stack", &_stack_bottom, &_stack_top, PMPCFG_PERM_RW, + PMP_PRIORITY_KERNEL), +}; + +#define KERNEL_MEMPOOL_COUNT \ + (sizeof(kernel_mempools) / sizeof(kernel_mempools[0])) + +/* Global PMP configuration (shadow of hardware state) */ +static pmp_config_t pmp_global_config; + +/* Helper to compute pmpcfg register index and bit offset for a given region */ +static inline void pmp_get_cfg_indices(uint8_t region_idx, uint8_t *cfg_idx, + uint8_t *cfg_offset) +{ + *cfg_idx = region_idx / 4; + *cfg_offset = (region_idx % 4) * 8; +} + +pmp_config_t *pmp_get_config(void) +{ + return &pmp_global_config; +} + +int32_t pmp_init(pmp_config_t *config) +{ + if (!config) + return ERR_PMP_INVALID_REGION; + + /* Clear all PMP regions in hardware and shadow configuration */ + for (uint8_t i = 0; i < PMP_MAX_REGIONS; i++) { + write_pmpaddr(i, 0); + if (i % 4 == 0) + write_pmpcfg(i / 4, 0); + + config->regions[i].addr_start = 0; + config->regions[i].addr_end = 0; + config->regions[i].permissions = 0; + config->regions[i].priority = PMP_PRIORITY_TEMPORARY; + config->regions[i].region_id = i; + config->regions[i].locked = 0; + } + + config->region_count = 0; + config->next_region_idx = 0; + config->initialized = 1; + + return ERR_OK; +} + +int32_t pmp_init_pools(pmp_config_t *config, const mempool_t *pools, + size_t count) +{ + if (!config || !pools || count == 0) + return ERR_PMP_INVALID_REGION; + + /* Initialize PMP hardware and state */ + int32_t ret = pmp_init(config); + if (ret < 0) + return ret; + + /* Configure each memory pool as a PMP region */ + for (size_t i = 0; i < count; i++) { + const mempool_t *pool = &pools[i]; + + /* Validate pool boundaries */ + if (pool->start >= pool->end) + return ERR_PMP_ADDR_RANGE; + + /* Prepare PMP region configuration */ + pmp_region_t region = { + .addr_start = pool->start, + .addr_end = pool->end, + .permissions = pool->flags & (PMPCFG_R | PMPCFG_W | PMPCFG_X), + .priority = pool->tag, + .region_id = i, + .locked = 0, + }; + + /* Configure the PMP region */ + ret = pmp_set_region(config, ®ion); + if (ret < 0) + return ret; + } + + return ERR_OK; +} + +int32_t pmp_init_kernel(pmp_config_t *config) +{ + return pmp_init_pools(config, kernel_mempools, KERNEL_MEMPOOL_COUNT); +} + +int32_t pmp_set_region(pmp_config_t *config, const pmp_region_t *region) +{ + if (!config || !region) + return ERR_PMP_INVALID_REGION; + + /* Validate region index is within bounds */ + if (region->region_id >= PMP_MAX_REGIONS) + return ERR_PMP_INVALID_REGION; + + /* Validate address range */ + if (region->addr_start >= region->addr_end) + return ERR_PMP_ADDR_RANGE; + + /* Check if region is already locked */ + if (config->regions[region->region_id].locked) + return ERR_PMP_LOCKED; + + uint8_t region_idx = region->region_id; + uint8_t pmpcfg_idx, pmpcfg_offset; + pmp_get_cfg_indices(region_idx, &pmpcfg_idx, &pmpcfg_offset); + + /* Build configuration byte with TOR mode and permissions */ + uint8_t pmpcfg_perm = region->permissions & (PMPCFG_R | PMPCFG_W | PMPCFG_X); + uint8_t pmpcfg_byte = PMPCFG_A_TOR | pmpcfg_perm; + if (region->locked) + pmpcfg_byte |= PMPCFG_L; + + /* Read current pmpcfg register to preserve other regions */ + uint32_t pmpcfg_val = read_pmpcfg(pmpcfg_idx); + + /* Clear the configuration byte for this region */ + pmpcfg_val &= ~(0xFFU << pmpcfg_offset); + + /* Write new configuration byte */ + pmpcfg_val |= (pmpcfg_byte << pmpcfg_offset); + + /* Write pmpaddr register with the upper boundary */ + write_pmpaddr(region_idx, region->addr_end); + + /* Write pmpcfg register with updated configuration */ + write_pmpcfg(pmpcfg_idx, pmpcfg_val); + + /* Update shadow configuration */ + config->regions[region_idx].addr_start = region->addr_start; + config->regions[region_idx].addr_end = region->addr_end; + config->regions[region_idx].permissions = region->permissions; + config->regions[region_idx].priority = region->priority; + config->regions[region_idx].region_id = region_idx; + config->regions[region_idx].locked = region->locked; + + /* Update region count if this is a newly used region */ + if (region_idx >= config->region_count) + config->region_count = region_idx + 1; + + return ERR_OK; +} + +int32_t pmp_disable_region(pmp_config_t *config, uint8_t region_idx) +{ + if (!config) + return ERR_PMP_INVALID_REGION; + + /* Validate region index is within bounds */ + if (region_idx >= PMP_MAX_REGIONS) + return ERR_PMP_INVALID_REGION; + + /* Check if region is already locked */ + if (config->regions[region_idx].locked) + return ERR_PMP_LOCKED; + + uint8_t pmpcfg_idx, pmpcfg_offset; + pmp_get_cfg_indices(region_idx, &pmpcfg_idx, &pmpcfg_offset); + + /* Read current pmpcfg register to preserve other regions */ + uint32_t pmpcfg_val = read_pmpcfg(pmpcfg_idx); + + /* Clear the configuration byte for this region (disables it) */ + pmpcfg_val &= ~(0xFFU << pmpcfg_offset); + + /* Write pmpcfg register with updated configuration */ + write_pmpcfg(pmpcfg_idx, pmpcfg_val); + + /* Update shadow configuration */ + config->regions[region_idx].addr_start = 0; + config->regions[region_idx].addr_end = 0; + config->regions[region_idx].permissions = 0; + + return ERR_OK; +} + +int32_t pmp_lock_region(pmp_config_t *config, uint8_t region_idx) +{ + if (!config) + return ERR_PMP_INVALID_REGION; + + /* Validate region index is within bounds */ + if (region_idx >= PMP_MAX_REGIONS) + return ERR_PMP_INVALID_REGION; + + uint8_t pmpcfg_idx, pmpcfg_offset; + pmp_get_cfg_indices(region_idx, &pmpcfg_idx, &pmpcfg_offset); + + /* Read current pmpcfg register to preserve other regions */ + uint32_t pmpcfg_val = read_pmpcfg(pmpcfg_idx); + + /* Get current configuration byte for this region */ + uint8_t pmpcfg_byte = (pmpcfg_val >> pmpcfg_offset) & 0xFFU; + + /* Set lock bit */ + pmpcfg_byte |= PMPCFG_L; + + /* Clear the configuration byte for this region */ + pmpcfg_val &= ~(0xFFU << pmpcfg_offset); + + /* Write new configuration byte with lock bit set */ + pmpcfg_val |= (pmpcfg_byte << pmpcfg_offset); + + /* Write pmpcfg register with updated configuration */ + write_pmpcfg(pmpcfg_idx, pmpcfg_val); + + /* Update shadow configuration */ + config->regions[region_idx].locked = 1; + + return ERR_OK; +} + +int32_t pmp_get_region(const pmp_config_t *config, uint8_t region_idx, + pmp_region_t *region) +{ + if (!config || !region) + return ERR_PMP_INVALID_REGION; + + /* Validate region index is within bounds */ + if (region_idx >= PMP_MAX_REGIONS) + return ERR_PMP_INVALID_REGION; + + uint8_t pmpcfg_idx, pmpcfg_offset; + pmp_get_cfg_indices(region_idx, &pmpcfg_idx, &pmpcfg_offset); + + /* Read the address and configuration from shadow configuration */ + region->addr_start = config->regions[region_idx].addr_start; + region->addr_end = config->regions[region_idx].addr_end; + region->permissions = config->regions[region_idx].permissions; + region->priority = config->regions[region_idx].priority; + region->region_id = region_idx; + region->locked = config->regions[region_idx].locked; + + return ERR_OK; +} + +int32_t pmp_check_access(const pmp_config_t *config, uint32_t addr, + uint32_t size, uint8_t is_write, uint8_t is_execute) +{ + if (!config) + return ERR_PMP_INVALID_REGION; + + uint32_t access_end = addr + size; + + /* In TOR mode, check all regions in priority order */ + for (uint8_t i = 0; i < config->region_count; i++) { + const pmp_region_t *region = &config->regions[i]; + + /* Skip disabled regions */ + if (region->addr_start == 0 && region->addr_end == 0) + continue; + + /* Check if access falls within this region */ + if (addr >= region->addr_start && access_end <= region->addr_end) { + /* Verify permissions match access type */ + uint8_t required_perm = 0; + if (is_write) + required_perm |= PMPCFG_W; + if (is_execute) + required_perm |= PMPCFG_X; + if (!is_write && !is_execute) + required_perm = PMPCFG_R; + + if ((region->permissions & required_perm) == required_perm) + return 1; /* Access allowed */ + else + return 0; /* Access denied */ + } + } + + /* Access not covered by any region */ + return 0; +} + +int32_t pmp_handle_access_fault(uint32_t fault_addr, uint8_t is_write) +{ + if (!kcb || !kcb->task_current || !kcb->task_current->data) + return -1; + + memspace_t *mspace = ((tcb_t *)kcb->task_current->data)->mspace; + if (!mspace) + return -1; + + /* Find flexpage containing faulting address */ + fpage_t *target_fpage = NULL; + for (fpage_t *fp = mspace->first; fp; fp = fp->as_next) { + if (fault_addr >= fp->base && fault_addr < (fp->base + fp->size)) { + target_fpage = fp; + break; + } + } + + if (!target_fpage || target_fpage->pmp_id != 0) + return -1; + + pmp_config_t *config = pmp_get_config(); + if (!config) + return -1; + + /* Load into available region or evict victim */ + if (config->next_region_idx < PMP_MAX_REGIONS) + return pmp_load_fpage(target_fpage, config->next_region_idx); + + fpage_t *victim = select_victim_fpage(mspace); + if (!victim) + return -1; + + int32_t ret = pmp_evict_fpage(victim); + return (ret == 0) ? pmp_load_fpage(target_fpage, victim->pmp_id) : ret; +} + +int32_t pmp_switch_context(memspace_t *old_mspace, memspace_t *new_mspace) +{ + if (old_mspace == new_mspace) + return 0; + + pmp_config_t *config = pmp_get_config(); + if (!config) + return -1; + + /* Evict old task's dynamic regions */ + if (old_mspace) { + for (fpage_t *fp = old_mspace->pmp_first; fp; fp = fp->pmp_next) { + uint8_t region_id = fp->pmp_id; + if (region_id != 0 && !config->regions[region_id].locked) { + pmp_disable_region(config, region_id); + fp->pmp_id = 0; + } + } + } + + /* Load new task's regions into available slots */ + if (new_mspace) { + uint8_t available_slots = PMP_MAX_REGIONS - config->region_count; + uint8_t loaded_count = 0; + + for (fpage_t *fp = new_mspace->first; fp && loaded_count < available_slots; fp = fp->as_next) { + uint8_t region_idx = config->region_count + loaded_count; + if (pmp_load_fpage(fp, region_idx) == 0) + loaded_count++; + } + } + + return 0; +} diff --git a/arch/riscv/pmp.h b/arch/riscv/pmp.h new file mode 100644 index 0000000..c8c6fe0 --- /dev/null +++ b/arch/riscv/pmp.h @@ -0,0 +1,135 @@ +/* RISC-V Physical Memory Protection (PMP) Hardware Layer + * + * Low-level interface to RISC-V PMP using TOR (Top-of-Range) mode for + * flexible region management without alignment constraints. + */ + +#pragma once + +#include +#include + +#include "csr.h" + +/* PMP Region Priority Levels (lower value = higher priority) + * + * Used for eviction decisions when hardware PMP regions are exhausted. + */ +typedef enum { + PMP_PRIORITY_KERNEL = 0, + PMP_PRIORITY_STACK = 1, + PMP_PRIORITY_SHARED = 2, + PMP_PRIORITY_TEMPORARY = 3, + PMP_PRIORITY_COUNT = 4 +} pmp_priority_t; + +/* PMP Region Configuration */ +typedef struct { + uint32_t addr_start; /* Start address (inclusive) */ + uint32_t addr_end; /* End address (exclusive, written to pmpaddr) */ + uint8_t permissions; /* R/W/X bits (PMPCFG_R | PMPCFG_W | PMPCFG_X) */ + pmp_priority_t priority; /* Eviction priority */ + uint8_t region_id; /* Hardware region index (0-15) */ + uint8_t locked; /* Lock bit (cannot modify until reset) */ +} pmp_region_t; + +/* PMP Global State */ +typedef struct { + pmp_region_t regions[PMP_MAX_REGIONS]; /* Shadow of hardware config */ + uint8_t region_count; /* Active region count */ + uint8_t next_region_idx; /* Next free region index */ + uint32_t initialized; /* Initialization flag */ +} pmp_config_t; + +/* PMP Management Functions */ + +/* Returns pointer to global PMP configuration */ +pmp_config_t *pmp_get_config(void); + +/* Initializes the PMP hardware and configuration state. + * @config : Pointer to pmp_config_t structure to be initialized. + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_init(pmp_config_t *config); + +/* Configures a single PMP region in TOR mode. + * @config : Pointer to PMP configuration state + * @region : Pointer to pmp_region_t structure with desired configuration + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_set_region(pmp_config_t *config, const pmp_region_t *region); + +/* Reads the current configuration of a PMP region. + * @config : Pointer to PMP configuration state + * @region_idx : Index of the region to read (0-15) + * @region : Pointer to pmp_region_t to store the result + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_get_region(const pmp_config_t *config, uint8_t region_idx, + pmp_region_t *region); + +/* Disables a PMP region. + * @config : Pointer to PMP configuration state + * @region_idx : Index of the region to disable (0-15) + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_disable_region(pmp_config_t *config, uint8_t region_idx); + +/* Locks a PMP region to prevent further modification. + * @config : Pointer to PMP configuration state + * @region_idx : Index of the region to lock (0-15) + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_lock_region(pmp_config_t *config, uint8_t region_idx); + +/* Verifies that a memory access is allowed by the current PMP configuration. + * @config : Pointer to PMP configuration state + * @addr : Address to check + * @size : Size of the access in bytes + * @is_write : 1 for write access, 0 for read access + * @is_execute : 1 for execute access, 0 for data access + * Returns 1 if access is allowed, 0 if denied, or negative error code. + */ +int32_t pmp_check_access(const pmp_config_t *config, uint32_t addr, + uint32_t size, uint8_t is_write, uint8_t is_execute); + +/* Memory Pool Management Functions */ + +/* Initializes PMP regions from an array of memory pool descriptors. + * @config : Pointer to PMP configuration state + * @pools : Array of memory pool descriptors + * @count : Number of pools in the array + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_init_pools(pmp_config_t *config, const mempool_t *pools, + size_t count); + +/* Initializes PMP with default kernel memory pools. + * @config : Pointer to PMP configuration state + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_init_kernel(pmp_config_t *config); + +/* Handles PMP access violations (exception codes 5 and 7). + * + * Attempts to recover from PMP access faults by loading the required memory + * region into a hardware PMP region. If all 16 regions are in use, selects a + * victim for eviction and reuses its region. + * + * @fault_addr : The faulting memory address (from mtval CSR) + * @is_write : 1 for store/AMO access (exception code 7), 0 for load (code 5) + * Returns 0 on successful recovery, negative error code on failure. + */ +int32_t pmp_handle_access_fault(uint32_t fault_addr, uint8_t is_write); + +/* Switches PMP configuration during task context switch. + * + * Evicts the old task's dynamic regions from hardware and loads the new + * task's regions into available PMP slots. Kernel regions marked as locked + * are preserved across all context switches. + * + * @old_mspace : Memory space of task being switched out (can be NULL) + * @new_mspace : Memory space of task being switched in (can be NULL) + * Returns 0 on success, negative error code on failure. + */ +int32_t pmp_switch_context(memspace_t *old_mspace, memspace_t *new_mspace); diff --git a/include/linmo.h b/include/linmo.h index 420e7d0..b67c589 100644 --- a/include/linmo.h +++ b/include/linmo.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include diff --git a/include/private/error.h b/include/private/error.h index 27dcfb8..6172dd5 100644 --- a/include/private/error.h +++ b/include/private/error.h @@ -29,6 +29,14 @@ enum { ERR_STACK_CHECK, /* Stack overflow or corruption detected */ ERR_HEAP_CORRUPT, /* Heap corruption or invalid free detected */ + /* PMP Configuration Errors */ + ERR_PMP_INVALID_REGION, /* Invalid PMP region parameters */ + ERR_PMP_NO_REGIONS, /* No free PMP regions available */ + ERR_PMP_LOCKED, /* Region is locked by higher priority */ + ERR_PMP_SIZE_MISMATCH, /* Size doesn't meet alignment requirements */ + ERR_PMP_ADDR_RANGE, /* Address range is invalid */ + ERR_PMP_NOT_INIT, /* PMP not initialized */ + /* IPC and Synchronization Errors */ ERR_PIPE_ALLOC, /* Pipe allocation failed */ ERR_PIPE_DEALLOC, /* Pipe deallocation failed */ diff --git a/include/sys/memprot.h b/include/sys/memprot.h new file mode 100644 index 0000000..69b5bd5 --- /dev/null +++ b/include/sys/memprot.h @@ -0,0 +1,130 @@ +/* Memory Protection Abstractions + * + * Software abstractions for managing memory protection at different + * granularities. These structures build upon hardware protection + * mechanisms (such as RISC-V PMP) to provide flexible, architecture- + * independent memory isolation. + */ + +#pragma once + +#include + +/* Forward declarations */ +struct fpage; +struct as; + +/* Flexpage + * + * Contiguous physical memory region with hardware-enforced protection. + * Supports arbitrary base addresses and sizes without alignment constraints. + */ +typedef struct fpage { + struct fpage *as_next; /* Next in address space list */ + struct fpage *map_next; /* Next in mapping chain */ + struct fpage *pmp_next; /* Next in PMP queue */ + uint32_t base; /* Physical base address */ + uint32_t size; /* Region size */ + uint32_t rwx; /* R/W/X permission bits */ + uint32_t pmp_id; /* PMP region index */ + uint32_t flags; /* Status flags */ + uint32_t priority; /* Eviction priority */ + int used; /* Usage counter */ +} fpage_t; + +/* Memory Space + * + * Collection of flexpages forming a task's memory view. Can be shared + * across multiple tasks. + */ +typedef struct memspace { + uint32_t as_id; /* Memory space identifier */ + struct fpage *first; /* Head of flexpage list */ + struct fpage *pmp_first; /* Head of PMP-loaded list */ + struct fpage *pmp_stack; /* Stack regions */ + uint32_t shared; /* Shared flag */ +} memspace_t; + +/* Memory Pool + * + * Static memory region descriptor for boot-time PMP initialization. + */ +typedef struct { + const char *name; /* Pool name */ + uintptr_t start; /* Start address */ + uintptr_t end; /* End address */ + uint32_t flags; /* Access permissions */ + uint32_t tag; /* Pool type/priority */ +} mempool_t; + +/* Memory Pool Declaration Helpers + * + * Simplifies memory pool initialization with designated initializers. + * DECLARE_MEMPOOL_FROM_SYMBOLS uses token concatenation to construct + * linker symbol names automatically. + */ +#define DECLARE_MEMPOOL(name_, start_, end_, flags_, tag_) \ + { \ + .name = (name_), \ + .start = (uintptr_t)(start_), \ + .end = (uintptr_t)(end_), \ + .flags = (flags_), \ + .tag = (tag_), \ + } + +#define DECLARE_MEMPOOL_FROM_SYMBOLS(name_, sym_base_, flags_, tag_) \ + DECLARE_MEMPOOL((name_), &(sym_base_##_start), &(sym_base_##_end), (flags_), (tag_)) + +/* Flexpage Management Functions */ + +/* Creates and initializes a new flexpage. + * @base : Physical base address + * @size : Size in bytes + * @rwx : Permission bits + * @priority : Eviction priority + * Returns pointer to created flexpage, or NULL on failure. + */ +fpage_t *mo_fpage_create(uint32_t base, uint32_t size, uint32_t rwx, + uint32_t priority); + +/* Destroys a flexpage. + * @fpage : Pointer to flexpage to destroy + */ +void mo_fpage_destroy(fpage_t *fpage); + +/* Memory Space Management Functions */ + +/* Creates and initializes a memory space. + * @as_id : Memory space identifier + * @shared : Whether this space can be shared across tasks + * Returns pointer to created memory space, or NULL on failure. + */ +memspace_t *mo_memspace_create(uint32_t as_id, uint32_t shared); + +/* Destroys a memory space and all its flexpages. + * @mspace : Pointer to memory space to destroy + */ +void mo_memspace_destroy(memspace_t *mspace); + +/* PMP Hardware Loading Functions */ + +/* Loads a flexpage into a PMP hardware region. + * @fpage : Pointer to flexpage to load + * @region_idx : Hardware PMP region index (0-15) + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_load_fpage(fpage_t *fpage, uint8_t region_idx); + +/* Evicts a flexpage from its PMP hardware region. + * @fpage : Pointer to flexpage to evict + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_evict_fpage(fpage_t *fpage); + +/* Victim Selection for PMP Region Eviction + * + * Selects a flexpage for eviction using priority-based algorithm. + * @mspace : Pointer to memory space + * Returns pointer to victim flexpage, or NULL if no evictable page found. + */ +fpage_t *select_victim_fpage(memspace_t *mspace); diff --git a/include/sys/task.h b/include/sys/task.h index 33d0b60..92d59df 100644 --- a/include/sys/task.h +++ b/include/sys/task.h @@ -44,6 +44,9 @@ enum task_states { TASK_SUSPENDED /* Task paused/excluded from scheduling until resumed */ }; +/* Task Flags */ +#define TASK_FLAG_ZOMBIE 0x01 /* Task terminated, awaiting cleanup */ + /* Priority Level Constants for Priority-Aware Time Slicing */ #define TASK_PRIORITY_LEVELS 8 /* Number of priority levels (0-7) */ #define TASK_HIGHEST_PRIORITY 0 /* Highest priority level */ @@ -59,6 +62,9 @@ enum task_states { #define TASK_TIMESLICE_LOW 10 /* Low priority: longer slice */ #define TASK_TIMESLICE_IDLE 15 /* Idle tasks: longest slice */ +/* Forward declaration */ +struct memspace; + /* Task Control Block (TCB) * * Contains all essential information about a single task, including saved @@ -71,6 +77,8 @@ typedef struct tcb { size_t stack_sz; /* Total size of the stack in bytes */ void (*entry)(void); /* Task's entry point function */ + /* Memory Protection */ + struct memspace *mspace; /* Memory space for task isolation */ /* Scheduling Parameters */ uint16_t prio; /* Encoded priority (base and time slice counter) */ uint8_t prio_level; /* Priority level (0-7, 0 = highest) */ @@ -78,7 +86,7 @@ typedef struct tcb { uint16_t delay; /* Ticks remaining for task in TASK_BLOCKED state */ uint16_t id; /* Unique task ID, assigned by kernel upon creation */ uint8_t state; /* Current lifecycle state (e.g., TASK_READY) */ - uint8_t flags; /* Task flags for future extensions (reserved) */ + uint8_t flags; /* Task flags (TASK_FLAG_ZOMBIE for deferred cleanup) */ /* Real-time Scheduling Support */ void *rt_prio; /* Opaque pointer for custom real-time scheduler hook */ @@ -276,6 +284,16 @@ uint64_t mo_uptime(void); */ void _sched_block(queue_t *wait_q); +/* Terminates the currently running task due to unrecoverable fault. + * + * Marks the current task as suspended and sets the zombie flag for deferred + * cleanup. Forces an immediate context switch to another task. The marked + * task's resources will be freed by the scheduler after the switch completes. + * + * This function does not return - execution continues in another task. + */ +void task_terminate_current(void) __attribute__((noreturn)); + /* Application Entry Point */ /* The main entry point for the user application. diff --git a/kernel/memprot.c b/kernel/memprot.c new file mode 100644 index 0000000..5298600 --- /dev/null +++ b/kernel/memprot.c @@ -0,0 +1,147 @@ +/* Memory Protection Management + * + * Provides allocation and management functions for flexpages, which are + * software abstractions representing contiguous physical memory regions with + * hardware-enforced protection attributes. + */ + +#include +#include +#include +#include + +/* Creates and initializes a flexpage */ +fpage_t *mo_fpage_create(uint32_t base, uint32_t size, uint32_t rwx, + uint32_t priority) +{ + fpage_t *fpage = malloc(sizeof(fpage_t)); + if (!fpage) + return NULL; + + /* Initialize all fields */ + fpage->as_next = NULL; + fpage->map_next = NULL; + fpage->pmp_next = NULL; + fpage->base = base; + fpage->size = size; + fpage->rwx = rwx; + fpage->pmp_id = 0; /* Not loaded into PMP initially */ + fpage->flags = 0; /* No flags set initially */ + fpage->priority = priority; + fpage->used = 0; /* Not in use initially */ + + return fpage; +} + +/* Destroys a flexpage */ +void mo_fpage_destroy(fpage_t *fpage) +{ + if (!fpage) + return; + + free(fpage); +} + +/* Selects victim flexpage for eviction using priority-based algorithm */ +fpage_t *select_victim_fpage(memspace_t *mspace) +{ + if (!mspace) + return NULL; + + fpage_t *victim = NULL; + uint32_t lowest_prio = 0; + + /* Select page with highest priority value (lowest priority). + * Kernel regions (priority 0) are never selected. */ + for (fpage_t *fp = mspace->pmp_first; fp; fp = fp->pmp_next) { + if (fp->priority > lowest_prio) { + victim = fp; + lowest_prio = fp->priority; + } + } + + return victim; +} + +/* Loads a flexpage into a PMP hardware region */ +int32_t pmp_load_fpage(fpage_t *fpage, uint8_t region_idx) +{ + if (!fpage) + return -1; + + pmp_config_t *config = pmp_get_config(); + if (!config) + return -1; + + /* Configure PMP region from flexpage attributes */ + pmp_region_t region = { + .addr_start = fpage->base, + .addr_end = fpage->base + fpage->size, + .permissions = fpage->rwx, + .priority = fpage->priority, + .region_id = region_idx, + .locked = 0, + }; + + int32_t ret = pmp_set_region(config, ®ion); + if (ret == 0) { + fpage->pmp_id = region_idx; + } + + return ret; +} + +/* Evicts a flexpage from its PMP hardware region */ +int32_t pmp_evict_fpage(fpage_t *fpage) +{ + if (!fpage) + return -1; + + /* Only evict if actually loaded into PMP */ + if (fpage->pmp_id == 0) + return 0; + + pmp_config_t *config = pmp_get_config(); + if (!config) + return -1; + + int32_t ret = pmp_disable_region(config, fpage->pmp_id); + if (ret == 0) { + fpage->pmp_id = 0; + } + + return ret; +} + +/* Creates and initializes a memory space */ +memspace_t *mo_memspace_create(uint32_t as_id, uint32_t shared) +{ + memspace_t *mspace = malloc(sizeof(memspace_t)); + if (!mspace) + return NULL; + + mspace->as_id = as_id; + mspace->first = NULL; + mspace->pmp_first = NULL; + mspace->pmp_stack = NULL; + mspace->shared = shared; + + return mspace; +} + +/* Destroys a memory space and all its flexpages */ +void mo_memspace_destroy(memspace_t *mspace) +{ + if (!mspace) + return; + + /* Free all flexpages in the list */ + fpage_t *fp = mspace->first; + while (fp) { + fpage_t *next = fp->as_next; + mo_fpage_destroy(fp); + fp = next; + } + + free(mspace); +} diff --git a/kernel/task.c b/kernel/task.c index 59ffdae..41a2a84 100644 --- a/kernel/task.c +++ b/kernel/task.c @@ -7,6 +7,7 @@ #include #include +#include #include #include "private/error.h" @@ -394,6 +395,48 @@ void sched_wakeup_task(tcb_t *task) } } +/* Helper to clean up zombie task resources from safe context */ +static void cleanup_zombie_task(tcb_t *zombie) +{ + if (!zombie || !(zombie->flags & TASK_FLAG_ZOMBIE)) + return; + + /* Find and remove task node from list */ + CRITICAL_ENTER(); + + list_node_t *node = NULL; + list_node_t *iter = kcb->tasks->head; + while (iter) { + if (iter->data == zombie) { + node = iter; + break; + } + iter = iter->next; + } + + if (node) { + list_remove(kcb->tasks, node); + kcb->task_count--; + + /* Clear from cache */ + for (int i = 0; i < TASK_CACHE_SIZE; i++) { + if (task_cache[i].task == zombie) { + task_cache[i].id = 0; + task_cache[i].task = NULL; + } + } + } + + CRITICAL_LEAVE(); + + /* Free resources outside critical section */ + if (zombie->mspace) + mo_memspace_destroy(zombie->mspace); + + free(zombie->stack); + free(zombie); +} + /* Efficient Round-Robin Task Selection with O(n) Complexity * * Selects the next ready task using circular traversal of the master task list. @@ -416,6 +459,16 @@ uint16_t sched_select_next_task(void) tcb_t *current_task = kcb->task_current->data; + /* Clean up current task if it's a zombie before proceeding */ + if (current_task->flags & TASK_FLAG_ZOMBIE) { + cleanup_zombie_task(current_task); + /* After cleanup, move to first real task to start fresh search */ + kcb->task_current = kcb->tasks->head->next; + if (!kcb->task_current || !kcb->task_current->data) + panic(ERR_NO_TASKS); + current_task = kcb->task_current->data; + } + /* Mark current task as ready if it was running */ if (current_task->state == TASK_RUNNING) current_task->state = TASK_READY; @@ -433,6 +486,16 @@ uint16_t sched_select_next_task(void) tcb_t *task = node->data; + /* Clean up zombie tasks during scheduling */ + if (task->flags & TASK_FLAG_ZOMBIE) { + list_node_t *next_node = list_cnext(kcb->tasks, node); + cleanup_zombie_task(task); + node = next_node ? next_node : kcb->tasks->head; + if (!node || !node->data) + continue; + task = node->data; + } + /* Skip non-ready tasks */ if (task->state != TASK_READY) continue; @@ -457,6 +520,7 @@ static int32_t noop_rtsched(void) return -1; } + /* The main entry point from the system tick interrupt. */ void dispatcher(void) { @@ -495,12 +559,20 @@ void dispatch(void) uint32_t ready_count = 0; list_foreach(kcb->tasks, delay_update_batch, &ready_count); + /* Save old task's memory space for PMP context switching */ + tcb_t *old_task = (tcb_t *)kcb->task_current->data; + memspace_t *old_mspace = old_task->mspace; + /* Hook for real-time scheduler - if it selects a task, use it */ if (kcb->rt_sched() < 0) sched_select_next_task(); /* Use O(1) priority scheduler */ hal_interrupt_tick(); + /* Switch PMP configuration if tasks have different memory spaces */ + memspace_t *new_mspace = ((tcb_t *) kcb->task_current->data)->mspace; + pmp_switch_context(old_mspace, new_mspace); + /* Restore next task context */ hal_context_restore(((tcb_t *) kcb->task_current->data)->context, 1); } @@ -526,7 +598,16 @@ void yield(void) if (!kcb->preemptive) list_foreach(kcb->tasks, delay_update, NULL); + /* Save old task's memory space for PMP context switching */ + tcb_t *old_task = (tcb_t *)kcb->task_current->data; + memspace_t *old_mspace = old_task->mspace; + sched_select_next_task(); /* Use O(1) priority scheduler */ + + /* Switch PMP configuration if tasks have different memory spaces */ + memspace_t *new_mspace = ((tcb_t *) kcb->task_current->data)->mspace; + pmp_switch_context(old_mspace, new_mspace); + hal_context_restore(((tcb_t *) kcb->task_current->data)->context, 1); } @@ -590,6 +671,30 @@ int32_t mo_task_spawn(void *task_entry, uint16_t stack_size_req) panic(ERR_STACK_ALLOC); } + /* Create memory space for task */ + tcb->mspace = mo_memspace_create(kcb->next_tid, 0); + if (!tcb->mspace) { + free(tcb->stack); + free(tcb); + panic(ERR_TCB_ALLOC); + } + + /* Register stack as flexpage */ + fpage_t *stack_fpage = + mo_fpage_create((uint32_t) tcb->stack, new_stack_size, + PMPCFG_R | PMPCFG_W, PMP_PRIORITY_STACK); + if (!stack_fpage) { + mo_memspace_destroy(tcb->mspace); + free(tcb->stack); + free(tcb); + panic(ERR_TCB_ALLOC); + } + + /* Add stack to memory space */ + stack_fpage->as_next = tcb->mspace->first; + tcb->mspace->first = stack_fpage; + tcb->mspace->pmp_stack = stack_fpage; + /* Minimize critical section duration */ CRITICAL_ENTER(); @@ -674,6 +779,33 @@ int32_t mo_task_cancel(uint16_t id) return ERR_OK; } +void task_terminate_current(void) +{ + NOSCHED_ENTER(); + + /* Verify we have a current task */ + if (unlikely(!kcb || !kcb->task_current || !kcb->task_current->data)) { + NOSCHED_LEAVE(); + panic(ERR_NO_TASKS); + } + + tcb_t *self = kcb->task_current->data; + + /* Mark as suspended to prevent re-scheduling */ + self->state = TASK_SUSPENDED; + + /* Set zombie flag for deferred cleanup */ + self->flags |= TASK_FLAG_ZOMBIE; + + NOSCHED_LEAVE(); + + /* Force immediate context switch - never returns */ + _dispatch(); + + /* Unreachable */ + __builtin_unreachable(); +} + void mo_task_yield(void) { _yield();