Re: [PATCH v8 2/5] riscv_cbqri: resctrl: Add cache allocation via capacity block mask

Reinette Chatre <[email protected]>
Newsgroups org.kernel.vger.linux-doc,dev.linux.lists.linux-rt-devel,org.infradead.lists.linux-riscv,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Hi Drew,

On 9/17/26 9:39 AM, Drew Fustini wrote:
> diff --git a/arch/riscv/include/asm/resctrl.h b/arch/riscv/include/asm/resctrl.h
> new file mode 100644
> index 000000000000..b08f4e12f7aa
> --- /dev/null
> +++ b/arch/riscv/include/asm/resctrl.h

...

> +/**
> + * resctrl_arch_alloc_capable() - any CBQRI controller exposes resctrl alloc
> + *
> + * Returns true once at least one CBQRI controller has successfully probed for
> + * a resctrl-exposed cache capacity allocation feature. Only meaningful after
> + * cbqri_resctrl_setup() runs at late_initcall.
> + */
> +bool resctrl_arch_alloc_capable(void);
> +
> +/**
> + * resctrl_arch_mon_capable() - any CBQRI controller exposes resctrl monitoring
> + *
> + * The CBQRI driver implements capacity allocation only and wires up no
> + * monitoring events, so this always returns false. fs/resctrl references it
> + * unconditionally, hence the stub.
> + */
> +bool resctrl_arch_mon_capable(void);
> +

fyi ... I aim to comment more details later in this patch but for now please note that
there are plans to remove the above two hooks since resctrl self has needed
information via the rdt_resource::alloc_capable and rdt_resource::mon_capable flags.

For reference:
	https://lore.kernel.org/lkml/[email protected]/

I see this has impact on this driver that I comment more below.

...

> diff --git a/drivers/resctrl/cbqri_resctrl.c b/drivers/resctrl/cbqri_resctrl.c
> new file mode 100644
> index 000000000000..0c4bfa2a7f43
> --- /dev/null
> +++ b/drivers/resctrl/cbqri_resctrl.c
> @@ -0,0 +1,785 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
> +
> +#include <linux/bitfield.h>
> +#include <linux/cacheinfo.h>
> +#include <linux/cleanup.h>
> +#include <linux/riscv_cbqri.h>

Just an observation (I have no intention to comment on the style):
The new c files in this series seem to make an effort to have the #include
files organized alphabetically, except when if comes to the file above?

> +#include <linux/cpu.h>
> +#include <linux/cpufeature.h>
> +#include <linux/cpuhotplug.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/rculist.h>
> +#include <linux/resctrl.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +
> +#include <asm/csr.h>
> +#include <asm/qos.h>
> +
> +#include "cbqri_internal.h"
> +
> +struct cbqri_resctrl_res {
> +	struct cbqri_controller *ctrl;
> +	struct rdt_resource     resctrl_res;
> +	bool                    cdp_enabled;
> +};
> +
> +struct cbqri_resctrl_dom {
> +	struct rdt_ctrl_domain  resctrl_ctrl_dom;
> +	struct cbqri_controller *hw_ctrl;
> +};

Is cbqri_resctrl_dom::hw_ctrl necessary? From what I can tell it is
initialized from cbqri_resctrl_res::ctrl when a new domain is created
and thus identical in all domains that belong to a resource. 

It looks to me as though the resource is always available when the associated
controller information is needed so it looks like just cbqri_resctrl_res::ctrl
could do?

The way the data is organized results in potentially confusing code since, for
example, when initializing the control values of a single domain
(cbqri_init_domain_ctrlval()) the RCID count is obtained from the domain's
cbqri_resctrl_dom::hw_ctrl::rcid_count but when initializing the control values of
all domains (resctrl_arch_reset_all_ctrls()) the RCID count is obtained from the
resource's cbqri_resctrl_res::ctrl::rcid_count instead of each domain's domain
cbqri_resctrl_dom::hw_ctrl::rcid_count.

> +
> +static struct cbqri_resctrl_res cbqri_resctrl_resources[RDT_NUM_RESOURCES];
> +
> +static bool exposed_alloc_capable;
> +
> +/* Protects ctrl_domain list mutations across CPU hotplug. */
> +static DEFINE_MUTEX(cbqri_domain_list_lock);
> +
> +static struct rdt_ctrl_domain *
> +cbqri_find_ctrl_domain(struct list_head *h, int id)
> +{
> +	struct rdt_domain_hdr *hdr = resctrl_find_domain(h, id, NULL);
> +
> +	return hdr ? container_of(hdr, struct rdt_ctrl_domain, hdr) : NULL;
> +}
> +

...

> +
> +bool resctrl_arch_alloc_capable(void)
> +{
> +	return exposed_alloc_capable;
> +}
> +

...

> +
> +static void cbqri_resctrl_accumulate_caps(void)
> +{
> +	int rid;
> +
> +	for (rid = 0; rid < RDT_NUM_RESOURCES; rid++) {
> +		struct cbqri_resctrl_res *hw_res = &cbqri_resctrl_resources[rid];
> +
> +		if (!hw_res->ctrl)
> +			continue;
> +		if (hw_res->ctrl->alloc_capable)
> +			exposed_alloc_capable = true;
> +	}
> +}

Since it captures whether any of the resources are alloc_capable it looks like
exposed_alloc_capable indeed reflects the same information as what resctrl will
use after resctrl_arch_alloc_capable() is dropped (see patch linked earlier).
Except that cbqri_resctrl_teardown() only resets exposed_alloc_capable but not
the rdt_resource::mon_capable and rdt_resource::alloc_capable flags that the new
helper will use to determine if a resource is capable of allocation or monitoring.

The new helper will thus change behavior. It looks like resctrl_exit() is only
called on error path during initialization so it seems unlikely that those flags
will be referenced by this driver.

If there are plans to call resctrl_exit() in other paths like MPAM driver does there
may be issues since the resctrl filesystem may still be mounted and when user space
unmounts it the rdt_resource::mon_capable and rdt_resource::alloc_capable will be
used and result in some arch callbacks called.

I wonder if it may simplify driver initialization to call resctrl_init() _after_
setting up the CPU online/offline handlers (this is how the x86 driver does it).

> +
> +/*
> + * Create, list-insert, and online a fresh ctrl_domain backing ctrl on
> + * resource res, seeded with cpu and identified by dom_id. Caller must
> + * hold cbqri_domain_list_lock and must have already verified that no
> + * existing ctrl_domain on res carries this id.
> + */
> +static struct rdt_ctrl_domain *cbqri_create_ctrl_domain(struct cbqri_controller *ctrl,
> +							struct rdt_resource *res,
> +							unsigned int cpu, int dom_id)
> +{
> +	struct rdt_ctrl_domain *domain;
> +	struct list_head *pos = NULL;
> +	int err;
> +
> +	domain = cbqri_new_domain(ctrl);
> +	if (!domain)
> +		return ERR_PTR(-ENOMEM);
> +
> +	cpumask_set_cpu(cpu, &domain->hdr.cpu_mask);
> +	domain->hdr.id = dom_id;
> +	domain->hdr.type = RESCTRL_CTRL_DOMAIN;
> +	domain->hdr.rid = res->rid;
> +
> +	err = cbqri_init_domain_ctrlval(res, domain);
> +	if (err)
> +		goto free;
> +
> +	err = resctrl_online_ctrl_domain(res, domain);
> +	if (err)
> +		goto free;
> +
> +	/*
> +	 * Publish only after the domain is fully initialized and online, so a
> +	 * reader walking the RCU list never sees a half-built domain.
> +	 */
> +	resctrl_find_domain(&res->ctrl_domains, dom_id, &pos);

The caller loops over the domain list to determine whether it needs to create
a new domain or not and then this domain create code loops over the list again
to determine where to insert the new domain. Could this perhaps be simplified if
the first loop  cbqri_attach_cpu_to_all_ctrls()->cbqri_find_ctrl_domain() determines
the position at the same time as determining the presence and pass that to
this function to just do the insert without searching the list again?

> +	list_add_tail_rcu(&domain->hdr.list, pos);
> +
> +	return domain;
> +free:
> +	kfree(container_of(domain, struct cbqri_resctrl_dom, resctrl_ctrl_dom));
> +	return ERR_PTR(err);
> +}

...

> +/*
> + * Attach a CPU to the capacity controller at each cache level whose cache
> + * the CPU shares. On failure, detach the CPU from everything attached so
> + * far: the cpuhp core does not run this state's offline teardown when its
> + * startup fails, so a partial attach would otherwise leak into the domain
> + * cpu_masks. Caller holds cbqri_domain_list_lock.
> + */
> +static int cbqri_attach_cpu_to_all_ctrls(unsigned int cpu)
> +{
> +	static const u32 levels[] = { 2, 3 };
> +	struct cbqri_controller *ctrl, *c;
> +	struct cbqri_resctrl_res *hw_res;
> +	struct rdt_ctrl_domain *d;
> +	struct cacheinfo *ci;
> +	int i, rid;
> +
> +	lockdep_assert_held(&cbqri_domain_list_lock);
> +
> +	/*
> +	 * Hold cbqri_controllers_lock across the walk so a controller
> +	 * registered after boot cannot corrupt it. The register path takes
> +	 * it as a leaf and never cbqri_domain_list_lock, so this nesting
> +	 * cannot invert.
> +	 */
> +	guard(mutex)(&cbqri_controllers_lock);
> +
> +	for (i = 0; i < ARRAY_SIZE(levels); i++) {
> +		ci = get_cpu_cacheinfo_level(cpu, levels[i]);
> +		if (!ci)
> +			continue;
> +
> +		rid = cbqri_cache_level_to_rid(levels[i]);
> +		hw_res = &cbqri_resctrl_resources[rid];
> +		if (!hw_res->ctrl)
> +			continue;
> +
> +		/* The controller backing this CPU's cache at this level. */
> +		ctrl = NULL;
> +		list_for_each_entry(c, &cbqri_controllers, list) {
> +			if (c->type == CBQRI_CONTROLLER_TYPE_CAPACITY &&> +			    c->alloc_capable &&
> +			    c->cache.cache_level == levels[i] &&
> +			    c->cache.cache_id == ci->id) {
> +				ctrl = c;
> +				break;

Is it necessary to loop over cbqri_controllers and repeat these tests? Above seems to
duplicate the work done during initialization (cbqri_resctrl_pick_caches()) that resulted
in initialization of cbqri_resctrl_res::ctrl so it seems that after testing for existence
this function could just use cbqri_resctrl_res::ctrl without again referencing cbqri_controllers?

If I understand correctly it may be that new controllers appear in cbqri_controllers 
after this driver is initialized and the resources are initialized so the CPU online/offline
helpers may need to take care how any controllers in cbqri_controllers not seen by
cbqri_resctrl_setup() are handled.

> +			}
> +		}
> +		if (!ctrl)
> +			continue;
> +
> +		d = cbqri_find_ctrl_domain(&hw_res->resctrl_res.ctrl_domains,
> +					   ci->id);
> +		if (d) {
> +			cpumask_set_cpu(cpu, &d->hdr.cpu_mask);
> +			continue;
> +		}
> +
> +		d = cbqri_create_ctrl_domain(ctrl, &hw_res->resctrl_res, cpu,
> +					     ci->id);
> +		if (IS_ERR(d)) {
> +			cbqri_detach_cpu_from_all_ctrls(cpu);
> +			return PTR_ERR(d);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static bool cbqri_resctrl_inited;
> +
> +static void cbqri_resctrl_teardown(void)
> +{
> +	int rid;
> +
> +	if (!cbqri_resctrl_inited)
> +		return;
> +
> +	resctrl_exit();
> +
> +	for (rid = 0; rid < RDT_NUM_RESOURCES; rid++) {
> +		struct cbqri_resctrl_res *hw_res = &cbqri_resctrl_resources[rid];
> +
> +		hw_res->ctrl = NULL;
> +		hw_res->cdp_enabled = false;
> +	}
> +	exposed_alloc_capable = false;
> +	cbqri_resctrl_inited = false;
> +}
> +
> +static int cbqri_resctrl_setup(void)
> +{
> +	int rid;
> +	int err;
> +
> +	for (rid = 0; rid < RDT_NUM_RESOURCES; rid++)
> +		cbqri_resctrl_resources[rid].resctrl_res.rid = rid;
> +
> +	cbqri_resctrl_pick_caches();
> +
> +	for (rid = 0; rid < RDT_NUM_RESOURCES; rid++)
> +		cbqri_resctrl_control_init(&cbqri_resctrl_resources[rid]);
> +
> +	cbqri_resctrl_accumulate_caps();
> +
> +	if (!exposed_alloc_capable) {
> +		pr_debug("no resctrl-capable CBQRI controllers found\n");
> +		return -ENODEV;
> +	}
> +
> +	err = resctrl_init();
> +	if (err)
> +		return err;
> +
> +	cbqri_resctrl_inited = true;
> +	return 0;
> +}
> +
> +static int cbqri_resctrl_online_cpu(unsigned int cpu)
> +{
> +	int err;
> +
> +	mutex_lock(&cbqri_domain_list_lock);
> +	err = cbqri_attach_cpu_to_all_ctrls(cpu);
> +	mutex_unlock(&cbqri_domain_list_lock);
> +	if (err)
> +		return err;
> +
> +	/*
> +	 * Seed the per-CPU default RCID/MCID to the reserved (0, 0) pair and
> +	 * notify the resctrl core so it tracks this CPU in the default group.
> +	 */
> +	resctrl_arch_set_cpu_default_closid_rmid(cpu, 0, 0);
> +	resctrl_online_cpu(cpu);
> +	return 0;
> +}
> +
> +static int cbqri_resctrl_offline_cpu(unsigned int cpu)
> +{
> +	resctrl_offline_cpu(cpu);
> +
> +	mutex_lock(&cbqri_domain_list_lock);
> +	cbqri_detach_cpu_from_all_ctrls(cpu);
> +	mutex_unlock(&cbqri_domain_list_lock);
> +	return 0;
> +}
> +
> +static int __init cbqri_arch_late_init(void)
> +{
> +	int err;
> +
> +	if (!riscv_isa_extension_available(NULL, SSQOSID))
> +		return -ENODEV;
> +
> +	err = cbqri_resctrl_setup();
> +	if (err)
> +		return err;
> +
> +	err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "cbqri:online",
> +				cbqri_resctrl_online_cpu,
> +				cbqri_resctrl_offline_cpu);
> +	if (err < 0) {
> +		cbqri_resctrl_teardown();

cbqri_resctrl_teardown() calls resctrl_exit() that will complain
via WARN_ON_ONCE() if any domains exist at that time. It is not clear
to me if this can be guaranteed here.

> +		return err;
> +	}
> +
> +	return 0;
> +}
> +late_initcall(cbqri_arch_late_init);
> 

Reinette
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.