H-17 Disk System
The H-17 disk system hardware and software implement a two-level file system. The lower layer is an array of sectors that can be read and written by sector number, and which is accessed by calling the H-17 disk driver. The higher layer is the HDOS-imposed file system with a directory and disk cluster allocation chains, using the low-level driver to access the sector array.
This page will document the low-level driver calls and show you how to use them to access the sector array yourself. A short example is included. A longer, more comprehensive example, Diskdump, can be downloaded from the project web site.
This page will also document the format of the label, the directory, and the Group Reservation Table, all essential to understanding how HDOS operates the file system on top of the low-level sector array.
One caution: Unless you really know what you are doing, the author strongly suggests that you read but not write using the low-level driver. If you are going to try writing, use a copy of the diskette image. It's very easy to corrupt the disk and lose your files.
Page Index |
H-17 Disk Format |
Label Group Reservation Table (GRT) Directory |
Low-Level Driver Calls |
Register Usage Read Write Read Regardless Abort Mount Ready |
|
Sample Program |
An H-17 disk consists of an array of 256-byte sectors numbered from zero through the maximum number of sectors possible for the given disk format, minus 1. The original 100K-byte disks had 400 sectors, so sector numbers were 0-399. Double-sided 40-track and single-sided 80-track disks each have 800 sectors. Double-sided 80-track disks have 1,600 sectors.
Each sector consists of two parts: a header and a body. The body consists of 256 data bytes followed by a checksum. The header contains:
There are 10 sectors per track. Sectors 0-9 are on track zero (ex: INT(9/10) = 0), relative sectors 0-9 (ex: 9-0 = 9). Sectors 10-19 are on track one (ex: INT(16/10) = 1, relative sectors 0-9 (ex: 16-(1*10) = 6. Sector 127 is on track 12, relative sector 7.
This calculation is also done in the other direction. Track 30 relative sector 3 is 30*10+3 = sector number 303.
The volume serial number is used to make sure that HDOS is operating on the correct disk; this validation helps prevent disk corruption problems. The sector headers on sectors 10-n contain the serial number given to the disk when it was initialized. The serial number recorded in the first 10 sectors is zero so that HDOS can read the first 10 sectors without knowing the serial number. As you'll see, the volume serial number is recorded in sector #9 as part of the label. Sectors 0-8 contain the boot program.
The label, in sector #9, contains information that allows HDOS to understand the size and format of the disk, and the location of the directory and the Group Reservation Table (GRT), which enables high-level file access.
The label is formatted as follows.
Offset | Length | Name | Description |
---|---|---|---|
0 | 1 | lab.ser | Disk volume serial number, recorded in the header of sectors 10-n |
1 | 2 | lab.ind | Date disk was initialized |
3 | 2 | lab.dis | Sector index of the first directory block |
5 | 2 | lab.grt | Logical sector number of the Group Reservation Table (GRT) |
7 | 1 | lab.spg | Sectors per group (2, 4 or 8) |
8 | 1 | lab.vlt |
Volume type: 0 = Data-only volume 1 = Bootable volume 2 = Disk with no directory |
9 | 1 | lab.ver | Version of init.abs that initialized the disk |
10 | 2 | lab.rgt | Logical sector number of the Reserved Group Table (RGT) |
12 | 2 | lab.siz | Number of sectors on the disk |
14 | 2 | lab.pss | Physical sector size (constant 256) |
16 | 1 | lab.vfl |
Volume flags 00000000B - 40 tracks, 1 side 00000001B - 40 tracks, 2 sides 00000010B - 80 tracks, 1 side 00000011B - 80 tracks, 2 sides |
17 | 60 | lab.lab | Printable volume label |
77 | 2 | reserved | |
79 | 1 | lab.spt | Sectors per track (constant 10) |
The remaining 176 bytes of sector #9 are unused.
lab.ser is used to mount the disk and allow the driver to access sectors 10-n of the disk, which have the serial number recorded as part of the sector header.
The size and format of the disk can be determined by looking at the following label fields.
Label Fields | Disk Format | ||||
---|---|---|---|---|---|
lab.ver | lab.vfl | lab.siz | lab.spg | Tracks | Sides |
16h | n/a | 0 | 2 | 40 | 1 |
20h | 00000000B | 400d | 2 | 40 | 1 |
20h | 00000001B | 800d | 4 | 40 | 2 |
20h | 00000010B | 800d | 4 | 80 | 1 |
20h | 00000011B | 1600d | 8 | 80 | 2 |
HDOS allocates disk space to files in groups of sectors. There are a fixed number of groups - 200 - available on each disk, no matter the size or format of the disk. Thus the theoretical maximum number of files on a disk is 200, with larger disks allowing those files to be larger.
The label field lab.spg contains the number of sectors per group for this disk, which will be 2, 4, or 8 depending on the disk format. (Ref: Disk Size Determination)
HDOS uses a chained allocation table to track groups of sectors; this table is called the Group Reservation Table, or GRT. The directory entry for the file contains the first GRT index for the file. The GRT byte at that offset contains the next index, and so on, with each new index representing potentially "lab.spg" sectors. The last index in the file has a value of zero, terminating the chain.
Consider the directory entry for PIE.ABS on the author's 40x2 system disk (all values octal):
120 111 105 000 000 000 000 000 101 102 123The two underlined bytes are the first and last group numbers, offsets into the GRT (ref: directory). Here is the applicable section of the GRT for this disk (all values octal):
Offset- Data---------------------------The group chain for the file PIE.ABS is underlined.
At offset 124 - the first group of sectors in this file - we find the index of the next set of sectors, 125. At offset 125, we find the index to the next set, 126, and so on. The first four groups and last three groups are contiguous, with a gap of eight groups between the two segments of the file.
This program takes seven groups of sectors. This disk uses four sectors per group, so the file is taking up 28 sectors of the disk. (The actual file size is 25 sectors; ref: directory.)
To determine the physical sector index in the low-level disk sector array, multiply the group number by the number of sectors per group. The first sector of PIE.ABS can be found in 124Q = 84 * 4 = sector 336 (track 33 sector 6). You would use this index with the low-level read function to read the first sector of the file.
The byte at offset zero into the GRT contains the index of the first group of free sectors, those sectors not yet allocated to any file. The free group list is chained just like a file. To determine the number of free sectors on a disk, you can follow this chain counting "lab.spg" sectors for every non-zero index in the chain.
The directory is a file that's positioned close to the physical center of the disk for rapid access. The directory is made up of xxxxxxxx directory blocks, each of which is 512 bytes long. Each block contains 22 directory entries.
Here is the layout of a single directory entry:
Offset | Length | Name | Description |
---|---|---|---|
0 | 8 | dir.nam | File name |
8 | 3 | dir.ext | File name extension |
11 | 1 | dir.pro | Project (unused, set to 0) |
12 | 1 | dir.ver | File version (unused, set to 0) |
13 | 1 | dir.clu | Cluster factor |
14 | 1 | dir.flg |
Flags: 10000000B - system file (S) 01000000B - flags locked (L) 00100000B - write protected (W) 00010000B - contiguous (C) |
15 | 1 | reserved | |
16 | 1 | dir.fgn | First group number |
17 | 1 | dir.lgn | Last group number |
18 | 1 | dir.lsi | Last sector index (in last group) |
19 | 2 | dir.crd | File creation date |
21 | 2 | dir.ald | File alteration date |
Of interest here are the first group index, dir.fgn, the last group index, dir.lgn, and the last sector index, dir.lsi. HDOS uses dir.fgn to find the front of the file, and dir.lgn to find the back when you are adding on to an existing file.
The last sector index, dir.lsi, is used to tell how many sectors are actually used in the last group allocated to the file. Refer to the GRT example above. PIE.ABS uses seven groups, each of which contains four sectors, so the file is occupying 7*4=28 sectors. But dir.lsi is 001Q, indicating that only the first sector in the last group is actually part of the file. So the file is really 6*4+1=25 sectors in length, with three sectors "wasted."
As noted above, each directory block contains 22 directory entries. The layout of a directory block is:
Offset | Length | Name | Description |
---|---|---|---|
0 | 506 | - | 22 directory entries |
506 | 1 | - | constant 000q |
507 | 1 | dir.enl | Entry length (027q) |
508 | 2 | dir.sec | Sector index of beginning of this block |
510 | 2 | dir.lnk | Sector index of beginning of next block |
HDOS reads the directory using low-level driver calls. The sector index of the first directory block is found in label field lab.dis. The end of each directory block contains the sector index of the next directory block (zero in the last directory block) so HDOS can easily follow the directory across the disk surface without using the high-level file system, speeding directory access.
There are eleven functions that can be provided by an HDOS driver; the complete list is in the following table. The low-level disk driver supports six of these functions.
Octal Code |
Function | Description |
---|---|---|
000Q | READ | Read one or more contiguous sectors from the disk surface. |
001Q | WRITE | Write one or more contiguous sectors to the disk surface. |
002Q | READR | Read one or more contiguous sectors from the disk surface, regardless of serial number protection. Used to read sectors from track zero of an already-mounted disk. |
003Q | OPENR | Not used with the low-level driver. Open a file for read access. |
004Q | OPENW | Not used with the low-level driver. Open a file for write access. |
005Q | OPENU | Not used with the low-level driver. Open a file for update access. |
006Q | CLOSE | Not used with the low-level driver. Close an open file. |
007Q | ABORT | Abort (e.g., clean up) the driver. |
010Q | MOUNT | Perform a low-level disk mount. |
011Q | LOADD | Not a user function. Called when the driver is loaded into memory. |
012Q | READY | Check a drive to determine whether a disk is inserted and spinning. |
(Note: LOADD is invoked when the driver is loaded using the .LOADD syscall. This function should not be called directly by your program.)
To invoke a driver function, store the appropriate physical drive number (0, 1, or 2) into the field AIO.UNI (41.061A), load the registers as appropriate for the function you are calling, load the function code into register (A), and call D.SYDD (40.130A). Both AIO.UNI and D.SYDD are included in the common deck HDOSRAM.ACM distributed on the Extras disk.
Register usage and documentation for each call are included below, and several are used in the sample program.
In all cases, register (A) must contain the function code when you call D.SYDD.
The data transfer functions read, write, and read regardless use the following registers:
(A) = function codeOn return from the call:
'C' flag set if errorMount uses the following registers:
(A) = function codeAbort and Ready only require the function code in register (A).
Reads one or more sectors starting from the sector index in (HL) and stores them at (DE). The number of bytes to be read is passed in (BC).
Make sure to set AIO.UNI to the physical drive number (0, 1, or 2) before calling D.SYDD. If you use any of the file system scalls between driver calls, you can count on AIO.UNI having been changed.
Look back at the PIE.ABS example in the GRT section. That program can be read into memory in two calls to the low-level driver:
MVI A,0The HDOS .READ scall requires that all disk I/O be performed in multiples of 256 bytes, but the driver's read function doesn't have that same requirement. There is one example of a short read of only 80 bytes, which you can locate in the HUG enhanced SY: driver, available from the project web site. It's not clear what happens when there are errors on a short read, since the sector's checksum won't be read and processed.
This function call is set up the same as read and uses the same registers, but sectors are written to the disk, not read.
Writes must be performed in 256-byte units.
This function call is the same as read, but allows you to read sectors on track zero of a disk that's already been mounted with its correct volume serial number.
Normally, read verifies that the serial number in each sector read is the same as the serial number that the disk was mounted with. But track zero has zeros in the volume serial number field of the sector header. Read Regardless expects a volume serial number of zero, which makes it possible to read sectors from track zero without remounting the disk.
Use this function if you want to read the label of a disk that's already been mounted.
Abort cleans up the driver from any past errors. This should be used at least once in any program that uses low-level driver calls to make sure that the driver is ready to access your disk.
I would recommend that you delay 300-500ms after aborting the driver before trying to mount or read to help prevent I/O errors. This is not necessary between an abort and a ready check.
This function does a low-level mount of the physical drive in AIO.UNI, recording the volume serial number to enable serial number verification during subsequent reads.
Note that this is a low-level mount that has nothing to do with the high-level mount invoked by the mount command or the .MOUNT scall. If you expect to use the high-level file system to access the disk, you will have to use the high-level .MOUNT scall. This mount is good only for low-level driver access.
Before calling mount, make sure the correct physical device number is in AIO.UNI, and load the volume serial number into register (L).
This function tests whether there's a disk inserted in the drive named in AIO.UNI by turning on the motors and counting sector holes. If there is no disk inserted in the drive, the ready call returns with the 'C' (carry) flag set. If there is a disk in the drive, 'C' is clear on return.
The disk does not have to be mounted or otherwise accessible to HDOS or the driver in order to use the ready function.
The following very short program uses several of the low-level driver functions in order to read a disk's label and it's first directory block. A set of notes keyed to the source code follows the code.
* Using low-level disk driver calls, mount a diskNotes:
Always ensure that AIO.UNI has the correct device number in it before making a low-level driver call. In this case, we're expecting the disk to be inserted into physical device #1.
Aborting the driver cleans up from short reads, errors, and other bad conditions, and ensures that the driver is ready for business.
We've asked for a disk to be inserted into device #1. We'll stay in this loop until device #1 is ready; i.e., there is a disk inserted. If there is already a disk in the drive, it will be ready immediately.
We could use the Read Regardless call here, but I chose to mount the disk with volume serial number zero and then use Read to get the label. We need the label so we can get the correct serial number, and for the first directory block index.
Having read the label, we can remount the disk with the correct volume serial number in preparation for reading the directory block. If we subsequently had to reread the label, we could use Read Regardless to get it without remounting the disk.
The first directory block sector index is at offset LAB.DIS from the beginning of the label. Note that we're reading two sectors in one call with this read.
This disk can be removed from the drive without notifying HDOS. In fact, it hasn't been mounted to HDOS at all - it's only had its volume serial number recorded for low-level driver access - so it's inaccessible to HDOS proper.