Hannes Reinecke 706539
From: Guenter Roeck <linux@roeck-us.net>
Hannes Reinecke 706539
Date: Wed, 6 Nov 2019 06:35:18 -0800
Hannes Reinecke 706539
Subject: [PATCH] nvme: Add hardware monitoring support
Hannes Reinecke 706539
Git-commit: 400b6a7b13a3fd71cff087139ce45dd1e5fff444
Hannes Reinecke 706539
Patch-mainline: v5.5-rc1
Hannes Reinecke 706539
References: bsc#1169045
Hannes Reinecke 706539
MIME-Version: 1.0
Hannes Reinecke 706539
Content-Type: text/plain; charset=UTF-8
Hannes Reinecke 706539
Content-Transfer-Encoding: 8bit
Hannes Reinecke 706539
Hannes Reinecke 706539
nvme devices report temperature information in the controller information
Hannes Reinecke 706539
(for limits) and in the smart log. Currently, the only means to retrieve
Hannes Reinecke 706539
this information is the nvme command line interface, which requires
Hannes Reinecke 706539
super-user privileges.
Hannes Reinecke 706539
Hannes Reinecke 706539
At the same time, it would be desirable to be able to use NVMe temperature
Hannes Reinecke 706539
information for thermal control.
Hannes Reinecke 706539
Hannes Reinecke 706539
This patch adds support to read NVMe temperatures from the kernel using the
Hannes Reinecke 706539
hwmon API and adds temperature zones for NVMe drives. The thermal subsystem
Hannes Reinecke 706539
can use this information to set thermal policies, and userspace can access
Hannes Reinecke 706539
it using libsensors and/or the "sensors" command.
Hannes Reinecke 706539
Hannes Reinecke 706539
Example output from the "sensors" command:
Hannes Reinecke 706539
Hannes Reinecke 706539
nvme0-pci-0100
Hannes Reinecke 706539
Adapter: PCI adapter
Hannes Reinecke 706539
Composite:    +39.0°C  (high = +85.0°C, crit = +85.0°C)
Hannes Reinecke 706539
Sensor 1:     +39.0°C
Hannes Reinecke 706539
Sensor 2:     +41.0°C
Hannes Reinecke 706539
Hannes Reinecke 706539
Reviewed-by: Christoph Hellwig <hch@lst.de>
Hannes Reinecke 706539
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Hannes Reinecke 706539
Signed-off-by: Keith Busch <kbusch@kernel.org>
Hannes Reinecke 706539
Acked-by: Hannes Reinecke <hare@suse.com>
Hannes Reinecke 706539
---
Hannes Reinecke 706539
 drivers/nvme/host/Kconfig  |  10 +++
Hannes Reinecke 706539
 drivers/nvme/host/Makefile |   1 +
Hannes Reinecke 706539
 drivers/nvme/host/core.c   |   6 ++
Hannes Reinecke 706539
 drivers/nvme/host/hwmon.c  | 181 +++++++++++++++++++++++++++++++++++++++++++++
Hannes Reinecke 706539
 drivers/nvme/host/nvme.h   |   8 ++
Hannes Reinecke 706539
 5 files changed, 206 insertions(+)
Hannes Reinecke 706539
 create mode 100644 drivers/nvme/host/hwmon.c
Hannes Reinecke 706539
Hannes Reinecke 706539
diff --git a/drivers/nvme/host/Kconfig b/drivers/nvme/host/Kconfig
Hannes Reinecke 706539
index 2b36f052bfb9..c6439638a419 100644
Hannes Reinecke 706539
--- a/drivers/nvme/host/Kconfig
Hannes Reinecke 706539
+++ b/drivers/nvme/host/Kconfig
Hannes Reinecke 706539
@@ -23,6 +23,16 @@ config NVME_MULTIPATH
Hannes Reinecke 706539
 	   /dev/nvmeXnY device will show up for each NVMe namespaces,
Hannes Reinecke 706539
 	   even if it is accessible through multiple controllers.
Hannes Reinecke 706539
 
Hannes Reinecke 706539
+config NVME_HWMON
Hannes Reinecke 706539
+	bool "NVMe hardware monitoring"
Hannes Reinecke 706539
+	depends on (NVME_CORE=y && HWMON=y) || (NVME_CORE=m && HWMON)
Hannes Reinecke 706539
+	help
Hannes Reinecke 706539
+	  This provides support for NVMe hardware monitoring. If enabled,
Hannes Reinecke 706539
+	  a hardware monitoring device will be created for each NVMe drive
Hannes Reinecke 706539
+	  in the system.
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+	  If unsure, say N.
Hannes Reinecke 706539
+
Hannes Reinecke 706539
 config NVME_FABRICS
Hannes Reinecke 706539
 	tristate
Hannes Reinecke 706539
 
Hannes Reinecke 706539
diff --git a/drivers/nvme/host/Makefile b/drivers/nvme/host/Makefile
Hannes Reinecke 706539
index 8a4b671c5f0c..fc7b26be692d 100644
Hannes Reinecke 706539
--- a/drivers/nvme/host/Makefile
Hannes Reinecke 706539
+++ b/drivers/nvme/host/Makefile
Hannes Reinecke 706539
@@ -14,6 +14,7 @@ nvme-core-$(CONFIG_TRACING)		+= trace.o
Hannes Reinecke 706539
 nvme-core-$(CONFIG_NVME_MULTIPATH)	+= multipath.o
Hannes Reinecke 706539
 nvme-core-$(CONFIG_NVM)			+= lightnvm.o
Hannes Reinecke 706539
 nvme-core-$(CONFIG_FAULT_INJECTION_DEBUG_FS)	+= fault_inject.o
Hannes Reinecke 706539
+nvme-core-$(CONFIG_NVME_HWMON)		+= hwmon.o
Hannes Reinecke 706539
 
Hannes Reinecke 706539
 nvme-y					+= pci.o
Hannes Reinecke 706539
 
Hannes Reinecke 706539
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
Hannes Reinecke 706539
index b4214e54f2d2..4be64703aa47 100644
Hannes Reinecke 706539
--- a/drivers/nvme/host/core.c
Hannes Reinecke 706539
+++ b/drivers/nvme/host/core.c
Hannes Reinecke 706539
@@ -2760,6 +2760,9 @@ int nvme_init_identify(struct nvme_ctrl *ctrl)
Hannes Reinecke 706539
 	ctrl->oncs = le16_to_cpu(id->oncs);
Hannes Reinecke 706539
 	ctrl->mtfa = le16_to_cpu(id->mtfa);
Hannes Reinecke 706539
 	ctrl->oaes = le32_to_cpu(id->oaes);
Hannes Reinecke 706539
+	ctrl->wctemp = le16_to_cpu(id->wctemp);
Hannes Reinecke 706539
+	ctrl->cctemp = le16_to_cpu(id->cctemp);
Hannes Reinecke 706539
+
Hannes Reinecke 706539
 	atomic_set(&ctrl->abort_limit, id->acl + 1);
Hannes Reinecke 706539
 	ctrl->vwc = id->vwc;
Hannes Reinecke 706539
 	if (id->mdts)
Hannes Reinecke 706539
@@ -2859,6 +2862,9 @@ int nvme_init_identify(struct nvme_ctrl *ctrl)
Hannes Reinecke 706539
 	if (ret < 0)
Hannes Reinecke 706539
 		return ret;
Hannes Reinecke 706539
 
Hannes Reinecke 706539
+	if (!ctrl->identified)
Hannes Reinecke 706539
+		nvme_hwmon_init(ctrl);
Hannes Reinecke 706539
+
Hannes Reinecke 706539
 	ctrl->identified = true;
Hannes Reinecke 706539
 
Hannes Reinecke 706539
 	return 0;
Hannes Reinecke 706539
diff --git a/drivers/nvme/host/hwmon.c b/drivers/nvme/host/hwmon.c
Hannes Reinecke 706539
new file mode 100644
Hannes Reinecke 706539
index 000000000000..5480cbb84f9f
Hannes Reinecke 706539
--- /dev/null
Hannes Reinecke 706539
+++ b/drivers/nvme/host/hwmon.c
Hannes Reinecke 706539
@@ -0,0 +1,181 @@
Hannes Reinecke 706539
+// SPDX-License-Identifier: GPL-2.0
Hannes Reinecke 706539
+/*
Hannes Reinecke 706539
+ * NVM Express hardware monitoring support
Hannes Reinecke 706539
+ * Copyright (c) 2019, Guenter Roeck
Hannes Reinecke 706539
+ */
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+#include <linux/hwmon.h>
Hannes Reinecke 706539
+#include <asm/unaligned.h>
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+#include "nvme.h"
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+struct nvme_hwmon_data {
Hannes Reinecke 706539
+	struct nvme_ctrl *ctrl;
Hannes Reinecke 706539
+	struct nvme_smart_log log;
Hannes Reinecke 706539
+	struct mutex read_lock;
Hannes Reinecke 706539
+};
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+static int nvme_hwmon_get_smart_log(struct nvme_hwmon_data *data)
Hannes Reinecke 706539
+{
Hannes Reinecke 706539
+	int ret;
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+	ret = nvme_get_log(data->ctrl, NVME_NSID_ALL, NVME_LOG_SMART, 0,
Hannes Reinecke 706539
+			   &data->log, sizeof(data->log), 0);
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+	return ret <= 0 ? ret : -EIO;
Hannes Reinecke 706539
+}
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+static int nvme_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
Hannes Reinecke 706539
+			   u32 attr, int channel, long *val)
Hannes Reinecke 706539
+{
Hannes Reinecke 706539
+	struct nvme_hwmon_data *data = dev_get_drvdata(dev);
Hannes Reinecke 706539
+	struct nvme_smart_log *log = &data->log;
Hannes Reinecke 706539
+	int temp;
Hannes Reinecke 706539
+	int err;
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+	/*
Hannes Reinecke 706539
+	 * First handle attributes which don't require us to read
Hannes Reinecke 706539
+	 * the smart log.
Hannes Reinecke 706539
+	 */
Hannes Reinecke 706539
+	switch (attr) {
Hannes Reinecke 706539
+	case hwmon_temp_max:
Hannes Reinecke 706539
+		*val = (data->ctrl->wctemp - 273) * 1000;
Hannes Reinecke 706539
+		return 0;
Hannes Reinecke 706539
+	case hwmon_temp_crit:
Hannes Reinecke 706539
+		*val = (data->ctrl->cctemp - 273) * 1000;
Hannes Reinecke 706539
+		return 0;
Hannes Reinecke 706539
+	default:
Hannes Reinecke 706539
+		break;
Hannes Reinecke 706539
+	}
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+	mutex_lock(&data->read_lock);
Hannes Reinecke 706539
+	err = nvme_hwmon_get_smart_log(data);
Hannes Reinecke 706539
+	if (err)
Hannes Reinecke 706539
+		goto unlock;
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+	switch (attr) {
Hannes Reinecke 706539
+	case hwmon_temp_input:
Hannes Reinecke 706539
+		if (!channel)
Hannes Reinecke 706539
+			temp = get_unaligned_le16(log->temperature);
Hannes Reinecke 706539
+		else
Hannes Reinecke 706539
+			temp = le16_to_cpu(log->temp_sensor[channel - 1]);
Hannes Reinecke 706539
+		*val = (temp - 273) * 1000;
Hannes Reinecke 706539
+		break;
Hannes Reinecke 706539
+	case hwmon_temp_alarm:
Hannes Reinecke 706539
+		*val = !!(log->critical_warning & NVME_SMART_CRIT_TEMPERATURE);
Hannes Reinecke 706539
+		break;
Hannes Reinecke 706539
+	default:
Hannes Reinecke 706539
+		err = -EOPNOTSUPP;
Hannes Reinecke 706539
+		break;
Hannes Reinecke 706539
+	}
Hannes Reinecke 706539
+unlock:
Hannes Reinecke 706539
+	mutex_unlock(&data->read_lock);
Hannes Reinecke 706539
+	return err;
Hannes Reinecke 706539
+}
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+static const char * const nvme_hwmon_sensor_names[] = {
Hannes Reinecke 706539
+	"Composite",
Hannes Reinecke 706539
+	"Sensor 1",
Hannes Reinecke 706539
+	"Sensor 2",
Hannes Reinecke 706539
+	"Sensor 3",
Hannes Reinecke 706539
+	"Sensor 4",
Hannes Reinecke 706539
+	"Sensor 5",
Hannes Reinecke 706539
+	"Sensor 6",
Hannes Reinecke 706539
+	"Sensor 7",
Hannes Reinecke 706539
+	"Sensor 8",
Hannes Reinecke 706539
+};
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+static int nvme_hwmon_read_string(struct device *dev,
Hannes Reinecke 706539
+				  enum hwmon_sensor_types type, u32 attr,
Hannes Reinecke 706539
+				  int channel, const char **str)
Hannes Reinecke 706539
+{
Hannes Reinecke 706539
+	*str = nvme_hwmon_sensor_names[channel];
Hannes Reinecke 706539
+	return 0;
Hannes Reinecke 706539
+}
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+static umode_t nvme_hwmon_is_visible(const void *_data,
Hannes Reinecke 706539
+				     enum hwmon_sensor_types type,
Hannes Reinecke 706539
+				     u32 attr, int channel)
Hannes Reinecke 706539
+{
Hannes Reinecke 706539
+	const struct nvme_hwmon_data *data = _data;
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+	switch (attr) {
Hannes Reinecke 706539
+	case hwmon_temp_crit:
Hannes Reinecke 706539
+		if (!channel && data->ctrl->cctemp)
Hannes Reinecke 706539
+			return 0444;
Hannes Reinecke 706539
+		break;
Hannes Reinecke 706539
+	case hwmon_temp_max:
Hannes Reinecke 706539
+		if (!channel && data->ctrl->wctemp)
Hannes Reinecke 706539
+			return 0444;
Hannes Reinecke 706539
+		break;
Hannes Reinecke 706539
+	case hwmon_temp_alarm:
Hannes Reinecke 706539
+		if (!channel)
Hannes Reinecke 706539
+			return 0444;
Hannes Reinecke 706539
+		break;
Hannes Reinecke 706539
+	case hwmon_temp_input:
Hannes Reinecke 706539
+	case hwmon_temp_label:
Hannes Reinecke 706539
+		if (!channel || data->log.temp_sensor[channel - 1])
Hannes Reinecke 706539
+			return 0444;
Hannes Reinecke 706539
+		break;
Hannes Reinecke 706539
+	default:
Hannes Reinecke 706539
+		break;
Hannes Reinecke 706539
+	}
Hannes Reinecke 706539
+	return 0;
Hannes Reinecke 706539
+}
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+static const struct hwmon_channel_info *nvme_hwmon_info[] = {
Hannes Reinecke 706539
+	HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
Hannes Reinecke 706539
+	HWMON_CHANNEL_INFO(temp,
Hannes Reinecke 706539
+			   HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT |
Hannes Reinecke 706539
+				HWMON_T_LABEL | HWMON_T_ALARM,
Hannes Reinecke 706539
+			   HWMON_T_INPUT | HWMON_T_LABEL,
Hannes Reinecke 706539
+			   HWMON_T_INPUT | HWMON_T_LABEL,
Hannes Reinecke 706539
+			   HWMON_T_INPUT | HWMON_T_LABEL,
Hannes Reinecke 706539
+			   HWMON_T_INPUT | HWMON_T_LABEL,
Hannes Reinecke 706539
+			   HWMON_T_INPUT | HWMON_T_LABEL,
Hannes Reinecke 706539
+			   HWMON_T_INPUT | HWMON_T_LABEL,
Hannes Reinecke 706539
+			   HWMON_T_INPUT | HWMON_T_LABEL,
Hannes Reinecke 706539
+			   HWMON_T_INPUT | HWMON_T_LABEL),
Hannes Reinecke 706539
+	NULL
Hannes Reinecke 706539
+};
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+static const struct hwmon_ops nvme_hwmon_ops = {
Hannes Reinecke 706539
+	.is_visible	= nvme_hwmon_is_visible,
Hannes Reinecke 706539
+	.read		= nvme_hwmon_read,
Hannes Reinecke 706539
+	.read_string	= nvme_hwmon_read_string,
Hannes Reinecke 706539
+};
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+static const struct hwmon_chip_info nvme_hwmon_chip_info = {
Hannes Reinecke 706539
+	.ops	= &nvme_hwmon_ops,
Hannes Reinecke 706539
+	.info	= nvme_hwmon_info,
Hannes Reinecke 706539
+};
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+void nvme_hwmon_init(struct nvme_ctrl *ctrl)
Hannes Reinecke 706539
+{
Hannes Reinecke 706539
+	struct device *dev = ctrl->dev;
Hannes Reinecke 706539
+	struct nvme_hwmon_data *data;
Hannes Reinecke 706539
+	struct device *hwmon;
Hannes Reinecke 706539
+	int err;
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
Hannes Reinecke 706539
+	if (!data)
Hannes Reinecke 706539
+		return;
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+	data->ctrl = ctrl;
Hannes Reinecke 706539
+	mutex_init(&data->read_lock);
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+	err = nvme_hwmon_get_smart_log(data);
Hannes Reinecke 706539
+	if (err) {
Hannes Reinecke 706539
+		dev_warn(dev, "Failed to read smart log (error %d)\n", err);
Hannes Reinecke 706539
+		devm_kfree(dev, data);
Hannes Reinecke 706539
+		return;
Hannes Reinecke 706539
+	}
Hannes Reinecke 706539
+
Hannes Reinecke 706539
+	hwmon = devm_hwmon_device_register_with_info(dev, "nvme", data,
Hannes Reinecke 706539
+						     &nvme_hwmon_chip_info,
Hannes Reinecke 706539
+						     NULL);
Hannes Reinecke 706539
+	if (IS_ERR(hwmon)) {
Hannes Reinecke 706539
+		dev_warn(dev, "Failed to instantiate hwmon device\n");
Hannes Reinecke 706539
+		devm_kfree(dev, data);
Hannes Reinecke 706539
+	}
Hannes Reinecke 706539
+}
Hannes Reinecke 706539
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
Hannes Reinecke 706539
index 2637d9dd278f..258534a7bb6c 100644
Hannes Reinecke 706539
--- a/drivers/nvme/host/nvme.h
Hannes Reinecke 706539
+++ b/drivers/nvme/host/nvme.h
Hannes Reinecke 706539
@@ -230,6 +230,8 @@ struct nvme_ctrl {
Hannes Reinecke 706539
 	u16 kas;
Hannes Reinecke 706539
 	u8 npss;
Hannes Reinecke 706539
 	u8 apsta;
Hannes Reinecke 706539
+	u16 wctemp;
Hannes Reinecke 706539
+	u16 cctemp;
Hannes Reinecke 706539
 	u32 oaes;
Hannes Reinecke 706539
 	u32 aen_result;
Hannes Reinecke 706539
 	u32 ctratt;
Hannes Reinecke 706539
@@ -665,4 +667,10 @@ static inline struct nvme_ns *nvme_get_ns_from_dev(struct device *dev)
Hannes Reinecke 706539
 	return dev_to_disk(dev)->private_data;
Hannes Reinecke 706539
 }
Hannes Reinecke 706539
 
Hannes Reinecke 706539
+#ifdef CONFIG_NVME_HWMON
Hannes Reinecke 706539
+void nvme_hwmon_init(struct nvme_ctrl *ctrl);
Hannes Reinecke 706539
+#else
Hannes Reinecke 706539
+static inline void nvme_hwmon_init(struct nvme_ctrl *ctrl) { }
Hannes Reinecke 706539
+#endif
Hannes Reinecke 706539
+
Hannes Reinecke 706539
 #endif /* _NVME_H */
Hannes Reinecke 706539
-- 
Hannes Reinecke 706539
2.16.4
Hannes Reinecke 706539