Apple Disk Checker
Here's a description of the technique and a program which is fast, simple, and doesn't require any modifications to DOS.
On sector 0 of track 17 of every diskette initialized by Apple DOS 3.3 is something called the Volume Table of Contents, or VTOC.
One of the things in the VTOC is the bitmap which tells which sectors are used and which are free. Each track on the diskette is represented by four bytes on the bitmap. There are 16 sectors per track in DOS 3.3. The first two bytes of the four assigned to each track keep count of the used and free sectors; the other two bytes are reserved for expansion and contain zeros. The problem, then, is how do you keep count of 16 sectors with just two bytes?
The bitmap is exactly what the name says: a map expressed in bits (binary O's and l's). It shows which sectors are used and which are free. When we look at memory in the Apple with the monitor, we see each byte as two hex characters. FF, for example, is one byte in memory. These hex characters represent the binary bits that make up that byte. In other words, the hex FF that we see represents 1111 1111 in the byte. The first F represents the first half-byte, and the second F the last half-byte. If the byte was, say, Cl, then in binary it would be 1100 0001.
Recall that DOS is keeping track of 16 sectors for us. Recall also that DOS is using two bytes of eight bits each to do it. Each bit in the two bytes keeps track of one of the 16 sectors. The map is laid out like this:
Byte 1 Sectors F E D C B A 9 8 Byte 2 Sectors 7 6 5 4 3 2 1 0
DOS uses a binary 1 to show a sector free and a binary 0 to show it used. Thus if sector F on a particular track is free, DOS sets the first bit of the first byte to 1.
Now for a few examples. Suppose track 8 on a diskette has only sectors 15 and 8 free. The bitmap for that track would be:
Byte 1 1000 0001 Byte 2 0000 0000
The first bit in the first byte is set to 1; this is the map location for sector $F (15). The locations for sectors E, D, C, B, A, and 9 are all set to 0 since these sectors are used. The bit location for sector 8, however, is set to 1 since that sector is free. In a similar fashion, the second byte containing the map to sectors 7, 6, 5, 4, 3, 2, 1, and 0 contains all zeros since the sectors are all used.
If we were to look at this bitmap, we would see it in hex as:
Byte 1 81 Byte 00
Let's take one more example. Suppose track 12 has sectors 15, 14, 11, 9, 7, 3, 2, and 1 all free. The bitmap would be:
Byte 1 1100 1010 Byte 2 1000 1110
If you check this against the layout for the map, you will see that the l's bits correspond to the free tracks. If we look at the map, we'll see the hex representation of the binary as:
Byte 1 CA Byte 8 8E
Understanding Disk Checker
The problem, then, is twofold. First we must get the VTOC into memory so we can look at the bitmap, and then we must count the binary l's to see how many free sectors there are on the diskette.
Apple DOS has a machine language subroutine called the Read/Write Track Sector routine, or RWTS for short, that will read or write a sector on a diskette. We can enter the routine through a jump to subroutine (JSR) instruction to address $3D9. The RWTS routine requires some information from us about what it is we want to read and where in memory to place it, etc. We provide the routine with this information in the form of a couple of tables that we build in memory. The first table is called the Input Output Control Block, or IOCB for short. It will provide the RWTS subroutine with the slot, drive number, track, sector, and the address in memory to store what it reads. The other table we need to provide is called the Device Characteristics Table.
As you might guess from the name, it provides the RWTS subroutine with information about the disk drive itself.
The RWTS routine normally reads a whole sector at a time, and since a sector is 256 bytes long, we must reserve memory space of that size to place the information that the Read/Write Track Sector subroutine reads in. Technically, this space is referred to as a buffer.
Table 1 shows the IOCB that we will be using, while Table 2 shows the Device Characteristics we will need.
Table 1: The IOCS
|Byte (in hex)||Value (in hex)||Description|
|$00||$01||Table type (always 1)|
|$01||$60||Slot number * 10 (slot 6)|
|$03||$00||Volume ID (00 means 6)|
|$04||$111||Track numbers ($ 11 is the track for the VTOC)|
|$05||$00||Sector number ($00 for the VIOC)|
|$06-09||$00,$60||Address of the buffer, low/high format|
|$0B||$00||Read whole sector|
|$0C||$00||Control code for a read|
|$0D||$00||Space for return code from DOS|
|$0E||$00||Volume ID of last access|
|$0F||$60||Slot number of last access|
|$10||$10||Drive number of last access|
Table 2:Device Characteristics
|Byte (in hex)||Value (in hex)||Description|
|$00||$00||Device type (always a 00 for Disk II)|
|$01||$01||Phases per track (always 01 for disk)|
|$02-03||$EF,$D8||Motor on time (always these values for disk II)|
After we build these two tables in memory, we load the Y and A registers with the address of the IOCB table, and JSR to $3D9. The RWTS subroutine will then read the track and sector that we provided it in the IOCB table and place that sector in the buffer address we provided it in the IOCB. Half of our problem is now taken care of; we have the VTOC in memory. Now all we have to do is count the binary l's in the bitmap to see how many free sectors we have.
The complication is that the bitmap will be in hex and we need to see it in binary. There are several ways in which we could do this. One is to use the machine language instruction ROL. ROL stands for rotate left. What this instruction does is roll the bits of a byte one bit to the left. It rolls the leftmost bit into the Carry flag, so if the bit was a 1, then the Carry flag gets set on.
As an example, say the accumulator held $C1. In binary this would be 1100 0001. The first ROL would set the Carry flag on, since the leftmost bit is a 1 and is rotated into the Carry flag. After that the contents of the accumulator look like this: 1000 0010. Now if we reset the Carry flag and ROL again, the flag gets set and the accumulator looks like this: 0000 0100.
What we will do is load the accumulator with the contents of the first byte of the bitmap. Then we will roll it left eight times as we count the number of times the Carry flag gets turned on. Each time the flag is set on we will increment (add one to) a special memory location that will hold our total. We then go get the next byte of the bitmap and repeat the entire operation again. The process continues until the whole bitmap is counted.
Recall that earlier we found out that each track has four bytes in the bitmap, but only the first two are really used. Our machine language program counts them all, but since the last two contain all zeros if doesn't matter. This is less complicated than trying to skip the unused bytes since it is easier to spend a few microseconds to count some zeros than to build the logic to go around them.
Program 1 is a BASIC program which POKEs in the machine language for "Disk Checker," then executes it. After you have typed in and run Program 1, you can save a copy of the machine language with:
With this copy of the machine language, you can use Program 2 as your disk HELLO program, so that you'll be told the number of free sectors on the disk when you boot the system.
Program 1: Disk Checker Loader
10 HOME 20 FOR I = 768 TO 852 30 READ A:CK = CK + A: POKE 1,A: NEXT 35 IF CK < > 7986 THEN PRINT "ERR OR IN DATA STATEMENTS": STOP 40 CALL 768 50 T = PEEK (896) + PEEK (897) * 2 56 60 PRINT "THERE ARE ";T;"FREE SECTORS" 61 REM 62 REM ****************** 65 REM DATA IS ASSEMBLY PR06RAM+I0 B AND DEVICE TABLES 67 REM 68 REM ********************* 70 DATA 169,3,160,64,32,217,3,169,0,141
80 DATA 128,3,141,129,3,24,162,0,160,56 90 DATA 185,0,96,42,144,24,24,238,128,3 1OO DATA 72,169,0,205,128,3,208,3,238,129 110 DATA 3,104,232,224,8,208,232,76,55,3 12O DATA 232,224,8,208,224,162,0,200,192,196 130 DATA 208,214,96,0,1,96,1,0,17,0 14O DATA 81,3,0,96,0,0,1,0,25,96,1 150 DATA 0,1,239,216
Program 2: Free Sector HELLO
10 HOME :D* = CHR$ (4) 20 PRINT D$"BLOAD SPACE.OBJ" 30 CALL 768 40 T = PEEK (896) + PEEK (897) * 256 50 PRINT "THERE ARE";T;"FREE SECTORS"