The Acorn DFS Osword commands - by - Gordon Horsington ---------------------------------------------------------- Module 1. The DFS Osword commands (part 1) ------------------------------------------ +----------------------------------------------------------+ | All the DFS modules in this series use programs which | | experiment with the format and contents of discs. These | | experiments may have disasterous effects if you use any | | of the programs on discs which store programs or data | | which you cannot afford to lose. You should first try | | out the programs using discs that have either been | | duplicated or, better still, have not been used at all. | +----------------------------------------------------------+ The DFS ROM intercepts and recognises three Osword calls. Osword &7D reads the number of times a disk has been written, Osword &7E reads the number of sectors on a disc, and Osword &7F executes the 8271 disc controller commands. ---------- Osword &7D ---------- Osword &7D reads the catalogue for the current default disc, as specified by the most recent *DRIVE command, and extracts the number of disc cycles from the catalogue. The number of disc cycles is a BCD number in the range from 0 to 99. This number gives some indication of how many times the disc has been written. Because The disc cycle number restarts at 0 after reaching 99 this is not a reliable count. The catalogue is read by Osword &7D and stored in the first two pages of the paged ROM absolute workspace (pages &0E and &0F with OS 1.2). The result is stored in a one byte parameter block specified by X and Y registers on entry. The program CYCLES demonstrates how Osword &7D can be used to read the disc cycles. 10 REM: CYCLES 20 osword=&FFF1 30 DIM mcode &100 40 FOR pass = 0 TO 2 STEP 2 50 P%=mcode 60 [ OPT pass 70 LDA #&7D 80 LDX #result MOD 256 90 LDY #result DIV 256 100 JSR osword 110 RTS 120 .result 130 EQUB &00 140 ] 150 NEXT 160 CALL mcode 170 PRINT"Disc cycles = ";~?result ---------- Osword &7E ---------- Osword &7E reads the catalogue for the current default disc, as specified by the most recent *DRIVE command, and extracts the number of sectors available on the disc. There are 800 (&320) sectors on an 80 track Acorn DFS disc and 400 (&190) sectors on a 40 track Acorn DFS disc. Non-standard and copy-protected discs may have different numbers of available sectors. The catalogue is read by Osword &7E and stored in the first two pages of the paged ROM absolute workspace (pages &0E and &0F with OS 1.2). The result is stored in a four byte parameter block specified by the X and Y registers on entry. The program HOWMANY demonstrates how Osword &7E can be used to read the number of available sectors on a DFS disc. The least significant byte of the number of sectors will be in byte &01 of the result and the most significant byte in byte &02 of the result. Bytes &00 and &03 of the result should always be zero. 10 REM: HOWMANY 20 osword=&FFF1 30 DIM mcode &100 40 FOR pass = 0 TO 2 STEP 2 50 P%=mcode 60 [ OPT pass 70 LDA #&7E 80 LDX #result MOD 256 90 LDY #result DIV 256 100 JSR osword 110 RTS 120 .result 130 EQUD &00 140 ] 150 NEXT 160 CALL mcode 170 PRINT"&";~result?2;~result?1;" Sectors" ---------- Osword &7F ---------- Osword &7F executes the 8271 disc controller commands. If the disc interface uses a 1770 disc controller then Osword &7F emulates the 8271 command set using the 1770. The complete command set executed by Osword &7F is shown in figure 1. Not all the 8271 commands can be emulated by the 1770 and the Osword &7F commands from &6C to &7D are not fully implemented with the Acorn 1770 disc interface. When you write software which uses the Osword &7F commands from &6C to &7D you should take care to ensure that your programs will work with the 1770 disc controller as well as with the 8271 interface. Some of the example programs used in this module use these partly implemented Osword &7F commands and may not work as expected with all 1770 disc interfaces. The programs used to illustrate the other Osword &7F commands all work with the Acorn 1770 DFS. +---------+--------+------+-------------------------------------------+ | Command | Param- | 1770 | Action | | number | eters | | | +---------+--------+------+-------------------------------------------+ | &4A | 2 | Yes | Write data 128 bytes | | &4B | 3 | Yes | Write data multi-sector | | &4E | 2 | Yes | Write deleted data 128 bytes | | &4F | 3 | Yes | Write deleted data multi-sector | | &52 | 2 | Yes | Read data 128 bytes | | &53 | 3 | Yes | Read data multi-sector | | &56 | 2 | Yes | Read data and deleted data 128 bytes | | &57 | 3 | Yes | Read data and deleted data multi-sector | | &5B | 3 | Yes | Read sector ids | | &5E | 2 | Yes | Verify data and deleted data 128 bytes | | &5F | 3 | Yes | Verify data and deleted data multi-sector | | &63 | 5 | Yes | Format track | | &69 | 1 | Yes | Seek | +---------+--------+------+-------------------------------------------+ | &6C | 0 | Part | Read drive status | | &75 | 4 | Part | Initialise 8271 | | &75 | 4 | Part | Load bad tracks | | &7A | 2 | Part | Write special register | | &7D | 1 | Part | Read special register | +---------+--------+------+-------------------------------------------+ Figure 1. The Osword &7F command set ------------------------------------ Osword &7F uses a variable length parameter block the address of which is specified by the X and Y registers on entry. Byte &00 of the parameter block is used to store the number of the disc drive to be used with the command. If a negative drive number is used (&80 to &FF) then Osword &7F uses the currently selected drive as specified by the most recent *DRIVE command. Bytes &01 to &04 of the parameter block store the address of a buffer area into which any data to be read will be placed, or from which any data to be written will be taken. A buffer is not needed by all the commands but bytes &01 to &04 of the parameter block are always assigned to a buffer address even if a buffer is not used. Column 2 of figure 1 shows that the Osword &7F commands use from 0 to 5 parameters. The number of parameters used by a call is stored in byte &05 of its parameter block. Byte &06 of the parameter block stores the command number (column 1, figure 1), and byte &07 onwards the parameters required by the command. The last byte of the parameter block is a result byte. Unless it is used to read the 8271 registers, Osword &7F will normally return the number zero in the result byte when a command has been executed sucessfully but it returns the number &20 (ie. bit 5 set) if deleted data have been sucessfully written to, read from, or verified on a disc. The result is returned in byte &07 of the parameter block for Osword &7F command number &6C, which uses no parameters. It is returned in byte &08 of the parameter block for command numbers &69 and &7D, which use one parameter, and so on up to byte &0C of the parameter block for command number &63 which uses 5 parameters. Errors are reported in bits 1 to 4 of the result byte. The error codes can be isolated by ANDing the result byte with #&1E and the error codes can be interpreted as shown in figure 2. +--------+----------------------------+ | Result | Interpretation | +--------+----------------------------+ | &02 | Scan met equal ** | | &04 | Scan met not equal ** | | &08 | Clock error | | &0A | Late DMA ** | | &0C | Sector ID CRC error | | &0E | Data CRC error | | &10 | Drive not ready | | &12 | Disc write protected | | &14 | Physical track 0 not found | | &16 | Write fault | | &18 | Sector not found | +--------+----------------------------+ | Errors marked ** should not occur | +-------------------------------------+ Figure 2. The error codes returned in the result byte ----------------------------------------------------- When designing software which uses Osword &7F you can test the result byte for specific errors after ANDing the result with #&1E to isolate the error bits. ANDing with #&1E excludes the deleted data bit which is not really an error at all. Testing for specific errors is not essential because all the above errors are fatal and any error should be used to halt your program. Testing for specific errors can always give some useful extra information when a routine fails to work as expected. In the rest of this module and whole of the next module I will discuss the commands executed by Osword &7F and give short examples of the some of them. Later in the series these commands will be used to show you how to create disc utility programs. The order in which the commands will be discussed is not the order in which they are listed in figure 1. I will cover the Osword &7F command numbers &69 to &7D in this module. With the exception of the Seek command, these commands are not fuly implemented with the 1770 disc interface but they are effectivly incorporated in other commands such as Write Data, Read Data, and Verify. The Osword &7F command numbers &4A to &63 will be covered in the next module. ---------------------------- Osword &7F Read Drive Status ---------------------------- The Osword &7F Read Drive Status command copies the 8271 drive control input register to the parameter block result byte. To use this command set up the following parameter block. Parameter block &00 = drive number (&00-&03 or &FF) Parameter block &01 - &04 = buffer address (not used) Parameter block &05 = &00 (no command parameters) Parameter block &06 = &6C (read drive status command) Parameter block &07 = result byte Each bit of the result byte has the following meaning: bit 7 = unused bit 6 = READY1 bit 5 = FAULT bit 4 = INDEX bit 3 = WR PROTECT bit 2 = READY0 bit 1 = TRK0 bit 0 = COUNT/OP1 These results are only really useful for tracking down hardware errors. You can use the demonstration program STATUS with both write protected and write enabled discs to see the effect that write protection has on the bits of the result register. You could use this command to test for a write protection tab on a disc but the only unique use for the Osword &7F Read Drive Status command is to clear a "not ready" signal. It is used by the DFS for this purpose. This is not one of the most useful commands and you will probably not need to use it in any of your programs. 10 REM: STATUS 20 oswrch=&FFEE 30 osword=&FFF1 40 DIM mcode &100 50 FOR pass = 0 TO 2 STEP 2 60 P%=mcode 70 [ OPT pass 80 LDA #&7F 90 LDX #block MOD 256 100 LDY #block DIV 256 110 JSR osword 120 RTS 130 .block 140 EQUB &FF \ current drive 150 EQUD &00 \ does not matter 160 EQUB &00 \ 0 parameters 170 EQUB &6C \ read status command 180 .result 190 EQUB &00 \ result byte 200 .binary 210 LDX #8 220 .loop 230 LDA #ASC("0") 240 ASL result 250 ADC #&00 260 JSR oswrch 270 DEX 280 BNE loop 290 RTS 300 ] 310 NEXT 320 CALL mcode 330 PRINT"Result = &";~?result;", %"; 340 CALL binary 350 PRINT -------------------------- Osword &7F Initialise 8271 -------------------------- Osword &7F Initialise 8271 is not fully implemented with the 1770 disc interface. For this reason you should avoid using it and use the equivalent Osbyte &FF which is available from the operating system of all BBC microcomputers. The hardware default setting is equivalent to *FX 255,0,255 and this gives access to the slowest disc drives. The Osbyte calls in figure 3 can be used to give access to faster drives but, if you are writing software to be used on unknown drives, it may be a good idea to select the slowest time. Osbyte &FF passes the values of the drive step time, settlement time, and head load time to the disc controller on soft break and they will remain in force until a hard break. +-----------+-------------+-----------+---------------+ | Step time | Settle time | Load time | Osbyte &FF | +-----------+-------------+-----------+---------------+ | 4 | 16 | 0 | *FX 255,0,207 | | 6 | 16 | 0 | *FX 255,0,223 | | 6 | 50 | 32 | *FX 255,0,239 | | 24 | 20 | 64 | *FX 255,0,255 | +-----------+-------------+-----------+---------------+ Figure 3. Disc access timings ----------------------------- If you need to use the Osword &7F Initialise 8271 command then the following parameter block must be used. Parameter block &00 = drive number (&00-&03 or &FF) Parameter block &01 - &04 = buffer address (not used) Parameter block &05 = &04 (4 command parameters) Parameter block &06 = &75 (initialise 8271 command) Parameter block &07 = &0D (init 8271 marker) Parameter block &08 = drive step time (milliseconds / 2) Parameter block &09 = head settlement time (miliseconds / 2) Parameter block &0A = head unload/load time (two 4 bit numbers) Parameter block &0B = result byte The drive step time should be in the range from &01 to &FF, representing 2 to 510 milliseconds in 2 millisecond steps. A drive step time of zero indicates that the drive will provide its own step pulses. The head settlement time should be in the range &00 to &FF representing a delay of 0 to 512 miliseconds in 2 millisecond steps. The four most significant bits of the head unload/load time should be in the range %0000 to %1110 (0-14) representing the number of complete disc revolutions before the head is unloaded. %1111 specifies that the head should not be unloaded at all. The four least significant bits of the head unload/load time specify the time taken to load the head in 8 milisecond intervals. This is a number in the range %0000 to %1111 (0-15) representing head load times of 0 to 120 milliseconds. The following code could be used to initialise the 8271. LDA #&7F LDX #block MOD 256 LDY #block DIV 256 JSR &FFF1 RTS .block EQUB &FF \ current drive EQUD &00 \ buffer address (not used) EQUB &04 \ 4 parameters EQUB &75 \ init 8271 command EQUB 12 \ 24 milliseconds step time EQUB 10 \ 20 milliseconds settle time EQUB &C8 \ Unload = 12 revs, load =64 milliseconds EQUB &00 \ result byte --------------- Osword &7F Seek --------------- The Osword &7F Seek command uses the appropriate track register as a base from which to seek a specified physical track. Register number &12 is used for drive 0/2 and register number &1A is used for drive 1/3. This command does not load the head and does not check the sector IDs. If track 0 is specified the seek command steps the head outwards until it trips the track 0 switch. If the TRK0 signal is missing after 255 attempts to find it, the command reports error &14 in the result byte. Error &14 is physical track zero not found (see figure 2). Seek track 0 can be used to find a base from which to seek any other physical track. This can be useful if the track register contains an unknown or incorrect physical track number. The following parameter block is used to seek a physical track. Parameter block &00 = drive number (&00-&03 or &FF) Parameter block &01 - &04 = buffer address (not used) Parameter block &05 = &01 (1 command parameter) Parameter block &06 = &69 (seek command) Parameter block &07 = physical track number Parameter block &08 = result byte The Osword &7F Seek command is useful if, for example, you want to write data onto a copy-protected disc which uses different physical and logical track numbers. You would use it to seek the physical track number and then use the Osword &7F Write Special Register command to write the logical track number into the appropriate track register. After writing to the disc you should then either rewrite the physical track number into the appropriate track register or seek track 0. The following code could be used to seek track 0 on the current drive. LDA #&7F LDX #block MOD 256 LDY #block DIV 256 JSR &FFF1 RTS .block EQUB &FF \ current drive EQUD &00 \ buffer address (not used) EQUB &01 \ 1 parameter EQUB &69 \ seek command EQUB &00 \ track 0 EQUB &00 \ result byte -------------------------- Osword &7F Load Bad Tracks -------------------------- This command is used to tell the 8271 that there are one or two "bad tracks" on a disc. This command is not fully implemented in the 1770 disc interface and is not used by either DFS. Because it is not used by the DFS it can be used for copy-protecting discs when the DFS *BACKUP command will give the "disc fault" error. Copy-protection will be covered in detail in module 6. Two bad track registers are available for each disc surface and they are used to specify which tracks are to be totally ignored by the 8271. For example, if track 1 is bad the 8271 will use track 3 when track 2 is specified. As far as the disc controller is concerned physical track 3 is seen as physical track 2, physical track 4 is seen as physical track 3, and so on. When bad tracks are used their track number IDs should be set to &FF when the disc is formatted. Track 0 must not be set as a bad track. The command lasts until a hard reset. The following parameter block is used by Osword &7F Load Bad Tracks. Parameter block &00 = drive number (&00-&03 or &FF) Parameter block &01 - &04 = buffer address (not used) Parameter block &05 = &04 (4 command parameters) Parameter block &06 = &75 (load bad tracks command) Parameter block &07 = drive pair (&10 = drive 0/2, &18 = drive 1/3) Parameter block &08 = bad track number 1 Parameter block &09 = bad track number 2 Parameter block &0A = current physical track Parameter block &0B = result byte The following code could be used to load bad tracks 1 and 2. Osword &7F Seek command is used to position the head over a known physical track, in this case track zero. JSR seekzero \ seek track zero LDA #&7F LDX #block MOD 256 LDY #block DIV 256 JSR &FFF1 RTS .block EQUB &00 \ drive 0 EQUD &00 \ buffer address (not used) EQUB &04 \ 4 parameters EQUB &75 \ load bad tracks command EQUB &10 \ drive 0/2 EQUB &01 \ track 1 EQUB &02 \ track 2 EQUB &00 \ current track EQUB &00 \ result byte --------------------------------- Osword &7F Write Special Register --------------------------------- The internal registers of the 8271 can be overwritten using the Osword &7F Write Special Register command. The contents of all the registers in figure 4 can be altered but you are advised to limit yourself to altering the track registers, numbers &12 and &1A. The bad track registers have their own Osword &7F Load Bad Tracks command described above but they can also be altered with the Osword &7F Write Special Register command. If you decide to alter any other registers the results are likely to be disasterous - you have been warned! +--------------+---------------------------------+ | Register no. | Register | +--------------+---------------------------------+ | &06 | Scan sector register | | &10 | Bad track register 1, drive 0/2 | | &11 | Bad track register 2, drive 0/2 | | &12 | Track register, drive 0/2 | | &13 | Scan count register (LSB) | | &14 | Scan count register (MSB) | | &17 | DMA mode register | | &18 | Bad track register 1, drive 1/3 | | &19 | Bad track register 2, drive 1/3 | | &1A | Track register, drive 1/3 | | &22 | Drive control input register | | &23 | Drive control output register | +--------------+---------------------------------+ Figure 4. The 8271 registers ---------------------------- The following parameter block must be used with the Osword &7F Write Special Register command. Parameter block &00 = drive number (&00-&03 or &FF) Parameter block &01 - &04 = buffer address (not used) Parameter block &05 = &02 (2 command parameters) Parameter block &06 = &7A (write special register command) Parameter block &07 = register number (from figure 4) Parameter block &08 = value to put in register Parameter block &09 = result byte The track registers &12 (drive 0/2) and &1A (drive 1/3) are used by the disc controller to identify its current track position. These registers contain the number of the physical track. If the logical track number stored in the sector ID is not the same as the physical track number you should always use the write special register command to store the logical track number in the track register before attempting to read from or write to the disc. You must always reset the track register to its original value after reading or writing. As you have seen in the module 0, using different logical and physical track numbers is yet another technique for copy-protecting discs. The following code could be used to seek track 0 and then load the track register for drive 0/2 with the number 1. Track zero will then be seen as physical track 1. JSR seekzero \ seek track zero LDA #&7F LDX #block MOD 256 LDY #block DIV 256 JSR &FFF1 RTS .block EQUB &00 \ drive 0 EQUD &00 \ buffer address (not used) EQUB &02 \ 2 parameters EQUB &7A \ write special register command EQUB &12 \ track register drive 0/2 EQUB &01 \ track 1 EQUB &00 \ result byte -------------------------------- Osword &7F Read Special Register -------------------------------- The internal registers of the 8271 can be read using Osword &7F Read Special Register. The contents of all the registers in figure 4 can be read but not all the registers give useful results. The drive control input register can be read using Osword &7F Read Drive Status command but the drive control output register, and all the other registers, have to be read with the Osword &7F Read Special Register command. The following parameter block must be used with the Osword &7F Read Special Register command. Parameter block &00 = drive number (&00-&03 or &FF) Parameter block &01 - &04 = buffer address (not used) Parameter block &05 = &01 (1 command parameter) Parameter block &06 = &7D (read special register command) Parameter block &07 = register number (from figure 4) Parameter block &08 = result byte The program OUTPUT demonstrates how to read the contents of the drive control output register for the current drive. The result is printed in hexadecimal and binary. Each bit of the result has the following meaning: bit 7 = SELECT 1 bit 6 = SELECT 0 bit 5 = FAULT RESET?OP0 bit 4 = LOW CURRENT bit 3 = LOAD HEAD bit 2 = DIRECTION bit 1 = SEEK/STEP bit 0 = WR ENABLE It is interesting to run the program OUTPUT with drive 0 selected (using *DRIVE 0) and then select drive 1 (with *DRIVE 1) and run the program again to see the difference it makes to the result. If you want to read any other register change the value &23 in line 180 for another register number taken from figure 4. 10 REM: OUTPUT 20 oswrch=&FFEE 30 osword=&FFF1 40 DIM mcode &100 50 FOR pass = 0 TO 2 STEP 2 60 P%=mcode 70 [ OPT pass 80 LDA #&7F 90 LDX #block MOD 256 100 LDY #block DIV 256 110 JSR osword 120 RTS 130 .block 140 EQUB &FF \ current drive 150 EQUD &00 \ does not matter 160 EQUB &01 \ 1 parameter 170 EQUB &7D \ read special register command 180 EQUB &23 \ drive control output register 190 .result 200 EQUB &00 \ result byte 210 .binary 220 LDX #8 230 .loop 240 LDA #ASC("0") 250 ASL result 260 ADC #&00 270 JSR oswrch 280 DEX 290 BNE loop 300 RTS 310 ] 320 NEXT 330 CALL mcode 340 PRINT"Result = &";~?result;", %"; 350 CALL binary 360 PRINT