BLib.DiskIO - Low-level disk access =================================== File: DiskIO - Update: 1.01 Author: J.G.Harston - Date: 27-Jul-2009 The DiskIO library contains functions for low-level disk access, reading and writing disk sectors directly. There are functions for DFS, ADFS and LVFS single density, double density and hard drive access, depending on the hardware the program is running on. Requirements and Dependancies ============================= The library requires a 16-byte global control block with X% holding the address of this block, and Y% holding X% DIV 256. This can easily be set up with DIM ctrl% 15 near the start of the program and X%=ctrl%:Y%=X%DIV256 at the begining of the major program code and at the start of any main program loop. Single/Double DFS disk access ============================= err%=FNdisk(addr%,cmd%,drv%,trk%,sec%,num%,den%) FNdisk() makes an 8271 disk access call to read or write data to a floppy disk with 256-byte sectors. If the DFS being called supports it then double density disks can be accessed. addr% is the address to memory to read or write data cmd% is the 8271 command to perform drv% is the drive to use numberd in DFS style 0/2 and 1/3 trk% is the track to start the data transfer sec% is the sector within the track to start the data transfer num% is the number of sectors to transfer, &01 to &FF den% is 1 for single density and 2 for double density if supported FNdisk() returns the 8271 result which will be zero for Ok and non-zero for an error. Reading and writing must be within one track, FNdisk() will not step past the end of a track to the next track as it has no knowledge of what sector numbering has been imposed on the disk. If accessing DFS-type disks sectors are numbered from 0 to 9 and consequently you cannot transfer more than (10 - sector) sectors in one go. Commands -------- 8271 commands are built from the following bitmap: b7 special command, normally 0 b6 should be set to 1 b5-3 command: 000 Scan 001 Write 010 Read 011 Verify 100 Format 101 Seek 110 Initialise 111 Special Registers b3 direction: 0 write to FDC 1 read from FDC b2 delete data: 0 ignore delete data 1 data and deleted data b1-0 transfer size: 00 multiple scan data 01 no transfer 10 128 bytes 11 multiple sectors 40 01 000 000 scan data 41 01 000 001 (scan data no transfer) 42 01 000 010 (scan data 128 bytes) 43 01 000 011 (scan data multiple sectors) 44 01 000 100 scan data and delete data multi-sector 45 01 000 101 (scan data no transfer) 46 01 000 110 (scan data 128 bytes data and deleted data) 47 01 000 111 (scan data multiple sectors data and deleted data) 48 01 001 000 (write data scan) 49 01 001 001 (write data no transfer) 4A 01 001 010 write data 128 bytes 4B 01 001 011 write data multi-sector 4C 01 001 100 (write deleted data scan) 4D 01 001 101 (write deleted data no transfer) 4E 01 001 110 write deleted data 128 bytes 4F 01 001 111 write deleted data multi-sector 50 01 010 000 (read data scan) 51 01 010 001 (read data no transfer) 52 01 010 010 read data 128 bytes 53 01 010 011 read data multi-sector 54 01 010 100 (read data and deleted data scan) 55 01 010 101 (read data and deleted data no transfer) 56 01 010 110 read data and deleted data 128 bytes 57 01 010 111 read data and deleted data multi-sector 58 01 011 000 (verify data scan) 59 01 011 001 (verify data no transfer) 5A 01 011 010 (verify data 128 bytes 5B 01 011 011 read sector IDs (verify data multi-sector) 5C 01 011 100 (verify data and deleted data scan) 5D 01 011 101 (verify data and deleted data no transfer) 5E 01 011 110 verify data and deleted data 128 bytes 5F 01 011 111 verify data and deleted data multi-sector 60 01 100 000 (format data scan) 61 01 100 001 (format data no transfer) 62 01 100 010 (format data 128 bytes) 63 01 100 011 format track (format data multi-sector) 64 01 100 100 (format data and deleted data scan) 65 01 100 101 (format data and deleted data no transfer) 66 01 100 110 (format data and deleted data 128 bytes) 67 01 100 111 (format data and deleted data multi-sector) 68 01 101 000 (seek data scan) 69 01 101 001 seek track (seek data no transfer) 6A 01 101 010 (seek data 128 bytes) 6B 01 101 011 (seek data multi-sector) 6C 01 101 100 read drive status (seek data and deleted data scan) 6D 01 101 101 (seek data and deleted data no transfer) 6E 01 101 110 (seek data and deleted data 128 bytes) 6F 01 101 111 (seek data and deleted data multi-sector) 70 01 110 000 (initialise data scan) 71 01 110 001 (initialise data no transfer) 72 01 110 010 (initialise data 128 bytes) 73 01 110 011 (initialise data multi-sector) 74 01 110 100 (initialise data and deleted data scan) 75 01 110 101 initialise (init data and deleted data no transfer) 76 01 110 110 (initialise data and deleted data 128 bytes) 77 01 110 111 (initialise data and deleted data multi-sector) 78 01 111 000 (special data scan) 79 01 111 001 (special data no transfer) 7A 01 111 010 write special register (special data 128 bytes) 7B 01 111 011 (special data multi-byte) 7C 01 111 100 (special data and deleted data scan) 7D 01 111 101 read special register (special data and deleted data no transfer) 7E 01 111 110 (speical data and deleted data 128 bytes) 7F 01 111 111 (speical data and deleted data multi-sector) This results in the following commands: &40 scan data &41 &48 &49 &50 &51 &58 &59 &42 &43 &4A Write data 128 bytes &4B Write data &52 Read data 128 bytes &53 Read data &5A &5B Read IDs &44 scan data and delete data &45 &4C &4D &54 &55 &5C &5D &46 &47 &4E Write deleted data 128 bytes &4F Write deleted data &56 Read data and deleted data 128 bytes &57 Read data and deleted data &5E Verify data and deleted data 128 bytes &5F Verify data and deleted data &60 &61 &68 &69 Seek track &70 &71 &78 &79 &62 &63 format track &6A &6B &72 &73 &7A Write special register, trk%=register, sec%=value &64 (EDOS) Write track &65 (EDOS) Read track &6C Read drive status and reset 'not ready' &6D &74 &75 initialise &7C &7D Read special register, trk%=register &66 &67 &6E &6F &76 (EDOS) Force interrupt &77 &7E &7F &E0 (1770) Read track &F0 (1770) Write track &4A Write data 128 bytes &4B Write data &52 Read data 128 bytes &53 Read data &5B Read IDs &4E Write deleted data 128 bytes &4F Write deleted data &56 Read data and deleted data 128 bytes &57 Read data and deleted data &5E Verify data and deleted data 128 bytes &5F Verify data and deleted data &64 (EDOS) Write track &65 (EDOS) Read track &69 Seek track &6C Read drive status and reset 'not ready' &76 (EDOS) Force interrupt &7A Write special register, trk%=register, sec%=value &7D Read special register, trk%=register &E0 (1770) Read track &F0 (1770) Write track &4A Write data 128 bytes &52 Read data 128 bytes &4B Write data &53 Read data &4E Write deleted data 128 bytes &56 Read data and deleted data 128 bytes &4F Write deleted data &57 Read data and deleted data &5B Read IDs &5E Verify data and deleted data 128 bytes &5F Verify data and deleted data &64 (EDOS) Write track &76 (EDOS) Force interrupt &65 (EDOS) Read track &7A Write special register, trk%=register, sec%=value &69 Seek track &7D Read special register, trk%=register &6C Read drive status and reset 'not ready' &E0 (1770) Read track &F0 (1770) Write track The most useful commands are &4B to write data and &53 to read data. b4 b3 ==8271 Commands== The 8271 command passed in the control block is actually a bitmap made up as follows: DFS copies the drive and side bits from the drive number passed in XY?0, and is the origin of the DFS drive numbering scheme. Bit 3 is used to select the appropriate NMI and Tube code to transfer to or from the floppy disk controller. Obviously, some combinations result in commands that are not sensible or are ineffective. For instance, it makes no sense to combine "Read" with "no transfer". &4B - Results ------- [[Title:OSWORD &7F]] [[Category:OSWORD]] {{#customtitle:OSWORD &7F (127) - Perform floppy disk operation}} __TOC__ ==Specification== {| cellpadding="0" cellspacing="0" | || align="left" | '''On entry:''' || align="left" | '''On exit:''' |- | colspan="3" | '''Control block''' |- align="left" valign="top" | align="center" | XY?&00 || Drive || |- align="left" valign="top" | align="center" | XY!&01 || Data address || |- align="left" valign="top" | align="center" | XY?&05 || Number of parameters (n) || |- align="left" valign="top" | align="center" | XY?&06 || Command || |- align="left" valign="top" | align="center" | XY+7... || Parameters || |- align="left" valign="top" | align="center" | XY?(7+n) || || Result |} No contents of the control block should be relied upon on exit other than the result byte. Some systems update the control block, some systems do not. {| cellpadding="0" cellspacing="0" | '''Command'''  || '''Parameters''' || '''Meaning''' |- valign="top" ** Not possible on a BBC as needs DMA hardware. ==Parameters== ===Drive=== The drive number specifies the drive to access. If bit 7 is set, then the previously-used drive is used, and the drive status check is skipped. The drive number can be expressed as a binary number %pxDDDdsd. * b7 : p - If 1, selects the previously-used drive and density * b5-b3 : DDD - select the drive density: : %xx0=Acorn single density : %xx1=Acorn double density : %0xx=Watford current density : %10x=Watford single density : %11x=Watford double density * b2,b0 : dd - physical drive number * b1 : s - side Single density DFSs do not have any ability to select double density disk access. Acorn DFSs select the density with b3, Watford DFSs select the density with b4 and b5. To specify a density in a manner compatible with both Acorn and Watford DFSs specify the density with both methods: %001000sd - to specify single density %001110sd - to specify double density Most DFSs do not implement physical drives numbers larger than 1 (logical drives greater than 3). Selecting the previously selected drive can only be relied upon if DFS is the currently selected filing system. A drive number of &FF should be passed to select the previously used drive. DFS 2.xx in the Master MOS 3.20 ROM incorrectly passes the density bit in b3 to the drive control register b3 instead of b5, so OSWORD &7F cannot access double density disks without patching the DFS http://mdfs.net/ROMs/Filing/Disk. ===Data Address=== This is the address to transfer data to or from. ===Track=== This is the logical track to transfer data to or from. This may be different from the physical track number if the drive has previously been stepped to the correct physical track. ===Sector=== This is the logical sector number to start data transfer from. Sectors can be numbered anything from &00 to &FF, but single-density DFS-formatted disks use &00-&09 and double-density DFS and ADFS disks use &00-&0F. ===Size + Count=== Bits 0 to 4 specify the number of sectors to be read or written. Specifying zero sectors is undefined and may result in unpredictable behaviour. Bits 5 to 7 specify the sector size as 128*2^size, as follows: {| cellpadding="0" cellspacing="0" |  b7  ||  b6  ||  b5  ||  Sector size |- align="center" | 0 || 0 || 0 || align="left" | 128 bytes |- align="center" | 0 || 0 || 1 || align="left" | 256 bytes |- align="center" | 0 || 1 || 0 || align="left" | 512 bytes |- align="center" | 0 || 1 || 1 || align="left" | 1024 bytes |- align="center" | 1 || 0 || 0 || align="left" | 2048 bytes (8271 only) |- align="center" | 1 || 0 || 1 || align="left" | 4096 bytes (8271 only) |- align="center" | 1 || 1 || 0 || align="left" | 8192 bytes (8271 only) |- align="center" | 1 || 1 || 1 || align="left" | 16384 bytes (8271 only) |} ===Special Registers=== Command &7A and &7D write and read internal special registers. These registers are: * &00 40/80 flag (Opus) : reserved:dstep:0:0:0:0:0:0 0=single, 1=double * &03 Density (Opus) : reserved:densy:0:0:0:0:0:0 0=single, 1=double * &04 SROM (Opus) : &00-&0F * &06 Scan Sector (sector any error occured at) * &10 Bad Track Register 1 (drive 0/2) * &11 Bad Track Register 2 (drive 0/2) * &12 Track * &13 Scan Count LSB * &14 Scan Count MSB * &17 DMA Mode Register 1:1:0:0:0:0:: * &18 Bad Track 1 (drive 1/3) * &19 Bad Track 2 (drive 1/3) * &1A Track (drive 1/3) * &22 Drive Control Input * &23 Drive Control Output SEL1:SEL0:FAULT:LOW:LOAD:DIR:SEEK:WREN What Special Registers are supported depends on the specific hardware and DFS firmware. ==Result== The result is returned to the byte after the last parameter. The location is addressed by XY?(7+XY?5). For command &6C this location contains the drive status; for command &7D it is the requested special register; and for command &7A the result is undefined. Values returned by the other commands are as made up as follows: * b7-b6: always zero (1770 DFS returns 11 if command unrecognised) * b5: deleted data found * b4-b3: completion type: 00=no error, 01=recoverable error, 10=unrecoverable error, 11=failure * b2-b1: completion code * b0: always zero This gives the following result values: {| cellpadding="0" cellspacing="0" |  type  ||  code  ||  Result  ||  Meaning |- align="center" | 00 || 00 || &00 || align="left" | Successful completion |- align="center" | 00 || 01 || &02 || align="left" | Success, scan met equal ** |- align="center" | 00 || 10 || &04 || align="left" | Success, scan met not equal ** |- align="center" | 00 || 11 || &06 || align="left" | unused |- align="center" | 01 || 00 || &08 || align="left" | Clock error |- align="center" | 01 || 01 || &0A || align="left" | Late DMA ** / Late ISR |- align="center" | 01 || 10 || &0C || align="left" | ID CRC error |- align="center" | 01 || 11 || &0E || align="left" | Data CRC error |- align="center" | 10 || 00 || &10 || align="left" | Drive not ready |- align="center" | 10 || 01 || &12 || align="left" | Disk write protected |- align="center" | 10 || 10 || &14 || align="left" | Track 0 not found |- align="center" | 10 || 11 || &16 || align="left" | Write error |- align="center" | 11 || 00 || &18 || align="left" | Sector not found |- align="center" | 11 || 01 || &1A || align="left" | unused |- align="center" | 11 || 10 || &1C || align="left" | unused |- align="center" | 11 || 11 || &1E || align="left" | Drive not present/drive empty |- |} ** Not possible on a BBC as needs DMA hardware. The result byte will have &20 added to it if deleted data has been read. Intel documentation states that result type 01 should be retried up to a total of ten times before abandoning. In practice: * results types 01 and 10, other than &10 - Drive not ready - and &12 - Disk write protected, should be retried up to ten times * result &10 should be retried indefinitely until some other result occurs * result &18 - Sector not found - could be followed by a seek to track zero then one additional attempt. * While result &1E - Drive not present/empty - is not mentioned in official documentation, if left trying to access an empty drive this result will eventually occur. As such, emulators such as BeebEm will give Disk fault 1E if trying to access an empty drive. 1770 DFSs usually return Disk error 18 (Sector not found) instead. The result byte can be preloaded with a value to return if no OSWORD &7F routine exists. For instance, preloading the result with &00 makes all such failed calls appear to succeed, preloading with &18 makes all calls appear to return 'Sector not found'. HADFS preloads the result byte with &1E to give a default result of 'Drive not present'. Read Drive Status (&6C) resets any 'not ready' error and returns the drive status as the result byte: * b7 : unused * b6 : READY1 * b5 : FAULT * b4 : INDEX * b3 : WR PROTECT * b2 : READY0 * b1 : TRACK0 * b0 : COUNT ==Implementation== Some DFSs, such as DFS 2.45 supplied in the Master MOS 3.50 ROM, only respond to OSWORD &7F if DFS is the current filing system and fail silently if any other filing system is selected. ===Acorn DFS=== This is the original implementation of OSWORD &7F. It is a fairly simple interface to the Intel 8271 floppy disc controller; it passes the command and parameters to the controller, supplies the appropriate NMI service routine to fetch or store the disc data, and returns the result. ===Acorn 1770 DFS=== This emulates key commands of the 8271. Software double-stepping is available. Double density access can be made by setting bit 3 of the drive number. Unfortunately, the programmers didn't realise that the double density bit was moved in the BBC Master hardware, so double density access is not possible without a patched filing system http://mdfs.net/ROMs/Filing/Disk. ===Watford DDFS=== Density selectable with b4-b5 of the drive number. ===Opus EDOS=== This emulates key commands of the 8271, and operates on each drive in the density that was detected at the last *CAT (single or double). Software double-stepping is enabled if *OPT 8,255 is entered and *CAT detects a 40 track disc. The Format Track command formats 256-byte sectors only in single density, or double density if 16 or more sectors are specified. The density of operation cannot be directly specified to other commands. The sector size parameter is ignored in favour of the sectors' N records. ==Coding== The following routine can be used to perform an OSWORD &7F call that takes three parameters, such as Write (&4B) and Read (&53), or Track Read (&E0) and Track Write (&F0) which take ten parameters. It requires X%=>15-byte control block, Y%=X%DIV256. den%=1 for single density and, if supported by the hardware, den%=2 for double density. It returns a result of &1E (not present) if no OSWORD &7F call is available. DEFFNdisk(addr%,cmd%,drv%,trk%,sec%,num%,den%):LOCAL fs% fs%=FNfs:IF fs%<>4:*FX143,18,4 REPEAT X%?0=drv%+den%*24+8:X%!1=addr%:X%?5=3-7*(cmd%>127) X%?6=cmd%:X%?7=trk%:X%?8=sec%:X%!9=num%OR&1E20 A%=127:CALL&FFF1:A%=X%?(X%?5+7) UNTIL A%<>&10:IF fs%<>4:OSCLI"FX143,18,"+STR$ fs% =A% DEFFNfs:LOCAL A%,E%,Y%:=(USR&FFDA)AND&FF ==Tube note== The Tube MOS copies a 16-byte control block over the Tube, so on a read operation, any data read to immediately after the control block will be overwitten by the returned control block if it is within 16 bytes of the start of the control block. If code is to run properly, the destination address of any reads must be at least 16 bytes away from the start of the control block, for example: DIM X% 15, T% 255 Y% = X% DIV 256 X%!1 = T% etc... If blocks are declared with variables in the heap, the variable definitions count towards the 16-byte distance, so the following is ok: DIM ctrl% 12, data% 255 X%=ctrl%:Y%=X% DIV 256 X%!1 = data% etc... As the variable data% is assigned space between the two buffers, the value of data% - ctrl% exceeds 15. ==See Also== * http://mdfs.net/Docs/Comp/BBC/Osword/Osword7F * http://mdfs.net/Docs/Comp/BBC/Osword/DFSOsword * [[OSWORD &72]] * [[OSWORD &62]] ==References== FNlfvs() returns the SCSI result code after completing. This will be zero for Ok and non-zero for an error. addr% is the Comma ------------------------------------------- 8271 disk command. den%=1 for single density, if supported den%=2 for double density typical commands are &53=read, &4B=write Returns 8271-style result values, eg &18 for Sector Not Found Laser disk sector access ======================== err%=FNlvfs(addr%,cmd%,drv%,sect%,num%) FNlvfs() passes a LaserDisk SCSI command to the disk controller. It takes the same parameters as an ADFS SCSI command, with the obvious difference that you cannot write data to disks. Commands -------- &00 - test drive ready &01 - seek to track zero &08 - read data &0B - seek to track &1B - park heads; park if num%=0, unpark if num%=1 Results ------- &00 - ok &04 - drive not ready &10 - sector not found Examples -------- The following code snippet would copy the sector-level contents of a Laser Disk to an open file: sect%=0 REPEAT err%=FNlvfs(mem%,8,0,sect%,bufsize%/256) PROCgbpb(2,out%,mem%,bufsize%,0) UNTIL done Send LVFS and return result string ================================== result$=FNlvfs_fcode(command$) FNlvfs_fcode() sends an f-code command to the Laser Disk player and returns the result string. Send LVFS command ================ err%=FNlvfs_cmd(command$) FNlvfs_cmd() sends a LVFS command string to the Laser Disk player. Read LVFS result ================ result$=FNlvfs_rd FNlvfs_rd reads the result string from a previous FNlvfs_cmd() command. See also ======== See beebwiki.mdfs.net/OSWORD_62 for more detailed command and results. BLib.DiskIO v1.01 ================= Interim documentation Reads or writes block device (disks) ------------------------------------ FNdisk(addr%,cmd%,drv%,trk%,sec%,num%,den%) FNscsi(addr%,cmd%,drv%,sect%,num%) FNlvfs(addr%,cmd%,drv%,sect%,num%) addr% = data address cmd% = command drv% = drive trk% = track sec% = sector within track sect% = logical sector num% = number of sectors den% = density 1=single, 2=double Functions return 0 for Ok or a disk access result. FNscsi(addr%,cmd%,drv%,sect%,num%) ---------------------------------- 1770 and hard drive disk command typical commands are &08 for read, &0A for write Maximum number of sectors is &FFFF Returns scsi result values. FNlvfs(addr%,cmd%,drv%,sect%,num%) ---------------------------------- Laserdisk disk access command typical commands are &08 for read. Maximum number of sectors is &FFFF Returns scsi result values. History: v1.01 09-Sep-2011: FNdisk() updated to pass correct control block for &E0 and &F0 (read track, write track) REM > BLib.DiskIO 1.01 REM Disk I/O functions : DEFFNdisk(addr%,cmd%,drv%,trk%,sec%,num%,den%):LOCALfs% fs%=FNfs:IFfs%<>4:*FX143,18,4 REPEATX%?0=drv%+den%*24+8:X%!1=addr%:X%?5=3-7*(cmd%>127) X%?6=cmd%:X%?7=trk%:X%?8=sec%:X%?9=num%OR&20:A%=127:CALL&FFF1 A%=X%?(7+X%?5):UNTILA%<>&10:IFfs%<>4:OSCLI"FX143,18,"+STR$fs% =A% : DEFFNscsi(addr%,cmd%,drv%,sect%,num%):LOCALfs% fs%=FNfs:IFfs%<>8:*FADFS X%?0=0:X%!1=addr%:X%?5=cmd%:X%?6=drv%*32+((sect%AND&1F0000)DIV65536) X%?7=((sect%AND&FF00)DIV256):X%?8=sect%:X%!9=num%:X%!11=0 A%=&72:CALL&FFF1:A%=?X%:IFfs%<>8:OSCLI"FX143,18,"+STR$fs% =A% : DEFFNlvfs(addr%,cmd%,drv%,sect%,num%):LOCALfs% fs%=FNfs:IFfs%<>10:*FX143,18,10 X%?0=0:X%!1=addr%:X%?5=cmd%:X%?6=drv%*32+((sect%AND&1F0000)DIV65536) X%?7=((sect%AND&FF00)DIV256):X%?8=sect%:X%!9=num%:X%!11=0 A%=&62:CALL&FFF1:A%=?X%:IFfs%<>10:OSCLI"FX143,18,"+STR$fs% =A% : DEFFNfs:LOCALA%,E%,Y%:=(USR&FFDA)AND&FF : DEFFNscsi_err(A%,S%) IFA%:PRINT"Disk error ";FNh0(A%,2);" (";MID$("fddhdd",(A%AND&40)/&40+1,3);") at "+FNh0(S%,6) =A% : DEFFNdisk_err(A%,S%) IFA%:PRINT"Disk error ";FNh0(A%,2);" at "+FNh0(S%,4) =A% : scsi can do trange formats by doing sect=track*16+sector