Add functions for boot device path parsing.
[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" // func defs
14 #include "cmos.h" // inb_cmos
15 #include "paravirt.h" // romfile_loadfile
16 #include "pci.h" //pci_bdf_to_*
17
18
19 /****************************************************************
20  * Boot priority ordering
21  ****************************************************************/
22
23 static char **Bootorder;
24 static int BootorderCount;
25
26 static void
27 loadBootOrder(void)
28 {
29     char *f = romfile_loadfile("bootorder", NULL);
30     if (!f)
31         return;
32
33     int i = 0;
34     BootorderCount = 1;
35     while (f[i]) {
36         if (f[i] == '\n')
37             BootorderCount++;
38         i++;
39     }
40     Bootorder = malloc_tmphigh(BootorderCount*sizeof(char*));
41     if (!Bootorder) {
42         warn_noalloc();
43         free(f);
44         return;
45     }
46
47     dprintf(3, "boot order:\n");
48     i = 0;
49     do {
50         Bootorder[i] = f;
51         f = strchr(f, '\n');
52         if (f)
53             *(f++) = '\0';
54         dprintf(3, "%d: %s\n", i+1, Bootorder[i]);
55         i++;
56     } while (f);
57 }
58
59 // See if 'str' starts with 'glob' - if glob contains an '*' character
60 // it will match any number of characters in str that aren't a '/' or
61 // the next glob character.
62 static char *
63 glob_prefix(const char *glob, const char *str)
64 {
65     for (;;) {
66         if (!*glob && (!*str || *str == '/'))
67             return (char*)str;
68         if (*glob == '*') {
69             if (!*str || *str == '/' || *str == glob[1])
70                 glob++;
71             else
72                 str++;
73             continue;
74         }
75         if (*glob != *str)
76             return NULL;
77         glob++;
78         str++;
79     }
80 }
81
82 // Search the bootorder list for the given glob pattern.
83 static int
84 find_prio(const char *glob)
85 {
86     dprintf(1, "Searching bootorder for: %s\n", glob);
87     int i;
88     for (i = 0; i < BootorderCount; i++)
89         if (glob_prefix(glob, Bootorder[i]))
90             return i+1;
91     return -1;
92 }
93
94 #define FW_PCI_DOMAIN "/pci@i0cf8"
95
96 static char *
97 build_pci_path(char *buf, int max, const char *devname, int bdf)
98 {
99     // Build the string path of a bdf - for example: /pci@i0cf8/isa@1,2
100     char *p = buf;
101     int bus = pci_bdf_to_bus(bdf);
102     if (bus)
103         // XXX - this isn't the correct path syntax
104         p += snprintf(p, max, "/bus%x", bus);
105
106     int dev = pci_bdf_to_dev(bdf), fn = pci_bdf_to_fn(bdf);
107     p += snprintf(p, buf+max-p, "%s/%s@%x", FW_PCI_DOMAIN, devname, dev);
108     if (fn)
109         p += snprintf(p, buf+max-p, ",%x", fn);
110     return p;
111 }
112
113 int bootprio_find_pci_device(int bdf)
114 {
115     // Find pci device - for example: /pci@i0cf8/ethernet@5
116     char desc[256];
117     build_pci_path(desc, sizeof(desc), "*", bdf);
118     return find_prio(desc);
119 }
120
121 int bootprio_find_ata_device(int bdf, int chanid, int slave)
122 {
123     if (bdf == -1)
124         // support only pci machine for now
125         return -1;
126     // Find ata drive - for example: /pci@i0cf8/ide@1,1/drive@1/disk@0
127     char desc[256], *p;
128     p = build_pci_path(desc, sizeof(desc), "*", bdf);
129     snprintf(p, desc+sizeof(desc)-p, "/drive@%x/disk@%x", chanid, slave);
130     return find_prio(desc);
131 }
132
133 int bootprio_find_fdc_device(int bdf, int port, int fdid)
134 {
135     if (bdf == -1)
136         // support only pci machine for now
137         return -1;
138     // Find floppy - for example: /pci@i0cf8/isa@1/fdc@03f1/floppy@0
139     char desc[256], *p;
140     p = build_pci_path(desc, sizeof(desc), "isa", bdf);
141     snprintf(p, desc+sizeof(desc)-p, "/fdc@%04x/floppy@%x", port, fdid);
142     return find_prio(desc);
143 }
144
145 int bootprio_find_pci_rom(int bdf, int instance)
146 {
147     // Find pci rom - for example: /pci@i0cf8/scsi@3:rom2
148     char desc[256], *p;
149     p = build_pci_path(desc, sizeof(desc), "*", bdf);
150     if (instance)
151         snprintf(p, desc+sizeof(desc)-p, ":rom%d", instance);
152     return find_prio(desc);
153 }
154
155 int bootprio_find_named_rom(const char *name, int instance)
156 {
157     // Find named rom - for example: /rom@genroms/linuxboot.bin
158     char desc[256], *p;
159     p = desc + snprintf(desc, sizeof(desc), "/rom@%s", name);
160     if (instance)
161         snprintf(p, desc+sizeof(desc)-p, ":rom%d", instance);
162     return find_prio(desc);
163 }
164
165
166 /****************************************************************
167  * Boot setup
168  ****************************************************************/
169
170 static int CheckFloppySig = 1;
171
172 #define DEFAULT_PRIO           9999
173
174 static int DefaultFloppyPrio = 101;
175 static int DefaultCDPrio     = 102;
176 static int DefaultHDPrio     = 103;
177 static int DefaultBEVPrio    = 104;
178
179 void
180 boot_setup(void)
181 {
182     if (! CONFIG_BOOT)
183         return;
184
185     SET_EBDA(boot_sequence, 0xffff);
186
187     if (!CONFIG_COREBOOT) {
188         // On emulators, get boot order from nvram.
189         if (inb_cmos(CMOS_BIOS_BOOTFLAG1) & 1)
190             CheckFloppySig = 0;
191         u32 bootorder = (inb_cmos(CMOS_BIOS_BOOTFLAG2)
192                          | ((inb_cmos(CMOS_BIOS_BOOTFLAG1) & 0xf0) << 4));
193         DefaultFloppyPrio = DefaultCDPrio = DefaultHDPrio
194             = DefaultBEVPrio = DEFAULT_PRIO;
195         int i;
196         for (i=101; i<104; i++) {
197             u32 val = bootorder & 0x0f;
198             bootorder >>= 4;
199             switch (val) {
200             case 1: DefaultFloppyPrio = i; break;
201             case 2: DefaultHDPrio = i;     break;
202             case 3: DefaultCDPrio = i;     break;
203             case 4: DefaultBEVPrio = i;    break;
204             }
205         }
206     }
207
208     loadBootOrder();
209 }
210
211
212 /****************************************************************
213  * BootList handling
214  ****************************************************************/
215
216 struct bootentry_s {
217     int type;
218     union {
219         u32 data;
220         struct segoff_s vector;
221         struct drive_s *drive;
222     };
223     int priority;
224     const char *description;
225     struct bootentry_s *next;
226 };
227 static struct bootentry_s *BootList;
228
229 #define IPL_TYPE_FLOPPY      0x01
230 #define IPL_TYPE_HARDDISK    0x02
231 #define IPL_TYPE_CDROM       0x03
232 #define IPL_TYPE_CBFS        0x20
233 #define IPL_TYPE_BEV         0x80
234 #define IPL_TYPE_BCV         0x81
235
236 static void
237 bootentry_add(int type, int prio, u32 data, const char *desc)
238 {
239     if (! CONFIG_BOOT)
240         return;
241     struct bootentry_s *be = malloc_tmp(sizeof(*be));
242     if (!be) {
243         warn_noalloc();
244         return;
245     }
246     be->type = type;
247     be->priority = prio;
248     be->data = data;
249     be->description = desc ?: "?";
250     dprintf(3, "Registering bootable: %s (type:%d prio:%d data:%x)\n"
251             , be->description, type, prio, data);
252
253     // Add entry in sorted order.
254     struct bootentry_s **pprev;
255     for (pprev = &BootList; *pprev; pprev = &(*pprev)->next) {
256         struct bootentry_s *pos = *pprev;
257         if (be->priority < pos->priority)
258             break;
259         if (be->priority > pos->priority)
260             continue;
261         if (be->type < pos->type)
262             break;
263         if (be->type > pos->type)
264             continue;
265         if (be->type <= IPL_TYPE_CDROM
266             && (be->drive->type < pos->drive->type
267                 || (be->drive->type == pos->drive->type
268                     && be->drive->cntl_id < pos->drive->cntl_id)))
269             break;
270     }
271     be->next = *pprev;
272     *pprev = be;
273 }
274
275 // Return the given priority if it's set - defaultprio otherwise.
276 static inline int defPrio(int priority, int defaultprio) {
277     return (priority < 0) ? defaultprio : priority;
278 }
279
280 // Add a BEV vector for a given pnp compatible option rom.
281 void
282 boot_add_bev(u16 seg, u16 bev, u16 desc, int prio)
283 {
284     bootentry_add(IPL_TYPE_BEV, defPrio(prio, DefaultBEVPrio)
285                   , SEGOFF(seg, bev).segoff
286                   , desc ? MAKE_FLATPTR(seg, desc) : "Unknown");
287     DefaultBEVPrio = DEFAULT_PRIO;
288 }
289
290 // Add a bcv entry for an expansion card harddrive or legacy option rom
291 void
292 boot_add_bcv(u16 seg, u16 ip, u16 desc, int prio)
293 {
294     bootentry_add(IPL_TYPE_BCV, defPrio(prio, DEFAULT_PRIO)
295                   , SEGOFF(seg, ip).segoff
296                   , desc ? MAKE_FLATPTR(seg, desc) : "Legacy option rom");
297 }
298
299 void
300 boot_add_floppy(struct drive_s *drive_g, const char *desc, int prio)
301 {
302     bootentry_add(IPL_TYPE_FLOPPY, defPrio(prio, DefaultFloppyPrio)
303                   , (u32)drive_g, desc);
304 }
305
306 void
307 boot_add_hd(struct drive_s *drive_g, const char *desc, int prio)
308 {
309     bootentry_add(IPL_TYPE_HARDDISK, defPrio(prio, DefaultHDPrio)
310                   , (u32)drive_g, desc);
311 }
312
313 void
314 boot_add_cd(struct drive_s *drive_g, const char *desc, int prio)
315 {
316     bootentry_add(IPL_TYPE_CDROM, defPrio(prio, DefaultCDPrio)
317                   , (u32)drive_g, desc);
318 }
319
320 // Add a CBFS payload entry
321 void
322 boot_add_cbfs(void *data, const char *desc, int prio)
323 {
324     bootentry_add(IPL_TYPE_CBFS, defPrio(prio, DEFAULT_PRIO), (u32)data, desc);
325 }
326
327
328 /****************************************************************
329  * Boot menu and BCV execution
330  ****************************************************************/
331
332 // Show IPL option menu.
333 static void
334 interactive_bootmenu(void)
335 {
336     if (! CONFIG_BOOTMENU || ! qemu_cfg_show_boot_menu())
337         return;
338
339     while (get_keystroke(0) >= 0)
340         ;
341
342     printf("Press F12 for boot menu.\n\n");
343
344     enable_bootsplash();
345     int scan_code = get_keystroke(CONFIG_BOOTMENU_WAIT);
346     disable_bootsplash();
347     if (scan_code != 0x86)
348         /* not F12 */
349         return;
350
351     while (get_keystroke(0) >= 0)
352         ;
353
354     printf("Select boot device:\n\n");
355     wait_threads();
356
357     // Show menu items
358     struct bootentry_s *pos = BootList;
359     int maxmenu = 0;
360     while (pos) {
361         char desc[60];
362         maxmenu++;
363         printf("%d. %s\n", maxmenu
364                , strtcpy(desc, pos->description, ARRAY_SIZE(desc)));
365         pos = pos->next;
366     }
367
368     // Get key press
369     for (;;) {
370         scan_code = get_keystroke(1000);
371         if (scan_code >= 1 && scan_code <= maxmenu+1)
372             break;
373     }
374     printf("\n");
375     if (scan_code == 0x01)
376         // ESC
377         return;
378
379     // Find entry and make top priority.
380     int choice = scan_code - 1;
381     struct bootentry_s **pprev = &BootList;
382     while (--choice)
383         pprev = &(*pprev)->next;
384     pos = *pprev;
385     *pprev = pos->next;
386     pos->next = BootList;
387     BootList = pos;
388     pos->priority = 0;
389 }
390
391 // BEV (Boot Execution Vector) list
392 struct bev_s {
393     int type;
394     u32 vector;
395 };
396 static struct bev_s BEV[20];
397 static int BEVCount;
398 static int HaveHDBoot, HaveFDBoot;
399
400 static void
401 add_bev(int type, u32 vector)
402 {
403     if (type == IPL_TYPE_HARDDISK && HaveHDBoot++)
404         return;
405     if (type == IPL_TYPE_FLOPPY && HaveFDBoot++)
406         return;
407     if (BEVCount >= ARRAY_SIZE(BEV))
408         return;
409     struct bev_s *bev = &BEV[BEVCount++];
410     bev->type = type;
411     bev->vector = vector;
412 }
413
414 // Prepare for boot - show menu and run bcvs.
415 void
416 boot_prep(void)
417 {
418     if (! CONFIG_BOOT) {
419         wait_threads();
420         return;
421     }
422
423     // XXX - show available drives?
424
425     // Allow user to modify BCV/IPL order.
426     interactive_bootmenu();
427     wait_threads();
428
429     // Map drives and populate BEV list
430     struct bootentry_s *pos = BootList;
431     while (pos) {
432         switch (pos->type) {
433         case IPL_TYPE_BCV:
434             call_bcv(pos->vector.seg, pos->vector.offset);
435             add_bev(IPL_TYPE_HARDDISK, 0);
436             break;
437         case IPL_TYPE_FLOPPY:
438             map_floppy_drive(pos->drive);
439             add_bev(IPL_TYPE_FLOPPY, 0);
440             break;
441         case IPL_TYPE_HARDDISK:
442             map_hd_drive(pos->drive);
443             add_bev(IPL_TYPE_HARDDISK, 0);
444             break;
445         case IPL_TYPE_CDROM:
446             map_cd_drive(pos->drive);
447             // NO BREAK
448         default:
449             add_bev(pos->type, pos->data);
450             break;
451         }
452         pos = pos->next;
453     }
454
455     // If nothing added a floppy/hd boot - add it manually.
456     add_bev(IPL_TYPE_FLOPPY, 0);
457     add_bev(IPL_TYPE_HARDDISK, 0);
458 }
459
460
461 /****************************************************************
462  * Boot code (int 18/19)
463  ****************************************************************/
464
465 // Jump to a bootup entry point.
466 static void
467 call_boot_entry(struct segoff_s bootsegip, u8 bootdrv)
468 {
469     dprintf(1, "Booting from %04x:%04x\n", bootsegip.seg, bootsegip.offset);
470     struct bregs br;
471     memset(&br, 0, sizeof(br));
472     br.flags = F_IF;
473     br.code = bootsegip;
474     // Set the magic number in ax and the boot drive in dl.
475     br.dl = bootdrv;
476     br.ax = 0xaa55;
477     call16(&br);
478 }
479
480 // Boot from a disk (either floppy or harddrive)
481 static void
482 boot_disk(u8 bootdrv, int checksig)
483 {
484     u16 bootseg = 0x07c0;
485
486     // Read sector
487     struct bregs br;
488     memset(&br, 0, sizeof(br));
489     br.flags = F_IF;
490     br.dl = bootdrv;
491     br.es = bootseg;
492     br.ah = 2;
493     br.al = 1;
494     br.cl = 1;
495     call16_int(0x13, &br);
496
497     if (br.flags & F_CF) {
498         printf("Boot failed: could not read the boot disk\n\n");
499         return;
500     }
501
502     if (checksig) {
503         struct mbr_s *mbr = (void*)0;
504         if (GET_FARVAR(bootseg, mbr->signature) != MBR_SIGNATURE) {
505             printf("Boot failed: not a bootable disk\n\n");
506             return;
507         }
508     }
509
510     /* Canonicalize bootseg:bootip */
511     u16 bootip = (bootseg & 0x0fff) << 4;
512     bootseg &= 0xf000;
513
514     call_boot_entry(SEGOFF(bootseg, bootip), bootdrv);
515 }
516
517 // Boot from a CD-ROM
518 static void
519 boot_cdrom(struct drive_s *drive_g)
520 {
521     if (! CONFIG_CDROM_BOOT)
522         return;
523     printf("Booting from DVD/CD...\n");
524
525     int status = cdrom_boot(drive_g);
526     if (status) {
527         printf("Boot failed: Could not read from CDROM (code %04x)\n", status);
528         return;
529     }
530
531     u16 ebda_seg = get_ebda_seg();
532     u8 bootdrv = GET_EBDA2(ebda_seg, cdemu.emulated_extdrive);
533     u16 bootseg = GET_EBDA2(ebda_seg, cdemu.load_segment);
534     /* Canonicalize bootseg:bootip */
535     u16 bootip = (bootseg & 0x0fff) << 4;
536     bootseg &= 0xf000;
537
538     call_boot_entry(SEGOFF(bootseg, bootip), bootdrv);
539 }
540
541 // Boot from a CBFS payload
542 static void
543 boot_cbfs(struct cbfs_file *file)
544 {
545     if (!CONFIG_COREBOOT || !CONFIG_COREBOOT_FLASH)
546         return;
547     printf("Booting from CBFS...\n");
548     cbfs_run_payload(file);
549 }
550
551 // Boot from a BEV entry on an optionrom.
552 static void
553 boot_rom(u32 vector)
554 {
555     printf("Booting from ROM...\n");
556     struct segoff_s so;
557     so.segoff = vector;
558     call_boot_entry(so, 0);
559 }
560
561 // Determine next boot method and attempt a boot using it.
562 static void
563 do_boot(u16 seq_nr)
564 {
565     if (! CONFIG_BOOT)
566         panic("Boot support not compiled in.\n");
567
568     if (seq_nr >= BEVCount) {
569         printf("No bootable device.\n");
570         // Loop with irqs enabled - this allows ctrl+alt+delete to work.
571         for (;;)
572             wait_irq();
573     }
574
575     // Boot the given BEV type.
576     struct bev_s *ie = &BEV[seq_nr];
577     switch (ie->type) {
578     case IPL_TYPE_FLOPPY:
579         printf("Booting from Floppy...\n");
580         boot_disk(0x00, CheckFloppySig);
581         break;
582     case IPL_TYPE_HARDDISK:
583         printf("Booting from Hard Disk...\n");
584         boot_disk(0x80, 1);
585         break;
586     case IPL_TYPE_CDROM:
587         boot_cdrom((void*)ie->vector);
588         break;
589     case IPL_TYPE_CBFS:
590         boot_cbfs((void*)ie->vector);
591         break;
592     case IPL_TYPE_BEV:
593         boot_rom(ie->vector);
594         break;
595     }
596
597     // Boot failed: invoke the boot recovery function
598     struct bregs br;
599     memset(&br, 0, sizeof(br));
600     br.flags = F_IF;
601     call16_int(0x18, &br);
602 }
603
604 // Boot Failure recovery: try the next device.
605 void VISIBLE32FLAT
606 handle_18(void)
607 {
608     debug_serial_setup();
609     debug_enter(NULL, DEBUG_HDL_18);
610     u16 ebda_seg = get_ebda_seg();
611     u16 seq = GET_EBDA2(ebda_seg, boot_sequence) + 1;
612     SET_EBDA2(ebda_seg, boot_sequence, seq);
613     do_boot(seq);
614 }
615
616 // INT 19h Boot Load Service Entry Point
617 void VISIBLE32FLAT
618 handle_19(void)
619 {
620     debug_serial_setup();
621     debug_enter(NULL, DEBUG_HDL_19);
622     SET_EBDA(boot_sequence, 0);
623     do_boot(0);
624 }