Add support for using floppy images in CBFS.
[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 FLOPPY_SECTOR_SIZE 512
18
19 #define BX_FLOPPY_ON_CNT 37   /* 2 seconds */
20
21 // New diskette parameter table adding 3 parameters from IBM
22 // Since no provisions are made for multiple drive types, most
23 // values in this table are ignored.  I set parameters for 1.44M
24 // floppy here
25 struct floppy_ext_dbt_s diskette_param_table2 VAR16_32 = {
26     .dbt = {
27         .specify1       = 0xAF,
28         .specify2       = 0x02, // head load time 0000001, DMA used
29         .shutoff_ticks  = 0x25,
30         .bps_code       = 0x02,
31         .sectors        = 18,
32         .interblock_len = 0x1B,
33         .data_len       = 0xFF,
34         .gap_len        = 0x6C,
35         .fill_byte      = 0xF6,
36         .settle_time    = 0x0F,
37         .startup_time   = 0x08,
38     },
39     .max_track      = 79,   // maximum track
40     .data_rate      = 0,    // data transfer rate
41     .drive_type     = 4,    // drive type in cmos
42 };
43
44 // Since no provisions are made for multiple drive types, most
45 // values in this table are ignored.  I set parameters for 1.44M
46 // floppy here
47 struct floppy_dbt_s diskette_param_table VAR16FIXED(0xefc7) = {
48     .specify1       = 0xAF,
49     .specify2       = 0x02, // head load time 0000001, DMA used
50     .shutoff_ticks  = 0x25,
51     .bps_code       = 0x02,
52     .sectors        = 18,
53     .interblock_len = 0x1B,
54     .data_len       = 0xFF,
55     .gap_len        = 0x6C,
56     .fill_byte      = 0xF6,
57     .settle_time    = 0x0F,
58     .startup_time   = 0x08,
59 };
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 void
89 addFloppy(int floppyid, int ftype, int driver)
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     Drives.drives[driveid].type = driver;
103     Drives.drives[driveid].blksize = FLOPPY_SECTOR_SIZE;
104     Drives.drives[driveid].floppy_type = ftype;
105     Drives.drives[driveid].sectors = (u16)-1;
106
107     memcpy(&Drives.drives[driveid].lchs, &FloppyInfo[ftype].chs
108            , sizeof(FloppyInfo[ftype].chs));
109
110     map_floppy_drive(driveid);
111 }
112
113 void
114 floppy_setup()
115 {
116     if (! CONFIG_FLOPPY)
117         return;
118     dprintf(3, "init floppy drives\n");
119
120     if (CONFIG_COREBOOT) {
121         // XXX - disable floppies on coreboot for now.
122     } else {
123         u8 type = inb_cmos(CMOS_FLOPPY_DRIVE_TYPE);
124         if (type & 0xf0)
125             addFloppy(0, type >> 4, DTYPE_FLOPPY);
126         if (type & 0x0f)
127             addFloppy(1, type & 0x0f, DTYPE_FLOPPY);
128     }
129
130     outb(0x02, PORT_DMA1_MASK_REG);
131
132     enable_hwirq(6, entry_0e);
133 }
134
135 // Find a floppy type that matches a given image size.
136 int
137 find_floppy_type(u32 size)
138 {
139     int i;
140     for (i=1; i<ARRAY_SIZE(FloppyInfo); i++) {
141         struct chs_s *c = &FloppyInfo[i].chs;
142         if (c->cylinders * c->heads * c->spt * FLOPPY_SECTOR_SIZE == size)
143             return i;
144     }
145     return -1;
146 }
147
148
149 /****************************************************************
150  * Low-level floppy IO
151  ****************************************************************/
152
153 static void
154 floppy_reset_controller()
155 {
156     // Reset controller
157     u8 val8 = inb(PORT_FD_DOR);
158     outb(val8 & ~0x04, PORT_FD_DOR);
159     outb(val8 | 0x04, PORT_FD_DOR);
160
161     // Wait for controller to come out of reset
162     while ((inb(PORT_FD_STATUS) & 0xc0) != 0x80)
163         ;
164 }
165
166 static int
167 wait_floppy_irq()
168 {
169     irq_enable();
170     u8 v;
171     for (;;) {
172         if (!GET_BDA(floppy_motor_counter)) {
173             irq_disable();
174             return -1;
175         }
176         v = GET_BDA(floppy_recalibration_status);
177         if (v & FRS_TIMEOUT)
178             break;
179         cpu_relax();
180     }
181     irq_disable();
182
183     v &= ~FRS_TIMEOUT;
184     SET_BDA(floppy_recalibration_status, v);
185     return 0;
186 }
187
188 static void
189 floppy_prepare_controller(u8 floppyid)
190 {
191     CLEARBITS_BDA(floppy_recalibration_status, FRS_TIMEOUT);
192
193     // turn on motor of selected drive, DMA & int enabled, normal operation
194     u8 prev_reset = inb(PORT_FD_DOR) & 0x04;
195     u8 dor = 0x10;
196     if (floppyid)
197         dor = 0x20;
198     dor |= 0x0c;
199     dor |= floppyid;
200     outb(dor, PORT_FD_DOR);
201
202     // reset the disk motor timeout value of INT 08
203     SET_BDA(floppy_motor_counter, BX_FLOPPY_ON_CNT);
204
205     // wait for drive readiness
206     while ((inb(PORT_FD_STATUS) & 0xc0) != 0x80)
207         ;
208
209     if (!prev_reset)
210         wait_floppy_irq();
211 }
212
213 static int
214 floppy_pio(u8 *cmd, u8 cmdlen)
215 {
216     floppy_prepare_controller(cmd[1] & 1);
217
218     // send command to controller
219     u8 i;
220     for (i=0; i<cmdlen; i++)
221         outb(cmd[i], PORT_FD_DATA);
222
223     int ret = wait_floppy_irq();
224     if (ret) {
225         floppy_reset_controller();
226         return -1;
227     }
228
229     return 0;
230 }
231
232 static int
233 floppy_cmd(struct disk_op_s *op, u16 count, u8 *cmd, u8 cmdlen)
234 {
235     // es:bx = pointer to where to place information from diskette
236     u32 addr = (u32)op->buf_fl;
237
238     // check for 64K boundary overrun
239     u32 last_addr = addr + count;
240     if ((addr >> 16) != (last_addr >> 16))
241         return DISK_RET_EBOUNDARY;
242
243     u8 mode_register = 0x4a; // single mode, increment, autoinit disable,
244     if (cmd[0] == 0xe6)
245         // read
246         mode_register = 0x46;
247
248     //DEBUGF("floppy dma c2\n");
249     outb(0x06, PORT_DMA1_MASK_REG);
250     outb(0x00, PORT_DMA1_CLEAR_FF_REG); // clear flip-flop
251     outb(addr, PORT_DMA_ADDR_2);
252     outb(addr>>8, PORT_DMA_ADDR_2);
253     outb(0x00, PORT_DMA1_CLEAR_FF_REG); // clear flip-flop
254     outb(count, PORT_DMA_CNT_2);
255     outb(count>>8, PORT_DMA_CNT_2);
256
257     // port 0b: DMA-1 Mode Register
258     // transfer type=write, channel 2
259     outb(mode_register, PORT_DMA1_MODE_REG);
260
261     // port 81: DMA-1 Page Register, channel 2
262     outb(addr>>16, PORT_DMA_PAGE_2);
263
264     outb(0x02, PORT_DMA1_MASK_REG); // unmask channel 2
265
266     int ret = floppy_pio(cmd, cmdlen);
267     if (ret)
268         return DISK_RET_ETIMEOUT;
269
270     // check port 3f4 for accessibility to status bytes
271     if ((inb(PORT_FD_STATUS) & 0xc0) != 0xc0)
272         return DISK_RET_ECONTROLLER;
273
274     // read 7 return status bytes from controller
275     u8 i;
276     for (i=0; i<7; i++) {
277         u8 v = inb(PORT_FD_DATA);
278         cmd[i] = v;
279         SET_BDA(floppy_return_status[i], v);
280     }
281
282     return DISK_RET_SUCCESS;
283 }
284
285
286 /****************************************************************
287  * Floppy media sense
288  ****************************************************************/
289
290 static inline void
291 set_diskette_current_cyl(u8 floppyid, u8 cyl)
292 {
293     SET_BDA(floppy_track[floppyid], cyl);
294 }
295
296 static void
297 floppy_drive_recal(u8 floppyid)
298 {
299     // send Recalibrate command (2 bytes) to controller
300     u8 data[12];
301     data[0] = 0x07;  // 07: Recalibrate
302     data[1] = floppyid; // 0=drive0, 1=drive1
303     floppy_pio(data, 2);
304
305     SETBITS_BDA(floppy_recalibration_status, 1<<floppyid);
306     set_diskette_current_cyl(floppyid, 0);
307 }
308
309 static int
310 floppy_media_sense(u8 driveid)
311 {
312     // for now cheat and get drive type from CMOS,
313     // assume media is same as drive type
314
315     // ** config_data **
316     // Bitfields for diskette media control:
317     // Bit(s)  Description (Table M0028)
318     //  7-6  last data rate set by controller
319     //        00=500kbps, 01=300kbps, 10=250kbps, 11=1Mbps
320     //  5-4  last diskette drive step rate selected
321     //        00=0Ch, 01=0Dh, 10=0Eh, 11=0Ah
322     //  3-2  {data rate at start of operation}
323     //  1-0  reserved
324
325     // ** media_state **
326     // Bitfields for diskette drive media state:
327     // Bit(s)  Description (Table M0030)
328     //  7-6  data rate
329     //    00=500kbps, 01=300kbps, 10=250kbps, 11=1Mbps
330     //  5  double stepping required (e.g. 360kB in 1.2MB)
331     //  4  media type established
332     //  3  drive capable of supporting 4MB media
333     //  2-0  on exit from BIOS, contains
334     //    000 trying 360kB in 360kB
335     //    001 trying 360kB in 1.2MB
336     //    010 trying 1.2MB in 1.2MB
337     //    011 360kB in 360kB established
338     //    100 360kB in 1.2MB established
339     //    101 1.2MB in 1.2MB established
340     //    110 reserved
341     //    111 all other formats/drives
342
343     u8 ftype = GET_GLOBAL(Drives.drives[driveid].floppy_type);
344     SET_BDA(floppy_last_data_rate, GET_GLOBAL(FloppyInfo[ftype].config_data));
345     u8 floppyid = GET_GLOBAL(Drives.drives[driveid].cntl_id);
346     SET_BDA(floppy_media_state[floppyid]
347             , GET_GLOBAL(FloppyInfo[ftype].media_state));
348     return DISK_RET_SUCCESS;
349 }
350
351 static int
352 check_recal_drive(u8 driveid)
353 {
354     u8 floppyid = GET_GLOBAL(Drives.drives[driveid].cntl_id);
355     if ((GET_BDA(floppy_recalibration_status) & (1<<floppyid))
356         && (GET_BDA(floppy_media_state[floppyid]) & FMS_MEDIA_DRIVE_ESTABLISHED))
357         // Media is known.
358         return DISK_RET_SUCCESS;
359
360     // Recalibrate drive.
361     floppy_drive_recal(floppyid);
362
363     // Sense media.
364     return floppy_media_sense(driveid);
365 }
366
367
368 /****************************************************************
369  * Floppy handlers
370  ****************************************************************/
371
372 static void
373 lba2chs(struct disk_op_s *op, u8 *track, u8 *sector, u8 *head)
374 {
375     u32 lba = op->lba;
376     u8 driveid = op->driveid;
377
378     u32 tmp = lba + 1;
379     u16 nlspt = GET_GLOBAL(Drives.drives[driveid].lchs.spt);
380     *sector = tmp % nlspt;
381
382     tmp /= nlspt;
383     u16 nlh = GET_GLOBAL(Drives.drives[driveid].lchs.heads);
384     *head = tmp % nlh;
385
386     tmp /= nlh;
387     *track = tmp;
388 }
389
390 // diskette controller reset
391 static int
392 floppy_reset(struct disk_op_s *op)
393 {
394     u8 floppyid = GET_GLOBAL(Drives.drives[op->driveid].cntl_id);
395     set_diskette_current_cyl(floppyid, 0); // current cylinder
396     return DISK_RET_SUCCESS;
397 }
398
399 // Read Diskette Sectors
400 static int
401 floppy_read(struct disk_op_s *op)
402 {
403     int res = check_recal_drive(op->driveid);
404     if (res)
405         goto fail;
406
407     u8 track, sector, head;
408     lba2chs(op, &track, &sector, &head);
409
410     // send read-normal-data command (9 bytes) to controller
411     u8 floppyid = GET_GLOBAL(Drives.drives[op->driveid].cntl_id);
412     u8 data[12];
413     data[0] = 0xe6; // e6: read normal data
414     data[1] = (head << 2) | floppyid; // HD DR1 DR2
415     data[2] = track;
416     data[3] = head;
417     data[4] = sector;
418     data[5] = 2; // 512 byte sector size
419     data[6] = sector + op->count - 1; // last sector to read on track
420     data[7] = 0; // Gap length
421     data[8] = 0xff; // Gap length
422
423     res = floppy_cmd(op, (op->count * FLOPPY_SECTOR_SIZE) - 1, data, 9);
424     if (res)
425         goto fail;
426
427     if (data[0] & 0xc0) {
428         res = DISK_RET_ECONTROLLER;
429         goto fail;
430     }
431
432     // ??? should track be new val from return_status[3] ?
433     set_diskette_current_cyl(floppyid, track);
434     return DISK_RET_SUCCESS;
435 fail:
436     op->count = 0; // no sectors read
437     return res;
438 }
439
440 // Write Diskette Sectors
441 static int
442 floppy_write(struct disk_op_s *op)
443 {
444     int res = check_recal_drive(op->driveid);
445     if (res)
446         goto fail;
447
448     u8 track, sector, head;
449     lba2chs(op, &track, &sector, &head);
450
451     // send write-normal-data command (9 bytes) to controller
452     u8 floppyid = GET_GLOBAL(Drives.drives[op->driveid].cntl_id);
453     u8 data[12];
454     data[0] = 0xc5; // c5: write normal data
455     data[1] = (head << 2) | floppyid; // HD DR1 DR2
456     data[2] = track;
457     data[3] = head;
458     data[4] = sector;
459     data[5] = 2; // 512 byte sector size
460     data[6] = sector + op->count - 1; // last sector to write on track
461     data[7] = 0; // Gap length
462     data[8] = 0xff; // Gap length
463
464     res = floppy_cmd(op, (op->count * FLOPPY_SECTOR_SIZE) - 1, data, 9);
465     if (res)
466         goto fail;
467
468     if (data[0] & 0xc0) {
469         if (data[1] & 0x02)
470             res = DISK_RET_EWRITEPROTECT;
471         else
472             res = DISK_RET_ECONTROLLER;
473         goto fail;
474     }
475
476     // ??? should track be new val from return_status[3] ?
477     set_diskette_current_cyl(floppyid, track);
478     return DISK_RET_SUCCESS;
479 fail:
480     op->count = 0; // no sectors read
481     return res;
482 }
483
484 // Verify Diskette Sectors
485 static int
486 floppy_verify(struct disk_op_s *op)
487 {
488     int res = check_recal_drive(op->driveid);
489     if (res)
490         goto fail;
491
492     u8 track, sector, head;
493     lba2chs(op, &track, &sector, &head);
494
495     // ??? should track be new val from return_status[3] ?
496     u8 floppyid = GET_GLOBAL(Drives.drives[op->driveid].cntl_id);
497     set_diskette_current_cyl(floppyid, track);
498     return DISK_RET_SUCCESS;
499 fail:
500     op->count = 0; // no sectors read
501     return res;
502 }
503
504 // format diskette track
505 static int
506 floppy_format(struct disk_op_s *op)
507 {
508     int ret = check_recal_drive(op->driveid);
509     if (ret)
510         return ret;
511
512     u8 head = op->lba;
513
514     // send format-track command (6 bytes) to controller
515     u8 floppyid = GET_GLOBAL(Drives.drives[op->driveid].cntl_id);
516     u8 data[12];
517     data[0] = 0x4d; // 4d: format track
518     data[1] = (head << 2) | floppyid; // HD DR1 DR2
519     data[2] = 2; // 512 byte sector size
520     data[3] = op->count; // number of sectors per track
521     data[4] = 0; // Gap length
522     data[5] = 0xf6; // Fill byte
523
524     ret = floppy_cmd(op, (op->count * 4) - 1, data, 6);
525     if (ret)
526         return ret;
527
528     if (data[0] & 0xc0) {
529         if (data[1] & 0x02)
530             return DISK_RET_EWRITEPROTECT;
531         return DISK_RET_ECONTROLLER;
532     }
533
534     set_diskette_current_cyl(floppyid, 0);
535     return DISK_RET_SUCCESS;
536 }
537
538 int
539 process_floppy_op(struct disk_op_s *op)
540 {
541     if (!CONFIG_FLOPPY)
542         return 0;
543
544     switch (op->command) {
545     case CMD_RESET:
546         return floppy_reset(op);
547     case CMD_READ:
548         return floppy_read(op);
549     case CMD_WRITE:
550         return floppy_write(op);
551     case CMD_VERIFY:
552         return floppy_verify(op);
553     case CMD_FORMAT:
554         return floppy_format(op);
555     default:
556         op->count = 0;
557         return DISK_RET_EPARAM;
558     }
559 }
560
561
562 /****************************************************************
563  * HW irqs
564  ****************************************************************/
565
566 // INT 0Eh Diskette Hardware ISR Entry Point
567 void VISIBLE16
568 handle_0e()
569 {
570     debug_isr(DEBUG_ISR_0e);
571     if (! CONFIG_FLOPPY)
572         goto done;
573
574     if ((inb(PORT_FD_STATUS) & 0xc0) != 0xc0) {
575         outb(0x08, PORT_FD_DATA); // sense interrupt status
576         while ((inb(PORT_FD_STATUS) & 0xc0) != 0xc0)
577             ;
578         do {
579             inb(PORT_FD_DATA);
580         } while ((inb(PORT_FD_STATUS) & 0xc0) == 0xc0);
581     }
582     // diskette interrupt has occurred
583     SETBITS_BDA(floppy_recalibration_status, FRS_TIMEOUT);
584
585 done:
586     eoi_pic1();
587 }
588
589 // Called from int08 handler.
590 void
591 floppy_tick()
592 {
593     if (! CONFIG_FLOPPY)
594         return;
595
596     // time to turn off drive(s)?
597     u8 fcount = GET_BDA(floppy_motor_counter);
598     if (fcount) {
599         fcount--;
600         SET_BDA(floppy_motor_counter, fcount);
601         if (fcount == 0)
602             // turn motor(s) off
603             outb(inb(PORT_FD_DOR) & 0xcf, PORT_FD_DOR);
604     }
605 }