Porting Linux lên board tùy biến¶
Trang này trình bày quy trình đưa Linux lên một board tự thiết kế (custom board) dùng một SoC đã được Linux hỗ trợ. Nội dung đi theo thứ tự boot: xác định phạm vi công việc, chọn điểm xuất phát, sau đó lần lượt khởi tạo tối thiểu, bootloader, kernel và Device Tree, root filesystem, ngoại vi, và cuối cùng là đưa cấu hình vào build system. Ví dụ xuyên suốt là một board dùng Zynq-7000, lấy Zedboard làm reference board.
Phần riêng của Zynq (chuỗi boot, BOOT.BIN, bootgen) nằm ở trang Porting Linux cho Zynq. Trang này chỉ dùng Zynq để minh họa các bước chung.
Phạm vi của porting¶
Porting có hai mức. Port SoC là thêm hỗ trợ cho một SoC hoặc kiến trúc mới vào kernel và bootloader, việc này thường do hãng chip làm. Port board là đưa Linux lên một board mới dùng SoC đã có hỗ trợ. Trang này chỉ nói về port board, là công việc thường gặp của kỹ sư hệ nhúng.
Với port board, phần lớn thay đổi là cấu hình và mô tả phần cứng, không phải viết code mới:
| Thành phần | Việc cần làm | Khi nào phải sửa code |
|---|---|---|
| Stage 1 bootloader (FSBL, SPL) | Cấu hình clock, DDR, pin mux theo board | Hiếm khi; thường sinh tự động từ công cụ của hãng |
| U-Boot | Defconfig và device tree cho board | Khi board cần khởi tạo đặc biệt (GPIO reset, nguồn) trước khi boot kernel |
| Kernel | Kernel config, file Device Tree của board | Khi có chip ngoại vi chưa có driver trong kernel |
| Root filesystem | Chọn init, thư viện, ứng dụng | Không; phần ứng dụng thuộc nhiệm vụ khác |
Một board có thể chạy Linux mà chưa cần driver mới nào nếu mọi chip ngoại vi đều đã có driver trong kernel. Việc chọn linh kiện có sẵn driver mainline từ khi thiết kế board giúp giảm đáng kể khối lượng porting.
Điểm xuất phát¶
Reference board¶
Cách làm thường dùng là bắt đầu từ reference board (evaluation kit) gần nhất với board tùy biến, vì cấu hình của board đó đã chạy được trên cả bootloader và kernel. Với Zynq-7000, các board có sẵn cấu hình trong mainline U-Boot gồm ZC702, ZC706, Zedboard, MicroZed và ZC770 [1].
Từ schematic của hai board, lập một bảng khác biệt. Bảng này là danh sách việc cần làm cho các bước sau:
| Hạng mục | Câu hỏi cần trả lời | Ảnh hưởng tới |
|---|---|---|
| Clock đầu vào | Tần số thạch anh hoặc oscillator cấp cho SoC | Stage 1, Device Tree |
| DDR | Loại, dung lượng, độ rộng bus, part number | Stage 1, node memory |
| Boot media | SD, eMMC, QSPI, NAND; chân boot mode | Stage 1, U-Boot |
| Console | UART nào nối ra cổng debug | U-Boot, Device Tree, console= |
| Pin mux | Ngoại vi nào ra chân nào (MIO trên Zynq) | Stage 1 |
| Ethernet | PHY nào, địa chỉ MDIO, kiểu giao tiếp (RGMII, SGMII) | U-Boot, Device Tree |
| Chip trên bus | Thiết bị trên I2C, SPI, địa chỉ, chân ngắt và reset | Device Tree, kernel config |
| Nguồn và reset | Chip nào cần bật nguồn hoặc nhả reset bằng GPIO | U-Boot, Device Tree |
Ví dụ, Zedboard dùng clock PS 33,333 MHz, 512 MB DDR, UART1 làm console và một PHY Ethernet ở địa chỉ MDIO 0 [2]. Board tùy biến dùng thạch anh khác hoặc PHY ở địa chỉ khác thì các giá trị này phải đổi tương ứng.
Lựa chọn mainline hoặc vendor tree¶
Hãng chip thường duy trì một nhánh riêng (vendor tree) của kernel và U-Boot. Với Xilinx là linux-xlnx và u-boot-xlnx. Hai lựa chọn khác nhau ở các điểm sau:
| Tiêu chí | Mainline | Vendor tree |
|---|---|---|
| Hỗ trợ IP đặc thù của hãng | Có thể chưa đủ | Đầy đủ hơn, gồm driver chưa lên mainline |
| Phiên bản kernel | Mới nhất | Thường chậm hơn mainline, theo chu kỳ phát hành của hãng |
| Công cụ của hãng (Petalinux, Yocto layer) | Phải tự tích hợp | Được hỗ trợ trực tiếp |
| Bảo trì lâu dài | Theo cộng đồng, có nhánh LTS | Phụ thuộc lịch hỗ trợ của hãng |
Một khác biệt cụ thể trên Zynq: mainline kernel không có xilinx_zynq_defconfig mà hỗ trợ Zynq qua multi_v7_defconfig (có CONFIG_ARCH_ZYNQ=y), còn xilinx_zynq_defconfig chỉ có trong linux-xlnx [3][4].
Quy trình theo thứ tự boot¶
Các bước dưới đây đi theo thứ tự boot. Mỗi bước có một mốc kiểm tra, và chỉ nên chuyển sang bước sau khi đã đạt mốc của bước trước: lỗi ở bước sớm thường biểu hiện thành triệu chứng khó hiểu ở bước muộn.
Khởi tạo tối thiểu¶
Stage 1 bootloader khởi tạo clock, DDR controller, pin mux và UART để stage tiếp theo chạy được trong DRAM. Đây là phần phụ thuộc board nhiều nhất và cũng khó debug nhất, vì lỗi ở đây thường không để lại log nào.
Trên Zynq-7000, các thông số này được nhập trong phần cấu hình Processing System (PS) của Vivado: tần số clock đầu vào, thông số DDR theo datasheet của chip nhớ, phân bổ MIO. Vivado xuất file thiết kế phần cứng (.xsa, bản cũ là .hdf), từ đó sinh ra hàm ps7_init mà FSBL gọi lúc khởi động. Mainline U-Boot cũng có thể dùng SPL thay cho FSBL, với file ps7_init_gpl.c sinh từ cùng thiết kế đó (xem Porting Linux cho Zynq). Khi đó spl/boot.bin và u-boot.img được chép vào phân vùng FAT của SD card [1].
Mốc kiểm tra:
- Stage 1 in được log ra UART. Trên FSBL của Xilinx, cần bật
FSBL_DEBUG_INFOđể có log chi tiết. - DDR ổn định: sau khi vào được U-Boot, chạy lệnh
mtest(cần bậtCONFIG_CMD_MEMTEST) trên một vùng nhớ đủ lớn. Lỗi DDR do thông số timing sai thường chỉ xuất hiện ngẫu nhiên, nên cần chạy đủ lâu.
Bootloader¶
Ở U-Boot, việc port board gồm chọn defconfig và tạo device tree cho board. Mainline U-Boot cho Zynq-7000 dùng chung một defconfig là xilinx_zynq_virt_defconfig, còn board cụ thể được chọn qua biến DEVICE_TREE [1]:
export CROSS_COMPILE=arm-linux-gnueabihf-
export DEVICE_TREE=zynq-myboard # arch/arm/dts/zynq-myboard.dts
make xilinx_zynq_virt_defconfig
make
File zynq-myboard.dts thường được tạo bằng cách sao chép device tree của reference board (zynq-zed.dts) rồi sửa theo bảng khác biệt. U-Boot dùng device tree cùng cú pháp với kernel. Các node cần dùng trước khi U-Boot relocate vào DRAM hoặc trong SPL (UART, SD) được đánh dấu bằng thuộc tính bootph-all, như trên uart1 và sdhci0 của Zedboard [2].
Mốc kiểm tra:
- Có prompt U-Boot trên UART.
- Đọc được boot media (
mmc info,sf probe). - Nếu board có Ethernet,
pingtới máy host thành công. Từ đây có thể nạp kernel và DTB qua TFTP thay vì ghi lại SD card mỗi lần thử (xem Môi trường phát triển trên host).
Chi tiết về environment, lệnh boot và FIT image xem trang U-Boot.
Kernel và Device Tree¶
Device Tree là phần thay đổi chính ở kernel khi port board. Device Tree của board thường gồm một file .dtsi mô tả SoC, do hãng chip cung cấp, và một file .dts mô tả board, include file .dtsi đó. Trong .dtsi, các ngoại vi của SoC đều được khai báo nhưng để status = "disabled". File .dts của board bật các ngoại vi thực sự được dùng và bổ sung thông tin chỉ board mới biết: dung lượng DDR, console, PHY, các chip trên bus. Trong mainline hiện tại, các file này của Zynq nằm ở arch/arm/boot/dts/xilinx/ [2][5].
Ví dụ dưới đây là phần khung của một board Zynq-7000, viết theo cấu trúc của zynq-zed.dts trong mainline [2]. Tên board và giá trị là giả định:
/dts-v1/;
#include "zynq-7000.dtsi"
/ {
model = "My Zynq-7000 board";
compatible = "mycompany,myboard", "xlnx,zynq-7000";
aliases {
ethernet0 = &gem0;
serial0 = &uart1;
mmc0 = &sdhci0;
};
memory@0 {
device_type = "memory";
reg = <0x0 0x20000000>; /* 512 MB DDR */
};
chosen {
stdout-path = "serial0:115200n8";
};
};
&clkc {
ps-clk-frequency = <33333333>; /* tần số clock PS trên board */
};
&uart1 {
bootph-all;
status = "okay";
};
&sdhci0 {
bootph-all;
status = "okay";
};
&gem0 {
status = "okay";
phy-mode = "rgmii-id";
phy-handle = <ðernet_phy>;
ethernet_phy: ethernet-phy@0 {
reg = <0>; /* địa chỉ MDIO của PHY */
};
};
Các điểm cần đối chiếu với bảng khác biệt:
ps-clk-frequencyphải khớp với clock thực tế cấp cho PS. Sai giá trị này làm sai mọi clock dẫn xuất, gồm baudrate của UART.- Alias
serial0 = &uart1quyết định UART1 trở thànhttyPS0trong Linux. Driver UART của Zynq đặt tên thiết bị làttyPS[6]. regcủa node PHY là địa chỉ MDIO, phải khớp với chân strap địa chỉ trên board.phy-modephụ thuộc vào việc delay của RGMII được thêm ở PHY, ở MAC hay trên đường mạch.
Cú pháp và cách driver đọc Device Tree xem trang Device Tree (Linux Kernel). Với IP nằm trong PL, Device Tree được sinh từ thiết kế Vivado, xem trang Porting Linux cho Zynq.
Kernel config bắt đầu từ defconfig của SoC (multi_v7_defconfig hoặc xilinx_zynq_defconfig), sau đó bật thêm driver cho các chip trên board bằng make menuconfig. Driver cần để mount rootfs (controller SD hoặc eMMC, filesystem ext4) phải build sẵn trong kernel (=y), trừ khi dùng initramfs để nạp module. Sau khi chỉnh, make savedefconfig sinh ra file defconfig tối giản để lưu thành defconfig của board.
Kernel command line tối thiểu cho bước này:
Khi earlycon không có tham số, kernel chọn console sớm theo thuộc tính stdout-path trong node chosen của Device Tree [7]. Nhờ đó kernel in được log ngay từ đầu, trước khi driver UART chính thức probe. Các tham số khác xem trang Kernel Command Line.
Mốc kiểm tra: kernel in log ra console và chạy tới bước mount rootfs. Nếu log dừng ở Starting kernel ... hoặc kernel panic khi mount rootfs, xem bảng triệu chứng ở trang Log boot và earlycon.
Root filesystem¶
Ở lần boot đầu, rootfs nên càng đơn giản càng tốt để tách lỗi của kernel khỏi lỗi của user space. Một initramfs chỉ có BusyBox, nhúng vào kernel hoặc nạp riêng bằng U-Boot, là đủ để có shell và kiểm tra phần cứng. Khi đã có shell, chuyển sang rootfs trên SD card hoặc eMMC, build bằng Buildroot hoặc Yocto.
Mốc kiểm tra: init chạy, có shell trên console. Cách tổ chức rootfs cho sản phẩm (chỉ đọc, overlayfs, phân vùng dữ liệu) xem trang Root filesystem cho thiết bị.
Ngoại vi¶
Sau khi có shell, đưa từng nhóm ngoại vi vào hoạt động theo thứ tự phụ thuộc: storage, Ethernet, các chip trên I2C và SPI, cuối cùng là IP trong PL. Mỗi nhóm kiểm tra bằng công cụ ở user space trước khi chuyển sang nhóm tiếp theo:
| Nhóm | Lệnh kiểm tra | Dấu hiệu đạt |
|---|---|---|
| Board và Device Tree | cat /proc/device-tree/model |
In đúng model của board, xác nhận kernel dùng đúng DTB |
| Storage | lsblk, dmesg \| grep mmc |
Thấy đủ thiết bị và phân vùng |
| Ethernet | ip link, ethtool eth0 |
Link lên đúng tốc độ, ping thông |
| I2C | i2cdetect -y <bus> |
Thiết bị trả lời ở đúng địa chỉ |
| Driver chưa probe | cat /sys/kernel/debug/devices_deferred |
Danh sách rỗng |
File devices_deferred trong debugfs liệt kê các thiết bị có driver đang hoãn probe, kèm lý do [8]. Thiết bị nằm lâu trong danh sách này thường là do thiếu một tài nguyên mà nó phụ thuộc: clock, regulator, GPIO reset, hoặc driver của tài nguyên đó chưa được bật trong kernel config.
Nạp bitstream và khai báo IP trong PL xem trang Nạp bitstream FPGA từ Linux (Zynq). Chip ngoại vi chưa có driver thì cần viết driver, phần này thuộc tab Linux Kernel.
Đưa cấu hình vào build system¶
Các bước trên thường làm thủ công để kiểm tra nhanh. Khi board đã chạy ổn, toàn bộ cấu hình cần được đưa vào build system để build lại được và bàn giao cho người khác:
| Build system | Nơi đặt cấu hình của board |
|---|---|
| Buildroot | Defconfig của board trong configs/ và thư mục board/<hãng>/<board>/ chứa DTS, patch, overlay; nên đặt trong một br2-external tree (xem Buildroot: package và BR2_EXTERNAL) |
| Yocto | Một BSP layer chứa file machine (conf/machine/<board>.conf), kernel config fragment và DTS (xem Yocto) |
| Petalinux | Project Petalinux nhập file thiết kế phần cứng từ Vivado; DTS bổ sung đặt trong recipe device tree của project (xem Xilinx Petalinux) |
Defconfig của U-Boot và kernel, file DTS và các patch phải nằm trong quản lý phiên bản cùng với cấu hình build, không chỉ tồn tại trong thư mục build.
Liên quan¶
- Serial console: nối UART với host,
console=và getty. - Log boot và earlycon: đọc log theo giai đoạn boot, console sớm,
initcall_debug, pstore. - Bring-up & Debug (Hardware Design): kiểm tra nguồn, clock, reset trên board trước khi chạy phần mềm.
- Linux Kernel Boot Process (Linux Kernel): kernel làm gì từ lúc nhận DTB tới khi chạy init.
- Device Tree (Linux Kernel): cú pháp và cách driver dùng Device Tree.
- Bootloader trong hệ nhúng Linux: chuỗi boot nhiều stage và bảng debug boot.
- Porting Linux cho Zynq: chuỗi boot,
BOOT.BIN, build U-Boot và kernel trên Zynq. - MCU trong hệ thống Linux: các chip có firmware riêng trên cùng board.
Tài liệu tham khảo¶
[1] - ZYNQ - tài liệu board Zynq trong source tree U-Boot (doc/board/xilinx/zynq.rst)
[2] - zynq-zed.dts - Device Tree của Zedboard trong mainline kernel
[3] - multi_v7_defconfig - mainline kernel
[4] - xilinx_zynq_defconfig - Xilinx linux-xlnx
[5] - zynq-7000.dtsi - Device Tree của SoC Zynq-7000 trong mainline kernel
[6] - xilinx_uartps.c - driver UART của Zynq (CDNS_UART_TTY_NAME)
[7] - The kernel's command-line parameters - docs.kernel.org, tham số earlycon
[8] - drivers/base/dd.c - mainline kernel, file debugfs devices_deferred