Simplify boot ordering by building an inclusive boot list.
[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 struct bootentry_s {
20     int type;
21     union {
22         u32 data;
23         struct segoff_s vector;
24         struct drive_s *drive;
25     };
26     int priority;
27     const char *description;
28     struct bootentry_s *next;
29 };
30
31 static struct bootentry_s *BootList;
32
33
34 /****************************************************************
35  * Boot setup
36  ****************************************************************/
37
38 static void
39 loadBootOrder(void)
40 {
41     char *f = romfile_loadfile("bootorder", NULL);
42     if (!f)
43         return;
44
45     int i;
46     IPL.fw_bootorder_count = 1;
47     while (f[i]) {
48         if (f[i] == '\n')
49             IPL.fw_bootorder_count++;
50         i++;
51     }
52     IPL.fw_bootorder = malloc_tmphigh(IPL.fw_bootorder_count*sizeof(char*));
53     if (!IPL.fw_bootorder) {
54         warn_noalloc();
55         free(f);
56         return;
57     }
58
59     dprintf(3, "boot order:\n");
60     i = 0;
61     do {
62         IPL.fw_bootorder[i] = f;
63         f = strchr(f, '\n');
64         if (f) {
65             *f = '\0';
66             f++;
67             dprintf(3, "%d: %s\n", i, IPL.fw_bootorder[i]);
68             i++;
69         }
70     } while(f);
71 }
72
73 #define DEFAULT_PRIO           9999
74
75 static int DefaultFloppyPrio = 101;
76 static int DefaultCDPrio     = 102;
77 static int DefaultHDPrio     = 103;
78 static int DefaultBEVPrio    = 104;
79
80 void
81 boot_setup(void)
82 {
83     if (! CONFIG_BOOT)
84         return;
85
86     SET_EBDA(boot_sequence, 0xffff);
87     IPL.checkfloppysig = 1;
88
89     if (!CONFIG_COREBOOT) {
90         // On emulators, get boot order from nvram.
91         if (inb_cmos(CMOS_BIOS_BOOTFLAG1) & 1)
92             IPL.checkfloppysig = 0;
93         u32 bootorder = (inb_cmos(CMOS_BIOS_BOOTFLAG2)
94                          | ((inb_cmos(CMOS_BIOS_BOOTFLAG1) & 0xf0) << 4));
95         DefaultFloppyPrio = DefaultCDPrio = DefaultHDPrio
96             = DefaultBEVPrio = DEFAULT_PRIO;
97         int i;
98         for (i=101; i<104; i++) {
99             u32 val = bootorder & 0x0f;
100             bootorder >>= 4;
101             switch (val) {
102             case 1: DefaultFloppyPrio = i; break;
103             case 2: DefaultHDPrio = i;     break;
104             case 3: DefaultCDPrio = i;     break;
105             case 4: DefaultBEVPrio = i;    break;
106             }
107         }
108     }
109
110     loadBootOrder();
111 }
112
113
114 /****************************************************************
115  * IPL and BCV handlers
116  ****************************************************************/
117
118 static void
119 bootentry_add(int type, int prio, u32 data, const char *desc)
120 {
121     if (! CONFIG_BOOT)
122         return;
123     struct bootentry_s *be = malloc_tmp(sizeof(*be));
124     if (!be) {
125         warn_noalloc();
126         return;
127     }
128     be->type = type;
129     be->priority = prio;
130     be->data = data;
131     be->description = desc;
132
133     // Add entry in sorted order.
134     struct bootentry_s **pprev;
135     for (pprev = &BootList; *pprev; pprev = &(*pprev)->next) {
136         struct bootentry_s *pos = *pprev;
137         if (be->priority < pos->priority)
138             break;
139         if (be->priority > pos->priority)
140             continue;
141         if (be->type < pos->type)
142             break;
143         if (be->type > pos->type)
144             continue;
145         if (be->type <= IPL_TYPE_CDROM
146             && (be->drive->type < pos->drive->type
147                 || (be->drive->type == pos->drive->type
148                     && be->drive->cntl_id < pos->drive->cntl_id)))
149             break;
150     }
151     be->next = *pprev;
152     *pprev = be;
153 }
154
155 // Add a BEV vector for a given pnp compatible option rom.
156 void
157 boot_add_bev(u16 seg, u16 bev, u16 desc)
158 {
159     bootentry_add(IPL_TYPE_BEV, DefaultBEVPrio, SEGOFF(seg, bev).segoff
160                   , desc ? MAKE_FLATPTR(seg, desc) : "Unknown");
161     DefaultBEVPrio = DEFAULT_PRIO;
162 }
163
164 // Add a bcv entry for an expansion card harddrive or legacy option rom
165 void
166 boot_add_bcv(u16 seg, u16 ip, u16 desc)
167 {
168     bootentry_add(IPL_TYPE_BCV, DEFAULT_PRIO, SEGOFF(seg, ip).segoff
169                   , desc ? MAKE_FLATPTR(seg, desc) : "Legacy option rom");
170 }
171
172 void
173 boot_add_floppy(struct drive_s *drive_g)
174 {
175     bootentry_add(IPL_TYPE_FLOPPY, DefaultFloppyPrio, (u32)drive_g
176                   , drive_g->desc);
177 }
178
179 void
180 boot_add_hd(struct drive_s *drive_g)
181 {
182     bootentry_add(IPL_TYPE_HARDDISK, DefaultHDPrio, (u32)drive_g
183                   , drive_g->desc);
184 }
185
186 void
187 boot_add_cd(struct drive_s *drive_g)
188 {
189     bootentry_add(IPL_TYPE_CDROM, DefaultCDPrio, (u32)drive_g
190                   , drive_g->desc);
191 }
192
193 // Add a CBFS payload entry
194 void
195 boot_add_cbfs(void *data, const char *desc)
196 {
197     bootentry_add(IPL_TYPE_CBFS, DEFAULT_PRIO, (u32)data, desc);
198 }
199
200
201 /****************************************************************
202  * Boot menu and BCV execution
203  ****************************************************************/
204
205 // Show IPL option menu.
206 static void
207 interactive_bootmenu(void)
208 {
209     if (! CONFIG_BOOTMENU || ! qemu_cfg_show_boot_menu())
210         return;
211
212     while (get_keystroke(0) >= 0)
213         ;
214
215     printf("Press F12 for boot menu.\n\n");
216
217     enable_bootsplash();
218     int scan_code = get_keystroke(CONFIG_BOOTMENU_WAIT);
219     disable_bootsplash();
220     if (scan_code != 0x86)
221         /* not F12 */
222         return;
223
224     while (get_keystroke(0) >= 0)
225         ;
226
227     printf("Select boot device:\n\n");
228     wait_threads();
229
230     // Show menu items
231     struct bootentry_s *pos = BootList;
232     int maxmenu = 0;
233     while (pos) {
234         char desc[60];
235         maxmenu++;
236         printf("%d. %s\n", maxmenu
237                , strtcpy(desc, pos->description, ARRAY_SIZE(desc)));
238         pos = pos->next;
239     }
240
241     for (;;) {
242         scan_code = get_keystroke(1000);
243         if (scan_code == 0x01)
244             // ESC
245             break;
246         if (scan_code < 1 || scan_code > maxmenu+1)
247             continue;
248         int choice = scan_code - 1;
249
250         // Find entry and make top priority.
251         struct bootentry_s **pprev = &BootList;
252         while (--choice)
253             pprev = &(*pprev)->next;
254         pos = *pprev;
255         *pprev = pos->next;
256         pos->next = BootList;
257         BootList = pos;
258         pos->priority = 0;
259         break;
260     }
261     printf("\n");
262 }
263
264 static int HaveHDBoot, HaveFDBoot;
265
266 static void
267 add_bev(int type, u32 vector)
268 {
269     if (type == IPL_TYPE_HARDDISK && HaveHDBoot++)
270         return;
271     if (type == IPL_TYPE_FLOPPY && HaveFDBoot++)
272         return;
273     if (IPL.bevcount >= ARRAY_SIZE(IPL.bev))
274         return;
275     struct ipl_entry_s *bev = &IPL.bev[IPL.bevcount++];
276     bev->type = type;
277     bev->vector = vector;
278 }
279
280 // Prepare for boot - show menu and run bcvs.
281 void
282 boot_prep(void)
283 {
284     if (! CONFIG_BOOT) {
285         wait_threads();
286         return;
287     }
288
289     // XXX - show available drives?
290
291     // Allow user to modify BCV/IPL order.
292     interactive_bootmenu();
293     wait_threads();
294
295     // Map drives and populate BEV list
296     struct bootentry_s *pos = BootList;
297     while (pos) {
298         switch (pos->type) {
299         case IPL_TYPE_BCV:
300             call_bcv(pos->vector.seg, pos->vector.offset);
301             add_bev(IPL_TYPE_HARDDISK, 0);
302             break;
303         case IPL_TYPE_FLOPPY:
304             map_floppy_drive(pos->drive);
305             add_bev(IPL_TYPE_FLOPPY, 0);
306             break;
307         case IPL_TYPE_HARDDISK:
308             map_hd_drive(pos->drive);
309             add_bev(IPL_TYPE_HARDDISK, 0);
310             break;
311         case IPL_TYPE_CDROM:
312             map_cd_drive(pos->drive);
313             // NO BREAK
314         default:
315             add_bev(pos->type, pos->data);
316             break;
317         }
318         pos = pos->next;
319     }
320
321     // If nothing added a floppy/hd boot - add it manually.
322     add_bev(IPL_TYPE_FLOPPY, 0);
323     add_bev(IPL_TYPE_HARDDISK, 0);
324 }
325
326
327 /****************************************************************
328  * Boot code (int 18/19)
329  ****************************************************************/
330
331 // Jump to a bootup entry point.
332 static void
333 call_boot_entry(u16 bootseg, u16 bootip, u8 bootdrv)
334 {
335     dprintf(1, "Booting from %04x:%04x\n", bootseg, bootip);
336     struct bregs br;
337     memset(&br, 0, sizeof(br));
338     br.flags = F_IF;
339     br.code = SEGOFF(bootseg, bootip);
340     // Set the magic number in ax and the boot drive in dl.
341     br.dl = bootdrv;
342     br.ax = 0xaa55;
343     call16(&br);
344 }
345
346 // Boot from a disk (either floppy or harddrive)
347 static void
348 boot_disk(u8 bootdrv, int checksig)
349 {
350     u16 bootseg = 0x07c0;
351
352     // Read sector
353     struct bregs br;
354     memset(&br, 0, sizeof(br));
355     br.flags = F_IF;
356     br.dl = bootdrv;
357     br.es = bootseg;
358     br.ah = 2;
359     br.al = 1;
360     br.cl = 1;
361     call16_int(0x13, &br);
362
363     if (br.flags & F_CF) {
364         printf("Boot failed: could not read the boot disk\n\n");
365         return;
366     }
367
368     if (checksig) {
369         struct mbr_s *mbr = (void*)0;
370         if (GET_FARVAR(bootseg, mbr->signature) != MBR_SIGNATURE) {
371             printf("Boot failed: not a bootable disk\n\n");
372             return;
373         }
374     }
375
376     /* Canonicalize bootseg:bootip */
377     u16 bootip = (bootseg & 0x0fff) << 4;
378     bootseg &= 0xf000;
379
380     call_boot_entry(bootseg, bootip, bootdrv);
381 }
382
383 // Boot from a CD-ROM
384 static void
385 boot_cdrom(struct ipl_entry_s *ie)
386 {
387     if (! CONFIG_CDROM_BOOT)
388         return;
389
390     if (!ie->vector)
391         return;
392     printf("Booting from DVD/CD...\n");
393
394     struct drive_s *drive_g = (void*)ie->vector;
395     int status = cdrom_boot(drive_g);
396     if (status) {
397         printf("Boot failed: Could not read from CDROM %s (code %04x)\n", drive_g->desc, status);
398         return;
399     }
400
401     u16 ebda_seg = get_ebda_seg();
402     u8 bootdrv = GET_EBDA2(ebda_seg, cdemu.emulated_extdrive);
403     u16 bootseg = GET_EBDA2(ebda_seg, cdemu.load_segment);
404     /* Canonicalize bootseg:bootip */
405     u16 bootip = (bootseg & 0x0fff) << 4;
406     bootseg &= 0xf000;
407
408     call_boot_entry(bootseg, bootip, bootdrv);
409 }
410
411 // Boot from a CBFS payload
412 static void
413 boot_cbfs(struct ipl_entry_s *ie)
414 {
415     if (!CONFIG_COREBOOT || !CONFIG_COREBOOT_FLASH)
416         return;
417     printf("Booting from CBFS...\n");
418     cbfs_run_payload((void*)ie->vector);
419 }
420
421 static void
422 do_boot(u16 seq_nr)
423 {
424     if (! CONFIG_BOOT)
425         panic("Boot support not compiled in.\n");
426
427     if (seq_nr >= IPL.bevcount) {
428         printf("No bootable device.\n");
429         // Loop with irqs enabled - this allows ctrl+alt+delete to work.
430         for (;;)
431             wait_irq();
432     }
433
434     // Boot the given BEV type.
435     struct ipl_entry_s *ie = &IPL.bev[seq_nr];
436     switch (ie->type) {
437     case IPL_TYPE_FLOPPY:
438         printf("Booting from Floppy...\n");
439         boot_disk(0x00, IPL.checkfloppysig);
440         break;
441     case IPL_TYPE_HARDDISK:
442         printf("Booting from Hard Disk...\n");
443         boot_disk(0x80, 1);
444         break;
445     case IPL_TYPE_CDROM:
446         boot_cdrom(ie);
447         break;
448     case IPL_TYPE_CBFS:
449         boot_cbfs(ie);
450         break;
451     case IPL_TYPE_BEV:
452         printf("Booting from ROM...\n");
453         call_boot_entry(ie->vector >> 16, ie->vector & 0xffff, 0);
454         break;
455     }
456
457     // Boot failed: invoke the boot recovery function
458     struct bregs br;
459     memset(&br, 0, sizeof(br));
460     br.flags = F_IF;
461     call16_int(0x18, &br);
462 }
463
464 // Boot Failure recovery: try the next device.
465 void VISIBLE32FLAT
466 handle_18(void)
467 {
468     debug_serial_setup();
469     debug_enter(NULL, DEBUG_HDL_18);
470     u16 ebda_seg = get_ebda_seg();
471     u16 seq = GET_EBDA2(ebda_seg, boot_sequence) + 1;
472     SET_EBDA2(ebda_seg, boot_sequence, seq);
473     do_boot(seq);
474 }
475
476 // INT 19h Boot Load Service Entry Point
477 void VISIBLE32FLAT
478 handle_19(void)
479 {
480     debug_serial_setup();
481     debug_enter(NULL, DEBUG_HDL_19);
482     SET_EBDA(boot_sequence, 0);
483     do_boot(0);
484 }