Arsitektur Memori Raspberry Pi 1: Panduan Lengkap Pengembangan OS Bare Metal

Dalam pengembangan sistem operasi bare metal pada Raspberry Pi 1 (SoC Broadcom BCM2835), pemahaman mendalam mengenai peta memori (memory map) adalah fondasi utama. Tanpa adanya sistem operasi perantara seperti Linux, pengembang bertanggung jawab penuh untuk mengelola tata letak instruksi dan data di dalam RAM.

Artikel ini akan membedah struktur memori Raspberry Pi 1 secara rinci, mulai dari alamat paling dasar hingga area kontrol perangkat keras, dilengkapi dengan contoh implementasi praktis.


Representasi Visual Memory Map

Berikut adalah struktur pemetaan memori pada Raspberry Pi 1 saat proses booting berlangsung:

Alamat Memori   Struktur / Bagian       Keterangan
0x00000000 ┬───────────────────┐
│ ARM Vector Table │ (Interupsi & Exception)
│ ATAGS / Dev Tree │ (Informasi Boot dari Firmware)
│ Stack (Grows Down)│ ← Biasanya SP diatur ke 0x8000
0x00008000 ┼───────────────────┤ ← Kernel Entry (Titik Mulai .text)
│ │
│ .text │ (Kode Program / Instruksi)
│ │
├───────────────────┤
│ .data │ (Variabel Global dengan Nilai)
├───────────────────┤
│ .bss │ (Variabel Global Kosong/Nol)
├───────────────────┤
│ │
│ Free RAM │ (Tempat untuk Heap / Alokasi Dinamis)
│ │
0x20000000 ┴───────────────────┘ ← Peripheral Base Address (I/O)
│ Registers (GPIO) │ (Memory Mapped I/O)
│ Registers (UART) │
└───────────────────┘

Analisis Bagian Memori

1. Area Low Memory (0x00000000 – 0x00007FFF)

Area ini merupakan wilayah yang sangat krusial pada saat inisialisasi awal.

ARM Vector Table (0x00000000)

Terletak tepat di alamat 0x00000000, bagian ini berisi tabel instruksi yang akan dieksekusi oleh prosesor ketika terjadi exception. Struktur standar vector table adalah:

0x00000000: Reset Handler
0x00000004: Undefined Instruction
0x00000008: Software Interrupt (SWI/SVC)
0x0000000C: Prefetch Abort
0x00000010: Data Abort
0x00000014: Reserved
0x00000018: IRQ (Interrupt Request)
0x0000001C: FIQ (Fast Interrupt Request)

Setiap entry berisi instruksi branch (B atau LDR PC) yang mengarahkan eksekusi ke handler yang sesuai.

ATAGS / Device Tree (0x00000100)

Firmware Raspberry Pi (GPU) meletakkan informasi sistem di alamat 0x00000100. Struktur ATAGS berisi:

  • ATAG_CORE: Tag pembuka yang menandai awal struktur
  • ATAG_MEM: Informasi ukuran dan lokasi RAM fisik
  • ATAG_CMDLINE: Parameter command line dari cmdline.txt
  • ATAG_NONE: Tag penutup

Informasi ini sangat berguna bagi kernel untuk mengetahui spesifikasi perangkat keras yang sedang berjalan.

Stack Space

Dalam arsitektur ARM, stack tumbuh ke arah alamat yang lebih rendah (grows downward). Secara konvensi, pengembang sering mengatur Stack Pointer (SP) di alamat 0x8000. Hal ini memastikan stack memiliki ruang kosong yang cukup (32KB) di bawah kernel tanpa risiko menimpa kode program.

Pertimbangan Penting:

  • Stack minimal 16KB untuk operasi dasar
  • Nested function calls dan interrupt handlers memerlukan stack lebih besar
  • Overflow stack dapat menimpa vector table dan menyebabkan crash fatal

2. Kernel Space (0x00008000 – Akhir BSS)

Alamat 0x00008000 adalah standar industri untuk memuat kernel pada Raspberry Pi 1.

.text (Code Section)

Berisi instruksi mesin yang dapat dieksekusi. Struktur umum:

  • .text.boot: Kode assembly bootstrap pertama yang dieksekusi
  • .text: Fungsi-fungsi C/C++ hasil kompilasi
  • .rodata: Data read-only seperti string constants

Best Practice:

.section ".text.boot"
.globl _start
_start:
    // Setup stack pointer
    ldr sp, =0x8000
    
    // Clear BSS section
    ldr r0, =__bss_start
    ldr r1, =__bss_end
    mov r2, #0
bss_loop:
    cmp r0, r1
    bge bss_done
    str r2, [r0], #4
    b bss_loop
bss_done:
    
    // Jump to kernel main
    bl kernel_main
halt:
    wfe
    b halt

.data (Data Section)

Menyimpan variabel global atau statis yang sudah memiliki nilai awal. Data ini disalin dari file image kernel ke RAM saat boot.

Contoh:

int initialized_global = 42;        // Masuk ke .data
const char* message = "Hello";      // Pointer di .data, string di .rodata

.bss (Block Started by Symbol)

Wilayah untuk variabel global yang belum diinisialisasi atau bernilai nol. Sesuai standar C, area ini harus dibersihkan (diisi nilai nol) oleh kode boot sebelum fungsi utama (main) dijalankan.

Contoh:

int uninitialized_global;           // Masuk ke .bss
static char buffer[1024];           // Masuk ke .bss

Catatan Penting: File binary kernel tidak menyimpan data .bss (hanya informasi ukuran), sehingga mengurangi ukuran file kernel.img.

3. Free RAM (Heap)

Area yang terletak di atas bagian .bss hingga mencapai batas peripheral (maksimal ~512MB pada RPi 1 Model B+) disebut sebagai Free RAM. Area ini digunakan untuk:

  • Heap allocation: Implementasi malloc() dan free()
  • Dynamic data structures: Linked lists, trees, hash tables
  • User space memory: Jika mengimplementasikan multi-tasking
  • Buffer management: Frame buffers, I/O buffers

Implementasi Sederhana Memory Allocator:

static char heap_space[1024 * 1024];  // 1MB heap
static size_t heap_offset = 0;

void* simple_malloc(size_t size) {
    if (heap_offset + size > sizeof(heap_space)) {
        return NULL;  // Out of memory
    }
    void* ptr = &heap_space[heap_offset];
    heap_offset += size;
    return ptr;
}

4. Peripheral Base Address (0x20000000 ke atas)

Pada SoC BCM2835, kontrol perangkat keras dilakukan melalui Memory Mapped I/O (MMIO).

Peta Peripheral Penting

0x20000000: Peripheral Base
0x20003000: System Timer
0x2000B000: Interrupt Controller
0x20100000: Power Management
0x20200000: GPIO (General Purpose I/O)
0x20201000: UART0 (Serial Communication)
0x20215000: SPI0
0x20300000: External Mass Media Controller (SD Card)
0x20980000: USB Controller

Contoh Akses GPIO

#define GPIO_BASE       0x20200000
#define GPFSEL1         ((volatile unsigned int*)(GPIO_BASE + 0x04))
#define GPSET0          ((volatile unsigned int*)(GPIO_BASE + 0x1C))
#define GPCLR0          ((volatile unsigned int*)(GPIO_BASE + 0x28))

// Set GPIO16 sebagai output (ACT LED pada RPi 1)
void led_init(void) {
    unsigned int selector = *GPFSEL1;
    selector &= ~(7 << 18);  // Clear GPIO16 function
    selector |= (1 << 18);   // Set sebagai output
    *GPFSEL1 = selector;
}

// Nyalakan LED
void led_on(void) {
    *GPCLR0 = (1 << 16);  // Clear GPIO16 (LED aktif low)
}

// Matikan LED
void led_off(void) {
    *GPSET0 = (1 << 16);  // Set GPIO16
}


Linker Script: Menghubungkan Teori dengan Implementasi

Linker script (.ld) adalah file konfigurasi yang memberitahu linker bagaimana mengatur section-section dalam file output.

Contoh Linker Script Lengkap

/* kernel.ld - Linker script untuk Raspberry Pi 1 bare metal */

ENTRY(_start)

SECTIONS
{
    /* Kernel dimulai di 0x8000 */
    . = 0x8000;
    
    __start = .;
    
    /* Boot code harus di paling awal */
    .text.boot : {
        *(.text.boot)
    }
    
    /* Kode program */
    .text : {
        *(.text)
        *(.text.*)
    }
    
    /* Data read-only */
    .rodata : {
        *(.rodata)
        *(.rodata.*)
    }
    
    /* Data dengan nilai awal */
    .data : {
        *(.data)
        *(.data.*)
    }
    
    /* BSS - harus di-zero oleh boot code */
    .bss : {
        __bss_start = .;
        *(.bss)
        *(.bss.*)
        *(COMMON)
        __bss_end = .;
    }
    
    __end = .;
    
    /* Heap dimulai setelah BSS */
    __heap_start = .;
}

Penggunaan Symbol dalam Kode

Symbol yang didefinisikan dalam linker script dapat diakses dari kode C:

extern unsigned int __bss_start;
extern unsigned int __bss_end;
extern unsigned int __heap_start;

void clear_bss(void) {
    unsigned int *bss = &__bss_start;
    unsigned int *bss_end = &__bss_end;
    
    while (bss < bss_end) {
        *bss++ = 0;
    }
}


Cache dan Memory Barriers

Raspberry Pi 1 memiliki instruction cache dan data cache yang dapat mempengaruhi perilaku memory access.

Pertimbangan Cache

  1. Write Buffer: CPU ARM memiliki write buffer yang dapat menunda write operations
  2. Cache Coherency: Penting saat mengakses peripheral registers
  3. Memory Barriers: Diperlukan untuk memastikan urutan operasi

Implementasi Memory Barrier

// Data Memory Barrier
static inline void dmb(void) {
    asm volatile ("mcr p15, 0, %0, c7, c10, 5" : : "r" (0) : "memory");
}

// Data Synchronization Barrier
static inline void dsb(void) {
    asm volatile ("mcr p15, 0, %0, c7, c10, 4" : : "r" (0) : "memory");
}

// Instruction Synchronization Barrier
static inline void isb(void) {
    asm volatile ("mcr p15, 0, %0, c7, c5, 4" : : "r" (0) : "memory");
}

// Contoh penggunaan saat akses MMIO
void mmio_write(uint32_t reg, uint32_t data) {
    dmb();  // Pastikan write sebelumnya selesai
    *(volatile uint32_t*)reg = data;
    dsb();  // Tunggu write selesai
}


Virtual Memory dan MMU

Meskipun pengembangan bare metal sering dimulai tanpa virtual memory, Raspberry Pi 1 memiliki Memory Management Unit (MMU) yang dapat dimanfaatkan.

Keuntungan Mengaktifkan MMU

  1. Memory Protection: Isolasi antar process/task
  2. Cache Control: Enable/disable cache per region
  3. Address Translation: Flexibility dalam memory layout
  4. Access Permissions: Read-only, read-write, execute

Setup Dasar MMU

#define MMU_SECTION         (1 << 1)
#define MMU_CACHEABLE       (1 << 2)
#define MMU_BUFFERABLE      (1 << 3)
#define MMU_AP_FULL         (3 << 10)

// Translation table (harus aligned 16KB)
__attribute__((aligned(16384))) 
static uint32_t page_table[4096];

void setup_mmu(void) {
    // Identity map RAM (0x00000000 - 0x20000000)
    for (int i = 0; i < 512; i++) {
        page_table[i] = (i << 20) | 
                        MMU_SECTION | 
                        MMU_CACHEABLE | 
                        MMU_BUFFERABLE | 
                        MMU_AP_FULL;
    }
    
    // Map peripherals (0x20000000 - 0x21000000) as device memory
    for (int i = 512; i < 528; i++) {
        page_table[i] = (i << 20) | 
                        MMU_SECTION | 
                        MMU_AP_FULL;
    }
    
    // Set translation table base
    asm volatile ("mcr p15, 0, %0, c2, c0, 0" : : "r" (page_table));
    
    // Set domain access (all domains = client)
    asm volatile ("mcr p15, 0, %0, c3, c0, 0" : : "r" (0xFFFFFFFF));
    
    // Enable MMU
    uint32_t control;
    asm volatile ("mrc p15, 0, %0, c1, c0, 0" : "=r" (control));
    control |= 0x1;  // Enable MMU bit
    control |= 0x4;  // Enable data cache
    control |= 0x1000; // Enable instruction cache
    asm volatile ("mcr p15, 0, %0, c1, c0, 0" : : "r" (control));
}


Debugging dan Troubleshooting

Common Memory-Related Issues

  1. Stack Overflow
    • Gejala: Random crashes, corrupted data
    • Solusi: Tingkatkan ukuran stack, kurangi recursive calls
    • Debug: Isi stack dengan pattern (0xDEADBEEF) dan cek boundary
  2. BSS Tidak Ter-clear
    • Gejala: Global variables memiliki nilai random
    • Solusi: Pastikan boot code clear BSS sebelum call main
  3. MMIO Access Tanpa Volatile
    • Gejala: Register writes hilang karena compiler optimization
    • Solusi: Selalu gunakan volatile untuk MMIO pointers
  4. Unaligned Access
    • Gejala: Data abort exception
    • Solusi: Gunakan __attribute__((aligned(4))) atau access byte-by-byte

Tool untuk Debugging

// Simple UART printf untuk debugging
void uart_puts(const char* str) {
    while (*str) {
        // Tunggu UART siap
        while (*(volatile uint32_t*)(0x20201018) & (1 << 5)) {}
        // Tulis karakter
        *(volatile uint32_t*)(0x20201000) = *str++;
    }
}

// Dump memory region
void dump_memory(uint32_t* addr, int count) {
    uart_puts("Memory dump at 0x");
    uart_put_hex((uint32_t)addr);
    uart_puts(":\n");
    
    for (int i = 0; i < count; i++) {
        if (i % 4 == 0) {
            uart_put_hex((uint32_t)(addr + i));
            uart_puts(": ");
        }
        uart_put_hex(addr[i]);
        uart_puts(" ");
        if (i % 4 == 3) uart_puts("\n");
    }
}


Performance Optimization

Cache Optimization

// Align data structures ke cache line (32 bytes pada ARM11)
struct __attribute__((aligned(32))) CacheOptimized {
    uint32_t frequently_accessed[8];
    // ... data lainnya
};

// Prefetch data untuk mengurangi cache miss
static inline void prefetch(const void* addr) {
    asm volatile ("pld [%0]" : : "r" (addr));
}

Memory Access Patterns

  1. Sequential Access: Lebih cepat karena hardware prefetcher
  2. Avoid Striding: Access setiap N-bytes dapat miss cache
  3. Struct Layout: Letakkan data yang sering diakses bersama berdekatan

Kesimpulan dan Best Practices

Checklist Pengembangan Bare Metal

  • ✓ Set stack pointer di boot code (SP = 0x8000)
  • ✓ Clear BSS section sebelum call main
  • ✓ Gunakan linker script yang benar
  • ✓ Tandai MMIO pointers sebagai volatile
  • ✓ Gunakan memory barriers saat akses peripheral
  • ✓ Align data structures sesuai kebutuhan (4-byte untuk word access)
  • ✓ Implementasi exception handlers untuk debugging
  • ✓ Test dengan berbagai ukuran RAM (256MB vs 512MB)

Kesalahan Umum yang Harus Dihindari

  1. Menggunakan stack sebelum menginisialisasi SP
  2. Menulis ke ROM region (peripheral registers yang read-only)
  3. Lupa disable/flush cache saat DMA operations
  4. Mengasumsikan nilai awal variabel global tanpa clear BSS
  5. Mengakses unaligned addresses untuk multi-byte operations

Resource Tambahan

  • BCM2835 ARM Peripherals Guide: Dokumentasi resmi register peripheral
  • ARM1176JZF-S Technical Reference Manual: Dokumentasi CPU
  • GNU Linker Manual: Referensi lengkap linker script syntax

Langkah Selanjutnya

Dengan pemahaman peta memori yang solid, Anda siap untuk:

  1. Implementasi Exception Handlers: Tangani interrupt dan error dengan proper
  2. Driver Development: Tulis driver untuk GPIO, UART, SPI, I2C
  3. Memory Management: Implementasi heap allocator yang efisien
  4. Multi-tasking: Buat simple scheduler dengan context switching
  5. File System: Akses SD card dan implementasi FAT32

Pengembangan OS bare metal adalah perjalanan yang menantang namun sangat memuaskan. Setiap byte memori yang Anda kendalikan membawa Anda lebih dekat ke pemahaman fundamental tentang bagaimana komputer bekerja di level terendah.

Selamat mengembangkan sistem Anda!


Appendix A: Quick Reference

Memory Regions

0x00000000 - 0x00007FFF : Low Memory (Vectors, ATAGS, Stack)
0x00008000 - 0x???????? : Kernel (.text, .data, .bss, heap)
0x20000000 - 0x20FFFFFF : Peripherals (MMIO)

Critical Registers

SP (Stack Pointer)    : 0x8000 (recommended initial value)
PC (Program Counter)  : 0x8000 (kernel entry point)
CPSR (Status Register): 0xD3 (SVC mode, IRQ/FIQ disabled)

Build Commands

# Compile
arm-none-eabi-gcc -mcpu=arm1176jzf-s -fpic -ffreestanding -c kernel.c

# Link
arm-none-eabi-ld -T kernel.ld -o kernel.elf kernel.o

# Convert to binary
arm-none-eabi-objcopy kernel.elf -O binary kernel.img