Porting Linux 2.6 ke Platform ARM Baru (bagian 2)

Bagian 1 Bagian 2 Bagian 3

Pada bagian pertama saya lebih banyak bercerita mengenai latar belakang dan kebutuhan dasar porting. Pada bagian ini saya akan menuliskan proses porting itu sendiri. Ada banyak bagian dalam kernel Linux dan kernel Linux sendiri terdiri dari jutaan baris kode, sehingga saya tidak akan menjelaskan secara detail setiap bagian.

Anda dapat melihat sendiri perubahan yang saya lakukan dengan mendownload file patch dari http://tinyhack.com. Karena saya mungkin masih akan memposting patch baru (untuk bug fix atau optimasi dan untuk mendukung kernel terbaru), saya tidak memberi URL sebuah file spesifik. Anda dapat melihat yang terbaru dari kategori agestar.

Hello world

Sebelum mulai mengkompilasi kernel, sebaiknya Anda mencoba dulu membuat program "Hello World". Program ini bukan program standar seperti ketika Anda belajar programming. Program hello world yang saya maksud harus dapat mengoutputkan sesuatu secara langsung ke port serial (atau ke layar jika device Anda punya layar). Kegunaan program ini adalah agar Anda dapat mengerti cara menghasilkan output sebelum fungsi printk kernel bisa digunakan.

Dalam arsitektur ARM, tidak ada instruksi untuk mengakses port seperti di Intel x86. Di Intel untuk mengakses port kita menggunakan instruksi khusus seperti inb, outb, outw, dan sebagainya. Di ARM semua IO dipetakan ke alamat memori tertentu. Hal ini sangat memudahkan dalam menulis output ke serial port.

Anda cukup mengetahui alamat mana output untuk serial port, berikutnya Anda perlu menulis ke alamat tersebut. Kita mengasumsikan bahwa serial port bisa sangat cepat, namun kenyataannya kecepatan serial port terbatas, jadi kita perlu menulis perlahan-lahan. Tapi menulis perlahan tidak menjamin bahwa data akan tertulis dengan benar. Cara yang benar adalah setelah menulis 1 karakter, Anda harus yakin bahwa transmit buffer serial port sudah kosong. Hal ini biasanya dilakukan dengan membaca alamat memori tertentu berulang-ulang sampai sebuah bit menyatakan bahwa kita bisa menulis 1 karakter lagi.

Berikut ini adalah kode sebuah string dalam versi C (untuk menulis sebuah karakter) pada STR9100.

void tmyputs(const char *s)
{
    int i =0;
    for (i=0; s[i]; i++) {
        __raw_writel(s[i], 0xf7800000);
        /*wait until transmit buffer is empty*/
           while (__raw_readl(0xf7800000+0x14) & (1<<5));
    }
}

Menambah Platform ARM Baru pada kernel Linux

Hal pertama yang perlu dilakukan adalah membuat arsitektur baru Anda bisa dipilih menggunakan make menuconfig. Hal ini dilakukan dengan menambahkan di file arch/arm/Kconfig

 config ARCH_STR9100
 bool "Star 9100"
 help
 This enables support for Star 9100

Jika prosessor Anda belum didukung, Anda perlu menulis kode spesifik prosessor di arch/arm/boot/head.S. Kemungkinan besar CPU Anda sudah didukung, jadi Anda tidak perlu khawatir. Dalam kasus yang saya temui, prosessor belum didukung, tapi sudah ada source code lain yang bisa saya pakai.

Untuk menampung kode-kode spesifik platform, Anda perlu membuat direktori arch/arm/NAMA, dengan NAMA adalah nama platform baru Anda. File header (file .h), perlu Anda letakkan di include/asm-arch/arch-NAMA. Pada direktori platform, Anda perlu membuat Makefile yang berisi instruksi file apa saja yang perlu dikompilasi, dan entry-macro.S yang berisi macro-macro spesifik untuk platform Anda.

Anda perlu menambah nomor mesin di arch/arm/tools/mach-type, nomor mesin ini bisa Anda pilih sendiri ketika development (asalkan tidak sama dengan yang sudah ada). Setelah selesai, Anda bisa mendaftarkan nomor arsitektur baru di http://www.arm-linux.org.

Kompilasi dan booting kernel

Kompilasi kernel dilakukan di PC dengan menggunakan cross compiler. Anda perlu mengeset variabel CROSS_COMPILER di Makefile dengan prefix cross compiler Anda. Contohnya jika cross compiler Anda ada di /opt/arm/bin/ dan nama binary diawali dengan arm-linux- (misalnya arm-linux-gcc, arm-linux-ld, dsb), maka Anda perlu mengeset cross compiler menjadi:

 CROSS_COMPILER=/opt/arm/bin/arm-linux-

Anda bisa memilih opsi kernel standar, tapi sebaiknya di awal development Anda meminimasi opsi kernel yang Anda pilih, ini tujuannya untuk mempercepat kompilasi, sekaligus mengisolasi bug yang mungkin ada di opsi yang Anda pilih. Nonaktifkan juga fitur module loading, ini akan membuat semua opsi dikompilasi ke dalam kernel. Aktifkan aneka opsi debugging pada bagian Kernel Hacking, karena ini akan sangat membantu menemukan kesalahan dalam proses porting kernel.

Setelah melakukan “make”, anda akan mendapatkan file zImage (arch/arm/boot/zImage untuk ARM). Sekarang Anda perlu menyalin image tersebut ke target. Jika bootloader mendukung TFTP (Trivial File Transfer Protocol), maka menggunakan TFTP biasanya yang paling cepat. Jika tidak, mungkin Anda perlu menggunakan serial port atau device lain.

Dalam kasus device yang saya miliki, device tersebut menggunakan boot loader ARMboot dan mendukung TFTP, jadi yang perlu saya lakukan adalah menginstall server TFTP di komputer yang saya miliki, lalu meload image di server ke device dengan perintah:

 tftpboot zImage 0x100000

Setelah image masuk ke memori, saya dapat memulainya dengan perintah:

 go 0x1000000

Memory Management Unit

Memory management unit (MMU) adalah unit dalam prosessor yang memungkinkan sebuah memori dipetakan ke alamat lain. Ada hardware yang tidak memiliki MMU (non MMU), device semacam itu akan memiliki kekurangan dalam manajemen memori, misalnya tidak bisa melakukan swap, tidak mendukung shared library, dll. Sebagian besar prosessor menggunakan MMU, jika tidak, Anda sebaiknya mengunjungi proyek uCLinux yang mengkhususkan diri pada Linux non MMU.

Dengan MMU, jika Anda mengakses memori alamat X, maka secara fisik yang diakses belum tentu alamat X, tapi tergantung pada pemetaan yang saat ini sedang berlaku. Untuk menambah arsitektur baru, Anda perlu membuat peta memori, peta ini digunakan Linux untuk memetakan alamat fisik ke alamat virtual. Di sini Anda memetakan alamat fisik device ke alamat logic yang akan dipakai oleh kernel.

Peta dibuat menggunakan array yang didaftarkan dengan io_table_init. Setiap elemen array memiliki tipe io_desc, yang isinya adalah alamat fisik, alamat virtual, ukuran, dan flag yang menyatakan attribut untuk pemetaan tersebut. Untuk lebih jelasnya Anda bisa melihat pada file mm.c.

Interupt dan Timer

Ketika booting kernel, Anda akan melihat baris-baris semacam ini:

 Memory: 29220KB available (2872K code, 209K data, 96K init)

 Calibrating delay loop... 153.19 BogoMIPS (lpj=765952)

Baris tersebut kelihatannya sederhana, namun banyak hal yang terjadi dalam baris tersebut. Pertama kemunculan baris tersebut dimungkinkan karena: console atau early console sudah berhasil aktif. Kedua: timer sudah berjalan. Tanpa timer, Linux tidak bisa mengkalibrasi delay loop. Ketiga: interrupt sudah berjalan, karena timer dijalankan dengan menggunakan interrupt.

Berdasarkan urutan tersebut, Anda perlu menginisialisasi interrupt controller. Interrupt controller adalah bagian yang menangani aksi ketika terjadi interrupt hardware, misalnya interrupt akan dibangkitkan ketika data telah dikirim atau data diterima. Ada beberapa jenis interrupt, tapi yang paling umum adalah level triggered dan edge triggered. Level triggered artinya status interrupt akan terus “on” sampai dimatikan, sedangkan pada edge triggered status interrupt akan berubah sesaat dan akan kembali lagi normal.

Inisialisasi interrupt controller biasanya dilakukan dengan masking semua interrupt (menonaktifkan semua interrupt). Setelah itu handler interrupt perlu diset (jika terjadi interrupt no X apakah yang dipanggil handle_level_irq atau handle_edge_irq) dan dinyatakan valid. Selain menginisialisasi controller, Anda perlu menyediakan tiga fungsi yang berhubungan dengan irq yaitu mask, unmask dan acknowledge dan memasukkannya dalam struktur irq_chip. Setelah itu kernel Linux yang akan mengatur semua hal yang berhubungan dengan interrupt. Untuk lebih jelasnya Anda bisa melihat file irq.c.

Tanpa timer, Linux tidak akan bisa menghitung kalibrasi loop, jadi bagian penting lain yang perlu dibuat adalah bagian timer. Ada dua fungsi yang perlu Anda tulis, fungsi pertama adalah untuk menginisialisasi timer, dan fungsi kedua adalah fungsi yang mengembalikan jumlah detik sejak sistem menyala. Anda bisa melihat pada timer.c bahwa kode untuk bagian ini cukup sederhana.

Membuat driver early console

Sebelum console yang sebenarnya diaktifkan, kita sebaiknya membuat driver early console. Driver ini sangat sederhana, dan hanya butuh kurang dari seratus baris kode. Driver ini nantinya tidak dipakai, tapi berguna di bagian awal development. Jika kode ini berjalan dengan lancar, Anda akan bisa melihat output kernel. Setelah melewati tahap tertentu, kendali diserahkan kepada console serial yang sebenarnya.

Kode untuk early console sangat sederhana, Anda hanya perlu membuat fungsi untuk menuliskan sebuah string ke serial port atau ke layar. Kode ini biasanya ditulis dalam C (tidak dalam assembly), dan Anda harus memperhatikan bahwa ketika driver ini diaktifkan, pemetaan memori sudah berlaku, jadi Anda harus mengakses I/O pada alamat virtual bukan alamat fisik. Contoh early console bisa Anda lihat pada 8250_early.c.

Membuat driver console

Berikutnya yang perlu dibuat adalah driver console. Driver console memungkinkan kita melihat keseluruhan output kernel untuk mengetahui jika ada masalah (dan kemungkinan pasti ada masalah). Driver console juga memungkinkan Anda berinteraksi dengan aplikasi. Jika Anda beruntung, console Anda sudah didukung oleh Linux. Serial port yang ada kebanyakan kompatibel dengan 16550A (standar de fakto), dan Anda hanya perlu memberi tahu alamat register untuk serial agar dapat dipakai oleh driver 8250.c.

Network Driver

Network driver merupakan bagian penting, karena jika Anda bisa menyelesaikan bagian ini Anda bisa memulai development dengan mudah. Setelah network driver selesai, Anda bisa mengaktifkan client NFS di kernel, sehingga Anda bisa memount filesystem di komputer Anda via protokol NFS. Dari titik tersebut, Anda bisa dengan mudah membuat modul kernel untuk driver yang lain, dan Anda tinggal meload lalu mengunload module tersebut tanpa perlu repot mengkompilasi ulang keseluruhan kernel dan mengirimkan kernel itu ke device.

Untuk bisa membuat network driver, mungkin Anda harus mengerjakan bagian lain. Mungkin Anda perlu mengerjakan dulu bagian PCI (jika network interface card ada di bus PCI) atau USB (jika network ada di bus USB). Pada beberapa SoC yang saya lihat, Network MAC controller berada di SoC itu sendiri (tidak berada pada bus tertentu), dan itulah kasus yang saya temui pada STAR 9100.

Bagian network driver agak kompleks. Linux banyak dipakai sebagai server Internet, sehingga kinerja subsistem jaringan berusaha dioptimalkan sebisa mungkin. Jika hardware mendukung, kita bisa menulis Network driver yang mendukung NAPI (new API) untuk mengoptimalkan penerimaan paket, dan mendukung paket fragmentation untuk mengoptimalkan pengiriman paket (terutama dengan system call sendfile).

Fungsi yang paling penting dalam network driver adalah hard_start_xmit untuk memulai proses pengiriman data, dan fungsi poll untuk menerima data. Secara sederhana proses pengiriman paket adalah sebagai berikut:

  1. Cek apakah buffer pengiriman sudah penuh, jika ya, hentikan antrian pengiriman (netif_stop_queue)

  2. Masukkan paket ke buffer pengiriman

Anda perlu interrupt handler yang menyatakan paket sudah terkirim. Dalam interrupt handler tersebut Anda menjalankan kembali antrian pengiriman netif_start_queue. Beberapa hardware memiliki kecepatan pengiriman terbatas, dan memiliki buffer yang besar, sehingga kita bisa yakin bahwa buffer tidak mungkin penuh. Contohnya jika hardware hanya bisa mengirim 10 paket per detik, dan prosessor tidak bisa mengirim lebih dari 5 per detik, maka pengecekan apakah buffer sudah penuh mungkin tidak perlu dilakukan. Pengecekan sebaiknya tetap dilakukan karena mungkin saja versi prosessor berikutnya bisa mengirimkan data lebih banyak lagi.

Membuat Driver controller USB

Anda beruntung jika device yang Anda miliki mempunyai USB controller, karena Anda dengan mudah bisa menemukan banyak sekali hardware yang bisa dihubungkan dengan USB (misalnya network adapter, usb disk, LCD, dll). Saat ini ada 2 standar USB, USB 1.1 (untuk device low dan full speed) dan USB 2.0 (untuk device high speed). Ada beberapa standar host controller, tapi yang paling banyak dipakai adalah EHCI (untuk USB 2.0) dan OHCI (untuk USB 1.1).

Perlu dicatat bahwa kedua buah driver diperlukan jika hardware Anda mendukung USB 1.1 dan USB 2.0 meskipun hanya memiliki satu port. Secara otomatis kernel akan memilih driver mana yang diaktifkan. Jika Anda hanya menuliskan salah satu driver saja, misalnya hanya USB 2.0, maka device USB 1.1 tidak bisa digunakan, dan demikian sebaliknya.

USB host driver di Linux sudah sangat baik, jika device Anda tidak menyimpang dari standar, Anda hanya perlu menyatakan cara mengakses register USB, dan driver ehc-hcd atau ohci-hcd akan mengatur sisanya. Sayangnya kadang-kadang ada saja device yang menyimpang dari standar. Dalam kasus saya, penyimpangan hanya pada proses inisialisasi saja. Jika Anda memiliki hardware yang menyimpang dalam hal lain (misalnya harus mengirimkan perintah tertentu setiap kali mengirimkan data), maka Anda perlu mengimplementasikan hal tersebut.

Membuat Driver Lain

Device yang saya miliki dilengkapi dengan PCI bus, tapi bus ini tidak terhubung dengan device apapun, jadi saya memutuskan untuk tidak membuat driver untuk bus PCI. Driver PCI ini sedikit lebih sulit dibanding USB karena ada beberapa versi, Banyak hardware yang sudah didukung Linux sehingga Anda tidak perlu menulis drivernya. Jika Anda punya hardware yang belum didukung, Anda bisa berkonsultasi pada buku Linux Device Driver. Buku tersebut memberi contoh untuk hampir setiap device driver yang ada pada Linux.

Di bagian berikutnya

Setelah sisi kernel selesai, di bagian berikutnya saya akan menjelaskan mengenai reverse engineering dan mengenai user space. Saya akan akan membahas beberapa aspek sisi user, seperti penggunaan library C (libc vs uClibC) dan juga mengenai pembuatan root filesystem menggunakan buildroot dan bagaimana menginstall Debian tanpa installer ke sebuah platform baru.

blog comments powered by Disqus

Copyright © 2009-2018 Yohanes Nugroho