STM32 з нуля без HAL. U-Boot на Luckfox Pico Pro: зламати, зрозуміти, зібрати своє. Частина 6

 

Як модифікувати bootloader, додати власну команду і прошити без Maskrom — з живої системи через MTD.

* * *

Навіщо чіпати U-Boot?

Якщо ви запустили 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.

Далі ланцюжок виглядає так:

 

U-Boot — третій ступінь. До нього RAM вже ініціалізована SPL, і U-Boot може завантажити kernel з NAND в RAM і передати керування.

MTD партиції: де що лежить

Вся SPI NAND на Luckfox розбита на MTD партиції. Подивитись можна так:

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 живе тут:

Поруч є rkbin — це закриті бінарники від Rockchip (DDR init, TrustZone). Ми їх не чіпаємо, вони використовуються при пакуванні фінального uboot.img.

Defconfig і конфіг плати

U-Boot конфігурується через Kconfig — так само як Linux kernel. Для Luckfox Pico Pro defconfig знаходиться тут:

Конфіг плати (board-специфічний хедер):

І board-специфічний C файл:

Як збирається uboot.img

Luckfox SDK має скрипт build.sh з окремою ціллю для U-Boot:

Після збірки готовий образ з’являється тут:

Це вже запакований 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:

Або однією командою:

 

Тепер при старті буде 3 секунди:

 

◆ bootdelay — це змінна оточення U-Boot. Її можна також змінити з консолі: setenv bootdelay 5 && saveenv. Але зміна в defconfig гарантує що після скидання env вона повернеться до вашого значення, а не до 0.

Патч 2: checkboard() — власний рядок при старті

U-Boot виводить інформацію при старті через ланцюжок функцій ініціалізації. Одна з них — show_board_info(), яка в свою чергу викликає checkboard(). Обидві оголошені як __weak в

__weak означає що компілятор візьме цю версію тільки якщо ніде більше немає визначення з таким іменем. Якщо ми додаємо свою checkboard() в board-специфічний файл — компілятор тихо замінює weak версію нашою. Ніяких #ifdef, ніяких патчів core коду.

Додаємо в кінець board/rockchip/evb_rv1106/evb_rv1106.c:

 

Результат при старті:

◆ 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 — це найпростіший шаблон:

 

Паттерн простий: функція do_xxx() + макрос U_BOOT_CMD(). Макрос реєструє команду в спеціальній секції .u_boot_list, звідки U-Boot збирає всі доступні команди при ініціалізації.

Створюємо cmd/mystatus.c:

 

Реєструємо в cmd/Makefile — одним рядком:

obj-y означає ‘завжди компілювати’, на відміну від obj-$(CONFIG_CMD_SOMETHING) де команда ввімкнена тільки якщо є відповідний CONFIG_ прапор. Для нашого випадку obj-y простіше.

* * *

◆ Частина 4. Збірка і прошивка

Збірка

Після всіх трьох патчів — збираємо:

Успішна збірка виглядає так:

Новий uboot.img лежить в output/image/uboot.img.

Прошивка з живої системи через MTD

Ось де стає цікаво. Більшість інструкцій кажуть: переведи плату в Maskrom (BOOT + USB), прошивай через upgrade_tool. Це працює, але якось все таки незручно — треба фізичний доступ до плати, USB кабель, і вся процедура займає певний час.

Але якщо Linux на платі вже запущений і є SSH — можна прошити U-Boot прямо по живому через MTD:

flashcp — утиліта з пакету mtd-utils. Вона робить три кроки автоматично:

Після цього — 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:

 

При старті бачимо:

Натискаємо будь-яку клавішу — потрапляємо в U-Boot shell. Перевіряємо нову команду:

128 MiB — реальне значення з gd->ram_size, яке SPL визначив при ініціалізації DDR. Це не константа в коді, а дані з живої системи.

* * *

◆ Частина 5. Що всередині uboot.img

FIT image

uboot.img — це не просто бінарник U-Boot. Це FIT (Flattened Image Tree) контейнер — той самий формат що використовується для Linux kernel + DTB. Всередині:

 

SPL — це мінімальний завантажувач розміром ~50KB. Він ініціалізує DDR RAM і завантажує повний U-Boot. TrustZone — закритий бінарник від Rockchip для безпечного завантаження, ми його не чіпаємо.

Rockchip запаковує все це своїм скриптом після компіляції. Як бачимо ./build.sh uboot це не просто make, а ще й запаковка у фінальний образ.

Чому 256KB якщо партиція 512KB

mtd2 має розмір 512KB (0x80000), але наш 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 (фізичні піни 1-2 на основному гребінці), а не на UART1. Документація про це мовчить. Якщо підключились до UART1 і бачите сміття або тишу — просто переключіть на сусідній роз’єм.

minicom Hardware Flow Control

За замовчуванням minicom вмикає Hardware Flow Control (RTS/CTS). CH340 адаптер підтримує тільки TX/RX без flow control — через це термінал або мовчить або показує сміття.

 

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:

Але якщо плата вже запущена і є SSH — flashcp через MTD простіше і швидше.

BRLTTY і CH340 на Ubuntu 22.04

Ubuntu 22.04 встановлює BRLTTY (Braille display daemon) який перехоплює CH340 адаптер. Симптом: /dev/ttyUSB0 з’являється і одразу зникає, або permission denied. Вирішення:

⚠ Це класична пастка 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 це буде легко.

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *