Just After Reset

The code executed immediately out of reset must be written in assembly language. The reset code should be written so that it can be executed either as a result of a real reset or powerup of the hardware or as a result of a firmware-invoked restart. (These two types of system startups are commonly referred to as coldstart and warmstart , respectively, and they perform very similar tasks .) With one exception, the goal is that after the execution of code through either entry point, the system looks like it just went through a power cycle or reset. The code at warmstart should do all the same stuff that would intrinsically be done by the CPU as a result of a power cycle and entry into coldstart (see list of initializations later); however, there must be a way for code that runs later to determine which entrypoint was taken. Later code must be able to determine the startup type so that it can decide reliably whether or not to initialize the monitors global variable space, whether to run some higher-level system startup procedure, and so forth. Following is a list of basic initializations needed at this point in the startup:

  • disable interrupts at the CPU level (not peripherals, just CPU);

  • disable/flush/invalidate caches appropriately;

  • enable the ability to access the boot flash memory at the desired speed and address;

  • enable the ability to access the system RAM/DRAM at the desired speed and address;

  • initialize a stack pointer.

Listing 4.1 gives a pseudo-code outline of reset.s .

Listing 4.1: Pseudo-Code for reset.s.
image from book
 .file       "reset.s"     .extern start, moncom     .global warmstart, moncomptr coldstart:     Initialize "something" to store away a state variable.     StateOfTarget = INITIIALIZE     JumpTo continuestart moncomptr:     .long moncom warmstart:     /* Load into StateOfTarget the parameter passed to warmstart      * as if it was the C function:       * warmstart(unsigned long state).      */ continuestart:     Disable interrupts     Flush/invalidate/disable cache     Adjust boot device access     Adjust system ram access     Establish the stack pointer     JumpTo start() 
image from book
 
Note 

In case you arent comfortable with assembly language conventions:

  • Strings ending with a colon ( coldstart: , moncomptr: , warmstart:, or continustart: ) are tags , or symbolically accessible addresses.

  • Words that start with a period ( .file , .extern , or .global ) are directives that tell the assembler to do something other than just generate code for some assembler mnemonic. The .extern directive lets the assembler know that the specified symbol is in another file, and the .global directive tells the assembler to make the specified symbol globally accessible (by other functions in other files).

The coldstart tag should be located at the address to which the CPU vectors after a power up or reset. Ignore moncomptr for now (more on moncomptr later). The warmstart tag is seen by the C code as a function. Placing it here gives the C code an easy point to re-start the firmware.

The code at coldstart stores the INITIALIZE value into some location that will not be corrupted by the reset code; hence, it is retrievable at the beginning of the first C function start() . Retrieving the INITIALIZE value allows the firmware after start() to decide whether it is executing as part of a complete or partial initialization sequence. A complete startup would initialize all the targets global variable space and all of the targets firmware subsystems. A partial startup (firmware-invoked warmstart) would not reinitialize the global variable space; thus state would be maintained but basic peripherals would be reinitialized.

Note the use of the StateOfTarget variable in Listing 4.1. The implementation from system to system may be slightly different depending on what is used to store the state of the target. The state of the target could be stored in memory, in a scratch register on some peripheral device, or perhaps even in a CPU register that will not be corrupted by any code between coldstart in assembly and start() in C.

Listing 4.2: start().
image from book
 void start(void) {     int argc;     register unsigned long state;     volatile register ulong *ramstart, *ramend;     /* Copy StateOfTarget to a register prior to the bss       *  init loop.      */     state = StateOfTarget;     /* Initialize monitor-owned ram... Since this loop initializes      *  all monitor ram, the code within the loop must NOT use any      *  ram-based variables (ramstart  & ramend are registers).  Also,      *  since the stack gets cleared during this operation, this      *  function must never return, and cannot expect non-register      *  local variables to retain their values until after this loop.      */     if (state == INITIALIZE) {         ramstart = (ulong *)&bss_start;         ramend   = (ulong *)&bss_end;         while(ramstart < ramend) {             *ramstart++ = 0;         }     }     /* Now that bss has been initialized, we can store      *  StateOfTarget once again in bss space.      */     StateOfTarget = state;     /* Load bottom of stack with 0xdeaddead for use by stkchk(). */     TargetStack[0] = 0xdeaddead;     /* Initialize vector/exception table. */     vinit();     /* Initialize system devices. */     devInit(19200);     /* Build argument list and go to main(). */      argc = 0;     while(argv[argc] != 0) {         argc++;     }     main(argc, argv);     /* The function main() should not return, but just in case... */     while(1);    } 
image from book
 

The very first C function executed by the target is start() (see Listing 4.2). Depending on the environment, you might need to put the first section of this function (the BSS init) at the end of the assembly language code that precedes the call to start() . Putting the BSS init at the end of the assembly language code avoids the potential of the BSS initialization loop stepping on something that is shouldnt. The BSS initialization loop might be a good place to throw a basic memory test, just to verify that the memory the monitor is using is somewhat sane. A simple address in address test (see Listing 4.3) could be inserted just above the ram initialization loop of Listing 4.2.

Listing 4.3: Testing RAM.
image from book
 #if INCLUDE_RAMTEST_AT_STARTUP         ramstart = (ulong *)&bss_start;         ramend   = (ulong *)&bss_end;         while(ramstart < ramend) {             *ramstart = ramstart;             ramstart++;         }           ramstart = (ulong *)&bss_start;         ramend   = (ulong *)&bss_end;         while(ramstart < ramend) {             if (*ramstart != ramstart)                 error!             ramstart++;         } #endif 
image from book
 
Note 

The address-in-address test is a simple pair of loops . The first loop writes 0 to address 0, 4 to address 4, etc. The second loop cycles through all the addresses again, verifying that a read operation will return the locations address. This simple but effective test will detect most common problems affecting either the data bus or the address bus.

Note 

In particular, this test is a good way to identify a stuck address line. For example, if I think I have 64K of RAM, but the upper bit of the address bus is stuck low, when the loop begins writing to the second bank of 32K, it will, instead, overwrite the first bank of 32K. When the address-in-address test attempts to verify the first 32K of memory, the test will fail and fail in a way that helps identify the source of the problem.

Note the use of bss_start and bss_end in Listing 4.2 and Listing 4.3. The bss_start and bss_end labels must be defined in the link-editor memory map file. Most toolsets have some intrinsic labels that signify the beginning and ending of various significant sections. I have found, however, that the tool-defined labels are not necessarily used consistently; hence, I add my own labels to the link editor file so that I know exactly what the labels mean.

Note that start() ends by calling main() . Clearly, the system can now support code written in C. Being at C-level doesnt mean youre free and clear. You still need to be aware of the following:

  • Stack is limited, or at least it might be. The point is that you are in brand new space here, so dont start writing functions that use much stack. The stack depth and reliability are still somewhat unknown. (Are you sure you initialized the stack pointer properly back in reset.s ?) As a matter of fact, for the first couple of passes , you should let start() be the only function and code everything else as in-line operations using global variables.

  • Initialized data is not writable. Remember that one of the MicroMonitor design goals is to be as compiler/CPU/target-independent as possible, and the issue of copying initialized data from ROM to RAM is always a bit different from one toolkit to the next .

  • You still dont have a C-level serial port.

I have found it extremely handy to build the first-stage firmware so that there is no need to copy any initialized data to RAM space. This convenience eliminates the hassle of dealing with the different ways that initialization is handled by different tool sets. This approach does, however, require some discipline when coding because there is no compile-time warning if you try to overwrite initialized data (unless you are very careful with the use of const ).

Also, I found it handy to not use any compiler-supplied libraries for higher-level functions unless absolutely necessary. This philosophy may be a bit conservative, but for a boot platform, I think it is important to have a tight grasp on the code, so MicroMonitor comes with its own printf() , malloc() , and so forth. Note that Im not too extreme I am not suggesting that you write your own math primitives!

Refer to External Chips by Part Number

If you are writing code that interfaces to some external device in the hardware, refer to that device by its part number, not some generic name or code name . If you are working on code that someone else wrote for hardware with which you are not that familiar, it is very frustrating to see code that says toggle pin 4 of the scorpion chip to get the interface to work. What is the scorpion chip? The schematics show devices by number, not code names, so use the numbers. Similar confusion can occur if you use generic names instead of part numbers . For example, the code might refer to the serial port. What if your board has more than one serial device? To the original writer, it might seem obvious that one of the ports is for the console and one is for communication with the external widget. What is obvious to one is lack-of-information to another, so spell out the details in the code comments.



Embedded Systems Firmware Demystified
Embedded Systems Firmware Demystified (With CD-ROM)
ISBN: 1578200997
EAN: 2147483647
Year: 2002
Pages: 118
Authors: Ed Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net