From: sashiko-bot@kernel.org
To: "Xin Tian" <tianx@yunsilicon.com>
Cc: linux-rdma@vger.kernel.org
Subject: Re: [PATCH v1 04/15] net/xsc: add the command queue
Date: Thu, 05 Nov 2026 16:09:32 +0000 [thread overview]
Message-ID: <sashiko-outbox-199426@kernel.org> (raw)
In-Reply-To: <20261104155658.37053-5-tianx@yunsilicon.com>
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] net/xsc: leaked command resources and wq hang on timeout in _xsc_cmd_exec()
- [High] net/xsc: IRQ deadlock and unchecked kthread_create() in xsc_cmd_use_polling()
- [High] net/xsc: infinite loop and premature up() in xsc_cmd_use_events()
- [Medium] net/xsc: wrong 16-byte inline threshold in xsc_alloc_msg() corrupts commands
- [Medium] net/xsc: broken CQ owner-bit learning in xsc_cmd_cq_polling()
- [Medium] net/xsc: unsynchronized shared board_info access in xsc_hw_setup()
- [Medium] net/xsc: unguarded V2 capability fields and 32-bit shifts in xsc_cmd_query_hca_cap()
--
commit bb1aecb9bb4cc115b4d4581a9902a3b7bb2ae064
Author: Xin Tian <tianx@yunsilicon.com>
net/xsc: add the command queue
This commit adds the firmware command queue implementation, mailbox memory
management, and initial hardware capability and board configuration queries
for the Yunsilicon XSC PCI driver.
> diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c b/drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c
> new file mode 100644
> index 0000000000000..c001b312224d6
> --- /dev/null
> +++ b/drivers/net/ethernet/yunsilicon/xsc/pci/cmdq.c
> @@ -0,0 +1,1244 @@
[ ... ]
> +enum {
> + NUM_LONG_LISTS = 2,
> + NUM_MED_LISTS = 64,
> + LONG_LIST_SIZE = (2ULL * 1024 * 1024 * 1024 / PAGE_SIZE) * 8 + 16 +
> + XSC_CMD_DATA_BLOCK_SIZE,
> + MED_LIST_SIZE = 16 + XSC_CMD_DATA_BLOCK_SIZE,
> +};
[ ... ]
> +static int xsc_cmd_invoke(struct xsc_core_device *xdev, struct xsc_cmd_msg *in,
> + struct xsc_rsp_msg *out, u8 *status)
> +{
> + struct xsc_cmd *cmd = &xdev->cmd;
> + struct xsc_cmd_work_ent *ent;
> + int err = 0;
> +
> + ent = xsc_alloc_cmd(cmd, in, out);
> + if (IS_ERR(ent))
> + return PTR_ERR(ent);
> +
> + init_completion(&ent->done);
> + INIT_WORK(&ent->work, cmd_work_handler);
> + if (!queue_work(cmd->wq, &ent->work)) {
> + pci_err(xdev->pdev, "failed to queue work\n");
> + err = -ENOMEM;
> + goto err_free;
> + }
> +
> + err = xsc_wait_func(xdev, ent);
> + if (err == -ETIMEDOUT)
> + /* leave the entry for the completion handler; the driver
> + * is marked timed out and rejects all further commands
> + */
> + return err;
[ ... ]
> +void xsc_cmd_use_events(struct xsc_core_device *xdev)
> +{
> + struct xsc_cmd *cmd = &xdev->cmd;
> + int i;
> +
> + /* flush first: cmd_work_handler() takes a semaphore permit, so
> + * flushing after all permits are held would deadlock against a
> + * just-queued work item
> + */
> + flush_workqueue(cmd->wq);
> +
> + /* keep the cmdq interrupt out while the polling kthread is still
> + * draining the queue
> + */
> + disable_irq(cmd->irqn);
> +
> + for (i = 0; i < cmd->max_reg_cmds; i++)
> + down(&cmd->sem);
> +
> + cmd->mode = XSC_CMD_MODE_EVENTS;
> +
> + while (cmd->cmd_pid != cmd->cq_cid)
> + msleep(20);
> + kthread_stop(cmd->cq_task);
> + cmd->cq_task = NULL;
> +
> + for (i = 0; i < cmd->max_reg_cmds; i++)
> + up(&cmd->sem);
> +
> + enable_irq(cmd->irqn);
> +}
[Severity: High]
Can the while (cmd->cmd_pid != cmd->cq_cid) loop in xsc_cmd_use_events()
spin forever if the request producer index and response consumer index start
at different offsets?
In xsc_cmd_init(), cmd->cmd_pid and cmd->cq_cid are initialized from separate
hardware registers:
cmd->cmd_pid = xsc_get_cmdq_req_pid(xdev) & XSC_CMDQ_INDEX_MASK;
cmd->cq_cid = xsc_get_cmdq_rsp_cid(xdev) & XSC_CMDQ_INDEX_MASK;
If the hardware request and response indices do not have the exact same
modulo-32 value after a kexec, FLR, or prior command error, cmd->cmd_pid and
cmd->cq_cid maintain a constant non-zero offset even when all commands have
completed, so cmd->cmd_pid != cmd->cq_cid will never become false.
In addition, in xsc_cmd_cq_polling() and xsc_cmd_resp_handler(),
xsc_cmd_comp_handler() calls up(&cmd->sem) before the caller advances
cmd->cq_cid, updates hardware via xsc_update_cmdq_rsp_cid(), and flips
cmd->owner_bit. Doesn't that allow xsc_cmd_use_events() or
xsc_cmd_use_polling() to acquire the final semaphore permit before the
completion handler finishes updating the CQ consumer state?
> +static int xsc_cmd_cq_polling(void *data);
> +void xsc_cmd_use_polling(struct xsc_core_device *xdev)
> +{
> + struct xsc_cmd *cmd = &xdev->cmd;
> + int i;
> +
> + flush_workqueue(cmd->wq);
> +
> + disable_irq(cmd->irqn);
> +
> + for (i = 0; i < cmd->max_reg_cmds; i++)
> + down(&cmd->sem);
> +
> + cmd->mode = XSC_CMD_MODE_POLLING;
> + cmd->cq_task = kthread_create(xsc_cmd_cq_polling,
> + (void *)xdev,
> + "xsc_cmd_cq_polling");
> + if (!IS_ERR(cmd->cq_task))
> + wake_up_process(cmd->cq_task);
> +
> + for (i = 0; i < cmd->max_reg_cmds; i++)
> + up(&cmd->sem);
> +
> + enable_irq(cmd->irqn);
> +}
[Severity: High]
Won't xsc_cmd_use_polling() deadlock on cmd->sem if a command is still in
flight when disable_irq(cmd->irqn) is called?
In XSC_CMD_MODE_EVENTS, cmd->cq_task is NULL and completions are driven by
the interrupt handler on cmd->irqn calling xsc_cmd_resp_handler() ->
xsc_cmd_comp_handler() -> up(&cmd->sem). Because cmd_work_handler() returns
as soon as it rings the doorbell while the in-flight command continues
holding its permit on cmd->sem, calling disable_irq(cmd->irqn) before
acquiring all cmd->max_reg_cmds permits masks the completion interrupt while
no polling thread is running, so up(&cmd->sem) cannot be called.
Also, why does xsc_cmd_use_polling() call enable_irq(cmd->irqn) at the end
after starting cmd->cq_task? With the interrupt enabled in polling mode,
xsc_cmd_resp_handler() and xsc_cmd_cq_polling() can race on cmd->cq_cid and
cmd->owner_bit.
Finally, if kthread_create() fails in xsc_cmd_use_polling(), cmd->cq_task
stores an ERR_PTR while cmd->mode is set to XSC_CMD_MODE_POLLING, and
xsc_cmd_use_events() later calls kthread_stop(cmd->cq_task) without checking
IS_ERR_OR_NULL(cmd->cq_task).
[ ... ]
> +static struct xsc_cmd_msg *xsc_alloc_msg(struct xsc_core_device *xdev,
> + unsigned int in_size)
> +{
> + struct xsc_cmd_msg *msg = ERR_PTR(-ENOMEM);
> + struct xsc_cmd *cmd = &xdev->cmd;
> + struct cache_ent *ent = NULL;
> +
> + if (in_size > MED_LIST_SIZE && in_size <= LONG_LIST_SIZE)
> + ent = &cmd->cache.large;
> + else if (in_size > 16 && in_size <= MED_LIST_SIZE)
> + ent = &cmd->cache.med;
> +
> + if (ent) {
> + spin_lock(&ent->lock);
> + if (!list_empty(&ent->head)) {
> + msg = list_entry(ent->head.next, typeof(*msg), list);
> + /* For cached lists, we must explicitly state what is
> + * the real size
> + */
> + msg->len = in_size;
> + list_del(&msg->list);
> + }
> + spin_unlock(&ent->lock);
> + }
> +
> + if (IS_ERR(msg))
> + msg = xsc_alloc_cmd_msg(xdev, GFP_KERNEL, in_size);
> +
> + return msg;
> +}
[Severity: Medium]
Should the inline threshold in xsc_alloc_msg(), MED_LIST_SIZE, and
LONG_LIST_SIZE be sizeof(msg->first.data) (32 bytes) instead of 16?
In struct xsc_cmd_first, data is 32 bytes (__be32 data[8]), and both
xsc_alloc_cmd_msg() and xsc_copy_to_cmd_msg() copy up to
sizeof(to->first.data) (32 bytes) inline before using overflow mailboxes in
to->next.
Because xsc_alloc_msg() checks in_size > 16, a command with 16 < in_size <= 32
(such as XSC_CMD_OP_ENABLE_NIC_HCA or XSC_CMD_OP_MODIFY_RAW_QP) is served from
cmd->cache.med, which has a pre-allocated overflow mailbox in msg->next.
xsc_copy_to_cmd_msg() consumes all in_size bytes into to->first.data (leaving
the remaining 32 - in_size inline bytes with stale cached data) and never
initializes to->next->buf, yet cmd_work_handler() still copies all 32 bytes
of ent->in->first.data into lay->in and sets lay->in_ptr to ent->in->next->dma.
[ ... ]
> +static int _xsc_cmd_exec(struct xsc_core_device *xdev,
> + void *in, unsigned int in_size,
> + void *out, unsigned int out_size)
> +{
> + struct xsc_cmd *cmd = &xdev->cmd;
> + struct xsc_rsp_msg *outb;
> + struct xsc_cmd_msg *inb;
> + u8 status = 0;
> + int err;
> +
> + if (cmd->cmd_status == XSC_CMD_STATUS_TIMEDOUT)
> + return -ETIMEDOUT;
> +
> + inb = xsc_alloc_msg(xdev, in_size);
> + if (IS_ERR(inb)) {
> + err = PTR_ERR(inb);
> + return err;
> + }
> +
> + err = xsc_copy_to_cmd_msg(inb, in, in_size);
> + if (err) {
> + pci_err(xdev->pdev, "copy to cmd_msg err %d\n", err);
> + goto err_free_msg;
> + }
> +
> + outb = xsc_alloc_rsp_msg(xdev, GFP_KERNEL, out_size);
> + if (IS_ERR(outb)) {
> + err = PTR_ERR(outb);
> + goto err_free_msg;
> + }
> +
> + err = xsc_cmd_invoke(xdev, inb, outb, &status);
> + if (err == -ETIMEDOUT)
> + /* the entry stays registered for the completion handler
> + * and keeps its mailboxes
> + */
> + return err;
[ ... ]
> +static void xsc_cmd_comp_handler(struct xsc_core_device *xdev, u8 idx,
> + struct xsc_rsp_layout *rsp)
> +{
[ ... ]
> + xsc_free_ent(cmd, ent->idx);
> + complete(&ent->done);
> + up(&cmd->sem);
> +}
> +
> +static int xsc_cmd_cq_polling(void *data)
> +{
> + struct xsc_core_device *xdev = data;
> + struct xsc_cmd *cmd = &xdev->cmd;
> + struct xsc_rsp_layout *rsp;
> + u32 cq_pid;
> +
> + while (!kthread_should_stop()) {
> + if (need_resched())
> + schedule();
> + cq_pid = xsc_get_cmdq_rsp_pid(xdev);
> + if (cmd->cq_cid == cq_pid) {
> + usleep_range(1000, 2000);
> + continue;
> + }
> +
> + rsp = xsc_get_cq_inst(cmd, cmd->cq_cid);
> + if (!cmd->ownerbit_learned) {
> + cmd->ownerbit_learned = 1;
> + cmd->owner_bit = xsc_rsp_owner_bit(rsp);
> + }
> + if (cmd->owner_bit != xsc_rsp_owner_bit(rsp)) {
> + dev_err_ratelimited(&xdev->pdev->dev,
> + "hw update cq doorbell but buf not ready %u %u\n",
> + cmd->cq_cid, cq_pid);
> + msleep(20);
> + continue;
> + }
[Severity: Medium]
Does learning cmd->owner_bit from xsc_rsp_owner_bit(rsp) right before
checking cmd->owner_bit != xsc_rsp_owner_bit(rsp) bypass the readiness check
on the first response in xsc_cmd_cq_polling() and xsc_cmd_resp_handler()?
In xsc_cmd_init(), cmd->cq_buf is zeroed with memset(cmd->cq_buf, 0,
PAGE_SIZE) and cmd->ownerbit_learned is set to 0. When the first completion
is polled, if the MMIO read of rsp_pid sees the updated producer index before
the device DMA write of the response descriptor reaches cmd->cq_buf,
xsc_rsp_owner_bit(rsp) reads 0 from the zeroed buffer, sets cmd->owner_bit = 0,
and immediately passes the cmd->owner_bit != xsc_rsp_owner_bit(rsp) check with
an all-zero descriptor while latching the wrong owner-bit polarity for subsequent
slots.
[ ... ]
> +void xsc_cmd_cleanup(struct xsc_core_device *xdev)
> +{
> + struct xsc_cmd *cmd = &xdev->cmd;
> +
> + destroy_workqueue(cmd->wq);
> + if (!IS_ERR_OR_NULL(cmd->cq_task))
> + kthread_stop(cmd->cq_task);
> + xsc_destroy_msg_cache(xdev);
> + dma_free_coherent(&xdev->pdev->dev, PAGE_SIZE, cmd->cq_buf,
> + cmd->cq_dma);
> + dma_free_coherent(&xdev->pdev->dev, PAGE_SIZE, cmd->cmd_buf,
> + cmd->dma);
> + dma_pool_destroy(cmd->pool);
> +}
[Severity: High]
When a command times out in xsc_cmd_invoke() and _xsc_cmd_exec(), where are
ent, inb, and outb freed?
On -ETIMEDOUT, xsc_cmd_invoke() leaves ent allocated and _xsc_cmd_exec()
returns early without freeing outb or returning/freeing inb. However, neither
xsc_cmd_comp_handler() (if a late completion arrives) nor xsc_cmd_cleanup()
(during error unwind in xsc_hw_setup() or device teardown) ever frees ent,
inb, or outb. Because inb (when detached from cmd->cache or allocated on
demand) and outb (when out_size > sizeof(outb->first.data), such as in
xsc_cmd_query_hca_cap()) hold blocks allocated from cmd->pool,
dma_pool_destroy(cmd->pool) in xsc_cmd_cleanup() will fail to destroy the
pool due to still-allocated DMA blocks and leak coherent DMA memory.
Furthermore, if cmd->max_reg_cmds commands time out while another work item
is queued on cmd->wq, cmd_work_handler() for that queued item will block
indefinitely in down(&cmd->sem), causing destroy_workqueue(cmd->wq) in
xsc_cmd_cleanup() to hang.
> diff --git a/drivers/net/ethernet/yunsilicon/xsc/pci/hw.c b/drivers/net/ethernet/yunsilicon/xsc/pci/hw.c
> new file mode 100644
> index 0000000000000..0358a40cca9b8
> --- /dev/null
> +++ b/drivers/net/ethernet/yunsilicon/xsc/pci/hw.c
> @@ -0,0 +1,316 @@
[ ... ]
> +int xsc_cmd_query_hca_cap(struct xsc_core_device *xdev,
> + struct xsc_caps *caps)
> +{
[ ... ]
> + caps->max_qp_mcg = be16_to_cpu(out->hca_cap.max_qp_mcg);
> + caps->max_ra_res_qp = 1 << (out->hca_cap.log_max_ra_res_qp & 0x3f);
> + caps->max_ra_req_qp = 1 << (out->hca_cap.log_max_ra_req_qp & 0x3f);
> + caps->max_srq_wqes = 1 << out->hca_cap.log_max_srq_sz;
[ ... ]
> + caps->max_qp = be32_to_cpu(out->hca_cap.max_qp);
> + caps->max_cq = be32_to_cpu(out->hca_cap.max_cq);
> + caps->max_pd = be32_to_cpu(out->hca_cap.max_pd);
> +
> + if (be16_to_cpu(out->hdr.ver) >= XSC_CMD_QUERY_HCA_CAP_V2)
> + caps->max_mtt = be32_to_cpu(out->hca_cap.max_mtt);
> + else
> + caps->max_mtt = 1 << (out->hca_cap.log_max_mtt & 0x3f);
[Severity: Medium]
In struct xsc_hca_cap, max_qp, max_cq, and max_pd are defined alongside
max_mtt in the V2 section of the mailbox. When be16_to_cpu(out->hdr.ver) is
earlier than XSC_CMD_QUERY_HCA_CAP_V2, won't reading out->hca_cap.max_qp,
out->hca_cap.max_cq, and out->hca_cap.max_pd unconditionally leave
caps->max_qp, caps->max_cq, and caps->max_pd as 0 instead of falling back to
1U << caps->log_max_qp, 1U << caps->log_max_cq, and 1U << caps->log_max_pd?
Also, in expressions like 1 << (out->hca_cap.log_max_mtt & 0x3f),
1 << (out->hca_cap.log_max_ra_res_qp & 0x3f), and
1 << out->hca_cap.log_max_srq_sz, can shifting the signed 32-bit integer
literal 1 by a 6-bit or 8-bit field overflow a 32-bit signed int?
[ ... ]
> +static int xsc_cmd_query_guid(struct xsc_core_device *xdev)
> +{
> + struct xsc_cmd_query_guid_mbox_out out;
> + struct xsc_cmd_query_guid_mbox_in in = {};
> + int err;
> +
> + in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_QUERY_GUID);
> + err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
> + if (err)
> + return err;
> +
> + xdev->board_info->guid = out.guid;
> + xdev->board_info->guid_valid = 1;
> + return 0;
> +}
> +
> +int xsc_query_guid(struct xsc_core_device *xdev)
> +{
> + if (xdev->board_info->guid_valid)
> + return 0;
> +
> + return xsc_cmd_query_guid(xdev);
> +}
> +
> +static int xsc_cmd_activate_hw_config(struct xsc_core_device *xdev)
> +{
> + struct xsc_cmd_activate_hw_config_mbox_out out;
> + struct xsc_cmd_activate_hw_config_mbox_in in = {};
> + int err = 0;
> +
> + in.hdr.opcode = cpu_to_be16(XSC_CMD_OP_ACTIVATE_HW_CONFIG);
> + err = xsc_cmd_exec(xdev, &in, sizeof(in), &out, sizeof(out));
> + if (err)
> + return err;
> +
> + xdev->board_info->hw_config_activated = 1;
> + return 0;
> +}
> +
> +int xsc_activate_hw_config(struct xsc_core_device *xdev)
> +{
> + if (xdev->board_info->hw_config_activated)
> + return 0;
> +
> + return xsc_cmd_activate_hw_config(xdev);
> +}
[Severity: Medium]
Since xdev->board_info is shared across all PCI functions of the same board,
can concurrent probes of multiple functions on the same card race in
xsc_query_guid(), xsc_activate_hw_config(), and xsc_hw_setup()?
In xsc_cmd_query_hca_cap(), board_info_lock protects the lookup and
allocation of xdev->board_info, but xsc_query_guid() and
xsc_activate_hw_config() check and update guid_valid, guid, and
hw_config_activated (and xsc_hw_setup() updates xdev->board_info->msg_opcode)
without holding board_info_lock or using memory barriers. Two PCI functions
probing in parallel can both see guid_valid == 0 or hw_config_activated == 0
and issue duplicate XSC_CMD_OP_QUERY_GUID and XSC_CMD_OP_ACTIVATE_HW_CONFIG
commands or observe guid_valid == 1 before the store to guid is visible.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261104155650.37053-1-tianx@yunsilicon.com?part=4
next prev parent reply other threads:[~2026-11-05 16:09 UTC|newest]
Thread overview: 30+ 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 [this message]
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-04 15:57 ` [PATCH v1 08/15] net/xsc: add ethernet TX path Xin Tian
2026-11-05 16:09 ` sashiko-bot
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
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-199426@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox