/*++ Copyright (c) 2001 Microsoft Corporation Module Name: legacy.c Abstract: This module implements code that works on Cyrix processors with LongHaul power management support. Author: Tom Brown (t-tbrown) 11-Jun-2001 Environment: Kernel mode Revision History: --*/ #include #include "..\lib\processor.h" #include "legacy.h" #include "viac3.h" extern PFDO_DATA DeviceExtensions[MAX_SUPPORTED_PROCESSORS]; extern UCHAR DevExtIndex; extern GLOBALS Globals; // set in IdentifyCPUVersion according to data in // "Samuel 2, Ezra, and C5M LongHaul Programmer's Guide (Version 3.0.0 Gamma)" ULONG LongHaulFlags; // Encoding of the LongHaul revision set by IdentifyCPUVersion. Use it with the following macros ULONG FsbFreq; // (MHz) Front Side Bus frequency #define POWER_V 1200 // Power is measured in mW, but this is a hack value used for states that have an unknown mV #define ABSOLUTE_MIN_FREQ 300 #define DEFAULT_LATENCY_US 500000 // Set default latency to 500ms in microseconds, because transitions suck (take a long time, occupy whole proc) ULONG NextTransitionThrottle; // (%) Next state (as percentage of total speed) that a worker thread will transition to, or INVALID_THROTTLE if no worker thread is queued #define RESV (-1) // A reserved value in the tables #define BR_MULT 2 // Used to avoid putting floats in the BR tables // divide by 2 is optimised to a shift by the compiler #define LH_SOFTBR_MIN 1 // index of lowest value in LH_SOFTBR const ULONG LH_SOFTBR [32] = { // Indexed by bits written to softBR(14,19:16) to the BR that the bits represent 20, 6, 8, 18, // Actual bus ratio = LH_SOFTBR[i] / BR_MULT 19, 7, 9, 11, 12, 14, 16, 10, 13, 15, 17, 24, RESV, 22, 24, RESV, 21, 23, 25, 27, 28, 30, 32, 26, 29, 31, RESV, RESV }; const ULONG LH_SOFTBR_SORT [32] = { // Indexes of LH_SOFTBR, sorted decreasing by value 26, 29, 25, 28, // BRs are put into the pss in decreasing order 24, 23, 27, 22, 18, 21, 17, 20, // LH_SOFTBR[15] is never used because there are two entries for 12.0X(24) 0, 4, 3, 14, 10, 13, 9, 12, 8, 7, 11, 6, 2, 5, 1, RESV, RESV, RESV, RESV, RESV }; #define LH_SOFTBR_SIZE (sizeof(LH_SOFTBR) / sizeof(ULONG)) ULONG const* LhSoftVid; // Set to LH_SOFTVID_VRM85 or LH_SOFTVID_MOBILE ULONG const* LhSoftVidSort; // Set to LH_SOFTVID_VRM85_SORT or LH_SOFTVID_MOBILE_SORT ULONG LhSoftVidSize; // Set to LH_SOFTVID_VRM85_SIZE or LH_SOFTVID_MOBILE_SIZE const ULONG LH_SOFTVID_VRM85 [32] = { // mVolts with a VRM 8.5 // VRM 8.5, bit15=0 1250, 1200, 1150, 1100, 1050, 1800, 1750, 1700, 1650, 1600, 1550, 1500, 1450, 1400, 1350, 1300, 1275, 1225, 1175, 1125, 1075, 1825, 1775, 1725, 1675, 1625, 1575, 1525, 1475, 1425, 1375, 1325 }; const ULONG LH_SOFTVID_VRM85_SORT [32] = { // Indexes of LH_SOFTVID_VRM85, sorted decreasing by value 21, 5, 22, 6, // voltage levels are used in decreasing order in the pss 23, 7, 24, 8, 25, 9, 26, 10, 27, 11, 28, 12, 29, 13, 30, 14, 31, 15, 16, 0, 17, 1, 18, 2, 19, 3, 20, 4 }; #define LH_SOFTVID_VRM85_SIZE (sizeof(LH_SOFTVID_VRM85) / sizeof(ULONG)) const ULONG LH_SOFTVID_MOBILE [32] = { // mVolts with a Mobile VRM // Mobile VRM, bit15=1 2000, 1950, 1900, 1850, 1800, 1750, 1700, 1650, 1600, 1550, 1500, 1450, 1400, 1350, 1300, RESV, 1275, 1250, 1225, 1200, 1175, 1150, 1125, 1100, 1075, 1050, 1025, 1000, 975, 950, 925, RESV }; const ULONG LH_SOFTVID_MOBILE_SORT [32] = { // Indexes of LH_SOFTVID_MOBILE, sorted decreasing by value 0, 1, 2, 3, // voltage levels are used in decreasing order in the pss 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, RESV, RESV }; #define LH_SOFTVID_MOBILE_SIZE (sizeof(LH_SOFTVID_MOBILE) / sizeof(ULONG)) // Indexed by bits MSR[43,35:32] to the BR*BR_MULT they represent const ULONG LH_MAX_BR [32] = { // bit 43=0 10, 6, 8, 20, // DIVIDE these values by BR_MULT to get actual BR! 11, 7, 9, 19, 18, 14, 16, 12, 24, 15, 17, 13, // bit 43=1 RESV, 22, 24, RESV, 27, 23, 25, 21, 26, 30, 32, 28, RESV, 31, RESV, 29 }; #define LH_MAX_BR_SIZE (sizeof(LH_MAX_BR) / sizeof(ULONG)) // Indexed by bits MSR[59,51:48] to the BR*BR_MULT they represent const ULONG LH_MIN_BR [32] = { // bit 59=0 10, 6, 8, 20, // DIVIDE these values by BR_MULT to get actual BR! 11, 7, 9, 19, 18, 14, 16, 12, 24, 15, 17, 13, // bit 59=1 RESV, RESV, RESV, RESV, RESV, RESV, RESV, RESV, RESV, RESV, RESV, RESV, RESV, RESV, RESV, RESV }; #define LH_MIN_BR_SIZE (sizeof(LH_MIN_BR) / sizeof(ULONG)) const ULONG LH_FSB [4] = { // (MHz) Frequency of FSB 133, 100, RESV, 66 }; VOID InitializeCPU() /*++ Routine Description: Optionally initializes the softVID value in the MSR to MaximumVID. Arguments: None Return Value: None --*/ { LONGHAUL_MSR longhaul_msr; // Need to initilize the MSR since it boots with an undetermined value. // The transition routine needs the value to be correct since it may need // to avoid taking voltage steps larger than MAX_V_STEP mV. if( SUPPORTS_SOFTVID && SET_MAXV_AT_STARTUP ) { longhaul_msr.AsQWord = ReadMSR(MSR_LONGHAUL_ADDR); ExSetTimerResolution (10032,TRUE); TransitionNow( RESV, FALSE, longhaul_msr.MaximumVID, TRUE ); ExSetTimerResolution (0, FALSE); } } VOID IdentifyLongHaulParameters() /*++ Routine Description: Read LondHaulMSR RevisionID. Make sure this software supports the revision and set the flags. Sets LhSoftVid, LhSoftVidSort, LhSoftVidSize to the correct constant. Arguments: None Return Value: None --*/ { LONGHAUL_MSR longhaul_msr; ULONG freq; ACPI_PSS_DESCRIPTOR pss; PSS_CONTROL control; longhaul_msr.AsQWord = ReadMSR(MSR_LONGHAUL_ADDR); if( longhaul_msr.RevisionID == 0 ) { LongHaulFlags |= SUPPORTS_MSR_FLAG | SUPPORTS_SOFTBR_FLAG; } else if( longhaul_msr.RevisionID == 1 ) { LongHaulFlags |= SUPPORTS_MSR_FLAG | SUPPORTS_SOFTBR_FLAG | SUPPORTS_SOFTVID_FLAG | NEEDS_VOLTAGE_STEPPING_FLAG | SET_MAXV_AT_STARTUP_FLAG; } else if( longhaul_msr.RevisionID == 2 ) { LongHaulFlags |= SUPPORTS_MSR_FLAG | SUPPORTS_SOFTBR_FLAG | SUPPORTS_SOFTVID_FLAG | NEEDS_VOLTAGE_STEPPING_FLAG | SET_MAXV_AT_STARTUP_FLAG; } else { DebugPrint((WARN, "Unknown LongHaul revision %u.\n", longhaul_msr.RevisionID)); return; } if( SUPPORTS_SOFTVID ) { if( longhaul_msr.VRMRev == VRM85 ) { LhSoftVid = LH_SOFTVID_VRM85; LhSoftVidSort = LH_SOFTVID_VRM85_SORT; LhSoftVidSize = LH_SOFTVID_VRM85_SIZE; } else { // Mobile VRM LhSoftVid = LH_SOFTVID_MOBILE; LhSoftVidSort = LH_SOFTVID_MOBILE_SORT; LhSoftVidSize = LH_SOFTVID_MOBILE_SIZE; } } } VOID IdentifyCPUVersion() /*++ Routine Description: Set the LongHaul flags according to what is known about the hardware. Expects flags to be cleared when called. Arguments: None Return Value: None --*/ { ULONG eax, ebx, ecx, edx; CPUID_FUNC1 cpuid_result; // Check CPUID Function 0 returns vendor string EBX:EDX:ECX = "CentaurHauls" CPUID(0x0, &eax, &ebx, &ecx, &edx); if ( ebx != CPUID_FUNC0_EBX || edx != CPUID_FUNC0_EDX || ecx != CPUID_FUNC0_ECX ) { DebugPrint((WARN, "Invalid result from CPUID Function 0.\n")); return; } // Get Family/Model/Stepping from CPUID Function 1 CPUID(0x1, &cpuid_result.AsDWord, &ebx, &ecx, &edx); if (cpuid_result.Family == 6 ) { if ( cpuid_result.Model == 6 ) { DebugPrint((TRACE, "Found VIA C3 Samuel 1 (C5A).\n" )); return; } if ( cpuid_result.Model == 7 ) { if( cpuid_result.Stepping==0) { DebugPrint((TRACE, "Found VIA C3 Samuel 2 (C5B), stepping 0.\n" )); return; } else if ( cpuid_result.Stepping <= 7 ) { DebugPrint((TRACE, "Found VIA C3 Samuel 2 (C5B), stepping 1-7.\n" )); IdentifyLongHaulParameters(); return; } else { DebugPrint((TRACE, "Found VIA C3 Ezra (C5C).\n" )); IdentifyLongHaulParameters(); return; } } if ( cpuid_result.Model == 8 ) { DebugPrint((TRACE, "Found VIA C3 Ezra-T (C5M).\n" )); IdentifyLongHaulParameters(); return; } } DebugPrint((WARN, "Unknown CPU family/model/stepping %u/%u/%u is not supported.\n", cpuid_result.Family, cpuid_result.Model, cpuid_result.Stepping )); return; } #ifdef DBG VOID DebugShowPossibleLongHaulMSR() /*++ Routine Description: Debug spew the contents of the LongHaul MSR. Arguments: None Return Value: None --*/ { LONGHAUL_MSR longhaul_msr; DebugAssert( SUPPORTS_MSR ); longhaul_msr.AsQWord = ReadMSR(MSR_LONGHAUL_ADDR); DebugPrint((TRACE," RevisionID: %i\n",longhaul_msr.RevisionID)); DebugPrint((TRACE," RevisionKey: %i\n", longhaul_msr.RevisionKey)); DebugPrint((TRACE," EnableSoftBusRatio: %i\n", longhaul_msr.EnableSoftBusRatio)); DebugPrint((TRACE," EnableSoftVID: %i\n", longhaul_msr.EnableSoftVID)); DebugPrint((TRACE," EnableSoftBSEL: %i\n", longhaul_msr.EnableSoftBSEL)); DebugPrint((TRACE," Reserved1: %i\n", longhaul_msr.Reserved1)); DebugPrint((TRACE," Reserved2: %i\n", longhaul_msr.Reserved2)); DebugPrint((TRACE," Reserved3: %i\n", longhaul_msr.Reserved3)); DebugPrint((TRACE," VRMRev: %i\n", longhaul_msr.VRMRev)); DebugPrint((TRACE," SoftBusRatio: %i\n", longhaul_msr.SoftBusRatio4<<4 | longhaul_msr.SoftBusRatio0)); DebugPrint((TRACE," SoftVID: %i\n", longhaul_msr.SoftVID)); DebugPrint((TRACE," Reserved4: %i\n", longhaul_msr.Reserved4)); DebugPrint((TRACE," SoftBSEL: %i\n", longhaul_msr.SoftBSEL)); DebugPrint((TRACE," Reserved5: %i\n", longhaul_msr.Reserved5)); DebugPrint((TRACE," MaxMHzBR: %i\n", longhaul_msr.MaxMHzBR4<<4 | longhaul_msr.MaxMHzBR0)); DebugPrint((TRACE," MaximumVID: %i\n", longhaul_msr.MaximumVID)); DebugPrint((TRACE," MaxMHzFSB: %i\n", longhaul_msr.MaxMHzFSB)); DebugPrint((TRACE," Reserved6: %i\n", longhaul_msr.Reserved6)); DebugPrint((TRACE," MinMHzBR: %i\n", longhaul_msr.MinMHzBR4<<4 | longhaul_msr.MinMHzBR0)); DebugPrint((TRACE," MinimumVID: %i\n", longhaul_msr.MinimumVID)); DebugPrint((TRACE," MinMHzFSB: %i\n", longhaul_msr.MinMHzFSB)); DebugPrint((TRACE," Reserved7: %i\n", longhaul_msr.Reserved7)); } VOID DebugShowCurrent() /*++ Routine Description: Show some information about the current state of the CPU. Arguments: None Return Value: None --*/ { ULONG freq; DebugPrint((TRACE,"Reading current CPU power state\n")); CalculateCpuFrequency( &freq ); DebugPrint((TRACE," running at %iMhz\n", freq)); if( SUPPORTS_MSR ) { DebugShowPossibleLongHaulMSR(); } else { DebugPrint((ERROR," Unknown CPU.\n")); } } #endif // DBG VOID TransitionRoutine ( IN PDEVICE_OBJECT DeviceObject, IN PVOID Context ) /*++ Routine Description: Run by a system worker thread as a work item. Arguments: DeviceObject - Device object Context - Pointer to ACPI_PSS_DESCRIPTOR for the state to transition to Return Value: None --*/ { ULONG Throttle; ULONG OldThrottle; ULONG newState; PFDO_DATA DeviceExtension; DebugEnter(); DeviceExtension = (PFDO_DATA) DeviceObject->DeviceExtension; do { // Get the next state Throttle = NextTransitionThrottle; DebugAssert( INVALID_PERF_STATE > 100 ); // Check // Run through the performance states looking for one // that matches this throttling level. for (newState = 0; newState < DeviceExtension->PerfStates->Count; newState++) { if (DeviceExtension->PerfStates->State[newState].PercentFrequency <= Throttle) { DebugPrint((TRACE, "TransitionRoutine found match. PerfState = %u, Freq %u%%\n", newState, DeviceExtension->PerfStates->State[newState].PercentFrequency)); break; } } if( newState >= DeviceExtension->PerfStates->Count ) { DebugAssert( !"Invalid throttle or perf state not found" ); return; } // Convert index in PerfStates to index in PssPackage newState = newState + DeviceExtension->PpcResult; TransitionToState( &(DeviceExtension->PssPackage->State[newState]) ); // If NextTransitionThrottle is still Throttle then set // NextTransitionThrottle to INVALID_PERF_STATE and exit. // If NextTransitionThrottle is a different value from the top // of this do...while then loop to perform transition to that throttle. OldThrottle = InterlockedCompareExchange( &NextTransitionThrottle, // pointer to data that will be read, compared, and optionly changed INVALID_PERF_STATE, // data that will be written if NextTransitionThrottle=Throttle Throttle ); // Throttle we just transitioned to. Check if NextTransitionThrottle still has this value DebugAssert( OldThrottle!=INVALID_PERF_STATE ); // OldThrottle could only be INVALID_PERF_STATE here if another thread in TransitionRoutine had cleared it, QueueTransition is designied to prevent that happening. } while( OldThrottle!=Throttle ); IoFreeWorkItem((PIO_WORKITEM)Context); DebugExit(); } NTSTATUS QueueTransition( IN PFDO_DATA DeviceExtension, IN ULONG NewState ) /*++ Routine Description: Setup a transition to be executed by a system worker thread and return. Needed because the transition code must be run at IRQL < DISPATCH_LEVEL. Creates a new queued work item if NextTransitionThrottle was INVALID_PERF_STATE. Arguments: DeviceExtension - Device object NewState - Number of the state to transition to in the PerfStates table Return Value: NTSTATUS - success if transition was successfully completed --*/ { PIO_WORKITEM pWorkItem; ULONG old_next_transition; DebugAssert( NewState < DeviceExtension->PerfStates->Count ); old_next_transition = InterlockedExchange( &NextTransitionThrottle, DeviceExtension->PerfStates->State[NewState].PercentFrequency ); if ( old_next_transition == INVALID_THROTTLE ) { DebugPrint((TRACE,"No worker thread item was in the queue. Making new work item to transition to %i%%.\n", DeviceExtension->PerfStates->State[NewState].PercentFrequency)); pWorkItem = IoAllocateWorkItem( DeviceExtension->Self ); IoQueueWorkItem( pWorkItem, TransitionRoutine, DelayedWorkQueue, pWorkItem ); } else { DebugPrint((TRACE,"Worker thread item was going to transition to %i%%. Now going to %i%%.\n", old_next_transition, DeviceExtension->PerfStates->State[NewState].PercentFrequency)); } return STATUS_SUCCESS; } ULONG CalcNextVoltageStep( IN ULONG VidFinal, IN ULONG VidCurrent ) /*++ Routine Description: Calculate the next safe vid. Part of a hack that only changes voltage in MAX_V_STEP (mV) steps. Arguments: vidFinal - Final goal vid(index into LhSoftVid) vidCurrent - Current vid Return Value: Next vid which the CPU can change to. --*/ { ULONG pos; // position in the LhSoftVidSort table if( (!NEEDS_VOLTAGE_STEPPING) || VidFinal==VidCurrent || LhSoftVid[VidFinal]==LhSoftVid[VidCurrent]) { return VidFinal; } // Search for vidCurrent in LhSoftVidSort pos = 0; while( pos LhSoftVid[VidCurrent] ) { // need to move pos backwards in LhSoftVidSort DebugAssert( pos != 0 ); // can't be first in table unless it is highest do { --pos; if( pos == 0 ) { break; } } while( LhSoftVidSort[pos] != VidFinal && LhSoftVid[LhSoftVidSort[pos]]-LhSoftVid[VidCurrent] < MAX_V_STEP ); DebugAssert( LhSoftVid[LhSoftVidSort[pos]]-LhSoftVid[VidCurrent] <= MAX_V_STEP ); } else{ // Need to move pos forwards in LhSoftVidSort DebugAssert( LhSoftVid[VidFinal] < LhSoftVid[VidCurrent] ); DebugAssert( pos+1 < LhSoftVidSize ); do { ++pos; } while( pos+1> 4; // Write the MS bit of the BR DebugPrint((TRACE,"changing to fid=%i(%i/%i)\n", Fid, LH_SOFTBR[Fid], BR_MULT )); } if( EnableVid ) { DebugAssert( SUPPORTS_SOFTVID ); msr.EnableSoftVID = 1; msr.SoftVID = Vid; DebugPrint((TRACE,"changing to vid=%i(%imV)\n", Vid, LhSoftVid[Vid] )); } // Raise the IRQL to mask off all interrupts except the timer KeRaiseIrql(CLOCK1_LEVEL-1, &irql); DebugPrint((TRACE,"Raised IRQL from %i to %i\n", irql, KeGetCurrentIrql())); WriteMSR(MSR_LONGHAUL_ADDR, msr.AsQWord); // Halt twice to guarantee that second halt lasts a full 1 ms before the timer interrupt // Possible optimization!! use a timer to see if the first halt last long enough to meet // the hardware specs DebugPrint((TRACE,"Calling halt\n")); _asm { hlt hlt } // Clean up as needed msr.AsQWord = ReadMSR(MSR_LONGHAUL_ADDR); msr.RevisionKey = msr.RevisionID; // Need to copy to RevisionKey for each write msr.EnableSoftBusRatio = 0; msr.EnableSoftVID = 0; WriteMSR(MSR_LONGHAUL_ADDR, msr.AsQWord); // Bring the IRQL back down to its previous value KeLowerIrql(irql); DebugPrint((TRACE,"Restored IRQL. Now %i\n", KeGetCurrentIrql())); } NTSTATUS TransitionToState( IN PACPI_PSS_DESCRIPTOR Pss ) /*++ Routine Description: Perform the state transition now and return success or not. Arguments: Pss - pointer to the ACPI_PSS_DESCRIPTOR of the state to transition to Return Value: NTSTATUS - success if transition was successfully completed --*/ { ULONG set_timer_result; PSS_CONTROL pssControl; ULONG vidNext; LONGHAUL_MSR msr_longhaul; DebugEnter(); // Copy state control bits pssControl.AsDWord = Pss->Control; DebugAssert( KeGetCurrentIrql() < DISPATCH_LEVEL ); // Callers of ExSetTimerResolution must be running at IRQL < DISPATCH_LEVEL do { // Change the timer resolution to the smallest safe amount, 1ms // Since we are at a low irql another thread may have changed it so // set it each time we call TransitionNow set_timer_result = ExSetTimerResolution (10032,TRUE); // Set timer to 1ms in 100ns units, fudged to what ExSetTimerResolution normally returns DebugPrint((TRACE,"Set the timer, returned value %lu\n", set_timer_result)); // Read the current state of the msr msr_longhaul.AsQWord = ReadMSR(MSR_LONGHAUL_ADDR); DebugPrint((TRACE,"current vid=%i(%imV) fid=%i(%i)\n", msr_longhaul.SoftVID, LhSoftVid[msr_longhaul.SoftVID], (msr_longhaul.SoftBusRatio4<<4) + msr_longhaul.SoftBusRatio0, LH_SOFTBR[msr_longhaul.SoftBusRatio4<<4 | msr_longhaul.SoftBusRatio0] )); if( pssControl.EnableVid ) { vidNext = CalcNextVoltageStep(pssControl.Vid,msr_longhaul.SoftVID); if( LhSoftVid[vidNext] >= LhSoftVid[pssControl.Vid] && pssControl.EnableFid ) { // Can set the freq and voltage DebugPrint((TRACE," vidNext=%i(%imV) pssControl.Vid=%i(%imV)\n", vidNext, LhSoftVid[vidNext], pssControl.Vid, LhSoftVid[pssControl.Vid] )); TransitionNow( pssControl.Fid, TRUE, vidNext, TRUE ); } else { // only set the voltage TransitionNow( RESV, FALSE, vidNext, TRUE ); } } else { DebugAssert( pssControl.EnableFid ); // every state should have EnableVid or EnableFid // No voltage transition TransitionNow( pssControl.Fid, TRUE, RESV, FALSE ); } }while( pssControl.EnableVid && pssControl.Vid != vidNext ); // Reset the timer to its default value set_timer_result = ExSetTimerResolution (0, FALSE); DebugPrint((TRACE,"Reset the timer, returned value %lu\n", set_timer_result)); DebugExit(); return STATUS_SUCCESS; } VOID MeasureFSBFreq() /*++ Routine Description: Measure the frequency the front side bus is running by setting the bus ratio to a known safe value and timing the core frequency. Value is put in global FsbFreq. Arguments: None Return Value: None --*/ { PSS_CONTROL control; ACPI_PSS_DESCRIPTOR pss; ULONG freq; // Set the BR to a known, safe, constant control.EnableFid = 1; control.Fid = LH_SOFTBR_MIN; control.EnableVid = 0; control.Vid = 0; pss.Control = control.AsDWord; TransitionToState(&pss); // Time the fsb CalculateCpuFrequency( &freq ); FsbFreq = freq * BR_MULT / LH_SOFTBR[LH_SOFTBR_MIN]; DebugPrint((TRACE, "Set BR to %i(%i.%i), measured CPU at %iMHz, meaning FSB at %iMHz\n", LH_SOFTBR_MIN, LH_SOFTBR[LH_SOFTBR_MIN]/BR_MULT, (100/BR_MULT) * (LH_SOFTBR[LH_SOFTBR_MIN]%BR_MULT), freq, FsbFreq)); } ULONG CalcMaxFreq( IN ULONG V, IN ULONG Vmin, IN ULONG Fmin, IN ULONG Vmax, IN ULONG Fmax ) /*++ Routine Description: Calculate the maximum operating frequency allowed at a particular voltage given parameters from the LongHaul MSR. Picks the max frequency that is below a line formed between two points on a voltage vs. frequency graph. The two points are (Vmin,Fmin) and (Vmax,Fmax) Arguments: V - The voltage at which you want to know the max freq Vmin - Min allowed voltage Fmin - Max allowed frequency Vmax - Max allowed voltage Fmax - Min allowed frequency Return Value: The maximum operating frequency at a specific voltage. --*/ { return ((Fmax-Fmin)*(V-Vmin) + Fmin*(Vmax-Vmin)) / (Vmax-Vmin); } NTSTATUS FindAcpiPerformanceStatesLongHaul( OUT PACPI_PSS_PACKAGE* Pss ) /*++ Routine Description: Build a new pss containing all the available states on this CPU. Only call if supports_longhaul_msr and supports_softBR. Function will either succeed and return STATUS_SUCCESS or fail and not touch pPss. Arguments: Pss - Reference to a pointer to a pss. The pointer must point to NULL when this function is called. Return Value: NT status code --*/ { LONGHAUL_MSR longhaul_msr; PACPI_PSS_PACKAGE tmpPss; ULONG iPssSize; ULONG vidStart; // index in LhSoftVidSort of the greatest possible vid ULONG vidEnd; // index+1 in LhSoftVidSort of the smallest possible vid ULONG fidStart; // index in LH_SOFTBR_SORT of the greatest possible fid ULONG fidEnd; // index+1 in LH_SOFTBR_SORT of the smallest possible fid ULONG vid; ULONG fid; ULONG freq; // temp var used for frequencies ULONG numPStates; ULONG pssState; DebugAssert( Pss ); DebugAssert( *Pss == NULL ); DebugAssert( SUPPORTS_MSR && SUPPORTS_SOFTBR ); // Must support the LongHaul MSR and softBR MeasureFSBFreq(); longhaul_msr.AsQWord= ReadMSR(MSR_LONGHAUL_ADDR); if( SUPPORTS_SOFTVID ) { // Search for the greatest vid vidStart = 0; while( vidStart < LhSoftVidSize && LhSoftVidSort[vidStart] != longhaul_msr.MaximumVID ) { ++vidStart; } DebugAssert( vidStart < LhSoftVidSize ); // search for the smallest vid, which should come after vidStart // set vidEnd to one more than that vidEnd = vidStart; while( vidEnd < LhSoftVidSize && LhSoftVidSort[vidEnd] != longhaul_msr.MinimumVID ) { ++vidEnd; } DebugAssert( vidEnd < LhSoftVidSize ); ++vidEnd; // Find the max freq at min volts in LH_SOFTBR_SORT freq = LH_MIN_BR[longhaul_msr.MinMHzBR4<<4 | longhaul_msr.MinMHzBR0] * LH_FSB[longhaul_msr.MinMHzFSB]; // freq is MHz*BR_MULT fidStart = 0; while( fidStart < LH_SOFTBR_SIZE && LH_SOFTBR_SORT[fidStart] != RESV && LH_SOFTBR[LH_SOFTBR_SORT[fidStart]] > (freq / FsbFreq) ) { ++fidStart; } if( fidStart == LH_SOFTBR_SIZE || LH_SOFTBR_SORT[fidStart] == RESV) { DebugAssert( fidStart > 0 ); --fidStart; } DebugPrint((TRACE, " vidStart: %u(%u,%u) vidEnd-1: %u(%u,%u)\n", vidStart, LhSoftVidSort[vidStart], LhSoftVid[LhSoftVidSort[vidStart]], vidEnd-1, LhSoftVidSort[vidEnd-1], LhSoftVid[LhSoftVidSort[vidEnd-1]] )); } else { // no vids vidStart = vidEnd = 0; // Find top fid, the max freq freq = LH_MAX_BR[longhaul_msr.MaxMHzBR4<<4 | longhaul_msr.MaxMHzBR0] * LH_FSB[longhaul_msr.MaxMHzFSB]; fidStart = 0; while( fidStart < LH_SOFTBR_SIZE && LH_SOFTBR_SORT[fidStart] != RESV && LH_SOFTBR[LH_SOFTBR_SORT[fidStart]] > freq/FsbFreq ) { ++fidStart; } if( fidStart == LH_SOFTBR_SIZE || LH_SOFTBR_SORT[fidStart] == RESV) { DebugAssert( fidStart > 0 ); --fidStart; } } fidEnd = fidStart; // Set fidEnd to index+1 in the LH_SOFTBR_SORT table of the lowest possible // BR at the lowest possible voltage while( fidEnd < LH_SOFTBR_SIZE && LH_SOFTBR_SORT[fidEnd] != RESV && LH_SOFTBR[LH_SOFTBR_SORT[fidEnd]] >= ABSOLUTE_MIN_FREQ/FsbFreq*BR_MULT) { ++fidEnd; } DebugAssert( fidStart <= fidEnd ); // Last fid to go in PSS must be slower than first fid, or they are the same for no freq only states DebugAssert( LH_SOFTBR_SORT[fidEnd-1]!=RESV || fidStart==fidEnd); DebugAssert( LH_SOFTBR[LH_SOFTBR_SORT[fidEnd-1]]>=(ABSOLUTE_MIN_FREQ/FsbFreq*BR_MULT) || fidStart==fidEnd); DebugPrint((TRACE, " fidStart: %u(%u,%u) fidEnd-1: %u(%u,%u)\n", fidStart, LH_SOFTBR_SORT[fidStart], LH_SOFTBR[LH_SOFTBR_SORT[fidStart]], fidEnd-1, LH_SOFTBR_SORT[fidEnd-1], LH_SOFTBR[LH_SOFTBR_SORT[fidEnd-1]] )); numPStates = fidEnd - fidStart; if( vidStart != vidEnd ) { // Have some vid states, add them without double counting the state that is both a vid and fid numPStates += vidEnd - vidStart - 1; } iPssSize = (sizeof(ACPI_PSS_DESCRIPTOR) * (numPStates - 1) ) + sizeof(ACPI_PSS_PACKAGE); tmpPss = ExAllocatePoolWithTag(NonPagedPool, iPssSize, PROCESSOR_POOL_TAG); if( ! tmpPss ) { return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(tmpPss, iPssSize); tmpPss->NumPStates = (UCHAR) numPStates; pssState = 0; if( SUPPORTS_SOFTVID ) { // Add the voltage states DebugAssert( vidStart != vidEnd ); // must have some states for( vid = vidStart; vid+1 < vidEnd; ++vid ) { // Last vid state is top fid state PSS_CONTROL pssControl = {0}; PSS_STATUS pssStatus = {0}; freq = CalcMaxFreq( LhSoftVid[LhSoftVidSort[vid]], LhSoftVid[longhaul_msr.MinimumVID], LH_MIN_BR[longhaul_msr.MinMHzBR4<<4 | longhaul_msr.MinMHzBR0] * LH_FSB[longhaul_msr.MinMHzFSB], LhSoftVid[longhaul_msr.MaximumVID], LH_MAX_BR[longhaul_msr.MaxMHzBR4<<4 | longhaul_msr.MaxMHzBR0] * LH_FSB[longhaul_msr.MaxMHzFSB] ); // freq is in MHz * BR_MULT DebugPrint((TRACE, "Max freq at %i volts: %i\n", LhSoftVid[LhSoftVidSort[vid]], freq/BR_MULT)); fid = 0; while( fid < LH_SOFTBR_SIZE && LH_SOFTBR_SORT[fid] != RESV && LH_SOFTBR[LH_SOFTBR_SORT[fid]] > freq/FsbFreq ) { ++fid; } DebugAssert( fid < LH_SOFTBR_SIZE ); DebugAssert( LH_SOFTBR[LH_SOFTBR_SORT[fid]] != RESV ); pssStatus.Fid = pssControl.Fid = LH_SOFTBR_SORT[fid]; pssControl.EnableFid = 1; pssStatus.Vid = pssControl.Vid = LhSoftVidSort[vid]; pssControl.EnableVid = 1; tmpPss->State[pssState].Control = pssControl.AsDWord; tmpPss->State[pssState].Status = pssStatus.AsDWord; tmpPss->State[pssState].CoreFrequency = FsbFreq * LH_SOFTBR[LH_SOFTBR_SORT[fid]] / BR_MULT; tmpPss->State[pssState].Power = LhSoftVid[LhSoftVidSort[vid]]; tmpPss->State[pssState].Latency = DEFAULT_LATENCY_US; DebugPrint((TRACE, "Adding state[%i]: Control:%x Status:%x CoreFreq:%i Power:%i V:%i F:%i/%i\n", pssState, tmpPss->State[pssState].Control, tmpPss->State[pssState].Status, tmpPss->State[pssState].CoreFrequency, tmpPss->State[pssState].Power, LhSoftVid[LhSoftVidSort[vid]], LH_SOFTBR[LH_SOFTBR_SORT[fid]], BR_MULT)); ++pssState; } DebugAssert(vid == vidEnd-1); // all fid states are at low volts for( fid = fidStart; fid < fidEnd; ++fid ) { PSS_CONTROL pssControl = {0}; PSS_STATUS pssStatus = {0}; pssStatus.Fid = pssControl.Fid = LH_SOFTBR_SORT[fid]; pssControl.EnableFid = 1; pssStatus.Vid = pssControl.Vid = LhSoftVidSort[vidEnd-1]; pssControl.EnableVid = 1; tmpPss->State[pssState].Control = pssControl.AsDWord; tmpPss->State[pssState].Status = pssStatus.AsDWord; tmpPss->State[pssState].CoreFrequency = FsbFreq * LH_SOFTBR[LH_SOFTBR_SORT[fid]] / BR_MULT; tmpPss->State[pssState].Power = LhSoftVid[LhSoftVidSort[vid]]; tmpPss->State[pssState].Latency = DEFAULT_LATENCY_US; DebugPrint((TRACE, "Adding state[%i]: Control:%x Status:%x CoreFreq:%i Power:%i V:%i F:%i/%i\n", pssState, tmpPss->State[pssState].Control, tmpPss->State[pssState].Status, tmpPss->State[pssState].CoreFrequency, tmpPss->State[pssState].Power, LhSoftVid[LhSoftVidSort[vid]], LH_SOFTBR[LH_SOFTBR_SORT[fid]], BR_MULT)); ++pssState; } } else { for( fid = fidStart; fid < fidEnd; ++fid ) { PSS_CONTROL pssControl = {0}; PSS_STATUS pssStatus = {0}; pssStatus.Fid = pssControl.Fid = LH_SOFTBR_SORT[fid]; pssControl.EnableFid = 1; pssControl.EnableVid = 0; tmpPss->State[pssState].Control = pssControl.AsDWord; tmpPss->State[pssState].Status = pssStatus.AsDWord; tmpPss->State[pssState].CoreFrequency = FsbFreq * LH_SOFTBR[LH_SOFTBR_SORT[fid]] / BR_MULT; tmpPss->State[pssState].Power = POWER_V; tmpPss->State[pssState].Latency = DEFAULT_LATENCY_US; DebugPrint((TRACE, "Adding state[%i]: Control:%x Status:%x CoreFreq:%i Power:%i F:%i/%i\n", pssState, tmpPss->State[pssState].Control, tmpPss->State[pssState].Status, tmpPss->State[pssState].CoreFrequency, tmpPss->State[pssState].Power, LH_SOFTBR[LH_SOFTBR_SORT[fid]], BR_MULT)); ++pssState; } } *Pss = tmpPss; return STATUS_SUCCESS; } NTSTATUS InitializeNonAcpiPerformanceStates( IN PFDO_DATA DeviceExtension ) /*++ Routine Description: Create a PSS table for the legacy CPU states. Arguments: DeviceExtension - pointer to the device extension Return Value: NTSTATUS - NT status code --*/ { NTSTATUS status = STATUS_SUCCESS; DebugEnter(); PAGED_CODE(); if ( !(SUPPORTS_MSR && SUPPORTS_SOFTBR) ) { DebugPrint((ERROR,"CPU not supported.\n")); status = STATUS_NOT_SUPPORTED; goto InitializeNonAcpiPerformanceStatesExit; } DeviceExtension->CurrentPerfState = INVALID_PERF_STATE; DeviceExtension->LegacyInterface = TRUE; // Set up _PCT DeviceExtension->PctPackage.Control.AddressSpaceID = AcpiGenericSpaceFixedFunction; DeviceExtension->PctPackage.Status.AddressSpaceID = AcpiGenericSpaceFixedFunction; status = FindAcpiPerformanceStatesLongHaul(&(DeviceExtension->PssPackage)); if (!NT_SUCCESS(status)) { goto InitializeNonAcpiPerformanceStatesExit; } // Need to merge this new data with our perfstates status = MergePerformanceStates(DeviceExtension); InitializeNonAcpiPerformanceStatesExit: if (!NT_SUCCESS(status)) { if (DeviceExtension->PssPackage) { // Need to undo what has been done ExFreePool(DeviceExtension->PssPackage); DeviceExtension->PssPackage = NULL; } DeviceExtension->LegacyInterface = FALSE; } DebugExitStatus(status); return status; } NTSTATUS AcpiLegacyPerfStateTransition( IN PFDO_DATA DeviceExtension, IN ULONG State ) /*++ Routine Description: Perform a transition using the FFH on LongHaul viac3 chips Arguments: DeviceExtension - pointer to the device extension State - Target State Return Value: NT Status --*/ { return Acpi2PerfStateTransition(DeviceExtension, State + DeviceExtension->PpcResult); } NTSTATUS GetLegacyMaxProcFrequency( OUT PULONG CpuSpeed ) /*++ Routine Description: Don't use Arguments: CpuSpeed - pointer to a ULONG Return Value: NTSTATUS - Always returns STATUS_NOT_FOUND --*/ { TRAP(); return STATUS_NOT_FOUND; }