305 lines
7.5 KiB
C

/*
* Copyright 2015 Daniel Versluis
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* schedule.h
*
* Created on: 12 nov. 2013
* Author: Daniel Versluis
* Email: VersD @ hr.nl
*
* Description:
* Contains the scheduler and the systick ISR.
* Simple and incomplete implementation.
*
*/
#include <stdlib.h>
#include <stdbool.h>
#include <stm32f4xx.h>
#include "VersdOS.h"
//The (external) assembly functions
void pushRegistersToCurrentPSP(void);
void popRegistersFromCurrentPSP(void);
void * readPSP(void);
void writePSP(void * ptr);
void returnToPSP(void);
void returnToMSP(void);
void toUnprivileged(void);
#define TASK_MASK MAX_TASKS-1
#define IDLE_TASK MAX_TASKS
//What states can our task have?
enum taskState {UNUSED = 0, RUNNING, READY, SLEEPING_DELAY};
//The task itself
typedef struct _task{
int *stack; // pointer to the stack on the heap
uint32_t counter; // a counter for delays
void (*function)(void); // function to execute
enum taskState state; // state
int8_t priority; // priority
} task;
// List of tasks
//add one space for idle task
task taskList[MAX_TASKS+1];
task * currentTask;
task * taskToExecute;
// Idle task
void idleTask(void)
{
while (1)
{
__asm(" wfi"); // Sleep until next SysTick
}
}
void addTaskToListAtIndex(void (*function)(void), uint32_t stackSize, int8_t priority, size_t pos)
{
task *taskToAdd = &taskList[pos];
taskToAdd->function = function;
// Allocate memory... do we wanna use malloc or our own implementation ;-) ?
taskToAdd->stack = (int *)malloc(stackSize)+stackSize;
/*
* For debugging purposes we initialize the stack with
* values that we can recognize.
*/
*(--(taskToAdd->stack)) = 0x01000000; //XSPR Thumb bit set
*(--(taskToAdd->stack)) = (int)taskToAdd->function; //set PC to function pointer, cast as int to silence the compiler
*(--(taskToAdd->stack)) = 0xFFFFFFFD; //LR, return with process stack (PSP)
*(--(taskToAdd->stack)) = 0x0000000C; //R12 Initial values used for debugging purposes
*(--(taskToAdd->stack)) = 0x00000003; //R3
*(--(taskToAdd->stack)) = 0x00000002; //R2
*(--(taskToAdd->stack)) = 0x00000001; //R1
*(--(taskToAdd->stack)) = 0x00000000; //R0
if(pos!=IDLE_TASK)
{
*(--(taskToAdd->stack)) = 0x0000000B; //R11
*(--(taskToAdd->stack)) = 0x0000000A; //R10
*(--(taskToAdd->stack)) = 0x00000009; //R9
*(--(taskToAdd->stack)) = 0x00000008; //R8
*(--(taskToAdd->stack)) = 0x00000007; //R7
*(--(taskToAdd->stack)) = 0x00000006; //R6
*(--(taskToAdd->stack)) = 0x00000005; //R5
*(--(taskToAdd->stack)) = 0x00000004; //R4
// Initialize the task properties
taskToAdd->state = READY;
}else{
taskToAdd->state = RUNNING;
currentTask = taskToAdd;
// Update the CPU PSP with our new stack pointer
writePSP(taskToAdd->stack);
}
taskToAdd->priority = priority;
}
/*
* Function to add a new task to the taskList
* Not sorted, prioritized or any of that kind.
*
* It simply allocates memory for the new task stack,
* fills the stack up so that the context switch will
* successfully pop these registers and start running
* at the correct address when returning from the SysTick ISR
*/
void addTaskToList(void (*function)(void), uint32_t stackSize, int8_t priority)
{
size_t i = 0;
// Simply find the next empty slot
// Loops when no more slots are available
while(taskList[i].state != UNUSED)
{
//increment i and roll back at the limit
i++;
i &= TASK_MASK;
}
addTaskToListAtIndex(function, stackSize, priority, i);
}
void startVersdOS(uint16_t sysTickPeriodIn_ms) {
// Configure SysTick of 1 ms
SysTick->LOAD = sysTickPeriodIn_ms * CLOCK_FREQ_IN_KHz - 1;
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk //Clock source selection = Processor clock (AHB)
| SysTick_CTRL_TICKINT_Msk //Counting down to zero to asserts the SysTick exception request
| SysTick_CTRL_ENABLE_Msk; //Counter enable
//set systick and pendsv interrupt priority to lowest.
//svc will be highest.
SCB->SHP[2] |= 0xFF<<24;
SCB->SHP[2] |= 0xFF<<16;
// Create Idle task
addTaskToListAtIndex(idleTask, 128, -1, IDLE_TASK);
__asm(" wfi"); // Sleep until next SysTick
}
// currentTask is running now, return next task to run
task * schedule()
{
task* tempTaskPtr = currentTask;
task *idleTaskPtr = &taskList[IDLE_TASK];
// tempTaskPtr->state = READY;
int teller=0;
//Find next ready, non idle task.
do
{
tempTaskPtr++;
if( (tempTaskPtr-1) == idleTaskPtr || tempTaskPtr == idleTaskPtr)
{
//since idle task is the last in the list, we've reached the end
//and need to continue at the beginning
tempTaskPtr = &taskList[0];
}
}while(tempTaskPtr->state != READY && teller++ <= MAX_TASKS);
//if no task was found
if(tempTaskPtr->state != READY)
{
//idle task
tempTaskPtr = idleTaskPtr;
}
return tempTaskPtr;
}
// flag used in blocking_delay function
bool SysTick_flag;
void decrement_sleeping_delay()
{
for (size_t i=0; i < MAX_TASKS; i++)
{
if (taskList[i].state == SLEEPING_DELAY)
{
if (taskList[i].counter == 0)
{
taskList[i].state = READY;
}
else
{
taskList[i].counter--;
}
}
}
}
/*
* Interrupt routine for the Systick timer
* simply calls the scheduler
* */
void SysTick_Handler(void)
{
SysTick_flag = true;
//decrement counter for task in SLEEPING_DELAY
decrement_sleeping_delay();
//select the next task
taskToExecute = schedule();
//request context switch
SCB->ICSR |= (1<<28);
}
__attribute__((naked)) // No function entry and exit code
void PendSV_Handler(void)
{
//Push {R4-R11} context to PSP
pushRegistersToCurrentPSP();
//Save the new stack pointer after the push
currentTask->stack = readPSP();
currentTask = taskToExecute;
//Load the new stack pointer from (new) currentTask
writePSP(currentTask->stack);
//Pop {R4-R11} context from PSP
popRegistersFromCurrentPSP();
toUnprivileged(); // <--- this line was added
returnToPSP();
}
/* The most simple SVC implementation
*/
void SVC_Handler(void)
{
uint32_t* sp = readPSP();
// get PC location
sp = sp + 6;
// get PC
uint16_t* pc = (uint16_t*)*sp;
// get SVC intruction
uint16_t instruction = *(pc - 1);
if ((instruction & 0xFF00) != 0xDF00) {
return;
}
uint8_t svc_id = instruction & 0x00FF;
switch (svc_id)
{
case 1: // taskYield
taskToExecute = schedule();
SCB->ICSR |= (1<<28);
break;
case 2: // setSysTick_10ms
SysTick->LOAD = 10 * CLOCK_FREQ_IN_KHz - 1;
break;
case 3: // setSysTick_1ms
SysTick->LOAD = 1 * CLOCK_FREQ_IN_KHz - 1;
break;
}
}
//Call Super Visor
void taskYield(void)
{
asm(" svc #1");
}
void delay(uint32_t ticks)
{
currentTask->state = SLEEPING_DELAY;
currentTask->counter = ticks;
taskYield();
}
void setSysTick_10ms(void)
{
asm(" svc #2");
}
void setSysTick_1ms(void)
{
asm(" svc #3");
}