Post by c128old on Jul 9, 2023 13:44:12 GMT
I could not decide on CP/M or DriveProgramming forum.
For the speedy, and roomy 3.5" 1581 disk drive, Commodore decided on a funny trick to enable CP/M booting.
A disk formatted for CP/M by the 'format.com' tool that comes with the last C= version (MAY 87) will first be formatted in a regular C= DOS fashion, with a directory located in the middle of the disk.
Then, an initialization-program is put on the disk with the magic naming of "COPYRIGHT CBM 86" (and some additional bits)
That autostart (Track 40, sector 4) is found (before anyone on the IEC bus has a chance to decide otherwise) and loaded and executed.
In the case of the CP/M format for 1581 disks, this program intercepts the attempt to do a block read ('U1') on track 1 sector 0.
This attempt is deferred to Logical Track 40, sector 5.
[
.. for a moment consider:
Block Read (U1) starts counting from Tr1 up (Tr0 is illegal). It expects to move 256 bytes from a disk to a buffer in the drive memory.
Burst commands (U0>..) start counting from Tr0 and use native blocks of 512 bytes.
This means "Track 40, Sector 5" would be "Track 39, Sector 3"
]
When the system is turned on, the Z80 ROM eventually moves control to the C128 OS. This starts the boot procedure, which does the (hard coded) "U1" on "Tr1,0". For the c1581 with a CP/M formatted disk that is intercepted to fetch the boot block from Tr40,5
Kernal Control passes to the boot code of the 256 bytes loaded to $0B00.
Code in the boot block of a 5.25" disk would be pretty simple: go z80 and execute 'restart 3' or so, with the aim to begin executing the z80 boot rom code that assumes a known C128 CP/M disk in IEC device 8.
Known here specifically means: it could be a single sided disk or a double sided disk. The boot code handles this difference, it must different because the disk layout for the two isn't the same: 1k logical blocks vs. 2k.
The larger room on a double sided 1571 disk requires a different logical-block-size. The boot rom code can read as much as 2 directory entries from CP/M and this means max 32k code for single sided, and 64k for double sided.
Now the 1581 case: the boot code knows it's running on a 1581, not a 1571/1541. This means: we are going to use the burst command set and we are going to be loading native blocks of 512 byte size. Also, so that the whole disk may be used, the directory track at Tr40 must be skipped and the number of logical CP/M blocks is more than 256 for 2k blocks.
This means CP/M will use 16bit block counters instead of 8bit. The Z80 BootRom code doesn't support that and the decision was taken to solve this with a dedicated boot loader. The 256 bytes in the boot block are enough for the required code to load blocks using burst commands.
The 1581 boot block loads several blocks from Tr40,6 and on, to $E000 (burst: Tr39,4) and the 1581 does this really fast.
Then the code in $0B00 shifts to Z80, which picks up at $E000.
The location of $E000 is a smart choice because 'surprisingly' this would not be overwritten by regular CP/M BIOS loading.
The CP/M BIOS is stored on the disk in memory-pages each of which are reverse-loaded into memory (so from top down)
This starts with 'high memory' (the highest usable location being $FBFF because $FCxx is parameters, $FCFF-$FE00 is used for interrupt numbers and $FExx-$FF00 is the buffer space. $FF00 and up is left alone) and walks down until the split-point of shared memory (set to $E000, for 8k shared bank 0 mem at the top, as opposed to 0.25k shared bottom for c128 kernal). The banked CP/M code will thus be below $E000
The $E000 z80 code is (not surprisingly) a close copy of the z80 boot code (not optimized) where the changes for 16bit block numbers are made.
This code loads CP/M 'as usual'.
[ another CP/M rant...
The 1581 disks are spacious yet can only load 32k CP/M like the 1541/1570 or single sided 1571-diks. this is because, as before, the z80 rom code (copied and updated in the 1581 loader) only checks 2 entries in the CP/M dir. With 8bit block IDs, for 2k blocks (as on double sided 5.25") we could read 64k (16 pointers per dir entry, 2 entries = 2 * 16 * 2048 = 64k). For a single sided disk the blocks are 1k. For a 1581 the blocks are 2k but need 16bit, and must fit in the 16 bytes of a dir entry, giving 2 * 8 * 2048 = 32k.
CP/M allocates information in 'blocks' (like the BAM in a commodore disk). The CP/M 'bam' is in essence mixed with the dir entries, thus the number of dir entries determines the number of blocks we can allocate on a disk. It's a bit messy.
These CP/M blocks are only 'allocation units'. Internally CP/M still talks in 128byte records. Anyway, too much CP/M for now.
]
Back to the drive: it would totally have been possible to put the boot block at the start of the disk instead of at the dir track.
Why would C= have decided for this complex scheme?
The c128 would have used a boot block with 'chain load' to load the next x blocks to memory with the desired updated boot loader.
Why not simply put the boot block in Tr1,0 and the 'boot loader' in Tr1,1-10 using the ability of the C128 to 'load the next blocks' to $E000? Totally possible!
The only argument I could come up with is:
CP/M can skip 'system tracks'. Imagine we set that to '1' then the first track of the disk will be skipped, costing 5k of storage. CP/M is accessing the disk with burst commands (because fast) and these count from Tr0, not Tr1.
The boot block would have to be on 'Tr1' which ends up in the 2nd track if burst is used. So this would lead to 2 tracks of waste.
Relocating the bootblock allows to circumvent this, but now you have to keep the dir track alive.
Relocating into the BAM area (otherwise unused in the CP/M case) allows to have (only) 1 track of 5k storage missing.
Alternatively, you could update the BIOS drive code to treat a 1581 completely different. Lot of work and difficult testing.
This is, I think why we ended up with this complex thing C= did.
Any thoughts?
Note: I think the C= loader for 1581 should have used "device = current drive" instead of a hard coded device 8 (LDX #8 replace with LDX $BA)