Legal issues:
Please do not distribute this version of the document. It is still work-in-process, and I don't want different version around the web yet. You can take copies or print it for your personal use if you want, but do not otherwise distribute this version. I will make a distributable version at some point in the future.
The code is free to use, however under the following license:
Code: Select all
/******************************************************************************
* Copyright (c) 2007 Teemu Voipio *
* *
* Permission is hereby granted, free of charge, to any person obtaining a *
* copy of this software and associated documentation files (the "Software"), *
* to deal in the Software without restriction, including without limitation *
* the rights to use, copy, modify, merge, publish, distribute, sublicense, *
* and/or sell copies of the Software, and to permit persons to whom the *
* Software is furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in *
* all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
* DEALINGS IN THE SOFTWARE. *
* *
******************************************************************************/
How to read (and supposedly write) normal floppies with DMA, one cylinder at a time (though you can read/write less, if you understand the text below).
My code is in C, and assumes that you have macros in8_p(port) and out8_p(port,byte) which read and write to IO ports with your usual iodelay (dummy port access or usleep() or whatever you have).
The prerequisite here is to be able to wait for an interrupt (and in production code preferably timeout) so make sure you have working interrupt routines first. It would be possible to write the floppy driver to do it's work from the interrupt routines (Linux does something like this), but it makes code a LOT less readable, so I'll just assume that when we call a function irq_wait(number), the scheduler will go pick another thread, and will wakeup us when an interrupt has occured.
You will also need to be able to sleep() for certain amounts of time, so you also need some sort of timer support. The version of sleep() I'll be using takes it's arguments as number of ticks, which are 10ms each, but I'll put comment on each sleep() with the real time, so you don't have to remember this. The normal rule with timers is that they never wake you before the requested time has elapsed, so sleep(1) really waits for two ticks: one so we know a point in time to count from, and another so we know a full period between ticks has elapsed.. but that's how every good timer works.
Basic concepts:
Floppy addressing is done in CHS format. C means cylinder, H means head and S means sector. Each cylinder has one track for each head. Typically there are one or two heads, one for each side of the floppy. Each track has a number of sectors, which varies depending on how the floppy was formatted. For the common case of 1.44MB floppy with normal formatting, there are 80 cylinders (addressed from 0 to 79), 2 heads (0 and 1) and 18 sectors per track (from 1 to 18, yes).
To find the correct track, one issues a SEEK command for the head in question, with a cylinder number to seek for. Sectors need not be seeked, the drive will do that for you when you issue a READ or WRITE command. In order for SEEK to work, one is supposed to first calibrate the drive, with a special command, that will move the head 79 times towards track 0. This is normally necessary only after media change (and certain errors?).
Once a READ or WRITE request is issued, the drive will wait until the specified sector is under the (relevant) head, and starts reading or writing data. It normally does this using ISA DMA (specifically channel 2). It will continue to read until it gets and error, or DMA tells it's happy. Errors can be anything from end of cylinder to bad or write-protected floppy. So one only needs to tell the floppy controller where to start, and DMA takes care of the rest, assuming everything goes right.
The important thing here is that DMA controls how much data we transfer. Bochs happens to be kind enough to not report an error if you run into the end of track before DMA says it's time to stop, but real hardware will (at least mine does). We'll get to the DMA details (which several sources actually get wrong) later.
Commands, registers and all that stuff:
There can be several floppy controllers, each with up to four drives. On most modern computers you'll find one controller, with one or two drives (=one cable), so whether or not you want to support more controllers is up to you. All of them will normally use the same DMA 8-bit channel (2), and the same IRQ (6). This obviously means that you can only access one drive at any given time.
Following is a list of the registers of the floppy controller, relative to the base address, and a list of commands. I will only list those commands and registers that we'll be using. You can get the rest from various sources of documentation.
Code: Select all
// standard base address of the primary floppy controller
static const int floppy_base = 0x03f0;
// standard IRQ number for floppy controllers
static const int floppy_irq = 6;
// The registers of interest. There are more, but we only use these here.
enum floppy_registers {
FLOPPY_DOR = 2, // digital output register
FLOPPY_MSR = 4, // master status register, read only
FLOPPY_FIFO = 5, // data FIFO, in DMA operation for commands
FLOPPY_CCR = 7 // configuration control register, write only
};
// The commands of interest. There are more, but we only use these here.
enum floppy_commands {
CMD_SPECIFY = 3, // SPECIFY
CMD_WRITE_DATA = 5, // WRITE DATA
CMD_READ_DATA = 6, // READ DATA
CMD_RECALIBRATE = 7, // RECALIBRATE
CMD_SENSE_INTERRUPT = 8, // SENSE INTERRUPT
CMD_SEEK = 15, // SEEK
};
There are various ways to figure out what kind of drive you have. We'll use the simplest method here: ask CMOS. So the drive detection looks like this:
Code: Select all
static const char * drive_types[8] = {
"none",
"360kB 5.25\"",
"1.2MB 5.25\"",
"720kB 3.5\"",
"1.44MB 3.5\"",
"2.88MB 3.5\"",
"unknown type",
"unknown type"
};
// Obviously you'd have this return the data, start drivers or something.
void floppy_detect_drives() {
out8_p(0x70, 0x10);
unsigned drives = in8_p(0x71);
printk(" - Floppy drive 0: %s\n", drive_types[drives >> 4]);
printk(" - Floppy drive 1: %s\n", drive_types[drives & 0xf]);
}
Writing commands and reading results
These are simple helper functions, since we'll have to check the MSR register in order to know if the controller is ready. I'll give the full meaning of the MSR as well, so you can have documentation together with the code to avoid having to lookup a lot of information later if you wonder what the details are.
Code: Select all
//
// The MSR byte: [read-only]
// -------------
//
// 7 6 5 4 3 2 1 0
// MRQ DIO NDMA BUSY ACTD ACTC ACTB ACTA
//
// MRQ is 1 when FIFO is ready (test before read/write)
// DIO tells if controller expects write (1) or read (0)
//
// NDMA tells if controller is in DMA mode (1 = no-DMA, 0 = DMA)
// BUSY tells if controller is executing a command (1=busy)
//
// ACTA, ACTB, ACTC, ACTD tell which drives position/calibrate (1=yes)
//
//
void floppy_write_cmd(int base, char cmd) {
int i; // do timeout, 60 seconds
for(i = 0; i < 600; i++) {
timer_sleep(1); // sleep 10 ms
if(0x80 & in8_p(base+FLOPPY_MSR)) {
return (void) out8_p(base+FLOPPY_FIFO, cmd);
}
}
panic("floppy_write_cmd: timeout");
}
unsigned char floppy_read_data(int base) {
int i; // do timeout, 60 seconds
for(i = 0; i < 600; i++) {
timer_sleep(1); // sleep 10 ms
if(0x80 & in8_p(base+FLOPPY_MSR)) {
return in8_p(base+FLOPPY_FIFO);
}
}
panic("floppy_read_data: timeout");
return 0; // not reached
}
Initialization
The FLOPPY_DOR registers controls various things, including whether the controller is enabled. It also controls the state of motors for all 4 drives. One of the bits is NOT_RESET which must be 1 for the controller to be enabled. So in order to reset the controller, one pulls all the bits to 0, then sets the NOT_RESET bit. We'll also set the DMA bit to enable interrupts and DMA. You'll need that to get interrupts even if you don't care for DMA (which is the simplest method anyway). We'll see the details of the register later...
Once the controller has been re-enabled, you'll get an interrupt, and you have to send a SENSE_INTERRUPT to get status from the controller. Note that you can't blindly do SENSE_INTERRUPT after each interrupt, because after READ/WRITE you'll get an interrupt but they have their own special way of reporting status (with more information). However we'll do SENSE_INTERRUPT from a couple of places, so make it a separate function. It needs to return two pieces of information, so we'll use output parameters. Here we just ignore the results (though you could check for error in st0).
We'll then set transfer speed in CCR. Most bits need to be zero, so valid values are 0=500kbps, 1=300kbps, 2=250kbps and 3=1Mbps. For 1.44MB floppies the correct setting is 00=500kbps. Other settings can be found for example in from http://www.isdaman.com/alsos/hardware/fdc/floppy.htm but we'll ignore such details.
You're also supposed to send a SPECIFY command, specifying mechanical timing such as "steprate", "head unload time" and "head load time". SPECIFY also sets whether we really wanna use DMA, or if we just set DMA bit in DOR for the purpose of having interrupts. One way to get the timings is to ask BIOS. Another way is to calculate them yourself to have values that you hope the drive to be able to do with. The simplest thing is to hardcode something that's likely to be sane (which I'll do here, for simplicity).
Finally the drive needs to be calibrated, so that it knows on which cylinder it's on. I'll skip the mechanical details (the controller knows) but after successful calibration we'll be at track 0. If we are not, then we'll retry a few times (my retry counts are seriously overkill). If we can't calibrate at all, then there's probably no floppy in the drive... or it's time to find a new floppy. Once we're done, the drive is ready to serve us.
Notice that curiously the calibration command doesn't take a head-number so supposedly it calibrates both heads at once? No idea, but it really does not take a head-number.
Code: Select all
void floppy_check_interrupt(int base, int *st0, int *cyl) {
floppy_write_cmd(base, CMD_SENSE_INTERRUPT);
*st0 = floppy_read_data(base);
*cyl = floppy_read_data(base);
}
// Move to cylinder 0, which calibrates the drive..
int floppy_calibrate(int base) {
int i, st0, cyl = -1; // set to bogus cylinder
floppy_motor(base, floppy_motor_on);
for(i = 0; i < 10; i++) {
// Attempt to positions head to cylinder 0
floppy_write_cmd(base, CMD_RECALIBRATE);
floppy_write_cmd(base, 0); // argument is drive, we only support 0
irq_wait(floppy_irq);
floppy_check_interrupt(base, &st0, &cyl);
if(st0 & 0xC0) {
static const char * status[] =
{ 0, "error", "invalid", "drive" };
printk("floppy_calibrate: status = %s\n", status[st0 >> 6]);
continue;
}
if(!cyl) { // found cylinder 0 ?
floppy_motor(base, floppy_motor_off);
return 0;
}
}
printk("floppy_calibrate: 10 retries exhausted\n");
floppy_motor(base, floppy_motor_off);
return -1;
}
int floppy_reset(int base) {
out8_p(base + FLOPPY_DOR, 0x00); // disable controller
out8_p(base + FLOPPY_DOR, 0x0C); // enable controller
irq_wait(floppy_irq); // sleep until interrupt occurs
{
int st0, cyl; // ignore these here..
floppy_check_interrupt(base, &st0, &cyl);
}
// set transfer speed 500kb/s
out8_p(base + FLOPPY_CCR, 0x00);
// - 1st byte is: bits[7:4] = steprate, bits[3:0] = head unload time
// - 2nd byte is: bits[7:1] = head load time, bit[0] = no-DMA
//
// steprate = (8.0ms - entry*0.5ms)*(1MB/s / xfer_rate)
// head_unload = 8ms * entry * (1MB/s / xfer_rate), where entry 0 -> 16
// head_load = 1ms * entry * (1MB/s / xfer_rate), where entry 0 -> 128
//
floppy_write_cmd(base, CMD_SPECIFY);
floppy_write_cmd(base, 0xdf); /* steprate = 3ms, unload time = 240ms */
floppy_write_cmd(base, 0x02); /* load time = 16ms, no-DMA = 0 */
// it could fail...
if(floppy_calibrate(base)) return -1;
}
Motor control:
The other thing you should notice, is the "floppy_motor" commands. For the drive to do much of anything, the motor must be on, and this detail must be taken care of by software. So before you can actually run the above code, you need something like this (with the functions and enum declared above previous code):
Code: Select all
//
// The DOR byte: [write-only]
// -------------
//
// 7 6 5 4 3 2 1 0
// MOTD MOTC MOTB MOTA DMA NRST DR1 DR0
//
// DR1 and DR0 together select "current drive" = a/00, b/01, c/10, d/11
// MOTA, MOTB, MOTC, MOTD control motors for the four drives (1=on)
//
// DMA line enables (1 = enable) interrupts and DMA
// NRST is "not reset" so controller is enabled when it's 1
//
enum { floppy_motor_off = 0, floppy_motor_on, floppy_motor_wait };
static volatile int floppy_motor_ticks = 0;
static volatile int floppy_motor_state = 0;
void floppy_motor(int base, int onoff) {
if(onoff) {
if(!floppy_motor_state) {
// need to turn on
out8_p(base + FLOPPY_DOR, 0x1c);
timer_sleep(50); // wait 500 ms = hopefully enough for modern drives
}
floppy_motor_state = floppy_motor_on;
} else {
if(floppy_motor_state == floppy_motor_wait) {
printk("floppy_motor: strange, fd motor-state already waiting..\n");
}
floppy_motor_ticks = 300; // 3 seconds, see floppy_timer() below
floppy_motor_state = floppy_motor_wait;
}
}
void floppy_motor_kill(int base) {
out8_p(base + FLOPPY_DOR, 0x0c);
floppy_motor_state = floppy_motor_off;
}
//
// THIS SHOULD BE STARTED IN A SEPARATE THREAD.
//
//
void floppy_timer() {
while(1) {
// sleep for 500ms at a time, which gives around half
// a second jitter, but that's hardly relevant here.
timer_sleep(50);
if(floppy_motor_state == floppy_motor_wait) {
floppy_motor_ticks -= 50;
if(floppy_motor_ticks <= 0) {
floppy_motor_kill(floppy_base);
}
}
}
}
You should really have 4 state machines, one for each drive, and you should really also have it select the right drive when we want to operate with one. And somehow somewhere you'd have to make sure you don't try to operate on more than one drive at the same time. But I'll skip all that, and just have my code assume there's one drive.
Seeking for the data
So now that we have done the initialization and know how to get it spin the disk, we'll next concentrate on figuring out how to get away from cylinder 0, where we ended with the calibration request. This command takes two parameters of interest: the head that we want to move, and the cylinder we want to end up at. Other than that it looks a LOT like the calibration command:
Code: Select all
// Seek for a given cylinder, with a given head
int floppy_seek(int base, unsigned cyli, int head) {
unsigned i, st0, cyl = -1; // set to bogus cylinder
floppy_motor(base, floppy_motor_on);
for(i = 0; i < 10; i++) {
// Attempt to position to given cylinder
// 1st byte bit[1:0] = drive, bit[2] = head
// 2nd byte is cylinder number
floppy_write_cmd(base, CMD_SEEK);
floppy_write_cmd(base, head<<2);
floppy_write_cmd(base, cyli);
irq_wait(floppy_irq);
floppy_check_interrupt(base, &st0, &cyl);
if(st0 & 0xC0) {
static const char * status[] =
{ "normal", "error", "invalid", "drive" };
printk("floppy_seek: status = %s\n", status[st0 >> 6]);
continue;
}
if(cyl == cyli) {
floppy_motor(base, floppy_motor_off);
return 0;
}
}
printk("floppy_seek: 10 retries exhausted\n");
floppy_motor(base, floppy_motor_off);
return -1;
}
Well, DMA is not ready to transfer. So next thing is to have a routine to setup it. There's a whole total of 2 bits difference depending on whether we are reading or writing, so we'll only have one routine. However, this whole thing is complicated by some unfortunate details about memory management: we'll need a buffer which is below 16MB and doesn't cross 64k boundary. You probably knew that already? Well, I'll leave it up to you to figure out one, but I'll cheat and have my linker give me a buffer aligned at 32k boundary. We'll need a big buffer because we'll do big transfers at once.
Basicly, you just give DMA the buffer address, the number of bytes to transfer, and whether we're transferring to or from memory. The devil is in the details. You need to give 16-bit addresses to a 8-bit device, which has to be told "I'm going to write another low-byte" every time you want to write a low-byte. At least after a low-byte it knows to expect a high-byte. But that is not the most interesting detail about this little beast.
The important thing to remember, is that DMA counts in a strange way. You give it a number to count down from. After every byte, it decrements this value. You'd think that it says "done" once it reaches 0. Nope, it does not. It's happy (done) when it wraps around from 0 to 0xffff. So it transfers one byte more than the count that was programmed into it. That's actually pretty nice, because you can do full 64k transfer by using "0xffff" as the count and it's impossible (some would say fnord) to transfer 0 bytes even if DMA didn't make it so, so nothing is lost. But it's easy to forget this detail. Many people actually do.
And the reason we want to care about a single byte, is that the floppy drive won't stop before DMA tells it to. So if we program too long count, we'll get too long transfer, or "end-of-track" error. I'd guess this is common reason people's code works in emulators but not on real hardware: at least Bochs doesn't report end-of-sector.
The other important thing to remember, is that you give DMA address is physical memory. This won't touch my example code when it's running in my own kernel, because my kernel is in the same place in both virtual and physical memory. But if you have something like a "higher-half" kernel, then you have to take care of finding the physical address and having continuous physical memory. And remember it can't cross 64k in physical memory. For ISA DMA, there is no virtual memory whatsoever.
Anyway, with these details we can have the following routine:
Code: Select all
// Used by floppy_dma_init and floppy_do_track to specify direction
typedef enum {
floppy_dir_read = 1,
floppy_dir_write = 2
} floppy_dir;
// we statically reserve a totally uncomprehensive amount of memory
// must be large enough for whatever DMA transfer we might desire
// and must not cross 64k borders so easiest thing is to align it
// to 2^N boundary at least as big as the block
#define floppy_dmalen 0x4800
static const char floppy_dmabuf[floppy_dmalen]
__attribute__((aligned(0x8000)));
static void floppy_dma_init(floppy_dir dir) {
union {
unsigned char b[4]; // 4 bytes
unsigned long l; // 1 long = 32-bit
} a, c; // address and count
a.l = (unsigned) &floppy_dmabuf;
c.l = (unsigned) floppy_dmalen - 1; // -1 because of DMA counting
// check that address is at most 24-bits (under 16MB)
// check that count is at most 16-bits (DMA limit)
// check that if we add count and address we don't get a carry
// (DMA can't deal with such a carry, this is the 64k boundary limit)
if((a.l >> 24) || (c.l >> 16) || (((a.l&0xffff)+c.l)>>16)) {
panic("floppy_dma_init: static buffer problem\n");
}
unsigned char mode;
switch(dir) {
// 01:0:0:01:10 = single/inc/no-auto/to-mem/chan2
case floppy_dir_read: mode = 0x46; break;
// 01:0:0:10:10 = single/inc/no-auto/from-mem/chan2
case floppy_dir_write: mode = 0x4a; break;
default: panic("floppy_dma_init: invalid direction");
return; // not reached, please "mode user uninitialized"
}
out8_p(0x0a, 0x06); // mask chan 2
out8_p(0x0c, 0xff); // reset flip-flop
out8_p(0x04, a.b[0]); // - address low byte
out8_p(0x04, a.b[1]); // - address high byte
out8_p(0x81, a.b[2]); // external page register
out8_p(0x0c, 0xff); // reset flip-flop
out8_p(0x05, c.b[0]); // - count low byte
out8_p(0x05, c.b[1]); // - count high byte
out8_p(0x0b, mode); // set mode (see above)
out8_p(0x0a, 0x02); // unmask chan 2
}
However, in order to make that more easily turned into generic code, I've pulled the relevant parts out into variables, so they can be made parameters easily.
Notice that I don't use the auto-reset. That's because when trying to figure out this stuff, I noticed that if you get a read error while the DMA is half-way done, you'll have to reinitialize it anyway, since you don't know where exactly it was. So we'll just do a full reset every time, so we don't need to keep track of what was done before.
Reading / writing 0x4800 (=2*18*512) bytes at once:
With the above support code, we can finally read/write to a floppy. If we're writing, you better hope you've got the data in the buffer. If we're reading, you can find the data in the buffer after this function return success. It's a bit of a monster, but quite simple in operation. Just lots of code to extract error codes. Since we just hardcoded DMA to 0x4800 bytes, we'll expect 18 sectors of 512 bytes each, and we'll have to start at sector 1 (yes they count from 1) to get them all, so we'll hardcode that as well.
But that only gives us 0x2400 bytes, so we'll throw in some extra fancy magic: MULTITRACK! Namely, you can tell the controller to start reading with head 0, and when it gets to the end of track, it should start reading with head 1. Once it then reaches end-of-track, it'll report and error, unless the DMA just told it "enough" (or if it's Bochs, it'll just give you success anyway, possibly doing "something" else, no idea). Multi-tracking means you'll only need to call the function 80 times (one for each cylinder) to read the whole floppy into the memory. Ofcourse normally you'd not do that. You'd read the cylinders that you need. But there's no point in reading single sectors.
Because the disk rotates the same speed whether you are reading or not, it'll take about the same time to read one sector or the whole track. Well, technically reading a single sector takes on average 37% of the average time to read a full track, or about 20% of the time to read both tracks, assuming the second track starts where the second track ends. So if we end up reading the other sectors as well, we'll waste around 6.6 (single track) or 7.2 times (multi-track) by reading sector at a time.
So .. at least for reading... go with multi-track for the whole cylinder. And you do it like this:
Code: Select all
// This monster does full cylinder (both tracks) transfer to
// the specified direction (since the difference is small).
//
// It retries (a lot of times) on all errors except write-protection
// which is normally caused by mechanical switch on the disk.
//
int floppy_do_track(int base, unsigned cyl, floppy_dir dir) {
// transfer command, set below
unsigned char cmd;
// Read is MT:MF:SK:0:0:1:1:0, write MT:MF:0:0:1:0:1
// where MT = multitrack, MF = MFM mode, SK = skip deleted
//
// Specify multitrack and MFM mode
static const int flags = 0xC0;
switch(dir) {
case floppy_dir_read:
cmd = CMD_READ_DATA | flags;
break;
case floppy_dir_write:
cmd = CMD_WRITE_DATA | flags;
break;
default:
panic("floppy_do_track: invalid direction");
return 0; // not reached, but pleases "cmd used uninitialized"
}
// seek both heads
if(floppy_seek(base, cyl, 0)) return -1
if(floppy_seek(base, cyl, 1)) return -1
int i;
for(i = 0; i < 20; i++) {
floppy_motor(base, motor_on);
// init dma..
floppy_dma_init(dir);
timer_sleep(10); // give some time (100ms) to settle after the seeks
floppy_write_cmd(base, cmd); // set above for current direction
floppy_write_cmd(base, 0); // 0:0:0:0:0:HD:US1:US0 = head and drive
floppy_write_cmd(base, cyl); // cylinder
floppy_write_cmd(base, 0); // first head (should match with above)
floppy_write_cmd(base, 1); // first sector, strangely counts from 1
floppy_write_cmd(base, 2); // bytes/sector, 128*2^x (x=2 -> 512)
floppy_write_cmd(base, 18); // number of tracks to operate on
floppy_write_cmd(base, 0x1b); // GAP3 length, 27 is default for 3.5"
floppy_write_cmd(base, 0xff); // data length (0xff if B/S != 0)
irq_wait(floppy_irq); // don't SENSE_INTERRUPT here!
// first read status information
unsigned char st0, st1, st2, rcy, rhe, rse, bps;
st0 = floppy_read_data(base);
st1 = floppy_read_data(base);
st2 = floppy_read_data(base);
/*
* These are cylinder/head/sector values, updated with some
* rather bizarre logic, that I would like to understand.
*
*/
rcy = floppy_read_data(base);
rhe = floppy_read_data(base);
rse = floppy_read_data(base);
// bytes per sector, should be what we programmed in
bps = floppy_read_data(base);
int error = 0;
if(st0 & 0xC0) {
static const char * status[] =
{ 0, "error", "invalid command", "drive not ready" };
printk("floppy_do_sector: status = %s\n", status[st0 >> 6]);
error = 1;
}
if(st1 & 0x80) {
printk("floppy_do_sector: end of cylinder\n");
error = 1;
}
if(st0 & 0x08) {
printk("floppy_do_sector: drive not ready\n");
error = 1;
}
if(st1 & 0x20) {
printk("floppy_do_sector: CRC error\n");
error = 1;
}
if(st1 & 0x10) {
printk("floppy_do_sector: controller timeout\n");
error = 1;
}
if(st1 & 0x04) {
printk("floppy_do_sector: no data found\n");
error = 1;
}
if((st1|st2) & 0x01) {
printk("floppy_do_sector: no address mark found\n");
error = 1;
}
if(st2 & 0x40) {
printk("floppy_do_sector: deleted address mark\n");
error = 1;
}
if(st2 & 0x20) {
printk("floppy_do_sector: CRC error in data\n");
error = 1;
}
if(st2 & 0x10) {
printk("floppy_do_sector: wrong cylinder\n");
error = 1;
}
if(st2 & 0x04) {
printk("floppy_do_sector: uPD765 sector not found\n");
error = 1;
}
if(st2 & 0x02) {
printk("floppy_do_sector: bad cylinder\n");
error = 1;
}
if(bps != 0x2) {
printk("floppy_do_sector: wanted 512B/sector, got %d", (1<<(bps+7)));
error = 1;
}
if(st1 & 0x02) {
printk("floppy_do_sector: not writable\n");
error = 2;
}
if(!error) {
floppy_motor(base, floppy_motor_off);
return 0;
}
if(error > 1) {
printk("floppy_do_sector: not retrying..\n");
floppy_motor(base, floppy_motor_off);
return -2;
}
}
printk("floppy_do_sector: 20 retries exhausted\n");
floppy_motor(base, floppy_motor_off);
return -1;
}
Code: Select all
int floppy_read_track(int base, unsigned cyl) {
return floppy_do_track(base, cyl, floppy_dir_read);
}
int floppy_write_track(int base, unsigned cyl) {
return floppy_do_track(base, cyl, floppy_dir_write);
}
Like you probably noticed, most of the code is simply to get human readable error messages.
The only possible oddity with the above code is the fact that indeed head is set in two places within the same read/write command. First in the same byte that specifies the drive, and then again as a separate byte after cylinder. This is indeed correct, even if odd. The cylinder specified in the read/write doesn't cause a seek (we don't try to figure out if the controller would support implied-seeks) but supposedly we'll get an error (at least somewhere, maybe) if we try to pass in something other than where we just seeked to. What you do with the errors is up to you. The above retries if something other than "not writable" happened. No point for retrying that, since it's probably write protected disk.
That's about it. Hope you understood how this works. To read/write single sectors, or other smaller regions, you can set a sector other than 1 and set the DMA to stop earlier. Drop the multi-track bit if you only want to do it on one track, and remember to set the head to 1 in both places if you want that head only.
As far as I understand, it doesn't matter much if you set or clear the "skip deleted" bit. If you don't set it (I don't) the read will fail when it hits a deleted address mark (which you can write with a special command it seems) while if you set it, the sector with such a mark will get skipped. So what are these deleted address marks then? I have no idea, but normal floppies seem to work quite fine whatever the setting is, so I'd guess it's something that's useful for tapes. There would be a READ_TRACK command for reading the whole track, deleted or not, but you can't do that multi-tracked on both heads.
For real reliablity you'd probably want single sector reads/writes so that you can deal with cases where some of the sectors are bad and such. No idea if anybody cares these days, I'll throw floppies away the first time I see an error with one.
----
So .. hopefully that helped someone understand a bit of something.