STM32 з нуля без HAL. U-Boot на Luckfox Pico Pro: зламати, зрозуміти, зібрати своє. Частина 6
Як модифікувати bootloader, додати власну команду і прошити без Maskrom — наживо через MTD.
Навіщо чіпати U-Boot?
Попередня частина — STM32 з нуля без HAL: Buildroot, Luckfox і перший крок у embedded Linux. Частина 5
Якщо ви запустили Luckfox Pico Pro і побачили рядки що побігли у терміналі — то ви вже побачили U-Boot. Він оживає між увімкненням живлення і появою Linux shell. Більшість користувачів його ніколи не чіпає. Та в мене вже руки сверблять — дізнатись, як воно працює.
До того ж, embedded розробник повинен розуміти весь стек: від першого такту процесора до userspace. U-Boot — це C проект. Він компілюється, патчується і прошивається як будь-який інший код.
У цій статті ми зробимо три конкретні речі:
- змінимо bootdelay — щоб мати час зловити консоль без акробатики з Ctrl+C
- додамо checkboard() — власний рядок при старті через механізм weak функцій
- напишемо нову shell команду mystatus — 20 рядків C, нуль магії
І прошиємо все це прямо з живої Linux системи — без Maskrom, без USB кабелю.
⚠ Ця стаття про Luckfox Pico Pro з RV1106 і U-Boot 2017.09 (Rockchip fork). Якщо у вас інша плата — концепти ті самі, шляхи до файлів будуть різні.
Частина 1. Що таке U-Boot і де він живе
Boot sequence на Luckfox
RV1106 — це Cortex-A7 з Rockchip ROM всередині. При увімкненні живлення процесор не знає де Linux, де файлова система, і взагалі нічого. Він знає тільки одне: прочитати код з фіксованої адреси в ROM і виконати його.
ROM code — це перший ступінь. Він ініціалізує мінімум заліза і шукає наступний завантажувач на SPI NAND (або eMMC, SD — залежно відBootROM straps). На Luckfox це SPI NAND.
Далі ланцюжок виглядає так:
ROM code (вбудований в чіп, незмінний) ↓ SPL / miniloader (ініціалізує DDR RAM) ↓ U-Boot proper (наш bootloader) ↓ Linux kernel ↓ init → userspace (ваш shell)
U-Boot — третій ступінь. До нього RAM вже ініціалізована SPL, і U-Boot може завантажити kernel з NAND в RAM і передати керування.
MTD партиції: де що лежить
Вся SPI NAND на Luckfox розбита на MTD партиції. Подивитись можна так:
$ cat /proc/mtd dev: size erasesize name mtd0: 00040000 00020000 "env" mtd1: 00040000 00020000 "idblock" mtd2: 00080000 00020000 "uboot" mtd3: 00400000 00020000 "boot" mtd4: 01e00000 00020000 "oem" mtd5: 00a00000 00020000 "userdata" mtd6: 0d200000 00020000 "rootfs"
mtd2 — це наш U-Boot, 512KB. Саме туди ми будемо прошивати. mtd1 (idblock) — це SPL з DDR init, його краще не чіпати без причини.
◆ MTD = Memory Technology Device. Це Linux підсистема для роботи з flash пам’яттю. /dev/mtd2 — символьний пристрій для прямого запису, /dev/mtdblock2 — блочний (як диск). Для прошивки bootloader використовуємо символьний.
Частина 2. Де живе код U-Boot
Структура Luckfox SDK
Luckfox SDK — це великий репозиторій де все вже зібрано: buildroot, kernel, u-boot, rkbin (бінарники від Rockchip). U-Boot живе тут:
~/luckfox-pico/sysdrv/source/uboot/u-boot/
Поруч є rkbin — це закриті бінарники від Rockchip (DDR init, TrustZone). Ми їх не чіпаємо, вони використовуються при пакуванні фінального uboot.img.
Defconfig і конфіг плати
U-Boot конфігурується через Kconfig — так само як Linux kernel. Для Luckfox Pico Pro defconfig знаходиться тут:
configs/luckfox_rv1106_uboot_defconfig
Конфіг плати (board-специфічний хедер):
include/configs/rv1106_common.h include/configs/evb_rv1106.h
І board-специфічний C файл:
board/rockchip/evb_rv1106/evb_rv1106.c
Як збирається uboot.img
Luckfox SDK має скрипт build.sh з окремою ціллю для U-Boot:
cd ~/luckfox-pico ./build.sh uboot
Після збірки готовий образ з’являється тут:
output/image/uboot.img
Це вже запакований FIT image з підписом Rockchip — не просто бінарник U-Boot, а контейнер з SPL, U-Boot proper і TrustZone. Саме його ми прошиватимемо в mtd2.
⚠ ./build.sh uboot збирає тільки U-Boot і займає ~2 хвилини. ./build.sh all — весь SDK включно з rootfs, це може зайняти
20-40 хвилин. Для наших патчів достатньо першого.
Частина 3. Три патчі
Патч 1: bootdelay — дати собі час
За замовчуванням Luckfox стартує з bootdelay=0. Це означає що U-Boot не чекає взагалі — він одразу завантажує Linux без паузи на prompt. Щоб зловити консоль треба було тримати Ctrl+C ще до натискання RESET, не питайте як я до цього дійшов ).
Змінюємо один рядок в defconfig:
# configs/luckfox_rv1106_uboot_defconfig # Було: CONFIG_BOOTDELAY=0 # Стало: CONFIG_BOOTDELAY=3
Або однією командою:
sed -i 's/CONFIG_BOOTDELAY=0/CONFIG_BOOTDELAY=3/' \ configs/luckfox_rv1106_uboot_defconfig
Тепер при старті буде 3 секунди:
Hit key to stop autoboot('CTRL+C'): 3
Hit key to stop autoboot('CTRL+C'): 2
Hit key to stop autoboot('CTRL+C'): 1◆ bootdelay — це змінна оточення U-Boot. Її можна також змінити з консолі: setenv bootdelay 5 && saveenv. Але зміна в defconfig гарантує що після скидання env вона повернеться до вашого значення, а не до 0.
Патч 2: checkboard() — власний рядок при старті
U-Boot виводить інформацію при старті через ланцюжок функцій ініціалізації. Одна з них — show_board_info(), яка в свою чергу викликає checkboard(). Обидві оголошені як __weak в
common/board_info.c:
// common/board_info.c
int __weak checkboard(void)
{
return 0; // порожня реалізація за замовчуванням
}
__weak означає що компілятор візьме цю версію тільки якщо ніде більше немає визначення з таким іменем. Якщо ми додаємо свою checkboard() в board-специфічний файл — компілятор тихо замінює weak версію нашою. Ніяких #ifdef, ніяких патчів core коду.
Додаємо в кінець board/rockchip/evb_rv1106/evb_rv1106.c:
int checkboard(void)
{
printf("Board: Luckfox Pico Pro (custom build by Alex)\n");
printf("Series: STM32 з нуля без HAL — article 6\n");
return 0;
}
Результат при старті:
Model: Luckfox Pico Pro Max Board: Luckfox Pico Pro (custom build by Alex) Series: STM32 з нуля без HAL — article 6
◆ Model: береться автоматично з DTB — поле model в device tree. Board: — це вже наш checkboard(). Два різних механізми, два різних рядки, один boot log.
⚠ Не визначайте checkboard() в двох місцях одночасно — лінкер видасть помилку ’multiple definition’. weak замінюється тільки одним strong визначенням.
Патч 3: команда mystatus — власний shell в U-Boot
U-Boot має вбудований командний інтерпретатор. Кожна команда — це окремий .c файл в директорії cmd/. Подивіться на version.c — це найпростіший шаблон:
// cmd/version.c (спрощено)
#include <common.h>
#include <command.h>
static int do_version(cmd_tbl_t *cmdtp, int flag,
int argc, char * const argv[])
{
printf(display_options_get_banner(false, buf, sizeof(buf)));
return 0;
}
U_BOOT_CMD(version, 1, 1, do_version, "print version", "");
Паттерн простий: функція do_xxx() + макрос U_BOOT_CMD(). Макрос реєструє команду в спеціальній секції .u_boot_list, звідки U-Boot збирає всі доступні команди при ініціалізації.
Створюємо cmd/mystatus.c:
/*
* mystatus — custom command for Luckfox Pico Pro
* Part of 'STM32 з нуля без HAL' article series
*/
#include <common.h>
#include <command.h>
static int do_mystatus(cmd_tbl_t *cmdtp, int flag,
int argc, char * const argv[])
{
DECLARE_GLOBAL_DATA_PTR;
printf("\n=== Luckfox Pico Pro — System Status ===\n");
printf("U-Boot version : 2017.09 (custom patch)\n");
printf("CPU : RV1103/RV1106 (Cortex-A7)\n");
printf("DRAM : %lu MiB\n", gd->ram_size / 1024 / 1024);
printf("Boot device : SPI NAND\n");
printf("Author : Alex (STM32 без HAL series)\n");
printf("=========================================\n\n");
return 0;
}
U_BOOT_CMD(
mystatus, 1, 0, do_mystatus,
"show Luckfox system status",
" — prints CPU, RAM, boot device info"
);
Реєструємо в cmd/Makefile — одним рядком:
obj-y += mystatus.o
obj-y означає ’завжди компілювати’, на відміну від obj-$(CONFIG_CMD_SOMETHING) де команда ввімкнена тільки якщо є відповідний CONFIG_ прапор. Для нашого випадку obj-y простіше.
◆ DECLARE_GLOBAL_DATA_PTR — це макрос який дає доступ до глобальної структури gd (global data). В ній U-Boot зберігає все що визначив при ініціалізації: розмір RAM, частоту CPU, стан периферії. gd->ram_size — реальний розмір RAM знайдений SPL при DDR init.
Частина 4. Збірка і прошивка
Збірка
Після всіх трьох патчів — збираємо:
cd ~/luckfox-pico ./build.sh uboot 2>&1 | tail -20
Успішна збірка виглядає так:
Image(no-signed, version=0): uboot.img (FIT with uboot, trust...) is ready pack uboot.img okay! [build.sh:info] Running build_uboot succeeded.
Новий uboot.img лежить в output/image/uboot.img.
Прошивка з живої системи через MTD
Ось де стає цікаво. Більшість інструкцій кажуть: переведи плату в Maskrom (BOOT + USB), прошивай через upgrade_tool. Це працює, але якось все таки незручно — треба фізичний доступ до плати, USB кабель, і вся процедура займає певний час.
Але якщо Linux на платі вже запущений і є SSH — можна прошити U-Boot прямо по живому через MTD:
# Копіюємо новий uboot.img на плату scp output/image/uboot.img [email protected]:/tmp/ # Прошиваємо mtd2 = uboot партиція ssh [email protected] 'flashcp -v /tmp/uboot.img /dev/mtd2'
flashcp — утиліта з пакету mtd-utils. Вона робить три кроки автоматично:
Erasing blocks: 2/2 (100%) Writing data: 256k/256k (100%) Verifying data: 256k/256k (100%)
Після цього — reboot, і новий U-Boot запускається.
⚠ Прошивка U-Boot по живому — це ризик. Якщо живлення пропаде під час запису в mtd2, плата може не завантажитись. На Luckfox відновлення можливе через Maskrom + upgrade_tool, але це вже інша пригода. Робіть при стабільному живленні.
⚠ flashcp перевіряє розмір файлу перед записом. Якщо uboot.img більший за партицію mtd2 — він відмовить і викине помилку. 256KB образ в 512KB партицію — норм.
Перевірка в U-Boot консолі
Підключаємо UART (UART2 на Luckfox, 115200 baud) і дивимось boot log:
minicom -b 115200 -D /dev/ttyUSB0
При старті бачимо:
HASH(c): OK
Some drivers failed to bind
Model: Luckfox Pico Pro Max
Board: Luckfox Pico Pro (custom build by Alex)
Series: STM32 з нуля без HAL — article 6
Net: eth0: ethernet@ffa80000
Hit key to stop autoboot('CTRL+C'): 3
Натискаємо будь-яку клавішу — потрапляємо в U-Boot shell. Перевіряємо нову команду:
=> mystatus === Luckfox Pico Pro — System Status === U-Boot version : 2017.09 (custom patch) CPU : RV1103/RV1106 (Cortex-A7) DRAM : 128 MiB Boot device : SPI NAND Author : Alex (STM32 без HAL series) =========================================
128 MiB — реальне значення з gd->ram_size, яке SPL визначив при ініціалізації DDR. Це не константа в коді, а дані з живої системи.
Частина 5. Що всередині uboot.img
FIT image
uboot.img — це не просто бінарник U-Boot. Це FIT (Flattened Image Tree) контейнер — той самий формат що використовується для Linux kernel + DTB. Всередині:
uboot.img ├── u-boot.bin (скомпільований U-Boot proper) ├── u-boot-spl.bin (Secondary Program Loader) └── trust.img (TrustZone / OP-TEE від Rockchip)
SPL — це мінімальний завантажувач розміром ~50KB. Він ініціалізує DDR RAM і завантажує повний U-Boot. TrustZone — закритий бінарник від Rockchip для безпечного завантаження, ми його не чіпаємо.
Rockchip запаковує все це своїм скриптом після компіляції. Як бачимо ./build.sh uboot це не просто make, а ще й запаковка у фінальний образ.
Чому 256KB якщо партиція 512KB
mtd2 має розмір 512KB (0×80000), але наш uboot.img — 256KB. Це нормально. flashcp записує рівно стільки скільки є в файлі, решта партиції залишається як є (або заповнена 0xFF після erase). U-Boot при старті читає тільки потрібні байти.
◆ Якщо ви коли-небудь захочете прочитати поточний U-Boot з плати: dd if=/dev/mtd2 of=/tmp/uboot_backup.img bs=256k count=1. Корисно зробити бекап перед першим патчем.
Підводні камені які зустрів
UART1 vs UART2: plug-and-pray
На Luckfox Pico Pro U-Boot console виведеео на UART2 (фізичні піни
minicom Hardware Flow Control
За замовчуванням minicom вмикає Hardware Flow Control (RTS/CTS). CH340 адаптер підтримує тільки TX/RX без flow control — через це термінал або мовчить або показує сміття.
# В minicom: Ctrl+A → O → Serial port setup → F (Hardware Flow Control) → No # Або одразу при запуску через конфіг: minicom -b 115200 -D /dev/ttyUSB0 -o
bootdelay=0 і Ctrl+C трюк
Якщо хочете зловити U-Boot prompt без зміни коду — є спосіб: затисніть Ctrl+C в minicom ще до натискання RESET на платі. U-Boot при старті перевіряє чи є символ в UART буфері, і якщо є Ctrl+C — зупиняється. Але це потребує точного таймінгу і не завжди спрацьовує.
Набагато надійніше просто змінити CONFIG_BOOTDELAY=3 як ми і зробили.
upgrade_tool і Maskrom
upgrade_tool очікує що плата вже в Rockusb режимі. Maskrom і Rockusb — різні режими. Якщо upgrade_tool каже ’No found any rockusb device’ — потрібно спочатку відправити download.bin через Maskrom щоб плата перейшла в Rockusb:
sudo upgrade_tool DB output/image/download.bin # Після цього: sudo upgrade_tool UB output/image/uboot.img
Але якщо плата вже запущена і є SSH — flashcp через MTD простіше і швидше.
BRLTTY і CH340 на Ubuntu 22.04
Ubuntu 22.04 встановлює BRLTTY (Braille display daemon) який перехоплює CH340 адаптер. Симптом: /dev/ttyUSB0 з’являється і одразу зникає, або permission denied. Вирішення:
sudo apt remove brltty # Або тільки відключити: sudo systemctl stop brltty sudo systemctl disable brltty
⚠ Це класична пастка Ubuntu 22.04 для всіх хто працює з CH340, CP2102, FTDI адаптерами. Якщо /dev/ttyUSB0 не з’являється після підключення — перша підозра завжди BRLTTY.
Замість висновку
Ми пройшли весь шлях від де ’U-Boot це чорна скринька’ до ’U-Boot це C проект який я можу патчити і прошивати по живому на пряму із системи за 5 хвилин’.
Три патчі показали три різних підходи до модифікації:
- defconfig — один рядок змінює поведінку всього завантаження
- __weak функції — розширюємо поведінку без зміни core коду
- cmd/ + U_BOOT_CMD() — додаємо нову команду в 20 рядків
І прошивка через MTD з живої системи — це те що відрізняє ’я читав про embedded’ від ’я це робив’.
Наступна стаття — Device Tree. Будемо читати DTS файли Luckfox, розуміти compatible і reg, і робити перший overlay. Та не переживай — після U-Boot це буде легко.
2 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарівminicom ще живий? привіт з кінця1990-х... підключав NEC Express 5800/230 через UART до Linux virtual console.
Живий, нестаріюча класика 😄