This chapter explains how to write a reboot program for ChorusOS. The reboot program is implemented as a separate binary executable file. It is installed by the bootstrap program during the cold boot procedure. The reboot program implements board-specific support for the sysReboot() system call. This chapter covers:
"Writing a Reboot Program for the ChorusOS Operating System" explains the implementation of a reboot program, using the SBC8260 board as an example.
"Reboot Program Implementation Framework" describes the library routines that can be used in a reboot program.
This section describes how to write a reboot program for the ChorusOS operating system, by taking a practical example of the implementation of the SBC8260 reboot program. It describes how the reboot program is initialized during the system boot, contains information on the global state of the reboot program and describes how the reboot program proceeds to reboot the system.
Code Example 5-1 is the reboot program initialization source code provided in source_dir/nucleus/bsp/powerpc/sbc8260/src/reboot/reboot.c.
#define MAX_CHUNKS 16 #define STR_LENGTH 256 PrstChunk chunks[MAX_CHUNKS]; char chunk_id_buf[STR_LENGTH]; RebootDesc rebootDesc; BootConfRebootEntry rebootEntry; DbgOps_reset resetOp; BootParams bootParams; void reboot_main(BootConf* conf, BootParams* bootp) { if (conf->rebootDesc == 0) { /* * This is a cold reboot. */ /* * Initialize printf()/scanf() support library */ _stdc_consInit(conf->dbgOps.consRead, conf->dbgOps.consWrite); /* * Register hot reboot descriptor */ rebootDesc.rebootOp = do_reboot; rebootDesc.hot.prstMem.numChunks = 0; rebootDesc.hot.prstMem.chunks = chunks; rebootDesc.hot.prstMem.maxChunks = MAX_CHUNKS; rebootDesc.hot.prstMem.curStrLen = 0; rebootDesc.hot.prstMem.maxStrLen = STR_LENGTH; rebootDesc.hot.prstMem.str = chunk_id_buf; conf->rebootDesc = &rebootdesc; /* * Save hot reboot entry point */ rebootEntry = conf->rebootEntry; /* * Save reset entry point */ resetOp = conf->dbgOps.reset; /* * Save bootparams */ if (bootp) { bootParams = *bootp; } } else { /* * This is a hot reboot. * * Nothing to do as the reboot program was initialized at the * last cold reboot. */ } }
The reboot program is installed only once during a cold boot. For subsequent hot reboots, the reboot program maintains the section of the system state that must be kept. The reboot_main() function is the reboot program entry point and is called by the bootstrap program each time the system boots.
For a cold boot, when the rebootDesc field of the conf argument is NULL, the reboot program initializes its static data. Whereas for a hot boot the reboot program re-initialization is empty.
One purpose of the program is to maintain a stable system state during hot reboots. This state is represented by a generic HotRebootDesc structure (see "HotRebootDesc Structure") and a board specific BootParams structure (see "Bootstrap Program Implementation").
The bootstrap program exports the address of the reboot service routine, do_reboot(), in the rebootOp field of the rebootDesc structure. The microkernel jumps to this routine to proceed with any kind of reboot. See "HotRebootDesc Structure" for details.
Code Example 5-2 is the HotRebootDesc structure definition provided in source_dir/nucleus/sys/common/src/kern/bki/reboot.h.
/* * PrstChunk::status bit values */ #define PRST_CHUNK_ALLOCATED 0x1 #define PRST_CHUNK_MAPPED 0x2 /* * Descriptor of a chunk of persistent memory */ typedef struct PrstChunk { char* id; /* name string */ int status; /* status bit string */ VmSize size; /* size in bytes */ PhAddr paddr; /* starting physical address * valid if PRST_CHUNK_ALLOCATED is set */ VmAddr vaddr; /* starting virtual address */ * valid if PRST_CHUNK_MAPPED is set */ } PrstChunk; /* * Persistent memory descriptor */ typedef struct PrstMem { int maxChunks; /* max number of persistent memory chunks */ int numChunks; /* number of persistent memory chunks */ PrstChunk* chunks; /* array of persistent memory chunks */ int maxStrLen; /* max cumulated size of chunk ids */ int curStrLen; /* current cumulated size of chunk ids */ char* str; /* chunk ids buffer*/ } PrstMem; /* * The state kept over a hot reboot */ typedef struct HotRebootDesc { PrstMem prstMem; /* persistent memory descriptor */ } HotRebootDesc;
The prstMem field of HotRebootDesc describes the persistent memory (the portion of system RAM that must remain the same during subsequent hot reboots). The description is an array of PrstChunk structures, each one describing a persistent memory device (a contiguous named portion of persistent memory). The array is statically allocated in the reboot program data segment.
A persistent memory device is described by its symbolic name, id, the starting physical address, paddr, the starting virtual addresses, vaddr, and the size.
Different operating system components are involved in persistent memory device allocations:
The allocation request comes from the sysReboot() system call. The system call specifies the name and size of the device to be created during the subsequent hot boot.
The reboot program allocates a new PrstChunk descriptor and initializes the id and size fields with the values specified by the sysReboot() call. See "Hot Reboot" for details.
Then, after reboot, the bootstrap program dynamically allocates RAM blocks and initializes the paddr field. It also sets the PRST_CHUNK_ALLOCATED bit in the status field. See "kernel_start()" for details.
Finally, the microkernel allocates a virtual address range and sets the vaddr field. It also sets the PRST_CHUNK_MAPPED bit in the status field.
This section describes how the reboot program reboots the board. It describes the implementations of the cold and hot reboot, as well as providing some information on how the reboot program boots a new system image.
Code Example 5-3 is the reboot service routine source code provided in source_dir/nucleus/bsp/powerpc/sbc8260/src/reboot/reboot.c
void do_reboot(RebootDesc* rd, KnRebootReq* req) { ASSERT(rd == &rebootDesc); if (req->mode == K_REBOOT_COLD) { rebootCold(req); } if (req->mode == K_REBOOT_HOT) { rebootHot(req); } if (req->mode == K_REBOOT_NEW) { rebootNew(req); } ASSERT(0); }
This service routine dispatches execution to a particular reboot procedure.
Code Example 5-4 is the cold reboot service routine source code provided in source_dir/nucleus/bsp/powerpc/sbc8260/src/reboot/reboot.c.
void rebootCold(KnRebootReq* req) { printf ("Cold reboot ...\n"); resetOp(0); }
This cold reboot service calls the debug agent hardware reset service routine.
Code Example 5-5 is the hot reboot service routine source code provided in source_dir/nucleus/bsp/powerpc/sbc8260/src/reboot/reboot.c.
#define STACK_SIZE 0x400 char stack[STACK_SIZE]; void rebootHot(KnRebootReq* req) { printf ("Hot reboot ...\n"); /* * Extend the persistent memory w.r.t the reboot request */ prstExtend(&rebootDesc.hot.prstMem, &req->u.hot); /* * switch to a private stack */ call_and_switch_stack(req, rebootHot_cont, stack + STACK_SIZE); } void rebootHot_cont(KnRebootReq* req) { /* * Disable I/D-Caches. */ cachesDisable(); /* * Disable pagination. */ setMSR(getMSR() & ~(MSR_IR | MSR_DR)); /* * Reboot */ rebootEntry(&rebootDesc, &bootParams); }
This hot reboot service routine:
Allocates, if required, a new persistent memory device descriptor. See "prstExtend() Routine" for details.
Enters in the real mode, disabling the memory management unit.
Jumps to the bootconf program reboot entry point.
Note that the implementation switches from the original stack to a small stack area which is statically allocated in the reboot program data segment. This occurs prior to the MMU disabling to ensure that the current stack is safely accessible in the real mode. See "call_and_switch_stack() routine" for details.
The ChorusOS boot monitor is implemented as an application on top of ChorusOS. The boot monitor reads the new system image from an external medium (for example, a local device, or the network) and stores that image in a buffer. It then performs a sysReboot() request that eventually invokes the rebootNew() service routine. To simplify the reboot implementation, the ChorusOS boot monitor has to use a ChorusOS system, configured with flat memory management, model. Code Example 5-6 is the service routine source code provided in source_dir/nucleus/bsp/powerpc/sbc8260/src/reboot/reboot.c.
void rebootNew(KnRebootReq* req) { KnNewRebootReq* nreq = >u.nw; BootParams* bootp; printf ("Boot new image ...\n"); if (nreq->workSize < (launch_end - launch_start) + sizeof(BootParams)) { printf ("Not enough working memory to reboot\n"); printf ("Try cold reboot\n"); resetOp(0); } /* * Disable I/D-Caches. */ cachesDisable(); if (nreq->ipcSiteNb) { /* * Get Chours IPC site number from the loader */ bootParams.ipcSiteNb = nreq->ipcSiteNb; } /* * Copy bootParams at the end of the working area */ bootp = (BootParams*) (nreq->workAddr + nreq->workSize - sizeof(BootParams)); bcopy(&bootParams, bootp, sizeof(BootParams)); /* * Copy the launch routine to the working memory */ bcopy(launch_start, (void*) nreq->workAddr, launch_end - launch_start); /* * Jump to the copied launch code */ ((launch) nreq->workAddr)(nreq->dstAddr, nreq->srcAddr, nreq->size, nreq->entry, bootp); }
The caller (that is, sysReboot()) provides a working area that is separate to that from the current system image, including the currently executing reboot program. The buffer, holding the new system image, and the system image destination area are also outside the working area. In the working area, the rebootNew() routine installs the bootParams structure and a small positional independent program, called launch(), and then runs launch(). The launch() program copies the new system image from the buffer to the destination address and jumps to its entry point, passing the address of the bootParams structure as the first argument. See "launch() Routine" for details.
This section describes the library routines that can be used by reboot programs.
The prstExtend() routine can be used to allocate a new persistent memory device descriptor. Code Example 5-7 is the prstExtend() routine source code provided in source_dir/nucleus/bsp/src/boot_tools/prstExtend.c.
void prstExtend(PrstMem* pm, KnHotRebootReq* hreq) { if (hreq->prstMemExtension) { if (pm->numChunks == pm->maxChunks) { printf ("Hot reboot can't allocate more than %d presistent " "memory devices\n", pm->maxChunks); } else { int id_len = strlen(hreq->prstMemId) + 1; if (pm->curStrLen + id_len > pm->maxStrLen) { printf ("Hot reboot can't allocate more than %d bytes " "for persistent memory device ids\n", pm->maxStrLen); } else { PrstChunk* ch_cur = pm->chunks+ pm->numChunks; ch_cur->status = 0; ch_cur->size = (PhSize) hreq->prstMemExtension; ch_cur->id = pm->str + pm->curStrLen; strcpy(ch_cur->id, hreq->prstMemId); pm->curStrLen += id_len; pm->numChunks += 1; } } } }
The prstExtend() routine allocates the next available entry in the chunk array (the array is held within the persistent memory descriptor and is pointed to by the pm argument). The routine also copies the symbolic name of the new memory device from the caller address space into the str buffer (also held within the persistent memory descriptor). Note that the persistent memory descriptor is typically allocated within the reboot program data segment.
Code Example 5-8 is the call_and_switch_stack() routine source code provided in source_dir/nucleus/bsp/src/boot_tools/call_and_switch_stack.s.
/* * void * call_and_switch_stack(void* cookie, void* new_pc, * void* new_sp) */ GLOBAL(call_and_switch_stack) call_and_switch_stack: mr r5, r1 /* sp = new_sp */ mtlr r4, lr /* (*new_pc) (cookie) */ blr
The call_and_switch_stack() routine switches to the stack provided as argument three, new_pc, jumps to the function specified by the second argument, new_pc, passing to that function the first argument, cookie.
The launch() routine source code provided in source_dir/nucleus/bsp/src/family/powerpc/boot_tools/launch.s.
The launch() function copies size bytes from the source address, src, to the destination address, dst, and then jumps to the location entry, passing cookie as the argument.
The launch() routine is position-independent. The caller can copy the instructions, starting from the address launch_start() up to the address launch_end(), to any destination location and execute them there.
The launch() routine does not require a stack and is not required to preserve any general register state.