InterruptHookIdle2()

Updated: April 19, 2023

Attach an “idle” interrupt handler

Synopsis:

#include <sys/neutrino.h>

int InterruptHookIdle2(
       void (*handler)( unsigned cpu,
                        struct syspage_entry *spp,
                        struct _idle_hook *ihp ),
       unsigned flags );

Arguments:

handler
A pointer to the handler function; see below.
flags
There are currently no flags that apply; specify 0 for this argument.

Library:

libc

Use the -l c option to qcc to link against this library. This library is usually included automatically.

Description:

The InterruptHookIdle2() kernel call attaches the specified handler to a synthetic interrupt, which is generated when a processor becomes idle. This handler is typically used to implement power management features. The simplest idle handler consists of a halt instruction.

Before calling this function, the thread must have the PROCMGR_AID_INTERRUPT ability enabled. For more information, see procmgr_ability(). Otherwise the attachment fails with an error code of EPERM.

To detach the interrupt handler, call InterruptDetach(); you can't pass NULL for the handler argument.

The arguments to the handler function are:

unsigned cpu
The CPU number that this hook call is occurring on.
struct syspage_entry * spp
A pointer to a writable version of the system page. For more information, see the System Page chapter of Building Embedded Systems, as well as the SYSPAGE_ENTRY() macro.
struct _idle_hook * ihp
A pointer to an _idle_hook structure that the kernel and the handler use to exchange information.

The handler function is invoked with a very limited amount of stack space, so it shouldn't use large automatic arrays. It's also invoked in such a manner that it can be aborted at any time and restarted from the beginning, with no user notification. It isn't allowed to make any OS service calls (e.g., message passing).

The _idle_hook structure is defined in <sys/neutrino.h> as follows:

struct _idle_hook {
        unsigned                hook_size;
        unsigned                cmd;
        unsigned                mode;
        unsigned                latency;
        uint64_t                next_fire;
        uint64_t                curr_time;
        uint64_t                tod_adjust;
        unsigned                resp;
        struct {
                unsigned        length;
                unsigned        scale;
        } time;
        struct sigevent         trigger;
        unsigned                *intrs;
        unsigned                block_stack_size;
};

The members include:

hook_size
The size of the _idle_hook structure, in bytes.
cmd
The kernel sets this member to one of the following values, to indicate the phase of the call (see below for more details):
  • _NTO_IH_CMD_SLEEP_SETUP
  • _NTO_IH_CMD_SLEEP_BLOCK
  • _NTO_IH_CMD_SLEEP_WAKEUP
  • _NTO_IH_CMD_SLEEP_ONLINE
mode
The meaning of this member depends on cmd:
  • For _NTO_IH_CMD_SLEEP_SETUP, _NTO_IH_CMD_SLEEP_BLOCK, and _NTO_IH_CMD_SLEEP_WAKEUP, this value (if nonzero) is the sleep mode to set.
  • For _NTO_IH_CMD_SLEEP_ONLINE, it's the CPU number that's being returned to operation.
latency
The maximum latency, in nanoseconds, that the CPU is allowed to have when responding to an interrupt. If the kernel is taking the CPU offline, it sets this field to ~0, and the handler should choose the absolute lowest power mode, which should be off.
next_fire
The time, in nanoseconds since the system was booted, when the next timer is to fire.
curr_time
The current time of day, in nanoseconds since the system was booted.
tod_adjust
The number of nanoseconds to add to curr_time to convert it to the time of day.
resp
The response from the handler; a bitwise OR of zero or more of the following:
  • _NTO_IH_RESP_NEEDS_BLOCK
  • _NTO_IH_RESP_NEEDS_WAKEUP
  • _NTO_IH_RESP_NEEDS_ONLINE
  • _NTO_IH_RESP_SYNC_TIME
  • _NTO_IH_RESP_SYNC_TLB
  • _NTO_IH_RESP_SUGGEST_OFFLINE
  • _NTO_IH_RESP_SLEEP_MODE_REACHED
  • _NTO_IH_RESP_DELIVER_INTRS

See below for more details.

time.length
Filled in by the handler. The number of time units that the system tick (sometimes called “timer tick”) interrupt has been disabled.
time.scale
Filled in by the handler. A scale factor such that time.length * time.scale gives the number of nanoseconds that the system tick interrupt has been disabled.
trigger
A sigevent that the handler wants the kernel to deliver (if the sigev_notify member isn't SIGEV_NONE). The target thread is the one that called InterruptHookIdle2().
intrs
A pointer to an array where each bit, if set to 1 by the handler, indicates an interrupt for which the kernel should arrange to invoke ISRs and deliver events specified by InterruptAttach(), InterruptAttachArray(), or InterruptAttachEvent(). The size of this array is based on the intrinfo section of the system page; it needs to be big enough to handle all the interrupts specified there (basically, total up the num_vector fields).

The _NTO_IH_RESP_DELIVER_INTRS bit must be set in the resp field for the kernel to check the contents of the array. See below for details on how to determine the bit(s) to be set.

block_stack_size
Before invoking the handler with _NTO_IH_CMD_SLEEP_SETUP, the kernel sets this field to the current stack size for _NTO_IH_CMD_SLEEP_BLOCK. On return, if the idle hook places a larger value in the field, the kernel attempts to grow the block stack to the newly requested value. On entry to the idle hook for _NTO_IH_CMD_SLEEP_BLOCK, you can check the size again. If it's less than the size that you requested, the grow request failed due to lack of memory.

The idle hook handler may be called up to three times per entry to or exit from the sleep state, depending on how the handler function responds to each of the previous calls. The kernel sets the cmd field for each invocation:

  1. _NTO_IH_CMD_SLEEP_SETUP

    In this call, the handler should set up the chip registers to enter the selected sleep state. The handler isn't allowed to actually enter a sleep state or block waiting for an interrupt or event.

    The usual activity for the handler at this point is to simply respond that it wants wants to be called with _NTO_IH_CMD_SLEEP_BLOCK at the appropriate time. However, if there is any long-running hardware preparation for low-power mode (e.g., writing to a register, then either waiting or polling a register until ready), you can use this callback to kick things off, so that the hardware will likely to be ready when the callback for _NTO_IH_CMD_SLEEP_BLOCK is called, when the hardware can be fully configured for low-power mode.

  2. _NTO_IH_CMD_SLEEP_BLOCK

    In this call, the handler can continue whatever setup is needed from the first call, and it should actually do what's needed to enter the selected sleep state. If all that's needed to actually enter the sleep state is for the chip to execute its halt instruction (HLT on x86, WFI on ARM, etc.), then that's all the handler has to do.

    Note: The handler is invoked from the context of a CPU-specific idle thread that runs at Ring 1 on x86_64, in Supervisor mode on ARMv7, and EL1 on AArch64. Ensure you have enough privilege to do what's required; for example, Intel's HLT instruction isn't allowed in Ring 1.
  3. _NTO_IH_CMD_SLEEP_WAKEUP

    At this point, an interrupt has occurred that has released the CPU from its sleep state. The handler should restore whatever state it needs to.

    This time, the callback is invoked from an interrupt context, with interrupts enabled, before the interrupt-specific ISR is invoked. You should provide information regarding missed time to the idle hook response before the low-power code transfers execution back to the OS.

For each of these calls, the handler fills in the resp field with a bitset indicating what the kernel needs to do:

Bit Description
_NTO_IH_RESP_NEEDS_BLOCK If set after the first handler call, the kernel will invoke the handler with the _NTO_IH_CMD_SLEEP_BLOCK state.
_NTO_IH_RESP_NEEDS_WAKEUP If set after the first handler call (or second, if the second call has been requested), the kernel will invoke the handler with the _NTO_IH_CMD_SLEEP_WAKEUP state.
_NTO_IH_RESP_NEEDS_ONLINE If the selected mode is such that an interprocessor interrupt (IPI) isn't sufficient to cause the CPU to respond (e.g., it's offline), the handler should set this bit. The kernel, when it decides to online the CPU, invokes the idle hook (on a different CPU) with the cmd field set to _NTO_IH_CMD_SLEEP_ONLINE. The mode field holds the CPU number that is being returned to operation.
_NTO_IH_RESP_SYNC_TIME

After the handler returns, the kernel will add time.length * time.scale nanoseconds to the SYSPAGE_ENTRY(qtime)->nsec field.

In QNX Neutrino 7.1 or later, the kernel uses the ClockCycles() function to detect if system ticks (or “timer ticks”) are lost and thus, determine how much time it should add to this field. This design makes this flag redundant unless there's the possibility that the hardware is interfered with during the time that the system tick interrupt is disabled. If this happens, for whatever reason, you'll need to compensate for the lack of timely delivery of system ticks.

If you freeze the hardware behind ClockCycles() and you want your system to reflect the time that it was in low-power mode with system ticks disabled, you must either correct that underlying hardware to reflect the time when system ticks were disabled, or set the _NTO_IH_RESP_SYNC_TIME flag so the kernel can apply the time correction after the handler returns.

_NTO_IH_RESP_SYNC_TLB After the handler returns, the kernel will resynchronize the translation lookaside buffer (TLB) state (the TLB wasn't being notified of modifications from other CPU's during the sleep state).

The kernel crashes if it gets this response.

_NTO_IH_RESP_SUGGEST_OFFLINE This allows the handler to suggest to the kernel that it might be a good idea to offline this CPU. The CPU must be marked as being available for dynamic offlining, and the kernel is free to ignore the suggestion (and it currently does).
_NTO_IH_RESP_SLEEP_MODE_REACHED This bit indicates that the sleep mode requested was actually entered by the hardware. The kernel doesn't do anything with this bit, but it can be used to gather statistics on how much power savings are being obtained.
_NTO_IH_RESP_DELIVER_INTRS If set, this bit indicates that the handler turned on bits in the intrs array, and the kernel needs to perform interrupt delivery processing for the indicated interrupts.

Indicating interrupts for delivery processing

If an unmasked interrupt occurs while the CPU is asleep and, for some reason, it will not cause CPU interrupt exception processing to occur when the CPU wakes up, the hook code must tell the kernel about the interrupt so that it can arrange to invoke the handlers (InterruptAttach() or InterruptAttachArray()) or deliver the sigevents (InterruptAttachEvent()) for that level.

The handler indicates this by turning on the appropriate bit(s) in the intrs array and setting _NTO_IH_RESP_DELIVER_INTRS in the resp field.

To determine the corresponding bit for each interrupt, examine the system page's intrinfo section and look at the number of interrupts that each controller is responsible for. If the first controller is responsible for N interrupts, then the interrupt numbers are 0 ... (N-1). If the second controller has M interrupts, then its numbers are N ... (N + M - 1), and so on. If you want to indicate that interrupt X has occurred, set the following bit:

idle_hook_ptr->intrs[X / (sizeof(unsigned)*CHAR_BIT)] |=
   1U << (X % (sizeof(unsigned)*CHAR_BIT));

Blocking states

This call doesn't block.

Returns:

An interrupt function ID, or -1 if an error occurs (errno is set).

Use the returned ID with InterruptDetach() to detach this interrupt handler.

Errors:

EAGAIN
All kernel interrupt entries are in use.
EINVAL
The handler argument is NULL.
EPERM
The calling process doesn't have the required permission (see procmgr_ability()).

Classification:

QNX Neutrino

Safety:  
Cancellation point No
Interrupt handler No
Signal handler Yes
Thread Yes