移植linux内核平台相关之 中断

来源:互联网 发布:mysql 设置编码 utf8 编辑:程序博客网 时间:2024/06/06 17:55

前言:

      在移植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  5165 BUILD_ROLLBACK_PROLOGUE handle_int166 NESTED(handle_int, PT_SIZE, sp)167 #ifdef CONFIG_TRACE_IRQFLAGS168         /*169          * Check to see if the interrupted code has just disabled170          * interrupts and ignore this interrupt for now if so.171          *172          * local_irq_disable() disables interrupts and then calls173          * trace_hardirqs_off() to track the state. If an interrupt is taken174          * after interrupts are disabled but before the state is updated175          * it will appear to restore_all that it is incorrectly returning with176          * interrupts disabled177          */178         .set    push179         .set    noat180         mfc0    k0, CP0_STATUS181 #if defined(CONFIG_CPU_R3000) || defined(CONFIG_CPU_TX39XX)182         and     k0, ST0_IEP183         bnez    k0, 1f184 185         mfc0    k0, CP0_EPC186         .set    noreorder187         j       k0188         rfe189 #else190         and     k0, ST0_IE191         bnez    k0, 1f192 193         eret194 #endif195 1:196         .set pop197 #endif198         SAVE_ALL199         CLI200         TRACE_IRQS_OFF201 202         LONG_L  s0, TI_REGS($28)203         LONG_S  sp, TI_REGS($28)204         PTR_LA  ra, ret_from_irq205         j       plat_irq_dispatch206         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_SOC148         do_IRQ(SOC_SOC_MODEM_IRQ);149         return;150 #endif151         /* 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) //0157         {158             irq = SOC_SOC_LCD_IRQ;159         }160         else if (intstatus & INT_MAC1) //1161         {162             irq = SOC_SOC_MAC1_IRQ;163         }164         else if (intstatus & INT_MAC2) //2165         {166             irq = SOC_SOC_MAC2_IRQ;167         }168         else if (intstatus & INT_AC97) //3169         {170             irq = SOC_SOC_AC97_IRQ;171         }172         else if (intstatus & INT_SPI) //8173         {174             irq = SOC_SOC_SPI_IRQ;175         }176         else if (intstatus & INT_UART0) //11177         {178             irq = SOC_SOC_UART0_IRQ;179         }180         else if (intstatus & INT_UART1) //12181         {182             irq = SOC_SOC_UART1_IRQ;183         }184         else if (intstatus & INT_KBD) //9185         {186             irq = SOC_SOC_KBD_IRQ;187         }188         else if (intstatus & INT_MOUSE) //10189         {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_check136 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 out1067          * which interrupt is which (messes up the interrupt freeing1068          * 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_FIXME1104         if (!retval && (irqflags & IRQF_SHARED)) {1105                 /*1106                  * It's a shared IRQ -- the driver ought to be prepared for it1107                  * to happen immediately, so let's make sure....1108                  * We disable the irq to make sure that a 'real' IRQ doesn't1109                  * 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 #endif1122         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,这是基于共享中断的实现。

 

 

 

 


----未完待续,请留意。

 

 

 

 

 

 

 

 

原创粉丝点击