From: sashiko-bot@kernel.org
To: "Xin Tian" <tianx@yunsilicon.com>
Cc: linux-rdma@vger.kernel.org
Subject: Re: [PATCH v1 08/15] net/xsc: add ethernet TX path
Date: Thu, 05 Nov 2026 16:09:34 +0000 [thread overview]
Message-ID: <sashiko-outbox-199430@kernel.org> (raw)
In-Reply-To: <20261104155708.37053-9-tianx@yunsilicon.com>
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] net/xsc: NETDEV_TX_BUSY on active queues in xsc_eth_xmit_start()
- [High] net/xsc: race waking stopped TX queues in xsc_eth_deactivate_priv_channels()
- [High] net/xsc: CQs armed before NAPI and queue state init in xsc_eth_open_channels()
- [Medium] net/xsc: missed error CQE checks in xsc_poll_tx_cq() poll loop
- [Medium] net/xsc: missing TX doorbell on err_drop path in xsc_eth_xmit_frame()
- [Medium] net/xsc: fix CQE error check, sqcc update, and budget in xsc_poll_tx_cq()
- [Low] net/xsc: chls->num_chl zeroed before error log in xsc_eth_open_channels()
--
commit 251dc7b87bb643704bb618df57c60cfb11aec1a1
Author: Xin Tian <tianx@yunsilicon.com>
net/xsc: add ethernet TX path
This commit implements the Ethernet transmit path for the xsc driver,
including per-channel CQ and raw-TSO SQ setup, NAPI completion polling,
WQE construction with DMA mapping and BQL tracking, and offload feature
advertisement.
> diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/main.c b/drivers/net/ethernet/yunsilicon/xsc/net/main.c
> index 21a50085fa879..a02f95145d018 100644
> --- a/drivers/net/ethernet/yunsilicon/xsc/net/main.c
> +++ b/drivers/net/ethernet/yunsilicon/xsc/net/main.c
> @@ -95,22 +95,825 @@ static int xsc_eth_netdev_init(struct xsc_adapter *adapter)
[ ... ]
> +static void xsc_eth_completion_event(struct xsc_core_cq *xcq)
> +{
> + struct xsc_cq *cq = container_of(xcq, struct xsc_cq, xcq);
> + struct xsc_core_device *xdev = cq->xdev;
> + struct xsc_rq *rq = NULL;
> +
> + if (unlikely(!cq->channel)) {
> + netdev_err(xsc_dev_to_netdev(xdev), "cq%d->channel is null\n",
> + xcq->cqn);
> + return;
> + }
> +
> + rq = &cq->channel->qp.rq;
> +
> + if (!test_bit(XSC_ETH_RQ_STATE_ENABLED, &rq->state))
> + netdev_err(xsc_dev_to_netdev(xdev),
> + "ch%d_cq%d, napi_flag=0x%lx\n",
> + cq->channel->chl_idx, xcq->cqn,
> + cq->napi->state);
> +
> + napi_schedule(cq->napi);
> + cq->event_ctr++;
> +}
[ ... ]
> +static int xsc_eth_open_cq(struct xsc_channel *c,
> + struct xsc_cq *pcq,
> + struct xsc_cq_param *pcq_param)
> +{
> + int ret;
> +
> + ret = xsc_eth_alloc_cq(c, pcq, pcq_param);
> + if (ret)
> + return ret;
> +
> + ret = xsc_eth_set_cq(c, pcq, pcq_param);
> + if (ret)
> + goto err_free_cq;
> +
> + xsc_cq_notify_hw_rearm(pcq);
> +
> + pcq->napi = &c->napi;
> + pcq->channel = c;
> +
> + return 0;
> +
> +err_free_cq:
> + xsc_eth_free_cq(pcq);
> + return ret;
> +}
[ ... ]
> +static int xsc_eth_open_channel(struct xsc_adapter *adapter,
> + int idx,
> + struct xsc_channel *c,
> + struct xsc_channel_param *chl_param)
> +{
> + struct xsc_core_device *xdev = adapter->xdev;
> + struct net_device *netdev = adapter->netdev;
> + const struct cpumask *aff;
> + unsigned int irqn;
> + u32 eqn;
> + int ret;
> +
> + c->adapter = adapter;
> + c->netdev = adapter->netdev;
> + c->chl_idx = idx;
> +
> + ret = xsc_core_vector2eqn(xdev, c->chl_idx, &eqn, &irqn);
> + if (ret)
> + goto err_out;
> + aff = irq_get_affinity_mask(irqn);
> + c->aff_mask = aff;
> + c->cpu = cpumask_first(aff);
> +
> + ret = xsc_eth_open_cq(c, &c->qp.rq.cq, &chl_param->rqcq_param);
> + if (ret)
> + goto err_out;
> +
> + ret = xsc_eth_open_cq(c, &c->qp.sq.cq, &chl_param->sqcq_param);
> + if (ret)
> + goto err_close_rq_cq;
> +
> + ret = xsc_eth_open_qp_sq(c, &c->qp.sq, &chl_param->sq_param);
> + if (ret)
> + goto err_close_sq_cq;
> +
> + netif_napi_add(netdev, &c->napi, xsc_eth_napi_poll);
[Severity: High]
Could this ordering between CQ creation, NAPI initialization, and queue
state enablement cause a NULL pointer dereference or unratelimited log
spam from hardirq context?
In xsc_eth_open_cq(), xsc_eth_set_cq() calls xsc_core_eth_create_cq(),
which inserts &pcq->xcq into xdev->dev_res->cq_table.tree while MSI-X EQs
are active, and then calls xsc_cq_notify_hw_rearm(pcq) before setting
pcq->napi and pcq->channel.
Furthermore, in xsc_eth_open_channel(), both CQs (rq.cq and sq.cq) are
opened and armed before netif_napi_add(netdev, &c->napi, xsc_eth_napi_poll)
initializes c->napi. Because c is zero-allocated with kcalloc_node(),
c->napi.state has NAPIF_STATE_SCHED cleared and c->napi.poll is NULL prior
to netif_napi_add(). If xsc_eq_int() dispatches XSC_EVENT_TYPE_COMP or
XSC_EVENT_TYPE_INTERNAL_ERROR for the CQ after pcq->channel is assigned
and before netif_napi_add() runs, xsc_eth_completion_event() will call
napi_schedule(cq->napi) on an uninitialized napi_struct.
In addition, xsc_eth_completion_event() checks
!test_bit(XSC_ETH_RQ_STATE_ENABLED, &rq->state) and logs an unratelimited
netdev_err() from hardirq context. In this commit XSC_ETH_RQ_STATE_ENABLED
is never set anywhere, so every TX completion interrupt triggers
netdev_err(). Even once RX sets XSC_ETH_RQ_STATE_ENABLED in
xsc_eth_activate_priv_channels(), xsc_eth_open_channels() transitions QPs
to RTS before xsc_eth_activate_priv_channels() sets the enabled state bits
(and xsc_eth_deactivate_channels() clears them before tearing down the QPs
and CQs), during which time xsc_poll_tx_cq() returns early when
XSC_ETH_SQ_STATE_ENABLED is clear while xsc_eth_napi_poll() re-arms the CQ
without draining it.
Should netif_napi_add() and pcq->napi / pcq->channel initialization happen
before registering and arming the CQs, and should the queue state bits be
set before arming CQs and transitioning QPs to RTS?
> +
> + netdev_dbg(adapter->netdev, "open channel%d ok\n", idx);
> + return 0;
[ ... ]
> +err_close_channel:
> + for (--i; i >= 0; i--)
> + xsc_eth_close_channel(&chls->c[i], false);
> +
> + kvfree(chl_param);
> +err_free_ch:
> + kfree(chls->c);
> +err_out:
> + chls->num_chl = 0;
> + netdev_err(adapter->netdev, "failed to open %d channels, err=%d\n",
> + chls->num_chl, ret);
> + return ret;
> +}
[Severity: Low]
In xsc_eth_open_channels(), chls->num_chl is set to 0 right before the
netdev_err() call at err_out. Won't this always log "failed to open 0
channels" on failure instead of the number of channels that failed to
open?
[ ... ]
> +static void xsc_eth_deactivate_channels(struct xsc_eth_channels *chs)
> +{
> + int i;
> +
> + for (i = 0; i < chs->num_chl; i++)
> + xsc_eth_deactivate_channel(&chs->c[i]);
> +
> + /* Sync with all NAPIs to wait until they stop using queues. */
> + synchronize_net();
> +
> + for (i = 0; i < chs->num_chl; i++)
> + /* last doorbell out */
> + napi_disable(&chs->c[i].napi);
> +}
> +
> +static void xsc_eth_activate_priv_channels(struct xsc_adapter *adapter)
> +{
> + struct net_device *netdev = adapter->netdev;
> +
> + netif_set_real_num_tx_queues(netdev, adapter->channels.num_chl);
> + netif_set_real_num_rx_queues(netdev, adapter->channels.num_chl);
> +
> + xsc_eth_build_tx2sq_maps(adapter);
> + xsc_eth_activate_channels(&adapter->channels);
> + netif_tx_start_all_queues(adapter->netdev);
> +}
> +
> +static void xsc_eth_deactivate_priv_channels(struct xsc_adapter *adapter)
> +{
> + netif_tx_disable(adapter->netdev);
> + xsc_eth_deactivate_channels(&adapter->channels);
> +}
[Severity: High]
Can a concurrent NAPI poll re-wake a TX queue during teardown in
xsc_eth_deactivate_priv_channels()?
xsc_eth_deactivate_priv_channels() calls netif_tx_disable(adapter->netdev)
before xsc_eth_deactivate_channels() clears XSC_ETH_SQ_STATE_ENABLED and
disables NAPI. If xsc_poll_tx_cq() is running concurrently on another CPU,
it can still see XSC_ETH_SQ_STATE_ENABLED set, observe
netif_tx_queue_stopped(sq->txq) is true (just set by netif_tx_disable()),
and call netif_tx_wake_queue(sq->txq):
drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c:xsc_poll_tx_cq() {
...
if (netif_tx_queue_stopped(sq->txq) &&
xsc_wqc_has_room_for(&sq->wq, sq->cc, sq->pc, sq->stop_room)) {
netif_tx_wake_queue(sq->txq);
}
}
Wouldn't that clear __QUEUE_STATE_DRV_XOFF and leave the TX queue awake
while xsc_eth_sw_deinit() proceeds to free TX WQEs and close the channels?
Should xsc_eth_deactivate_channels() run before netif_tx_disable()?
[ ... ]
> @@ -172,14 +1053,26 @@ static int xsc_set_port_admin_status(struct xsc_adapter *adapter,
> static int xsc_eth_open(struct net_device *netdev)
> {
> struct xsc_adapter *adapter = netdev_priv(netdev);
> + bool link_up;
> int ret;
>
> if (adapter->opened) {
> netdev_err(adapter->netdev, "ndo_open on an already opened device\n");
> return 0;
> }
> + ret = xsc_eth_sw_init(adapter);
> + if (ret)
> + return ret;
>
> - if (xsc_eth_get_link_status(adapter)) {
> + ret = xsc_eth_enable_nic_hca(adapter);
> + if (ret)
> + goto err_sw_deinit;
> +
> + ret = xsc_eth_get_link_status(adapter, &link_up);
> + if (ret)
> + netdev_warn(netdev, "failed to query link status, err=%d\n", ret);
> +
> + if (!ret && link_up) {
> netdev_info(netdev, "Link up\n");
> netif_carrier_on(adapter->netdev);
> } else {
> @@ -196,6 +1089,10 @@ static int xsc_eth_open(struct net_device *netdev)
> ret);
>
> return 0;
> +
> +err_sw_deinit:
> + xsc_eth_sw_deinit(adapter);
> + return ret;
> }
>
> static int xsc_eth_close(struct net_device *netdev)
> @@ -209,6 +1106,13 @@ static int xsc_eth_close(struct net_device *netdev)
>
> netif_carrier_off(adapter->netdev);
>
> + xsc_eth_sw_deinit(adapter);
[Severity: High]
Can xsc_eth_xmit_start() return NETDEV_TX_BUSY on an active, unstopped TX
queue during xsc_eth_open() and xsc_eth_close()?
In xsc_eth_open(), xsc_eth_sw_init() calls xsc_eth_activate_priv_channels(),
which calls netif_set_real_num_tx_queues() and
netif_set_real_num_rx_queues() (without checking their return values) and
starts all TX queues via netif_tx_start_queue() and
netif_tx_start_all_queues() while adapter->opened is still false.
xsc_eth_open() then sleeps in xsc_eth_enable_nic_hca() and
xsc_eth_get_link_status() and calls netif_carrier_on() before setting
adapter->opened = true (and xsc_eth_link_event_handler() can also turn the
carrier on asynchronously).
Similarly, in xsc_eth_close(), adapter->opened = false is set before
netif_carrier_off() and before xsc_eth_sw_deinit() calls
netif_tx_disable().
During both windows, TX queues are active and unstopped while
adapter->opened is false. Any packet reaching xsc_eth_xmit_start() will
hit:
drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_tx.c:xsc_eth_xmit_start() {
if (!adapter->opened)
return NETDEV_TX_BUSY;
}
without stopping the TX queue, causing sch_direct_xmit() to requeue the skb
and reschedule NET_TX_SOFTIRQ in a tight retry loop (and leaving any
earlier skbs in a netdev_xmit_more() batch without a doorbell ring).
Should adapter->opened be set before starting the TX queues (or TX queues
started only after HCA enablement and adapter->opened = true), and in
xsc_eth_close() should the TX queues be disabled before clearing
adapter->opened?
> diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_tx.c b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_tx.c
> new file mode 100644
> index 0000000000000..376b2cd0d8f29
> --- /dev/null
> +++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_tx.c
[ ... ]
> +static void xsc_txwqe_complete(struct xsc_sq *sq, struct sk_buff *skb,
> + u8 opcode, u16 ds_cnt,
> + u8 num_wqebbs, u32 num_bytes, u8 num_dma,
> + struct xsc_tx_wqe_info *wi)
> +{
> + struct xsc_wq_cyc *wq = &sq->wq;
> +
> + wi->num_bytes = num_bytes;
> + wi->num_dma = num_dma;
> + wi->num_wqebbs = num_wqebbs;
> + wi->skb = skb;
> +
> + netdev_tx_sent_queue(sq->txq, num_bytes);
> +
> + sq->pc += wi->num_wqebbs;
> +
> + if (unlikely(!xsc_wqc_has_room_for(wq, sq->cc, sq->pc, sq->stop_room)))
> + netif_tx_stop_queue(sq->txq);
> +
> + if (!netdev_xmit_more() || netif_xmit_stopped(sq->txq))
> + xsc_sq_notify_hw(wq, sq->pc, sq);
> +}
> +
> +static uint32_t xsc_eth_xmit_frame(struct sk_buff *skb,
> + struct xsc_sq *sq,
> + struct xsc_tx_wqe *wqe,
> + u16 pi)
> +{
[ ... ]
> + xsc_txwqe_complete(sq, skb, opcode, ds_cnt, num_wqebbs, num_bytes,
> + num_dma, wi);
> + stats->bytes += num_bytes;
> + stats->packets += num_pkts;
> +
> + return NETDEV_TX_OK;
> +
> +err_drop:
> + stats->dropped++;
> + dev_kfree_skb_any(skb);
> +
> + return NETDEV_TX_OK;
> +}
[Severity: Medium]
What happens to previously queued WQEs in a netdev_xmit_more() batch if a
subsequent packet in that batch takes the err_drop path in
xsc_eth_xmit_frame()?
When earlier packets in a batch are queued with netdev_xmit_more() true and
the queue is not stopped, xsc_txwqe_complete() advances sq->pc and skips
xsc_sq_notify_hw(). If a later packet (such as the final packet in the
batch where netdev_xmit_more() is false) jumps to err_drop (for example on
skb->len > sq->hw_mtu, skb_linearize() failure, skb->len == 0, or DMA
mapping failure in xsc_txwqe_build_dsegs()), err_drop frees the skb and
returns without ringing the doorbell for the earlier WQEs. Should err_drop
call xsc_sq_notify_hw() when !netdev_xmit_more() or when uncommitted WQEs
are pending?
> diff --git a/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c
> new file mode 100644
> index 0000000000000..262421740e974
> --- /dev/null
> +++ b/drivers/net/ethernet/yunsilicon/xsc/net/xsc_eth_txrx.c
[ ... ]
> +static bool xsc_poll_tx_cq(struct xsc_cq *cq, int napi_budget)
> +{
> + struct xsc_adapter *adapter;
> + struct xsc_cqe *cqe;
> + struct device *dev;
> + struct xsc_sq *sq;
> + u32 dma_fifo_cc;
> + u32 nbytes = 0;
> + u16 npkts = 0;
> + int i = 0;
> + u16 sqcc;
> +
> + sq = container_of(cq, struct xsc_sq, cq);
> + if (!test_bit(XSC_ETH_SQ_STATE_ENABLED, &sq->state))
> + return false;
> +
> + adapter = sq->channel->adapter;
> + dev = adapter->dev;
> +
> + cqe = xsc_cqwq_get_cqe(&cq->wq);
> + if (!cqe)
> + goto out;
> +
> + if (unlikely(get_cqe_opcode(cqe) & BIT(7)))
> + xsc_dump_error_sqcqe(sq, cqe);
> +
> + sqcc = sq->cc;
> +
> + /* avoid dirtying sq cache line every cqe */
> + dma_fifo_cc = sq->dma_fifo_cc;
> + i = 0;
> + do {
> + struct xsc_tx_wqe_info *wi;
> + struct sk_buff *skb;
> + int j;
> + u16 ci;
> +
> + xsc_cqwq_pop(&cq->wq);
> +
> + ci = xsc_wq_cyc_ctr2ix(&sq->wq, sqcc);
> + wi = &sq->db.wqe_info[ci];
> + skb = wi->skb;
> +
> + if (unlikely(!skb))
> + continue;
> +
> + for (j = 0; j < wi->num_dma; j++) {
> + struct xsc_sq_dma *dma = xsc_dma_get(sq, dma_fifo_cc++);
> +
> + xsc_tx_dma_unmap(dev, dma);
> + }
> +
> + npkts++;
> + nbytes += wi->num_bytes;
> + sqcc += wi->num_wqebbs;
> + napi_consume_skb(skb, 0);
> +
> + } while ((++i < napi_budget) && (cqe = xsc_cqwq_get_cqe(&cq->wq)));
[Severity: Medium]
There are a few issues in the xsc_poll_tx_cq() loop:
1. get_cqe_opcode(cqe) & BIT(7) is only checked once before the do-while
loop rather than inside the loop before xsc_cqwq_pop(&cq->wq). Won't any
error CQEs fetched after the first CQE in a NAPI poll batch be silently
ignored instead of logged via xsc_dump_error_sqcqe()?
2. If unlikely(!skb) is taken after xsc_cqwq_pop(&cq->wq), the loop
executes continue without advancing sqcc (unlike xsc_free_tx_wqe(), which
increments sq->cc++), and wi->skb is never cleared when a WQE completes.
Could that desynchronize sqcc from the CQ if a nop WQE is encountered?
3. napi_consume_skb(skb, 0) is called with a hardcoded budget of 0, which
causes napi_consume_skb() to always take the !budget non-NAPI fallback
(dev_consume_skb_any()) instead of using the NAPI per-CPU skb cache. Should
the NAPI poll budget be passed through to napi_consume_skb() here?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261104155650.37053-1-tianx@yunsilicon.com?part=8
next prev parent reply other threads:[~2026-11-05 16:09 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-04 15:57 [PATCH v1 00/15] Yunsilicon XSC ethernet and RDMA drivers Xin Tian
2026-11-04 15:56 ` [PATCH v1 01/15] MAINTAINERS: add the Yunsilicon XSC drivers Xin Tian
2026-11-04 15:56 ` [PATCH v1 02/15] net/xsc: add PCI core driver skeleton Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:56 ` [PATCH v1 03/15] net/xsc: add the register HAL Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 04/15] net/xsc: add the command queue Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 05/15] net/xsc: add event queues, interrupts and the Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 06/15] net/xsc: add the CQ and QP resource pools Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 07/15] net/xsc: add ethernet netdev registration Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-07 21:03 ` Andrew Lunn
2026-11-04 15:57 ` [PATCH v1 08/15] net/xsc: add ethernet TX path Xin Tian
2026-11-05 16:09 ` sashiko-bot [this message]
2026-11-04 15:57 ` [PATCH v1 09/15] net/xsc: add ethernet RX path and statistics Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 10/15] RDMA/xsc: add RDMA device support Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 11/15] RDMA/xsc: add RDMA PD/MR Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 12/15] RDMA/xsc: add RDMA CQ/SRQ/AH Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 13/15] RDMA/xsc: add QP verbs Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 14/15] RDMA/xsc: add the RDMA send path Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 15/15] RDMA/xsc: add RDMA counters and sysfs Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-10 18:48 ` [PATCH v1 00/15] Yunsilicon XSC ethernet and RDMA drivers Jakub Kicinski
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=sashiko-outbox-199430@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=tianx@yunsilicon.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.