/*! @file boot.c Copyright 2007 VMware Inc. Author: David Elliott */ #include "libsaio.h" #include "saio_types.h" #include "bootstruct.h" #include "preboot.h" #include "boot.h" //========================================================================== // Zero the BSS. static void zeroBSS() { extern char _DATA__bss__begin, _DATA__bss__end; extern char _DATA__common__begin, _DATA__common__end; bzero( &_DATA__bss__begin, (&_DATA__bss__end - &_DATA__bss__begin) ); bzero( &_DATA__common__begin, (&_DATA__common__end - &_DATA__common__begin) ); } //========================================================================== // Malloc error function static void malloc_error(char *addr, size_t size) { printf("\nMemory allocation error (0x%x, 0x%x)\n", (unsigned)addr, (unsigned)size); asm volatile ("hlt"); } //========================================================================== // A20 check #if 0 static uint32_t a20_test_dword;// In BSS static const uint32_t A20_TEST_VALUE = ('_' << 24) | ('A' << 16) | ('2' << 8) | '0'; bool isA20Enabled() { a20_test_dword = 0; uint32_t *p_high_a20_test_dword = (uint32_t*)((char*)&a20_test_dword + (1 << 20)); *p_high_a20_test_dword = A20_TEST_VALUE; return (a20_test_dword != *p_high_a20_test_dword); } #endif extern int chainbootdev; /* We build asm.s with -DBOOT1 meaning we have no loader() and don't really want it either. Provide this stub to do nothing which is actually exactly what happens anyway since the INT2b call it makes is clearly provided by some other piece of proprietary code. On a normal system (even one with PXE) it does nothing but waste time. */ void loader(UInt32 code, UInt32 cmdptr) { } bool selectBootDevice(int biosdev); bool shouldBootCD(int biosdev); // String UUID of the CD that was booted static char gDisc1FSUUIDStr[37]; //========================================================================== // main function void boot(int biosdev_) { zeroBSS(); int biosdev = biosdev_ & kBIOSDevMask; gBIOSDev = biosdev; printf("VMware Darwin CD Prebooter\n"); printf("Copyright 2007 VMware Inc.\n"); printf("Author: David Elliott\n"); printf("\n"); #if 0 if(isA20Enabled()) printf("A20 was enabled\n"); else printf("A20 was disabled\n"); #endif enableA20(); #if 0 if(isA20Enabled()) printf("A20 is now enabled\n"); else printf("A20 is now disabled\n"); #endif // WARNING: malloc uses memory just above 64 MB. If you don't have it.. you crash malloc_init(0, 0, 0, malloc_error); // A lot of code depends on bootArgs and perhaps more importantly bootInfo being valid initKernBootStruct(); printf("Booting from BIOS device 0x%x\n", biosdev); // FIXME: It may not at all be necessary to call scanBootVolumes BVRef bvChain = scanBootVolumes(biosdev, 0); int i=0; BVRef bvCurr = bvChain; for(; bvCurr != NULL; bvCurr = bvCurr->next) { ++i; char description[1024]; (bvCurr->description)(bvCurr, description, 1024); printf("Volume %2d: %s\n", i, description); } printf("Displayed %d potential volumes\n", i); if(bvChain->biosdev != biosdev) { stop("Selected boot volume is not the BIOS boot volume.\nDid you forget to write the CD as an HFS+ hybrid?\n"); } // Record the UUID of the first CD filesystem so we can double-check the user changed it. if(bvChain->fs_getuuid != NULL) (*bvChain->fs_getuuid)(bvChain, gDisc1FSUUIDStr); else bzero(gDisc1FSUUIDStr, 37); printf("Attempting to open second stage booter\n"); int bootf = open("/usr/standalone/i386/boot", 0); if(bootf < 0) stop("Unable to open second stage booter\n"); printf("Reading second stage booter into its final resting place\n"); // int bytesread = read(bootf, (void*)0x20200, 0x10000 - 0x200); int bytesread = read(bootf, (void*)0x20200, 0x20000); printf("Read %d bytes\n", bytesread); close(bootf); bootf = -1; if(bytesread <= 0) stop("Failed to read second stage booter.\n"); //------------------------------------------------------------------------------------ int initrdf = open("/usr/standalone/i386/initrd.img", 0); if(initrdf >= 0) { printf("Reading initrd.img into upper memory...\n"); // Read to PREBOOT_DATA + 4k, 16M (0x1000000) // FIXME: Read smaller chunks in a loop.. although the underlying read code // actually does this so the trick here is to simply peg the read size high enough // to be larger than any really useful initrd. bytesread = read(initrdf, (void*)(PREBOOT_DATA + 0x4000), 0x1000000); printf("Read %d bytes\n", bytesread); close(initrdf); struct preboot_header *pPrebootHeader = (void*)PREBOOT_DATA; bzero(pPrebootHeader, sizeof(*pPrebootHeader)); bcopy(PREBOOT_SIGNATURE, pPrebootHeader->signature, 8); pPrebootHeader->length = sizeof(*pPrebootHeader); pPrebootHeader->checksum = 0; pPrebootHeader->p_initrd = PREBOOT_DATA + 0x4000; // checksum value is two's complement of checksum8 result. pPrebootHeader->checksum = ~checksum8(pPrebootHeader, sizeof(*pPrebootHeader)) + 1; } else printf("No initrd found. Will boot unmodified system.\n"); //------------------------------------------------------------------------------------ struct driveInfo di; if(get_drive_info(biosdev, &di) < 0) stop("Failed to get drive info\n"); if(di.no_emulation) printf("BIOS device is in El Torito no-emulation mode\n", biosdev); el_torito_spec_packet cd_spec; int ret = elToritoGetStatus(biosdev, &cd_spec); if(ret != 0) stop("El Torito Get Status failed with error 0x%02X\n", ret); printf("Packet size %d\n", cd_spec.packet_size); printf("3K Buffer segment at 0x%04x\n", cd_spec.user_buffer_segment); for(;;) { printf("Ejecting Media\n"); ret = ebiosEjectMedia(biosdev); printf("Media ejected with status 0x%02x\n", ret); if(selectBootDevice(biosdev)) { if(biosdev != chainbootdev) { ret = elToritoTerminateDiskEmulation(biosdev, &cd_spec); printf("Terminated disk emulation with status 0x%02X\n", ret); // Give me a chance to hit F8 printf("If you want to set special boot options, hit F8 NOW\n"); sleep(1); break; } else if(shouldBootCD(biosdev)) break; } } } bool shouldBootCD(int biosdev) { struct DiskBVMap* oldMap = diskResetBootVolumes(biosdev); CacheReset(); // NOTE: No need to clear cached drive info, it doesn't change w/ ElTorito anyway #if 0 // NOTE: This could be problematic if the new map/bvref reuse the old // memory and cache.c doesn't realize they changed. diskFreeMap(oldMap); oldMap = NULL; #else (void)oldMap; #endif #if 0 struct driveInfo di; if(get_drive_info(biosdev, &di) < 0) stop("Failed to get drive info\n"); if(di.no_emulation) printf("BIOS device is (still) in El Torito no-emulation mode\n", biosdev); #endif BVRef newBV = scanBootVolumes(biosdev, NULL); if(newBV == NULL || newBV->biosdev != biosdev) { printf("Please insert a CD\n"); struct DiskBVMap* badMap = diskResetBootVolumes(biosdev); if(badMap != NULL) diskFreeMap(badMap); return false; } char newFSUUID[37]; bool uuidDidChange = false; if(newBV->fs_getuuid != NULL) { (*newBV->fs_getuuid)(newBV, newFSUUID); uuidDidChange = strcmp(gDisc1FSUUIDStr, newFSUUID) != 0; } if(!uuidDidChange) { printf("You should eject this CD and insert an OS X disc\n"); printf("Do you want to continue booting this CD (probably won't work)?"); int ch = bgetc(); // Don't loop.. just one chance to say yes. if(ch == 'y') uuidDidChange = true; printf("\n"); } return uuidDidChange; } // Returns TRUE if a device was selected, FALSE otherwise (e.g. eject CD-ROM again) // Fills in chainbootdev if it returns TRUE bool selectBootDevice(int biosdev) { printf("\nBoot Menu\n"); printf("c: Boot CD-ROM\n"); printf("h: Boot first Hard Disk\n"); printf("e: Eject CD\n"); printf("Your choice: "); int ch; for(;;) { ch = getc(); if(ch == 'c') chainbootdev = biosdev; else if(ch == 'h') chainbootdev = biosdev = 0x80; else if(ch == 'e') biosdev = -1; else continue; break; // Loop to get a different character } printf("%c\n", ch); return (biosdev >= 0); }