UZI180

UZI180 is a unix-like operating system for Z180-based machines. Derived from UZI (a Unix Edition 7 lookalike system for Z80 machines), UZI180 further benefits from Z180-specific features like 1Mb addressing capabilities, internal timer-counter, etc. The original version of UZI180 from H. Bower was available from the TCJ web site (now gone). Mr Bower has also written an excellent introduction to UZI180, I have a copy online here.

The original UZI180, as posted on the TCJ, was a snapshot version of a work in progress. As such, it was far from stable and many features did not work properly. Since then, I've done several corrections and bug fixes to the sources that increased the overall stability of the system (it used to crash quite often with a "Trap @ 0xXXXX" message). The changes are summarized below:

Kernel:

  • Debugged the process dispatcher, most of the stability problems were related to process-context switching bugs.
  • Signals and pipes now work.
  • Added a /dev/proc pseudo-device as a way to communicate with the kernel in order to get system-related information, currently it is used only by the ps program to retrieve information about running processes.
  • The embedded CP/M emulator is now working:
    • 16-bit record values are now used for file I/O operations, so file sizes are no longer limited to 32K.
    • the emulator now issues a CALL (and not a JMP) to the application code, so programs that save and restore the stack pointer and return to CCP will no longer crash on exit.
    • applications that directly access the BIOS now work: the emulator module was being wrongly compiled with jump optimization, which compromised the integrity of the BIOS JMP table as most of the jumps were replaced with a 2-byte relative jump instructions.
    • found a workaround for those cases when an application opens a large number of files for read and never close them (e.g. the HiTech C compiler). That's perfectly legal in CP/M, but under UZI that causes file descriptor leaks and the emulator quickly runs out of resources.
  • Many other minor bug corrections.

Disk utilities:

  • Corrected a couple of serious bugs in fsck (present in the original UZI and also in UZI280), that actually caused it to corrupt an otherwise correct filesystem.

Other:

  • Made a UZI version of the Hi-Tech C runtime library to be used by application programs.
  • Many system utilities added.
  • Added a boot loader, so now standalone bootable UZI floppies can be created.

For a full and detailed list of the changes, see the ChangeLog file included in the distribution. Sources and binaries for the kernel, utilities and libraries can be found in the downloads page.

How to create a UZI floppy

Under CP/M, you can just use a batch file similar to this one. In case you prefer to do the whole thing manually, here are step-by-step instructions:

Assuming you have a two 3.5"-floppy drive system, insert a CP/M disk with the UZI utilities on A: and a newly formatted floppy on B: (UZI's device 1), then follow these steps (user entered commands are shown in boldface):

First, we create the UZI filesystem with the mkfs (make filesystem) command:

A>mkfs 1 50 2880

Here, '1' tells mkfs to create the filesystem on device number 1 (our 'fd1', see dev_tab in config.h), '50' is the size of the system area (boot block + superblock + inode table), and '2880' is the size of the disk in blocks (one block is 512 bytes). Why 50? Well, that gives us 48 blocks for the inode table, and since an inode takes 64 bytes, we'll have a total of 384 inodes. Inodes are much like the data part of the CP/M directory entries, there is just one inode per file or directory, but unlike CP/M directory entries, a large file does not take several inodes. With 384 inodes you can have a maximum of 384 files and/or directories, which seems to be enough for a 1.44Mb floppy.

We can now use the fsck (filesystem check) utility to check the just created filesystem:

A>fsck 1

As before, '1' refers to the device number 1. Once finished, assuming that no errors were found, we can start the interactive ucp utility to create the directory structure and to import files:

A>ucp 1
ucp:

Here 'ucp:' is, as you probably have guessed, ucp's prompt. For a list of available commands you can enter 'help' or just '?'. You'll probably notice that the command syntax closely resembles unix commands. We begin by creating some directories, like the ones usually found in standard unix systems:

ucp: mkdir dev
ucp: mkdir bin
ucp: mkdir etc
ucp: mkdir lib
ucp: mkdir usr
ucp: mkdir usr/bin
ucp: mkdir root
ucp: mkdir home
ucp: mkdir tmp

Next, we go to the /dev directory and create entries for our devices:

ucp: mknod fd0 60644 0
ucp: mknod fd1 60644 1
ucp: mknod tty1 20666 12
ucp: mknod tty2 20666 13
ucp: mknod null 20622 14
ucp: mknod kmem 20622 16

Here, the first argument represents the device name (fd0 and fd1 for the floppy drives, tty1 and tty2 for the two terminals, null for the null devive, and kmem for a special "kernel memory" device). The second argument is the device modes and permission, in octal, 20666 meaning "character device" (20000) and readable and writable by anybody (00666); see the unix.h file included with the kernel sources for more details about mode bits. The third argument is the device number, that should correspond to the numbers defined in dev_tab in config.h.

Now we proceed to copy some files. First we need to put the init program in the root directory:

ucp: cd /
ucp: bget init
ucp: chmod init 755

The second argument to the chmod command is the file permissions. In this case we are making the file executable and readable by anyone (00755). Similary, we populate the /bin directory with utilities:

ucp: cd /bin
ucp: bget ssh
ucp: chmod ssh 775
ucp: bget ls
ucp: chmod ls 755
ucp: bget cp
ucp: chmod bget 755
... etc ...

Which and how many utilities you put in the /bin directory is entirely up to you. However, it is recommended to have a minimum set of standard utilities in order to have a comfortable working environment. You should always have at least a shell (in our case ssh - the simple shell). Other utilities include ls, pwd, cp, mv, rm, chmod, mkdir, rmdir, mknod, cat, mount and umount. Some shells already have builtin versions of these most used commands, but unfortunatelly not our tiny ssh. If you have enough disk space you can also add df, dd, ed, chown, chgrp, date, echo, kill, ps, sync, touch, more, grep, etc... It is recommended to place the system utilities in /bin and application programs in /usr/bin.

Don't forget to put a suitable passwd file in the /etc directory, otherwise you will not be able to login. Initially the passwd file would contain just a single line:

root::0:0:superuser:/root:/bin/ssh

The init utility supplied understands the standard unix passwd file format, and supports encrypted passwords. Likewise, you'll want to have a group file as well. The passwd and group files are used by some other utilities, like ls, in order to display user and group names instead of numeric values. A minimalist group file would contain just a single line:

root::0:root

Once you're finished copying files and making/removing directories you might want to boot your UZI system. Just quit from the ucp program,

ucp: quit
A>

optionally check the disk again using fsck as explained above and, keeping the UZI disk in B:, start the kernel. At the boot prompt answer '1' (device number 1, B: in CP/M terms):

A>uzi
UZI180 version 1.5.2 built Sat Dec 7 19:25:31 CET 2002
Copyright (c) 1998-2002 by H.F.Bower, D.Braun, S.Nitschke, H.Peraza
1024kB total RAM, 960kB available to processes (15 processes max)
boot: 1
Mounting root fs: ok
init version 0.8

Welcome to UZI180 on /dev/tty1 (Z180)

login:

If everything went OK (i.e., your disk contained no filesystem errors, and the /init and /bin/ssh programs were present), then you will get the login prompt. Login as root, hit enter at the password prompt, and you are at the shell command prompt:

login: root
Password:

ssh #

You can now run some programs, try let's say 'ls' or 'ls -al', 'date', etc...

These same steps can be used to prepare an UZI partition on a hard disk. You'll need to change the device number 1 in the mkfs and fsck commands to the appropriate device number for the particular hard disk partition. For the mkfs command, you'll need in addition to increase the number of inodes, and to specify the correct number of partition blocks.

Too complicated? Well, there is an easier way: get a ready UZI180 disk image for a 3.5", 1.44Mb floppy. You can use the rawrite.exe program under MS-DOS or the dd command under Unix to transfer the image to a floppy. For best performance format first the disk under CP/M, so the sectors will have the optimum interleave value for the P112 hardware. The floppy is bootable with a suitable kernel, so you should not need CP/M at all. Just insert the floppy into drive A, reset the P112 and at the UZI's login prompt enter root and password root. Note that this time the kernel will not ask for a device to boot. The kernel was compiled for a board with a 16MHz CPU, so if you use a different clock frequency you'll end up with different timings.

Using UZI180's CP/M emulator

One of the most attractive features of the UZI180 operating system is that it contains a built-in CP/M 2.2 emulator. It allows you to run existing CP/M programs like compilers, assemblers and editors directly from the shell command prompt.

Before executing a program, the kernel first tries to determine whether it is a native UZI application or a CP/M program by reading the first few bytes. UZI programs have an embedded signature, while CP/M programs usually begin with a JMP command (0C3h opcode). If that is not the case, then file must be patched. Here is the source for an utility to patch CP/M files that puts a JMP command at the beginning (UZI binary here). This utility could be compiled as well under CP/M (or MS-DOS, or Unix) with minor modifications.

Boot Loader for UZI180

The block 0 on the UZI filesystem is the boot block, and is reserved for the bootloader code. However, existing UZI implementations rely on CP/M in order to boot the system: the kernel is started as a normal CP/M application and then it takes control over the system. It would be more desirable, however, to have standalone bootable disks.

Here is a simple UZI boot loader for the P112. It consists of the following: the boot program code and a boot installer.

How to use it: once you compiled your kernel, copy the kernel binary file to your UZI floppy disk (the location and name is not important), and then copy both the boot installer and the boot loader (the insboot and loader files respectively) to some directory, both of them must reside in the same directory. Make sure insboot has execute permissions (chmod 0755 insboot). Start UZI if you're not already running it, then invoke insboot specifying the name of the device to be made bootable and the full path to the kernel binary file.

For example, suppose your kernel is named vmuzi and is located in the root directory, and the boot utilities reside in the /boot directory; the command sequence will be the following:

ssh# cd /boot
ssh# ./insboot /dev/fd0 /vmuzi 0
insboot: boot sector installed
ssh#

The last parameter in the example above is optional, and specifies the drive number from which the root filesystem should be mounted (i.e. the value you would usually type in response to the kernel's "boot:" prompt). When that parameter is specified, the kernel does not prompt for the filesystem to boot.

Here is how the loader works: the boot installer program first reads the block allocation map of the kernel file, then writes to the boot block of the specified device the boot code from the 'loader' binary file and appends the block allocation map. When the system is booted, the loader uses the block allocation map to figure out where the kernel resides, then proceeds to load it block by block into memory starting from address 100h, and finally passes the control to it.

With this scheme it does not matter where the kernel is physically located or whether it is fragmented over the disk or not. You should, however, be aware that the insboot program must be run again every time the location or size of the kernel file changes, for example:

  • if you replaced the kernel file with a newer version, even if you copied it to the same place and using the same name.
  • if you copied the kernel file to another directory and then erased the original.

In the other hand, insboot does not need to be re-run:

  • if you used the mv command to rename the kernel file or to move it from one directory to another.

Note that this simple boot loader will work only under the P112 machine, since it makes use of the ROM monitor disk services. However, the whole idea can be applied to any other system as well, you'll just have to write a suitable loader program and to modify the installer appropriately. In those cases where the boot code is so large that the kernel allocation map would not fit, you can use the following workaround: save the kernel allocation map into a separate file (that file will occupy just a single block), and pass that block number to the bootloader. During boot, the bootloader must first fetch the block containing the kernel allocation map, and then use it to load the kernel.

Last modified 10-Sep-2007