Tprrt's Blog

Yet another blog about embedded Linux, the open source and hardware

Dec 31, 2025

Open-Source Game Engines for Retro Consoles

Introduction

Retro console development has experienced a renaissance in recent years, thanks to passionate homebrew communities and modern open-source tooling. What was once the domain of professional game studios with expensive proprietary SDKs is now accessible to anyone with a Linux machine and a passion for classic gaming hardware.

This guide catalogs the best open-source game engines and frameworks available for developing games on classic consoles, from the 8-bit Game Boy Color to sixth generation systems like the PlayStation 2. All tools mentioned are compatible with Linux development environments, making them perfect for a fully free and open-source workflow.

8-bit and 16-bit Consoles

Game Boy Color (GBC)

GB Studio

For those wanting to create Game Boy games without writing code, GB Studio is the perfect starting point. This visual game editor features a drag-and-drop interface that lets you build complete RPGs, adventure games, platformers, and shooters without touching a single line of code.

Key Features:

  • Full visual scene editor with intuitive drag-and-drop
  • Built-in sprite and background editors
  • Integrated music tracker
  • Event system for complex game logic
  • Exports to actual GB/GBC ROMs that run on real hardware
  • Cross-platform support (Linux, Windows, macOS)

License: MIT

Links: GitHub | Website | Documentation

GBDK-2020

For developers who prefer code, GBDK-2020 is a modern fork of the classic Game Boy Development Kit. It brings C99 support and modern toolchain features to Game Boy development.

Key Features:

  • Modern C99 compiler
  • ROM banking support for large games
  • Libraries for sprites, backgrounds, and sound
  • Compatible with both Game Boy and Game Boy Color
  • Strong toolchain integration

License: Various (mostly permissive)

Links: GitHub | API Documentation

Game Boy Advance (GBA)

Butano

Butano is a modern C++17 game engine built on devkitARM that makes GBA development feel contemporary. It abstracts the hardware complexity while still giving you full control over the system's capabilities.

Key Features:

  • Modern C++17 syntax and features
  • Sprite management with affine transformations
  • Regular and affine background layers
  • Audio support (DMG and DirectSound)
  • Scene management system
  • GBA-optimized math utilities
  • Documentation and examples
  • Active Discord community

License: zlib License

Links: GitHub | Documentation

Tonclib

Tonclib is the veteran of GBA development. While less actively developed, it remains stable and is accompanied by some of the best documentation in retro game development.

Key Features:

  • Hardware abstraction layer
  • Advanced sprite and background management
  • Mode 7 (affine) support for pseudo-3D effects
  • Built-in text rendering
  • Excellent tutorial and documentation (Tonc)
  • Used by many commercial-quality homebrews

License: MIT-like (custom permissive)

Links: GitHub | Tonc Tutorial

Nintendo DS (NDS)

NightFox's Lib

NightFox's Lib provides a high-level 2D game library built on top of libnds, making DS development more approachable.

Key Features:

  • Sprite engine with rotation and scaling
  • Tiled background support
  • Collision detection
  • 2D and 3D text rendering
  • Sound and MOD music playback
  • File system access
  • Includes examples and templates

License: MIT

Links: GitHub

libnds + devkitARM

For those wanting full control, libnds is the official devkitPro library providing low-level access to all DS features.

Key Features:

  • Complete hardware access to both screens
  • 2D and 3D graphics support
  • Touchscreen and button input
  • WiFi networking support
  • FAT file system access
  • Audio subsystem control
  • Most flexible but requires hardware knowledge

License: zlib License

Links: GitHub | Documentation | Examples

Nintendo 3DS

citro2d / citro3d

The citro libraries are the official devkitPro solution for 3DS development, providing hardware-accelerated 2D and 3D graphics.

Key Features:

  • Hardware-accelerated rendering via PICA200 GPU
  • 2D sprite batching (citro2d)
  • Full 3D graphics pipeline (citro3d)
  • Shader support
  • Stereoscopic 3D rendering
  • Text rendering
  • Used by most modern 3DS homebrew

License: zlib License

Links: citro3d | citro2d | Documentation | Examples

Super Nintendo (SNES)

PVSnesLib

PVSnesLib is a modern C library bringing contemporary development practices to the Super Nintendo.

Key Features:

  • Modern C API
  • Sprite management (OAM)
  • Background and tilemap support
  • Mode 7 support for rotation and scaling
  • Sound driver integration
  • Gamepad input handling
  • DMA and HDMA operations
  • Documentation

License: MIT

Links: GitHub | Wiki

libSFX

libSFX is a powerful macro assembler framework for SNES development, optimized for performance.

Key Features:

  • Assembly-first with C support
  • Highly optimized for speed
  • Full hardware access
  • Super FX (GSU) support
  • Music and sound effects
  • Can integrate with C code
  • Steeper learning curve but very capable

License: MIT

Links: GitHub | Wiki

Sega Mega Drive / Genesis

SGDK (Sega Genesis Development Kit)

SGDK has become the industry standard for Mega Drive homebrew development, with an incredibly active community and extensive documentation.

Key Features:

  • Complete development framework
  • Sprite engine with hardware scrolling
  • Multiple background plane support
  • VDP (video display processor) management
  • Z80 sound driver with XGM music format
  • DMA operations
  • Built-in collision detection
  • ResComp resource compiler for assets
  • Extensive tutorials and documentation
  • Large, active community
  • Excellent Linux support

License: MIT

Links: GitHub | Wiki | Forums

Neo Geo

NGDK (Neo Geo Development Kit)

NGDK brings C development to the Neo Geo arcade platform and AES home console.

Key Features:

  • C framework for Neo Geo development
  • Sprite system management
  • Background and fix layer handling
  • Input handling for arcade controls
  • Sound support (Z80 + YM2610)
  • Asset conversion tools
  • Example games included

License: Custom permissive

Links: GitHub | Wiki

PC Engine / TurboGrafx-16

HuC (Hudson C Compiler)

The classic HuC compiler has been maintained by the community and remains a solid choice for PC Engine development.

Key Features:

  • C compiler for PC Engine
  • Support for HuCard and CD-ROM²
  • PSG sound support
  • Sprite management
  • Background and tilemap support
  • ADPCM audio for CD games
  • Standard C library subset

License: BSD-like

Links: GitHub

Squirrel (HuDK)

Squirrel (HuDK) is a more modern alternative to HuC with improved optimization.

Key Features:

  • Modern PC Engine framework
  • Better optimization than classic HuC
  • CD-ROM support
  • Active development
  • Growing community

License: Open source

Links: GitHub

Fifth and Sixth Generation Consoles

Sony PlayStation 1 (PS1)

PSn00bSDK

PSn00bSDK is a modern, lightweight SDK that makes PS1 development accessible and enjoyable. It's cleaner and more approachable than the old Psy-Q SDK.

Key Features:

  • Modern, clean API design
  • Hardware 3D graphics (GTE) support
  • 2D sprite and primitive rendering
  • CD-ROM file system access
  • SPU sound support with ADPCM and XA audio
  • Memory card management
  • Controller input (standard and analog)
  • Serial I/O support
  • Examples
  • Excellent Linux support

License: MPL 2.0

Links: GitHub | Wiki | Examples

Sega Saturn

Jo Engine

Jo Engine is a high-level 2D and 3D game engine that makes Saturn development approachable.

Key Features:

  • High-level API for 2D and 3D
  • Sprite engine with scaling and rotation
  • 3D model support with converter tools
  • Audio support (PCM, CD audio)
  • Save game management
  • Collision detection
  • Map and tilemap support
  • USB dev cart support for rapid testing
  • Video tutorials available

License: MIT

Links: GitHub | Website | Wiki

Yaul

Yaul is a modern alternative to the old Sega Basic Library, offering a clean API for advanced Saturn developers.

Key Features:

  • Modern library design
  • Clean API
  • VDP1 and VDP2 support
  • SCU DMA operations
  • CD block support
  • SCSP (sound) support
  • USB dev cart support
  • Excellent documentation

License: BSD

Links: GitHub | Documentation

Nintendo 64

libdragon

libdragon has revolutionized N64 development by making it far more accessible than the old Nintendo SDK.

Key Features:

  • Modern N64 development library
  • 3D graphics via RDP/RSP
  • Audio subsystem support
  • Controller input
  • ROM file system
  • Hardware sprites
  • Much easier than old SDKs
  • Very active community
  • Good documentation

License: Unlicense (public domain)

Links: GitHub | Documentation

Sega Dreamcast

KallistiOS (KOS)

KallistiOS is the de facto standard for Dreamcast homebrew, with an incredibly mature ecosystem.

Key Features:

  • Complete OS-like framework
  • 2D and 3D graphics (PowerVR)
  • Network support (modem, broadband adapter)
  • VMU (Visual Memory Unit) support
  • Input device support
  • CD-ROM file system (ISO9660)
  • AICA SPU audio support
  • Threading and multitasking
  • USB development support
  • Extensive library ecosystem
  • Very mature and well-documented

License: BSD-style

Links: GitHub | Documentation | Forums

Additional KOS libraries include GLdc (OpenGL-like API) and SDL ports, making cross-platform development easier.

Sony PlayStation 2 (PS2)

PS2SDK

PS2SDK provides complete access to the powerful PlayStation 2 hardware.

Key Features:

  • Complete PS2 development SDK
  • Graphics Synthesizer (GS) support for 2D/3D
  • Emotion Engine and I/O Processor access
  • Vector Unit (VU) programming
  • Sound library (audsrv)
  • USB and network support
  • Memory card management
  • DVD file system access
  • Excellent Linux compatibility
  • Large, active community

License: BSD/Academic Free License

Links: GitHub | Website | Examples

Nintendo GameCube / Wii

devkitPPC + libogc

The official devkitPro toolchain for GameCube and Wii provides hardware access.

Key Features:

  • Official devkitPro toolchain
  • Full hardware access for both systems
  • GX 3D graphics library
  • ASND audio library
  • Controller support (PAD/WPAD)
  • Network library
  • USB and SD card storage
  • DVD reading
  • Homebrew Channel integration (Wii)
  • Large community

License: Various (permissive)

Links: GitHub | Documentation | Examples | devkitPro

Sony PlayStation Portable (PSP)

PSPSDK

PSPSDK is the complete homebrew SDK for PSP development.

Key Features:

  • Complete PSP SDK
  • 3D graphics (GU library) with hardware acceleration
  • 2D sprite rendering
  • Multi-format audio support
  • WiFi and networking
  • USB support
  • Memory Stick access
  • Save data management
  • MP3, AAC playback
  • Mature and stable
  • Great Linux support

License: BSD/GPL

Links: GitHub | Forums | Examples

PlayStation Vita

Vita SDK

Vita SDK provides a complete homebrew development solution for Sony's handheld.

Key Features:

  • Complete PS Vita SDK
  • OpenGL ES-like graphics
  • Touch screen support
  • Accelerometer and gyroscope
  • Camera support
  • Network and WiFi
  • Trophy system support
  • Save data management
  • Multi-format audio
  • Very active homebrew scene

License: Various

Links: GitHub | Website | Documentation | Examples

Xbox (Original)

nxdk

nxdk is a clean-room open-source Xbox SDK with no Microsoft code.

Key Features:

  • Open-source Xbox SDK
  • Direct3D 8-like graphics API
  • Audio support
  • Controller input
  • Network support
  • Hard drive access
  • SDL port available
  • Growing community

License: Various (LGPL/MIT)

Links: GitHub | Wiki | Examples

Development Tools and Workflow

DevkitPro Toolchain

Many frameworks (GBA, DS, 3DS, GameCube/Wii) require the devkitPro toolchain, which works excellently on Linux:

  • Website
  • Getting Started Guide
  • Includes devkitARM, devkitPPC, and associated libraries
  • Available via pacman (devkitPro package manager) on Fedora

Graphics Tools

For a fully open-source workflow, these tools are all free, open-source, and Linux-native:

Pixel Art Editors:

  • Pixelorama (MIT): Modern pixel art editor with animation support, built with Godot. Excellent Aseprite alternative. Website
  • LibreSprite (GPL v2): Fork of old GPL Aseprite with familiar interface. Website
  • GrafX2 (GPL v2): Inspired by Deluxe Paint, excellent for retro graphics. Website
  • Piskel (Apache 2.0): Web-based and offline pixel art editor. Website

Tilemap Editor:

  • Tiled (GPL v2/BSD): Industry-standard tilemap editor. Website

General Graphics:

  • GIMP (GPL v3+): Full-featured image editor. Website

Music and Sound Tools

All tools below are free, open-source, and Linux-native:

Chiptune (Hardware Chip Emulation):

  • Furnace (GPL v2+): Multi-system chiptune tracker supporting 60+ sound chips (NES, SNES, Genesis, Game Boy, etc.). Perfect for authentic retro console music. Available on Flathub. GitHub

Module Trackers (Sample-based):

  • MilkyTracker (GPL v3): FastTracker II-inspired tracker for MOD/XM formats. Website
  • Schism Tracker (GPL v2): Impulse Tracker clone for S3M/IT formats. Website

NES/Famicom Specific:

  • FamiStudio (MIT): DAW-style NES/Famicom music editor with expansion chip support. Available on Flathub. Website

Audio Editor:

  • Audacity (GPL v2/v3): Multi-track audio editor and recorder. Website

Emulators for Testing

All emulators below are open-source and Linux-compatible:

  • mGBA: Game Boy Advance - Website
  • DeSmuME: Nintendo DS - Website
  • Citra: Nintendo 3DS - Website
  • bsnes: Super Nintendo - GitHub
  • Genesis Plus GX: Sega Mega Drive - GitHub
  • Mednafen: Multi-system (PC Engine, PS1, Saturn, etc.) - Website
  • DuckStation: PlayStation 1 - GitHub
  • PCSX2: PlayStation 2 - Website
  • Dolphin: GameCube/Wii - Website
  • PPSSPP: PlayStation Portable - Website
  • Vita3K: PlayStation Vita - Website
  • Flycast: Sega Dreamcast - GitHub
  • Mupen64Plus: Nintendo 64 - Website
  • xemu: Original Xbox - Website

Recommendations by Experience Level

Beginner-Friendly

8-bit/16-bit:

  • GB Studio (GBC): Visual editor, no coding required
  • GBDK-2020 (GBC): Simple C development
  • SGDK (Mega Drive): Excellent documentation and community

Fifth/Sixth Generation:

  • PSn00bSDK (PS1): Clean, modern API
  • Jo Engine (Saturn): High-level engine with tutorials
  • PSPSDK (PSP): Well-documented and stable

Intermediate

8-bit/16-bit:

  • Butano (GBA): Modern C++ with great docs
  • PVSnesLib (SNES): Comprehensive library
  • NightFox's Lib (DS): High-level 2D development

Fifth/Sixth Generation:

  • KallistiOS (Dreamcast): Mature ecosystem
  • devkitPPC (GC/Wii): Official toolchain
  • Vita SDK (Vita): Active community

Advanced

8-bit/16-bit:

  • libSFX (SNES): Assembly-first, highly optimized
  • citro3d (3DS): Direct hardware access
  • libnds (DS): Low-level control

Fifth/Sixth Generation:

  • PS2SDK (PS2): Complex but powerful
  • Yaul (Saturn): Modern low-level library
  • libdragon (N64): RDP/RSP programming
  • nxdk (Xbox): Direct3D 8 development

Community Resources

General Communities:

  • NESDev Forums: Multi-platform retro development - Forums
  • GBAtemp: DS/3DS homebrew - Website
  • devkitPro Discord: Nintendo handheld development

Platform-Specific:

  • GBADev: Game Boy Advance - Website
  • PSXDev: PlayStation 1 - Website
  • PS2Dev Forums: PS2, PSP - Forums
  • DCEmulation: Dreamcast - Website
  • SegaXtreme: Saturn, Mega Drive - Website
  • N64brew: Nintendo 64 - Website
  • GC-Forever: GameCube/Wii - Website
  • r/vitahacks: PS Vita homebrew

Conclusion

The retro console homebrew scene has never been more vibrant or accessible. With modern open-source toolchains, documentation, and active communities, developing games for classic consoles is now within reach of any motivated developer with a Linux machine.

Whether you want to create a simple Game Boy puzzle game with GB Studio's visual editor, or push the limits of the PlayStation 2's Emotion Engine with assembly-optimized code, the tools are available and the communities are welcoming.

The best part? This entire workflow can be accomplished with 100% free and open-source software, from the development tools to the graphics editors to the music trackers. This guide should give you everything you need to start your retro game development journey.

Happy coding, and may your sprites never flicker!

Mar 11, 2025

Retro Console Hardware Comparison: A Technical Deep Dive

Introduction

Understanding the hardware capabilities of classic gaming consoles provides valuable insight for both homebrew developers and retro gaming enthusiasts. Each console generation brought significant improvements in processing power, graphics capabilities, and audio quality, while working within tight memory constraints and power budgets.

This guide provides detailed technical comparisons across multiple console generations, from the 8-bit Game Boy to modern hybrid systems like the Nintendo Switch. Whether you're developing homebrew games or simply curious about the technical evolution of gaming hardware, these tables offer a reference.

CPU and Memory Architecture

The processors and memory configurations of gaming consoles reveal much about their capabilities and limitations. Early consoles operated with kilobytes of RAM, while modern systems have gigabytes at their disposal.

Processor Specifications

Console CPU Clock Speed
Game Boy Custom Sharp LR35902 4.19 MHz
Game Boy Color Custom Sharp Z80 8 MHz
NES Ricoh 2A03 (MOS 6502) 1.79 MHz (NTSC) / 1.66 MHz (PAL)
SNES Ricoh 5A22 (65C816-based) 3.58 MHz (max)
PC Engine HuC6280 (MOS 6502-based) 7.16 MHz
Neo Geo Motorola 68000 + Zilog Z80 12 MHz + 4 MHz
Game Boy Adv. ARM7TDMI 16.78 MHz
Nintendo DS ARM946E-S + ARM7 67 MHz + 33 MHz
Nintendo 3DS Dual-Core ARM11 MPCore 268 MHz
Wii IBM PowerPC "Broadway" 729 MHz
PSP MIPS R4000-based CPU 333 MHz
Switch NVIDIA Tegra X1 (ARM Cortex-A57) 1.02 GHz

Memory Configurations

Console RAM
Game Boy 8 KB
Game Boy Color 32 KB + 16 KB VRAM
NES 2 KB + 2 KB VRAM
SNES 128 KB + 64 KB VRAM
PC Engine 8 KB + 64 KB VRAM
Neo Geo 64 KB + 68 KB VRAM
Game Boy Adv. 256 KB + 96 KB VRAM
Nintendo DS 4 MB + 656 KB VRAM
Nintendo 3DS 128 MB + 6 MB VRAM
Wii 88 MB (24 MB + 64 MB GDDR3)
PSP 32 MB (PSP-1000) / 64 MB (PSP-2000+)
Switch 4 GB LPDDR4

Key Observations:

The evolution from kilobytes to gigabytes of RAM represents a million-fold increase in memory capacity. The NES operated with just 2 KB of main RAM, requiring extremely efficient programming. Modern consoles like the Switch have 4 GB, enabling complex 3D worlds and high-resolution textures.

2D Graphics Capabilities

Early gaming consoles were built around dedicated 2D graphics hardware with hardware sprites and tile-based rendering systems.

Color Depth and Palette

Console Graphics Processor Displayable Colors
Game Boy Custom Sharp LR35902 4 shades of gray
Game Boy Color Custom Sharp Z80 32,768, 56 max
NES PPU (2C02 or 2C03) 52, 25 max
SNES S-PPU 32,768, 256 max
PC Engine HuC6270A VDC 512, 482 max
Neo Geo Custom LSPC2-A2 65,536, 4,096 max
Game Boy Adv. Custom 2D Core 32,768, 512 max
Nintendo DS 2D/3D Graphics Engine 32,768, 4,096 max
Nintendo 3DS PICA200 GPU 16.8 million
Wii ATI Hollywood GPU 16.8 million
PSP Sony CXD2962GG + Media 16.8 million
Switch NVIDIA Tegra X1 16.8 million

Sprite Capabilities

Console Sprite Size Max Sprites on Screen
Game Boy 8x8 or 8x16 px 40 sprites, max 10 per line
Game Boy Color 8x8 or 8x16 px 40 sprites, max 10 per line
NES 8x8 or 8x16 px 64 sprites, max 8 per line
SNES Up to 64x64 px 128 sprites, max 32 per line
PC Engine 16x16 px 64 sprites, max 16 per line
Neo Geo Up to 16x512 px 380 sprites, no strict limit
Game Boy Adv. Up to 64x64 px 128 sprites, max 32 per line
Nintendo DS Up to 64x64 px 128 sprites, max 32 per line
Nintendo 3DS Variable Sprite handling via 3D engine
Wii Variable Sprite handling via 3D engine
PSP Variable Sprite handling via 3D engine
Switch Variable Sprite handling via 3D engine

Key Observations:

Sprite-per-line limits were a critical constraint for 8-bit and 16-bit consoles. Developers had to carefully manage sprite placement to avoid flickering. The Neo Geo's massive sprite sizes (up to 16x512 pixels) and high sprite count made it exceptional for arcade-style action games.

Video Output Specifications

Display resolution, refresh rate, and aspect ratio define the visual output characteristics of each console.

Display Characteristics

Console Resolution Refresh Rate Aspect Ratio
Game Boy 160x144 59.7 Hz 10:9
Game Boy Color 160x144 59.7 Hz 10:9
NES 256x240 60 Hz (NTSC) 50 Hz (PAL) 4:3
SNES 256x224 512x448i 60 Hz (NTSC) 50 Hz (PAL) 4:3
PC Engine 256x224 59.94 Hz 4:3
Neo Geo 320x224 59.18 Hz 4:3
Game Boy Adv. 240x160 59.7 Hz 3:2
Nintendo DS 256x192 (per screen) 59.8 Hz 4:3
Nintendo 3DS 400x240 (top) 320x240 (bottom) 60 Hz 5:3 (top) 4:3 (bottom)
Wii 640x480 60 Hz 4:3 or 16:9
PSP 480x272 60 Hz 16:9
Switch 1280x720 (Handheld) 1920x1080 (Docked) 60 Hz 16:9

Key Observations:

Resolution evolved from the Game Boy's 160x144 to Full HD (1920x1080) on the Switch when docked. Most classic consoles targeted NTSC's 60 Hz or PAL's 50 Hz refresh rates. The shift from 4:3 to 16:9 aspect ratios occurred around the PSP/Wii generation.

Audio Capabilities

Audio capabilities progressed from simple tone generators to full PCM sample playback and streaming capabilities.

Sound Architecture

Console Sound Channels Sample Rate
Game Boy 4 (2 square, 1 wave, 1 noise) ~8 kHz
Game Boy Color 4 (same as GB) ~8 kHz
NES 5 (2 pulse, 1 triangle, 1 noise, 1 DPCM) ~21.3 kHz (NTSC) ~17.3 kHz (PAL)
SNES 8 PCM 32 kHz
PC Engine 6 PCM ~7.16 kHz to ~20 kHz
Neo Geo 4 FM, 3 PSG, ADPCM-A, ADPCM-B ~15.7 kHz (ADPCM-A) ~18.5 kHz (ADPCM-B)
Game Boy Adv. 6 (2 direct PCM + 4 PSG) 32 kHz
Nintendo DS 16 PCM 32 kHz
Nintendo 3DS 24 PCM 32 kHz
Wii 64 PCM 48 kHz
PSP 32 PCM 44.1 kHz
Switch 32 PCM 48 kHz

Audio Output

Console Audio Processor Audio Output
Game Boy Custom Sharp LR35902 Mono
Game Boy Color Custom Sharp Z80 Mono
NES Ricoh 2A03 (NTSC) / Ricoh 2A07 (PAL) Mono
SNES Sony SPC700 + DSP Stereo
PC Engine HuC6280 PSG Mono
Neo Geo Yamaha YM2610 Stereo
Game Boy Adv. Custom 2D Core Stereo
Nintendo DS 2D/3D Graphics Engine Stereo
Nintendo 3DS PICA200 GPU Stereo
Wii ATI Hollywood GPU Stereo / DPL II
PSP Sony CXD2962GG + Media Stereo
Switch NVIDIA Tegra X1 Stereo / DPL IIx

Key Observations:

The SNES was revolutionary with its 8-channel PCM audio at 32 kHz, enabling CD-quality sound. The transition from mono to stereo output occurred in the 16-bit generation. Modern consoles support Dolby Pro Logic surround sound encoding.

Special Graphics Features

Beyond basic sprite and tile rendering, many consoles included special graphics modes that enabled advanced visual effects.

Hardware Effects by Console

Game Boy / Game Boy Color:

  • No special graphics modes beyond basic tile and sprite rendering

NES:

  • Attribute Tables (Limited Tile Coloring)
  • CHR-ROM for Tile-Based Graphics

SNES:

  • Mode 7: Affine transformations for scaling and rotation, enabling pseudo-3D effects (used in games like F-Zero and Super Mario Kart)
  • Windowing Effects: Variable transparency regions
  • HDMA (Horizontal Direct Memory Access): Per-scanline effects
  • Color Math: Hardware addition/subtraction for transparency and lighting effects

PC Engine:

  • No special graphics modes beyond standard tile/sprite capabilities

Neo Geo:

  • Hardware Scaling for sprites
  • Line Scroll: Independent line offsets for parallax effects
  • Raster Effects: Per-scanline modifications

Game Boy Advance:

  • Affine Transformation: Mode 7-like scaling and rotation
  • Mosaic Effect: Hardware pixelation for special effects
  • Alpha Blending: Multi-layer transparency
  • Object Priority: Hardware Z-ordering for sprites and backgrounds

Nintendo DS:

  • 3D Rendering: Hardware-accelerated 3D graphics engine
  • Extended Affine Transformations: Advanced 2D rotation and scaling
  • Fog Effects: Depth-based atmospheric effects
  • Multiple Background Layers: Up to 4 background layers with independent scrolling

Nintendo 3DS:

  • Stereoscopic 3D: Glasses-free autostereoscopic 3D display
  • Advanced Shader Support: Programmable vertex and fragment shaders
  • GPU-Accelerated Rendering: PICA200 graphics processor

Wii:

  • GPU Effects: Programmable shaders, bloom, motion blur
  • Texture Mapping: Advanced texture filtering and mipmapping
  • Bump Mapping: Per-pixel lighting simulation
  • Hardware Anti-Aliasing: Multi-sample anti-aliasing (MSAA)

PSP:

  • Hardware Transform & Lighting (T&L): Vertex processing on GPU
  • Texture Compression: Efficient VRAM usage
  • Advanced Alpha Blending: Complex transparency effects

Switch:

  • Advanced Shaders: Physically-Based Rendering (PBR)
  • Hardware-Accelerated Global Illumination: Realistic lighting
  • HDR (High Dynamic Range): Expanded color and brightness range
  • Post-Processing Effects: Depth of field, screen-space ambient occlusion (SSAO), temporal anti-aliasing

Key Observations:

The SNES Mode 7 was revolutionary for its time, enabling pseudo-3D effects with 2D hardware. The transition from fixed-function 2D hardware to programmable 3D GPUs occurred around the Nintendo DS/PSP generation. Modern consoles like the Switch support physically-based rendering and advanced post-processing effects comparable to modern gaming PCs.

Conclusion

The evolution of gaming console hardware represents one of the most dramatic technological progressions in computing history. From the humble Game Boy's 4.19 MHz processor and 8 KB of RAM to the Switch's 1+ GHz quad-core CPU and 4 GB of RAM, each generation brought order-of- magnitude improvements in capabilities.

Understanding these hardware specifications is essential for homebrew developers targeting specific platforms. The constraints of each system - limited sprite counts, scanline restrictions, memory budgets - defined the creative solutions developers employed to create memorable gaming experiences.

Whether you're developing a Game Boy game with 40 sprites and 4 colors, or a Switch title with millions of polygons and advanced shaders, these specifications provide the foundation for understanding what's possible on each platform.

For developers, these tables serve as quick references when planning projects. For enthusiasts, they illuminate why certain games looked and played the way they did. The ingenuity of developers working within these constraints produced some of gaming's most iconic titles.

Aug 06, 2024

Secure Boot with AHAB on i.MX93: A Complete Guide

The security of embedded devices has never been more critical. In a world where attacks targeting IoT systems are becoming increasingly sophisticated, ensuring the integrity of the boot process is a must. This is where Secure Boot comes in—an essential technology that guarantees only authorized code can execute on a device from the moment it starts. In this article, we will explore the implementation of Secure Boot using AHAB, the solution provided by NXP to secure the i.MX93 from its initial boot stages.

Why is Secure Boot crucial for your device?

A secure boot ensures that no malicious code interferes with the critical boot process, protecting your device from attacks targeting the bootloader and early boot stages. Furthermore, AHAB, integrated into i.MX93 processors, enables advanced authentication right from the initial boot stages, ensuring that only validated components can be loaded, thereby strengthening security from the get-go.

Secure boot is a critical security feature that ensures only authenticated and authorized code can run on a device. It operates through a chain of trust, where each component verifies the integrity of the next element in the chain.

Several mechanisms must be used to authenticate each element of this chain, but the mechanism for authenticating the first boot stages depends on the target SoC. The i.MX93 series uses NXP's Advanced High Assurance Boot (AHAB) to secure the first boot stages.

For subsequent stages, you can implement mechanisms such as:

  • Using U-Boot's "verified boot" feature to sign the kernel,
  • Using the default environment (cf. USE_DEFAULT_ENV_FILE), and restricting write access to only a few environment variables (cf. ENV_WRITEABLE_LIST), which are necessary for writable access, such as for OTA updates,
  • Using DM-verity to authenticate the root filesystem,
  • And finally, using OverlayFS combined with DM-crypt to mount encrypted, writable subfolders.

Here, we'll focus on the first part of the secure boot process, using NXP's AHAB to authenticate the bootloader on the NXP i.MX93 in single-boot mode. We will also briefly discuss how to generate the keys to sign the bootloader and provide an introduction to AHAB.

Note: AHAB also provides a complementary encryption feature designed to protect the confidentiality and integrity of data, whereas secure boot focuses on verifying the integrity and authenticity of the boot process. This post will not cover encryption in detail.

AHAB Architecture

The AHAB authentication mechanism is based on public key cryptography using asymmetric keys.

On the i.MX93, AHAB support is provided by a security co-processor, the EdgeLock enclave (ELE), which handles the authentication of binaries signed with one or more private keys. This co-processor contains fuses that must be burned with the hash of the public keys.

AHAB Containers

Since multiple boot stages (e.g., TF-A, OP-TEE, U-Boot, etc.) and firmwares are required to boot i.MX93 platforms, these binaries are packed into containers using the imx-mkimage tool:

bl31.bin
lpddr4_dmem_1d_v202201.bin
lpddr4_dmem_2d_v202201.bin
lpddr4_imem_1d_v202201.bin
lpddr4_imem_2d_v202201.bin
mx93a1-ahab-container.img
tee.bin
u-boot.bin
u-boot-spl.bin

In i.MX93 single-boot mode, the bootloader image contains at least three containers:

  • mx93a1-ahab-container.img: Contains the ELE Firmware.
  • u-boot-atf-container.img: Contains at least the SPL.
  • flash.bin: Contains TF-A, OP-TEE, and U-Boot.
        *start ----> +---------------------------+ ---------
                     |   1st Container header    |   ^
                     |       and signature       |   |
                     +---------------------------+   |
                     | Padding for 1kB alignment |   |
*start + 0x400 ----> +---------------------------+   |
                     |   2nd Container header    |   |
                     |       and signature       |   |
                     +---------------------------+   |
                     |          Padding          |   |  Authenticated at
                     +---------------------------+   |  ELE ROM/FW Level
                     |           ELE FW          |   |
                     +---------------------------+   |
                     |          Padding          |   |
                     +---------------------------+   |
                     |       Cortex-M Image      |   |
                     +---------------------------+   |
                     |         SPL Image         |   v
                     +---------------------------+ ---------
                     |   3rd Container header    |   ^
                     |       and signature       |   |
                     +---------------------------+   |
                     |          Padding          |   | Authenticated
                     +---------------------------+   | at SPL Level
                     |            TF-A           |   |
                     +---------------------------+   |
                     |           OP-TEE          |   |
                     +---------------------------+   |
                     |           U-Boot          |   v
                     +---------------------------+ ---------

These containers are signed offline using NXP Code-Signing Tools (CST), which also allow the creation of an OEM private key infrastructure (PKI) and the generation of the associated public keys (SRK) table, which is burned into the fuses. The CST can also be used with the PKCS#11 standard to access cryptographic services from tokens or devices such as HSM, TPM, and smart cards.

The first container is signed with NXP keys and is authenticated by the ELE ROM, while the other containers are signed with OEM keys.

AHAB Boot Flow

In single boot mode, the Cortex-A55 ROM reads data from the selected boot device, loading all containers in the chosen boot image set one by one. All images within each container (e.g., EdgeLock secure enclave firmware, Cortex-M33 firmware, A55 firmware, OP-TEE, and U-Boot) are loaded, and the EdgeLock secure enclave (ELE) is tasked with authenticating them. The ELE firmware is authenticated by the ELE ROM, and images in the second container are verified by the ELE firmware.

If the bootloader image contains more than two containers, the third and subsequent containers are authenticated by the SPL instead of the ELE.

PKI Generation

To authenticate the bootloader, we need to generate keys. These keys can be created with the CST. The private key will be used to sign the bootloader, and the public key will be burned into the i.MX93 fuses to authenticate the bootloader during boot.

Follow these steps to generate the keys:

cd cst-3.4.1/keys
echo 00000001 > serial

Write the passphrase for the certificate (replace "fooahabcert" with your choice) in two lines, separated by \n. It is important to store this passphrase securely with backups:

echo -e "fooahabcert\nfooahabcert" > key_pass.txt

Generate a P384 ECC PKI tree with a subordinate SGK key on CST:

./ahab_pki_tree.sh
[...]
Do you want to use an existing CA key (y/n)?: n

Key type options (confirm targeted device supports desired key type):
Select the key type (possible values: rsa, rsa-pss, ecc)?: ecc
Enter length for elliptic curve to be used for PKI tree:
Possible values p256, p384, p521:  p384
Enter the digest algorithm to use: sha384
Enter PKI tree duration (years): 10
Do you want the SRK certificates to have the CA flag set? (y/n)?: n

Generate the Signing Root Keys (SRK) Table and SRK Hash for 64-bit Linux machines:

cd ../crts/
../linux64/bin/srktool -a -d sha256 -s sha384 -t SRK_1_2_3_4_table.bin \
    -e SRK_1_2_3_4_fuse.bin -f 1 -c \
    SRK1_sha384_secp384r1_v3_usr_crt.pem,\
    SRK2_sha384_secp384r1_v3_usr_crt.pem,\
    SRK3_sha384_secp384r1_v3_usr_crt.pem,\
    SRK4_sha384_secp384r1_v3_usr_crt.pem

Do not enter spaces between the commas when specifying the SRKs in the "-c" or "--certs" option. Otherwise, the certificates specified after the first space will be excluded from the table.

Regenerate the SRK HASH (SRK_1_2_3_4_fuse.bin) using SHA256 with the SRK_1_2_3_4_table.bin:

openssl dgst -binary -sha256 SRK_1_2_3_4_table.bin

Optionally, verify that the sha256sum of SRK_1_2_3_4_table matches the SRK_1_2_3_4_fuse.bin:

od -t x4 SRK_1_2_3_4_fuse.bin
0000000 29eec727 eaed9aa7 c7e53bc0 36835f78
0000020 6901bc47 b244753c f78d3162 27ae36b9
0000040

Bootloader Signature

The CST uses CSF description files to sign (and encrypt) containers generated by imx-mkimage with OEM keys. When imx-mkimage generates containers, it also specifies the block offsets to be used in the CSF description files. For example, imx-mkimage returns the following values for your bootloader:

CST: CONTAINER 0 offset: 0x0
CST: CONTAINER 0: Signature Block: offset is at 0x190
CST: CONTAINER 0 offset: 0x400
CST: CONTAINER 0: Signature Block: offset is at 0x490

Where 0x190 is the block offset for the second container header and 0x490 is the block offset for the third container header.

The CSF description file used to sign a container contains three sections:

  • [Header]: Information about the HAB version to use for signing.
  • [Authenticate Data]: Information about the key used to sign.
  • [Install SRK]: Information about the container being signed.

The following CSF description files were used to sign the u-boot-atf-container.img in our example:

[Header]
Target = AHAB
Version = 1.0

[Install SRK]
# SRK table generated by srktool
File = "SRK_1_2_3_4_table.bin"
# Public key certificate in PEM format
Source = "SRK1_sha384_secp384r1_v3_usr_crt.pem"
# Index of the public key certificate within the SRK table (0 .. 3)
Source index = 0
# Type of SRK set (NXP or OEM)
Source set = OEM
# bitmask of the revoked SRKs
Revocations = 0x0

[Authenticate Data]
# Binary to be signed generated by mkimage
File = "u-boot-atf-container.img"
# Offsets = Container header  Signature block (printed out by mkimage)
Offsets = 0x0 0x190

The following CSF description files were used to sign flash.bin in our example:

[Header]
Target = AHAB
Version = 1.0

[Install SRK]
# SRK table generated by srktool
File = "SRK_1_2_3_4_table.bin"
# Public key certificate in PEM format
Source = "SRK1_sha384_secp384r1_v3_usr_crt.pem"
# Index of the public key certificate within the SRK table (0 .. 3)
Source index = 0
# Type of SRK set (NXP or OEM)
Source set = OEM
# bitmask of the revoked SRKs
Revocations = 0x0

[Authenticate Data]
# Binary to be signed generated by mkimage
File = "flash.bin"
# Offsets = Container header  Signature block (printed out by mkimage)
Offsets = 0x400 0x490

The first step is to generate a u-boot-atf-container.img, then copy the block offsets into the CSF description file to sign it:

make SOC=iMX9 REV=A1 dtbs=imx93-11x11-evk.dtb u-boot-atf-container.img

Next, sign it with the following command and replace the unsigned version:

cst -i u-boot-atf-container.img.csf -o u-boot-atf-container.img.signed
mv u-boot-atf-container.img.signed u-boot-atf-container.img

Then generate a flash.bin containing the signed u-boot-atf-container.img:

make SOC=iMX9 REV=A1 V2X=NO dtbs=imx93-11x11-evk.dtb flash_singleboot

Finally, sign the resulting flash.bin:

cst -i flash.bin.csf -o flash.bin.signed

Burn Fuses

Once the signed flash.bin is flashed, you need to burn the public keys used to sign the bootloader into the i.MX93 fuses to finalize AHAB secure boot. This requires using a U-Boot that provides AHAB functionalities, such as checking ELE events during bootloader authentication and securing the device.

Program SRK

The following commands enable AHAB secure boot by programming the SRK_HASH[255:0] fuses on i.MX93, ensuring that only bootloaders signed with keys matching the SRK hash programmed into the fuses will be accepted:

fuse prog -y 16 0 0x29eec727
fuse prog -y 16 1 0xeaed9aa7
fuse prog -y 16 2 0xc7e53bc0
fuse prog -y 16 3 0x36835f78
fuse prog -y 16 4 0x6901bc47
fuse prog -y 16 5 0xb244753c
fuse prog -y 16 6 0xf78d3162
fuse prog -y 16 7 0x27ae36b9

Close the Device

Once the SRK fuses are programmed, you can "close" the device to allow only the bootloader signed with keys matching the SRK table to boot:

ahab_close

Before closing the device, you can verify that the fuses have been written correctly by checking that no ELE events are raised:

ahab_status
Lifecycle: 0x00000008, OEM Open

No Events Found!
=>
Lifecycle: 0x00000008, OEM Open

No Events Found!

Once the device is closed, the ahab_status command will show OEM closed:

ahab_status
Lifecycle: 0x00000020, OEM closed

No Events Found!
=>
Lifecycle: 0x00000020, OEM closed
No Events Found!

As long as OEM Open appears in the status, the device is not secured and can still execute unsigned bootloaders or those signed with invalid keys.

Conclusion

By implementing AHAB on the i.MX93 platform, you can ensure that your boot process is protected from unauthorized code. The use of public key cryptography and secure containers adds an extra layer of security, making your device more resilient to attacks. This process is crucial for applications where integrity and authenticity from the very first boot stage are paramount.

Jul 29, 2022

Zephyr Device Tree Guide

Introduction

The goal of the Zephyr project, hosted by the Linux foundation, since 2016, is to provide a safe and secured real time operating system (RTOS) for connected devices that are too small for Linux, or for core companion, through the Apache 2.0 open source license.

It is designed for resource-constrained devices such as microcontrollers and Internet of Things (IoT) devices, to be modular and scalable. This makes it ideal for a wide range of devices, from simple sensors to complex systems. The operating system is written in C and is fully compatible with the C11 and C++17 standards.

One of the key benefits of the Zephyr device model is its small footprint, it can be configured to run on devices with as little as 10 KB of memory.

It supports multiple 32 bits and 64 bits architectures: Cortex-A, Cortex-M, Cortex-R, RISC-V, x86-64, etc. But it also support several boards and extensions: Feather, nRF52840, ST Discovery, ST Nucleo, ESP-32, etc. It is able to manage several kinds of connectivity: Bluetooth, ethernet, wifi, LoRa. And it support some network protocols: IPv4, IPv6,UDP, TCP, CoAP, LWM2M, MQTT, DNS, etc.

As Linux, Zephyr use Kconfig, and its device model is mainly based on device tree.

Device tree

Device trees are tree data structures that describe the hardware components and their relationships in a system. They are stored in a text file, named device tree sources (*.dts), and they written by developers to describe hardware architectures of SoCs and boards. And they are used by the operating system to determine how to initialize and interact with the hardware.

Each node describe a device of the system, has its own properties that describe their characteristics, and they have only one parent (except for the root node).

Each device driver is associated with a specific device tree node, which represents a hardware component in the system. The device driver provides the necessary code and data to control the behavior of the hardware component.

test_i2c_bme280: bme280@6 {
        compatible = "bosch,bme280";
        reg = <0x6>;
};

In the Linux kernel, device tree sources are compiled to device tree binaries (dtb) that are parsed, at boot, by bootloader stages (U-Boot, TF-A...) and the kernel to allow support several hardware configuration with same binaries.

But in Zephyr, device tree sources are transformed to a "devicetree_generated.h" C header file at build, that contains macro definitions and data structures allowing device drivers to access information about the hardware components in the system, such as the memory mapping of a device, its pin assignments, and its IRQ numbers:

#define DT_COMPAT_HAS_OKAY_bosch_bme280 1
#define DT_N_INST_bosch_bme280_NUM_OKAY 1
#define DT_FOREACH_OKAY_bosch_bme280(fn) fn(DT_N_S_soc_S_i2c_40005400_S_bme280_77)
#define DT_FOREACH_OKAY_VARGS_bosch_bme280(fn, ...) fn(DT_N_S_soc_S_i2c_40005400_S_bme280_77, __VA_ARGS__)
#define DT_FOREACH_OKAY_INST_bosch_bme280(fn) fn(0)
#define DT_FOREACH_OKAY_INST_VARGS_bosch_bme280(fn, ...) fn(0, __VA_ARGS__)
#define DT_COMPAT_bosch_bme280_BUS_i2c 1

Where:

  • DT_COMPAT_HAS_OKAY_bosch_bme280: indicates that there is at least one instance of BME280
  • DT_N_INST_bosch_bme280_NUM_OKAY: defines the number of BME280 instances that are marked okay
  • DT_FOREACH_OKAY_bosch_bme280: allows you to apply a function fn to each instance of the BME280
  • DT_FOREACH_OKAY_VARGS_bosch_bme280: also allows you to apply a function fn to each instance of the BME280, but with additional arguments
  • DT_FOREACH_OKAY_INST_bosch_bme280: allows you to apply a function fn to each instance of the BME280, passing the instance number as an argument
  • DT_FOREACH_OKAY_INST_VARGS_bosch_bme280: is similar to the previous macro, but this one allows for additional arguments
  • DT_COMPAT_bosch_bme280_BUS_i2c: indicates that the BME280 device is connected to an I2C bus.
  • DT_N_S_soc_S_i2c_40005400_S_bme280_77: refers to a specific node in the device tree, here it refers to the BME280 sensor connected to the I2C controller with the base address 0x40005400 within the SoC. The sensor's address on this I2C bus is 0x77.

In addition, device tree sources can be extended or overridden, for example to connect additional devices to a board, or to disable board devices which will not be used:

/ {
        aliases {
                bme280 = &bme280;
        };
};

&spi1 {
        status = "disabled";
};

&i2c1 {
        status = "okay";
        bme280: bme280@77 {
                compatible = "bosch,bme280";
                reg = <0x77>;
        };
};

Binding

Content of device tree sources is described in binding files, that are written in human readable and easy to parse YAML. Binding files can be also used to validate device tree sources by comparing the information in the YAML file with the information in the device tree sources.

description: BME280 integrated environmental sensor

compatible: "bosch,bme280"

include: [sensor-device.yaml, i2c-device.yaml]

Device driver

In Zephyr, a device driver can access the properties of an associated node in the device tree using the macro that are defined in C header files. For example, the following code can be used to initialize a BME280 sensor using properties defined in the device tree:

#include <device.h>
#include <drivers/i2c.h>
#include <devicetree.h>
#include <zephyr.h>

// Define the node identifier for the BME280 sensor
#define BME280_NODE DT_N_S_soc_S_i2c_40005400_S_bme280_77

// Function to initialize the BME280 sensor
static int bme280_init(const struct device *dev)
{
    // Check if the node is available
    if (!device_is_ready(dev)) {
        printk("Device %s is not ready\n", dev->name);
        return -ENODEV;
    }

    // Retrieve the I2C device associated with the BME280 node
    const struct device *i2c_dev = DEVICE_DT_GET(DT_BUS(BME280_NODE));

    if (!device_is_ready(i2c_dev)) {
        printk("I2C device not ready\n");
        return -ENODEV;
    }

    // Write some initialization code here, such as configuring registers

    printk("BME280 sensor initialized\n");
    return 0;
}

// Initialize the BME280 sensor at boot time
SYS_INIT(bme280_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);

Conclusion

Those who have already implemented BSP or driver on Linux shouldn't encounter too much difficulty, but on the other hand, the step is a little higher for people coming from the world of micro-controllers.

Sep 27, 2020

Build RIOT-OS with Podman

Summary

This article is a tip that explains how it is possible to build a RIOT-OS application with Podman and the official build container. And I would like to take this opportunity to introduce you to Podman and RIOT-OS.

Podman

Some Linux distribution, like Fedora chosen to officially support only Podman instead of Docker for several reasons:

  • It is daemonless container engine.
  • It is rootless.
  • It follows Open Container Initiative (OCI) standards.
  • It is safer than the Docker engine.
  • It introduces the notion of Pods: a group of container(s) that share storage or network resources.

Moreover, Podman is able to use the images built by the Docker engine and has been stored in Docker registry.

However, most of the time the Podman commands are identical to that of Docker, then a simple alias is enough to be misleading: alias docker=podman.

But as Podman is rootless and safer than Docker, then sometimes it is necessary to specify additional security parameters.

RIOT-OS

RIOT-OS is a memory-constrained RTOS, such as Contiki, that provides real-time and multithreading abilities, and it runs on processors from 8bits to 32bits.

It was designed for IoT devices then to be low power consumption and it provides three very complete network stacks including some protocols as:

The RIOT-OS project also provides some useful tools including a build container (riotdocker).

And the build environment of RIOT-OS offers a Makefile to build an application with this container simply by setting the variable BUILD_IN_DOCKER to 1. Then the prebuilt image is downloaded and instantiated to execute the make command.

By default, this feature is configured to be used with the Docker engine, but it is possible to override some variables from the build environment either to use a custom prebuilt image, either use another engine or to use custom engine parameters.

Then here, we will use these environments variable to instantiate a container with Podman (instead of Docker) and with the required parameters.

Tip of the day

In the following example, we build the Helloworld application for a STM32 Discovery board. To do that we specify the engine by setting the variable DOCKER to the value podman. The variable DOCKER_USER is set empty because in the variable DOCKER_RUN_FLAGS the parameter --userns is set to keep-id to map the uid:gid of the current rootless user (from host) with the values that will be used into the container.

export BUILD_IN_DOCKER=1
export DOCKER="podman"
export DOCKER_USER=""
export DOCKER_RUN_FLAGS="--rm -i -t --security-opt seccomp=unconfined --security-opt label=disable --userns=keep-id"
export DOCKER_MAKE_ARGS="-j$(nproc)"

make BOARD=stm32l476g-disco
Launching build container using image "riot/riotbuild:latest".
podman run --rm -i -t --security-opt seccomp=unconfined --security-opt label=disable --userns=keep-id -v '/usr/share/zoneinfo/Europe/Paris:/etc/localtime:ro' -v '/home/tperrot/dev/tprrt/pwm-ramp-gen/RIOT:/data/riotbuild/riotbase:delegated' -e 'RIOTBASE=/data/riotbuild/riotbase' -e 'CCACHE_BASEDIR=/data/riotbuild/riotbase' -e 'BUILD_DIR=/data/riotbuild/riotbase/build' -v '/home/tperrot/dev/tprrt/pwm-ramp-gen:/data/riotbuild/riotproject:delegated' -e 'RIOTPROJECT=/data/riotbuild/riotproject' -e 'RIOTCPU=/data/riotbuild/riotbase/cpu' -e 'RIOTBOARD=/data/riotbuild/riotbase/boards' -e 'RIOTMAKE=/data/riotbuild/riotbase/makefiles'     -v '/home/tperrot/dev/tprrt/pwm-ramp-gen/.git:/home/tperrot/dev/tprrt/pwm-ramp-gen/.git:delegated' -e 'BOARD=stm32l476g-disco'  -w '/data/riotbuild/riotproject/' 'riot/riotbuild:latest' make 'BOARD=stm32l476g-disco'   -j8
Building application "hello-world" for "stm32l476g-disco" with MCU "stm32".

[INFO] cloning stm32cmsis
fatal: not a git repository: /data/riotbuild/riotbase/../.git/modules/RIOT
Cloning into '/data/riotbuild/riotbase/cpu/stm32/include/vendor/cmsis/l4'...
remote: Enumerating objects: 364, done.
remote: Counting objects: 100% (364/364), done.
remote: Compressing objects: 100% (71/71), done.
remote: Total 364 (delta 309), reused 344 (delta 289), pack-reused 0
Receiving objects: 100% (364/364), 709.56 KiB | 561.00 KiB/s, done.
Resolving deltas: 100% (309/309), done.
HEAD is now at e442c72 Release v1.6.1
[INFO] updating stm32cmsis /data/riotbuild/riotbase/cpu/stm32/include/vendor/cmsis/l4/.pkg-state.git-downloaded
echo e442c72651e8d4757f6562acc14da949644944ce   > /data/riotbuild/riotbase/cpu/stm32/include/vendor/cmsis/l4/.pkg-state.git-downloaded
[INFO] patch stm32cmsis
"make" -C /data/riotbuild/riotbase/boards/stm32l476g-disco
"make" -C /data/riotbuild/riotbase/core
"make" -C /data/riotbuild/riotbase/cpu/stm32
"make" -C /data/riotbuild/riotbase/drivers
"make" -C /data/riotbuild/riotbase/sys
"make" -C /data/riotbuild/riotbase/cpu/cortexm_common
"make" -C /data/riotbuild/riotbase/cpu/stm32/periph
"make" -C /data/riotbuild/riotbase/drivers/periph_common
"make" -C /data/riotbuild/riotbase/cpu/stm32/stmclk
"make" -C /data/riotbuild/riotbase/sys/auto_init
"make" -C /data/riotbuild/riotbase/cpu/cortexm_common/periph
"make" -C /data/riotbuild/riotbase/cpu/stm32/vectors
"make" -C /data/riotbuild/riotbase/sys/malloc_thread_safe
"make" -C /data/riotbuild/riotbase/sys/newlib_syscalls_default
"make" -C /data/riotbuild/riotbase/sys/pm_layered
"make" -C /data/riotbuild/riotbase/sys/stdio_uart
   text    data     bss     dec     hex filename
   8900     112    2300   11312    2c30 /data/riotbuild/riotproject/bin/stm32l476g-disco/hello-world.elf

Sep 08, 2020

How the Busybox's chrt applet works

Introduction

In this article, I will dissect how the chrt applet from the release 1.32.0 of Busybox works, what it does, etc.

This command is a Linux utils allowing to consult or to modify the scheduling attributes of a process.

chrt -m
SCHED_OTHER min/max priority    : 0/0
SCHED_FIFO min/max priority     : 1/99
SCHED_RR min/max priority       : 1/99
SCHED_BATCH min/max priority    : 0/0
SCHED_IDLE min/max priority     : 0/0
SCHED_DEADLINE min/max priority : 0/0

pidof firefox
6987 6851 6825 6816 6800 6771 6767 6761 6720 6611

chrt -p 6987
pid 6987's current scheduling policy: SCHED_OTHER
pid 6987's current scheduling priority: 0

sudo chrt -f -p 1 6987
chrt -p 6987
pid 6987's current scheduling policy: SCHED_FIFO
pid 6987's current scheduling priority: 1

Busybox provides an applet whose size, once compiled, is ten times smaller than that of the binary implementation and with some limitations.

The dissection

The implementation of the chrt applet is in the file util-linux/chrt.c that containing several functions which are called in the main function of this applet.

The main function of this applet is divided into three main parts: - the first parses the command options - the second prints the scheduler's information - the last one, to apply scheduler changes in case of a set

At start of main, the character string containing the options are parsed to obtain a bitfield easier to use:

opt = getopt32(argv, "^"
                "+" "mprfobi"
                "\0"
                /* only one policy accepted: */
                "r--fobi:f--robi:o--rfbi:b--rfoi:i--rfob"
);

If the (-m) is set then the min and max valid priorities for each scheduling policies are shown and the command exits:

if (opt & OPT_m) { /* print min/max and exit */
        show_min_max(SCHED_OTHER);
        show_min_max(SCHED_FIFO);
        show_min_max(SCHED_RR);
        show_min_max(SCHED_BATCH);
        show_min_max(SCHED_IDLE);
        fflush_stdout_and_exit(EXIT_SUCCESS);
}

The function show_min_max uses the Posix functions sched_get_priority_max and sched_get_priority_min from the standard C library to send a syscall to the kernel in order to obtain the min and max values accepted by each policy:

max = sched_get_priority_max(pol);
min = sched_get_priority_min(pol);
if ((max|min) < 0)
    fmt = "SCHED_%s not supported\n";

Otherwise the required options and arguments to show or to apply real-time attributes of a process:

//if (opt & OPT_r)
//  policy = SCHED_RR; - default, already set
if (opt & OPT_f)
    policy = SCHED_FIFO;
if (opt & OPT_o)
    policy = SCHED_OTHER;
if (opt & OPT_b)
    policy = SCHED_BATCH;
if (opt & OPT_i)
    policy = SCHED_IDLE;

argv += optind;
if (!argv[0])
    bb_show_usage();
if (opt & OPT_p) {
    pid_str = *argv++;
    if (*argv) { /* "-p PRIO PID [...]" */
            priority = pid_str;
            pid_str = *argv;
    }
    /* else "-p PID", and *argv == NULL */
    pid = xatoul_range(pid_str, 1, ((unsigned)(pid_t)ULONG_MAX) >> 1);
} else {
    priority = *argv++;
    if (!*argv)
            bb_show_usage();
}

Then the applet uses the Posix function sched_getscheduler provides by the standard C library to obtain the scheduling attributes of the process specified by the pid.

print_rt_info:
    pol = sched_getscheduler(pid);
    if (pol < 0)
            bb_perror_msg_and_die("can't %cet pid %u's policy", 'g', (int)pid);

Finally, when the chrt applet is used to modify scheduling attributes then the Posix function sched_getscheduler is used and the new scheduling attributes are showed:

if (sched_setscheduler(pid, policy, &sp) < 0)
    bb_perror_msg_and_die("can't %cet pid %u's policy", 's', (int)pid);

if (!argv[0]) /* "-p PRIO PID [...]" */
    goto print_rt_info;

The function sched_setscheduler and sched_getscheduler will send a syscall to the scheduler subsystem of the kernel Linux. This subsystem also exposes this information from /proc:

cat /proc/6987/sched
WebExtensions (6987, #threads: 23)
-------------------------------------------------------------------
se.exec_start                                :       4421312.640001
se.vruntime                                  :        344438.942254
se.sum_exec_runtime                          :         38238.466094
se.nr_migrations                             :                 6811
nr_switches                                  :                49452
nr_voluntary_switches                        :                21749
nr_involuntary_switches                      :                27703
se.load.weight                               :              1048576
se.runnable_weight                           :              1048576
se.avg.load_sum                              :                 3415
se.avg.runnable_load_sum                     :                 3415
se.avg.util_sum                              :              3497621
se.avg.load_avg                              :                   74
se.avg.runnable_load_avg                     :                   74
se.avg.util_avg                              :                   74
se.avg.last_update_time                      :        4421312640000
se.avg.util_est.ewma                         :                   75
se.avg.util_est.enqueued                     :                   75
policy                                       :                    0
prio                                         :                  120
clock-delta                                  :                   89
mm->numa_scan_seq                            :                    0
numa_pages_migrated                          :                    0
numa_preferred_nid                           :                   -1
total_numa_faults                            :                    0
current_node=0, numa_group_id=0
numa_faults node=0 task_private=0 task_shared=0 group_private=0 group_shared=0

Limitations

Below a short list of limitations that I observed during my analysis of this applet.

Resetting scheduling policy

The chrt applet doesn't offer an option (-R) to specify if the scheduling policy should be applied or reset when a process forks to create children. This feature, introduced since Linux 2.6.32, can be only enabled or disabled at the build of busybox and it is applied on all scheduling attributes modifications done with this applet.

Deadline support

The chrt applet doesn't provide the required scheduling options (-d, -T, -P and -D) to set the deadline scheduling attributes of a process.

Jun 27, 2020

Build an embedded Linux in less than 15 minutes

Introduction

Since some years, I haven't built an embedded Linux without using a framework, like Open Embedded from the Yocto project. Then here, I wanted to make a guide to help you to build quickly, from "scratch" a very minimal embedded Linux to boot a target. The following examples have been written to boot a virtual Qemu target but, they can be adapted to boot a real target. Moreover, the build environment will be bootstrapped with a prebuilt cross-toolchain, I have chosen to use one provided by Bootlin and using glibc.

Setup the environment

First, it is required to install the packages that are needed to install and use the cross-toolchain but also to compile the host tools and to provide Qemu:

  • The Ncurses libraries are only required to execute the command make menuconfig.
  • The certificates and wget will be used to download the prebuilt toolchain.
  • In the same way, git will be used to checkout the source of Busybox and Linux.
  • The Qemu packages will be used to emulate system platform and to execute static binaries cross-compiled for aarch64 on the x86-64 host.
apt update
apt install -y --no-install-recommends \
    bc \
    build-essential \
    ca-certificates \
    cpio \
    file \
    flex \
    git \
    ipxe-qemu \
    libncurses5-dev \
    libncursesw5-dev \
    libssl-dev \
    qemu \
    qemu-system-aarch64 \
    qemu-user-static \
    wget

Now, it is time to download and install the prebuilt toolchain:

mkdir ~/src
cd ~/src
wget https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64/tarballs/aarch64--glibc--stable-2020.08-1.tar.bz2
tar xvjf aarch64--glibc--stable-2020.08-1.tar.bz2

Once the toolchain has been extracted you have to set the required environment variables to cross-compile binaries:

  • PATH: It shall be extended so that the cross-tools from the cross-toolchain will be available from the environment
  • CROSS_COMPILE: In order to clarify the prefix used by the cross-tools
  • ARCH: The architecture of the target platform
ls ~/src/aarch64--glibc--stable-2020.08-1/bin/*gcc
~/src/aarch64--glibc--stable-2020.08-1/bin/aarch64-linux-gcc

export PATH=~/src/aarch64--glibc--stable-2020.08-1/bin:$PATH
export CROSS_COMPILE=aarch64-linux-

Now, it is possible to call the cross-tools from the shell:

aarch64-linux-gcc -v
Using built-in specs.
COLLECT_GCC=~/src/aarch64--glibc--stable-2020.08-1/bin/aarch64-linux-gcc.br_real
COLLECT_LTO_WRAPPER=~/src/aarch64--glibc--stable-2020.08-1/bin/../libexec/gcc/aarch64-buildroot-linux-gnu/9.3.0/lto-wrapper
Target: aarch64-buildroot-linux-gnu
<...>
Thread model: posix
gcc version 9.3.0 (Buildroot 2020.08-14-ge5a2a90)

Concerning the variable PATH this one will be set afterwards because its value depends on the binary that will be built.

Build the Linux kernel

So, the environment is ready to pull the sources of the latest stable branch of the kernel Linux and to build them:

git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
cd linux
git checkout -b local/linux-5.4.y origin/linux-5.4.y
# git show HEAD

export ARCH=arm64

make defconfig
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/kconfig/conf.o
  HOSTCC  scripts/kconfig/confdata.o
  HOSTCC  scripts/kconfig/expr.o
  LEX     scripts/kconfig/lexer.lex.c
  YACC    scripts/kconfig/parser.tab.[ch]
  HOSTCC  scripts/kconfig/lexer.lex.o
  HOSTCC  scripts/kconfig/parser.tab.o
  HOSTCC  scripts/kconfig/preprocess.o
  HOSTCC  scripts/kconfig/symbol.o
  HOSTLD  scripts/kconfig/conf
*** Default configuration is based on 'defconfig'
#
# configuration written to .config
#

# make menuconfig

make -j$(nproc)
  <...>
  AR      drivers/net/ethernet/built-in.a
  AR      drivers/net/built-in.a
  AR      drivers/built-in.a
  GEN     .version
  CHK     include/generated/compile.h
  LD      vmlinux.o
  MODPOST vmlinux.o
  MODINFO modules.builtin.modinfo
  LD      .tmp_vmlinux.kallsyms1
  KSYM    .tmp_vmlinux.kallsyms1.o
  LD      .tmp_vmlinux.kallsyms2
  KSYM    .tmp_vmlinux.kallsyms2.o
  LD      vmlinux
  SORTEX  vmlinux
  SYSMAP  System.map
  Building modules, stage 2.
  MODPOST 531 modules
  OBJCOPY arch/arm64/boot/Image
  GZIP    arch/arm64/boot/Image.gz

The command make defconfig will apply the default configuration for the target platform (cf. ARCH=arm64), and the compilation will be performed by make -j$(nproc).

The commands git show HEAD and make defconfig are optional: - the first is useful to verify that the latest commit corresponding to the latest tag of the branch linux-5.4.y. - the second can be used if you want to customize the kernel configuration.

NB. The kernel Linux but also Busybox and some projects use Kbuild to manage the build options

Populate the sysroot

The easy way to bootstrap a sysroot is to use Busybox that has been created to offer common UNIX tools into a single executable and it is size-optimized. To create a sysroot, it is only required to add a few configuration files.

The steps to pull and build Busybox are similar to those of the kernel Linux.

git clone git://git.busybox.net/busybox
cd busybox
git checkout -b local/1_32_stable origin/1_32_stable
# git show HEAD

export ARCH=aarch64
export LDFLAGS="--static"

make defconfig
# make menuconfig
make -j$(nproc)

make install

Here, the LDFLAGS is set to force static linking of Busybox quickly, but it is also possible to use make menuconfig to set CONFIG_STATIC=y. The advantage of the static executable is that it can be tested with Qemu:

qemu-aarch64-static busybox echo "Hello!"
Hello!
qemu-aarch64-static busybox date
Sat Jun 27 15:06:41 UTC 2020

The binary qemu-aarch64-static allows to execute a binary built for another architecture on the host computer, for example here it allows to execute the Busybox binary compiled for an aarch64 target on a x86-64 host.

The last command make install created a tree into the _install directory that can be used to populate the sysroot:

ls -l _install
total 4
drwxr-xr-x. 1 tperrot tperrot 974 Nov 30 15:22 bin
lrwxrwxrwx. 1 tperrot tperrot  11 Nov 30 15:22 linuxrc -> bin/busybox
drwxr-xr-x. 1 tperrot tperrot 986 Nov 30 15:22 sbin
drwxr-xr-x. 1 tperrot tperrot  14 Nov 30 15:22 usr

ls -l _install/bin
<...>
lrwxrwxrwx. 1 tperrot tperrot       7 Nov 30 15:22 umount -> busybox
lrwxrwxrwx. 1 tperrot tperrot       7 Nov 30 15:22 uname -> busybox
lrwxrwxrwx. 1 tperrot tperrot       7 Nov 30 15:22 usleep -> busybox
lrwxrwxrwx. 1 tperrot tperrot       7 Nov 30 15:22 vi -> busybox
lrwxrwxrwx. 1 tperrot tperrot       7 Nov 30 15:22 watch -> busybox
lrwxrwxrwx. 1 tperrot tperrot       7 Nov 30 15:22 zcat -> busybox

In order, to finalize this minimal sysroot, it is required to create a rcS init script:

mkdir _install/proc _install/sys _install/dev _install/etc _install/etc/init.d
cat > _install/etc/init.d/rcS << EOF
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
/sbin/mdev -s
[ ! -h /etc/mtab ]  && ln -s /proc/mounts /etc/mtab
[ ! -f /etc/resolv.conf ] && cat /proc/net/pnp > /etc/resolv.conf
EOF
chmod +x _install/etc/init.d/rcS

Build the filesystem

The target of this step is to package the sysroot tree into a filesystem that can be mounted by the kernel. There is two available possibilities, either build a ramfs or a rootfs.

Globally, the difference between both is that:

  • the ramfs is a very simple filesystem that can be used by the kernel to create a block device into the RAM space from an archive.
  • the rootfs is a filesystem mounted from a non volatile device by the kernel.

For more information about the difference between the ramfs and the rootfs, you can you refer to the kernel documentation.

Build a ramfs

To build the ramfs we will use cpio and gzip to construct the compressed archive after modifying the rights:

mkdir _rootfs
rsync -a _install/ _rootfs
chown -R root:root _rootfs
cd _rootfs
find . | cpio -o --format=newc > ../rootfs.cpio
cd ..
gzip -c rootfs.cpio > rootfs.cpio.gz

Build a rootfs

To build the rootfs, the first step is to create an empty binary blob that will be mounted into a loop device to be formatted to create a ext3 filesystem. Then the tree can be copied and the rights updated.

dd if=/dev/zero of=rootfs.img bs=1M count=10
mke2fs -j rootfs.img
mkdir _rootfs
mount -o loop rootfs.img _rootfs
rsync -a _install/ _rootfs
chown -R root:root _rootfs
sync
umount _rootfs

Boot the target

Following, the qemu commands to boot the minimal embedded Linux system that has been built.

# With the ramfs
qemu-system-aarch64 -nographic -no-reboot -machine virt -cpu cortex-a57 -smp 2 -m 256 \
    -kernel ~/src/linux/arch/arm64/boot/Image \
    -initrd ~/src/busybox/rootfs.cpio.gz \
    -append "panic=5 ro ip=dhcp root=/dev/ram rdinit=/sbin/init"

# With the rootfs
qemu-system-aarch64 -nographic -no-reboot -machine virt -cpu cortex-a57 -smp 2 -m 256 \
    -kernel ~/src/linux/arch/arm64/boot/Image \
    -append "panic=5 ro ip=dhcp root=/dev/vda" \
    -drive file=~/src/busybox/rootfs.img,format=raw,if=none,id=hd0 -device virtio-blk-device,drive=hd0

Then the target will be boot to shell, "It's alive!":

[    0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd070]
[    0.000000] Linux version 5.10.0-rc5 (tperrot@27ea4a863f61) (aarch64-linux-gcc.br_real (Buildroot 2020.08-14-ge5a2a90) 9.3.0, GNU ld (GNU Binutils) 2.33.1) #1 SMP PREEMPT Mon Nov 30 14:40:05 UTC 2020
[    0.000000] Machine model: linux,dummy-virt
<...>
[    0.858346] Sending DHCP requests ., OK
[    0.870558] IP-Config: Got DHCP answer from 10.0.2.2, my address is 10.0.2.15
[    0.870909] IP-Config: Complete:
[    0.871199]      device=eth0, hwaddr=52:54:00:12:34:56, ipaddr=10.0.2.15, mask=255.255.255.0, gw=10.0.2.2
[    0.871566]      host=10.0.2.15, domain=, nis-domain=(none)
[    0.871825]      bootserver=10.0.2.2, rootserver=10.0.2.2, rootpath=
[    0.871866]      nameserver0=10.0.2.3
[    0.872389]
[    0.875863] ALSA device list:
[    0.876151]   No soundcards found.
[    0.879353] uart-pl011 9000000.pl011: no DMA platform data
[    0.920237] Freeing unused kernel memory: 5952K
[    0.921223] Run /sbin/init as init process

Please press Enter to activate this console.

May 28, 2020

My blog opening

Welcome,

After closing my last blog seventeen years ago, in order to share my knowledge and my little experiments about embedded open source. As you might have guessed, this blog will mainly focus on embedded Linux operating systems, but also about open firmware and rtos, as well as related topics like virtualization, security, etc.

I hope you will like the articles of this blog, enjoy the reading.