Apa itu Bootloader?
Bootloader adalah segmen kode pertama yang dieksekusi dalam sistem tertanam setelah dinyalakan. Setelah menyelesaikan inisialisasi CPU dan perangkat keras terkait, bootloader memuat citra sistem operasi atau program aplikasi tertanam ke dalam memori, lalu beralih ke ruang tempat sistem operasi berada, sehingga memulai pengoperasian sistem operasi. Proses ini sangat penting bagi siapa pun yang terlibat dalam pemrograman mikrokontroler tertentu seperti STM32.
Mirip dengan program aplikasi, Bootloader adalah program mandiri yang berisi komponen-komponen penting seperti kode startup, interupsi, program utama (fungsi Boot_main), dan secara opsional, sistem operasi. Meskipun ukurannya kecil, Bootloader mencakup fungsi-fungsi kritis.
Bootloader biasanya sangat bergantung pada perangkat keras dan sangat penting dalam ranah sistem tertanam. Akibatnya, membuat bootloader yang dapat diterapkan secara universal dalam domain tertanam hampir tidak mungkin. Namun demikian, kita masih dapat menggeneralisasi beberapa konsep tentang bootloader untuk memandu pengguna dalam merancang dan mengimplementasikan bootloader tertentu.
Mode Operasi Bootloader
Sebagian besar bootloader memiliki dua mode operasi yang berbeda: mode boot dan mode unduh.
Saat dinyalakan, bootloader menginisialisasi lingkungan perangkat lunak dan perangkat keras sistem serta memilih salah satu mode operasi berdasarkan kondisi perangkat keras saat ini. Hal ini mencakup konfigurasi mode operasi CPU, inisialisasi memori, menonaktifkan interupsi, dan menangani tugas-tugas seperti mematikan MMU/Cache.
Mode Pemuatan Boot:
Mode ini, yang juga dikenal sebagai mode "otomatis", melibatkan Boot Loader yang secara otomatis memuat sistem operasi dari perangkat penyimpanan solid-state pada mesin tujuan ke dalam RAM. Seluruh proses ini berlangsung tanpa campur tangan pengguna. Mode ini merupakan cara kerja normal dari Boot Loader.
Mode Unduh:
Dalam mode ini, Boot Loader pada mesin target memulai komunikasi melalui koneksi serial atau jaringan dengan mesin host untuk mengunduh berkas. Berkas yang diperoleh dari mesin host biasanya disimpan dalam RAM mesin target oleh Boot Loader sebelum ditulis ke memori Flash atau perangkat penyimpanan solid-state serupa pada mesin target.
Bagaimana Cara Kerja Bootloader?
Ada dua jenis proses booting untuk Bootloader: Satu Tahap dan Multi-Tahap. Secara umum, Bootloader multi-tahap memiliki fungsi yang lebih kompleks dan portabilitas yang lebih baik. Bootloader yang dimulai dari perangkat penyimpanan solid-state sering kali menggunakan proses dua tahap, yang dibagi menjadi tahap 1 dan tahap 2: tahap 1 melakukan inisialisasi perangkat keras, menyiapkan ruang memori untuk tahap 2, menyalin tahap 2 ke memori, menyiapkan stack, dan kemudian beralih ke tahap 2.
Tahap 1 Bootloader
Inisialisasi Perangkat Keras
- Nonaktifkan Semua Interupsi: Penanganan interupsi biasanya menjadi tanggung jawab driver perangkat sistem operasi, sehingga Boot Loader dapat mengabaikan respons interupsi selama proses eksekusinya. Penutupan interupsi dapat dilakukan dengan memodifikasi register masker interupsi CPU atau register status (seperti register CPSR pada ARM).
- Atur Kecepatan CPU dan Frekuensi Jam.
- Inisialisasi RAM: Ini mencakup konfigurasi yang benar terhadap register fungsi pengontrol memori sistem dan berbagai register kontrol bank memori.
- Inisialisasi LED: LED sering kali digerakkan melalui GPIO untuk menunjukkan status sistem (OK atau Error). Jika tidak ada LED, inisialisasi UART untuk mencetak logo Boot Loader atau informasi karakter melalui komunikasi serial dapat digunakan untuk tujuan ini.
- Nonaktifkan Cache Instruksi/Data Internal CPU.
Siapkan Ruang RAM untuk Memuat Bootloader Tahap 2
Agar eksekusi lebih cepat, tahap 2 biasanya dimuat ke dalam RAM. Oleh karena itu, rentang memori yang tersedia harus dialokasikan untuk memuat tahap 2 Boot Loader.
Karena tahap 2 biasanya berisi kode bahasa C, ruang yang dibutuhkan harus mempertimbangkan ukuran eksekusi tahap 2 dan ruang tumpukan. Selain itu, ruang tersebut sebaiknya selaras dengan ukuran halaman memori (biasanya 4KB). Umumnya, ruang RAM sebesar 1MB sudah cukup. Rentang alamat spesifik dapat dipilih secara sewenang-wenang. Misalnya, pendekatan umum adalah mengalokasikan gambar eksekusi tahap 2 untuk dieksekusi dalam ruang 1MB yang dimulai dari alamat dasar RAM sistem 0xc0200000. Namun, mengalokasikan tahap 2 ke 1MB teratas dari seluruh ruang RAM (yaitu, (RamEnd-1MB) – RamEnd) merupakan strategi yang direkomendasikan.
Mari kita sebut ukuran rentang ruang RAM yang dialokasikan sebagai "stage2_size" (dalam byte), dan alamat awal serta akhir sebagai "stage2_start" dan "stage2_end" (kedua alamat tersebut disejajarkan dengan batas 4-byte). Dengan demikian:
stage2_end = stage2_start + stage2_size
Selain itu, sangat penting untuk memastikan bahwa rentang alamat yang dialokasikan memang merupakan ruang RAM yang dapat ditulis dan dibaca. Untuk memastikan hal ini, pengujian terhadap rentang alamat yang dialokasikan diperlukan. Metode pengujian yang sesuai, seperti yang digunakan oleh "blob," melibatkan pengujian dua kata pertama setiap halaman memori untuk kemampuan baca-tulis.
Salin Tahap 2 Boot Loader ke ruang RAM
Untuk melakukannya, pastikan dua hal berikut:
- Lokasi gambar eksekusi tahap 2 pada perangkat penyimpanan solid-state.
- Alamat awal ruang RAM.
Atur Penunjuk Tumpukan (SP)
Penetapan penunjuk tumpukan (sp) merupakan persiapan untuk menjalankan kode bahasa C. Biasanya, nilai sp dapat ditetapkan ke (stage2_end-4), yang mewakili ujung paling atas dari ruang RAM 1 MB yang dialokasikan pada bagian 3.1.2 (tumpukan tumbuh ke bawah).
Selain itu, sebelum mengatur penunjuk tumpukan, dimungkinkan untuk mematikan LED sebagai sinyal kepada pengguna bahwa transisi ke tahap 2 akan segera terjadi.
Setelah langkah-langkah eksekusi ini, tata letak memori fisik sistem seharusnya menyerupai diagram di bawah ini.

Lompat ke titik masuk C pada tahap 2
Setelah semua hal di atas siap, Anda dapat beralih ke tahap 2 Boot Loader untuk menjalankannya. Misalnya, pada sistem ARM, hal ini dapat dilakukan dengan mengubah nilai register PC ke alamat yang sesuai. Tata letak memori sistem saat gambar eksekusi tahap 2 dari bootloader baru saja disalin ke ruang RAM ditunjukkan pada gambar di atas.
Tahap 2 Bootloader
Inisialisasi Perangkat Keras
- Inisialisasi setidaknya satu port serial untuk komunikasi output I/O dengan pengguna terminal.
- Inisialisasi timer dan komponen perangkat keras lainnya.
Sebelum menginisialisasi perangkat-perangkat ini, LED juga dapat dinyalakan untuk menandakan dimulainya eksekusi fungsi main(). Setelah inisialisasi perangkat, informasi tertentu seperti string nama program dan nomor versi dapat ditampilkan.
Deteksi Pemetaan Memori Sistem
Pemetaan memori mengacu pada alokasi rentang alamat di dalam ruang alamat fisik sebesar 4 GB untuk mengalamati unit RAM sistem. Misalnya, pada CPU SA-1100, ruang alamat sebesar 512 MB yang dimulai dari 0xC000,0000 berfungsi sebagai ruang alamat RAM sistem. Pada CPU Samsung S3C44B0X, ruang alamat 64MB antara 0x0c00,0000 dan 0x1000,0000 digunakan untuk ruang alamat RAM sistem. Meskipun CPU biasanya menyisihkan sebagian besar ruang alamat untuk RAM sistem, tidak semua ruang alamat RAM yang disisihkan tersebut mungkin digunakan saat membangun sistem tertanam tertentu. Oleh karena itu, sistem tertanam seringkali hanya memetakan sebagian dari ruang alamat RAM yang disisihkan CPU ke unit RAM, sehingga sebagian ruang alamat RAM yang disisihkan tetap tidak terpakai. Mengingat fakta ini, tahap 2 Boot Loader harus memeriksa seluruh pemetaan memori sistem sebelum melakukan tindakan apa pun (seperti membaca gambar kernel yang disimpan dalam flash ke ruang RAM). Boot Loader perlu mengetahui bagian mana dari ruang alamat RAM yang dicadangkan CPU yang benar-benar dipetakan ke unit alamat RAM dan bagian mana yang berada dalam status "tidak terpakai".
Penjelasan tentang Pemetaan Memori
Struktur data berikut ini dapat digunakan untuk menggambarkan rentang alamat kontinu dalam ruang alamat RAM:
typedef struct memory_area_struct {
u32 start; /* the base address of the memory region */
u32 size; /* the byte number of the memory region */
int used;
} memory_area_t;
Rentang alamat yang berurutan dalam ruang alamat RAM dapat berada dalam salah satu dari dua keadaan berikut:
- used=1 menunjukkan bahwa rentang alamat berurutan telah diimplementasikan dan benar-benar dipetakan ke unit RAM.
- used=0 menunjukkan bahwa rentang alamat berurutan tersebut belum diimplementasikan dalam sistem dan tetap tidak terpakai.
Berdasarkan struktur data memory_area_t yang dijelaskan di atas, seluruh ruang alamat RAM yang dicadangkan CPU dapat direpresentasikan oleh sebuah array bertipe memory_area_t, seperti yang ditunjukkan di bawah ini:
memory_area_t memory_map[NUM_MEM_AREAS] = {
[0 ... (NUM_MEM_AREAS - 1)] = {
.start = 0,
.size = 0,
.used = 0
},
};
Deteksi Pemetaan Memori
Berikut ini adalah algoritma sederhana namun efektif untuk mendeteksi kondisi pemetaan memori di seluruh ruang alamat RAM:
/* Initialize the array */
for(i = 0; i < NUM_MEM_AREAS; i++)
memory_map[i].used = 0;
/* Write 0 to all memory locations */
for(addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE)
*(u32 *)addr = 0;
for(i = 0, addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE) {
/*
* Check whether the address space starting from base address
* MEM_START + i * PAGE_SIZE, with a size of PAGE_SIZE, is a valid RAM address space.
*/
Call the algorithm test_mempage() from section 3.1.2;
if (current memory page is not a valid RAM page) {
/* no RAM here */
if (memory_map[i].used )
i++;
continue;
}
/*
* The current page is a valid address range mapped to RAM.
* However, we need to determine if it's an alias of some address page within the 4GB address space.
*/
if (*(u32 *)addr != 0) { /* alias? */
/* This memory page is an alias of an address page within the 4GB address space. */
if (memory_map[i].used )
i++;
continue;
}
/*
* The current page is a valid address range mapped to RAM,
* and it's not an alias of an address page within the 4GB address space.
*/
if (memory_map[i].used == 0) {
memory_map[i].start = addr;
memory_map[i].size = PAGE_SIZE;
memory_map[i].used = 1;
} else {
memory_map[i].size += PAGE_SIZE;
}
} /* end of for (…) */
Setelah menjalankan algoritma di atas untuk mendeteksi status pemetaan memori sistem, Boot Loader juga dapat menampilkan informasi pemetaan memori secara terperinci ke port serial.
Memuat Gambar Kernel dan Sistem Berkas Root
Perencanaan Tata Letak Memori
Hal ini mencakup dua aspek:
- rentang memori yang ditempati oleh gambar kernel;
- rentang memori yang ditempati oleh sistem file root. Saat merencanakan tata letak memori, pertimbangkan alamat dasar dan ukuran gambar.
Untuk citra kernel, biasanya disalin ke rentang memori yang dimulai dari (MEM_START + 0x8000), dengan ukuran sekitar 1MB (kernel Linux tertanam biasanya berukuran kurang dari 1MB). Mengapa menyisakan ruang 32KB dari MEM_START hingga MEM_START + 0x8000? Hal ini karena kernel Linux menempatkan struktur data global tertentu di segmen memori ini, seperti parameter boot dan tabel halaman kernel.
Untuk gambar sistem file root, umumnya disalin ke lokasi yang dimulai dari MEM_START + 0x0010,0000. Jika menggunakan Ramdisk sebagai gambar sistem file root, ukuran tanpa kompresi biasanya sekitar 1MB.
Menyalin dari Flash
Karena CPU tertanam seperti ARM biasanya mengakses perangkat penyimpanan Flash dan perangkat penyimpanan solid-state lainnya dalam ruang alamat memori terpadu, membaca data dari Flash mirip dengan membaca dari unit RAM. Cukup menggunakan loop sederhana untuk menyalin gambar dari perangkat Flash:
while (count) {
*dest++ = *src++; /* they are all aligned with word boundary */
count -= 4; /* byte number */
};
Menyetel Parameter Boot Kernel
Setelah menyalin citra kernel dan citra sistem file root ke ruang RAM, proses booting kernel Linux dapat dipersiapkan. Namun, sebelum menjalankan kernel, ada langkah persiapan yang perlu dilakukan: mengatur parameter boot kernel Linux.
Kernel Linux mulai versi 2.4.x ke atas mengharapkan parameter boot diserahkan dalam bentuk daftar bertanda. Daftar bertanda parameter boot dimulai dengan tag ATAG_CORE dan diakhiri dengan tag ATAG_NONE. Setiap tag terdiri dari struktur tag_header yang mengidentifikasi parameter, diikuti oleh struktur data yang berisi nilai parameter. Struktur data tag dan tag_header didefinisikan dalam file header include/asm/setup.h dari kode sumber kernel Linux.
Pada sistem Linux tertanam, parameter boot umum yang perlu diatur oleh Boot Loader meliputi ATAG_CORE, ATAG_MEM, ATAG_CMDLINE, ATAG_RAMDISK, dan ATAG_INITRD.
Sebagai contoh, berikut adalah cara mengatur ATAG_CORE:
params = (struct tag *)BOOT_PARAMS;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size(tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next(params);
Di sini, BOOT_PARAMS mewakili alamat dasar awal parameter boot kernel dalam memori, dan penunjuk params bertipe struct tag. Makro tag_next() menghitung alamat awal tag berikutnya yang langsung berada setelah tag saat ini. Perlu diperhatikan bahwa ID perangkat untuk sistem berkas akar kernel ditetapkan di sini.
Berikut adalah contoh kode untuk mengatur informasi pemetaan memori:
for (i = 0; i < NUM_MEM_AREAS; i++) {
if (memory_map[i].used) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size(tag_mem32);
params->u.mem.start = memory_map[i].start;
params->u.mem.size = memory_map[i].size;
params = tag_next(params);
}
}
Dalam larik memory_map[], setiap segmen memori yang valid sesuai dengan tag parameter ATAG_MEM.
Kernel Linux dapat menerima informasi sebagai parameter baris perintah selama proses booting. Hal ini memungkinkan kita untuk menyediakan informasi parameter perangkat keras yang tidak dapat dideteksi oleh kernel sendiri atau mengganti informasi yang telah dideteksi oleh kernel. Misalnya, kita menggunakan string parameter baris perintah "console=ttyS0,115200n8" untuk menginstruksikan kernel agar menggunakan ttyS0 sebagai konsol dengan pengaturan "115200 bps, tanpa paritas, 8 bit data." Berikut adalah contoh kode untuk mengatur string parameter baris perintah kernel:
char *p;
for (p = commandline; *p == ' '; p++)
;
if (*p == '\0')
return;
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size = (sizeof(struct tag_header) + strlen(p) + 1 + 4) >> 2;
strcpy(params->u.cmdline.cmdline, p);
params = tag_next(params);
Perhatikan bahwa dalam kode di atas, saat menentukan ukuran tag_header, ukuran tersebut harus mencakup karakter penutup '\0' dalam string dan dibulatkan ke atas ke kelipatan 4 byte terdekat, karena anggota `size` dari struktur tag_header mewakili jumlah kata.
Berikut adalah contoh kode untuk mengatur ATAG_INITRD, yang menunjukkan lokasi di RAM tempat gambar initrd (dalam format terkompresi) dapat ditemukan, beserta ukurannya:
params->hdr.tag = ATAG_INITRD2;
params->hdr.size = tag_size(tag_initrd);
params->u.initrd.start = RAMDISK_RAM_BASE;
params->u.initrd.size = INITRD_LEN;
params = tag_next(params);
Terakhir, tetapkan tag ATAG_NONE untuk menutup seluruh daftar parameter startup:
static void setup_end_tag(void) {
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
Memanggil Kernel
Boot Loader memanggil kernel Linux dengan melompat langsung ke instruksi pertama kernel, yaitu melompat langsung ke alamat MEM_START + 0x8000. Kondisi-kondisi berikut harus dipenuhi saat melakukan lompatan:
- Pengaturan register CPU:
R0 = 0
R1 = ID tipe mesin (untuk Nomor Tipe Mesin, lihat linux/arch/arm/tools/mach-types)
R2 = Alamat dasar awal dari daftar parameter boot yang diberi tag di RAM - Mode CPU:
Nonaktifkan interupsi (IRQ dan FIQ)
CPU harus berada dalam mode SVC - Pengaturan cache dan MMU:
MMU harus dimatikan Cache
instruksi dapat diaktifkan atau dinonaktifkan Cache
data harus dimatikan
Jika menggunakan C, memanggil kernel dapat dilakukan seperti ini:
void (*theKernel)(int zero, int arch, u32 params_addr) =
(void (*)(int, int, u32))KERNEL_RAM_BASE;
theKernel(0, ARCH_NUMBER, (u32)kernel_params_start);
Perlu diperhatikan bahwa panggilan fungsi ke `theKernel()` tidak boleh mengembalikan nilai apa pun. Jika fungsi tersebut mengembalikan nilai, berarti telah terjadi kesalahan.
Bootloader Sistem Tertanam vs Bootloader PC
Dalam arsitektur PC, bootloader terdiri dari BIOS (pada dasarnya merupakan firmware) dan bootloader sistem operasi yang terletak di MBR hard disk (misalnya, LILO, GRUB). Setelah BIOS menyelesaikan deteksi perangkat keras dan alokasi sumber daya, BIOS memuat bootloader dari MBR hard disk ke RAM sistem dan menyerahkan kendali kepada bootloader OS. Tugas utama bootloader adalah membaca citra kernel dari hard disk ke RAM dan kemudian melompat ke titik masuk kernel untuk memulai sistem operasi.
Pada sistem tertanam, biasanya tidak ada program firmware seperti BIOS (meskipun beberapa CPU tertanam mungkin menyertakan program boot tertanam kecil). Akibatnya, seluruh tugas pemuatan dan pengaktifan sistem dilakukan oleh bootloader. Misalnya, pada sistem tertanam berbasis inti ARM7TDMI, sistem biasanya memulai eksekusi pada alamat 0x00000000 saat dinyalakan atau direset, dan alamat ini biasanya berisi program bootloader sistem.
CPU dan papan tertanam yang didukung oleh Boot Loader
Setiap arsitektur CPU yang berbeda memiliki Boot Loader yang berbeda pula. Beberapa Boot Loader juga mendukung CPU dengan berbagai arsitektur. Misalnya, U-Boot mendukung baik arsitektur ARM maupun arsitektur MIPS. Selain bergantung pada arsitektur CPU, Boot Loader sebenarnya juga bergantung pada konfigurasi perangkat papan tertanam tertentu. Dengan kata lain, Boot Loader belum tentu cocok untuk kedua papan tertanam yang berbeda tersebut, meskipun keduanya dibangun berdasarkan CPU yang sama.
Media Instalasi Bootloader
Saat sistem dinyalakan atau direset, CPU biasanya mengambil instruksi dari alamat yang telah ditentukan sebelumnya oleh produsen CPU. Misalnya, CPU yang berbasis inti ARM7TDMI biasanya mengambil instruksi pertamanya dari alamat 0x00000000 setelah direset. Sistem tertanam yang dibangun berdasarkan arsitektur CPU sering kali memetakan suatu jenis perangkat penyimpanan solid-state (seperti ROM, EPROM, atau FLASH) ke alamat yang telah ditentukan ini. Oleh karena itu, setelah sistem dinyalakan, CPU pertama-tama menjalankan program bootloader.




