Virtual DMA patch, (3rd attempt)

Alain Knaff (alain@linux.lu)
Wed, 18 Nov 1998 00:22:26 +0100


The following patch fixes a problem with some floppy disk controllers
which do not correctly stop data transfers when using virtual DMA
(PIO). PIO is used in low memory conditions, and on certain types of
Laptops which do not have fully functional DMA controllers. The
effect of the bug can be quite serious if the "right" circumstances
meet. Indeed if virtual DMA is switched on and if a buggy floppy disk
controller is used and if only part of a track is written, then the
floppy disk controller will zero out the next sector.

The patch works by pretending to the floppy disk controller that the
disk has fewer sectors than it actually has: this way it won't zero
out the following sector (because it thinks it doesn't exist).

The patch is already in the 2.1.x kernels since long ago, but so far
it hasn't found its way into the 2.0.x kernels.

Regards,

Alain

diff -ur 2.0.36/linux/drivers/block/floppy.c linux/drivers/block/floppy.c
--- 2.0.36/linux/drivers/block/floppy.c Tue Nov 17 00:54:28 1998
+++ linux/drivers/block/floppy.c Wed Nov 18 00:03:22 1998
@@ -521,6 +521,8 @@
static unsigned char current_drive = 0;
static long current_count_sectors = 0;
static unsigned char sector_t; /* sector in track */
+static unsigned char in_sector_offset; /* offset within physical sector,
+ * expressed in units of 512 bytes */

#ifndef fd_eject
#define fd_eject(x) -EINVAL
@@ -2056,7 +2058,7 @@

#define CODE2SIZE (ssize = ((1 << SIZECODE) + 3) >> 2)
#define FM_MODE(x,y) ((y) & ~(((x)->rate & 0x80) >>1))
-#define CT(x) ((x) | 0x40)
+#define CT(x) ((x) | 0xc0)
static void setup_format_params(int track)
{
struct fparm {
@@ -2224,7 +2226,7 @@
/* Interrupt handler evaluating the result of the r/w operation */
static void rw_interrupt(void)
{
- int nr_sectors, ssize, eoc;
+ int nr_sectors, ssize, eoc, heads;

if (!DRS->first_read_date)
DRS->first_read_date = jiffies;
@@ -2236,14 +2238,19 @@
eoc = 1;
else
eoc = 0;
- nr_sectors = ((R_TRACK-TRACK)*_floppy->head+R_HEAD-HEAD) *
- _floppy->sect + ((R_SECTOR-SECTOR+eoc) << SIZECODE >> 2) -
- (sector_t % _floppy->sect) % ssize;
+
+ if(COMMAND & 0x80)
+ heads = 2;
+ else
+ heads = 1;
+
+ nr_sectors = (((R_TRACK-TRACK) * heads +
+ R_HEAD-HEAD) * SECT_PER_TRACK +
+ R_SECTOR-SECTOR + eoc) << SIZECODE >> 2;

#ifdef FLOPPY_SANITY_CHECK
- if (nr_sectors > current_count_sectors + ssize -
- (current_count_sectors + sector_t) % ssize +
- sector_t % ssize){
+ if (nr_sectors / ssize >
+ (in_sector_offset + current_count_sectors + ssize - 1)/ssize) {
DPRINT("long rw: %x instead of %lx\n",
nr_sectors, current_count_sectors);
printk("rs=%d s=%d\n", R_SECTOR, SECTOR);
@@ -2253,6 +2260,7 @@
sector_t, ssize);
}
#endif
+ nr_sectors -= in_sector_offset;
INFBOUND(nr_sectors,0);
SUPBOUND(current_count_sectors, nr_sectors);

@@ -2424,6 +2432,32 @@
#endif
}

+/* work around a bug in pseudo DMA
+ * (on some FDCs) pseudo DMA does not stop when the CPU stops
+ * sending data. Hence we need a different way to signal the
+ * transfer length: We use SECT_PER_TRACK. Unfortunately, this
+ * does not work with MT, hence we can only transfer one head at
+ * a time
+ */
+static void virtualdmabug_workaround(void) {
+ int hard_sectors, end_sector;
+ if(CT(COMMAND) == FD_WRITE) {
+ COMMAND &= ~0x80; /* switch off multiple track mode */
+
+ hard_sectors = raw_cmd->length >> (7 + SIZECODE);
+ end_sector = SECTOR + hard_sectors - 1;
+#ifdef FLOPPY_SANITY_CHECK
+ if(end_sector > SECT_PER_TRACK) {
+ printk("too many sectors %d > %d\n",
+ end_sector, SECT_PER_TRACK);
+ return;
+ }
+#endif
+ SECT_PER_TRACK = end_sector; /* make sure SECT_PER_TRACK points
+ * to end of transfer */
+ }
+}
+
/*
* Formulate a read/write request.
* this routine decides where to load the data (directly to buffer, or to
@@ -2511,8 +2545,11 @@
max_sector = HEAD * _floppy->sect + tracksize;
} else if (!TRACK && !HEAD && !(_floppy->rate & FD_2M) && probing)
max_sector = _floppy->sect;
+ else if (!HEAD && CT(COMMAND) == FD_WRITE)
+ max_sector = _floppy->sect;

- aligned_sector_t = sector_t - (sector_t % _floppy->sect) % ssize;
+ in_sector_offset = (sector_t % _floppy->sect) % ssize;
+ aligned_sector_t = sector_t - in_sector_offset;
max_size = CURRENT->nr_sectors;
if ((raw_cmd->track == buffer_track) &&
(current_drive == buffer_drive) &&
@@ -2522,7 +2559,7 @@
copy_buffer(1, max_sector, buffer_max);
return 1;
}
- } else if (aligned_sector_t != sector_t || CURRENT->nr_sectors < ssize){
+ } else if (in_sector_offset || CURRENT->nr_sectors < ssize){
if (CT(COMMAND) == FD_WRITE){
if (sector_t + CURRENT->nr_sectors > ssize &&
sector_t + CURRENT->nr_sectors < ssize + ssize)
@@ -2575,6 +2612,7 @@
indirect, direct, sector_t);
return 0;
}
+ virtualdmabug_workaround();
return 2;
}
}
@@ -2588,7 +2626,7 @@
sector_t > buffer_max ||
sector_t < buffer_min ||
((CT(COMMAND) == FD_READ ||
- (aligned_sector_t == sector_t && CURRENT->nr_sectors >= ssize))&&
+ (!in_sector_offset && CURRENT->nr_sectors >= ssize))&&
max_sector > 2 * max_buffer_sectors + buffer_min &&
max_size + sector_t > 2 * max_buffer_sectors + buffer_min)
/* not enough space */){
@@ -2605,7 +2643,7 @@
* is either aligned or the data already in the buffer
* (buffer will be overwritten) */
#ifdef FLOPPY_SANITY_CHECK
- if (sector_t != aligned_sector_t && buffer_track == -1)
+ if (in_sector_offset && buffer_track == -1)
DPRINT("internal error offset !=0 on write\n");
#endif
buffer_track = raw_cmd->track;
@@ -2616,7 +2654,7 @@
2*max_buffer_sectors+buffer_min-aligned_sector_t);

/* round up current_count_sectors to get dma xfer size */
- raw_cmd->length = sector_t+current_count_sectors-aligned_sector_t;
+ raw_cmd->length = in_sector_offset+current_count_sectors;
raw_cmd->length = ((raw_cmd->length -1)|(ssize-1))+1;
raw_cmd->length <<= 9;
#ifdef FLOPPY_SANITY_CHECK
@@ -2678,6 +2716,7 @@
return 0;
}
#endif
+ virtualdmabug_workaround();
return 2;
}

-----------------------------------------------------------------------.
Email: alain@linux.lu Alain Lucien Knaff .
Tel(home): (352) 22 35 41 19, rue Jean l'Aveugle .
Tel(work): (352) 42 42 33 32 L-1148 Luxembourg-City .
Luxembourg .

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/