SuperTinyKernel™ RTOS 1.06.x
Lightweight, high-performance, deterministic, bare-metal C++ RTOS for resource-constrained embedded systems. MIT Open Source License.
Loading...
Searching...
No Matches
stk_arch_risc-v.cpp
Go to the documentation of this file.
1/*
2 * SuperTinyKernel(TM) RTOS: Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems.
3 *
4 * Source: https://github.com/SuperTinyKernel-RTOS
5 *
6 * Copyright (c) 2022-2026 Neutron Code Limited <stk@neutroncode.com>. All Rights Reserved.
7 * License: MIT License, see LICENSE for a full text.
8 */
9
10// note: If missing, this header must be customized (get it in the root of the source folder) and
11// copied to the /include folder manually.
12#include "stk_config.h"
13
14#ifdef _STK_ARCH_RISC_V
15
16#include "stk_arch.h"
18#include "stk_helper.h"
19
20using namespace stk;
21
60//#define _STK_RISCV_USE_PENDSV
61
62// CLINT
63// Details: https://github.com/riscv/riscv-aclint/blob/main/riscv-aclint.adoc
64#ifndef STK_RISCV_CLINT_BASE_ADDR
65 #define STK_RISCV_CLINT_BASE_ADDR (0x2000000U)
66#endif
67#ifndef STK_RISCV_CLINT_MTIMECMP_ADDR
68 #define STK_RISCV_CLINT_MTIMECMP_ADDR (STK_RISCV_CLINT_BASE_ADDR + 0x4000U) // 8-byte value, 1 per hart
69#endif
70#ifndef STK_RISCV_CLINT_MTIME_ADDR
71 #define STK_RISCV_CLINT_MTIME_ADDR (STK_RISCV_CLINT_BASE_ADDR + 0xBFF8U) // 8-byte value, global
72#endif
73
78#define STK_RISCV_ISR_STACK_SIZE 256U
79
83#ifndef STK_TIMER_CLOCK_FREQUENCY
84 #define STK_TIMER_CLOCK_FREQUENCY 1000000U
85#endif
86
91#ifndef STK_RISCV_ISR_SECTION
92 #define STK_RISCV_ISR_SECTION
93#endif
94
99#define STK_RISCV_ISR extern "C" STK_RISCV_ISR_SECTION __attribute__ ((interrupt("machine")))
100
102#define STK_ASM_EXIT_FROM_HANDLER "mret"
103
109#ifndef STK_RISCV_CLINT_MTIMECMP_PER_HART
110 #define STK_RISCV_CLINT_MTIMECMP_PER_HART 1
111#endif
112
117#ifndef STK_ARCH_GET_CPU_ID
118 #define STK_ARCH_GET_CPU_ID() read_csr(mhartid)
119#endif
120
122#if (__riscv_flen == 0)
123 #define STK_RISCV_FPU 0
124#else
125 #define STK_RISCV_FPU __riscv_flen
126#endif
127
128#define STR(x) #x
129#define XSTR(s) STR(s)
130
132#if (__riscv_xlen == 32)
133 #define REGBYTES XSTR(4)
134 #define LREG XSTR(lw)
135 #define SREG XSTR(sw)
136#elif (__riscv_xlen == 64)
137 #define REGBYTES XSTR(8)
138 #define LREG XSTR(ld)
139 #define SREG XSTR(sd)
140#else
141 #error Unsupported RISC-V platform!
142#endif
143
144#if (STK_RISCV_FPU == 32)
145 #define FREGBYTES XSTR(4)
146 #define FLREG XSTR(flw)
147 #define FSREG XSTR(fsw)
148#elif (STK_RISCV_FPU == 64)
149 #define FREGBYTES XSTR(8)
150 #define FLREG XSTR(fld)
151 #define FSREG XSTR(fsd)
152#elif (STK_RISCV_FPU != 0)
153#error Unsupported FP register count!
154#endif
155
156
157#if (__riscv_32e == 1)
158 #define STK_RISCV_REGISTER_COUNT (15U + (STK_RISCV_FPU != 0U ? 31U : 0U))
159#else
160 #define STK_RISCV_REGISTER_COUNT (31U + (STK_RISCV_FPU != 0U ? 31U : 0U))
161#endif
162
163#define STK_SERVICE_SLOTS 2U // (0) mepc, (1) mstatus
164
165#if (__riscv_32e == 1)
166 #define FOFFSET XSTR(68) // FP stack offset = (17 * 4)
167 #if (STK_RISCV_FPU == 0)
168 #define REGSIZE XSTR(((15U + STK_SERVICE_SLOTS) * 4U)) // STK_RISCV_REGISTER_COUNT + 2 for mepc, mstatus
169 #else
170 #if (STK_RISCV_FPU == 32)
171 #define REGSIZE XSTR((((15U + STK_SERVICE_SLOTS) * 4U) + (31U * 4U))) // STK_RISCV_REGISTER_COUNT + 2 for mepc, mstatus + 32 fp registers
172 #elif (STK_RISCV_FPU == 64)
173 #define REGSIZE XSTR((((15U + STK_SERVICE_SLOTS) * 4U) + (31U * 8U))) // STK_RISCV_REGISTER_COUNT + 2 for mepc, mstatus + 32 fp registers
174 #endif
175 #endif
176#elif (__riscv_xlen == 32)
177 #define FOFFSET XSTR(132) // FP stack offset = (33 * 4)
178 #if (STK_RISCV_FPU == 0)
179 #define REGSIZE XSTR(((31U + STK_SERVICE_SLOTS) * 4U)) // STK_RISCV_REGISTER_COUNT + 2 for mepc, mstatus
180 #else
181 #if (STK_RISCV_FPU == 32)
182 #define REGSIZE XSTR((((31U + STK_SERVICE_SLOTS) * 4U) + (31U * 4U))) // STK_RISCV_REGISTER_COUNT + 2 for mepc, mstatus + 32 fp registers
183 #elif (STK_RISCV_FPU == 64)
184 #define REGSIZE XSTR((((31U + STK_SERVICE_SLOTS) * 4U) + (31U * 8U))) // STK_RISCV_REGISTER_COUNT + 2 for mepc, mstatus + 32 fp registers
185 #endif
186 #endif
187#elif (__riscv_xlen == 64)
188 #define FOFFSET XSTR(264) // FP stack offset = (33 * 8)
189 #if (STK_RISCV_FPU == 0)
190 #define REGSIZE XSTR(((31U + STK_SERVICE_SLOTS) * 8U)) // STK_RISCV_REGISTER_COUNT + 2 for mepc, mstatus
191 #else
192 #if (STK_RISCV_FPU == 32)
193 #define REGSIZE XSTR((((31U + STK_SERVICE_SLOTS) * 8U) + (31U * 4U))) // STK_RISCV_REGISTER_COUNT + 2 for mepc, mstatus + 32 fp registers
194 #elif (STK_RISCV_FPU == 64)
195 #define REGSIZE XSTR((((31U + STK_SERVICE_SLOTS) * 8U) + (31U * 8U))) // STK_RISCV_REGISTER_COUNT + 2 for mepc, mstatus + 32 fp registers
196 #endif
197 #endif
198#endif
199
200#if (__riscv_xlen == 32)
201 #define REGBYTES_LOG2 "2" // log2(4) - used for hart-index shift
202#elif (__riscv_xlen == 64)
203 #define REGBYTES_LOG2 "3" // log2(8)
204#endif
205
212#ifndef STK_SYSTICK_HANDLER
213 #define STK_SYSTICK_HANDLER riscv_mtvec_mti
214#endif
215
222#ifndef STK_SVC_HANDLER
223 #define STK_SVC_HANDLER riscv_mtvec_exception
224#endif
225
233#ifndef STK_MSI_HANDLER
234 #define STK_MSI_HANDLER riscv_mtvec_msi
235#endif
236
255struct TaskFrame
256{
257 // Service slots (indices 0, 1) - sit at sp+0 and sp+REGBYTES
258 Word MEPC;
259 Word MSTATUS;
260
261 // General-purpose register slots (indices 2..N), one per xN
262 Word X1_RA;
263 Word X2_SP;
264#if (STK_RISCV_FPU != 0)
265 Word X3_FSR;
266#else
267 Word X3_GP;
268#endif
269 Word X4;
270 Word X5;
271 Word X6;
272 Word X7;
273 Word X8;
274 Word X9;
275 Word X10_A0;
276 Word X11;
277 Word X12;
278 Word X13;
279 Word X14;
280 Word X15;
281#if (__riscv_32e != 1)
282 Word X16;
283 Word X17;
284 Word X18;
285 Word X19;
286 Word X20;
287 Word X21;
288 Word X22;
289 Word X23;
290 Word X24;
291 Word X25;
292 Word X26;
293 Word X27;
294 Word X28;
295 Word X29;
296 Word X30;
297 Word X31;
298#endif
299#if (STK_RISCV_FPU != 0)
300 // FP register slots - at FOFFSET from frame base, immediately after integer slots.
301 // FREGBYTES may differ from REGBYTES (32-bit FP on a 64-bit integer machine).
302 // Declared as Word arrays for uniform struct sizing; the FP load/store
303 // instructions address them by byte offset and tolerate the type mismatch.
304 Word F[32];
305#endif
306};
307
310static __stk_forceinline void __DSB()
311{
312 __asm volatile("fence rw, rw" ::: "memory");
313}
314
317static __stk_forceinline void __ISB()
318{
319#ifdef __riscv_zifencei
320 __asm volatile("fence.i" ::: "memory");
321#else
322 __sync_synchronize();
323#endif
324}
325
328static __stk_forceinline void __WFI()
329{
330 __asm volatile("wfi");
331}
332
336static __stk_forceinline void HW_StartScheduler()
337{
338 __asm volatile("ecall");
339}
340
343static __stk_forceinline uint8_t HW_GetHartId()
344{
345 return STK_ARCH_GET_CPU_ID();
346}
347
350static __stk_forceinline void HW_DisableInterrupts()
351{
352 __asm volatile("csrrci zero, mstatus, %0"
353 : /* output: none */
354 : "i"(MSTATUS_MIE)
355 : /* clobbers: none */);
356}
357
360static __stk_forceinline void HW_EnableInterrupts()
361{
362 __asm volatile("csrrsi zero, mstatus, %0"
363 : /* output: none */
364 : "i"(MSTATUS_MIE)
365 : /* clobbers: none */);
366}
367
371static __stk_forceinline Word HW_EnterCriticalSection()
372{
373 Word ses;
374 __asm volatile("csrrci %0, mstatus, %1"
375 : "=r"(ses)
376 : "i"(MSTATUS_MIE)
377 : /* clobbers: none */);
378
379 return ses;
380}
381
385static __stk_forceinline void HW_ExitCriticalSection(Word ses)
386{
387 __asm volatile("csrrs zero, mstatus, %0"
388 : /* output: none */
389 : "r"(ses)
390 : /* clobbers: none */);
391}
392
395static __stk_forceinline void HW_EnterSleepMode()
396{
397 __DSB(); // data barrier
398 __WFI(); // enter standby mode until time slot expires
399}
400
403static __stk_forceinline void HW_StopMTimer()
404{
405 clear_csr(mie, MIP_MTIP);
406}
407
410static __stk_forceinline void HW_ClearPendingSwitch()
411{
412#ifdef _STK_RISCV_USE_PENDSV
413 clear_csr(mie, MIP_MSIP);
414#endif
415}
416
420static __stk_forceinline uint32_t HW_CoreClockFrequency()
421{
422 return SystemCoreClock; // CPU speed, e.g. 125/150 MHz
423}
424
429static __stk_forceinline uint32_t HW_MtimeClockFrequency()
430{
431 return STK_TIMER_CLOCK_FREQUENCY; // Timer frequency, e.g. 1 MHz
432}
433
439static __stk_forceinline uint64_t HW_GetMtime()
440{
441#if ( __riscv_xlen > 32)
442 return *(hw::WordToPtr<volatile uint64_t>(STK_RISCV_CLINT_MTIME_ADDR));
443#else
444 const Word mtime_base = STK_RISCV_CLINT_MTIME_ADDR;
445 const volatile uint32_t *const mtime_lo = hw::WordToPtr<volatile uint32_t>(mtime_base);
446 const volatile uint32_t *const mtime_hi = hw::WordToPtr<volatile uint32_t>(mtime_base + sizeof(uint32_t));
447
448 uint32_t hi, lo;
449 do
450 {
451 hi = (*mtime_hi);
452 lo = (*mtime_lo);
453 }
454 while (hi != (*mtime_hi)); // make sure mtime_hi did not tick when read mtime_lo
455
456 return (static_cast<uint64_t>(hi) << 32) | lo;
457#endif
458}
459
463static __stk_forceinline void HW_SetMtimecmp(uint64_t time_next)
464{
465#if STK_RISCV_CLINT_MTIMECMP_PER_HART
466 const uint8_t hart = HW_GetHartId();
467#else
468 const uint8_t hart = 0U;
469#endif
470
471#if (__riscv_xlen == 64)
472 hw::WordToPtr<volatile uint64_t>(STK_RISCV_CLINT_MTIMECMP_ADDR)[hart] = next;
473#else
474 const Word mtimecmp_base = STK_RISCV_CLINT_MTIMECMP_ADDR + (hart * sizeof(uint64_t));
475 volatile uint32_t *mtimecmp_lo = hw::WordToPtr<volatile uint32_t>(mtimecmp_base);
476 volatile uint32_t *mtimecmp_hi = hw::WordToPtr<volatile uint32_t>(mtimecmp_base + sizeof(uint32_t));
477
478 // expecting 4-byte aligned memory
479 STK_ASSERT(((uintptr_t)mtimecmp_lo & (4U - 1U)) == 0U);
480 STK_ASSERT(((uintptr_t)mtimecmp_hi & (4U - 1U)) == 0U);
481
482 // prevent unexpected interrupt by setting some very large value to the high part
483 // details: https://riscv.org/wp-content/uploads/2017/05/riscv-privileged-v1.10.pdf, page 31
484 (*mtimecmp_hi) = ~0U;
485
486 (*mtimecmp_lo) = (uint32_t)(time_next & 0xFFFFFFFFU);
487 (*mtimecmp_hi) = (uint32_t)(time_next >> 32);
488#endif
489}
490
496static __stk_forceinline uint64_t HW_GetMtimeElapsed(uint64_t since)
497{
498 return HW_GetMtime() - since;
499}
500
503static __stk_forceinline void HW_EnableCycleCounter()
504{
505 __asm volatile("csrci mcountinhibit, 0x1");
506}
507
511static __stk_forceinline Cycles HW_GetCycleCounter()
512{
513 uint32_t high, low, check;
514 do
515 {
516 __asm volatile("csrr %0, mcycleh" : "=r"(high));
517 __asm volatile("csrr %0, mcycle" : "=r"(low));
518 __asm volatile("csrr %0, mcycleh" : "=r"(check));
519 }
520 while (high != check);
521
522 return (static_cast<Cycles>(high) << 32) | low;
523}
524
527static __stk_forceinline Word HW_GetCallerSP()
528{
529 Word sp;
530 __asm volatile("mv %0, sp"
531 : "=r"(sp)
532 : /* input: none */
533 : /* clobbers: none */);
534
535 return sp;
536}
537
540static __stk_forceinline void HW_CriticalSectionStart(Word &ses)
541{
542 ses = HW_EnterCriticalSection();
543
544 // ensure the disable is recognized before subsequent code
545 __DSB();
546 __ISB();
547}
548
551static __stk_forceinline void HW_CriticalSectionEnd(Word ses)
552{
553 // ensure all memory work is finished before re-enabling
554 __DSB();
555
556 HW_ExitCriticalSection(ses);
557
558 // synchronization point: any pending interrupt can be serviced immediately at this boundary
559 __ISB();
560}
561
576static __stk_forceinline bool HW_SpinLockTryLock(volatile bool &lock)
577{
578 return !__atomic_test_and_set(&lock, __ATOMIC_ACQUIRE);
579}
580
597static __stk_forceinline void HW_SpinLockLock(volatile bool &lock)
598{
599 uint32_t timeout = 0xFFFFFFU;
600 while (!HW_SpinLockTryLock(lock))
601 {
602 if (--timeout == 0U)
603 {
604 // Invariant violated: the lock owner exited without releasing,
605 // Kernel state is suspect, enter defined safe state.
607 }
608 __stk_relax_cpu();
609 }
610}
611
627static __stk_forceinline void HW_SpinLockUnlock(volatile bool &lock)
628{
629 if (!lock)
630 STK_KERNEL_PANIC(KERNEL_PANIC_SPINLOCK_DEADLOCK); // release attempt of unowned lock
631
632 // ensure all data writes (like scheduling metadata) are flushed before the lock is released:
633 // __atomic_clear with __ATOMIC_RELEASE provides the required store-release barrier,
634 // the explicit fence rw,w is retained for toolchains that do not lower __ATOMIC_RELEASE
635 // to a full release fence on all RISC-V targets
636 __asm volatile("fence rw, w" ::: "memory");
637
638 __atomic_clear(&lock, __ATOMIC_RELEASE);
639}
640
643static __stk_forceinline void HW_ScheduleContextSwitch(uint8_t hart)
644{
645#ifdef _STK_RISCV_USE_PENDSV
646 // Pend Machine Software Interrupt (MSI) - equivalent of ARM's PENDSVSET
647 volatile uint32_t *msip = (volatile uint32_t *)(STK_RISCV_CLINT_BASE_ADDR);
648 msip[hart] = 1U; // set pending
649 __DSB();
650#else
651 (void)hart;
652#endif
653}
654
656#ifndef _STK_SYSTEM_CORE_CLOCK_EXTERNAL
658#endif
659
662static volatile bool s_StkRiscvCsuLock = false;
663
671
675#ifdef _STK_RISCV_USE_PENDSV
676Stack *volatile s_StkRiscvStackIdle[STK_ARCH_CPU_COUNT] = {};
677
683volatile Word s_StkRiscvSpIsrInt[STK_ARCH_CPU_COUNT] = {};
684#endif
685
689Stack *volatile s_StkRiscvStackActive[STK_ARCH_CPU_COUNT] = {};
690
694Stack *volatile s_StkRiscvStackIsr[STK_ARCH_CPU_COUNT] = {};
695
697
717
727struct JmpFrame
728{
729 Word RA;
730 Word SP;
731 Word S0;
732 Word S1;
733 Word S2;
734 Word S3;
735 Word S4;
736 Word S5;
737 Word S6;
738 Word S7;
739 Word S8;
740 Word S9;
741 Word S10;
742 Word S11;
743#if (STK_RISCV_FPU != 0)
744 Word FCSR;
745#endif
746};
747
762__attribute__((naked))
763int32_t SaveJmp(JmpFrame &/*f*/)
764{
765 __asm volatile(
766 // a0 = &f - no prologue has touched sp or s0 yet
767 SREG " ra, 0*" REGBYTES "(a0) \n" // save return address
768 SREG " sp, 1*" REGBYTES "(a0) \n" // save caller's stack pointer
769 SREG " s0, 2*" REGBYTES "(a0) \n"
770 SREG " s1, 3*" REGBYTES "(a0) \n"
771 SREG " s2, 4*" REGBYTES "(a0) \n"
772 SREG " s3, 5*" REGBYTES "(a0) \n"
773 SREG " s4, 6*" REGBYTES "(a0) \n"
774 SREG " s5, 7*" REGBYTES "(a0) \n"
775 SREG " s6, 8*" REGBYTES "(a0) \n"
776 SREG " s7, 9*" REGBYTES "(a0) \n"
777 SREG " s8, 10*" REGBYTES "(a0) \n"
778 SREG " s9, 11*" REGBYTES "(a0) \n"
779 SREG " s10, 12*" REGBYTES "(a0) \n"
780 SREG " s11, 13*" REGBYTES "(a0) \n"
781#if (STK_RISCV_FPU != 0)
782 "frcsr t0 \n" // read fcsr (rounding mode + flags)
783 SREG " t0, 14*" REGBYTES "(a0) \n" // save to JmpFrame::FCSR
784#endif
785 "li a0, 0 \n" // return 0
786 "ret \n" // explicit return (naked)
787 );
788}
789
807__attribute__((naked, noreturn))
808void RestoreJmp(JmpFrame &/*f*/, int32_t /*val*/)
809{
810 __asm volatile(
811 // a0 = &f, a1 = val
812 LREG " ra, 0*" REGBYTES "(a0) \n"
813 LREG " sp, 1*" REGBYTES "(a0) \n"
814 LREG " s0, 2*" REGBYTES "(a0) \n"
815 LREG " s1, 3*" REGBYTES "(a0) \n"
816 LREG " s2, 4*" REGBYTES "(a0) \n"
817 LREG " s3, 5*" REGBYTES "(a0) \n"
818 LREG " s4, 6*" REGBYTES "(a0) \n"
819 LREG " s5, 7*" REGBYTES "(a0) \n"
820 LREG " s6, 8*" REGBYTES "(a0) \n"
821 LREG " s7, 9*" REGBYTES "(a0) \n"
822 LREG " s8, 10*" REGBYTES "(a0) \n"
823 LREG " s9, 11*" REGBYTES "(a0) \n"
824 LREG " s10, 12*" REGBYTES "(a0) \n"
825 LREG " s11, 13*" REGBYTES "(a0) \n"
826#if (STK_RISCV_FPU != 0)
827 LREG " t0, 14*" REGBYTES "(a0) \n" // load saved fcsr into t0
828 "fscsr t0 \n" // restore rounding mode + flags
829#endif
830 "mv a0, a1 \n" // return val to SaveJmp's caller
831 "ret \n" // jump to saved RA
832 );
833}
834
836
838#if STK_SUBMICORSECOND_PRECISION_TIMER
839class HiResClockCYCLE
840{
841public:
842 static HiResClockCYCLE *GetInstance()
843 {
844 // keep declaration function-local to allow compiler stripping it from the binary if
845 // it is unused by the user code
846 static HiResClockCYCLE clock;
847 return &clock;
848 }
849
850 Cycles GetCycles()
851 {
852 return HW_GetCycleCounter();
853 }
854
855 uint32_t GetFrequency()
856 {
857 return HW_CoreClockFrequency();
858 }
859};
860typedef HiResClockCYCLE HiResClockImpl;
861#else
862class HiResClockMTIME
863{
864public:
865 static HiResClockMTIME *GetInstance()
866 {
867 // keep declaration function-local to allow compiler stripping it from the binary if
868 // it is unused by the user code
869 static HiResClockMTIME clock;
870 return &clock;
871 }
872
873 Cycles GetCycles()
874 {
875 return HW_GetMtime();
876 }
877
878 uint32_t GetFrequency()
879 {
880 return HW_MtimeClockFrequency();
881 }
882};
883typedef HiResClockMTIME HiResClockImpl;
884#endif // !STK_SUBMICORSECOND_PRECISION_TIMER
885
887static struct Context final : public PlatformContext
888{
889 explicit Context() : PlatformContext(), m_stack_main(), m_stack_isr(), m_stack_isr_mem(),
890 m_exit_buf(), m_overrider(nullptr), m_specific(nullptr), m_tick_period(0), m_last_mtime(0ULL),
891 #if STK_TICKLESS_IDLE
892 m_sleep_ticks(0),
893 #endif
894 m_csu(0), m_csu_nesting(0),
895 m_starting(false), m_started(false), m_exiting(false)
896
897 {}
898
902 STK_VIRT_DTOR ~Context() = default;
903
904 void Initialize(IPlatform::IEventHandler *handler, IKernelService *service, Stack *exit_trap,
905 uint32_t resolution_us) override
906 {
907 PlatformContext::Initialize(handler, service, exit_trap, resolution_us);
908
909 // init ISR's stack
910 {
911 StackMemoryWrapper<STK_RISCV_ISR_STACK_SIZE> stack_isr_mem(&m_stack_isr_mem);
912 m_stack_isr.SP = InitStackMemory(&stack_isr_mem);
913 m_stack_isr.access_mode = ACCESS_PRIVILEGED;
914 }
915
916 // init Main stack
917 {
918 m_stack_main.SP = STK_STACK_MEMORY_FILLER;
919 m_stack_main.access_mode = ACCESS_PRIVILEGED;
920 }
921
922 m_csu = 0U;
923 m_csu_nesting = 0U;
924 m_tick_period = ConvertTimeUsToClockCycles(STK_TIMER_CLOCK_FREQUENCY, resolution_us);
925 m_last_mtime = 0ULL;
926 m_starting = false;
927 m_started = false;
928 m_exiting = false;
929
930 // mcycle counter must be enabled per-core
931 #if STK_SUBMICORSECOND_PRECISION_TIMER
932 HW_EnableCycleCounter();
933 #endif
934 }
935
936 __stk_forceinline void ProcessTick()
937 {
938 // process tick - scheduler may update m_stack_active to point at a new task
939 Word cs;
940 HW_CriticalSectionStart(cs);
941
942 #if STK_TICKLESS_IDLE
943 Timeout ticks = m_sleep_ticks;
944 #endif
945
946 if (m_handler->OnTick(m_stack_idle, m_stack_active
947 #if STK_TICKLESS_IDLE
948 , ticks
949 #endif
950 ))
951 {
952 // refresh ISR asm pointer cache so the naked ISR reads the correct
953 // (possibly new) active stack SP immediately when jal returns
954 // s_StkRiscvStackActive[hart] always points to Context::m_stack_active, the pointer
955 // itself is stable, but we reassign here so multi-core hart-indexed builds
956 // stay correct if the hart mapping ever changes in future,
957 // for single-core builds this is a simple store to a known address at index 0
958 const uint8_t hart = HW_GetHartId();
959 s_StkRiscvStackActive[hart] = m_stack_active;
960 #ifdef _STK_RISCV_USE_PENDSV
961 s_StkRiscvStackIdle[hart] = m_stack_idle;
962 #endif
963
964 HW_ScheduleContextSwitch(hart);
965 }
966
967 #if STK_TICKLESS_IDLE
968 m_sleep_ticks = ticks;
969 #endif
970
971 HW_CriticalSectionEnd(cs);
972 }
973
974 __stk_forceinline void EnterCriticalSection()
975 {
976 // disable local interrupts and save state
977 Word current_ses;
978 HW_CriticalSectionStart(current_ses);
979
980 if (m_csu_nesting == 0U)
981 {
982 // ONLY attempt the global spinlock if we aren't already nested
983 HW_SpinLockLock(s_StkRiscvCsuLock);
984
985 // store the hardware interrupt state to restore later
986 m_csu = current_ses;
987 }
988
989 // increase nesting count within a limit
990 if (++m_csu_nesting > STK_CRITICAL_SECTION_NESTINGS_MAX)
991 {
992 // invariant violated: exceeded max allowed number of recursions
993 STK_KERNEL_PANIC(KERNEL_PANIC_CS_NESTING_OVERFLOW);
994 }
995 }
996
997 __stk_forceinline void ExitCriticalSection()
998 {
999 STK_ASSERT(m_csu_nesting != 0U);
1000 --m_csu_nesting;
1001
1002 if (m_csu_nesting == 0U)
1003 {
1004 // capture the state before releasing lock
1005 const Word ses_to_restore = m_csu;
1006
1007 // release global lock
1008 HW_SpinLockUnlock(s_StkRiscvCsuLock);
1009
1010 // restore hardware interrupts
1011 HW_CriticalSectionEnd(ses_to_restore);
1012 }
1013 }
1014
1015 uint64_t GetSleepTicksPrev()
1016 {
1017 #if STK_TICKLESS_IDLE
1018 const uint64_t ticks = (static_cast<uint64_t>(m_sleep_ticks) * static_cast<uint64_t>(m_tick_period));
1019 #else
1020 const uint64_t ticks = (1U * static_cast<uint64_t>(m_tick_period));
1021 #endif
1022 return ticks;
1023 }
1024
1025 uint64_t GetTimeNow(uint64_t &error)
1026 {
1027 const uint64_t mtime_now = HW_GetMtime();
1028 error = (mtime_now - m_last_mtime) - GetSleepTicksPrev();
1029 return mtime_now;
1030 }
1031
1032 void RearmTimer(const uint64_t mtime_now, const uint64_t error)
1033 {
1034 #if STK_TICKLESS_IDLE
1035 // guard against overflow (theoretical at normal tick periods and CPU frequencies)
1036 STK_ASSERT((static_cast<uint64_t>(m_sleep_ticks) * static_cast<uint64_t>(m_tick_period)) <= (UINT64_MAX - mtime_now));
1037 const uint64_t next_time = (static_cast<uint64_t>(m_sleep_ticks) * static_cast<uint64_t>(m_tick_period));
1038 #else
1039 const uint64_t next_time = (1U * static_cast<uint64_t>(m_tick_period));
1040 #endif
1041 HW_SetMtimecmp(mtime_now + next_time - error);
1042 m_last_mtime = mtime_now;
1043 }
1044
1045 __stk_forceinline void OnSwitchContext()
1046 {
1047 // capture mtime at ISR entry as the absolute base for the next period;
1048 // this eliminates drift from time spent inside OnTick regardless of how
1049 // long the scheduler takes to run
1050 uint64_t error = 0U;
1051 const uint64_t mtime_now = GetTimeNow(error);
1052 __stk_compiler_barrier(); // avoid compiler reordering, we count ticks from this point
1053
1054 // make sure timer is enabled by the Kernel::Start(), disable its start anywhere else
1055 STK_ASSERT(m_started);
1056 STK_ASSERT(m_handler != nullptr);
1057
1058 // process tick - scheduler may update m_stack_active and m_sleep_ticks
1059 ProcessTick();
1060
1061 // rearm timer: use the ISR-entry mtime snapshot as the absolute base so
1062 // any CPU cycles consumed by OnTick do not accumulate as period drift
1063 RearmTimer(mtime_now, error);
1064 }
1065
1066 void StartTickTimer(Timeout elapsed_ticks)
1067 {
1068 #if STK_TICKLESS_IDLE
1069 // reset sleep ticks if kernel was restarted
1070 m_sleep_ticks = elapsed_ticks;
1071 #else
1072 STK_UNUSED(elapsed_ticks);
1073 #endif
1074
1075 // start timer with default periodicity
1076 m_last_mtime = HW_GetMtime();
1077 HW_SetMtimecmp(m_last_mtime + m_tick_period);
1078
1079 // enable timer interrupt
1080 set_csr(mie, MIP_MTIP);
1081 }
1082
1083 void OnSleepOverride()
1084 {
1085 #if STK_TICKLESS_IDLE
1086 const Timeout sleep_ticks = m_sleep_ticks;
1087 #else
1088 const Timeout sleep_ticks = 1;
1089 #endif
1090
1091 if (!m_overrider->OnSleep(sleep_ticks))
1092 {
1093 HW_EnterSleepMode();
1094 }
1095 }
1096
1097 void Start();
1098 void OnStart();
1099 void OnStop();
1100#if STK_TICKLESS_IDLE
1101 Timeout Suspend();
1102 void Resume(Timeout elapsed_ticks);
1103#endif
1104
1105 typedef IPlatform::IEventOverrider eovrd_t;
1106 typedef PlatformRiscV::ISpecificEventHandler sehndl_t;
1107 typedef StackMemoryWrapper<STK_RISCV_ISR_STACK_SIZE>::MemoryType isrmem_t;
1108
1109 Stack m_stack_main;
1110 Stack m_stack_isr;
1111 isrmem_t m_stack_isr_mem;
1112 JmpFrame m_exit_buf;
1113 eovrd_t *m_overrider;
1114 sehndl_t *m_specific;
1115 uint32_t m_tick_period;
1116 uint64_t m_last_mtime;
1117#if STK_TICKLESS_IDLE
1118 Timeout m_sleep_ticks;
1119#endif
1120 Word m_csu;
1121 uint8_t m_csu_nesting;
1122 bool m_starting;
1123 bool m_started;
1124 volatile bool m_exiting;
1125}
1126s_StkPlatformContext[STK_ARCH_CPU_COUNT];
1127
1129{
1130#ifdef _STK_RISCV_USE_PENDSV
1131 Word cs;
1132 HW_CriticalSectionStart(cs);
1133
1134 GetContext().ProcessTick();
1135
1136 HW_CriticalSectionEnd(cs);
1137#else
1138 // unsupported scenario
1139 STK_ASSERT(false);
1140#endif
1141}
1142
1144static volatile EKernelPanicId g_LastPanicId = KERNEL_PANIC_NONE;
1145
1146__stk_attr_noinline // keep out of inlining to preserve stack frame
1147__stk_attr_noreturn // never returns - a trap
1149{
1150 g_LastPanicId = id;
1151
1152 // disable all maskable interrupts: this prevents scheduler from running again and corrupting state further
1153 HW_DisableInterrupts();
1154
1155 // spin forever: with a watchdog active this produces a clean reset, without a watchdog,
1156 // a debugger can attach and inspect 'id'
1157 for (;;)
1158 {
1159 __stk_relax_cpu();
1160 }
1161}
1162
1163#define STK_ASM_SAVE_CONTEXT_BASE\
1164 SREG " x1, 2*" REGBYTES "(sp) \n"\
1165 /*SREG " x2, 3*" REGBYTES "(sp) \n" // skip saving sp, Stack pointer */\
1166 /*SREG " x3, 4*" REGBYTES "(sp) \n" // skip saving gp, Global pointer (note: slot is used by fscsr) */\
1167 SREG " x4, 5*" REGBYTES "(sp) \n"\
1168 SREG " x5, 6*" REGBYTES "(sp) \n"\
1169 SREG " x6, 7*" REGBYTES "(sp) \n"\
1170 SREG " x7, 8*" REGBYTES "(sp) \n"\
1171 SREG " x8, 9*" REGBYTES "(sp) \n"\
1172 SREG " x9, 10*" REGBYTES "(sp) \n"\
1173 SREG " x10, 11*" REGBYTES "(sp) \n"\
1174 SREG " x11, 12*" REGBYTES "(sp) \n"\
1175 SREG " x12, 13*" REGBYTES "(sp) \n"\
1176 SREG " x13, 14*" REGBYTES "(sp) \n"\
1177 SREG " x14, 15*" REGBYTES "(sp) \n"\
1178 SREG " x15, 16*" REGBYTES "(sp) \n"
1179
1180#if (__riscv_32e != 1)
1181#define STK_ASM_SAVE_CONTEXT_RV32I_EXT\
1182 SREG " x16, 17*" REGBYTES "(sp) \n"\
1183 SREG " x17, 18*" REGBYTES "(sp) \n"\
1184 SREG " x18, 19*" REGBYTES "(sp) \n"\
1185 SREG " x19, 20*" REGBYTES "(sp) \n"\
1186 SREG " x20, 21*" REGBYTES "(sp) \n"\
1187 SREG " x21, 22*" REGBYTES "(sp) \n"\
1188 SREG " x22, 23*" REGBYTES "(sp) \n"\
1189 SREG " x23, 24*" REGBYTES "(sp) \n"\
1190 SREG " x24, 25*" REGBYTES "(sp) \n"\
1191 SREG " x25, 26*" REGBYTES "(sp) \n"\
1192 SREG " x26, 27*" REGBYTES "(sp) \n"\
1193 SREG " x27, 28*" REGBYTES "(sp) \n"\
1194 SREG " x28, 29*" REGBYTES "(sp) \n"\
1195 SREG " x29, 30*" REGBYTES "(sp) \n"\
1196 SREG " x30, 31*" REGBYTES "(sp) \n"\
1197 SREG " x31, 32*" REGBYTES "(sp) \n"
1198#else
1199#define STK_ASM_SAVE_CONTEXT_RV32I_EXT
1200#endif
1201
1202#if (STK_RISCV_FPU != 0)
1203#define STK_ASM_SAVE_CONTEXT_FP\
1204 FSREG " f0, " FOFFSET "+0*" FREGBYTES "(sp) \n"\
1205 FSREG " f1, " FOFFSET "+1*" FREGBYTES "(sp) \n"\
1206 FSREG " f2, " FOFFSET "+2*" FREGBYTES "(sp) \n"\
1207 FSREG " f3, " FOFFSET "+3*" FREGBYTES "(sp) \n"\
1208 FSREG " f4, " FOFFSET "+4*" FREGBYTES "(sp) \n"\
1209 FSREG " f5, " FOFFSET "+5*" FREGBYTES "(sp) \n"\
1210 FSREG " f6, " FOFFSET "+6*" FREGBYTES "(sp) \n"\
1211 FSREG " f7, " FOFFSET "+7*" FREGBYTES "(sp) \n"\
1212 FSREG " f8, " FOFFSET "+8*" FREGBYTES "(sp) \n"\
1213 FSREG " f9, " FOFFSET "+9*" FREGBYTES "(sp) \n"\
1214 FSREG " f10, " FOFFSET "+10*" FREGBYTES "(sp) \n"\
1215 FSREG " f11, " FOFFSET "+11*" FREGBYTES "(sp) \n"\
1216 FSREG " f12, " FOFFSET "+12*" FREGBYTES "(sp) \n"\
1217 FSREG " f13, " FOFFSET "+13*" FREGBYTES "(sp) \n"\
1218 FSREG " f14, " FOFFSET "+14*" FREGBYTES "(sp) \n"\
1219 FSREG " f15, " FOFFSET "+15*" FREGBYTES "(sp) \n"\
1220 FSREG " f16, " FOFFSET "+16*" FREGBYTES "(sp) \n"\
1221 FSREG " f17, " FOFFSET "+17*" FREGBYTES "(sp) \n"\
1222 FSREG " f18, " FOFFSET "+18*" FREGBYTES "(sp) \n"\
1223 FSREG " f19, " FOFFSET "+19*" FREGBYTES "(sp) \n"\
1224 FSREG " f20, " FOFFSET "+20*" FREGBYTES "(sp) \n"\
1225 FSREG " f21, " FOFFSET "+21*" FREGBYTES "(sp) \n"\
1226 FSREG " f22, " FOFFSET "+22*" FREGBYTES "(sp) \n"\
1227 FSREG " f23, " FOFFSET "+23*" FREGBYTES "(sp) \n"\
1228 FSREG " f24, " FOFFSET "+24*" FREGBYTES "(sp) \n"\
1229 FSREG " f25, " FOFFSET "+25*" FREGBYTES "(sp) \n"\
1230 FSREG " f26, " FOFFSET "+26*" FREGBYTES "(sp) \n"\
1231 FSREG " f27, " FOFFSET "+27*" FREGBYTES "(sp) \n"\
1232 FSREG " f28, " FOFFSET "+28*" FREGBYTES "(sp) \n"\
1233 FSREG " f29, " FOFFSET "+29*" FREGBYTES "(sp) \n"\
1234 FSREG " f30, " FOFFSET "+30*" FREGBYTES "(sp) \n"\
1235 FSREG " f31, " FOFFSET "+31*" FREGBYTES "(sp) \n"
1236#else
1237#define STK_ASM_SAVE_CONTEXT_FP
1238#endif
1239
1240#define STK_ASM_SAVE_CONTEXT_PC_STATUS\
1241 "csrr t0, mepc \n"\
1242 "csrr t1, mstatus \n"\
1243 SREG " t0, 0*" REGBYTES "(sp) \n"\
1244 SREG " t1, 1*" REGBYTES "(sp) \n"
1245
1246#if (STK_RISCV_FPU != 0)
1247#define STK_ASM_SAVE_CONTEXT_FRCSR\
1248 "frcsr t0 \n"\
1249 SREG " t0, 4*" REGBYTES "(sp) \n" /* use stack memory slot of gp (see comment for x3 above) */
1250#else
1251#define STK_ASM_SAVE_CONTEXT_FRCSR
1252#endif
1253
1254#define STK_ASM_SAVE_CONTEXT\
1255 "addi sp, sp, -" REGSIZE " \n" /* allocate stack memory for registers */\
1256 STK_ASM_SAVE_CONTEXT_BASE\
1257 STK_ASM_SAVE_CONTEXT_RV32I_EXT\
1258 STK_ASM_SAVE_CONTEXT_FP\
1259 STK_ASM_SAVE_CONTEXT_PC_STATUS\
1260 STK_ASM_SAVE_CONTEXT_FRCSR
1261
1262#define STK_ASM_LOAD_CONTEXT_BASE\
1263 LREG " x1, 2*" REGBYTES "(sp) \n"\
1264 /*LREG " x2, 3*" REGBYTES "(sp) \n" skip loading sp, Stack pointer */\
1265 /*LREG " x3, 4*" REGBYTES "(sp) \n" skip loading gp, Global pointer (note: slot is used by fscsr) */\
1266 LREG " x4, 5*" REGBYTES "(sp) \n"\
1267 LREG " x5, 6*" REGBYTES "(sp) \n"\
1268 LREG " x6, 7*" REGBYTES "(sp) \n"\
1269 LREG " x7, 8*" REGBYTES "(sp) \n"\
1270 LREG " x8, 9*" REGBYTES "(sp) \n"\
1271 LREG " x9, 10*" REGBYTES "(sp) \n"\
1272 LREG " x10, 11*" REGBYTES "(sp) \n"\
1273 LREG " x11, 12*" REGBYTES "(sp) \n"\
1274 LREG " x12, 13*" REGBYTES "(sp) \n"\
1275 LREG " x13, 14*" REGBYTES "(sp) \n"\
1276 LREG " x14, 15*" REGBYTES "(sp) \n"\
1277 LREG " x15, 16*" REGBYTES "(sp) \n"
1278
1279#if (__riscv_32e != 1)
1280#define STK_ASM_LOAD_CONTEXT_RV32I_EXT\
1281 LREG " x16, 17*" REGBYTES "(sp) \n"\
1282 LREG " x17, 18*" REGBYTES "(sp) \n"\
1283 LREG " x18, 19*" REGBYTES "(sp) \n"\
1284 LREG " x19, 20*" REGBYTES "(sp) \n"\
1285 LREG " x20, 21*" REGBYTES "(sp) \n"\
1286 LREG " x21, 22*" REGBYTES "(sp) \n"\
1287 LREG " x22, 23*" REGBYTES "(sp) \n"\
1288 LREG " x23, 24*" REGBYTES "(sp) \n"\
1289 LREG " x24, 25*" REGBYTES "(sp) \n"\
1290 LREG " x25, 26*" REGBYTES "(sp) \n"\
1291 LREG " x26, 27*" REGBYTES "(sp) \n"\
1292 LREG " x27, 28*" REGBYTES "(sp) \n"\
1293 LREG " x28, 29*" REGBYTES "(sp) \n"\
1294 LREG " x29, 30*" REGBYTES "(sp) \n"\
1295 LREG " x30, 31*" REGBYTES "(sp) \n"\
1296 LREG " x31, 32*" REGBYTES "(sp) \n"
1297#else
1298#define STK_ASM_LOAD_CONTEXT_RV32I_EXT
1299#endif
1300
1301#if (STK_RISCV_FPU != 0)
1302#define STK_ASM_LOAD_CONTEXT_FP\
1303 FLREG " f0, " FOFFSET "+0*" FREGBYTES "(sp) \n"\
1304 FLREG " f1, " FOFFSET "+1*" FREGBYTES "(sp) \n"\
1305 FLREG " f2, " FOFFSET "+2*" FREGBYTES "(sp) \n"\
1306 FLREG " f3, " FOFFSET "+3*" FREGBYTES "(sp) \n"\
1307 FLREG " f4, " FOFFSET "+4*" FREGBYTES "(sp) \n"\
1308 FLREG " f5, " FOFFSET "+5*" FREGBYTES "(sp) \n"\
1309 FLREG " f6, " FOFFSET "+6*" FREGBYTES "(sp) \n"\
1310 FLREG " f7, " FOFFSET "+7*" FREGBYTES "(sp) \n"\
1311 FLREG " f8, " FOFFSET "+8*" FREGBYTES "(sp) \n"\
1312 FLREG " f9, " FOFFSET "+9*" FREGBYTES "(sp) \n"\
1313 FLREG " f10, " FOFFSET "+10*" FREGBYTES "(sp) \n"\
1314 FLREG " f11, " FOFFSET "+11*" FREGBYTES "(sp) \n"\
1315 FLREG " f12, " FOFFSET "+12*" FREGBYTES "(sp) \n"\
1316 FLREG " f13, " FOFFSET "+13*" FREGBYTES "(sp) \n"\
1317 FLREG " f14, " FOFFSET "+14*" FREGBYTES "(sp) \n"\
1318 FLREG " f15, " FOFFSET "+15*" FREGBYTES "(sp) \n"\
1319 FLREG " f16, " FOFFSET "+16*" FREGBYTES "(sp) \n"\
1320 FLREG " f17, " FOFFSET "+17*" FREGBYTES "(sp) \n"\
1321 FLREG " f18, " FOFFSET "+18*" FREGBYTES "(sp) \n"\
1322 FLREG " f19, " FOFFSET "+19*" FREGBYTES "(sp) \n"\
1323 FLREG " f20, " FOFFSET "+20*" FREGBYTES "(sp) \n"\
1324 FLREG " f21, " FOFFSET "+21*" FREGBYTES "(sp) \n"\
1325 FLREG " f22, " FOFFSET "+22*" FREGBYTES "(sp) \n"\
1326 FLREG " f23, " FOFFSET "+23*" FREGBYTES "(sp) \n"\
1327 FLREG " f24, " FOFFSET "+24*" FREGBYTES "(sp) \n"\
1328 FLREG " f25, " FOFFSET "+25*" FREGBYTES "(sp) \n"\
1329 FLREG " f26, " FOFFSET "+26*" FREGBYTES "(sp) \n"\
1330 FLREG " f27, " FOFFSET "+27*" FREGBYTES "(sp) \n"\
1331 FLREG " f28, " FOFFSET "+28*" FREGBYTES "(sp) \n"\
1332 FLREG " f29, " FOFFSET "+29*" FREGBYTES "(sp) \n"\
1333 FLREG " f30, " FOFFSET "+30*" FREGBYTES "(sp) \n"\
1334 FLREG " f31, " FOFFSET "+31*" FREGBYTES "(sp) \n"
1335#else
1336#define STK_ASM_LOAD_CONTEXT_FP
1337#endif
1338
1339#define STK_ASM_LOAD_CONTEXT_PC_STATUS\
1340 LREG " t0, 0*" REGBYTES "(sp) \n"\
1341 LREG " t1, 1*" REGBYTES "(sp) \n"\
1342 "csrw mepc, t0 \n"\
1343 "csrw mstatus, t1 \n"
1344
1345#if (STK_RISCV_FPU != 0)
1346#define STK_ASM_LOAD_CONTEXT_FRCSR\
1347 LREG " t0, 4*" REGBYTES "(sp) \n" /* use stack memory slot of gp (see comment for x3 below) */\
1348 "fscsr t0 \n"
1349#else
1350#define STK_ASM_LOAD_CONTEXT_FRCSR
1351#endif
1352
1353#define STK_ASM_LOAD_CONTEXT\
1354 STK_ASM_LOAD_CONTEXT_PC_STATUS\
1355 STK_ASM_LOAD_CONTEXT_FRCSR\
1356 STK_ASM_LOAD_CONTEXT_BASE\
1357 STK_ASM_LOAD_CONTEXT_RV32I_EXT\
1358 STK_ASM_LOAD_CONTEXT_FP\
1359 "addi sp, sp, " REGSIZE " \n" /* shrink stack memory of registers */
1360
1361static __stk_forceinline void HW_LoadContextAndExit()
1362{
1363 __asm volatile(
1364 LREG " t0, %0 \n" // load the first member (SP) into t0
1365 LREG " sp, 0(t0) \n" // sp = t0
1366
1367 STK_ASM_LOAD_CONTEXT
1368 STK_ASM_EXIT_FROM_HANDLER " \n"
1369
1370 : /* output: none */
1371 : "m"(GetContext().m_stack_active)
1372 : "t0", "t1", "a2", "a3", "a4", "a5", "gp", "memory");
1373}
1374
1375static __stk_forceinline void HW_EnableFullFpuAccess()
1376{
1377#if (STK_RISCV_FPU != 0)
1378 __asm volatile(
1379 "csrs mstatus, %0"
1380 : /* output: none */
1381 : "r"(MSTATUS_FS | MSTATUS_XS)
1382 : "memory" /* ensure no FP instructions are moved before this call */);
1383#endif
1384}
1385
1386static __stk_forceinline void HW_ClearFpuState()
1387{
1388#if (STK_RISCV_FPU != 0)
1389 __asm volatile(
1390 "fssr x0"
1391 : /* output: none */
1392 : /* input: none */
1393 : "memory" /* ensure flags are cleared before next FP op */);
1394#endif
1395}
1396
1397static __stk_forceinline void HW_SaveMainSP()
1398{
1399 __asm volatile(
1400 SREG " sp, %0"
1401 : "=m"(GetContext().m_stack_main)
1402 : /* input: none */
1403 : "memory" /* protect against compiler reordering */ );
1404}
1405
1406static __stk_forceinline void HW_LoadMainSP()
1407{
1408 __asm volatile(
1409 LREG " sp, %0"
1410 : /* output: none */
1411 : "m"(GetContext().m_stack_main)
1412 : "memory" /* protect against compiler reordering */ );
1413}
1414
1424static __stk_forceinline Word HW_GetCurrentException()
1425{
1426 Word mcause;
1427 __asm volatile("csrr %0, mcause" : "=r"(mcause));
1428 return mcause;
1429}
1430
1431static __stk_forceinline bool HW_IsHandlerMode()
1432{
1433 const Word current_sp = HW_GetCallerSP();
1434
1435 // get the bounds of the ISR stack from our Context
1436 // note: STK uses StackMemoryWrapper, so we check against that memory block
1437 const Word isr_stack_base = hw::PtrToWord(&GetContext().m_stack_isr_mem);
1438 const Word isr_stack_top = isr_stack_base + (STK_RISCV_ISR_STACK_SIZE * sizeof(Word));
1439
1440 return ((current_sp >= isr_stack_base) && (current_sp < isr_stack_top));
1441}
1442
1443static __stk_forceinline void OnTaskStart()
1444{
1445 HW_LoadContextAndExit();
1446}
1447
1448// __stk_attr_used for LTO
1449extern "C" STK_RISCV_ISR_SECTION __stk_attr_used void TrySwitchContext()
1450{
1451 GetContext().OnSwitchContext();
1452}
1453
1454#ifdef _STK_RISCV_USE_PENDSV
1455extern "C" STK_RISCV_ISR_SECTION __stk_attr_naked void STK_SYSTICK_HANDLER()
1456{
1457 __asm volatile(
1458 // 1. save full interrupted context onto the task stack
1459 STK_ASM_SAVE_CONTEXT
1460
1461 // 2. store task SP into s_StkRiscvSpIsrInt[hart] directly (plain Word, no struct indirection)
1462#if (STK_ARCH_CPU_COUNT > 1)
1463 "csrr t0, mhartid \n"
1464 "la t1, s_StkRiscvSpIsrInt \n"
1465 "slli t0, t0, " REGBYTES_LOG2 " \n" // t0 = hart * sizeof(Word)
1466 "add t1, t1, t0 \n" // t1 = &s_StkRiscvSpIsrInt[hart]
1467 SREG " sp, 0(t1) \n" // store sp directly - no pointer dereference
1468#else
1469 "la t1, s_StkRiscvSpIsrInt \n"
1470 SREG " sp, 0(t1) \n" // store sp directly - no pointer dereference
1471#endif
1472
1473 // 3. switch to private ISR stack
1474#if (STK_ARCH_CPU_COUNT > 1)
1475 "csrr t0, mhartid \n"
1476 "la t1, s_StkRiscvStackIsr \n"
1477 "slli t0, t0, " REGBYTES_LOG2 " \n"
1478 "add t1, t1, t0 \n"
1479 LREG " t1, 0(t1) \n"
1480#else
1481 "la t1, s_StkRiscvStackIsr \n"
1482 LREG " t1, 0(t1) \n"
1483#endif
1484 LREG " sp, 0(t1) \n" // sp = Stack::SP of ISR stack
1485
1486 // 4. run scheduler
1487 "jal ra, TrySwitchContext \n"
1488
1489 // 5. restore the interrupted task's SP from s_StkRiscvSpIsrInt[hart]
1490#if (STK_ARCH_CPU_COUNT > 1)
1491 "csrr t0, mhartid \n"
1492 "la t1, s_StkRiscvSpIsrInt \n"
1493 "slli t0, t0, " REGBYTES_LOG2 " \n"
1494 "add t1, t1, t0 \n"
1495 LREG " sp, 0(t1) \n" // sp = saved task SP - direct load, no struct
1496#else
1497 "la t1, s_StkRiscvSpIsrInt \n"
1498 LREG " sp, 0(t1) \n" // sp = saved task SP - direct load, no struct
1499#endif
1500
1501 // 6. restore context
1502 STK_ASM_LOAD_CONTEXT
1503
1504 // 7. exit ISR handler
1505 STK_ASM_EXIT_FROM_HANDLER " \n"
1506
1507 : /* outputs: none - naked, compiler emits nothing outside this asm */
1508 : /* inputs: all addresses loaded as linker symbols via "la" */
1509 : /* clobbers: none - the asm string owns all registers */);
1510}
1511extern "C" STK_RISCV_ISR_SECTION __stk_attr_naked void STK_MSI_HANDLER()
1512{
1513 __asm volatile(
1514 // 1. save context
1515 STK_ASM_SAVE_CONTEXT
1516
1517 // 2. store task SP into s_StkRiscvStackIdle[hart]->SP
1518 // all integer registers are now saved. t0/t1 are free to use as scratch.
1519 // "la" loads the address of the global array - a linker-time constant,
1520 // no compiler-generated runtime code, safe to use here
1521#if (STK_ARCH_CPU_COUNT > 1)
1522 "csrr t0, mhartid \n"
1523 "la t1, s_StkRiscvStackIdle \n"
1524 "slli t0, t0, " REGBYTES_LOG2 " \n" // t0 = hart * sizeof(Stack*)
1525 "add t1, t1, t0 \n" // t1 = &s_StkRiscvStackIdle[hart]
1526 LREG " t1, 0(t1) \n" // t1 = s_StkRiscvStackIdle[hart] (Stack*)
1527#else
1528 "la t1, s_StkRiscvStackIdle \n"
1529 LREG " t1, 0(t1) \n" // t1 = s_StkRiscvStackIdle[0] (Stack*)
1530#endif
1531 SREG " sp, 0(t1) \n" // Stack::SP = task's sp (SP is first member)
1532
1533 // 3. clear exception: MSIP[hart] = 0
1534#if (STK_ARCH_CPU_COUNT > 1)
1535 "csrr t0, mhartid \n"
1536 "slli t0, t0, 2 \n" // t0 = hart * 4
1537 "li t1, %[clint_msip_base] \n"
1538 "add t0, t0, t1 \n" // t0 = &MSIP[hart]
1539#else
1540 "li t0, %[clint_msip_base] \n" // t0 = &MSIP[0]
1541#endif
1542 "sw zero, 0(t0) \n" // MSIP[hart] = 0
1543 "fence rw, rw \n" // fence rw,rw - ensure the write is visible before re-enable
1544
1545 // 4. load SP from s_StkRiscvStackActive[hart]->SP
1546#if (STK_ARCH_CPU_COUNT > 1)
1547 "csrr t0, mhartid \n"
1548 "la t1, s_StkRiscvStackActive \n"
1549 "slli t0, t0, " REGBYTES_LOG2 " \n"
1550 "add t1, t1, t0 \n"
1551 LREG " t1, 0(t1) \n"
1552#else
1553 "la t1, s_StkRiscvStackActive \n"
1554 LREG " t1, 0(t1) \n"
1555#endif
1556 LREG " sp, 0(t1) \n" // sp = active task's saved SP
1557
1558 // 5. load context of the active task
1559 STK_ASM_LOAD_CONTEXT
1560
1561 // 6. exit ISR handler
1562 STK_ASM_EXIT_FROM_HANDLER " \n"
1563
1564 : /* outputs: none - naked, compiler emits nothing outside this asm */
1565 : [clint_msip_base] "i" (STK_RISCV_CLINT_BASE_ADDR) /* other inputs: all addresses loaded as linker symbols via "la" */
1566 : /* clobbers: none - the asm string owns all registers */);
1567}
1568#else // !_STK_RISCV_USE_PENDSV
1569/* STK_SYSTICK_HANDLER
1570
1571RISC-V machine-timer ISR: Saves the interrupted task's full context, switches
1572to the private ISR stack, calls TrySwitchContext (which reschedules the timer
1573and runs the scheduler), then restores the (possibly new) task's context.
1574
1575DESIGN RULES - must be obeyed to work correctly at all optimisation levels:
1576
1577 1. Single asm volatile, no compiler operands.
1578 The function body is ONE __asm volatile("..." : : : ) with empty
1579 input/output/clobber lists. No "m" or "r" constraints are used because
1580 the compiler evaluates those as C expressions BEFORE emitting any asm
1581 text, i.e. before the register save - trashing uninitialized registers.
1582
1583 2. All addresses are linker symbols loaded via "la" inside the asm.
1584 s_StkRiscvStackActive and s_StkRiscvStackIsr are plain file-scope globals. "la reg, sym"
1585 emits a PC-relative load that is resolved at link time, it produces no
1586 compiler-generated code outside the asm string.
1587
1588 3. Stack pointer indexing uses sizeof(Stack*) == REGBYTES.
1589 For multi-hart builds the array index is hart * REGBYTES, which is a
1590 single left-shift by log2(REGBYTES): 2 for RV32 (4 bytes), 3 for RV64
1591 (8 bytes). REGBYTES_LOG2 is defined below accordingly.
1592
1593 4. s_StkRiscvStackActive[hart]->SP is updated by TrySwitchContext.
1594 The naked asm reads it fresh after the jal returns, so it always sees
1595 the task the scheduler has chosen - even if it changed.
1596
1597 Stack frame layout (offsets from sp after "addi sp,-REGSIZE"):
1598 [0*REGBYTES] mepc (service slot 0)
1599 [1*REGBYTES] mstatus (service slot 1)
1600 [2*REGBYTES] x1 / ra
1601 [3*REGBYTES] x2 / sp - SKIPPED, managed explicitly
1602 [4*REGBYTES] x3 / gp - SKIPPED, fixed register; slot reused for FCSR
1603 [5*REGBYTES] x4 / tp
1604 [6*REGBYTES] x5 / t0
1605 ...
1606 [32*REGBYTES] x31 / t6 (RV32I; absent on RV32E)
1607 [FOFFSET + n*FREGBYTES] fn (FP registers, if STK_RISCV_FPU != 0)
1608*/
1609extern "C" STK_RISCV_ISR_SECTION __stk_attr_naked void STK_SYSTICK_HANDLER()
1610{
1611 __asm volatile(
1612 // 1. save context
1613 STK_ASM_SAVE_CONTEXT
1614
1615 // 2. store task SP into s_StkRiscvStackActive[hart]->SP
1616 // all integer registers are now saved. t0/t1 are free to use as scratch.
1617 // "la" loads the address of the global array - a linker-time constant,
1618 // no compiler-generated runtime code, safe to use here
1619#if (STK_ARCH_CPU_COUNT > 1)
1620 "csrr t0, mhartid \n"
1621 "la t1, s_StkRiscvStackActive \n"
1622 "slli t0, t0, " REGBYTES_LOG2 " \n" // t0 = hart * sizeof(Stack*)
1623 "add t1, t1, t0 \n" // t1 = &s_StkRiscvStackActive[hart]
1624 LREG " t1, 0(t1) \n" // t1 = s_StkRiscvStackActive[hart] (Stack*)
1625#else
1626 "la t1, s_StkRiscvStackActive \n"
1627 LREG " t1, 0(t1) \n" // t1 = s_StkRiscvStackActive[0] (Stack*)
1628#endif
1629 SREG " sp, 0(t1) \n" // Stack::SP = task's sp (SP is first member)
1630
1631 // 3. switch to private ISR stack
1632#if (STK_ARCH_CPU_COUNT > 1)
1633 "csrr t0, mhartid \n"
1634 "la t1, s_StkRiscvStackIsr \n"
1635 "slli t0, t0, " REGBYTES_LOG2 " \n"
1636 "add t1, t1, t0 \n"
1637 LREG " t1, 0(t1) \n" // t1 = s_StkRiscvStackIsr[hart] (Stack*)
1638#else
1639 "la t1, s_StkRiscvStackIsr \n"
1640 LREG " t1, 0(t1) \n" // t1 = s_StkRiscvStackIsr[0] (Stack*)
1641#endif
1642 LREG " sp, 0(t1) \n" // sp = Stack::SP of ISR stack
1643
1644 // 4. call TrySwitchContext
1645 // runs on the ISR stack: reschedules timer, runs scheduler
1646 // (which may update m_stack_active to a new task), then updates
1647 // s_StkRiscvStackActive[hart] so step 5 below reads the correct new SP,
1648 // all caller-saved registers (a0-a7, t0-t6, ra) are trashed - expected
1649 "jal ra, TrySwitchContext \n"
1650
1651 // 5. reload SP from s_StkRiscvStackActive[hart]->SP
1652 // TrySwitchContext updated s_StkRiscvStackActive[hart] before returning,
1653 // we re-read it fresh to pick up any task switch the scheduler made
1654#if (STK_ARCH_CPU_COUNT > 1)
1655 "csrr t0, mhartid \n"
1656 "la t1, s_StkRiscvStackActive \n"
1657 "slli t0, t0, " REGBYTES_LOG2 " \n"
1658 "add t1, t1, t0 \n"
1659 LREG " t1, 0(t1) \n"
1660#else
1661 "la t1, s_StkRiscvStackActive \n"
1662 LREG " t1, 0(t1) \n"
1663#endif
1664 LREG " sp, 0(t1) \n" // sp = active task's saved SP
1665
1666 // 6. load context of the active task
1667 STK_ASM_LOAD_CONTEXT
1668
1669 // 7. exit ISR handler
1670 STK_ASM_EXIT_FROM_HANDLER " \n"
1671
1672 : /* outputs: none - naked, compiler emits nothing outside this asm */
1673 : /* inputs: none - all addresses loaded as linker symbols via "la" */
1674 : /* clobbers: none - the asm string owns all registers */
1675 );
1676}
1677#endif // !_STK_RISCV_USE_PENDSV
1678
1679void Context::OnStart()
1680{
1681 const uint8_t hart = HW_GetHartId();
1682
1683 // save SP of main stack to reuse it for scheduler exit
1684 HW_SaveMainSP();
1685
1686 // enable FPU (if available)
1687 HW_EnableFullFpuAccess();
1688
1689 // clear FPU usage status if FPU was used before kernel start
1690 HW_ClearFpuState();
1691
1692 // notify kernel
1693 m_handler->OnStart(m_stack_active);
1694
1695 // initialize ISR asm pointer cache
1696 s_StkRiscvStackIsr[hart] = &m_stack_isr; // set once here, the ISR stack never moves
1697 s_StkRiscvStackActive[hart] = m_stack_active;
1698#ifdef _STK_RISCV_USE_PENDSV
1699 s_StkRiscvStackIdle[hart] = m_stack_idle;
1700#endif
1701
1702 // start with initially 1 elapsed tick (after timer expires)
1703 StartTickTimer(1);
1704
1705 // change state before enabling interrupts
1706 m_started = true;
1707 m_starting = false;
1708
1709 // enable SV exception
1710#ifdef _STK_RISCV_USE_PENDSV
1711 set_csr(mie, MIP_MSIP);
1712#endif
1713}
1714
1715STK_RISCV_ISR void STK_SVC_HANDLER()
1716{
1717 const Word cause = HW_GetCurrentException();
1718
1719 /*if (cause & (1UL << (__riscv_xlen - 1)))
1720 {
1721 cause &= ~(1UL << (__riscv_xlen - 1));
1722
1723 if (cause == IRQ_M_TIMER)
1724 {
1725
1726 }
1727 }*/
1728
1729 if (cause == IRQ_M_EXT)
1730 {
1731 // not starting scheduler, then try to forward ecall to user
1732 if (!GetContext().m_starting)
1733 {
1734 // forward event to user
1735 if (GetContext().m_specific != nullptr)
1736 GetContext().m_specific->OnException(cause);
1737
1738 // switch to the next instruction of the caller space (PC) after the return
1739 write_csr(mepc, read_csr(mepc) + sizeof(Word));
1740 }
1741 else
1742 {
1743 // make sure interrupts do not interfere
1744 HW_DisableInterrupts();
1745
1746 // configure scheduling
1747 GetContext().OnStart();
1748
1749 // start first task
1750 OnTaskStart();
1751 }
1752 }
1753 else
1754 {
1755 if (GetContext().m_specific != nullptr)
1756 {
1757 // forward event to user
1758 GetContext().m_specific->OnException(cause);
1759 }
1760 else
1761 {
1762 // trap further execution
1763 // note: normally, if trapped here with cause 2 or 4 then check stack memory size of the
1764 // tasks, scheduler and ISR, they were likely overwritten if your code is 100% correct
1766 }
1767 }
1768}
1769
1770static void OnTaskRun(ITask *task)
1771{
1772 task->Run();
1773}
1774
1775static void OnTaskExit()
1776{
1777 Word cs;
1778 HW_CriticalSectionStart(cs);
1779
1780 GetContext().m_handler->OnTaskExit(GetContext().m_stack_active);
1781
1782 HW_CriticalSectionEnd(cs);
1783
1784 for (;;)
1785 {
1786 __DSB(); // data barrier
1787 __WFI(); // enter standby mode until time slot expires
1788 }
1789}
1790
1791static STK_RISCV_ISR_SECTION void OnSchedulerSleep()
1792{
1793 // if hit here, increase the size of STK_SLEEP_TRAP_STACK_SIZE
1795
1796#if STK_SEGGER_SYSVIEW
1797 SEGGER_SYSVIEW_OnIdle();
1798#endif
1799
1800 for (;;)
1801 {
1802 HW_EnterSleepMode();
1803 }
1804}
1805
1806static STK_RISCV_ISR_SECTION void OnSchedulerSleepOverride()
1807{
1808 // if hit here, increase the size of STK_SLEEP_TRAP_STACK_SIZE
1810
1811#if STK_SEGGER_SYSVIEW
1812 SEGGER_SYSVIEW_OnIdle();
1813#endif
1814
1815 for (;;)
1816 {
1817 GetContext().OnSleepOverride();
1818 }
1819}
1820
1821static void OnSchedulerExit()
1822{
1823 // switch to main stack
1824 HW_LoadMainSP();
1825
1826 // jump to the exit from the IKernel::Start()
1827 RestoreJmp(GetContext().m_exit_buf, 0);
1828}
1829
1830void PlatformRiscV::Initialize(IEventHandler *event_handler, IKernelService *service, uint32_t resolution_us, Stack *exit_trap)
1831{
1832 GetContext().Initialize(event_handler, service, exit_trap, resolution_us);
1833}
1834
1835void Context::Start()
1836{
1837 m_exiting = false;
1838
1839 // save jump location of the Exit trap
1840 SaveJmp(m_exit_buf);
1841 if (m_exiting)
1842 {
1843 // notify kernel about a full stop
1844 m_handler->OnStop();
1845 }
1846 else
1847 {
1848 // enable FPU (if available)
1849 HW_EnableFullFpuAccess();
1850
1851 // start
1852 m_starting = true;
1853 HW_StartScheduler();
1854 }
1855}
1856
1857#if STK_TICKLESS_IDLE
1858Timeout Context::Suspend()
1859{
1860 const uint32_t resolution = static_cast<uint32_t>(ConvertTimeUsToClockCycles(HW_CoreClockFrequency(), m_tick_resolution));
1861 if (resolution == 0U)
1862 {
1863 STK_ASSERT(false);
1864 return NO_WAIT;
1865 }
1866
1867 HW_DisableInterrupts();
1868
1869 // stop tick timer
1870 HW_StopMTimer();
1871
1872 // clear pending PendSV exception
1873 HW_ClearPendingSwitch();
1874
1875 // get already elapsed CPU cycles since SysTick ISR invocation up to SysTick timer stop (see above)
1876 // to account for them for a new period value
1877 const uint32_t elapsed = HW_GetMtime() - m_last_mtime;
1878
1879 // get already elapsed ticks since the OnTick and a call to Suspend(), we shall account for this
1880 // period and return only the remainder
1881 const Timeout elapsed_ticks = static_cast<Timeout>(elapsed / resolution);
1882 const Timeout sleep_ticks = Max(m_sleep_ticks - elapsed_ticks, static_cast<Timeout>(0));
1883
1884 // notify core
1885 m_handler->OnSuspend(true);
1886
1887 HW_EnableInterrupts();
1888
1889 return sleep_ticks;
1890}
1891#endif
1892
1893#if STK_TICKLESS_IDLE
1894void Context::Resume(Timeout elapsed_ticks)
1895{
1896 HW_DisableInterrupts();
1897
1898 // notify core
1899 m_handler->OnSuspend(false);
1900
1901 // start with initially elapsed ticks (OnTick will fire with elapsed_ticks + 1)
1902 StartTickTimer(elapsed_ticks + 1);
1903
1904 HW_EnableInterrupts();
1905}
1906#endif
1907
1909{
1910 GetContext().Start();
1911}
1912
1913void PlatformRiscV::InitStack(EStackType stack_type, Stack *stack, IStackMemory *stack_memory, ITask *user_task)
1914{
1915 // TaskFrame must map exactly onto the slot layout consumed by STK_ASM_SAVE_CONTEXT / STK_ASM_LOAD_CONTEXT - no padding allowed
1916 STK_STATIC_ASSERT_DESC(sizeof(TaskFrame) == (STK_RISCV_REGISTER_COUNT + STK_SERVICE_SLOTS) * sizeof(Word),
1917 "TaskFrame size must match REGSIZE: (REGISTER_COUNT + SERVICE_SLOTS) * REGBYTES");
1918
1919 STK_ASSERT(stack_memory->GetStackSize() > (STK_RISCV_REGISTER_COUNT + STK_SERVICE_SLOTS));
1920
1921 // initialize stack memory (fills all slots with STK_STACK_MEMORY_FILLER)
1922 const Word stack_top = PlatformContext::InitStackMemory(stack_memory);
1923
1924 // initialize Stack Pointer (SP): frame sits at the bottom of the register window
1925 stack->SP = stack_top - ((STK_RISCV_REGISTER_COUNT + STK_SERVICE_SLOTS) * sizeof(Word));
1926
1927 // place the task frame at SP directly at the base of the register window
1928 TaskFrame *const task_frame = hw::WordToPtr<TaskFrame>(stack->SP);
1929
1930 // initialize registers for the user task's first start
1931 switch (stack_type)
1932 {
1933 case STACK_USER_TASK: {
1934 task_frame->MEPC = hw::PtrToWord(&OnTaskRun);
1935 task_frame->X1_RA = hw::PtrToWord(&OnTaskExit);
1936 task_frame->X10_A0 = hw::PtrToWord(user_task);
1937 break; }
1938
1939 case STACK_SLEEP_TRAP: {
1940 task_frame->MEPC = hw::PtrToWord(GetContext().m_overrider != nullptr ? &OnSchedulerSleepOverride : &OnSchedulerSleep);
1941 task_frame->X1_RA = STK_STACK_MEMORY_FILLER; // should not attempt to exit
1942 break; }
1943
1944 case STACK_EXIT_TRAP: {
1945 task_frame->MEPC = hw::PtrToWord(&OnSchedulerExit);
1946 task_frame->X1_RA = STK_STACK_MEMORY_FILLER; // should not attempt to exit
1947 break; }
1948
1949 default: {
1951 break; }
1952 }
1953
1954 // mstatus: return to M-mode (MPP), interrupts enabled on mret (MPIE),
1955 // FPU/extension state initial (FS/XS) if FPU present
1956 task_frame->MSTATUS = MSTATUS_MPP | MSTATUS_MPIE | (STK_RISCV_FPU != 0U ? (MSTATUS_FS | MSTATUS_XS) : 0U);
1957
1958#if (STK_RISCV_FPU != 0)
1959 task_frame->X3_FSR = 0U; // FCSR = 0: round-to-nearest, no accrued exception flags
1960#endif
1961}
1962
1963void Context::OnStop()
1964{
1965 // stop timer
1966 HW_StopMTimer();
1967
1968 // clear pending SV exception
1969 HW_ClearPendingSwitch();
1970
1971 m_started = false;
1972 m_exiting = true;
1973
1974 // make sure all assignments are set and executed
1975 __DSB();
1976 __ISB();
1977}
1978
1980{
1981 GetContext().OnStop();
1982
1983 // load context of the Exit trap
1984 HW_DisableInterrupts();
1985 OnTaskStart();
1986}
1987
1988uint32_t PlatformRiscV::GetTickResolution() const
1989{
1990 return GetContext().m_tick_resolution;
1991}
1992
1994{
1995 return static_cast<Cycles>(HW_GetMtime());
1996}
1997
1999{
2000 return HW_MtimeClockFrequency();
2001}
2002
2004{
2005 GetContext().m_handler->OnTaskSwitch(HW_GetCallerSP());
2006}
2007
2008void PlatformRiscV::Sleep(Timeout ticks)
2009{
2010 GetContext().m_handler->OnTaskSleep(HW_GetCallerSP(), ticks);
2011}
2012
2013bool PlatformRiscV::SleepUntil(Ticks timestamp)
2014{
2015 return GetContext().m_handler->OnTaskSleepUntil(HW_GetCallerSP(), timestamp);
2016}
2017
2018EWaitResult PlatformRiscV::Wait(ISyncObject *sync_obj, IMutex *mutex, Timeout timeout)
2019{
2020 return GetContext().m_handler->OnTaskWait(HW_GetCallerSP(), sync_obj, mutex, timeout);
2021}
2022
2024{
2025 TId result;
2026
2027 if (HW_IsHandlerMode())
2028 {
2029 // to avoid the collision with TID_ISR_N mask, extract and fit into available space:
2030
2031 const Word exc = HW_GetCurrentException();
2032 const Word num = (exc & 0x7FFU);
2033 #if (__riscv_xlen > 32)
2034 const Word interrupt_bit = ((exc & (1ULL << (__riscv_xlen - 1))) ? 0x800U : 0);
2035 #else
2036 const Word interrupt_bit = ((exc & (1U << (__riscv_xlen - 1))) ? 0x800U : 0);
2037 #endif
2038
2039 const TId isr_tid = TID_ISR_N | num | interrupt_bit;
2040 STK_ASSERT(IsIsrTid(isr_tid));
2041 result = isr_tid;
2042 }
2043 else
2044 {
2045 result = GetContext().m_handler->OnGetTid(HW_GetCallerSP());
2046 }
2047
2048 return result;
2049}
2050
2052{
2053#if STK_TICKLESS_IDLE
2054 return GetContext().Suspend();
2055#else
2056 return 0;
2057#endif
2058}
2059
2060void PlatformRiscV::Resume(Timeout elapsed_ticks)
2061{
2062#if STK_TICKLESS_IDLE
2063 GetContext().Resume(elapsed_ticks);
2064#else
2065 STK_UNUSED(elapsed_ticks);
2066#endif
2067}
2068
2070{
2071 bool is_handled = false;
2072
2073 if (GetContext().m_overrider != nullptr)
2074 {
2075 is_handled = GetContext().m_overrider->OnHardFault();
2076 }
2077
2078 if (!is_handled)
2079 {
2081 }
2082}
2083
2084void PlatformRiscV::SetEventOverrider(IEventOverrider *overrider)
2085{
2086 STK_ASSERT(!GetContext().m_started);
2087 GetContext().m_overrider = overrider;
2088}
2089
2091{
2092 return HW_GetCallerSP();
2093}
2094
2095void PlatformRiscV::SetSpecificEventHandler(ISpecificEventHandler *handler)
2096{
2097 STK_ASSERT(!GetContext().m_started);
2098 GetContext().m_specific = handler;
2099}
2100
2102{
2103 return GetContext().m_service;
2104}
2105
2107{
2108 GetContext().EnterCriticalSection();
2109}
2110
2112{
2113 GetContext().ExitCriticalSection();
2114}
2115
2117{
2118 HW_SpinLockLock(m_lock);
2119}
2120
2122{
2123 HW_SpinLockUnlock(m_lock);
2124}
2125
2127{
2128 return HW_SpinLockTryLock(m_lock);
2129}
2130
2132{
2133 return HW_IsHandlerMode();
2134}
2135
2137{
2138 // Always Privileged on RISC-V.
2139 return true;
2140}
2141
2143{
2144 return HiResClockImpl::GetInstance()->GetCycles();
2145}
2146
2148{
2149 const uint32_t freq = HiResClockImpl::GetInstance()->GetFrequency();
2150 STK_ASSERT(freq != 0U);
2151 return freq;
2152}
2153
2154#endif // _STK_ARCH_RISC_V
#define STK_SYSTEM_CORE_CLOCK_FREQUENCY
System clock frequency in Hz. Default: 150 MHz.
#define STK_SYSTEM_CORE_CLOCK_VAR
Definition of the system core clock variable holding frequency of the CPU in Hz.
volatile uint32_t SystemCoreClock
System clock frequency in Hz.
Contains common inventory for platform implementation.
#define STK_ARCH_GET_CPU_ID()
Get CPU core id of the caller, e.g. if called while running on core 0 then returned value must be 0.
#define GetContext()
Get platform's context.
Hardware Abstraction Layer (HAL) declarations for the stk::hw namespace.
void STK_PANIC_HANDLER_DEFAULT(stk::EKernelPanicId id)
Default panic handler: disable interrupts, record the id, and spin in a tight loop — a defined,...
#define STK_UNUSED(X)
Explicitly marks a variable as unused to suppress compiler warnings.
Definition stk_defs.h:608
#define __stk_attr_used
Marks a symbol as used, preventing the linker from discarding it even if no references are visible (d...
Definition stk_defs.h:244
#define __stk_forceinline
Forces compiler to always inline the decorated function, regardless of optimisation level.
Definition stk_defs.h:175
#define STK_SLEEP_TRAP_STACK_SIZE
Stack size for the sleep trap in elements of Word (default: STK_STACK_SIZE_MIN).
Definition stk_defs.h:546
#define STK_ASSERT(e)
Runtime assertion. Halts execution if the expression e evaluates to false.
Definition stk_defs.h:409
#define STK_STACK_SIZE_MIN
Minimum stack size in elements of Word, shared by all stack allocation lower-bound checks.
Definition stk_defs.h:533
#define __stk_attr_noinline
Prevents compiler from inlining the decorated function (function prefix).
Definition stk_defs.h:255
#define STK_CRITICAL_SECTION_NESTINGS_MAX
Maximum allowable recursion depth for critical section entry (default: 16).
Definition stk_defs.h:483
#define STK_ARCH_CPU_COUNT
Number of physical CPU cores available to the scheduler (default: 1).
Definition stk_defs.h:493
#define __stk_attr_naked
Suppresses compiler-generated function prologue and epilogue (function prefix).
Definition stk_defs.h:211
#define STK_STATIC_ASSERT_DESC(X, DESC)
Compile-time assertion with a custom error description. Produces a compilation error if X is false.
Definition stk_defs.h:429
#define STK_STACK_MEMORY_FILLER
Sentinel value written to the entire stack region at initialization (stack watermark pattern).
Definition stk_defs.h:456
#define __stk_attr_noreturn
Declares that function never returns to its caller (function prefix).
Definition stk_defs.h:222
#define STK_STATIC_ASSERT(X)
Compile-time assertion. Produces a compilation error if X is false.
Definition stk_defs.h:446
#define STK_VIRT_DTOR
Makes destructors virtual and compliant to strict rules if STK_STRICT_COMPLIANCY=0.
Definition stk_defs.h:159
Contains helper implementations which simplify user-side code.
Namespace of STK package.
uintptr_t Word
Native processor word type.
Definition stk_common.h:136
constexpr TId TID_ISR_N
Bitmask sentinel for ISR-context task identifiers.
Definition stk_common.h:193
@ ACCESS_PRIVILEGED
Privileged access mode (access to hardware is fully unrestricted).
Definition stk_common.h:44
constexpr Timeout NO_WAIT
Timeout value: return immediately if the synchronization object is not yet signaled (non-blocking pol...
Definition stk_common.h:210
static __stk_forceinline Cycles ConvertTimeUsToClockCycles(uint32_t clock_freq, Ticks time_us)
Convert time (microseconds) to core clock cycles.
int64_t Ticks
Ticks value.
Definition stk_common.h:151
int32_t Timeout
Timeout time (ticks).
Definition stk_common.h:146
static constexpr T Max(T a, T b)
Compile-time maximum of two values.
Definition stk_defs.h:645
EStackType
Stack type.
Definition stk_common.h:83
@ STACK_SLEEP_TRAP
Stack of the Sleep trap.
Definition stk_common.h:85
@ STACK_USER_TASK
Stack of the user task.
Definition stk_common.h:84
@ STACK_EXIT_TRAP
Stack of the Exit trap.
Definition stk_common.h:86
static bool IsIsrTid(TId id)
Test whether a task identifier represents an ISR context.
Definition stk_common.h:233
static __stk_forceinline void STK_KERNEL_PANIC(stk::EKernelPanicId id)
Called when the kernel detects an unrecoverable internal fault.
Definition stk_arch.h:75
EWaitResult
Wait result (see IKernelService::Wait).
Definition stk_common.h:123
uint64_t Cycles
Cycles value.
Definition stk_common.h:161
Word TId
Task (thread) id.
Definition stk_common.h:141
EKernelPanicId
Identifies the source of a kernel panic.
Definition stk_common.h:64
@ KERNEL_PANIC_BAD_STACK_TYPE
Stack type is unknown.
Definition stk_common.h:75
@ KERNEL_PANIC_HRT_HARD_FAULT
Kernel running in KERNEL_HRT mode reported deadline failure of the task.
Definition stk_common.h:69
@ KERNEL_PANIC_NONE
Panic is absent (no fault).
Definition stk_common.h:65
@ KERNEL_PANIC_CPU_EXCEPTION
CPU reported an exception and halted execution.
Definition stk_common.h:70
@ KERNEL_PANIC_SPINLOCK_DEADLOCK
Spin-lock timeout expired: lock owner never released.
Definition stk_common.h:66
static constexpr T * WordToPtr(Word value) noexcept
Cast a CPU register-width integer back to a pointer.
Definition stk_arch.h:123
static constexpr Word PtrToWord(T *const ptr) noexcept
Cast a pointer to a CPU register-width integer.
Definition stk_arch.h:106
bool IsContextPrivileged()
Check if caller is Privileged.
bool IsInsideISR()
Check whether the CPU is currently executing inside a hardware interrupt service routine (ISR).
Cycles GetSysTimerCount() const override
Get system timer count value.
Word GetCallerSP() const override
Get caller's Stack Pointer (SP).
void Resume(Timeout elapsed_ticks) override
Resume scheduling after a prior Suspend() call.
void Start() override
Start scheduling.
uint32_t GetTickResolution() const override
Get resolution of the system tick timer in microseconds. Resolution means a number of microseconds be...
bool SleepUntil(Ticks timestamp) override
Put calling process into a sleep state until the specified timestamp.
uint32_t GetSysTimerFrequency() const override
Get system timer frequency.
void ProcessHardFault() override
Cause a hard fault of the system.
void InitStack(EStackType stack_type, Stack *stack, IStackMemory *stack_memory, ITask *user_task) override
Initialize stack memory of the user task.
void ProcessTick() override
Process one tick.
TId GetTid() const override
Get thread Id.
EWaitResult Wait(ISyncObject *sync_obj, IMutex *mutex, Timeout timeout) override
Put calling process into a waiting state until synchronization object is signaled or timeout occurs.
void SetSpecificEventHandler(ISpecificEventHandler *handler)
void SetEventOverrider(IEventOverrider *overrider) override
Set platform event overrider.
void Stop() override
Stop scheduling.
void SwitchToNext() override
Switch to a next task.
Timeout Suspend() override
Suspend scheduling.
void Sleep(Timeout ticks) override
Put calling process into a sleep state.
void Initialize(IEventHandler *event_handler, IKernelService *service, uint32_t resolution_us, Stack *exit_trap) override
Initialize scheduler's context.
Base platform context for all platform implementations.
static Word InitStackMemory(IStackMemory *const memory)
Initialize stack memory by filling it with STK_STACK_MEMORY_FILLER.
static void Enter()
Enter a critical section.
static void Exit()
Exit a critical section.
bool TryLock()
Attempt to acquire SpinLock in a single non-blocking attempt.
void Lock()
Acquire SpinLock, blocking until it is available.
void Unlock()
Release SpinLock, allowing another thread or core to acquire it.
static uint32_t GetFrequency()
Get clock frequency.
static Cycles GetCycles()
Get number of clock cycles elapsed.
Stack descriptor.
Definition stk_common.h:302
Word SP
Stack Pointer (SP) register (note: must be the first entry in this struct).
Definition stk_common.h:303
Interface for a stack memory region.
Definition stk_common.h:319
virtual size_t GetStackSize() const =0
Get number of elements of the stack memory array.
Synchronization object.
Definition stk_common.h:456
Interface for mutex synchronization primitive.
Definition stk_common.h:613
Interface for a user task.
Definition stk_common.h:670
virtual void Run()=0
Entry point of the user task.
Interface for the kernel services exposed to the user processes during run-time when Kernel started s...
static IKernelService * GetInstance()
Get CPU-local instance of the kernel service.
RISC-V specific event handler.