Convert Figma logo to code with AI

jserv logomini-arm-os

Build a minimal multi-tasking OS kernel for ARM from scratch

1,046
243
1,046
2

Top Related Projects

Raspberry Pi ARM based bare metal examples

3,152

LK embedded kernel

180,417

Linux kernel source tree

An Agile RISC-V SoC Design Framework with in-order cores, out-of-order cores, accelerators, and more

Quick Overview

Mini-ARM-OS is an educational project that demonstrates the fundamentals of operating system development for ARM-based systems. It provides a minimalistic implementation of core OS concepts, including context switching, memory management, and basic scheduling, designed to run on ARM Cortex-M series microcontrollers.

Pros

  • Excellent learning resource for understanding low-level OS concepts
  • Specifically tailored for ARM architecture, which is widely used in embedded systems
  • Well-documented code with clear explanations of key concepts
  • Modular structure allows for easy extension and experimentation

Cons

  • Limited functionality compared to full-fledged operating systems
  • Primarily focused on educational purposes, not suitable for production use
  • Requires specific hardware (ARM Cortex-M series) for testing and development
  • May be challenging for beginners without prior knowledge of ARM assembly and C programming

Code Examples

  1. Context switching implementation:
void pendsv_handler(void)
{
    /* Save current task's context */
    asm volatile("mrs r0, psp");
    asm volatile("stmdb r0!, {r4-r11, lr}");
    asm volatile("msr psp, r0");

    /* Switch to next task */
    current_task = current_task->next;

    /* Load next task's context */
    asm volatile("mrs r0, psp");
    asm volatile("ldmia r0!, {r4-r11, lr}");
    asm volatile("msr psp, r0");
}

This code snippet demonstrates the context switching mechanism, saving and restoring task contexts using ARM assembly instructions.

  1. Task creation:
int task_create(void (*func)())
{
    unsigned int *stack;
    stack = malloc(STACK_SIZE);
    if (stack == NULL)
        return -1;

    stack += STACK_SIZE - 32; /* End of stack, minus what we are about to push */
    stack[8] = (unsigned int)func;
    stack[9] = 0x01000000; /* PSR Thumb bit */

    task_t *task = (task_t *)malloc(sizeof(task_t));
    task->stack = stack;
    task->next = current_task->next;
    current_task->next = task;

    return 0;
}

This function creates a new task by allocating memory for its stack and initializing the task structure.

  1. Simple scheduler implementation:
void schedule(void)
{
    task_t *prev = current_task;
    current_task = current_task->next;

    /* Trigger PendSV interrupt */
    *((uint32_t volatile *)0xE000ED04) = 0x10000000;

    if (prev != current_task)
        while (1); /* Wait until PendSV has been served */
}

This scheduler function demonstrates a simple round-robin scheduling algorithm, triggering a PendSV interrupt to perform context switching.

Getting Started

To get started with Mini-ARM-OS:

  1. Clone the repository:

    git clone https://github.com/jserv/mini-arm-os.git
    
  2. Install the ARM GCC toolchain and OpenOCD for your platform.

  3. Navigate to one of the example directories (e.g., 03-ContextSwitch-1).

  4. Compile the code:

    make
    
  5. Flash the compiled binary to your ARM Cortex-M board using OpenOCD or other flashing tools.

  6. Use a debugger or serial console to observe the OS in action.

Competitor Comparisons

Raspberry Pi ARM based bare metal examples

Pros of raspberrypi

  • More comprehensive coverage of Raspberry Pi hardware and peripherals
  • Includes examples for various Raspberry Pi models (Pi Zero, Pi 3, etc.)
  • Provides more advanced topics like USB and network programming

Cons of raspberrypi

  • Less focused on educational aspects of OS development
  • May be overwhelming for beginners due to its breadth of topics
  • Lacks structured progression through OS concepts

Code Comparison

mini-arm-os (context switching):

void activate(task_t *next) {
    if (current && current->state == TASK_RUNNING)
        current->state = TASK_READY;
    current = next;
    current->state = TASK_RUNNING;
}

raspberrypi (GPIO initialization):

void gpio_init() {
    PUT32(GPFSEL1,0x00040000);
    PUT32(GPSET0,1<<16);
    PUT32(GPCLR0,1<<16);
}

mini-arm-os focuses on core OS concepts like task switching, while raspberrypi provides lower-level hardware interaction examples. The raspberrypi repository offers a wider range of topics but may be less structured for learning OS development fundamentals compared to mini-arm-os.

3,152

LK embedded kernel

Pros of LK

  • More comprehensive and feature-rich operating system
  • Supports multiple architectures (ARM, x86, RISC-V)
  • Active development and community support

Cons of LK

  • Higher complexity and steeper learning curve
  • Larger codebase, potentially harder to understand for beginners
  • May be overkill for simple embedded projects

Code Comparison

mini-arm-os (context switching):

__attribute__((naked)) void PendSV_Handler(void)
{
    __asm volatile(
        "CPSID   I                 \n"
        "MRS     R0, PSP           \n"
        "STMDB   R0!, {R4-R11}     \n"
        // ... (additional assembly code)
    );
}

LK (context switching):

void arch_context_switch(thread_t *oldthread, thread_t *newthread)
{
    LTRACEF("old %p (%s), new %p (%s)\n", oldthread, oldthread->name, newthread, newthread->name);

    /* set the current cpu pointer to the new thread */
    arch_set_current_thread(newthread);

    /* do the switch */
    arm64_context_switch(&oldthread->arch.context, &newthread->arch.context);
}

mini-arm-os is a simpler, bare-metal OS designed for educational purposes, focusing on ARM architecture. LK (Little Kernel) is a more robust, multi-architecture embedded operating system with broader functionality and ongoing development. While mini-arm-os is excellent for learning basic OS concepts, LK is better suited for more complex embedded projects requiring advanced features and broader hardware support.

180,417

Linux kernel source tree

Pros of Linux

  • Comprehensive and feature-rich operating system supporting a wide range of hardware and use cases
  • Large, active community with extensive documentation and support
  • Highly optimized and battle-tested in production environments

Cons of Linux

  • Complex codebase with a steep learning curve for new contributors
  • Significant resource requirements for building and testing
  • Slower development cycle due to rigorous review process and compatibility concerns

Code Comparison

mini-arm-os (simple context switch):

void switch_to(struct task_struct *next)
{
    if (current == next)
        return;
    struct task_struct *prev = current;
    current = next;
    context_switch(prev, next);
}

Linux (more complex context switch):

__switch_to(struct task_struct *prev_p, struct task_struct *next_p)
{
    struct thread_info *next = task_thread_info(next_p);
    struct thread_info *prev = task_thread_info(prev_p);
    struct mm_struct *mm = next_p->mm;
    unsigned int cpu = smp_processor_id();
    // ... (additional logic)
}

The mini-arm-os example demonstrates a simplified context switch, while the Linux version includes more complex logic for handling various architectural details and multi-core systems.

An Agile RISC-V SoC Design Framework with in-order cores, out-of-order cores, accelerators, and more

Pros of Chipyard

  • More comprehensive and feature-rich, offering a full SoC design framework
  • Supports multiple ISAs (RISC-V, x86) and includes advanced tools for chip design
  • Actively maintained with frequent updates and a larger community

Cons of Chipyard

  • Steeper learning curve due to its complexity and extensive feature set
  • Requires more computational resources to run and simulate
  • May be overkill for simple embedded projects or learning basic OS concepts

Code Comparison

mini-arm-os:

void task1(void)
{
    while (1) {
        printf("Task1: Running...\n");
        os_wait(1000);
    }
}

Chipyard (example using Chisel):

class SimpleCore extends Module {
  val io = IO(new Bundle {
    val imem = new MemoryIO
    val dmem = new MemoryIO
    val exit = Output(Bool())
  })
  // Core implementation...
}

The mini-arm-os code shows a simple task implementation, while the Chipyard example demonstrates a basic hardware description for a core using Chisel. Chipyard's approach is more focused on hardware design, whereas mini-arm-os is geared towards basic OS concepts.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

Build a minimal multi-tasking OS kernel for ARM from scratch

Prerequisites

./configure --disable-werror --enable-debug \
    --target-list="arm-softmmu" \
    --extra-cflags=-DSTM32_UART_NO_BAUD_DELAY \
    --extra-cflags=-DSTM32_UART_ENABLE_OVERRUN \
    --disable-gtk
make

Steps

  • 00-Semihosting
    • Minimal semihosted ARM Cortex-M "Hello World"
  • 00-HelloWorld
    • Enable STM32 USART to print trivial greetings
  • 01-HelloWorld
    • Almost identical to the previous one but improved startup routines
  • 02-ContextSwitch-1
    • Basic switch between user and kernel mode
  • 03-ContextSwitch-2
    • system call is introduced as an effective method to return to kernel
  • 04-Multitasking
    • Two user tasks are interatively switching
  • 05-TimerInterrupt
    • Enable SysTick for future scheduler implementation
  • 06-Preemptive
    • Basic preemptive scheduling
  • 07-Threads
    • Implement user-level threads
  • 08-CMSIS
    • Illustrate hardware abstraction layer (HAL) by introducing CMSIS
    • Both emulator (based on stm32-p103) and real device (STM32F429i discovery) are supported.

Building and Verification

  • Changes the current working directory to the specified one and then
make
make qemu

Guide to Hacking 08-CMSIS

08-CMSIS implements preemptive round-robin scheduling with user-level threads for STM32F429i-Discovery (real device) and STM32-P103 (qemu).

Get the dependencies:

git submodule init
git submodule update

Install additional utilities:

Quick Start / Support Devices:

  • STM32F429i-Discovery(physical devices)
    • Details in Chinese by NCKU
    • STM32F429i-Discovery uses USART1(Tx=PA9,Rx=PA10,baud rate=115200) as default serial port here.
      • You would need a terminal emulator, such as screen
        • Installation on Ubuntu / Debian based systems: sudo apt-get install screen
        • Then, attach the device file where a serial to USB converter is attached: screen /dev/ttyUSB0 115200 8n1
        • Once you want to quit screen, press: Ctrl-a then k

Available commands:

Overall

  • make all
    • Build all target's bin,elf,objdump files in the "release" directory.
    • NOTE: make doe NOT equal to make all here because Makefile uses eval for targets.
  • make clean
    • Remove the entire "release" directory.

STM32-P103(QEMU)

  • make p103 or make target PLAT=p103
    • Build "p103.bin"
  • make qemu
    • Build "p103.bin" and run QEMU automatically.
  • make qemu_GDBstub
    • Build "p103.bin" and run QEMU with GDB stub and wait for remote GDB automatically.
  • make qemu_GDBconnect
    • Open remote GDB to connect to the QEMU GDB stub with port:1234 (the default port).

STM32F429i-Discovery(physical device)

  • make f429disco or make target PLAT=f429disco
    • Build "f429disco.bin"
  • make st-flash
    • Build "f429disco.bin" and flash the binary into STM32F429 with st-link.
  • make st-erase
    • Sometimes, STM32F429i-Discovery will not be able to flash new binary file, then you will need to erase flash memory with st-link.
    • Erase the entire flash on STM32F429.
  • make gdb_ST-UTIL
    • Using GDB with ST-LINK on STM32F429.
    • Remember to open another terminal,and type "st-util" to open "STLINK GDB Server"

Directory Tree:

  • core
    • Hardware independent source and header files.
  • platform
    • Hardware dependent source and header files.
  • cmsis
    • With cmsis,porting would be much easier!
  • release
    • This directory will be created after "Target" in Makefile is called.
    • Containing the elf,bin,objdump files in corresponding directory.
    • make clean will remove the entire directory,do not put personal files inside it!

Porting Guide:

You should know what CMSIS is and why it saves us a lot of efforts.

cmsis is a submodule from TibaChang/cmsis, maintained by Jia-Rung Chang.

The full project can be divided into two layer:

  • hardware-dependent part (HAL)
    • "platform" directory
  • hardware-indepentent part
    • "core" directory

Steps to launch the kernel on real devices:

STEP 1

Select a target name for your device, such as f429disco.

In this guide, we assume its name is example_device with vendor name "f429disco".

Create example_device directory in "platform" and f429disco directory in "cmsis" directory.

Create "include" and "src" directory in platform/example_device/

STEP 2

Introducing your CMSIS for your target, where it should be in the mbed repo.

For example, the CMSIS for STM32F429i-discovery could be found here.

We only need ".h" files, do not copy any ".c" files.

Put the header files into cmsis/f429discoexample_device.

cmsis is a submodule in this project, maintianed by Tiba Chang.

NOTE: You may encounter some error messages during building binary for your target. You need to solve it mannually. Usually, some files may be missing caused by some specific "define". You could just comment out that definition to resolve this problem.

STEP 3

This is the most difficult part.

You have to implement the files in platform/example_device/include/ and platform/example_device/src/.

According to different device vendor(such as STMicroelectronics, NXP, etc), the implementation is very different.

Please look into the current example:f429disco,and you will figure it out!

The function interface must be as same as the function interface in "platform/STM32F429/inc/" due to this is HAL for the entire project.

STEP 4

Add your target rules into Makefile.

Please look the example f429disco in the Makefile.

Most of the rules are reusable,so all you need is copy-n-paste, modifying the variable/target name and knowing what gcc arguments suit your target!

  • rules.mk
    • You should NOT modify this file!  - All of the rules are encapsulated into macro, used in Makefile.
  • Makefile:
    • If your device vendor name does not exist, create new variable and assign a name to it!
      • E.g.STM32 := STM32
    • Add your device name
      • E.g.STM32F429_DEVICE := f429disco
    • Check your device CPU type(Cortex-M3/4)
      • In target_using_CM4_list
    • Will you use CMSIS?
      • If NOT,add to target_NOT_using_CMSIS_list
    • Use the predefined macro to produce the corresponding directory and device specific variable(device name,vendor name)
      • E.g. $(eval $(call eval_all_variable,$(STM32F429_DEVICE),$(STM32)))
      • The vendor name is used in the cmsis directory name. They must be associated with each other.
    • Use the predefined macro to produce the corresponding GCC commands
      • E.g.: $(eval $(call eval_build_command,$(STM32F429_DEVICE)))
      • This will derive lots of variables that you don't see in the Makefile.
    • Add your device name to all
      • In the specific form:$($(YOUR_DEVICE_NAME)_TARGET) , this variable will be automatic derived by rules.mk
      • E.g.: $($(STM32F429_DEVICE)_TARGET)

STEP 5

Congratulations!

Now, you can try the "Available commands" in this README.

Licensing

mini-arm-os is freely redistributable under the two-clause BSD License. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

Reference