Enhance boot menu to allow user to select which CD drive to boot from.
[seabios.git] / src / boot.c
1 // Code to load disk image and start system boot.
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 "util.h" // irq_enable
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
16 struct ipl_s IPL;
17
18
19 /****************************************************************
20  * IPL and BCV handlers
21  ****************************************************************/
22
23 void
24 boot_setup()
25 {
26     if (! CONFIG_BOOT)
27         return;
28     dprintf(3, "init boot device ordering\n");
29
30     memset(&IPL, 0, sizeof(IPL));
31
32     // Floppy drive
33     struct ipl_entry_s *ie = &IPL.bev[0];
34     ie->type = IPL_TYPE_FLOPPY;
35     ie->description = "Floppy";
36     ie++;
37
38     // First HDD
39     ie->type = IPL_TYPE_HARDDISK;
40     ie->description = "Hard Disk";
41     ie++;
42
43     // CDROM
44     if (CONFIG_CDROM_BOOT) {
45         ie->type = IPL_TYPE_CDROM;
46         ie->description = "CD-Rom";
47         ie++;
48     }
49
50     IPL.bevcount = ie - IPL.bev;
51     SET_EBDA(boot_sequence, 0xffff);
52     if (CONFIG_COREBOOT) {
53         // XXX - hardcode defaults for coreboot.
54         IPL.bootorder = 0x00000231;
55         IPL.checkfloppysig = 1;
56     } else {
57         // On emulators, get boot order from nvram.
58         IPL.bootorder = (inb_cmos(CMOS_BIOS_BOOTFLAG2)
59                          | ((inb_cmos(CMOS_BIOS_BOOTFLAG1) & 0xf0) << 4));
60         if (!(inb_cmos(CMOS_BIOS_BOOTFLAG1) & 1))
61             IPL.checkfloppysig = 1;
62     }
63 }
64
65 // Add a BEV vector for a given pnp compatible option rom.
66 void
67 add_bev(u16 seg, u16 bev, u16 desc)
68 {
69     if (! CONFIG_BOOT)
70         return;
71     if (IPL.bevcount >= ARRAY_SIZE(IPL.bev))
72         return;
73
74     struct ipl_entry_s *ie = &IPL.bev[IPL.bevcount++];
75     ie->type = IPL_TYPE_BEV;
76     ie->vector = (seg << 16) | bev;
77     const char *d = "Unknown";
78     if (desc)
79         d = MAKE_FLATPTR(seg, desc);
80     ie->description = d;
81 }
82
83 // Add a bcv entry for an expansion card harddrive or legacy option rom
84 void
85 add_bcv(u16 seg, u16 ip, u16 desc)
86 {
87     if (! CONFIG_BOOT)
88         return;
89     if (IPL.bcvcount >= ARRAY_SIZE(IPL.bcv))
90         return;
91
92     struct ipl_entry_s *ie = &IPL.bcv[IPL.bcvcount++];
93     ie->type = IPL_TYPE_BEV;
94     ie->vector = (seg << 16) | ip;
95     const char *d = "Legacy option rom";
96     if (desc)
97         d = MAKE_FLATPTR(seg, desc);
98     ie->description = d;
99 }
100
101 // Add a bcv entry for an ata harddrive
102 void
103 add_bcv_hd(int driveid, const char *desc)
104 {
105     if (! CONFIG_BOOT)
106         return;
107     if (IPL.bcvcount >= ARRAY_SIZE(IPL.bcv))
108         return;
109
110     struct ipl_entry_s *ie = &IPL.bcv[IPL.bcvcount++];
111     ie->type = IPL_TYPE_HARDDISK;
112     ie->vector = driveid;
113     ie->description = desc;
114 }
115
116
117 /****************************************************************
118  * Boot menu and BCV execution
119  ****************************************************************/
120
121 // Show a generic menu item
122 static int
123 menu_show_default(struct ipl_entry_s *ie, int menupos)
124 {
125     char desc[33];
126     printf("%d. %s\n", menupos
127            , strtcpy(desc, ie->description, ARRAY_SIZE(desc)));
128     return 1;
129 }
130
131 // Show floppy menu item - but only if there exists a floppy drive.
132 static int
133 menu_show_floppy(struct ipl_entry_s *ie, int menupos)
134 {
135     if (!FloppyCount)
136         return 0;
137     return menu_show_default(ie, menupos);
138 }
139
140 // Show menu items from BCV list.
141 static int
142 menu_show_harddisk(struct ipl_entry_s *ie, int menupos)
143 {
144     int i;
145     for (i = 0; i < IPL.bcvcount; i++) {
146         struct ipl_entry_s *ie = &IPL.bcv[i];
147         switch (ie->type) {
148         case IPL_TYPE_HARDDISK:
149             printf("%d. ata%d-%d %s\n", menupos + i
150                    , ie->vector / 2, ie->vector % 2, ie->description);
151             break;
152         default:
153             menu_show_default(ie, menupos+i);
154             break;
155         }
156     }
157     return IPL.bcvcount;
158 }
159
160 // Show cdrom menu item - but only if there exists a cdrom drive.
161 static int
162 menu_show_cdrom(struct ipl_entry_s *ie, int menupos)
163 {
164     int i;
165     for (i = 0; i < ATA.cdcount; i++) {
166         int driveid = ATA.idmap[1][i];
167         printf("%d. CD-Rom [ata%d-%d %s]\n", menupos + i
168                , driveid / 2, driveid % 2, ATA.devices[driveid].model);
169     }
170     return ATA.cdcount;
171 }
172
173 // Show IPL option menu.
174 static void
175 interactive_bootmenu()
176 {
177     if (! CONFIG_BOOTMENU)
178         return;
179
180     while (get_keystroke(0) >= 0)
181         ;
182
183     printf("Press F12 for boot menu.\n\n");
184
185     int scan_code = get_keystroke(2500);
186     if (scan_code != 0x86)
187         /* not F12 */
188         return;
189
190     while (get_keystroke(0) >= 0)
191         ;
192
193     printf("Select boot device:\n\n");
194
195     int subcount[ARRAY_SIZE(IPL.bev)];
196     int menupos = 1;
197     int i;
198     for (i = 0; i < IPL.bevcount; i++) {
199         struct ipl_entry_s *ie = &IPL.bev[i];
200         int sc = 1;
201         switch (ie->type) {
202         case IPL_TYPE_FLOPPY:
203             sc = menu_show_floppy(ie, menupos);
204             break;
205         case IPL_TYPE_HARDDISK:
206             sc = menu_show_harddisk(ie, menupos);
207             break;
208         case IPL_TYPE_CDROM:
209             sc = menu_show_cdrom(ie, menupos);
210             break;
211         default:
212             sc = menu_show_default(ie, menupos);
213             break;
214         }
215         subcount[i] = sc;
216         menupos += sc;
217     }
218
219     for (;;) {
220         scan_code = get_keystroke(1000);
221         if (scan_code == 0x01)
222             // ESC
223             break;
224         if (scan_code < 1 || scan_code > menupos)
225             continue;
226         int choice = scan_code - 1;
227
228         // Find out which IPL this was for.
229         int bev = 0;
230         while (choice > subcount[bev]) {
231             choice -= subcount[bev];
232             bev++;
233         }
234
235         switch (IPL.bev[bev].type) {
236         case IPL_TYPE_HARDDISK:
237             // A harddrive request enables a BCV order.
238             IPL.bcv_override = choice-1;
239             break;
240         case IPL_TYPE_CDROM:
241             // Select cdrom to boot from.
242             IPL.cdrom_override = choice-1;
243             break;
244         }
245
246         // Add user choice to the boot order.
247         IPL.bootorder = (IPL.bootorder << 4) | (bev+1);
248         break;
249     }
250     printf("\n");
251 }
252
253 // Run the specified bcv.
254 static void
255 run_bcv(struct ipl_entry_s *ie)
256 {
257     switch (ie->type) {
258     case IPL_TYPE_HARDDISK:
259         map_drive(ie->vector);
260         break;
261     case IPL_TYPE_BEV:
262         call_bcv(ie->vector >> 16, ie->vector & 0xffff);
263         break;
264     }
265 }
266
267 // Prepare for boot - show menu and run bcvs.
268 void
269 boot_prep()
270 {
271     if (! CONFIG_BOOT)
272         return;
273
274     // Allow user to modify BCV/IPL order.
275     interactive_bootmenu();
276
277     // Run BCVs
278     int override = IPL.bcv_override;
279     if (override < IPL.bcvcount)
280         run_bcv(&IPL.bcv[override]);
281     int i;
282     for (i=0; i<IPL.bcvcount; i++)
283         if (i != override)
284             run_bcv(&IPL.bcv[i]);
285 }
286
287
288 /****************************************************************
289  * Boot code (int 18/19)
290  ****************************************************************/
291
292 // Jump to a bootup entry point.
293 static void
294 call_boot_entry(u16 bootseg, u16 bootip, u8 bootdrv)
295 {
296     dprintf(1, "Booting from %x:%x\n", bootseg, bootip);
297
298     struct bregs br;
299     memset(&br, 0, sizeof(br));
300     br.ip = bootip;
301     br.cs = bootseg;
302     // Set the magic number in ax and the boot drive in dl.
303     br.dl = bootdrv;
304     br.ax = 0xaa55;
305     call16(&br);
306 }
307
308 // Boot from a disk (either floppy or harddrive)
309 static void
310 boot_disk(u8 bootdrv, int checksig)
311 {
312     u16 bootseg = 0x07c0;
313
314     // Read sector
315     struct bregs br;
316     memset(&br, 0, sizeof(br));
317     br.dl = bootdrv;
318     br.es = bootseg;
319     br.ah = 2;
320     br.al = 1;
321     br.cl = 1;
322     call16_int(0x13, &br);
323
324     if (br.flags & F_CF) {
325         printf("Boot failed: could not read the boot disk\n\n");
326         return;
327     }
328
329     if (checksig) {
330         struct mbr_s *mbr = (void*)0;
331         if (GET_FARVAR(bootseg, mbr->signature) != MBR_SIGNATURE) {
332             printf("Boot failed: not a bootable disk\n\n");
333             return;
334         }
335     }
336
337     /* Canonicalize bootseg:bootip */
338     u16 bootip = (bootseg & 0x0fff) << 4;
339     bootseg &= 0xf000;
340
341     call_boot_entry(bootseg, bootip, bootdrv);
342 }
343
344 // Boot from a CD-ROM
345 static void
346 boot_cdrom()
347 {
348     if (! CONFIG_CDROM_BOOT)
349         return;
350     int status = cdrom_boot(IPL.cdrom_override);
351     if (status) {
352         printf("Boot failed: Could not read from CDROM (code %04x)\n", status);
353         return;
354     }
355
356     u16 ebda_seg = get_ebda_seg();
357     u8 bootdrv = GET_EBDA2(ebda_seg, cdemu.emulated_drive);
358     u16 bootseg = GET_EBDA2(ebda_seg, cdemu.load_segment);
359     /* Canonicalize bootseg:bootip */
360     u16 bootip = (bootseg & 0x0fff) << 4;
361     bootseg &= 0xf000;
362
363     call_boot_entry(bootseg, bootip, bootdrv);
364 }
365
366 static void
367 do_boot(u16 seq_nr)
368 {
369     if (! CONFIG_BOOT)
370         panic("Boot support not compiled in.\n");
371
372     u32 bootdev = IPL.bootorder;
373     bootdev >>= 4 * seq_nr;
374     bootdev &= 0xf;
375
376     if (bootdev == 0) {
377         printf("No bootable device.\n");
378         // Loop with irqs enabled - this allows ctrl+alt+delete to work.
379         for (;;)
380             usleep(1000000);
381     }
382
383     /* Translate bootdev to an IPL table offset by subtracting 1 */
384     bootdev -= 1;
385
386     if (bootdev >= IPL.bevcount) {
387         dprintf(1, "Invalid boot device (0x%x)\n", bootdev);
388         goto fail;
389     }
390
391     /* Do the loading, and set up vector as a far pointer to the boot
392      * address, and bootdrv as the boot drive */
393     struct ipl_entry_s *ie = &IPL.bev[bootdev];
394     char desc[33];
395     printf("Booting from %s...\n"
396            , strtcpy(desc, ie->description, ARRAY_SIZE(desc)));
397
398     switch(ie->type) {
399     case IPL_TYPE_FLOPPY:
400         boot_disk(0x00, IPL.checkfloppysig);
401         break;
402     case IPL_TYPE_HARDDISK:
403         boot_disk(0x80, 1);
404         break;
405     case IPL_TYPE_CDROM:
406         boot_cdrom();
407         break;
408     case IPL_TYPE_BEV:
409         call_boot_entry(ie->vector >> 16, ie->vector & 0xffff, 0);
410         break;
411     }
412
413     // Boot failed: invoke the boot recovery function
414     struct bregs br;
415 fail:
416     memset(&br, 0, sizeof(br));
417     call16_int(0x18, &br);
418 }
419
420 // Boot Failure recovery: try the next device.
421 void VISIBLE32
422 handle_18()
423 {
424     debug_serial_setup();
425     debug_enter(NULL, DEBUG_HDL_18);
426     u16 ebda_seg = get_ebda_seg();
427     u16 seq = GET_EBDA2(ebda_seg, boot_sequence) + 1;
428     SET_EBDA2(ebda_seg, boot_sequence, seq);
429     do_boot(seq);
430 }
431
432 // INT 19h Boot Load Service Entry Point
433 void VISIBLE32
434 handle_19()
435 {
436     debug_serial_setup();
437     debug_enter(NULL, DEBUG_HDL_19);
438     SET_EBDA(boot_sequence, 0);
439     do_boot(0);
440 }
441
442 // Ughh - some older gcc compilers have a bug which causes VISIBLE32
443 // functions to not be exported as global variables.
444 asm(".global handle_18, handle_19");