# Week 1.4 ## Assinment 4.1 > The teacher has built a simple preemptive OS that is still missing some important features. In these assignments you’ll implement some extra features and gain a bigger understanding in how an OS operates. Next week we’ll start using a fully developed RTOS with all necessary features for a production environment. > A) Download the project VersdOS.zip. > B) Import this project, Open and Finish. Build and debug the project. The LEDs should blink. > C) Browse through the code and especially make sure you understand the scheduling process and the context switch. > D) Measure the periods at which the LEDs toggle using a logic analyzer and explain why this is not 2× but 8× the `blocking_delay` time. ![](https://live.kladjes.nl/uploads/1cdce708-a2e7-4a3d-b727-d5423b892687.png) In the Code it the values for blocking delay are 100, 200, 400, 800 for green, orange, red, blue. The `blocking_delay` function wait for this number of SysTicks, witch is set to $1ms$. I measure delays of $0.4s$, $0.8s$, $1.6s$ and $3.2s$. This is indeed 4x slower of what I would expect on first glance. This delay is because this is a preemptive scheduler, the other tasks run in between. There are 4 task all with the same priority, so each task takes 4 time longer. ## Assignment 4.2 > A) implement a non-blocking delay so that a task can request the OS to be kept out of the scheduling loop for a certain number of system ticks. The scheduler should remain preemptive and perform round-robin on all the available(not delayed) tasks. Once the requested number of system ticks have passed, the scheduler should include the task in the selection process. You may use the `taskYield()` function to let the OS know a task is ready to be switched out. To allow for a non blocking delay the OS should keep track of the time the to wake it up on the correct time. I did this by adding the new task state `SLEEPING_DELAY` to the `enum`. There already is an counter in the task struct. but no function that decrements this counter. So I added the following function the the OS. ```c void decrement_sleeping_delay() { for (usize_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--; } } } } ``` This function is added to the `SysTick_Handeler` before the scheduler is run. So that if the counter is 0 the `decrement_sleeping_delay` makes the task ready again, and is directly available for to be scheduled. This makes a delay of 0, wait for the next SysTick. The `SysTick_Handeler` now look like the following ```c 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); } ``` Before stating to write the delay function for the tasks, in the `schedule` funciton the line that sets the current task to ready sould be removed (line 163 in `VersdOS.c`). If the state is chanced during execution of the task, this line reset is at the next try to reschedule this state back to `READY` so the state will never actually be changed. As last the delay function itself. ```c void delay(uint32_t ticks) { currentTask->state = SLEEPING_DELAY; currentTask->counter = ticks; taskYield(); } ``` Now all the line `extern void delay(uint32_t ticks);` can be added to the begin the `main.c` and all the `blocking_delay` calls can be replace by `delay`. ### Result The compete code can be found at [/report-2/week_1.4/assignment_4.2/](https://git.gay/LailaTheElf/RTS10_reports/src/branch/main/report-2/week_1.4/assignment_4.2). ![logic-analyser measurement of the preemptive scheduler\label{42_logic}](https://live.kladjes.nl/uploads/c65810e2-a5fc-48f3-be6b-2f30acc5747a.png) This implementation works, only the delays are a bit off. To make it a nice counter again all delays should be decremented by one (figure \ref{42_logic} shows the output with the original timings, not the decremented ones). This is because with my implementation there is a delay added until the next SysTick after `delay` is called. `blocking_delay(0)` instantly returns without waiting for an SysTick (code below). It check if the counter is zero before waiting. For my `delay(0)`, it does wait for a SysTick and checks the counter after. To me the added delay make more sens. My delay is between number of the arguments in SysTick up to one extra, depending how long until the first SysTick is. ```c void blocking_delay(unsigned int ticks) { while (ticks != 0) { extern bool SysTick_flag; while (SysTick_flag == false); // busy wait SysTick_flag = false; ticks--; } } ``` ## Assignment 4.3 > A) Try changing the SysTick period to 2 ms within a task. A task can change the SysTick period to any value it wants, but you can imagine this isn’t desired behavior. I changed the `toggleBlue` task to the following ```c void toggleBlue(void) { while(1) { GPIOD->ODR ^= 1 << BLUE; delay(800-1); SysTick->LOAD = 2 * CLOCK_FREQ_IN_KHz - 1; } } ``` ![](https://live.kladjes.nl/uploads/06bc9958-cc48-4f9c-8b5f-300ea7efb231.png) > B) Implement unprivileged mode for the tasks. When succeeded the previous test should fail and be caught in an infinite ISR fault-handler. With Ctrl+F in some books I only found the _nPRIV_ bit in the control register (PM0214 - STM32 Cortex® -M4 MCUs and MPUs programming manual) as information how to switch privilege. I assume the interrupts used by the OS automatically go to privileged mode, and I only need to go to unprivileged mode before starting a task. The control register is a special register to it need to be set and read with special instructions. With `MSR` (page 187 of PM0214) a special register, like control, can be set and `MRS` (page 186 of PM0214) it can be read. With this information I added the following function to `VersdOS_ll.s` to set the `nPRIV` bit. ```asm .global toUnprivileged toUnprivileged: mrs R0, CONTROL // copy control register to R0 ORR R0, R0, #1 // clear bit 0 (nPRIV) msr CONTROL, R0 // copy R0 to control register bx lr ``` And called this function in the `PendSV_Handeler` ```c void toUnprivileged(void); __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 resulting code can be found at [/report-2/week_1.4/assignment_4.3/](https://git.gay/LailaTheElf/RTS10_reports/src/branch/main/report-2/week_1.4/assignment_4.3) And tested it. One step after the 'malicious' code, it jumps to this infinite loop. ![](https://live.kladjes.nl/uploads/e3f8273a-2a70-44d2-a6db-28f2a22fe5f2.png) and when the 'malicious' code is removed, it functions as normal. ## Assignment 4.4 > Tasks are no longer able to access the SysTick peripheral. It is time to implement a system call using the SVC interrupt mechanism. The `taskYield()` function would also classify as a system call. Write a system call function that will allow a task to change the SysTick period to a value between 1 and 10 ms . The system call should utilize the SVC mechanism by passing a different number than the `taskYield()` call. Make sure both calls keep working! Within the SVC interrupt service routine you will have to find the used SVC instruction using the PC value and look at the LSB to determine the system call number. You can also utilize the tasks’ stack to find the passed SysTick period parameter. When this parameter is invalid the system call should simply return. To find the instruction I first added the function to that contains the different `SVC` insurrections. I did this by adding the following code to `VersOS.c` and add the corresponding lines to `VersOS.h`. ```c void setSysTick_10ms(void) { asm(" svc #2"); } void setSysTick_1ms(void) { asm(" svc #3"); } ``` And made use of them in the `toggelBlue` task by updating it to the following. ```c void toggleBlue(void) { while(1) { GPIOD->ODR ^= 1 << BLUE; delay(800-1); setSysTick_10ms(); GPIOD->ODR ^= 1 << BLUE; delay(800-1); setSysTick_1ms(); } } ``` Then added an break-point at each `SVC` instruction and noted `PS` and `PC`. Then stepped to the `SVC_Handler`, and looked at the memory at the address stored in the `PSP` register. ``` task: PC: 0x08000660 SP: 0x200005ac SVC_Handler: PSP: 0x20000588 0x20000588 : 0x20000588 Address 0 - 3 4 - 7 8 - B C - F 20000580 1F030000 9C000020 1F030000 01000000 20000590 1F030000 9C000020 0C000000 F9060008 200005A0 62060008 00020001 B0050020 B0050020 200005B0 07000000 FDFFFFFF 0030012B 11D10260 200005C0 08230C68 E40348BF 0123F2D4 0C68E406 200005D0 48BF0523 EDD40C68 14F0EF0F E5D00623 200005E0 E7E79DF8 00004A68 52085200 4A6032BD 200005F0 78B40822 784B8DF8 14020020 00000000 20000600 00000000 3C060020 00000000 00000000 20000610 00000000 00000000 00000000 01000000 20000620 02000000 03000000 0C000000 FDFFFFFF 20000630 28020008 00020001 23020008 E8FF0120 20000640 11D11460 08251E68 F60348BF 0125F2D4 20000650 1E68F606 48BF0525 EDD41E68 16F0EF0F 20000660 E5D00625 E7E79DF8 0020082A 47D15A68 20000670 22F44072 5A605A68 5A605A68 42F00102 ``` In the memory map I found a littleendien encoded address witch was very close to the tasks `PC` at `0x200005A0`. This could very well be the place the `PC` is stored. To test this I updated the `SVC_Handler` to the following. ```c 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; } } ``` This code worked. The resulting code can be found at [/report-2/week_1.4/assignment_4.4/](https://git.gay/LailaTheElf/RTS10_reports/src/branch/main/report-2/week_1.4/assignment_4.4). ![](https://live.kladjes.nl/uploads/fb408589-67d1-4b37-8eb2-f852c65575f8.png) ## assignment 4.5 > The current scheduler implements a round-robin algorithm. In a real-time situation it would be necessary to give tasks different priorities in order to make important deadlines. > A) In reality tasks have more important jobs to do than just toggling a LED. This means tasks will take time. Use the original blocking delay function to give tasks a realistic waiting time in a random amount of milliseconds. In order to verify timing requirements with a logic analyzer, you can toggle the LED at the end of the busy waiting period. The LED is turn on while the task is await, and of when it is sleeping. | | green | orange | red | blue | | -------------- | ------ | ------ | ------ | ------ | | blocking_delay | 100 ms | 200 ms | 300 ms | 400 ms | | delay (sleep) | 100 ms | 200 ms | 300 ms | 400 ms | ```c void toggleGreen(void) { while(1) { GPIOD->ODR |= 1 << GREEN; blocking_delay(100); GPIOD->ODR &= ~(1 << GREEN); delay(100-1); } } void toggleOrange(void) { while(1) { GPIOD->ODR |= 1 << ORANGE; blocking_delay(200); GPIOD->ODR &= ~(1 << ORANGE); delay(200-1); } } void toggleRed(void) { while(1) { GPIOD->ODR |= 1 << RED; blocking_delay(300); GPIOD->ODR &= ~(1 << RED); delay(300-1); } } void toggleBlue(void) { while(1) { GPIOD->ODR |= 1 << BLUE; blocking_delay(400); GPIOD->ODR &= ~(1 << BLUE); delay(400-1); } } ``` > B) Implement a priority based scheduling algorithm. When adding tasks you should be able to pass the priority. When there are multiple tasks available with the same priority a round-robin algorithm should be applied. Test this functionality for multiple tasks with and without non-blocking delays. The priority is already passed with the `addTaskToList` function and this is saved in the task struct. I did update the priorities to have green as the highest priority, then orange, red and blue has the lowest priority. ```c addTaskToList(toggleGreen, 128, 2); addTaskToList(toggleOrange, 128, 3); addTaskToList(toggleRed, 128, 4); addTaskToList(toggleBlue, 128, 5); ``` > C) The round-robin period thus far equaled the SysTick period. Implement the ability to have a task-dependent round-robin period. E.g. if taskA and taskB have equal priorities, TaskA will run 2 SysTicks, taskB will need 5 SysTicks. This period should also be something passed at task creation. Verify this functionality with a logic analyzer. I added a new pointer `nextTaskPtr` to store the highest priority task that is ready. The do while loop is updated to first filter all task tasks that aren't ready than if there is no task yet in `nextTaskPtr` it should not compare priorities. Otherwise check if it has a higher priority than the current `nextTaskPtr`, if so replace it. ```c task * schedule() { task* nextTaskPtr = NULL; task* tempTaskPtr = currentTask; task* idleTaskPtr = &taskList[IDLE_TASK]; int teller=0; 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]; } if (tempTaskPtr->state != READY) { continue; } if (nextTaskPtr == NULL) { nextTaskPtr = tempTaskPtr; continue; } if (tempTaskPtr->priority < nextTaskPtr->priority) { nextTaskPtr = tempTaskPtr; continue; } } while (teller++ < MAX_TASKS); //if no task was found if(nextTaskPtr == NULL) { //idle task nextTaskPtr = idleTaskPtr; } return nextTaskPtr; } ``` The full code is available at [/report-2/week_1.4/assignment_4.5/](https://git.gay/LailaTheElf/RTS10_reports/src/branch/main/report-2/week_1.4/assignment_4.5). ### Testing ![](https://live.kladjes.nl/uploads/cbbdbcdb-0484-4074-8112-4a48b80f424f.png) Green (`D0`) has the highest priority, so it starts. Orange (`D2`) follows after the $100ms$ green is taking, orange should take $200ms$ but green is correctly halting orange. ![](https://live.kladjes.nl/uploads/a3e2237d-2099-4695-a943-b447098f3ef5.png) It is also clearly viable that red (`D1`) and blue (`D3`) have lower priorities. It seems to work correctly.