Breakup boot_setup() bootorder code into its own function.
[seabios.git] / src / boot.c
1 // Code to load disk image and start system boot.
2 //
3 // Copyright (C) 2008-2010  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 "util.h" // dprintf
9 #include "biosvar.h" // GET_EBDA
10 #include "config.h" // CONFIG_*
11 #include "disk.h" // cdrom_boot
12 #include "bregs.h" // struct bregs
13 #include "boot.h" // struct ipl_s
14 #include "cmos.h" // inb_cmos
15 #include "paravirt.h"
16
17 struct ipl_s IPL;
18
19
20 /****************************************************************
21  * Boot setup
22  ****************************************************************/
23
24 static void
25 loadBootOrder(void)
26 {
27     char *f = romfile_loadfile("bootorder", NULL);
28     if (!f)
29         return;
30
31     int i;
32     IPL.fw_bootorder_count = 1;
33     while (f[i]) {
34         if (f[i] == '\n')
35             IPL.fw_bootorder_count++;
36         i++;
37     }
38     IPL.fw_bootorder = malloc_tmphigh(IPL.fw_bootorder_count*sizeof(char*));
39     if (!IPL.fw_bootorder) {
40         warn_noalloc();
41         free(f);
42         return;
43     }
44
45     dprintf(3, "boot order:\n");
46     i = 0;
47     do {
48         IPL.fw_bootorder[i] = f;
49         f = strchr(f, '\n');
50         if (f) {
51             *f = '\0';
52             f++;
53             dprintf(3, "%d: %s\n", i, IPL.fw_bootorder[i]);
54             i++;
55         }
56     } while(f);
57 }
58
59 void
60 boot_setup(void)
61 {
62     if (! CONFIG_BOOT)
63         return;
64     dprintf(3, "init boot device ordering\n");
65
66     memset(&IPL, 0, sizeof(IPL));
67     struct ipl_entry_s *ie = &IPL.bev[0];
68
69     // Floppy drive
70     ie->type = IPL_TYPE_FLOPPY;
71     ie->description = "Floppy";
72     ie++;
73
74     // First HDD
75     ie->type = IPL_TYPE_HARDDISK;
76     ie->description = "Hard Disk";
77     ie++;
78
79     // CDROM
80     if (CONFIG_CDROM_BOOT) {
81         ie->type = IPL_TYPE_CDROM;
82         ie->description = "DVD/CD";
83         ie++;
84     }
85
86     if (CONFIG_COREBOOT && CONFIG_COREBOOT_FLASH) {
87         ie->type = IPL_TYPE_CBFS;
88         ie->description = "CBFS";
89         ie++;
90     }
91
92     IPL.bevcount = ie - IPL.bev;
93     SET_EBDA(boot_sequence, 0xffff);
94     if (CONFIG_COREBOOT) {
95         // XXX - hardcode defaults for coreboot.
96         IPL.bootorder = 0x87654231;
97         IPL.checkfloppysig = 1;
98     } else {
99         // On emulators, get boot order from nvram.
100         IPL.bootorder = (inb_cmos(CMOS_BIOS_BOOTFLAG2)
101                          | ((inb_cmos(CMOS_BIOS_BOOTFLAG1) & 0xf0) << 4));
102         if (!(inb_cmos(CMOS_BIOS_BOOTFLAG1) & 1))
103             IPL.checkfloppysig = 1;
104     }
105
106     loadBootOrder();
107 }
108
109
110 /****************************************************************
111  * IPL and BCV handlers
112  ****************************************************************/
113
114 // Add a BEV vector for a given pnp compatible option rom.
115 void
116 add_bev(u16 seg, u16 bev, u16 desc)
117 {
118     if (! CONFIG_BOOT)
119         return;
120     if (IPL.bevcount >= ARRAY_SIZE(IPL.bev))
121         return;
122
123     struct ipl_entry_s *ie = &IPL.bev[IPL.bevcount++];
124     ie->type = IPL_TYPE_BEV;
125     ie->vector = (seg << 16) | bev;
126     const char *d = "Unknown";
127     if (desc)
128         d = MAKE_FLATPTR(seg, desc);
129     ie->description = d;
130 }
131
132 // Add a IPL entry for BAID cdrom.
133 void
134 add_baid_cdrom(struct drive_s *drive_g)
135 {
136     if (! CONFIG_CDROM_BOOT)
137         return;
138
139     /* put first cdrom into ipl 3 for compatability with qemu */
140     struct ipl_entry_s *ie = &IPL.bev[2];
141     if (IPL.bevcount >= ARRAY_SIZE(IPL.bev) && ie->vector)
142         return;
143
144     if (ie->vector)
145         ie = &IPL.bev[IPL.bevcount++];
146     ie->type = IPL_TYPE_CDROM;
147     ie->vector = (u32)drive_g;
148     ie->description = "DVD/CD";
149 }
150
151 // Add a bcv entry for an expansion card harddrive or legacy option rom
152 void
153 add_bcv(u16 seg, u16 ip, u16 desc)
154 {
155     if (! CONFIG_BOOT)
156         return;
157     if (IPL.bcvcount >= ARRAY_SIZE(IPL.bcv))
158         return;
159
160     struct ipl_entry_s *ie = &IPL.bcv[IPL.bcvcount++];
161     ie->type = BCV_TYPE_EXTERNAL;
162     ie->vector = (seg << 16) | ip;
163     const char *d = "Legacy option rom";
164     if (desc)
165         d = MAKE_FLATPTR(seg, desc);
166     ie->description = d;
167 }
168
169 // Add a bcv entry for an internal harddrive
170 void
171 add_bcv_internal(struct drive_s *drive_g)
172 {
173     if (! CONFIG_BOOT)
174         return;
175     if (IPL.bcvcount >= ARRAY_SIZE(IPL.bcv))
176         return;
177
178     struct ipl_entry_s *ie = &IPL.bcv[IPL.bcvcount++];
179     if (CONFIG_THREADS) {
180         // Add to bcv list with assured drive order.
181         struct ipl_entry_s *end = ie;
182         for (;;) {
183             struct ipl_entry_s *prev = ie - 1;
184             if (prev < IPL.bcv || prev->type != BCV_TYPE_INTERNAL)
185                 break;
186             struct drive_s *prevdrive = (void*)prev->vector;
187             if (prevdrive->type < drive_g->type
188                 || (prevdrive->type == drive_g->type
189                     && prevdrive->cntl_id < drive_g->cntl_id))
190                 break;
191             ie--;
192         }
193         if (ie != end)
194             memmove(ie+1, ie, (void*)end-(void*)ie);
195     }
196     ie->type = BCV_TYPE_INTERNAL;
197     ie->vector = (u32)drive_g;
198     ie->description = "";
199 }
200
201
202 /****************************************************************
203  * Boot menu and BCV execution
204  ****************************************************************/
205
206 // Show a generic menu item
207 static int
208 menu_show_default(struct ipl_entry_s *ie, int menupos)
209 {
210     char desc[33];
211     printf("%d. %s\n", menupos
212            , strtcpy(desc, ie->description, ARRAY_SIZE(desc)));
213     return 1;
214 }
215
216 // Show floppy menu item - but only if there exists a floppy drive.
217 static int
218 menu_show_floppy(struct ipl_entry_s *ie, int menupos)
219 {
220     int i;
221     for (i = 0; i < Drives.floppycount; i++) {
222         struct drive_s *drive_g = getDrive(EXTTYPE_FLOPPY, i);
223         printf("%d. Floppy [%s]\n", menupos + i, drive_g->desc);
224     }
225     return Drives.floppycount;
226 }
227
228 // Show menu items from BCV list.
229 static int
230 menu_show_harddisk(struct ipl_entry_s *ie, int menupos)
231 {
232     int i;
233     for (i = 0; i < IPL.bcvcount; i++) {
234         struct ipl_entry_s *ie = &IPL.bcv[i];
235         struct drive_s *drive_g = (void*)ie->vector;
236         switch (ie->type) {
237         case BCV_TYPE_INTERNAL:
238             printf("%d. %s\n", menupos + i, drive_g->desc);
239             break;
240         default:
241             menu_show_default(ie, menupos+i);
242             break;
243         }
244     }
245     return IPL.bcvcount;
246 }
247
248 // Show cdrom menu item - but only if there exists a cdrom drive.
249 static int
250 menu_show_cdrom(struct ipl_entry_s *ie, int menupos)
251 {
252     struct drive_s *drive_g = (void*)ie->vector;
253     if (!ie->vector)
254         return 0;
255     printf("%d. DVD/CD [%s]\n", menupos, drive_g->desc);
256     return 1;
257 }
258
259 // Show coreboot-fs menu item.
260 static int
261 menu_show_cbfs(struct ipl_entry_s *ie, int menupos)
262 {
263     int count = 0;
264     struct cbfs_file *file = NULL;
265     for (;;) {
266         file = cbfs_findprefix("img/", file);
267         if (!file)
268             break;
269         const char *filename = cbfs_filename(file);
270         printf("%d. Payload [%s]\n", menupos + count, &filename[4]);
271         count++;
272         if (count > 8)
273             break;
274     }
275     return count;
276 }
277
278 // Show IPL option menu.
279 static void
280 interactive_bootmenu(void)
281 {
282     if (! CONFIG_BOOTMENU || ! qemu_cfg_show_boot_menu())
283         return;
284
285     while (get_keystroke(0) >= 0)
286         ;
287
288     printf("Press F12 for boot menu.\n\n");
289
290     enable_bootsplash();
291     int scan_code = get_keystroke(CONFIG_BOOTMENU_WAIT);
292     disable_bootsplash();
293     if (scan_code != 0x86)
294         /* not F12 */
295         return;
296
297     while (get_keystroke(0) >= 0)
298         ;
299
300     printf("Select boot device:\n\n");
301     wait_threads();
302
303     int subcount[ARRAY_SIZE(IPL.bev)];
304     int menupos = 1;
305     int i;
306     for (i = 0; i < IPL.bevcount; i++) {
307         struct ipl_entry_s *ie = &IPL.bev[i];
308         int sc;
309         switch (ie->type) {
310         case IPL_TYPE_FLOPPY:
311             sc = menu_show_floppy(ie, menupos);
312             break;
313         case IPL_TYPE_HARDDISK:
314             sc = menu_show_harddisk(ie, menupos);
315             break;
316         case IPL_TYPE_CDROM:
317             sc = menu_show_cdrom(ie, menupos);
318             break;
319         case IPL_TYPE_CBFS:
320             sc = menu_show_cbfs(ie, menupos);
321             break;
322         default:
323             sc = menu_show_default(ie, menupos);
324             break;
325         }
326         subcount[i] = sc;
327         menupos += sc;
328     }
329
330     for (;;) {
331         scan_code = get_keystroke(1000);
332         if (scan_code == 0x01)
333             // ESC
334             break;
335         if (scan_code < 1 || scan_code > menupos)
336             continue;
337         int choice = scan_code - 1;
338
339         // Find out which IPL this was for.
340         int bev = 0;
341         while (choice > subcount[bev]) {
342             choice -= subcount[bev];
343             bev++;
344         }
345         IPL.bev[bev].subchoice = choice-1;
346
347         // Add user choice to the boot order.
348         IPL.bootorder = (IPL.bootorder << 4) | (bev+1);
349         break;
350     }
351     printf("\n");
352 }
353
354 // Run the specified bcv.
355 static void
356 run_bcv(struct ipl_entry_s *ie)
357 {
358     switch (ie->type) {
359     case BCV_TYPE_INTERNAL:
360         map_hd_drive((void*)ie->vector);
361         break;
362     case BCV_TYPE_EXTERNAL:
363         call_bcv(ie->vector >> 16, ie->vector & 0xffff);
364         break;
365     }
366 }
367
368 // Prepare for boot - show menu and run bcvs.
369 void
370 boot_prep(void)
371 {
372     if (! CONFIG_BOOT) {
373         wait_threads();
374         return;
375     }
376
377     // XXX - show available drives?
378
379     // Allow user to modify BCV/IPL order.
380     interactive_bootmenu();
381     wait_threads();
382
383     // Setup floppy boot order
384     int override = IPL.bev[0].subchoice;
385     struct drive_s *tmp = Drives.idmap[EXTTYPE_FLOPPY][0];
386     Drives.idmap[EXTTYPE_FLOPPY][0] = Drives.idmap[EXTTYPE_FLOPPY][override];
387     Drives.idmap[EXTTYPE_FLOPPY][override] = tmp;
388
389     // Run BCVs
390     override = IPL.bev[1].subchoice;
391     if (override < IPL.bcvcount)
392         run_bcv(&IPL.bcv[override]);
393     int i;
394     for (i=0; i<IPL.bcvcount; i++)
395         if (i != override)
396             run_bcv(&IPL.bcv[i]);
397 }
398
399
400 /****************************************************************
401  * Boot code (int 18/19)
402  ****************************************************************/
403
404 // Jump to a bootup entry point.
405 static void
406 call_boot_entry(u16 bootseg, u16 bootip, u8 bootdrv)
407 {
408     dprintf(1, "Booting from %04x:%04x\n", bootseg, bootip);
409     struct bregs br;
410     memset(&br, 0, sizeof(br));
411     br.flags = F_IF;
412     br.code = SEGOFF(bootseg, bootip);
413     // Set the magic number in ax and the boot drive in dl.
414     br.dl = bootdrv;
415     br.ax = 0xaa55;
416     call16(&br);
417 }
418
419 // Boot from a disk (either floppy or harddrive)
420 static void
421 boot_disk(u8 bootdrv, int checksig)
422 {
423     u16 bootseg = 0x07c0;
424
425     // Read sector
426     struct bregs br;
427     memset(&br, 0, sizeof(br));
428     br.flags = F_IF;
429     br.dl = bootdrv;
430     br.es = bootseg;
431     br.ah = 2;
432     br.al = 1;
433     br.cl = 1;
434     call16_int(0x13, &br);
435
436     if (br.flags & F_CF) {
437         printf("Boot failed: could not read the boot disk\n\n");
438         return;
439     }
440
441     if (checksig) {
442         struct mbr_s *mbr = (void*)0;
443         if (GET_FARVAR(bootseg, mbr->signature) != MBR_SIGNATURE) {
444             printf("Boot failed: not a bootable disk\n\n");
445             return;
446         }
447     }
448
449     /* Canonicalize bootseg:bootip */
450     u16 bootip = (bootseg & 0x0fff) << 4;
451     bootseg &= 0xf000;
452
453     call_boot_entry(bootseg, bootip, bootdrv);
454 }
455
456 // Boot from a CD-ROM
457 static void
458 boot_cdrom(struct ipl_entry_s *ie)
459 {
460     if (! CONFIG_CDROM_BOOT)
461         return;
462
463     if (!ie->vector)
464         return;
465
466     struct drive_s *drive_g = (void*)ie->vector;
467     int status = cdrom_boot(drive_g);
468     if (status) {
469         printf("Boot failed: Could not read from CDROM %s (code %04x)\n", drive_g->desc, status);
470         return;
471     }
472
473     u16 ebda_seg = get_ebda_seg();
474     u8 bootdrv = GET_EBDA2(ebda_seg, cdemu.emulated_extdrive);
475     u16 bootseg = GET_EBDA2(ebda_seg, cdemu.load_segment);
476     /* Canonicalize bootseg:bootip */
477     u16 bootip = (bootseg & 0x0fff) << 4;
478     bootseg &= 0xf000;
479
480     call_boot_entry(bootseg, bootip, bootdrv);
481 }
482
483 // Boot from a CBFS payload
484 static void
485 boot_cbfs(struct ipl_entry_s *ie)
486 {
487     if (!CONFIG_COREBOOT || !CONFIG_COREBOOT_FLASH)
488         return;
489     int count = ie->subchoice;
490     struct cbfs_file *file = NULL;
491     for (;;) {
492         file = cbfs_findprefix("img/", file);
493         if (!file)
494             return;
495         if (count--)
496             continue;
497         cbfs_run_payload(file);
498     }
499 }
500
501 static void
502 do_boot(u16 seq_nr)
503 {
504     if (! CONFIG_BOOT)
505         panic("Boot support not compiled in.\n");
506
507     u32 bootdev = IPL.bootorder;
508     bootdev >>= 4 * seq_nr;
509     bootdev &= 0xf;
510
511     /* Translate bootdev to an IPL table offset by subtracting 1 */
512     bootdev -= 1;
513
514     if (bootdev >= IPL.bevcount) {
515         printf("No bootable device.\n");
516         // Loop with irqs enabled - this allows ctrl+alt+delete to work.
517         for (;;)
518             wait_irq();
519     }
520
521     /* Do the loading, and set up vector as a far pointer to the boot
522      * address, and bootdrv as the boot drive */
523     struct ipl_entry_s *ie = &IPL.bev[bootdev];
524     char desc[33];
525     printf("Booting from %s...\n"
526            , strtcpy(desc, ie->description, ARRAY_SIZE(desc)));
527
528     switch (ie->type) {
529     case IPL_TYPE_FLOPPY:
530         boot_disk(0x00, IPL.checkfloppysig);
531         break;
532     case IPL_TYPE_HARDDISK:
533         boot_disk(0x80, 1);
534         break;
535     case IPL_TYPE_CDROM:
536         boot_cdrom(ie);
537         break;
538     case IPL_TYPE_CBFS:
539         boot_cbfs(ie);
540         break;
541     case IPL_TYPE_BEV:
542         call_boot_entry(ie->vector >> 16, ie->vector & 0xffff, 0);
543         break;
544     }
545
546     // Boot failed: invoke the boot recovery function
547     struct bregs br;
548     memset(&br, 0, sizeof(br));
549     br.flags = F_IF;
550     call16_int(0x18, &br);
551 }
552
553 // Boot Failure recovery: try the next device.
554 void VISIBLE32FLAT
555 handle_18(void)
556 {
557     debug_serial_setup();
558     debug_enter(NULL, DEBUG_HDL_18);
559     u16 ebda_seg = get_ebda_seg();
560     u16 seq = GET_EBDA2(ebda_seg, boot_sequence) + 1;
561     SET_EBDA2(ebda_seg, boot_sequence, seq);
562     do_boot(seq);
563 }
564
565 // INT 19h Boot Load Service Entry Point
566 void VISIBLE32FLAT
567 handle_19(void)
568 {
569     debug_serial_setup();
570     debug_enter(NULL, DEBUG_HDL_19);
571     SET_EBDA(boot_sequence, 0);
572     do_boot(0);
573 }