Ми називатимемо те, шо англ. термінологія називає booting, запусканням, запуском, пуском. Бо "буцання" (прямий переклад "booting") звучить грубувато, а "завантаження" не передає суть процесу так як хотілося б. Те, шо в англійській називають loading, коли мають на увазі саме процес зчитування компонентів системи з постійної пам'яти в оперативну, ми, на додачу до вже усталеного вантаження, називатимемо зчитуванням, читанням. Взагалі, такий акцент здається вдалішим за акцент на вантаженні чи грузінні. Хоча ми використовуватимемо всі три (читання, грузіння, вантаження) як синоніми. До речі, абсолютно не можна погодитися, шо дієслово "грузити" не рідне українське. Нарешті, loader, ми називатимемо вантажником або запускачем, від "запускати", як в ряду "орати" — "орач". Знову, того, шо акцентація на значенні "запускання", замість "вантаження компонентів в оперативну пам'ять", точніше, повніше, передає суть процесу, бо він і є запусканням ОС. Зчитування її файлів в оперативку (або, інакше, грузіння їх туди) — це лише частина справи. Але, оскільки вантажник вже вкоренився, — хай буде. Термін грузій ми теж використовуватимемо. Для тих, хто іржатиме з "грузія" ось вам кілька швидких аналогій, поширених в мові: носити - носій, палити - палій, красти - крадій, скиглити - скиглій. Трохи прикладів для ясности:
Спочатку, ми, як підготовчий і параллельний для Ант проєкт, намагались писати свою імплементацію Uefi — efify. Датування в C-шних файлах ядра показують квітень 2016-го. Це був по суті наш перший код. Писав я тоді Пускові Сервіси Uefi (Uefi Boot Services). Дивлячись зараз на ці файли в робочій копії Svn репозиторію efify, бачу десь до 2000-х рядків коду, не рахуючи Uefi заголовків. 2015-го, треба розуміти, я читав і роздумував. А, ще ось цей блог писав. З планшету! До речі, саме там видно, шо ім'я Ант вже було на березень 2015-го. Взагалі, efify потребує окремої книжки, колись може напишемо, а тут згадаємо за цей етап, бо він належить і Ант.
Ідея писати efify базувалась на думці, шо ППЗ є міні ОС, його важко писати, але, все ж, легше за ОС. Цей підпроєкт сам по собі бачився дуже перспективним і був вельми наполеонівським. Окрім спонук з точки зору його власних цілей, робота над ним давала б добре знання цього середовища, шо допомагало б писати вантажник. Не кажучи вже за надбаний досвід системного програмування загалом. Звичайно, я розумів, шо такий проєкт самому можливо буде зробити лише для кількох плат і для яких має бути дуже добра документація. Адже таке ПЗ на те й "платформне", шо дуже залізноспецифічне. Було вибрано Beagle Bone Black та Mips Creator. Cubieboard 2 теж розглядався, але не було ясно чи його документації буде досить. Мурановий am3358, мав хоч і доволі плутану, та все ж детальну документацію від Texas Instruments. А CI20 я хотів як ціль, в будь якому разі, бо mips машина була особливо збудливою, адже Uefi на mips-і просто не існувало ще, це дуже заохочувало. Та й документація і доступний вихідний код від Ingenic, давали надію на можливість втілення ідеї. Головну роль, в тому, шо саме mips став тим стартом, зіграв оцей чудовий блог, який я знайшов, скоріше за все на цьому сайті. Принаймні на тій сторінці досі (7-е жовтня 2023-го ) є посилання на нього і я точно навідувався на цей сайт багато разів тоді. Читання того блогу й дало мені ясність як же те голе залізо програмувати, дало впевненість і рішучість. Чого TI-шний мануал навряд міг дати новачку як я. Уявіть, це ж навіть не середовище без ОС, немає ще й "фірмварі", а значить ОЗП не ініціалізоване теж, перед твоїм кодом пробіг лише ROM код, як найнайперший.
Успішний тест нашого першого коду стався у 2017-му. Це теж був запускач, але вже фірмварі, яку, нагадаю, ми називаємо ППЗ (Платформне Програмне Забезпечення). Бачите, і тут прослідковується аналогія ППЗ з ОС — вони обоє починаються з свого вантажника. І в обох випадках він виконує аналогічну функцію розгортання решти своєї структури й подальшого передавання на неї керування, тобто — запуск. Тоді, орієнтуючись дещо на Platform Initialization (PI) специфікацію, яка є типу специфікації на імплементацію іншої специфікації (Uefi), ми назвали цю фазу, SEC фазою. Це стартовий код ППЗ і він власне запускає його ядро, після того, як ініціалізує оперативну пам'ять і зчитає ядро туди. Скриншот успішного старту нашої mips-івської SEC фази датується 17-м березня 2017-го. Тоді ж, 2017-го цей код і писався (дата створення SEC.S файлу для jz4780 — 28 лютого). Датування у таких же файлах для arm-івських цілей показують літо 2016-го, але, вони, як і згадані вгорі файли ядра, писалися в "стіл", максимум збиралися, але не тестувалися. Та й файл такий лише для am3358 був чимось більшим, ніж заглушкою. Це неабияка морока — починати писати "фірмварю", не маючи жодного досвіду програмування заліза на такому низькому рівні. Навіть знайти як звязатися з ROM кодом, шоб він схотів грузити і виконувати твій код було неясно як. З Аргоном (Cubieboard 2), це точно було непросто, бо документація була звалищем обривків інформації, написаних паганою англійською. Інтерфейс з Allwinner-івським Boot ROM-ом (BROM-ом) був засекречений — явно ненайкращий варіант для початківця. А для Йода, ми наштовхнулися на блог, як вже згадано. Так mips і став стартом, де відбулося тестування написаного. Принаймні стартового коду. Ми доволі легко переконали його ROM код нас запустити, та ще й з SD картки! То була магія, таке, в шо навіть бачачи, важко віриш. Уся ця мандрівка 2017-го була магією і була найнижньорівневішим програмуванням, яке я коли небудь робив. По суті, спочатку, я переписував з C на mips асемблер код з згаданого блогу і uboot-у, на який блог посилався. Перші кроки — ініціалізація PLL (Phase Locked Loop), UART-у, встановлення тактових частот для системних компонентів (процесор, його кеш, ОЗП), ініціалізація SDRAM пам'яти! З часом, я додавав свого, як от тестування пам'яти і певних перевірок конфіґурування DDR контроллера, вимикання (gating) невикористовуваних контроллерів, або читання з SD картки. Це вже робилося на набутому досвіді й читаючи Ingenic-івський мануал. Нижче, скриншот 16-го листопада 2017-го, де код робе все перелічене, де вже вміє читати багато блоків за одну команду і робе два успішних читання — одну одноблокову (cmd17) і одну кількаблокову (cmd18). Це було накреслення зчитування "фірмварного" тому з дальшими фазами в ньому, і далі застрибування в них, розгортання ядра Uefi тобто. Проте, у 2018-му, ми перемкнулись на ОС, в голому uboot-і на arm-івських міні ПК і зараз точно не скажу, чого efify була так облишена тоді. Посеред дороги так би мовити. Мабуть вже аж занадто хотілось почати писати "саму ось". :)
Як я щойно сказав, першими цілями вже самого грузія Ант стали arm-івські одноплатні міні ПК (ОПК). Спочатку, робота над вантажником на цій платформі йшла в середовищі голого uboot-у. Того шо підтримка Uefi в uboot-і тоді була паганою. Тоді я вирішив не ігнорувати емулятори, а вони мали Uefi як ППЗ опцію для своїх віртуальних машин (у вигляді OVMF), тож, березня 2020-го, я почав досліджувате це поле, шо давно хотів робити, і квітня 2020-го, я мав перший пуск вантажника, який бігав на Qemu і Virtual Box і який показував цю веселу пичку. Скриншот нижче показує тест на x64, але arm-івські копії теж були прогнані (на тому етапі не було нічого архітектурноспецифічного).
Станом на 07.03.2021 і увесь 2021-й, робота йшла (коли йшла) з uboot-івським Uefi на arm-івських справжньозалізних цілях, переважно на Ксеноні. Підтримка Uefi в uboot-і, особливо для деяких arm64 чипів (як от ксенонового rk3399) стала на цей момент значно кращою. Ця сторінка розказує історію створення такого (в середовищі Uefi) вантажника Ант. Детальніше процес вантаження Ант описаний в деталях пуску, а коротко, Торпеда (або antload, за іменем його файла) — це однорівневий вантажник, навідміну від Windows-івського напарника, де bootmgr дещо дуплікуючи Пусковий Менеджер ППЗ, обслуговує всі Windows-івські інсталяції, присутні на машині, в т.ч. показуючи користувачу меню їхнього вибору, а далі передає керування на наступний вантажник, свій для кожної інсталяції — winload. antload покладається на Uefi-шний Пусковий Менеджер в плані керування кількома інсталяціями (Ант) і дбає лише за ту інсталяцію, інформацію за яку він отримав через Опцію Вантаження (англ. Load Option). І він не універсальний вантажник, він може запускати лише Ант. Або, якшо точніше, будь яку ОС, яка використовуватиме пусковий протокол Ант.
Це один з перших записаних прогонів antload на справжній машині. І мабуть перший, де малюється графіка, де показується Екран Пуску, англ. Boot Screen. Машина — Сосна.
Далі йде відео, яке показує antload на Ксеноні. Обидва використовують uboot-івське Uefi. Цей прогін власне показує свій Екран Пуску як і попередній, але без прикладання "костилів", без яких тест на Сосні не був би успішним.
Загалом, проблемою з uboot-івським Uefi є його неповнота і можлива несумісність. На додачу, до помилок, звичайно, які, як відомо, одвічні переслідувачі будь якого ПЗ. Наприклад, на березень 2021-го, з п'яти спробуваних плат, лише Ксенон мав усе потрібне для роботи вантажника. В плані неповноти, головним подразником є часта відсутність підтримки відеовиведення, яке зазвичай йде через HDMI (на армівських ОПК мається на увазі). Або, іноді, навіть підтримуючи HDMI, uboot ще не підтримує GOP (Graphical Output Protocol). І, як результат, відеофреймбуфер для ранніх фаз ОС відсутній. Для фази вантажника, ця відсутність некритична, ми щонайменше можемо використовувати STOP (Simple Text Output Protocol) на UART, ця річ завжди імплементована. Для ОС, це було б доволі відчутним обмеженням, головним чином того, шо цей фреймбуфер нам потрібен не лише для мання відеовиведення на ранніх стадіях ініціалізації. Ще довгий час наша система не мала б графічного драйвера (графіка — один з найскладніших компонентів як в плані залізної організації так і в плані доступности документації на неї), тож ми б покладалися на факт ініціалізації дисплейного контроллера платформним кодом. Таким чином мання GOP фреймбуфера було б таким же рятівним для всіх графічних компонентів ОС, яким в часи BIOS був стандарт VGA. Відсутність GOP фреймбуфера означала б відсутність графіки взагалі, аж доки ми не заведемо собі той GPU драйвер. Також, ми б мали вписати базову підтримку Uart-у в нашу бібліотеку підсистеми ранньої графічної/консольної взаємодії, bootvid.dll, для кожного сімейства чипів. Але ми маємо емулятори, вони дуже допомагають в цьому плані, вони завжди мають GOP. Звичайно, вони допомагають з x86 теж.
Балакаючи за x86. На початках розробляння Uefi-шної Торпеди, ми мали лише одну Uefi-спроможну машину, наш домашній ноутбук (Іридій), — не зовсім тестова машина. І це на додачу до цікавого бзіка в його ППЗ.
Загалом, з неясних мені причин, тестовані машини, і справжні й віртуальні, з різних часів, виставляють два GOP зразки. Ще дивнішим є те, шо гендл, який має на собі один з цих зразків, завжди не має зразку протоколу Шляху Пристрою, англ. Device Path, не дивлячись на те, шо специфікація вимагає, шоб всі гендли мали його. Тим не менше, на всіх тестованих машинах, обидва зразки працюють як належить. На всіх, крім нашого домашнього HP Probook 4530s. Коли ти викликаєш LocateProtocol() на цій машині, функція вертає тобі саме той зразок GOP, чий гендл не підтримує Device Path, і, як виявляється, який також не малює в поточний фреймбуфер. Коли ти викликаєш його Blt(), всі виклики вертають успіх. Але малювання йдуть в якусь іншу пам'ять, не ту, яка виставлена через адресу фреймбуфера того самого зразка GOP. До речі, обидва зразки звітують ту саму адресу через поле FramebufferBase — 0xC0000000, вона належить інтелівському Intel GMA, між іншим, тут два ГП, другий AMD-шний "Радивон" — Radeon HD 7470M. Наймістишнішим є те, шо твоє малювання на екран, таки з'являється там ... на коротку мить після того, як вантажник виходе, перед тим, як Пусковий Менеджер Uefi перемальовує свій інтерфейс. Ти можеш бачити свій Пусковий Екран якісь 100 мілісекунд. Але ефект можна розтягти, бо якшо вантажник не зупинить на виході таймер з подією (англ. event), чия нотифікаційна функція відповідає за перемальовування кадрів анімованого індикатора Пускового Екрану, то цей індикатор продовжуватиме світитися прямо посеред інтерфейсу Пускового Менеджера Uefi, навіть після того, як той перемалював наш Екран Пуску! Навіть по виході, код вантажника (нотифікаційна функція перемальовувач індикатора) продовжує періодично викликатися фірмварею і малювати "фази" індикатора. Які тепер видно! Тоді як їх не було видно, поки вантажник бігав — все догори дригом. Писання ж у фреймбуфер прямо через надану адресу працює як має. Але якшо ти використовуватимеш другий зразок GOP, його Blt() малюватиме на екран. Схоже, шо незважаючи на звітування правильної адреси фреймбуфера, Blt() плутає куди саме малювати, коли обслуговує перший зразок свого протоколу. Припускаю, шо код функції у всіх зразків той самий, а мабуть якісь глобальні структури могли втратити узгодженість. Така ось дивна помилка. Уникнення її на стороні вантажника можливе: ти викликаєш LocateHandleBuffer() замість LocateProtocol(), а тоді йдеш і відкриваєш Device Path через OpenProtocol() на кожному вернутому гендлі в буфері. Щойно ти знайшов гендл, який підтримує Device Path, використовуй його для відкриття GOP. І це верне робочий зразок протоколу, який малюватиме на екран. Але використання цього підходу означатиме внесення в код дуже специфічного маневрування, яке не матиме жодного сенсу для машин, які не мають цього багу (проте можуть мати якісь свої). Тож ми лишили все як є, будучи задоволеними самим фактом, шо знайшли обхід. Бо знову ж, ConOut працює, фреймбуфер для Ант є, саме там він і буде використаний через адресу, адже не забувайте — ОС не може викликати Blt(), лише використовувати відзвітовані параметри — адресу, роздільну здатність, байтів на сканлінію тощо; і чесно, на таких ранніх стадіях розробляння, малювання Пускового Екрану вантажником протривало б якісь мікросекунди, навряд було б помітним. Гаразд, якшо ОС не перемальовує статичні елементи Екрану Пуску, а лише дбає за індикатор, то їх не буде видно на ініціаліазації теж, лише індикатор. Саме невидність намальованого вантажником є наслідком цієї помилки.
Пізніше, в жовтні 2020-го, ми купили Xiaomi RedmiBook 16, Галій, і протестували antload на ньому теж. Нижче йде фото цього успішного тесту.
Коротеньке відео долі з ганебною спробою пояснювального супроводу ByMe™ є дещо особливим, оскільки воно показує тест на rv64 архітектурі, яка, ще пару місяців назад, навіть не розглядалась як цільова (не було машин, не було здатних генерувати PE файли компілятори — не дивно, шо не розглядалась). А тепер, ми маємо робочий antload, який показує Екран Опцій (в цій конфігурації не було GOP-у, тож демонстрація мала задовільнятися UART-івськими красотами). Конфігурація: qemu-івська virt машина з uboot-ом як ППЗ. Я написав утиліту (Pegen), яка переробляла ELF образ в PE, в т.ч. генеруючи базові переміщення (релокації)! Тож, таким чином, ми спромоглися створити робочий Uefi вантажник для цієї нової архітектури. Це було серпня 2021-го. Відео показує прогін.
І хоча станом на 2023-й, rv64 є поза списком підтримки, це може змінитися. Наприклад Microsoft випусте підтримку rv64 в MSVC.