AlphaStation 的 SROM 探索之旅
JP's Website
AlphaStation 的 SROM
发布于 2025-03-30
内容
背景
我对运行 UNIX 的 怪异 90 年代 RISC 工作站情有独钟。所以当 Rob 说“嘿,我车的后备箱里有一台 AlphaStation……”时,很明显它就被我带回家了。 不幸的是,这台 AlphaStation 已经坏了。 PSU 运行(并且持续运行,除非外壳断开,在这种情况下它会立即停止运行)。电压看起来没问题。但是机器中的两个 PCI 卡(一个 32 位的 Matrox Millenium G200 显卡和一个 64 位的 LSI Logic SCSI 卡)都坏了——插入我的 Pentium 3 PC 时都无法检测到。而且第一个 DIMM 插槽中一半的 RAM 变得非常热。因此,我怀疑在其生命周期中发生了某种灾难性事件。但是,至少我从中获得了一个 18GB 1.6" SCSI 硬盘驱动器,以及一个工作正常的(ish)12x SCSI CD-ROM。
AlphaStation 500
这不是一篇关于 AlphaStation 500 的文章。我会在我让它工作的时候(如果可以的话)再写。这是一篇关于 AlphaStation 500 如何启动的文章。但这就是它。
AlphaStation 500 是 Digital 公司 1996 年左右的工作站。我的是 500 MHz 型号,并有一个 Alpha 21164A 处理器(又名 EV56)。它的启动方式很_奇怪_。
在你的普通 PC 上,一直都有某种 ROM 芯片。它保存着一段被称为 BIOS 的固件。这个 ROM 芯片在处理器地址空间中有一个众所周知的位置(记住任何 PC 处理器都以 16 位、8088 兼容模式启动,具有 1 MiB 地址空间,就像 IBM PC 5150 一样),并且处理器在复位后立即开始在其中执行代码。
Alpha(或者至少这台 AlphaStation 500 ——虽然我认为它们大部分都是这样工作的)是不同的。
SROM
主板上有一个串行 ROM (SROM),复位后,CPU 内部的一些逻辑开始产生时钟脉冲。这驱动一个(外部)计数器,该计数器为 ROM 提供连续地址。ROM 的 1 位输出与时钟脉冲同步地发送到处理器中。Alpha 处理器将此数据存储在其内部指令缓存中。代码加载完成后,处理器随后从其缓存中执行代码。这段代码足以让处理器配置其外部内存总线,并找到一个内存映射的并行 ROM,然后它将控制权交给该 ROM。 但是等等,你怎么找到一个 1 位宽的 ROM 芯片?难道大多数 ROM 芯片不是至少 8 位宽吗?是的,它们是。
SROM 镜像选择
AlphaStation 500 主板有一系列的八个跳线位置,你应该在其中一个位置上安装一个跳线。事实上,如果你试图安装多个跳线,机器将无法启动。
SROM 芯片实际上包含八个复用在一起的镜像——你从 ROM 加载的每个字节包含来自八个镜像中的每一个的一位。跳线控制着 ROM 上的八个数据位中的哪一个实际连接到处理器上的串行输入。
我的机器上的 SROM 镜像有:
跳线| 描述
---|---
J11| 上电
J12| 迷你控制台
J13| 软盘加载器
J14| 内存测试
J15| 不适用
J16| 不适用
J17| MCHK 迷你控制台
J19| 无初始化迷你控制台
所以,鉴于我显然有 RAM 问题,_理论上_我应该能够将跳线从 J11 移动到 J12,并看到某种由完全由处理器从内部 I-Cache 及其 32 个内部寄存器运行的 '迷你控制台' 驱动的控制台,即使没有安装 RAM。
但是,我没有。或者至少,如果有一个控制台,我找不到它。我读到它应该在用于将 SROM 加载到芯片中的相同引脚上,但是我没有在那里看到任何 UART 通信。
在互联网上有一些 一些 SROM 源代码,在一个为使用 Digital 出售的 OEM AlphaPC 主板的制造商设计的包中,基于 Alpha 的计算机。但我不知道它是否与我的 AlphaStation 上的东西有任何相似之处。
也许有一种方法可以查看 SROM 代码并计算出它应该做什么?
分割 SROM
我的 SROM 芯片里有什么?好吧,它是一个 PLCC 插槽中的 Am27C010,所以我把它拔了出来,把它放进一个 TL866-II 闪存编程器里,然后把它倾倒了出来。 在此处下载。 记住,这是八个流复用在一起的,所以我们需要把它们分开。这并非易事,因为,如果每个字节中都有一个来自所选镜像的位,那么第一个位是字节中最重要的位,还是字节中最小有效位? 因为我不确定它是如何工作的,所以我用 Python 进行了hack,我认为我是对的。
f = open("am27c010_image_alphastation500.bin", "rb")
contents = f.read()
for image in range(0, 8):
count = 0
acc = 0
data = []
for b in contents:
bit = (b >> image) & 1
# 从顶部加载位,因此第一个位
# 最终成为 LSB
acc >>= 1
acc |= (bit << 7)
count = count + 1
if count == 8:
data.append(acc)
acc = 0
count = 0
o = open(f"srom_{image}.bin", "wb")
o.truncate()
o.write(bytes(data))
o.close()
但是,现在我们有八个镜像,并且……它们都不包含任何 ASCII 字符串,而且它们看起来都不像 Alpha 机器代码(就 alpha-linux-gnu-objdump 而言)。
解码镜像
所以,我说过 SROM 数据被加载到 I-Cache 中?嗯,它实际上是被一位一位地时钟输入到缓存行中。但并非所有缓存行都是有效数据!某些缓存行位是奇偶校验,有些是记录该行是哪个地址的缓存副本的元数据。这很复杂。
Digital 实际上在 Alpha PC SDK 中放置了一个工具,名为 srom.c。此工具接受一些 Alpha 机器代码,并添加所有标签和奇偶校验信息,为您提供一个 SROM 镜像,您可以将其串行加载到芯片上,在启动时。事实证明,在 Alpha 21164 处理器上,每个缓存行长 200 位。因此,在此程序中,对于进入的每 16 个字节(或四个 32 位指令),都会出来 25 个字节(200 位)。
所以我只需要找到每 25 个字节的块中哪些 16 个字节是有效数据,对吗?
不。
缓存行_很复杂_,事实证明传入的 32 位字不是按顺序存储的。事实上,这些_位_甚至不会彼此相邻——由于某种原因,两个字被按位交织在一起。 srom.c 工具具有此方便的表:
int dfillmap [128] = { /* data 0:127 -- fillmap[0:127]*/
42,44,46,48,50,52,54,56, /* 0:7 */
58,60,62,64,66,68,70,72, /* 8:15 */
74,76,78,80,82,84,86,88, /* 16:23 */
90,92,94,96,98,100,102,104, /* 24:31 */
43,45,47,49,51,53,55,57, /* 32:39 */
59,61,63,65,67,69,71,73, /* 40:47 */
75,77,79,81,83,85,87,89, /* 48:55 */
91,93,95,97,99,101,103,105, /* 56:63 */
128,130,132,134,136,138,140,142, /* 64:71 */
144,146,148,150,152,154,156,158, /* 72:79 */
160,162,164,166,168,170,172,174, /* 80:87 */
176,178,180,182,184,186,188,190, /* 88:95 */
129,131,133,135,137,139,141,143, /* 96:103 */
145,147,149,151,153,155,157,159, /* 104:111 */
161,163,165,167,169,171,173,175, /* 112:119 */
177,179,181,183,185,187,189,191 /* 120:127 */
};
在Alpha 21164 Hardware Reference Manual中,相同的表作为_附录 C:串行 Icache 加载预解码值_ 包含在内。 所以……如果我编写一个程序来使用该表反转编码,将 25 字节的缓存行转换回四个 32 位指令字,该怎么办?那会奏效吗? 这次我使用了 Rust。
//! Alpha 21164 SROM decoder
use std::io::Write;
fn main() {
let mut args = std::env::args_os();
let _ = args.next();
let infilename = args.next().expect("Need input filename");
let outfilename = args.next().expect("Need output filename");
println!("Reading {}", infilename.to_string_lossy());
let mut data = std::fs::read(infilename).expect("Failed to load file");
println!("Read {} bytes", data.len());
let mut outfile = std::fs::File::create(outfilename).expect("Can't open output file");
outfile.set_len(0).expect("Can't truncate file");
let remainder = data.len() % 25;
if remainder != 0 {
eprintln!("I want a multiple of 25 bytes for 21164 SROM");
}
let mut lines = Vec::new();
while data.len() >= 25 {
let remainder = data.split_off(25);
let mut line = Vec::new();
for word_bytes in data.chunks_exact(4) {
let word_bytes: [u8; 4] = word_bytes.try_into().unwrap();
let word: u32 = u32::from_le_bytes(word_bytes);
line.push(word);
}
data = remainder;
lines.push(line);
}
for line in lines {
for word in line.iter() {
print!("0x{:08x} ", word);
}
print!(" -- ");
let decoded = process_line(&line);
for word in decoded.iter() {
print!("0x{:08x} ", word);
outfile.write_all(&word.to_le_bytes()).expect("Writing to output");
}
println!();
}
}
static DFILLMAP: [usize; 128] = [
/* data 0:127 -- fillmap[0:127]*/
42, 44, 46, 48, 50, 52, 54, 56, /* 0:7 */
58, 60, 62, 64, 66, 68, 70, 72, /* 8:15 */
74, 76, 78, 80, 82, 84, 86, 88, /* 16:23 */
90, 92, 94, 96, 98, 100, 102, 104, /* 24:31 */
43, 45, 47, 49, 51, 53, 55, 57, /* 32:39 */
59, 61, 63, 65, 67, 69, 71, 73, /* 40:47 */
75, 77, 79, 81, 83, 85, 87, 89, /* 48:55 */
91, 93, 95, 97, 99, 101, 103, 105, /* 56:63 */
128, 130, 132, 134, 136, 138, 140, 142, /* 64:71 */
144, 146, 148, 150, 152, 154, 156, 158, /* 72:79 */
160, 162, 164, 166, 168, 170, 172, 174, /* 80:87 */
176, 178, 180, 182, 184, 186, 188, 190, /* 88:95 */
129, 131, 133, 135, 137, 139, 141, 143, /* 96:103 */
145, 147, 149, 151, 153, 155, 157, 159, /* 104:111 */
161, 163, 165, 167, 169, 171, 173, 175, /* 112:119 */
177, 179, 181, 183, 185, 187, 189, 191 /* 120:127 */
];
fn process_line(line: &[u32]) -> [u32; 4] {
if line.len() != 6 {
panic!("Only want 6 words per line");
}
let mut output = [0u32; 4];
for (out_idx, &in_idx) in DFILLMAP.iter().enumerate() {
let in_word = in_idx >> 5;
let in_offset = in_idx & 0x1F;
let bit = (line[in_word] >> in_offset) & 1;
if bit != 0 {
let out_word = out_idx >> 5;
let out_offset = out_idx & 0x1F;
output[out_word] |= 1 << out_offset;
}
}
output
}
事实证明,25 的最后一个字节对我们没有用处,所以我一次加载 25 个字节的文件,将其转换为六个 32 位字,然后对其进行处理以生成四个 32 位指令。
同样,这需要一些反复试验。我不知道我的 SROM 芯片的内容是否有效,或者它们应该是什么样的。因此,我使用 srom.c 程序对一些随机数据进行编码,然后我使用我的程序将其再次解码,并且我验证了输入文件和输出文件是否匹配。这需要一些反复试验,但我得到了一些至少可以正确地来回处理我的随机示例文件的东西。
反汇编已解码的镜像
好吧,让我们只将其中一个文件扔给 objdump,看看会发生什么。
$ alpha-linux-gnu-objdump -b binary -m alpha -D /mnt/c/Users/msn/OneDrive/Shared/computers/digital/srom_0.decoded | head -n 40
/mnt/c/Users/msn/OneDrive/Shared/computers/digital/srom_0.decoded: file format binary
Disassembly of section .data:
0000000000000000 <.data>:
0: 0f 01 ff 77 pal1d 0x3ff010f
4: 56 01 ff 77 pal1d 0x3ff0156
8: 57 01 ff 77 pal1d 0x3ff0157
c: 39 00 40 d3 bsr ra,0xf4
10: 47 00 40 d3 bsr ra,0x130
14: 2e 01 40 d3 bsr ra,0x4d0
18: 56 01 21 64 pal19 0x210156
...
好的,嗯……那是对的吗?那是有效的 Alpha 机器代码吗?我认为是!
在Alpha 21164 Hardware Reference Manual中进一步阅读后,我看到 pal1d 是 Alpha 21164 特定指令 HW_MTPR 的通用助记符。这是将值从 CPU 寄存器_存储到_ 内部处理器寄存器_的指令。Alpha 21164 上的 pal19 指令是 HW_MFPR,这意味着将值从_内部处理器寄存器 加载到 CPU 寄存器中。基本上,我认为这些 IPR 就像 Arm 处理器上的 CP15 系统寄存器一样。而且,其中一个被称为 SL_XMIT,这听起来像是做串行传输的事情——这正是我所期望的 SROM 迷你控制台所做的事情。
但是,objdump 似乎不知道这些 21164 的具体情况,因此我需要编写一个工具,该工具采用一个汇编文件并将 pal1d 和 pal19 汇编语言重写为可以帮助我们理解正在发生的事情的东西。我将只将 alpha-linux-gnu-objdump 调用插入到上面的 SROM 解码器中,并对输出进行后处理。
我还将阅读一些 Raymond Chen 的文章,以尝试了解此 Alpha 汇编代码。
好的,这是再次提供的机器代码,但带有一些自动生成的注释:
0000000000000000 <.data>:
0: 0f 01 ff 77 pal1d 0x3ff010f ; HW_MTPR: write zero to ICM
4: 56 01 ff 77 pal1d 0x3ff0156 ; HW_MTPR: write zero to PALtemp22
8: 57 01 ff 77 pal1d 0x3ff0157 ; HW_MTPR: write zero to PALtemp23
c: 39 00 40 d3 bsr ra,0xf4
10: 47 00 40 d3 bsr ra,0x130
14: 2e 01 40 d3 bsr ra,0x4d0
18: 56 01 21 64 pal19 0x210156 ; HW_MFPR: read PALtemp22 to t0
好吧,但串行的东西呢?我看到一个名为 SL_XMIT 的 IPR,用于发送一位数据,另一个名为 SL_RCV,用于接收。看来 CPU 基本上必须使用一个输出引脚和一个输入引脚以及一些延迟来位bash 一个 UART,以确保 UART 以正确的波特率运行。
经过一番探索,我发现了一个看起来像 get_char 的函数,我尝试对其进行注释(尽管我并不完全理解它):
get_char:
191c: 05 14 e1 47 mov 0x8,t4 ; we want eight bits
1920: 04 04 ff 47 clr t3
1924: 03 04 ff 47 clr t2
char_start:
1928: 17 01 42 64 pal19 0x420117 ; HW_MFPR: read SL_RCV to t1
192c: fe ff 5f f4 bne t1,0x1928
1930: 27 00 60 d3 bsr t12,0x19d0 ; wait for half bit time
get_bit:
1934: 28 00 60 d3 bsr t12,0x19d8 ; wait for bit time
1938: 17 01 42 64 pal19 0x420117 ; HW_MFPR: read SL_RCV to t1
193c: 82 d6 40 48 srl t1,0x6,t1 ; put bit in t1<0>
1940: 22 07 44 48 sll t1,t3,t1 ; move bit into correct position for byte
1944: 03 04 43 44 or t1,t2,t2 ; OR bit into data byte in t2
1948: 04 34 80 40 addq t3,0x1,t3 ; increment bit index
194c: 25 35 a0 40 subq t4,0x1,t4 ; decrement loop count
1950: f8 ff bf f4 bne t4,0x1934 ; goto get_bit
1954: 20 00 60 d3 bsr t12,0x19d8 ; wait for bit time
1958: 17 01 42 64 pal19 0x420117 ; HW_MFPR: read SL_RCV to t1
195c: 00 04 7f 44 or t2,zero,v0 ; copy received byte to v0
1960: 02 10 10 45 and t7,0x80,t1 ;
1964: 07 00 40 e4 beq t1,0x1984 ; goto clr_int_and_exit
1968: 02 18 0b 44 xor v0,0x58,t1 ; Is it ASCII 'X'?
196c: 05 00 40 f4 bne t1,0x1984 ; goto clr_int_and_exit
1970: 56 01 42 64 pal19 0x420156 ; HW_MFPR: read PALtemp22 to t1
1974: 02 f6 41 48 zap t1,0xf,t1 ; zero bottom four bytes of t1
1978: 08 11 10 45 andnot t7,0x80,t7 ; ?
197c: 02 04 02 45 or t7,t1,t1 ; ?
1980: 56 01 42 74 pal1d 0x420156 ; HW_MTPR: write t1 to PALtemp22
clr_int_and_exit:
1984: 01 00 5f 20 lda t1,1 ; clear interrupt 33
1988: 22 37 44 48 sll t1,0x21,t1
198c: 15 01 42 74 pal1d 0x420115 ; HW_MTPR: write t1 to HWINT_CLR
1990: 01 80 fe 6b ret zero,(sp),0x1
因此,是的,看起来我的 SROM 有效,并且可以通过等待起始位,然后逐个计数位周期来通过串行端口接收东西。
结论
回到 Alpha 21164 Hardware Reference Manual,我现在看到了至关重要的信息,如果我足够仔细地阅读该文档,它一直都在那里:slonh
SROM 加载发生在内部周期时间内,大约为
srom_clk_h的 126 个 CPU 周期……srom_data_h/Rx- 接收 SROM 或串行终端数据srom_clk_h/Tx- 为 SROM 提供时钟或发送串行终端数据 因此,我应该尝试在启动时将 TTL UART 信号注入到携带 SROM 数据的相同引脚上,并且我应该在携带 SROM 数据时钟信号的引脚上寻找 TTL UART 输出(并且使用 500 MHz CPU,我应该期待 4 MHz 时钟信号,因此需要至少以 16 MHz 的速度进行采样,理想情况下为 24 MHz)。 好的,我们学到了什么?
- Alpha CPU 从串行 ROM(SROM)启动
- 该 SROM 包含打包在一起的八个镜像
- 每个镜像都经过编码,可以准备好直接流式传输到 Alpha CPU 的 I-Cache 中
- 我们可以反转该编码并取回原始镜像
- Alpha CPU 具有特殊的_内部处理器寄存器_,可让他们执行控制几个 GPIO 引脚(以及更多)之类的操作
- 我的 SROM 闪存芯片中的镜像似乎很好,表明该芯片没有损坏
- 有时你需要经历漫长的旅程才能最终回到你开始的地方,但是你在路上获得的知识可以让你看到更好的前进路线
- 我们仍然不知道我的 Alpha 为什么无法启动
链接
- 包含我编写的工具源代码的 Github:https://github.com/thejpster/alpha-srom
- Alpha 主板软件开发工具包 V4.0:https://github.com/jramstedt/ebsdk