Articles tagués “freertos

Cortex-M : comment savoir si on est sous IT ?

En écrivant des wrappers C++ pour FreeRTOS, j’ai eu besoin de savoir si le code était appelé depuis une interruption ou pas. En effet, plusieurs fonctions de FreeRTOS possède une variante avec le suffixe fromISR : il faut appeler la version normale ou la version fromISR aux bons moments. Quand on utilise un Cortex-M, il faut regarder du côté du System Control Block (SCB) et plus particulièrement l’Interrupt Control and State Register (ICSR). Voici une fonction tout simple, prise sur stackoverflow, et qui fait le taf :

bool isInInterrupt() {
    return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) != 0;
}

Voici un exemple d’utilisation pour mon wrapper de sémaphore :

bool AbstractSemaphore::give() {
	BaseType_t result = isInInterrupt() ?
				xSemaphoreGiveFromISR(nativeHandle_m, nullptr) :
				xSemaphoreGive(nativeHandle_m);
	return result == pdTRUE;
}

Il m’est ainsi possible d’appeler la fonction give() sans me soucier de savoir si l’appel se fait depuis une IT ou pas. Attention, cela ne veut pas dire qu’il ne faut se soucier de rien ! En effet, il existe dans FreeRTOS un mécanisme empêchant l’appel des  fonctions dites syscalls depuis une IT dont la priorité est supérieure à un seuil. Ce seuil est configuré dans FreeRTOSConfig.h avec la constante configMAX_SYSCALL_INTERRUPT_PRIORITY (pour plus de détails, voir la documentation de FreeRTOS) :

/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

/* Interrupt priorities used by the kernel port layer itself.  These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY 		( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

En fait, la valeur vraiment utile est celle de configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY : 5. Pour rappel, plus la valeur est grande, plus la priorité est faible. Dans ma configuration actuelle, cela signifie que je peux appeler give() depuis mon IT d’external interrupt seulement si la priorité d’interruption est configurée pour x => 5 :

NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), x, 0));

Le cas échéant, par exemple si x == 3, une assertion de FreeRTOS bloque l’exécution :

A la ligne 742 de port.c, on trouve :

configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );

Il est possible de rajouter sa propre assertion dans le wrapper C si on a une meilleure remontée des erreurs qu’un simple blocage de l’application. Il est aussi possible de redéfinir la macro configASSERT() dans FreeRTOSConfig.h (voir à nouveau la documentation), qui par défaut est définie ainsi :

#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );}

PSP et MSP sur ARM Cortex-M

Si vous jetez un oeil aux registres d’un processeur ARM Cortex-M, vous ne serez pas surpris de voir Stack Pointer (SP) mais vous serez peut-être plus dubitatifs en voyant qu’il y a aussi Main Stack Pointer (MSP) et Process Stack Pointer (PSP). Il y a en fait deux stack pointers (MSP et PSP) et le registre SP contient soit la valeur de l’un, soit la valeur de l’autre. Laquelle ? Il faut regarder le bit 1 (SPSEL) du registre Control : s’il est à 1, le processeur utilise le PSP ; sinon, il utilise le MSP.

Si vous vous demandez à quoi cela sert, la réponse est assez simple : c’est essentiellement fait pour améliorer la robustesse du système lors de l’utilisation d’un système d’exploitation. Chris Shore (de chez ARM) explique pourquoi dans cette discussion :

Having two separate stack pointers allows the operating system to be safer and more robust. Usually, you would configure the operating system to use Main Stack Pointer (MSP) and user applications to use Process Stack Pointer (PSP). The switch from one stack to another then happens automatically when an exception is handled.

The fact that the operating system and exception handlers use a different stack from the application means that the OS can protect its stack and prevent applications from accessing or corrupting it. You can also ensure that the OS does not run out of stack if the application consumes all the available PSP stack space – that means that there is always space on the stack to run an exception handler in the case of an error occurring.

Note that you don’t have to use both stack pointers. By default, the system will only use a single stack pointer (MSP) and must be manually configured to use PSP. Also, some Cortex-M microcontrollers do not support two stack pointers.

Il est très simple de constater ce comportement de changement de stack pointer. Dans une application utilisant un OS comme FreeRTOS, il suffit de mettre un breakpoint dans une task de l’OS et un autre dans un handler d’interruption. Quand les breakpoints sont touchés et votre application se met en pause, jettez un oeil aux registres et à la callstack. Dans le premier cas, le bit 1 de Control est à 1, on est bien en Process Stack Pointer : dans le second cas, ce bit est à 0, on est bien en Main Stack Pointer.

Voici ce que ça donne quand on est s’arrête dans une task. On constate que les registres SP et PSP sont égaux :

Le débogueur arrive même à nous dire que le PSP pointe vers une zone dans ucHeap. C’est cohérent avec le fait que j’ai créé dynamiquement la tâche FreeRTOS et donc que sa stack a été allouée dans le heap de FreeRTOS.

Voici ce que ça donne pour une interruption. On voit que la valeur des registres SP et MSP sont égales :

Le PSP nous permet de voir que la tâche qui a été interrompue était l’idle task. La callstack nous montre aussi que les interruptions se font dans le même contexte que l’OS. prvPortStartFirstStack() est la fonction par laquelle démarre FreeRTOS et donne la main aux tasks en modifiant le stack pointer.