This chapter describes how to write a bootstrap for your system. The bootstrap is implemented as three binaries:
"Power-up Program Implementation" explains how to write a power-up initialization program
"bootconf Implementation" explains the implementation of the generic bootconf program
"Bootstrap Program Implementation" explains how to write a bootstrap program
"The Boot-Kernel Interface" describes the boot kernel interface
In Figure 4-1 you will see a description of the boot process showing the system image and the binary files that it consists of and the flow of control between them.
The power-up program is required when the operating system boots from the ROM. Otherwise, power-up inititializations are performed by the initial loader (firmware or ChorusOS bootMonitor).
The bootconf binary gets control from the power-up initialization program (or from the initial loader) and launches the bootstrap program. The bootstrap program launches the debug agent, the reboot program and the microkernel, according to the boot-kernel interface (BKI) specification (see "The Boot-Kernel Interface" for details).
The right part of the figure shows the BootConf data structure that is initialized by the bootconf part of the boot program and used to pass information to other binary programs in the system image.
The only jump between programs in the boot process is between the boot program and the microkernel.
This section describes the power-up initialization program, including practical examples.
Example 4-1 is an example of a power-up initialization program for the EST SBC8260 board. The source is provided in source_dir/nucleus/bsp/powerpc/sbc8260/src/boot/trampoline.s.
.text .globl reset reset: /* * Put power up initialization code here */ /* * Jump to bootconf entry point */ xor r3, r3, r3 addis r5, r0, entryPoint@ha /* */ addi r5, r5, entryPoint@l /* */ lwz r5, 0(r5) /* r5 = entryPoint */ mtlr r5 /* */ blr /* (*entryPoint)(); */ .globl __mkimage__label__REF_start __mkimage__label__REF_start: entryPoint: .long 0 /* bootconf entry point */
At image generation the mkimage utility binds the bootconf program entry point to the power-up initialization program, patching the contents of the location labelled __mkimage__label__REF_start with the address of the entry point location labelled by __mkimage__label__start, as described in "bconf_main.c Source File".
This power-up program forces the board-specific (second) argument of the bootconf program to 0, using the argument passing convention described in "bootconf Binary".
This section describes the bootconf program implementation provided in ChorusOS.
The main role of bootconf is to hold the BootConf structure described in "BootConf Structure". The structure is generated by the mkimage tool using the ChorusOS configuration file, as described in Chapter 7, Configuring the ChorusOS System Image. mkimage then links the generated BootConf structure with the program described in this section.
The role of the bootconf code is to install the standalone binaries (such as the bootstrap program, debug agents and debug drivers) and to transfer control to the bootstrap program, passing a pointer to the BootConf structure as an argument.
The bootconf implementation assumes that the memory bank containing the bootconf binary has already been installed at the bank's starting address by the initial loader.
The bootconf code is board-independent; it can be used on any board based on a given processor family. Examples in this section are for the PowerPC family of boards. The implementations for other target families have similar logic and structure.
The bootconf implementation is contained in two source files, bconf_crt0.s and bconf_main.c.
The source_dir/nucleus/bsp/src/family/powerpc/boot_tools/bconf_crt0.s file contains the bootconf start-up routine, bconf_start().
.section .chr.my.rw.vaddr rw_vaddr: .section .chr.my.rw.kaddr rw_kaddr: .section .chr.my.rw.size rw_size: .section .chr.my.bss.vaddr bss_vaddr: .section .chr.my.bss.size bss_size: .section .text GLOBAL(bconf_start) bconf_start: GLOBAL(__mkimage__label__start) __mkimage__label__start: LoadAddr(r5,bconf_main) /* bconf_main(r3) */ b .L1 GLOBAL(bconf_reboot_start) bconf_reboot_start: GLOBAL(__mkimage__label__reboot) __mkimage__label__reboot: LoadAddr(r5,bconf_reboot_main) /* bconf_reboot_main(r3, r4) */ b .L1 .L1: /* * Copy read/write segment if necessary * * (note that the segment can include the code that * we are executing) */ LoadAddr(r8, rw_kaddr) /* r8 = src_addr */ LoadAddr(r9, rw_vaddr) /* r9 = dst_addr */ LoadAddr(r10, rw_size) /* r10 = size */ cmpwi r10,0 /* if (size) */ beq .L3 /* */ cmpw r8, r9 /* if (dst_addr != src_addr) */ beq .L3 /* */ .L2: /* do { */ lbz r0,0(r8) /* r0 = *src_addr */ stb r0,0(r9) /* *dst_addr = r0 */ addi r8,r8,1 /* ++src_addrq */ addi r9,r9,1 /* ++dst_addr */ subic. r10,r10,1 /* --size */ bne .L2 /* } while (size) */ .L3: /* */ /* * Zero bss segment if necessary */ LoadAddr(r9, bss_vaddr) /* r9 = dst_addr */ LoadAddr(r10, bss_size) /* r10 = size */ cmpwi r10,0 /* if (size) */ beq .L5 /* */ li r0,0 /* r0 = 0 */ .L4: /* do { */ stb r0,0(r9) /* *dst_addr = 0 */ addi r9,r9,1 /* ++dst_addr */ subic. r10,r10,1 /* --size */ bne .L4 /* } while (size) */ .L5: /* * Setup the stack */ stack_setup: LoadAddr(r1, heap) /* sp = heap[] */ LoadAddr(r9, heapSize) /* */ lwz r9, 0(r9) /* */ add r1, r1, r9 /* sp = sp + heapSize */ /* * Jump to the main * * (note that if the code was also copied we must jump into * the copy) */ jump_to_main: mtlr r5 blr
The bootconf program defines two entry points bconf_start() and bconf_reboot_start(). The power-up initialization program enters using bconf_start() for a cold boot, whereas the reboot program enters using bconf_reboot_start() in the hot reboot path.
The install_me() routine tests whether the read-write segment and bss segment are link-edited in place (that is, at their places within the memory bank). To obtain the segment's layout within its memory bank and link-editing address, bconf_start() refers to predefined sections that mkimage relocated as follows:
The .chr.my.rw.vaddr section indicates the base link-editing address of the read-write segment.
The .chr.my.rw.kaddr section indicates the beginning of the read-write segment's image within the memory bank.
The .chr.my.rw.size section indicates the size of the read-write segment.
The .chr.my.bss.vaddr section indicates the base link-editing address of the bss segment.
The .chr.my.bss.size section indicates the size of the bss segment.
If necessary, install_me() copies the read-write segment and initializes the bss segment to zero. Note that the bootconf code can be part of the read-write segment.
The stack_setup() routine initializes the stack pointer with the limit of the heap. The heap location and size are defined by heapStart and heapLimit (see "Heap and Stack").
The jump_to_main() routine jumps to the bconf_main() routine. If the bootconf code is part of the previously copied read-write segment, this jump transfers control from the original to the copy. The bconf_start program passes the content of register r3 as an opaque argument to bconf_main(). Note that this jump works without calling sync/isync instructions as caching is disabled at this time.
The __mkimage_label_start label enables the power-up program to transfer control to the bootconf binary, as described in Example 4-1.
Note also that bconf_crt0.s preserves the value of r3 and r4 to allow callers to pass, at most, two arguments to the subsequent programs. For example, the initial loader can pass one board-dependent argument to the bootstrap program in r3, whereas the reboot program can pass two arguments (see "Hot Reboot").
The source_dir/nucleus/bsp/src/boot_tools/bconf_main.c file contains the bootconf main routine, bconf_main().
BootConfRebootEntry __mkimage__label__REF_reboot = 0; void bconf_main(void* cookie) { int i; /* * Register hot reboot entry */ bootConf->rebootEntry = __mkimage__label__REF_reboot; /* * Install all standalone sections */ binInstallByMask(BIN_STANDALONE); /* * Launch bootstrap program */ for (i = 0; i < bootConf->numBins; ++i) { BinDesc* bin = &bootConf->binDesc[i]; if (bin->type == BIN_BOOTSTRAP) { (* (BootstrapEntry) bin->entry)(bootConf, cookie); } } }
First of all, the bconf_main() routine registers the bootconf hot reboot entry point in the BootConf structure. Note that the registered address must be a reference to the bootconf image. This address can be different from the entry point's link-edit address if the bootconf text segment is not XIP. To provide the reference, the mkimage tool stores the system image address of the location __mkimage__label__reboot in the data location __mkimage__label__REF_reboot.
The bconf_main() routine calls binInstallByMask() to copy (if necessary), the read-write segments of all standalone binaries and initializes their bss segments to zero. Calling binInstallByMask() at this point, bconf_main() assumes that any memory banks containing standalone binaries have already been installed at the required address. See "binInstallByMask() and binInstallByType()" for more information.
The binInstallByMask() function is implemented by the bootstrap framework library (see "Common Bootstrap Implementation Framework").
Finally, the bconf_main() function retrieves the descriptor that points to the bootstrap program binary in the BootConf structure, and jumps to its entry point. It passes as arguments the address of the BootConf structure and an opaque value from the initial loader.
The source_dir/nucleus/bsp/src/boot_tools/bconf_main.c file also contains the second main routine, bconf_reboot_main(), which is used for performing a hot reboot:
void bconf_reboot_main(RebootDesc* rd, void* cookie) { if (rd) { /* * Restore the pointer to the persistent memory description */ bootConf->rebootDesc = rd; } /* * Join the cold bootstrap path */ bconf_main(cookie); }
bconf_reboot_main() registers a reference to the system state, preserved by the hot reboot (see "HotRebootDesc Structure" for details) in the BootConf structure, and then joins the cold reboot pass.
This section includes information on the common bootstrap implementation framework and information on target specific bootstrap implementation framework.
A typical bootstrap program does the following:
Initializes CPU items such as interrupts and the memory cache. This depends on the state in which the initial loader (or power-up initialization) leaves the CPU.
Installs banks, as specified by the BootConf structure.
Discovers existing RAM and modifies the ramAllocator field in the BootConf structure.
If necessary, builds the initial microkernel virtual address space, as defined by the kernelSpace field in the BootConf structure.
Launches the microkernel.
Example 4-5 is the bootstrap program provided for the SBC8260 board. The source code is provided in source_dir/nucleus/bsp/powerpc/sbc8260/src/boot/boot.c.
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 */ }
The start() function 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 initialization program, the value of bootp is NULL. However, for the reboot program (see Example 5-2) , bootp points to a data structure containing the ChorusOS 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, bootMonitor) the bootstrap program checks that the structure had not already been corrupted by the system currently booting.
The ppc60x_init() function is the PowerPC 60x initialization program, and is described in "PowerPC 60x Bootstrap Implementation Framework".
The binCheckOverlap(), dbg_start(), ram_tag(), reboot_start(), prstInstall()and kernel_start() functions are target-independent routines, and are described in "Common Bootstrap Implementation Framework".
The buildDeviceTree() function is board-specific and builds the initial state of the SBC8260 device tree (see "Initial Device Tree").
This section describes the common routines used in the bootstrap implementation.
Example 4-6 is an example of binInstallByMask() and binInstallByType() source code, provided in source_dir/nucleus/bsp/src/boot_tools/binInstall.c.
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 4-1.
Table 4-1 mask Bitsmask Bit | Meaning |
---|---|
BIN_STANDALONE | install bootstrap program and debug agent binaries |
BIN_KERNEL | install microkernel |
BIN_ACTOR | install built-in drivers and actors |
The binInstallByMask() function 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 initializes the bss segment. binInstallOne() only processes segments that belong to the initial microkernel address space, SEG_KSP.
The binInstallOne() function assumes that the memory banks containing the installing binaries have already been installed and that the destination addresses are already accessible.
The dbg_start() function starts the debug agent drivers and the debug agent (see "The Debug Agent"). Example 4-7 is dbg_start() source code provided in source_dir/nucleus/bsp/src/boot_tools/dbg_start.c.
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); }
The dbg_start() function 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 the BootConf structure 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.
The dbg_start() function assumes that the debug agent and its driver are already installed.
Finally, dbg_start() makes console I/O available for the bootstrap program by initializing its printf()/scanf() support library.
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. The reboot_start() function differentiates between a cold boot and a hot boot by testing the value of the rebootDesc field in the BootConf structure:
Initially, in the case of a cold boot, rebootDesc is NULL. The reboot program subsequently initializes rebootDesc with a non-zero value (see "Reboot Program Initialization") . During the subsequent hot boot, the value of rebootDesc is passed from the reboot program to the bootconf program (see "Hot Reboot").
For a hot boot, rebootDesc is also initially set to NULL. The bootconf program then re-initializes the field with the argument passed from the reboot program (see Example 4-5).
The reboot_start() function calls binInstallByType() (see "binInstallByMask() and binInstallByType()") to install the reboot program. It then retrieves the descriptor that points to the reboot program binary and jumps to its entry point, passing the address of the BootConf structure as an argument.
The reboot_start() function assumes that the memory banks containing the reboot program have already been installed and that the destination addresses are already accessible.
Code Example 4-8 is reboot_start() source code provided in source_dir/nucleus/bsp/src/boot_tools/reboot_start.c.
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); } } }
The prstInstall() function is used by the bootstrap program to check whether:
there are RAM blocks already occupied by persistent memory devices that must be registered (tagged) as allocated
there are requests for additional RAM blocks to be allocated for new persistent memory devices
Code Example 4-9 is prstInstall() source code provided in source_dir/nucleus/bsp/src/boot_tools/prstInstall.c.
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; } } } }
The kernel_start() function calls binInstallByMask() (see "binInstallByMask() and binInstallByType()") to install the microkernel and all actors that belong to the initial microkernel 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.
The kernel_start() function 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 breakpoints, for example.
The kernel_start() function assumes that the memory banks containing the microkernel and the installing actors have already been installed and that the destination addresses are already accessible.
Code Example 4-10 is kernel_start() source code provided in source_dir/nucleus/bsp/src/boot_tools/kernel_start.c.
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); } } }
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:
RAM_ALLOCATED, indicating that the physical address belongs to an occupied portion of RAM
RAM_FREE, indicating that the physical address belongs to a portion of RAM available for allocation
RAM_NONEXISTENT, indicating that the physical address does not belong to RAM (or is not available for allocation as RAM)
Three routines are provided for managing RamDesc
. These
routines can be used by the bootstrap program to update RamDesc
according to
the target BKI.
ram_tag()
void ram_tag (RamDesc* ram, PhAddr paddr, Phsize size, int tag);
The ram_tag() function attempts to change the tag values of all addresses from the address range of size size starting from paddr to the value specified by tag. The ram_tag() function only changes a tag if the initial value is RAM_FREE or RAM_NONEXISTENT. It does not change a tag if the initial value is RAM_ALLOCATED.
ram_alloc()
int ram_alloc (RamDesc* ram, PhAddr*, paddr, PhSize size, int align);
The ram_alloc() function allocates a portion of available RAM, tags the corresponding address range as RAM_ALLOCATED, and returns the physical address of the allocated RAM in the variable that paddr points to. align specifies the RAM alignment constraints (in bytes), or is 0 if there are no constraints. ram_alloc() returns 1 if successful, and 0 if the allocation failed.
ram_free()
void ram_free (RamDesc* ram, PhAddr paddr, PhSize size);
The ram_free() function changes the tag values for the specified range of physical addresses to RAM_FREE.
The ppc60x_init() function initializes the CPU for the PowerPC BKI (see "PowerPC BKI"). This routine 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:
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])
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.
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.
#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
The _start() routine initializes the CPU to the state required by the PowerPC BKI (see "PowerPC BKI"). Code Example 4-12 applies to the PowerPC MPC8xx microcontroller family. To put the CPU in the state required by the MPC8xx PowerPC BKI, the routine goes through the following steps:
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])
Puts the processor in a recoverable exception state (MSR[RI])
Resets instruction and data MMU control registers to clear any Translation Lookaside Buffer (TLB) reservation. Invalidates the TLB entries and resets, disables and/or invalidates all MMU registers.
The L1 data cache is assumed to be disabled.
_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 */
This section describes the Boot-Kernel interface, including both common and target specific BKI.
The Boot-Kernel interface (BKI) is a set of rules used when the kernel is launched. Part of the BKI is common to all target families, and part of it is specific to a given family. A family-specific BKI can define a number of dialects. The microkernel binary must be configured to understand the dialect required by the bootstrap program.
The bootstrap program must transfer control to the microkernel entry point, passing, in a register, a pointer to an object of type BootConf (see "BootConf Structure"). The register name or number is defined by the family-specific BKI. The BootConf structure and all objects that it points to must be accessible for both reading and writing.
The microkernel code assumes that all text and data are already at the correct execution addresses, that the data is writable, and that non-initialized data is already set to zero.
All interrupts must be disabled at the CPU level before the bootstrap program transfers control to the microkernel. The microkernel assumes that all available memory caches that can be disabled are already disabled and have been properly flushed.
The microkernel does not expect a valid program stack from the bootstrap program.
The BootConf structure must be in the following state:
binDesc must describe the binaries
of the microkernel and of all drivers and actors that the microkernel will
launch. The microkernel assumes that all segments of type SEG_KSP
are already installed at their execution addresses and that the segment's
zero-initialized data is already set to zero. binDesc
can contain descriptors of other binaries. See "Binaries"
for more information about binDesc.
ramAllocator must tag as RAM_FREE the portions of RAM available for immediate allocation.
All other RAM must be tagged as RAM_ALLOCATED. When the
system initialization procedure no longer requires the BootConf structure, the microkernel can reuse the RAM occupied by binaries
of the types BIN_BOOTSTRAP
and BIN_BOOTCONF
.
The device tree is a data structure that describes hardware topology and device properties. The hardware topology is specified in terms of parent/child relationships. Each device node has some generic and some specific properties associated with it. A device property is a name/value pair. The property name is a null-terminated ASCII string. The property value is a sequence of bytes specified by a length/address pair.
The initial device tree is built by the bootstrap program using the DKI tree browsing API. See Chapter 9, Driver Kernel Interface Overview for information about the DKI interface and the device tree.
Generic device node properties used by the microkernel are defined in ddi/prop.h, as shown in code Example 4-13.
/* * Generic properties: * * The "byte-order" property specifies the byte ordering on cpu/bus. * name: PROP_BYTE_ORDER * value: PropByteOrder * constants: BYTE_ORDER_BIG - big-endian byte order * BYTE_ORDER_LITTLE - little-endian byte order * * The "clock-freq" property specifies the CPU/bus clock frequency in Hz. * name: PROP_CLOCK_FREQ * value: PropClockFreq * * The "active" property flags active device nodes, i.e. devices * which are already serviced by drivers. * name: PROP_ACTIVE * value: - * * The "driver" property specifies the name of driver which should be * binded to the node: * name: PROP_DRIVER * value: char[] (NULL terminated ASCII string) * * The "system-tick" property flags a timer device node which * is assigned to the system tick. * name: PROP_SYSTEM_TICK * value: - * * The "perf-tick" property flags a timer device node which is * assigned to the PERF module. * name: PROP_PERF_TICK * value: - * * The "prof-tick" property flags a timer device node which is * assigned to the PROF module. * name: PROP_PROF_TICK * value: - * * The "ram-size" property specifies the amount of available RAM memory * name: PROP_RAM_SIZE * value: PhSize * * The "family" property specifies the name of the product family * name: PROP_FAMILY * value: char[] * * The "platform" property specifies the name of the target platform * name: PROP_PLATFORM * value: char[] * * The "cache-size" property specifies the size of the L2 cache * name: PROP_CACHE_SIZE * value: PropCacheSize * * The "banks" property specifies the physical memory layout * name: PROP_BANKS * value: PhChunk[] * * The "timer-freq" property specifies the frequency of a * given timer. * name: PROP_TIMER_FREQ * value: PropTimerFreq * * The "dbg-link" property flags a device node which is assigned to * the debugging agent. * name: PROP_DBG_LINK * value: - */ #define PROP_BYTE_ORDER "byte-order" #define PROP_CLOCK_FREQ "clock-freq" #define PROP_ACTIVE "active" #define PROP_DRIVER "driver" #define PROP_SYSTEM_TICK "system-tick" #define PROP_PERF_TICK "perf-tick" #define PROP_PROF_TICK "prof-tick" #define PROP_SYSTEM_PIC "system-pic" #define PROP_RAM_SIZE "ram-size" #define PROP_FAMILY "family" #define PROP_PLATFORM "platform" #define PROP_CACHE_SIZE "cache-size" #define PROP_BANKS "banks" #define PROP_TIMER_FREQ "timer-freq" #define PROP_DBG_LINK "dbg-link" #define PROP_SYSTEM_SPEAKER "system-speaker" typedef uint32_f PropByteOrder; #define BYTE_ORDER_BIG 0x00010203 /* big-endian */ #define BYTE_ORDER_LITTLE 0x03020100 /* little-endian */ typedef uint32_f PropClockFreq; typedef uint32_f PropTimerFreq; typedef PhSize PropRamSize; typedef uint32_f PropCacheSize; /* Values for PROP_NODE property, common to all architectures */ #define NODE_ROOT "local-bus" #define NODE_CPU "cpu" #define NODE_MEM "ram" #define NODE_L2_CACHE "l2-cache"
Family specific properties are defined in ddi/ddi_f.h.
The microkernel requires that the device tree contains the following device nodes and properties:
The root node must be defined and have the following properties:
PROP_NODE must have the value NODE_ROOT.
PROP_FAMILY must be defined and identify the processor family. The value of the property is a family-dependent string, such as FAMILY_POWERPC or FAMILY_INTEL.
PROP_PLATFORM must be defined and identify the board. The value is a platform-dependent string, such as "Motorola MCP7xx" or "Intel x86 PC/AT".
The CPU node must be defined and have the following properties:
PROP_NODE must have the value NODE_CPU.
PROP_CLOCK_FREQ specifies the CPU clock frequency. Its value is of type PropClockFreq.
A timer device node with the PROP_SYSTEM_TICK property must be defined, to describe the system clock.
Optionally, a second timer device node with the PROP_PERF_TICK property can be used by the microkernel for benchmarks.
This section details the state that the CPU should be in for PowerPC BKI acceptance.
For historical reasons, the naming convention for PowerPC BKI constants uses PPC60x, instead of PowerPC. The original PowerPC BKI was written for the PPC60x PowerPC BKI, however, all PowerPC processor families (8xx, 8260, 60x) now share the same BKI.
Register r3 must contain a pointer to the BootConf structure.
External interrupts must be disabled.
Instruction and data translations must be disabled.
Internal (L1) memory cache must be disabled.
The MMU must be disabled; all MMU registers and Translation Lookaside Buffer (TLB) entries, if any, must be in a reset/disabled/invalidated state.
The processor must be in Supervisor privilege state.
Machine-check exceptions must be enabled.
The processor must be in a recoverable exception state.
Floating point instructions must be disabled.
The kernelSpace field in the BootConf structure must point to a description of the initial
space that must be established by the microkernel's memory management unit,
as described in "The Initial Address Space". The family-dependent
part of the tag field of the PhChunk
entry is defined as a combination of the following bits:
KSP_PPC60x_BAT bit specifies
that the mapping must be established using two Block Address Translation (BAT)
registers. In this case, the mapping specified by PhChunk
(the
physical address, the corresponding virtual address and the size) must be
properly aligned as required by the PowerPC 60x specifications. The microkernel
will establish the required mapping in one data BAT (DBAT) and one instruction
BAT (IBAT). Therefore, only four kernelSpace entries
can have BAT mappings. If the KSP_PPC60x_BAT bit is not
specified, the microkernel uses page address translations to establish the
required mapping.
KSP_PPC60x_W bit specifies that the write-through memory access attribute must be set in the corresponding BAT or PTEs.
KSP_PPC60x_I bit specifies that the caching-inhibited memory access attribute must be set in the corresponding BAT or PTEs.
KSP_PPC60x_M bit specifies that the memory coherency memory access attribute must be set in the corresponding BAT or PTEs.
KSP_PPC60x_G bit specifies that the guarded memory access attribute must be set in the corresponding BAT or PTEs.
KSP_PPC60x_RW bits specify that read and write access are allowed.
KSP_PPC60x_RO bits specify that only read access is allowed.
This section details the state that the CPU should be in for the x86 BKI.
The BootConf structure pointer is pushed onto the stack before calling the microkernel entry point.
Interrupts must be disabled.
The processor must be in protected mode, and all the segments must cover the entire address space.
Translation must be disabled.
This section details the state that the CPU should be in for the UltraSPARC IIi BKI.
Register %o0 must contain a pointer to the BootConf structure.
Interrupts must be disabled.
Instruction and data caches must be disabled.
At address 0, a minimal trap table must be installed with spill/fill trap handlers.
MMU translation must be enabled.
An initial one-to-one mapping must be built by the boot program. This one-to-one mapping must start at 0 and be large enough to cover all virtual addresses for the memory bank's binary segments. Note that instruction and data Translation Lookaside Buffer (TLB) mapped in this way will initially be locked and all TLB entries not included in the initial mapping will be invalid.
The kernelSpace field in the BootConf structure must point to a description of the initial
space established by the microkernel's memory management unit, as described
in "The Initial Address Space". The family-dependent part of the
tag field of the PhChunk
entry is defined as a combination of
the following bits:
PhChunk
KSP Region TypePhChunk Bit | Meaning |
---|---|
KSP_US_TYPE_RAM | one-to-one RAM mapping |
Table 4-3
PHChunk
Data AttributesPhChunk Bit | Meaning |
---|---|
KSP_US_ATTR_DG | global |
KSP_US_ATTR_DW | writable |
KSP_US_ATTR_DP | privileged |
KSP_US_ATTR_DE | side effect |
KSP_US_ATTR_DCV | cached virtually |
KSP_US_ATTR_DCP | cached physically (L2-cache) |
KSP_US_ATTR_DIE | inverted endiannes |
KSP_US_ATTR_DNFO | non faulted only |
KSP_US_ATTR_DV | valid |
Table 4-4
PHChunk
Instruction AttributesPHChunk Bit | Meaning |
---|---|
KSP_US_ATTR_IG | global |
KSP_US_ATTR_IV | valid |
KSP_US_ATTR_IP | privileged |
KSP_US_ATTR_ICP | cached physically (L2-cache) |
The initial kernel space must at least contain the one-to-one mapping of the available RAM.
The f_bootConf field of the BootConf structure must point to a processor specific decriptor:
typedef struct usparc_BootConf { VmAddr tlb_lock_start; /* start, size and attributes of the memory */ VmSize tlb_lock_size; /* region locked in I and D TLB by */ int tlb_lock_attr; /* the bootstrap program */ VmAddr trap_table_start; /* address of the trap table */ } usparc_BootConf;
The first three fields decribe the initial one-to-one mapping. The last
one gives the address of the trap table. The SPARC_PROP_I_CACHE
and SPARC_PROP_D_CACHE properties must be present in the
initial device tree. Values for these properties are of the type SparcPropCache
:
typedef struct { uint32_f csize; /* cache size */ uint32_f bsize; /* cache bank size */ uint32_f lsize; /* cache line size */ uint32_f nbanks; /* number of bunks (csize = bsize * nbanks) */ uint32_f type; /* cache type */ } SparcPropCache;