在Sega 32X上运行Linux。谁还需要硬件同步原语呢?
摘要
作者详细介绍了将Linux移植到Sega 32X(一款复古游戏附加组件)的成功项目,强调了技术挑战和电路板启动的乐趣。
暂无内容
查看缓存全文
缓存时间: 2026/07/13 19:55
# 在 Sega 32X 上运行 Linux——谁还需要硬件同步原语呢?
来源:https://cakehonolulu.github.io/linux-on-32x/
## 前言
这篇文章的开头,我别无选择,只能感谢所有喜欢之前在 Jaguar 上运行 Linux(https://cakehonolulu.github.io/linux-for-jaguar/)那篇文章的朋友们。
特别感谢 `linuxmd` 项目的再次支持。
这是一次非常有趣的经历,我们还上了几家新闻媒体:
或者
我也把它发到了 Hacker News(https://news.ycombinator.com/item?id=48808663)上,引起了不少兴趣(还有不错的问题!)。
所以,是的,再次感谢大家!
## 为什么这(Linux)潮流还在继续?
这个问题我有个简单的回答:主要是为了提升我的板卡启动技能。
我在固件/操作系统/嵌入式领域专业工作已经超过两年了(从专业角度来说),而求职时常见的需求之一就是“具备从头开始进行板卡启动的能力”。
我知道这听起来不是特别吸引人,但对我来说,除了找工作(其实这也不是我的主要收入来源)之外,让一块芯片跑起来,并且运行你制作/协助制作的东西,这本身就非常有趣。你可以尝试组成结果的各个不同层面:模拟器、技术文档、实验、调查……你知道的,就是那些常见的工程活儿。
倒不是说阅读90年代初扫描的数据手册是我的爱好(有些文档里嵌入的只是纯图像PDF),但其中总有一种东西让我一次又一次地回来。
实际上,我在13/14年就开始做这件事了。那时我12/13岁,清楚地记得父母给我买了第一部手机。它基于联发科6589四核处理器,配有一GB内存——厉害吧!
那时(没过多久),谷歌发布了Android 5.0 Lollipop,但手机厂商决定不把手机升级到这个版本。我真的很伤心……所以只能自己动手了。
我会把故事简短总结一下,因为它有点长;但关键是,我要求该公司遵守GPL,发布该手机的Linux内核源代码,他们拒绝了,但我们社区团结起来,最终他们发布了(以及其他几款手机的源代码树)。
当我发现源代码是半吊子的,并且板卡配置根本无法在手机上启动时,可以想象我的惊讶。幸运的是,那时我发现了一个针对6589变种的MediaTek ALPS(https://wiki.postmarketos.org/wiki/ALPS)完整源代码的泄露版本,看起来很有希望(包含了完整的Little-Kernel引导程序、基带、Preloader、Linux内核和AOSP),让我可以研究。
我花了几个月时间,试图逆向工程手机厂商提供的原始启动镜像中的zImage部分,以填补MediaTek代码的某些部分(主要与液晶屏、电池、芯片组怪癖等有关)。经过大量挖掘和实验(以及在xda-developers(https://xdaforums.com/t/cyanogenmod-for-mediatek-devices.2274332/)上与酷哥们的愉快聊天),我最终让Android 5.0在我的手机上运行起来了——注意,并不完美。没有蓝牙、没有硬件合成器(硬件加速)、没有媒体引擎……但当看到纯净、未经修改的新Material Design在我的手机上显示出来时,我还是被震撼了。
Android 5.0 Lollipop*Android 5.0 Lollipop*
也许有一天我应该专门写一篇文章讲述这个故事。
## 强大的 Sega 32X
Sega 32X 于1994年11月在美洲/欧洲(1994年12月在日本)发布,是一款附加设备,号称是16位时代Genesis和即将到来的32位Sega Saturn之间的过渡步骤。我不太会讲故事,所以导致那个时刻(对于世嘉来说)发生的故事有点混乱(日本世嘉 vs 美国世嘉),所以我留给读者自己去了解吧。
不管怎样,在32X之前,世嘉推出了一款CD附加设备:Sega CD(在美国)或Mega CD(在欧洲/日本)。它插入Genesis的扩展接口,支持CD质量的音频流(以及游戏/游戏资源流)。它本身就是一个独立机器(它有一个比Genesis频率更高的68000处理器)。有两个不同的型号:第一代是前置式……:
Sega CD 1*Sega [Mega]CD Model 1 with Mega Drive/Genesis Model 1*
……以及第二代,顶置式:
Sega CD 2*Sega [Mega]CD Model 2 with Mega Drive/Genesis Model 2*
但这篇文章是关于32X的……所以,这就是它了:
Sega 32X*Sega 32X*
这个附加设备实际插在Genesis的卡带插槽上(它有一个独立的电源适配器和视频输出,后来视频输出是通过“组合”在Genesis视频输出的时序/视频/色彩通道信号之上实现的)。
Sega 32X & Genesis Model 2*Sega 32X & Genesis Model 2*
如果你曾经有幸搭建过“权力之塔”,你就会知道,拥有三个独立的电源适配器,再加上从32X获取立体声输出的线缆、“补丁”线以及许多我现在可能忘记的零碎东西,那可不是一般的讲究。
那么所有这些动力是用来做什么的呢?
## 32X 硬件规格
不可否认,这款附加设备比基础主机强大得多:
| 组件 | Sega Genesis/Mega Drive | Sega 32X |
| :--- | :--- | :--- |
| 主CPU | 1 x Motorola 68000 @ 7.6 MHz | 2 x Hitachi SuperH SH2(SH7604)@ 23 MHz |
| 位宽 | 16位 | 32位 |
| 最大屏幕颜色数 | 64 | 32768 |
| 系统内存 | 64KB + 256KB SDRAM(加上Genesis的64K) | 64KB + 256KB SDRAM(加上Genesis的64K) |
| 视频内存 | 64KB | 256KB(双缓冲) |
| 图形 | 基于精灵 | 软件渲染3D(平面着色、高洛德着色、简单纹理映射多边形) |
如你所见,差别相当大。我甚至没有列出所有不同,主要就是这些。
不幸的是,对于世嘉来说,这款产品的发布几乎与索尼的PlayStation 1同时进行,而后者凭借更简单的架构注定要占领游戏行业。另外,Sega Saturn也接近发布,这也没有任何帮助(所以消费者很困惑该买哪一个)。这才是真正的世嘉风格!
不管怎样,如果我们能让Linux在Jaguar上运行,那么在32X上应该也能做到,对吧?
## jmp _linux,集合点
要让Linux在SH2上运行(即让它通过UART输出任何有意义的信息)需要一些头脑风暴。
我们需要设置三个不同的CPU:两个SH2和一个68000。幸运的是,GitHub上已经有现成的例子了,所以我选了看起来最流行的32X Hello World示例:Chilly Willy's Sega MD/CD/32X devkit(https://github.com/viciious/32XDK/releases)。
这个基础示例在32X和68000之间建立了一个通信通道(这样你可以请求68000做一些32X不能做的事情),通过特定的内存映射IO地址;它准备好SH2(设置缓存,直接从68000机器码中将SH2代码从卡带上传……)并让主SH2跳转到那里,同时辅SH2被停靠。
我所做的是扩展这个功能,以便我拥有一个简单的状态机,可以将字符推送到Genesis的UART上(因为32X无法直接访问它)。但有一个问题……
## 256K + 64K
将Linux移植到一个新的(并且在发布时相对较新的)平台的最大障碍通常是完成启动所需的接线工作;如果你有足够的内存来加载它,情况通常就是这样。但对我们来说不是……
幸运的是,有基于FPGA的闪存卡带(可以让你在基础和32X模式下运行游戏;甚至还有专门的“核心”提供Sega CD支持,如果你没有附加设备的话!)。其中一系列卡带来自Krikkz(https://krikzz.com/)。
他基本上做了闪存卡带该做的事,但在各个方面都走得更远——这不仅仅是运行游戏那么简单!
我们对这些卡带的一个特别之处感兴趣:
扩展SSFv2映射器(https://krikzz.com/pub/support/mega-everdrive/x3x5x7/dev/extended_ssf-v2.txt)
早期的卡带系统通常有“映射器”这个概念。
映射器基本上是位于卡带上的IC,它可以增强主机在内存处理方面的能力。
它们还可以提供与内存无关的其他功能,例如世嘉的虚拟处理器(SVP),它有一个三星设计的DSP来进行繁重的数学计算(使Virtua Racer能够在基础Genesis上显示其所有3D多边形);或者任天堂的SuperFX芯片,它提供了与SVP类似的能力,由《雷霆壮志》或《猛禽》的创作者Argonaut Games制造。
映射器通常用于以银行切换的方式提供更多的ROM空间,以便容纳更大/更详细的游戏。你会写入某个特定内存地址/执行一组特定的访问,然后IC会在内部将“扩展”ROM的部分重新映射到主机可以直接寻址的内存窗口。如果仔细想想,这有点像MS-DOS的`HIMEM.SYS`的精神。
所以Krikkz基本上为我们完成了所有工作,结果我们可以将ROM当作RAM使用(通过配置某些位,可以将ROM设置为可写)。这为我们提供了4 MB的RAM。
我基本需要扩展SDK示例来执行ROM→RAM的映射,但必须留出一些空间供Linux + initramfs二进制文件驻留(基本上是卡带内存窗口内的实际ROM)。我最终找到了一个平衡点:大约540 KB的ROM和剩余的RAM。Linux加载器基本上将二进制文件复制到它链接到的内存位置,然后跳转到它。
## 编译和运行 Linux
于是,我制作了一个`sh2eb-elf`交叉编译器,并开始编译Linux……但编译很快就失败了。
在我遇到的所有工具链问题中,这个可能是最有趣的。
出于某种原因,GCC在编译`kernel/nstree.c`时出现了内部编译器错误(ICE)。幸运的是,我找到了一个解决方法(https://github.com/torvalds/linux/commit/6fea4c411fe0ca70fe0ea2147eefd37faf41a0bd)(当时没时间提交错误报告)。
完成所有这些工作后(以及编写了一个小型earlycon驱动程序,以便从32X向Genesis输出UART信息),我看到了Linux的第一条消息。
还有一个单独的问题:某时Linux似乎停止了启动过程,并无限期挂起。
然后我发现了另一组问题,这次与`ashlsi3, ashrsi3 & lshrsi3`有关。我不太确定这些是如何编写的(它们是从对应的`libgcc`复制/借鉴而来吗?),但比较交叉编译器对这些函数所做的和Linux所做的,很明显我们在破坏一个不应该破坏的寄存器。我之所以发现这个问题,是因为我开始注意到一些`pr_info`和`pr_notice`的栈被破坏了(而且从未返回给调用者),之后我追踪到这里(https://github.com/torvalds/linux/commit/a72625dbf9687b975d9565df8e56fccd5c628921)。
然后我为了安全起见,对`arch/sh/lib/checksum.S`做了一个额外的(类似的)修复(https://github.com/torvalds/linux/commit/e3ec8b05a8f6d83f8b1d513ab470bd930a96682d),基本上我禁用了`shrd`指令,因为它不存在于32X的SH2中(如果使用……就会产生非法操作码)。
这一修复让我们顺利通过了时钟校准之前的整个启动过程;为此我决定为SH2的FRT(自由运行定时器)编写一个简单的驱动程序;这让我们更进一步,我们即将启动`init`进程。
幸运的是,这次我能够利用`ELF FDPIC`支持(这比Jaguar的`FLAT`二进制支持让我轻松多了)来构建我的`rootfs`。我还非常幸运地得到了musl-cross-make(https://github.com/richfelker/musl-cross-make)的帮助,它为我提供了一个可用的`sh2eb-linux-muslfdpic-`工具链(我必须承认,我努力用过`crosstool-ng`、`buildroot`等工具,但都没有成功)。
于是,一切都启动起来了。
结……案……了……?
## 对称多处理(SMP)
是的,我控制不住自己。
事实证明,你需要一些硬件原语来实现CPU间同步……而32X上的SH2没有这些原语¹。
¹:即使在开发者手册要求不要这样做之后,像D32XR这样的项目似乎仍然使用了TAS.B操作码,因为适配器总线没有锁定的读-修改-写周期机制。
我们也没有缓存一致性,所以又多了个问题。
但我想,嘿,两个SH2功能上相同;我们肯定能想出办法……对吧?
事实证明,`sparc32`正是有一些代码可以帮助解决这个问题。
我们基本上可以利用Peterson算法(https://en.wikipedia.org/wiki/Peterson%27s_algorithm)完全在软件中实现所需的原语。注意,这不高效,但确实有效。
我基本上需要对所有需要关注的原语(比如`cmpxchg`)进行启动,并应用Peterson提出的思路。
我还需要连接68000的代码(目前只处理UART相关功能),以便它能够帮助向两个SH2传递IPI(我扩展了68000可以接收的命令,使其能够正确地完成工作),同时设置共享内存区域以帮助同步任务。可以把此时的68000看作总线仲裁器/额外中断路由器。
从SH2端,我只需发送一个“IPI”命令,68000解析该命令并根据数据字的一个位(0为主SH2,1为辅SH2)来决定断言哪个中断线。
还有缓存一致性问题,我通过不使用缓存映射(并且无论如何缓存都是禁用的)来放置Peterson的`flag`和`turn`变量(例如,避免SH2因`flag`的陈旧缓存值而自旋)来解决。
我面临的另一个问题是,如何识别哪个CPU是哪个(这样Linux才能正确管理和调用`hard_smp_processor_id()`):考虑到我无论如何都在使用`softfloat`,我直接让`CPUID_REG`指向SH2私有的硬件分频器寄存器(`DVSR`),它没有其他用途,所以有效地成为了我的“我是什么CPU”寄存器。该寄存器由Linux加载器在启动时为主SH2和辅SH2写入。
性能很差,总线争用很疯狂;但它确实有效,这对我来说就够了。
```
smp: Bringing up secondary CPUs ...
MARS SMP: released secondary SH2 to 22082000
smp: Brought up 1 node, 2 CPUs
```
我必须承认,这并不像看起来那么容易,实际上涉及大量的调试和(大部分)试错(我还花了两个晚上追踪为什么两个CPU都会被唤醒,但很快又空闲了;结果是我需要对`default_idle()`进行修复(https://github.com/torvalds/linux/commit/c816358004d5ab6ce59c985d07847431bbedb41a),因为调度器会出错,无法正确继续执行)。
但一切都是值得的。
然后我基本上就做了一个简单的控制台显示驱动程序(这样你就可以在实际屏幕上看到效果了)……
Linux on 32X
*瞧*
不用说,所有这些都需要大量的微调(我经常内存不足,并且需要让Linux和Busybox都进行精简),但这又是一次非常有回报的经历。
## 供大家尝试的杂项配置文件
Linux源码树(https://github.com/cakehonolulu/linux_ports/tree/sega/32x)(`mars_defconfig`)
Busybox配置(https://cakehonolulu.github.io/misc/linux_32x/busybox.config)
初始化脚本(https://cakehonolulu.github.io/misc/linux_32x/init)
相似文章
Sega MegaDrive上的Linux
一个项目将Linux移植到Sega MegaDrive游戏机,使用EverDrive卡带,提供了构建和启动指南,以及一个用于模拟的自定义QEMU分支。
在Atari Jaguar上运行Linux。没错,真的。
一位开发者成功地将Linux移植到了Atari Jaguar游戏主机上,利用了uClinux和m68k架构来应对系统内存有限且缺乏MMU的问题。
用 x86_64 汇编写成的 Linux 桌面
一位开发者借助 Claude Code,用纯 x86_64 汇编重建了完整的 Linux 桌面栈——从 shell、终端、窗口管理器到各种工具,实现微秒级启动,并延长数小时续航。
一人,双内核,与大量RISC-V
QRV Systems的Yuri Zaporozhets在FPGA上构建了一台基于RISC-V的个人计算机和一台大型机,并两次重写了QNX。他最新的操作系统QSOE正在FOSS世界中引起关注。
适用于 Linux 的 Windows 9x 子系统
一个让经典 Windows 9x 环境在现代 Linux 子系统中运行的业余项目。