blob: 0257e961a3d8d03cc99fc17332a733bba3f0e181 [file] [log] [blame]
/*
* FreeRTOS Kernel V10.4.2
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* https://www.FreeRTOS.org
* https://github.com/FreeRTOS
*
* 1 tab == 4 spaces!
*/
/* Standard includes. */
#include <limits.h>
/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#if ( configUSE_PORT_OPTIMISED_TASK_SELECTION == 1 )
/* Check the configuration. */
#if( configMAX_PRIORITIES > 32 )
#error configUSE_PORT_OPTIMISED_TASK_SELECTION can only be set to 1 when configMAX_PRIORITIES is less than or equal to 32. It is very rare that a system requires more than 10 to 15 difference priorities as tasks that share a priority will time slice.
#endif
#endif /* configUSE_PORT_OPTIMISED_TASK_SELECTION */
#if( configISR_STACK_SIZE < ( configMINIMAL_STACK_SIZE * 2 ) )
#warning configISR_STACK_SIZE is probably too small!
#endif /* ( configISR_STACK_SIZE < configMINIMAL_STACK_SIZE * 2 ) */
#if( ( configMAX_API_CALL_INTERRUPT_PRIORITY > portMAX_PRIORITY ) || ( configMAX_API_CALL_INTERRUPT_PRIORITY < 2 ) )
#error configMAX_API_CALL_INTERRUPT_PRIORITY must be between 2 and 15
#endif
#if( ( configSUPPORT_FPU == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 0 ) )
#error configSUPPORT_DYNAMIC_ALLOCATION must be set to 1 to use this port with an FPU
#endif
/* A critical section is exited when the critical section nesting count reaches
this value. */
#define portNO_CRITICAL_NESTING ( ( uint32_t ) 0 )
/* Tasks are not created with a floating point context, but can be given a
floating point context after they have been created. A variable is stored as
part of the tasks context that holds portNO_FLOATING_POINT_CONTEXT if the task
does not have an FPU context, or any other value if the task does have an FPU
context. */
#define portNO_FLOATING_POINT_CONTEXT ( ( StackType_t ) 0 )
/* Only the IF bit is set so tasks start with interrupts enabled. */
#define portINITIAL_EFLAGS ( 0x200UL )
/* Error interrupts are at the highest priority vectors. */
#define portAPIC_LVT_ERROR_VECTOR ( 0xfe )
#define portAPIC_SPURIOUS_INT_VECTOR ( 0xff )
/* EFLAGS bits. */
#define portEFLAGS_IF ( 0x200UL )
/* FPU context size if FSAVE is used. */
#define portFPU_CONTEXT_SIZE_BYTES 108
/* The expected size of each entry in the IDT. Used to check structure packing
is set correctly. */
#define portEXPECTED_IDT_ENTRY_SIZE 8
/* Default flags setting for entries in the IDT. */
#define portIDT_FLAGS ( 0x8E )
/* This is the lowest possible ISR vector available to application code. */
#define portAPIC_MIN_ALLOWABLE_VECTOR ( 0x20 )
/* If configASSERT() is defined then the system stack is filled with this value
to allow for a crude stack overflow check. */
#define portSTACK_WORD ( 0xecececec )
/*-----------------------------------------------------------*/
/*
* Starts the first task executing.
*/
extern void vPortStartFirstTask( void );
/*
* Used to catch tasks that attempt to return from their implementing function.
*/
static void prvTaskExitError( void );
/*
* Complete one descriptor in the IDT.
*/
static void prvSetInterruptGate( uint8_t ucNumber, ISR_Handler_t pxHandlerFunction, uint8_t ucFlags );
/*
* The default handler installed in each IDT position.
*/
extern void vPortCentralInterruptWrapper( void );
/*
* Handler for portYIELD().
*/
extern void vPortYieldCall( void );
/*
* Configure the APIC to generate the RTOS tick.
*/
static void prvSetupTimerInterrupt( void );
/*
* Tick interrupt handler.
*/
extern void vPortTimerHandler( void );
/*
* Check an interrupt vector is not too high, too low, in use by FreeRTOS, or
* already in use by the application.
*/
static BaseType_t prvCheckValidityOfVectorNumber( uint32_t ulVectorNumber );
/*-----------------------------------------------------------*/
/* A variable is used to keep track of the critical section nesting. This
variable must be initialised to a non zero value to ensure interrupts don't
inadvertently become unmasked before the scheduler starts. It is set to zero
before the first task starts executing. */
volatile uint32_t ulCriticalNesting = 9999UL;
/* A structure used to map the various fields of an IDT entry into separate
structure members. */
struct IDTEntry
{
uint16_t usISRLow; /* Low 16 bits of handler address. */
uint16_t usSegmentSelector; /* Flat model means this is not changed. */
uint8_t ucZero; /* Must be set to zero. */
uint8_t ucFlags; /* Flags for this entry. */
uint16_t usISRHigh; /* High 16 bits of handler address. */
} __attribute__( ( packed ) );
typedef struct IDTEntry IDTEntry_t;
/* Use to pass the location of the IDT to the CPU. */
struct IDTPointer
{
uint16_t usTableLimit;
uint32_t ulTableBase; /* The address of the first entry in xInterruptDescriptorTable. */
} __attribute__( ( __packed__ ) );
typedef struct IDTPointer IDTPointer_t;
/* The IDT itself. */
static __attribute__ ( ( aligned( 32 ) ) ) IDTEntry_t xInterruptDescriptorTable[ portNUM_VECTORS ];
#if ( configUSE_COMMON_INTERRUPT_ENTRY_POINT == 1 )
/* A table in which application defined interrupt handlers are stored. These
are called by the central interrupt handler if a common interrupt entry
point it used. */
static ISR_Handler_t xInterruptHandlerTable[ portNUM_VECTORS ] = { NULL };
#endif /* configUSE_COMMON_INTERRUPT_ENTRY_POINT */
#if ( configSUPPORT_FPU == 1 )
/* Saved as part of the task context. If pucPortTaskFPUContextBuffer is NULL
then the task does not have an FPU context. If pucPortTaskFPUContextBuffer is
not NULL then it points to a buffer into which the FPU context can be saved. */
uint8_t *pucPortTaskFPUContextBuffer __attribute__((used)) = pdFALSE;
#endif /* configSUPPORT_FPU */
/* The stack used by interrupt handlers. */
static uint32_t ulSystemStack[ configISR_STACK_SIZE ] __attribute__((used)) = { 0 };
/* Don't use the very top of the system stack so the return address
appears as 0 if the debugger tries to unwind the stack. */
volatile uint32_t ulTopOfSystemStack __attribute__((used)) = ( uint32_t ) &( ulSystemStack[ configISR_STACK_SIZE - 5 ] );
/* If a yield is requested from an interrupt or from a critical section then
the yield is not performed immediately, and ulPortYieldPending is set to pdTRUE
instead to indicate the yield should be performed at the end of the interrupt
when the critical section is exited. */
volatile uint32_t ulPortYieldPending __attribute__((used)) = pdFALSE;
/* Counts the interrupt nesting depth. Used to know when to switch to the
interrupt/system stack and when to save/restore a complete context. */
volatile uint32_t ulInterruptNesting __attribute__((used)) = 0;
/*-----------------------------------------------------------*/
/*
* See header file for description.
*/
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
uint32_t ulCodeSegment;
/* Setup the initial stack as expected by the portFREERTOS_INTERRUPT_EXIT macro. */
*pxTopOfStack = 0x00;
pxTopOfStack--;
*pxTopOfStack = 0x00;
pxTopOfStack--;
/* Parameters first. */
*pxTopOfStack = ( StackType_t ) pvParameters;
pxTopOfStack--;
/* There is nothing to return to so assert if attempting to use the return
address. */
*pxTopOfStack = ( StackType_t ) prvTaskExitError;
pxTopOfStack--;
/* iret used to start the task pops up to here. */
*pxTopOfStack = portINITIAL_EFLAGS;
pxTopOfStack--;
/* CS */
__asm volatile( "movl %%cs, %0" : "=r" ( ulCodeSegment ) );
*pxTopOfStack = ulCodeSegment;
pxTopOfStack--;
/* First instruction in the task. */
*pxTopOfStack = ( StackType_t ) pxCode;
pxTopOfStack--;
/* General purpose registers as expected by a POPA instruction. */
*pxTopOfStack = 0xEA;
pxTopOfStack--;
*pxTopOfStack = 0xEC;
pxTopOfStack--;
*pxTopOfStack = 0xED1; /* EDX */
pxTopOfStack--;
*pxTopOfStack = 0xEB1; /* EBX */
pxTopOfStack--;
/* Hole for ESP. */
pxTopOfStack--;
*pxTopOfStack = 0x00; /* EBP */
pxTopOfStack--;
*pxTopOfStack = 0xE5; /* ESI */
pxTopOfStack--;
*pxTopOfStack = 0xeeeeeeee; /* EDI */
#if ( configSUPPORT_FPU == 1 )
{
pxTopOfStack--;
/* Buffer for FPU context, which is initialised to NULL as tasks are not
created with an FPU context. */
*pxTopOfStack = portNO_FLOATING_POINT_CONTEXT;
}
#endif /* configSUPPORT_FPU */
return pxTopOfStack;
}
/*-----------------------------------------------------------*/
static void prvSetInterruptGate( uint8_t ucNumber, ISR_Handler_t pxHandlerFunction, uint8_t ucFlags )
{
uint16_t usCodeSegment;
uint32_t ulBase = ( uint32_t ) pxHandlerFunction;
xInterruptDescriptorTable[ ucNumber ].usISRLow = ( uint16_t ) ( ulBase & USHRT_MAX );
xInterruptDescriptorTable[ ucNumber ].usISRHigh = ( uint16_t ) ( ( ulBase >> 16UL ) & USHRT_MAX );
/* When the flat model is used the CS will never change. */
__asm volatile( "mov %%cs, %0" : "=r" ( usCodeSegment ) );
xInterruptDescriptorTable[ ucNumber ].usSegmentSelector = usCodeSegment;
xInterruptDescriptorTable[ ucNumber ].ucZero = 0;
xInterruptDescriptorTable[ ucNumber ].ucFlags = ucFlags;
}
/*-----------------------------------------------------------*/
void vPortSetupIDT( void )
{
uint32_t ulNum;
IDTPointer_t xIDT;
#if ( configUSE_COMMON_INTERRUPT_ENTRY_POINT == 1 )
{
for( ulNum = 0; ulNum < portNUM_VECTORS; ulNum++ )
{
/* If a handler has not already been installed on this vector. */
if( ( xInterruptDescriptorTable[ ulNum ].usISRLow == 0x00 ) && ( xInterruptDescriptorTable[ ulNum ].usISRHigh == 0x00 ) )
{
prvSetInterruptGate( ( uint8_t ) ulNum, vPortCentralInterruptWrapper, portIDT_FLAGS );
}
}
}
#endif /* configUSE_COMMON_INTERRUPT_ENTRY_POINT */
/* Set IDT address. */
xIDT.ulTableBase = ( uint32_t ) xInterruptDescriptorTable;
xIDT.usTableLimit = sizeof( xInterruptDescriptorTable ) - 1;
/* Set IDT in CPU. */
__asm volatile( "lidt %0" :: "m" (xIDT) );
}
/*-----------------------------------------------------------*/
static void prvTaskExitError( void )
{
/* A function that implements a task must not exit or attempt to return to
its caller as there is nothing to return to. If a task wants to exit it
should instead call vTaskDelete( NULL ).
Artificially force an assert() to be triggered if configASSERT() is
defined, then stop here so application writers can catch the error. */
configASSERT( ulCriticalNesting == ~0UL );
portDISABLE_INTERRUPTS();
for( ;; );
}
/*-----------------------------------------------------------*/
static void prvSetupTimerInterrupt( void )
{
extern void vPortAPICErrorHandlerWrapper( void );
extern void vPortAPICSpuriousHandler( void );
/* Initialise LAPIC to a well known state. */
portAPIC_LDR = 0xFFFFFFFF;
portAPIC_LDR = ( ( portAPIC_LDR & 0x00FFFFFF ) | 0x00000001 );
portAPIC_LVT_TIMER = portAPIC_DISABLE;
portAPIC_LVT_PERF = portAPIC_NMI;
portAPIC_LVT_LINT0 = portAPIC_DISABLE;
portAPIC_LVT_LINT1 = portAPIC_DISABLE;
portAPIC_TASK_PRIORITY = 0;
/* Install APIC timer ISR vector. */
prvSetInterruptGate( ( uint8_t ) portAPIC_TIMER_INT_VECTOR, vPortTimerHandler, portIDT_FLAGS );
/* Install API error handler. */
prvSetInterruptGate( ( uint8_t ) portAPIC_LVT_ERROR_VECTOR, vPortAPICErrorHandlerWrapper, portIDT_FLAGS );
/* Install Yield handler. */
prvSetInterruptGate( ( uint8_t ) portAPIC_YIELD_INT_VECTOR, vPortYieldCall, portIDT_FLAGS );
/* Install spurious interrupt vector. */
prvSetInterruptGate( ( uint8_t ) portAPIC_SPURIOUS_INT_VECTOR, vPortAPICSpuriousHandler, portIDT_FLAGS );
/* Enable the APIC, mapping the spurious interrupt at the same time. */
portAPIC_SPURIOUS_INT = portAPIC_SPURIOUS_INT_VECTOR | portAPIC_ENABLE_BIT;
/* Set timer error vector. */
portAPIC_LVT_ERROR = portAPIC_LVT_ERROR_VECTOR;
/* Set the interrupt frequency. */
portAPIC_TMRDIV = portAPIC_DIV_16;
portAPIC_TIMER_INITIAL_COUNT = ( ( configCPU_CLOCK_HZ >> 4UL ) / configTICK_RATE_HZ ) - 1UL;
}
/*-----------------------------------------------------------*/
BaseType_t xPortStartScheduler( void )
{
BaseType_t xWord;
/* Some versions of GCC require the -mno-ms-bitfields command line option
for packing to work. */
configASSERT( sizeof( struct IDTEntry ) == portEXPECTED_IDT_ENTRY_SIZE );
/* Fill part of the system stack with a known value to help detect stack
overflow. A few zeros are left so GDB doesn't get confused unwinding
the stack. */
for( xWord = 0; xWord < configISR_STACK_SIZE - 20; xWord++ )
{
ulSystemStack[ xWord ] = portSTACK_WORD;
}
/* Initialise Interrupt Descriptor Table (IDT). */
vPortSetupIDT();
/* Initialise LAPIC and install system handlers. */
prvSetupTimerInterrupt();
/* Make sure the stack used by interrupts is aligned. */
ulTopOfSystemStack &= ~portBYTE_ALIGNMENT_MASK;
ulCriticalNesting = 0;
/* Enable LAPIC Counter.*/
portAPIC_LVT_TIMER = portAPIC_TIMER_PERIODIC | portAPIC_TIMER_INT_VECTOR;
/* Sometimes needed. */
portAPIC_TMRDIV = portAPIC_DIV_16;
/* Should not return from the following function as the scheduler will then
be executing the tasks. */
vPortStartFirstTask();
return 0;
}
/*-----------------------------------------------------------*/
void vPortEndScheduler( void )
{
/* Not implemented in ports where there is nothing to return to.
Artificially force an assert. */
configASSERT( ulCriticalNesting == 1000UL );
}
/*-----------------------------------------------------------*/
void vPortEnterCritical( void )
{
if( ulCriticalNesting == 0 )
{
#if( configMAX_API_CALL_INTERRUPT_PRIORITY == portMAX_PRIORITY )
{
__asm volatile( "cli" );
}
#else
{
portAPIC_TASK_PRIORITY = portMAX_API_CALL_PRIORITY;
configASSERT( portAPIC_TASK_PRIORITY == portMAX_API_CALL_PRIORITY );
}
#endif
}
/* Now interrupts are disabled ulCriticalNesting can be accessed
directly. Increment ulCriticalNesting to keep a count of how many times
portENTER_CRITICAL() has been called. */
ulCriticalNesting++;
}
/*-----------------------------------------------------------*/
void vPortExitCritical( void )
{
if( ulCriticalNesting > portNO_CRITICAL_NESTING )
{
/* Decrement the nesting count as the critical section is being
exited. */
ulCriticalNesting--;
/* If the nesting level has reached zero then all interrupt
priorities must be re-enabled. */
if( ulCriticalNesting == portNO_CRITICAL_NESTING )
{
/* Critical nesting has reached zero so all interrupt priorities
should be unmasked. */
#if( configMAX_API_CALL_INTERRUPT_PRIORITY == portMAX_PRIORITY )
{
__asm volatile( "sti" );
}
#else
{
portAPIC_TASK_PRIORITY = 0;
}
#endif
/* If a yield was pended from within the critical section then
perform the yield now. */
if( ulPortYieldPending != pdFALSE )
{
ulPortYieldPending = pdFALSE;
__asm volatile( portYIELD_INTERRUPT );
}
}
}
}
/*-----------------------------------------------------------*/
uint32_t ulPortSetInterruptMask( void )
{
volatile uint32_t ulOriginalMask;
/* Set mask to max syscall priority. */
#if( configMAX_API_CALL_INTERRUPT_PRIORITY == portMAX_PRIORITY )
{
/* Return whether interrupts were already enabled or not. Pop adjusts
the stack first. */
__asm volatile( "pushf \t\n"
"pop %0 \t\n"
"cli "
: "=rm" (ulOriginalMask) :: "memory" );
ulOriginalMask &= portEFLAGS_IF;
}
#else
{
/* Return original mask. */
ulOriginalMask = portAPIC_TASK_PRIORITY;
portAPIC_TASK_PRIORITY = portMAX_API_CALL_PRIORITY;
configASSERT( portAPIC_TASK_PRIORITY == portMAX_API_CALL_PRIORITY );
}
#endif
return ulOriginalMask;
}
/*-----------------------------------------------------------*/
void vPortClearInterruptMask( uint32_t ulNewMaskValue )
{
#if( configMAX_API_CALL_INTERRUPT_PRIORITY == portMAX_PRIORITY )
{
if( ulNewMaskValue != pdFALSE )
{
__asm volatile( "sti" );
}
}
#else
{
portAPIC_TASK_PRIORITY = ulNewMaskValue;
configASSERT( portAPIC_TASK_PRIORITY == ulNewMaskValue );
}
#endif
}
/*-----------------------------------------------------------*/
#if ( configSUPPORT_FPU == 1 )
void vPortTaskUsesFPU( void )
{
/* A task is registering the fact that it needs an FPU context. Allocate a
buffer into which the context can be saved. */
pucPortTaskFPUContextBuffer = ( uint8_t * ) pvPortMalloc( portFPU_CONTEXT_SIZE_BYTES );
configASSERT( pucPortTaskFPUContextBuffer );
/* Initialise the floating point registers. */
__asm volatile( "fninit" );
}
#endif /* configSUPPORT_FPU */
/*-----------------------------------------------------------*/
void vPortAPICErrorHandler( void )
{
/* Variable to hold the APIC error status for viewing in the debugger. */
volatile uint32_t ulErrorStatus = 0;
portAPIC_ERROR_STATUS = 0;
ulErrorStatus = portAPIC_ERROR_STATUS;
( void ) ulErrorStatus;
/* Force an assert. */
configASSERT( ulCriticalNesting == ~0UL );
}
/*-----------------------------------------------------------*/
#if( configUSE_COMMON_INTERRUPT_ENTRY_POINT == 1 )
void vPortCentralInterruptHandler( uint32_t ulVector )
{
if( ulVector < portNUM_VECTORS )
{
if( xInterruptHandlerTable[ ulVector ] != NULL )
{
( xInterruptHandlerTable[ ulVector ] )();
}
}
/* Check for a system stack overflow. */
configASSERT( ulSystemStack[ 10 ] == portSTACK_WORD );
configASSERT( ulSystemStack[ 12 ] == portSTACK_WORD );
configASSERT( ulSystemStack[ 14 ] == portSTACK_WORD );
}
#endif /* configUSE_COMMON_INTERRUPT_ENTRY_POINT */
/*-----------------------------------------------------------*/
#if ( configUSE_COMMON_INTERRUPT_ENTRY_POINT == 1 )
BaseType_t xPortRegisterCInterruptHandler( ISR_Handler_t pxHandler, uint32_t ulVectorNumber )
{
BaseType_t xReturn;
xReturn = prvCheckValidityOfVectorNumber( ulVectorNumber );
if( xReturn != pdFAIL )
{
/* Save the handler passed in by the application in the vector number
passed in. The addresses are then called from the central interrupt
handler. */
xInterruptHandlerTable[ ulVectorNumber ] = pxHandler;
}
return xReturn;
}
#endif /* configUSE_COMMON_INTERRUPT_ENTRY_POINT */
/*-----------------------------------------------------------*/
BaseType_t xPortInstallInterruptHandler( ISR_Handler_t pxHandler, uint32_t ulVectorNumber )
{
BaseType_t xReturn;
xReturn = prvCheckValidityOfVectorNumber( ulVectorNumber );
if( xReturn != pdFAIL )
{
taskENTER_CRITICAL();
{
/* Update the IDT to include the application defined handler. */
prvSetInterruptGate( ( uint8_t ) ulVectorNumber, ( ISR_Handler_t ) pxHandler, portIDT_FLAGS );
}
taskEXIT_CRITICAL();
}
return xReturn;
}
/*-----------------------------------------------------------*/
static BaseType_t prvCheckValidityOfVectorNumber( uint32_t ulVectorNumber )
{
BaseType_t xReturn;
/* Check validity of vector number. */
if( ulVectorNumber >= portNUM_VECTORS )
{
/* Too high. */
xReturn = pdFAIL;
}
else if( ulVectorNumber < portAPIC_MIN_ALLOWABLE_VECTOR )
{
/* Too low. */
xReturn = pdFAIL;
}
else if( ulVectorNumber == portAPIC_TIMER_INT_VECTOR )
{
/* In use by FreeRTOS. */
xReturn = pdFAIL;
}
else if( ulVectorNumber == portAPIC_YIELD_INT_VECTOR )
{
/* In use by FreeRTOS. */
xReturn = pdFAIL;
}
else if( ulVectorNumber == portAPIC_LVT_ERROR_VECTOR )
{
/* In use by FreeRTOS. */
xReturn = pdFAIL;
}
else if( ulVectorNumber == portAPIC_SPURIOUS_INT_VECTOR )
{
/* In use by FreeRTOS. */
xReturn = pdFAIL;
}
else if( xInterruptHandlerTable[ ulVectorNumber ] != NULL )
{
/* Already in use by the application. */
xReturn = pdFAIL;
}
else
{
xReturn = pdPASS;
}
return xReturn;
}
/*-----------------------------------------------------------*/
void vGenerateYieldInterrupt( void )
{
__asm volatile( portYIELD_INTERRUPT );
}