This page is available in: en

HOWTO: Assembly Language Interrupt Handlers

Written by: Mohit Sindhwani, Viometrix

In a previous article, I had written about defining your own high-level interrupt handler from a T-Kernel application/ middleware. In this brief ‘how-to’ article, we look at some of the concepts in creating a low-level interrupt handler for the T-Kernel.

Background

In many embedded systems, interrupts are used to interface with the real world. When an event happens, the peripheral responsible for dealing with it raises an interrupt to the CPU. The CPU then stops doing what it’s doing, saves some of its essential context and attends to the interrupt. The routine that deals with the interrupt is called the interrupt handler (or interrupt service routine) and is supposed to quickly do interrupt-related processing. After this is completed, the CPU restores the context of the previously running program and execution continues as though nothing happened. The specifics of how to install and use interrupt handlers is usually dependent on the CPU and/ or the RTOS.

The T-Kernel allows developers to create two types of interrupt handlers: handlers written in assembly or handlers written in a high-level language. This article focuses primarily on assembly language handlers. Details for high-level language handlers are available in the T-Kernel specification and in our other article.

Essential Reading: As before, the main reference for this is actually Section 4.8 – Interrupt Management Function of the T-Kernel specification. Refer to that section for more details.

Coding the Interrupt Handler

Similar to high-level interrupt handlers, a low-level interrupt handler must do at least the same sets of things:

  1. Read/ write data from/ to the peripheral: This is the main task that needs to be done to respond to the interrupt.
  2. Remove the interrupt request: In devices such as the UART, a read interrupt request is automatically removed when you read from the device. However, in some devices such as timers, it is necessary to write to a special flag that clears the interrupt request.
  3. Inform the application that the interrupt occurred: In many cases, you will need to communicate to the running application that the event has happened. Typically, this is done by issuing a system call. To see the system calls that are possible from the ISR, check Section 3.2.2 of the T-Kernel specification.
  4. Return from the exception: Finally, you need to return from the service handler.

The main points of difference between an assembly interrupt handler and high-level interrupt handler are:

  1. Entry into the Handler
  2. Return from the exception

Entry into the Handler

In the case of a high-level ISR, the T-Kernel carries out a number of processes before the high-level ISR function is called. Most importantly, this involves processes related to saving context so that the ISR can do its processing without causing problems to the running task.

A low-level interrupt handler is started either by the CPU directly in response to the interrupt or by a support function in the T-Monitor. Depending on the CPU architecture, some CPU state may have been saved before the interrupt is invoked. The remaining state has to be saved by your low-level ISR when it starts. The details of this are highly dependent on the CPU architecture and the specific implementation of the T-Monitor for that architecture. You will need to refer to the Implementation Specification of the T-Monitor for the T-Engine and the Architecture Manual for the CPU to get a better understanding of this aspect.

Return from the Exception

A high-level ISR can issue a return from the exception/ interrupt simply by executing a C-style return from function. In the case of an assembly ISR, the ISR is responsible for more things – it must properly return from the exception and also restore the context. Further, if interrupts are enabled when a low-level ISR is run, there is a chance that dispatching may be required when returning from a low-level ISR. Therefore, returning from a low-level ISR requires a special process.

The T-Kernel requires every port to provide a system call to support a return from interrupt. This system call is called tk_ret_int and must be called at the end of a low-level interrupt handler. It ensures that the necessary processing for possible dispatching is carried out when it returns from the ISR.

Again, these processes are highly dependent on the CPU and the T-Monitor architecture. The Implementation Specification for the port should outline the specific process for creating a low-level ISR.

Template for Low-level ISRs

The main flow of a low-level ISR is as follows:

  1. Save the context of the CPU
  2. Read/ write data from/ to the peripheral
  3. Inform the application that the interrupt has occurred
  4. Return from the exception by calling tk_ret_int

Specifically in this case, the details are system dependent – so please refer to the following sources:

  • CPU Port Implementation Specification
  • T-Kernel Specification Section 4.8
  • CPU Architecture Manual

Examples of Low-level ISRs

The main example of a low-level ISR is the timer service routine that runs at every system tick. The source code for this function can be found in each of the CPU ports of the T-Kernel. If you have access to the source (or if you download it from the T-Engine Forum site), you can find the relevant function and use that as a basic template of a low-level ISR for your CPU.

Registering/ defining a Low Level ISR

This step is similar to that for a high-level ISR. Once you have written the function for the interrupt handler, you need to register it with the T-Kernel so that it is called when the interrupt happens the next time. You register an interrupt handler by calling tk_def_int.

The C language interface for tk_def_int is as shown below:

ER ercd = tk_def_int (UINT dintno, T_DINT *pk_dint ) ;

The first parameter dintno is the interrupt number. Usually, this is the interrupt vector number – the specific meaning of this number is implementation dependent and can be found in the Implementation Specifications that came with your T-Kernel port.

Other than this, all you need to do is fill out the details of the T_DINT structure to include the following information:

  • The type of handler: high level or assembly (specified using TA_HLANG or TA_ASM respectively) – we specify TA_ASM in this case
  • The address of the handler function

The TA_ASM specifies that the interrupt handler being registered is an assembly language interrupt handler. The code for defining the interrupt handler is shown below. You need to call tk_def_int and pass it the interrupt number and the address of the T_DINT structure.


{
    T_DINT int_pkt;
    ER ercd;
    int_pkt.intatr = TA_ASM;
    int_pkt.inthdr = tk_my_isr;
    ercd = tk_def_int (SERIAL_RX_ISR, &int_pkt);
}

That’s all there is to it! Once this is done, the T-Kernel will now call your specific function when the interrupt occurs.

If you want to remove the definition that you have made, you need to call tk_def_int and pass it the interrupt number (usually the interrupt vector number) and NULL as the address of the T_DINT structure.