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