Skip to content

Commit d3fd64d

Browse files
committed
Merge branch 'bugfix/hwlp_coproc_saving_master' into 'master'
fix(freertos): workaround a hardware bug related to HWLP coprocessor See merge request espressif/esp-idf!37246
2 parents 847ac1a + 0bc169e commit d3fd64d

File tree

5 files changed

+123
-93
lines changed

5 files changed

+123
-93
lines changed

components/freertos/FreeRTOS-Kernel/portable/riscv/port.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*
77
* SPDX-License-Identifier: MIT
88
*
9-
* SPDX-FileContributor: 2023-2024 Espressif Systems (Shanghai) CO LTD
9+
* SPDX-FileContributor: 2023-2025 Espressif Systems (Shanghai) CO LTD
1010
*
1111
* Permission is hereby granted, free of charge, to any person obtaining a copy of
1212
* this software and associated documentation files (the "Software"), to deal in
@@ -297,15 +297,19 @@ static void vPortCleanUpCoprocArea(void *pvTCB)
297297
const UBaseType_t bottomstack = (UBaseType_t) task->pxDummy8;
298298
RvCoprocSaveArea* sa = pxRetrieveCoprocSaveAreaFromStackPointer(bottomstack);
299299

300-
/* If the Task used any coprocessor, check if it is the actual owner of any.
301-
* If yes, reset the owner. */
302-
if (sa->sa_enable != 0) {
300+
/* If the Task ever saved the original stack pointer, restore it before returning */
301+
if (sa->sa_allocator != 0) {
303302
/* Restore the original lowest address of the stack in the TCB */
304303
task->pxDummy6 = sa->sa_tcbstack;
305304

306305
/* Get the core the task is pinned on */
307306
#if ( configNUM_CORES > 1 )
308307
const BaseType_t coreID = task->xDummyCoreID;
308+
/* If the task is not pinned on any core, it didn't use any coprocessor than need to be freed (FPU or PIE).
309+
* If it used the HWLP coprocessor, it has nothing to clear since there is no "owner" for it. */
310+
if (coreID == tskNO_AFFINITY) {
311+
return;
312+
}
309313
#else /* configNUM_CORES > 1 */
310314
const BaseType_t coreID = 0;
311315
#endif /* configNUM_CORES > 1 */

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

Lines changed: 95 additions & 87 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,30 +186,47 @@ 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 */
201-
lw a1, RV_COPROC_ENABLE(a0)
202-
/* To avoid having branches below, set the coprocessor enable flag now */
203-
andi a2, a1, 1 << HWLP_COPROC_IDX
204-
beqz a2, _hwlp_restore_never_used
208+
lw a2, RV_COPROC_ENABLE(a0)
209+
andi a1, a2, 1 << HWLP_COPROC_IDX
210+
beqz a1, _hwlp_restore_end
205211
/* Enable bit was set, restore the coprocessor context */
206-
lw a0, RV_COPROC_SA+HWLP_COPROC_IDX*4(a0) /* a0 = RvCoprocSaveArea->sa_coprocs[HWLP_COPROC_IDX] */
207-
hwlp_restore_regs a0
208-
_hwlp_restore_never_used:
209-
/* Clear the context */
212+
lw a3, RV_COPROC_SA+HWLP_COPROC_IDX*4(a0) /* a0 = RvCoprocSaveArea->sa_coprocs[HWLP_COPROC_IDX] */
213+
/* This will set the dirty flag for sure, a2 is preserved */
214+
hwlp_restore_regs a3
215+
#if SOC_CPU_HAS_HWLOOP_STATE_BUG && ESP32P4_REV_MIN_FULL <= 1
216+
/* The hardware doesn't update the HWLP state properly after executing the last instruction,
217+
* as such, we must manually put the state of the HWLP to dirty now if any counter is not 0 */
218+
csrr a3, CSR_LOOP0_COUNT
219+
bnez a3, _hwlp_restore_end
220+
csrr a3, CSR_LOOP1_COUNT
221+
bnez a3, _hwlp_restore_end
222+
/* The counters are 0, mark the HWLP coprocessor as disabled in the enable flag and clean the state */
223+
xori a2, a2, 1 << HWLP_COPROC_IDX
224+
sw a2, RV_COPROC_ENABLE(a0)
225+
#endif /* SOC_CPU_HAS_HWLOOP_STATE_BUG && ESP32P4_REV_MIN_FULL <= 1 */
210226
csrwi CSR_HWLP_STATE_REG, HWLP_CLEAN_STATE
211-
lw ra, (sp)
212-
addi sp, sp, 16
227+
_hwlp_restore_end:
228+
lw ra, (sp)
229+
addi sp, sp, 16
213230
ret
214231

215232
#endif /* SOC_CPU_HAS_HWLOOP */
@@ -458,6 +475,11 @@ rtos_current_tcb:
458475
.global rtos_int_enter
459476
.type rtos_int_enter, @function
460477
rtos_int_enter:
478+
#if SOC_CPU_COPROC_NUM > 0
479+
/* Use s2 to store the state of the coprocessors */
480+
li s2, 0
481+
#endif /* SOC_CPU_COPROC_NUM > 0 */
482+
461483
#if ( configNUM_CORES > 1 )
462484
csrr s0, mhartid /* s0 = coreID */
463485
slli s0, s0, 2 /* s0 = coreID * 4 */
@@ -483,7 +505,6 @@ rtos_int_enter:
483505
li a0, 0 /* return 0 in case we are going to branch */
484506
bnez a1, rtos_int_enter_end /* if (port_uxInterruptNesting[coreID] > 0) jump to rtos_int_enter_end */
485507

486-
li s2, 0
487508
#if SOC_CPU_COPROC_NUM > 0
488509
/* Disable the coprocessors to forbid the ISR from using it */
489510
#if SOC_CPU_HAS_PIE
@@ -525,6 +546,7 @@ rtos_int_enter:
525546
addi a1, a1, -HWLP_DIRTY_STATE
526547
bnez a1, 1f
527548
/* State is dirty! The hardware loop feature was used, save the registers */
549+
ori s2, s2, 1 << HWLP_COPROC_IDX /* Mark the HWLP coprocessor as enabled (dirty) */
528550
li a1, 1 /* Allocate the save area if not already allocated */
529551
li a2, HWLP_COPROC_IDX
530552
mv s1, ra
@@ -537,8 +559,6 @@ rtos_int_enter:
537559
/* Get the area where we need to save the HWLP registers */
538560
lw a0, RV_COPROC_SA+HWLP_COPROC_IDX*4(a0) /* a0 = RvCoprocSaveArea->sa_coprocs[\coproc_idx] */
539561
hwlp_save_regs a0
540-
/* Disable the HWLP feature so that ISR cannot use them */
541-
csrwi CSR_HWLP_STATE_REG, HWLP_CLEAN_STATE
542562
1:
543563
#endif
544564

@@ -559,9 +579,16 @@ rtos_int_enter:
559579
ESP_HW_STACK_GUARD_MONITOR_START_CUR_CORE a0 a1
560580
#endif /* CONFIG_ESP_SYSTEM_HW_STACK_GUARD */
561581

582+
rtos_int_enter_end:
583+
/* Disable the HWLP coprocessor for ISRs */
584+
#if SOC_CPU_HAS_HWLOOP
585+
csrwi CSR_HWLP_STATE_REG, HWLP_OFF_STATE
586+
#endif
587+
588+
#if SOC_CPU_COPROC_NUM > 0
562589
/* Return the coprocessor context from s2 */
563590
mv a0, s2
564-
rtos_int_enter_end:
591+
#endif /* SOC_CPU_COPROC_NUM > 0 */
565592
ret
566593

567594
/**
@@ -577,19 +604,19 @@ rtos_int_enter_end:
577604
.type rtos_int_exit, @function
578605
rtos_int_exit:
579606
/* 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 */
607+
* handler, let's use callee-saved registers instead of stack space */
608+
mv s10, ra
582609
mv s11, a0
583610
#if SOC_CPU_COPROC_NUM > 0
584611
/* Save a1 as it contains the bitmap with the enabled coprocessors */
585612
mv s8, a1
586613
#endif
587614

588615
#if ( configNUM_CORES > 1 )
589-
csrr a1, mhartid /* a1 = coreID */
590-
slli a1, a1, 2 /* a1 = a1 * 4 */
616+
csrr s7, mhartid /* s7 = coreID */
617+
slli s7, s7, 2 /* s7 = s7 * 4 */
591618
la a0, port_xSchedulerRunning /* a0 = &port_xSchedulerRunning */
592-
add a0, a0, a1 /* a0 = &port_xSchedulerRunning[coreID] */
619+
add a0, a0, s7 /* a0 = &port_xSchedulerRunning[coreID] */
593620
lw a0, (a0) /* a0 = port_xSchedulerRunning[coreID] */
594621
#else
595622
lw a0, port_xSchedulerRunning /* a0 = port_xSchedulerRunning */
@@ -599,7 +626,7 @@ rtos_int_exit:
599626
/* Update nesting interrupts counter */
600627
la a2, port_uxInterruptNesting /* a2 = &port_uxInterruptNesting */
601628
#if ( configNUM_CORES > 1 )
602-
add a2, a2, a1 /* a2 = &port_uxInterruptNesting[coreID] // a1 already contains coreID * 4 */
629+
add a2, a2, s7 /* a2 = &port_uxInterruptNesting[coreID] // s7 already contains coreID * 4 */
603630
#endif /* ( configNUM_CORES > 1 ) */
604631
lw a0, 0(a2) /* a0 = port_uxInterruptNesting[coreID] */
605632

@@ -611,84 +638,66 @@ rtos_int_exit:
611638
bnez a0, rtos_int_exit_end
612639

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

616647
/* Schedule the next task if a yield is pending */
617-
la s7, xPortSwitchFlag /* s7 = &xPortSwitchFlag */
648+
la s6, xPortSwitchFlag /* s6 = &xPortSwitchFlag */
618649
#if ( configNUM_CORES > 1 )
619-
add s7, s7, a1 /* s7 = &xPortSwitchFlag[coreID] // a1 already contains coreID * 4 */
650+
add s6, s6, s7 /* s6 = &xPortSwitchFlag[coreID] // s7 already contains coreID * 4 */
620651
#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 */
652+
lw a1, 0(s6) /* a1 = xPortSwitchFlag[coreID] */
653+
bnez a1, context_switch_requested /* if (xPortSwitchFlag[coreID] != 0) jump to context_switch_requested */
623654

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
655+
no_context_switch:
656+
/* No need to do anything on the FPU side, its state is already saved in `s11` */
657+
658+
#if SOC_CPU_HAS_HWLOOP
659+
csrwi CSR_HWLP_STATE_REG, HWLP_CLEAN_STATE
660+
/* If the HWLP coprocessor has a hardware bug with its state, manually set the state to DIRTY
661+
* if it was already dirty before the interrupt, else, keep it to CLEAN */
662+
#if SOC_CPU_HAS_HWLOOP_STATE_BUG && ESP32P4_REV_MIN_FULL <= 1
663+
andi a1, s8, 1 << HWLP_COPROC_IDX
664+
beqz a1, 1f
665+
/* To re-enable the HWLP coprocessor, set the status to DIRTY */
666+
csrwi CSR_HWLP_STATE_REG, HWLP_DIRTY_STATE
667+
1:
668+
#endif /* SOC_CPU_HAS_HWLOOP_STATE_BUG && ESP32P4_REV_MIN_FULL <= 1 */
669+
#endif /* SOC_CPU_HAS_HWLOOP */
670+
671+
#if SOC_CPU_HAS_PIE
672+
/* Re-enable the PIE coprocessor if it was used */
673+
andi a1, s8, 1 << PIE_COPROC_IDX
674+
beqz a1, 1f
675+
pie_enable a1
676+
1:
677+
#endif /* SOC_CPU_HAS_PIE */
678+
j restore_stack_pointer
679+
680+
context_switch_requested:
628681
#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 */
682+
/* Preserve former TCB in s9 */
634683
mv s9, a0
635-
#endif
684+
#endif /* ( SOC_CPU_COPROC_NUM > 0 ) */
636685
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; */
686+
/* Clears the switch pending flag (stored in s6) */
687+
sw zero, 0(s6) /* xPortSwitchFlag[coreID] = 0; */
642688

643689
#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 */
690+
/* If the Task to schedule is NOT the same as the former one (s9), keep the coprocessors disabled. */
691+
/* Check if the new TCB is the same as the previous one */
645692
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-
693+
beq a0, s9, no_context_switch
665694
#endif /* ( SOC_CPU_COPROC_NUM > 0 ) */
666695

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 */
670696
#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 */
678697
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 */
682698
#endif /* SOC_CPU_HAS_HWLOOP */
683699

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:
700+
restore_stack_pointer:
692701

693702
#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD
694703
/* esp_hw_stack_guard_monitor_stop(); pass the scratch registers */
@@ -698,10 +707,8 @@ no_switch_restored:
698707

699708
#if ( configNUM_CORES > 1 )
700709
/* Recover the stack of next task and prepare to exit */
701-
csrr a1, mhartid
702-
slli a1, a1, 2
703710
la a0, pxCurrentTCBs /* a0 = &pxCurrentTCBs */
704-
add a0, a0, a1 /* a0 = &pxCurrentTCBs[coreID] */
711+
add a0, a0, s7 /* a0 = &pxCurrentTCBs[coreID] */
705712
lw a0, 0(a0) /* a0 = pxCurrentTCBs[coreID] */
706713
lw sp, 0(a0) /* sp = previous sp */
707714
#else
@@ -724,4 +731,5 @@ no_switch_restored:
724731

725732
rtos_int_exit_end:
726733
mv a0, s11 /* a0 = new mstatus */
734+
mv ra, s10
727735
ret

components/riscv/vectors.S

Lines changed: 15 additions & 2 deletions
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,13 +236,26 @@ _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-
/* Hardware loop cannot be treated lazily, so we should never end here if a HWLP instruction is used */
239+
240240
#if SOC_CPU_HAS_PIE
241241
/* Check if the PIE bit is set. */
242242
andi a1, a0, EXT_ILL_RSN_PIE
243243
bnez a1, rtos_save_pie_coproc
244244
#endif /* SOC_CPU_HAS_PIE */
245245

246+
/* We cannot check the HWLP bit in a0 since a hardware bug may set this bit even though no HWLP
247+
* instruction was executed in the program at all, so check mtval (`t0`) */
248+
#if SOC_CPU_HAS_HWLOOP
249+
/* HWLP instructions all have an opcode of 0b0101011 */
250+
andi a1, t0, 0b1111111
251+
addi a1, a1, -0b0101011
252+
bnez a1, hwlp_not_used
253+
/* HWLP used in an ISR, abort */
254+
mv a0, sp
255+
j vPortCoprocUsedInISR
256+
hwlp_not_used:
257+
#endif /* SOC_CPU_HAS_HWLOOP */
258+
246259
#if SOC_CPU_HAS_FPU
247260
/* Check if the FPU bit is set. When targets have the FPU reason bug (SOC_CPU_HAS_FPU_EXT_ILL_BUG),
248261
* it is possible that another bit is set even if the reason is an FPU instruction.

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)