OpenEZ Documentation

 

A GPL Operating System and Stack for the eZ80 family of microprocessors.

 

Tasks or Processes

The terms “task” and “process” are used interchangeably within OpenEZ.  At the heart of Task management is the TCB structure defined in gcs.h and allocated in stacks.c. To increase the number of task that can run at once, just setup more entries in the array.  The only fields that really need to be statically initialized are the flag and the stackorg.  A flag value of 0xff (255) means the entry is not in use and is available to launch a new task.  The stack origin sets the location in memory where this task’s stack will be located.  Each task has it own stack.  I have them setup with addresses such that each task has a 8192 byte stack which, so far, has been adequate.

 

Tasks are setup by calling set_task(), the code for which is in stacks.c. This routine finds an available (unused) TCB and sets up the stack, so the new task can receive control from time to time. Never ending tasks, might as well be started in main().  The console login, the lwIP, and TimerQ tasks are done this way.  Tasks can terminate by simply ending, using a return statement, or by calling exit() or abort().  Note that each task must do its own “cleanup” before ending.  In the interest of simplicity, the OS does not keep track of memory allocated by each task, or other resources the task may have reserved.  It is each task’s responsibility to free those resources before ending.

 

The basic task scheduling algorithm is “round-robin”, that is each task is given a chance to run, and then not given another chance to run until each other task has had a turn.  However, this basic scheduling technique can be adjusted by two features: priority and time slice.  Priority simply reduces the number of times a given task is given the chance to run.  So by setting the priority field to 3,  each priority 1 task will run 3 times for each 1 time the priority 3 task will run. Priority can be set to any number from 1 to 127. Once a task is given control of the CPU, it can either run until it voluntarily gives up that control, or until its time slice has run out.  Each task can be given a time slice value.  This value specifies the upper limit of the number of microseconds the task will be able to keep control.  If the task has not given up control voluntarily before the time slice expires, then control is taken away by the OS.  The priority and time slice features can be used together or separately to tune the system for best possible allocation of the CPUs cycles.

 

Most of the code responsible for task scheduling is in dispatch.asm.  To give up control voluntarily, a task simply calls dispatch();  At some time later, control will return to the task, to the line immediately following the dispatch() call, with the stack and all registers restored.  Unless the time slice feature is used, a call to dispatch() should be included in all “spin loops” and sprinkled about in any lengthy compute section.  Generally it is more effieient to have tasks voluntarily give up control when it makes sense for them to do so, rather than to always use time slices.

 

There are certain times when the time-slice feature needs to be temporarily suspended.  This can be accomplished by using the macros: ENTER_SYSTEM_MODE, which disables the feature, and EXIT_SYSTEM_MODE, which restore it.  Certain “system” functions do this, for example.  These macros are defined in gcs.h and consist of several assembler instructions to quickly adjust the feature to the desired state.

 

Timer 1 is used to implement the time slice feature.  When a task is given control, and the time slice feature is selected for that task, the dispatcher sets up timer 1 so it will issue an interrupt when the specified time period has expired.   The timer 1 ISR, simulates a call to dispatch(), as if the task itself had actually issued a dispatch() function call.  It also plugs in a return value, such that when the ISR returns, the dispatcher rather than the task, gets control.

 

 

 

Timer Queues

The timer queue is a feature and a set of routines that supports the scheduling of future events.  Any task can schedule a future event.  The nature of the future event can be anything the hardware is capable of doing, but is usually a fairly short function.  When setting up a future event, you specify the function you want to get control, so the OS has no knowledge or need to know exactly what that future event will be doing.

 

There are options available when setting up the event.  You can, for example,  specify that the event is reoccurring.  A reoccurring event will be called over and over at the specified inverval until the event is deleted.  So if you have some function that needs to happen once per minute, a reoccurring event would be a good way to impliment it.

 

These future events normally execute under the TimerQ task umbrella.  This means that the TimerQ taks simply calls the function you specify at the appointed time.  It’s important that the function called not hog the TimerQ task for very long, or other events could be delayed.  However there is an option for these future events to be called as a new task, completely independent of the TimerQ task..If this method is used, the new tasks can have their own priority and time slice values.  So it’s possible to have a future event that is very time consuming or long running, if it is needed.

 

The timing used for this feature comes from timer0 interrupts, and so the resolution of events is 1 millisecond.  Interestingly, there is not a lot of compute power to implement this feature, even though you can schedule an unlimited number of future events.  The reason this is true, is the timer is really only worrying about a single event at a time, ie the one with the least remaining time before calling.  All the other events are kept in a linked list, in time order.  So only one timer is actually needed no matter how many events are waiting to be called.

 

The structures: TIMEQUEUE and TQ_TASK, if you want the furure event to be invoked as a separate task, are defined in timeQueue.h.  It’s usually not a good idea to place these structures in with your local variables.  It’s quite possible that the function that schedules the future event, will not continue to be active during the entire time the future event will be pending.  So it’s best to place these structures in global memory. In the case of the TIMEQUEUE, you need to provide this structure, but you need not initialize any of the fields inside it.  That is done by calling timerQueue_add().  If you are using the TQ_TASK structure, you will need to set all the fields within it before your call to timerQueue_add().

 

 

 

Mutex

A mutex provides a way for several tasks to share a resource in a cooperative way.  Many resources can be used by more than one task as long as only one at a time is using it.  Examples of such resources might be a memory control block, or a data port.  Regardless of the nature of the resource, if all the tasks that need to use it, only do so when the mutex indicates it is available, then taks will not use it at the same time in any kind of overlapping way.  Function calls are provided to researve a mutex, either blocking or not, and to release a mutex.  There is no limit on the number of mutex’s in the system, nor is there a limit on the number of tasks waiting on a single mutex.

 

The mutex_t structure is defined in mutex.h and these structures must be kept in global memory.  A mutex is only useful if it can be addressed by multiple tasks, and a mutex does not really belong to a specific task, in any case.  When defining a mutex, use the static initualizier MUTEX_INITIALIZIER to set the fields to their starting values. Which are:  unlocked/free no owner, no tasks waiting.  After initialization, the mutex fields should not be used directly by your code. 

 

 

 

HTTPd - webserver

The web server, used in OpenEZ supports the GET, POST, and HEAD request methods.  Routines are provided to make it easy to retrieve the name/value pairs and the HTTP headers coming from the client.  Since there are no disks or file systems within OpenEZ, http pages are stored in memory, either ram or flash.  There is a Perl script that converts web files, including .html, .jpg, .gif and others, into an assembler source file, named filedata.asm that then gets combined with the rest of your code.  This script is involked from the IAR tools menu as “Update Web Pages”.  In order for the script to work properly, you need to place all your web pages in or under the directory: $PROJ_DIR\httpd\html.  Files placed in this directory will appear in the root of the fille system as seem by the web client.  You can create subdirectories to whatever depth desired, to further organize your files.  All the files’ contents will be encoded into the one file: filedata.asm even if contained in subdirectories, but from the client point of view, the directory structure you setup will be presearved.

Your web pages can be static html, or cgi type functions.  There is also a special html tag < DATA function> which lets you serve up other wise static pages, with a bit of calculated data.

 

Each HTTP connection is served by a separate task, which is dynamically created to respond to the client’s request.  Once the request has been handled, the task is terminated, the number of simultaneous tasks is only limited by the numbers you specify for MEMP_NUM_TCP_PCB in the file lwipopts.h less any connections for other uses.  The HTTPd server listens on the standard tcp port 80, but this is easily changed to something else in the routine httpd_init() in file httpd_callbacks.c.

 

There are three dynamic content functions included with OpenEZ mostly to demonstrate how to write such functions.  The simpliest is the mycounter() function.  This function will insert a counter into a web page where ever the tag < DATA counter> in encountered.  The counter is reset when the hardare is reset.  The next is the function mydatetime() which will replace the tag < DATA datetime> with the current system data and time values.  Note in the tags above I have placed a space between the “<” and the text “DATA”, so that the tags will apprear in this document even if served from the ez80 platform.  To make these tags actually work, that space would be removed. 

 

Also included is the function Http_Dump().  This function is a full blown cgi type function that is able to work with either the GET or POST methods.  It takes in only one name/value pair, but the method can be expanded as needed for other functions.  This function will send back a 256 byte hex dump of the memory area specified either in the URL or from a form, such as form.html (included).

 

 

 

Encription / Authentication

OpenEZ includes ports of industry standard ecription and authentication methods.  In the security directory you will find the code for md5, hmac_md5, sha, hmac_sha, AES and Blowfish.  These functions are well documented on the web, so I will not go into detail here.  If you are planning to export a product from the US and use Blowfish, be cautious about chosing your key length.  You do not want to run afoul of US export control laws, however since Blowfish was not developed in the US perhaps those laws may not apply. See RFC 2104, for a description of hmac_md5, see Paj’s Home for a handy way to test MD5 algorithms, and Counterpane Labs has an excellent page about Blowfish. OpenEZ includes a security test function to ensure these algorithms are working properly.

 

 

 

Containers

Two containers are supplied with OpenEZ, they are the CFIFO and BCS.  The CFIFO contains individual unsigned characters.  You can create a CFIFO of any size you want up to 64K, this is specified when the CFIFO is initialized.  You must provide a CFIFO structure, defined in cfifo.h, which should almost always be in global memory. You must also supply the actual buffer area, as the CFIFO structure contains no actual storage for the bytes. The buffer area must usually be in global memory as well. A CFIFO container is process safe, meaning it can be used to pass things between tasks, or between ISRs and tasks.  Multiple tasks can output to the same CFIFO container and multiple tasks can remove things from the same CFIFO, if that makes sense in your application.  Functions are provided to add bytes, remove bytes, peek without removing, test for full and test for empty.  The function cfifo_peek_ahead() is particularly helpful when parsing a stream of characters.  Within OpenEZ the OS uses CFIFOs as temporary holders for uart transmit and receive buffering, and for network buffering on the receive side.

 

BCS structures are used to contain MESSAGEs.  A MESSAGE is an internal format for storing packets to be transmitted over a network.  In addition to the data area, there are fields used to help route the message both internally and externally.  Depending on the protocol, not all these fields are actually pasted between hosts over the wire.  The only use of a BCS within OpenEZ is as a container for packets to be sent out over TCP/IP.  A single BCS is setup for all network traffic, regardless of the task that generated it or the way it is to be transmitted.  The MESSAGEs are consumed by the lwIP task, which them gets them on the wire.

 

 

TCP/IP

The TCP/IP code in OpenEZ is derived from the open source project lwIP.  LwIP was originally written by Adam Dunkel, who is still actively involved in it’s development.  OpenEZ uses the “raw api” method of interfacing to the stack.  This method was chosen because it is faster in terms of code execution and also less memory intensive.  Mr. Dunken writes that the raw method is “the preferred way of writing applications that should be small in code size and memory usage.”  OpenEZ sets up a single task to run all of the lwIP code. This task polls the cs8900 chip, calls tcp_write(), and polls the various lwIP timers.  All data to be sent out over TCP/IP is placed in a BCS container called “NetBuffer” and is tranmitted on a first-in first-out basis.  Incoming IP data is passed to the lwIP call back routines.  The built-in telnet, echo, and httpd servers are good examples of how to write your own server.

 

Typically you would use the standard output stream for tcp/ip output and the standard input stream for tcp/ip input.  The stream i/o calls will then provide almost all the logic you need other than having to supply a few simple call backs to run as part of lwIP.

 

Leon Woestenberg’s DHCP, which is now part of the lwIP CVS tree, is also included in OpenEZ.  His cs8900 driver was part of the basis for the cs8900 driver in OpenEZ.  However, I rewrote this driver code because the sc8900 is interfaced as an 8 bit device, versus the 16 bit interface used in Leon’s driver.  Also because the interface is only 8 bits wide, I poll the chip rather than use interrupts.  Cirrus Logic, who makes the cs8900 chip, advises that interrupts cannot be reliably used when interfacing with 8 bits.

 

 

Real Time Clock

The real time clock uses the eZ80 built in real time clock hardware to keep track of the current date and time.  To set the date and/or time, call set_rtc().  To read the present value, call get_rtc().  The real time clock issues an interrupt once per second to properly maintain the fields in the structure “current_time”.  It is best not to access this structure directly as a RTC interrupt could possibly occur while in the middle of doing so.  As of release 0.86, the RTC function set_rtc() calculates the day-of-the-week based on a perpetual calander formula.

 

Shell

The shell provides commands for setting and displaying time and date, displaying memory areas, erasing flash, displaying various status and configuration information and more.  Typically the serial connections and telnet connections are passed to a task running the shell, once logon is completed.  New commands can easily be added.  Type  “help” for a list of the currently supported commands.  Note that as of version 0.86, the shell supports the back-space charactor.

 

 

 

Copyright2002, Robert Laughlin

Last Update 3/7/2005 1:16:03 PM