Linux на Atari Jaguar. Нет, серьёзно

от автора

Что это за чертовщина такая — Atari Jaguar?

Выпущенная в Северной Америке в ноябре 1993 года, Atari Jaguar обещала стать новым хитом благодаря своей (весьма спорной) 64-битной мощности.

Сама консоль в итоге оказалась коммерческим провалом даже после выпуска CD-дополнения Jaguar CD, которое продавалось ещё хуже в отчаянной попытке конкурировать с Sony Playstation и Sega Saturn.

Дополнение для Atari Jaguar CD

Дополнение для Atari Jaguar CD

Зачем, чёрт возьми, Linux?

Интересно, что до сих пор существует архитектурный код Linux для семейства процессоров 68000: 68040, 68030, 68010… и даже для оригинального базового процессора 68000. Всё аккуратно структурировано в папке arch/m68k/.

Напомним, что Motorola 68000 был CISC-процессором со смешанными 16- и 32-битными возможностями (его обычно описывают как 32-битный внутри из-за ширины регистров и 16-битный, потому что шина данных была 16-битной, а за раз передавались 2 байта).

Он имел 24-битную адресную шину, 2 в степени 24; таким образом, максимальный объём адресуемой памяти составлял около 16 МБ.

Процессор был выпущен в 1979 году, чтобы конкурировать со вскоре появившимися 16-битными CPU той эпохи.

Motorola 68000

Motorola 68000

В целом, он получил широкое коммерческое распространение и в итоге был включён во множество популярных коммерческих устройств. Главными претендентами являются оригинальный Macintosh (и Apple Lisa), компьютеры серии Commodore Amiga (с различными поколениями 68000), Sega Genesis/Megadrive, Neo-Geo AES, рабочие станции Plexus… и Jaguar.

Теперь давайте перейдем к главному вопросу: почему Linux?

Ну, все просто: потому что мы можем (почти).

jmp_linux?

Запуск нового порта Linux на базе 68000 должен быть простым… верно? Что ж… вас ждёт увлекательное занятие.

Как вы, возможно, знаете (или нет), существует распространённое мнение, что для работы Linux требуется MMU (вы знаете, возможность использовать виртуальную память — это хорошо, если мы хотим запускать программное обеспечение без головной боли, которая длится целый день).

Технически вы отчасти правы, но есть uClinux, который как раз и делает это возможным. В какой-то момент он перестал быть ответвлением Linux и стал его частью; к счастью, это встроено в m68k (и, конечно же, плоская модель памяти и все остальные требования, которые вы можете себе представить для системы без MMU).

Итак, мы включаем все необходимые флаги конфигурации в меню menuconfig Linux (чтобы, по сути, указать ему не использовать MMU и использовать плоскую модель памяти), компилируем, и всё должно работать, верно? Ну да… но нет.

Что же на самом деле включает в себя jmp_linux?

У Jaguar есть 2 мегабайта ОЗУ (отображается по адресу 0x000000), (до) 6 мегабайт ПЗУ (по сути, картридж), отображаемые по адресу 0x80000, и 2 пользовательских микросхемы (Tom & Jerry, «GPU» и DSP), которые также отображаются в память.

Главная проблема, с которой мы сталкиваемся, — это объём используемой памяти; хотя это и неплохой объём, он всё же не бесконечен (речь идёт о мегабайтах, а не о гигабайтах).

В принципе, нам нужно найти способ оптимизировать использование оперативной памяти. Сначала можно сделать простые вещи: удалить функции из ядра, отключить отладку… обычные методы; но, вероятно, проблема с загрузкой ядра в ОЗУ без ошибки нехватки памяти (чёрт возьми, мы даже не рассматриваем initramfs, который тоже занимает много места).

Хорошо, что Linux достаточно умён, чтобы позволить нам «разделить» ядро ​​на две отдельные области памяти. Можно воспользоваться тем фактом, что нам разрешено хранить разделы только для чтения (например, .rodata или .text) на картридже (ПЗУ), а динамические разделы (.data или .bss) в ОЗУ (например, XIP; eXecute-In-Place).

К счастью, нам достаточно указать, где находится ОЗУ и где ПЗУ, и Linux сам перераспределит память.

Круто, теперь мы можем установить Linux на Jaguar, и он должен работать… верно? Конечно, можно попробовать указать базовый путь ядра (помните, код не для PIC) и загрузить его туда, просто перейдя по ссылке $80000… но как узнать, что происходит под капотом?

Что нужно Linux для загрузки

В контексте загрузки Linux нам нужны две вещи (по крайней мере, при запуске):

  • любой тип вывода, чтобы мы могли видеть сообщения ядра;

  • любой способ управления системой (по сути, таймер).

Первое необходимое условие обычно включает в себя старый-добрый UART. DSP процессора Jaguar (Jerry) имеет контакты TXD и RXD, которые (по крайней мере, для загрузки Linux) можно перенаправить на вывод данных через последовательный порт (если не учитывать ничего, связанного со звуком). Написание небольшого драйвера консоли, управляющего этими контактами, достаточно, чтобы начать получать сообщения earlyprintk.

Второй случай немного сложнее. Но мы можем применить любой из двух таймеров, которые есть в микросхеме Jerry. Коммерческие игры и программное обеспечение используют их для задач, связанных со звуком, но мы будем задействовать их, чтобы позволить Linux калибровать и настраивать планировщик и системы, которые также зависят от PIT. Полезно, что они могут запускать прерывания как на самом Jerry, так и на 68000. По сути, мы можем переопределить инициализацию 68000, специфичную для данной платы, в Linux, и назначить её в качестве PIT-метода, который Linux будет использовать с этого момента.

Компилируем, пытаемся запустить…

Ничего?

Ладно… это странно; мы должны что-то получить? В смысле, всё в порядке… конфигурация ядра: проверено… адреса: проверено… компилятор… компилятор?

Оказывается, встроенный кросс-компилятор m68k-linux- в репозиториях Ubuntu генерирует невыровненные обращения к памяти (даже после передачи параметра -68000), поэтому он, по сути, аварийно завершал работу (поскольку базовая версия 68000 не реализует обработку невыровненных обращений к памяти).

Использование компилятора, собранного из исходных кодов, специально предназначенных для m68k-elf- для 68000, похоже, решает эту проблему, но это определённо странно.

Это также указывает на отдельную проблему. Поскольку наша ПЗУ отображается не по адресу 0x00000, а по адресу 0x80000, процессор 68000 пытается перейти к VBR (0x0), и, поскольку там нет обработчиков, он, по сути, «съедает» сам себя и выходит из строя. Для исправления этого в специфичном для платформы Jaguar коде Linux потребовалось отдельное копирование так называемых векторов в базовый адрес ОЗУ.

Мне также пришлось скомпилировать gdb из исходного кода, чтобы он работал с gdbtsub от MAME. По какой-то причине gdb-multiarch некорректно управлял заглушкой, и она прыгала по всему адресному пространству; это было забавно, пока я не решил протестировать это со сборкой, специально предназначенной для m68k.

Отлично, наконец-то получаем вывод:

Linux version 7.2.0-rc1+ (cakehonolulu@jaguar) (m68k-elf-gcc (GCC) 16.1.0, GNU ld (GNU Binutils) 2.46.1) #38 Sun Jul  5 11:56:37 CEST 2026printk: legacy bootconsole [early_jerry0] enableduClinux with CPU MC68000Flat model support (C) 1998,1999 Kenneth Albanowski, D. Jeff DionneZone ranges:  DMA      [mem 0x0000000000000000-0x00000000001fffff]  Normal   emptyMovable zone start for each nodeEarly memory node ranges  node   0: [mem 0x0000000000000000-0x00000000001fffff]Initmem setup node 0 [mem 0x0000000000000000-0x00000000001fffff]On node 0, zone DMA: 512 pages in unavailable rangesprintk: log buffer data + meta data: 4096 + 12800 = 16896 bytesDentry cache hash table entries: 1024 (order: 0, 4096 bytes, linear)Inode-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)Built 1 zonelists, mobility grouping off.  Total pages: 512mem auto-init: stack:all(zero), heap alloc:off, heap free:offSLUB: HWalign=16, Order=0-1, MinObjects=0, CPUs=1, Nodes=1NR_IRQS: 32clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 nsCalibrating delay loop... 1.04 BogoMIPS (lpj=5248)pid_max: default: 32768 minimum: 301Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)

Теперь он пытается найти и запустить процесс инициализации… которого у нас нет… поэтому он снова зависает.

Пользовательское пространство

Одно из многих наших ограничений заключается в том, что мы не можем использовать файлы ELF (мы используем плоские бинарные файлы, в основном из-за ситуации с nommu); это немного усложняет настройку набора инструментов.

Мне так и не удалось найти способ скомпилировать elf2flt (утилиту, которая, по сути, преобразует один формат в другой) в автономном режиме. Для этого требуется предоставить несколько файлов .a и .h (связанных с libiberty и akin), которые, похоже, исчезли из стандартных конфигураций binutils+gcc для 68000… Но, как ни странно, Buildroot, похоже, недавно обновился, и они всё это исправили для базовой версии 68000.

Спасибо проекту linuxmd, патч для включения Nommu для m68k датирован маем 2026 года (то есть на момент написания этого поста ему уже 2 месяца!).

Поэтому я, по сути, использовал его для решения этой задачи, а также надумал использовать Busybox, один из немногих (если не единственный) проектов такого рода, поддерживающих Nommu.

Были некоторые проблемы (в основном связанные с тем, что я никогда раньше не работал с плоскими бинарными файлами), но опять же, ничего такого, что нельзя было бы исправить несколькими минутами отладки и логирования. Самая очевидная проблема, с которой я столкнулся, — это проблемы нехватки памяти (OOM) при попытке выполнить init. Я просто сделал следующее:

❯ cat ~/jagfs/rootfs/init #!/bin/busybox sh/bin/busybox sh❯ 

Обычно вы просите busybox заполнить остальные утилиты (это просто символические ссылки на себя с разными именами; то, что они называют дизайном апплетов, где на основе argv[1] он знает, какой апплет отправить: echo, ls, cat…), но по какой-то причине это вызывало OOM в ядре; поэтому пока я использую только sh (часть shebang также выполняется, но, похоже, не занимает всю память, как —install).

Теперь у нас есть корневая файловая система, мы упаковываем её как cpio и встраиваем в сам образ vmlinux… и вуаля!

В качестве упражнения для читателя, есть несколько интересных моментов, которые вы можете проверить в Linux в отношении плоских бинарных файлов (например, существуют разные способы обработки их выполнения: загрузка всего бинарного файла в ОЗУ, перемещение GOT/PLT… всё это можно проверить с помощью flthdr).

❯ flthdr ~/jagfs/rootfs/bin/busybox/home/cakehonolulu/jagfs/rootfs/bin/busybox    Magic:        bFLT    Rev:          4    Build Date:   Thu Jul  2 03:08:09 2026    Entry:        0x44    Data Start:   0x1e0a0    Data End:     0x25808    BSS End:      0x2a1c0    Stack Size:   0x10000    Reloc Start:  0x25808    Reloc Count:  0x100e    Flags:        0x1 ( Load-to-Ram )

Довольно полезная штука…

Нам пришлось выполнить отдельную настройку библиотеки libc (uClibc) в инструментальной цепочке, которая включает изменение стратегии malloc на malloc-simple; в противном случае метаданные, связанные с более быстрым и интеллектуальным выделением памяти, очень быстро потребляют её, и мы снова паникуем.

Это изменение в выделении памяти было внесено в menuconfig uClibC в buildroot.

У меня также появилось время для реализации простого драйвера консоли Tom (GPU, но не GPU, скорее текстовый/объектный процессор и блиттер), чтобы его можно было протестировать и на реальном оборудовании.

Не забудьте, что если вы запускаете на реальном оборудовании преобразованный в бинарный файл vmlinux (с использованием objcopy), необходимо скомпилировать с фиксированным смещением от 0x80000, чтобы можно было добавить заголовок картриджа Jaguar (который, в свою очередь, перейдёт в указанное вами место). Таким образом, вам нужно добавить смещение 8 КБ к CONFIG_ROM_START, CONFIG_ROM_LENGTH (которое будет немного меньше из-за увеличения базового адреса), CONFIG_ROMVEC и CONFIG_ROMSTART.

Основная идея для воспроизведения этой настройки заключается в компиляции Buildroot, ориентированной на 68000 (с измененной стратегией malloc). Вы можете попросить Linux собрать busybox с пользовательским путём к файлу .config (указан в конце статьи), чтобы получить плоский бинарный файл. Затем вы заполняете простую файловую структуру initramfs (в интернете есть ресурсы о том, что нужно сделать, обычно это включает создание каталогов /{dev,bin,usr,sbin…} и добавление некоторой магии mknode в /dev/ для поддержки узлов /dev/console/, а также скрипт инициализации; он также приведен в конце этой статьи).

Затем вы указываете Linux, где находится ваш только что собранный файл initramfs.cpio, и вуаля!

Вот и все!

Модифицированный репозиторий Linux можно найти по адресу: https://github.com/cakehonolulu/linux_jag

В отличие от linuxmd, у нас нет специальных картографов (SSF2, который добавляет 4 МБ оперативной памяти на MegaDrive), поэтому я не смог оправдать (с точки зрения памяти) добавление какого-либо загрузчика (например, u-boot), и в основном использовал подход, описанный в блоге (по сути, это jmp_linux).

Различные конфигурационные файлы, если кто-то захочет попробовать:

Между тем разработчики представили открытый проект под названием LinuxMD. Это порт ядра Linux для Sega Mega Drive. Решение работает через флеш‑картридж EverDrive с 4 МБ ОЗУ и загрузчик U‑Boot. Проект LinuxMD создан в экспериментальных и образовательных целях, направленных на запуск ядра Linux на 16-битном процессоре Motorola 68000 (12 МГц) без аппаратного MMU.

ссылка на оригинал статьи https://habr.com/ru/articles/1056714/