/*
 * 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)
{
	taskToExecute = schedule();
	SCB->ICSR |= (1<<28);
}

//Call Super Visor
void taskYield(void)
{
	asm("	svc #1");
}

void delay(uint32_t ticks)
{
	currentTask->state = SLEEPING_DELAY;
	currentTask->counter = ticks;
	taskYield();
}


