/* * Copyright (c) 2002-2003 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@ */ /* * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. * * */ // $Log: PowerMac7_2_CPUFanCtrlLoop.cpp,v $ // Revision 1.13 2003/08/01 00:42:34 wgulland // Merging in branch PR-3338565 // // Revision 1.12.2.1 2003/07/31 17:53:10 eem // 3338565 - q37 intake fan speed is 97% of exhaust fan speed, but still gets // clipped at the same min/max. This prevents the intake fan speed from falling // below 300 RPM and being turned off by the FCU. Version bumped to 1.0.1b1. // // Revision 1.12 2003/07/24 21:47:18 eem // [3338565] Q37 Final Fan data // // Revision 1.11 2003/07/20 23:41:11 eem // [3273577] Q37: Systems need to run at Full Speed during test // // Revision 1.10 2003/07/18 00:22:24 eem // [3329244] PCI fan conrol algorithm should use integral of power consumed // [3254911] Q37 Platform Plugin must disable debugging accessors before GM // // Revision 1.9 2003/07/17 06:57:39 eem // 3329222 and other sleep stability issues fixed. // // Revision 1.8 2003/07/16 02:02:10 eem // 3288772, 3321372, 3328661 // // Revision 1.7 2003/07/08 04:32:51 eem // 3288891, 3279902, 3291553, 3154014 // // Revision 1.6 2003/06/26 17:16:20 eem // Fixed a retain count leak in PowerMac7_2_CPUFanCtrlLoop that was bracketed // by CTRLLOOP_DLOG. // // Revision 1.5 2003/06/25 02:16:25 eem // Merged 101.0.21 to TOT, fixed PM72 lproj, included new fan settings, bumped // version to 101.0.22. // // Revision 1.4.4.6 2003/06/21 02:02:25 eem // Fix massive bone-up. // // Revision 1.4.4.5 2003/06/21 01:42:08 eem // Final Fan Tweaks. // // Revision 1.4.4.4 2003/06/20 09:07:37 eem // Added rising/falling slew limiters, integral clipping, etc. // // Revision 1.4.4.3 2003/06/20 01:40:00 eem // Although commented out in this submision, there is support here to nap // the processors if the fans are at min, with the intent of keeping the // heat sinks up to temperature. // // Revision 1.4.4.2 2003/06/19 10:24:20 eem // Pulled common PID code into IOPlatformPIDCtrlLoop and subclassed it with // PowerMac7_2_CPUFanCtrlLoop and PowerMac7_2_PIDCtrlLoop. Added history // length to meta-state. No longer adjust T_err when the setpoint changes. // Don't crank the CPU fans for overtemp, just slew slow. // // Revision 1.4.4.1 2003/06/18 20:40:12 eem // Added variable sample history length, removed cpu fan response to // internal overtemp, made proportional gain only apply to proportional // term. // // Revision 1.4 2003/06/07 01:30:58 eem // Merge of EEM-PM72-ActiveFans-2 branch, with a few extra tweaks. This // checkin has working PID control for PowerMac7,2 platforms, as well as // a first shot at localized strings. // // Revision 1.3.2.8 2003/06/06 11:00:45 eem // Drive Bay and U3 zones now also under PID control. // // Revision 1.3.2.7 2003/06/06 08:17:58 eem // Holy Motherfucking shit. PID is really working. // // Revision 1.3.2.6 2003/06/01 14:52:55 eem // Most of the PID algorithm is implemented. // // Revision 1.3.2.5 2003/05/31 08:11:38 eem // Initial pass at integrating deadline-based timer callbacks for PID loops. // // Revision 1.3.2.4 2003/05/29 03:51:36 eem // Clean up environment dictionary access. // // Revision 1.3.2.3 2003/05/26 10:07:17 eem // Fixed most of the bugs after the last cleanup/reorg. // // Revision 1.3.2.2 2003/05/23 06:36:59 eem // More registration notification stuff. // // Revision 1.3.2.1 2003/05/22 01:31:05 eem // Checkin of today's work (fails compilations right now). // // Revision 1.3 2003/05/21 21:58:55 eem // Merge from EEM-PM72-ActiveFans-1 branch with initial crack at active fan // control on Q37. // // Revision 1.2.4.2 2003/05/17 11:08:25 eem // All active fan data present, table event-driven. PCI power sensors are // not working yet so PCI fan is just set to 67% PWM and forgotten about. // // Revision 1.2.4.1 2003/05/17 02:55:05 eem // Intermediate commit with cpu fan loop states/outputs included. This // probably won't compile right now. // // Revision 1.2 2003/05/10 06:50:36 eem // All sensor functionality included for PowerMac7_2_PlatformPlugin. Version // is 1.0.1d12. // // Revision 1.1.2.1 2003/05/01 09:28:47 eem // Initial check-in in progress toward first Q37 checkpoint. // // #include "IOPlatformPluginDefs.h" #include "IOPlatformPluginSymbols.h" #include "PowerMac7_2_PlatformPlugin.h" #include "IOPlatformSensor.h" #include "PowerMac7_2_CPUFanCtrlLoop.h" #define super IOPlatformPIDCtrlLoop OSDefineMetaClassAndStructors(PowerMac7_2_CPUFanCtrlLoop, IOPlatformPIDCtrlLoop) //extern const OSSymbol * gPM72EnvShroudRemoved; //extern const OSSymbol * gPM72EnvAllowNapping; extern PowerMac7_2_PlatformPlugin * PM72Plugin; extern const OSSymbol * gPM72EnvSystemUncalibrated; bool PowerMac7_2_CPUFanCtrlLoop::init( void ) { if (!super::init()) return(false); secOutputControl = NULL; tempHistory[0].sensValue = tempHistory[1].sensValue = 0; tempIndex = 0; secondsAtMaxCooling = 0; return(true); } void PowerMac7_2_CPUFanCtrlLoop::free( void ) { if (secOutputControl) { secOutputControl->release(); secOutputControl = NULL; } super::free(); } IOReturn PowerMac7_2_CPUFanCtrlLoop::initPlatformCtrlLoop( const OSDictionary *dict) { IOReturn status; const OSArray * array; const OSNumber * num; //CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::initPlatformCtrlLoop ENTERED\n"); // the cpu-id property tells me which cpu I'm related to num = OSDynamicCast(OSNumber, dict->getObject("cpu-id")); if (!num) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::initPlatformCtrlLoop no cpu-id\n"); return(kIOReturnError); } procID = num->unsigned32BitValue(); // Choose a PID dataset if (!choosePIDDataset( dict )) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::initPlatformCtrlLoop failed to choose PID dataset\n"); return(kIOReturnError); } status = super::initPlatformCtrlLoop(dict); // the second control is the slave control (index 1) if ((array = OSDynamicCast(OSArray, dict->getObject(kIOPPluginThermalControlIDsKey))) == NULL || (secOutputControl = platformPlugin->lookupControlByID( OSDynamicCast(OSNumber, array->getObject(1)) )) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::initPlatformCtrlLoop no sec control ID!!\n"); return(kIOReturnError); } secOutputControl->retain(); addControl( secOutputControl ); // the secondary output control (a.k.a. the intake fan) is programmed with a scaled version of the // value from the primary. This plist variable sets the scaling factor. It is in 16.16 fixed point // format. num = OSDynamicCast(OSNumber, dict->getObject("intake-scaling-factor")); if (!num) { intakeScaling = 0x00010000; // 1.0 in 16.16 fixed point CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::initPlatformCtrlLoop no intake-scaling-factor, assuming 1.0\n"); } else { intakeScaling = num->unsigned32BitValue(); CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::initPlatformCtrlLoop(%d) intake-scaling-factor is 0x%08lX\n", procID, intakeScaling); } // voltage sensor is at index 1 in the sensor-id array if ((array = OSDynamicCast(OSArray, dict->getObject(kIOPPluginThermalSensorIDsKey))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::initPlatformCtrlLoop no SensorIDArray\n"); return(kIOReturnError); } if ((voltageSensor = OSDynamicCast(IOPlatformSensor, platformPlugin->lookupSensorByID( OSDynamicCast(OSNumber, array->getObject(1)) ))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::initPlatformCtrlLoop no voltage sensor ID!!\n"); return(kIOReturnError); } voltageSensor->retain(); addSensor( voltageSensor ); // current sensor is at index 2 in the sensor-id array if ((currentSensor = OSDynamicCast(IOPlatformSensor, platformPlugin->lookupSensorByID( OSDynamicCast(OSNumber, array->getObject(2)) ))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::initPlatformCtrlLoop no current sensor ID!!\n"); return(kIOReturnError); } currentSensor->retain(); addSensor( currentSensor ); // power sensor is at index 3 in the sensor-id array if ((powerSensor = OSDynamicCast(IOPlatformSensor, platformPlugin->lookupSensorByID( OSDynamicCast(OSNumber, array->getObject(3)) ))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::initPlatformCtrlLoop no power sensor ID!!\n"); return(kIOReturnError); } powerSensor->retain(); addSensor( powerSensor ); #ifdef CTRLLOOP_DEBUG const OSNumber * slewID = OSNumber::withNumber( 0x10, 32 ); if ((slewControl = platformPlugin->lookupControlByID( slewID )) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::initPlatformCtrlLoop no slew control!!\n"); return(kIOReturnError); } slewID->release(); #endif //CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::initPlatformCtrlLoop ENTERED\n"); return(status); } bool PowerMac7_2_CPUFanCtrlLoop::choosePIDDataset( const OSDictionary * ctrlLoopDict ) { int i, count, cmp; const OSArray * tmp_array; OSArray * metaStateArray; OSDictionary * eepromDataset, * plistDataset, * chosenDataset; const OSData * binID, * plistBinID; if (!ctrlLoopDict) return(false); // get a pointer to the meta state array if ((metaStateArray = OSDynamicCast(OSArray, ctrlLoopDict->getObject(gIOPPluginThermalMetaStatesKey))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::choosePIDDataset failed to find meta state array\n"); return(false); } //CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::choosePIDDataset proc %u ctrlLoopDict %08lX metaStateArray %08lX\n", procID, // (UInt32) ctrlLoopDict, (UInt32) metaStateArray); // extract meta state data from IIC ROM if ((eepromDataset = fetchPIDDatasetFromROM()) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::choosePIDDataset failed to fetch EEPROM dataset\n"); return(false); } if ((binID = OSDynamicCast(OSData, eepromDataset->getObject( kPM72ProcessorBinKey ))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::choosePIDDataset failed to fetch processor bin id\n"); return(false); } // Search the plist for a dataset that matches this CPU's bin ID if ((tmp_array = OSDynamicCast(OSArray, ctrlLoopDict->getObject( kPM72CPUPIDDatasetsKey ))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::choosePIDDataset failed to fetch plist datasets\n"); chosenDataset = eepromDataset; goto installChosenDataset; } count = tmp_array->getCount(); //CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::choosePIDDataset proc %u count %d\n", procID, count); for (i=0; igetObject(i))) == NULL) continue; if ((plistBinID = OSDynamicCast(OSData, plistDataset->getObject( kPM72ProcessorBinKey ))) == NULL) continue; if (binID->isEqualTo(plistBinID)) break; plistDataset = NULL; } if (plistDataset == NULL) { // since we don't have a valid parameter list in our plist, use the data fetched from the EEPROM CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::choosePIDDataset plist datasets don't match processor\n"); chosenDataset = eepromDataset; goto installChosenDataset; } plistDataset->retain(); // Compare versions of the eeprom and plist datasets. Choose the one that's later, or use the // eeprom dataset if the versions are equal. cmp = comparePIDDatasetVersions( OSDynamicCast(OSData, eepromDataset->getObject(kPM72PIDDatasetVersionKey)), OSDynamicCast(OSData, plistDataset->getObject(kPM72PIDDatasetVersionKey)) ); if (cmp == -2) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::choosePIDDataset dataset version comparison failed\n"); return(false); } else if (cmp == -1) { chosenDataset = plistDataset; goto installChosenDataset; } else // cmp == 0 || cmp == 1 { chosenDataset = eepromDataset; goto installChosenDataset; } installChosenDataset: CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::choosePIDDataset using %s\n", chosenDataset == eepromDataset ? "EEPROM" : "PLIST"); metaStateArray->replaceObject(0, chosenDataset); if (plistDataset) plistDataset->release(); eepromDataset->release(); return(true); } // Compares two PID dataset versions. These are four bytes. The format is as follows: // 0xuuuabcld // u = unused // a = major // b = minor // c = very minor // l = level (d, a, b, f) // d = rev // // so 0x000100d9 is version 1.0.0d9. // // comparison procedure is to compare a, then b, then c, then l, then d // // this routine returns: // -1 if v1 is less than v2 // 0 if v1 is equal to v2 // 1 if v1 is greater than v2 // -2 for error #define MAJOR(x) (((x) & 0x000F0000) >> 16) #define MINOR(x) (((x) & 0x0000F000) >> 12) #define VERY_MINOR(x) (((x) & 0x00000F00) >> 8) #define LEVEL(x) (((x) & 0x000000F0) >> 4) #define REVISION(x) (((x) & 0x0000000F) >> 0) int PowerMac7_2_CPUFanCtrlLoop::comparePIDDatasetVersions( const OSData * v1, const OSData * v2 ) const { UInt32 lv1, lv2, t1, t2; UInt8 levelLookup[] = { /* 0 */ 0xFF, /* 1 */ 0xFF, /* 2 */ 0xFF, /* 3 */ 0xFF, /* 4 */ 0xFF, /* 5 */ 0xFF, /* 6 */ 0xFF, /* 7 */ 0xFF, /* 8 */ 0xFF, /* 9 */ 0xFF, /* A */ 1, /* B */ 2, /* C */ 0xFF, /* D */ 0, /* E */ 0xFF, /* F */ 3 }; if (v1 == NULL || v2 == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::comparePIDDatasetVersions bad args\n"); return(-2); } lv1 = (*((UInt32 *) v1->getBytesNoCopy()) & 0x000FFFFF); lv2 = (*((UInt32 *) v2->getBytesNoCopy()) & 0x000FFFFF); //CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::comparePIDDatasetVersions %08lX %08lX\n", lv1, lv2); // check for equal if (lv1 == lv2) return(0); // compare major t1 = MAJOR(lv1); t2 = MAJOR(lv2); if (t1 < t2) return(-1); else if (t1 > t2) return(1); // compare minor t1 = MINOR(lv1); t2 = MINOR(lv2); if (t1 < t2) return(-1); else if (t1 > t2) return(1); // compare very minor t1 = VERY_MINOR(lv1); t2 = VERY_MINOR(lv2); if (t1 < t2) return(-1); else if (t1 > t2) return(1); // compare level : d=0, a=1, b=2, f=3 t1 = (UInt32) levelLookup[LEVEL(lv1)]; t2 = (UInt32) levelLookup[LEVEL(lv2)]; if (t1 == 0xFF || t2 == 0xFF) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::comparePIDDatasetVersions invalid version\n"); return(-2); } if (t1 < t2) return(-1); else if (t1 > t2) return(1); t1 = REVISION(lv1); t2 = REVISION(lv2); if (t1 < t2) return(-1); else if (t1 > t2) return(1); // if we're here, something is wrong. The versions are not equal, but they're not greater // than or less than either?!?! CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::comparePIDDatasetVersions THIS SHOULDN'T HAPPEN\n"); return(-2); } OSDictionary *PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM( void ) const { OSDictionary * dataset; const OSSymbol * tmp_symbol; const OSData * tmp_osdata; const OSNumber * tmp_number; UInt8 tmp_bytebuf[4]; if ((dataset = OSDictionary::withCapacity(14)) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM failed to allocate dictionary\n"); return(NULL); } // Flag the source of this dataset tmp_symbol = OSSymbol::withCString( kPM72PIDDatasetSourceIICROM ); dataset->setObject( kPM72PIDDatasetSourceKey, tmp_symbol ); tmp_symbol->release(); // Get processor bin identifier if (!PM72Plugin->readProcROM( procID, 0x08, 3, tmp_bytebuf )) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM failed to fetch processor bin ID\n"); goto failReleaseDataset; } tmp_osdata = OSData::withBytes( tmp_bytebuf, 3 ); dataset->setObject( kPM72ProcessorBinKey, tmp_osdata ); tmp_osdata->release(); // Get dataset version if (!PM72Plugin->readProcROM( procID, 0x04, 4, tmp_bytebuf )) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM failed to fetch processor ROM version\n"); goto failReleaseDataset; } tmp_osdata = OSData::withBytes( tmp_bytebuf, 4 ); dataset->setObject( kPM72PIDDatasetVersionKey, tmp_osdata ); tmp_osdata->release(); // Proportional gain (G_p) if (!PM72Plugin->readProcROM( procID, 0x2C, 4, tmp_bytebuf )) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM failed to fetch G_p\n"); goto failReleaseDataset; } tmp_osdata = OSData::withBytes( tmp_bytebuf, 4 ); dataset->setObject( kIOPPIDCtrlLoopProportionalGainKey, tmp_osdata ); tmp_osdata->release(); // Reset gain (G_r) -- this is actually used as the power integral gain if (!PM72Plugin->readProcROM( procID, 0x30, 4, tmp_bytebuf )) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM failed to fetch G_r\n"); goto failReleaseDataset; } tmp_osdata = OSData::withBytes( tmp_bytebuf, 4 ); dataset->setObject( kIOPPIDCtrlLoopResetGainKey, tmp_osdata ); tmp_osdata->release(); // Derivative gain (G_d) if (!PM72Plugin->readProcROM( procID, 0x34, 4, tmp_bytebuf )) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM failed to fetch G_d\n"); goto failReleaseDataset; } tmp_osdata = OSData::withBytes( tmp_bytebuf, 4 ); dataset->setObject( kIOPPIDCtrlLoopDerivativeGainKey, tmp_osdata ); tmp_osdata->release(); // Min output (RPM/PWM) if (!PM72Plugin->readProcROM( procID, 0x50, 2, tmp_bytebuf )) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM failed to fetch min output\n"); goto failReleaseDataset; } tmp_number = OSNumber::withNumber( *((UInt16 *) tmp_bytebuf), 16 ); dataset->setObject( kIOPPIDCtrlLoopOutputMinKey, tmp_number ); tmp_number->release(); // Max output (RPM/PWM) if (!PM72Plugin->readProcROM( procID, 0x52, 2, tmp_bytebuf )) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM failed to fetch max output\n"); goto failReleaseDataset; } tmp_number = OSNumber::withNumber( *((UInt16 *) tmp_bytebuf), 16 ); dataset->setObject( kIOPPIDCtrlLoopOutputMaxKey, tmp_number ); tmp_number->release(); // history length if (!PM72Plugin->readProcROM( procID, 0x2B, 1, tmp_bytebuf )) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM failed to fetch history length\n"); goto failReleaseDataset; } tmp_number = OSNumber::withNumber( tmp_bytebuf[0], 8 ); dataset->setObject( kIOPPIDCtrlLoopHistoryLenKey, tmp_number ); tmp_number->release(); // Target temperature if (!PM72Plugin->readProcROM( procID, 0x28, 1, tmp_bytebuf )) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM failed to fetch target temp\n"); goto failReleaseDataset; } tmp_number = OSNumber::withNumber( ((UInt32)tmp_bytebuf[0]) << 16, 32 ); dataset->setObject( kIOPPIDCtrlLoopInputTargetKey, tmp_number ); tmp_number->release(); // Max temperature if (!PM72Plugin->readProcROM( procID, 0x29, 1, tmp_bytebuf )) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM failed to fetch max temp\n"); goto failReleaseDataset; } tmp_number = OSNumber::withNumber( ((UInt32)tmp_bytebuf[0]) << 16, 32 ); dataset->setObject( kIOPPIDCtrlLoopInputMaxKey, tmp_number ); tmp_number->release(); // Max power if (!PM72Plugin->readProcROM( procID, 0x2A, 1, tmp_bytebuf )) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM failed to fetch max power\n"); goto failReleaseDataset; } tmp_number = OSNumber::withNumber( ((UInt32)tmp_bytebuf[0]) << 16, 32 ); dataset->setObject( kPM72MaxPowerKey, tmp_number ); tmp_number->release(); // Power adjustment if (!PM72Plugin->readProcROM( procID, 0x27, 1, tmp_bytebuf )) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::fetchPIDDatasetFromROM failed to fetch max power adjustment\n"); goto failReleaseDataset; } tmp_number = OSNumber::withNumber( ((UInt32)tmp_bytebuf[0]) << 16, 32 ); dataset->setObject( kPM72MaxPowerAdjustmentKey, tmp_number ); tmp_number->release(); // The iteration interval is not stored in the ROM, it is always set to 1 second dataset->setObject( kIOPPIDCtrlLoopIntervalKey, gIOPPluginOne ); return(dataset); failReleaseDataset: dataset->release(); return(NULL); } bool PowerMac7_2_CPUFanCtrlLoop::updateMetaState( void ) { const OSArray * metaStateArray; const OSDictionary * metaStateDict; const OSNumber * newMetaState; // else if there is an overtemp condition, use meta-state 1 // else if there is a forced meta state, use it // else, use meta-state 0 if ((metaStateArray = OSDynamicCast(OSArray, infoDict->getObject(gIOPPluginThermalMetaStatesKey))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::updateMetaState no meta state array\n"); return(false); } // Check for overtemp condition // if ((platformPlugin->envArrayCondIsTrue(gIOPPluginEnvInternalOvertemp)) || // (platformPlugin->envArrayCondIsTrue(gIOPPluginEnvExternalOvertemp))) if ((platformPlugin->envArrayCondIsTrue(gIOPPluginEnvExternalOvertemp)) || (platformPlugin->getEnv(gPM72EnvSystemUncalibrated)) != NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::updateMetaState Entering Overtemp Mode\n"); if ((metaStateDict = OSDynamicCast(OSDictionary, metaStateArray->getObject(1))) != NULL && (cacheMetaState( metaStateDict ) == true)) { // successfully entered overtemp mode setMetaState( gIOPPluginOne ); return(true); } else { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::updateMetaState Overtemp Mode Failed!\n"); } } // Look for forced meta state if ((metaStateDict = OSDynamicCast(OSDictionary, infoDict->getObject(gIOPPluginForceCtrlLoopMetaStateKey))) != NULL) { if (cacheMetaState( metaStateDict ) == true) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::updateMetaState using forced meta state\n"); newMetaState = OSNumber::withNumber( 0xFFFFFFFF, 32 ); setMetaState( newMetaState ); newMetaState->release(); return(true); } else { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::updateMetaState forced meta state is invalid, removing...\n"); infoDict->removeObject(gIOPPluginForceCtrlLoopMetaStateKey); } } // Use default "Normal" meta state if ((metaStateDict = OSDynamicCast(OSDictionary, metaStateArray->getObject(0))) != NULL && (cacheMetaState( metaStateDict ) == true)) { //CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::updateMetaState use meta state zero\n"); setMetaState( gIOPPluginZero ); return(true); } else { // can't find a valid meta state, nothing we can really do except log an error CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::updateMetaState no valid meta states!\n"); return(false); } } bool PowerMac7_2_CPUFanCtrlLoop::acquireSample( void ) { samplePoint * latest; const OSNumber * curValue; // Set up the temperature history if (tempIndex == 1) tempIndex = 0; else tempIndex = 1; // fetch the temperature reading if ((curValue = getAggregateSensorValue()) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::acquireSample failed to fetch temp sensor value\n"); tempHistory[tempIndex].sensValue = 0; goto failGetTemperatureValue; } tempHistory[tempIndex].sensValue = (SInt32) curValue->unsigned32BitValue(); curValue->release(); // move the top of the power array to the next spot -- it's circular if (latestSample == 0) latestSample = historyLen - 1; else latestSample -= 1; // get a pointer to the array element where we'll store this sample point latest = &historyArray[latestSample]; // fetch the power reading // the power sensor is a "fake" logical sensor. In order to get a good reading, // we have to update the current and power sensors first. if ((curValue = voltageSensor->fetchCurrentValue()) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::acquireSample failed to update voltage reading\n"); goto failGetPowerValue; } voltageSensor->setCurrentValue( curValue ); curValue->release(); if ((curValue = currentSensor->fetchCurrentValue()) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::acquireSample failed to update current reading\n"); goto failGetPowerValue; } currentSensor->setCurrentValue( curValue ); curValue->release(); if ((curValue = powerSensor->fetchCurrentValue()) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::acquireSample failed to update power reading\n"); goto failGetPowerValue; } powerSensor->setCurrentValue( curValue ); // store the sample in the history latest->sample.sensValue = (SInt32) curValue->unsigned32BitValue(); // calculate the error term and store it latest->error.sensValue = powerMaxAdj.sensValue - latest->sample.sensValue; //CTRLLOOP_DLOG("*** SAMPLE *** InT: 0x%08lX Cur: 0x%08lX Error: 0x%08lX\n", // inputTarget.sensValue, latest->sample.sensValue, latest->error.sensValue); curValue->release(); return(true); failGetPowerValue: latest->sample.sensValue = 0; latest->error.sensValue = 0; failGetTemperatureValue: return(false); } bool PowerMac7_2_CPUFanCtrlLoop::cacheMetaState( const OSDictionary * metaState ) { const OSData * dataG_p, * dataG_d, * dataG_r; const OSNumber * numInterval, * numOverride, * numInputTarget, * numInputMax; const OSNumber * numOutputMin, * numOutputMax, * numHistLen, * numPowerMax, * numPowerAdj; //const OSNumber * tmpNumber; samplePoint * sample, * newHistoryArray; unsigned int i; UInt32 tempInterval, newHistoryLen; // cache the interval. it is listed in seconds. if ((numInterval = OSDynamicCast(OSNumber, metaState->getObject("interval"))) != NULL) { tempInterval = numInterval->unsigned32BitValue(); if ((tempInterval == 0) || (tempInterval > 300)) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::cacheMetaState meta state interval is out of bounds\n"); goto failNoInterval; } } else { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::cacheMetaState meta state interval is absent\n"); goto failNoInterval; } // if there is an output-override key, flag it. Otherwise, look for the full // set of coefficients, setpoints and output bounds if ((numOverride = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopOutputOverrideKey))) != NULL) { overrideActive = true; outputOverride = numOverride; outputOverride->retain(); //CTRLLOOP_DLOG("*** PID CACHE *** Override: 0x%08lX\n", outputOverride->unsigned32BitValue()); } else { // look for G_p, G_d, G_r, input-target, output-max, output-min if ((dataG_p = OSDynamicCast(OSData, metaState->getObject(kIOPPIDCtrlLoopProportionalGainKey))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::cacheMetaState meta state has no G_p\n"); goto failFullSet; } if ((dataG_d = OSDynamicCast(OSData, metaState->getObject(kIOPPIDCtrlLoopDerivativeGainKey))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::cacheMetaState meta state has no G_d\n"); goto failFullSet; } if ((dataG_r = OSDynamicCast(OSData, metaState->getObject(kIOPPIDCtrlLoopResetGainKey))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::cacheMetaState meta state has no G_r\n"); goto failFullSet; } if ((numInputTarget = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopInputTargetKey))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::cacheMetaState meta state has no intput-target\n"); goto failFullSet; } if ((numInputMax = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopInputMaxKey))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::cacheMetaState meta state has no input-max\n"); goto failFullSet; } if ((numPowerMax = OSDynamicCast(OSNumber, metaState->getObject(kPM72MaxPowerKey))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::cacheMetaState meta state has no power-max\n"); goto failFullSet; } if ((numPowerAdj = OSDynamicCast(OSNumber, metaState->getObject(kPM72MaxPowerAdjustmentKey))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::cacheMetaState meta state has no power-max-adjustment\n"); goto failFullSet; } if ((numOutputMin = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopOutputMinKey))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::cacheMetaState meta state has no output-min\n"); goto failFullSet; } if ((numOutputMax = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopOutputMaxKey))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::cacheMetaState meta state has no output-max\n"); goto failFullSet; } if ((numHistLen = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopHistoryLenKey))) == NULL) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::cacheMetaState meta state has no history-length\n"); goto failFullSet; } overrideActive = false; if (outputOverride) { outputOverride->release(); outputOverride = NULL; } G_p = *((SInt32 *) dataG_p->getBytesNoCopy()); G_d = *((SInt32 *) dataG_d->getBytesNoCopy()); G_r = *((SInt32 *) dataG_r->getBytesNoCopy()); inputTarget.sensValue = (SInt32)numInputTarget->unsigned32BitValue(); inputMax.sensValue = (SInt32)numInputMax->unsigned32BitValue(); powerMaxAdj.sensValue = ((SInt32) numPowerMax->unsigned32BitValue()) - ((SInt32) numPowerAdj->unsigned32BitValue()); CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::cacheMetaState powerMaxAdj: %ld\n", powerMaxAdj.sensValue >> 16); outputMin = numOutputMin->unsigned32BitValue(); outputMax = numOutputMax->unsigned32BitValue(); // resize the history array if necessary newHistoryLen = numHistLen->unsigned32BitValue(); if (newHistoryLen == 0) newHistoryLen = 2; // must be two or more in order to have a valid // derivative term if (newHistoryLen != historyLen) { newHistoryArray = (samplePoint *) IOMalloc( sizeof(samplePoint) * newHistoryLen ); bzero( newHistoryArray, sizeof(samplePoint) * newHistoryLen ); // copy samples from the old array into the new for (i=0; isample.sensValue = sample->sample.sensValue; (&(newHistoryArray[i]))->error.sensValue = sample->error.sensValue; } IOFree( historyArray, sizeof(samplePoint) * historyLen ); historyArray = newHistoryArray; historyLen = newHistoryLen; latestSample = 0; } // CTRLLOOP_DLOG("*** PID CACHE *** G_p: 0x%08lX G_d: 0x%08lX G_r: 0x%08lX\n" // "***************** inT: 0x%08lX oMi: 0x%08lX oMa: 0x%08lX\n", // G_p, G_d, G_r, inputTarget, outputMin, outputMax); } // set the interval intervalSec = tempInterval; clock_interval_to_absolutetime_interval(intervalSec, NSEC_PER_SEC, &interval); // CTRLLOOP_DLOG("***************** Interval: %u\n", intervalSec); return(true); failFullSet: failNoInterval: return(false); } const OSNumber *PowerMac7_2_CPUFanCtrlLoop::calculateNewTarget( void ) const { SInt32 pRaw, dRaw, rRaw; SInt64 accum, dProd, rProd, pProd; //UInt32 result, prevResult, scratch; SInt32 result; UInt32 uResult; SensorValue adjInputTarget, sVal; const OSNumber * newTarget; // if there is an output override, use it if (overrideActive) { CTRLLOOP_DLOG("*** PID *** Override Active\n"); newTarget = outputOverride; newTarget->retain(); } // apply the PID algorithm to choose a new control target value else { if (ctrlloopState == kIOPCtrlLoopFirstAdjustment) { result = 0; } else { // Calculate the adjusted target rRaw = calculateIntegralTerm().sensValue; // this is 12.20 * 16.16 => 28.36 rProd = (SInt64)G_r * (SInt64)rRaw; sVal.sensValue = (SInt32)((rProd >> 20) & 0xFFFFFFFF); sVal.sensValue = inputMax.sensValue - sVal.sensValue; adjInputTarget.sensValue = inputTarget.sensValue < sVal.sensValue ? inputTarget.sensValue : sVal.sensValue; // do the PID iteration result = (SInt32)outputControl->getTargetValue()->unsigned32BitValue(); // calculate the derivative term // apply the derivative gain // this is 12.20 * 16.16 => 28.36 dRaw = calculateDerivativeTerm().sensValue; accum = dProd = (SInt64)G_d * (SInt64)dRaw; // calculate the reset term // apply the reset gain // this is 12.20 * 16.16 => 28.36 //rRaw = calculateIntegralTerm().sensValue; //rProd = (SInt64)G_r * (SInt64)rRaw; //accum += rProd; // calculate the proportional term // apply the proportional gain // this is 12.20 * 16.16 => 28.36 pRaw = tempHistory[tempIndex].sensValue - adjInputTarget.sensValue; pProd = (SInt64)G_p * (SInt64)pRaw; accum += pProd; // truncate the fractional part accum >>= 36; //result = (UInt32)(accum < 0 ? 0 : (accum & 0xFFFFFFFF)); result += (SInt32)accum; } uResult = (UInt32)(result > 0) ? result : 0; // apply the hard limits if (uResult < outputMin) uResult = outputMin; else if (uResult > outputMax) uResult = outputMax; /* #ifdef CTRLLOOP_DEBUG if (timerCallbackActive) { const OSString * tempDesc; const OSNumber * val; val = slewControl->fetchCurrentValue(); slewControl->setCurrentValue( val ); val->release(); #endif CTRLLOOP_DLOG("%s" " powerSum=%08lX (%ld)" " G_power=%08lX" " powerProd=%016llX (%lld)" " Ttgt=%08lX (%ld)" " Tmax=%08lX (%ld)" " TtgtAdj=%08lX (%ld)" " T_cur=%08lX (%ld)" " T_err=%08lX (%ld)" " G_p=%08lX" " pProd=%016llX (%lld)" " dRaw=%08lX (%ld)" " G_d=%08lX" " dProd=%016llX (%lld)" " Res=%016llX" " Out=%lu" " Spd=%0u\n", (tempDesc = OSDynamicCast( OSString, infoDict->getObject(kIOPPluginThermalGenericDescKey))) != NULL ? tempDesc->getCStringNoCopy() : "Unknown CtrlLoop", rRaw, rRaw >> 16, G_r, rProd, rProd >> 36, inputTarget.sensValue, inputTarget.sensValue >> 16, inputMax.sensValue, inputMax.sensValue >> 16, adjInputTarget.sensValue, adjInputTarget.sensValue >> 16, tempHistory[tempIndex].sensValue, tempHistory[tempIndex].sensValue >> 16, pRaw, pRaw >> 16, G_p, pProd, pProd >> 36, dRaw, dRaw >> 16, G_d, dProd, dProd >> 36, accum, uResult, slewControl->getCurrentValue()->unsigned8BitValue() ); #ifdef CTRLLOOP_DEBUG } #endif */ newTarget = OSNumber::withNumber( uResult, 32 ); } return(newTarget); } SensorValue PowerMac7_2_CPUFanCtrlLoop::calculateDerivativeTerm( void ) const { int latest, previous; SensorValue result; latest = tempIndex; previous = tempIndex == 0 ? 1 : 0; // get the change in the error term over the latest inteval result.sensValue = tempHistory[latest].sensValue - tempHistory[previous].sensValue; // divide by the elapsed time to get the slope result.sensValue /= (SInt32)intervalSec; return(result); } void PowerMac7_2_CPUFanCtrlLoop::deadlinePassed( void ) { bool deadlineAbsolute; bool didSetEnv = false; bool tMaxExceededNow, tMaxExceededNowCritical, tMaxExceededPreviously, maxCoolingApplied; SensorValue temperatureDiff; deadlineAbsolute = (ctrlloopState == kIOPCtrlLoopFirstAdjustment); timerCallbackActive = true; // sample the input if (!acquireSample()) { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::deadlinePassed FAILED TO ACQUIRE INPUT SAMPLE!!!\n"); } // check to see if we've exceeded the critical temperature temperatureDiff.sensValue = tempHistory[tempIndex].sensValue - inputMax.sensValue; tMaxExceededPreviously = platformPlugin->envArrayCondIsTrueForObject(this, gIOPPluginEnvInternalOvertemp); tMaxExceededNow = temperatureDiff.sensValue > 0; // if the CPU temperature is above T_max, then we may need to force the machine to sleep if (tMaxExceededNow) { // if tMaxExceededPreviously is true, we know that the internal overtemp flag was set // causing the CPU to slew slow maxCoolingApplied = (tMaxExceededPreviously && outputControl->getTargetValue()->unsigned32BitValue() >= outputMax); if (maxCoolingApplied) { secondsAtMaxCooling += intervalSec; CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::deadlinePassed secondsAtMaxCooling=%u\n", secondsAtMaxCooling); } // Check for forced sleep conditions // If CPU temperature is above (T_max + 8) // -OR- // CPU temperature is above (T_max) and max cooling has been applied for 30 seconds or more tMaxExceededNowCritical = temperatureDiff.sensValue >= kPM72CPUTempCriticalOffset; if (tMaxExceededNowCritical || secondsAtMaxCooling >= kPM72CPUMaxCoolingLimit) { IOLog("PowerMac7,2 Thermal Manager: Thermal Runaway Detected: System Will Sleep\n"); CTRLLOOP_DLOG("PowerMac7,2 Thermal Manager: Thermal Runaway Detected: System Will Sleep\n"); CTRLLOOP_DLOG("PM72 T_cur=%u, T_max=%u, tMaxExceededNowCritical=%s, secondsAtMaxCooling=%u\n", tempHistory[tempIndex].sensValue >> 16, inputMax.sensValue >> 16, tMaxExceededNowCritical ? "TRUE" : "FALSE", secondsAtMaxCooling); platformPlugin->sleepSystem(); } } // set or clear the internal overtemp flag as needed. If set, this causes the CPU to slew slow. if (tMaxExceededPreviously && !tMaxExceededNow) { secondsAtMaxCooling = 0; platformPlugin->setEnvArray(gIOPPluginEnvInternalOvertemp, this, false); didSetEnv = true; } else if (tMaxExceededNow && !tMaxExceededPreviously) { platformPlugin->setEnvArray(gIOPPluginEnvInternalOvertemp, this, true); didSetEnv = true; } // If we changed the environment, the platform plugin will invoke updateMetaState() // and adjustControls(). If not, then we just need to call adjustControls() if (!didSetEnv) { adjustControls(); } // set the deadline if (deadlineAbsolute) { // this is the first time we're setting the deadline. In order to better stagger // timer callbacks, offset the deadline by 100us * ctrlloopID. AbsoluteTime adjustedInterval; const OSNumber * id = getCtrlLoopID(); // 100 * ctrlLoopID -> absolute time format clock_interval_to_absolutetime_interval(100 * id->unsigned32BitValue(), NSEC_PER_USEC, &adjustedInterval); // Add standard interval to produce adjusted interval ADD_ABSOLUTETIME( &adjustedInterval, &interval ); clock_absolutetime_interval_to_deadline(adjustedInterval, &deadline); } else { ADD_ABSOLUTETIME(&deadline, &interval); } timerCallbackActive = false; } void PowerMac7_2_CPUFanCtrlLoop::sendNewTarget( const OSNumber * newTarget ) { const OSNumber * secNewTarget; UInt32 secRPM; /* // If the fans are at minimum speed, don't let the processors nap. We need to // keep the heat sink warm or it loses efficiency. bool allowNapping = (newTarget->unsigned32BitValue() != outputMin); // Sync the internal napping state with the platform plugin's environment dict if (ctrlloopState == kIOPCtrlLoopFirstAdjustment || allowNapping != platformPlugin->envArrayCondIsTrueForObject(this, gPM72EnvAllowNapping)) { platformPlugin->setEnvArray(gPM72EnvAllowNapping, this, allowNapping); } */ // If the new target value is different, send it to the control if (ctrlloopState == kIOPCtrlLoopFirstAdjustment || ctrlloopState == kIOPCtrlLoopDidWake || !newTarget->isEqualTo( outputControl->getTargetValue() )) { if (outputControl->sendTargetValue( newTarget )) { outputControl->setTargetValue(newTarget); ctrlloopState = kIOPCtrlLoopAllRegistered; } else { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::sendNewTarget failed to send target value\n"); } // the secondary control (secOutputControl) is programmed with a scaled version // of the primary control (outputControl) value. Now I need to apply the 16.16 // scaling factor to the primary control's target in order to calculate the // secondary control's target. This requires some shifting since the scaling // factor is a 16.16 fixed point value. secRPM = (newTarget->unsigned32BitValue() * intakeScaling) >> 16; // The scaled value is again clipped to the min/max output rpm. if (secRPM < outputMin) secRPM = outputMin; else if (secRPM > outputMax) secRPM = outputMax; secNewTarget = OSNumber::withNumber( secRPM, 32 ); if (secOutputControl->sendTargetValue( secNewTarget )) { secOutputControl->setTargetValue(secNewTarget); } else { CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::sendNewTarget failed to send target value (secondary)\n"); } secNewTarget->release(); } } void PowerMac7_2_CPUFanCtrlLoop::sensorRegistered( IOPlatformSensor * aSensor ) { //CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::sensorRegistered - entered\n"); if (inputSensor->isRegistered() == kOSBooleanTrue && voltageSensor->isRegistered() == kOSBooleanTrue && currentSensor->isRegistered() == kOSBooleanTrue && outputControl->isRegistered() == kOSBooleanTrue && secOutputControl->isRegistered() == kOSBooleanTrue) { //CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::sensorRegistered allRegistered!\n"); ctrlloopState = kIOPCtrlLoopFirstAdjustment; // set the deadline deadlinePassed(); } } void PowerMac7_2_CPUFanCtrlLoop::controlRegistered( IOPlatformControl * aControl ) { //CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::controlRegistered - entered\n"); if (((aControl == outputControl && secOutputControl->isRegistered() == kOSBooleanTrue) || (aControl == secOutputControl && outputControl->isRegistered() == kOSBooleanTrue)) && inputSensor->isRegistered() == kOSBooleanTrue && voltageSensor->isRegistered() == kOSBooleanTrue && currentSensor->isRegistered() == kOSBooleanTrue) { //CTRLLOOP_DLOG("PowerMac7_2_CPUFanCtrlLoop::controlRegistered allRegistered!\n"); ctrlloopState = kIOPCtrlLoopFirstAdjustment; // set the deadline deadlinePassed(); } }