From: sashiko-bot@kernel.org
To: "Liao Xuan" <liaoxuan@open-hieco.net>
Cc: linux-pci@vger.kernel.org
Subject: Re: [PATCH 1/3] nvme: Add adaptive PCIe link rate switching function
Date: Thu, 24 Sep 2026 06:29:41 +0000 [thread overview]
Message-ID: <20260924062941.ADFE91F000FF@smtp.kernel.org> (raw)
In-Reply-To: <9e99e007bf05a608b1b82da740c2b33935a27175.1790222172.git.liaoxuan@hygon.cn>
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [High] nvme: UAF on per-CPU stats and timer re-arming race during teardown
- [High] nvme: infinite link retrain loop on rollback success
- [High] nvme: TOCTOU data race on sw->target_speed causes link mismatch
- [High] nvme: speed switch timer runs during suspend causing deadlocks
- [High] nvme: missing memalloc_noio_save() around queue freeze
- [High] nvme: nvme_unfreeze clears NVME_CTRL_FROZEN prematurely
- [Medium] nvme: synchronous PCIe config space read on block I/O fast path
- [Medium] nvme: data race when zeroing remote per-CPU statistics
- [Medium] nvme: speed switch permanently disabled after controller reset
--
commit 3ad252ce0a89b0f36c40c41b569dd1f1382434b5
Author: Liao Xuan <liaoxuan@open-hieco.net>
nvme: Add adaptive PCIe link rate switching function
This commit introduces dynamic adjustment of the PCIe link rate for NVMe
devices based on I/O activity. The link speed is downgraded during periods
of low activity to save power and upgraded to maximum when I/O load crosses
a calculated threshold.
> diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
> index 1322c678f4eb8..254053ea15fcb 100644
> --- a/drivers/nvme/host/core.c
> +++ b/drivers/nvme/host/core.c
[ ... ]
> @@ -5089,6 +5090,7 @@ EXPORT_SYMBOL_GPL(nvme_remove_io_tag_set);
>
> void nvme_stop_ctrl(struct nvme_ctrl *ctrl)
> {
> + nvme_speed_switch_exit(ctrl);
> nvme_mpath_stop(ctrl);
> nvme_auth_stop(ctrl);
[Severity: Medium]
Since nvme_add_ctrl() only initializes the speed switch once during device
probe, will calling nvme_speed_switch_exit() during nvme_stop_ctrl()
permanently disable the feature after the first controller reset? The reset
recovery path does not appear to call nvme_speed_switch_init() again.
[ ... ]
> diff --git a/drivers/nvme/host/speed_switch.c b/drivers/nvme/host/speed_switch.c
> new file mode 100644
> index 0000000000000..88c046fcdde89
> --- /dev/null
> +++ b/drivers/nvme/host/speed_switch.c
[ ... ]
> +static void nvme_speed_switch_work(struct work_struct *work)
> +{
> + struct nvme_speed_switch *sw = container_of(work, struct nvme_speed_switch, work);
> + struct nvme_ctrl *ctrl = container_of(sw, struct nvme_ctrl, speed_switch);
> + struct pci_dev *pdev = to_pci_dev(ctrl->dev);
> + struct pci_dev *bridge = pdev->bus->self;
> + u8 cur_speed = READ_ONCE(sw->cur_speed);
> + int ret;
> +
> + if (nvme_ctrl_state(ctrl) != NVME_CTRL_LIVE)
> + return;
[Severity: High]
Is this guard sufficient to prevent operations during host-managed suspend?
The controller state remains NVME_CTRL_LIVE during host-managed suspend,
which might allow the timer to fire, schedule this work, and attempt to
access the PCIe configuration space of a suspended device.
> +
> + /* Freeze I/O to avoid timeouts during link retraining. */
> + nvme_start_freeze(ctrl);
> + nvme_wait_freeze(ctrl);
> +
> + ret = pcie_set_target_speed(bridge, nvme_speed_to_bus_speed(sw->target_speed),
> + true);
[Severity: High]
Should this section be protected by memalloc_noio_save() and
memalloc_noio_restore()? Code executing while a block queue is frozen
typically needs NOIO protection. Otherwise, memory reclaim interrupting this
section could attempt to flush dirty pages to the frozen NVMe device,
leading to a system deadlock under memory pressure.
[Severity: High]
Could reading sw->target_speed here introduce a TOCTOU data race? This value
is read to program the hardware, but read again below to update sw->cur_speed.
> + if (ret) {
> + dev_warn(ctrl->device,
> + "failed to set target rate Gen%u (%d), trying rollback to Gen%u\n",
> + sw->target_speed, ret, cur_speed);
> + ret = pcie_set_target_speed(bridge, nvme_speed_to_bus_speed(cur_speed),
> + true);
> + if (ret) {
> + dev_err(ctrl->device,
> + "rollback to Gen%u failed (%d), disabling speed switch\n",
> + cur_speed, ret);
> + WRITE_ONCE(sw->enabled, false);
> + nvme_unfreeze(ctrl);
> + return;
> + }
[Severity: High]
If the pcie_set_target_speed() rollback succeeds, the code omits disabling the
speed switch. Since the function returns while sw->enabled remains true, won't
the timer fire again 100ms later, see a target speed mismatch, and repeatedly
fail, freezing the queues continuously?
> + } else {
> + WRITE_ONCE(sw->cur_speed, sw->target_speed);
> + }
[Severity: High]
If the timer concurrently updates sw->target_speed between the hardware
programming step and this write, sw->cur_speed will receive the new value
while the hardware was programmed to the old value. Will this cause the
hardware link speed to become permanently out-of-sync with the software state?
> +
> + nvme_unfreeze(ctrl);
[Severity: High]
Is it safe to unconditionally call nvme_unfreeze() here? nvme_unfreeze()
explicitly clears the NVME_CTRL_FROZEN bit. If this work finishes while a
concurrent controller reset is ongoing, it clears the flag prematurely, which
could break the reset state machine and allow namespaces to be added
concurrently with the reset.
> + dev_dbg(ctrl->device, "link rate changed to Gen%u\n",
> + READ_ONCE(sw->cur_speed));
> +}
[ ... ]
> +/* Called on the I/O submission path to accumulate the transferred bytes. */
> +void nvme_update_io_stats(struct nvme_ctrl *ctrl, struct request *req)
> +{
> + struct nvme_speed_switch *sw = &ctrl->speed_switch;
> + struct nvme_speed_switch_stats *stat;
> + enum req_op op = req_op(req);
> + unsigned int bytes = blk_rq_bytes(req);
> +
> + if (!READ_ONCE(sw->enabled))
> + return;
> +
> + if (op != REQ_OP_READ && op != REQ_OP_WRITE)
> + return;
> +
> + if (!sw->stats) {
> + dev_dbg(ctrl->device, "I/O statistics not allocated, skip accounting\n");
> + return;
> + }
> +
> + stat = get_cpu_ptr(sw->stats);
[Severity: High]
Could the I/O thread locklessly dereference sw->stats after it has been
freed? If the I/O thread is preempted right after checking sw->enabled, and a
concurrent controller reset calls nvme_speed_switch_exit() (which frees
sw->stats), the I/O thread would resume and access the freed memory here.
> + if (op == REQ_OP_READ)
> + stat->read_bytes += bytes;
> + else
> + stat->write_bytes += bytes;
> + put_cpu_ptr(sw->stats);
> +
> + if (atomic_cmpxchg(&sw->timer_active, NVME_SPEED_TIMER_INACTIVE,
> + NVME_SPEED_TIMER_ACTIVE) != NVME_SPEED_TIMER_INACTIVE)
> + return;
> +
> + /* First I/O of the activity period: compute the threshold and arm the timer. */
> + WRITE_ONCE(sw->threshold, nvme_calc_speed_threshold(sw,
> + pcie_get_width_cap(to_pci_dev(ctrl->dev))));
[Severity: Medium]
Does pcie_get_width_cap() perform a synchronous read of the PCIe configuration
space? Evaluating this on the first I/O of an activity period within the block
I/O submission fast path might introduce severe latency spikes for affected
requests.
> + if (!READ_ONCE(sw->threshold)) {
> + dev_warn(ctrl->device,
> + "failed to compute speed switch threshold, keeping current rate\n");
> + atomic_set(&sw->timer_active, NVME_SPEED_TIMER_INACTIVE);
> + return;
> + }
> + mod_timer(&sw->timer, jiffies + msecs_to_jiffies(READ_ONCE(sw->monitor_interval)));
> +}
[Severity: High]
Following up on the preemption scenario, if nvme_speed_switch_exit() completes
its teardown (including timer_delete_sync()), will this mod_timer() call
re-arm the timer after teardown has finished, leading to a delayed
use-after-free when the timer fires?
[ ... ]
> +static int nvme_check_io_and_decide_speed(struct nvme_speed_switch *sw)
> +{
> + struct nvme_ctrl *ctrl = container_of(sw, struct nvme_ctrl, speed_switch);
> + u64 read_bytes = 0, write_bytes = 0;
> + unsigned long read_kb, write_kb;
> + int cpu;
> +
> + for_each_possible_cpu(cpu) {
> + struct nvme_speed_switch_stats *stat = per_cpu_ptr(sw->stats, cpu);
> +
> + read_bytes += stat->read_bytes;
> + write_bytes += stat->write_bytes;
> + stat->read_bytes = 0;
> + stat->write_bytes = 0;
> + }
[Severity: Medium]
Could zeroing these remote per-CPU statistics lead to a data race? The timer
callback iterates over all CPUs and assigns 0 to stat->read_bytes and
stat->write_bytes of remote CPUs concurrently while the I/O submission path
might be updating these variables. Without atomic operations or
synchronization, this could cause incorrect statistics and dropped increments.
[ ... ]
> +void nvme_speed_switch_exit(struct nvme_ctrl *ctrl)
> +{
> + struct nvme_speed_switch *sw = &ctrl->speed_switch;
> +
> + if (!sw->initialized)
> + return;
> +
> + sw->initialized = false;
> + WRITE_ONCE(sw->enabled, false);
> + timer_delete_sync(&sw->timer);
> + cancel_work_sync(&sw->work);
> +
> + free_percpu(sw->stats);
> + sw->stats = NULL;
> +}
[Severity: High]
This teardown routine frees sw->stats and uses timer_delete_sync(), but does
this adequately prevent the timer from being asynchronously re-armed by an I/O
thread that had already passed the sw->enabled check?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1790222172.git.liaoxuan@hygon.cn?part=1
next prev parent reply other threads:[~2026-09-24 6:29 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-24 6:15 [PATCH 0/3] Add adaptive NVMe link rate switching function Liao Xuan
2026-09-24 6:15 ` [PATCH 1/3] nvme: Add adaptive PCIe " Liao Xuan
2026-09-24 6:29 ` sashiko-bot [this message]
2026-09-24 6:15 ` [PATCH 2/3] nvme: Add hysteresis and idle detection to link rate switching Liao Xuan
2026-09-24 6:27 ` sashiko-bot
2026-09-24 6:15 ` [PATCH 3/3] nvme: Expose link rate switching tunables via sysfs Liao Xuan
2026-09-24 6:29 ` 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=20260924062941.ADFE91F000FF@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=liaoxuan@open-hieco.net \
--cc=linux-pci@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/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