fdrawcmd.sys — IOCTL Codes

Introduction

Requests are sent to the driver using the Win32 DeviceIoControl function. The following ioctl codes categories are supported:

Driver Version

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.

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 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.

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_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_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.

IOCTL Parameters

The details below show the required input and expected output buffers for DeviceIoControl.

IOCTL_FDRAWCMD_GET_VERSION

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.

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)
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.


IOCTL_FDCMD_FORMAT_TRACK

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.

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 (part ID)

IOCTL_FDCMD_PERPENDICULAR_MODE

Description Set 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.
Notes

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_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)
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.

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)
Notes 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
Notes 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.
Notes 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.
Notes See IOCTL_FDCMD_READ_DATA for typical input parameter values.

IOCTL_FD_CHECK_DISK

IOCTL_FDCMD_WRITE_DELETED_DATA

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.

IOCTL_FD_GET_FDC_INFO

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.

IOCTL_FD_GET_REMAIN_COUNT

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.

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
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.

IOCTL_FD_LOCK_FDC

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.


IOCTL_FD_MOTOR_OFF

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!


IOCTL_FD_MOTOR_ON

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.


IOCTL_FD_RAW_READ_TRACK

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!


IOCTL_FD_RESET

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.

IOCTL_FD_SET_DATA_RATE

Description Set drive data rate.
Input BYTE (see notes)
Output None
Notes The input value should be one of:
  • 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)
  • 3 for 1Mbps (extra-density, for 2.88M drives)

IOCTL_FD_SET_DISK_CHECK

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.


IOCTL_FD_SET_MOTOR_TIMEOUT

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.

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)
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.


IOCTL_FD_SET_SECTOR_OFFSET

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.

IOCTL_FD_SET_HEAD_SETTLE_TIME

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.

IOCTL_FD_SET_SHORT_WRITE

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.


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)
Notes 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

Description Release exclusive access to the controller.
Input None
Output None
Notes 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