Updated for the Sun 3 and converted to HTML, January 1995 by Barry Brachman
The second connection each of the Xinu machines has is an ethernet port. The ethernet is a high-speed network that the Xinu machines can use for communicating with each other, and with a boot server that runs Unix. The boot server, in turn, is connected to the network of HP systems that form the cross-development environment. The transfer of a compiled Xinu system from an HP to a Xinu machine, mentioned above, takes place over this network.
To allow the Xinu machines to be used from locations other than the lab, the Xinu machines' console ports are not connected directly to terminals. Software is provided that allows a user on any HP machine to communicate with these console ports, effectively making his terminal a temporary console for the Xinu machine.
#include <conf.h>
main()
{
printf("Hello world!\n");
}
This program may be compiled and linked with the Xinu operating system, with the command:
xcc hello.c
Note: The programs xcc and getxinu (described below) reside in the directory ~xinu/bin. It is probably desirable to add this directory to one's command search path by editing the .cshrc file in one's home directory, adding ~xinu/bin to the list of directories in the set path command.If no errors occur, this creates an executable image in the file a.out. To run this on a Xinu machine, use the getxinu command:
getxinu a.outThis command has the following effects. First, it attempts to obtain a free Xinu machine. If all machines are busy, then the user is queued, and getxinu will pause until a machine is free. During this waiting period, the user may type s to obtain a status report indicating how many users are waiting to use Xinu machines, or q to quit waiting.
Once a machine is available, a message indicating which one the user has been assigned to is printed, and the user's terminal is effectively connected to the indicated Xinu machine, becoming its console.
Loading and running the Xinu program (a.out) is now a two-step procedure. First, the Xinu machine must be put in a state in which its PROM monitor has control. Pressing RETURN a couple of times can be done to check this; when the monitor is running it will respond with its prompt ``>''. If something else, or nothing, occurs, then the machine is either running someone else's leftover Xinu program, or crashed. Typing \b (a backslash followed by a ``b'') sends a BREAK to the Xinu machine; this usually will (after a few seconds) cause the machine to stop whatever it is doing, and return to the monitor. If this repeatedly fails to get the monitor's attention, the machine must be power-cycled. Turn the appropriate Xinu machine off, wait 10 seconds, then turn it on again. The machine, once turned on, will sit for a while (checking memory etc.) and then print some messages on the console while it attempts to boot Unix. This boot attempt should be aborted by sending a break (\b) as before, which now is fairly certain to get the monitor's attention.
The Xinu program may now be loaded and run by issuing an appropriate boot command to the monitor. This command has the form:
ble() your-login/prognamewhere your-login is the user's login name, and progname is the program name (a.out in this example) provided with the getxinu command.
The Xinu program should now run, printing in this case:
Xinu Version 7.0 SUN3 (02/29/88)
720895 bytes real mem
33204 bytes Xinu code
clock enabled
Hello world!
All user processes have completed.
For the convenience of later users of the Xinu machine, who may be signed on remotely and not be able to power-cycle the machine, it is a good idea to return the machine to the monitor state before typing \q to quit getxinu.
Getxinu may be instructed to obtain a specific Xinu machine with the -m option; e.g. getxinu -m xinu1 a.out to run a.out on machine xinu1. This is usually unnecessary but may be important when running programs involving the Xinu networking system.
Please release your Xinu machine as soon as you are finished with it.
Others may be waiting to use it.
-V -v -c -o -D -I -O -S -P -sAll of the options behave as they do for gcc, except for -V and -v. The -v flag is for verbose output; it causes xcc to spew out a lot of information. The -V flag is the version flag, and if used, the following argument is taken as a version-identifier, which indicates which version of Xinu is to be linked with the user's programs. The versions currently available are:
Xinu consists of a large collection of routines, most written in C, with some in 680X0 assembler language. The source for these routines is in the directory ~xinu/xinu.sun3/src/sys/sys. The header files are in ~xinu/xinu.sun3/src/sys/h. Usually only one or a few routines live in a single source file, whose name is that of the most important function in the file. For example, the kill() (process destruction) routine resides in ~xinu/xinu.sun3/src/sys/sys/kill.c. All Xinu routines have been compiled and linked to form a library that is automatically searched by xcc. To modify one or more of the Xinu routines, the procedure is to make a copy of the appropriate source files in a local directory, change this copy, and provide this file on the command line to xcc, along with the test program. This will cause the changed version of the file to be used, and the original one in the library to be ignored.
As an example, suppose we wish to make a change to the kill() routine. First, we make a copy in our home directory (or some subdirectory):
cp ~xinu/xinu.sun3/src/sys/sys/kill.c .Now we modify this file, and prepare a test program:
vi kill.c
...
vi mytest.c
...
Now we compile our test program and changed kill() routine,
and link them with the remainder of Xinu:
xcc -o mytest mytest.c kill.c(The option -o mytest causes the result to be placed in the file mytest rather than a.out.)
Now the program can be run as before:
getxinu mytest
getxinu prognamecauses the file progname to be copied to the directory
~xinu/tftpboot/your-login/prognameon the boot server. The monitor command
ble() your-login/prognameinstructs the machine to load and run the named executable file from the directory ~xinu/tftpboot/your-login. You should remove old copies of the executables placed in that directory to conserve disk space. But in the mean time, it is possible to maintain a set of Xinu executables in one's subdirectory of ~xinu/tftpboot, running any of them when desired with the appropriate ble() command.
Getxinu may be run with no program argument (i.e. simply getxinu or getxinu -m machine-name), which will connect the user to a Xinu machine without copying an executable.
Getxinu may also be run with more that one program argument (i.e. getxinu progone progtwo progthree), which will copy all the named files into one's subdirectory and connect the user to a Xinu machine.
The 68010 number of registers, is capable of moving or operating on data which resides either in these registers or in main memory, can receive interrupts from external sources, etc. A notable feature of the 68000 series processors is that they differentiate between registers that hold data and those that hold addresses. There are 8 data registers conventionally referred to as d0 through d7. There are 7 standard address registers (a0 through a6) and the special stack address register a7. In addition, there is a status register (sr) and the program counter (pc). Register a7 is set apart from the others because it is actually associated with 2 registers. The register to which it refers depends on whether the processor is in supervisor or user mode. In user mode certain processor operations are treated as privileged and an attempt to execute them results in an exception being generated. The dual stack registers provide further security by ensuring that when the processor is in user mode it cannot access the supervisor stack.
The distinction between address registers and data registers can be seen clearly in the instruction set of the processor. The operations that can be performed on address registers are a subset of those available for data registers, while any form of indirect addressing must use an address register. Since the contents of one type of register can be moved to the a register of the other type, however, there are no real restrictions on the manipulations that can be performed.
When running Xinu, you don't have to be concerned with running the processor in user mode. When the Sun 2 boots your Xinu code it places the machine in supervisor mode and the Xinu system never places the machine in user mode while it is operating. The effect of this is that any of your code will always run in supervisor mode. One of the effects of being in supervisor mode is that you can access the system byte of the status register. The status register is 16 bits wide. The lower 8 bits are the user byte, and their interpretations are given in the following table:
Bit Meaning
=============-=====
0 Carry Flag
1 Overflow Flag
2 Zero Flag
3 Negative Flag
4 Extend Flag
5-7 Not Used
Status Register User Byte
As can be seen from the table, the user byte simply contains
the arithmetic flags (the extend bit is essentially identical to the carry
bit).
The upper bits are the system byte and their interpretations
are given in the following table:
Bit Meaning
==========================
0-2 Interrupt Level Mask
3-4 Not Used
5 Supervisor Mode Flag
6 Not Used
7 Trace Mode Flag
Status Register System Byte
The supervisor mode bit is used to control whether the processor is
in supervisor mode or not (bit = 1 --> supervisor mode),
while the trace mode bit acts similarly.
When in trace mode, a trace exception
is generated after each instruction is executed.
The first three bits are used to form an interrupt priority level.
They are interpreted as a number from 0 through to 7.
When set to a given level N,
all interrupts not greater than N are ignored.
More information will be given on interrupt levels in the next section.
The invocation of an exception handler is similar to a C function call. In fact, C routines can be used as exception handlers as long as the programmer is aware of the differences between invocation as an exception handler and invocation as a C function. The first difference is that all exception handling code is executed in supervisor mode. The second difference is that the stack format established for an exception handler is not like that established for a function. When an exception occurs, the processor switches to supervisor mode and pushes the status register and then the program counter onto the stack. This differs from a function call where only a return address is pushed onto the stack. Because of this difference, the return from an exception handler cannot be achieved via the normal ``return from subroutine'' instruction (rts). Instead, the ``return from exception'' instruction (rte) must be used.
To accommodate these differences, Xinu uses assembler stubs (called interrupt dispatchers ) for its exception handlers. These stubs usually do no more than set up the stack appropriately and call a C language routine. When control returns from the C routine, they execute an rte instruction to complete exception processing.
It was noted above that the status register contains three bits that are used to form an interrupt level number. When an external device signals an interrupt to the processor, it provides both an exception number (an offset into the exception vector table) and an exception level (from 0 through to 7). The level of the interrupt is compared with the interrupt mask from the status register, and if the interrupt level is not greater, it is not acknowledged until the interrupt mask is lowered. Additionally, when an interrupt of a given level occurs, the interrupt level mask is set to the level of the interrupt so that lower priority interrupts cannot occur. Of course, the interrupt mask can be manipulated directly by altering the value of the interrupt mask bits in the status register. This, in fact, is what Xinu's disable() and enable() routines do. The disable() routine stores the value of the status register in a location provided by the caller, and then sets the interrupt level mask to 7. The enable() routine restores the status register from the location provided by the caller (presumably the same one as used in the disable() call).
Note: The disable() and restore() routines for the version of Xinu that you are using are different from those used in Comer's book. Specifically, the parameter to each routine is simply a short in the book, while you must declare the parameter to the routines to be a STATWORD.
As a final note, both the 68010 and the 68020 support memory management hardware, and have mechanisms for dealing with page faults. The handling of these exceptions is more complicated than outlined above because the processor may be in the middle of an instruction when the fault occurs and it needs to be capable of restarting that instruction. This requirement means that a great deal of state information needs to be pushed on the stack when a page fault occurs. The stack format for exceptions is thus not as simple as in the explanation given above. Refer to a text on the 68010 or 68020 for more information about exception handling on these processors.
xcc -S test.c
causes the compiler to generate assembly code for your program
(putting it in test.s)
rather than generating an executable.
The compiler passes arguments to a subroutine on the stack. These arguments are pushed onto the stack in reverse order before the function call is made. For example, consider the following C fragment:
main()
{
int a, b, c;
foo(a, b, c);
}
int foo(x, y, z)
int x, y, z;
{
int d, e;
d = x + z + e;
return(e);
}
The call to the function foo() generates the code shown below:
movl a6@(-0xc),sp@-
movl a6@(-0x8),sp@-
movl a6@(-0x4),sp@-
jbsr _foo
lea sp@(0xc),sp
rts
The instruction movl a6@(-0xc),sp@- pushes the value of the
variable c onto the stack.
Similarly, movl a6@(-0x8),sp@-
pushes the value of the variable b onto the stack.
The function is invoked with the instruction jbsr _foo (note
that all C names are prepended with an underscore in assembler).
The instruction lea sp@(0xc),sp is an obscure
(though efficient) mechanism for adding the (hexadecimal) value
0xc to the stack pointer, effectively popping off the
arguments that were pushed onto the stack when foo()
was called.
For the above code to make much sense, you'll have to be able to make sense of the various addressing modes of the 68000. An instruction like movl a6@(-0xc),sp@- means take the value in address register 6, add the constant -0xc (-12), fetch the contents of the memory address which results from this computation, place them in the memory location referred to by the stack pointer, and then decrement the value of the stack pointer by 4. In general, the symbol @ means to take a value and refer to the memory location indicated by that value. Values in parentheses are offsets that are to be added to registers before dereferencing occurs. Trailing minus or plus signs indicate that the value of a register is to be decremented or incremented by a certain amount as a side effect of the instruction.
An interesting point about the above example is that even though parameters are passed on the stack (recall that the stack pointer is a7), the local variables are referred to via offsets from register a6. The C compiler that you use for your Xinu programs uses register a6 as a ``frame pointer''. This frame pointer provides a convenient pointer into the stack from which a routine's local variables and parameters may be accessed. The format of this stack frame is as shown below:
+-----------------------+
SP | |
+-----------------------+
(-n*4) | local variable n |
+-----------------------+
. .
. .
. .
+-----------------------+
(-8) | local variable 2 |
+-----------------------+
(-4) | local variable 1 |
+-----------------------+
A6 | old A6 |
+-----------------------+
(+4) | return address |
+-----------------------+
(+8) | parameter 1 |
+-----------------------+
(+12) | parameter 2 |
+-----------------------+
. .
. .
. .
+-----------------------+
(+n*4+4) | parameter n |
+-----------------------+
At any given time during the execution of a process, its stack consists
of a series of these stack frames piled end to end, with the sp
pointing just under the local variables of the ``active'' stack frame.
The active stack frame belongs to the procedure which the process
is currently executing.
Thus, the active stack frame contains the
parameters to, and local variables of, the procedure currently being
executed by a given process.
The stack frame above the active stack frame
belongs to the caller of the active procedure, and so on.
Maintaining register a6 so that these relationships hold is achieved via a special ``prologue'' of assembler instructions prepended to each routine. Consider the code that is generated for the routine foo() of the C code given above:
.globl _foo
_foo:
link a6,#0
addl #-LF24,sp
moveml #LS24,sp@
movl a6@(0x8),d0
addl a6@(0x10),d0
addl a6@(-0x8),d0
movl d0,a6@(-0x4)
movl a6@(-0x8),d0
jra LE24
LE24:
unlk a6
rts
LF24 = 8
LS24 = 0x0
The instruction link a6,#0 takes the value of
a6
and stores it in the memory location referred to by the
stack pointer.
The value of the stack pointer is then copied
into a6, and then the stack pointer is decremented by 4.
The instruction addl #-LF24,sp decrements the stack
pointer by 8 (since that is the value of the symbol LF24),
which allocates space on the stack for 8 bytes worth of local variables.
At the end of the routine, the instruction unlk a6 reverses
the above procedure, restoring the old value of a6 and
setting the stack pointer to the value it had at entry into the routine.
This information should be sufficient to allow you to interface assembler and C routines to each other, and will help shed light on what the various assembler routines that constitute a portion of Xinu, are doing.
Almost all the information relating to the structure of the device management system is contained in the file Configuration which resides in the same directory as all the Xinu source files. This file lists all the devices that constitute the Xinu device subsystem, and indicates which routines correspond to the basic device operations (open, close, read, write, etc.), where the device's control register is, and which interrupt vector it uses. To add or remove devices from the Xinu system, all that is required is to write the various routines to handle the basic device operations, write an interrupt handler if it is a real device, and add a description of the device to the configuration file. In the same directory as the configuration file, there is a program called config that reads the configuration file and produces two other files: conf.c and conf.h. Once these new files have been generated, Xinu can be recompiled and will recognize the new device.
This structure is intended to make it very simple to alter the configuration of the Xinu system, and to isolate hardware dependencies. Unfortunately, the version of Xinu we are actually running has been hacked to a certain degree, and in various places the values defined in the configuration file are not used. For example, in the file ttyinit.c which contains the code to initialize the tty device, you can see that the constant SVECTOR is used in a call to the routine set_evec() which is used to give a value to an entry in the processor's exception vector table. What should actually be used here is the vector number from the device structure, as SVECTOR is a constant defined in the file slu.h. If an individual attempted to reconfigure the tty port Xinu used via a change to the configuration file, he would would probably be somewhat discouraged to find that his attempts were in vain because of the incorrect manner in which the interrupt vector is set up in ttyinit.c. As we have time we will attempt to remove some of these bugs, and we would appreciate your help in spotting any more.
Given the above description of the configuration file, you might have a guess at how Xinu handles the basic device operations. When for instance, you do an open() call on a given device you pass a device number to Xinu's open() routine. The open routine uses this number to index into a table of device descriptors, from which it obtains the address of the open routine for that specific device (the table of device descriptors is declared and initialized in the file conf.c, which you will recall was generated by the config program). It then calls that routine, passing it whatever additional parameters that you provided. Initialization of devices works by a similar mechanism. In the configuration file, each device has an initialization routine associated with it. When the system is started up, one of the first things that it does is to scan through the device descriptor table and call all the initialization routines found there. When the initialization routine is called, it receives as a parameter its entry from the device descriptor table. From this entry it can obtain information such as the location of the control register for the device, and which interrupt vector it is to use.
This brings us to the last piece of information regarding the device management system in Xinu: interrupt processing. As was just noted, each device initialization routine is provided (indirectly) with the interrupt vector that is to be used for that device. All the initialization routine has to do is call the routine set_evec() which will place a pointer to a given routine into the indicated entry of the exception vector table. Once this has taken place, any interrupt that occurs will result in that routine being called. Typically, these interrupt service routines are just short assembler language stubs which call C language routines which do the real work. The reason that the assembler stubs are required is that while a C language routine terminates with a ``return from subroutine'' instruction, an interrupt handler must terminate with the ``return from exception'' instruction. In addition, the assembler routine may want to preprocess the interrupt to a certain degree.
A short note should be made at this point of the difference between the version of Xinu you are using (Version 7.0) and the version discussed in Comer's book (Version 6.0). The older Xinu version used a statically initialized vector table. The static definitions for the table were found in the file lowcore.s. Since the vector table in the newer version of Xinu is dynamically initialized, the file lowcore.s no longer exists. It would not, therefore, be worthwhile to spend much time looking for it.
To gain a more complete understanding of the Xinu device management system, look at the configuration file, some device initialization routines, and some of the code that handles a particular device call, such as write(). Once you have done this you should have a good feel for the manner in which the various components of the Xinu system interact with one another.
Each half of the 8530 logically contains 16 registers, but these registers are not mapped onto 16 memory locations in the Sun 2. Instead, each half has only 2 memory-mapped registers. The first of these is referred to as the control register, while the second is referred to as the data register. Accessing one of the chip's logical registers is a two step process:
struct zscc_device
{
unsigned char zscc_control;
unsigned char :8; /* pad byte */
unsigned char zscc_data;
unsigned char :8; /* pad byte */
};
The memory locations to which the 8530 registers are mapped, are found in
the configuration file.
Hardware dependent values can also be found in
the file slu.h.
In the latter file, the address
of only one half of the 8530 is provided (it is the preprocessor constant
SERIAL0_BASE).
This half of the 8530 is externally connected to serial port A on the Sun 2.
Both files also contain values for the interrupt vector offset used by the
8530.
NOTE: The symbolic names given for each bit are located in the file zsreg.h.
The layout for register 0 (for a read operation) is shown in the following table. This register provides the status information that is required for using the 8530 in a polled (as opposed to interrupt driven) environment.
Bit Meaning
0 ZSRR0_RX_READY, if 1 indicates that a received
character is available
1 Not Used
2 ZSRR0_TX_READY, if 1 indicates that the device
is ready to transmit a character
3-6 Not Used
7 ZSRR0_BREAK, if 1 indicates that a break has
been detected
Register 0 (read)
On writes, register 0 behaves a little differently than most of the
SCC control registers, which can, like register 0, be regarded simply
as an array of bits.
Instead, a number of commands can be written into
the register.
A subset of these commands are given in the following table:
ZSWR0_RESET_ERRORS (0x30) -- reset the error flags
ZSWR0_RESET_STATUS (0x10) -- reset the status bits
ZSWR0_CLR_INTR (0x38) -- acknowledge interrupt
Register 0 (write)
A further interesting point is that the control register of the SCC
is, in fact, write register 0.
That is, to issue one of the above
commands all you need do is write the given command into the
SCC control register.
Commands 0x00 through 0x0f simply tell the
SCC to prepare to read/write one of its internal registers on the next
access to the control register.
The layout for register 1 (for read operations) is given in the next table. This register provides information on device errors. Note that these error bits are latched and must be cleared by issuing the ZSWR0_RESET_ERRORS command.
Bit Meaning
0-3 Not Used
4 ZSRR1_PE, if 1 indicates a parity error
5 ZSRR1_DO, if 1 indicates a data overrun
6 ZSRR1_FE, if 1 indicates a framing error
Register 1 (read)
On write operations, register 1 allows control over the enabling
and disabling of interrupts.
The layout of this register is shown in the following table:
Bit Meaning
0 ZSWR1_SIE, if 1 enable interrupts when the
status of the device changes
1 ZSWR1_TIE, if 1 enable transmitter interrupts
2-3 Not Used
4 ZSWR1_RIE, if 1 enable receiver interrupts
5-7 Not Used
Register 1 (write)
Register 2 (for both read and write operations) contains
the interrupt vector offset for the device.
Note that the device generates only one interrupt even though
there are numerous conditions that trigger the generation of
an interrupt.
Read register 3 contains status bits that allow
the cause of an SCC interrupt to be determined.
Register 3 (for read operations) contains status information pertaining to interrupts. Its layout is given in the next table.
Bit Meaning
0 ZSRR3_IP_B_STAT, if 1 indicates that a status
interrupt is pending on channel B
1 ZSRR3_IP_B_TX, if 1 indicates that a transmitter
interrupt is pending on channel B
2 ZSRR3_IP_B_RX, if 1 indicates that a receiver
interrupt is pending on channel B
3 ZSRR3_IP_A_STAT, this and the following 2
bits serve the same function as bits 0-2
but for channel A rather than B
4 ZSRR3_IP_A_TX
5 ZSRR3_IP_A_RX
6-7 Not Used
Register 3 (read)
This register is a bit odd in that it contains information about both
devices in the SCC.
Furthermore, it can only be accessed from the A
device's control register.
This means using the B device from within
Xinu would require something of a hack because the B device's control
block would have to contain a pointer to the A device's control block
in order for it to be able to access this register.