intel core 2 duo温度读取
1 目的:实现intel core 2 duo cpu的温度检测
2 分析:
对于intel处理器,每个处理器核心有Digital Thermal Sensors(DTS 数字敏感传感器),该数字传感器的检测值不依赖于外围电路,仅仅取决于 cpu核心的热度,其取值存储于cpu上的一个特殊寄存器中,该寄存器称为Model Specific Register 0x19c(MSR),可以通过rdmsr指令读取,内核代码coretemp.c文件实现了该文件的读取,插入coretemp.ko模块后,可以通过/sys/devices/platform/coretemp.0/目录下的temp1_input、temp2_input等文件读取,温度值基本与lm-sensors读取值一致(lm-sensors基于该内核驱动模块实现的)。
3 hwmon/coretemp.c文件导读,注:该检测文件的对应文档信息在/documation/hdware/coretemp文件下
3.1 前期工作
对于intel core 2 duo cpu而言,coretemp.c中包含特定架构的头文件为x86/include/asm/process.h
在process.h中定义了cpu_data数据结构:
1 DECLARE_PER_CPU(struct cpuinfo_x86, cpu_info); 2 #define cpu_data(cpu) per_cpu(cpu_info, cpu);
所以cpu_data(0)即为第1个cpu对应的struct cpuinfo_x86结构,该结构定义如下:
1 /* 2 * CPU type and hardware bug flags. Kept separately for each CPU. 3 * Members of this structure are referenced in head.S, so think twice 4 * before touching them. [mj] 5 */ 6 注:该结构应该可以通过cat /proc/cpuinfo来获取 7 struct cpuinfo_x86 { 8 __u8 x86; /* CPU family */ == X86 9 __u8 x86_vendor; /* CPU vendor */ == X86_VENDOR_INTEL 10 __u8 x86_model; == 6,14,15,22,23,26 11 __u8 x86_mask; 12 #ifdef CONFIG_X86_32 13 char wp_works_ok; /* It doesn't on 386's */ 14 15 /* Problems on some 486Dx4's and old 386's: */ 16 char hlt_works_ok; 17 char hard_math; 18 char rfu; 19 char fdiv_bug; 20 char f00f_bug; 21 char coma_bug; 22 char pad0; 23 #else 24 /* Number of 4K pages in DTLB/ITLB combined(in pages): */ 25 int x86_tlbsize; 26 __u8 x86_virt_bits; 27 __u8 x86_phys_bits; 28 #endif 29 /* CPUID returned core id bits: */ 30 __u8 x86_coreid_bits; 31 /* Max extended CPUID function supported: */ 32 __u32 extended_cpuid_level; 33 /* Maximum supported CPUID level, -1=no CPUID: */ 34 int cpuid_level; 35 __u32 x86_capability[NCAPINTS]; 36 char x86_vendor_id[16]; //genuineIntel 37 char x86_model_id[64]; //intel core 2 duo cpu Txxx 38 /* in KB - valid for CPUS which support this call: */ 39 int x86_cache_size; 40 int x86_cache_alignment; /* In bytes */ 41 int x86_power; 42 unsigned long loops_per_jiffy; 43 #ifdef CONFIG_SMP 44 /* cpus sharing the last level cache: */ 45 cpumask_t llc_shared_map; 46 #endif 47 /* cpuid returned max cores value: */ 48 u16 x86_max_cores; 49 u16 apicid; 50 u16 initial_apicid; 51 u16 x86_clflush_size; 52 #ifdef CONFIG_SMP 53 /* number of cores as seen by the OS: */ 54 u16 booted_cores; 55 /* Physical processor id: */ 56 u16 phys_proc_id; 57 /* Core id: */ 58 u16 cpu_core_id; 59 /* Index into per_cpu list: */ 60 u16 cpu_index; 61 #endif 62 } __attribute__((__aligned__(SMP_CACHE_BYTES)));
3.2 模块插入后,驱动加载原理和检测过程
3.2.1 cpu温度探测驱动加载原理
A 该模块的初始函数如下:
1 static int __init coretemp_init(void) 2 { 3 int i, err = -ENODEV; 4 struct pdev_entry *p, *n; 5 6 /* quick check if we run Intel */ 7 if (cpu_data(0).x86_vendor != X86_VENDOR_INTEL) 8 goto exit; 9 10 err = platform_driver_register(&coretemp_driver); //获取每个cpu核心温度信息的驱动结构,当加载驱动后,在/sys/devices/platform/coretemp.o下建立各个检测文件 11 if (err) 12 goto exit; 13 14 for_each_online_cpu(i) { 15 struct cpuinfo_x86 *c = &cpu_data(i); 16 17 /* check if family 6, models 0xe, 0xf, 0x16, 0x17, 0x1A */ 18 if ((c->cpuid_level < 0) || (c->x86 != 0x6) || 19 !((c->x86_model == 0xe) || (c->x86_model == 0xf) || 20 (c->x86_model == 0x16) || (c->x86_model == 0x17) || 21 (c->x86_model == 0x1A))) { 22 23 /* supported CPU not found, but report the unknown 24 family 6 CPU */ 25 if ((c->x86 == 0x6) && (c->x86_model > 0xf)) 26 printk(KERN_WARNING DRVNAME ": Unknown CPU " 27 "model %x\n", c->x86_model); 28 continue; 29 } 30 31 err = coretemp_device_add(i); //在/sys/devices/platform/coretemp.0下建立coretemp.0与sysfs中其他目录的连接关系,每个cpu对应一个目录,双核cpu对应一个目录
32 if (err) 33 goto exit_devices_unreg; 34 } 35 if (list_empty(&pdev_list)) { 36 err = -ENODEV; 37 goto exit_driver_unreg; 38 } 39 40 #ifdef CONFIG_HOTPLUG_CPU 41 register_hotcpu_notifier(&coretemp_cpu_notifier); 42 #endif 43 return 0; 44 ...... 45 }
B probe实现原理-->建立用户实现温度读取的sys文件接口过程
C 层次关系建立过程-->建立coretemp驱动与sys目录其他层次的连接关系
3.2.2 温度检测函数的实现
1 static ssize_t show_temp(struct device *dev, 2 struct device_attribute *devattr, char *buf) 3 { 4 struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 5 struct coretemp_data *data = coretemp_update_device(dev); 6 int err; 7 8 if (attr->index == SHOW_TEMP) 9 err = data->valid ? sprintf(buf, "%d\n", data->temp) : -EAGAIN; 10 else if (attr->index == SHOW_TJMAX) 11 err = sprintf(buf, "%d\n", data->tjmax); 12 else 13 err = sprintf(buf, "%d\n", data->ttarget); 14 return err; 15 }
每个cpu核心对应一个coretemp_data结构,该结构信息如下:
1 struct coretemp_data { 2 struct device *hwmon_dev; 3 struct mutex update_lock; 4 const char *name; 5 u32 id; cpu编号 6 char valid; /* zero until following fields are valid */ 7 unsigned long last_updated; /* in jiffies */ 8 int temp; 当前检测的温度,= real*1000 9 int tjmax; 该cpu的最大温度值,在probe过程中,通过调用adjust_tjmax来修改,一般=100,
当为移动设备的cpu且rdmsr_safe_on_cpu(id, 0xee, &eax, &edx),eax=40000000,时,将adjust_tjmax=85
10 int ttarget; 11 u8 alarm; 12 };
实际cpu温度的获取过程
1 static struct coretemp_data *coretemp_update_device(struct device *dev) 2 { 3 struct coretemp_data *data = dev_get_drvdata(dev); 4 5 mutex_lock(&data->update_lock); 6 7 if (!data->valid || time_after(jiffies, data->last_updated + HZ)) { 8 u32 eax, edx; 9 10 data->valid = 0; 11 rdmsr_on_cpu(data->id, MSR_IA32_THERM_STATUS, &eax, &edx); //获取该cpu上的温度数据主函数,eax最高位置1,且17位-23位共7位存储温度的信息delta 12 data->alarm = (eax >> 5) & 1; //MSR_IA32_THERM_STATUS = 0x19c 13 /* update only if data has been valid */ 14 if (eax & 0x80000000) { 15 data->temp = data->tjmax - (((eax >> 16) //实际的温度temp = data->temp/1000 =(100 000 - delta*1000)/1000,delta 从0-127,一般从0-100
16 & 0x7f) * 1000); 17 data->valid = 1; 18 } else { 19 dev_dbg(dev, "Temperature data invalid (0x%x)\n", eax); 20 } 21 data->last_updated = jiffies; 22 } 23 24 mutex_unlock(&data->update_lock); 25 return data;
特定cpu温度寄存器上数据读取过程
1 int rdmsr_on_cpu(unsigned int cpu, u32 msr_no, u32 *l, u32 *h) //该函数定义于arch/x86/lib/msr-on-cpu.c中 2 { 3 int err; 4 struct msr_info rv; 5 6 rv.msr_no = msr_no;//保存端口号 7 err = smp_call_function_single(cpu, __rdmsr_on_cpu, &rv, 1); 将反馈信息l、h和端口号打包到结构体msr_info中,并调用smp_call_function_single函数 8 *l = rv.l; //smp_call_function_single函数只是确保将__rdmsr_on_cpu函数发送到特定的处理器cpu上,然后又该处理器调用__rdmsr_on_cpu来实现该cpu温度的读取操作 9 *h = rv.h; //所以关键还是__rdmsr_on_cpu 10 11 return err; 12 }
1 /* 注:该函数是导出的,当内核模块要求某个函数在特定cpu上执行时,可以调用这个函数实现,所以将其放在这里 2 * smp_call_function_single - Run a function on a specific CPU 3 * @func: The function to run. This must be fast and non-blocking. 4 * @info: An arbitrary pointer to pass to the function. 5 * @wait: If true, wait until function has completed on other CPUs. 6 * 7 * Returns 0 on success, else a negative status code. Note that @wait 8 * will be implicitly turned on in case of allocation failures, since 9 * we fall back to on-stack allocation. 10 */ 11 int smp_call_function_single(int cpu, void (*func) (void *info), void *info, 12 int wait) 13 { 14 struct call_single_data d; 15 unsigned long flags; 16 /* prevent preemption and reschedule on another processor, 17 as well as CPU removal */ 18 int me = get_cpu(); 19 int err = 0; 20 21 /* Can deadlock when called with interrupts disabled */ 22 WARN_ON(irqs_disabled()); 23 24 if (cpu == me) { 25 local_irq_save(flags); 26 func(info); 27 local_irq_restore(flags); 28 } else if ((unsigned)cpu < NR_CPUS && cpu_online(cpu)) { 29 struct call_single_data *data = NULL; 30 31 if (!wait) { 32 data = kmalloc(sizeof(*data), GFP_ATOMIC); 33 if (data) 34 data->flags = CSD_FLAG_ALLOC; 35 } 36 if (!data) { 37 data = &d; 38 data->flags = CSD_FLAG_WAIT; 39 } 40 41 data->func = func; 42 data->info = info; 43 generic_exec_single(cpu, data); 44 } else { 45 err = -ENXIO; /* CPU not online */ 46 } 47 48 put_cpu(); 49 return err; 50 } 51 EXPORT_SYMBOL(smp_call_function_single);
实际上当前cpu执行实际温度获取操作为:
1 static void __rdmsr_on_cpu(void *info) 2 { 3 struct msr_info *rv = info; 4 5 rdmsr(rv->msr_no, rv->l, rv->h); 6 }
在文件x86/asm/include/msr.h文件中,定义如下
/* * Access to machine-specific registers (available on 586 and better only) * Note: the rd* operations modify the parameters directly (without using * pointer indirection), this allows gcc to optimize better */ #define rdmsr(msr, val1, val2) \ do { \ u64 __val = native_read_msr((msr)); \ (val1) = (u32)__val; \ (val2) = (u32)(__val >> 32); \ } while (0)
1 static inline unsigned long long native_read_msr(unsigned int msr) 2 { 3 DECLARE_ARGS(val, low, high); 4 5 asm volatile("rdmsr" : EAX_EDX_RET(val, low, high) : "c" (msr)); 6 return EAX_EDX_VAL(val, low, high); 7 }
在X86_32位系统中,实质上为函数:
1 static inline unsigned long long native_read_msr(unsigned int msr) 2 { 3 unsigned long long val; 4 5 asm volatile("rdmsr" : "A"(val) : "c" (msr)); 6 return (val); 7 }
可见,实质上读取该cpu核心温度的函数是以0x19c来读取msr寄存器