SuperTinyKernel™ RTOS 1.07.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_CS_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#if STK_TLS && !STK_INLINE_TLS
1098 Word GetTls()
1099 {
1100 hw::CriticalSection::ScopedLock cs_;
1101
1102 STK_ASSERT(m_stack_active != nullptr);
1103
1104 return m_stack_active->tls;
1105 }
1106
1107 void SetTls(Word tp)
1108 {
1109 hw::CriticalSection::ScopedLock cs_;
1110
1111 STK_ASSERT(m_stack_active != nullptr);
1112
1113 m_stack_active->tls = tp;
1114 }
1115#endif // STK_TLS && !STK_INLINE_TLS
1116
1117 void Start();
1118 void OnStart();
1119 void OnStop();
1120#if STK_TICKLESS_IDLE
1121 Timeout Suspend();
1122 void Resume(Timeout elapsed_ticks);
1123#endif
1124
1125 typedef IPlatform::IEventOverrider eovrd_t;
1126 typedef PlatformRiscV::ISpecificEventHandler sehndl_t;
1127 typedef StackMemoryWrapper<STK_RISCV_ISR_STACK_SIZE>::MemoryType isrmem_t;
1128
1129 Stack m_stack_main;
1130 Stack m_stack_isr;
1131 isrmem_t m_stack_isr_mem;
1132 JmpFrame m_exit_buf;
1133 eovrd_t *m_overrider;
1134 sehndl_t *m_specific;
1135 uint32_t m_tick_period;
1136 uint64_t m_last_mtime;
1137#if STK_TICKLESS_IDLE
1138 Timeout m_sleep_ticks;
1139#endif
1140 Word m_csu;
1141 uint8_t m_csu_nesting;
1142 bool m_starting;
1143 bool m_started;
1144 volatile bool m_exiting;
1145}
1146s_StkPlatformContext[STK_ARCH_CPU_COUNT];
1147
1149{
1150#ifdef _STK_RISCV_USE_PENDSV
1151 Word cs;
1152 HW_CriticalSectionStart(cs);
1153
1154 GetContext().ProcessTick();
1155
1156 HW_CriticalSectionEnd(cs);
1157#else
1158 // unsupported scenario
1159 STK_ASSERT(false);
1160#endif
1161}
1162
1164static volatile EKernelPanicId g_LastPanicId = KERNEL_PANIC_NONE;
1165
1166__stk_attr_noinline // keep out of inlining to preserve stack frame
1167__stk_attr_noreturn // never returns - a trap
1169{
1170 g_LastPanicId = id;
1171
1172 // disable all maskable interrupts: this prevents scheduler from running again and corrupting state further
1173 HW_DisableInterrupts();
1174
1175 // spin forever: with a watchdog active this produces a clean reset, without a watchdog,
1176 // a debugger can attach and inspect 'id'
1177 for (;;)
1178 {
1179 __stk_relax_cpu();
1180 }
1181}
1182
1183#define STK_ASM_SAVE_CONTEXT_BASE\
1184 SREG " x1, 2*" REGBYTES "(sp) \n"\
1185 /*SREG " x2, 3*" REGBYTES "(sp) \n" // skip saving sp, Stack pointer */\
1186 /*SREG " x3, 4*" REGBYTES "(sp) \n" // skip saving gp, Global pointer (note: slot is used by fscsr) */\
1187 SREG " x4, 5*" REGBYTES "(sp) \n"\
1188 SREG " x5, 6*" REGBYTES "(sp) \n"\
1189 SREG " x6, 7*" REGBYTES "(sp) \n"\
1190 SREG " x7, 8*" REGBYTES "(sp) \n"\
1191 SREG " x8, 9*" REGBYTES "(sp) \n"\
1192 SREG " x9, 10*" REGBYTES "(sp) \n"\
1193 SREG " x10, 11*" REGBYTES "(sp) \n"\
1194 SREG " x11, 12*" REGBYTES "(sp) \n"\
1195 SREG " x12, 13*" REGBYTES "(sp) \n"\
1196 SREG " x13, 14*" REGBYTES "(sp) \n"\
1197 SREG " x14, 15*" REGBYTES "(sp) \n"\
1198 SREG " x15, 16*" REGBYTES "(sp) \n"
1199
1200#if (__riscv_32e != 1)
1201#define STK_ASM_SAVE_CONTEXT_RV32I_EXT\
1202 SREG " x16, 17*" REGBYTES "(sp) \n"\
1203 SREG " x17, 18*" REGBYTES "(sp) \n"\
1204 SREG " x18, 19*" REGBYTES "(sp) \n"\
1205 SREG " x19, 20*" REGBYTES "(sp) \n"\
1206 SREG " x20, 21*" REGBYTES "(sp) \n"\
1207 SREG " x21, 22*" REGBYTES "(sp) \n"\
1208 SREG " x22, 23*" REGBYTES "(sp) \n"\
1209 SREG " x23, 24*" REGBYTES "(sp) \n"\
1210 SREG " x24, 25*" REGBYTES "(sp) \n"\
1211 SREG " x25, 26*" REGBYTES "(sp) \n"\
1212 SREG " x26, 27*" REGBYTES "(sp) \n"\
1213 SREG " x27, 28*" REGBYTES "(sp) \n"\
1214 SREG " x28, 29*" REGBYTES "(sp) \n"\
1215 SREG " x29, 30*" REGBYTES "(sp) \n"\
1216 SREG " x30, 31*" REGBYTES "(sp) \n"\
1217 SREG " x31, 32*" REGBYTES "(sp) \n"
1218#else
1219#define STK_ASM_SAVE_CONTEXT_RV32I_EXT
1220#endif
1221
1222#if (STK_RISCV_FPU != 0)
1223#define STK_ASM_SAVE_CONTEXT_FP\
1224 FSREG " f0, " FOFFSET "+0*" FREGBYTES "(sp) \n"\
1225 FSREG " f1, " FOFFSET "+1*" FREGBYTES "(sp) \n"\
1226 FSREG " f2, " FOFFSET "+2*" FREGBYTES "(sp) \n"\
1227 FSREG " f3, " FOFFSET "+3*" FREGBYTES "(sp) \n"\
1228 FSREG " f4, " FOFFSET "+4*" FREGBYTES "(sp) \n"\
1229 FSREG " f5, " FOFFSET "+5*" FREGBYTES "(sp) \n"\
1230 FSREG " f6, " FOFFSET "+6*" FREGBYTES "(sp) \n"\
1231 FSREG " f7, " FOFFSET "+7*" FREGBYTES "(sp) \n"\
1232 FSREG " f8, " FOFFSET "+8*" FREGBYTES "(sp) \n"\
1233 FSREG " f9, " FOFFSET "+9*" FREGBYTES "(sp) \n"\
1234 FSREG " f10, " FOFFSET "+10*" FREGBYTES "(sp) \n"\
1235 FSREG " f11, " FOFFSET "+11*" FREGBYTES "(sp) \n"\
1236 FSREG " f12, " FOFFSET "+12*" FREGBYTES "(sp) \n"\
1237 FSREG " f13, " FOFFSET "+13*" FREGBYTES "(sp) \n"\
1238 FSREG " f14, " FOFFSET "+14*" FREGBYTES "(sp) \n"\
1239 FSREG " f15, " FOFFSET "+15*" FREGBYTES "(sp) \n"\
1240 FSREG " f16, " FOFFSET "+16*" FREGBYTES "(sp) \n"\
1241 FSREG " f17, " FOFFSET "+17*" FREGBYTES "(sp) \n"\
1242 FSREG " f18, " FOFFSET "+18*" FREGBYTES "(sp) \n"\
1243 FSREG " f19, " FOFFSET "+19*" FREGBYTES "(sp) \n"\
1244 FSREG " f20, " FOFFSET "+20*" FREGBYTES "(sp) \n"\
1245 FSREG " f21, " FOFFSET "+21*" FREGBYTES "(sp) \n"\
1246 FSREG " f22, " FOFFSET "+22*" FREGBYTES "(sp) \n"\
1247 FSREG " f23, " FOFFSET "+23*" FREGBYTES "(sp) \n"\
1248 FSREG " f24, " FOFFSET "+24*" FREGBYTES "(sp) \n"\
1249 FSREG " f25, " FOFFSET "+25*" FREGBYTES "(sp) \n"\
1250 FSREG " f26, " FOFFSET "+26*" FREGBYTES "(sp) \n"\
1251 FSREG " f27, " FOFFSET "+27*" FREGBYTES "(sp) \n"\
1252 FSREG " f28, " FOFFSET "+28*" FREGBYTES "(sp) \n"\
1253 FSREG " f29, " FOFFSET "+29*" FREGBYTES "(sp) \n"\
1254 FSREG " f30, " FOFFSET "+30*" FREGBYTES "(sp) \n"\
1255 FSREG " f31, " FOFFSET "+31*" FREGBYTES "(sp) \n"
1256#else
1257#define STK_ASM_SAVE_CONTEXT_FP
1258#endif
1259
1260#define STK_ASM_SAVE_CONTEXT_PC_STATUS\
1261 "csrr t0, mepc \n"\
1262 "csrr t1, mstatus \n"\
1263 SREG " t0, 0*" REGBYTES "(sp) \n"\
1264 SREG " t1, 1*" REGBYTES "(sp) \n"
1265
1266#if (STK_RISCV_FPU != 0)
1267#define STK_ASM_SAVE_CONTEXT_FRCSR\
1268 "frcsr t0 \n"\
1269 SREG " t0, 4*" REGBYTES "(sp) \n" /* use stack memory slot of gp (see comment for x3 above) */
1270#else
1271#define STK_ASM_SAVE_CONTEXT_FRCSR
1272#endif
1273
1274#define STK_ASM_SAVE_CONTEXT\
1275 "addi sp, sp, -" REGSIZE " \n" /* allocate stack memory for registers */\
1276 STK_ASM_SAVE_CONTEXT_BASE\
1277 STK_ASM_SAVE_CONTEXT_RV32I_EXT\
1278 STK_ASM_SAVE_CONTEXT_FP\
1279 STK_ASM_SAVE_CONTEXT_PC_STATUS\
1280 STK_ASM_SAVE_CONTEXT_FRCSR
1281
1282#define STK_ASM_LOAD_CONTEXT_BASE\
1283 LREG " x1, 2*" REGBYTES "(sp) \n"\
1284 /*LREG " x2, 3*" REGBYTES "(sp) \n" skip loading sp, Stack pointer */\
1285 /*LREG " x3, 4*" REGBYTES "(sp) \n" skip loading gp, Global pointer (note: slot is used by fscsr) */\
1286 LREG " x4, 5*" REGBYTES "(sp) \n"\
1287 LREG " x5, 6*" REGBYTES "(sp) \n"\
1288 LREG " x6, 7*" REGBYTES "(sp) \n"\
1289 LREG " x7, 8*" REGBYTES "(sp) \n"\
1290 LREG " x8, 9*" REGBYTES "(sp) \n"\
1291 LREG " x9, 10*" REGBYTES "(sp) \n"\
1292 LREG " x10, 11*" REGBYTES "(sp) \n"\
1293 LREG " x11, 12*" REGBYTES "(sp) \n"\
1294 LREG " x12, 13*" REGBYTES "(sp) \n"\
1295 LREG " x13, 14*" REGBYTES "(sp) \n"\
1296 LREG " x14, 15*" REGBYTES "(sp) \n"\
1297 LREG " x15, 16*" REGBYTES "(sp) \n"
1298
1299#if (__riscv_32e != 1)
1300#define STK_ASM_LOAD_CONTEXT_RV32I_EXT\
1301 LREG " x16, 17*" REGBYTES "(sp) \n"\
1302 LREG " x17, 18*" REGBYTES "(sp) \n"\
1303 LREG " x18, 19*" REGBYTES "(sp) \n"\
1304 LREG " x19, 20*" REGBYTES "(sp) \n"\
1305 LREG " x20, 21*" REGBYTES "(sp) \n"\
1306 LREG " x21, 22*" REGBYTES "(sp) \n"\
1307 LREG " x22, 23*" REGBYTES "(sp) \n"\
1308 LREG " x23, 24*" REGBYTES "(sp) \n"\
1309 LREG " x24, 25*" REGBYTES "(sp) \n"\
1310 LREG " x25, 26*" REGBYTES "(sp) \n"\
1311 LREG " x26, 27*" REGBYTES "(sp) \n"\
1312 LREG " x27, 28*" REGBYTES "(sp) \n"\
1313 LREG " x28, 29*" REGBYTES "(sp) \n"\
1314 LREG " x29, 30*" REGBYTES "(sp) \n"\
1315 LREG " x30, 31*" REGBYTES "(sp) \n"\
1316 LREG " x31, 32*" REGBYTES "(sp) \n"
1317#else
1318#define STK_ASM_LOAD_CONTEXT_RV32I_EXT
1319#endif
1320
1321#if (STK_RISCV_FPU != 0)
1322#define STK_ASM_LOAD_CONTEXT_FP\
1323 FLREG " f0, " FOFFSET "+0*" FREGBYTES "(sp) \n"\
1324 FLREG " f1, " FOFFSET "+1*" FREGBYTES "(sp) \n"\
1325 FLREG " f2, " FOFFSET "+2*" FREGBYTES "(sp) \n"\
1326 FLREG " f3, " FOFFSET "+3*" FREGBYTES "(sp) \n"\
1327 FLREG " f4, " FOFFSET "+4*" FREGBYTES "(sp) \n"\
1328 FLREG " f5, " FOFFSET "+5*" FREGBYTES "(sp) \n"\
1329 FLREG " f6, " FOFFSET "+6*" FREGBYTES "(sp) \n"\
1330 FLREG " f7, " FOFFSET "+7*" FREGBYTES "(sp) \n"\
1331 FLREG " f8, " FOFFSET "+8*" FREGBYTES "(sp) \n"\
1332 FLREG " f9, " FOFFSET "+9*" FREGBYTES "(sp) \n"\
1333 FLREG " f10, " FOFFSET "+10*" FREGBYTES "(sp) \n"\
1334 FLREG " f11, " FOFFSET "+11*" FREGBYTES "(sp) \n"\
1335 FLREG " f12, " FOFFSET "+12*" FREGBYTES "(sp) \n"\
1336 FLREG " f13, " FOFFSET "+13*" FREGBYTES "(sp) \n"\
1337 FLREG " f14, " FOFFSET "+14*" FREGBYTES "(sp) \n"\
1338 FLREG " f15, " FOFFSET "+15*" FREGBYTES "(sp) \n"\
1339 FLREG " f16, " FOFFSET "+16*" FREGBYTES "(sp) \n"\
1340 FLREG " f17, " FOFFSET "+17*" FREGBYTES "(sp) \n"\
1341 FLREG " f18, " FOFFSET "+18*" FREGBYTES "(sp) \n"\
1342 FLREG " f19, " FOFFSET "+19*" FREGBYTES "(sp) \n"\
1343 FLREG " f20, " FOFFSET "+20*" FREGBYTES "(sp) \n"\
1344 FLREG " f21, " FOFFSET "+21*" FREGBYTES "(sp) \n"\
1345 FLREG " f22, " FOFFSET "+22*" FREGBYTES "(sp) \n"\
1346 FLREG " f23, " FOFFSET "+23*" FREGBYTES "(sp) \n"\
1347 FLREG " f24, " FOFFSET "+24*" FREGBYTES "(sp) \n"\
1348 FLREG " f25, " FOFFSET "+25*" FREGBYTES "(sp) \n"\
1349 FLREG " f26, " FOFFSET "+26*" FREGBYTES "(sp) \n"\
1350 FLREG " f27, " FOFFSET "+27*" FREGBYTES "(sp) \n"\
1351 FLREG " f28, " FOFFSET "+28*" FREGBYTES "(sp) \n"\
1352 FLREG " f29, " FOFFSET "+29*" FREGBYTES "(sp) \n"\
1353 FLREG " f30, " FOFFSET "+30*" FREGBYTES "(sp) \n"\
1354 FLREG " f31, " FOFFSET "+31*" FREGBYTES "(sp) \n"
1355#else
1356#define STK_ASM_LOAD_CONTEXT_FP
1357#endif
1358
1359#define STK_ASM_LOAD_CONTEXT_PC_STATUS\
1360 LREG " t0, 0*" REGBYTES "(sp) \n"\
1361 LREG " t1, 1*" REGBYTES "(sp) \n"\
1362 "csrw mepc, t0 \n"\
1363 "csrw mstatus, t1 \n"
1364
1365#if (STK_RISCV_FPU != 0)
1366#define STK_ASM_LOAD_CONTEXT_FRCSR\
1367 LREG " t0, 4*" REGBYTES "(sp) \n" /* use stack memory slot of gp (see comment for x3 below) */\
1368 "fscsr t0 \n"
1369#else
1370#define STK_ASM_LOAD_CONTEXT_FRCSR
1371#endif
1372
1373#define STK_ASM_LOAD_CONTEXT\
1374 STK_ASM_LOAD_CONTEXT_PC_STATUS\
1375 STK_ASM_LOAD_CONTEXT_FRCSR\
1376 STK_ASM_LOAD_CONTEXT_BASE\
1377 STK_ASM_LOAD_CONTEXT_RV32I_EXT\
1378 STK_ASM_LOAD_CONTEXT_FP\
1379 "addi sp, sp, " REGSIZE " \n" /* shrink stack memory of registers */
1380
1381static __stk_forceinline void HW_LoadContextAndExit()
1382{
1383 __asm volatile(
1384 LREG " t0, %0 \n" // load the first member (SP) into t0
1385 LREG " sp, 0(t0) \n" // sp = t0
1386
1387 STK_ASM_LOAD_CONTEXT
1388 STK_ASM_EXIT_FROM_HANDLER " \n"
1389
1390 : /* output: none */
1391 : "m"(GetContext().m_stack_active)
1392 : "t0", "t1", "a2", "a3", "a4", "a5", "gp", "memory");
1393}
1394
1395static __stk_forceinline void HW_EnableFullFpuAccess()
1396{
1397#if (STK_RISCV_FPU != 0)
1398 __asm volatile(
1399 "csrs mstatus, %0"
1400 : /* output: none */
1401 : "r"(MSTATUS_FS | MSTATUS_XS)
1402 : "memory" /* ensure no FP instructions are moved before this call */);
1403#endif
1404}
1405
1406static __stk_forceinline void HW_ClearFpuState()
1407{
1408#if (STK_RISCV_FPU != 0)
1409 __asm volatile(
1410 "fssr x0"
1411 : /* output: none */
1412 : /* input: none */
1413 : "memory" /* ensure flags are cleared before next FP op */);
1414#endif
1415}
1416
1417static __stk_forceinline void HW_SaveMainSP()
1418{
1419 __asm volatile(
1420 SREG " sp, %0"
1421 : "=m"(GetContext().m_stack_main)
1422 : /* input: none */
1423 : "memory" /* protect against compiler reordering */ );
1424}
1425
1426static __stk_forceinline void HW_LoadMainSP()
1427{
1428 __asm volatile(
1429 LREG " sp, %0"
1430 : /* output: none */
1431 : "m"(GetContext().m_stack_main)
1432 : "memory" /* protect against compiler reordering */ );
1433}
1434
1444static __stk_forceinline Word HW_GetCurrentException()
1445{
1446 Word mcause;
1447 __asm volatile("csrr %0, mcause" : "=r"(mcause));
1448 return mcause;
1449}
1450
1451static __stk_forceinline bool HW_IsHandlerMode()
1452{
1453 const Word current_sp = HW_GetCallerSP();
1454
1455 // get the bounds of the ISR stack from our Context
1456 // note: STK uses StackMemoryWrapper, so we check against that memory block
1457 const Word isr_stack_base = hw::PtrToWord(&GetContext().m_stack_isr_mem);
1458 const Word isr_stack_top = isr_stack_base + (STK_RISCV_ISR_STACK_SIZE * sizeof(Word));
1459
1460 return ((current_sp >= isr_stack_base) && (current_sp < isr_stack_top));
1461}
1462
1463static __stk_forceinline void OnTaskStart()
1464{
1465 HW_LoadContextAndExit();
1466}
1467
1468// __stk_attr_used for LTO
1469extern "C" STK_RISCV_ISR_SECTION __stk_attr_used void TrySwitchContext()
1470{
1471 GetContext().OnSwitchContext();
1472}
1473
1474#ifdef _STK_RISCV_USE_PENDSV
1475extern "C" STK_RISCV_ISR_SECTION __stk_attr_naked void STK_SYSTICK_HANDLER()
1476{
1477 __asm volatile(
1478 // 1. save full interrupted context onto the task stack
1479 STK_ASM_SAVE_CONTEXT
1480
1481 // 2. store task SP into s_StkRiscvSpIsrInt[hart] directly (plain Word, no struct indirection)
1482#if (STK_ARCH_CPU_COUNT > 1)
1483 "csrr t0, mhartid \n"
1484 "la t1, s_StkRiscvSpIsrInt \n"
1485 "slli t0, t0, " REGBYTES_LOG2 " \n" // t0 = hart * sizeof(Word)
1486 "add t1, t1, t0 \n" // t1 = &s_StkRiscvSpIsrInt[hart]
1487 SREG " sp, 0(t1) \n" // store sp directly - no pointer dereference
1488#else
1489 "la t1, s_StkRiscvSpIsrInt \n"
1490 SREG " sp, 0(t1) \n" // store sp directly - no pointer dereference
1491#endif
1492
1493 // 3. switch to private ISR stack
1494#if (STK_ARCH_CPU_COUNT > 1)
1495 "csrr t0, mhartid \n"
1496 "la t1, s_StkRiscvStackIsr \n"
1497 "slli t0, t0, " REGBYTES_LOG2 " \n"
1498 "add t1, t1, t0 \n"
1499 LREG " t1, 0(t1) \n"
1500#else
1501 "la t1, s_StkRiscvStackIsr \n"
1502 LREG " t1, 0(t1) \n"
1503#endif
1504 LREG " sp, 0(t1) \n" // sp = Stack::SP of ISR stack
1505
1506 // 4. run scheduler
1507 "jal ra, TrySwitchContext \n"
1508
1509 // 5. restore the interrupted task's SP from s_StkRiscvSpIsrInt[hart]
1510#if (STK_ARCH_CPU_COUNT > 1)
1511 "csrr t0, mhartid \n"
1512 "la t1, s_StkRiscvSpIsrInt \n"
1513 "slli t0, t0, " REGBYTES_LOG2 " \n"
1514 "add t1, t1, t0 \n"
1515 LREG " sp, 0(t1) \n" // sp = saved task SP - direct load, no struct
1516#else
1517 "la t1, s_StkRiscvSpIsrInt \n"
1518 LREG " sp, 0(t1) \n" // sp = saved task SP - direct load, no struct
1519#endif
1520
1521 // 6. restore context
1522 STK_ASM_LOAD_CONTEXT
1523
1524 // 7. exit ISR handler
1525 STK_ASM_EXIT_FROM_HANDLER " \n"
1526
1527 : /* outputs: none - naked, compiler emits nothing outside this asm */
1528 : /* inputs: all addresses loaded as linker symbols via "la" */
1529 : /* clobbers: none - the asm string owns all registers */);
1530}
1531extern "C" STK_RISCV_ISR_SECTION __stk_attr_naked void STK_MSI_HANDLER()
1532{
1533 __asm volatile(
1534 // 1. save context
1535 STK_ASM_SAVE_CONTEXT
1536
1537 // 2. store task SP into s_StkRiscvStackIdle[hart]->SP
1538 // all integer registers are now saved. t0/t1 are free to use as scratch.
1539 // "la" loads the address of the global array - a linker-time constant,
1540 // no compiler-generated runtime code, safe to use here
1541#if (STK_ARCH_CPU_COUNT > 1)
1542 "csrr t0, mhartid \n"
1543 "la t1, s_StkRiscvStackIdle \n"
1544 "slli t0, t0, " REGBYTES_LOG2 " \n" // t0 = hart * sizeof(Stack*)
1545 "add t1, t1, t0 \n" // t1 = &s_StkRiscvStackIdle[hart]
1546 LREG " t1, 0(t1) \n" // t1 = s_StkRiscvStackIdle[hart] (Stack*)
1547#else
1548 "la t1, s_StkRiscvStackIdle \n"
1549 LREG " t1, 0(t1) \n" // t1 = s_StkRiscvStackIdle[0] (Stack*)
1550#endif
1551 SREG " sp, 0(t1) \n" // Stack::SP = task's sp (SP is first member)
1552
1553 // 3. clear exception: MSIP[hart] = 0
1554#if (STK_ARCH_CPU_COUNT > 1)
1555 "csrr t0, mhartid \n"
1556 "slli t0, t0, 2 \n" // t0 = hart * 4
1557 "li t1, %[clint_msip_base] \n"
1558 "add t0, t0, t1 \n" // t0 = &MSIP[hart]
1559#else
1560 "li t0, %[clint_msip_base] \n" // t0 = &MSIP[0]
1561#endif
1562 "sw zero, 0(t0) \n" // MSIP[hart] = 0
1563 "fence rw, rw \n" // fence rw,rw - ensure the write is visible before re-enable
1564
1565 // 4. load SP from s_StkRiscvStackActive[hart]->SP
1566#if (STK_ARCH_CPU_COUNT > 1)
1567 "csrr t0, mhartid \n"
1568 "la t1, s_StkRiscvStackActive \n"
1569 "slli t0, t0, " REGBYTES_LOG2 " \n"
1570 "add t1, t1, t0 \n"
1571 LREG " t1, 0(t1) \n"
1572#else
1573 "la t1, s_StkRiscvStackActive \n"
1574 LREG " t1, 0(t1) \n"
1575#endif
1576 LREG " sp, 0(t1) \n" // sp = active task's saved SP
1577
1578 // 5. load context of the active task
1579 STK_ASM_LOAD_CONTEXT
1580
1581 // 6. exit ISR handler
1582 STK_ASM_EXIT_FROM_HANDLER " \n"
1583
1584 : /* outputs: none - naked, compiler emits nothing outside this asm */
1585 : [clint_msip_base] "i" (STK_RISCV_CLINT_BASE_ADDR) /* other inputs: all addresses loaded as linker symbols via "la" */
1586 : /* clobbers: none - the asm string owns all registers */);
1587}
1588#else // !_STK_RISCV_USE_PENDSV
1589/* STK_SYSTICK_HANDLER
1590
1591RISC-V machine-timer ISR: Saves the interrupted task's full context, switches
1592to the private ISR stack, calls TrySwitchContext (which reschedules the timer
1593and runs the scheduler), then restores the (possibly new) task's context.
1594
1595DESIGN RULES - must be obeyed to work correctly at all optimisation levels:
1596
1597 1. Single asm volatile, no compiler operands.
1598 The function body is ONE __asm volatile("..." : : : ) with empty
1599 input/output/clobber lists. No "m" or "r" constraints are used because
1600 the compiler evaluates those as C expressions BEFORE emitting any asm
1601 text, i.e. before the register save - trashing uninitialized registers.
1602
1603 2. All addresses are linker symbols loaded via "la" inside the asm.
1604 s_StkRiscvStackActive and s_StkRiscvStackIsr are plain file-scope globals. "la reg, sym"
1605 emits a PC-relative load that is resolved at link time, it produces no
1606 compiler-generated code outside the asm string.
1607
1608 3. Stack pointer indexing uses sizeof(Stack*) == REGBYTES.
1609 For multi-hart builds the array index is hart * REGBYTES, which is a
1610 single left-shift by log2(REGBYTES): 2 for RV32 (4 bytes), 3 for RV64
1611 (8 bytes). REGBYTES_LOG2 is defined below accordingly.
1612
1613 4. s_StkRiscvStackActive[hart]->SP is updated by TrySwitchContext.
1614 The naked asm reads it fresh after the jal returns, so it always sees
1615 the task the scheduler has chosen - even if it changed.
1616
1617 Stack frame layout (offsets from sp after "addi sp,-REGSIZE"):
1618 [0*REGBYTES] mepc (service slot 0)
1619 [1*REGBYTES] mstatus (service slot 1)
1620 [2*REGBYTES] x1 / ra
1621 [3*REGBYTES] x2 / sp - SKIPPED, managed explicitly
1622 [4*REGBYTES] x3 / gp - SKIPPED, fixed register; slot reused for FCSR
1623 [5*REGBYTES] x4 / tp
1624 [6*REGBYTES] x5 / t0
1625 ...
1626 [32*REGBYTES] x31 / t6 (RV32I; absent on RV32E)
1627 [FOFFSET + n*FREGBYTES] fn (FP registers, if STK_RISCV_FPU != 0)
1628*/
1629extern "C" STK_RISCV_ISR_SECTION __stk_attr_naked void STK_SYSTICK_HANDLER()
1630{
1631 __asm volatile(
1632 // 1. save context
1633 STK_ASM_SAVE_CONTEXT
1634
1635 // 2. store task SP into s_StkRiscvStackActive[hart]->SP
1636 // all integer registers are now saved. t0/t1 are free to use as scratch.
1637 // "la" loads the address of the global array - a linker-time constant,
1638 // no compiler-generated runtime code, safe to use here
1639#if (STK_ARCH_CPU_COUNT > 1)
1640 "csrr t0, mhartid \n"
1641 "la t1, s_StkRiscvStackActive \n"
1642 "slli t0, t0, " REGBYTES_LOG2 " \n" // t0 = hart * sizeof(Stack*)
1643 "add t1, t1, t0 \n" // t1 = &s_StkRiscvStackActive[hart]
1644 LREG " t1, 0(t1) \n" // t1 = s_StkRiscvStackActive[hart] (Stack*)
1645#else
1646 "la t1, s_StkRiscvStackActive \n"
1647 LREG " t1, 0(t1) \n" // t1 = s_StkRiscvStackActive[0] (Stack*)
1648#endif
1649 SREG " sp, 0(t1) \n" // Stack::SP = task's sp (SP is first member)
1650
1651 // 3. switch to private ISR stack
1652#if (STK_ARCH_CPU_COUNT > 1)
1653 "csrr t0, mhartid \n"
1654 "la t1, s_StkRiscvStackIsr \n"
1655 "slli t0, t0, " REGBYTES_LOG2 " \n"
1656 "add t1, t1, t0 \n"
1657 LREG " t1, 0(t1) \n" // t1 = s_StkRiscvStackIsr[hart] (Stack*)
1658#else
1659 "la t1, s_StkRiscvStackIsr \n"
1660 LREG " t1, 0(t1) \n" // t1 = s_StkRiscvStackIsr[0] (Stack*)
1661#endif
1662 LREG " sp, 0(t1) \n" // sp = Stack::SP of ISR stack
1663
1664 // 4. call TrySwitchContext
1665 // runs on the ISR stack: reschedules timer, runs scheduler
1666 // (which may update m_stack_active to a new task), then updates
1667 // s_StkRiscvStackActive[hart] so step 5 below reads the correct new SP,
1668 // all caller-saved registers (a0-a7, t0-t6, ra) are trashed - expected
1669 "jal ra, TrySwitchContext \n"
1670
1671 // 5. reload SP from s_StkRiscvStackActive[hart]->SP
1672 // TrySwitchContext updated s_StkRiscvStackActive[hart] before returning,
1673 // we re-read it fresh to pick up any task switch the scheduler made
1674#if (STK_ARCH_CPU_COUNT > 1)
1675 "csrr t0, mhartid \n"
1676 "la t1, s_StkRiscvStackActive \n"
1677 "slli t0, t0, " REGBYTES_LOG2 " \n"
1678 "add t1, t1, t0 \n"
1679 LREG " t1, 0(t1) \n"
1680#else
1681 "la t1, s_StkRiscvStackActive \n"
1682 LREG " t1, 0(t1) \n"
1683#endif
1684 LREG " sp, 0(t1) \n" // sp = active task's saved SP
1685
1686 // 6. load context of the active task
1687 STK_ASM_LOAD_CONTEXT
1688
1689 // 7. exit ISR handler
1690 STK_ASM_EXIT_FROM_HANDLER " \n"
1691
1692 : /* outputs: none - naked, compiler emits nothing outside this asm */
1693 : /* inputs: none - all addresses loaded as linker symbols via "la" */
1694 : /* clobbers: none - the asm string owns all registers */
1695 );
1696}
1697#endif // !_STK_RISCV_USE_PENDSV
1698
1699void Context::OnStart()
1700{
1701 const uint8_t hart = HW_GetHartId();
1702
1703 // save SP of main stack to reuse it for scheduler exit
1704 HW_SaveMainSP();
1705
1706 // enable FPU (if available)
1707 HW_EnableFullFpuAccess();
1708
1709 // clear FPU usage status if FPU was used before kernel start
1710 HW_ClearFpuState();
1711
1712 // notify kernel
1713 m_handler->OnStart(m_stack_active);
1714
1715 // initialize ISR asm pointer cache
1716 s_StkRiscvStackIsr[hart] = &m_stack_isr; // set once here, the ISR stack never moves
1717 s_StkRiscvStackActive[hart] = m_stack_active;
1718#ifdef _STK_RISCV_USE_PENDSV
1719 s_StkRiscvStackIdle[hart] = m_stack_idle;
1720#endif
1721
1722 // start with initially 1 elapsed tick (after timer expires)
1723 StartTickTimer(1);
1724
1725 // change state before enabling interrupts
1726 m_started = true;
1727 m_starting = false;
1728
1729 // enable SV exception
1730#ifdef _STK_RISCV_USE_PENDSV
1731 set_csr(mie, MIP_MSIP);
1732#endif
1733}
1734
1735STK_RISCV_ISR void STK_SVC_HANDLER()
1736{
1737 const Word cause = HW_GetCurrentException();
1738
1739 /*if (cause & (1UL << (__riscv_xlen - 1)))
1740 {
1741 cause &= ~(1UL << (__riscv_xlen - 1));
1742
1743 if (cause == IRQ_M_TIMER)
1744 {
1745
1746 }
1747 }*/
1748
1749 if (cause == IRQ_M_EXT)
1750 {
1751 // not starting scheduler, then try to forward ecall to user
1752 if (!GetContext().m_starting)
1753 {
1754 // forward event to user
1755 if (GetContext().m_specific != nullptr)
1756 GetContext().m_specific->OnException(cause);
1757
1758 // switch to the next instruction of the caller space (PC) after the return
1759 write_csr(mepc, read_csr(mepc) + sizeof(Word));
1760 }
1761 else
1762 {
1763 // make sure interrupts do not interfere
1764 HW_DisableInterrupts();
1765
1766 // configure scheduling
1767 GetContext().OnStart();
1768
1769 // start first task
1770 OnTaskStart();
1771 }
1772 }
1773 else
1774 {
1775 if (GetContext().m_specific != nullptr)
1776 {
1777 // forward event to user
1778 GetContext().m_specific->OnException(cause);
1779 }
1780 else
1781 {
1782 // trap further execution
1783 // note: normally, if trapped here with cause 2 or 4 then check stack memory size of the
1784 // tasks, scheduler and ISR, they were likely overwritten if your code is 100% correct
1786 }
1787 }
1788}
1789
1790static void OnTaskRun(ITask *task)
1791{
1792 task->Run();
1793}
1794
1795static void OnTaskExit()
1796{
1797 Word cs;
1798 HW_CriticalSectionStart(cs);
1799
1800 GetContext().m_handler->OnTaskExit(GetContext().m_stack_active);
1801
1802 HW_CriticalSectionEnd(cs);
1803
1804 for (;;)
1805 {
1806 __DSB(); // data barrier
1807 __WFI(); // enter standby mode until time slot expires
1808 }
1809}
1810
1811static STK_RISCV_ISR_SECTION void OnSchedulerSleep()
1812{
1813 // if hit here, increase the size of STK_SLEEP_TRAP_STACK_SIZE
1815
1816#if STK_SEGGER_SYSVIEW
1817 SEGGER_SYSVIEW_OnIdle();
1818#endif
1819
1820 for (;;)
1821 {
1822 HW_EnterSleepMode();
1823 }
1824}
1825
1826static STK_RISCV_ISR_SECTION void OnSchedulerSleepOverride()
1827{
1828 // if hit here, increase the size of STK_SLEEP_TRAP_STACK_SIZE
1830
1831#if STK_SEGGER_SYSVIEW
1832 SEGGER_SYSVIEW_OnIdle();
1833#endif
1834
1835 for (;;)
1836 {
1837 GetContext().OnSleepOverride();
1838 }
1839}
1840
1841static void OnSchedulerExit()
1842{
1843 // switch to main stack
1844 HW_LoadMainSP();
1845
1846 // jump to the exit from the IKernel::Start()
1847 RestoreJmp(GetContext().m_exit_buf, 0);
1848}
1849
1850void PlatformRiscV::Initialize(IEventHandler *event_handler, IKernelService *service, uint32_t resolution_us, Stack *exit_trap)
1851{
1852 GetContext().Initialize(event_handler, service, exit_trap, resolution_us);
1853}
1854
1855void Context::Start()
1856{
1857 m_exiting = false;
1858
1859 // save jump location of the Exit trap
1860 SaveJmp(m_exit_buf);
1861 if (m_exiting)
1862 {
1863 // notify kernel about a full stop
1864 m_handler->OnStop();
1865 }
1866 else
1867 {
1868 // enable FPU (if available)
1869 HW_EnableFullFpuAccess();
1870
1871 // start
1872 m_starting = true;
1873 HW_StartScheduler();
1874 }
1875}
1876
1877#if STK_TICKLESS_IDLE
1878Timeout Context::Suspend()
1879{
1880 const uint32_t resolution = static_cast<uint32_t>(ConvertTimeUsToClockCycles(HW_CoreClockFrequency(), m_tick_resolution));
1881 if (resolution == 0U)
1882 {
1883 STK_ASSERT(false);
1884 return NO_WAIT;
1885 }
1886
1887 HW_DisableInterrupts();
1888
1889 // stop tick timer
1890 HW_StopMTimer();
1891
1892 // clear pending PendSV exception
1893 HW_ClearPendingSwitch();
1894
1895 // get already elapsed CPU cycles since SysTick ISR invocation up to SysTick timer stop (see above)
1896 // to account for them for a new period value
1897 const uint32_t elapsed = HW_GetMtime() - m_last_mtime;
1898
1899 // get already elapsed ticks since the OnTick and a call to Suspend(), we shall account for this
1900 // period and return only the remainder
1901 const Timeout elapsed_ticks = static_cast<Timeout>(elapsed / resolution);
1902 const Timeout sleep_ticks = Max(m_sleep_ticks - elapsed_ticks, static_cast<Timeout>(0));
1903
1904 // notify core
1905 m_handler->OnSuspend(true);
1906
1907 HW_EnableInterrupts();
1908
1909 return sleep_ticks;
1910}
1911#endif
1912
1913#if STK_TICKLESS_IDLE
1914void Context::Resume(Timeout elapsed_ticks)
1915{
1916 HW_DisableInterrupts();
1917
1918 // notify core
1919 m_handler->OnSuspend(false);
1920
1921 // start with initially elapsed ticks (OnTick will fire with elapsed_ticks + 1)
1922 StartTickTimer(elapsed_ticks + 1);
1923
1924 HW_EnableInterrupts();
1925}
1926#endif
1927
1929{
1930 GetContext().Start();
1931}
1932
1933void PlatformRiscV::InitStack(EStackType stack_type, Stack *stack, IStackMemory *stack_memory, ITask *user_task)
1934{
1935 // TaskFrame must map exactly onto the slot layout consumed by STK_ASM_SAVE_CONTEXT / STK_ASM_LOAD_CONTEXT - no padding allowed
1936 STK_STATIC_ASSERT_DESC(sizeof(TaskFrame) == (STK_RISCV_REGISTER_COUNT + STK_SERVICE_SLOTS) * sizeof(Word),
1937 "TaskFrame size must match REGSIZE: (REGISTER_COUNT + SERVICE_SLOTS) * REGBYTES");
1938
1939 STK_ASSERT(stack_memory->GetStackSize() > (STK_RISCV_REGISTER_COUNT + STK_SERVICE_SLOTS));
1940
1941 // initialize stack memory (fills all slots with STK_STACK_MEMORY_FILLER)
1942 const Word stack_top = PlatformContext::InitStackMemory(stack_memory);
1943
1944 // initialize Stack Pointer (SP): frame sits at the bottom of the register window
1945 stack->SP = stack_top - ((STK_RISCV_REGISTER_COUNT + STK_SERVICE_SLOTS) * sizeof(Word));
1946
1947 // place the task frame at SP directly at the base of the register window
1948 TaskFrame *const task_frame = hw::WordToPtr<TaskFrame>(stack->SP);
1949
1950 // initialize registers for the user task's first start
1951 switch (stack_type)
1952 {
1953 case STACK_USER_TASK: {
1954 task_frame->MEPC = hw::PtrToWord(&OnTaskRun);
1955 task_frame->X1_RA = hw::PtrToWord(&OnTaskExit);
1956 task_frame->X10_A0 = hw::PtrToWord(user_task);
1957 break; }
1958
1959 case STACK_SLEEP_TRAP: {
1960 task_frame->MEPC = hw::PtrToWord(GetContext().m_overrider != nullptr ? &OnSchedulerSleepOverride : &OnSchedulerSleep);
1961 task_frame->X1_RA = STK_STACK_MEMORY_FILLER; // should not attempt to exit
1962 break; }
1963
1964 case STACK_EXIT_TRAP: {
1965 task_frame->MEPC = hw::PtrToWord(&OnSchedulerExit);
1966 task_frame->X1_RA = STK_STACK_MEMORY_FILLER; // should not attempt to exit
1967 break; }
1968
1969 default: {
1971 break; }
1972 }
1973
1974 // mstatus: return to M-mode (MPP), interrupts enabled on mret (MPIE),
1975 // FPU/extension state initial (FS/XS) if FPU present
1976 task_frame->MSTATUS = MSTATUS_MPP | MSTATUS_MPIE | (STK_RISCV_FPU != 0U ? (MSTATUS_FS | MSTATUS_XS) : 0U);
1977
1978#if (STK_RISCV_FPU != 0)
1979 task_frame->X3_FSR = 0U; // FCSR = 0: round-to-nearest, no accrued exception flags
1980#endif
1981}
1982
1983void Context::OnStop()
1984{
1985 // stop timer
1986 HW_StopMTimer();
1987
1988 // clear pending SV exception
1989 HW_ClearPendingSwitch();
1990
1991 m_started = false;
1992 m_exiting = true;
1993
1994 // make sure all assignments are set and executed
1995 __DSB();
1996 __ISB();
1997}
1998
2000{
2001 GetContext().OnStop();
2002
2003 // load context of the Exit trap
2004 HW_DisableInterrupts();
2005 OnTaskStart();
2006}
2007
2008uint32_t PlatformRiscV::GetTickResolution() const
2009{
2010 return GetContext().m_tick_resolution;
2011}
2012
2014{
2015 return static_cast<Cycles>(HW_GetMtime());
2016}
2017
2019{
2020 return HW_MtimeClockFrequency();
2021}
2022
2024{
2025 GetContext().m_handler->OnTaskSwitch(HW_GetCallerSP());
2026}
2027
2028void PlatformRiscV::Sleep(Timeout ticks)
2029{
2030 GetContext().m_handler->OnTaskSleep(HW_GetCallerSP(), ticks);
2031}
2032
2033bool PlatformRiscV::SleepUntil(Ticks timestamp)
2034{
2035 return GetContext().m_handler->OnTaskSleepUntil(HW_GetCallerSP(), timestamp);
2036}
2037
2038EWaitResult PlatformRiscV::Wait(ISyncObject *sync_obj, IMutex *mutex, Timeout timeout)
2039{
2040 return GetContext().m_handler->OnTaskWait(HW_GetCallerSP(), sync_obj, mutex, timeout);
2041}
2042
2044{
2045 TId result;
2046
2047 if (HW_IsHandlerMode())
2048 {
2049 // to avoid the collision with TID_ISR_N mask, extract and fit into available space:
2050
2051 const Word exc = HW_GetCurrentException();
2052 const Word num = (exc & 0x7FFU);
2053 #if (__riscv_xlen > 32)
2054 const Word interrupt_bit = ((exc & (1ULL << (__riscv_xlen - 1))) ? 0x800U : 0);
2055 #else
2056 const Word interrupt_bit = ((exc & (1U << (__riscv_xlen - 1))) ? 0x800U : 0);
2057 #endif
2058
2059 const TId isr_tid = TID_ISR_N | num | interrupt_bit;
2060 STK_ASSERT(IsIsrTid(isr_tid));
2061 result = isr_tid;
2062 }
2063 else
2064 {
2065 result = GetContext().m_handler->OnGetTid(HW_GetCallerSP());
2066 }
2067
2068 return result;
2069}
2070
2072{
2073#if STK_TICKLESS_IDLE
2074 return GetContext().Suspend();
2075#else
2076 return 0;
2077#endif
2078}
2079
2080void PlatformRiscV::Resume(Timeout elapsed_ticks)
2081{
2082#if STK_TICKLESS_IDLE
2083 GetContext().Resume(elapsed_ticks);
2084#else
2085 STK_UNUSED(elapsed_ticks);
2086#endif
2087}
2088
2090{
2091 bool is_handled = false;
2092
2093 if (GetContext().m_overrider != nullptr)
2094 {
2095 is_handled = GetContext().m_overrider->OnHardFault();
2096 }
2097
2098 if (!is_handled)
2099 {
2101 }
2102}
2103
2104void PlatformRiscV::SetEventOverrider(IEventOverrider *overrider)
2105{
2106 STK_ASSERT(!GetContext().m_started);
2107 GetContext().m_overrider = overrider;
2108}
2109
2111{
2112 return HW_GetCallerSP();
2113}
2114
2115void PlatformRiscV::SetSpecificEventHandler(ISpecificEventHandler *handler)
2116{
2117 STK_ASSERT(!GetContext().m_started);
2118 GetContext().m_specific = handler;
2119}
2120
2122{
2123 return GetContext().m_service;
2124}
2125
2127{
2128 STK_UNUSED(is_npriv);
2129 GetContext().EnterCriticalSection();
2130 return DEFAULT_SESSION;
2131}
2132
2134{
2135 STK_UNUSED(is_npriv);
2136 GetContext().ExitCriticalSection();
2137}
2138
2140{
2141 HW_SpinLockLock(m_lock);
2142}
2143
2145{
2146 HW_SpinLockUnlock(m_lock);
2147}
2148
2150{
2151 return HW_SpinLockTryLock(m_lock);
2152}
2153
2155{
2156 return HW_IsHandlerMode();
2157}
2158
2160{
2161 // always Privileged on RISC-V
2162 return true;
2163}
2164
2166{
2167 return HiResClockImpl::GetInstance()->GetCycles();
2168}
2169
2171{
2172 const uint32_t freq = HiResClockImpl::GetInstance()->GetFrequency();
2173 STK_ASSERT(freq != 0U);
2174 return freq;
2175}
2176
2177#if STK_TLS && !STK_INLINE_TLS
2178Word stk::hw::GetTls()
2179{
2180 return GetContext().GetTls();
2181}
2182
2183void stk::hw::SetTls(Word tp)
2184{
2185 GetContext().SetTls(tp);
2186}
2187#endif // STK_TLS && !STK_INLINE_TLS
2188
2189#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:629
#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:265
#define __stk_forceinline
Forces compiler to always inline the decorated function, regardless of optimisation level.
Definition stk_defs.h:196
#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:567
#define STK_CS_NESTINGS_MAX
Maximum allowable recursion depth for critical section entry (default: 16).
Definition stk_defs.h:504
#define STK_ASSERT(e)
Runtime assertion. Halts execution if the expression e evaluates to false.
Definition stk_defs.h:430
#define STK_STACK_SIZE_MIN
Minimum stack size in elements of Word, shared by all stack allocation lower-bound checks.
Definition stk_defs.h:554
#define __stk_attr_noinline
Prevents compiler from inlining the decorated function (function prefix).
Definition stk_defs.h:276
#define STK_ARCH_CPU_COUNT
Number of physical CPU cores available to the scheduler (default: 1).
Definition stk_defs.h:514
#define __stk_attr_naked
Suppresses compiler-generated function prologue and epilogue (function prefix).
Definition stk_defs.h:232
#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:450
#define STK_STACK_MEMORY_FILLER
Sentinel value written to the entire stack region at initialization (stack watermark pattern).
Definition stk_defs.h:477
#define __stk_attr_noreturn
Declares that function never returns to its caller (function prefix).
Definition stk_defs.h:243
#define STK_STATIC_ASSERT(X)
Compile-time assertion. Produces a compilation error if X is false.
Definition stk_defs.h:467
#define STK_VIRT_DTOR
Makes destructors virtual and compliant to strict rules if STK_STRICT_COMPLIANCY=0.
Definition stk_defs.h:180
Contains helper implementations which simplify user-side code.
Namespace of STK package.
uintptr_t Word
Native processor word type.
Definition stk_common.h:140
static constexpr TId TID_ISR_N
Bitmask sentinel for ISR-context task identifiers.
Definition stk_common.h:197
@ ACCESS_PRIVILEGED
Privileged access mode (access to hardware is fully unrestricted).
Definition stk_common.h:38
static constexpr Timeout NO_WAIT
Timeout value: return immediately if the synchronization object is not yet signaled (non-blocking pol...
Definition stk_common.h:214
EWaitResult
Wait result (see IKernelService::Wait).
Definition stk_common.h:118
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:155
int32_t Timeout
Timeout time (ticks).
Definition stk_common.h:150
static bool IsIsrTid(TId id)
Test whether a task identifier represents an ISR context.
Definition stk_common.h:237
static __stk_forceinline void STK_KERNEL_PANIC(stk::EKernelPanicId id)
Called when the kernel detects an unrecoverable internal fault.
Definition stk_arch.h:75
static constexpr T Max(T a, T b) noexcept
Compile-time maximum of two values.
Definition stk_defs.h:673
EStackType
Stack type.
Definition stk_common.h:78
@ STACK_SLEEP_TRAP
Stack of the Sleep trap.
Definition stk_common.h:80
@ STACK_USER_TASK
Stack of the user task.
Definition stk_common.h:79
@ STACK_EXIT_TRAP
Stack of the Exit trap.
Definition stk_common.h:81
uint64_t Cycles
Cycles value.
Definition stk_common.h:165
Word TId
Task (thread) id.
Definition stk_common.h:145
EKernelPanicId
Identifies the source of a kernel panic.
Definition stk_common.h:58
@ KERNEL_PANIC_BAD_STACK_TYPE
Stack type is unknown.
Definition stk_common.h:69
@ KERNEL_PANIC_HRT_HARD_FAULT
Kernel running in KERNEL_HRT mode reported deadline failure of the task.
Definition stk_common.h:63
@ KERNEL_PANIC_NONE
Panic is absent (no fault).
Definition stk_common.h:59
@ KERNEL_PANIC_CPU_EXCEPTION
CPU reported an exception and halted execution.
Definition stk_common.h:64
@ KERNEL_PANIC_SPINLOCK_DEADLOCK
Spin-lock timeout expired: lock owner never released.
Definition stk_common.h:60
bool IsPrivilegedContext()
Check if caller context is Privileged.
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 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 constexpr Session DEFAULT_SESSION
Default session value passed to Enter()/Exit() when the caller does not need to force a specific hand...
Definition stk_arch.h:369
static Session Enter(const Session ses=DEFAULT_SESSION)
Enter a critical section.
uint8_t Session
Opaque session token returned by Enter() and consumed by Exit().
Definition stk_arch.h:362
static void Exit(const Session ses=DEFAULT_SESSION)
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:316
Word SP
Offset 0: Stack Pointer (SP) register.
Definition stk_common.h:317
Interface for a stack memory region.
Definition stk_common.h:334
virtual size_t GetStackSize() const =0
Get number of elements of the stack memory array.
Synchronization object interface.
Definition stk_common.h:477
Interface for mutex synchronization primitive.
Definition stk_common.h:610
Interface for a user task.
Definition stk_common.h:667
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.