| /* ----------------------------------------------------------------------- * |
| * |
| * Copyright 2001-2009 H. Peter Anvin - All Rights Reserved |
| * Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin |
| * Portions copyright 2009-2010 Shao Miller |
| * [El Torito code, mBFT, "safe hook"] |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, Inc., 53 Temple Place Ste 330, |
| * Boston MA 02111-1307, USA; either version 2 of the License, or |
| * (at your option) any later version; incorporated herein by reference. |
| * |
| * ----------------------------------------------------------------------- */ |
| |
| #include <stdint.h> |
| #include <minmax.h> |
| #include <suffix_number.h> |
| #include "bda.h" |
| #include "dskprobe.h" |
| #include "e820.h" |
| #include "conio.h" |
| #include "version.h" |
| #include "memdisk.h" |
| #include <version.h> |
| |
| const char memdisk_version[] = "MEMDISK " VERSION_STR " " DATE; |
| const char copyright[] = |
| "Copyright " FIRSTYEAR "-" YEAR_STR " H. Peter Anvin et al"; |
| |
| extern const char _binary_memdisk_chs_512_bin_start[]; |
| extern const char _binary_memdisk_chs_512_bin_end[]; |
| extern const char _binary_memdisk_chs_512_bin_size[]; |
| extern const char _binary_memdisk_edd_512_bin_start[]; |
| extern const char _binary_memdisk_edd_512_bin_end[]; |
| extern const char _binary_memdisk_edd_512_bin_size[]; |
| extern const char _binary_memdisk_iso_512_bin_start[]; |
| extern const char _binary_memdisk_iso_512_bin_end[]; |
| extern const char _binary_memdisk_iso_512_bin_size[]; |
| extern const char _binary_memdisk_iso_2048_bin_start[]; |
| extern const char _binary_memdisk_iso_2048_bin_end[]; |
| extern const char _binary_memdisk_iso_2048_bin_size[]; |
| |
| /* Pull in structures common to MEMDISK and MDISKCHK.COM */ |
| #include "mstructs.h" |
| |
| /* An EDD disk packet */ |
| struct edd_dsk_pkt { |
| uint8_t size; /* Packet size */ |
| uint8_t res1; /* Reserved */ |
| uint16_t count; /* Count to transfer */ |
| uint32_t buf; /* Buffer pointer */ |
| uint64_t start; /* LBA to start from */ |
| uint64_t buf64; /* 64-bit buf pointer */ |
| } __attribute__ ((packed)); |
| |
| /* Change to 1 for El Torito debugging */ |
| #define DBG_ELTORITO 0 |
| |
| #if DBG_ELTORITO |
| extern void eltorito_dump(uint32_t); |
| #endif |
| |
| /* |
| * Routine to seek for a command-line item and return a pointer |
| * to the data portion, if present |
| */ |
| |
| /* Magic return values */ |
| #define CMD_NOTFOUND ((char *)-1) /* Not found */ |
| #define CMD_BOOL ((char *)-2) /* Found boolean option */ |
| #define CMD_HASDATA(X) ((int)(X) >= 0) |
| |
| static const char *getcmditem(const char *what) |
| { |
| const char *p; |
| const char *wp = what; |
| int match = 0; |
| |
| for (p = shdr->cmdline; *p; p++) { |
| switch (match) { |
| case 0: /* Ground state */ |
| if (*p == ' ') |
| break; |
| |
| wp = what; |
| match = 1; |
| /* Fall through */ |
| |
| case 1: /* Matching */ |
| if (*wp == '\0') { |
| if (*p == '=') |
| return p + 1; |
| else if (*p == ' ') |
| return CMD_BOOL; |
| else { |
| match = 2; |
| break; |
| } |
| } |
| if (*p != *wp++) |
| match = 2; |
| break; |
| |
| case 2: /* Mismatch, skip rest of option */ |
| if (*p == ' ') |
| match = 0; /* Next option */ |
| break; |
| } |
| } |
| |
| /* Check for matching string at end of line */ |
| if (match == 1 && *wp == '\0') |
| return CMD_BOOL; |
| |
| return CMD_NOTFOUND; |
| } |
| |
| /* |
| * Check to see if this is a gzip image |
| */ |
| #define UNZIP_ALIGN 512 |
| |
| extern const char _end[]; /* Symbol signalling end of data */ |
| |
| void unzip_if_needed(uint32_t * where_p, uint32_t * size_p) |
| { |
| uint32_t where = *where_p; |
| uint32_t size = *size_p; |
| uint32_t zbytes; |
| uint32_t startrange, endrange; |
| uint32_t gzdatasize, gzwhere; |
| uint32_t orig_crc, offset; |
| uint32_t target = 0; |
| int i, okmem; |
| |
| /* Is it a gzip image? */ |
| if (check_zip((void *)where, size, &zbytes, &gzdatasize, |
| &orig_crc, &offset) == 0) { |
| |
| if (offset + zbytes > size) { |
| /* |
| * Assertion failure; check_zip is supposed to guarantee this |
| * never happens. |
| */ |
| die("internal error: check_zip returned nonsense\n"); |
| } |
| |
| /* |
| * Find a good place to put it: search memory ranges in descending |
| * order until we find one that is legal and fits |
| */ |
| okmem = 0; |
| for (i = nranges - 1; i >= 0; i--) { |
| /* |
| * We can't use > 4G memory (32 bits only.) Truncate to 2^32-1 |
| * so we don't have to deal with funny wraparound issues. |
| */ |
| |
| /* Must be memory */ |
| if (ranges[i].type != 1) |
| continue; |
| |
| /* Range start */ |
| if (ranges[i].start >= 0xFFFFFFFF) |
| continue; |
| |
| startrange = (uint32_t) ranges[i].start; |
| |
| /* Range end (0 for end means 2^64) */ |
| endrange = ((ranges[i + 1].start >= 0xFFFFFFFF || |
| ranges[i + 1].start == 0) |
| ? 0xFFFFFFFF : (uint32_t) ranges[i + 1].start); |
| |
| /* Make sure we don't overwrite ourselves */ |
| if (startrange < (uint32_t) _end) |
| startrange = (uint32_t) _end; |
| |
| /* Allow for alignment */ |
| startrange = |
| (ranges[i].start + (UNZIP_ALIGN - 1)) & ~(UNZIP_ALIGN - 1); |
| |
| /* In case we just killed the whole range... */ |
| if (startrange >= endrange) |
| continue; |
| |
| /* |
| * Must be large enough... don't rely on gzwhere for this |
| * (wraparound) |
| */ |
| if (endrange - startrange < gzdatasize) |
| continue; |
| |
| /* |
| * This is where the gz image would be put if we put it in this |
| * range... |
| */ |
| gzwhere = (endrange - gzdatasize) & ~(UNZIP_ALIGN - 1); |
| |
| /* Cast to uint64_t just in case we're flush with the top byte */ |
| if ((uint64_t) where + size >= gzwhere && where < endrange) { |
| /* |
| * Need to move source data to avoid compressed/uncompressed |
| * overlap |
| */ |
| uint32_t newwhere; |
| |
| if (gzwhere - startrange < size) |
| continue; /* Can't fit both old and new */ |
| |
| newwhere = (gzwhere - size) & ~(UNZIP_ALIGN - 1); |
| printf("Moving compressed data from 0x%08x to 0x%08x\n", |
| where, newwhere); |
| |
| memmove((void *)newwhere, (void *)where, size); |
| where = newwhere; |
| } |
| |
| target = gzwhere; |
| okmem = 1; |
| break; |
| } |
| |
| if (!okmem) |
| die("Not enough memory to decompress image (need 0x%08x bytes)\n", |
| gzdatasize); |
| |
| printf("gzip image: decompressed addr 0x%08x, len 0x%08x: ", |
| target, gzdatasize); |
| |
| *size_p = gzdatasize; |
| *where_p = (uint32_t) unzip((void *)(where + offset), zbytes, |
| gzdatasize, orig_crc, (void *)target); |
| } |
| } |
| |
| /* |
| * Figure out the "geometry" of the disk in question |
| */ |
| struct geometry { |
| uint32_t sectors; /* Sector count */ |
| uint32_t c, h, s; /* C/H/S geometry */ |
| uint32_t offset; /* Byte offset for disk */ |
| uint32_t boot_lba; /* LBA of bootstrap code */ |
| uint8_t type; /* Type byte for INT 13h AH=08h */ |
| uint8_t driveno; /* Drive no */ |
| uint8_t sector_shift; /* Sector size as a power of 2 */ |
| const char *hsrc, *ssrc; /* Origins of H and S geometries */ |
| }; |
| |
| /* Format of a DOS partition table entry */ |
| struct ptab_entry { |
| uint8_t active; |
| uint8_t start_h, start_s, start_c; |
| uint8_t type; |
| uint8_t end_h, end_s, end_c; |
| uint32_t start; |
| uint32_t size; |
| } __attribute__ ((packed)); |
| |
| /* Format of a FAT filesystem superblock */ |
| struct fat_extra { |
| uint8_t bs_drvnum; |
| uint8_t bs_resv1; |
| uint8_t bs_bootsig; |
| uint32_t bs_volid; |
| char bs_vollab[11]; |
| char bs_filsystype[8]; |
| } __attribute__ ((packed)); |
| struct fat_super { |
| uint8_t bs_jmpboot[3]; |
| char bs_oemname[8]; |
| uint16_t bpb_bytspersec; |
| uint8_t bpb_secperclus; |
| uint16_t bpb_rsvdseccnt; |
| uint8_t bpb_numfats; |
| uint16_t bpb_rootentcnt; |
| uint16_t bpb_totsec16; |
| uint8_t bpb_media; |
| uint16_t bpb_fatsz16; |
| uint16_t bpb_secpertrk; |
| uint16_t bpb_numheads; |
| uint32_t bpb_hiddsec; |
| uint32_t bpb_totsec32; |
| union { |
| struct { |
| struct fat_extra extra; |
| } fat16; |
| struct { |
| uint32_t bpb_fatsz32; |
| uint16_t bpb_extflags; |
| uint16_t bpb_fsver; |
| uint32_t bpb_rootclus; |
| uint16_t bpb_fsinfo; |
| uint16_t bpb_bkbootsec; |
| char bpb_reserved[12]; |
| /* Clever, eh? Same fields, different offset... */ |
| struct fat_extra extra; |
| } fat32 __attribute__ ((packed)); |
| } x; |
| } __attribute__ ((packed)); |
| |
| /* Format of a DOSEMU header */ |
| struct dosemu_header { |
| uint8_t magic[7]; /* DOSEMU\0 */ |
| uint32_t h; |
| uint32_t s; |
| uint32_t c; |
| uint32_t offset; |
| uint8_t pad[105]; |
| } __attribute__ ((packed)); |
| |
| #define FOUR(a,b,c,d) (((a) << 24)|((b) << 16)|((c) << 8)|(d)) |
| |
| static const struct geometry *get_disk_image_geometry(uint32_t where, |
| uint32_t size) |
| { |
| static struct geometry hd_geometry; |
| struct dosemu_header dosemu; |
| unsigned int sectors, xsectors, v; |
| unsigned int offset; |
| int i; |
| const char *p; |
| |
| printf("command line: %s\n", shdr->cmdline); |
| |
| hd_geometry.sector_shift = 9; /* Assume floppy/HDD at first */ |
| |
| offset = 0; |
| if (CMD_HASDATA(p = getcmditem("offset")) && (v = atou(p))) |
| offset = v; |
| |
| sectors = xsectors = (size - offset) >> hd_geometry.sector_shift; |
| |
| hd_geometry.hsrc = "guess"; |
| hd_geometry.ssrc = "guess"; |
| hd_geometry.sectors = sectors; |
| hd_geometry.offset = offset; |
| |
| if ((p = getcmditem("iso")) != CMD_NOTFOUND) { |
| #if DBG_ELTORITO |
| eltorito_dump(where); |
| #endif |
| struct edd4_bvd *bvd = (struct edd4_bvd *)(where + 17 * 2048); |
| /* Tiny sanity check */ |
| if ((bvd->boot_rec_ind != 0) || (bvd->ver != 1)) |
| printf("El Torito BVD sanity check failed.\n"); |
| struct edd4_bootcat *boot_cat = |
| (struct edd4_bootcat *)(where + bvd->boot_cat * 2048); |
| /* Another tiny sanity check */ |
| if ((boot_cat->validation_entry.platform_id != 0) || |
| (boot_cat->validation_entry.key55 != 0x55) || |
| (boot_cat->validation_entry.keyAA != 0xAA)) |
| printf("El Torito boot catalog sanity check failed.\n"); |
| /* If we have an emulation mode, set the offset to the image */ |
| if (boot_cat->initial_entry.media_type) |
| hd_geometry.offset += boot_cat->initial_entry.load_block * 2048; |
| else |
| /* We're a no-emulation mode, so we will boot to an offset */ |
| hd_geometry.boot_lba = boot_cat->initial_entry.load_block * 4; |
| if (boot_cat->initial_entry.media_type < 4) { |
| /* We're a floppy emulation mode or our params will be |
| * overwritten by the no emulation mode case |
| */ |
| hd_geometry.driveno = 0x00; |
| hd_geometry.c = 80; |
| hd_geometry.h = 2; |
| } |
| switch (boot_cat->initial_entry.media_type) { |
| case 0: /* No emulation */ |
| hd_geometry.driveno = 0xE0; |
| hd_geometry.type = 10; /* ATAPI removable media device */ |
| hd_geometry.c = 65535; |
| hd_geometry.h = 255; |
| hd_geometry.s = 15; |
| /* 2048-byte sectors, so adjust the size and count */ |
| hd_geometry.sector_shift = 11; |
| break; |
| case 1: /* 1.2 MB floppy */ |
| hd_geometry.s = 15; |
| hd_geometry.type = 2; |
| sectors = 2400; |
| break; |
| case 2: /* 1.44 MB floppy */ |
| hd_geometry.s = 18; |
| hd_geometry.type = 4; |
| sectors = 2880; |
| break; |
| case 3: /* 2.88 MB floppy */ |
| hd_geometry.s = 36; |
| hd_geometry.type = 6; |
| sectors = 5760; |
| break; |
| case 4: |
| hd_geometry.driveno = 0x80; |
| hd_geometry.type = 0; |
| break; |
| } |
| sectors = (size - hd_geometry.offset) >> hd_geometry.sector_shift; |
| |
| /* For HDD emulation, we figure out the geometry later. Otherwise: */ |
| if (hd_geometry.s) { |
| hd_geometry.hsrc = hd_geometry.ssrc = "El Torito"; |
| } |
| hd_geometry.sectors = sectors; |
| } |
| |
| /* Do we have a DOSEMU header? */ |
| memcpy(&dosemu, (char *)where + hd_geometry.offset, sizeof dosemu); |
| if (!memcmp("DOSEMU", dosemu.magic, 7)) { |
| /* Always a hard disk unless overruled by command-line options */ |
| hd_geometry.driveno = 0x80; |
| hd_geometry.type = 0; |
| hd_geometry.c = dosemu.c; |
| hd_geometry.h = dosemu.h; |
| hd_geometry.s = dosemu.s; |
| hd_geometry.offset += dosemu.offset; |
| sectors = (size - hd_geometry.offset) >> hd_geometry.sector_shift; |
| |
| hd_geometry.hsrc = hd_geometry.ssrc = "DOSEMU"; |
| } |
| |
| if (CMD_HASDATA(p = getcmditem("c")) && (v = atou(p))) |
| hd_geometry.c = v; |
| if (CMD_HASDATA(p = getcmditem("h")) && (v = atou(p))) { |
| hd_geometry.h = v; |
| hd_geometry.hsrc = "cmd"; |
| } |
| if (CMD_HASDATA(p = getcmditem("s")) && (v = atou(p))) { |
| hd_geometry.s = v; |
| hd_geometry.ssrc = "cmd"; |
| } |
| |
| if (!hd_geometry.h || !hd_geometry.s) { |
| int h, s, max_h, max_s; |
| |
| max_h = hd_geometry.h; |
| max_s = hd_geometry.s; |
| |
| if (!(max_h | max_s)) { |
| /* Look for a FAT superblock and if we find something that looks |
| enough like one, use geometry from that. This takes care of |
| megafloppy images and unpartitioned hard disks. */ |
| const struct fat_extra *extra = NULL; |
| const struct fat_super *fs = (const struct fat_super *) |
| ((char *)where + hd_geometry.offset); |
| |
| if ((fs->bpb_media == 0xf0 || fs->bpb_media >= 0xf8) && |
| (fs->bs_jmpboot[0] == 0xe9 || fs->bs_jmpboot[0] == 0xeb) && |
| fs->bpb_bytspersec == 512 && |
| fs->bpb_numheads >= 1 && fs->bpb_numheads <= 256 && |
| fs->bpb_secpertrk >= 1 && fs->bpb_secpertrk <= 63) { |
| extra = |
| fs->bpb_fatsz16 ? &fs->x.fat16.extra : &fs->x.fat32.extra; |
| if (! |
| (extra->bs_bootsig == 0x29 && extra->bs_filsystype[0] == 'F' |
| && extra->bs_filsystype[1] == 'A' |
| && extra->bs_filsystype[2] == 'T')) |
| extra = NULL; |
| } |
| if (extra) { |
| hd_geometry.driveno = extra->bs_drvnum & 0x80; |
| max_h = fs->bpb_numheads; |
| max_s = fs->bpb_secpertrk; |
| hd_geometry.hsrc = hd_geometry.ssrc = "FAT"; |
| } |
| } |
| |
| if (!(max_h | max_s)) { |
| /* No FAT filesystem found to steal geometry from... */ |
| if ((sectors < 4096 * 2) && (hd_geometry.sector_shift == 9)) { |
| int ok = 0; |
| unsigned int xsectors = sectors; |
| |
| hd_geometry.driveno = 0; /* Assume floppy */ |
| |
| while (!ok) { |
| /* Assume it's a floppy drive, guess a geometry */ |
| unsigned int type, track; |
| int c, h, s = 0; |
| |
| if (xsectors < 320 * 2) { |
| c = 40; |
| h = 1; |
| type = 1; |
| } else if (xsectors < 640 * 2) { |
| c = 40; |
| h = 2; |
| type = 1; |
| } else if (xsectors < 1200 * 2) { |
| c = 80; |
| h = 2; |
| type = 3; |
| } else if (xsectors < 1440 * 2) { |
| c = 80; |
| h = 2; |
| type = 2; |
| } else if (xsectors < 2880 * 2) { |
| c = 80; |
| h = 2; |
| type = 4; |
| } else { |
| c = 80; |
| h = 2; |
| type = 6; |
| } |
| track = c * h; |
| while (c < 256) { |
| s = xsectors / track; |
| if (s < 63 && (xsectors % track) == 0) { |
| ok = 1; |
| break; |
| } |
| c++; |
| track += h; |
| } |
| if (ok) { |
| max_h = h; |
| max_s = s; |
| hd_geometry.hsrc = hd_geometry.ssrc = "fd"; |
| } else { |
| /* No valid floppy geometry, fake it by simulating broken |
| sectors at the end of the image... */ |
| xsectors++; |
| } |
| |
| hd_geometry.type = type; |
| } |
| } else { |
| /* Assume it is a hard disk image and scan for a partition table */ |
| const struct ptab_entry *ptab = (const struct ptab_entry *) |
| ((char *)where + hd_geometry.offset + (512 - 2 - 4 * 16)); |
| |
| /* Assume hard disk */ |
| if (!hd_geometry.driveno) |
| hd_geometry.driveno = 0x80; |
| |
| if (*(uint16_t *) ((char *)where + hd_geometry.offset + 512 - 2) == 0xaa55) { |
| for (i = 0; i < 4; i++) { |
| if (ptab[i].type && !(ptab[i].active & 0x7f)) { |
| s = (ptab[i].start_s & 0x3f); |
| h = ptab[i].start_h + 1; |
| |
| if (max_h < h) |
| max_h = h; |
| if (max_s < s) |
| max_s = s; |
| |
| s = (ptab[i].end_s & 0x3f); |
| h = ptab[i].end_h + 1; |
| |
| if (max_h < h) { |
| max_h = h; |
| hd_geometry.hsrc = "MBR"; |
| } |
| if (max_s < s) { |
| max_s = s; |
| hd_geometry.ssrc = "MBR"; |
| } |
| } |
| } |
| } |
| |
| hd_geometry.type = 0; |
| } |
| } |
| |
| if (!max_h) |
| max_h = xsectors > 2097152 ? 255 : 64; |
| if (!max_s) |
| max_s = xsectors > 2097152 ? 63 : 32; |
| |
| hd_geometry.h = max_h; |
| hd_geometry.s = max_s; |
| } |
| |
| if (!hd_geometry.c) |
| hd_geometry.c = xsectors / (hd_geometry.h * hd_geometry.s); |
| |
| if ((p = getcmditem("floppy")) != CMD_NOTFOUND) { |
| hd_geometry.driveno = CMD_HASDATA(p) ? atou(p) & 0x7f : 0; |
| } else if ((p = getcmditem("harddisk")) != CMD_NOTFOUND) { |
| hd_geometry.driveno = CMD_HASDATA(p) ? atou(p) | 0x80 : 0x80; |
| } |
| |
| if (hd_geometry.driveno & 0x80) { |
| hd_geometry.type = 0; /* Type = hard disk */ |
| } else { |
| if (hd_geometry.type == 0) |
| hd_geometry.type = 0x10; /* ATAPI floppy, e.g. LS-120 */ |
| } |
| |
| if ((size - hd_geometry.offset) & 0x1ff) { |
| puts("MEMDISK: Image has fractional end sector\n"); |
| } |
| if (sectors % (hd_geometry.h * hd_geometry.s)) { |
| puts("MEMDISK: Image seems to have fractional end cylinder\n"); |
| } |
| if ((hd_geometry.c * hd_geometry.h * hd_geometry.s) > sectors) { |
| puts("MEMDISK: Image appears to be truncated\n"); |
| } |
| |
| return &hd_geometry; |
| } |
| |
| /* |
| * Find a $PnP installation check structure; return (ES << 16) + DI value |
| */ |
| static uint32_t pnp_install_check(void) |
| { |
| uint32_t *seg; |
| unsigned char *p, csum; |
| int i, len; |
| |
| for (seg = (uint32_t *) 0xf0000; seg < (uint32_t *) 0x100000; seg += 4) { |
| if (*seg == ('$' + ('P' << 8) + ('n' << 16) + ('P' << 24))) { |
| p = (unsigned char *)seg; |
| len = p[5]; |
| if (len < 0x21) |
| continue; |
| csum = 0; |
| for (i = len; i; i--) |
| csum += *p++; |
| if (csum != 0) |
| continue; |
| |
| return (0xf000 << 16) + (uint16_t) (unsigned long)seg; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Relocate the real-mode code to a new segment |
| */ |
| struct gdt_ptr { |
| uint16_t limit; |
| uint32_t base; |
| } __attribute__ ((packed)); |
| |
| static void set_seg_base(uint32_t gdt_base, int seg, uint32_t v) |
| { |
| *(uint16_t *) (gdt_base + seg + 2) = v; |
| *(uint8_t *) (gdt_base + seg + 4) = v >> 16; |
| *(uint8_t *) (gdt_base + seg + 7) = v >> 24; |
| } |
| |
| static void relocate_rm_code(uint32_t newbase) |
| { |
| uint32_t gdt_base; |
| uint32_t oldbase = rm_args.rm_base; |
| uint32_t delta = newbase - oldbase; |
| |
| cli(); |
| memmove((void *)newbase, (void *)oldbase, rm_args.rm_size); |
| |
| rm_args.rm_return += delta; |
| rm_args.rm_intcall += delta; |
| rm_args.rm_bounce += delta; |
| rm_args.rm_base += delta; |
| rm_args.rm_gdt += delta; |
| rm_args.rm_pmjmp += delta; |
| rm_args.rm_rmjmp += delta; |
| |
| gdt_base = rm_args.rm_gdt; |
| |
| *(uint32_t *) (gdt_base + 2) = gdt_base; /* GDT self-pointer */ |
| |
| /* Segments 0x10 and 0x18 are real-mode-based */ |
| set_seg_base(gdt_base, 0x10, rm_args.rm_base); |
| set_seg_base(gdt_base, 0x18, rm_args.rm_base); |
| |
| #if __SIZEOF_POINTER__ == 4 |
| asm volatile ("lgdtl %0"::"m" (*(char *)gdt_base)); |
| #elif __SIZEOF_POINTER__ == 8 |
| asm volatile ("lgdt %0"::"m" (*(char *)gdt_base)); |
| #else |
| #error "unsupported architecture" |
| #endif |
| |
| *(uint32_t *) rm_args.rm_pmjmp += delta; |
| *(uint16_t *) rm_args.rm_rmjmp += delta >> 4; |
| |
| rm_args.rm_handle_interrupt += delta; |
| |
| sti(); |
| } |
| |
| static uint8_t checksum_buf(const void *buf, int count) |
| { |
| const uint8_t *p = buf; |
| uint8_t c = 0; |
| |
| while (count--) |
| c += *p++; |
| |
| return c; |
| } |
| |
| static int stack_needed(void) |
| { |
| const unsigned int min_stack = 128; /* Minimum stack size */ |
| const unsigned int def_stack = 512; /* Default stack size */ |
| unsigned int v = 0; |
| const char *p; |
| |
| if (CMD_HASDATA(p = getcmditem("stack"))) |
| v = atou(p); |
| |
| if (!v) |
| v = def_stack; |
| |
| if (v < min_stack) |
| v = min_stack; |
| |
| return v; |
| } |
| |
| /* |
| * Set max memory by reservation |
| * Adds reservations to data in INT15h to prevent access to the top of RAM |
| * if there's any above the point specified. |
| */ |
| void setmaxmem(unsigned long long restop_ull) |
| { |
| uint32_t restop; |
| struct e820range *ep; |
| const int int15restype = 2; |
| |
| /* insertrange() works on uint32_t */ |
| restop = min(restop_ull, UINT32_MAX); |
| /* printf(" setmaxmem '%08x%08x' => %08x\n", |
| (unsigned int)(restop_ull>>32), (unsigned int)restop_ull, restop); */ |
| |
| for (ep = ranges; ep->type != -1U; ep++) { |
| if (ep->type == 1) { /* Only if available */ |
| if (ep->start >= restop) { |
| /* printf(" %08x -> 2\n", ep->start); */ |
| ep->type = int15restype; |
| } else if (ep[1].start > restop) { |
| /* printf(" +%08x =2; cut %08x\n", restop, ep->start); */ |
| insertrange(restop, (ep[1].start - restop), int15restype); |
| } |
| } |
| } |
| parse_mem(); |
| } |
| |
| struct real_mode_args rm_args; |
| |
| /* |
| * Actual setup routine |
| * Returns the drive number (which is then passed in %dl to the |
| * called routine.) |
| */ |
| void setup(const struct real_mode_args *rm_args_ptr) |
| { |
| unsigned int bin_size; |
| char *memdisk_hook; |
| struct memdisk_header *hptr; |
| struct patch_area *pptr; |
| struct mBFT *mbft; |
| uint16_t driverseg; |
| uint32_t driverptr, driveraddr; |
| uint16_t dosmem_k; |
| uint32_t stddosmem; |
| const struct geometry *geometry; |
| unsigned int total_size; |
| unsigned int cmdline_len, stack_len, e820_len; |
| const struct edd4_bvd *bvd; |
| const struct edd4_bootcat *boot_cat = 0; |
| com32sys_t regs; |
| uint32_t ramdisk_image, ramdisk_size; |
| uint32_t boot_base, rm_base; |
| int bios_drives; |
| int do_edd = 1; /* 0 = no, 1 = yes, default is yes */ |
| int do_eltorito = 0; /* default is no */ |
| int no_bpt; /* No valid BPT presented */ |
| uint32_t boot_seg = 0; /* Meaning 0000:7C00 */ |
| uint32_t boot_len = 512; /* One sector */ |
| const char *p; |
| |
| /* We need to copy the rm_args into their proper place */ |
| memcpy(&rm_args, rm_args_ptr, sizeof rm_args); |
| sti(); /* ... then interrupts are safe */ |
| |
| /* Show signs of life */ |
| printf("%s %s\n", memdisk_version, copyright); |
| |
| if (!shdr->ramdisk_image || !shdr->ramdisk_size) |
| die("MEMDISK: No ramdisk image specified!\n"); |
| |
| ramdisk_image = shdr->ramdisk_image; |
| ramdisk_size = shdr->ramdisk_size; |
| |
| e820map_init(); /* Initialize memory data structure */ |
| get_mem(); /* Query BIOS for memory map */ |
| parse_mem(); /* Parse memory map */ |
| |
| printf("Ramdisk at 0x%08x, length 0x%08x\n", ramdisk_image, ramdisk_size); |
| |
| unzip_if_needed(&ramdisk_image, &ramdisk_size); |
| |
| geometry = get_disk_image_geometry(ramdisk_image, ramdisk_size); |
| |
| if (getcmditem("edd") != CMD_NOTFOUND || |
| getcmditem("ebios") != CMD_NOTFOUND) |
| do_edd = 1; |
| else if (getcmditem("noedd") != CMD_NOTFOUND || |
| getcmditem("noebios") != CMD_NOTFOUND || |
| getcmditem("cbios") != CMD_NOTFOUND) |
| do_edd = 0; |
| else |
| do_edd = (geometry->driveno & 0x80) ? 1 : 0; |
| |
| if (getcmditem("iso") != CMD_NOTFOUND) { |
| do_eltorito = 1; |
| do_edd = 1; /* Mandatory */ |
| } |
| |
| /* Choose the appropriate installable memdisk hook */ |
| if (do_eltorito) { |
| if (geometry->sector_shift == 11) { |
| bin_size = (int)&_binary_memdisk_iso_2048_bin_size; |
| memdisk_hook = (char *)&_binary_memdisk_iso_2048_bin_start; |
| } else { |
| bin_size = (int)&_binary_memdisk_iso_512_bin_size; |
| memdisk_hook = (char *)&_binary_memdisk_iso_512_bin_start; |
| } |
| } else { |
| if (do_edd) { |
| bin_size = (int)&_binary_memdisk_edd_512_bin_size; |
| memdisk_hook = (char *)&_binary_memdisk_edd_512_bin_start; |
| } else { |
| bin_size = (int)&_binary_memdisk_chs_512_bin_size; |
| memdisk_hook = (char *)&_binary_memdisk_chs_512_bin_start; |
| } |
| } |
| |
| /* Reserve the ramdisk memory */ |
| insertrange(ramdisk_image, ramdisk_size, 2); |
| parse_mem(); /* Recompute variables */ |
| |
| /* Figure out where it needs to go */ |
| hptr = (struct memdisk_header *)memdisk_hook; |
| pptr = (struct patch_area *)(memdisk_hook + hptr->patch_offs); |
| |
| dosmem_k = rdz_16(BIOS_BASEMEM); |
| pptr->mdi.olddosmem = dosmem_k; |
| stddosmem = dosmem_k << 10; |
| /* If INT 15 E820 and INT 12 disagree, go with the most conservative */ |
| if (stddosmem > dos_mem) |
| stddosmem = dos_mem; |
| |
| pptr->driveno = geometry->driveno; |
| pptr->drivetype = geometry->type; |
| pptr->cylinders = geometry->c; /* Possible precision loss */ |
| pptr->heads = geometry->h; |
| pptr->sectors = geometry->s; |
| pptr->mdi.disksize = geometry->sectors; |
| pptr->mdi.diskbuf = ramdisk_image + geometry->offset; |
| pptr->mdi.sector_shift = geometry->sector_shift; |
| pptr->statusptr = (geometry->driveno & 0x80) ? 0x474 : 0x441; |
| |
| pptr->mdi.bootloaderid = shdr->type_of_loader; |
| |
| pptr->configflags = CONFIG_SAFEINT; /* Default */ |
| /* Set config flags */ |
| if (getcmditem("ro") != CMD_NOTFOUND) { |
| pptr->configflags |= CONFIG_READONLY; |
| } |
| if (getcmditem("raw") != CMD_NOTFOUND) { |
| pptr->configflags &= ~CONFIG_MODEMASK; |
| pptr->configflags |= CONFIG_RAW; |
| } |
| if (getcmditem("bigraw") != CMD_NOTFOUND) { |
| pptr->configflags &= ~CONFIG_MODEMASK; |
| pptr->configflags |= CONFIG_BIGRAW | CONFIG_RAW; |
| } |
| if (getcmditem("int") != CMD_NOTFOUND) { |
| pptr->configflags &= ~CONFIG_MODEMASK; |
| /* pptr->configflags |= 0; */ |
| } |
| if (getcmditem("safeint") != CMD_NOTFOUND) { |
| pptr->configflags &= ~CONFIG_MODEMASK; |
| pptr->configflags |= CONFIG_SAFEINT; |
| } |
| |
| printf("Disk is %s%d, %u%s K, C/H/S = %u/%u/%u (%s/%s), EDD %s, %s\n", |
| (geometry->driveno & 0x80) ? "hd" : "fd", |
| geometry->driveno & 0x7f, |
| geometry->sectors >> 1, |
| (geometry->sectors & 1) ? ".5" : "", |
| geometry->c, geometry->h, geometry->s, |
| geometry->hsrc, geometry->ssrc, |
| do_edd ? "on" : "off", |
| pptr->configflags & CONFIG_READONLY ? "ro" : "rw"); |
| |
| puts("Using "); |
| switch (pptr->configflags & CONFIG_MODEMASK) { |
| case 0: |
| puts("standard INT 15h"); |
| break; |
| case CONFIG_SAFEINT: |
| puts("safe INT 15h"); |
| break; |
| case CONFIG_RAW: |
| puts("raw"); |
| break; |
| case CONFIG_RAW | CONFIG_BIGRAW: |
| puts("big real mode raw"); |
| break; |
| default: |
| printf("unknown %#x", pptr->configflags & CONFIG_MODEMASK); |
| break; |
| } |
| puts(" access to high memory\n"); |
| |
| /* Set up a drive parameter table */ |
| if (geometry->driveno & 0x80) { |
| /* Hard disk */ |
| pptr->dpt.hd.max_cyl = geometry->c - 1; |
| pptr->dpt.hd.max_head = geometry->h - 1; |
| pptr->dpt.hd.ctrl = (geometry->h > 8) ? 0x08 : 0; |
| } else { |
| /* Floppy - most of these fields are bogus and mimic |
| a 1.44 MB floppy drive */ |
| pptr->dpt.fd.specify1 = 0xdf; |
| pptr->dpt.fd.specify2 = 0x02; |
| pptr->dpt.fd.delay = 0x25; |
| pptr->dpt.fd.sectors = geometry->s; |
| pptr->dpt.fd.bps = 0x02; |
| pptr->dpt.fd.isgap = 0x12; |
| pptr->dpt.fd.dlen = 0xff; |
| pptr->dpt.fd.fgap = 0x6c; |
| pptr->dpt.fd.ffill = 0xf6; |
| pptr->dpt.fd.settle = 0x0f; |
| pptr->dpt.fd.mstart = 0x05; |
| pptr->dpt.fd.maxtrack = geometry->c - 1; |
| pptr->dpt.fd.cmos = geometry->type > 5 ? 5 : geometry->type; |
| |
| pptr->dpt.fd.old_fd_dpt = rdz_32(BIOS_INT1E); |
| } |
| |
| /* Set up an EDD drive parameter table */ |
| if (do_edd) { |
| pptr->edd_dpt.sectors = geometry->sectors; |
| /* The EDD spec has this as <= 15482880 sectors (1024x240x63); |
| this seems to make very little sense. Try for something saner. */ |
| if (geometry->c <= 1024 && geometry->h <= 255 && geometry->s <= 63) { |
| pptr->edd_dpt.c = geometry->c; |
| pptr->edd_dpt.h = geometry->h; |
| pptr->edd_dpt.s = geometry->s; |
| /* EDD-4 states that invalid geometry should be returned |
| * for INT 0x13, AH=0x48 "EDD Get Disk Parameters" call on an |
| * El Torito ODD. Check for 2048-byte sector size |
| */ |
| if (geometry->sector_shift != 11) |
| pptr->edd_dpt.flags |= 0x0002; /* Geometry valid */ |
| } |
| if (!(geometry->driveno & 0x80)) { |
| /* Floppy drive. Mark it as a removable device with |
| media change notification; media is present. */ |
| pptr->edd_dpt.flags |= 0x0014; |
| } |
| |
| pptr->edd_dpt.devpath[0] = pptr->mdi.diskbuf; |
| pptr->edd_dpt.chksum = -checksum_buf(&pptr->edd_dpt.dpikey, 73 - 30); |
| } |
| |
| if (do_eltorito) { |
| bvd = (struct edd4_bvd *)(ramdisk_image + 17 * 2048); |
| boot_cat = |
| (struct edd4_bootcat *)(ramdisk_image + bvd->boot_cat * 2048); |
| pptr->cd_pkt.type = boot_cat->initial_entry.media_type; /* Cheat */ |
| pptr->cd_pkt.driveno = geometry->driveno; |
| pptr->cd_pkt.start = boot_cat->initial_entry.load_block; |
| boot_seg = pptr->cd_pkt.load_seg = boot_cat->initial_entry.load_seg; |
| pptr->cd_pkt.sect_count = boot_cat->initial_entry.sect_count; |
| boot_len = pptr->cd_pkt.sect_count * 512; |
| pptr->cd_pkt.geom1 = (uint8_t)(pptr->cylinders) & 0xFF; |
| pptr->cd_pkt.geom2 = |
| (uint8_t)(pptr->sectors) | (uint8_t)((pptr->cylinders >> 2) & 0xC0); |
| pptr->cd_pkt.geom3 = (uint8_t)(pptr->heads); |
| } |
| |
| if ((p = getcmditem("mem")) != CMD_NOTFOUND) { |
| setmaxmem(suffix_number(p)); |
| } |
| |
| /* The size is given by hptr->total_size plus the size of the E820 |
| map -- 12 bytes per range; we may need as many as 2 additional |
| ranges (each insertrange() can worst-case turn 1 area into 3) |
| plus the terminating range, over what nranges currently show. */ |
| total_size = hptr->total_size; /* Actual memdisk code */ |
| e820_len = (nranges + 3) * sizeof(ranges[0]); |
| total_size += e820_len; /* E820 memory ranges */ |
| cmdline_len = strlen(shdr->cmdline) + 1; |
| total_size += cmdline_len; /* Command line */ |
| stack_len = stack_needed(); |
| total_size += stack_len; /* Stack */ |
| printf("Code %u, meminfo %u, cmdline %u, stack %u\n", |
| hptr->total_size, e820_len, cmdline_len, stack_len); |
| printf("Total size needed = %u bytes, allocating %uK\n", |
| total_size, (total_size + 0x3ff) >> 10); |
| |
| if (total_size > dos_mem) |
| die("MEMDISK: Insufficient low memory\n"); |
| |
| driveraddr = stddosmem - total_size; |
| driveraddr &= ~0x3FF; |
| |
| printf("Old dos memory at 0x%05x (map says 0x%05x), loading at 0x%05x\n", |
| stddosmem, dos_mem, driveraddr); |
| |
| /* Reserve this range of memory */ |
| wrz_16(BIOS_BASEMEM, driveraddr >> 10); |
| insertrange(driveraddr, dos_mem - driveraddr, 2); |
| parse_mem(); |
| |
| pptr->mem1mb = low_mem >> 10; |
| pptr->mem16mb = high_mem >> 16; |
| if (low_mem == (15 << 20)) { |
| /* lowmem maxed out */ |
| uint32_t int1588mem = (high_mem >> 10) + (low_mem >> 10); |
| pptr->memint1588 = (int1588mem > 0xffff) ? 0xffff : int1588mem; |
| } else { |
| pptr->memint1588 = low_mem >> 10; |
| } |
| |
| printf("1588: 0x%04x 15E801: 0x%04x 0x%04x\n", |
| pptr->memint1588, pptr->mem1mb, pptr->mem16mb); |
| |
| driverseg = driveraddr >> 4; |
| driverptr = driverseg << 16; |
| |
| /* Anything beyond the end is for the stack */ |
| pptr->mystack = (uint16_t) (stddosmem - driveraddr); |
| |
| pptr->mdi.oldint13.uint32 = rdz_32(BIOS_INT13); |
| pptr->mdi.oldint15.uint32 = rdz_32(BIOS_INT15); |
| |
| /* Adjust the E820 table: if there are null ranges (type 0) |
| at the end, change them to type end of list (-1). |
| This is necessary for the driver to be able to report end |
| of list correctly. */ |
| while (nranges && ranges[nranges - 1].type == 0) { |
| ranges[--nranges].type = -1; |
| } |
| |
| if (getcmditem("nopassany") != CMD_NOTFOUND) { |
| printf("nopassany specified - we're the only drive of any kind\n"); |
| bios_drives = 0; |
| pptr->drivecnt = 0; |
| no_bpt = 1; |
| pptr->mdi.oldint13.uint32 = driverptr + hptr->iret_offs; |
| wrz_8(BIOS_EQUIP, rdz_8(BIOS_EQUIP) & ~0xc1); |
| wrz_8(BIOS_HD_COUNT, 0); |
| } else if (getcmditem("nopass") != CMD_NOTFOUND) { |
| printf("nopass specified - we're the only drive\n"); |
| bios_drives = 0; |
| pptr->drivecnt = 0; |
| no_bpt = 1; |
| } else { |
| /* Query drive parameters of this type */ |
| memset(®s, 0, sizeof regs); |
| regs.es = 0; |
| regs.eax.b[1] = 0x08; |
| regs.edx.b[0] = geometry->driveno & 0x80; |
| intcall(0x13, ®s, ®s); |
| |
| /* Note: per suggestion from the Interrupt List, consider |
| INT 13 08 to have failed if the sector count in CL is zero. */ |
| if ((regs.eflags.l & 1) || !(regs.ecx.b[0] & 0x3f)) { |
| printf("INT 13 08: Failure, assuming this is the only drive\n"); |
| pptr->drivecnt = 0; |
| no_bpt = 1; |
| } else { |
| printf("INT 13 08: Success, count = %u, BPT = %04x:%04x\n", |
| regs.edx.b[0], regs.es, regs.edi.w[0]); |
| pptr->drivecnt = regs.edx.b[0]; |
| no_bpt = !(regs.es | regs.edi.w[0]); |
| } |
| |
| /* Compare what INT 13h returned with the appropriate equipment byte */ |
| if (geometry->driveno & 0x80) { |
| bios_drives = rdz_8(BIOS_HD_COUNT); |
| } else { |
| uint8_t equip = rdz_8(BIOS_EQUIP); |
| |
| if (equip & 1) |
| bios_drives = (equip >> 6) + 1; |
| else |
| bios_drives = 0; |
| } |
| |
| if (pptr->drivecnt > bios_drives) { |
| printf("BIOS equipment byte says count = %d, go with that\n", |
| bios_drives); |
| pptr->drivecnt = bios_drives; |
| } |
| } |
| |
| /* Add ourselves to the drive count */ |
| pptr->drivecnt++; |
| |
| /* Discontiguous drive space. There is no really good solution for this. */ |
| if (pptr->drivecnt <= (geometry->driveno & 0x7f)) |
| pptr->drivecnt = (geometry->driveno & 0x7f) + 1; |
| |
| /* Probe for contiguous range of BIOS drives starting with driveno */ |
| pptr->driveshiftlimit = probe_drive_range(geometry->driveno) + 1; |
| if ((pptr->driveshiftlimit & 0x80) != (geometry->driveno & 0x80)) |
| printf("We lost the last drive in our class of drives.\n"); |
| printf("Drive probing gives drive shift limit: 0x%02x\n", |
| pptr->driveshiftlimit); |
| |
| /* Pointer to the command line */ |
| pptr->mdi.cmdline.seg_off.offset = bin_size + (nranges + 1) * sizeof(ranges[0]); |
| pptr->mdi.cmdline.seg_off.segment = driverseg; |
| |
| /* Copy driver followed by E820 table followed by command line */ |
| { |
| unsigned char *dpp = (unsigned char *)(driverseg << 4); |
| |
| /* Adjust these pointers to point to the installed image */ |
| /* Careful about the order here... the image isn't copied yet! */ |
| pptr = (struct patch_area *)(dpp + hptr->patch_offs); |
| hptr = (struct memdisk_header *)dpp; |
| |
| /* Actually copy to low memory */ |
| dpp = mempcpy(dpp, memdisk_hook, bin_size); |
| dpp = mempcpy(dpp, ranges, (nranges + 1) * sizeof(ranges[0])); |
| dpp = mempcpy(dpp, shdr->cmdline, cmdline_len); |
| } |
| |
| /* Note the previous INT 13h hook in the "safe hook" structure */ |
| hptr->safe_hook.old_hook.uint32 = pptr->mdi.oldint13.uint32; |
| |
| /* Re-fill the "safe hook" mBFT field with the physical address */ |
| mbft = (struct mBFT *)(((const char *)hptr) + hptr->safe_hook.mbft); |
| hptr->safe_hook.mbft = (size_t)mbft; |
| |
| /* Update various BIOS magic data areas (gotta love this shit) */ |
| |
| if (geometry->driveno & 0x80) { |
| /* Update BIOS hard disk count */ |
| uint8_t nhd = pptr->drivecnt; |
| |
| if (nhd > 128) |
| nhd = 128; |
| |
| if (!do_eltorito) |
| wrz_8(BIOS_HD_COUNT, nhd); |
| } else { |
| /* Update BIOS floppy disk count */ |
| uint8_t equip = rdz_8(BIOS_EQUIP); |
| uint8_t nflop = pptr->drivecnt; |
| |
| if (nflop > 4) /* Limit of equipment byte */ |
| nflop = 4; |
| |
| equip &= 0x3E; |
| if (nflop) |
| equip |= ((nflop - 1) << 6) | 0x01; |
| |
| wrz_8(BIOS_EQUIP, equip); |
| |
| /* Install DPT pointer if this was the only floppy */ |
| if (getcmditem("dpt") != CMD_NOTFOUND || |
| ((nflop == 1 || no_bpt) && getcmditem("nodpt") == CMD_NOTFOUND)) { |
| /* Do install a replacement DPT into INT 1Eh */ |
| pptr->mdi.dpt_ptr = |
| hptr->patch_offs + offsetof(struct patch_area, dpt); |
| } |
| } |
| |
| /* Complete the mBFT */ |
| mbft->acpi.signature[0] = 'm'; /* "mBFT" */ |
| mbft->acpi.signature[1] = 'B'; |
| mbft->acpi.signature[2] = 'F'; |
| mbft->acpi.signature[3] = 'T'; |
| mbft->safe_hook = (size_t)&hptr->safe_hook; |
| mbft->acpi.checksum = -checksum_buf(mbft, mbft->acpi.length); |
| |
| /* Install the interrupt handlers */ |
| printf("old: int13 = %08x int15 = %08x int1e = %08x\n", |
| rdz_32(BIOS_INT13), rdz_32(BIOS_INT15), rdz_32(BIOS_INT1E)); |
| |
| wrz_32(BIOS_INT13, driverptr + hptr->int13_offs); |
| wrz_32(BIOS_INT15, driverptr + hptr->int15_offs); |
| if (pptr->mdi.dpt_ptr) |
| wrz_32(BIOS_INT1E, driverptr + pptr->mdi.dpt_ptr); |
| |
| printf("new: int13 = %08x int15 = %08x int1e = %08x\n", |
| rdz_32(BIOS_INT13), rdz_32(BIOS_INT15), rdz_32(BIOS_INT1E)); |
| |
| /* Figure out entry point */ |
| if (!boot_seg) { |
| boot_base = 0x7c00; |
| shdr->sssp = 0x7c00; |
| shdr->csip = 0x7c00; |
| } else { |
| boot_base = boot_seg << 4; |
| shdr->sssp = boot_seg << 16; |
| shdr->csip = boot_seg << 16; |
| } |
| |
| /* Relocate the real-mode code to below the stub */ |
| rm_base = (driveraddr - rm_args.rm_size) & ~15; |
| if (rm_base < boot_base + boot_len) |
| die("MEMDISK: bootstrap too large to load\n"); |
| |
| relocate_rm_code(rm_base); |
| |
| /* Reboot into the new "disk" */ |
| puts("Loading boot sector... "); |
| |
| memcpy((void *)boot_base, |
| (char *)pptr->mdi.diskbuf + geometry->boot_lba * 512, |
| boot_len); |
| |
| if (getcmditem("pause") != CMD_NOTFOUND) { |
| puts("press any key to boot... "); |
| memset(®s, 0, sizeof regs); |
| regs.eax.w[0] = 0; |
| intcall(0x16, ®s, NULL); |
| } |
| |
| puts("booting...\n"); |
| |
| /* On return the assembly code will jump to the boot vector */ |
| shdr->esdi = pnp_install_check(); |
| shdr->edx = geometry->driveno; |
| } |
| |