FreeRTOS SMP Port to VS

portYIELD() and portYIELD_CORE(xCoreID) serve different purpose -

  • portYIELD() - is used when a core want to run scheduler itself.
  • portYIELD_CORE(xCoreID) - is used when a core wants to interrupt some other core.

taskYIELD_ANY_CORE_IF_USING_PREEMPTION is called at this line which is inside a critical section - critical nesting count, therefore, must be greater than zero. Most likely your implementation of portENTER_CRITICAL and portEXIT_CRITICAL is incorrect. Did you set portCRITICAL_NESTING_IN_TCB to 1? If yes, you need to implement portENTER_CRITICAL and portEXIT_CRITICAL using vTaskEnterCritical and vTaskExitCritical. Take a look here.

Thank You very much,If it is like this,I will unify these two macros:portYIELD() and portYIELD_CORE(xCoreID)

Thank You very much, Your suggestion is extremely valuable,and I have the direction to move forward again.

First, a taskstart task was created and bound to core0 (mask 0x01), and then FreeRTOS entered vTaskStartScheduler(), creating idle tasks for the two cores respectively, and creating a daemon task for the timer on core0. Firstly, the two cores switched to their respective idletasks to run. After a period of time, core0 would switch to the taskstart task to run. In the taskstart, taska and taskb were created, one periodic timer and two one-shot timers, one semaphore, and one queue. Among them, taska was bound to core0 and taskb was bound to core1. Finally, the taskstart task was deleted. After the tasktask was finished running, taska and taskb were not found in the corresponding priority positions in the ready list, resulting in no scheduling interruption at last. The two cores have been running idle all the time. What is the reason for this?

Check the return code of the xTaskCreate API to ensure that these tasks were created successfully.

From the debugging over the past few days, it seems that the issue is not due to the tasks failing to be created. There must be another reason, but the root cause has not yet been identified. Recently, I have added trace points to the scheduling of core0 and core1, and here are the observations:

Prerequisites:

  • Tmr Svc is bound to core0.
  • Start Task is bound to core0.
  • Within Start Task, TestA task is created, and it is bound to core0.
  • Within Start Task, TestB task is created, and it is bound to core1.
  • A periodic timer C is created within Start Task.

After running the program for a while and then stopping it:

  1. From the trace, the following task switching processes are observed:

    • On core0: IDLE0 → Tmr Svc → Start Task → TestA task → Start Task
    • On core1: IDLE1 → IDLE1 → IDLE1
  2. From the call stack, it is seen that:

    • TestB task is being created within Start Task.

After restarting and running for a while, then stopping again, the same phenomenon is observed as before, meaning there are no changes in the trace and call stack.

What could be causing this? Or what debugging methods can be used to further debug the issue?

TestB task should have been scheduled on core1. Can you check why core1 is not picking it when it is running the scheduler? One way to do may be to put a conditional breakpoint in vTaskSwitchContext when xCoreID is 1.

Sure, I will try it later according to your method.

There are two questions as follows:
1.What is the scope of concern for task locks and ISR locks, respectively? Is the following understanding correct: Acquiring a task lock: First, take the lock and stop all task scheduling; Releasing a task lock: Release the lock and start all tasks; Acquiring an ISR lock: First, take the lock and stop interrupts; Releasing an ISR lock: Release the lock and start interrupts, and if the PendSV and tick interrupts are not zero, then start scheduling?
2. What is the difference between portDISABLE_INTERRUPTS() & portENABLE_INTERRUPTS() and portSET_INTERRUPT_MASK() & portCLEAR_INTERRUPT_MASK() ? Currently, portDISABLE_INTERRUPTS() & portENABLE_INTERRUPTS() are defined as empty, portSET_INTERRUPT_MASK() directly prevents all interrupt service routines from executing, and portCLEAR_INTERRUPT_MASK() restores the execution of interrupt service routines. However, during my debugging process, I found that portDISABLE_INTERRUPTS() & portENABLE_INTERRUPTS() are used extensively in task.c .

These locks are to prevent multiple cores from simultaneously entering a code section:

  • When a task enters a critical section, it acquires both the task lock and the ISR lock.
  • When an ISR enters a critical section, it acquires only the ISR lock.

The reason for using 2 locks is to reduce lock contention between tasks and ISRs. One way to implement them is spin locks. As I have mentioned before, you can refer to existing implementations.

portENABLE_INTERRUPTS/portDISABLE_INTERRUPTS enable and disable all the interrupts while portSET_INTERRUPT_MASK/portCLEAR_INTERRUPT_MASK allow you restore the previous interrupt state.

Where do you see that? Please link the code.

Following your suggestion, I have revised the port file. After debugging over the past few days, the scheduling situation on core0 and core1 is as follows:

Prerequisites:

  • The timer service is bound to core0.
  • The start task is bound to core0.
  • Within the start task, the TestA task is created, which is bound to core0.
  • Within the start task, the TestB task is created, which is bound to core1.
  • A periodic timer C is created within the start task.
  • The priority of TestA is higher than TestB, and the priority of TestB is higher than the priority of the Start Task.

#define portEnter_CRITICAL() vTaskEnterCritical()
#define portExit_CRITICAL() vTaskExitCritical()

From the trace, the following task switching process is observed:
On core0: IDLE0 → Tmr Svc → Start Task → TestA Task → TestA Task → TestA Task → Start Task
On core1: IDLE1 → IDLE1 → IDLE1

The TestB Task is created within the Start Task. After vTaskCriticalExit, a Yield is performed, At the same time a Tick Interrupt was generated. During the execution of prvProccessTickInterrupt, xTaskIncrementTick() is called. In xTaskIncrementTick(), the pxTCB (pointing to TestA Task) waiting for a timeout is found, and then prvYieldForTask(pxTCB) is called. In prvYieldForTask, the first call is to configASSERT(portGET_CRITICAL_NESTING_COUNT() > 0UL). At this time, pxCurrentTCBs[0]->uxCriticalNesting is 0, causing core0 to be continuously blocked at configASSERT(portGET_CRITICAL_NESTING_COUNT() > 0UL), while core1 keeps idling. Could you please help to figure out what might be causing this issue?
[wuxu]:
The cause of the aforementioned issues is likely due to the absence of taskENTER_CRITICAL_FROM_ISR and taskEXIT_CRITICAL_FROM_ISR in prvProcessTickInterrupt. After adding critical section protection, the aforementioned issues were resolved. Inadvertently, I found from prvSelectHighestPriorityTask that task scheduling is based on coreid. At this point, there were no conditions for scheduling on core1, even though the TestB task had already been added to the ready list. However, TestB task would never have a chance to be scheduled. Therefore, I considered splitting the Start Task into Start For Core0 Task and Start For Core1 Task, both with the same priority, bound to core0 and core1 respectively, to initialize the tasks running on each core. The task scheduling situation on each core is as follows:
On core0: IDLE0 → Tmr Svc → Start For Core0 Task → TestA Task
On core1: IDLE1 → IDLE1 → Start For Core1 Task
When creating the TestB Task in Start For Core1 Task, an exception occurred. The exception occurred on core1’s call stack:
Start For Core1 Task
xTaskCreateAffinitySet: Create TestB, bound to core1
prvCreateTask
pvPortMalloc
vTaskSuspendAll
prvCheckForRunStateChange: The exception occurred in this function
The snippet of code in prvCheckForRunStateChange where the exception occurred is as follows:
configASSERT(pxThisTCB->xTaskRunState == taskTASK_SCHEDULED_TO_YIELD)
portENABLE_INTERRUPTS();
configASSERT(pxThisTCB->xTaskRunState != taskTASK_SCHEDULED_TO_YIELD)
At this time, pxThisTCB points to Start For Core1 Task, and the value of xTaskRunState is -2. Since it seems that portSET_INTERRUPT_MASK() & portCLEAR_INTERRUPT_MASK() are already in place, portENABLE_INTERRUPTS() and portDISABLE_INTERRUPTS are not defined. Even if portENABLE_INTERRUPTS() were defined, it could not change the value of xTaskRunState, right? portENABLE_INTERRUPTS() is to disable interrupts, so how can disabling interrupts change the task state? I don’t understand why it’s written this way.

vTaskSwitchContext requires a parameter xCoreID. Are you passing that correctly?

prvCheckForRunStateChange addresses the case when a task was asked to yield while it was waiting on the task spin lock. The sequence in this case would have been the following -

Disable interrupts. 
Acquire Task Lock.
prvCheckForRunStateChange();

Lets say the above code was executed by a task running on Core1. When the task was waiting to acquire spin lock, Core0 decided to evict this task for some other high priority task. When the task reaches prvCheckForRunStateChange after acquiring the spin lock, it is already scheduled to yield (i.e. its state is taskTASK_SCHEDULED_TO_YIELD) and the scheduler interrupt is pending for Core1. The moment you enable interrupts, the core will service the pending interrupt and will pick a task to run whose state cannot be taskTASK_SCHEDULED_TO_YIELD. Hope that helps.

Yes, this part is correct, but the problem is: when tasks for core1 are created on core0, it seems that the task scheduling does not generate a scheduling interrupt for core1?

As you said, the eviction of Start For Core1 Task by core0, which is about to transition Start For Core1 Task from the taskTASK_SCHEDULED_TO_YIELD state to a non-taskTASK_SCHEDULED_TO_YIELD state, should occur during the process where Start For Core1 Task on Core1 is requesting the spin lock, right? Or is it during the process of disabling interrupts? If the eviction process is within one of the above two processes, then it should not simultaneously present the following in prvCheckForRunStateChange:

configASSERT(pxThisTCB->xTaskRunState == taskTASK_SCHEDULED_TO_YIELD)
portENABLE_INTERRUPTS();
configASSERT(pxThisTCB->xTaskRunState != taskTASK_SCHEDULED_TO_YIELD)

Unless it is the portENABLE_INTERRUPTS() within prvCheckForRunStateChange that implements the eviction of the Start For Core1 Task?

This line should ensure that the newly created task gets scheduled.

Yes, it happens when the task is spinning waiting for the spin lock. The Core0 decides that task1 needs to be evicted and does the following -

  1. Change the task’s state to taskTASK_SCHEDULED_TO_YIELD.
  2. Interrupt core1 so that it runs the scheduler again. Note that interrupts are disabled on Core1, so core1 will not be able to run the scheduler and the interrupt will remain pending.
  3. Core 0 releases spin lock.

Core1 now acquires spin lock and runs prvCheckForRunStateChange which sees that task’s state as taskTASK_SCHEDULED_TO_YIELD. portENABLE_INTERRUPTS(); will cause the pending interrupt to be services immediately and the scheduler will run on core1. When the core1 is done running the scheduler, a task would have been picked to run core 1 and its status will be 1(indicating that it is running on core1) instead of taskTASK_SCHEDULED_TO_YIELD.

Does the taskYIELD_ANY_CORE_IF_USING_PREEMPTION function require the PREEMPTION macro to be enabled? What are the configurations related to PREEMPTION? Sure, based on this direction, I’ll go and debug it.

I seem to understand what you’re saying, which means that the macro definitions for portENABLE_INTERRUPTS and portDISABLE_INTERRUPTS must also be handled with hardware logic and cannot be empty. When portENABLE_INTERRUPTS is enabled, it should be able to trigger the response inside the PendSV interrupt service routine. I also have the following questions:

  1. Are portENABLE_INTERRUPTS and portDISABLE_INTERRUPTS for enabling and disabling interrupts for all Cores, right?
  2. Are portSET_INTERRUPT_MASK() and portCLEAR_INTERRUPT_MASK() for masking and unmasking all interrupts for all Cores? Since portSET_INTERRUPT_MASK() has no parameters and only returns a value, this means it cannot specify which interrupts to mask.

configUSE_PREEMPTION and configUSE_TIME_SLICING. Please set both to 1 in your FreeRTOSConfig.h:

#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1

No, for the current core only.

No, for the current core only.

3q,based on this direction, I’ll go ahead

3q,based on this direction, I’ll go ahead

I have three more questions:

  1. Do portGET_TASK_LOCK and portRELEASE_TASK_LOCK apply to all Cores, or just the current Core?
  2. Do portGET_ISR_LOCK and portRELEASE_ISR_LOCK apply to all Cores, or just the current Core?
  3. After portCLEAR_INTERRUPT_MASK is executed, will it also trigger the scheduling of the current Core, similar to portENABLE_INTERRUPTS?

Both these locks are to provide corss-core synchronization. Therefore, only one core can acquire at a time.

The following will trigger the scheduler -

  1. portCLEAR_INTERRUPT_MASKunmasks the scheduler interrupt. [Note thatportENABLE_INTERRUPTS` unmasks all the interrupts].
  2. The scheduler interrupt is pending.

I would like to inquire further about the interdependencies and order of use among these four sets of interfaces:

  • portGET_TASK_LOCK | portRELEASE_TASK_LOCK
  • portGET_ISR_LOCK | portRELEASE_ISR_LOCK
  • portSET_INTERRUPT_MASK | portCLEAR_INTERRUPT_MASK
  • portDISABLE_INTERRUPTS | portENABLE_INTERRUPTS

Actually, I am more concerned about:

  • portGET_ISR_LOCK | portRELEASE_ISR_LOCK
  • portSET_INTERRUPT_MASK | portCLEAR_INTERRUPT_MASK
  • portDISABLE_INTERRUPTS | portENABLE_INTERRUPTS

Are there any dependencies when using these three sets of interfaces?

1.Does this mean that portGET_ISR_LOCK | portRELEASE_ISR_LOCK is used to protect critical sections within interrupt service routines?
2.Does the portGET_ISR_LOCK | portRELEASE_ISR_LOCK interval still have interrupt masking functionality?

When a task wants to enter a critical section, it does the following -

  1. Disable interrupts on the current core.
  2. Acquire task spin lock. After it is acquired, no other task on any other core can enter critical section.
  3. Acquire isr spin lock. After it is acquired, no other ISR on any other core can enter critical section.

Yes.

No - as the name clearly suggests, these macros just acquire and release isr lock.

Thank you very much, your response has given me a general direction again:

  1. I need to add protection with portGET_ISR_LOCK and portRELEASE_ISR_LOCK in the simulated interrupt service routine.
  2. Define portSET_INTERRUPT_MASK and portCLEAR_INTERRUPT_MASK to have the same functionality as portDISABLE_INTERRUPTS and portENABLE_INTERRUPTS (for software simulation of interrupts, these two seem to make no difference), but after interrupts are enabled, scheduling must be possible.

Based on the above points, I will give it another try.

Suddenly,I have some more questions: It seems that on SMP systems, the queue API cannot be used for communication. So, which APIs are needed for
communication on dual - core systems?Are there any constraints on using those APIs for single-core communication,such as semaphores,mutexes,event groups,and soft timers, when applied in an smp environment?