[FreeRTOS learning plan] section V Protection of critical sections

Posted by bubblybabs on Sun, 27 Feb 2022 05:32:29 +0100

What is critical section

A critical segment is a piece of code that cannot be interrupted by internal interrupts during execution. In FreeRTOS, the most common critical segment is the operation of global variables. Global variables are like a gun handle. Anyone can shoot him, but when I shoot, you can't shoot, otherwise you don't know who hit the target.

Critical segments are interrupted during system scheduling and external interrupt execution. In FreeRTOS, system scheduling ultimately generates PendSV interrupts. Task switching is realized in PendSV Handler, so it can still be attributed to interrupts. Therefore, the protection of critical section by FreeRTOS is the control of on interrupt and off interrupt.

Contex-M kernel fast shutdown interrupt instruction

In order to quickly switch interrupts, the Cortex-M kernel has specially set up a CPS instruction, which has four uses

CPSID I ;PRIMASK=1 ; Off interrupt
CPSIE I ;PRIMASK=0 ; Open interrupt
CPSID F ;FAULTMASK=1 ; Guan anomaly
CPSIE F ;FAULTMASK=0 ; Abnormal opening

RIMASK and faultmaster are two of the three interrupt mask registers in Cortex-M kernel, and the other is BASEPRI. The detailed usage of these three registers is as follows

nameFunction description
PEIMASKThis is a single bit register. After it is set to 1, all maskable exceptions are turned off, leaving only NMI and hard FAULT to respond. Its default value is 0, indicating that there is no shutdown interrupt.
FAULTMASKThis is a one bit register. When it is set to 1, only NMI can respond, and all other exceptions, even hard FAULT, shut up. Its default value is also 0, indicating that there is no off exception.
BASEPRIThis register has a maximum of 9 bits (determined by the number of bits expressing priority). It defines the threshold of the masked priority. When it is set to a certain value, all interrupts with priority number greater than or equal to this value are turned off (the higher the priority number, the lower the priority). However, if it is set to 0, no interrupt will be turned off, and 0 is also the default value.

However, in FreeRTOS, the on and off of interrupts are realized by operating the BASEPRI register, that is, interrupts greater than or equal to the value of BASEPRI will be masked, and interrupts less than the value of BASEPRI will not be masked and will not be managed by FreeRTOS. Users can set the value of BASEPRI to selectively leave a way back for some very urgent interrupts.

Off interrupt

The function of FreeRTOS off interrupt is in portmacro Defined in H, there are two types: without return value and with return value

Off interrupt function without return value

/* ①The off interrupt function without return value cannot be nested or used in interrupt */
#define portINLINE __inline

#ifndef portFORCE_INLINE
	#define portFORCE_INLINE __forceinline
#endif

#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI(void)
 {
 uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;//②
 __asm
 {
 msr basepri, ulNewBASEPRI//③
 dsb
 isb
 }
 }

① Off interrupt functions without return value cannot be nested and used in interrupts. No return value means that when writing a new value to BASEPRI, you don't have to save the value of BASEPRI first, that is, you don't care about the current interrupt state. Since you don't care about the current interrupt state, it means that such a function can't be called in the interrupt.

②configMAX_SYSCALL_INTERRUPT_PRIORITY is a free r-tosconfig The macro defined in H, that is, the value to be written to the BASEPRI register. The macro is defined as 191 by default, and the upper four bits are valid, that is, it is equal to 0xb0 or 11, that is, interrupts with priority greater than or equal to 11 will be shielded, and interrupts within 11 will not be managed by FreeRTOS.

③ Set configmax_ SYSCALL_ INTERRUPT_ The value of priority is written into the BASEPRI register to realize the off interrupt (accurately, the off part interrupt).

Off interrupt function with return value

/* ①The off interrupt function with return value can be nested and can be used in the interrupt */
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()

static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI(void)
{
	uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;//②
	
	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		mrs ulReturn, basepri		//③
		msr basepri, ulNewBASEPRI	//④
		dsb
		isb
	}

	return ulReturn;				//⑤
}

① The off interrupt function with return value can be nested and can be used in the interrupt. With return value means that when writing a new value to BASEPRI, save the value of BASEPRI first. When updating the value of BASEPRI, return the previously saved value of BASEPRI, and the returned value is passed into the interrupt function as a formal parameter.

②configMAX_SYSCALL_INTERRUPT_PRIORITY is a free r-tosconfig The macro defined in H, that is, the value to be written to the BASEPRI register. The macro is defined as 191 by default, and the upper four bits are valid, that is, it is equal to 0xb0 or 11, that is, interrupts with priority greater than or equal to 11 will be shielded, and interrupts within 11 will not be managed by FreeRTOS.

③ Save the value of BASEPRI and record which interrupts are closed at present.

④ Update the value of BASEPRI.

⑤ Returns the value of the original BASEPRI.

Open interrupt

FreeRTOS interrupt function is in portmacro Defined in H

/* ①Interrupt function without interrupt protection */
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )

/* ②Interrupt function with interrupt protection */
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI)//③
{
	__asm
	{
		/* Barrier instructions are not used as this function is only used to
		lower the BASEPRI value. */
		msr basepri, ulBASEPRI
	}
}

① The interrupt function without interrupt protection directly sets the value of BASEPRI to 0, which is the same as portDISABLE_INTERRUPTS() is used in pairs.

② The interrupt function with interrupt protection takes the value of BASEPRI saved during the last interrupt as a formal parameter, which is similar to portSET_INTERRUPT_MASK_FROM_ISR() is used in pairs.

③ The interrupt function is to update the passed formal parameters to the BASEPRI register. According to the different parameters passed in, it is divided into interrupt protection version and non interrupt protection version.

Macro to enter / exit critical segment

Enter and exit the macro of critical segment in task Defined in H

#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()

#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

The macro of entering and exiting the critical section is divided into interrupt protection version and non interrupt version, but they are finally realized through on / off. Then the next step is to exit and enter the critical section of the code.

Enter critical section

In port Defined in C

void vPortEnterCritical(void)
{
	portDISABLE_INTERRUPTS();
	uxCriticalNesting++;//①
	
	
	/* This is not the interrupt safe version of the enter critical function so
	assert() if it is being called from an interrupt context.  Only API
	functions that end in "FromISR" can be used in an interrupt.  Only assert if
	the critical nesting count is 1 to protect against recursive calls if the
	assert function also uses a critical section. */
	if( uxCriticalNesting == 1 )//②
	{
		//configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
	}
}

① uxCriticalNesting is in port The static variable defined in C represents the critical segment nesting counter. It is initialized to 0xaaaa by default and will be re initialized to 0 when the scheduler starts: vtaskstartscheduler() - > xportstartscheduler() - > uxCriticalNesting = 0.

② If uxCriticalNesting is equal to 1, that is, a layer of nesting, ensure that no interrupt is currently active, that is, the interrupt and control register SCB in the kernel peripheral SCB_ The lower 8 bits of ICSR should be equal to 0. About SCB_ Refer to "STM32F10xxxCortex-M3 programmingmanual-4.4.2" for specific description of ICSR.

Exit critical section

In port Defined in C

void vPortExitCritical( void )
{
	//configASSERT( uxCriticalNesting );
	uxCriticalNesting--;
    
	if( uxCriticalNesting == 0 )
	{
		portENABLE_INTERRUPTS();
	}
}

Application of critical segment code

In FreeRTOS, the protection of critical section occurs in two occasions, one is in interrupt and the other is in non interrupt

/* In case of interruption, critical segments can be nested */
{
uint32_t ulReturn;
/* Enter the critical segment, which can be nested */
ulReturn = taskENTER_CRITICAL_FROM_ISR();

 /* Critical segment code */

 /* Exit critical section */
 taskEXIT_CRITICAL_FROM_ISR( ulReturn );
 }

 /* In the case of non interruption, critical segments cannot be nested */
 {
 /* Enter critical section */
 taskENTER_CRITICAL();

 /* Critical segment code */

 /* Exit critical section */
 taskEXIT_CRITICAL();
 }

Reference: FreeRTOS Kernel Implementation and application development practice - based on RT1052

Topics: Single-Chip Microcomputer stm32 FreeRTOS