TRS-80 DOS - LDOS 5.3.1 for the Model I - SYS8/SYS Disassembled
Page Customization
Page Index
SYS8/SYS
Other Navigation
Summary:
LDOS 5.3.1 SYS8/SYS Disassembly - Dynamic File Space Allocation (Model I)
SYS8/SYS is the LDOS 5.3.1 overlay that gives a disk file more space while it is being written. Whenever the resident file-handling code in SYS0/SYS needs a record that lies beyond the granules the file already owns, it issues RST 28H with request code 9AH. The resident supervisor dispatcher at 4BCDH loads this overlay to 4E00H and enters it, and the overlay finds free space on the disk, records it in the file's directory entry, and hands back the physical position of the granule that was wanted.
The overlay is small, a single granule of 662 bytes spanning 4E00H through 5095H, plus a 2-byte load block deposited at 4BC9H in the resident padding area. Its transfer address is 4E00H. It must be present on any working SYSTEM diskette, and must be on the boot disk if a configuration file is to be loaded, because without it no file can ever grow.
An LDOS file records its disk space as a chain of extents. Four 2-byte extent entries live in the file's 32-byte directory record at DIR+16H through DIR+1DH. The first byte of an entry is the cylinder, and the second packs the starting granule on that cylinder into bits 7-5 and the number of granules in the extent, less one, into bits 4-0. A single extent can therefore describe at most 32 granules. The entry after the last one in use holds FFH. When all four entries are occupied, the link field at DIR+1EH is set to FEH and DIR+1FH names a secondary extended directory record which carries four more extent entries of its own, so the chain can be followed from record to record.
Free space is tracked by the Granule Allocation Table in sector 0 of the directory cylinder. It holds one byte per cylinder, and each set bit marks a granule on that cylinder which is already in use. The Hash Index Table in sector 1 holds one byte per directory record, carrying the hash of the file name occupying it, with zero marking a free record. SYS8/SYS reads the Granule Allocation Table into its own buffer at 5100H, immediately above its code, and uses the resident shared sector buffer SBUFF$ at 4200H for the Hash Index Table so the two never collide.
The allocation strategy favours contiguity. The overlay first works out the granule that physically follows the file's current last extent and tests that granule's bit in the Granule Allocation Table; if it is free the existing extent is simply grown by one, which costs no new extent entry at all. Only when that granule is taken, when the extent has reached its 32-granule limit, or when the next cylinder falls outside the cylinder map does the overlay start a fresh extent, scanning the table for any cylinder with a free granule. The scan makes two passes so that a search beginning part way along the disk wraps round and covers every cylinder before giving up. Each granule claimed is marked in the table image, the extent entry is built up in place, and the loop repeats until the file reaches the granule the caller asked for. The Granule Allocation Table and the directory record are then written back together.
Two conditions end the work with an error. If every cylinder is full the overlay returns error 27, disk space full. If a secondary extended directory record is needed but the Hash Index Table shows no free directory record, it returns error 30, directory full and cannot extend the file. Failures of the tables themselves return errors 20 and 21 for the Granule Allocation Table and 22 and 23 for the Hash Index Table.
On a successful return the overlay also refreshes the five-entry extent cache the resident routines keep at FCB+0EH inside the caller's File Control Block, whose entries are four bytes each, either updating the matching entry in place or shifting the cache down to open its first slot, and returns in Register A the granule's offset within the extent that holds it.
Memory Map
| Address Range | Purpose |
|---|---|
| 4BC9H-4BCAH 2 bytes | Load block deposited into the resident padding area below the RST 28H dispatcher. SYS8/SYS writes the bytes 96H and 02H there. |
| 4E00H-4E06H 7 bytes | Overlay entry and request dispatch. Masks the request code and accepts only sub-function 10H. |
| 4E07H-4E4BH 69 bytes | Allocation driver, File Control Block extent cache update (five 4-byte entries from FCB+0EH), and the return of the granule's offset within its extent. |
| 4E4CH-4E96H 75 bytes | Extent chain walk across the primary and any secondary extended directory records, with the trigger that extends the file. |
| 4E97H-4F23H 141 bytes | Granule allocator. Reads the Granule Allocation Table and tries to grow the file's last extent contiguously. |
| 4F24H-4F79H 86 bytes | Two-pass free granule search and extent construction, returning error 27 when the disk is full. |
| 4F7AH-4F85H 12 bytes | Commit path. Writes the Granule Allocation Table and then the current directory record. |
| 4F86H-4FDDH 88 bytes | Creation and linking of a secondary extended directory record, returning error 30 when the directory is full. |
| 4FDEH-5019H 60 bytes | Hash Index Table search for a free directory record. |
| 501AH-5032H 25 bytes | Self-modifying builders for the granule bit test and the granule bit mask. |
| 5033H-503EH 12 bytes | Per-cylinder granule count adjustment for double-sided drives. |
| 503FH-5095H 87 bytes | Granule Allocation Table and Hash Index Table sector read and write helpers. |
| 5100H-51FFH 256 bytes | Granule Allocation Table buffer. Scratch space above the overlay image, not part of the loaded file. |
Variables and Self-Modified Operands
| Address Range | Purpose |
|---|---|
| 4E4AH 1 byte | Operand of the LD A,nn at 4E49H. Written at 4E12H with the target granule's offset within its extent, which is the value Register A returns to the caller. |
| 4F36H 1 byte | Operand of the CP nn at 4F35H. Written at 4F2FH with the number of cylinders on the drive, bounding the free-space scan. |
| 4F82H 1 byte | Operand of the LD B,nn at 4F81H. Written at 4E54H with the number of the directory record currently being processed, so the commit path rewrites the correct record of the chain. Also read back at 4FAEH and 4FC9H when a secondary record is linked. |
| 4FD9H 1 byte | Operand of the LD (HL),nn at 4FD8H. Written at 4F9AH with the number of the newly created secondary directory record, which is then stored into the parent record's link field at DIR+1FH. |
| 5008H 1 byte | Operand of the CP nn at 5007H. Written at 4FF8H with the number of directory records the drive holds, bounding the Hash Index Table search. |
| 5025H 1 byte | Second opcode byte of the BIT 0,B at 5024H. Written at 5021H so the instruction tests the wanted granule's bit in a cylinder allocation byte. |
| 5031H 1 byte | Second opcode byte of the SET 0,A at 5030H. Written at 502CH so the instruction builds a mask holding only the wanted granule's bit. |
| 5100H-51FFH 256 bytes | Granule Allocation Table sector image. One byte per cylinder, each set bit marking an allocated granule. The per-cylinder map ends below offset CCH, which is where the table's configuration word begins. |
Major Routines
| Address | Entry | Exit | Name and Purpose |
|---|---|---|---|
| 4E00H | Register A = request code, Register Pair IX = File Control Block | Returns to the RST 28H dispatcher | Overlay Entry Masks the request code with 70H and services only sub-function 10H, which request code 9AH selects. |
| 4E07H | Register Pair BC = target file-relative granule number, Register Pair IX = File Control Block | Register A = granule offset within its extent, Register Pair HL = extent word, CARRY set if the cache was shifted | Allocation Driver Locates or creates the target granule and refreshes the five-entry, 4-byte-per-entry extent cache at FCB+0EH. |
| 4E4CH | Register Pair BC = target granule number, Register Pair IX = File Control Block | Z set with Register Pair HL = extent word and Register Pair DE = preceding granule count, or NZ with an error code in Register A | Extent Chain Walk Adds up the granules in each extent across the primary and any secondary extended directory records until the target granule is covered, following FEH links and extending the file at an FFH terminator. |
| 4E97H | Register Pair BC = target granule number, Register Pair DE = granules already owned, Register Pair HL = the FFH terminator | Z set when the file has been extended, or NZ with an error code in Register A | Granule Allocator Reads the Granule Allocation Table and grows the last extent contiguously where possible, otherwise moving to a fresh extent slot or a secondary directory record. |
| 4F24H | Register Pair HL = Granule Allocation Table pointer, Register Pair DE = extent slot, Register Pair BC = granules still needed | Falls into the commit path, or returns error 27 in Register A | Free Granule Search Scans every cylinder twice for a free granule, claims it in the table image, and builds the extent entry around it. |
| 4F7AH | Register Pair IX = File Control Block, operand at 4F82H = directory record number | Z set on success, or NZ with error 21 or a directory write error | Commit Path Writes the Granule Allocation Table image back and then rewrites the current directory record. |
| 4F86H | Register Pair IX = File Control Block, operand at 4F82H = parent record number | Z set on success, or NZ with error 30 or a table error in Register A | Secondary Record Creation Claims a free Hash Index Table slot, builds a secondary extended directory record with empty extents, and links it from the parent record. |
| 4FDEH | Register A = starting slot position, Register C = drive number, Register Pair HL based at the Hash Index Table image | Z set with Register Pair HL addressing a free slot, or NZ when the directory is full | Hash Index Table Search Bounds the search by the drive's real directory size and scans for a zero hash byte, wrapping once. |
| 501AH | Register A = granule number 0-7, Register B = cylinder allocation byte | Z set when the granule is free | Granule Bit Test Builder Constructs and executes a BIT n,B instruction for the given granule number. |
| 5027H | Register A = granule number 0-7 | Register A = mask with only that granule's bit set | Granule Bit Mask Builder Constructs and executes a SET n,A instruction for the given granule number. |
| 5033H | Register A = granules per track, Register C = drive number | Register A = granules per cylinder | Per-Cylinder Adjustment Doubles the count when Drive Code Table byte 4 bit 5 shows a double-sided drive. |
| 503FH | Register C = drive number | Z set on success, or NZ with error 20 | Read Granule Allocation Table Reads sector 0 of the directory cylinder into the buffer at 5100H. |
| 5052H | Register C = drive number | Z set on success, or NZ with error 21 | Write Granule Allocation Table Writes 5100H back to sector 0 with a deleted-data address mark and verifies it. |
| 506BH | Register C = drive number | Z set on success, or NZ with error 22 | Read Hash Index Table Reads sector 1 of the directory cylinder into SBUFF$ at 4200H. |
| 507DH | Register C = drive number | Z set on success, or NZ with error 23 | Write Hash Index Table Writes 4200H back to sector 1 with a deleted-data address mark and verifies it. |
Error Codes Returned
| Code | Raised At | Meaning |
|---|---|---|
| 14H (20) | 504FH | GAT read error. Sector 0 of the directory cylinder could not be read. |
| 15H (21) | 5066H | GAT write error. The updated Granule Allocation Table could not be written or failed to verify. |
| 16H (22) | 507AH | HIT read error. Sector 1 of the directory cylinder could not be read. |
| 17H (23) | 5091H | HIT write error. The updated Hash Index Table could not be written or failed to verify. |
| 1BH (27) | 4F49H | Disk space full. Both passes of the free granule search found every cylinder fully allocated. |
| 1EH (30) | 4F95H | Directory full, cannot extend file. No free directory record was available for a secondary extended entry. |
Cross-References
SYS8/SYS is reached only through the resident supervisor. The resident record-positioning code in SYS0/SYS issues RST 28H with request code 9AH when a record lies beyond the space a file owns. The call goes through the launch stub at 4AB3H (LD A,9AH then RST 28H), reached by the CALL 4AB3H at 4A98H, and the dispatcher at 4BCDH loads this overlay through the loader at 4BF5H. On return the resident code plants the offset this overlay leaves in Register A into its own operand at 4AE5H, and tests the CARRY flag at 4AAFH to decide whether the freed extent cache slot still has to be filled at 4AD5H. The overlay in turn calls back into the resident core for every disk operation: DIRRD at 4B10H and DIRWR at 4B1FH for directory records, DIRCYL at 4B65H and RDSSEC at 4B45H to reach the directory cylinder, WRPROT at 4768H and VERSEC at 4772H to write and verify the Granule Allocation Table and Hash Index Table sectors with a deleted-data address mark, DCTBYT at 479CH for drive geometry, and MULTEA at 4B6CH and DIVEA at 4B7BH for the arithmetic. It also reads the resident tick counter TIMER$ at 4040H and the constant search-origin cylinder byte at 4767H, and uses the resident shared sector buffer SBUFF$ at 4200H.
The work SYS8/SYS does is undone by SYS3/SYS, which on closing a file releases any granules that were allocated but never used, unlinking and de-hashing a secondary extended directory record that becomes empty. The error codes returned here are turned into readable text by SYS4/SYS, which services @ERROR through request code 96H. The directory record and extent layout it maintains is the same structure written by SYS3/SYS and read by the directory display in SYS6/SYS.
Disassembly:
4BC9H - Overlay Load Block
The two bytes every SYS overlay deposits into the padding area of the resident core immediately below the RST 28H dispatcher. They are data, not instructions, and are never executed. SYS8/SYS writes 96H and 02H here, in the same way SYS3/SYS writes A6H and 02H and SYS4/SYS writes AFH and 03H.
4E00H - Overlay Entry and Request Dispatch
SYS8/SYS is entered here by the resident RST 28H dispatcher at 4BCDH with the request code in Register A and Register Pair IX addressing the caller's File Control Block. Only sub-function 10H, which request code 9AH selects, is implemented; every other value returns immediately.
4E07H - Allocation Driver and File Control Block Extent Cache Update
The driver locates the granule the caller asked for, extending the file if the extent chain is too short, and then refreshes the five-entry extent cache held in the File Control Block at FCB+0EH so the resident record-positioning routines can convert record numbers into physical cylinders and granules. It returns the granule's offset within its extent in Register A.
Store the within-extent granule offset held in Register A into 4E4AH, which is the immediate operand of the LD A,nn instruction at 4E49H. The exit path of this routine executes that instruction, so the offset is what Register A returns to the caller.
Fetch the cylinder byte of the extent cache entry addressed by Register Pair HL into Register A.
Loop End
The 00H operand at 4E4AH was overwritten at 4E12H with the target granule's offset within its extent. Register A therefore returns that offset to the caller.
4E4CH - Extent Chain Walk and Extend Trigger
This routine reads the file's primary directory record and walks its four extent entries, adding up the granules each one contains until the running total covers the target granule. An FEH marker in an extent's cylinder byte links to a secondary extended directory record, whose extents are walked in turn; an FFH marker ends the chain and triggers the allocator at 4E97H.
Store the directory record number held in Register A into 4F82H, the immediate operand of the LD B,nn instruction at 4F81H. The commit routine at 4F7AH executes that instruction, so it always rewrites whichever directory record of the chain is currently being processed.
Fetch the cylinder byte of the extent entry addressed by Register Pair DE into Register A. A value below FEH is a real cylinder number; FEH marks a link to a secondary extended directory record; FFH marks the end of the file's extent chain.
4E97H - Granule Allocator
The allocator reads the drive's Granule Allocation Table, works out how many granules the file is short by, and tries first to grow the file's current last extent with the granule that physically follows it. When that granule is taken, when the extent already holds its maximum of 32 granules, or when the candidate cylinder lies outside the cylinder map, it moves on to a fresh extent slot, and when all four slots of the record are in use it creates a secondary extended directory record.
4F24H - Free Granule Search and Extent Construction
This section scans the Granule Allocation Table image for a cylinder that still has a free granule, making two passes so that a search which starts part way along the disk wraps around and covers every cylinder. The chosen granule is marked allocated in the table image and folded into the extent slot, whose cylinder, starting granule and granule count fields are built up as each granule is claimed. When no free granule exists anywhere the routine returns error 27, disk space full.
Store the drive's cylinder count held in Register A into 4F36H, the immediate operand of the CP nn instruction at 4F35H, so the scan below stops at the last cylinder that actually exists on this drive.
Copy Register L, the cylinder number currently being examined in the Granule Allocation Table image, into Register A.
Compare the current cylinder number in Register A against the operand at 4F36H, which was set to the drive's cylinder count at 4F2FH. If Register A is below that count the CARRY FLAG is set.
Copy the granule position being tested from Register C into Register A.
4F7AH - Commit the Granule Allocation Table and Directory Record
The common commit path writes the updated Granule Allocation Table image back to sector 0 of the directory cylinder and then rewrites the directory record whose number was planted at 4F82H, so that the granules just claimed and the extent entries that describe them reach the disk together.
Load Register B with the operand at 4F82H, which 4E54H set to the number of the directory record currently being processed, so the write below updates the correct record of the file's chain.
4F86H - Create and Link a Secondary Extended Directory Record
When all four extent slots of a directory record are in use, the file is given a further record. A free slot is found in the Hash Index Table and given the same name hash as the primary record, the new record is built as a secondary entry with empty extent slots, and the parent record's link field at DIR+1EH is set to FEH followed by the new record number. A full directory returns error 30.
Store the new directory record number held in Register A into 4FD9H, the immediate operand of the LD (HL),nn instruction at 4FD8H, so the parent record's extended-directory link is later made to point at this new record.
Fetch the operand at 4F82H into Register A. 4E54H set it to the number of the directory record that this new record extends.
Store zero into the byte of the new directory record addressed by Register Pair HL, clearing the date, end-of-file, logical record length, name, extension and password fields which a secondary record does not use.
Loop End
Store 0FFH into the byte of the new directory record addressed by Register Pair HL, marking every extent slot and the extended-directory link of the new record as unused.
Loop End
Fetch the operand at 4F82H into Register A. It holds the number of the parent directory record which must now be linked to the record just created.
Store the operand at 4FD9H, which 4F9AH set to the number of the newly created secondary directory record, into DIR+1FH of the parent record addressed by Register Pair HL, completing the link between the two records.
4FDEH - Hash Index Table Free Record Search
This routine works out how many directory records the drive actually holds from its Drive Code Table geometry, then scans the Hash Index Table image for a byte of zero, which marks a free directory record. The scan starts alongside the file's own slot position and steps through the groups of thirty-two records, wrapping once so that every slot is examined.
Store that directory sector count held in Register A into 5008H, the immediate operand of the CP nn instruction at 5007H, so the scan below never runs past the last directory record that exists on this drive.
Compare the current slot position in Register A against the operand at 5008H, which 4FF8H set to the number of directory sectors on this drive. If Register A is below that count the CARRY FLAG is set.
501AH - Granule Allocation Bit Test Builder
A granule number from 0 to 7 cannot be used directly by a Z80 bit instruction, so this helper constructs the second opcode byte of a BIT n,B instruction for that granule and plants it in the instruction at 5024H before executing it against a cylinder's allocation byte.
Store the constructed opcode byte held in Register A into 5025H, which is the second byte of the BIT 0,B instruction at 5024H, turning it into a test of the wanted granule's bit.
Test the bit of Register B selected by the operand planted at 5025H. Register B holds a cylinder's Granule Allocation Table byte, so the Z FLAG comes back set when that granule is free and the NZ FLAG when it is already allocated.
5027H - Granule Allocation Bit Mask Builder
The companion of the bit test builder constructs the second opcode byte of a SET n,A instruction for a granule number and plants it in the instruction at 5030H, producing in Register A a mask holding only that granule's bit so it can be merged into a cylinder's allocation byte.
Store the constructed opcode byte held in Register A into 5031H, which is the second byte of the SET 0,A instruction at 5030H, turning it into a set of the wanted granule's bit.
Set the bit of Register A selected by the operand planted at 5031H. Register A was cleared at 502FH, so it comes back holding a mask with only the wanted granule's bit set, ready to be merged into a cylinder's Granule Allocation Table byte.
5033H - Per-Cylinder Granule Count Adjustment
Drive Code Table byte 8 records the number of granules on one track. This helper doubles that figure when Drive Code Table byte 4 shows the drive is double sided, so callers work in granules per cylinder.
503FH - Read the Granule Allocation Table
Sector 0 of the directory cylinder holds the Granule Allocation Table, one byte per cylinder with a set bit for every granule in use. It is read into this overlay's own buffer at 5100H, immediately above the overlay code, so that the resident shared buffer at 4200H remains free for directory records. A failure returns error 20.
5052H - Write the Granule Allocation Table
The updated Granule Allocation Table image at 5100H is written back to sector 0 of the directory cylinder with a deleted-data address mark, as every directory-cylinder sector is, and then verified. A failure returns error 21.
506BH - Read the Hash Index Table
Sector 1 of the directory cylinder holds the Hash Index Table, one hash byte per directory record with zero marking a free record. It is read into the resident shared sector buffer SBUFF$ at 4200H. A failure returns error 22.
507DH - Write the Hash Index Table
The updated Hash Index Table image at 4200H is written back to sector 1 of the directory cylinder with a deleted-data address mark and then verified, claiming the directory record taken for a secondary extended entry. A failure returns error 23.