/*  Program to print out the value reported by the onboard temperature sensor in
 *  PowerMac G4 (FW800) systems, and the associated fan speed.
 *
 *  The IOKitGetPropertyAtGivenPathCopy function is taken from an e-mail message
 *  to the darwin-development mailing list from Chad Jones (chadj@mail.apple.com)
 *  on September 5, 2001. The message is available here:
 *  http://lists.apple.com/archives/darwin-development/2001/Sep/05/ioregistryharddiskhardwa.001.txt
 *  (user: archives, password: archives)
 *
 *  Information on the Dallas Semiconductor 1775 part was obtained from the
 *  publicly available specifications sheet available on the web. All knowledge
 *  of the workings of this system is based on speculation on my part, not on
 *  any information from Apple. To my knowledge the AppleCPUThermo and AppleFan
 *  kernel extensions are closed-source Apple proprietary, so I have no knowledge
 *  of their inner workings, only what I could gleam from information available
 *  in the IO Registry.
 *  
 *  Author: Eric Carlson, carl0240@tc.umn.edu
 *  
 *  This software is supplied 'as is', with no warranty. 
 *  You are free to use and/or redistribute this source, modified or unmodified,
 *  in any form.
 */

// build using:
// gcc -o temp temp.c -O3 -Wall -g -framework IOKit -framework CoreFoundation

#include <mach/mach.h>
#include <mach/mach_error.h>
#include <IOKit/IOKitLib.h>
#include <CoreFoundation/CoreFoundation.h>

CFDataRef IOKitGetPropertyAtGivenPathCopy(char* PathToLookUp, char* PropertyToLookup)
{
     mach_port_t MasterPort;
     kern_return_t status;
     io_registry_entry_t IOKitEntry;
     CFStringRef PropertyToLookupCFString;
     CFMutableDictionaryRef DictionaryOfIOKitEntries;
     CFDataRef DataToReturn;

     if ((PropertyToLookup == NULL) || (PathToLookUp == NULL))
     {
         return(NULL); //we fail if any of the passed in values are null.
     }
    
     //Below we are getting the master port used to communicate with IOKit. 
     //first argument: Mach_Port_NULL indicating that we want to get the master port.
     //Second Argument: The variable where the master port will be stored in.
     //status must be kern_success or we must fail.  We don't need to release the result.
     status = IOMasterPort(MACH_PORT_NULL,&MasterPort);

     if (status != KERN_SUCCESS) //if we get error here then we fail with null.
     {
         return(NULL);
     }

     //Now need to get the IORegistry entry we are going to lookup.  This is very like getting the directory of the file
     //we want to lookup in Finder. 
     //First Argument: The master port we need to lookup from. 
     //Second Argument: The IOKit path to the entry we are interested in.
     //Note the result must be released with IOObjectRelease later

     IOKitEntry = IORegistryEntryFromPath(MasterPort,PathToLookUp);

     //if we get an error back then give up
     if (IOKitEntry == MACH_PORT_NULL)
     {
         return(NULL);
     }

     //Now we need to get a list of all the keys which are available from that entry.  This is equivelent of doing 'ls' in terminal which 
     //lists all the entries at the current level.  The DictionaryOfIOKitEntries contains the result which we are interested 
     //in.  We then pull the information out of the dictionary next.
     //First Argument: The IOKit entry we are interested in.  This is 
     //very much like the directory in a file system we are interested int.
     //Second Argument: The CFDictioanry where the results will be stored after this call is made.
     //Third Argument: NULL since we want default CFAllocator (as usual)
     //Forth Argument: NULL since we don't want any special options in the lookup/conversion.

     status = IORegistryEntryCreateCFProperties(IOKitEntry,&DictionaryOfIOKitEntries,NULL,NULL);
     IOObjectRelease(IOKitEntry); //We are now done with IOKitEntry so release it.
    
     if (status != KERN_SUCCESS) //if we get error here then we fail with null.
     {
         return(NULL);
     }

     //Now need to convert the character string into a CFString for use in dictionary lookup.
    
     PropertyToLookupCFString = CFStringCreateWithCString(NULL, PropertyToLookup, kCFStringEncodingASCII);
    
     if (PropertyToLookupCFString == NULL)
     {
         return(NULL);  //if we don't have a property to lookup then we fail.
     }

     //Now pull the property we are interested in out of the Dictioary. 
     DataToReturn = CFDictionaryGetValue(DictionaryOfIOKitEntries,PropertyToLookupCFString);
    
     CFRetain(DataToReturn); //need the value for later so retain now
     CFRelease(DictionaryOfIOKitEntries);  //done with dictionary so release.
     CFRelease(PropertyToLookupCFString); //done with CFString

     return(DataToReturn);
}

int main(int argc, char* argv[])
{
  //the path to the node with the temperature info as provided by the AppleCPUThermo kernel extension
  char *ioRegPathTemp = "IOService:/MacRISC2PE/uni-n/AppleUniN/i2c/PPCI2CInterface/temp-monitor/AppleCPUThermo";
  char *propertyNameTemp = "temperature";
  
  //the path to the node with the fan speed table as provided by the AppleFan kernel extension
  char *ioRegPathFan = "IOService:/MacRISC2PE/uni-n/AppleUniN/i2c/PPCI2CInterface/fan/AppleFan";
  char *propertyNameFan = "default-params";

  int tempNum;
  CFIndex i;
  SInt64 fanVal;
  float temp;

  CFNumberRef tempCFNum = (CFNumberRef) IOKitGetPropertyAtGivenPathCopy(ioRegPathTemp, propertyNameTemp);
  CFDictionaryRef fanCFDict = (CFDictionaryRef) IOKitGetPropertyAtGivenPathCopy(ioRegPathFan, propertyNameFan);
  CFArrayRef fanSpeedTempArray;

  if(tempCFNum == NULL || fanCFDict == NULL) {
    return 1;
  }

  //extract the array of temperature values from the CFDictionary we got out of the IO registry
  fanSpeedTempArray = (CFArrayRef) CFDictionaryGetValue(fanCFDict, (const void*)CFStringCreateWithCString(NULL, "fan-speed-table", kCFStringEncodingASCII));

  //get the temperature number (later converted to degrees C)
  if(!CFNumberGetValue(tempCFNum, kCFNumberIntType, (void*) &tempNum)) {
    return 1;
  }

  //loop through the temperature/fan speed table to find the current fan speed based on the temp we got
  i = CFArrayGetCount(fanSpeedTempArray);
  do {
    i--;
    CFNumberGetValue(CFArrayGetValueAtIndex(fanSpeedTempArray, i), kCFNumberSInt64Type, (void*) &fanVal);
  } while( ( fanVal > tempNum) && (i > 0) );

  //the value stored in the IO registry appears to be the raw value returned from the temperature sensor,
  // a Dallas Semiconductor 1775 (ds1775) part. According to spec, the data is returned in two's complement form,
  // with apparently 12 of the 16 bit positions used (the default is 12 for the chip, and this seems to be what it is set to).
  //
  // So, to convert to degrees C, divide by 2^4 and multiply by 0.0625 (the resolution)

  temp = (tempNum / 16.0) * 0.0625; //convert temp number to degrees C
  printf("%f\n", (temp*(9.0/5.0))+32.0); //output in degrees F
  printf("%d\n",(int)i); //print fan value

  return 0;
}

