Floppy Disk Controller: Difference between revisions

m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
(Complete rewrite with lots of additional info)
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(37 intermediate revisions by 15 users not shown)
Line 1:
== Overview and Documentation ==
The Floppy Disk Controller (FDC) is a (legacy) device that controls hardware for access to'''internal''' 3.5/5.25 inch floppy disks.disk Theredrive aredevices aon rangedesktop ofx86 chips that havesystems.
There are a range of chips that have been produced for this function andwhich include: 8272A, 82078, 82077SL & 82077AA. The 82077AA is the most advanced, and has been produced since 1991. For more
advanced, and has been produced since 1991. For more recent systems, a model of that chip has been embedded in the motherboard chipset. (So pay close attention to that datasheet, below.)
(So pay close attention to that datasheet, below.)
 
=== Accessing Floppies in Real Mode ===
For bootloaders or OSes that run with the CPU remaining in [[Real Mode]], use [[BIOS#BIOS functions|BIOS Function]]
Use BIOS function INT13h AH=2 (read) or AH=3 (write). You need to know the "drive number" (typically 0 or 1), and put that value in DL.
INT13h AH=2 (read) or AH=3 (write) to access floppy drives. You need to know the "drive number" (typically 0 or 1), and put that value in DL.
More detailed info can be found in the [[ATA in x86 RealMode (BIOS)]] article, because accessing a floppy is identical to accessing a hard disk
(using CHS) in Real modeMode. The rest of this article deals with creating Protected Mode drivers for the floppy subsystem.
 
Note: the Extended BIOS Int13h functions do not work with floppies.
 
Note2: the BIOS IRQ0 handler remembers a timeout for turning the motor off, from the last BIOS floppy access. The last access may have been
attempting to load your bootloader. So, in the distant future, if the BIOS ever receives 36 more IRQ0 ticks (if you ever return to Real Mode)
it may turn off your floppy motors for you, once.
 
=== Accessing USB Floppy Drives ===
All [[USB]] devices, including USB floppy drives, are accessed indirectly (using SCSI-style commands encoded in USB datapackets) over the USB bus.
USB floppy drives do not use any of the IO ports or FDC commands described in this article.
 
=== The Floppy Subsystem is Ugly ===
Line 17 ⟶ 29:
subsystem is the primary remaining one that accesses the obsolete and terrible ISA DMA system for its data transfers.
 
=== How Many Controllers? One! Usually. ===
Each ribbon cable for floppy drives can support 2 drives. One floppy controller chip can control 2 floppy cables, for a total
of 4 drives. It is theoretically possible for a system to have more than one controller chip, but you will not find anymany existing systems with
more than one. If a system were to havehas more than one floppy controller, the second controller wouldwill be found at a base IO port address of 0x370.
 
Some systems also have a third floppy controller, but these are exceedingly rare. Most of these will have originally been disk
=== How Many Drives? ===
duplication systems. The third floppy controller can be found at a base IO port address of 0x360.
 
=== How Many Drives? ===
It is probably wisest to always get the drive count and types from [[CMOS]], register 0x10.
 
Line 29 ⟶ 43:
In the olden days, there used to be 5.25 inch low-density, high-density, and single-sided drives. There used to be media
for each of these drive types. There also used to be 3.5 inch low density media. None of this really exists anymore. The only
actual hardware you will typically run into anymorenow is 3.5 inch, 1.44MB drives.
 
A 1.44MB floppy disk hasis usually formatted with 80 cylinders, 2 sides, and 18 sectors per track. The drive records the two sides with two "heads" that
with two "heads" that are bolted together. They cannot seek independently. The "track" on one side of a disk is always exactly opposite the
opposite the track on the other side of the disk. (There is a misconception about this in many floppy driver code examples.) You only
You only need to seek one head to find a particular cylinder, so that both heads may read/write that cylinder.
 
=== CHS ===
Line 42 ⟶ 56:
numbers are typically 0 to 79 inclusive, heads are 0 or 1, and sector numbers are 1 to 18 inclusive. Asking for sector number 0
is always highly illegal and this is a major source of errors in prototype driver code.
 
It is, however, much more logical to address things in LBA (Logical Block Addressing), as the first sector is at 0 (like an array).
Conversion between the two is rather simple. The equations are as follows:
 
''CYL = LBA / (HPC * SPT)''
 
''HEAD = (LBA % (HPC * SPT)) / SPT''
 
''SECT = (LBA % (HPC * SPT)) % SPT + 1''
 
''LBA = ( ( CYL * HPC + HEAD ) * SPT ) + SECT - 1''
 
This can be described in C with the following code:
 
<syntaxhighlight lang="c">
void lba_2_chs(uint32_t lba, uint16_t* cyl, uint16_t* head, uint16_t* sector)
{
*cyl = lba / (2 * FLOPPY_144_SECTORS_PER_TRACK);
*head = ((lba % (2 * FLOPPY_144_SECTORS_PER_TRACK)) / FLOPPY_144_SECTORS_PER_TRACK);
*sector = ((lba % (2 * FLOPPY_144_SECTORS_PER_TRACK)) % FLOPPY_144_SECTORS_PER_TRACK + 1);
}
</syntaxhighlight>
 
You would then send this data to the floppy controller.
 
=== DMA Data Transfers ===
The floppy typically uses ISA DMA (which is NOT'''not''' the same thing as PCI BusMastering DMA) to do data transfers. The floppy
is hardwired to DMA channel 2. The only other way of doing data transfers is called "PIO Mode" (see below).
 
Line 67 ⟶ 105:
 
If you are using PIO mode in a singletasking environment then the IRQ6s just waste CPU cycles, and you should be using polling instead.
You can usually shut the IRQs off by setting bit 3 (value = 8) of the Digital OuptputOutput Register (see below). If you want, you can even
toggle that bit on and off for specific commands, so that you receive some types of interrupts but not others.
 
To do PIO data transfers: init/reset the controller if needed (see below), select the drive if needed (see below),
Line 74 ⟶ 113:
in/out of the FIFO buffer -- or wait for an IRQ6 to do the same thing. When the transfer is complete, read the "result" bytes to see
if there were any errors. See below for more detail.
 
==== Bochs Can't Handle Polling PIO ====
If you try to turn off the IRQs in Bochs (to use pure polling PIO mode), Bochs will panic. It may also not be able to handle
non-DMA data transfers properly, even if IRQs are turned on. It is currently mainly designed to model the floppy using DMA.
 
=== There are 3 "Modes" ===
There were several generations of floppy controller chips on several generations of 80286 (and prior) computers, before
the 82077AA chip existed. The 82077AA was built to emulate all of them, by setting various pins on the chip to 5 volts.
The three modes are: PC-AT mode, PS/2 mode, and Model 30 mode. The most likely mode you will see on any hardware that
still runs is Model 30 mode. You may find some pre-1996 Pentium machines using PS/2 mode. Sadly, most emulator programs run in PS/2 mode!
The only mode you will ever see on any hardware that still runs is Model 30 Mode. So in the documentation, you
canIn justthe ignoredocumentation, theyou othercan ignore 2PC-AT modesmode. Or you can try to handle all three, by only using registers and commands that are identical
in all 3 modes.
in all 3 modes. The mode does not affect the driver code much, except for detecting "Disk Change".
 
=== Most Commands Run "Silently" ===
There are only a few commands/conditions that produce interrupts: a reset (in polling mode only), Seek, Recalibrate, or one of the
read/write/verify/format commands. Several commands do not produce any result bytes, either. If you want to verify that a silent
command actually worked, just about the only thing you can do is use the Dumpreg command to check the current state of the
controller.
 
=== Timing Issues ===
On real hardware, there are definite timing issues. Seek delays and motor delays are just what any programmer would expect. It
would be nice if the drive would send an IRQ when the motor was up to speed, but it does not. Two things that you may not
expect are that quite new hardware probably still needs artificial delays between outputting "command/parameter" bytes, and that you
probably also need artificial delays between inputting "result" bytes. There is a bit in the MSR to test in order to know when the
next byte can be sent/retrieved. It is not a good idea to simply hardcode specific delays between output/input bytes.
Looping 20 times, and testing the value of the RQM bit in the MSR each time, should always be a sufficient "timeout".
 
However, using IO Port reads to generate delays (or polling MSR) leads to poor performance in a multitasking environment, and
you may want to put the driver to sleep for the shortest possible period (microsleep), instead.
 
=== Reliability ===
In real hardware, floppy drives are extremely unreliable. On emulators, it doesn't matter -- but for code that is intended for
real hardware, make sure to retry every command at least twice more after any error.
 
== Registers ==
Line 91 ⟶ 155:
The basic set of floppy registers can be found in the following enumeration:
 
<sourcesyntaxhighlight lang="c">
enum FloppyRegisters
{
Line 104 ⟶ 168:
CONFIGURATION_CONTROL_REGISTER = 0x3F7 // write-only
};
</syntaxhighlight>
</source>
 
All commands, parameter information, result codes, and disk data transfers go through the FIFO port.
Line 113 ⟶ 177:
Note: IO port 0x3F6 is the ATA (hard disk) Alternate Status register, and is not used by any floppy controller.
 
Note2: some people prefer to give the registers values based on their offset from the base address, and then add the FDC's base address (0x3F0 or 0x370).
FDC's base address (0x3F0 or 0x370). So STATUS_REGISTER_A would have value 0, STATUS_REGISTER_B value 1, etc., and to access STATUS_REGISTER_B on FDC 1,
access STATUS_REGISTER_B on FDC 1, you would use FDC1_BASE_ADDRESS + STATUS_REGISTER_B = 0x370 + 1 = 0x371.
 
=== Bitflags and Registers that you can rely on ===
As said above, the most common controller chip has 3 modes, and many bitflags in the registers are different (or opposite!) depending on
the mode. SomeHowever, all of the important registers and bitflags remain the same, however.between They are the following:modes.
They are the following:
 
=== FIFO ===
FIFO: The FIFO register may not have a 16byte buffer in all modes, but this is a minor difference that does not really affect its operation.
TDR: The Tape Drive Register is identical in all modes, but it is useless (you will never find functional equipment that requires it).
 
==== DORTape bitflagDrive definitionsRegister ====
 
TDR: The Tape Drive Register is a R/W register which is identical in all modes. According to the documentation for the Intel 82077AA, the two least significant bits form a number between 1 and 3 (0 is an invalid value, drive 0 may not be selected) that assigns tape support to one particular drive out of four total drives. It is probably irrelevant for modern uses, is not supported by any emulator and is probably not worth implementing support for unless you have all the real hardware to test this functionality on.
 
=== DOR bitflag definitions ===
 
{| {{wikitable}}
|-
! Mnemonic
! bit number
! value
! meaning/usage
|-
| MOTD
| 7
| 0x80
| Set to turn drive 3's motor ON
|-
| MOTC
| 6
| 0x40
| Set to turn drive 2's motor ON
|-
| MOTB
| 5
| 0x20
| Set to turn drive 1's motor ON
|-
| MOTA
| 4
| 0x10
| Set to turn drive 0's motor ON
|-
| IRQ
| 3
| 8
| Set to enable IRQs and DMA
|-
| RESET
| 2
| 4
| Clear = enter reset mode, Set = normal operation
|-
| DSEL1 and 0
| 0, 1
Line 172 ⟶ 240:
in place, just in case the IRQs happen anyway. The bit '''must''' be set for DMA to function.
 
Note2: Ifif you want to execute a command that accesses a disk (see the command list below), then that respective disk must have its
motor spinning (and up to speed), '''and''' its "select" bits must be set in the DOR, '''first'''.
 
Note3: toggling DOR reset state requires a 4 microsecond delay. It may be smarter to use DSR reset mode, because
==== MSR bitflag definitions ====
the hardware "untoggles" reset mode automatically after the proper delay.
 
=== MSR bitflag definitions ===
 
{| {{wikitable}}
|-
! Mnemonic
! bit number
! value
! meaning/usage
|-
| RQM
| 7
| 0x80
| Set if it's OK (or mandatory) to exchange bytes with the FIFO IO port
|-
| DIO
| 6
| 0x40
| Set if FIFO IO port expects an IN opcode
|-
| NDMA
| 5
| 0x20
| Set in Execution phase of PIO mode read/write commands only.
|-
| CB
| 4
| 0x10
| Command Busy: set when command byte received, cleared at end of Result phase
|-
| ACTD
| 3
| 8
| Drive 3 is seeking
|-
| ACTC
| 2
| 4
| Drive 2 is seeking
|-
| ACTB
| 1
| 2
| Drive 1 is seeking
|-
| ACTA
| 0
Line 225 ⟶ 296:
|}
 
The two important bits are RQM and DIO. NDMA isand BUSY are also useful in polling PIO mode, and BUSY, too.
Most important is RQM, which is set when it is OK (or necessary!) to read/write data from/to the FIFO port.
DIO and BUSY should be checked to verify proper command termination (the end of "result phase" and beginning of "command phase").
NDMA signals the end of the "execution phase" of a command, and the beginning of "result phase".
DIO and BUSY should be checked to verify proper command termination (the end of "result phase" and beginning of "command phase").
 
==== CCR and DSR ====
The bottom 2 bits of DSR match CCR, and setting one of them sets the other. The upper 6 bits on both DSR and CCR default to 0, and
can always be set to zero safely. So, even though they have different bit definitions, you always set them with identical values, and
one or the other of DSR and CCR can be ignored in any modern system.
Alternately, you can always set both of them (and then do two Sense Interrupt commands), for maximum compatibility with ancient chipsets.
 
The bottom 2 bits specify the data transfer rate to/from the drive. You want both bits set to zero for a 1.44MB or 1.2MB floppy drive.
So generally, you want to set CCR to zero just once, after bootup (because the BIOS may not have done it)., Thisunless producesit anbooted IRQ6a whichfloppy disk).
must be waited for (or RQM polled in PIO mode, if IRQ6 is disabled). Then a Sense Interrupt command is mandatory.
 
Note: a reset procedure does not affect this register. However, if you have drives of different types on the bus which
use different datarates, then you need to switch the datarate when you select the other drive. It also seems to be possible
to modify this register while the FDC is in "reset state".
 
Note2: some tutorials seem to claim that changing/setting the datarate causes an IRQ6. This is false.
 
Datarates used for setting either DSR or CCR:
 
<pre>
Datarate value Drive Type
1Mbps 3 2.88M
500Kbps 0 1.44M, 1.2M
250Kbps 2
</pre>
 
Note: There is also a 300Kbps setting (value = 1), butand youa should250Kbps neversetting use(value it;= 250Kbps2) but they are only for isutterly powerupobsolete defaultdrives/media.
 
=== ImportantDIR Bitflagsregister, thatDisk dependChange on the Modebit ===
This bit (bit 7, value = 0x80) is fairly useful. It gets set if the floppy door was opened/closed. Sadly, almost all the emulator
programs set and clear this bit completely inappropriately (especially after a reset).
Do not trust your handling of this bit until you have tested the functionality on real hardware.
 
Note: The datasheet is very confusing about the value of the bit, because Model 30 mode shows the bit as being inverted. But
==== DIR register, Disk Change bit ====
in Model 30 mode, the '''signal''' is also inverted, so it comes out the same. "False" always means the bit is cleared, and
"true" always means the bit is set.
 
Note2: You must turn on the drive motor bit before you access the DIR register for a selected drive (you do not have to wait
This bit (bit 7, value = 0x80) could be very useful, if it actually works for you. It is supposed to tell you if the floppy door was
for the motor to spin up, though). It may also be necessary to read the register five times (discard the first 4 values) when
opened/closed.
'''changing''' the selected drive -- because "selecting" sometimes takes a little time.
 
SoBasically, you want to keep a flag for whether there is media in theeach drive. ifIf Disk Change is "true"set and there was media,
the OS should get a signal that the previous media was ejected.
 
Once the Disk Change bit signals "true" (and you have processed that "event"), you need to try to clear the bit. The main way to
If the driver wants to automatically check for new media, it can periodically do a Reset procedure on the drive/controller.
clear the bit is with a '''successful''' Seek/Recalibrate to a '''new''' cylinder on the media. (A reset does not work. If the
If Disk Change remains "true", then the drive is still empty. If it goes "false", it means that a new floppy has been inserted,
controller thinks the heads are already on the correct cylinder, it will eat a Seek command without clearing the Disk
and the OS should get a "media inserted" signal.
Change bit. If the heads are already on cylinder 0, a Recalibrate is also a no-op.) If the seek '''fails''', you can be fairly
certain that there is no media in the drive anymore. It is important to note that
this means you should check the value of Disk Change just prior to every Seek command that you do, because otherwise you
will lose any Disk Change information. This is also true for implied seeks, and relative seeks.
 
Apparently a small number of floppy drives also support one additional way to clear the bit -- something that
But there is a problem. As the section title says, the problem is that the value of the Disk Change bitflag depends
Linux calls a "twaddle". Simply toggle the drive motor bit on, off, and then on again. When your driver tries to clear the
on the mode. And there are 3 possible modes.
Disk Change bit the first time, it can try a twaddle, and see if it works, and keep a flag if it does.
And the idiots at Intel who made this chip failed utterly in giving you a definitive way of figuring out the mode.
The three modes, again, are PC-AT mode, PS/2 mode, and Model 30 mode. It is easy to distinguish PS/2 mode from
Model 30 mode. So how do you distinguish PC-AT mode from the other two? You can't. There isn't a bitflag anywhere
that will tell you whether the chip is in PC-AT mode.
 
What can you do? There are a few possibilities. The two main ones:
# Ignore PC-AT mode. This is really a very safe assumption.
# Do a workaround to figure out if this Disk Change bit means "true" when set, or "false" when set.
 
If you ignore PC-AT mode, then you can tell whether Disk Change is true or false by looking at the other bits in upper DIR.
Bits 4, 5, and 6 of DIR tell you what value of Disk Change is "true". Usually "true" = 0.
 
The workaround (to do ONCE): Read DIR. Assume that the current value of Disk Change means "true". Send a Recalibrate command.
If it works, then there is media in the drive, and Disk Change should have toggled to "false". So
read DIR again, check the Disk Change bit, and see if it flipped values. If it did, your initial guess for
the value of "true" is correct. (Otherwise, it was the opposite.)
If the Recalibrate fails, then there is no media in the drive, and Disk Change should continue to always be "true".
So your initial guess was correct.
 
Logic diagram:
{| {{wikitable}}
|-
! State of media
! actual DskChg flag
! guess
! Recalibrate result
! new actual DskChg flag
! guess good?
|-
| None in drive
| true
| true
| Failure
| true
| yes
|-
| Just inserted
| true
| true
| Success
| false
| yes
|-
| accessed many times
| false
| true
| Success
| false
| no
|}
 
 
==== Other bitflags in other registers ====
If you really want to try using non-essential bitflags that are mode-dependent, you will need to look in the
datasheet linked below for their locations and meanings. Including them would add too much extraneous info to
this article.
 
== Programming Details ==
Overall, the controller needs to be initialized and reset once (see below for the steps involved). Then a particular drivedrives can be
accessed. To doaccess that, youdrives:
# Turn on the drive's motor and select the drive, using an "outb" command to the DOR IO port.
# Wait for awhile for the motor to get up to speed, using some waiting scheme.
# Issue your command byte plus some parameter bytes (the "command phase") to the FIFO IO port.
# Exchange data with the drive / "seek" the drive heads (the "execution phase"), on the FIFO IO port.
# Get an IRQ6 at the end of the execution phase, but '''only if the command HAS an execution phase'''.
# Read any "result bytes" produced by the command (the "result phase"), on the FIFO IO port.
# The commands "Recalibrate", "Seek", and "Seek Relative" do not have a result phase, and require an additional "Sense Interrupt" command to be sent.
 
And then you are ready for the next command. See below for more detail on how to issue a command.
 
=== Motor Delays ===
Note: the Linux floppy driver sourcecode has a comment that claims that turning on the MOTC or MOTD bits in the DOR
When you turn the motor on, it takes quite a few milliseconds to "spinup" -- to reach the (stabilized) speed needed for data transfer.
can completely lock up some systems. This claim seems unlikely?
The controller has electronics to handle a large variation in rotation speed, but it has its limits.
If you start reading/writing immediately after the motor is turned on, "the PLL will fail to lock on to the data signal" and you'll get an error.
 
When you turn a floppy drive motor on, it takes quite a few milliseconds to "spin up" -- to reach the (stabilized) speed
After you're done reading (or writing), you should typically wait an additional 2 seconds to turn the motor off. (It may also be smart
needed for data transfer. The controller has electronics to handle a large variation in rotation speed, but it has its limits.
If you start reading/writing immediately after the motor is turned on, "the PLL will fail to lock on to the data signal" and you will get an error.
 
After you are done reading (or writing), you should typically wait an additional two seconds to turn the motor off. (It may also be smart
to seek the heads back to cylinder 0 just before turning the motor off.) You could leave the motor on longer,
but it might give the user the idea that the floppy is still transferring data and that it's taking an awfully long time.
The reason to leave the motor on is that your driver may not know if there is a queue of sector reads or writes
that are going to be executed next. If there are going to be more drive accesses immediately, they won't need to wait
for the motor to spin up.
 
The suggested delays when turning the motor on are:
Line 354 ⟶ 388:
* 500 milliseconds (for a 5.25" floppy).
 
These values should be more than enough for any floppy drive to spin up correctly. 100ms50ms should be sufficient, in fact.
 
Another functional method is not delaying or waiting at all, but just enter a loop and keep retrying any command until it works.
Line 373 ⟶ 407:
a 1680Kb floppy disk. These disks may be unreliable on very old computers (where accuracy and speed variation may be worse),
but were considered reliable enough for Microsoft to distribute a version of Windows on 1680Kb floppies (Windows 95 on a set of 12 floppies).
It is also possible to format 3 extra cylinders on each disk, for a total of 83.
 
=== Hardware Control of Bad Cylinders ===
The FDC subsystem has a built-in method for handling unreliable media. However, it is probably not a good idea to use it.
It involves finding a bad sector on the media, and then marking the entire track or cylinder as being bad, during the formatting process.
 
If you try to do this, then you cannot simply seek to a cylinder. All of the cylinders get "remapped" with new "TrackID"s. Whenever
you seek to a cylinder, then you need to use the ReadID command to verify that the cylinder you seeked to contains the data that you
actually want. So this eliminates any possibility of using implied seeks, and adds an extra step to most read/write operations.
 
=== Procedures ===
Line 378 ⟶ 421:
==== Reinitialization ====
 
The BIOS probably leaves the controller in its default state. Polling"Drive polling mode" on, FIFO off, threshold = 1, implied seek off, lock off. This
lock off. This is a lousy state for the controller to be in, and your OS will need to fix it. The BIOS probably also does not have any better
any better guess as to the proper values for the "Specify" command than your OS does (the values are specific to the particular drive). There
drive). There is certainly no reason why you should trust the BIOS to have done any of it '''correctly.'''
 
So, when your OS is initializing:
# Send 4 Sense Interrupt commands to the controller. This may clear a problem with default "polling mode".
# Send a Version command to the controller.
# Verify that the result byte is 0x90 -- if it is not, it might be a good idea to abort and not support the floppy subsystem. Almost all of the code based on this article will work, even on the oldest chipsets -- but there are a few commands that will not.
# If you don't want to bother having to send another Configure command after every Reset procedure, then:
## Send a better Configure command to the controller. A suggestion would be: drive polling mode off, FIFO on, threshold = 8, implied seek on, precompensation 0.
## send a Lock command.
# Do a Controller Reset procedure.
# Send a Recalibrate command to each of the drives. (You can do them all at the same time, or you can use this as an opportunity to handle the "Disk Change" bit, and/or you can use this as a chance to detect which drives actually have media in them.)
 
==== Controller Reset ====
Line 400 ⟶ 442:
 
# Either use:
## Bit 2 (value = 4) in the DOR: Save the current/"original" value of the DOR, write a 0 to the DOR, wait 4 microseconds, then write the original value (bit 2 is always set) back to the DOR.
## '''or''' Bit 7 (value = 0x80) in the DSR: Precalculate a good value for the DSR (generally 0), and OR it with 0x80. Write that value to the DSR.
# Wait for the resulting IRQ6. (unless you have IRQs turned off in the DOR)
# SendIf a(and only if) [[#Configure|drive polling mode]] is turned on, send 4 Sense Interrupt commandcommands (required).
# If your OS/driver never sent a Lock command, then you haveprobably need to send 3a morenew SenseConfigure Interruptcommand commands(the andfifo yousettings needwere tolost sendin athe new Configure commandreset).
# Do a Drive Select procedure for the next drive to be accessed.
 
Note: A reset clears all the Specify information, so the next Select procedure must send a new Specify command (use some sort of flag to tell the driver to do this).
driver to do this).
 
Note2: Emulators will often set the Disk Change flag to "true" after a reset, '''but this does not happen on real hardware''' -- it is
a shared bug in all the emulators that do it. Another shared bug is that most emulators do not fire an IRQ6 if disk polling mode is off.
 
Note3: A reset does not change the [[#Configure|drive polling mode or implied seek]] settings.
 
==== Drive Selection ====
Line 416 ⟶ 464:
 
# Send the correct Datarate setting to CCR. Usually this is a 0 (1.44MB floppy drive).
# Wait for the IRQ6.
# Send a Sense Interrupt command (required).
# If the newly selected drive is a different type than the previously selected drive (or changing from PIO to DMA mode), send a new Specify command.
# Set the "drive select" bits (and the other bitflags) in DOR properly, including possibly turning on the Motor bit for the drive (if it will be accessed soon).
Line 424 ⟶ 470:
 
The user can swap media out of a floppy drive at any moment. If your driver sends a command to the drive, and the
command fails -- this may be the reason why.
 
# Turn the drive motor bit on.
# You need to either know or assume the "true" value for the Disk Change bitflag in DIR.
# Read DIR. If the "Disk Change" bitflag is set to "true", then the floppy drive door was opened, so the driverOS needs to Recalibratetest if a new disk is in the drive,.
then the OS needs to test if a new disk is in the drive.
 
==== Waiting ====
Line 443 ⟶ 488:
# Verify that RQM = 1 and DIO = 0 ((Value & 0xc0) == 0x80) -- if not, reset the controller and start all over.
# Send your chosen command byte to the FIFO port (port 0x3F5).
# In a loop: loop on reading MSR until RQM = 1. Verify DIO = 0, then send the next parameter byte for the command to the FIFO port.
# Either Execution or Result Phase begins when all parameter bytes have been sent, depending on whether you are in PIO mode, and the command has an Execution phase. If using DMA, or the command does not perform read/write/head movement operations, skip to the Result Phase.
# (In PIO Mode Execution phase) read MSR, verify NDMA = 1 ((Value & 0x20) == 0x20) -- if it's not set, the command has no Execution phase, so skip to Result phase.
# If using DMA, or the command does not perform read/write operations, skip to the Result Phase.
# (In PIO Mode) read MSR, verify NDMA = 1 ((Value & 0x20) == 0x20)
# begin a loop:
# Either poll MSR until RQM = 1, or wait for an IRQ6, using some waiting method.
# In an inner loop: transfer a byte in or out of the FIFO port via a system buffer, then read MSR. Repeat while RQM = 1 and NDMA = 1 ((Value & 0xa0) == 0xa0).
# if NDMA = 1, loop back to the beginning of the outer loop, unless your data buffer ran out (detect underflow/overflow).
# Result Phase begins. If the command does not have a Result phase, it silently exits to waiting for the next command.
# Result Phase begins.
# ifIf using DMA on a read/write command, wait for a terminal IRQ6.
## Loop on reading MSR until RQM = 1, verify that DIO = 1.
# ''If the command has a result phase:''
## loop on reading MSR until RQM = 1, verify that DIO = 1
## In a loop: read the next result byte from the FIFO, loop on reading MSR until RQM = 1, verify CMD BSY = 1 and DIO = 1 ((Value & 0x50) == 0x50).
# After reading all the expected result bytes: check them for error conditions, verify that RQM = 1, CMD BSY = 0, and DIO = 0. '''If not''' retry the entire command again, several times, starting from step 2!
entire command again, several times, starting from step 2!
 
Note: implementing a failure timeout for each loop and the IRQ is pretty much required -- it is the only way to detect many command errors.
Line 468 ⟶ 510:
 
A command byte may only be sent to the FIFO port if the RQM bit is 1 and the DIO bit is 0, in the MSR. If these
bits are not correct, then the previous command encountered a fatal error, and you must issue a [reset|[#"Controller Reset"|reset]].
 
The following is an enumeration of the values of the command bytes. The ones that you actually will '''use''' are marked
with a * and a comment.
 
<sourcesyntaxhighlight lang="c">
enum FloppyCommands
{
READ_TRACK = 2, // generates IRQ6
SPECIFY = 3, // * set drive parameters
SENSE_DRIVE_STATUS = 4,
Line 484 ⟶ 526:
SENSE_INTERRUPT = 8, // * ack IRQ6, get status of last command
WRITE_DELETED_DATA = 9,
READ_ID = 10, // generates IRQ6
READ_DELETED_DATA = 12,
FORMAT_TRACK = 13, // *
DUMPREG = 14,
SEEK = 15, // * seek both heads to cylinder X
VERSION = 16, // * used during initialization, once
Line 492 ⟶ 535:
PERPENDICULAR_MODE = 18, // * used during initialization, once, maybe
CONFIGURE = 19, // * set controller parameters
LOCK = 20, // * protect controller params from a reset
VERIFY = 22,
SCAN_LOW_OR_EQUAL = 25,
SCAN_HIGH_OR_EQUAL = 29
};
</syntaxhighlight>
</source>
 
==== "Deleted sectors" ====
Deleted sectors is a legacy feature, dating back to the 1970s, when IBM data entry terminals stored a single database record in each (128 byte) floppy sector, and a record could be marked as deleted by writing a different address mark before the sector. This feature was almost never used with IBM PC and compatibles – exceptions were occasional abuse by copy protection schemes, and (possibly) hiding bad sectors. Few (if any) emulators emulate this functionality, and many late model FDCs didn't implement it.
 
The "WRITE_DELETED_DATA" command can be used to create a deleted sector, and the "READ_DELETED_DATA" command can be used to read one back.
 
==== Option bits ====
Line 506 ⟶ 555:
===== Bit MF =====
Value = 0x40. "MFM" magnetic encoding mode. Always set it for read/write/format/verify operations.
 
A zero value represents the old "single density" FM format, which (on IBM PCs) was only used by 8-inch floppies (which were extremely rare). Few people ever used FM format, except for data interchange with non-PC systems (such as CP/M or minicomputers). Later model FDCs (e.g. Intel 82078) dropped support for it.
 
===== Bit SK =====
Value = 0x20. Skip mode. IgnoreThis thisskips bit"deleted andsectors" leave(see itexplanation cleared,in unlesssection you have a really good reason not toabove).
 
Ignore this bit and leave it cleared, unless you have a really good reason not to.
==== Status Registers ====
 
==== Status Registers ====
There are 3 registers that hold information about the last error encountered. The st0 register information
is passed back with the result bytes of most commands,. andThe canst1 beand retrievedst2 atinformation anyis timereturned within athe Senseresult
bytes of read/write commands. They can also be retrieved with a Dumpreg command.
Interrupt command. The st1 and st2 information is returned in the result bytes of read/write commands. They
can also be retrieved with a Dumpreg command.
 
===== st0 =====
The top bit2 bits (value = 0x800xC0) beingare set indicatesafter twoa typesreset ofprocedure, errorswith thatpolling youon. shouldEither neverbit see.being set at any other
time is an error indication.
Bit 6 (value = 0x40) is set for any "normal" error condition, unless the error locks up the controller completely.
Bit 45 (value = 0x100x20) is set ifafter every Recalibrate, Seek, or an implied failsseek. The other bits are not useful.
 
===== st1 =====
The st1 register provides more detail about errors during read/write operations.
Line 526 ⟶ 579:
caused by '''not subtracting 1''' when setting the DMA byte count.
Bit 4 (value = 0x10) is set if your driver is too slow to get bytes in or out of the FIFO port in time.
Bit 1 (value = 2) is set if the floppy drive is indicating that the media is write protected.
The rest of the bits are for various types of data errors,; indicating bad media, or a bad drive.
 
===== st2 =====
The st2 register provides more (useless) detail about errors during read/write operations.
 
The bits all indicate various types of data errors, indicatingfor either bad media, or a bad drive.
 
 
==== Configure ====
This command initializes controller-specific values: the data buffer "threshold" value, implied seek enable, FIFO enabledisable, polling enable.
(And "Write Precompensation".) A good setting is: implied seek on, FIFO on, drive polling mode off, threshold = 8, precompensation 0.
 
If you enable implied seeks, then you don't have to send Seek commands (or Sense Interrupt commands for the Seek commands).
Line 544 ⟶ 598:
transfer!).
 
PollingDrive polling mode is just an annoyance that is there for backwards software compatibility. It makes you need extra Sense Interrupts
after every reset. Always turn it off, if you send a Configure command.
 
Line 555 ⟶ 609:
* Configure command = 0x13
* First parameter byte = 0
* Second parameter byte = (implied seek enableENable << 6) | (fifo enable'''DIS'''able << 5) | (drive polling enablemode '''DIS'''able << 4) | thresh_val (= threshold - 1)
* Third parameter byte = precomp_val = 0
* No result bytes.
* No interrupt.
 
==== Version ====
Returns one byte. If the value is 0x90, the floppy controller is a 82077AA.
 
* Version command = 0x10
* No parameter bytes.
* No interrupt.
* First result byte = 0x90
 
==== Lock ====
Under default circumstances, every Controller Reset will disable the fifo, and set the fifo threshold to 1 (thresh_val = 0).
If you change these settings with the Configure command and don't want to have to fix them after every Controller Reset,
then you can send a Lock command with the lock bit turned on. You can "unset" the lock, by sending another Lock command
with the lock bit turned off. Use the MT option bit as the lock bit.
 
* Lock command = 0x94
* '''or''' Unlock command = 0x14
* No parameter bytes.
* No interrupt.
* First result byte = lock bit << 4
 
==== Specify ====
Line 572 ⟶ 647:
* Second parameter byte = HLT_value << 1 | NDMA
* No result bytes.
* No interrupt.
 
===== SRT, HLT and HUT =====
These parameters sent with the Specify command to the controller are meant to optimize drive performance, and head lifetime.
 
TheyThe values are specific to the exact model, condition, and age of floppy drive installed on the system. The values sent by the driver to the
controller were always meant
to be '''adaptive'''. That is, your driver is theoretically supposed to keep statistics of how often Seek commands fail with the current
setting of SRT. If they always work, and your driver wants to optimize performance, then it can send a new Specify command, with the SRT
value reduced by 1. Then begin keeping new statistics. Similarly for HLT and HUT regarding Read/Write operations. As drives age and collect
dirt, the driver would automatically compensate by seeing higher statistical error rates, and increase the values of SRT, HLT, and HUTHLT.
 
Keeping statistics in that way only works when the drive in question is used often. Now that internal floppy drives are nearly obsolete,
it is worthless. So the current recommendataionrecommendation is just to use very safe values, and forget about performance.
 
If you look up spec sheets for individual floppy drives, they usually show a worst-case "track to track seek time" = SRT, but not the other two.
Line 605 ⟶ 681:
==== Sense Interrupt ====
This command's main function is to return any error code from a Seek or Recalibrate command to your driver. It also clears an internal
bitflag in the controller. It is required in fourthree circumstances that produce interrupts.
 
# After doing a Controller Reset procedure with [[#Configure|drive polling mode]] turned on.
# After setting the datarate in CCR/DSR.
# After doing a Controller Reset procedure (you may need to send 4 of them, then).
# After the completion of a Seek command (or Relative Seek).
# After the completion of a Recalibrate command.
 
These are the only times when you should send a Sense Interrupt. You canshould optionallystill send itthem anyeven otherif timeyou thathave theIRQs turned off in
the DOR and you are using PIO polling instead. If you send Sense Interrupt commands at other times: the command will complete, return
controller is in command phase, but you will get an error if you try to send it when the controller is still in result phase,
a 0x80, and then lock up the controller until you do a Reset.
and when you get an IRQ6, you usually do not know what phase the controller is in.
 
* Sense Interrupt command = 0x8
* No parameter bytes.
* No interrupt.
* First result byte = [#st0]
* First result byte = [[#st0|st0]]
* Second result byte = controller's idea of the current cylinder
 
Note: if you try to read the result bytes without waiting for RQM to set, then you are likely to always get an incorrect result value
of 0. This is also likely to get your driver out of sync with the FDC for input/output. The correct value of st0 after a reset should
be 0xC0 | drive number (drive number = 0 to 3). After a Recalibrate/Seek it should be 0x20 | drive number.
 
==== Recalibrate ====
Note: There is an error in the FDC datasheet regarding this command. Some statements say the command will try a maximum of 80 head
assembly steps. In other places it says 79 steps. The value 79 is correct.
 
The motor needs to be on, and the drive needs to be selected. For this particular command, you do not have to wait for the command to
complete before selecting a different drive, and sending another Recalibrate command to it. (but the Step Rates have to match, for this
to work).
 
TheIt commandis willpossible only tryfor a maximumnormal of1.44M 80floppy headto assemblybe stepsformatted with 83 cylinders. So, theoretically, it may take severaltwo Recalibrates(or tomore) move the head backRecalibrates
to move the head back to cylinder 0. So itIt is a good idea to test forbit an5 error(value = 0x20) in [[#st0|st0]] after the Sense Interrupt, and retry the command.
retry the Recalibrate command if that bit is clear.
 
* Recalibrate command = 0x7
* First parameter byte = drive number = 0 to 3.
* No result bytes.
* The interrupt may take up to 3 seconds to arrive, so use a long timeout.
 
It is possible to poll the "disk active" bits in the MSR to find out when the head movement is finished.
 
A Sense Interrupt command is required after this command completes, to clear it from being BUSY. (Multiple Sense Interrupts,
if you ran multiple simultaneous Recalibrates.)
 
==== Seek ====
The motor needs to be on, and the drive needs to be selected.
For this particular command, you do not have to wait for the command to complete before selecting a different drive, and sending another
Seek command to it. Maximum cylinder number is 255; if the disk has more, you must use the Relative Seek command, instead. There is
really no reason to ever use head 1 when seeking.
 
* Seek command = 0xf
Line 646 ⟶ 732:
* Second parameter byte = requested cylinder number
* No result bytes.
* The interrupt may take up to 3 seconds to arrive, so use a long timeout.
 
It is possible to poll the "disk active" bits in the MSR to find out when the head movement is finished.
Line 651 ⟶ 738:
A Sense Interrupt command is required after this command completes, to clear it from being BUSY. (Multiple Sense Interrupts,
if you ran multiple Seeks.)
 
Note: the controller tries to remember what cylinder each drive's heads are currently on. If you try to seek to that same
cylinder, then the controller will silently ignore the command (and return a "success" value). One of the things this means
is that you can get a "success" return value on a seek '''even if there is no media in the drive''', if you happen to seek
to the wrong cylinder number.
 
===== Relative seek =====
Line 661 ⟶ 753:
 
==== Read/Write ====
 
Note: Remember that this is in CHS format, so the sector number starts at 1.
 
Line 669 ⟶ 760:
* Second parameter byte = cylinder number
* Third parameter byte = head number (yes, this is a repeat of the above value)
* ThirdFourth parameter byte = starting sector number
* FourthFifth parameter byte = 2 (all floppy drives use 512bytes per sector)
* FifthSixth parameter byte = EOT (end of track, the last sector countnumber on tothe transfertrack)
* SixthSeventh parameter byte = 0x1b (GAP1 default size)
* SeventhEighth parameter byte = 0xff (all floppy drives use 512bytes per sector)
 
* First result byte = [[#st0|st0]] status register
* Second result byte = [[#st1|st1]] status register
* Third result byte = [[#st2|st2]] status register
* Fourth result byte = cylinder number
* Fifth result byte = ending head number
Line 683 ⟶ 774:
* Seventh result byte = 2
 
Note: if you try to read the result bytes without waiting for RQM to set, then you are likely to always get an incorrect result
value of 0. This is also likely to get your driver out of sync with the FDC for input/output.
 
Note2: You'll see that there's no sector count parameter. Instead, the FDC figures out when to stop by the DMA signaling to the FDC
=== Perpendicular Mode and 2.88M floppies ===
that it's done, or in PIO mode, by the FDC experiencing a FIFO overrun or underrun.
 
Note3: Floppy media and electronics are well known for being unreliable. Any read or write command that fails should be retried
at least twice, unless it was a write and failed on "write protect".
 
=== Perpendicular Mode and 2.88M floppies ===
If you are using an emulator and you need a floppy disk image that is bigger than 1440Kb, there is a 2880Kb image available.
In order to access it in Pmode, you need to modify your driver to handle Perpendicular Mode. Basically, it is an extra configuration
Line 695 ⟶ 793:
* First parameter byte = (Drive 3 enable << 5) | (Drive 2 enable << 4) | (Drive 1 enable << 3) | (Drive 0 enable << 2)
* No result bytes.
* No interrupt.
 
You also need to set CCR/DSR for the 1M datarate (value = 3) to access a 2.88M drive.
 
=== Additional Programming Notes ===
If you are doing a transfer between 2 floppy drives (so that both motors are on), and you are toggling "selection" between the two,
there may be a short delay required.
 
== Code Examples ==
 
=== A common coding error example ===
The following code intentionally contains a common bug that causes an infinite loop (waiting for IRQ6) on most emulators.
 
<sourcesyntaxhighlight lang="c">
volatile byte ReceivedIRQ = false;
 
Line 727 ⟶ 830:
WaitForIRQ();
}
</syntaxhighlight>
</source>
 
Sure this code ''looks'' OK, but some emulators or floppy drives might manage to be faster than your code. What if you've just returned from
Line 733 ⟶ 836:
set it to false again and then infinitely loop, waiting for an IRQ that has already been received. It's usually better to do something like:
 
<sourcesyntaxhighlight lang="c">
volatile byte ReceivedIRQ = false;
 
Line 768 ⟶ 871:
outb(Controller.FIFO, headload_ndma);
}
</syntaxhighlight>
</source>
 
== Related Links ==
Line 779 ⟶ 882:
* http://bos.asmhackers.net/docs/floppy/docs/floppy_tutorial.txt
* [http://www.intel.com/design/archives/periphrl/docs/29046803.htm Intel 82078 CHMOS SINGLE-CHIP FLOPPY DISK CONTROLLER datasheet (useless)]
* http://www.brokenthorn.com/Resources/OSDev20.html
 
=== Forum Posts ===
Line 789 ⟶ 893:
 
=== Implementations ===
* [httphttps://kodersgithub.com/cFDOS/fid051291340B94EC7F5D1A38EF6843466C0B07627Bkernel/blob/master/drivers/floppy.aspx?s=fdcasm freedosFreeDOS] (C, GPL)
* [http://bos.asmhackers.net/docs/floppy/snippet_9/fdc.c GazOS] (C, GPL)
* [http://bos.asmhackers.net/docs/floppy/snippet_5/FLOPPY.ASM RDOS] (ASMAssembly, GPL)
* [https://github.com/torvalds/linux/blob/master/drivers/block/floppy.c Linux] (C, GPL)
 
[[de:Floppy Disk Controller]]
[[Category:Storage]]
[[Category:Common Devices]]