[PATCH net-next 3/6] eth: fbnic: cache hwmon sensor readings

From: Zinc Lim

Date: Tue Jul 21 2026 - 20:52:46 EST


Each hwmon attribute access triggers its own TSENE firmware mailbox
round-trip, so reading the full set of attributes or polling them at a
high rate floods the firmware mailbox with quick, successive IPC messages
for data that barely changes between ticks.

Cache the last temperature and voltage reading and serve reads from it
for the remainder of the current jiffy. A single TSENE response carries
both readings, so one transaction on a miss refreshes both and satisfies
a whole batch of reads. The cache is seeded with the FBNIC_SENSOR_NO_DATA
sentinel at registration so the first read always refreshes, and
concurrent reads are serialized by the hwmon core so no additional
locking is required.

Signed-off-by: Zinc Lim <limzhineng2@xxxxxxxxx>
---
drivers/net/ethernet/meta/fbnic/fbnic.h | 7 ++++
drivers/net/ethernet/meta/fbnic/fbnic_fw.h | 7 ++++
drivers/net/ethernet/meta/fbnic/fbnic_hwmon.c | 36 +++++++++++++------
3 files changed, 40 insertions(+), 10 deletions(-)

diff --git a/drivers/net/ethernet/meta/fbnic/fbnic.h b/drivers/net/ethernet/meta/fbnic/fbnic.h
index d0715695c43e..f647ef07704b 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic.h
+++ b/drivers/net/ethernet/meta/fbnic/fbnic.h
@@ -22,11 +22,18 @@ struct fbnic_napi_vector;
#define FBNIC_MAX_NAPI_VECTORS 128u
#define FBNIC_MBX_CMPL_SLOTS 4

+struct fbnic_hwmon_cache {
+ unsigned long last_read;
+ s32 temp_mdeg;
+ s32 volt_mv;
+};
+
struct fbnic_dev {
struct device *dev;
struct net_device *netdev;
struct dentry *dbg_fbd;
struct device *hwmon;
+ struct fbnic_hwmon_cache hwmon_cache;
struct devlink_health_reporter *fw_reporter;
struct devlink_health_reporter *otp_reporter;

diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.h b/drivers/net/ethernet/meta/fbnic/fbnic_fw.h
index d84723e4cfa3..42a5f83ddb45 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.h
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.h
@@ -6,6 +6,7 @@

#include <linux/completion.h>
#include <linux/if_ether.h>
+#include <linux/limits.h>
#include <linux/types.h>

struct fbnic_dev;
@@ -44,6 +45,12 @@ struct fbnic_fw_ver {
char commit[FBNIC_FW_CAP_RESP_COMMIT_MAX_SIZE];
};

+/* Sentinel for a sensor value the driver does not have: a threshold the
+ * firmware never populated (older firmware) or a cache entry not yet
+ * refreshed.
+ */
+#define FBNIC_SENSOR_NO_DATA S32_MIN
+
struct fbnic_fw_cap {
struct {
struct fbnic_fw_ver mgmt, bootloader;
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_hwmon.c b/drivers/net/ethernet/meta/fbnic/fbnic_hwmon.c
index 38bb26cb8e6c..f35cb0065093 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_hwmon.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_hwmon.c
@@ -2,6 +2,7 @@
/* Copyright (c) Meta Platforms, Inc. and affiliates. */

#include <linux/hwmon.h>
+#include <linux/jiffies.h>

#include "fbnic.h"
#include "fbnic_mac.h"
@@ -25,26 +26,32 @@ static umode_t fbnic_hwmon_is_visible(const void *drvdata,

static int fbnic_hwmon_sensor_read(struct fbnic_dev *fbd, int id, long *val)
{
+ struct fbnic_hwmon_cache *cache = &fbd->hwmon_cache;
struct fbnic_fw_completion *fw_cmpl;
int err = 0;
- s32 *sensor;
-
- fw_cmpl = fbnic_fw_alloc_cmpl(FBNIC_TLV_MSG_ID_TSENE_READ_RESP);
- if (!fw_cmpl)
- return -ENOMEM;
+ s32 *cached;

switch (id) {
case FBNIC_SENSOR_TEMP:
- sensor = &fw_cmpl->u.tsene.millidegrees;
+ cached = &cache->temp_mdeg;
break;
case FBNIC_SENSOR_VOLTAGE:
- sensor = &fw_cmpl->u.tsene.millivolts;
+ cached = &cache->volt_mv;
break;
default:
- err = -EINVAL;
- goto exit_free;
+ return -EINVAL;
+ }
+
+ if (*cached != FBNIC_SENSOR_NO_DATA &&
+ time_is_after_eq_jiffies(cache->last_read)) {
+ *val = *cached;
+ return 0;
}

+ fw_cmpl = fbnic_fw_alloc_cmpl(FBNIC_TLV_MSG_ID_TSENE_READ_RESP);
+ if (!fw_cmpl)
+ return -ENOMEM;
+
err = fbnic_fw_xmit_tsene_read_msg(fbd, fw_cmpl);
if (err) {
dev_err(fbd->dev,
@@ -67,7 +74,12 @@ static int fbnic_hwmon_sensor_read(struct fbnic_dev *fbd, int id, long *val)
goto exit_cleanup;
}

- *val = *sensor;
+ /* FW returns both readings in one response, cache both. */
+ cache->temp_mdeg = fw_cmpl->u.tsene.millidegrees;
+ cache->volt_mv = fw_cmpl->u.tsene.millivolts;
+ cache->last_read = jiffies;
+
+ *val = *cached;
exit_cleanup:
fbnic_mbx_clear_cmpl(fbd, fw_cmpl);
exit_free:
@@ -107,6 +119,10 @@ void fbnic_hwmon_register(struct fbnic_dev *fbd)
if (!IS_REACHABLE(CONFIG_HWMON))
return;

+ /* Seed cache with sentinel so the first read always refreshes. */
+ fbd->hwmon_cache.temp_mdeg = FBNIC_SENSOR_NO_DATA;
+ fbd->hwmon_cache.volt_mv = FBNIC_SENSOR_NO_DATA;
+
fbd->hwmon = hwmon_device_register_with_info(fbd->dev, "fbnic",
fbd, &fbnic_chip_info,
NULL);
--
2.53.0-Meta