!lm10
!rm76
Commented Listing of DOS 3.2.1 $B800-BCFF

Here is the third installment of DOS disassembly, covering the routines called by RWTS.

There are six major subroutines between $B800 and BCFF.  PRE.NYBBLE and POST.NYBBLE convert between memory format and disk format.  READ.ADDRESS reads the next address header.  READ.SECTOR reads a sector, and WRITE.SECTOR writes one.  SEEK.TRACK.ABSOLUTE moves the head in or out to the desired track.  With the sole exception of initializing a disk, all actual disk I/O is done by these six subroutines.

The bits that are written on the disk are considerably different from those in memory.  Some computer systems make the transformation with expensive hardware controllers, but Wozniak's unique system does most of the work in software.  The 13-sector controller cannot read accurately data which has two or more consecutive zero-bits.  Of course, almost every byte you want to write has two or more zero-bits in a row!  Therefore the software must encode the bytes you want to write.

One way to encode the bytes is to take four bits at a time, and interleave them with "clock" bits.  In fact, the data in the address headers is recorded this way.  For example, to record the byte "xyxyxyxy" in an address header, the two bytes "1x1x1x1x" and "1y1y1y1y" will be written.  This means a 256-byte sector will take 512 bytes on the disk surface (plus header and trailer).

DOS 3.2.1 (and previous versions) use a more elaborate scheme.  Each 256-byte sector is recorded as 410 bytes on the disk surface.  The subroutine PRE.NYBBLE converts the 256-byte buffer to 410 bytes of 5-bits each.  then the 5-bit values are converted to 8-bit values from NYBBLE.TABLE.  These 8-bit values are chosen carefully; they have the following properties:  1) the first bit is "1";  2) no consecutive zero-bits; and 3) the values $AA and $D5 are not used.  As a sector is read back into memory, BYTE.TABLE is used to convert the 8-bit codes back to 5-bit values.  POST.NYBBLE converts the 410 5-bit values back to 256 8-bit bytes.

In case you are curious, PRE.NYBBLE moves the bits from 256-bytes to 410 bytes like this:
!lm15

1.  The first 5 bytes are rearranged into 8 bytes:

     5 input bytes       8 output bytes
    7 6 5 4 3 2 1 0     7 6 5 4 3 2 1 0

    A A A A A B B B     0 0 0 A A A A A
    C C C C C D D D     0 0 0 C C C C C
    E E E E E F F F     0 0 0 E E E E E
    G G G G G H I J     0 0 0 G G G G G
    K K K K K L M N     0 0 0 K K K K K
                        0 0 0 B B B H L
                        0 0 0 D D D I M
                        0 0 0 F F F J N
!np
2.  The 8 bytes are stored at the end of the 8 sections (at BB32, BB65, BB98, BBCB, BC32, BC65, AND BC98).

3.  The second group of 5 bytes is rearranged into 8 bytes, and stored right before the first 8 (at BB31, BB64, ..., BC97).

4.  The next 49 groups of 5 bytes are treated in the same way, with the last group being stored at BB00, BB33, BB66, BB99, BBCC, BC00, BC33, AND BC66.

5.  The top 5 bits of the last byte are stored at BBFF, and the bottom 3 bits of the last byte are stored at BC99.

!lm10
DOS 3.3 uses an even better scheme, but it requires a change in the controller ROMs.  The change to one ROM gives you a different boot program; the other ROM makes the controller able to read two consecutive zero-bits accurately.  (Note that SOME controller-drive combinations may be able to read two zero-bits in a row accurately WITHOUT the new ROM.  Anyway, mine works!)  DOS 3.3 converts the 256 bytes to 342 6-bit values; since each sector is shorter, more sectors can be written in each track.  I may publish the disassembly of these same subroutines in the DOS 3.3 version next month.

Remember that DOS 3.2.1 puts 13-sectors on each track, with each sector having this format:  sync bytes, address header, sync bytes, data block.  Sync bytes are written to automatically synchronize the reading process, so that we can be sure we are not splitting bytes.  Each sync byte is 8 one-bits followed by 1 zero-bit.  The address header is 14-bytes long on the disk surface, and looks like this (in hex):  D5 AA B5 vv vv ss ss tt tt cc cc DE AA EB.  "vv vv" stands for the two bytes used to record the volume number; "ss ss" is the sector number; "tt tt" is the track number; and "cc cc" is the checksum of the volume, track, and sector.  The data block is like this:  D5 AA AD <410 bytes of data> <checksum> DE AA EB.

