在Linux/MIPS平台上正确实现SMP



过去几周,我一直在努力让SMP支持在Linux/MIPS内核到SGI Octane(IP30)的端口上重新工作。单处理器支持工作正常,但我在使用第二个CPU时遇到了很多问题。我可以将机器引导到init进程,但这在大多数情况下都会随着SIGSEGV或SIGBUS而终止。我有5年多前编写的补丁中的大部分支持代码,但我怀疑我要么没有正确锁定,要么意外地重新启用了IRQ。


硬件的一些背景:

MIPS R10000系列CPU实现8个中断,IP0IP7:

  • IP0IP1:仅软件中断,目前使用不多
  • IP2IP6:一般路由到其他一些硬件功能进行处理
  • 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级,IRQ015->CPUIP2
  • 级别1,IRQ1631->CPUIP3
  • 级别2,IRQ3149->CPUIP4
  • 3级,IRQ50->CPUIP5
  • 级别4,IRQ5163->CPUIP6


关于这些优先级有一些注意事项:

  • 0级和1级IRQ主要分配给系统中的设备(SCSI、以太网等)。

  • 级别2有几个用途:

    • IRQ3240也可供系统中的设备使用(尤其是那些需要更高优先级的设备)
    • IRQ41硬接线用于按下电源按钮
    • IRQ42至CCD_
    • IRQ4649是用于4个可能的CPU的SMP处理器间中断(IPI)

  • 级别3,IRQ50,专门用于HEART本身的计数器/比较定时器。它的工作频率是12.5MHz(我想是80ns)。它有一个单独的计数寄存器和比较寄存器。从Linuxclockevent的角度来看,我认为这是一个分辨率更好的计时器,可以用作系统计时器(52位计数器,24位比较)。

  • 4级用于错误IRQ:

    • IRQ5158是XIO总线(以星形拓扑布置的高速总线,由XBOWASIC服务)上的8个可用Xtalk窗口小部件中的每一个的错误IRQ
    • IRQ59至CCD_
    • IRQ63是CCD_ 42本身的异常错误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_irqsavespin_unlock_irqrestore保护对HEART寄存器的访问。我不能100%确定我是否应该在这些访问者中使用这些锁定功能。



对于处理所有中断,标准Linux/MIPS平台调度功能采取以下操作:

  • IP7->调用do_IRQ()来处理CPU计时器IRQ
  • IP6->调用ip30_do_error_irq()向系统日志报告任何HEART错误
  • IP5->调用do_IRQ()来处理分配给HEART定时器的时钟事件IRQ
  • IP4IP3IP2->调用ip30_do_heart_irq()来处理从0到49的所有HEARTIRQ


这是当前用于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-4548-4961-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的相关性即可。

不要犹豫,分享结果。希望能有所帮助。

艾曼。

相关内容

  • 没有找到相关文章

最新更新