Skip to content

Commit afae6d9

Browse files
committed
Cortex-R82: Start tasks at EL0
This commit includes the following changes * Currently, the expectation is that applications would explicitly call xPortStartScheduler() on all secondary cores. However, this approach places an unnecessary burden on the application developer and assumes strict adherence to our example code, which we cannot guarantee. To make the port more inclusive and align with the principle that the kernel should take care of system responsibilities, we are shifting this logic into the port itself. With this change, the application developer only needs to call vTaskStartScheduler() on the primary core, does an SVC call once the secondary cores are online, and the kernel will ensure that all secondary cores are properly managed. * Support tasks running in EL0 and only kernel running in EL1, the changes are just having multiple SVCs for different purposes (whenever a privileged instruction (e.g., MSR) is to be executed). Signed-off-by: Ahmed Ismail <Ahmed.Ismail@arm.com>
1 parent cab99bc commit afae6d9

File tree

4 files changed

+393
-181
lines changed

4 files changed

+393
-181
lines changed

portable/GCC/ARM_CR82/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,26 @@ The port is supported and tested on the following toolchains:
1717
- This port assumes the hardware or model is fully cache coherent.
1818
- The port does not perform cache maintenance for shared buffers.
1919
- If your hardware or model doesn't support full cache coherency, you must handle cache clean/invalidate operations, memory attributes, and any additional barriers in your BSP/application (especially around shared-memory regions).
20+
21+
# SMP Multicore Bring-up
22+
23+
For SMP systems using this port, the application only needs to start the scheduler on the primary core and issue an SVC from each secondary core once they are online. The kernel coordinates the rest and ensures all cores are properly managed.
24+
25+
- Developer-facing summary: call `vTaskStartScheduler()` on the primary core; each secondary core, in its **reset handler**, performs its local init and then issues an SVC (immediate value `106`) to hand off to the kernel. The port will bring all cores under the scheduler.
26+
27+
Primary core flow:
28+
29+
1. Perform core-specific and shared initialization (e.g., set EL1 stack pointer, zero-initialize `.bss`).
30+
2. Jump to `main()`, create user tasks, optionally pin tasks to specific cores.
31+
3. Call `vTaskStartScheduler()` which invokes `xPortStartScheduler()`.
32+
4. `xPortStartScheduler()` configures the primary core tick timer and signals secondary cores that shared init is complete using the `ucPrimaryCoreInitDoneFlag` variable.
33+
5. Wait until all secondary cores report as brought up.
34+
6. Once all cores are up, call `vPortRestoreContext()` to schedule the first task on the primary core.
35+
36+
Secondary core flow (to be done in each core’s reset handler):
37+
38+
1. Perform core-specific initialization (e.g., set EL1 stack pointer).
39+
2. Wait for the primary core's signal that shared initialization is complete (i.e., `ucPrimaryCoreInitDoneFlag` set to 1).
40+
3. Update `VBAR_EL1` from the boot vector table to the FreeRTOS vector table.
41+
4. Initialize the GIC redistributor and enable SGIs so interrupts from the primary core are receivable; signal the primary that this secondary is online and ready by setting the its flag in the `ucSecondaryCoresReadyFlags` array.
42+
5. Issue an SVC with immediate value `106` to enter `FreeRTOS_SWI_Handler`, which will call `vPortRestoreContext()` based on the SVC number to start scheduling on this core.

portable/GCC/ARM_CR82/port.c

Lines changed: 134 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -88,27 +88,8 @@
8888
* this value. */
8989
#define portNO_CRITICAL_NESTING ( ( size_t ) 0 )
9090

91-
/* In all GICs 255 can be written to the priority mask register to unmask all
92-
* (but the lowest) interrupt priority. */
93-
#define portUNMASK_VALUE ( 0xFFUL )
94-
9591
/* Macro to unmask all interrupt priorities. */
96-
#define portCLEAR_INTERRUPT_PRIORITIES_MASK() \
97-
{ \
98-
__asm volatile ( \
99-
"MSR DAIFSET, #2 \n" \
100-
"DSB SY \n" \
101-
"ISB SY \n" \
102-
"MSR ICC_PMR_EL1, %0 \n" \
103-
"DSB SY \n" \
104-
"ISB SY \n" \
105-
"MSR DAIFCLR, #2 \n" \
106-
"DSB SY \n" \
107-
"ISB SY \n" \
108-
: \
109-
: "r" ( portUNMASK_VALUE ) \
110-
); \
111-
}
92+
#define portCLEAR_INTERRUPT_PRIORITIES_MASK() __asm volatile ( "SVC %0" : : "i" ( portSVC_UNMASK_ALL_INTERRUPTS ) : "memory" )
11293

11394
/* Tasks are not created with a floating point context, but can be given a
11495
* floating point context after they have been created. A variable is stored as
@@ -121,8 +102,9 @@
121102
#define portSP_ELx ( ( StackType_t ) 0x01 )
122103
#define portSP_EL0 ( ( StackType_t ) 0x00 )
123104
#define portEL1 ( ( StackType_t ) 0x04 )
105+
#define portEL0 ( ( StackType_t ) 0x00 )
124106

125-
#define portINITIAL_PSTATE ( portEL1 | portSP_EL0 )
107+
#define portINITIAL_PSTATE ( portEL0 | portSP_EL0 )
126108

127109
/* Used by portASSERT_IF_INTERRUPT_PRIORITY_INVALID() when ensuring the binary
128110
* point is zero. */
@@ -178,8 +160,9 @@ extern void vGIC_EnableCPUInterface( void );
178160
uint64_t ullPortYieldRequired[ configNUMBER_OF_CORES ] = { pdFALSE };
179161
uint64_t ullPortInterruptNestings[ configNUMBER_OF_CORES ] = { 0 };
180162

181-
/* Flag to control tick ISR handling, this is made true just before schedular start. */
182-
uint8_t ucPortSchedulerRunning = pdFALSE;
163+
/* Flags to check if the secondary cores are ready. */
164+
volatile uint8_t ucSecondaryCoresReadyFlags[ configNUMBER_OF_CORES - 1 ] = { 0 };
165+
volatile uint8_t ucPrimaryCoreInitDoneFlag = 0;
183166
#endif /* #if ( configNUMBER_OF_CORES == 1 ) */
184167

185168
/* Used in the ASM code. */
@@ -320,39 +303,36 @@ BaseType_t xPortStartScheduler( void )
320303

321304
#if ( configASSERT_DEFINED == 1 )
322305
{
323-
if ( portGET_CORE_ID() == 0 )
324-
{
325-
volatile uint8_t ucOriginalPriority;
326-
volatile uint8_t * const pucFirstUserPriorityRegister = ( volatile uint8_t * const ) ( configINTERRUPT_CONTROLLER_BASE_ADDRESS + portINTERRUPT_PRIORITY_REGISTER_OFFSET );
327-
volatile uint8_t ucMaxPriorityValue;
306+
volatile uint8_t ucOriginalPriority;
307+
volatile uint8_t * const pucFirstUserPriorityRegister = ( volatile uint8_t * const ) ( configINTERRUPT_CONTROLLER_BASE_ADDRESS + portINTERRUPT_PRIORITY_REGISTER_OFFSET );
308+
volatile uint8_t ucMaxPriorityValue;
328309

329-
/* Determine how many priority bits are implemented in the GIC.
330-
*
331-
* Save the interrupt priority value that is about to be clobbered. */
332-
ucOriginalPriority = *pucFirstUserPriorityRegister;
310+
/* Determine how many priority bits are implemented in the GIC.
311+
*
312+
* Save the interrupt priority value that is about to be clobbered. */
313+
ucOriginalPriority = *pucFirstUserPriorityRegister;
333314

334-
/* Determine the number of priority bits available. First write to
335-
* all possible bits. */
336-
*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
315+
/* Determine the number of priority bits available. First write to
316+
* all possible bits. */
317+
*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
337318

338-
/* Read the value back to see how many bits stuck. */
339-
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
319+
/* Read the value back to see how many bits stuck. */
320+
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
340321

341-
/* Shift to the least significant bits. */
342-
while( ( ucMaxPriorityValue & portBIT_0_SET ) != portBIT_0_SET )
343-
{
344-
ucMaxPriorityValue >>= ( uint8_t ) 0x01;
345-
}
322+
/* Shift to the least significant bits. */
323+
while( ( ucMaxPriorityValue & portBIT_0_SET ) != portBIT_0_SET )
324+
{
325+
ucMaxPriorityValue >>= ( uint8_t ) 0x01;
326+
}
346327

347-
/* Sanity check configUNIQUE_INTERRUPT_PRIORITIES matches the read
348-
* value. */
349-
configASSERT( ucMaxPriorityValue >= portLOWEST_INTERRUPT_PRIORITY );
328+
/* Sanity check configUNIQUE_INTERRUPT_PRIORITIES matches the read
329+
* value. */
330+
configASSERT( ucMaxPriorityValue >= portLOWEST_INTERRUPT_PRIORITY );
350331

351332

352-
/* Restore the clobbered interrupt priority register to its original
353-
* value. */
354-
*pucFirstUserPriorityRegister = ucOriginalPriority;
355-
}
333+
/* Restore the clobbered interrupt priority register to its original
334+
* value. */
335+
*pucFirstUserPriorityRegister = ucOriginalPriority;
356336
}
357337
#endif /* configASSERT_DEFINED */
358338

@@ -365,20 +345,36 @@ BaseType_t xPortStartScheduler( void )
365345
* not execute while the scheduler is being started. Interrupts are
366346
* automatically turned back on in the CPU when the first task starts
367347
* executing. */
368-
portDISABLE_INTERRUPTS();
348+
__asm volatile ( "MSR DAIFSET, #2\n"
349+
"DSB SY\n"
350+
"ISB SY\n" ::: "memory" );
369351
#if ( configNUMBER_OF_CORES > 1 )
370-
if( portGET_CORE_ID() == 0 )
371-
{
372-
/* Start the timer that generates the tick ISR. */
373-
configSETUP_TICK_INTERRUPT();
374-
ucPortSchedulerRunning = pdTRUE;
375-
__asm__ volatile ( "dsb sy" );
376-
/* Start all other Cores and let them execute vPortRestoreTaskContext(). */
377-
__asm__ volatile ( "sev" );
378-
}
379-
else
352+
/* Start the timer that generates the tick ISR. */
353+
configSETUP_TICK_INTERRUPT();
354+
ucPrimaryCoreInitDoneFlag = 1;
355+
__asm volatile ( "SEV \n"
356+
"DSB SY \n"
357+
"ISB SY \n"
358+
::: "memory" );
359+
/* Hold the primary core here until all the secondary cores are ready, this would be achieved only when
360+
* all elements of ucSecondaryCoresReadyFlags are set.
361+
*/
362+
while( 1 )
380363
{
381-
portSETUP_SGI_INTERRUPT();
364+
BaseType_t xAllCoresReady = pdTRUE;
365+
for( uint32_t ulCoreID = 0; ulCoreID < ( configNUMBER_OF_CORES - 1 ); ulCoreID++ )
366+
{
367+
if( ucSecondaryCoresReadyFlags[ ulCoreID ] != pdTRUE )
368+
{
369+
xAllCoresReady = pdFALSE;
370+
break;
371+
}
372+
}
373+
374+
if ( xAllCoresReady == pdTRUE )
375+
{
376+
break;
377+
}
382378
}
383379
#else /* if ( configNUMBER_OF_CORES > 1 ) */
384380
/* Start the timer that generates the tick ISR. */
@@ -478,7 +474,9 @@ void FreeRTOS_Tick_Handler( void )
478474

479475
/* Ok to enable interrupts after the interrupt source has been cleared. */
480476
configCLEAR_TICK_INTERRUPT();
481-
portENABLE_INTERRUPTS();
477+
__asm volatile ( "MSR DAIFCLR, #2\n"
478+
"DSB SY\n"
479+
"ISB SY\n" ::: "memory" );
482480

483481
#if ( configNUMBER_OF_CORES > 1 )
484482
UBaseType_t x = portENTER_CRITICAL_FROM_ISR();
@@ -525,41 +523,78 @@ void FreeRTOS_Tick_Handler( void )
525523

526524
void vPortClearInterruptMask( UBaseType_t uxNewMaskValue )
527525
{
528-
if( uxNewMaskValue == pdFALSE )
526+
if( uxNewMaskValue == portUNMASK_VALUE )
529527
{
528+
/* Unmask all interrupt priorities. */
530529
portCLEAR_INTERRUPT_PRIORITIES_MASK();
531530
}
531+
else
532+
{
533+
__asm volatile (
534+
"SVC %0 \n"
535+
:
536+
: "i" ( portSVC_UNMASK_INTERRUPTS ), "r" ( uxNewMaskValue )
537+
: "memory"
538+
);
539+
}
532540
}
533541

542+
void vPortClearInterruptMaskFromISR( UBaseType_t uxNewMaskValue )
543+
{
544+
__asm volatile (
545+
"MSR DAIFSET, #2 \n"
546+
"DSB SY \n"
547+
"ISB SY \n"
548+
"MSR ICC_PMR_EL1, %0 \n"
549+
"DSB SY \n"
550+
"ISB SY \n"
551+
"MSR DAIFCLR, #2 \n"
552+
"DSB SY \n"
553+
"ISB SY \n"
554+
:
555+
: "r" ( uxNewMaskValue )
556+
);
557+
}
534558
/*-----------------------------------------------------------*/
535559

536560
UBaseType_t uxPortSetInterruptMask( void )
537561
{
538-
uint32_t ulReturn;
539-
uint64_t ullPMRValue;
562+
UBaseType_t ullPMRValue;
563+
564+
/* Use SVC so this can be called safely from EL0 tasks. */
565+
__asm volatile (
566+
"svc %1 \n"
567+
"mov %0, x0 \n"
568+
: "=r" ( ullPMRValue )
569+
: "i" ( portSVC_MASK_ALL_INTERRUPTS )
570+
: "x0", "memory"
571+
);
572+
573+
return ullPMRValue;
574+
}
575+
576+
/* EL1/ISR variant to avoid SVC from interrupt context. */
577+
UBaseType_t uxPortSetInterruptMaskFromISR( void )
578+
{
579+
UBaseType_t ullPMRValue;
540580

541-
/* Interrupt in the CPU must be turned off while the ICCPMR is being
542-
* updated. */
543-
portDISABLE_INTERRUPTS();
544581
__asm volatile ( "MRS %0, ICC_PMR_EL1" : "=r" ( ullPMRValue ) );
545582

546-
if( ullPMRValue == ( configMAX_API_CALL_INTERRUPT_PRIORITY << portPRIORITY_SHIFT ) )
583+
if( ullPMRValue != ( configMAX_API_CALL_INTERRUPT_PRIORITY << portPRIORITY_SHIFT ) )
547584
{
548-
/* Interrupts were already masked. */
549-
ulReturn = pdTRUE;
550-
}
551-
else
552-
{
553-
ulReturn = pdFALSE;
554-
__asm volatile ( "MSR ICC_PMR_EL1, %0 \n"
555-
"DSB SY \n"
556-
"ISB SY \n"
585+
__asm volatile ( "MSR DAIFSET, #2 \n"
586+
"DSB SY \n"
587+
"ISB SY \n"
588+
"MSR ICC_PMR_EL1, %0 \n"
589+
"DSB SY \n"
590+
"ISB SY \n"
591+
"MSR DAIFCLR, #2 \n"
592+
"DSB SY \n"
593+
"ISB SY \n"
557594
::"r" ( configMAX_API_CALL_INTERRUPT_PRIORITY << portPRIORITY_SHIFT ) : "memory" );
558595
}
559596

560-
portENABLE_INTERRUPTS();
561-
562-
return ulReturn;
597+
return ullPMRValue;
563598
}
564599

565600
/*-----------------------------------------------------------*/
@@ -645,8 +680,9 @@ UBaseType_t uxPortSetInterruptMask( void )
645680
"dmb sy\n"
646681
"mov w1, #0\n"
647682
"str w1, [%x0]\n"
648-
"dsb sy\n"
649683
"sev\n"
684+
"dsb sy\n"
685+
"isb sy\n"
650686
:
651687
: "r" ( ulLock )
652688
: "memory", "w1"
@@ -732,7 +768,13 @@ UBaseType_t uxPortSetInterruptMask( void )
732768

733769
while( prvSpinTrylock( &ulGateWord[ eLockNum ] ) != 0 )
734770
{
735-
__asm volatile ( "wfe" );
771+
/* Follow Arm's recommended way of sleeping
772+
* sevl is used to prime the wait loop,
773+
* the first wfe wakes immediately as sevl has set the flag
774+
* the second wfe sleeps the core. This way the core is ensured
775+
* to sleep.
776+
*/
777+
__asm volatile ( "sevl; wfe; wfe" );
736778
}
737779
}
738780

@@ -771,16 +813,14 @@ UBaseType_t uxPortSetInterruptMask( void )
771813

772814
BaseType_t xPortGetCoreID( void )
773815
{
774-
register BaseType_t xCoreID;
775-
816+
BaseType_t xCoreID;
776817
__asm volatile (
777-
"mrs x0, MPIDR_EL1\n"
778-
"and %0, x0, #0xFF\n"
818+
"svc %1 \n"
819+
"mov %0, x0 \n"
779820
: "=r" ( xCoreID )
780-
:
781-
: "memory", "x0"
782-
);
783-
821+
: "i" ( portSVC_GET_CORE_ID )
822+
: "x0", "memory"
823+
);
784824
return xCoreID;
785825
}
786826

@@ -814,7 +854,9 @@ UBaseType_t uxPortSetInterruptMask( void )
814854
::"r" ( configMAX_API_CALL_INTERRUPT_PRIORITY << portPRIORITY_SHIFT ) : "memory" );
815855

816856
/* Ok to enable interrupts after the interrupt source has been cleared. */
817-
portENABLE_INTERRUPTS();
857+
__asm volatile ( "MSR DAIFCLR, #2\n"
858+
"DSB SY\n"
859+
"ISB SY\n" ::: "memory" );
818860

819861
#if ( configNUMBER_OF_CORES == 1 )
820862
ullPortYieldRequired = pdTRUE;

0 commit comments

Comments
 (0)