diff -r 578be2adaf3e -r 307f4279f433 Adaptation/GUID-2E54DA7D-1094-41C6-AFB0-9999471991F8.dita --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Adaptation/GUID-2E54DA7D-1094-41C6-AFB0-9999471991F8.dita Fri Oct 15 14:32:18 2010 +0100 @@ -0,0 +1,430 @@ + + + + + +Interrupt Implementation GuideDescribes how to implement the Interrupt class. +
Introduction

Interrupt handling is implemented by writing platform +specific versions of the structures and functions of the Interrupt +class. The details of the implementation depend on hardware and the +architecture of the device. This document describes a simple implementation +of the structures and functions and then discusses more elaborate +strategies required with the use of device specific interrupts , chained +interrupts and multiple interrupt sources. It also covers implementation +on unicore platforms only. The SMP version of the kernel now implements +support for the ARM Generic Interrupt Controller and relies on its +use.

    +
  • Chained interrupts are interrupts which are output by one controller +and input to another. They need to be distinguished in the ISR table +and require extensions to the handler functions.

  • +
  • Multiple interrupt sources to the same ISR require the use +of pseudo-interrupts.

  • +
  • When a Symbian port is split into an ASSP and variant (common +and device specific) layer, the variant may include additional interrupt +sources. Their API is defined by the port. Device specific interrupts +are sometimes used to handle interrupts from peripherals: another +technique is to route peripheral interrupts to a GPIO pin. Peripheral +interrupts cannot be specified as part of the ASSP layer.

  • +

The Template Baseport provides a skeleton implementation for +developers to modify at kernelhwsrv/bsptemplate/asspandvariant/template_assp/interrupts.cpp.

+
The +ISR table

The ISR table is a data structure which pairs +each ISR with the interrupt source to which it will be bound. It must +have enough space for each interrupt source on the device. It is implemented +as an array of Interrupt::SInterruptHander of size KINterruptSourceCount in which the interrupt Id is used +as an index to the table. This example code assumes a size of 32.

... +const TInt KInterruptSourceCount = 32; +SInterruptHandler IsrHandlers[KInterruptSourceCount]; +...
+
DisableAndClearAll()

Interrupts must be disabled and cleared with a call to Interrupt::DisableAndClearAll() before the call to Interrupt::Init1() at the start of initialization. Implementation +of this function is entirely hardware dependent.

+
Init1()

The kernel is initialized in phases, interrupts being involved +in the first phase and sometimes the third. The Init1() function should +be implemented to

    +
  • Initialize the ISR table, binding all ISRs to the spurious +interrupts handler.

  • +
  • Register the dispatcher functions.

  • +
  • Bind ISRs which handle chained or pseudo interrupts.

  • +

Interrupts must be disabled during first phase initialization.

This example code illustrates initialization of the ISR table.

... +const TInt KInterruptSourceCount = 32; +SInterruptHandler IsrHandlers[KInterruptSourceCount]; +... + +for( TInt i = 0; i < KInterruptSourceCount; ++i ) + { + IsrHandlers[i].iIsr = SpuriousHandler; + IsrHandlers[i].iPtr = (TAny*)i; + }

This example code illustrates an implementation +of Interrupt::Init1() after the point at which +the ISR table has been initialized.

void TemplateInterrupt::Init1() + { + // + // need to hook the ARM IRQ and FIQ handlers as early as possible + // and disable and clear all interrupt sources + // + ... + DisableAndClearAll(); + Arm::SetIrqHandler((TLinAddr)TemplateInterrupt::IrqDispatch); + Arm::SetFiqHandler((TLinAddr)TemplateInterrupt::FiqDispatch); + } +
+
Init3()

Third phase initialization involves initializing various interrupt +handlers and sources which can only be initialized when the kernel +is fully functional. This is done with a call to Interrupt::Init3().

It is important to remember that interrupts are enabled during +third phase initialization.

+
Spurious()

Interrupts not bound to a real ISR must be bound to a 'spurious' +handler function Interrupt::Spurious() which returns +an error with the number of the interrupt.

+
Bind()

The Interrupt::Bind() function binds ISRs to +interrupt sources.

The argument aId is the +Id of an interrupt source and is used to index an entry in the ISR +table. Set the iPtr and iIsr members +of that entry (ISR parameter and ISR pointer) to the passed in values aPtr and aIsr.

The implementation +should perform some preliminary checks.

    +
  • The interrupt Id must be checked for validity

  • +
  • The ISR must not already be bound to a real interrupt. It should +have been bound to the spurious interrupt handler at initialization.

  • +

All interrupts must be disabled during binding.

This +example code provides a basic implementation.

EXPORT_C TInt Interrupt::Bind(TInt aId, TIsr aIsr, TAny* aPtr) + { + TInt r = KErrNone; + If(TUint(aId)>=TUint(KInterruptSourceCount)) + { + r = KErrArgument; // Illegal interrupt number + } + else + { + SInterruptHandler& h = IsrHandlers[aId]; + TInt irq = NKern::DisableAllInterrupts(); + if (h.iIsr != &SpuriousHandler) + { + r = KErrInUse; // Already bound to an ISR + } + else + { + h.iPtr = aPtr; // The ISR parameter + h.iIsr = aIsr; // Pointer to the ISR + } + NKern::RestoreInterrupts(irq); + } + return r; + } +

The implementation of Interrupt::Bind() can be more complicated in the case of chained interrupts, multiple +interrupt sources, pseudo interrupt sources and device interrupts: +see the discussion of those topics.

+
Unbind()

The Interrupt::Unbind() function unbinds ISRs +from interrupt sources.

The argument aId is +the Id of an interrupt source and is used to index an entry in the +ISR table. Reset the entry in the ISR table to reference the spurious +handler function.

The implementation should perform some preliminary +checks.

    +
  • The interrupt Id must be checked for validity

  • +
  • The ISR must not already be unbound (that is, bound to the +spurious interrupt handler).

  • +

All interrupts must be disabled during unbinding.

This +example code provides a basic implementation.

EXPORT_C TInt Interrupt::Unbind(TInt aId) + { + TInt r = KErrNone; + if (TUint(aId) >= TUint(KInterruptSourceCount)) + { + r = KErrArgument; // Illegal interrupt number + } + else + { + SInterruptHandler& h = IsrHandlers[aId]; + TInt irq = NKern::DisableAllInterrupts(); + if (h.iIsr == &SpuriousHandler) + { + r = KErrGeneral; // Already unbound + } + else + { + h.iPtr =(TAny*)aId; + h.iIsr = SpuriousHandler; // Replace with spurious handler + // NOTE: at this point it may be wise to + // force the hardware interrupt source to disabled. + } + NKern::RestoreInterrupts(irq); + } + return r; + } +

The implementation of Interrupt::Unbind() can be more complicated in the case of chained interrupts, multiple +interrupt sources, pseudo interrupt sources and device interrupts: +see the discussion of those topics below.

+
Enable()

Device drivers call the Interrupt::Enable() function to enable the interrupt source identified by the argument anId in the interrupt controller hardware.

The implementation +is entirely hardware dependent.

This example involves a check +for chained interrupts, which are discussed in their own section below.

EXPORT_C TInt Interrupt::Enable(TInt anId) + { + TInt r=KErrNone; + // if ID indicates a chained interrupt, call variant... + if (anId<0 && ((((TUint)anId)>>16)&0x7fff)<(TUint)KNumTemplateInts) + r=TemplateAssp::Variant->InterruptEnable(anId); + else if ((TUint)anId>=(TUint)KNumTemplateInts) + r=KErrArgument; + else if (TemplateInterrupt::Handlers[anId].iIsr==TemplateInterrupt::Spurious) + r=KErrNotReady; + else + { + // + // TO DO: (mandatory) + // + // Enable the corresponding Hardware Interrupt source + // + } + return r; + } +
+
Disable()

Device drivers call the Interrupt::Disable() function to disable the interrupt source identified by the argument anId in the interrupt controller hardware. The implementation +is entirely hardware dependent.

+
Clear()

Device drivers call the Interrupt::Clear() function +to acknowledge that they have serviced the interrupt and cleared the +pending flag in the interrupt controller hardware. The implementation +is entirely hardware dependent.

Clear() is +a useful function in cases where an interrupt must be cleared explicitly +rather than as a side effect of I/O register access: for instance +in PC card and MMC controller code.

+
SetPriority()

The Interrupt::SetPriority() function associates +a priority value (passed as a TInt) with an interrupt +Id. The meaning of the priority value is entirely hardware dependent.

Priority is a property of interrupts on some hardware, an example +being OMAP. Where the hardware is of this type, Interrupt::SetPriority() can be used to modify priorities in hardware. A simple use is to +determine whether an interrupt generates an IRQ or an FIQ. If priority +adjustment is not supported or not specified, the function should +simply return KErrNotSupported.

The implementation +is entirely hardware dependent.

+
IrqDispatch() +and FiqDispatch()

The functions Interrupt::IrqDispatch() and Interrupt::FiqDispatch() dispatch an interrupt +by calling the associated ISR. Interrupts are either IRQ or FIQ interrupts +and separate dispatch functions must be provided for each type. What +follows refers to IRQs but applies equally to FIQs as the distinction only operates at the level of hardware and the two +dispatch functions look the same.

In the simplest implementation, +the interrupt Id is used as an index into the ISR table. The iIsr member of the entry is called as a function with the iPtr member as its argument. The interrupt Id is taken from +a list of pending IRQs as in this example code.

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; + } + } +

This code is a simplified example which assumes that

    +
  • The interrupt controller provides 32 interrupt sources and +has a 32 bit pending interrupt register where a 1 indicates a pending +interrupt and all ones are cleared when the register is read.

  • +
  • The interrupt source represented by the low order bit in the +pending interrupt register is represented by interrupt Id 0 and so +on.

  • +

Implementation will be more complex where chained interrupts +and multiple interrupt sources are involved, as discussed below.

Dispatch functions are time critical. You will probably write +an initial implementation in C++ to get them working and then rewrite +in assembler for maximum efficiency.

+
Chained +interrupts

A platform often has multiple interrupt controllers +of higher and lower priority (higher and lower level controllers), +organized so that the output of a lower level controller is one of +the inputs to a higher level controller. Interrupt sources organized +in this way are called chained interrupts.

In a system with +chained interrupts, the ISR table must be structured so that interrupts +from higher and lower level controllers can be distinguished by their +Ids.

The Interrupt::Bind() and Interrupt::Unbind() functions are the same whether interrupts are chained or not.

In a system with chained interrupts it can be desirable to write +the Interrupt::Enable() and Interrupt::Disable() functions so as to disable not the interrupt itself but a the higher +level interrupt on the controller to which it is the input.

The Interrupt::IrqDispatch() and Interrupt::FiqDispatch() functions need to be extended in a system with chained interrupts. +There are two techniques for doing this, both of which involve a separate +second level dispatch function, but which differ in the way it is +called.

    +
  • In one technique, the main interrupt dispatcher calls the second +level dispatcher if the relevant condition is satisfied.

  • +
  • In the other technique, the second level dispatcher is bound +directly to an interrupt source as its ISR.

  • +

The first technique works well in cases where there is only +a main and a secondary interrupt controller. It does not scale well +in cases which make use of multiple controllers chained to substantial +depth.

You need to allocate locations in your ISR table for +the secondary controllers so that the interrupt Id identifies which +hardware controller the input is on. For example, if each interrupt +controller handles 32 interrupt sources, you could allocate the first +32 Ids to the highest level controller, the next 32 to a second level +controller and so on.

This example code illustrates a main dispatcher +which calls a second level dispatcher.

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 example code illustrates a second level dispatcher +bound directly to an interrupt source.

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 ); + } +

This example assumes an ISR table in which the second +level interrupt ISRs begin at location 32 (EStartOfSecondLevelIntId). EMainIntChainIrq is the interrupt Id of the chained +interrupt source to the main interrupt controller. The second level +dispatcher is itself an ISR with an argument TAny* which is not needed in this example (possible uses are to distinguish +between core and device specific ISR tables or to point to I/O addresses).

+
Multiple +interrupt sources

In cases where multiple peripherals are +connected to the same interrupt source, multiple sources may generate +the same interrupt which will then require a different ISR depending +on the specific source. However, EKA2 does not allow binding of multiple +ISRs to the same interrupt. There are two strategies for solving this +problem, both of which involve assigning the multiple ISRs not to +the real interrupt but to pseudo-interrupt Ids. In one strategy the +dispatcher examines the hardware to determine where the interrupt +originated and calls the appropriate ISR. In the other strategy, the +ISRs are written so that they examine their peripheral hardware and +only run if it is actually signalling an interrupt: in this strategy +the dispatcher calls all the ISRs bound to the real interrupt but +only one of them runs.

There is no requirement to extend the +implementation of Interrupt::Bind() and Interrupt::Unbind() where multiple interrupt sources are +involved.

Multiple interrupt sources require you to extend the +implementation of Interrupt::Enable() and Interrupt::Disable() to enable and disable the true interrupt +source.

The dispatch functions should be extended in the same +way as with chained interrupts, using one of the two techniques described +for that case.

The ISR table should be structured so that the +interrupt Id identifies the hardware controller the interrupt is on. +For instance the first 32 Ids might refer to the highest level controller, +the next 32 to a second level controller and so on.

+
Device +specific interrupts

Interrupts generated by peripherals +are sometimes routed to a GPIO pin and sometimes included in a variant +layer. Where they are part of the variant layer, they must be listed +in a separate ISR table which is part of the device implementation. +However, we want device drivers to be able to use the Interrupt class +functions on interrupts of either type. The solution is to write separate +device specific functions derived from those of the core class. Core +class functions are then written in such a way as to identify device +specific interrupts and pass them on to the derived functions.

A recommended way of labelling interrupts as being device specific +is to assign negative numbers as their Ids. The core functions can +then identify negative Ids as belonging to device specific interrupts +and pass them to the device specific derived functions. The device +specific functions can convert them to positive numbers which serve +as indexes to the device specific ISR table.

This example code +illustrates device specific interrupt handling.

EXPORT_C TInt Interrupt::Bind(TInt aId, TIsr aIsr, TAny* aPtr) + { + TInt r = KErrNone; + if(aId < 0 ) + { + return MyAsic->VariantBind( aId, aIsr, aPtr ); // Device specific ID, call variant + } + else if (aId >= KInterruptSourceCount) + { + r = KErrArgument; // Illegal interrupt number + } + else + { + SInterruptHandler& h = IsrHandlers[aId]; + TInt irq = NKern::DisableAllInterrupts(); + if (h.iIsr != SpuriousHandler) + { + r = KErrInUse; // Already bound to an ISR + } + else + { + h.iPtr = aPtr; + h.iIsr = anIsr; + } + NKern::RestoreInterrupts(irq); + } + return r; + } + +SInterruptHandler VariantHandlers[KNumVariantInts]; +EXPORT_C TInt TMyVariant::VariantBind(TInt aId, TIsr aIsr, TAny* aPtr) + { + TInt r = KErrNone; + aId = (-aId)-1; // convert to positive number >=0 + If (aId >= KInterruptSourceCount || aId < 0) + { + r = KErrArgument; // Illegal interrupt number + } + else + { + SInterruptHandler& h = VariantHandlers[aId]; + TInt irq = NKern::DisableAllInterrupts(); + if (h.iIsr != VariantSpuriousHandler) + { + r = KErrInUse; // Already bound to an ISR + } + else + { + h.iPtr = aPtr; + h.iIsr = anIsr; + } + NKern::RestoreInterrupts(irq); + } + return r; + }

The example provides a version of Interrupt::Bind() which calls a variant layer function VariantBind() to process device specific interrupts (here assigned negative Ids) +to ISRs held in the variant specific table VariantHandlers.

+
+Interrupt +Technology Guide +Interrupt +Client Interface Guide +
\ No newline at end of file