6.3. IPI中断
CSR.ESTAT寄存器中的IS[12],支持核间中断IPI,用于通知当前处理器核有其他处理器核产生了IPI中断请求。
下面我们主要以3A6000处理器为例。
3A6000 为每个处理器核都实现了 8 个核间中断寄存器(IPI)以支持多核 BIOS 启
动和操作系统运行时在处理器核之间进行中断和通信。
3A6000支持两种不同的访问方式,一种是地址访问模式,另一种是为了支持处 理器寄存器空间的直接私有访问。
处理器核间中断相关的寄存器及其功能描述如下表所示:
| 名称 | 读写权限 | 描述 |
|---|---|---|
| IPI_Status | R | 32 位状态寄存器,任何一位有被置 1 且对应位使能情况下, 处理器核 INT4中断线被置位。 |
| IPI_Enable | RW | 32 位使能寄存器,控制对应中断位是否有效 |
| IPI_Set | W | 32 位置位寄存器,往对应的位写 1,则对应的 STATUS 寄存器位被置 1 |
| IPI_Clear | W | 32 位清除寄存器,往对应的位写 1,则对应的 STATUS 寄存器位被清 0 |
| MailBox0 | RW | 缓存寄存器,供启动时传递参数使用,按 64 或者 32 位的 uncache 方式进行访问。 |
| MailBox01 | RW | 缓存寄存器,供启动时传递参数使用,按 64 或者 32 位的 uncache 方式进行访问。 |
| MailBox02 | RW | 缓存寄存器,供启动时传递参数使用,按 64 或者 32 位的 uncache 方式进行访问。 |
| MailBox03 | RW | 缓存寄存器,供启动时传递参数使用,按 64 或者 32 位的 uncache 方式进行访问。 |
6.3.1. 按地址访问模式(MMIO)
对于龙芯 3A6000,上述的寄存器可以使用基地址 0x1fe00000 进行访问,也可以使用配置
寄存器指令(IOCSR)进行访问。具体寄存器说明和地址见下面的表格。
由于龙芯 3A6000 中包括 8 个逻辑核,连续的两个逻辑核号对应一个物理核,在龙芯3
号空间规则下,8 个逻辑核被划分为两个结点。当使用地址进行访问时,各个内部结点的基
地址需要在地址的[17:16]上加上内部结点号。例如,核4至核7的基地址为 0x1fe10000,
核0至核3的基地址为 0x1fe00000。
0 号处理器核的核间中断与通信寄存器列表如下所示:
| 名称 | 偏移地址 | 权限 | 描述 |
|---|---|---|---|
| Core0_IPI_Status | 0x1000 | R | 0 号处理器核的 IPI_Status 寄存器 |
| Core0_IPI_Enalbe | 0x1004 | RW | 0 号处理器核的 IPI_Enalbe 寄存器 |
| Core0_IPI_Set | 0x1008 | W | 0 号处理器核的 IPI_Set 寄存器 |
| Core0_IPI_Clear | 0x100c | W | 0 号处理器核的 IPI_Clear 寄存器 |
| Core0_MailBox0 | 0x1020 | RW | 0 号处理器核的 IPI_MailBox0 寄存器 |
| Core0_MailBox1 | 0x1028 | RW | 0 号处理器核的 IPI_MailBox1 寄存器 |
| Core0_MailBox2 | 0x1030 | RW | 0 号处理器核的 IPI_MailBox2 寄存器 |
| Core0_MailBox3 | 0x1038 | RW | 0 号处理器核的 IPI_MailBox3 寄存器 |
1 号处理器核的核间中断与通信寄存器列表如下所示:
| 名称 | 偏移地址 | 权限 | 描述 |
|---|---|---|---|
| Core1_IPI_Status | 0x1100 | R | 1 号处理器核的 IPI_Status 寄存器 |
| Core1_IPI_Enalbe | 0x1104 | RW | 1 号处理器核的 IPI_Enalbe 寄存器 |
| Core1_IPI_Set | 0x1108 | W | 1 号处理器核的 IPI_Set 寄存器 |
| Core1_IPI_Clear | 0x110c | W | 1 号处理器核的 IPI_Clear 寄存器 |
| Core1_MailBox0 | 0x1120 | RW | 1 号处理器核的 IPI_MailBox0 寄存器 |
| Core1_MailBox1 | 0x1128 | RW | 1 号处理器核的 IPI_MailBox1 寄存器 |
| Core1_MailBox2 | 0x1130 | RW | 1 号处理器核的 IPI_MailBox2 寄存器 |
| Core1_MailBox3 | 0x1138 | RW | 1 号处理器核的 IPI_MailBox3 寄存器 |
2 号处理器核的核间中断与通信寄存器列表如下所示:
| 名称 | 偏移地址 | 权限 | 描述 |
|---|---|---|---|
| Core2_IPI_Status | 0x1200 | R | 2 号处理器核的 IPI_Status 寄存器 |
| Core2_IPI_Enalbe | 0x1204 | RW | 2 号处理器核的 IPI_Enalbe 寄存器 |
| Core2_IPI_Set | 0x1208 | W | 2 号处理器核的 IPI_Set 寄存器 |
| Core2_IPI_Clear | 0x120c | W | 2 号处理器核的 IPI_Clear 寄存器 |
| Core2_MailBox0 | 0x1220 | RW | 2 号处理器核的 IPI_MailBox0 寄存器 |
| Core2_MailBox1 | 0x1228 | RW | 2 号处理器核的 IPI_MailBox1 寄存器 |
| Core2_MailBox2 | 0x1230 | RW | 2 号处理器核的 IPI_MailBox2 寄存器 |
| Core2_MailBox3 | 0x1238 | RW | 2 号处理器核的 IPI_MailBox3 寄存器 |
3 号处理器核的核间中断与通信寄存器列表如下所示:
| 名称 | 偏移地址 | 权限 | 描述 |
|---|---|---|---|
| Core3_IPI_Status | 0x1300 | R | 3 号处理器核的 IPI_Status 寄存器 |
| Core3_IPI_Enalbe | 0x1304 | RW | 3 号处理器核的 IPI_Enalbe 寄存器 |
| Core3_IPI_Set | 0x1308 | W | 3 号处理器核的 IPI_Set 寄存器 |
| Core3_IPI_Clear | 0x130c | W | 3 号处理器核的 IPI_Clear 寄存器 |
| Core3_MailBox0 | 0x1320 | RW | 3 号处理器核的 IPI_MailBox0 寄存器 |
| Core3_MailBox1 | 0x1328 | RW | 3 号处理器核的 IPI_MailBox1 寄存器 |
| Core3_MailBox2 | 0x1330 | RW | 3 号处理器核的 IPI_MailBox2 寄存器 |
| Core3_MailBox3 | 0x1338 | RW | 3 号处理器核的 IPI_MailBox3 寄存器 |
Tip
上述的地址描述是基于统一内存地址排布的,核0至核3的基地址为 0x1fe00000,
核4至核7的基地址为 0x1fe10000。
6.3.2. 按配置寄存器指令模式(IOCSR)
龙芯 3A6000 中新增了处理器核直接的寄存器访问指令,可以通过私有空间对配置 寄存器进行访问。为了更方便地使用核间中断寄存器。主要是通过IOCSR相关指令操作。
当前处理器核核间中断与通信寄存器列表
| 名称 | 偏移地址 | 权限 | 描述 |
|---|---|---|---|
| perCore_IPI_Status | 0x1000 | R | 当前处理器核的 IPI_Status 寄存器 |
| perCore_IPI_Enalbe | 0x1004 | RW | 当前处理器核的 IPI_Enalbe 寄存器 |
| perCore_IPI_Set | 0x1008 | W | 当前处理器核的 IPI_Set 寄存器 |
| perCore_IPI_Clear | 0x100c | W | 当前处理器核的 IPI_Clear 寄存器 |
| perCore_MailBox0 | 0x1020 | RW | 当前处理器核的 IPI_MailBox0 寄存器 |
| perCore_MailBox1 | 0x1028 | RW | 当前处理器核的 IPI_MailBox1 寄存器 |
| perCore_MailBox2 | 0x1030 | RW | 当前处理器核的 IPI_MailBox2 寄存器 |
| perCore_MailBox3 | 0x1038 | RW | 当前处理器核的 IPI_MailBox3 寄存器 |
为了向其它核发送核间中断请求及 MailBox 通信,通过以下寄存器进行访问。
处理器核核间通信寄存器
| 名称 | 偏移地址 | 权限 | 描述 |
|---|---|---|---|
| IPI_Send | 0x1040 | WO | 32 位中断分发寄存器: [31] 等待完成标志,置 1 时会等待中断生效; [30:26] 保留; [25:16] 处理器核号; [15:5] 保留; [4:0] 中断向量号,对应 IPI_Status 中的向量; |
| Mail_Send | 0x1048 | WO | 64 位 MailBox 缓存寄存器: [63:32] MailBox 数据; [31] 等待完成标志,置 1 时会等待写入生效; [30:27] 写入数据的 mask,每一位表示 32 位写数据 对应的字节不会真正写入目标地址,如 1000b 表示写 入第 0-2 字节,0000b 则 0-3 字节全部写入 [26] 保留 [25:16] 处理器核号 [15:5] 保留 [4:2] MailBox 号: 0 - MailBox0 低 32 位; 1 - MailBox0 高 32 位; 2 - MailBox1 低 32 位; 3 - MailBox1 高 32 位; 4 - MailBox2 低 32 位; 5 - MailBox2 高 32 位; 6 - MailBox3 低 32 位; 7 - MailBox4 高 32 位; [1:0] 保留; |
| FREQ_Send | 0x1058 | WO | 32 位频率使能寄存器; [31] 等待完成标志,置 1 时会等待设置生效; [30:27] 写入数据的 mask,每一位表示 32 位写数据; 对应的字节不会真正写入目标地址,如 1000b 表示写; 入第 0-2 字节,0000b 则 0-3 字节全部写入; [26] 保留; [25:16] 处理器核号; [15:5] 保留; [4:0] 写入对应的处理器核私有频率配置寄存器。 IOCSR[0x1050] |
Warning
需要注意的是,由于 Mail_Send 寄存器一次只可以发送 32 位的数据,当发送 64 位数据
时必须拆分为两次发送。因此,目标核在等待 Mail_Box 内容时,需要通过其它的软件手段
来确保传输的完整性。例如,发送完 Mail_Box 数据之后,通过核间中断来表示已经发送完
成。
6.3.3. 多核中断处理流程
首先源处理器核,准备数据,发送到目标的MailBox寄存器。
/* Send mailbox buffer via Mail_Send */
static void csr_mail_send(uint64_t data, int cpu, int mailbox)
{
uint64_t val;
/* Send high 32 bits */
val = IOCSR_MBUF_SEND_BLOCKING;
val |= (IOCSR_MBUF_SEND_BOX_HI(mailbox) << IOCSR_MBUF_SEND_BOX_SHIFT);
val |= (cpu << IOCSR_MBUF_SEND_CPU_SHIFT);
val |= (data & IOCSR_MBUF_SEND_H32_MASK);
iocsr_write64(val, LOONGARCH_IOCSR_MBUF_SEND);
/* Send low 32 bits */
val = IOCSR_MBUF_SEND_BLOCKING;
val |= (IOCSR_MBUF_SEND_BOX_LO(mailbox) << IOCSR_MBUF_SEND_BOX_SHIFT);
val |= (cpu << IOCSR_MBUF_SEND_CPU_SHIFT);
val |= (data << IOCSR_MBUF_SEND_BUF_SHIFT);
iocsr_write64(val, LOONGARCH_IOCSR_MBUF_SEND);
};
如上所示,首先向Mail_Send写入相应的值,通过IOCSR指令,写入到地址空间中。
根据Mail_Send的定义:
MailSend[63:32]: 存放的是发送的32位数据。
MailSend[31]: 等待完成的标志,置1时会等待写入生效。上述例子中,IOCSR_MBUF_SEND_BLOCKING的值为
1<<31。MailSend[25:16]: 是写入目的CPU的MailBox寄存器,比如为0,是写入CPU-0;值为1,就是写入CPU-1。上述 例子中,直接将
cpu<<16写入。MailSend[4:2]:是写入目标CPU的哪一个MailBox,具体的分类方法如下:
#define IOCSR_MBUF_SEND_BOX_LO(box) (box << 1)
#define IOCSR_MBUF_SEND_BOX_HI(box) ((box << 1) + 1)
对应于下面:
MailSend[4:2]的值 |
对应写入的目标MailBox内容(32位) |
|---|---|
0 |
MailBox0 低 32 位 |
1 |
MailBox0 高 32 位 |
2 |
MailBox1 低 32 位 |
3 |
MailBox1 高 32 位 |
4 |
MailBox2 低 32 位 |
5 |
MailBox2 高 32 位 |
6 |
MailBox3 低 32 位 |
7 |
MailBox4 高 32 位 |
Tip
上述iocsr_write64(val, LOONGARCH_IOCSR_MBUF_SEND)的执行结果,会根据Mail_Send值具体的定义,
写入目标寄存器的MailBox寄存器中,也就是上面描述的perCore_MailBox0,perCore_MailBox1, perCore_MailBox2
perCore_MailBox3。
Warning
上述对Mail_Send的操作,只是将内容的值写入目标CPU的MailBox中,但是并不会打断目标CPU的当前执行的指令!
源处理器核向目标处理器核通过IPI_Send发送IPI中断。
static void ipi_write_action(int cpu, u32 action)
{
uint32_t val;
val = IOCSR_IPI_SEND_BLOCKING | action;
val |= (cpu << IOCSR_IPI_SEND_CPU_SHIFT);
iocsr_write32(val, LOONGARCH_IOCSR_IPI_SEND);
}
根据IPI_Send的定义:
IPISend[31]: 等待完成的标志,置1时会等待写入生效。上述例子中,IOCSR_IPI_SEND_BLOCKING的值为
1<<31。IPISend[25:16]: 是写入目的CPU的IPI_Status寄存器,比如为0,是写入CPU-0;值为1,就是写入CPU-1。上述 例子中,直接将
cpu<<16写入。MailSend[4:0]:中断向量号,对应于目标CPU中IPI_Status中的向量(注意是32位)。
比如,向MailSend[4:0]写入0,对应于目标CPU中IPI_Status[0]=1;
向MailSend[4:0]写入3,对应于目标CPU中IPI_Status[3]=1;
下面是Linux中,LoongArch的SMP定义的几个向量类型,相当于定义了IPI_Status寄存器中,每位对应的含义。
#define ACTION_BOOT_CPU 0
#define ACTION_RESCHEDULE 1
#define ACTION_CALL_FUNCTION 2
#define ACTION_IRQ_WORK 3
#define ACTION_CLEAR_VECTOR 4
#define SMP_BOOT_CPU BIT(ACTION_BOOT_CPU)
#define SMP_RESCHEDULE BIT(ACTION_RESCHEDULE)
#define SMP_CALL_FUNCTION BIT(ACTION_CALL_FUNCTION)
#define SMP_IRQ_WORK BIT(ACTION_IRQ_WORK)
#define SMP_CLEAR_VECTOR BIT(ACTION_CLEAR_VECTOR)
Tip
上述iocsr_write32(val, LOONGARCH_IOCSR_IPI_SEND)的执行结果,会根据IPI_Send值具体的定义,
写入目标寄存器的IPI_Status寄存器中。至于会不会产生中断,还是要根据配置情况。
目标处理器核根据配置选项,看是否产生IPI中断。
具体是否产生中断的判断是:
IPI <= |(IPI_Status[31:0] & IPI_Enable[31:0])
如果对应IPI_Status的使能位IPI_Enable也置1了,就会产生相应的IPI中断,反之,就算IPI_Status置位了,但是IPI_Enable 没有使能,也是不会产生IPI的异常。
产生中断异常后,进入异常处理程序,目标处理器核根据自身当前的IPI_Status来决定执行什么样的操作。
static irqreturn_t loongson_ipi_interrupt(int irq, void *dev)
{
unsigned int action;
unsigned int cpu = smp_processor_id();
action = ipi_read_clear(cpu_logical_map(cpu));
if (action & SMP_RESCHEDULE) {
scheduler_ipi();
per_cpu(irq_stat, cpu).ipi_irqs[IPI_RESCHEDULE]++;
}
if (action & SMP_CALL_FUNCTION) {
generic_smp_call_function_interrupt();
per_cpu(irq_stat, cpu).ipi_irqs[IPI_CALL_FUNCTION]++;
}
if (action & SMP_IRQ_WORK) {
irq_work_run();
per_cpu(irq_stat, cpu).ipi_irqs[IPI_IRQ_WORK]++;
}
if (action & SMP_CLEAR_VECTOR) {
complete_irq_moving();
per_cpu(irq_stat, cpu).ipi_irqs[IPI_CLEAR_VECTOR]++;
}
return IRQ_HANDLED;
}
执行完相应的IPI历程外,还需要清除对应的在IPI_Status中的置位。
static u32 ipi_read_clear(int cpu)
{
u32 action;
/* Load the ipi register to figure out what we're supposed to do */
action = iocsr_read32(LOONGARCH_IOCSR_IPI_STATUS);
/* Clear the ipi register to clear the interrupt */
iocsr_write32(action, LOONGARCH_IOCSR_IPI_CLEAR);
wbflush();
return action;
}
主要是想IPI_Clear对应的位写1,就会将IPI_Status对应的位的1清除。
如果当前处理器核想主动的发起IPI中断,也是可行的,主要是通过向IPI_Set寄存器的对应位写入1。
iocsr_write32(action, LOONGARCH_IOCSR_IPI_SET);
后续的处理方式和前面的设置是一样的。