过去几周,我一直在努力让SMP支持在Linux/MIPS内核到SGI Octane(IP30)的端口上重新工作。单处理器支持工作正常,但我在使用第二个CPU时遇到了很多问题。我可以将机器引导到init
进程,但这在大多数情况下都会随着SIGSEGV或SIGBUS而终止。我有5年多前编写的补丁中的大部分支持代码,但我怀疑我要么没有正确锁定,要么意外地重新启用了IRQ。
硬件的一些背景:
MIPS R10000系列CPU实现8个中断,IP0
至IP7
:
IP0
和IP1
:仅软件中断,目前使用不多IP2
到IP6
:一般路由到其他一些硬件功能进行处理-
IP7
:R10K定时器/计数器/比较中断。 -
R10K支持MIPS-IV ISA,并且同时具有I高速缓存和D高速缓存。
- I-cache是32kB、VIPT、双向和64字节的行大小
- D缓存是32kB、VIPT、双向、无别名和32字节行大小
- R10K二级缓存大小为2MB、双向和128字节行
- R10K是超标量,采用推测执行,并且可以无序执行
- 辛烷值是缓存一致的,因此不会受到推测执行的影响
- 具体来说,我在这个系统中有一个R14000双模块。除了它主要是一款具有模具收缩和更快时钟速度的R10K之外,人们对它知之甚少。SGI从未发布过该处理器的硬件数据表,也没有任何勘误表信息
Octane有一个名为HEART
的ASIC作为其内存控制器和中断控制器。HEART
被设计为最多支持4个处理器,并具有64个可用中断(IRQ)。这64个IRQ被划分为几个优先级,并映射到上面的R10K CPU IPx IRQ:
- 0级,IRQ
0
至15
->CPUIP2
- 级别1,IRQ
16
至31
->CPUIP3
- 级别2,IRQ
31
至49
->CPUIP4
- 3级,IRQ
50
->CPUIP5
- 级别4,IRQ
51
至63
->CPUIP6
关于这些优先级有一些注意事项:
-
0级和1级IRQ主要分配给系统中的设备(SCSI、以太网等)。
-
级别2有几个用途:
- IRQ
32
至40
也可供系统中的设备使用(尤其是那些需要更高优先级的设备) - IRQ
41
硬接线用于按下电源按钮 - IRQ
42
至CCD_ - IRQ
46
到49
是用于4个可能的CPU的SMP处理器间中断(IPI)
- IRQ
-
级别3,IRQ
50
,专门用于HEART
本身的计数器/比较定时器。它的工作频率是12.5MHz(我想是80ns)。它有一个单独的计数寄存器和比较寄存器。从Linuxclockevent
的角度来看,我认为这是一个分辨率更好的计时器,可以用作系统计时器(52位计数器,24位比较)。 -
4级用于错误IRQ:
- IRQ
51
至58
是XIO总线(以星形拓扑布置的高速总线,由XBOW
ASIC服务)上的8个可用Xtalk
窗口小部件中的每一个的错误IRQ - IRQ
59
至CCD_ - IRQ
63
是CCD_ 42本身的异常错误IRQ
- IRQ
HEART
提供了几个用于处理中断的寄存器。每个寄存器为64位宽,每个中断一位:
HEART_ISR
-只读寄存器,用于获取挂起中断的列表HEART_SET_ISR
—只写寄存器,用于设置特定的中断位HEART_CLR_ISR
-只写寄存器以清除特定中断位HEAR_IMR(x)
—读/写寄存器,用于设置或清除特定CPU上特定中断的中断掩码,用x
表示
我使用以下代码进行基本的IRQ确认/屏蔽/取消屏蔽操作
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);
这些基本操作是使用3.1x系列Linux内核中的struct irq_chip
访问器实现的,我使用spin_lock_irqsave
和spin_unlock_irqrestore
保护对HEART
寄存器的访问。我不能100%确定我是否应该在这些访问者中使用这些锁定功能。
对于处理所有中断,标准Linux/MIPS平台调度功能采取以下操作:
IP7
->调用do_IRQ()
来处理CPU计时器IRQIP6
->调用ip30_do_error_irq()
向系统日志报告任何HEART
错误IP5
->调用do_IRQ()
来处理分配给HEART
定时器的时钟事件IRQIP4
、IP3
和IP2
->调用ip30_do_heart_irq()
来处理从0到49的所有HEART
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操作。原始代码使用由CPUID索引的全局int数组(ip30_ipi_mailbox
)来指定要传递给其他处理器的IPI操作。
此外,即使HEART
被设计为最多支持4个处理器,SGI也只生产过一个双CPU模块。因此,IRQ44
-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工作的正确道路,使我能够更好地识别错误代码并专注于使其正常工作:
- 我是否正确使用了spinlock
- 我是否使用正确的自旋锁变体
- 我是否需要在任何位置添加同步调用(即
smp_rmb()
、smp_wmb()
等) - 我的问题会不会在这个核心平台支持代码之外(比如视频驱动程序中)
- 我会看到一个未知的硬件错误随机损坏内存吗
- 以上代码中的任何一个可以更好地实现吗?(其中大部分是从Linux 2.6.17的原始端口到Octane的代码,刚刚更新为与内核中其他东西的工作方式更加一致)
任何能让我走上正确道路的信息都将不胜感激。我的希望是让SMP进入可用状态(效率无关紧要,我只需要它工作),这样我就可以开始把东西分解成补丁,并在某个时候把它包含在主线内核中。如果我不能让SMP工作,我将放弃它的支持,转而专注于将单处理器代码发送到上游。
错误最终被解决为没有将IRQ编号分配给正确的处理程序。我最初将所有64个IRQ分配为使用handle_level_irq
,这对于SMP处理器间中断(IPI)来说是不正确的。修复结果是将8个CPU特定中断42-45和46-49分配给handle_percpu_irq
。
这个案例非常有趣:尤其是如果有办法复制它的话。不幸的是,我没有:)。但如果必须解决这个问题,我想检查很多事情:1-禁用所有级别的缓存(每个cpu),以丢弃错误的窥探和缓存同步机制。2-将所有中断绑定在一个CPU上并引导板。如果出现问题,那么我认为您添加的用于处理SMP上中断的代码并不是有罪的。请将irqs的亲和力设置为一个CPU(比如cpu0)。对于此测试,请保持代码原样。。。只需确保修改中断与一个CPU的相关性即可。
不要犹豫,分享结果。希望能有所帮助。
艾曼。