The hn_dev_link_update() function issues RNDIS queries for link status
and link speed on every call. Since this function can be called from
multiple threads concurrently, it triggers the same RNDIS command
concurrency issue where only one request can be pending at a time.
Remove the hn_rndis_get_linkstatus() and hn_rndis_get_linkspeed()
functions and modify hn_dev_link_update() to use the cached values
directly. The link status and speed are already:
1. Queried once during device attach in hn_rndis_attach()
2. Updated via RNDIS status indications (MEDIA_CONNECT/DISCONNECT
and LINK_SPEED_CHANGE) handled in hn_rndis_link_status()
This matches the Linux netvsc driver behavior where link status is
queried once in rndis_filter_device_add() and then tracked via
status indications.
Fixes: 4e9c73e96e83 ("net/netvsc: add Hyper-V network device")
Cc: [email protected]
Reported-by: Madhuker Mythri <[email protected]>
Signed-off-by: Stephen Hemminger <[email protected]>
---
drivers/net/netvsc/hn_ethdev.c | 13 ++++---
drivers/net/netvsc/hn_rndis.c | 65 ++++++++++++++++++++++++++--------
drivers/net/netvsc/hn_rndis.h | 2 --
3 files changed, 57 insertions(+), 23 deletions(-)
diff --git a/drivers/net/netvsc/hn_ethdev.c b/drivers/net/netvsc/hn_ethdev.c
index 6584819f4f..c4580d0bb3 100644
--- a/drivers/net/netvsc/hn_ethdev.c
+++ b/drivers/net/netvsc/hn_ethdev.c
@@ -246,16 +246,15 @@ hn_dev_link_update(struct rte_eth_dev *dev,
{
struct hn_data *hv = dev->data->dev_private;
struct rte_eth_link link, old;
- int error;
old = dev->data->dev_link;
- error = hn_rndis_get_linkstatus(hv);
- if (error)
- return error;
-
- hn_rndis_get_linkspeed(hv);
-
+ /*
+ * Use cached link_status and link_speed which are set during
+ * device attach and updated via RNDIS status indications
+ * (MEDIA_CONNECT/DISCONNECT and LINK_SPEED_CHANGE).
+ * This avoids RNDIS queries which are not thread-safe.
+ */
link = (struct rte_eth_link) {
.link_duplex = RTE_ETH_LINK_FULL_DUPLEX,
.link_autoneg = RTE_ETH_LINK_SPEED_FIXED,
diff --git a/drivers/net/netvsc/hn_rndis.c b/drivers/net/netvsc/hn_rndis.c
index ed053ee905..c85953af34 100644
--- a/drivers/net/netvsc/hn_rndis.c
+++ b/drivers/net/netvsc/hn_rndis.c
@@ -299,6 +299,7 @@ static void hn_rndis_link_alarm(void *arg)
void hn_rndis_link_status(struct rte_eth_dev *dev, const void *msg)
{
const struct rndis_status_msg *indicate = msg;
+ struct hn_data *hv = dev->data->dev_private;
hn_rndis_dump(msg);
@@ -311,11 +312,41 @@ void hn_rndis_link_status(struct rte_eth_dev *dev, const
void *msg)
break;
case RNDIS_STATUS_LINK_SPEED_CHANGE:
+ /*
+ * Update link speed from the status buffer.
+ * This matches the Linux netvsc driver behavior.
+ */
+ if (indicate->stbuflen >= sizeof(uint32_t)) {
+ const uint8_t *p = (const uint8_t *)msg;
+ uint32_t offset =
RNDIS_STBUFOFFSET_ABS(indicate->stbufoffset);
+
+ if (offset + sizeof(uint32_t) <= indicate->len) {
+ uint32_t speed;
+
+ memcpy(&speed, p + offset, sizeof(speed));
+ hv->link_speed = speed;
+ PMD_DRV_LOG(DEBUG, "link speed changed to %u",
+ speed);
+ }
+ }
+ if (dev->data->dev_conf.intr_conf.lsc)
+ rte_eal_alarm_set(10, hn_rndis_link_alarm, dev);
+ break;
+
case RNDIS_STATUS_MEDIA_CONNECT:
+ hv->link_status = NDIS_MEDIA_STATE_CONNECTED;
+ PMD_DRV_LOG(INFO, "link connected");
+ if (dev->data->dev_conf.intr_conf.lsc)
+ rte_eal_alarm_set(10, hn_rndis_link_alarm, dev);
+ break;
+
case RNDIS_STATUS_MEDIA_DISCONNECT:
+ hv->link_status = NDIS_MEDIA_STATE_DISCONNECTED;
+ PMD_DRV_LOG(INFO, "link disconnected");
if (dev->data->dev_conf.intr_conf.lsc)
rte_eal_alarm_set(10, hn_rndis_link_alarm, dev);
break;
+
default:
PMD_DRV_LOG(NOTICE, "unknown RNDIS indication: %#x",
indicate->status);
@@ -1095,20 +1126,6 @@ hn_rndis_get_mtu(struct hn_data *hv, uint32_t *mtu)
mtu, sizeof(uint32_t));
}
-int
-hn_rndis_get_linkstatus(struct hn_data *hv)
-{
- return hn_rndis_query(hv, OID_GEN_MEDIA_CONNECT_STATUS, NULL, 0,
- &hv->link_status, sizeof(uint32_t));
-}
-
-int
-hn_rndis_get_linkspeed(struct hn_data *hv)
-{
- return hn_rndis_query(hv, OID_GEN_LINK_SPEED, NULL, 0,
- &hv->link_speed, sizeof(uint32_t));
-}
-
int
hn_rndis_attach(struct hn_data *hv)
{
@@ -1133,6 +1150,26 @@ hn_rndis_attach(struct hn_data *hv)
return error;
}
+ /*
+ * Query initial link status and speed. These will be updated
+ * via RNDIS status indications (MEDIA_CONNECT/DISCONNECT and
+ * LINK_SPEED_CHANGE) rather than being re-queried each time.
+ * This matches the Linux netvsc driver behavior.
+ */
+ error = hn_rndis_query(hv, OID_GEN_MEDIA_CONNECT_STATUS, NULL, 0,
+ &hv->link_status, sizeof(hv->link_status));
+ if (error) {
+ PMD_DRV_LOG(ERR, "failed to query link status: %d", error);
+ return error;
+ }
+
+ error = hn_rndis_query(hv, OID_GEN_LINK_SPEED, NULL, 0,
+ &hv->link_speed, sizeof(hv->link_speed));
+ if (error) {
+ PMD_DRV_LOG(ERR, "failed to query link speed: %d", error);
+ return error;
+ }
+
return 0;
}
diff --git a/drivers/net/netvsc/hn_rndis.h b/drivers/net/netvsc/hn_rndis.h
index 7f40f6221d..26854f45a4 100644
--- a/drivers/net/netvsc/hn_rndis.h
+++ b/drivers/net/netvsc/hn_rndis.h
@@ -11,8 +11,6 @@ int hn_rndis_attach(struct hn_data *hv);
void hn_rndis_detach(struct hn_data *hv);
int hn_rndis_get_eaddr(struct hn_data *hv, uint8_t *eaddr);
int hn_rndis_get_mtu(struct hn_data *hv, uint32_t *mtu);
-int hn_rndis_get_linkstatus(struct hn_data *hv);
-int hn_rndis_get_linkspeed(struct hn_data *hv);
int hn_rndis_set_rxfilter(struct hn_data *hv, uint32_t filter);
void hn_rndis_rx_ctrl(struct hn_data *hv, const void *data,
int dlen);
--
2.51.0