diff --git a/Makefile b/Makefile index 9f36841..b65a051 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION := 0.1.4 +VERSION := 0.1.5 TARGET := $(shell uname -r) DKMS_ROOT_PATH := /usr/src/zenpower-$(VERSION) diff --git a/README.md b/README.md index 8cb208f..b602656 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Zenpower Zenpower is Linux kernel driver for reading temperature, voltage(SVI2), current(SVI2) and power(SVI2) for AMD Zen family CPUs. -Make sure that your Linux kernel have support for your CPUs as Zenpower is using kernel calls to read values from SMN. +Make sure that your Linux kernel have support for your CPUs as Zenpower is using kernel function `amd_smn_read` to read values from SMN. A fallback method (which may or may not work!) will be used when it is detected that kernel function `amd_smn_read` lacks support for your CPU. For AMD family 17h Model 70h (Ryzen 3000) CPUs you need kernel version 5.3.4 or newer or kernel with this patch: https://patchwork.kernel.org/patch/11043277/ ## Installation diff --git a/zenpower.c b/zenpower.c index 098a838..1f85e72 100644 --- a/zenpower.c +++ b/zenpower.c @@ -7,7 +7,7 @@ MODULE_DESCRIPTION("AMD ZEN family CPU Sensors Driver"); MODULE_AUTHOR("Ondrej Čerman"); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.1.4"); +MODULE_VERSION("0.1.5"); // based on k10temp - GPL - (c) 2009 Clemens Ladisch // @@ -41,6 +41,8 @@ MODULE_VERSION("0.1.4"); #define F17H_M01H_SVI 0x0005A000 #define F17H_M01H_SVI_TEL_PLANE0 F17H_M01H_SVI + 0xc #define F17H_M01H_SVI_TEL_PLANE1 F17H_M01H_SVI + 0x10 +#define F17H_M70H_CCD1_TEMP 0x00059954 +#define F17H_M70H_CCD2_TEMP 0x00059958 #define F17H_TEMP_ADJUST_MASK 0x80000 @@ -51,6 +53,9 @@ struct zenpower_data { u32 svi_soc_addr; int temp_offset; bool zen2; + bool kernel_smn_support; + bool amps_visible; + bool ccd1_visible, ccd2_visible; }; struct tctl_offset { @@ -68,9 +73,29 @@ static const struct tctl_offset tctl_offset_table[] = { { 0x17, "AMD Ryzen Threadripper 29", 27000 }, /* 29{20,50,70,90}[W]X */ }; +static DEFINE_MUTEX(nb_smu_ind_mutex); + static umode_t zenpower_is_visible(struct kobject *kobj, struct attribute *attr, int index) { + struct device *dev = container_of(kobj, struct device, kobj); + struct zenpower_data *data = dev_get_drvdata(dev); + + switch (index) { + case 4 ... 11: // amperage and wattage + if (!data->amps_visible) + return 0; + break; + case 17 ... 18: // CCD1 temperature + if (!data->ccd1_visible) + return 0; + break; + case 19 ... 20: // CCD2 temperature + if (!data->ccd2_visible) + return 0; + break; + } + return attr->mode; } @@ -107,7 +132,7 @@ static u32 get_soc_current(u32 plane, bool zen2) return (fc * idd_cor) / 1000; } -static unsigned int get_raw_temp(struct zenpower_data *data) +static unsigned int get_ctl_temp(struct zenpower_data *data) { unsigned int temp; u32 regval; @@ -119,11 +144,19 @@ static unsigned int get_raw_temp(struct zenpower_data *data) return temp; } +static unsigned int get_ccd_temp(struct zenpower_data *data, u32 ccd_addr) +{ + u32 regval; + data->read_amdsmn_addr(data->pdev, ccd_addr, ®val); + + return (regval & 0xfff) * 125 - 305000; +} + static ssize_t temp1_input_show(struct device *dev, struct device_attribute *attr, char *buf) { struct zenpower_data *data = dev_get_drvdata(dev); - unsigned int temp = get_raw_temp(data); + unsigned int temp = get_ctl_temp(data); if (temp > data->temp_offset) temp -= data->temp_offset; @@ -143,7 +176,25 @@ static ssize_t temp2_input_show(struct device *dev, struct device_attribute *devattr, char *buf) { struct zenpower_data *data = dev_get_drvdata(dev); - unsigned int temp = get_raw_temp(data); + unsigned int temp = get_ctl_temp(data); + + return sprintf(buf, "%u\n", temp); +} + +static ssize_t temp3_input_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct zenpower_data *data = dev_get_drvdata(dev); + unsigned int temp = get_ccd_temp(data, F17H_M70H_CCD1_TEMP); + + return sprintf(buf, "%u\n", temp); +} + +static ssize_t temp4_input_show(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct zenpower_data *data = dev_get_drvdata(dev); + unsigned int temp = get_ccd_temp(data, F17H_M70H_CCD2_TEMP); return sprintf(buf, "%u\n", temp); } @@ -216,7 +267,7 @@ static ssize_t power2_input_show(struct device *dev, int static debug_addrs_arr[] = { F17H_M01H_SVI + 0x8, F17H_M01H_SVI_TEL_PLANE0, F17H_M01H_SVI_TEL_PLANE1, - 0x000598BC, 0x0005994C, 0x00059954, 0x00059958, 0x0005995C + 0x000598BC, 0x0005994C, F17H_M70H_CCD1_TEMP, F17H_M70H_CCD2_TEMP, 0x0005995C }; static ssize_t debug_data_show(struct device *dev, @@ -226,6 +277,7 @@ static ssize_t debug_data_show(struct device *dev, struct zenpower_data *data = dev_get_drvdata(dev); u32 smndata; + len += sprintf(buf, "kernel_smn_support = %d\n", data->kernel_smn_support); for (i = 0; i < ARRAY_SIZE(debug_addrs_arr); i++){ data->read_amdsmn_addr(data->pdev, debug_addrs_arr[i], &smndata); len += sprintf(buf + len, "%08x = %08x\n", debug_addrs_arr[i], smndata); @@ -257,6 +309,10 @@ static ssize_t zen_label_show(struct device *dev, return sprintf(buf, "Tdie\n"); case 32: return sprintf(buf, "Tctl\n"); + case 33: + return sprintf(buf, "Tccd1\n"); + case 34: + return sprintf(buf, "Tccd2\n"); } return 0; @@ -279,8 +335,13 @@ static DEVICE_ATTR_RO(temp1_max); static SENSOR_DEVICE_ATTR(temp1_label, 0444, zen_label_show, NULL, 31); static DEVICE_ATTR_RO(temp2_input); static SENSOR_DEVICE_ATTR(temp2_label, 0444, zen_label_show, NULL, 32); +static DEVICE_ATTR_RO(temp3_input); +static SENSOR_DEVICE_ATTR(temp3_label, 0444, zen_label_show, NULL, 33); +static DEVICE_ATTR_RO(temp4_input); +static SENSOR_DEVICE_ATTR(temp4_label, 0444, zen_label_show, NULL, 34); static DEVICE_ATTR_RO(debug_data); + static struct attribute *zenpower_attrs[] = { &dev_attr_in1_input.attr, &sensor_dev_attr_in1_label.dev_attr.attr, @@ -299,6 +360,10 @@ static struct attribute *zenpower_attrs[] = { &sensor_dev_attr_temp1_label.dev_attr.attr, &dev_attr_temp2_input.attr, &sensor_dev_attr_temp2_label.dev_attr.attr, + &dev_attr_temp3_input.attr, + &sensor_dev_attr_temp3_label.dev_attr.attr, + &dev_attr_temp4_input.attr, + &sensor_dev_attr_temp4_label.dev_attr.attr, &dev_attr_debug_data.attr, NULL }; @@ -310,17 +375,28 @@ static const struct attribute_group zenpower_group = { __ATTRIBUTE_GROUPS(zenpower); -static void read_amdsmn_addr(struct pci_dev *pdev, u32 address, u32 *regval) +static void kernel_smn_read(struct pci_dev *pdev, u32 address, u32 *regval) { amd_smn_read(amd_pci_dev_to_node_id(pdev), address, regval); } +// fallback method from k10temp +// may return inaccurate results on multi-die chips +static void nb_index_read(struct pci_dev *pdev, u32 address, u32 *regval) +{ + mutex_lock(&nb_smu_ind_mutex); + pci_bus_write_config_dword(pdev->bus, PCI_DEVFN(0, 0), 0x60, address); + pci_bus_read_config_dword(pdev->bus, PCI_DEVFN(0, 0), 0x64, regval); + mutex_unlock(&nb_smu_ind_mutex); +} + static int zenpower_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct device *dev = &pdev->dev; struct zenpower_data *data; struct device *hwmon_dev; bool swapped_addr = false; + u32 tmp; int i; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); @@ -329,11 +405,45 @@ static int zenpower_probe(struct pci_dev *pdev, const struct pci_device_id *id) data->zen2 = false; data->pdev = pdev; - data->read_amdsmn_addr = read_amdsmn_addr; + data->read_amdsmn_addr = nb_index_read; + data->kernel_smn_support = false; + data->amps_visible = false; + data->ccd1_visible = false; + data->ccd2_visible = false; - if (boot_cpu_data.x86 == 0x17 && boot_cpu_data.x86_model == 0x71) { - data->zen2 = true; - swapped_addr = true; + for (id = amd_nb_misc_ids; id->vendor; id++) { + if (pdev->vendor == id->vendor && pdev->device == id->device) { + data->kernel_smn_support = true; + data->read_amdsmn_addr = kernel_smn_read; + break; + } + } + + if (boot_cpu_data.x86 == 0x17) { + switch (boot_cpu_data.x86_model) { + case 0x1: // Zen + case 0x8: // Zen+ + case 0x11: // Zen APU + case 0x18: // Zen+ APU + data->amps_visible = true; + break; + + case 0x71: // Zen2 + data->amps_visible = true; + data->zen2 = true; + swapped_addr = true; + + data->read_amdsmn_addr(pdev, F17H_M70H_CCD1_TEMP, &tmp); + if ((tmp & 0xfff) > 0) { + data->ccd1_visible = true; + } + + data->read_amdsmn_addr(pdev, F17H_M70H_CCD2_TEMP, &tmp); + if ((tmp & 0xfff) > 0) { + data->ccd2_visible = true; + } + break; + } } #ifdef SWAP_CORE_SOC