libBIOS & emOS
An OpenSource BIOS for ARM platforms.
David Aparicio (daparic@terra.es) Mar-2001
1.- Goals
2.- Procedure
If there is a second step in ROM and user doesn’t press any key, then
Else
3.- Memory layout
You can customize ROM and RAM mapping. Default partitioning is as follows:
RAM (16Mb or more)
Offset range |
Size |
Description |
0x00000000-0x000FFFFF |
1 Mbyte |
3rd step CODE segment (emOS) |
0x00100000-0x003FFFFF |
3 Mbyte |
3rd step DATA segment (emOS) |
0x00400000-0x007EFFFF |
4032 Kbyte |
2nd step CODE segment / Dowload zone |
0x007F0000-0x007FFFFF |
64 Kbyte |
2nd step DATA segment |
0x00800000-0x00FFFFFF |
8 Mbyte |
Heap space |
0x01000000-0x03FFFFFF |
48 Mbyte |
Free (MCB zone for emOS) |
ROM(256Kbytes or more)
Offset range |
Size |
Description |
0x00000000-0x00003FFF |
16 Kbyte |
1st step CODE segment (non writable) |
0x00004000-0x0003FFFF |
240 Kbyte |
2nd & 3rd step for default boot |
4.- Binary layout
As you have already guessed, 2nd and 3rd steps are loaded together, in a single image. To enable this, special headers are built for 1st and 2nd steps, so a simple organization can be done. Header is as follows:
U32 |
- |
This long word is reserved |
U32 |
arch |
Architecture. SA110 by now. |
U32 |
type |
Type of binary. One of POST or LOAD |
U32 |
- |
This long word is reserved, must be zero. |
U8 [32] |
label |
Name for this entry |
U8 * |
baseC |
Absolute address for CODE segment |
U32 |
sizeC |
Size of CODE segment, in bytes |
U8 * |
baseD |
Absolute address for DATA segment |
U32 |
sizeD |
Size of DATA segment, in bytes |
A POST image has exactly one entry with previous layout. So this code is run in ROM, baseC points to absolute addressing of ROM space in that architecture. For a 21285 Footbridge, this is 0x41000000.
So POST has no data segment, baseD is used as a hint to absolute address in ROM to search 2nd and 3rd steps. According to memory layout, and for a 21285 Footbridge, this is 0x41004000.
A LOAD image has 64 entries like previous one. The first one describes LOAD itself. Rest of them are available for describing attached binaries (3rd step) loaded together. So they have not special headers theirselves, all information for loading them in RAM is inside these entries. Size of CODE for entry 0 (LOAD) is total size for LOAD CODE, and sum of CODE and DATA sizes for all binaries attached. According to memory layout, entry[0].baseC is 0x00400000, and entry[0].baseD is 0x007F0000.
Header for entries above zero (attachments) is as follows:
U32 |
offs |
Byte offset for this attach (from entry[0].baseC) |
U32 |
arch |
Architecture. SA110 by now. |
U32 |
type |
Type of binary. One of EXEC or BZIP |
U32 |
entry |
Byte offset for starting point in CODE segment. |
U8 [32] |
label |
Name for this entry |
U8 * |
baseC |
Absolute address for CODE segment |
U32 |
sizeC |
Size of CODE segment, in bytes |
U8 * |
baseD |
Absolute address for DATA segment |
U32 |
sizeD |
Size of DATA segment, in bytes |
5.- Improvements
This system has been designed to be as flexible as possible. MMU is not activated, for 1st neither 2nd step, and it will be brought up by 3rd one (e.g. Linux). For SA110, start of SDRAM begins at 0, so no remapping is necessary. For other architectures (like SA1100) this can be false, and some discussion can be done in order to make binaries (3rd step) as interchangeable as possible.
Linux can be loaded by this method by taking an uncompressed ELF image of kernel, and aplying it the tools providen with libBIOS (MKrom). That way, it would be attached in a single image with a 2nd step, and, after being in RAM, decompressed in proper place and then called by it. The only limitation is that it could never touch memory hole from 4th Mb to 7th Mb, because loader executes from here.
6.- Boot loading process: First loop
7. mon (a monitor)
I have enclosed a small shell as an example of a standalone application. This let you snoop in your system (very useful for PCI), so you can test that some devices work properly, or just for managing hardware in an interactive way. I have selected this piece of software for adding code that enables you to copy a multipart binary from download zone (SDRAM) to flash, so upgrading your system. If this procedure somehow fails, you will always be able to download a workaround from serial line or whatever your 1st step link is (an USB connection would be nice).
This code runs from 0x00000000 (for code) and 0x00100000 (for data), same place as emOS would do.
Available commands for this version are:
?b |
<address> |
Read range of bytes |
?w |
<address> |
Read range of words |
?l |
<address> |
Read range of longs |
=b |
<address> <value> |
Write range of bytes |
=w |
<address> <value> |
Write range of words |
=l |
<address> <value> |
Write range of longs |
pci |
List all PCI devices in the system |
|
pci |
<device> |
List properties of a PCI device |
map |
List all PCI resource assignment in the system |
|
map |
<device> |
List assignment of a PCI device |
sfs |
List all binaries in RAM and ROM |
|
sfs |
ram:<binary> |
Do the assigned action for this RAM binary (COPY or FLASH) |
sfs |
rom:<binary> |
Do the assigned action for this ROM binary (COPY or LOAD) |
jump |
<addr> |
Execute code at <addr> (At your own risk) |
quit |
Leave shell and reboot |
Some notes on this:
You can specify any number in decimal, hexa, octal or binary. For example 10, 0xA, 012 and 0b1010 is the same number. For reading or writing you can specify an address range, an array or a list of addresses, and, of course, a single address. For example:
=b 0x100, 0x110, 0x120 0
=b 0x100-0x1ff 0
=b 0x100[256] 0
As you can see, there is no "download" command. You should compose a proper binary by mixing this monitor with some other binary, and then download all together by 1st step (or 3rd step TFTP if available). Then, you have "sfs" command for managing all attachments downloaded.
Also, there is no "flash" comand. If you do an sfs-action on a 2nd step binary in RAM, it will be flashed (writting it after 1st boot code, replacing previous 2nd and 3rd code). ("SFS" stands for a Simple File System).
I think all basic management of this shell is explained. A more complex description could be done in the future.
8.- emOS (the embbeded OS for libBIOS)
So some devices has so little flash space, burning linux is not always possible. Also, for an embedded world, having Linux as our first option in flash is not so good idea. Operating systems are created for a range of applications, and Linux seems a generic one. Embedded world doesn’t need too much power, neither RAM or resources comsumption. A small RTOS, developed for another embedded system of our own, has been adapted to run very closely to libBIOS environment. Microkernel is very small (less than 8 Kbytes), and is not so difficult to add some drivers for supporting your hardware. We did so, migrating "pcnet32" and "ide" drivers from Linux to our system, it took about a month to have a running system with BOOTP/TFTP and disk storage capabilities, in less than 50 Kbytes. This is a good choice for having a first breath in your system, and a temporary step to load another OS from a massive source, like a hard disk or the network. Sometimes, emOS can be the only thing you need to run in a tiny box.
First release of this code doesn’t have any driver, but only the microkernel and a small test system. Depending on usefulness of this project, it could be nice to extend some features of this RTOS microkernel.
As a basic concept, there are tasks (TCB), messages (MCB) and timers. You can not create or destroy tasks, they are born with the microkernel, and they have only one of two choices: they can be idle or running. emOS has 4 priority levels, and there is a round-robin policy for ready tasks with te same priority level. Task 0 is the idle task, and there are 31 more possible tasks running. They might seem too few, but remember this is a small, embedded microkernel. If you need more, probably an RTOS is not the choice you want to run to solve your problems.
Tasks sleep as they communicate between them, waiting for resources or resposes to queries they did. Also, emOS scans for new candidates to run every 1 millisecond.
Messages are dinamically created and destroyed by system calls. They can live beyond tasks that created them, I mean, a task can create a message, pass it to another task, and this one destroy it or pass to another task (as a general idea, we will see that this depends on the mode of communication. As long as a message "lives", its place in memory is fixed, there is no copy involved in communication. Also, you can send a message to a group of destinations, much the way a multicast datagram is sent. Last but not leas, a group of messages can be joined together and sent as a single communication, optimizing communications in some cases.
Available system calls are:
Msg *NEW(int nelem)
FREE(Msg *)
These calls creates messages and releases them. You can specify more than one message creating them, so you get a group of messages chained together.
JOIN(Msg **dst, Msg **org)
SPLIT(Msg **dst, Msg **org)
You can also join several chains of messages in a single one, or split a single message from a chain.
BIND(int pid, int channel)
UNBIND(int pid, int channel)
Tasks can suscribe theirselves to so called "channels". When you send a message, you specify a "channel" and not a single "pid". This way several receivers can get your message (remember that there is no copy of that message, and so all receivers share the same data) at once.
First 32 channels are static, and theis "suscribers" are the corresponding pids. That is, channel 3 and pid 3 is the same. You can bind and unbind suscriptors to channels equal or above 32, to MAX_SLOT.
STREAM(msg *, int channel, int size)
Msg *RECEIVE(int msecs, FL_STREAM)
These syscalls let you the simplest way of communication/sincronization in emOS. You select a message (size is an optional information, so there is no copy of information involved) and a destination channel. The message will no longer property of the sender, so it should forget the sent buffer.
For a receiver, it can specify a sleeping time (in milliseconds). If receiver has no messages ready and there is a nonzero value, receiver will wait till someone sends it something. Grouped messages are received in a single receptions. Different STREAM calls will prodsuce several RECEIVE iterations. If you put zero as timeout, you will simply poll if there are any messages for you (this one is not recommeded but for very specific situations).
CONTROL(msg *, int channel, int size, int msecs)
Msg *RECEIVE(int msecs, FL_CONTROL)
REPLY(msg *, int size)
This way of communication let you send a message and wait for a response (written in the same message). Of course, a task will send it (CONTROL) and another one will receive and return it (REPLY). A timeout (in millisecs) is provided for the sender. If the message is not replied in the specified time, the response will be discarded (but the receiver won’t notice this).
SIGNAL(msg *, int size)
Msg *RECEIVE(int msecs, FL_SIGNAL)
RELEASE(msg *)
I have not said yet, but every task has two main functions, one for normal operation (that is, some inicalization and then a forever loop with a receive and a big "switch" inside. This function will never return). The another one is a heartbeat, and is called every fixed amount of millisecs. This one is a fast function that must return. It is aimed to check some hardware coinditions (driver polling) and then signaling to the forever loop that something has happened. That is what SIGNAL stands for. It will send a precreated message from the syncronous function (the polling one) to the asyncronous one (the forever loop). When the last one receives the message and RELEASE it, let the syncronous part to SIGNAL it again and again. The message is not destroyed as long as you call it this way. Is something like triggering a signal, then unlock it for reuse.
SLEEP(int msecs)
If you have to wait for something to be done by the hardware, you should call this routine, being nice with the multitask system, and freeing CPU for others to run.
Msg *RECEIVE(int msecs, FL_SIGNAL|FL_CONTROL|FL_STEAM)
Last but not least, you can combine all receives in a single receiving point, combining all in a single call. If there are more than one type of message in a single point, priority is first serve all SIGNALS, then (after more receive calling) all CONTROL, finally all STREAM.
I recommend you take a look at "libBIOS/_i/libEMOS.h", and example in "os_test" to have a complete view of the picture.
We will release in the near future some reference drivers that demostrates you that is very easy to build a driver for your preferred hardware. We have migrated it taking as a reference linux code. Also, it is very easy to run some protocols (BOOTP/TFTP, SNMP or whatever) as tasks. We have not migrated a TCP core because we have not needed to do, but seems not so difficult to do so.
As a general idea, you will use SIGNAL for triggering some event from hardware to be handled, CONTROL for commanding something and get a response of the result, and STREAM for sending data from one hardware point to another (for example, UDP packets).
9.- Licensing policy
I have seen a lot of problems around linux and driver support from commercial companies. I think GPL licensing is restrictive, because is tricky to use a GPL product without becoming your own development a GPL one. On the other hand, GPL seems the most secure copyright for this kind of software in a possible lawsuit, I have not released this for someone (big) to take profit of my code against Open Community, by awful royalties on a modification of this code. After thinking about this, I decided to put an LGPL license on this system.
I have put all complexity of this code inside libBIOS, and this is LGPL released. You can do your own work on post, loader or mon, they are not GPLed, so you (and me) are free to keep your work for yourself, or to give it to all as a new LGPLed library.
The only thing that I would like is to maintain a standard and stable interface, so a generic platform could be built for all SA1100, SA110, Xscale and some other ARM platforms. A lot of people helped me for free to build this, I would like to payback all them in some way. I hope this be useful to you.
10.- Growing up