cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

k8temp.c (5554B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 * k8temp.c - Linux kernel module for hardware monitoring
      4 *
      5 * Copyright (C) 2006 Rudolf Marek <r.marek@assembler.cz>
      6 *
      7 * Inspired from the w83785 and amd756 drivers.
      8 */
      9
     10#include <linux/module.h>
     11#include <linux/init.h>
     12#include <linux/slab.h>
     13#include <linux/pci.h>
     14#include <linux/hwmon.h>
     15#include <linux/err.h>
     16#include <linux/mutex.h>
     17#include <asm/processor.h>
     18
     19#define TEMP_FROM_REG(val)	(((((val) >> 16) & 0xff) - 49) * 1000)
     20#define REG_TEMP	0xe4
     21#define SEL_PLACE	0x40
     22#define SEL_CORE	0x04
     23
     24struct k8temp_data {
     25	struct mutex update_lock;
     26
     27	/* registers values */
     28	u8 sensorsp;		/* sensor presence bits - SEL_CORE, SEL_PLACE */
     29	u8 swap_core_select;    /* meaning of SEL_CORE is inverted */
     30	u32 temp_offset;
     31};
     32
     33static const struct pci_device_id k8temp_ids[] = {
     34	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_K8_NB_MISC) },
     35	{ 0 },
     36};
     37MODULE_DEVICE_TABLE(pci, k8temp_ids);
     38
     39static int is_rev_g_desktop(u8 model)
     40{
     41	u32 brandidx;
     42
     43	if (model < 0x69)
     44		return 0;
     45
     46	if (model == 0xc1 || model == 0x6c || model == 0x7c)
     47		return 0;
     48
     49	/*
     50	 * Differentiate between AM2 and ASB1.
     51	 * See "Constructing the processor Name String" in "Revision
     52	 * Guide for AMD NPT Family 0Fh Processors" (33610).
     53	 */
     54	brandidx = cpuid_ebx(0x80000001);
     55	brandidx = (brandidx >> 9) & 0x1f;
     56
     57	/* Single core */
     58	if ((model == 0x6f || model == 0x7f) &&
     59	    (brandidx == 0x7 || brandidx == 0x9 || brandidx == 0xc))
     60		return 0;
     61
     62	/* Dual core */
     63	if (model == 0x6b &&
     64	    (brandidx == 0xb || brandidx == 0xc))
     65		return 0;
     66
     67	return 1;
     68}
     69
     70static umode_t
     71k8temp_is_visible(const void *drvdata, enum hwmon_sensor_types type,
     72		  u32 attr, int channel)
     73{
     74	const struct k8temp_data *data = drvdata;
     75
     76	if ((channel & 1) && !(data->sensorsp & SEL_PLACE))
     77		return 0;
     78
     79	if ((channel & 2) && !(data->sensorsp & SEL_CORE))
     80		return 0;
     81
     82	return 0444;
     83}
     84
     85static int
     86k8temp_read(struct device *dev, enum hwmon_sensor_types type,
     87	    u32 attr, int channel, long *val)
     88{
     89	struct k8temp_data *data = dev_get_drvdata(dev);
     90	struct pci_dev *pdev = to_pci_dev(dev->parent);
     91	int core, place;
     92	u32 temp;
     93	u8 tmp;
     94
     95	core = (channel >> 1) & 1;
     96	place = channel & 1;
     97
     98	core ^= data->swap_core_select;
     99
    100	mutex_lock(&data->update_lock);
    101	pci_read_config_byte(pdev, REG_TEMP, &tmp);
    102	tmp &= ~(SEL_PLACE | SEL_CORE);
    103	if (core)
    104		tmp |= SEL_CORE;
    105	if (place)
    106		tmp |= SEL_PLACE;
    107	pci_write_config_byte(pdev, REG_TEMP, tmp);
    108	pci_read_config_dword(pdev, REG_TEMP, &temp);
    109	mutex_unlock(&data->update_lock);
    110
    111	*val = TEMP_FROM_REG(temp) + data->temp_offset;
    112
    113	return 0;
    114}
    115
    116static const struct hwmon_ops k8temp_ops = {
    117	.is_visible = k8temp_is_visible,
    118	.read = k8temp_read,
    119};
    120
    121static const struct hwmon_channel_info *k8temp_info[] = {
    122	HWMON_CHANNEL_INFO(temp,
    123		HWMON_T_INPUT, HWMON_T_INPUT, HWMON_T_INPUT, HWMON_T_INPUT),
    124	NULL
    125};
    126
    127static const struct hwmon_chip_info k8temp_chip_info = {
    128	.ops = &k8temp_ops,
    129	.info = k8temp_info,
    130};
    131
    132static int k8temp_probe(struct pci_dev *pdev,
    133				  const struct pci_device_id *id)
    134{
    135	u8 scfg;
    136	u32 temp;
    137	u8 model, stepping;
    138	struct k8temp_data *data;
    139	struct device *hwmon_dev;
    140
    141	data = devm_kzalloc(&pdev->dev, sizeof(struct k8temp_data), GFP_KERNEL);
    142	if (!data)
    143		return -ENOMEM;
    144
    145	model = boot_cpu_data.x86_model;
    146	stepping = boot_cpu_data.x86_stepping;
    147
    148	/* feature available since SH-C0, exclude older revisions */
    149	if ((model == 4 && stepping == 0) ||
    150	    (model == 5 && stepping <= 1))
    151		return -ENODEV;
    152
    153	/*
    154	 * AMD NPT family 0fh, i.e. RevF and RevG:
    155	 * meaning of SEL_CORE bit is inverted
    156	 */
    157	if (model >= 0x40) {
    158		data->swap_core_select = 1;
    159		dev_warn(&pdev->dev,
    160			 "Temperature readouts might be wrong - check erratum #141\n");
    161	}
    162
    163	/*
    164	 * RevG desktop CPUs (i.e. no socket S1G1 or ASB1 parts) need
    165	 * additional offset, otherwise reported temperature is below
    166	 * ambient temperature
    167	 */
    168	if (is_rev_g_desktop(model))
    169		data->temp_offset = 21000;
    170
    171	pci_read_config_byte(pdev, REG_TEMP, &scfg);
    172	scfg &= ~(SEL_PLACE | SEL_CORE);	/* Select sensor 0, core0 */
    173	pci_write_config_byte(pdev, REG_TEMP, scfg);
    174	pci_read_config_byte(pdev, REG_TEMP, &scfg);
    175
    176	if (scfg & (SEL_PLACE | SEL_CORE)) {
    177		dev_err(&pdev->dev, "Configuration bit(s) stuck at 1!\n");
    178		return -ENODEV;
    179	}
    180
    181	scfg |= (SEL_PLACE | SEL_CORE);
    182	pci_write_config_byte(pdev, REG_TEMP, scfg);
    183
    184	/* now we know if we can change core and/or sensor */
    185	pci_read_config_byte(pdev, REG_TEMP, &data->sensorsp);
    186
    187	if (data->sensorsp & SEL_PLACE) {
    188		scfg &= ~SEL_CORE;	/* Select sensor 1, core0 */
    189		pci_write_config_byte(pdev, REG_TEMP, scfg);
    190		pci_read_config_dword(pdev, REG_TEMP, &temp);
    191		scfg |= SEL_CORE;	/* prepare for next selection */
    192		if (!((temp >> 16) & 0xff)) /* if temp is 0 -49C is unlikely */
    193			data->sensorsp &= ~SEL_PLACE;
    194	}
    195
    196	if (data->sensorsp & SEL_CORE) {
    197		scfg &= ~SEL_PLACE;	/* Select sensor 0, core1 */
    198		pci_write_config_byte(pdev, REG_TEMP, scfg);
    199		pci_read_config_dword(pdev, REG_TEMP, &temp);
    200		if (!((temp >> 16) & 0xff)) /* if temp is 0 -49C is unlikely */
    201			data->sensorsp &= ~SEL_CORE;
    202	}
    203
    204	mutex_init(&data->update_lock);
    205
    206	hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
    207							 "k8temp",
    208							 data,
    209							 &k8temp_chip_info,
    210							 NULL);
    211
    212	return PTR_ERR_OR_ZERO(hwmon_dev);
    213}
    214
    215static struct pci_driver k8temp_driver = {
    216	.name = "k8temp",
    217	.id_table = k8temp_ids,
    218	.probe = k8temp_probe,
    219};
    220
    221module_pci_driver(k8temp_driver);
    222
    223MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>");
    224MODULE_DESCRIPTION("AMD K8 core temperature monitor");
    225MODULE_LICENSE("GPL");