Unify cd emulation access and main disk access code.
[seabios.git] / src / disk.c
1 // 16bit code to access hard 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 LGPLv3 license.
7
8 #include "disk.h" // floppy_13
9 #include "biosvar.h" // SET_BDA
10 #include "config.h" // CONFIG_*
11 #include "util.h" // debug_enter
12 #include "pic.h" // eoi_pic2
13 #include "bregs.h" // struct bregs
14 #include "pci.h" // pci_bdf_to_bus
15 #include "ata.h" // ATA_CB_DC
16
17
18 /****************************************************************
19  * Helper functions
20  ****************************************************************/
21
22 void
23 __disk_ret(struct bregs *regs, u32 linecode, const char *fname)
24 {
25     u8 code = linecode;
26     if (regs->dl < EXTSTART_HD)
27         SET_BDA(floppy_last_status, code);
28     else
29         SET_BDA(disk_last_status, code);
30     if (code)
31         __set_code_fail(regs, linecode, fname);
32     else
33         set_code_success(regs);
34 }
35
36 static void
37 __disk_stub(struct bregs *regs, int lineno, const char *fname)
38 {
39     __debug_stub(regs, lineno, fname);
40     __disk_ret(regs, DISK_RET_SUCCESS | (lineno << 8), fname);
41 }
42
43 #define DISK_STUB(regs)                         \
44     __disk_stub((regs), __LINE__, __func__)
45
46 static void
47 fillLCHS(u8 driveid, u16 *nlc, u16 *nlh, u16 *nlspt)
48 {
49     if (CONFIG_CDROM_EMU && driveid == GET_GLOBAL(cdemu_driveid)) {
50         // Emulated drive - get info from ebda.  (It's not possible to
51         // populate the geometry directly in the driveid because the
52         // geometry is only known after the bios segment is made
53         // read-only).
54         u16 ebda_seg = get_ebda_seg();
55         *nlc = GET_EBDA2(ebda_seg, cdemu.lchs.cylinders);
56         *nlh = GET_EBDA2(ebda_seg, cdemu.lchs.heads);
57         *nlspt = GET_EBDA2(ebda_seg, cdemu.lchs.spt);
58         return;
59     }
60     *nlc = GET_GLOBAL(Drives.drives[driveid].lchs.cylinders);
61     *nlh = GET_GLOBAL(Drives.drives[driveid].lchs.heads);
62     *nlspt = GET_GLOBAL(Drives.drives[driveid].lchs.spt);
63 }
64
65 // Perform read/write/verify using old-style chs accesses
66 static void
67 basic_access(struct bregs *regs, u8 driveid, u16 command)
68 {
69     struct disk_op_s dop;
70     dop.driveid = driveid;
71     dop.command = command;
72
73     u8 count = regs->al;
74     u16 cylinder = regs->ch | ((((u16)regs->cl) << 2) & 0x300);
75     u16 sector = regs->cl & 0x3f;
76     u16 head = regs->dh;
77
78     if (count > 128 || count == 0 || sector == 0) {
79         dprintf(1, "int13_harddisk: function %02x, parameter out of range!\n"
80                 , regs->ah);
81         disk_ret(regs, DISK_RET_EPARAM);
82         return;
83     }
84     dop.count = count;
85
86     u16 nlc, nlh, nlspt;
87     fillLCHS(driveid, &nlc, &nlh, &nlspt);
88
89     // sanity check on cyl heads, sec
90     if (cylinder >= nlc || head >= nlh || sector > nlspt) {
91         dprintf(1, "int13_harddisk: function %02x, parameters out of"
92                 " range %04x/%04x/%04x!\n"
93                 , regs->ah, cylinder, head, sector);
94         disk_ret(regs, DISK_RET_EPARAM);
95         return;
96     }
97
98     // translate lchs to lba
99     dop.lba = (((((u32)cylinder * (u32)nlh) + (u32)head) * (u32)nlspt)
100                + (u32)sector - 1);
101
102     dop.buf_fl = MAKE_FLATPTR(regs->es, regs->bx);
103
104     int status = send_disk_op(&dop);
105
106     regs->al = dop.count;
107
108     disk_ret(regs, status);
109 }
110
111 // Perform read/write/verify using new-style "int13ext" accesses.
112 static void
113 extended_access(struct bregs *regs, u8 driveid, u16 command)
114 {
115     struct disk_op_s dop;
116     // Get lba and check.
117     dop.lba = GET_INT13EXT(regs, lba);
118     dop.command = command;
119     dop.driveid = driveid;
120     if (dop.lba >= GET_GLOBAL(Drives.drives[driveid].sectors)) {
121         dprintf(1, "int13_harddisk: function %02x. LBA out of range\n"
122                 , regs->ah);
123         disk_ret(regs, DISK_RET_EPARAM);
124         return;
125     }
126
127     dop.buf_fl = SEGOFF_TO_FLATPTR(GET_INT13EXT(regs, data));
128     dop.count = GET_INT13EXT(regs, count);
129
130     int status = send_disk_op(&dop);
131
132     SET_INT13EXT(regs, count, dop.count);
133
134     disk_ret(regs, status);
135 }
136
137
138 /****************************************************************
139  * Hard Drive functions
140  ****************************************************************/
141
142 // disk controller reset
143 static void
144 disk_1300(struct bregs *regs, u8 driveid)
145 {
146     struct disk_op_s dop;
147     dop.driveid = driveid;
148     dop.command = CMD_RESET;
149     int status = send_disk_op(&dop);
150     disk_ret(regs, status);
151 }
152
153 // read disk status
154 static void
155 disk_1301(struct bregs *regs, u8 driveid)
156 {
157     u8 v;
158     if (regs->dl < EXTSTART_HD)
159         // Floppy
160         v = GET_BDA(floppy_last_status);
161     else
162         v = GET_BDA(disk_last_status);
163     regs->ah = v;
164     set_cf(regs, v);
165     // XXX - clear disk_last_status?
166 }
167
168 // read disk sectors
169 static void
170 disk_1302(struct bregs *regs, u8 driveid)
171 {
172     basic_access(regs, driveid, CMD_READ);
173 }
174
175 // write disk sectors
176 static void
177 disk_1303(struct bregs *regs, u8 driveid)
178 {
179     basic_access(regs, driveid, CMD_WRITE);
180 }
181
182 // verify disk sectors
183 static void
184 disk_1304(struct bregs *regs, u8 driveid)
185 {
186     basic_access(regs, driveid, CMD_VERIFY);
187     // FIXME verify
188 }
189
190 // format disk track
191 static void
192 disk_1305(struct bregs *regs, u8 driveid)
193 {
194     DISK_STUB(regs);
195
196     u16 nlc, nlh, nlspt;
197     fillLCHS(driveid, &nlc, &nlh, &nlspt);
198
199     u8 num_sectors = regs->al;
200     u8 head        = regs->dh;
201
202     if (head >= nlh || num_sectors == 0 || num_sectors > nlspt) {
203         disk_ret(regs, DISK_RET_EPARAM);
204         return;
205     }
206
207     struct disk_op_s dop;
208     dop.driveid = driveid;
209     dop.command = CMD_FORMAT;
210     dop.lba = head;
211     dop.count = num_sectors;
212     dop.buf_fl = MAKE_FLATPTR(regs->es, regs->bx);
213     int status = send_disk_op(&dop);
214     disk_ret(regs, status);
215 }
216
217 // read disk drive parameters
218 static void
219 disk_1308(struct bregs *regs, u8 driveid)
220 {
221     u16 ebda_seg = get_ebda_seg();
222     // Get logical geometry from table
223     u16 nlc, nlh, nlspt;
224     fillLCHS(driveid, &nlc, &nlh, &nlspt);
225     nlc--;
226     nlh--;
227     u8 count;
228     if (regs->dl < EXTSTART_HD) {
229         // Floppy
230         count = GET_GLOBAL(Drives.floppycount);
231
232         if (CONFIG_CDROM_EMU && driveid == GET_GLOBAL(cdemu_driveid))
233             regs->bx = GET_EBDA2(ebda_seg, cdemu.media) * 2;
234         else
235             regs->bx = GET_GLOBAL(Drives.drives[driveid].floppy_type);
236
237         // set es & di to point to 11 byte diskette param table in ROM
238         regs->es = SEG_BIOS;
239         regs->di = (u32)&diskette_param_table2;
240     } else if (regs->dl < EXTSTART_CD) {
241         // Hard drive
242         count = GET_BDA(hdcount);
243         nlc--;  // last sector reserved
244     } else {
245         // Not supported on CDROM
246         disk_ret(regs, DISK_RET_EPARAM);
247         return;
248     }
249
250     if (CONFIG_CDROM_EMU && GET_EBDA2(ebda_seg, cdemu.active)) {
251         u8 emudrive = GET_EBDA2(ebda_seg, cdemu.emulated_extdrive);
252         if (((emudrive ^ regs->dl) & 0x80) == 0)
253             // Note extra drive due to emulation.
254             count++;
255         if (regs->dl < EXTSTART_HD && count > 2)
256             // Max of two floppy drives.
257             count = 2;
258     }
259
260     regs->al = 0;
261     regs->ch = nlc & 0xff;
262     regs->cl = ((nlc >> 2) & 0xc0) | (nlspt & 0x3f);
263     regs->dh = nlh;
264
265     disk_ret(regs, DISK_RET_SUCCESS);
266     regs->dl = count;
267 }
268
269 // initialize drive parameters
270 static void
271 disk_1309(struct bregs *regs, u8 driveid)
272 {
273     DISK_STUB(regs);
274 }
275
276 // seek to specified cylinder
277 static void
278 disk_130c(struct bregs *regs, u8 driveid)
279 {
280     DISK_STUB(regs);
281 }
282
283 // alternate disk reset
284 static void
285 disk_130d(struct bregs *regs, u8 driveid)
286 {
287     DISK_STUB(regs);
288 }
289
290 // check drive ready
291 static void
292 disk_1310(struct bregs *regs, u8 driveid)
293 {
294     // should look at 40:8E also???
295
296     struct disk_op_s dop;
297     dop.driveid = driveid;
298     dop.command = CMD_ISREADY;
299     int status = send_disk_op(&dop);
300     disk_ret(regs, status);
301 }
302
303 // recalibrate
304 static void
305 disk_1311(struct bregs *regs, u8 driveid)
306 {
307     DISK_STUB(regs);
308 }
309
310 // controller internal diagnostic
311 static void
312 disk_1314(struct bregs *regs, u8 driveid)
313 {
314     DISK_STUB(regs);
315 }
316
317 // read disk drive size
318 static void
319 disk_1315(struct bregs *regs, u8 driveid)
320 {
321     disk_ret(regs, DISK_RET_SUCCESS);
322     if (regs->dl < EXTSTART_HD || regs->dl >= EXTSTART_CD) {
323         // Floppy or cdrom
324         regs->ah = 1;
325         return;
326     }
327     // Hard drive
328
329     // Get logical geometry from table
330     u16 nlc, nlh, nlspt;
331     fillLCHS(driveid, &nlc, &nlh, &nlspt);
332
333     // Compute sector count seen by int13
334     u32 lba = (u32)(nlc - 1) * (u32)nlh * (u32)nlspt;
335     regs->cx = lba >> 16;
336     regs->dx = lba & 0xffff;
337     regs->ah = 3; // hard disk accessible
338 }
339
340 static void
341 disk_1316(struct bregs *regs, u8 driveid)
342 {
343     if (regs->dl >= EXTSTART_HD) {
344         // Hard drive
345         disk_ret(regs, DISK_RET_EPARAM);
346         return;
347     }
348     disk_ret(regs, DISK_RET_ECHANGED);
349 }
350
351 // IBM/MS installation check
352 static void
353 disk_1341(struct bregs *regs, u8 driveid)
354 {
355     regs->bx = 0xaa55;  // install check
356     regs->cx = 0x0007;  // ext disk access and edd, removable supported
357     disk_ret(regs, DISK_RET_SUCCESS);
358     regs->ah = 0x30;    // EDD 3.0
359 }
360
361 // IBM/MS extended read
362 static void
363 disk_1342(struct bregs *regs, u8 driveid)
364 {
365     extended_access(regs, driveid, CMD_READ);
366 }
367
368 // IBM/MS extended write
369 static void
370 disk_1343(struct bregs *regs, u8 driveid)
371 {
372     extended_access(regs, driveid, CMD_WRITE);
373 }
374
375 // IBM/MS verify
376 static void
377 disk_1344(struct bregs *regs, u8 driveid)
378 {
379     extended_access(regs, driveid, CMD_VERIFY);
380 }
381
382 // lock
383 static void
384 disk_134500(struct bregs *regs, u8 driveid)
385 {
386     u16 ebda_seg = get_ebda_seg();
387     u8 locks = GET_EBDA2(ebda_seg, cdrom_locks[driveid]);
388     if (locks == 0xff) {
389         regs->al = 1;
390         disk_ret(regs, DISK_RET_ETOOMANYLOCKS);
391         return;
392     }
393     SET_EBDA2(ebda_seg, cdrom_locks[driveid], locks + 1);
394     regs->al = 1;
395     disk_ret(regs, DISK_RET_SUCCESS);
396 }
397
398 // unlock
399 static void
400 disk_134501(struct bregs *regs, u8 driveid)
401 {
402     u16 ebda_seg = get_ebda_seg();
403     u8 locks = GET_EBDA2(ebda_seg, cdrom_locks[driveid]);
404     if (locks == 0x00) {
405         regs->al = 0;
406         disk_ret(regs, DISK_RET_ENOTLOCKED);
407         return;
408     }
409     locks--;
410     SET_EBDA2(ebda_seg, cdrom_locks[driveid], locks);
411     regs->al = (locks ? 1 : 0);
412     disk_ret(regs, DISK_RET_SUCCESS);
413 }
414
415 // status
416 static void
417 disk_134502(struct bregs *regs, u8 driveid)
418 {
419     u8 locks = GET_EBDA(cdrom_locks[driveid]);
420     regs->al = (locks ? 1 : 0);
421     disk_ret(regs, DISK_RET_SUCCESS);
422 }
423
424 static void
425 disk_1345XX(struct bregs *regs, u8 driveid)
426 {
427     disk_ret(regs, DISK_RET_EPARAM);
428 }
429
430 // IBM/MS lock/unlock drive
431 static void
432 disk_1345(struct bregs *regs, u8 driveid)
433 {
434     if (regs->dl < EXTSTART_CD) {
435         // Always success for HD
436         disk_ret(regs, DISK_RET_SUCCESS);
437         return;
438     }
439
440     switch (regs->al) {
441     case 0x00: disk_134500(regs, driveid); break;
442     case 0x01: disk_134501(regs, driveid); break;
443     case 0x02: disk_134502(regs, driveid); break;
444     default:   disk_1345XX(regs, driveid); break;
445     }
446 }
447
448 // IBM/MS eject media
449 static void
450 disk_1346(struct bregs *regs, u8 driveid)
451 {
452     if (regs->dl < EXTSTART_CD) {
453         // Volume Not Removable
454         disk_ret(regs, DISK_RET_ENOTREMOVABLE);
455         return;
456     }
457
458     u8 locks = GET_EBDA(cdrom_locks[driveid]);
459     if (locks != 0) {
460         disk_ret(regs, DISK_RET_ELOCKED);
461         return;
462     }
463
464     // FIXME should handle 0x31 no media in device
465     // FIXME should handle 0xb5 valid request failed
466
467     // Call removable media eject
468     struct bregs br;
469     memset(&br, 0, sizeof(br));
470     br.ah = 0x52;
471     call16_int(0x15, &br);
472
473     if (br.ah || br.flags & F_CF) {
474         disk_ret(regs, DISK_RET_ELOCKED);
475         return;
476     }
477     disk_ret(regs, DISK_RET_SUCCESS);
478 }
479
480 // IBM/MS extended seek
481 static void
482 disk_1347(struct bregs *regs, u8 driveid)
483 {
484     extended_access(regs, driveid, CMD_SEEK);
485 }
486
487 // IBM/MS get drive parameters
488 static void
489 disk_1348(struct bregs *regs, u8 driveid)
490 {
491     u16 size = GET_INT13DPT(regs, size);
492
493     // Buffer is too small
494     if (size < 26) {
495         disk_ret(regs, DISK_RET_EPARAM);
496         return;
497     }
498
499     // EDD 1.x
500
501     u8  type    = GET_GLOBAL(Drives.drives[driveid].type);
502     u16 npc     = GET_GLOBAL(Drives.drives[driveid].pchs.cylinders);
503     u16 nph     = GET_GLOBAL(Drives.drives[driveid].pchs.heads);
504     u16 npspt   = GET_GLOBAL(Drives.drives[driveid].pchs.spt);
505     u64 lba     = GET_GLOBAL(Drives.drives[driveid].sectors);
506     u16 blksize = GET_GLOBAL(Drives.drives[driveid].blksize);
507
508     dprintf(DEBUG_HDL_13, "disk_1348 size=%d t=%d chs=%d,%d,%d lba=%d bs=%d\n"
509             , size, type, npc, nph, npspt, (u32)lba, blksize);
510
511     SET_INT13DPT(regs, size, 26);
512     if (type == DTYPE_ATAPI) {
513         // 0x74 = removable, media change, lockable, max values
514         SET_INT13DPT(regs, infos, 0x74);
515         SET_INT13DPT(regs, cylinders, 0xffffffff);
516         SET_INT13DPT(regs, heads, 0xffffffff);
517         SET_INT13DPT(regs, spt, 0xffffffff);
518         SET_INT13DPT(regs, sector_count, (u64)-1);
519     } else {
520         if (lba > (u64)npspt*nph*0x3fff) {
521             SET_INT13DPT(regs, infos, 0x00); // geometry is invalid
522             SET_INT13DPT(regs, cylinders, 0x3fff);
523         } else {
524             SET_INT13DPT(regs, infos, 0x02); // geometry is valid
525             SET_INT13DPT(regs, cylinders, (u32)npc);
526         }
527         SET_INT13DPT(regs, heads, (u32)nph);
528         SET_INT13DPT(regs, spt, (u32)npspt);
529         SET_INT13DPT(regs, sector_count, lba);
530     }
531     SET_INT13DPT(regs, blksize, blksize);
532
533     if (size < 30 || (type != DTYPE_ATA && type != DTYPE_ATAPI)) {
534         disk_ret(regs, DISK_RET_SUCCESS);
535         return;
536     }
537
538     // EDD 2.x
539
540     u16 ebda_seg = get_ebda_seg();
541     SET_INT13DPT(regs, size, 30);
542
543     SET_INT13DPT(regs, dpte_segment, ebda_seg);
544     SET_INT13DPT(regs, dpte_offset
545                  , offsetof(struct extended_bios_data_area_s, dpte));
546
547     // Fill in dpte
548     u8 ataid = GET_GLOBAL(Drives.drives[driveid].cntl_id);
549     u8 channel = ataid / 2;
550     u8 slave = ataid % 2;
551     u16 iobase1 = GET_GLOBAL(ATA_channels[channel].iobase1);
552     u16 iobase2 = GET_GLOBAL(ATA_channels[channel].iobase2);
553     u8 irq = GET_GLOBAL(ATA_channels[channel].irq);
554
555     u16 options = 0;
556     if (type == DTYPE_ATA) {
557         u8 translation = GET_GLOBAL(Drives.drives[driveid].translation);
558         if (translation != TRANSLATION_NONE) {
559             options |= 1<<3; // CHS translation
560             if (translation == TRANSLATION_LBA)
561                 options |= 1<<9;
562             if (translation == TRANSLATION_RECHS)
563                 options |= 3<<9;
564         }
565     } else {
566         // ATAPI
567         options |= 1<<5; // removable device
568         options |= 1<<6; // atapi device
569     }
570     options |= 1<<4; // lba translation
571     if (CONFIG_ATA_PIO32)
572         options |= 1<<7;
573
574     SET_EBDA2(ebda_seg, dpte.iobase1, iobase1);
575     SET_EBDA2(ebda_seg, dpte.iobase2, iobase2 + ATA_CB_DC);
576     SET_EBDA2(ebda_seg, dpte.prefix, ((slave ? ATA_CB_DH_DEV1 : ATA_CB_DH_DEV0)
577                                       | ATA_CB_DH_LBA));
578     SET_EBDA2(ebda_seg, dpte.unused, 0xcb);
579     SET_EBDA2(ebda_seg, dpte.irq, irq);
580     SET_EBDA2(ebda_seg, dpte.blkcount, 1);
581     SET_EBDA2(ebda_seg, dpte.dma, 0);
582     SET_EBDA2(ebda_seg, dpte.pio, 0);
583     SET_EBDA2(ebda_seg, dpte.options, options);
584     SET_EBDA2(ebda_seg, dpte.reserved, 0);
585     SET_EBDA2(ebda_seg, dpte.revision, 0x11);
586
587     u8 sum = checksum_far(
588         ebda_seg, (void*)offsetof(struct extended_bios_data_area_s, dpte), 15);
589     SET_EBDA2(ebda_seg, dpte.checksum, -sum);
590
591     if (size < 66) {
592         disk_ret(regs, DISK_RET_SUCCESS);
593         return;
594     }
595
596     // EDD 3.x
597     SET_INT13DPT(regs, key, 0xbedd);
598     SET_INT13DPT(regs, dpi_length, 36);
599     SET_INT13DPT(regs, reserved1, 0);
600     SET_INT13DPT(regs, reserved2, 0);
601
602     SET_INT13DPT(regs, host_bus[0], 'P');
603     SET_INT13DPT(regs, host_bus[1], 'C');
604     SET_INT13DPT(regs, host_bus[2], 'I');
605     SET_INT13DPT(regs, host_bus[3], 0);
606
607     u32 bdf = GET_GLOBAL(ATA_channels[channel].pci_bdf);
608     u32 path = (pci_bdf_to_bus(bdf) | (pci_bdf_to_dev(bdf) << 8)
609                 | (pci_bdf_to_fn(bdf) << 16));
610     SET_INT13DPT(regs, iface_path, path);
611
612     SET_INT13DPT(regs, iface_type[0], 'A');
613     SET_INT13DPT(regs, iface_type[1], 'T');
614     SET_INT13DPT(regs, iface_type[2], 'A');
615     SET_INT13DPT(regs, iface_type[3], 0);
616     SET_INT13DPT(regs, iface_type[4], 0);
617     SET_INT13DPT(regs, iface_type[5], 0);
618     SET_INT13DPT(regs, iface_type[6], 0);
619     SET_INT13DPT(regs, iface_type[7], 0);
620
621     SET_INT13DPT(regs, device_path, slave);
622
623     SET_INT13DPT(regs, checksum
624                  , -checksum_far(regs->ds, (void*)(regs->si+30), 35));
625
626     disk_ret(regs, DISK_RET_SUCCESS);
627 }
628
629 // IBM/MS extended media change
630 static void
631 disk_1349(struct bregs *regs, u8 driveid)
632 {
633     if (regs->dl < EXTSTART_CD) {
634         // Always success for HD
635         disk_ret(regs, DISK_RET_SUCCESS);
636         return;
637     }
638     set_fail(regs);
639     // always send changed ??
640     regs->ah = DISK_RET_ECHANGED;
641 }
642
643 static void
644 disk_134e01(struct bregs *regs, u8 driveid)
645 {
646     disk_ret(regs, DISK_RET_SUCCESS);
647 }
648
649 static void
650 disk_134e03(struct bregs *regs, u8 driveid)
651 {
652     disk_ret(regs, DISK_RET_SUCCESS);
653 }
654
655 static void
656 disk_134e04(struct bregs *regs, u8 driveid)
657 {
658     disk_ret(regs, DISK_RET_SUCCESS);
659 }
660
661 static void
662 disk_134e06(struct bregs *regs, u8 driveid)
663 {
664     disk_ret(regs, DISK_RET_SUCCESS);
665 }
666
667 static void
668 disk_134eXX(struct bregs *regs, u8 driveid)
669 {
670     disk_ret(regs, DISK_RET_EPARAM);
671 }
672
673 // IBM/MS set hardware configuration
674 static void
675 disk_134e(struct bregs *regs, u8 driveid)
676 {
677     switch (regs->al) {
678     case 0x01: disk_134e01(regs, driveid); break;
679     case 0x03: disk_134e03(regs, driveid); break;
680     case 0x04: disk_134e04(regs, driveid); break;
681     case 0x06: disk_134e06(regs, driveid); break;
682     default:   disk_134eXX(regs, driveid); break;
683     }
684 }
685
686 static void
687 disk_13XX(struct bregs *regs, u8 driveid)
688 {
689     disk_ret(regs, DISK_RET_EPARAM);
690 }
691
692 static void
693 disk_13(struct bregs *regs, u8 driveid)
694 {
695     //debug_stub(regs);
696
697     // clear completion flag
698     SET_BDA(disk_interrupt_flag, 0);
699
700     switch (regs->ah) {
701     case 0x00: disk_1300(regs, driveid); break;
702     case 0x01: disk_1301(regs, driveid); break;
703     case 0x02: disk_1302(regs, driveid); break;
704     case 0x03: disk_1303(regs, driveid); break;
705     case 0x04: disk_1304(regs, driveid); break;
706     case 0x05: disk_1305(regs, driveid); break;
707     case 0x08: disk_1308(regs, driveid); break;
708     case 0x09: disk_1309(regs, driveid); break;
709     case 0x0c: disk_130c(regs, driveid); break;
710     case 0x0d: disk_130d(regs, driveid); break;
711     case 0x10: disk_1310(regs, driveid); break;
712     case 0x11: disk_1311(regs, driveid); break;
713     case 0x14: disk_1314(regs, driveid); break;
714     case 0x15: disk_1315(regs, driveid); break;
715     case 0x16: disk_1316(regs, driveid); break;
716     case 0x41: disk_1341(regs, driveid); break;
717     case 0x42: disk_1342(regs, driveid); break;
718     case 0x43: disk_1343(regs, driveid); break;
719     case 0x44: disk_1344(regs, driveid); break;
720     case 0x45: disk_1345(regs, driveid); break;
721     case 0x46: disk_1346(regs, driveid); break;
722     case 0x47: disk_1347(regs, driveid); break;
723     case 0x48: disk_1348(regs, driveid); break;
724     case 0x49: disk_1349(regs, driveid); break;
725     case 0x4e: disk_134e(regs, driveid); break;
726     default:   disk_13XX(regs, driveid); break;
727     }
728 }
729
730 static void
731 floppy_13(struct bregs *regs, u8 driveid)
732 {
733     // Only limited commands are supported on floppies.
734     switch (regs->ah) {
735     case 0x00:
736     case 0x01:
737     case 0x02:
738     case 0x03:
739     case 0x04:
740     case 0x05:
741     case 0x08:
742     case 0x15:
743     case 0x16:
744         disk_13(regs, driveid);
745         break;
746     default:   disk_13XX(regs, driveid); break;
747     }
748 }
749
750
751 /****************************************************************
752  * Entry points
753  ****************************************************************/
754
755 static int
756 get_driveid(struct bregs *regs, u8 exttype, u8 extdriveoffset)
757 {
758     // basic check : device has to be defined
759     if (extdriveoffset >= ARRAY_SIZE(Drives.idmap[0]))
760         return -1;
761
762     // Get the ata channel
763     u8 driveid = GET_GLOBAL(Drives.idmap[exttype][extdriveoffset]);
764
765     // basic check : device has to be valid
766     if (driveid >= ARRAY_SIZE(Drives.drives))
767         return -1;
768
769     return driveid;
770 }
771
772 static void
773 handle_legacy_disk(struct bregs *regs, u8 extdrive)
774 {
775     if (! CONFIG_DRIVES) {
776         // XXX - support handle_1301 anyway?
777         disk_ret(regs, DISK_RET_EPARAM);
778         return;
779     }
780
781     if (extdrive < EXTSTART_HD) {
782         int driveid = get_driveid(regs, EXTTYPE_FLOPPY, extdrive);
783         if (driveid < 0)
784             goto fail;
785         floppy_13(regs, driveid);
786         return;
787     }
788
789     int driveid;
790     if (extdrive >= EXTSTART_CD)
791         driveid = get_driveid(regs, EXTTYPE_CD, extdrive - EXTSTART_CD);
792     else
793         driveid = get_driveid(regs, EXTTYPE_HD, extdrive - EXTSTART_HD);
794     if (driveid < 0)
795         goto fail;
796     disk_13(regs, driveid);
797     return;
798
799 fail:
800     // XXX - support 1301/1308/1315 anyway?
801     disk_ret(regs, DISK_RET_EPARAM);
802 }
803
804 void VISIBLE16
805 handle_40(struct bregs *regs)
806 {
807     debug_enter(regs, DEBUG_HDL_40);
808     handle_legacy_disk(regs, regs->dl);
809 }
810
811 // INT 13h Fixed Disk Services Entry Point
812 void VISIBLE16
813 handle_13(struct bregs *regs)
814 {
815     debug_enter(regs, DEBUG_HDL_13);
816     u8 extdrive = regs->dl;
817
818     if (CONFIG_CDROM_EMU) {
819         if (regs->ah == 0x4b) {
820             cdemu_134b(regs);
821             return;
822         }
823         u16 ebda_seg = get_ebda_seg();
824         if (GET_EBDA2(ebda_seg, cdemu.active)) {
825             u8 emudrive = GET_EBDA2(ebda_seg, cdemu.emulated_extdrive);
826             if (extdrive == emudrive) {
827                 int cdemuid = GET_GLOBAL(cdemu_driveid);
828                 if (regs->ah > 0x16) {
829                     // Only old-style commands supported.
830                     disk_13XX(regs, cdemuid);
831                     return;
832                 }
833                 disk_13(regs, cdemuid);
834                 return;
835             }
836             if (extdrive < EXTSTART_CD && ((emudrive ^ extdrive) & 0x80) == 0)
837                 extdrive--;
838         }
839     }
840     handle_legacy_disk(regs, extdrive);
841 }
842
843 // record completion in BIOS task complete flag
844 void VISIBLE16
845 handle_76()
846 {
847     debug_isr(DEBUG_ISR_76);
848     SET_BDA(disk_interrupt_flag, 0xff);
849     eoi_pic2();
850 }
851
852 // Old Fixed Disk Parameter Table (newer tables are in the ebda).
853 struct fdpt_s OldFDPT VAR16FIXED(0xe401);