-
Notifications
You must be signed in to change notification settings - Fork 4
/
os.c
357 lines (302 loc) · 11.1 KB
/
os.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
//==================================================================================================
// INCLUDES
//==================================================================================================
#include "os.h"
#include "iferr.h"
#include "schedl_timer.h"
#include "stm32f3xx_hal.h"
//==================================================================================================
// DEFINES - MACROS
//==================================================================================================
//==================================================================================================
// ENUMS - STRUCTS - TYPEDEFS
//==================================================================================================
/**
* TCBState indicates whether the TCB can be used by OS_ThreadCreate
* to create a new thread.
*/
typedef enum
{
TCBStateFree,
TCBStateActive
} TCBState_t;
/**
* Thread Control Block
*
* IMPORTANT!
* The fn OSAsm_Start and OSAsm_ThreadSwitch, implemented in os_asm.s, expect the stack pointer
* to be placed first in the struct. Don't shuffle it!
*/
typedef struct TCB
{
uint32_t *sp; /* Stack pointer, valid for threads not running */
struct TCB *next; /* Pointer to circular-linked-list of TCBs */
uint32_t sleep; /* Sleep duration in ms, zero means not sleeping */
TCBState_t status; /* TCB active or free */
Semaphore_t *blocked; /* Pointer to semaphore on which the thread is blocked, NULL if not blocked */
uint8_t priority; /* Thread priority, 0 is highest, 255 is lowest */
const char *name; /* Descriptive name to facilitate debugging */
} TCB_t;
//==================================================================================================
// GLOBAL AND STATIC VARIABLES
//==================================================================================================
static TCB_t TCBs[MAXNUMTHREADS];
static uint32_t Stacks[MAXNUMTHREADS][STACKSIZE];
/* Pointer to the currently running thread */
TCB_t *RunPt;
/* The variable ActiveTCBsCount tracks the number of TCBs in use by the OS */
static uint32_t ActiveTCBsCount;
//==================================================================================================
// FUNCTION PROTOTYPES
//==================================================================================================
/**
* The fn OS_InitTCBsStatus initializes all TCBs' statuses to be free at startup.
*/
static void OS_InitTCBsStatus(void);
/**
* The fn OS_Init initializes the SchedlTimer and the TCBs.
*/
void OS_Init(uint32_t scheduler_frequency_hz);
/**
* The fn OS_SetInitialStack sets up the thread's stack as if it had already been running and then suspended.
* Finally, it sets the TCB's SP (stack pointer) to the top of the stack (grows downwards).
* Check the "STM32 Cortex-M4 Programming Manual" on page 18 for the list of processor core registers.
*/
static void OS_SetInitialStack(uint32_t tcb_idx);
/**
* The fn OS_Thread_CreateFirst establishes the circular linked list of TCBs with one node,
* and points RunPt to that node. The fn must be called before the OS is launched.
*/
void OS_Thread_CreateFirst(void (*task)(void), uint8_t priority, const char *name);
/**
* The fn OS_Thread_Create adds a new thread to the circular linked list of TCBs, then runs it.
* It fails if all the TCBs are already active.
*
* The fn can be called both:
* - before the OS is launched (but after the first thread is created);
* - after the OS is launched (by a running thread).
*
* The thread that calls this function keeps running until the end of its scheduled time-slice.
* The new thread is run next.
*/
void OS_Thread_Create(void (*task)(void), uint8_t priority, const char *name);
/**
* The fn OS_Launch enables the SchedlTimer, then calls OSAsm_Start, which launches the first thread.
*/
void OS_Launch(void);
/**
* The fn OSAsm_Start, implemented in os_asm.s, is called by OS_Launch once.
* It "restores" the first thread's stack on the main stack.
*/
extern void OSAsm_Start(void);
/**
* The fn OSAsm_ThreadSwitch, implemented in os_asm.s, is periodically called by the SchedlTimer (ISR).
* It preemptively switches to the next thread, that is, it stores the stack of the running
* thread and restores the stack of the next thread.
* It calls OS_Schedule to determine which thread is run next and update RunPt.
*/
extern void OSAsm_ThreadSwitch(void);
/**
* The fn OS_Scheduler is called by OSAsm_ThreadSwitch and is responsible for determining
* which thread is run next.
*/
void OS_Scheduler(void);
/**
* The fn OS_Thread_Suspend halts the current thread and switches to the next.
* It's called by the running thread itself.
*/
void OS_Thread_Suspend(void);
/**
* The fn OS_Thread_Sleep makes the current thread dormant for a specified time.
* It's called by the running thread itself.
* The fn OS_DecrementTCBsSleepDuration is called by the SysTick ISR every ms and decrements the
* the value of sleep on the TCBs.
*/
void OS_Thread_Sleep(uint32_t ms);
void OS_DecrementTCBsSleepDuration(void);
/**
* The fn OS_Thread_Kill kills the thread that calls it, then starts the thread scheduled next.
* It fails if the last active thread tries to kill itself.
*/
void OS_Thread_Kill(void);
/**
* The fn OS_Semaphore_Wait decrements the semaphore counter.
* If the new counter's value is < 0, it marks the current thread as blocked and switches
* to the next one.
*/
void OS_Semaphore_Wait(Semaphore_t *sem);
/**
* The fn OS_Semaphore_Signal increments the semaphore counter.
* If the new counter's value is <= 0, it wakes up the next thread blocked on that semaphore.
*/
void OS_Semaphore_Signal(Semaphore_t *sem);
//==================================================================================================
// IMPLEMENTATION
//==================================================================================================
static void OS_InitTCBsStatus(void)
{
for (uint32_t idx = 0; idx < MAXNUMTHREADS; idx++)
{
TCBs[idx].status = TCBStateFree;
}
}
void OS_Init(uint32_t scheduler_frequency_hz)
{
SchedlTimer_Init(scheduler_frequency_hz);
OS_InitTCBsStatus();
}
static void OS_SetInitialStack(uint32_t tcb_idx)
{
/* From the "STM32 Cortex-M4 Programming Manual" on page 23:
* attempting to execute instructions when the T bit is 0 results in a fault or lockup */
Stacks[tcb_idx][STACKSIZE - 1] = 0x01000000; /* Thumb Bit (PSR) */
// Stacks[tcb_idx][STACKSIZE - 2] = /* R15 (PC) -> set later in fn OS_AddThreads
Stacks[tcb_idx][STACKSIZE - 3] = 0x14141414; /* R14 (LR) */
Stacks[tcb_idx][STACKSIZE - 4] = 0x12121212; /* R12 */
Stacks[tcb_idx][STACKSIZE - 5] = 0x03030303; /* R3 */
Stacks[tcb_idx][STACKSIZE - 6] = 0x02020202; /* R2 */
Stacks[tcb_idx][STACKSIZE - 7] = 0x01010101; /* R1 */
Stacks[tcb_idx][STACKSIZE - 8] = 0x00000000; /* R0 */
Stacks[tcb_idx][STACKSIZE - 9] = 0x11111111; /* R11 */
Stacks[tcb_idx][STACKSIZE - 10] = 0x10101010; /* R10 */
Stacks[tcb_idx][STACKSIZE - 11] = 0x09090909; /* R9 */
Stacks[tcb_idx][STACKSIZE - 12] = 0x08080808; /* R8 */
Stacks[tcb_idx][STACKSIZE - 13] = 0x07070707; /* R7 */
Stacks[tcb_idx][STACKSIZE - 14] = 0x06060606; /* R6 */
Stacks[tcb_idx][STACKSIZE - 15] = 0x05050505; /* R5 */
Stacks[tcb_idx][STACKSIZE - 16] = 0x04040404; /* R4 */
TCBs[tcb_idx].sp = &Stacks[tcb_idx][STACKSIZE - 16]; /* Thread's stack pointer */
}
void OS_Thread_CreateFirst(void (*task)(void), uint8_t priority, const char *name)
{
assert_or_panic(ActiveTCBsCount == 0);
TCBs[0].next = &(TCBs[0]);
TCBs[0].sleep = 0;
TCBs[0].status = TCBStateActive;
TCBs[0].blocked = NULL;
TCBs[0].priority = priority;
TCBs[0].name = name;
OS_SetInitialStack(0);
Stacks[0][STACKSIZE - 2] = (int32_t)task; /* PC */
/* Thread 0 will run first */
RunPt = &(TCBs[0]);
ActiveTCBsCount++;
}
void OS_Thread_Create(void (*task)(void), uint8_t priority, const char *name)
{
assert_or_panic(ActiveTCBsCount > 0 && ActiveTCBsCount < MAXNUMTHREADS);
__disable_irq();
/* Find next available TCB */
uint32_t new_tcb_idx;
for (new_tcb_idx = 0; new_tcb_idx < MAXNUMTHREADS; new_tcb_idx++)
{
if (TCBs[new_tcb_idx].status == TCBStateFree)
break;
}
TCBs[new_tcb_idx].next = RunPt->next;
RunPt->next = &(TCBs[new_tcb_idx]);
TCBs[new_tcb_idx].sleep = 0;
TCBs[new_tcb_idx].status = TCBStateActive;
TCBs[new_tcb_idx].blocked = NULL;
TCBs[new_tcb_idx].priority = priority;
TCBs[new_tcb_idx].name = name;
OS_SetInitialStack(new_tcb_idx);
Stacks[new_tcb_idx][STACKSIZE - 2] = (int32_t)task; /* PC */
ActiveTCBsCount++;
__enable_irq();
}
void OS_Launch(void)
{
assert_or_panic(ActiveTCBsCount > 0);
/* Prevent the timer's ISR from firing before OSAsm_Start is called */
__disable_irq();
SchedlTimer_Start();
OSAsm_Start();
/* This statement should not be reached */
panic();
}
void OS_Scheduler(void)
{
/* If this fn has been invoked by OS_Thread_Kill, the current TCB has been removed from the
* linked list, so it's correct to start iterating from the next TCB */
TCB_t *next_pt = RunPt->next;
TCB_t *iterating_pt = next_pt;
/* Search for highest priority thread not sleeping or blocked */
uint32_t max_priority = UINT8_MAX + 1;
TCB_t *best_pt = next_pt;
do
{
if ((iterating_pt->priority < max_priority) && (iterating_pt->sleep == 0) && (iterating_pt->blocked == NULL))
{
best_pt = iterating_pt;
max_priority = best_pt->priority;
}
iterating_pt = iterating_pt->next;
} while (iterating_pt != next_pt);
RunPt = best_pt;
}
void OS_Thread_Suspend(void)
{
SchedlTimer_ResetCounter();
}
void OS_Thread_Sleep(uint32_t sleep_duration_ms)
{
RunPt->sleep = sleep_duration_ms;
OS_Thread_Suspend();
}
void OS_DecrementTCBsSleepDuration(void)
{
for (size_t tcb_idx = 0; tcb_idx < MAXNUMTHREADS; tcb_idx++)
{
if (TCBs[tcb_idx].sleep > 0)
{
TCBs[tcb_idx].sleep -= 1;
}
}
}
void OS_Thread_Kill(void)
{
assert_or_panic(ActiveTCBsCount > 1);
__disable_irq();
TCB_t *previous_tcb = RunPt;
while (1)
{
previous_tcb = previous_tcb->next;
if (previous_tcb->next == RunPt)
break;
}
TCB_t *next_tcb = RunPt->next;
previous_tcb->next = next_tcb;
RunPt->status = TCBStateFree;
ActiveTCBsCount--;
__enable_irq();
OS_Thread_Suspend();
}
void OS_Semaphore_Wait(Semaphore_t *sem)
{
__disable_irq();
(*sem) = (*sem) - 1;
if ((*sem) < 0)
{
RunPt->blocked = sem; /* Reason the thread is blocked */
__enable_irq();
OS_Thread_Suspend();
}
__enable_irq();
}
void OS_Semaphore_Signal(Semaphore_t *sem)
{
__disable_irq();
(*sem) = (*sem) + 1;
if ((*sem) <= 0)
{
/* Search for a TCB blocked on this semaphore and wake it up */
TCB_t *a_tcb = RunPt->next;
while (a_tcb->blocked != sem)
{
a_tcb = a_tcb->next;
}
a_tcb->blocked = 0;
}
__enable_irq();
}