From: netdev-bot+sashiko@kernel.org
To: longli@microsoft.com
Cc: kotaranov@microsoft.com, kuba@kernel.org, davem@davemloft.net,
pabeni@redhat.com, edumazet@google.com, andrew+netdev@lunn.ch,
jgg@ziepe.ca, leon@kernel.org, haiyangz@microsoft.com,
kys@microsoft.com, wei.liu@kernel.org, decui@microsoft.com,
shradhagupta@linux.microsoft.com, horms@kernel.org,
ernis@linux.microsoft.com, stephen@networkplumber.org,
shirazsaleem@microsoft.com, netdev@vger.kernel.org,
linux-rdma@vger.kernel.org, linux-hyperv@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH net-next v4 4/4] net: mana: add dynamic HWC queue depth with reinit path
Date: Sat, 05 Sep 2026 20:02:26 +0000 [thread overview]
Message-ID: <178863854630.219967.11264470350603990060@kernel.org> (raw)
In-Reply-To: <20260901200018.3194525-5-longli@microsoft.com>
Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider:
Critical: 0 · High: 3 (3) · Medium: 1 · Low: 2
- [High] The HWC doorbell page index is taken straight from the device
(`gd->doorbell = eq_db.doorbell` in mana_hwc_init_event_handler(),…
- [High] NULL pointer dereference on the new reinit_fallback path.
- [High] The new INVALID_DOORBELL guard is placed after the write it is
supposed to prevent.
- [Medium] Negotiated-dimension validation is not applied on every
establish, contradicting the commit message.
- [Low] Commit message claims the pre-existing 'first establish omits the
doorbell' unchecked-MMIO path is not addressed, while the new…
- [Low] Contradictory comments inside mana_hwc_create_channel(): the
early publish stores gc->hwc.driver_data under gc->hwc_lock with a…
Pre-existing issues:
- [High] mana_hwc_destroy_cq() frees the CQ completion state before the
EQ interrupt is fenced: it does kfree(hwc_cq->comp_buf), then…
- [High] The service-reset path and the PCI PM lifecycle can enter HWC
teardown concurrently.
- [High] Service work is queued on the system workqueue and is never
cancelled or flushed during driver teardown, so it can run against a…
--
net: mana: add dynamic HWC queue depth with reinit path
The HWC is bootstrapped at queue depth 1, then torn down and rebuilt at the
device-reported maximum depth so more management commands can be in flight,
with a fallback back to the bootstrap depth when the rebuild fails. A new
capability flag advertises the support and the negotiated dimensions are
validated before they size DMA allocations.
A few questions on the doorbell handling, the dimension validation and the
fallback ordering below.
>From the commit message:
> Because that reset
> runs on every establish, a firmware that supplied the doorbell on the
> first one but omits it on the second would leave INVALID_DOORBELL
> behind, so refuse the channel in that case rather than let
> mana_gd_ring_doorbell() turn it into an unchecked write far outside the
> BAR. The same write is already reachable without this patch when the
> very first establish omits the doorbell; that is pre-existing and not
> addressed here.
[Severity: Low]
Is this description of the scope accurate? The new check sits in the
common body of mana_hwc_establish_channel(), so it also runs on the first
establish, not just the second one.
What remains unaddressed is something different: the doorbell is rung from
hard IRQ in mana_gd_process_eq_events() before the check can run at all, on
either establish (see the separate comment on the reset hunk). Could the
wording say which part is actually still reachable?
> diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c
> index 91fcf7c092113..83df4f194ee3b 100644
> --- a/drivers/net/ethernet/microsoft/mana/hw_channel.c
> +++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c
[ ... ]
> @@ -797,6 +806,21 @@ static int mana_hwc_establish_channel(struct gdma_context *gc, u16 *q_depth,
> struct gdma_queue *cq = hwc->cq->gdma_cq;
> int err;
>
> + /* Clear the values a previous establish left behind so a firmware
> + * that omits an HWC_INIT_DATA_* item on this cycle cannot silently
> + * reuse stale dimensions from the last one. The same applies to the
> + * routing identities: the queues are rebuilt from scratch, so a
> + * doorbell, PDID or PF destination left over from the previous
> + * channel does not describe them.
> + */
> + hwc->hwc_init_q_depth_max = 0;
> + hwc->hwc_init_max_req_msg_size = 0;
> + hwc->hwc_init_max_resp_msg_size = 0;
> + gc->hwc.doorbell = INVALID_DOORBELL;
[Severity: High]
Can this write happen before the guard added below can catch it?
The HWC EQ is armed from hard IRQ while the handshake is still running:
drivers/net/ethernet/microsoft/mana/gdma_main.c:mana_gd_process_eq_events() {
...
mana_gd_ring_doorbell(gc, eq->gdma_dev->doorbell, eq->type, eq->id,
head, SET_ARM_BIT);
...
}
So the call chain is:
mana_gd_intr() -> mana_gd_process_eq_events() -> mana_gd_ring_doorbell()
If an EQE batch (INIT_DATA, INIT_DONE) is processed before
GDMA_EQE_HWC_INIT_EQ_ID_DB is consumed, gd->doorbell is still the sentinel
this hunk just stored, and mana_gd_ring_doorbell() does writeq() at
gc->db_page_base + gc->db_page_size * 0xffffffff, which is far outside the
ioremap'ed BAR0 window. The guard below only runs after
wait_for_completion_timeout() returns.
On the second establish this also discards a doorbell index that was
already known good. Would keeping (and range-checking) the index be
preferable to clearing it and checking afterwards?
> + gc->hwc.pdid = INVALID_PDID;
> + hwc->pf_dest_vrq_id = 0;
> + hwc->pf_dest_vrcq_id = 0;
> +
> init_completion(&hwc->hwc_init_eqe_comp);
>
> err = mana_smc_setup_hwc(&gc->shm_channel, false,
> @@ -815,6 +839,20 @@ static int mana_hwc_establish_channel(struct gdma_context *gc, u16 *q_depth,
> *max_req_msg_size = hwc->hwc_init_max_req_msg_size;
> *max_resp_msg_size = hwc->hwc_init_max_resp_msg_size;
>
> + /* The doorbell was cleared before the handshake, so a firmware that
> + * signals INIT_DONE without sending GDMA_EQE_HWC_INIT_EQ_ID_DB
> + * leaves INVALID_DOORBELL behind. mana_gd_ring_doorbell() turns
> + * that into gc->db_page_base + gc->db_page_size * 0xffffffff, an
> + * unchecked MMIO write far outside the mapped BAR, and the channel
> + * test below rings it. Everything else the device reports either
> + * fails the dimension checks in mana_hwc_create_channel() or leaves
> + * the queues unable to complete, which that test already catches.
> + */
> + if (gc->hwc.doorbell == INVALID_DOORBELL) {
> + dev_err(hwc->dev, "HWC: no doorbell in init data\n");
> + return -EPROTO;
> + }
[Severity: High]
Does this catch anything other than the omission of
GDMA_EQE_HWC_INIT_EQ_ID_DB?
The doorbell index comes straight from the device in
mana_hwc_init_event_handler():
case GDMA_EQE_HWC_INIT_EQ_ID_DB:
eq_db.as_uint32 = event->details[0];
hwc->cq->gdma_eq->id = eq_db.eq_id;
gd->doorbell = eq_db.doorbell;
union hwc_init_eq_id_db declares doorbell as a 16-bit field, so the device
can never report UINT_MAX; only an omitted EQ_ID_DB produces the sentinel.
An in-field but out-of-BAR value such as 0xffff still reaches:
drivers/net/ethernet/microsoft/mana/gdma_main.c:mana_gd_ring_doorbell() {
void __iomem *addr = gc->db_page_base + gc->db_page_size * db_index;
...
writeq(e.as_uint64, addr);
}
with a 4 KB doorbell page that is roughly 256 MB past the mapping. The
driver already has the corresponding check on the peer path:
drivers/net/ethernet/microsoft/mana/gdma_main.c:mana_gd_register_device() {
if (gc->db_page_off + gc->db_page_size * ((u64)resp.db_id + 1) > gc->bar0_size) {
dev_err(gc->dev, "Doorbell ID %u out of range\n", resp.db_id);
return -EPROTO;
}
}
Would the same range test here work better, given it also subsumes the
sentinel case?
> /* Both were set in mana_hwc_init_event_handler(). */
> if (WARN_ON(cq->id >= gc->max_num_cqs))
> return -EPROTO;
[ ... ]
> @@ -872,13 +916,64 @@ static int mana_hwc_init_queues(struct hw_channel_context *hwc, u16 q_depth,
> return err;
> }
>
> +/* Tear down all HWC queues and free associated resources. Used on
> + * the reinit-with-higher-queue-depth path and reinit fallback.
> + *
> + * PRECONDITION: must be called only during channel bring-up in
> + * mana_hwc_create_channel(), before the channel carries traffic:
> + * channel_up is still false, caller_ctx is not yet allocated, the
> + * data path is not probed yet, and active_senders is 0 — so no
> + * request or response user can reach these queues. That is why this
> + * skips the hwc_lock-protected driver_data clear + active_senders
> + * drain that mana_hwc_destroy_channel() needs for the runtime
> + * teardown race; only the CQ-first ordering below (to fence off a
> + * pending interrupt) is required. Bring-up itself runs under the
> + * PCI/PM device_lock, or under GC_IN_SERVICE on the service path;
> + * those two do not exclude each other, so a service reset racing a PM
> + * transition is not serialized — but that is pre-existing and applies
> + * equally to mana_hwc_destroy_channel(), which frees the same
> + * objects. Calling this on a live, published channel would be a
> + * use-after-free.
[Severity: High]
This is a pre-existing issue and not introduced by this patch, but since
the comment documents the gap: can the two teardown contexts free the same
hwc twice?
mana_serv_reset() drives the suspend/resume cycle from a workqueue with no
device_lock held:
drivers/net/ethernet/microsoft/mana/gdma_main.c:mana_serv_reset() {
spin_unlock_irqrestore(&gc->hwc_lock, flags);
dev_info(&pdev->dev, "MANA reset cycle start\n");
mana_gd_suspend(pdev, PMSG_SUSPEND);
...
}
while .suspend and .remove run under device_lock, and GC_IN_SERVICE only
serializes service jobs against each other. mana_hwc_destroy_channel()
then reads the pointer outside hwc_lock:
struct hw_channel_context *hwc = gc->hwc.driver_data;
if (!hwc)
return;
so both callers can observe the same non-NULL hwc and both proceed to
destroy the CQ/TXQ/RXQ and kfree(hwc). Is something else preventing that?
[Severity: High]
This is also pre-existing and not introduced by this patch, but related to
the service path named in the comment: is the service work ever cancelled
before gc is freed?
mana_schedule_serv_work() puts the item on the system workqueue:
INIT_WORK(&mns_wk->serv_work, mana_serv_func);
schedule_work(&mns_wk->serv_work);
while cleanup only drains the unrelated ordered queue:
drivers/net/ethernet/microsoft/mana/gdma_main.c:mana_gd_cleanup_device() {
if (gc->service_wq) {
destroy_workqueue(gc->service_wq);
gc->service_wq = NULL;
}
}
and mana_gd_remove() then does vfree(gc). mana_serv_func() ->
mana_do_service() -> mana_serv_reset() takes gc from
pci_get_drvdata(pdev) and dereferences gc->hwc_lock and
gc->hwc.driver_data, so a worker that already read a valid pointer would
operate on freed memory. Would a cancel_work_sync() for the pending item
in the remove path close that?
> + */
> +static void mana_hwc_destroy_queues(struct hw_channel_context *hwc)
> +{
> + struct gdma_context *gc = hwc->gdma_dev->gdma_context;
> +
> + /* Destroy CQ first to deregister the EQ from the interrupt
> + * handler list before freeing caller_ctx, TXQ, or RXQ memory.
> + * A pending interrupt handler could still reach handle_resp()
> + * which dereferences caller_ctx.
> + */
> + if (hwc->cq) {
> + mana_hwc_destroy_cq(gc, hwc->cq);
> + hwc->cq = NULL;
> + }
[Severity: High]
This isn't a bug introduced by this patch, but does mana_hwc_destroy_cq()
actually fence the interrupt before it frees its own state?
mana_hwc_destroy_cq() frees comp_buf and the GDMA CQ first, and only then
the EQ:
drivers/net/ethernet/microsoft/mana/hw_channel.c:mana_hwc_destroy_cq() {
kfree(hwc_cq->comp_buf);
if (hwc_cq->gdma_cq)
mana_gd_destroy_queue(gc, hwc_cq->gdma_cq);
if (hwc_cq->gdma_eq)
mana_gd_destroy_queue(gc, hwc_cq->gdma_eq);
...
}
but the IRQ removal and the RCU fence only happen in the EQ teardown:
drivers/net/ethernet/microsoft/mana/gdma_main.c:mana_gd_deregister_irq() {
list_del_rcu(&eq->entry);
...
synchronize_rcu();
}
So a handler already inside its read section via mana_gd_intr() ->
mana_gd_process_eq_events() -> mana_gd_process_eqe() ->
mana_hwc_comp_event() can still touch the freed gdma_cq and the freed
comp_buf. The comment here relies on the CQ-first ordering fencing off a
pending interrupt, which only holds for what is freed after
mana_hwc_destroy_cq() returns. Would destroying/deregistering the EQ
before freeing comp_buf and the CQ be the right order?
> +
> + kfree(hwc->caller_ctx);
> + hwc->caller_ctx = NULL;
[ ... ]
> @@ -926,8 +1021,200 @@ int mana_hwc_create_channel(struct gdma_context *gc)
> goto out;
> }
>
> + /* The channel was bootstrapped at a minimal queue depth. If the
> + * device reports a higher maximum, tear down and rebuild with
> + * the larger depth so more HWC commands can be in flight.
> + */
> + if (q_depth_max > HW_CHANNEL_VF_BOOTSTRAP_QUEUE_DEPTH) {
[ ... ]
> + if (max_req_msg_size != HW_CHANNEL_MAX_REQUEST_SIZE ||
> + max_resp_msg_size != HW_CHANNEL_MAX_RESPONSE_SIZE ||
> + (u64)q_depth_max * max_req_msg_size >
> + U32_MAX - MANA_PAGE_SIZE ||
> + (u64)q_depth_max * max_resp_msg_size >
> + U32_MAX - MANA_PAGE_SIZE) {
> + dev_err(hwc->dev,
> + "HWC: invalid dims q=%u req=%u resp=%u\n",
> + q_depth_max, max_req_msg_size,
> + max_resp_msg_size);
> + q_depth_max = HW_CHANNEL_VF_BOOTSTRAP_QUEUE_DEPTH;
> + goto skip_reinit;
> + }
[Severity: Medium]
The commit message says "Reject a device-reported message size above the
driver maximum", but is that what happens here?
This is the only message-size validation added, and it is nested under
q_depth_max > HW_CHANNEL_VF_BOOTSTRAP_QUEUE_DEPTH. A device that reports
depth 0 or 1 together with arbitrary or zero HWC_INIT_DATA_MAX_REQUEST /
HWC_INIT_DATA_MAX_RESPONSE values never reaches this check;
mana_hwc_init_event_handler() stores them verbatim and
mana_hwc_establish_channel() hands them back unchecked.
When the depth is larger and the sizes do mismatch, the mismatch is not
rejected either - q_depth_max is reset and the code jumps to skip_reinit,
continuing on the already-established channel.
The backstop the commit message names also does not look like one:
static int mana_hwc_test_channel(struct hw_channel_context *hwc, u16 q_depth,
u32 max_req_msg_size, u32 max_resp_msg_size)
neither max_req_msg_size nor max_resp_msg_size is read in the body, so it
cannot catch a contradictory or omitted size report.
Was returning an error intended for the mismatch case, and should the check
move out of the depth > bootstrap branch?
[ ... ]
> + }
> +
> + goto skip_reinit;
> +
> +reinit_fallback:
> + /* Restore bootstrap-depth channel so the device remains functional.
> + * Free cq_table if it was allocated by a partially successful
> + * establish attempt.
> + */
> + dev_warn(hwc->dev, "HWC reinit failed, falling back to bootstrap depth\n");
> +
> + mana_hwc_destroy_queues(hwc);
> +
> + old_cq_table = gc->cq_table;
> + gc->cq_table = NULL;
> + /* Clear the bound with the table, as above. */
> + gc->max_num_cqs = 0;
[Severity: High]
Can this order oops on a NULL gc->cq_table?
mana_hwc_destroy_queues() runs before gc->max_num_cqs is cleared here, and
on this path gc->cq_table was already set to NULL by the reinit branch
above. If the re-establish then failed after
mana_hwc_init_event_handler() stored HWC_INIT_DATA_MAX_NUM_CQS and
HWC_INIT_DATA_CQID from hard IRQ, but before gc->cq_table was allocated,
max_num_cqs is non-zero with a NULL table. The new -EPROTO return for the
missing doorbell, the wait_for_completion_timeout() -ETIMEDOUT return and
the vcalloc() -ENOMEM return all leave that state.
mana_hwc_destroy_queues() -> mana_hwc_destroy_cq() ->
mana_gd_destroy_queue() then reaches:
drivers/net/ethernet/microsoft/mana/gdma_main.c:mana_gd_destroy_cq() {
u32 id = queue->id;
if (id >= gc->max_num_cqs)
return;
if (!gc->cq_table[id])
return;
...
}
which passes the bound and dereferences the NULL table. The runtime
teardown avoids this by setting gc->max_num_cqs = 0 before
mana_hwc_destroy_cq(). Would zeroing gc->max_num_cqs and gc->cq_table
before the mana_hwc_destroy_queues() call here (and resetting
gc->max_num_cqs alongside the other stale init fields in
mana_hwc_establish_channel()) be enough?
> + synchronize_rcu();
> + vfree(old_cq_table);
> +
[ ... ]
> +skip_reinit:
> +
> + /* No RCU needed: still in mana_hwc_create_channel, the
> + * pointer has not been published to concurrent senders yet.
> + */
[Severity: Low]
This comment and the publish earlier in the same function seem to disagree.
Earlier in mana_hwc_create_channel():
/* Publish driver_data last, under hwc_lock: the lock orders the hwc
* initialisation above before the pointer becomes visible and
* serialises the publish against the control-plane readers in
* mana_gd_send_request(), mana_need_log() and mana_serv_reset().
*/
spin_lock_irqsave(&gc->hwc_lock, flags);
gc->hwc.driver_data = hwc;
spin_unlock_irqrestore(&gc->hwc_lock, flags);
so the pointer has been published by the time skip_reinit is reached. What
keeps senders out is hwc->channel_up, which mana_hwc_get_msg_index()
tests. Could the comment say that instead?
> err = mana_hwc_test_channel(gc->hwc.driver_data,
> - HW_CHANNEL_VF_BOOTSTRAP_QUEUE_DEPTH,
> + hwc->num_inflight_msg,
> max_req_msg_size, max_resp_msg_size);
> if (err) {
> dev_err(hwc->dev, "Failed to test HWC: %d\n", err);
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901200018.3194525-1-longli%40microsoft.com
prev parent reply other threads:[~2026-09-05 20:02 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-01 20:00 [PATCH net-next v4 0/4] net: mana: concurrent HWC requests and dynamic queue depth Long Li
2026-09-01 20:00 ` [PATCH net-next v4 1/4] net: mana: track when the HWC has been handed to the PF Long Li
2026-09-02 20:01 ` sashiko-bot
2026-09-02 20:04 ` [EXTERNAL] " Long Li
2026-09-05 20:02 ` netdev-bot+sashiko
2026-09-01 20:00 ` [PATCH net-next v4 2/4] net: mana: give each HWC message slot its own completion state Long Li
2026-09-05 20:02 ` netdev-bot+sashiko
2026-09-01 20:00 ` [PATCH net-next v4 3/4] net: mana: support concurrent HWC requests Long Li
2026-09-05 20:02 ` netdev-bot+sashiko
2026-09-01 20:00 ` [PATCH net-next v4 4/4] net: mana: add dynamic HWC queue depth with reinit path Long Li
2026-09-02 20:01 ` sashiko-bot
2026-09-02 20:04 ` [EXTERNAL] " Long Li
2026-09-05 20:02 ` netdev-bot+sashiko [this message]
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=178863854630.219967.11264470350603990060@kernel.org \
--to=netdev-bot+sashiko@kernel.org \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=decui@microsoft.com \
--cc=edumazet@google.com \
--cc=ernis@linux.microsoft.com \
--cc=haiyangz@microsoft.com \
--cc=horms@kernel.org \
--cc=jgg@ziepe.ca \
--cc=kotaranov@microsoft.com \
--cc=kuba@kernel.org \
--cc=kys@microsoft.com \
--cc=leon@kernel.org \
--cc=linux-hyperv@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=longli@microsoft.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=shirazsaleem@microsoft.com \
--cc=shradhagupta@linux.microsoft.com \
--cc=stephen@networkplumber.org \
--cc=wei.liu@kernel.org \
/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.