INTERRUPT HANDLING
This invention relates to handling interrupts in a data processing system.
Figure 1 illustrates a microprocessor 1 and an interrupt controller 2. Microprocessors have a limited number of hardware inputs for receiving interrupts, sometimes only one. When the microprocessor is implemented in a complex device there are often more sources of interrupts than the microprocessor itself can receive. To allow the microprocessor to handle more interrupts an interrupt controller can be connected to the microprocessor to expand the number of identifiable interrupt sources that are supported. This interrupt controller has multiple interrupt inputs 3 and an output 4 which is coupled to an interrupt input 5 of the microprocessor. When an interrupt is raised on one of the inputs to the interrupt controller the interrupt controller raises an interrupt on the microprocessor via input 5. When it comes to handle that interrupt the microprocessor can interrogate the interrupt controller to find which of the interrupt inputs 3 has been triggered.
Typical interrupt controllers provide between 8 and 32 inputs, but on modern systems such as desktop computers and smartphones it is not unusual to have over 100 possible sources of an interrupt. Therefore, for complex systems a single interrupt controller sometimes still does not provide enough interrupt inputs for all the sources of interrupts in the system. One way to address this is to chain together multiple interrupt controllers in a tree structure. This is illustrated in figure 2. In the configuration of figure 2 interrupt controller 10 provides an output to interrupt controller 11. Interrupt controller 12 receives inputs from interrupt controllers 11 and 13 and provides an interrupt output to the microprocessor 14. The interrupt controllers may be at multiple depths (as in the case of controllers 12, 11 and 10), and there may be multiple controllers at the same depth (as in the case of controllers 11 and 13).
In a practical implementation operations will need to be performed to configure the interrupt controller. Examples of these operations include:
• Binding a service function to an interrupt input
• Unbinding a service function from an interrupt input
• Enabling an interrupt input
• Disabling an interrupt input
• Configuring an interrupt input - such as edge/level, high/low triggering
• Acknowledging a pending interrupt input
• Configuring the priority of an interrupt input
Software running on the device may need to perform operations of this type on the interrupt controller. To allow applications to perform these operations it is normal for the device's operating system to provide an API (application programming interface) that allows applications to communicate with the interface controllers. The API needs to be specific to the interrupt controllers of the device since the calls that need to be made to a particular interrupt controller to cause it to carry out a desired operation will depend on how it is configured in the device and potentially also on the type of the interrupt controller.
If the number and the type of the interrupt controllers and how they are configured were known at the time when the operating system's APIs were coded then the operating system could provide all the APIs that are needed for configuring all the interrupt controllers in the system. However, it often happens that the details of the interrupt controllers are not known at the time the API implementation of the operating system is written. There are two common reasons for this:
1) The user might add new hardware expansion to the system (for example by adding PCI expansion cards on a desktop computer or add-on modules to a smartphone). These expansion cards may have one or more on-board interrupt controllers for handling multiple on-board interrupt sources. At the time the operating system is written it will not be known what expansion the end-user might want to add, and the expansion hardware might not even have been designed at the time the operating system is written.
2) The operating system may be designed for a "core" or "base" system to which it is intended that device manufacturers can add additional hardware of their own before devices are released to end-users. It might theoretically be possible for the manufacturer to modify the operating system that is shipped with the devices, but the manufacturer might not want to do so in case the manufacturer's modifications cause a conflict within the device. Also, for security reasons the manufacturer might not be allowed to make changes to the operating system.
As a result, it might be impossible to design the operating system to support all the interrupt controllers that might be added to the system.
A common way to solve this is to create separate APIs for any additional interrupt controllers or groups of additional interrupt controllers that cannot be controlled by the API provided by the operating system. This is inconvenient for a number of reasons. First, it means there are multiple APIs duplicating the same basic functionality, and so a developer who is writing software that is to run on the system needs to know which of the APIs to use when he wants to address each interrupt controller. Second, it often happens that the various APIs behave slightly differently, which causes confusion and introduces defects when a developer who is familiar with using one API writes code to use another.
There is therefore a need for an improved way of implementing APIs for configuring and accessing functions of interrupt controllers.
According to one aspect of the present invention there is provided a computing device having multiple interrupt controllers for receiving interrupt events, a processor and a memory storing instructions for execution by the processor to cause the processor to interact with interrupt events, the instructions being such as to implement an interface including: a first function that can be invoked by software running on the device for interacting with interrupts on two or more of the interrupt controllers; and a second function that can interact with interrupt events on a specific one of the interrupt controllers; the first function being such as to,
when it is invoked for interacting with an interrupt event on the specific one of the interrupt controllers, invoke the second function so as to cause it to interact with that event.
According to a second aspect of the present application there is provided software for execution by a computing device having multiple interrupt controllers for receiving interrupt events, a processor and a memory storing instructions for execution by the processor to cause the processor to interact with interrupt events, the software comprising instructions that are such as to implement an interface including: a first function that can be invoked by software running on the device for interacting with interrupts on two or more of the interrupt controllers; and a second function that can interact with interrupt events on a specific one of the interrupt controllers; the first function being such as to, when it is invoked for interacting with an interrupt event on the specific one of the interrupt controllers, invoke the second function so as to cause it to interact with that event.
According to a third aspect of the present application there is provided a method for servicing interrupts in a computing device having multiple interrupt controllers for receiving interrupt events, a processor and a memory storing instructions for execution by the processor to cause the processor to interact with interrupt events, the instructions being such as to implement an interface including: a first function that can be invoked by software running on the device for interacting with interrupts on two or more of the interrupt controllers; and a second function that can interact with interrupt events on a specific one of the interrupt controllers; the method comprising: invoking the first function for interacting with an interrupt event on the specific one of the interrupt controllers; and subsequently invoking the second function so as to cause it to interact with that event.
The interface may include a third function that can interact with interrupt events on another specific one of the interrupt controllers. Then when the first function has been invoked to interact with an interrupt event, it can be determined which one of
the second and third functions should be invoked to interact with that interrupt event.
Preferably the interface is capable of, whilst the device is in operation, being configured so as to respond to its being invoked for interacting with an interrupt event on one of the interrupt controllers by invoking the second function so as to cause it to interact with the event.
The interface may be such as to maintain a record of interrupts available in the device and an indication of the interrupts for which of it should invoke the second function to interact with events thereon. The record of interrupts may be in the form of a linked list that links each interrupt to a respective implementation mechanism, the implementation mechanisms including the invocation of the second function. The record of interrupts may be in the form of an array that links groups of one or more interrupts to a respective implementation mechanisms, the implementation mechanisms including the invocation of the second function. Each interrupt may be identified by an interrupt identifier. The interface may then be arranged to allocate the interrupt identifiers such that for each of the interrupts the bits in a common set of bit positions indicate the implementation mechanism to be invoked for that interrupt.
The first function may be a component of an application programming interface (API).
The interface may comprise a generic part that includes the first function and a configurable part that includes the second function. The second function may be an object that is an instance of a base class defined by the generic part of the interface.
The generic part may be defined by executable code none of which is configurable during operation of the device.
The device may have a first interrupt controller and a second interrupt controller. The second function may be capable of interacting with interrupt events on the first interrupt controller and the interface may include a third function capable of interacting with interrupt events on the second interrupt controller. The first interrupt controller may be arranged so as to, when a first interrupt event is raised on it, raise a second interrupt event on the second interrupt controller. The second function may be configured so as to, on being invoked to interact with that second interrupt event, cause the third function to be invoked to interact with that first interrupt event.
The interface may comprise a fourth function that simulates interaction with a dummy interrupt. The first function may be such as to, when it is invoked for interacting with an event on the dummy interrupt, invoke the fourth function so as to cause it simulate interaction with the dummy interrupt.
The present invention will now be described by way of example with reference to the accompanying drawings. In the drawings:
Figure 1 shows the configuration of a single interrupt controller;
Figure 2 shows the configuration of multiple interrupt controllers;
Figure 3 shows the logical architecture of a computing device; and
Figure 4 illustrates deriving an MlnterruptControllerBase object from a given interrupt ID.
In the system to be described below, the operating system provides an extendable API which is accessible to applications. That API is designed so that specific interface implementations for any added interrupt controllers can be plugged in to it. Application code can then use the single operating system API to access interrupts regardless of which interrupt controller the relevant interrupt input is connected to. This enables developers to use a common API for controlling interrupts on any interrupt controller.
The specific interface implementations provide implementations of some or all of the functionality offered by the operating system API, such as bind, unbind, enable, disable, etc. These implementations are configured to be appropriate to the respective interrupt controller that the interface implementation is to interact with. Each of these implementations is registered with the operating system API. The operating system API maintains a list of the implementations of the API for each interrupt controller and acts as a wrapper around them.
Each interrupt controller can be allocated a range of interrupt IDs that are unique to that controller. For any function called on the operating system API, the interrupt ID passed to it can uniquely identify the interrupt controller that it applies to. The operating system can use the interrupt ID to find the implementation for that interrupt controller and pass the request on to it. Even in an object-oriented environment the API effectively implements a function that can be called by an application or other software to handle an interrupt or otherwise with an interrupt event (whether one that has occurred or one that may occur in the future). That effective function can call other subsidiary effective functions in order to handle the interrupt. As will be described below, those functions could then call each other to echo chaining of interrupt controllers in hardware.
Figure 3 shows the logical architecture of a computing device 20, divided into hardware and software domains 21 and 22 respectively. The device comprises a central processor 23 which executes code that implements an operating system 25 for the device. The operating system 25 of the device is the software component that loads automatically when the device is turned on. It controls the access of applications that run on the device to the hardware of the device, including their access to the memory 26 of the device. The operating system is capable of preventing each application from accessing certain areas of the memory that might be reserved to the operating system or to other applications. In contrast, components of the operating system preferably have unrestricted access to any areas of the memory that are accessible by the processor.
The operating system supports applications 27 which run on the device. The device has a number of hardware components 28. The nature of the hardware components is not important but they could for example include data interfaces, video controllers, sound cards, headsets or wireless transceivers. The hardware components are coupled to one or more interrupt inputs of interrupt controllers 29 so as to be able to pass interrupts to the controllers. The interrupt controllers are configured so that each interrupt controller can pass an interrupt output to the processor 23 either directly or via one or more other interrupt controllers. The interrupt controllers could be arranged at multiple levels and with multiple controllers at each level. The operating system includes an API 30 that allows the applications 27 to interact with the interrupt controllers 28. The API 30 will be described in more detail below.
The applications 27, the operating system 25 and the objects 31 are defined by code 32 stored in memory 26. The software defining those components could be stored on any suitable data carrier and in any form: compiled, partly compiled or source code.
In the following description an example of a system according to the invention will be described with reference to C++ classes. The invention could equally be implemented using other object-oriented languages or using non-object-oriented languages like C or assembler.
API 30 is configured so as to serve as a standard API for use by all code, whether in applications 27 or in the operating system 25, that needs to control interrupts. An object 31 derived from an abstract base class provided by API 30, will be implemented for each interrupt controller 28 in the system. That abstract base class defines an API that can be implemented for each interrupt controller in order for it to operate with API 30. This type of hierarchy is in accordance with normal object-oriented conventions. API 30 will be referred to as Tlnterrupt. The abstract base class provided by API 30 will be referred to as MlnterruptControllerBase. The concrete implementations 31 of that class will each be referred to as
MlnterruptControllerBase objects. An MlnterruptControllerBase object is declared for every interrupt controller in the device that is to work with API 30. In figure 3 the MlnterruptControllerBase objects are shown as being external to the operating system but they could be part of the operating system.
It is intended that Tlnterrupt will be able to allow the applications to access the interrupt controllers via the appropriate MlnterruptControllerBase object. To allow it to do that it needs to have knowledge of each of those objects. For that purpose each of those objects is registered with the main Tlnterrupt API before it can be used. This conveniently happens when the device starts up, or when a routine is run to install newly-added hardware. For each interrupt controller the device's memory contains data that defines the operations required of a corresponding MlnterruptControllerBase object. That data could be built into the device, which is especially convenient if the interrupt controller is part of the standard hardware of the device, or could be stored in firmware or other reprogrammable memory. When the device configures itself for operation by automatic or manual detection and installation of attached hardware the interrupt controllers are detected and the appropriate MlnterruptControllerBase objects are set up through Tlnterrupt API.
The Tlnterrupt API maintains a list of the registered MlnterruptControllerBase objects. There are several ways to do this; each is a trade-off between performance, memory usage and convenience to the developer. Some examples of ways in which the list can be maintained are:
1) By dynamically creating a linked list. Each time a MlnterruptControllerBase object is registered the Tlnterrupt code allocates a linked-list item to store a pointer to the object, and these linked-list items are connected together into a singly- or doubly-linked list.
2) By embedding the linked-list pointers into the MlnterruptControllerBase object a linked list of these objects can be formed without having to allocate any more memory.
3) By creating an array of pointers which can be set to point to an MlnterruptControllerBase object. This limits the number of
MlnterruptControllerBase objects that can exist in the system, unless the array can grow dynamically, but can be more efficient to access than walking a linked-list.
Interrupt inputs are usually identified by a number, which will be referred to herein as an interrupt ID. Each interrupt controller is assigned a range of interrupt IDs that are unique to that controller. When an interrupt is raised the source of the interrupt is identified by the appropriate interrupt ID. That interrupt ID is made available to Tlnterrupt and can be read by an application that is to handle the interrupt. When the application issues a command in order to handle the interrupt it identifies the interrupt by its interrupt ID. That ID allows Tlnterrupt to determine which of the MlnterruptControllerBase objects is to be used to pass the command to the appropriate interrupt controller.
Various methods of allocating the interrupt ID are available. Some options are:
1) Statically define a range of interrupt IDs at the point when the operating system or the relevant part of it is compiled. In order for this to work successfully the operating system programmer needs to be able to rely on (a) the chosen range of IDs being unique and (b) no other object re-using the same IDs. This is not necessarily guaranteed when hardware can be added to the system after manufacture. However, if the hardware is not intended to be extendable after manufacture then this method can be used, and can be an efficient implementation because the interrupt IDs are fixed and can be defined in header files.
2) Dynamically assign a range of interrupt IDs. In this case the MlnterruptControllerBase object will indicate the number of interrupt IDs that it requires, and the Tlnterrupt implementation will assign a range of free IDs to that MlnterruptControllerBase object. This method will work even if the interrupt IDs are not known at compile time.
3) Hybrid schemes. These could use various combinations of static and dynamic allocation. Interrupt controllers that are known in advance, such as those that are a fixed part of the system, can be assigned static interrupt
IDs, and those that are added later can be assigned interrupt IDs dynamically.
A convenient way of implementing a hybrid scheme is to allocate a range of static IDs to interrupt controllers that are known at the time of writing the Tlnterrupt implementation, and to reserve another range for dynamic allocation of MlnterruptControllerBase objects. Those two ranges could conveniently be distinguished by a single bit of the interrupt ID number, e.g. by the sign of the interrupt ID when expressed as a signed integer. For instance, positive (>=0) IDs could signify the static range and negative (<0) IDs could signify the dynamic range.
Tlnterrupt could be configured to use a linked list structure to manage the allocation of interrupt IDs. In this implementation, when the
MlnterruptControllerBase object is being set up it declares to Tlnterrupt how many interrupt IDs it requires. Tlnterrupt then allocates exactly that number of IDs from a pool of free IDs, and then adds an entry corresponding to that MlnterruptControllerBase object to the linked-list. The linked-list entry (which could be the MlnterruptController object itself in an object-oriented implementation) includes sufficient information to identify the interrupt IDs allocated to that object: for example the start and end interrupt IDs allocated to that entry so that the list can be searched by interrupt ID. This implementation has the advantage that because MlnterruptControllerBase objects are stored as a linked-list there is freedom in the allocation of interrupt IDs. One disadvantage is that it may take longer to search the list to find the correct MlnterruptControllerBase object. Another disadvantage is that Tlnterrupt may need to perform operations to manage free IDs.
If MlnterruptControllerBase objects are never removed from the list (which would happen in a system whose hardware cannot be removed at runtime) then the free IDs can be allocated from a simple one-way-counter. Otherwise, the Tlnterrupt
object can maintain a pool of free interrupt IDs - for example by putting linked-list objects back onto a free list when an MlnterruptControllerBase object is deleted.
Another option is to use a form of granular dynamic allocation in conjunction with a linked list. This can be achieved in a similar way to the method described above, with the exception that interrupt IDs are allocated in fixed-size blocks instead of in completely free ranges. Each MlnterruptControllerBase object is allocated a range of IDs including a predetermined number of IDs. If a particular controller has fewer interrupt inputs than the assigned ID range the remaining IDs can be left unused. In the simplest form of this scheme an MlnterruptControllerBase object would never be permitted to represent more interrupt inputs than this fixed-size range. If an interrupt controller were to have more interrupt inputs than this defined range it could use multiple MlnterruptControllerBase objects to cover a large enough range of interrupt IDs. For example, it could be defined that an MlnterruptControllerBase object is not allowed to handle more than 128 interrupt inputs. New MlnterruptControllerBase objects will always be allocated a range of 128 interrupt IDs. If one such object only provides 8 interrupt inputs the remaining 120 IDs will be unused.
Using fixed-size ranges can improve the efficiency of allocating IDs and searching the linked-list. Because ranges are fixed-size, the ID allocation algorithm can use a simple bitmap of free and allocated ranges. As an example of this, consider a 32-bit word. Each bit in this word can be defined as representing a range of interrupt IDs, and have the value '0' when the range is free and T when the range has been assigned. Thus a 32-bit word can represent allocation of 32 interrupt ID ranges, and searching for a free range is as simple as searching for a bit in this word with value 1O'.
When such a block allocation scheme is in use, the extent of the range of IDs for each entry in the allocation table is known. A consequence of this is that when searching the linked list for the MlnterruptControllerBase object that handles a given interrupt ID, only the base interrupt ID of each entry needs to be compared.
It is convenient to implement the allocation scheme so that the base ID can be obtained from the given interrupt ID by a logical-AND operation. To allow this to be done, the lower Λ/ bits of each interrupt ID represent the interrupt number within the fixed-size range and the remaining upper bits represent the MlnterruptControllerBase object that is to be invoked to handle calls within that range. This allows any given interrupt ID to be AND-ed with NOT(2W-1) so as to give the base interrupt ID for the associated MlnterruptControllerBase object. A simple equality comparison can then be done when walking the linked list in order to find the appropriate object.
Interrupt IDs can be allocated statically. When interrupt IDs are allocated statically, all that is necessary is to create a linked-list item with the correct base interrupt ID and range. When the linked-list uses fixed-size ranges, the requested set of static IDs can be rounded down to the base of an available range, if they are not assumed to be already defined on range multiples. Static allocation will fail if there is a conflict between two requests for static ranges. This can either be assumed not to happen, or it can be tested for by checking the list for overlapping ranges and rejecting any request that conflicts. When using statically allocated interrupt IDs with a fixed-slot array, the requested interrupt ID range can define which slot in the array the MlnterruptControllerBase object will go into. If the base of the requested static ID range is not assumed to be a multiple of the range size, then it is first rounded down to the base of an available range.
Methods of allocation that do not employ linked lists are also possible. One example is fixed-slot allocation. In this method, pointers to
MlnterruptControllerBase objects are kept in an array. This array may be fixed- size or variable size. Items in the array will be referred to as slots. Each slot covers a fixed-size range of interrupt IDs. The range of IDs allocated to a MlnterruptControllerBase object is defined by which slot it occupies in the array. This mechanism allows for efficient lookup of the MlnterruptControllerBase object from the interrupt ID. The interrupt ID can be defined such that M bits are used to index the array entry and N bits are used to select the interrupt input within that
MlnterruptControllerBase object. The MlnterruptControllerBase object can be found from any given interrupt ID by simply using the M bits as the array index. This process is illustrated in figure 4. The upper example of figure 4 illustrates an MlnterruptControllerBase object 40 that occupies slot [2] of the array 41. The fact that the object occupies slot [2] means that the upper bits of the words that represent that object's interrupt IDs are binary 2. The individual interrupts within that range are represented by the remaining bits of the word. The lower example illustrates the mapping of an interrupt ID 42 on to an MlnterruptControllerBase object 43. The first M bits of the interrupt ID point to a slot in the array 41. The content of that slot represent the appropriate MlnterruptControllerBase object. The remaining bits of the interrupt ID represent the specific interrupt within that range that is being processed.
This method has the advantage that the process of identifying an MlnterruptControllerBase object from the interrupt ID is simple because the object is specified by a number that is the array index shifted left by a number of bits. When new MlnterruptControllerBase objects come to be allocated there is no need for additional structures to keep track of unallocated interrupt ID ranges. All that is necessary is to search the array for an empty slot, defined by an illegal pointer value. That value is conventionally NULL but any illegal value is suitable. Although searching the list may occupy time, adding interrupt controllers is generally not a time-critical operation so the array search is acceptable.
The dispatching of API calls using Tlnterrupt API will now be discussed.
When an application wishes to access an interrupt controller it calls the desired function in the Tlnterrupt API. A parameter passed with the function call specifies the ID of the interrupt that the call is to operate on. Thus, the call to enable interrupt 136 could for example be: enable ( 136 )
The code making the call does not need to know which interrupt controller handles the specified interrupt, and the parameters to the call do not need to
mention the relevant interrupt controller. The Tlnterrupt API will work out, as a result of whichever allocation scheme for interrupt IDs has been used, which interrupt controller to pass the call to.
Using one of the techniques described above or another technique Tlnterrupt API identifies which one of the MlnterruptControllerBase objects is responsible for handling the specified interrupt ID. Tlnterrupt then calls that object, specifying the interrupt ID or at least the necessary bits of it, in order to have that object carry out the desired operation. If no object is found matching the interrupt ID the Tlnterrupt API can either return an error, or treat this as a fatal exception. It is convenient for the functions in the MlnterruptControllerBase objects to directly correspond to equivalent functions in the Tlnterrupt API. This allows that when a particular function of Tlnterrupt is called, it can simply call the corresponding function in the appropriate MlnterruptControllerBase object.
Tlnterrupt API may distinguish between a core set of interrupts that can be handled by a standard function (which could potentially be a part of Tinterrupt API itself) an extension set of interrupts that are handled by MlnterruptControllerBase objects. In this implementation, when an interrupt function is called, Tlnterrupt API checks whether the interrupt ID is in the set of the core interrupts. If so, it causes it to be handled by its own interrupt handling functions. If not, it will pass it to the appropriate MlnterruptControllerBase object. In a hardware-extensible device that can be sold in an ASSP (Application Specific Standard Product) form, the standard function could conveniently be capable of servicing all the interrupts of the ASSP form.
Some coded examples of the components described above will now be given. The examples are self-consistent for ease of understanding. The examples are written in C++ but any suitable language could be used.
The following is an example of the class Tlnterrupt. As described above, the Tlnterrupt class defines the static API through which all code manipulates interrupts.
class Tlnterrupt
{ public:
// Bind function to interrupt id static int Bind( int id, TCallbackFn function );
// Unbind an interrupt id static int Unbind ( int id ) ;
// Enable and disable an interrupt static int Enable ( int id ) ; static int Disable ( int id ) ;
private : friend class MInterruptControllerBase;
// Register an MInterruptControllerBase object static int Register ( MInterruptControllerBase& controller ) ;
} ;
The following is an example of the MInterruptControllerBase class. This class defines the API through which Tlnterrupt dispatches requests to the handlers for a particular interrupt controller. In practice, each interrupt controller will have an implementation of this API derived from MInterruptControllerBase.
class MInterruptControllerBase
{ public :
// Bind function to interrupt id virtual int Bind( int id, TCallbackFn function ) = 0 ;
// Unbind an interrupt id virtual int Unbind ( int id ) = 0 ;
// Enable and disable an interrupt virtual int Enable ( int id ) = 0 ; virtual int Disable ( int id ) = 0;
protected:
// Called by derived implementation to register this object inline int Register ()
{ return TInterrupt :: Register ( *this ) ; }
private :
// Internal members manipulated by TInterrupt friend class TInterrupt;
// Linked list pointer MInterruptControllerBase* next;
// ID range start and end int id_base,- int id_top,- // not required for fixed-range allocation };
The following code will find which MlnterruptControllerBase object handles a given interrupt ID if the system is using the fixed-range linked list allocation mechanism.
// Given an interrupt id this function finds the
MlnterruptControllerBase
// object which handles it
MlnterruptControllerBase* Findθbject( int id )
{
MlnterruptControllerBase* entry = iLinkedListHead;
int range_base = id & -KRangeMask; while ( entry )
{ if ( range_base == entry- >id_base )
{ return entry;
} entry = entry- >next ;
} return NULL;
}
The following code will find which MlnterruptControllerBase object handles a given interrupt ID if the system is using the fixed-slot array allocation mechanism.
// Given an interrupt id this function finds the
MlnterruptControllerBase
// object which handles it
MInterruptControllerBase* FindObj ect ( int id )
{ int index = id >> KBitsInRangePart; return iArray [ index ] ; }
The EnableO function identifies which interrupt controller to call and then dispatches the call to the appropriate MInterruptControllerBase implementation.
int TInterrupt : : Enable ( int id )
{
MInterruptControllerBase* obj = Findθbject( id ) ; if( obj )
{ return obj ->Enable ( id - obj->id_base );
} else
{ return NOT_FOUND;
} }
The Register() function adds entries corresponding to implementations of MInterruptControllerBase to the appropriate data structure when a new MInterruptControllerBase object is registered. The function can be implemented in various ways depending on how the data structure is defined.
A first example of the Register() function adds static interrupt controller entries to a fixed-range linked list.
int TInterrupt: : Register ( MInterruptControllerBase& controller )
{
// Requested static start_id must be in controller . id_base
// Check for conflict if ( Findθbject( controller . id_base ) )
{ return ALREADY_EXISTS ;
} controller .next = iLinkedListHead; iLinkedListHead = &controller; return SUCCESS;
}
A second example of the Register() function adds dynamic interrupt controller entries to a fixed-range linked list.
int TInterrupt :: Register ( MInterruptControllerBase& controller )
{
// Call a function to find a free range of IDs
// this example ignores possibility of no free ranges int range_base = FindFreeRange ( ) ; controller .next = iLinkedListHead; iLinkedListHead = &controller; controller . id_base = range_base; return SUCCESS;
}
A third example of the Register() function adds static interrupt controller entries to a fixed-slot array.
int TInterrupt: : Register ( MInterruptControllerBase& controller )
{
// Requested static start_id must be in controller. id_base
int index = controller . id_base >> KBitsInRangePart; if ( iArray [ index ] != NULL )
{ return ALREADY_EXISTS;
} else
{ iArray [ index ] = &controller; return SUCCESS; }
A fourth example of the Register() function adds dynamic interrupt controller entries to a fixed-slot array.
int TInterrupt :: Register ( MInterruptControllerBase& controller )
{ for ( int index = 0; index < KMaxIndex; ++index )
{ if ( iArray [ index ] == NULL ) break;
}
// this example ignores possibility of no free slots
controller . id_base = index << KBitsInRangePart ; iArray [ index ] = &controller; return SUCCESS; }
If the system is to be implemented in a language which does not offer built-in support for object orientation and classes, the MlnterruptControllerBase object can be implemented as a definition of a jump table. Such a jump table could, for example, be implemented in C in the following manner:
typedef MlnterruptControllerBase ; typedef struct
{ int (*bind) ( int id, Callback* function ) ; int (*enable) ( int id ) ; int (*disable) ( int id ) ;
int id_base;
MlnterruptControllerBase* next; } MlnterruptControllerBase;
In this implementation, each version of MlnterruptControllerBase will provide a memory area conforming to this structure, filled in with pointers to the functions of the corresponding interrupt controller. This memory area will be passed to the Tlnterrupt API when registering the implementation.
This simplest way of dispatching interrupts in a system that has multiple interrupt controllers is for the interrupt dispatch functions for each interrupt controller to chain in exactly the same way as the hardware chains. There is no need for any
lookup of which interrupt controller implements a particular interrupt during the dispatch phase. Thus, if the interrupt controllers are chained at a hardware level, the MlnterruptControllerBase objects can be chained in the same topology.
As an example, the present API could be used to support interrupt calls in a system whose hardware is configured in the manner shown in Figure 2. In that system, interrupt controller B is connected to interrupt input 2 of interrupt controller A. This means that when there is a pending interrupt on Controller B1 it will raise an INT2 interrupt on controller A. To echo that in the context of the present API the dispatcher function of controller B can be bound directly to the dispatcher function of controller A's INT2. This might be done using the following call:
TInterrupt: :Bind (C0NTR0LLER_A_INT2_ID, ControllerBDispatch );
Then, when the interrupt dispatch stage for Controller A processes the pending INT2, it will just call ControllerBDispatch() without any need to know what that function represents. ControllerBDispatch() then processes pending interrupts on interrupt controller B.
If static interrupt IDs are used, they can be defined in a header file and compiled into code as a constant. This is not possible with dynamic IDs. For those the actual ID can be looked up at runtime using a mechanism for translating from a description of the desired interrupt to its ID. One example of such a mechanism is to use a database of available hardware on the system, constructed dynamically as hardware is identified and interrupt IDs are assigned. This might for example identify devices by a type and number. Such a table might look like this:
Type number interrupt ID
UART 0 0
UART 1 1
CLOCK 0 32
USB 0 64
USB 1 65
IDE 0 66
IDE 1 67
Drivers can search the table for the peripheral and interrupt source they handle to find an interrupt ID.
The mechanism described above can also be used to provide dummy interrupts on a device. A component of the device such as its operating system kernel can install a plug-in dispatcher of the type described above to provide dummy interrupts which do not correspond to any hardware and therefore will never generate an interrupt request. This can make it easier for the device to cope with applications that try to access hardware that does not actually exist on the device. If an application attempts to access a function on an interrupt that does not exist on the device and that has been implemented as a dummy interrupt then that access attempt will be directed to the dummy interrupt. Software could handle any access to the dummy interrupt, for example by reporting that the access has been successful even though the interrupt does not exist at the hardware level. This is useful for devices that may be implemented with different levels of hardware. The same device drivers can be present on each device, irrespective of its hardware capabilities. Those drivers that correspond to non-existent hardware will be able to configure themselves as normal, even though that hardware is not present, and will then just lie dormant since they will never be woken by an interrupt.
The present mechanism of interrupt handling can be implemented on a wide range of devices. Examples include desktop and laptop computers, personal digital assistants (PDAs), mobile telephones, smartphones, digital cameras and digital music players and converged devices incorporating the functionality of one or more of the classes of device already mentioned, as well as on many other
industrial and domestic electronic appliances. The device is preferably portable. The device preferably has a self-contained power source such as a battery or a fuel cell that can power the device when it is operating to exchange data.
Many interrupt controllers accept a similar set of commands for interacting with interrupts; including, for example, bind, unbind, enable and disable. To allow MlnterruptControllerBase to be compatible with interrupt controllers that accept different or additional commands MlnterruptControllerBase could include a generic function that can be extended as needed to allow it to interact with such controllers. If a controller uses non-standard functions to implement standard actions such as bind then the mapping from (e.g.) bind to that non-standard function can be done in the appropriate MlnterruptControllerBase object. That makes the mapping transparent to the application programmer.
The applicant hereby discloses in isolation each individual feature described herein and any combination of two or more such features, to the extent that such features or combinations are capable of being carried out based on the present specification as a whole in light of the common general knowledge of a person skilled in the art, irrespective of whether such features or combinations of features solve any problems disclosed herein, and without limitation to the scope of the claims. The applicant indicates that aspects of the present invention may consist of any such feature or combination of features. In view of the foregoing description it will be evident to a person skilled in the art that various modifications may be made within the scope of the invention.