From: Zinc Lim <limzhineng2@gmail.com>
To: alexanderduyck@fb.com, kuba@kernel.org, andrew+netdev@lunn.ch,
davem@davemloft.net, edumazet@kernel.org, pabeni@redhat.com,
linux@roeck-us.net, horms@kernel.org, hmohsin@meta.com,
mike.marciniszyn@gmail.com
Cc: kernel-team@meta.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org,
zinclim@meta.com, limzhineng2@gmail.com
Subject: [PATCH net-next v4 5/6] eth: fbnic: report temperature and voltage alarms via hwmon
Date: Wed, 14 Oct 2026 15:22:23 -0700 [thread overview]
Message-ID: <20261014222225.2803660-6-limzhineng2@gmail.com> (raw)
In-Reply-To: <20261014222225.2803660-1-limzhineng2@gmail.com>
Firmware reports the range a sensor has entered on every threshold
crossing, including the one back into the normal range. Expose that as
temp1_alarm and in0_alarm, so the alarm reflects a condition the
hardware raised and an excursion that starts and ends between two sysfs
reads is still caught. Each crossing wakes anything polling the
attribute through hwmon_notify_event().
Each alarm is only visible if all of its thresholds are reported by the
firmware. Voltage has no crit threshold, only min and max.
Firmware only reports a range when it changes, so a sensor already out of
range when the driver attaches has nothing cached. The first alarm read
resolves that via a sensor read, its response carrying the range.
Old firmware that doesn't report a range leaves the alarm to be
classified from the reading against the thresholds, with no range to
notify on.
fbnic_hwmon_notify_event() runs from the mailbox IRQ, so it can race
registration and teardown. Publish fbd->hwmon with WRITE_ONCE() only
after a successful registration, clear it with WRITE_ONCE(NULL) followed
by synchronize_irq() to drain any in-flight handler, and skip the
notification when the read returns NULL.
Signed-off-by: Zinc Lim <limzhineng2@gmail.com>
---
drivers/net/ethernet/meta/fbnic/fbnic.h | 1 +
drivers/net/ethernet/meta/fbnic/fbnic_fw.c | 8 +-
drivers/net/ethernet/meta/fbnic/fbnic_hwmon.c | 146 ++++++++++++++++--
3 files changed, 142 insertions(+), 13 deletions(-)
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic.h b/drivers/net/ethernet/meta/fbnic/fbnic.h
index 93bf7004b67d..90e54cffe6a9 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic.h
+++ b/drivers/net/ethernet/meta/fbnic/fbnic.h
@@ -200,6 +200,7 @@ void fbnic_fw_free_mbx(struct fbnic_dev *fbd);
void fbnic_hwmon_register(struct fbnic_dev *fbd);
void fbnic_hwmon_unregister(struct fbnic_dev *fbd);
+void fbnic_hwmon_notify_event(struct fbnic_dev *fbd, int id);
int fbnic_mac_request_irq(struct fbnic_dev *fbd);
void fbnic_mac_free_irq(struct fbnic_dev *fbd);
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c
index 7863f6ba1e7d..9485665cba46 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c
@@ -1738,8 +1738,10 @@ static int fbnic_fw_parse_threshold_exceeded_resp(void *opaque,
alarm, value);
/* Old firmware reports no range, leave the cache alone */
- if (alarm != FBNIC_FW_TSENE_ALARM_DEPRECATED)
+ if (alarm != FBNIC_FW_TSENE_ALARM_DEPRECATED) {
WRITE_ONCE(fbd->hwmon_cache.temp_alarm, alarm);
+ fbnic_hwmon_notify_event(fbd, FBNIC_SENSOR_TEMP);
+ }
rec_flag = true;
}
@@ -1756,8 +1758,10 @@ static int fbnic_fw_parse_threshold_exceeded_resp(void *opaque,
alarm, value);
/* Old firmware reports no range, leave the cache alone */
- if (alarm != FBNIC_FW_TSENE_ALARM_DEPRECATED)
+ if (alarm != FBNIC_FW_TSENE_ALARM_DEPRECATED) {
WRITE_ONCE(fbd->hwmon_cache.volt_alarm, alarm);
+ fbnic_hwmon_notify_event(fbd, FBNIC_SENSOR_VOLTAGE);
+ }
rec_flag = true;
}
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_hwmon.c b/drivers/net/ethernet/meta/fbnic/fbnic_hwmon.c
index 4df808f850cf..310a2c52bb4d 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_hwmon.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_hwmon.c
@@ -29,6 +29,10 @@ static umode_t fbnic_hwmon_is_visible(const void *drvdata,
return fbnic_hwmon_thr_visible(fbd->fw_cap.temp.max);
case hwmon_temp_crit:
return fbnic_hwmon_thr_visible(fbd->fw_cap.temp.crit);
+ case hwmon_temp_alarm:
+ return fbnic_hwmon_thr_visible(fbd->fw_cap.temp.min) &
+ fbnic_hwmon_thr_visible(fbd->fw_cap.temp.max) &
+ fbnic_hwmon_thr_visible(fbd->fw_cap.temp.crit);
}
break;
case hwmon_in:
@@ -39,6 +43,9 @@ static umode_t fbnic_hwmon_is_visible(const void *drvdata,
return fbnic_hwmon_thr_visible(fbd->fw_cap.volt.min);
case hwmon_in_max:
return fbnic_hwmon_thr_visible(fbd->fw_cap.volt.max);
+ case hwmon_in_alarm:
+ return fbnic_hwmon_thr_visible(fbd->fw_cap.volt.min) &
+ fbnic_hwmon_thr_visible(fbd->fw_cap.volt.max);
}
break;
default:
@@ -120,6 +127,74 @@ static int fbnic_hwmon_read_threshold(long thr, long *val)
return 0;
}
+static bool fbnic_hwmon_at_or_above(long val, s32 thr)
+{
+ return thr != FBNIC_SENSOR_NO_DATA && val >= thr;
+}
+
+static bool fbnic_hwmon_below(long val, s32 thr)
+{
+ return thr != FBNIC_SENSOR_NO_DATA && val < thr;
+}
+
+static u8 fbnic_hwmon_classify(const struct fbnic_threshold *thr, long val)
+{
+ if (fbnic_hwmon_at_or_above(val, thr->crit))
+ return FBNIC_FW_TSENE_ALARM_CRIT;
+ if (fbnic_hwmon_at_or_above(val, thr->max))
+ return FBNIC_FW_TSENE_ALARM_ABOVE_MAX;
+ if (fbnic_hwmon_below(val, thr->min))
+ return FBNIC_FW_TSENE_ALARM_BELOW_MIN;
+
+ return FBNIC_FW_TSENE_ALARM_NORMAL;
+}
+
+static int fbnic_hwmon_alarm_read(struct fbnic_dev *fbd,
+ enum hwmon_sensor_types type, long *val)
+{
+ struct fbnic_hwmon_cache *cache = &fbd->hwmon_cache;
+ const struct fbnic_threshold *thr;
+ u8 *cached, alarm;
+ long reading;
+ int id, err;
+
+ switch (type) {
+ case hwmon_temp:
+ thr = &fbd->fw_cap.temp;
+ cached = &cache->temp_alarm;
+ id = FBNIC_SENSOR_TEMP;
+ break;
+ case hwmon_in:
+ thr = &fbd->fw_cap.volt;
+ cached = &cache->volt_alarm;
+ id = FBNIC_SENSOR_VOLTAGE;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ /* Paired with WRITE_ONCE() on the mailbox thread. Firmware only
+ * reports on a threshold crossing, so nothing is cached until the
+ * first one. Read the sensor, whose response carries the range.
+ */
+ alarm = READ_ONCE(*cached);
+ if (alarm == FBNIC_FW_TSENE_ALARM_UNKNOWN) {
+ err = fbnic_hwmon_sensor_read(fbd, id, &reading);
+ if (err)
+ return err;
+
+ alarm = READ_ONCE(*cached);
+
+ /* Firmware too old to report a range, derive it instead */
+ if (alarm == FBNIC_FW_TSENE_ALARM_UNKNOWN)
+ alarm = fbnic_hwmon_classify(thr, reading);
+ }
+
+ *val = alarm != FBNIC_FW_TSENE_ALARM_NORMAL;
+
+ return 0;
+}
+
static int fbnic_hwmon_temp_read(struct fbnic_dev *fbd, u32 attr, long *val)
{
switch (attr) {
@@ -131,6 +206,8 @@ static int fbnic_hwmon_temp_read(struct fbnic_dev *fbd, u32 attr, long *val)
return fbnic_hwmon_read_threshold(fbd->fw_cap.temp.max, val);
case hwmon_temp_crit:
return fbnic_hwmon_read_threshold(fbd->fw_cap.temp.crit, val);
+ case hwmon_temp_alarm:
+ return fbnic_hwmon_alarm_read(fbd, hwmon_temp, val);
default:
return -EOPNOTSUPP;
}
@@ -145,6 +222,8 @@ static int fbnic_hwmon_in_read(struct fbnic_dev *fbd, u32 attr, long *val)
return fbnic_hwmon_read_threshold(fbd->fw_cap.volt.min, val);
case hwmon_in_max:
return fbnic_hwmon_read_threshold(fbd->fw_cap.volt.max, val);
+ case hwmon_in_alarm:
+ return fbnic_hwmon_alarm_read(fbd, hwmon_in, val);
default:
return -EOPNOTSUPP;
}
@@ -172,10 +251,11 @@ static const struct hwmon_ops fbnic_hwmon_ops = {
static const struct hwmon_channel_info *fbnic_hwmon_info[] = {
HWMON_CHANNEL_INFO(temp,
- HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX |
- HWMON_T_CRIT),
+ HWMON_T_INPUT | HWMON_T_ALARM |
+ HWMON_T_MIN | HWMON_T_MAX | HWMON_T_CRIT),
HWMON_CHANNEL_INFO(in,
- HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX),
+ HWMON_I_INPUT | HWMON_I_ALARM |
+ HWMON_I_MIN | HWMON_I_MAX),
NULL
};
@@ -186,25 +266,69 @@ static const struct hwmon_chip_info fbnic_chip_info = {
void fbnic_hwmon_register(struct fbnic_dev *fbd)
{
+ struct device *hwmon;
+
if (!IS_REACHABLE(CONFIG_HWMON))
return;
- fbd->hwmon = hwmon_device_register_with_info(fbd->dev, "fbnic",
- fbd, &fbnic_chip_info,
- NULL);
- if (IS_ERR(fbd->hwmon)) {
+ hwmon = hwmon_device_register_with_info(fbd->dev, "fbnic",
+ fbd, &fbnic_chip_info,
+ NULL);
+ if (IS_ERR(hwmon)) {
dev_notice(fbd->dev,
"Failed to register hwmon device %pe\n",
- fbd->hwmon);
- fbd->hwmon = NULL;
+ hwmon);
+ return;
}
+
+ WRITE_ONCE(fbd->hwmon, hwmon);
}
void fbnic_hwmon_unregister(struct fbnic_dev *fbd)
{
+ struct device *hwmon;
+
if (!IS_REACHABLE(CONFIG_HWMON) || !fbd->hwmon)
return;
- hwmon_device_unregister(fbd->hwmon);
- fbd->hwmon = NULL;
+ hwmon = fbd->hwmon;
+ /* Pair with READ_ONCE() in fbnic_hwmon_notify_event(). Publish NULL
+ * and wait for any in-flight FW mailbox IRQ handler to finish so it
+ * cannot dereference the hwmon device after we unregister it.
+ */
+ WRITE_ONCE(fbd->hwmon, NULL);
+ synchronize_irq(fbd->fw_msix_vector);
+
+ hwmon_device_unregister(hwmon);
+}
+
+void fbnic_hwmon_notify_event(struct fbnic_dev *fbd, int id)
+{
+ enum hwmon_sensor_types type;
+ struct device *hwmon;
+ u32 attr;
+
+ if (!IS_REACHABLE(CONFIG_HWMON))
+ return;
+
+ switch (id) {
+ case FBNIC_SENSOR_TEMP:
+ type = hwmon_temp;
+ attr = hwmon_temp_alarm;
+ break;
+ case FBNIC_SENSOR_VOLTAGE:
+ type = hwmon_in;
+ attr = hwmon_in_alarm;
+ break;
+ default:
+ return;
+ }
+
+ /* Pair with WRITE_ONCE() in fbnic_hwmon_unregister(). Skip the
+ * notification if hwmon failed to register or has already been torn
+ * down.
+ */
+ hwmon = READ_ONCE(fbd->hwmon);
+ if (hwmon)
+ hwmon_notify_event(hwmon, type, attr, 0);
}
--
2.53.0-Meta
next prev parent reply other threads:[~2026-10-14 22:22 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-14 22:22 [PATCH net-next v4 0/6] eth: fbnic: expand hwmon sensor support Zinc Lim
2026-10-14 22:22 ` [PATCH net-next v4 1/6] eth: fbnic: move sensor read logic out of fbnic_mac Zinc Lim
2026-10-14 22:22 ` [PATCH net-next v4 2/6] eth: fbnic: cache hwmon sensor readings Zinc Lim
2026-10-14 22:22 ` [PATCH net-next v4 3/6] eth: fbnic: report temperature and voltage thresholds via hwmon Zinc Lim
2026-10-14 22:22 ` [PATCH net-next v4 4/6] eth: fbnic: parse the alarm range reported by firmware Zinc Lim
2026-10-14 22:22 ` Zinc Lim [this message]
2026-10-14 22:22 ` [PATCH net-next v4 6/6] eth: fbnic: derive the alarm range on older firmware Zinc Lim
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261014222225.2803660-6-limzhineng2@gmail.com \
--to=limzhineng2@gmail.com \
--cc=alexanderduyck@fb.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=hmohsin@meta.com \
--cc=horms@kernel.org \
--cc=kernel-team@meta.com \
--cc=kuba@kernel.org \
--cc=linux-hwmon@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=mike.marciniszyn@gmail.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=zinclim@meta.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox