Repository navigation
Porting Guide
A mipOS port is intentionally small. The goal is to provide the kernel with the
few primitives it needs, then bring up optional services one at a time. This page
uses the current BSP implementations as reference material: host simulator,
QEMU Cortex-M, QEMU vexpress-a9, and STM8/SDCC.
- Startup code: stack,
.data,.bss, vector table, and a call intomain. - UART
putc: once text works, debugging becomes much easier. - Critical sections: save interrupt state, disable interrupts, restore state.
- Context switch:
setjmp/longjmpor equivalent save/restore primitive. - First task dispatch on an application-provided stack.
- Tick source or scheduler epoch hook feeding
mipos_update_rtc. - Console RX path, watchdog, reset/clock hooks, and optional device drivers.
Do not bring up storage or networking before the first four items are stable. Most difficult failures in a new port look like filesystem/network bugs but are actually stack alignment, critical-section, or context-save bugs.
mipos/mipos_bsp.h is the selection boundary. New targets should add one BSP
include path and keep board-specific code out of the kernel core.
#if MIPOS_TARGET_QEMU_ARM
#include "mipos_bsp_qemu_arm.h"
#elif MIPOS_TARGET_QEMU_VEXPRESS_A9
#include "mipos_bsp_qemu_vexpress_a9.h"
#elif MIPOS_TARGET_STM32
#include "mipos_bsp_stm32.h"
#elif MIPOS_TARGET_STM8
#include "mipos_bsp_stm8.h"
#else
#include "mipos_bsp_simu.h"
#endifA new port normally needs:
- a
mipos/arch/<target>/mipos_bsp_<target>.hcontract header; - a
mipos/arch/<target>/mipos_bsp_<target>.cimplementation file; - startup code if the target is bare metal;
- a linker script if the target is not a host process;
- one minimal example under
examples/<target>/.
A port usually supplies:
- context save/restore macros;
- stack switching helpers;
- critical-section macros;
- serial configure, putc, and non-blocking receive;
- timer creation or scheduler epoch notification;
- optional watchdog, reset, clock, and exception-vector hooks.
The exact implementation depends on the CPU and toolchain. Host simulator ports can rely on libc and small assembly files. Bare-metal ARM ports need startup assembly, a linker script, and MMIO access to the board UART/timer. STM8 uses compiler-specific inline assembly and the local STM8S peripheral headers.
Critical sections must preserve the previous interrupt mask, not merely disable and re-enable interrupts blindly. That matters when kernel code nests critical sections or when a caller entered the kernel with interrupts already disabled.
The QEMU Cortex-M BSP saves PRIMASK and restores it on leave:
static inline unsigned int _mipos_qemu_arm_irq_save(void)
{
unsigned int primask;
__asm__ __volatile__("mrs %0, primask\n\t"
"cpsid i\n\t"
: "=r"(primask)
:
: "memory");
return primask;
}
#define mipos_enter_cs() \
do { \
_mipos_cs_primask = _mipos_qemu_arm_irq_save(); \
} while (0)The vexpress-a9 port does the same job with CPSR:
__asm__ __volatile__("mrs %0, cpsr\n\t"
"cpsid i\n\t"
: "=r"(cpsr)
:
: "memory");The host simulator has empty critical sections because it is a cooperative host process in the supported examples. That is valid for simulator tests, but it is not a model for bare-metal interrupt handling.
#define mipos_init_cs()
#define mipos_enter_cs()
#define mipos_leave_cs()The scheduler expects two primitives:
-
mipos_save_context(buffer)returns0when saving directly and non-zero when resumed; -
mipos_context_switch_to(buffer)resumes a previously saved context.
Most ports implement this with setjmp/longjmp where the C library or runtime
saves the right callee-saved registers for the ABI.
#define mipos_save_context(_x) setjmp((void*)(_x))
#define mipos_context_switch_to(_x) longjmp((void*)(_x), 1)This is only correct if the target runtime's setjmp saves the registers and
stack pointer required by the ABI. If that is not true, the port needs a custom
assembly pair, as older Cortex-M support did under mipos/arch/ctxm3/.
First dispatch is different from resume. The kernel has no saved task context
yet, so it switches to the task's application-provided stack and calls the entry
point. A BSP must provide mipos_replace_sp(old_sp, new_sp) and mipos_set_sp.
The QEMU Cortex-M implementation stores the current stack pointer, aligns the
new one to 8 bytes, then updates sp in one assembly block:
#define mipos_replace_sp(__OLD_SP, __STACK_P) \
do { \
uint32_t __mipos_tmp; \
__asm__ __volatile__("mov %0, sp\n\t" \
"str %0, [%1]\n\t" \
"bic %0, %2, #7\n\t" \
"mov sp, %0\n\t" \
: "=&r"(__mipos_tmp) \
: "r"(__OLD_SP), "r"(__STACK_P) \
: "memory"); \
} while (0)STM8/SDCC uses the same contract with STM8 instructions:
#define mipos_replace_sp(__OLD_SP, __N_SP) \
__asm__("ldw x, sp\n" \
"ldw _" #__OLD_SP "+0, x\n" \
"ldw x, _" #__N_SP "+0\n" \
"ldw sp, x\n")On 64-bit host simulator builds the repo uses helper functions such as
mipos_start_task64 or mipos_start_task_gnu. The reason is ABI compliance:
x64 stack alignment and callee-saved registers are stricter than the historical
32-bit direct stack switch path.
The kernel only needs monotonically increasing time. The BSP decides whether it comes from an interrupt, a polled scheduler epoch hook, or host clock readings.
The required kernel call is small:
void mipos_update_rtc(uint32_t quantum)
{
mipos_kernel_env.rtc_counter += quantum;
mipos_kick_watchdog();
}The QEMU Cortex-M BSP accumulates SysTick interrupts and drains them from the scheduler epoch hook, so kernel data structures are touched from thread context:
void SysTick_Handler(void)
{
++systick_pending_ticks;
}
void mipos_bsp_qemu_arm_rtc_epoch(void)
{
uint32_t ticks;
mipos_enter_cs();
ticks = systick_pending_ticks;
systick_pending_ticks = 0;
mipos_leave_cs();
if (ticks) {
mipos_update_rtc(ticks);
}
}The STM8 BSP feeds RTC directly from the TIM4 interrupt:
INTERRUPT_HANDLER(TIM4_UPD_OVF_IRQHandler, 23)
{
mipos_update_rtc(1 /*ms*/);
TIM4_ClearITPendingBit(TIM4_IT_UPDATE);
}The simulator uses host time in mipos_bsp_notify_scheduler_epoch. On Linux it
uses gettimeofday, computes elapsed milliseconds, and calls mipos_update_rtc.
The console and debug output need three functions:
void mipos_bsp_configure_rs232(unsigned int baud_rate);
unsigned int mipos_bsp_rs232_recv_byte(unsigned char* key);
void mipos_bsp_rs232_putc(unsigned char c);The QEMU Cortex-M UART implementation is typical for MMIO boards:
void mipos_bsp_rs232_putc(unsigned char c)
{
while (UART0_FR & UART_FR_TXFF) {
}
UART0_DR = c;
}Non-blocking receive returns 0 if no byte is available and 1 when it stores a
byte. Ports should normalize terminal line endings if their console backend can
produce LF-only input:
if (c == '\n') {
c = '\r';
}
*key = c;
return 1;For early simulator and QEMU targets, reset/watchdog hooks can be no-ops. For a physical board they should reflect the real hardware. STM8, for example, maps watchdog kicking to the independent watchdog reload call:
#define mipos_kick_watchdog() IWDG_ReloadCounter()Each BSP also defines endian helpers used by filesystem and protocol code:
#define MIPOS_LE_U16(x) (x)
#define MIPOS_LE_U32(x) (x)Those identity mappings are correct on the little-endian targets currently in the tree. A big-endian target must implement explicit byte swapping.
Start with a firmware that prints a banner before the kernel starts. Then start a root task and print again from task context. After that, add a timer and a sleeping task. Only add storage or networking once scheduling and time are reliable.
Good early checks:
- banner before
mipos_start; - root task banner after scheduler startup;
- repeated timer ticks;
- two tasks yielding in a predictable sequence;
- console RX/TX;
- filesystem or network smoke test.
The host-side scheduler smoke test is the model for the two-task check. It creates two workers, each worker yields, and the root task verifies that the sequence interleaves before exiting.
QEMU targets make porting safer because startup, stack switching, and interrupt handling can be tested without physical hardware. In this repository:
-
lm3s6965evbis the mature Cortex-M3 teaching/workhorse target; -
vexpress-a9is the ARMv7-A didactic board bring-up path; -
virtis the future CI target for virtio block and network work.
Once a QEMU target is stable, the same BSP checklist can be applied to a real evaluation board with similar CPU and peripherals.