在过去的几周里,我一直试图让 SMP 支持在 Linux/MIPS 内核的端口上再次运行到 SGI Octane (IP30)。单处理器支持工作正常,但我在使用第二个 CPU 时遇到了很多问题。我可以将机器引导到该init
进程,但大多数情况下都会因 SIGSEGV 或 SIGBUS 而死。我从 5 年前编写的补丁中获得了大部分支持代码,但我怀疑我要么没有正确锁定事物,要么意外地重新启用了 IRQ。
一些硬件背景:
MIPS R10000 系列 CPU 实现了 8 个中断,IP0
以IP7
:
IP0
和IP1
:仅软件中断,目前使用不多。IP2
toIP6
:一般路由到其他一些硬件函数进行处理IP7
:R10K 定时器/计数器/比较中断。R10K 支持 MIPS-IV ISA,并具有 I-cache 和 D-cache。
- I-cache 为 32kB、VIPT、2-way 和 64 字节行大小。
- D-cache 为 32kB、VIPT、2-way、无别名和 32 字节行大小。
- R10K L2 高速缓存为 2MB、2 路和 128 字节行大小。
- R10K 是超标量,采用推测执行,并且可以乱序执行。
- Octane 是缓存一致的,因此不会受到推测执行的影响。
- 具体来说,我在这个系统中有一个 R14000 双模块。除了它主要是具有芯片缩小和更快时钟速度的 R10K 之外,对此知之甚少。SGI 从未发布此处理器的硬件数据表,也没有任何勘误信息。
Octane 有一个称为HEART
其内存控制器和中断控制器的 ASIC。 HEART
旨在支持多达 4 个处理器,并提供 64 个中断 (IRQ)。这 64 个 IRQ 分为几个优先级,并映射到上面的 R10K CPU IPx IRQ:
- 级别 0,IRQ
0
到15
-> CPUIP2
- 级别 1,
16
到31
-> CPU的 IRQIP3
- 级别 2,
31
到49
-> CPU的 IRQIP4
- 级别 3,IRQ
50
-> CPUIP5
51
第 4 级,到63
-> CPU的 IRQIP6
关于这些优先级有一些说明:
0 级和 1 级 IRQ 主要分配给系统中的设备(SCSI、以太网等)。
2 级有多种用途:
- IRQ也
32
可供40
系统中的设备使用(尤其是那些需要更高优先级的设备)。 - IRQ
41
是硬连线的,用于按下电源按钮。 - IRQ
42
用于45
向 4 个可能的 CPU 发送调试器信号。 - IRQ
46
是49
4 个可能的 CPU 的 SMP 处理器间中断 (IPI)。
- IRQ也
第 3 级 IRQ
50
专门用于HEART
自身的计数器/比较计时器。它以 12.5MHz(我认为是 80ns)运行。它有一个计数寄存器和比较寄存器。从 Linux 的clockevent
角度来看,我认为这是一个更好的分辨率定时器,可用作系统定时器(52 位计数器,24 位比较)。级别 4 用于错误 IRQ:
- IRQ是 XIO 总线(
51
以星形拓扑排列的高速总线,由ASIC 提供服务)上58
8 个可用小部件中的每一个的错误 IRQ 。Xtalk
XBOW
- IRQ
59
是62
4 个可能的 CPU 的总线错误 IRQ。 - IRQ
63
是HEART
自身的异常错误 IRQ。
- IRQ是 XIO 总线(
HEART
提供了几个用于处理中断的寄存器。每个寄存器为 64 位宽,每个中断一位:
HEART_ISR
- 只读寄存器,用于获取挂起的中断列表。HEART_SET_ISR
- 只写寄存器设置一个特定的中断位。HEART_CLR_ISR
- 只写寄存器清除特定中断位HEAR_IMR(x)
- 读/写寄存器,用于设置或清除特定 CPU 上特定中断的中断掩码,由 . 表示x
。
我将以下代码用于基本的 IRQ ack/mask/unmasking 操作
u64 *imr; /* Address of the mask register to work on */
static int heart_irq_owner[64]; /* Which CPU owns which IRQ? (global) */
Ack: writeq((1UL << irq), HEART_CLR_ISR);
Mask: imr = HEART_IMR(heart_irq_owner[irq]);
writeq(readq(imr) & (~(1UL << irq)), imr);
Unmask: imr = HEART_IMR(heart_irq_owner[irq]);
writeq(readq(imr) | (1UL << irq), imr);
这些基本操作是使用struct irq_chip
3.1x 系列 Linux 内核中的访问器实现的,我使用和保护对HEART
寄存器的访问。我不能 100% 确定我是否应该在这些访问器中使用这些锁定功能。spin_lock_irqsave
spin_unlock_irqrestore
为了处理所有中断,标准的 Linux/MIPS 平台调度函数采取以下动作:
IP7
-> 调用do_IRQ()
处理 CPU 定时器 IRQ。IP6
-> 调用以向系统日志ip30_do_error_irq()
报告任何错误。HEART
IP5
-> 调用do_IRQ()
处理分配给HEART
定时器的clockevent IRQ。IP4
,IP3
, 和IP2
-> 调用ip30_do_heart_irq()
处理HEART
从 0 到 49 的所有 IRQ。
这是当前用于的代码ip30_do_heart_irq()
:
static noinline void ip30_do_heart_irq(void)
{
int irqnum = 49;
int cpu = smp_processor_id();
u64 heart_isr = readq(HEART_ISR);
u64 heart_imr = readq(HEART_IMR(cpu));
u64 irqs = (heart_isr & 0x0003ffffffffffffULL &
heart_imr);
/* Poll all IRQs in decreasing priority order */
do {
if (irqs & (1UL << irqnum))
do_IRQ(irqnum);
irqnum--;
} while (likely(irqnum >= 0));
}
在 SMP 支持方面,与其他 Linux/MIPS 平台不同,我没有类似于硬件中的邮箱寄存器的东西来存储应该采取什么样的 IPI 操作。原始代码使用ip30_ipi_mailbox
由 CPUID 索引的全局 int 数组 ( ) 来指定要传递给其他处理器的 IPI 操作。
此外,即使HEART
设计为最多支持 4 个处理器,SGI 也只生产过双 CPU 模块。因此,IRQ 44
- 45
、48
-49
和61
-62
从未实际用于任何事情。
给定这些全局变量:
#define IPI_CPU(x) (46 + (x))
static DEFINE_SPINLOCK(ip30_ipi_lock);
static u32 ip30_ipi_mailbox[4];
这是当前用于向其他 CPU 发送 IPI 的代码:
static void ip30_send_ipi_single(int cpu, u32 action)
{
unsigned long flags;
spin_lock_irqsave(&ip30_ipi_lock, flags);
ip30_ipi_mailbox[cpu] |= action;
spin_unlock_irqrestore(&ip30_ipi_lock, flags);
writeq(1UL << IPI_CPU(cpu)), HEART_SET_ISR);
}
为了响应 IPI,每个 CPU 调用request_irq
其初始化代码并注册一个中断处理程序。这是处理程序中当前用于服务 IPI 中断的代码:
static irqreturn_t ip30_ipi_irq(int irq, void *dev_id)
{
u32 action;
int cpu = smp_processor_id();
unsigned long flags;
spin_lock_irqsave(&ip30_ipi_lock, flags);
action = ip30_ipi_mailbox[cpu];
ip30_ipi_mailbox[cpu] = 0;
spin_unlock_irqrestore(&ip30_ipi_lock, flags);
if (action & SMP_RESCHEDULE_YOURSELF)
scheduler_ipi();
if (action & SMP_CALL_FUNCTION)
smp_call_function_interrupt();
return IRQ_HANDLED;
}
这就是背景信息。
我当前的内核配置除了帧缓冲区和“Impact”视频驱动程序之外的所有内容都被剥离了。没有 PCI,没有块层,没有网络,没有串行,没有键盘/鼠标。我有一个大约 7 年历史的 initramfs,我正在加载它,如果一切正常,应该放到 bash 提示符下。然而,因为它加载到 RAM 中,它能够相当快地暴露内存损坏,结果我要么得到上述 SIGSEGV 或 SIGBUS 错误。
由于 IOC3 PCI 设备,目前无法选择使用远程 GDB 或内置 KGDB。IOC3 是一个多功能 PCI 设备,它声称是一个单功能设备,其背后是键盘/鼠标、串行端口、实时时钟和以太网的硬件位。目前还不存在绕过 IOC3 并直接访问远程 GDB 的串行端口的代码,而且 KGDB 也不知道如何与 IOC3 上的标准 i8042 键盘控制器通信。
我添加了一个标准 PCI 串行卡(基于 Moschip),但该驱动程序显然不是字节序安全的,因此探测串行端口会使内核恐慌。
我希望,回答以下问题将使我能够更好地识别错误代码并专注于使其正常工作,从而使我走上让 SMP 正常工作的正确道路:
- 我是否正确使用自旋锁?
- 我是否使用了正确的自旋锁变体?
- 我是否需要在任何地方添加同步调用(即
smp_rmb()
,smp_wmb()
等)? - 我的问题可能出在这个核心平台支持代码之外(例如在视频驱动程序中)吗?
- 我可以查看随机破坏内存的未知硬件错误吗?
- 上述任何代码都可以更好地实现吗?(其中大部分是从 Linux 2.6.17 的原始端口到 Octane 的代码,刚刚更新以更符合内核中其他事物的工作方式)
任何可以让我走上正确道路的信息都将不胜感激。我的希望是让 SMP 进入可用状态(效率无关紧要,我只需要它工作),因此我可以开始着手将其分解为补丁,并在某个时候将其包含在主线内核中。如果我不能让 SMP 工作,我将放弃它的支持并专注于让单处理器代码向上游发送。