プログラミング
Atari Jaguar上でLinuxを動かす。マジで。
Linux on the Atari Jaguar. No, really. (cakehonolulu.github.io)
要約
本記事は、商業的に失敗した1990年代のゲーム機「Atari Jaguar」にLinuxを移植する試みについて解説しています。MMU非搭載という制約の中でuClinuxを使用し、メモリ制限やコンパイラの問題、ハードウェア固有の課題を克服して、最終的にLinuxカーネルの起動に成功するまでの技術的な道のりが詳述されています。
全文翻訳
Atari Jaguarとは一体何だろうか?1993年11月に北米で発売されたAtari Jaguarは、(大いに議論された)64ビットの純粋なパワーのおかげで、新しいクールなマシンになると約束されていた。
Atari Jaguar
コンソール自体は商業的な大失敗に終わった。PlaystationやSega Saturnと競合しようとする絶望的な試みで、Jaguar CDというCDアドオンが発売された後でさえ、さらに少ないユニットしか売れなかった。
Atari Jaguar CD Add-on
なぜ、よりによってLinuxなのか?興味深いことに、今日に至るまで、Linuxは68000ファミリーのプロセッサ用のアーキテクチャコードを持っている。68040、68030、68010…そしてオリジナルのベース68000プロセッサさえも。すべてarch/m68k/の下にきれいに整理されている。
復習として、Motorola 68000は、16/32ビットの機能を併せ持つCISCプロセッサだった(レジスタ幅の長さから内部的には32ビット、データバスが16ビットで一度に2バイト転送されるため32ビットと説明されることが多い)。24ビットのアドレスバスを持ち、2の24乗、つまり最大16MBのメモリアドレス空間があった。1979年に、当時の16ビットCPUのリリースに対抗して発売された。
Motorola 68000
全体として、商業的に多くの支持を得て、多くの人気のあるハードウェアに組み込まれた。主な競合製品は、オリジナルのMacintosh(およびApple Lisa)、Commodore Amigaシリーズのコンピュータ(様々な世代の68000を搭載)、Sega Genesis/Megadrive、Neo-Geo AES、Plexusワークステーション…そしてJaguarだ。
さて、本題に入ろう。なぜLinuxなのか?まあ、簡単だ。なぜなら、我々にはそれができる(…というわけでもない)。
jmp _linux?
新しい68000ベースのLinuxポートの起動作業は簡単だろう…そうだろう?まあ、楽しい時間になるだろう。
ご存知かもしれない(あるいはご存知ないかもしれない)が、Linuxは実行にMMUを必要とするという考えが広くある(仮想メモリを使えることは、一日中頭痛の種なしでソフトウェアを実行したい場合に良いことだ)。技術的には、あなたは正しい部分もあるが、uClinuxがある。これはまさにこれを可能にするものだ。かつてはLinuxのダウンストリームフォークだったが、Linuxの一部になった。幸いなことに、m68k向けに組み込まれている(そして、フラットメモリモデルやMMUなしのシステムで想像できるその他の要件も)。
さて、Linuxのメニューコンフィグで必要なすべての設定フラグを有効にし(基本的にMMUを使わず、フラットメモリモデルを使用するように指示する)、コンパイルすれば実行できるはずだ…よね?まあ、はい…しかし、いいえ。
実際にはjmp _linuxとは何か
Jaguarは2メガバイトのRAM(0x000000にマップされている)、最大6メガバイトのROM(基本的にカートリッジ)を0x80000にマップしており、2つのカスタムIC(Tom & Jerry、GPUとDSP)もメモリマップされている。
主なハードルはメモリフットプリントだ。確かにかなりの量だが、無限ではない(ギガバイトではなく、メガバイトの話をしている)。基本的にRAM使用量を最適化する方法を見つける必要がある。
簡単なことから始めよう。カーネルから機能を取り除き、デバッグを無効にする…いつもの手だ。しかし、それでもカーネルをRAMにロードできない(OOMになる)という問題は残るだろう( initramfs のことはまだ考えていないが、それらもかさばる)。
幸いなことに、Linuxはカーネルを2つの別々のメモリ領域に「分割」することを可能にするほど賢い。1つは、読み取り専用セクション(.rodataや.textなど)をカートリッジ(ROM)に格納し、動的セクション(.dataや.bss)をRAMに格納できるという事実を利用できる(XIP; eXecute-In-Placeを考えてほしい)。幸いなことに、RAMがどこにあるか、ROMがどこにあるかを指定するだけで、あとは(Linuxが)リロケーションを処理してくれる。
クール、これでJaguarにLinuxを搭載でき、実行できるはずだ…よね?まあ、確かに、カーネルのベースアドレスを指定して(非PICコードを覚えておいて)、そこにロードしてjmp $80000を実行しようとすることはできる…しかし、裏で何が起こっているのか、どうやって知ることができるだろうか?
Linuxが起動するために必要なもの
Linuxを起動するという文脈では、少なくとも起動作業を行う際には、2つのものが必要だ。
カーネルメッセージを確認できる何らかの出力手段
システムをティックさせる方法(基本的にタイマー)
最初の要件は、通常、おなじみのUARTを必要とする。JaguarのDSP(Jerry)にはTXDとRXDピンがあり、(少なくともLinux起動のためには)シリアル出力を行うために再利用できる(サウンドに関連するものはすべて無視すると仮定して)。ピンをビットバンギングする小さなコンソールドライバを書けば、初期のprintkメッセージを見始めるのに十分だ。
2番目の要件は、もう少し難しい。しかし、Jerry ICが持つ2つのタイマーのいずれかを使用できる。商用ゲームやソフトウェアはサウンド関連のタスクに使用するが、ここではLinuxがキャリブレーションを行い、スケジューラやPITに依存するシステムを設定するために使用する。どちらもJerry自体に割り込みをトリガーできるだけでなく、68000にもトリガーできるのは便利だ。Linuxの68000のボード固有の初期化をオーバーライドし、それをLinuxが今後使用するPITとして指定できる。
コンパイルし、実行してみる…
…何も?よし…それは奇妙だ。何か得られるはずだが?つまり、すべてチェックアウトした…カーネル設定:チェック…アドレス:チェック…コンパイラ…コンパイラ?
Ubuntuのリポジトリにあるm68k-linux-クロスコンパイラが、アラインメントされていないメモリアクセスを出力することが判明した(-68000を渡した後でも)。そのため、実質的にクラッシュしていた(ベース68000はアラインメントされていないメモリアクセスハンドリングを実装していないため)。m68k-elf-をターゲットにソースからビルドされたコンパイラを使用するとこれが修正されるが、それは確かに奇妙なことだ。
これはまた別の問題も示唆している。ROMは0x00000ではなく0x80000にマップされているため、68000はVBR(0x0)にジャンプしようとし、そこにハンドラがないため、実質的に自己破壊し、燃え尽きる。
これを修正するには、いわゆるベクタをRAMのベースに個別にmemcpyする必要があった。Jaguarプラットフォーム固有のLinuxコードでこれを修正した。
MAMEのgdbtsubをうまく動作させるために、gdbもソースからコンパイルする必要があった。なぜか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 2026
printk: legacy bootconsole [early_jerry0] enabled
uClinux with CPU MC68000 Flat model support (C) 1998,1999 Kenneth Albanowski, D. Jeff Dionne
Zone ranges: DMA [mem 0x0000000000000000-0x00000000001fffff] Normal empty
Movable zone start for each node
node 0: [mem 0x0000000000000000-0x00000000001fffff]
Early memory node ranges
node 0: [mem 0x0000000000000000-0x00000000001fffff]
On node 0, zone DMA: 512 pages in unavailable ranges
printk: log buffer data + meta data: 4096 + 12800 = 16896 bytes
Dentry 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: 512
mem auto-init: stack:all(zero), heap alloc:off, heap free:off
SLUB: HWalign=16, Order=0-1, MinObjects=0, CPUs=1, Nodes=1
NR_IRQS: 32
clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns
Calibrating delay loop... 1.04 BogoMIPS (lpj=5248)
pid_max: default: 32768 minimum: 301
Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
次に、initプロセスを見つけて実行しようとしますが…それがないので、再びクラッシュします。
ユーザー空間
多くの制限の1つは、ELFファイルを使用できないことです(MMUなしの状況のため、主にFLATバイナリを使用します)。そのため、ツールチェーンのセットアップが少し複雑になります。elf2flt(一方の形式から他方の形式に変換するユーティリティ)をスタンドアロンでコンパイルする方法を見つけることができませんでした。基本的に、.aファイルとそれに関連するものを指定する必要があります。