Compare commits

...

28 commits

Author SHA1 Message Date
c176fdb0d5
Add Lucienne support
Thanks Lukas Stockner!
2022-11-03 21:24:08 -04:00
c36a86c64b
Use -Wno-implicit-fallthrough instead of -Wimplicit-fallthrough=3 to support clang 2022-04-13 08:27:39 -05:00
ccc7d9e2d1
Merge pull request #9 from axelgenus/master
Fixed fall-through warning/error.
2022-03-10 07:30:47 -06:00
Alessandro Calori
184c4c7797 Fixed fall-through warning/error. 2022-03-10 09:25:34 +01:00
27eb87fb05
Merge pull request #7 from h3o66/fedora-copr
Add Fedora copr package repo to README
2022-02-26 17:01:23 -06:00
Christian
d2582d9194 Add Fedora copr package repo to README 2022-02-26 23:30:59 +01:00
2ab1693f4b
Bump version to 0.2.0 2021-12-19 09:56:44 -06:00
318d591b12
Update README to point to zenmonitor3 2021-12-16 11:40:25 -06:00
3773f2a7b2
Create debugging script
https://gist.github.com/ocerman/6566b00072c8af96a109f65075f2d8b7
2021-12-16 11:24:40 -06:00
16e81693e5
Add alternate register base for Zen 2 and 3 APUs
https://github.com/ocerman/zenpower/issues/31
2021-12-16 11:22:55 -06:00
a0cdb44fc5
Expose amps and ad SVI_TEL_PLANE defines for Zen3 2021-12-16 08:20:44 -06:00
90637b5561
Add correct Cezanne PCI IDs
The IDs can be found here: ac08b1c68d/include/linux/pci_ids.h
2021-12-15 09:14:28 -06:00
aba5677583
Fix #4: Fix PCI Device ID typo 2021-11-13 15:51:07 -06:00
5ed3765eff
WIP #2: Add Zen 3 APU PCI IDs 2021-11-12 19:55:24 -06:00
625e56d034
Remove *.mod files from git 2021-09-25 20:12:01 -05:00
938c36c3dc
WIP: #2 add Cezanne support 2021-09-25 20:06:18 -05:00
7cc5f1fefb
Update README.md 2021-06-20 00:06:45 +00:00
cd677b2677
Update README.md 2021-05-20 14:36:56 +00:00
25a3bb8ff1
Merge pull request #1 from feiyeung/master
minor improvements to the Makefile target "dkms-install"
2021-05-20 09:36:18 -05:00
1acdb47889
Update README 2021-05-20 09:34:15 -05:00
52ce0be504
Add Zen3 support 2021-05-20 09:31:15 -05:00
Feiyang Xue
2d0e5fad2d check for dkms availability first in the dkms-install target 2020-07-31 18:38:04 -05:00
Feiyang Xue
caf80fef03 add "-p" arg to "mkdir" for "dkms-install" 2020-07-31 18:33:59 -05:00
Ondrej Čerman
5e2f56fabe Added PCI Dev IDS for Model 60 (Ryzen 4000 APUs) 2020-06-14 21:57:52 +02:00
Ondrej Čerman
86f79621b0 Changed detection code to be compatible with kernel 5.7
Version 0.1.11
2020-06-06 15:20:38 +02:00
Ondrej Čerman
ee05fcfcf0 Version 0.1.10 2020-02-22 21:49:46 +01:00
Ondrej Čerman
5f50ac2dbd Added cpuid to the sensor label when using multiple cpus 2020-02-22 19:18:08 +01:00
Ondrej Čerman
2fc24934a0 Support for dual EPYC CPUs 2020-02-19 21:54:17 +01:00
5 changed files with 278 additions and 65 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
*.ko
*.o
*.mod
*.mod.c
.tmp_versions/
*.order

View file

@ -1,7 +1,9 @@
VERSION := 0.1.9
VERSION := 0.2.0
TARGET := $(shell uname -r)
DKMS_ROOT_PATH := /usr/src/zenpower-$(VERSION)
KBUILD_CFLAGS += -Wno-implicit-fallthrough
KERNEL_MODULES := /lib/modules/$(TARGET)
ifneq ("","$(wildcard /usr/src/linux-headers-$(TARGET)/*)")
@ -30,7 +32,8 @@ clean:
@$(MAKE) -C $(KERNEL_BUILD) M=$(CURDIR) clean
dkms-install:
mkdir $(DKMS_ROOT_PATH)
dkms --version >> /dev/null
mkdir -p $(DKMS_ROOT_PATH)
cp $(CURDIR)/dkms.conf $(DKMS_ROOT_PATH)
cp $(CURDIR)/Makefile $(DKMS_ROOT_PATH)
cp $(CURDIR)/zenpower.c $(DKMS_ROOT_PATH)

View file

@ -1,23 +1,29 @@
# Zenpower
Zenpower is Linux kernel driver for reading temperature, voltage(SVI2), current(SVI2) and power(SVI2) for AMD Zen family CPUs.
# Zenpower3
Zenpower3 is a Linux kernel driver for reading temperature, voltage(SVI2), current(SVI2) and power(SVI2) for AMD Zen family CPUs, now with Zen 3 support!
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
You can install this module via dkms.
You can install this module via DKMS.
### Installation commands for Ubuntu
```
### Installation for Ubuntu
```sh
sudo apt install dkms git build-essential linux-headers-$(uname -r)
cd ~
git clone https://github.com/ocerman/zenpower.git
cd zenpower
git clone https://github.com/Ta180m/zenpower3.git
cd zenpower3
sudo make dkms-install
```
### Installation for Arch
You can install the [AUR package](https://aur.archlinux.org/packages/zenpower3-dkms/).
### Installation for Fedora 35+
You can install it from the [copr package repo](https://copr.fedorainfracloud.org/coprs/birkch/zenpower3/)
## Module activation
Because zenpower is using same PCI device as k10temp, you have to disable k10temp first.
Because zenpower is using same PCI device as k10temp, you have to disable k10temp first. This is automatically done by the AUR package.
1. Check if k10temp is active. `lsmod | grep k10temp`
2. Unload k10temp `sudo modprobe -r k10temp`
@ -27,7 +33,7 @@ Because zenpower is using same PCI device as k10temp, you have to disable k10tem
*If k10temp is not blacklisted, you may have to manually unload k10temp after each restart.
## Sensors monitoring
You can use this app: [zenmonitor](https://github.com/ocerman/zenmonitor), or your favourie sensors monitoring software
You can use the `sensors` command, [zenmonitor3](https://github.com/Ta180m/zenmonitor3), or your favorite sensors monitoring software.
## Update instructions
1. Unload zenpower `sudo modprobe -r zenpower`

View file

@ -1,7 +1,7 @@
/*
* Zenpower - Driver for reading temperature, voltage, current and power for AMD 17h CPUs
*
* Copyright (c) 2018-2020 Ondrej Čerman
* Copyright (c) 2018-2021 Anthony Wang
*
* This driver is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License; either
@ -18,6 +18,8 @@
*/
/*
* forked from the original zenpower by Ondrej Čerman
*
* based on k10temp by Clemens Ladisch
*
* Docs:
@ -36,9 +38,13 @@
#include <asm/amd_nb.h>
MODULE_DESCRIPTION("AMD ZEN family CPU Sensors Driver");
MODULE_AUTHOR("Ondrej Čerman");
MODULE_AUTHOR("Anthony Wang");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.1.9");
MODULE_VERSION("0.2.0");
static bool zen1_calc;
module_param(zen1_calc, bool, 0);
MODULE_PARM_DESC(zen1_calc, "Set to 1 to use ZEN1 calculation");
#ifndef PCI_DEVICE_ID_AMD_17H_DF_F3
@ -53,18 +59,50 @@ MODULE_VERSION("0.1.9");
#define PCI_DEVICE_ID_AMD_17H_M30H_DF_F3 0x1493
#endif
#ifndef PCI_DEVICE_ID_AMD_17H_M60H_DF_F3
#define PCI_DEVICE_ID_AMD_17H_M60H_DF_F3 0x144b
#endif
#ifndef PCI_DEVICE_ID_AMD_17H_M70H_DF_F3
#define PCI_DEVICE_ID_AMD_17H_M70H_DF_F3 0x1443
#endif
/* ZEN3 */
#ifndef PCI_DEVICE_ID_AMD_19H_DF_F3
#define PCI_DEVICE_ID_AMD_19H_DF_F3 0x1653
#endif
#ifndef PCI_DEVICE_ID_AMD_19H_M40H_DF_F3
#define PCI_DEVICE_ID_AMD_19H_M40H_DF_F3 0x167c
#endif
#ifndef PCI_DEVICE_ID_AMD_19H_M50H_DF_F3
#define PCI_DEVICE_ID_AMD_19H_M50H_DF_F3 0x166d
#endif
/* F17H_M01H_SVI, should be renamed to something generic I think... */
#define F17H_M01H_REPORTED_TEMP_CTRL 0x00059800
#define F17H_M01H_SVI 0x0005A000
#define F17H_M02H_SVI 0x0006F000
#define F17H_M01H_SVI_TEL_PLANE0 (F17H_M01H_SVI + 0xC)
#define F17H_M01H_SVI_TEL_PLANE1 (F17H_M01H_SVI + 0x10)
#define F17H_M30H_SVI_TEL_PLANE0 (F17H_M01H_SVI + 0x14)
#define F17H_M30H_SVI_TEL_PLANE1 (F17H_M01H_SVI + 0x10)
#define F17H_M60H_SVI_TEL_PLANE0 (F17H_M02H_SVI + 0x38)
#define F17H_M60H_SVI_TEL_PLANE1 (F17H_M02H_SVI + 0x3C)
#define F17H_M70H_SVI_TEL_PLANE0 (F17H_M01H_SVI + 0x10)
#define F17H_M70H_SVI_TEL_PLANE1 (F17H_M01H_SVI + 0xC)
/* ZEN3 SP3/TR */
#define F19H_M01H_SVI_TEL_PLANE0 (F17H_M01H_SVI + 0x14)
#define F19H_M01H_SVI_TEL_PLANE1 (F17H_M01H_SVI + 0x10)
/* ZEN3 Ryzen desktop */
#define F19H_M21H_SVI_TEL_PLANE0 (F17H_M01H_SVI + 0x10)
#define F19H_M21H_SVI_TEL_PLANE1 (F17H_M01H_SVI + 0xC)
/* ZEN3 APU */
#define F19H_M50H_SVI_TEL_PLANE0 (F17H_M02H_SVI + 0x38)
#define F19H_M50H_SVI_TEL_PLANE1 (F17H_M02H_SVI + 0x3C)
#define F17H_M70H_CCD_TEMP(x) (0x00059954 + ((x) * 4))
#define F17H_TEMP_ADJUST_MASK 0x80000
@ -79,17 +117,14 @@ MODULE_VERSION("0.1.9");
})
#endif
/* CPUID function 0x80000001, ebx */
#define CPUID_PKGTYPE_MASK 0xf0000000
#define CPUID_PKGTYPE_SP3 0x40000000 // https://www.sandpile.org/x86/cpuid.htm
#define CPUID_PKGTYPE_SP3r2 0x70000000
struct zenpower_data {
struct pci_dev *pdev;
void (*read_amdsmn_addr)(struct pci_dev *pdev, u16 node_id, u32 address, u32 *regval);
u32 svi_core_addr;
u32 svi_soc_addr;
u16 node_id;
u8 cpu_id;
u8 nodes_per_cpu;
int temp_offset;
bool zen2;
bool kernel_smn_support;
@ -113,6 +148,7 @@ static const struct tctl_offset tctl_offset_table[] = {
};
static DEFINE_MUTEX(nb_smu_ind_mutex);
static bool multicpu = false;
static umode_t zenpower_is_visible(const void *rdata,
enum hwmon_sensor_types type,
@ -211,7 +247,7 @@ int static debug_addrs_arr[] = {
F17H_M01H_SVI + 0x14, 0x000598BC, 0x0005994C, F17H_M70H_CCD_TEMP(0),
F17H_M70H_CCD_TEMP(1), F17H_M70H_CCD_TEMP(2), F17H_M70H_CCD_TEMP(3),
F17H_M70H_CCD_TEMP(4), F17H_M70H_CCD_TEMP(5), F17H_M70H_CCD_TEMP(6),
F17H_M70H_CCD_TEMP(7)
F17H_M70H_CCD_TEMP(7), F17H_M02H_SVI + 0x38, F17H_M02H_SVI + 0x3C
};
static ssize_t debug_data_show(struct device *dev,
@ -221,8 +257,10 @@ 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);
len += sprintf(buf + len, "node_id = %d\n", data->node_id);
len += sprintf(buf + len, "KERN_SUP: %d\n", data->kernel_smn_support);
len += sprintf(buf + len, "NODE%d; CPU%d; ", data->node_id, data->cpu_id);
len += sprintf(buf + len, "N/CPU: %d\n", data->nodes_per_cpu);
for (i = 0; i < ARRAY_SIZE(debug_addrs_arr); i++){
data->read_amdsmn_addr(data->pdev, data->node_id, debug_addrs_arr[i], &smndata);
len += sprintf(buf + len, "%08x = %08x\n", debug_addrs_arr[i], smndata);
@ -323,51 +361,118 @@ static int zenpower_read(struct device *dev, enum hwmon_sensor_types type,
return 0;
}
static const char *zenpower_temp_label[] = {
"Tdie",
"Tctl",
"Tccd1",
"Tccd2",
"Tccd3",
"Tccd4",
"Tccd5",
"Tccd6",
"Tccd7",
"Tccd8",
static const char *zenpower_temp_label[][10] = {
{
"Tdie",
"Tctl",
"Tccd1",
"Tccd2",
"Tccd3",
"Tccd4",
"Tccd5",
"Tccd6",
"Tccd7",
"Tccd8",
},
{
"cpu0 Tdie",
"cpu0 Tctl",
"cpu0 Tccd1",
"cpu0 Tccd2",
"cpu0 Tccd3",
"cpu0 Tccd4",
"cpu0 Tccd5",
"cpu0 Tccd6",
"cpu0 Tccd7",
"cpu0 Tccd8",
},
{
"cpu1 Tdie",
"cpu1 Tctl",
"cpu1 Tccd1",
"cpu1 Tccd2",
"cpu1 Tccd3",
"cpu1 Tccd4",
"cpu1 Tccd5",
"cpu1 Tccd6",
"cpu1 Tccd7",
"cpu1 Tccd8",
}
};
static const char *zenpower_in_label[] = {
"",
"SVI2_Core",
"SVI2_SoC",
static const char *zenpower_in_label[][3] = {
{
"",
"SVI2_Core",
"SVI2_SoC",
},
{
"",
"cpu0 SVI2_Core",
"cpu0 SVI2_SoC",
},
{
"",
"cpu1 SVI2_Core",
"cpu1 SVI2_SoC",
}
};
static const char *zenpower_curr_label[] = {
"SVI2_C_Core",
"SVI2_C_SoC",
static const char *zenpower_curr_label[][2] = {
{
"SVI2_C_Core",
"SVI2_C_SoC",
},
{
"cpu0 SVI2_C_Core",
"cpu0 SVI2_C_SoC",
},
{
"cpu1 SVI2_C_Core",
"cpu1 SVI2_C_SoC",
}
};
static const char *zenpower_power_label[] = {
"SVI2_P_Core",
"SVI2_P_SoC",
static const char *zenpower_power_label[][2] = {
{
"SVI2_P_Core",
"SVI2_P_SoC",
},
{
"cpu0 SVI2_P_Core",
"cpu0 SVI2_P_SoC",
},
{
"cpu1 SVI2_P_Core",
"cpu1 SVI2_P_SoC",
}
};
static int zenpower_read_labels(struct device *dev,
enum hwmon_sensor_types type, u32 attr,
int channel, const char **str)
{
struct zenpower_data *data;
u8 i = 0;
if (multicpu) {
data = dev_get_drvdata(dev);
if (data->cpu_id <= 1)
i = data->cpu_id + 1;
}
switch (type) {
case hwmon_temp:
*str = zenpower_temp_label[channel];
*str = zenpower_temp_label[i][channel];
break;
case hwmon_in:
*str = zenpower_in_label[channel];
*str = zenpower_in_label[i][channel];
break;
case hwmon_curr:
*str = zenpower_curr_label[channel];
*str = zenpower_curr_label[i][channel];
break;
case hwmon_power:
*str = zenpower_power_label[channel];
*str = zenpower_power_label[i][channel];
break;
default:
return -EOPNOTSUPP;
@ -451,7 +556,10 @@ 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;
struct pci_dev *misc;
int i, ccd_check = 0;
bool multinode;
u8 node_of_cpu;
u32 val;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
@ -471,17 +579,26 @@ static int zenpower_probe(struct pci_dev *pdev, const struct pci_device_id *id)
data->ccd_visible[i] = false;
}
for (id = amd_nb_misc_ids; id->vendor; id++) {
if (pdev->vendor == id->vendor && pdev->device == id->device) {
for (i = 0; i < amd_nb_num(); i++) {
misc = node_to_amd_nb(i)->misc;
if (pdev->vendor == misc->vendor && pdev->device == misc->device) {
data->kernel_smn_support = true;
data->read_amdsmn_addr = kernel_smn_read;
data->node_id = amd_pci_dev_to_node_id(pdev);
break;
}
}
if (data->kernel_smn_support) {
data->node_id = amd_pci_dev_to_node_id(pdev);
}
// CPUID_Fn8000001E_ECX [Node Identifiers] (Core::X86::Cpuid::NodeId)
// 10:8 NodesPerProcessor
data->nodes_per_cpu = 1 + ((cpuid_ecx(0x8000001E) >> 8) & 0b111);
multinode = (data->nodes_per_cpu > 1);
node_of_cpu = data->node_id % data->nodes_per_cpu;
data->cpu_id = data->node_id / data->nodes_per_cpu;
if (data->cpu_id > 0)
multicpu = true;
if (boot_cpu_data.x86 == 0x17) {
switch (boot_cpu_data.x86_model) {
@ -489,18 +606,15 @@ static int zenpower_probe(struct pci_dev *pdev, const struct pci_device_id *id)
case 0x8: // Zen+
data->amps_visible = true;
val = cpuid_ebx(0x80000001) & CPUID_PKGTYPE_MASK; // package type
if (val == CPUID_PKGTYPE_SP3 || val == CPUID_PKGTYPE_SP3r2) {
// Threadripper / EPYC
if (data->node_id == 0) {
if (multinode) { // Threadripper / EPYC
if (node_of_cpu == 0) {
data->svi_soc_addr = F17H_M01H_SVI_TEL_PLANE0;
}
if (data->node_id == 1) {
if (node_of_cpu == 1) {
data->svi_core_addr = F17H_M01H_SVI_TEL_PLANE0;
}
}
else{
// Ryzen
else { // Ryzen
data->svi_core_addr = F17H_M01H_SVI_TEL_PLANE0;
data->svi_soc_addr = F17H_M01H_SVI_TEL_PLANE1;
}
@ -515,26 +629,91 @@ static int zenpower_probe(struct pci_dev *pdev, const struct pci_device_id *id)
break;
case 0x31: // Zen2 Threadripper/EPYC
data->zen2 = true;
/* fixme: add helper for that */
if (!zen1_calc) {
data->zen2 = true;
dev_info(dev, "Using ZEN2 calculation formula.\n");
} else {
dev_info(dev, "Using ZEN1 calculation formula.\n");
}
data->amps_visible = true;
data->svi_core_addr = F17H_M30H_SVI_TEL_PLANE0;
data->svi_soc_addr = F17H_M30H_SVI_TEL_PLANE1;
ccd_check = 8;
break;
case 0x60: // Zen2 APU
case 0x68: // Zen2 Lucienne
if (!zen1_calc) {
data->zen2 = true;
dev_info(dev, "Using ZEN2 calculation formula.\n");
} else {
dev_info(dev, "Using ZEN1 calculation formula.\n");
}
data->amps_visible = true;
data->svi_core_addr = F17H_M60H_SVI_TEL_PLANE0;
data->svi_soc_addr = F17H_M60H_SVI_TEL_PLANE1;
ccd_check = 8;
break;
case 0x71: // Zen2 Ryzen
data->zen2 = true;
if (!zen1_calc) {
data->zen2 = true;
dev_info(dev, "Using ZEN2 calculation formula.\n");
} else {
dev_info(dev, "Using ZEN1 calculation formula.\n");
}
data->amps_visible = true;
data->svi_core_addr = F17H_M70H_SVI_TEL_PLANE0;
data->svi_soc_addr = F17H_M70H_SVI_TEL_PLANE1;
ccd_check = 8;
break;
default:
}
} else if (boot_cpu_data.x86 == 0x19) {
switch (boot_cpu_data.x86_model) {
case 0x0 ... 0x1: /* Zen3 SP3/TR */
if (!zen1_calc) {
/* The code need refactoring but calculation is the same
* Is this per Server/Desktop/APU?. EG: each one has his own set of formula(s)?
*/
data->zen2 = true;
dev_info(dev, "Using ZEN2 calculation formula.\n");
} else {
dev_info(dev, "Using ZEN1 calculation formula.\n");
}
data->amps_visible = true;
data->svi_core_addr = F19H_M01H_SVI_TEL_PLANE0;
data->svi_soc_addr = F19H_M01H_SVI_TEL_PLANE1;
ccd_check = 8; /* max 64C, 8C per CCD = max 8 CCDs */
break;
case 0x21: /* Zen3 Ryzen */
if (!zen1_calc) {
data->zen2 = true;
dev_info(dev, "using ZEN2 calculation formula.\n");
} else {
dev_info(dev, "using ZEN1 calculation formula.\n");
}
data->amps_visible = true;
data->svi_core_addr = F19H_M21H_SVI_TEL_PLANE0;
data->svi_soc_addr = F19H_M21H_SVI_TEL_PLANE1;
ccd_check = 2; /* max 16C, 8C per CCD = max 2 CCD's */
break;
case 0x50: /* Zen3 APU */
if (!zen1_calc) {
data->zen2 = true;
dev_info(dev, "using ZEN2 calculation formula.\n");
} else {
dev_info(dev, "using ZEN1 calculation formula.\n");
}
data->amps_visible = true;
data->svi_core_addr = F19H_M50H_SVI_TEL_PLANE0;
data->svi_soc_addr = F19H_M50H_SVI_TEL_PLANE1;
ccd_check = 2;
break;
}
} else {
data->svi_core_addr = F17H_M01H_SVI_TEL_PLANE0;
data->svi_soc_addr = F17H_M01H_SVI_TEL_PLANE1;
break;
}
}
for (i = 0; i < ccd_check; i++) {
@ -566,7 +745,11 @@ static const struct pci_device_id zenpower_id_table[] = {
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M10H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M30H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F3) },
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) },
{}
};
MODULE_DEVICE_TABLE(pci, zenpower_id_table);

20
zp_read_debug.sh Executable file
View file

@ -0,0 +1,20 @@
#!/bin/bash
hwmon="/sys/class/hwmon"
mdevs=`ls $hwmon`
zenmon=""
ok=0
for dev in $mdevs; do
path="$hwmon/$dev"
devname=`cat $path/name`
if [ "$devname" == "zenpower" ]; then
cat $path/debug_data
ok=1
fi
done
if [ $ok -ne 1 ]; then
echo "Zenpower not found"
exit
fi