CPU core operating frequency

Is it possible to obtain the current CPU core operating frequency from a .NET application?

I would like to investigate whether the operation is restricted as the temperature rises.

Supplement

At the current frequency, it is difficult to judge whether or not restriction is applied.
It is best to be able to acquire the maximum value or the limit value of the operating frequency.

Dear @kyas

The actual CPU reading is simple, but unfortunately there’s quite some definitions and initialization required to make it work.
I collected all the information below, but didn’t test the code, so there might be some typos or other errors. The code does not have any error checking to keep it simple.

Here’s how you can do it in native c:

#include <windows.h>
//====== Definitions of types and variables ======

// Define return type
typedef struct NvRmDfsClockUsageRec
{
    /// Minimum clock domain frequency.
    DWORD MinKHz;

    /// Maximum clock domain frequency.
    DWORD MaxKHz;

    /// Low corner frequency - current low boundary for DFS control algorithm.
    /// Can be dynamically adjusted via APIs: NvRmDfsSetLowCorner() for all DFS
    /// domains, NvRmDfsSetCpuEnvelope() for CPU, and NvRmDfsSetEmcEnvelope()
    /// for EMC. When all DFS domains hit low corner, DFS stops waking up CPU
    ///  from low power state.
    DWORD LowCornerKHz;

    /// High corner frequency - current high boundary for DFS control algorithm.
    /// Can be dynamically adjusted via APIs: NvRmDfsSetCpuEnvelope() for Cpu,
    /// NvRmDfsSetEmcEnvelope() for Emc, and NvRmDfsSetAvHighCorner() for other
    //  DFS domains.
    DWORD HighCornerKHz;

    /// Current clock domain frequency
    DWORD CurrentKHz;

    /// Average frequency of domain *activity* (not average frequency). For
    /// domains that do not have activity monitors reported as unspecified.
    DWORD AverageKHz;
} NvRmDfsClockUsage;

// Define Function Types
typedef DWORD (*PFNNvRmOpen)                  (DWORD *pHandle, DWORD DeviceId);
typedef void  (*PFNNvRmClose)                 (DWORD hDevice);
typedef DWORD (*PFNNvRmDfsGetClockUtilization)(DWORD hRmDeviceHandle, NvRmDfsClockId ClockId, NvRmDfsClockUsage *pClockUsage);

// Define Function Pointer Variables
static DWORD PFNNvRmOpen                   NvRmOpen;
static void  PFNNvRmClose                  NvRmClose;
static DWORD PFNNvRmDfsGetClockUtilization NvRmDfsGetClockUtilization;

static const DWORD NvRmDfsClockId_Cpu = 1;
static       DWORD g_rm = 0;

//====== Code to read CPU temperature ======
DWORD GetCpuTemperature()
{
    HMODULE           hMod;
    NvRmDfsClockUsage clk_cpu;

    hMod                       = LoadLibrary(L"libnvrm.dll");
    NvRmOpen                   = (PFNNvRmOpen)GetProcAddress(hMod, "NvRmOpen");
    NvRmClose                  = (PFNNvRmClose)GetProcAddress(hMod, "NvRmClose");
    NvRmDfsGetClockUtilization = (PFNNvRmDfsGetClockUtilization)GetProcAddress(hMod, "NvRmDfsGetClockUtilization");

    NvRmOpen(&g_rm, 0);

    // ---- This is the actual temperature reading - everything else is initialization ----
    NvRmDfsGetClockUtilization(g_rm, NvRmDfsClockId_Cpu, &clk_cpu);

    NvRmClose(g_rm);
    return clk_cpu.CurrentKHz;
}

To use it from .NET there are two possible approaches:

  1. PInvoke the 3 functions NvRmOpen, NvRmClose and NvRmDfsGetClockUtilization. You have to port all the definitions to C#.
  2. Create a native DLL containing the C code listed below. PInvoke the resulting function GetCpuTemperature().

Regards, Andy

Dear Andy

Thank you for providing the code.

I tried converting to C # by referring to this code, but seems to work well so far.

// Define Function Types
[System.Runtime.InteropServices.DllImport("libnvrm.dll", EntryPoint = "NvRmOpen")]
public static extern UInt32 NvRmOpen(ref UInt32 pHandle, UInt32 DeviceId);

[System.Runtime.InteropServices.DllImport("libnvrm.dll", EntryPoint = "NvRmClose")]
public static extern UInt32 NvRmClose(UInt32 hDevice);

[System.Runtime.InteropServices.DllImport("libnvrm.dll", EntryPoint = "NvRmDfsGetClockUtilization")]
public static extern UInt32 NvRmDfsGetClockUtilization(UInt32 hRmDeviceHandle, UInt32 ClockId, ref NvRmDfsClockUsage pClockUsage);

public struct NvRmDfsClockUsage
{
    /// Minimum clock domain frequency.
    public UInt32 MinKHz;

    /// Maximum clock domain frequency.
    public UInt32 MaxKHz;

    /// Low corner frequency - current low boundary for DFS control algorithm.
    /// Can be dynamically adjusted via APIs: NvRmDfsSetLowCorner() for all DFS
    /// domains, NvRmDfsSetCpuEnvelope() for CPU, and NvRmDfsSetEmcEnvelope()
    /// for EMC. When all DFS domains hit low corner, DFS stops waking up CPU
    ///  from low power state.
    public UInt32 LowCornerKHz;

    /// High corner frequency - current high boundary for DFS control algorithm.
    /// Can be dynamically adjusted via APIs: NvRmDfsSetCpuEnvelope() for Cpu,
    /// NvRmDfsSetEmcEnvelope() for Emc, and NvRmDfsSetAvHighCorner() for other
    //  DFS domains.
    public UInt32 HighCornerKHz;

    /// Current clock domain frequency
    public UInt32 CurrentKHz;

    /// Average frequency of domain *activity* (not average frequency). For
    /// domains that do not have activity monitors reported as unspecified.
    public UInt32 AverageKHz;

    public void Clear()
    {
        MinKHz = 0;
        MaxKHz = 0;
        LowCornerKHz = 0;
        HighCornerKHz = 0;
        CurrentKHz = 0;
        AverageKHz = 0;
    }
}

private const UInt32 NvRmDfsClockId_Cpu = 1;
private UInt32 g_rm = 0;

//====== Code to read CPU temperature ======
public UInt32 GetCpuTemperature()
{
    NvRmDfsClockUsage clk_cpu = new NvRmDfsClockUsage();
    clk_cpu.Clear();

    NvRmOpen(ref g_rm, 0);

    // --- This is the actual temperature reading - everything else is initialization ---
    NvRmDfsGetClockUtilization(g_rm, NvRmDfsClockId_Cpu, ref clk_cpu);

    NvRmClose(g_rm);
    return clk_cpu.CurrentKHz;
}

output

MinKHz       :40000
MaxKHz       :1300000
LowCornerKHz :216000
HighCornerKHz:1300000
CurrentKHz   :380273
AverageKHz   :336724

I have a question about the contents of the output.

(a)MaxKHz is output as 1300000. Why is it different from 1.4 GHz of Apalis T30?

(b)Apalis T30 has heard that the operating frequency is limited to 800MHz when the core temperature exceeds 60°C.
In this case, is it correct with the recognition that CurrentKHz or AverageKHz is output as maximum 800000?

Dear @kyas

(a) The statement from the Apalis T30 datasheet is:

The Cortex A9 quad core CPU peaks up
to 1.4 GHz Single Core / 1.3 GHz Quad
Core.

This means that the T30 can run at 1.4GHz only if one core is active. We don’t support this single-core-1.4GHz mode in WinCe.


(b) I’m not sure whether I did understand your question correctly. I hope the following explanations are sufficient:

  • CurrentKHz represents a momentary snapshot of the CPU frequency
  • AverageKHz represents CurrentKHz, averaged over a time of several hundred milliseconds.

If the two figures are at 800 MHz or below, it means that the CPU is working at reduced speed, either because the lower frequency is still high enough to provide the currently required performance, or because the CPU is too hot and therefore needs the limitation.
There is no way to tell from the frequency readings, which of the two reasons caused the frequency limitation.

Regards, Andy