[PATCH v2 1/1] nvme-hwmon: notify hwmon alarms on the SMART / Health Status AEN

Guixin Liu kanie at linux.alibaba.com
Tue Nov 10 03:07:37 PST 2026


The NVMe hwmon support exposes the temperature alarm as a plain sysfs
attribute, so monitoring tools have to poll temp1_alarm to learn about
a critical temperature condition. The device does send a SMART / Health
Status asynchronous event when a critical warning is raised, but the
host never enables it in the Asynchronous Event Configuration, and the
completion path silently drops the event.

The temperature warning is bit 1 of the SMART / Health Critical
Warnings field of the Asynchronous Event Configuration (feature 0x0b,
command dword 11 bits 7:0), which mirrors the critical_warning byte of
the SMART log. Unlike the notice events, it has no OAES capability bit,
so the subscription does not go through ctrl->oaes.

The event stays latched until the SMART log is read with RAE cleared,
so the deferred work re-reads it before the completion path re-arms
the Asynchronous Event Request, and then calls hwmon_notify_event()
on temp1_alarm. hwmon_notify_event() also feeds the thermal zone
registered with HWMON_C_REGISTER_TZ and emits the standard hwmon
uevent, which a plain sysfs_notify() would skip.

The nvme-pci transport completes asynchronous events from interrupt
context, so the completion path only queues the work.

Signed-off-by: Guixin Liu <kanie at linux.alibaba.com>
---
 drivers/nvme/host/core.c  | 17 ++++++++++++++++-
 drivers/nvme/host/hwmon.c | 36 ++++++++++++++++++++++++++++++++++++
 drivers/nvme/host/nvme.h  | 15 +++++++++++++++
 3 files changed, 67 insertions(+), 1 deletion(-)

diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index cd0df9b038063..e932e1a7e479c 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -1792,6 +1792,17 @@ static void nvme_enable_aen(struct nvme_ctrl *ctrl)
 	u32 result, supported_aens = ctrl->oaes & NVME_AEN_SUPPORTED;
 	int status;
 
+	/*
+	 * Unlike the notice events above, SMART / Health Status events have
+	 * no OAES capability bit: AEC dw11 bits 7:0 are the SMART / Health
+	 * Critical Warnings (SHCW) mask, which mirrors the critical_warning
+	 * byte of the SMART log.  Subscribe to the temperature warning
+	 * whenever the hwmon support is present, as that is the only
+	 * consumer.
+	 */
+	if (nvme_hwmon_present(ctrl))
+		supported_aens |= NVME_SMART_CRIT_TEMPERATURE;
+
 	if (!supported_aens)
 		return;
 
@@ -5009,8 +5020,12 @@ void nvme_complete_async_event(struct nvme_ctrl *ctrl, __le16 status,
 			nvme_handle_aer_persistent_error(ctrl);
 			return;
 		}
-		fallthrough;
+		ctrl->aen_result = result;
+		break;
 	case NVME_AER_SMART:
+		nvme_hwmon_smart_event(ctrl);
+		ctrl->aen_result = result;
+		break;
 	case NVME_AER_CSS:
 	case NVME_AER_VS:
 		ctrl->aen_result = result;
diff --git a/drivers/nvme/host/hwmon.c b/drivers/nvme/host/hwmon.c
index 406203d8b9adb..172ef63f1180c 100644
--- a/drivers/nvme/host/hwmon.c
+++ b/drivers/nvme/host/hwmon.c
@@ -14,6 +14,7 @@ struct nvme_hwmon_data {
 	struct nvme_ctrl *ctrl;
 	struct nvme_smart_log *log;
 	struct mutex read_lock;
+	struct work_struct smart_work;
 };
 
 static int nvme_get_temp_thresh(struct nvme_ctrl *ctrl, int sensor, bool under,
@@ -223,6 +224,39 @@ static const struct hwmon_chip_info nvme_hwmon_chip_info = {
 	.info	= nvme_hwmon_info,
 };
 
+static void nvme_hwmon_smart_notify(struct work_struct *work)
+{
+	struct nvme_hwmon_data *data =
+		container_of(work, struct nvme_hwmon_data, smart_work);
+	struct nvme_ctrl *ctrl = data->ctrl;
+	int ret;
+
+	/*
+	 * The event stays latched on the controller until the SMART log
+	 * is read with RAE cleared, so re-read it here before the
+	 * completion path re-arms the Asynchronous Event Request.
+	 */
+	mutex_lock(&data->read_lock);
+	ret = nvme_hwmon_get_smart_log(data);
+	mutex_unlock(&data->read_lock);
+	if (ret)
+		return;
+
+	hwmon_notify_event(ctrl->hwmon_device, hwmon_temp,
+			   hwmon_temp_alarm, 0);
+}
+
+void nvme_hwmon_smart_event(struct nvme_ctrl *ctrl)
+{
+	struct nvme_hwmon_data *data;
+
+	if (!ctrl->hwmon_device)
+		return;
+
+	data = dev_get_drvdata(ctrl->hwmon_device);
+	queue_work(nvme_wq, &data->smart_work);
+}
+
 int nvme_hwmon_init(struct nvme_ctrl *ctrl)
 {
 	struct device *dev = ctrl->device;
@@ -242,6 +276,7 @@ int nvme_hwmon_init(struct nvme_ctrl *ctrl)
 
 	data->ctrl = ctrl;
 	mutex_init(&data->read_lock);
+	INIT_WORK(&data->smart_work, nvme_hwmon_smart_notify);
 
 	err = nvme_hwmon_get_smart_log(data);
 	if (err) {
@@ -273,6 +308,7 @@ void nvme_hwmon_exit(struct nvme_ctrl *ctrl)
 		struct nvme_hwmon_data *data =
 			dev_get_drvdata(ctrl->hwmon_device);
 
+		cancel_work_sync(&data->smart_work);
 		hwmon_device_unregister(ctrl->hwmon_device);
 		ctrl->hwmon_device = NULL;
 		kfree(data->log);
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index 2543e52d6358a..fe43ab17c3967 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -1276,6 +1276,12 @@ static inline struct nvme_ns *nvme_get_ns_from_dev(struct device *dev)
 #ifdef CONFIG_NVME_HWMON
 int nvme_hwmon_init(struct nvme_ctrl *ctrl);
 void nvme_hwmon_exit(struct nvme_ctrl *ctrl);
+void nvme_hwmon_smart_event(struct nvme_ctrl *ctrl);
+
+static inline bool nvme_hwmon_present(struct nvme_ctrl *ctrl)
+{
+	return ctrl->hwmon_device != NULL;
+}
 #else
 static inline int nvme_hwmon_init(struct nvme_ctrl *ctrl)
 {
@@ -1285,6 +1291,15 @@ static inline int nvme_hwmon_init(struct nvme_ctrl *ctrl)
 static inline void nvme_hwmon_exit(struct nvme_ctrl *ctrl)
 {
 }
+
+static inline void nvme_hwmon_smart_event(struct nvme_ctrl *ctrl)
+{
+}
+
+static inline bool nvme_hwmon_present(struct nvme_ctrl *ctrl)
+{
+	return false;
+}
 #endif
 
 static inline void nvme_start_request(struct request *rq)
-- 
2.43.7




More information about the Linux-nvme mailing list