/* * Copyright (c) 1999-2003 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights * Reserved. This file contains Original Code and/or Modifications of * Original Code as defined in and that are subject to the Apple Public * Source License Version 2.0 (the "License"). You may not use this file * except in compliance with the License. Please obtain a copy of the * License at http://www.apple.com/publicsource and read it before using * this file. * * The Original Code and all software distributed under the License are * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Mach Operating System * Copyright (c) 1990 Carnegie-Mellon University * Copyright (c) 1989 Carnegie-Mellon University * All rights reserved. The CMU software License Agreement specifies * the terms and conditions for use and redistribution. */ /* * INTEL CORPORATION PROPRIETARY INFORMATION * * This software is supplied under the terms of a license agreement or * nondisclosure agreement with Intel Corporation and may not be copied * nor disclosed except in accordance with the terms of that agreement. * * Copyright 1988, 1989 by Intel Corporation */ /* * Copyright 1993 NeXT Computer, Inc. * All rights reserved. */ /* * Completely reworked by Sam Streeper (sam_s@NeXT.com) * Reworked again by Curtis Galloway (galloway@NeXT.com) */ #include "boot.h" #include "bootstruct.h" #include "fake_efi.h" #include "sl.h" #include "libsa.h" #include "preboot.h" #include "pxe.h" long gBootMode; /* defaults to 0 == kBootModeNormal */ cpu_type_t gDesiredCpuType = CPU_TYPE_ANY; BOOL gOverrideKernel; static char gBootKernelCacheFile[512]; static char gCacheNameAdler[64 + 256]; char *gPlatformName = gCacheNameAdler; char gRootDevice[512]; char gMKextName[512]; BVRef gBootVolume; static void selectBiosDevice(void); static unsigned long Adler32(unsigned char *buffer, long length); static BOOL gUnloadPXEOnExit = 0; /* * How long to wait (in seconds) to load the * kernel after displaying the "boot:" prompt. */ #define kBootErrorTimeout 5 /* * Default path to kernel cache file */ #define kDefaultCachePath "/System/Library/Caches/com.apple.kernelcaches/kernelcache" //========================================================================== // Zero the BSS. static void zeroBSS() { extern struct {} _DATA__bss__begin ASM_MACHO_SECTION_BEGIN(__DATA,__bss), _DATA__bss__end ASM_MACHO_SECTION_END(__DATA,__bss); extern struct {} _DATA__common__begin ASM_MACHO_SECTION_BEGIN(__DATA,__common), _DATA__common__end ASM_MACHO_SECTION_END(__DATA,__common); bzero( &_DATA__bss__begin, ((char *)&_DATA__bss__end - (char *)&_DATA__bss__begin) ); bzero( &_DATA__common__begin, ((char *)&_DATA__common__end - (char *)&_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"); } /*! Initializes the runtime. Right now this means zeroing the BSS and initializing malloc. */ void initialize_runtime() { zeroBSS(); // Initialize malloc malloc_init(0, 0, 0, malloc_error); // Set up the IDT (right now it only handles #GP for rdmsr_safe) initIdt(); } /*! @abstract Registers saved at real-mode entrypoint (boot2.s) @discussion You must keep this in synch with the asm. */ struct entry_saved_regs { uint16_t es; uint16_t ds; uint16_t di; uint16_t si; uint16_t bp; uint16_t bx; uint16_t cx; } __attribute__((packed)); extern uint32_t save_ss asm("save_ss"); extern uint32_t save_sp asm("save_sp"); /*! Uses the registers saved on the stack at entrypoint time to fill in assumed pointers to the '!PXE' and 'PXENV+' structures. */ bool initPxeBoot() { // Bottom of stack at call to _switch_stack which saves SS and SP struct entry_saved_regs *regs = (void *)ADDR32(save_ss & 0xffff, save_sp & 0xffff); gPXE_real_entry_ebp = regs->bp; return initPxeFromBootstrap( (void*)ADDR32( (uint32_t)*(uint16_t*)ADDR32(save_ss & 0xffff, regs->bp + 8), (uint32_t)*(uint16_t*)ADDR32(save_ss & 0xffff, regs->bp + 6) ), (void*)ADDR32((uint32_t)regs->es, regs->bx) ); } /*! If a PXE "DHCP ACK" packet exists, copy it into /chosen dhcp-response. */ void add_dhcp_packet_to_device_tree() { // If we booted PXE, stuff the DHCP REPLY packet into the device tree. // If we don't do this it still works because the kernel will run its own DHCP client // but the IP address it receives might be different. if(gDhcpAck != NULL && gDhcpAckSize != 0) { Node *node = DT__FindNode("/chosen", true); if(node != NULL) { DT__AddProperty(node, "dhcp-response", gDhcpAckSize, gDhcpAck); verbose("Added dhcp-response\n"); } } } //========================================================================== // execKernel - Load the kernel image (mach-o) and jump to its entry point. static int ExecKernel(void *binary) { entry_t kernelEntry; int ret; BOOL bootGraphics; bootArgs->kaddr = bootArgs->ksize = 0; ret = DecodeKernel(binary, &kernelEntry, (char **) &bootArgs->kaddr, (int *)&bootArgs->ksize ); if ( ret != 0 ) return ret; // Reserve space for boot args reserveKernBootStruct(); // Load boot drivers from the specifed root path. // gHaveKernelCache will be TRUE if the mach_kernel has a __PRELINK segment with // non-zero vmsize. All mach_kernel I've seen have zero vmsize. if (!gHaveKernelCache) { LoadDrivers("/"); } clearActivityIndicator(); if (gErrors) { printf("Errors encountered while starting up the computer.\n"); printf("Pausing %d seconds...\n", kBootErrorTimeout); sleep(kBootErrorTimeout); } add_dhcp_packet_to_device_tree(); setupFakeEfi(); printf("Starting Darwin/x86"); // Cleanup the PXE base code. if ( (gBootFileType == kNetworkDeviceType) && gUnloadPXEOnExit ) { if ( (ret = nbpUnloadBaseCode()) != nbpStatusSuccess ) { printf("nbpUnloadBaseCode error %d\n", (int) ret); sleep(2); } } // Unless Boot Graphics = No, Always switch to graphics mode // just before starting the kernel. if (!getBoolForKey(kBootGraphicsKey, &bootGraphics)) { bootGraphics = YES; } if (bootGraphics) { if (bootArgs->Video.v_display == VGA_TEXT_MODE) { // If we were in text mode, switch to graphics mode. // This will draw the boot graphics unless we are in // verbose mode. setVideoMode( GRAPHICS_MODE ); } else { // If we were already in graphics mode, clear the screen. drawBootGraphics(); } } else { // Always set text mode to initialize video fields // in the boot args structure. setVideoMode( VGA_TEXT_MODE ); setCursorType( kCursorTypeHidden ); } finalizeBootStruct(); // Newer xnu enable interrupts (sti) before initializing the IOAPIC and // it does not seem like there is any code at all for handling the 8259A. // This manifests as a double fault, fortunately one that occurs after // the console has been set up and is thus printed (if -v is used). // // Since it is virtually guaranteed that some 8259A interrupt is pending // the typical failure address is in kernel_bootstrap_thread immediately // after the sti instruction (spllo macro invocation). // // However when using the VMware GDB stub you can actually instruction // step (stepi) as much as you want but as soon as you do something // which causes VMware to run normally again it will check for (virtual) // hardware interrupts and cause the double fault. In that case the // failure address would be whatever instruction you were about to // execute when you did a continue, finish, advance, etc. __asm__( "outb %0, $0xa1\n\t" // Mask all interrupts on cascaded (AT) 8259A "outb %0, $0x21\n\t" // Mask all interrupts on primary (XT) 8259A : // no output : "a"((unsigned char)0xff) // Input in %al must be 0xff : // Clobbers nothing ); // Jump to kernel's entry point. There's no going back now. startprog( kernelEntry, bootArgs ); // Not reached return 0; } //========================================================================== // Scan and record the system's hardware information. static void scanHardware() { extern int ReadPCIBusInfo(PCI_bus_info_t *); extern void PCI_Bus_Init(PCI_bus_info_t *); ReadPCIBusInfo( &bootInfo->pciInfo ); // // Initialize PCI matching support in the booter. // Not used, commented out to minimize code size. // // PCI_Bus_Init( &bootInfo->pciInfo ); } /*! This is the entrypoint from real-mode which functions exactly as it did before. Multiboot does its own runtime initialization, does some of its own things, and then calls common_boot. */ void boot(int biosdev) { initialize_runtime(); // Enable A20 gate before accessing memory above 1Mb. enableA20(); bool isPxeBoot = initPxeBoot(); return common_boot(biosdev, isPxeBoot); } //========================================================================== // The 'main' function for the booter. Called by boot0 when booting // from a block device, or by the network booter. // // arguments: // biosdev - Value passed from boot1/NBP to specify the device // that the booter was loaded from. // // If biosdev is kBIOSDevNetwork, then this function will return if // booting was unsuccessful. This allows the PXE firmware to try the // next boot device on its list. #define DLOG(x) outb(0x80, (x)) void common_boot(int biosdev, bool isPxeBoot) { int status; char *bootFile; unsigned long adler32; BOOL quiet; BOOL firstRun = YES; BVRef bvChain; // Set reminder to unload the PXE base code. Neglect to unload // the base code will result in a hang or kernel panic. gUnloadPXEOnExit = 1; // Record the device that the booter was loaded from. gBIOSDev = biosdev & kBIOSDevMask; // Initialize boot info structure. initKernBootStruct(); // Setup VGA text mode. // Not sure if it is safe to call setVideoMode() before the // config table has been loaded. Call video_mode() instead. #if DEBUG printf("before video_mode\n"); #endif video_mode( 2 ); // 80x25 mono text mode. #if DEBUG printf("after video_mode\n"); #endif // Check to see that this hardware is supported. status = checkForSupportedHardware(); if (status != 0) { printf("This hardware configuration is not supported by Darwin/x86. (%d)", status); } // Scan hardware configuration. scanHardware(); // First get info for boot volume. if(isPxeBoot) // FIXME: 0xE0 could theoretically be a valid device type. bvChain = scanBootVolumes(kBIOSDevTypeNetwork, 0); else bvChain = NULL; if(bvChain == NULL) bvChain = scanBootVolumes(gBIOSDev, 0); if(bvChain != NULL) gBIOSDev = bvChain->biosdev; selectPrebootVolume(scanPrebootVolumes()); // Record default boot device. gBootVolume = selectBootVolume(bvChain); setRootVolume(gBootVolume); // Load default config file from boot device. status = loadSystemConfig(0, 0); // If the selected boot volume was a system partition (i.e. ESP) and it had // no config file then ignore it and try again. if( (gBootVolume->flags & kBVFlagSystemPartition) && status < 0 ) { // Make sure the volume will not be considered for boot. gBootVolume->flags &= ~(kBVFlagNativeBoot | kBVFlagPrimary); // Try again to find a boot volume using the normal search mechanism // but having excluded the ESP itself from contention. gBootVolume = selectBootVolume(bvChain); setRootVolume(gBootVolume); status = loadSystemConfig(0, 0); } if ( getBoolForKey( kQuietBootKey, &quiet ) && quiet ) { gBootMode |= kBootModeQuiet; } // Parse args, load and start kernel. while (1) { const char *val; int len; int trycache; long flags, cachetime, kerneltime, exttime; int ret = -1; void *binary = (void *)kLoadAddr; // Initialize globals. sysConfigValid = 0; gErrors = 0; status = getBootOptions(firstRun); firstRun = NO; if (status == -1) continue; status = processBootOptions(); // Status==1 means to chainboot if ( status == 1 ) break; // Status==-1 means that the config file couldn't be loaded or that gBootVolume is NULL if ( status == -1 ) { // gBootVolume == NULL usually means the user hit escape. if(gBootVolume == NULL) selectBiosDevice(); continue; } // Other status (e.g. 0) means that we should proceed with boot. // Found and loaded a config file. Proceed with boot. // Reset cache name. bzero(gCacheNameAdler + 64, sizeof(gCacheNameAdler) - 64); sprintf(gCacheNameAdler + 64, "%s,%s", gRootDevice, bootInfo->bootFile); adler32 = Adler32((unsigned char *)gCacheNameAdler, sizeof(gCacheNameAdler)); if (getValueForKey(kKernelCacheKey, &val, &len) == YES) { strlcpy(gBootKernelCacheFile, val, len+1); } else { sprintf(gBootKernelCacheFile, "%s.%08lX", kDefaultCachePath, adler32); } // Check for cache file. trycache = (((gBootMode & kBootModeSafe) == 0) && (gOverrideKernel == NO) && (gBootFileType == kBlockDeviceType) && (gMKextName[0] == '\0') && (gBootKernelCacheFile[0] != '\0')); printf("Loading Darwin/x86\n"); if (trycache) do { // if we haven't found the kernel yet, don't use the cache ret = GetFileInfo(NULL, bootInfo->bootFile, &flags, &kerneltime); if ((ret != 0) || ((flags & kFileTypeMask) != kFileTypeFlat)) { trycache = 0; break; } ret = GetFileInfo(NULL, gBootKernelCacheFile, &flags, &cachetime); if ((ret != 0) || ((flags & kFileTypeMask) != kFileTypeFlat) || (cachetime < kerneltime)) { trycache = 0; break; } ret = GetFileInfo("/System/Library/", "Extensions", &flags, &exttime); if ((ret == 0) && ((flags & kFileTypeMask) == kFileTypeDirectory) && (cachetime < exttime)) { trycache = 0; break; } if (kerneltime > exttime) { exttime = kerneltime; } if (cachetime != (exttime + 1)) { trycache = 0; break; } } while (0); do { // In case we fail to load, reset the selected CPU type before trying the kernel instead of the cache. selectCpuType(gDesiredCpuType); if (trycache) { bootFile = gBootKernelCacheFile; verbose("Loading kernel cache %s\n", bootFile); ret = LoadFile(bootFile); binary = (void *)kLoadAddr; if (ret >= 0) { break; } selectCpuType(gDesiredCpuType); } bootFile = bootInfo->bootFile; verbose("Loading kernel %s\n", bootFile); ret = LoadThinFatFile(bootFile, &binary); } while (0); clearActivityIndicator(); #if DEBUG printf("Pausing..."); sleep(8); #endif if (ret < 0) { error("Can't find %s\n", bootFile); if ( gBootFileType == kNetworkDeviceType ) { // Return control back to PXE. Don't unload PXE base code. gUnloadPXEOnExit = 0; break; } } else { /* Won't return if successful. */ ret = ExecKernel(binary); } } /* while(1) */ if ((gBootFileType == kNetworkDeviceType) && gUnloadPXEOnExit) { nbpUnloadBaseCode(); } } /*! Selects a new BIOS device, taking care to update the global state appropriately. */ static void selectBiosDevice(void) { struct DiskBVMap *oldMap = diskResetBootVolumes(gBIOSDev); CacheReset(); diskFreeMap(oldMap); oldMap = NULL; int dev = selectAlternateBootDevice(gBIOSDev); BVRef bvchain = scanBootVolumes(dev, 0); BVRef bootVol = selectBootVolume(bvchain); gBootVolume = bootVol; setRootVolume(bootVol); gBIOSDev = dev; } #define BASE 65521L /* largest prime smaller than 65536 */ #define NMAX 5000 // NMAX (was 5521) the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 #define DO1(buf,i) {s1 += buf[i]; s2 += s1;} #define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); #define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); #define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); #define DO16(buf) DO8(buf,0); DO8(buf,8); unsigned long Adler32(unsigned char *buf, long len) { unsigned long s1 = 1; // adler & 0xffff; unsigned long s2 = 0; // (adler >> 16) & 0xffff; unsigned long result; int k; while (len > 0) { k = len < NMAX ? len : NMAX; len -= k; while (k >= 16) { DO16(buf); buf += 16; k -= 16; } if (k != 0) do { s1 += *buf++; s2 += s1; } while (--k); s1 %= BASE; s2 %= BASE; } result = (s2 << 16) | s1; return OSSwapHostToBigInt32(result); }