The Acorn DFS Osword commands - by - Gordon Horsington ---------------------------------------------------------- Module 0. Introduction ---------------------- +----------------------------------------------------------+ | 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. | +----------------------------------------------------------+ There are eight modules in this part of the Opcodes series. The modules are in files named T/DFS00 to T/DFS07. The following topics will be covered in these series modules. Module 0. This module. Introduction. Programs: IDSDUMP, VERIFY Module 1. The DFS Osword commands (Part 1). Programs: CYCLES, HOWMANY, STATUS, OUTPUT Module 2. The DFS Osword commands (Part 2). Programs: FORM10, NOTFORM, WRITE10, READ10 Module 3. Formatting single density discs. Program: OFFSET, IDSDUMP, VERIFY Module 4. Converting 40 track discs to run on 80 track disc drives. Program: CONVERT Module 5. Creating discs compatible with both 40 and 80 track drives. Program: DUALDFS, OFFSET Module 6. Creating copy-protected single density discs. Programs: SECTOR5, ENCODE, DECODE, IDSDUMP, VERIFY Module 7. Duplicating copy-protected single density discs. Programs: COPYDFS, COPYALL, DEFORM, IDSDUMP, VERIFY The later modules develop the ideas and techniques described in the earlier modules and for this reason the series needs to be worked through from beginning to end rather than used as a reference guide. ------------------------------------ Introduction to single density discs ------------------------------------ There are three levels at which data can be written to and read from single density discs. The highest level, with which all disc users should be familiar, is through using the DFS star commands, filenames and the range of BGET, BPUT and other filing system commands. This level has a large ammount of filing system independence so that, for example, the same *SAVE command syntax can be used with both tape and disc. When this high level access to the DFS is used, both the programmer and the program user are tied to the restrictions of the particular DFS ROM so that, for example, non-standard disc formats are not available. A lower level of access to the DFS ROM is provided by the DFS Osword commands. Oswords &7D to &7F are used by the DFS and Oswords &70 to &73 are used by the ADFS. The ADFS is only available on BBC computers which use the Western Digital 1770 (or 1772) disc controller. The BBC B DFS was designed to use the Intel 8271 disc controller and cannot support the ADFS without a 1770 upgrade. The 1770 disc controller is fitted as standard to the BBC B+ and Master series computers. The 1770 disc controller can support both single density and double density disc formats but the 8271 can only support the single density format. All disc based BBC computers are capable of using Oswords &7D to &7F but only those with the ADFS can use Oswords &70 to &73. Only Oswords &7D to &7F will be considered in detail in this series. The lowest level of access to discs can be achieved by programing the disc contoller directly. The hardware which makes up a BBC microcomputer system is memory mapped. This means that the usable registers of all the hardware devices available to the I/O processor are mapped onto the main memory address space used by the 6502 CPU. Page &FE, ie. memory from &FE00 to &FEFF, is known as Sheila and this page is reserved for the hardware on the I/O processor's circuit board. Sheila addresses &80 to &9F are available to the floppy disc controller. Five of the BBC B's 8271 registers are mapped onto the Sheila addresses from &FE80 to &FE82. Three of the five registers can only be written into and the other two can only be read from. For this reason only three Sheila addresses need to be used to access the five registers. These three addresses can be used to communicate with the 8271 and to instruct it to execute a wide range of functions. Sheila address &FE84 is used to pass data to, and to read data from, the disc controller. The mapping of the 8271 registers onto Sheila addresses is shown in figure 1. +-----------+---------+---------+ | 8271 | Sheila | read or | | register | address | write | +-----------+---------+---------+ | status | &FE80 | read | | result | &FE81 | read | | command | &FE80 | write | | parameter | &FE81 | write | | reset | &FE82 | write | +-----------+---------+---------+ Figure 1. The 8271 registers mapped onto Sheila addresses --------------------------------------------------------- The 8271 has twelve other registers, known as the Special Registers, which are not mapped onto the Sheila addresses. Access to these registers can be achieved indirectly using the 8271 Read Special Register command or the 8271 Write Special Register command, both of which can be sent to the disc controller using the registers in figure 1. The Sheila addresses can be peeked or poked using the indirection operator but to produce Tube- compatible code it is necessary to use Osbyte &96 to read the Sheila addresses and Osbyte &97 to write to them. Using Osbytes &96 and &97 will ensure that the code is Tube-compatible but it not the easiest or the best way to program the disc controller. Osword &7F executes a single 8271 command through all its phases and relieves the programmer of the problems associated with techniques such as non-maskable interupt handling which must be used when programming the 8271. Osword &7F uses the Sheila addresses from &FE80 to &FE84 but the programmer does not have to be concerned with, or even be aware of, the detailed use of these addresses. Osword &7F provides a standard interface on the disc controller and is the method of accessing the disc hardware used in this series. Before looking at the single density Oswords in detail it is necessary to understand the format used by single density discs. The BBC computer is capable of using 3 1/2 inch, 5 1/4 inch and 8 inch discs, although 8 inch discs are something of a rarity these days. Because 8 inch discs are so uncommon they will not be discussed in this series. Both 3 1/2 inch and 5 1/4 inch discs use the same format and are available in both single and double sided versions with either 40 or 80 tracks per side. Each track is subdivided into a number of sectors each of which has an identification field (usually called an ID field) and a data field. There are a number of gaps associated with each track. The gaps are a variable number of bytes between the ID and data fields and are used to space out the fields to prevent the sectors overwriting each other when the disc speed varies. Physical tracks and sectors are identified by their physical position on a disc. Physical track 0 is the outermost track and physical sector 0 is the first sector on a track after the index pulse hole. Every sector is given a one-byte logical track number and a one-byte logical sector number. The physical track numbers and logical track numbers are the same on discs formatted for the Acorn DFS but the physical and logical sector numbers do not have to be the same. One of the many ways of copy-protecting discs is to make the physical and logical track numbers different, this effectivly disables the DFS *BACKUP command. The number of sectors, the size of the data fields, and the gap sizes are determined when the disc is formatted. In module 3 you will have the opportunity to vary these parameters but, whatever the format, each single density track has the layout shown in figure 2 below. +------------+-----------+------+----------+ | Sync bytes | Data mark | data | data CRC | +------------+-----------+------+----------+ \ / Index \ Data / End of mark \ field / track +-----+----+-----+------+-----+----+-----+------+-----+ +-----+-----+ | Gap | ID | Gap | Data | Gap | ID | Gap | Data | Gap | ... | Gap | Gap | | 1 | | 2 | 0 | 3 | | 2 | 1 | 3 | | 4 | 5 | +-----+----+-----+------+-----+----+-----+------+-----+ +-----+-----+ / ID \ / field \ / \ +-------+------+---------+--------+---------+-----------+--------+ | Sync | ID | Logical | Head | Logical | Data size | Sector | | bytes | mark | track | number | sector | code | ID CRC | +-------+------+---------+--------+---------+-----------+--------+ Figure 2. The layout of a DFS track. ------------------------------------ Figure 2 illustrates a complete track with one of the data fields expanded above the centre line and one of the ID fields expanded below. If figure 2 is taken as an illustration of a 10 sector track, then sectors 2 - 9 have been left out. The index mark at the beginning of the track has its position determined by the physical index pulse hole in the disc. This mark is followed by Gap 1. Gap 1 occurs once per track between the index mark and the start of physical sector 0. It should always be 16 (&10) bytes long. Gap 1 is followed by the ID field for physical sector 0. The ID field starts with 6 sync bytes. These sync bytes synchronise the controller to the rotational speed of the disc. The sync bytes are followed by a sector ID mark, which simply marks the start of the sector. This in turn is followed by the logical track number. The logical track number is normally the same as the physical track number but, on non-standard discs, it does not have to be the same. If you use the demonstration progran IDSDUMP on a copy-protected disc you may find that the physical track numbers and the logical track numbers of the protected tracks are different. The logical track number is followed by the head number. This should be &00 for drives 0 and 1 (which use the under side of the disc), and &01 for drives 2 and 3 (which use the top side of the disc) but most formatting programs use a head number of &00 for all disc surfaces. A head number of &00 seems to be ignored by the disc controller. Next comes the logical sector number. The logical sector number does not have to be the same as the physical sector number and, as will be demonstrated in module 3, there can be a small advantage to be gained by not using the same logical and physical sector numbers. The data size code follows the logical sector number and it indicates the number of data bytes in the data field (see figure 3 below). This is followed by a two byte cyclic redundancy check (CRC). The CRC is used to check for errors in the data stored within the ID field. The ID field is followed by Gap 2. Gap 2 should always be 11 (&0B) bytes long and it is positioned between the ID field and the data field of each sector on the track. Each data field is followed by Gap3. The Gap 3 after the last data field is followed by Gap 4 which in turn is followed by Gap 5. The relationship between the sector sizes and the gap sizes for 3 1/2 inch and 5 1/4 inch discs is shown in figure 3. All the numbers in figure 3 are in decimal. +-------------+-----------+--------+------+------+------+------+------+ | No. Sectors | Size code | Length | Gap1 | Gap2 | Gap3 | Gap4 | Gap5 | +-------------+-----------+--------+------+------+------+------+------+ | 18 | 0 | 128 | 16 | 11 | 11 | 24 | 0 | | 10 | 1 | 256 | 16 | 11 | 21 | 30 | 0 | | 5 | 2 | 512 | 16 | 11 | 74 | 88 | 0 | | 2 | 3 | 1024 | 16 | 11 | 255 | 740 | 0 | | 1 | 4 | 2048 | 16 | 11 | 0 | 1028 | 0 | +-------------+-----------+--------+------+------+------+------+------+ Figure 3. The relationship between sector size code, length and gap size. ------------------------------------------------------------------------- The data field for each sector starts with 6 sync bytes which are used to synchronise the disc controller with the rotational speed of the disc. The sync bytes are followed by the data mark which identifies the start of the data and also indicates if the data are marked as "deleted". Deleted data are not physically deleted from the disc, they are simply marked as deleted. This type of data marking will be used in module 6 to help produce copy-protected discs. The data follow the data mark and are terminated with two data CRC bytes. The program IDSDUMP can be used to print the ID field for every sector on a single density disc. I will not explain how the program works because it uses techniques that will be covered in the next two modules. The program is commented so that you can come back to it after reading modules 1 and 2 when you should be able to understand how it works. 10 REM: IDSDUMP 20 zeropage=&70 30 osasci=&FFE3 40 osnewl=&FFE7 50 osword=&FFF1 60 osbyte=&FFF4 70 DIM buffer &50 80 DIM mcode &200 90 FOR pass=0 TO 2 STEP 2 100 P%=mcode 110 [ OPT pass 120 LDA #14 \ paged mode 130 JSR osasci 140 .mainloop 150 JSR escape \ check escape flag 160 JSR firstsector \ read sector id first sector 170 BNE notformatted \ if error, track not formatted 180 JSR tracknumber \ print track number 190 JSR sectorids \ read all sector ids 200 .notformatted 210 INC physical \ increment physical track number 220 LDA physical \ load physical track number 230 CMP last \ all done? 240 BNE mainloop \ if not copy next track 250 JSR osnewl 260 RTS \ return to BASIC 270 .escape 280 LDA &FF \ escape flag 290 BMI pressed \ bit 7 set if pressed 300 RTS 310 .pressed 320 LDA #&7E 330 JSR osbyte \ acknowledge Escape 340 BRK 350 BRK 360 EQUS "Escape" 370 BRK 380 .firstsector 390 LDA physical \ physical track number 400 STA idsblock+7 \ store physical track 410 LDA #1 \ one sector 420 STA idsblock+9 \ number of ids 430 LDA #&7F 440 LDX #idsblock MOD 256 450 LDY #idsblock DIV 256 460 JSR osword 470 LDA idsblock+10 \ result 480 AND #&1E \ = 0 if formatted 490 RTS 500 .sectorids 510 LDX buffer+3 \ load data size code 520 LDA sizes,X \ load number of sectors 530 STA idsblock+9 \ store number of sectors 540 ASL A \ *2 550 ASL A \ *4 560 STA sectornumber \ store index on sectors 570 LDA #&7F 580 LDX #idsblock MOD 256 590 LDY #idsblock DIV 256 600 JSR osword 610 LDA idsblock+10 \ result 620 AND #&1E 630 BNE idserror \ = 0 if OK 640 LDX #0 650 .next 660 LDY #0 670 .printloop 680 LDA buffer,X 690 JSR printbyte \ print every byte of sector table 700 INX 710 INY 720 CPY #4 \ 4 bytes per line 730 BNE printloop 740 JSR osnewl 750 CPX sectornumber \ last byte? 760 BCC next \ go back for more 770 RTS 780 .idserror 790 BRK 800 BRK 810 EQUS "Sector ID Error" 820 BRK 830 .tracknumber 840 JSR osnewl 850 LDX #title MOD 256 860 LDY #title DIV 256 870 JSR printtext \ print "Track &" 880 LDA physical \ load physical track number 890 JSR printbyte \ print track number 900 LDX #header MOD 256 910 LDY #header DIV 256 920 JMP printtext \ print "LT HN LS DS" 930 .printtext 940 STX zeropage 950 STY zeropage+1 960 LDY #0 970 .textloop 980 LDA (zeropage),Y 990 BEQ endtext 1000 JSR osasci 1010 INY 1020 BNE textloop 1030 .endtext 1040 RTS 1050 .printbyte 1060 PHA 1070 LSR A 1080 LSR A 1090 LSR A 1100 LSR A 1110 JSR nybble \ print MS nybble 1120 PLA 1130 JSR nybble \ print LS nybble 1140 LDA #ASC(" ") 1150 JSR osasci \ print space 1160 JMP osasci \ print space 1170 .nybble 1180 AND #&0F 1190 SED 1200 CLC 1210 ADC #&90 1220 ADC #&40 1230 CLD 1240 JMP osasci \ print nybble and return 1250 .idsblock 1260 EQUB &FF \ current drive 1270 EQUD buffer \ address of buffer 1280 EQUD &00005B03 \ read sector ids 1290 EQUW 0 1300 .sizes 1310 EQUB 18 1320 EQUB 10 1330 EQUB 5 1340 EQUB 2 1350 EQUB 1 1360 .title 1370 EQUS " Track &" 1380 BRK 1390 .header 1400 EQUB &0D 1410 EQUS " ----------" 1420 EQUB &0D 1430 EQUS "LT HN LS DS" 1440 EQUB &0D 1450 EQUS "--------------" 1460 EQUB &0D 1470 BRK 1480 .physical 1490 EQUB &00 1500 .sectornumber 1510 EQUB &00 1520 .last 1530 EQUB &00 1540 ] 1550 NEXT 1560 INPUT'"Number of tracks (40/80) "tracks$ 1570 IF tracks$="40" ?last = 40 ELSE ?last = 80 1580 PRINT'"Insert ";?last;" track disc into current drive" 1590 PRINT"and press Spacebar to print sector IDs" 1600 REPEAT 1610 UNTIL GET=32 1620 PRINT'"Press Shift to scroll" 1630 CALL mcode Chain the program IDSDUMP and, when prompted, put a suitable disc in the current drive. Then press the spacebar to print the sector IDs for every track on the disc. The track number is displayed and the logical track (LT), head number (HN), logical sector (LS) and data size code (DS) are printed for every physical sector on each track, starting with physical sector 0. It is quite interesting to use this program with a copy-protected disc. You will almost certainly find that some of the physical and logical track numbers are different and you may also find some unexpected logical sector numbers. Figure 4 is a part of the output I produced with the 40 track single density disc version of the game "Grand Prix Construction Set". Track &0A ---------- LT HN LS DS -------------- 14 00 00 01 14 00 01 01 14 00 02 01 14 00 03 01 14 00 04 01 14 00 05 01 14 00 06 01 14 00 07 01 14 00 08 01 14 00 09 01 Figure 4. Part of the output from the program IDSDUMP ----------------------------------------------------- You should notice that physical track &0A uses logical track number &14 but the physical sector numbers, indicated by the order of the sectors, are the same as the logical sector numbers. This use of different physical and logical track numbers is sufficient to prevent the *BACKUP command duplicating the disc. As if to make sure it can't be copied, the disc also uses deleted data to re-inforce the same effect. Deleted data markers cannot be displayed with the program IDSDUMP and so I have provided a program called VERIFY which will verify copy-protected discs and indicate which tracks use deleted data. The deleted data mark is a part of the data field and can be read using the verify command. This will also be explained in detail in a later module but, for now, you should find it interesting to use the program VERIFY to find the deleted data on a copy-protected disc. 10 REM: VERIFY 20 REM: for copy-protected discs 30 osnewl=&FFE7 40 oswrch=&FFEE 50 osword=&FFF1 60 osbyte=&FFF4 70 DIM buffer &50 80 DIM mcode &500 90 FOR pass=0 TO 2 STEP 2 100 P%=mcode 110 [ OPT pass 120 JSR osnewl 130 .mainloop 140 JSR escape \ check escape flag 150 JSR seek \ seek physical tracks 0 - 40 160 JSR firstsector \ read sector id first sector 170 BNE notverify \ if error track not formatted 180 JSR sectorids \ read all sector ids 190 JSR verify \ verify all sectors 200 .notverify 210 JSR printbyte \ print track number 220 INC physical \ increment physical track number 230 LDA physical \ load physical track number 240 CMP last \ all done? 250 BNE mainloop \ if not copy next track 260 JSR osnewl 270 RTS \ return to BASIC 280 .escape 290 LDA &FF \ escape flag 300 BMI pressed \ bit 7 set if pressed 310 RTS 320 .pressed 330 LDA #&7E 340 JSR osbyte \ acknowledge Escape 350 BRK 360 BRK 370 EQUS "Escape" 380 BRK 390 .seek 400 LDA physical \ physical track number 410 STA seekblock+7 420 LDA #&7F 430 LDX #seekblock MOD 256 440 LDY #seekblock DIV 256 450 JSR osword 460 LDA seekblock+8 \ result 470 BNE seekerror \ = 0 if OK 480 RTS 490 .seekerror 500 BRK 510 BRK 520 EQUS "Seek error" 530 BRK 540 .firstsector 550 LDA physical \ physical track number 560 STA idsblock+7 \ store physical track 570 LDA #1 \ one sector 580 STA idsblock+9 \ number of ids 590 LDA #&7F 600 LDX #idsblock MOD 256 610 LDY #idsblock DIV 256 620 JSR osword 630 LDA idsblock+10 \ = 0 if formatted 640 RTS 650 .sectorids 660 LDX buffer+3 \ load data size code 670 LDA sizes,X \ load number of sectors 680 STA idsblock+9 \ store number of sectors 690 ASL A \ *2 700 ASL A \ *4 710 SEC 720 SBC #4 \ sectors*4-4 730 STA sectornumber \ store index on sectors 740 TXA \ transfer data size code 750 ASL A \ *2 760 ASL A \ *4 770 ASL A \ *8 780 ASL A \ *16 790 ASL A \ *32 800 ORA idsblock+9 \ add number of sectors 810 STA verblock+9 \ store for verify 820 LDA #&7F 830 LDX #idsblock MOD 256 840 LDY #idsblock DIV 256 850 JSR osword 860 LDA idsblock+10 \ result 870 BNE idserror \ = 0 if OK 880 RTS 890 .idserror 900 BRK 910 BRK 920 EQUS "Sector ID Error" 930 BRK 940 .verify 950 LDX sectornumber \ load index on table 960 LDA buffer+2,X \ load logical sector number 970 STA verblock+8 \ store for verify 980 .lowest 990 DEX 1000 DEX 1010 DEX 1020 DEX 1030 BMI finished 1040 LDA buffer+2,X \ load logical sector number 1050 CMP verblock+8 \ is it lower than the last one? 1060 BCS lowest \ branch if not lowest sector 1070 STA verblock+8 \ store if it is lower 1080 BCC lowest \ look for lower sector number 1090 .finished 1100 LDA buffer \ load logical track number 1110 STA verblock+7 \ and store for verify 1120 JSR register \ write track register 1130 LDA #&7F 1140 LDX #verblock MOD 256 1150 LDY #verblock DIV 256 1160 JSR osword 1170 LDA physical \ physical track number 1180 JSR register \ write track register 1190 LDA verblock+10 1200 AND #&1E \ isolate error bits 1210 BNE vererror 1220 RTS 1230 .vererror 1240 BRK 1250 BRK 1260 EQUS "Verify error" 1270 BRK 1280 .register 1290 STA regblock+8 \ value to put in register 1300 LDA #&7F 1310 LDX #regblock MOD 256 1320 LDY #regblock DIV 256 1330 JSR osword 1340 LDA regblock+9 1350 BNE regerror 1360 RTS 1370 .regerror 1380 BRK 1390 BRK 1400 EQUS "Special register error" 1410 BRK 1420 .printbyte 1430 LDA physical \ print physical track number 1440 PHA 1450 LSR A 1460 LSR A 1470 LSR A 1480 LSR A 1490 JSR nybble \ print MS nybble 1500 PLA 1510 JSR nybble \ print LS nybble 1520 LDA #ASC(" ") 1530 LDX verblock+10 \ load deleted data flag 1540 BEQ space \ if =0 not deleted 1550 LDA #ASC("d") \ deleted data mark 1560 .space 1570 JSR oswrch \ print space or "d" 1580 LDA #ASC(" ") 1590 JMP oswrch \ print space 1600 .nybble 1610 AND #&0F 1620 SED 1630 CLC 1640 ADC #&90 1650 ADC #&40 1660 CLD 1670 JMP oswrch \ print nybble and return 1680 .seekblock 1690 EQUB &FF \ current drive 1700 EQUD &00 \ does not matter 1710 EQUD &00006901 \ seek, 1 parameter 1720 .idsblock 1730 EQUB &FF \ current drive 1740 EQUD buffer \ address of buffer 1750 EQUD &00005B03 \ read sector ids 1760 EQUW &00 1770 .verblock 1780 EQUB &FF \ current drive 1790 EQUD &00 \ does not matter 1800 EQUD &00005F03 \ verify multi sector 1810 EQUW &00 1820 .regblock 1830 EQUB &FF \ current drive 1840 EQUD &00 \ does not matter 1850 EQUD &00127A02 1860 EQUB &00 \ result 1870 .sizes 1880 EQUB 18 1890 EQUB 10 1900 EQUB 5 1910 EQUB 2 1920 EQUB 1 1930 .physical 1940 EQUB &00 1950 .sectornumber 1960 EQUB &00 1970 .last 1980 EQUB &00 1990 ] 2000 NEXT 2010 INPUT'"Number of tracks (40/80) "tracks$ 2020 IF tracks$="40" ?last = 40 ELSE ?last = 80 2030 PRINT"Insert ";?last;" track disc into current drive" 2040 PRINT"and press the Spacebar to verify" 2050 REPEAT 2060 UNTIL GET=32 2070 CALL mcode The program is commented to explain how it works and you might like to come back to it after reading module 2. When the program VERIFY was used on the "Grand Prix Construction Set" disc the output shown in figure 5 was produced. >LO."VERIFY" >RUN Number of tracks (40/80) 40 Insert 40 track disc into current drive and press the Spacebar to verify 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0Dd 0Ed 0Fd 10d 11d 12 13 14 15 16 17 18 19d 1Ad 1Bd 1Cd 1Dd 1Ed 1Fd 20d 21d 22d 23 24 25 26 27 > Figure 5. The output from the program VERIFY -------------------------------------------- The lower case letter d following tracks &0D to &11 and tracks &19 to &22 indicates that these tracks have deleted data stored on them. The game on this disc loads in two parts and I would not be at all surprised to find that the two parts are stored on the tracks with deleted data. If the program VERIFY is used with a standard DFS disc it will not produce a lower case d to indicate the use of the deleted data marker.