PART 2: THE DISK SYSTEM

Accessing the disk system is slightly more difficult than tape, but then it is a more complex storage system. As far as tape is concerned, you can only read a file & store it. With disks, not only can you read files, but access tracks & sectors and even read & write different disk formats. Again a majority of the work in talking to the disk system is handled by the Dragon's DOS and all you have to do is call the appropriate ROM routine.

All the 'legal' ROM routines are stored in a jump table at the start of the DOS ROM at $C004-$C02E. This means that no matter which DOS you are using, calling these routines will have the same effect. This table contains addresses which point to the relevent DOS routines, and should be called from assembler using an indirect jump (using square or round brackets) eg.

	JSR [$C004]

This is as far as compatability goes between different DOS's. Even the workspace differs between different variants. If you jump into the ROM directly, then it is highly likely that whilst the routine you have called will work, executing it on another DOS will cause it to crash.

THE FILING SYSTEM:

The top level of the disk system, is the DOS filing system. This manages all the files stored on the disk, and you need know nothing about tracks & sectors in order to use it. There is a host of ROM routines to manage this, but the primary ones are as follows:

[$C008] - validate a filename
[$C00A] - 'open' a file
[$C00C] - create a file
[$C012] - close a file
[$C014] - load some file data
[$C016] - write some data to a file

The Dragon's DOS system uses a number to represent each file that is open from 1 to 10.

Using the calls is fairly straightforward. First, you format a filename in memory (using the same format as normal when referring to DOS files), then call [$C008] to validate it:

Workspace:

FILENAME FCC \2:TESTFILE.DAT\
DEFEXT   FCC \DAT\
FCBN     RMB 1   
TESTDATA FCC \THIS IS A TEST DISK FILE\
START    LDX #FILENAME    - X points to the name
	 LDB #14          - B = filename length
	 LDY #DEFEXT      - default extension
	 JSR [$C008]      - validate filename
	 BNE ERROR        - if an error occurs

The Y register can point to a default file extension, in this example DAT. If you then specify a filename without an extension (as you would when loading BASIC files) this extension is assumed (if you don't want a default extension, then point the Y register to a block of 3 spaces ie. FCC \ \.) When the routine is called, it sets the zero flag if an error occurs, which can be acted upon by the BNE instruction to an error handler. If an error has occured, it puts the BASIC error number into the B register, and jumping to $8344 will generate this error eg.

ERROR JMP $8344   - generate the BASIC error.

After the filename is validated, call [$C00A] to open the file. This should always be the case, even if the file doesn't exist yet. This routine will allocate your file it's own number. As before, the routine sets the zero flag if an error occurs, but you need to be careful if you're writing a new file, as it will indicate an error if the file doesn't yet exist:

	JSR [$C00A]  - open the file
	BEQ OPENOK   - opened successfully
	CMPB #$A0    - did routine return ?NE 
		       ERROR.
	BEQ OPENOK   - yes, so OK
	BRA ERROR    - generate an error 
		       otherwise

If the file is opened successfully, this routine will return the number assigned to the file (the File Control Block Number). Up to 10 disk files can be open at a time on the Dragon, and each is given a number from 1 to 10 by the DOS. From now on, all the disk operations are performed simply by referring to this File Control Block Number:

 OPENOK STA FCBN     - store the Control Block 
		       No. returned in A         
			  register

The first thing to do when writing a new file, is to create it, by calling [$C00C]. This requires the File Control Block Number in the A register, and since we have just loaded it:

	JSR [$C00C]  - create a new file
	BNE ERROR    - deal with any error

This routine also deals with the renaming of files to .BAK if the file already exists.

In order to write some data, use the [$C016] call:

	LDA FCBN     - our file control block no
	LDX #TESTDATA - point to data to write
	LDU #24      - no of bytes to write
	LDY #0000    - Y & B registers hold the
	CLRB         - file pointer
	JSR [$C016]  - write the data
	BNE ERROR    - handle any error

The inputs to this routine are straightforward, except for Y & B which are the point in the file you wish to write to. Unlike tape, where you must write data sequentially, byte after byte, with disk you can write anywhere in the file. In this case, we want to start at the beginning, so set both values to 0.

Now the file can be closed:

	LDA FCBN      - file to close
	JSR [$C012]   - close the file
	BNE ERROR     - any error
	RTS

It is important that all files are closed, otherwise the disk will become corrupt. This call is responsible for updating all the directory information & file sizes. Because of this, it is worthwhile modifying your error routine to close the file if an error occurs:

 ERROR PSHS B        - preserve the error no
       LDA FCBN      
       JSR [$C012]   - close the file
       PULS B        - restore error no
       JMP #8344     - generate the error

If you were now to run this code, and directory the disk in drive 2, you should find a file 24 bytes long named TESTFILE.DAT.

Having written the test file, it can be read back from disk using a similar method. The following sample program demonstrates this, taking much of the code from the previous article:

FILENAME FCC \2:TESTFILE.DAT\
DEFEXT FCC \DAT\
FCBN   RMB 1
TEXTSCR EQU $400  - address of the text screen
LOADER LDX #FILENAME     - point to filename
       LDY #DEFEXT       - default extension
       LDB #14           - filename length
       JSR [$C008]       - validate filename
       BNE ERROR 
       JSR [$C00A]       - open file
       BNE ERROR         - note we don't need to

			 check for ?NE error
			 since this is a read
			 operation only.

       STA FCBN       - store File Control No.
       LDY #24           - 24 bytes to load
       LDX #TEXTSCR      - load it onto the text
			   screen
       LDU #0000         - file read pointer
       CLRB           - starting at file start
       JSR [$C014]       - load the file
       BNE ERROR         
       LDA FCBN
       JSR [$C012]       - close the file
       BNE ERROR
       RTS

 ERROR JMP $8344         - error handler

Running this program, will display the text THIS IS A TEST DISK FILE in the top left hand corner of the screen.

Whilst this is all well & good, these programs are rather too simplistic for everyday use. Often as not, programs will not have a fixed filename, you won't know how big the file is or even where to start reading or writing from. Again making use of the Dragon's DOS & ROM makes life significantly easier.

For example there are standard ROM calls which can convert a filename passed as a string to the format which the validate filename [$C008] routine needs. This would enable you to specify the filename as part of an EXEC call eg.

EXEC 21480,"2:TESTFILE.DAT" or

FI$="2:TESTFILE":EXEC 21480,FI$

The code to accomplish this is as follows:

	JSR $89AA   - skip the comma
	JSR $8887   - Get String Calls
	JSR $8877
	LDX >$52    - $52 points to string 
		      descriptor
	LDB ,X      - byte 0 contains length
	LDX 2,X     - byte 2,3 points to string
	LDY #DEFEXT - Y still point to def extn
	JSR [$C008] - validate filename
	BNE ERROR

The open file call [$C00A] also returns some additional information, apart from the File Control Block Number. Location $F1 also contains the File Control Block Number and is used by DOS to store the current control block in use. Location $EB contains the drive number of the current file, but most importantly the X register is set to point into the Control Block of the file.

Each file in use has it's own Control Block which consists of 31 bytes stored in the DOS's workspace between 1536-3072 in memory. Since 10 files can be open at once, there are 10 of these blocks. Each File Control Block contains details about the file in use, such as it's name, drive number, details of tracks/sectors, directory data etc. etc. most of which is not particularly useful to us. However it does contain the current read & write pointers of the current file.

These pointers tie up with the registers we have to load when calling the write a block of data [$C016] or read a block of data [$C014] routines. As a file is read or written, they are automatically updated with whereabouts you are in the current file. Since for most applications, you won't want to manipulate these pointers yourself, you can let the system do it for us.

The X register passed back from the open call [$C00A] points to the current read pointer in your file control block. This is a bit of a tricky concept, it makes my head spin if I'm not fully awake as it is. The read pointer is 3 locations in memory which tell you where you are in the current file. The X register contains the location in memory where you can find these 3 locations, so it effectively 'points' to the read pointer.

As an example, suppose you wanted to read in 20 bytes from a file, process them, then read another 20 bytes etc. etc. You could make use of the read pointer as follows:

	-- get the filename, validate it --

	JSR [$C00A]    - open file
	STA FCBN       - store control block no
	STX READPTR    - preserve X register

      
 READLP LDA FCBN       - file no to read from
	LDX #BUFFER    - buffer for data to read
	LDY #20        - want to get 20 bytes
	LDU READPTR    - fetch the pointer to
			 the read pointer
	LDB 2,U        - fetch the lower part of
			 the read pointer
	LDU ,U         - fetch the upper part of
			 the read pointer
	JSR [$C014]    - read the block
	BNE ERROR

	-- process data --

	BRA READLP     - back to get next block

Writing a file is equally straightforward except you need to add 4 to the X register to get the pointer to the write pointer:

	-- get the filename, validate it --

	JSR [$C00A]    - open file
	STA FCBN       - store control block no
	LEAX 4,X       - add 4 to get write ptr
	STX WRITEPTR   - preserve X register

      
 WRITLP LDA FCBN       - file no to write to
	LDX #BUFFER   - buffer for data to write
	LDU #20        - want to store 20 bytes
	LDX WRITEPTR   - fetch the pointer to
			 the write pointer
	LDB 2,X        - fetch the lower part of
			 the write pointer
	LDX ,X         - fetch the upper part of
			 the write pointer
	JSR [$C016]    - write the block
	BNE ERROR

	-- fetch next 20 bytes into buffer --


	BRA READLP     - back to get next block

The 2 programs show up another problem - when to stop reading & writing. Presumably, the write routine will stop when the 'fetch next 20 bytes into buffer' routine identifies there is no more data to write, but the read routine needs some way of detecting when the end of file has been reached.

Again, the DOS comes to the rescue again, with another call:

[$C00E] - get file length

This will return the length of the file in the same format used to store the pointers. It can therefore be called after opening the file:

	FILELEN RMB 3  - storage for file length

	LDA FCBN     - control block no of file
	JSR [$C00E]  - get file length
	BNE ERROR    - any error
	STU FILELEN  - upper part of file len
	STA FILELEN+2  - lower part of file len

This can then be compared with the read pointer at the required time:

	LDU READPTR  - get pointer to read ptr
	LDB 2,U      - get lower part of ptr
	LDU ,U       - get upper part of read
ptr
	CMPU FILELEN - compare upper part of
		       pointer with upper part
		       of file length
	BHI EOF      - exceeded file length
	BLO MOREDATA - still more data

at this point we know that the upper part of the write pointer = upper part of the file length, so now check the lower part.

	CMPB FILELEN+2 - compare with lower part
	BHI EOF        - exceeded file length
MOREDATA  

-- continue to read next data chunk --

So far I've managed to cover accessing the Dragon's disk system from machine code, making use as far as possible of what DragonDOS does automatically. Whilst it is fine to read & write your own format files, there may come a time when you want to read or write a recognized DOS file such as a BASIC or binary file. If you attempt to LOAD the TESTFILE.DAT file we wrote several articles back, then the system will refuse to do so, returning an ?FM ERROR. Even renaming the file to .BAS or .BIN will make no difference.

A recognized DOS file is identified by the first 9 bytes of the file. You may notice that whenever you save a .BIN file that 9 extra bytes are added to it. For example, just saving a 1 byte file eg

SAVE "1BYTE",1024,1025,1024

will result in a 10 byte file created on the disk.

These 9 bytes are used to identify the file, and therefore LOAD the file correctly. For example, the loading of a BASIC file is different from that of a binary file. The 9 byte header structure is defined as follows:

     Byte    0 - always hex $55
     Byte    1 - file type: 01 = BASIC
			    02 = Binary
     Bytes 2,3 - Load Address 
     Bytes 4,5 - Length
     Bytes 6,7 - EXEC address
     Byte    8 - always hex $AA

Data files (.DAT) have no header and just contain the data written to them.

Knowing this you can successfully read DOS structure files. More likely, is the need to read binary files, and therefore a simple piece of code to achieve this:

	-- assume file already opened --

	LDA FCBN    - file control block no
	LDX #BUFFER - buffer for header info
	LDY #9      - 9 bytes to read
	LDU #0000   - from start of file
	LDB #0
	JSR [$C014] - read header
	BNE ERROR
	LDA BUFFER+1 - get the file type
	CMPA #2      - is it binary
	BNE FMERR    - no, generate an FM error

proceed to load the file based on the header information:

	LDA FCBN
	LDX BUFFER+2 - get the start address
	LDY BUFFER+4 - get the length
	LDU #0000
	LDB #9       - start from byte 9
	JSR  [$6014]  - load the file into memory
	BNE ERROR
	LDX BUFFER+6 - load the EXEC address
	STX 157      - store in the EXEC vector
 
	-- close file & quit --

FMERR LDB $2C    - code for ?FM ERROR
      BRA ERROR  

Writing binary files is similarly straight forward. Simply build the 9 byte header in memory, write it out then dump the file out. In addition this format can also be used if you want to create user defined disk formats. For example, for my sampler system I defined a type 04 file, which uses exactly the same 9 byte structure except the start and length fields contain a multiple of 256 bytes. Hence a file length of 4 indicates 4*256 = 1K. (I didn't use type 3 files because this is actually defined as another DOS file type Segmented Binary which I've never used and don't know of anything that does).

Finally in this section on the DOS file structure, some other ROM calls useful for manipulating files:

[$C01A] - KILL a file

eg. 
	LDA FCBN
	JSR [$C01A]   - kill the current file
	BNE ERROR     - handle an error

[$C01C] - set/reset file protection

eg.
	LDA FCBN
	CLRB          - B = 0 Protect off
			B = 1 Protect on
	JSR [$C01C]   - protect off then
	BNE ERROR

[$C01E] - rename a file

You should use this in conjunction with the [$C008] validate filename call. Open the file as usual, then validate the new filename & call the rename routine:

	-- assume file already open -

	LDX #NEWFILE       - new file name
	LDB #NEWFILELEN    -new filename length
	LDY #DEFEXT 
	JSR [$C008]        - validate filename
	BNE ERROR
	LDA FCBN           - control no
	JSR [$C01E]        - perform rename
	BNE ERROR

The level below DOS's filing system concerns tracks & sectors directly. The Dragon's disk system implements this in a fairly straight forward manner, making the stepping up from 40 track single sided disks to 80 track double sided disks an easy process.

The specifications for a standard Dragon disk are as follows:

256 bytes per sector
18 sectors per track (1 to 18)
40 tracks per disk   (0 to 39)

Double sided disks are handled by increasing the number of sectors per track visible to the user, not complicating issues with side numbers, so for a double sided disk:

256 bytes per sector
36 sectors per track (1 to 36)
40 tracks per disk   

Any combination of disk can be used, double sided 40 disks, single sided 80 etc. The Dragon provides track & sector access through the following ROM calls in the jump table:

[$C004] - controlling 'processor' 
[$C026] - read absolute sector
[$C028] - write absolute sector
[$C02A] - verify absolute sector - later DOSs
	  only.

The read & write sector calls are the most useful and I'll concerntrate on those first. They both take 2 register inputs, the X register points to where the sector is to be read from/written to and the Y register contains the absolute sector to read/write. In addition, location $EB should b set to the drive number required.

The absolute sector is calculated as follows:

Absolute sector = sectors per track * track no. + sector no. - 1

Therefore to write track 2 sector 1:

Abs sector = 18 * 2 + 1 - 1 = 36

so the assembler code is:

BUFFER RMB 256
WRTSCT LDA #1       - drive 1
       STA $EB      
       LDX #BUFFER  - sector to write
       LDY #36      - abs sector no.
       JSR [$C028]  - write the sector
       BNE ERROR
       RTS
ERROR  JMP $8344    - BASIC error handler
:

and it's here that the problem emerges. By assuming you are using a single sided disk and putting 18 sectors per track into the formula will mess up anyone attempting to use a double sided disk, since for these disks:

Abs sector = 36 * 2 + 1 - 1 = 72

The other problem arises if you use this call immediately after you have put a different format disk in the drive or directly after you have switched the Dragon on, because the machine doesn't know what format the new disk is.

The answer to both these problems is to tell the system what the disk is, then use the data held by DOS to work out what the disk type is.

The best way to tell DOS what the disk is, is simply to do a DIR on the new disk. This will read the information off the disk into it's workspace. You can then refer to this when calculating how many sectors per track are in use:

This routine writes the BUFFER to the DRIVE, TRACK, SECTOR specified in memory. It uses the Drive Descriptor table which consists of 25 bytes per drive to fetch the number of sectors per track.

DRVTBL  EQU $6A7   - drive descriptor for drv 1
DRVSIZE EQU 25     - no. of bytes in table
BUFFER  RMB 256    - buffer to write
SECTOR  RMB 1      - sector no. to write
TRACK   RMB 1      - track no. to write
DRIVE   RMB 1      - drive no. to write
WRTTRK  LDA DRIVE  - drive no required
	STA $EB
	DECA       - set drive range from 0-3
	LDX #DRVTBL  - point to drive table
	LDB #DRVSIZE - setup for multiply
	MUL          - do (DRIVE-1) * 25
		       to select which drive
		       table
	LEAX D,X     - point to the physical
		       table in memory
	LDA ,X       - read the sectors/track
	LDB TRACK    - load the track number
	MUL          - do
			sectors per track *
TRACK
	ADDD SECTOR  - add the sector number
	SUBD #1      - and subtract 1
	TFR D,Y      - Y register ready for call
	LDX #BUFFER  - point to buffer
	JSR [$C028]  - write the sector
	BNE ERROR
	RTS

A similar routine can be used for reading a sector, simply changing the call to [$C026].

The lowest level interface DOS provides to the disk controller is through the Disk Operation Processor call. This provides all the track/ sector read/write calls and formatting calls. In most instances there are ways around using this call, reading & writing sectors for instance can be handled a lot easier via the Read & Write absolute sector calls detailed last time.

The disk operation processor indirect jump table interface is:

  [$C004] - Basic disk operation processor
  [$C006] - operation block address

The operation block address [$C006] points to where the data is held for the [$C004] call. In almost all instances it points to location $00EA, and you can probably get away with assuming that it does. The structure of this block is as follows:

  $EA    - Basic disk operation number
  $EB    - current drive number
  $EC    - track number
  $ED    - sector number
  $EE:EF - disk buffer address
  $F0    - status
  $F1    - current File Control Block Number

The majority of this data is self explanatory. In order to use the disk operation processor, you set up this data and call [$C004]. The operation codes to store in location $EA are:

  0 - restore to track 0
  1 - seek track
  2 - read sector
  3 - write sector, verify if VERIFY ON
  4 - write sector, no verify
  5 - write track
  6 - read track address
  7 - read sector to verify
  8 - read track address into locations 18-23

Codes 0-4 are straightforward, the remainder slightly more unusual and I'll cover these only briefly.

The following example demonstrates the usage of this call, in reading track 3, sector 4 into a buffer.

  BUFFER RMB 256     - buffer for sector
	 LDA #1
	 STA $EA     - prepare for seek
	 STA $EB     - on drive 1
	 LDA #3
	 STA $EC     - seek for track 3
	 LDA #4
	 STA $ED     - setup for sector 4
	 JSR [$C004] - perform the seek
	 BCS ERROR
	 LDA #2
	 STA $EA     - prepare for read
	 LDX #BUFFER
	 STX $EE     - point to our buffer
	 JSR [$C004] - perform the read
	 BCS ERROR
	 RTS

  ERROR  JMP $8344

Note that the [$C004] operation sets the carry flag if an error occurs, instead of the zero flag, hence the need for a BCS instead of a BNE instruction on testing for an error. The seek command is used to move to different tracks, therefore there is no need to call seek when reading sectors from the same track.

And thats just about all there is to it. The remaining codes 5-8 are as follows:

5 - Write track. This is a call to format the current track. However, you can't simply call the ROM routine and expect to get a Dragon formatted track. The disk buffer ($EE:EF) should point to the data required to encode a Dragon format track. In addition, some more of the workspace pointed to by [$C006] needs data to be set up. I wrote an article a while back for UPDATE which details using this call to format 1 track of a disk. Suffice to say, the only other use for this call would be in formatting non Dragon disks (more next time!).

6 - Read address of track. This is used as a kind of 'disk status' call. It returns 6 bytes of data into the buffer pointed to by $EE:EF. It's principal use is to find out which track number the disk head is on, in order to prevent unecessary seeks. The 6 byte structure is as follows:

0 - track number
1 - side number (0=side 1, 1=side 2)
2 - sector number*
3 - sector length ( 1=256 bytes/sector)
4 - sector CRC 1*
5 - sector CRC 2*

* The only reliable information you can get from this call is the track number & side number. The sector number returned is whatever sector the disk head happens to be over at the time the operation is called. Bytes 4 & 5 are the sector's checksum values, used for data integrity.

7 - Read sector to verify. This reads the sector, and discards all but the first 2 bytes which it stores into temporary workspace locations 79,80.

8 - Read address of track into locations 18-23. As per call 6, except the 6 bytes are placed into temporary workspace.

After the Disk Command Processor, the next level is talking to the disc controller chip directly. After that, its off into the realms of disc encoding methods which is way beyond the scope of most of us. In fact, there are very few reasons you'd want to talk to the disc chip directly anyway. To do so means that you're seriously into disc formats or that you're more than probably a trifle nuts. If you want to persist, your best bet is to get hold of a WD2797 (disk controller chip) data sheet and spend a few hours one evening having a quiet read. Therefore, in this last but one article I'll briefly touch on how to access the chip through the Dragon and how to go about writing code to do it. The only gain to be made in accessing the disk controller chip directly, is the ability to read in alien disk formats (remember PC disks & BBC Micro disks are not alien, and the standard sector calls can handle these) which the controller cannot access normally. Any disc which is written using standard double or single density methods should be accessable then by the Dragon. However, you need to know a lot about the internal disc structure (see later) to understand the data you'll get back. The only other possible gain, is in performing some sort of specialized disc operation - I wrote an experimental program once to attempt to play samples direct off disc. The disc controller chip itself, is mapped into the IO map, like all the other IO devices, at address $FF40 as follows:

$FF40 - Command/Status register
$FF41 - Track register
$FF42 - Sector register
$FF43 - Data register

in addition, location $FF48 is an external latch chip, which is used to switch on the disc motors, select the drive & select density required on the disc controller. On the face of it then, accessing the disc is straightforward. Simply turn the drive motors on, select the drive (thru $FF48). Then put the track number & sector number required into $FF41, $FF42, put the required command into $FF40 (read, write etc.) and read or write the data from $FF43. When all is done, check the status in $FF40 and take any necessary actions. And don't forget to switch the motors off.

In fact, that is all there is to it, except that there is a major problem. The data comes across so fast, that the Dragon can't keep up with it.

Therefore, a rather strange method of extracting the data has to be used which makes use of both of the two main interfacing methods.

The first method, is that of polled IO. The Dragon uses this a lot, because it's simple and easy to implement. The idea, involves asking each device if it wants some more data, or has got some more data to pass back. Both the keyboard & the printer are polled on the Dragon. Whilst polled IO is simple, it's also slow & time wasting. The processor spends a lot of time asking a device if it's got some data, and 90% of the time it will say no. Like the keyboard, for example where although your typing may be pretty impressive, it's no match for the machine itself. Taking the other extreme, if a device on the system (like the disc controller) is chucking out data so fast, it's highly likely that whilst the processor is off asking your keyboard if it's got some data, that the disc controller has just dumped half a sector down a black hole.

The way around this, is to use interrupt IO. This is where a device tells the processor that it wants attention, by pulsing one of the interrupt lines. The processor can then stop whatever its doing and go off and deal with the interrupt. The 6809 in the Dragon has 3 interrupt lines Interrupt Request (IRQ), Fast Interrupt Request (FIRQ), and Non-Maskable Interrupt (NMI). The Dragon itself, though makes very little use of these interrupts. On a no-frills D32, for example the only interrupt to occur normally is an IRQ 50 times a second to update the TIMER variable.

In an interrupt driven system, when an interrupt occurs, the processor stops what it is doing and looks to see what caused the interrupt (there may be more than 1 device on an interrupt line). When it has located the device requesting attention, the data then be handled, before the processor resumes what it was doing. This is ideally, how the disc system should work. On the BBC for example, an NMI goes off every time a byte is sent/received from the disc controller, whereupon the processor deals with it. Unfortunatly, on the Dragon there is a problem. The act of answering an interrupt involves dumping all the processor registers (A,B,X,Y,U,PC,DP,CC) onto the stack in order that when the interrupt has finished it can pull them all off and carry on with what it was doing. However, pushing registers onto the stack is the most time consuming operation the 6809 can do and by the time it's done this and gone off to service the interrupt, the data (like the last bus) has been and gone and where were you anyway?

So the Dragon, has to use polled/interrupt IO to read & write it's disks. This bears some explaining. The disk controller has 2 interrupt lines connected through the cartridge port, one connects to the NMI line on the processor, the other to the CART line, which connects to a PIA control line. The PIA, when programmed correctly will generate an FIRQ interrupt whenever the CART line is pulsed. Every time a byte is sent to the computer or written to the disk controller, the CART line gets pulsed, which in turn causes an FIRQ. When the current operation is complete, an NMI is generated.

The process works on the Dragon, by masking all interrupts setting up the disk for reading or writing and then going into a tight loop similar to the following:

READLP SYNC
       LDA >$43
       LDB >$22
       STA ,X+
       BRA READLP

The SYNC instruction simply waits for an interrupt to occur (an FIRQ from the CART line) to indicate a byte has arrived. However, the interrupt will not be acted upon because it has been masked (via an ORCC #$50 command previously). Instead, when the FIRQ occurs, it leaves the SYNC instruction & proceeds to read the byte returned by the disk controller in $FF43. Prior to this loop being called, the direct page register will have been setup to point to the top page $FF to enable these instructions to execute faster. The FIRQ status is then cleared by reading $FF22, and the byte read is stored into a buffer previously setup. The loop then returns to the SYNC instruction ready for the next byte.

When the operation is completed, the disk controller generates an NMI to break out of the loop. By its definition (Non-Maskable) this interrupt is not masked by the ORCC instruction and therefore executes normally. Its operation, is merely to discard what had just been pushed onto the stack, and return whatever called the disk routine.

From this, it is clear how the Dragon can read different length sectors (eg. the 512 byte ones on a PC disk). The machine has no control over how much data is received, the disk controller just transfers however much data it has to send.

Of course, this whole data transfer system is very heavy handed on the machine and is the cause of a lot of agravation. The first of these, is that in order to succeed the only source of interrupts during a disk operation can be the disk controller. Any other device causing an interrupt will break the SYNC instruction and cause the next data byte to be read or written when the controller isn't ready, with the resulting disk corruption to follow. Therefore all interrupts must be switched off, not by masking them (ORCC) but by physically deactivating (on the PIA or ACIA). Whilst the disk system handles those that it expects on the Dragon (like the TIMER interrupt) anything additional you may have added, or the serial chip on the D64 must be disabled by yourself prior to a disk operation.

The problems really show themselves up under operating systems like OS9, where switching off interrupts & masking them is a definite no-no. Everything under OS9 is interrupt driven, even the polling of the keyboard is put into the 50Hz IRQ routine enabling keystrokes to be buffered. This has the advantage, that you can type away whilst the machine is doing something else, and when it has finished, your text is displayed on screen. Of course, with the disk system constantly messing about with interrupts, you typing something like 'CHD /D1' whilst it's scrolling a directory, will probably turn into 'H D1'. It also makes putting things involving disk IO in background a waste of time, since every time the disk gets accessed everything freezes.

However, like it or not (and I don't) we're stuck with it. Providing you access the data in this way, all should go well.

The chip itself excepts 11 commands into it's Command Register ($FF40), most of these tie up with the Disk Command Processor commands and you can use that instead:

1. Restore to track 0

2. Seek

3. Step - this steps the disk head 1 track in the direction it was last stepped.

4. Step in - this steps the disk head 1 track towards track 00.

5. Step out - this steps the disk head 1 track towards track 79 (or 39).

6. Read sector

7. Write sector

8. Read address off track

9. Read track (see below)

10. Write track (format)

11. Force interrupt - this code is used to force an NMI interrupt and therefore abort whatever the controller is doing at the time. This also provides a way of resetting the chip.

The values to input into the command register for these commands is documented in the data sheet, and rather than detail them all here, I'll use one of the commands as an example.

On first glance, it would appear that the most useful command not implemented by the Disk Operation Processor [$C004] is the Read Track call. This provides the opposite to the write track command (surprise) and reads back the information written to the disk when it was formmatted. However, there is a good reason for it not being implemented. When formatting the disc (Write Track) certain bytes written to the Data Register have special meanings and tell the chip to perform some special disk operation (synchronize data, special timing etc.) During the Read Track call any data bytes encountered which match these special codes will cause them to be acted upon. This means that valid data can get interpreted as a control code with the resulting data being corrupt. Usually, the data gets shifted by 1 or 2 bits. However, I've included a listing of the code I used to discover this purely for demonstrational purposes as to how to use the disk chip. This call, will not only return all the sectors contained on the specified track, but all the information about these sectors written at format time and not normally accessable.

Assuming we've already SEEKed to the required track (as an example track 16), the format of the read track call is as follows:

binary#  7  6  5  4  3  2  1  0

	 1  1  1  0  0  E  U  0

E = head settling time. If set to 1 the disk head is allowed 30MS to settle down, otherwise not. Since we know no better, setting it to 1 should prevent any problem.

U = Update side select output. If set to 0, output for side 1, or if set to 1, output for side 2. Assume single sided disks for the moment. Therefore READ TRACK = 228.

The following program issues the read track command to the disk controller & reads in whatever track the disk head happens to be over (ie. no seek). The data is stored in the graphics workspace from 3072 onwards, and you should PCLEAR 5 prior to running it.

    *DISK READ TRACK DEMO
    *INTERRUPTS OFF
    START ORCC #$50
    *CLEAR LAST DISK STATUS
    LDA $FF40
    *FORCE INTERRUPT (NOIRQ)
    LDA #$D0
    STA $FF40
    *DISK MOTOR TIMEOUT
    STA $605
    *SELECT DRV 0: BITS 0,1:0
    *MOTORS ON   : BIT  3  :1
    *ENABLE NMI  : BIT  5  :1
    LDA #36
    STA $FF48
    *COPY OF DRIVE SEL LATCH
    STA $607
    *SWITCH IRQS OFF ON ALL PIAS
    PIA 0:SIDE A
    LDA $FF01
    PSHS A
    ANDA #$FC
    STA $FF01
    *PIA 0:SIDE B
    LDA $FF03
    PSHS A
    ANDA #$FC
    STA $FF03
    *PIA 1:SIDE A
    LDA $FF21
    PSHS A
    ANDA #$FC
    STA $FF21
    *PIA 1:SIDE B
    LDA $FF23
    PSHS A
    ANDA #$FC
    STA $FF23
    *ENABLE FIRQ ON PIA 1:SIDE B
    ORA #$37
    STA $FF23
    *USE EXISTING NMI ROUTINE
    *SETUP FOR DIRECT PAGE $FF ADDRESSING
    LDA #$FF
    TFR A,DP
    *READ WHATEVER TRACK WE'RE ON
    BSR READTRK
    *NMI PUTS CONTROL BACK HERE
    *PUT DP BACK
    CLRA
    TFR A,DP
    *RETURN PIA CONTROL REG SETTINGS
    PULS A
    STA $FF23
    PULS A
    STA $FF21
    PULS A
    STA $FF03
    PULS A
    STA $FF01
    *ALL DONE
    RTS
    *BUFFER FOR TRACK DATA
    READTRK LDX #3072
    *O/P READ TRACK CALL, HEAD LOADING DELAY, 
    SIDE 0
    LDA #228
    STA >$40
    *WAIT FOR DRIVE READY
    NRDY LDB >$23
    BMI READ2
    BRA NRDY
    *READ TRACK, NMI EXITS LOOP
    READ1 SYNC
    READ2 LDA >$43
    LDB >$22
    STA ,X+
    BRA READ1

The program first masks interrupts, and issues a Force Interrupt command to the disk controller. This effectively resets the chip. It then masks all the interrupts on the Dragon's PIA chips, preserving the original settings on the stack. The drive is then selected, and the motors switched on. Location $605 is used by all DOSs I believe as a counter to switch of the motors by the Dragon's normal IRQ routine so we do not need to switch them off ourselves. Once the PIAs have been reset, FIRQs are enabled on the second PIA which connects to the disk controller (via the CART line). The direct page is setup to the IO area, the subroutine to actually read the track is then called. This issues the command & waits for the drive to send the first byte. Ideally, there should be a timout counter here so that if a problem occurs the machine doesn't wait forever. When the first byte is received, the routine goes to the SYNC loop & reads the track. The systems NMI vector routine will return us to the main program when all the data is read, the PIAs are returned to normal and the program finishes.

If you type PRINT PEEK(&HFF40) after the operation, this will tell you if the data has become corrupt due to a special control byte being read. If it is 0, then the data has read in okay.

What you get back is about 6K of data, which all appears pretty meaningless. There is about 100 bytes of track header data, followed by the first sector. This too has some header information, the track number, side (0=side 1), sector no, sector length (01=256 bytes/sector) plus about 40 more bytes of data before the actual sector. Then there some sector footer data. This lot repeats for each sector (the Dragon also interleaves sectors, so sector won't come after sector 1), and at the end of the last sector is some final padding out data to finish the track off.

I've found one use for this call! I've managed to write samples (yet another sampler utility!) directly to tracks using the Write Track command, and use the Read Track call to get them back so they can be played off disk real time.

One final word of warning. DOS likes to know exactly what is going on disk wise, and that why it keeps copies of the disk controller registers in its own workspace, as an example to avoid seeking to a track when you are already over it. Using your own routines, which will almost certainly not update the DOS variables, can leave the system in a bit of a dodgy state. Therefore, its worth testing your routines on scrap disks (as always) and saving the assembler routines to an unimportant disk. Even when you've got it working, its worth using the routine and then resetting the machine fully.

---------------------------------------------