* [PATCH 01/27] arm_mpam: Move MPAMF_ECR write helpers to allow reuse
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 02/27] arm_mpam: Restore the error interrupt enable from mpam_cpu_online() James Morse
` (25 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Gavin Shan, James Morse
From: Ben Horgan <ben.horgan@arm.com>
In preparation for calling mpam_enable_msc_ecr() from the CPU hotplug
handlers move it higher in the file. Move mpam_disable_msc_ecr() too to
keep them together.
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Tested-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: James Morse <james.morse@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 38 +++++++++++++++++-----------------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index dd422c56fbb1..580d7ed38569 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1817,6 +1817,25 @@ static void mpam_reprogram_msc(struct mpam_msc *msc)
mutex_unlock(&msc->cfg_lock);
}
+static int mpam_enable_msc_ecr(void *_msc)
+{
+ struct mpam_msc *msc = _msc;
+
+ __mpam_write_reg(msc, MPAMF_ECR, MPAMF_ECR_INTEN);
+
+ return 0;
+}
+
+/* This can run in mpam_disable(), and the interrupt handler on the same CPU */
+static int mpam_disable_msc_ecr(void *_msc)
+{
+ struct mpam_msc *msc = _msc;
+
+ __mpam_write_reg(msc, MPAMF_ECR, 0);
+
+ return 0;
+}
+
static void _enable_percpu_irq(void *_irq)
{
int *irq = _irq;
@@ -2443,25 +2462,6 @@ static char *mpam_errcode_names[16] = {
[12 ... 15] = "Reserved"
};
-static int mpam_enable_msc_ecr(void *_msc)
-{
- struct mpam_msc *msc = _msc;
-
- __mpam_write_reg(msc, MPAMF_ECR, MPAMF_ECR_INTEN);
-
- return 0;
-}
-
-/* This can run in mpam_disable(), and the interrupt handler on the same CPU */
-static int mpam_disable_msc_ecr(void *_msc)
-{
- struct mpam_msc *msc = _msc;
-
- __mpam_write_reg(msc, MPAMF_ECR, 0);
-
- return 0;
-}
-
static irqreturn_t __mpam_irq_handler(int irq, struct mpam_msc *msc)
{
u64 reg;
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 02/27] arm_mpam: Restore the error interrupt enable from mpam_cpu_online()
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
2026-10-02 17:39 ` [PATCH 01/27] arm_mpam: Move MPAMF_ECR write helpers to allow reuse James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 03/27] arm_mpam: Set mpam_feat_msmon_mbwu_31counter when there are bandwidth counters James Morse
` (24 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Gavin Shan, James Morse
From: Ben Horgan <ben.horgan@arm.com>
When all the associated CPUs to an MSC are offline, possibly caused by
PSCI_CPU_OFF, the power supply to the MSC can be cut off and all the
registers lose their value, including MPAMF_ECR.INTEN. Afterwards, we need
to explicitly set MPAMF_ECR.INTEN when the first associated CPU becomes
online and the power supply to the MSC is recovered.
Restore MPAMF_ECR.INTEN in mpam_cpu_online() when the first associated CPU
of the MSC becomes online.
Fixes: 49aa621c4dca ("arm_mpam: Register and enable IRQs")
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Tested-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: James Morse <james.morse@arm.com>
[morse: rephrased commit message]
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 580d7ed38569..32bde94dfbc3 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1856,8 +1856,14 @@ static int mpam_cpu_online(unsigned int cpu)
if (msc->reenable_error_ppi)
_enable_percpu_irq(&msc->reenable_error_ppi);
- if (atomic_fetch_inc(&msc->online_refs) == 0)
+ if (atomic_fetch_inc(&msc->online_refs) == 0) {
+ mutex_lock(&msc->error_irq_lock);
+ if (msc->error_irq_hw_enabled)
+ mpam_touch_msc(msc, mpam_enable_msc_ecr, msc);
+ mutex_unlock(&msc->error_irq_lock);
+
mpam_reprogram_msc(msc);
+ }
}
if (mpam_resctrl_enabled)
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 03/27] arm_mpam: Set mpam_feat_msmon_mbwu_31counter when there are bandwidth counters
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
2026-10-02 17:39 ` [PATCH 01/27] arm_mpam: Move MPAMF_ECR write helpers to allow reuse James Morse
2026-10-02 17:39 ` [PATCH 02/27] arm_mpam: Restore the error interrupt enable from mpam_cpu_online() James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 04/27] arm_mpam: Add missing mon_sel locking in MBWU save and restore James Morse
` (23 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Gavin Shan, James Morse
From: Ben Horgan <ben.horgan@arm.com>
When MPAMF_MSMON_IDR.MSMON_MBWU is 1, MSMON_MBWU is present and has a VALUE
field of 31 bits. If additionally, MPAMF_MBWUMON_IDR.HAS_LONG is 1, then
MSMON_MBWU_L is also present and has a VALUE field of 44 or 63 bits as
indicated by MPAMF_MBWUMON_IDR.LWD. Hence, if there are memory bandwidth
counters then there are always 31 bit counters irrespective of whether
there also long counters.
Set the 31 bit bandwidth counter feature bit whenever there are bandwidth
counters.
Fixes: fdc29a141d63 ("arm_mpam: Probe for long/lwd mbwu counters")
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Tested-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: James Morse <james.morse@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 32bde94dfbc3..e349db525882 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -930,9 +930,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
mpam_set_feature(mpam_feat_msmon_mbwu_63counter, props);
else
mpam_set_feature(mpam_feat_msmon_mbwu_44counter, props);
- } else {
- mpam_set_feature(mpam_feat_msmon_mbwu_31counter, props);
}
+
+ mpam_set_feature(mpam_feat_msmon_mbwu_31counter, props);
}
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 04/27] arm_mpam: Add missing mon_sel locking in MBWU save and restore
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (2 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 03/27] arm_mpam: Set mpam_feat_msmon_mbwu_31counter when there are bandwidth counters James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 05/27] arm_mpam: Ensure MBWU counters are reset on restore James Morse
` (22 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Gavin Shan, James Morse
From: Ben Horgan <ben.horgan@arm.com>
The mon_sel_lock is used to protect the mbwu_state, as well as h/w accesses
that use MPAMCFG_MON_SEL. However, in mpam_restore/save_mbwu_state(),
mbwu_state is accessed without holding the mon_sel_lock.
Add the missing locking.
Fixes: 41e8a14950e1 ("arm_mpam: Track bandwidth counter state for power management")
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Tested-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: James Morse <james.morse@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 27 ++++++++++++++++++---------
1 file changed, 18 insertions(+), 9 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index e349db525882..d39d210574a6 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1652,17 +1652,26 @@ static int mpam_restore_mbwu_state(void *_ris)
u64 val;
struct mon_read mwbu_arg;
struct mpam_msc_ris *ris = _ris;
+ struct mpam_msc *msc = ris->vmsc->msc;
struct mpam_class *class = ris->vmsc->comp->class;
for (i = 0; i < ris->props.num_mbwu_mon; i++) {
- if (ris->mbwu_state[i].enabled) {
- mwbu_arg.ris = ris;
- mwbu_arg.ctx = &ris->mbwu_state[i].cfg;
- mwbu_arg.type = mpam_msmon_choose_counter(class);
- mwbu_arg.val = &val;
+ if (WARN_ON_ONCE(!mpam_mon_sel_lock(msc)))
+ return -EIO;
- __ris_msmon_read(&mwbu_arg);
+ if (!ris->mbwu_state[i].enabled) {
+ mpam_mon_sel_unlock(msc);
+ continue;
}
+
+ mwbu_arg.ris = ris;
+ mwbu_arg.ctx = &ris->mbwu_state[i].cfg;
+ mwbu_arg.type = mpam_msmon_choose_counter(class);
+ mwbu_arg.val = &val;
+
+ mpam_mon_sel_unlock(msc);
+
+ __ris_msmon_read(&mwbu_arg);
}
return 0;
@@ -1680,12 +1689,12 @@ static int mpam_save_mbwu_state(void *arg)
struct mpam_msc *msc = ris->vmsc->msc;
for (i = 0; i < ris->props.num_mbwu_mon; i++) {
+ if (WARN_ON_ONCE(!mpam_mon_sel_lock(msc)))
+ return -EIO;
+
mbwu_state = &ris->mbwu_state[i];
cfg = &mbwu_state->cfg;
- if (WARN_ON_ONCE(!mpam_mon_sel_lock(msc)))
- return -EIO;
-
mon_sel = FIELD_PREP(MSMON_CFG_MON_SEL_MON_SEL, i) |
FIELD_PREP(MSMON_CFG_MON_SEL_RIS, ris->ris_idx);
mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel);
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 05/27] arm_mpam: Ensure MBWU counters are reset on restore
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (3 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 04/27] arm_mpam: Add missing mon_sel locking in MBWU save and restore James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 06/27] arm_mpam: Use __ris_msmon_read() for saving MBWU state James Morse
` (21 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Gavin Shan, James Morse
From: Ben Horgan <ben.horgan@arm.com>
When an MSC becomes inaccessible due to cpu offline CFG_MBWU_CTL is set to
zero in mpam_save_mbwu_state(). This is very likely to mean that the config
will mismatch when restoring and so the monitor will be reset. However, the
state may have been lost and so there are no guarantees. Ensure the reset
happens by setting the reset_on_next_read and remove the unnecessary writes
from mpam_save_mbwu_state().
Fixes: 41e8a14950e1 ("arm_mpam: Track bandwidth counter state for power management")
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Tested-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: James Morse <james.morse@arm.com>
[morse: Restored clearing of CTL register for kexec/powerdown]
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index d39d210574a6..6df48bec82e2 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1652,6 +1652,7 @@ static int mpam_restore_mbwu_state(void *_ris)
u64 val;
struct mon_read mwbu_arg;
struct mpam_msc_ris *ris = _ris;
+ struct msmon_mbwu_state *mbwu_state;
struct mpam_msc *msc = ris->vmsc->msc;
struct mpam_class *class = ris->vmsc->comp->class;
@@ -1659,16 +1660,20 @@ static int mpam_restore_mbwu_state(void *_ris)
if (WARN_ON_ONCE(!mpam_mon_sel_lock(msc)))
return -EIO;
- if (!ris->mbwu_state[i].enabled) {
+ mbwu_state = &ris->mbwu_state[i];
+
+ if (!mbwu_state->enabled) {
mpam_mon_sel_unlock(msc);
continue;
}
mwbu_arg.ris = ris;
- mwbu_arg.ctx = &ris->mbwu_state[i].cfg;
+ mwbu_arg.ctx = &mbwu_state->cfg;
mwbu_arg.type = mpam_msmon_choose_counter(class);
mwbu_arg.val = &val;
+ mbwu_state->reset_on_next_read = true;
+
mpam_mon_sel_unlock(msc);
__ris_msmon_read(&mwbu_arg);
@@ -1703,13 +1708,10 @@ static int mpam_save_mbwu_state(void *arg)
cur_ctl = mpam_read_monsel_reg(msc, CFG_MBWU_CTL);
mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0);
- if (mpam_ris_has_mbwu_long_counter(ris)) {
+ if (mpam_ris_has_mbwu_long_counter(ris))
val = mpam_msc_read_mbwu_l(msc);
- mpam_msc_zero_mbwu_l(msc);
- } else {
+ else
val = mpam_read_monsel_reg(msc, MBWU);
- mpam_write_monsel_reg(msc, MBWU, 0);
- }
cfg->mon = i;
cfg->pmg = FIELD_GET(MSMON_CFG_x_FLT_PMG, cur_flt);
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 06/27] arm_mpam: Use __ris_msmon_read() for saving MBWU state
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (4 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 05/27] arm_mpam: Ensure MBWU counters are reset on restore James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 18:52 ` Ben Horgan
2026-10-02 17:39 ` [PATCH 07/27] arm_mpam: Initialize all of struct mon_read in mpam_restore_mbwu_state() James Morse
` (20 subsequent siblings)
26 siblings, 1 reply; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Gavin Shan, James Morse
From: Ben Horgan <ben.horgan@arm.com>
mbwu_save_mbwu_state() reads the MBWU counters and adds that to a saved
correction value. However, the type of counter to read is determined by the
RIS rather than the class and overflow is not taken into account. Fix this
and mitigate against further divergence by using a locked variant of the
same helper used for user monitor reads, __ris_msmon_read(). Using the
locked variant avoids having to drop and retake the mon_sel lock. If the
lock was dropped, an interleaved monitor read which detects overflow would
cause the overflow not to be accounted for in the saved value of
mbwu_state->correction. The correction is no longer updated for disabled
counters but this has no effect as the saved values are not expected to be
useful for disabled counters.
Fixes: 41e8a14950e1 ("arm_mpam: Track bandwidth counter state for power management")
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Tested-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
[morse: Added comment on correction being included in mpam_save_mbwu_state()]
Reviewed-by: James Morse <james.morse@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 68 +++++++++++++++++++++++-----------
1 file changed, 47 insertions(+), 21 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 6df48bec82e2..a3d58a51dc73 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1029,12 +1029,6 @@ struct mon_read {
bool waited_timeout;
};
-static bool mpam_ris_has_mbwu_long_counter(struct mpam_msc_ris *ris)
-{
- return (mpam_has_feature(mpam_feat_msmon_mbwu_63counter, &ris->props) ||
- mpam_has_feature(mpam_feat_msmon_mbwu_44counter, &ris->props));
-}
-
static u64 mpam_msc_read_mbwu_l(struct mpam_msc *msc)
{
int retry = 3;
@@ -1202,13 +1196,12 @@ static u64 mpam_msmon_overflow_val(enum mpam_device_features type,
return overflow_val;
}
-static void __ris_msmon_read(void *arg)
+static void __ris_msmon_read_locked(struct mon_read *m)
{
u64 now;
bool nrdy = false;
bool config_mismatch;
bool overflow = false;
- struct mon_read *m = arg;
struct mon_cfg *ctx = m->ctx;
bool reset_on_next_read = false;
struct mpam_msc_ris *ris = m->ris;
@@ -1216,10 +1209,8 @@ static void __ris_msmon_read(void *arg)
struct mpam_msc *msc = m->ris->vmsc->msc;
u32 mon_sel, ctl_val, flt_val, cur_ctl, cur_flt;
- if (!mpam_mon_sel_lock(msc)) {
- m->err = -EIO;
- return;
- }
+ mpam_mon_sel_lock_held(msc);
+
mon_sel = FIELD_PREP(MSMON_CFG_MON_SEL_MON_SEL, ctx->mon) |
FIELD_PREP(MSMON_CFG_MON_SEL_RIS, ris->ris_idx);
mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel);
@@ -1312,7 +1303,6 @@ static void __ris_msmon_read(void *arg)
default:
m->err = -EINVAL;
}
- mpam_mon_sel_unlock(msc);
if (nrdy)
m->err = -EBUSY;
@@ -1323,6 +1313,21 @@ static void __ris_msmon_read(void *arg)
*m->val += now;
}
+static void __ris_msmon_read(void *arg)
+{
+ struct mon_read *m = arg;
+ struct mpam_msc *msc = m->ris->vmsc->msc;
+
+ if (!mpam_mon_sel_lock(msc)) {
+ m->err = -EIO;
+ return;
+ }
+
+ __ris_msmon_read_locked(m);
+
+ mpam_mon_sel_unlock(msc);
+}
+
static int _msmon_read(struct mpam_component *comp, struct mon_read *arg)
{
int err, any_err = 0;
@@ -1674,9 +1679,9 @@ static int mpam_restore_mbwu_state(void *_ris)
mbwu_state->reset_on_next_read = true;
- mpam_mon_sel_unlock(msc);
+ __ris_msmon_read_locked(&mwbu_arg);
- __ris_msmon_read(&mwbu_arg);
+ mpam_mon_sel_unlock(msc);
}
return 0;
@@ -1688,10 +1693,12 @@ static int mpam_save_mbwu_state(void *arg)
int i;
u64 val;
struct mon_cfg *cfg;
+ struct mon_read mbwu_arg;
u32 cur_flt, cur_ctl, mon_sel;
struct mpam_msc_ris *ris = arg;
struct msmon_mbwu_state *mbwu_state;
struct mpam_msc *msc = ris->vmsc->msc;
+ struct mpam_class *class = ris->vmsc->comp->class;
for (i = 0; i < ris->props.num_mbwu_mon; i++) {
if (WARN_ON_ONCE(!mpam_mon_sel_lock(msc)))
@@ -1708,17 +1715,36 @@ static int mpam_save_mbwu_state(void *arg)
cur_ctl = mpam_read_monsel_reg(msc, CFG_MBWU_CTL);
mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0);
- if (mpam_ris_has_mbwu_long_counter(ris))
- val = mpam_msc_read_mbwu_l(msc);
- else
- val = mpam_read_monsel_reg(msc, MBWU);
-
cfg->mon = i;
cfg->pmg = FIELD_GET(MSMON_CFG_x_FLT_PMG, cur_flt);
cfg->match_pmg = FIELD_GET(MSMON_CFG_x_CTL_MATCH_PMG, cur_ctl);
cfg->partid = FIELD_GET(MSMON_CFG_x_FLT_PARTID, cur_flt);
- mbwu_state->correction += val;
mbwu_state->enabled = FIELD_GET(MSMON_CFG_x_CTL_EN, cur_ctl);
+
+ if (!mbwu_state->enabled) {
+ mpam_mon_sel_unlock(msc);
+ continue;
+ }
+
+ val = 0;
+ mbwu_arg = (struct mon_read) {
+ .ris = ris,
+ .ctx = cfg,
+ .type = mpam_msmon_choose_counter(class),
+ .val = &val,
+ };
+
+ __ris_msmon_read_locked(&mbwu_arg);
+
+ mbwu_state->reset_on_next_read = true;
+ if (!mbwu_arg.err) {
+ /*
+ * __ris_msmon_read_locked() already included the
+ * previous correction value.
+ */
+ mbwu_state->correction = val;
+ }
+
mpam_mon_sel_unlock(msc);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* Re: [PATCH 06/27] arm_mpam: Use __ris_msmon_read() for saving MBWU state
2026-10-02 17:39 ` [PATCH 06/27] arm_mpam: Use __ris_msmon_read() for saving MBWU state James Morse
@ 2026-10-02 18:52 ` Ben Horgan
2026-10-02 18:54 ` Catalin Marinas
0 siblings, 1 reply; 30+ messages in thread
From: Ben Horgan @ 2026-10-02 18:52 UTC (permalink / raw)
To: James Morse, linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Andre Przywara, Fuad Tabba,
Gavin Shan
Hi James,
On 02/10/2026 18:39, James Morse wrote:
> From: Ben Horgan <ben.horgan@arm.com>
>
> mbwu_save_mbwu_state() reads the MBWU counters and adds that to a saved
> correction value. However, the type of counter to read is determined by the
> RIS rather than the class and overflow is not taken into account. Fix this
> and mitigate against further divergence by using a locked variant of the
> same helper used for user monitor reads, __ris_msmon_read(). Using the
> locked variant avoids having to drop and retake the mon_sel lock. If the
> lock was dropped, an interleaved monitor read which detects overflow would
> cause the overflow not to be accounted for in the saved value of
> mbwu_state->correction. The correction is no longer updated for disabled
> counters but this has no effect as the saved values are not expected to be
> useful for disabled counters.
>
> Fixes: 41e8a14950e1 ("arm_mpam: Track bandwidth counter state for power management")
> Signed-off-by: Ben Horgan <ben.horgan@arm.com>
> Tested-by: Gavin Shan <gshan@redhat.com>
> Reviewed-by: Gavin Shan <gshan@redhat.com>
> [morse: Added comment on correction being included in mpam_save_mbwu_state()]
> Reviewed-by: James Morse <james.morse@arm.com>
> Signed-off-by: James Morse <james.morse@arm.com>
> ---
> drivers/resctrl/mpam_devices.c | 68 +++++++++++++++++++++++-----------
> 1 file changed, 47 insertions(+), 21 deletions(-)
>
> diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
> index 6df48bec82e2..a3d58a51dc73 100644
> --- a/drivers/resctrl/mpam_devices.c
> +++ b/drivers/resctrl/mpam_devices.c
> @@ -1029,12 +1029,6 @@ struct mon_read {
> bool waited_timeout;
> };
>
> -static bool mpam_ris_has_mbwu_long_counter(struct mpam_msc_ris *ris)
> -{
> - return (mpam_has_feature(mpam_feat_msmon_mbwu_63counter, &ris->props) ||
> - mpam_has_feature(mpam_feat_msmon_mbwu_44counter, &ris->props));
> -}
> -
> static u64 mpam_msc_read_mbwu_l(struct mpam_msc *msc)
> {
> int retry = 3;
> @@ -1202,13 +1196,12 @@ static u64 mpam_msmon_overflow_val(enum mpam_device_features type,
> return overflow_val;
> }
>
> -static void __ris_msmon_read(void *arg)
> +static void __ris_msmon_read_locked(struct mon_read *m)
> {
> u64 now;
> bool nrdy = false;
> bool config_mismatch;
> bool overflow = false;
> - struct mon_read *m = arg;
> struct mon_cfg *ctx = m->ctx;
> bool reset_on_next_read = false;
> struct mpam_msc_ris *ris = m->ris;
> @@ -1216,10 +1209,8 @@ static void __ris_msmon_read(void *arg)
> struct mpam_msc *msc = m->ris->vmsc->msc;
> u32 mon_sel, ctl_val, flt_val, cur_ctl, cur_flt;
>
> - if (!mpam_mon_sel_lock(msc)) {
> - m->err = -EIO;
> - return;
> - }
> + mpam_mon_sel_lock_held(msc);
> +
> mon_sel = FIELD_PREP(MSMON_CFG_MON_SEL_MON_SEL, ctx->mon) |
> FIELD_PREP(MSMON_CFG_MON_SEL_RIS, ris->ris_idx);
> mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel);
> @@ -1312,7 +1303,6 @@ static void __ris_msmon_read(void *arg)
> default:
> m->err = -EINVAL;
> }
> - mpam_mon_sel_unlock(msc);
>
> if (nrdy)
> m->err = -EBUSY;
> @@ -1323,6 +1313,21 @@ static void __ris_msmon_read(void *arg)
> *m->val += now;
> }
>
> +static void __ris_msmon_read(void *arg)
> +{
> + struct mon_read *m = arg;
> + struct mpam_msc *msc = m->ris->vmsc->msc;
> +
> + if (!mpam_mon_sel_lock(msc)) {
> + m->err = -EIO;
> + return;
> + }
> +
> + __ris_msmon_read_locked(m);
> +
> + mpam_mon_sel_unlock(msc);
> +}
> +
> static int _msmon_read(struct mpam_component *comp, struct mon_read *arg)
> {
> int err, any_err = 0;
> @@ -1674,9 +1679,9 @@ static int mpam_restore_mbwu_state(void *_ris)
>
> mbwu_state->reset_on_next_read = true;
>
> - mpam_mon_sel_unlock(msc);
> + __ris_msmon_read_locked(&mwbu_arg);
>
> - __ris_msmon_read(&mwbu_arg);
> + mpam_mon_sel_unlock(msc);
> }
>
> return 0;
> @@ -1688,10 +1693,12 @@ static int mpam_save_mbwu_state(void *arg)
> int i;
> u64 val;
> struct mon_cfg *cfg;
> + struct mon_read mbwu_arg;
> u32 cur_flt, cur_ctl, mon_sel;
> struct mpam_msc_ris *ris = arg;
> struct msmon_mbwu_state *mbwu_state;
> struct mpam_msc *msc = ris->vmsc->msc;
> + struct mpam_class *class = ris->vmsc->comp->class;
>
> for (i = 0; i < ris->props.num_mbwu_mon; i++) {
> if (WARN_ON_ONCE(!mpam_mon_sel_lock(msc)))
> @@ -1708,17 +1715,36 @@ static int mpam_save_mbwu_state(void *arg)
> cur_ctl = mpam_read_monsel_reg(msc, CFG_MBWU_CTL);
> mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0);
__ris_msmon_read_locked() checks the _CTL value and reconfigures on
mismatch and counters to be lost. This needs to move after the
__ris_msmon_read_locked().
>
> - if (mpam_ris_has_mbwu_long_counter(ris))
> - val = mpam_msc_read_mbwu_l(msc);
> - else
> - val = mpam_read_monsel_reg(msc, MBWU);
> -
> cfg->mon = i;
> cfg->pmg = FIELD_GET(MSMON_CFG_x_FLT_PMG, cur_flt);
> cfg->match_pmg = FIELD_GET(MSMON_CFG_x_CTL_MATCH_PMG, cur_ctl);
> cfg->partid = FIELD_GET(MSMON_CFG_x_FLT_PARTID, cur_flt);
> - mbwu_state->correction += val;
> mbwu_state->enabled = FIELD_GET(MSMON_CFG_x_CTL_EN, cur_ctl);
> +
> + if (!mbwu_state->enabled) {
> + mpam_mon_sel_unlock(msc);
> + continue;
> + }
> +
> + val = 0;
> + mbwu_arg = (struct mon_read) {
> + .ris = ris,
> + .ctx = cfg,
> + .type = mpam_msmon_choose_counter(class),
> + .val = &val,
> + };
> +
> + __ris_msmon_read_locked(&mbwu_arg);
> +
> + mbwu_state->reset_on_next_read = true;
> + if (!mbwu_arg.err) {
> + /*
> + * __ris_msmon_read_locked() already included the
> + * previous correction value.
> + */
> + mbwu_state->correction = val;
> + }
I'd put it here.
Thanks,
Ben
> +
> mpam_mon_sel_unlock(msc);
> }
>
^ permalink raw reply [flat|nested] 30+ messages in thread* Re: [PATCH 06/27] arm_mpam: Use __ris_msmon_read() for saving MBWU state
2026-10-02 18:52 ` Ben Horgan
@ 2026-10-02 18:54 ` Catalin Marinas
0 siblings, 0 replies; 30+ messages in thread
From: Catalin Marinas @ 2026-10-02 18:54 UTC (permalink / raw)
To: Ben Horgan
Cc: James Morse, linux-arm-kernel, Will Deacon, Andre Przywara,
Fuad Tabba, Gavin Shan
On Fri, Oct 02, 2026 at 07:52:31PM +0100, Ben Horgan wrote:
> > @@ -1708,17 +1715,36 @@ static int mpam_save_mbwu_state(void *arg)
> > cur_ctl = mpam_read_monsel_reg(msc, CFG_MBWU_CTL);
> > mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0);
>
> __ris_msmon_read_locked() checks the _CTL value and reconfigures on
> mismatch and counters to be lost. This needs to move after the
> __ris_msmon_read_locked().
[...]
> I'd put it here.
Queued already on the for-next/mpam arm64 branch. Please send fixes on
top.
Thanks.
--
Catalin
^ permalink raw reply [flat|nested] 30+ messages in thread
* [PATCH 07/27] arm_mpam: Initialize all of struct mon_read in mpam_restore_mbwu_state()
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (5 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 06/27] arm_mpam: Use __ris_msmon_read() for saving MBWU state James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 08/27] arm_mpam: resctrl: Correct check that existing class is L3 James Morse
` (19 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Gavin Shan, James Morse
From: Ben Horgan <ben.horgan@arm.com>
m->err and *m->val may be read before initialization in __ris_msmon_read()
when called from mpam_restore_mbwu_state().
Initialize the whole struct mon_read in mpam_restore_mbwu_state() and fix
the spelling of mbwu in the name.
Fixes: 41e8a14950e1 ("arm_mpam: Track bandwidth counter state for power management")
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Tested-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: James Morse <james.morse@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index a3d58a51dc73..2369f814de48 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1655,7 +1655,7 @@ static int mpam_restore_mbwu_state(void *_ris)
{
int i;
u64 val;
- struct mon_read mwbu_arg;
+ struct mon_read mbwu_arg;
struct mpam_msc_ris *ris = _ris;
struct msmon_mbwu_state *mbwu_state;
struct mpam_msc *msc = ris->vmsc->msc;
@@ -1672,14 +1672,17 @@ static int mpam_restore_mbwu_state(void *_ris)
continue;
}
- mwbu_arg.ris = ris;
- mwbu_arg.ctx = &mbwu_state->cfg;
- mwbu_arg.type = mpam_msmon_choose_counter(class);
- mwbu_arg.val = &val;
+ val = 0;
+ mbwu_arg = (struct mon_read) {
+ .ris = ris,
+ .ctx = &mbwu_state->cfg,
+ .type = mpam_msmon_choose_counter(class),
+ .val = &val,
+ };
mbwu_state->reset_on_next_read = true;
- __ris_msmon_read_locked(&mwbu_arg);
+ __ris_msmon_read_locked(&mbwu_arg);
mpam_mon_sel_unlock(msc);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 08/27] arm_mpam: resctrl: Correct check that existing class is L3
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (6 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 07/27] arm_mpam: Initialize all of struct mon_read in mpam_restore_mbwu_state() James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 09/27] arm_mpam: resctrl: Make read_mon_cdp_safe() self consistent James Morse
` (18 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Gavin Shan, James Morse
From: Ben Horgan <ben.horgan@arm.com>
The class used to back mbm_total_bytes can be either at the L3 or the
memory. If a platform had candidate classes at both the memory and the L3
then, as the check for whether the existing class is L3 or not in
counter_update_class() is broken, the class that will be chosen depends on
which order the classes are considered and so the probe order.
Fix the check.
Fixes: 1458c4f05335 ("arm_mpam: resctrl: Add support for csu counters")
Tested-by: Gavin Shan <gshan@redhat.com>
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: James Morse <james.morse@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_resctrl.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index 9d223057953a..b825bd90d4bd 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -1076,7 +1076,7 @@ static void counter_update_class(enum resctrl_event_id evt_id,
struct mpam_class *existing_class = mpam_resctrl_counters[evt_id].class;
if (existing_class) {
- if (class->level == 3) {
+ if (existing_class->level == 3) {
pr_debug("Existing class is L3 - L3 wins\n");
return;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 09/27] arm_mpam: resctrl: Make read_mon_cdp_safe() self consistent
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (7 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 08/27] arm_mpam: resctrl: Correct check that existing class is L3 James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 10/27] arm_mpam: Don't loop forever if there is the maximum possible amount of PARTIDs James Morse
` (17 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Gavin Shan, James Morse
From: Ben Horgan <ben.horgan@arm.com>
When read_mon_cdp_safe() is called with cdp_enabled equal to false then the
reading returned in val is the sum of the monitor values for the given
component. However, when cdp_enabled equal to true it is again the sum of
the monitor values but with the initial value of val also added. Both
resctrl_arch_cntr_read() and resctrl_arch_rmid_read() call
read_mon_cdp_safe() and are documented to return the val directly without
consideration of the older value.
The val is always zeroed before read_mon_cdp_safe() is called except in
__check_limbo() and __l3_mon_event_count_sum() but the latter is not
relevant on MPAM systems. The extra accumulation in __check_limbo() means
when there are multiple RMIDs in limbo some of them may be marked dirty
when they are already clean.
Change the cdp_enabled equals true case to match the false case.
Fixes: fb56b29932ca ("arm_mpam: resctrl: Add resctrl_arch_rmid_read()")
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Tested-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: James Morse <james.morse@arm.com>
[morse: fixed commit message indenting and removed stale description]
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_resctrl.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index b825bd90d4bd..321741a702fe 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -497,7 +497,7 @@ static int read_mon_cdp_safe(struct mpam_resctrl_mon *mon, struct mpam_component
if (err)
return err;
- *val += code_val + data_val;
+ *val = code_val + data_val;
return 0;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 10/27] arm_mpam: Don't loop forever if there is the maximum possible amount of PARTIDs
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (8 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 09/27] arm_mpam: resctrl: Make read_mon_cdp_safe() self consistent James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 11/27] arm_mpam: Switch to kvzmalloc_objs() for allocation of component cfg James Morse
` (16 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Gavin Shan, James Morse
From: Ben Horgan <ben.horgan@arm.com>
On a theoretical platform with the maximum possible number of usable
PARTIDs, 0XFFFF, the loops iterating over PARTID in mpam_reset_ris() and
mpam_reprogram_ris() will never reach their termination condition leading
to a system hang when register the cpu hotplug handlers when MPAM is
enabling.
Fix these loops.
Fixes: f188a36ca241 ("arm_mpam: Reset MSC controls from cpuhp callbacks")
Fixes: 09b89d2a72f3 ("arm_mpam: Allow configuration to be applied and restored during cpu online")
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Tested-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: James Morse <james.morse@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 2369f814de48..67545df984fa 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1760,7 +1760,7 @@ static int mpam_save_mbwu_state(void *arg)
*/
static int mpam_reset_ris(void *arg)
{
- u16 partid, partid_max;
+ u16 partid_max;
struct mpam_config reset_cfg = {};
struct mpam_msc_ris *ris = arg;
@@ -1770,7 +1770,7 @@ static int mpam_reset_ris(void *arg)
spin_lock(&partid_max_lock);
partid_max = mpam_partid_max;
spin_unlock(&partid_max_lock);
- for (partid = 0; partid <= partid_max; partid++)
+ for (u32 partid = 0; partid <= partid_max; partid++)
mpam_reprogram_ris_partid(ris, partid, &reset_cfg);
return 0;
@@ -1817,7 +1817,6 @@ static int __write_config(void *arg)
static void mpam_reprogram_msc(struct mpam_msc *msc)
{
- u16 partid;
bool reset;
struct mpam_config *cfg;
struct mpam_msc_ris *ris;
@@ -1841,7 +1840,7 @@ static void mpam_reprogram_msc(struct mpam_msc *msc)
arg.comp = ris->vmsc->comp;
arg.ris = ris;
reset = true;
- for (partid = 0; partid <= mpam_partid_max; partid++) {
+ for (u32 partid = 0; partid <= mpam_partid_max; partid++) {
cfg = &ris->vmsc->comp->cfg[partid];
if (!bitmap_empty(cfg->features, MPAM_FEATURE_LAST))
reset = false;
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 11/27] arm_mpam: Switch to kvzmalloc_objs() for allocation of component cfg
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (9 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 10/27] arm_mpam: Don't loop forever if there is the maximum possible amount of PARTIDs James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 12/27] arm_mpam: resctrl: Don't stop early when tearing down a class James Morse
` (15 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Gavin Shan, James Morse
From: Ben Horgan <ben.horgan@arm.com>
Each component cfg is sized by the size of a per-PARTID multiplied by the
number of PARTIDs. On a platform sized to push the limits of the
specification, the allocation using kzalloc_objs() will consistently
fail. Change to use kvzmalloc_objs() so that large allocations fall back to
vmalloc() based allocations.
Fixes: 09b89d2a72f3 ("arm_mpam: Allow configuration to be applied and restored during cpu online")
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Tested-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: James Morse <james.morse@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 67545df984fa..527867ab5f1b 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -157,7 +157,7 @@ static void mpam_free_garbage(void)
if (iter->pdev)
devm_kfree(&iter->pdev->dev, iter->to_free);
else
- kfree(iter->to_free);
+ kvfree(iter->to_free);
}
}
@@ -2698,7 +2698,7 @@ static int __allocate_component_cfg(struct mpam_component *comp)
if (comp->cfg)
return 0;
- comp->cfg = kzalloc_objs(*comp->cfg, mpam_partid_max + 1);
+ comp->cfg = kvzalloc_objs(*comp->cfg, mpam_partid_max + 1);
if (!comp->cfg)
return -ENOMEM;
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 12/27] arm_mpam: resctrl: Don't stop early when tearing down a class
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (10 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 11/27] arm_mpam: Switch to kvzmalloc_objs() for allocation of component cfg James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 13/27] MAINTAINERS: Add mpam.rst to the MPAM DRIVER entry James Morse
` (14 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Sashiko, Gavin Shan, James Morse
From: Ben Horgan <ben.horgan@arm.com>
An MPAM class can back more than one control and more than one
monitor. mpam_resctrl_teardown_class() iterates through all the controls
but stops if it finds a match which means classes backing more than one
class aren't torn down. The same for monitors, in which case the mbwu
counters will be leaked.
Stop terminating the loops early.
As we are already changing mpam_resctrl_teardown_class() remove the
misleading comment. It doesn't call resctrl_exit() and isn't required to do
so as the only caller does it first.
Fixes: fb481ec08699 ("arm_mpam: resctrl: Call resctrl_init() on platforms that can support resctrl")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: James Morse <james.morse@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_resctrl.c | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c
index 321741a702fe..9cec5843e8fa 100644
--- a/drivers/resctrl/mpam_resctrl.c
+++ b/drivers/resctrl/mpam_resctrl.c
@@ -1943,10 +1943,6 @@ static void mpam_resctrl_teardown_mon(struct mpam_resctrl_mon *mon, struct mpam_
mon->mbwu_idx_to_mon = NULL;
}
-/*
- * The driver is detaching an MSC from this class, if resctrl was using it,
- * pull on resctrl_exit().
- */
void mpam_resctrl_teardown_class(struct mpam_class *class)
{
struct mpam_resctrl_res *res;
@@ -1957,17 +1953,14 @@ void mpam_resctrl_teardown_class(struct mpam_class *class)
might_sleep();
for_each_mpam_resctrl_control(res, rid) {
- if (res->class == class) {
+ if (res->class == class)
res->class = NULL;
- break;
- }
}
for_each_mpam_resctrl_mon(mon, eventid) {
if (mon->class == class) {
mon->class = NULL;
mpam_resctrl_teardown_mon(mon, class);
- break;
}
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 13/27] MAINTAINERS: Add mpam.rst to the MPAM DRIVER entry
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (11 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 12/27] arm_mpam: resctrl: Don't stop early when tearing down a class James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 15/27] arm_mpam: let low level MSC accessors return an error James Morse
` (13 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, James Morse
From: Fuad Tabba <fuad.tabba@linux.dev>
Documentation/arch/arm64/mpam.rst is not listed under MPAM DRIVER, so
get_maintainer.pl names neither MPAM maintainer on a patch to it.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
Signed-off-by: James Morse <james.morse@arm.com>
---
MAINTAINERS | 1 +
1 file changed, 1 insertion(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index cc3cae2e378b..c107b266a150 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18440,6 +18440,7 @@ M: Ben Horgan <ben.horgan@arm.com>
R: Reinette Chatre <reinette.chatre@intel.com>
R: Fenghua Yu <fenghuay@nvidia.com>
S: Maintained
+F: Documentation/arch/arm64/mpam.rst
F: drivers/resctrl/mpam_*
F: drivers/resctrl/test_mpam_*
F: include/linux/arm_mpam.h
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 15/27] arm_mpam: let low level MSC accessors return an error
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (12 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 13/27] MAINTAINERS: Add mpam.rst to the MPAM DRIVER entry James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 16/27] arm_mpam: propagate MSC access errors for hw_probe functions James Morse
` (12 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Jonathan Cameron, Gavin Shan, James Morse
From: Andre Przywara <andre.przywara@arm.com>
The upcoming MPAM-Fb support does not use MMIO primitives to access an
MSC, but employs a shared-memory/doorbell based firmware protocol.
Its complexity means that it must be able to handle errors, whereas we
always assume an MMIO based MSC access succeeds today.
Change the __mpam_read_reg() low level accessor function to return the
requested data through a pointer, and return an error code instead.
Also change the __mpam_write_reg() accessor function to return an error
code. At the moment this is always 0, so all error handling paths are
not exercised at the moment. This will change later when alternative
MSC access methods like MPAM-Fb are activated.
Change all direct users of those MSC wrappers to comply with the new
prototypes, though the errors are not propagated all the way up yet.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Tested-by: Gavin Shan <gshan@redhat.com> # on NVIDIA Grace Hopper
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 203 +++++++++++++++++++++------------
1 file changed, 130 insertions(+), 73 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 527867ab5f1b..77502a87f0f4 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -177,59 +177,68 @@ static void mpam_assert_partid_sizes_fixed(void)
WARN_ON_ONCE(!partid_max_published);
}
-static u32 __mpam_read_reg(struct mpam_msc *msc, u16 reg)
+static int __mpam_read_reg(struct mpam_msc *msc, u16 reg, u32 *res)
{
WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility));
- return readl_relaxed(msc->mapped_hwpage + reg);
+ *res = readl_relaxed(msc->mapped_hwpage + reg);
+
+ return 0;
}
-static inline u32 _mpam_read_partsel_reg(struct mpam_msc *msc, u16 reg)
+static inline int _mpam_read_partsel_reg(struct mpam_msc *msc, u16 reg,
+ u32 *res)
{
lockdep_assert_held_once(&msc->part_sel_lock);
- return __mpam_read_reg(msc, reg);
+ return __mpam_read_reg(msc, reg, res);
}
-#define mpam_read_partsel_reg(msc, reg) _mpam_read_partsel_reg(msc, MPAMF_##reg)
+#define mpam_read_partsel_reg(msc, reg, res) _mpam_read_partsel_reg(msc, MPAMF_##reg, res)
-static void __mpam_write_reg(struct mpam_msc *msc, u16 reg, u32 val)
+static int __mpam_write_reg(struct mpam_msc *msc, u16 reg, u32 val)
{
WARN_ON_ONCE(reg + sizeof(u32) > msc->mapped_hwpage_sz);
WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility));
writel_relaxed(val, msc->mapped_hwpage + reg);
+
+ return 0;
}
-static inline void _mpam_write_partsel_reg(struct mpam_msc *msc, u16 reg, u32 val)
+static inline int _mpam_write_partsel_reg(struct mpam_msc *msc, u16 reg, u32 val)
{
lockdep_assert_held_once(&msc->part_sel_lock);
- __mpam_write_reg(msc, reg, val);
+ return __mpam_write_reg(msc, reg, val);
}
#define mpam_write_partsel_reg(msc, reg, val) _mpam_write_partsel_reg(msc, MPAMCFG_##reg, val)
-static inline u32 _mpam_read_monsel_reg(struct mpam_msc *msc, u16 reg)
+static inline int _mpam_read_monsel_reg(struct mpam_msc *msc, u16 reg,
+ u32 *res)
{
mpam_mon_sel_lock_held(msc);
- return __mpam_read_reg(msc, reg);
+ return __mpam_read_reg(msc, reg, res);
}
-#define mpam_read_monsel_reg(msc, reg) _mpam_read_monsel_reg(msc, MSMON_##reg)
+#define mpam_read_monsel_reg(msc, reg, res) _mpam_read_monsel_reg(msc, MSMON_##reg, res)
-static inline void _mpam_write_monsel_reg(struct mpam_msc *msc, u16 reg, u32 val)
+static inline int _mpam_write_monsel_reg(struct mpam_msc *msc, u16 reg, u32 val)
{
mpam_mon_sel_lock_held(msc);
- __mpam_write_reg(msc, reg, val);
+ return __mpam_write_reg(msc, reg, val);
}
#define mpam_write_monsel_reg(msc, reg, val) _mpam_write_monsel_reg(msc, MSMON_##reg, val)
static bool mpam_msc_check_aidr(struct mpam_msc *msc)
{
- u32 aidr = __mpam_read_reg(msc, MPAMF_AIDR);
- u32 major = FIELD_GET(MPAMF_AIDR_ARCH_MAJOR_REV, aidr);
- u32 minor = FIELD_GET(MPAMF_AIDR_ARCH_MINOR_REV, aidr);
+ u32 aidr, major, minor;
+ if (__mpam_read_reg(msc, MPAMF_AIDR, &aidr))
+ return false;
+
+ major = FIELD_GET(MPAMF_AIDR_ARCH_MAJOR_REV, aidr);
+ minor = FIELD_GET(MPAMF_AIDR_ARCH_MINOR_REV, aidr);
/*
* v0.0 and >v2.x aren't supported, but anything else should be backward
* compatible to v0.1 or v1.0.
@@ -242,25 +251,41 @@ static bool mpam_msc_check_aidr(struct mpam_msc *msc)
return true;
}
-static u64 mpam_msc_read_idr(struct mpam_msc *msc)
+static int mpam_msc_read_idr(struct mpam_msc *msc, u64 *res)
{
- u64 idr_high = 0, idr_low;
+ u32 idr_high, idr_low;
+ int ret;
lockdep_assert_held(&msc->part_sel_lock);
- idr_low = mpam_read_partsel_reg(msc, IDR);
- if (FIELD_GET(MPAMF_IDR_EXT, idr_low))
- idr_high = mpam_read_partsel_reg(msc, IDR + 4);
+ ret = mpam_read_partsel_reg(msc, IDR, &idr_low);
+ if (ret)
+ return ret;
- return (idr_high << 32) | idr_low;
+ if (FIELD_GET(MPAMF_IDR_EXT, idr_low)) {
+ ret = mpam_read_partsel_reg(msc, IDR + 4, &idr_high);
+ if (ret)
+ return ret;
+ } else {
+ idr_high = 0;
+ }
+
+ *res = ((u64)idr_high << 32) | idr_low;
+
+ return 0;
}
-static void mpam_msc_clear_esr(struct mpam_msc *msc)
+static int mpam_msc_clear_esr(struct mpam_msc *msc)
{
- u64 esr_low = __mpam_read_reg(msc, MPAMF_ESR);
+ u32 esr_low;
+ int ret;
+
+ ret = __mpam_read_reg(msc, MPAMF_ESR, &esr_low);
+ if (ret)
+ return ret;
if (!esr_low)
- return;
+ return 0;
/*
* Clearing the high/low bits of MPAMF_ESR can not be atomic.
@@ -268,44 +293,59 @@ static void mpam_msc_clear_esr(struct mpam_msc *msc)
* lower half prevent hardware from updating either half of the
* register.
*/
- if (msc->has_extd_esr)
- __mpam_write_reg(msc, MPAMF_ESR + 4, 0);
- __mpam_write_reg(msc, MPAMF_ESR, 0);
+ if (msc->has_extd_esr) {
+ ret = __mpam_write_reg(msc, MPAMF_ESR + 4, 0);
+ if (ret)
+ return ret;
+ }
+
+ return __mpam_write_reg(msc, MPAMF_ESR, 0);
}
-static u64 mpam_msc_read_esr(struct mpam_msc *msc)
+static int mpam_msc_read_esr(struct mpam_msc *msc, u64 *res)
{
- u64 esr_high = 0, esr_low;
+ u32 esr_high, esr_low;
+ int ret;
- esr_low = __mpam_read_reg(msc, MPAMF_ESR);
- if (msc->has_extd_esr)
- esr_high = __mpam_read_reg(msc, MPAMF_ESR + 4);
+ ret = __mpam_read_reg(msc, MPAMF_ESR, &esr_low);
+ if (ret)
+ return ret;
- return (esr_high << 32) | esr_low;
+ if (msc->has_extd_esr) {
+ ret = __mpam_read_reg(msc, MPAMF_ESR + 4, &esr_high);
+ if (ret)
+ return ret;
+ } else {
+ esr_high = 0;
+ }
+
+ *res = ((u64)esr_high << 32) | esr_low;
+
+ return 0;
}
-static void __mpam_part_sel_raw(u32 partsel, struct mpam_msc *msc)
+static int __mpam_part_sel_raw(u32 partsel, struct mpam_msc *msc)
{
lockdep_assert_held(&msc->part_sel_lock);
- mpam_write_partsel_reg(msc, PART_SEL, partsel);
+ return mpam_write_partsel_reg(msc, PART_SEL, partsel);
}
-static void __mpam_part_sel(u8 ris_idx, u16 partid, struct mpam_msc *msc)
+static int __mpam_part_sel(u8 ris_idx, u16 partid, struct mpam_msc *msc)
{
u32 partsel = FIELD_PREP(MPAMCFG_PART_SEL_RIS, ris_idx) |
FIELD_PREP(MPAMCFG_PART_SEL_PARTID_SEL, partid);
- __mpam_part_sel_raw(partsel, msc);
+ return __mpam_part_sel_raw(partsel, msc);
}
-static void __mpam_intpart_sel(u8 ris_idx, u16 intpartid, struct mpam_msc *msc)
+static int __mpam_intpart_sel(u8 ris_idx, u16 intpartid, struct mpam_msc *msc)
{
u32 partsel = FIELD_PREP(MPAMCFG_PART_SEL_RIS, ris_idx) |
FIELD_PREP(MPAMCFG_PART_SEL_PARTID_SEL, intpartid) |
MPAMCFG_PART_SEL_INTERNAL;
- __mpam_part_sel_raw(partsel, msc);
+ return __mpam_part_sel_raw(partsel, msc);
}
int mpam_register_requestor(u16 partid_max, u8 pmg_max)
@@ -774,13 +814,13 @@ static bool mpam_ris_hw_probe_csu_nrdy(struct mpam_msc_ris *ris)
mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val);
_mpam_write_monsel_reg(msc, MSMON_CSU, MSMON___NRDY);
- now = _mpam_read_monsel_reg(msc, MSMON_CSU);
+ _mpam_read_monsel_reg(msc, MSMON_CSU, &now);
can_set = now & MSMON___NRDY;
_mpam_write_monsel_reg(msc, MSMON_CSU, 0);
/* Configuration change to try and coax hardware into setting nrdy */
mpam_write_monsel_reg(msc, CFG_CSU_FLT, 0x1);
- now = _mpam_read_monsel_reg(msc, MSMON_CSU);
+ _mpam_read_monsel_reg(msc, MSMON_CSU, &now);
can_clear = !(now & MSMON___NRDY);
mpam_mon_sel_unlock(msc);
@@ -800,7 +840,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
/* Cache Capacity Partitioning */
if (FIELD_GET(MPAMF_IDR_HAS_CCAP_PART, ris->idr)) {
- u32 ccap_features = mpam_read_partsel_reg(msc, CCAP_IDR);
+ u32 ccap_features;
+
+ mpam_read_partsel_reg(msc, CCAP_IDR, &ccap_features);
props->cmax_wd = FIELD_GET(MPAMF_CCAP_IDR_CMAX_WD, ccap_features);
if (props->cmax_wd &&
@@ -823,7 +865,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
/* Cache Portion partitioning */
if (FIELD_GET(MPAMF_IDR_HAS_CPOR_PART, ris->idr)) {
- u32 cpor_features = mpam_read_partsel_reg(msc, CPOR_IDR);
+ u32 cpor_features;
+
+ mpam_read_partsel_reg(msc, CPOR_IDR, &cpor_features);
props->cpbm_wd = FIELD_GET(MPAMF_CPOR_IDR_CPBM_WD, cpor_features);
if (props->cpbm_wd)
@@ -832,7 +876,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
/* Memory bandwidth partitioning */
if (FIELD_GET(MPAMF_IDR_HAS_MBW_PART, ris->idr)) {
- u32 mbw_features = mpam_read_partsel_reg(msc, MBW_IDR);
+ u32 mbw_features;
+
+ mpam_read_partsel_reg(msc, MBW_IDR, &mbw_features);
/* portion bitmap resolution */
props->mbw_pbm_bits = FIELD_GET(MPAMF_MBW_IDR_BWPBM_WD, mbw_features);
@@ -860,7 +906,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
/* Priority partitioning */
if (FIELD_GET(MPAMF_IDR_HAS_PRI_PART, ris->idr)) {
- u32 pri_features = mpam_read_partsel_reg(msc, PRI_IDR);
+ u32 pri_features;
+
+ mpam_read_partsel_reg(msc, PRI_IDR, &pri_features);
props->intpri_wd = FIELD_GET(MPAMF_PRI_IDR_INTPRI_WD, pri_features);
if (props->intpri_wd && FIELD_GET(MPAMF_PRI_IDR_HAS_INTPRI, pri_features)) {
@@ -879,7 +927,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
/* Performance Monitoring */
if (FIELD_GET(MPAMF_IDR_HAS_MSMON, ris->idr)) {
- u32 msmon_features = mpam_read_partsel_reg(msc, MSMON_IDR);
+ u32 msmon_features;
+
+ mpam_read_partsel_reg(msc, MSMON_IDR, &msmon_features);
/*
* If the firmware max-nrdy-us property is missing, the
@@ -892,7 +942,8 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
if (FIELD_GET(MPAMF_MSMON_IDR_MSMON_CSU, msmon_features)) {
u32 csumonidr;
- csumonidr = mpam_read_partsel_reg(msc, CSUMON_IDR);
+ mpam_read_partsel_reg(msc, CSUMON_IDR, &csumonidr);
+
props->num_csu_mon = FIELD_GET(MPAMF_CSUMON_IDR_NUM_MON, csumonidr);
if (props->num_csu_mon) {
bool hw_managed;
@@ -915,7 +966,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
}
if (FIELD_GET(MPAMF_MSMON_IDR_MSMON_MBWU, msmon_features)) {
bool has_long;
- u32 mbwumon_idr = mpam_read_partsel_reg(msc, MBWUMON_IDR);
+ u32 mbwumon_idr;
+
+ mpam_read_partsel_reg(msc, MBWUMON_IDR, &mbwumon_idr);
props->num_mbwu_mon = FIELD_GET(MPAMF_MBWUMON_IDR_NUM_MON, mbwumon_idr);
if (props->num_mbwu_mon) {
@@ -945,8 +998,11 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
*/
if (FIELD_GET(MPAMF_IDR_HAS_PARTID_NRW, ris->idr) &&
class->type != MPAM_CLASS_UNKNOWN) {
- u32 nrwidr = mpam_read_partsel_reg(msc, PARTID_NRW_IDR);
- u16 partid_max = FIELD_GET(MPAMF_PARTID_NRW_IDR_INTPARTID_MAX, nrwidr);
+ u16 partid_max;
+ u32 nrwidr;
+
+ mpam_read_partsel_reg(msc, PARTID_NRW_IDR, &nrwidr);
+ partid_max = FIELD_GET(MPAMF_PARTID_NRW_IDR_INTPARTID_MAX, nrwidr);
mpam_set_feature(mpam_feat_partid_nrw, props);
msc->partid_max = min(msc->partid_max, partid_max);
@@ -970,8 +1026,9 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc)
/* Grab an IDR value to find out how many RIS there are */
mutex_lock(&msc->part_sel_lock);
- idr = mpam_msc_read_idr(msc);
- msc->iidr = mpam_read_partsel_reg(msc, IIDR);
+ mpam_msc_read_idr(msc, &idr);
+ mpam_read_partsel_reg(msc, IIDR, &msc->iidr);
+
mutex_unlock(&msc->part_sel_lock);
mpam_enable_quirks(msc);
@@ -985,7 +1042,7 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc)
for (ris_idx = 0; ris_idx <= msc->ris_max; ris_idx++) {
mutex_lock(&msc->part_sel_lock);
__mpam_part_sel(ris_idx, 0, msc);
- idr = mpam_msc_read_idr(msc);
+ mpam_msc_read_idr(msc, &idr);
mutex_unlock(&msc->part_sel_lock);
partid_max = FIELD_GET(MPAMF_IDR_PARTID_MAX, idr);
@@ -1033,24 +1090,24 @@ static u64 mpam_msc_read_mbwu_l(struct mpam_msc *msc)
{
int retry = 3;
u32 mbwu_l_low;
- u64 mbwu_l_high1, mbwu_l_high2;
+ u32 mbwu_l_high1, mbwu_l_high2;
mpam_mon_sel_lock_held(msc);
WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz);
WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility));
- mbwu_l_high2 = __mpam_read_reg(msc, MSMON_MBWU_L + 4);
+ __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2);
do {
mbwu_l_high1 = mbwu_l_high2;
- mbwu_l_low = __mpam_read_reg(msc, MSMON_MBWU_L);
- mbwu_l_high2 = __mpam_read_reg(msc, MSMON_MBWU_L + 4);
+ __mpam_read_reg(msc, MSMON_MBWU_L, &mbwu_l_low);
+ __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2);
retry--;
} while (mbwu_l_high1 != mbwu_l_high2 && retry > 0);
if (mbwu_l_high1 == mbwu_l_high2)
- return (mbwu_l_high1 << 32) | mbwu_l_low;
+ return ((u64)mbwu_l_high1 << 32) | mbwu_l_low;
pr_warn("Failed to read a stable value\n");
return MSMON___L_NRDY;
@@ -1114,14 +1171,14 @@ static void read_msmon_ctl_flt_vals(struct mon_read *m, u32 *ctl_val,
switch (m->type) {
case mpam_feat_msmon_csu:
- *ctl_val = mpam_read_monsel_reg(msc, CFG_CSU_CTL);
- *flt_val = mpam_read_monsel_reg(msc, CFG_CSU_FLT);
+ mpam_read_monsel_reg(msc, CFG_CSU_CTL, ctl_val);
+ mpam_read_monsel_reg(msc, CFG_CSU_FLT, flt_val);
break;
case mpam_feat_msmon_mbwu_31counter:
case mpam_feat_msmon_mbwu_44counter:
case mpam_feat_msmon_mbwu_63counter:
- *ctl_val = mpam_read_monsel_reg(msc, CFG_MBWU_CTL);
- *flt_val = mpam_read_monsel_reg(msc, CFG_MBWU_FLT);
+ mpam_read_monsel_reg(msc, CFG_MBWU_CTL, ctl_val);
+ mpam_read_monsel_reg(msc, CFG_MBWU_FLT, flt_val);
break;
default:
pr_warn("Unexpected monitor type %d\n", m->type);
@@ -1199,6 +1256,7 @@ static u64 mpam_msmon_overflow_val(enum mpam_device_features type,
static void __ris_msmon_read_locked(struct mon_read *m)
{
u64 now;
+ u32 now32;
bool nrdy = false;
bool config_mismatch;
bool overflow = false;
@@ -1258,9 +1316,9 @@ static void __ris_msmon_read_locked(struct mon_read *m)
switch (m->type) {
case mpam_feat_msmon_csu:
- now = mpam_read_monsel_reg(msc, CSU);
- nrdy = now & MSMON___NRDY;
- now = FIELD_GET(MSMON___VALUE, now);
+ mpam_read_monsel_reg(msc, CSU, &now32);
+ nrdy = now32 & MSMON___NRDY;
+ now = FIELD_GET(MSMON___VALUE, now32);
if (mpam_has_quirk(IGNORE_CSU_NRDY, msc) && m->waited_timeout)
nrdy = false;
@@ -1278,9 +1336,9 @@ static void __ris_msmon_read_locked(struct mon_read *m)
else
now = FIELD_GET(MSMON___L_VALUE, now);
} else {
- now = mpam_read_monsel_reg(msc, MBWU);
- nrdy = now & MSMON___NRDY;
- now = FIELD_GET(MSMON___VALUE, now);
+ mpam_read_monsel_reg(msc, MBWU, &now32);
+ nrdy = now32 & MSMON___NRDY;
+ now = FIELD_GET(MSMON___VALUE, now32);
}
if (mpam_has_quirk(T241_MBW_COUNTER_SCALE_64, msc))
@@ -1713,9 +1771,8 @@ static int mpam_save_mbwu_state(void *arg)
mon_sel = FIELD_PREP(MSMON_CFG_MON_SEL_MON_SEL, i) |
FIELD_PREP(MSMON_CFG_MON_SEL_RIS, ris->ris_idx);
mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel);
-
- cur_flt = mpam_read_monsel_reg(msc, CFG_MBWU_FLT);
- cur_ctl = mpam_read_monsel_reg(msc, CFG_MBWU_CTL);
+ mpam_read_monsel_reg(msc, CFG_MBWU_FLT, &cur_flt);
+ mpam_read_monsel_reg(msc, CFG_MBWU_CTL, &cur_ctl);
mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0);
cfg->mon = i;
@@ -2518,7 +2575,7 @@ static irqreturn_t __mpam_irq_handler(int irq, struct mpam_msc *msc)
&msc->accessibility)))
return IRQ_NONE;
- reg = mpam_msc_read_esr(msc);
+ mpam_msc_read_esr(msc, ®);
errcode = FIELD_GET(MPAMF_ESR_ERRCODE, reg);
if (!errcode)
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 16/27] arm_mpam: propagate MSC access errors for hw_probe functions
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (13 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 15/27] arm_mpam: let low level MSC accessors return an error James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 17/27] arm_mpam: propagate MSC access errors for MBWU counters James Morse
` (11 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Jonathan Cameron, Gavin Shan, James Morse
From: Andre Przywara <andre.przywara@arm.com>
Allow the functions probing for MSC hardware and features to return an
error, and propagate read and write errors from the lower level up.
This uses some "scoped cleanup" functions like scoped_guard() and
ACQUIRE() to avoid the complexity of error handling when a lock has been
taken. Since the mon_sel_lock is a bit special (even more so in an
upcoming patch), we define a new GUARD type for it.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Tested-by: Gavin Shan <gshan@redhat.com> # on NVIDIA Grace Hopper
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 145 ++++++++++++++++++++++----------
drivers/resctrl/mpam_internal.h | 4 +
2 files changed, 103 insertions(+), 46 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 77502a87f0f4..a5365b56b1e7 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -792,44 +792,62 @@ static void mpam_enable_quirks(struct mpam_msc *msc)
* Try and see what values stick in this bit. If we can write either value,
* its probably not implemented by hardware.
*/
-static bool mpam_ris_hw_probe_csu_nrdy(struct mpam_msc_ris *ris)
+static int mpam_ris_hw_probe_csu_nrdy(struct mpam_msc_ris *ris)
{
u32 now, mon_sel, ctl_val;
bool can_set, can_clear;
struct mpam_msc *msc = ris->vmsc->msc;
+ int ret;
- if (WARN_ON_ONCE(!mpam_mon_sel_lock(msc)))
- return false;
+ ACQUIRE(mon_sel_lock, guard)(msc);
+ ret = ACQUIRE_ERR(mon_sel_lock, &guard);
+ if (ret)
+ return ret;
mon_sel = FIELD_PREP(MSMON_CFG_MON_SEL_MON_SEL, 0) |
FIELD_PREP(MSMON_CFG_MON_SEL_RIS, ris->ris_idx);
- mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel);
+ ret = mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel);
+ if (ret)
+ return ret;
/* Hardware might ignore nrdy if it's not enabled */
ctl_val = MSMON_CFG_CSU_CTL_TYPE_CSU;
ctl_val |= MSMON_CFG_x_CTL_MATCH_PARTID;
ctl_val |= MSMON_CFG_x_CTL_MATCH_PMG;
ctl_val |= MSMON_CFG_x_CTL_EN;
- mpam_write_monsel_reg(msc, CFG_CSU_FLT, 0);
- mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val);
+ ret = mpam_write_monsel_reg(msc, CFG_CSU_FLT, 0);
+ if (ret)
+ return ret;
+ ret = mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val);
+ if (ret)
+ return ret;
- _mpam_write_monsel_reg(msc, MSMON_CSU, MSMON___NRDY);
- _mpam_read_monsel_reg(msc, MSMON_CSU, &now);
+ ret = _mpam_write_monsel_reg(msc, MSMON_CSU, MSMON___NRDY);
+ if (ret)
+ return ret;
+ ret = _mpam_read_monsel_reg(msc, MSMON_CSU, &now);
+ if (ret)
+ return ret;
can_set = now & MSMON___NRDY;
- _mpam_write_monsel_reg(msc, MSMON_CSU, 0);
+ ret = _mpam_write_monsel_reg(msc, MSMON_CSU, 0);
+ if (ret)
+ return ret;
/* Configuration change to try and coax hardware into setting nrdy */
- mpam_write_monsel_reg(msc, CFG_CSU_FLT, 0x1);
- _mpam_read_monsel_reg(msc, MSMON_CSU, &now);
+ ret = mpam_write_monsel_reg(msc, CFG_CSU_FLT, 0x1);
+ if (ret)
+ return ret;
+ ret = _mpam_read_monsel_reg(msc, MSMON_CSU, &now);
+ if (ret)
+ return ret;
can_clear = !(now & MSMON___NRDY);
- mpam_mon_sel_unlock(msc);
return (!can_set || !can_clear);
}
-static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
+static int mpam_ris_hw_probe(struct mpam_msc_ris *ris)
{
- int err;
+ int fw_has_nrdy_err, err;
struct mpam_msc *msc = ris->vmsc->msc;
struct device *dev = &msc->pdev->dev;
struct mpam_props *props = &ris->props;
@@ -842,7 +860,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
if (FIELD_GET(MPAMF_IDR_HAS_CCAP_PART, ris->idr)) {
u32 ccap_features;
- mpam_read_partsel_reg(msc, CCAP_IDR, &ccap_features);
+ err = mpam_read_partsel_reg(msc, CCAP_IDR, &ccap_features);
+ if (err)
+ return err;
props->cmax_wd = FIELD_GET(MPAMF_CCAP_IDR_CMAX_WD, ccap_features);
if (props->cmax_wd &&
@@ -867,7 +887,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
if (FIELD_GET(MPAMF_IDR_HAS_CPOR_PART, ris->idr)) {
u32 cpor_features;
- mpam_read_partsel_reg(msc, CPOR_IDR, &cpor_features);
+ err = mpam_read_partsel_reg(msc, CPOR_IDR, &cpor_features);
+ if (err)
+ return err;
props->cpbm_wd = FIELD_GET(MPAMF_CPOR_IDR_CPBM_WD, cpor_features);
if (props->cpbm_wd)
@@ -877,8 +899,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
/* Memory bandwidth partitioning */
if (FIELD_GET(MPAMF_IDR_HAS_MBW_PART, ris->idr)) {
u32 mbw_features;
-
- mpam_read_partsel_reg(msc, MBW_IDR, &mbw_features);
+ err = mpam_read_partsel_reg(msc, MBW_IDR, &mbw_features);
+ if (err)
+ return err;
/* portion bitmap resolution */
props->mbw_pbm_bits = FIELD_GET(MPAMF_MBW_IDR_BWPBM_WD, mbw_features);
@@ -907,8 +930,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
/* Priority partitioning */
if (FIELD_GET(MPAMF_IDR_HAS_PRI_PART, ris->idr)) {
u32 pri_features;
-
- mpam_read_partsel_reg(msc, PRI_IDR, &pri_features);
+ err = mpam_read_partsel_reg(msc, PRI_IDR, &pri_features);
+ if (err)
+ return err;
props->intpri_wd = FIELD_GET(MPAMF_PRI_IDR_INTPRI_WD, pri_features);
if (props->intpri_wd && FIELD_GET(MPAMF_PRI_IDR_HAS_INTPRI, pri_features)) {
@@ -929,20 +953,24 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
if (FIELD_GET(MPAMF_IDR_HAS_MSMON, ris->idr)) {
u32 msmon_features;
- mpam_read_partsel_reg(msc, MSMON_IDR, &msmon_features);
+ err = mpam_read_partsel_reg(msc, MSMON_IDR, &msmon_features);
+ if (err)
+ return err;
/*
* If the firmware max-nrdy-us property is missing, the
* CSU counters can't be used. Should we wait forever?
*/
- err = device_property_read_u32(&msc->pdev->dev,
- "arm,not-ready-us",
- &msc->nrdy_usec);
+ fw_has_nrdy_err = device_property_read_u32(&msc->pdev->dev,
+ "arm,not-ready-us",
+ &msc->nrdy_usec);
if (FIELD_GET(MPAMF_MSMON_IDR_MSMON_CSU, msmon_features)) {
u32 csumonidr;
- mpam_read_partsel_reg(msc, CSUMON_IDR, &csumonidr);
+ err = mpam_read_partsel_reg(msc, CSUMON_IDR, &csumonidr);
+ if (err)
+ return err;
props->num_csu_mon = FIELD_GET(MPAMF_CSUMON_IDR_NUM_MON, csumonidr);
if (props->num_csu_mon) {
@@ -954,13 +982,16 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
mpam_set_feature(mpam_feat_msmon_csu_xcl, props);
/* Is NRDY hardware managed? */
- hw_managed = mpam_ris_hw_probe_csu_nrdy(ris);
+ err = mpam_ris_hw_probe_csu_nrdy(ris);
+ if (err < 0)
+ return err;
+ hw_managed = err;
/*
* Accept the missing firmware property if NRDY appears
* un-implemented.
*/
- if (err && hw_managed)
+ if (fw_has_nrdy_err && hw_managed)
dev_err_once(dev, "Counters are not usable because not-ready timeout was not provided by firmware.");
}
}
@@ -968,7 +999,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
bool has_long;
u32 mbwumon_idr;
- mpam_read_partsel_reg(msc, MBWUMON_IDR, &mbwumon_idr);
+ err = mpam_read_partsel_reg(msc, MBWUMON_IDR, &mbwumon_idr);
+ if (err)
+ return err;
props->num_mbwu_mon = FIELD_GET(MPAMF_MBWUMON_IDR_NUM_MON, mbwumon_idr);
if (props->num_mbwu_mon) {
@@ -1001,16 +1034,22 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris)
u16 partid_max;
u32 nrwidr;
- mpam_read_partsel_reg(msc, PARTID_NRW_IDR, &nrwidr);
+ err = mpam_read_partsel_reg(msc, PARTID_NRW_IDR, &nrwidr);
+ if (err)
+ return err;
+
partid_max = FIELD_GET(MPAMF_PARTID_NRW_IDR_INTPARTID_MAX, nrwidr);
mpam_set_feature(mpam_feat_partid_nrw, props);
msc->partid_max = min(msc->partid_max, partid_max);
}
+
+ return 0;
}
static int mpam_msc_hw_probe(struct mpam_msc *msc)
{
+ int ret;
u64 idr;
u16 partid_max;
u8 ris_idx, pmg_max;
@@ -1025,11 +1064,15 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc)
}
/* Grab an IDR value to find out how many RIS there are */
- mutex_lock(&msc->part_sel_lock);
- mpam_msc_read_idr(msc, &idr);
- mpam_read_partsel_reg(msc, IIDR, &msc->iidr);
+ scoped_guard(mutex, &msc->part_sel_lock) {
+ ret = mpam_msc_read_idr(msc, &idr);
+ if (ret)
+ return ret;
- mutex_unlock(&msc->part_sel_lock);
+ ret = mpam_read_partsel_reg(msc, IIDR, &msc->iidr);
+ if (ret)
+ return ret;
+ }
mpam_enable_quirks(msc);
@@ -1040,10 +1083,15 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc)
msc->pmg_max = FIELD_GET(MPAMF_IDR_PMG_MAX, idr);
for (ris_idx = 0; ris_idx <= msc->ris_max; ris_idx++) {
- mutex_lock(&msc->part_sel_lock);
- __mpam_part_sel(ris_idx, 0, msc);
- mpam_msc_read_idr(msc, &idr);
- mutex_unlock(&msc->part_sel_lock);
+ scoped_guard(mutex, &msc->part_sel_lock) {
+ ret = __mpam_part_sel(ris_idx, 0, msc);
+ if (ret)
+ return ret;
+
+ ret = mpam_msc_read_idr(msc, &idr);
+ if (ret)
+ return ret;
+ }
partid_max = FIELD_GET(MPAMF_IDR_PARTID_MAX, idr);
pmg_max = FIELD_GET(MPAMF_IDR_PMG_MAX, idr);
@@ -1051,17 +1099,22 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc)
msc->pmg_max = min(msc->pmg_max, pmg_max);
msc->has_extd_esr = FIELD_GET(MPAMF_IDR_HAS_EXTD_ESR, idr);
- mutex_lock(&mpam_list_lock);
- ris = mpam_get_or_create_ris(msc, ris_idx);
- mutex_unlock(&mpam_list_lock);
- if (IS_ERR(ris))
- return PTR_ERR(ris);
+ scoped_guard(mutex, &mpam_list_lock) {
+ ris = mpam_get_or_create_ris(msc, ris_idx);
+ if (IS_ERR(ris))
+ return PTR_ERR(ris);
+ }
ris->idr = idr;
- mutex_lock(&msc->part_sel_lock);
- __mpam_part_sel(ris_idx, 0, msc);
- mpam_ris_hw_probe(ris);
- mutex_unlock(&msc->part_sel_lock);
+ scoped_guard(mutex, &msc->part_sel_lock) {
+ ret = __mpam_part_sel(ris_idx, 0, msc);
+ if (ret)
+ return ret;
+
+ ret = mpam_ris_hw_probe(ris);
+ if (ret)
+ return ret;
+ }
}
/* Clear any stale errors */
diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h
index def0e3a65c23..68a6cf2b9cc7 100644
--- a/drivers/resctrl/mpam_internal.h
+++ b/drivers/resctrl/mpam_internal.h
@@ -162,6 +162,10 @@ static inline void mpam_mon_sel_lock_init(struct mpam_msc *msc)
raw_spin_lock_init(&msc->_mon_sel_lock);
}
+DEFINE_GUARD(mon_sel, struct mpam_msc *,
+ mpam_mon_sel_lock(_T), mpam_mon_sel_unlock(_T));
+DEFINE_GUARD_COND(mon_sel, _lock, mpam_mon_sel_lock(_T), _RET);
+
/* Bits for mpam features bitmaps */
enum mpam_device_features {
mpam_feat_cpor_part,
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 17/27] arm_mpam: propagate MSC access errors for MBWU counters
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (14 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 16/27] arm_mpam: propagate MSC access errors for hw_probe functions James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 18/27] arm_mpam: propagate MSC access errors for msmon helpers James Morse
` (10 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Jonathan Cameron, Gavin Shan, James Morse
From: Andre Przywara <andre.przywara@arm.com>
Allow the mpam_msc_read_mbwu_l() function to return an error, and
propagate errors from the lower level up. This also changes the users
of this function.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Tested-by: Gavin Shan <gshan@redhat.com> # on NVIDIA Grace Hopper
[morse: Fixed merge conflicts in mpam_save_mbwu_state() and __ris_msmon_read()]
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 31 ++++++++++++++++++++++---------
1 file changed, 22 insertions(+), 9 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index a5365b56b1e7..c573fe08ce01 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1139,8 +1139,9 @@ struct mon_read {
bool waited_timeout;
};
-static u64 mpam_msc_read_mbwu_l(struct mpam_msc *msc)
+static int mpam_msc_read_mbwu_l(struct mpam_msc *msc, u64 *res)
{
+ int ret;
int retry = 3;
u32 mbwu_l_low;
u32 mbwu_l_high1, mbwu_l_high2;
@@ -1150,20 +1151,30 @@ static u64 mpam_msc_read_mbwu_l(struct mpam_msc *msc)
WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz);
WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility));
- __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2);
+ ret = __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2);
+ if (ret)
+ return ret;
+
do {
mbwu_l_high1 = mbwu_l_high2;
- __mpam_read_reg(msc, MSMON_MBWU_L, &mbwu_l_low);
- __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2);
+ ret = __mpam_read_reg(msc, MSMON_MBWU_L, &mbwu_l_low);
+ if (ret)
+ return ret;
+ ret = __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2);
+ if (ret)
+ return ret;
retry--;
} while (mbwu_l_high1 != mbwu_l_high2 && retry > 0);
- if (mbwu_l_high1 == mbwu_l_high2)
- return ((u64)mbwu_l_high1 << 32) | mbwu_l_low;
+ if (mbwu_l_high1 == mbwu_l_high2) {
+ *res = ((u64)mbwu_l_high1 << 32) | mbwu_l_low;
+ } else {
+ pr_warn("Failed to read a stable value\n");
+ *res = MSMON___L_NRDY;
+ }
- pr_warn("Failed to read a stable value\n");
- return MSMON___L_NRDY;
+ return 0;
}
static void mpam_msc_zero_mbwu_l(struct mpam_msc *msc)
@@ -1381,7 +1392,9 @@ static void __ris_msmon_read_locked(struct mon_read *m)
case mpam_feat_msmon_mbwu_44counter:
case mpam_feat_msmon_mbwu_63counter:
if (m->type != mpam_feat_msmon_mbwu_31counter) {
- now = mpam_msc_read_mbwu_l(msc);
+ m->err = mpam_msc_read_mbwu_l(msc, &now);
+ if (m->err)
+ return;
nrdy = now & MSMON___L_NRDY;
if (m->type == mpam_feat_msmon_mbwu_63counter)
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 18/27] arm_mpam: propagate MSC access errors for msmon helpers
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (15 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 17/27] arm_mpam: propagate MSC access errors for MBWU counters James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 19/27] arm_mpam: propagate MSC access errors for __ris_msmon_read() James Morse
` (9 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Jonathan Cameron, Gavin Shan, Ritwick Sharma,
James Morse
From: Andre Przywara <andre.przywara@arm.com>
Allow the helper functions for msmon accesses to return an error, and
propagate read and write errors from the lower level up.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Tested-by: Gavin Shan <gshan@redhat.com> # on NVIDIA Grace Hopper
Tested-by: Ritwick Sharma <ritwick.sharma@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 72 ++++++++++++++++++++++------------
1 file changed, 47 insertions(+), 25 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index c573fe08ce01..72a5d1ab9684 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1177,15 +1177,20 @@ static int mpam_msc_read_mbwu_l(struct mpam_msc *msc, u64 *res)
return 0;
}
-static void mpam_msc_zero_mbwu_l(struct mpam_msc *msc)
+static int mpam_msc_zero_mbwu_l(struct mpam_msc *msc)
{
+ int ret;
+
mpam_mon_sel_lock_held(msc);
WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz);
WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility));
- __mpam_write_reg(msc, MSMON_MBWU_L, 0);
- __mpam_write_reg(msc, MSMON_MBWU_L + 4, 0);
+ ret = __mpam_write_reg(msc, MSMON_MBWU_L, 0);
+ if (ret)
+ return ret;
+
+ return __mpam_write_reg(msc, MSMON_MBWU_L + 4, 0);
}
static void gen_msmon_ctl_flt_vals(struct mon_read *m, u32 *ctl_val,
@@ -1228,24 +1233,28 @@ static void gen_msmon_ctl_flt_vals(struct mon_read *m, u32 *ctl_val,
}
}
-static void read_msmon_ctl_flt_vals(struct mon_read *m, u32 *ctl_val,
- u32 *flt_val)
+static int read_msmon_ctl_flt_vals(struct mon_read *m, u32 *ctl_val,
+ u32 *flt_val)
{
struct mpam_msc *msc = m->ris->vmsc->msc;
+ int ret;
switch (m->type) {
case mpam_feat_msmon_csu:
- mpam_read_monsel_reg(msc, CFG_CSU_CTL, ctl_val);
- mpam_read_monsel_reg(msc, CFG_CSU_FLT, flt_val);
- break;
+ ret = mpam_read_monsel_reg(msc, CFG_CSU_CTL, ctl_val);
+ if (ret)
+ return ret;
+ return mpam_read_monsel_reg(msc, CFG_CSU_FLT, flt_val);
case mpam_feat_msmon_mbwu_31counter:
case mpam_feat_msmon_mbwu_44counter:
case mpam_feat_msmon_mbwu_63counter:
- mpam_read_monsel_reg(msc, CFG_MBWU_CTL, ctl_val);
- mpam_read_monsel_reg(msc, CFG_MBWU_FLT, flt_val);
- break;
+ ret = mpam_read_monsel_reg(msc, CFG_MBWU_CTL, ctl_val);
+ if (ret)
+ return ret;
+ return mpam_read_monsel_reg(msc, CFG_MBWU_FLT, flt_val);
default:
pr_warn("Unexpected monitor type %d\n", m->type);
+ return -EINVAL;
}
}
@@ -1258,10 +1267,11 @@ static inline void clean_msmon_ctl_val(u32 *cur_ctl)
*cur_ctl &= ~MSMON_CFG_MBWU_CTL_OFLOW_STATUS_L;
}
-static void write_msmon_ctl_flt_vals(struct mon_read *m, u32 ctl_val,
- u32 flt_val)
+static int write_msmon_ctl_flt_vals(struct mon_read *m, u32 ctl_val,
+ u32 flt_val)
{
struct mpam_msc *msc = m->ris->vmsc->msc;
+ int ret;
/*
* Write the ctl_val with the enable bit cleared, reset the counter,
@@ -1269,25 +1279,37 @@ static void write_msmon_ctl_flt_vals(struct mon_read *m, u32 ctl_val,
*/
switch (m->type) {
case mpam_feat_msmon_csu:
- mpam_write_monsel_reg(msc, CFG_CSU_FLT, flt_val);
- mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val);
- mpam_write_monsel_reg(msc, CSU, 0);
- mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val | MSMON_CFG_x_CTL_EN);
- break;
+ ret = mpam_write_monsel_reg(msc, CFG_CSU_FLT, flt_val);
+ if (ret)
+ return ret;
+ ret = mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val);
+ if (ret)
+ return ret;
+ ret = mpam_write_monsel_reg(msc, CSU, 0);
+ if (ret)
+ return ret;
+ return mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val | MSMON_CFG_x_CTL_EN);
case mpam_feat_msmon_mbwu_31counter:
case mpam_feat_msmon_mbwu_44counter:
case mpam_feat_msmon_mbwu_63counter:
- mpam_write_monsel_reg(msc, CFG_MBWU_FLT, flt_val);
- mpam_write_monsel_reg(msc, CFG_MBWU_CTL, ctl_val);
- mpam_write_monsel_reg(msc, CFG_MBWU_CTL, ctl_val | MSMON_CFG_x_CTL_EN);
+ ret = mpam_write_monsel_reg(msc, CFG_MBWU_FLT, flt_val);
+ if (ret)
+ return ret;
+ ret = mpam_write_monsel_reg(msc, CFG_MBWU_CTL, ctl_val);
+ if (ret)
+ return ret;
+ ret = mpam_write_monsel_reg(msc, CFG_MBWU_CTL,
+ ctl_val | MSMON_CFG_x_CTL_EN);
+ if (ret)
+ return ret;
/* Counting monitors require NRDY to be reset by software */
if (m->type == mpam_feat_msmon_mbwu_31counter)
- mpam_write_monsel_reg(msc, MBWU, 0);
- else
- mpam_msc_zero_mbwu_l(m->ris->vmsc->msc);
- break;
+ return mpam_write_monsel_reg(msc, MBWU, 0);
+
+ return mpam_msc_zero_mbwu_l(m->ris->vmsc->msc);
default:
pr_warn("Unexpected monitor type %d\n", m->type);
+ return -EINVAL;
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 19/27] arm_mpam: propagate MSC access errors for __ris_msmon_read()
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (16 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 18/27] arm_mpam: propagate MSC access errors for msmon helpers James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 20/27] arm_mpam: propagate MSC access errors for state saving function James Morse
` (8 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Jonathan Cameron, Gavin Shan, James Morse
From: Andre Przywara <andre.przywara@arm.com>
Allow the function for RIS accesses to return an error, and propagate
read and write errors from the lower level up.
This function is a bit special, since it can be called via an SMP
helper, so the prototype must stay as void. So propagate any errors
using the "err" field in the struct passed in as the sole argument to
that function.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Tested-by: Gavin Shan <gshan@redhat.com> # on NVIDIA Grace Hopper
[morse: Fixed merge conflicts in mpam_save_mbwu_state() and __ris_msmon_read()]
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 37 +++++++++++++++++++++++++---------
1 file changed, 28 insertions(+), 9 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 72a5d1ab9684..853e5c5d7158 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1339,6 +1339,10 @@ static u64 mpam_msmon_overflow_val(enum mpam_device_features type,
return overflow_val;
}
+/*
+ * This function might be called via smp_call_function_any(), so propagate
+ * errors inside the arg struct.
+ */
static void __ris_msmon_read_locked(struct mon_read *m)
{
u64 now;
@@ -1357,7 +1361,9 @@ static void __ris_msmon_read_locked(struct mon_read *m)
mon_sel = FIELD_PREP(MSMON_CFG_MON_SEL_MON_SEL, ctx->mon) |
FIELD_PREP(MSMON_CFG_MON_SEL_RIS, ris->ris_idx);
- mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel);
+ m->err = mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel);
+ if (m->err)
+ return;
switch (m->type) {
case mpam_feat_msmon_mbwu_31counter:
@@ -1377,7 +1383,9 @@ static void __ris_msmon_read_locked(struct mon_read *m)
* Read the existing configuration to avoid re-writing the same values.
* This saves waiting for 'nrdy' on subsequent reads.
*/
- read_msmon_ctl_flt_vals(m, &cur_ctl, &cur_flt);
+ m->err = read_msmon_ctl_flt_vals(m, &cur_ctl, &cur_flt);
+ if (m->err)
+ return;
if (mpam_feat_msmon_mbwu_31counter == m->type)
overflow = cur_ctl & MSMON_CFG_x_CTL_OFLOW_STATUS;
@@ -1391,18 +1399,24 @@ static void __ris_msmon_read_locked(struct mon_read *m)
cur_ctl != (ctl_val | MSMON_CFG_x_CTL_EN);
if (config_mismatch || reset_on_next_read) {
- write_msmon_ctl_flt_vals(m, ctl_val, flt_val);
+ m->err = write_msmon_ctl_flt_vals(m, ctl_val, flt_val);
+ if (m->err)
+ return;
overflow = false;
} else if (overflow) {
- mpam_write_monsel_reg(msc, CFG_MBWU_CTL,
- cur_ctl &
- ~(MSMON_CFG_x_CTL_OFLOW_STATUS |
- MSMON_CFG_MBWU_CTL_OFLOW_STATUS_L));
+ m->err = mpam_write_monsel_reg(msc, CFG_MBWU_CTL,
+ cur_ctl &
+ ~(MSMON_CFG_x_CTL_OFLOW_STATUS |
+ MSMON_CFG_MBWU_CTL_OFLOW_STATUS_L));
+ if (m->err)
+ return;
}
switch (m->type) {
case mpam_feat_msmon_csu:
- mpam_read_monsel_reg(msc, CSU, &now32);
+ m->err = mpam_read_monsel_reg(msc, CSU, &now32);
+ if (m->err)
+ return;
nrdy = now32 & MSMON___NRDY;
now = FIELD_GET(MSMON___VALUE, now32);
@@ -1424,7 +1438,9 @@ static void __ris_msmon_read_locked(struct mon_read *m)
else
now = FIELD_GET(MSMON___L_VALUE, now);
} else {
- mpam_read_monsel_reg(msc, MBWU, &now32);
+ m->err = mpam_read_monsel_reg(msc, MBWU, &now32);
+ if (m->err)
+ return;
nrdy = now32 & MSMON___NRDY;
now = FIELD_GET(MSMON___VALUE, now32);
}
@@ -1831,6 +1847,9 @@ static int mpam_restore_mbwu_state(void *_ris)
__ris_msmon_read_locked(&mbwu_arg);
mpam_mon_sel_unlock(msc);
+
+ if (mbwu_arg.err && mbwu_arg.err != -EBUSY)
+ return mbwu_arg.err;
}
return 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 20/27] arm_mpam: propagate MSC access errors for state saving function
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (17 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 19/27] arm_mpam: propagate MSC access errors for __ris_msmon_read() James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 21/27] arm_mpam: propagate MSC access errors for mpam_reprogram_ris_partid() James Morse
` (7 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Jonathan Cameron, Gavin Shan, James Morse
From: Andre Przywara <andre.przywara@arm.com>
Allow the mpam_save_mbwu_state() function to return an error, and
propagate read and write errors from the lower level up.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Tested-by: Gavin Shan <gshan@redhat.com> # on NVIDIA Grace Hopper
[morse: Fixed merge conflicts in mpam_save_mbwu_state() and __ris_msmon_read()]
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 853e5c5d7158..39437361ed7a 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1859,6 +1859,7 @@ static int mpam_restore_mbwu_state(void *_ris)
static int mpam_save_mbwu_state(void *arg)
{
int i;
+ int ret;
u64 val;
struct mon_cfg *cfg;
struct mon_read mbwu_arg;
@@ -1877,10 +1878,18 @@ static int mpam_save_mbwu_state(void *arg)
mon_sel = FIELD_PREP(MSMON_CFG_MON_SEL_MON_SEL, i) |
FIELD_PREP(MSMON_CFG_MON_SEL_RIS, ris->ris_idx);
- mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel);
- mpam_read_monsel_reg(msc, CFG_MBWU_FLT, &cur_flt);
- mpam_read_monsel_reg(msc, CFG_MBWU_CTL, &cur_ctl);
- mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0);
+ ret = mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel);
+ if (ret)
+ return ret;
+ ret = mpam_read_monsel_reg(msc, CFG_MBWU_FLT, &cur_flt);
+ if (ret)
+ return ret;
+ ret = mpam_read_monsel_reg(msc, CFG_MBWU_CTL, &cur_ctl);
+ if (ret)
+ return ret;
+ ret = mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0);
+ if (ret)
+ return ret;
cfg->mon = i;
cfg->pmg = FIELD_GET(MSMON_CFG_x_FLT_PMG, cur_flt);
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 21/27] arm_mpam: propagate MSC access errors for mpam_reprogram_ris_partid()
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (18 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 20/27] arm_mpam: propagate MSC access errors for state saving function James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 22/27] arm_mpam: propagate MSC access errors for interrupt control James Morse
` (6 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Jonathan Cameron, Gavin Shan, Srivathsa L Rao,
James Morse
From: Andre Przywara <andre.przywara@arm.com>
Allow the mpam_reprogram_ris_partid() function to check for and return
errors, and propagate MSC read and write errors from the lower level up.
This also covers the callers of this function: mpam_reset_ris() and
apply_config().
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Tested-by: Gavin Shan <gshan@redhat.com> # on NVIDIA Grace Hopper
Reviewed-by: Srivathsa L Rao <srivathsa.rao@oss.qualcomm.com>
[morse: Fixed merge conflicts in mpam_save_mbwu_state() and __ris_msmon_read()]
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 137 ++++++++++++++++++++++-----------
1 file changed, 94 insertions(+), 43 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 39437361ed7a..7e7b483ff900 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1616,7 +1616,7 @@ void mpam_msmon_reset_mbwu(struct mpam_component *comp, struct mon_cfg *ctx)
}
}
-static void mpam_reset_msc_bitmap(struct mpam_msc *msc, u16 reg, u16 wd)
+static int mpam_reset_msc_bitmap(struct mpam_msc *msc, u16 reg, u16 wd)
{
u32 num_words, msb;
u32 bm = ~0;
@@ -1625,15 +1625,20 @@ static void mpam_reset_msc_bitmap(struct mpam_msc *msc, u16 reg, u16 wd)
lockdep_assert_held(&msc->part_sel_lock);
if (wd == 0)
- return;
+ return 0;
/*
* Write all ~0 to all but the last 32bit-word, which may
* have fewer bits...
*/
num_words = DIV_ROUND_UP(wd, 32);
- for (i = 0; i < num_words - 1; i++, reg += sizeof(bm))
- __mpam_write_reg(msc, reg, bm);
+ for (i = 0; i < num_words - 1; i++, reg += sizeof(bm)) {
+ int ret;
+
+ ret = __mpam_write_reg(msc, reg, bm);
+ if (ret)
+ return ret;
+ }
/*
* ....and then the last (maybe) partial 32bit word. When wd is a
@@ -1641,7 +1646,7 @@ static void mpam_reset_msc_bitmap(struct mpam_msc *msc, u16 reg, u16 wd)
*/
msb = (wd - 1) % 32;
bm = GENMASK(msb, 0);
- __mpam_write_reg(msc, reg, bm);
+ return __mpam_write_reg(msc, reg, bm);
}
static void mpam_apply_t241_erratum(struct mpam_msc_ris *ris, u16 partid)
@@ -1714,40 +1719,59 @@ static u16 mpam_wa_t241_calc_min_from_max(struct mpam_props *props,
}
/* Called via IPI. Call while holding an SRCU reference */
-static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid,
- struct mpam_config *cfg)
+static int mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid,
+ struct mpam_config *cfg)
{
u16 cmax = MPAMCFG_CMAX_CMAX;
struct mpam_msc *msc = ris->vmsc->msc;
struct mpam_props *rprops = &ris->props;
+ int ret;
- mutex_lock(&msc->part_sel_lock);
- __mpam_part_sel(ris->ris_idx, partid, msc);
+ guard(mutex)(&msc->part_sel_lock);
+ ret = __mpam_part_sel(ris->ris_idx, partid, msc);
+ if (ret)
+ return ret;
if (mpam_has_feature(mpam_feat_partid_nrw, rprops)) {
/* Update the intpartid mapping */
- mpam_write_partsel_reg(msc, INTPARTID,
- MPAMCFG_INTPARTID_INTERNAL | partid);
+ ret = mpam_write_partsel_reg(msc, INTPARTID,
+ MPAMCFG_INTPARTID_INTERNAL | partid);
+ if (ret)
+ return ret;
/*
* Then switch to the 'internal' partid to update the
* configuration.
*/
- __mpam_intpart_sel(ris->ris_idx, partid, msc);
+ ret = __mpam_intpart_sel(ris->ris_idx, partid, msc);
+ if (ret)
+ return ret;
}
if (mpam_has_feature(mpam_feat_cpor_part, rprops)) {
- if (mpam_has_feature(mpam_feat_cpor_part, cfg))
- mpam_write_partsel_reg(msc, CPBM, cfg->cpbm);
- else
- mpam_reset_msc_bitmap(msc, MPAMCFG_CPBM, rprops->cpbm_wd);
+ if (mpam_has_feature(mpam_feat_cpor_part, cfg)) {
+ ret = mpam_write_partsel_reg(msc, CPBM, cfg->cpbm);
+ if (ret)
+ return ret;
+ } else {
+ ret = mpam_reset_msc_bitmap(msc, MPAMCFG_CPBM,
+ rprops->cpbm_wd);
+ if (ret)
+ return ret;
+ }
}
if (mpam_has_feature(mpam_feat_mbw_part, rprops)) {
- if (mpam_has_feature(mpam_feat_mbw_part, cfg))
- mpam_write_partsel_reg(msc, MBW_PBM, cfg->mbw_pbm);
- else
- mpam_reset_msc_bitmap(msc, MPAMCFG_MBW_PBM, rprops->mbw_pbm_bits);
+ if (mpam_has_feature(mpam_feat_mbw_part, cfg)) {
+ ret = mpam_write_partsel_reg(msc, MBW_PBM, cfg->mbw_pbm);
+ if (ret)
+ return ret;
+ } else {
+ ret = mpam_reset_msc_bitmap(msc, MPAMCFG_MBW_PBM,
+ rprops->mbw_pbm_bits);
+ if (ret)
+ return ret;
+ }
}
if (mpam_has_feature(mpam_feat_mbw_min, rprops)) {
@@ -1760,27 +1784,47 @@ static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid,
val = max(val, min);
}
- mpam_write_partsel_reg(msc, MBW_MIN, val);
+ ret = mpam_write_partsel_reg(msc, MBW_MIN, val);
+ if (ret)
+ return ret;
}
if (mpam_has_feature(mpam_feat_mbw_max, rprops)) {
- if (mpam_has_feature(mpam_feat_mbw_max, cfg))
- mpam_write_partsel_reg(msc, MBW_MAX, cfg->mbw_max);
- else
- mpam_write_partsel_reg(msc, MBW_MAX, MPAMCFG_MBW_MAX_MAX);
+ if (mpam_has_feature(mpam_feat_mbw_max, cfg)) {
+ ret = mpam_write_partsel_reg(msc, MBW_MAX, cfg->mbw_max);
+ if (ret)
+ return ret;
+ } else {
+ ret = mpam_write_partsel_reg(msc, MBW_MAX,
+ MPAMCFG_MBW_MAX_MAX);
+ if (ret)
+ return ret;
+ }
}
- if (mpam_has_feature(mpam_feat_mbw_prop, rprops))
- mpam_write_partsel_reg(msc, MBW_PROP, 0);
+ if (mpam_has_feature(mpam_feat_mbw_prop, rprops)) {
+ ret = mpam_write_partsel_reg(msc, MBW_PROP, 0);
+ if (ret)
+ return ret;
+ }
- if (mpam_has_feature(mpam_feat_cmax_cmax, rprops))
- mpam_write_partsel_reg(msc, CMAX, cmax);
+ if (mpam_has_feature(mpam_feat_cmax_cmax, rprops)) {
+ ret = mpam_write_partsel_reg(msc, CMAX, cmax);
+ if (ret)
+ return ret;
+ }
- if (mpam_has_feature(mpam_feat_cmax_cmin, rprops))
- mpam_write_partsel_reg(msc, CMIN, 0);
+ if (mpam_has_feature(mpam_feat_cmax_cmin, rprops)) {
+ ret = mpam_write_partsel_reg(msc, CMIN, 0);
+ if (ret)
+ return ret;
+ }
- if (mpam_has_feature(mpam_feat_cmax_cassoc, rprops))
- mpam_write_partsel_reg(msc, CASSOC, MPAMCFG_CASSOC_CASSOC);
+ if (mpam_has_feature(mpam_feat_cmax_cassoc, rprops)) {
+ ret = mpam_write_partsel_reg(msc, CASSOC, MPAMCFG_CASSOC_CASSOC);
+ if (ret)
+ return ret;
+ }
if (mpam_has_feature(mpam_feat_intpri_part, rprops) ||
mpam_has_feature(mpam_feat_dspri_part, rprops)) {
@@ -1804,12 +1848,14 @@ static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid,
pri_val |= FIELD_PREP(MPAMCFG_PRI_DSPRI, dspri);
}
- mpam_write_partsel_reg(msc, PRI, pri_val);
+ ret = mpam_write_partsel_reg(msc, PRI, pri_val);
+ if (ret)
+ return ret;
}
mpam_quirk_post_config_change(ris, partid, cfg);
- mutex_unlock(&msc->part_sel_lock);
+ return 0;
}
/* Call with msc cfg_lock held */
@@ -1936,6 +1982,7 @@ static int mpam_reset_ris(void *arg)
u16 partid_max;
struct mpam_config reset_cfg = {};
struct mpam_msc_ris *ris = arg;
+ int ret;
if (ris->in_reset_state)
return 0;
@@ -1943,8 +1990,11 @@ static int mpam_reset_ris(void *arg)
spin_lock(&partid_max_lock);
partid_max = mpam_partid_max;
spin_unlock(&partid_max_lock);
- for (u32 partid = 0; partid <= partid_max; partid++)
- mpam_reprogram_ris_partid(ris, partid, &reset_cfg);
+ for (u32 partid = 0; partid <= partid_max; partid++) {
+ ret = mpam_reprogram_ris_partid(ris, partid, &reset_cfg);
+ if (ret)
+ return ret;
+ }
return 0;
}
@@ -1983,9 +2033,8 @@ static int __write_config(void *arg)
{
struct mpam_write_config_arg *c = arg;
- mpam_reprogram_ris_partid(c->ris, c->partid, &c->comp->cfg[c->partid]);
-
- return 0;
+ return mpam_reprogram_ris_partid(c->ris, c->partid,
+ &c->comp->cfg[c->partid]);
}
static void mpam_reprogram_msc(struct mpam_msc *msc)
@@ -3144,6 +3193,7 @@ int mpam_apply_config(struct mpam_component *comp, u16 partid,
struct mpam_msc_ris *ris;
struct mpam_vmsc *vmsc;
struct mpam_msc *msc;
+ int ret;
lockdep_assert_cpus_held();
@@ -3161,14 +3211,15 @@ int mpam_apply_config(struct mpam_component *comp, u16 partid,
srcu_read_lock_held(&mpam_srcu)) {
msc = vmsc->msc;
- mutex_lock(&msc->cfg_lock);
+ guard(mutex)(&msc->cfg_lock);
list_for_each_entry_srcu(ris, &vmsc->ris, vmsc_list,
srcu_read_lock_held(&mpam_srcu)) {
arg.ris = ris;
- mpam_touch_msc(msc, __write_config, &arg);
+ ret = mpam_touch_msc(msc, __write_config, &arg);
+ if (ret)
+ return ret;
ris->in_reset_state = false;
}
- mutex_unlock(&msc->cfg_lock);
}
return 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 22/27] arm_mpam: propagate MSC access errors for interrupt control
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (19 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 21/27] arm_mpam: propagate MSC access errors for mpam_reprogram_ris_partid() James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 23/27] arm_mpam: propagate MSC access errors in mpam_reset_class_locked() James Morse
` (5 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Srivathsa L Rao, Jonathan Cameron, James Morse
From: Andre Przywara <andre.przywara@arm.com>
Allow the functions dealing with interrupt registration and enablement
to check for and return errors, and propagate MSC read and write errors
from the lower level up.
This does not cover the IRQ handler yet, as this needs some more
attention.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Srivathsa L Rao <srivathsa.rao@oss.qualcomm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 23 +++++++++++------------
1 file changed, 11 insertions(+), 12 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 7e7b483ff900..c0f51d41e7fe 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -1118,7 +1118,9 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc)
}
/* Clear any stale errors */
- mpam_msc_clear_esr(msc);
+ ret = mpam_msc_clear_esr(msc);
+ if (ret)
+ return ret;
spin_lock(&partid_max_lock);
mpam_partid_max = min(mpam_partid_max, msc->partid_max);
@@ -2082,9 +2084,7 @@ static int mpam_enable_msc_ecr(void *_msc)
{
struct mpam_msc *msc = _msc;
- __mpam_write_reg(msc, MPAMF_ECR, MPAMF_ECR_INTEN);
-
- return 0;
+ return __mpam_write_reg(msc, MPAMF_ECR, MPAMF_ECR_INTEN);
}
/* This can run in mpam_disable(), and the interrupt handler on the same CPU */
@@ -2092,9 +2092,7 @@ static int mpam_disable_msc_ecr(void *_msc)
{
struct mpam_msc *msc = _msc;
- __mpam_write_reg(msc, MPAMF_ECR, 0);
-
- return 0;
+ return __mpam_write_reg(msc, MPAMF_ECR, 0);
}
static void _enable_percpu_irq(void *_irq)
@@ -2823,11 +2821,12 @@ static int mpam_register_irqs(void)
return err;
}
- mutex_lock(&msc->error_irq_lock);
+ guard(mutex)(&msc->error_irq_lock);
msc->error_irq_req = true;
- mpam_touch_msc(msc, mpam_enable_msc_ecr, msc);
+ err = mpam_touch_msc(msc, mpam_enable_msc_ecr, msc);
+ if (err)
+ return err;
msc->error_irq_hw_enabled = true;
- mutex_unlock(&msc->error_irq_lock);
}
return 0;
@@ -2848,8 +2847,8 @@ static void mpam_unregister_irqs(void)
mutex_lock(&msc->error_irq_lock);
if (msc->error_irq_hw_enabled) {
- mpam_touch_msc(msc, mpam_disable_msc_ecr, msc);
- msc->error_irq_hw_enabled = false;
+ if (!mpam_touch_msc(msc, mpam_disable_msc_ecr, msc))
+ msc->error_irq_hw_enabled = false;
}
if (msc->error_irq_req) {
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 23/27] arm_mpam: propagate MSC access errors in mpam_reset_class_locked()
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (20 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 22/27] arm_mpam: propagate MSC access errors for interrupt control James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 24/27] arm_mpam: prepare mon_sel locking for MPAM-Fb James Morse
` (4 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Jonathan Cameron, James Morse
From: Andre Przywara <andre.przywara@arm.com>
The mpam_reset_ris() function can return an error, propagate this up
through mpam_reset_component_locked() to mpam_reset_class_locked(),
which is exported. The external caller is a void function, so cannot
really handle the error at the moment, but at least we report everything
on the MPAM interface side.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 24 ++++++++++++++++++------
drivers/resctrl/mpam_internal.h | 2 +-
2 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index c0f51d41e7fe..31f4826ab399 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -3040,7 +3040,7 @@ static void mpam_enable_once(void)
mpam_partid_max + 1, mpam_pmg_max + 1);
}
-static void mpam_reset_component_locked(struct mpam_component *comp)
+static int mpam_reset_component_locked(struct mpam_component *comp)
{
struct mpam_vmsc *vmsc;
@@ -3054,26 +3054,38 @@ static void mpam_reset_component_locked(struct mpam_component *comp)
srcu_read_lock_held(&mpam_srcu)) {
struct mpam_msc *msc = vmsc->msc;
struct mpam_msc_ris *ris;
+ int ret;
list_for_each_entry_srcu(ris, &vmsc->ris, vmsc_list,
srcu_read_lock_held(&mpam_srcu)) {
- if (!ris->in_reset_state)
- mpam_touch_msc(msc, mpam_reset_ris, ris);
+ if (!ris->in_reset_state) {
+ ret = mpam_touch_msc(msc, mpam_reset_ris, ris);
+ if (ret)
+ return ret;
+ }
ris->in_reset_state = true;
}
}
+
+ return 0;
}
-void mpam_reset_class_locked(struct mpam_class *class)
+int mpam_reset_class_locked(struct mpam_class *class)
{
struct mpam_component *comp;
+ int ret;
lockdep_assert_cpus_held();
guard(srcu)(&mpam_srcu);
list_for_each_entry_srcu(comp, &class->components, class_list,
- srcu_read_lock_held(&mpam_srcu))
- mpam_reset_component_locked(comp);
+ srcu_read_lock_held(&mpam_srcu)) {
+ ret = mpam_reset_component_locked(comp);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
static void mpam_reset_class(struct mpam_class *class)
diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h
index 68a6cf2b9cc7..09f8168fb4d2 100644
--- a/drivers/resctrl/mpam_internal.h
+++ b/drivers/resctrl/mpam_internal.h
@@ -465,7 +465,7 @@ void mpam_enable(struct work_struct *work);
void mpam_disable(struct work_struct *work);
/* Reset all the RIS in a class under cpus_read_lock() */
-void mpam_reset_class_locked(struct mpam_class *class);
+int mpam_reset_class_locked(struct mpam_class *class);
int mpam_apply_config(struct mpam_component *comp, u16 partid,
struct mpam_config *cfg);
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 24/27] arm_mpam: prepare mon_sel locking for MPAM-Fb
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (21 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 23/27] arm_mpam: propagate MSC access errors in mpam_reset_class_locked() James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 25/27] arm_mpam: add MPAM-Fb MSC firmware access support James Morse
` (3 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Jonathan Cameron, Gavin Shan, James Morse
From: Andre Przywara <andre.przywara@arm.com>
The MSC MON_SEL register needs to be accessed from hardirq for the overflow
interrupt, and when taking an IPI to access these registers on platforms
where MSCs are not accesible from every CPU. This makes an irqsave
spinlock the obvious lock to protect these registers. On systems with
MPAM-Fb mailbox MSC access it must be able to sleep, meaning a mutex must
be used. So MPAM-Fb platforms cannot support an overflow interrupt easily.
Clearly these two methods can't exist for one MSC at the same time.
Change the mon_sel locking wrapper function to only use a spinlock when
the MSC is accessed directly via MMIO. In case of MPAM-Fb, we use a
mutex, but only if we are in a sleepable context. If that's not the
case, we return an error. This should not happen, as MPAM-Fb by design
does not require an MSC access to happen from a specific CPU, so there
is no need for any IPIs or preemption disabling to satisfy CPU
constraints. And since overflow interrupts are not supported at the moment
anyway, we also wouldn't meet the other case.
Bailing out early is already happening in rare occasions today.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Tested-by: Gavin Shan <gshan@redhat.com> # on NVIDIA Grace Hopper
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 6 ++++-
drivers/resctrl/mpam_internal.h | 43 +++++++++++++++++++++++++++------
2 files changed, 41 insertions(+), 8 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 31f4826ab399..2a0ac23cc042 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -2349,7 +2349,6 @@ static struct mpam_msc *do_mpam_msc_drv_probe(struct platform_device *pdev)
if (err)
return ERR_PTR(err);
- mpam_mon_sel_lock_init(msc);
msc->id = pdev->id;
msc->pdev = pdev;
INIT_LIST_HEAD_RCU(&msc->all_msc_list);
@@ -2370,6 +2369,11 @@ static struct mpam_msc *do_mpam_msc_drv_probe(struct platform_device *pdev)
else
msc->iface = MPAM_IFACE_PCC;
+ /* Lock type depends on MSC interface used */
+ err = mpam_mon_sel_lock_init(dev, msc);
+ if (err)
+ return ERR_PTR(err);
+
if (msc->iface == MPAM_IFACE_MMIO) {
void __iomem *io;
diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h
index 09f8168fb4d2..58f7523bc087 100644
--- a/drivers/resctrl/mpam_internal.h
+++ b/drivers/resctrl/mpam_internal.h
@@ -126,6 +126,12 @@ struct mpam_msc {
*/
raw_spinlock_t _mon_sel_lock;
unsigned long _mon_sel_flags;
+ /*
+ * mon_sel_mutex is the mutex version of the _mon_sel_lock above.
+ * Always use the mpam_mon_sel_lock() helpers when taking the lock,
+ * as this will select the correct lock type automatically.
+ */
+ struct mutex mon_sel_mutex;
void __iomem *mapped_hwpage;
size_t mapped_hwpage_sz;
@@ -139,27 +145,50 @@ struct mpam_msc {
/* Returning false here means accesses to mon_sel must fail and report an error. */
static inline bool __must_check mpam_mon_sel_lock(struct mpam_msc *msc)
{
- /* Locking will require updating to support a firmware backed interface */
- if (WARN_ON_ONCE(msc->iface != MPAM_IFACE_MMIO))
+ if (msc->iface == MPAM_IFACE_MMIO) {
+ raw_spin_lock_irqsave(&msc->_mon_sel_lock, msc->_mon_sel_flags);
+
+ return true;
+ }
+
+ if (!preemptible())
return false;
- raw_spin_lock_irqsave(&msc->_mon_sel_lock, msc->_mon_sel_flags);
+ mutex_lock(&msc->mon_sel_mutex);
+
return true;
}
static inline void mpam_mon_sel_unlock(struct mpam_msc *msc)
{
- raw_spin_unlock_irqrestore(&msc->_mon_sel_lock, msc->_mon_sel_flags);
+ if (msc->iface == MPAM_IFACE_MMIO) {
+ raw_spin_unlock_irqrestore(&msc->_mon_sel_lock,
+ msc->_mon_sel_flags);
+
+ return;
+ }
+
+ mutex_unlock(&msc->mon_sel_mutex);
}
static inline void mpam_mon_sel_lock_held(struct mpam_msc *msc)
{
- lockdep_assert_held_once(&msc->_mon_sel_lock);
+ if (msc->iface == MPAM_IFACE_MMIO)
+ lockdep_assert_held_once(&msc->_mon_sel_lock);
+ else
+ lockdep_assert_held_once(&msc->mon_sel_mutex);
}
-static inline void mpam_mon_sel_lock_init(struct mpam_msc *msc)
+static inline int mpam_mon_sel_lock_init(struct device *dev,
+ struct mpam_msc *msc)
{
- raw_spin_lock_init(&msc->_mon_sel_lock);
+ if (msc->iface == MPAM_IFACE_MMIO) {
+ raw_spin_lock_init(&msc->_mon_sel_lock);
+
+ return 0;
+ }
+
+ return devm_mutex_init(dev, &msc->mon_sel_mutex);
}
DEFINE_GUARD(mon_sel, struct mpam_msc *,
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 25/27] arm_mpam: add MPAM-Fb MSC firmware access support
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (22 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 24/27] arm_mpam: prepare mon_sel locking for MPAM-Fb James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 26/27] arm_mpam: change MPAM-Fb error IRQ to use a threaded IRQ handler James Morse
` (2 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Jonathan Cameron, Ritwick Sharma, Gavin Shan,
Srivathsa L Rao, James Morse
From: Andre Przywara <andre.przywara@arm.com>
The Arm MPAM Firmware-backed (Fb) Profile document[1] describes an
alternative way of accessing the "Memory System Components" (MSC) in an
MPAM enabled system.
Normally the MSCs are MMIO mapped, but in some implementations this
might not be possible (MSC located outside of the local socket, MSC
mapped secure-only) or desirable (direct MMIO access too slow or needs
to be mediated through a control processor). MPAM-fb standardises a
protocol to abstract MSC accesses, building on the SCMI protocol.
Add functions that do an MSC read or write access by redirecting the
request through a firmware interface. For now this done via an ACPI
PCC shared memory and mailbox combination.
Since the protocol used is only a small subset of the full SCMI spec,
and the SCMI protocol has no full ACPI support anyway, open-code the
(simple) SCMI message generation, for just the fields we need.
[1] https://developer.arm.com/documentation/den0144/latest
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Tested-by: Ritwick Sharma <ritwick.sharma@arm.com> # on Arm AGI CPU
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Srivathsa L Rao <srivathsa.rao@oss.qualcomm.com>
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/acpi/arm64/mpam.c | 6 +-
drivers/resctrl/Kconfig | 1 +
drivers/resctrl/Makefile | 2 +-
drivers/resctrl/mpam_devices.c | 65 ++++++++--
drivers/resctrl/mpam_fb.c | 217 ++++++++++++++++++++++++++++++++
drivers/resctrl/mpam_internal.h | 19 +++
include/linux/arm_mpam.h | 2 +-
7 files changed, 296 insertions(+), 16 deletions(-)
create mode 100644 drivers/resctrl/mpam_fb.c
diff --git a/drivers/acpi/arm64/mpam.c b/drivers/acpi/arm64/mpam.c
index 84963a20c3e7..c57bd367922b 100644
--- a/drivers/acpi/arm64/mpam.c
+++ b/drivers/acpi/arm64/mpam.c
@@ -220,8 +220,8 @@ static struct platform_device * __init acpi_mpam_parse_msc(struct acpi_mpam_msc_
struct platform_device *pdev __free(platform_device_put) =
platform_device_alloc("mpam_msc", tbl_msc->identifier);
int next_res = 0, next_prop = 0, err;
- /* pcc, nrdy, affinity and a sentinel */
- struct property_entry props[4] = { 0 };
+ /* pcc, msc-id, nrdy, affinity and a sentinel */
+ struct property_entry props[5] = { 0 };
/* mmio, 2xirq, no sentinel. */
struct resource res[3] = { 0 };
struct acpi_device *companion;
@@ -256,6 +256,8 @@ static struct platform_device * __init acpi_mpam_parse_msc(struct acpi_mpam_msc_
} else if (iface == MPAM_IFACE_PCC) {
props[next_prop++] = PROPERTY_ENTRY_U32("pcc-channel",
tbl_msc->base_address);
+ props[next_prop++] = PROPERTY_ENTRY_U32("mpam-fb-msc-id",
+ tbl_msc->identifier);
}
acpi_mpam_parse_irqs(pdev, tbl_msc, res, &next_res);
diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig
index 672abea3b03c..9591d792736e 100644
--- a/drivers/resctrl/Kconfig
+++ b/drivers/resctrl/Kconfig
@@ -2,6 +2,7 @@ menuconfig ARM64_MPAM_DRIVER
bool "MPAM driver"
depends on ARM64 && ARM64_MPAM
select ACPI_MPAM if ACPI
+ select MAILBOX
help
Memory System Resource Partitioning and Monitoring (MPAM) driver for
System IP, e.g. caches and memory controllers.
diff --git a/drivers/resctrl/Makefile b/drivers/resctrl/Makefile
index 4f6d0e81f9b8..097c036724e9 100644
--- a/drivers/resctrl/Makefile
+++ b/drivers/resctrl/Makefile
@@ -1,5 +1,5 @@
obj-$(CONFIG_ARM64_MPAM_DRIVER) += mpam.o
-mpam-y += mpam_devices.o
+mpam-y += mpam_devices.o mpam_fb.o
mpam-$(CONFIG_ARM64_MPAM_RESCTRL_FS) += mpam_resctrl.o
ccflags-$(CONFIG_ARM64_MPAM_DRIVER_DEBUG) += -DDEBUG
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 2a0ac23cc042..89706106e890 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -12,6 +12,7 @@
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/device.h>
+#include <linux/errname.h>
#include <linux/errno.h>
#include <linux/gfp.h>
#include <linux/interrupt.h>
@@ -82,6 +83,21 @@ static DECLARE_WORK(mpam_broken_work, &mpam_disable);
/* When mpam is disabled, the printed reason to aid debugging */
static char *mpam_disable_reason;
+static int mpam_disable_errno;
+static int mpam_disable_mpam_fb_err;
+
+void mpam_fb_disable_mpam(int err, int mpam_fb_err)
+{
+ /* Prevent repeated calls when mpam_disable() does MSC accesses. */
+ if (!mpam_is_enabled())
+ return;
+
+ mpam_disable_errno = err;
+ mpam_disable_mpam_fb_err = mpam_fb_err;
+
+ mpam_disable_reason = "MPAM-Fb error";
+ schedule_work(&mpam_broken_work);
+}
/*
* Whether resctrl has been setup. Used by cpuhp in preference to
@@ -179,8 +195,11 @@ static void mpam_assert_partid_sizes_fixed(void)
static int __mpam_read_reg(struct mpam_msc *msc, u16 reg, u32 *res)
{
+
+ if (msc->iface == MPAM_IFACE_PCC)
+ return mpam_fb_send_read_request(msc, reg, res);
+
WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility));
-
*res = readl_relaxed(msc->mapped_hwpage + reg);
return 0;
@@ -197,9 +216,12 @@ static inline int _mpam_read_partsel_reg(struct mpam_msc *msc, u16 reg,
static int __mpam_write_reg(struct mpam_msc *msc, u16 reg, u32 val)
{
+
+ if (msc->iface == MPAM_IFACE_PCC)
+ return mpam_fb_send_write_request(msc, reg, val);
+
+ WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility));
WARN_ON_ONCE(reg + sizeof(u32) > msc->mapped_hwpage_sz);
- WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility));
-
writel_relaxed(val, msc->mapped_hwpage + reg);
return 0;
@@ -1150,8 +1172,11 @@ static int mpam_msc_read_mbwu_l(struct mpam_msc *msc, u64 *res)
mpam_mon_sel_lock_held(msc);
- WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz);
- WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility));
+ if (msc->iface == MPAM_IFACE_MMIO) {
+ WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz);
+ WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(),
+ &msc->accessibility));
+ }
ret = __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2);
if (ret)
@@ -1185,8 +1210,11 @@ static int mpam_msc_zero_mbwu_l(struct mpam_msc *msc)
mpam_mon_sel_lock_held(msc);
- WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz);
- WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility));
+ if (msc->iface == MPAM_IFACE_MMIO) {
+ WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz);
+ WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(),
+ &msc->accessibility));
+ }
ret = __mpam_write_reg(msc, MSMON_MBWU_L, 0);
if (ret)
@@ -1507,11 +1535,16 @@ static int _msmon_read(struct mpam_component *comp, struct mon_read *arg)
srcu_read_lock_held(&mpam_srcu)) {
arg->ris = ris;
- err = smp_call_function_any(&msc->accessibility,
- __ris_msmon_read, arg,
- true);
- if (!err && arg->err)
+ if (msc->iface == MPAM_IFACE_MMIO) {
+ err = smp_call_function_any(&msc->accessibility,
+ __ris_msmon_read,
+ arg, true);
+ if (!err)
+ err = arg->err;
+ } else {
+ __ris_msmon_read(arg);
err = arg->err;
+ }
/*
* Save one error to be returned to the caller, but
@@ -2018,6 +2051,9 @@ static int mpam_get_msc_preferred_cpu(struct mpam_msc *msc)
static int mpam_touch_msc(struct mpam_msc *msc, int (*fn)(void *a), void *arg)
{
+ if (msc->iface != MPAM_IFACE_MMIO)
+ return fn(arg);
+
lockdep_assert_irqs_enabled();
lockdep_assert_cpus_held();
WARN_ON_ONCE(!srcu_read_lock_held((&mpam_srcu)));
@@ -3148,7 +3184,12 @@ void mpam_disable(struct work_struct *ignored)
mutex_unlock(&mpam_list_lock);
mpam_free_garbage();
- pr_err_once("MPAM disabled due to %s\n", mpam_disable_reason);
+ if (mpam_disable_errno || mpam_disable_mpam_fb_err)
+ pr_err_once("MPAM disabled due to %s: %s, MPAM-Fb error %d\n",
+ mpam_disable_reason, errname(mpam_disable_errno),
+ mpam_disable_mpam_fb_err);
+ else
+ pr_err_once("MPAM disabled due to %s\n", mpam_disable_reason);
}
/*
diff --git a/drivers/resctrl/mpam_fb.c b/drivers/resctrl/mpam_fb.c
new file mode 100644
index 000000000000..72f45e9582df
--- /dev/null
+++ b/drivers/resctrl/mpam_fb.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2024-2026 Arm Ltd.
+
+#include <linux/arm_mpam.h>
+#include <linux/cleanup.h>
+#include <linux/errno.h>
+#include <linux/mailbox_client.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#include <acpi/pcc.h>
+#include <asm/mpam.h>
+
+#include "mpam_internal.h"
+
+#define MPAM_MSC_TOKEN_MASK GENMASK_U32(27, 18)
+#define MPAM_MSC_PROT_ID_MASK GENMASK_U32(17, 10)
+
+#define MPAM_FB_PROTOCOL_ID 0x1a
+
+#define MPAM_PROTOCOL_VERSION_CMD 0x0
+#define MPAM_MSC_READ_CMD 0x4
+#define MPAM_MSC_WRITE_CMD 0x5
+
+#define MPAM_FB_PROT_HEADER_LEN sizeof(u32)
+
+#define MPAM_FB_SUCCESS 0
+#define MPAM_FB_ERR_NOT_SUPPORTED -1
+#define MPAM_FB_ERR_INVALID_PARAMETERS -2
+#define MPAM_FB_ERR_DENIED -3
+#define MPAM_FB_ERR_NOT_FOUND -4
+#define MPAM_FB_ERR_OUT_OF_RANGE -5
+#define MPAM_FB_ERR_BUSY -6
+#define MPAM_FB_ERR_COMMS_ERROR -7
+#define MPAM_FB_ERR_GENERIC_ERROR -8
+#define MPAM_FB_ERR_HW_ERROR -9
+#define MPAM_FB_ERR_PROTOCOL_ERROR -10
+#define MPAM_FB_ERR_IN_USE -11
+
+static atomic_t mpam_fb_token = ATOMIC_INIT(0);
+
+static int mpam_fb_to_linux_errno(int mpam_fb_code)
+{
+ switch (mpam_fb_code) {
+ case MPAM_FB_ERR_NOT_SUPPORTED:
+ return -EOPNOTSUPP;
+ case MPAM_FB_ERR_INVALID_PARAMETERS:
+ return -EINVAL;
+ case MPAM_FB_ERR_NOT_FOUND:
+ return -ENOENT;
+ case MPAM_FB_ERR_OUT_OF_RANGE:
+ return -ERANGE;
+ case MPAM_FB_ERR_BUSY:
+ return -EBUSY;
+ default:
+ return -EINVAL;
+ }
+}
+
+static void mpam_fb_build_version_message(unsigned int token,
+ void __iomem *msg_buf)
+{
+ struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem = msg_buf;
+
+ /* .signature is filled by the platform */
+ writel_relaxed(PCC_CMD_COMPLETION_NOTIFY, &pcc_shmem->flags);
+ writel_relaxed(MPAM_FB_PROT_HEADER_LEN, &pcc_shmem->length);
+ writel_relaxed(MPAM_PROTOCOL_VERSION_CMD |
+ FIELD_PREP(MPAM_MSC_TOKEN_MASK, token) |
+ FIELD_PREP(MPAM_MSC_PROT_ID_MASK, MPAM_FB_PROTOCOL_ID),
+ &pcc_shmem->command);
+}
+
+static void mpam_fb_build_read_message(int msc_fb_id, int reg,
+ unsigned int token,
+ void __iomem *msg_buf)
+{
+ struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem = msg_buf;
+ struct mpam_fb_read_payload {
+ u32 msc_fb_id;
+ u32 flags;
+ u32 reg_offset;
+ } __packed __iomem *payload = msg_buf + sizeof(*pcc_shmem);
+ int msg_size = MPAM_FB_PROT_HEADER_LEN + sizeof(*payload);
+
+ /* .signature is filled by the platform */
+ writel_relaxed(PCC_CMD_COMPLETION_NOTIFY, &pcc_shmem->flags);
+ writel_relaxed(msg_size, &pcc_shmem->length);
+ writel_relaxed(MPAM_MSC_READ_CMD |
+ FIELD_PREP(MPAM_MSC_TOKEN_MASK, token) |
+ FIELD_PREP(MPAM_MSC_PROT_ID_MASK, MPAM_FB_PROTOCOL_ID),
+ &pcc_shmem->command);
+
+ writel_relaxed(msc_fb_id, &payload->msc_fb_id);
+ writel_relaxed(0, &payload->flags);
+ writel_relaxed(reg, &payload->reg_offset);
+}
+
+static void mpam_fb_build_write_message(int msc_fb_id, int reg, u32 val,
+ unsigned int token,
+ void __iomem *msg_buf)
+{
+ struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem = msg_buf;
+ struct mpam_fb_write_payload {
+ u32 msc_fb_id;
+ u32 flags;
+ u32 reg_offset;
+ u32 value;
+ } __packed __iomem *payload = msg_buf + sizeof(*pcc_shmem);
+ int msg_size = MPAM_FB_PROT_HEADER_LEN + sizeof(*payload);
+
+ /* .signature is filled by the platform */
+ writel_relaxed(PCC_CMD_COMPLETION_NOTIFY, &pcc_shmem->flags);
+ writel_relaxed(msg_size, &pcc_shmem->length);
+ writel_relaxed(MPAM_MSC_WRITE_CMD |
+ FIELD_PREP(MPAM_MSC_TOKEN_MASK, token) |
+ FIELD_PREP(MPAM_MSC_PROT_ID_MASK, MPAM_FB_PROTOCOL_ID),
+ &pcc_shmem->command);
+
+ writel_relaxed(msc_fb_id, &payload->msc_fb_id);
+ writel_relaxed(0, &payload->flags);
+ writel_relaxed(reg, &payload->reg_offset);
+ writel_relaxed(val, &payload->value);
+}
+
+static int mpam_fb_send_request(struct mpam_msc *msc, u32 msc_fb_id,
+ u16 reg, u32 *result, int mpam_fb_command)
+{
+ unsigned int token = atomic_inc_return(&mpam_fb_token);
+ struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem;
+ struct mpam_pcc_chan *pcc_chan;
+ struct pcc_mbox_chan *chan;
+ void __iomem *payload_ofs;
+ int mpam_fb_err = 0;
+ u32 status;
+ int ret;
+
+ pcc_chan = msc->pcc_chan;
+ if (!pcc_chan)
+ return -ENODEV;
+
+ chan = pcc_chan->pcc_chan;
+
+ /* prune token to fit into the 10 bits inside the command register */
+ token = FIELD_GET(MPAM_MSC_TOKEN_MASK,
+ FIELD_PREP(MPAM_MSC_TOKEN_MASK, token));
+
+ mutex_lock(&pcc_chan->pcc_chan_lock);
+
+ switch (mpam_fb_command) {
+ case MPAM_PROTOCOL_VERSION_CMD:
+ mpam_fb_build_version_message(token, chan->shmem);
+ break;
+ case MPAM_MSC_READ_CMD:
+ mpam_fb_build_read_message(msc_fb_id, reg, token, chan->shmem);
+ break;
+ case MPAM_MSC_WRITE_CMD:
+ mpam_fb_build_write_message(msc_fb_id, reg, *result,
+ token, chan->shmem);
+ break;
+ default:
+ dev_err(&msc->pdev->dev, "unsupported MPAM-Fb command %d\n",
+ mpam_fb_command);
+ ret = -EINVAL;
+ goto out_err;
+ }
+
+ dma_wmb();
+ ret = mbox_send_message(chan->mchan, NULL);
+ if (ret < 0)
+ goto out_err;
+
+ pcc_shmem = chan->shmem;
+ payload_ofs = chan->shmem + sizeof(*pcc_shmem);
+ status = readl(&pcc_shmem->command);
+ if (FIELD_GET(MPAM_MSC_TOKEN_MASK, status) != token) {
+ ret = -ETIMEDOUT;
+ goto out_err;
+ }
+
+ mpam_fb_err = readl(payload_ofs + 0x0);
+ if (mpam_fb_err < 0) {
+ ret = mpam_fb_to_linux_errno(mpam_fb_err);
+ goto out_err;
+ }
+
+ if (mpam_fb_command != MPAM_MSC_WRITE_CMD)
+ *result = readl(payload_ofs + 0x4);
+
+ mutex_unlock(&pcc_chan->pcc_chan_lock);
+
+ return 0;
+
+out_err:
+ mutex_unlock(&pcc_chan->pcc_chan_lock);
+
+ mpam_fb_disable_mpam(ret, mpam_fb_err);
+
+ /*
+ * TODO: Error codes are filtered in higher layers, return something
+ * that is known there for now, to actually report them.
+ */
+ return -EIO;
+}
+
+int mpam_fb_send_read_request(struct mpam_msc *msc, u16 reg, u32 *result)
+{
+ return mpam_fb_send_request(msc, msc->fb_id, reg, result,
+ MPAM_MSC_READ_CMD);
+}
+
+int mpam_fb_send_write_request(struct mpam_msc *msc, u16 reg, u32 value)
+{
+ return mpam_fb_send_request(msc, msc->fb_id, reg, &value,
+ MPAM_MSC_WRITE_CMD);
+}
diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h
index 58f7523bc087..99057d6abee1 100644
--- a/drivers/resctrl/mpam_internal.h
+++ b/drivers/resctrl/mpam_internal.h
@@ -11,6 +11,7 @@
#include <linux/io.h>
#include <linux/jump_label.h>
#include <linux/llist.h>
+#include <linux/mailbox_client.h>
#include <linux/mutex.h>
#include <linux/resctrl.h>
#include <linux/spinlock.h>
@@ -57,6 +58,15 @@ struct mpam_garbage {
struct platform_device *pdev;
};
+struct mpam_pcc_chan {
+ struct list_head pcc_chans;
+ struct mbox_client pcc_cl;
+ struct pcc_mbox_chan *pcc_chan;
+ struct mutex pcc_chan_lock; /* only one message at a time */
+ struct kref refcount;
+ int subspace_id;
+};
+
struct mpam_msc {
/* member of mpam_all_msc */
struct list_head all_msc_list;
@@ -66,6 +76,8 @@ struct mpam_msc {
/* Not modified after mpam_is_enabled() becomes true */
enum mpam_msc_iface iface;
+ struct mpam_pcc_chan *pcc_chan;
+ int fb_id;
u32 nrdy_usec;
cpumask_t accessibility;
bool has_extd_esr;
@@ -493,6 +505,9 @@ extern u8 mpam_pmg_max;
void mpam_enable(struct work_struct *work);
void mpam_disable(struct work_struct *work);
+/* helper function to call from outside mpam_devices.c */
+void mpam_fb_disable_mpam(int err, int mpam_fb_err);
+
/* Reset all the RIS in a class under cpus_read_lock() */
int mpam_reset_class_locked(struct mpam_class *class);
@@ -520,6 +535,10 @@ static inline void mpam_resctrl_offline_cpu(unsigned int cpu) { }
static inline void mpam_resctrl_teardown_class(struct mpam_class *class) { }
#endif /* CONFIG_RESCTRL_FS */
+/* MPAM-Fb Firmware-backed protocol wrappers */
+int mpam_fb_send_read_request(struct mpam_msc *msc, u16 reg, u32 *result);
+int mpam_fb_send_write_request(struct mpam_msc *msc, u16 reg, u32 value);
+
/*
* MPAM MSCs have the following register layout. See:
* Arm Memory System Resource Partitioning and Monitoring (MPAM) System
diff --git a/include/linux/arm_mpam.h b/include/linux/arm_mpam.h
index f92a36187a52..002f56e15362 100644
--- a/include/linux/arm_mpam.h
+++ b/include/linux/arm_mpam.h
@@ -12,7 +12,7 @@ struct mpam_msc;
enum mpam_msc_iface {
MPAM_IFACE_MMIO, /* a real MPAM MSC */
- MPAM_IFACE_PCC, /* a fake MPAM MSC */
+ MPAM_IFACE_PCC, /* using the MPAM-Fb firmware redirection */
};
enum mpam_class_types {
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 26/27] arm_mpam: change MPAM-Fb error IRQ to use a threaded IRQ handler
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (23 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 25/27] arm_mpam: add MPAM-Fb MSC firmware access support James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 17:39 ` [PATCH 27/27] arm_mpam: detect and enable MPAM-Fb PCC support James Morse
2026-10-02 19:02 ` [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 Catalin Marinas
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Gavin Shan, Srivathsa L Rao, Jonathan Cameron,
James Morse
From: Andre Przywara <andre.przywara@arm.com>
When an MPAM MSC gets into an error condition, it can trigger an error
IRQ. We cannot really do much about those errors, but we at least query
and log the error, then disable MPAM functionality.
This error report relies on reading the MSC's error status register
(ESR) in the current hard-IRQ handler, which is not possible for MPAM-Fb
based MSC accesses, since they involve mailbox routines that might sleep.
The same is true for clearing the interrupt at the source, which requires
a (potentially sleeping) MSC access as well.
Change the error IRQ handler to be a threaded interrupt, but keep the
handling in the hard-IRQ part for MMIO MSCs. This is needed since the
CPU affinity check in the MSC accessors requires a non-preemptible
context.
When the MSC is using an MPAM-Fb based access, we push the work into the
threaded part of the handler, where the accessors are allowed to sleep.
Also forbid per-CPU interrupts (PPIs) for MPAM-Fb, as we cannot use a
threaded IRQ here.
The actual IRQ handler learns how to deal with errors. We cannot really
handle them, but we can try our best to disable the IRQ anyway.
Should the level IRQ line deactivation fail on the device side, we mask
the IRQ on the irqchip level, to prevent an interrupt storm.
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Srivathsa L Rao <srivathsa.rao@oss.qualcomm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/mpam_devices.c | 77 ++++++++++++++++++++++++++++------
1 file changed, 65 insertions(+), 12 deletions(-)
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 89706106e890..95caa151b27a 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -2767,24 +2767,42 @@ static char *mpam_errcode_names[16] = {
[12 ... 15] = "Reserved"
};
+/*
+ * This will run as the threaded IRQ handler part when using MPAM-Fb, but
+ * as the sole hard-IRQ handler for MMIO based accesses.
+ */
static irqreturn_t __mpam_irq_handler(int irq, struct mpam_msc *msc)
{
u64 reg;
+ int ret;
u16 partid;
u8 errcode, pmg, ris;
- if (WARN_ON_ONCE(!msc) ||
+ if (WARN_ON_ONCE(!msc))
+ return IRQ_NONE;
+
+ if (msc->iface == MPAM_IFACE_MMIO &&
WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(),
&msc->accessibility)))
return IRQ_NONE;
- mpam_msc_read_esr(msc, ®);
+ ret = mpam_msc_read_esr(msc, ®);
+ if (ret) {
+ pr_err_ratelimited("unknown error irq from msc:%u\n", msc->id);
+
+ /* Try out best here ... */
+ goto out_disable;
+ }
errcode = FIELD_GET(MPAMF_ESR_ERRCODE, reg);
if (!errcode)
return IRQ_NONE;
- /* Clear level triggered irq */
+ /*
+ * Clear the level triggered IRQ. If that fails, we cannot do anything
+ * about it, so ignore any errors. We will disable the IRQ either on
+ * the device side or on the irqchip level next anyway.
+ */
mpam_msc_clear_esr(msc);
partid = FIELD_GET(MPAMF_ESR_PARTID_MON, reg);
@@ -2795,16 +2813,24 @@ static irqreturn_t __mpam_irq_handler(int irq, struct mpam_msc *msc)
msc->id, mpam_errcode_names[errcode], partid, pmg,
ris);
- /* Disable this interrupt. */
- mpam_disable_msc_ecr(msc);
+out_disable:
+ /*
+ * Disable this interrupt on the device side. If that fails, disable
+ * the IRQ on the irqchip level, as we must prevent further handler
+ * invocations. We only take the interrupt once anyway, as we
+ * are going to free the IRQ next, in mpam_disable().
+ */
+ ret = mpam_disable_msc_ecr(msc);
+ if (ret)
+ disable_irq_nosync(irq);
- /* Are we racing with the thread disabling MPAM? */
+ /* Check whether we are racing with the thread disabling MPAM. */
if (!mpam_is_enabled())
return IRQ_HANDLED;
/*
- * Schedule the teardown work. Don't use a threaded IRQ as we can't
- * unregister the interrupt from the threaded part of the handler.
+ * Schedule the teardown work. We have to defer it as we can't
+ * unregister the interrupt from the threaded part of a handler.
*/
mpam_disable_reason = "hardware error interrupt";
schedule_work(&mpam_broken_work);
@@ -2819,10 +2845,30 @@ static irqreturn_t mpam_ppi_handler(int irq, void *dev_id)
return __mpam_irq_handler(irq, msc);
}
-static irqreturn_t mpam_spi_handler(int irq, void *dev_id)
+/*
+ * MMIO based MSC accesses must run in non-preemptible context, as they
+ * might have affinity requirements to check.
+ * MPAM-Fb based MSC accesses must NOT run in hard-IRQ context, as they
+ * can sleep.
+ * So split the IRQ handling up, depending on the MSC access type.
+ */
+static irqreturn_t mpam_shared_hard_irq(int irq, void *dev_id)
{
struct mpam_msc *msc = dev_id;
+ if (msc->iface == MPAM_IFACE_MMIO)
+ return __mpam_irq_handler(irq, msc);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t mpam_shared_thread_irq(int irq, void *dev_id)
+{
+ struct mpam_msc *msc = dev_id;
+
+ if (msc->iface == MPAM_IFACE_MMIO)
+ return IRQ_HANDLED;
+
return __mpam_irq_handler(irq, msc);
}
@@ -2843,6 +2889,11 @@ static int mpam_register_irqs(void)
/* The MPAM spec says the interrupt can be SPI, PPI or LPI */
/* We anticipate sharing the interrupt with other MSCs */
if (irq_is_percpu(irq)) {
+ if (msc->iface != MPAM_IFACE_MMIO) {
+ dev_err(&msc->pdev->dev,
+ "Only MMIO MSCs can use per-CPU interrupts\n");
+ return -EINVAL;
+ }
err = request_percpu_irq(irq, &mpam_ppi_handler,
"mpam:msc:error",
msc->error_dev_id);
@@ -2854,9 +2905,11 @@ static int mpam_register_irqs(void)
&_enable_percpu_irq, &irq,
true);
} else {
- err = devm_request_irq(&msc->pdev->dev, irq,
- &mpam_spi_handler, IRQF_SHARED,
- "mpam:msc:error", msc);
+ err = devm_request_threaded_irq(&msc->pdev->dev, irq,
+ &mpam_shared_hard_irq,
+ &mpam_shared_thread_irq,
+ IRQF_SHARED | IRQF_ONESHOT,
+ "mpam:msc:error", msc);
if (err)
return err;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* [PATCH 27/27] arm_mpam: detect and enable MPAM-Fb PCC support
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (24 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 26/27] arm_mpam: change MPAM-Fb error IRQ to use a threaded IRQ handler James Morse
@ 2026-10-02 17:39 ` James Morse
2026-10-02 19:02 ` [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 Catalin Marinas
26 siblings, 0 replies; 30+ messages in thread
From: James Morse @ 2026-10-02 17:39 UTC (permalink / raw)
To: linux-arm-kernel
Cc: Catalin Marinas, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba, Ritwick Sharma, Jonathan Cameron, Srivathsa L Rao,
Gavin Shan, James Morse
From: Andre Przywara <andre.przywara@arm.com>
The Arm MPAM-Fb specification [1] describes a protocol to access MSC
registers through a firmware interface. This requires a shared memory
region to hold the message, and a mailbox to trigger the access.
For ACPI this is wrapped as a PCC channel, described using existing
ACPI abstractions.
Add code to parse those PCC table descriptions associated with an MSC,
and store the parsed information in the MSC struct.
There can be multiple PCC channels, and each channel can serve multiple
MSCs, so we need to keep track of the channel usage, using a list and
a refcount.
This will be used by the MPAM-Fb access wrapper code.
[1] https://developer.arm.com/documentation/den0144/latest
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Tested-by: Ritwick Sharma <ritwick.sharma@arm.com> # on Arm AGI CPU
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Tested-by: Srivathsa L Rao <srivathsa.rao@oss.qualcomm.com>
Reviewed-by: Srivathsa L Rao <srivathsa.rao@oss.qualcomm.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Ben Horgan <ben.horgan@arm.com>
Signed-off-by: James Morse <james.morse@arm.com>
---
drivers/resctrl/Kconfig | 1 +
drivers/resctrl/mpam_devices.c | 119 +++++++++++++++++++++++++++++++-
drivers/resctrl/mpam_fb.c | 41 +++++++++++
drivers/resctrl/mpam_internal.h | 2 +
4 files changed, 161 insertions(+), 2 deletions(-)
diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig
index 9591d792736e..f5ff9b1f6c48 100644
--- a/drivers/resctrl/Kconfig
+++ b/drivers/resctrl/Kconfig
@@ -2,6 +2,7 @@ menuconfig ARM64_MPAM_DRIVER
bool "MPAM driver"
depends on ARM64 && ARM64_MPAM
select ACPI_MPAM if ACPI
+ select PCC if ACPI
select MAILBOX
help
Memory System Resource Partitioning and Monitoring (MPAM) driver for
diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c
index 95caa151b27a..c32175426433 100644
--- a/drivers/resctrl/mpam_devices.c
+++ b/drivers/resctrl/mpam_devices.c
@@ -20,14 +20,19 @@
#include <linux/irqdesc.h>
#include <linux/list.h>
#include <linux/lockdep.h>
+#include <linux/mailbox_client.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/printk.h>
+#include <linux/property.h>
#include <linux/srcu.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/workqueue.h>
+#include <acpi/pcc.h>
+#include <acpi/acpi_io.h>
+
#include "mpam_internal.h"
/* Values for the T241 errata workaround */
@@ -50,6 +55,87 @@ static LIST_HEAD(mpam_all_msc);
struct srcu_struct mpam_srcu;
+/* PCC channels might be serving multiple MSCs, so keep a refcounted list. */
+static DEFINE_MUTEX(pcc_chan_list_lock);
+static LIST_HEAD(pcc_chan_list);
+
+static void mpam_pcc_chan_release(struct kref *ref)
+{
+ struct mpam_pcc_chan *cur = container_of(ref, struct mpam_pcc_chan,
+ refcount);
+
+ pcc_mbox_free_channel(cur->pcc_chan);
+ list_del(&cur->pcc_chans);
+ mutex_destroy(&cur->pcc_chan_lock);
+ kfree(cur);
+}
+
+static struct mpam_pcc_chan *mpam_pcc_chan_get(struct device *dev,
+ int subspace_id)
+{
+ struct mpam_pcc_chan *cur;
+
+ guard(mutex)(&pcc_chan_list_lock);
+
+ list_for_each_entry(cur, &pcc_chan_list, pcc_chans) {
+ if (cur->subspace_id == subspace_id) {
+ kref_get(&cur->refcount);
+
+ return cur;
+ }
+ }
+
+ cur = kzalloc_obj(*cur);
+ if (!cur)
+ return ERR_PTR(-ENOMEM);
+
+ cur->pcc_cl.tx_block = true;
+
+ cur->pcc_chan = pcc_mbox_request_channel(&cur->pcc_cl, subspace_id);
+ if (IS_ERR(cur->pcc_chan)) {
+ long err = PTR_ERR(cur->pcc_chan);
+
+ kfree(cur);
+ return ERR_PTR(err);
+ }
+
+ /*
+ * Timeout based on the "nominal latency" in us, from the
+ * PCC ACPI table. tx_tout is in ms.
+ * Add some margin here to be on the safe side.
+ */
+ cur->pcc_cl.tx_tout = DIV_ROUND_UP(cur->pcc_chan->latency * 5, 1000);
+
+ mutex_init(&cur->pcc_chan_lock);
+
+ cur->subspace_id = subspace_id;
+ kref_init(&cur->refcount);
+
+ list_add_tail(&cur->pcc_chans, &pcc_chan_list);
+
+ return cur;
+}
+
+static int mpam_pcc_chan_put(struct mpam_pcc_chan *pcc_chan)
+{
+ struct mpam_pcc_chan *cur, *tmp;
+
+ if (!pcc_chan)
+ return 0;
+
+ guard(mutex)(&pcc_chan_list_lock);
+
+ list_for_each_entry_safe(cur, tmp, &pcc_chan_list, pcc_chans) {
+ if (cur == pcc_chan) {
+ kref_put(&cur->refcount, mpam_pcc_chan_release);
+
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
/*
* Number of MSCs that have been probed. Once all MSCs have been probed MPAM
* can be enabled.
@@ -2334,6 +2420,8 @@ static void mpam_msc_destroy(struct mpam_msc *msc)
mpam_ris_destroy(ris);
list_del_rcu(&msc->all_msc_list);
+ synchronize_srcu(&mpam_srcu);
+ mpam_pcc_chan_put(msc->pcc_chan);
platform_set_drvdata(pdev, NULL);
add_to_garbage(msc);
@@ -2356,7 +2444,7 @@ static void mpam_msc_drv_remove(struct platform_device *pdev)
static struct mpam_msc *do_mpam_msc_drv_probe(struct platform_device *pdev)
{
int err;
- u32 tmp;
+ u32 pcc_subspace_id;
struct mpam_msc *msc;
struct resource *msc_res;
struct device *dev = &pdev->dev;
@@ -2400,7 +2488,7 @@ static struct mpam_msc *do_mpam_msc_drv_probe(struct platform_device *pdev)
if (err)
return ERR_PTR(err);
- if (device_property_read_u32(&pdev->dev, "pcc-channel", &tmp))
+ if (device_property_read_u32(dev, "pcc-channel", &pcc_subspace_id))
msc->iface = MPAM_IFACE_MMIO;
else
msc->iface = MPAM_IFACE_PCC;
@@ -2421,6 +2509,33 @@ static struct mpam_msc *do_mpam_msc_drv_probe(struct platform_device *pdev)
}
msc->mapped_hwpage_sz = msc_res->end - msc_res->start;
msc->mapped_hwpage = io;
+ } else if (msc->iface == MPAM_IFACE_PCC) {
+ u32 mpam_fb_msc_id;
+
+ msc->pcc_chan = mpam_pcc_chan_get(dev, pcc_subspace_id);
+ if (IS_ERR(msc->pcc_chan)) {
+ pr_err("Failed to request MSC PCC channel\n");
+ return ERR_CAST(msc->pcc_chan);
+ }
+
+ err = mpam_fb_check_shared_buffer_size(msc);
+ if (err) {
+ mpam_pcc_chan_put(msc->pcc_chan);
+
+ return ERR_PTR(err);
+ }
+
+ err = mpam_fb_check_protocol_version(msc);
+ if (err) {
+ mpam_pcc_chan_put(msc->pcc_chan);
+
+ return ERR_PTR(err);
+ }
+
+ if (device_property_read_u32(&pdev->dev, "mpam-fb-msc-id",
+ &mpam_fb_msc_id))
+ mpam_fb_msc_id = msc->id;
+ msc->fb_id = mpam_fb_msc_id;
} else {
return ERR_PTR(-EINVAL);
}
diff --git a/drivers/resctrl/mpam_fb.c b/drivers/resctrl/mpam_fb.c
index 72f45e9582df..54834052cbde 100644
--- a/drivers/resctrl/mpam_fb.c
+++ b/drivers/resctrl/mpam_fb.c
@@ -20,10 +20,15 @@
#define MPAM_FB_PROTOCOL_ID 0x1a
#define MPAM_PROTOCOL_VERSION_CMD 0x0
+#define MPAM_FB_VERSION_MAJOR_MASK GENMASK_U32(31, 16)
+#define MPAM_FB_VERSION_MINOR_MASK GENMASK_U32(15, 0)
+
#define MPAM_MSC_READ_CMD 0x4
#define MPAM_MSC_WRITE_CMD 0x5
#define MPAM_FB_PROT_HEADER_LEN sizeof(u32)
+/* The longest message is MPAM_MSC_WRITE, with 4 parameters. */
+#define MPAM_FB_MAX_MSG_SIZE (4 * sizeof(u32))
#define MPAM_FB_SUCCESS 0
#define MPAM_FB_ERR_NOT_SUPPORTED -1
@@ -215,3 +220,39 @@ int mpam_fb_send_write_request(struct mpam_msc *msc, u16 reg, u32 value)
return mpam_fb_send_request(msc, msc->fb_id, reg, &value,
MPAM_MSC_WRITE_CMD);
}
+
+/* We only support MPAM-Fb protocol version 1.x */
+int mpam_fb_check_protocol_version(struct mpam_msc *msc)
+{
+ u32 version;
+ int ret;
+
+ ret = mpam_fb_send_request(msc, 0, 0, &version,
+ MPAM_PROTOCOL_VERSION_CMD);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(MPAM_FB_VERSION_MAJOR_MASK, version) != 1) {
+ pr_err("Incompatible MPAM-Fb protocol version %d.%d\n",
+ FIELD_GET(MPAM_FB_VERSION_MAJOR_MASK, version),
+ FIELD_GET(MPAM_FB_VERSION_MINOR_MASK, version));
+
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int mpam_fb_check_shared_buffer_size(struct mpam_msc *msc)
+{
+ int min_buffer_size = MPAM_FB_MAX_MSG_SIZE +
+ sizeof(struct acpi_pcct_ext_pcc_shared_memory);
+
+ if (msc->pcc_chan->pcc_chan->shmem_size < min_buffer_size) {
+ pr_err("MPAM-Fb PCC channel size too small.\n");
+
+ return -ENOMEM;
+ }
+
+ return 0;
+}
diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h
index 99057d6abee1..ee7c3953a9b6 100644
--- a/drivers/resctrl/mpam_internal.h
+++ b/drivers/resctrl/mpam_internal.h
@@ -538,6 +538,8 @@ static inline void mpam_resctrl_teardown_class(struct mpam_class *class) { }
/* MPAM-Fb Firmware-backed protocol wrappers */
int mpam_fb_send_read_request(struct mpam_msc *msc, u16 reg, u32 *result);
int mpam_fb_send_write_request(struct mpam_msc *msc, u16 reg, u32 value);
+int mpam_fb_check_protocol_version(struct mpam_msc *msc);
+int mpam_fb_check_shared_buffer_size(struct mpam_msc *msc);
/*
* MPAM MSCs have the following register layout. See:
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread* Re: [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4
2026-10-02 17:39 [GIT PULL/PATCH 00/27] MPAM driver changes for v7.4 James Morse
` (25 preceding siblings ...)
2026-10-02 17:39 ` [PATCH 27/27] arm_mpam: detect and enable MPAM-Fb PCC support James Morse
@ 2026-10-02 19:02 ` Catalin Marinas
26 siblings, 0 replies; 30+ messages in thread
From: Catalin Marinas @ 2026-10-02 19:02 UTC (permalink / raw)
To: James Morse
Cc: linux-arm-kernel, Will Deacon, Ben Horgan, Andre Przywara,
Fuad Tabba
On Fri, Oct 02, 2026 at 06:39:13PM +0100, James Morse wrote:
> The following changes since commit eb0fde98fee407118bfcb10e38400494e0699201:
>
> arm64: mpam: Document when to set arm64.nompam (2026-10-01 17:00:43 +0100)
>
> are available in the Git repository at:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/morse/linux.git tags/mpam/for-next/v7.4_v2
>
> for you to fetch changes up to cb351e3f304b260fc2f11cca5ed819fc9ac7c46d:
>
> arm_mpam: detect and enable MPAM-Fb PCC support (2026-10-02 18:27:11 +0100)
Merged into the arm64 for-next/mpam branch (b4 gives a python stack dump
when I tried to use 'ty').
https://git.kernel.org/arm64/c/5415b256badbe1681183e85f42462fa01cf09c83
Thanks!
--
Catalin
^ permalink raw reply [flat|nested] 30+ messages in thread