/*
* fdc.c
*
* floppy controller handler functions
*
* Copyright (C) 1998 Fabian Nunez
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* The author can be reached by email at: fabian@cs.uct.ac.za
*
* or by airmail at: Fabian Nunez
* 10 Eastbrooke
* Highstead Road
* Rondebosch 7700
* South Africa
*/
#include <kernel/kernel.h>
#include <kernel/driver.h>
#include <kernel/sys.h>
#include <kernel/cache.h>
#include <stdio.h>
#include <os/blkdev.h>
#include <errno.h>
#include "util.h"
#include "fdc.h"
#include <kernel/debug.h>
//! \ingroup fdc
//!@{
/* globals */
struct Fdc
{
device_t dev;
volatile bool done;
bool dchange;
bool motor;
dword motor_end;
byte status[7];
byte statsz;
byte sr0;
byte fdc_track;
DrvGeom geometry;
long tbaddr; /* physical address of track buffer located below 1M */
};
/* prototypes */
void sendbyte(byte b);
unsigned getbyte();
void int1c(Fdc* fdc);
bool fdcWait(Fdc* fdc, bool sensei);
bool fdc_rw(Fdc* fdc, int block,byte *blockbuff,bool read);
/* helper functions */
/* deinit driver */
void fdcCleanup(Fdc* fdc)
{
//set_irq_handler(6,NULL,&oldirq6);
//wprintf(L"uninstalling IRQ6 handler\n");
//set_irq_handler(0x1c,NULL,&oldint1c);
//wprintf(L"uninstalling timer handler\n");
devRegisterIrq(&fdc->dev, 6, false);
devRegisterIrq(&fdc->dev, 0, false);
/* stop motor forcefully */
out(FDC_DOR, FDC_DOR_ENABLE | FDC_DOR_IRQIO);
}
/* sendbyte() routine from intel manual */
void sendbyte(byte b)
{
volatile int msr;
int tmo;
for (tmo = 0;tmo < 128;tmo++) {
msr = in(FDC_MSR);
if ((msr & 0xc0) == 0x80) {
out(FDC_DATA, b);
return;
}
in(0x80); /* delay */
}
}
/* getbyte() routine from intel manual */
unsigned getbyte()
{
volatile int msr;
int tmo;
for (tmo = 0;tmo < 128;tmo++) {
msr = in(FDC_MSR);
if ((msr & 0xd0) == 0xd0) {
return in(FDC_DATA);
}
in(0x80); /* delay */
}
return (unsigned) -1; /* read timeout */
}
/* this waits for FDC command to complete */
bool fdcWait(Fdc* fdc, bool sensei)
{
dword timeout_end = sysUpTime() + 1000; /* set timeout to 1 second */
bool failed;
failed = false;
/* wait for IRQ6 handler to signal command finished */
enable();
while (!fdc->done)
if (sysUpTime() > timeout_end)
{
failed = true;
break;
}
/* read in command result bytes */
fdc->statsz = 0;
while ((fdc->statsz < 7) && (in(FDC_MSR) & (1<<4))) {
fdc->status[fdc->statsz++] = getbyte();
}
if (sensei) {
/* send a "sense interrupt status" command */
sendbyte(CMD_SENSEI);
fdc->sr0 = getbyte();
fdc->fdc_track = getbyte();
}
fdc->done = false;
if (failed) {
/* timed out! */
if (in(FDC_DIR) & 0x80) /* check for diskchange */
fdc->dchange = true;
return false;
} else
return true;
}
/*
* converts linear block address to head/track/sector
*
* blocks are numbered 0..heads*tracks*spt-1
* blocks 0..spt-1 are serviced by head #0
* blocks spt..spt*2-1 are serviced by head 1
*
* WARNING: garbage in == garbage out
*/
void block2hts(Fdc* fdc, int block,int *head,int *track,int *sector)
{
*head = (block % (fdc->geometry.spt * fdc->geometry.heads)) / (fdc->geometry.spt);
*track = block / (fdc->geometry.spt * fdc->geometry.heads);
*sector = block % fdc->geometry.spt + 1;
}
/**** disk operations ****/
/* this gets the FDC to a known state */
void fdcReset(Fdc* fdc)
{
/* stop the motor and disable IRQ/DMA */
out(FDC_DOR, 0);
fdc->motor_end = -1;
fdc->motor = false;
/* program data rate (500K/s) */
//out(FDC_DRS,0);
/* re-enable interrupts */
out(FDC_DOR, FDC_DOR_ENABLE | FDC_DOR_IRQIO);
/* resetting triggered an interrupt - handle it */
fdc->done = true;
fdcWait(fdc, true);
/* specify drive timings (got these off the BIOS) */
sendbyte(CMD_SPECIFY);
sendbyte(0xdf); /* SRT = 3ms, HUT = 240ms */
sendbyte(0x02); /* HLT = 16ms, ND = 0 */
/* clear "disk change" status */
fdcSeek(fdc, 1);
fdcRecalibrate(fdc);
fdc->dchange = false;
}
/* this returns whether there was a disk change */
bool fdcDiskChange(Fdc* fdc)
{
return fdc->dchange;
}
/* this turns the motor on */
void fdcMotorOn(Fdc* fdc)
{
if (!fdc->motor)
{
out(FDC_DOR, FDC_DOR_MOTOR0 | FDC_DOR_ENABLE | FDC_DOR_IRQIO);
msleep(500); /* delay 500ms for motor to spin up */
fdc->motor = true;
//TRACE0("floppy: motor on\n");
}
fdc->motor_end = -1; /* stop motor kill countdown */
}
/* this turns the motor off */
void fdcMotorOff(Fdc* fdc)
{
if (fdc->motor) {
fdc->motor_end = sysUpTime() + 2000; /* start motor kill countdown: 36 ticks ~ 2s */
}
}
/* recalibrate the drive */
void fdcRecalibrate(Fdc* fdc)
{
/* turn the motor on */
fdcMotorOn(fdc);
/* send actual command bytes */
sendbyte(CMD_RECAL);
sendbyte(0);
/* wait until seek finished */
fdcWait(fdc, true);
/* turn the motor off */
fdcMotorOff(fdc);
}
/* seek to track */
bool fdcSeek(Fdc* fdc, int track)
{
if (fdc->fdc_track == track) /* already there? */
return true;
fdcMotorOn(fdc);
/* send actual command bytes */
sendbyte(CMD_SEEK);
sendbyte(0);
sendbyte(track);
/* wait until seek finished */
if (!fdcWait(fdc, true))
return false; /* timeout! */
/* now let head settle for 15ms */
msleep(15);
fdcMotorOff(fdc);
/* check that seek worked */
if ((fdc->sr0 != 0x20) || (fdc->fdc_track != track))
return false;
else
return true;
}
/* checks drive geometry - call this after any disk change */
bool fdcLogDisk(Fdc* fdc, DrvGeom *g)
{
/* get drive in a known status before we do anything */
fdcReset(fdc);
/* assume disk is 1.68M and try and read block #21 on first track */
fdc->geometry.heads = DG168_HEADS;
fdc->geometry.tracks = DG168_TRACKS;
fdc->geometry.spt = DG168_SPT;
if (fdcReadBlock(fdc, 20,NULL)) {
/* disk is a 1.68M disk */
if (g) {
g->heads = fdc->geometry.heads;
g->tracks = fdc->geometry.tracks;
g->spt = fdc->geometry.spt;
}
return true;
}
/* OK, not 1.68M - try again for 1.44M reading block #18 on first track */
fdc->geometry.heads = DG144_HEADS;
fdc->geometry.tracks = DG144_TRACKS;
fdc->geometry.spt = DG144_SPT;
if (fdcReadBlock(fdc, 17,NULL)) {
/* disk is a 1.44M disk */
if (g) {
g->heads = fdc->geometry.heads;
g->tracks = fdc->geometry.tracks;
g->spt = fdc->geometry.spt;
}
return true;
}
/* it's not 1.44M or 1.68M - we don't support it */
return false;
}
/* read block (blockbuff is 512 byte buffer) */
bool fdcReadBlock(Fdc* fdc, int block,byte *blockbuff)
{
return fdc_rw(fdc, block,blockbuff,true);
}
/* write block (blockbuff is 512 byte buffer) */
bool fdcWriteBlock(Fdc* fdc, int block,byte *blockbuff)
{
return fdc_rw(fdc, block,blockbuff,false);
}
/*
* since reads and writes differ only by a few lines, this handles both. This
* function is called by read_block() and write_block()
*/
bool fdc_rw(Fdc* fdc, int block,byte *blockbuff,bool read)
{
int head,track,sector,tries,i;
/* convert logical address into physical address */
block2hts(fdc, block,&head,&track,§or);
TRACE4("block %d = %d:%02d:%02d\n",block,head,track,sector);
/* spin up the disk */
fdcMotorOn(fdc);
if (!read && blockbuff)
{
/* copy data from data buffer into track buffer */
//movedata(_my_ds(),(long)blockbuff,_dos_ds,tbaddr,512);
memcpy((void*) fdc->tbaddr, blockbuff, 512);
}
for (tries = 0;tries < 3;tries++)
{
TRACE1("attempt %d\t", tries);
/* check for diskchange */
if (in(FDC_DIR) & 0x80)
{
fdc->dchange = true;
fdcSeek(fdc, 1); /* clear "disk change" status */
fdcRecalibrate(fdc);
fdcMotorOff(fdc);
return false;
}
/* move head to right track */
TRACE0("seek\t");
if (!fdcSeek(fdc, track))
{
fdcMotorOff(fdc);
return false;
}
/* program data rate (500K/s) */
out(FDC_CCR, FDC_CCR_500K);
/* send command */
if (read)
{
TRACE0("dma\t");
dma_xfer(2,fdc->tbaddr,512,false);
TRACE0("read\t");
sendbyte(CMD_READ);
} else
{
TRACE0("dma\t");
dma_xfer(2,fdc->tbaddr,512,true);
TRACE0("write\t");
sendbyte(CMD_WRITE);
}
sendbyte(head << 2);
sendbyte(track);
sendbyte(head);
sendbyte(sector);
sendbyte(2); /* 512 bytes/sector */
sendbyte(fdc->geometry.spt);
if (fdc->geometry.spt == DG144_SPT)
sendbyte(DG144_GAP3RW); /* gap 3 size for 1.44M read/write */
else
sendbyte(DG168_GAP3RW); /* gap 3 size for 1.68M read/write */
sendbyte(0xff); /* DTL = unused */
/* wait for command completion */
/* read/write don't need "sense interrupt status" */
TRACE0("wait\t");
if (!fdcWait(fdc, false))
{
TRACE0("failed\n");
return false; /* timed out! */
}
TRACE0("finished\n");
if ((fdc->status[0] & 0xc0) == 0) break; /* worked! outta here! */
fdcRecalibrate(fdc); /* oops, try again... */
}
/* stop the motor */
fdcMotorOff(fdc);
if (read && blockbuff) {
/* copy data from track buffer into data buffer */
//movedata(_dos_ds,tbaddr,_my_ds(),(long)blockbuff,512);
memcpy(blockbuff, (void*) fdc->tbaddr, 512);
}
TRACE0("status bytes: ");
for (i = 0;i < fdc->statsz;i++)
TRACE1("%02x ",fdc->status[i]);
TRACE0("\n");
return (tries != 3);
}
/* this formats a track, given a certain geometry */
bool fdcFormatTrack(Fdc* fdc, byte track,DrvGeom *g)
{
int i,h,r,r_id,split;
byte tmpbuff[256];
/* check geometry */
if (g->spt != DG144_SPT && g->spt != DG168_SPT)
return false;
/* spin up the disk */
fdcMotorOn(fdc);
/* program data rate (500K/s) */
out(FDC_CCR,0);
fdcSeek(fdc, track); /* seek to track */
/* precalc some constants for interleave calculation */
split = g->spt / 2;
if (g->spt & 1) split++;
for (h = 0;h < g->heads;h++) {
/* for each head... */
/* check for diskchange */
if (in(FDC_DIR) & 0x80) {
fdc->dchange = true;
fdcSeek(fdc, 1); /* clear "disk change" status */
fdcRecalibrate(fdc);
fdcMotorOff(fdc);
return false;
}
i = 0; /* reset buffer index */
for (r = 0;r < g->spt;r++) {
/* for each sector... */
/* calculate 1:2 interleave (seems optimal in my system) */
r_id = r / 2 + 1;
if (r & 1) r_id += split;
/* add some head skew (2 sectors should be enough) */
if (h & 1) {
r_id -= 2;
if (r_id < 1) r_id += g->spt;
}
/* add some track skew (1/2 a revolution) */
if (track & 1) {
r_id -= g->spt / 2;
if (r_id < 1) r_id += g->spt;
}
/**** interleave now calculated - sector ID is stored in r_id ****/
/* fill in sector ID's */
tmpbuff[i++] = track;
tmpbuff[i++] = h;
tmpbuff[i++] = r_id;
tmpbuff[i++] = 2;
}
/* copy sector ID's to track buffer */
//movedata(_my_ds(),(long)tmpbuff,_dos_ds,tbaddr,i);
memcpy((void*) fdc->tbaddr, tmpbuff, i);
/* start dma xfer */
dma_xfer(2,fdc->tbaddr,i,true);
/* prepare "format track" command */
sendbyte(CMD_FORMAT);
sendbyte(h << 2);
sendbyte(2);
sendbyte(g->spt);
if (g->spt == DG144_SPT)
sendbyte(DG144_GAP3FMT); /* gap3 size for 1.44M format */
else
sendbyte(DG168_GAP3FMT); /* gap3 size for 1.68M format */
sendbyte(0); /* filler byte */
/* wait for command to finish */
if (!fdcWait(fdc, false))
return false;
if (fdc->status[0] & 0xc0) {
fdcMotorOff(fdc);
return false;
}
}
fdcMotorOff(fdc);
return true;
}
bool fdcRequest(device_t* dev, request_t* req)
{
Fdc* fdc = (Fdc*) dev;
int tries;
DrvGeom geom;
dword pos;
block_size_t* size;
//if (req->code != DEV_ISR)
//TRACE2("#%c%c", req->code / 256, req->code % 256);
switch (req->code)
{
case DEV_REMOVE:
fdcCleanup(fdc);
hndFree(fdc);
case DEV_OPEN:
case DEV_CLOSE:
hndSignal(req->event, true);
return true;
case DEV_ISR:
switch (req->params.isr.irq)
{
case 0:
if (fdc->motor_end != -1 &&
sysUpTime() >= fdc->motor_end &&
fdc->motor)
{
//TRACE0("floppy: turning off motor\n");
out(FDC_DOR, FDC_DOR_ENABLE | FDC_DOR_IRQIO); /* turn off floppy motor */
fdc->motor = false;
fdc->motor_end = -1;
}
return false;
case 6:
fdc->done = true;
return true;
}
return false;
case DEV_READ:
case DEV_WRITE:
if (req->params.buffered.length < 512)
{
req->result = EINVALID;
return false;
}
req->user_length = req->params.buffered.length;
req->params.buffered.length = 0;
pos = req->params.buffered.pos / 512;
while (req->params.buffered.length < req->user_length)
{
for (tries = 0; tries < 2; tries++)
{
if (fdc_rw(fdc,
pos,
(byte*) req->params.buffered.buffer +
req->params.buffered.length,
req->code == DEV_READ))
{
pos++;
req->params.buffered.length += 512;
break;
}
else if (!fdcLogDisk(fdc, &geom))
{
req->result = EINVALID;
return false;
}
else
TRACE3("floppy: new disk geometry: %%d:%02d:%02d\n",
geom.heads, geom.tracks, geom.spt);
}
}
hndSignal(req->event, true);
return true;
case BLK_GETSIZE:
size = (block_size_t*) req->params.buffered.buffer;
size->block_size = 512;
size->total_blocks =
fdc->geometry.heads * fdc->geometry.tracks * fdc->geometry.spt;
hndSignal(req->event, true);
return true;
}
req->result = ENOTIMPL;
return false;
}
device_t* fdcAddDevice(driver_t* drv, const wchar_t* name, device_config_t* cfg)
{
Fdc *fdc;
int i;
fdc = hndAlloc(sizeof(Fdc), NULL);
memset(fdc, 0, sizeof(Fdc));
fdc->dev.request = fdcRequest;
fdc->fdc_track = 0xff;
fdc->geometry.heads = DG144_HEADS;
fdc->geometry.tracks = DG144_TRACKS;
fdc->geometry.spt = DG144_SPT;
devRegisterIrq(&fdc->dev, 6, true);
devRegisterIrq(&fdc->dev, 0, true);
/* allocate track buffer (must be located below 1M) */
fdc->tbaddr = alloc_dma_buffer();
fdcReset(fdc);
/* get floppy controller version */
sendbyte(CMD_VERSION);
i = getbyte();
if (i == 0x80)
wprintf(L"NEC765 controller found\n");
else
wprintf(L"enhanced controller found\n");
return ccInstallBlockCache(&fdc->dev, 512);
//return &fdc->dev;
}
bool STDCALL INIT_CODE drvInit(driver_t* drv)
{
drv->add_device = fdcAddDevice;
return true;
}
//@}