chain.c32: support swapping BIOS drive numbers
[syslinux.git] / com32 / modules / chain.c
bloba162ca336c994d02472bc1f0d927c019e91c557a
1 /* ----------------------------------------------------------------------- *
3 * Copyright 2003-2008 H. Peter Anvin - All Rights Reserved
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.
11 * ----------------------------------------------------------------------- */
14 * chain.c
16 * Chainload a hard disk (currently rather braindead.)
18 * Usage: chain hd<disk#> [<partition>] [options]
19 * chain fd<disk#> [options]
20 * chain mbr:<id> [<partition>] [options]
22 * ... e.g. "chain hd0 1" will boot the first partition on the first hard
23 * disk.
26 * The mbr: syntax means search all the hard disks until one with a
27 * specific MBR serial number (bytes 440-443) is found.
29 * Partitions 1-4 are primary, 5+ logical, 0 = boot MBR (default.)
31 * Options:
33 * -file <loader>:
34 * loads the file <loader> **from the SYSLINUX filesystem**
35 * instead of loading the boot sector.
37 * -ntldr:
38 * jumps to 07C0:0000 instead of 0000:7C00, not sure if this is
39 * really necessary.
41 * -swap:
42 * if the disk is not fd0/hd0, install a BIOS stub which swaps
43 * the drive numbers.
46 #include <com32.h>
47 #include <stdlib.h>
48 #include <stdio.h>
49 #include <ctype.h>
50 #include <string.h>
51 #include <console.h>
52 #include <stdbool.h>
53 #include <syslinux/loadfile.h>
54 #include <syslinux/bootrm.h>
56 #define SECTOR 512 /* bytes/sector */
58 static struct options {
59 const char *loadfile;
60 uint16_t keeppxe;
61 bool ntldr;
62 bool swap;
63 } opt;
65 static inline void error(const char *msg)
67 fputs(msg, stderr);
71 * Call int 13h, but with retry on failure. Especially floppies need this.
73 static int int13_retry(const com32sys_t *inreg, com32sys_t *outreg)
75 int retry = 6; /* Number of retries */
76 com32sys_t tmpregs;
78 if ( !outreg ) outreg = &tmpregs;
80 while ( retry-- ) {
81 __intcall(0x13, inreg, outreg);
82 if ( !(outreg->eflags.l & EFLAGS_CF) )
83 return 0; /* CF=0, OK */
86 return -1; /* Error */
90 * Query disk parameters and EBIOS availability for a particular disk.
92 struct diskinfo {
93 int disk;
94 int ebios; /* EBIOS supported on this disk */
95 int cbios; /* CHS geometry is valid */
96 int head;
97 int sect;
98 } disk_info;
100 static int get_disk_params(int disk)
102 static com32sys_t getparm, parm, getebios, ebios;
104 disk_info.disk = disk;
105 disk_info.ebios = disk_info.cbios = 0;
107 /* Get EBIOS support */
108 getebios.eax.w[0] = 0x4100;
109 getebios.ebx.w[0] = 0x55aa;
110 getebios.edx.b[0] = disk;
111 getebios.eflags.b[0] = 0x3; /* CF set */
113 __intcall(0x13, &getebios, &ebios);
115 if ( !(ebios.eflags.l & EFLAGS_CF) &&
116 ebios.ebx.w[0] == 0xaa55 &&
117 (ebios.ecx.b[0] & 1) ) {
118 disk_info.ebios = 1;
121 /* Get disk parameters -- really only useful for
122 hard disks, but if we have a partitioned floppy
123 it's actually our best chance... */
124 getparm.eax.b[1] = 0x08;
125 getparm.edx.b[0] = disk;
127 __intcall(0x13, &getparm, &parm);
129 if ( parm.eflags.l & EFLAGS_CF )
130 return disk_info.ebios ? 0 : -1;
132 disk_info.head = parm.edx.b[1]+1;
133 disk_info.sect = parm.ecx.b[0] & 0x3f;
134 if ( disk_info.sect == 0 ) {
135 disk_info.sect = 1;
136 } else {
137 disk_info.cbios = 1; /* Valid geometry */
140 return 0;
144 * Get a disk block; buf is REQUIRED TO BE IN LOW MEMORY.
146 struct ebios_dapa {
147 uint16_t len;
148 uint16_t count;
149 uint16_t off;
150 uint16_t seg;
151 uint64_t lba;
152 } *dapa;
154 static int read_sector(void *buf, unsigned int lba)
156 com32sys_t inreg;
158 memset(&inreg, 0, sizeof inreg);
160 if ( disk_info.ebios ) {
161 dapa->len = sizeof(*dapa);
162 dapa->count = 1; /* 1 sector */
163 dapa->off = OFFS(buf);
164 dapa->seg = SEG(buf);
165 dapa->lba = lba;
167 inreg.esi.w[0] = OFFS(dapa);
168 inreg.ds = SEG(dapa);
169 inreg.edx.b[0] = disk_info.disk;
170 inreg.eax.b[1] = 0x42; /* Extended read */
171 } else {
172 unsigned int c, h, s, t;
174 if ( !disk_info.cbios ) {
175 /* We failed to get the geometry */
177 if ( lba )
178 return -1; /* Can only read MBR */
180 s = 1; h = 0; c = 0;
181 } else {
182 s = (lba % disk_info.sect) + 1;
183 t = lba / disk_info.sect; /* Track = head*cyl */
184 h = t % disk_info.head;
185 c = t / disk_info.head;
188 if ( s > 63 || h > 256 || c > 1023 )
189 return -1;
191 inreg.eax.w[0] = 0x0201; /* Read one sector */
192 inreg.ecx.b[1] = c & 0xff;
193 inreg.ecx.b[0] = s + (c >> 6);
194 inreg.edx.b[1] = h;
195 inreg.edx.b[0] = disk_info.disk;
196 inreg.ebx.w[0] = OFFS(buf);
197 inreg.es = SEG(buf);
200 return int13_retry(&inreg, NULL);
203 /* Search for a specific drive, based on the MBR signature; bytes
204 440-443. */
205 static int find_disk(uint32_t mbr_sig, void *buf)
207 int drive;
209 for (drive = 0x80; drive <= 0xff; drive++) {
210 if (get_disk_params(drive))
211 continue; /* Drive doesn't exist */
212 if (read_sector(buf, 0))
213 continue; /* Cannot read sector */
215 if (*(uint32_t *)((char *)buf + 440) == mbr_sig)
216 return drive;
219 return -1;
222 /* A DOS partition table entry */
223 struct part_entry {
224 uint8_t active_flag; /* 0x80 if "active" */
225 uint8_t start_head;
226 uint8_t start_sect;
227 uint8_t start_cyl;
228 uint8_t ostype;
229 uint8_t end_head;
230 uint8_t end_sect;
231 uint8_t end_cyl;
232 uint32_t start_lba;
233 uint32_t length;
234 } __attribute__((packed));
237 /* Search for a logical partition. Logical partitions are actually implemented
238 as recursive partition tables; theoretically they're supposed to form a
239 linked list, but other structures have been seen.
241 To make things extra confusing: data partition offsets are relative to where
242 the data partition record is stored, whereas extended partition offsets
243 are relative to the beginning of the extended partition all the way back
244 at the MBR... but still not absolute! */
246 int nextpart; /* Number of the next logical partition */
248 static struct part_entry *
249 find_logical_partition(int whichpart, char *table, struct part_entry *self,
250 struct part_entry *root)
252 struct part_entry *ptab = (struct part_entry *)(table + 0x1be);
253 struct part_entry *found;
254 int i;
256 if ( *(uint16_t *)(table + 0x1fe) != 0xaa55 )
257 return NULL; /* Signature missing */
259 /* We are assumed to already having enumerated all the data partitions
260 in this table if this is the MBR. For MBR, self == NULL. */
262 if ( self ) {
263 /* Scan the data partitions. */
265 for ( i = 0 ; i < 4 ; i++ ) {
266 if ( ptab[i].ostype == 0x00 || ptab[i].ostype == 0x05 ||
267 ptab[i].ostype == 0x0f || ptab[i].ostype == 0x85 )
268 continue; /* Skip empty or extended partitions */
270 if ( !ptab[i].length )
271 continue;
273 /* Adjust the offset to account for the extended partition itself */
274 ptab[i].start_lba += self->start_lba;
276 /* Sanity check entry: must not extend outside the extended partition.
277 This is necessary since some OSes put crap in some entries. */
278 if ( ptab[i].start_lba + ptab[i].length <= self->start_lba ||
279 ptab[i].start_lba >= self->start_lba + self->length )
280 continue;
282 /* OK, it's a data partition. Is it the one we're looking for? */
283 if ( nextpart++ == whichpart )
284 return &ptab[i];
288 /* Scan the extended partitions. */
289 for ( i = 0 ; i < 4 ; i++ ) {
290 if ( ptab[i].ostype != 0x05 &&
291 ptab[i].ostype != 0x0f && ptab[i].ostype != 0x85 )
292 continue; /* Skip empty or data partitions */
294 if ( !ptab[i].length )
295 continue;
297 /* Adjust the offset to account for the extended partition itself */
298 if ( root )
299 ptab[i].start_lba += root->start_lba;
301 /* Sanity check entry: must not extend outside the extended partition.
302 This is necessary since some OSes put crap in some entries. */
303 if ( root )
304 if ( ptab[i].start_lba + ptab[i].length <= root->start_lba ||
305 ptab[i].start_lba >= root->start_lba + root->length )
306 continue;
308 /* Process this partition */
309 if ( read_sector(table+SECTOR, ptab[i].start_lba) )
310 continue; /* Read error, must be invalid */
312 if ( (found = find_logical_partition(whichpart, table+SECTOR, &ptab[i],
313 root ? root : &ptab[i])) )
314 return found;
317 /* If we get here, there ain't nothing... */
318 return NULL;
321 static void do_boot(void *boot_sector, size_t boot_size,
322 struct syslinux_rm_regs *regs)
324 uint16_t * const bios_fbm = (uint16_t *)0x413;
325 uint32_t * const int13_vec = (uint32_t *)(0x13*4);
326 uint16_t old_bios_fbm = *bios_fbm;
327 uint32_t old_int13_vec = *int13_vec;
328 struct syslinux_memmap *mmap;
329 struct syslinux_movelist *mlist = NULL;
330 addr_t dosmem = old_bios_fbm << 10;
332 mmap = syslinux_memory_map();
334 if (!mmap) {
335 error("Cannot read system memory map");
336 return;
339 if (opt.swap) {
340 uint8_t *p;
341 uint8_t driveno = regs->edx.b[0];
342 uint8_t swapdrive = driveno & 0x80;
344 regs->edx.b[0] = swapdrive;
346 dosmem -= 1024;
347 p = (uint8_t *)dosmem;
349 /* Install swapper stub */
350 *p++ = 0x80; /* cmp dl,swapdrive */
351 *p++ = 0xfa;
352 *p++ = swapdrive;
353 *p++ = 0x74; /* je swap1 */
354 *p++ = 0x09;
355 *p++ = 0x80; /* cmp dl,driveno */
356 *p++ = 0xfa;
357 *p++ = driveno;
358 *p++ = 0x75; /* je noswap */
359 *p++ = 0x06;
360 *p++ = 0xb2; /* mov dl,swapdrive */
361 *p++ = swapdrive;
362 *p++ = 0xeb; /* jmp noswap */
363 *p++ = 0x02;
364 *p++ = 0xb2; /* swap1: mov dl,driveno */
365 *p++ = driveno;
366 *p++ = 0xea; /* noswap: jmp far <oldint13> */
367 *(uint32_t *)p = old_int13_vec;
370 syslinux_add_memmap(&mmap, dosmem, 0xa0000-dosmem, SMT_RESERVED);
372 if (syslinux_memmap_type(mmap, 0x7c00, boot_size) != SMT_FREE) {
373 error("Loader file too large");
374 return;
377 if (syslinux_add_movelist(&mlist, 0x7c00, (addr_t)boot_sector, boot_size)) {
378 error("Out of memory");
379 return;
382 fputs("Booting...\n", stdout);
384 if (opt.swap) {
385 /* Do this as late as possible */
386 *bios_fbm = dosmem >> 10;
387 *int13_vec = dosmem << 12;
390 syslinux_shuffle_boot_rm(mlist, mmap, opt.keeppxe, regs);
392 /* If we get here, badness happened */
393 if (opt.swap) {
394 *bios_fbm = old_bios_fbm;
395 *int13_vec = old_int13_vec;
397 error("Chainboot failed!\n");
400 int main(int argc, char *argv[])
402 char *mbr;
403 void *boot_sector = NULL;
404 struct part_entry *partinfo;
405 struct syslinux_rm_regs regs;
406 char *drivename, *partition;
407 int hd, drive, whichpart;
408 int i;
409 size_t boot_size = SECTOR;
411 openconsole(&dev_null_r, &dev_stdcon_w);
413 drivename = NULL;
414 partition = NULL;
416 /* Prepare the register set */
417 memset(&regs, 0, sizeof regs);
419 for (i = 1; i < argc; i++) {
420 if (!strcmp(argv[i], "-file") && argv[i+1]) {
421 opt.loadfile = argv[++i];
422 } else if (!strcmp(argv[i], "-ntldr")) {
423 opt.ntldr = true;
424 } else if (!strcmp(argv[i], "-swap")) {
425 opt.swap = true;
426 } else if (!strcmp(argv[i], "keeppxe")) {
427 opt.keeppxe = 3;
428 } else {
429 if (!drivename)
430 drivename = argv[i];
431 else if (!partition)
432 partition = argv[i];
436 if ( !drivename ) {
437 error("Usage: chain.c32 (hd#|fd#|mbr:#) [partition] [-swap][-ntldr] "
438 "[-file loader]\n");
439 goto bail;
442 if (opt.ntldr) {
443 regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = 0x07c0;
444 } else {
445 regs.ip = regs.esp.l = 0x7c00;
448 /* Divvy up the bounce buffer. To keep things sector-
449 aligned, give the EBIOS DAPA the first sector, then
450 the MBR next, and the rest is used for the partition-
451 chasing stack. */
452 dapa = (struct ebios_dapa *)__com32.cs_bounce;
453 mbr = (char *)__com32.cs_bounce + SECTOR;
455 drivename = argv[1];
456 partition = argv[2]; /* Possibly null */
458 hd = 0;
459 if ( !memcmp(drivename, "mbr:", 4) ) {
460 drive = find_disk(strtoul(drivename+4, NULL, 0), mbr);
461 if (drive == -1) {
462 error("Unable to find requested MBR signature\n");
463 goto bail;
465 } else {
466 if ( (drivename[0] == 'h' || drivename[0] == 'f') &&
467 drivename[1] == 'd' ) {
468 hd = drivename[0] == 'h';
469 drivename += 2;
471 drive = (hd ? 0x80 : 0) | strtoul(drivename, NULL, 0);
474 regs.edx.b[0] = drive;
476 whichpart = 0; /* Default */
478 if ( partition )
479 whichpart = strtoul(partition, NULL, 0);
481 if ( !(drive & 0x80) && whichpart ) {
482 error("Warning: Partitions of floppy devices may not work\n");
485 /* Get the disk geometry and disk access setup */
486 if ( get_disk_params(drive) ) {
487 error("Cannot get disk parameters\n");
488 goto bail;
491 /* Get MBR */
492 if ( read_sector(mbr, 0) ) {
493 error("Cannot read Master Boot Record\n");
494 goto bail;
497 if ( whichpart == 0 ) {
498 /* Boot the MBR */
499 partinfo = NULL;
500 boot_sector = mbr;
501 } else if ( whichpart <= 4 ) {
502 /* Boot a primary partition */
503 partinfo = &((struct part_entry *)(mbr + 0x1be))[whichpart-1];
504 if ( partinfo->ostype == 0 ) {
505 error("Invalid primary partition\n");
506 goto bail;
508 } else {
509 /* Boot a logical partition */
511 nextpart = 5;
512 partinfo = find_logical_partition(whichpart, mbr, NULL, NULL);
514 if ( !partinfo || partinfo->ostype == 0 ) {
515 error("Requested logical partition not found\n");
516 goto bail;
520 /* Do the actual chainloading */
521 if (opt.loadfile) {
522 if ( loadfile(opt.loadfile, &boot_sector, &boot_size) ) {
523 error("Failed to load the boot file\n");
524 goto bail;
526 } else if (partinfo) {
527 /* Actually read the boot sector */
528 /* Pick the first buffer that isn't already in use */
529 boot_sector = (void *)(((uintptr_t)partinfo + 511) & ~511);
530 if ( read_sector(boot_sector, partinfo->start_lba) ) {
531 error("Cannot read boot sector\n");
532 goto bail;
536 if (partinfo) {
537 /* 0x7BE is the canonical place for the first partition entry. */
538 regs.esi.w[0] = 0x7be;
539 memcpy((char *)0x7be, partinfo, sizeof(*partinfo));
542 do_boot(boot_sector, boot_size, &regs);
544 bail:
545 return 255;