[Bare metal development] IRQ interrupt service function (1) - assembly initialization

IRQ is not the same as the previous Reset function. When an IRQ interrupt is generated, we do not know which peripheral the IRQ interrupt comes from. Therefore, we need to obtain the interrupt ID first, and then jump to the real interrupt service function for execution. processing logic.

The entire IRQ interrupt processing can be seen as containing two parts:

  • Assembly part (environment preparation): obtain the interrupt ID and jump to the interrupt processing function
  • C language part: Execute interrupt logic processing

Table of contents

1. IRQ assembly initialization steps

2. Compilation implementation

1. Save the scene

2. Get the interrupt ID

3. Switch SVC mode

4. Jump to the general interrupt service function

5. Restoration site

Three, complete writing


1. IRQ assembly initialization steps

The overall idea of ​​interrupt processing is to save the scene first, switch to the specified mode, and then jump to the interrupt processing function. After executing the processing logic, return to the original state and continue to execute downward.

The work to be done here is also similar, the only thing that may be slightly different is the addition of new content related to interrupt processing logic:

  • Save the scene (save LR, CPSR registers and other registers)
  • Get interrupt ID
  • Toggle SVC mode
  • jump interrupt handler
  • Restore the scene (restore SPSR, return to the next position before the interruption occurred)

2. Compilation implementation

1. Save the scene

Because we are about to jump to the interrupt service function, we need to save the following content:

  • Contextual content : The contents of the r0-r3 registers will be used below.
  • Address of the next instruction : When an interrupt occurs, the LR register automatically saves the address of the next instruction.
  • SPSR register : used to save the state of the CPSR register so that it can be restored to its original state after the interrupt is processed.
push {lr}             /* 保存 lr 地址 */
push {r0-r3, r12}     /* 保存 r0-r3,r12 寄存器 */ 

mrs r0, spsr          /* 读取 spsr 寄存器 */
push {r0}             /* 保存 spsr 寄存器 */

 

2. Get the interrupt ID

(1) Get the register address

The interrupt controller GIC contains a series of registers (register groups), such as registers that record register status and register interrupt IDs. As long as we obtain the first address of this register group , we can subsequently obtain the address of each register based on the offset .

The following is the CBAR register, in which PERIPHBASE records the first address of the GIC controller. We can obtain the first address of the register bank of GIC through the following command. Now save it to the r1 register.

mrc p15, 4, r1, c15, c0, 0        /* 将CP15协处理器的C0内的值保存到 r1 寄存器中 */

Now after getting the first address, you also need to get the offset and locate it in the specified register. What we want to focus on is the CPU Interface register group. We first get the first address of the CPU Interface register group, which is 0x2000 relative to the first address of GIC.

add r1, r1, #0x2000        /* r1中本就保存了 GIC 基地址,现在需要加上偏移 0x2000 */
                           /* 于是就得到了CPU Interface 寄存器组的基地址 */

We see the GICC_IAR register, which holds the interrupt ID. The offset of this register relative to the CPU Interface register group is 0x000C.

add r1, r1, #0x000C     

(2) Get the interrupt ID

The following is the field distribution of GICC_IAR. We only need to obtain the 9-0 bits.

  • First load the address pointed to by the r1 register into the r0 register
  • Then the r0 register takes out the lower 10 bits

ldr r0, [r1]            /* 将 r1 寄存器指向的地址对应内容保存到 r0 */ 
ldr r4, =0x3ff          /* 无法直接使用 #0x3ff,因为 0x3ff 不是立即数*/
and r0, r0, r4          /* 取出低10位 */ 

(3) Save the interrupt ID

Afterwards, it will jump to the interrupt service function. We need to pass parameters to the function. When the interrupt service function is called, the parameter content is obtained from the r0-r3 register by default. The first parameter is obtained from r0, the second parameter is obtained from r1,... and so on.

When calling a C function to pass parameters, if the number of parameters is less than 4, you can use r0 - r3 to pass; if it is more than 4, you need to use stack delivery.

push {r0, r1}             /* 保存 r0,r1 */

3. Switch SVC mode

When an IRQ interrupt occurs, the system will automatically enter the IRQ mode, but when we execute the interrupt processing logic, other interrupts may be generated, so when processing the interrupt logic, we can first enter the SVC mode, and then switch back to the IRQ mode after processing .

Next we use the cps command to switch directly to SVC mode

cps #0x13         /* 进入 SVC 模式,允许其他中断再次进去 */

4. Jump to the general interrupt service function

Everything is ready, just waiting for the jump. system_irqhandler is a function we defined in C language.

push {lr}                     /* 保存 SVC 模式的 lr 寄存器 */
ldr r2, =system_irqhandler    /* 加载 C 语言中断处理函数到 r2 寄存器中*/
blx r2                        /* 运行 C 语言中断处理函数,带有一个参数 */
pop {lr}                      /* 执行完 C 语言中断服务函数,lr 出栈 */
/*
 * 参数 giccIar 是中断ID
 */
void system_irqhandler(unsigned int giccIar)
{
    /* 中断处理逻辑 */
}

5. Restoration site

All that's left is some popping operations.

  • Switch to IRQ mode. The purpose of switching to SVC mode is to allow other interrupts to enter when the interrupt service function is executed. Now that the interrupt service function is executed, it needs to switch back to IRQ mode
  • Each time the interrupt ID is read from GICC_IAR, the interrupt ID value needs to be written to the GICC_EOIR register.
  • Restore SPSR, r0 - r3, r12 registers
  • subs pc, lr, #4
cps #0x12             /* 切换回 IRQ 模式 */
pop {r0, r1}          /* 对应之前的 push {r0, r1} */

str r0, [r1, #0x10]   /* 中断执行完成,写 EOIR */

pop {r0}
msr spsr_cxsf, r0     /* 恢复 spsr */
pop {r0-r3, r12}      /* r0-r3,r12 出栈 */
pop {lr}              /* lr 出栈 */

subs pc, lr, #4       /* 将 lr-4 赋给 pc */

Analysis: subs pc, lr, #4

This involves the problem of the instruction pipeline. When the instruction is executed, the decoder and the PC counter will not be idle, but will first obtain the instruction to be used next time. This results in a difference of 8 bytes between the instruction address saved in the PC counter and the instruction address actually being executed.

Assuming that an interrupt is generated when executing instruction 1, after executing the instruction at hand, it will transfer to the interrupt vector table, and then call the corresponding interrupt service function, but when it comes back, it needs to execute the instruction pointed to by PC, so we I found that instruction 2 was skipped directly here.

Therefore, after the interrupt service function is executed, we need to decrement the value of the PC register by 4, that is, return to the previous instruction and continue running.

Three, complete writing

push {lr}             /* 保存 lr 地址 */
push {r0-r3, r12}     /* 保存 r0-r3,r12 寄存器 */ 
mrs r0, spsr          /* 读取 spsr 寄存器 */
push {r0}             /* 保存 spsr 寄存器 */

mrc p15, 4, r1, c15, c0, 0         /* 将CP15协处理器的C0内的值保存到 r1 寄存器中 */
add r1, r1, #0x2000                /* r1中本就保存了 GIC 基地址,现在需要加上偏移 0x2000 */
                                   /* 于是就得到了CPU Interface 寄存器组的基地址 */
add r1, r1, #0x000C  
ldr r0, [r1]            /* 将 r1 寄存器指向的地址对应内容保存到 r0 */ 
ldr r4, =0x3ff          /* 无法直接使用 #0x3ff,因为 0x3ff 不是立即数*/
and r0, r0, r4          /* 取出低10位 */ 
push {r0, r1}                      /* 保存 r0,r1 */
​
cps #0x13                     /* 进入 SVC 模式,允许其他中断再次进去 */
push {lr}                     /* 保存 SVC 模式的 lr 寄存器 */
ldr r2, =system_irqhandler    /* 加载 C 语言中断处理函数到 r2 寄存器中*/
blx r2                        /* 运行 C 语言中断处理函数,带有一个参数 */
pop {lr}                      /* 执行完 C 语言中断服务函数,lr 出栈 */

​cps #0x12             /* 切换回 IRQ 模式 */
pop {r0, r1}          /* 对应之前的 push {r0, r1} */
str r0, [r1, #0x10]   /* 中断执行完成,写 EOIR */
pop {r0}
msr spsr_cxsf, r0     /* 恢复 spsr */
pop {r0-r3, r12}      /* r0-r3,r12 出栈 */
pop {lr}              /* lr 出栈 */

subs pc, lr, #4       /* 将 lr-4 赋给 pc */

Guess you like

Origin blog.csdn.net/challenglistic/article/details/131240525