IOCTL Codes
Driver Version
The following code can be sent to either the driver's \\.\fdrawcmd device or the regular \\.\fdrawX devices:
| IOCTL_FDRAWCMD_GET_VERSION | get fdrawcmd.sys driver version |
Controller Commands
The following map directly to floppy controller commands. In many cases the input buffer matches the data expected by the controller for each command, and the output buffer supplies an appropriate result.
| IOCTL_FDCMD_CONFIGURE | configure controller parameters |
| IOCTL_FDCMD_DUMPREG | dump controller registers |
| IOCTL_FDCMD_FORMAT_TRACK | format track |
| IOCTL_FDCMD_LOCK | lock settings |
| IOCTL_FDCMD_PART_ID | identify controller part id |
| IOCTL_FDCMD_PERPENDICULAR_MODE | set perpendicular mode |
| IOCTL_FDCMD_READ_DATA | read sectors |
| IOCTL_FDCMD_READ_DELETED_DATA | read deleted sectors |
| IOCTL_FDCMD_READ_ID | read next id header |
| IOCTL_FDCMD_READ_TRACK | raw track read |
| IOCTL_FDCMD_RECALIBRATE | recalibrate drive |
| IOCTL_FDCMD_RELATIVE_SEEK | relative seek |
| IOCTL_FDCMD_SEEK | absolute seek |
| IOCTL_FDCMD_SENSE_DRIVE_STATUS | returns status register 3 |
| IOCTL_FDCMD_SENSE_INT_STATUS | sense interrupt status |
| IOCTL_FDCMD_SPECIFY | set drive specification |
| IOCTL_FDCMD_VERIFY | verify sectors |
| IOCTL_FDCMD_VERSION | check for enhanced controller |
| IOCTL_FDCMD_WRITE_DATA | write sectors |
| IOCTL_FDCMD_WRITE_DELETED_DATA | write deleted sectors |
| IOCTL_FDCMD_FORMAT_AND_WRITE | format and write in a single pass |
Support for some commands may be missing from older hardware. Please consult the controller documentation (PDF) for further details.
Controller Functions
The following control other aspects of the controller:
| IOCTL_FD_GET_RESULT | read result bytes for last command |
| IOCTL_FD_GET_FDC_INFO | get controller information |
| IOCTL_FD_GET_REMAIN_COUNT | get remaining data count |
| IOCTL_FD_LOCK_FDC | acquire exclusive controller access |
| IOCTL_FD_MOTOR_ON | spin up drive motor |
| IOCTL_FD_MOTOR_OFF | turn drive motor off |
| IOCTL_FD_RAW_READ_TRACK | 2-drive raw track read |
| IOCTL_FD_RESET | reset controller |
| IOCTL_FD_SET_MOTOR_TIMEOUT | set motor idle timeout |
| IOCTL_FD_SET_DATA_RATE | set data rate |
| IOCTL_FD_SET_DISK_CHECK | control disk presence check |
| IOCTL_FD_SET_HEAD_SETTLE_TIME | set head settle time |
| IOCTL_FD_SET_SECTOR_OFFSET | sync to sector offset |
| IOCTL_FD_SET_SHORT_WRITE | set short write length |
| IOCTL_FD_UNLOCK_FDC | release previously locked controller |
| IOCTL_FD_WAIT_INDEX | sync to index hole |
| IOCTL_FD_CHECK_DISK | check for disk in drive |
| IOCTL_FD_GET_TRACK_TIME | measure time for one disk rotation |
Special Functions
This section covers compound operations that are either timing-sensitive or not easily achieved using other functionality provided by the driver.
| IOCTL_FD_SCAN_TRACK | scan layout of current track |
| IOCTL_FD_TIMED_SCAN_TRACK | scan layout with times |
IOCTL_FDRAWCMD_GET_VERSION
Description: Get the fdrawcmd.sys driver version
Input: none
Output: DWORD
The 32-bit value returned holds the dotted version number, from MSB to LSB. i.e. 0x0100010b is version 1.0.1.11.
IOCTL_FDCMD_CONFIGURE
Description: Configure controller parameters
Input: FD_CONFIGURE_PARAMS
Output: FD_CMD_RESULT (optional)
IOCTL_FDCMD_DUMPREG
Description: Controller register dump
Input: none
Output: FD_DUMPREG_RESULT
IOCTL_FDCMD_FORMAT_AND_WRITE
Description: Format track and write sector data in a single pass
Input: FD_FORMAT_PARAMS (variable size)
Output: FD_CMD_RESULT (optional)
The size of the input buffer depends on the number of sectors in the track. The FD_FORMAT_PARAMS structure should be immediately followed by a list of FD_ID_HEADER structures, with each header followed by the data to be written to its data field.
This command requires an 82078 or later FDC, with unpredictable results if used on older controllers. Use IOCTL_FD_GET_FDC_INFO to determine whether a suitable controller version is present.
IOCTL_FDCMD_FORMAT_TRACK
Description: Format track
Input: FD_FORMAT_PARAMS (variable size)
Output: FD_CMD_RESULT (optional)
The size of the input buffer depends on the number of sectors in the track. The FD_FORMAT_PARAMS structure should be immediately followed by a block of FD_ID_HEADER structures.
IOCTL_FDCMD_LOCK
Description: Protect configure settings from reset
Input: FD_LOCK_PARAMS
Output: FD_LOCK_RESULT (optional)
IOCTL_FDCMD_PART_ID
Description: Check for an enhanced floppy controller
Input: none
Output: BYTE
IOCTL_FDCMD_PERPENDICULAR_MODE
Description: Set a drive to use perpendicular mode
Input: FD_PERPENDICULAR_PARAMS
Output: FD_CMD_RESULT (optional)
IOCTL_FDCMD_READ_DATA
Description: Read sectors
Input: FD_READ_WRITE_PARAMS
Output: Buffer for received data
For a typical read, set the input structure as follows:
- flags to FD_OPTION_MFM, to use normal MFM encoding.
- phead to the physical head to read from (0 or 1).
- cyl, head, sector and size to the values expected in the ID header of the first sector to read from the disk. The controller will fail to find the sector unless all these values match the sector on disk.
- eot to the sector number after the final sector to read. e.g. to read sectors 5 to 10, use an eot value of 11.
- gap to 10, so the controller ignores a small region around the write splice points. Consult a floppy controller specification for futher details.
- datalen to 255, unless you're using 128-byte sectors when you should use 128 instead.
Use IOCTL_FD_GET_RESULT to determine the failure point in multi-sector reads. The sector value will hold the sector number with the problem.
IOCTL_FDCMD_READ_DELETED_DATA
Description: Read deleted sectors
Input: FD_READ_WRITE_PARAMS
Output: Buffer for received data
IOCTL_FDCMD_READ_ID
Description: Read the next encountered ID header
Input: FD_READ_ID_PARAMS
Output: FD_CMD_RESULT
IOCTL_FDCMD_READ_TRACK
Description: Raw track read
Input: FD_READ_WRITE_PARAMS
Output: Buffer for received data
IOCTL_FDCMD_RECALIBRATE
Description: Recalibrate drive head position
Input: none
Output: FD_INTERRUPT_STATUS (optional)
IOCTL_FDCMD_RELATIVE_SEEK
Description: Seek relative to current head location
Input: FD_RELATIVE_SEEK_PARAMS
Output: FD_INTERRUPT_STATUS (optional)
IOCTL_FDCMD_SEEK
Description: Seek head to absolute location
Input: FD_SEEK_PARAMS
Output: FD_INTERRUPT_STATUS (optional)
For backwards compatibility, the input buffer may also be a single BYTE holding the cylinder number. When used, the head value is taken to be 0.
IOCTL_FDCMD_SENSE_DRIVE_STATUS
Description: Read status register 3
Input: FD_SENSE_PARAMS
Output: FD_DRIVE_STATUS
IOCTL_FDCMD_SENSE_INT_STATUS
Description: Sense interrupt status
Input: none
Output: FD_INTERRUPT_STATUS
IOCTL_FDCMD_SPECIFY
Description: Set drive specification
Input: FD_SPECIFY_PARAMS
Output: FD_CMD_RESULT (optional)
IOCTL_FDCMD_VERIFY
Description: Verify sectors
Input: FD_READ_WRITE_PARAMS
Output: FD_CMD_RESULT (optional)
Input parameters are the same as IOCTL_FDCMD_READ_DATA, except the eot value holds the number of sectors to verify.
IOCTL_FDCMD_VERSION
Description: Read controller version
Input: none
Output: BYTE
To read the driver version, use IOCTL_FDRAWCMD_GET_VERSION instead.
IOCTL_FDCMD_WRITE_DATA
Description: Write normal sectors
Input: FD_READ_WRITE_PARAMS
Output: Buffer containing data to write
See IOCTL_FDCMD_READ_DATA for typical input parameter values.
IOCTL_FDCMD_WRITE_DELETED_DATA
Description: Write deleted sectors
Input: FD_READ_WRITE_PARAMS
Output: Buffer containing data to write
IOCTL_FD_CHECK_DISK
Description: Checks whether a disk is present in the drive
Input: none
Output: none (only return status)
The request fails with ERROR_NO_MEDIA_IN_DRIVE if no disk is present, otherwise it completes successfully.
IOCTL_FD_GET_FDC_INFO
Description: Get controller information
Input: none
Output: FD_FDC_INFO
IOCTL_FD_GET_REMAIN_COUNT
Description: Get the remain count for the last command
Input: none
Output: DWORD
This function retrieves the remaining data count for the last command. In normal cases this will be zero, but may be non-zero if the command was terminated abnormally.
IOCTL_FD_GET_RESULT
Description: Get the result bytes from last command
Input: none
Output: FD_CMD_RESULT
IOCTL_FD_GET_TRACK_TIME
Description: Measure the time for one disk rotation
Input: none
Output: DWORD
The value returned is the time for a single disk rotation, in microseconds. The setup and measurement process for each request takes 4 disk rotations.
IOCTL_FD_LOCK_FDC
Description: Acquire exclusive controller access
Input: none
Output: none
This function allows exclusive access to the controller to be held even when the drive is idle. When locked, attempts to use another drive on the same controller will fail.
The drive lock can be acquired multiple times, but each must have a corresponding IOCTL_FD_UNLOCK_FDC.
IOCTL_FD_MOTOR_OFF
Description: Turn motor off
Input: none
Output: none
The main use of this function is to allow immediate access to another drive on the same controller, without waiting for the motor-off idle timeout to expire.
Like other requests, it is only processed when the previous request has completed. This means you can't use it to interrupt in-progress commands for various copy protection tricks!
IOCTL_FD_MOTOR_ON
Description: Spins up the drive motor
Input: none
Output: none
By default, the drive motor is automatically started before any commands that need it. IOCTL_FD_MOTOR_ON starts the motor and allows a 750ms settle-time before completing the request. If the motor is already running the function completes immediately.
The motor is stopped a short time after the drive becomes idle, and the controller is released for other programs. The default idle timeout is 1 second, but it can be extended using IOCTL_FD_SET_MOTOR_TIMEOUT. Also note that the 750ms spin-up delay does not count against the timeout value.
IOCTL_FD_RAW_READ_TRACK
Decription: Perform a raw track read using 2 drives
Input: FD_RAW_READ_PARAMS
Output: Buffer for received data
This function uses the 2-drive raw track reading technique discovered by Vincent Joguin, as implemented in his Disk2FDI utility. It requires a second drive to be present, containing a 1.44M formatted disk (can be write-protected). The main drive holds the double-density disk to raw-read from.
Before using this function, select the data rate to be used by the read using IOCTL_FD_SET_DATA_RATE. This can be zero for 500Kbps, which will return both clock and data bits from the source disk. A value of two for 250Kbps will return only data or clock bits, depending on how the controller syncs to the data stream on the main drive.
In the function parameters, the flags value should be FD_OPTION_MFM for the PC-formatted disk, and head is the physical head to raw-read from. The size parameter determines how much data to read from the main disk. As the starting point for the read is semi-random, you'll need to read 16-32K to give a long enough bitstream to recover the required information. It may also be necessary to retry if a long enough stream can't be found. Some controllers may not support 32K reading - use IOCTL_FD_GET_REMAIN_COUNT to ensure that the remain count is zero after the read, indicating the controller completed your full request.
500Kbps reading of standard-format Amiga disks is quite reliable, as the original disks are written in a single pass. Most other disks (including PC disks) are formatted in one pass, but have data written to sectors afterwards. This separate writing creates seams in the bitstream where the new writing started and stopped in the bitstream. The noise around these splice points will disturb the controller sync during a raw reads, causing it to return patches of zero bytes instead of the real data. For this reason 500Kbps reading is generally not possible.
250Kbps reading is supported by more controllers, and the only way to read disks containing the splice points described above. This reading is still not a straightforward task, particularly since you only have data bits to work with. Assumptions must be made about patterns in the data, such as the A1A1A1 sequence used by address marks, and it's also necessary to check data CRCs to ensure the stream was complete enough. On top of this you'll also need to reassemble the order of sectors on the track, which is a topic beyond what I can describe here!
IOCTL_FD_RESET
Description: Reset controller
Input: none
Output: none
After the reset, the controller is reinitialised using the previously set data rate and configure/specify parameters. As the controller track register will have also been reset, a recalibrate will also be performed before the next command.
IOCTL_FD_SET_DATA_RATE
Description: Set drive data rate
Input: BYTE
Output: none
The input value should be 0 for 500Kbps (high-density 1.44M), 1 for 300Kbps (double-density for 5.25" drives), 2 for 250Kbps (double-density 720K for 3" and 3.5" drives), or 3 for 1Mbps (extra-density, for 2.88M drives).
IOCTL_FD_SET_DISK_CHECK
Description:Control disk presence checking
Input: BYTE
Output: BYTE (optional)
By default, the driver uses the disk change line to ensure a disk is present before commands that need one. This works well with modern 3.5" drives, but causes problems with older drives (including 3" drives) that lack the change line.
A zero input value disables the disk check, and a non-zero value enables it. You may optionally provide an output buffer to receive the previous value.
IOCTL_FD_SET_MOTOR_TIMEOUT
Description: Set motor-off timeout for idle controller
Input: BYTE
Output: none
The input value should be 1, 2 or 3, to set the timeout for 1-3 seconds.
IOCTL_FD_SCAN_TRACK
Description: Returns a list of sector headers for a given track
Input: FD_SCAN_PARAMS
Output: FD_SCAN_RESULT (variable size)
On success, the count member of FD_SCAN_RESULT contains the number of headers found. A count of zero indicates either an unformatted track or that there were no sectors found matching the current data rate / encoding. The headers are returned in the order they are encountered, beginning from the index hole.
The output buffer must be large enough to hold the scanned number of sector headers, which may not be known in advance. Space for 32 headers will be enough in most situations, but more is recommended when dealing with unknown disks.
IOCTL_FD_SET_SECTOR_OFFSET
Description: Synchronise to a specific sector
Input: FD_SECTOR_OFFSET_PARAMS
Output: none
When dealing with duplicate sector IDs, read/writes would normally apply to the next one the controller encountered. This function allows a specific sector offset to be given, to ensure the correct one is used. Use 0 to synchronise to the first sector, 1 to the second, etc.
IOCTL_FD_SET_HEAD_SETTLE_TIME
Description: Set the head settle time after seeks
Input: BYTE
Output: none
Sets a new head settle time, in milliseconds.
IOCTL_FD_SET_SHORT_WRITE
Description: Set a write threshold before interrupting the controller
Input: FD_SHORT_WRITE_PARAMS
Output: none
To support writing a number of copy-protection schemes, it's necessary to interrupt the controller part way through a write command. This function sets the number of bytes to write before interrupting the controller, and applies to all write and format commands.
To interrupt sector writes, simply set the number of bytes to stop after. If the count is exactly the sector size, the write will terminate before the CRC is written to disk, generating a sector with specific data but a CRC error. Using a smaller count allows around 6K of known data to be written to 8K sectors.
To interrupt formats you need to allow 4 bytes for each sector to write. Remember that stopping immediately after the final sector will be just before the ID header CRC is written, and also before the data address mark and the data field itself.
In both cases, the controller requires a small amount of time between accepting the final data and it being written to disk. The finetune parameter in the structure allows an additional delay (in microseconds) for this data to be processed. My own experiments showed that 86us was required in 250Kbps MFM mode, with an additional 32us for each byte to allow beyond that.
IOCTL_FD_TIMED_SCAN_TRACK
Description: Returns a list of sector headers with times
Input: FD_SCAN_PARAMS
Output: FD_TIMED_SCAN_RESULT (variable size)
Details are as for IOCTL_FD_SCAN_TRACK, except that the results include a time for each sector, relative to the index hole.
IOCTL_FD_UNLOCK_FDC
Decription: Release exclusive access to the controller
Input: none
Output: none
Release the controller lock set with IOCTL_FD_LOCK_FDC
IOCTL_FD_WAIT_INDEX
Description: Synchronise with the index hole on the disk
Input: none
Output: none