View of xos/drivers/floppy.c


XOS | Parent Directory | View | Download

/* 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 <http://www.gnu.org/licenses/>. */
 
/* 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 <block_driver_struct.h>
#include <alarm.h>
#include <alarm_struct.h>
#include <printk.h>
#include <mutex.h>
#include <mutex_struct.h>
#include <condition.h>
#include <areas.h>
#include <asm.h>
#include <config.h>
#include <util.h>
#include <string.h>
 
/* 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
};