Initial checkin.
[people/mcb30/legacybios.git] / src / floppy.c
1 // 16bit code to access floppy drives.
2 //
3 // Copyright (C) 2008  Kevin O'Connor <kevin@koconnor.net>
4 // Copyright (C) 2002  MandrakeSoft S.A.
5 //
6 // This file may be distributed under the terms of the GNU GPLv3 license.
7
8 #include "types.h" // u8
9 #include "disk.h" // DISK_RET_SUCCESS
10 #include "config.h" // CONFIG_FLOPPY_SUPPORT
11 #include "biosvar.h" // struct bregs
12 #include "util.h" // irq_disable
13 #include "cmos.h" // inb_cmos
14
15 #define BX_FLOPPY_ON_CNT 37   /* 2 seconds */
16
17 ////.org 0xefc7
18 // Since no provisions are made for multiple drive types, most
19 // values in this table are ignored.  I set parameters for 1.44M
20 // floppy here
21 char diskette_param_table[11] = {
22     0xAF,
23     0x02, // head load time 0000001, DMA used
24     0x25,
25     0x02,
26     18,
27     0x1B,
28     0xFF,
29     0x6C,
30     0xF6,
31     0x0F,
32     0x08,
33 };
34
35 // New diskette parameter table adding 3 parameters from IBM
36 // Since no provisions are made for multiple drive types, most
37 // values in this table are ignored.  I set parameters for 1.44M
38 // floppy here
39 char diskette_param_table2[14] VISIBLE = {
40     0xAF,
41     0x02, // head load time 0000001, DMA used
42     0x25,
43     0x02,
44     18,
45     0x1B,
46     0xFF,
47     0x6C,
48     0xF6,
49     0x0F,
50     0x08,
51     79,   // maximum track
52     0,    // data transfer rate
53     4,    // drive type in cmos
54 };
55
56 // Oddities:
57 //   Return codes vary greatly - AL not cleared consistenlty, BDA return
58 //      status not set consistently, sometimes panics.
59 //   Extra outb(0x000a, 0x02) in read?
60 //   Does not disable interrupts on failure paths.
61 //   numfloppies used before set in int_1308
62 //   int_1305 verifies track but doesn't use it?
63
64 static inline void
65 set_diskette_current_cyl(u8 drive, u8 cyl)
66 {
67     if (drive)
68         SET_BDA(floppy_track1, cyl);
69     else
70         SET_BDA(floppy_track0, cyl);
71 }
72
73 static u16
74 get_drive_type(u8 drive)
75 {
76     // check CMOS to see if drive exists
77     u8 drive_type = inb_cmos(CMOS_FLOPPY_DRIVE_TYPE);
78     if (drive == 0)
79         drive_type >>= 4;
80     else
81         drive_type &= 0x0f;
82     return drive_type;
83 }
84
85 static u16
86 floppy_media_known(u8 drive)
87 {
88     if (!(GET_BDA(floppy_recalibration_status) & (1<<drive)))
89         return 0;
90     u8 v = GET_BDA(floppy_media_state[drive]);
91     if (!(v & FMS_MEDIA_DRIVE_ESTABLISHED))
92         return 0;
93     return 1;
94 }
95
96 static void
97 floppy_reset_controller()
98 {
99     // Reset controller
100     u8 val8 = inb(PORT_FD_DOR);
101     outb(val8 & ~0x04, PORT_FD_DOR);
102     outb(val8 | 0x04, PORT_FD_DOR);
103
104     // Wait for controller to come out of reset
105     while ((inb(PORT_FD_STATUS) & 0xc0) != 0x80)
106         ;
107 }
108
109 static void
110 floppy_prepare_controller(u8 drive)
111 {
112     CLEARBITS_BDA(floppy_recalibration_status, FRS_TIMEOUT);
113
114     // turn on motor of selected drive, DMA & int enabled, normal operation
115     u8 prev_reset = inb(PORT_FD_DOR) & 0x04;
116     u8 dor = 0x10;
117     if (drive)
118         dor = 0x20;
119     dor |= 0x0c;
120     dor |= drive;
121     outb(dor, PORT_FD_DOR);
122
123     // reset the disk motor timeout value of INT 08
124     SET_BDA(floppy_motor_counter, BX_FLOPPY_ON_CNT);
125
126     // wait for drive readiness
127     while ((inb(PORT_FD_STATUS) & 0xc0) != 0x80)
128         ;
129
130     if (prev_reset == 0) {
131         irq_enable();
132         u8 v;
133         do {
134             v = GET_BDA(floppy_recalibration_status);
135         } while ((v & FRS_TIMEOUT) == 0);
136         irq_disable();
137
138         v &= ~FRS_TIMEOUT;
139         SET_BDA(floppy_recalibration_status, v);
140     }
141 }
142
143 static u8
144 floppy_pio(u8 *cmd, u8 cmdlen)
145 {
146     floppy_prepare_controller(cmd[1] & 1);
147
148     // send command to controller
149     u8 i;
150     for (i=0; i<cmdlen; i++)
151         outb(cmd[i], PORT_FD_DATA);
152
153     irq_enable();
154     u8 v;
155     do {
156         if (!GET_BDA(floppy_motor_counter)) {
157             irq_disable();
158             floppy_reset_controller();
159             return DISK_RET_ETIMEOUT;
160         }
161         v = GET_BDA(floppy_recalibration_status);
162     } while (!(v & FRS_TIMEOUT));
163     irq_disable();
164
165     v &= ~FRS_TIMEOUT;
166     SET_BDA(floppy_recalibration_status, v);
167
168     if ((inb(PORT_FD_STATUS) & 0xc0) != 0xc0)
169         BX_PANIC("int13_diskette: ctrl not ready\n");
170
171     return 0;
172 }
173
174 static u8
175 floppy_cmd(struct bregs *regs, u16 count, u8 *cmd, u8 cmdlen)
176 {
177     // es:bx = pointer to where to place information from diskette
178     // port 04: DMA-1 base and current address, channel 2
179     // port 05: DMA-1 base and current count, channel 2
180     u16 page = regs->es >> 12;   // upper 4 bits
181     u16 base_es = regs->es << 4; // lower 16bits contributed by ES
182     u16 base_address = base_es + regs->bx; // lower 16 bits of address
183     // contributed by ES:BX
184     if (base_address < base_es)
185         // in case of carry, adjust page by 1
186         page++;
187
188     // check for 64K boundary overrun
189     u16 last_addr = base_address + count;
190     if (last_addr < base_address)
191         return DISK_RET_EBOUNDARY;
192
193     u8 mode_register = 0x4a; // single mode, increment, autoinit disable,
194     if (cmd[0] == 0xe6)
195         // read
196         mode_register = 0x46;
197
198     DEBUGF("floppy dma c2");
199     outb(0x06, PORT_DMA1_MASK_REG);
200     outb(0x00, PORT_DMA1_CLEAR_FF_REG); // clear flip-flop
201     outb(base_address, PORT_DMA_ADDR_2);
202     outb(base_address>>8, PORT_DMA_ADDR_2);
203     outb(0x00, PORT_DMA1_CLEAR_FF_REG); // clear flip-flop
204     outb(count, PORT_DMA_CNT_2);
205     outb(count>>8, PORT_DMA_CNT_2);
206
207     // port 0b: DMA-1 Mode Register
208     // transfer type=write, channel 2
209     outb(mode_register, PORT_DMA1_MODE_REG);
210
211     // port 81: DMA-1 Page Register, channel 2
212     outb(page, PORT_DMA_PAGE_2);
213
214     outb(0x02, PORT_DMA1_MASK_REG); // unmask channel 2
215
216     u8 ret = floppy_pio(cmd, cmdlen);
217     if (ret)
218         return ret;
219
220     // read 7 return status bytes from controller
221     u8 i;
222     for (i=0; i<7; i++) {
223         u8 v = inb(PORT_FD_DATA);
224         cmd[i] = v;
225         SET_BDA(floppy_return_status[i], v);
226     }
227
228     return 0;
229 }
230
231 static void
232 floppy_drive_recal(u8 drive)
233 {
234     // send Recalibrate command (2 bytes) to controller
235     u8 data[12];
236     data[0] = 0x07;  // 07: Recalibrate
237     data[1] = drive; // 0=drive0, 1=drive1
238     floppy_pio(data, 2);
239
240     SETBITS_BDA(floppy_recalibration_status, 1<<drive);
241     set_diskette_current_cyl(drive, 0);
242 }
243
244 static u16
245 floppy_media_sense(u8 drive)
246 {
247     u16 rv;
248     u8 config_data, media_state;
249
250     floppy_drive_recal(drive);
251
252     // for now cheat and get drive type from CMOS,
253     // assume media is same as drive type
254
255     // ** config_data **
256     // Bitfields for diskette media control:
257     // Bit(s)  Description (Table M0028)
258     //  7-6  last data rate set by controller
259     //        00=500kbps, 01=300kbps, 10=250kbps, 11=1Mbps
260     //  5-4  last diskette drive step rate selected
261     //        00=0Ch, 01=0Dh, 10=0Eh, 11=0Ah
262     //  3-2  {data rate at start of operation}
263     //  1-0  reserved
264
265     // ** media_state **
266     // Bitfields for diskette drive media state:
267     // Bit(s)  Description (Table M0030)
268     //  7-6  data rate
269     //    00=500kbps, 01=300kbps, 10=250kbps, 11=1Mbps
270     //  5  double stepping required (e.g. 360kB in 1.2MB)
271     //  4  media type established
272     //  3  drive capable of supporting 4MB media
273     //  2-0  on exit from BIOS, contains
274     //    000 trying 360kB in 360kB
275     //    001 trying 360kB in 1.2MB
276     //    010 trying 1.2MB in 1.2MB
277     //    011 360kB in 360kB established
278     //    100 360kB in 1.2MB established
279     //    101 1.2MB in 1.2MB established
280     //    110 reserved
281     //    111 all other formats/drives
282
283     switch (get_drive_type(drive)) {
284     case 1:
285         // 360K 5.25" drive
286         config_data = 0x00; // 0000 0000
287         media_state = 0x25; // 0010 0101
288         rv = 1;
289         break;
290     case 2:
291         // 1.2 MB 5.25" drive
292         config_data = 0x00; // 0000 0000
293         media_state = 0x25; // 0010 0101   // need double stepping??? (bit 5)
294         rv = 1;
295         break;
296     case 3:
297         // 720K 3.5" drive
298         config_data = 0x00; // 0000 0000 ???
299         media_state = 0x17; // 0001 0111
300         rv = 1;
301         break;
302     case 4:
303         // 1.44 MB 3.5" drive
304         config_data = 0x00; // 0000 0000
305         media_state = 0x17; // 0001 0111
306         rv = 1;
307         break;
308     case 5:
309         // 2.88 MB 3.5" drive
310         config_data = 0xCC; // 1100 1100
311         media_state = 0xD7; // 1101 0111
312         rv = 1;
313         break;
314     //
315     // Extended floppy size uses special cmos setting
316     case 6:
317         // 160k 5.25" drive
318         config_data = 0x00; // 0000 0000
319         media_state = 0x27; // 0010 0111
320         rv = 1;
321         break;
322     case 7:
323         // 180k 5.25" drive
324         config_data = 0x00; // 0000 0000
325         media_state = 0x27; // 0010 0111
326         rv = 1;
327         break;
328     case 8:
329         // 320k 5.25" drive
330         config_data = 0x00; // 0000 0000
331         media_state = 0x27; // 0010 0111
332         rv = 1;
333         break;
334     default:
335         // not recognized
336         config_data = 0x00; // 0000 0000
337         media_state = 0x00; // 0000 0000
338         rv = 0;
339     }
340
341     SET_BDA(floppy_last_data_rate, config_data);
342     SET_BDA(floppy_media_state[drive], media_state);
343     return rv;
344 }
345
346 static inline void
347 floppy_ret(struct bregs *regs, u8 code)
348 {
349     regs->ah = code;
350     SET_BDA(floppy_last_status, code);
351     set_cf(regs, code);
352 }
353
354 static inline void
355 floppy_fail(struct bregs *regs, u8 code)
356 {
357     regs->al = 0; // no sectors read
358     floppy_ret(regs, code);
359 }
360
361 static u16
362 check_drive(struct bregs *regs, u8 drive)
363 {
364     // see if drive exists
365     if (drive > 1 || !get_drive_type(drive)) {
366         floppy_fail(regs, DISK_RET_ETIMEOUT);
367         return 1;
368     }
369
370     // see if media in drive, and type is known
371     if (floppy_media_known(drive) == 0 && floppy_media_sense(drive) == 0) {
372         floppy_fail(regs, DISK_RET_EMEDIA);
373         return 1;
374     }
375     return 0;
376 }
377
378 // diskette controller reset
379 static void
380 floppy_1300(struct bregs *regs, u8 drive)
381 {
382     if (drive > 1) {
383         floppy_ret(regs, DISK_RET_EPARAM);
384         return;
385     }
386     if (!get_drive_type(drive)) {
387         floppy_ret(regs, DISK_RET_ETIMEOUT);
388         return;
389     }
390     set_diskette_current_cyl(drive, 0); // current cylinder
391     floppy_ret(regs, DISK_RET_SUCCESS);
392 }
393
394 // Read Diskette Status
395 static void
396 floppy_1301(struct bregs *regs, u8 drive)
397 {
398     u8 v = GET_BDA(floppy_last_status);
399     regs->ah = v;
400     set_cf(regs, v);
401 }
402
403 // Read Diskette Sectors
404 static void
405 floppy_1302(struct bregs *regs, u8 drive)
406 {
407     if (check_drive(regs, drive))
408         return;
409
410     u8 num_sectors = regs->al;
411     u8 track       = regs->ch;
412     u8 sector      = regs->cl;
413     u8 head        = regs->dh;
414
415     if (head > 1 || sector == 0 || num_sectors == 0
416         || track > 79 || num_sectors > 72) {
417         BX_INFO("int13_diskette: read/write/verify: parameter out of range\n");
418         floppy_fail(regs, DISK_RET_EPARAM);
419         return;
420     }
421
422     // send read-normal-data command (9 bytes) to controller
423     u8 data[12];
424     data[0] = 0xe6; // e6: read normal data
425     data[1] = (head << 2) | drive; // HD DR1 DR2
426     data[2] = track;
427     data[3] = head;
428     data[4] = sector;
429     data[5] = 2; // 512 byte sector size
430     data[6] = sector + num_sectors - 1; // last sector to read on track
431     data[7] = 0; // Gap length
432     data[8] = 0xff; // Gap length
433
434     u16 ret = floppy_cmd(regs, (num_sectors * 512) - 1, data, 9);
435     if (ret) {
436         floppy_fail(regs, ret);
437         return;
438     }
439
440     if (data[0] & 0xc0) {
441         floppy_fail(regs, DISK_RET_ECONTROLLER);
442         return;
443     }
444
445     // ??? should track be new val from return_status[3] ?
446     set_diskette_current_cyl(drive, track);
447     // AL = number of sectors read (same value as passed)
448     floppy_ret(regs, DISK_RET_SUCCESS);
449 }
450
451 // Write Diskette Sectors
452 static void
453 floppy_1303(struct bregs *regs, u8 drive)
454 {
455     if (check_drive(regs, drive))
456         return;
457
458     u8 num_sectors = regs->al;
459     u8 track       = regs->ch;
460     u8 sector      = regs->cl;
461     u8 head        = regs->dh;
462
463     if (head > 1 || sector == 0 || num_sectors == 0
464         || track > 79 || num_sectors > 72) {
465         BX_INFO("int13_diskette: read/write/verify: parameter out of range\n");
466         floppy_fail(regs, DISK_RET_EPARAM);
467         return;
468     }
469
470     // send write-normal-data command (9 bytes) to controller
471     u8 data[12];
472     data[0] = 0xc5; // c5: write normal data
473     data[1] = (head << 2) | drive; // HD DR1 DR2
474     data[2] = track;
475     data[3] = head;
476     data[4] = sector;
477     data[5] = 2; // 512 byte sector size
478     data[6] = sector + num_sectors - 1; // last sector to write on track
479     data[7] = 0; // Gap length
480     data[8] = 0xff; // Gap length
481
482     u8 ret = floppy_cmd(regs, (num_sectors * 512) - 1, data, 9);
483     if (ret) {
484         floppy_fail(regs, ret);
485         return;
486     }
487
488     if (data[0] & 0xc0) {
489         if (data[1] & 0x02) {
490             regs->ax = 0x0300;
491             set_cf(regs, 1);
492             return;
493         }
494         BX_PANIC("int13_diskette_function: read error\n");
495     }
496
497     // ??? should track be new val from return_status[3] ?
498     set_diskette_current_cyl(drive, track);
499     // AL = number of sectors read (same value as passed)
500     floppy_ret(regs, DISK_RET_SUCCESS);
501 }
502
503 // Verify Diskette Sectors
504 static void
505 floppy_1304(struct bregs *regs, u8 drive)
506 {
507     if (check_drive(regs, drive))
508         return;
509
510     u8 num_sectors = regs->al;
511     u8 track       = regs->ch;
512     u8 sector      = regs->cl;
513     u8 head        = regs->dh;
514
515     if (head > 1 || sector == 0 || num_sectors == 0
516         || track > 79 || num_sectors > 72) {
517         BX_INFO("int13_diskette: read/write/verify: parameter out of range\n");
518         floppy_fail(regs, DISK_RET_EPARAM);
519         return;
520     }
521
522     // ??? should track be new val from return_status[3] ?
523     set_diskette_current_cyl(drive, track);
524     // AL = number of sectors verified (same value as passed)
525     floppy_ret(regs, DISK_RET_SUCCESS);
526 }
527
528 // format diskette track
529 static void
530 floppy_1305(struct bregs *regs, u8 drive)
531 {
532     DEBUGF("floppy f05\n");
533
534     if (check_drive(regs, drive))
535         return;
536
537     u8 num_sectors = regs->al;
538     u8 head        = regs->dh;
539
540     if (head > 1 || num_sectors == 0 || num_sectors > 18) {
541         BX_INFO("int13_diskette: read/write/verify: parameter out of range\n");
542         floppy_fail(regs, DISK_RET_EPARAM);
543         return;
544     }
545
546     // send format-track command (6 bytes) to controller
547     u8 data[12];
548     data[0] = 0x4d; // 4d: format track
549     data[1] = (head << 2) | drive; // HD DR1 DR2
550     data[2] = 2; // 512 byte sector size
551     data[3] = num_sectors; // number of sectors per track
552     data[4] = 0; // Gap length
553     data[5] = 0xf6; // Fill byte
554
555     u8 ret = floppy_cmd(regs, (num_sectors * 4) - 1, data, 6);
556     if (ret) {
557         floppy_fail(regs, ret);
558         return;
559     }
560
561     if (data[0] & 0xc0) {
562         if (data[1] & 0x02) {
563             regs->ax = 0x0300;
564             set_cf(regs, 1);
565             return;
566         }
567         BX_PANIC("int13_diskette_function: read error\n");
568     }
569
570     set_diskette_current_cyl(drive, 0);
571     floppy_ret(regs, 0);
572 }
573
574 // read diskette drive parameters
575 static void
576 floppy_1308(struct bregs *regs, u8 drive)
577 {
578     DEBUGF("floppy f08\n");
579
580     u8 drive_type = inb_cmos(CMOS_FLOPPY_DRIVE_TYPE);
581     u8 num_floppies = 0;
582     if (drive_type & 0xf0)
583         num_floppies++;
584     if (drive_type & 0x0f)
585         num_floppies++;
586
587     if (drive > 1) {
588         regs->ax = 0;
589         regs->bx = 0;
590         regs->cx = 0;
591         regs->dx = 0;
592         regs->es = 0;
593         regs->di = 0;
594         regs->dl = num_floppies;
595         set_cf(regs, 0);
596         return;
597     }
598
599     if (drive == 0)
600         drive_type >>= 4;
601     else
602         drive_type &= 0x0f;
603
604     regs->bh = 0;
605     regs->bl = drive_type;
606     regs->ah = 0;
607     regs->al = 0;
608     regs->dl = num_floppies;
609
610     switch (drive_type) {
611     case 0: // none
612         regs->cx = 0;
613         regs->dh = 0; // max head #
614         break;
615
616     case 1: // 360KB, 5.25"
617         regs->cx = 0x2709; // 40 tracks, 9 sectors
618         regs->dh = 1; // max head #
619         break;
620
621     case 2: // 1.2MB, 5.25"
622         regs->cx = 0x4f0f; // 80 tracks, 15 sectors
623         regs->dh = 1; // max head #
624         break;
625
626     case 3: // 720KB, 3.5"
627         regs->cx = 0x4f09; // 80 tracks, 9 sectors
628         regs->dh = 1; // max head #
629         break;
630
631     case 4: // 1.44MB, 3.5"
632         regs->cx = 0x4f12; // 80 tracks, 18 sectors
633         regs->dh = 1; // max head #
634         break;
635
636     case 5: // 2.88MB, 3.5"
637         regs->cx = 0x4f24; // 80 tracks, 36 sectors
638         regs->dh = 1; // max head #
639         break;
640
641     case 6: // 160k, 5.25"
642         regs->cx = 0x2708; // 40 tracks, 8 sectors
643         regs->dh = 0; // max head #
644         break;
645
646     case 7: // 180k, 5.25"
647         regs->cx = 0x2709; // 40 tracks, 9 sectors
648         regs->dh = 0; // max head #
649         break;
650
651     case 8: // 320k, 5.25"
652         regs->cx = 0x2708; // 40 tracks, 8 sectors
653         regs->dh = 1; // max head #
654         break;
655
656     default: // ?
657         BX_PANIC("floppy: int13: bad floppy type\n");
658     }
659
660     /* set es & di to point to 11 byte diskette param table in ROM */
661     regs->es = SEG_BIOS;
662     regs->di = (u16)diskette_param_table2;
663     /* disk status not changed upon success */
664 }
665
666 // read diskette drive type
667 static void
668 floppy_1315(struct bregs *regs, u8 drive)
669 {
670     DEBUGF("floppy f15\n");
671     if (drive > 1) {
672         regs->ah = 0; // only 2 drives supported
673         // set_diskette_ret_status here ???
674         set_cf(regs, 1);
675         return;
676     }
677     u8 drive_type = get_drive_type(drive);
678
679     regs->ah = (drive_type != 0);
680     set_cf(regs, 0);
681 }
682
683 // get diskette change line status
684 static void
685 floppy_1316(struct bregs *regs, u8 drive)
686 {
687     DEBUGF("floppy f16\n");
688     if (drive > 1) {
689         floppy_ret(regs, DISK_RET_EPARAM);
690         return;
691     }
692     floppy_ret(regs, DISK_RET_ECHANGED);
693 }
694
695 static void
696 floppy_13XX(struct bregs *regs, u8 drive)
697 {
698     BX_INFO("int13_diskette: unsupported AH=%02x\n", GET_AH());
699     floppy_ret(regs, DISK_RET_EPARAM);
700 }
701
702 void
703 floppy_13(struct bregs *regs, u8 drive)
704 {
705     if (CONFIG_FLOPPY_SUPPORT) {
706         switch (regs->ah) {
707         case 0x00: floppy_1300(regs, drive); break;
708         case 0x01: floppy_1301(regs, drive); break;
709         case 0x02: floppy_1302(regs, drive); break;
710         case 0x03: floppy_1303(regs, drive); break;
711         case 0x04: floppy_1304(regs, drive); break;
712         case 0x05: floppy_1305(regs, drive); break;
713         case 0x08: floppy_1308(regs, drive); break;
714         case 0x15: floppy_1315(regs, drive); break;
715         case 0x16: floppy_1316(regs, drive); break;
716         default:   floppy_13XX(regs, drive); break;
717         }
718     } else {
719         switch (regs->ah) {
720         case 0x01: floppy_1301(regs, drive); break;
721         default:   floppy_13XX(regs, drive); break;
722         }
723     }
724 }
725
726 // INT 0Eh Diskette Hardware ISR Entry Point
727 void VISIBLE
728 handle_0e(struct bregs *regs)
729 {
730     debug_enter(regs);
731     if ((inb(PORT_FD_STATUS) & 0xc0) != 0xc0) {
732         outb(0x08, PORT_FD_DATA); // sense interrupt status
733         while ((inb(PORT_FD_STATUS) & 0xc0) != 0xc0)
734             ;
735         do {
736             inb(PORT_FD_DATA);
737         } while ((inb(PORT_FD_STATUS) & 0xc0) == 0xc0);
738     }
739     eoi_master_pic();
740     // diskette interrupt has occurred
741     SETBITS_BDA(floppy_recalibration_status, FRS_TIMEOUT);
742 }
743
744 // Called from int08 handler.
745 void
746 floppy_tick()
747 {
748     // time to turn off drive(s)?
749     u8 fcount = GET_BDA(floppy_motor_counter);
750     if (fcount) {
751         fcount--;
752         SET_BDA(floppy_motor_counter, fcount);
753         if (fcount == 0)
754             // turn motor(s) off
755             outb(inb(PORT_FD_DOR) & 0xcf, PORT_FD_DOR);
756     }
757 }