Hardware initialization

Updated: April 19, 2023

The startup code does the minimum needed to detect the hardware configuration and completes any other configuration needed for the OS kernel to run.

The source code for the initialization routines is found in the src/hardware/startup/boards/boardname/ and src/hardware/startup/lib/ directories of the BSP. The boardname directory has the platform- and platform variant-specific source code. If the source code is generic for the architecture (which is more common with x86 platforms), the source code is in the startup/lib/ directory.

A typical startup main() function (see Source code structure) calls the following hardware initialization functions in this order:

  1. init_raminfo()
  2. init_mmu()
  3. init_intrinfo()
  4. init_qtime()
  5. init_cacheattr()
  6. init_cpuinfo()

Many BSPs also include hardware initialization functions for supported technologies; you will find these in appropriately named files (e.g., init_sata.c, init_usb.c). For more information, see the init_*() functions in the Startup Library.

Adding hardware initialization code

If you need to add code to look after hardware initialization that isn't covered by one of the init_*() functions in the library (or your custom version of one of these functions), you can do one of the following:

Below is an example of a small code segment added to the main.c file for a fictitious Fortinbras board:

// Announce start of Startup code   
for (i = 0; i < 8; ++i)   
    led[i] = startup_msg[i]; 

Setting interrupt sensitivity

Most systems today use only level-sensitive interrupts. QNX Neutrino supports level-sensitive interrupts but also edge-sensitive interrupts to support legacy hardware (e.g., Intel x86 hardware with the 8259 PIC on an ISA bus).

It is the responsibility of the interrupt controller initialization code along with the interrupt kernel callouts to specify the interrupt sensitivity (see Interrupt controller for information about interrupt kernel callouts).

The code snippet below is from the gic_v2_init() function, which performs some GIC initializations during startup of a 64-bit ARM board:

/* 
 * Make all SPI interrupts level-triggered by default 
 */
for (i = 32; i < itn; i += 16) {
    out32(gicd + ARM_GICD_ICFGRn + (i * 4 / 16), 0);
}

On this board, the sensitivity information for each interrupt ID is stored in the GIC's ICFGR registers. Zero (0) means level-sensitive interrupts. The code snippet above writes 0 to these registers. Check your board's documentation to learn what registers you need to set (if any) and to what values to enable level-sensitive interrupt support.

Below, for context, is the complete GIC interrupt initialization function from which the snippet is taken:

gic_v2_init(paddr_t gicd, paddr_t gicc)
{
    struct aarch64_gic_map_entry *gic_map = lsp.cpu.aarch64_gic_map.p;
    unsigned    gic_cpu;
    unsigned    itn;
    unsigned    i;

    gic_gicd = gicd;
    gic_gicc = gicc;
    gic_cpu_init = gic_v2_gicc_init;
    gic_sendipi = &sendipi_gic_v2;

    /*
     * Initialise the GIC cpu map with invalid values.
     * gic_v2_gicc_init() will set the mapping for each cpu as it comes up.
     */
    for (i = 0; i < lsp.syspage.p->num_cpu; i++) {
        gic_map->gic_cpu[i] = ~0u;
    }
    gic_cpu = gic_v2_cpunum();

    /*
     * Disable distributor
     */
    out32(gicd + ARM_GICD_CTLR, 0);

    /*
     * Calculate number of interrupt lines
     */
    itn = ((in32(gicd + ARM_GICD_TYPER) & ARM_GICD_TYPER_ITLN) + 1) * 32;
    gic_v2_intr.num_vectors = itn;

    if (debug_flag) {
        kprintf("GICv2: %d interrupts\n", itn);
    }

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

    /*
     * Set default priority of all interrupts to 0xa0
     */
    for (i = 0; i < itn; i += 4) {
        out32(gicd + ARM_GICD_IPRIORITYn + i, 0xa0a0a0a0);
    }

    /*
     * Route all SPI interrupts to cpu0
     */
    if (debug_flag) {
        kprintf("GICv2: routing SPIs to gic cpu %d\n", gic_cpu);
    }
    for (i = 32; i < itn; i += 4) {
        out32(gicd + ARM_GICD_ITARGETSRn + i, (0x01010101u << gic_cpu));
    }

    /*
     * Make all SPI interrupts level-triggered by default 
     */
    for (i = 32; i < itn; i += 16) {
        out32(gicd + ARM_GICD_ICFGRn + (i * 4 / 16), 0);
    }

    /*
     * Enable distributor - cpu interface is initialised via init_cpuinfo()
     */
    out32(gicd + ARM_GICD_CTLR, ARM_GICD_CTLR_EN);

    /*
     * Add the interrupt callouts
     */
    add_interrupt(&gic_v2_intr);
}
Note:

A programmer's use of interrupt-related functions such as InterruptAttach() or InterruptMask() isn't affected by the interrupt sensitivity, though you should be aware of the implications of the interrupt sensitivity on your system, as this may affect what you must do when working with interrupts.

For more information about interrupts, see the Interrupts chapter in Getting Started with QNX Neutrino, and the Writing an Interrupt Handler chapter in the Programmer's Guide.