Category Archives: Interrupt

Interrupt(8):callout for cascaded interrupts

SDMA interrupt entry is added in init_intrinfo().

 Identify SDMA interrupt source.
 * Returns interrupt number in r4
 * -----------------------------------------------------------------------
CALLOUT_START(interrupt_id_omap4_sdma, 0, interrupt_patch_sdma)
	 * Get the interrupt controller base address (patched)
	mov		ip,     #0x000000ff
	orr		ip, ip, #0x0000ff00
	orr		ip, ip, #0x00ff0000
	orr		ip, ip, #0xff000000

	 * Read Interrupt Mask and Status
	ldr		r3, [ip, #SDMA_IRQSTATUS]		// Status
	ldr		r2, [ip, #SDMA_IRQENABLE]		// Mask
	and		r3, r3, r2

	 * Scan for first set bit
#if 0
	mov		r4, #32
	mov		r1, #1

	subs	r4, r4, #1
	blt		1f
	tst		r3, r1, lsl r4
	beq		0b
	clz		r4, r3
	rsbs	r4, r4, #31
	blt		1f
	mov		r1, #1
	 * Mask the interrupt source
	mov		r1, r1, lsl r4
	bic		r2, r2, r1
	str		r2, [ip, #SDMA_IRQENABLE]
	ldr		r2, [ip, #SDMA_IRQENABLE]

	 * Clear interrupt status
	 * 09.17.2014: clearing the staus bit is moved to the eoi-callout since the status bit related
	 * to a channel can only be claered if the channel status register of the associated
	 * channel is cleared. Clearing the csr can't be done in a generic way here because the attached
	 * isterrupt service routines need to know the interrupt reason (block, fram, drop etc. ...)
	//str		r1, [ip, #SDMA_IRQSTATUS]

 * -----------------------------------------------------------------------
 * Acknowledge specified SDMA interrupt
 * On entry:
 *	r4 contains the interrupt number
 *	r7 contains the interrupt mask count
 * -----------------------------------------------------------------------
CALLOUT_START(interrupt_eoi_omap4_sdma, 0, interrupt_patch_sdma)
	 * Get the interrupt controller base address (patched)
	mov		ip,     #0x000000ff
	orr		ip, ip, #0x0000ff00
	orr		ip, ip, #0x00ff0000
	orr		ip, ip, #0xff000000

     * Only unmask interrupt if mask count is zero
	teq		r7, #0
	bne		0f
	 * Clear interrupt status
	 * see comment in the id-callout
	mov		r2, #1
	mov		r2, r2, lsl r4
	str		r2, [ip, #SDMA_IRQSTATUS]

	ldr		r1, [ip, #SDMA_IRQENABLE]
	orr		r1, r1, r2
	str		r1, [ip, #SDMA_IRQENABLE]


Interrupt(7): Init & Attach/Detach in OS

All the interrupt information is generated in startup, store in syspage, then gets copied to one area.

syspage_init() gets the pointers to each syspage entry


	qtimeptr = SYSPAGE_ENTRY(qtime);

	intrinfoptr = SYSPAGE_ENTRY(intrinfo);
	intrinfo_num = _syspage_ptr->intrinfo.entry_size / sizeof(*intrinfoptr);

	calloutptr = SYSPAGE_ENTRY(callout);



  1. initializes some interrupt variables based on syspage intrinfo: here we use  intrinfo initialized here as an example:
    • num_external_level =  (32 + 192) + 32 = 256  ==>the sum of all iip->num_vectors
    • interrupt_level[] is an array of struct interrupt_level, the size is: 256+ NUM_HOOK_RTNS.
      • then let interrupt_level points at first external level: interrupt_level += NUM_HOOK_RTNS;
      • ilp: each interrupt entry (GIC, SDMA, GPIO, etc) in syspage
        • ilp->level_base = vector base of this entry;
        • ilp->info = the pointer to the start of the interrupt entry
        • for regular interrupt entry,
          • ilp->mask_count = 1;
          • ilp->cascade_level = -1
        • for cascaded entry,
          • ilp->cascade_level = vector base of this entry;
          • ilp->info->unmask()
          • ilp->mask_count = 0;
  2. write the interrupt entries to the processor/CPU vector table: see cpu_interrupt_init() for ARM

 interrupt_attach(level, (handler), …). there is a global variable “count” in this function:

  • find the interrupt_level: ilp = &interrupt_level[level];
  • count the number of existing interrupt entries which have attached to this level/IRQ;
  • allocate an interrupt entry “itp” by calling object_alloc().
    • itp->thread = current thread;
    • itp->level = level; // IRQ number
    • itp->handler = handler
  • add itp to the interrupt vector: id= vector_add(&interrupt_vector, itp, 0);
    • itp->id = id;
  • add itp to the table of existing interrupt entries for that level.
  • if count == 0 (the first handler gets installed), call interrupt_unmask(level, NULL).

interrupt_detach(level, handler)

  • remove the entry from the interrupt vector: itp = vector_rem();
  • if there are no more interrupt entries attached to this interrupt_level[level],
    • disable the interrupt: interrupt_mask();
    • interrupt_level[level].mask_count = 1;
    • itp->mask_count = 0;
  • if there is still interrupt enties for this level, set need_maskcount_check = 1.
  • remove interrpt handler from interrupt table.

interupt_unmask() —> unmask() callout

Kernel calls

InterruptAttach(): Attach an interrupt handler to an IRQ. This call automatically enable (unmask) the interrupt.

  • The first process to attach to an interrupt unmasks the interrupt. When the last process detaches from an interrupt, the system masks it.
  • If the thread that attached the interrupt handler terminates without detaching the handler, the kernel does it automatically — different behaviorNTO_INTR_FLAGS_PROCESS is set!
  • Processor interrupts are enabled during the execution of the handler. Don’t attempt to talk to the interrupt controller chip. The operating system issues the end-of-interrupt command to the chip after processing all handlers at a given level.

  • a few flags:
      • for shared interrupt.
      • adds the handler at the end of any existing handlers (default is to add in front of existing handlers).
      • associates the interrupt handler with the process instead of the attaching thread.
      • The interrupt handler is removed when the process exits, instead of when the attaching thread exits.
      • _NTO_INTR_FLAGS_TRK_MSK flag and the id argument to InterruptMask() and InterruptUnmask() let the kernel track the number of times a particular interrupt handler or event has been masked. Then, when an application detaches from the interrupt, the kernel can perform the proper number of unmasks to ensure that the interrupt functions normally. This is important for shared interrupt levels.

Note: in my libraries, all the interrupts are attached using “_NTO_INTR_FLAGS_PROCESS” and “_NTO_INTR_FLAGS_TRK_MSK” flag.


  • These kernel calls detach the interrupt handler specified by the id argument. If, after detaching, no thread is attached to the interrupt, then the interrupt is masked off.The thread that detaches the interrupt handler must be in the same process as the thread that attached it.

Interrupt(6): interrupt handling in OS

When an interrupt occurs,
  • Interrupt controller asserts one signal to the processor.
  • the CPU jumps to the vector table, looking for the handler for that type of exception.
  • the handler (pieces of assembly code, generated by cpu_init_interrupt())
    • intr_entry_start          // services/system/ker/arm/kernel.S
      • save registers, branch to id()
    • id()
      • acknowledge the GIC by reading the acknowledge register.
      • mask/disable the interrupt ID in GIC.
      • return interrupt number in r4.
    • if it is cascaded interrupt, branch to cascaded interrupt id()
      • find the interrupt source by reading registers
      • mask/disable the interrupt source in the controller
      • return the interrupt ID in r4
    • intr_process_queue
      • dispatch the interrupt to the appropriate handlers, — branch to interrupt().
      • This passes the INTRLEVEL in r8.
    • interrupt(): defined in services/system/ker/arm/interrupt.c
      • interruptEnable(); what’s the purpose of doing this?
      • find ilp->queue, and look for the handler for each entry in that queue
        • if there is a handler, execute it;
          • if the event returned by the handler is not NULL,  call intrevent_add();
        • if there is no handler,
          • interrrupt_mask(isr->level, isr);
          • intrevent_add();
      • InterruptDisable();
    • if it is cascaded interrupt, and if it is necessary (another interrupt ID), call id() callout again ?????
    • eoi()
    • intr_done
      • return from interrupt handling.


  1. the input of eoi is a interrupt id. so it is only possible to unmask one interrupt.
  2. mask_count is for a particular Interrupt Level (IRQ)
    • The mask count increases for every interrupt handler you have attached to the same vector. So when you attach an interrupt handler the Kernel will call the unmask callout (to enable the interrupt vector) and increment the mask count. When the interrupt goes off the ID callout occurs and masks/disables the interrupt and then calls each handler that is attach on that vector. When each interrupt handler completes the mask count is  decremented and the eoi callout is called. The last handler to exit will cause the mask count to reach 0 and the eoi will unmask/re-enable the interrupt.
  3. If there are two SDMA channel complete at the same time – two bits are set in IRQSTATUS. My understanding is, based on the reading of the callout code, id() will be entered twice, so does the eoi().
    • the first time sdma_id() is called, there are two bits set in IRQSTATUS, the code scan for first set bit, and disable the corresponding bit in IRQENABLE.
    • when eoi() is entered, it checks if  mask_count is zero (all handlers for that IRQ have exited). if yes, clear the bit in IRQSTATUS, and set the bit in IRQENABLE.
    • the 2nd id() is entered, there is only one bit left in IRQSTATUS.


Interrupt(5): enable & disable & mask & unmask

InterruptEnable()  — enables all hardware interrupts. can be called from a thread or from an interrupt handler.

__asm {
MRS r1, CPSR      —> copy CPSR to r1
BIC r1, r1, #0x80  —> set bit 7 to 0
MSR CPSR_c, r1   –> copy the modified value back to CPSR

InterruptDisable() — disable all hardware interrupts.

__asm {
MRS r1, CPSR      —> copy CPSR to r1
ORR r1, r1, #0x80  —> set bit 7 to 1
MSR CPSR_c, r1   –> copy the modified value back to CPSR


  1. interruptEnable() and InterruptDisable() only work on single processor hardware. Use InterruptLock() and InterruptUnlock() pair, which can be used on either multi-processor or single processor hardware
  2. InterruptLock() calls InterruptDisable() first.
  3. InterruptUnlock() calls Interruptenable() at the end.

InterruptLock(spinlock) — Guards a critical section by locking the specified spinlock.

  • InterruptLock() protects access to shared data structures between an interrupt handler and the thread that owns the handler.
  • InterruptLock() tries to acquire the spinlock (a variable shared between the interrupt handler and a thread) while interrupts are disabled. The code spins in a tight loop until the lock is acquired. It’s important to release the lock as soon as possible.
  • the thread must have the PROCMGR_AID_IO ability enabled.

    It’s important to release the lock as soon as possible.

  • If spinlock isn’t a static variable, you must initialize it by calling:
    memset( spinlock, 0, sizeof( *spinlock ) );

InteruptMask(int IRQ, int id)   Disable a hardware interrupt. id is the interrupt id returned by InterruptAttach().

  • only calls mask() callout if the current ip->mask_cont == 0;
    • otherwise, only increase the mask_count for that intetrupt level: ilp->mask_count++;
  • if the parameter id (itp) is not NULL, ++ itp->mask_count.

note: The id is ignored unless you use the _NTO_INTR_FLAGS_TRK_MSK flag when you attach the handler.

What’s behind InterruptMask() call?

the kernel will  either look for the corresponding “mask” function for this interrupt in SYSPAGE area (if in_interrupt() returns true), or call __interruptMask

in QNX, it is usually implemented as a callout. For ARM based processor,

  • mask() callout will write to ICDICER (distributer Interrupt Clear-Enable register);
  • unmask() callout will write to ICDISER (distributer Interrupt Set-Enable register).

Interrupt(4): Exception handling & Vector Table

Exceptions occur when:

  • externally generated interrupts
  • an attempt by the processor to execute an undefined instruction
  • accessing privileged operating system functions.

When an exception occurs, control passes through an area of memory called the vector table. This is a reserved area usually at the bottom of the memory map. Within the table one word is allocated to each of the various exception types. This word contains either a branch instruction or, in the case of ARMv6-M and ARMv7-M, an address to the relevant exception handler.


A vector table consists of a set of ARM instructions that manipulate the PC (i.e. B, MOV, and LDR). These instructions cause the PC to jump to a specific location that can handle a specific exception or interrupt.

Example:  Typical vector table using a literal pool

                AREA vectors, CODE, READONLY
                LDR pc, Reset_Addr
                LDR pc, Undefined_Addr
                LDR pc, SVC_Addr
                LDR pc, Prefetch_Addr
                LDR pc, Abort_Addr
                NOP                    ;Reserved vector
                LDR pc, IRQ_Addr
                ; FIQ handler code - max 4kB in size

Reset_Addr      DCD Reset_Handler
Undefined_Addr  DCD Undefined_Handler
SVC_Addr        DCD SVC_Handler
Prefetch_Addr   DCD Prefetch_Handler
Abort_Addr      DCD Abort_Handler
                DCD 0                  ;Reserved vector
IRQ_Addr        DCD IRQ_Handler

The processor response to an exception

When an exception is generated, the processor performs the following actions:

  1. Copies the CPSR into the appropriate SPSR. This saves the current mode, interrupt mask, and condition flags.
  2. Switches state automatically if the current state does not match the instruction set used in the exception vector table.
  3. Changes the appropriate CPSR mode bits to:
    • Change to the appropriate mode, and map in the appropriate banked out registers for that mode.
    • Disable interrupts. IRQs are disabled when any exception occurs. FIQs are disabled when an FIQ occurs and on reset.
  4. Sets the appropriate LR to the return address.
  5. Sets the PC to the vector address for the exception.

Returning from an exception handler

The method used to return from an exception depends on whether the exception handler uses stack operations or not. In both cases, to return execution to the place where the exception occurred an exception handler must:

  • restore the CPSR from the appropriate SPSR
  • restore the PC using the return address from the appropriate LR.


the GIC signals an interrupt exception request to the processor.

gic_id() is executed, to read  ICCIAR to get the interrupt ID. — how kernel find gic_id()?

then gic_id() disables that interrupt in GIC.

for cascaded interrupt, sdma_id is exectured,

interrupt handler(s) are entered.



Interrupt(3): interrupt callouts (ARM GIC)


  1. Interrupt Acknowledge Register (ICCIAR)
      • ICCIAR[12:10]: the processor(CPU) that requested the interrupt;
      • ICCIAR[9:0]: the interrupt ID
    • The processor reads this register to obtain the interrupt ID of the signaled interrupt.This read acts as an acknowledge for the interrupt.
    • A read of the ICCIAR returns the interrupt ID of the highest priority pending interrupt for the CPU interface,
      • The read returns a spurious interrupt ID of 1023 if any of the following apply:
        • Signalling of interrupts to the CPU interface is disabled
        • There is no pending interrupt on this CPU interface with sufficient priority for the interface to signal it to the processor.
  2. End of Interrupt Register (ICCEOIR)
    • A processor writes to this register to inform the CPU interface that it has completed its interrupt service routine for the specified interrupt.
    • The interrupt service routine on the connected processor must write to the ICCEOIR for every read of a valid Interrupt ID from the ICCIAR,
    • Writing to this register causes the GIC to change the status of the identified interrupt:
      • to inactive, if it was active
      • to pending, if it was active and pending.
  3. Interrupt Clear-Enable Registers (ICDICERn), n = 0 ~31
    • Writing 1 to a Clear-enable bit disables forwarding of the corresponding interrupt to the CPU interfaces.
  4. Interrupt Set-Enable Registers (ICDISERn)
    • Writing 1 to a Set-enable bit enables forwarding of the corresponding interrupt to the CPU interfaces.

id() callout:

  1. read ICCIAR reg to get the interrupt ID –> this is step 5 described in arm-interrupt-controller.
    • if  ICCIAR[9:0] is 0 (it might be SGI), write   ICCEOIR and exit id().
    • if ICCIAR[12:0} is set to 0x3FF, it is a spurious interrupt ID, exit id()
  2. disable interrupt source by writing to the corresponding bit in ICENABLERn.
  3. writes the interrupt ID to the ICCEOIR, to signal the completion — > step 6

eoi() callout:

  1. if Interrupt ID is 0, do nothing.
  2. if mask count is not zero, do nothing.
  3. if mask count is zero, enable interrupt source by writing to the corresponding bit in ISENABLERn

mask() callout: write to ICDICERn.

unmask() callout writes to ISDICERn

 * -----------------------------------------------------------------------
 * Identify interrupt source.
 * Returns interrupt number in r4
 * -----------------------------------------------------------------------
CALLOUT_START(interrupt_id_gic, rw_intr, patch_id)
	mov		ip,     #0x000000ff		// RW data offset (patched)	
	orr		ip, ip, #0x0000ff00
	add		ip, r5, ip				// INTR_GENFLAG_LOAD_SYSPAGE specified

	ldr		r0, [ip, #OFF_GIC_CPU]	// cpu interface registers
	ldr		r1, [ip, #OFF_GIC_DIST]	// distributor registers

	 * Get interrupt ID and check for special cases:
	 * ID0    - used for IPI: immediately send eoi as we need src cpu to eoi
	 * ID1023 - spurious interrupt: set vector to -1
	ldr		r4, [r0, #ARM_GICC_IAR]
	bics	ip, r4, #ARM_GICC_IAR_SRCID   // defined as 0x1C00. r4 contains Interrupt ID
	streq	r4, [r0, #ARM_GICC_EOIR]   // if r4[9:0] is 0, write EOI.
	mov		r4, ip   // mov from ip to r4, Interrupt IDs?
	beq		0f
	mov		ip,     #0x0ff
	orr		ip, ip, #0x300
	teq		r4, ip
	mvneq	r4, #0     // if (r4 == 0x3FF), move -1 to r4
	beq		0f

	 * Mask the interrupt source
	and		r2, r4, #0x1f     // lower 5 bits of r4 --> r2
	mov		r3, #1
	mov		r3, r3, lsl r2	// 1 << r2 --> r3   ==> 1 << (id % 32)
	mov		r2, r4, lsr #5   // r4 << 5 --> r2  ==> get the index n
	mov		r2, r2, lsl #2   // r2 << 2 --> r2 ==> reg offset to GICD_ICENABLEERn
	add		r2, r2, #ARM_GICD_ICENABLERn		// ENABLE_CLR[id / 32]
	str		r3, [r1, r2]

	 * Send EOI to controller
	str		r4, [r0, #ARM_GICC_EOIR]
 * On entry:
 *	r4 contains the interrupt number
 *	r7 contains the interrupt mask count
 * -----------------------------------------------------------------------
CALLOUT_START(interrupt_eoi_gic, rw_intr, patch_rw)
	mov		ip,     #0x000000ff		// RW data offset (patched)	
	orr		ip, ip, #0x0000ff00
	add		ip, r5, ip				// INTR_GENFLAG_LOAD_SYSPAGE specified

	 * Special case: IPI interrupt (ID0) is never disabled
	teq		r4, #0    // if r4 == 0, the condition is false (0)
	beq		0f

	 * Only unmask interrupt if mask count is zero
	teq		r7, #0
	bne		0f

	ldr		r0, [ip, #OFF_GIC_DIST]				// distributor registers
	and		r2, r4, #0x1f
	mov		r3, #1
	mov		r3, r3, lsl r2						// 1 << (id % 32)
	mov		r2, r4, lsr #5
	mov		r2, r2, lsl #2
	add		r2, r2, #ARM_GICD_ISENABLERn		// ENABLE_SET[id / 32]
	str		r3, [r0, r2]

Interrupt(2): ARM Interrupt Controller

ARM uses Global Interrupt Controller ( GIC) architecture, which splits GIC logically into two blocks:

  • Distributor: performs interrupt prioritization and distribution to CPU interfaces
  • CPU interface: connects to the processors, performs priority masking and preemption handling

Interrupt type

  • Peripheral Interrupt: an interrupt asserted by a signal to the GIC (Edge-triggered, or Level sensitive)
    • Private (PPI, interrupt IDs 16 – 31): specific to a single processor
    • Shared (SPI, interrupt IDs 32 – 1019): can be routed to any combination of processors
  • Software-Generated Interrupt (SGI, interrupt IDs 0 – 15), generated by software writing to a specific register in GIC.

Initialization after rest or power on

After a reset, the Distributor and CPU interfaces are disabled, and software must initialize the Distributor and all CPU interfaces.

  1. For SPIs (distributor part)
    • specify whether each interrupt is level-sensitive or edge-triggered — reg ICDICFRs.
    • specify the priority value for each interrupt — reg ICDIPRs
    • specify the target processor list for each interrupt, if the GIC is part of a multiprocessor implementation. — reg ICDIPTRs
    • enable the interrupts — reg ICDISERs
  2. for PPIs and SGIs, …..
  3. for each CPU interface
    • set the priority mask for the interface — ICCPMR
    • set the binary point position, that determineds preemption on the interface — ICCBPR
    • enable signalling of interrupts by this interface — ICCICR
  4. Enable the Distributor  — ICDDCR

Sample code:

init_intrinfo() calls ar_gic_init() to initialize the interrupt controller, before filling the syspage_entry intrinfo.

arm_gic_init(paddr_t dist_base, paddr_t cpu_base)
	unsigned	i;
	unsigned	itn;

	gic_dist_base = dist_base;
	gic_cpu_base = cpu_base;

	// Make sure distributor is disabled
	no_writeback_out32(dist_base + ARM_GICD_CTLR, 0);
	 // Calculate number of interrupt lines
	itn = ((in32(dist_base + ARM_GICD_TYPER) & ARM_GICD_TYPER_ITLN) + 1) * 32;

	 // Disable all interrupts and clear pending state
	for (i = 0; i < itn; i += 32) {
		no_writeback_out32(dist_base + ARM_GICD_ICENABLERn + (i * 4 / 32), 0xffffffff);
		no_writeback_out32(dist_base + ARM_GICD_ICPENDRn + (i * 4 / 32), 0xffffffff);

	 // Set default priority of all interrupts to 0xa0
	for (i = 0; i < itn; i += 4) {
		no_writeback_out32(dist_base + ARM_GICD_IPRIORITYn + i, 0xa0a0a0a0);
	 // Route all SPI interrupts to cpu0
	for (i = 32; i < itn; i += 4) {
		no_writeback_out32(dist_base + ARM_GICD_ITARGETSRn + i, 0x01010101);

	 // Default all SPI interrupts as level triggered
	for (i = 32; i < itn; i += 16) {
		no_writeback_out32(dist_base + ARM_GICD_ICFGRn + (i * 4 / 16), 0);

	 // Enable distributor
	no_writeback_out32(dist_base + ARM_GICD_CTLR, ARM_GICD_CTLR_EN);

	 // Enable cpu interface for cpu0.
	 // note: Init_intrinfo() only initialize the CPU interface for CPU0
         // Secondary cpu interfaces are enabled as those cpus are initialised
	 // Disable all banked PPI interrupts
	 // Enable all SGI interrupts
	out32(gic_dist_base + ARM_GICD_ICENABLERn, 0xffff0000);
	out32(gic_dist_base + ARM_GICD_ISENABLERn, 0x0000ffff);

	 // Set priority mask to allow all interrupts and enable cpu interface
	out32(gic_cpu_base + ARM_GICC_PMR, 0xF0);
	out32(gic_cpu_base + ARM_GICC_CTLR, ARM_GICC_CTLR_EN);

How does the GIC operate on Interrupts?

  1. The GIC determines whether each interrupt is enabled. An interrupt that is not enabled has no further effect on the GIC.
  2. For each enabled interrupt that is pending, the Distributor determines the targeted processor or processors.
  3. For each processor, the Distributor determines the highest priority pending interrupt, based on the priority information it holds for each interrupt, and forwards the interrupt to the CPU interface.
  4. The CPU interface compares the interrupt priority with the current interrupt priority for the processor, determined by a combination of the Priority Mask Register, the current preemption settings, and the highest priority active interrupt for the processor. If the interrupt has sufficient priority, the GIC signals an interrupt exception request to the processor.
  5. When the processor takes the interrupt exception, it reads the ICCIAR in its CPU interface to acknowledge the interrupt, This read returns an Interrupt ID that the processor uses to select the correct interrupt handler. When it recognizes this read, the GIC changes the state of the interrupt:
    • if the pending state of the interrupt persists when the interrupt becomes active, or if the
      interrupt is generated again, from pending to active and pending;
    • otherwise, from pending to active
      • A level-sensitive peripheral interrupt persists when it is acknowledged by the processor, because the interrupt signal to the GIC remains asserted until the interrupt service routine (ISR) running on the processor accesses the peripheral asserting the signal.
  6. When the processor has completed handling the interrupt, it signals this completion by writing to the ICCEOIR in the GIC.
    • The GIC requires the order of completion of interrupts by a particular processor to be the reverse of the order of acknowledgement, so the last interrupt acknowledged must be the first interrupt completed.
    • When the processor writes to the ICCEOIR, the GIC changes the state of the interrupt, for the corresponding CPU interface, either:
      • from active to inactive
      • from active and pending to pending