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.