Skip to content

Commit c26879d

Browse files
committed
fix(freertos): workaround a hardware bug related to HWLP coprocessor
This commit manually sets the HWLP context to dirty when a Task that needs it is scheduled it.
1 parent 46847b7 commit c26879d

File tree

4 files changed

+103
-81
lines changed

4 files changed

+103
-81
lines changed

components/freertos/FreeRTOS-Kernel/portable/riscv/portasm.S

Lines changed: 87 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -186,28 +186,44 @@ rtos_save_\name\()_coproc_norestore:
186186

187187
/**
188188
* @brief Restore the HWLP registers contained in the dedicated save area if the given task ever used it.
189-
* This routine sets the HWLP context to clean in any case.
189+
* This routine sets the HWLP context to dirty if the task ever used it and any of the loop counter
190+
* is not zero. Else, it sets it to clean.
190191
*
191192
* @param a0 StaticTask address for the newly scheduled task
192193
*/
193194
hwlp_restore_if_used:
194195
addi sp, sp, -16
195196
sw ra, (sp)
196-
/* Check if the HWLP was in use beforehand */
197+
/* Re-enable the HWLP coprocessor */
198+
csrwi CSR_HWLP_STATE_REG, HWLP_CLEAN_STATE
199+
/* Check if the HWLP was ever used by this task, if yes:
200+
* - Set HWLP state to DIRTY if any of the HWLP counter is != 0.
201+
* Please note that the `hwlp_restore_regs` macro will set the DIRTY bit!
202+
* - Keep the state as CLEAN if both counters are 0.
203+
*/
197204
li a1, 0
198205
li a2, HWLP_COPROC_IDX
199206
call pxPortGetCoprocArea
200207
/* Get the enable flags from the coprocessor save area */
201208
lw a1, RV_COPROC_ENABLE(a0)
202209
/* To avoid having branches below, set the coprocessor enable flag now */
203210
andi a2, a1, 1 << HWLP_COPROC_IDX
204-
beqz a2, _hwlp_restore_never_used
211+
beqz a2, _hwlp_restore_end
205212
/* Enable bit was set, restore the coprocessor context */
206213
lw a0, RV_COPROC_SA+HWLP_COPROC_IDX*4(a0) /* a0 = RvCoprocSaveArea->sa_coprocs[HWLP_COPROC_IDX] */
214+
/* This will set the dirty flag for sure */
207215
hwlp_restore_regs a0
208-
_hwlp_restore_never_used:
209-
/* Clear the context */
216+
#if SOC_CPU_HAS_HWLOOP_STATE_BUG && ESP32P4_REV_MIN_FULL <= 1
217+
/* The hardware doesn't update the HWLP state properly after executing the last instruction,
218+
* as such, we must manually put the state of the HWLP to dirty now if any counter is not 0 */
219+
csrr a0, CSR_LOOP0_COUNT
220+
bnez a0, _hwlp_restore_end
221+
csrr a0, CSR_LOOP1_COUNT
222+
bnez a0, _hwlp_restore_end
223+
/* The counters are 0, cleaning the state */
224+
#endif /* SOC_CPU_HAS_HWLOOP_STATE_BUG && ESP32P4_REV_MIN_FULL <= 1 */
210225
csrwi CSR_HWLP_STATE_REG, HWLP_CLEAN_STATE
226+
_hwlp_restore_end:
211227
lw ra, (sp)
212228
addi sp, sp, 16
213229
ret
@@ -458,6 +474,11 @@ rtos_current_tcb:
458474
.global rtos_int_enter
459475
.type rtos_int_enter, @function
460476
rtos_int_enter:
477+
#if SOC_CPU_COPROC_NUM > 0
478+
/* Use s2 to store the state of the coprocessors */
479+
li s2, 0
480+
#endif /* SOC_CPU_COPROC_NUM > 0 */
481+
461482
#if ( configNUM_CORES > 1 )
462483
csrr s0, mhartid /* s0 = coreID */
463484
slli s0, s0, 2 /* s0 = coreID * 4 */
@@ -483,7 +504,6 @@ rtos_int_enter:
483504
li a0, 0 /* return 0 in case we are going to branch */
484505
bnez a1, rtos_int_enter_end /* if (port_uxInterruptNesting[coreID] > 0) jump to rtos_int_enter_end */
485506

486-
li s2, 0
487507
#if SOC_CPU_COPROC_NUM > 0
488508
/* Disable the coprocessors to forbid the ISR from using it */
489509
#if SOC_CPU_HAS_PIE
@@ -525,6 +545,7 @@ rtos_int_enter:
525545
addi a1, a1, -HWLP_DIRTY_STATE
526546
bnez a1, 1f
527547
/* State is dirty! The hardware loop feature was used, save the registers */
548+
ori s2, s2, 1 << HWLP_COPROC_IDX /* Mark the HWLP coprocessor as enabled (dirty) */
528549
li a1, 1 /* Allocate the save area if not already allocated */
529550
li a2, HWLP_COPROC_IDX
530551
mv s1, ra
@@ -537,8 +558,6 @@ rtos_int_enter:
537558
/* Get the area where we need to save the HWLP registers */
538559
lw a0, RV_COPROC_SA+HWLP_COPROC_IDX*4(a0) /* a0 = RvCoprocSaveArea->sa_coprocs[\coproc_idx] */
539560
hwlp_save_regs a0
540-
/* Disable the HWLP feature so that ISR cannot use them */
541-
csrwi CSR_HWLP_STATE_REG, HWLP_CLEAN_STATE
542561
1:
543562
#endif
544563

@@ -559,9 +578,16 @@ rtos_int_enter:
559578
ESP_HW_STACK_GUARD_MONITOR_START_CUR_CORE a0 a1
560579
#endif /* CONFIG_ESP_SYSTEM_HW_STACK_GUARD */
561580

581+
rtos_int_enter_end:
582+
/* Disable the HWLP coprocessor for ISRs */
583+
#if SOC_CPU_HAS_HWLOOP
584+
csrwi CSR_HWLP_STATE_REG, HWLP_OFF_STATE
585+
#endif
586+
587+
#if SOC_CPU_COPROC_NUM > 0
562588
/* Return the coprocessor context from s2 */
563589
mv a0, s2
564-
rtos_int_enter_end:
590+
#endif /* SOC_CPU_COPROC_NUM > 0 */
565591
ret
566592

567593
/**
@@ -577,19 +603,19 @@ rtos_int_enter_end:
577603
.type rtos_int_exit, @function
578604
rtos_int_exit:
579605
/* To speed up this routine and because this current routine is only meant to be called from the interrupt
580-
* handler, let's use callee-saved registers instead of stack space. Registers `s5-s11` are not used by
581-
* the caller */
606+
* handler, let's use callee-saved registers instead of stack space */
607+
mv s10, ra
582608
mv s11, a0
583609
#if SOC_CPU_COPROC_NUM > 0
584610
/* Save a1 as it contains the bitmap with the enabled coprocessors */
585611
mv s8, a1
586612
#endif
587613

588614
#if ( configNUM_CORES > 1 )
589-
csrr a1, mhartid /* a1 = coreID */
590-
slli a1, a1, 2 /* a1 = a1 * 4 */
615+
csrr s7, mhartid /* s7 = coreID */
616+
slli s7, s7, 2 /* s7 = s7 * 4 */
591617
la a0, port_xSchedulerRunning /* a0 = &port_xSchedulerRunning */
592-
add a0, a0, a1 /* a0 = &port_xSchedulerRunning[coreID] */
618+
add a0, a0, s7 /* a0 = &port_xSchedulerRunning[coreID] */
593619
lw a0, (a0) /* a0 = port_xSchedulerRunning[coreID] */
594620
#else
595621
lw a0, port_xSchedulerRunning /* a0 = port_xSchedulerRunning */
@@ -599,7 +625,7 @@ rtos_int_exit:
599625
/* Update nesting interrupts counter */
600626
la a2, port_uxInterruptNesting /* a2 = &port_uxInterruptNesting */
601627
#if ( configNUM_CORES > 1 )
602-
add a2, a2, a1 /* a2 = &port_uxInterruptNesting[coreID] // a1 already contains coreID * 4 */
628+
add a2, a2, s7 /* a2 = &port_uxInterruptNesting[coreID] // s7 already contains coreID * 4 */
603629
#endif /* ( configNUM_CORES > 1 ) */
604630
lw a0, 0(a2) /* a0 = port_uxInterruptNesting[coreID] */
605631

@@ -611,84 +637,66 @@ rtos_int_exit:
611637
bnez a0, rtos_int_exit_end
612638

613639
isr_skip_decrement:
614-
/* If the CPU reached this label, a2 (uxInterruptNesting) is 0 for sure */
640+
641+
#if ( SOC_CPU_COPROC_NUM > 0 )
642+
/* Keep the current TCB in a0 */
643+
call rtos_current_tcb
644+
#endif /* ( SOC_CPU_COPROC_NUM > 0 ) */
615645

616646
/* Schedule the next task if a yield is pending */
617-
la s7, xPortSwitchFlag /* s7 = &xPortSwitchFlag */
647+
la s6, xPortSwitchFlag /* s6 = &xPortSwitchFlag */
618648
#if ( configNUM_CORES > 1 )
619-
add s7, s7, a1 /* s7 = &xPortSwitchFlag[coreID] // a1 already contains coreID * 4 */
649+
add s6, s6, s7 /* s6 = &xPortSwitchFlag[coreID] // s7 already contains coreID * 4 */
620650
#endif /* ( configNUM_CORES > 1 ) */
621-
lw a0, 0(s7) /* a0 = xPortSwitchFlag[coreID] */
622-
beqz a0, no_switch_restore_coproc /* if (xPortSwitchFlag[coreID] == 0) jump to no_switch_restore_coproc */
651+
lw a1, 0(s6) /* a1 = xPortSwitchFlag[coreID] */
652+
bnez a1, context_switch_requested /* if (xPortSwitchFlag[coreID] != 0) jump to context_switch_requested */
623653

624-
/* Preserve return address and schedule next task. To speed up the process, and because this current routine
625-
* is only meant to be called from the interrupt handle, let's save some speed and space by using callee-saved
626-
* registers instead of stack space. Registers `s3-s11` are not used by the caller */
627-
mv s10, ra
654+
no_context_switch:
655+
/* No need to do anything on the FPU side, its state is already saved in `s11` */
656+
657+
#if SOC_CPU_HAS_HWLOOP
658+
csrwi CSR_HWLP_STATE_REG, HWLP_CLEAN_STATE
659+
/* If the HWLP coprocessor has a hardware bug with its state, manually set the state to DIRTY
660+
* if it was already dirty before the interrupt, else, keep it to CLEAN */
661+
#if SOC_CPU_HAS_HWLOOP_STATE_BUG && ESP32P4_REV_MIN_FULL <= 1
662+
andi a1, s8, 1 << HWLP_COPROC_IDX
663+
beqz a1, 1f
664+
/* To re-enable the HWLP coprocessor, set the status to DIRTY */
665+
csrwi CSR_HWLP_STATE_REG, HWLP_DIRTY_STATE
666+
1:
667+
#endif /* SOC_CPU_HAS_HWLOOP_STATE_BUG && ESP32P4_REV_MIN_FULL <= 1 */
668+
#endif /* SOC_CPU_HAS_HWLOOP */
669+
670+
#if SOC_CPU_HAS_PIE
671+
/* Re-enable the PIE coprocessor if it was used */
672+
andi a1, s8, 1 << PIE_COPROC_IDX
673+
beqz a1, 1f
674+
pie_enable a1
675+
1:
676+
#endif /* SOC_CPU_HAS_PIE */
677+
j restore_stack_pointer
678+
679+
context_switch_requested:
628680
#if ( SOC_CPU_COPROC_NUM > 0 )
629-
/* In the cases where the newly scheduled task is different from the previously running one,
630-
* we have to disable the coprocessors to let them trigger an exception on first use.
631-
* Else, if the same task is scheduled, restore the former coprocessors state (before the interrupt) */
632-
call rtos_current_tcb
633-
/* Keep former TCB in s9 */
681+
/* Preserve former TCB in s9 */
634682
mv s9, a0
635-
#endif
683+
#endif /* ( SOC_CPU_COPROC_NUM > 0 ) */
636684
call vTaskSwitchContext
637-
#if ( SOC_CPU_COPROC_NUM == 0 )
638-
mv ra, s10 /* Restore original return address */
639-
#endif
640-
/* Clears the switch pending flag (stored in s7) */
641-
sw zero, 0(s7) /* xPortSwitchFlag[coreID] = 0; */
685+
/* Clears the switch pending flag (stored in s6) */
686+
sw zero, 0(s6) /* xPortSwitchFlag[coreID] = 0; */
642687

643688
#if ( SOC_CPU_COPROC_NUM > 0 )
644-
/* If the Task to schedule is NOT the same as the former one (s9), keep the coprocessors disabled */
689+
/* If the Task to schedule is NOT the same as the former one (s9), keep the coprocessors disabled. */
690+
/* Check if the new TCB is the same as the previous one */
645691
call rtos_current_tcb
646-
mv ra, s10 /* Restore original return address */
647-
beq a0, s9, no_switch_restore_coproc
648-
649-
#if SOC_CPU_HAS_HWLOOP
650-
/* We have to restore the context of the HWLP if the newly scheduled task used it before. In all cases, this
651-
* routine will also clean the state and set it to clean */
652-
mv s7, ra
653-
/* a0 contains the current TCB address */
654-
call hwlp_restore_if_used
655-
mv ra, s7
656-
#endif /* SOC_CPU_HAS_HWLOOP */
657-
658-
#if SOC_CPU_HAS_FPU
659-
/* Disable the FPU in the `mstatus` value to return */
660-
li a1, ~CSR_MSTATUS_FPU_DISABLE
661-
and s11, s11, a1
662-
#endif /* SOC_CPU_HAS_FPU */
663-
j no_switch_restored
664-
692+
beq a0, s9, no_context_switch
665693
#endif /* ( SOC_CPU_COPROC_NUM > 0 ) */
666694

667-
no_switch_restore_coproc:
668-
/* We reach here either because there is no switch scheduled or because the TCB that is going to be scheduled
669-
* is the same as the one that has been interrupted. In both cases, we need to restore the coprocessors status */
670695
#if SOC_CPU_HAS_HWLOOP
671-
/* Check if the ISR altered the state of the HWLP */
672-
csrr a1, CSR_HWLP_STATE_REG
673-
addi a1, a1, -HWLP_DIRTY_STATE
674-
bnez a1, 1f
675-
/* ISR used the HWLP, restore the HWLP context! */
676-
mv s7, ra
677-
/* a0 contains the current TCB address */
678696
call hwlp_restore_if_used
679-
mv ra, s7
680-
1:
681-
/* Else, the ISR hasn't touched HWLP registers, we don't need to restore the HWLP registers */
682697
#endif /* SOC_CPU_HAS_HWLOOP */
683698

684-
#if SOC_CPU_HAS_PIE
685-
andi a0, s8, 1 << PIE_COPROC_IDX
686-
beqz a0, 2f
687-
pie_enable a0
688-
2:
689-
#endif /* SOC_CPU_HAS_PIE */
690-
691-
no_switch_restored:
699+
restore_stack_pointer:
692700

693701
#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
694702
/* esp_hw_stack_guard_monitor_stop(); pass the scratch registers */
@@ -698,10 +706,8 @@ no_switch_restored:
698706

699707
#if ( configNUM_CORES > 1 )
700708
/* Recover the stack of next task and prepare to exit */
701-
csrr a1, mhartid
702-
slli a1, a1, 2
703709
la a0, pxCurrentTCBs /* a0 = &pxCurrentTCBs */
704-
add a0, a0, a1 /* a0 = &pxCurrentTCBs[coreID] */
710+
add a0, a0, s7 /* a0 = &pxCurrentTCBs[coreID] */
705711
lw a0, 0(a0) /* a0 = pxCurrentTCBs[coreID] */
706712
lw sp, 0(a0) /* sp = previous sp */
707713
#else
@@ -724,4 +730,5 @@ no_switch_restored:
724730

725731
rtos_int_exit_end:
726732
mv a0, s11 /* a0 = new mstatus */
733+
mv ra, s10
727734
ret

components/riscv/vectors.S

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -236,6 +236,16 @@ _panic_handler:
236236
la ra, _return_from_exception
237237
/* EXT_ILL CSR should contain the reason for the Illegal Instruction */
238238
csrrw a0, EXT_ILL_CSR, zero
239+
#if SOC_CPU_HAS_HWLOOP
240+
/* Check if the HWLP bit is set. */
241+
andi a1, a0, EXT_ILL_RSN_HWLP
242+
beqz a1, hwlp_not_used
243+
/* HWLP used in an ISR, abort */
244+
mv a0, sp
245+
j vPortCoprocUsedInISR
246+
hwlp_not_used:
247+
#endif /* SOC_CPU_HAS_HWLOOP */
248+
239249
/* Hardware loop cannot be treated lazily, so we should never end here if a HWLP instruction is used */
240250
#if SOC_CPU_HAS_PIE
241251
/* Check if the PIE bit is set. */

components/soc/esp32p4/include/soc/Kconfig.soc_caps.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,10 @@ config SOC_CPU_HAS_HWLOOP
523523
bool
524524
default y
525525

526+
config SOC_CPU_HAS_HWLOOP_STATE_BUG
527+
bool
528+
default y
529+
526530
config SOC_CPU_HAS_PIE
527531
bool
528532
default y

components/soc/esp32p4/include/soc/soc_caps.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@
179179
#define SOC_CPU_HAS_FPU 1
180180
#define SOC_CPU_HAS_FPU_EXT_ILL_BUG 1 // EXT_ILL CSR doesn't support FLW/FSW
181181
#define SOC_CPU_HAS_HWLOOP 1
182+
#define SOC_CPU_HAS_HWLOOP_STATE_BUG 1 // HWLOOP state doesn't go to DIRTY after executing the last instruction of a loop
182183
/* PIE coprocessor assembly is only supported with GCC compiler */
183184
#define SOC_CPU_HAS_PIE 1
184185

0 commit comments

Comments
 (0)