Merge branch 'eth-fbnic-add-hardware-monitoring-support'

Sanman Pradhan says:

====================
eth: fbnic: Add hardware monitoring support

This patch series adds hardware monitoring support to the fbnic driver.
It implements support for reading temperature and voltage sensors via
firmware requests, and exposes this data through the HWMON interface.

The series is structured as follows:

Patch 1: Adds completion infrastructure for firmware requests
Patch 2: Implements TSENE sensor message handling
Patch 3: Adds HWMON interface support

Output:
$ ls -l /sys/class/hwmon/hwmon1/
total 0
lrwxrwxrwx 1 root root    0 Sep 10 00:00 device -> ../../../0000:01:00.0
-r--r--r-- 1 root root 4096 Sep 10 00:00 in0_input
-r--r--r-- 1 root root 4096 Sep 10 00:00 name
lrwxrwxrwx 1 root root    0 Sep 10 00:00 subsystem -> ../../../../../../class/hwmon
-r--r--r-- 1 root root 4096 Sep 10 00:00 temp1_input
-rw-r--r-- 1 root root 4096 Sep 10 00:00 uevent

$ cat /sys/class/hwmon/hwmon1/temp1_input
40480
$ cat /sys/class/hwmon/hwmon1/in0_input
750
====================

Link: https://patch.msgid.link/20250114000705.2081288-1-sanman.p211993@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2025-01-15 14:14:15 -08:00
commit 2974e66ba0
8 changed files with 357 additions and 0 deletions

View File

@ -13,6 +13,7 @@ fbnic-y := fbnic_csr.o \
fbnic_ethtool.o \
fbnic_fw.o \
fbnic_hw_stats.o \
fbnic_hwmon.o \
fbnic_irq.o \
fbnic_mac.o \
fbnic_netdev.o \

View File

@ -24,6 +24,7 @@ struct fbnic_dev {
struct device *dev;
struct net_device *netdev;
struct dentry *dbg_fbd;
struct device *hwmon;
u32 __iomem *uc_addr0;
u32 __iomem *uc_addr4;
@ -41,6 +42,7 @@ struct fbnic_dev {
struct fbnic_fw_mbx mbx[FBNIC_IPC_MBX_INDICES];
struct fbnic_fw_cap fw_cap;
struct fbnic_fw_completion *cmpl_data;
/* Lock protecting Tx Mailbox queue to prevent possible races */
spinlock_t fw_tx_lock;
@ -149,6 +151,9 @@ void fbnic_devlink_unregister(struct fbnic_dev *fbd);
int fbnic_fw_enable_mbx(struct fbnic_dev *fbd);
void fbnic_fw_disable_mbx(struct fbnic_dev *fbd);
void fbnic_hwmon_register(struct fbnic_dev *fbd);
void fbnic_hwmon_unregister(struct fbnic_dev *fbd);
int fbnic_pcs_irq_enable(struct fbnic_dev *fbd);
void fbnic_pcs_irq_disable(struct fbnic_dev *fbd);

View File

@ -228,6 +228,63 @@ static void fbnic_mbx_process_tx_msgs(struct fbnic_dev *fbd)
tx_mbx->head = head;
}
static int fbnic_mbx_map_req_w_cmpl(struct fbnic_dev *fbd,
struct fbnic_tlv_msg *msg,
struct fbnic_fw_completion *cmpl_data)
{
unsigned long flags;
int err;
spin_lock_irqsave(&fbd->fw_tx_lock, flags);
/* If we are already waiting on a completion then abort */
if (cmpl_data && fbd->cmpl_data) {
err = -EBUSY;
goto unlock_mbx;
}
/* Record completion location and submit request */
if (cmpl_data)
fbd->cmpl_data = cmpl_data;
err = fbnic_mbx_map_msg(fbd, FBNIC_IPC_MBX_TX_IDX, msg,
le16_to_cpu(msg->hdr.len) * sizeof(u32), 1);
/* If msg failed then clear completion data for next caller */
if (err && cmpl_data)
fbd->cmpl_data = NULL;
unlock_mbx:
spin_unlock_irqrestore(&fbd->fw_tx_lock, flags);
return err;
}
static void fbnic_fw_release_cmpl_data(struct kref *kref)
{
struct fbnic_fw_completion *cmpl_data;
cmpl_data = container_of(kref, struct fbnic_fw_completion,
ref_count);
kfree(cmpl_data);
}
static struct fbnic_fw_completion *
fbnic_fw_get_cmpl_by_type(struct fbnic_dev *fbd, u32 msg_type)
{
struct fbnic_fw_completion *cmpl_data = NULL;
unsigned long flags;
spin_lock_irqsave(&fbd->fw_tx_lock, flags);
if (fbd->cmpl_data && fbd->cmpl_data->msg_type == msg_type) {
cmpl_data = fbd->cmpl_data;
kref_get(&fbd->cmpl_data->ref_count);
}
spin_unlock_irqrestore(&fbd->fw_tx_lock, flags);
return cmpl_data;
}
/**
* fbnic_fw_xmit_simple_msg - Transmit a simple single TLV message w/o data
* @fbd: FBNIC device structure
@ -651,6 +708,84 @@ void fbnic_fw_check_heartbeat(struct fbnic_dev *fbd)
dev_warn(fbd->dev, "Failed to send heartbeat message\n");
}
/**
* fbnic_fw_xmit_tsene_read_msg - Create and transmit a sensor read request
* @fbd: FBNIC device structure
* @cmpl_data: Completion data structure to store sensor response
*
* Asks the firmware to provide an update with the latest sensor data.
* The response will contain temperature and voltage readings.
*
* Return: 0 on success, negative error value on failure
*/
int fbnic_fw_xmit_tsene_read_msg(struct fbnic_dev *fbd,
struct fbnic_fw_completion *cmpl_data)
{
struct fbnic_tlv_msg *msg;
int err;
if (!fbnic_fw_present(fbd))
return -ENODEV;
msg = fbnic_tlv_msg_alloc(FBNIC_TLV_MSG_ID_TSENE_READ_REQ);
if (!msg)
return -ENOMEM;
err = fbnic_mbx_map_req_w_cmpl(fbd, msg, cmpl_data);
if (err)
goto free_message;
return 0;
free_message:
free_page((unsigned long)msg);
return err;
}
static const struct fbnic_tlv_index fbnic_tsene_read_resp_index[] = {
FBNIC_TLV_ATTR_S32(FBNIC_TSENE_THERM),
FBNIC_TLV_ATTR_S32(FBNIC_TSENE_VOLT),
FBNIC_TLV_ATTR_S32(FBNIC_TSENE_ERROR),
FBNIC_TLV_ATTR_LAST
};
static int fbnic_fw_parse_tsene_read_resp(void *opaque,
struct fbnic_tlv_msg **results)
{
struct fbnic_fw_completion *cmpl_data;
struct fbnic_dev *fbd = opaque;
int err = 0;
/* Verify we have a completion pointer to provide with data */
cmpl_data = fbnic_fw_get_cmpl_by_type(fbd,
FBNIC_TLV_MSG_ID_TSENE_READ_RESP);
if (!cmpl_data)
return -EINVAL;
if (results[FBNIC_TSENE_ERROR]) {
err = fbnic_tlv_attr_get_unsigned(results[FBNIC_TSENE_ERROR]);
if (err)
goto exit_complete;
}
if (!results[FBNIC_TSENE_THERM] || !results[FBNIC_TSENE_VOLT]) {
err = -EINVAL;
goto exit_complete;
}
cmpl_data->u.tsene.millidegrees =
fbnic_tlv_attr_get_signed(results[FBNIC_TSENE_THERM]);
cmpl_data->u.tsene.millivolts =
fbnic_tlv_attr_get_signed(results[FBNIC_TSENE_VOLT]);
exit_complete:
cmpl_data->result = err;
complete(&cmpl_data->done);
fbnic_fw_put_cmpl(cmpl_data);
return err;
}
static const struct fbnic_tlv_parser fbnic_fw_tlv_parser[] = {
FBNIC_TLV_PARSER(FW_CAP_RESP, fbnic_fw_cap_resp_index,
fbnic_fw_parse_cap_resp),
@ -658,6 +793,9 @@ static const struct fbnic_tlv_parser fbnic_fw_tlv_parser[] = {
fbnic_fw_parse_ownership_resp),
FBNIC_TLV_PARSER(HEARTBEAT_RESP, fbnic_heartbeat_resp_index,
fbnic_fw_parse_heartbeat_resp),
FBNIC_TLV_PARSER(TSENE_READ_RESP,
fbnic_tsene_read_resp_index,
fbnic_fw_parse_tsene_read_resp),
FBNIC_TLV_MSG_ERROR
};
@ -802,3 +940,25 @@ void fbnic_get_fw_ver_commit_str(struct fbnic_dev *fbd, char *fw_version,
fbnic_mk_full_fw_ver_str(mgmt->version, delim, mgmt->commit,
fw_version, str_sz);
}
void fbnic_fw_init_cmpl(struct fbnic_fw_completion *fw_cmpl,
u32 msg_type)
{
fw_cmpl->msg_type = msg_type;
init_completion(&fw_cmpl->done);
kref_init(&fw_cmpl->ref_count);
}
void fbnic_fw_clear_compl(struct fbnic_dev *fbd)
{
unsigned long flags;
spin_lock_irqsave(&fbd->fw_tx_lock, flags);
fbd->cmpl_data = NULL;
spin_unlock_irqrestore(&fbd->fw_tx_lock, flags);
}
void fbnic_fw_put_cmpl(struct fbnic_fw_completion *fw_cmpl)
{
kref_put(&fw_cmpl->ref_count, fbnic_fw_release_cmpl_data);
}

View File

@ -44,6 +44,19 @@ struct fbnic_fw_cap {
u8 link_fec;
};
struct fbnic_fw_completion {
u32 msg_type;
struct completion done;
struct kref ref_count;
int result;
union {
struct {
s32 millivolts;
s32 millidegrees;
} tsene;
} u;
};
void fbnic_mbx_init(struct fbnic_dev *fbd);
void fbnic_mbx_clean(struct fbnic_dev *fbd);
void fbnic_mbx_poll(struct fbnic_dev *fbd);
@ -52,6 +65,12 @@ void fbnic_mbx_flush_tx(struct fbnic_dev *fbd);
int fbnic_fw_xmit_ownership_msg(struct fbnic_dev *fbd, bool take_ownership);
int fbnic_fw_init_heartbeat(struct fbnic_dev *fbd, bool poll);
void fbnic_fw_check_heartbeat(struct fbnic_dev *fbd);
int fbnic_fw_xmit_tsene_read_msg(struct fbnic_dev *fbd,
struct fbnic_fw_completion *cmpl_data);
void fbnic_fw_init_cmpl(struct fbnic_fw_completion *cmpl_data,
u32 msg_type);
void fbnic_fw_clear_compl(struct fbnic_dev *fbd);
void fbnic_fw_put_cmpl(struct fbnic_fw_completion *cmpl_data);
#define fbnic_mk_full_fw_ver_str(_rev_id, _delim, _commit, _str, _str_sz) \
do { \
@ -76,6 +95,8 @@ enum {
FBNIC_TLV_MSG_ID_OWNERSHIP_RESP = 0x13,
FBNIC_TLV_MSG_ID_HEARTBEAT_REQ = 0x14,
FBNIC_TLV_MSG_ID_HEARTBEAT_RESP = 0x15,
FBNIC_TLV_MSG_ID_TSENE_READ_REQ = 0x3C,
FBNIC_TLV_MSG_ID_TSENE_READ_RESP = 0x3D,
};
#define FBNIC_FW_CAP_RESP_VERSION_MAJOR CSR_GENMASK(31, 24)
@ -117,6 +138,13 @@ enum {
FBNIC_FW_LINK_FEC_BASER = 3,
};
enum {
FBNIC_TSENE_THERM = 0x0,
FBNIC_TSENE_VOLT = 0x1,
FBNIC_TSENE_ERROR = 0x2,
FBNIC_TSENE_MSG_MAX
};
enum {
FBNIC_FW_OWNERSHIP_FLAG = 0x0,
FBNIC_FW_OWNERSHIP_MSG_MAX

View File

@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/hwmon.h>
#include "fbnic.h"
#include "fbnic_mac.h"
static int fbnic_hwmon_sensor_id(enum hwmon_sensor_types type)
{
if (type == hwmon_temp)
return FBNIC_SENSOR_TEMP;
if (type == hwmon_in)
return FBNIC_SENSOR_VOLTAGE;
return -EOPNOTSUPP;
}
static umode_t fbnic_hwmon_is_visible(const void *drvdata,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
if (type == hwmon_temp && attr == hwmon_temp_input)
return 0444;
if (type == hwmon_in && attr == hwmon_in_input)
return 0444;
return 0;
}
static int fbnic_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct fbnic_dev *fbd = dev_get_drvdata(dev);
const struct fbnic_mac *mac = fbd->mac;
int id;
id = fbnic_hwmon_sensor_id(type);
return id < 0 ? id : mac->get_sensor(fbd, id, val);
}
static const struct hwmon_ops fbnic_hwmon_ops = {
.is_visible = fbnic_hwmon_is_visible,
.read = fbnic_hwmon_read,
};
static const struct hwmon_channel_info *fbnic_hwmon_info[] = {
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
HWMON_CHANNEL_INFO(in, HWMON_I_INPUT),
NULL
};
static const struct hwmon_chip_info fbnic_chip_info = {
.ops = &fbnic_hwmon_ops,
.info = fbnic_hwmon_info,
};
void fbnic_hwmon_register(struct fbnic_dev *fbd)
{
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)) {
dev_notice(fbd->dev,
"Failed to register hwmon device %pe\n",
fbd->hwmon);
fbd->hwmon = NULL;
}
}
void fbnic_hwmon_unregister(struct fbnic_dev *fbd)
{
if (!IS_REACHABLE(CONFIG_HWMON) || !fbd->hwmon)
return;
hwmon_device_unregister(fbd->hwmon);
fbd->hwmon = NULL;
}

View File

@ -686,6 +686,77 @@ fbnic_mac_get_eth_mac_stats(struct fbnic_dev *fbd, bool reset,
MAC_STAT_TX_BROADCAST);
}
static int fbnic_mac_get_sensor_asic(struct fbnic_dev *fbd, int id,
long *val)
{
struct fbnic_fw_completion *fw_cmpl;
int err = 0, retries = 5;
s32 *sensor;
fw_cmpl = kzalloc(sizeof(*fw_cmpl), GFP_KERNEL);
if (!fw_cmpl)
return -ENOMEM;
/* Initialize completion and queue it for FW to process */
fbnic_fw_init_cmpl(fw_cmpl, FBNIC_TLV_MSG_ID_TSENE_READ_RESP);
switch (id) {
case FBNIC_SENSOR_TEMP:
sensor = &fw_cmpl->u.tsene.millidegrees;
break;
case FBNIC_SENSOR_VOLTAGE:
sensor = &fw_cmpl->u.tsene.millivolts;
break;
default:
err = -EINVAL;
goto exit_free;
}
err = fbnic_fw_xmit_tsene_read_msg(fbd, fw_cmpl);
if (err) {
dev_err(fbd->dev,
"Failed to transmit TSENE read msg, err %d\n",
err);
goto exit_free;
}
/* Allow 2 seconds for reply, resend and try up to 5 times */
while (!wait_for_completion_timeout(&fw_cmpl->done, 2 * HZ)) {
retries--;
if (retries == 0) {
dev_err(fbd->dev,
"Timed out waiting for TSENE read\n");
err = -ETIMEDOUT;
goto exit_cleanup;
}
err = fbnic_fw_xmit_tsene_read_msg(fbd, NULL);
if (err) {
dev_err(fbd->dev,
"Failed to transmit TSENE read msg, err %d\n",
err);
goto exit_cleanup;
}
}
/* Handle error returned by firmware */
if (fw_cmpl->result) {
err = fw_cmpl->result;
dev_err(fbd->dev, "%s: Firmware returned error %d\n",
__func__, err);
goto exit_cleanup;
}
*val = *sensor;
exit_cleanup:
fbnic_fw_clear_compl(fbd);
exit_free:
fbnic_fw_put_cmpl(fw_cmpl);
return err;
}
static const struct fbnic_mac fbnic_mac_asic = {
.init_regs = fbnic_mac_init_regs,
.pcs_enable = fbnic_pcs_enable_asic,
@ -695,6 +766,7 @@ static const struct fbnic_mac fbnic_mac_asic = {
.get_eth_mac_stats = fbnic_mac_get_eth_mac_stats,
.link_down = fbnic_mac_link_down_asic,
.link_up = fbnic_mac_link_up_asic,
.get_sensor = fbnic_mac_get_sensor_asic,
};
/**

View File

@ -47,6 +47,11 @@ enum {
#define FBNIC_LINK_MODE_PAM4 (FBNIC_LINK_50R1)
#define FBNIC_LINK_MODE_MASK (FBNIC_LINK_AUTO - 1)
enum fbnic_sensor_id {
FBNIC_SENSOR_TEMP, /* Temp in millidegrees Centigrade */
FBNIC_SENSOR_VOLTAGE, /* Voltage in millivolts */
};
/* This structure defines the interface hooks for the MAC. The MAC hooks
* will be configured as a const struct provided with a set of function
* pointers.
@ -83,6 +88,8 @@ struct fbnic_mac {
void (*link_down)(struct fbnic_dev *fbd);
void (*link_up)(struct fbnic_dev *fbd, bool tx_pause, bool rx_pause);
int (*get_sensor)(struct fbnic_dev *fbd, int id, long *val);
};
int fbnic_mac_init(struct fbnic_dev *fbd);

View File

@ -296,6 +296,8 @@ static int fbnic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
/* Capture snapshot of hardware stats so netdev can calculate delta */
fbnic_reset_hw_stats(fbd);
fbnic_hwmon_register(fbd);
if (!fbd->dsn) {
dev_warn(&pdev->dev, "Reading serial number failed\n");
goto init_failure_mode;
@ -358,6 +360,7 @@ static void fbnic_remove(struct pci_dev *pdev)
fbnic_netdev_free(fbd);
}
fbnic_hwmon_unregister(fbd);
fbnic_dbg_fbd_exit(fbd);
fbnic_devlink_unregister(fbd);
fbnic_fw_disable_mbx(fbd);