
Learn to build a small 64-bit operating system for Raspberry Pi on arm64, writing the kernel in C, and implementing memory management, a file system, processes, and a console.
Explore basic operating system concepts, including address space, 64-bit byte-addressed memory, and per-process memory maps. Learn how kernel and user modes use system calls to manage processes and access hardware.
Explore operating system structures, comparing monolithic and microkernel designs, including how scheduling, memory management, and system call interfaces differ; this course guides you to write a monolithic kernel.
Connect Raspberry Pi to a computer with a USB to TTL serial cable, wiring GPIO 14 (transmit), 15 (receive), and ground for serial testing.
Write the os image to the sd card using Raspberry Pi Imager, after downloading and unzipping the image, and ensure the correct device for cross-platform setup.
Enable Windows Subsystem for Linux, install Ubuntu 18.04, and set up a Linux environment for the Raspberry Pi OS project in Windows 10/11.
Install and verify aarch64 gcc on ubuntu, install putty and Visual Studio Code with ARM and C extensions, install qemu on Windows, configure path, and run qemu system aarch64.
Set up the Ubuntu environment for Raspberry Pi projects by installing the aarch64 gcc cross compiler, putty, and Visual Studio Code with arm and c extensions, plus qemu for virtualization.
Set up a macOS environment for raspberry pi projects by installing aarch64 gcc, unzipping and moving it to /usr/local, and configuring vscode with arm and c extensions.
Boot the Raspberry Pi and load a simple kernel at address 80000 by assembling boot.s and main.c, then build a kernel8.img using a custom linker script and build script.
Build a Raspberry Pi project on Windows 10 using the Ubuntu subsystem, mounting the C drive and running build.sh to produce kernel8.img. Test with qemu for Raspberry Pi 3.
Install the aarch64 cross gcc on ubuntu and build the project with the boot/build.sh script to generate kernel8.img, then test it in qemu with Raspberry Pi 3 and serial stdio.
Set up a macOS aarch64 cross compiler, build kernel8.img with the build script, and test the project in qemu system aarch64 on Raspberry Pi 3 using serial output.
Learn to build and test raspberry pi project on Windows 10, run build.sh to generate kernel8 image, copy to sd card, and set up PuTTY com port at 115200.
Learn to build and test Raspberry Pi projects on Ubuntu by cross compiling with aarch64 gcc, creating kernel8.img, and using PuTTY over usb serial to view boot messages.
Learn to build and test Raspberry Pi projects on macOS by configuring aarch64 cross gcc, running the build script, and flashing kernel8 image to an SD card via USB serial.
Learn how arm64 calling conventions pass parameters in x0–x7 or on the stack, return values in x0, and how caller and callee save rules shape stack frames.
assemble a simple library in assembly and c, implementing delay, out word, and in word, with a header guard and fixed width types, and prepare for a uart module.
Configure UART on Raspberry Pi 3 to send messages to the computer console via memory-mapped registers using UART0 and GPIO pins.
Initialize uart0 on raspberry pi 4, configure baud rate 115200, and send strings to the console using memory-mapped registers and gpio transmit/receive lines.
Diagnose a no-console issue by booting via HDMI, then fix start4.elf compatibility by updating firmware. Download Raspberry Pi firmware and copy start4.elf and fixup4.dat to the SD card, then reboot.
Implement the printk print function for the kernel with a printf-like format parser on Raspberry Pi, supporting x, d, s, and u using va_list and writing to the uart console.
Add and implement memset, memcpy, memmove, and memcmp in assembly for the Raspberry Pi OS library, enable global use, and initialize bss during boot.
Implement an assert macro in the kernel to catch errors in the barricade and trusted area, printing the file name and line number, then halting.
Learn the essentials of aarch64 in armv8, including x0 through x30, pstate and nzcv flags, stack pointers, exception levels el0 through el3, and secure versus normal world with trustzone.
Practice retrieving the current exception level on Raspberry Pi by reading the current el register with mrs, shifting two bits, and printing the level.
Switch from el2 to el1 by configuring hcr_el2 and system control el1, using spsr_eln and elr_eln with eret. Boot the Raspberry Pi to display the exception level 1.
Explore interrupts and exceptions on Raspberry Pi, including vector tables and exception handlers. Understand synchronous and asynchronous exceptions and how el1 and el0 levels influence handling.
Define a 16-entry vector table aligned to 2k, implement error and synchronous handlers using esr and elr, then test by triggering a synchronous exception from an invalid memory access.
Learn how to preserve a task’s register state by saving all 31 general purpose registers to the stack during an exception, then restore them before returning.
Configure and handle hardware timer interrupts on QEMU for Raspberry Pi 3, enabling the timer, enabling IRQ, and printing timer events every 10 ms.
Configure and test a uart interrupt on qemu by initializing the interrupt controller, enabling the receive interrupt, and implementing a handler that reads data and prints characters.
Switch from el1 to el0 on qemu using eret with spsr_el1 and elr_el1, and verify the pstate mode becomes 0 at el0.
Learn to configure the raspberry pi 3 interrupt controller, enable the arm timer, and handle a recurring 10 ms timer interrupt by tracking ticks and printing every second.
Configure the uart to generate receive interrupts, enable the interrupt in the controller, and implement a handler that reads and prints pressed keys to emulate a keyboard.
Switch from el1 to el0 by using spsr_el1 and elr_el1 with eret, then enable timer interrupts at el0 and verify via spsr values and a 10 ms timer handler.
Configure the gic400 interrupt controller (distributor and cpu interface), set priority and target, and enable timer interrupts to handle hardware interrupts on Raspberry Pi 4.
implement uart interrupt handling on Raspberry Pi 4 using GIC400, enabling receive interrupts, wiring an interrupt handler to print pressed keys to the console and emulate a keyboard.
Learn how to switch from el1 to el0 on Raspberry Pi4 using spsr and elr, handle exceptions, and enable timer interrupts at el0 to run a 10ms loop.
Explain memory management and paging in 64-bit systems by mapping virtual to physical addresses with the MMU, using 2m pages and a page table to separate user and kernel space.
Enable paging on qemu by configuring vm setup and mmu, mapping kernel and user via global directory table and middle directory table, and applying memory attributes via mair and tcr.
Enable paging on the Raspberry Pi 3 by setting up vm and mmu with 4k-aligned page tables, mapping kernel and user space, configuring mair and tcr, switching to virtual addressing.
Enable paging on raspberry pi 4 by setting up vm and mmu, mapping global, upper, and middle directory tables, and enabling kernel and user translations with mair and tcr.
Collect free physical memory, divide it into 2m pages, and manage them as a linked list with kfree and kalloc, including address alignment and p2v/v2p conversions.
Map memory by implementing paging with multi-level tables, mapping virtual to physical addresses, validating alignment, allocating pages, and switching vm with ttbr0 loading and tlb handling.
Free memory by walking the global, upper, and middle directory tables to release physical pages, then free the vm and its 4k translation tables used for 2m pages.
Learn to parse a Fat16 file system, run programs from a boot image, and read text data by assembling strings, with all file system data loaded into memory during boot.
Create a fat16 image on Windows 10, then inspect it with a hex editor and mount it with osfmount after formatting via freedos and bochs.
Create a fat16 image using FreeDOS and configure it in Bochs on Ubuntu, then partition, format, and mount the os image for testing.
Create a fat16 image for an operating system using freedos and bochs, using bximage to make the image, then partition, format, and mount it for testing.
Load the fat16 os.img into memory by appending it to the kernel file, then copy the file system to the fs base and verify its signature in init fs.
Explore the fat16 structure, from bios parameter block to the file allocation table, root directory, and data sections, with hands-on steps for tracing clusters and locating files.
Learn to implement a fat16 file system module to read files from a fat16 image, covering init fs, get bios parameter block, root directory operations, and file loading via clusters.
Create your first process by mapping kernel space to shared physical pages while giving each process its own user-space address space with distinct program data, heap, and stack.
Define and initialize the idle process in the process module, using a 10-entry process table with pid and state management. Enable timer interrupts and test on Raspberry Pi.
Examine and implement a trap frame to save and restore the process context during exceptions, using el0 and el1 stacks, trapno, esr, elr, and spsr.
Allocate the first user process, set up kernel and user stacks and trap frame, map user memory, load init.bin, and launch with trap return.
Resolve the invalid signature by embedding the os image in the kernel with incbin, avoiding bss_start misalignment, and build and test the unified kernel with fsimg_start and fsimg_end in QEMU.
Explore system calls that print messages from user programs using svc, synchronize with the kernel, and a write console workflow through kernel and user libraries.
Implement a timer interrupt driven scheduler that switches between idle and user processes via a trap handler, using a ready list and context switch with swap to manage process state.
Learn to shift a process between sleep and active states using the scheduler and wait lists. Implement sleep and wake up in process control, sys sleep, and timer wake up.
Learn how an operating system manages process exit and wait through exit and wait system calls. Kernel handling and cleanup of killed processes occur in a Raspberry Pi OS.
Terminate a process by checking the spsr mode field to detect user-mode exceptions, then call exit and wait to free the process; halt if in kernel mode.
Initialize the fcb and file descriptor table, then manage file access via the file descriptor pointer array to open and access files within the kernel.
Learn to implement open and close file operations in a Raspberry Pi OS project, including managing file descriptors and a cached FCB table through the process module.
Define a get file size function in the kernel module that takes the current process and a file descriptor and uses the fcb table to return the file size.
Implement read file data in the kernel, using file size, position, and cluster indices to read into memory buffers, then test with a sample file on Raspberry Pi.
Implement fork by creating a new process as a copy of the parent, copying program data and trap frame, and sharing open files.
Explore how the operating system executes a program on the Raspberry Pi, from initialization to forking, loading from an image, and starting a test process in user space.
Implement a keyboard interrupt handler that buffers UART key presses in a circular buffer, updating front and end pointers and waking waiting processes for reads.
Create a console in qemu that runs in user mode, handling keyboard input, buffering, and command execution. Open, read, and execute commands by matching files and updating the prompt.
Build a Raspberry Pi console that reads keyboard input into a buffer, displays a shell prompt, and executes file commands by locating files in the image.
Implement the ls command to list files by retrieving route items, copying directory data to a buffer, and printing file names with checks for directories and files.
Welcome to Raspberry Pi: Write Your Own Operating System course.
This course teaches you how to build a simple operating system from scratch. It covers basics about the AArch64 architecture and low-level programming. In this course, we will take you through the process of building a small working system step by step.
The first part of the course teaches all you need to know (processor modes, paging, exceptions and interrupts handling, etc.) before you can build the kernel on the AArch64 architecture. In this part, we will see how to prepare for mode switching. We switch from EL2 to EL1 and then jump to EL0. Our kernel is running in 64-bit mode. We will see how to handle exceptions and interrupts, how to switch between kernel mode and user mode.
The second part of the course teaches you how to build kernel modules such as processes, memory management, interrupt handling, etc. In this part, we will see how to interact with the OS kernel using the console. After finishing this part, you should know how to write user programs and make them running in your own operating system.
In this course you will learn:
How to build a simple operating system for the AArch64 architecture.
How to handle interrupts and exceptions
How to write OS kernel with the assembly code and C code
Be able to write print function to print characters on the screen
Be able to build a memory manager using paging mechanism
How to write timer handler for the process manager
How to build a process manager to schedule processes and change them among different states (sleep, ready, killed)
How to implement system call module to make user programs running in the system
Write a UART driver
Write a simple console and interact with OS kernel using commands
Be able to write a simple file system module which supports reading fat16 system.
At the end of the course, you should be able to develop your own 64-bit operating system.