OpenEZ Documentation
A GPL Operating System and Stack
for the eZ80 family of microprocessors.
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.
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().
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.
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).
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.
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.
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.
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.
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.