Linux Interrupts

From LinuxMIPS
Revision as of 00:14, 6 May 2005 by (Talk)

Jump to: navigation, search

If you followed the previous steps, most likely you will see kernel hanging at the BogusMIPS calibration step. The reason is simple: The interrupt code is not there and jiffies are never updated. Therefore the calibration can never be done.

Before you start writing interrupt code, it really pays to study the hardware first. Pay particular attention to identify all interrupt sources, their corresponding controllers and how they are routed.

Then you need to come up with a strategy, which typically includes:

  • a static interrupt routing map
  • a list of interrupt sources
  • a list of their corresponding controllers
  • how interrupt controllers cascade from each other

Interrupt code overview

To completely service an interrupt, four different pieces of code work together:

IRQ detection/dispatching 
This is typically assembly code in a file called 'int_handler.S'. Sometimes there is also secondary-level dispatching code written in C for complicated IRQ detection. The end result is that we identify and select a single IRQ source, represented by an integer, and then pass it to function 'do_IRQ()'.
do_IRQ() is provided in the 'arch/mips/kernel/irq.c' file. It provides a common framework for IRQ handling. It invokes the individual IRQ controller code to enable/disable a particular interrupt. It calls the driver supplied interrupt handling routine that does the real processing.
It is a structure associated with each IRQ source. The structure is a collection of function pointers, which tells do_IRQ() how it should deal with this particular IRQ.
driver interrupt handling code 
The code that does the real job.

Obviously for our porting purposes we need to write IRQ detection/disptaching code and the hw_irq_handler code for any new IRQ controller used in the system. In addition, there is also IRQ setup and initialization code.

CPU as an IRQ controller

Most R4K-compatible MIPS CPUs have the ability to support eight interrupt sources. The first two are software interrupts, and the last one is typically used for CPU counter interrupt. Therefore you can have up to five external interrupt sources.

Luckily if the CPU is the only IRQ controller in the system, you are done! The hw_irq_handler code is written in arch/mips/kernel/irq_cpu.c file. All you have to do is to define "CONFIG_IRQ_CPU" for your machine. And in your irq setup routine make sure you call mips_cpu_irq_init(0). This call will initialize the interrupt descriptor and set up the IRQ numbers to range from 0 to 7.

You can also find a matching interrupt dispatching code in this file.

Set up cascading interrupts

More than likely you will have more interrupt sources than those that can directly connect to the CPU interrupt pins. A second or even third-level interrupt controller may connect to one or more of those CPU pins. In that case, you have cascading interrupts.

There are plenty of examples of how cascading interrupt works, such as the DDB5477 and the Osprey. Here is a short summary:

  • Assign blocks of IRQ numbers to various interrupt controllers in the whole system. For example, in the case of the Osprey, CPU interrupts occupy IRQ 0 to 7. Vr4181 system interrupts occupy IRQ 8 to 39, and GPIO interrupts occupy 40 to 56. In most cases, the actual IRQ numbers do not matter, as long as the driver knows which IRQ number it should use. However, if you have an i8259 interrupt controller and an ISA bus, you should try to assign IRQ number 0 to 16 for the i8259 interrupts because it will make the legacy PC drivers happy. (Please note before ~12/08/2001 in version 2.4.16 of the Linux kernel, the 'i8259.c' file set the base vector to be 0x20. If you use the IRQ acknowledgement cycle to obtain the interrupt vector, you will get an IRQ number from 0x20 to 0x2f. You will then need to substract 0x20 from the return value to get the correct IRQ number.)
  • Write the 'hw_irq_controller' member functinos for your specific controllers. Note that CPU and i8259 already have their code written. You just need to define appropriate CONFIG options for your board. See the next sub-section for more details about writing 'hw_irq_controller' member functions.
  • In your IRQ setup routine, initialize all the controllers, usually by calling 'interrupt_controller_XXX_init()' functions.
  • In your IRQ setup routine, setup the cascading IRQs. This setup will enable interrupts for the upper interrupt controller so that the lower-level interrupts can cascade through once they are enabled. A typical way of doing this is to have a dummy 'irqaction struct' and setup as follows:
       static struct irqaction cascade =
               { no_action, SA_INTERRUPT, 0, "cascade", NULL, NULL };

       extern int setup_irq(unsigned int irq, struct irqaction *irqaction);

       void __init <my>_irq_init(void)
               setup_irq(CPU_IP3, &cascade);

  • You need to expand your interrupt dispatching code properly to identify the added interrupt sources. If the code is simple enough, you can do it in the same int_handler.S file. If it is more complicated, you may do it in a separate C function (such as in the DDB5476 board).

The hw_irq_controller struct

The 'hw_irq_controller structure' is a defined in the 'include/linux/irq.h' file as an alias for the 'hw_interrupt_type' structure.

   struct hw_interrupt_type {
           const char * typename;
           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 (*end)(unsigned int irq);
           void (*set_affinity)(unsigned int irq, unsigned long mask);

The 'arch/mips/kernel/irq_cpu.c' is a good sample code to write 'hw_irq_controller' member functions. Here are some more programming notes for each of these functions:

   const char * typename;
       Controller name. Will be displayed under /proc/interrupts.
   unsigned int (*startup)(unsigned int irq);
       Invoked when request_irq() or setup_irq are called. You need to enable
       this interrupt here. Other than that you may also want to do some
       IRQ-specific initialization (such as turning on power for this
       interrupt, perhaps).
   void (*shutdown)(unsigned int irq);
       Invoked when free_irq() is called. You need to disable this interrupt
       and perhaps some other IRQ-specific cleanup.
   void (*enable)(unsigned int irq) and void (*disable)(unsigned int irq)
       They are used to implement enable_irq(), disable_irq() and
       disable_irq_nosync(), which in turn are used by driver code.
   void (*ack)(unsigned int irq)
       ack() is invoked at the beginning of do_IRQ() when we want to
       acknoledge an interrupt. I think you need also to disable this
       interrupt here so that you don't get recursive interrupts on the same
       interrupt source. [HELP: can someone confirm?]
   void (*end)(unsigned int irq)
       This is called by do_IRQ() after it has handled this interrupt. If you
       disabled interrupt in ack() function, you should enable it here. [HELP:
       generally what else we should do here?]
   void (*set_affinity)(unsigned int irq, unsigned long mask)
       This is used in SMP machines to set up interrupt handling affinity with
       certain CPUs. [TODO] [HELP]

The IRQ initialization code

The IRQ initialization is done in 'init_IRQ()'. Currently it is supplied by each individual board. In the future, it will probably be a MIPS common routine, which will further invoke a board-specific function, board_irq_init(). board_irq_init will be a function pointer that <my_board>_setup() function needs to assign propoer value.

In any case, the following is a skeleton code for a normal init_IRQ() routine.

   extern asmlinkage void vr4181_handle_irq(void);
   extern void breakpoint(void);
   extern int setup_irq(unsigned int irq, struct irqaction *irqaction);
   extern void mips_cpu_irq_init(u32 irq_base);
   extern void init_generic_irq(void);

   static struct irqaction cascade =
           { no_action, SA_INTERRUPT, 0, "cascade", NULL, NULL };
   static struct irqaction reserved =
           { no_action, SA_INTERRUPT, 0, "reserved", NULL, NULL };
   static struct irqaction error_irq =
           { error_action, SA_INTERRUPT, 0, "error", NULL, NULL };

   void __init init_IRQ(void)
           int i;
           extern irq_desc_t irq_desc[];

           /* hardware initialization code */

           /* this is the routine defined in int_handler.S file */
           set_except_vector(0, my_irq_dispatcher);

           /* setup the default irq descriptor */

           /* init all interrupt controllers */

           /* set up cascading IRQ */
           setup_irq(CPU_IRQ_IP3, &cascade);

           /* set up reserved IRQ so that others can not mistakingly request
            * it later.
           setup_irq(CPU_IRQ_IP4, &reserved);

   #ifdef CONFIG_DEBUG
           /* setup debug IRQ so that if that interrupt happens, we can
            * capture it.
           setup_irq(CPU_IRQ_IP4, &error_irq);

           printk("Setting debug traps - please connect the remote debugger.\n");

Final notes

What is described in this chapter is what is so-called new style interrupt handling. We used to have three different ways to handle interrupts: new style (CONFIG_NEW_IRQ), the old style (CONFIG_ROTTEN_IRQ) and board-private ad hoc routines. New style is now the only valid method since October-2002.

Next page: System Time and Timer