From: sashiko-bot@kernel.org
To: "Tao Cui" <cui.tao@linux.dev>
Cc: bpf@vger.kernel.org, tj@kernel.org, hannes@cmpxchg.org, mkoutny@suse.com
Subject: Re: [RFC PATCH v12 1/4] blk-iocost: add BPF struct_ops cost model support
Date: Tue, 03 Nov 2026 13:51:13 +0000 [thread overview]
Message-ID: <sashiko-outbox-195400@kernel.org> (raw)
In-Reply-To: <20261103132613.1952975-2-cui.tao@linux.dev>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] blk-iocost: lock order inversion between queue freeze and rq_qos_mutex
- [Medium] blk-iocost: stale autop timestamps across model attach/detach
--
commit 145ca796c6aa0e9492a94bdce49377cc07f92750
Author: Tao Cui <cuitao@kylinos.cn>
blk-iocost: add BPF struct_ops cost model support
This commit adds BPF struct_ops support (iocost_model_ops) to blk-iocost so
a BPF program can calculate per-bio vtime costs and provide page transfer
coefficients for a target block device. It also extends io.cost.model to
switch between the linear and attached BPF cost models while suspending
ctrl=auto profile stepping when a BPF model is active.
> diff --git a/block/blk-iocost.c b/block/blk-iocost.c
> index a81488c755cab..0f9c695b685af 100644
> --- a/block/blk-iocost.c
> +++ b/block/blk-iocost.c
[ ... ]
> @@ -806,7 +844,7 @@ static int ioc_autop_idx(struct ioc *ioc, struct gendisk *disk)
> return AUTOP_SSD_DFL;
>
> /* if user is overriding anything, maintain what was there */
> - if (ioc->user_qos_params || ioc->user_cost_model)
> + if (ioc->user_qos_params || ioc->user_cost_model || ioc_model(ioc))
> return idx;
>
> /* step up/down based on the vrate */
[ ... ]
> @@ -2908,10 +2968,33 @@ static void ioc_rqos_queue_depth_changed(struct rq_qos *rqos)
> spin_unlock_irq(&ioc->lock);
> }
>
> +/* Called by .unreg and by device removal, whichever is first. */
> +static void ioc_detach_model(struct ioc *ioc)
> +{
> +#ifdef CONFIG_BLK_CGROUP_IOCOST_BPF
> + const struct iocost_model_ops *ops = ioc->attached;
> +
> + lockdep_assert_held(&ioc->rqos.disk->queue->rq_qos_mutex);
> +
> + if (!ops)
> + return;
> +
> + spin_lock_irq(&ioc->lock);
> + rcu_assign_pointer(ioc->model, NULL);
> + ioc->attached = NULL;
> + ioc->attached_link = NULL;
> + spin_unlock_irq(&ioc->lock);
[Severity: Medium]
When ctrl=auto is active under the builtin linear model and vrate_pct crosses
too_fast_vrate_pct or too_slow_vrate_pct in ioc_autop_idx(), the current
timestamp is recorded in ioc->autop_too_fast_at or ioc->autop_too_slow_at,
requiring AUTOP_CYCLE_NSEC (10 seconds) of sustained condition before stepping
ioc->autop_idx.
When a BPF model is attached in ioc_bpf_reg() and later detached in
ioc_detach_model(), ioc->model is updated without calling
ioc_refresh_params(ioc, true) or clearing ioc->autop_too_fast_at and
ioc->autop_too_slow_at, unlike ioc_cost_model_write() and ioc_qos_write():
block/blk-iocost.c:ioc_refresh_params_disk() {
...
idx = ioc_autop_idx(ioc, disk);
p = &autop[idx];
if (idx == ioc->autop_idx && !force)
return false;
...
ioc->autop_idx = idx;
ioc->autop_too_fast_at = 0;
ioc->autop_too_slow_at = 0;
...
}
While the BPF model is active, ioc_autop_idx() returns idx early at the
ioc_model(ioc) check before reaching the timestamp update and reset logic, and
ioc_refresh_params_disk(ioc, false, disk) returns false early because
idx == ioc->autop_idx, leaving any pre-attachment timestamp in
ioc->autop_too_fast_at or ioc->autop_too_slow_at intact.
Once the BPF model is detached via ioc_bpf_unreg() -> ioc_detach_model(), on
the first timer tick (ioc_timer_fn() -> ioc_refresh_params(ioc, false) ->
ioc_autop_idx()), if vrate_pct is past the threshold,
now_ns - ioc->autop_too_fast_at (or autop_too_slow_at) includes the entire
time the BPF model was attached.
Could this cause ioc_autop_idx() to immediately exceed AUTOP_CYCLE_NSEC and
step ioc->autop_idx (resetting vtime_rate) on the first tick after detach
instead of waiting for a full AUTOP_CYCLE_NSEC cycle? Should ioc_bpf_reg() and
ioc_detach_model() reset the autop timestamps or call
ioc_refresh_params(ioc, true) under ioc->lock?
[ ... ]
> @@ -3559,7 +3644,11 @@ static ssize_t ioc_cost_model_write(struct kernfs_open_file *of, char *input,
> continue;
> case COST_MODEL:
> match_strlcpy(buf, &args[0], sizeof(buf));
> - if (strcmp(buf, "linear"))
> + if (!strcmp(buf, "linear"))
> + model = NULL;
> + else if (!strcmp(buf, "bpf") && ioc->attached)
> + model = ioc->attached;
> + else
> goto unlock;
> continue;
> }
> @@ -3579,6 +3668,7 @@ static ssize_t ioc_cost_model_write(struct kernfs_open_file *of, char *input,
> } else {
> ioc->user_cost_model = false;
> }
> + rcu_assign_pointer(ioc->model, model);
> ioc_refresh_params(ioc, true);
>
> ret = 0;
> @@ -3626,6 +3716,166 @@ static struct blkcg_policy blkcg_policy_iocost = {
> .pd_stat_fn = ioc_pd_stat,
> };
>
> +#ifdef CONFIG_BLK_CGROUP_IOCOST_BPF
> +static int ioc_bpf_reg(void *kdata, struct bpf_link *link)
> +{
> + struct iocost_model_ops *ops = kdata;
> + struct block_device *bdev;
> + struct request_queue *q;
> + unsigned int memflags;
> + struct ioc *ioc;
> + int ret;
> +
> + bdev = blkdev_get_no_open(new_decode_dev(ops->dev), false);
> + if (!bdev)
> + return -ENODEV;
> +
> + q = bdev_get_queue(bdev);
> + if (bdev_is_partition(bdev) || !queue_is_mq(q)) {
> + blkdev_put_no_open(bdev);
> + return -EOPNOTSUPP;
> + }
> +
> + /* the order of ioc_qos_write() */
> + memflags = blk_mq_freeze_queue(q);
> + mutex_lock(&q->rq_qos_mutex);
[Severity: High]
In ioc_bpf_reg(), blk_mq_freeze_queue(q) is called before acquiring
q->rq_qos_mutex, which establishes the lock order:
q->io_lockdep_map -> q->q_lockdep_map -> q->rq_qos_mutex
However, in ioc_cost_model_write() (which is used to toggle between
model=linear and model=bpf), blkg_conf_open_bdev(&ctx) acquires
q->rq_qos_mutex first, and unlike ioc_qos_write(), ioc_cost_model_write() does
not drop q->rq_qos_mutex before calling blk_iocost_init() and
blk_mq_freeze_queue(q):
block/blk-iocost.c:ioc_cost_model_write() {
blkg_conf_init(&ctx, input);
ret = blkg_conf_open_bdev(&ctx); // locks q->rq_qos_mutex
if (ret)
return ret;
...
ioc = q_to_ioc(q);
if (!ioc) {
ret = blk_iocost_init(ctx.bdev->bd_disk);
if (ret)
goto close_bdev;
ioc = q_to_ioc(q);
}
memflags = blk_mq_freeze_queue(q); // locks q->io_lockdep_map -> q->q_lockdep_map
...
}
This establishes the opposite lock order in ioc_cost_model_write():
q->rq_qos_mutex -> q->io_lockdep_map -> q->q_lockdep_map
In addition, paths that hold q->rq_qos_mutex without freezing the queue first
(such as ioc_cost_model_write() -> blk_iocost_init() and ioc_weight_write() ->
blkg_conf_prep()) perform GFP_KERNEL allocations under q->rq_qos_mutex without
memalloc_noio_save(), which can enter fs_reclaim and wait on a frozen queue.
Can attaching a BPF model via ioc_bpf_reg() and writing to io.cost.model via
ioc_cost_model_write() trigger a lockdep circular locking dependency splat or
an ABBA deadlock between queue freeze and q->rq_qos_mutex? Should
ioc_cost_model_write() release and re-acquire q->rq_qos_mutex around
blk_mq_freeze_queue(q) the same way ioc_qos_write() does?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261103132613.1952975-1-cui.tao@linux.dev?part=1
next prev parent reply other threads:[~2026-11-03 13:51 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-03 13:26 [RFC PATCH v12 0/4] blk-iocost: BPF struct_ops cost model Tao Cui
2026-11-03 13:26 ` [RFC PATCH v12 1/4] blk-iocost: add BPF struct_ops cost model support Tao Cui
2026-11-03 13:51 ` sashiko-bot [this message]
2026-11-04 3:10 ` Tao Cui
2026-11-03 16:51 ` Alexei Starovoitov
2026-11-04 3:08 ` Tao Cui
2026-11-03 13:26 ` [RFC PATCH v12 2/4] blk-iocost: add iocost_ioc_tick tracepoint for per-period device summary Tao Cui
2026-11-03 14:07 ` bot+bpf-ci
2026-11-04 3:12 ` Tao Cui
2026-11-03 13:26 ` [RFC PATCH v12 3/4] selftests/bpf: add iocost cost model test Tao Cui
2026-11-03 14:07 ` bot+bpf-ci
2026-11-04 3:13 ` Tao Cui
2026-11-03 14:16 ` sashiko-bot
2026-11-04 3:15 ` Tao Cui
2026-11-03 13:26 ` [RFC PATCH v12 4/4] docs: cgroup-v2: document the iocost BPF cost model attachment Tao Cui
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-195400@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=cui.tao@linux.dev \
--cc=hannes@cmpxchg.org \
--cc=mkoutny@suse.com \
--cc=sashiko-reviews@lists.linux.dev \
--cc=tj@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.