/* Pilote pour le controleur de disquette Intel 82077AA */ /* Copyright (C) 2008 Emmanuel Varoquaux This file is part of XOS. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Le pilote supporte uniquement des disquettes de type 1.44M. * Seul le moteur 0 est pris en charge. */ #include "floppy_motor.h" #include "floppy_reg.h" #include "dma.h" #include #include #include #include #include #include #include #include #include #include #include #include /* commandes du controleur */ #define FD_READ_DATA 0xe6 /* lecture avec MT, MFM, SK */ #define FD_WRITE_DATA 0xc5 /* ecriture avec MT, MFM */ #define FD_VERSION 0x10 /* version */ #define FD_RECALIBRATE 0x07 /* recalibrage */ #define FD_SENSE_INTERRUPT_STATUS 0x08 /* registre d'etat */ #define FD_SPECIFY 0x03 /* specifications */ #define FD_SENSE_DRIVE_STATUS 0x04 /* etat du lecteur de disquettes */ #define FD_SEEK 0x0f /* positionnement */ #define FD_CONFIGURE 0x13 /* configuration */ /* geometrie et caracteristiques de la disquette 1.44M */ #define SIZE 2880 /* capacite totale */ #define SECTORS 18 /* nombre de secteurs par piste */ #define HEADS 2 /* nombre de tetes de lecture */ #define SECTOR_SIZE 512 /* taille d'un secteur */ #define GAP 0x1b #define RATE 0x00 #define SPEC 0xcf /* type du controleur */ #define FD_8272A 0x80 #define FD_82077AA 0x90 #define BLOCK_COUNT (SIZE * SECTOR_SIZE / BLOCK_SIZE) /* requete pour les lectures et ecritures */ struct rw_request_struct { unsigned char head; unsigned char track; unsigned char sector; unsigned int count; }; /* indicateurs */ static int need_reset; static int disk_change; static int write_protected; /* lecteur */ static unsigned char current_track; static struct mutex_struct drive_mutex; /* resultats */ #define MAX_RESULTS 7 static int result_phase; static int result_status; static unsigned int result_count; static unsigned char results[MAX_RESULTS]; /* etats d'attente d'interruption */ enum { INTERRUPT_WAITING, INTERRUPT_RECEIVED, INTERRUPT_TIMEOUT }; /* interruption */ static int interrupt_status; static struct condition_struct interrupt_condition; static struct alarm_struct interrupt_alarm; /* version du controleur */ static unsigned char fd_version; static inline const char *get_version_str(unsigned char version) { switch (version) { case FD_82077AA: return "82077AA"; case FD_8272A: default: return "8272A"; } } /* Utilitaires pour les commandes */ /* DS p39 */ static int send_byte(unsigned char byte) { int counter; for (counter = 0; counter < 10000; counter++) if ((inbp(FD_MSR) & 0xc0) == 0x80) { outbp(byte, FD_FIFO); return 1; } return 0; } /* DS p40 */ static int get_byte(unsigned char *byte) { int counter; for (counter = 0; counter < 10000; counter++) if ((inbp(FD_MSR) & 0xc0) == 0xc0) { *byte = inbp(FD_FIFO); return 1; } return 0; } static int read_results() { int counter; int status; result_count = 0; for (counter = 0; counter < 10000; counter++) { status = inbp(FD_MSR) & 0xd0; if (status == 0x80) return 1; if (status == 0xd0) { if (result_count == MAX_RESULTS) return 0; if (!get_byte(&results[result_count++])) return 0; } } return 0; } /* Interruption */ void do_floppy_interrupt() { /* Il est necessaire de lire les resultats pour qu'une nouvelle commande puisse etre executee. */ if (result_phase) { result_status = read_results(); result_phase = 0; } if (interrupt_status != INTERRUPT_WAITING) return; /* trop tard */ interrupt_status = INTERRUPT_RECEIVED; alarm_unset(&interrupt_alarm); condition_signal(&interrupt_condition); } static void interrupt_alarm_handler() { if (interrupt_status != INTERRUPT_WAITING) return; /* l'interruption a deja ete recue */ interrupt_status = INTERRUPT_TIMEOUT; condition_signal(&interrupt_condition); } /* Les interruptions doivent etre desactivees, et sont reactivees par cette * fonction. */ static void wait_for_interrupt() { interrupt_status = INTERRUPT_WAITING; alarm_set(&interrupt_alarm, 3000); /* timeout de 3s */ condition_wait(&interrupt_condition); sti(); } /* Commandes */ /* Les commandes qui ont une phase d'execution attendent sa completion sur * interrupt_condition. Il est necessaire de desactiver les interruptions entre * l'envoi de la commande au controleur et l'attente sur la condition, afin que * l'interruption ne se produise pas dans cet intervalle. */ /* DS p18,24-26,43 */ static int do_rw_data(unsigned char command, const struct rw_request_struct *request) { cli(); if (!send_byte(command) /* commande */ || !send_byte(request->head << 2) /* head, drive */ || !send_byte(request->track) /* cylinder address */ || !send_byte(request->head) /* head address */ || !send_byte(request->sector) /* sector address */ || !send_byte(2) /* sector size code (512 bytes) */ || !send_byte(SECTORS) /* end of track */ || !send_byte(GAP) /* gap length */ || !send_byte(0xff)) /* special sector size */ goto error_sti; result_phase = 1; wait_for_interrupt(); if (interrupt_status == INTERRUPT_TIMEOUT) return 0; if (!result_status) return 0; if (result_count != 7) return 0; write_protected |= results[1] & 0x2; /* Not Writable */ return !(results[0] & 0xf8) && !(results[1] & 0xb7) && !(results[2] & 0x73); error_sti: sti(); return 0; } /* DS p19,32 */ static int do_version() { if (!send_byte(FD_VERSION)) /* VERSION */ return 0; if (!read_results()) return 0; if (result_count != 1) return 0; fd_version = results[0]; return 1; } /* DS p21,30,42 */ static int do_recalibrate() { cli(); if (!send_byte(FD_RECALIBRATE) /* RECALIBRATE */ || !send_byte(0x00)) /* drive */ goto error_sti; result_phase = 0; wait_for_interrupt(); if (interrupt_status == INTERRUPT_TIMEOUT) return 0; return 1; error_sti: sti(); return 0; } /* DS p31,31 */ static int do_sense_interrupt_status(int polling) { if (!send_byte(FD_SENSE_INTERRUPT_STATUS)) /* SENSE INTERRUPT STATUS */ return 0; if (!read_results()) return 0; if (result_count != 2) return 0; current_track = results[1]; return (results[0] & 0xf8) == (polling ? 0xc0 : 0x20); } /* DS p21,31,42 */ static int do_specify() { if (!send_byte(FD_SPECIFY) /* SPECIFY */ || !send_byte(SPEC) /* SRT=4ms, HUT=240ms */ || !send_byte(0x06)) /* HLT=6ms, DMA */ return 0; return 1; } /* DS p21,31 */ static int do_sense_drive_status(unsigned char head) { if (!send_byte(FD_SENSE_DRIVE_STATUS) /* SENSE DRIVE STATUS */ || !send_byte(head << 2)) /* head, drive */ return 0; if (!read_results()) return 0; if (result_count != 1) return 0; write_protected = results[0] & 0x40; /* Write Protected */ return 1; } /* DS p21,30,42 */ static int do_seek(unsigned char head, unsigned char track) { cli(); if (!send_byte(FD_SEEK) /* SEEK */ || !send_byte(head << 2) /* head, drive */ || !send_byte(track)) /* new cylinder number */ goto error_sti; result_phase = 0; wait_for_interrupt(); if (interrupt_status == INTERRUPT_TIMEOUT) return 0; return 1; error_sti: sti(); return 0; } /* DS p21,32 */ static int do_configure() { if (!send_byte(FD_CONFIGURE) /* CONFIGURE */ || !send_byte(0) || !send_byte(0x1a) /* no implied seek, FIFO enabled, polling disabled, 10 byte threshold */ || !send_byte(0)) /* precompensation from track 0 */ return 0; return 1; } /* Utilitaires pout les operations */ static void enable_drive() { floppy_motor_enable(); if (!disk_change) disk_change = inb(FD_DIR) & 0x80; /* DSKCHG */ } /* Operations */ /* DS p40 */ static int initialize() { int i; /* RESET */ cli(); outbp(inbp(FD_DOR) & ~0x04, FD_DOR); for (i = 0; i < 100; i++) nop(); outbp(inbp(FD_DOR) | 0x04, FD_DOR); /* reset */ outb(RATE, FD_CCR); /* taux de transfert de 500kb/s */ /* Un RESET active le mode polling, ce qui entraine une interruption (DS p32). */ result_phase = 0; wait_for_interrupt(); if (interrupt_status == INTERRUPT_TIMEOUT) return 0; /* DS p31,41 */ for (i = 0; i < 4; i++) if (!do_sense_interrupt_status(1)) return 0; if (fd_version == FD_82077AA) if (!do_configure()) return 0; if (!do_specify()) return 0; return 1; } /* DS p42 */ static int recalibrate() { enable_drive(); if (!do_recalibrate()) return 0; if (!do_sense_interrupt_status(0)) return 0; if (current_track) return 0; return 1; } /* DS p42 */ static int seek(unsigned char head, unsigned char track) { enable_drive(); if (!do_seek(head, track)) return 0; if (!do_sense_interrupt_status(0)) return 0; if (current_track != track) return 0; return 1; } /* DS p43-44 */ static int rw(unsigned char command, const struct rw_request_struct *request) { int seek_try, rw_try; enable_drive(); outb(RATE, FD_CCR); for (seek_try = 0; seek_try < 3; seek_try++) { if (request->track != current_track) seek(request->head, request->track); floppy_motor_wait_until_started(); for (rw_try = 0; rw_try < 3; rw_try++) { dma_setup(DMA_DISKETTE_ADAPTER, command == FD_READ_DATA ? DMA_WRITE : DMA_READ, (unsigned long)floppy_dma_buffer_start, request->count); if (!do_rw_data(command, request)) { if (interrupt_status == INTERRUPT_TIMEOUT) return 0; else if (write_protected) return 0; else continue; } return 1; } recalibrate(); } return 0; } /* Utilitaires pour l'interface */ static void unlock_drive(int status) { if (!status) need_reset = 1; floppy_motor_unlock(); mutex_unlock(&drive_mutex); } static int lock_drive() { if (!mutex_lock(&drive_mutex)) return 0; floppy_motor_lock(); if (need_reset) { if (!initialize()) goto error; if (!recalibrate()) goto error; need_reset = 0; } return 1; error: unlock_drive(0); return 0; } /* Interface */ int floppy_init() { need_reset = 1; disk_change = 0; /* write_protected est indetermine pour le moment. */ /* current_track est indetermine jusqu'au premier initialize(). */ mutex_init(&drive_mutex); floppy_motor_init(); result_phase = 0; /* pas de phase de resultats par defaut */ /* result_status, result_count et results sont indetermines pour le moment. */ /* interrupt_status est indetermine jusqu'a la premiere attente d'interruption. */ condition_init(&interrupt_condition); alarm_init(&interrupt_alarm, interrupt_alarm_handler); outbp(0x0c, FD_DOR); /* RESET, DMA */ /* Remarque : RESET devrait provoquer une interruption (qu'on peut ignorer) lorsque les interruptions seront demasquees, mais il semble que la commande VERSION l'annule. */ if (!do_version()) /* ok, pas de cli()..sti() */ return 0; return 1; } void floppy_print_info() { printk("Floppy disk controller: Intel %s\n", get_version_str(fd_version)); } static unsigned long floppy_get_block_count() { return BLOCK_COUNT; } static int floppy_is_writable() { int retval; if (!lock_drive()) return 0; if (!do_sense_drive_status(0)) goto error; retval = !write_protected; unlock_drive(1); return retval; error: unlock_drive(0); return 0; } static int floppy_has_changed() { int retval; if (!lock_drive()) return 0; enable_drive(); /* met a jour disk_change */ if (disk_change) { /* effacement de l'indicateur DSKCHG */ if (!recalibrate()) goto error; disk_change = 0; retval = 1; } else retval = 0; unlock_drive(1); return retval; error: unlock_drive(0); return 1; } static int floppy_rw(unsigned char command, unsigned long block, void *buffer) { unsigned long sector; unsigned int count; struct rw_request_struct request; if (block >= BLOCK_COUNT) return 0; if (!lock_drive()) return 0; sector = block * BLOCK_SIZE / SECTOR_SIZE; count = BLOCK_SIZE; do { request.head = sector / SECTORS % HEADS; request.track = sector / (SECTORS * HEADS); request.sector = sector % SECTORS + 1; request.count = min(count, (unsigned int)((HEADS * SECTORS - (request.head * SECTORS + request.sector - 1)) * SECTOR_SIZE)); /* MT active, lecture multi-piste (par cylindre) */ if (request.count > (unsigned int)(floppy_dma_buffer_end - floppy_dma_buffer_start)) request.count = floppy_dma_buffer_end - floppy_dma_buffer_start; if (command == FD_WRITE_DATA) memcpy(floppy_dma_buffer_start, buffer, request.count); if (!rw(command, &request)) goto error; if (command == FD_READ_DATA) memcpy(buffer, floppy_dma_buffer_start, request.count); buffer += request.count; sector += request.count / SECTOR_SIZE; count -= request.count; } while (count); unlock_drive(1); return 1; error: unlock_drive(0); return 0; } /* Lit 1 bloc. */ static int floppy_read(unsigned long block, void *buffer) { return floppy_rw(FD_READ_DATA, block, buffer); } /* Ecrit 1 bloc. */ static int floppy_write(unsigned long block, void *buffer) { return floppy_rw(FD_WRITE_DATA, block, buffer); } const struct block_driver_struct floppy_driver = { .get_block_count = floppy_get_block_count, .is_writable = floppy_is_writable, .has_changed = floppy_has_changed, .read = floppy_read, .write = floppy_write };