Using fdrawcmd.sys
Introduction
This document details how to use fdrawcmd.sys from your own programs.
Using the driver requires just 4 regular Win32 functions:
- CreateFile to open a floppy device
- DeviceIoControl to send requests
- CloseHandle to close the device
- GetLastError to query error conditions
There are no other libraries or modules to link with your own code, just a single header file containing the IOCTL code definitions and structures.
Opening the device
To open the first floppy device, A:, use:
HANDLE h = CreateFile("\\\\.\\fdraw0", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
Replace \\.\fdraw0 with \\.\fdraw1 to open B: instead.
For asynchronous/non-blocking access to the device, you can supply an OVERLAPPED structure for the final parameter to CreateFile(). The same structure must then be passed when making requests.
Sending requests
All device requests are made using DeviceIoControl(), which accepts input and output buffers, returning a status and (if successful) a count of the bytes returned:
BOOL f = DeviceIoControl(h, IOCTL_FDCMD_SEEK, &sp, sizeof(sp), NULL, 0, &dwRet, NULL);
If the call succeeds (returning TRUE), the output buffer contains any result data, and the count of bytes written to the buffer is also supplied.
If the call fails, use GetLastError() to determine the reason for the failure. Controller error conditions are seen as failures, with most mapped to a suitable Win32 status (ERROR_CRC for CRC errors, etc). If you'd prefer to decode it yourself, the raw controller result bytes can still be read using IOCTL_FD_GET_RESULT.
See the IOCTL codes list for details of what to pass as input and output buffers. For cases not needing a particular buffer, pass NULL with a size of 0.
For efficiency, the read/write data buffers are passed directly to the driver, requiring them to be correctly aligned on system page boundaries. To ensure this condition is met, use VirtualAlloc to allocate the main data buffers.
Closing the device
Simply use CloseHandle to close the device when you're done:
CloseHandle(h);
Examples
To keep the example code simple, no error checking is performed. Don't forget to add it to your own code!
Reading the driver version
In addition to the \\.\fdrawX devices, there is also a \\.\fdrawcmd device to represent the driver as a whole. This allows the driver version to be queried without opening a specific floppy device:
DWORD dwRead, dwVersion = 0;
// open the driver device object
HANDLE h = CreateFile("\\\\.\\fdrawcmd", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);// query driver version
DeviceIoControl(h, IOCTL_FDRAWCMD_GET_VERSION, NULL, 0, &dwVersion, sizeof(dwVersion), &dwRead, NULL);CloseHandle(h);
// check driver compatibility
if (HIWORD(dwVersion) != HIWORD(FDRAWCMD_VERSION))
; // incompatible!
The CreateFile call will fail if fdrawcmd.sys is not installed, or if no floppy drives are present (meaning the driver is not loaded).
If the DeviceIoControl call is successful, the driver version is returned in the output buffer. The 32-bit value holds the dotted components of the version number, so 0x01000013 is 1.0.0.19.
You should always check the driver version before using it. If the upper 16-bits differ from the compile-time version number (FDRAWCMD_VERSION), the driver incompatible. You can either recommend a driver update (if it was older), or that they get a newer version of your program.
Reading a sector
The following code reads a single 512-byte sector from head 0 on cylinder 0:
DWORD dwRet;
FD_READ_WRITE_PARAMS rwp;
FD_SEEK_PARAMS sp;// details of sector to read
rwp.flags = FD_OPTION_MFM;
rwp.phead = 0;
rwp.cyl = 0;
rwp.head = 0;
rwp.sector = 1;
rwp.size = 2;
rwp.eot = 1+1;
rwp.gap = 0x0a;
rwp.datalen = 0xff;// details of seek location
sp.cyl = 0;
sp.head = 0;// data buffers must be correctly aligned
PVOID pv = VirtualAlloc(NULL, 512, MEM_COMMIT, PAGE_READWRITE);// open A:
HANDLE h = CreateFile("\\\\.\\fdraw0", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);// seek to cyl 0
DeviceIoControl(h, IOCTL_FDCMD_SEEK, &sp, sizeof(sp), NULL, 0, &dwRet, NULL);// read the boot sector
DeviceIoControl(h, IOCTL_FDCMD_READ_DATA, &rwp, sizeof(rwp), pv, 512, &dwRet, NULL);// cleanup
CloseHandle(h);
VirtualFree(pv, 0, MEM_RELEASE);
Determining track layout
The following code determines the MFM sectors present under head 0 on cylinder 0:
int i;
DWORD dwRet;
// Allow space for 32 headers
BYTE buf[1+4*32];
PFD_SCAN_RESULT psr = (PFD_SCAN_RESULT)buf;
// open A:
HANDLE h = CreateFile("\\\\.\\fdraw0", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
// set up scan parameters
FD_SCAN_PARAMS sp;
sp.flags = FD_OPTION_MFM;
sp.cyl = 0;
sp.head = 0;
// seek and scan track
DeviceIoControl(h, IOCTL_FD_SCAN_TRACK, &sp, sizeof(sp), psr, sizeof(buf), &dwRet, NULL);
// process the returned header list
for (i = 0 ; i < psr->count ; i++)
{
// do something with psr->Header[i]
}
CloseHandle(h);
Demo Disk Utility
DiskUtil is a sample utility to read/write/verify/format most regular format disks. The default geometry setup is for the SAM Coupé / MGT +D, but you can easily change the definitions to fit other formats.
The archive below contains a Visual C++ 6 project and the source code, as well as a pre-built EXE for your convenience:
Simon Owen
simon@simonowen.com