A few questions about task notifications

So i’m trying to understand a few things regarding task notifications.

So for this sample app, I read UART data through interrupts and upon receiving \r, it notifies another blocked task to run (by calling xTaskNotifyFromISR() within a UART ISR).

  • I see there are two critical sections in ulTaskNotifyTake(), and looks like the first one executes before the notification is received and the second is executed after the notification is received. Why doesn’t the program go into the second critical section right away after exiting the first?

  • How does a task notification work as a semaphore? I see xTaskGenericNotifyFromISR() sets the notification state
    pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED; and adds the notified task to a ready list…but i’m not certain of how synchronization occurs. All I see is the second critical section is executing (code below).

  • Given the equal priority of the tasks, the context switch would only happen if the notified task has a higher priority than the currently executed task? Currently, I see that context switching happens after the ISR is done executing (and not right away) i.e when pxHigherPriorityTaskWoken is set to pdTRUE by xTaskNotifyFromISR()

//task.c
#if( configUSE_TASK_NOTIFICATIONS == 1 )

	uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
	{
	uint32_t ulReturn;

		taskENTER_CRITICAL();
		{
			/* Only block if the notification count is not already non-zero. */
			if( pxCurrentTCB->ulNotifiedValue == 0UL )
			{
				/* Mark this task as waiting for a notification. */
				pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;

				if( xTicksToWait > ( TickType_t ) 0 )
				{
					prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
					traceTASK_NOTIFY_TAKE_BLOCK();

					/* All ports are written to allow a yield in a critical
					section (some will yield immediately, others wait until the
					critical section exits) - but it is not something that
					application code should ever do. */
					portYIELD_WITHIN_API();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		taskEXIT_CRITICAL();

		taskENTER_CRITICAL();
		{
			traceTASK_NOTIFY_TAKE();
			ulReturn = pxCurrentTCB->ulNotifiedValue;

			if( ulReturn != 0UL )
			{
				if( xClearCountOnExit != pdFALSE )
				{
					pxCurrentTCB->ulNotifiedValue = 0UL;
				}
				else
				{
					pxCurrentTCB->ulNotifiedValue = ulReturn - ( uint32_t ) 1;
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
		}
		taskEXIT_CRITICAL();

		return ulReturn;
	}```

// main.c

int main(void) {
   // other init...
   xTaskCreate(vTask1_menuDisplay, "Menu display", 500, NULL, 2, &xTaskHandle1);

   vTaskStartScheduler();
}
   void vTask1_menuDisplay(void *params)
   {
	while(1)
	{
		xQueueSend(uartWriteQueue, menu, portMAX_DELAY);

		// wait till a notification from an ISR
		ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
		printf ("Sent...\n");
	}
   }
    void USART_AppCallback(void). // called inside UART ISR when 'r' is received
    {
	BaseType_t xHigherPriorityTaskWoken = pdFALSE;
	xTaskNotifyFromISR(xTaskHandle1, 0, eNoAction, &xHigherPriorityTaskWoken);
    }```

So you’re eager to know how FreeRTOS notifications are implemented and are working internally ? Try harder :wink:
Regarding the ISR it seems that portYIELD_FROM_ISR is missing to immediately switch to a higher prio task if possible.

Isn’t it suggested to understand the kernel code rather than merely using high level APIs? isn’t yielding done through pxHigherPriorityTaskWoken?

                if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
				{
					/* The notified task has a priority above the currently
					executing task so a yield is required. */
					if( pxHigherPriorityTaskWoken != NULL )
					{
						*pxHigherPriorityTaskWoken = pdTRUE;
					}

					/* Mark that a yield is pending in case the user is not
					using the "xHigherPriorityTaskWoken" parameter to an ISR
					safe FreeRTOS function. */
					xYieldPending = pdTRUE;
				}```

IMHO it’s a matter of taste and perhaps complexity if one wants to fully understand an implementation of a library before using it. But it’s required to understand the APIs.
So no, I don’t know the FreeRTOS implementation in every detail because I don’t need it. I’m just using it.
I’d give using portYIELD_FROM_ISR in your ISR a try and see what’s happening.
It’s there and documented for a purpose along with the pxHigherPriorityTaskWoken argument of various FreeRTOS functions potentially causing context switches.
If it wouldn’t be needed it wouldn’t be there :wink:

If you do not call the “end switching ISR” macro then if a context switch is necessary it won’t happen until the running task is switched out, worst case at the next tick interrupt. If you do call it the context switch happens immediately and the interrupt may return directly to the unblocked task if it’s priority is above the currently running task. The behavior is described on the website and in the free book, and there is an FAQ about the design decisions.

I was just clarifying if the context switching does happen after xTaskNotifyFromISR() but looks like it doesn’t unless the notified task has a higher priority.

If you do call it the context switch happens immediately and the interrupt may return directly to the unblocked task

if I don’t call xTaskNotifyFromISR() you mean? how else the other task will know if I don’t notify it anyways

Sure, a context switch only happens if there is something to switch resp. to schedule.
So depending on the configured scheduling scheme (preemption, time sliced, etc.) either the currently running task blocks or its time slice expired or a higher prio tasks got ready to run causes a context switch and makes an other task running.
The scheduling schemes and the associated config items are very well documented in the FreeRTOS docs and the book.
Richard also refers to the “end switching ISR” macro which should be called in ISRs to make a potential context switch to a higher priority task happen immediately i.e. returning to this new task instead of the currently running task which was interrupted.
Hence the argument of the FreeRTOS functions which might wake up a task (by notifying it or by signaling a semaphore etc.) is named pxHigherPriorityTaskWoken.

Just to clarify: the higher the ISR’s interrupt, the lower it’s priority whereas for RTOS, the higher the priority number, the higher its priority? USART’s IRQ number is set to 85 (higher than that of configmax_syscall_interrupt_priority) whereas the IRQ of the notified task is set to 2, which makes sense since I’m not explicitly calling taskYIELD() to do so? Currently I see the context switching after the ISR is done executing…

the main difference that I see between fromISR and without fromISR is the following code:

				{
					/* The notified task has a priority above the currently
					executing task so a yield is required. */
					if( pxHigherPriorityTaskWoken != NULL )
					{
						*pxHigherPriorityTaskWoken = pdTRUE;
					}

					/* Mark that a yield is pending in case the user is not
					using the "xHigherPriorityTaskWoken" parameter to an ISR
					safe FreeRTOS function. */
					xYieldPending = pdTRUE;
				}

Where does it context switch though? here it’s just setting the flag. It might be linked to the question that I asked in the description about how the second critical section is called of the notified task after ulTaskNotifyTake()

It isn’t the interrupt NUMBER that is important, it is the interrupt PRIORITY, which is set via a function call (or should be) as part of your set up. Note that on Cortex M processors, for interrupts, the hardware treats lower interrupt priorities as higher priority.

In the code above, pxHigherPriorityTaskWoken was a parameter to the function, which if not passed as a NULL should be a pointer to a variable the ISR is keeping track of if it needs to generete the yield, via a call to portYIELD_FROM_ISR().

If your ISR doesn’t call portYIELD_FROM_ISR() (or another macro/function that calls it) then when the ISR return, it will go back to the task that it interrupted, and any task it unblocked won’t get switched to until the next time something check if it needs to switch tasks (which is why your ISR should be using that flag)

My bad. That’s what I was trying to get at. So how does the processor decide between an ISR and task’s priority given the way it’s computed is opposite?

okay so the ISR is responsible for calling portYIELD_FROM_ISR() if there’s a need for a context switch and not the kernel code which merely sets pxHigherPriorityTaskWoken to pdTRUE?

then when the ISR return, it will go back to the task that it interrupted

you mean the normal context switching done by a schedule in case portYIELD_FROM_ISR() isn’t called from within the ISR?

Also, could you briefly explain how the task notification works as a semaphore? I stepped through the code & I see that the second critical section is invoked after xTaskGenericNotifyFromISR() is done executing. How so?

Other than that, what I see is the task is first added to a delayed list & then xTaskGenericNotifyFromISR() adds the task to a ready list but how does xTaskGenericNotifyFromISR() synchronize with the delayed task? (sample snippet shown in the description).

See the direct to task notification section on the website for information on how to use a task notification as a binary or counting semaphore. No point repeating an abbreviated version here.

On a cortex-m calling the yield from isr function pends a PendSV interrupt which executes when all application interrupts have executed. The context switch is performed in the PendSV isr.

Would normally include links to the above but writing this on my phone.

Don‘t mix up hardware interrupts and their (hardware) priorities with tasks.
Task priorities are logical priorities and belong to task scheduling only.
Interrupts always kick in as long as they’re not temporarily disabled.
However, the kernel needs to know the hardware interrupt priority range used for interrupts/ISRs which in turn use kernel calls (fromISR) to implement critical sections needed by the kernel code itself.
See e.g. configMAX_API_CALL_INTERRUPT_PRIORITY for further details.

The short answer is that it doesn’t. The processor doesn’t know anything at all about FreeRTOS, or tasks, their priorities, etc.

right, the kernel does. How does the blocked task really get unblocked via task notifications? upon debugging, I see the second critical section gets executed after as a result of unblocking

Ultimately, when the task gets unblocked, it is moved from one list of tasks (either those that are blocked for a specified period of time, or those that are block indefinitely) to the list of ready tasks at its current task priority. Having been moved to the ready list, it will get execution time when it becomes the highest priority ready task.