Tổng quan về U-Boot
Để khởi động một hệ điều hành Linux trên các hệ thống nhúng, cần có một chương trình bootloader. Bootloader là phần mềm đầu tiên chạy khi chip được cấp điện. Chức năng chính của nó là khởi tạo các thiết bị ngoại vi cơ bản như DDR RAM, sau đó sao chép nhân Linux từ bộ nhớ flash (NAND, NOR FLASH, SD, EMMC) vào DDR và cuối cùng là khởi động nhân Linux. Vai trò của bootloader tương tự như BIOS trên máy tính cá nhân trong việc khởi động Windows.
Có nhiều phiên bản U-Boot khác nhau:
- U-Boot chính thức (Official U-Boot): Được cộng đồng U-Boot duy trì, cập nhật thường xuyên và hỗ trợ nhiều chip phổ biến.
- U-Boot của nhà sản xuất bán dẫn (Semiconductor Vendor's U-Boot): Các nhà sản xuất chip (như STMicroelectronics, NXP) cung cấp phiên bản U-Boot tùy chỉnh, tối ưu hóa cho chip của họ và thường có sự hỗ trợ tốt hơn cho các tính năng độc quyền của chip.
- U-Boot của nhà sản xuất bo mạch (Board Vendor's U-Boot): Dựa trên U-Boot của nhà sản xuất bán dẫn, các nhà sản xuất bo mạch tích hợp thêm hỗ trợ cho các thiết bị ngoại vi và cấu hình phần cứng cụ thể của bo mạch phát triển của họ.
Trong thực tế, người dùng thường sử dụng U-Boot do nhà sản xuất bán dẫn hoặc nhà sản xuất bo mạch cung cấp vì chúng được tối ưu hóa tốt hơn cho phần cứng cụ thể. Sử dụng U-Boot chính thức có thể yêu cầu nhiều công đoạn porting (chuyển đổi) hơn để hỗ trợ đầy đủ thiết bị ngoại vi trên bo mạch.
Biên dịch U-Boot
1. Chuẩn bị môi trường biên dịch
Trước tiên, cần cài đặt các thư viện cần thiết trên hệ thống Ubuntu của bạn:
sudo apt-get install libncurses5-dev bison flex
Giải nén mã nguồn U-Boot vào thư mục làm việc mong muốn của bạn. Giả sử tệp mã nguồn là u-boot-stm32mp-2020.01-v1.x.tar.bz2:
tar -vxf u-boot-stm32mp-2020.01-v1.x.tar.bz2
2. Cấu hình và biên dịch
Quá trình biên dịch U-Boot bao gồm các bước sau:
make distclean
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp157d_atk_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- DEVICE_TREE=stm32mp157d-atk all
ARCH: Chỉ định kiến trúc nền tảng, ở đây làarm.CROSS_COMPILE: Tiền tố của bộ biên dịch chéo (cross-compiler) được sử dụng, ví dụ:arm-none-linux-gnueabihf-.DEVICE_TREE: Tệp Device Tree (cây thiết bị). U-Boot cũng sử dụng Device Tree để mô tả phần cứng, vì vậy cần chỉ định tệp Device Tree phù hợp với bo mạch của bạn, ví dụ:stm32mp157d-atk.
Để đơn giản hóa lệnh biên dịch, bạn có thể chỉnh sửa tệp Makefile trong thư mục gốc của U-Boot để gán giá trị mặc định cho ARCH và CROSS_COMPILE. Tìm và sửa đổi các dòng tương ứng:
ARCH = arm
CROSS_COMPILE = arm-none-linux-gnueabihf-
Sau khi chỉnh sửa Makefile, các lệnh biên dịch sẽ ngắn gọn hơn:
make distclean
make stm32mp157d_atk_defconfig
make V=1 DEVICE_TREE=stm32mp157d-atk all
Lệnh make V=1 sẽ hiển thị chi tiết quá trình biên dịch. Để tăng tốc độ, bạn có thể sử dụng biên dịch đa luồng bằng tùy chọn -j, ví dụ sử dụng 8 luồng:
make V=1 DEVICE_TREE=stm32mp157d-atk all -j8
Sau khi biên dịch thành công, các tệp ảnh U-Boot quan trọng sẽ được tạo, đặc biệt là u-boot.bin (tệp nhị phân thực thi) và u-boot.stm32 (tệp nhị phân có thêm 256 byte thông tin tiêu đề). Tệp u-boot.stm32 thường được sử dụng để flash lên thiết bị.
Ghi U-Boot vào thiết bị
Để ghi U-Boot đã biên dịch vào bộ nhớ EMMC của bo mạch, bạn có thể sử dụng công cụ như STM32CubeProgrammer. Cần cập nhật tệp tf-a.tsv (nếu bạn đang sử dụng TF-A) để thêm chỉ thị ghi U-Boot. Thêm dòng sau vào cuối tệp:
# U-Boot
- 0x00020000 u-boot.stm32
Sao chép tệp u-boot.stm32 vừa biên dịch vào thư mục images mà bạn đã sử dụng cho các bước flash trước đó. Sau đó, sử dụng STM32CubeProgrammer để flash U-Boot qua kết nối USB OTG. Khi quá trình hoàn tất, thiết lập các công tắc trên bo mạch để khởi động từ EMMC.
Kết nối cổng USB\_TTL của bo mạch với máy tính qua cáp USB Type-C và mở một chương trình terminal (ví dụ MobaXterm hoặc PuTTY) để theo dõi và tương tác với U-Boot. Khi bạn thấy thông báo "Hit any key to stop autoboot: " với bộ đếm ngược, nhấn phím Enter để vào chế độ dòng lệnh của U-Boot. Nếu không nhấn, U-Boot sẽ tự động cố gắng khởi động nhân Linux (nếu có).
Phân tích thông báo khởi động U-Boot
Khi U-Boot khởi động, nó sẽ hiển thị một loạt thông tin:
U-Boot 2020.01-stm32mp-r1 (Jul 30 2023 - 12:37:27 +0800) // Phiên bản U-Boot và thời gian biên dịch.
CPU: STM32MP157DAA Rev.Z // Thông tin về CPU.
Model: STMicroelectronics STM32MP157D eval daughter // Mô tả bo mạch, có thể là từ thiết kế tham chiếu.
Board: stm32mp1 in trusted mode (st,stm32mp157d-atk) // Chế độ hoạt động của bo mạch.
DRAM: 1 GiB // Dung lượng bộ nhớ DDR.
Clocks: // Tần số các khối chức năng.
- MPU : 800 MHz
- MCU : 208.878 MHz
- AXI : 266.500 MHz
- PER : 24 MHz
- DDR : 533 MHz
WDT: Started with servicing (32s timeout) // Thông tin về Watchdog Timer.
NAND: 0 MiB // Dung lượng NAND (0 nếu không có).
MMC: STM32 SD/MMC: 0, STM32 SD/MMC: 1 // Các thiết bị MMC/SD có sẵn (SD/MMC0 thường là SD card, SD/MMC1 thường là EMMC).
Loading Environment from MMC... OK // Tải biến môi trường từ MMC.
In: serial // Kênh đầu vào chuẩn là serial.
Out: serial // Kênh đầu ra chuẩn là serial.
Err: serial // Kênh lỗi chuẩn là serial.
invalid MAC address in OTP 00:00:00:00:00:00 // Lỗi địa chỉ MAC không hợp lệ (cần thiết lập).
Net:
Error: ethernet@5800a000 address not set.
No ethernet found. // Thông tin mạng ban đầu, có thể thiếu MAC address.
lcd_id = 02 // Một biến môi trường nào đó.
Hit any key to stop autoboot: 0 // Bộ đếm ngược tự động khởi động.
Boot over mmc1!
switch to partitions #0, OK
mmc1(part 0) is current device
** Unrecognized filesystem type **
STM32MP> // Dấu nhắc lệnh U-Boot.
Sử dụng các lệnh U-Boot
Trong chế độ dòng lệnh U-Boot, bạn có thể nhập help hoặc ? theo sau là tên lệnh để xem chi tiết cách sử dụng. Ví dụ: ? bootz.
1. Lệnh truy vấn thông tin
bdinfo: Hiển thị thông tin tổng quan về bo mạch, bao gồm địa chỉ và kích thước DRAM, địa chỉ tham số khởi động, tốc độ baud, địa chỉ con trỏ ngăn xếp (SP), v.v.printenv: Hiển thị tất cả các biến môi trường hiện tại. U-Boot hỗ trợ tự động hoàn thành bằng phím TAB. Các biến môi trường là các chuỗi giá trị có thể cấu hình, ví dụbootdelay=1(thời gian chờ khởi động mặc định 1 giây).version: Hiển thị phiên bản U-Boot.
2. Lệnh thao tác biến môi trường
Các lệnh chính để quản lý biến môi trường là setenv và saveenv. Các biến môi trường được lưu trữ trong flash và được đọc vào DRAM khi U-Boot khởi động. Mọi thay đổi với setenv chỉ có hiệu lực trong DRAM cho đến khi bạn sử dụng saveenv để lưu chúng vào flash.
- Thay đổi biến môi trường: Để thay đổi giá trị của một biến, ví dụ đặt
bootdelaythành5giây:setenv bootdelay 5 saveenvSau khi lưu và khởi động lại, U-Boot sẽ chờ 5 giây trước khi tự động khởi động.
- Tạo biến môi trường mới: Sử dụng
setenvtương tự để tạo biến mới. Ví dụ, tạo biếnkernel_params:setenv kernel_params 'console=ttySTM0,115200 root=/dev/mmcblk2p2 rootwait rw' saveenvLưu ý sử dụng dấu nháy đơn nếu giá trị chứa khoảng trắng.
- Xóa biến môi trường: Để xóa một biến, gán cho nó một giá trị rỗng:
setenv kernel_params saveenvSau khi lưu và khởi động lại, biến
kernel_paramssẽ không còn.
3. Lệnh thao tác bộ nhớ (DRAM)
Các lệnh này dùng để đọc và ghi trực tiếp vào bộ nhớ DRAM. Các số trong lệnh U-Boot luôn được hiểu là hệ thập lục phân (hexadecimal).
md[.b, .w, .l] address [# of objects]: Hiển thị nội dung bộ nhớ..b,.w,.l: Chỉ định hiển thị theo byte (1 byte), word (2 byte) hoặc long (4 byte).address: Địa chỉ bắt đầu.# of objects: Số lượng đối tượng để hiển thị (không phải số byte). Ví dụ,0x10đối tượng hiển thị theo.bsẽ là 16 byte, theo.wsẽ là 32 byte, theo.lsẽ là 64 byte.
md.b C0100000 10 // Hiển thị 0x10 (16) byte từ địa chỉ 0xC0100000 md.w C0100000 10 // Hiển thị 0x10 (16) word (32 byte) từ địa chỉ 0xC0100000 md.l C0100000 10 // Hiển thị 0x10 (16) long (64 byte) từ địa chỉ 0xC0100000nm[.b, .w, .l] address: Sửa đổi giá trị bộ nhớ tại địa chỉ chỉ định. Nhập giá trị mới sau dấu nhắc?, sau đó nhậpqđể thoát. Địa chỉ sẽ không tự động tăng.nm.l C0100000 // Sửa 4 byte tại 0xC0100000 // Nhập giá trị mới, ví dụ: 12345678, sau đó nhấn Enter, rồi nhấn qmm[.b, .w, .l] address: Sửa đổi giá trị bộ nhớ, địa chỉ sẽ tự động tăng. Tương tựnmnhưng tiện lợi hơn cho việc sửa nhiều vùng nhớ liên tiếp.mm.l C0100000 // Sửa 4 byte tại 0xC0100000, sau đó địa chỉ sẽ tăng lên C0100004 // Nhập giá trị mới, ví dụ: 05050505, sau đó nhấn Enter, rồi nhấn qmw[.b, .w, .l] address value [count]: Điền một giá trị cụ thể vào một vùng bộ nhớ.address: Địa chỉ bắt đầu.value: Giá trị cần điền.count: Số lượng đối tượng cần điền.
mw.l C0100000 0A0A0A0A 10 // Điền giá trị 0x0A0A0A0A vào 0x10 (16) long (64 byte) từ 0xC0100000cp[.b, .w, .l] source target count: Sao chép dữ liệu từ một vùng bộ nhớ này sang vùng bộ nhớ khác.source: Địa chỉ nguồn.target: Địa chỉ đích.count: Số lượng đối tượng cần sao chép.
cp.l c0100000 c0100100 10 // Sao chép 0x10 long (64 byte) từ 0xC0100000 đến 0xC0100100cmp[.b, .w, .l] addr1 addr2 count: So sánh dữ liệu giữa hai vùng bộ nhớ.addr1: Địa chỉ bắt đầu vùng nhớ thứ nhất.addr2: Địa chỉ bắt đầu vùng nhớ thứ hai.count: Số lượng đối tượng cần so sánh.
cmp.l c0100000 c0100100 10 // So sánh 0x10 long (64 byte) từ 0xC0100000 và 0xC0100100U-Boot sẽ báo cáo nếu hai vùng giống nhau hoặc khác nhau.
4. Lệnh thao tác mạng
U-Boot hỗ trợ nhiều chức năng mạng, rất hữu ích cho việc gỡ lỗi nhân Linux qua mạng. Trước khi sử dụng, cần cấu hình các biến môi trường mạng:
setenv ipaddr 192.168.1.106 // Địa chỉ IP của bo mạch
setenv ethaddr b8:ae:1d:01:01:00 // Địa chỉ MAC của bo mạch (phải là duy nhất trong mạng)
setenv gatewayip 192.168.1.1 // Địa chỉ gateway
setenv netmask 255.255.255.0 // Subnet mask
setenv serverip 192.168.1.105 // Địa chỉ IP của máy chủ (ví dụ: máy tính Ubuntu)
saveenv
Đảm bảo rằng bo mạch và máy chủ Ubuntu của bạn ở cùng một dải mạng.
ping [IP]: Kiểm tra kết nối mạng với một máy chủ khác.ping 192.168.1.105Lưu ý: U-Boot chỉ có thể ping các thiết bị khác, các thiết bị khác không thể ping U-Boot vì U-Boot không xử lý các yêu cầu ping đến.
dhcp: Lấy địa chỉ IP tự động từ router DHCP. Chỉ hoạt động khi bo mạch được kết nối với router.dhcpLệnh này cũng có thể được cấu hình để khởi động nhân Linux qua TFTP.
nfs [loadAddress] [[hostIPaddr:]bootfilename]: Tải tệp qua NFS (Network File System) vào DRAM. Hữu ích cho việc gỡ lỗi nhân và device tree mà không cần flash.Để sử dụng NFS, bạn cần thiết lập máy chủ NFS trên Ubuntu. Ví dụ, tạo thư mục
/home/nguoidung/linux/nfsvà export nó.nfs C2000000 192.168.1.105:/home/nguoidung/linux/nfs/uImageC2000000là địa chỉ DRAM để lưu tệp,192.168.1.105là IP máy chủ, và phần còn lại là đường dẫn tệp trên máy chủ NFS.tftpboot [loadAddress] [[hostIPaddr:]bootfilename]: Tải tệp qua TFTP (Trivial File Transfer Protocol) vào DRAM.Cần cài đặt và cấu hình máy chủ TFTP trên Ubuntu:
sudo apt-get install tftp-hpa tftpd-hpa xinetd mkdir /home/nguoidung/linux/tftpboot chmod 777 /home/nguoidung/linux/tftpbootTạo tệp cấu hình
/etc/xinetd.d/tftpvới nội dung:server tftp { socket_type = dgram protocol = udp wait = yes user = root server = /usr/sbin/in.tftpd server_args = -s /home/nguoidung/linux/tftpboot/ disable = no per_source = 11 cps = 100 2 flags = IPv4 }Cấu hình tệp
/etc/default/tftpd-hpa:# /etc/default/tftpd-hpa TFTP_USERNAME="tftp" TFTP_DIRECTORY="/home/nguoidung/linux/tftpboot" TFTP_ADDRESS=":69" TFTP_OPTIONS="-l -c -s"Khởi động lại dịch vụ TFTP:
sudo service tftpd-hpa restartSao chép tệp cần thiết vào thư mục TFTP và cấp quyền:
cp uImage /home/nguoidung/linux/tftpboot/ chmod 777 /home/nguoidung/linux/tftpboot/uImageSử dụng lệnh
tftpboottrong U-Boot (chỉ cần tên tệp, không cần đường dẫn đầy đủ trên máy chủ):tftp C2000000 uImageCác lỗi "Permission denied" thường do thiếu quyền truy cập vào thư mục TFTP hoặc tệp.
5. Lệnh thao tác với EMMC và SD Card
U-Boot cung cấp nhiều lệnh để tương tác với các thiết bị MMC/SD.
mmc info: Hiển thị thông tin về thiết bị MMC/SD hiện tại.mmc rescan: Quét lại các thiết bị MMC/SD để nhận diện các thiết bị mới hoặc thay đổi.mmc list: Liệt kê tất cả các thiết bị MMC/SD được U-Boot phát hiện.mmc dev [devnum] [partnum]: Chọn thiết bị MMC/SD và phân vùng làm việc.devnumlà chỉ số thiết bị (ví dụ 0 cho SD card, 1 cho EMMC),partnumlà chỉ số phân vùng.mmc read addr blk# cnt: Đọccntkhối (block) từ vị tríblk#của thiết bị MMC/SD đã chọn vào địa chỉaddrtrong DRAM.mmc write addr blk# cnt: Ghicntkhối từ địa chỉaddrtrong DRAM vào vị tríblk#của thiết bị MMC/SD đã chọn.fatls mmc <dev>:<part> [<directory>]: Liệt kê các tệp trên phân vùng FAT của thiết bị MMC/SD.ext4ls mmc <dev>:<part> [<directory>]: Liệt kê các tệp trên phân vùng EXT4 của thiết bị MMC/SD.fatload mmc <dev>:<part> <addr> <filename>: Tải tệpfilenametừ phân vùng FAT vào địa chỉaddrtrong DRAM.ext4load mmc <dev>:<part> <addr> <filename>: Tải tệpfilenametừ phân vùng EXT4 vào địa chỉaddrtrong DRAM.ext4load mmc 1:2 C2000000 uImage // Tải uImage từ phân vùng 2 của EMMC (mmc 1) vào C2000000Đây là một lệnh quan trọng để tải nhân Linux hoặc Device Tree từ hệ thống tệp EXT4 trên thẻ nhớ/EMMC.
6. Lệnh khởi động (Boot)
Các lệnh này được dùng để khởi động nhân Linux sau khi đã tải nó vào DRAM.
bootm [addr [arg ...]]: Khởi động tệp ảnh nhân Linux định dạnguImage.addr: Địa chỉ nhân Linux trong DRAM.arg...: Các tham số tùy chọn. Nếu cóinitrd, tham số thứ hai là địa chỉinitrd. Nếu sử dụng Device Tree, tham số thứ ba là địa chỉ Device Tree trong DRAM. Dùng-nếu không cóinitrd.
Ví dụ, tải nhân và device tree qua TFTP, sau đó khởi động:
tftp C2000000 uImage tftp C4000000 stm32mp157d-atk.dtb bootm C2000000 - C4000000bootz [addr [initrd[:size]] [fdt]]: Tương tự nhưbootmnhưng dùng cho tệp ảnh nhân Linux định dạngzImage. Các tham số tương tựbootm.bootvàbootd: Hai lệnh này thực chất cùng thực hiện một hàm, thường được sử dụng để khởi động hệ thống theo biến môi trườngbootcmd.bootcmdlà một biến môi trường lưu trữ chuỗi các lệnh khởi động. Bạn có thể định cấu hìnhbootcmdđể tự động khởi động Linux khi bộ đếm ngược kết thúc. Ví dụ:setenv bootcmd 'tftp C2000000 uImage;tftp C4000000 stm32mp157d-atk.dtb;bootm C2000000 - C4000000' saveenv bootLệnh
bootsẽ thực thi chuỗi lệnh được lưu trongbootcmd.
7. Lệnh ums (USB Mass Storage)
Lệnh ums cho phép bo mạch hoạt động như một thiết bị lưu trữ USB (USB Mass Storage), cho phép bạn truy cập bộ nhớ flash của bo mạch (như EMMC hoặc SD card) từ máy tính của mình như một USB drive thông thường.
ums <USB_controller> [<devtype>] <dev[:part]>
USB_controller: Chỉ số của bộ điều khiển USB (thường là0cho cổng USB_OTG).devtype: Loại thiết bị lưu trữ (mặc định làmmc).dev[:part]: Thiết bị flash cần gắn kết, cùng với phân vùng tùy chọn. Ví dụ1cho EMMC, hoặc1:2cho phân vùng 2 của EMMC.
Kết nối cổng USB_OTG của bo mạch với máy tính qua cáp Type-C, sau đó thực thi:
ums 0 mmc 1
Lệnh này sẽ biến EMMC của bo mạch thành một thiết bị lưu trữ USB mà máy tính có thể nhận diện.
8. Các lệnh thông dụng khác
reset: Khởi động lại bo mạch.go addr [arg ...]: Nhảy đến địa chỉaddrtrong DRAM và thực thi mã tại đó.run <env_var>: Thực thi chuỗi lệnh được lưu trữ trong biến môi trường<env_var>. Điều này rất hữu ích để tạo các kịch bản khởi động tùy chỉnh.Ví dụ, tạo các biến môi trường để khởi động từ EMMC hoặc qua mạng:
setenv mybootemmc 'ext4load mmc 1:2 C2000000 uImage;ext4load mmc 1:2 C4000000 stm32mp157d-atk.dtb;bootm C2000000 - C4000000' setenv mybootnet 'tftp C2000000 uImage;tftp C4000000 stm32mp157d-atk.dtb;bootm C2000000 - C4000000' saveenvSau đó, bạn có thể chạy:
run mybootemmchoặc
run mybootnetđể chuyển đổi giữa các phương pháp khởi động dễ dàng.
mtest [start [end [pattern [iterations]]]]: Một lệnh đơn giản để kiểm tra đọc/ghi bộ nhớ DDR.start: Địa chỉ bắt đầu kiểm tra.end: Địa chỉ kết thúc kiểm tra.
mtest C0000000 C0001000 // Kiểm tra vùng nhớ từ 0xC0000000 đến 0xC0001000Nhấn
Ctrl+Cđể dừng kiểm tra.
9. Lệnh MII
Các lệnh MII (Media Independent Interface) được sử dụng để đọc và ghi vào các thanh ghi của chip PHY mạng. Điều này rất hữu ích khi gỡ lỗi chức năng Ethernet.
Trên một số nền tảng (như STM32MP157), đồng hồ ETHMAC có thể bị tắt sau mỗi lần giao tiếp mạng, khiến các lệnh MII không hoạt động. Để khắc phục, bạn cần kích hoạt lại đồng hồ ETHMAC trước khi sử dụng lệnh MII bằng cách đặt các bit 8-10 của thanh ghi 0x50000218 thành 1. Các bước thực hiện:
- Ping một thiết bị mạng khác để xác nhận kết nối mạng cơ bản.
- Kích hoạt đồng hồ ETHMAC (sử dụng lệnh
mwhoặcmmđể thiết lập các bit thanh ghi). - Sử dụng các lệnh MII để truy vấn thanh ghi PHY.
Ví dụ về lệnh MII:
mii info <addr>: Hiển thị thông tin về chip PHY tại địa chỉ<addr>. Địa chỉ PHY thường là0x00hoặc0x01tùy thuộc vào chip cụ thể (ví dụ, YT8511 thường là0x00, RTL8211 thường là0x01).
mii info 0