Detailed explanation of ARM interrupt implementation in linux-2.6.26 kernel

Publisher:骄阳少年Latest update time:2012-09-01 Source: 21IC Keywords:linux Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

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 PC) and spsr_

@ (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_ , already fixed up for correct return/restart

@ 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 //Each specific processor defines asmlinkage in this file

#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 , asmlinkage is not defined, so it is meaningless (don't think that the parameters are passed from the stack. For the ARM platform, it still complies with the ATPCS procedure call standard and is passed through registers).

But for X86 processors It is defined as follows:

[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.

Keywords:linux Reference address:Detailed explanation of ARM interrupt implementation in linux-2.6.26 kernel

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

Application of Linux System in Embedded DVR
    With the advantage of strong stability, embedded DVR is increasingly accepted by the security industry and customers. The stability advantage of embedded DVR does not only come from the low failure rate of hardware, but also depends largely on the high stability of the operating system and application software used
[Microcontroller]
The whole process of porting SD/MMC to S3C2440 under linux-2.6.24.3
1. First download the 2.6.24.3 kernel source code. Other kernels are not guaranteed to work. I won't talk about the porting part. If you don't know how to do it, please refer to my other articles. I only tested this one. 2. Go to http://svnweb.openmoko.org/*check ... _mci.patch?rev=4096 to download the S
[Microcontroller]
Linux UART serial port driver development documentation
1. Serial port interface and layer of Linux. Serial port is a widely used device, so the support under Linux is very complete, with a unified programming interface. The complete work of driver developers is to complete the corresponding configuration macros for different serial port ICs. These configuration macr
[Microcontroller]
基於tiny4412的Linux內核移植--- 中斷和GPIO學習(1)
平臺 tiny4412 ADK Linux-4.4.4 u-boot使用的U-Boot 2010.12,是友善自帶的,爲支持設備樹和uImage做了稍許改動 Overview 這篇博客以一個簡單的按鍵中斷來演示一下有了設備樹後的中斷的使用,其中涉及到新版kernel的pinctrl和gpio子系統。 在tiny4412的底板上有四個key,如下: 上圖中,在沒有按鍵的時候,對應的GPIO是被拉高的,當按下鍵的時候,對應的GPIO被拉低,從而產生一個下降沿中斷。 有了上面的準備,首先我們需要修改設備樹,添加相應的節點和相關的屬性:  1 diff --git a/arch/arm/boot/dts/ex
[Microcontroller]
ARM Study Notes 001 arm-linux-gcc 4.3.2 Download and Installation
Download arm-linux-gcc-4.3.2.tgz (84MB) Install the cross-compilation toolchain: 1. First log in as root user 2. Copy arm-linux-gcc-4.3.2.tgz to the tmp folder in the root directory 3. Decompression command     tar xvzf arm-linux-gcc-4.3.2.tgz -C /     Note that there is a space between tgz and -C, -C is upperca
[Microcontroller]
ARM Study Notes 001 arm-linux-gcc 4.3.2 Download and Installation
Python2.5.4 ported to arm-linux
1 Migration requirements Requirement: Python 2.5.4 running on Arm9. ARM Target Environment: S3C2410A & arm-linux-2.4.18, and AT91SAM9261 & arm-linux-2.6.20. Host compilation environment: RHEL5U3, gcc 4.1.2, arm-linux-gcc 2.95.3,arm-linux-gcc 3.4.4 Packages that require cross compile: sqlite 3.6.14, python
[Microcontroller]
Undefined instruction error when uboot starts Linux kernel
Error description U-Boot 1.1.6 (Oct 18 2011 - 15:23:51) for FriendlyARM MINI6410 CPU: S3C6410@532MHz Fclk = 532MHz, Hclk = 133MHz, Pclk = 66MHz, Serial = CLKUART (SYNC Mode) Board: MINI6410 DRAM: 256 MB Flash: 0 kB NAND: 256 MB In: serial Out: serial Err: s
[Microcontroller]
Run/debug arm linux with Qemu
I have used Qemu to run/debug arm linux several times, but I forgot about it after a while. I had to look up the information again every time, which wasted a lot of time. This time I made the whole process into a script and put it on github. You can just download it and run it, which is convenient for me and friends
[Microcontroller]
Latest Microcontroller Articles
  • Download from the Internet--ARM Getting Started Notes
    A brief introduction: From today on, the ARM notebook of the rookie is open, and it can be regarded as a place to store these notes. Why publish it? Maybe you are interested in it. In fact, the reason for these notes is ...
  • Learn ARM development(22)
    Turning off and on interrupts Interrupts are an efficient dialogue mechanism, but sometimes you don't want to interrupt the program while it is running. For example, when you are printing something, the program suddenly interrupts and another ...
  • Learn ARM development(21)
    First, declare the task pointer, because it will be used later. Task pointer volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • Learn ARM development(20)
    With the previous Tick interrupt, the basic task switching conditions are ready. However, this "easterly" is also difficult to understand. Only through continuous practice can we understand it. ...
  • Learn ARM development(19)
    After many days of hard work, I finally got the interrupt working. But in order to allow RTOS to use timer interrupts, what kind of interrupts can be implemented in S3C44B0? There are two methods in S3C44B0. ...
  • Learn ARM development(14)
  • Learn ARM development(15)
  • Learn ARM development(16)
  • Learn ARM development(17)
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号