[PATCH net-next 4/7] bng_en: add support for link async events

From: Bhargava Marreddy

Date: Mon Feb 23 2026 - 11:46:38 EST


Register for firmware asynchronous events, including link-status,
link-speed, and PHY configuration changes. Upon event reception,
re-query the PHY and update ethtool settings accordingly.

Signed-off-by: Bhargava Marreddy <bhargava.marreddy@xxxxxxxxxxxx>
Reviewed-by: Vikas Gupta <vikas.gupta@xxxxxxxxxxxx>
Reviewed-by: Rajashekar Hudumula <rajashekar.hudumula@xxxxxxxxxxxx>
Reviewed-by: Ajit Kumar Khaparde <ajit.khaparde@xxxxxxxxxxxx>
---
.../ethernet/broadcom/bnge/bnge_hwrm_lib.c | 17 ++++++-
.../net/ethernet/broadcom/bnge/bnge_link.c | 47 +++++++++++++++++++
.../net/ethernet/broadcom/bnge/bnge_link.h | 4 ++
.../net/ethernet/broadcom/bnge/bnge_netdev.c | 19 ++++++++
.../net/ethernet/broadcom/bnge/bnge_txrx.c | 35 ++++++++++++--
5 files changed, 117 insertions(+), 5 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c
index 3e6a5bd45a0..43d5ba15728 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_hwrm_lib.c
@@ -15,6 +15,13 @@
#include "bnge_rmem.h"
#include "bnge_resc.h"

+static const u16 bnge_async_events_arr[] = {
+ ASYNC_EVENT_CMPL_EVENT_ID_LINK_STATUS_CHANGE,
+ ASYNC_EVENT_CMPL_EVENT_ID_LINK_SPEED_CHANGE,
+ ASYNC_EVENT_CMPL_EVENT_ID_LINK_SPEED_CFG_CHANGE,
+ ASYNC_EVENT_CMPL_EVENT_ID_PORT_PHY_CFG_CHANGE,
+};
+
int bnge_hwrm_ver_get(struct bnge_dev *bd)
{
u32 dev_caps_cfg, hwrm_ver, hwrm_spec_code;
@@ -166,10 +173,12 @@ int bnge_hwrm_fw_set_time(struct bnge_dev *bd)

int bnge_hwrm_func_drv_rgtr(struct bnge_dev *bd)
{
+ DECLARE_BITMAP(async_events_bmap, 256);
+ u32 *events = (u32 *)async_events_bmap;
struct hwrm_func_drv_rgtr_output *resp;
struct hwrm_func_drv_rgtr_input *req;
u32 flags;
- int rc;
+ int rc, i;

rc = bnge_hwrm_req_init(bd, req, HWRM_FUNC_DRV_RGTR);
if (rc)
@@ -190,6 +199,12 @@ int bnge_hwrm_func_drv_rgtr(struct bnge_dev *bd)
req->ver_min = cpu_to_le16(DRV_VER_MIN);
req->ver_upd = cpu_to_le16(DRV_VER_UPD);

+ memset(async_events_bmap, 0, sizeof(async_events_bmap));
+ for (i = 0; i < ARRAY_SIZE(bnge_async_events_arr); i++)
+ __set_bit(bnge_async_events_arr[i], async_events_bmap);
+ for (i = 0; i < ARRAY_SIZE(req->async_event_fwd); i++)
+ req->async_event_fwd[i] |= cpu_to_le32(events[i]);
+
resp = bnge_hwrm_req_hold(bd, req);
rc = bnge_hwrm_req_send(bd, req);
if (!rc) {
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_link.c b/drivers/net/ethernet/broadcom/bnge/bnge_link.c
index f037229db7b..9f0f9b4db1d 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_link.c
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_link.c
@@ -94,6 +94,21 @@ static void bnge_set_auto_speed(struct bnge_net *bn)
elink_info->advertising_pam4 = link_info->auto_pam4_link_speeds;
}

+static u16 bnge_get_force_speed(struct bnge_net *bn)
+{
+ struct bnge_ethtool_link_info *elink_info = &bn->eth_link_info;
+ struct bnge_link_info *link_info;
+ struct bnge_dev *bd = bn->bd;
+
+ link_info = &bd->link_info;
+
+ if (bd->phy_flags & BNGE_PHY_FL_SPEEDS2)
+ return link_info->force_link_speed2;
+ if (elink_info->req_signal_mode == BNGE_SIG_MODE_PAM4)
+ return link_info->force_pam4_link_speed;
+ return link_info->force_link_speed;
+}
+
static void bnge_set_force_speed(struct bnge_net *bn)
{
struct bnge_ethtool_link_info *elink_info = &bn->eth_link_info;
@@ -1262,3 +1277,35 @@ int bnge_set_link_ksettings(struct net_device *dev,

return rc;
}
+
+void bnge_link_async_event_process(struct bnge_net *bn,
+ u16 event_id, u32 evt_data)
+{
+ switch (event_id) {
+ case ASYNC_EVENT_CMPL_EVENT_ID_LINK_SPEED_CFG_CHANGE: {
+ struct bnge_ethtool_link_info *elink_info = &bn->eth_link_info;
+
+ /* print unsupported speed warning in forced speed mode only */
+ if (!(elink_info->autoneg & BNGE_AUTONEG_SPEED) &&
+ (evt_data & BNGE_SPEED_CFG_CHANGE_SP_NOT_SUPP)) {
+ u16 fw_speed = bnge_get_force_speed(bn);
+ u32 speed = bnge_fw_to_ethtool_speed(fw_speed);
+
+ if (speed != SPEED_UNKNOWN)
+ netdev_warn(bn->netdev, "Link speed %d no longer supported\n",
+ speed);
+ }
+ set_bit(BNGE_LINK_SPEED_CHNG_SP_EVENT, &bn->sp_event);
+ fallthrough;
+ }
+ case ASYNC_EVENT_CMPL_EVENT_ID_LINK_SPEED_CHANGE:
+ case ASYNC_EVENT_CMPL_EVENT_ID_PORT_PHY_CFG_CHANGE:
+ set_bit(BNGE_LINK_CFG_CHANGE_SP_EVENT, &bn->sp_event);
+ fallthrough;
+ case ASYNC_EVENT_CMPL_EVENT_ID_LINK_STATUS_CHANGE:
+ set_bit(BNGE_LINK_CHNG_SP_EVENT, &bn->sp_event);
+ break;
+ default:
+ break;
+ }
+}
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_link.h b/drivers/net/ethernet/broadcom/bnge/bnge_link.h
index 12be5cab579..bdcb75a941c 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_link.h
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_link.h
@@ -197,4 +197,8 @@ int bnge_set_link_ksettings(struct net_device *dev,
int bnge_get_link_ksettings(struct net_device *dev,
struct ethtool_link_ksettings *lk_ksettings);
u32 bnge_get_link(struct net_device *dev);
+#define BNGE_SPEED_CFG_CHANGE_SP_NOT_SUPP BIT(17)
+
+void bnge_link_async_event_process(struct bnge_net *bn,
+ u16 event_id, u32 evt_data);
#endif /* _BNGE_LINK_H_ */
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c
index 2ad0021d38e..4909727547e 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_netdev.c
@@ -164,6 +164,25 @@ static void bnge_sp_task(struct work_struct *work)
}
}

+ if (test_and_clear_bit(BNGE_LINK_CHNG_SP_EVENT, &bn->sp_event)) {
+ int rc;
+
+ mutex_lock(&bd->link_lock);
+ if (test_and_clear_bit(BNGE_LINK_SPEED_CHNG_SP_EVENT,
+ &bn->sp_event))
+ bnge_hwrm_phy_qcaps(bd);
+
+ rc = bnge_update_link(bn, true);
+ if (rc)
+ netdev_err(bn->netdev, "SP task cannot update link (rc: %d)\n",
+ rc);
+
+ if (test_and_clear_bit(BNGE_LINK_CFG_CHANGE_SP_EVENT,
+ &bn->sp_event))
+ bnge_init_ethtool_link_settings(bn);
+ mutex_unlock(&bd->link_lock);
+ }
+
/* Ensure all sp_task work is done before clearing the state */
smp_mb__before_atomic();
clear_bit(BNGE_STATE_IN_SP_TASK, &bn->state);
diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_txrx.c b/drivers/net/ethernet/broadcom/bnge/bnge_txrx.c
index a2616f03755..88f9cf1db30 100644
--- a/drivers/net/ethernet/broadcom/bnge/bnge_txrx.c
+++ b/drivers/net/ethernet/broadcom/bnge/bnge_txrx.c
@@ -1128,6 +1128,29 @@ static void __bnge_poll_work_done(struct bnge_net *bn, struct bnge_napi *bnapi,
}
}

+static void bnge_async_event_process(struct bnge_net *bn,
+ struct hwrm_async_event_cmpl *cmpl)
+{
+ u16 event_id = le16_to_cpu(cmpl->event_id);
+ u32 data1 = le32_to_cpu(cmpl->event_data1);
+ u32 data2 = le32_to_cpu(cmpl->event_data2);
+
+ netdev_dbg(bn->netdev, "hwrm event 0x%x {0x%x, 0x%x}\n",
+ event_id, data1, data2);
+
+ switch (event_id) {
+ case ASYNC_EVENT_CMPL_EVENT_ID_LINK_SPEED_CFG_CHANGE:
+ case ASYNC_EVENT_CMPL_EVENT_ID_LINK_SPEED_CHANGE:
+ case ASYNC_EVENT_CMPL_EVENT_ID_PORT_PHY_CFG_CHANGE:
+ case ASYNC_EVENT_CMPL_EVENT_ID_LINK_STATUS_CHANGE:
+ bnge_link_async_event_process(bn, event_id, data1);
+ break;
+ default:
+ return;
+ }
+ __bnge_queue_sp_work(bn);
+}
+
static void
bnge_hwrm_update_token(struct bnge_dev *bd, u16 seq_id,
enum bnge_hwrm_wait_state state)
@@ -1146,7 +1169,7 @@ bnge_hwrm_update_token(struct bnge_dev *bd, u16 seq_id,
dev_err(bd->dev, "Invalid hwrm seq id %d\n", seq_id);
}

-static int bnge_hwrm_handler(struct bnge_dev *bd, struct tx_cmp *txcmp)
+static int bnge_hwrm_handler(struct bnge_net *bn, struct tx_cmp *txcmp)
{
struct hwrm_cmpl *h_cmpl = (struct hwrm_cmpl *)txcmp;
u16 cmpl_type = TX_CMP_TYPE(txcmp), seq_id;
@@ -1154,10 +1177,14 @@ static int bnge_hwrm_handler(struct bnge_dev *bd, struct tx_cmp *txcmp)
switch (cmpl_type) {
case CMPL_BASE_TYPE_HWRM_DONE:
seq_id = le16_to_cpu(h_cmpl->sequence_id);
- bnge_hwrm_update_token(bd, seq_id, BNGE_HWRM_COMPLETE);
+ bnge_hwrm_update_token(bn->bd, seq_id, BNGE_HWRM_COMPLETE);
break;

case CMPL_BASE_TYPE_HWRM_ASYNC_EVENT:
+ bnge_async_event_process(bn,
+ (struct hwrm_async_event_cmpl *)txcmp);
+ break;
+
default:
break;
}
@@ -1235,7 +1262,7 @@ static int __bnge_poll_work(struct bnge_net *bn, struct bnge_cp_ring_info *cpr,
} else if (unlikely(cmp_type == CMPL_BASE_TYPE_HWRM_DONE ||
cmp_type == CMPL_BASE_TYPE_HWRM_FWD_REQ ||
cmp_type == CMPL_BA_TY_HWRM_ASY_EVT)) {
- bnge_hwrm_handler(bn->bd, txcmp);
+ bnge_hwrm_handler(bn, txcmp);
}
raw_cons = NEXT_RAW_CMP(raw_cons);

@@ -1355,7 +1382,7 @@ int bnge_napi_poll(struct napi_struct *napi, int budget)
budget - work_done);
nqr->has_more_work |= cpr->has_more_work;
} else {
- bnge_hwrm_handler(bn->bd, (struct tx_cmp *)nqcmp);
+ bnge_hwrm_handler(bn, (struct tx_cmp *)nqcmp);
}
raw_cons = NEXT_RAW_CMP(raw_cons);
}
--
2.47.3