Requests are sent to the driver using the Win32 DeviceIoControl function. The following ioctl codes categories are supported:
This request can be sent to either the driver’s \\.\fdrawcmd
device or the regular \\.\fdraw0
or \\.\fdraw1
devices:
IOCTL_FDRAWCMD_GET_VERSION | Read fdrawcmd.sys driver version. |
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 head. |
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.
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_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. |
This section covers compound operations that are either timing-sensitive or not easily achieved using other functionality provided by the driver.
IOCTL_FD_RAW_READ_TRACK | 2-drive raw track read. |
IOCTL_FD_SCAN_TRACK | Scan layout of current track. |
IOCTL_FD_TIMED_SCAN_TRACK | Scan layout with times. |
The details below show the required input and expected output buffers for DeviceIoControl
.
Description | Get the fdrawcmd.sys driver version. |
---|---|
Input | None |
Output | DWORD |
Notes | The 32-bit value returned holds the dotted version number, from MSB to LSB. i.e. 0x0100010B is version 1.0.1.11. |
Description | Configure controller parameters. |
---|---|
Input | FD_CONFIGURE_PARAMS |
Output | FD_CMD_RESULT (optional) |
Description | Controller register dump. |
---|---|
Input | None |
Output | FD_DUMPREG_RESULT |
Description | Format track and write sector data in a single pass. |
---|---|
Input | FD_FORMAT_PARAMS (variable size) |
Output | FD_CMD_RESULT (optional) |
Notes |
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. |
Description | Format track. |
---|---|
Input | FD_FORMAT_PARAMS (variable size) |
Output | FD_CMD_RESULT (optional) |
Notes | 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. |
Description | Protect configure settings from reset. |
---|---|
Input | FD_LOCK_PARAMS |
Output | FD_LOCK_RESULT (optional) |
Description | Check for an enhanced floppy controller. |
---|---|
Input | None |
Output | BYTE (part ID) |
Description | Set drive to use perpendicular mode. |
---|---|
Input | FD_PERPENDICULAR_PARAMS |
Output | FD_CMD_RESULT (optional) |
Description | Read sectors. |
---|---|
Input | FD_READ_WRITE_PARAMS |
Output | Buffer for received data. |
Notes |
For a typical read, set the input structure as follows:
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. |
Description | Read the next encountered ID header. |
---|---|
Input | FD_READ_ID_PARAMS |
Output | FD_CMD_RESULT |
Description | Raw track read. |
---|---|
Input | FD_READ_WRITE_PARAMS |
Output | Buffer for received data. |
Description | Recalibrate drive head position. |
---|---|
Input | None |
Output | FD_INTERRUPT_STATUS (optional) |
Description | Seek relative to current head location. |
---|---|
Input | FD_RELATIVE_SEEK_PARAMS |
Output | FD_INTERRUPT_STATUS (optional) |
Description | Seek head to absolute location. |
---|---|
Input | FD_SEEK_PARAMS |
Output | FD_INTERRUPT_STATUS (optional) |
Notes | 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. |
Description | Read status register 3. |
---|---|
Input | FD_SENSE_PARAMS |
Output | FD_DRIVE_STATUS |
Description | Sense interrupt status. |
---|---|
Input | None |
Output | FD_INTERRUPT_STATUS |
Description | Set drive specification. |
---|---|
Input | FD_SPECIFY_PARAMS |
Output | FD_CMD_RESULT (optional) |
Description | Verify sectors. |
---|---|
Input | FD_READ_WRITE_PARAMS |
Output | FD_CMD_RESULT (optional) |
Notes | Input parameters are the same as IOCTL_FDCMD_READ_DATA, except the eot value holds the number of sectors to verify. |
Description | Read controller version. |
---|---|
Input | None |
Output | BYTE |
Notes | To read the driver version, use IOCTL_FDRAWCMD_GET_VERSION instead. |
Description | Write normal sectors. |
---|---|
Input | FD_READ_WRITE_PARAMS |
Output | Buffer containing data to write. |
Notes | See IOCTL_FDCMD_READ_DATA for typical input parameter values. |
Description | Write deleted sectors. |
---|---|
Input | FD_READ_WRITE_PARAMS |
Output | Buffer containing data to write. |
Notes | See IOCTL_FDCMD_READ_DATA for typical input parameter values. |
Description | Checks whether a disk is present in the drive. |
---|---|
Input | None |
Output | None (only return status) |
Notes | The request fails with ERROR_NO_MEDIA_IN_DRIVE if no disk is present, otherwise it completes successfully. |
Description | Get controller information. |
---|---|
Input | None |
Output | FD_FDC_INFO |
Notes | The request fails with ERROR_NO_MEDIA_IN_DRIVE if no disk is present, otherwise it completes successfully. |
Description | Get the remain count for the last command. |
---|---|
Input | None |
Output | DWORD |
Notes | 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 early. |
Description | Get the result bytes from last command. |
---|---|
Input | None |
Output | FD_CMD_RESULT |
Description | Measure the time for one disk rotation. |
---|---|
Input | None |
Output | DWORD |
Notes | 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. |
Description | Acquire exclusive controller access. |
---|---|
Input | None |
Output | None |
Notes |
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. |
Description | Turn drive motor off. |
---|---|
Input | None |
Output | None |
Notes |
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! |
Description | Spins up the drive motor. |
---|---|
Input | None |
Output | None |
Notes |
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. |
Description | Perform a raw track read using 2 drives. |
---|---|
Input | FD_RAW_READ_PARAMS |
Output | Buffer for received data. |
Notes |
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! |
Description | Reset floppy disk controller. |
---|---|
Input | None |
Output | None |
Notes | 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. |
Description | Set drive data rate. |
---|---|
Input | BYTE (see notes) |
Output | None |
Notes | The input value should be one of:
|
Description | Control disk presence checking. |
---|---|
Input | BYTE (see notes) |
Output | BYTE (optional) |
Notes |
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. |
Description | Set motor-off timeout for idle controller. |
---|---|
Input | BYTE (see notes) |
Output | None |
Notes | The input value should be 1, 2 or 3, to set the timeout for 1-3 seconds. |
Description | Returns a list of sector headers for a given track. |
---|---|
Input | FD_SCAN_PARAMS |
Output | FD_SCAN_RESULT (variable size) |
Notes |
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. |
Description | Synchronise to a specific sector. |
---|---|
Input | FD_SECTOR_OFFSET_PARAMS |
Output | None |
Notes | 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. |
Description | Set the head settle time after seeks. |
---|---|
Input | BYTE (see notes) |
Output | None |
Notes | The input value is the new head settle time, in milliseconds. |
Description | Set a write threshold before interrupting the controller. |
---|---|
Input | FD_SHORT_WRITE_PARAMS |
Output | None |
Notes |
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. |
Description | Returns a list of sector headers with times. |
---|---|
Input | FD_SCAN_PARAMS |
Output | FD_TIMED_SCAN_RESULT (variable size) |
Notes | Details are as for IOCTL_FD_SCAN_TRACK, except that the results include a time for each sector, relative to the index hole. |
Description | Release exclusive access to the controller. |
---|---|
Input | None |
Output | None |
Notes | Release the controller lock set with IOCTL_FD_LOCK_FDC |
Description | Synchronise with the index hole on the disk. |
---|---|
Input | None |
Output | None |