]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
arm_mpam: resctrl: Add monitor initialisation and domain boilerplate
authorBen Horgan <ben.horgan@arm.com>
Fri, 13 Mar 2026 14:46:04 +0000 (14:46 +0000)
committerJames Morse <james.morse@arm.com>
Fri, 27 Mar 2026 15:31:10 +0000 (15:31 +0000)
Add the boilerplate that tells resctrl about the mpam monitors that are
available. resctrl expects all (non-telemetry) monitors to be on the L3 and
so advertise them there and invent an L3 resctrl resource if required. The
L3 cache itself has to exist as the cache ids are used as the domain
ids.

Bring the resctrl monitor domains online and offline based on the cpus
they contain.

Support for specific monitor types is left to later.

Tested-by: Punit Agrawal <punit.agrawal@oss.qualcomm.com>
Reviewed-by: Zeng Heng <zengheng4@huawei.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Tested-by: Gavin Shan <gshan@redhat.com>
Tested-by: Shaopeng Tan <tan.shaopeng@jp.fujitsu.com>
Tested-by: Jesse Chick <jessechick@os.amperecomputing.com>
Signed-off-by: James Morse <james.morse@arm.com>
drivers/resctrl/mpam_internal.h
drivers/resctrl/mpam_resctrl.c

index 2751eeaba302dbd9832bd7a384e468f28f265536..301cf5c151bd9c04bf6679ca5bd6498e5cbc3dd2 100644 (file)
@@ -336,7 +336,16 @@ struct mpam_msc_ris {
 
 struct mpam_resctrl_dom {
        struct mpam_component           *ctrl_comp;
+
+       /*
+        * There is no single mon_comp because different events may be backed
+        * by different class/components. mon_comp is indexed by the event
+        * number.
+        */
+       struct mpam_component           *mon_comp[QOS_NUM_EVENTS];
+
        struct rdt_ctrl_domain          resctrl_ctrl_dom;
+       struct rdt_l3_mon_domain        resctrl_mon_dom;
 };
 
 struct mpam_resctrl_res {
@@ -345,6 +354,12 @@ struct mpam_resctrl_res {
        bool                    cdp_enabled;
 };
 
+struct mpam_resctrl_mon {
+       struct mpam_class       *class;
+
+       /* per-class data that resctrl needs will live here */
+};
+
 static inline int mpam_alloc_csu_mon(struct mpam_class *class)
 {
        struct mpam_props *cprops = &class->props;
index f8d4666fbaa8503ef92460ee1459cd709889de2e..e03d0f400993c50ac015ad27184378edcbec59b7 100644 (file)
@@ -34,6 +34,23 @@ static struct mpam_resctrl_res mpam_resctrl_controls[RDT_NUM_RESOURCES];
             rid < RDT_NUM_RESOURCES;                                           \
             rid++, res = &mpam_resctrl_controls[rid])
 
+/*
+ * The classes we've picked to map to resctrl events.
+ * Resctrl believes all the worlds a Xeon, and these are all on the L3. This
+ * array lets us find the actual class backing the event counters. e.g.
+ * the only memory bandwidth counters may be on the memory controller, but to
+ * make use of them, we pretend they are on L3. Restrict the events considered
+ * to those supported by MPAM.
+ * Class pointer may be NULL.
+ */
+#define MPAM_MAX_EVENT QOS_L3_MBM_TOTAL_EVENT_ID
+static struct mpam_resctrl_mon mpam_resctrl_counters[MPAM_MAX_EVENT + 1];
+
+#define for_each_mpam_resctrl_mon(mon, eventid)                                        \
+       for (eventid = QOS_FIRST_EVENT, mon = &mpam_resctrl_counters[eventid];  \
+            eventid <= MPAM_MAX_EVENT;                                         \
+            eventid++, mon = &mpam_resctrl_counters[eventid])
+
 /* The lock for modifying resctrl's domain lists from cpuhp callbacks. */
 static DEFINE_MUTEX(domain_list_lock);
 
@@ -63,6 +80,15 @@ bool resctrl_arch_alloc_capable(void)
        return false;
 }
 
+bool resctrl_arch_mon_capable(void)
+{
+       struct mpam_resctrl_res *res = &mpam_resctrl_controls[RDT_RESOURCE_L3];
+       struct rdt_resource *l3 = &res->resctrl_res;
+
+       /* All monitors are presented as being on the L3 cache */
+       return l3->mon_capable;
+}
+
 bool resctrl_arch_get_cdp_enabled(enum resctrl_res_level rid)
 {
        return mpam_resctrl_controls[rid].cdp_enabled;
@@ -89,6 +115,8 @@ static void resctrl_reset_task_closids(void)
 int resctrl_arch_set_cdp_enabled(enum resctrl_res_level rid, bool enable)
 {
        u32 partid_i = RESCTRL_RESERVED_CLOSID, partid_d = RESCTRL_RESERVED_CLOSID;
+       struct mpam_resctrl_res *res = &mpam_resctrl_controls[RDT_RESOURCE_L3];
+       struct rdt_resource *l3 = &res->resctrl_res;
        int cpu;
 
        if (!IS_ENABLED(CONFIG_EXPERT) && enable) {
@@ -110,6 +138,11 @@ int resctrl_arch_set_cdp_enabled(enum resctrl_res_level rid, bool enable)
        cdp_enabled = enable;
        mpam_resctrl_controls[rid].cdp_enabled = enable;
 
+       if (enable)
+               l3->mon.num_rmid = resctrl_arch_system_num_rmid_idx() / 2;
+       else
+               l3->mon.num_rmid = resctrl_arch_system_num_rmid_idx();
+
        /* The mbw_max feature can't hide cdp as it's a per-partid maximum. */
        if (cdp_enabled && !mpam_resctrl_controls[RDT_RESOURCE_MBA].cdp_enabled)
                mpam_resctrl_controls[RDT_RESOURCE_MBA].resctrl_res.alloc_capable = false;
@@ -674,6 +707,56 @@ static int mpam_resctrl_pick_domain_id(int cpu, struct mpam_component *comp)
        return comp->comp_id;
 }
 
+static int mpam_resctrl_monitor_init(struct mpam_resctrl_mon *mon,
+                                    enum resctrl_event_id type)
+{
+       struct mpam_resctrl_res *res = &mpam_resctrl_controls[RDT_RESOURCE_L3];
+       struct rdt_resource *l3 = &res->resctrl_res;
+
+       lockdep_assert_cpus_held();
+
+       /*
+        * There also needs to be an L3 cache present.
+        * The check just requires any online CPU and it can't go offline as we
+        * hold the cpu lock.
+        */
+       if (get_cpu_cacheinfo_id(raw_smp_processor_id(), 3) == -1)
+               return 0;
+
+       /*
+        * If there are no MPAM resources on L3, force it into existence.
+        * topology_matches_l3() already ensures this looks like the L3.
+        * The domain-ids will be fixed up by mpam_resctrl_domain_hdr_init().
+        */
+       if (!res->class) {
+               pr_warn_once("Faking L3 MSC to enable counters.\n");
+               res->class = mpam_resctrl_counters[type].class;
+       }
+
+       /*
+        * Called multiple times!, once per event type that has a
+        * monitoring class.
+        * Setting name is necessary on monitor only platforms.
+        */
+       l3->name = "L3";
+       l3->mon_scope = RESCTRL_L3_CACHE;
+
+       /*
+        * num-rmid is the upper bound for the number of monitoring groups that
+        * can exist simultaneously, including the default monitoring group for
+        * each control group. Hence, advertise the whole rmid_idx space even
+        * though each control group has its own pmg/rmid space. Unfortunately,
+        * this does mean userspace needs to know the architecture to correctly
+        * interpret this value.
+        */
+       l3->mon.num_rmid = resctrl_arch_system_num_rmid_idx();
+
+       if (resctrl_enable_mon_event(type, false, 0, NULL))
+               l3->mon_capable = true;
+
+       return 0;
+}
+
 u32 resctrl_arch_get_config(struct rdt_resource *r, struct rdt_ctrl_domain *d,
                            u32 closid, enum resctrl_conf_type type)
 {
@@ -901,11 +984,26 @@ static void mpam_resctrl_domain_insert(struct list_head *list,
        list_add_tail_rcu(&new->list, pos);
 }
 
+static struct mpam_component *find_component(struct mpam_class *class, int cpu)
+{
+       struct mpam_component *comp;
+
+       guard(srcu)(&mpam_srcu);
+       list_for_each_entry_srcu(comp, &class->components, class_list,
+                                srcu_read_lock_held(&mpam_srcu)) {
+               if (cpumask_test_cpu(cpu, &comp->affinity))
+                       return comp;
+       }
+
+       return NULL;
+}
+
 static struct mpam_resctrl_dom *
 mpam_resctrl_alloc_domain(unsigned int cpu, struct mpam_resctrl_res *res)
 {
        int err;
        struct mpam_resctrl_dom *dom;
+       struct rdt_l3_mon_domain *mon_d;
        struct rdt_ctrl_domain *ctrl_d;
        struct mpam_class *class = res->class;
        struct mpam_component *comp_iter, *ctrl_comp;
@@ -945,8 +1043,56 @@ mpam_resctrl_alloc_domain(unsigned int cpu, struct mpam_resctrl_res *res)
        } else {
                pr_debug("Skipped control domain online - no controls\n");
        }
+
+       if (r->mon_capable) {
+               struct mpam_component *any_mon_comp;
+               struct mpam_resctrl_mon *mon;
+               enum resctrl_event_id eventid;
+
+               /*
+                * Even if the monitor domain is backed by a different
+                * component, the L3 component IDs need to be used... only
+                * there may be no ctrl_comp for the L3.
+                * Search each event's class list for a component with
+                * overlapping CPUs and set up the dom->mon_comp array.
+                */
+
+               for_each_mpam_resctrl_mon(mon, eventid) {
+                       struct mpam_component *mon_comp;
+
+                       if (!mon->class)
+                               continue;       // dummy resource
+
+                       mon_comp = find_component(mon->class, cpu);
+                       dom->mon_comp[eventid] = mon_comp;
+                       if (mon_comp)
+                               any_mon_comp = mon_comp;
+               }
+               if (!any_mon_comp) {
+                       WARN_ON_ONCE(0);
+                       err = -EFAULT;
+                       goto offline_ctrl_domain;
+               }
+
+               mon_d = &dom->resctrl_mon_dom;
+               mpam_resctrl_domain_hdr_init(cpu, any_mon_comp, r->rid, &mon_d->hdr);
+               mon_d->hdr.type = RESCTRL_MON_DOMAIN;
+               err = resctrl_online_mon_domain(r, &mon_d->hdr);
+               if (err)
+                       goto offline_ctrl_domain;
+
+               mpam_resctrl_domain_insert(&r->mon_domains, &mon_d->hdr);
+       } else {
+               pr_debug("Skipped monitor domain online - no monitors\n");
+       }
+
        return dom;
 
+offline_ctrl_domain:
+       if (r->alloc_capable) {
+               mpam_resctrl_offline_domain_hdr(cpu, &ctrl_d->hdr);
+               resctrl_offline_ctrl_domain(r, ctrl_d);
+       }
 free_domain:
        kfree(dom);
        dom = ERR_PTR(err);
@@ -954,6 +1100,35 @@ free_domain:
        return dom;
 }
 
+/*
+ * We know all the monitors are associated with the L3, even if there are no
+ * controls and therefore no control component. Find the cache-id for the CPU
+ * and use that to search for existing resctrl domains.
+ * This relies on mpam_resctrl_pick_domain_id() using the L3 cache-id
+ * for anything that is not a cache.
+ */
+static struct mpam_resctrl_dom *mpam_resctrl_get_mon_domain_from_cpu(int cpu)
+{
+       int cache_id;
+       struct mpam_resctrl_dom *dom;
+       struct mpam_resctrl_res *l3 = &mpam_resctrl_controls[RDT_RESOURCE_L3];
+
+       lockdep_assert_cpus_held();
+
+       if (!l3->class)
+               return NULL;
+       cache_id = get_cpu_cacheinfo_id(cpu, 3);
+       if (cache_id < 0)
+               return NULL;
+
+       list_for_each_entry_rcu(dom, &l3->resctrl_res.mon_domains, resctrl_mon_dom.hdr.list) {
+               if (dom->resctrl_mon_dom.hdr.id == cache_id)
+                       return dom;
+       }
+
+       return NULL;
+}
+
 static struct mpam_resctrl_dom *
 mpam_resctrl_get_domain_from_cpu(int cpu, struct mpam_resctrl_res *res)
 {
@@ -967,7 +1142,11 @@ mpam_resctrl_get_domain_from_cpu(int cpu, struct mpam_resctrl_res *res)
                        return dom;
        }
 
-       return NULL;
+       if (r->rid != RDT_RESOURCE_L3)
+               return NULL;
+
+       /* Search the mon domain list too - needed on monitor only platforms. */
+       return mpam_resctrl_get_mon_domain_from_cpu(cpu);
 }
 
 int mpam_resctrl_online_cpu(unsigned int cpu)
@@ -994,6 +1173,11 @@ int mpam_resctrl_online_cpu(unsigned int cpu)
 
                                mpam_resctrl_online_domain_hdr(cpu, &ctrl_d->hdr);
                        }
+                       if (r->mon_capable) {
+                               struct rdt_l3_mon_domain *mon_d = &dom->resctrl_mon_dom;
+
+                               mpam_resctrl_online_domain_hdr(cpu, &mon_d->hdr);
+                       }
                }
        }
 
@@ -1012,8 +1196,9 @@ void mpam_resctrl_offline_cpu(unsigned int cpu)
        guard(mutex)(&domain_list_lock);
        for_each_mpam_resctrl_control(res, rid) {
                struct mpam_resctrl_dom *dom;
+               struct rdt_l3_mon_domain *mon_d;
                struct rdt_ctrl_domain *ctrl_d;
-               bool ctrl_dom_empty;
+               bool ctrl_dom_empty, mon_dom_empty;
                struct rdt_resource *r = &res->resctrl_res;
 
                if (!res->class)
@@ -1032,7 +1217,16 @@ void mpam_resctrl_offline_cpu(unsigned int cpu)
                        ctrl_dom_empty = true;
                }
 
-               if (ctrl_dom_empty)
+               if (r->mon_capable) {
+                       mon_d = &dom->resctrl_mon_dom;
+                       mon_dom_empty = mpam_resctrl_offline_domain_hdr(cpu, &mon_d->hdr);
+                       if (mon_dom_empty)
+                               resctrl_offline_mon_domain(&res->resctrl_res, &mon_d->hdr);
+               } else {
+                       mon_dom_empty = true;
+               }
+
+               if (ctrl_dom_empty && mon_dom_empty)
                        kfree(dom);
        }
 }
@@ -1042,12 +1236,15 @@ int mpam_resctrl_setup(void)
        int err = 0;
        struct mpam_resctrl_res *res;
        enum resctrl_res_level rid;
+       struct mpam_resctrl_mon *mon;
+       enum resctrl_event_id eventid;
 
        wait_event(wait_cacheinfo_ready, cacheinfo_ready);
 
        cpus_read_lock();
        for_each_mpam_resctrl_control(res, rid) {
                INIT_LIST_HEAD_RCU(&res->resctrl_res.ctrl_domains);
+               INIT_LIST_HEAD_RCU(&res->resctrl_res.mon_domains);
                res->resctrl_res.rid = rid;
        }
 
@@ -1063,25 +1260,37 @@ int mpam_resctrl_setup(void)
                err = mpam_resctrl_control_init(res);
                if (err) {
                        pr_debug("Failed to initialise rid %u\n", rid);
-                       break;
+                       goto internal_error;
                }
        }
-       cpus_read_unlock();
 
-       if (err) {
-               pr_debug("Internal error %d - resctrl not supported\n", err);
-               return err;
+       for_each_mpam_resctrl_mon(mon, eventid) {
+               if (!mon->class)
+                       continue;       // dummy resource
+
+               err = mpam_resctrl_monitor_init(mon, eventid);
+               if (err) {
+                       pr_debug("Failed to initialise event %u\n", eventid);
+                       goto internal_error;
+               }
        }
 
-       if (!resctrl_arch_alloc_capable()) {
-               pr_debug("No alloc(%u) found - resctrl not supported\n",
-                        resctrl_arch_alloc_capable());
+       cpus_read_unlock();
+
+       if (!resctrl_arch_alloc_capable() && !resctrl_arch_mon_capable()) {
+               pr_debug("No alloc(%u) or monitor(%u) found - resctrl not supported\n",
+                        resctrl_arch_alloc_capable(), resctrl_arch_mon_capable());
                return -EOPNOTSUPP;
        }
 
        /* TODO: call resctrl_init() */
 
        return 0;
+
+internal_error:
+       cpus_read_unlock();
+       pr_debug("Internal error %d - resctrl not supported\n", err);
+       return err;
 }
 
 static int __init __cacheinfo_ready(void)