IRQ and FIQ Dispatchers

The ASSP must supply two dispatcher functions, one for IRQ interrupts, and one for FIQ interrupts.

The following example code is a simple dispatcher for IRQ interrupts. It assumes a simplistic interrupt controller that provides 32 interrupt sources, and has a 32-bit pending-interrupt register where a 'one' bit indicates a pending interrupt and all ones are cleared when the register is read.

void IrqDispatch()
    {
    TUint32 pendingIrqs = TheAssp::IrqPendingRegister();
    // for the purposes of this example we assume that reading
    // this register also clears the pending flags

    TInt index = 0;
    while( pendingIrqs )
        {
        // while there is at least one pending IRQ
        if( pendingIrqs & 1 )
            {
            // the next interrupt is pending - dispatch it
            (IsrHandlers[index].iIsr)(IsrHandlers[index].iPtr);
            }
        ++index;
        pendingIrqs >>= 1;
        }
    }

The code assumes that the interrupt source represented by the low order bit in the pending-interrupt register is represented by interrupt ID number 0 etc.

When implementing the dispatcher it is usual to write it in C++ initially, but once you have it working you would probably want to rewrite it in assembler to gain maximum efficiency for what is a time-critical section of code.

Dealing with chained interrupts

There are two considerations when dealing with chained interrupts:

  1. how to identify interrupts on the lower priority chained controllers

  2. how to handle the dispatch of a chained interrupt.

The first point is a question of allocating locations in your ISR table for the secondary controllers so that the interrupt ID identifies which hardware controller the interrupt is on. For example, if each interrupt controller handles 32 interrupt sources, you could make the first 32 IDs refer to the highest-level controller, the next 32 refer to one of the second-level controllers etc.

There is no need to change the Interrupt::Bind() and Interrupt::Unbind() functions, although you may need to consider extending the Interrupt::Enable() and Interrupt::Disable() functions to enable and disable the chain interrupt in a higher-level interrupt controller, if necessary.

There are at least two ways of dispatching a chained interrupt:

Dispatching a chained interrupt (1)

One way of dispatching a chained interrupt is simply to make it a special case in the main interrupt dispatcher. For example:

void IrqDispatch()
    {
    TUint32 pendingIrqs = TheAssp::IrqPendingRegister();

    TInt index = 0;
    while( pendingIrqs )
        {
        if( pendingIrqs & 1 )
            {
            if( index == EMainIntChainIrq )
                {
                // second-level controller is signalling
                SecondLevelIrqDispatch();
                }
            else
                {
                // call ISR
                (IsrHandlers[index].iIsr)(IsrHandlers[index].iPtr);
                }
            }
        ++index;
        pendingIrqs >>= 1;
        }
    }

This approach works for a simple case, for example, where there is only a main and secondary interrupt controller. It is does not scale well because the special cases in the dispatcher become an overhead on the dispatch of normal, unchained interrupts as the number and depth of the chaining increases.

Dispatching a chained interrupt (2)

A better way of handling chained interrupts is to bind an ISR to the interrupt source in the main interrupt controller and use it to dispatch the chained interrupt. This is far more scalable because you can bind any number of ISRs without having to add special cases to any of the interrupt dispatchers.

The dispatcher code could then be re-implemented as:

void IrqDispatch()
    // MAIN IRQ DISPATCHER, FIRST-LEVEL INTERRUPT
    {
    TUint32 pendingIrqs = TheAssp::IrqPendingRegister();

    TInt index = 0;
    while( pendingIrqs )
        {
        if( pendingIrqs & 1 )
            {
            (IsrHandlers[index].iIsr)(IsrHandlers[index].iPtr);
            }
        ++index;
        pendingIrqs >>= 1;
        }
    }
void SecondLevelIrqDispatch( TAny* /* aParam */ )
    {
    TUint32 pendingIrqs = TheAssp::SecondLevelIrqPendingRegister();
 
    TInt index = EStartOfSecondLevelIntId;
    while( pendingIrqs )
        {
        if( pendingIrqs & 1 )
            {
            (IsrHandlers[index].iIsr)(IsrHandlers[index].iPtr);
            }
        ++index;
        pendingIrqs >>= 1;
        }
    }
void InitialiseSecondLevelDispatch()
    // Bind and enable the second-level dispatcher
    {
    Interrupt::Bind(EMainIntChainIrq,SecondLevelIrqDispatch,NULL);
    Interrupt::Enable( EMainIntChainIrq );
    }
Figure 1. Interrupt sources
  • The second level dispatcher, SecondLevelIrqDispatch(), is itself an ISR, and takes a TAny * parameter, but is not needed in this specific example. It reads the interrupt pending register of the second-level interrupt controller. Note, however, that the TAny * parameter may be useful when the second level dispatcher is in the variant as opposed to the ASSP. In that case you have separate interrupt IDs for variant level interrupts and separate ISR tables. The TAny * parameter can point to the appropriate ISR table. Alternatively the TAny * can point to a data block containing IO addresses - especially useful if you have many I/O devices mapped as hardware chunks. See the code in ...\assabet\specific\variant.cpp.

  • The index count starts at offset EStartOfSecondLevelIntId into the ISR table, where the second-level interrupt ISRs are located. In this example, this symbol would equate to 32.

  • EMainIntChainIrq is the interrupt ID of the chained interrupt source to the main interrupt controller.

Dealing with multiple interrupt sources

The case where multiple peripherals are connected to the same interrupt source can be handled through the technique of pseudo interrupt sources. This involves assigning pseudo-interrupt IDs in the ISR table to correspond to each of the peripherals that is attached to the interrupt line, i.e. ISRs are bound to these pseudo-interrupt sources.

Dealing with pseudo interrupt sources is, in essence, a special case of Chained interrupts.

The dispatcher can do one of two things:

  • examine the peripheral hardware to determine which of the interrupts are pending, and then call the appropriate ISR

  • call all the ISRs and leave them to determine whether their peripheral is actually signalling an interrupt.

As usual, it is entirely up to you to choose the ID numbers for these pseudo-interrupts.

There should be no need to alter the implementations of Interrupt::Bind() or Interrupt::Unbind() but you will need some special handling in Interrupt::Enable() to enable the true interrupt source, and Interrupt::Disable() to disable this interrupt source.

Dispatching the interrupt can be done in either of the two ways described in dealing with chained interrupts. Making use of a special case in the main interrupt dispatcher is acceptable for simple cases, but for more complicated cases with large numbers of pseudo-interrupts or a combination of chained and pseudo-interrupts, it is better to use an ISR dispatcher bound to the true interrupt source.