Linux PCI subsystem development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Liao Xuan" <liaoxuan@open-hieco.net>
Cc: linux-pci@vger.kernel.org
Subject: Re: [PATCH 2/3] nvme: Add hysteresis and idle detection to link rate switching
Date: Thu, 24 Sep 2026 06:27:29 +0000	[thread overview]
Message-ID: <20260924062729.860BF1F000FF@smtp.kernel.org> (raw)
In-Reply-To: <de23e8b9699b5a7a77e7ee215e8e4c9f530ce5da.1790222172.git.liaoxuan@hygon.cn>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] nvme: data race between timer and worker causes stuck link rate
--

commit 281ed4a8d51a45615fb54b154554270a5683089c
Author: Liao Xuan <liaoxuan@open-hieco.net>

nvme: Add hysteresis and idle detection to link rate switching
    
This patch adds counters to avoid excessive rate switching when the
workload fluctuates, requiring consecutive windows above or below a
threshold to trigger an upgrade or downgrade. It also stops monitoring
after 10 seconds of inactivity.

> diff --git a/drivers/nvme/host/speed_switch.c b/drivers/nvme/host/speed_switch.c
> index 88c046fcdde89..22f180c8119b2 100644
> --- a/drivers/nvme/host/speed_switch.c
> +++ b/drivers/nvme/host/speed_switch.c
[ ... ]
> @@ -247,31 +252,62 @@ static int nvme_check_io_and_decide_speed(struct nvme_speed_switch *sw)
>  		stat->write_bytes = 0;
>  	}
>  
> +	if (read_bytes || write_bytes) {
> +		*io_activity = true;
> +		sw->idle_cnt = 0;
> +	} else {
> +		sw->idle_cnt++;
> +	}
> +
> +	/* Stop monitoring after 10 seconds of inactivity (100 windows of 100ms). */
> +	if (sw->idle_cnt >= 100)
> +		*io_activity = false;
> +
>  	read_kb = read_bytes / 1024;
>  	write_kb = write_bytes / 1024;
>  	dev_dbg(ctrl->device,
>  		"I/O in window: read=%lu KB, write=%lu KB, threshold=%u KB\n",
>  		read_kb, write_kb, READ_ONCE(sw->threshold));
>  
> -	if (read_kb >= sw->threshold || write_kb >= sw->threshold)
> +	if (read_kb >= sw->threshold || write_kb >= sw->threshold) {
> +		sw->up_cnt++;
> +		sw->down_cnt = 0;
> +	} else {
> +		sw->down_cnt++;
> +		sw->up_cnt = 0;
> +	}
> +
> +	if (sw->up_cnt > READ_ONCE(sw->up_threshold)) {
> +		sw->up_cnt = 0;
>  		return sw->max_speed;
> +	}
> +
> +	if (sw->down_cnt > READ_ONCE(sw->down_threshold) || !*io_activity) {
> +		sw->down_cnt = 0;
> +		return sw->min_speed;
> +	}
>  
> -	return sw->min_speed;
> +	return READ_ONCE(sw->cur_speed);

[Severity: High]
Does returning cur_speed here cause a data race between the timer and the
worker that leaves the hardware link rate permanently throttled?

If the timer previously returned min_speed and scheduled
nvme_speed_switch_work(), the worker will freeze the queues and begin a slow
hardware transition via pcie_set_target_speed().

While the worker is blocked on the hardware transition, I/O is 0 because the
queues are frozen. The concurrent timer will increment down_cnt, but since it
is below down_threshold, it will fall through and return
READ_ONCE(sw->cur_speed).

Because the worker hasn't updated sw->cur_speed yet, it's still max_speed.
The timer then updates sw->target_speed to max_speed:

drivers/nvme/host/speed_switch.c:nvme_speed_switch_timer_fn() {
    ...
	sw->target_speed = nvme_check_io_and_decide_speed(sw, &io_activity);
    ...
}

When the worker finishes setting the hardware to min_speed, it double-reads
sw->target_speed:

drivers/nvme/host/speed_switch.c:nvme_speed_switch_work() {
    ...
	ret = pcie_set_target_speed(bridge, nvme_speed_to_bus_speed(sw->target_speed),
				    true);
	if (ret) {
        ...
	} else {
		WRITE_ONCE(sw->cur_speed, sw->target_speed);
	}
    ...
}

This overwrites cur_speed with the timer-corrupted value (max_speed).

The hardware is now throttled at min_speed, but software thinks it is at
max_speed.

When high I/O resumes, the timer will decide the target is max_speed, but
since cur_speed is already max_speed, it won't schedule the worker to restore
the link rate, causing a severe performance regression.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1790222172.git.liaoxuan@hygon.cn?part=2

  reply	other threads:[~2026-09-24  6:27 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
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 [this message]
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=20260924062729.860BF1F000FF@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