0% found this document useful (0 votes)
121 views106 pages

(Embedded) - Realtime Operating Systems - Concepts & Implementation of Microkernels For Embedded Systems (2spp)

This document provides an overview of real-time operating systems and microkernel concepts for embedded systems. It describes the key requirements for an RTOS including general requirements, memory requirements, performance, and portability. It then covers core RTOS concepts such as task scheduling, preemptive multitasking, semaphores, queues, interprocess communication and interrupt handling. The document details the implementation of an RTOS kernel including architecture, hardware model, task management, synchronization, and bootstrap process. It provides examples of using the RTOS monitor and creating applications on the system.

Uploaded by

Mohammed Molhem
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
121 views106 pages

(Embedded) - Realtime Operating Systems - Concepts & Implementation of Microkernels For Embedded Systems (2spp)

This document provides an overview of real-time operating systems and microkernel concepts for embedded systems. It describes the key requirements for an RTOS including general requirements, memory requirements, performance, and portability. It then covers core RTOS concepts such as task scheduling, preemptive multitasking, semaphores, queues, interprocess communication and interrupt handling. The document details the implementation of an RTOS kernel including architecture, hardware model, task management, synchronization, and bootstrap process. It provides examples of using the RTOS monitor and creating applications on the system.

Uploaded by

Mohammed Molhem
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 106

2

Realtime Operating Systems

Concepts and Implementation of Microkernels

for Embedded Systems

Dr. Jürgen Sauermann, Melanie Thelen


ii

Contents
3.4.2 Semaphore Destructor ............................................................................... 46
3.4.3 Semaphore P() ........................................................................................... 46
3.4.4 Semaphore Poll() ....................................................................................... 48
List of Figures.............................................................................v 3.4.5 Semaphore V() .......................................................................................... 49
3.5 Queues ....................................................................................................... 51
List of Tables .............................................................................vi 3.5.1 Ring Buffer Constructor and Destructor ................................................... 51
3.5.2 RingBuffer Member Functions.................................................................. 52
Preface ........................................................................................1 3.5.3 Queue Put and Get Functions .................................................................... 53
1 Requirements ..............................................................................3 3.5.4 Queue Put and Get Without Disabling Interrupts...................................... 53
3.6 Interprocess Communication..................................................................... 54
1.1 General Requirements ................................................................................. 3
3.7 Serial Input and Output ............................................................................. 59
1.2 Memory Requirements ................................................................................ 3
3.7.1 Channel Numbers ...................................................................................... 62
1.3 Performance................................................................................................. 4
3.7.2 SerialIn and SerialOut Classes and Constructors/Destructors .................. 63
1.4 Portability .................................................................................................... 5
3.7.3 Public SerialOut Member Functions ......................................................... 65
2 Concepts .....................................................................................7 3.7.4 Public SerialIn Member Functions............................................................ 69
2.1 Specification and Execution of Programs.................................................... 7 3.8 Interrupt Processing................................................................................... 71
2.1.1 Compiling and Linking ............................................................................... 7 3.8.1 Hardware Initialization.............................................................................. 71
2.2 Loading and Execution of Programs ......................................................... 11 3.8.2 Interrupt Service Routine .......................................................................... 73
2.3 Preemptive Multitasking............................................................................ 12 3.9 Memory Management ............................................................................... 77
2.3.1 Duplication of Hardware ........................................................................... 12 3.10 Miscellaneous Functions ........................................................................... 79
2.3.2 Task Switch ............................................................................................... 14 3.10.1Miscellaneous Functions in Task.cc ......................................................... 79
2.3.3 Task Control Blocks .................................................................................. 16 3.10.2Miscellaneous Functions in os.cc ............................................................. 80
2.3.4 De-Scheduling ........................................................................................... 19
4 Bootstrap...................................................................................81
2.4 Semaphores ............................................................................................... 21
4.1 Introduction ............................................................................................... 81
2.5 Queues ....................................................................................................... 26
4.2 System Start-up ......................................................................................... 81
2.5.1 Ring Buffers .............................................................................................. 26
4.3 Task Start-up.............................................................................................. 87
2.5.2 Ring Buffer with Get Semaphore .............................................................. 28
4.3.1 Task Parameters......................................................................................... 87
2.5.3 Ring Buffer with Put Semaphore .............................................................. 29
4.3.2 Task Creation............................................................................................. 89
2.5.4 Ring Buffer with Get and Put Semaphores ............................................... 30
4.3.3 Task Activation.......................................................................................... 92
3 Kernel Implementation .............................................................33 4.3.4 Task Deletion............................................................................................. 92
3.1 Kernel Architecture ................................................................................... 33
5 An Application .........................................................................95
3.2 Hardware Model........................................................................................ 34
5.1 Introduction ............................................................................................... 95
3.2.1 Processor ................................................................................................... 34
5.2 Using the Monitor ..................................................................................... 95
3.2.2 Memory Map ............................................................................................. 35
5.3 A Monitor Session..................................................................................... 98
3.2.3 Peripherals ................................................................................................. 35
5.4 Monitor Implementation......................................................................... 102
3.2.4 Interrupt Assignment ................................................................................. 36
3.2.5 Data Bus Usage ......................................................................................... 36 6 Development Environment .....................................................107
3.3 Task Switching .......................................................................................... 39 6.1 General .................................................................................................... 107
3.4 Semaphores ............................................................................................... 46 6.2 Terminology ............................................................................................ 107
3.4.1 Semaphore Constructors............................................................................ 46 6.3 Prerequisites ............................................................................................ 109
. iii iv

6.3.1 Scenario 1: UNIX or Linux Host ............................................................ 109 Index .......................................................................................201


6.3.2 Scenario 2: DOS Host ............................................................................. 110
6.3.3 Scenario 3: Other Host or Scenarios 1 and 2 Failed................................ 110
6.4 Building the Cross-Environment ............................................................. 112
6.4.1 Building the GNU cross-binutils package............................................... 112
6.4.2 Building the GNU cross-gcc package ..................................................... 113
6.4.3 The libgcc.a library.................................................................................. 114
6.5 The Target Environment .......................................................................... 117
6.5.1 The Target Makefile................................................................................. 117
6.5.2 The skip_aout Utility............................................................................... 121

7 Miscellaneous .........................................................................123
7.1 General .................................................................................................... 123
7.2 Porting to different Processors ................................................................ 123
7.2.1 Porting to MC68000 or MC68008 Processors ........................................ 123
7.2.2 Porting to Other Processor families......................................................... 124
7.3 Saving Registers in Interrupt Service Routines....................................... 125
7.4 Semaphores with time-out....................................................................... 127

A Appendices .............................................................................130
A.1 Startup Code (crt0.S) .............................................................................. 130
A.2 Task.hh .................................................................................................... 137
A.3 Task.cc .................................................................................................... 140
A.4 os.hh ....................................................................................................... 143
A.5 os.cc ........................................................................................................ 145
A.6 Semaphore.hh ......................................................................................... 150
A.7 Queue.hh ................................................................................................. 151
A.8 Queue.cc ................................................................................................. 153
A.9 Message.hh ............................................................................................. 157
A.10 Channels.hh ............................................................................................ 158
A.11 SerialOut.hh ............................................................................................ 159
A.12 SerialOut.cc ............................................................................................ 160
A.13 SerialIn.hh .............................................................................................. 166
A.14 SerialIn.cc ............................................................................................... 167
A.15 TaskId.hh ................................................................................................ 170
A.16 duart.hh ................................................................................................... 171
A.17 System.config ......................................................................................... 175
A.18 ApplicationStart.cc ................................................................................. 176
A.19 Monitor.hh .............................................................................................. 177
A.20 Monitor.cc ............................................................................................... 178
A.21 Makefile .................................................................................................. 187
A.22 SRcat.cc .................................................................................................. 189
List of Figures List of Tables

Figure 2.1 Hello.o Structure ......................................................................................................8 Table 2.1 Execution of a program....................................................................11


Figure 2.2 libc.a Structure..........................................................................................................9 Table 2.2 Duplication of Hardware .................................................................14
Figure 2.3 Hello Structure .......................................................................................................10 Table 2.3 Semaphore States.............................................................................22
Figure 2.4 Program Execution .................................................................................................13 Table 2.4 P() and V() properties ......................................................................24
Figure 2.5 Parallel execution of two programs ........................................................................13 Table 2.5 Typical Initial Counter Values .........................................................25
Figure 2.6 Clock ......................................................................................................................14 TABLE 1. Commands available in all menus ...................................................97
Figure 2.7 Task Switch ............................................................................................................15 TABLE 2. Specific commands ..........................................................................97
Figure 2.8 Shared ROM and RAM ..........................................................................................16
Figure 2.9 Final Hardware Model for Preemptive Multitasking .............................................17
Figure 2.10 Task Control Blocks and CurrentTask....................................................................18
Figure 2.11 Task State Machine.................................................................................................21
Figure 2.12 P() and V() Function Calls .....................................................................................24
Figure 2.13 Ring Buffer.............................................................................................................27
Figure 2.14 Serial Communication between a Task and a Serial Port.......................................30
Figure 3.1 Kernel Architecture ................................................................................................33
Figure 3.2 Data Bus Contention ..............................................................................................36
Figure 3.3 Modes and Interrupts vs. Time ...............................................................................40
Figure 3.4 Exception Stack Frame...........................................................................................42
Figure 3.5 Serial Router (Version A) .......................................................................................59
Figure 3.6 Serial Router (Version B) .......................................................................................60
Figure 3.7 Serial Router (Version C) .......................................................................................61
Figure 4.1 ??? .DATA and .TEXT during System Start-Up ??? ........81
Figure 5.1 Monitor Menu Structure .........................................................................................96
Figure 7.1 Task State Machine...............................................................................................127
Figure 7.2 Task State Machine with new State S_BLKD......................................................128
2

Preface
been used, running a cooperative multitasking operating system. At that time, the
system had already reached its limits, and the operating system had shown some
serious flaws. It became apparent that at least the operating system called for
major redesign, and chances were good that the performance of the
microcontroller would be the next bottleneck. These problems had already caused
serious project delay, and the most promising solution was to replace the old
operating system by the new microkernel, and to design a new hardware based on
Every year, millions of microprocessor and microcontroller chips are sold as a MC68020 processor. The new hardware was ready in summer 1996, and the
CPUs for general purpose computers, such as PCs or workstations, but also for port from the simulation to the real hardware took less than three days. In the two
devices that are not primarily used as computers, such as printers, TV sets, SCSI months that followed, the applications were ported from the old operating system
controllers, cameras, and even coffee machines. Such devices are commonly to the new microkernel. This port brought along a dramatic simplification of the
called embedded systems. Surprisingly, the number of chips used for embedded application as well as a corresponding reduction in source code size. This
systems exceeds by far the number of chips used for general purpose computers. reduction was possible because serial I/O and interprocess communication were
now provided by the microkernel rather than being part of the applications.
Both general purpose computers and embedded systems (except for the very
simple ones) require an operating system. Most general purpose computers Although the microkernel was not designed with any particular application in
(except mainframes) use either UNIX, Windows, or DOS. For these operating mind, it perfectly met the requirements of the project. This is neither by accident
systems, literature abounds. In contrast, literature on operating systems of nor by particular ingenuity of the author. It is mainly due to a good example: the
embedded systems is scarce, although many different operating systems for MIRAGE operating system written by William Dowling of Sahara Software Ltd.
embedded systems are available. One reason for this great variety of operating about twenty years ago. That operating system was entirely written in assembler
systems might be that writing an operating system is quite a challenge for a and famous for its real-time performance. Many concepts of the microkernel
system designer. But what is more, individually designed systems can be presented in this book have been adopted from the MIRAGE operating system.
extended in exactly the way required, and the developer does not depend on a
commercial microkernel and its flaws.

The microkernel presented in this book may not be any better than others, but at
least you will get to know how it works and how you can modify it. Apart from
that, this microkernel has been used in practice, so it has reached a certain level of
maturity and stability. You will learn about the basic ideas behind this
microkernel, and you are provided with the complete source code that you can use
for your own extensions.

The work on this microkernel was started in summer 1995 to study the efficiency
of an embedded system that was mainly implemented in C++. Sometimes C++ is
said to be less efficient than C and thus less suitable for embedded systems. This
may be true when using a particular C++ compiler or programming style, but has
not been confirmed by the experiences with the microkernel provided in this
book. In 1995, there was no hardware platform available to the author on which
the microkernel could be tested. So instead, the microkernel was executed on a
simulated MC68020 processor. This simulation turned out to be more useful for
the development than real hardware, since it provided more information about the
execution profile of the code than hardware could have done. By mere
coincidence, the author joined a project dealing with automated testing of
telecommunication systems. In that project, originally a V25 microcontroller had
4 1.3 Performance

1 Requirements
floppy disks, CD-ROMs, and tapes. The most economical solution for embedded
systems is to combine hard disks (which provide non-volatility) and dynamic
RAMs (which provide fast access times).

Generally, the memory technology used for an embedded system is determined


by the actual application: For example, for a laser printer, the RAM will be
dynamic, and the program memory will be either EEPROM, EPROM, or RAM
1.1 General Requirements loaded from a hard disk. For a mobile phone, EEPROMs and static RAMs will
rather be used.
Proper software design starts with analyzing the requirements that have to be
fulfilled by the design. For embedded systems, the requirements are defined by One technology which is particularly interesting for embedded systems is on-chip
the purpose of the system. General definitions of the requirements are not memory. Comparatively large on-chip ROMs have been available for years, but
possible: for example, the requirements of a printer will definitely be different their lack of flexibility limited their use to systems produced in large quantities.
from those of a mobile phone. There are, however, a few common requirements The next generation of microcontrollers were on-chip EPROMs, which were
for embedded systems which are described in the following sections. suitable also for smaller quantities. Recent microcontrollers provide on-chip
EEPROM and static RAM. The Motorola 68HC9xx series, for example, offers
1.2 Memory Requirements on-chip EEPROM of 32 to 100 kilobytes and static RAM of 1 to 4 kilobytes.

The first PCs of the early eighties had 40 kilobytes of ROM, 256 or 512 kilobytes With the comeback of the Z80 microprocessor, another interesting solution has
of RAM, and optionally a hard disk drive with 5 or 10 megabytes capacity. In the become available. Although it is over two decades old, this chip seems to
mid-nineties, an off-the-shelf PC had slightly more ROM, 32 megabytes of RAM, outperform its successors. The structure of the Z80 is so simple that it can be
and a hard disk drive of 2 or 4 gigabytes capacity. Floppy disks with 360 or integrated in FPGAs (Field Programmable Logic Arrays). With this technique,
720 kilobyte capacity, which were the standard medium for software packages entire microcontrollers can be designed to fit on one chip, providing exactly the
and backups, had been replaced by CD-ROM and tape streamers with capacities functions required by an application. Like several other microcontrollers, the Z80
well above 500 megabytes. Obviously, capacity has doubled about every two provides a total memory space of 64 kilobytes.
years, and there is no indication that this trend will change. So why bother about
memory requirements? Although the memory size provided on chips will probably increase in the future,
the capacities available today suggest that an operating system for embedded
A PC is an open system that can be extended both in terms of memory and system should be less than 32 kilobytes in size, leaving enough space for the
peripherals. For a short while, a PC can be kept up to date with technological application.
developments by adding more memory and peripherals until it is ultimately
outdated. Anyway, a PC could live for decades; but its actual lifetime is often 1.3 Performance
determined by the increasing memory demands of operating systems and
applications rather than by the lifetime of its hardware. So to extend the lifetime The increase in the PCs’ memory size is accompanied by a similar increase in
of a PC as much as possible and thus to reduce the costs, its configuration has to performance. The first PCs had an 8 bit 8088 CPU running at 8 MHz, while today
be planned thoroughly. a 32 bit CPU running at 200 MHz is recommended. So CPU performance has
doubled about every two years, too. Surprisingly, this dramatic increase in
For a given embedded system, in contrast, the memory requirements are known in performance is not perceived by the user: today’s operating systems consume
advance; so costs can be saved by using only as much memory as required. even more memory and CPU performance than technological development can
Unlike PCs, where the ROM is only used for booting the system, ROM size plays provide. So the more advanced the operating system, the slower the applications.
a major role for the memory requirements of embedded systems, because in One reason for the decreasing performance of applications and also of big
embedded systems, the ROM is used as program memory. For the ROM, various operating systems might be that re-use of code has become common practice;
types of memory are available, and their prices differ dramatically: EEPROMs are coding as such is avoided as much as possible. And since more and more code is
most expensive, followed by static RAMs, EPROMs, dynamic RAMs, hard disks,
1. Requirements 5 6 1.4 Portability

executed in interfaces between existing modules, rather than used for the actual can be a problem if they support only a limited number of microcontrollers, or not
problem, performance steadily deteriorates. the one that would otherwise perfectly meet the specific requirements for a
product. In any case, portability should be considered from the outset.
Typically, performance demands of embedded systems are higher than those of
general purpose computers. Of course, if a PC or embedded system is too slow, The obvious approach for achieving portability is to use high level languages, in
you could use a faster CPU. This is a good option for PCs, where CPU costs are particular C or C++. In principle, portability for embedded system is easier to
only a minor part of the total costs. For embedded systems, however, the cost achieve than for general purpose computers. The reason is that complex
increase would be enormous. So the performance of the operating system has applications for general purpose computers not only depend on the CPU used, but
significant impact on the costs of embedded systems, especially for single-chip also on the underlying operating system, the window system used, and the
systems. configuration of the system.

For example, assume an embedded system requiring serial communication at a A very small part of the microkernel presented in this book was written in
speed of 38,400 Baud. In 1991, a manufacturer of operating systems located in Assembler; the rest was written in C++. The part of the kernel which depends on
Redmond, WA, writes in his C/C++ Version 7.0 run-time library reference: “The the CPU type and which needs to be ported when a different CPU family is used,
_bios_serialcom routine may not be able to establish reliable communications at is the Assembler part and consists of about 200 Assembler instructions. An
baud rates in excess of 1,200 Baud (_COM_1200) due to the overhead associated experienced programmer, familiar with both the microkernel and the target CPU,
with servicing computer interrupts”. Although this statement assumes a slow 8 bit will be able to port it in less than a week.
PC running at 8 MHz, no PC would have been able to deal with 38,400 baud at
that time. In contrast, embedded systems had been able to manage that speed The entire kernel, plus a simple application, fit in less than 16 kilobyte ROM for a
already a decade earlier: using 8 bit CPUs at even lower clock frequencies than MC68020 CPU. Hence it is especially suitable for single chip solutions.
the PCs’.

Performance is not only determined by the operating system, but also by power
consumption. Power consumption becomes particularly important if an embedded
system is operated from a battery, for example a mobile phone. For today’s
commonly used CMOS semiconductor technology, the static power required is
virtually zero, and the power actually consumed by a circuit is proportional to the
frequency at which the circuit is operated. So if the performance of the operating
system is poor, the CPU needs to be operated at higher frequencies, thus
consuming more power. Consequently, the system needs larger batteries, or the
time the system can be operated with a single battery charge is reduced. For
mobile phones, where a weight of 140g including batteries and stand-by times of
80 hours are state of the art, both of these consequences would be show stoppers
for the product. Also for other devices, power consumption is critical; and last,
but not least, power consumption should be considered carefully for any electrical
device for the sake of our environment.

1.4 Portability

As time goes by, the demands on products are steadily increasing. A disk
controller that was the fastest on the market yesterday will be slow tomorrow.
Mainstream CPUs have a much wider performance range than the different
microcontroller families available on the market. Thus eventually it will be
necessary to change to a different family. At this point, commercial microkernels
8 2.1 Specification and Execution of Programs

2 Concepts
preprocessing. The purpose of stdio.h is to tell the compiler that printf is not a
spelling mistake, but the name of a function that is defined elsewhere. We can
imagine the generation of Hello.o as shown in Figure 2.1.1

#include <stdio.h>
... .TEXT
...
2.1 Specification and Execution of Programs .DATA
Hello.cc Hello.o
The following sections describe the structure of a program, how a program is
prepared for execution, and how the actual execution of the program works. FIGURE 2.1 Hello.o Structure

2.1.1 Compiling and Linking Several object files can be collected in one single file, a so-called library. An
important library is libc.a (the name may vary with the operating system used): it
Let us start with a variant of the well known “Hello World!” program: contains the code for the printf function used in our example, and also for other
functions. We can imagine the generation of libc.a as shown in Figure 2.2.
#include <stdio.h>

const char * Text = "Hello World\n";

char Data[] = "Hello Data\n";

int Uninitialized; // Bad Practice

int main(int argc, char * argv[])


{
printf(Text);
}

This C++ program prints “Hello World”, followed by a line feed on the screen of
a computer when it is executed. Before it can be executed, however, it has to be
transformed into a format that is executable by the computer. This transformation
is done in two steps: compilation and linking.

The first step, compilation, is performed by a program called compiler. The


compiler takes the program text shown above from one file, for example Hello.cc,
and produces another file, for example Hello.o. The command to compile a file is
typically something like
1. Note: The BSS section contains space for symbols that uninitialized when starting the
g++ -o Hello.o Hello.cc program. For example, the integer variable Uninitialized will be included here in order to speed
up the loading of the program. However, this is bad programming practice, and the bad style is not
weighed up by the gain in speed. Apart from that, the memory of embedded systems is rather
The name of the C++ compiler, g++ in our case, may vary from computer to small, and thus loading does not take long anyway. Moreover, we will initialize the complete data
computer. The Hello.o file, also referred to as object file, mainly consists of three memory for security reasons; so eventually, there is no speed advantage at all. Therefore, we
sections: TEXT, DATA, and BSS. The so-called include file stdio.h is simply assume that the BSS section is always empty, which is why it is not shown in Figure 2.1, and why
copied into Hello.cc in an early execution phase of the compiler, known as it will not be considered further on.
2. Concepts 9 10 2.1 Specification and Execution of Programs

.TEXT
.TEXT .TEXT .DATA
.DATA .DATA
Hello.o
foo.o foo.o

.TEXT .TEXT .TEXT


.DATA .DATA .DATA .TEXT

printf.o printf.o foo.o .DATA

Hello
.TEXT .TEXT .TEXT
.DATA .DATA .DATA

bar.o bar.o printf.o


libc.a
.TEXT
FIGURE 2.2 libc.a Structure
.DATA

bar.o
The second step of transforming program text into an executable program is
linking. A typical link command is e.g. libc.a

ld -o Hello Hello.o FIGURE 2.3 Hello Structure

With the linking process, which is illustrated in Figure 2.3, all unresolved
references are resolved. In our example, printf is such an unresolved reference, as
it is used in main(), but defined in printf.o, which in turn is contained in libc.a.
The linking process combines the TEXT and DATA sections of different object
files in one single object file, consisting of one TEXT and one DTA section only.
If an object file is linked against a library, only those object files containing
definitions for unresolved symbols are used. It should be noted that a linker can
produce different file formats. For our purposes, the so-called Motorola S-record
format will be used.
2. Concepts 11 12 2.3 Preemptive Multitasking

2.2 Loading and Execution of Programs 2.3 Preemptive Multitasking

After a program has been compiled and linked, it can be executed. While The previous sections described the execution of one program at a time. But what
compilation and linking is basically identical for embedded systems and general needs to be done if several programs are to be executed in parallel? The method
purpose computers, there are some differences regarding the execution of we have chosen for parallel processing is preemptive multitasking. By definition,
programs. Table 2.1 lists the steps performed during program execution and a task is a program that is to be executed, and multitasking refers to several tasks
shows the differences between general purpose computers and embedded being executed in parallel. The term preemptive multitasking as such may imply a
systems: complex concept. But it is much simpler than other solutions, as for example TSR
(Terminate and Stay Resident) programs in DOS, or cooperative multitasking.

To explain the concepts of preemptive multitasking, we developed a model which


is described in the following sections.
General Purpose Computer Embedded System
1 The TEXT section of the program The TEXT section is already
is loaded into the program memory existing in the program memory 2.3.1 Duplication of Hardware
(part of the computer’s RAM). (EEPROM) of the embedded
system.
Let us start with a single CPU, with a program memory referred to as ROM (Read
2 Depending on the object format The addresses are computed by the Only Memory), and a data memory, RAM (Random Access Memory). The CPU
generated by the linker, the linker. may read from the ROM, as well as read from and write to the RAM. In practice,
addresses of the TEXT section may
the ROM is most likely an EEPROM (Electrically Erasable Programmable ROM).
need to be relocated. If the compiler
The CPU reads and executes instructions from the ROM. These instructions
produced position independent
code (PIC), this step is omitted. comprise major parts of the TEXT section in our example program on page 7.
Some of these instructions cause parts of the RAM to be transferred into the CPU,
3 The DATA section of the program The DATA section is already in the
or parts of the CPU to be transferred to the RAM, as shown in Figure 2.4 on
is loaded into program memory EEPROM of the embedded system.
page 13. For general purpose computers, the program memory is a RAM, too. But
(part of the computer’s RAM).
in contrast to embedded systems, the RAM is not altered after the program has
4 Depending of the object format The DATA section is copied as a been loaded – except for programs which modify themselves, or paged systems
generated by the linker, the whole to its final address in RAM.
where parts of the program are reloaded at runtime.
addresses of the TEXT section may
need to be relocated.

TABLE 2.1 Execution of a program

Obviously, the execution of a program in an embedded system is much easier than


in a general purpose computer.
2. Concepts 13 14 2.3 Preemptive Multitasking

Because of the increased hardware costs, this approach for running different
programs in parallel is not optimal. But on the other hand, it has some important
advantages which are listed in Table 2.2. Our goal will be to eliminate the
.TEXT disadvantage while keeping the benefits of our first approach.
ROM

CPU Advantages Disadvantages


The two programs are entirely Two ROMs are needed (although
protected against each other. If one the total amount of ROM space is
program crashes the CPU, then the the same).
other program is not affected by the
crash.
.DATA
RAM Two RAMs are needed (although
the total amount of RAM space is
the same).
FIGURE 2.4 Program Execution Two CPUs are needed.
The two programs cannot
Now let us assume we have two different programs to be run in parallel. This can communicate with each other.
be achieved surprisingly easy_ by duplicating the hardware. Thus, one program
can be executed on one system, and the second program can be executed on the TABLE 2.2 Duplication of Hardware
other system, as shown in Figure 2.5. Note that the TEXT and DATA sections are
at different locations in the ROMs and RAMs of Figure 2.5.
2.3.2 Task Switch

The next step in developing our model is to eliminate one of the two ROMs and
one of the two RAMs. To enable our two CPUs to share one ROM and one RAM,
.TEXT1
we have to add a new hardware device: a clock. The clock has a single output
.TEXT0
producing a signal (see Figure 2.5). This signal shall be inactive (low) for 1,000 to
ROM0 ROM1
10,000 CPU cycles, and active (high) for 2 to 3 CPU cycles. That is, the time
while the signal is high shall be sufficient for a CPU to complete a cycle.
CPU0 CPU1

.DATA1
.DATA0
RAM0 RAM1 CLOCK

FIGURE 2.5 Parallel execution of two programs FIGURE 2.6 Clock


2. Concepts 15 16 2.3 Preemptive Multitasking

The output of the clock is used to drive yet another device: the task switch (see
Figure 2.7). The task switch has one input and two outputs. The outputs shall be
used for turning on and off the two CPUs. The clock (CLK) signal turning from
inactive to active is referred to as task switch event. On every task switch event, CLK
OUT1
the task switch deactivates the active output, OUT0 or OUT1. Then the task OUT0
switch waits until the CLK signal becomes inactive again in order to allow the
CPU to complete its current cycle. Finally, the task switch activates the other CLOCK TASK SWITCH
output, OUT0 or OUT1.

.TEXT1
.TEXT0
CLK OUT1 ROM
OUT0

CLOCK TASK SWITCH


CPU0 CPU1

CLK

OUT0 .DATA1
.DATA0
RAM

OUT1
FIGURE 2.8 Shared ROM and RAM

FIGURE 2.7 Task Switch By using the shared RAM, the two CPUs can communicate with each other. We
have thus lost one of the advantages listed in Table 2.2: the CPUs are no longer
protected against each other. So if one CPU overwrites the DATA segment of the
Each of the CPUs has an input that allows the CPU to be switched on or off. If the
other CPU during a crash, then the second CPU will most likely crash, too.
input is active, the CPU performs its normal operation. If the input goes inactive,
However, the risk of one CPU going into an endless loop is yet eliminated. By the
the CPU completes its current cycle and releases the connections towards ROM
way, when using cooperative multitasking, an endless loop in one task would
and RAM. This way, only one CPU at a time is operating and connected to ROM
suspend all other tasks from operation.
and RAM, while the other CPU is idle and thus not requiring a connection to
ROM and RAM. Consequently, we can remove the duplicated ROM and RAM
from our model, and the remaining ROM and RAM can be shared by the two
2.3.3 Task Control Blocks
CPUs (see Figure 2.8).
The final steps to complete our model are to move the duplicated CPU, and to
implement the task switch in software rather than in hardware. These two steps
are closely related. The previous step of two CPUs sharing one ROM and one
RAM was relatively easy to implement by using different sections of the ROM
and RAM. Replacing the two CPUs by a single one is not as easy, since a CPU
2. Concepts 17 18 2.3 Preemptive Multitasking

cannot be divided into different sections. But before discussing the details, let us However, this algorithm does not make much sense, as our final model in
have a look at the final configuration which is shown in Figure 2.9: Figure 2.9 on page 17 is to have only one CPU. Instead of having two CPUs, we
use a data structure called TCB, Task Control Block, to represent the CPUs of the
system. These TCBs provide space for storing the contents of the CPUs’ registers
R0 to Rn. Moreover, each TCB has a pointer to the TCB that represents the next
CPU. The task switch of Figure 2.8 on page 16 is replaced by a variable,
.TEXT1
.TEXT0
CurrentTask. The TCB concept is illustrated in Figure 2.10.
ROM

INT
CPU

CLOCK CurrentTask NextTask NextTask


R0 R0
... ...
.DATA1
.DATA0 Rn Rn
RAM

FIGURE 2.9 Final Hardware Model for Preemptive Multitasking FIGURE 2.10 Task Control Blocks and CurrentTask

In contrast to the configuration with two CPUs shown in Figure 2.8, the final As a result, the proper task switch algorithm, which is an Interrupt Service
configuration (see Figure 2.9) has only one CPU and no task switch. Moreover, Routine, ISR, is as follows:
the CLK signal has been replaced by an INT signal. This signal indicates that in
the final model, task switching is initiated by a regular interrupt towards the CPU. • Reset the interrupt, if required
• Store the internal CPU registers into the TCB to which CurrentTask is
The final configuration is very similar to our initial model shown in Figure 2.4 on pointing
page 13. We merely have added the clock device, which is now connected to the • Replace CurrentTask by NextTask pointer of the TCB to which
interrupt input of the CPU. Note that our final model is able to run more than two CurrentTask is pointing
programs in parallel.
• Restore the internal CPU registers from the TCB to which
The main reason why we wanted to remove the duplicated CPU is the following: CurrentTask points now
Think of the two CPUs shown in Figure 2.8 on page 16. At any time, these two • Return from ISR
CPUs are most likely in different states. The two possible states are represented
by the internal registers of the CPU and determined by the programs executed by Not that the ISR itself does not change the CPU state during the task switch. But
the CPUs. So to remove the duplicated CPU, we need to replace the hardware this ISR is all we need for preemptive multitasking. By inserting further TCBs in
task switch by a software algorithm. Upon a task switch event (that is, the time the TCB NextTask pointer ring, the model can be extended to perform any
when the clock signal goes inactive, or low), the state of one CPU needs to be number of tasks.
saved, and the state of the second CPU needs to be restored. So we obtain the
following algorithm: There is an important invariant for this scheme: Whenever a task examines the
variable CurrentTask, it will find this variable pointing to its own TCB. If
• Save the internal registers of CPU0
CurrentTask does not point to some arbitrary task, then this task is not active at
• Restore the internal registers of CPU1
2. Concepts 19 20 2.3 Preemptive Multitasking

that time, and thus this condition cannot be detected. In brief, for every task, the DeSchedule() function allows to assign the CPU time to the task that actually
CurrentTask refers to the tasks’s own TCB. needs it, while still maintaining the simplicity of our model. Note that explicit de-
scheduling should only be used rarely, because … (ausdrückliche Begründung
fehlt!!!).
2.3.4 De-Scheduling

Up to now, our two tasks had equal share of CPU time. As long as both tasks are
busy with useful operations, there is no need to change the distribution of CPU
time. For embedded systems, however, a typical situation is as follows: each task
waits for a certain event. If the event occurs, the task handles this event. Then the
task waits for the next event, and so on. For example, assume that each of our
tasks monitors one button which is assigned to the relevant task. If one of the
buttons is pressed, a long and involved computation, lic, is called:
task_0_main()
{
for (;;)
if (button_0_pressed()) lic_0();
}

task_1_main()
{
for (;;)
if (button_1_pressed()) lic_1();
}

As task switching is controlled by our clock device, each task consumes 50


percent of the CPU time, regardless of whether a button is being pressed or not.
This situation is described as busy wait. So precious CPU time is wasted by the
tasks being busy with waiting as long as the button_x_pressed() functions return
0. To ensure optimal exploitation of CPU time, we add a DeSchedule() function
which causes a task to release explicitly its CPU time:
task_0_main()
{
for (;;)
if (button_0_pressed()) lic_0();
else DeSchedule();
}

task_1_main()
{
for (;;)
if (button_1_pressed()) lic_1();
else DeSchedule();
}

So the DeSchedule() function initiates the same activities as our ISR, except that
there is no interrupt to be reset. Unless both buttons are pressed simultaneously,
2. Concepts 21 22 2.4 Semaphores

2.4 Semaphores There is an important invariant: Whenever a task examines the variable State,
it will find this variable set to RUN. State may have any value at any time; but if
To further enhance the usage of CPU time and to reduce the time for task State is not set to RUN, then this task is not active at that time, and thus the task
switching, we will make use of yet another powerful data structure of preemptive cannot find itself in another state.
multitasking: semaphores. These semaphores allow changing the state of our
tasks. This invariant does not yet have any impact on our model, since our tasks are
permanently in state RUN. Clearly, if no task were in state RUN, the above ISR
In our current model, the two tasks are permanently running and thus consuming would loop forever. It will be the semaphores that control the state changes of a
precious CPU capacity. For this purpose, we introduce two new variables in the task; that is, switch between RUN and BLKD.
TCB: State and NextWaiting. For now, State is initially set to the value RUN,
and NextWaiting is set to 0. If required, State may be set to the value BLKD A semaphore represents the number of abstract resources: if resources are
(that is, blocked). So if we refer to the task as being RUN or BLOCKED, that available, the semaphore counts the number of resources. If no resources are
means that the State variable has the corresponding value. As a result, we obtain available, the semaphore counts the number of tasks that are waiting for
the TCB and the state machine shown in Figure 2.11. The state machine will be resources. The latter situation can also be expressed as the “number of resources
extended later. missing”. If there are resources missing, then the TCBs of the tasks waiting for
these resources are appended to a linked list of TCBs of waiting tasks, where the
head of the list is part of the semaphore.

NextTask
The semaphore consists of two variables: a counter and a pointer to a TCB. The
RUN TCB pointer NextWaiting is only valid if the counter is less than 0; otherwise, it
State
is invalid and set to 0 for clarity. The pointer represents the state of the semaphore
NextWaiting as shown in Table 2.3.
R0
...

R0 BLKD
TCB Counter NextWaiting TCB
Value Pointer State
N>0 0 N resources available
FIGURE 2.11 Task State Machine
N=0 0 No resource available, and no task waiting
for a resource
Next, we slightly modify our task switching ISR so that it ignores tasks that are -N < 0 Next task waiting for a N tasks waiting for a resource; that is, N
not in state RUN: resource represented by resources are missing
this semaphore
• Reset the interrupt, if required
• Store the internal CPU registers into the TCB to which CurrentTask is TABLE 2.3 Semaphore States
pointing
• Repeat When a semaphore is created, the counter is initialized with the number N > 0 of
Replace CurrentTask by NextTask pointer of the TCB to which CurrentTask is
resources initially available, and the NextWaiting pointer is set to 0. Then tasks
pointing may request a resource by calling a function P(), or the tasks may release a
until the state of CurrentTask is RUN resource by calling a function V(). The names P and V have been established by
Dijkstra, who invented the semaphores concept. In C++, a semaphore is best
• Restore the internal CPU registers from the TCB to which represented as an instance of a class Semaphore, while P() and V() are public
CurrentTask is pointing now member functions of that class.
• Return from ISR
2. Concepts 23 24 2.4 Semaphores

The algorithm for the P() member function is as follows: sequence of P() function calls performed by a task T0, and V() function calls
performed by another task or ISR on the same semaphore.
• If Counter > 0 (i.e. if resources are available)
Decrement Counter (decrement number of resources)
• Else (i.e. if no resources are available)
Decrement Counter, (increment number of tasks waiting) Count = 2
Set State of CurrentTask to BLKD Count = 1
Count = 0
Append CurrentTask at the end of the waiting chain Count = -1
DeSchedule()
T0 RUN
The P() function examines Counter in order to verify if there are any resources T0 BLKD
available. If so, the number of resources is simply decremented and execution
proceeds. Otherwise, the number of waiting tasks is increased (which again
causes the counter to be decreased, since -Counter is increased), the task is V P P P V V P P V
blocked and appended to the waiting chain, and finally DeSchedule() is called to
make the blocking effective. Obviously, Counter is decremented in any case. So FIGURE 2.12 P() and V() Function Calls
decrementing the counter can be placed outside the conditional part, thereby
changing the comparison from > 0 to > 0. By inverting the condition from > 0 to <
0 and by exchanging the If part (which is empty now) and the Else part, we get the A semaphore is very similar to a bank account. There are no restrictions to pay
following equivalent algorithm: money into your account (V()) whenever you like. In contrast, you can withdraw
money (P()) only if you have deposited it before. If there is no money left, you
• Decrement Counter have to wait until somebody is kind enough to fill the account again. If you try to
• If Counter < 0 cheat the bank by trying to withdraw money from an empty account (P() when
Set State of CurrentTask to BLKD Counter = 0), you go to jail (get blocked) until there is enough money again.
Append CurrentTask at the end of the waiting chain Unfortunately, if you are in jail, there is no way for yourself to fix the problem by
DeSchedule() depositing money, since in jail you can’t do anything at all.

The V() member function has the following algorithm: As for the bank account, there are huge differences between the P() and V()
functions, see Table 2.3.
• If Counter > 0 (i.e. if there are no tasks waiting)
Increment Counter (increment number of resources)
• Else (i.e. if there are tasks waiting)
Increment Counter, (decrement number of tasks waiting)
P() V()
Set State of first waiting task to RUN
P() must not be called in an ISR V() may be called from anywhere,
Remove first waiting task from the head of the waiting chain
including ISR.
The V() function examines Counter. If V() finds that Counter is > 0, which A P() function call may block the calling A V() function call may not block any
task task
means there are no tasks waiting, then it just increments Counter, indicating there
is one more resource available. If V() finds that Counter is < 0, there are tasks
waiting. The number of waiting tasks is decremented by incrementing the TABLE 2.4 P() and V() properties
counter, the first task in the waiting chain is then unblocked by setting its state
back to RUN, and the task is removed from the waiting chain. The task that is
being activated had issued a P() operation before and continues execution just
after the DeSchedule() call it made in the P() function. Figure 2.12 shows a
2. Concepts 25 26 2.5 Queues

2.5 Queues
P() V()
The negative value of Counter is limited Any number of V() operations may be Although semaphores provide the most powerful data structure for preemptive
by the number of existing tasks, since performed, thus increasing Counter to multitasking, they are only occasionally used explicitly. More often, they are
every task is blocked at a P() call with arbitrarily high values. hidden by another data structure called queues. Queues, also called FIFOs (first
Counter < 0. in, first out), are buffers providing at least two functions: Put() and Get(). The
The P() call requires time O(N) if The V() call requires constant time size of the items stored in a queue may vary, thus Queue is best implemented as a
Counter < 0; else, P() requires time template class. The number of items may vary as well, so the constructor of the
O(1). The time can be made constant by class will take the desired length as an argument.
using a pointer to the tail of the waiting
chain, but it is usually not worth the
effort. 2.5.1 Ring Buffers

TABLE 2.4 P() and V() properties The simplest form of a queue is a ring buffer. A consecutive part of memory,
Semaphores used some common initial values which have specific semantics, as referred to as Buffer, is allocated, and two variables, the GetIndex and the
shown in Table 2.3. PutIndex, are initialized to 0, thus pointing to the beginning of the memory
space. The only operation performed on the GetIndex and the PutIndex is
incrementing them. If they happen to exceed the end of the memory, they are reset
to the beginning. This wrapping around at the end turns the straight piece of
memory into a ring. The buffer is empty if and only if GetIndex = PutIndex.
Initial Otherwise, the PutIndex is always ahead of the GetIndex (although the
Counter Semantic PutIndex may be less than the GetIndex if the PutIndex already wrapped
N>1 The semaphore represents a pool of N resources. around at the end, while the GetIndex did not wrap around yet). In Figure 2.13, a
N=1 A single resource that may only be used by one task at a time; for ring buffer is shown both as straight memory and as a logical ring.
example, hardware devices.
N=0 One or several resources, but none available initially; for example, a
buffer for received characters.

TABLE 2.5 Typical Initial Counter Values


2. Concepts 27 28 2.5 Queues

In practice, an empty buffer is much more likely than a buffer overflow. In


embedded systems, an empty buffer is a sign of proper design, while a full buffer
Buffer usually shows that something is wrong. So Get() and Put() can also be compared
Item Item Item Item Item Item to a bank account, which tends to be empty rather than overflow.

Assume that we don not want to return an error condition on full or empty buffers.
Get Put There are good reasons not to return an error condition, since this condition is
likely to disappear again, and the response to such an error condition will most
often be a retry of the Put() or Get(). That is, we assume we want to wait. The
Item simplest (and worst) approach is again busy wait:
Item
For the Get() function:
Item

Item
et
• While GetIndex = PutIndex
G
Pu Do Nothing (i.e. waste time)
t
For the Put() function:

Item
Item

• While GetIndex = (PutIndex + 1) modulo BufferSize


Do Nothing (i.e. was time)
Item
Item
The note on bank accounts and the term busy wait should have reminded you of
semaphores.
FIGURE 2.13 Ring Buffer
2.5.2 Ring Buffer with Get Semaphore
The algorithm for Put(), which takes an item as its arguments and puts it into the
ring buffer, is as follows: The basic idea is to consider the items in a buffer as resources. I have seen this
idea for the first time in an operating system called MIRAGE about twenty years
• Wait as long as the Buffer is full, or return Error indicating overflow ago. It was used for interrupt-driven character I/O.
• Buffer[PutIndex] = Item
• PutIndex = (PutIndex + 1) modulo BufferSize (increment In addition to the GetIndex and PutIndex variables, we add a semaphore called
PutIndex, wrap GetSemaphore, which represents the items in the buffer. As GetIndex and
around at end) PutIndex are initialized to 0 (that is, the buffer is initially empty), this semaphore
is initialized with its Counter variable set to 0.
Get(), which removes the next item from the ring buffer and returns it, has the
following algorithm: For each Put(), a V() call is made to this semaphore after the item has been
inserted into the buffer. This indicates that another item is available.
• Wait as long as Buffer is empty, or return Error indicating underflow
• Wait as long as the Buffer is full, or return Error indicating overflow
• Item = Buffer[GettIndex]
• Buffer[PutIndex] = Item
• GetIndex = (GetIndex + 1) modulo BufferSize(increment GetIndex,
wrap around at end) • PutIndex = (PutIndex + 1) modulo BufferSize(increment PutIndex,
wrap around at end)
• Return Item
• Call V() for GetSemaphore
2. Concepts 29 30 2.5 Queues

For each Get(), a P() call is made before removing an item from the buffer. If driven serial port. For each direction, a buffer is used between the task and the
there are no more items in the buffer, then the task performing the Get() and thus serial port, as shown in Figure 2.14. Assume further that the task shall echo all
the P() is blocked until someone uses Put() and thus V() to insert an item. characters received to the serial port, possibly running at a lower speed. At a first
glance, you may expect to have the (upper) receive buffer used with a get
• Call P() for GetSemaphore
semaphore, and the (lower) transmit buffer with a put semaphore. The task will be
• Item = Buffer[GettIndex] blocked most of the time on the get semaphore, which is a normal condition.
• GetIndex = (GetIndex + 1) modulo BufferSize(increment GetIndex, What would happen, however, if the task would block on the put semaphore, i.e.
wrap around at end) if the transmit buffer is full? This will eventually happen if the transmit data rate
• Return Item is lower than the receive data rate. In this case, one would normally signal the
sender at the far end to stop transmission for a while, for example by hardware or
software handshake. A blocked task, however, would not be able to do this. This
2.5.3 Ring Buffer with Put Semaphore scenario is quite common, and one would use a get semaphore for the upper
buffer, but a plain ring buffer for the lower one.
Instead of considering the items that are already inserted as resources, we could
as well consider the free space in the buffer as resources. In addition to the
GetIndex and PutIndex variables for the plain ring buffer, we add a semaphore
called PutSemaphore, which represents the free space in the buffer. As Serial Port
GetIndex and PutIndex are initialized to 0 (that is, the buffer is initially empty), Put Get
this semaphore (in contrast to the GetSemaphore) is initialized with its Counter Rx
variable set to BufferSize. Task
Tx
For each Put(), a P() call is made to this semaphore before the item is inserted Get Put
into the buffer and thus buffer space is reduced. If there is no more free space in
the buffer, then the task performing the Put() and thus the P() is blocked until
someone uses Get() and thus V() to increase the space again. FIGURE 2.14 Serial Communication between a Task and a Serial Port
• Call P() for PutSemaphore
• Buffer[PutIndex] = Item 2.5.4 Ring Buffer with Get and Put Semaphores
• PutIndex = (PutIndex + 1) modulo BufferSize(increment PutIndex,
wrap around at end) The final option is to use both a get and a put semaphore. The buffer and the
semaphores are initialized as described in the previous sections.
For each Get(), a P() call is made after removing an item from the buffer,
indicating another free position in the buffer. For each Put(), a P() call is made to the put semaphore before the item is inserted,
and a V() call is made to the get semaphore after the item is inserted:
• Wait as long as Buffer is empty, or return Error indicating underflow
• Item = Buffer[GettIndex] • Call P() for PutSemaphore (block until there is space)
• GetIndex = (GetIndex + 1) modulo BufferSize(increment GetIndex, • Buffer[PutIndex] = Item
wrap around at end) • PutIndex = (PutIndex + 1) modulo BufferSize
• Call V() for PutSemaphore • Call V() for GetSemaphore (indicate a new item)
• Return Item
For each Get(), a V() call is made on the get semaphore before an item is
This scheme is used less often than the ring buffer with Get semaphore. To removed, and a P() call is made on the put semaphore after removing an item
understand why, let us consider a task which communicates with an interrupt- from the buffer.
2. Concepts 31 32

• Call P() for GetSemaphore (block until there is an item)


• Item = Buffer[GettIndex]
• GetIndex = (GetIndex + 1) modulo BufferSize
• Call V() for PutSemaphore (indicate space available)
• Return Item

This ring buffer with get and put semaphore is optimal in the sense that no time is
wasted, and no error condition is returned on either full or empty queues.
However, it cannot be used in any ISR, since both sides, Put() and Get(), use the
P() call which is forbidden for ISRs. Thus the only application for this scheme
would be the communication between tasks. Moreover, the disadvantages of put
semaphores apply here as well.
3. Kernel Implementation 33 34 3.2 Hardware Model

3 Kernel Implementation executed in supervisor mode is written in assembler and is contained in the file
crt0.S. The code in crt0.S is divided into the start-up code, functions for
accessing the hardware, interrupt service routines, the task switch (scheduler),
and the semaphore functions that are written in assembler for performance
reasons.

The middle part of Figure 3.1 shows the rest of the kernel, which is executed in
user mode. Any call to the code in crt0.S requires a change to supervisor mode,
i.e. every arrow from the middle to the lower part is related to one or several
3.1 Kernel Architecture
TRAP instructions which cause a change to supervisor mode. Class os contains a
collection of wrapper functions with TRAP instructions and enables the
Figure 3.1 shows the overall architecture of the kernel implementation. application to access certain hardware parts. The classes SerialIn and SerialOut,
referred to as Serial I/O, require hardware access and are also accessed from the
interrupt service routine. Class Task contains anything related to task
management and uses the supervisor part of the kernel for (explicit) task
Application
switching. Task switching is also caused by the interrupt service routine. Class
Application Semaphore provides wrapper functions to make the implementation of its
Kernel member functions available in user mode. Several Queue classes are used inside
the kernel and are also made available to the application; most of them use class
Queue Semaphore.

Application Serial I/O Task Normally, an application is not concerned with the internal kernel interfaces. The
Startup relevant interfaces towards the kernel are those defined in classes os, SerialIn,
os Queue Queue Semaphore SerialOut, Task, Queue, and sometimes Semaphore.

User Mode 3.2 Hardware Model


Supervisor
Mode In order to understand the kernel implementation, we need some information
about the underlying hardware:
• Which processor type is used?
Startup Hardware Access ISR Scheduler P(), V(), Poll() • How is the memory of the processor mapped?
crt0.S • Which peripherals are used?
• Which interrupt assignment of the peripherals are used?
• How do the peripherals use the data bus?
Hardware (DUART)
For the implementation discussed here, the hardware described in the following
sections is assumed.
FIGURE 3.1 Kernel Architecture
3.2.1 Processor
The bottom part of Figure 3.1 shows the part of the kernel that is (along with the
functions called from there) executed in supervisor mode. All code that is We assume that any processor of the Motorola MC68000 family is used. The
implementation works for the following processors:
3. Kernel Implementation 35 36 3.2 Hardware Model

• MC68000 3.2.4 Interrupt Assignment


• MC68008
We assume the DUART may issue interrupts at level 2 to the CPU. We further
• MC68010
assume that the interrupt vector is determined by the interrupt level (i.e. the vector
• MC68012 is a so called autovector) rather than by the DUART.
• MC68020
• MC68030
3.2.5 Data Bus Usage
• MC68040
• CPU32 We assume the DUART is connected to data lines D16..D23 of a MC68020, and
that it indicates WORD size for read accesses because of the considerable turn-off
Note that out of this range of processors, only the MC68020 has been tested. For time of 150 nS for the data bus of the MC68681 as well as for many other
use of other chips, see also Section 3.2.5. peripherals. For a MC68020 running at 20 MHz, the timing to deal with is as
shown in Figure 3.2.

3.2.2 Memory Map


CLK
We assume the following memory map for the processor:
• (E)EPROM at address 0x00000000..0x0003FFF AS
• RAM at address 0x20000000..0x2003FFF
• DUART at address 0xA0000000..A000003C CSDUART

DATADUART
The EPROM and RAM parts of the memory map are specified in the
System.config file.
CSROM
1 #define ROMbase 0x00000000
2 #define ROMsize 0x00040000
DATAROM
3 #define RAMbase 0x20000000
4 #define RAMsize 0x00040000
T= 0 100 150 250

3.2.3 Peripherals
FIGURE 3.2 Data Bus Contention
We assume a MC68681 DUART with two serial ports, a timer, and several
general purpose input and output lines. After deasserting the DUART’s chip select, the DUART needs a long time to
three-state its data bus. This causes contention on the data bus between the
The DUART base address, along with the addresses of the various DUART DUART and the device addressed with the next cycle, which is usually a ROM or
registers, is contained in the file duart.hh. RAM. Adding wait states does not help here: this way, the CSDUART would
5 #define DUART 0xA0000000 merely be extended, while the contention remains as it is. The standard way of
dealing with this contention is to separate the DUART from the CPU’s data bus
by means of a bidirectional driver, which is switched on with the DUART’s chip
select CSDUART. But this solution requires an additional driver, and frequently
cost limits, PCB space, or components do not allow for this.
3. Kernel Implementation 37 38 3.2 Hardware Model

For the MC68000 family, this problem can also be solved in a different way: by instruction that causes the lower word of a data register to be sign-extended into
generating two read cycles towards the DUART instead of one read cycle only. the upper word. That is, .W refers to the source size only. Failing to save the upper
However, only in the first cycle, a CSDUART is generated, while the second is a word of the register is a common mistake that is hard to detect, since it usually
dummy cycle allowing the DUART to completely three-state its data bus. For occurs in an interrupt service routine.
higher speeds, the dummy cycle can be extended by wait states.
As a result, crt0.S contains the following two lines for all CPUs of the MC68000
As the CPUs of the MC68000 family have different memory interfaces, the way family except for MC68008:
to implement such a dummy cycle depends on the CPU used. 136 MOVEM.L rDUART_ISR, D7 | get interrupt sources
137 SWAP D7 |
For MC68020, MC68030, and MC68040 CPUs, the CPU executes a LONG move
from the peripheral. This causes the CPU’s SIZ0 and SIZ1 to request a LONG For the MC68008, the above lines need to be replaced by the following code:
read cycle from the peripheral. The peripheral would, however, indicate a WORD MOVEM.W rDUART_ISR, D7 | CCAUTION: D7.W is sign-extended !!!
size at the end of the cycle. The CPU then automatically initiates another cycle ASR.W #8, D7 |

with size WORD in order to get the missing data. This second cycle is the dummy
cycle. The actual value read by the CPU contains only one valid byte from the
peripheral (in D23..D16 or D31..D24, depending on where the peripheral is
located on the data bus). The remaining three bytes read are invalid. If the SIZ0
and SIZ1 lines are properly decoded, generating a bus error for all other sizes, this
method is safe even in the case of software faults.

For the MC68000, MC68010 and MC68012, such dynamic bus resizing is not
possible. However, the data bus size of the peripheral is limited to WORD size
anyway for these CPUs. Unfortunately, these CPUs do not provide SIZ0 and SIZ1
lines to indicate the size of a cycle. Instead, the A1 address line has to be decoded
in order to distinguish between the first cycle towards the peripheral and the
following dummy cycle. This method is not entirely safe though: by mistake, one
might access the address for the dummy cycle first.

Finally, for the MC68008, which has a 8 bit data bus only, the same method as for
the MC68000 can be used, except that a WORD read cycle instead of a LONG
read cycle is executed, and address line A0 is used instead of A1. The CPU splits
this WORD read cycle into two BYTE read cycles automatically. Surprisingly,
this method is safe again, because a word read to an odd address causes an
address error trap.

In any case, some part of the data bus is undefined. The CPUs of the MC68000
family may change their Z (zero) and N (negative) flag depending on the data
read. There is a negligeable chance that these flags become metastable inside the
CPU when the floating part of the data bus changes just in the moment when the
data lines are latched by the CPU. Although most likely this has no effect in
practice, one should use a move instruction that does not change any status bits,
for example MOVEM. It is primarily intended for moving several registers, but
can serve for this particular purpose as well. In the case of a MC68008 CPU, i.e
when using MOVEM.W, be aware of a strange inconsistency of the MOVEM
3. Kernel Implementation 39 40 3.3 Task Switching

3.3 Task Switching ones cited above are rarely used, because one would have to analyze carefully
which data structures could be modified at which interrupt level. Changing
The MC68000 family of microprocessors which is used for our implementation interrupt levels would then mean repeating this analysis, which is an error-prone
provides two basic modes of operation: the user mode and the supervisor mode. procedure.
(The 68020 microprocessors and higher also feature a sub-mode of the supervisor
mode, the master mode, which allows for a cleaner implementation of interrupt
handling. But for compatibility reasons, we did not use it here.) In user mode,
only a subset of the instructions provided by the microprocessor can be executed.
U0
An attempt to execute a privileged instruction (that is, an instruction not allowed
in user mode) causes a privilege violation exception to be executed instead of the U7
instruction. Usually, C++ compilers do no generate any privileged instructions. S0
The microprocessor indicates its present mode also to the hardware by its FC2 S2
output. This way, certain hardware parts, such as the DUART in our S7
implementation, are protected against inadvertent accesses from user mode.

One could ignore the user mode feature and run the whole system in supervisor T= 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
mode. A task could then e.g. write to a hardware register at address reg directly
from C++:
*(unsigned char *)reg = data; FIGURE 3.3 Modes and Interrupts vs. Time

This method is commonly used for processors that have no separate user and
supervisor modes. But the price paid for this simplicity is a considerable loss of As shown in the above figure, the system starts at T=0 in supervisor mode, with
protection. all interrupts disabled. After initialization, the first task (which is the idle task
explained later) starts execution at T=1, with interrupts still disabled. The idle
The MC68000 family evolved in such a way that the distinction between user and task sets up other tasks and enables interrupts in the hardware. At T=2, the idle
supervisor mode could be applied to memory accesses also by using a hardware task wants to lower the interrupt mask to 0. Since this is a privileged instruction, it
memory management unit (MMU). From the MC68040 on, this MMU was even has to enter supervisor mode, change interrupt mask and return to user mode with
integrated in the microprocessor chip. By using a MMU, tasks are completely interrupts enabled at T=3. At this point, that is at T=4, interrupts from the
protected against each other. Therefore, we chose not to take the easy way, but to hardware are accepted by the CPU. The interrupt changes to supervisor mode and
used the separate user and supervisor modes: regular task code is run in user automatically sets the interrupt level to 2. As we will see later, in our
mode, while code accessing critical resources is run in supervisor mode. Such implementation we will always check for possible task switches before returning
critical resources are peripherals as for example our DUART, or the interrupt to user mode. This check is made with interrupts disabled. Hence every return to
mask of the processor. user mode is from S7. Thus at T=5, the interrupt processing is finished, and a
check for task switching is made with interrupts disabled. At T=6, this check is
Sometimes, plotting the mode (U is user mode, S is supervisor mode) together finished, and the CPU returns to user mode, which may be code of the same task
with the interrupt level against time proves to be useful. A typical plot is shown in or a different one. At T=7, a task performs a protected operation in supervisor
Figure 3.3. In our system, we use only one interrupt at level 2. Thus the only mode, such as writing to a hardware register. Like before, it returns to user mode
interrupt mask levels that make sense in our system are 0 (all interrupts will be (via S7 at T=8) at T=9. Next, we see a task intending to raise the interrupt level in
served), 2 (only interrupts above level 2 will be served), and 7 (only non- order to modify a critical data structure. It does so by entering supervisor mode at
maskable interrupts will be served). Regular task code runs in user mode, with all T=10 and returning to user mode in the usual way (via S7 at T=11), but with
interrupts enabled (indicated by U0). In some cases, in particular when interrupts disabled, at T=12. After finishing the critical section, it enters
performing operations on queues, interrupt service routines must be prevented supervisor mode again at T=13 and returns to user mode with interrupts enabled
from changing a queue’s variables. The can be easily achieved by disabling (via S7 at T=14) at T=15.
interrupts even in user mode, U7. In user mode, other interrupt levels than the
3. Kernel Implementation 41 42 3.3 Task Switching

As already mentioned, we check for tasks switches at every return to user mode. currTask points to the task currently running. This variable is static, i.e. it is
Instead, it would also be possible to switch tasks immediately, whenever desired. shared by all instances of the class Task.
However, it is of no use to switch tasks while in supervisor mode, as the task
switch would come into effect only at return to user mode. Switching tasks The easiest way to trigger a task switch is to explicitly de-schedule a task, which
immediately could lead to several task switches while in supervisor mode, but is implemented as the inline function Dsched(). This function merely executes a
only one of these task switches would have any effect. It is thus desirable to avoid Trap #1 instruction. This instruction causes the CPU to enter supervisor mode
unnecessary task switches and delay the decision whether to switch tasks until and to continue execution at an address specified by a vector associated with the
returning to user mode. Since task switching affects critical data structures, instruction (see also crt0.S in Appendix A.1).
interrupts are disabled when tasks are actually switched. 58 .LONG _deschedule | 33 TRAP #1 vector
...
As explained in Section 2.3, each task is represented by a Task Control Block, 228 |-----------------------------------------------------------------------|
229 | TRAP #1 (SCHEDULER) |
TCB. This TCB is implemented as an instance of the class Task. This class 230 |-----------------------------------------------------------------------|
contains all functions necessary for managing tasks. For task switching, the 231 |
232 _deschedule: |
following members of class Task are relevant: 233 ST _consider_ts | request task switch
234 |
25 class Task
235 _return_from_exception: | check for task switch
26 {
...
...
418 _consider_ts: .BYTE 0 | true if task switch need be checked
30 Task * next; // 0x00
...
32 unsigned long Task_D0, Task_D1, Task_D2, Task_D3; // 0x08.. So executing Trap #1 causes the CPU to proceed in supervisor mode at label
33 unsigned long Task_D4, Task_D5, Task_D6, Task_D7; // 0x18..
34 unsigned long Task_A0, Task_A1, Task_A2, Task_A3; // 0x28.. _deschedule. There, a flag called _consider_ts is set, and the common code for
35 unsigned long Task_A4, Task_A5, Task_A6; // 0x38.. all returns to user mode is executed. It is this common code that may actually
36 unsigned long * Task_USP; // 0x44..
37 void (*Task_PC)(); // 0x48
perform the task switch.
38 unsigned long TaskSleep; // 0x4C
... Upon entering supervisor mode, the CPU automatically creates an exception stack
40 unsigned short priority; // 0x54
41 unsigned char Task_CCR; // 0x56 frame on its supervisor stack, as shown in Figure 3.4:
42 unsigned char TaskStatus; // 0x57
...
71 static void Dsched()
72 { asm("TRAP #1"); };
...
108 enum { RUN = 0x00,
109 BLKD = 0x01,
110 STARTED = 0x02,
111 TERMINATED = 0x04, PC low
112 SLEEP = 0x08,
113 FAILED = 0x10, PC high
114 };
... SSP SSR CCR
132 static Task * currTask;
...
139 };

FIGURE 3.4 Exception Stack Frame


The variables Task_D0..Task_D7, Task_A0..Task_A6, Task_USP, Task_PC
and Task_CCR provide space for saving the corresponding CPU registers when a
task is swapped out. Let us have a closer look at the code after label _return_from_exception. First of
all, all interrupts are disabled, so that this code is not interrupted before the
The Task pointer next is used to find the next TCB, while the task’s priority and exception is completely handled:
status are analyzed in order to find the next task to be run at a task switch.
3. Kernel Implementation 43 44 3.3 Task Switching

235 _return_from_exception: | check for task switch Now all data belonging to the current task are saved in their TCB. We are free to
236 OR.W #0x0700, SR | disable interrupts
use the CPU registers from here on. The next step is to find the next task to run:
Then the stack frame is analyzed to determine in which mode the exception by chasing the next pointer of the current task, until the current task is reached
occurred. If the supervisor bit is set (0x2000 in the SR), then the exception again. We use A2 to mark where the search started. The task we are looking for is
occurred in supervisor mode, and the task switch shall thus be deferred until the one with the highest priority in state RUN (i.e. 0). If the current task is in state
returning to user mode. If the exception occurred in user mode, but with nonzero RUN, then we need not consider tasks with lower priority, which speeds up the
interrupt level (SR & 0x0700) in user mode, then the task switch shall be deferred search loop. Otherwise we make sure that at least the idle task will run in case no
as well, since the task has disabled interrupts. That is, whenever (SR & 0x2700) is other task can:
nonzero, the task switch shall not be performed, and the CPU directly returns 258 |---------------------------------------|
257 | find next task to run
from the exception: 260 | A2: marker for start of search
261 | A6: best candidate found
237 MOVE.W (SP), -(SP) | get status register before exception
262 | D6: priority of task A6
238 AND.W #0x2700, (SP)+ | supervisor mode or ints disabled ?
263 | A0: next task to probe
239 BNE L_task_switch_done | yes dont switch task
264 | D0: priority of task A0
...
265 |---------------------------------------|
304 L_task_switch_done: |
266 |
305 RTE |
267 MOVE.L __4Task$currTask, A2 |
268 MOVE.L A2, A6 |
Otherwise, it is checked whether a task switch is required at all. In our case, this 269 MOVEQ #0, D6 |
270 TST.B TaskStatus(A6) | status = RUN ?
was true, since we have unconditionally set _consider_ts. In certain situations, 271 BNE L_PRIO_OK | no, run at least idle task
_consider_ts is not set; for example when unblocking a task that has a lower 272 MOVE.W TaskPriority(A6), D6 |
priority than the current task. Then case the CPU merely returns from the 273 L_PRIO_OK: |
274 MOVE.L TaskNext(A6), A0 | next probe
exception: 275 BRA L_TSK_ENTRY |

240 TST.B _consider_ts | task switch requested ? The search loop skips all tasks which are not in state RUN or have a lower priority
241 BEQ L_task_switch_done | no
than the last suitable task found. If several tasks in state RUN have the same
priority, the first of these tasks is chosen. The best candidate found is stored in
At this point, we initiate a task switch. First, _consider_ts is reset to prevent
A6:
further task switches. Then the CPU registers are stored in the current TCB. Since
we may not destroy any CPU registers here, we save A6 onto the stack and restore 276 L_TSK_LP: |
277 TST.B TaskStatus(A0) | status = RUN ?
it back to the TCB afterwards: 278 BNE L_NEXT_TSK | no, skip
277 MOVEQ #0, D0 |
242 CLR.B _consider_ts | reset task switch request
280 MOVE.W TaskPriority(A0), D0 |
243 |
281 CMP.L D0, D6 | D6 higher priority ?
244 |---------------------------------------|
282 BHI L_NEXT_TSK | yes, skip
245 | swap out current task by saving
283 MOVE.L A0, A6 |
246 | all user mode registers in TCB
284 MOVE.L D0, D6 |
247 |---------------------------------------|
285 ADDQ.L #1, D6 | prefer this if equal priority
248 |
286 L_NEXT_TSK: |
249 MOVE.L A6, -(SP) | save A6
287 MOVE.L TaskNext(A0), A0 | next probe
250 MOVE.L __4Task$currTask, A6 |
288 L_TSK_ENTRY: |
251 MOVEM.L D0-D7/A0-A5, Task_D0(A6)| store D0-D7 and A0-A5 in TCB
289 CMP.L A0, A2 |
252 MOVE.L (SP)+, Task_A6(A6) | store saved A6 in TCB
290 BNE L_TSK_LP |
291 |
Swapping out the task is completed by saving the USP (i.e., A7 when in user
mode), the CCR, and the PC of the current task into the TCB: Here, A6 points to the TCB of the next task which is to run and which is set as
253 MOVE USP, A0 | current task. In the same way as the previous task was swapped out, the new
254 MOVE.L A0, Task_USP(A6) | save USP in TCB current task is swapped in. First, the CCR and PC in the exception stack frame are
255 MOVE.B 1(SP), Task_CCR(A6) | save CCR from stack in TCB
256 MOVE.L 2(SP), Task_PC(A6) | save PC from stack in TCB
replaced by that of the new current task:
257 |
3. Kernel Implementation 45 46 3.4 Semaphores

292 |---------------------------------------| 3.4 Semaphores


293 | next task found (A6)
294 | swap in next task by restoring
295 | all user mode registers in TCB Semaphores are declared in file Semaphore.hh. Although they could be
296 |---------------------------------------|
297 |
implemented in C++, we will see that they are best implemented in assembler.
298 MOVE.L A6, __4Task$currTask | task found. Thus, there is no Semaphore.cc file. The interface to the assembler routines is
299 MOVE.L Task_PC(A6), 2(SP) | restore PC on stack specified inline in Semaphore.hh.
300 MOVE.B Task_CCR(A6), 1(SP) | restore CCR on stack

Then the USP and registers for the new current task are restored, and the CPU
3.4.1 Semaphore Constructors
returns from exception processing. This way, the execution would normally be
continued where the former current task was interrupted. But since we have
One of the most common special cases for semaphores are semaphores
replaced the return address and CCR of the stack frame by that of the new current
representing a single resource that is available from the outset. We have chosen
task, execution proceeds where the new current task was interrupted instead:
this case for the default constructor. Semaphores representing 0 or more than one
301 MOVE.L Task_USP(A6), A0 | resources initially can be constructed by providing the initial count:
302 MOVE A0, USP | restore USP
303 MOVEM.L Task_D0(A6), D0-D7/A0-A6| restore D0-D7, A0-A5 (56 bytes) 13 Semaphore() : count(1), nextTask(0) {};
304 L_task_switch_done: | 14 Semaphore(int cnt) : count(cnt), nextTask(0) {};
305 RTE |

3.4.2 Semaphore Destructor

There is no destructor for semaphores. In general, it is dangerous to destruct


semaphores at all. If a semaphore with a counter value < 0 is deleted, then the
tasks in the waiting queue would either be unblocked (although most likely the
resource they are waiting for would not be available), or blocked forever. In the
first case, the semaphore would need to return an error code which would need to
be checked after any P() operation. This is not very handy, so we made P() a
function returning no value at all. Generally, semaphores should have an infinite
lifetime, i.e. they should be static.

However, sometimes dynamic semaphores can be useful. In these cases, it is the


responsibility of the programmer to make sure that the semaphore dies in the
correct way.

3.4.3 Semaphore P()

The P() member function could be written in C++. While the semaphore and
possibly the chain of waiting tasks is modified, interrupts must be disabled:
void Semaphore::P()
{
oldIntMask = os::set_INT_MAK(7); // disable interrupts

counter --;

if (counter < 0) // if no resource available


{
3. Kernel Implementation 47 48 3.4 Semaphores

consider_ts = 1; // task switch required 312 OR #0x0700, SR | disable interrupts


CurrentTask->Status |= BLKD; // block current task 313 SUBQ.L #1, SemaCount(A0) | count down resources
CurrentTask->nextWaiting = 0; // current task is end of waiting chain 314 BGE _return_from_exception | if resource available
315 ST _consider_ts | request task switch
if (nextTask == 0) // no other task waiting 316 MOVE.L SemaNextTask(A0), D0 | get waiting task (if any)
{ 317 BNE.S Lsp_append | got a waiting task
nextTask = CurrentTask; // head of task waiting chain 318 MOVE.L __4Task$currTask, D0 | get current Task
} 319 MOVE.L D0, SemaNextTask(A0) | store as first waiting
else 320 MOVE.L D0, A0 |
{ 321 BSET #0, TaskStatus(A0) | block current task
Task * t = nextTask; 322 CLR.L TaskNextWaiting(A0) | say this is last waiting
323 BRA _return_from_exception | done
// find end of task waiting chain... 324 |
while (t->nextWaiting;) t = t->nextWaiting; 325 Lsp_append: | goto end of waiting list
326 MOVE.L D0, A0 |
// here t is the last task in the waiting chain 327 MOVE.L TaskNextWaiting(A0), D0 | get next waiting (if any)
t->nextWaiting = CurrentTask; 328 BNE.S Lsp_append | if not last waiting
} 329 |
} 330 MOVE.L __4Task$currTask, D0 | get current task
331 MOVE.L D0, TaskNextWaiting(A0) | store as last waiting
os::set_INT_MASK(oldIntMask); // restore interrupt level 332 MOVE.L D0, A0 |
return; 333 BSET #0, TaskStatus(A0) | block current task
} 334 CLR.L TaskNextWaiting(A0) | say this is last waiting
335 BRA _return_from_exception | done
336 |
Note that the actual task switch would happen at the second set_INT_MASK()
call, when the corresponding exception processing changes back to user mode.
Disabling and enabling interrupts would cause two TRAP instructions for the 3.4.4 Semaphore Poll()
set_INT_MASK() calls and for the relevant check for task switches at the end of
exception processing. Compared to an assembler implementation, this would be a The Poll() member function is the simplest semaphore. In C++ we would have the
significant overhead. Considering that semaphores are used by higher level data following lines of code:
structures, such as queues, as well as in every character I/O interrupt service
routine (V() only), this overhead should be avoided by implementing all void Semaphore::Poll()
{
Semaphore member functions in assembler (see also crt0.S in Appendix A.1). int result = 1; // assume no resource avaliable
For the P() function, we use TRAP #3 to switch to supervisor mode, passing the
oldIntMask = os::set_INT_MAK(7); // disable interrupts
semaphore in register A0 and telling the compiler that D0 might be changed, so
that we do not need to save it. if (counter > 0)
{
15 void P() { counter--;
16 asm volatile ("MOVE.L %0, A0 result = 0;
17 TRAP #3" : : "g"(this) : "d0", "a0"); }
18 };
os::set_INT_MASK(oldIntMask); // restore interrupt level
return result;
In crt0.S, the TRAP #3 vector points to the actual assembler code for P(): }
60 .LONG _Semaphore_P | 35 TRAP #3 vector
Like for P(), we implement this in assembler, using TRAP #5:
The assembler code is only slightly longer than the C++ code. Since this is an 23 int Poll() {
exception handling routine, we do not need to restore the interrupt level at the 24 int r;
end. 25
26 asm volatile ("MOVE.L %1, A0
307 |-----------------------------------------------------------------------| 27 TRAP #5
308 | TRAP #3 (Semaphore P operation) | 28 MOVE.L D0, %0"
309 |-----------------------------------------------------------------------| 29 : "=g"(r) : "g"(this) : "d0", "a0");
310 | 30 return r;
311 _Semaphore_P: | A0 -> Semaphore 31 };
3. Kernel Implementation 49 50 3.4 Semaphores

In crt0.S, the TRAP #5 vector points to the actual assembler code for Poll(): 19 void V() {
20 asm volatile ("MOVE.L %0, A0
62 .LONG _Semaphore_Poll | 37 TRAP #5 vector 21 TRAP #4" : : "g"(this) : "d0", "a0");
22 };

And the code is straightforward:


This time, TRAP #4 is used:
61 .LONG _Semaphore_V | 36 TRAP #4 vector

363 |-----------------------------------------------------------------------|
364 | TRAP #5 (Semaphore Poll operation) | The assembler code for V() is as follows:
365 |-----------------------------------------------------------------------|
337 |-----------------------------------------------------------------------|
366 |
338 | TRAP #4 (Semaphore V operation) |
367 _Semaphore_Poll: | A0 -> Semaphore
339 |-----------------------------------------------------------------------|
368 OR #0x700, SR | disable interrupts
340 |
369 MOVEQ #1, D0 | assume failure
370 TST.L SemaCount(A0) | get count 341 _Semaphore_V: | A0 -> Semaphore
371 BLE _return_from_exception | failure 342 OR #0x0700, SR | disable interrupts
372 SUBQ.L #1, SemaCount(A0) | 343 ADDQ.L #1, SemaCount(A0) |
373 MOVEQ #0, D0 | success 344 BLE.S Lsv_unblock | unblock waiting task
374 BRA _return_from_exception | check for task switch 345 CLR.L SemaNextTask(A0) |
375 | 346 BRA _return_from_exception | done
347 |
348 Lsv_unblock: |
349 EXG D0, A1 |
3.4.5 Semaphore V() 350 MOVE.L SemaNextTask(A0), A1 | get next waiting task
351 MOVE.L TaskNextWaiting(A1), SemaNextTask(A0)
352 MOVE.L A1, A0 |
The last member function required is V(). Again, we provide a C++ 353 EXG D0, A1 |
354 BCLR #0, TaskStatus(A0) | unblock the blocked task
implementation first to understand the assembler code: 355 CLR.L TaskNextWaiting(A0) | just in case
356 MOVE.W TaskPriority(A0), D0 | get priority of unblocked Task
void Semaphore::V()
357 MOVE.L __4Task$currTask, A0 | get current Task
{
358 CMP.W TaskPriority(A0), D0 | current prio >= unblocked prio ?
oldIntMask = os::set_INT_MAK(7); // disable interrupts
359 BLS _return_from_exception | yes, done
360 ST _consider_ts | no, request task switch
counter ++;
361 BRA _return_from_exception | done
362 |
if (counter <= 0) // if any task waiting
{
Task * head = nextTask Up to now, we have presented almost all of the code written in assembler. So it is
nextTask = head->nextWaiting; // remove head of waiting chain
time to relax by looking at some simple C++ code.
head>Status &= ~BLKD; // unblock head of waiting chain

if (CurrentTask->priority < head->priority)


consider_ts = 1; // task switch required
}

os::set_INT_MASK(oldIntMask); // restore interrupt level


return;
}

The comparison (CurrentTask->priority < head->priority) is crucial for the entire


system performance. If we always set consider_ts, then e.g. any character
received, for which a lower priority task is waiting, would swap out and in again
every higher priority task. In contrast to P(), V() may be used in interrupt service
routines. Thus performance is even more critical, and V() is implemented in
assembler:
3. Kernel Implementation 51 52 3.5 Queues

3.5 Queues ...


16 template <class Type> RingBuffer<Type>::~RingBuffer()
17 {
As we already saw, there are different kinds of queues, depending on where 18 delete [] data;
19 }
semaphores are used. But common to all queues is a ring buffer. Hence we
implement ring buffers as a separate class from which the different queues are
derived. Since a ring buffer may contain any kind of items, we make a template
3.5.2 RingBuffer Member Functions
class called RingBuffer.
1 // Queue.hh The member functions IsEmpty() and IsFull() are self-explanatory. Peek(Type
...
12 template <class Type> class RingBuffer & dest) returns QUEUE_FAIL (i.e. nonzero) if the queue is empty. Otherwise, it
13 { stores the next item in the queue in dest, but without removing it from the queue.
14 public:
15 RingBuffer(unsigned int Size);
The Peek() function is useful for scanners which usually require a single
16 ~RingBuffer(); character look-ahead. Traditionally, a character looked ahead is pushed back into
17 a queue by means of a function unput(char) if the character is not required. But
18 int IsEmpty() const { return (count) ? 0 : -1; };
19 int IsFull() const { return (count < size) ? 0 : -1; }; this solution causes several problems. ??? Which problems ??? So providing a
20 look-ahead function like Peek() is the better solution, as it does not remove any
21 int Peek(Type & dest) const;
22 item from the queue.
23 protected:
24 enum { QUEUE_OK = 0, QUEUE_FAIL = -1 }; 1 // Queue.cc
...
25
21 template <class Type> int RingBuffer<Type>::Peek(Type & dest) const
26 virtual int PolledGet(Type & dest) = 0;
22 {
27 virtual int PolledPut(const Type & dest) = 0;
23 int ret = QUEUE_FAIL;
28 inline void GetItem(Type & source);
24
29 inline void PutItem(const Type & src);
25 {
30
26 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS);
31 unsigned int size;
27 if (count) { dest = data[get]; ret = QUEUE_OK; }
32 unsigned int count;
28 os::set_INT_MASK(old_INT_MASK);
33
29 }
34 private:
30 return ret;
35 Type * data;
31 }
36 unsigned int get;
37 unsigned int put;
38 }; The member function PutItem() inserts, and GetItem() removes an item from the
queue. However, PutItem() assumes that the queue is not full when it is called,
and GetItem() assumes that the queue is not empty. This condition is not
3.5.1 Ring Buffer Constructor and Destructor checked, because the check as such is different for queues that use semaphores
and queues that do not use semaphores. Apart from that, interrupts are in general
The constructor initializes the put and get indices to 0, the count of items in the to be disabled when these functions are called. To avoid direct usage of these
buffer to 0, and stores the size of the buffer. Then the constructor allocates a functions, they are made protected so that only classes derived from RingBuffer
buffer big enough to store size instances of class Type. can use them.
1 // Queue.cc 33 template <class Type> inline void RingBuffer<Type>::GetItem(Type & dest)
... 34 {
9 template <class Type> RingBuffer<Type>::RingBuffer(unsigned int Size) 35 dest = data[get++];
10 : size(Size), get(0), put(0), count(0) 36 if (get >= size) get = 0;
11 37 count--;
12 { 38 }
13 data = new Type[size]; ...
14 :

The destructor releases the memory allocated for the buffer.


1 // Queue.cc
3. Kernel Implementation 53 54 3.6 Interprocess Communication

40 template <class Type> inline void RingBuffer<Type>::PutItem(const Type &src) 3.6 Interprocess Communication
41 {
42 data[put++] = src;
43 if (put >= size) put = 0; So far, we have considered different tasks as being independent of each other.
44 count++;
45 }
Most often, however, some of the tasks in an embedded system have to exchange
information. The simplest way for the tasks to enable this exchange is to share
Finally, it has shown to be useful to provide polled access to both ends of a queue, memory. One task updates a variable in the memory while another task reads that
even if semaphores are used. For this purpose, the member functions PolledGet() variable. Although shared memory is considered as the fastest way of exchanging
and PolledPut() are used. Their implementation depends on where semaphores information, this is only true for the information exchange as such. In addition to
are used; thus they are purely virtual. exchanging the information, the tasks have to coordinate when the information is
valid (i.e. when it is provided by the sending task) and how long it is processed by
the receiving task. This coordination could be implemented as a valid flag, which
3.5.3 Queue Put and Get Functions is initially set to invalid. After a task has provided information, it sets the flag to
valid. The receiving task then processes the information and sets the flag back to
The polled and semaphore-controlled Put() and Get() for the four possible types invalid, so that the memory can be used again. Obviously, this procedure means
of queues result in a total of 12 functions. Rather than explaining them all in busy wait for both tasks involved and is thus inefficient.
detail, we only present the basic principles:
A much better way is to use queues containing messages for exchanging
• Interrupts are disabled while the ring buffer is accessed. information. To avoid busy waiting at either end, both put and get semaphores are
• For polled operation, if a semaphore is used at the polled end of the used. If the queue is full, the sending task is blocked until the receiving task has
queue, the semaphore is polled as well in order to keep the semaphore removed items. For small information quantities, such as characters or integers,
synchronized with the item count. the information can be stored in the message itself; for larger quantities, pointers
• It is always checked if the queue is full before PutItem is called, and if to the information are used. This way, the performance of shared memory for the
the queue is empty before GetItem is called. This check is explicit if no information exchange as such can be maintained. Using pointers is tricky in
semaphore is used at the respective ends, or implicit by polling the detail, since it needs to be defined whether the receiver or the sender must release
semaphore. the memory. For example, the receiver must release the memory if the memory is
allocated with the new operator. The sender has to release the memory, e.g. if the
memory is allocated on the senders stack; in this case, the sender needs to know
3.5.4 Queue Put and Get Without Disabling Interrupts when the receiver has finished processing of the message. If the memory is
released by the sender, then the receiver typically sends an acknowledgment back
In the implementation shown, the manipulation of the queue is always performed to the sender to indicate that the memory is no longer needed. As a consequence,
with interrupts enabled. Considering the short code, this causes a significant the receiver needs to know which task has sent the message.
overhead. Often interrupts are already disabled anyway, e.g. in interrupt service
routines. In those cases, one can derive other queue classes from RingBuffer that Rather than defining a specific queue for each particular purpose, it is convenient
do not disable interrupts. to have the same data structure for messages in the whole system, as defined in
Message.hh (see also Appendix A.9).
It should also be noted that the get and put ends of the queue are more or less 1 // Message.hh
...
independent of each other. As we have seen in PutItem() and GetItem(), the 5 class Message
count is always modified after putting or getting an item. If incrementing or 6 {
decreasing count is atomic (which is the case for most compilers), and if there is 7 public:
8 Message() : Type(0), Body(0), Sender(0) {};
only one task or interrupt service routine at all (which is the case for most 9 Message(int t, void * b) : Type(t), Body(b), Sender(0) {};
queues), then it is not necessary at all to disable interrupts. It may as well be the 10 int Type;
11 void * Body;
case that interrupts need to be disabled only at one end of a queue, e.g. for one 12 const Task * Sender;
task that receives messages from several other tasks. A good candidate for such 13 };
optimizations are the character input and output queues for serial I/O.
3. Kernel Implementation 55 56 3.6 Interprocess Communication

This data structure contains a type that indicates the kind of message, a body that send pointers to the .TEXT section of the program to the receiver (unless this is
is optionally used for pointers to larger data structures, and a task pointer not prevented by hardware memory management):
identifying the sender of the task. void sayHello(Task * Receiver)
{
Communication between tasks being so common, every task is provided with a Message msg(0, "Hello");
message queue: Receiver->SendMessage(msg);
}
// Task.hh
25 class Task
26 { This ??? structure/function/code ??? is valid since “Hello” has infinite
... lifetime. It is illegal, however, to send dangling pointers to the receiver; as it is
138 Queue_Gsem_Psem<Message> msgQ;
139 };
illegal to use dangling pointers in general:
void DONT_DO_THIS(Task * Receiver)
The size of the message queue can be specified individually for each task in order {
to meet the task’s communication requirements. char hello[6] = "Hello";
Message msg(0, hello);
1 // Task.cc Receiver->SendMessage(msg); // DON’T DO THIS !!!
... }
33 Task::Task(void (*main)(),
...
35 unsigned short qsz, After the above function has returned, the pointer sent to the receiver points to the
... stack of the sender which is not well defined when the receiver gets the message.
38 )
39 : US_size(usz),
... The receiving task may call GetMessage() in order to get the next message it has
44 msgQ(qsz), been sent. This function is even shorter, so it is declared inline as well:
// Task.hh
As we know by now, every task executing code must be the current task. Thus a 56 static void GetMessage(Message & msg)
message sent is always sent by CurrentTask. Since Message itself is a very small 57 { currTask->msgQ.Get(msg); };
data structure, we can copy the Type, Body and Sender members without loosing
much of the performance. This copy is made by the Put() function for queues. The receiver uses GetMessage() as follows:
The code for sending a message becomes so short that it makes sense to have it void waitForMessage()
inline. {
Message msg();
// Task.hh
96 void SendMessage(Message & msg)
Task::GetMessage(msg);
97 { msg.Sender = currTask; msgQ.Put(msg); }; switch(msg.Type)
{
...
Note that SendMessage() is a non-static member function of class task. That is, }
the instance of the class for which SendMessage() is called is the receiver of the }
message, not the sender. In the simplest case, only a message type is sent, e.g. to
indicate that an event has occurred: This usage pattern of the Message class explains its two constructors: the
void informReceiver(Task * Receiver, int Event)
constructor with Type and Body arguments is used by the sender, while the
{ receiver uses the default constructor without any arguments that is updated by
Message msg(Event, 0); GetMessage() later on. A scenario where the sender allocates memory which is
Receiver->SendMessage(msg); released by the receiver could be as follows: the sender sends integers 0, 1 and 2
}
to the receiver. The memory is allocated by new, rather than ??? pointing ???
The sender may return from informReceiver() before the receiver has received on the stack like in the bad example above.
the message, since the message is copied into the message queue. It is also safe to void sendData(Task * Receiver)
{
3. Kernel Implementation 57 58 3.6 Interprocess Communication

int * data = new int[3]; The sender is blocked as soon as it has sent the message, since the semaphore was
data[0] = 0; data[1] = 1; data[2] = 2;
initialized with its counter set to 0, indicating that the resource (i.e. the data) is not
Message msg(0, data); available. The receiver processes the message and unlocks it, which causes the
Receiver->SendMessage(msg); sender to proceed:
}
void receiveDataAndUnlock()
{
The receiver would then release the memory after having received the message: Message msg();
Task::GetMessage(msg);
void receiveData()
...
{
((DataSemaphore *)msg.Body).V();
Message msg();
}
Task::GetMessage(msg);
...
delete [] (int *)(msg.Body); Unfortunately, blocking the sender is a disadvantage of this otherwise perfect
} method. The sender may, however, proceed its operation as long as it does not
return from the function. This is also one of the very few examples where a
If a system uses hardware memory management (which is rarely the case for semaphore is not static. It does work here because both sender and receiver
embedded systems today, but may be used more frequently in the future), the data cooperate in the right way. Although we have not shown any perfect solution for
transmitted must of course be accessible by both tasks. any situation of interprocess communication, we have at least seen a set of
different options with different characteristics. Chances are good that one of them
The last scenario using new/delete is safe and provides sufficient flexibility for will suit the particular requirements of your application.
large data structures. Unfortunately, using new/delete is a bad idea for embedded
systems in general. While resetting a PC twice a day is not uncommon, resets
cannot be accepted for a robot on the mars. The safest but least flexible way of
allocating memory is by means of static variables. Automatic allocation on the
stack is a bit more risky, because the stack might overflow; but this solution is
much more flexible. The ultimate flexibility is provided by new/delete, but it is
rather difficult to determine the memory requirements beforehand, which is partly
due to the fragmentation of the memory. The problem in the bad example above
was the lifetime of the variable hello, which was controlled by the sender. This
problem can be fixed by using a semaphore that is unlocked by the receiver after
having processed the message:
class DataSemaphore
{
public:
DataSemaphore() : sem(0) {}; // resource not available
int data[3];
Semaphore sem;
}

void sendMessageAndWait(Task * Receiver)


{
DataSemaphore ds;
Message msg(0, ds);
ds.data[0] = 0; ds.data[1] = 1; ds.data[2] = 2;
Receiver->SendMessage(msg);
ds.sem.P();
}
3. Kernel Implementation 59 60 3.7 Serial Input and Output

3.7 Serial Input and Output For each serial port, there is a receive task (RX T0..2) that receives characters
from its serial port. If a complete packet is received, the receive task fetches a
The basic model for serial input and output has already been discussed in Section pointer to an idle packet handler task and sends a message containing the packet
2.5.3 and presented in Figure 2.14. In principle, the input and output directions to that task. The packet handler task processes the packet and may create other
are completely independent of each other, except for the software flow control packets that are sent as messages to some of the transmit tasks (Tx T0..2). When
(e.g. XON/XOFF protocol) at the hardware side of the receive buffer, and a packet handler has finished processing a packet, it puts itself back into the queue
possibly line editing functions (e.g. echoing of characters) at the task side of the of idle packet handlers. The transmit tasks merely send the packets out on their
receive buffer. respective serial outputs. In this implementation, each serial input is handled by
one task Rx Ti, and each serial output is handled by a task Tx Ti dedicated to that
This section deals with the task side of both the receive and transmit buffers; the port. The main purpose of these tasks is to maintain atomicity at packet level.
hardware side is discussed in Section 3.8. Strictly speaking, the aspects of serial That is, these tasks are responsible for assembling and de-assembling sequences
input and output discussed here are not part of the operating system itself. But of characters into packets and vice versa. Since the receive and transmit tasks are
they are so commonly used that it is appropriate to include them in the kernel. statically bound to their serial ports, there is no conflict between tasks with regard
to ports.
Several tasks sharing one serial input or output channel is a common source of
trouble. A typical example is a router that receives data packets on several serial Now assume there is some mechanism by which a task can temporarily claim a
ports and transmits them (after possibly modifying them) on other serial ports. serial input and output port for a period of time so that no other task can use that
??? What is the trouble ???An implementation with three serial ports could port at the same time. Then the number of tasks involved could be reduced as
be as shown in Figure 3.5. shown in Figure 3.6.

Packet Packet
Rx Buf 0 Rx T0 Tx T0 Tx Buf 0 Rx Buf 0 Handler Tx Buf 0
Handler

Packet
Rx Buf 1 Rx T1 Tx T1 Tx Buf 1 Rx Buf 1 Handler Tx Buf 1

Packet Packet Packet


Rx Buf 2 Rx T2 Tx T2 Tx Buf 2 Rx Buf 2 Handler Handler Tx Buf 2
Handler

Queue of idle Packet Handlers Queue of unserved input ports

Packet Packet Packet Packet Packet Packet Packet Packet


Handler Handler Handler Handler Handler Handler Handler Handler

FIGURE 3.5 Serial Router (Version A) FIGURE 3.6 Serial Router (Version B)
3. Kernel Implementation 61 62 3.7 Serial Input and Output

At the output side, a packet handler merely claims a serial output port when it char * Packet = getPacket(port);
needs to transmit a packet. The queue of idle packet handlers has been replaced Port_i_Input.V();
handlePacket(Packet); // deletes Packet
by a queue of input ports that have no packet handlers assigned; this queue
}
initially contains all serial input ports. A packet handler first gets an unserved }
input port, so that shortly after start-up of the system each input port is served by
a packet handler; the other packet handlers are blocked at the queue for unserved The semaphores control the claiming and releasing of the serial input and output
inputs. A packet handler serving an input first claims that input port and starts ports. Using semaphores explicitly is not very elegant though. First, it must be
collecting the characters of the next packet. When a complete packet is received, assured that any task using a serial port is claiming and releasing the
the packet handler releases the input port (which causes the next idle packet corresponding semaphore. Also it is often desirable to have a “dummy” port (such
server to take over that port), puts it back into the queue of unserved input ports, as /dev/nul in UNIX) that behaves like a real serial port. Such a dummy port could
and continues processing of the packet. Like in router version A, this scheme be used e.g. to turn logging information on and off. But claiming and releasing
schedules the packet handlers between the ports in a fair way. Sometimes, in dummy ports makes little sense. In general, the actual implementation of a port
particular if the serial ports need to have different priorities (e.g. due to different should be hidden from the interface using the port. Thus for a clean object-
communication speeds), a scheduling on a per-port basis is required. This leads to oriented design, the semaphores should be maintained by the port rather than by
an even simpler implementation shown in Figure 3.7. an application using the port. This leads to the kernel implementation of serial
input and output described in the following sections.

Packet 3.7.1 Channel Numbers


Rx Buf 0 Handler Tx Buf 0
It is convenient to refer to serial ports by channel numbers. In our hardware
Packet model, we assumed one DUART with two serial ports, which we call SERIAL_0
Rx Buf 1 Tx Buf 1
Handler and SERIAL_1. These are normally operated in an interrupt-driven manner.
Sometimes however, it is required to have a polled operation available, in
Packet Packet particular before the interrupt system has been initialized, and in the case of fatal
Rx Buf 2 Handler Handler Tx Buf 2
system errors. For achieving this polled operation, the channels
SERIAL_0_POLLED and SERIAL_1_POLLED are provided. Finally, the
DUMMY_SERIAL channel is used when the actual serial output needs to be
FIGURE 3.7 Serial Router (Version C) suppressed.
1 // Channels.hh
...
With this implementation, one can e.g. assign different priorities to each input 5 enum Channel {
port and use different numbers of packet servers. The packet servers queue 6 SERIAL_0 = 0,
themselves by claiming the input port, so that the queue of unserved input ports 7 SERIAL_1 = 1,
8 SERIAL_0_POLLED = 4,
used in version B becomes obsolete. As a consequence, no initialization of that 9 SERIAL_1_POLLED = 5,
queue is required. The code for the packet handler becomes as simple as that: 10 DUMMY_SERIAL = 8,
11 };
Semaphore Port_0_Input, Port_0_Output;
Semaphore Port_1_Input, Port_1_Output;
Semaphore Port_2_Input, Port_2_Output; Often, one would like to turn the serial output on and off, e.g. for debugging
purposes. Therefore, channel variables rather than explicit channels are used:
void packet_handler_main(Semaphore & Port_i_Input)
1 // Channels.hh
{
...
for (;;) 13 extern Channel MonitorIn;
{ 14 extern Channel MonitorOut;
Port_i_Input.P(); 15 extern Channel ErrorOut;
3. Kernel Implementation 63 64 3.7 Serial Input and Output

16 extern Channel GeneralOut; 40 return;


41 }
42 }
If the variable ErrorOut is used for e.g. debugging information, then this output
can be suppressed or directed to any serial port by setting the ErrorOut variable
Basically, the constructor performs a P() operation on the Channel_0/1
to DUMMY_SERIAL or SERIAL_0/1. This can be done in a dynamic way and
semaphore associated with the channel. If another task tries to create a SerialOut
can be extended to several debugging levels by introducing new Channel
object, then that task is blocked until the task that created the SerialOut object
variables in accordance with the various debugging levels.
first has destroyed it again. The SerialOut object also stores for which channel it
has been constructed, so that subsequent changes e.g. of a channel variable do not
3.7.2 SerialIn and SerialOut Classes and Constructors/Destructors affect a SerialOut object. Note that the P() operation is only performed for those
channels that are subject to access conflicts. If multitasking is not yet in effect (i.e.
Since the serial input and output are mainly independent of each other, they are during system start-up), the construction is creating a polled serial port. Thus the
implemented as separate classes. The constructors and destructors are so similar, code creating a SERIAL_0/1 object will work even at system start-up.
however, that they are described together.
The semaphores must be static and private to prevent abuse of the semaphores:
As we already saw, a mechanism allowing a task to exclusively claim a serial 1 /* SerialOut.hh */
(input or output) port for a certain period of time is required. Clearly, this ...
12 class SerialOut
mechanism will be based on a semaphore. A particularly elegant implementation 13 {
of this mechanism is to create an object with a lifetime that is exactly the period ...
during which the port is being claimed. The lifetime of an object is the time 23 private:
...
between the construction and the destruction of the object. Thus if we perform the 36 static Semaphore Channel_0;
semaphore P() operation inside the constructor and the V() operation inside the 37 static Semaphore Channel_1;
destructor, ??? was dann ???. For the SerialOut class, we get the following ...
44 };
constructor:
1 /* SerialOut.cc */ The destructor performs the V() operation only for those ports for which the
...
16 Semaphore SerialOut::Channel_0;
constructor has performed a P() operation. Thus if a SERIAL_0/1 object is
17 Semaphore SerialOut::Channel_1; created before multitasking has started, then channel is mapped to a polled port
... in the constructor, and the destructor will not perform a V() operation on the
20 SerialOut::SerialOut(Channel ch) : channel(ch) semaphore later on.
21 {
22 switch(channel) 1 /* SerialOut.cc */
23 { ...
24 case SERIAL_0: 44 SerialOut::~SerialOut()
25 if (Task::SchedulerRunning()) Channel_0.P(); 45 {
26 else channel = SERIAL_0_POLLED; 46 switch(channel)
27 return; 47 {
28 48 case SERIAL_0: Channel_0.V(); return;
29 case SERIAL_1: 49 case SERIAL_1: Channel_1.V(); return;
30 if (Task::SchedulerRunning()) Channel_1.P(); 50 }
31 else channel = SERIAL_1_POLLED; 51 }
32 return;
33
34 case SERIAL_0_POLLED:
The constructor and destructor for the SerialIn class are conceptionally identical
35 case SERIAL_1_POLLED: to those of the SerialOut class, so that we do not repeat them here. The only
36 return; difference is a simplification in the SerialIn constructor: it does not check
37 whether multitasking is already running, because during system start-up, there is
38 default:
39 channel = DUMMY_SERIAL; // dummy channel typically no serial input, while serial output for debugging purposes is quite
3. Kernel Implementation 65 66 3.7 Serial Input and Output

common. It would do no harm, however, to make the SerialIn constructor If a channel is polled, then the polled Putc() function makes sure that the
identical to that of SerialOut. initialization of the hardware has reached a sufficient level (Polled_IO, i.e. the
DUART has been initialized, but interrupts are not yet enabled), and then it polls
the DUART’s status register until it is able to accept a new character.
3.7.3 Public SerialOut Member Functions
1 /* SerialOut.cc */
...
The simplest public member function of the SerialOut class is Putc(int 77 void SerialOut::Putc_0_polled(int c)
character). The purpose of Putc() is to transmit its argument character on the 78 {
79 if (os::initLevel() < os::Polled_IO) os::init(os::Polled_IO);
channel. Since the way how this transmission has to be done is different for the 80
channels (interrupt driven for SERIAL_0/1, polled for SERIAL_0/1_POLLED, 81 while (!(os::readDuartRegister(rDUART_SR_A) & SR_TxRDY)) /**/ ;
or just discarding the character for DUMMY_SERIAL), Putc() simply decodes 82
83 os::writeRegister(wDUART_THR_A, c);
the channel and then calls the appropriate function that actually transmits the 84
character. 85 while (!(os::readDuartRegister(rDUART_SR_A) & SR_TxRDY)) /**/ ;
86 }
1 /* SerialOut.cc */
...
104 void SerialOut::Putc(int c) In the case of the DUMMY_SERIAL channel, the corresponding Putc()
105 { function does not do anything.
106 switch(channel)
107 { 1 /* SerialOut.cc */
108 case SERIAL_0: Putc_0(c); return; ...
109 case SERIAL_1: Putc_1(c); return; 99 void SerialOut::Putc_dummy(int)
110 case SERIAL_0_POLLED: Putc_0_polled(c); return; 100 {
111 case SERIAL_1_POLLED: Putc_1_polled(c); return; 101 // dummy Putc to compute length
112 case DUMMY_SERIAL: return; 102 }
113 default: return;
114 }
115 }
Although Putc_dummy() is not called in Putc(), it will be required later on,
where any of the above specific Putc_() functions will be passed as an argument
Thus Putc() provides a unified interface towards the different channels. to a print function discussed below.

If a channel is interrupt driven (as for SERIAL_0/1), then the character is put into Note that in the case of interrupt-driven serial output, the Putc() function may
the corresponding output buffer. As we will see in Section 3.8, transmit interrupts return long before the character has been transmitted by the DUART, since the
need to be disabled if the output queue becomes empty. If this situation is Putc() only places the character into the output buffer. Sometimes we also want to
indicated by the TxEnabled_0/1 variable, then the interrupts must be turned on know if the character has indeed been transmitted. For this purpose, the
again by writing a certain command into the DUART. IsEmpty() function returns true if the output buffer of a channel is empty.
1 /* SerialOut.cc */ Based on the Putc() function, we can implement more sophisticated functions for
...
53 void SerialOut::Putc_0(int c) formatted output similar to the fprintf() in C libraries. There are both a static
54 { Print() function taking a channel as an argument and a non-static Print()
55 unsigned char cc = c; function.
56
57 outbuf_0.Put(cc); 1 /* SerialOut.hh */
58 if (!TxEnabled_0) ...
59 { 12 class SerialOut
60 TxEnabled_0 = 1; 13 {
61 os::writeRegister(wDUART_CR_A, CR_TxENA); // enable Tx ...
62 } 18 static int Print(Channel, const char *, ...);
63 } ...
21 int Print(const char *, ...);
3. Kernel Implementation 67 68 3.7 Serial Input and Output

... So, why are two different Printf() functions needed? The reason is that
44 };
sometimes not all information to be printed together is easily available
beforehand. Consider two tasks running the same code and using the same
The static Print() function creates a SerialOut object for the channel and then
channel:
proceeds exactly like the non-static Print() function.
void task_main(Channel ch)
1 /* SerialOut.cc */
{
...
for (;;)
132 int SerialOut::Print(Channel channel, const char * format, ...)
133 { {
134 SerialOut so(channel); Message msg;
... char * p = (char *)(msg.Body);
Task::GetMessage(msg);
for (unsigned int i = 0; msg.Body[i]; i++)
The SerialOut object is automatic in the static Print() function so that it is
SerialOut::Print(ch,"%c ",p[i]);
automatically destructed when Print() returns. This way it is ensured that }
anything being printed is not interrupted by other tasks calling a Print() function }
for the same channel.
In this example, each message character with its trailing blank from any task is
The non-static Print() function selects the proper Putc_() function for its channel printed as a whole, since the lifetime of the SerialOut objects created
and either calls this Putc_() function (for those characters of the format string that automatically by the static Print() function is basically the time it takes for the
are to be copied to the output), or calls print_form() for format characters. The print function to execute. If one task receives “AAA” and the other tasks receives
implementation of print_form() is straightforward, but somewhat lengthy, so that “BBB” as the body of a message at the same time, then the lines of both tasks
we skip it here and refer to Appendix A.12. Any of the Print() functions return may be intermixed, producing e.g. the following output:
the number of characters printed on the channel.
1 /* SerialOut.cc */ AABBBA
...
159 int SerialOut::Print(const char * format, ...) In contrast, the output
160 {
161 void (*putc)(int);
162 const unsigned char ** ap = (const unsigned char **)&format; A AB B B A
163 const unsigned char * f = *ap++;
164 int len = 0; would never be produced, since the trailing blank is always “bound” to its
165 int cc;
166 preceding character by the single invocation of the static Print() function. If we
167 switch(channel) want to print a whole message, i.e. produce e.g. A A A B B B instead of A A B B
168 { B A, then we have to extend the lifetime of the SerialOut object. This is where
169 case SERIAL_0: putc = Putc_0; break;
170 case SERIAL_1: putc = Putc_1; break;
the non-static Print() function is used, like in the following code:
171 case SERIAL_0_POLLED: putc = Putc_0_polled; break; void task_main(Channel ch)
172 case SERIAL_1_POLLED: putc = Putc_1_polled; break; {
173 case DUMMY_SERIAL: putc = Putc_dummy; break;
for (;;)
174 default: return 0;
{
175 }
176 Message msg;
177 while (cc = *f++) char * p = (char *)(msg.Body);
178 if (cc != '%') { putc(cc); len++; } Task::GetMessage(msg);
179 else len += print_form(putc, ap, f);
180
181 return len;
182 }
3. Kernel Implementation 69 70 3.7 Serial Input and Output

{ 56 }
SerialOut so(ch);
for (unsigned int i = 0; msg.Body[i]; i++) Often one wants to receive characters up to, but not including a terminating
so.Print(ch,"%c ",p[i]); character; e.g. if decimal numbers of unknown length are entered. UNIX has a
}
}
unputc() function which undoes the last putc(). We have not adopted this
} scheme, but instead provide a function Peekc() which works like Pollc(), but does
not remove the character from the receive queue. Both the unputc() approach and
Now there is only one SerialOut object instead of one for each message character the Peekc() approach have their advantages and disadvantages, and one can easily
which causes an entire message to be printed. Thus the static Print() is typically implement unputc() in the SerialIn class.
used when the item to be printed can be expressed by a single format string, while 1 /* SerialIn.cc */
the non-static Print() is used otherwise. ...
58 int SerialIn::Peekc()
59 {
60 unsigned char cc;
3.7.4 Public SerialIn Member Functions 61
62 switch(channel)
63 {
The simplest public member function of the SerialIn class is Getc() which 64 case SERIAL_0: return inbuf_0.Peek(cc) ? -1 : cc;
returns the next character received on a channel. If no characters are available, 65 case SERIAL_1: return inbuf_1.Peek(cc) ? -1 : cc;
then the task calling Getc() is blocked until the next character is received. In 66 default: return -1;
contrast to the SerialOut class, Getc() returns useful results only for interrupt 67 }
68 }
driven I/O and indicates EOF (-1) otherwise. Getc() returns int rather than char
in order to distinguish the EOF condition from the regular char 0xFF (i.e. -1).
GetDec() and GetHex() are based on the Pollc() and Peekc() functions and
1 /* SerialIn.cc */ collect decimal (’0’..’9’) or hexadecimal (’0’..’9’,’A’..’F’ and ’a’..’f’) sequences
...
34 int SerialIn::Getc()
of characters, and return the resulting integer value. These functions do not
35 { necessarily belong to an operating system, but are provided since they are
36 unsigned char cc; commonly required.
37
38 switch(channel)
39 {
For serial output, characters can never get lost, since tasks performing output
40 case SERIAL_0: inbuf_0.Get(cc); return cc; would block before the transmit buffer overflows. For serial input however, the
41 case SERIAL_1: inbuf_1.Get(cc); return cc; receive buffer may overflow, e.g. if no task is performing Getc() for some time.
42 default: return -1; The function getOverflowCounter() returns the number of characters lost due to
43 }
44 } buffer overflow, and 0 for polled or dummy serial input where this condition can
not be easily detected.
If it is not desired to block the task, Pollc() can be used instead. Pollc() returns
EOF when Putc() would block the task.
1 /* SerialIn.cc */
...
46 int SerialIn::Pollc()
47 {
48 unsigned char cc;
49
50 switch(channel)
51 {
52 case SERIAL_0: return inbuf_0.PolledGet(cc) ? -1 : cc;
53 case SERIAL_1: return inbuf_1.PolledGet(cc) ? -1 : cc;
54 default: return -1;
55 }
3. Kernel Implementation 71 72 3.8 Interrupt Processing

3.8 Interrupt Processing 138 if (iLevel == Interrupt_IO && init_level < Interrupt_IO)
139 {
140 readDuartRegister (rDUART_STOP); // stop timer
As shown in Section 3.2.4, the only device generating interrupts is the DUART 141 writeRegister(xDUART_CTUR, CTUR_DEFAULT); // set CTUR
142 writeRegister(xDUART_CTLR, CTLR_DEFAULT); // set CTLR
using interrupt level 2, which corresponds to autovector #2 in the CPU’s vector 143 readDuartRegister(rDUART_START); // start timer
table. After reset, interrupts from the DUART are disabled in the DUART, and in 144
addition, the CPU’s interrupt mask is set to level 7, thus preventing interrupts 145 writeRegister(wDUART_IMR, INT_DEFAULT);
146 init_level = Interrupt_IO;
from the DUART. Before discussing the interrupt processing, we shall have a look 147 }
at the hardware initialization. 148 }

Initialization to level Polled_IO basically sets the baud rate and data format for
3.8.1 Hardware Initialization both DUART channels to 9600 Baud, 8 data bits, two stop bits, and enables the
receivers and transmitters of both serial channels. Thus after reaching this
Hardware initialization is performed in two steps, which are controlled by the initialization level, the DUART can be operated in a polled mode.
variable os::init_level and by the function os::init() which performs initialization
up to a requested level. Initialization to level Interrupt_IO programs the DUART timer to generate
interrupts every 10ms. This is the rate at which task scheduling is performed.
1 /* os.hh */
... Then interrupts from all internal interrupt sources of the DUART that are used are
18 class os enabled: the timer interrupt as well as receive and transmit interrupts for all
19 {
...
channels. These interrupts are never turned off afterwards. If a transmit buffer
30 enum INIT_LEVEL { gets empty, then the corresponding transmit interrupt is disabled by disabling the
31 Not_Initialized = 0, transmitter rather than masking its interrupt (otherwise, one would need to
32 Polled_IO = 1,
33 Interrupt_IO = 2 maintain a copy of the interrupt mask register, which would be less elegant).
34 };
35
36 static void init(INIT_LEVEL new_level);
At this point, the interrupts are enabled in the DUART, but the CPU’s interrupt
... mask is still at level 7, so that interrupts have no effect yet.
49 static INIT_LEVEL init_level;
... 1 // Task.cc
88 }; 78 void main()
79 {
80 if (Task::SchedulerStarted) return -1;
After RESET, the init_level is Not_initialized. The Polled_IO level refers to a 81
hardware state, where the DUART is initialized, but interrupts are masked. The 82 for (int i = 0; i < TASKID_COUNT; i++) Task::TaskIDs[i] = 0;
83 setupApplicationTasks();
final level is Interrupt_IO, where interrupts are also enabled. If an initialization 84
to Interrupt_IO is requested, then the initialization for level Polled_IO is 85 for (Task * t = Task::currTask->next; t != Task::currTask; t = t->next)
86 t->TaskStatus &= ~Task::STARTED;
automatically performed by the os:init() function. During normal system start-up, 87
the Polled_IO level is never requested; instead, the initialization jumps directly 88 Task::SchedulerStarted = 1;
from Not_initialized to Interrupt_IO. This happens at a rather late stage in the 89 os::init(os::Interrupt_IO); // switch on interrupt system
90 os::set_INT_MASK(os::ALL_INTS);
start-up of the system. If debugging printouts are inserted during system start-up, 91
then the Putc_0/1_polled() functions request initialization to level Polled_IO. 92 Task::Dsched();
93
128 void os::init(INIT_LEVEL iLevel) 94 for (;;) os::Stop();
129 { 95
130 enum { green = 1<<7 }; // green LED, write to BCLR turns LED on 96 return 0; /* not reached */
131 97 }
132 if (init_level < Polled_IO)
133 {
134 initDuart(DUART, CSR_9600, CSR_9600); The initialization to level Interrupt_IO is done in function main(). This function
135 init_level = Polled_IO; first sets up all tasks that are supposed to run after systems start-up, initializes the
136 }
137
hardware to level Interrupt_IO, and finally lowers the CPU’s interrupt mask so
3. Kernel Implementation 73 74 3.8 Interrupt Processing

that all interrupts are accepted. The main() function is actually executed by the ...

idle task, which deschedules itself and then enters an infinite loop. Since the idle
task has the lowest priority of all tasks, it only executes if all other tasks are The same applies for an interrupt from the receiver for SERIAL_1.
blocked. It thus stops the CPU until the next interrupt occurs. ...
150 BTST #5, _duart_isreg | RxRDY_B ?
151 BEQ LnoRxB | no
152 MOVEM.L rDUART_RHR_B, D0 | get char received
3.8.2 Interrupt Service Routine 153 MOVE.L D0, -(SP) |
154 PEA 1(SP) | address of char received
155 PEA __8SerialIn$inbuf_1 | inbuf_1 object
As we already saw, the only interrupt that could occur in our system is an 156 JSR _PolledPut__t10Queue_Gsem1ZUcRCUc
autolevel 2 interrupt. Of course, the system can be easily extended to support 157 LEA 12(SP), SP | cleanup stack
158 LnoRxB: |
more peripherals. Thus if an interrupt occurs, the CPU fetches the corresponding 159 |
interrupt vector and proceeds at the address _duart_isr, where the interrupt ...
service routine for the DUART starts. The CPU is in supervisor mode at this
point. If the interrupt is caused by the transmitter for SERIAL_0, then the next
character from the transmit queue for SERIAL_0 is fetched. The transmit queue
1 | crt0.S
... may be empty, however; in this case, the transmitter is disabled to clear the
52 .LONG _duart_isr | 26 level 2 autovector interrupt. This is also indicated towards the Putc_0() function by the
...
SerialOut::TxEnabled_0 variable (see also Section 3.7.3). If the queue is not
empty, then the next character is written to the DUART which clears this
The CPU first turns on a LED. This LED is turned off each time the CPU is
interrupt.
stopped. The brightness of the LED thus shows the actual CPU load, which is
very useful sometimes. The CPU then saves its registers onto the system stack ...
160 BTST #0, _duart_isreg | TxRDY_A ?
and reads the interrupt status from the DUART which indicates the source(s) of 161 BEQ LnoTxA | no
the interrupt. 162 LEA -2(SP), SP | space for next char
163 PEA 1(SP) | address of char received
... 164 PEA __9SerialOut$outbuf_0 | outbuf_0 object
133 _duart_isr: | 165 JSR _PolledGet__t10Queue_Psem1ZUcRUc
134 MOVE.B #LED_YELLOW, wLED_ON | yellow LED on 166 LEA 8(SP), SP | cleanup stack
135 MOVEM.L D0-D7/A0-A6, -(SP) | save all registers 167 MOVE.W (SP)+, D1 | next output char (valid if D0 = 0)
136 MOVEM.L rDUART_ISR, D7 | get interrupt sources 168 TST.L D0 | char valid ?
137 SWAP D7 | 169 BEQ Ld1i11 | yes
138 MOVE.B D7, _duart_isreg | 170 CLR.L __9SerialOut$TxEnabled_0| no, disable Tx
139 | 171 MOVE.B #0x08, wDUART_CR_A | disable transmitter
... 172 BRA LnoTxA |
173 Ld1i11: MOVE.B D1, wDUART_THR_A | write char (clears int)
174 LnoTxA: |
If the interrupt is caused by the receiver for SERIAL_0, then the received 175 |
character is read from the DUART and put into the receive queue of SERIAL_0. ...
This queue has a get semaphore, so that as a consequence, a task blocked on the
receive queue may be unblocked. Reading the received character from the The same is true for an interrupt from the transmitter for SERIAL_1.
DUART automatically clears this interrupt. ...
176 BTST #4, _duart_isreg | TxRDY_B ?
...
177 BEQ LnoTxB | no
140 BTST #1, _duart_isreg | RxRDY_A ?
178 LEA -2(SP), SP | space for next char
141 BEQ LnoRxA | no
179 PEA 1(SP) | address of char received
142 MOVEM.L rDUART_RHR_A, D0 | get char received
180 PEA __9SerialOut$outbuf_1 | outbuf_1 object
143 MOVE.L D0, -(SP) |
181 JSR _PolledGet__t10Queue_Psem1ZUcRUc
144 PEA 1(SP) | address of char received
182 LEA 8(SP), SP | cleanup stack
145 PEA __8SerialIn$inbuf_0 | inbuf_0 object
183 MOVE.W (SP)+, D1 | next output char (valid if D0 = 0)
146 JSR _PolledPut__t10Queue_Gsem1ZUcRCUc
184 TST.L D0 | char valid ?
147 LEA 12(SP), SP | cleanup stack
185 BEQ Ld1i21 | yes
148 LnoRxA: |
186 CLR.L __9SerialOut$TxEnabled_1| no, disable Tx
149 |
3. Kernel Implementation 75 76 3.8 Interrupt Processing

187 MOVE.B #0x08, wDUART_CR_B | disable transmitter The variable _consider_ts may or may not have been set during the interrupt
188 BRA LnoTxB |
189 Ld1i21: MOVE.B D1, wDUART_THR_B | write char (clears int) service routine. The final step is to proceed at label _return_from_exception.
190 LnoTxB: |
...
191 |
216 MOVEM.L (SP)+, D0-D7/A0-A6 | restore all registers
...
217 BRA _return_from_exception |

The last option is a timer interrupt. In this case, the interrupt is cleared by writing The processing at label _return_from_exception has already been described in
to the DUART’s stop/start registers. Next, a pair of variables indicating the system Section 3.3, i.e. it will be checked whether a task switch is required. Note that for
time since power on in milliseconds is updated. This implements a simple system the code starting at _return_from_exception it makes no difference whether a
clock: task switch was caused by an interrupt or not.
...
192 BTST #3, _duart_isreg | timer ?
193 BEQ LnoTim | no
194 MOVEM.L rDUART_STOP, D1 | stop timer
195 MOVEM.L rDUART_START, D1 | start timer
196 |
197 | increment system time
198 ADD.L #10, _sysTimeLo | 10 milliseconds
199 BCC.S Lsys_time_ok |
200 ADDQ.L #1, _sysTimeHi |
201 Lsys_time_ok: |
202 |
...

A common problem is to poll a peripheral (e.g. a switch) in regular intervals or to


wait for certain period of time. Neither blocking a task or busy wait is appropriate
for this purpose. Instead, we implement a function Task::Sleep() which will be
explained later on. This Sleep() function uses a variable TaskSleepCount for
each task which is decremented with every timer interrupt. If the variable reaches
0, the task return to state RUN by clearing a particular bit in the task’s status
register.
...
203 MOVE.L __4Task$currTask, D1 |
204 MOVE.L D1, A0 |
205 L_SLEEP_LP: | decrement sleep counters...
206 SUBQ.L #1, TaskSleepCount(A0) |
207 BNE L_NO_WAKEUP |
208 BCLR #3, TaskStatus(A0) | clear sleep state
209 L_NO_WAKEUP: |
210 MOVE.L TaskNext(A0), A0 |
211 CMP.L A0, D1 |
212 BNE L_SLEEP_LP |
213 ST _consider_ts | request task switch anyway
214 LnoTim: |
215 |
...

Now all interrupt sources causing the present interrupt are cleared. During this
process, new interrupts may have occurred. In that case, the interrupt service
routine will be entered again when returning from exception processing. The
interrupt processing is finished by restoring the interrupts saved at the beginning.
3. Kernel Implementation 77 78 3.9 Memory Management

3.9 Memory Management 31 }


32
33 return ret;
As we will see in Section 6.4, a library libgcc2 has to be provided in order to link 34 }
the kernel. This library contains in particular the code for the global C++
operators new and delete. The code in libgcc2 basically calls two functions, The function sbrk(unsigned long size) increases the free_RAM pointer by size
malloc() (for operator new) and free() (for operator delete). and returns its previous value. That is, a memory block of size size is allocated
and returned by sbrk().
One way to provide these functions is to compile the GNU malloc package and to 36 extern "C" void * malloc(unsigned long size)
link it to the kernel. But this method consumes considerable memory space. It 37 {
38 void * ret = sbrk((size+3) & 0xFFFFFFFC);
should also be noted that the malloc package contains uninitialized variables and 39
would thus result in a non-empty BSS section. Since we do not use the BSS 40 if (ret == (void *)-1) return 0;
section, the source code of the malloc package needs to be modified by 41 return ret;
42 }
initializing all uninitialized variables to 0.
Our malloc() implementation rounds the memory request size up to a multiple of
As you may have noticed, we never used the new operator in the kernel code, four bytes so that the memory is aligned to a long word boundary.
except for creating new tasks and their associated stacks. The main reason for not
45 extern "C" void free(void *)
using this operator is that in an embedded system, there is most likely no way to 46 {
deal with the situation where new (i.e. malloc()) fails due to lack of memory. The 47 }
malloc package allocates memory in pages (e.g. 4kByte; the page size can be
adjusted) and groups memory requests of similar size (i.e. rounded up to the next Finally, our free() function does not free the memory returned. As a consequence,
power of 2) in the same page. Thus if there are requests for different sizes, a delete must not be used. As long as tasks are not created dynamically and new is
significant number of pages could be allocated. For conventional computers with not used elsewhere, this scheme is most efficient and adequate. Otherwise, one
several megabytes of memory this is a good strategy, since the waste of memory should use the standard malloc package or write an own version meeting specific
in partly used pages is comparatively small. For embedded systems, however, the requirements. A better solution than the global new operator is to overload the
total amount of memory is typically much smaller, so that the standard malloc() is new operator for specific classes. For example, memory for certain classes could
not the right choice. be allocated statically and the class specific new operator (which defaults to the
global new operator) could be overloaded. This gives more control over the
We actually used the standard malloc() in the early kernel versions, but replaced it memory allocation.
later on by the following.
1 /* os.cc */
Finally, it should be noted that embedded systems with hardware memory
... management need a memory management scheme that is written specifically for
17 extern int edata; the memory management unit used.
18 char * os::free_RAM = (char *)&edata;

The label edata is computed by the linker and indicates the end of the .DATA
section; i.e. past the last initialized variable. The char pointer free_RAM is thus
initialized and points to the first unused RAM location.
21 extern "C" void * sbrk(unsigned long size)
22 {
23 void * ret = os::free_RAM;
24
25 os::free_RAM += size;
26
27 if (os::free_RAM > (char *)RAMend) // out of memory
28 {
29 os::free_RAM -= size;
30 ret = (void *) -1;
3. Kernel Implementation 79 80 3.10 Miscellaneous Functions

3.10 Miscellaneous Functions 3.10.2Miscellaneous Functions in os.cc

So far, we have discussed most of the code comprising the kernel. What is getSystemTime() returns the time in millisecond since system start-up (more
missing is the code for starting up tasks (which is described in Section 4.3) and precisely since multitasking was enabled) as a long long. initChannel()
some functions that are conceptually of minor importance but nevertheless of initializes the data format (data bits, stop bits) of a DUART channel,
certain practical use. They are described in less detail in the following sections. setBaudRate() sets ??? What ???. Panic() disables all interrupts, turns on the
red LED and then continuously dumps an exception stack frame on SERIAL_0.
This function is used whenever an exception for which no handler exists is taken
3.10.1Miscellaneous Functions in Task.cc (label _fatal). That is, if a fatal system error occurs, the red LED is turned on, and
we can connect a terminal to SERIAL_0. The exception stack frame can then be
The Monitor class uses member functions that are not used otherwise. Current() analyzed, together with the map file created by the linker, to locate the fault in the
returns a pointer to the current task. Dsched() explicitly deschedules the current source code. readDuartRegister() is called to read a DUART register.
task. MyName() returns a string for the current task that is provided as an writeRegister() is used to write into a hardware (i.e. DUART) register.
argument when a task is started; Name() returns that string for any task.
MyPriority() returns the priority of the current task, Priority() returns the
priority for any task. userStackBase() returns the base address of the user stack;
userStackSize() returns the size of the user stack; and userStackUsed() returns
the size of the user stack that has already been used by a task. When a task is
created, its user stack is initialized to contain characters ’U’. userStackUsed()
scans the user stack from the bottom until it finds a character which differs from
’U’ and so computes the size of the used part of the stack. Status() returns the
task status bitmap.

Next() returns the next task in the ring of all existing tasks. If we need to perform
a certain function for all tasks, we could do it as follows:
for (const Task * t = Task::Current();;)
{
...
t = t->Next();
if (t == Task::Current()) break;
}

Sleep(unsigned int ticks) puts the current task into sleep mode for ticks timer
interrupts. That is, the task does not execute for a time of ticks*10ms without
wasting CPU time.

When a task is created, its state is set to STARTED; i.e. the task is not in state
RUN. This allows for setting up tasks before multitasking is actually enabled.
Start() resets the task state to RUN.

Terminate() sets a task’s state to TERMINATED. This way, the task is


prevented from execution without the task being deleted.

GetMessage(Message & dest) copies the next message sent to the current task
into dest and removes it from the task’s message queue (msgQ).
82 4.2 System Start-up

4 Bootstrap
The .TEXT section, in contrast, does not need any special handling. Figure 4.1
shows the output of the linker on the left. The ROM image for the system is
created by appending the .DATA section after the .TEXT section. The address of
the .DATA section in ROM can be computed from the end of the .TEXT section;
this address is provided by the linker (symbol _etext). Depending on the target
system for which the linker has been installed, _etext may need to be rounded up
(e.g. to the next 2Kbyte boundary) to determine the exact address of the .DATA
4.1 Introduction section in RAM. Although it is not strictly necessary, it is generally a good idea to
initialize the unused part of the RAM to 0. This allows to reproduce faults created
In this chapter, the start-up of the kernel is described. It contains two phases: the by uninitialized variables.
initialization of the system after RESET, and the initialization of the tasks defined
in the application. After RESET, the CPU loads its supervisor stack pointer with the vector at
address 0 and its program counter with the next vector. In our implementation, the
4.2 System Start-up vector for the supervisor stack pointer is somewhat abused, as it contains a branch
to the start of the system initialization. This allows for issuing a JMP 0 (in
The compilation of the various source files and the linking of the resulting object supervisor mode) to restart the system, although this feature is not used yet. These
files results in two files containing the .TEXT and ..DATA sections of the final two vectors are followed by the other exception vectors. Most of them are set to
system (see also Section 2.1.1). The linker has generated addresses referring to label _fatal, which is the handler for all fatal system errors.
the .DATA section, which normally starts at the bottom of the system’s RAM. 1 | crt0.S
After RESET, however, this RAM is not initialized. Thus the .DATA section must 37 _null: BRA _reset | 0 initial SSP (end of RAM)
be contained in the system’s ROM and copied to the RAM during system start-up, 38 .LONG _reset | 1 initial PC
39 .LONG _fatal, _fatal | 2, 3 bus error, adress error
??? as shown in Figure 4.1 ??? 40 .LONG _fatal, _fatal | 4, 5 illegal instruction, divide/0
41 .LONG _fatal, _fatal | 6, 7 CHK, TRAPV instructions
42 .LONG _fatal, _fatal | 8, 9 privilege violation, trace
43 .LONG _fatal, _fatal | 10,11 Line A,F Emulators
44 |
45 .LONG _fatal,_fatal,_fatal | 12... (reserved)
46 .LONG _fatal,_fatal,_fatal | 15... (reserved)
47 .LONG _fatal,_fatal,_fatal | 18... (reserved)
48 .LONG _fatal,_fatal,_fatal | 21... (reserved)
49 |
50 .LONG _fatal | 24 spurious interrupt
.DATA .DATA 51 .LONG _fatal | 25 level 1 autovector
52 .LONG _duart_isr | 26 level 2 autovector
53 .LONG _fatal | 27 level 3 autovector
RAM 54 .LONG _fatal, _fatal | 28,29 level 4,5 autovector
55 .LONG _fatal, _fatal | 30,31 level 6,7 autovector
56 |
57 .LONG _stop | 32 TRAP #0 vector
58 .LONG _deschedule | 33 TRAP #1 vector
.DATA 59 .LONG _fatal | 34 TRAP #2 vector
60 .LONG _Semaphore_P | 35 TRAP #3 vector
61 .LONG _Semaphore_V | 36 TRAP #4 vector
.TEXT .TEXT .TEXT 62 .LONG _Semaphore_Poll | 37 TRAP #5 vector
63 .LONG _fatal, _fatal | 38,39 TRAP #6, #7 vector
64 .LONG _fatal, _fatal | 40,41 TRAP #8, #9 vector
ROM ROM 65 .LONG _fatal, _fatal | 42,43 TRAP #10,#11 vector
66 .LONG _fatal | 44 TRAP #12 vector
67 .LONG _set_interrupt_mask | 45 TRAP #13 vector
68 .LONG _readByteRegister_HL | 46 TRAP #14 vector
FIGURE 4.1 ??? .DATA and .TEXT during System Start-Up ??? 69 .LONG _writeByteRegister | 47 TRAP #15 vector
...
4. Bootstrap 83 84 4.2 System Start-up

Thus after RESET, processing continues at label _reset. The supervisor stack 108 |
109 MOVE #0x0700, SR | user mode, no ints
pointer is initialized to point to the top of the RAM. This is necessary because the 110 JSR _main |
vector for this purpose was abused for the branch to _reset. Next the vector base 111 |
112 _fatal: |
register (VBR) is set to the beginning of the vector table. This applies only for
MC68020 chips and above and allows for relocation of the vector table. Actually,
If for any reason label _fatal is reached, then all interrupts are disabled, the red
the branch to _reset is intended for jumping to the content of the VBR so that the
LED is turned on, and the SERIAL_1 transmitter is enabled to allow for polled
system can be restarted with a relocated .TEXT section, provided that the VBR
serial output. Then the present supervisor stack pointer, which points to the
points to the proper vector table. For processors such as the MC68000 that do not
exception stack frame created for the fatal system error, is saved and the
provide a VBR, this instruction must be removed. After setting the VBR, the
supervisor stack pointer is set to the end of the RAM. Then os::Panic() is called
LEDs are turned off.
forever with the saved exception stack frame as its argument. os::Panic() prints
81 _reset: | the stack frame in a readable format on the SERIAL_1 channel, so that the cause
82 MOVE.L #RAMend, SP | since we abuse vector 0 for BRA.W
83 LEA _null, A0 | of the fault can easily be determined. It ??? what is it ??? is called forever, so
84 MOVEC A0, VBR | MC68020++ only that a terminal can be connected to SERIAL_1 even after a fatal system error and
85 | enable cache
86 MOVE.B #0, wDUART_OPCR | all outputs via BSET/BCLR the stack frame is not lost, but repeated forever.
87 MOVE.B #LED_ALL, wLED_OFF | all LEDs off
112 _fatal: |
113 MOVE.W #0x2700, SR |
Then the RAM is initialized to 0. The end of the .TEXT section is rounded up to 114 MOVE.B #LED_RED, wLED_ON | red LED on
115 MOVE.B #0x04, wDUART_CR_B | enable transmitter
the next 2Kbyte boundary (assuming the linker was configured to round up the 116 MOVE.L SP, A0 | old stack pointer
.TEXT section to a 2Kbyte boundary), which yields the start of the .DATA section 117 MOVE.L #RAMend, SP |
in ROM. The size of the .DATA section is computed, and the .DATA section is 118 _forever: |
119 MOVE.L A0, -(SP) | save old stack pointer
then copied from ROM to the RAM. 120 MOVE.L A0, -(SP) | push argument
121 JSR _Panic__2osPs | print stack frame
89 MOVE.L #RAMbase, A1 | clear RAM...
122 LEA 2(SP), SP | remove argument
90 MOVE.L #RAMend, A2 |
123 MOVE.L (SP)+, A0 | restore old stack pointer
91 L_CLR: CLR.L (A1)+ |
124 BRA _forever |
92 CMP.L A1, A2 |
125 |
93 BHI L_CLR |
126 _on_exit: |
94 | relocate data section...
127 RTS |
95 MOVE.L #_etext, D0 | end of text section
96 ADD.L #0x00001FFF, D0 | align to next 2K boundary
97 AND.L #0xFFFFE000, D0 | In general, a function name in assembler refers to a C function, whose name is the
98 MOVE.L D0, A0 | source (.data section in ROM)
99 MOVE.L #_sdata, A1 | destination (.data section in RAM)
same except for the leading underscore. This would mean that “JSR _main”
100 MOVE.L #_edata, A2 | end of .data section in RAM would call main(), which is defined in Task.cc. For the GNU C++ compiler/
101 L_COPY: MOVE.L (A0)+, (A1)+ | copy data section from ROM to RAM linker, the main() function is handled in a special way. In this case, a function
102 CMP.L A1, A2 |
103 BHI L_COPY | __main() is automatically created and called just before main(). This __main()
function basically calls the constructors for all statically defined objects so that
At this point, the .TEXT and .DATA sections are located at those addresses to these are initialized properly. The way this is done may change in future, so
which they had been linked. The supervisor stack pointer is set to the final special attention should be paid to the compiler/linker release used. The __main
supervisor stack, and the user stack pointer is set to the top of the idle task’s user function also calls on_exit() (i.e. label _on_exit above), which just returns. So the
stack (the code executed here will end up as the idle task). call of main() in crt0.S basically initializes the static objects and proceeds in the
105 MOVE.L #_SS_top, A7 | set up supervisor stack
real main().
106 MOVE.L #_IUS_top, A0 |
107 MOVE A0, USP | set up user stack Now the CPU is in user mode, but interrupts are still disabled. First, the variable
SchedulerStarted is checked to ensure main() is not called by mistake; in our
Finally (with respect to crt0.S), the CPU enters user mode and calls function case SchedulerStarted is 0.
_main(). It is not intended to return from this call; if this would happen, then it
1 // Task.cc
would be a fatal system error. ...
4. Bootstrap 85 86 4.2 System Start-up

78 void main() occurs. That is, the yellow LED is turned on whenever the CPU is not in stopped
79 {
80 if (Task::SchedulerStarted) return -1; mode, thus indicating the CPU load. After an interrupt occurred, the CPU
proceeds at label _return_from_exception, where it checks if a task switch is
Then a vector containing all tasks known at system start-up is initialized to 0 and required. Note that the interrupt itself cannot cause a task switch directly, since
setupApplicationTasks() is called. In setupApplicationTasks(), all tasks the interrupt occurs while the CPU is in supervisor mode.
required by the application are created (see also Section 4.3). All tasks created 223 _stop: |
have their status set to STARTED. That is, the task ring is completely set up, but 224 MOVE.B #LED_YELLOW, wLED_OFF | yellow LED off
225 STOP #0x2000 |
no task is in state RUN. Next, the status for each task is set from STARTED to 226 BRA _return_from_exception | check for task switch
RUN. 227 |

82 for (int i = 0; i < TASKID_COUNT; i++) Task::TaskIDs[i] = 0;


83 setupApplicationTasks(); After having left supervisor mode, the idle task is again in its endless loop and
84 stops the CPU again, provided that no other task with higher priority is in state
85 for (Task * t = Task::currTask->next; t != Task::currTask; t = t->next)
86 t->TaskStatus &= ~Task::STARTED; RUN.

Here all tasks are in state RUN, but interrupts are still disabled. In the next step,
variable SchedulerStarted is set to prevent subsequent calls to main() (which
would have disastrous effects). Then the hardware is initialized to level
Interrupt_IO, and finally interrupts are enabled. The idle task then de-schedules
itself, which causes the task with the highest priority to execute. The idle task
itself goes into an infinite loop. Whenever the idle task is swapped in (i.e. no other
task is in state RUN), it calls os::Stop().
88 Task::SchedulerStarted = 1;
89 os::init(os::Interrupt_IO); // switch on interrupt system
90 os::set_INT_MASK(os::ALL_INTS);
91
92 Task::Dsched();
93
94 for (;;) os::Stop();
95
96 return 0; /* not reached */
97 }

Function os::Stop() merely executes TRAP #0.


1 /* os.cc */
...
67 void os::Stop()
68 {
69 asm("TRAP #0");
70 }

The CPU thus enters supervisor mode, fetches the corresponding vector and
proceeds at label _stop.
1 | crt0.S
...
57 .LONG _stop | 32 TRAP #0 vector

At label _stop, the yellow LED (which is turned on at every interrupt) is turned
off. The CPU then stops execution with all interrupts enabled until an interrupt
4. Bootstrap 87 88 4.3 Task Start-up

4.3 Task Start-up {


Message msg(“Hello”);
t->SendMessage(msg);
As already mentioned in Section 4.2, a task is started in two steps. First, a task if (t->Next() == Current() break;
}
control block (i.e. an instance of class Task) is created and inserted into the task
ring. At this point, the task status is set to STARTED (i.e. not RUN) so that the
Unfortunately, this approach has some drawbacks. First, the order in which this
task exists, but may not yet execute. In the second step, the task status is set to
loop is performed is different when executed by different tasks. Second, it is
RUN. The main reason for this two-step approach is that tasks often set up in
assumed that all tasks are present in the task chain. Although this is the case in
groups that cooperate by sending messages to each other. Suppose, for instance,
our implementation, one may consider to remove tasks that are not in state RUN
that a task T0 sets up two other tasks T1 and T2. Suppose further that both tasks
temporarily from the task chain in order to speed up task switching. In this case,
T1 and T2 send messages to each other directly after being created. It then might
only tasks in state RUN would receive the message which is probably not what
happen that task T1, provided its priority is higher than the priority of T0,
was desired. A better approach is to maintain a table of task pointers, which is
executes before task T2 is created by task T0. Sending a message from T0 to T1
indexed by an integer task ID. The task IDs could be defined as follows:
would then fail. In our two-step approach, however, T2 would exist already, but
would not yet execute. Thus the message from T1 to T2 would be delivered 1 // TaskId.hh
correctly. 2
3 enum { TASKID_IDLE = 0,
4 TASKID_MONITOR,
5 TASKID_COUNT // number of Task IDs
4.3.1 Task Parameters 6 };

The creation of a task is controlled by a number of parameters. A task is created More task IDs can be added before the TASK_ID_COUNT, so that
by creating an instance of class Task: TASK_ID_COUNT always reflects the proper number of tasks handled this way.
Task IDs and task pointers are mapped by a table:
// Task.hh
... 1 // Task.cc
25 class Task ...
26 { 13 Task * Task::TaskIDs[TASKID_COUNT];
...
49 Task( void (* main)(),
50 unsigned long userStackSize, As a matter of convenience, the task pointers can now be defined as macros:
51 unsigned short queueSize,
52 unsigned short priority, 1 // TaskId.hh
53 const char * taskName ...
54 ); 8 #define IdleTask (Task::TaskIDs[TASKID_IDLE])
... 9 #define MonitorTask (Task::TaskIDs[TASKID_MONITOR])
139 };

This is nearly equivalent to defining e.g MonitorTask directly as a task pointer:


The parameters are the function to be executed by the task, the size of the stack
Task * MonitorTask;
for the task, the size of the task’s message queue, the priority at which the task
shall run, and a character string specifying the name of the task. The task name is
The difference between using a table and direct declaration of Task pointers is
useful for debug messages generated by the task and can be retrieved by the
basically that for a table, all pointers are collected while for the direct declaration,
function Task::MyName() which returns this string:
they are spread over different object files. For a reasonably smart compiler, the
SerialOut::Print(SERIAL_0, “\nTask %s started”, Task::MyName()); macros can be resolved at compile time so that no overhead in execution time or
memory space is caused by the table. Instead, the code of our example is even
So far, tasks have only been referred to by Task pointers, since the name is only simplified:
used for printing purposes. But sometimes it is convenient to refer to tasks by an
for (int t_ID = 0; t_ID < TASKID_COUNT; t_ID++)
integer task ID rather than by task pointers. Assume we want to send a message to {
all tasks. One way of doing this is the following: Message msg(“Hello”);
TaskIDs[t_ID]->SendMessage(msg);
for (const Task * t = Current(); ; t = t->Next()) }
4. Bootstrap 89 90 4.3 Task Start-up

The TaskIDs table is initialized to zero in the idle task’s main() function. should be noted, however, that monitor_main() may return (although most task
functions will not) and that this requires special attention. For task creation, we
assume that a hypothetical function magic() exists. This function does not
4.3.2 Task Creation actually exist as code, but only for the purpose of explaining the task creation.
Function magic() is defined as follows:
As a matter of style, for each task a function that starts up the task should be void magic()
provided. This way, the actual parameters for the task are hidden at the {
application start-up level, thus supporting modularity. The function Task::Terminate_0( monitor_main() );
/* not reached */
setupApplicationTasks(), which is called by the idle task in its main() function, }
sets the serial channels to their desired values (SERIAL_1 in this case) and then
calls the start-up function(s) for the desired tasks. In this example, there is only Note that Terminate_0() is actually defined to have no arguments, but since
one application task; its start-up function is defined in class Monitor (see also magic() is only hypothetically, this does no harm.
Chapter 5). 1 // Task.cc
1 // ApplicationStart.cc ...
99 void Task::Terminate_0()
...
100 {
22 void setupApplicationTasks() 101 Terminate(0);
23 { 102 }
24 MonitorIn = SERIAL_1; ...
25 MonitorOut = SERIAL_1; 104 void Task::Terminate(int ex)
26 ErrorOut = SERIAL_1; 105 {
27 GeneralOut = SERIAL_1; 106 {
28 107 SerialOut so(ErrorOut);
29 Monitor::setupMonitorTask(); 108 so.Print("\n%s Terminated", currTask->name);
30 } 109 }
110 currTask->ExitCode = ex;
111 currTask->TaskStatus |= TERMINATED;
The function setupMonitorTask() creates a new instance of class Task with task 112 Dsched();
113 }
function monitor_main, a user mode stack of 2048 bytes, a message queue of
16 messages, a priority of 240, and the name of the task set to “Monitor Task”.
magic() calls the task’s main function, which is provided when the task is created
1 // Monitor.cc (in this case monitor_main()), as well as Terminate_0() in case the main
...
13 void Monitor::setupMonitorTask()
function returns. Normally tasks do not return from their main functions; but if
14 { they do, then this return is handled by the Terminate_0() function, which merely
15 MonitorTask = new Task ( calls Terminate(0). The functions Terminate_0() and Terminate(int ex) may
16 monitor_main, // function also be called explicitly by a task in order to terminate a task; e.g. in the case of
17 2048, // user stack size
18 16, // message queue size errors. If these functions are called explicitly, then a message is printed, an exit
19 240, // priority code is stored in the TCB, and the task’s state is set to TERMINATED. This
20 "Monitor Task"); causes the task to refrain from execution forever. The TCB, however, is not
21 }
deleted, and the exit code TCB may be analyzed later on in order to determine
why the task died. Setting the task status to TERMINATED does not
The priority (240) should be higher than that of other tasks (which do not exist in
immediately affect the execution of the task; hence it is followed by a Dsched()
the above example) so that the monitor executes even if another task does not
call which causes the task to be swapped out.
block. This allows for identifying such tasks ??? What tasks ???. Creating a
new instance of class Task (i.e new Task(...)) returns a Task pointer which is Now task creation mainly means setting up the TCB and the user stack of the task.
stored in the TaskIDs table, remembering that MonitorTask was actually a The user stack is created as if the task had been put in state STARTED after
macro defined as TaskIDs[TASKID_MONITOR]. With the Task::Task(...) calling Terminate_0() in magic, but before the first instruction of the task’s main
constructor, a new task which starts the execution of a function monitor_main() function. First, several variables in the TCB are set up according to the parameters
is created. The function monitor_main() itself is not of particular interest here. It
4. Bootstrap 91 92 4.3 Task Start-up

supplied to the constructor. At this point, the TCB is not yet linked into the task If currTask is not set yet (i.e. if this is the first task that is created), then a TCB
chain. for the idle task is created, and currTask is set to that TCB. For this purpose, a
1 // Task.cc
Task constructor without arguments is used. In view of this code, it seems more
... reasonable to create the idle task from the outset rather than when the first
33 Task::Task(void (*main)(), application task is created.
34 unsigned long usz,
35 unsigned short qsz, 67 if (!currTask)
36 unsigned short prio, 68 currTask = new Task();
37 const char * taskName
38 )
39 : US_size(usz), Finally, the TCB is linked into the task chain directly after currTask (which may
40 priority(prio), be the idle task, as in our example, or another task). This operation must not be
41 name(taskName),
42 TaskStatus(STARTED), interrupted, so interrupts are masked here.
43 nextWaiting(0),
70 {
44 msgQ(qsz),
71 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS);
45 ExitCode(0)
72 next = currTask->next;
...
73 currTask->next = this;
74 os::set_INT_MASK(old_INT_MASK);
Then the user stack of the task is allocated and initialized to the character 75 }
76 }
userStackMagic (’U’). This initialization allows to determine the stack size used
by the task later on.
The TCB of the newly created task is in a state as if it were put in state STARTED
46 { just before executing the first instruction of its main function.
47 int i;
48
49 Stack = new char[US_size]; // allocate stack
50 4.3.3 Task Activation
51 for (i = 0; i < US_size;) Stack[i++] = userStackMagic;

The task’s program counter is set to the first instruction of its main function. If the After creating a number of tasks, these tasks need to be activated. This is done by
task is swapped in later on, the execution proceeds right at the beginning of the changing the tasks’ state from STARTED to RUN.
task’s main function. Also all other registers of the CPU in the TCB are 1 // Task.cc
initialized. This is not necessary, but improves reproducibility of faults, e.g. due ...
78 void main()
to dangling pointers. 79 {
...
53 Task_A0 = 0xAAAA5555; Task_A1 = 0xAAAA4444; 85 for (Task * t = Task::currTask->next; t != Task::currTask; t = t->next)
54 Task_A2 = 0xAAAA3333; Task_A3 = 0xAAAA2222; 86 t->TaskStatus &= ~Task::STARTED;
55 Task_A4 = 0xAAAA1111; Task_A5 = 0xAAAA0000;
56 Task_A6 = 0xAAAA6666;
57 Task_D0 = 0xDDDD7777; Task_D1 = 0xDDDD6666; If an application task (rather than the idle task) creates new tasks, it should
58 Task_D2 = 0xDDDD5555; Task_D3 = 0xDDDD4444;
59 Task_D4 = 0xDDDD3333; Task_D5 = 0xDDDD2222;
activate the tasks after creating them in a similar way.
60 Task_D6 = 0xDDDD1111; Task_D7 = 0xDDDD0000;
61 Task_PC = main;
62 Task_CCR = 0x0000;
4.3.4 Task Deletion
The user stack pointer of the task is set to the top of the user stack. Then the
If a task terminates, its TCB still exists. Deleting TCBs largely depends on the
address of Terminate_0() is pushed on the user stack. Task::Terminate_0() is
actual application and requires great care. Since TCBs have been allocated with
called in case the task’s main function returns.
the new operator, they need to be deleted with the delete operator. Also, if the
64 Task_USP = (unsigned long *)(Stack + US_size); TaskIDs table is used for a task (which is probably not the case for dynamically
65 *--Task_USP = (unsigned long)Terminate_0;
created tasks), the Task pointer needs to be removed from the table as well. In
addition, it must be assured that no other task maintains a pointer to the deleted
4. Bootstrap 93

task. Finally, use of the delete operator requires use of the malloc package, in
contrast to the simple allocation mechanism we used by default.

An alternative to deleting tasks (which is likely to be a risk due to memory


management as discussed in Section 3.9) is to provide a pool of static tasks which
put themselves in a queue when they are idle. A task requiring a dynamic
task would get such a task out of the queue and send a message
containing a function to be performed to it. ??? Hä ??? This leads to
structures similar to those discussed for the serial router in Section 3.7. In
principle, static TCB can be used instead of the new operator for TCBs. The
reason why we used new rather than static TCBs has historical reasons. The first
application for which our kernel was used had a DIP switch that selected one of
several applications. The kernel was the same for all applications, and the actual
application was selected in setupApplicationTasks() by starting different tasks
depending on the DIP switch setting. Static TCB allocation would have wasted
RAM for those tasks not used for a particular DIP switch setting, while allocation
by new used only those TCBs actually required, thus saving a significant amount
of RAM.
5. An Application 95 96 5.2 Using the Monitor

5 An Application
Main
Menu

Command
Command
Command
5.1 Introduction
Command
In this chapter, we present a simple application: a monitor program that receives Command
commands from a serial port, executes them, and prints the result on the same
serial port. The commands are mainly concerned with retrieving information
Your Info Duart Memory Task
about the running system, such as the status of tasks, or the memory used. This Menu Menu Menu Menu Menu
monitor has shown to be quite useful in practice, so it is recommended to include
it in any application. In order to use the monitor, a terminal or a computer running
a terminal emulation, for example the kermit program, is connected to the serial Command Command Command Command Command
port used by the monitor. Command Command Command Command
Command
5.2 Using the Monitor
Your
Sub-menu
The monitor supports a collection of commands that are grouped in menus: the
main menu, the info menu, the duart menu, the memory menu, and the task menu.
Other menus can easily be added if required. The only purpose of the main menu Command
is to enter one of the other menus.

FIGURE 5.1 Monitor Menu Structure

In each menu, the monitor prints a prompt, such as “Main >” when the monitor is
ready to accept a command. A command consists of a single character and, for
some commands, of an additional argument. Some commands may be activated
by different characters (e.g. H or ? for help), and commands are not case-
sensitive. It is not possible to edit commands or arguments.

The two commands shown in Table 1 are valid for all menus:
5. An Application 97 98 5.3 A Monitor Session

5.3 A Monitor Session

The commands of the monitor are best understood by looking at a commented


Command Action
monitor session. Commands and arguments entered are shown in bold font. When
Hh? Print Help on commands available in menu. the monitor is started, it prints a start-up message:
Q q ESC Return from this menu (ignored in main menu).
Monitor started on channel 1.
TABLE 1. Commands available in all menus Type H or ? for help.
Main Menu [D I M T H]
The remaining commands shown in Table 2 are only valid in their specific menus. Main >

H (or ?) shows the options available in the (main) menu:


Main > h
D - Duart Menu
Menu Command Action Argument I - Info Menu
Main Ii Enter Info Menu - M - Memory Menu
Main Dd Enter Duart Menu - T - Task Menu

Main Mm Enter Memory Menu -


D enters the duart menu and h shows the options available:
Main Tt Enter Task Menu -
Main > d
Info Os Display Overflows - Duart Menu [B C M T H Q]
Info Ss Display Top of Memory - Duart_A > ?
B - Set Baud Rate
Info Tt Display System Time - C - Change Channel
Duart Bb Set Baud Rate Baud Rate M - Change Mode
Duart Cc Change Channel - T - Transmit Character

Duart Mm Set Serial Mode Data bits and Parity


B sets the baud rate of the duart channel A (SERIAL_0), M sets the data format.
Duart Tt Transmit Character Character (hex) The monitor itself is running on SERIAL_1 so that this setting does not disturb
Memory D Display Memory Address (hex) the monitor session.
Memory \n Continue Display Memory - Duart_A > b
Task Ss Display all Tasks - Baud Rate ? 9600
Duart_A >
Task Tt Display particular Task Task number Duart_A > m
Task Pp Set Task Priority Priority (decimal) Data Bits (5-8) ? 8
Parity (N O E M S) ? n
TABLE 2. Specific commands
Databits = 8 / Parity = n set.

C toggles the duart channel, which changes the prompt of the duart menu.
Duart_A > c
Duart_B >

T transmits a character. The character is entered in hex (0x44 is ASCII ’D’).


Duart_B > t 44
Sending 0x44D
Duart_B >
5. An Application 99 100 5.3 A Monitor Session

The last character (’D’) in the line above is the character transmitted. Q exits the 00000080: 0000 02F6 0000 0306 0000 0172 0000 03AC ...........r....
duart menu and i enters the info menu. 00000090: 0000 03FE 0000 0444 0000 0172 0000 0172 .......D...r...r
000000A0: 0000 0172 0000 0172 0000 0172 0000 0172 ...r...r...r...r
Duart_B > q 000000B0: 0000 0172 0000 0458 0000 046A 0000 0474 ...r...X...j...t
Main > i 000000C0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF ................
Info > ? 000000D0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF ................
O - Overflows 000000E0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF ................
S - System Memory 000000F0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF ................
T - System Time Memory > q
Info Menu [O S T H Q]
T enters the task menu and h shows the available options.
O displays the overflows of the serial input queues.
Main > t
Info > o Task Menu [P S T H Q]
Ch 0 in : 0 Task > h
Ch 1 in : 0 P - Set Task Priority
S - Show Tasks
S displays the top of the system RAM used. Since the RAM is starting at address T - Show Task
0x20000, the total amount of RAM required is slightly more than 4 kBytes:
S displays a list of all tasks. The current task is marked with an arrow:
Info > s
Top of System Memory: 20001050 Task > s Show Tasks:
----------------------------------------------------
T shows the time since system start-up in milliseconds (i.e. 23 seconds) and q TCB Status Pri TaskName ID US Usage
----------------------------------------------------
leaves the info menu. --> 20000664 RUN 240 Monitor Task 1 0000014C
Info > t 20000FB4 RUN 0 Idle Task 0 000000A0
System Time: 0:23140 ====================================================
Info > q

M enters the memory menu and h shows the available options. T shows details of a particular task. The task number entered is the position of the
task in the display of the previous command, starting at 0, rather than the task ID.
Main > m
Memory Menu [D H Q]
Thus entering 1 displays the idle task rather than the monitor task.
Memory > h Task > t Show Task:
D - Dump Memory Task number = 1
Task Name: Idle Task
D dumps the memory from the address specified. The memory dump may be Priority: 0
continued after the last address by typing return (not shown). Here, the address is TCB Address: 20000FB4
Status: RUN
0; thus dumping the vector table at the beginning of crt0.S. Q leaves the memory
US Base: 2000020C
menu. US Size: 00000200
Memory > d Dump Mamory at address 0x0 US Usage: 000000A0 (31%)
00000000: 6000 00FE 0000 0100 0000 0172 0000 0172 ‘..........r...r Task >
00000010: 0000 0172 0000 0172 0000 0172 0000 0172 ...r...r...r...r
00000020: 0000 0172 0000 0172 0000 0172 0000 0172 ...r...r...r...r Apparently the user stack of 512 bytes for the idle task could be reduced to 160
00000030: 0000 0172 0000 0172 0000 0172 0000 0172 ...r...r...r...r bytes. Finally, p sets the monitor task priority and q returns to the main menu:
00000040: 0000 0172 0000 0172 0000 0172 0000 0172 ...r...r...r...r
00000050: 0000 0172 0000 0172 0000 0172 0000 0172 ...r...r...r...r Task > p Set Task Priority:
00000060: 0000 0172 0000 0172 0000 01A4 0000 0172 ...r...r.......r Task number = 0
00000070: 0000 0172 0000 0172 0000 0172 0000 0172 ...r...r...r...r Task priority = 200
5. An Application 101 102 5.4 Monitor Implementation

Set Monitor Task Priority to 200 5.4 Monitor Implementation


Task >
Task > q
The different monitor commands and menus are contained in a class Monitor, see
Main >
Section A.19 for details. The monitor is included in the system by creating a task
In some cases, an additional prompt is printed after having entered numbers. The for the monitor in setupApplicationStart() and setting the channels MonitorIn
function accepting numbers waits until a non-digit, such as carriage return, is and MonitorOut to the desired serial channel, in our case SERIAL_1.
entered. If this carriage return is not caught, then it is interpreted as a command. 1 // ApplicationStart.cc
Except for the memory menu, carriage return is not a valid command; it is ignored ...
22 void setupApplicationTasks()
and a new prompt is displayed. 23 {
24 MonitorIn = SERIAL_1;
25 MonitorOut = SERIAL_1;
26 ErrorOut = SERIAL_1;
27 GeneralOut = SERIAL_1;
28
29 Monitor::setupMonitorTask();
30 }

With Monitor::setupMonitorTask(), the monitor task is created:


1 // Monitor.cc
...
13 void Monitor::setupMonitorTask()
14 {
15 MonitorTask = new Task (
16 monitor_main, // function
17 2048, // user stack size
18 16, // message queue size
19 240, // priority
20 "Monitor Task");
21 }

Function setupMonitorTask() creates a task with main function monitor_main,


a user stack of 2048 bytes, a message queue for 16 messages (which is actually
not used), a task name of “Monitor Task”, and a priority of 240. The monitor
should have a priority higher than that of all other tasks. This allows the monitor
to display all tasks even if some task (of lower priority) is in busy wait (e.g by
mistake) of some kind and to identify such tasks.

Function monitor_main(), which is the code executed by the monitor task, prints
a message that the task has started and creates an instance of class Monitor using
MonitorIn and MonitorOut as channels for the serial port and enters the main
menu of the monitor.
1 // Monitor.cc
...
23 void Monitor::monitor_main()
24 {
25 SerialOut::Print(GeneralOut,
26 "\nMonitor started on channel %d.",
27 MonitorOut);
5. An Application 103 104 5.4 Monitor Implementation

28 The same ??? structure/code ??? applies for all other menus. However, we
29 Monitor Mon(MonitorIn, MonitorOut);
30 Mon.MonitorMainMenu(); should focus on an interesting situation in the duart menu: here, the user can
31 } toggle the duart channel to which the commands of the duart menu apply with the
command C; i.e. toggle between channels SERIAL_0 and SERIAL_1. The
The constructor for class Monitor creates a SerialIn object si for its input actual channel chosen is displayed as the prompt of the duart menu. Now consider
channel. In contrast, the output channel is merely stored, but no SerialOut object the T command, which reads a character to transmit (in hex), prints the character
is created. As a result, the input channel is reserved for the monitor forever, while to be transmitted, and finally transmits the character on the duart channel selected.
the output channel can be used by other tasks as well. This explains why A naive implementation would be the following:
ErrorOut and GeneralOut could have been set to SERIAL_1 as well. The case 't': case 'T':
remaining data members of class Monitor are used to remember the state of sub- {
menus even if the monitor returns from the menus. SerialOut so(channel);
currentChar = si.Gethex(so);
1 // Monitor.hh
... so.Print("\nSending 0x%2X", currentChar & 0xFF);
11 class Monitor
12 { Channel bc;
13 public:
14 Monitor(Channel In, Channel Out) if (currentChannel) bc = SERIAL_1;
15 : si(In), channel(Out), currentChannel(0), last_addr(0) {}; else bc = SERIAL_0;
...
48 }; SerialOut::Print(bc, "%c", currentChar);
}
The code for the menus is straightforward and basically the same for all menus. continue;
For instance, the main menu prints a prompt, receives the next character
(command), and calls the function corresponding to the command (if any). Function getCurrentChannel() simply returns SERIAL_0 or SERIAL_1,
depending on what has been selected with the C command. This works fine if
1 // Monitor.cc
...
SERIAL_0 is selected. But what happens otherwise, i.e. if getCurrentChannel()
59 //----------------------------------------------------------------- returns SERIAL_1? In this case, we have already created a SerialOut object so
60 void Monitor::MonitorMainMenu() for channel (which is SERIAL_1), and we are about to perform a
61 { SerialOut::Print(bc,...) with bc set to SERIAL_1 as well. This print will try to
62 SerialOut::Print(channel, "\nType H or ? for help.");
63 SerialOut::Print(channel, "\nMain Menu [D I M T H]\n");
create another SerialOut object for SERIAL_1. As we are already using
64 SERIAL_1, the task blocks itself forever, because it claims a resource it already
65 for (;;) switch(getCommand("Main")) owns. This is a nice example of a deadlock. The proper way of handling the
66 { situation is as follows:
67 case 'h': case 'H': case '?':
68 { 226 case 't': case 'T':
69 SerialOut so(channel); 227 {
70 so.Print("\nD - Duart Menu"); 228 SerialOut so(channel);
71 so.Print("\nI - Info Menu"); 229 currentChar = si.Gethex(so);
72 so.Print("\nM - Memory Menu"); 230
73 so.Print("\nT - Task Menu"); 231 so.Print("\nSending 0x%2X", currentChar & 0xFF);
74 } 232 }
75 continue; 233 {
76 234 Channel bc;
77 case 'd': case 'D': DuartMenu(); continue; 235
78 case 'i': case 'I': InfoMenu(); continue; 236 if (currentChannel) bc = SERIAL_1;
79 case 'm': case 'M': MemoryMenu(); continue; 237 else bc = SERIAL_0;
80 case 't': case 'T': TaskMenu(); continue; 238
81 } 239 SerialOut::Print(bc, "%c", currentChar);
82 } 240 }
241 continue;
5. An Application 105

The lifetime of the so object is simply limited to merely getting the parameter and
printing the message about the character that is about to be transmitted. The so
object is then destructed, making channel so available again. The
SerialOut::Print(bc, ...) can then use channel bc (whether it happens to be
SERIAL_1 or not) without deadlocking the monitor task.
6. Development Environment 107 108 6.2 Terminology

6 Development Environment It is even possible to create the cross-environment for the host on yet another
system called the build machine. But in most cases, the host is the same as the
build machine.

6.1 General

In this chapter, we specify a complete development environment. This


environment is based on the GNU C++ compiler gcc which is available for a large
number of target systems (i.e. CPU families for the embedded system in this
context). The gcc is available on the WWW and several CD-ROM distributions,
particularly for Linux.

6.2 Terminology

In the following sections, two terms are frequently used: a host is a computer
system used for developing software, while a target is a computer system on
which this software is supposed to run, in our case an embedded system. In this
context, a computer system is characterized by a CPU type or family, a
manufacturer, and an operating system. Regarding the target, the manufacturer
and the operating system are of little concern, since we are building this operating
system ourselves. The basic idea here is to find an already existing target system
that is supported by gcc and as similar as possible to our embedded system. This
helps to reduce the configuration effort to the minimum.

Thus we are looking for a development environment that exactly matches our host
(e.g. a workstation or a PC running DOS or Linux) and the CPU family of our
embedded system (e.g. the MC68xxx family). All of the programs required and
described below will run on the host, but some of them need to be configured to
generate code for the target.

A program for which host and target are identical is called native; if host and
target are different, the prefix cross- is used. For instance, a C++ compiler running
on a PC under DOS and generating code to be executed under DOS as well is a
native C++ compiler. Another C++ compiler running on a PC under DOS, but
generating code for MC68xxx processors is a cross-compiler.

Due to the large number of possible systems, there are many more cross-
compilers possible than native compilers. For this reason, native compilers are
often available as executable programs in various places, while cross-compilers
usually need to be made according to the actual host/target combination required.
110 6.3 Prerequisites

> cd make-3.76.1
• Read the files README and INSTALL for instructions particular for your
host
• Configure the package:
> ./configure
• Build the packet. This takes about 5 minutes:
> make
• Install the packet. This may require root privileges, depending on where
you want it to be installed. At this point, consider the name conflicts with
the existing make program. Make sure that GNU make is installed as
gmake:
> make install

6.3.2 Scenario 2: DOS Host

The simplest way for a DOS host is to fetch binary versions of gcc and gmake.
Please refer to

ftp://prep.ai.mit.edu/pub/gnu/MicrosPorts/MSDOS.gcc

for links to sites providing such binaries.

The gcc and binutils packages provide special means for building the cross-
environment for DOS. The gmake is not strictly required, since it is not needed
for building the cross-environment, and you will have to modify the Makefile for
the embedded system anyway, since most UNIX commands are not available
under DOS. You should fetch the gmake nevertheless, because this requires less
changes for the target Makefile.

6.3.3 Scenario 3: Other Host or Scenarios 1 and 2 Failed

If none of the above scenarios discussed above succeeds, you can still survive:
• Get hold of a machine satisfying one of the above scenarios. This machine
is called the build machine.
• On the build machine, install gmake (not required for scenario 2) and gcc
as a native C compiler for the build machine.
• On the build machine, build the cross-environment as described later on.
Observe the README and INSTALL files particularly carefully. When
configuring the packets, set the --build, --host and --target options
accordingly.
6. Development Environment 111 112 6.4 Building the Cross-Environment

• Copy the cross-environment to your host. 6.4 Building the Cross-Environment

After that, the build machine is no longer needed. In the following, we assume that the cross-environment is created in a directory
called /CROSS on a UNIX or Linux host, which is also the build machine. In
order to perform the “make install” steps below, you either need to be root or the
/CROSS directory exists and you have write permission for it.

Since we assume a MC68020 CPU for the embedded system, we choose a sun3
machine as target. This machine has a CPU of the MC68000 family and is
referred to as m68k-sun-sunos4.1 when specifying targets. The general name for
a target has the form CPU-Manufacturer-OperatingSystem.

For a DOS host, please follow the installation instructions provided with the
binutils and gcc packages instead.

6.4.1 Building the GNU cross-binutils package

The GNU binutils package contains a collection of programs, of which some are
essential. The absolute minimum required is the cross-assembler as (which is
required by the GNU C++ cross-compiler) and the cross-linker ld. The Makefile
provided in this book also uses the cross-archive program ar, the name utility nm
and the objcopy program.
1 # Makefile for gmake
2 #
3
4 # Development environment.
5 # Replace /CROSS by the path where you installed the environment
6 #
7 AR := /CROSS/bin/m68k-sun-sunos4.1-ar
8 AS := /CROSS/bin/m68k-sun-sunos4.1-as
9 LD := /CROSS/bin/m68k-sun-sunos4.1-ld
10 NM := /CROSS/bin/m68k-sun-sunos4.1-nm
11 OBJCOPY := /CROSS/bin/m68k-sun-sunos4.1-objcopy
12 CC := /CROSS/bin/m68k-sun-sunos4.1-gcc
13 MAKE := gmake

Since the Makefile provided with the binutils package builds all these programs
by default, there is no use at all to build only particular programs instead of the
complete binutils suite.

To install the GNU binutils package, proceed as follows:


• Get hold of a file called binutils-2.8.1.tar.gz and store it in a separate
directory, for instance /CROSS/src. You can get this file either from a CD-
ROM, e.g. from a Linux distribution, or from the WWW:
ftp://prep.ai.mit.edu/pub/gnu/binutils-2.8.1.tar.gz or
6. Development Environment 113 114 6.4 Building the Cross-Environment

ftp://ftp.funet.fi/pub/gnu/gnu/binutils-2.8.1.tar.gz --with-gnu-ld \
• In the /CROSS/src directory, unpack the file: --with-gnu-as
> cd /CROSS/src • Build the C and C++ compilers, which takes about 30 minutes. This make is
> tar -xvzf binutils-2.8.1.tar.gz or supposed to fail when making libgcc1.cross. This is on purpose, since we
> zcat binutils-2.8.1.tar.gz | tar -xvf - if your tar program does not have not supplied a libgcc1.a at this point:
support the -z option > make LANGUAGES=”C C++”
• Change to the directory created by the tar program: • Install the compilers, either as root or with write permission to /CROSS:
> cd binutils-2.8.1 > make LANGUAGES=”c c++” install-common
• Read the file README for instructions particular for your host > make LANGUAGES=”c c++” install-driver
• Configure the package. There is a period of a few minutes during which no • You may optionally install man pages and/or info files as root:
screen output is generated. If your build machine is not the host, you need > make LANGUAGES=”c c++” install-man
to specify a --host= option as well: > make LANGUAGES=”c c++” install-info
> ./configure --target=m68k-sun-sunos4.1 \
> --enable-targets=m68k-sun-sunos4.1 \ Note: There are some dependencies between the actual gcc compiler version and
--prefix=/CROSS the libgcc.a library used with it. There are also dependencies between the
compiler version and the source code for the target, in particular regarding
• Build the packet, which takes about 20 minutes:
template class instantiation and support for C++ exceptions. It might therefore be
> gmake all-gcc
necessary to change the source code provided in this book for different compiler
• Install the packet, either as root or with write permission to /CROSS. versions.
> gmake install

6.4.3 The libgcc.a library


6.4.2 Building the GNU cross-gcc package
The gcc compiler requires a library that contains functions generated by the
To install the GNU gcc package, proceed as follows: compiler itself. This library is usually called libgcc.a. The default installation
• Get hold of a file called gcc-2.8.1.tar.gz and store it in a separate directory, procedure of gcc requires that a library libgcc1.a is provided beforehand and
for instance. /CROSS/src. You can get this file either from a CD-ROM, e.g. creates another library libgcc2.a itself. These two libraries libgcc1.a and
from a Linux distribution, or from the WWW: libgcc2.a are then merged into the library libgcc.a. Since we have not provided a
ftp://prep.ai.mit.edu/pub/gnu/gcc-2.8.1.tar.gz or libgcc1.a, the build was aborted when building the make target libgcc1.cross as
ftp://ftp.funet.fi/pub/gnu/gnu/gcc-2.8.1.tar.gz described in Section 6.4.2. The difference between libgcc1.a and libgcc2.a
(besides the fact that they contain entirely different functions) is that libgcc2.a
• In the /CROSS/src directory, unpack the file: can be compiled with gcc, while libgcc1.a functions usually cannot, at least not
> cd /CROSS/src without in-line assembly code.
> tar -xvzf gcc-2.8.1.tar.gz or
> zcat gcc-2.8.1.tar.gz | tar -xvf - if your tar program does not The final step in setting up the cross-environment is to create libgcc.a:
support the -z option
• Change to the directory created by the tar program: • Change to the gcc build directory:
> cd /CROSS/gcc-2.8.1
> cd gcc-2.8.1
• Read the file INSTALL for instructions particular for your host • Build the libgcc2 library:
> make LANGUAGES=”c c++” libgcc2.a
• Configure the package. If your build machine is not the host, you need to
• Rename libgcc2.a to libgcc.a:
specify a --host= option as well:
> mv libgcc2.a libgcc.a
> ./configure --target=m68k-sun-sunos4.1 \
--prefix=/CROSS \
6. Development Environment 115 116 6.4 Building the Cross-Environment

At this point, you have a libgcc.a, but it still lacks the functions of libgcc1.a . The without applications. You should therefore disable exception handling with the
functions in libgcc1.a provide multiplication, division, and modulo operations for gcc option -fno-exceptions.
32 bit and 64 bit integers. For the MC68020 and higher CPUs, these operations
are directly supported by the CPU, and the gcc will use them if the -mc68020 flag
is present. In this case, there is nothing more to do and you may decide to leave
the libggc.a as it is. If you do so, you should always check the final Target.td file
for undefined symbols.

If you want to do it the proper way because you do not have a MC68020 CPU, or
if you want to make sure that your cross-environment works under all
circumstances, you have to provide the functions for libgcc1.a yourself. In order
to get them compiled with gcc, you are of course not allowed to use the functions
you are implementing.

As an example, we consider the function _mulsi3, which is supposed to multiply


two signed 32 bit integers and to return the result. You may implement it as
follows (not tested): ??? sollte das nicht besser doch getested sein ???
long _mulsi3(long p1, long p2)
{
long result;
int negative = 0;

if (p1 < 0) { p1 = -p1; negative++; }


if (p2 < 0) { p2 = -p2; negative++; }
asm("
MOVE.L %1,D1 | D1.L == p1
MOVE.L %2,D2 | D2.L == p2
MOVE.W D2,D0 | D0.W == p1_low
MULU D1,D0 | D0.L == p1_low * p2_low
MOVE.L D2,D3 | D3.L == p2
SWAP D3 | D3.W == p2_high
MULU D1,D3 | D3.L == p1_low * p2_high
SWAP D1 | D1.W == p1_high
MULU D2,D1 | D1.L == p1_high * p2_low
ADD.L D1,D3 | D3.L == p1_low * p2_high + p1_high * p2_low
SWAP D3 | shift D3.L 16 bits, D3.W dirty
CLR.W D3 | D3.L == (p1_low * p2_high + p1_high * p2_low) << 16
ADD.L D3,D0 | D0.L == p1 * p2
MOVE.L D0,%0 | store result
" : =g(result) : "g"(p1), "g"(p2) : "d0", "d1", "d2", "d3" );

if (negative & 1) return -result;


else return result;
}

The libgcc.a contains several modules for C++ exception support. For an
embedded system, you will most probably not use any exceptions at all, since
exceptions are fatal errors in this context. When compiling C++ programs, the gcc
enables exception processing by default. This will increase the size of the ROM
image by about 9 kilobytes, which is slightly less than the whole operating system
6. Development Environment 117 118 6.5 The Target Environment

6.5 The Target Environment The command line options for the assembler, linker, and compiler follow. The
assembler is instructed to allow the additional MC68020 opcodes and addressing
The target environment is created by installing all files listed in the appendices in modes. The compiler is also told to use maximum optimization and not to use a
a separate directory on the host. In that directory, you can compile the sources in frame pointer if none is required. The linker is instructed not to use standard
order to build the final ROM image, which can then be burned into an EPROM for libraries (remember that we did not build standard libraries for our environments),
the embedded system. Building the ROM image is achieved by entering to use the target addresses specified above for the .TEXT and .DATA sections,
and to create a map file. The map file should be checked after the build is
• > gmake
completed.
This command invokes the build process, which is controlled by the Makefile, 21 # compiler and linker flags.
22 #
and creates the ROM image both in binary format (file Tartget.bin) and in 23 ASFLAGS := -mc68020
Srecord format (file Target). 24 CCFLAGS := -mc68020 -O2 -fomit-frame-pointer -fno-exceptions
25
26 LDFLAGS := -i -nostdlib \
27 -Ttext $(ROM_BASE) -Tdata $(RAM_BASE) \
6.5.1 The Target Makefile
28 -Xlinker -Map -Xlinker Target.map

The whole process of creating the ROM image is controlled by the Makefile Our source files are the assembler start-up file crt0.S and all files *.cc, assuming
which is explained in this section. The Makefile is used by gmake to start that no other files with extension .cc are stored in the directory where the ROM
compilers, linkers, and so on as required for building the final ROM image. The image is made.
Makefile starts with the locations where the cross-compiler and cross-binutils are
30 # Source files
installed. In our case, the gcc and binutils packages have been installed with 31 #
prefix=/CROSS, which installed them below the /CROSS directory. 32 SRC_S := $(wildcard *.S)
33 SRC_CC := $(wildcard *.cc)
1 # Makefile for gmake
34 SRC := $(SRC_S) $(SRC_CC)
2 #
3
4 # Development environment. For each .cc file, the compiler creates a .d file later on, using the -MM option.
5 # Replace /CROSS by where you installed the cross-environment Rather than making a .cc file dependent of all header (.hh) files, which would lead
6 #
7 CROSS-PREFIX:= /CROSS to re-compiling all .cc files when any header file is changed, this ??? -MM
8 AR := $(CROSS-PREFIX)/bin/m68k-sun-sunos4.1-ar option ??? only causes those .cc files to be compiled that include changed .hh
9 AS := $(CROSS-PREFIX)/bin/m68k-sun-sunos4.1-as
10 LD := $(CROSS-PREFIX)/bin/m68k-sun-sunos4.1-ld
files, which speeds up compilation.
11 NM := $(CROSS-PREFIX)/bin/m68k-sun-sunos4.1-nm 36 # Dependency files
12 OBJCOPY := $(CROSS-PREFIX)/bin/m68k-sun-sunos4.1-objcopy 37 #
13 CC := $(CROSS-PREFIX)/bin/m68k-sun-sunos4.1-gcc 38 DEP_CC := $(SRC_CC:.cc=.d)
14 MAKE := gmake 39 DEP_S := $(SRC_S:.S=.d)
15 40 DEP := $(DEP_CC) $(DEP_S)

The object files to be created by the assembler or the compiler:


Then the target addresses for ROM and RAM are specified. These addresses are
used by the linker. ROM_BASE is where the .TEXT section is to be linked, and 42 # Object files
43 #
RAM_BASE is where the .DATA section is to be linked. 44 OBJ_S := $(SRC_S:.S=.o)
16 # Target memory mapping. 45 OBJ_CC := $(SRC_CC:.cc=.o)
17 # 46 OBJ := $(OBJ_S) $(OBJ_CC)
18 ROM_BASE:= 0
19 RAM_BASE:= 20000000 The files that are created by the build process and that may thus be deleted
without harm:
48 CLEAN := $(OBJ) $(DEP) libos.a \
6. Development Environment 119 120 6.5 The Target Environment

49 Target Target.bin \ 81
50 Target.td Target.text Target.data \ 82 %.d: %.S
51 Target.map Target.sym 83 $(SHELL) -ec '$(CC) -MM $(ASFLAGS) $< \
84 | sed '\''s/$*\.o/$*\.o $@/'\'' > $@'
The default target (all) for the Makefile is the ROM image (Target) and the
corresponding map and symbol files. Other targets are clean, which removes all All object files are placed in a library called libos.a. Consequently, only the code
non-source files (should also be used if entire source files are deleted), and tar, that is actually required is included in the ROM image. If code size becomes an
which creates a tar file containing the source files and the Makefile. issue, then one can break down the source files into smaller source files,
containing for instance only one function each. Linking is usually performed at
Note: Lines containing a command, like line 66, must start with a tab, rather than file level, so that for files containing both used and unused functions, the unused
spaces. functions are included in the final result as well. Splitting larger source files into
smaller ones can thus reduce the final code size.
53 # Targets
54 # 86 libos.a:$(OBJ)
55 .PHONY: all 87 $(AR) -sr libos.a $?
56 .PHONY: clean
57 .PHONY: tar
58
The final ROM image, Target, is made by converting the corresponding binary
59 all: Target Target.sym file, Target.bin, into Srecord format. Most EPROM programmers accept both
60 binary and Srecord files. However, Srecord files are more convenient to read or to
61 clean: send by mail, and they also contain checksums.
62 /bin/rm -f $(CLEAN)
63 89 Target: Target.bin
64 tar: clean 90 $(OBJCOPY) -I binary -O srec $< $@
65 tar:
66 tar -cvzf ../src.tar *
The file Target.text contains the .TEXT section of the linker’s output Target.td
in binary format. It is created by instructing the objcopy to remove the .DATA
The dependency files are included to create the proper dependencies between the
segment and to store the result in binary format.
included .cc files and .hh files:
92 Target.text:Target.td
68 include $(DEP) 93 $(OBJCOPY) -R .data -O binary $< $@

How are object and dependency files made? An object file is made by compiling a The file Target.data contains the .DATA section of the linker’s output Target.td
.cc or .S file, using the compiler flags discussed above. A dependency file is made in binary format. It is created by instructing the objcopy to remove the .TEXT
by compiling a .cc file using the -MM option additionally. The dependency file segment and to store the result in binary format.
itself has the same dependencies as the object file, but the dependency of the
95 Target.data:Target.td
dependency file is not maintained automatically by the compiler. For this reason, 96 $(OBJCOPY) -R .text -O binary $< $@
the left side of a dependency (e.g. file.o:) is extended by the corresponding
dependency file (resulting in file.o file.d:). This method will not work for DOS, For the target configuration we have chosen (aout format), a 32 byte header
because DOS does not have essential commands such as sed. created is created if the .TEXT segment is linked to address 0. This header must
70 # Standard Pattern rules... be removed, e.g. by a small utility skip_aout which is described below. The file
71 # Target.bin is created by removing this header from Target.text and appending
72 %.o: %.cc
73 $(CC) -c $(CCFLAGS) $< -o $@
Target.data:
74 98 Target.bin:Target.text Target.data
75 %.o: %.S 99 cat Target.text | skip_aout | cat - Target.data > $@
76 $(CC) -c $(ASFLAGS) $< -o $@
77
78 %.d: %.cc The map file Target.sym is created by the nm utility with the linker’s output. The
79 $(SHELL) -ec '$(CC) -MM $(CCFLAGS) $< \ nm is instructed to create a format easier to read by humans then the default
80 | sed '\''s/$*\.o/$*\.o $@/'\'' > $@' output by the option --demangle. From this output, several useless symbols are
6. Development Environment 121

removed. The map file is useful to translate absolute addresses (e.g. in stack
dumps created in the case of fatal errors) to function names.
101 Target.sym:Target.td
102 $(NM) -n --demangle $< \
103 | awk '{printf("%s %s\n", $$1, $$3)}' \
104 | grep -v compiled | grep -v "\.o" \
105 | grep -v "_DYNAMIC" | grep -v "^U" > $@

The object file crt0.o for the start-up code crt0.S is linked with libos.a
(containing all object files for our sources) and with libgcc (containing all object
files required by the gcc compiler).
108 Target.td:crt0.o libos.a libgcc.a
109 $(CC) -o $@ crt0.o -L. -los -lgcc $(LDFLAGS)

6.5.2 The skip_aout Utility

As already mentioned, the .TEXT segment extracted from Target.td by objcopy


starts with a 32 byte header if the link address is 0. This header can be removed by
the following utility skip_aout, which simply discards the first 32 bytes from
stdin and copies the remaining bytes to stdout.
// skip_aout.cc
#include <stdio.h>

enum { AOUT_OFFSET = 0x20 }; // 32 byte aout header to skip

int main(int, char *[])


{
int count, cc;

for (count = 0; (cc = getchar()) != EOF; count++)


if (count >= AOUT_OFFSET) putchar(cc);

exit(count < AOUT_OFFSET ? 1 : 0);


}
7. Miscellaneous 123 124 7.2 Porting to different Processors

7 Miscellaneous ...
81 _reset: |
82 MOVE.L #RAMend, SP | since we abuse vector 0 for BRA.W
83 LEA _null, A0 |
84 MOVEC A0, VBR | MC68010++ only

The first instruction after label _reset sets up the SSP, which fixes the abuse of
vector 0. Then the VBR is set to point to the actual vector table. For a MC68000
or a MC68008, there is no VBR and the instruction would cause an illegal
instruction trap at this point. For a MC68000 or MC68008 CPU, the move
7.1 General instruction to the VBR must be removed. Clearly, for such CPUs it is impossible
to locate the vector table (i.e. crt0.S) to anywhere else than address 0.
This chapter covers topics that do not fit in the previous chapters in any natural
way.
7.2.2 Porting to Other Processor families
7.2 Porting to different Processors
The only specific feature of the MC68000 family we used was the distinction
So far, a MC68020 has been assumed as target CPU. For using a different CPU, between supervisor mode and user mode. At the end of an exception processing
the assembler part of the kernel has to be rewritten. Since most of the code is routine, it was checked whether a change back to user mode would happen. If so,
specified in C++, the amount of code to be rewritten is fairly small. The files a pending task switch was executed.
concerned are crt0.S and the files containing in-line assembler code, i.e. os.cc, 235 _return_from_exception: | check for task switch
os.hh, Task.hh, and Semaphore.hh. 236 OR.W #0x0700, SR | disable interrupts
237 MOVE.W (SP), -(SP) | get status register before exception
238 AND.W #0x2700, (SP)+ | supervisor mode or ints disabled ?
239 BNE L_task_switch_done | yes dont switch task
7.2.1 Porting to MC68000 or MC68008 Processors
If a processor, e.g a Z80, does not provide different modes, then these modes can
If the target CPU is a MC68000 or MC68008, then only one instruction in crt0.S be emulated by a counter which is initialized to 0. For every exception, i.e.
needs to be removed. The start-up code crt0.S has been written so that it can be interrupts and also the function calls using the TRAP interface such as
linked not only to base address 0 (i.e. assuming the code is executed directly after Semaphore::P(), this counter is incremented. At the end of every exception
a processor RESET) but also to other addresses. In this case, a jump to the start of processing, the counter is decremented, and reaching 0 is equivalent to returning
crt0.S is required: to user mode.
1 | crt0.S
...
37 _null: BRA _reset | 0 initial SSP (end of RAM)
38 .LONG _reset | 1 initial PC

Normally, exception vector 0 contains the initial supervisor stack pointer, but
since the supervisor stack pointer is not required from the outset, we have inserted
a branch to label _reset instead. Thus a BRA _null has the same effect as a
processor RESET. The CPU needs to know, however, where the vector table
(starting at label _null) is located in the memory. For MC68010 CPUs and above,
a special register, the vector base register VBR, has been implemented. After
RESET, the VBR is set to 0. If crt0.S is linked to a different address, then the
VBR has to be set accordingly. In crt0.S, the vector base address is computed
automatically so that the user is not concerned with this matter:
1 | crt0.S
7. Miscellaneous 125 126 7.3 Saving Registers in Interrupt Service Routines

7.3 Saving Registers in Interrupt Service Routines 134 MOVE.B #LED_YELLOW, wLED_ON | yellow LED on
135 MOVEM.L D0/D1/D7/A0/A1, -(SP) | save registers used later on
...
An interrupt service routine must not alter any registers. For a simple interrupt 216 MOVEM.L (SP)+, D0/D1/D7/A0/A1 | restore registers
...
service routine, this can be achieved by saving those registers that the interrupt
service routine uses and by restoring them after completion.
This causes only 5 instead of 15 registers to be saved and restored. Since
1 | crt0.S compilers tend to choose lower register numbers (D0, D1, A0, A1, FP0, and FP1)
...
133 _duart_isr: | for registers that they may destroy, we chose a high register (D7) for the interrupt
134 MOVE.B #LED_YELLOW, wLED_ON | yellow LED on status so that it does not need to be saved before C++ function calls.
135 MOVEM.L D0-D7/A0-A6, -(SP) | save all registers
...
216 MOVEM.L (SP)+, D0-D7/A0-A6 | restore all registers
...

This is a safe way, but not the most efficient one. Considering the code between
line 135 and 216, only registers D0, D1, D7, and A0 are modified by the interrupt
service routine. So it would be sufficient to save and restore only these registers.
However, the interrupt service routine calls other functions which may alter other
registers, and these need to be saved as well. In order to save only those registers
changed by the interrupt service routine and the functions it calls, one needs to
know which registers are altered by the functions generated by the compiler. For
some compilers, there is a convention such as “any function generated by the
compiler may alter registers D0 through D3 and A0 through A3 and leaves all
other registers intact”. The register preserving convention is usually documented
for a compiler in a chapter like “function calling conventions”. In case of gcc,
there is a file config/<machine>/<machine>.h in the directory where the compiler
sources are installed, where <machine> stands for the target for which the
compiler was configured. In our case, this would be the file config/m68k/m68k.h.
In this file, a macro CALL_USED_REGISTERS is defined, which marks those
registers with 1 that are changed by a function call. The first line refers to data
registers, the next line to address registers and the third line to floating point
registers.
// config/m68k/m68k.h
...
#define CALL_USED_REGISTERS \
{1, 1, 0, 0, 0, 0, 0, 0, \
1, 1, 0, 0, 0, 0, 0, 1, \
1, 1, 0, 0, 0, 0, 0, 0 }

That is, if the compiler is configured to use the file m68k.h, then registers D0, D1,
A0, A1, A7, and floating point registers FP0 and FP1 may be altered by function
calls generated by the compiler. If the compiler uses other registers, it saves and
restores them automatically. Although A7 (i.e. the SP) is altered, it is restored by
the function call mechanism. With this knowledge, one could safely write
1 | crt0.S
...
133 _duart_isr: |
7. Miscellaneous 127 128 7.4 Semaphores with time-out

7.4 Semaphores with time-out

So far, the state machine shown in Figure 7.1 is used for the state of a task.
STARTED

Start()
STARTED
P()
Terminate()
Error RUN BLKD
V()
Start()
P_Timeout()
P() Sleep()
Terminate() Time-out
Error RUN BLKD
V() V() or
SLEEP Time-out S_BLKD
Sleep() Time-out FAILED,
TERMINATED

SLEEP

FAILED,
TERMINATED FIGURE 7.2 Task State Machine with new State S_BLKD

The new state S_BLKD combines the properties of states SLEEP and BLKD by
FIGURE 7.1 Task State Machine returning the task to state RUN if either the resource represented by a semaphore
is available (the character is received in our example) or the time-out provided
with the call Semaphore::P_Timeout(unsigned int time) has expired. The task
Sometimes a combination of the states SLEEP and BLKD is required. One calling P_Timeout() must of course be able to determine whether the resource is
example is waiting for a character, but indicating a failure if the character is not available or time-out has occurred. That is, P_Timeout() will return e.g. an int
received within a certain period of time. With the present state machine, there are indicating the result rather than Semaphore::P(), which returns void. The new
several possibilities to achieve this, but none is perfect. We could, for instance, state can be implemented as follows, where the details are left as an exercise to
first Sleep() for the period and then Poll() to check if a character has arrived the reader. ??? willst Du die Lösung nicht verraten ???
during Sleep(). This would lead to bad performance, in particular if the period is
• The class Task gets two new data members int P_Timeout_Result and
long and if time-out rarely occurs. One could increase the performance by
Semaphore * P_Timeout_Semaphore.
performing Sleep() and Poll() in a loop with smaller intervals, but this would cost
extra processing time. Another alternative would be to use two additional tasks: • The class Semaphore is extended by a new member function int
one that is responsible for receiving characters, and the other for sleeping. Any of P_Timeout(unsigned long time). This function is similar to P() with the
these additional tasks would send an event to the task that is actually waiting for a following differences: If a resource is available, P_Timeout() returns 0
character or time-out, indicating that the character has been received or that time- indicating no time-out. Otherwise it sets the current task’s member
out has occurred. All this is significant effort for an otherwise simple problem. P_Timeout_Semaphore to the semaphore on which P_Timeout is
The best solution is to extend the task state machine by a new state S_BLKD, as performed, sets the current task’s TaskSleep to time, and blocks the task by
shown in Figure 7.2. setting both the BLKD and the SLEEP bits in the current task’s
TaskStatus. After the task has been unblocked by either a V() call or time-
out, it returns P_Timeout_Result of the current task.
7. Miscellaneous 129

A Appendices
• Semaphore::V() is modified so that it sets the P_Timeout_Result of a task
that is unblocked to 0, indicating no time-out. That task will then return 0 as A.1 Startup Code (crt0.S)
the result of its P_Timeout() function call. It also clears the SLEEP bit of 1 | crt0.S
the task that is unblocked. 2
3 #define ASSEMBLER
• If the sleep period of a task has expired (after label L_SLEEP_LP in 4
crt0.S), then the BLKD bit is examined besides clearing the SLEEP bit of 5 #include "Duart.hh"
6 #include "Task.hh"
the task. If it is set, i.e. if the task is in state S_BLKD, then this bit is 7 #include "Semaphore.hh"
cleared as well, the task is removed from the semaphore waiting chain 8 #include "System.config"
(using the P_Timeout_Semaphore member of the task) and 9 |
10 .global _null |
P_Timeout_Result is set to nonzero, indicating time-out. 11 .global _on_exit |
12 .global _reset |
13 .global _fatal |
After the semaphore class has been extended this way, the queue classes are 14 .global _deschedule |
extended accordingly, implementing member functions like Get_Timeout() and 15 .global _consider_ts |
Put_Timeout(). Since all these changes require considerable effort, they should 16 .global _return_from_exception|
17 .global _stop |
only be implemented when needed. As a matter of fact, we have implemented 18 .global _sdata |
quite complex applications without the need for time-outs in semaphores. 19 .global _idle_stack |
20 .global _IUS_top |
21 .global _sysTimeHi |
22 .global _sysTimeLo |
23 |
24 .text |
25 |
26 wLED_ON = wDUART_BCLR |
27 wLED_OFF = wDUART_BSET |
28 LED_GREEN = 0x80 |
29 LED_YELLOW = 0x40 |
30 LED_RED = 0x20 |
31 LED_ALL = 0xE0 |
32 |
33 |=======================================================================|
34 | VECTOR TABLE |
35 |=======================================================================|
36 | Vector
37 _null: BRA _reset | 0 initial SSP (end of RAM)
38 .LONG _reset | 1 initial PC
39 .LONG _fatal, _fatal | 2, 3 bus error, adress error
40 .LONG _fatal, _fatal | 4, 5 illegal instruction, divide/0
41 .LONG _fatal, _fatal | 6, 7 CHK, TRAPV instructions
42 .LONG _fatal, _fatal | 8, 9 privilege violation, trace
43 .LONG _fatal, _fatal | 10,11 Line A,F Emulators
44 |
45 .LONG _fatal,_fatal,_fatal | 12... (reserved)
46 .LONG _fatal,_fatal,_fatal | 15... (reserved)
47 .LONG _fatal,_fatal,_fatal | 18... (reserved)
48 .LONG _fatal,_fatal,_fatal | 21... (reserved)
49 |
50 .LONG _fatal | 24 spurious interrupt
51 .LONG _fatal | 25 level 1 autovector
52 .LONG _duart_isr | 26 level 2 autovector
53 .LONG _fatal | 27 level 3 autovector
54 .LONG _fatal, _fatal | 28,29 level 4,5 autovector
55 .LONG _fatal, _fatal | 30,31 level 6,7 autovector
56 |
57 .LONG _stop | 32 TRAP #0 vector
58 .LONG _deschedule | 33 TRAP #1 vector
A. Appendices 131 132 A.1 Startup Code (crt0.S)

59 .LONG _fatal | 34 TRAP #2 vector 121 JSR _Panic__2osPs | print stack frame
60 .LONG _Semaphore_P | 35 TRAP #3 vector 122 LEA 2(SP), SP | remove argument
61 .LONG _Semaphore_V | 36 TRAP #4 vector 123 MOVE.L (SP)+, A0 | restore old stack pointer
62 .LONG _Semaphore_Poll | 37 TRAP #5 vector 124 BRA _forever |
63 .LONG _fatal, _fatal | 38,39 TRAP #6, #7 vector 125 |
64 .LONG _fatal, _fatal | 40,41 TRAP #8, #9 vector 126 _on_exit: |
65 .LONG _fatal, _fatal | 42,43 TRAP #10,#11 vector 127 RTS |
66 .LONG _fatal | 44 TRAP #12 vector 128 |
67 .LONG _set_interrupt_mask | 45 TRAP #13 vector 129 |-----------------------------------------------------------------------|
68 .LONG _readByteRegister_HL | 46 TRAP #14 vector 130 | Duart interrupt |
69 .LONG _writeByteRegister | 47 TRAP #15 vector 131 |-----------------------------------------------------------------------|
70 | 132 |
71 .FILL 16, 4, -1 | 48 .. 63 (reserved) 133 _duart_isr: |
72 | 134 MOVE.B #LED_YELLOW, wLED_ON | yellow LED on
73 |=======================================================================| 135 MOVEM.L D0-D7/A0-A6, -(SP) | save all registers
74 | CODE | 136 MOVEM.L rDUART_ISR, D7 | get interrupt sources
75 |=======================================================================| 137 SWAP D7 |
76 | 138 MOVE.B D7, _duart_isreg |
77 |-----------------------------------------------------------------------| 139 |
78 | STARTUP CODE | 140 BTST #1, _duart_isreg | RxRDY_A ?
79 |-----------------------------------------------------------------------| 141 BEQ LnoRxA | no
80 | 142 MOVEM.L rDUART_RHR_A, D0 | get char received
81 _reset: | 143 MOVE.L D0, -(SP) |
82 MOVE.L #RAMend, SP | since we abuse vector 0 for BRA.W 144 PEA 1(SP) | address of char received
83 LEA _null, A0 | 145 PEA __8SerialIn$inbuf_0 | inbuf_0 object
84 MOVEC A0, VBR | MC68010++ only 146 JSR _PolledPut__t10Queue_Gsem1ZUcRCUc
85 | 147 LEA 12(SP), SP | cleanup stack
86 MOVE.B #0, wDUART_OPCR | all outputs via BSET/BCLR 148 LnoRxA: |
87 MOVE.B #LED_ALL, wLED_OFF | all LEDs off 149 |
88 | 150 BTST #5, _duart_isreg | RxRDY_B ?
89 MOVE.L #RAMbase, A1 | clear RAM... 151 BEQ LnoRxB | no
90 MOVE.L #RAMend, A2 | 152 MOVEM.L rDUART_RHR_B, D0 | get char received
91 L_CLR: CLR.L (A1)+ | 153 MOVE.L D0, -(SP) |
92 CMP.L A1, A2 | 154 PEA 1(SP) | address of char received
93 BHI L_CLR | 155 PEA __8SerialIn$inbuf_1 | inbuf_1 object
94 | relocate data section... 156 JSR _PolledPut__t10Queue_Gsem1ZUcRCUc
95 MOVE.L #_etext, D0 | end of text section 157 LEA 12(SP), SP | cleanup stack
96 ADD.L #0x00001FFF, D0 | align to next 2K boundary 158 LnoRxB: |
97 AND.L #0xFFFFE000, D0 | 159 |
98 MOVE.L D0, A0 | source (.data section in ROM) 160 BTST #0, _duart_isreg | TxRDY_A ?
99 MOVE.L #_sdata, A1 | destination (.data section in RAM) 161 BEQ LnoTxA | no
100 MOVE.L #_edata, A2 | end of .data section in RAM 162 LEA -2(SP), SP | space for next char
101 L_COPY: MOVE.L (A0)+, (A1)+ | copy data section from ROM to RAM 163 PEA 1(SP) | address of char received
102 CMP.L A1, A2 | 164 PEA __9SerialOut$outbuf_0 | outbuf_0 object
103 BHI L_COPY | 165 JSR _PolledGet__t10Queue_Psem1ZUcRUc
104 | 166 LEA 8(SP), SP | cleanup stack
105 MOVE.L #_SS_top, A7 | set up supervisor stack 167 MOVE.W (SP)+, D1 | next output char (valid if D0 = 0)
106 MOVE.L #_IUS_top, A0 | 168 TST.L D0 | char valid ?
107 MOVE A0, USP | set up user stack 169 BEQ Ld1i11 | yes
108 | 170 CLR.L __9SerialOut$TxEnabled_0| no, disable Tx
109 MOVE #0x0700, SR | user mode, no ints 171 MOVE.B #0x08, wDUART_CR_A | disable transmitter
110 JSR _main | 172 BRA LnoTxA |
111 | 173 Ld1i11: MOVE.B D1, wDUART_THR_A | write char (clears int)
112 _fatal: | 174 LnoTxA: |
113 MOVE.W #0x2700, SR | 175 |
114 MOVE.B #LED_RED, wLED_ON | red LED on 176 BTST #4, _duart_isreg | TxRDY_B ?
115 MOVE.B #0x04, wDUART_CR_B | enable transmitter 177 BEQ LnoTxB | no
116 MOVE.L SP, A0 | old stack pointer 178 LEA -2(SP), SP | space for next char
117 MOVE.L #RAMend, SP | 179 PEA 1(SP) | address of char received
118 _forever: | 180 PEA __9SerialOut$outbuf_1 | outbuf_1 object
119 MOVE.L A0, -(SP) | save old stack pointer 181 JSR _PolledGet__t10Queue_Psem1ZUcRUc
120 MOVE.L A0, -(SP) | push argument 182 LEA 8(SP), SP | cleanup stack
A. Appendices 133 134 A.1 Startup Code (crt0.S)

183 MOVE.W (SP)+, D1 | next output char (valid if D0 = 0) 245 | swap out current task by saving
184 TST.L D0 | char valid ? 246 | all user mode registers in TCB
185 BEQ Ld1i21 | yes 247 |---------------------------------------|
186 CLR.L __9SerialOut$TxEnabled_1| no, disable Tx 248 |
187 MOVE.B #0x08, wDUART_CR_B | disable transmitter 249 MOVE.L A6, -(SP) | save A6
188 BRA LnoTxB | 250 MOVE.L __4Task$currTask, A6 |
189 Ld1i21: MOVE.B D1, wDUART_THR_B | write char (clears int) 251 MOVEM.L D0-D7/A0-A5, Task_D0(A6)| store D0-D7 and A0-A5 in TCB
190 LnoTxB: | 252 MOVE.L (SP)+, Task_A6(A6) | store saved A6 in TCB
191 | 253 MOVE USP, A0 |
192 BTST #3, _duart_isreg | timer ? 254 MOVE.L A0, Task_USP(A6) | save USP from stack in TCB
193 BEQ LnoTim | no 255 MOVE.B 1(SP), Task_CCR(A6) | save CCR from stack in TCB
194 MOVEM.L rDUART_STOP, D1 | stop timer 256 MOVE.L 2(SP), Task_PC(A6) | save PC from stack in TCB
195 MOVEM.L rDUART_START, D1 | start timer 257 |
196 | 258 |---------------------------------------|
197 | increment system time 259 | find next task to run
198 ADD.L #10, _sysTimeLo | 10 milliseconds 260 | A2: marker for start of search
199 BCC.S Lsys_time_ok | 261 | A6: best candidate found
200 ADDQ.L #1, _sysTimeHi | 262 | D6: priority of task A6
201 Lsys_time_ok: | 263 | A0: next task to probe
202 | 264 | D0: priority of task A0
203 MOVE.L __4Task$currTask, D1 | 265 |---------------------------------------|
204 MOVE.L D1, A0 | 266 |
205 L_SLEEP_LP: | decrement sleep counters... 267 MOVE.L __4Task$currTask, A2 |
206 SUBQ.L #1, TaskSleepCount(A0) | 268 MOVE.L A2, A6 |
207 BNE L_NO_WAKEUP | 269 MOVEQ #0, D6 |
208 BCLR #3, TaskStatus(A0) | clear sleep state 270 TST.B TaskStatus(A6) | status = RUN ?
209 L_NO_WAKEUP: | 271 BNE L_PRIO_OK | no, run at least idle task
210 MOVE.L TaskNext(A0), A0 | 272 MOVE.W TaskPriority(A6), D6 |
211 CMP.L A0, D1 | 273 L_PRIO_OK: |
212 BNE L_SLEEP_LP | 274 MOVE.L TaskNext(A6), A0 | next probe
213 ST _consider_ts | request task switch anyway 275 BRA L_TSK_ENTRY |
214 LnoTim: | 276 L_TSK_LP: |
215 | 277 TST.B TaskStatus(A0) | status = RUN ?
216 MOVEM.L (SP)+, D0-D7/A0-A6 | restore all registers 278 BNE L_NEXT_TSK | no, skip
217 BRA _return_from_exception | 279 MOVEQ #0, D0 |
218 | 280 MOVE.W TaskPriority(A0), D0 |
219 |-----------------------------------------------------------------------| 281 CMP.L D0, D6 | D6 higher priority ?
220 | TRAP #0 (STOP PROCESSOR) | 282 BHI L_NEXT_TSK | yes, skip
221 |-----------------------------------------------------------------------| 283 MOVE.L A0, A6 |
222 | 284 MOVE.L D0, D6 |
223 _stop: | 285 ADDQ.L #1, D6 | prefer this if equal priority
224 MOVE.B #LED_YELLOW, wLED_OFF | yellow LED off 286 L_NEXT_TSK: |
225 STOP #0x2000 | 287 MOVE.L TaskNext(A0), A0 | next probe
226 BRA _return_from_exception | check for task switch 288 L_TSK_ENTRY: |
227 | 289 CMP.L A0, A2 |
228 |-----------------------------------------------------------------------| 290 BNE L_TSK_LP |
229 | TRAP #1 (SCHEDULER) | 291 |
230 |-----------------------------------------------------------------------| 292 |---------------------------------------|
231 | 293 | next task found (A6)
232 _deschedule: | 294 | swap in next task by restoring
233 ST _consider_ts | request task switch 295 | all user mode registers in TCB
234 | 296 |---------------------------------------|
235 _return_from_exception: | check for task switch 297 |
236 OR.W #0x0700, SR | disable interrupts 298 MOVE.L A6, __4Task$currTask | task found.
237 MOVE.W (SP), -(SP) | get status register before exception 299 MOVE.L Task_PC(A6), 2(SP) | restore PC on stack
238 AND.W #0x2700, (SP)+ | supervisor mode or ints disabled ? 300 MOVE.B Task_CCR(A6), 1(SP) | restore CCR on stack
239 BNE L_task_switch_done | yes dont switch task 301 MOVE.L Task_USP(A6), A0 |
240 TST.B _consider_ts | task switch requested ? 302 MOVE A0, USP | restore USP
241 BEQ L_task_switch_done | no 303 MOVEM.L Task_D0(A6), D0-D7/A0-A6| restore D0-D7, A0-A5 (56 bytes)
242 CLR.B _consider_ts | reset task switch request 304 L_task_switch_done: |
243 | 305 RTE |
244 |---------------------------------------| 306 |
A. Appendices 135 136 A.1 Startup Code (crt0.S)

307 |-----------------------------------------------------------------------| 368 OR #0x700, SR | disable interrupts


308 | TRAP #3 (Semaphore P operation) | 369 MOVEQ #1, D0 | assume failure
309 |-----------------------------------------------------------------------| 370 TST.L SemaCount(A0) | get count
310 | 371 BLE _return_from_exception | failure
311 _Semaphore_P: | A0 -> Semaphore 372 SUBQ.L #1, SemaCount(A0) |
312 OR #0x0700, SR | disable interrupts 373 MOVEQ #0, D0 | success
313 SUBQ.L #1, SemaCount(A0) | count down resources 374 BRA _return_from_exception | check for task switch
314 BGE _return_from_exception | if resource available 375 |
315 ST _consider_ts | request task switch 376 |-----------------------------------------------------------------------|
316 MOVE.L SemaNextTask(A0), D0 | get waiting task (if any) 377 | TRAP #13 (SET INTERRUPT MASK) |
317 BNE.S Lsp_append | got a waiting task 378 |-----------------------------------------------------------------------|
318 MOVE.L __4Task$currTask, D0 | get current Task 379 |
319 MOVE.L D0, SemaNextTask(A0) | store as first waiting 380 _set_interrupt_mask: |
320 MOVE.L D0, A0 | 381 MOVEQ #7, D0 |
321 BSET #0, TaskStatus(A0) | block current task 382 AND.B (SP), D0 | get old status register
322 CLR.L TaskNextWaiting(A0) | say this is last waiting 383 AND.B #7, D1 | interrupt bits only
323 BRA _return_from_exception | done 384 AND.B #0xF8, (SP) | clear interrupt bits
324 | 385 OR.B D1, (SP) | set interrupt bits from D1
325 Lsp_append: | goto end of waiting list 386 BRA _return_from_exception | check for task switch
326 MOVE.L D0, A0 | 387 |
327 MOVE.L TaskNextWaiting(A0), D0 | get next waiting (if any) 388 |-----------------------------------------------------------------------|
328 BNE.S Lsp_append | if not last waiting 389 | TRAP #14 (READ DUART REGISTER) |
329 | 390 |-----------------------------------------------------------------------|
330 MOVE.L __4Task$currTask, D0 | get current task 391 |
331 MOVE.L D0, TaskNextWaiting(A0) | store as last waiting 392 _readByteRegister_HL: | (emulated)
332 MOVE.L D0, A0 | 393 MOVEM.L (A0), D0 | .L to force dummy cycle
333 BSET #0, TaskStatus(A0) | block current task 394 SWAP D0 | D23..D16 -> D7..D0
334 CLR.L TaskNextWaiting(A0) | say this is last waiting 395 BRA _return_from_exception | check for task switch
335 BRA _return_from_exception | done 396 |
336 | 397 |-----------------------------------------------------------------------|
337 |-----------------------------------------------------------------------| 398 | TRAP #15 (WRITE HARDWARE REGISTER) |
338 | TRAP #4 (Semaphore V operation) | 399 |-----------------------------------------------------------------------|
339 |-----------------------------------------------------------------------| 400 |
340 | 401 _writeByteRegister: | (emulated)
341 _Semaphore_V: | A0 -> Semaphore 402 MOVE.B D0, (A0) |
342 OR #0x0700, SR | disable interrupts 403 BRA _return_from_exception | check for task switch
343 ADDQ.L #1, SemaCount(A0) | 404 |
344 BLE.S Lsv_unblock | unblock waiting task 405 |=======================================================================|
345 CLR.L SemaNextTask(A0) | 406 | DATA |
346 BRA _return_from_exception | done 407 |=======================================================================|
347 | 408 |
348 Lsv_unblock: | 409 .data |
349 EXG D0, A1 | 410 |
350 MOVE.L SemaNextTask(A0), A1 | get next waiting task 411 _sdata: .LONG 0 |
351 MOVE.L TaskNextWaiting(A1), SemaNextTask(A0) 412 _sysTimeHi: .LONG 0 | system time high
352 MOVE.L A1, A0 | 413 _sysTimeLo: .LONG 0 | system time low
353 EXG D0, A1 | 414 _super_stack: .FILL 512, 1, 'S' | supervisor stack
354 BCLR #0, TaskStatus(A0) | unblock the blocked task 415 _SS_top: | top of supervisor stack
355 CLR.L TaskNextWaiting(A0) | just in case 416 _idle_stack: .FILL 512, 1, 'U' | idle task user stack
356 MOVE.W TaskPriority(A0), D0 | get priority of unblocked Task 417 _IUS_top: | top of idle task user stack
357 MOVE.L __4Task$currTask, A0 | get current Task 418 _consider_ts: .BYTE 0 | true if task switch need be checked
358 CMP.W TaskPriority(A0), D0 | current prio >= unblocked prio ? 419 _duart_isreg: .BYTE 0 |
359 BLS _return_from_exception | yes, done 420 |
360 ST _consider_ts | no, request task switch 421 .ALIGN 2 |
361 BRA _return_from_exception | done 422 .END
362 |
363 |-----------------------------------------------------------------------|
364 | TRAP #5 (Semaphore Poll operation)
|
365 |-----------------------------------------------------------------------|
366 |
367 _Semaphore_Poll: | A0 -> Semaphore
A. Appendices 137 138 A.2 Task.hh

A.2 Task.hh 61
62 static const char * const MyName()
1 #ifdef ASSEMBLER 63 { return currTask->name; };
2 64
3 #define TaskNext 65 static unsigned short MyPriority()
4 #define TaskNextWaiting 0x04 66 { return currTask->priority; };
5 #define Task_D0 0x08 67
6 #define Task_A6 0x40 68 static Task * Current()
7 #define Task_USP 0x44 69 { return currTask; };
8 #define Task_PC 0x48 70
9 #define TaskSleepCount 0x4C 71 static void Dsched()
10 #define TaskHitCount 0x50 72 { asm("TRAP #1"); };
11 #define TaskPriority 0x54 73
12 #define Task_CCR 0x56 74 static int SchedulerRunning() { return SchedulerStarted; };
13 #define TaskStatus 0x57 75 static unsigned int Sleep(unsigned int);
14 76 static void Terminate(int);
15 #else 77
16 78 const char * const Name() const
17 #ifndef __TASK_HH_DEFINED__ 79 { return name; };
18 #define __TASK_HH_DEFINED__ 80
19 #include "Semaphore.hh" 81 unsigned short Priority() const
20 #include "Message.hh" 82 { return priority; };
21 #include "Queue.hh" 83
22 84 void setPriority(unsigned short newPriority)
23 void setupApplicationTasks(); 85 { priority = newPriority; };
24 86
25 class Task 87 Task * Next() const
26 { 88 { return next; };
27 friend class Monitor; 89
28 private: 90 unsigned char Status() const
29 // Make sure the following locations match the assembler defs above !!! 91 { return TaskStatus; };
30 Task * next; // 0x00 92
31 Task * nextWaiting; // 0x04 93 void Start()
32 unsigned long Task_D0, Task_D1, Task_D2, Task_D3; // 0x08.. 94 { TaskStatus &= ~STARTED; };
33 unsigned long Task_D4, Task_D5, Task_D6, Task_D7; // 0x18.. 95
34 unsigned long Task_A0, Task_A1, Task_A2, Task_A3; // 0x28.. 96 void SendMessage(Message & msg)
35 unsigned long Task_A4, Task_A5, Task_A6; // 0x38.. 97 { msg.Sender = currTask; msgQ.Put(msg); };
36 unsigned long * Task_USP; // 0x44.. 98
37 void (*Task_PC)(); // 0x48 99 int checkStacks();
38 unsigned long TaskSleep; // 0x4C 100 unsigned int userStackUsed() const;
39 unsigned long TaskHitCount; // 0x50 101
40 unsigned short priority; // 0x54 102 unsigned int userStackBase() const
41 unsigned char Task_CCR; // 0x56 103 { return (unsigned int)Stack; };
42 unsigned char TaskStatus; // 0x57 104
43 // End of definitions also used in assembler 105 unsigned int userStackSize() const
44 106 { return US_size; };
45 friend main(); 107
46 friend class Semaphore; 108 enum { RUN = 0x00,
47 109 BLKD = 0x01,
48 public: 110 STARTED = 0x02,
49 Task( void (* main)(), 111 TERMINATED = 0x04,
50 unsigned long userStackSize, 112 SLEEP = 0x08,
51 unsigned short queueSize, 113 FAILED = 0x10,
52 unsigned short priority, 114 };
53 const char * taskName 115
54 ); 116 static Task * TaskIDs[];
55 117 private:
56 static void GetMessage(Message & msg) 118 Task();
57 { currTask->msgQ.Get(msg); }; 119 ~Task();
58 120
59 static int PolledGetMessage(Message & msg) 121 void clearHitCount()
60 { return currTask->msgQ.PolledGet(msg); }; 122 { TaskHitCount = 0; };
A. Appendices 139 140 A.3 Task.cc

123 A.3 Task.cc


124 unsigned int HitCount() const
125 { return TaskHitCount; }; 1 // Task.cc
126 2
127 3 #include "Task.hh"
128 enum { userStackMagic = 'U', superStackMagic = 'S' }; 4 #include "TaskId.hh"
129 5 #include "System.config"
130 static void Terminate_0(); 6 #include "os.hh"
131 static int SchedulerStarted; 7 #include "SerialOut.hh"
132 static Task * currTask; 8
133 9 //-------------------------------------------------------------------------
134 char * Stack; // user stack base 10 int Task::SchedulerStarted = 0;
135 const unsigned long US_size; // user stack size 11
136 const char * name; 12 Task * Task::currTask = 0;
137 int ExitCode; 13 Task * Task::TaskIDs[TASKID_COUNT];
138 Queue_Gsem_Psem<Message> msgQ; 14
139 }; 15 //=========================================================================
140 16 extern char idle_stack;
141 #endif __TASK_HH_DEFINED__ 17 extern char IUS_top;
142 18
143 #endif ASSEMBLER 19 Task::Task()
20 : US_size(&IUS_top - &idle_stack),
21 priority(0),
22 name("Idle Task"),
23 TaskStatus(RUN),
24 next(this),
25 nextWaiting(0),
26 Stack(&idle_stack),
27 msgQ(1),
28 ExitCode(0)
29 {
30 TaskIDs[TASKID_IDLE] = this;
31 }
32 //-------------------------------------------------------------------------
33 Task::Task(void (*main)(),
34 unsigned long usz,
35 unsigned short qsz,
36 unsigned short prio,
37 const char * taskName
38 )
39 : US_size(usz),
40 priority(prio),
41 name(taskName),
42 TaskStatus(STARTED),
43 nextWaiting(0),
44 msgQ(qsz),
45 ExitCode(0)
46 {
47 int i;
48
49 Stack = new char[US_size]; // allocate stack
50
51 for (i = 0; i < US_size;) Stack[i++] = userStackMagic;
52
53 Task_A0 = 0xAAAA5555; Task_A1 = 0xAAAA4444;
54 Task_A2 = 0xAAAA3333; Task_A3 = 0xAAAA2222;
55 Task_A4 = 0xAAAA1111; Task_A5 = 0xAAAA0000;
56 Task_A6 = 0xAAAA6666;
57 Task_D0 = 0xDDDD7777; Task_D1 = 0xDDDD6666;
58 Task_D2 = 0xDDDD5555; Task_D3 = 0xDDDD4444;
59 Task_D4 = 0xDDDD3333; Task_D5 = 0xDDDD2222;
60 Task_D6 = 0xDDDD1111; Task_D7 = 0xDDDD0000;
A. Appendices 141 142 A.3 Task.cc

61 Task_PC = main; 122 unsigned int Task::Sleep(unsigned int ticks)


62 Task_CCR = 0x0000; 123 {
63 124 if (!SchedulerStarted) return 0;
64 Task_USP = (unsigned long *)(Stack + US_size); 125 if (ticks == 0) ticks++;
65 *--Task_USP = (unsigned long)Terminate_0; 126
66 127 {
67 if (!currTask) 128 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS);
68 currTask = new Task(); 129 currTask->TaskStatus |= SLEEP;
69 130 currTask->TaskSleep = ticks;
70 { 131 os::set_INT_MASK(old_INT_MASK);
71 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS); 132 }
72 next = currTask->next; 133 Dsched();
73 currTask->next = this; 134 return ticks;
74 os::set_INT_MASK(old_INT_MASK); 135 }
75 } 136 //=========================================================================
76 } 137 unsigned int Task::userStackUsed() const
77 //========================================================================= 138 {
78 void main() 139 for (int i = 0; Stack[i] == userStackMagic; i++) /* empty */ ;
79 { 140 return US_size - i;
80 if (Task::SchedulerStarted) return -1; 141 }
81 142 //=========================================================================
82 for (int i = 0; i < TASKID_COUNT; i++) Task::TaskIDs[i] = 0;
83 setupApplicationTasks();
84
85 for (Task * t = Task::currTask->next; t != Task::currTask; t = t->next)
86 t->TaskStatus &= ~Task::STARTED;
87
88 Task::SchedulerStarted = 1;
89 os::init(os::Interrupt_IO); // switch on interrupt system
90 os::set_INT_MASK(os::ALL_INTS);
91
92 Task::Dsched();
93
94 for (;;) os::Stop();
95
96 return 0; /* not reached */
97 }
98 //=========================================================================
99 void Task::Terminate_0()
100 {
101 Terminate(0);
102 }
103 //=========================================================================
104 void Task::Terminate(int ex)
105 {
106 {
107 SerialOut so(ErrorOut);
108 so.Print("\n%s Terminated", currTask->name);
109 }
110 currTask->ExitCode = ex;
111 currTask->TaskStatus |= TERMINATED;
112 Dsched();
113 }
114 //=========================================================================
115 int Task::checkStacks()
116 {
117 if ((char *)Task_USP < Stack ) return 1;
118 if ((char *)Task_USP >= Stack + US_size) return 2;
119 return 0;
120 }
121 //
=============================================================================
A. Appendices 143 144 A.4 os.hh

A.4 os.hh 60 MOVE.L D0, %0" : "=g"(result) : "g"(reg) : "d0", "a0"


61 );
62 return result;
1 /* os.hh */ 63 };
2 64
3 #include "Channels.hh" 65 static void writeRegister(HW_ADDRESS reg, int val);
4 66
5 #ifndef __OS_HH_DEFINED__ 67 public:
6 #define __OS_HH_DEFINED__ 68 enum INT_MASK {
7 69 NO_INTS = 0x07,
8 extern "C" void * sbrk(unsigned long); 70 ALL_INTS = 0x00
9 template <class Type> class RingBuffer; 71 };
10 template <class Type> class Queue; 72
11 template <class Type> class Queue_Gsem; 73 static INT_MASK set_INT_MASK(INT_MASK new_INT_MASK)
12 template <class Type> class Queue_Psem; 74 {
13 template <class Type> class Queue_Gsem_Psem; 75 INT_MASK old_INT_MASK;
14 class Semaphore; 76
15 77 asm volatile (
16 typedef unsigned long HW_ADDRESS; 78 "MOVE.B %1, D1
17 79 TRAP #13
18 class os 80 MOVE.B D0, %0"
19 { 81 : "=g"(old_INT_MASK)
20 public: 82 : "g"(new_INT_MASK)
21 friend class Monitor; 83 : "d0", "d1"
22 friend class SerialIn; 84 );
23 friend class SerialOut; 85
24 friend void * sbrk(unsigned long); 86 return old_INT_MASK;
25 87 };
26 static void Stop(); // for Idle Task only 88 };
27 89
28 static unsigned long long getSystemTime(); // system time in ms 90 #endif __OS_HH_DEFINED__
29 91
30 enum INIT_LEVEL {
31 Not_Initialized = 0,
32 Polled_IO = 1,
33 Interrupt_IO = 2
34 };
35
36 static void init(INIT_LEVEL new_level);
37 static int setBaudRate(Channel, int);
38 static int setSerialMode(Channel, int databits, int parity);
39 static INIT_LEVEL initLevel() { return init_level; };
40 static void * top_of_RAM() { return free_RAM; };
41
42 private:
43 os(); // dont instantiate
44
45 static char * free_RAM;
46
47 static void Panic(short * SP);
48
49 static INIT_LEVEL init_level;
50 static void initDuart(HW_ADDRESS base, int baudA, int baudB);
51 static void initChannel(HW_ADDRESS base, int baud);
52 static void resetChannel(HW_ADDRESS base);
53
54 static unsigned int readDuartRegister(HW_ADDRESS reg)
55 {
56 int result;
57 asm volatile (
58 "MOVE.L %1, A0
59 TRAP #14
A. Appendices 145 146 A.5 os.cc

A.5 os.cc 61
62 //=========================================================================
1 /* os.cc */ 63 //
2 #include "System.config" 64 // crt0.S interface functions...
3 #include "os.hh" 65 //
4 #include "Task.hh" 66
5 #include "Semaphore.hh" 67 void os::Stop()
6 #include "SerialOut.hh" 68 {
7 #include "Channels.hh" 69 asm("TRAP #0");
8 #include "Duart.hh" 70 }
9 71 //-------------------------------------------------------------------------
10 os::INIT_LEVEL os::init_level = Not_Initialized; 72 void os::writeRegister(HW_ADDRESS reg, int v)
11 73 {
12 //========================================================================= 74 asm("MOVE.L %0,A0; MOVE.L %1,D0; TRAP #15" : : "g"(reg), "g"(v) :
13 // "d0","a0");
14 // functions required by libgcc2.a... 75 }
15 // 76 //-------------------------------------------------------------------------
16 77 // return time since power on (or reload) in milliseconds
17 extern int edata; 78 //
18 char * os::free_RAM = (char *)&edata; 79
19 80 extern volatile unsigned long sysTimeLo; // in crt0.S
20 //------------------------------------------------------------------------- 81 extern volatile unsigned long sysTimeHi; // in crt0.S
21 extern "C" void * sbrk(unsigned long size) 82
22 { 83 unsigned long long os::getSystemTime()
23 void * ret = os::free_RAM; 84 {
24 85 for (;;)
25 os::free_RAM += size; 86 {
26 87 unsigned long sys_high_1 = sysTimeHi;
27 if (os::free_RAM > *(char **)0) // out of memory 88 unsigned long sys_low = sysTimeLo;
28 { 89 unsigned long sys_high_2 = sysTimeHi;
29 os::free_RAM -= size; 90
30 ret = (void *) -1; 91 // sys_low overflows every 49.86 days. If this function is
31 } 92 // hit by that event (very unlikely) then it may be that
32 93 // sys_high_1 != sys_high_2. If so, we repeat reading
33 return ret; 94 // the system time.
34 } 95 if (sys_high_1 != sys_high_2) continue;
35 //------------------------------------------------------------------------- 96
36 extern "C" void * malloc(unsigned long size) 97 unsigned long long ret = sys_high_1;
37 { 98 ret <<= 32;
38 void * ret = sbrk((size+3) & 0xFFFFFFFC); 99 return ret + sys_low;
39 100 }
40 if (ret == (void *)-1) return 0; 101 }
41 return ret; 102 //-------------------------------------------------------------------------
42 } 103 // print stack frame in case of fatal errors
43 104 //
44 //------------------------------------------------------------------------- 105 void os::Panic(short * SP)
45 extern "C" void free(void *) 106 {
46 { 107 SerialOut so(SERIAL_0_POLLED);
47 } 108 int i;
48 //------------------------------------------------------------------------- 109
49 extern "C" void write(int, const char *text, int len) 110 so.Print("\n\n======================================");
50 { 111 so.Print("\nFATAL ERROR STACK DUMP: SP=%8X", SP);
51 SerialOut so(SERIAL_1); 112 so.Print("\n======================================");
52 so.Print(text, len); 113 // for (i = -5; i < 0; i++)
53 } 114 // so.Print("\n[SP - 0x%2X] : %4X" , -2*i, SP[i] & 0xFFFF);
54 //------------------------------------------------------------------------- 115 so.Print("\n[SP + 0x00] : %4X (SR)" , SP[0] & 0xFFFF);
55 extern "C" void _exit(int ex) 116 so.Print("\n[SP + 0x02] : %4X%4X (PC)" , SP[1] & 0xFFFF, SP[2] & 0xFFFF);
56 { 117 so.Print("\n[SP + 0x06] : %4X (FType/Vector)" , SP[3] & 0xFFFF);
57 Task::Terminate(ex); 118 for (i = 4; i < 10; i++)
58 /* not reached */ 119 so.Print("\n[SP + 0x%2X] : %4X" , 2*i, SP[i] & 0xFFFF);
59 for (;;); 120 so.Print("\n======================================\n");
60 } 121 }
A. Appendices 147 148 A.5 os.cc

122 183 writeRegister(cr, CR_TxENA); // enable transmitter


123 //========================================================================= 184 writeRegister(cr, CR_RxENA); // enable receiver
124 // 185 }
125 // hardware initialization functions... 186 //-------------------------------------------------------------------------
126 // 187 int os::setSerialMode(Channel ch, int databits, int parity)
127 188 {
128 void os::init(INIT_LEVEL iLevel) 189 int mr1 = MR1_DEFAULT & ~(MR1_P_MASK | MR1_BITS_mask);
129 { 190
130 enum { green = 1<<7 }; // green LED, write to BCLR turns LED on 191 switch(databits)
131 192 {
132 if (init_level < Polled_IO) 193 case 5: mr1 |= MR1_BITS_5; break;
133 { 194 case 6: mr1 |= MR1_BITS_6; break;
134 initDuart(DUART, CSR_9600, CSR_9600); 195 case 7: mr1 |= MR1_BITS_7; break;
135 init_level = Polled_IO; 196 case 8: mr1 |= MR1_BITS_8; break;
136 } 197 default: return -1;
137 198 }
138 if (iLevel == Interrupt_IO && init_level < Interrupt_IO) 199
139 { 200 switch(parity)
140 readDuartRegister (rDUART_STOP); // stop timer 201 {
141 writeRegister(xDUART_CTUR, CTUR_DEFAULT); // set CTUR 202 case 0: mr1 |= MR1_P_EVEN ; break;
142 writeRegister(xDUART_CTLR, CTLR_DEFAULT); // set CTLR 203 case 1: mr1 |= MR1_P_ODD ; break;
143 readDuartRegister(rDUART_START); // start timer 204 case 2: mr1 |= MR1_P_LOW ; break;
144 205 case 3: mr1 |= MR1_P_HIGH ; break;
145 writeRegister(wDUART_IMR, INT_DEFAULT); 206 case 4: mr1 |= MR1_P_NONE ; break;
146 init_level = Interrupt_IO; 207 default: return -1;
147 } 208 }
148 } 209
149 //------------------------------------------------------------------------- 210 switch(ch)
---- 211 {
150 void 212 case SERIAL_0:
151 os::initDuart(HW_ADDRESS base, int baudA, int baudB) 213 writeRegister(wDUART_CR_A, CR_MR1); // select MR1
152 { 214 writeRegister(xDUART_MR_A, mr1); // set MR1
153 // setup outputs 215 return 0;
154 writeRegister((HW_ADDRESS)(base + w_OPCR), OPCR_DEFAULT); 216
155 217 case SERIAL_1:
156 resetChannel(base + _A); 218 writeRegister(wDUART_CR_B, CR_MR1); // select MR1
157 resetChannel(base + _B); 219 writeRegister(xDUART_MR_B, mr1); // set MR1
158 220 return 0;
159 writeRegister(base + w_ACR, ACR_DEFAULT); 221 }
160 222
161 initChannel(base + _A, baudA); 223 return -1;
162 initChannel(base + _B, baudB); 224 }
163 } 225 //-------------------------------------------------------------------------
164 //------------------------------------------------------------------------- 226 int os::setBaudRate(Channel ch, int baud)
165 void os::resetChannel(HW_ADDRESS channel_base) 227 {
166 { 228 int csr;
167 const HW_ADDRESS cr = channel_base + w_CR; 229
168 230 switch(baud)
169 writeRegister(cr, CR_RxRESET); // reset receiver 231 {
170 writeRegister(cr, CR_TxRESET); // reset transmitter 232 case 38400: if ( ACR_DEFAULT & ACR_BRG_1) return -1;
171 } 233 csr = CSR_38400; break;
172 //------------------------------------------------------------------------- 234 case 19200: if (~ACR_DEFAULT & ACR_BRG_1) return -1;
173 void os::initChannel(HW_ADDRESS channel_base, int baud) 235 csr = CSR_19200; break;
174 { 236 case 9600: csr = CSR_9600; break;
175 const HW_ADDRESS mr = channel_base + x_MR; 237 case 4800: csr = CSR_4800; break;
176 const HW_ADDRESS cr = channel_base + w_CR; 238 case 2400: csr = CSR_2400; break;
177 const HW_ADDRESS csr = channel_base + w_CSR; 239 case 1200: csr = CSR_1200; break;
178 240 case 600: csr = CSR_600; break;
179 writeRegister(cr, CR_MR1); // select MR1 241 default: return -1;
180 writeRegister(mr, MR1_DEFAULT); // set MR1 242 }
181 writeRegister(mr, MR2_DEFAULT); // set MR2 243
182 writeRegister(csr, baud); // set baud rate 244 switch(ch)
A. Appendices 149 150 A.6 Semaphore.hh

245 { A.6 Semaphore.hh


246 case SERIAL_0: writeRegister(wDUART_CSR_A, csr); return 0;
247 case SERIAL_1: writeRegister(wDUART_CSR_B, csr); return 0; 1 #ifdef ASSEMBLER
248 } 2 #define SemaCount
249 return -1; 3 #define SemaNextTask 4
250 } 4 #else !ASSEMBLER
5 #ifndef __SEMAPHORE_HH_DEFINED__
6 #define __SEMAPHORE_HH_DEFINED__
7
8 class Task;
9
10 class Semaphore
11 {
12 public:
13 Semaphore() : count(1), nextTask(0) {};
14 Semaphore(int cnt) : count(cnt), nextTask(0) {};
15 void P() {
16 asm volatile ("MOVE.L %0, A0
17 TRAP #3" : : "g"(this) : "d0", "a0");
18 };
19 void V() {
20 asm volatile ("MOVE.L %0, A0
21 TRAP #4" : : "g"(this) : "d0", "a0");
22 };
23 int Poll() {
24 int r;
25
26 asm volatile ("MOVE.L %1, A0
27 TRAP #5
28 MOVE.L D0, %0"
29 : "=g"(r) : "g"(this) : "d0", "a0");
30 return r;
31 };
32 private:
33 long count;
34 Task * nextTask;
35 };
36 #endif __SEMAPHORE_HH_DEFINED__
37 #endif ASSEMBLER
38
A. Appendices 151 152 A.7 Queue.hh

A.7 Queue.hh 61 {
62 public:
1 // Queue.hh 63 Queue_Gsem(unsigned int sz)
2 64 : RingBuffer<Type>(sz), overflow(0), GetSemaphore(0)
3 #ifndef __QUEUE_HH_DEFINED__ 65 {};
4 #define __QUEUE_HH_DEFINED__ 66
5 67 unsigned int getOverflowCount() const { return overflow; };
6 #include "os.hh" 68 void clearOverflowCounter() { overflow = 0; };
7 #include "Semaphore.hh" 69
8 70 int PolledGet(Type & dest);
9 #pragma interface 71 int PolledPut(const Type & dest);
10 72 void Get(Type & dest);
11 //------------------------------------------------------------------------- 73
12 template <class Type> class RingBuffer 74 private:
13 { 75 Semaphore GetSemaphore;
14 public: 76 unsigned int overflow;
15 RingBuffer(unsigned int Size); 77 };
16 ~RingBuffer(); 78 //-------------------------------------------------------------------------
17 79 template <class Type> class Queue_Psem : public RingBuffer<Type>
18 int IsEmpty() const { return (count) ? 0 : -1; }; 80 {
19 int IsFull() const { return (count < size) ? 0 : -1; }; 81 public:
20 82 Queue_Psem(unsigned int sz)
21 int Peek(Type & dest) const; 83 : RingBuffer<Type>(sz),
22 84 PutSemaphore(sz),
23 protected: 85 underflow(0)
24 enum { QUEUE_OK = 0, QUEUE_FAIL = -1 }; 86 {};
25 87
26 virtual int PolledGet(Type & dest) = 0; 88 unsigned int getUnderflowCount() const { return underflow; };
27 virtual int PolledPut(const Type & dest) = 0; 89 void clearUnderflowCounter() { underflow = 0; };
28 inline void GetItem(Type & source); 90
29 inline void PutItem(const Type & src); 91 int PolledGet(Type & dest);
30 92 int PolledPut(const Type & dest);
31 unsigned int size; 93 void Put(const Type & dest);
32 unsigned int count; 94
33 95 private:
34 private: 96 unsigned int underflow;
35 Type * data; 97 Semaphore PutSemaphore;
36 unsigned int get; 98 };
37 unsigned int put; 99 //-------------------------------------------------------------------------
38 }; 100 template <class Type> class Queue_Gsem_Psem : public RingBuffer<Type>
39 //------------------------------------------------------------------------- 101 {
40 template <class Type> class Queue : public RingBuffer<Type> 102 public:
41 { 103 Queue_Gsem_Psem(unsigned int sz)
42 public: 104 : RingBuffer<Type>(sz), PutSemaphore(sz), GetSemaphore(0)
43 Queue(unsigned int sz) 105 {};
44 : RingBuffer<Type>(sz), overflow(0), underflow(0) 106
45 {}; 107 int PolledGet(Type & dest);
46 108 int PolledPut(const Type & dest);
47 unsigned int getUnderflowCount() const { return underflow; }; 109 void Get(Type & dest);
48 void clearUnderflowCounter() { underflow = 0; }; 110 void Put(const Type & dest);
49 unsigned int getOverflowCount() const { return overflow; }; 111
50 void clearOverflowCounter() { overflow = 0; }; 112 private:
51 113 Semaphore GetSemaphore;
52 int PolledGet(Type & dest); 114 Semaphore PutSemaphore;
53 int PolledPut(const Type & dest); 115 };
54 116 //-------------------------------------------------------------------------
55 private: 117 #endif __QUEUE_HH_DEFINED__
56 unsigned int underflow;
57 unsigned int overflow;
58 };
59 //-------------------------------------------------------------------------
60 template <class Type> class Queue_Gsem : public RingBuffer<Type>
A. Appendices 153 154 A.8 Queue.cc

A.8 Queue.cc 61 {
62 int ret;
1 // Queue.cc 63
2 64 {
3 #pragma implementation "Queue.hh" 65 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS);
4 66 if (count < size) { PutItem(dest); ret = QUEUE_OK; }
5 #include "Queue.hh" 67 else { overflow++; ret = QUEUE_FAIL; }
6 #include "Message.hh" 68 os::set_INT_MASK(old_INT_MASK);
7 69 }
8 //========================================================================= 70 return ret;
9 template <class Type> RingBuffer<Type>::RingBuffer(unsigned int Size) 71 }
10 : size(Size), get(0), put(0), count(0) 72 //=========================================================================
11 73 template <class Type> void Queue_Gsem<Type>::Get(Type & dest)
12 { 74 {
13 data = new Type[size]; 75 GetSemaphore.P();
14 } 76 {
15 //------------------------------------------------------------------------- 77 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS);
16 template <class Type> RingBuffer<Type>::~RingBuffer() 78 GetItem(dest);
17 { 79 os::set_INT_MASK(old_INT_MASK);
18 delete [] data; 80 }
19 } 81 }
20 //------------------------------------------------------------------------- 82 //-------------------------------------------------------------------------
21 template <class Type> int RingBuffer<Type>::Peek(Type & dest) const 83 template <class Type> int Queue_Gsem<Type>::PolledGet(Type & dest)
22 { 84 {
23 int ret = QUEUE_FAIL; 85 if (GetSemaphore.Poll()) return QUEUE_FAIL;
24 86 {
25 { 87 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS);
26 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS); 88 GetItem(dest);
27 if (count) { dest = data[get]; ret = QUEUE_OK; } 89 os::set_INT_MASK(old_INT_MASK);
28 os::set_INT_MASK(old_INT_MASK); 90 }
29 } 91 return QUEUE_OK;
30 return ret; 92 }
31 } 93 //-------------------------------------------------------------------------
32 //------------------------------------------------------------------------- 94 template <class Type> int Queue_Gsem<Type>::PolledPut(const Type & dest)
33 template <class Type> inline void RingBuffer<Type>::GetItem(Type & dest) 95 {
34 { 96 int ret = QUEUE_FAIL;
35 dest = data[get++]; 97
36 if (get >= size) get = 0; 98 {
37 count--; 99 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS);
38 } 100 if (count < size)
39 //------------------------------------------------------------------------- 101 {
40 template <class Type> inline void RingBuffer<Type>::PutItem(const Type &src) 102 PutItem(dest);
41 { 103 GetSemaphore.V();
42 data[put++] = src; 104 ret = QUEUE_OK;
43 if (put >= size) put = 0; 105 }
44 count++; 106 os::set_INT_MASK(old_INT_MASK);
45 } 107 }
46 //========================================================================= 108 return ret;
47 template <class Type> int Queue<Type>::PolledGet(Type & dest) 109 }
48 { 110 //=========================================================================
49 int ret; 111 template <class Type> int Queue_Psem<Type>::PolledGet(Type & dest)
50 112 {
51 { 113 int ret = QUEUE_FAIL;
52 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS); 114
53 if (count) { GetItem(dest); ret = QUEUE_OK; } 115 {
54 else { underflow++; ret = QUEUE_FAIL; } 116 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS);
55 os::set_INT_MASK(old_INT_MASK); 117 if (count)
56 } 118 {
57 return ret; 119 GetItem(dest);
58 } 120 PutSemaphore.V();
59 //------------------------------------------------------------------------- 121 ret = QUEUE_OK;
60 template <class Type> int Queue<Type>::PolledPut(const Type & dest) 122 }
A. Appendices 155 156 A.8 Queue.cc

123 else 185 }


124 { 186 //-------------------------------------------------------------------------
125 underflow++; 187 template <class Type> int Queue_Gsem_Psem<Type>::PolledPut(const Type &
126 ret = QUEUE_FAIL; dest)
127 } 188 {
128 os::set_INT_MASK(old_INT_MASK); 189 if (PutSemaphore.Poll()) return QUEUE_FAIL;
129 } 190 {
130 return ret; 191 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS);
131 } 192 PutItem(dest);
132 //------------------------------------------------------------------------- 193 os::set_INT_MASK(old_INT_MASK);
133 template <class Type> void Queue_Psem<Type>::Put(const Type & dest) 194 }
134 { 195 GetSemaphore.V();
135 PutSemaphore.P(); 196 return QUEUE_OK;
136 { 197 }
137 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS); 198 //=========================================================================
138 PutItem(dest); 199 typedef Queue_Gsem_Psem<Message> MessageQueue;
139 os::set_INT_MASK(old_INT_MASK); 200 typedef Queue_Gsem<unsigned char> serialInQueue;
140 } 201 typedef Queue_Psem<unsigned char> serialOutQueue;
141 } 202 //=========================================================================
142 //-------------------------------------------------------------------------
143 template <class Type> int Queue_Psem<Type>::PolledPut(const Type & dest)
144 {
145 if (PutSemaphore.Poll()) return QUEUE_FAIL;
146 {
147 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS);
148 PutItem(dest);
149 os::set_INT_MASK(old_INT_MASK);
150 }
151 return QUEUE_OK;
152 }
153 //=========================================================================
154 template <class Type> void Queue_Gsem_Psem<Type>::Get(Type & dest)
155 {
156 GetSemaphore.P();
157 {
158 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS);
159 GetItem(dest);
160 os::set_INT_MASK(old_INT_MASK);
161 }
162 PutSemaphore.V();
163 }
164 //-------------------------------------------------------------------------
165 template <class Type> int Queue_Gsem_Psem<Type>::PolledGet(Type & dest)
166 {
167 if (GetSemaphore.Poll()) return QUEUE_FAIL;
168 {
169 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS);
170 GetItem(dest);
171 os::set_INT_MASK(old_INT_MASK);
172 }
173 return QUEUE_OK;
174 }
175 //-------------------------------------------------------------------------
176 template <class Type> void Queue_Gsem_Psem<Type>::Put(const Type & dest)
177 {
178 PutSemaphore.P();
179 {
180 os::INT_MASK old_INT_MASK = os::set_INT_MASK(os::NO_INTS);
181 PutItem(dest);
182 os::set_INT_MASK(old_INT_MASK);
183 }
184 GetSemaphore.V();
A. Appendices 157 158 A.10 Channels.hh

A.9 Message.hh A.10 Channels.hh


1 // Message.hh
2 1 // Channels.hh
3 #ifndef __MESSGAE_HH_DEFINED__ 2 #ifndef __CHANNELS_HH_DEFINED__
4 #define __MESSGAE_HH_DEFINED__ 3 #define __CHANNELS_HH_DEFINED__
5 class Message 4
6 {
5 enum Channel {
7 public:
8 Message() : Type(0), Body(0), Sender(0) {};
6 SERIAL_0 = 0,
9 Message(int t, void * b) : Type(t), Body(b), Sender(0) {}; 7 SERIAL_1 = 1,
10 int Type; 8 SERIAL_0_POLLED = 4,
11 void * Body; 9 SERIAL_1_POLLED = 5,
12 const Task * Sender; 10 DUMMY_SERIAL = 8,
13 }; 11 };
14 12
15 #endif __MESSGAE_HH_DEFINED__ 13 extern Channel MonitorIn;
14 extern Channel MonitorOut;
15 extern Channel ErrorOut;
16 extern Channel GeneralOut;
17
18 #endif __CHANNELS_HH_DEFINED__
A. Appendices 159 160 A.12 SerialOut.cc

A.11 SerialOut.hh A.12 SerialOut.cc

1 /* SerialOut.hh */ 1 /* SerialOut.cc */
2 2
3 #ifndef __SERIALOUT_HH_DEFINED__ 3 #include "System.config"
4 #define __SERIALOUT_HH_DEFINED__ 4 #include "os.hh"
5 5 #include "Task.hh"
6 #include "Channels.hh" 6 #include "SerialOut.hh"
7 7 #include "Duart.hh"
8 // forward declarations... 8
9 class Semaphore; 9 //=================================================================
10 template <class Type> class Queue_Psem; 10 Queue_Psem<unsigned char> SerialOut::outbuf_0 (OUTBUF_0_SIZE);
11 11 Queue_Psem<unsigned char> SerialOut::outbuf_1 (OUTBUF_1_SIZE);
12 class SerialOut 12
13 { 13 int SerialOut::TxEnabled_0 = 1; // pretend Transmitter is enabled
14 public: at startup
15 SerialOut(Channel); 14 int SerialOut::TxEnabled_1 = 1;
16 ~SerialOut(); 15
17 16 Semaphore SerialOut::Channel_0;
18 static int Print(Channel, const char *, ...); 17 Semaphore SerialOut::Channel_1;
19 static int IsEmpty(Channel); 18
20 19 //=================================================================
21 int Print(const char *, ...); 20 SerialOut::SerialOut(Channel ch) : channel(ch)
22 void Putc(int character); 21 {
23 private: 22 switch(channel)
24 static int print_form(void (*)(int), 23 {
25 const unsigned char **&, 24 case SERIAL_0:
26 unsigned const char * &); 25 if (Task::SchedulerRunning()) Channel_0.P();
27 26 else channel = SERIAL_0_POLLED;
28 static void Putc_0(int c); 27 return;
29 static void Putc_1(int c); 28
30 static void Putc_0_polled(int c); // Putc_0 before scheduler is 29 case SERIAL_1:
running 30 if (Task::SchedulerRunning()) Channel_1.P();
31 static void Putc_1_polled(int c); // Putc_1 before scheduler is 31 else channel = SERIAL_1_POLLED;
running 32 return;
32 static void Putc_dummy(int c); // dummy Putc to compute 33
length 34 case SERIAL_0_POLLED:
33 35 case SERIAL_1_POLLED:
34 Channel channel; 36 return;
35 37
36 static Semaphore Channel_0; 38 default:
37 static Semaphore Channel_1; 39 channel = DUMMY_SERIAL; // dummy channel
38 40 return;
39 static Queue_Psem<unsigned char> outbuf_0; 41 }
40 static Queue_Psem<unsigned char> outbuf_1; 42 }
41 43 //-----------------------------------------------------------------
42 static int TxEnabled_0; 44 SerialOut::~SerialOut()
43 static int TxEnabled_1; 45 {
44 }; 46 switch(channel)
45 47 {
46 #endif __SERIALOUT_HH_DEFINED__ 48 case SERIAL_0: Channel_0.V(); return;
49 case SERIAL_1: Channel_1.V(); return;
50 }
51 }
52 //=================================================================
53 void SerialOut::Putc_0(int c)
A. Appendices 161 162 A.12 SerialOut.cc

54 { 110 case SERIAL_0_POLLED: Putc_0_polled(c); return;


55 unsigned char cc = c; 111 case SERIAL_1_POLLED: Putc_1_polled(c); return;
56 112 case DUMMY_SERIAL: return;
57 outbuf_0.Put(cc); 113 default: return;
58 if (!TxEnabled_0) 114 }
59 { 115 }
60 TxEnabled_0 = 1; 116 //=================================================================
61 os::writeRegister(wDUART_CR_A, CR_TxENA); // enable Tx 117
62 } 118 const char * const hex = "0123456789abcdef";
63 } 119 const char * const HEX = "0123456789ABCDEF";
64 //----------------------------------------------------------------- 120
65 void SerialOut::Putc_1(int c) 121 //-----------------------------------------------------------------
66 { 122 int SerialOut::IsEmpty(Channel channel)
67 unsigned char cc = c; 123 {
68 124 switch(channel)
69 outbuf_1.Put(cc); 125 {
70 if (!TxEnabled_1) 126 case 0: return outbuf_0.IsEmpty();
71 { 127 case 1: return outbuf_1.IsEmpty();
72 TxEnabled_1 = 1; 128 }
73 os::writeRegister(wDUART_CR_B, CR_TxENA); // enable Tx 129 return 1; // Polled, dummy and remote IO is always empty
74 } 130 }
75 } 131 //-----------------------------------------------------------------
76 //----------------------------------------------------------------- 132 int SerialOut::Print(Channel channel, const char * format, ...)
77 void SerialOut::Putc_0_polled(int c) 133 {
78 { 134 SerialOut so(channel);
79 if (os::initLevel() < os::Polled_IO) os::init(os::Polled_IO); 135
80 136 void (*putc)(int);
81 while (!(os::readDuartRegister(rDUART_SR_A) & SR_TxRDY)) /**/ ; 137 const unsigned char ** ap = (const unsigned char **)&format;
82 138 const unsigned char * f = *ap++;
83 os::writeRegister(wDUART_THR_A, c); 139 int len = 0;
84 140 int cc;
85 while (!(os::readDuartRegister(rDUART_SR_A) & SR_TxRDY)) /**/ ; 141
86 } 142 switch(channel)
87 //----------------------------------------------------------------- 143 {
88 void SerialOut::Putc_1_polled(int c) 144 case SERIAL_0: putc = Putc_0; break;
89 { 145 case SERIAL_1: putc = Putc_1; break;
90 if (os::initLevel() < os::Polled_IO) os::init(os::Polled_IO); 146 case SERIAL_0_POLLED: putc = Putc_0_polled; break;
91 147 case SERIAL_1_POLLED: putc = Putc_1_polled; break;
92 while (!(os::readDuartRegister(rDUART_SR_B) & SR_TxRDY)) /**/ ; 148 case DUMMY_SERIAL: putc = Putc_dummy; break;
93 149 default: return 0;
94 os::writeRegister(wDUART_THR_B, c); 150 }
95 151
96 while (!(os::readDuartRegister(rDUART_SR_B) & SR_TxRDY)) /**/ ; 152 while (cc = *f++)
97 } 153 if (cc != '%') { putc(cc); len++; }
98 //----------------------------------------------------------------- 154 else len += print_form(putc, ap, f);
99 void SerialOut::Putc_dummy(int) 155
100 { 156 return len;
101 // dummy Putc to compute length 157 }
102 } 158 //-----------------------------------------------------------------
103 //----------------------------------------------------------------- 159 int SerialOut::Print(const char * format, ...)
104 void SerialOut::Putc(int c) 160 {
105 { 161 void (*putc)(int);
106 switch(channel) 162 const unsigned char ** ap = (const unsigned char **)&format;
107 { 163 const unsigned char * f = *ap++;
108 case SERIAL_0: Putc_0(c); return; 164 int len = 0;
109 case SERIAL_1: Putc_1(c); return; 165 int cc;
A. Appendices 163 164 A.12 SerialOut.cc

166 222
167 switch(channel) 223 case 'd':
168 { 224 data.cp = *ap++;
169 case SERIAL_0: putc = Putc_0; break; 225 if (data.lo < 0)
170 case SERIAL_1: putc = Putc_1; break; 226 {
171 case SERIAL_0_POLLED: putc = Putc_0_polled; break; 227 data.lo = -data.lo;
172 case SERIAL_1_POLLED: putc = Putc_1_polled; break; 228 putc('-'); len++;
173 case DUMMY_SERIAL: putc = Putc_dummy; break; 229 }
174 default: return 0; 230
175 } 231 do { buf[buf_idx++] = '0' + data.ul%10;
176 232 data.ul = data.ul/10;
177 while (cc = *f++) 233 } while (data.lo);
178 if (cc != '%') { putc(cc); len++; } 234
179 else len += print_form(putc, ap, f); 235 while (min_len-- > buf_idx) { putc(' '); len++;
180 }
181 return len; 236
182 } 237 do { cc = buf[--buf_idx]; putc(cc); len++; }
183 //================================================================= 238 while (buf_idx);
184 int 239 return len;
185 SerialOut::print_form(void (*putc)(int), 240
186 const unsigned char **& ap, 241 case 's':
187 const unsigned char * & f) 242 data.cp = *ap++;
188 { 243 if (data.scp == 0) data.scp = "(null)";
189 int len = 0; 244 while (cc = *data.cp++)
190 int min_len = 0; 245 { putc(cc); len++; min_len--; }
191 int buf_idx = 0; 246
192 union { const unsigned char * cp; 247 while (min_len-- > 0)
193 const char * scp; 248 { putc(' '); len++; }
194 long lo; 249 return len;
195 unsigned long ul; } data; 250
196 int cc; 251 case 'x':
197 unsigned char buf[10]; 252 data.cp = *ap++;
198 253 do { buf[buf_idx++] = hex[0x0F & data.ul];
199 for (;;) 254 data.ul >>= 4;
200 { 255 } while (data.ul);
201 switch(cc = *f++) 256
202 { 257 while (min_len-- > buf_idx) { putc('0'); len++;
203 case '0': min_len *= 10; continue; }
204 case '1': min_len *= 10; min_len += 1; continue; 258
205 case '2': min_len *= 10; min_len += 2; continue; 259 do { cc = buf[--buf_idx]; putc(cc); len++; }
206 case '3': min_len *= 10; min_len += 3; continue; 260 while (buf_idx);
207 case '4': min_len *= 10; min_len += 4; continue; 261 return len;
208 case '5': min_len *= 10; min_len += 5; continue; 262
209 case '6': min_len *= 10; min_len += 6; continue; 263 case 'X':
210 case '7': min_len *= 10; min_len += 7; continue; 264 data.cp = *ap++;
211 case '8': min_len *= 10; min_len += 8; continue; 265 do { buf[buf_idx++] = HEX[0x0F & data.ul];
212 case '9': min_len *= 10; min_len += 9; continue; 266 data.ul >>= 4;
213 267 } while (data.ul);
214 case '%': 268
215 putc('%'); 269 while (min_len-- > buf_idx) { putc('0'); len++;
216 return 1; }
217 270
218 case 'c': 271 do { cc = buf[--buf_idx]; putc(cc); len++; }
219 data.cp = *ap++; 272 while (buf_idx);
220 putc(data.lo); 273 return len;
221 return 1; 274 }
A. Appendices 165 166 A.13 SerialIn.hh

275 } A.13 SerialIn.hh


276 }
277 //=================================================================
1 /* SerialIn.hh */
2
3 #ifndef __SERIALIN_HH_DEFINED__
4 #define __SERIALIN_HH_DEFINED__
5
6 #include "Channels.hh"
7
8 // forward declarations...
9 class Semaphore;
10 class SerialOut;
11 template <class Type> class Queue_Gsem;
12
13 class SerialIn
14 {
15 public:
16 SerialIn(Channel);
17 ~SerialIn();
18
19 static unsigned int getOverflowCounter(Channel);
20
21 int Getc();
22 int Pollc();
23 int Peekc();
24 int Gethex(SerialOut &);
25 int Getdec(SerialOut &);
26
27 enum SerialError
28 {
29 OVERRUN_ERROR = 1,
30 PARITY_ERROR = 2,
31 FRAME_ERROR = 3,
32 BREAK_DETECT = 4
33 };
34 private:
35 Channel channel;
36
37 static Semaphore Channel_0;
38 static Semaphore Channel_1;
39
40 static Queue_Gsem<unsigned char> inbuf_0;
41 static Queue_Gsem<unsigned char> inbuf_1;
42 };
43
44 #endif __SERIALIN_HH_DEFINED__
A. Appendices 167 168 A.14 SerialIn.cc

A.14 SerialIn.cc 55 }
56 }
57 //=================================================================
1 /* SerialIn.cc */
58 int SerialIn::Peekc()
2
59 {
3 #include "System.config"
60 unsigned char cc;
4 #include "SerialIn.hh"
61
5 #include "SerialOut.hh"
62 switch(channel)
6 #include "Task.hh"
63 {
7 #include "Queue.hh"
64 case SERIAL_0: return inbuf_0.Peek(cc) ? -1 : cc;
8
65 case SERIAL_1: return inbuf_1.Peek(cc) ? -1 : cc;
9 Queue_Gsem<unsigned char> SerialIn::inbuf_0 (INBUF_0_SIZE);
66 default: return -1;
10 Queue_Gsem<unsigned char> SerialIn::inbuf_1 (INBUF_1_SIZE);
67 }
11
68 }
12 Semaphore SerialIn::Channel_0;
69 //=================================================================
13 Semaphore SerialIn::Channel_1;
70 int SerialIn::Gethex(SerialOut &so)
14
71 {
15 //=================================================================
72 int ret = 0;
16 SerialIn::SerialIn(Channel ch) : channel(ch)
73 int cc;
17 {
74
18 switch(channel)
75 for (;;) switch(cc = Peekc())
19 {
76 {
20 case SERIAL_0: Channel_0.P(); break;
77 case -1: // no char arrived yet
21 case SERIAL_1: Channel_1.P(); break;
78 Task::Sleep(1);
22 }
79 continue;
23 }
80
24 //=================================================================
81 case '0': case '1': case '2': case '3': case '4':
25 SerialIn::~SerialIn()
82 case '5': case '6': case '7': case '8': case '9':
26 {
83 ret <<= 4;
27 switch(channel)
84 ret += cc-'0';
28 {
85 so.Print("%c", Pollc()); // echo char
29 case SERIAL_0: Channel_0.V(); break;
86 continue;
30 case SERIAL_1: Channel_1.V(); break;
87
31 }
88 case 'A': case 'B': case 'C':
32 }
89 case 'D': case 'E': case 'F':
33 //=================================================================
90 ret <<= 4;
34 int SerialIn::Getc()
91 ret += cc+10-'A';
35 {
92 so.Print("%c", Pollc()); // echo char
36 unsigned char cc;
93 continue;
37
94
38 switch(channel)
95 case 'a': case 'b': case 'c':
39 {
96 case 'd': case 'e': case 'f':
40 case SERIAL_0: inbuf_0.Get(cc); return cc;
97 ret <<= 4;
41 case SERIAL_1: inbuf_1.Get(cc); return cc;
98 ret += cc+10-'a';
42 default: return -1;
99 so.Print("%c", Pollc()); // echo char
43 }
100 continue;
44 }
101
45 //=================================================================
102 default:
46 int SerialIn::Pollc()
103 return ret;
47 {
104 }
48 unsigned char cc;
105 }
49
106 //=================================================================
50 switch(channel)
107 int SerialIn::Getdec(SerialOut &so)
51 {
108 {
52 case SERIAL_0: return inbuf_0.PolledGet(cc) ? -1 : cc;
109 int ret = 0;
53 case SERIAL_1: return inbuf_1.PolledGet(cc) ? -1 : cc;
110 int cc;
54 default: return -1;
A. Appendices 169 170 A.15 TaskId.hh

111 A.15 TaskId.hh


112 for (;;) switch(cc = Peekc())
113 {
1 // TaskId.hh
114 case -1: // no char arrived yet
2
115 Task::Sleep(1);
3 enum { TASKID_IDLE = 0,
116 continue;
4 TASKID_MONITOR,
117
5 TASKID_COUNT // number of Task IDs
118 case '0': case '1': case '2': case '3': case '4':
6 };
119 case '5': case '6': case '7': case '8': case '9':
7
120 ret *= 10;
8 #define IdleTask (Task::TaskIDs[TASKID_IDLE])
121 ret += cc-'0';
9 #define MonitorTask (Task::TaskIDs[TASKID_MONITOR])
122 so.Print("%c", Pollc()); // echo char
123 continue;
124
125 default:
126 return ret;
127 }
128 }
129 //=================================================================
130 unsigned int SerialIn::getOverflowCounter(Channel channel)
131 {
132 switch(channel)
133 {
134 case SERIAL_0: return inbuf_0.getOverflowCount();
135 case SERIAL_1: return inbuf_1.getOverflowCount();
136 default: return 0;
137 }
138 }
139 //=================================================================
A. Appendices 171 172 A.16 duart.hh

A.16 duart.hh 55 #define wDUART_IMR (DUART + w_IMR )


56 #define wDUART_CSR_B (DUART + w_CSR + _B)
57 #define wDUART_CR_B (DUART + w_CR + _B)
1 #ifndef __DUART_HH_DEFINED__
58 #define wDUART_THR_B (DUART + w_THR + _B)
2 #define __DUART_HH_DEFINED__
59 #define wDUART_OPCR (DUART + w_OPCR )
3
60 #define wDUART_BSET (DUART + w_BSET )
4 /* DUART base address */
61 #define wDUART_BCLR (DUART + w_BCLR )
5 #define DUART 0xA0000000
62
6
63 /* DUART MR1 bit definitions */
7 /* DUART channel offsets */
64 #define MR1_RxRTS (1<<7)
8 #define _A 0x00
65 #define MR1_FFUL (1<<6)
9 #define _B 0x20
66 #define MR1_EBLOCK (1<<5)
10
67
11 /* DUART register offsets */
68 #define MR1_P_EVEN (0<<2)
12 #define x_MR 0x00
69 #define MR1_P_ODD (1<<2)
13 #define r_SR 0x04
70 #define MR1_P_LOW (2<<2)
14 #define w_CSR 0x04
71 #define MR1_P_HIGH (3<<2)
15 #define w_CR 0x08
72 #define MR1_P_NONE (4<<2)
16 #define r_RHR 0x0C
73 #define MR1_P_void (5<<2)
17 #define w_THR 0x0C
74 #define MR1_M_DATA (6<<2)
18 #define r_IPCR 0x10
75 #define MR1_M_ADDR (7<<2)
19 #define w_ACR 0x10
76 #define MR1_P_MASK (7<<2)
20 #define r_ISR 0x14
77
21 #define w_IMR 0x14
78 #define MR1_BITS_5 (0<<0)
22 #define x_CTUR 0x18
79 #define MR1_BITS_6 (1<<0)
23 #define x_CTLR 0x1C
80 #define MR1_BITS_7 (2<<0)
24 #define x_IVR 0x30
81 #define MR1_BITS_8 (3<<0)
25 #define r_IPU 0x34
82 #define MR1_BITS_mask (3<<0)
26 #define w_OPCR 0x34
83
27 #define r_START 0x38
84 #define MR1_DEFAULT (MR1_P_NONE | MR1_BITS_8)
28 #define w_BSET 0x38
85
29 #define r_STOP 0x3C
86 /* DUART MR2 bit definitions */
30 #define w_BCLR 0x3C
87 #define MR2_NORM (0<<6)
31
88 #define MR2_ECHO (1<<6)
32 /* DUART read/write registers */
89 #define MR2_LOLO (2<<6)
33 #define xDUART_MR_A (DUART + x_MR + _A)
90 #define MR2_RELO (3<<6)
34 #define xDUART_MR_B (DUART + x_MR + _B)
91
35 #define xDUART_IVR (DUART + x_IVR)
92 #define MR2_TxRTS (1<<5)
36 #define xDUART_CTUR (DUART + x_CTUR)
93 #define MR2_TxCTS (1<<4)
37 #define xDUART_CTLR (DUART + x_CTLR)
94 #define MR2_STOP_2 (15<<0)
38
95 #define MR2_STOP_1 (7<<0)
39 /* DUART read only registers */
96
40 #define rDUART_SR_A (DUART + r_SR + _A)
97 #define MR2_DEFAULT MR2_STOP_2
41 #define rDUART_RHR_A (DUART + r_RHR + _A)
98
42 #define rDUART_IPCR (DUART + r_IPCR )
99 /* DUART SR bit definitions */
43 #define rDUART_ISR (DUART + r_ISR )
100 #define SR_BREAK (1<<7)
44 #define rDUART_SR_B (DUART + r_SR + _B)
101 #define SR_FRAME (1<<6)
45 #define rDUART_RHR_B (DUART + r_RHR + _B)
102 #define SR_PARITY (1<<5)
46 #define rDUART_IPU (DUART + r_IPU )
103 #define SR_OVERRUN (1<<4)
47 #define rDUART_START (DUART + r_START )
104 #define SR_TxEMPTY (1<<3)
48 #define rDUART_STOP (DUART + r_STOP )
105 #define SR_TxRDY (1<<2)
49
106 #define SR_RxFULL (1<<1)
50 /* DUART write only registers */
107 #define SR_RxRDY (1<<0)
51 #define wDUART_CSR_A (DUART + w_CSR + _A)
108
52 #define wDUART_CR_A (DUART + w_CR + _A)
109 /* DUART CSR bit definitions */
53 #define wDUART_THR_A (DUART + w_THR + _A)
110 #define BD_600 5
54 #define wDUART_ACR (DUART + w_ACR )
A. Appendices 173 174 A.16 duart.hh

111 #define BD_1200 6 167 #define CTUR_DEFAULT (CT_DEFAULT / 256)


112 #define BD_2400 8 168 #define CTLR_DEFAULT (CT_DEFAULT & 255)
113 #define BD_4800 9 169
114 #define BD_9600 11 170 /* DUART IMR/ISR bit definitions */
115 #define BD_19200 12 171 #define INT_IPC (1<<7)
116 #define BD_38400 BD_19200 172 #define INT_BxB (1<<6)
117 #define BD_TIMER 13 173 #define INT_RxB (1<<5)
118 174 #define INT_TxB (1<<4)
119 #define CSR_600 (BD_600 | BD_600 <<4) 175 #define INT_CT (1<<3)
120 #define CSR_1200 (BD_4800 | BD_4800 <<4) 176 #define INT_BxA (1<<2)
121 #define CSR_2400 (BD_2400 | BD_2400 <<4) 177 #define INT_RxA (1<<1)
122 #define CSR_4800 (BD_4800 | BD_4800 <<4) 178 #define INT_TxA (1<<0)
123 #define CSR_9600 (BD_9600 | BD_9600 <<4) 179
124 #define CSR_19200 (BD_19200 | BD_19200<<4) 180 #define INT_DEFAULT (INT_RxB | INT_TxB | INT_RxA | INT_TxA |
125 #define CSR_38400 (BD_38400 | BD_38400<<4) INT_CT)
126 #define CSR_TIMER (BD_TIMER | BD_TIMER<<4) 181
127 182 /* DUART OPCR bit definitions */
128 /* DUART CR bit definitions */ 183 #define OPCR_7_TxRDY_B (1<<7)
129 #define CR_NOP (0<<4) 184 #define OPCR_6_TxRDY_A (1<<6)
130 #define CR_MR1 (1<<4) 185 #define OPCR_5_RxRDY_B (1<<5)
131 #define CR_RxRESET (2<<4) 186 #define OPCR_4_RxRDY_A (1<<4)
132 #define CR_TxRESET (3<<4) 187
133 #define CR_ExRESET (4<<4) 188 #define OPCR_3_OPR_3 (0<<2)
134 #define CR_BxRESET (5<<4) 189 #define OPCR_3_CT (1<<2)
135 #define CR_B_START (6<<4) 190 #define OPCR_3_TxC_B (2<<2)
136 #define CR_B_STOP (7<<4) 191 #define OPCR_3_RxC_B (3<<2)
137 192
138 #define CR_TxENA (1<<2) 193 #define OPCR_2_OPR_2 (0<<0)
139 #define CR_TxDIS (2<<2) 194 #define OPCR_2_TxC_A16 (1<<0)
140 195 #define OPCR_2_TxC_A (2<<0)
141 #define CR_RxENA (1<<0) 196 #define OPCR_2_RxC_A (3<<0)
142 #define CR_RxDIS (2<<0) 197
143 198 #define OPCR_DEFAULT 0
144 /* DUART ACR bit definitions */ 199
145 #define ACR_BRG_0 (0<<7) 200 #endif __DUART_HH_DEFINED__
146 #define ACR_BRG_1 (1<<7) 201
147
148 #define ACR_CNT_IP2 (0<<4)
149 #define ACR_CNT_TxCA (1<<4)
150 #define ACR_CNT_TxCB (2<<4)
151 #define ACR_CNT_XTAL (3<<4)
152 #define ACR_TIM_IP2 (4<<4)
153 #define ACR_TIM_IP2_16 (5<<4)
154 #define ACR_TIM_XTAL (6<<4)
155 #define ACR_TIM_XTAL_16 (7<<4)
156
157 #define ACR_INT_IP3 (1<<3)
158 #define ACR_INT_IP2 (1<<2)
159 #define ACR_INT_IP1 (1<<1)
160 #define ACR_INT_IP0 (1<<0)
161
162 #define ACR_DEFAULT (ACR_TIM_XTAL_16 | ACR_BRG_0)
163 #define XTAL_FREQ (3686400/2)
164 #define XTAL_FREQ_16 (XTAL_FREQ/16)
165 #define TS_RATE 100
166 #define CT_DEFAULT (XTAL_FREQ_16/TS_RATE)
A. Appendices 175 176 A.18 ApplicationStart.cc

A.17 System.config A.18 ApplicationStart.cc

1 #define ROMbase 0x00000000 1 // ApplicationStart.cc


2 #define ROMsize 0x00040000 2
3 #define RAMbase 0x20000000 3 #include "os.hh"
4 #define RAMsize 0x00040000 4 #include "Channels.hh"
5 #define RAMend (RAMbase+RAMsize) 5 #include "SerialIn.hh"
6 6 #include "SerialOut.hh"
7 #define OUTBUF_0_SIZE 80 7 #include "Task.hh"
8 #define OUTBUF_1_SIZE 80 8 #include "TaskId.hh"
9 #define INBUF_0_SIZE 80 9 #include "Monitor.hh"
10 #define INBUF_1_SIZE 80 10
11 Channel MonitorIn = DUMMY_SERIAL;
12 Channel MonitorOut = DUMMY_SERIAL;
13 Channel ErrorOut = DUMMY_SERIAL;
14 Channel GeneralOut = DUMMY_SERIAL;
15
16 //-----------------------------------------------------------------
17 //
18 // Note: do not Print() here !
19 // Multitasking and interrupt IO is not yet up and running
20 //
21 //
22 void setupApplicationTasks()
23 {
24 MonitorIn = SERIAL_1;
25 MonitorOut = SERIAL_1;
26 ErrorOut = SERIAL_1;
27 GeneralOut = SERIAL_1;
28
29 Monitor::setupMonitorTask();
30 }
A. Appendices 177 178 A.20 Monitor.cc

A.19 Monitor.hh A.20 Monitor.cc

1 // Monitor.hh 1 // Monitor.cc
2 2
3 #ifndef MONITOR_HH_DEFINED 3 #include "System.config"
4 #define MONITOR_HH_DEFINED 4 #include "os.hh"
5 5 #include "SerialIn.hh"
6 #include "Channels.hh" 6 #include "SerialOut.hh"
7 7 #include "Channels.hh"
8 class SerialIn; 8 #include "Task.hh"
9 class SerialOut; 9 #include "TaskId.hh"
10 10 #include "Monitor.hh"
11 class Monitor 11
12 { 12 //-----------------------------------------------------------------
13 public: 13 void Monitor::setupMonitorTask()
14 Monitor(Channel In, Channel Out) 14 {
15 : si(In), channel(Out), currentChannel(0), last_addr(0) {}; 15 MonitorTask = new Task (
16 16 monitor_main, // function
17 static void setupMonitorTask(); 17 2048, // user stack size
18 18 16, // message queue size
19 private: 19 240, // priority
20 static void monitor_main(); 20 "Monitor Task");
21 21 }
22 // menus... 22 //-----------------------------------------------------------------
23 void MonitorMainMenu(); 23 void Monitor::monitor_main()
24 void InfoMenu(); 24 {
25 void DuartMenu(); 25 SerialOut::Print(GeneralOut,
26 void TaskMenu(); 26 "\nMonitor started on channel %d.",
27 void MemoryMenu(); 27 MonitorOut);
28 28
29 int getCommand(const char * prompt); 29 Monitor Mon(MonitorIn, MonitorOut);
30 int getCommand(const char * prompt, char arg); 30 Mon.MonitorMainMenu();
31 int echoResponse(); 31 }
32 // complex functions... 32 //-----------------------------------------------------------------
33 void setTaskPriority(); 33 int Monitor::getCommand(const char * prompt)
34 void showTasks(); 34 {
35 void showTask(); 35 SerialOut::Print(channel, "\n%s > ", prompt);
36 void showTask(SerialOut &, const Task *, const char *); 36 return echoResponse();
37 const char * const showTaskStatus(const Task * t); 37 }
38 void displayMemory(int cont); 38 //-----------------------------------------------------------------
39 39 int Monitor::getCommand(const char * prompt, char arg)
40 SerialIn si; 40 {
41 const Channel channel; 41 SerialOut::Print(channel, "\n%s_%c > ", prompt, arg);
42 42 return echoResponse();
43 int currentChannel; // used in DuartMenu() 43 }
44 int currentChar; // used in DuartMenu() 44 //-----------------------------------------------------------------
45 unsigned long last_addr; // used in MemoryMenu() 45 int Monitor::echoResponse()
46 46 {
47 enum { ESC = 0x1B }; 47 int cc = si.Getc() & 0x7F;
48 }; 48
49 49 switch(cc)
50 #endif MONITOR_HH_DEFINED 50 {
51 case ESC: SerialOut::Print(channel, "ESC "); break;
52 case '\n': break;
53 case '\r': break;
54 default: if (cc < ' ') break;
A. Appendices 179 180 A.20 Monitor.cc

55 SerialOut::Print(channel, "%c ", cc); 111 case 's': case 'S':


56 } 112 {
57 return cc; 113 SerialOut::Print(channel, "\nTop of System Memory:
58 } %8X",
59 //----------------------------------------------------------------- 114 os::top_of_RAM());
60 void Monitor::MonitorMainMenu() 115 }
61 { 116 continue;
62 SerialOut::Print(channel, "\nType H or ? for help."); 117
63 SerialOut::Print(channel, "\nMain Menu [D I M T H]\n"); 118 case 't': case 'T':
64 119 {
65 for (;;) switch(getCommand("Main")) 120 unsigned long long time = os::getSystemTime();
66 { 121 unsigned long t_low = time;
67 case 'h': case 'H': case '?': 122 unsigned long t_high = time>>32;
68 { 123
69 SerialOut so(channel); 124 SerialOut::Print(channel, "\nSystem Time: %d:%d",
70 so.Print("\nD - Duart Menu"); 125 t_high, t_low);
71 so.Print("\nI - Info Menu"); 126 }
72 so.Print("\nM - Memory Menu"); 127 continue;
73 so.Print("\nT - Task Menu"); 128 }
74 } 129 }
75 continue; 130 //-----------------------------------------------------------------
76 131 void Monitor::DuartMenu()
77 case 'd': case 'D': DuartMenu(); continue; 132 {
78 case 'i': case 'I': InfoMenu(); continue; 133 int currentChar;
79 case 'm': case 'M': MemoryMenu(); continue; 134 int databits;
80 case 't': case 'T': TaskMenu(); continue; 135 int parity;
81 } 136 int baud;
82 } 137
83 //----------------------------------------------------------------- 138 SerialOut::Print(channel, "\nDuart Menu [B C M T H Q]");
84 void Monitor::InfoMenu() 139 for (;;) switch(getCommand("Duart", 'A' + currentChannel))
85 { 140 {
86 SerialOut::Print(channel, "\nInfo Menu [O S T H Q]"); 141 case 'h': case 'H': case '?':
87 for (;;) switch(getCommand("Info")) 142 {
88 { 143 SerialOut so(channel);
89 case 'h': case 'H': case '?': 144 so.Print("\nB - Set Baud Rate");
90 { 145 so.Print("\nC - Change Channel");
91 SerialOut so(channel); 146 so.Print("\nM - Change Mode");
92 so.Print("\nO - Overflows"); 147 so.Print("\nT - Transmit Character");
93 so.Print("\nS - System Memory"); 148 }
94 so.Print("\nT - System Time"); 149 continue;
95 } 150
96 continue; 151 case ESC: case 'Q': case 'q':
97 152 return;
98 case ESC: case 'Q': case 'q': 153
99 return; 154 case 'b': case 'B':
100 155 {
101 case 'o': case 'O': 156 SerialOut so(channel);
102 { 157 so.Print("\nBaud Rate ? ");
103 SerialOut so(channel); 158 baud = si.Getdec(so);
104 so.Print("\nCh 0 in : %d", 159 Channel bc;
105 SerialIn::getOverflowCounter(SERIAL_0)); 160
106 so.Print("\nCh 1 in : %d", 161 if (currentChannel) bc = SERIAL_1;
107 SerialIn::getOverflowCounter(SERIAL_1)); 162 else bc = SERIAL_0;
108 } 163
109 continue; 164 if (os::setBaudRate(bc, baud))
110 165 so.Print("\nIllegal Baud Rate %d", baud);
A. Appendices 181 182 A.20 Monitor.cc

166 } 222 databits, parity);


167 continue; 223 }
168 224 continue;
169 case 'c': case 'C': 225
170 currentChannel = 1 & ++currentChannel; 226 case 't': case 'T':
171 continue; 227 {
172 228 SerialOut so(channel);
173 case 'm': case 'M': 229 currentChar = si.Gethex(so);
174 SerialOut::Print(channel, "\nData Bits (5-8) ? "); 230
175 databits = echoResponse() - '0'; 231 so.Print("\nSending 0x%2X", currentChar & 0xFF);
176 if (databits < 5 || databits > 8) 232 }
177 { 233 {
178 SerialOut::Print(channel, 234 Channel bc;
179 "\nIllegal Data bit count %d", 235
180 databits); 236 if (currentChannel) bc = SERIAL_1;
181 continue; 237 else bc = SERIAL_0;
182 } 238
183 239 SerialOut::Print(bc, "%c", currentChar);
184 240 }
185 SerialOut::Print(channel, "\nParity (N O E M S) ? "); 241 continue;
186 parity = echoResponse(); 242 }
187 243 }
188 { 244 //-----------------------------------------------------------------
189 SerialOut so(channel); 245 void Monitor::TaskMenu()
190 Channel bc; 246 {
191 247 SerialOut::Print(channel, "\nTask Menu [P S T H Q]");
192 if (currentChannel) bc = SERIAL_1; 248 for (;;) switch(getCommand("Task"))
193 else bc = SERIAL_0; 249 {
194 250 case 'h': case 'H': case '?':
195 switch(parity) 251 {
196 { 252 SerialOut so(channel);
197 case 'E': case 'e': 253 so.Print("\nP - Set Task Priority");
198 os::setSerialMode(bc, databits, 0); 254 so.Print("\nS - Show Tasks");
199 break; 255 so.Print("\nT - Show Task");
200 256 }
201 case 'O': case 'o': 257 continue;
202 os::setSerialMode(bc, databits, 1); 258
203 break; 259 case ESC: case 'Q': case 'q':
204 260 return;
205 case 'M': case 'm': 261
206 os::setSerialMode(bc, databits, 2); 262 case 'p': case 'P':
207 break; 263 SerialOut::Print(channel, "Set Task Priority:");
208 264 setTaskPriority();
209 case 'S': case 's': 265 continue;
210 os::setSerialMode(bc, databits, 3); 266
211 break; 267 case 's': case 'S':
212 268 SerialOut::Print(channel, "Show Tasks:");
213 case 'N': case 'n': 269 showTasks();
214 os::setSerialMode(bc, databits, 4); 270 continue;
215 break; 271
216 272 case 't': case 'T':
217 default: 273 SerialOut::Print(channel, "Show Task:");
218 so.Print("\nIllegal Parity %c", parity); 274 showTask();
219 continue; 275 continue;
220 } 276 }
221 so.Print("\nDatabits = %d / Parity = %c set.", 277 }
A. Appendices 183 184 A.20 Monitor.cc

278 //----------------------------------------------------------------- 334 {


279 void Monitor::MemoryMenu() 335 cc = ((char *)addr)[j];
280 { 336 if (cc < ' ' || cc > 0x7E) cc = '.';
281 int gotD = 0; 337 so.Print("%c", cc);
282 338 }
283 SerialOut::Print(channel, "\nMemory Menu [D H Q]"); 339
284 for (;;) switch(getCommand("Memory")) 340 addr += 16;
285 { 341 }
286 case 'h': case 'H': case '?': 342 last_addr = addr;
287 { 343 }
288 SerialOut so(channel); 344 //-----------------------------------------------------------------
289 so.Print("\nD - Dump Memory"); 345 void Monitor::setTaskPriority()
290 gotD = 0; 346 {
291 } 347 Task * t = Task::Current();
292 continue; 348 unsigned short priority;
293 349 {
294 case ESC: case 'Q': case 'q': 350 SerialOut so(channel);
295 return; 351 while (si.Pollc() != -1) /* empty */ ;
296 352 so.Print("\nTask number = ");
297 case 'd': case 'D': 353
298 SerialOut::Print(channel, "Dump Mamory at address 0x"); 354 for (int tindex = si.Getdec(so); tindex; tindex--)
299 displayMemory(0); 355 t = t->Next();
300 gotD = 1; 356
301 continue; 357 while (si.Pollc() != -1) /* empty */ ;
302 358 so.Print("\nTask priority = ");
303 case '\n': 359 priority = si.Getdec(so);
304 if (gotD) displayMemory(1); 360
305 continue; 361 if (priority == 0) priority++;
306 } 362 so.Print("\nSet %s Priority to %d", t->Name(), priority);
307 } 363 }
308 //----------------------------------------------------------------- 364 t->setPriority(priority);
309 void Monitor::displayMemory(int cont) 365 }
310 { 366 //-----------------------------------------------------------------
311 unsigned int addr = last_addr; 367 void Monitor::showTask()
312 368 {
313 if (cont == 0) // dont continue 369 const Task * t = Task::Current();
314 { 370 SerialOut so(channel);
315 SerialOut so(channel); 371
316 addr = si.Gethex(so); 372 so.Print("\nTask number = ");
317 si.Pollc(); // discard terminating char for Gethex() 373 for (int tindex = si.Getdec(so); tindex; tindex--)
318 } 374 t = t->Next();
319 375
320 for (int line = 0; line < 16; line++) 376 const char * const stat = showTaskStatus(t);
321 if ( ROMbase <= addr && addr < ROMbase+ROMsize-16 377 unsigned int stackUsed = t->userStackUsed();
322 || RAMbase <= addr && addr < RAMbase+RAMsize-16 378
323 ) 379 so.Print("\nTask Name: %s", t->Name());
324 { 380 so.Print("\nPriority: %d", t->Priority());
325 SerialOut so(channel); 381 so.Print("\nTCB Address: %8X", t);
326 int j; 382 if (stat) so.Print("\nStatus: %s", stat);
327 char cc; 383 else so.Print("\nStatus: %2X", t->Status());
328 so.Print("\n%8X: ", addr); 384 so.Print("\nUS Base: %8X", t->userStackBase());
329 385 so.Print("\nUS Size: %8X", t->userStackSize());
330 for (j = 0; j < 8; j++) 386 so.Print("\nUS Usage: %8X (%d%%)",
331 so.Print("%4X ", 0xFFFF & (int)(((short *)addr)[j])); 387 stackUsed, (stackUsed*100)/t->userStackSize());
332 388 }
333 for (j = 0; j < 16; j++) 389 //-----------------------------------------------------------------
A. Appendices 185 186 A.20 Monitor.cc

390 void Monitor::showTasks() 446 }


391 { 447 //-----------------------------------------------------------------
392 const Task * t = Task::Current();
393 SerialOut so(channel);
394
395 so.Print(
396 "\n----------------------------------------------------");
397 so.Print(
398 "\n TCB Status Pri TaskName ID US Usage");
399 so.Print(
400 "\n----------------------------------------------------");
401 for (;;)
402 {
403 if (t == Task::Current()) showTask(so, t, "-->");
404 else showTask(so, t, " ");
405
406 t = t->Next();
407 if (t == Task::Current()) break;
408 }
409 so.Print(
410 "\n====================================================\n");
411 }
412 //-----------------------------------------------------------------
413 void Monitor::showTask(SerialOut & so, const Task * t,
414 const char * prefix)
415 {
416 const char * const stat = showTaskStatus(t);
417 int i;
418
419 so.Print("\n%s %8X ", prefix, t);
420 if (stat) so.Print("%s", stat);
421 else so.Print("%4X ", t->Status());
422 so.Print("%3d ", t->Priority());
423 so.Print("%16s", t->Name());
424
425 for (i = 0; i < TASKID_COUNT; i++)
426 if (t == Task::TaskIDs[i]) break;
427
428 if (i < TASKID_COUNT) so.Print("%2d ", i);
429 else so.Print("--- ");
430
431 so.Print("%8X ", t->userStackUsed());
432 }
433 //-----------------------------------------------------------------
434 const char * const Monitor::showTaskStatus(const Task * t)
435 {
436 switch(t->Status())
437 {
438 case Task::RUN: return "RUN ";
439 case Task::BLKD: return "BLKD ";
440 case Task::STARTED: return "START ";
441 case Task::TERMINATED: return "TERM ";
442 case Task::SLEEP: return "SLEEP ";
443 case Task::FAILED: return "FAILED ";
444 default: return 0;
445 }
A. Appendices 187 188 A.21 Makefile

A.21 Makefile 55 .PHONY: all


56 .PHONY: clean
57 .PHONY: tar
1 # Makefile for gmake
58
2 #
59 all: Target Target.sym
3
60
4 # Development environment.
61 clean:
5 # Replace /CROSS by where you installed the cross-environment
62 /bin/rm -f $(CLEAN)
6 #
63
7 CROSS-PREFIX:= /CROSS
64 tar: clean
8 AR := $(CROSS-PREFIX)/bin/m68k-sun-sunos4.1-ar
65 tar:
9 AS := $(CROSS-PREFIX)/bin/m68k-sun-sunos4.1-as
66 tar -cvzf ../src.tar *
10 LD := $(CROSS-PREFIX)/bin/m68k-sun-sunos4.1-ld
67
11 NM := $(CROSS-PREFIX)/bin/m68k-sun-sunos4.1-nm
68 include $(DEP)
12 OBJCOPY := $(CROSS-PREFIX)/bin/m68k-sun-sunos4.1-objcopy
69
13 CC := $(CROSS-PREFIX)/bin/m68k-sun-sunos4.1-gcc
70 # Standard Pattern rules...
14 MAKE := gmake
71 #
15
72 %.o: %.cc
16 # Target memory mapping.
73 $(CC) -c $(CCFLAGS) $< -o $@
17 #
74
18 ROM_BASE:= 0
75 %.o: %.S
19 RAM_BASE:= 20000000
76 $(CC) -c $(ASFLAGS) $< -o $@
20
77
21 # compiler and linker flags.
78 %.d: %.cc
22 #
79 $(SHELL) -ec '$(CC) -MM $(CCFLAGS) $< \
23 ASFLAGS := -mc68020
80 | sed '\''s/$*\.o/$*\.o $@/'\'' > $@'
24 CCFLAGS := -mc68020 -O2 -fomit-frame-pointer -fno-exceptions
81
25
82 %.d: %.S
26 LDFLAGS := -i -nostdlib \
83 $(SHELL) -ec '$(CC) -MM $(ASFLAGS) $< \
27 -Ttext $(ROM_BASE) -Tdata $(RAM_BASE) \
84 | sed '\''s/$*\.o/$*\.o $@/'\'' > $@'
28 -Xlinker -Map -Xlinker Target.map
85
29
86 libos.a:$(OBJ)
30 # Source files
87 $(AR) -sr libos.a $?
31 #
88
32 SRC_S := $(wildcard *.S)
89 Target: Target.bin
33 SRC_CC := $(wildcard *.cc)
90 $(OBJCOPY) -I binary -O srec $< $@
34 SRC := $(SRC_S) $(SRC_CC)
91
35
92 Target.text:Target.td
36 # Dependency files
93 $(OBJCOPY) -R .data -O binary $< $@
37 #
94
38 DEP_CC := $(SRC_CC:.cc=.d)
95 Target.data:Target.td
39 DEP_S := $(SRC_S:.S=.d)
96 $(OBJCOPY) -R .text -O binary $< $@
40 DEP := $(DEP_CC) $(DEP_S)
97
41
98 Target.bin:Target.text Target.data
42 # Object files
99 cat Target.text | skip_aout | cat - Target.data > $@
43 #
100
44 OBJ_S := $(SRC_S:.S=.o)
101 Target.sym:Target.td
45 OBJ_CC := $(SRC_CC:.cc=.o)
102 $(NM) -n --demangle $< \
46 OBJ := $(OBJ_S) $(OBJ_CC)
103 | awk '{printf("%s %s\n", $$1, $$3)}' \
47
104 | grep -v compiled | grep -v "\.o" \
48 CLEAN := $(OBJ) $(DEP) libos.a \
105 | grep -v "_DYNAMIC" | grep -v "^U" > $@
49 Target Target.bin \
106
50 Target.td Target.text Target.data \
107
51 Target.map Target.sym
108 Target.td:crt0.o libos.a libgcc.a
52
109 $(CC) -o $@ crt0.o -L. -los -lgcc $(LDFLAGS)
53 # Targets
54 #
A. Appendices 189 190 A.22 SRcat.cc

A.22 SRcat.cc 53 int main(int argc, char * argv[])


54 {
55 int exit_code = 0;
1 // SRcat.cc
56 const char * argv1 = 0;
2
57
3 #include <stdio.h>
58 prog = argv[0];
4 #include <stdlib.h>
59
5 #include <string.h>
60 if (argc < 2) exit(-8);
6 #include <assert.h>
61 else argv1 = argv[1];
7
62 if (!strcmp(argv1, "aout")) skip = AOUT;
8 FILE * infile;
63 else if (!strcmp(argv1, "noaout")) skip = 0;
9
64 else exit(-9);
10 enum { MAX_REC_SIZE = 256 };
65
11 enum { AOUT = 0x20 };
66 ROM = new unsigned char[ROMSIZE];
12
67 if (ROM == 0) exit(-1);
13 class SRecord
68
14 {
69 for (int i = 0; i < ROMSIZE; i++) ROM[i] = 0;
15 public:
70
16 SRecord() {};
71 for (int arg = 2; arg < argc; arg++)
17
72 {
18 int readRecord();
73 const char * av = argv[arg];
19 void writeRecord(int rtype);
74 int address = 0;
20 enum { ERR_EOF = -1,
75
21 ERR_BAD_CHAR = -2,
76 if (!strcmp(av, "-dsp_code"))
22 ERR_CHECKSUM = -3
77 {
23 };
78 printf("// This file is automatically generated, don't
24
edit !\n");
25 unsigned int address;
79 if (rom_index == (3*(rom_index/3)))
26 unsigned int size;
80 printf("enum { dsp_code_bytes = %d, dsp_code_words =
27 char data[MAX_REC_SIZE];
%d };\n",
28 private:
81 rom_index, rom_index/3);
29 int type;
82 else
30 int getHeader();
83 printf("#error \"Byte Count not multiple of 3\"\n");
31 int getWord();
84 printf("const char dsp_code[dsp_code_bytes] = {");
32 int getByte();
85
33 int getNibble();
86 for (int i = 0; i < rom_index; i++)
34 void putByte(unsigned int);
87 {
35
88 if (!(i & 15)) printf("\n");
36 unsigned char checksum;
89 printf("0x%2.2X,", ROM[i] & 0xFF);
37 };
90 }
38
91 printf("\n };\n\n");
39 int load_file(const char * filename);
92 }
40 void store_file(unsigned int address, unsigned char * data,
93 else if (!strcmp(av, "-crlf"))
unsigned int size);
94 {
41 void store_odd_even(unsigned int odd, unsigned char * data,
95 crlf = 1;
unsigned int size);
96 }
42 unsigned long compute_crc(unsigned char * data, unsigned int size);
97 else if (!strcmp(av, "-version"))
43
98 {
44 unsigned char * ROM = 0;
99 unsigned long Release = (ROM[0x100] << 24)
45 const char * prog = 0;
100 | (ROM[0x101] << 16)
46 int rom_index = 0;
101 | (ROM[0x102] << 8 )
47 int skip = AOUT;
102 | (ROM[0x103] );
48 int crlf = 0;
103 unsigned long Revision = (ROM[0x104] << 24)
49
104 | (ROM[0x105] << 16)
50 enum { ROMSIZE = 0x00020000 };
105 | (ROM[0x106] << 8 )
51
106 | (ROM[0x107] );
52 // ----------------------------------------------------------------
A. Appendices 191 192 A.22 SRcat.cc

107 fprintf(stderr, "%s: FW Revision -> %u.%u\n", 162 if (infile == 0) return exit_code = -3;
108 prog, Release, Revision); 163
109 } 164 for (;;)
110 else if (!strcmp(av, "-crc")) 165 {
111 { 166 int res = srec.readRecord();
112 unsigned long crc = compute_crc(ROM, ROMSIZE-4); 167 record++;
113 fprintf(stderr, "%s: CRC -> 0x%8.8X\n", prog, 168
crc); 169 switch(res)
114 ROM[ROMSIZE-4] = crc>>24; 170 {
115 ROM[ROMSIZE-3] = crc>>16; 171 case 0:
116 ROM[ROMSIZE-2] = crc>> 8; 172 fprintf(stderr, "%s: S0 %s\n", prog, srec.data);
117 ROM[ROMSIZE-1] = crc; 173 continue;
118 rom_index = ROMSIZE; 174
119 } 175 case 1:
120 else if (!strcmp(av, "-even")) 176 case 2:
121 { 177 case 3:
122 store_odd_even(0, ROM, rom_index); 178 {
123 } 179 if (mini == -1) // first data record
124 else if (!strcmp(av, "-odd")) 180 {
125 { 181 mini = srec.address;
126 store_odd_even(1, ROM, rom_index); 182 fprintf(stderr, "%s: S%d 0x%8.8X ->
127 } 0x%8.8X\n",
128 else if (!strncmp(av, "0x", 2)) 183 prog, res, mini, rom_index);
129 { 184 }
130 if (sscanf(av, "%X", &address) == 1) 185 else if (res != 1 && srec.address != maxi)
131 { 186 {
132 fprintf(stderr, "%s: Storing -> 0x%8.8X\n", 187 fprintf(stderr,
133 prog, address); 188 "%s: Record %d: Gap/Overlap at
134 store_file(address, ROM, rom_index); 0x%8.8X\n",
135 } 189 prog, record, srec.address);
136 else 190 exit_code = -7;
137 exit_code = -2; 191 break;
138 if (exit_code) break; 192 }
139 } 193
140 else // file name 194 maxi = srec.address + srec.size;
141 { 195
142 fprintf(stderr, "%s: Loading %s:\n", prog, av); 196 for (int i = 0; i < srec.size; i++)
143 exit_code = load_file(av); 197 {
144 if (exit_code) break; 198 if (skip)
145 } 199 skip--;
146 } 200 else if (rom_index <= ROMSIZE)
147 201 ROM[rom_index++] = srec.data[i];
148 delete ROM; ROM = 0; 202 else
149 exit(exit_code); 203 {
150 } 204 fprintf(stderr, "%s: S%d above ROM\n",
151 205 prog, res);
152 int load_file(const char * filename) 206 exit_code = -5;
153 { 207 break;
154 SRecord srec; 208 }
155 int mini = -1; 209 }
156 int maxi = -1; 210 }
157 int record = 0; 211 continue;
158 int exit_code = 0; 212
159 int initial_skip = skip; 213 case 7:
160 214 case 8:
161 infile = fopen(filename, "r"); 215 case 9:
A. Appendices 193 194 A.22 SRcat.cc

216 fprintf(stderr, "%s: S%d 0x%8.8X -> 0x%8.8X\n", 270 }


217 prog, res, maxi, rom_index); 271 // ----------------------------------------------------------------
218 break; 272 void store_odd_even(unsigned int odd, unsigned char * data,
219 unsigned int size)
220 default: 273 {
221 fprintf(stderr, "%s: Bad Record S%d\n", prog, 274 unsigned int addr;
res); 275 SRecord srec;
222 exit_code = -5; 276 char * name;
223 break; 277 int i, sl;
224 } 278
225 break; 279 if (odd)
226 } 280 {
227 281 name = "EEPROM.ODD";
228 fclose(infile); 282 addr = 1;
229 fprintf(stderr, "%s: Size 0x%8.8X\n", 283 }
230 prog, maxi-mini-initial_skip); 284 else
231 return exit_code; 285 {
232 } 286 name = "EEPROM.EVE";
233 // ---------------------------------------------------------------- 287 addr = 0;
234 void store_file(unsigned int addr, unsigned char * data, unsigned 288 }
int size) 289
235 { 290 sl = strlen(name);
236 SRecord srec; 291
237 char name[20]; 292 // write S0 record
238 int i, sl, dr, er; 293 srec.address = 0;
239 294 for (i = 0; i < sl; i++) srec.data[i] = name[i];
240 sprintf(name, "Image_0x%8.8X", addr); 295 srec.size = sl;
241 sl = strlen(name); 296 srec.writeRecord(0);
242 297
243 // write S0 record 298 // write S2/S3 records
244 srec.address = 0; 299 for (int idx = 0; idx < size; idx += 32)
245 for (i = 0; i < sl; i++) srec.data[i] = name[i]; 300 {
246 srec.size = sl; 301 srec.address = idx>>1;
247 srec.writeRecord(0); 302 srec.size = 0;
248 303 for (i = addr; i < 32; i+=2)
249 if ((addr+size) <= 0x01000000) { dr = 2; er = 8; } // S2/S8 304 {
250 else { dr = 3; er = 7; } // S3/S7 305 if ((idx+i) >= size) break;
251 306 srec.data[i>>1] = data[idx+i];
252 // write S2/S3 records 307 srec.size++;
253 for (int idx = 0; idx < size; idx += 32) 308 }
254 { 309 srec.writeRecord(1);
255 srec.address = addr+idx; 310 }
256 srec.size = 0; 311
257 for (i = 0; i < 32; i++) 312 // write S9 records
258 { 313 srec.address = 0;
259 if ((idx+i) >= size) break; 314 srec.size = 0;
260 srec.data[i] = data[idx+i]; 315 srec.writeRecord(9);
261 srec.size++; 316 }
262 } 317 // ----------------------------------------------------------------
263 srec.writeRecord(dr); 318 void SRecord::writeRecord(int rtype)
264 } 319 {
265 320 int i;
266 // write S8/S7 records 321 const char * CRLF = "\n";
267 srec.address = 0; 322
268 srec.size = 0; 323 if (crlf) CRLF = "\r\n";
269 srec.writeRecord(er); 324
A. Appendices 195 196 A.22 SRcat.cc

325 checksum = 0; 381 putByte(address);


326 switch(type = rtype) 382 for (i = 0; i < size; i++)
327 { 383 putByte(data[i]);
328 case 0: printf("S0"); 384 checksum = ~checksum;
329 putByte(size+3); 385 putByte(checksum);
330 putByte(address>>8); 386 printf(CRLF);
331 putByte(address); 387 return;
332 for (i = 0; i < size; i++) 388 case 8:
333 putByte(data[i]); 389 printf("S8");
334 checksum = ~checksum; 390 putByte(size+4);
335 putByte(checksum); 391 putByte(address>>16);
336 printf(CRLF); 392 putByte(address>>8);
337 return; 393 putByte(address);
338 394 for (i = 0; i < size; i++)
339 case 1: printf("S1"); 395 putByte(data[i]);
340 putByte(size+3); 396 checksum = ~checksum;
341 putByte(address>>8); 397 putByte(checksum);
342 putByte(address); 398 printf(CRLF);
343 for (i = 0; i < size; i++) 399 return;
344 putByte(data[i]); 400 case 9:
345 checksum = ~checksum; 401 printf("S9");
346 putByte(checksum); 402 putByte(size+3);
347 printf(CRLF); 403 putByte(address>>8);
348 return; 404 putByte(address);
349 405 for (i = 0; i < size; i++)
350 case 2: printf("S2"); 406 putByte(data[i]);
351 putByte(size+4); 407 checksum = ~checksum;
352 putByte(address>>16); 408 putByte(checksum);
353 putByte(address>>8); 409 printf(CRLF);
354 putByte(address); 410 return;
355 for (i = 0; i < size; i++) 411 }
356 putByte(data[i]); 412 }
357 checksum = ~checksum; 413 // ----------------------------------------------------------------
358 putByte(checksum); 414 void SRecord::putByte(unsigned int val)
359 printf(CRLF); 415 {
360 return; 416 printf("%2.2X", val & 0xFF);
361 417 checksum += val;
362 case 3: printf("S3"); 418 }
363 putByte(size+5); 419 // ----------------------------------------------------------------
364 putByte(address>>24); 420 int SRecord::readRecord()
365 putByte(address>>16); 421 {
366 putByte(address>>8); 422 int dat, w, total;
367 putByte(address); 423
368 for (i = 0; i < size; i++) 424 getHeader();
369 putByte(data[i]); 425 checksum = 1;
370 checksum = ~checksum; 426 total = getByte(); if (total < 0) return total;
371 putByte(checksum); 427 switch(type)
372 printf(CRLF); 428 {
373 return; 429 case 0: address = getWord(); if (address < 0) return
374 address;
375 case 7: 430 total -= 2;
376 printf("S7"); 431 break;
377 putByte(size+5); 432
378 putByte(address>>24); 433 case 1:
379 putByte(address>>16); 434 case 9: address = getWord(); if (address < 0) return
380 putByte(address>>8); address;
A. Appendices 197 198 A.22 SRcat.cc

435 total -= 2; 487 default: fprintf(stderr, "\ngetHeader: not 0, 1-3 or 7-9


436 break; [%d]", c);
437 488 return type = ERR_BAD_CHAR;
438 case 2: 489 }
439 case 8: w = getByte(); if (w < 0) return w; 490 }
440 address = getWord(); if (address < 0) return 491 // ----------------------------------------------------------------
address; 492 int SRecord::getWord()
441 address += w << 16; 493 {
442 total -= 3; 494 int b, w;
443 break; 495
444 496 b = getByte(); if (b < 0) return b;
445 case 3: 497 w = getByte(); if (w < 0) return w;
446 case 7: w = getWord(); if (w < 0) return w; 498 return (b<<8) + w;
447 address = getWord(); if (address < 0) return 499 }
address; 500
448 address += w << 16; 501 // ----------------------------------------------------------------
449 total -= 4; 502 int SRecord::getByte()
450 break; 503 {
451 504 int n, b;
452 default: return ERR_BAD_CHAR; // error 505
453 } 506 n = getNibble(); if (n < 0) return n;
454 507 b = getNibble(); if (b < 0) return b;
455 size = total-1; // 1 checksum 508 b += n<<4;
456 509 checksum += b;
457 for (int i = 0; i < total; i++) 510 return b;
458 { data[i] = dat = getByte(); if (dat < 0) return dat; } 511 }
459 data[size] = 0; // terminator if used as string, e.g. for S0 512
records 513 // ----------------------------------------------------------------
460 514 int SRecord::getNibble()
461 if (checksum) return ERR_CHECKSUM; 515 {
462 516 int c;
463 return type; 517
464 } 518 for (;;)
465 // ---------------------------------------------------------------- 519 {
466 int SRecord::getHeader() 520 c = fgetc(infile);
467 { 521 if (c == EOF) return ERR_EOF;
468 int c; 522 if (c > ' ') break;
469 523 }
470 for (;;) 524
471 { 525 c &= 0x7F; // strip parity
472 c = fgetc(infile); 526 if (c < '0') return ERR_BAD_CHAR;
473 if (c == 'S') break; 527 if (c <= '9') return c - '0';
474 if (c == EOF) return type = ERR_EOF; 528 if (c < 'A') return ERR_BAD_CHAR;
475 if (c <= ' ') continue; // whitespace 529 if (c <= 'F') return c + 10 - 'A';
476 return type = ERR_BAD_CHAR; 530 if (c < 'a') return ERR_BAD_CHAR;
477 } 531 if (c <= 'f') return c + 10 - 'a';
478 532 return ERR_BAD_CHAR;
479 // here we got an 'S'... 533 }
480 switch(c = fgetc(infile)) 534
481 { 535 // ----------------------------------------------------------------
482 case '0': 536 unsigned long compute_crc(unsigned char * ROM, unsigned int size)
483 case '1': case '2': case '3': 537 {
484 case '7': case '8': case '9': 538 unsigned long D5 = 0x00A00805; // CRC-32 polynomial
485 return type = c - '0'; 539 unsigned long D1 = 0xFFFFFFFF; // preset CRC value to all ones
486 540 unsigned long D2; // data
541 unsigned long D3; // temp data
A. Appendices 199 200 A.22 SRcat.cc

542 unsigned long D4; // bit counter


543
544 for (unsigned int D0 = 0; D0 < size; D0 += 4) // long loop
545 {
546 D2 = (ROM[D0] << 24) & 0xFF000000
547 | (ROM[D0+1] << 16) & 0x00FF0000
548 | (ROM[D0+2] << 8) & 0x0000FF00
549 | (ROM[D0+3] ) & 0x000000FF;
550
551 for (D4 = 0; D4 < 32; D4++) // bit loop
552 {
553 D3 = D1 ^ D2;
554 D1 += D1;
555 D2 += D2;
556 if (D3 & 0x80000000) D1 ^= D5;
557 }
558 }
559 return D1;
560 }
561 // ----------------------------------------------------------------
202 Index

Index
Symbols Data bus contention .......................................... 36 L Panic() (class os) ........................ 80, 84, 143, 146
.DATA ............................................................... 81 delete ................................................................ 77 libgcc ................................................................ 77 Peek() (class RingBuffer) ....................... 151, 153
.TEXT ............................................................... 81 DeSchedule() .................................................... 19 Library ................................................................ 8 Peekc() (class SerialIn)..................... 70, 166, 168
__main() ........................................................... 84 Dsched() (class Task) ......................... 72, 79, 138 Linking ............................................................... 7 Poll() (class Semaphore) .......................... 48, 150
_consider_ts .................................. 42, 50, 76, 136 DUART..................................................... 35, 171 Loading of programs ........................................ 11 Pollc() (class SerialIn) ...................... 69, 166, 167
_deschedule .............................................. 42, 133 duart.hh..................................................... 35, 171 Polled_IO (class os) ................................... 66, 71
_duart_isr .......................................... 73, 125, 132 Dummy cycle ................................................... 37 M PolledGet() (class Queue) ...................... 151, 153
_exit().............................................................. 145 Dynamic bus resizing ....................................... 37 main .................................................................. 89 PolledGet() (class Queue_Gsem) ........... 152, 154
_fatal ........................................................... 80, 82 main() ........................................... 72, 85, 92, 141 PolledGet() (class Queue_Gsem_Psem) 152, 155
_idle_stack ...................................................... 136 E malloc ......................................................... 77, 93 PolledGet() (class Queue_Psem)............ 152, 154
_IUS_top................................................... 83, 136 edata ................................................................. 77 malloc()..................................................... 78, 145 PolledGet() (class RingBuffer)................. 53, 151
_null .................................................. 82, 123, 130 event ................................................................. 55 Memory map .................................................... 35 PolledGetMessage() (class Task) ................... 137
_on_exit .................................................... 84, 132 Exception stack frame ...................................... 42 Message PolledPut() (class Queue)....................... 151, 153
_readByteRegister_HL ................................... 136 Execution of programs ..................................... 11 Message().............................................. 54, 157 PolledPut() (class Queue_Gsem) ........... 152, 154
_reset................................................. 83, 124, 131 Message.hh ............................................... 54, 157 PolledPut() (class Queue_Gsem_Psem). 152, 156
_return_from_exception ................. 42, 43, 76, 86 F Monitor PolledPut() (class Queue_Psem) .................... 152
_sdata .............................................................. 136 FIFO ................................................................. 26 setupMonitorTask()....................... 89, 102, 176 PolledPut() (class RingBuffer) ................. 53, 151
_Semaphore_P ................................................ 135 free() ................................................... 77, 78, 145 Monitor.cc............................................... 178, 187 PolledPut(class Queue_Psem)........................ 155
_Semaphore_V ............................................... 135 free_RAM......................................................... 77 Monitor.hh ...................................................... 177 Pre-emptive multitasking.................................. 12
_set_interrupt_mask ....................................... 136 msgQ (class Task)....................................... 55, 79 Print() (class SerialOut).................... 66, 159, 162
_SS_top..................................................... 83, 136 G MyName() (class Task) ............................ 79, 138 print_form() (class SerialOut ......................... 159
_stop ................................................... 85, 86, 133 Get() ................................................................. 26 MyPriority (class Task) ............................ 79, 138 print_form() (class SerialOut) ........................ 163
_super_stack ................................................... 136 Get() (class Queue_Gsem) ..................... 152, 154 Priority() (class Task) ............................... 79, 138
_sysTimeHi..................................................... 136 Get() (class Queue_Gsem_Psem)........... 152, 155 N Privilege violation ............................................ 39
_sysTimeLo .................................................... 136 Getc() (class SerialIn)....................... 69, 166, 167 Name() (class Task) .................................. 79, 138 Privileged instructions ...................................... 39
_writeByteRegister ......................................... 136 Getdec() (class SerialIn)......................... 166, 168 new.................................................................... 77 Processor .......................................................... 34
Gethex() (class SerialIn)......................... 166, 168 Next() (class Task).................................... 79, 138 Put (class Queue_Psem) ................................. 155
A GetItem() (class RingBuffer)............ 52, 151, 153 Not_Initialized (class os) .................................. 71 Put() .................................................................. 26
ApplicationStart.cc ......................................... 176 GetMessage() (class Task)........................ 79, 137 Put() (class Queue_Gsem_Psem) ........... 152, 155
autolevel............................................................ 73 getOverflowCounter() (class SerialIn)70, 166, 169 O Put() (class Queue_Psem) .............................. 152
Autovector ........................................................ 36 getSystemTime() (class os) .............. 80, 143, 146 Object file ........................................................... 7 Putc() (class SerialOut) .................... 65, 159, 161
GNU ................................................................. 77 os PutItem() (class RingBuffer) ............ 52, 151, 153
B getSystemTime()........................... 80, 143, 146
Baudrate............................................................ 72 H init() .............................................. 71, 143, 147 Q
BSS ..................................................................... 7 Hardware initialization ..................................... 71 INIT_LEVEL........................................ 71, 143 Queue ................................................. 26, 51, 151
Busy wait .................................................... 19, 28 Hardware memory management .... 39, 56, 57, 78 init_level ............................................... 71, 143 PolledGet() ......................................... 151, 153
Hardware model ............................................... 34 initChannel() ................................. 80, 143, 147 PolledPut() .......................................... 151, 153
C initDuart() ..................................... 71, 143, 147 Queue() ....................................................... 151
Channel (enum) .............................................. 158 I initLevel() ................................................... 143 Queue.cc ................................................... 51, 153
Channel variable ............................................... 64 Idle task ............................................................ 73 INT_MASK ................................................ 144 Queue.hh .................................................. 51, 151
Channels.hh .............................................. 62, 158 INBUF_0_SIZE.............................................. 175 Interrupt_IO .................................................. 71 Queue_Gsem
checkStacks() (class Task)...................... 138, 141 INBUF_1_SIZE.............................................. 175 Not_initialized .............................................. 71 Get().................................................... 152, 154
class ................................................................ 151 init() (class os) .................................. 71, 143, 147 Panic()..................................... 80, 84, 143, 146 PolledGet() ......................................... 152, 154
Message ................................................ 54, 157 INIT_LEVEL (class os) ........................... 71, 143 Polled_IO ................................................ 66, 71 PolledPut() .......................................... 152, 154
Monitor ......................................................... 79 init_level (class os) ................................... 71, 143 readDuartRegister() .............................. 80, 143 Queue_Gsem()............................................ 152
os................................................................. 143 initChannel() (class os)............................. 80, 147 resetChannel()..................................... 143, 147 Queue_Gsem_Psem
Queue ...................................................... 34, 51 initChannle() (class os)................................... 143 sbrk()........................................................... 143 Get().................................................... 152, 155
Queue_Gsem............................................... 151 initDuart() (class os)......................... 71, 143, 147 set_INT_MASK() ........................... 47, 72, 144 PolledGet() ......................................... 152, 155
Queue_Gsem_Psem .................................... 152 initLevel() (class os) ....................................... 143 setBaudRate() ............................... 80, 143, 148 PolledPut() .......................................... 152, 156
Queue_Psem ............................................... 152 INT_MASK (class os).................................... 144 setSerialMode()................................... 143, 148 Put() .................................................... 152, 155
RingBuffer ............................................ 51, 151 Interprocess communication ............................ 54 Stop() ...................................... 72, 85, 143, 146 Queue_Gsem_Psem() ................................. 152
Semaphore ............................................ 34, 150 Interrupt assignment ......................................... 36 top_of_RAM() ............................................ 143 Queue_Psem
SerialIn.................................................. 34, 166 Interrupt mask .................................................. 72 writeRegister() .............................. 80, 144, 146 PolledGet() ......................................... 152, 154
SerialOut ............................................... 34, 159 Interrupt service routine ................................... 73 os.cc ................................................................ 145 PolledPut() .......................................... 152, 155
Task ........................................... 34, 41, 87, 137 Interrupt_IO (class os)...................................... 71 os.hh................................................................ 143 Put() .................................................... 152, 155
Compiling ........................................................... 7 IsEmpty() (class RingBuffer) ......................... 151 OUTBUF_0_SIZE.......................................... 175 Queue_Psem() ............................................ 152
crt0.S............................................. 34, 42, 47, 130 IsEmpty() (class SerialOut) .............. 66, 159, 162 OUTBUF_1_SIZE.......................................... 175
Current() (class Task) ............................... 79, 138 IsFull() (class RingBuffer) ............................. 151 R
P RAMbase.................................................. 35, 175
D K P() ..................................................................... 22 RAMend ......................................................... 175
DATA ............................................................ 7, 77 Kernel architecture ........................................... 33 P() (class Semaphore)............................... 46, 150 RAMsize................................................... 35, 175
readDuartRegister() (class os) .................. 80, 143
Index 203 204 Index

red LED ............................................................ 80 startup code .................................................... 130 W


resetChannel() (class os)......................... 143, 147 Status() (class Task).................................. 79, 138 write() ............................................................. 145
Ring Buffer ....................................................... 26 Stop() (class os) .................................. 72, 85, 143 writeRegister() (class os) .................. 80, 144, 146
RingBuffer ........................................................ 51 Supervisor mode............................................... 39
~RingBuffer() ............................... 52, 151, 153 Supervisor stack ............................................... 42
GetItem()....................................... 52, 151, 153 System.config ........................................... 35, 175
IsEmpty() .................................................... 151
IsFull() ........................................................ 151 T
Peek() .................................................. 151, 153 Task ................................................................ 140
PolledGet()............................................ 53, 151 checkStacks()...................................... 138, 141
PolledPut() ............................................ 53, 151 Current() ............................................... 79, 138
PutItem() ....................................... 52, 151, 153 Dsched() ......................................... 72, 79, 138
RingBuffer().................................. 51, 151, 153 GetMessage()........................................ 79, 137
ROMbase .................................................. 35, 175 msgQ....................................................... 55, 79
ROMsize ................................................... 35, 175 MyName() ............................................ 79, 138
RUN............................................................ 22, 23 MyPriority().......................................... 79, 138
RUN (class Task) .................................. 44, 75, 79 Name() .................................................. 79, 138
Next().................................................... 79, 138
S PolledGetMessage() ................................... 137
sbrk()................................................. 77, 143, 145 Priority() ............................................... 79, 138
SchedulerRunning() (class Task).................... 138 RUN.................................................. 44, 75, 79
Section ................................................................ 7 SchedulerRunning().................................... 138
Semaphore .......................................... 21, 46, 150 SendMessage() ..................................... 55, 138
P() ......................................................... 46, 150 setPriority()................................................. 138
Poll() ..................................................... 48, 150 Sleep()..................................... 75, 79, 138, 142
Semaphore().......................................... 46, 150 Start() .................................................... 79, 138
V()......................................................... 49, 150 STARTED..................................................... 79
Semaphore.hh ........................................... 46, 150 Status().................................................. 79, 138
SendMessage() (class Task)...................... 55, 138 Task() ...................................... 87, 91, 137, 140
Serial I/O .......................................................... 59 TaskIDs[] ...................................... 88, 138, 140
SerialIn ............................................................. 69 Terminate() ............................. 79, 90, 138, 141
~SerialIn()........................................... 166, 167 TERMINATED............................................. 79
Getc() ............................................ 69, 166, 167 userStackBase() .................................... 79, 138
Getdec() .............................................. 166, 168 userStackSize() ..................................... 79, 138
Gethex() .............................................. 166, 168 userStackUsed().................................... 79, 142
getOverflowCounter() ................... 70, 166, 169 Task switching .................................................. 39
Peekc() .......................................... 70, 166, 168 Task.cc ............................................................ 140
Pollc() ........................................... 69, 166, 167 Task.hh ........................................................... 137
SerialIn() ............................................. 166, 167 TaskId.hh ........................................................ 170
SerialIn.cc ....................................................... 167 TaskIDs[] .................................................. 88, 140
SerialIn.hh ...................................................... 166 TaskIDs[} (class Task).................................... 138
SerialOut Terminate (class Task).................................... 138
~SerialOut() ........................................ 159, 160 Terminate() (class Task) ..................... 79, 90, 141
IsEmpty() ...................................... 66, 159, 162 TERMINATED (class Task)............................. 79
Print() ............................................ 66, 159, 162 TEXT.................................................................. 7
print_form() ........................................ 159, 163 top_of_RAM() (class os)................................ 143
Putc()............................................. 65, 159, 161 TxEnabled (class SerialOut)............................. 65
SerialOut() .................................... 63, 159, 160 TxEnabled_ (class SerialOut)................... 74, 160
TxEnabled_..................................... 65, 74, 160
SerialOut.cc .............................................. 63, 160 U
SerialOut.hh.............................................. 64, 159 unput() .............................................................. 52
set_INT_MASK() (class os)............... 47, 72, 144 unputc() ............................................................ 70
setBaudRate() (class os) ................... 80, 143, 148 User mode ........................................................ 39
setPriority() (class Task) ................................. 138 userStackBase() (class Task) .................... 79, 138
setSerialMode() (class os) ...................... 143, 148 userStackSize() (class Task) ..................... 79, 138
setupApplicationTasks...................................... 89 userStackUsed() (class Task).................... 79, 142
setupApplicationTasks() ..... 85, 89, 102, 137, 176
setupMonitorTask() (class Monitor) . 89, 102, 176 V
Sleep() (class Task)..................... 75, 79, 138, 142 V() .................................................................... 22
S-record .............................................................. 9 V() (class Semaphore) .............................. 49, 150
Start() (class Task) .................................... 79, 138
STARTED (class Task)..................................... 79

You might also like