ajfa/epix-cdc4000-mame
GitHub: ajfa/epix-cdc4000-mame
修复 MAME 中阻碍 CDC 4000 系列 EP/IX 2.1.1 启动的三个模拟 bug,并提供用于定位问题的逆向工程工具集。
Stars: 0 | Forks: 0
# epix-cdc4000-mame
针对 [MAME](https://github.com/mamedev/mame) 的三个修复,使得 **EP/IX 2.1.1** —
Control Data 面向 CDC 4000 系列的 UNIX — 能够在模拟的
MIPS RS2030 上安装并启动,此外还包括用于发现这些修复的逆向工程工具。
EP/IX 是 MIPS RISC/os 的扩展。它的 `pkginfo` 将 `rc2030`/`rs2030` 列为
一等支持目标,这正是 MAME 在 `src/mame/mips/mips_i2000.cpp` 中模拟的机器,且没有 `MACHINE_NOT_WORKING` 标志。但它仍然
无法启动:安装介质的设备探测遇到了三个独立的模拟 bug。
```
epix Console login: root
****************************************************
* CONTROL DATA PROPRIETARY PRODUCT *
* Copyright Control Data Systems, Inc. *
* 1990, 1991, 1992, 1993 *
****************************************************
(C) Copyright 1986-1992, MIPS Computer Systems
epix, EP/IX Version 2.1.1
epix # uname -a
epix epix 2.1.1 RISCos mips
epix # df
Filesystem Type kbytes use avail %use Mounted on
/dev/root ffs 19770 10270 9500 52% /
/dev/usr ffs 850894 365655 485239 43% /usr
```
## 应用
补丁针对 **MAME 0.288**,请在源码根目录下应用:
```
git clone --depth 1 --branch mame0288 https://github.com/mamedev/mame.git
cd mame
for p in ../epix-cdc4000-mame/patches/*.diff; do patch -p1 < "$p"; done
make SOURCES=src/mame/mips/mips_i2000.cpp SUBTARGET=mips2030 TOOLS=1 NOWERROR=1 -j"$(nproc)"
```
`patches/` 包含修复内容;如果您喜欢直接放入,`patched/` 中包含了完整的结果文件。
## 这些 bug
### 1. AIC-6250 DMA 字节计数器为 24 位,MAME 却将其视为 32 位
`patches/01-aic6250-24bit-dma-count.diff`
数据手册明确说明 — *"24-Bit DMA Byte Counter"*,*"The 24-bit counter
allows data transfers up to 16 Mbytes without a DMA wrap"*(寄存器 00-02)。
MAME 将其保存在 `u32 m_dma_count` 中,该变量
* 从未初始化(它只在 `save_item` 中出现过),并且
* 一次加载一个字节,且使用的掩码只清除各自的字节:
`m_dma_count &= ~0x0000ff`,因此 **第 24-31 位从未被触及**。
因此,最高字节保留的是新分配的设备对象中的任何值。在 Rx2030 上,这使得 IOP 的上电诊断在大约一半的
时间里失败 —— 这种间歇性现象就是线索,因为它与堆内容有关:
```
SCSI Test...: SCSI Power Up Failure: dma count 0 bit invalid
```
跟踪显示加载了 6 字节的 SCSI 命令,如下所示
```
[:aic6250] dma_cntrl_w 0x03
[:aic6250] dma transfer from memory, count 1392508934 <- 0x53000006
```
低 24 位是正确的 6。由于计数永远达不到零,
*DMA BYTE COUNT ZERO*(状态寄存器 0,位 0)永远不会亮起,传输
也永远无法完成。
**这个 bug 并非 EP/IX 专属**:它会影响驱动 AIC-6250 的每一台
机器 —— MIPS Rx2030、Data General AViiON、Microbotics HardFrame、pc532。
*验证*:连续 8 次 `rc2030` 启动产生了逐字节完全相同的控制台
快照,全部显示为 `SCSI Test...Passed`;在修复之前,大约有一半会失败。
RISC/os 4.52 仍然可以启动到其登录提示符,因此没有产生回归。
### 2. 数据手册中关于 DMA 输入/输出的传输条件
`patches/02-aic6250-init-and-transfer-conditions.diff`
初始化计数器,并实现了数据手册中要求芯片作为启动者移动字节前所需满足的条件:*SCSI 相位与预期相位匹配,REQ
已被断言,传输字节数不为零,并且 FIFO 未满(输入)/不为空(输出)*。当 FIFO 持有剩余传输量时,它还会停止内存预取,根据 *"the AIC-6250 will stop the memory prefetch when the
number of bytes in the FIFO, plus the number of bytes already transferred on the
SCSI bus, sums to the total transfer length"*。
MAME 只检查了 FIFO 条件;它自己的源码是这么说的:
```
// FIXME: assert ack when: req asserted && phase match && count not zero && fifo not empty
```
这关闭了该 FIXME。就其本身而言,它不会改变任何观察到的行为,但它消除了潜在的协议违规(REQ 之前的 ACK,以及上一次传输残留的预取字节)。
### 3. `nscsi_hd` 未实现 MODE SENSE page 0x38
`patches/03-nscsi-hd-mode-sense-page-38.diff`
**这正是实际阻碍启动的 bug。** EP/IX 的设备探测发出
```
CDB 1a 00 38 00 1c 00 MODE SENSE(6), page 0x38, allocation length 28
```
Page 0x38 是 Common Command Set 的缓存控制页,那个时代的驱动器都实现了它。`src/devices/bus/nscsi/hd.cpp` 知道页面 00、01、02、03、
04、08 和 30,因此 0x38 直接落入 `default: fail = true`,磁盘
返回 CHECK CONDITION。Rx2030 IOP 固件随即完全停止
处理该单元的 IOCB,而 EP/IX 则会永远重试该请求:
```
iocb SCSI0 command 0x0200 <- the request
iocb UART0 command 0x0003 <- "SCSI 0L0: POLLED timeout" printed
...repeat...
```
28 字节的分配长度准确地说明了驱动程序期望的返回内容:4 字节
的 header + 8 字节的块描述符 + 16 字节的页面,即一个 page-length 字节为 14 的 page 0x38。七行代码,EP/IX 就启动了。
## 这些 bug 是如何被发现的
由于失败发生在两者之间,因此双方都进行了反汇编。
**内核。** `unix.i2000_std` 是 MIPSEB ECOFF 格式且 *未 strip* — 包含 5296 个
外部符号。`tools/ecoff.py` 解析符号头(HDRR,magic
0x7009)并使用 capstone 进行反汇编。这从内核
自身的代码中定位到了 IOP ABI:`iopb` 是一个包含 24 个 16 字节条目的表,其中命令
参数在 +0 处,结果在 +4 处,命令信号量在 +8 处,**响应
信号量在 +0xa 处**(`iop_wait` 轮询的对象),缓冲区指针在 +0xc 处。在每个卡住的
日志中都出现的地址 `0x8017070c`,原来是
`iop_poke+0x180` — 即触发 IOP 门铃的 `sb $t7, 3($t9)`,证明内核端
是正常的。
**IOP。** `tools/mkiop.py` 从四个
PROM 重建了 256 KB 的 NEC V50 固件,使用的是 `ROM_START(i2000)` 所采用的交错方式;`tools/iopdis.py` 将其作为 16 位 x86 进行反汇编。通过搜索 AIC-6250 端口地址,找到了寄存器
原语(位于 0xf7623 的 `aic_read`,位于 0xf7637 的 `aic_write`),并从那里找到了
轮询状态寄存器 1 位 3(Command Done)的轮询传输循环。它
还确定了是谁在抱怨:内核的字符串是 `POLLED time out`,而
固件的字符串是 `POLLED timeout` — 屏幕上显示的是固件的。
**插桩。** 在寄存器级别跟踪 AIC-6250 会生成
数百 MB 的数据(每个字节和每次 REQ/ACK 转换各一行),并使机器
大幅减速,以至于永远无法到达失败点。奏效的方法是在驱动程序中记录 IOCB 参数
块,然后仅记录 SCSI 总线相位,最后仅记录在
COMMAND 相位中发送的字节 — 每条命令只有寥寥几行,有问题的 CDB 就是这样
浮出水面的。这些插桩补丁位于
`tools/instrumentation/` 中(它们包含绝对路径;请将它们视为方法
记录,而不是照原样运行的东西)。
## 工具
| 文件 | 功能 |
|---|---|
| `ecoff.py` | MIPS ECOFF 符号表 + 反汇编程序 (`syms`、`which`、`xrefs`、`dis`) |
| `mkiop.py`、`iopdis.py`、`iopcalls.py`、`iopref.py` | 重建并反汇编 NEC V50 IOP 固件 |
| `ffs.py` | 用于大端序 4.2BSD FFS 的只读读取器 (`ls`、`tree`、`cat`、`get`、`extract`) |
| `vh.py`、`chdinfo.py`、`fsfree.py` | SGI 卷头、CHD 头、FFS 可用空间 |
| `mktarget.py` | 写入卷头并将 miniroot 放入交换分区 |
| `cdbstats.py` | 从 MAME 日志汇总 SCSI 操作码 |
`ecoff.py` 和 `iopdis.py` 需要 `pip install capstone`。
## 此处未包含的内容
没有 ROM、没有磁盘镜像,也没有 EP/IX 介质。EP/IX 是
Control Data Systems 的专有软件;此存储库仅包含模拟器修复和分析
工具。
`docs/FINDINGS.md` 是完整的工作日志,包括安装过程和
死胡同。`docs/NOTAS.md` 包含了精确的安装程序答案。
## 许可证
这些补丁是对 MAME 的修改,并遵循 MAME 的许可证(BSD-3-Clause)。
`tools/` 中的工具采用 MIT 许可证,请参阅 `LICENSE`。
标签:CDC, MAME, MIPS, 云资产清单, 系统补丁, 逆向工具, 逆向工程