前言:
在移植linux的时候,需要加入一段平台相关的代码。而在这部分代码中,中断是一个重要的环节。所以我们需要去了解linux内核的中断处理结构是怎样的,然后才能在适当的地方加上平台相关的代码。在不同的linux内核版本中,可能中断处理的结构不尽相同,这就要具体问题具体去分析了,本文主要是介绍在移植linux2.6.38.4到龙芯soc3210的时候,得出的一些关于中断处理的经验和体会,在此分享。
从./init/main.c中的start_kernel开始
init_IRQ为初始化中断IRQ的函数入口,进入之后,可以看到:
arch_init_irq()
从函数名可以看出,该函数是与平台相关的,也就是我们需要实现的函数。
然而,我们需要怎样去实现这个函数呢,也就是说,要在这个函数里干些什么事呢,这就是移植的重点,也是本文的重点。
根据龙芯3210的设计(实际上就是MIPS的中断设计)上分析中断的处理过程:
CPU发生中断后,首先会跳转到异常入口地址,如果是中断,那么开始执行中断处理。在中断处理里,读取原因寄存器cause,判断IP位是否有置位的,再根据IP7~IP0的的不同置位去执行不同类型的中断例程,在同一种类型的中断处理里,又根据中断状态寄存器来判断具体发生了什么中断,然后再去执行这个中断的服务程序。
根据以上的处理过程,看linux的代码的实现过程:
在trap_init函数里设置异常处理代码:
set_handler(0x180, &except_vec3_generic, 0x80);
把except_vec3_generic函数指针放在地址为CKSEG0 + 0x180处,这个地址就是中断异常的入口地址。
再来看except_vec3_generic函数,这是一个汇编函数,在./arch/mips/kernel/genex.S中定义:
52 NESTED(except_vec3_generic, 0, sp)
53 .set push
54 .set noat
55 #if R5432_CP0_INTERRUPT_WAR
56 mfc0 k0, CP0_INDEX
57 #endif
58 mfc0 k1, CP0_CAUSE
59 andi k1, k1, 0x7c
60 #ifdef CONFIG_64BIT
61 dsll k1, k1, 1
62 #endif
63 PTR_L k0, exception_handlers(k1)
64 jr k0
65 .set pop
66 END(except_vec3_generic)
从代码中可以看出,读取Cause寄存器中的ExcCode的值,以此为索引,从数组exception_handlers中找到要执行的处理例程的函数指针并执行,如果是中断异常,索引为0,也就是执行exception_handlers[0]函数。
从函数except_vec3_generic中可以知道,现在需要给exception_handlers[0]赋值,在trap_init函数中:
set_except_vector(0, rollback ? rollback_handle_int : handle_int);
其中rollback为0,也就是说exception_handlers[0] = handle_int。
再来看handle_int:
164 .align 5
165 BUILD_ROLLBACK_PROLOGUE handle_int
166 NESTED(handle_int, PT_SIZE, sp)
167 #ifdef CONFIG_TRACE_IRQFLAGS
168 /*
169 * Check to see if the interrupted code has just disabled
170 * interrupts and ignore this interrupt for now if so.
171 *
172 * local_irq_disable() disables interrupts and then calls
173 * trace_hardirqs_off() to track the state. If an interrupt is taken
174 * after interrupts are disabled but before the state is updated
175 * it will appear to restore_all that it is incorrectly returning with
176 * interrupts disabled
177 */
178 .set push
179 .set noat
180 mfc0 k0, CP0_STATUS
181 #if defined(CONFIG_CPU_R3000) || defined(CONFIG_CPU_TX39XX)
182 and k0, ST0_IEP
183 bnez k0, 1f
184
185 mfc0 k0, CP0_EPC
186 .set noreorder
187 j k0
188 rfe
189 #else
190 and k0, ST0_IE
191 bnez k0, 1f
192
193 eret
194 #endif
195 1:
196 .set pop
197 #endif
198 SAVE_ALL
199 CLI
200 TRACE_IRQS_OFF
201
202 LONG_L s0, TI_REGS($28)
203 LONG_S sp, TI_REGS($28)
204 PTR_LA ra, ret_from_irq
205 j plat_irq_dispatch
206 END(handle_int)
可以看到,最后handle_int会跳转到plat_irq_dispatch执行。根据中断处理过程,plat_irq_dispatch要实现中断类型的判断和执行具体的中断服务程序。这些明显是平台相关的,也就是移植中需要实现的平台相关的代码。
在soc3210的平台相关代码中:
asmlinkage void plat_irq_dispatch(struct pt_regs *regs)
{
unsigned int cause = read_c0_cause() & ST0_IM;
unsigned int status = read_c0_status() & ST0_IM;
unsigned int pending = cause & status;
if (pending & CAUSEF_IP7) {
do_IRQ(63);
} else if (pending & CAUSEF_IP2) {
soc_soc_hw0_irqdispatch(regs);
} else {
spurious_interrupt();
}
}
从plat_irq_dispatch中看出,首先通过IP值来判断中断类型,其中IRQ对应IP2,IP7对应定时器。先来看IRQ,进入函数 soc_soc_hw0_irqdispatch,可以猜想,这个函数实际上是读取中断状态寄存器来确定执行具体的中断服务例程。
140 void soc_soc_hw0_irqdispatch(struct pt_regs *regs)
141 {
142 int irq;
143 int intstatus = 0;
144 int status;
145
146 /* Fix Me!!*/
147 #ifdef CONFIG_SIMOS_SOC_SOC
148 do_IRQ(SOC_SOC_MODEM_IRQ);
149 return;
150 #endif
151 /* Receive interrupt signal, compute the irq */
152 status = read_c0_cause();
153 intstatus = soc_soc_hw0_icregs->int_isr;
154
155
156 if (intstatus & INT_LCD) //0
157 {
158 irq = SOC_SOC_LCD_IRQ;
159 }
160 else if (intstatus & INT_MAC1) //1
161 {
162 irq = SOC_SOC_MAC1_IRQ;
163 }
164 else if (intstatus & INT_MAC2) //2
165 {
166 irq = SOC_SOC_MAC2_IRQ;
167 }
168 else if (intstatus & INT_AC97) //3
169 {
170 irq = SOC_SOC_AC97_IRQ;
171 }
172 else if (intstatus & INT_SPI) //8
173 {
174 irq = SOC_SOC_SPI_IRQ;
175 }
176 else if (intstatus & INT_UART0) //11
177 {
178 irq = SOC_SOC_UART0_IRQ;
179 }
180 else if (intstatus & INT_UART1) //12
181 {
182 irq = SOC_SOC_UART1_IRQ;
183 }
184 else if (intstatus & INT_KBD) //9
185 {
186 irq = SOC_SOC_KBD_IRQ;
187 }
188 else if (intstatus & INT_MOUSE) //10
189 {
190 irq = SOC_SOC_MOUSE_IRQ;
191 }
192 //------------------------------------------------------------------------------
193 else if((soc_soc_hw0_icregs->int_en && (1<<SOC_SOC_CAN0_IRQ))&&((soc_soc_can0_status=*(volatile char *)0xbf004403) & 0x1f))
194 {
195 irq=SOC_SOC_CAN0_IRQ;
196 }
197 else if((soc_soc_hw0_icregs->int_en&&(1<<SOC_SOC_CAN1_IRQ)) && ((soc_soc_can1_status=*(volatile char *)0xbf004303) & 0x1f))
198 {
199 irq=SOC_SOC_CAN1_IRQ;
200 }
201
202 //------------------------------------------------------------------------------
203 else if (intstatus & INT_PCI_INTA)
204 irq = SOC_SOC_PCI_INTA_IRQ;
205 else if (intstatus & INT_PCI_INTB)
206 irq = SOC_SOC_PCI_INTB_IRQ;
207 else if (intstatus & INT_PCI_INTC)
208 irq = SOC_SOC_PCI_INTC_IRQ;
209 else if (intstatus & INT_PCI_INTD)
210 irq = SOC_SOC_PCI_INTD_IRQ;
211 else if (intstatus & INT_GPIO15)
212 irq =SOC_SOC_GPIO15_IRQ;
213 else if (intstatus & INT_GPIO14)
214 irq =SOC_SOC_GPIO14_IRQ;
215 else if (intstatus & INT_GPIO13)
216 irq =SOC_SOC_GPIO13_IRQ;
217 else if (intstatus & INT_GPIO12)
218 irq =SOC_SOC_GPIO12_IRQ;
219 else {
220 printk("Unknow interrupt status %x intstatus %x /n" , status, intstatus);
221 return;
222 }
223 do_IRQ(irq);
224 }
该函数的内容这么多,实际上要做的事情就是根据中断状态寄存器确定中断号,然后,以中断号为参数,传入do_IRQ,也就是说,do_IRQ才是实际具体中断服务例程的执行者。
182 void __irq_entry do_IRQ(unsigned int irq)
183 {
184 irq_enter();
185 check_stack_overflow();
186 __DO_IRQ_SMTC_HOOK(irq);
187 generic_handle_irq(irq);
188 irq_exit();
189 }
进入generic_handle_irq(irq);
114 static inline void generic_handle_irq(unsigned int irq)
115 {
116 generic_handle_irq_desc(irq, irq_to_desc(irq));
117 }
进入 generic_handle_irq_desc:
109 static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
110 {
111 desc->handle_irq(irq, desc);
112 }
到这里,我们知道执行者是desc->handle_irq,所以这个IRQ描述符desc的初始化,或者说是成员赋值就是关键了。
先把上面的问题放一下,回到驱动程序的层面来看中断服务程序的申请,来看是怎样执行驱动里的中断服务例程的。
以串口驱动为例:
在./driver/tty/serial/8250.c的serial_link_irq_chain函数中,申请irq:
1730 ret = request_irq(up->port.irq, serial8250_interrupt,
1731 irq_flags, "serial", i);
也就是说,如果注册irq成功,那么一旦发生串口中断,那就调用中断服务程序serial8250_interrupt。
来看函数request_irq是怎么把函数serial8250_interrupt放进linux的中断处理系统的:
135 static inline int __must_check
136 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
137 const char *name, void *dev)
138 {
139 return request_threaded_irq(irq, handler, NULL, flags, name, dev);
140 }
进入函数request_threaded_irq:
1056 int request_threaded_irq(unsigned int irq, irq_handler_t handler,
1057 irq_handler_t thread_fn, unsigned long irqflags,
1058 const char *devname, void *dev_id)
1059 {
1060 struct irqaction *action;
1061 struct irq_desc *desc;
1062 int retval;
1063
1064 /*
1065 * Sanity-check: shared interrupts must pass in a real dev-ID,
1066 * otherwise we'll have trouble later trying to figure out
1067 * which interrupt is which (messes up the interrupt freeing
1068 * logic etc).
1069 */
1070 if ((irqflags & IRQF_SHARED) && !dev_id)
1071 return -EINVAL;
1072
1073 desc = irq_to_desc(irq);
1074 if (!desc)
1075 return -EINVAL;
1076
1077 if (desc->status & IRQ_NOREQUEST)
1078 return -EINVAL;
1079
1080 if (!handler) {
1081 if (!thread_fn)
1082 return -EINVAL;
1083 handler = irq_default_primary_handler;
1084 }
1085
1086 action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
1087 if (!action)
1088 return -ENOMEM;
1089
1090 action->handler = handler;
1091 action->thread_fn = thread_fn;
1092 action->flags = irqflags;
1093 action->name = devname;
1094 action->dev_id = dev_id;
1095
1096 chip_bus_lock(desc);
1097 retval = __setup_irq(irq, desc, action);
1098 chip_bus_sync_unlock(desc);
1099
1100 if (retval)
1101 kfree(action);
1102
1103 #ifdef CONFIG_DEBUG_SHIRQ_FIXME
1104 if (!retval && (irqflags & IRQF_SHARED)) {
1105 /*
1106 * It's a shared IRQ -- the driver ought to be prepared for it
1107 * to happen immediately, so let's make sure....
1108 * We disable the irq to make sure that a 'real' IRQ doesn't
1109 * run in parallel with our fake.
1110 */
1111 unsigned long flags;
1112
1113 disable_irq(irq);
1114 local_irq_save(flags);
1115
1116 handler(irq, dev_id);
1117
1118 local_irq_restore(flags);
1119 enable_irq(irq);
1120 }
1121 #endif
1122 return retval;
1123 }
函数中的handler就是我们的传入来的中断服务程序的函数指针,这个是关键。其中action的handler指向handler,也就换了跟踪目标为action这个结构体指针。进入 __setup_irq函数,注意跟踪action这个传入参数。
__setup_irq函数比较长,在这里就不贴出来了,有兴趣的可以去看源码。就我们关心的action参数,action指针可以理解为中断动作列表,这个函数的作用大概是这样的:
(1)判断该中断描述符desc的irq_data的chip成员是不是对应no_irq_chip,如果是直接返回;
(2)判断该中断描述符是否已存在中断动作,也就是desc->action是否为NULL;
(3)如果不为NULL,那么该中断号属于共享中断号,那么把新的action加入到该中断描述符desc的动作列表action的最后;
(4)如果不是共享中断号,那么就开始初始化这个中断描述符desc的irq_data的chip成员,最后也将新的action加入到desc的动作列表。
从上面4步可以看出,(3)和(4)最后都是将action加入到中断描述符的动作列表中。看(4)是如果初始化chip成员的:
irq_chip_set_defaults(desc->irq_data.chip);
都是一些默认的初始化操作,如果未初始化的成员就将其进行默认初始化,而默认的函数未必是正确的,也就是说,如果其中一些函数是跟平台相关的话,那么,在arch_init_irq的时候就是将其进行平台相关的初始化。但就目前而言,还不知道哪些将会是被调用到的,所以算放一下这个问题。
分析到此,先作个小结:
(1)中断发生时,desc->handle_irq函数会被调用。
(2)我们的中断服务程序保存在desc->action中。
那么,剩下的问题应该就是如何在desc->handle_irq函数中调用desc->action动作列表中的handler了。换句话说,我们需要一个函数调用动作列表中的handler,而这个函数以指针的形式赋值给desc->handler_irq。幸运的是内核已经实现了这个函数。
void handle_level_irq(unsigned int irq, struct irq_desc *desc)
void handle_percpu_irq(unsigned int irq, struct irq_desc *desc)
其中handle_percpu_irq为多CPU设计的,那么这里我们用handle_level_irq对desc->handle_irq进行初始化。
这两个函数最終都调用了handle_IRQ_event,所以不影响我们的分析。
进入handle_IRQ_event(irq, desc->action):
61 irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
62 {
63 irqreturn_t ret, retval = IRQ_NONE;
64 unsigned int status = 0;
65
66 do {
67 trace_irq_handler_entry(irq, action);
68 ret = action->handler(irq, action->dev_id);
69 trace_irq_handler_exit(irq, action, ret);
70
71 switch (ret) {
72 case IRQ_WAKE_THREAD:
73 /*
74 * Set result to handled so the spurious check
75 * does not trigger.
76 */
77 ret = IRQ_HANDLED;
78
79 /*
80 * Catch drivers which return WAKE_THREAD but
81 * did not set up a thread function
82 */
83 if (unlikely(!action->thread_fn)) {
84 warn_no_thread(irq, action);
85 break;
86 }
87
88 /*
89 * Wake up the handler thread for this
90 * action. In case the thread crashed and was
91 * killed we just pretend that we handled the
92 * interrupt. The hardirq handler above has
93 * disabled the device interrupt, so no irq
94 * storm is lurking.
95 */
96 if (likely(!test_bit(IRQTF_DIED,
97 &action->thread_flags))) {
98 set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
99 wake_up_process(action->thread);
100 }
101
102 /* Fall through to add to randomness */
103 case IRQ_HANDLED:
104 status |= action->flags;
105 break;
106
107 default:
108 break;
109 }
110
111 retval |= ret;
112 action = action->next;
113 } while (action);
114
115 if (status & IRQF_SAMPLE_RANDOM)
116 add_interrupt_randomness(irq);
117 local_irq_disable();
118
119 return retval;
120 }
注意到:ret = action->handler(irq, action->dev_id)就是调用action动作列表中的handler,这个handler就是指向由request_irq申请中断时传入的中断服务程序的函数指针。也就是说在此执行了真正的中断服务程序。
以上的代码用遍历了中断描述符的动作列表,分别执行了列表中的handler,这是基于共享中断的实现。
----未完待续,请留意。