[RFC PATCH 28/31] x86/resctrl: Emulate the legacy MBA controller via the region MAX controls

From: Chen Yu

Date: Sun Aug 02 2026 - 12:19:08 EST


On ERDT platforms the legacy MBA "default" controller does have hardware of
its own, but the region-based MAX controllers are the preferred way to
throttle memory bandwidth. Rather than programming the legacy MSR, realize
the default controller's effect through the MAX controller of every memory
region, which keeps a single consistent throttling mechanism in use. Link
the default controller to the MAX controller of every available region via
resctrl_ctrl::emul in erdt_get_mem_config() so they are associated once
the controllers are created.

Route the arch control writes accordingly. When a control is emulated
(ctrl->emul is not empty), do not program the legacy MSR; instead
translate the configured value into each emulation controller's units and
program that controller's hardware.

Signed-off-by: Chen Yu <yu.c.chen@xxxxxxxxx>
---
arch/x86/kernel/cpu/resctrl/ctrlmondata.c | 75 ++++++++++++++++++++---
arch/x86/kernel/cpu/resctrl/erdt.c | 18 ++++++
include/linux/resctrl.h | 3 +
3 files changed, 88 insertions(+), 8 deletions(-)

diff --git a/arch/x86/kernel/cpu/resctrl/ctrlmondata.c b/arch/x86/kernel/cpu/resctrl/ctrlmondata.c
index 7bacb8bf2e27..7fe48912d2d5 100644
--- a/arch/x86/kernel/cpu/resctrl/ctrlmondata.c
+++ b/arch/x86/kernel/cpu/resctrl/ctrlmondata.c
@@ -28,6 +28,48 @@ static void ctrl_hw_update(struct resctrl_ctrl *ctrl, struct rdt_ctrl_domain *d,
smp_call_function_any(&d->hdr.cpu_mask, rdt_ctrl_update, m, 1);
}

+static u32 emul_translate_val(struct resctrl_ctrl *src, struct resctrl_ctrl *dst,
+ u32 val)
+{
+ return val * dst->scalar.max_bw / src->scalar.max_bw;
+}
+
+static void _resctrl_arch_update_emul(struct rdt_resource *r,
+ struct resctrl_ctrl *ctrl,
+ struct rdt_ctrl_domain *d, u32 low, u32 high)
+{
+ struct rdt_hw_ctrl_domain *emul_hw_dom;
+ struct rdt_hw_ctrl_domain *hw_dom;
+ struct rdt_ctrl_domain *emul_d;
+ struct resctrl_ctrl *emul;
+ struct rdt_domain_hdr *hdr;
+ struct hw_param emul_param;
+ unsigned int i;
+
+ hw_dom = resctrl_to_arch_ctrl_dom(d);
+
+ for_each_emul_ctrl(emul, ctrl) {
+ hdr = resctrl_find_domain(&emul->domains, d->hdr.id, NULL);
+ if (!hdr)
+ continue;
+
+ emul_d = container_of(hdr, struct rdt_ctrl_domain, hdr);
+ emul_hw_dom = resctrl_to_arch_ctrl_dom(emul_d);
+
+ for (i = low; i < high; i++)
+ emul_hw_dom->ctrl_val[i] = emul_translate_val(ctrl, emul,
+ hw_dom->ctrl_val[i]);
+
+ emul_param.res = r;
+ emul_param.ctrl = emul;
+ emul_param.dom = emul_d;
+ emul_param.low = low;
+ emul_param.high = high;
+
+ ctrl_hw_update(emul, emul_d, &emul_param);
+ }
+}
+
int resctrl_arch_update_one(struct rdt_resource *r, struct resctrl_ctrl *ctrl,
struct rdt_ctrl_domain *d, u32 closid,
enum resctrl_conf_type t, u32 cfg_val)
@@ -42,12 +84,20 @@ int resctrl_arch_update_one(struct rdt_resource *r, struct resctrl_ctrl *ctrl,

hw_dom->ctrl_val[idx] = cfg_val;

- hw_param.res = r;
- hw_param.ctrl = ctrl;
- hw_param.dom = d;
- hw_param.low = idx;
- hw_param.high = idx + 1;
- hw_ctrl->hw_update(&hw_param);
+ /*
+ * When this control is emulated, program the emulation controllers'
+ * hardware instead.
+ */
+ if (!list_empty(&ctrl->emul)) {
+ _resctrl_arch_update_emul(r, ctrl, d, idx, idx + 1);
+ } else {
+ hw_param.res = r;
+ hw_param.ctrl = ctrl;
+ hw_param.dom = d;
+ hw_param.low = idx;
+ hw_param.high = idx + 1;
+ hw_ctrl->hw_update(&hw_param);
+ }

return 0;
}
@@ -89,8 +139,17 @@ static void _resctrl_arch_update_domains(struct rdt_resource *r,
hw_param.high = max(hw_param.high, idx + 1);
}
}
- if (hw_param.res)
- ctrl_hw_update(ctrl, d, &hw_param);
+ if (hw_param.res) {
+ /*
+ * When this control is emulated, program the emulation
+ * controllers' hardware instead.
+ */
+ if (!list_empty(&ctrl->emul))
+ _resctrl_arch_update_emul(r, ctrl, d,
+ hw_param.low, hw_param.high);
+ else
+ ctrl_hw_update(ctrl, d, &hw_param);
+ }
}
}

diff --git a/arch/x86/kernel/cpu/resctrl/erdt.c b/arch/x86/kernel/cpu/resctrl/erdt.c
index 6464463973bb..4515e3251476 100644
--- a/arch/x86/kernel/cpu/resctrl/erdt.c
+++ b/arch/x86/kernel/cpu/resctrl/erdt.c
@@ -325,6 +325,7 @@ static void marc_hw_update(struct hw_param *m)
__init bool erdt_get_mem_config(struct rdt_resource *r)
{
struct rdt_hw_resource *hw_res = resctrl_to_arch_res(r);
+ struct resctrl_ctrl *def_ctrl = NULL, *ctrl;
struct resctrl_hw_ctrl *hw_ctrl;
struct erdt_domain_info *d;
struct acpi_erdt_marc *marc;
@@ -343,6 +344,13 @@ __init bool erdt_get_mem_config(struct rdt_resource *r)
max_regions = acpi_mrrm_max_mem_region();
hw_res->num_closid = max(hw_res->num_closid, erdt_max_clos + 1);

+ for_each_resource_ctrl(ctrl, r) {
+ if (ctrl->name == RESCTRL_CTRL_NAME_DEF) {
+ def_ctrl = ctrl;
+ break;
+ }
+ }
+
for (region = 0; region < max_regions; region++) {
for (type = RESCTRL_CTRL_REGION_TYPE_OPT; type < RESCTRL_CTRL_REGION_NR_CTRLS; type++) {
if (type == RESCTRL_CTRL_REGION_TYPE_OPT && !(marc->flags & MARC_FLAG_OPT))
@@ -374,6 +382,16 @@ __init bool erdt_get_mem_config(struct rdt_resource *r)
/* MARC controls live in MMIO, program from any CPU. */
hw_ctrl->r_ctrl.flags |= RESCTRL_CTRL_FLAG_ANY_CPU;
list_add_tail(&hw_ctrl->r_ctrl.entry, &r->controls);
+
+ /*
+ * Throttle memory bandwidth through the region MAX
+ * controls rather than the legacy MBA MSR, so let the
+ * MAX control of every region emulate the legacy MBA
+ * control.
+ */
+ if (def_ctrl && type == RESCTRL_CTRL_REGION_TYPE_MAX)
+ list_add_tail(&hw_ctrl->r_ctrl.emul_entry,
+ &def_ctrl->emul);
}
}

diff --git a/include/linux/resctrl.h b/include/linux/resctrl.h
index 8260b09aa323..57615af637ad 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -51,6 +51,9 @@ int proc_resctrl_show(struct seq_file *m,
#define for_each_resource_ctrl(ctrl, r) \
list_for_each_entry(ctrl, &r->controls, entry)

+#define for_each_emul_ctrl(emul, ctrl) \
+ list_for_each_entry(emul, &ctrl->emul, emul_entry)
+
enum resctrl_res_level {
RDT_RESOURCE_L3,
RDT_RESOURCE_L2,
--
2.43.0