interrupt_id_*() and interrupt_eoi_*()

Updated: April 19, 2023

To improve performance, the interrupt_id_*() and interrupt_eoi_*() kernel callouts are intergrated directly into the kernel code.

The interrupt ID and end of interrupt (EOI) kernel callouts aren't called in the same way as the other callouts. For details about interrupt_id_*() and interrupt_eoi_*(), see the callout_interrupt_*.s files in the startup library for the relevant CPU architecture. These files have descriptions that specify which registers are used to pass values to and from these callouts.

For example, the following comments are provided with a callout_interrupt_*.s file for an ARM board:

/*
 * MC9328MX1/MC9328MX21/MCIMX31 specific interrupt callouts.
 *
 * interrupt_id_* and interrupt_eoi_* are copied and intermixed with other
 * kernel code during initialisation.
 *
 * They do not follow normal calling conventions, and must fall through
 * to the end, rather than attempting to perform a return instruction.
 *
 * The INTR_GENFLAG_* bits in the intrinfo_entry defines which of the
 * following values can be loaded on entry to these code fragments:
 *
 *	r5 - holds the syspageptr		(INTR_GENFLAG_SYSPAGE  set)
 *	r6 - holds the intrinfo_entry pointer	(INTR_GENFLAG_INTRINFO set)
 *	r7 - holds the interrupt mask count	(INTR_GENFLAG_INTRMASK set)
 *
 * The interrupt_id_* routine returns the (controller-relative) level in r4
 */
Note:

You can't return from the middle of an interrupt_id_*() or interrupt_eoi_*() callout—you must fall through to the end of the callout code.

For more information about interrupt flags, see INTR_* flags in the System Page chapter.

interrupt_id_*() kernel callouts

An interrupt_id_*() kernel callout has the following responsibilities:

To perform these tasks, an interrupt ID callout needs to:

  1. Read information from some kind of interrupt Status register.
  2. Perform bit manipulation to determine the interrupt level.
  3. Put the interrupt level value into a general-purpose CPU register, so the information can be used by the kernel.
  4. If a glitch or spurious assertion occurs, put a -1 into a GPR to indicate no interrupt to the kernel.
  5. Mask the interrupt by manipulating bits in an enable-and-mask register.

Below is an example of a kernel callout that gets the interrupt ID and masks the interrupt from an ARM V2 GIC. Check your hardware documentation to learn what your callout must do to identify and mask callouts.

/*
 * -----------------------------------------------------------------------
 * Identify interrupt source
 *
 *	x20 - syspage pointer
 * -----------------------------------------------------------------------
 */
CALLOUT_START(interrupt_id_gic_v2, rw_size, rw_patch)
	mov		w7, 0xabcd              // offset to rw data (patched)
	add		x7, x7, x20             // address of rw data
	ldr		x6, [x7, #OFF_GICC]

	/*
	 * Get interrupt ID and handle special cases:
	 * ID0    - used for IPIs
	 * ID1022 - spurious interrupt
	 * ID1023 - spurious interrupt
	 */
	ldr		w0, [x6, #ARM_GICC_IAR]
	and		w19, w0, #ARM_GICC_IAR_IDMASK
	cbz		w19, 0f
	cmp		w19, #1022
	bhi		1f

	/*
	 * Mask interrupt
	 */
	ldr		x5, [x7, #OFF_GICD]
	add		x5, x5, #ARM_GICD_ICENABLERn
	and		w0, w19, #0x1f
	mov		w1, #1
	lsl		w1, w1, w0              // bit to set = 1 << (id % 32)
	lsr		w0, w19, #5             // index = id / 32
	str		w1, [x5, x0, lsl #2]    // ICENABLERn[index] = bit
	b		2f

	/*
	 * IPI interrupt (ID0) - acknowledge using full SRCID and exit
	 */
0:	str		w0, [x6, #ARM_GICC_EOIR]
	b		2f

	/*
	 * Spurious interrupt - set id to -1
	 */
1:	mov		x19, #-1

	/*
	 * Done - interrupt id is in x19
	 */
2:
CALLOUT_END(interrupt_id_gic_v2)

interrupt_eoi_*() kernel callouts

An interrupt_eoi_*() kernel callout has the following responsibilities:

Below is an example of a kernel callout that sends the EOI and unmasks the interrupt from an ARM V2 GIC. See your hardware documentation to learn what your callout must do to perform these tasks.

/*
 * -----------------------------------------------------------------------
 * Acknowledge specified interrupt
 *
 *	x19 - contains interrupt id
 *	x20 - contains syspage pointer (INTR_GENFLAG_LOAD_SYSPAGE was used)
 *	x22 - contains intr mask count (INTR_GENFLAG_LOAD_INTRMASK was used)
 * -----------------------------------------------------------------------
 */
CALLOUT_START(interrupt_eoi_gic_v2, rw_size, rw_patch)
	mov		w7, 0xabcd              // offset to rw data (patched)

	/*
	 * Skip ID0 because we EOI in the id callout and never mask it
	 */
	cbz		w19, 0f

	add		x7, x7, x20             // address of rw data
	ldr		x5, [x7, #OFF_GICD]
	ldr		x6, [x7, #OFF_GICC]

	/*
	 * Send EOI
	 */
	str		w19, [x6, #ARM_GICC_EOIR]

	/*
	 * Unmask interrupt if mask count is zero.
	 */
	cbnz	w22, 0f
	add		x5, x5, #ARM_GICD_ISENABLERn
	and		w0, w19, #0x1f
	mov		w1, #1
	lsl		w1, w1, w0              // bit to set = 1 << (id % 32)
	lsr		w0, w19, #5             // index = id / 32
	str		w1, [x5, x0, lsl #2]    // ISENABLERn[index] = bit
0:
CALLOUT_END(interrupt_eoi_gic_v2)