Just a fan site. Looking for official stuff? Try microware.com.

http://os9al.com Ed# 02-03-15 18:47

{pwd}
Understanding Low-Level Booters
home action appnotes consultants history links os-9 al systems vendors

7,191 visits (2 today, 2 this week, 19 this month, 382 this year) [1 yesterday. Best day was 9 visitors]

Understanding Low-Level Booters by OS-9 Al

The purpose of a BOOTER module is to load or locate the OS-9 boot file. Standard booters available with OS-9 allow loading the OS from floppy disk, hard drive, ethernet (bootp/tftp), kermit (serial port), and RAM/ROM.

All low-level booters contain a special function called p2start which will fill in certain elements of the Rominfo structure. This enables the low-level system to locate boot services. An commented example of a typical p2start function is shown here:

The booter will create a structure of settings and parameters. This structure will contain all the standard parameters a booter can have. A typical booter might declare this structure as a global and then fill it in inside the p2start routine. A booter module might contain several ways of booting. For instance, the romboot.c booter has two modes. The "load from ROM" mode simply scans the memory region looking for the kernel. Once located, it allows the system to boot from that ROM area. The "load to RAM" mode starts out the same, then allocates a chunk of RAM and copies the ROM image into RAM and then boots from RAM (for speed). For this example, we will show how two boot services can be patched in from one p2start routine.

bootdev			bootmethod1, bootmethod2;

The routine will be called with two parameters. The first is a pointer to the low-level ROM info structure. The second is a pointer to global storage.

error_code p2start( Rominfo rinf, u_char *globals )
{

To simplify things, we obtain the pointer to the ROM info boots structure. We also create a pointer to a boot device record, which we shall build in a moment.

 	Boot_svcs 	control = rinf->boots;
   	Bootdev 	booter;

If the system has no boot services, the booter can't install and will return with a "No Boot System" error code.

   	if ( control==NULL) {
   		return EOS_NOBOOTSYS;
   	}

If a boot system is present, a structure is created which contains function pointers and other things.

    booter 				= &bootmethod1;		/* 
   	booter->struct_id 	= BOOTDEVID;		/* this is a booter */
   	booter->struct_ver 	= BDV_VER_MAX;		/* ? */
   	booter->bt_probe 	= &probe;			/* pointer to probe() routine */
   	booter->bt_boot 	= &romboot;			/* pointer to booter routine */
   	booter->bt_flags 	= RUN;				/* flags to be passed in */

The booter will appear in the boot menu with a description and keyboard. For instance, the ROM booter is invoked by typing "ro" and it shows up in the boot menu as "Boot from ROM". Here is what those entries might look like:

   	booter->bt_abname 	= (u_char*)"ro";
   	booter->bt_name 	= (u_char*)"Boot from ROM";

   	booter->bt_data 	= globals;			/* pointer to globals */
   	booter->bt_next 	= control->rom_bdevlist;

Above, the booter is saying "the next booter after me is the current one." Then the booter will say "the new current booter is now me" which effectively adds this booter onto a list of booters that have registered already.

   	control->rom_bdevlist = booter;

In the case of this booter, a second entry is made. It points to the same functions but has a different flag value passed in (which the boot routine will parse to figure out if it is supposed to boot from ROM or copy to RAM) and also a different keyword and description string.

   	booter 				= &bootmethod2;
   	booter->struct_id 	= BOOTDEVID;
   	booter->struct_ver 	= BDV_VER_MAX;
   	booter->bt_probe 	= &probe;
   	booter->bt_boot 	= &romboot;
   	booter->bt_flags 	= LOADNGO;
   	booter->bt_abname 	= (u_char *)"lr";
   	booter->bt_name 	= (u_char *)"Load from ROM";
   	booter->bt_data 	= globals;
   	booter->bt_next 	= control->rom_bdevlist;

   	control->rom_bdevlist = booter;
   	return SUCCESS;
}

There are a few booter functions which have now been made available to the system: probe() and the actual booter function (romboot() in this example). Let's look at probe() first. The purpose of this routine is to test to see if this booter is able to boot. It might check to see if hardware is available or, in the case of the ROM boot, if the system has a rom list structure. Without a rom list, the booter would have no idea where to search for the kernel.

error_code probe( Bootdev bdev, Rominfo rinf )
{
Mem_svcs mem = rinf->memory; /* memory structure */
Rom_list bootlist = mem->rom_bootlist; /* boot list */
Rom_list romlist = mem->rom_romlist; /* rom list */

If there is no bootlist, we can't work. If there is a rom list, we can.

	if ( bootlist==NULL ) return EOS_NOBOOTLIST;
if ( (romlist!=NULL ) && ( romlist->addr!=NULL ) ) {
return SUCCESS; } else {
return EOS_NOROMLIST; }
}

This, of course, brings us to the actual boot function. The OS-9 EOM package contains source code to most of the available booters, but here is a run down of what the ROM/RAM booter does.

During the "find all the low level modules" stage, static storage is allocated for each module. One of the first things a booter will do is change the static pointer over to its own statics. The change_static() function does this. It returns a pointer to the statics which were in us, and that allows the booter to restore them on exit. This is important! If the booter fails, it must restore the statics before exiting.

error_code romboot( Bootdev bdev, Rominfo rinf )
{
u_char *old_statics;
Mem_svcs mem = rinf->memory;
Rom_list romlist = mem->rom_romlist;
u_char *romseg;
u_int32 romsize, modsize, memsize;
Cons_svcs console = rinf->cons;
Mh_exec modptr, krnlmod;
error_code stat=0;
Rom_list bootlist = mem->rom_bootlist;
old_statics = (u_char*)change_static( bdev->bt_data );

First we loop through each segment... romseg points to the first ROM memory segment. romsize is the size of that segment. If a system had two 4MB banks of ROM, there might be two segments each of 4MB.

	do {
romseg = romlist->addr;
romsize = romlist->siz;

The booter will loop through each segment, scanning the memory trying to find the kernel module. To accomplish this, the booter will look for valid OS-9 modules in each segment. If it finds one, but it is not the kernel, it will calculate the size of that module then jump past it and continue looking.

		do {
			/* scan from (romseg) to (romseg+romsize) */
			/* if we fouund the kernel, we break */
			/* if we found a non-kernel OS-9 module, we skip over it */
} while ( ... );
if ( we_found_the_kernel ) break;
} while ( more_segments_to_check );
if ( !we_found_the_kernel ) { /* inform user */ /* restore statics and exit with error code */
change_static(old_statics);
return( error );
}

If here, we found the kernel module and we can continue. In this example,w e check the flags that were passed in to decide if we want to just continue booting using the kernel we found in ROM, or if we want to allocate some RAM, copy the ROM modules into that RAM and boot from RAM instead.

	if ( bdev->bt_flags == RUN ) {
		/* if we are just going to "run in place" from ROM... */
		/* we just tell the system where the kernel is located... */
		/* and how large it is. */
bootlist->addr = (u_char *)modptr;
bootlist->siz = modsize; /* then we can exit! */
change_static(old_statics);
return (stat);
}

If we are not going to run from ROM, we need to know how big the entire boot file is so we can allocate that much RAM and copy it over. We do this by scanning through the entire rom memory range looking for modules until we find the end.

	/* start with a pointer to the kernel (the first module) */
	while( we_are_pointing_to_an_os9_module && pointer_is_within_romlist ) {
		/* look in module header, get size of module */
		/* increment pointer so it is after this module */
	}
	/* if here, we are now pointing to the end of the bootfile */ 


	/* calculate the bootfile size by subtracting the end pointer... */
	/* from the start pointer (kernel pointer) */


	/* allocate RAM, and store the pointer to this RAM in the... */
	/* rom info bootlist address pointer */
	error = mem->rom_malloc( &bootfilesize, (char **)&bootlist->addr, rinf );
if ( error!=SUCCESS ) { /* tell user we couldn't allocate memory for the bootfile */
} else { /* otherwise, copy the ROM memory range into the RAM range */
memcpy( bootlist->addr, (u_char *)kerlel_pointer, bootfilesize );
bootlist->siz = bootfilesize;
}
/* restore statics and exit */
change_static(old_statics);
return( error );
}

To see an actual working RAM/ROM booter, refer to Microware's source for romboot.c. It may be found in the MWOS\SRC\ROM\BOOTERS\ROMBOOT directory of an OS-9 OEM installation. (I do not have access to this code nor could I make it available if I did.)

As you can see, the basic theory of a low-level booter is fairly straight forward. If you wanted to add the ability to boot from a compressed ROM image, you would simply insert code to decompress the ROM image into allocated RAM and then start up from there. (It would look very similar to the "load to RAM" code except instead of scanning to find the size of the boot image it would look into the compressed bootfile and find the uncompressed size. Instead of a memcpy() it would run an uncompress routine to move the data from ROM to RAM (decompressing).

References:

Comments:

home action appnotes consultants history links os-9 al systems vendors

Site contents © 2001-2002 by Allen Huffman. OS-9 and all related trademarks belong to Microware Systems Corporation (or, I guess, RadiSys since they now own Microware). This site has no affiliation with Microware (or RadiSys). While I would like to think that every bit of information on this site is accurate, most likely there are many errors. If you need official information about anything discussed here, go to the manufacturer. I'm just an end user who enjoys the product. Peace.