美文网首页
读取CPU核心温度

读取CPU核心温度

作者: 十八砖 | 来源:发表于2018-09-07 16:29 被阅读123次

    将多年前的一个小驱动搬到简书~

    CPU温度简述

    最近在搞一个读取CPU温度的驱动,网上翻了好多资料,可发现全是copy的,原稿也就两三篇,可经实践发现其中不乏错误与片面,让人着实走弯路,燃起了我要总结一番的欲望。

    这个驱动搞了一个多星期,总算可以运行了,测试了几台Intel和AMD的机器也都测试通过,测试对比用的是CPUID HWMonitor和Core Temp。

    Intel和AMD的CPU中都有温度传感器(DTS),每个核心都有一个,温度就是由此获取来的,多核cpu可以使用 SetProcessAffinityMask API 来指定执行的CPU。

    首先是利用CPUID来区分是Intel型号还是AMD型号,利用汇编和函数都可实现,考虑到64位系统不支持嵌入汇编,所以还是直接利用API函数就行。

    CPUID其实就是对eax执行cpuid指令,返回信息储存在eax,ebx,ecx,edx中,令eax=0,可将CPU厂商信息返回在ebx,ecx,edx中,

        int CPUInfo[4];
    
        __cpuid(CPUInfo,0); 
    

    Intel信息字符串为GenuineIntel,AMD为AuthenticAMD,只判断前4个字符就可以,只需与CPUInfo[1](ebx)比较就可得出型号。

    Intel

    接下来说如何获取温度,先从简单的说起,Intel实现起来比较简单:

    先以eax=0 执行 cpuid 检测 eax 支持的最大命令数,如果小于6就肯定不支持DTS。然后以eax=6 执行 cpuid, 然后测试 eax 第一位是否为1,如果为1表示CPU支持DTS。

    读取DTS:以 ecx=0x1A2 执行 rdmsr 指令, 测试 eax 的第30位是否为 1, 如果为 1 表示温度计算的初始值为 85 度否则表示从100度开始计算,这个值称为 Tjunction.

    eax=__readmsr(0x01A2)

    然后以 ecx=0x19c 执行 rdmsr 指令, eax 的 16-23 位为表示当前DTS 值,当前温度要以下面公式计算.
    当前cpu温度 = Tjunction - DTS

    注意 signature 为 0x6f1, 0x6f0的 CPU DTS 值直接代表当前温度而不用Tjunction 相减. 而 signature 小于等于 0x6f4 的 Tjunction 一直为100。

    AMD

    AMD就比较恶心了,研究了挺长时间:

    AMD温度存储在NB寄存器中,这是一个热传感寄存器。AMD的CPU分为K8和K10,K8的温度存储在这个寄存器的23-14位,K10的在31-21位。

    要访问这个状态寄存器,需要对PCI进行读写。
    先介绍俩个PCI用到的寄存器,CF8h和CFCh
    CF8h: 存放配置空间的地址(CONFIG-ADDRESS)
    CFCh: 保存配置空间的读写数据(CONFIG-DATA)
    这两个空间对应于PCI桥路的两个寄存器,当桥路看到CPU在局部总线对这两个 I/O空间进行双字操作时,就将该I/O操作转变为PCI总线的配置操作。

    温度读取:
    如果是K8的话,可以忽略低俩位,读取23-16就可以了,当然也可以读23-14,然后\4或者>>2;
    如果是K10的话,那就读取31-21

    如何判断K8,K10:

    __cpuid(CPUInfo,1); //cpuid执行1,取出eax
    t=CPUInfo[0];
    
    family=((t>>20)&0xFF) + ((t>>8)&0xF);
    model=((t>>12)&0xF0) + ((t>>4)&0xF);
    stepping=t&0xF;
    
    如果Family ==0xf 而除了
                   (((model == 4) && (stepping == 0)) ||
                        ((model == 5) && (stepping <= 1)))
    则为K8
    如果Family > 0xf,一般是G。那就是K10
    

    温度的计算公式:

    K8 Temp = Value - 49'.   49这个值需要修正的:if (model >= 0x69 && model != 0xc1 && model != 0x6c 
    && model != 0x7c)  temp=Value-49+21;
    K10 Temp = Value / 8'.
    

    IO访问PCI总线设备配置空间:
    配置空间地址寄存器的格式:
    31  24 23   16 15     11 10      8 7      2 1  0
    | reserve | bus number | device number | function number | register number | 0 | 1/0 |

    所以知道 bus number, device number, function number, register number后,可以这么来构造配置空间地址寄存器:
    IOADDR = 0x80000000+bus*0x10000 +(device*8)*0x100 + uFunction&0x07 + register number&~3;
    为什么需要0x80000000呢,因为
    当CPU发出对I/O空间CFCh的操作时,PCI桥路将检查配置空间地址寄存器CF8h的31位。如果为1,就在PCI总线上产生一个相应的配置空间读或写操作,0x80000000就是使配置空间地址寄存器为1。
    经过上面的讨论后,可以写成

    #define DeviceSlot(uDevice, uFunction) ((((uDevice)&0x1f)<<3)|((uFunction)&0x07))
    #define GetDevice(uBus,uSlot,uAddress) (0x80000000L |((uBus&0xff)<<16)|(uSlot<<8)|(uAddress&~3));
    

    这样知道 uBus, uDevice, uFunction, uAddress后就可以通过IO指令来读写了。

    对于K8, uAddress为0xE4,对于K10 uAddress为0xA4

    怎样获取uBus, uDevice, uFunction:
    从上面知道GetDevice需要 uBus, uDevice, uFunction的。
    可以扫描PCI总线来获取,对于AMD K8来说,设备ID为0x1103,对于K10来说,设备ID为0x1203。 二者的uFunction都为3.
    通过扫描PCI总线,匹配设备ID来获取。

    BOOL get_bus_dev( int devieid,int *BUS, int *DEV ) //遍历PCI得到bus和dev
    {
    
    ULONG bus;
    ULONG dev;
    ULONG func=3; //K8 K10 fun为3
    unsigned long Size;
    PCI_COMMON_CONFIG PciConfig;
    PCI_SLOT_NUMBER SlotNumber;
    
    for(bus = 0; bus <= 255; ++bus) 
    {
    for(dev = 0; dev <= 31; ++dev) 
    {
    SlotNumber.u.AsULONG = 0;
    SlotNumber.u.bits.DeviceNumber = dev;
    SlotNumber.u.bits.FunctionNumber = func;
    RtlZeroMemory(&PciConfig, sizeof(PCI_COMMON_CONFIG));
    
    Size = HalGetBusData(PCIConfiguration,
    bus,
    SlotNumber.u.AsULONG,
    &PciConfig,
    PCI_COMMON_HDR_LENGTH); //API函数
    
    if (Size==PCI_COMMON_HDR_LENGTH)
    {
    if ( devieid==PciConfig.DeviceID )
    {
    *BUS=bus;
    *DEV=dev;
    DbgPrint("BUS:%d \n",bus);
    DbgPrint("DEV:%d \n",dev);
    return TRUE;
    }   
    }   
    }
    }
    
    return FALSE;
    }
    

    然后进行IO读写就可以获取温度了,K8:

    static once =1;
    
    if (once)
    {
    int bus,dev,slot;
    if ( !get_bus_dev(0x1103,&bus,&dev) )
    {
    DbgPrint("获取BUS、DEV失败! \n");
    return;
    }
    
    slot=DeviceSlot(dev,0x3);  //上面定义的宏
    IO_ADDRE=GetDevice(bus,slot,0xE4);  //上面定义的宏
    
    once=0;
    }
    
    _outpd(0xCF8,IO_ADDRE);//端口读写
    CPUTemp=_inpd(0xCFC);//端口读写
    
    
    CPUTemp=(CPUTemp>>16)&0xFF;
    CPUTemp=CPUTemp - g_Offset;//g_Offset为49-21
    
    DbgPrint("CPUTemp: %d \n",CPUTemp);
    

    相关文章

      网友评论

          本文标题:读取CPU核心温度

          本文链接:https://www.haomeiwen.com/subject/abjcgftx.html