Floppy Disk Controller
Aus Lowlevel
| Diese Seite oder Abschnitt ist zwar komplett, es wird aber folgende Verbesserungen gewünscht:
Hilf Lowlevel, den Artikel zu verbessern. |
Der Floppy Disk Controller (FDC) ist ein Chip, der ein bis vier Disketten-Laufwerke ansteuern kann.
Eigenschaften und Besonderheiten
- Der FDC sendet IRQ 6, wenn z.B. eine Lese- oder Schreiboperation beendet ist
- Der FDC kann über DMA Daten direkt aus dem Speicher auf Diskette schreiben und anders herum. Dafür wird DMA Channel 2 verwendet
- Der FDC ist recht schlecht standardisiert, da verschiedene Hersteller unterschiedliche Funktionalitäten hinzugefügt haben.
- Bei allen Operationen, für die der Schreib-/Lesekopf bewegt werden muss, sollte eine Wartezeit eingehalten werden, bis die nächste Operation folgt, da insbesondere Disketten sehr langsame Zugriffszeiten besitzen. Bei Fehlen dieser Wartezeiten funktioniert der Treiber oft nur in Emulatoren, versagt aber auf echter Hardware!
- Der FDC kann nur Sektorangaben in CHS verarbeiten
- Der FDC kann theoretisch bis zu vier Diskettenlaufwerke ansteuern, allerdings findet man heute wohl kein System mehr, welches mehr als zwei besitzt.
- Diskettenlaufwerke sind sehr fehleranfällig! Deshalb muss ein Befehl oftmals mehrmals gesendet werden, bevor er funktioniert. Großzügige Timeouts sind Pflicht in jedem Treiber für Diskettenlaufwerke.
Verhalten in Emulatoren und echter Hardware
- Bochs unterstützt nur die Übertragung der Daten via DMA. Allerdings ist dies auch die sinnvollste Variante.
- Microsoft Virtual PC hat Probleme mit DMA, wenn das Auto Bit gesetzt ist. Damit der Code funktioniert, muss der DMA Trnasfer jedes Mal durch die CPU reinitialisiert werden.
Register
Übersicht
| Name | Port | Beschreibung | lesen/schreiben |
|---|---|---|---|
| SRA | 0x3F0 | Status Register A | lesen |
| SRB | 0x3F1 | Status Register B | lesen |
| DOR | 0x3F2 | Digital Output Register | schreiben |
| TDR | 0x3F3 | Tape Drive Register | lesen/schreiben |
| MSR | 0x3F4 | Main Status Register | lesen |
| DRSR | 0x3F4 | Data Rate Select Register | schreiben |
| DR | 0x3F5 | Data Register | lesen/schreiben |
| DIR | 0x3F7 | Digital Input Register | lesen |
| CCR | 0x3F7 | Configuration Control Register | schreiben |
Aufbau
Wenn nichts dasteht, gilt das Flag bei gesetztem Bit.
MSR
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| Name | MRQ | DIO | NDMA | BUSY | ACTD | ACTC | ACTB | ACTA |
- MRQ
- Status
- 0: DR nicht bereit
- 1: DR bereit
- DIO
- Daten I/O
- 0: CPU -> FDC (Daten schreiben)
- 1: FDC -> CPU (Daten lesen)
- NDMA
- DMA-Modus
- 0: DMA-Modus aktiviert
- 1: DMA-Modus deaktiviert
- BUSY
- BUSY (Befehl wird ausgeführt)
- 0: nicht busy
- 1: busy
- ACTD-ACTA
- Laufwerk D-A ist im Positionierungs-Modus
DOR
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 0 |
|---|---|---|---|---|---|---|---|
| Name | MOTD | MOTC | MOTB | MOTA | DMA | RESET | DRIVE |
- MOTD-A
- Motorsteuerung
- 0: Motor ausschalten
- 1: Motor anschalten
- DMA
- DMA und IRQ
- 0: deaktiviert
- 1: aktiviert
- RESET
- Controller Reset
- 0: Reset ausführen (!!!)
- 1: Controller aktiviert (Reset ausgeführt)
- DRIVE
- Laufwerk auswählen
- 00: Drive 0
- 01: Drive 1
- 10: Drive 2
- 11: Drive 3
DR
In das Data-Register werden die Befehle geschrieben, die jeweils aus mehreren Bytes bestehen. Nach manchen Befehlen werden die Status-Register im DR zurückgegeben.
ST0
| Bit | 7 6 | 5 | 4 | 3 | 2 | 1 0 |
|---|---|---|---|---|---|---|
| Name | IC | SE | UC | NR | HD | US |
- IC
- Interrupt Code
- 00: der Befehl wurde ohne Fehler ausgeführt
- 01: der Befehl wurde gestartet, aber nicht richtig beendet
- 10: invalid Command
- 11: Controller war nicht bereit (Polling)
- SE
- Seek end
- der Controller hat einen Befehl mit inplizitem Seek erfolgreich ausgeführt
- UC
- unit check
- gesetzt bei Fehler
- NR
- drive not ready
- HD
- aktiver head
- 0: Head 0
- 1: Head 1
- US
- aktives Laufwerk
- 00: A
- 01: B
- 10: C
- 11: D
ST1
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| Name | EN | 0 | DE | TO | 0 | NDAT | NW | NID |
- EN
- End of Cylinder
- gesetzt wenn die geforderte Sektoranzahl die Anzahl der Sektoren auf einer Spur überschreitet
- DE
- Data Error
- gesetzt bei einem Fehler im ID Addressfield oder im Datafield
- TO
- Time-out
- gesetzt wenn der FDC keine Signale von CPU oder DMA empfängt
- NDAT
- No Data
- gesetzt wenn bei einem read sektor Befehl der Sektor nicht gefunden wurde
- oder wenn der FDC nach einem read ID Befehl die ID nicht lesen konnte
- NW
- Not Writeable
- gesetzt wenn die Floppy schreibgeschützt ist
- NID
- No Address Mark
- gesetzt wenn die ID Address Mark nicht gefunden wurde
ST2
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| Name | 0 | DADM | CRCE | WCYL | SEQ | SERR | BCYL | NDAM |
- DADM
- Deleted Address Mark
- Bei einem read Sector Befehl:
- eine gelöschte Data Address Mark wurde gefunden
- Bei einem read deleted Sector Befehl:
- eine gesetzte Data Address Mark wurde gefunden
- CRCE
- CRC error in data field
- WCYL
- wrong cylinder
- SEQ
- seek equal
- SERR
- seek error
- BCYL
- bad cylinder
- NDAM
- not data address mark DAM
ST3
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 0 |
|---|---|---|---|---|---|---|---|
| Name | ESIG | WPDR | RDY | TRKO | DSDR | HDDR | DS |
- ESIG
- Error
- gesetzt nach einem Fehler
- WPDR
- Write Protection
- gesetzt wenn die Disc schreibgeschützt ist
- RDY
- Ready
- gesetzt wenn die Drive ready ist
- TRKO
- Track 0
- der Head ist über Track 0 (?)
- DSDR
- Double sided drive
- gesetzt wenn die Floppy doppelseitig ist
- HDDR
- Head
- 0: Head 0
- 1: Head 1
- DS
- Drive select
- 00: A
- 01: B
- 10: C
- 11: D
CCR
Im CCR sind nur die Funktionen der ersten zwei Bits definiert (Bit 0 und Bit 1):
- 00 500 Kbps
- 10 250 Kbps
- 01 300 Kbps
- 11 1 Mbps
Befehle
Senden von Befehlen
Das Senden von Befehlen erfolgt in drei Phasen:
- Command-Phase: Der Befehl wird von der CPU an den FDC gesendet. Dafür werden die Daten wie weiter unten beschrieben in das Datenregister geschrieben. Vorher sollte getestet werden, ob dieses wirklich leer ist.
- Execution-Phase: Der Befehl wird vom FDC ausgeführt. Dabei sollte im Treiber eine Wartezeit eingehalten werden, da Diskettenlaufwerke sehr langsam sind. Bei einigen Befehlen wird die Execution-Phase zusätzlich durch das Senden eines IRQs beendet.
- Result-Phase: Bei einigen Befehlen liefert der FDC Daten zurück, z.B. ob der Befehl erfolgreich ausgeführt wurde. Diese müssen alle gelesen werden, damit erneut Befehle gesendet werden können.
Allgemeine Parameter
| Abkürzung | Beschreibung | Empfohlener Wert für 3,5" |
|---|---|---|
| MT | Multitrack, Befehl wird auf beiden Seiten der Diskette ausgeführt | |
| DTL | Datenlänge | 0xFF |
| N | Sekorengröße, wobei 0=128Byte, 1=256Byte, 2=512Byte, ... | 2 |
| Gap3 | Abstand zwischen Sektoren | 27 |
| MFM | High Density (HD) Mode | 1 |
| SK | Skip Mode, als gelöscht markierte Daten werden automatisch übersprungen | 1 |
| Drive, Cylinder, Head, Sektor | Positionsangabe für einen bestimmten Sektor einer Diskette. Siehe CHS | |
| Trackgröße | Anzahl der Sektoren, die auf einem Track („Ring“) liegen | 18 (pro Track und Seite) |
Technische Laufweksdaten einstellen (3h)
Command Phase:
| Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| #1: | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
| #2: | Step Rate | Head Unload Time | ||||||
| #3 | Head Load Time | Kein DMA | ||||||
Result Phase: keine
Anmerkungen:
- Die benötigten Daten sollten vom BIOS abgefragt werden (siehe unten)
- Im DOR kann eingestellt werden, ob DMA/IRQ oder keins von beidem, hier wird dann noch einmal explizit zwischen DMA (Bit gelöscht) und kein DMA (Bit gesetzt) entschieden.
Laufwerk kalibrieren (7h)
Command Phase:
| Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| #1: | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 |
| #2: | 0 | 0 | 0 | 0 | 0 | Head | Drive | |
Result Phase: keine
Anmerkungen:
- Nach der Kalibrierung wird ein IRQ6 gesendet
- Es kann passieren, dass der Befehl nicht funktioniert. Desshalb sollte mit "Interrput Status überprüfen" (8h) überprüft werden, ob Bit 4 in St0 gelöscht ist.
- Bei Disketten mit mehr als 80 Tracks muss dieser Befehl in jedem Fall mehrmals gesendet werden.
Laufwerksstatus überprüfen (4h)
Command Phase:
| Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| #1: | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| #2: | 0 | 0 | 0 | 0 | 0 | Head | Drive | |
Result Phase:
| ST3 |
Interrupt Status überprüfen (8h)
Command Phase:
| Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| #1: | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
Result Phase:
| #1: | ST0 |
| #2: | Aktueller Zylinder |
Anmerkungen:
- Dieser Befehl sollte immer gesendet werden, wenn der FDC einen IRQ 6 gesendet hat.
- Er muss außerdem viermal (!) nach einem Reset des FDCs gesendet werden.
- Die Zylindernummer ist oftmals falsch und sollte ignoriert werden.
Schreib- /Lesekopf positionieren (fh)
Command Phase:
| Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| #1: | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| #2: | 0 | 0 | 0 | 0 | 0 | Head | Drive | |
| #3: | Cylinder | |||||||
Result Phase: Keine
Anmerkungen:
- Es kann passieren, dass der Befehl nicht funktioniert. Desshalb muss mit "Interrput Status überprüfen" (8h) überprüft werden, ob Bit 5 in St0 gesetzt ist.
- Nach Beendigung wird ein IRQ 6 gesendet.
Sektor schreiben (5h)
Command Phase:
| Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| #1: | MT | MFM | SK | 0 | 0 | 1 | 0 | 1 |
| #2: | 0 | 0 | 0 | 0 | 0 | Head | Drive | |
| #3: | Cylinder | |||||||
| #4: | Head | |||||||
| #5: | Sektor | |||||||
| #6: | Sektorgröße N | |||||||
| #7: | Track-Größe | |||||||
| #8: | GAP3 | |||||||
| #9: | DTL | |||||||
Result Phase:
| #1: | ST0 |
| #2: | ST1 |
| #3: | ST2 |
| #4: | Cylinder |
| #5: | Head |
| #6: | Sektor |
| #7: | Sektorgröße N |
Anmerkungen:
- Nach dem Schreibvorgang wird ein IRQ 6 gesendet.
Sektor lesen (6h)
Command Phase:
| Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| #1: | MT | MFM | SK | 0 | 0 | 1 | 1 | 0 |
| #2: | 0 | 0 | 0 | 0 | 0 | Head | Drive | |
| #3: | Cylinder | |||||||
| #4: | Head | |||||||
| #5: | Sektor | |||||||
| #6: | Sektorgröße N | |||||||
| #7: | Track-Größe | |||||||
| #8: | GAP3 | |||||||
| #9: | DTL | |||||||
Result Phase:
| #1: | ST0 |
| #2: | ST1 |
| #3: | ST2 |
| #4: | Cylinder |
| #5: | Head |
| #6: | Sektor |
| #7: | Sektorgröße N |
Anmerkungen:
- Nach dem Lesevorgang wird ein IRQ 6 gesendet.
Kompletten Track lesen (2h)
Command Phase:
| Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| #1: | 0 | MFM | SK | 0 | 0 | 0 | 1 | 0 |
| #2: | 0 | 0 | 0 | 0 | 0 | Head | Drive | |
| #3: | Cylinder | |||||||
| #4: | Head | |||||||
| #5: | 0 | |||||||
| #6: | Sektorgröße N | |||||||
| #7: | Track-Größe | |||||||
| #8: | GAP3 | |||||||
| #9: | DTL | |||||||
Result Phase:
| #1: | ST0 |
| #2: | ST1 |
| #3: | ST2 |
| #4: | Cylinder |
| #5: | Head |
| #6: | Sektor |
| #7: | Sektorgröße N |
Anmerkungen:
- Dieser Befehl ließt alle Sektoren eines Tracks hintereinander ein
- Der Befehl funktioniert nur für eine Seite der Diskette, für beide Seiten muss der Befehl mehrmals gesendet werden
- Es ist immer schneller einen gesamten Track zu lesen anstatt einzeln jeden Sektor des Tracks.
Andere
Der FDC kennt noch weitere Befehle, auf die hier nicht weiter eingegangen wird. Diese sind:
- Gelöschten Sektor lesen / schreiben
- Track formatieren
- Sektor ID einlesen
Darüber hinaus gibt es noch erweiterte Befehle. Man kann sich aber nicht darauf verlassen, dass der eingebaute FDC diese unterstützt:
- Register Zusammenfassung
- Controller Version lesen
- Bestätigen
- Relatives Positionieren des Schreib-/Lesekopfes
Beispielcode
Erkennen von Diskettenlaufwerken
Zum Erkennen von den an den Computer angeschlossenen Diskettenlaufwerken gibt es zwei Möglichkeiten:
- Im Real Mode das BIOS fragen (in 0x13, ah=8), siehe dazu Interrupt 13h
- Im CMOS nachschauen (Offset 0x10), hier sind allerdings keine technischen Daten abfragbar, sondern nur der Typ des Laufwerks
Daten/Befehle schreiben
Bevor man Daten bzw. Befehle ins Datenregister schreiben darf, muss man im MSR testen, ob das DIO-Bit gelöscht und das MRQ-Bit gesetzt ist. Erst dann ist das Datenregister leer und bereit Daten zu empfangen.
#define NO_ERROR = 0; #define ERROR_TIMEOUT = 1; BYTE error=NO_ERROR; VOID send_cmd(BYTE cmd) { send_data(cmd); } VOID send_data(BYTE data) { BYTE timeout; for (timeout=0; timeout<200; timeout++) { if ( (inb(MSR) & (MSR_MASK_DATAREG|MSR_MASK_DIO2CPU)) == MSR_MASK_DATAREG) { outb(CMD,cmd); error = NO_ERROR; return; } sleep(5); //5ms warten vor dem nächsten Versuch } error = ERROR_TIMEOUT; return; }
Daten lesen
Ähnlich wie beim Senden von Daten müssen beim Lesen das MRQ-Bit und DIO-Bit gesetzt sein.
BYTE read_data() { BYTE timeout; for(timeout=0; timeout<200; timeout++) { if( (inb(MSR) & (MSR_MASK_DATAREG|MSR_MASK_DIO2CPU)) == (MSR_MASK_DATAREG|MSR_MASK_DIO2CPU)) { error = NO_ERROR; return inb(DATA); } sleep(5); } error = ERROR_TIMEOUT; return 0; }
Motor starten/anhalten
VOID start_motor(BYTE n) { if (!drive_states[n].motor_on) { outb(DOR,n | (0x01<<(n+4)) | DOR_MASK_NRESET | DOR_MASK_DMA); } } VOID stop_motor(BYTE n) { if (drive_states[n].motor_on) { outb(DOR,n | DOR_MASK_NRESET | DOR_MASK_DMA); } }
Laufwerk kallibrieren
VOID calibrate_drive(BYTE drive) { ...TODO
Schreib-/Lesekopf postionieren
BOOL seek_head(BYTE cyl, BYTE head, BYTE sector) { ...TODO
Reset
VOID reset() { int i; outb(DOR, 0); sleep(10); outb(DOR, DOR_MASK_NRESET | DOR_MASK_DMA); wait_irq(); for(i=0; i<4; i++) { check_interrupt_status(); } outb(CCR, 0); /* TODO: zusätzlich sollten jetzt noch die Laufwerke kallibriert werden */ }
Sektoren lesen/schreiben
Die folgende Funktion nutzt Code aus dem DMA Artikel:
#define CMD_WRITE_SECTOR (0x05 | 0x40) #define CMD_READ_SECTOR (0x06 | 0x40) BOOL transfer_sector(BYTE drive, BYTE cylinder, BYTE head, BYTE sector, BOOL write, VOID* buffer) { int i; select_drive(drive); //TODO start_motor(drive); //TODO seek_head(drive); //TODO dma_begin_transfer(buffer, write); for (i=0; i<5; i++) { send_command(wrie ? CMD_WRIE_SECTOR : CMD_READ_SECTOR); send_data(drive | head<<2) ); send_data(cylinder); send_data(head); send_data(sector); send_data(2); send_data(18); send_data(27); send_data(0xFF); wait_irq(); read_data(); //st0 read_data(); //st1 read_data(); //st2 read_data(); //cylinder read_data(); //head read_data(); //sector read_data(); //Sektorgröße if ( inb(MSR) & 0xC0) { check_interrupt_status(); return TRUE; } } /* fünf Fehlversuche */ return FALSE; }
Siehe auch
Interrupt 13h - Disketten- und Festplattenzugriff

