ChorusOS 4.0 Porting Guide

Bootstrap Program Implementation

A typical bootstrap program does the following:

Example 3-5 is the bootstrap program provided for the SBC8260 board. The source code is provided in src/nucleus/bsp/powerpc/src/boot/boot.c.


Example 3-5 Bootstrap Program

BootCBootConf* bootConf;

    void
start(BootConf* conf, BootParams* bootp)
{
    bootConf = conf;

    /*
     * PowerPC 60x specific initializations
     */
    ppc60x_init();
        
    /*
     * Start debug agent
     */
    dbg_start(bootp);

    /*
     * Console IO is now available 
     */
    printf ("Booting ChorusOS ...");

    if (bootp) {
        /*
         * Check that BootParams doesn't overlap with already installed system 
         * segments
         */
        int seg_nb;
        BinDesc* bin = 
            binCheckOverlap((VmAddr) bootp, sizeof(*bootp), BIN_STANDALONE, &seg_nb);
            if (bin) {
            printf("Address range from 0x%x to 0x%x was used by the loader\n",
                   bootp, bootp + 1);
            printf("It overlaps with the segment #%d of '%s'\n", 
                   seg_nb - bin->firstSeg, bin->name);
            printf("Change the corresponding linging area dimensions "
                   "  to avoid the conflict\n");
            ASSERT(0);

        }
        bootConf->siteNumber = bootp->ipcSiteNb;
    }

    /*
     * Tag as free all available RAM
     */
    ram_tag(&bootConf->ramAllocator, 0, ramSize(), PH_RAM_FREE);

    /*
     * Start reboot program
     */
    reboot_start(bootp);

    /*
     * Allocate persistent memory
     */
    prstInstall();

    /*
     *  Build device tree
     */
    bootConf->rootDevice = buildDeviceTree(conf);

    /*
     * Jump to the u-kernel
     */
    kernel_start();

    /*
     * Never returns
     */
}

start() is the bootstrap program entry point. It gets control from the bootconf program. The bootp argument is a pointer to a board-dependent structure. It is passed to boot.c, as a cookie, through the bootconf program, from either the power-up initialization program or the reboot program.

For the SBC8260 power-up intialization program, the value of bootp is NULL. However, for the reboot program (see "HotRebootDesc Structure") , bootp points to a data structure containing the Chorus IPC site number value:

typedef struct BootParams {
		uint32_f		ipcSiteNb;
} BootParams;

As the BootParam structure can be allocated by another instance of the ChorusOS operating system (for example, ChorusOS Boot Monitor) the bootstrap program checks that the structure had not already been corrupted by the system currently booting.

ppc60x_init() is the PowerPC 60x initialization program, and is described in "PowerPC 60x Bootstrap Implementation Framework".

binCheckOverlap(), dbg_start(), ram_tag(), reboot_start(), prstInstall()and kernel_start() are target-independent routines, and are described in "Common Bootstrap Implementation Framework".

buildDeviceTree() is board-specific and builds the initial state of the SBC8260 device tree (see "Initial Device Tree").


Common Bootstrap Implementation Framework

This section describes the common routines used in the bootstrap implementation.

binInstallByMask and binInstallByType()

Example 3-6 is an example of binInstallByMask() and binInstallByType() source code, provided in kernel/snippet/nucleus/boot_tools/binInstall.c.


Example 3-6 binInstallByMask

    void
binInstallOne(BinDesc* bin)
{
    int j;
    for (j = bin->firstSeg; j <= bin->lastSeg; j++) {
        BinSegDesc* seg = &bootConf->segDesc[j];               
        if (seg->space == SEG_KSP) {
            if ((VmAddr) seg->kaddr != seg->vaddr) {
                /*
                 * Move segment to its base address 
                 */
                bcopy((void*) seg->kaddr, (void*) seg->vaddr, seg->ksize);
            }
            if (seg->ksize < seg->vsize) {
                /*
                 *  Zero segments's bss
                 */
                bzero((void*) (seg->vaddr + seg->ksize), 
                      seg->vsize - seg->ksize);
            }
        }
    }
}

    void
binInstallByMask(BinType mask)
{
    int i;
    for (i = 0; i < bootConf->numBins; ++i) {
        BinDesc* bin =  &bootConf->binDesc[i];
        if (bin->type & mask) {
            binInstallOne(bin);
        }
    }
}

    void
binInstallByType(BinType type)
{
    int i;
    for (i = 0; i < bootConf->numBins; ++i) {
        BinDesc* bin =  &bootConf->binDesc[i];
        if (bin->type == type) {
            binInstallOne(bin);
        }
    }
}

The binInstallByMask() function installs binaries specified by the mask argument. The mask can be any combination of the bits listed in Table 3-1.

Table 3-1 mask Bits
mask Bit Meaning
 BIN_STANDALONE install bootstrap program and debug agent binaries
 BIN_KERNEL install microkernel
 BIN_ACTOR install built-in drivers and actors

binInstallByMask() installs all binaries with type values that match at least one bit set in the mask argument. See "Binaries" for more information.

The binInstallByType() function installs all binaries of one particular type.

To install a binary, the binInstallOne() internal function is used. It copies, if necessary, the binary segments from the memory bank to RAM and zeros bss. binInstallOne() only processes segments that belong to the initial kernel address space, SEG_KSP.

binInstallOne() assumes that the memory banks containing the installing binaries have already been installed and that the destination addresses are already accessible.

dbg_start()

The dbg_start() function starts the debug agent driver and the debug agent. Example 3-7 is dbg_start() source code provided in kernel/snippet/nucleus/boot_tools/dbg_start.c.


Example 3-7 dbg_start()

    void
dbg_start(void* cookie)
{
    int i;

    /*
     * Launch debuging console driver
     */
    for (i = 0; i < bootConf->numBins; ++i) {
        BinDesc* bin = &bootConf->binDesc[i];
        if (bin->type == BIN_DBG_DRIVER) {
            (* (DbgDriverEntry) bin->entry)(bootConf, cookie);
        }
    }

    /*
     * Launch debuging agent
     */
    for (i = 0; i < bootConf->numBins; ++i) {
        BinDesc* bin = &bootConf->binDesc[i];
        if (bin->type == BIN_DBG_AGENT) {
            (* (DbgAgentEntry) bin->entry)(bootConf);
        }
    }

    _stdc_consInit(bootConf->dbgOps.consRead,
                   bootConf->dbgOps.consWrite);
}

dbg_start() retrieves the descriptor pointing to the debug agent driver binary from the bootConf structure. It then calls the debug agent driver's entry point, passing it the address of bootConf and the opaque value that came from the initial loader. Once the driver has been started, dbg_start() retrieves the descriptor pointing to the debug agent itself. It calls the debug agent entry point, passing it the address of bootConf.

dbg_start() assumes that the debug agent and its driver are already installed.

Finaly, dbg_start() makes console IO available for the bootstrap program by intializing its printf()/scanf() support library.

reboot_start()

The reboot_start() function installs and launches the reboot program. The reboot program is installed only once during the first cold boot. It maintains a section of the system state that must be kept over subsequent hot reboots. reboot_start() differentiates between a cold boot and a hot boot by testing the value of the rebootDesc field in the bootConf structure:

reboot_start() calls binInstallByType() (see "binInstallByMask and binInstallByType()") to install the reboot program. It then retrieves the descriptor pointing to the reboot program binary and jumps to its entry point, passing the address of the bootConf structure as an argument.

reboot_start() assumes that the memory banks containing the reboot program have already been installed and that the destination addresses are already accessible.

Example 3-10 is reboot_start() source code provided in kernel/snippet/nucleus/boot_tools/reboot_start.c.


Example 3-8 reboot_start()

    void
reboot_start(void* cookie)
{
    int i;

    if (bootConf->rebootDesc == 0) {
        /*
         * This is a cold reboot. Install reboot program
         */
        binInstallByType(BIN_REBOOT);
    }

    for (i = 0; i < bootConf->numBins; ++i) {
        BinDesc* bin = &bootConf->binDesc[i];
        if (bin->type == BIN_REBOOT) {
            (* (RebootEntry) bin->entry)(bootConf, cookie);
        }
    }
}

prstInstall()

The prstInstall() function is used by the bootstrap program to check whether:

Typically, during a cold boot, prstInstall() does nothing because there are no persistent memory devices occupying or requesting RAM blocks. During the first hot boot, creation of the first persistent memory device can be required. Subsequently, during the second hot boot, the first memory device would already be registered and another memory device can request RAM block allocation.

Example 3-10 is prstInstall() source code provided in kernel/snippet/nucleus/boot_tools/prstInstall.c.


Example 3-9 prstInstall()

    void
prstInstall()
{
    int i;

    RamDesc* ram = &bootConf->ramAllocator;
    RebootDesc* rd = bootConf->rebootDesc;

    /*
     * Tag existing persistent memory as allocated
     */
    for (i = 0; i < rd->hot.prstMem.numChunks; ++i) {
        PrstChunk* chunk = &rd->hot.prstMem.chunks[i];
        ASSERT(CEILING2(chunk->size, PAGE_SIZE_f) == chunk->size);
        if (chunk->status & PRST_CHUNK_ALLOCATED) {
            ram_tag(ram, chunk->paddr, (PhSize) chunk->size, PH_RAM_ALLOCATED);
        }
    }

    /*
     * Extend persistent memory
     */
    for (i = 0; i < rd->hot.prstMem.numChunks; ++i) {
        PrstChunk* chunk = &rd->hot.prstMem.chunks[i];
        ASSERT(CEILING2(chunk->size, PAGE_SIZE_f) == chunk->size);
        if (!(chunk->status & PRST_CHUNK_ALLOCATED)) {
            if (!ram_alloc(ram, &chunk->paddr, (PhSize) chunk->size, 
                           PAGE_SIZE_f)) {
                printf ("can't allocate 0x%x bytes of persistent memory\n", 
                        chunk->size);
                ASSERT(0);
            } else {
                chunk->status |= PRST_CHUNK_ALLOCATED;
            }
        }
    }
}

kernel_start()

The kernel_start() function calls binInstallByMask() (see "binInstallByMask and binInstallByType()") to install the microkernel and all actors that belong to the initial kernel address space. It then retrieves the descriptor pointing to the microkernel binary and jumps to the microkernel entry point, passing the address of the bootConf structure to the microkernel as an argument.

kernel_start() also informs the system debugger that the microkernel was installed in the dedicated memory. From this moment, the debugger has access to the microkernel memory, in order to set break points, for example

kernel_start() assumes that the memory banks containing the microkernel and the installing actors have already been installed and that the destination addresses are already accessible.

Example 3-10 is kernel_start() source code provided in kernel/snippet/nucleus/boot_tools/kernel_start.c.


Example 3-10 kernel_start()

void
kernel_start()
{
    int i;

    /*
     * Install all u-kernel's and actor's KSP sections
     */
    binInstallByMask(BIN_KERNEL | BIN_ACTOR);

    /*
     * Report to the debug agent that the kernel was installed in the memory
     * dedicated for it.
     */
    bootConf->dbgOps.kernelInitLevel(KERNEL_INSTALLED);

    /*
     * Jump to the u-kernel
     */
    for (i = 0; i < bootConf->numBins; ++i) {
        BinDesc* bin = &bootConf->binDesc[i];
        if (bin->type == BIN_KERNEL) {
            (* (KernelEntry) bin->entry)(bootConf);
        }
    }
}

RAM Allocator interface

The RAM Allocator interface is used to allocate and free RAM. During ChorusOS boot and initialization, the RAM occupation is described by a RamDesc object. .The ramAllocator field in the BootConf structure points to the RamDesc object. The ramAllocator field is initialized by the mkimage tool as described in "RAM Occupation".

RAM occupation is described by a particular tag value associated with each address of the physical address space. The tag can have one of the following values:

Three routines are provided for managing RamDesc. These routines can be used by the bootstrap program to update RamDesc according to the target BKI.

PowerPC 60x Bootstrap Implementation Framework

ppc60x_init()

ppc60x_init() initializes the CPU for the PowerPC BKI (see "PowerPC BKI"). This routines applies to PowerPC 60x, 750, and MPC8260 processors. To put the CPU in an appropriate state for the PowerPC 60x BKI, the routine goes through the following steps:

  1. Clears all bits in the MSR register, except the ME and RI bits which are set:

    • disables external interrupts (MSR[EE])

    • disables instruction and data translations (MSR[IR], MSR[DR])

    • disables FPU instructions (MSR[FP])

    • puts the processor into a Supervisor privilege state (MSR[PR])

    • enables machine-check exceptions (MSR[ME)

    • puts the processor into a recoverable exception state (MSR[RI])

  2. Clears and invalidates all Segment Registers (SR) , including all Translation Lookaside Buffer (TLB) entries and Block Address Translation (BAT) registers. Also, resets, disables and/or invalidates all MMU registers.

  3. Invalidates all L1 data cache, for both L1 instruction and data caches and disables internal (L1) memory caches.

In addition, if the CPU is a MCP750 processor, then the L2 cache is also disabled as it is directly interfaced with the CPU.


Example 3-11 ppc60x_init()

#define MPC_750 8

        .text
        
        GLOBAL(ppc60x_init)
ppc60x_init:

        /*
         * Reset MSR register
         */
        li      r9, 0x0
        ori     r9, r9, MSR_ME | MSR_RI
        mtmsr   r9      
        isync
                
        /* 
         * Invalidate all segment registers 
         */
        lis     r9,   0x0
        mtsr    sr0,  r9
        mtsr    sr1,  r9
        mtsr    sr2,  r9
        mtsr    sr3,  r9
        mtsr    sr4,  r9
        mtsr    sr5,  r9
        mtsr    sr6,  r9
        mtsr    sr7,  r9
        mtsr    sr8,  r9
        mtsr    sr9,  r9
        mtsr    sr10, r9
        mtsr    sr11, r9
        mtsr    sr12, r9
        mtsr    sr13, r9
        mtsr    sr14, r9
        mtsr    sr15, r9

        /* 
         * Invalidate all TLB entries
         *
         * Implementation note:
         *
         *   as tlbia in not implemented on 604 nor on MPC750 processors,
         *   we use tlbie in a loop for the 64*4 entries (on 604e)
         *   followed by tlbsync
         */
        mfctr   r9
        li      r7, 64
        mtctr   r7
        li      r7, 0
loop:
        tlbie   r7
        addi    r7, r7, 0x1000
        bdnz    loop
        tlbsync
        mtctr   r9

        /* 
         * Reset BAT registers.
         *
         * Implementation note:
         *
         *   The 604 BAT registers are not initialized by the hardware
         *   after the power-up or reset sequence. Consequently, all valid
         *   bits in both instruction and data BAT areas must be cleared
         *   before setting any BAT area for the first time.
         *   This is true regardless of wether address translation is
         *   enabled. Also, software must avoid overlapping blocks while
         *   updating a BAT area or areas. Even if translation is
         *                               ^^^^^^^^^^^^^^^^^^^^^^
         *   disabled, multiple BAT area hits are treated as programming
         *   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         *   errors and can corrupt the BAT registers and produce
         *              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         *   unpredictable results.
         *
         *   from: "PowerPC 604 User's manual" 
         *         (Chapter 5: Memory management, p 5-13)
         *
         *   Note that this true for MPC750 too.
         */
        li      r9,     0
        mtspr   dbat0u, r9
        mtspr   dbat0l, r9
        mtspr   dbat1u, r9
        mtspr   dbat1l, r9
        mtspr   dbat2u, r9
        mtspr   dbat2l, r9
        mtspr   dbat3u, r9
        mtspr   dbat3l, r9
        mtspr   ibat0u, r9
        mtspr   ibat0l, r9
        mtspr   ibat1u, r9
        mtspr   ibat1l, r9
        mtspr   ibat2u, r9
        mtspr   ibat2l, r9
        mtspr   ibat3u, r9
        mtspr   ibat3l, r9
        isync

        /*
         * Disable L1 instruction and data caches
         */
        mfspr   r9, hid0
        andi.   r10, r9, (HID0_ICACHE_ENABLE | HID0_DCACHE_ENABLE)
        beq     L1Disabled              /* if disabled, nothing to do */
                /*
                 * flush L1 cache
                 */
        li      r3, 1024                /* 1024 blocks of 32 bytes*/
loop_load:
        lwz     r6, 0(r3)               /* load block       */
        addi    r3, r3, 32              /* go to next block */
        bdnz    loop_load
        li      r3, 1024                /* 1024 blocks of 32 bytes*/
        mtctr   r3
        li      r3, 0        
        mtctr   r3,
        li      r3, 0
loop_flush:
        dcbf    r0, r3
        addi    r3, r3, 32              /* cache line size */
        bdnz    loop_flush
                /*
                 * disable L1 cache
                 */
        rlwinm  r9, r9, 0, HID0_dce + 1, HID0_ice - 1
                                        /* Clear HID0[ICE,DCE] bits */
        isync
        mtspr   hid0, r9
        isync
L1Disabled:

        /*
         * Disable MPC750 L2 cache
         */
        mfspr   r9, pvr
        srwi    r9, r9, 16              /* only interested in version */
        cmpwi   r9, MPC_750             /* if not 750, nothing to do  */
        bne     L2Disabled              

        mfspr   r9, l2cr                
        andis.  r10, r9, HIWORDA(L2CR_ENABLE)
        beq     L2Disabled              /* if disabled, nothing to do */     
                                
        rlwinm  r10, r9, 0, L2CR_l2e + 1, 31
                                        /* clear L2CR[L2E] bit  */
        sync                    
        mtspr   l2cr, r10
        sync                    
        isync
L2Disabled:

        blr

MPC8xx PowerPC Bootstrap Implementation Framework

The _start() routine initializes the CPU to the state required by the PowerPC BKI (see "PowerPC BKI"). Example 3-12 applies to the PowerPC MPC8xx micro-controller family. To put the CPU in the state required by the MPC8xx PowerPC BKI, the routine goes through the following steps:

  1. Clears all bits in the MSR register except the ME and RI bits, which are set, and:

    • Disables all external interrupts (MSR[EE])

    • Disables all instruction and data translations (MSR[IR], MSR[DR])

    • Disables FPU instructions (MSR[FP])

    • Puts the processor into Supervisor privilege state (MSR[PR])

    • Enables machine-check exceptions (MSR[ME)

    • Pust the processor in a recoverable exception state (MSR[RI])

  2. Resets instruction and data MMU control registers to clear any TLB reservation. Invalidates the TLB entries and resets, disables and/or invalidates all MMU registers.

  3. The L1 data cache is assumed to be disabled.


Example 3-12 start()

_start:
                               /* Setup init value for MSR */
                addi    r9, r0, (MSR_ME+MSR_RI)
                mtmsr   r9
                               /* Clear reservation of TLB entries */
                lis     r9, 0x0
                mtspr   mi_ctr, r9
                mtspr   md_ctr, r9
                mtspr   m_casid, r9
                mtspr   md_ap, r9
                tlbia          /* invalidate all TLB entries */

                               /* setup stack for boot */
                LoadAddr(r1, stackPtr)  
                bl      start
        loop:
                b       loop   /* Never here */