linux arm irq (2): interrupt handling


linux arm irq (2)


2 interrupt handling



Author: Yangkai Wang
wang_yangkai@163.com
Coding in 2021/05/10
轉載請注明author,出處.



linux version 3.4.39
s5p6818 soc

Cortex-A53 Octa core CPU
Interrupt Controller,GIC400

  • idle進程(start_kernel)stack(svc)的設置
/* arch/arm/kernel/head-common.S */

...
/*
 * The following fragment of code is executed with the MMU on in MMU mode,
 * and uses absolute addresses; this is not position independent.
 *
 *  r0  = cp#15 control register
 *  r1  = machine ID
 *  r2  = atags/dtb pointer
 *  r9  = processor ID
 */
	__INIT
__mmap_switched:
	adr	r3, __mmap_switched_data

	ldmia	r3!, {r4, r5, r6, r7}
	cmp	r4, r5				@ Copy data segment if needed
1:	cmpne	r5, r6
	ldrne	fp, [r4], #4
	strne	fp, [r5], #4
	bne	1b

	mov	fp, #0				@ Clear BSS (and zero fp)
1:	cmp	r6, r7
	strcc	fp, [r6],#4
	bcc	1b

 ARM(	ldmia	r3, {r4, r5, r6, r7, sp})
 THUMB(	ldmia	r3, {r4, r5, r6, r7}	)
 THUMB(	ldr	sp, [r3, #16]		)
	str	r9, [r4]			@ Save processor ID
	str	r1, [r5]			@ Save machine type
	str	r2, [r6]			@ Save atags pointer
	bic	r4, r0, #CR_A			@ Clear 'A' bit
	stmia	r7, {r0, r4}			@ Save control register values
	b	start_kernel
ENDPROC(__mmap_switched)
...


	.align	2
	.type	__mmap_switched_data, %object
__mmap_switched_data:
	.long	__data_loc			@ r4
	.long	_sdata				@ r5
	.long	__bss_start			@ r6
	.long	_end				@ r7
	.long	processor_id			@ r4
	.long	__machine_arch_type		@ r5
	.long	__atags_pointer			@ r6
	.long	cr_alignment			@ r7
	.long	init_thread_union + THREAD_START_SP @ sp
	.size	__mmap_switched_data, . - __mmap_switched_data

__mmap_switched:
adr r3, __mmap_switched_data
...
ARM( ldmia r3, {r4, r5, r6, r7, sp}) /* init_thread_union + THREAD_START_SP @ sp */
b start_kernel

/* arch/arm/kernel/init_task.c */
...
/*
 * Initial thread structure.
 *
 * We need to make sure that this is 8192-byte aligned due to the
 * way process stacks are handled. This is done by making sure
 * the linker maps this in the .text segment right after head.S,
 * and making head.S ensure the proper alignment.
 *
 * The things we do for performance..
 */
union thread_union init_thread_union __init_task_data =
	{ INIT_THREAD_INFO(init_task) };
...
/* include/linux/init_task.h */
...
/* Attach to the init_task data structure for proper alignment */
#define __init_task_data __attribute__((__section__(".data..init_task")))
...

其他SMP core,stack怎么設置的,以后再分析;


  • linux kernel arm exception stack init
  • sets up the CPU(startup core) stacks
void __init start_kernel(void)
	|
	setup_arch(&command_line);
		|
		setup_processor();
			|
			cpu_init();
/* arch/arm/kernel/setup.c */

...
struct stack {
	u32 irq[3];
	u32 abt[3];
	u32 und[3];
} ____cacheline_aligned;

static struct stack stacks[NR_CPUS];
...

/*
 * cpu_init - initialise one CPU.
 *
 * cpu_init sets up the per-CPU stacks.
 */
void cpu_init(void)
{
	unsigned int cpu = smp_processor_id();
	struct stack *stk = &stacks[cpu];

	if (cpu >= NR_CPUS) {
		printk(KERN_CRIT "CPU%u: bad primary CPU number\n", cpu);
		BUG();
	}

	cpu_proc_init();

	/*
	 * Define the placement constraint for the inline asm directive below.
	 * In Thumb-2, msr with an immediate value is not allowed.
	 */
#ifdef CONFIG_THUMB2_KERNEL
#define PLC	"r"
#else
#define PLC	"I"
#endif

	/*
	 * setup stacks for re-entrant exception handlers
	 */
	__asm__ (
	"msr	cpsr_c, %1\n\t"
	"add	r14, %0, %2\n\t"
	"mov	sp, r14\n\t"
	"msr	cpsr_c, %3\n\t"
	"add	r14, %0, %4\n\t"
	"mov	sp, r14\n\t"
	"msr	cpsr_c, %5\n\t"
	"add	r14, %0, %6\n\t"
	"mov	sp, r14\n\t"
	"msr	cpsr_c, %7"
	    :
	    : "r" (stk),
	      PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),
	      "I" (offsetof(struct stack, irq[0])),
	      PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),
	      "I" (offsetof(struct stack, abt[0])),
	      PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),
	      "I" (offsetof(struct stack, und[0])),
	      PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE)
	    : "r14");
}

arm c代碼中內嵌匯編,語法形式: asm(匯編語句: 輸出操作數列表: 輸入操作數列表: 破壞描述部分)
匯編語句,由匯編語句序列組成,語句之間使用 “;”、“\n”或“\n\t”分開;
指令中的操作數可以使用占位符引用C語言變量,操作數占位符最多10個,名稱如下:%0,%1,%2,…,%9;
指令中使用占位符表示的操作數,總被視為long型(4 byte);

輸入和輸出操作數列表的格式是一樣的,是由一個或者多個操作數組成,多個操作數采用逗號隔開,單個操作數的格式:
[C變量在匯編中的訪問名稱] "限制性字符“ (C傳遞進來的變量名稱或立即數)

常用限制性字符:
I: 立即數
m: 內存地址(變量地址)
r: 寄存器(r0-r15,傳參)


/* ARM ®  Architecture Reference Manual(ARM ® v7-A and ARM ® v7-R edition)
Application Level Programmers’ Model
Instruction Details*/

A8.6.102 MRS
A8.6.103 MSR (immediate)
A8.6.104 MSR (register)

Move to Special Register from ARM core register moves selected bits of a general-purpose register to the 
APSR.

Encoding T1 ARMv6T2, ARMv7
MSR<c> <spec_reg>,<Rn>
...
<spec_reg> Is one of:
• APSR_<bits>
• CPSR_<fields>.

ARM,只有 MSR instruction可以設置APSR processor狀態寄存器(CPSR/SPSR)

PS:
MRS
Move to Register from Special Register moves the value from the APSR into a general-purpose register.
MRS ,<spec_reg>

只有 MRS instruction可以讀取APSR processor狀態寄存器(CPSR/SPSR)

"msr	cpsr_c, %1\n\t"    /* 將(PSR_F_BIT | PSR_I_BIT | IRQ_MODE) 賦值給CPSR, 進入IRQ mode */
"add	r14, %0, %2\n\t"    /*  stk的地址值 + ((size_t) &((struct stack *)0)->irq[0]) 的值賦值給r14寄存器 */
"mov	sp, r14\n\t"    /* the value of r14 賦值給IRQ mode 的SP register */

依次設置IRQ,ABT,UND,模式的stack;  最后"msr	cpsr_c, %7", 切回SVC mode;

  • early_trap_init(vectors)
void __init start_kernel(void)
	|
	setup_arch(&command_line);
		|
		paging_init(mdesc);
			|
			map_lowmem();
				|
				/* Map all the lowmem memory banks. */
			|
			devicemaps_init(mdesc);
				|
				/*
	 			* Allocate the vector page early.
	 			*/
				vectors = early_alloc(PAGE_SIZE);

				early_trap_init(vectors);
				...

				/*
				 * Create a mapping for the machine vectors at the high-vectors
				 * location (0xffff0000).  If we aren't using high-vectors, also
				 * create a mapping at the low-vectors virtual address.
				 */
				map.pfn = __phys_to_pfn(virt_to_phys(vectors));
				map.virtual = 0xffff0000;
				map.length = PAGE_SIZE;
				map.type = MT_HIGH_VECTORS;
				create_mapping(&map, false);

				if (!vectors_high()) {
					map.virtual = 0;
					map.type = MT_LOW_VECTORS;
					create_mapping(&map, false);
				}
				...

/* arch/arm/kernel/traps.c */

...
void __init early_trap_init(void *vectors_base)
{
	unsigned long vectors = (unsigned long)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;

	vectors_page = vectors_base;

	/*
	 * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
	 * into the vector page, mapped at 0xffff0000, and ensure these
	 * are visible to the instruction stream.
	 */
	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);

	/*
	 * Do processor specific fixups for the kuser helpers
	 */
	kuser_get_tls_init(vectors);

	/*
	 * Copy signal return handlers into the vector page, and
	 * set sigreturn to be a pointer to these.
	 */
	memcpy((void *)(vectors + KERN_SIGRETURN_CODE - CONFIG_VECTORS_BASE),
	       sigreturn_codes, sizeof(sigreturn_codes));
	memcpy((void *)(vectors + KERN_RESTART_CODE - CONFIG_VECTORS_BASE),
	       syscall_restart_code, sizeof(syscall_restart_code));

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

call early_alloc(PAGE_SIZE)
|
memblock_alloc()
申請了一個page 4K大小的內存;

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這三段到這個page;這樣分布:


__vectors_start /* 0x0*/
...
__vectors_end
...

__stubs_start /* 0x200 */
...
__stubs_end
...

__kuser_helper_start
...
__kuser_helper_end
...


之后,將這個page 映射到虛擬地址0xffff0000;

reference:
/* ARM ®  Architecture Reference Manual(ARM ® v7-A and ARM ® v7-R edition)
Part B System Level Architecture
The System Level Programmers’ Model
B1.6 Exceptions
B1.6.1 Exception vectors and the exception base address */

If the Security Extensions are not implemented there is a single exception base address. This is controlled 
by the SCTLR.V bit:
V == 0  Exception base address = 0x00000000. This setting is referred to as normal vectors, or as low 
vectors.
V == 1  Exception base address = 0xFFFF0000. This setting is referred to as high vectors, or Hivecs.

linux ARM32 不配置為Exception base address = 0x00000000,因為0x00000000屬於0G-3G,用戶空間,就不去分割占用 用戶空間了,讓0-3G 連續地址空間都給應用程序用;



  • exception entry
/* ARM ®  Architecture Reference Manual(ARM ® v7-A and ARM ® v7-R edition)
Part B System Level Architecture
The System Level Programmers’ Model
B1.6 Exceptions */

B1.6.3 Exception entry
On taking an exception:
1. The value of the CPSR is saved in the SPSR for the exception mode that is handling the exception. 
2. The value of (PC + exception-dependent offset) is saved in the LR for the exception mode that is handling the exception, see Table B1-4.
3. The CPSR and PC are updated with information for the exception handler:
	• The CPSR is updated with new context information. This includes:
		— Setting CPSR.M to the processor mode in which the exception is to be handled. 
		— Disabling appropriate classes of interrupt, to prevent uncontrolled nesting of exception handlers. For more information, see Table B1-6 on page B1-36, Table B1-7 on page B1-37, and Table B1-8 on page B1-37.
		— Setting the instruction set state to the instruction set chosen for exception entry, see Instruction set state on exception entry on page B1-35.
		— Setting the endianness to the value chosen for exception entry, see CPSR.E bit value on exception entry on page B1-38.
		— Clearing the IT[7:0] bits to 0.
		For more information, see CPSR M field and A, I, and F mask bit values on exception entry on page B1-36.
	• The appropriate exception vector is loaded to the PC, see Exception vectors and the exception base address on page B1-30.
4. Execution continues from the address held in the PC.
...

B1.6.4 Exception return
...


  • exception vectors
/* arch/arm/kernel/entry-armv.S */

...
	.equ	stubs_offset, __vectors_start + 0x200 - __stubs_start

	.globl	__vectors_start
__vectors_start:
 ARM(	swi	SYS_ERROR0	)
 THUMB(	svc	#0		)
 THUMB(	nop			)
	W(b)	vector_und + stubs_offset
	W(ldr)	pc, .LCvswi + stubs_offset
	W(b)	vector_pabt + stubs_offset
	W(b)	vector_dabt + stubs_offset
	W(b)	vector_addrexcptn + stubs_offset
	W(b)	vector_irq + stubs_offset
	W(b)	vector_fiq + stubs_offset

	.globl	__vectors_end
__vectors_end:
...

interrupt request occur in svc/usr processor mode

  1. CPSR is saved in the SPSR for the IRQ exception mode.

  2. The value of PC is saved in the LR for the IRQ exception mode.

  3. The CPSR is updated
    — Setting CPSR.M to the IRQ exception mode.
    — Disabling IRQ interrupt.
    — Setting the instruction set state to the instruction set chosen for exception entry.
    — Setting the endianness to the value chosen for exception entry.
    — Clearing the IT[7:0] bits to 0.
    For more information, see CPSR M field and A, I, and F mask bit values on exception entry on page B1-36.
    (On exception entry, the CPSR.I bit is always set to 1, to disable IRQs)
    (On IRQ exception entry, CPSR.A:1, CPSR.A:Unchanged)


    The appropriate exception vector is loaded to the PC.

  4. Execution continues from the address held in the PC.


W(b) vector_irq + stubs_offset

.equ stubs_offset, __vectors_start + 0x200 - __stubs_start

/* arch/arm/kernel/entry-armv.S */

	.align	2
	@ handler addresses follow this label
1:
	.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)
	.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

/*
 * Data abort dispatcher
 * Enter in ABT mode, spsr = USR CPSR, lr = USR PC
 */
	vector_stub	dabt, ABT_MODE, 8

	.long	__dabt_usr			@  0  (USR_26 / USR_32)
	.long	__dabt_invalid			@  1  (FIQ_26 / FIQ_32)
	.long	__dabt_invalid			@  2  (IRQ_26 / IRQ_32)
	.long	__dabt_svc			@  3  (SVC_26 / SVC_32)
	.long	__dabt_invalid			@  4
	.long	__dabt_invalid			@  5
	.long	__dabt_invalid			@  6
	.long	__dabt_invalid			@  7
	.long	__dabt_invalid			@  8
	.long	__dabt_invalid			@  9
	.long	__dabt_invalid			@  a
	.long	__dabt_invalid			@  b
	.long	__dabt_invalid			@  c
	.long	__dabt_invalid			@  d
	.long	__dabt_invalid			@  e
	.long	__dabt_invalid			@  f

/*
 * Prefetch abort dispatcher
 * Enter in ABT mode, spsr = USR CPSR, lr = USR PC
 */
	vector_stub	pabt, ABT_MODE, 4

	.long	__pabt_usr			@  0 (USR_26 / USR_32)
	.long	__pabt_invalid			@  1 (FIQ_26 / FIQ_32)
	.long	__pabt_invalid			@  2 (IRQ_26 / IRQ_32)
	.long	__pabt_svc			@  3 (SVC_26 / SVC_32)
	.long	__pabt_invalid			@  4
	.long	__pabt_invalid			@  5
	.long	__pabt_invalid			@  6
	.long	__pabt_invalid			@  7
	.long	__pabt_invalid			@  8
	.long	__pabt_invalid			@  9
	.long	__pabt_invalid			@  a
	.long	__pabt_invalid			@  b
	.long	__pabt_invalid			@  c
	.long	__pabt_invalid			@  d
	.long	__pabt_invalid			@  e
	.long	__pabt_invalid			@  f

/*
 * Undef instr entry dispatcher
 * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 */
	vector_stub	und, UND_MODE

	.long	__und_usr			@  0 (USR_26 / USR_32)
	.long	__und_invalid			@  1 (FIQ_26 / FIQ_32)
	.long	__und_invalid			@  2 (IRQ_26 / IRQ_32)
	.long	__und_svc			@  3 (SVC_26 / SVC_32)
	.long	__und_invalid			@  4
	.long	__und_invalid			@  5
	.long	__und_invalid			@  6
	.long	__und_invalid			@  7
	.long	__und_invalid			@  8
	.long	__und_invalid			@  9
	.long	__und_invalid			@  a
	.long	__und_invalid			@  b
	.long	__und_invalid			@  c
	.long	__und_invalid			@  d
	.long	__und_invalid			@  e
	.long	__und_invalid			@  f

	.align	5

/*=============================================================================
 * Undefined FIQs
 *-----------------------------------------------------------------------------
 * Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC
 * MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg.
 * Basically to switch modes, we *HAVE* to clobber one register...  brain
 * damage alert!  I don't think that we can execute any code in here in any
 * other mode than FIQ...  Ok you can switch to another mode, but you can't
 * get out of that mode without clobbering one register.
 */
vector_fiq:
	subs	pc, lr, #4

/*=============================================================================
 * Address exception handler
 *-----------------------------------------------------------------------------
 * These aren't too critical.
 * (they're not supposed to happen, and won't happen in 32-bit data mode).
 */

vector_addrexcptn:
	b	vector_addrexcptn

/*
 * We group all the following data together to optimise
 * for CPUs with separate I & D caches.
 */
	.align	5

.LCvswi:
	.word	vector_swi

	.globl	__stubs_end
__stubs_end:

/* arch/arm/kernel/entry-armv.S */

/*
 * Vector stubs.
 *
 * This code is copied to 0xffff0200 so we can use branches in the
 * vectors, rather than ldr's.  Note that this code must not
 * exceed 0x300 bytes.
 *
 * Common stub entry macro:
 *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 *
 * SP points to a minimal amount of processor-private memory, the address
 * of which is copied into r0 for the mode specific abort handler.
 */
	.macro	vector_stub, name, mode, correction=0
	.align	5

vector_\name:
	.if \correction
	sub	lr, lr, #\correction
	.endif

	@
	@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
	@ (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 | PSR_ISETSTATE)
	msr	spsr_cxsf, r0

	@
	@ the branch table must immediately follow this code
	@
	and	lr, lr, #0x0f
 THUMB(	adr	r0, 1f			)
 THUMB(	ldr	lr, [r0, lr, lsl #2]	)
	mov	r0, sp
 ARM(	ldr	lr, [pc, lr, lsl #2]	)
	movs	pc, lr			@ branch to handler in SVC mode
ENDPROC(vector_\name)

vector_stub irq, IRQ_MODE, 4
展開,去除THUMB() 相關代碼,

THUMB() 和 ARM() 預處理宏 用於根據目標指令集 有條件地編譯 源代碼;
if target instruction set is ARM,則THUMB()宏為no ops;
if target instruction set is THUMB,則THUMB()宏將擴展為其參數;
/* arch/arm/include/asm/unified.h, arch/arm/include/asm/assembler.h for more conditionalized macros */
CONFIG_THUMB2_KERNEL這個宏是沒定義的;

/* arch/arm/include/asm/unified.h */
...
#ifdef CONFIG_THUMB2_KERNEL

#if __GNUC__ < 4
#error Thumb-2 kernel requires gcc >= 4
#endif

/* The CPSR bit describing the instruction set (Thumb) */
#define PSR_ISETSTATE	PSR_T_BIT

#define ARM(x...)
#define THUMB(x...)	x
#ifdef __ASSEMBLY__
#define W(instr)	instr.w
#define BSYM(sym)	sym + 1
#endif

#else	/* !CONFIG_THUMB2_KERNEL */

/* The CPSR bit describing the instruction set (ARM) */
#define PSR_ISETSTATE	0

#define ARM(x...)	x
#define THUMB(x...)
#ifdef __ASSEMBLY__
#define W(instr)	instr
#define BSYM(sym)	sym
#endif

#endif	/* CONFIG_THUMB2_KERNEL */
...


有:

	.align	2
	@ handler addresses follow this label
1:
	.endm

	.globl	__stubs_start
__stubs_start:
/*
 * Interrupt dispatcher
 */
	/*vector_stub	irq, IRQ_MODE, 4*/

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

vector_\name:
	.if \correction
	sub	lr, lr, #\correction
	.endif
/*  進入IRQ中斷,W(b)	vector_irq + stubs_offset;跳轉到sub	lr, lr, #\correction;
這里,IRQ mode,當前的context:
r0 到 r12,沒操作,還是中斷前的context;
lr,irq mode的lr, save被中斷那刻的PC值;
sp,irq mode, save irq mode棧的地址;
cpsr, irq模式
spsr, save中斷前的CPSR(svc/usr)

correction的值是4,
sub	lr, lr, #\correction; /*lr = lr - 4;*/

ARM 架構執行一條instruction的pipeline至少包含流程:取值,譯碼,執行,(訪存),(回寫);
PC寄存器保存的是取指PC值, 被中斷時執行的指令的地址是:PC - 8; 以后中斷處理完成,需要接着執行被中斷的下一條指令的地址是PC - 4; 故lr 的值 減去 4;  進入不同異常模式correction 值不同;
*/

	@
	@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
	@ (parent CPSR)
	@
	stmia	sp, {r0, lr}		@ save r0, lr
	mrs	lr, spsr
	str	lr, [sp, #8]		@ save spsr

/* stmia sp, {r0, lr} ;將 r0 lr的數據存儲到 sp(irq) 指向的地址上, sp 后沒有帶!,SP值不更新;
因為后面r0 會用到,故需要先保存到棧;這個棧是IRQ棧,只有12個字節大小;
lr 保存的是中斷跳轉前一刻的,pc(svc/usr) - 4值;
mrs	lr, spsr; spsr(irq)保存中斷前svc/usr模式的cpsr;  spsr的值賦給lr,  
str	lr, [sp, #8]; sp = sp + 8;  lr的值保存到sp 指向的地址;
IRQ的棧,依次入棧了:r0 lr(svc/usr pc) SPSR(svc/usr cpsr);
*/

	@
	@ Prepare for SVC32 mode.  IRQs remain disabled.
	@
	mrs	r0, cpsr
	eor	r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
	msr	spsr_cxsf, r0

/* mrs	r0, cpsr; cpsr 的值 賦值給 r0;
eor	r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE);  eor,異或操作指令; r0 = r0 eor 
mode 的值是IRQ_MODE,/* arm/arm/include/asm/ptrace.h*/
#define IRQ_MODE	0x00000012
#define SVC_MODE        0x00000013

eor r0, r0, #(0x12 ^ 0x13 | 0x0)
eor r0 , r0, #(0x01)
r0(bit[0:4]:10010) eor 0x01; -> r0(bit[0:4]:10011), CPSR M[0:4]:svc mode;

msr	spsr_cxsf, r0;  parts of r0 的值save to SPSR_irq; 其中SPSR_irq 中的M[0:4],改為SVC mode
(msr here is SPSR_<fields>, where <fields> "Is a sequence of one or more of" c, x, s, f, which represent bits 7:0, 15:8, 23:16 and 31:24 respectively)

*/

	@
	@ the branch table must immediately follow this code
	@
	and	lr, lr, #0x0f
	mov	r0, sp
 ARM(	ldr	lr, [pc, lr, lsl #2]	)
	movs	pc, lr			@ branch to handler in SVC mode
/*ENDPROC(vector_\name)*/

	.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

/* and	lr, lr, #0x0f; lr &= 0x0f; 進入中斷前cpsr的值&0x0f,以確認是從svc 模式還是從usr模式進入中斷的;
reference:B1.3.1 ARM processor modes
processor mode, mode encoding,
User            10000
FIQ             10001
IRQ             10010
Supervisor      10011
Monitor         10110
Abort           10111
Undefined       11011
System          11111

lr & 0x0f 值為0,從User模式進入中斷; 為3,從svc模式進入中斷;
mov	r0, sp;  sp(IRQ)的值賦值給r0;
ARM(	ldr	lr, [pc, lr, lsl #2]	);  lr = [pc + (lr << 2)]; lsl, #2, 左移2bit;相當於與剩以4;
如果lr 值等於0, lr 的值為pc的值;
如果lr 值等於3, lr 的值為pc + 12;

ARM 架構執行一條instruction的pipeline至少包含流程:取值,譯碼,執行,(訪存),(回寫);
ARM core pc寄存器的值,存的是取指的地址;當執行到ARM(	ldr	lr, [pc, lr, lsl #2]	),這條指令時,pc 存的當前指令 地址值+8;

故:
如果lr 值等於0, lr 的值為pc的值;即:__irq_usr
如果lr 值等於3, lr 的值為pc + 12;即:__irq_svc

到這里IRQ mode, 當前的context:
r0 保存 sp(IRQ) 的值;IRQ的棧,保存中斷跳轉前一刻的r0, pc, cpsr;
r1 到 r12,進入中斷后沒操作,還是中斷前的context;
sp指向 sp的棧;
cpsr, irq模式;
spsr, irq模式; CPSR_irq的cxsf,save to SPSR_irq,且其中的M[0:4],為SVC mode

movs	pc, lr;lr 的值賦值給pc;
跳轉到
__irq_usr or __irq_svc

movs, 
s, If present, specifies that the instruction updates the flags. Otherwise, the instruction does not update the flags.
執行MOVS pc, lr時,SPSR_irq會copy(move) 到 CPSR_svc;也就是會切換到svc mode;

到這里切換到SVC mode, 當前的context:
r0 保存 sp(IRQ) 的值;sp_irq棧,保存中斷跳轉前一刻的r0, pc, cpsr;
r1 到 r12,進入中斷后沒操作,還是中斷前的context;
sp指向 svc的棧;
cpsr, svc模式;
spsr, svc模式;

PS:
user mode context:       svn mode context:        irq mode context: 
R0_usr                   R0_usr                   R0_usr
R1_usr                   R1_usr                   R1_usr
R2_usr                   R2_usr                   R2_usr
R3_usr                   R3_usr                   R3_usr
R4_usr                   R4_usr                   R4_usr
R5_usr                   R5_usr                   R5_usr
R6_usr                   R6_usr                   R6_usr
R7_usr                   R7_usr                   R7_usr
R8_usr                   R8_usr                   R8_usr
R9_usr                   R9_usr                   R9_usr
R10_usr                  R10_usr                  R10_usr
R11_usr                  R11_usr                  R11_usr
R12_usr                  R12_usr                  R12_usr
SP_usr                   SP_svc                   SP_irq
LR_usr                   LR_svc                   LR_irq
PC                       PC                       PC
CPSR                     CPSR                     CPSR
                         SPSR_svc                 SPSR_irq
*/


/*
 * Data abort dispatcher
 * Enter in ABT mode, spsr = USR CPSR, lr = USR PC
 */
	vector_stub	dabt, ABT_MODE, 8

	.long	__dabt_usr			@  0  (USR_26 / USR_32)
	.long	__dabt_invalid			@  1  (FIQ_26 / FIQ_32)
	.long	__dabt_invalid			@  2  (IRQ_26 / IRQ_32)
	.long	__dabt_svc			@  3  (SVC_26 / SVC_32)
	.long	__dabt_invalid			@  4
	.long	__dabt_invalid			@  5
	.long	__dabt_invalid			@  6
	.long	__dabt_invalid			@  7
	.long	__dabt_invalid			@  8
	.long	__dabt_invalid			@  9
	.long	__dabt_invalid			@  a
	.long	__dabt_invalid			@  b
	.long	__dabt_invalid			@  c
	.long	__dabt_invalid			@  d
	.long	__dabt_invalid			@  e
	.long	__dabt_invalid			@  f
...


  • __irq_usr
/* arch/arm/kernel/entry-armv.S */

	.align	5
__irq_usr:
	usr_entry
	kuser_cmpxchg_check
	irq_handler
	get_thread_info tsk
	mov	why, #0
	b	ret_to_user_from_irq
 UNWIND(.fnend		)
ENDPROC(__irq_usr)

usr_entry宏,去掉THUMB(),code:

/* arch/arm/kernel/entry-armv.S */

/*
 * User mode handlers
 *
 * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE
 */

#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7)
#error "sizeof(struct pt_regs) must be a multiple of 8"
#endif

	.macro	usr_entry
 UNWIND(.fnstart	)
 UNWIND(.cantunwind	)	@ don't unwind the user space
	sub	sp, sp, #S_FRAME_SIZE
 ARM(	stmib	sp, {r1 - r12}	)

/*
#define S_FRAME_SIZE 72 /* sizeof(struct pt_regs) /* include/generated/asm-offsets.h */
這里是在svc mode;
sp = sp - 72; ARM 的棧是高地址往低地址長; 72 / 4 = 18,減去72,先騰出72個byte的棧空間;
為啥是18 * sizeof(unsigned long);這是上下文的大小;

arch/arm/include/asm/ptrace.h:
/*
 * This struct defines the way the registers are stored on the
 * stack during a system call.  Note that sizeof(struct pt_regs)
 * has to be a multiple of 8.
 */
#ifndef __KERNEL__
struct pt_regs {
	long uregs[18];
};
#else /* __KERNEL__ */
struct pt_regs {
	unsigned long uregs[18];
};
#endif /* __KERNEL__ */

#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]


ARM(	stmib	sp, {r1 - r12}	)
STMIB  sp!,{R1-r12}  ;將 r1~r12 的數據保存到內存中,sp指針在保存第一個值之前增加,增長方向為向上增長。 
這里sp后面沒有!,sp 指向的地址沒變;

low address
|_    sp save地址值
|_r1
|_r2
|_r3
|_r4
|_r5
|_r6
|_r7
|_r8
|_r9
|_r10
|_r11
|_r12
|_
|_
|_
|_
|_
high address

*/

	ldmia	r0, {r3 - r5}
	add	r0, sp, #S_PC		@ here for interlock avoidance
	mov	r6, #-1			@  ""  ""     ""        ""

	str	r3, [sp]		@ save the "real" r0 copied
					@ from the exception stack

/* r0 保存IRQ mode棧的地址;
ldmia	r0, {r3 - r5}
ldmia   r0!,{r3-r5}; 加載 r0 指向的地址上的多字數據,保存到 r3~r9 中,r0 值更新,沒有!,r0不更新;
這樣r3到r5,依次保存: 中斷跳轉前一刻的r0, pc, cpsr;

dd	r0, sp, #S_PC; r0 = sp + #S_PC; 
include/generated/asm-offsets.h:
#define S_PC 60 /* offsetof(struct pt_regs, ARM_pc)     @ */
60 / 4 = 15

mov	r6, #-1

str	r3, [sp]

到這里svc stack:
low address
|_r0    sp save的地址值
|_r1
|_r2
|_r3
|_r4
|_r5
|_r6
|_r7
|_r8
|_r9
|_r10
|_r11
|_r12
|_
|_
|_    r0 save的地址值
|_
|_
high address

*/

	@
	@ We are now ready to fill in the remaining blanks on the stack:
	@
	@  r4 - lr_<exception>, already fixed up for correct return/restart
	@  r5 - spsr_<exception>
	@  r6 - orig_r0 (see pt_regs definition in ptrace.h)
	@
	@ Also, separately save sp_usr and lr_usr
	@
	stmia	r0, {r4 - r6}
 ARM(	stmdb	r0, {sp, lr}^			)

/*stmia	r0, {r4 - r6}
r4,r5為  中斷跳轉前一刻的pc(lr_irq), cpsr(spsr_irq)值;  r6的值為-1;
將 r4~r6 的數據存儲到 r0 指向的地址上,增長方向為向上增長,r0 不更新;

low address
|_r0    sp save的地址值
|_r1
|_r2
|_r3
|_r4
|_r5
|_r6
|_r7
|_r8
|_r9
|_r10
|_r11
|_r12
|_
|_
|_pc    r0 save的地址值
|_cpsr
|_-1
high address

 ARM(	stmdb	r0, {sp, lr}^			)
將 sp,lr 的數據保存到內存中,增長方向為向下增長, ^表示訪問usr mode的寄存器;

這時有:
low address
|_r0    svc sp save的地址值
|_r1
|_r2
|_r3
|_r4
|_r5
|_r6
|_r7
|_r8
|_r9
|_r10
|_r11
|_r12
|_lr_usr
|_sp_usr
|_pc    r0 save的地址值
|_cpsr
|_-1
high address

到這里中斷發生前一刻的context都保存到svc stack中了;
中斷程序處理完成后,restore這個上下文,就接着執行中斷被打斷后的下一條指令;

mov r6 , #-1;  為啥要orig_r0 ?

*/

	@
	@ Enable the alignment trap while in kernel mode
	@
	alignment_trap r0

	@
	@ Clear FP to mark the first stack frame
	@
	zero_fp

#ifdef CONFIG_IRQSOFF_TRACER
	bl	trace_hardirqs_off
#endif
	.endm


  • irq_handler:
/* arch/arm/kernel/entry-armv.S */

/*
 * Interrupt handling.
 */
	.macro	irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
	ldr	r1, =handle_arch_irq
	mov	r0, sp
	adr	lr, BSYM(9997f)
	ldr	pc, [r1]
#else
	arch_irq_handler_default
#endif
9997:
	.endm

/*
./include/generated/autoconf.h:813:#define CONFIG_MULTI_IRQ_HANDLER 1

ldr	r1, =handle_arch_irq
mov	r0, sp; /* sp_svc 地址值save to r0, 傳參 */
adr	lr, BSYM(9997f); /* adr 偽指令,將BSYM(9997f) 地址值放到lr,  f應該是front的含義 */
./arch/arm/include/asm/unified.h:52:#define BSYM(sym)	sym
irq_handler,是個宏,  這里將lable 9997f后的指令的地址值,給到lr;  
為從handle_arch_irq回來做准備;

ldr	pc, [r1]; /* 跳轉到handle_arch_irq()  */

*/

  • IRQ exit

Note:

/* ./arch/arm/kernel/entry-header.S */

/*
 * These are the registers used in the syscall handler, and allow us to
 * have in theory up to 7 arguments to a function - r0 to r6.
 *
 * r7 is reserved for the system call number for thumb mode.
 *
 * Note that tbl == why is intentional.
 *
 * We must set at least "tsk" and "why" when calling ret_with_reschedule.
 */
scno    .req    r7              @ syscall number
tbl     .req    r8              @ syscall table pointer
why     .req    r8              @ Linux syscall (!= 0)
tsk     .req    r9              @ current thread_info


  • get_thread_info tsk
/* ./arch/arm/kernel/entry-header.S */

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

/*
SP_svc 保存的值,右移動13 bit,再左移13bit; 13bit,8K;
獲取當前進程的thread_info的地址值,賦值給rd 寄存器;
*/


  • ret_to_user_from_irq
/* arch/arm/kernel/entry-common.S */

/*
 * "slow" syscall return path.  "why" tells us if this was a real syscall.
 */
ENTRY(ret_to_user)
ret_slow_syscall:
	disable_irq				@ disable interrupts
ENTRY(ret_to_user_from_irq)
	ldr	r1, [tsk, #TI_FLAGS]
	tst	r1, #_TIF_WORK_MASK
	bne	work_pending
no_work_pending:
#if defined(CONFIG_IRQSOFF_TRACER)
	asm_trace_hardirqs_on
#endif
	/* perform architecture specific actions before user return */
	arch_ret_to_user r1, lr

	restore_user_regs fast = 0, offset = 0
ENDPROC(ret_to_user_from_irq)
ENDPROC(ret_to_user)

/*
在call ret_to_user_from_irq,前有:
__irq_usr:
	usr_entry
	kuser_cmpxchg_check
	irq_handler
	get_thread_info tsk
	mov	why, #0
	b	ret_to_user_from_irq

thread_info 地址放到r9(tsk);
r8(why) 賦值為0;
b ret_to_user_from_irq;
*/

#if 0
/* arch/arm/include/asm/thread_info.h */

/*
 * low level task data that entry.S needs immediate access to.
 * __switch_to() assumes cpu_context follows immediately after cpu_domain.
 */
struct thread_info {
	unsigned long		flags;		/* low level flags */
	int			preempt_count;	/* 0 => preemptable, <0 => bug */
	mm_segment_t		addr_limit;	/* address limit */
	struct task_struct	*task;		/* main task structure */
	struct exec_domain	*exec_domain;	/* execution domain */
	__u32			cpu;		/* cpu */
	__u32			cpu_domain;	/* cpu domain */
	struct cpu_context_save	cpu_context;	/* cpu context */
	__u32			syscall;	/* syscall number */
	__u8			used_cp[16];	/* thread used copro */
	unsigned long		tp_value;
	struct crunch_state	crunchstate;
	union fp_state		fpstate __attribute__((aligned(8)));
	union vfp_state		vfpstate;
#ifdef CONFIG_ARM_THUMBEE
	unsigned long		thumbee_state;	/* ThumbEE Handler Base register */
#endif
	struct restart_block	restart_block;
};
#endif

/*
arch/arm/kernel/asm-offsets.c:51:  DEFINE(TI_FLAGS,		offsetof(struct thread_info, flags));
arch/arm//include/asm/thread_info.h:174:#define _TIF_WORK_MASK		0x000000ff
*/

#if 0
/* arch/arm/include/asm/thread_info.h */

/*
 * We use bit 30 of the preempt_count to indicate that kernel
 * preemption is occurring.  See <asm/hardirq.h>.
 */
#define PREEMPT_ACTIVE	0x40000000

/*
 * thread information flags:
 *  TIF_SYSCALL_TRACE	- syscall trace active
 *  TIF_SYSCAL_AUDIT	- syscall auditing active
 *  TIF_SIGPENDING	- signal pending
 *  TIF_NEED_RESCHED	- rescheduling necessary
 *  TIF_NOTIFY_RESUME	- callback before returning to user
 *  TIF_USEDFPU		- FPU was used by this task this quantum (SMP)
 *  TIF_POLLING_NRFLAG	- true if poll_idle() is polling TIF_NEED_RESCHED
 */
#define TIF_SIGPENDING		0
#define TIF_NEED_RESCHED	1
#define TIF_NOTIFY_RESUME	2	/* callback before returning to user */
#define TIF_SYSCALL_TRACE	8
#define TIF_SYSCALL_AUDIT	9
#define TIF_POLLING_NRFLAG	16
#define TIF_USING_IWMMXT	17
#define TIF_MEMDIE		18	/* is terminating due to OOM killer */
#define TIF_RESTORE_SIGMASK	20
#define TIF_SECCOMP		21
#define TIF_SWITCH_MM		22	/* deferred switch_mm */

#define _TIF_SIGPENDING		(1 << TIF_SIGPENDING)
#define _TIF_NEED_RESCHED	(1 << TIF_NEED_RESCHED)
#define _TIF_NOTIFY_RESUME	(1 << TIF_NOTIFY_RESUME)
#define _TIF_SYSCALL_TRACE	(1 << TIF_SYSCALL_TRACE)
#define _TIF_SYSCALL_AUDIT	(1 << TIF_SYSCALL_AUDIT)
#define _TIF_POLLING_NRFLAG	(1 << TIF_POLLING_NRFLAG)
#define _TIF_USING_IWMMXT	(1 << TIF_USING_IWMMXT)
#define _TIF_RESTORE_SIGMASK	(1 << TIF_RESTORE_SIGMASK)
#define _TIF_SECCOMP		(1 << TIF_SECCOMP)


/* Checks for any syscall work in entry-common.S*/
#define _TIF_SYSCALL_WORK (_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT)


/*
 * Change these and you break ASM code in entry-common.S
 */
#define _TIF_WORK_MASK		0x000000ff

#endif

ldr	r1, [tsk, #TI_FLAGS]
tst	r1, #_TIF_WORK_MASK
bne	work_pending

判斷thread_info->flags & 0xff,是否不為0; 
ne  標志Z:0  不相等
eq  標志Z:1  相等 

不為0,b work_pending; 

這里判斷當前task是否有signal pending,resched等標志,有的話進入work_pending,進行相關處理;
這里可以看出,內核對signal的處理是在,異常發生后,從異常返回用戶態 前(內核)處理的;  


  • arch_ret_to_user r1, lr
/* arch/arm/kernel/entry-common.S */

#ifdef CONFIG_NEED_RET_TO_USER
#include <mach/entry-macro.S>
#else
	.macro  arch_ret_to_user, tmp1, tmp2
	.endm
#endif

/* CONFIG_NEED_RET_TO_USER 這個宏是沒有定義的 */


  • restore_user_regs fast = 0, offset = 0
/* arch/arm/kernel/entry-header.S */

	.macro	restore_user_regs, fast = 0, offset = 0
	ldr	r1, [sp, #\offset + S_PSR]	@ get calling cpsr
	ldr	lr, [sp, #\offset + S_PC]!	@ get pc
	msr	spsr_cxsf, r1			@ save in spsr_svc

	.if	\fast
	ldmdb	sp, {r1 - lr}^			@ get calling r1 - lr
	.else
	ldmdb	sp, {r0 - lr}^			@ get calling r0 - lr
	.endif
	mov	r0, r0				@ ARMv5T and earlier require a nop
						@ after ldm {}^
	add	sp, sp, #S_FRAME_SIZE - S_PC
	movs	pc, lr				@ return & move spsr_svc into cpsr
	.endm

/*
./kernel/asm-offsets.c:92:  DEFINE(S_PSR,			offsetof(struct pt_regs, ARM_cpsr));
./kernel/asm-offsets.c:91:  DEFINE(S_PC,			offsetof(struct pt_regs, ARM_pc));

ldr	r1, [sp, #\offset + S_PSR]	@ get calling cpsr
ldr	lr, [sp, #\offset + S_PC]!	@ get pc
msr	spsr_cxsf, r1			@ save in spsr_svc

如注釋;
這里有個地方要注意,媽呀,看了好久才發現:
ldr	lr, [sp, #\offset + S_PC]!	@ get pc;
這里有個!;!用來控制基址變址尋址的最終新地址是否進行回寫操作;
這里sp save的值更新為sp : sp + S_PS;


svc stack:
low address
|_r0    svc sp save的地址值
|_r1
|_r2
|_r3
|_r4
|_r5
|_r6
|_r7
|_r8
|_r9
|_r10
|_r11
|_r12
|_lr_usr
|_sp_usr
|_pc
|_cpsr
|_-1
high address

ldmdb	sp, {r0 - lr}^			@ get calling r0 - lr
將SP_svc指向的內存地址,數據保存到{r0 - lr}中,增長方向為向上增長, ^表示訪問usr mode的寄存器; sp 后面沒有!;

add	sp, sp, #S_FRAME_SIZE - S_PC;  SP_svc save的地址值 回到中斷發生前的位置

movs	pc, lr				@ return & move spsr_svc into cpsr

*/

  • __irq_svc
/* arch/arm/kernel/entry-armv.S */

	.align	5
__irq_svc:
	svc_entry
	irq_handler

#ifdef CONFIG_PREEMPT
	get_thread_info tsk
	ldr	r8, [tsk, #TI_PREEMPT]		@ get preempt count
	ldr	r0, [tsk, #TI_FLAGS]		@ get flags
	teq	r8, #0				@ if preempt count != 0
	movne	r0, #0				@ force flags to 0
	tst	r0, #_TIF_NEED_RESCHED
	blne	svc_preempt
#endif

#ifdef CONFIG_TRACE_IRQFLAGS
	@ The parent context IRQs must have been enabled to get here in
	@ the first place, so there's no point checking the PSR I bit.
	bl	trace_hardirqs_on
#endif
	svc_exit r5				@ return from exception
 UNWIND(.fnend		)
ENDPROC(__irq_svc)




  • handle_arch_irq()
void __init start_kernel(void)
    |
    /* arch/arm/kernel/setup.c */
    void __init setup_arch(char **cmdline_p)
        |
		...

		/* arm/kernel/entry-armv.S irq_handler, call handle_arch_irq() */
        #ifdef CONFIG_MULTI_IRQ_HANDLER
	        handle_arch_irq = mdesc->handle_irq;  /* call gic_handle_irq() */
        #endif
		...

  • void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
/* arch/arm/mach-s5p6818/gic.c */

asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
	u32 irqstat, irqnr;
	struct gic_chip_data *gic = &gic_data[0];
	void __iomem *cpu_base = gic_data_cpu_base(gic);

	/*printk("~~~ %s() ARM_cpsr:0x%08x\n", __func__, regs->ARM_cpsr);*/

	do {
		irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
		//printk("~~~ %s() irq ack reg:0x%08x\n", __func__, irqstat);
		irqnr = irqstat & ~0x1c00;

		if (likely(irqnr > 15 && irqnr < 1021)) {
			if (irqnr >= IRQ_PHY_GPIOA)
				printk("~~~ %s() hwirq:%d\n", __func__, irqnr);
			irqnr = irq_find_mapping(gic->domain, irqnr);
			if (irqnr >= IRQ_PHY_GPIOA)
				printk("~~~ %s() irqnr:%d\n", __func__, irqnr);
			handle_IRQ(irqnr, regs);
			continue;
		}
		if (irqnr < 16) {
			writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
			handle_IPI(irqnr, regs);
#endif
			continue;
		}
		break;
	} while (1);
}

irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);

define GIC_CPU_INTACK 0x0c

ARM ® Generic Interrupt Controller Architecture version 2.0,
4.4.4 Interrupt Acknowledge Register, GICC_IAR;
Purpose:The processor reads this register to obtain the interrupt ID of the signaled interrupt. This read acts as an acknowledge for the interrupt.


Interrupt Acknowledge Register, GICC_IAR,[9:0], The interrupt ID.

		if (irqnr < 16) {
			writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
			handle_IPI(irqnr, regs);
			continue;
		}

define GIC_CPU_EOI 0x10

4.4.5 End of Interrupt Register, GICC_EOIR
Purpose A processor writes to this register to inform the CPU interface either:
• that it has completed the processing of the specified interrupt
• in a GICv2 implementation, when the appropriate GICC_CTLR.EOImode bit is set to 1, to indicate that the interface should perform priority drop for the specified interrupt.


[9:0] EOIINTID The Interrupt ID value from the corresponding GICC_IAR access.

讀取GIC cpu interface, Interrupt Acknowledge Register,得到Interrupt ID;
如果id值小於16,是SGI中斷; 用於core 之間通信;
把這個ID值寫到GIC cpu interface,End of Interrupt Register,that it has completed the processing of the specified interrupt;
之后call handle_IPI(irqnr, regs); and continue,直到所有中斷處理完成;



		if (likely(irqnr > 15 && irqnr < 1021)) {
			if (irqnr >= IRQ_PHY_GPIOA)
				printk("~~~ %s() hwirq:%d\n", __func__, irqnr);
			irqnr = irq_find_mapping(gic->domain, irqnr);
			if (irqnr >= IRQ_PHY_GPIOA)
				printk("~~~ %s() irqnr:%d\n", __func__, irqnr);
			handle_IRQ(irqnr, regs);
			continue;
		}

如果id值大於15,是Private Peripheral Interrupt(PPI) 和 Share Peripheral Interrupt(SPI)中斷;
irqnr = irq_find_mapping(gic->domain, irqnr);
輸入gic->domain, GIC 硬件 interrupt number, 以確定是哪個struct irq_desc irq_desc;
得到對應中斷,全局的struct irq_desc irq_desc[NR_IRQS]的下標; irqnr;

/* kernel/irq/irqdomain.c */

/**
 * irq_find_mapping() - Find a linux irq from an hw irq number.
 * @domain: domain owning this hardware interrupt
 * @hwirq: hardware irq number in that domain space
 *
 * This is a slow path, for use by generic code. It's expected that an
 * irq controller implementation directly calls the appropriate low level
 * mapping function.
 */
unsigned int irq_find_mapping(struct irq_domain *domain,
			      irq_hw_number_t hwirq)
{
	unsigned int i;
	unsigned int hint = hwirq % nr_irqs;
	unsigned int irq;

	/* Look for default domain if nececssary */
	if (domain == NULL)
		domain = irq_default_domain;
	if (domain == NULL)
		return 0;

	/* legacy -> bail early */
	if (domain->revmap_type == IRQ_DOMAIN_MAP_LEGACY) {
		irq = irq_domain_legacy_revmap(domain, hwirq);
		if (irq >= IRQ_PHY_GPIOA)
			printk("~~~ %s() hwirq:%lu, irq:%u\n", __func__, \
					hwirq, irq);
		return irq;
	}

	/* Slow path does a linear search of the map */
	if (hint == 0)
		hint = 1;
	i = hint;
	do {
		struct irq_data *data = irq_get_irq_data(i);
		if (data && (data->domain == domain) && (data->hwirq == hwirq))
			return i;
		i++;
		if (i >= nr_irqs)
			i = 1;
	} while(i != hint);
	return 0;
}
EXPORT_SYMBOL_GPL(irq_find_mapping);
/* kernel/irq/irqdomain.c */

static unsigned int irq_domain_legacy_revmap(struct irq_domain *domain,
					     irq_hw_number_t hwirq)
{
	irq_hw_number_t first_hwirq = domain->revmap_data.legacy.first_hwirq;
	int size = domain->revmap_data.legacy.size;

	if (hwirq >= IRQ_PHY_GPIOA)
		printk("~~~ %s() hwirq:%lu\n", __func__, hwirq);

	if (WARN_ON(hwirq < first_hwirq || hwirq >= first_hwirq + size))
		return 0;
	return hwirq - first_hwirq + domain->revmap_data.legacy.first_irq;
}

根據hw interrupt 和 irqnr 的映射關系,獲取irqnr;
之后call handle_IRQ(irqnr, regs);
之后,and continue,直到所有中斷處理完成;


  • void handle_IRQ(unsigned int irq, struct pt_regs *regs)
/*
 * handle_IRQ handles all hardware IRQ's.  Decoded IRQs should
 * not come via this function.  Instead, they should provide their
 * own 'handler'.  Used by platform code implementing C-based 1st
 * level decoding.
 */
void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);

	irq_enter();

	/*
	 * Some hardware gives randomly wrong interrupts.  Rather
	 * than crashing, do something sensible.
	 */
	if (unlikely(irq >= nr_irqs)) {
		if (printk_ratelimit())
			printk(KERN_WARNING "Bad IRQ%u\n", irq);
		ack_bad_irq(irq);
	} else {
		generic_handle_irq(irq);
	}

	/* AT91 specific workaround */
	irq_finish(irq);

	irq_exit();
	set_irq_regs(old_regs);
}

  • int generic_handle_irq(unsigned int irq)
/* kernel/irq/irqdesc.c */

/**
 * generic_handle_irq - Invoke the handler for a particular irq
 * @irq:	The irq number to handle
 *
 */
int generic_handle_irq(unsigned int irq)
{
	struct irq_desc *desc = irq_to_desc(irq);

	if (irq >= IRQ_PHY_GPIOA)
		printk("~~~ %s() irq:%u, irq_desc->irq_data.irq:%u\n", \
			__func__, irq, irq_desc->irq_data.irq);

	if (!desc)
		return -EINVAL;
	generic_handle_irq_desc(irq, desc);
	return 0;
}
EXPORT_SYMBOL_GPL(generic_handle_irq);

  • inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
/* include/linux/irqdesc.h */

/*
 * Architectures call this to let the generic IRQ layer
 * handle an interrupt. If the descriptor is attached to an
 * irqchip-style controller then we call the ->handle_irq() handler,
 * and it calls __do_IRQ() if it's attached to an irqtype-style controller.
 */
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
	if (irq >= IRQ_PHY_GPIOA)
		printk("~~~ %s() irq:%u, irq_desc->irq_data.irq:%u\n", \
			__func__, irq, irq_desc->irq_data.irq);

	desc->handle_irq(irq, desc);
}

struct irq_desc desc = irq_to_desc(irq);
call desc->handle_irq(irq, desc); /
call high level interrupt handle */


gic_init(0, IRQ_GIC_PPI_START, dist_base, cpu_base);
|
void __init gic_init_bases(unsigned int gic_nr, int irq_start,
void __iomem *dist_base, void __iomem *cpu_base,
u32 percpu_offset, struct device_node *node)
|
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
hwirq_base, &gic_irq_domain_ops, gic);
|
ops->map(domain, irq, hwirq);
/ * call struct irq_domain_ops gic_irq_domain_ops, .map = gic_irq_domain_map
set desc->handle_irq = handle; / * high level irq-events handler */
如果是SPI中斷,set desc->handle_irq:handle_fasteoi_irq();
*/

/* arch/arm/mach-s5p6818/gic.c */

...
const struct irq_domain_ops gic_irq_domain_ops = {
	.map = gic_irq_domain_map,
	.xlate = gic_irq_domain_xlate,
};
...
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
				irq_hw_number_t hw)
{
	/*printk("~~~ %s() irq:%d, hw:%d, call irq_set_chip_and_handler()\n", \
			__func__, irq, hw); */
	
	if (hw < 32) {
		irq_set_percpu_devid(irq);
		irq_set_chip_and_handler(irq, &gic_chip,
					 handle_percpu_devid_irq);
		set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);
	} else {
		irq_set_chip_and_handler(irq, &gic_chip,
					 handle_fasteoi_irq);
		set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
	}
	irq_set_chip_data(irq, d->host_data);
	return 0;
}


如果是SPI中斷

static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
	desc->handle_irq(irq, desc);  /* call handle_fasteoi_irq(); */
}
  • void handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
/* kernel/irq/chip.c */

/**
 *	handle_fasteoi_irq - irq handler for transparent controllers
 *	@irq:	the interrupt number
 *	@desc:	the interrupt description structure for this irq
 *
 *	Only a single callback will be issued to the chip: an ->eoi()
 *	call when the interrupt has been serviced. This enables support
 *	for modern forms of interrupt handlers, which handle the flow
 *	details in hardware, transparently.
 */
void
handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
{
	if (irq >= IRQ_PHY_GPIOA)
		printk("~~~ %s() irq:%d\n", __func__, irq);

	raw_spin_lock(&desc->lock);

	if (unlikely(irqd_irq_inprogress(&desc->irq_data)))
		if (!irq_check_poll(desc))
			goto out;

	desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
	kstat_incr_irqs_this_cpu(irq, desc);

	/*
	 * If its disabled or no action available
	 * then mask it and get out of here:
	 */
	if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
		desc->istate |= IRQS_PENDING;
		mask_irq(desc);
		goto out;
	}

	if (desc->istate & IRQS_ONESHOT)
		mask_irq(desc);

	preflow_handler(desc);
	handle_irq_event(desc);

	if (desc->istate & IRQS_ONESHOT)
		cond_unmask_irq(desc);

out_eoi:
	if (irq >= IRQ_PHY_GPIOA)
		printk("~~~ %s() irq:%d, call chip->irq_eoi()\n", \
			__func__, irq);
	desc->irq_data.chip->irq_eoi(&desc->irq_data);
out_unlock:
	raw_spin_unlock(&desc->lock);
	return;
out:
	if (!(desc->irq_data.chip->flags & IRQCHIP_EOI_IF_HANDLED))
		goto out_eoi;
	goto out_unlock;
}

主要有:

	if (desc->istate & IRQS_ONESHOT)   
	mask_irq(desc);   /* disable this int */

	handle_irq_event(desc);   /* handle */

	if (desc->istate & IRQS_ONESHOT)   
		cond_unmask_irq(desc);   /* enable this int */ */

	desc->irq_data.chip->irq_eoi(&desc->irq_data); /* call irq chip end of interrupt */

可以看到 IRQS_ONESHOT flag的用做處之一;

  • static void gic_eoi_irq(struct irq_data *d)
/* arch/arm/mach-s5p6818/gic.c */
...
static struct irq_chip gic_chip = {
	.name			= "GIC",
	.irq_mask		= gic_mask_irq,
	.irq_unmask		= gic_unmask_irq,
	.irq_eoi		= gic_eoi_irq,
	.irq_set_type		= gic_set_type,
	.irq_retrigger		= gic_retrigger,
#ifdef CONFIG_SMP
	.irq_set_affinity	= gic_set_affinity,
#endif
	.irq_set_wake		= gic_set_wake,
};
...
static void gic_eoi_irq(struct irq_data *d)
{
	if (d->hwirq >= IRQ_PHY_GPIOA)
		printk("~~~ %s() hwirq:%d\n", __func__, d->hwirq);

	if (gic_arch_extn.irq_eoi) {
		raw_spin_lock(&irq_controller_lock);
		gic_arch_extn.irq_eoi(d);
		raw_spin_unlock(&irq_controller_lock);
	}

	writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI);
}

  • irqreturn_t handle_irq_event(struct irq_desc *desc)
/*  */

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
	struct irqaction *action = desc->action;
	irqreturn_t ret;

	if (desc->irq_data.irq >= IRQ_PHY_GPIOA)
		printk("~~~ %s() irq:%d, name:%s, irq_chip:%s\n", \
			__func__, desc->irq_data.irq, desc->name, \
			(desc->irq_data.chip)->name);

	desc->istate &= ~IRQS_PENDING;
	irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
	raw_spin_unlock(&desc->lock);

	ret = handle_irq_event_percpu(desc, action);

	raw_spin_lock(&desc->lock);
	irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
	return ret;
}

  • irqreturn_t handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
	irqreturn_t retval = IRQ_NONE;
	unsigned int flags = 0, irq = desc->irq_data.irq;

	if (irq >= IRQ_PHY_GPIOA)
		printk("~~~ %s() irq:%d, name:%s\n", __func__, irq, desc->name);

	do {
		irqreturn_t res;

		trace_irq_handler_entry(irq, action);
		res = action->handler(irq, action->dev_id);
		if (irq >= IRQ_PHY_GPIOA)
			printk("~~~ %s() after call action->handler(), name:%s, res:%d\n", \
				__func__, action->name, res);
		trace_irq_handler_exit(irq, action, res);

		if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
			      irq, action->handler))
			local_irq_disable();

		switch (res) {
		case IRQ_WAKE_THREAD:
			/*
			 * Catch drivers which return WAKE_THREAD but
			 * did not set up a thread function
			 */
			if (unlikely(!action->thread_fn)) {
				warn_no_thread(irq, action);
				break;
			}

			if (irq >= IRQ_PHY_GPIOA)
				printk("~~~ %s() irq_wake_thread\n", \
					__func__);
			irq_wake_thread(desc, action);

			/* Fall through to add to randomness */
		case IRQ_HANDLED:
			flags |= action->flags;
			break;

		default:
			break;
		}

		retval |= res;
		action = action->next;
	} while (action);

	add_interrupt_randomness(irq, flags);

	if (!noirqdebug)
		note_interrupt(irq, desc, retval);
	return retval;
}

終於到達:
res = action->handler(irq, action->dev_id);


review:
linux arm irq (1)

  • ARM SOC(GIC)發生中斷主要處理流程(精簡描述):
  1. 進入irq模式,切換到svc模式,保存現場;
  2. 讀取GIC irq chip 獲取是哪個硬件中斷number,這個Hw int number是SOC硬件設計確定的;
    對於GIC Share Peripheral Interrupt(SPI),一般是一個soc controller對應一個Hw int number;
  3. 找到這個Hw int 對應的interrupt descriptor,(struct irq_desc);call 其handle_irq(high level irq-events handler);
    handle_irq中call action->handler();
    PS:這個Hw int如果是一個irq chip,(這個中斷chained若干個中斷,可以是int controller或一個GPIO控制器連接若干個IO),這個中斷的handle_irq(),(highlevel irq-events handler)中就需要去讀取這個irq chip 的寄存器,確定Hw init number;再走lable 3流程;
  4. 執行GIC 的end of irq operations;(action->handler()中有clear irq(operate controller))
  5. b ret_to_user_from_irq or svc_exit r5;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM