Satoshi Yoneda 1 місяць тому
батько
коміт
173026cd6c

+ 1 - 1
.gitignore

@@ -1,3 +1,3 @@
 build
 !.vscode/*
-2.3.1/
+2.3.1

+ 12 - 26
CMakeLists.txt

@@ -33,38 +33,24 @@ project(tinykernel C CXX ASM)
 # Initialise the Raspberry Pi Pico SDK
 pico_sdk_init()
 
-# Tinykernel: minimal preemptive task scheduler (Cortex-M33 / RP2350)
+# Tinykernel library
+add_subdirectory(tinykernel)
 
-add_library(tinykernel_lib STATIC
-        src/scheduler.cpp
-        src/port/m33/port_m33.c
-        src/port/m33/context.S
-)
-target_include_directories(tinykernel_lib PUBLIC
-        ${CMAKE_CURRENT_LIST_DIR}/include
-)
-target_link_libraries(tinykernel_lib PUBLIC pico_stdlib)
-
-# Add executable. Default name is the project name, version 0.1
+# Add executable demo
+add_executable(tinykernel_app main.cpp)
 
-add_executable(tinykernel main.cpp )
-
-pico_set_program_name(tinykernel "tinykernel")
-pico_set_program_version(tinykernel "0.1")
+pico_set_program_name(tinykernel_app "tinykernel")
+pico_set_program_version(tinykernel_app "0.1")
 
 # Modify the below lines to enable/disable output over UART/USB
-pico_enable_stdio_uart(tinykernel 1)
-pico_enable_stdio_usb(tinykernel 0)
+pico_enable_stdio_uart(tinykernel_app 1)
+pico_enable_stdio_usb(tinykernel_app 0)
 
-# Add the standard library to the build
-target_link_libraries(tinykernel
+# Link libraries
+target_link_libraries(tinykernel_app
         pico_stdlib
-        tinykernel_lib)
-
-# Add the standard include files to the build
-target_include_directories(tinykernel PRIVATE
-        ${CMAKE_CURRENT_LIST_DIR}
+        tinykernel
 )
 
-pico_add_extra_outputs(tinykernel)
+pico_add_extra_outputs(tinykernel_app)
 

+ 16 - 0
tinykernel/CMakeLists.txt

@@ -0,0 +1,16 @@
+# tinykernel: minimal preemptive task scheduler library (RP2350 / Pico 2)
+
+add_library(tinykernel STATIC
+    src/scheduler.cpp
+    src/port/m33/port_m33.c
+    src/port/m33/context.S
+)
+
+target_include_directories(tinykernel PUBLIC
+    ${CMAKE_CURRENT_LIST_DIR}/include
+)
+
+target_link_libraries(tinykernel PUBLIC
+    pico_stdlib
+)
+

+ 0 - 0
include/tinykernel/kernel.hpp → tinykernel/include/tinykernel/kernel.hpp


+ 15 - 33
src/port/m33/context.S → tinykernel/src/port/m33/context.S

@@ -15,11 +15,10 @@
     .syntax unified
     .thumb
 
-    .extern tk_reschedule
+    .extern tk_switch_context
     .extern tk_fpu_scratch
     .extern tk_task_return_trampoline
-    .global isr_systick
-    .global isr_svcall
+    .global isr_pendsv
     .global tk_port_start
     .global tk_port_make_frame
 
@@ -37,24 +36,12 @@
     vldmia  r3!, {d8-d15}
 .endm
 
-// Shared dispatch for SysTick (preemption tick) and SVCall (yield/sleep/exit).
+// PendSV Handler: dedicated context switch handler running at the lowest priority.
 // Hardware has already pushed the Core8 frame to the current task's PSP.
 // Handler mode automatically runs on MSP.
-    .type isr_systick,%function
+    .type isr_pendsv,%function
     .thumb_func
-isr_systick:
-    movs    r1, #1                    // REASON_TICK
-    b       tk_dispatch_common
-
-    .type isr_svcall,%function
-    .thumb_func
-isr_svcall:
-    movs    r1, #2                    // REASON_SVC
-    b       tk_dispatch_common
-
-    .type tk_dispatch_common,%function
-    .thumb_func
-tk_dispatch_common:
+isr_pendsv:
     // 1. Get current task stack pointer (PSP)
     mrs     r0, psp
 
@@ -64,31 +51,26 @@ tk_dispatch_common:
     // 3. Save FPU state to global scratch buffer
     save_fpu_to_scratch
 
-    // 4. Preserve EXC_RETURN (lr) and reason (r1) on MSP (8-byte stack alignment)
-    push    {r1, lr}
+    // 4. Preserve EXC_RETURN (lr) on MSP (push 2 registers for 8-byte stack alignment)
+    push    {r0, lr}
 
-    // 5. Call scheduler: r0 = cur_sp, r1 = reason
-    // Returns next task's SP in r0, or 0 to keep running current task.
-    bl      tk_reschedule
+    // 5. Call scheduler: r0 = cur_sp
+    // Returns next task's SP in r0.
+    bl      tk_switch_context
 
-    // 6. Restore FPU state for next (or continuing) task
+    // 6. Restore FPU state for next task
     restore_fpu_from_scratch
 
-    // 7. Restore EXC_RETURN (lr) and r1 from MSP
+    // 7. Restore EXC_RETURN (lr) from MSP
     pop     {r1, lr}
 
-    // 8. If r0 == 0, keep current task running
-    cmp     r0, #0
-    beq     .keep_running
-
-    // 9. Switch to new task: restore R4-R11 from new task's stack
+    // 8. Restore R4-R11 from new task's stack
     ldmia   r0!, {r4-r11}
 
-    // 10. Update PSP with new task's SP (points to Core8 hardware frame)
+    // 9. Update PSP with new task's SP (points to Core8 hardware frame)
     msr     psp, r0
 
-.keep_running:
-    // 11. Exception return: hardware pops Core8 frame from PSP and returns to Thread mode
+    // 10. Exception return: hardware pops Core8 frame from PSP and returns to Thread mode
     bx      lr
 
 // Start the first task.

+ 6 - 4
src/port/m33/port_m33.c → tinykernel/src/port/m33/port_m33.c

@@ -20,12 +20,14 @@ void tk_port_init(uint32_t ticks_per_sec) {
     uint32_t clk = clock_get_hz(clk_sys);
     systick_hw->rvr = ((clk + ticks_per_sec / 2u) / ticks_per_sec - 1u) & 0x00FFFFFFu;
 
-    // Run SVC and SysTick at the lowest priority (15 of 15 on M33) so peripheral
-    // ISRs always preempt them. Byte offsets follow CMSIS __NVIC_SetPriority:
+    // Run PendSV at the lowest priority (15 of 15 on M33) so peripheral ISRs
+    // and SysTick always preempt it, ensuring context switches happen only when all
+    // higher-priority interrupt handling is complete. SysTick runs at priority 14.
+    // Byte offsets follow CMSIS __NVIC_SetPriority:
     // SHPR base = SCB+0x18, byte index = ((IRQn) & 0xF) - 4.
     uint8_t* shpr = (uint8_t*)&scb_hw->shpr[0];
-    shpr[((int32_t)(-5) & 0xF) - 4] = 15u << 4;  // SVCall
-    shpr[((int32_t)(-1) & 0xF) - 4] = 15u << 4;  // SysTick
+    shpr[((int32_t)(-2) & 0xF) - 4] = 15u << 4;  // PendSV (priority 15: lowest)
+    shpr[((int32_t)(-1) & 0xF) - 4] = 14u << 4;  // SysTick (priority 14)
 
     // NOTE: SysTick is intentionally NOT enabled here. tk_port_start() enables it
     // (ENABLE|TICKINT|CLKSOURCE) in asm, right before jumping into the first task,

+ 9 - 1
src/port/m33/port_m33.h → tinykernel/src/port/m33/port_m33.h

@@ -2,11 +2,13 @@
 
 #include <stdint.h>
 
+#include "hardware/structs/scb.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-// One-time port setup: FPU context policy + SysTick. Called from tk::start().
+// One-time port setup: FPU context policy + SysTick + PendSV priority. Called from tk::start().
 void tk_port_init(uint32_t ticks_per_sec);
 
 // Build a task's initial stack frame. Returns the SP to start with.
@@ -15,6 +17,12 @@ uintptr_t tk_port_make_frame(uint8_t* base, uint32_t size_bytes, void* entry_fn,
 // Jump into the first task (r0 = SP from tk_port_make_frame). Never returns.
 void tk_port_start(uintptr_t sp);
 
+// Trigger a PendSV exception to initiate context switching.
+static inline void tk_port_trigger_pendsv(void) {
+    scb_hw->icsr = 1u << 28;  // SCB_ICSR_PENDSVSET_Msk
+    __asm__ volatile("isb" ::: "memory");
+}
+
 #ifdef __cplusplus
 }
 #endif

+ 45 - 61
src/scheduler.cpp → tinykernel/src/scheduler.cpp

@@ -19,11 +19,6 @@ namespace {
 
 enum class State : uint8_t { Free = 0, Ready, Sleeping, Terminated };
 
-// Must match the reason constants passed in r1 by context.S.
-enum Reason : int { REASON_TICK = 1, REASON_SVC = 2 };
-
-enum PendingOp : uint8_t { OP_NONE = 0, OP_YIELD, OP_SLEEP, OP_EXIT };
-
 struct TaskSlot {
     State state;
     tk::TaskFn fn;
@@ -46,8 +41,6 @@ static uint8_t g_cur = 0;
 static int64_t g_tick = 0;
 static bool g_started = false;
 static uint8_t g_rr_last[16] = {0};
-static PendingOp g_pending_op = OP_NONE;
-static int32_t g_pending_ticks = 0;
 
 constexpr uint32_t kIdlePrio = 15u;
 constexpr int32_t kSliceTicks = (int32_t)(((uint64_t)TK_TIME_SLICE_MS * TK_TICKS_PER_SEC + 999u) / 1000u);
@@ -97,17 +90,6 @@ static uint32_t select_next(bool force_switch) {
     return pick_round_robin((uint32_t)level);
 }
 
-static inline void tk_svc(void) {
-    __asm__ volatile("svc #0" ::: "memory");
-}
-
-// Called by the trampoline when a task function simply returns.
-extern "C" void tk_task_return_trampoline(void) {
-    g_pending_op = OP_EXIT;
-    tk_svc();
-    for (;;) __asm__ volatile("wfi");
-}
-
 }  // namespace
 
 // Referenced (unmangled) from context.S: scratch area the port asm uses to move FPU
@@ -116,50 +98,39 @@ extern "C" {
     alignas(8) FpuCtx tk_fpu_scratch = {};
 }
 
-extern "C" uintptr_t tk_reschedule(uintptr_t cur_sp, int reason) {
-    TaskSlot& cur = g_slots[g_cur];
-    bool force = false;
-
-    if (reason == REASON_TICK) {
-        ++g_tick;
-        for (uint32_t i = 1; i < TK_MAX_TASKS; ++i) {
-            TaskSlot& s = g_slots[i];
-            if (s.state == State::Sleeping && g_tick >= s.wake_tick) s.state = State::Ready;
-        }
-        if (!cur.is_idle) {
-            --cur.slice_left;
-            if (cur.slice_left <= 0) {
-                cur.slice_left = kSliceTicks;
-                force = true;
+// SysTick interrupt handler: advances ticks, wakes sleeping tasks, and triggers PendSV when needed.
+extern "C" void isr_systick(void) {
+    ++g_tick;
+    bool need_switch = false;
+    for (uint32_t i = 1; i < TK_MAX_TASKS; ++i) {
+        TaskSlot& s = g_slots[i];
+        if (s.state == State::Sleeping && g_tick >= s.wake_tick) {
+            s.state = State::Ready;
+            if (s.prio < g_slots[g_cur].prio) {
+                need_switch = true;
             }
         }
-    } else if (reason == REASON_SVC) {
-        switch (g_pending_op) {
-            case OP_YIELD:
-                force = true;
-                break;
-            case OP_SLEEP:
-                cur.state = State::Sleeping;
-                cur.wake_tick = g_tick + g_pending_ticks;
-                force = true;
-                break;
-            case OP_EXIT:
-                // Phase 1: the slot and its stack are not reclaimed.
-                cur.state = State::Terminated;
-                force = true;
-                break;
-            default:
-                break;
+    }
+    TaskSlot& cur = g_slots[g_cur];
+    if (!cur.is_idle) {
+        --cur.slice_left;
+        if (cur.slice_left <= 0) {
+            cur.slice_left = kSliceTicks;
+            need_switch = true;
         }
-        g_pending_op = OP_NONE;
     }
+    if (need_switch) {
+        tk_port_trigger_pendsv();
+    }
+}
 
-    uint32_t next = select_next(force);
-    if (next == g_cur) return 0;  // keep the current context running
-
+// PendSV context switch function: saves cur_sp, selects next task, and returns next SP.
+extern "C" uintptr_t tk_switch_context(uintptr_t cur_sp) {
+    TaskSlot& cur = g_slots[g_cur];
     cur.sp = cur_sp;
     memcpy(&cur.fpu, &tk_fpu_scratch, sizeof(cur.fpu));
 
+    uint32_t next = select_next(/*force_switch=*/true);
     TaskSlot& nxt = g_slots[next];
     if (nxt.sp == 0) {
         nxt.sp = tk_port_make_frame(reinterpret_cast<uint8_t*>(&g_stacks[next][0]),
@@ -237,25 +208,38 @@ void tk::start() {
 }
 
 void tk::yield() {
-    g_pending_op = OP_YIELD;
-    tk_svc();
+    bool lk = tk::lock();
+    g_slots[g_cur].slice_left = 0;
+    tk_port_trigger_pendsv();
+    tk::unlock(lk);
 }
 
 void tk::sleep_ms(uint32_t ms) {
     uint64_t ticks = ((uint64_t)ms * TK_TICKS_PER_SEC + 999u) / 1000u;
     if (ticks < 1u) ticks = 1u;
-    g_pending_ticks = (int32_t)(ticks > 0x7FFFFFFFu ? 0x7FFFFFFFu : ticks);
-    g_pending_op = OP_SLEEP;
-    tk_svc();
+    int32_t sleep_ticks = (int32_t)(ticks > 0x7FFFFFFFu ? 0x7FFFFFFFu : ticks);
+
+    bool lk = tk::lock();
+    TaskSlot& cur = g_slots[g_cur];
+    cur.state = State::Sleeping;
+    cur.wake_tick = g_tick + sleep_ticks;
+    tk_port_trigger_pendsv();
+    tk::unlock(lk);
 }
 
 void tk::exit(int code) {
     (void)code;
-    g_pending_op = OP_EXIT;
-    tk_svc();
+    bool lk = tk::lock();
+    g_slots[g_cur].state = State::Terminated;
+    tk_port_trigger_pendsv();
+    tk::unlock(lk);
     for (;;) __asm__ volatile("wfi");  // unreachable: the scheduler switches away
 }
 
+extern "C" void tk_task_return_trampoline(void) {
+    tk::exit(0);
+}
+
 uint64_t tk::now_ms() {
     return (uint64_t)g_tick * 1000u / TK_TICKS_PER_SEC;
 }