Як працює LinuxCard: власний завантажувач, емуляція та патчі ядра (Частина 2)

19.08.2025 4 хвилин Автор: Lady Liberty

У другій частині ми заглиблюємось у технічні деталі LinuxCard — візитки, що запускає Linux. Автор пояснює, як працює власний завантажувач без традиційного PROM, як реалізовано мінімальний набір служб для запуску ядра та чому SD-карта використовується як основне сховище. Детально розглядаються оптимізації емулятора uMIPS, створення драйвера паравіртуалізованого диска, кешування інструкцій та багаторівневий доступ до оперативної пам’яті. Також описані патчі до ядра Linux 4.4.292, що зменшують його розмір і прискорюють роботу, а ще — експерименти із запуском Ultrix та навіть NetBSD. LinuxCard доводить, що з мікроконтролера ATSAMD21 і кількох мікросхем пам’яті можна побудувати унікальну платформу, здатну запускати кілька ОС у форматі кишенькового проєкту.

Як це працює

Як завантажується звичайна DECstation

Зазвичай за фізичною адресою 0x1fc00000 є вбудований ПЗП об’ємом 256 КБ ( який DEC називає PROM ) , який містить достатньо коду для відображення повідомлень на екрані та прийняття вводу з клавіатури, зв’язку з пристроями SCSI, завантаження файлів з диска в оперативну пам’ять та переходу до них. Цей PROM також надає багато послуг завантаженій операційній системі через масив зворотних викликів. Це включає такі речі, як ведення журналу консолі, змінні середовища, що підтримуються EEPROM, інформація про відображення пам’яті тощо. Це досить схоже на UEFI. Зазвичай цей PROM зчитує змінні середовища з EEPROM, які повідомляють йому, з якого пристрою завантажуватися, а потім завантажує ядро та завантажується з цього пристрою, якщо все пройде добре. Цей емулятор не завантажується таким чином.

Як завантажується uMIPS

У мене не було бажання включати великий ПЗП в емулятор, оскільки обсяг флеш-пам’яті в мікроконтролері обмежений. У мене також немає графічної консолі чи клавіатури як такої. З огляду на це, мені довелося якось реалізувати значну підмножину ПЗП , оскільки MIPS Linux використовує її. Що робити? Я вирішив придумати власний процес завантаження, який все ще може працювати так само добре. ПЗП дійсно є за адресою 0x1fc00000 . Це необхідно для перезавантаження з Linux. Його вихідний код знаходиться в каталозі “romboot”. Він завантажує перший сектор SD-карти на початок оперативної пам’яті за адресою 0x80000000 і переходить до нього. Перший сектор SD-карти містить стандартну таблицю розділів MBR та до 446 байт коду. Код, який знаходиться тут, можна знайти в каталозі “mbrboot”. Він також досить простий. Він шукає в таблиці розділів розділ з типом байт 0xBB . Якщо не знайдено, відображається помилка. В іншому випадку розділ повністю зчитується в оперативну пам’ять за адресою 0x80001000 , а потім переходить до нього. Цей розділ може бути довільно великим, і саме там знаходиться моя реалізація “PROM”. Фактичне обмеження його розміру встановлюється тим фактом, що MIPS Linux очікує завантаження за адресою 0x80040000 . Це не випадково – перші 192 КБ оперативної пам’яті зарезервовані для використання PROM , поки операційна система очікує використовувати служби PROM . Таким чином, обмеження розміру завантажувача становить 188 КБ.

Код моєї реалізації PROM можна знайти в каталозі “loader”. Він шукатиме на SD-карті розділ, позначений як активний, спробує змонтувати його як FAT12/16/32 та шукатиме файл під назвою “VMLINUX” у кореневому каталозі. Якщо його знайдено, він буде розібраний як ELF-файл, належним чином завантажений та запущений. В іншому випадку буде показано помилку. Оскільки цей код не має серйозних обмежень за розміром, він реалізує належну можливість ведення журналу в консоль, printf та всілякі подібні зручності. Що стосується служб PROM , то вони забезпечують ведення журналу в консолі, інформацію про мапування пам’яті та читання змінних середовища, принаймні достатньо, щоб Linux був задоволений. Я ще не пробував завантажувати інші операційні системи на uMIPS (ще?).

Командний рядок ядра, який я передаю, досить простий: earlyprintk=prom0 console=ttyS3 root=/dev/pvd3 rootfstype=ext4 rw init=/bin/sh . Перший параметр забезпечує ведення журналу раннього завантаження через консоль PROM , що корисно бачити. Після запуску ядра воно використовуватиме третій послідовний порт для консолі. Спочатку для DECstation це був послідовний порт принтера, але користувачі Linux на DECstation використовують його для послідовної консолі, оскільки це найпростіший порт для перетворення на простий послідовний порт. Решта просто вказує ядру, як завантажитися. Я надаю перевагу завантаженню в sh, а потім самостійно виконувати exec init , таким чином init=/bin/sh

Як працює uMIPS

Після всіх оптимізацій (про які я детально розповім трохи пізніше) ефективна швидкість мого віртуального MIPS R2000/R3000 на цьому пекельному чіпі ATSAMD21 становить близько 1,2 МГц. Процесор витрачає близько 8% свого часу на обробку переривань таймера, і таким чином близько 1,06 MIPS циклів процесора залишається для корисної роботи. Завдяки цьому ядру потрібно близько 2 хвилин для завантаження та запуску sh . Виконання init busybox та перехід до запиту входу займає ще хвилину. Загалом непогано. Команди відповідають миттєво або протягом кількох секунд. Компіляція програми на C “hello world” gcc займає близько 2 хвилин, і я оцінюю, що за кілька днів можна було б перебудувати ядро на самому пристрої, скопіювати його в /boot та перезавантажитися.

Емульований годинник реального часу насправді показує реальний час, плюс-мінус неточності наднизькоенергетичного таймера ATSAMD21 з частотою 32 кГц. Це достатньо нормально, щоб ви цього не помітили. Спробуйте команду uptime .

Є лише одна річ, яку я ще не обговорив щодо запуску Linux на uMIPS. Це пам’ять. Я сказав, що це SD-карта, але DECstation точно не має слота для SD-карти. Однак Linux має відкритий вихідний код. Я просто створив свій власний дуже простий драйвер паравіртуалізованого диска, який використовує гіпервиклик для безпосереднього зв’язку з емулятором та запитує сектори для читання або запису безпосередньо у віртуальну оперативну пам’ять. Для Linux це виглядає так само, як DMA, тільки миттєво. Вся реалізація драйвера займає менше 200 рядків коду та може бути помічена у pvd.patch.

Зміни в Linux

Я вніс деякі зміни до ядра, щоб спростити життя. Вони надаються як патчі для ядра 4.4.292, а також як робочий образ ядра. Чому саме ця версія? Тому що, коли я починав цей проект, це була LTS-версія ядра, а оскільки оперативної пам’яті мало, я хотів мати якомога менше ядро, тому це було краще, ніж пізніша версія. Конфігурація, яку я використовую, доступна у kernel_4.4.292.config . Конфігурація для ще меншого ядра (яке вимагає uMIPS для емуляції повного FPU) доступна у kernel_4.4.292.config_nofpu.

Я доклав багато зусиль, щоб зробити ядро якомога меншим. Оскільки Linux не підтримує виведення фрагментів ядра на сторінки, кожен байт коду ядра стає на один байт меншим, доступним для використання в користувацькому просторі. Я безжально видалив непотрібні опції. Зрештою, мені вдалося зменшити розмір ядра до трохи менше 4 МБ, що досить непогано, враховуючи, що інструкції MIPS не дуже щільні.

В рамках цієї роботи я зробив кілька патчів коду. З різних причин (хм… слоти затримки… хм) ядру може знадобитися інтерпретувати код простору користувача або розбирати інструкції простору користувача. Незалежно від того, які конфігурації ядра я надавав, код для обробки microMIPS (майбутнє розширення MIPS, невідоме за часів R2000/R3000) був присутній. Це витрачало місце та час, намагаючись обробляти речі, які ніколи не відбудуться. Патч useless_exc_code.patch видаляє цей код, якщо цільовий процесор не підтримує microMIPS.

Перш ніж я реалізував свій емулятор FPU, я використовував код емуляції FPU ядра, який перехоплює та виконує інструкції FPU. У ньому була помилка. Якщо його компілювати для 32-бітного процесора MIPS, він неправильно емулював деякі інструкції FPU, які працюють з типом double. Я вважаю, що це неправильно. Це спричиняло збої в коді, скомпільованому для R3000. Патч fpu.patch змінює емулятор MIPS FPU ядра, додаючи опцію конфігурації, щоб увімкнути повну емуляцію FPU навіть на чіпах MIPS-I.

Через відмінності між R2000/R3000 та R4000, ядро під час збірки повинно знати, для якого процесора воно збирається. Якщо ви спробуєте запустити неправильний тип ядра на неправильному типі процесора, це лише заведе вас до паніки. Добре, гаразд, але тоді чому цей прапорець не впливає на значну частину коду обробки TLB? Обидва типи завжди компілюються, незважаючи на те, що ми знаємо під час збірки зі 100% впевненістю, що принаймні половина з них ніколи не буде корисною? Патч tlbex_shrinkify.patch огортає непотрібний код перевірками на тип процесора, вибраний під час компіляції, і таким чином видаляє частину коду ядра, заощаджуючи цінні байти.

Оскільки uMIPS працює зі справжнім годинником реального часу, я не хотів, щоб Linux витрачав забагато часу на обробку переривань таймера. Зазвичай на DECstations у Linux використовується таймер 128 Гц. Я також додав опції для тактів таймера 64 Гц, 32 Гц та 16 Гц. Це зменшує ефективну роздільну здатність таймера, але фактично розвантажує віртуальний процесор від необхідності витрачати більшу частину часу на обробку переривань таймера. Патч clocksrc.patch робить це, а той, що називається kill_clocksrc_warning.patch , замовчує безглузде попередження про роздільну здатність таймера.

Якщо ви збираєте uMIPS з повною емуляцією FPU, також існує патч для видалення всього коду емуляції FPU з ядра, щоб заощадити кілька КБ оперативної пам’яті: fpu.patch.

Покращення продуктивності

Кеш інструкцій

Одна з речей, яку процесор точно робитиме кожного циклу, це вибірка інструкції. Це означає, що кожен цикл починається з доступу до пам’яті. Для нас це болюча тема через блок SPI Atmel, що містить помилки. І не тільки це, також має відбуватися трансляція пам’яті, а це також займає час. Гарним способом уникнути обох цих проблем є кеш інструкцій VIVT. Він зчитуватиме інструкції по 32 байти за раз і, сподіваємося, часто не потребуватиме трансляції адрес або звернення до основної пам’яті. Я виділив 2 КБ оперативної пам’яті для цього кешу. Це 32 набори 2-смугових ліній по 32 байти. Щоразу, коли змінюються відображення пам’яті, їх потрібно анулювати. Я роблю це автоматично, і таким чином код, що виконується на віртуальному процесорі MIPS, не повинен знати про це. Виміряний коефіцієнт влучань під час завантаження Linux становить близько 95%, що досить непогано для такого невеликого кешу. Геометрія була визначена експериментально шляхом профілювання часу завантаження з різними геометріями кешу. Ця була визнана найкращою.

Покращення швидкості процесора

Серія ATSAMD21 розрахована на роботу на частоті 48 МГц. Під час мого тестування вони чудово працюють до 96 МГц, а деякі окремі чіпи здатні досягати 110 МГц. Я не виявив, що жоден чіп працює нестабільно на частоті 96 МГц, тому вирішив просто працювати на частоті 90 МГц для певного запасу міцності. Це одразу ж дало мені досить серйозне підвищення продуктивності. Ні, це не зовсім 100%, оскільки (1) оперативна пам’ять SPI все ще обмежена обмеженням швидкості SPI, і (2) флеш-пам’ять має стани очікування, які довелося збільшити для вищої швидкості. Але це дало мені чесне покращення на 65%. Все ще непоганий початок. Тепер оперативна пам’ять SPI працює на частоті CPU / 6 = 15 МГц.

Покращення пропускної здатності оперативної пам’яті

Оскільки я не міг пришвидшити роботу блоків RAM SPI через некомпетентність Atmel, я вирішив розширити його! Я можу керувати чотирма блоками одночасно. Враховуючи, що кожна команда читання та запису потребує додаткових витрат, це все одно швидше, ніж один чи два. Досить влучне твердження, чи не так? Цей мікроконтролер має чотириканальні RAM! Емулятор звертається до RAM з кроком 32 байти. Самі команди читання/запису RAM мають розмір 4 байти кожна. Це означає, що для ситуації з одним мікросхемою RAM читання 32 байтів займає (4 + 32) * 8 = 288 бітів SPI. У двоканальній конфігурації знадобиться (4 + 16) * 8 = 160 бітів SPI, оскільки команда все ще має довжину 4 байти, але ми зчитуємо лише 16 байтів з кожної оперативної пам’яті, загалом 32. Для чотириканальної оперативної пам’яті ми маємо (4 + 8) * 8 = 96 бітів SPI для зчитування 32 байтів. Це покращення на 66% порівняно з одноканальним варіантом! Насправді покращення менше, оскільки чотириканальний режим взагалі не може використовувати DMA, тому він трохи повільніший. Реальні вимірювання показують, що чотириканальний режим на 50% покращує ситуацію порівняно з одноканальним варіантом. Але все ж, враховуючи цей клятий чіп, будь-яке покращення є покращенням, яке я прийму.

Але чому всі звернення до оперативної пам’яті мають розмір 32 байти? Ну, як бачите, звернення до оперативної пам’яті повільне. Типовий 32-байтовий звернення займає приблизно 140 циклів SPI, що становить близько 12 мікросекунд. Якби кожен звернення займало стільки часу, мій емульований процесор був би обмежений не більше ніж 85 000 звернень до пам’яті за секунду. Це занадто повільно, щоб бути практичним. Щось потрібно було робити. Я вирішив використовувати кеш. На жаль, мій мікроконтролер має дуже обмежений обсяг оперативної пам’яті, тому кеш мав бути невеликим. Я оцінив різні геометрії кешу та виявив, що 20-канальний двосторонній кеш з 32-байтовими лініями забезпечив найкраще підвищення продуктивності емулятора. Він отримує 91% влучень під час завантаження ядра, що є досить непоганим результатом для 1,25 КБ оперативної пам’яті. З влученням, яке займає близько півмікросекунди, та промахом, що займає близько 12 мікросекунд, додавання цього кешу покращило середній звернення до пам’яті на 87%! Так, це фактично кеш L2. Скільки ви знаєте емуляторів, які мають кеш L2, щоб приховати жахливу продуктивність обраного ними обладнання? Кеш виділяє пам’ять на читання та запис, за винятком читання та запису розміром рівно 32 байти. Вони передаються безпосередньо, оскільки це або DMA для доступу до SD-карти, або вибірки icache, які не потрібно також кешувати в цьому кеші.

Після додаткового профілювання я переписав “гарячу” частину коду доступу до пам’яті на асемблері для ще більшого прискорення. GCC, можливо, й пройшов довгий шлях з минулого десятиліття, але він все ще не може зрівнятися з рукописним асемблером. Я видалив підтримку одно- та двоканальної оперативної пам’яті, щоб також спростити “гарячий шлях”. Тож тепер вам потрібно заповнити всі чотири слоти оперативної пам’яті для завантаження карти. Якщо ви заповните різні розміри оперативної пам’яті, найменший з них диктуватиме кінцевий корисний розмір оперативної пам’яті. Корисний розмір оперативної пам’яті завжди буде в чотири рази більшим за розмір найменшого чіпа оперативної пам’яті. Це не велика проблема, DECstation постачалася з 4 МБ оперативної пам’яті, і її можна було оснастити максимум 24 МБ. Цю карту можна оснастити 32 МБ, тож ви будете жити як король! З огляду на це, через розмір ядра Linux, ви не отримаєте успішного завантаження Linux, якщо у вас немає щонайменше 6 МБ оперативної пам’яті.

Брудні хаки спеціально для Linux

Я також згадував, що через те, як працюють слоти затримки, якщо процесор приймає виняток для інструкції в слоті затримки, ядро повинно мати можливість повністю емулювати цю інструкцію або іншим чином виконати її, а потім перейти в потрібне місце? Linux використовує той факт, що MIPS не має інструкцій, пов’язаних з PC, окрім стрибків, і розміщувати стрибок у слоті затримки заборонено. Як? Замість того, щоб емулювати інструкцію слота затримки, Linux копіює її на спеціальну сторінку в пам’яті, де за нею слідує пастка. Потім Linux переходить туди в режимі користувача, щоб дозволити їй виконатися, перехоплює пастку, а потім перенаправляє виконання туди, куди воно має йти. Тепер, якщо це звучить для вас як величезний клопіт, ви маєте рацію. Що ми можемо зробити? Ну, якщо інструкція в слоті затримки викликає фактичний виняток (наприклад, незаконний доступ, або виняток поповнення TLB, або щось подібне), мало що можна зробити. Але те, що ми МОЖЕМО зробити, це не погіршити ситуацію. uMIPS не буде доставляти IRQ перед виконанням інструкції в слоті затримки гілки. У гіршому випадку це призведе до затримки IRQ на один цикл, що не впливає на коректність. Перевага полягає в тому, що таке копіювання та жонглювання інструкціями можна виконувати рідше.

Як його створити та використовувати

Будівля

А тепер, чому ви насправді сюди прийшли. Як ви можете її отримати? Ну, ви можете спробувати познайомитися зі мною особисто та попросити мою візитну картку, у мене є кілька, які я можу роздати, але крім цього, ось як це зробити.

Вам потрібно буде замовити плату у виробництві плат. Я фанат JLPCB і рекомендую їх. Файли Gerber, які я надаю, бувають двох варіантів. Один точно такий, як ви бачите мою картку, а інший без мого імені та контактної інформації :). Це чотиришарова плата, виробник плати запитає вас про порядок шарів, це: GTL, G1, G2, GBL. Принаймні, JLPCB має опції покриття крайового роз’єму золотом для кращого контакту, які називаються “золотими пальцями”, та шліфування краю плати до 45° для легшого вставки. Я пропоную вибрати обидва ці варіанти – вони безкоштовні. Не забудьте встановити товщину плати на 0,8 мм.

Поки ви чекаєте на отримання плати, вам потрібно буде замовити деталі. Вам знадобляться чотири однакові мікросхеми пам’яті (посилання є вище), ATSAMDA1E16, слот для SD-карти AMPHENOL 11400841 та регулятор MIC5317-3.3YM5TR. Вам також потрібно буде (за бажанням) замовити синій або білий світлодіод розміру 0603 для індикатора активності SD-картки. Якщо ви оберете цей світлодіод, вам також знадобиться резистор 430 Ом розміру 0603 або 0805. Крім того, вам знадобляться: 2 резистори 5,1 кОм, 1 резистор 1 кОм, 3 конденсатори 0,1 мкФ та 7 конденсаторів 1,0 мкФ. Вам також знадобиться SD-карта та будь-який програматор SWD, здатний програмувати мікросхему ATSAMD. Їх багато. Виберіть свій улюблений.

Вам також знадобиться SD-карта. 128 МБ – це абсолютний мінімум, якщо ви хочете розмістити rootfs на основі busybox. Щоб розмістити образ debian або гібрид, який я надаю, вам знадобиться щонайменше 512 МБ. Ви можете записати образ на карту за допомогою вашого улюбленого інструменту для цього. У Linux та MacOS це, ймовірно, dd , у Windows – Win32DiskImager.

Після того, як ви зібрали плату, запрограмуйте мікроконтролер за допомогою наданого бінарного файлу /emu/uMIPS.bin, і все готово!

Будуємо з джерела

Вам потрібно буде зібрати кілька речей. Вам знадобиться як ARM (CodeSourcery), так і MIPS GCC toolchain (я використовував mips-mti-linux, який знайшов в Інтернеті). Спочатку зберіть “romboot”, “mbrboot” та “loader”. Потім зберіть ядро. Я надав конфігурацію, патчі тощо. Потім вам потрібно буде зібрати емулятор. Щоб зібрати для MCU, використовуйте ОНОВЛЕННЯ : власне ім’я цілі змінено, див. оновлення далі у статті). Щоб зібрати для ПК, спробуйте make CPU=pc . Потім ви можете зібрати образ SD-карти. Вам потрібно буде скопіювати MBR з одного з моїх та змінити його, а потім скористатися mkdisk.sh для вбудовування вашого ядра, mbrboot та завантажувача. Використовуйте монтування за допомогою петлі для копіювання у вашу rootfs.

Якщо ви хочете запустити емулятор на ПК, слід врахувати кілька моментів. По-перше, Ctrl^C вб’є його :). По-друге, на відміну від версії для MCU, версія для ПК не містить завантажувача ROM у двійковому файлі, тому вам потрібно буде вказати на нього в командному рядку. Типовий командний рядок — ./uMIPS ../romboot/loader.bin ../disk.wheezy

Якщо ви ліниві

Для лінивих я пробую продати всі деталі та плату разом як комплект на tindie. Подивлюся, як піде. Підозрюю, що це виявиться величезним нудним завданням і не вартим витраченого часу, але я даю цьому шанс. РЕДАКЦІЯ: Очевидно, ні, і навіть не з поважної причини. Цитую: Будь ласка, надішліть ще раз на схвалення адміністратора, як тільки ви вкажете: Інша причина. . LOL, як щодо НІ ? До речі, якщо хтось знає компанії, які роблять для мене подібні речі (продають комплект, який я розробив), будь ласка, напишіть мені електронною поштою. Якщо вам справді ліньки, я міг би також розглянути можливість зібрати партію цих комплектів на заводі в JLPCB. Якщо ви зацікавлені, натисніть тут і дайте мені знати. Поки що жодних обіцянок.

Використання

Я надаю кілька образів дисків. Найменший — це образ на основі busybox (disk.busybox) — він маленький, швидкий і класний. Я зібрав busybox з вихідного коду для MIPS-I з такою кількістю увімкнених аплетів, скільки міг собі уявити. Другий образ — це повноцінний rootfs від debian wheezy (остання версія з підтримкою MIPS-I). Повинен попередити, що “init” debian запускає близько 3000 процесів під час завантаження, тому це займає багато часу. Якщо ви використовуєте образ диска debian (disk.wheezy), я наполегливо рекомендую просто змонтувати proc та sys, і виконувати свої дії в “sh” без запуску “init”, але це працюватиме, якщо ви це зробите… зрештою. Я також надаю гібридний образ (disk.hybrid). Він має оболонку busybox та init, але містить усі бінарні файли debian, тому речі, які не надаються busybox, все ще є та працюють, як-от gcc та vim. Це “гібридний” образ.

Користуватися LinuxCard дуже просто: вставте SD-карту, підключіть USB-C до комп’ютера та відкрийте улюблену консольну програму з послідовним портом (minicom, PuTTY тощо). Якщо журнал завантаження не відображається, спробуйте інший віртуальний послідовний порт (їх є два). У разі помилки завантаження світлодіод SD-картки блиматиме нескінченно, ви можете переглянути код для отримання детальної інформації про те, що означає різна кількість блимань.

Щойно ви побачите запит оболонки, ви можете поекспериментувати або продовжити завантаження для входу, ввівши exec init . Після цього ви зможете увійти як “root” з паролем “mipsmips”. Також на другому послідовному порту з’явиться запит на вхід. Так круто!

Версія 2

Завантаження Ultrix

Ultrix — це актуальна версія UNIX для DECstation2100/3100. Найновіша версія — 4.5, і за допомогою деяких пошуків у Google ви можете знайти ISO-образи інсталяційного носія. Вона чудово підтримує DECstation2100/3100 і навіть має інтерфейс користувача на базі X11! Метою прошивки v2 було забезпечити правильну роботу Ultrix на карті. Зрештою, це вимагало багато роботи. Мені довелося покращити точність емуляції та впровадити більше обладнання. Але це спрацювало!

Перше завантаження Ultrix

Мої перші спроби були простими – скопіювати ядро на розділ “boot” і спробувати завантажити його. Звичайно, воно не знайшло б кореневу файлову систему і запанікувало б, але я хотів побачити, як далеко я взагалі зайду. Перша перешкода була очевидною – ядро не у форматі ELF , який використовує ядро Linux і очікує мій завантажувач. Воно у старішому форматі під назвою COFF . Я відкопав документацію та почав працювати над парсером для COFF . Після невеликої роботи мені вдалося завантажити ядро та дати йому запуститися, просто щоб побачити, як далеко воно зайде. На мій подив, воно зайшло достатньо далеко, щоб зареєструвати деякі повідомлення в консолі! Невдовзі після цього воно зависло, коли запитало в моєму PROM-коді змінну середовища “scsiid0”, про яку я не знав. Непоганий початок. На цьому етапі я подумав, що приблизно через тиждень я завантажу Ultrix. Це зайняло трохи більше часу…

Ultrix був розроблений для цієї машини та розроблений для підтримки всіх її частин. Він не перевіряє наявність обладнання, оскільки знає, що DECstation2100/3100 має його мати. Він припускає, що необхідне обладнання є, і починає його ініціалізацію. Це було проблемою для мене — я все ще не емулював графіку, SCSI чи мережеву карту. Linux не підтримує їх, тому я не став цим займатися.

SCSI

Оскільки я вперше намагався емулювати SCSI, це зайняло деякий час. SCSI настільки перевантажений інженерією, що саме слово “перевантажений” не передає всієї повноти того, наскільки це можливо. Є повідомлення, команди, статуси, вибір і повторний вибір, і, о, так багато іншого. Чіп SCSI в DECstation2100/3100 дуже дивний, який DEC розробив саме для цього пристрою. Він називається SII або SMII, і я не знайшов жодної документації до нього, окрім офіційного опису в специфікації DECstation3100. Це було корисно, оскільки там перераховані біти та значення регістрів. Це був початок. Спостереження за тим, як ядро Ultrix намагається отримати до нього доступ, перш ніж воно здалося і запанікує, дало додаткову допомогу, а прочитання специфікацій SCSI-I та SCSI-II заповнило решту. Після довгої роботи здавалося, що ядро було достатньо щасливе, щоб спробувати перерахувати шину. Воно спробує вибрати кожен пристрій по порядку. Прогрес!

Звідти наступним кроком було написання віртуального SCSI-диска. Якщо ви раніше не мали справу з SCSI, то це дещо відрізняється від більшості розумних конструкцій. У розумній конструкції хост-контролер був би важкою/дорогою/складною машиною, яка взаємодіє з дешевими простими пристроями. Це має сенс, оскільки зазвичай пристроїв більше, ніж хост-контролерів. Але не тут. SCSI-пристрій керує шиною та визначає, що та робить і коли. Єдине, що може зробити хост, це запросити увагу від пристрою. Мені знадобилося трохи часу, щоб це зрозуміти, оскільки це досить навпаки. Насправді це ще складніше, оскільки цільовий пристрій може відключитися від шини, щоб виконати певні дії, а пізніше знову підключитися та продовжити транзакцію. Це справді досить складно. На щастя, дещо з цього необов’язково. Пристрій також може відповідати, не відключаючись, і мій віртуальний диск це робить. Доклавши багато зусиль, я зміг розібратися з правильним механізмом станів, щоб Ultrix дійсно ідентифікував мій віртуальний SCSI-диск та взаємодіяв з ним. Я розділив код на два шари. Нижній відповідає за основи простого існування SCSI-пристрою, а верхній – за фактичні речі, пов’язані з диском.

Пізніше код було розширено для підтримки емуляції CD-ROM, що дозволило б мені виконувати встановлення Ultrix з віртуального CD-ROM. Працюючи над цим, я помітив, що перерахування шини значно уповільнює завантаження. Проблема полягає в тому, що немає способу визначити, що «на шині немає пристрою з цим ідентифікатором». Потрібно спробувати вибрати, а потім чекати тайм-ауту. Це займало деякий час, оскільки Ultrix реалізував тайм-аут за допомогою циклу з лічильником (не використовуючи RTC), і на швидкості мого віртуального процесора це займало секунди. Рішенням був фіктивний SCSI-пристрій, який відповідає на деякі команди достатньо, щоб його ідентифікували та повідомили хосту, що у нього немає носія та він невідомого типу. Цей пристрій є «SCSI нічим».

Контролер SII має 128 КБ SRAM для передачі даних через DMA на/з пристроїв. Ідея полягає в тому, що хтось планує передачу, і вона відбувається у своєму темпі. Після її завершення виникає переривання, і дані можна копіювати в цю пам’ять/з неї. На ПК це просто – я можу виділити 128 КБ оперативної пам’яті та на цьому закінчити. На мікроконтролері у мене не так багато SRAM, тому я краду трохи пам’яті із зовнішньої пам’яті для цього та надаю віртуальній ОС менше, ніж повний обсяг. Це добре працює для Ultrix, оскільки він перевіряє обсяг пам’яті посторінково. Linux перевіряє з кроком 4 МБ, але в мене є патч allow_64K_memory_multiples.patch , який змінює його на перевіряння з меншими кроками, щоб ця крадіжка пам’яті не витрачала 4 МБ корисної оперативної пам’яті.

Linux не підтримує контролер SII SCSI, тому він продовжує використовувати пристрій PVD .

ЛЕНС

Мережева карта в DECstation2100/3100 — це LANCE. Вона частково задокументована в специфікації DECstation2100/3100, і я реалізував її достатньо, щоб задовольнити Ultrix. Вона ніколи не надсилає і не отримує жодних пакетів (я можу додати це пізніше), але вона ініціалізує та перериває за потреби. LANCE має буфер SRAM розміром 64 КБ для пакетів. Збірка uMIPS для ПК повністю підтримує це, «мікро» збірка uMIPS просто ігноруватиме записи та не здійснюватиме жодних читань цієї області, щоб уникнути витрачання 64 КБ пам’яті. Це працює достатньо добре, щоб задовольнити Ultrix. Linux не підтримує LANCE, тому я не маю уявлення, чи буде це нормально з цією конфігурацією.

ESAR

MAC-адреса мережевої карти зберігається у вбудованій EPROM під назвою “ESAR” (Ethernet Station AddRess). Вона знаходиться за тією ж адресою, що й годинник реального часу, за винятком того, що вона підключена до старшого байта кожного слова, тоді як DS1287 підключений до молодшого байта. Це дивна річ, але вона працює. Це означає, що можливі деякі дивні речі, наприклад, одночасне зчитування як ESAR, так і регістрів годинника реального часу за один раз. На щастя, зазвичай це не робиться. Дані ESAR мають деякі контрольні суми та надмірність (щоб їх правильність було легко перевірити). Я реалізував ESAR для uMIPS, призначив пристрою адресу Ethernet 66:44:22:44:66:22 та забезпечив усю необхідну надмірність та контрольні суми. Ultrix цим задоволений.

Зондування пам’яті та належний API PROM

Під час завантаження Ultrix я помітив, що він безпосередньо перевіряє обсяг оперативної пам’яті в системі. Це дивно, оскільки Linux просто запитував обсяг пам’яті з API PROM, який для цього зручно існує. Насправді це була моя помилка, оскільки я емулював набагато новіший інтерфейс PROM, ніж мав справжній DECstation2100/3100, і Linux із задоволенням його використовував. Новіший стандарт (під назвою REX) надає ОС таблицю вказівників функцій з великою кількістю API. Щоб сигналізувати про підтримку REX, також передається магічне значення. DECstation2100/3100 передували REX API та використовували інший метод надання API ОС – таблиця переходів розміщується на відомих зміщеннях від початку PROM в адресному просторі 0xbfcXXXXX . Цей API також є більш примітивним і, наприклад, не має можливості повідомляти ОС, скільки оперативної пам’яті є. Тепер частини стають на свої місця… Моя єдина проблема полягає в тому, що я не маю можливості мати величезний PROM, як я писав раніше. Мені потрібен був інший метод, щоб запропонувати цей API. Я вирішив справді мати цю таблицю переходів, але перенаправити всі переходи на адресу в області оперативної пам’яті, зарезервовану для PROM 0x80001000..0x8002ffff . Ви пам’ятаєте, що мій завантажувач ОС завантажується туди. Тепер він може надавати цей PROM API, так само, як це було з REX API. Круто! Тестування Linux також показує, що він також із задоволенням використовує цей API належним чином. Звичайно, тепер він також змушений перевіряти обсяг оперативної пам’яті. Нічого страшного. Я знайшов справжню помилку в ядрі! Хоча це означає (згідно з коментарями) перевіряти максимум 480 МБ оперативної пам’яті, насправді перевіряється лише до 30. Виправлення знаходиться у fix_mem_limit.patch .

Ультрікс Лоудер

На цьому етапі ядро завантажувалося настільки, що починала панікувати через неможливість знайти кореневу файлову систему, тому настав час знайти хороший спосіб, як це зробити. Проблема полягає в тому, що Ultrix використовує зовсім іншу систему розділення, ніж добре знайома мені MBR. “Мітка диска” Ultrix дозволяє створювати 8 “розділів”, але з деякими припущеннями, наприклад, що перший (який називається “a”) завжди є rootfs, другий (який називається “b”) завжди є swap, третій (“c”) завжди охоплює весь диск (так, він охоплює і має перекривати інші), а ще один (“g”) – /usr . Тепер, якщо це ще не було достатньо цікаво, сама таблиця розділів має знаходитися всередині розділу rootfs, і ціла низка інструментів (включно з інсталятором) припускають, що все починається з нульового сектора. Цікаво, еге ж?

Я витратив багато часу, намагаючись зрозуміти, як зробити так, щоб інсталятор не запускав rootfs з 0-го сектора, але це була втрачена справа. Велика кількість скриптів припускає, що і розділ “a”, і розділ “c” починаються з нуля. Ядро також має подібні припущення. За допомогою деяких патчів я змусив його працювати зі зміщенням, але це був не дуже хороший підхід. Я вирішив спробувати змиритися з тим, як працює Ultrix, замість того, щоб намагатися змусити його робити все по-моєму. Хоча rootfs і таблиця розділів починаються з 0-го сектора, вони обидва резервують певний простір спереду для “коду завантаження”. Зокрема, перші 16 секторів (8 КБ) завжди вільні. Я вирішив просто розмістити там свій завантажувач і навчити його розуміти мітку диска Ultrix. В рамках цієї роботи я рефакторингував завантажувач на кілька частин. Одна частина була обробником таблиці розділів. Є опція для MBR, одна для Ultrix і одна для міток дисків NetBSD. Один з них (визначається часом збірки) підключається до завантажувача за потреби. Інший модуль був бінарним завантажувачем. Існує два: ELF для Linux та NetBSD, і COFF для Ultrix. Як і раніше, лише один підключається до завантажувача за потреби. Третій модуль — це драйвер файлової системи. Є один для FAT12/16/32 (використовується для моєї послідовності завантаження Linux), один для старого UFS (для Ultrix) і один для сучасного UFS (для NetBSD). Знову ж таки, підключається лише один за потреби.

Найкрутіше те, що я можу комбінувати ці частини за потреби, щоб створити завантажувач для ОС, яку я хочу завантажити. Таким чином, завантажувач Linux — це FAT + ELF + MBR , для Ultrix — UFS.old + COFF + Ultrix disklabel , а для NetBSD — UFS.new + ELF + NetBSD disklabel . Мені було надто ліньки реалізувати належне завантаження з компакт-диска, тому встановлення Ultrix трохи дивне. Я створюю образ диска лише з ядром інсталятора (витягнутим з компакт-диска), у розділі FAT, підключаю CD-ROM до емулятора, а потім завантажуюся. Потім інсталятор перерозбиває диск. Для цього використовується ще одна комбінація завантажувача: FAT + COFF + MBR . Модульність окупається!

Змусити Ультрікс працювати

Буфер кадрів

Як тільки ядро Ultrix завантажилося належним чином, принаймні у збірці uMIPS для ПК, я дуже захотів, щоб запрацював графічний інтерфейс. Хто б ні? Фреймбуфер для цієї машини був двох варіантів. Був монохромний та 8-бітний кольоровий. Обидва також підтримували апаратний курсор. Я реалізував більшість зазвичай використовуваних режимів в апаратному забезпеченні курсора, але не реалізував жодного тестового режиму. Я емулював обидва типи фреймбуфера, і вони обидва працюють! 8-бітний фреймбуфер може відображати на екрані одночасно до 259 кольорів з 24-бітної палітри. Це не друкарська помилка. Сам дисплей може відображати 256 кольорів, а курсор має власну палітру з 3 записів, яка не обов’язково повинна використовувати будь-який з тих самих кольорів. Роздільна здатність в пам’яті становить 1024×1024, а на екрані – 1024×864. Решта пам’яті вільна для використання ОС на свій розсуд. Я краду пам’ять з основної оперативної пам’яті, так само, як і для буфера SII. 128 КБ використовується для монофреймбуфера, а цілий мегабайт — для кольорового. Палітра також зберігається у викраденій оперативній пам’яті (майже кілобайт).

Миша, клавіатура, … і планшет

Звісно, щоб це запрацювало, мені також довелося налаштувати клавіатуру та мишу. Вони взаємодіють з DECstation через послідовний порт, а протокол дещо відомий з різних уривків, доступних в Інтернеті. Мені досить швидко вдалося зібрати прийнятний емулятор клавіатури. Це не дурна клавіатура. Вона має області клавіш, дзвінок, деякі підсвічування та може підтримувати різні налаштування автоповтору для кожної групи клавіш. Насправді вона досить крута. Миша досить проста, з трьома кнопками. Я досить швидко налаштував її. Проблема з емуляцією мишей добре відома – вони є відносними пристроями, і більшість ОС застосовують прискорення до миші, коли ви продовжуєте її рухати, щоб забезпечити кращий доступ. Тепер, якщо ви використовуєте іншу ОС і передаєте їй ці прискорені рухи, вона ще більше їх прискорить. Зрештою, це призводить до безладу. Ось чому більшість рішень для віртуалізації воліють завантажувати драйвер абсолютного вказівного пристрою в гостьову систему. Я не був готовий зламувати Ultrix або шукати спосіб завантажити в нього інший драйвер миші. Але потім я помітив, що DEC писав про “графічний планшет”, який вони продавали, і який підключався до порту миші. Чи може бути, що Ultrix підтримує це? Так… Ultrix підтримує. Я написав емулятор для планшета, і він чудово працював – більше жодної надмірно прискореної миші для мене! Чудово!

Патчі

Ultrix припускає, що він завантажується на реальній DECstation2100/3100, і це включає очікування наявності кешів у процесора. Мій віртуальний процесор не надає доступ до кешів гостьовій ОС, і хоча Linux чудово з цим справляється, Ultrix цього не робить. Він правильно зондує кеш і знаходить його розмір рівним нулю. Але в r3_kn01flush_cache є логічна помилка , де якщо розмір кешу дорівнює нулю, він потрапляє в майже нескінченний цикл. Оскільки uMIPS не надає доступ до кешу, має сенс перетворити цю функцію на просто повернення. Є ще одна цікава функція: kn01delay . Вона використовується для коротких затримок очікування зайнятості під час роботи з обладнанням. Усе наше віртуальне обладнання є миттєво швидким, тому затримки не потрібні. Поки я виправляю ядро, можна зробити його швидшим. Є також третя область, яка мене цікавить, – періодичний таймер. У Linux я зміг змінити позначку на 16 Гц, але я не можу зібрати Ultrix з вихідного коду, тому не можу легко його змінити. Ultrix використовує такт 256 Гц. За такої частоти на обладнанні uMIPS ми ніколи не виконали б жодної корисної роботи, обробляючи лише переривання. Я спробував налаштувати Ultrix на використання таймера 16 Гц та правильно його врахувати. Це не працює – трапляються математичні помилки. 64 Гц працює, але це все ще занадто часто для того, щоб обладнання uMIPS було корисно швидким. Зрештою, я налаштував код ініціалізації, щоб встановити таймер на 16 Гц, але код врахування діяв так, ніби це 64 Гц. Це означає, що “реальний час” в Ultrix працює в 4 рази повільніше, ніж фактичний реальний час, але це не дуже важливо. Просто майте на увазі, що сплячий режим 1 затримає 4 секунди, а не 1.

То як взагалі застосовувати такі патчі? Як знайти потрібні місця для патча? Я витратив БАГАТО часу на вивчення ледь документованого формату символів, що використовується в ядрі Ultrix. Це спрацювало! Я створив для нього робочий парсер і зміг правильно ідентифікувати потрібні мені символи та вилатати ті місця, які потребували виправлення. Це було добре, поки я не зрозумів, що хоча ядро інсталятора постачається з символами, ядро, встановлене для першого завантаження, їх не має (після першого завантаження ядро перекомпілюється знову з вибраними вами опціями, і ця версія ДІЙСНО містить символи). Відсутність символів означає, що я не можу використовувати їх для пошуку потрібних місць для патча. Я обрав інший метод – двійкове зіставлення. Шукайте правильний набір байтів поспіль, він має бути унікальним у ядрі. Якщо ви знайдете лише один випадок – це правильний. Щоб заощадити місце в завантажувачі (оскільки він обмежений 8 КБ), я розумно стискаю “шаблон для пошуку”. Круто. Це останній підхід, який я використав, і ви можете побачити його у loadUltrix.c .

Покращення в емуляторі

Покращення USB

Після довгих пошуків в Google я дізнався про дескриптори асоціації інтерфейсів. Виявилося, що без них Windows не завантажуватиме драйвери USB CDC-ACM для пристрою. Після їх додавання Windows коректно завантажуватиме драйвер, і він відображатиметься як COM-порт. Я також дізнався про особливі способи, якими Windows перераховує пристрої. Іноді вона запитує дескриптор, повідомляючи, що прийматиме 64 байти, але після отримання лише одного 8-байтового пакета вона скидає шину. Це ламало мій код USB, і тепер це виправлено. Windows тепер правильно підтримує uMIPS і показує його як два COM-порти. Чудово!

Більше покращень продуктивності

В кінці емуляції кожної інструкції емулятор переходить “на початок”, отримуючи нову інструкцію для виконання. У більшості випадків перед цим виконується перевірка на наявність переривання. Цей перехід здійснювався за допомогою BL – єдиної гілки з великою відстанню, доступної на Cortex-M0. Він займає 3 цикли. Перевірка включала завантаження байта з пам’яті (2 цикли), перевірку, чи він дорівнює нулю (1 цикл), і перехід до коду створення винятку переривання, якщо так (1 цикл, якщо ні – найпоширеніший випадок). Це означає, що весь крок “перехід і початок обробки наступної інструкції” займав 6 циклів. Я хотів якось пришвидшити його. Я вирішив, що якщо я можу звільнити регістр, я можу. Деяка переробка звільнила r11 . Є параметр, який ви можете передати gcc, щоб вказати йому не використовувати заданий регістр у жодному компілюється коді C: –ffixed-r11 . Тепер, коли цей регістр ніким ніколи не використовується, ми можемо зробити хитру річ. Ми зберігаємо в ньому адресу мітки “завантажити наступну інструкцію та виконати її”. Тепер ми можемо перейти до нього, використовуючи лише bx r11 . Це займає лише 2 цикли — 4 цикли економляться на віртуальну інструкцію — значне прискорення. Але що робити, якщо у нас є віртуальне переривання для повідомлення? Щоразу, коли у нас є таке для передачі, ми просто встановлюємо r11 на мітку “повідомити віртуальне переривання”, і щоразу, коли емуляція поточної віртуальної інструкції завершується, переривання буде повідомлено, а r11 буде скинуто. Потрібно трохи більше механізмів, щоб це запрацювало, але це все, загалом кажучи, і це працює!

Я також змінив принцип роботи хешу TLB (з таблиці 32-бітних вказівників на таблицю 8-бітних індексів), щоб зменшити розмір таблиці та кожного запису (з 24 байт до 16). Це заощадило трохи менше кілобайта оперативної пам’яті, яку я зміг виділити для кешу L2. Тепер він зріс з 1,25 КБ до цілих 2 КБ, що значно покращує продуктивність!

Швидкий шлях видалення заправки TLB

Для Linux я реалізував пришвидшений шлях для коду поповнення TLB — він виконував у нативному коді те, що робив би обробник поповнення TLB. За моїми вимірюваннями, це дещо покращило продуктивність. З усіма іншими покращеннями продуктивності, які я впровадив, це більше не пропонувало помітного покращення. Крім того, це взагалі не допомогло Ultrix, за визначенням. Його видалення заощадило місце на флеш-пам’яті та усунуло складність. Менша складність завжди краще. Його більше немає.

Зміни геометрії кешу

Раніше, під час профілювання для пошуку найкращої геометрії L1i, я використовував процес завантаження Linux. Я вирішив спробувати більше. Тепер я профілював це, компілюючи gcc деякий код, кілька інших бінарних файлів Linux, завантажувальний процес Ultrix та деякі утиліти користувацького простору Ultrix. Результатом цього дослідження стало те, що кеш L1i з прямим відображенням трохи швидший, ніж двосторонній кеш L1i. Частота влучань дещо знижується, але перевірка лише одного рядка кешу замість двох пришвидшує перевірку достатньо, щоб компенсувати це. Таким чином, я переналаштував кеш як кеш з прямим відображенням.

Серійні вдосконалення

Раніше емулятор чекав фіксовані 20 мс, щоб відправити символ на ПК, перш ніж здаватися. Я змінив це на постійне очікування головної консолі. Це дозволяє користувачеві не пропускати жодного виводу, якщо він закриває термінал. Емулятор також показує свою версію одразу, оскільки тепер її точно не буде втрачено. Починаючи з прошивки версії 2.1.1, uMIPS також показує конфігурацію оперативної пам’яті з точки зору кількості мікросхем, розміру кожної мікросхеми та розрядності інтерфейсу для кожного мікросхеми.

Більше роботи з модулями з плаваючою комою

Я вже реалізував повний віртуальний FPU, але тепер хотів побачити, наскільки це насправді необхідно. Я знав, що Linux працюватиме, якщо я взагалі не емулюватиму FPU, і емулюватиме його. Я хотів побачити, чи запуститься Ultrix. Він не запустився — він зламався через перехоплення недійсної інструкції в ядрі. Це не було так вже й дивно. Знову ж таки, він був скомпільований для конкретної машини — машини, яка мала FPU. Його припущення про існування FPU було розумним. Але ще потрібно було дослідити більше. Специфікація MIPS говорить, що FPU може відмовитися виконувати будь-яку інструкцію, якщо він не впевнений, що може виконати її ідеально точно. Оскільки специфікація не зовсім зрозуміла, що це насправді означає, практично будь-яка ОС, що працює на такому чіпі MIPS, повинна реалізувати повний резервний варіант FPU, здатний емулювати будь-яку інструкцію FPU. Але тоді чому я стикаюся з винятком?

Хитрость полягає в тому, що FPU все ще повинен існувати, він повинен відмовлятися виконувати математичні операції. Це кардинально відрізняється від повної відсутності. Таким чином, я реалізував “мінімальний” FPU. Він реалізує інструкції для самоідентифікації, переміщення даних у регістри з плаваючою комою та з них, а також завантаження та зберігання регістрів з плаваючою комою в пам’ять. Будь-які спроби виконати фактичні математичні операції з плаваючою комою повідомляють про “виняток використання співпроцесора”, що є правильним способом для FPU відмовитися від математичних операцій. Це спрацювало правильно для Ultrix – тепер він не аварійно завершує роботу під час завантаження, усі програми, які виконують математичні операції з плаваючою комою, все ще працюють, а ядро емулює математичні операції. Я перевірив, і Linux також підтримує цю конфігурацію. Таким чином, uMIPS тепер має три конфігурації FPU, за допомогою яких його можна зібрати: повну , мінімальну та жодну .

Завантажувач

Зі збільшенням кількості цих карток, історію оновлень потрібно було покращити. Не у кожного є CortexProg для перепрошивки прошивки. Я вирішив зробити це простим і вимагати якомога менше взаємодії з користувачем. Завантажувач має розмір трохи менше 3 КБ, я виділив йому 4 КБ флеш-пам’яті та переніс основну прошивку для запуску 4 КБ у флеш-пам’ять. Отже, як це працює? Під час завантаження завантажувач мінімально ініціалізує SD-карту, намагається знайти на ній розділ FAT16, перевіряє, чи містить він файл правильного розміру під назвою FIRMWARE.BIN , і якщо так, прошивка буде прошита з цього файлу. У разі помилки номер помилки буде блимати на світлодіоді багаторазово. У разі успіху світлодіод зі змінною частотою світла буде повторюватися нескінченно.

Якщо карту не вдається ініціалізувати, якщо її не вдається змонтувати, якщо файл оновлення не існує або якщо його розмір неправильний, завантажувач продовжить завантаження існуючої прошивки, якщо така існує (виконується деяка перевірка на справність). Це означає, що коли ви вставите карту з моїм образом Linux або образом Ultrix, все працюватиме належним чином. Підтримується лише FAT16, тому для більших карт може знадобитися певний розбивка. Я можу з цим змиритися.

Покращення апаратного забезпечення

апаратне забезпечення версії 1.3

Після прочитання моєї оригінальної статті, кілька людей написали (зокрема в розділі коментарів тут, у твіттері та електронною поштою) з пропозицією, що, можливо, мені варто повністю відмовитися від цих жахливих SPI-модулів у цьому чіпі. Спочатку я хвилювався, що проблема зі швидкістю SPI-модулів насправді пов’язана зі швидкістю порту вводу-виводу, але швидкий тест показав, що я можу надійно перемикати контакти на половині тактової частоти мого процесора та отримувати гарні прямокутні ребра. Я створив прототип SPI з біт-бенкінгом на існуючій платі, щоб побачити, яких швидкостей я можу досягти, і це виявилося багатообіцяючим. Потім я розгорнув нову плату з іншою розводкою, щоб мати змогу використовувати режим QSPI. Зображення для нових схем та розміток ви бачите тут!

Серія ATSAMD21 має порт вводу-виводу з одноцикловою швидкістю. Ця додаткова функція Cortex-M0+ досить корисна для обробки бітів. Вона дійсно швидка за один цикл. Звичайне завантаження та зберігання займає щонайменше два цикли на Cortex-M0+, але для таких пристроїв потрібно лише один. Саме так я міг перемикати контакт на половинній швидкості процесора для мого тесту, про який я щойно згадував.

З “великим вибухом” (big-banging) секрет полягає в тому, щоб виконувати якомога менше операцій за цикл. Враховуючи це, ідеально було б мінімізувати “перемикання бітів”. Було б суперкруто, якби я міг підключити чотири мікросхеми QSPI до GPIOS з номерами 0..15, що дозволило б мені лише читати/записувати нижні 16 бітів порту GPIO для простого доступу. На жаль, цього не мало бути задумано. Цей чіп не має 16 суміжних контактів GPIO, підключених до фізичних контактів, тому я обрав підключення RAM0 до GPIO0..3, RAM1 до GPIO4..7, RAM2 до GPIO8..11 та RAM3 до GPIO14..17. Оскільки я буду керувати ними всіма разом, лінії тактової частоти та вибору мікросхеми з’єднані разом. Зрештою, після того, як збірка була закодована, і пил осів, мені вдалося отримати середню тактову частоту близько 9 МГц. Оскільки команда та адреса також надсилаються з шириною 4 біти, збільшення швидкості є приємним. Раніше (при використанні апаратного SPI) читання/запис 32 байт займав близько 8 мікросекунд, тепер — трохи менше 4 мікросекунд. Непогане прискорення.

Уважний читач може помітити, що перші три оперативні пам’яті ЗНАХОДЯТЬСЯ на послідовних контактах GPIO. Три нам не дуже корисна, оскільки це не степінь двійки, а два … Так, дійсно, використовуючи лише дві оперативні пам’яті, я можу досягти вищих швидкостей (але з половинною шириною). Фактичний час читання/запису 32 байтів становить близько 5 мікросекунд. З огляду на це, я вирішив знову додати раніше видалену підтримку використання менше 4 оперативних пам’ятей на платі. І я це зробив. Найновіша прошивка тепер підтримує 1, 2 або 4 оперативні пам’яті, встановлені на нових платах. Потім я пішов далі і знову додав цю підтримку для старих плат. Це не так добре оптимізовано – це на C, а не на ASM, але достатньо добре, щоб поекспериментувати. Це дозволить зібрати ці плати дешевше. Крім того, Ultrix з радістю завантажується та працює з 4 МБ (хоча для запуску графічного інтерфейсу йому потрібно 5 МБ).

І старе обладнання також

Я не хотів підтримувати дві окремі, але майже однакові гілки коду для старішого обладнання v1.2 та нового обладнання v1.3. Також не було простого способу відрізнити їх програмно з першого погляду. Але трохи детальніше дослідження дає уявлення. Підключення оперативної пам’яті достатньо відрізняється, щоб ми могли спробувати кожен спосіб і подивитися, чи виявимо ми правдоподібний чіп оперативної пам’яті. Допомагає те, що незаповнення RAM0 ніколи не підтримується. Власне, саме це я і зробив. Я спробував обидві конфігурації та подивився, яка з них видає правильний ідентифікатор з RAM0. Звідти всі чотири оперативні пам’яті досліджуються, ідентифікуються та вибирається конфігурація.

Підтримка менше ніж 4 заповнених оперативних пам’ятей викликає кілька цікавих питань. Щодо швидкості, всі оперативні пам’яті обробляються так, ніби вони однакового розміру, тому розмір найменшої оперативної пам’яті визначає загальний обсяг доступної оперативної пам’яті. Звичайно, це тому, що я розподіляю дані по всіх них. Отже, що, якщо RAM0 заповнена 8 МБ, а RAM1 – 2 МБ? Ми можемо використовувати лише RAM0 та отримати 8 МБ оперативної пам’яті, або ми можемо використовувати обидві та отримати лише 4 МБ, але швидше, оскільки більше оперативної пам’яті паралельно завжди швидше. Я вирішив, що більше оперативної пам’яті краще, ніж швидша оперативна пам’ять, тому у разі таких конфліктів завжди вибирається більше оперативної пам’яті. Коли є нічия, використовується швидша конфігурація, наприклад: 4 МБ, 1 МБ, 1 МБ, 1 МБ заповнені оперативні пам’яті дають у сумі 4 МБ в обох конфігураціях x1 та x4. У цьому випадку буде обрано конфігурацію x4, і будуть використані всі оперативні пам’яті.

Збірка з вихідного коду (оновлено)

Емулятор

Новий параметр під назвою FPU тепер передається до збірки uMIPS для визначення бажаного типу FPU. Варіанти: none – FPU взагалі відсутній, Ultrix це не сподобається, але він створює найменше зображення; minimal – FPU, який може зберігати значення, але відмовляється виконувати математичні обчислення – Ultrix та Linux підтримуватимуть це, він трохи більший; та full – повний FPU, який виконує всі математичні обчислення – найшвидший варіант, який роздуває зображення Cortex-M0 приблизно на 17 КБ.

Вантажник

Щоб зібрати відповідний завантажувач, передайте параметр BUILD команді make . Доступні варіанти: linux , ultrix , ultrix_install або netbsd . Завантажувач install призначений лише для чистої інсталяції, яку вам не потрібно робити, оскільки я вже зробив це за вас. Завантажувач netbsd призначений для спроби завантаження NetBSD на цій машині, оскільки він підтримується NetBSD. Для робочої системи відповідний завантажувач потрібно зібрати та інтегрувати в образ диска.

Етап інтеграції також змінився: mkdisk.sh зник, його замінили кілька різних інструментів, залежно від цільової системи. Це: mkdisk-linux.sh , mkdisk-netbsd.sh , mkdisk-unix.sh та mkdisk-unixinstall.sh . Unix тут, звичайно, означає Ultrix. Скрипти невеликі та зрозумілі самі по собі. Відкрийте їх для отримання додаткової інформації. Усі вони працюють на образі диска під назвою “disk”.

Щоб увімкнути графічний інтерфейс в Ultrix, потрібно правильно встановити змінну середовища “console”. У loader.c знайдіть її та встановіть для неї значення “0,0” для текстового режиму або “1,0” для консольного режиму.

Подальші оновлення

Прошивка версії 2.1.1

У цій версії доступ до пам’яті BBQSPI пришвидшився на 11% для 4-чіпового корпусу та на 6% для інших. Конфігурація оперативної пам’яті відображається під час завантаження.

Прошивка версії 2.2.0

У цій версії завантажувач було оновлено для кращої підтримки інших компонентів ATSAMD21, включаючи ті, що мають більше флеш-пам’яті та оперативної пам’яті. Тепер він також надає байт версії за зміщенням 0x08 . Попередній завантажувач мав версію 0x10 , що робить цю версію 0x11 . Тепер версія буде відображатися на послідовній консолі під час завантаження.

Також, оскільки ATSAMDA1E16, очевидно, зараз ніде немає в наявності, я додав підтримку для ATSAMD21E17A-AU / ATSAMD21E17A-AUT . Сумна новина полягає в тому, що ця неавтомобільна деталь розганяється не так добре. Вона стає нестабільною набагато вище 76 МГц, тому я вирішив встановити її на частоті 72 МГц. Вона має більше оперативної пам’яті (16 КБ), що дозволило мені виділити набагато більше пам’яті для кешів L1i та L2. У більшості вимірювань втрата продуктивності через нижчу швидкість компенсується перевагами більшого кешу.

Щодо продуктивності, я також переписав код кешу L2 на асемблері для збільшення швидкості та розміру. Приріст швидкості значний. Для додаткової швидкості тепер є опція перенесення фактичних функцій доступу до оперативної пам’яті (яка швидша за флеш-пам’ять). Це збільшує швидкість на 8%, але ціною використання оперативної пам’яті. На старих компонентах з 8 КБ оперативної пам’яті це не завжди виправдано, оскільки потрібно зменшити розмір L2 з 2 КБ до 1,625 КБ, щоб звільнити місце. Однак на нових компонентах з 16 КБ оперативної пам’яті це того варте. Слід зазначити, що існує 6 варіантів функцій низького рівня доступу до оперативної пам’яті, оскільки є 2 можливі типи доступу (SERCOM або з розрядністю) та 3 можливі кількості чіпів (1, 2 або 4). Тільки ті, які ви плануєте використовувати, потрібно перенести в оперативну пам’ять. Інші все ще працюватимуть з флеш-пам’яті, якщо ви хочете створити універсальну прошивку. Прошивка, яку я надаю, тепер переносить 4-чіпові функції з розрядністю в оперативну пам’ять для ATSAMD21E17. Див. RAM_FUNCS_IN_RAM у Makefile та вміст spiRamAtsamd21.c.

Під час переміщення функцій до оперативної пам’яті легко випадково використати забагато оперативної пам’яті та призвести до випадкових збоїв, оскільки стек зіштовхується з даними. Налагодження таких процесів є проблемою, тому я вирішив покращити цей процес. Як опція в Makefile тепер є можливість увімкнути STACKGUARD . Що це робить? Як останнє слово в попередньо виділеній оперативній пам’яті (і, отже, перше, яке стек переповнить), код зберігатиме магічний cookie, значення якого залежить від поточного значення ticks.hi . Це значення перевіряється та оновлюється в перериванні SysTick , яке відбувається кожні 16 мільйонів циклів. Якщо перевірка не вдається, світлодіод блиматиме, а виконання буде зупинено.

Починаючи з цієї версії, правильні заклинання make тепер такі: make CPU=atsamda1e16 та make CPU=atsamd21e17.

Завантаження оновлено новим кодом та бінарними файлами для обох типів чіпів. Їх можна оновити за допомогою завантажувача та SD-карти.

Підписатися
Сповістити про
0 Коментарі
Найстаріші
Найновіше Найбільше голосів
Знайшли помилку?
Якщо ви знайшли помилку, зробіть скріншот і надішліть його боту.