I have read some articles on the Internet about the implementation of Linux interrupts, and I feel that some of them are very well written. I would like to thank them for their selfless contributions, and then I would like to add my understanding of some issues. Let's start with the problem of function registration.
1. Interrupt registration method
The function used to request an interrupt in the Linux kernel is request_IRq(), and the function prototype is defined in Kernel/irq/manage.c:
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned lONg irqflags, const char *devname, void *dev_id)
irq is the hardware interrupt number to be applied for.
Handler is an interrupt handling function registered with the system. It is a callback function. When an interrupt occurs, the system calls this function and the dev_id parameter will be passed to it.
irqflags is the property of interrupt processing. If IRQF_DISABLED is set (SA_INteRRUPT in the old version, which is no longer supported in this version), it means that the interrupt handler is a fast handler. When the fast handler is called, all interrupts are blocked, and the slow handler is not blocked. If IRQF_SHARED is set (SA_SHIRQ in the old version), it means that multiple devices share interrupts. If IRQF_SAMPLE_RANDOM is set (SA_SAMPLE_RANDOM in the old version), it means that it contributes to the system entropy and is good for the system to obtain random numbers. (These flags can be used simultaneously in or)
dev_id is used when interrupts are shared and is generally set to the device structure of this device or NULL.
devname sets the interrupt name, which can be seen in cat /proc/interrupts.
request_irq() returns 0 for success, -INVAL for invalid interrupt number or NULL for processing function pointer, and -EBUSY for interrupt already occupied and cannot be shared.
For examples of interrupt registration, you can search for request_irq in the kernel.
In the process of writing drivers, the most common places to get confused are:
1. Where is the interrupt vector table located? How is it established?
2. Starting from the interrupt, how does the system execute the function I registered myself?
3. How is the interrupt number determined? How is the interrupt number determined for hardware with sub-interrupts?
4. What is interrupt sharing? What is the function of dev_id?
This article uses the 2.6.26 kernel and S3C2410 processor as examples to explain these issues.
2. Establishment of exception vector table
In most processors after ARM V4 and V4T, the interrupt vector table can have two locations: one is 0 and the other is 0xffff0000. It can be controlled by the V bit (bit[13]) in the CP15 coprocessor c1 register. The corresponding relationship between V and the interrupt vector table is as follows:
V=0 ~ 0x00000000~0x0000001C
V=1 ~ 0xffff0000~0xffff001C
arch/arm/mm/proc-arm920.S中
.section ".text.init", #alloc, #execinstr
__arm920_setup:
…… orr r0, r0, #0x2100 @ ..1. ...1 ..11 ...1
//bit13=1 The interrupt vector table base address is 0xFFFF0000. The value of R0 will be paid to C1 of CP15.
In Linux, the function to create the vector table is:
init/main.c->start_kernel()->trap_init()
void __init trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE;
……
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
....
}
In the 2.6.26 kernel CONFIG_VECTORS_BASE was originally set in the configuration files for each platform, such as:
arch/arm/configs/s3c2410_defconfig中
CONFIG_VECTORS_BASE=0xffff0000
The exception vector table is between __vectors_end and __vectors_start.
Located in arch/arm/kernel/entry-armv.S
.globl __vectors_start
__vectors_start:
swi SYS_ERROR0:
b vector_und + stubs_offset //Reset exception:
ldr pc, .LCvswi + stubs_offset //Undefined instruction exception:
b vector_pa^ + stubs_offset //Software interrupt exception:
b vector_da^ + stubs_offset //Data anomaly:
b vector_addrexcptn + stubs_offset //Reserved:
b vector_irq + stubs_offset //Ordinary interrupt exception:
b vector_fiq + stubs_offset //Fast interrupt exception:
.globl __vectors_end:
__vectors_end:
The exception handling is between __stubs_end and __stubs_start. It is also located in the file arch/arm/kernel/entry-armv.S. vector_und, vector_pa^, vector_irq, and vector_fiq are all between them.
The stubs_offset values are as follows:
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
How is stubs_offset determined? (Quote a more detailed explanation on the Internet)
When the assembler sees the B instruction, it will convert the label to jump to into an offset (±32M) relative to the current PC and write it into the instruction code. From the above code, we can see that the interrupt vector table and stubs have both moved code, so if the interrupt vector table is still written as b vector_irq, then when actually executed, it will not be able to jump to the moved vector_irq, because the instruction code writes the original offset, so the offset in the instruction code needs to be written as the one after the move. We record the irq entry address in the interrupt vector table before the move as irq_PC, and its offset in the interrupt vector table is irq_PC-vectors_start, and the offset of vector_irq in the stubs is vector_irq-stubs_start. These two offsets remain unchanged before and after the move. After the move, vectors_start is at 0xffff0000, and stubs_start is at 0xffff0200, so the offset of the moved vector_irq relative to the interrupt entry address in the interrupt vector is 200 + the offset of vector_irq in the stubs minus the offset of the interrupt entry in the vector table, that is, 200 + vector_irq-stubs_start-irq_PC + vectors_start = (vector_irq-irq_PC) + vectors_start+200-stubs_start. The value in the brackets is actually the vector_irq written in the interrupt vector table. The subtraction of irq_PC is done by the assembler, and the subsequent vectors_start+200-stubs_start should be stubs_offset, which is actually defined in entry-armv.S.
[page]
3. Interrupt handling process
This section will take S3C2410 as an example to describe how the interrupt is executed step by step to our registered function in the linux-2.6.26 kernel, starting from the interrupt.
3.1 Interrupt vector table archarmkernelentry-armv.S
__vectors_STart:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pa^ + stubs_offset
b vector_da^ + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_IRq + stubs_offset
b vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
After the interrupt occurs, jump to b vector_irq + stubs_offset. Note that the initial position of the vector table is now 0xffff0000.
3.2 Interrupt jump entry location archarmkernelentry-armv.S
.globl __stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4 @IRQ_MODE is defined in includeasmptrace.h: 0x12
.lONg __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
The vector_stub macro in the above code is defined as:
.macro vector_stub, name, mode, correcTIon=0
.align 5
vector_nAME:
.if correction
sub lr, lr, #correction
.endif
@
@ Save r0, lr_
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(mode ^ SVC_MODE)
msr spsr_cxsf, r0 @Prepare for entering svc mode later
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f @The last 4 bits of the mode before entering the interrupt
@#define USR_MODE 0x00000010
@#define FIQ_MODE 0x00000011
@#define IRQ_MODE 0x00000012
@#define SVC_MODE 0x00000013
@#define ABT_MODE 0x00000017
@#define AND_MODE 0x0000001b
@#define SYSTEM_MODE 0x0000001f
mov r0, sp
ldr lr, [pc, lr, lsl #2] @If it is usr before entering the interrupt, then take out the content of PC+4*0, that is, __irq_usr @If it is svc before entering the interrupt, then take out the content of PC+4*3, that is, __irq_svc
movs pc, lr @ When the destination register of the instruction is PC and the instruction ends with S, it will restore the value of @ spsr to cpsr branch to handler in SVC mode
.endm
.globl __stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
Replace the "name, mode, correction" in the macro vector_stub with "irq, IRQ_MODE, 4" and find that the entry position of our interrupt processing is vector_irq (vector_name in the macro).
From the comments in the above code, we can see that depending on the working mode before entering the interrupt, the next step of the program will jump to _irq_usr or __irq_svc. We first select __irq_usr as the target for the next step of tracking.
3.3 __irq_usr implementation archarmkernelentry-armv.S
__irq_usr:
usr_entry @ Explanation follows
kuser_cmpxchg_check
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
get_thread_info tsk @Get the address of the member variable thread_info in the process descriptor of the current process and save the address to register tsk equal to r9 (defined in entry-header.S)
#ifdef CONFIG_PREEMPT //If preemption is defined, increase the preemption value
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
add r7, r8, #1 @ increment it
str r7, [tsk, #TI_PREEMPT]
#endif
irq_handler @interrupt processing, which is what we are most concerned about. The implementation process is described in Section 3.4.
#ifdef CONFIG_PREEMPT
ldr r0, [tsk, #TI_PREEMPT]
str r8, [tsk, #TI_PREEMPT]
teq r0, r7
collapse r0, [r0, -r0]
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on
#endif
mov why, #0
b ret_to_user @Interrupt processing is completed, return to the location where the interrupt occurred, the implementation process is in Section 3.7
The usr_entry in the above code is a macro, which mainly saves the registers and interrupt return addresses in the usr mode to the stack.
.macro usr_entry
sub sp, sp, #S_frame_SIZE @ The value of S_FRAME_SIZE is in archarmkernelasm-offsets.c
@ Define DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs)); is actually equal to 72
stmib sp, {r1 - r12}
ldmia r0, {r1 - r3}
add r0, sp, #S_PC @ here for interlock avoidance
mov r4, #-1 @ "" "" "" ""
str r1, [sp] @ save the "real" r0 copied
@ from the exception stack
@
@ We are now ready to fill in the remaining blanks on the stack:[page]
@
@ r2 - lr_
@ r3 - spsr_
@ r4 - orig_r0 (see pt_regs definition in ptrace.h)
@
@ Also, separately save sp_usr and lr_usr
@
stmia r0, {r2 - r4}
stmdb r0, {sp, lr}^
@
@ Enable the alignment trap while in kernel mode
@
alignment_trap r0
@
@ Clear FP to mark the first stack frame
@
zero_fp
.endm
The above code mainly fills the structure pt_regs. The struct pt_regs mentioned here is defined in include/asm/ptrace.h. At this time, sp points to struct pt_regs.
struct pt_regs {
long uregs[18];
};
#define ARM_cpsr uregs[16]
#define ARM_pc uregs[15]
#define ARM_lr uregs[14]
#define ARM_sp uregs[13]
#define ARM_ip uregs[12]
#define ARM_fp uregs[11]
#define ARM_r10 uregs[10]
#define ARM_r9 uregs[9]
#define ARM_r8 uregs[8]
#define ARM_r7 uregs[7]
#define ARM_r6 uregs[6]
#define ARM_r5 uregs[5]
#define ARM_r4 uregs[4]
#define ARM_r3 uregs[3]
#define ARM_r2 uregs[2]
#define ARM_r1 uregs[1]
#define ARM_r0 uregs[0]
#define ARM_ORIG_r0 uregs[17]
3.4 irq_handler implementation process, archarmkernelentry-armv.S
.macro irq_handler
get_irqnr_preamble r5, lr
@In include/asm/arch-s3c2410/entry-macro.s, the macro get_irqnr_preamble is defined as a null operation, doing nothing
1: get_irqnr_and_base r0, r6, r5, lr @Judge the interrupt number and return through R0. The implementation process is in Section 3.5
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, 1b
bne asm_do_IRQ @Enter interrupt processing.
……
.endm
3.5 get_irqnr_and_base interrupt number judgment process, include/asm/arch-s3c2410/entry-macro.s
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
mov base, #S3C24XX_VA_IRQ
@@ try the interrupt offset register, since it is there
ldr irqstat, [ base, #INTPND ]
teq irqstat, #0
frog 1002f
ldr irqnr, [ base, #INTOFFSET ] @Get the interrupt position by judging the INTOFFSET register
mov tmp, #1
tst irqstat, tmp, lsl irqnr
bne 1001f
@@ the number specified is not a valid irq, so try
@@ and work it out for ourselves
mov irqnr, #0 @@ start here
@@ work out which irq (if any) we got
movs tmp, irqstat, lsl#16
addeq irqnr, irqnr, #16
moveq irqstat, irqstat, lsr#16
tst irqstat, #0xff
addeq irqnr, irqnr, #8
moveq irqstat, irqstat, lsr#8
tst irqstat, #0xf
addeq irqnr, irqnr, #4
moveq irqstat, irqstat, lsr#4
tst irqstat, #0x3
addeq irqnr, irqnr, #2
moveq irqstat, irqstat, lsr#2
tst irqstat, #0x1
addeq irqnr, irqnr, #1
@@ we have the value
1001:
adds irqnr, irqnr, #IRQ_EINT0 @Add the base value of the interrupt number to get the final interrupt number. Note: The specific situation of the sub-interrupt is not considered at this time (the problem of sub-interrupt will be explained later). IRQ_EINT0 is defined in include/asm/arch-s3c2410/irqs.h. From here, it can be seen that the specific value of the interrupt number is determined by the platform-related code, which is not equal to the interrupt number in the hardware interrupt pending register.
1002:
@@ exit here, Z flag unset if IRQ
.endm
3.6 asm_do_IRQ implementation process, arch/arm/kernel/irq.c
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct irq_desc *desc = irq_desc + irq; //Find the corresponding irq_desc according to the interrupt number
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (irq >= NR_IRQS)
desc = &bad_irq_desc;
irq_enter(); //No special work, you can skip it
desc_handle_irq(irq, desc); // Enter interrupt processing according to interrupt number and desc
/* AT91 specific workaround */
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);
}
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc); //interrupt handling
}
The above asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs) uses the asmlinkage identifier. So how do we understand the meaning of this identifier?
This symbol is defined in kernel/include/linux/linkage.h as follows:
#include
#ifdef __cplusplus
#define CPP_ASMLINKAGE extern "C"
#else
#define CPP_ASMLINKAGE
#endif
#ifndef asmlinkage //If asmlinkage was not defined before
#define asmlinkage CPP_ASMLINKAGE
#endif
For ARM processors
But for X86 processors
[page]
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
Indicates that function parameter passing is done through the stack.
3.7 Describe the ret_to_user interrupt return process in Section 3.3, /arch/arm/kernel/entry-common.S
ENTRY(ret_to_user)
ret_slow_syscall:
disable_irq @ disable interrupts
ldr r1, [tsk, #TI_FLAGS]
tst r1, #_TIF_WORK_MASK
bne work_pending
no_work_pending:
/* perform. architecture specific actions before user return */
arch_ret_to_user r1, lr
@ slow_restore_user_regs
ldr r1, [sp, #S_PSR] @ get calling cpsr
ldr lr, [sp, #S_PC]! @ get pc
msr spsr_cxsf, r1 @ save in spsr_svc
ldmdb sp, {r0 - lr}^ @ get calling r0 - lr
mov r0, r0
add sp, sp, #S_FRAME_SIZE - S_PC
movs pc, lr @ return & move spsr_svc into cpsr
Chapter 3 mainly traces the process from the occurrence of an interrupt to the call to the desc->handle_irq(irq, desc) interrupt function corresponding to the interrupt number. The following chapters will continue to explain the following content.
Previous article:Design of autonomous obstacle avoidance robot fish based on ARM
Next article:Design of remote temperature control system using STM32
Recommended ReadingLatest update time:2024-11-16 15:30
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- Innolux's intelligent steer-by-wire solution makes cars smarter and safer
- 8051 MCU - Parity Check
- How to efficiently balance the sensitivity of tactile sensing interfaces
- What should I do if the servo motor shakes? What causes the servo motor to shake quickly?
- 【Brushless Motor】Analysis of three-phase BLDC motor and sharing of two popular development boards
- Midea Industrial Technology's subsidiaries Clou Electronics and Hekang New Energy jointly appeared at the Munich Battery Energy Storage Exhibition and Solar Energy Exhibition
- Guoxin Sichen | Application of ferroelectric memory PB85RS2MC in power battery management, with a capacity of 2M
- Analysis of common faults of frequency converter
- In a head-on competition with Qualcomm, what kind of cockpit products has Intel come up with?
- Dalian Rongke's all-vanadium liquid flow battery energy storage equipment industrialization project has entered the sprint stage before production
- Allegro MicroSystems Introduces Advanced Magnetic and Inductive Position Sensing Solutions at Electronica 2024
- Car key in the left hand, liveness detection radar in the right hand, UWB is imperative for cars!
- After a decade of rapid development, domestic CIS has entered the market
- Aegis Dagger Battery + Thor EM-i Super Hybrid, Geely New Energy has thrown out two "king bombs"
- A brief discussion on functional safety - fault, error, and failure
- In the smart car 2.0 cycle, these core industry chains are facing major opportunities!
- The United States and Japan are developing new batteries. CATL faces challenges? How should China's new energy battery industry respond?
- Murata launches high-precision 6-axis inertial sensor for automobiles
- Ford patents pre-charge alarm to help save costs and respond to emergencies
- New real-time microcontroller system from Texas Instruments enables smarter processing in automotive and industrial applications
- How to import pictures in AD19 version and run script software crash
- JD642 SDRAM bus matching pre-simulation and pcb download
- [STM32WB55 Review] 6# Use of STM32WB development board UART
- [New Year's Taste Competition] + New Year's Eve dinner + New Year's 7 days of fun, I wish you all a prosperous New Year, smooth sailing and a good start.
- Sell TI POS Kit Chip
- cc2530 zigbee-zha modification supports serial port 1 position 1 P0_4 P0_5
- EEWORLD University ---- Learn myRIO with me
- Classic MATLAB simulation model for parameter identification of three-phase asynchronous motor
- 900 yuan, looking to buy a DE1-SOC, no accessories are required [
- EEWORLD University Hall----Introduction to FPGA Peripheral Circuits (Intel Official Tutorial)