Add floppy controllers to "drives" list also.
[seabios.git] / src / floppy.c
1 // 16bit code to access floppy drives.
2 //
3 // Copyright (C) 2008,2009  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 LGPLv3 license.
7
8 #include "types.h" // u8
9 #include "disk.h" // DISK_RET_SUCCESS
10 #include "config.h" // CONFIG_FLOPPY
11 #include "biosvar.h" // SET_BDA
12 #include "util.h" // irq_disable
13 #include "cmos.h" // inb_cmos
14 #include "pic.h" // eoi_pic1
15 #include "bregs.h" // struct bregs
16
17 #define BX_FLOPPY_ON_CNT 37   /* 2 seconds */
18
19 // New diskette parameter table adding 3 parameters from IBM
20 // Since no provisions are made for multiple drive types, most
21 // values in this table are ignored.  I set parameters for 1.44M
22 // floppy here
23 struct floppy_ext_dbt_s diskette_param_table2 VAR16_32 = {
24     .dbt = {
25         .specify1       = 0xAF,
26         .specify2       = 0x02, // head load time 0000001, DMA used
27         .shutoff_ticks  = 0x25,
28         .bps_code       = 0x02,
29         .sectors        = 18,
30         .interblock_len = 0x1B,
31         .data_len       = 0xFF,
32         .gap_len        = 0x6C,
33         .fill_byte      = 0xF6,
34         .settle_time    = 0x0F,
35         .startup_time   = 0x08,
36     },
37     .max_track      = 79,   // maximum track
38     .data_rate      = 0,    // data transfer rate
39     .drive_type     = 4,    // drive type in cmos
40 };
41
42 // Since no provisions are made for multiple drive types, most
43 // values in this table are ignored.  I set parameters for 1.44M
44 // floppy here
45 struct floppy_dbt_s diskette_param_table VAR16FIXED(0xefc7) = {
46     .specify1       = 0xAF,
47     .specify2       = 0x02, // head load time 0000001, DMA used
48     .shutoff_ticks  = 0x25,
49     .bps_code       = 0x02,
50     .sectors        = 18,
51     .interblock_len = 0x1B,
52     .data_len       = 0xFF,
53     .gap_len        = 0x6C,
54     .fill_byte      = 0xF6,
55     .settle_time    = 0x0F,
56     .startup_time   = 0x08,
57 };
58
59 u8 FloppyTypes[2] VAR16_32;
60
61 struct floppyinfo_s {
62     struct chs_s chs;
63     u8 config_data;
64     u8 media_state;
65 };
66
67 struct floppyinfo_s FloppyInfo[] VAR16_32 = {
68     // Unknown
69     { {0, 0, 0}, 0x00, 0x00},
70     // 1 - 360KB, 5.25" - 2 heads, 40 tracks, 9 sectors
71     { {2, 40, 9}, 0x00, 0x25},
72     // 2 - 1.2MB, 5.25" - 2 heads, 80 tracks, 15 sectors
73     { {2, 80, 15}, 0x00, 0x25},
74     // 3 - 720KB, 3.5"  - 2 heads, 80 tracks, 9 sectors
75     { {2, 80, 9}, 0x00, 0x17},
76     // 4 - 1.44MB, 3.5" - 2 heads, 80 tracks, 18 sectors
77     { {2, 80, 18}, 0x00, 0x17},
78     // 5 - 2.88MB, 3.5" - 2 heads, 80 tracks, 36 sectors
79     { {2, 80, 36}, 0xCC, 0xD7},
80     // 6 - 160k, 5.25"  - 1 heads, 40 tracks, 8 sectors
81     { {1, 40, 8}, 0x00, 0x27},
82     // 7 - 180k, 5.25"  - 1 heads, 40 tracks, 9 sectors
83     { {1, 40, 9}, 0x00, 0x27},
84     // 8 - 320k, 5.25"  - 2 heads, 40 tracks, 8 sectors
85     { {2, 40, 8}, 0x00, 0x27},
86 };
87
88 static void
89 addFloppy(int floppyid, int ftype)
90 {
91     if (ftype <= 0 || ftype >= ARRAY_SIZE(FloppyInfo)) {
92         dprintf(1, "Bad floppy type %d\n", ftype);
93         return;
94     }
95
96     int driveid = Drives.drivecount;
97     if (driveid >= ARRAY_SIZE(Drives.drives))
98         return;
99     Drives.drivecount++;
100     memset(&Drives.drives[driveid], 0, sizeof(Drives.drives[0]));
101     Drives.drives[driveid].cntl_id = floppyid;
102     FloppyTypes[floppyid] = ftype;
103
104     memcpy(&Drives.drives[driveid].lchs, &FloppyInfo[ftype].chs
105            , sizeof(FloppyInfo[ftype].chs));
106
107     map_floppy_drive(driveid);
108 }
109
110 void
111 floppy_setup()
112 {
113     if (! CONFIG_FLOPPY)
114         return;
115     dprintf(3, "init floppy drives\n");
116     FloppyTypes[0] = FloppyTypes[1] = 0;
117
118     if (CONFIG_COREBOOT) {
119         // XXX - disable floppies on coreboot for now.
120     } else {
121         u8 type = inb_cmos(CMOS_FLOPPY_DRIVE_TYPE);
122         if (type & 0xf0)
123             addFloppy(0, type >> 4);
124         if (type & 0x0f)
125             addFloppy(1, type & 0x0f);
126     }
127
128     outb(0x02, PORT_DMA1_MASK_REG);
129
130     enable_hwirq(6, entry_0e);
131 }
132
133 #define floppy_ret(regs, code)                                  \
134     __floppy_ret((regs), (code) | (__LINE__ << 8), __func__)
135
136 void
137 __floppy_ret(struct bregs *regs, u32 linecode, const char *fname)
138 {
139     u8 code = linecode;
140     SET_BDA(floppy_last_status, code);
141     if (code)
142         __set_code_fail(regs, linecode, fname);
143     else
144         set_code_success(regs);
145 }
146
147
148 /****************************************************************
149  * Low-level floppy IO
150  ****************************************************************/
151
152 static void
153 floppy_reset_controller()
154 {
155     // Reset controller
156     u8 val8 = inb(PORT_FD_DOR);
157     outb(val8 & ~0x04, PORT_FD_DOR);
158     outb(val8 | 0x04, PORT_FD_DOR);
159
160     // Wait for controller to come out of reset
161     while ((inb(PORT_FD_STATUS) & 0xc0) != 0x80)
162         ;
163 }
164
165 static int
166 wait_floppy_irq()
167 {
168     irq_enable();
169     u8 v;
170     for (;;) {
171         if (!GET_BDA(floppy_motor_counter)) {
172             irq_disable();
173             return -1;
174         }
175         v = GET_BDA(floppy_recalibration_status);
176         if (v & FRS_TIMEOUT)
177             break;
178         cpu_relax();
179     }
180     irq_disable();
181
182     v &= ~FRS_TIMEOUT;
183     SET_BDA(floppy_recalibration_status, v);
184     return 0;
185 }
186
187 static void
188 floppy_prepare_controller(u8 floppyid)
189 {
190     CLEARBITS_BDA(floppy_recalibration_status, FRS_TIMEOUT);
191
192     // turn on motor of selected drive, DMA & int enabled, normal operation
193     u8 prev_reset = inb(PORT_FD_DOR) & 0x04;
194     u8 dor = 0x10;
195     if (floppyid)
196         dor = 0x20;
197     dor |= 0x0c;
198     dor |= floppyid;
199     outb(dor, PORT_FD_DOR);
200
201     // reset the disk motor timeout value of INT 08
202     SET_BDA(floppy_motor_counter, BX_FLOPPY_ON_CNT);
203
204     // wait for drive readiness
205     while ((inb(PORT_FD_STATUS) & 0xc0) != 0x80)
206         ;
207
208     if (!prev_reset)
209         wait_floppy_irq();
210 }
211
212 static int
213 floppy_pio(u8 *cmd, u8 cmdlen)
214 {
215     floppy_prepare_controller(cmd[1] & 1);
216
217     // send command to controller
218     u8 i;
219     for (i=0; i<cmdlen; i++)
220         outb(cmd[i], PORT_FD_DATA);
221
222     int ret = wait_floppy_irq();
223     if (ret) {
224         floppy_reset_controller();
225         return -1;
226     }
227
228     return 0;
229 }
230
231 static int
232 floppy_cmd(struct bregs *regs, u16 count, u8 *cmd, u8 cmdlen)
233 {
234     // es:bx = pointer to where to place information from diskette
235     u32 addr = (u32)MAKE_FLATPTR(regs->es, regs->bx);
236
237     // check for 64K boundary overrun
238     u32 last_addr = addr + count;
239     if ((addr >> 16) != (last_addr >> 16)) {
240         floppy_ret(regs, DISK_RET_EBOUNDARY);
241         return -1;
242     }
243
244     u8 mode_register = 0x4a; // single mode, increment, autoinit disable,
245     if (cmd[0] == 0xe6)
246         // read
247         mode_register = 0x46;
248
249     //DEBUGF("floppy dma c2\n");
250     outb(0x06, PORT_DMA1_MASK_REG);
251     outb(0x00, PORT_DMA1_CLEAR_FF_REG); // clear flip-flop
252     outb(addr, PORT_DMA_ADDR_2);
253     outb(addr>>8, PORT_DMA_ADDR_2);
254     outb(0x00, PORT_DMA1_CLEAR_FF_REG); // clear flip-flop
255     outb(count, PORT_DMA_CNT_2);
256     outb(count>>8, PORT_DMA_CNT_2);
257
258     // port 0b: DMA-1 Mode Register
259     // transfer type=write, channel 2
260     outb(mode_register, PORT_DMA1_MODE_REG);
261
262     // port 81: DMA-1 Page Register, channel 2
263     outb(addr>>16, PORT_DMA_PAGE_2);
264
265     outb(0x02, PORT_DMA1_MASK_REG); // unmask channel 2
266
267     int ret = floppy_pio(cmd, cmdlen);
268     if (ret) {
269         floppy_ret(regs, DISK_RET_ETIMEOUT);
270         return -1;
271     }
272
273     // check port 3f4 for accessibility to status bytes
274     if ((inb(PORT_FD_STATUS) & 0xc0) != 0xc0) {
275         floppy_ret(regs, DISK_RET_ECONTROLLER);
276         return -1;
277     }
278
279     // read 7 return status bytes from controller
280     u8 i;
281     for (i=0; i<7; i++) {
282         u8 v = inb(PORT_FD_DATA);
283         cmd[i] = v;
284         SET_BDA(floppy_return_status[i], v);
285     }
286
287     return 0;
288 }
289
290
291 /****************************************************************
292  * Floppy media sense
293  ****************************************************************/
294
295 static inline void
296 set_diskette_current_cyl(u8 floppyid, u8 cyl)
297 {
298     SET_BDA(floppy_track[floppyid], cyl);
299 }
300
301 static void
302 floppy_drive_recal(u8 floppyid)
303 {
304     // send Recalibrate command (2 bytes) to controller
305     u8 data[12];
306     data[0] = 0x07;  // 07: Recalibrate
307     data[1] = floppyid; // 0=drive0, 1=drive1
308     floppy_pio(data, 2);
309
310     SETBITS_BDA(floppy_recalibration_status, 1<<floppyid);
311     set_diskette_current_cyl(floppyid, 0);
312 }
313
314 static int
315 floppy_media_sense(u8 floppyid)
316 {
317     // for now cheat and get drive type from CMOS,
318     // assume media is same as drive type
319
320     // ** config_data **
321     // Bitfields for diskette media control:
322     // Bit(s)  Description (Table M0028)
323     //  7-6  last data rate set by controller
324     //        00=500kbps, 01=300kbps, 10=250kbps, 11=1Mbps
325     //  5-4  last diskette drive step rate selected
326     //        00=0Ch, 01=0Dh, 10=0Eh, 11=0Ah
327     //  3-2  {data rate at start of operation}
328     //  1-0  reserved
329
330     // ** media_state **
331     // Bitfields for diskette drive media state:
332     // Bit(s)  Description (Table M0030)
333     //  7-6  data rate
334     //    00=500kbps, 01=300kbps, 10=250kbps, 11=1Mbps
335     //  5  double stepping required (e.g. 360kB in 1.2MB)
336     //  4  media type established
337     //  3  drive capable of supporting 4MB media
338     //  2-0  on exit from BIOS, contains
339     //    000 trying 360kB in 360kB
340     //    001 trying 360kB in 1.2MB
341     //    010 trying 1.2MB in 1.2MB
342     //    011 360kB in 360kB established
343     //    100 360kB in 1.2MB established
344     //    101 1.2MB in 1.2MB established
345     //    110 reserved
346     //    111 all other formats/drives
347
348     u8 ftype = GET_GLOBAL(FloppyTypes[floppyid]);
349     SET_BDA(floppy_last_data_rate, GET_GLOBAL(FloppyInfo[ftype].config_data));
350     SET_BDA(floppy_media_state[floppyid]
351             , GET_GLOBAL(FloppyInfo[ftype].media_state));
352     return 0;
353 }
354
355 static int
356 check_recal_drive(struct bregs *regs, u8 floppyid)
357 {
358     if ((GET_BDA(floppy_recalibration_status) & (1<<floppyid))
359         && (GET_BDA(floppy_media_state[floppyid]) & FMS_MEDIA_DRIVE_ESTABLISHED))
360         // Media is known.
361         return 0;
362
363     // Recalibrate drive.
364     floppy_drive_recal(floppyid);
365
366     // Sense media.
367     int ret = floppy_media_sense(floppyid);
368     if (ret) {
369         floppy_ret(regs, DISK_RET_EMEDIA);
370         return -1;
371     }
372     return 0;
373 }
374
375
376 /****************************************************************
377  * Floppy int13 handlers
378  ****************************************************************/
379
380 // diskette controller reset
381 static void
382 floppy_1300(struct bregs *regs, u8 driveid)
383 {
384     u8 floppyid = GET_GLOBAL(Drives.drives[driveid].cntl_id);
385     set_diskette_current_cyl(floppyid, 0); // current cylinder
386     floppy_ret(regs, DISK_RET_SUCCESS);
387 }
388
389 // Read Diskette Status
390 static void
391 floppy_1301(struct bregs *regs, u8 driveid)
392 {
393     u8 v = GET_BDA(floppy_last_status);
394     regs->ah = v;
395     set_cf(regs, v);
396 }
397
398 // Read Diskette Sectors
399 static void
400 floppy_1302(struct bregs *regs, u8 driveid)
401 {
402     u8 floppyid = GET_GLOBAL(Drives.drives[driveid].cntl_id);
403     if (check_recal_drive(regs, floppyid))
404         goto fail;
405
406     u8 num_sectors = regs->al;
407     u8 track       = regs->ch;
408     u8 sector      = regs->cl;
409     u8 head        = regs->dh;
410
411     if (head > 1 || sector == 0 || num_sectors == 0
412         || track > 79 || num_sectors > 72) {
413         floppy_ret(regs, DISK_RET_EPARAM);
414         goto fail;
415     }
416
417     // send read-normal-data command (9 bytes) to controller
418     u8 data[12];
419     data[0] = 0xe6; // e6: read normal data
420     data[1] = (head << 2) | floppyid; // HD DR1 DR2
421     data[2] = track;
422     data[3] = head;
423     data[4] = sector;
424     data[5] = 2; // 512 byte sector size
425     data[6] = sector + num_sectors - 1; // last sector to read on track
426     data[7] = 0; // Gap length
427     data[8] = 0xff; // Gap length
428
429     int ret = floppy_cmd(regs, (num_sectors * 512) - 1, data, 9);
430     if (ret)
431         goto fail;
432
433     if (data[0] & 0xc0) {
434         floppy_ret(regs, DISK_RET_ECONTROLLER);
435         goto fail;
436     }
437
438     // ??? should track be new val from return_status[3] ?
439     set_diskette_current_cyl(floppyid, track);
440     // AL = number of sectors read (same value as passed)
441     floppy_ret(regs, DISK_RET_SUCCESS);
442     return;
443 fail:
444     regs->al = 0; // no sectors read
445 }
446
447 // Write Diskette Sectors
448 static void
449 floppy_1303(struct bregs *regs, u8 driveid)
450 {
451     u8 floppyid = GET_GLOBAL(Drives.drives[driveid].cntl_id);
452     if (check_recal_drive(regs, floppyid))
453         goto fail;
454
455     u8 num_sectors = regs->al;
456     u8 track       = regs->ch;
457     u8 sector      = regs->cl;
458     u8 head        = regs->dh;
459
460     if (head > 1 || sector == 0 || num_sectors == 0
461         || track > 79 || num_sectors > 72) {
462         floppy_ret(regs, DISK_RET_EPARAM);
463         goto fail;
464     }
465
466     // send write-normal-data command (9 bytes) to controller
467     u8 data[12];
468     data[0] = 0xc5; // c5: write normal data
469     data[1] = (head << 2) | floppyid; // HD DR1 DR2
470     data[2] = track;
471     data[3] = head;
472     data[4] = sector;
473     data[5] = 2; // 512 byte sector size
474     data[6] = sector + num_sectors - 1; // last sector to write on track
475     data[7] = 0; // Gap length
476     data[8] = 0xff; // Gap length
477
478     int ret = floppy_cmd(regs, (num_sectors * 512) - 1, data, 9);
479     if (ret)
480         goto fail;
481
482     if (data[0] & 0xc0) {
483         if (data[1] & 0x02)
484             floppy_ret(regs, DISK_RET_EWRITEPROTECT);
485         else
486             floppy_ret(regs, DISK_RET_ECONTROLLER);
487         goto fail;
488     }
489
490     // ??? should track be new val from return_status[3] ?
491     set_diskette_current_cyl(floppyid, track);
492     // AL = number of sectors read (same value as passed)
493     floppy_ret(regs, DISK_RET_SUCCESS);
494     return;
495 fail:
496     regs->al = 0; // no sectors read
497 }
498
499 // Verify Diskette Sectors
500 static void
501 floppy_1304(struct bregs *regs, u8 driveid)
502 {
503     u8 floppyid = GET_GLOBAL(Drives.drives[driveid].cntl_id);
504     if (check_recal_drive(regs, floppyid))
505         goto fail;
506
507     u8 num_sectors = regs->al;
508     u8 track       = regs->ch;
509     u8 sector      = regs->cl;
510     u8 head        = regs->dh;
511
512     if (head > 1 || sector == 0 || num_sectors == 0
513         || track > 79 || num_sectors > 72) {
514         floppy_ret(regs, DISK_RET_EPARAM);
515         goto fail;
516     }
517
518     // ??? should track be new val from return_status[3] ?
519     set_diskette_current_cyl(floppyid, track);
520     // AL = number of sectors verified (same value as passed)
521     floppy_ret(regs, DISK_RET_SUCCESS);
522     return;
523 fail:
524     regs->al = 0; // no sectors read
525 }
526
527 // format diskette track
528 static void
529 floppy_1305(struct bregs *regs, u8 driveid)
530 {
531     u8 floppyid = GET_GLOBAL(Drives.drives[driveid].cntl_id);
532     dprintf(3, "floppy f05\n");
533
534     if (check_recal_drive(regs, floppyid))
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         floppy_ret(regs, DISK_RET_EPARAM);
542         return;
543     }
544
545     // send format-track command (6 bytes) to controller
546     u8 data[12];
547     data[0] = 0x4d; // 4d: format track
548     data[1] = (head << 2) | floppyid; // HD DR1 DR2
549     data[2] = 2; // 512 byte sector size
550     data[3] = num_sectors; // number of sectors per track
551     data[4] = 0; // Gap length
552     data[5] = 0xf6; // Fill byte
553
554     int ret = floppy_cmd(regs, (num_sectors * 4) - 1, data, 6);
555     if (ret)
556         return;
557
558     if (data[0] & 0xc0) {
559         if (data[1] & 0x02)
560             floppy_ret(regs, DISK_RET_EWRITEPROTECT);
561         else
562             floppy_ret(regs, DISK_RET_ECONTROLLER);
563         return;
564     }
565
566     set_diskette_current_cyl(floppyid, 0);
567     floppy_ret(regs, 0);
568 }
569
570 // read diskette drive parameters
571 static void
572 floppy_1308(struct bregs *regs, u8 driveid)
573 {
574     dprintf(3, "floppy f08\n");
575
576     regs->ax = 0;
577     regs->dx = GET_GLOBAL(Drives.floppycount);
578
579     u8 floppyid = GET_GLOBAL(Drives.drives[driveid].cntl_id);
580     u8 ftype = GET_GLOBAL(FloppyTypes[floppyid]);
581     regs->bx = ftype;
582
583     u16 nlc = GET_GLOBAL(Drives.drives[driveid].lchs.cylinders);
584     u16 nlh = GET_GLOBAL(Drives.drives[driveid].lchs.heads);
585     u16 nlspt = GET_GLOBAL(Drives.drives[driveid].lchs.spt);
586     nlc -= 1; // 0 based
587     nlh -= 1;
588
589     regs->ch = nlc & 0xff;
590     regs->cl = ((nlc >> 2) & 0xc0) | (nlspt & 0x3f);
591     regs->dh = nlh;
592
593     /* set es & di to point to 11 byte diskette param table in ROM */
594     regs->es = SEG_BIOS;
595     regs->di = (u32)&diskette_param_table2;
596     /* disk status not changed upon success */
597     set_success(regs);
598 }
599
600 // read diskette drive type
601 static void
602 floppy_1315(struct bregs *regs, u8 driveid)
603 {
604     dprintf(6, "floppy f15\n");
605     regs->ah = 1;
606     set_success(regs);
607 }
608
609 // get diskette change line status
610 static void
611 floppy_1316(struct bregs *regs, u8 driveid)
612 {
613     floppy_ret(regs, DISK_RET_ECHANGED);
614 }
615
616 static void
617 floppy_13XX(struct bregs *regs, u8 driveid)
618 {
619     floppy_ret(regs, DISK_RET_EPARAM);
620 }
621
622 void
623 floppy_13(struct bregs *regs, u8 driveid)
624 {
625     switch (regs->ah) {
626     case 0x00: floppy_1300(regs, driveid); break;
627     case 0x01: floppy_1301(regs, driveid); break;
628     case 0x02: floppy_1302(regs, driveid); break;
629     case 0x03: floppy_1303(regs, driveid); break;
630     case 0x04: floppy_1304(regs, driveid); break;
631     case 0x05: floppy_1305(regs, driveid); break;
632     case 0x08: floppy_1308(regs, driveid); break;
633     case 0x15: floppy_1315(regs, driveid); break;
634     case 0x16: floppy_1316(regs, driveid); break;
635     default:   floppy_13XX(regs, driveid); break;
636     }
637 }
638
639
640 /****************************************************************
641  * HW irqs
642  ****************************************************************/
643
644 // INT 0Eh Diskette Hardware ISR Entry Point
645 void VISIBLE16
646 handle_0e()
647 {
648     debug_isr(DEBUG_ISR_0e);
649     if (! CONFIG_FLOPPY)
650         goto done;
651
652     if ((inb(PORT_FD_STATUS) & 0xc0) != 0xc0) {
653         outb(0x08, PORT_FD_DATA); // sense interrupt status
654         while ((inb(PORT_FD_STATUS) & 0xc0) != 0xc0)
655             ;
656         do {
657             inb(PORT_FD_DATA);
658         } while ((inb(PORT_FD_STATUS) & 0xc0) == 0xc0);
659     }
660     // diskette interrupt has occurred
661     SETBITS_BDA(floppy_recalibration_status, FRS_TIMEOUT);
662
663 done:
664     eoi_pic1();
665 }
666
667 // Called from int08 handler.
668 void
669 floppy_tick()
670 {
671     if (! CONFIG_FLOPPY)
672         return;
673
674     // time to turn off drive(s)?
675     u8 fcount = GET_BDA(floppy_motor_counter);
676     if (fcount) {
677         fcount--;
678         SET_BDA(floppy_motor_counter, fcount);
679         if (fcount == 0)
680             // turn motor(s) off
681             outb(inb(PORT_FD_DOR) & 0xcf, PORT_FD_DOR);
682     }
683 }