/*
 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (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.
 * 
 * This 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@
 */
/*
 *  drivers.c - Driver Loading Functions.
 *
 *  Copyright (c) 2000-2005 Apple Computer, Inc.
 *
 *  DRI: Josh de Cesare
 */

#include <sl.h>

#define DRIVER_DEBUG 0

#define kPropCFBundleIdentifier ("CFBundleIdentifier")
#define kPropCFBundleExecutable ("CFBundleExecutable")
#define kPropOSBundleRequired   ("OSBundleRequired")
#define kPropOSBundleLibraries  ("OSBundleLibraries")
#define kPropIOKitPersonalities ("IOKitPersonalities")
#define kPropIONameMatch        ("IONameMatch")

struct Module {  
  struct Module *nextModule;
  long          willLoad;
  TagPtr        dict;
  char          *plistAddr;
  long          plistLength;
  char          *driverPath;
};
typedef struct Module Module, *ModulePtr;

struct DriverInfo {
  char *plistAddr;
  long plistLength;
  void *moduleAddr;
  long moduleLength;
};
typedef struct DriverInfo DriverInfo, *DriverInfoPtr;

#define kDriverPackageSignature1 'MKXT'
#define kDriverPackageSignature2 'MOSX'

struct DriversPackage {
  unsigned long signature1;
  unsigned long signature2;
  unsigned long length;
  unsigned long adler32;
  unsigned long version;
  unsigned long numDrivers;
  unsigned long reserved1;
  unsigned long reserved2;
};
typedef struct DriversPackage DriversPackage;

enum {
  kCFBundleType2,
  kCFBundleType3
};

static long FileLoadDrivers(char *dirSpec, long plugin);
static long NetLoadDrivers(char *dirSpec);
static long LoadDriverMKext(char *fileSpec);
static long LoadDriverPList(char *dirSpec, char *name, long bundleType);
static long LoadMatchedModules(void);
static long MatchPersonalities(void);
static long MatchLibraries(void);
static ModulePtr FindModule(char *name);
static long XML2Module(char *buffer, ModulePtr *module, TagPtr *personalities);

static ModulePtr gModuleHead, gModuleTail;
static TagPtr    gPersonalityHead, gPersonalityTail;
static char      gDriverSpec[4096];
static char      gFileSpec[4096];
static char      gTempSpec[4096];
static char      gFileName[4096];

// Public Functions

long LoadDrivers(char *dirSpec)
{
  if (gBootFileType == kNetworkDeviceType) {
    if(NetLoadDrivers(dirSpec) < 0) return -1;
  } else if (gBootFileType == kBlockDeviceType) {
    FileLoadDrivers(dirSpec, 0);	// never returns errors
  } else {
    return 0;
  }
  
  MatchPersonalities();
  
  MatchLibraries();
  
  LoadMatchedModules();
  
  return 0;
}

// Private Functions

// XX FileLoadDrivers could use some more error checking
static long FileLoadDrivers(char *dirSpec, long plugin)
{
  long ret, length, index, flags, time, time2, bundleType;
  char *name;
  
  if (!plugin) {
    ret = GetFileInfo(dirSpec, "Extensions.mkext", &flags, &time);
    if ((ret == 0) && ((flags & kFileTypeMask) == kFileTypeFlat)) {
      ret = GetFileInfo(dirSpec, "Extensions", &flags, &time2);
      // use mkext if if it looks right or if the folder was bad
      if ((ret != 0) ||
	  ((flags & kFileTypeMask) != kFileTypeDirectory) ||
	  (((gBootMode & kBootModeSafe) == 0) && (time == (time2 + 1)))) {
	sprintf(gDriverSpec, "%sExtensions.mkext", dirSpec);
	printf("FileLoadDrivers: Loading from [%s]\n", gDriverSpec);
	if (LoadDriverMKext(gDriverSpec) == 0) return 0;
      } else if(time != (time2 + 1)) {
	  printf("mkext timestamp isn't quite right (delta: %d); ignoring...\n",
	      time2 - time);
	}
    }
    
    strcat(dirSpec, "Extensions");
  }
  
  printf("FileLoadDrivers: Loading from [%s]\n", dirSpec);
  
  index = 0;
  while (1) {
    ret = GetDirEntry(dirSpec, &index, &name, &flags, &time);
    if (ret == -1) break;

    // Make sure this is a directory.
    if ((flags & kFileTypeMask ) != kFileTypeDirectory) continue;
    
    // Make sure this is a kext.
    length = strlen(name);
    if (strcmp(name + length - 5, ".kext")) continue;
    
    // Save the file name.
    strcpy(gFileName, name);
    
    // Determine the bundle type.
    sprintf(gTempSpec, "%s\\%s", dirSpec, gFileName);
    ret = GetFileInfo(gTempSpec, "Contents", &flags, &time);
    if (ret == 0) bundleType = kCFBundleType2;
    else bundleType = kCFBundleType3;
    
    if (!plugin) {
      sprintf(gDriverSpec, "%s\\%s\\%sPlugIns", dirSpec, gFileName,
	      (bundleType == kCFBundleType2) ? "Contents\\" : "");
    }
    
    ret = LoadDriverPList(dirSpec, gFileName, bundleType);
    
    if (!plugin) {
      ret = FileLoadDrivers(gDriverSpec, 1);
    }
  }
  
  return 0;
}


static long NetLoadDrivers(char *dirSpec)
{
  long tries, cnt;
  
  // Get the name of the kernel
  cnt = strlen(gBootFile);
  while (cnt--) {
    if ((gBootFile[cnt] == '\\')  || (gBootFile[cnt] == ',')) {
      cnt++;
      break;
    }
  }
  
  sprintf(gDriverSpec, "%s%s.mkext", dirSpec, gBootFile + cnt);
  
  printf("NetLoadDrivers: Loading from [%s]\n", gDriverSpec);
  
  tries = 10;
  while (tries--) {
    if (LoadDriverMKext(gDriverSpec) == 0) break;
  }
  if (tries == -1) return -1;
  
  return 0;
}


static long LoadDriverMKext(char *fileSpec)
{
  unsigned long  driversAddr, driversLength, length;
  char           segName[32];
  DriversPackage *package;
  
  // Load the MKext.
  length = LoadThinFatFile(fileSpec, (void **)&package);
  if (length == -1) return -1;
  
  // Verify the MKext.
  if ((package->signature1 != kDriverPackageSignature1) ||
      (package->signature2 != kDriverPackageSignature2)) return -1;
  if (package->length > kMaxMKextSize) {
    printf("mkext segment too big (%ld bytes)\n", package->length);
    return -1;
  }
  if (package->adler32 != Adler32((char *)&package->version,
				  package->length - 0x10)) return -1;
  
  // Make space for the MKext.
  driversLength = package->length;
  driversAddr = AllocateKernelMemory(driversLength);
  
  // Copy the MKext.
  memcpy((void *)driversAddr, (void *)package, driversLength);
  
  // Add the MKext to the memory map.
  sprintf(segName, "DriversPackage-%x", driversAddr);
  AllocateMemoryRange(segName, driversAddr, driversLength);
  
  return 0;
}


static long LoadDriverPList(char *dirSpec, char *name, long bundleType)
{
  long      length, ret, driverPathLength;
  char      *buffer;
  ModulePtr module;
  TagPtr    personalities;
  char      *tmpDriverPath;
  
  // Reset the malloc zone.
  malloc_init((char *)kMallocAddr, kMallocSize);
  
  // Save the driver path.
  sprintf(gFileSpec, "%s\\%s\\%s", dirSpec, name,
	  (bundleType == kCFBundleType2) ? "Contents\\MacOS\\" : "");
  driverPathLength = strlen(gFileSpec);
  tmpDriverPath = malloc(driverPathLength + 1);
  if (tmpDriverPath == 0) return -1;
  strcpy(tmpDriverPath, gFileSpec);
  
  // Construct the file spec.
  sprintf(gFileSpec, "%s\\%s\\%sInfo.plist", dirSpec, name,
	  (bundleType == kCFBundleType2) ? "Contents\\" : "");
  
  length = LoadFile(gFileSpec);
  *((char*)kLoadAddr + length) = '\0';  // terminate for parser safety
  if (length == -1) {
    free(tmpDriverPath);
    return -1;
  }
  
  buffer = malloc(length + 1);
  if (buffer == 0) {
    free(tmpDriverPath);
    return -1;
  }
  strncpy(buffer, (char *)kLoadAddr, length);
  
  ret = XML2Module(buffer, &module, &personalities);
  free(buffer);
  if (ret != 0) {
    // could trap ret == -2 and report missing OSBundleRequired
    free(tmpDriverPath);
    return -1;
  }
  
  // Allocate memory for the driver path and the plist.
  module->driverPath = AllocateBootXMemory(driverPathLength + 1);
  module->plistAddr = AllocateBootXMemory(length + 1);
  
  if ((module->driverPath == 0) | (module->plistAddr == 0)) {
    free(tmpDriverPath);
    return -1;
  }
  
  // Save the driver path in the module.
  strcpy(module->driverPath, tmpDriverPath);
  free(tmpDriverPath);
  
  // Add the origin plist to the module.
  strncpy(module->plistAddr, (char *)kLoadAddr, length);
  module->plistLength = length + 1;
  
  // Add the module to the end of the module list.
  if (gModuleHead == 0) gModuleHead = module;
  else gModuleTail->nextModule = module;
  gModuleTail = module;
  
  // Add the extracted personalities to the list.
  if (personalities) personalities = personalities->tag;
  while (personalities != 0) {
    if (gPersonalityHead == 0) gPersonalityHead = personalities->tag;
    else gPersonalityTail->tagNext = personalities->tag;
    gPersonalityTail = personalities->tag;
    
    personalities = personalities->tagNext;
  }
  
  return 0;
}


static long LoadMatchedModules(void)
{
  TagPtr        prop;
  ModulePtr     module;
  char          *fileName, segName[32];
  DriverInfoPtr driver;
  unsigned long length, driverAddr, driverLength;
  void          *driverModuleAddr;
  
  module = gModuleHead;
  while (module != 0) {
    if (module->willLoad) {
      prop = GetProperty(module->dict, kPropCFBundleExecutable);
      if (prop != 0) {
	fileName = prop->string;
	sprintf(gFileSpec, "%s%s", module->driverPath, fileName);
	length = LoadThinFatFile(gFileSpec, &driverModuleAddr);
      } else length = 0;
      if (length != -1) {
	// Make make in the image area.
	driverLength = sizeof(DriverInfo) + module->plistLength + length;
	driverAddr = AllocateKernelMemory(driverLength);
	
	// Set up the DriverInfo.
	driver = (DriverInfoPtr)driverAddr;
	driver->plistAddr = (char *)(driverAddr + sizeof(DriverInfo));
	driver->plistLength = module->plistLength;
	if (length != 0) {
	  driver->moduleAddr   = (void *)(driverAddr + sizeof(DriverInfo) +
					module->plistLength);
	  driver->moduleLength = length;
	} else {
	  driver->moduleAddr   = 0;
	  driver->moduleLength = 0;
	}
	
	// Save the plist and module.
	strcpy(driver->plistAddr, module->plistAddr);
	if (length != 0) {
	  memcpy(driver->moduleAddr, driverModuleAddr, driver->moduleLength);
	}
	
	// Add an entry to the memory map.
	sprintf(segName, "Driver-%x", driver);
	AllocateMemoryRange(segName, driverAddr, driverLength);
      }
    }
    
    module = module->nextModule;
  }
  
  return 0;
}


static long MatchPersonalities(void)
{
  TagPtr    persionality;
  TagPtr    prop;
  ModulePtr module;
  CICell    ph;
  
  // Try to match each of the personalities.
  for(persionality = gPersonalityHead; persionality != 0;
	persionality = persionality->tagNext) {
    // Get the module name. Make sure it exists and has not
    // already been marked for loading.
    prop = GetProperty(persionality, kPropCFBundleIdentifier);
    if (prop == 0) continue;
    module = FindModule(prop->string);
    if (module == 0) continue;
    if (module->willLoad) continue;
    
    // Look for the exact match property.
    // Try to match with it.
    
    // Look for the old match property.
    // Try to match with it.
    ph = 0;
    prop = GetProperty(persionality, kPropIONameMatch);
    if ((prop != 0) && (prop->tag != 0)) prop = prop->tag;
    while (prop != 0) {
      ph = SearchForNodeMatching(0, 1, prop->string);
      if (ph != 0) break;
      
      prop = prop->tagNext;
    }
    
    // If a node was found mark the module to be loaded.
    if (ph != 0) {
      module->willLoad = 1;
    }
  }
  
  return 0;
}


static long MatchLibraries(void)
{
  TagPtr        prop, prop2;
  ModulePtr     module, module2;
  long          done;
  
  do {
    done = 1;
    module = gModuleHead;
    while (module != 0) {
      if (module->willLoad == 1) {
	prop = GetProperty(module->dict, kPropOSBundleLibraries);
	if (prop != 0) {
	  prop = prop->tag;
	  while (prop != 0) {
	    module2 = gModuleHead;
	    while (module2 != 0) {
	      prop2 = GetProperty(module2->dict, kPropCFBundleIdentifier);
	      if ((prop2 != 0) && (!strcmp(prop->string, prop2->string))) {
		if (module2->willLoad == 0) module2->willLoad = 1;
		break;
	      }
	      module2 = module2->nextModule;
	    }
	    prop = prop->tagNext;
	  }
	}
	module->willLoad = 2;
	done = 0;
      }
      module = module->nextModule;
    }
  } while (!done);
  
  return 0;
}


static ModulePtr FindModule(char *name)
{
  ModulePtr module;
  TagPtr    prop;
  
  module = gModuleHead;
  
  while (module != 0) {
    prop = GetProperty(module->dict, kPropCFBundleIdentifier);
    if ((prop != 0) && !strcmp(name, prop->string)) break;
    module = module->nextModule;
  }
  
  return module;
}

/* turn buffer of XML into a ModulePtr for driver analysis */
static long XML2Module(char *buffer, ModulePtr *module, TagPtr *personalities)
{
  TagPtr         moduleDict = NULL, required;
  ModulePtr      tmpModule;

  if(ParseXML(buffer, &moduleDict) < 0)
    return -1;

  required = GetProperty(moduleDict, kPropOSBundleRequired);
  if ((required == 0) || (required->type != kTagTypeString) ||
      !strcmp(required->string, "Safe Boot")) {
    FreeTag(moduleDict);
    return -2;
  }
  
  tmpModule = AllocateBootXMemory(sizeof(Module));
  if (tmpModule == 0) {
    FreeTag(moduleDict);
    return -1;
  }
  tmpModule->dict = moduleDict;
  
  // For now, load any module that has OSBundleRequired != "Safe Boot".
  tmpModule->willLoad = 1;
  
  *module = tmpModule;
  
  // Get the personalities.
  *personalities = GetProperty(moduleDict, kPropIOKitPersonalities);
  
  return 0;
}
