Floppy Disk Controller: Difference between revisions
Jump to navigation
Jump to search
[unchecked revision] | [unchecked revision] |
Content added Content deleted
(Added some notes, fixed one error) |
(Auto motor off info, fixed a couple more errors) |
||
Line 1: | Line 1: | ||
== Overview and Documentation == |
== Overview and Documentation == |
||
The Floppy Disk Controller (FDC) is a (legacy) device that controls internal 3.5/5.25 inch floppy disk drive devices on desktop x86 systems. |
The Floppy Disk Controller (FDC) is a (legacy) device that controls '''internal''' 3.5/5.25 inch floppy disk drive devices on desktop x86 systems. |
||
There are a range of chips that have been produced for this function which include: 8272A, 82078, 82077SL & 82077AA. The 82077AA is the most |
There are a range of chips that have been produced for this function which include: 8272A, 82078, 82077SL & 82077AA. The 82077AA is the most |
||
advanced, and has been produced since 1991. For more recent systems, a model of that chip has been embedded in the motherboard chipset. |
advanced, and has been produced since 1991. For more recent systems, a model of that chip has been embedded in the motherboard chipset. |
||
Line 6: | Line 6: | ||
=== Accessing Floppies in Real Mode === |
=== Accessing Floppies in Real Mode === |
||
For bootloaders or OSes that run with the CPU remaining in [[Real Mode]], use [[BIOS (PC)#BIOS functions|BIOS Function]] |
For bootloaders or OSes that run with the CPU remaining in [[Real Mode]], use [[BIOS (PC)#BIOS functions|BIOS Function]] |
||
floppy drives. 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 |
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 Mode. The rest of this article deals with creating Protected Mode drivers for the floppy subsystem. |
(using CHS) in Real Mode. The rest of this article deals with creating Protected Mode drivers for the floppy subsystem. |
||
Line 50: | Line 50: | ||
=== DMA Data Transfers === |
=== DMA Data Transfers === |
||
The floppy typically uses ISA DMA (which is |
The floppy typically uses ISA DMA (which is '''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). |
is hardwired to DMA channel 2. The only other way of doing data transfers is called "PIO Mode" (see below). |
||
Line 86: | Line 86: | ||
=== There are 3 "Modes" === |
=== There are 3 "Modes" === |
||
There were several generations of floppy controller chips on several generations of 80286 computers, before |
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 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 three modes are: PC-AT mode, PS/2 mode, and Model 30 mode. |
||
The only mode you will ever see on any hardware that still runs is |
The only mode you will ever see on any hardware that still runs is Model 30 mode. Sadly, most emulator programs run in PS/2 mode! |
||
In the documentation, you can ignore PC-AT mode. 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. |
||
=== Most commands run "silently" === |
=== 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 |
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 |
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 |
command actually worked, just about the only thing you can do is use the Dumpreg command to check the current state of the |
||
Line 101: | Line 101: | ||
=== Timing Issues === |
=== Timing Issues === |
||
On real hardware, there are definite timing issues. Seek delays and motor delays are just what any programmer would expect. It |
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. |
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 |
expect are that quite new hardware may still need 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, to know when the next byte can be sent. It is not a good idea to simply hardcode specific delays between output bytes. |
to test, 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. |
||
If you really feel you must do that, though, then a 400ns delay (consisting of reading the MSR port 4 times and discarding the |
|||
Looping 20 times, and testing the value of the RMQ bit in the MSR each time, should always be a sufficient "timeout". |
|||
a sufficient "timeout". |
|||
Using IO Port reads to generate delays 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. |
|||
== Registers == |
== Registers == |
||
Line 138: | Line 140: | ||
Note: IO port 0x3F6 is the ATA (hard disk) Alternate Status register, and is not used by any floppy controller. |
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 |
Note2: some people prefer to give the registers values based on their offset from the base address, and then add the |
||
So STATUS_REGISTER_A would have value 0, STATUS_REGISTER_B value 1, etc., and to |
FDC's base address (0x3F0 or 0x370). So STATUS_REGISTER_A would have value 0, STATUS_REGISTER_B value 1, etc., and to |
||
you would use FDC1_BASE_ADDRESS + STATUS_REGISTER_B = 0x370 + 1 = 0x371. |
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 === |
=== Bitflags and Registers that you can rely on === |
||
Line 199: | Line 201: | ||
in place, just in case the IRQs happen anyway. The bit '''must''' be set for DMA to function. |
in place, just in case the IRQs happen anyway. The bit '''must''' be set for DMA to function. |
||
Note2: |
Note2: if 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''' |
motor spinning (and up to speed), '''and''' its "select" bits must be set in the DOR, '''first''' -- but if you wait more than a |
||
couple of seconds after turning a motor on, it will turn itself off automatically and your command will fail. |
|||
Note3: toggling DOR reset state requires a 4 microsecond delay. It may be smarter to use DSR reset mode, because |
Note3: toggling DOR reset state requires a 4 microsecond delay. It may be smarter to use DSR reset mode, because |
||
Line 266: | Line 269: | ||
Alternately, you can always set both of them, for maximum compatibility with ancient chipsets. |
Alternately, you can always set both of them, 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. |
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, unless it booted a floppy disk). |
So generally, you want to set CCR to zero just once, after bootup (because the BIOS may not have done it, unless it booted a floppy disk). |
||
Line 281: | Line 284: | ||
</pre> |
</pre> |
||
Note: There is also a 300Kbps (value = 1), and a 250Kbps setting (value = 2) but they are only |
Note: There is also a 300Kbps (value = 1), and a 250Kbps setting (value = 2) but they are only for utterly obsolete drives/media. |
||
=== Important Bitflags that depend on the Mode === |
=== Important Bitflags that depend on the Mode === |
||
==== DIR register, Disk Change bit ==== |
==== DIR register, Disk Change bit ==== |
||
⚫ | |||
⚫ | |||
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 |
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 |
||
opened. Sadly, almost all the emulator programs set and clear this bit completely inappropriately. Do not trust your handling of this |
opened. Sadly, almost all the emulator programs set and clear this bit completely inappropriately. Do not trust your handling of this |
||
bit until you have tested the functionality on real hardware. |
bit until you have tested the functionality on real hardware. |
||
⚫ | |||
⚫ | |||
Basically, you want to keep a flag for whether there is media in each drive. if Disk Change is "true" and there was media, |
Basically, you want to keep a flag for whether there is media in each drive. if Disk Change is "true" and there was media, |
||
the OS should get a signal that the previous media was ejected, and the driver should automatically recalibrate the drive |
the OS should get a signal that the previous media was ejected, and the driver should automatically try to recalibrate the drive |
||
for the new media. The recalibration serves two purposes. The heads may have moved when the new media was inserted, and the |
for the new media. The recalibration serves two purposes. The heads may have moved when the new media was inserted, and the |
||
recalibrate will correct for this. Also, the recalibrate will clear the Disk Change flag to "false" if there is '''new''' media |
recalibrate will correct for this. Also, the recalibrate will clear the Disk Change flag to "false" if there is '''new''' media |
||
in the drive. You find out initially whether there is media in the drives by issuing Recalibrate commands to all the drives |
in the drive. You can find out initially whether there is media in the drives by issuing Recalibrate or Seek commands to all the drives |
||
(see the [[#Reinitialization|reinitialization]] procedure below). |
(see the [[#Reinitialization|reinitialization]] procedure below). |
||
Line 303: | Line 306: | ||
on the drive. If Disk Change remains "true", then the drive is still empty. If it goes "false", it means that a new |
on the drive. If Disk Change remains "true", then the drive is still empty. If it goes "false", it means that a new |
||
floppy has been inserted, and the OS should get a "media inserted" signal. On some/most hardware, it is possible to |
floppy has been inserted, and the OS should get a "media inserted" signal. On some/most hardware, it is possible to |
||
simply toggle the drive motor bit off and then on again, to achieve the same result as doing a Seek (Linux calls this a "twaddle"). |
simply toggle the drive motor bit on, off, and then on again, to achieve the same result as doing a Seek (Linux calls this a "twaddle"). |
||
But there is a problem. As the section title says, the problem is that the '''value''' of the Disk Change bitflag depends |
But there is a problem. As the section title says, the problem is that the '''value''' of the Disk Change bitflag depends |
||
Line 317: | Line 320: | ||
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. |
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 " |
Bits 4, 5, and 6 of DIR tell you what value of Disk Change is "false". Usually "true" = 1 on real hardware. |
||
The workaround (to do ONCE): Read DIR. Assume that the current value of Disk Change means "true". Send a Recalibrate command. |
The workaround (to do ONCE): Read DIR. Assume that the current value of Disk Change means "true". Send a Recalibrate command. |
||
Line 371: | Line 374: | ||
# Turn on the drive's motor and select the drive, using an "outb" command to the DOR IO port. |
# 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. |
# 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"). |
# 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"). |
# 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'''. |
# 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"). |
# 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. |
# The commands "Recalibrate", "Seek", and "Seek Relative" do not have a result phase, and require an additional "Sense Interrupt" command to be sent. |
||
Line 380: | Line 383: | ||
=== Motor Delays === |
=== Motor Delays === |
||
Note: the Linux floppy driver sourcecode has a comment that claims that turning on the |
Note: the Linux floppy driver sourcecode has a comment that claims that turning on the MOTC or MOTD bits in the DOR |
||
can completely lock up some systems. This claim seems unlikely? |
|||
Note2: modern floppy drives |
Note2: modern floppy drives only spin the disk for one or two seconds, and then automatically turn the motors back off by themselves. |
||
You need to access the drive within that window, or you will get an error. The motor might not even visibly turn on until you send |
|||
a command. |
|||
When you turn a floppy drive motor on, it takes quite a few milliseconds to "spinup" -- to reach the (stabilized) speed |
When you turn a floppy drive motor on, it takes quite a few milliseconds to "spinup" -- to reach the (stabilized) speed |
||
Line 389: | Line 394: | ||
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. |
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 |
After you are done reading (or writing), you should typically wait an additional half second to force 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, |
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 |
but it might give the user the idea that the floppy is still transferring data and that it's taking an awfully long time -- |
||
and, as said above, the floppy drive may well turn its own motor off anyway. |
|||
The suggested delays when turning the motor on are: |
The suggested delays when turning the motor on are: |
||
Line 398: | Line 404: | ||
* 500 milliseconds (for a 5.25" floppy). |
* 500 milliseconds (for a 5.25" floppy). |
||
These values should be more than enough for any floppy drive to spin up correctly. |
These values should be more than enough for any floppy drive to spin up correctly. 50ms 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. |
Another functional method is not delaying or waiting at all, but just enter a loop and keep retrying any command until it works. |
||
Line 417: | Line 423: | ||
a 1680Kb floppy disk. These disks may be unreliable on very old computers (where accuracy and speed variation may be worse), |
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). |
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 === |
=== Procedures === |
||
Line 452: | Line 467: | ||
# Do a Drive Select procedure for the next drive to be accessed. |
# 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 |
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). |
|||
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. |
|||
==== Drive Selection ==== |
==== Drive Selection ==== |
||
Line 552: | Line 571: | ||
==== Status Registers ==== |
==== Status Registers ==== |
||
There are 3 registers that hold information about the last error encountered. The st0 register information |
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, and can be retrieved at any time with a Sense |
is passed back with the result bytes of most commands, and can be retrieved at any time with a Sense |
||
Line 561: | Line 579: | ||
The top bit (value = 0x80) being set indicates two types of errors that you should never see. |
The top bit (value = 0x80) being set indicates two types of errors that you should never see. |
||
Bit 6 (value = 0x40) is set for any "normal" error condition, unless the error locks up the controller completely. |
Bit 6 (value = 0x40) is set for any "normal" error condition, unless the error locks up the controller completely. |
||
Bit 4 (value = 0x10) is set if Recalibrate |
Bit 4 (value = 0x10) is set if Recalibrate, Seek, or an implied seek succeeds. The other bits are not useful. |
||
===== st1 ===== |
===== st1 ===== |
||
The st1 register provides more detail about errors during read/write operations. |
The st1 register provides more detail about errors during read/write operations. |
||
Line 683: | Line 701: | ||
* First result byte = [[#st0|st0]] |
* First result byte = [[#st0|st0]] |
||
* Second result byte = controller's idea of the current cylinder |
* 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. |
|||
==== Recalibrate ==== |
==== 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 |
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 |
complete before selecting a different drive, and sending another Recalibrate command to it (but the Step Rates have to match, for this |
||
to work). |
|||
It is possible for a normal 1.44M floppy to be formatted with 83 cylinders. So, theoretically, it may take two (or more) Recalibrates |
|||
to cylinder 0. |
to move the head back to cylinder 0. It is a good idea to test bit 5 (value = 0x20) in [[#st0|st0]] after the Sense Interrupt, and |
||
retry the Recalibrate command if that bit is clear. |
|||
* Recalibrate command = 0x7 |
* Recalibrate command = 0x7 |
||
Line 703: | Line 729: | ||
The motor needs to be on, and the drive needs to be selected. |
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 |
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. |
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 |
* Seek command = 0xf |
||
Line 747: | Line 774: | ||
* Seventh result byte = 2 |
* 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: Floppy media and electronics are well known for being unreliable. Any read or write command that fails should be retried |
|||
at least twice. |
|||
=== Perpendicular Mode and 2.88M floppies === |
=== Perpendicular Mode and 2.88M floppies === |