ARM Linux interrupt mechanism interrupt processing

Publisher:心有归属Latest update time:2016-06-20 Source: eefocusKeywords:ARM Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere
  //Now let's take a look at another function of interrupt initialization, early_trap_init(), which is implemented in the file arch/arm/kernel/traps.c.

void __init early_trap_init(void)
{

//CONFIG_VECTORS_BASE is defined in autoconf.h (this file is automatically generated), the value is 0xffff0000,
 unsigned long vectors = CONFIG_VECTORS_BASE;
 extern char __stubs_start[], __stubs_end[];
 extern char __vectors_start[], __vectors_end[];
 extern char __kuser_helper_start[], __kuser_helper_end[];
 int kuser_sz = __kuser_helper_end - __kuser_helper_start;

 

/* Exception vector table is copied to 0x0000_0000 (or 0xFFFF_0000), and 
the exception handler stub is copied to 0x0000_0200 (or 0xFFFF_0200) */ 
 memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
 memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
 memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

 

/* Copy signal processing function*/ 
 memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
        sizeof(sigreturn_codes));

/* Refresh the cache and modify the access rights of the pages occupied by the exception vector table*/

 flush_icache_range(vectors, vectors + PAGE_SIZE);
 modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}

This function relocates the exception vector table and the exception handler stub defined in arch/arm/kernel/entry-armv.S
: the exception vector table is copied to 0xFFFF_0000, and the exception vector handler stub is copied to 0xFFFF_0200.
Then modify_domain() is called to modify the access rights of the page occupied by the exception vector table, which makes it impossible for the user state
to access the page, and only the kernel state can access it. 
 
When an exception occurs in the arm processor, it will always jump to the exception vector table at 0xFFFF_0000 (when set to "high-end vector configuration")
, so this relocation work is performed.

Exception vector table, in the file arch/arm/kernel/entry-armv.S

 .equ stubs_offset, __vectors_start + 0x200 - __stubs_start

 .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_pabt + stubs_offset //Software interrupt exception:
 b vector_dabt + stubs_offset //Data exception: 
 b vector_addrexcptn + stubs_offset //Reserved: 
 b vector_irq + stubs_offset //Normal interrupt exception:
 b vector_fiq + stubs_offset //Fast interrupt exception:

 .globl __vectors_end
__vectors_end:

When an ARM processor has an exception (an interrupt is a type of exception), it jumps to the exception vector table, finds the corresponding exception in the vector table, and jumps to

The exception handler is executed.

stubs_offset, defined as __vectors_start + 0x200 - __stubs_start.

In the interrupt initialization function early_trap_init(), the vector table is copied to 0xFFFF_0000 and the exception handling program segment is copied to 0xFFFF_0200.

For example, if an interrupt exception occurs at this time, vector_irq + stubs_offset will jump to the interrupt exception handler segment to execute. Due to vector_irq,

The jump position between the exception handler segment __stubs_start and __stubs_end will be __vectors_start + 0x200 + vector_irq - __stubs_start.

 

The exception handling procedure is as follows:

When an ARM processor has an exception (an interrupt is a type of exception), it jumps to the exception vector table, finds the corresponding exception in the vector table, and jumps to

The exception handler is executed at the location where the exception is handled, and these exception handlers are placed in the following exception handler segment.

 .globl __stubs_start
__stubs_start:

//vector_stub is a macro, which means a program is placed here. irq, IRQ_MODE, 4 are the parameters passed to the macro vector_stub.
 vector_stub irq, IRQ_MODE, 4

//The following is the jump table, which is used in the program segment represented by the macro vector_stub to find the location where the program should jump.

//If it is user mode when entering the terminal interrupt, call the __irq_usr routine. If it is system mode, call __irq_svc. If it is other modes, it means an error.

//Then call __irq_invalid.

 .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


 vector_stub dabt, ABT_MODE, 8

 .。。。。。。


 vector_stub pabt, ABT_MODE, 4

。。。。。。


 vector_stub and, AND_MODE

。。。。。。


vector_fiq:
 disable_fiq
 subs pc, lr, #4

vector_addrexcptn:
 b vector_addrexcptn
 .align 5

.LCvswi:
 .word vector_swi

 .globl __stubs_end
__stubs_end:

The program segment represented by the macro vector_stub is as follows: name, mode, correction store the passed parameters

 .macro vector_stub, name, mode, correction=0
 .align 5

vector_\name:
 .if \correction
 sub lr, lr, #\correction //Correct the return address, which is the address of the instruction to be executed after the interrupt is
 processed.endif

 @
 @ Save r0, lr_ (parent PC) and spsr_
 @ (parent CPSR)
 @

///Save the return address to the stack. Since we will use the r0 register soon, we also need to save r0. There is no ! after sp, so the location pointed to by sp does not change.

 stmia sp, {r0, lr} @ save r0, lr  

 mrs lr, spsr  
 str lr, [sp, #8]  @ save spsr

// A stack that grows upwards.

// At this time, the stack is the stack in interrupt mode, the stack in interrupt mode and system mode under ARM

// The stack is different. Although ARM provides seven modes, Linux only uses two, one

// One is user mode, the other is system mode, so this stack is just a temporary stack.

/*

In arch/arm/include/asm/ptrace.h, there are seven working modes of the processor.

#define USR_MODE 0x00000010
#define FIQ_MODE 0x00000011
#define IRQ_MODE 0x00000012
#define SVC_MODE 0x00000013
#define ABT_MODE 0x00000017
#define UND_MODE 0x0000001b
#define SYSTEM_MODE 0x0000001f

*/
 mrs r0, cpsr
 eor r0, r0, #(\mode ^ SVC_MODE)
 msr spsr_cxsf, r0 ////Set spsr to management mode. //Write all controls of spsr and inject all values ​​of r0 into spsr

 @
 @ the branch table must immediately follow this code
 @
 //and lr, lr, #0x0f // After this instruction, the lower 4 bits of spsr in lr are used. The 16 entries in the jump table above correspond to these 16 states.
 //mov r0, sp //Use r0 to save the address of the stack pointer

//When analyzing this program, remember that this program is placed in front of the jump table in the form of a macro vector_stub.

//Store the corresponding address entry in the jump table into lr. Because each entry in the jump table is 4 bytes long, we shift left by two bits here
 ldr lr, [pc, lr, lsl #2]

  movs pc, lr @ branch to handler in SVC mode // program jump.
ENDPROC(vector_\name)
 .endm

 

Here we take the interrupt exception occurring in user space as an example, that is, the program jumps to __irq_usr.

 .align 5
__irq_usr:
 usr_entry // usr_entry is a macro representing a program segment inserted here. The program segment represented by the macro usr_entry will be analyzed below (1)

 kuser_cmpxchg_check

#ifdef CONFIG_TRACE_IRQFLAGS
 bl trace_hardirqs_off
#endif

//Next, let's look at get_thread_info, which is also a macro used to get the address of the current thread. We will also analyze it later. tsk stores the address of the thread structure.

/*

The thread structure prototype is as follows in the file include/linux/sched.h

struct thread_info {
 struct task_struct *task;  /* main task structure */
 unsigned long  flags;
 struct exec_domain *exec_domain; /* execution domain */
 int   preempt_count; /* 0 => preemptable, <0 => BUG */
 __u32 cpu; /* should always be 0 on m68k */
 struct restart_block    restart_block;
};

*/
 get_thread_info tsk                                                                                             (2)
#ifdef CONFIG_PREEMPT

//TI_PREEMPT is defined in the file arch\arm\kernel\asm-offsets.c as the member preempt_count of the thread structure thread_info

//Offset in structure thread_info

/*

 Kernel mode can preempt the kernel. Only when preempt_count is 0, schedule() will be called to check
whether a process switch is needed. If necessary, it will switch.

*/
 ldr r8, [tsk, #TI_PREEMPT] //Get preempt_count
 add r7, r8, #1 @ increment it //Increment the member by one
 str r7, [tsk, #TI_PREEMPT] //Save the changed value into preempt_count
#endif

 irq_handler //Call interrupt handling function, irq_handler is a macro, described later (3)
#ifdef CONFIG_PREEMPT
 ldr r0, [tsk, #TI_PREEMPT]
 str r8, [tsk, #TI_PREEMPT]
 teq r0, r7
 strne r0, [r0, -r0]
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
 bl trace_hardirqs_on
#endif

 mov why, #0 //why is defined as r8 in the file arch/arm/kernel/entry-header.S. :why .req r8
 b ret_to_user //Return to user mode, this macro is defined in the file linux/arch/arm/kernel/entry-common.S. (4)
 UNWIND(.fnend )
ENDPROC(__irq_usr)

 

The following is an analysis of the above four macros. (usr_entry, get_thread_info tsk, irq_handler, ret_to_user)

(1)

 .macro usr_entry
 UNWIND(.fnstart )
 UNWIND(.cantunwind ) @ don't unwind the user space

//S_FRAME_SIZE is defined in the file arch\arm\kernel\asm-offsets.c to represent the size structure of the register structure pt_regs

//There are 18 registers r0~cpsr in pt_regs, which is 72 bytes.
 sub sp, sp, #S_FRAME_SIZE //Create stack space for register pt_regs structure, let stack pointer sp point to r0.

//stmib is a pre-storage addition, so space is reserved here for storing r0, and r1 - r12 are stored in the stack. No addition after sp!

//So the stack position pointed to by sp has not changed, and it always points to the storage space used to store r0.

 stmib sp, {r1 - r12}

// Take out the values ​​of r0, lr, spsr before the interrupt and store them in r1 - r3. At this time, r0 is used as the sp of the stack.

//Its value points to the location where the value of r0 is stored in the stack before the interrupt. Above the location of the register structure pt_regs in the stack.

 ldmia r0, {r1 - r3}  

 //S_PC is the PC register position in pt_regs, let r0 point to this position. Although S_PC has not been saved to the stack, its position in the stack exists
 add r0, sp, #S_PC  

 mov r4, #-1 //Put an invalid value in r4.

 str r1, [sp] //r1 stores the value of r0 before the interruption. At this time, the value is stored in the stack. The problem of the location of r0 flowing out of the stack has been explained above.
 
//At this time, r2-r4 stores the values ​​of lr and spsr before the interruption and invalid.

 // At this time, store these values ​​in the corresponding position of the register in pt_regs in the stack, that is, at this time, the values ​​of lr and spsr before the interrupt and the invalid

//Store in ARM_pc, ARM_cpsr, ARM_ORIG_r0 of register structure pt_regs.
 stmia r0, {r2 - r4}
 stmdb r0, {sp, lr}^ //stmdb is decremented, store ARM_lr, ARM_sp in lr, sp.


 alignment_trap r0

//The macro zero_fp is defined in the file arch/arm/kernel/entry-header.S, clearing fp.
 zero_fp
 .endm

 

The struct pt_regs mentioned above is defined in include/asm/ptrace.h

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]

(2)

//The macro macro get_thread_info is defined in the file arch/arm/kernel/entry-header.S. It is used to get the address of the current thread.

/*

include/linux/sched.h中:

union thread_union {

    struct thread_info thread_info; // thread attributes

    unsigned long stack[THREAD_SIZE/sizeof(long)]; // 栈

};

The thread defined by it is 8K byte aligned, and the thread_info object, that is, the object of the stack owner thread, is stored at the lowest address of this 8K. Get_thread_info obtains the address of the current thread_info object by clearing the lower 13 bits of sp to 0 (8K boundary).

THREAD_SIZE is defined in the file arch/arm/include/asm/thread_info.h: #define THREAD_SIZE 8192

*/

 .macro get_thread_info, rd
 mov \rd, sp, lsr #13
 mov \rd, \rd, lsl #13
 .endm

(3)

//The macro irq_handler is defined in the file arch/arm/kernel/entry-armv.S:

 .macro irq_handler

//The macro get_irqnr_preamble is a no-op, defined in the file arch/arm/mach-s3c2410/include/mach/entry-macro.S
 get_irqnr_preamble r5, lr  

//The macro get_irqnr_and_base gets the interrupt number by reading the register INTPND. Some parameters obtained in this macro will be stored in these registers r0, r6, r5, lr.

//The macro get_irqnr_and_base is defined in the file arch/arm/mach-s3c2410/include/mach/entry-macro.S. This macro will be discussed later.
1: get_irqnr_and_base r0, r6, r5, lr  
 movne r1, sp
 @
 @ routine called with r0 = irq number, r1 = struct pt_regs *
 @
 adrne lr, 1b

/*

// The above macro get_irqnr_and_base prepares the parameter interrupt number for calling asm_do_IRQ.

So asm_do_IRQ is called to handle the interrupt. Function asm_do_IRQ() is the C language entry point for the interrupt handling function. This function will be discussed later.

The function asm_do_IRQ() is implemented in the file linux/arch/arm/kernel/irq.c.

*/
 bne asm_do_IRQ

#ifdef CONFIG_SMP
 。。。。。。
 
#endif

 .endm

get_irqnr_and_base is platform-dependent. This macro queries the ISPR (IRQ pending interrupt service register. When there is an interrupt that needs to be processed, the corresponding bit of this register will be set. At any time, at most one bit will be set). The calculated interrupt number is placed in the register specified by irqnr. This macro is different on different ARM chips. The main function of this macro is to obtain the interrupt number of the interrupt. For s3c2440, the code is in arch/arm/mach-s3c2410/include/entry-macro.S. After the macro is processed, r0 = interrupt number.

 .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
  beq 1002f
  ldr \irqnr, [ \base, #INTOFFSET ]
  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
1002:
  @@ exit here, Z flag unset if IRQ

 .endm

(4)

The macro ret_to_user is defined in the file arch/arm/kernel/entry-common.S:

ENTRY(ret_to_user)
ret_slow_syscall:
 disable_irq //Disable interrupt
 ldr r1, [tsk, #TI_FLAGS] //Get the flags member of the thread structure thread_union
 tst r1, #_TIF_WORK_MASK //Judge whether the task is blocked
 bne work_pending //Switch the process as needed, the code is described below.
no_work_pending: //No need to switch the process
 /* 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 //// Save the status of the interrupted code in spsr (cpsp)
 ldmdb sp, {r0 - lr}^ //Restore the register values ​​before the interrupt to each register.
 mov r0, r0
 add sp, sp, #S_FRAME_SIZE - S_PC
 movs pc, lr //Return to user mode
ENDPROC(ret_to_user)

In arch/arm/kernel/entry-common.S

work_pending: 
 tst r1, #_TIF_NEED_RESCHED //Judge whether the process needs to be scheduled
 bne work_resched //Process scheduling
 tst r1, #_TIF_SIGPENDING
 beq no_work_pending //No need to schedule, return
 mov r0, sp @ 'regs'
 mov r2, why @ 'syscall'
 bl do_notify_resume
 b ret_slow_syscall @ Check work again

work_resched:
 bl schedule //Call process switching function.

 

Here we only discuss interrupt handling in user mode. The processing method in kernel mode is basically the same, so I will not go into details.

 

C language entry of interrupt handling function

 

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
 struct pt_regs *old_regs = set_irq_regs(regs);

 irq_enter(); //Enter interrupt context


 if (irq >= NR_IRQS)
  handle_bad_irq(irq, &bad_irq_desc);
 else
  generic_handle_irq(irq); //Get the interrupt description structure according to the interrupt number and call its interrupt handling function.


 irq_finish(irq); //Exit interrupt context

 irq_exit();
 set_irq_regs(old_regs);
}

//The function generic_handle_irq() is a wrapper for the function generic_handle_irq_desc().

static inline void generic_handle_irq(unsigned int irq)
{
 generic_handle_irq_desc(irq, irq_to_desc(irq));
}

/*

If the upper interrupt handling function desc->handle_irq is implemented, it is called. In fact, each interrupt handling function s3c24xx_init_irq() has been

The interrupt line is assigned an upper-level interrupt handling function.

If desc->handle_irq is empty, the general interrupt processing function __do_IRQ(irq); is called, and the function handle_IRQ_event() is called in the dry function.

Each interrupt routine on the interrupt line is executed in the function handle_IRQ_event().

*/

static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
 desc->handle_irq(irq, desc);
#else
 if (likely(desc->handle_irq))
  desc->handle_irq(irq, desc);
 else
  __do_IRQ(irq);
#endif
}

 
Keywords:ARM Reference address:ARM Linux interrupt mechanism interrupt processing

Previous article:ARM interrupt processing bottom-level analysis
Next article:ARM Linux boot analysis headarmv.S insider

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号