# Running Linux 6.14 on stm32f429 Contributed by < [`kk908676`]((https://github.com/kk908676)) > Continued from @cychen [`hugo0406`](https://github.com/hugo0406) : [Linux 核心專題: Running Linux 6.10 on stm32f429](https://hackmd.io/0Ua7E28UTDOR8vo0Cqikdg?view) ## STM32F429i-Discovery - STM32F429 MCU (ARM Cortex-M4 up to 180MHz, 2MB internal flash, 256KB internal RAM) - 8MB SDRAM - USB OTG Full-Speed - 2.4" LCD - MEMS motion sensor - STLink/V2 (debugging) - Extension headers ![image](https://hackmd.io/_uploads/HyjgefKE0.png) ### STM32 Reset 參考 STM32F429 的 [Reference Manual](https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf) Boot mode selection pins: BOOT0,BOOT1 CPU reset 後會從這兩根 pin 的狀態來決定哪一塊 memory 當 boot space,STM32F429 選用的 default boot space 是 Main Flash memory | BOOT1 | BOOT0 | Boot Mode | Aliasing | |:-----:|:-----:|:----------------- |:-------- | | X | 0 | Main Flash Memory |Main Flash Memory is selected as the boot space | | 0 | 1 | System Memory |System Memory is selected as the boot space | | 1 | 1 | Embedded SRAM |Embedded SRAM is selected as the boot space | Flash memory 的 start address 是 `0x08000000` 等開機時,會自動將 `0x08000000-0x080FFFFF` 映射到 `0x00000000-0x000FFFFF`,所以將 bin檔燒在 `0x08000000` ,STM32F429 就可以從 `0x00000000` 開始執行 ![image](https://hackmd.io/_uploads/BJoOsfAkge.png) 參考 STM32F429 的 [Reference Manual](https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf) - ARM Cross Compile Toolchain: ```bash sudo apt-get install arm-linux-gnueabi-gcc which arm-linux-gnueabi-gcc export PATH=/usr/bin:$PATH source ~/.bashrc ``` ```diff - sudo apt-get install arm-linux-gnueabi-gcc + sudo apt install gcc-arm-linux-gnueabi ``` - STLINK Tools: `stlink` is an open source toolset to program and debug STM32 devices and boards manufactured by STMicroelectronics. ```bash cd ~ git clone https://github.com/stlink-org/stlink cd stlink sudo apt-get install -y cmake libusb-1.0-0-dev sudo apt-get -y install cmake sudo apt-get install libstlink1 make cd build/Release sudo make install ``` To verify the successful installation ```bash $ st-flash invalid command line usage: st-flash [options] read [file] [addr] [size] st-flash [options] write <file> [addr] [size] st-flash [options] write <value> st-flash [options] erase <addr> <size> st-flash [options] reset options: --freq <kHz> Frequency of JTAG/SWD, default 1800kHz. --serial <serial> STLink device to use. --connect-under-reset Pull reset low while connecting. --hot-plug Connect without reset. --reset Reset after writing. --format {binary|ihex} Format of file to read or write. When writing with ihex specifying addr is not needed. --flash <size> Specify size of flash, e.g. 128k, 1M. --area <area> Area to access, one of: main(default), system, otp, option, option_boot_add, optcr, optcr1. --opt Skip writing empty bytes at the tail end. --debug Output extra debug information. --version Print version information. --help Show this help. examples: st-flash --area=option read [file] [size] st-flash --area=option write 0xXXXXXXXX st-flash --area=option_boot_add read st-flash --area=option_boot_add write 0xXXXXXXXX st-flash --area=optcr read st-flash --area=optcr write 0xXXXXXXXX st-flash --area=optcr1 read st-flash --area=optcr1 write 0xXXXXXXXX st-flash --area=otp read <file> st-flash --area=otp write <file> 0xXXXXXXXX ``` 這時候可能會發現有這麼一段錯誤訊息 ``` $ st-info --probe 2025-03-11T21:19:20 ERROR usb.c: Could not open USB device 0x0483:0x374b, access error. ``` 大多數發行版不允許存取 USB 裝置,從[官方檔案](https://github.com/stlink-org/stlink/blob/testing/doc/compiling.md#Windows)中我們可以解決這問題 ``` Set device access permissions and the role of udev By default most distributions don't allow access to USB devices. In this context udev rules, which create devices nodes, are necessary to run the tools without root permissions. To achieve this you need to ensure that the group plugdev exists and the user who is trying to access these devices is a member of this group. Within the sourcefolder of the project, these rules are located in the subdirectory config/udev/rules.d and are automatically installed along with sudo make install on linux. Afterwards it may be necessary to reload the udev rules: $ sudo cp -a config/udev/rules.d/* /lib/udev/rules.d/ $ sudo udevadm control --reload-rules $ sudo udevadm trigger udev will now create device node files, e.g. /dev/stlinkv3_XX, /dev/stlinkv2_XX, /dev/stlinkv1_XX. ``` 如此一來就順利讀取到我們想要的開發板了 ``` $ st-info --probe Found 1 stlink programmers version: V2J17 serial: 303030303030303030303031 flash: 2097152 (pagesize: 16384) sram: 262144 chipid: 0x419 dev-type: STM32F42x_F43x ``` ### 燒錄測試 可透過下方連結取得完整程式碼 ```bash git clone https://gist.github.com/kk908676/1128f48c35bb4b89933ba319b4a282e2 ``` - [ ] `blink.c` ```c #define RCC_AHB1_PERI_ENBLR_ADDR (*((volatile unsigned long*) (0x40023800 + 0x30) )) #define GPIO_G_PORTMODE (*((volatile unsigned long*) 0x40021800 )) #define GPIO_G_OUTPUT_TYPE (*((volatile unsigned long*) (0x40021800 + 0x4 ) )) #define GPIO_G_PUPD (*((volatile unsigned long*) (0x40021800 + 0xC ) )) #define GPIO_G_PORT_SETRESET (*((volatile unsigned long*) (0x40021800 + 0x18) )) asm(".word 0x20001000"); asm(".word main "); int main() { int i; RCC_AHB1_PERI_ENBLR_ADDR |= (0x00000001 << 6); /*Enalbe GPIO G clock*/ GPIO_G_PORTMODE |= (0x00000001 << 26 | 0x00000001 << 28); /* Set PG13 PG14 mode to GPIO */ GPIO_G_PORTMODE &= ~(0x00000001 << 27 | 0x00000001 << 29); GPIO_G_OUTPUT_TYPE &= ~(0x00000001 << 13 | 0x00000001 << 14); /*Set PG13 PG14 to push-pull*/ GPIO_G_PUPD |= (0x00000001 << 27 | 0x00000001 << 29); /*Set PG13 PG14 to pull-down*/ while(1) { GPIO_G_PORT_SETRESET = 0x20004000; /*PG13 off, PG14 on*/ for(i = 0 ; i < 100000 ; i++); GPIO_G_PORT_SETRESET = 0x40002000; /*PG14 off, PG13 on*/ for(i = 0 ; i < 100000 ; i++); } return 0; } ``` - [ ] `Makefile` ```shell CROSS_COMPILE ?= arm-none-eabi- .PHONY: all all: blink.bin blink.o: blink.c $(CROSS_COMPILE)gcc -mcpu=cortex-m4 -mthumb -nostartfiles -c blink.c -o blink.o blink.out: blink.o blink.ld $(CROSS_COMPILE)ld -T blink.ld -o blink.out blink.o blink.bin: blink.out $(CROSS_COMPILE)objcopy -j .text -O binary blink.out blink.bin clean: rm -rf *.o *.out *.bin ``` - [ ] `blink.ld` ```c SECTIONS { . = 0x0; .text : { *(.text) } } ``` 編譯完後會產生 `blink.out` 及 `blink.bin` ,將 `blink.bin` 燒進 flash ``` $ st-flash --reset write blink.bin 0x08000000 st-flash 1.8.0 2025-03-13T16:45:58 INFO common_legacy.c: STM32F4x5_F4x7: 192 KiB SRAM, 1024 KiB flash in at least 16 KiB pages. file blink.bin md5 checksum: 4bb06e5d434b414b47c0f421f60e795, stlink checksum: 0x00002cae 2025-03-13T16:45:58 INFO common_flash.c: Attempting to write 160 (0xa0) bytes to stm32 address: 134217728 (0x8000000) 2025-03-13T16:45:58 INFO flash_loader.c: Starting Flash write for F2/F4/F7/L4 2025-03-13T16:45:58 INFO flash_loader.c: Successfully loaded flash loader in sram 2025-03-13T16:45:58 INFO flash_loader.c: Clear DFSR 2025-03-13T16:45:58 INFO flash_loader.c: Clear CFSR 2025-03-13T16:45:58 INFO flash_loader.c: Clear HFSR 2025-03-13T16:45:58 INFO flash_loader.c: enabling 32-bit flash writes 2025-03-13T16:45:58 INFO common_flash.c: Starting verification of write complete 2025-03-13T16:45:58 INFO common_flash.c: Flash written and verified! jolly good! ``` 燒錄後按 RESET 即可看到 `LD3(綠)`、`LD4(紅)` 開始交替閃爍 燒進 flash 可能會遇到寫入保護( Write Protection )鎖定 , 因此要先解除保護: ``` $ st-flash erase ``` 之後再按照上方操作即可 ### Boot loader 當處理器啟動時,記憶體中尚未載入作業系統,因此需要一段特殊的軟體來將作業系統從儲存媒體中載入至記憶體。這段軟體通常是一小段稱為「開機載入器(boot loader)」的程式碼。 在現代電腦系統中,開機載入器通常與UEFI(統一可延伸韌體介面)合作運作,並從使用GUID分割表(GPT, GUID Partition Table)的磁碟架構中讀取作業系統的相關資料。與傳統BIOS與MBR(主開機記錄區)架構不同,UEFI與GPT提供了更大的磁碟支援、更高的可靠性,以及更彈性的系統啟動流程 #### U-Boot 是一個常用於嵌入式系統的開機載入器(bootloader) - 啟動時,U-Boot 負責: 1. 初始化 CPU 和記憶體(如 DDR/SDRAM) 2. 設定時鐘、中斷控制器、UART 等周邊 3. 載入作業系統(如 Linux kernel)到記憶體 4. 傳遞啟動參數(例如 device tree 與 initrd)給 kernel - 可透過 串列埠提供 U-Boot Shell,進行手動載入或除錯 - 支援多種啟動來源(如 NOR/NAND Flash、SD 卡、SPI Flash、USB、TFTP 等) 下載 U-boot 原始碼: ```bash git clone git://git.denx.de/u-boot.git cd u-boot ``` 編譯 U-boot : ```bash= make mrproper make stm32f429-discovery_defconfig # make menuconfig make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j12 ``` - 第一行是移除之前編譯所產生的文件、配置、備份 - 第二行是生成一個 .config 預設配置 - 第三行是用於更改預設配置,因此它會修改產生的 .config - 第四行直接開始編譯 U-Boot 原始碼,目標檔案是 u-boot.bin 燒進 FLASH ```bash $ st-flash write u-boot.bin 0x08000000 st-flash 1.8.0-99-gb149e24 2025-05-04T15:53:54 INFO common_legacy.c: STM32F42x_F43x: 256 KiB SRAM, 2048 KiB flash in at least 16 KiB pages. file u-boot.bin md5 checksum: d14f49e335e81f6078882ae9ed546dac, stlink checksum: 0x00d07b04 2025-05-04T15:53:54 INFO common_flash.c: Attempting to write 169436 (0x295dc) bytes to stm32 address: 134217728 (0x8000000) EraseFlash - Sector:0x0 Size:0x4000 -> Flash page at 0x8000000 erased (size: 0x4000) EraseFlash - Sector:0x1 Size:0x4000 -> Flash page at 0x8004000 erased (size: 0x4000) EraseFlash - Sector:0x2 Size:0x4000 -> Flash page at 0x8008000 erased (size: 0x4000) EraseFlash - Sector:0x3 Size:0x4000 -> Flash page at 0x800c000 erased (size: 0x4000) EraseFlash - Sector:0x4 Size:0x10000 -> Flash page at 0x8010000 erased (size: 0x10000) EraseFlash - Sector:0x5 Size:0x20000 -> Flash page at 0x8020000 erased (size: 0x20000) 2025-05-04T15:53:59 INFO flash_loader.c: Starting Flash write for F2/F4/F7/L4 2025-05-04T15:53:59 INFO flash_loader.c: Successfully loaded flash loader in sram 2025-05-04T15:53:59 INFO flash_loader.c: Clear DFSR 2025-05-04T15:53:59 INFO flash_loader.c: Clear CFSR 2025-05-04T15:53:59 INFO flash_loader.c: Clear HFSR 2025-05-04T15:53:59 INFO flash_loader.c: enabling 32-bit flash writes 2025-05-04T15:54:01 INFO common_flash.c: Starting verification of write complete 2025-05-04T15:54:03 INFO common_flash.c: Flash written and verified! jolly good! 2025-05-04T15:54:03 INFO common_legacy.c: Go to Thumb mode ``` #### USART Connection - The STM32F429 Discovery is equipped with various USARTs. USART stands for Universal Synchronous Asynchronous Receiver Transmitter. The USARTs on the STM32F429 support a wide range of serial protocols, the usual asynchronous ones, plus things like IrDA, SPI etc. Since the STM32 works on 3.3V levels, a level shifting component is needed to connect the USART of the STM32F429 to a PC serial port. | STM32 PIN | VCP | | --------- | ---- | | PA9 | USART1_TX | | PA10 | USART1_RX | USB to TTL 接線: - PA9 $\to$ RXD - PA10 $\to$ TXD - GND $\to$ GND ![image](https://hackmd.io/_uploads/SJkzQZmrA.png =375x225) ```shell $ sudo dmesg | grep tty [ 0.222738] printk: legacy console [tty0] enabled [18797.223501] usb 1-11: pl2303 converter now attached to ttyUSB0 $ sudo chmod 666 /dev/ttyUSB0 ``` 安裝 `kermit` ``` $ sudo apt-get install ckermit ``` 可以從 [官方文件中](https://github.com/u-boot/u-boot/blob/master/drivers/serial/Kconfig) 得知 **Baud Rate** 預設為 `115200` ,**UART number** 預設為 `1` ``` config BAUDRATE int "Default baudrate" default 115200 help Select a default baudrate, where "default" has a driver-specific meaning of either setting the baudrate for the early debug UART in the SPL stage (most drivers) or for choosing a default baudrate in the absence of an environment setting (serial_mxc.c). config CONS_INDEX int "UART used for console" depends on SPECIFY_CONSOLE_INDEX range 0 6 default 1 help Set this to match the UART number of the serial console. ``` 設定與開發板溝通的基本參數 輸入 `vi ~/.kermrc` ``` set line /dev/ttyUSB0 set speed 115200 set carrier-watch off set handshake none set flow-control none robust set file type binary set file name literal set receive packet-length 1000 set send packet-length 1000 set window 5 ``` 接著輸入 `kermit` 就可以看到 ``` $ kermit C-Kermit 10.0 pre-Beta.11, 06 Feb 2024, for Linux+SSL (64-bit) Copyright (C) 1985, 2024, Trustees of Columbia University in the City of New York. Open Source 3-clause BSD license since 2011. Type ? or HELP for help. (~/Desktop/) C-Kermit> ``` 因為剛剛已經設定好了一些基本的參數,這時只要在命令列輸入 `c` 且按下開發板上的 RESET 就可以看到 ``` (~/Desktop/) C-Kermit>c Connecting to /dev/ttyUSB1, speed 115200 Escape character: Ctrl-\ (ASCII 28, FS): enabled Type the escape character followed by C to get back, or followed by ? to see other options. ---------------------------------------------------- U-Boot 2025.04-rc4-00008-g15d6518c942f (Mar 17 2025 - 16:28:20 +0800) DRAM: 8 MiB Core: 73 devices, 11 uclasses, devicetree: separate Flash: 2 MiB Loading Environment from Flash... *** Warning - bad CRC, using default environment In: serial@40011000 Out: serial@40011000 Err: serial@40011000 U-Boot > help ? - alias for 'help' base - print or set address offset bdinfo - print Board Info structure boot - boot default, i.e., run 'bootcmd' bootd - boot default, i.e., run 'bootcmd' bootelf - Boot from an ELF image in memory bootm - boot application image from memory bootvx - Boot vxWorks from an ELF image cmp - memory compare coninfo - print console devices and information cp - memory copy crc32 - checksum calculation dm - Driver model low level access echo - echo args to console editenv - edit environment variable env - environment handling commands erase - erase FLASH memory exit - exit script false - do nothing, unsuccessfully fdt - flattened device tree utility commands flinfo - print FLASH memory information go - start application at address 'addr' help - print command description/usage iminfo - print header information for application image imls - list all images found in flash imxtract - extract a part of a multi-image itest - return true/false on integer compare loadb - load binary file over serial line (kermit mode) loads - load S-Record file over serial line loadx - load binary file over serial line (xmodem mode) loady - load binary file over serial line (ymodem mode) loop - infinite loop on address range md - memory display mm - memory modify (auto-incrementing address) mw - memory write (fill) nm - memory modify (constant address) panic - Panic with optional message pinmux - show pin-controller muxing printenv - print environment variables protect - enable or disable FLASH write protection reset - Perform RESET of the CPU run - run commands in an environment variable saveenv - save environment variables to persistent storage setenv - set environment variables showvar - print local hushshell variables sleep - delay execution for some time source - run script from memory test - minimal test like /bin/sh timer - access the system timer true - do nothing, successfully version - print monitor, compiler and linker version U-Boot > ``` `version` -> 顯示目前 U-Boot 版本 ``` U-Boot > version U-Boot 2024.07-rc3-00020-gea722aa5eb (Jun 09 2024 - 22:25:14 +0800) ``` `bdinfo` -> 顯示目前板子資訊 ``` U-Boot > bdinfo boot_params = 0x00000000 DRAM bank = 0x00000000 -> start = 0x90000000 -> size = 0x00800000 flashstart = 0x08000000 flashsize = 0x00200000 flashoffset = 0x000241b8 baudrate = 115200 bps relocaddr = 0x907ca000 reloc off = 0x887ca000 Build = 32-bit fdt_blob = 0x905c2cb0 new_fdt = 0x905c2cb0 fdt_size = 0x00005220 lmb_dump_all: memory.cnt = 0x1 / max = 0x10 memory[0] [0x90000000-0x907fffff], 0x00800000 bytes flags: 0 reserved.cnt = 0x1 / max = 0x10 reserved[0] [0x905be9a8-0x907fffff], 0x00241658 bytes flags: 0 devicetree = separate arch_number = 0x00000000 TLB addr = 0x907f0000 irq_sp = 0x905c2ca0 sp start = 0x905c2c90 Early malloc usage: f40 / 2000 ``` ### Kernel 核心編譯 Linux-6.14.0 ```bash git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git -b linux-6.14.y --depth 1 ``` #### Menuconfig & Kconfig 詳細內容請參閱核心程式碼目錄下的檔案 ./Documentation/kbuild/kconfig-language.rst 使用預設 `.config` 檔 ``` cd linux/ make ARCH=arm stm32_defconfig ``` #### Menu Config 用來對剛剛已經產生的 `.config` 再做一些調整 ```bash make ARCH=arm menuconfig ``` 先檢查是否有安裝 `libncurses5-dev` ``` sudo apt-get install libncurses5-dev ``` 之後可以看到以下畫面 ![Screenshot from 2025-03-22 17-15-41](https://hackmd.io/_uploads/Hy7AkW3nJe.png) #### Initramfs ##### 方法一(較舊) 他是在啟動階段被 `Linux` 核心呼叫的臨時檔案系統,用於根目錄被掛載之前的準備工作, 這裡用 [Embedded Linux Wiki](https://elinux.org/File:Stm32_mini_rootfs.cpio.bz2#filelinks) 所提供的 `Stm32 mini rootfs.cpio.bz2` 下載之後先解壓縮成 `cpio` 檔 : ``` bzip2 -d Stm32_mini_rootfs.cpio.bz2 ``` 接下來建立 `rootfs` 目錄,作為之後 `Linux` 啟動時的臨時檔案系統: ``` mkdir rootfs cd rootfs ``` 將下載的檔案解壓縮到 `rootfs` 中 ``` cpio -idmv < ../Stm32_mini_rootfs.cpio ``` 確認 `dev` 中是否有 `null` : 特殊裝置,所有寫入的資料都會被丟棄,用於丟棄輸出或忽略訊息 `tty0` : 目前活動中的虛擬主控台( `active virtual console` ),系統訊息會顯示在目前啟用的終端上 `console` : 系統的核心輸出通道,用於顯示開機訊息與核心錯誤,會對應到具體的終端裝置(如 `ttyS0` 或 `tty0` ) 如果沒有就需要手動加入 ``` sudo mknod console c 5 1 sudo mknod null c 1 3 sudo mknod tty0 c 204 64 ``` 最後在 `rootfs` 新增 `init` 檔案,用來手動掛載 `/dev` 目錄並初始化裝置節點,然後將標準輸入、輸出和錯誤輸出重定向到終端機,最後執行系統的初始化程式 `/sbin/init` ```bash #!/bin/sh # devtmpfs does not get automounted for initramfs /bin/mount -t devtmpfs devtmpfs /dev exec 0</dev/console exec 1>/dev/console exec 2>/dev/console exec /sbin/init $ ``` 最後再將此 `rootfs` 壓縮成 `rootfs.cpio` ``` find . | cpio -o -H newc > ../rootfs.cpio ``` ##### 方法二(較新) 下載 [Linux on the STM32F429I Discovery board with Buildroot](https://github.com/winfred-lu/STM32F429I-disco_Buildroot) 專案 ```bash git clone https://github.com/winfred-lu/STM32F429I-disco_Buildroot.git ``` 接著依序執行以下命令 ``` make bootstrap make defconfig make build ``` 執行完後在 `buildroot/output/images/` 看到我們需要的 `rootfs.cpio` `rootfs` 準備好了之後要在 `menuconfig` 設定 `rootfs` 的來源位置, 這樣核心可以在啟動時加載它作為初始 `rootfs` 位置 : General setup $\to$ Initial RAM filesystem and RAM disk (initramfs/initrd) support ```bash [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support (/home/an/Downloads/rootfs.cpio) Initramfs source file(s) ``` ![image](https://hackmd.io/_uploads/SJdSznExel.png) 編譯核心 : ``` make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j12 ``` 編譯 `Linux` 核心並產生可啟動的核心映像 `uImage` : (你會在 `linux/arch/arm/boot/` 看到 `uImage` , `uImage` 是 `U-Boot` 用來引導核心的映像格式) ``` $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- uImage LOADADDR=0x90008000 -j12 CALL scripts/checksyscalls.sh Kernel configured for XIP (CONFIG_XIP_KERNEL=y) Only the xipImage target is available in this case make[2]: *** [arch/arm/boot/Makefile:45: arch/arm/boot/Image] Error 1 make[1]: *** [arch/arm/Makefile:300: Image] Error 2 make: *** [Makefile:251: __sub-make] Error 2 ``` 因為預設的 kernel 設定中啟用了 XIP (Execute In Place) 模式,導致無法產生一般的 uImage, 只能建立 xipImage, 所以需要關閉 XIP 設定 位置 : Boot options $\to$ Kernel Execute-In-Place from ROM ![Screenshot from 2025-03-25 17-13-19](https://hackmd.io/_uploads/rylZHex6yx.png) 如畫面中關閉之後再執行 ``` make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- uImage LOADADDR=0x90008000 -j12 ``` :::warning TODO: 比照 [stm32f429-linux-builder](https://github.com/jserv/stm32f429-linux-builder/blob/master/Makefile),藉由 GNU make (或其他建構工具),將上述操作予以自動化。 ::: 編譯裝置樹二進制檔案 : (你會在 `linux/arch/arm/boot/dts/st/` 看到 `stm32f429-disco.dtb` 裝置樹描述了周邊硬體的組成方式,並告訴核心如何操作這些裝置,因此在編譯核心時需要產生對應的裝置樹檔案,這樣核心才能正確識別並配置周邊硬體) ``` make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- dtbs ``` 等待這幾個核心檔案都產生之後, 接下來就是放入到 `SDRAM` 之後核心才能做後續相對應的硬體和裝置的初始化 從剛剛 `U-Boot` 所產生的資訊來看, `SDRAM` 的起始位置是 `0x90000000` 大小是 `8MB` ``` U-Boot > bdinfo boot_params = 0x00000000 DRAM bank = 0x00000000 -> start = 0x90000000 -> size = 0x00800000 flashstart = 0x08000000 flashsize = 0x00200000 flashoffset = 0x000241b8 baudrate = 115200 bps relocaddr = 0x907ca000 reloc off = 0x887ca000 Build = 32-bit fdt_blob = 0x905c2cb0 new_fdt = 0x905c2cb0 fdt_size = 0x00005220 ``` 因此我們使用 `loadb` 將 `uImage` 傳入位置 `0x90100000` ``` U-Boot 2025.04-rc4-00008-g15d6518c942f (Mar 17 2025 - 16:28:20 +0800) DRAM: 8 MiB Core: 73 devices, 11 uclasses, devicetree: separate Flash: 2 MiB Loading Environment from Flash... OK In: serial@40011000 Out: serial@40011000 Err: serial@40011000 U-Boot > <INTERRUPT> U-Boot > <INTERRUPT> U-Boot > loadb 90100000 ## Ready for binary (kermit) download to 0x90100000 at 115200 bps... ``` 可以看到在等待檔案, 接著輸入 `Ctrl + \` + `c` 來回到命令模式並傳送 `uImage` ,待傳完之後打 `c` 回到 `U-Boot` ``` (Back at an-Pro-E500-G6-WS720T) ---------------------------------------------------- (~/Desktop/) C-Kermit>send linux/arch/arm/boot/uImage (~/Desktop/) C-Kermit>c Connecting to /dev/ttyUSB1, speed 115200 Escape character: Ctrl-\ (ASCII 28, FS): enabled Type the escape character followed by C to get back, or followed by ? to see other options. ---------------------------------------------------- ## Total Size = 0x001b48d8 = 1788120 Bytes ## Start Addr = 0x90100000 ``` 接下來將 `dtb` 傳入位置 `0x90500000` ``` U-Boot > loadb 90500000 ## Ready for binary (kermit) download to 0x90500000 at 115200 bps... (Back at an-Pro-E500-G6-WS720T) ---------------------------------------------------- (~/Desktop/) C-Kermit>send linux/arch/arm/boot/dts/st/stm32f429-disco.dtb (~/Desktop/) C-Kermit>c Connecting to /dev/ttyUSB1, speed 115200 Escape character: Ctrl-\ (ASCII 28, FS): enabled Type the escape character followed by C to get back, or followed by ? to see other options. ---------------------------------------------------- ## Total Size = 0x00004c90 = 19600 Bytes ## Start Addr = 0x90500000 ``` 最後在 `U-Boot` 設定核心的啟動參數 ``` U-Boot > setenv bootargs 'console=ttySTM0,115200 root=/dev/ram earlyprintk' U-Boot > bootm 90100000 - 90500000 ``` 就可以看到成功啟動了 `linux` ``` ## Booting kernel from Legacy Image at 90100000 ... Image Name: Linux-6.14.0-rc7-00179-gb3ee1e46 Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 1788056 Bytes = 1.7 MiB Load Address: 90008000 Entry Point: 90008001 Verifying Checksum ... OK ## Flattened Device Tree blob at 90500000 Booting using the fdt blob at 0x90500000 Working FDT set to 90500000 Loading Kernel Image to 90008000 Loading Device Tree to 905b7000, end 905bec8f ... OK Working FDT set to 905b7000 Starting kernel ... [ 0.000000] Booting Linux on physical CPU 0x0 [ 0.000000] Linux version 6.14.0-rc7-00179-gb3ee1e460951-dirty (an@an-Pro-E500-G6-WS720T) (arm-linux-gnueabi-gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #18 PREEMPT Sat Mar 22 15:48:05 CST 2025 [ 0.000000] CPU: ARMv7-M [410fc241] revision 1 (ARMv7M), cr=00000000 [ 0.000000] CPU: unknown data cache, unknown instruction cache [ 0.000000] OF: fdt: Machine model: STMicroelectronics STM32F429i-DISCO board [ 0.000000] printk: legacy bootconsole [earlycon0] enabled [ 0.000000] Zone ranges: [ 0.000000] Normal [mem 0x0000000090000000-0x00000000907fffff] [ 0.000000] Movable zone start for each node [ 0.000000] Early memory node ranges [ 0.000000] node 0: [mem 0x0000000090000000-0x00000000907fffff] [ 0.000000] Initmem setup node 0 [mem 0x0000000090000000-0x00000000907fffff] [ 0.000000] OF: reserved mem: Reserved memory: No reserved-memory node in the DT [ 0.000000] Kernel command line: console=ttySTM0,115200 root=/dev/ram earlyprintk [ 0.000000] printk: log buffer data + meta data: 65536 + 204800 = 270336 bytes [ 0.000000] Dentry cache hash table entries: 1024 (order: 0, 4096 bytes, linear) [ 0.000000] Inode-cache hash table entries: 1024 (order: 0, 4096 bytes, linear) [ 0.000000] Built 1 zonelists, mobility grouping off. Total pages: 2048 [ 0.000000] mem auto-init: stack:all(zero), heap alloc:off, heap free:off [ 0.000000] SLUB: HWalign=8, Order=0-3, MinObjects=0, CPUs=1, Nodes=1 [ 0.000000] rcu: Preemptible hierarchical RCU implementation. [ 0.000000] rcu: RCU event tracing is enabled. [ 0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 10 jiffies. [ 0.000000] NR_IRQS: 16, nr_irqs: 16, preallocated irqs: 16 [ 0.000000] /soc/interrupt-controller@40013c00: bank0 [ 0.000000] rcu: srcu_init: Setting srcu_struct sizes based on contention. [ 0.000000] clocksource: arm_system_timer: mask: 0xffffff max_cycles: 0xffffff, max_idle_ns: 331816030 ns [ 0.000000] ARM System timer initialized as clocksource [ 0.000036] sched_clock: 32 bits at 90MHz, resolution 11ns, wraps every 23860929530ns [ 0.009485] timers@40000c00: STM32 sched_clock registered [ 0.016161] Switching to timer-based delay loop, resolution 11ns [ 0.023345] timers@40000c00: STM32 delay timer registered [ 0.029965] clocksource: timers@40000c00: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 21236227187 ns [ 0.041948] /soc/timers@40000c00: STM32 clockevent driver initialized (32 bits) [ 0.058845] Calibrating delay loop (skipped), value calculated using timer frequency.. 180.00 BogoMIPS (lpj=900000) [ 0.071494] pid_max: default: 4096 minimum: 301 [ 0.080117] Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear) [ 0.089280] Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear) [ 0.157293] rcu: Hierarchical SRCU implementation. [ 0.163581] rcu: Max phase no-delay instances is 1000. [ 0.188171] Memory: 4588K/8192K available (1677K kernel code, 272K rwdata, 628K rodata, 240K init, 102K bss, 3240K reserved, 0K cma-reserved) [ 0.209267] devtmpfs: initialized [ 0.462273] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns [ 0.474441] pinctrl core: initialized pinctrl subsystem [ 0.756757] /soc/spi@40015000/display@1: Fixed dependency cycle(s) with /soc/display-controller@40016800 [ 0.769703] /soc/display-controller@40016800: Fixed dependency cycle(s) with /soc/spi@40015000/display@1 [ 0.830969] /soc/spi@40015000/display@1: Fixed dependency cycle(s) with /soc/display-controller@40016800 [ 0.853089] /soc/spi@40015000/display@1: Fixed dependency cycle(s) with /soc/display-controller@40016800 [ 0.869242] /soc/display-controller@40016800: Fixed dependency cycle(s) with /soc/spi@40015000/display@1 [ 0.956606] stm32f429-pinctrl soc:pinctrl@40020000: GPIOA bank added [ 0.976370] stm32f429-pinctrl soc:pinctrl@40020000: GPIOB bank added [ 1.000595] stm32f429-pinctrl soc:pinctrl@40020000: GPIOC bank added [ 1.025950] stm32f429-pinctrl soc:pinctrl@40020000: GPIOD bank added [ 1.048708] stm32f429-pinctrl soc:pinctrl@40020000: GPIOE bank added [ 1.072381] stm32f429-pinctrl soc:pinctrl@40020000: GPIOF bank added [ 1.097161] stm32f429-pinctrl soc:pinctrl@40020000: GPIOG bank added [ 1.123180] stm32f429-pinctrl soc:pinctrl@40020000: GPIOH bank added [ 1.149769] stm32f429-pinctrl soc:pinctrl@40020000: GPIOI bank added [ 1.177386] stm32f429-pinctrl soc:pinctrl@40020000: GPIOJ bank added [ 1.200420] stm32f429-pinctrl soc:pinctrl@40020000: GPIOK bank added [ 1.209116] stm32f429-pinctrl soc:pinctrl@40020000: Pinctrl STM32 initialized [ 1.326514] stm32-dma 40026000.dma-controller: STM32 DMA driver registered [ 1.380118] stm32-dma 40026400.dma-controller: STM32 DMA driver registered [ 1.400838] vcc5v-otg-regulator enforce active low on GPIO handle [ 1.435452] clocksource: Switched to clocksource timers@40000c00 [ 1.544632] workingset: timestamp_bits=30 max_order=11 bucket_order=0 [ 1.559542] io scheduler mq-deadline registered [ 1.565121] io scheduler kyber registered [ 1.640383] io scheduler bfq registered [ 2.412772] STM32 USART driver initialized [ 2.454646] 40011000.serial: ttySTM0 at MMIO 0x40011000 (irq = 47, base_baud = 5625000) is a stm32-usart [ 2.487278] printk: legacy console [ttySTM0] enabled [ 2.487278] printk: legacy console [ttySTM0] enabled [ 2.514769] printk: legacy bootconsole [earlycon0] disabled [ 2.514769] printk: legacy bootconsole [earlycon0] disabled [ 2.578654] stm32_rtc 40002800.rtc: registered as rtc0 [ 2.586152] stm32_rtc 40002800.rtc: setting system clock to 2000-01-01T02:29:39 UTC (946693779) [ 2.601124] stm32_rtc 40002800.rtc: Date/Time must be initialized [ 2.615053] i2c_dev: i2c /dev entries driver [ 2.652100] stmpe-i2c 0-0041: stmpe811 detected, chip id: 0x811 [ 2.701109] stm32f4-i2c 40005c00.i2c: STM32F4 I2C driver registered [ 3.156715] input: gpio-keys as /devices/platform/gpio-keys/input/input0 [ 3.188285] clk: Disabling unused clocks [ 3.228156] Freeing unused kernel image (initmem) memory: 240K [ 3.235916] This architecture does not have kernel memory protection. [ 3.243884] Run /init as init process [ 3.250807] Failed to execute /init (error -13) [ 3.257230] Run /sbin/init as init process / # ls bin dev etc init linuxrc proc sbin sys usr ``` :::warning TODO: 改用 [winfred-lu/STM32F429I-disco_Buildroot](https://github.com/winfred-lu/STM32F429I-disco_Buildroot) TODO: 參照 [MartinK7/stm32f429-disco-running-linux](https://github.com/MartinK7/stm32f429-disco-running-linux),以輕量級的 afboot-stm32 取代 u-boot,並使用 SD card (免除燒錄韌體) ::: --- ### TODO 1. 重建 rootfs,使用 [winfred-lu/STM32F429I-disco_Buildroot](https://github.com/winfred-lu/STM32F429I-disco_Buildroot),預期 `buildroot/output/images` 目錄可見 `rootfs.cpio` 2. 原本使用 u-boot,可改用 [afboot-stm32](https://github.com/mcoquelin-stm32/afboot-stm32),好處是精簡、可追蹤開機流程 3. 運用 [LTO](https://lwn.net/Articles/744507/) 縮減 kernel image size 4. 準備用 SD card reader --- ### 使用 afboot-stm32 取代 u-boot 下載 afboot-stm32 ```bash git clone https://github.com/mcoquelin-stm32/afboot-stm32.git ``` 直接編譯後會發現這個問題 ``` $ make stm32f429i-disco arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m4 -ffunction-sections -fdata-sections -Os -std=gnu99 -Wall -DKERNEL_ADDR=0x08008000 -DDTB_ADDR=0x08004000 stm32f429i-disco.c -o stm32f429i-disco.o arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m4 -ffunction-sections -fdata-sections -Os -std=gnu99 -Wall -DKERNEL_ADDR=0x08008000 -DDTB_ADDR=0x08004000 gpio.c -o gpio.o arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m4 -ffunction-sections -fdata-sections -Os -std=gnu99 -Wall -DKERNEL_ADDR=0x08008000 -DDTB_ADDR=0x08004000 mpu.c -o mpu.o arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m4 -ffunction-sections -fdata-sections -Os -std=gnu99 -Wall -DKERNEL_ADDR=0x08008000 -DDTB_ADDR=0x08004000 qspi.c -o qspi.o arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m4 -ffunction-sections -fdata-sections -Os -std=gnu99 -Wall -DKERNEL_ADDR=0x08008000 -DDTB_ADDR=0x08004000 start_kernel.c -o start_kernel.o arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m4 -ffunction-sections -fdata-sections -Os -std=gnu99 -Wall -DKERNEL_ADDR=0x08008000 -DDTB_ADDR=0x08004000 usart-f4.c -o usart-f4.o arm-none-eabi-ld -T stm32f429.lds -nostartfiles --gc-sections -o stm32f429i-disco.elf stm32f429i-disco.o gpio.o mpu.o qspi.o start_kernel.o usart-f4.o arm-none-eabi-ld: Error: unable to disambiguate: -nostartfiles (did you mean --nostartfiles ?) make: *** [Makefile:28: stm32f429i-disco] Error 1 ``` 這是因為鏈接器從 `gcc` 改為直接使用 `ld`,但 `ld` 的新版本不再接受 `-nostartfiles` 選項,導致編譯錯誤( `Error: unable to disambiguate: -nostartfiles` ) 修改 Makefile ```diff - LINKERFLAGS := -nostartfiles --gc-sections + LINKERFLAGS := --gc-sections ``` 修改完後又出現了另一個問題 ``` $ make stm32f429i-disco arm-none-eabi-ld -T stm32f429.lds --gc-sections -o stm32f429i-disco.elf stm32f429i-disco.o gpio.o mpu.o qspi.o start_kernel.o usart-f4.o arm-none-eabi-ld: stm32f429i-disco.o: in function `reset': stm32f429i-disco.c:(.text.reset+0x1a): undefined reference to `memcpy' arm-none-eabi-ld: stm32f429i-disco.c:(.text.reset+0x34): undefined reference to `memset' make: *** [Makefile:28: stm32f429i-disco] Error 1 ``` 因為在 `gcc` 編譯時,編譯器會將某些 `hand-written code` (例如看起來像 `memcpy` 或 `memset` 的迴圈)最佳化為對 C 標準庫函數的呼叫。但是, 在 `bare-metal` 環境中, 這些標準庫函數不可用,導致鏈接錯誤( `undefined reference to memcpy` 和 `memset` ) 再對 Makefile 做以下修改 ```diff CFLAGS := -mthumb -mcpu=cortex-m4 CFLAGS += -ffunction-sections -fdata-sections CFLAGS += -Os -std=gnu99 -Wall +CFLAGS += -fno-builtin LINKERFLAGS := -nostartfiles --gc-sections ``` 修改完之後執行 ``` $ make clean $ make stm32f429i-disco arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m4 -ffunction-sections -fdata-sections -Os -std=gnu99 -Wall -fno-builtin -DKERNEL_ADDR=0x08008000 -DDTB_ADDR=0x08004000 stm32f429i-disco.c -o stm32f429i-disco.o arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m4 -ffunction-sections -fdata-sections -Os -std=gnu99 -Wall -fno-builtin -DKERNEL_ADDR=0x08008000 -DDTB_ADDR=0x08004000 gpio.c -o gpio.o arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m4 -ffunction-sections -fdata-sections -Os -std=gnu99 -Wall -fno-builtin -DKERNEL_ADDR=0x08008000 -DDTB_ADDR=0x08004000 mpu.c -o mpu.o arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m4 -ffunction-sections -fdata-sections -Os -std=gnu99 -Wall -fno-builtin -DKERNEL_ADDR=0x08008000 -DDTB_ADDR=0x08004000 qspi.c -o qspi.o arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m4 -ffunction-sections -fdata-sections -Os -std=gnu99 -Wall -fno-builtin -DKERNEL_ADDR=0x08008000 -DDTB_ADDR=0x08004000 start_kernel.c -o start_kernel.o arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m4 -ffunction-sections -fdata-sections -Os -std=gnu99 -Wall -fno-builtin -DKERNEL_ADDR=0x08008000 -DDTB_ADDR=0x08004000 usart-f4.c -o usart-f4.o arm-none-eabi-ld -T stm32f429.lds --gc-sections -o stm32f429i-disco.elf stm32f429i-disco.o gpio.o mpu.o qspi.o start_kernel.o usart-f4.o arm-none-eabi-objcopy -Obinary stm32f429i-disco.elf stm32f429i-disco.bin arm-none-eabi-size stm32f429i-disco.elf text data bss dec hex filename 1708 0 0 1708 6ac stm32f429i-disco.elf ``` 就會看到目錄中多了 `stm32f429i-disco.bin` ,之後將它燒入開發板中 ``` $ st-flash write stm32f429i-disco.bin 0x08000000 st-flash 1.8.0-99-gb149e24 2025-05-04T19:43:05 INFO common_legacy.c: STM32F42x_F43x: 256 KiB SRAM, 2048 KiB flash in at least 16 KiB pages. file stm32f429i-disco.bin md5 checksum: b27515311742bd4736eec8ee740f828, stlink checksum: 0x00033fd4 2025-05-04T19:43:05 INFO common_flash.c: Attempting to write 2258 (0x8d2) bytes to stm32 address: 134217728 (0x8000000) EraseFlash - Sector:0x0 Size:0x4000 -> Flash page at 0x8000000 erased (size: 0x4000) 2025-05-04T19:43:05 INFO flash_loader.c: Starting Flash write for F2/F4/F7/L4 2025-05-04T19:43:05 INFO flash_loader.c: Successfully loaded flash loader in sram 2025-05-04T19:43:05 INFO flash_loader.c: Clear DFSR 2025-05-04T19:43:05 INFO flash_loader.c: Clear CFSR 2025-05-04T19:43:05 INFO flash_loader.c: Clear HFSR 2025-05-04T19:43:05 INFO flash_loader.c: enabling 32-bit flash writes 2025-05-04T19:43:05 INFO common_flash.c: Starting verification of write complete 2025-05-04T19:43:05 INFO common_flash.c: Flash written and verified! jolly good! ``` ### 改用 neocon 簡化操作 下載 neocon 原始碼 ```bash git clone https://github.com/sysprog21/neocon.git ``` 進入 neocon 執行 ``` make ``` 之後依照自己的板子的 `ttyUSB*` 做執行 (* 替換為數字) ```shell $ sudo chmod 666 /dev/ttyUSB* ./neocon /dev/ttyUSB* ``` ### Buildroot Buildroot 是一個用於建立嵌入式 Linux 系統的建構框架,它可以用來自動化建構包含 `root filesystem` 、`toolchain` 、 `bootloader` 、 `Linux kernel` 及應用程式等元件的整個系統映像。整個 Buildroot 系統是透過 `Kconfig` 和 `Makefile` 進行配置,使用方式與編譯 Linux kernel 類似,可以透過 `menuconfig` 等工具進行設定。雖然 Buildroot 通常會搭配 Linux kernel 使用,但它也可以在沒有 kernel 的情況下建立檔案系統,例如用於 `bare-metal` 環境 #### 為什麼需要Buildroot ? 當我們編譯 `Linux` 系統時,往往需要自行準備交叉編譯器(cross-compiler)、解決套件編譯相依性、手動建構 kernel、rootfs 和 boot loader,過程繁瑣易出錯。 `Buildroot` 正是為了簡化這一流程而設計的工具。它提供一個整合式的建構系統,讓你可以: - 選擇目標平台與 kernel 版本 - 自動下載與編譯 BusyBox 、 toolchain 、 bootloader 等元件 - 建立最小可運作的 root filesystem(initramfs 或獨立掛載) - 透過 `menuconfig` 介面進行自定義設定 - 透過幾個命令自動化建構整個可開機的 Linux 映像 適合嵌入式開發,能大幅減少設定錯誤與整合時間 下載 Buildroot 原始碼 : ```bash git clone https://github.com/buildroot/buildroot ``` 使用預設的 config ``` make stm32f429_disco_xip_defconfig ``` 選擇我們需要的特定 Linux Kernel 版本 ``` make menuconfig ``` 位置: Kernel $\to$ Kernel version ![image](https://hackmd.io/_uploads/BJp08Rtegl.png) 需要做的更動 ``` 6.1.133 -> 6.14.5 ``` 以及 `In-tree Device Tree Source file names` 位置 ``` stm32f429-disco -> st/stm32f429-disco ``` ![image](https://hackmd.io/_uploads/S1YfO0Kggg.png) 接著更改用來建構 C 標頭檔( kernel headers ) 的來源版本 位置 : Toolchain $\to$ Custom kernel headers series ``` 6.1.x -> 6.13.x or later ``` ![image](https://hackmd.io/_uploads/S1lQ_1cgex.png) 另外我們在編譯時, Buildroot 為了安全性會去強制驗證下載的 `linux-6.14.5.tar.xz` 的 SHA256 雜湊值是否與 Buildroot 官方所設定的一致 為避免因為沒有對應的雜湊, 所以先將強制驗證關閉 位置 : Build options $\to$ Advanced $\to$ Force all downloads to have a valid hash ![image](https://hackmd.io/_uploads/Bkd_uJ5lgx.png) 將設定保存後, 緊接著就是編譯 ``` make -j12 ``` 完成後會在 `/buildroot/output/images/` 看到 `xipImage` ``` $ ls -lh xipImage -rw-r--r-- 1 an an 2.6M May 8 16:26 xipImage ``` ### 運用 [LTO](https://lwn.net/Articles/744507/) 縮減 kernel image size #### LTO 的主要概念 1. 跨檔案的死碼移除( Dead Code Elimination ) - 一般情況下,編譯器無法判斷某些非 `static` 函式是否會在其他模組中被呼叫,因此不會刪除 - 使用 LTO,即便這些函數來自不同的檔案,只要沒有被用到就會被刪除,有效減少最終二進位檔大小 2. 函數內嵌展開( Inlining )與常數折疊( Constant Folding ) - LTO 能自動將函數內聯至使用者處,尤其是 `static inline` 函數,使程式碼更加緊湊 - 若參數為常數,也能提早做數值運算,減少執行時的指令數量 要在 Linux 核心啟用 Link Time Optimization (LTO) 建議使用 Clang/LLVM , 主要是 : 1. Clang/LLVM 對 LTO 的原生支援: - LLVM 工具鏈內建了對 LTO 的強大支援。LTO 是一種編譯器優化技術,允許在連結階段對整個程式碼進行跨模組的分析和優化,從而產生更高效的程式碼。Clang 作為 LLVM 的前端,與 LLVM 的後端工具(如 lld 連結器)緊密整合,能夠高效地執行 LTO - 相較之下,GNU 工具鏈(GCC 和 binutils)的 LTO 實現(透過 gcc 和 ld) 在 Linux 核心的背景下,支援度和穩定性可能不如 Clang/LLVM,尤其是在複雜的核心編譯場景中 2. Linux 核心對 Clang/LLVM LTO 的支援: - Linux 核心的建構系統(Kbuild)已經針對 Clang 的 LTO 進行了專門的優化和測試 - 雖然 GCC 也支援 LTO,但核心社群對 GCC LTO 的支援和測試不如 Clang 完善,特別是在某些架構(如 ARM64 或 RISC-V)上,Clang 的 LTO 實現更為穩定且廣泛使用 3. Clang/LLVM 的模組化與效能優勢: - LLVM 的模組化設計使得 LTO 的實現更靈活,能夠更好地處理 Linux 核心這樣的大型專案。Clang/LLVM 的 LTO 能執行更進階的優化,例如函數內嵌、死碼移除和全局變數優化,從而顯著縮減核心二進位檔案大小並提升執行效能 - 例如,Google 和 Android 等專案廣泛使用 Clang 編譯的核心,並啟用 LTO,以實現更小的核心映像和更快的執行速度,這在嵌入式系統和資料中心中尤為重要 ``` sudo apt install clang lld llvm ``` ``` make LLVM=1 ARCH=arm menuconfig ``` ``` make LLVM=1 ARCH=arm LLVM_IAS=1 CROSS_COMPILE=arm-linux-gnueabi- ``` 實驗進行中.... ### 使用 SD card (免除燒錄韌體) ##### <font color="#f00">目前使用的 Linux 版本為 6.1.133 測試 SD card功能</font> 為了避免在實務過程中反覆的燒錄韌體, 所以將必要的 Linux Kernel image 以及 Device Tree 燒錄在 SD card 並且透過 SPI 傳輸資料 | STM32 PIN | VCP | | --------- | ---- | | PE2 | SPI4_SCK | | PE4 | SPI4_NSS | | PE5 | SPI4_MISO | | PE6 | SPI4_MOSI | USB to TTL 接線: - PE2 $\to$ SCK - PE4 $\to$ CS - PE5 $\to$ MISO - PE6 $\to$ MOSI - GND $\to$ GND - 5V $\to$ VCC ![MicroSD-card-adapter-pin-out](https://hackmd.io/_uploads/Byb9zloZgg.png) 下方是 Linux Kernel image 以及 Device Tree 擺放位置 ``` # FLASH # ----------------------------------- # AFBOOT 0x08000000 - max 2MiB # # SDRAM # ----------------------------------- # KERNEL (XIP) 0x90000000 - 3MiB-32KiB = 3064KiB # DTB 0x902F8000 - 32KiB # FREE SDRAM 0x90300000 5MiB # # SDCARD # ----------------------------------- # Master Boot Record (MBR) # 1. Partition - Linux Kernel image (RAW data) # 2. Partition - Device Tree Blob (RAW data) # 3. Partition - Reserved ``` 之後就藉由 afboot 做 : - 初始化 SPI 與 SD 卡 - 從 SD 卡讀取 MBR 分割區資訊 - 分別將 kernel 與 device tree 從對應分割區載入至指定 SDRAM 位址 下載 afboot patch 來修改原本的程式碼 ```bash git clone https://gist.github.com/kk908676/d9d659cb64f60318505ff70d5b19bafa ``` 執行 ``` cd ~/Desktop/afboot-stm32/ && git apply ~/Desktop/d9d659cb64f60318505ff70d5b19bafa/afboot.patch ``` 燒錄進板子內 ``` cd afboot-stm32 make flash_stm32f429i-disco ``` 有了 bootloader 之後我們就只需要將 Linux Kernel image 以及 Device Tree 燒錄在 SD card ,這樣 bootloader 就可以到指定的地點透過 SPI 抓取這些資料以此來啟動 kernel 在準備 image 和 device tree 時依據上方的記憶體配置圖修改 SDRAM 大小以及位置、 xipImage 執行位置 ``` cd buildroot/ make linux-menuconfig ``` 位置 : System Type $\to$ Set flash/sdram size and base addr ![image](https://hackmd.io/_uploads/HkyhVbLZee.png) 更改 (S)DRAM Base Address 為 `0x90300000` 更改 (S)DRAM SIZE 為 `0x00500000` 位置 : Boot options $\to$ Kernel Execute-In-Place from ROM ![image](https://hackmd.io/_uploads/HkbeUWUbxe.png) 更改 XIP Kernel Physical Location 為 `0x90000000` 改完之後就能直接編譯產生我們要的 Linux Kernel image 以及 Device Tree ``` make -j12 ``` 會在 `buildroot/output/images` 看到 xipImage 以及 stm32f429-disco.dtb 接著就要將這兩個檔案燒錄進 SD card ,下載 : ```bash sudo apt install genimage ``` 然後還需要一個 SD card 的映像檔用來定義 SD card 的分割區結構 ``` $vi sdcard.config image sdcard.img { hdimage { } partition xipImage { image = "output/images/xipImage" } partition dtb { image = "output/images/stm32f429-disco.dtb" } } ``` 執行 ``` genimage --config sdcard.config --inputpath . --outputpath . ``` 就可以看到 `sdcard.img` 的映像檔了 接下來就把 SD card 插入電腦 ``` $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS loop0 7:0 0 73.9M 1 loop /snap/core22/1748 loop1 7:1 0 4K 1 loop /snap/bare/5 loop2 7:2 0 258M 1 loop /snap/firefox/5751 loop3 7:3 0 73.9M 1 loop /snap/core22/1963 loop4 7:4 0 11.1M 1 loop /snap/firmware-updater/167 loop5 7:5 0 241.9M 1 loop /snap/firefox/6159 loop6 7:6 0 516M 1 loop /snap/gnome-42-2204/202 loop7 7:7 0 91.7M 1 loop /snap/gtk-common-themes/1535 loop8 7:8 0 10.8M 1 loop /snap/snap-store/1248 loop9 7:9 0 10.8M 1 loop /snap/snap-store/1270 loop10 7:10 0 44.4M 1 loop /snap/snapd/23545 loop11 7:11 0 50.9M 1 loop /snap/snapd/24505 loop12 7:12 0 568K 1 loop /snap/snapd-desktop-integration/253 loop13 7:13 0 210.4M 1 loop /snap/thunderbird/644 loop14 7:14 0 210.2M 1 loop /snap/thunderbird/721 loop15 7:15 0 9.8M 1 loop ├─loop15p1 259:5 0 1.8M 1 part ├─loop15p2 259:6 0 19.5K 1 part └─loop15p3 259:7 0 8M 1 part sda 8:0 0 931.5G 0 disk ├─sda1 8:1 0 16M 0 part └─sda2 8:2 0 931.5G 0 part sdb 8:16 0 1.8T 0 disk ├─sdb1 8:17 0 409.9G 0 part / ├─sdb2 8:18 0 1G 0 part /boot/efi ├─sdb3 8:19 0 30G 0 part [SWAP] └─sdb4 8:20 0 956.5G 0 part /home sdc 8:32 1 2M 0 disk /media/an/DIS_F429ZI sdd 8:48 1 15G 0 disk └─sdd1 8:49 1 15G 0 part nvme0n1 259:0 0 465.8G 0 disk ├─nvme0n1p1 259:1 0 529M 0 part ├─nvme0n1p2 259:2 0 99M 0 part ├─nvme0n1p3 259:3 0 16M 0 part └─nvme0n1p4 259:4 0 465.1G 0 part ``` 可以看到 `sdd` 就是剛剛插入大小為 15G 的 SD card ``` sdd 8:48 1 15G 0 disk └─sdd1 8:49 1 15G 0 part ``` 透過以下命令將 `sdcard.img` 燒錄進 SD card ``` sudo dd if=sdcard.img of=/dev/sdd bs=4M status=progress conv=fsync ``` 便能看到 `Linux Kernel image` 和 `Device Tree` ``` sdd 8:48 1 15G 0 disk ├─sdd1 8:49 1 1.6M 0 part └─sdd2 8:50 1 18.5K 0 part ``` 隨後將 SD card 插入 microsd card adapter 待 bootloader 成功讀抓取到這些資料後便能啟動 Linux kernel ``` Booting ... Reading MBR ... Loading kernel ... .......................... Loading device tree blob ... ................... Executing kernel ... ** 7 printk messages dropped ** [ 0.000000] Normal [mem 0x0000000090300000-0x00000000907fffff] [ 0.000000] Movable zone start for each node [ 0.000000] Early memory node ranges [ 0.000000] node 0: [mem 0x0000000090300000-0x00000000907fffff] [ 0.000000] Initmem setup node 0 [mem 0x0000000090300000-0x00000000907fffff] [ 0.000000] On node 0, zone Normal: 768 pages in unavailable ranges [ 0.000000] pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768 [ 0.000000] pcpu-alloc: [0] 0 [ 0.000000] Built 1 zonelists, mobility grouping off. Total pages: 1270 [ 0.000000] Kernel command line: root=/dev/ram [ 0.000000] Dentry cache hash table entries: 1024 (order: 0, 4096 bytes, linear) [ 0.000000] Inode-cache hash table entries: 1024 (order: 0, 4096 bytes, linear) [ 0.000000] mem auto-init: stack:all(zero), heap alloc:off, heap free:off [ 0.000000] Memory: 4804K/5120K available (951K kernel code, 69K rwdata, 216K rodata, 72K init, 45K bss, 316K reserved, 0K cma-reserved) [ 0.000000] rcu: Preemptible hierarchical RCU implementation. [ 0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 100 jiffies. [ 0.000000] NR_IRQS: 16, nr_irqs: 16, preallocated irqs: 16 [ 0.000000] /soc/interrupt-controller@40013c00: bank0 [ 0.000000] rcu: srcu_init: Setting srcu_struct sizes based on contention. [ 0.000000] clocksource: arm_system_timer: mask: 0xffffff max_cycles: 0xffffff, max_idle_ns: 355517175 ns [ 0.000000] ARM System timer initialized as clocksource [ 0.000026] sched_clock: 32 bits at 84MHz, resolution 11ns, wraps every 25565281274ns [ 0.000490] timers@40000c00: STM32 sched_clock registered [ 0.000894] Switching to timer-based delay loop, resolution 11ns [ 0.001128] timers@40000c00: STM32 delay timer registered [ 0.001478] clocksource: timers@40000c00: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 22753100554 ns [ 0.002093] /soc/timers@40000c00: STM32 clockevent driver initialized (32 bits) [ 0.009570] Calibrating delay loop (skipped), value calculated using timer frequency.. 168.00 BogoMIPS (lpj=84000) [ 0.010091] pid_max: default: 4096 minimum: 301 [ 0.011757] Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear) [ 0.012352] Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear) [ 0.046812] rcu: Hierarchical SRCU implementation. [ 0.046973] rcu: Max phase no-delay instances is 400. [ 0.059041] devtmpfs: initialized [ 0.748431] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 1911260446275000 ns [ 0.749793] pinctrl core: initialized pinctrl subsystem [ 1.108871] /soc/spi@40015000/display@1: Fixed dependency cycle(s) with /soc/display-controller@40016800 [ 1.110786] /soc/display-controller@40016800: Fixed dependency cycle(s) with /soc/spi@40015000/display@1 [ 1.173585] /soc/spi@40015000/display@1: Fixed dependency cycle(s) with /soc/display-controller@40016800 [ 1.193676] /soc/spi@40015000/display@1: Fixed dependency cycle(s) with /soc/display-controller@40016800 [ 1.207708] /soc/display-controller@40016800: Fixed dependency cycle(s) with /soc/spi@40015000/display@1 [ 1.370683] stm32f429-pinctrl soc:pinctrl@40020000: GPIOA bank added [ 1.393696] stm32f429-pinctrl soc:pinctrl@40020000: GPIOB bank added [ 1.418110] stm32f429-pinctrl soc:pinctrl@40020000: GPIOC bank added [ 1.454932] stm32f429-pinctrl soc:pinctrl@40020000: GPIOD bank added [ 1.479779] stm32f429-pinctrl soc:pinctrl@40020000: GPIOE bank added [ 1.506033] stm32f429-pinctrl soc:pinctrl@40020000: GPIOF bank added [ 1.533503] stm32f429-pinctrl soc:pinctrl@40020000: GPIOG bank added [ 1.569453] stm32f429-pinctrl soc:pinctrl@40020000: GPIOH bank added [ 1.606354] stm32f429-pinctrl soc:pinctrl@40020000: GPIOI bank added [ 1.643034] stm32f429-pinctrl soc:pinctrl@40020000: GPIOJ bank added [ 1.670745] stm32f429-pinctrl soc:pinctrl@40020000: GPIOK bank added [ 1.671841] stm32f429-pinctrl soc:pinctrl@40020000: Pinctrl STM32 initialized [ 2.188840] stm32-dma 40026000.dma-controller: STM32 DMA driver registered [ 2.316773] stm32-dma 40026400.dma-controller: STM32 DMA driver registered [ 2.358368] clocksource: Switched to clocksource timers@40000c00 [ 2.493684] workingset: timestamp_bits=30 max_order=11 bucket_order=0 [ 3.620731] STM32 USART driver initialized [ 3.669996] 40011000.serial: ttySTM0 at MMIO 0x40011000 (irq = 47, base_baud = 5250000) is a stm32-usart [ 4.486832] printk: console [ttySTM0] enabled [ 4.538656] random: crng init done [ 4.590596] stm32_rtc 40002800.rtc: registered as rtc0 [ 4.597101] stm32_rtc 40002800.rtc: setting system clock to 2000-01-01T00:00:35 UTC (946684835) [ 4.611273] stm32_rtc 40002800.rtc: Date/Time must be initialized [ 5.589741] clk: Disabling unused clocks [ 5.613033] Freeing unused kernel image (initmem) memory: 24K [ 5.620087] This architecture does not have kernel memory protection. [ 5.627857] Run /init as init process [ 5.632266] with arguments: [ 5.636088] /init [ 5.639232] with environment: [ 5.643288] HOME=/ [ 5.646647] TERM=linux [ 7.643876] S01seedrng: page allocation failure: order:7, mode:0xcc0(GFP_KERNEL), nodemask=(null) [ 7.655967] CPU: 0 PID: 33 Comm: S01seedrng Not tainted 6.1.133 #4 [ 7.663247] Hardware name: STM32 (Device Tree Support) [ 7.669164] Function entered at [<90002b52>] from [<90001f9b>] [ 7.675925] Function entered at [<90001f9b>] from [<900d9b45>] [ 7.682691] Function entered at [<900d9b45>] from [<90046edb>] [ 7.689470] Function entered at [<90046edb>] from [<90047259>] [ 7.696234] Function entered at [<90047259>] from [<90047677>] [ 7.703006] Function entered at [<90047677>] from [<90047889>] [ 7.709659] Function entered at [<90047889>] from [<90044765>] [ 7.716546] Function entered at [<90044765>] from [<9003f9dd>] [ 7.723321] Function entered at [<9003f9dd>] from [<9006a9e9>] [ 7.730086] Function entered at [<9006a9e9>] from [<9004f3f3>] [ 7.736974] Function entered at [<9004f3f3>] from [<9004f6cb>] [ 7.743634] Function entered at [<9004f6cb>] from [<900500ad>] [ 7.750511] Function entered at [<900500ad>] from [<90000061>] [ 7.757233] Exception stack(0x9041ffa8 to 0x9041fff0) [ 7.763459] ffa0: 9031f698 9031f658 9031f698 9031f658 9055f648 0000000b [ 7.773023] ffc0: 9031f698 9031f658 905dca70 0000000b 9031f698 9031f368 0000000d 00000000 [ 7.782384] ffe0: 00000000 905dca70 9058f6b3 90596964 [ 7.788897] Mem-Info: [ 7.791830] active_anon:0 inactive_anon:0 isolated_anon:0 [ 7.791830] active_file:0 inactive_file:0 isolated_file:0 [ 7.791830] unevictable:270 dirty:0 writeback:0 [ 7.791830] slab_reclaimable:0 slab_unreclaimable:397 [ 7.791830] mapped:0 shmem:0 pagetables:0 [ 7.791830] sec_pagetables:0 bounce:0 [ 7.791830] kernel_misc_reclaimable:0 [ 7.791830] free:283 free_pcp:0 free_cma:0 [ 7.833706] Node 0 active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:1080kB isolated(anon):0kB isolated(file):0kB mapped:0kB dirty:0kB writeback:0kB shmem:0kB writeback_tmp:0kB kernel_stack:176kB pagetables:0kB sec_pagetables:0kB all_unreclaimable? no [ 7.862644] Normal free:1132kB boost:0kB min:276kB low:344kB high:412kB reserved_highatomic:0KB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:1080kB writepending:0kB present:5120kB managed:4828kB mlocked:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB [ 7.892809] lowmem_reserve[]: 0 0 [ 7.897660] Normal: 1*4kB (U) 1*8kB (U) 0*16kB 1*32kB (U) 1*64kB (U) 4*128kB (U) 2*256kB (U) 0*512kB 0*1024kB 0*2048kB 0*4096kB = 1132kB [ 7.917635] 276 total pagecache pages [ 7.922090] 1280 pages RAM [ 7.925692] 0 pages HighMem/MovableOnly [ 7.930547] 73 pages reserved [ 7.934253] nommu: Allocation of length 380928 from process 33 (S01seedrng) failed [ 7.943652] active_anon:0 inactive_anon:0 isolated_anon:0 [ 7.943652] active_file:0 inactive_file:0 isolated_file:0 [ 7.943652] unevictable:270 dirty:0 writeback:0 [ 7.943652] slab_reclaimable:0 slab_unreclaimable:397 [ 7.943652] mapped:0 shmem:0 pagetables:0 [ 7.943652] sec_pagetables:0 bounce:0 [ 7.943652] kernel_misc_reclaimable:0 [ 7.943652] free:283 free_pcp:0 free_cma:0 [ 7.984977] Node 0 active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:1080kB isolated(anon):0kB isolated(file):0kB mapped:0kB dirty:0kB writeback:0kB shmem:0kB writeback_tmp:0kB kernel_stack:176kB pagetables:0kB sec_pagetables:0kB all_unreclaimable? no [ 8.013762] Normal free:1132kB boost:0kB min:276kB low:344kB high:412kB reserved_highatomic:0KB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:1080kB writepending:0kB present:5120kB managed:4828kB mlocked:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB [ 8.043815] lowmem_reserve[]: 0 0 [ 8.048685] Normal: 1*4kB (U) 1*8kB (U) 0*16kB 1*32kB (U) 1*64kB (U) 4*128kB (U) 2*256kB (U) 0*512kB 0*1024kB 0*2048kB 0*4096kB = 1132kB [ 8.068712] 276 total pagecache pages [ 8.073247] binfmt_flat: Unable to allocate RAM for process text/data, errno -12 Welcome to Buildroot (none) login: ```