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 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
}
Previous article:ARM interrupt processing bottom-level analysis
Next article:ARM Linux boot analysis headarmv.S insider
- Popular Resources
- Popular amplifiers
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- LED chemical incompatibility test to see which chemicals LEDs can be used with
- Application of ARM9 hardware coprocessor on WinCE embedded motherboard
- What are the key points for selecting rotor flowmeter?
- LM317 high power charger circuit
- A brief analysis of Embest's application and development of embedded medical devices
- Single-phase RC protection circuit
- stm32 PVD programmable voltage monitor
- Introduction and measurement of edge trigger and level trigger of 51 single chip microcomputer
- Improved design of Linux system software shell protection technology
- What to do if the ABB robot protection device stops
- 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
- Last week: 100 sets of Pingtouge RISC-V development boards, invite you to play ~ Come quickly ~ Share with friends and win red envelopes
- HP laser printer original toner cartridges are useless if you don't open them after using them up.
- Problems caused by macros that IAR cannot find defaulting to 0
- help
- FPGA technology introduction and FPGA application fields
- Learn MSP430F5529 programming routines
- Diode as a temperature compensation circuit for transistors
- TL431 as a voltage regulator
- Is the product on Taobao claiming to be an energy saver genuine?
- Questions about DC Boost Circuit