Level Triggered Interrupt Handling and Nested Interrupts

Level Triggered Interrupt handling and nested interrupts

Q1. Would the interrupt be raised again from Interrupt Controller to Core?

Of course, it will be re-raised. This is a property of the level triggered interrupt. There is no state in the interrupt controller. It would have difficulty to tell if the interrupt has been re-raised or if it persisted. Especially, the interrupt may have been serviced for a very small time and the GIC would not see the high-low-high transition to tell the difference between a new and an existing interrupt source.

Q2. If after (e) core directly does (g), will the interrupt be raised again from Interrupt Controller to core

This seems identical to the above question. There maybe a level triggered device where servicing the device leaves the interrupt line high. For example, an interrupt might be FIFO not empty. If the FIFO has two entries, the first read may not clear the interrupt.

See level triggered interrupts at Wikipedia. After servicing this device.... You must always service the device with level triggered interrupts. The interrupt controller (GIC) has no idea how a peripheral works. Putting assumptions in the controller will limit its use.

Now how would interrupt nesting work in this case.

It is not clear what is nesting. For instance with the FIFO example above, you can read the device for the number of entries or read and check the interrupt status after each read. When a read clears the interrupt, it is fine to re-anble the interrupt source.

Nesting of separate IRQ sources is standard. At step f, the IRQ service routine must service the device until the level is not driven. The irqActive bits at 0x300-0x304 can be read to determine if the IRQ service is finished. Then the level triggered ISR returns. If it is pre-empted at any point, the controller will detect an new level source, or the ISR will continue servicing the peripheral.

  • Device raises line informs GIC.
  • GIC signals ARM core and jumps to vector.
  • Vector reads GIC interrupt ACK and jumps to ISR.
  • Level routine disables the level IRQ and re-enables interrupts.
  • Level routine services device until irqActive low. (may pre-empt to other ISRs here).
  • Mask interrupts, re-enable level source, and return to caller.

If in the last step (or just before) an additional service item happens, there will be a back-to-back level interrupt. This will be in-frequent as multiple interrupt sources will have to happen during the same time period. This is typical of interrupt nesting. The overall system will be busier, but latency will be better.

Section 3.2.1 Priority drop and interrupt deactivation has the following steps to disable the level interrupt,

  1. read IAR - initial read of active interrupt.
  2. write EOIR - drop it from the priority; allow nesting of lower priority.
  3. write DIR - say it has ended (or serviced).

When the actual device has been determined to have been serviced, the interrupt is re-enabled. If you wish to allow only higher priority interrupts, then the write to EOIR would be delayed until the end of the ISR; the higher priority interrupts will naturally pre-empt the level interrupt.

Edit:

Now there can be two cases here.

A. Core masks the particular interrupt on GIC, but does nothing on the device which will clear the interrupt on device. Core enables its interrupts

If the interrupt is masked, it will not re-assert.

B. Core sets GICC_EOImode =1, and writes interrupt id to EOIR. Core enables its interrupts

Writing the EOIR will transition from active+pending to just active and the interrupt will refire (if all you are doing is 'B').

In interrupt nesting, Linux naturally does the first part of the picture. When there are two active ISR (right side), this is an optional configuration; during 'IRQ-k', interrupts must be re-enabled. It takes more stack to do this and you will have to modify the stock Linux afaik.

Edit2: The GICC_CTRL.EOImode =1 is confusing. This separates the interrupt serviced from the priority drop portion. If you have an interrupt with a critical portion and a non-critical, you can separate the phases. Write to EOIR after the critical portion to drop the priority. Then the DIR register says that the interrupt service is finished. I would always leave GICC_CTRL.EOImode=0 as I don't think this is needed. The manual documentation is written from the perspective of the interrupt controller and not a CPU using it (and hence a programmer's mental model); deactivated means the current IRQ line and not the interrupt in general.

What happens when an ISR is running and another interrupt happens?

Normally, an interrupt service routine proceeds until it is complete without being interrupted itself in most of the systems. However, If we have a larger system, where several devices may interrupt the microprocessor, a priority problem may arise.

If you set the interrupt enable flag within the current interrupt as well, then you can allow further interrupts that are higher priority than the one being executed. This "interrupt of an interrupt" is called a nested interrupt. It is handled by stopping execution of the original service routine and storing another sequence of registers on the stack. This is similar to nested subroutines. Because of the automatic decrementing of the stack pointer by each interrupt and subsequent incrementing by the RETURN instruction, the first interrupt service routine is resumed after the second interrupt is completed, and the interrupts are serviced in the proper order. Interrupts can be nested to any depth, limited only by the amount of memory available for the stack.

For example, In the following diagram, Thread A is running. Interrupt IRQx causes interrupt handler Intx to run, which is preempted by IRQy and its handler Inty. Inty returns an event causing Thread B to run; Intx returns an event causing Thread C to run.

Sample Image
Image Ref

For hardware interrupts, Priority Interrupt Controller Chips (PIC's) are hardware chips designed to make the task of a device presenting its own address to the CPU simple. The PIC also assesses the priority of the devices connected to it. Modern PIC's can also be programmed to prevent the generation of interrupts which are lower than a desired level.

UPDATE: How Nested Interrupt Works on Atmel AVRs

The AVR hardware clears the global interrupt flag in SREG before entering an interrupt vector. Therefore, normally interrupts remain disabled inside the handler until the handler exits, where the RETI instruction (that is emitted by the compiler as part of the normal function epilogue for an interrupt handler) will eventually re-enable further interrupts. For that reason, interrupt handlers normally do not nest. For most interrupt handlers, this is the desired behaviour, for some it is even required in order to prevent infinitely recursive interrupts (like UART interrupts, or level-triggered external interrupts).

In rare circumstances though nested interrupts might be desired by re-enabling the global interrupt flag as early as possible in the interrupt handler, in order to not defer any other interrupt more than absolutely needed. This could be done using an sei() instruction right at the beginning of the interrupt handler, but this still leaves few instructions inside the compiler-generated function prologue to run with global interrupts disabled. The compiler can be instructed to insert an SEI instruction right at the beginning of an interrupt handler by declaring the handler the following way:

ISR(XXX_vect, ISR_NOBLOCK)
{
...
}

where XXX_vect is the name of a valid interrupt vector for the MCU type.

Also, have a look at this Application Note for more info on interrupts on Atmel AVRs.

ARM 7 nested interrupts confusion

ARM7 would most likely have been used with a vectored interrupt controller - a component tightly coupled to the core, but not as tightly coupled as the modern interrupt controllers used or integrated with either Cortex-M (ARMv6-M, ARMv7-M and ARMv8-M) - which are integrated into the exception model, or the A-class interrupt controllers.

This older type of vectored interrupt controller would provide multiple inputs, with masking, and priority. It would raise an IRQ input to the core, and provide an address which the core could read as part of the common interrupt handler - this then branched to the exception specific handler. Note that at the architectural level, there would just be one IRQ interrupt - containing a hardware assisted jump table.

ARM7 also allowed a simpler interrupt architecture - the exact implementation being customised for the application. Most trivial (and maybe uncommon) would be to OR all of the interrupts together and require the interrupt handler to read the interrupt status of each peripheral to discover what is pending. This may seem very crude today, but it is still done to some extent - you might dedicate Rx and Tx interrupts for each I/O peripheral, yet combine all the error/overflow interrups for everything (since any of these mean game-over).

The trade-off between software and dedicated hardware handling of features is one example of how changing implementation costs can influence design decisions over time.

the lowest interrupt priority is ignored by the kernel and is not processed

how write in arm documentation (mask priority) my 0xF0 priority is "idle priority" and he is masked always

Do interrupts interrupt other interrupts on Arduino?

On Arduino (aka AVR) hardware, nested interrupts don't happen unless you intentionally create the conditions to allow it to happen.

From avr-lib:

The AVR hardware clears the global interrupt flag in SREG before entering an interrupt vector. Thus, normally interrupts will remain disabled inside the handler until the handler exits, where the RETI instruction (that is emitted by the compiler as part of the normal function epilogue for an interrupt handler) will eventually re-enable further interrupts. For that reason, interrupt handlers normally do not nest. For most interrupt handlers, this is the desired behaviour, for some it is even required in order to prevent infinitely recursive interrupts (like UART interrupts, or level-triggered external interrupts). In rare circumstances though it might be desired to re-enable the global interrupt flag as early as possible in the interrupt handler, in order to not defer any other interrupt more than absolutely needed. This could be done using an sei() instruction right at the beginning of the interrupt handler, but this still leaves few instructions inside the compiler-generated function prologue to run with global interrupts disabled.

(source: http://linux.die.net/man/3/avr_interrupts )



Related Topics



Leave a reply



Submit