chain.c32: finalize the NTLDR interface
[people/xl0/syslinux-lua.git] / com32 / modules / chain.c
1 /* ----------------------------------------------------------------------- *
2  *
3  *   Copyright 2003-2008 H. Peter Anvin - All Rights Reserved
4  *
5  *   This program is free software; you can redistribute it and/or modify
6  *   it under the terms of the GNU General Public License as published by
7  *   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
8  *   Boston MA 02111-1307, USA; either version 2 of the License, or
9  *   (at your option) any later version; incorporated herein by reference.
10  *
11  * ----------------------------------------------------------------------- */
12
13 /*
14  * chain.c
15  *
16  * Chainload a hard disk (currently rather braindead.)
17  *
18  * Usage: chain hd<disk#> [<partition>] [options]
19  *        chain fd<disk#> [options]
20  *        chain mbr:<id> [<partition>] [options]
21  *
22  * ... e.g. "chain hd0 1" will boot the first partition on the first hard
23  * disk.
24  *
25  *
26  * The mbr: syntax means search all the hard disks until one with a
27  * specific MBR serial number (bytes 440-443) is found.
28  *
29  * Partitions 1-4 are primary, 5+ logical, 0 = boot MBR (default.)
30  *
31  * Options:
32  *
33  * -file <loader>:
34  *      loads the file <loader> **from the SYSLINUX filesystem**
35  *      instead of loading the boot sector.
36  *
37  * -seg <segment>:
38  *      loads at and jumps to <seg>:0000 instead of 0000:7C00.
39  *
40  * -ntldr <loader>:
41  *      equivalent to -seg 0x2000 -file <loader>, used with WinNT's loaders
42  *
43  * -swap:
44  *      if the disk is not fd0/hd0, install a BIOS stub which swaps
45  *      the drive numbers.
46  */
47
48 #include <com32.h>
49 #include <stdlib.h>
50 #include <stdio.h>
51 #include <ctype.h>
52 #include <string.h>
53 #include <console.h>
54 #include <stdbool.h>
55 #include <syslinux/loadfile.h>
56 #include <syslinux/bootrm.h>
57
58 #define SECTOR 512              /* bytes/sector */
59
60 static struct options {
61   const char *loadfile;
62   uint16_t keeppxe;
63   uint16_t seg;
64   bool swap;
65 } opt;
66
67 static inline void error(const char *msg)
68 {
69   fputs(msg, stderr);
70 }
71
72 /*
73  * Call int 13h, but with retry on failure.  Especially floppies need this.
74  */
75 static int int13_retry(const com32sys_t *inreg, com32sys_t *outreg)
76 {
77   int retry = 6;                /* Number of retries */
78   com32sys_t tmpregs;
79
80   if ( !outreg ) outreg = &tmpregs;
81
82   while ( retry-- ) {
83     __intcall(0x13, inreg, outreg);
84     if ( !(outreg->eflags.l & EFLAGS_CF) )
85       return 0;                 /* CF=0, OK */
86   }
87
88   return -1;                    /* Error */
89 }
90
91 /*
92  * Query disk parameters and EBIOS availability for a particular disk.
93  */
94 struct diskinfo {
95   int disk;
96   int ebios;                    /* EBIOS supported on this disk */
97   int cbios;                    /* CHS geometry is valid */
98   int head;
99   int sect;
100 } disk_info;
101
102 static int get_disk_params(int disk)
103 {
104   static com32sys_t getparm, parm, getebios, ebios;
105
106   disk_info.disk = disk;
107   disk_info.ebios = disk_info.cbios = 0;
108
109   /* Get EBIOS support */
110   getebios.eax.w[0] = 0x4100;
111   getebios.ebx.w[0] = 0x55aa;
112   getebios.edx.b[0] = disk;
113   getebios.eflags.b[0] = 0x3;   /* CF set */
114
115   __intcall(0x13, &getebios, &ebios);
116
117   if ( !(ebios.eflags.l & EFLAGS_CF) &&
118        ebios.ebx.w[0] == 0xaa55 &&
119        (ebios.ecx.b[0] & 1) ) {
120     disk_info.ebios = 1;
121   }
122
123   /* Get disk parameters -- really only useful for
124      hard disks, but if we have a partitioned floppy
125      it's actually our best chance... */
126   getparm.eax.b[1] = 0x08;
127   getparm.edx.b[0] = disk;
128
129   __intcall(0x13, &getparm, &parm);
130
131   if ( parm.eflags.l & EFLAGS_CF )
132     return disk_info.ebios ? 0 : -1;
133
134   disk_info.head = parm.edx.b[1]+1;
135   disk_info.sect = parm.ecx.b[0] & 0x3f;
136   if ( disk_info.sect == 0 ) {
137     disk_info.sect = 1;
138   } else {
139     disk_info.cbios = 1;        /* Valid geometry */
140   }
141
142   return 0;
143 }
144
145 /*
146  * Get a disk block and return a malloc'd buffer.
147  * Uses the disk number and information from disk_info.
148  */
149 struct ebios_dapa {
150   uint16_t len;
151   uint16_t count;
152   uint16_t off;
153   uint16_t seg;
154   uint64_t lba;
155 } *dapa;
156
157 static void *read_sector(unsigned int lba)
158 {
159   com32sys_t inreg;
160   void *buf = __com32.cs_bounce;
161   void *data;
162
163   memset(&inreg, 0, sizeof inreg);
164
165   if ( disk_info.ebios ) {
166     dapa->len = sizeof(*dapa);
167     dapa->count = 1;            /* 1 sector */
168     dapa->off = OFFS(buf);
169     dapa->seg = SEG(buf);
170     dapa->lba = lba;
171
172     inreg.esi.w[0] = OFFS(dapa);
173     inreg.ds       = SEG(dapa);
174     inreg.edx.b[0] = disk_info.disk;
175     inreg.eax.b[1] = 0x42;      /* Extended read */
176   } else {
177     unsigned int c, h, s, t;
178
179     if ( !disk_info.cbios ) {
180       /* We failed to get the geometry */
181
182       if ( lba )
183         return NULL;            /* Can only read MBR */
184
185       s = 1;  h = 0;  c = 0;
186     } else {
187       s = (lba % disk_info.sect) + 1;
188       t = lba / disk_info.sect; /* Track = head*cyl */
189       h = t % disk_info.head;
190       c = t / disk_info.head;
191     }
192
193     if ( s > 63 || h > 256 || c > 1023 )
194       return NULL;
195
196     inreg.eax.w[0] = 0x0201;    /* Read one sector */
197     inreg.ecx.b[1] = c & 0xff;
198     inreg.ecx.b[0] = s + (c >> 6);
199     inreg.edx.b[1] = h;
200     inreg.edx.b[0] = disk_info.disk;
201     inreg.ebx.w[0] = OFFS(buf);
202     inreg.es       = SEG(buf);
203   }
204
205   if (int13_retry(&inreg, NULL))
206     return NULL;
207
208   data = malloc(SECTOR);
209   if (data)
210     memcpy(data, buf, SECTOR);
211   return data;
212 }
213
214 /* Search for a specific drive, based on the MBR signature; bytes
215    440-443. */
216 static int find_disk(uint32_t mbr_sig, void *buf)
217 {
218   int drive;
219   bool is_me;
220
221   for (drive = 0x80; drive <= 0xff; drive++) {
222     if (get_disk_params(drive))
223       continue;                 /* Drive doesn't exist */
224     if (!(buf = read_sector(0)))
225       continue;                 /* Cannot read sector */
226     is_me = (*(uint32_t *)((char *)buf + 440) == mbr_sig);
227     free(buf);
228     if (is_me)
229       return drive;
230   }
231   return -1;
232 }
233
234 /* A DOS partition table entry */
235 struct part_entry {
236   uint8_t active_flag;          /* 0x80 if "active" */
237   uint8_t start_head;
238   uint8_t start_sect;
239   uint8_t start_cyl;
240   uint8_t ostype;
241   uint8_t end_head;
242   uint8_t end_sect;
243   uint8_t end_cyl;
244   uint32_t start_lba;
245   uint32_t length;
246 } __attribute__((packed));
247
248
249 /* Search for a logical partition.  Logical partitions are actually implemented
250    as recursive partition tables; theoretically they're supposed to form a
251    linked list, but other structures have been seen.
252
253    To make things extra confusing: data partition offsets are relative to where
254    the data partition record is stored, whereas extended partition offsets
255    are relative to the beginning of the extended partition all the way back
256    at the MBR... but still not absolute! */
257
258 int nextpart;                   /* Number of the next logical partition */
259
260 static struct part_entry *
261 find_logical_partition(int whichpart, char *table, struct part_entry *self,
262                        struct part_entry *root)
263 {
264   static struct part_entry ltab_entry;
265   struct part_entry *ptab = (struct part_entry *)(table + 0x1be);
266   struct part_entry *found;
267   char *sector;
268
269   int i;
270
271   if ( *(uint16_t *)(table + 0x1fe) != 0xaa55 )
272     return NULL;                /* Signature missing */
273
274   /* We are assumed to already having enumerated all the data partitions
275      in this table if this is the MBR.  For MBR, self == NULL. */
276
277   if ( self ) {
278     /* Scan the data partitions. */
279
280     for ( i = 0 ; i < 4 ; i++ ) {
281       if ( ptab[i].ostype == 0x00 || ptab[i].ostype == 0x05 ||
282            ptab[i].ostype == 0x0f || ptab[i].ostype == 0x85 )
283         continue;               /* Skip empty or extended partitions */
284
285       if ( !ptab[i].length )
286         continue;
287
288       /* Adjust the offset to account for the extended partition itself */
289       ptab[i].start_lba += self->start_lba;
290
291       /* Sanity check entry: must not extend outside the extended partition.
292          This is necessary since some OSes put crap in some entries. */
293       if ( ptab[i].start_lba + ptab[i].length <= self->start_lba ||
294            ptab[i].start_lba >= self->start_lba + self->length )
295         continue;
296
297       /* OK, it's a data partition.  Is it the one we're looking for? */
298       if ( nextpart++ == whichpart ) {
299         memcpy(&ltab_entry, &ptab[i], sizeof ltab_entry);
300         return &ltab_entry;
301       }
302     }
303   }
304
305   /* Scan the extended partitions. */
306   for ( i = 0 ; i < 4 ; i++ ) {
307     if ( ptab[i].ostype != 0x05 &&
308          ptab[i].ostype != 0x0f && ptab[i].ostype != 0x85 )
309       continue;         /* Skip empty or data partitions */
310
311     if ( !ptab[i].length )
312       continue;
313
314     /* Adjust the offset to account for the extended partition itself */
315     if ( root )
316       ptab[i].start_lba += root->start_lba;
317
318     /* Sanity check entry: must not extend outside the extended partition.
319        This is necessary since some OSes put crap in some entries. */
320     if ( root )
321       if ( ptab[i].start_lba + ptab[i].length <= root->start_lba ||
322            ptab[i].start_lba >= root->start_lba + root->length )
323         continue;
324
325     /* Process this partition */
326     if ( !(sector = read_sector(ptab[i].start_lba)) )
327       continue;                 /* Read error, must be invalid */
328
329     found = find_logical_partition(whichpart, sector, &ptab[i],
330                                    root ? root : &ptab[i]);
331     free(sector);
332     if (found)
333       return found;
334   }
335
336   /* If we get here, there ain't nothing... */
337   return NULL;
338 }
339
340 static void do_boot(void *boot_sector, size_t boot_size,
341                     struct syslinux_rm_regs *regs)
342 {
343   static const uint8_t swapstub[] = {
344     0x53,                       /* 00: push bx */
345     0x0f,0xb6,0xda,             /* 01: movzx bx,dl */
346     0x2e,0x8a,0x57,0x10,        /* 04: mov dl,[cs:bx+16] */
347     0x5b,                       /* 08: pop bx */
348     0xea,0,0,0,0,               /* 09: jmp far 0:0 */
349     0x90,0x90,                  /* 0E: nop; nop */
350   };
351   uint16_t * const bios_fbm  = (uint16_t *)0x413;
352   uint32_t * const int13_vec = (uint32_t *)(0x13*4);
353   uint16_t old_bios_fbm  = *bios_fbm;
354   uint32_t old_int13_vec = *int13_vec;
355   struct syslinux_memmap *mmap;
356   struct syslinux_movelist *mlist = NULL;
357   addr_t dosmem = old_bios_fbm << 10;
358   uint8_t driveno   = regs->edx.b[0];
359   uint8_t swapdrive = driveno & 0x80;
360   int i;
361   addr_t loadbase = opt.seg ? (opt.seg << 4) : 0x7c00;
362
363   mmap = syslinux_memory_map();
364
365   if (!mmap) {
366     error("Cannot read system memory map");
367     return;
368   }
369
370   if (opt.swap && driveno != swapdrive) {
371     uint8_t *p;
372
373     regs->edx.b[0] = swapdrive;
374     
375     dosmem -= 1024;
376     p = (uint8_t *)dosmem;
377
378     /* Install swapper stub */
379     memset(p, 0, 1024);         /* For debugging... */
380     memcpy(p, swapstub, sizeof swapstub);
381     *(uint32_t *)&p[0x0a] = old_int13_vec;
382     p += sizeof swapstub;
383
384     /* Mapping table; start out with identity mapping everything */
385     for (i = 0; i < 256; i++)
386       p[i] = i;
387
388     /* And the actual swap */
389     p[driveno] = swapdrive;
390     p[swapdrive] = driveno;
391   }
392
393   syslinux_add_memmap(&mmap, dosmem, 0xa0000-dosmem, SMT_RESERVED);
394
395   if (syslinux_memmap_type(mmap, loadbase, boot_size) != SMT_FREE) {
396     error("Loader file too large");
397     return;
398   }
399
400   if (syslinux_add_movelist(&mlist, loadbase, (addr_t)boot_sector,
401                             boot_size)) {
402     error("Out of memory");
403     return;
404   }
405
406   fputs("Booting...\n", stdout);
407
408   if (opt.swap) {
409     /* Do this as late as possible */
410     *bios_fbm  = dosmem >> 10;
411     *int13_vec = dosmem << 12;
412   }
413
414   syslinux_shuffle_boot_rm(mlist, mmap, opt.keeppxe, regs);
415
416   /* If we get here, badness happened */
417   if (opt.swap) {
418     *bios_fbm  = old_bios_fbm;
419     *int13_vec = old_int13_vec;
420   }
421   error("Chainboot failed!\n");
422 }
423
424 int main(int argc, char *argv[])
425 {
426   char *mbr;
427   void *boot_sector = NULL;
428   struct part_entry *partinfo;
429   struct syslinux_rm_regs regs;
430   char *drivename, *partition;
431   int hd, drive, whichpart;
432   int i;
433   size_t boot_size = SECTOR;
434
435   openconsole(&dev_null_r, &dev_stdcon_w);
436
437   drivename = NULL;
438   partition = NULL;
439
440   /* Prepare the register set */
441   memset(&regs, 0, sizeof regs);
442
443   for (i = 1; i < argc; i++) {
444     if (!strcmp(argv[i], "-file") && argv[i+1]) {
445       opt.loadfile = argv[++i];
446     } else if (!strcmp(argv[i], "-seg") && argv[i+1]) {
447       uint32_t segval = strtoul(argv[++i], NULL, 0);
448       if (segval < 0x7c0 || segval > 0x9f000) {
449         error("Invalid segment");
450         goto bail;
451       }
452       opt.seg = segval;
453     } else if (!strcmp(argv[i], "-ntldr") && argv[i+1]) {
454       opt.seg = 0x2000;         /* NTLDR wants this address */
455       opt.loadfile = argv[++i];
456     } else if (!strcmp(argv[i], "-swap")) {
457       opt.swap = true;
458     } else if (!strcmp(argv[i], "keeppxe")) {
459       opt.keeppxe = 3;
460     } else {
461       if (!drivename)
462         drivename = argv[i];
463       else if (!partition)
464         partition = argv[i];
465     }
466   }
467
468   if ( !drivename ) {
469     error("Usage: chain.c32 (hd#|fd#|mbr:#) [partition] [options]\n");
470     goto bail;
471   }
472
473   if (opt.seg) {
474     regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = opt.seg;
475   } else {
476     regs.ip = regs.esp.l = 0x7c00;
477   }
478
479   /* Divvy up the bounce buffer.  To keep things sector-
480      aligned, give the EBIOS DAPA the first sector, then
481      the MBR next, and the rest is used for the partition-
482      chasing stack. */
483   dapa = (struct ebios_dapa *)__com32.cs_bounce;
484   mbr  = (char *)__com32.cs_bounce + SECTOR;
485
486   drivename = argv[1];
487   partition = argv[2];          /* Possibly null */
488
489   hd = 0;
490   if ( !memcmp(drivename, "mbr:", 4) ) {
491     drive = find_disk(strtoul(drivename+4, NULL, 0), mbr);
492     if (drive == -1) {
493       error("Unable to find requested MBR signature\n");
494       goto bail;
495     }
496   } else {
497     if ( (drivename[0] == 'h' || drivename[0] == 'f') &&
498          drivename[1] == 'd' ) {
499       hd = drivename[0] == 'h';
500       drivename += 2;
501     }
502     drive = (hd ? 0x80 : 0) | strtoul(drivename, NULL, 0);
503   }
504
505   regs.edx.b[0] = drive;
506
507   whichpart = 0;                /* Default */
508
509   if ( partition )
510     whichpart = strtoul(partition, NULL, 0);
511
512   if ( !(drive & 0x80) && whichpart ) {
513     error("Warning: Partitions of floppy devices may not work\n");
514   }
515
516   /* Get the disk geometry and disk access setup */
517   if ( get_disk_params(drive) ) {
518     error("Cannot get disk parameters\n");
519     goto bail;
520   }
521
522   /* Get MBR */
523   if ( !(mbr = read_sector(0)) ) {
524     error("Cannot read Master Boot Record\n");
525     goto bail;
526   }
527
528   if ( whichpart == 0 ) {
529     /* Boot the MBR */
530     partinfo = NULL;
531     boot_sector = mbr;
532   } else if ( whichpart <= 4 ) {
533     /* Boot a primary partition */
534     partinfo = &((struct part_entry *)(mbr + 0x1be))[whichpart-1];
535     if ( partinfo->ostype == 0 ) {
536       error("Invalid primary partition\n");
537       goto bail;
538     }
539   } else {
540     /* Boot a logical partition */
541
542     nextpart = 5;
543     partinfo = find_logical_partition(whichpart, mbr, NULL, NULL);
544
545     if ( !partinfo || partinfo->ostype == 0 ) {
546       error("Requested logical partition not found\n");
547       goto bail;
548     }
549   }
550
551   /* Do the actual chainloading */
552   if (opt.loadfile) {
553     fputs("Loading the boot file...\n", stdout);
554     if ( loadfile(opt.loadfile, &boot_sector, &boot_size) ) {
555       error("Failed to load the boot file\n");
556       goto bail;
557     }
558   } else if (partinfo) {
559     /* Actually read the boot sector */
560     /* Pick the first buffer that isn't already in use */
561     if ( !(boot_sector = read_sector(partinfo->start_lba)) ) {
562       error("Cannot read boot sector\n");
563       goto bail;
564     }
565   }
566
567   if (partinfo) {
568     /* 0x7BE is the canonical place for the first partition entry. */
569     regs.esi.w[0] = 0x7be;
570     memcpy((char *)0x7be, partinfo, sizeof(*partinfo));
571   }
572
573   do_boot(boot_sector, boot_size, &regs);
574
575 bail:
576   return 255;
577 }