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: 9+ 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-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-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®