Skip to content

Commit 88764b9

Browse files
committed
added basic syscall handler that allows a task to yield, sleep and delete/create a task
1 parent eac1557 commit 88764b9

File tree

8 files changed

+380
-85
lines changed

8 files changed

+380
-85
lines changed

klib/rtos/base_task.hpp

Lines changed: 0 additions & 31 deletions
This file was deleted.

rtos/CMakeLists.txt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# set the sources
2+
set(SOURCES
3+
${CMAKE_CURRENT_SOURCE_DIR}/syscall.cpp
4+
)
5+
6+
set(HEADERS_PRIVATE
7+
${CMAKE_CURRENT_SOURCE_DIR}/base_task.hpp
8+
)
9+
10+
set(HEADERS_PUBLIC
11+
${CMAKE_CURRENT_SOURCE_DIR}/scheduler.hpp
12+
${CMAKE_CURRENT_SOURCE_DIR}/syscall.hpp
13+
${CMAKE_CURRENT_SOURCE_DIR}/task.hpp
14+
)
15+
16+
# add the library
17+
add_library(rtos OBJECT
18+
${SOURCES}
19+
${HEADERS_PUBLIC}
20+
${HEADERS_PRIVATE}
21+
)
22+
set_target_properties(rtos PROPERTIES FOLDER "rtos")
23+
24+
# alias projectname::rtos to rtos
25+
add_library(${PROJECT_NAME}::rtos ALIAS rtos)
26+
27+
# enable C++20 support for the library
28+
target_compile_features(rtos PUBLIC cxx_std_23)
29+
30+
# link to the traget_cpu_options to get all the cpu flags
31+
target_link_libraries(rtos PUBLIC target_cpu_options target_cpu klib)
32+
33+
# Global includes. Used by all targets
34+
# Note:
35+
# - header can be included by C++ code `#include <klib/klib.hpp>`
36+
# - header location in project: ${CMAKE_CURRENT_BINARY_DIR}/generated_headers
37+
target_include_directories(
38+
rtos PUBLIC
39+
"$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>"
40+
"$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>"
41+
"$<BUILD_INTERFACE:${GENERATED_HEADERS_DIR}>"
42+
)

rtos/base_task.hpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#ifndef KLIB_RTOS_BASE_TASK_HPP
2+
#define KLIB_RTOS_BASE_TASK_HPP
3+
4+
#include <cstdint>
5+
#include <cstdlib>
6+
7+
#include <klib/units.hpp>
8+
9+
namespace klib::rtos::detail {
10+
/**
11+
* @brief Task to run in the RTOS
12+
*
13+
* @tparam StackSize
14+
*/
15+
class base_task {
16+
public:
17+
// time to sleep for the task
18+
volatile klib::time::ms time_to_sleep;
19+
20+
// pointer to the current stack position
21+
size_t* stack_pointer;
22+
23+
// current priority of the task
24+
uint8_t current_priority;
25+
26+
// the base priority of the task
27+
const uint8_t priority;
28+
29+
/**
30+
* @brief Construct a new base task
31+
*
32+
* @param priority
33+
*/
34+
base_task(uint8_t priority = 0):
35+
time_to_sleep(0),
36+
stack_pointer(nullptr),
37+
current_priority(priority),
38+
priority(priority)
39+
{}
40+
};
41+
}
42+
43+
#endif

klib/rtos/scheduler.hpp renamed to rtos/scheduler.hpp

Lines changed: 100 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,24 @@
88
#include <rtos/rtos.hpp>
99

1010
#include "task.hpp"
11+
#include "syscall.hpp"
1112

1213
namespace klib::rtos {
13-
template <uint32_t CpuId, typename Irq>
1414
class scheduler {
15+
public:
16+
/**
17+
* @brief All the available syscalls
18+
*
19+
*/
20+
enum class syscalls {
21+
create_task = 0,
22+
delete_task = 1,
23+
yield = 2,
24+
sleep = 3,
25+
malloc = 4,
26+
free = 5
27+
};
28+
1529
protected:
1630
// make sure we can register our callback with the systick handler
1731
static_assert(SYSTICK_CALLBACK_ENABLED, "Systick callback needs to be enabled to switch tasks");
@@ -46,6 +60,13 @@ namespace klib::rtos {
4660
*
4761
*/
4862
static void schedule_irq() {
63+
// check if we have any tasks when we are called
64+
// by the systick interrupt
65+
if (tasks.size() == 0) {
66+
// no tasks to schedule
67+
return;
68+
}
69+
4970
// TODO: implement tick count for tasks
5071
// decrement sleep time for all tasks
5172
for (size_t i = 0; i < tasks.size(); i++) {
@@ -54,18 +75,6 @@ namespace klib::rtos {
5475
}
5576
}
5677

57-
// remove tasks that are marked for deletion. We move backwards
58-
// to not mess up the indexing when erasing. Note this is something
59-
// that is specific to our dynamic array implementation.
60-
// TODO: update this when changing away from the dynamic array
61-
if (tasks.size() > 0) {
62-
for (auto it = tasks.end() - 1; it != tasks.begin(); it--) {
63-
if ((*it)->marked_for_deletion) {
64-
tasks.erase(it);
65-
}
66-
}
67-
}
68-
6978
// call the scheduler to pick the next task
7079
schedule();
7180
}
@@ -76,14 +85,23 @@ namespace klib::rtos {
7685
*
7786
*/
7887
static void schedule() {
79-
if (!tasks.size()) {
80-
return;
81-
}
88+
// schedule the next task. We use the priority to pick
89+
// the next task to run. We always pick the first task
90+
// with the highest priority that is not sleeping. If no
91+
// task is ready we run the idle task.
92+
next_task = (current_task == nullptr ? &idle_task : (
93+
current_task->time_to_sleep.value == 0 ? current_task : &idle_task
94+
));
8295

8396
// TODO: implement a better scheduling algorithm
84-
// check if we have a task that is not sleeping
8597
for (size_t i = 0; i < tasks.size(); i++) {
86-
if (tasks[i]->time_to_sleep.value == 0) {
98+
if (tasks[i]->time_to_sleep.value > 0) {
99+
// task is sleeping
100+
continue;
101+
}
102+
103+
// task is not sleeping, check priority
104+
if (tasks[i]->current_priority > next_task->current_priority) {
87105
next_task = tasks[i];
88106
break;
89107
}
@@ -112,26 +130,22 @@ namespace klib::rtos {
112130
* @brief Start the scheduler
113131
*
114132
*/
133+
template <uint32_t CpuId, typename Irq>
115134
static void start() {
116135
// update the callback to our scheduler
117136
io::systick<CpuId, SYSTICK_CALLBACK_ENABLED>::set_callback(schedule_irq);
118137

119138
// register our interrupt to the pendsv interrupt
120-
// Irq::template register_irq<Irq::arm_vector::pendsv>(pendsv_handler);
121139
Irq::template register_irq<Irq::arm_vector::pendsv>(pendsv);
122140

141+
// register the syscall handler
142+
Irq::template register_irq<Irq::arm_vector::svcall>(klib::target::rtos::detail::sycall_handler<scheduler>);
143+
123144
// add the idle task to the scheduler. This task is always ready to run
124145
create_task(&idle_task);
125146

126147
// start the scheduler from the target
127148
klib::target::rtos::detail::scheduler_start();
128-
129-
// endless loop to wait on the start of the scheduler
130-
while (true) {
131-
// wait for a interrupt. The systick callback
132-
// should schedule what task to run
133-
asm volatile("wfi");
134-
}
135149
}
136150

137151
/**
@@ -144,25 +158,70 @@ namespace klib::rtos {
144158
// add the task to the queue
145159
tasks.push_back(task);
146160
}
147-
161+
162+
public:
148163
/**
149-
* @brief Sleep the current task for the given time
164+
* @brief Syscall handler, called when a syscall is invoked
165+
* by the target implementation. Parameters need to be passed
166+
* in registers r1-r3. These are converted to the correct
167+
* types based on the syscall.
150168
*
169+
* @param number
170+
* @param arg0
171+
* @param arg1
172+
* @param arg2
173+
* @return uint32_t
151174
*/
152-
static void sleep(klib::time::ms time) {
153-
// TODO: change this to use syscalls. This should stop
154-
// the current task from running and switch to another task
155-
// if available. As we are in unprivileged mode we cannot
156-
// trigger a context switch ourselves.
157-
158-
// set the time to sleep for the current task
159-
current_task->time_to_sleep.value = time.value;
160-
161-
// wait until the time to sleep is over
162-
while (current_task->time_to_sleep.value > 0) {
163-
// wait for the systick to wake us up
164-
asm volatile("wfi");
175+
static uint32_t syscall_handler(syscalls number, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
176+
switch (number) {
177+
case syscalls::create_task:
178+
// add the task to the scheduler
179+
tasks.push_back(reinterpret_cast<detail::base_task*>(arg0));
180+
break;
181+
182+
case syscalls::delete_task:
183+
// find and remove the task from the scheduler. We move backwards
184+
// to not mess up the indexing when erasing. Note this is something
185+
// that is specific to our dynamic array implementation.
186+
// TODO: update this when changing away from the dynamic array
187+
for (size_t i = 0; i < tasks.size(); i++) {
188+
if (tasks[i] == reinterpret_cast<detail::base_task*>(arg0)) {
189+
// erase the provided task
190+
tasks.erase(tasks.begin() + i);
191+
192+
// check if we are running the task we are deleting
193+
if (current_task == reinterpret_cast<detail::base_task*>(arg0)) {
194+
// clear the current task and switch to the
195+
// next one
196+
current_task = nullptr;
197+
198+
// switch to the next task
199+
schedule();
200+
}
201+
202+
// task found and deleted
203+
return true;
204+
}
205+
}
206+
207+
// task not found
208+
return false;
209+
210+
case syscalls::yield:
211+
// yield the cpu to the next task
212+
schedule();
213+
break;
214+
215+
case syscalls::sleep:
216+
// set the time to sleep for the current task
217+
current_task->time_to_sleep.value = arg0;
218+
219+
// switch to the next task
220+
schedule();
221+
break;
165222
}
223+
224+
return true;
166225
}
167226
};
168227
}

rtos/syscall.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include <rtos/rtos.hpp>
2+
3+
#include "syscall.hpp"
4+
#include "scheduler.hpp"
5+
6+
namespace klib::rtos::syscall {
7+
/**
8+
* @brief Invoke a syscall with the given arguments
9+
*
10+
* @tparam Return
11+
* @tparam Args
12+
* @param number
13+
* @param args
14+
* @return Return
15+
*/
16+
template <typename Return, typename... Args>
17+
static Return syscall_invoke(scheduler::syscalls number, Args... args) {
18+
// Call the target specific syscall invoke function
19+
return klib::target::rtos::detail::syscall_invoke<Return, Args...>(
20+
static_cast<uint32_t>(number), args...
21+
);
22+
}
23+
24+
bool create_task(detail::base_task* task) {
25+
// invoke the create_task syscall
26+
return syscall_invoke<bool, detail::base_task*>(scheduler::syscalls::create_task, task);
27+
}
28+
29+
bool delete_task(detail::base_task* task) {
30+
// invoke the delete_task syscall
31+
return syscall_invoke<bool, detail::base_task*>(scheduler::syscalls::delete_task, task);
32+
}
33+
34+
void yield() {
35+
// invoke the yield syscall
36+
syscall_invoke<void>(scheduler::syscalls::yield);
37+
}
38+
39+
void sleep(klib::time::ms time) {
40+
// invoke the sleep syscall
41+
syscall_invoke<void, klib::time::ms>(scheduler::syscalls::sleep, time.value);
42+
}
43+
44+
void* malloc(uint32_t size) {
45+
// invoke the malloc syscall
46+
return syscall_invoke<void*, uint32_t>(scheduler::syscalls::malloc, size);
47+
}
48+
49+
void free(const void *const ptr) {
50+
// invoke the free syscall
51+
syscall_invoke<void, const void*>(scheduler::syscalls::free, ptr);
52+
}
53+
}

0 commit comments

Comments
 (0)