[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