Netdev List
 help / color / mirror / Atom feed
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


  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