Category Archives: syspage

syspage(3): callouts

In order for the Neutrino microkernel to work on all boards, all hardware-dependent operations have been factored out of the code — Known as kernel callouts.


  • are provided by the startup program.
  • get overwritten when the kernel starts up.
    • Startup program will copy the callouts (the code between CALLOUT_START and CALLOUT_END)from the startup program into the system page and after this, the startup memory (text and data) is freed.
  • allow you to “hook into” the kernel and gain control when a given event occurs.
    • The callouts operate in an environment similar to that of an interrupt service routine — you have a very limited stack, and you can’t invoke any kernel calls (such as mutex operations, etc.).
  • must be Position-independent
    • reason: they won’t be in the location that they were loaded in, they must be coded to be position-independent.
    • how: be coded in assembler
  • No static read/write storage
    • if needed, you can make a small storage available for it, by using the patcher routines and the 2nd parameter to CALLOUT_START. see the example below on how it works.
  • For all but two of teh routines (interrupt_id(), interrupt_eoi), the kernel invokes the callouts with the normal function-calling conventions
    • For performance reasons, the kernel intermixes id() and eoi() directly with kernel code.

Types of Callouts

  • debug interface
  • clock/timer interface
  • interrupt controller interface
    • 3 callouts for interrupt controller interface: mask(), unmask(), config().
    • 2 callouts as code stubs: id(), eoi()
    • Each group of callouts (i.e. id, eoi, mask, unmask) for each level of interrupt controller deals with a set of interrupt vectors that start at 0 (zero-based).
  • cache controller interface
  • system reset
  • power management

Callout macros, defined in “callout.ah”

rw_intr: .word 8

CALLOUT_START(interrupt_id_gic, rw_intr, patch_id)

  •   1st parameter: name of the callout routine
  • 2nd parameter: address of a 4-byte variable that contains the amount of  read/write storage the callout needs.
  • 3rd parameter: either a zero or the address of a patcher() routine.


“patching” the callout code

  • make it possible for the same callout routine to be used on the different boards, where the device might be at different locations.
  • a patcher() is invoked immediately after the callout has been copied to its final resting place.
  • make read-write storage available for the callout, when used together with 2nd parameter of CALLOUT_START.
    • “rw_intr: .word 8″ tells the startup library that the routine needs 8 bytes of read/write storage.
    • The startup library allocates space at the end of the system page and passes the offset to it as the rw_offset parameter of the patcher routine.
    • The patcher routine then modifies the initial instruction of the callout to the appropriate offset.
    • While the callout is executing, the t3 register will contain a pointer to the read/write storage.


intrinfo is automatically filled in by init_intrinfo()


  1. struct startup_intrinfo is defined and used by startup code. the public definition is in sys/syspage.h.
  2. Each group of callouts (i.e. id, eoi, mask, unmask) for each level of interrupt controller deals with a set of interrupt vectors that start at 0 (zero-based). Interrupt vector numbers are passed without offset to the callout routines. The association between the zero-based interrupt vectors the callouts use and the system-wide interrupt vectors is configured within the startup-intrinfo structures.
  3. flags:
      • Before the interrupt id or eoi code sequence is generated, a piece of code needs to be inserted to fetch the system page pointer into a register so that it’s usable within the id code sequence.
      • Used only by EOI routines for hardware that doesn’t automatically mask at the chip level.
      • When the EOI routine is about to reenable interrupts, it should reenable only those interrupts that are actually enabled at the user level (e.g. managed by the functions InterruptMask() and InterruptUnmask()). When this flag is set, the existing interrupt mask is stored in a register for access by the EOI routine. A zero in the register indicates that the interrupt should be unmasked; a nonzero indicates it should remain masked.
struct intrinfo_entry {
	_Uint32t vector_base; // base number of the logical interrupt/vector numbers(IRQs) that programs will use
	_Uint32t num_vectors; // the number of the vectors
	_Uint32t cascade_vector;// the logical IRQ number for cascaded interrupts
	_Uint32t cpu_intr_base;
	_Uint16t cpu_intr_stride;
	_Uint16t flags;
	struct __intrgen_data	id;
	struct __intrgen_data	eoi;
	_SPFPTR(int, mask, (struct syspage_entry *, int));
	_SPFPTR(int, unmask, (struct syspage_entry *, int));
	_SPFPTR(unsigned, config, (struct syspage_entry *, struct intrinfo_entry *, int));
	_Uint32t				spare[4];

A piece of sample code:

// Adding main ARM GIC Controller
const static struct startup_intrinfo intrs[] =
        .vector_base      = _NTO_INTR_CLASS_EXTERNAL, // (0x0000UL << 16)
        .num_vectors      = 32+192, // including SGIs, PPIs, SPIs
        .cascade_vector   = _NTO_INTR_SPARE, // (0x7FFFFUL << 16) | 0xFFFF
        .cpu_intr_base    = 0,
        .cpu_intr_stride  = 0,
        .flags            = 0,
        .id               = { INTR_GENFLAG_LOAD_SYSPAGE, 0, &interrupt_id_gic},
        .eoi              = { INTR_GENFLAG_LOAD_SYSPAGE | INTR_GENFLAG_LOAD_INTRMASK, 0, &interrupt_eoi_gic},
        .mask             = &interrupt_mask_gic,
        .unmask           = &interrupt_unmask_gic,
        .config           = &interrupt_config_gic,
        .patch_data       = NULL,
// Adding System DMA interrupt cascaded into OMAP54XX_SDMA_IRQ_0 only
static struct startup_intrinfo sdmaintrs[] = 
        .vector_base      = 256,
        .num_vectors      = 32,
        .cascade_vector   = OMAP54XX_SDMA_IRQ_0,
        .cpu_intr_base    = 0,
        .cpu_intr_stride  = 0,
        .flags            = 0,
        .id               = { 0, 0, &interrupt_id_omap4_sdma },
        .eoi              = { INTR_GENFLAG_LOAD_INTRMASK, 0, &interrupt_eoi_omap4_sdma },
        .mask             = &interrupt_mask_omap4_sdma,
        .unmask           = &interrupt_unmask_omap4_sdma,
        .config           = 0,
        .patch_data       = &sdma_base,
    initialize the interrupt controller; // See the sample code in Interrupt(2): ARM Interrupt Controller
    add_interrupt_array(intrs, sizeof(intrs));
    disable all SDMA channel interrupts, and clear all channel statuses; 
    add_interrupt_array(sdmaintrs, sizeof(sdmaintrs));


System page: an in-memory data structure which stores the information about the system, e.g. processor type, the location and size of available system RAM.

  • the system page is initialized by the startup program.
  • the kernel as well as applications can access this information as a read-only data structure.
  • struct syspage_entry is defined in <sys/syspage.h>

struct syspage_entry {
uint16_t size;
uint16_t total_size;
uint16_t type;          // ARM, MIPS, PPC, SH4, or X86
uint16_t num_cpu;
syspage_entry_info system_private;
syspage_entry_info asinfo;
syspage_entry_info hwinfo;
syspage_entry_info cpuinfo; //  CPU type, speed, capabilities, performance, and cache sizes, etc
syspage_entry_info cacheattr;
syspage_entry_info qtime;  // get a timestamp via: SYSPAGE_ENTRY(qtime)->nsec
syspage_entry_info callout; // allow you to “hook into” the kernel and gain control when a given event occurs.
syspage_entry_info callin;
syspage_entry_info typed_strings;
syspage_entry_info strings;
syspage_entry_info intrinfo;  // information about the interrupt system
syspage_entry_info smp;
syspage_entry_info pminfo;
union {
struct x86_syspage_entry x86;
struct ppc_syspage_entry ppc;
struct mips_syspage_entry mips;
struct arm_syspage_entry arm;
struct sh_syspage_entry sh; }}