linux的中断子系统简介(汇编和hard irq部分)_ARM平台(S5PV210)

时间:2021-11-29 12:28:07

2011年9月份时候做的笔记, 当时阅读中断子系统的代码后做的一个PPT, 内核版本不记得了, 硬件平台是samsung 的S5PV210.

这部分主要是针对汇编和hard irq的部分, 在hard irq处理后的softirq的处理, 以及下半部的处理(tasklet/workqueue)都没有涉及.

Agenda

•Interrupts in ARM
•Important structs
•External interrupt resources in S5PV210
•Code flow
•Kernel API


•Interrupts in ARM
ARM CPU CORE 中只有两根中断引脚, 分别是IRQ和FIQ.
•IRQ
–Why chip can handle so many IRQS?      ===>  VIC
•FIQ

•Important structs

struct irq_desc <strong>irq_desc</strong>[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.status = IRQ_DISABLED,
.chip = &no_irq_chip,
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
}; //NR_IRQS = 393 in R70

irq_desc is a global struct describing all the interrupt lines in the system.

struct irq_desc {
<span style="color:#FF0000;"> unsigned int irq;</span>
struct timer_rand_state *timer_rand_state;
unsigned int *kstat_irqs;
#ifdef CONFIG_INTR_REMAP
struct irq_2_iommu *irq_2_iommu;
#endif
<span style="color:#FF0000;"> irq_flow_handler_t handle_irq; //high level irq-events handle
struct irq_chip *chip;</span>
struct msi_desc *msi_desc;
void *handler_data;
void *chip_data;
<span style="color:#FF0000;"> struct irqaction *action; /* IRQ action list */</span>
unsigned int status; /* IRQ status */

unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled;
raw_spinlock_t lock;
#ifdef CONFIG_SMP
cpumask_var_t affinity;
const struct cpumask *affinity_hint;
unsigned int node;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name;
} ____cacheline_internodealigned_in_smp;

struct <strong>irq_chip</strong> {
const char *name;
unsigned int (*startup)(unsigned int irq);
void (*shutdown)(unsigned int irq);
void (*enable)(unsigned int irq);
void (*disable)(unsigned int irq);

void (*ack)(unsigned int irq);
void (*mask)(unsigned int irq);
void (*mask_ack)(unsigned int irq);
void (*unmask)(unsigned int irq);
void (*eoi)(unsigned int irq);

void (*end)(unsigned int irq);
int (*set_affinity)(unsigned int irq, const struct cpumask *dest);
int (*retrigger)(unsigned int irq);
int (*set_type)(unsigned int irq, unsigned int flow_type);
int (*set_wake)(unsigned int irq, unsigned int on);

void (*bus_lock)(unsigned int irq);
void (*bus_sync_unlock)(unsigned int irq);

/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
void (*release)(unsigned int irq, void *dev_id);
#endif
/*
* For compatibility, ->typename is copied into ->name.
* Will disappear.
*/
const char *typename;
};
struct <strong>irqaction</strong> {<span style="color:#FF0000;">	irq_handler_t handler;  // handler assigned by request_irq</span>	unsigned long flags;	const char *name;	void *dev_id;	struct irqaction *next;	int irq;	struct proc_dir_entry *dir;	irq_handler_t thread_fn;	struct task_struct *thread;	unsigned long thread_flags;};
之间的关系图:
linux的中断子系统简介(汇编和hard irq部分)_ARM平台(S5PV210)

•External interrupt resources in S5PV210

linux的中断子系统简介(汇编和hard irq部分)_ARM平台(S5PV210)

•Code flow
首先可以查看系统中有哪些有效的中断以及相关的信息.
# cat /proc/interrupts  //只显示有相应的action的IRQ的信息
CPU0
IRQ_NR count desc->chip->name action->name
16: 43 s3c-uart s5pv210-uart
18: 59 s3c-uart s5pv210-uart
33: 1 s5p_vic_eint mmc1
36: 0 s5p_vic_eint a700_ts
37: 1 s5p_vic_eint aic3254 headset irq
38: 0 s5p_vic_eint keypad
39: 0 s5p_vic_eint keypad
40: 0 s5p_vic_eint keypad
41: 0 s5p_vic_eint keypad
42: 0 s5p_vic_eint keypad
43: 0 s5p_vic_eint keypad
45: 1 s5p_vic_eint hpd
46: 1 s5p_vic_eint USB wak up
50: 0 VIC s3c-pl330.0
51: 0 VIC s3c-pl330.1
52: 0 VIC s3c-pl330.2
58: 0 VIC System timer
59: 0 VIC s3c2410-wdt
61: 14772 VIC rtc-tick
78: 220 VIC s3c2440-i2c.0
83: 27985 VIC s3c2440-i2c.2
88: 1 VIC s3c-udc
90: 52662 VIC mmc0
92: 268 VIC mmc1
93: 0 VIC s3c-csis
97: 2582 VIC s3cfb, s3cfb
102: 0 VIC s3c-fimc1
103: 0 VIC s3c-fimc2
105: 0 VIC s3c-g2d
106: 747 VIC pvrsrvkm
107: 0 VIC s5p-tvout
108: 0 VIC s5p-tvout
109: 0 VIC s3c2440-i2c.1
110: 0 VIC s3c-mfc
111: 0 VIC s5p-tvout
130: 13 VIC mmc2
170: 0 s5p-eint Bq27520_INT
Err: 0

首先是初始化的过程, IRQ init sequence:
start_kernel
setup_arch
early_trap_init
early_irq_init //没做什么事
init_IRQ
s5pv210_init_irq
s5p_init_irq
vic_init(irq_nr直接是从32开始的, 前面的目前看起来至少留给了timer和UART)
s3c_init_vic_timer_irq
s3c_init_uart_irqs
其中的early_trap_init, 基本的思路就是, 对于有MMU的系统, 异常向量的虚拟地址被映射到0xFFFF0000, 所以, 真正的7个异常向量(__vectors_start~__vectors_end)是被拷贝到这个0xFFFF0000开始的地方了. 接着, 异常处理代码块(__stubs_start~__stubs_end)被拷贝到0xFFFF0200处.
void __init early_trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE; //0xFFFF0000
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;

/*
* 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.
*/
<span style="color:#FF0000;"> 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);</span>

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

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

接下来, 中断发生后, 首先我们看到的是前面注册的那些vectors. 异常向量表的写法主要使用跳转指令B来进行. 因为异常向量表和异常处理代码块之间没有超过B指令要求的2^24=32MB, 仅仅相差0x200. 但是因为vertor_swi不是在这个文件中定义的, 所以, 只能用LDR指令来进行处理了.
__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 // prefetch abort
W(b) vector_dabt + stubs_offset // data abort
W(b) vector_addrexcptn + stubs_offset //
W(b) vector_irq + stubs_offset //IRQ入口
W(b) vector_fiq + stubs_offset //FIQ

.globl __vectors_end
__vectors_end:

@@@   中断处理程序的 stub  
vector_irq:
@ 调整 LR_irq
sub lr, lr, #4

@ 保存 R0, LR_irq(中断之前的 PC, 断点), SPSR_irq(中断之前的 CPSR) 到 irq模式的栈中
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr

@ SPSR 设置为 SVC模式
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0

@ 根据中断前的模式跳转到相应的处理程序

@ lr是中断刚开始时的 SPSR,即被中断代码的 CPSR,其低 4位表示中断之前的模式
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]

@ 跳转到相应模式的处理程序,模式变为 SVC(SPSR 拷贝到 CPSR )
movs pc, lr

@ 跳转表,必须紧跟 ldr lr,[pc,lr,lsl #2]和 movs pc,lr 两条指令(ARM 流水线机制??)
.long __irq_usr @ 0 (USR)
.long __irq_invalid @ 1 (FIQ)
.long __irq_invalid @ 2 (IRQ)
.long __irq_svc @ 3 (SVC)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6 (ABT)
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b (UND)
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f (SYS)

user mode的处理
@@@ USR模式中断入口 
__irq_usr:

@ 在内核栈中产生 include/asm-arm/ptrace.h中 pt_regs 定义的栈帧结构
sub sp, sp, #S_FRAME_SIZE
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:
@ r2 - lr_<exception>, already fixed up for correct return/restart
@ r3 - spsr_<exception>
@ 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}^

@ Clear FP to mark the first stack frame
zero_fp

@ 把被中断任务的 preempt_count 增加 1
get_thread_info tsk
#ifdef CONFIG_PREEMPT
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
add r7, r8, #1 @ increment it
str r7, [tsk, #TI_PREEMPT]
#endif

•@  循环调用 asm_do_IRQ()   
•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
• bne <span style="color:#FF0000;">asm_do_IRQ</span>
• 
•#ifdef CONFIG_PREEMPT
•  ldr  r0, [tsk, #TI_PREEMPT]
•  str  r8, [tsk, #TI_PREEMPT]
• teq r0, r7
• strne  r0, [r0, -r0]
•#endif
•@  返回到 user 模式  
• mov why, #0
• <span style="color:#FF0000;">b ret_to_user</span>

svc mode的处理
@@@ SVC模式中断入口 
__irq_svc:
@ 在内核栈中产生 include/asm-arm/ptrace.h中 pt_regs 定义的栈帧结构
sub sp, sp, #S_FRAME_SIZE
tst sp, #4
bicne sp, sp, #4
stmib sp, {r1 - r12}

ldmia r0, {r1 - r3}
add r5, sp, #S_SP @ here for interlock avoidance
mov r4, #-1 @ "" "" "" ""
add r0, sp, #S_FRAME_SIZE @ "" "" "" ""
addne r0, r0, #4
str r1, [sp] @ save the "real" r0 copied from the exception stack
mov r1, lr

@ We are now ready to fill in the remaining blanks on the stack:
@ r0 - sp_svc
@ r1 - lr_svc
@ r2 - lr_<exception>, already fixed up for correct return/restart
@ r3 - spsr_<exception>
@ r4 - orig_r0 (see pt_regs definition in ptrace.h)
stmia r5, {r0 - r4}

@ 把被中断任务的 preempt_count 增加 1
#ifdef CONFIG_PREEMPT
get_thread_info tsk
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
add r7, r8, #1 @ increment it
str r7, [tsk, #TI_PREEMPT]
#endif

@ 循环调用 asm-do_IRQ()
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
bne asm_do_IRQ

@ 如果需要调度,调用 svc_preempt进行内核抢占
#ifdef CONFIG_PREEMPT
ldr r0, [tsk, #TI_FLAGS] @ get flags
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
preempt_return:
ldr r0, [tsk, #TI_PREEMPT] @ read preempt value
str r8, [tsk, #TI_PREEMPT] @ restore preempt count
teq r0, r7
strne r0, [r0, -r0] @ bug()
#endif
@ 返回到内核空间
ldr r0, [sp, #S_PSR] @ irqs are already disabled
msr spsr_cxsf, r0
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr

可以看到, 都调用了 asm_do_IRQ
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);

<span style="color:#FF0000;">irq_enter();</span>

/*
* 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 {
<span style="color:#FF0000;">generic_handle_irq(irq);</span>
}

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

irq_exit();
set_irq_regs(old_regs);
}

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

static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
<span style="color:#FF0000;">desc->handle_irq(irq, desc); // high level handle, take handle_level_irq for example</span>
#else
if (likely(desc->handle_irq))
desc->handle_irq(irq, desc);
else
__do_IRQ(irq);
#endif
}

然后进入 handle_level_irq
Void  handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
struct irqaction *action;
irqreturn_t action_ret;

raw_spin_lock(&desc->lock);
mask_ack_irq(desc, irq);

if (unlikely(desc->status & IRQ_INPROGRESS))
goto out_unlock;
desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
kstat_incr_irqs_this_cpu(irq, desc);

action = desc->action;
if (unlikely(!action || (desc->status & IRQ_DISABLED)))
goto out_unlock;

desc->status |= IRQ_INPROGRESS;
raw_spin_unlock(&desc->lock);

action_ret = <span style="color:#FF0000;">handle_IRQ_event(irq, action);</span>
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);

raw_spin_lock(&desc->lock);
desc->status &= ~IRQ_INPROGRESS;

if (!(desc->status & (IRQ_DISABLED | IRQ_ONESHOT)))
unmask_irq(desc, irq);
out_unlock:
raw_spin_unlock(&desc->lock);
}
然后是handle_IRQ_event, 这边调用了request_irq时候注册的那个handle.
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;

do {
trace_irq_handler_entry(irq, action);
ret = <span style="color:#FF0000;">action->handler(irq, action->dev_id); // handle registered by request_irq</span>
trace_irq_handler_exit(irq, action, ret);

switch (ret) {
case IRQ_WAKE_THREAD:

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

default:
break;
}

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

if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();

return retval;
}
以图表示的话, 就是:
linux的中断子系统简介(汇编和hard irq部分)_ARM平台(S5PV210)

•Kernel API
int request_irq(unsigned int irq, irq_handler_t handler,  unsigned long irqflags, const char *devname, void *dev_id) 
参数:
irq---中断通道号,取值范围为 0~NR_IRQS – 1
handler---中断处理程序,原型为 irq_return_t isr_func(int irq, void *dev_id)
irq_flags---标志位
dev_name---名称,将会显示在/proc/interrupts中
dev_id---区分共享同一个中断通道的不同的处理程序


void free_irq(unsigned int irq, void *dev_id)
参数:
irq---中断通道号,取值范围为 0~NR_IRQS – 1
dev_id---区分共享同一个中断通道的不同的处理程序时才需要用到.

int set_irq_chip(unsigned int irq, struct irq_chip *chip) 
设置 chip

int set_irq_chip_data(unsigned int irq, void *data)
设置 chip_data

int set_irq_handle(unsigned int irq, irq_flow_handler_t handle)
设置 handle_irq

int set_irq_data(unsigned int irq, void *data)
设置 handler_data

int set_irq_type(unsigned int irq, unsigned int type)
设置指定通道的触发类型

Ryan: PPT完成于2011.9.15, blog完成于2015.1.4