* [PATCH v2 5/8] riscv_cbqri: resctrl: Add cache allocation via capacity block mask
From: Drew Fustini @ 2026-06-25 1:38 UTC (permalink / raw)
To: Adrien Ricciardi, Alexandre Ghiti, Atish Kumar Patra, Atish Patra,
Babu Moger, Ben Horgan, Borislav Petkov, Chen Pei, Conor Dooley,
Conor Dooley, Dave Hansen, Dave Martin, Fenghua Yu, Gong Shuai,
Gong Shuai, guo.wenjia23, James Morse, Kornel Dulęba,
Krzysztof Kozlowski, liu.qingtao2, Liu Zhiwei, Palmer Dabbelt,
Paul Walmsley, Peter Newman, Radim Krčmář,
Reinette Chatre, Rob Herring, Samuel Holland,
Sebastian Andrzej Siewior, Tony Luck, Vasudevan Srinivasan,
Ved Shanbhogue, Weiwei Li, yunhui cui, Drew Fustini
Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>
Wire CBQRI capacity controllers into resctrl as RDT_RESOURCE_L2 and
RDT_RESOURCE_L3 schemata.
Mismatched CC caps at the same cache level are treated as a fatal
configuration error since fs/resctrl exposes a single per-rid cap
set. Domains are created lazily in the cpuhp online callback so
cpu_mask reflects only currently online CPUs.
Assisted-by: Claude:claude-opus-4-7
Co-developed-by: Adrien Ricciardi <aricciardi@baylibre.com>
Signed-off-by: Adrien Ricciardi <aricciardi@baylibre.com>
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
MAINTAINERS | 2 +
arch/riscv/include/asm/resctrl.h | 147 ++++++++
drivers/resctrl/Kconfig | 4 +
drivers/resctrl/Makefile | 1 +
drivers/resctrl/cbqri_resctrl.c | 779 +++++++++++++++++++++++++++++++++++++++
5 files changed, 933 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 811c0c9b1fac806945cad0229c5330654420a835..9e1092165046c773771b055869030bc1bdb64b16 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23299,9 +23299,11 @@ R: yunhui cui <cuiyunhui@bytedance.com>
L: linux-riscv@lists.infradead.org
S: Supported
F: arch/riscv/include/asm/qos.h
+F: arch/riscv/include/asm/resctrl.h
F: arch/riscv/kernel/qos.c
F: drivers/resctrl/cbqri_devices.c
F: drivers/resctrl/cbqri_internal.h
+F: drivers/resctrl/cbqri_resctrl.c
F: include/linux/riscv_cbqri.h
RISC-V RPMI AND MPXY DRIVERS
diff --git a/arch/riscv/include/asm/resctrl.h b/arch/riscv/include/asm/resctrl.h
new file mode 100644
index 0000000000000000000000000000000000000000..b08f4e12f7aa98663de75ec073ccb8090594583e
--- /dev/null
+++ b/arch/riscv/include/asm/resctrl.h
@@ -0,0 +1,147 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _ASM_RISCV_RESCTRL_H
+#define _ASM_RISCV_RESCTRL_H
+
+#include <linux/resctrl_types.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+
+#include <asm/qos.h>
+
+struct rdt_resource;
+
+/*
+ * Sentinel "no CLOSID assigned" used by resctrl_arch_rmid_idx_decode().
+ * fs/resctrl treats this opaquely. CBQRI uses MCID directly as the linear
+ * rmid index, so closid is unused on decode.
+ */
+#define RISCV_RESCTRL_EMPTY_CLOSID ((u32)~0)
+
+/*
+ * Terminology mapping between x86 (Intel RDT/AMD QoS) and RISC-V:
+ *
+ * CLOSID on x86 is RCID on RISC-V
+ * RMID on x86 is MCID on RISC-V
+ * CDP on x86 is AT (access type) on RISC-V
+ */
+
+/**
+ * resctrl_arch_alloc_capable() - any CBQRI controller exposes resctrl alloc
+ *
+ * Returns true once at least one CBQRI controller has successfully probed for
+ * a resctrl-exposed cache capacity allocation feature. Only meaningful after
+ * cbqri_resctrl_setup() runs at late_initcall.
+ */
+bool resctrl_arch_alloc_capable(void);
+
+/**
+ * resctrl_arch_mon_capable() - any CBQRI controller exposes resctrl monitoring
+ *
+ * The CBQRI driver implements capacity allocation only and wires up no
+ * monitoring events, so this always returns false. fs/resctrl references it
+ * unconditionally, hence the stub.
+ */
+bool resctrl_arch_mon_capable(void);
+
+/**
+ * resctrl_arch_rmid_idx_encode() - encode (RCID, MCID) into a linear index
+ * @closid: RCID (resource control id)
+ * @rmid: MCID (monitoring counter id)
+ *
+ * RISC-V uses MCID directly as the linear index into per-RMID arrays
+ * managed by fs/resctrl, since CBQRI controllers admit any MCID for any
+ * RCID. closid is unused here. CDP is encoded via the AT field on each
+ * CBQRI op rather than via the index.
+ */
+u32 resctrl_arch_rmid_idx_encode(u32 closid, u32 rmid);
+
+/**
+ * resctrl_arch_rmid_idx_decode() - inverse of resctrl_arch_rmid_idx_encode()
+ * @idx: linear index
+ * @closid: out: always RISCV_RESCTRL_EMPTY_CLOSID
+ * @rmid: out: the MCID that @idx encodes
+ */
+void resctrl_arch_rmid_idx_decode(u32 idx, u32 *closid, u32 *rmid);
+
+/**
+ * resctrl_arch_set_cpu_default_closid_rmid() - install per-CPU srmcfg default
+ * @cpu: CPU number
+ * @closid: RCID to use when no task is matched
+ * @rmid: MCID to use when no task is matched
+ *
+ * Sets the per-CPU cpu_srmcfg_default so __switch_to_srmcfg() can fall back
+ * to the CPU's default RCID/MCID for default-group tasks (those whose
+ * thread.srmcfg encodes to 0, i.e. closid == RESCTRL_RESERVED_CLOSID and
+ * rmid == RESCTRL_RESERVED_RMID). Implements resctrl allocation rule 2
+ * ("CPU default") on RISC-V.
+ */
+void resctrl_arch_set_cpu_default_closid_rmid(int cpu, u32 closid, u32 rmid);
+
+/**
+ * resctrl_arch_sched_in() - context-switch hook to install task RCID/MCID
+ * @tsk: the task being scheduled in
+ *
+ * Called from finish_task_switch() to write tsk->thread.srmcfg into the
+ * srmcfg CSR. Tasks tagged with RISCV_RESCTRL_EMPTY_CLOSID inherit the
+ * per-CPU default set via resctrl_arch_set_cpu_default_closid_rmid().
+ */
+void resctrl_arch_sched_in(struct task_struct *tsk);
+
+/**
+ * resctrl_arch_set_closid_rmid() - tag a task with an RCID/MCID
+ * @tsk: task to tag
+ * @closid: RCID to install
+ * @rmid: MCID to install
+ *
+ * Updates tsk->thread.srmcfg with the encoded (RCID, MCID) pair. The new
+ * value takes effect on the next resctrl_arch_sched_in() for this task.
+ */
+void resctrl_arch_set_closid_rmid(struct task_struct *tsk, u32 closid, u32 rmid);
+
+/**
+ * resctrl_arch_match_closid() - test whether a task carries a given RCID
+ * @tsk: task
+ * @closid: RCID
+ */
+bool resctrl_arch_match_closid(struct task_struct *tsk, u32 closid);
+
+/**
+ * resctrl_arch_match_rmid() - test whether a task carries a given (RCID, MCID)
+ * @tsk: task
+ * @closid: RCID
+ * @rmid: MCID
+ */
+bool resctrl_arch_match_rmid(struct task_struct *tsk, u32 closid, u32 rmid);
+
+/**
+ * resctrl_arch_mon_ctx_alloc() - allocate per-monitor-event arch context
+ * @r: resctrl resource being monitored
+ * @evtid: which monitor event needs context
+ *
+ * The CBQRI driver implements no monitoring events, so there is no per-event
+ * context to allocate and the stub returns NULL. fs/resctrl references it
+ * unconditionally before checking resctrl_arch_mon_capable().
+ */
+void *resctrl_arch_mon_ctx_alloc(struct rdt_resource *r, enum resctrl_event_id evtid);
+
+/**
+ * resctrl_arch_mon_ctx_free() - release context returned by mon_ctx_alloc()
+ * @r: resctrl resource
+ * @evtid: monitor event id
+ * @arch_mon_ctx: pointer returned by resctrl_arch_mon_ctx_alloc()
+ */
+void resctrl_arch_mon_ctx_free(struct rdt_resource *r, enum resctrl_event_id evtid,
+ void *arch_mon_ctx);
+
+static inline unsigned int resctrl_arch_round_mon_val(unsigned int val)
+{
+ return val;
+}
+
+/* Not needed for RISC-V */
+static inline void resctrl_arch_enable_mon(void) { }
+static inline void resctrl_arch_disable_mon(void) { }
+static inline void resctrl_arch_enable_alloc(void) { }
+static inline void resctrl_arch_disable_alloc(void) { }
+
+#endif /* _ASM_RISCV_RESCTRL_H */
diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig
index 92b9c82cf9f397437d28006e79d40c2d9b384eb0..f8566c003d49570b844908d57c231d73c3bb0f6e 100644
--- a/drivers/resctrl/Kconfig
+++ b/drivers/resctrl/Kconfig
@@ -42,3 +42,7 @@ menuconfig RISCV_CBQRI
if RISCV_CBQRI
endif
+
+config RISCV_CBQRI_RESCTRL_FS
+ bool
+ default y if RISCV_CBQRI && RESCTRL_FS
diff --git a/drivers/resctrl/Makefile b/drivers/resctrl/Makefile
index 4d8a2c4b5627144a651da007174d4d9df6171330..a7631712dba9e1c9dd2a0b07a089204671f85d1f 100644
--- a/drivers/resctrl/Makefile
+++ b/drivers/resctrl/Makefile
@@ -6,3 +6,4 @@ ccflags-$(CONFIG_ARM64_MPAM_DRIVER_DEBUG) += -DDEBUG
obj-$(CONFIG_RISCV_CBQRI) += cbqri.o
cbqri-y += cbqri_devices.o
+cbqri-$(CONFIG_RISCV_CBQRI_RESCTRL_FS) += cbqri_resctrl.o
diff --git a/drivers/resctrl/cbqri_resctrl.c b/drivers/resctrl/cbqri_resctrl.c
new file mode 100644
index 0000000000000000000000000000000000000000..2f31baff032b61cbbe9f7a67a85e18f6fb90f7a4
--- /dev/null
+++ b/drivers/resctrl/cbqri_resctrl.c
@@ -0,0 +1,779 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
+
+#include <linux/bitfield.h>
+#include <linux/cacheinfo.h>
+#include <linux/riscv_cbqri.h>
+#include <linux/cpu.h>
+#include <linux/cpufeature.h>
+#include <linux/cpuhotplug.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/resctrl.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <asm/csr.h>
+#include <asm/qos.h>
+
+#include "cbqri_internal.h"
+
+struct cbqri_resctrl_res {
+ struct cbqri_controller *ctrl;
+ struct rdt_resource resctrl_res;
+ bool cdp_enabled;
+};
+
+struct cbqri_resctrl_dom {
+ struct rdt_ctrl_domain resctrl_ctrl_dom;
+ struct cbqri_controller *hw_ctrl;
+};
+
+static struct cbqri_resctrl_res cbqri_resctrl_resources[RDT_NUM_RESOURCES];
+
+static bool exposed_alloc_capable;
+
+/* Protects ctrl_domain list mutations across CPU hotplug. */
+static DEFINE_MUTEX(cbqri_domain_list_lock);
+
+static struct rdt_ctrl_domain *
+cbqri_find_ctrl_domain(struct list_head *h, int id)
+{
+ struct rdt_domain_hdr *hdr = resctrl_find_domain(h, id, NULL);
+
+ return hdr ? container_of(hdr, struct rdt_ctrl_domain, hdr) : NULL;
+}
+
+/* Map a hardware cache level to its resctrl resource id, or -ENODEV. */
+static int cbqri_cache_level_to_rid(u32 cache_level)
+{
+ switch (cache_level) {
+ case 2:
+ return RDT_RESOURCE_L2;
+ case 3:
+ return RDT_RESOURCE_L3;
+ default:
+ return -ENODEV;
+ }
+}
+
+static int cbqri_apply_cache_config_dom(struct cbqri_resctrl_dom *hw_dom,
+ struct rdt_resource *r,
+ u32 closid, enum resctrl_conf_type t,
+ u64 cbm)
+{
+ struct cbqri_resctrl_res *hw_res =
+ container_of(r, struct cbqri_resctrl_res, resctrl_res);
+ struct cbqri_cc_config cfg = {
+ .cbm = cbm,
+ .at = (t == CDP_CODE) ? CBQRI_CONTROL_REGISTERS_AT_CODE :
+ CBQRI_CONTROL_REGISTERS_AT_DATA,
+ .cdp_enabled = hw_res->cdp_enabled,
+ };
+
+ return cbqri_apply_cache_config(hw_dom->hw_ctrl, closid, &cfg);
+}
+
+bool resctrl_arch_alloc_capable(void)
+{
+ return exposed_alloc_capable;
+}
+
+bool resctrl_arch_mon_capable(void)
+{
+ return false;
+}
+
+bool resctrl_arch_get_cdp_enabled(enum resctrl_res_level rid)
+{
+ if (rid != RDT_RESOURCE_L2 && rid != RDT_RESOURCE_L3)
+ return false;
+ return cbqri_resctrl_resources[rid].cdp_enabled;
+}
+
+int resctrl_arch_set_cdp_enabled(enum resctrl_res_level rid, bool enable)
+{
+ struct cbqri_resctrl_res *cbqri_res;
+
+ if (rid != RDT_RESOURCE_L2 && rid != RDT_RESOURCE_L3)
+ return -ENODEV;
+
+ cbqri_res = &cbqri_resctrl_resources[rid];
+ if (!cbqri_res->resctrl_res.cdp_capable)
+ return -ENODEV;
+
+ cbqri_res->cdp_enabled = enable;
+ return 0;
+}
+
+struct rdt_resource *resctrl_arch_get_resource(enum resctrl_res_level l)
+{
+ if (l >= RDT_NUM_RESOURCES)
+ return NULL;
+
+ return &cbqri_resctrl_resources[l].resctrl_res;
+}
+
+/*
+ * fs/resctrl unconditionally references the symbols below before checking
+ * mon_capable. They are stubs for features CBQRI does not yet support.
+ */
+bool resctrl_arch_is_evt_configurable(enum resctrl_event_id evt)
+{
+ return false;
+}
+
+void *resctrl_arch_mon_ctx_alloc(struct rdt_resource *r,
+ enum resctrl_event_id evtid)
+{
+ return NULL;
+}
+
+void resctrl_arch_mon_ctx_free(struct rdt_resource *r,
+ enum resctrl_event_id evtid, void *arch_mon_ctx)
+{
+}
+
+void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ enum resctrl_event_id evtid, u32 rmid, u32 closid,
+ u32 cntr_id, bool assign)
+{
+}
+
+int resctrl_arch_cntr_read(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ u32 unused, u32 rmid, int cntr_id,
+ enum resctrl_event_id eventid, u64 *val)
+{
+ return -EOPNOTSUPP;
+}
+
+bool resctrl_arch_mbm_cntr_assign_enabled(struct rdt_resource *r)
+{
+ return false;
+}
+
+int resctrl_arch_mbm_cntr_assign_set(struct rdt_resource *r, bool enable)
+{
+ return -EOPNOTSUPP;
+}
+
+void resctrl_arch_reset_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ u32 unused, u32 rmid, int cntr_id,
+ enum resctrl_event_id eventid)
+{
+}
+
+bool resctrl_arch_get_io_alloc_enabled(struct rdt_resource *r)
+{
+ return false;
+}
+
+int resctrl_arch_io_alloc_enable(struct rdt_resource *r, bool enable)
+{
+ return -EOPNOTSUPP;
+}
+
+void resctrl_arch_mon_event_config_read(void *info)
+{
+}
+
+void resctrl_arch_mon_event_config_write(void *info)
+{
+}
+
+void resctrl_arch_reset_rmid_all(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+{
+}
+
+void resctrl_arch_reset_rmid(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ u32 unused, u32 rmid, enum resctrl_event_id eventid)
+{
+}
+
+int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_domain_hdr *hdr,
+ u32 closid, u32 rmid, enum resctrl_event_id eventid,
+ void *arch_priv, u64 *val, void *arch_mon_ctx)
+{
+ return -ENODATA;
+}
+
+/*
+ * Note about terminology between x86 (Intel RDT/AMD QoS) and RISC-V:
+ * CLOSID on x86 is RCID on RISC-V
+ * RMID on x86 is MCID on RISC-V
+ */
+u32 resctrl_arch_get_num_closid(struct rdt_resource *res)
+{
+ struct cbqri_resctrl_res *hw_res;
+
+ hw_res = container_of(res, struct cbqri_resctrl_res, resctrl_res);
+
+ if (!hw_res->ctrl)
+ return 0;
+
+ return hw_res->ctrl->rcid_count;
+}
+
+u32 resctrl_arch_system_num_rmid_idx(void)
+{
+ return 1;
+}
+
+u32 resctrl_arch_rmid_idx_encode(u32 closid, u32 rmid)
+{
+ return rmid;
+}
+
+void resctrl_arch_rmid_idx_decode(u32 idx, u32 *closid, u32 *rmid)
+{
+ *closid = RISCV_RESCTRL_EMPTY_CLOSID;
+ *rmid = idx;
+}
+
+void resctrl_arch_set_cpu_default_closid_rmid(int cpu, u32 closid, u32 rmid)
+{
+ u32 srmcfg = FIELD_PREP(SRMCFG_RCID_MASK, closid) |
+ FIELD_PREP(SRMCFG_MCID_MASK, rmid);
+
+ WRITE_ONCE(per_cpu(cpu_srmcfg_default, cpu), srmcfg);
+}
+
+void resctrl_arch_sched_in(struct task_struct *tsk)
+{
+ __switch_to_srmcfg(tsk);
+}
+
+void resctrl_arch_set_closid_rmid(struct task_struct *tsk, u32 closid, u32 rmid)
+{
+ u32 srmcfg = FIELD_PREP(SRMCFG_RCID_MASK, closid) |
+ FIELD_PREP(SRMCFG_MCID_MASK, rmid);
+
+ WRITE_ONCE(tsk->thread.srmcfg, srmcfg);
+}
+
+void resctrl_arch_sync_cpu_closid_rmid(void *info)
+{
+ struct resctrl_cpu_defaults *r = info;
+
+ lockdep_assert_preemption_disabled();
+
+ if (r) {
+ resctrl_arch_set_cpu_default_closid_rmid(smp_processor_id(),
+ r->closid, r->rmid);
+ }
+
+ resctrl_arch_sched_in(current);
+}
+
+bool resctrl_arch_match_closid(struct task_struct *tsk, u32 closid)
+{
+ return FIELD_GET(SRMCFG_RCID_MASK, READ_ONCE(tsk->thread.srmcfg)) == closid;
+}
+
+bool resctrl_arch_match_rmid(struct task_struct *tsk, u32 closid, u32 rmid)
+{
+ return FIELD_GET(SRMCFG_MCID_MASK, READ_ONCE(tsk->thread.srmcfg)) == rmid;
+}
+
+void resctrl_arch_pre_mount(void)
+{
+ /* All controllers discovered at boot via late_initcall. Nothing to do. */
+}
+
+int resctrl_arch_update_one(struct rdt_resource *r, struct rdt_ctrl_domain *d,
+ u32 closid, enum resctrl_conf_type t, u32 cfg_val)
+{
+ struct cbqri_resctrl_dom *dom;
+
+ dom = container_of(d, struct cbqri_resctrl_dom, resctrl_ctrl_dom);
+
+ if (!r->alloc_capable)
+ return -EINVAL;
+
+ switch (r->rid) {
+ case RDT_RESOURCE_L2:
+ case RDT_RESOURCE_L3:
+ return cbqri_apply_cache_config_dom(dom, r, closid, t, cfg_val);
+ default:
+ return -EINVAL;
+ }
+}
+
+int resctrl_arch_update_domains(struct rdt_resource *r, u32 closid)
+{
+ struct resctrl_staged_config *cfg;
+ enum resctrl_conf_type t;
+ struct rdt_ctrl_domain *d;
+ int err = 0;
+
+ /* Walking r->ctrl_domains, ensure it can't race with cpuhp */
+ lockdep_assert_cpus_held();
+
+ list_for_each_entry(d, &r->ctrl_domains, hdr.list) {
+ for (t = 0; t < CDP_NUM_TYPES; t++) {
+ cfg = &d->staged_config[t];
+ if (!cfg->have_new_ctrl)
+ continue;
+ err = resctrl_arch_update_one(r, d, closid, t, cfg->new_ctrl);
+ if (err)
+ return err;
+ }
+ }
+ return err;
+}
+
+u32 resctrl_arch_get_config(struct rdt_resource *r, struct rdt_ctrl_domain *d,
+ u32 closid, enum resctrl_conf_type type)
+{
+ struct cbqri_resctrl_dom *hw_dom;
+ struct cbqri_controller *ctrl;
+ u32 at;
+ u32 val;
+ int err;
+
+ hw_dom = container_of(d, struct cbqri_resctrl_dom, resctrl_ctrl_dom);
+ ctrl = hw_dom->hw_ctrl;
+ val = resctrl_get_default_ctrl(r);
+
+ if (!r->alloc_capable)
+ return val;
+
+ switch (r->rid) {
+ case RDT_RESOURCE_L2:
+ case RDT_RESOURCE_L3:
+ at = (type == CDP_CODE) ? CBQRI_CONTROL_REGISTERS_AT_CODE :
+ CBQRI_CONTROL_REGISTERS_AT_DATA;
+ err = cbqri_read_cache_config(ctrl, closid, at, &val);
+ if (err < 0)
+ val = resctrl_get_default_ctrl(r);
+ break;
+ default:
+ break;
+ }
+
+ return val;
+}
+
+void resctrl_arch_reset_all_ctrls(struct rdt_resource *r)
+{
+ struct cbqri_resctrl_res *hw_res;
+ struct rdt_ctrl_domain *d;
+ enum resctrl_conf_type t;
+ u32 default_ctrl;
+ int i;
+
+ lockdep_assert_cpus_held();
+
+ hw_res = container_of(r, struct cbqri_resctrl_res, resctrl_res);
+ default_ctrl = resctrl_get_default_ctrl(r);
+
+ if (!hw_res->ctrl)
+ return;
+
+ list_for_each_entry(d, &r->ctrl_domains, hdr.list) {
+ for (i = 0; i < hw_res->ctrl->rcid_count; i++) {
+ for (t = 0; t < CDP_NUM_TYPES; t++) {
+ int rerr;
+
+ rerr = resctrl_arch_update_one(r, d, i, t, default_ctrl);
+ if (rerr)
+ pr_err_ratelimited("rid=%d reset RCID %u type %u failed (%d)\n",
+ r->rid, i, t, rerr);
+ }
+ }
+ }
+}
+
+static struct rdt_ctrl_domain *cbqri_new_domain(struct cbqri_controller *ctrl)
+{
+ struct cbqri_resctrl_dom *hw_dom;
+ struct rdt_ctrl_domain *domain;
+
+ hw_dom = kzalloc_obj(*hw_dom, GFP_KERNEL);
+ if (!hw_dom)
+ return NULL;
+
+ hw_dom->hw_ctrl = ctrl;
+ domain = &hw_dom->resctrl_ctrl_dom;
+
+ INIT_LIST_HEAD(&domain->hdr.list);
+
+ return domain;
+}
+
+static int cbqri_init_domain_ctrlval(struct rdt_resource *r, struct rdt_ctrl_domain *d)
+{
+ struct cbqri_resctrl_res *hw_res;
+ enum resctrl_conf_type t;
+ int err = 0;
+ int i;
+
+ hw_res = container_of(r, struct cbqri_resctrl_res, resctrl_res);
+
+ for (i = 0; i < hw_res->ctrl->rcid_count; i++) {
+ /*
+ * Seed both DATA and CODE staged slots so a later mount
+ * with -o cdp does not see stale CODE values.
+ * On non-AT controllers cbqri_cc_alloc_op() masks AT to 0
+ * so all three iterations land on the same hardware state.
+ * The redundant writes are harmless.
+ */
+ for (t = 0; t < CDP_NUM_TYPES; t++) {
+ err = resctrl_arch_update_one(r, d, i, t,
+ resctrl_get_default_ctrl(r));
+ if (err)
+ return err;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Walk cbqri_controllers and pick one capacity controller (CC) per cache
+ * level (L2/L3) to back the corresponding RDT_RESOURCE_L*. When more than
+ * one CC sits at the same level (e.g. one per socket), they must agree on
+ * rcid_count / ncblks / alloc_capable. A mismatch is fatal because resctrl
+ * exposes a single set of caps per rid. The first matching controller wins.
+ */
+static int cbqri_resctrl_pick_caches(void)
+{
+ struct cbqri_controller *ctrl;
+ int ret = 0;
+
+ mutex_lock(&cbqri_controllers_lock);
+
+ list_for_each_entry(ctrl, &cbqri_controllers, list) {
+ struct cbqri_resctrl_res *cbqri_res;
+ int rid;
+
+ if (ctrl->type != CBQRI_CONTROLLER_TYPE_CAPACITY)
+ continue;
+ if (!ctrl->alloc_capable)
+ continue;
+
+ rid = cbqri_cache_level_to_rid(ctrl->cache.cache_level);
+ if (rid < 0) {
+ pr_info("skipping controller at unsupported cache level %u\n",
+ ctrl->cache.cache_level);
+ continue;
+ }
+
+ cbqri_res = &cbqri_resctrl_resources[rid];
+ if (cbqri_res->ctrl) {
+ /*
+ * CCs at the same cache level must agree on every cap
+ * resctrl exposes globally. Reject mismatches at pick
+ * time so the inconsistency is visible at boot.
+ */
+ if (cbqri_res->ctrl->rcid_count != ctrl->rcid_count ||
+ cbqri_res->ctrl->cc.ncblks != ctrl->cc.ncblks ||
+ cbqri_res->ctrl->cc.supports_alloc_at_code !=
+ ctrl->cc.supports_alloc_at_code ||
+ cbqri_res->ctrl->alloc_capable != ctrl->alloc_capable) {
+ pr_err("L%d controllers have mismatched capabilities\n",
+ ctrl->cache.cache_level);
+ ret = -EINVAL;
+ break;
+ }
+ continue;
+ }
+
+ cbqri_res->ctrl = ctrl;
+ }
+
+ mutex_unlock(&cbqri_controllers_lock);
+ return ret;
+}
+
+/*
+ * Fill the rdt_resource fields for one picked rid. An rid with no picked
+ * controller is left untouched so it stays out of resctrl_arch_get_resource().
+ */
+static void cbqri_resctrl_control_init(struct cbqri_resctrl_res *cbqri_res)
+{
+ struct cbqri_controller *ctrl = cbqri_res->ctrl;
+ struct rdt_resource *res = &cbqri_res->resctrl_res;
+
+ if (!ctrl)
+ return;
+
+ switch (res->rid) {
+ case RDT_RESOURCE_L2:
+ case RDT_RESOURCE_L3:
+ res->name = (res->rid == RDT_RESOURCE_L2) ? "L2" : "L3";
+ res->schema_fmt = RESCTRL_SCHEMA_BITMAP;
+ res->ctrl_scope = (res->rid == RDT_RESOURCE_L2) ?
+ RESCTRL_L2_CACHE : RESCTRL_L3_CACHE;
+ res->cache.cbm_len = ctrl->cc.ncblks;
+ res->cache.shareable_bits = 0;
+ res->cache.min_cbm_bits = 1;
+ res->cache.arch_has_sparse_bitmasks = false;
+ res->cdp_capable = ctrl->cc.supports_alloc_at_code;
+ res->alloc_capable = ctrl->alloc_capable;
+ INIT_LIST_HEAD(&res->ctrl_domains);
+ INIT_LIST_HEAD(&res->mon_domains);
+ break;
+ default:
+ break;
+ }
+}
+
+static void cbqri_resctrl_accumulate_caps(void)
+{
+ int rid;
+
+ for (rid = 0; rid < RDT_NUM_RESOURCES; rid++) {
+ struct cbqri_resctrl_res *hw_res = &cbqri_resctrl_resources[rid];
+
+ if (!hw_res->ctrl)
+ continue;
+ if (hw_res->ctrl->alloc_capable)
+ exposed_alloc_capable = true;
+ }
+}
+
+/*
+ * Create, list-insert, and online a fresh ctrl_domain backing ctrl on
+ * resource res, seeded with cpu and identified by dom_id. Caller must
+ * hold cbqri_domain_list_lock and must have already verified that no
+ * existing ctrl_domain on res carries this id.
+ */
+static struct rdt_ctrl_domain *cbqri_create_ctrl_domain(struct cbqri_controller *ctrl,
+ struct rdt_resource *res,
+ unsigned int cpu, int dom_id)
+{
+ struct rdt_ctrl_domain *domain;
+ struct list_head *pos = NULL;
+ int err;
+
+ domain = cbqri_new_domain(ctrl);
+ if (!domain)
+ return ERR_PTR(-ENOMEM);
+
+ cpumask_set_cpu(cpu, &domain->hdr.cpu_mask);
+ domain->hdr.id = dom_id;
+ domain->hdr.type = RESCTRL_CTRL_DOMAIN;
+
+ err = cbqri_init_domain_ctrlval(res, domain);
+ if (err) {
+ kfree(container_of(domain, struct cbqri_resctrl_dom,
+ resctrl_ctrl_dom));
+ return ERR_PTR(err);
+ }
+
+ /* Insert sorted by id so user-visible ordering is deterministic. */
+ resctrl_find_domain(&res->ctrl_domains, dom_id, &pos);
+ list_add_tail(&domain->hdr.list, pos);
+
+ resctrl_online_ctrl_domain(res, domain);
+
+ return domain;
+}
+
+static int cbqri_attach_cpu_to_cap_ctrl(struct cbqri_controller *ctrl,
+ unsigned int cpu)
+{
+ struct cbqri_resctrl_res *hw_res;
+ struct rdt_ctrl_domain *domain;
+ struct rdt_resource *res;
+ int dom_id;
+ int rid;
+
+ rid = cbqri_cache_level_to_rid(ctrl->cache.cache_level);
+ if (rid < 0)
+ return 0;
+ hw_res = &cbqri_resctrl_resources[rid];
+
+ if (!hw_res->ctrl)
+ return 0;
+
+ res = &hw_res->resctrl_res;
+ dom_id = ctrl->cache.cache_id;
+
+ domain = cbqri_find_ctrl_domain(&res->ctrl_domains, dom_id);
+ if (domain) {
+ cpumask_set_cpu(cpu, &domain->hdr.cpu_mask);
+ return 0;
+ }
+
+ domain = cbqri_create_ctrl_domain(ctrl, res, cpu, dom_id);
+ if (IS_ERR(domain))
+ return PTR_ERR(domain);
+
+ return 0;
+}
+
+static void cbqri_detach_cpu_from_ctrl_domains(struct rdt_resource *res,
+ unsigned int cpu)
+{
+ struct rdt_ctrl_domain *domain, *tmp;
+
+ list_for_each_entry_safe(domain, tmp, &res->ctrl_domains, hdr.list) {
+ if (!cpumask_test_cpu(cpu, &domain->hdr.cpu_mask))
+ continue;
+ cpumask_clear_cpu(cpu, &domain->hdr.cpu_mask);
+ if (cpumask_empty(&domain->hdr.cpu_mask)) {
+ resctrl_offline_ctrl_domain(res, domain);
+ list_del(&domain->hdr.list);
+ kfree(container_of(domain, struct cbqri_resctrl_dom,
+ resctrl_ctrl_dom));
+ }
+ }
+}
+
+/*
+ * Remove a CPU from every domain it was attached to. The per-resource
+ * detach helpers act only when the CPU is set in a domain's mask, so this
+ * is idempotent and undoes a partial online attach as well as a full
+ * offline. Caller holds cbqri_domain_list_lock.
+ */
+static void cbqri_detach_cpu_from_all_ctrls(unsigned int cpu)
+{
+ int rid;
+
+ lockdep_assert_held(&cbqri_domain_list_lock);
+
+ for (rid = 0; rid < RDT_NUM_RESOURCES; rid++) {
+ struct cbqri_resctrl_res *hw_res = &cbqri_resctrl_resources[rid];
+
+ if (!hw_res->ctrl)
+ continue;
+ cbqri_detach_cpu_from_ctrl_domains(&hw_res->resctrl_res, cpu);
+ }
+}
+
+/*
+ * Attach a CPU to every controller that claims it. On failure, detach the
+ * CPU from everything attached so far: the cpuhp core does not run this
+ * state's offline teardown when its startup fails, so a partial attach
+ * would otherwise leak into the domain cpu_masks. Caller holds
+ * cbqri_domain_list_lock.
+ */
+static int cbqri_attach_cpu_to_all_ctrls(unsigned int cpu)
+{
+ struct cbqri_controller *ctrl;
+ int err = 0;
+
+ lockdep_assert_held(&cbqri_domain_list_lock);
+
+ list_for_each_entry(ctrl, &cbqri_controllers, list) {
+ if (ctrl->type != CBQRI_CONTROLLER_TYPE_CAPACITY)
+ continue;
+ if (!cpumask_test_cpu(cpu, &ctrl->cache.cpu_mask))
+ continue;
+ if (!ctrl->alloc_capable)
+ continue;
+
+ err = cbqri_attach_cpu_to_cap_ctrl(ctrl, cpu);
+ if (err) {
+ cbqri_detach_cpu_from_all_ctrls(cpu);
+ break;
+ }
+ }
+
+ return err;
+}
+
+static bool cbqri_resctrl_inited;
+
+static void cbqri_resctrl_teardown(void)
+{
+ int rid;
+
+ if (!cbqri_resctrl_inited)
+ return;
+
+ resctrl_exit();
+
+ for (rid = 0; rid < RDT_NUM_RESOURCES; rid++) {
+ struct cbqri_resctrl_res *hw_res = &cbqri_resctrl_resources[rid];
+
+ hw_res->ctrl = NULL;
+ hw_res->cdp_enabled = false;
+ }
+ exposed_alloc_capable = false;
+ cbqri_resctrl_inited = false;
+}
+
+static int cbqri_resctrl_setup(void)
+{
+ int rid;
+ int err;
+
+ for (rid = 0; rid < RDT_NUM_RESOURCES; rid++)
+ cbqri_resctrl_resources[rid].resctrl_res.rid = rid;
+
+ err = cbqri_resctrl_pick_caches();
+ if (err)
+ return err;
+
+ for (rid = 0; rid < RDT_NUM_RESOURCES; rid++)
+ cbqri_resctrl_control_init(&cbqri_resctrl_resources[rid]);
+
+ cbqri_resctrl_accumulate_caps();
+
+ if (!exposed_alloc_capable) {
+ pr_debug("no resctrl-capable CBQRI controllers found\n");
+ return -ENODEV;
+ }
+
+ err = resctrl_init();
+ if (err)
+ return err;
+
+ cbqri_resctrl_inited = true;
+ return 0;
+}
+
+static int cbqri_resctrl_online_cpu(unsigned int cpu)
+{
+ int err;
+
+ mutex_lock(&cbqri_domain_list_lock);
+ err = cbqri_attach_cpu_to_all_ctrls(cpu);
+ mutex_unlock(&cbqri_domain_list_lock);
+ if (err)
+ return err;
+
+ /*
+ * Seed the per-CPU default RCID/MCID to the reserved (0, 0) pair and
+ * notify the resctrl core so it tracks this CPU in the default group.
+ */
+ resctrl_arch_set_cpu_default_closid_rmid(cpu, 0, 0);
+ resctrl_online_cpu(cpu);
+ return 0;
+}
+
+static int cbqri_resctrl_offline_cpu(unsigned int cpu)
+{
+ resctrl_offline_cpu(cpu);
+
+ mutex_lock(&cbqri_domain_list_lock);
+ cbqri_detach_cpu_from_all_ctrls(cpu);
+ mutex_unlock(&cbqri_domain_list_lock);
+ return 0;
+}
+
+static int __init cbqri_arch_late_init(void)
+{
+ int err;
+
+ if (!riscv_isa_extension_available(NULL, SSQOSID))
+ return -ENODEV;
+
+ err = cbqri_resctrl_setup();
+ if (err)
+ return err;
+
+ err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "cbqri:online",
+ cbqri_resctrl_online_cpu,
+ cbqri_resctrl_offline_cpu);
+ if (err < 0) {
+ cbqri_resctrl_teardown();
+ return err;
+ }
+
+ return 0;
+}
+late_initcall(cbqri_arch_late_init);
--
2.34.1
^ permalink raw reply related
* [PATCH v2 4/8] riscv_cbqri: Add capacity controller probe and allocation device ops
From: Drew Fustini @ 2026-06-25 1:38 UTC (permalink / raw)
To: Adrien Ricciardi, Alexandre Ghiti, Atish Kumar Patra, Atish Patra,
Babu Moger, Ben Horgan, Borislav Petkov, Chen Pei, Conor Dooley,
Conor Dooley, Dave Hansen, Dave Martin, Fenghua Yu, Gong Shuai,
Gong Shuai, guo.wenjia23, James Morse, Kornel Dulęba,
Krzysztof Kozlowski, liu.qingtao2, Liu Zhiwei, Palmer Dabbelt,
Paul Walmsley, Peter Newman, Radim Krčmář,
Reinette Chatre, Rob Herring, Samuel Holland,
Sebastian Andrzej Siewior, Tony Luck, Vasudevan Srinivasan,
Ved Shanbhogue, Weiwei Li, yunhui cui, Drew Fustini
Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>
Add support for the RISC-V CBQRI capacity controller. A platform driver
passes a cbqri_controller_info descriptor together with the cache level
to riscv_cbqri_register_cc_dt(), which probes the controller and adds it
to the controller list.
Assisted-by: Claude:claude-opus-4-7
Co-developed-by: Adrien Ricciardi <aricciardi@baylibre.com>
Signed-off-by: Adrien Ricciardi <aricciardi@baylibre.com>
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
MAINTAINERS | 3 +
drivers/resctrl/Kconfig | 13 +
drivers/resctrl/Makefile | 3 +
drivers/resctrl/cbqri_devices.c | 520 +++++++++++++++++++++++++++++++++++++++
drivers/resctrl/cbqri_internal.h | 107 ++++++++
include/linux/riscv_cbqri.h | 47 ++++
6 files changed, 693 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 07109e1a8f8470377916c98074ab68fec51dfdc6..811c0c9b1fac806945cad0229c5330654420a835 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23300,6 +23300,9 @@ L: linux-riscv@lists.infradead.org
S: Supported
F: arch/riscv/include/asm/qos.h
F: arch/riscv/kernel/qos.c
+F: drivers/resctrl/cbqri_devices.c
+F: drivers/resctrl/cbqri_internal.h
+F: include/linux/riscv_cbqri.h
RISC-V RPMI AND MPXY DRIVERS
M: Rahul Pathak <rahul@summations.net>
diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig
index 672abea3b03ccbeb4532832dd18e5cb80e90ff5b..92b9c82cf9f397437d28006e79d40c2d9b384eb0 100644
--- a/drivers/resctrl/Kconfig
+++ b/drivers/resctrl/Kconfig
@@ -29,3 +29,16 @@ config ARM64_MPAM_RESCTRL_FS
default y if ARM64_MPAM_DRIVER && RESCTRL_FS
select RESCTRL_RMID_DEPENDS_ON_CLOSID
select RESCTRL_ASSIGN_FIXED
+
+menuconfig RISCV_CBQRI
+ bool "RISC-V CBQRI support"
+ depends on RISCV && RISCV_ISA_SSQOSID
+ help
+ Capacity and Bandwidth QoS Register Interface (CBQRI) support for
+ RISC-V cache QoS resources. CBQRI exposes cache capacity
+ allocation through the resctrl filesystem at /sys/fs/resctrl when
+ RESCTRL_FS is also enabled.
+
+if RISCV_CBQRI
+
+endif
diff --git a/drivers/resctrl/Makefile b/drivers/resctrl/Makefile
index 4f6d0e81f9b8f34bd8842fdb13be69da72515b79..4d8a2c4b5627144a651da007174d4d9df6171330 100644
--- a/drivers/resctrl/Makefile
+++ b/drivers/resctrl/Makefile
@@ -3,3 +3,6 @@ mpam-y += mpam_devices.o
mpam-$(CONFIG_ARM64_MPAM_RESCTRL_FS) += mpam_resctrl.o
ccflags-$(CONFIG_ARM64_MPAM_DRIVER_DEBUG) += -DDEBUG
+
+obj-$(CONFIG_RISCV_CBQRI) += cbqri.o
+cbqri-y += cbqri_devices.o
diff --git a/drivers/resctrl/cbqri_devices.c b/drivers/resctrl/cbqri_devices.c
new file mode 100644
index 0000000000000000000000000000000000000000..8ad9df404f65d5d82722cf8b78f02936c489ca6d
--- /dev/null
+++ b/drivers/resctrl/cbqri_devices.c
@@ -0,0 +1,520 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
+
+#include <linux/bitfield.h>
+#include <linux/riscv_cbqri.h>
+#include <linux/cpumask.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/ioport.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <asm/csr.h>
+
+#include "cbqri_internal.h"
+
+LIST_HEAD(cbqri_controllers);
+
+/*
+ * Serializes cbqri_controllers mutations against a concurrent insert under
+ * asynchronous driver probing, and against the boot-time walk in the resctrl
+ * glue. Runtime cpuhp walks happen after registration has settled.
+ */
+DEFINE_MUTEX(cbqri_controllers_lock);
+
+/* Set capacity block mask (cc_block_mask) */
+static void cbqri_set_cbm(struct cbqri_controller *ctrl, u64 cbm)
+{
+ iowrite64(cbm, ctrl->base + CBQRI_CC_BLOCK_MASK_OFF);
+}
+
+static int cbqri_wait_busy_flag(struct cbqri_controller *ctrl, int reg_offset,
+ u64 *regp)
+{
+ u64 reg;
+ int ret;
+
+ /*
+ * Sleeping poll: caller holds ctrl->lock as a sleeping mutex, so
+ * 10us/1ms is safe under PREEMPT_RT.
+ */
+ ret = readq_poll_timeout(ctrl->base + reg_offset, reg,
+ !FIELD_GET(CBQRI_CONTROL_REGISTERS_BUSY_MASK, reg),
+ 10, 1000);
+ if (ret)
+ return ret;
+ if (regp)
+ *regp = reg;
+ return 0;
+}
+
+/*
+ * Perform capacity allocation control operation on capacity controller.
+ * Caller must hold ctrl->lock.
+ */
+static int cbqri_cc_alloc_op(struct cbqri_controller *ctrl, int operation,
+ int rcid, u32 at)
+{
+ int reg_offset = CBQRI_CC_ALLOC_CTL_OFF;
+ int status;
+ u64 reg;
+
+ lockdep_assert_held(&ctrl->lock);
+
+ if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
+ pr_err_ratelimited("BUSY timeout before starting operation\n");
+ return -EIO;
+ }
+ FIELD_MODIFY(CBQRI_CONTROL_REGISTERS_OP_MASK, ®, operation);
+ FIELD_MODIFY(CBQRI_CONTROL_REGISTERS_RCID_MASK, ®, rcid);
+
+ /*
+ * CBQRI Table 1: AT 0=Data, 1=Code. Program AT on controllers
+ * that report supports_alloc_at_code. On controllers that don't,
+ * AT is reserved-zero and the op acts on both halves.
+ */
+ reg &= ~CBQRI_CONTROL_REGISTERS_AT_MASK;
+ if (ctrl->cc.supports_alloc_at_code)
+ reg |= FIELD_PREP(CBQRI_CONTROL_REGISTERS_AT_MASK, at);
+
+ iowrite64(reg, ctrl->base + reg_offset);
+
+ if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
+ pr_err_ratelimited("BUSY timeout during operation\n");
+ return -EIO;
+ }
+
+ status = FIELD_GET(CBQRI_CONTROL_REGISTERS_STATUS_MASK, reg);
+ if (status != CBQRI_CC_ALLOC_CTL_STATUS_SUCCESS) {
+ pr_err_ratelimited("operation %d failed: status=%d\n", operation, status);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * Apply a capacity block mask and verify via CONFIG_LIMIT + READ_LIMIT.
+ *
+ * AT-capable controllers with CDP off need a second CONFIG_LIMIT on the
+ * other AT half (the spec encodes AT only as 0=Data / 1=Code, there is
+ * no "both halves" value). CDP-on issues separate per-type writes from
+ * resctrl, so a single CONFIG_LIMIT per call is correct.
+ */
+int cbqri_apply_cache_config(struct cbqri_controller *ctrl, u32 closid,
+ const struct cbqri_cc_config *cfg)
+{
+ bool need_at_mirror;
+ u64 saved_cbm = 0;
+ int err = 0;
+ u64 reg;
+
+ mutex_lock(&ctrl->lock);
+
+ need_at_mirror = ctrl->cc.supports_alloc_at_code && !cfg->cdp_enabled;
+
+ /*
+ * Capture the cfg->at half CBM before any write so a partial
+ * AT-mirror failure can revert and keep the two halves consistent.
+ * Pre-clear cc_block_mask so a silent firmware no-op (status
+ * SUCCESS but staging not updated) shows as a zero readback
+ * rather than carrying stale data from a prior op.
+ */
+ if (need_at_mirror) {
+ cbqri_set_cbm(ctrl, 0);
+ err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT,
+ closid, cfg->at);
+ if (err < 0)
+ goto out;
+ saved_cbm = ioread64(ctrl->base + CBQRI_CC_BLOCK_MASK_OFF);
+ }
+
+ /* Set capacity block mask (cc_block_mask) */
+ cbqri_set_cbm(ctrl, cfg->cbm);
+
+ /* Capacity config limit operation for the AT half implied by cfg->at */
+ err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT,
+ closid, cfg->at);
+ if (err < 0)
+ goto out;
+
+ /*
+ * CDP-off mirror: on AT-capable controllers, also program the
+ * other AT half with the same mask so the two halves stay in sync.
+ */
+ if (need_at_mirror) {
+ u32 other = (cfg->at == CBQRI_CONTROL_REGISTERS_AT_CODE) ?
+ CBQRI_CONTROL_REGISTERS_AT_DATA :
+ CBQRI_CONTROL_REGISTERS_AT_CODE;
+
+ cbqri_set_cbm(ctrl, cfg->cbm);
+ err = cbqri_cc_alloc_op(ctrl,
+ CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT,
+ closid, other);
+ if (err < 0) {
+ int rerr;
+
+ /*
+ * Best-effort revert of the cfg->at half so the two
+ * halves stay in sync. A schemata read sees only one
+ * half, so silent divergence would otherwise report
+ * the new value as if the write had succeeded.
+ */
+ cbqri_set_cbm(ctrl, saved_cbm);
+ rerr = cbqri_cc_alloc_op(ctrl,
+ CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT,
+ closid, cfg->at);
+ if (rerr < 0)
+ pr_err_ratelimited("AT-mirror revert failed (err=%d), AT halves diverged\n",
+ rerr);
+ goto out;
+ }
+ }
+
+ /* Clear cc_block_mask before read limit to verify op works */
+ cbqri_set_cbm(ctrl, 0);
+
+ /* Perform a capacity read limit operation to verify blockmask */
+ err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT,
+ closid, cfg->at);
+ if (err < 0)
+ goto out;
+
+ /*
+ * Read capacity blockmask and narrow to u32 to match resctrl's CBM
+ * width. cbqri_probe_cc() rejects ncblks > 32 so the upper bits are
+ * reserved zero.
+ */
+ reg = ioread64(ctrl->base + CBQRI_CC_BLOCK_MASK_OFF);
+ if (lower_32_bits(reg) != cfg->cbm) {
+ pr_err_ratelimited("CBM verify mismatch (reg=%llx != cbm=%llx)\n",
+ reg, cfg->cbm);
+ err = -EIO;
+ }
+
+out:
+ mutex_unlock(&ctrl->lock);
+ return err;
+}
+
+/*
+ * Read the configured CBM for closid on the at half via READ_LIMIT.
+ * Pre-clears cc_block_mask before the op so a silent firmware no-op
+ * (status SUCCESS but staging not updated) is detectable in cbm_out.
+ */
+int cbqri_read_cache_config(struct cbqri_controller *ctrl, u32 closid,
+ u32 at, u32 *cbm_out)
+{
+ int err;
+
+ mutex_lock(&ctrl->lock);
+ cbqri_set_cbm(ctrl, 0);
+ err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT, closid, at);
+ if (err == 0) {
+ /*
+ * cc_block_mask is a 64-bit MMIO register. resctrl exposes the
+ * CBM as a u32. cbqri_probe_cc() rejects ncblks > 32 so the
+ * upper 32 bits are reserved zero by the spec. Narrow
+ * explicitly via lower_32_bits() so the assumption is visible
+ * at the read site.
+ */
+ *cbm_out = lower_32_bits(ioread64(ctrl->base + CBQRI_CC_BLOCK_MASK_OFF));
+ }
+ mutex_unlock(&ctrl->lock);
+ return err;
+}
+
+static int cbqri_probe_feature(struct cbqri_controller *ctrl, int reg_offset,
+ int operation, int *status, bool *access_type_supported)
+{
+ const u64 active_mask = CBQRI_CONTROL_REGISTERS_OP_MASK |
+ CBQRI_CONTROL_REGISTERS_AT_MASK |
+ CBQRI_CONTROL_REGISTERS_RCID_MASK;
+ u64 reg, saved_reg;
+ int at;
+
+ /*
+ * Default the output to false so the status==0 (feature not
+ * implemented) path returns a deterministic value to the caller
+ * rather than leaving an uninitialized bool.
+ */
+ *access_type_supported = false;
+
+ /* Keep the initial register value to preserve the WPRI fields */
+ reg = ioread64(ctrl->base + reg_offset);
+ saved_reg = reg;
+
+ /* Drain any in-flight firmware op before issuing our own write. */
+ if (cbqri_wait_busy_flag(ctrl, reg_offset, &saved_reg) < 0) {
+ pr_err("BUSY timeout before probe operation\n");
+ return -EIO;
+ }
+
+ /*
+ * Execute the requested operation with all active fields
+ * (OP/AT/RCID) zeroed except OP itself. Every bit not in
+ * active_mask is WPRI and gets carried over from saved_reg.
+ */
+ reg = (saved_reg & ~active_mask) |
+ FIELD_PREP(CBQRI_CONTROL_REGISTERS_OP_MASK, operation);
+ iowrite64(reg, ctrl->base + reg_offset);
+ if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
+ pr_err_ratelimited("BUSY timeout during operation\n");
+ return -EIO;
+ }
+
+ /* Get the operation status */
+ *status = FIELD_GET(CBQRI_CONTROL_REGISTERS_STATUS_MASK, reg);
+
+ /*
+ * Check for the AT support if the register is implemented
+ * (if not, the status value will remain 0)
+ */
+ if (*status != 0) {
+ /*
+ * Re-issue operation with AT=CODE so the controller
+ * latches AT=CODE on supported hardware (or resets it to 0
+ * on hardware that doesn't). OP must be a defined CBQRI op
+ * here. OP=0 is a no-op and would silently disable CDP.
+ */
+ reg = (saved_reg & ~active_mask) |
+ FIELD_PREP(CBQRI_CONTROL_REGISTERS_OP_MASK, operation) |
+ FIELD_PREP(CBQRI_CONTROL_REGISTERS_AT_MASK,
+ CBQRI_CONTROL_REGISTERS_AT_CODE);
+ iowrite64(reg, ctrl->base + reg_offset);
+ if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
+ pr_err("BUSY timeout setting AT field\n");
+ return -EIO;
+ }
+
+ /*
+ * If the AT field value has been reset to zero,
+ * then the AT support is not present
+ */
+ at = FIELD_GET(CBQRI_CONTROL_REGISTERS_AT_MASK, reg);
+ if (at == CBQRI_CONTROL_REGISTERS_AT_CODE)
+ *access_type_supported = true;
+ }
+
+ /*
+ * Restore the original register value.
+ * Clear OP to avoid re-triggering the probe op.
+ */
+ saved_reg &= ~CBQRI_CONTROL_REGISTERS_OP_MASK;
+ iowrite64(saved_reg, ctrl->base + reg_offset);
+ if (cbqri_wait_busy_flag(ctrl, reg_offset, NULL) < 0) {
+ pr_err("BUSY timeout restoring register value\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int cbqri_probe_cc(struct cbqri_controller *ctrl)
+{
+ int err, status;
+ int ver_major, ver_minor;
+ u64 reg;
+
+ reg = ioread64(ctrl->base + CBQRI_CC_CAPABILITIES_OFF);
+ if (reg == 0)
+ return -ENODEV;
+
+ ver_minor = FIELD_GET(CBQRI_CC_CAPABILITIES_VER_MINOR_MASK, reg);
+ ver_major = FIELD_GET(CBQRI_CC_CAPABILITIES_VER_MAJOR_MASK, reg);
+ ctrl->cc.ncblks = FIELD_GET(CBQRI_CC_CAPABILITIES_NCBLKS_MASK, reg);
+
+ pr_debug("version=%d.%d ncblks=%d cache_level=%d\n",
+ ver_major, ver_minor,
+ ctrl->cc.ncblks, ctrl->cache.cache_level);
+
+ /*
+ * NCBLKS == 0 would divide-by-zero in the schemata math while
+ * ctrl->lock is held.
+ */
+ if (!ctrl->cc.ncblks) {
+ pr_warn("CC at %pa has 0 capacity blocks, skipping\n",
+ &ctrl->addr);
+ return -ENODEV;
+ }
+
+ if (ctrl->cc.ncblks > 32) {
+ pr_warn("CC at %pa has ncblks=%u > 32 (resctrl CBM is u32), skipping\n",
+ &ctrl->addr, ctrl->cc.ncblks);
+ return -ENODEV;
+ }
+
+ /* Probe allocation features */
+ err = cbqri_probe_feature(ctrl, CBQRI_CC_ALLOC_CTL_OFF,
+ CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT,
+ &status, &ctrl->cc.supports_alloc_at_code);
+ if (err)
+ return err;
+
+ if (status == CBQRI_CC_ALLOC_CTL_STATUS_SUCCESS)
+ ctrl->alloc_capable = true;
+
+ return 0;
+}
+
+static int cbqri_probe_controller(struct cbqri_controller *ctrl)
+{
+ int err;
+
+ pr_debug("controller info: type=%d addr=%pa size=%pa max-rcid=%u\n",
+ ctrl->type, &ctrl->addr, &ctrl->size, ctrl->rcid_count);
+
+ if (!ctrl->addr) {
+ pr_warn("controller has invalid addr=0x0, skipping\n");
+ return -EINVAL;
+ }
+
+ if (ctrl->size < CBQRI_CTRL_MIN_REG_SPAN) {
+ pr_warn("controller at %pa: size %pa < minimum 0x%x, skipping\n",
+ &ctrl->addr, &ctrl->size, CBQRI_CTRL_MIN_REG_SPAN);
+ return -EINVAL;
+ }
+
+ if (!request_mem_region(ctrl->addr, ctrl->size, "cbqri_controller")) {
+ pr_err("request_mem_region failed for %pa\n", &ctrl->addr);
+ return -EBUSY;
+ }
+
+ ctrl->base = ioremap(ctrl->addr, ctrl->size);
+ if (!ctrl->base) {
+ pr_err("ioremap failed for %pa\n", &ctrl->addr);
+ err = -ENOMEM;
+ goto err_release;
+ }
+
+ switch (ctrl->type) {
+ case CBQRI_CONTROLLER_TYPE_CAPACITY:
+ err = cbqri_probe_cc(ctrl);
+ break;
+ default:
+ pr_err("unknown controller type %d\n", ctrl->type);
+ err = -ENODEV;
+ break;
+ }
+
+ if (err)
+ goto err_iounmap;
+
+ return 0;
+
+err_iounmap:
+ iounmap(ctrl->base);
+ ctrl->base = NULL;
+err_release:
+ release_mem_region(ctrl->addr, ctrl->size);
+ return err;
+}
+
+void cbqri_controller_destroy(struct cbqri_controller *ctrl)
+{
+ /*
+ * cbqri_probe_controller() clears ctrl->base on its error paths and
+ * releases the mem region itself, so reach into both only when
+ * destroy is rolling back a successful probe.
+ */
+ if (ctrl->base) {
+ iounmap(ctrl->base);
+ release_mem_region(ctrl->addr, ctrl->size);
+ }
+ kfree(ctrl);
+}
+
+/**
+ * riscv_cbqri_register_cc_dt() - register a DT-described capacity controller
+ * @info: registration descriptor. info->cache_id is used as the
+ * resctrl domain id. info->type must be CAPACITY.
+ * @cache_level: cache level (2 or 3) the controller backs, mapped to the
+ * resctrl L2/L3 resource by the resctrl glue.
+ * @cpu_mask: CPUs that share this cache.
+ *
+ * The cache topology is supplied directly by the caller. A device-tree
+ * platform driver that already knows which CPUs share the cache and at what
+ * level passes that in. There is no firmware table to resolve it from.
+ *
+ * Return: 0 on success, or a negative errno on failure.
+ */
+int riscv_cbqri_register_cc_dt(const struct cbqri_controller_info *info,
+ u32 cache_level, const struct cpumask *cpu_mask)
+{
+ struct cbqri_controller *ctrl;
+ int err;
+
+ if (!info->addr) {
+ pr_warn("skipping controller with invalid addr=0x0\n");
+ return -EINVAL;
+ }
+
+ if (info->type != CBQRI_CONTROLLER_TYPE_CAPACITY) {
+ pr_warn("register_cc_dt called with non-capacity type %u\n",
+ info->type);
+ return -EINVAL;
+ }
+
+ if (!cpu_mask || cpumask_empty(cpu_mask)) {
+ pr_warn("register_cc_dt called with empty cpu_mask\n");
+ return -EINVAL;
+ }
+
+ ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+ return -ENOMEM;
+
+ mutex_init(&ctrl->lock);
+
+ ctrl->addr = info->addr;
+ ctrl->size = info->size;
+ ctrl->type = info->type;
+ ctrl->rcid_count = info->rcid_count;
+
+ /*
+ * SRMCFG encodes RCID in 12 bits. Reject an out-of-range count rather
+ * than silently truncating in every FIELD_PREP(SRMCFG_RCID_MASK, closid)
+ * on the schedule-in fast path.
+ */
+ if (ctrl->rcid_count > FIELD_MAX(SRMCFG_RCID_MASK) + 1) {
+ pr_warn("CC at %pa has RCID count %u beyond the 12-bit SRMCFG field, skipping\n",
+ &ctrl->addr, ctrl->rcid_count);
+ cbqri_controller_destroy(ctrl);
+ return -EINVAL;
+ }
+
+ ctrl->cache.cache_id = info->cache_id;
+ ctrl->cache.cache_level = cache_level;
+ cpumask_copy(&ctrl->cache.cpu_mask, cpu_mask);
+
+ err = cbqri_probe_controller(ctrl);
+ if (err) {
+ cbqri_controller_destroy(ctrl);
+ return err;
+ }
+
+ /*
+ * Allocation capability comes from the capabilities register probed
+ * above, not from device tree. rcid_count only bounds the RCID range,
+ * so a controller the hardware reports as alloc-capable but described
+ * with no RCID count cannot be driven. Reject that inconsistency. A
+ * monitoring-only controller (not alloc_capable) needs no RCID count.
+ */
+ if (ctrl->alloc_capable && !ctrl->rcid_count) {
+ pr_warn("CC at %pa is alloc-capable but has no RCID count, skipping\n",
+ &ctrl->addr);
+ cbqri_controller_destroy(ctrl);
+ return -EINVAL;
+ }
+
+ mutex_lock(&cbqri_controllers_lock);
+ list_add_tail(&ctrl->list, &cbqri_controllers);
+ mutex_unlock(&cbqri_controllers_lock);
+ return 0;
+}
diff --git a/drivers/resctrl/cbqri_internal.h b/drivers/resctrl/cbqri_internal.h
new file mode 100644
index 0000000000000000000000000000000000000000..2192a3831bcde7cd4fddf03f2ce1103902e8815c
--- /dev/null
+++ b/drivers/resctrl/cbqri_internal.h
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _DRIVERS_RESCTRL_CBQRI_INTERNAL_H
+#define _DRIVERS_RESCTRL_CBQRI_INTERNAL_H
+
+#include <linux/bitfield.h>
+#include <linux/riscv_cbqri.h>
+#include <linux/cpumask.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+/* Capacity Controller (CC) MMIO register offsets. */
+#define CBQRI_CC_CAPABILITIES_OFF 0
+#define CBQRI_CC_ALLOC_CTL_OFF 24
+#define CBQRI_CC_BLOCK_MASK_OFF 32
+
+/*
+ * Highest defined register offset (0x20) plus the 8-byte register width.
+ * cbqri_probe_controller() rejects smaller mappings so a u64 access at
+ * BLOCK_MASK stays in bounds.
+ */
+#define CBQRI_CTRL_MIN_REG_SPAN 0x28u
+
+#define CBQRI_CC_CAPABILITIES_VER_MINOR_MASK GENMASK_ULL(3, 0)
+#define CBQRI_CC_CAPABILITIES_VER_MAJOR_MASK GENMASK_ULL(7, 4)
+#define CBQRI_CC_CAPABILITIES_NCBLKS_MASK GENMASK_ULL(23, 8)
+
+/*
+ * CC control registers are 64-bit. Keep every field mask GENMASK_ULL so
+ * FIELD_MODIFY() or ~mask on a u64 register never zero-extends a 32-bit
+ * mask and clobbers STATUS/BUSY/WPRI in bits 63:32 if RV32 support is
+ * added in the future.
+ */
+#define CBQRI_CONTROL_REGISTERS_OP_MASK GENMASK_ULL(4, 0)
+#define CBQRI_CONTROL_REGISTERS_AT_MASK GENMASK_ULL(7, 5)
+/* AT field values (CBQRI Table 1): data vs code half for CDP */
+#define CBQRI_CONTROL_REGISTERS_AT_DATA 0
+#define CBQRI_CONTROL_REGISTERS_AT_CODE 1
+#define CBQRI_CONTROL_REGISTERS_RCID_MASK GENMASK_ULL(19, 8)
+#define CBQRI_CONTROL_REGISTERS_STATUS_MASK GENMASK_ULL(38, 32)
+#define CBQRI_CONTROL_REGISTERS_BUSY_MASK GENMASK_ULL(39, 39)
+
+#define CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT 1
+#define CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT 2
+#define CBQRI_CC_ALLOC_CTL_STATUS_SUCCESS 1
+
+/* Capacity Controller hardware capabilities */
+struct riscv_cbqri_capacity_caps {
+ u16 ncblks;
+ bool supports_alloc_at_code;
+};
+
+/**
+ * struct cbqri_cc_config - desired capacity allocation state for one rcid
+ * @cbm: capacity block mask
+ * @at: AT half the @cbm applies to (CBQRI_CONTROL_REGISTERS_AT_DATA
+ * or CBQRI_CONTROL_REGISTERS_AT_CODE)
+ * @cdp_enabled: when false and the controller supports AT, mirror @cbm
+ * into the other AT half so both stay in sync
+ */
+struct cbqri_cc_config {
+ u64 cbm;
+ u32 at;
+ bool cdp_enabled;
+};
+
+struct cbqri_controller {
+ void __iomem *base;
+ /*
+ * Serializes the write-then-poll-busy MMIO sequences on this
+ * controller. Each CBQRI op may busy-wait up to 1 ms on slow
+ * firmware, so use a sleeping mutex to keep preemption enabled.
+ * All resctrl-arch entry points run in process context.
+ */
+ struct mutex lock;
+
+ struct riscv_cbqri_capacity_caps cc;
+
+ bool alloc_capable;
+
+ phys_addr_t addr;
+ phys_addr_t size;
+ enum cbqri_controller_type type;
+ u32 rcid_count;
+
+ struct list_head list;
+
+ struct cache_controller {
+ u32 cache_level;
+ struct cpumask cpu_mask;
+ /* Cache id used as the resctrl domain id */
+ u32 cache_id;
+ } cache;
+};
+
+extern struct list_head cbqri_controllers;
+extern struct mutex cbqri_controllers_lock;
+
+void cbqri_controller_destroy(struct cbqri_controller *ctrl);
+
+int cbqri_apply_cache_config(struct cbqri_controller *ctrl, u32 closid,
+ const struct cbqri_cc_config *cfg);
+
+int cbqri_read_cache_config(struct cbqri_controller *ctrl, u32 closid,
+ u32 at, u32 *cbm_out);
+
+#endif /* _DRIVERS_RESCTRL_CBQRI_INTERNAL_H */
diff --git a/include/linux/riscv_cbqri.h b/include/linux/riscv_cbqri.h
new file mode 100644
index 0000000000000000000000000000000000000000..58737224d2f2c67a443f78f9fea7f368c7d16044
--- /dev/null
+++ b/include/linux/riscv_cbqri.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Public registration API for the RISC-V Capacity and Bandwidth QoS
+ * Register Interface (CBQRI) core. Discovery layers (device tree
+ * platform drivers) call riscv_cbqri_register_cc_dt() to hand a capacity
+ * controller descriptor to the core, which owns all subsequent state.
+ */
+#ifndef _LINUX_RISCV_CBQRI_H
+#define _LINUX_RISCV_CBQRI_H
+
+#include <linux/types.h>
+
+struct cpumask;
+
+enum cbqri_controller_type {
+ CBQRI_CONTROLLER_TYPE_CAPACITY,
+};
+
+/**
+ * struct cbqri_controller_info - registration descriptor
+ * @addr: MMIO base address of the controller's register interface
+ * @size: size of the MMIO region
+ * @type: controller type (capacity)
+ * @rcid_count: number of supported RCIDs
+ * @cache_id: cache id used as the resctrl domain id
+ */
+struct cbqri_controller_info {
+ phys_addr_t addr;
+ phys_addr_t size;
+ enum cbqri_controller_type type;
+ u32 rcid_count;
+ u32 cache_id;
+};
+
+#if IS_ENABLED(CONFIG_RISCV_CBQRI)
+int riscv_cbqri_register_cc_dt(const struct cbqri_controller_info *info,
+ u32 cache_level, const struct cpumask *cpu_mask);
+#else
+static inline int
+riscv_cbqri_register_cc_dt(const struct cbqri_controller_info *info,
+ u32 cache_level, const struct cpumask *cpu_mask)
+{
+ return -ENODEV;
+}
+#endif
+
+#endif /* _LINUX_RISCV_CBQRI_H */
--
2.34.1
^ permalink raw reply related
* [PATCH v2 3/8] riscv: Add support for srmcfg CSR from Ssqosid extension
From: Drew Fustini @ 2026-06-25 1:38 UTC (permalink / raw)
To: Adrien Ricciardi, Alexandre Ghiti, Atish Kumar Patra, Atish Patra,
Babu Moger, Ben Horgan, Borislav Petkov, Chen Pei, Conor Dooley,
Conor Dooley, Dave Hansen, Dave Martin, Fenghua Yu, Gong Shuai,
Gong Shuai, guo.wenjia23, James Morse, Kornel Dulęba,
Krzysztof Kozlowski, liu.qingtao2, Liu Zhiwei, Palmer Dabbelt,
Paul Walmsley, Peter Newman, Radim Krčmář,
Reinette Chatre, Rob Herring, Samuel Holland,
Sebastian Andrzej Siewior, Tony Luck, Vasudevan Srinivasan,
Ved Shanbhogue, Weiwei Li, yunhui cui, Drew Fustini
Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>
Add support for the srmcfg CSR defined in the Ssqosid ISA extension.
The CSR contains two fields:
- Resource Control ID (RCID) for resource allocation
- Monitoring Counter ID (MCID) for tracking resource usage
Requests from a hart to shared resources are tagged with these IDs,
allowing resource usage to be associated with the running task.
Add a srmcfg field to thread_struct with the same format as the CSR so
the scheduler can set the RCID and MCID for each task on context
switch. A per-cpu cpu_srmcfg variable mirrors the CSR state to avoid
redundant writes. L1D-hot memory access is faster than a CSR read and
avoids traps under virtualization.
A per-cpu cpu_srmcfg_default holds the default srmcfg for each CPU as
set by resctrl CPU group assignment. On context switch, RCID and MCID
inherit from the CPU default independently: a task whose thread RCID
field is zero takes the CPU default's RCID, and likewise for MCID.
Link: https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0
Assisted-by: Claude:claude-opus-4-7
Co-developed-by: Kornel Dulęba <mindal@semihalf.com>
Signed-off-by: Kornel Dulęba <mindal@semihalf.com>
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
MAINTAINERS | 8 ++++
arch/riscv/Kconfig | 18 +++++++
arch/riscv/include/asm/csr.h | 5 ++
arch/riscv/include/asm/processor.h | 3 ++
arch/riscv/include/asm/qos.h | 83 ++++++++++++++++++++++++++++++++
arch/riscv/include/asm/switch_to.h | 3 ++
arch/riscv/kernel/Makefile | 2 +
arch/riscv/kernel/qos.c | 98 ++++++++++++++++++++++++++++++++++++++
8 files changed, 220 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 0b9d7c8276acbafdb28a0ea5e81aa853ebee50b9..07109e1a8f8470377916c98074ab68fec51dfdc6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23293,6 +23293,14 @@ F: drivers/perf/riscv_pmu.c
F: drivers/perf/riscv_pmu_legacy.c
F: drivers/perf/riscv_pmu_sbi.c
+RISC-V QOS RESCTRL SUPPORT
+M: Drew Fustini <fustini@kernel.org>
+R: yunhui cui <cuiyunhui@bytedance.com>
+L: linux-riscv@lists.infradead.org
+S: Supported
+F: arch/riscv/include/asm/qos.h
+F: arch/riscv/kernel/qos.c
+
RISC-V RPMI AND MPXY DRIVERS
M: Rahul Pathak <rahul@summations.net>
M: Anup Patel <anup@brainfault.org>
diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index 3f0a647218e407f72890e83722ba8472858c1a59..ee586925f97227668c228b5481c05a2f914d928c 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -590,6 +590,24 @@ config RISCV_ISA_SVNAPOT
If you don't know what to do here, say Y.
+config RISCV_ISA_SSQOSID
+ bool "Ssqosid extension support for supervisor mode Quality of Service ID"
+ depends on 64BIT
+ default n
+ help
+ Adds support for the Ssqosid ISA extension (Supervisor-mode
+ Quality of Service ID).
+
+ Ssqosid defines the srmcfg CSR which allows the system to tag the
+ running process with an RCID (Resource Control ID) and MCID
+ (Monitoring Counter ID). The RCID is used to determine resource
+ allocation. The MCID is used to track resource usage in event
+ counters.
+
+ For example, a cache controller may use the RCID to apply a
+ cache partitioning scheme and use the MCID to track how much
+ cache a process, or a group of processes, is using.
+
config RISCV_ISA_SVPBMT
bool "Svpbmt extension support for supervisor mode page-based memory types"
depends on 64BIT && MMU
diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h
index 31b8988f4488daa89b854ccc97c4efe1c82bcc3e..7bce928e5daa09bd62f0917279b04cfad30f46f5 100644
--- a/arch/riscv/include/asm/csr.h
+++ b/arch/riscv/include/asm/csr.h
@@ -84,6 +84,10 @@
#define SATP_ASID_MASK _AC(0xFFFF, UL)
#endif
+/* SRMCFG fields */
+#define SRMCFG_RCID_MASK GENMASK(11, 0)
+#define SRMCFG_MCID_MASK GENMASK(27, 16)
+
/* Exception cause high bit - is an interrupt if set */
#define CAUSE_IRQ_FLAG (_AC(1, UL) << (__riscv_xlen - 1))
@@ -328,6 +332,7 @@
#define CSR_STVAL 0x143
#define CSR_SIP 0x144
#define CSR_SATP 0x180
+#define CSR_SRMCFG 0x181
#define CSR_STIMECMP 0x14D
#define CSR_STIMECMPH 0x15D
diff --git a/arch/riscv/include/asm/processor.h b/arch/riscv/include/asm/processor.h
index 812517b2cec1350f741849c1c56a35027321ef50..49a386d74cd3f0603a3ff919059d077a7e4d513c 100644
--- a/arch/riscv/include/asm/processor.h
+++ b/arch/riscv/include/asm/processor.h
@@ -123,6 +123,9 @@ struct thread_struct {
/* A forced icache flush is not needed if migrating to the previous cpu. */
unsigned int prev_cpu;
#endif
+#ifdef CONFIG_RISCV_ISA_SSQOSID
+ u32 srmcfg;
+#endif
};
/* Whitelist the fstate from the task_struct for hardened usercopy */
diff --git a/arch/riscv/include/asm/qos.h b/arch/riscv/include/asm/qos.h
new file mode 100644
index 0000000000000000000000000000000000000000..e9e1d69f3797be5f89785a9b3aa7d9d51c476a8a
--- /dev/null
+++ b/arch/riscv/include/asm/qos.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_RISCV_QOS_H
+#define _ASM_RISCV_QOS_H
+
+#include <linux/percpu-defs.h>
+
+#ifdef CONFIG_RISCV_ISA_SSQOSID
+
+#include <linux/bitfield.h>
+#include <linux/cpufeature.h>
+#include <linux/sched.h>
+
+#include <asm/csr.h>
+#include <asm/fence.h>
+#include <asm/hwcap.h>
+
+/* cached value of srmcfg csr for each cpu */
+DECLARE_PER_CPU(u32, cpu_srmcfg);
+
+/* default srmcfg value for each cpu, set via resctrl cpu assignment */
+DECLARE_PER_CPU(u32, cpu_srmcfg_default);
+
+static inline void __switch_to_srmcfg(struct task_struct *next)
+{
+ u32 thread_srmcfg, default_srmcfg;
+
+ thread_srmcfg = READ_ONCE(next->thread.srmcfg);
+ default_srmcfg = __this_cpu_read(cpu_srmcfg_default);
+
+ /*
+ * RCID and MCID inherit from cpu_srmcfg_default independently.
+ * RESCTRL_RESERVED_CLOSID and RESCTRL_RESERVED_RMID are both 0, so a
+ * zero field means "unassigned" and takes the CPU default.
+ */
+ if (thread_srmcfg == 0) {
+ thread_srmcfg = default_srmcfg;
+ } else {
+ u32 rcid = FIELD_GET(SRMCFG_RCID_MASK, thread_srmcfg);
+ u32 mcid = FIELD_GET(SRMCFG_MCID_MASK, thread_srmcfg);
+
+ if (rcid == 0 || mcid == 0) {
+ if (rcid == 0)
+ rcid = FIELD_GET(SRMCFG_RCID_MASK, default_srmcfg);
+ if (mcid == 0)
+ mcid = FIELD_GET(SRMCFG_MCID_MASK, default_srmcfg);
+ thread_srmcfg = FIELD_PREP(SRMCFG_RCID_MASK, rcid) |
+ FIELD_PREP(SRMCFG_MCID_MASK, mcid);
+ }
+ }
+
+ if (thread_srmcfg != __this_cpu_read(cpu_srmcfg)) {
+ /*
+ * Drain stores from the outgoing task before the CSR write
+ * so they retain the previous RCID/MCID tag at the cache
+ * interconnect.
+ */
+ RISCV_FENCE(rw, o);
+
+ __this_cpu_write(cpu_srmcfg, thread_srmcfg);
+ csr_write(CSR_SRMCFG, thread_srmcfg);
+ /*
+ * Order the csrw before the new task's loads/stores so they
+ * pick up the new tag. Zicsr 6.1.1 makes CSR writes weakly
+ * ordered (device-output) vs memory ops. Ssqosid v1.0 is
+ * silent so honor the general CSR rule.
+ */
+ RISCV_FENCE(o, rw);
+ }
+}
+
+static __always_inline bool has_srmcfg(void)
+{
+ return riscv_has_extension_unlikely(RISCV_ISA_EXT_SSQOSID);
+}
+
+#else /* ! CONFIG_RISCV_ISA_SSQOSID */
+
+struct task_struct;
+static __always_inline bool has_srmcfg(void) { return false; }
+static inline void __switch_to_srmcfg(struct task_struct *next) { }
+
+#endif /* CONFIG_RISCV_ISA_SSQOSID */
+#endif /* _ASM_RISCV_QOS_H */
diff --git a/arch/riscv/include/asm/switch_to.h b/arch/riscv/include/asm/switch_to.h
index 0e71eb82f920cac2f14bb626879bb219a2f247cc..1c7ea53ec012adeaf03bf7c5d549ab21849768b5 100644
--- a/arch/riscv/include/asm/switch_to.h
+++ b/arch/riscv/include/asm/switch_to.h
@@ -14,6 +14,7 @@
#include <asm/processor.h>
#include <asm/ptrace.h>
#include <asm/csr.h>
+#include <asm/qos.h>
#ifdef CONFIG_FPU
extern void __fstate_save(struct task_struct *save_to);
@@ -119,6 +120,8 @@ do { \
__switch_to_fpu(__prev, __next); \
if (has_vector() || has_xtheadvector()) \
__switch_to_vector(__prev, __next); \
+ if (has_srmcfg()) \
+ __switch_to_srmcfg(__next); \
if (switch_to_should_flush_icache(__next)) \
local_flush_icache_all(); \
__switch_to_envcfg(__next); \
diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile
index cabb99cadfb6d1e1284d6b4e9ae76044d36949f5..ebe1c3588177b4b825a52af9ca17e17b5561427c 100644
--- a/arch/riscv/kernel/Makefile
+++ b/arch/riscv/kernel/Makefile
@@ -128,3 +128,5 @@ obj-$(CONFIG_ACPI_NUMA) += acpi_numa.o
obj-$(CONFIG_GENERIC_CPU_VULNERABILITIES) += bugs.o
obj-$(CONFIG_RISCV_USER_CFI) += usercfi.o
+
+obj-$(CONFIG_RISCV_ISA_SSQOSID) += qos.o
diff --git a/arch/riscv/kernel/qos.c b/arch/riscv/kernel/qos.c
new file mode 100644
index 0000000000000000000000000000000000000000..ea33201a43f61534bf28b9c02b62801f30f62154
--- /dev/null
+++ b/arch/riscv/kernel/qos.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/cpu.h>
+#include <linux/cpu_pm.h>
+#include <linux/cpuhotplug.h>
+#include <linux/notifier.h>
+#include <linux/percpu-defs.h>
+#include <linux/types.h>
+
+#include <asm/cpufeature-macros.h>
+#include <asm/hwcap.h>
+#include <asm/qos.h>
+
+/*
+ * Cached value of srmcfg csr for each cpu. Seeded to U32_MAX so the next
+ * __switch_to_srmcfg() unconditionally writes the CSR. The encoding
+ * MCID << 16 | RCID with both fields well under 16 bits can never
+ * produce this sentinel. This covers early-boot context switches that
+ * happen before riscv_srmcfg_init() runs as an arch_initcall.
+ */
+DEFINE_PER_CPU(u32, cpu_srmcfg) = U32_MAX;
+
+/* default srmcfg value for each cpu, set via resctrl cpu assignment */
+DEFINE_PER_CPU(u32, cpu_srmcfg_default);
+
+/*
+ * Invalidate the per-CPU srmcfg cache, used as both the cpuhp startup and
+ * teardown callback. The sentinel is a value no real srmcfg encoding can
+ * produce (MCID << 16 | RCID, both fields well under 16 bits) so the next
+ * __switch_to_srmcfg() unconditionally writes the CSR.
+ *
+ * Ssqosid v1.0 leaves CSR state across hart stop/start implementation-
+ * defined, so the cached value cannot be trusted after online. Invalidating
+ * on offline as well means the sentinel persists across the offline period:
+ * a CPU brought back online finds the cache already invalidated before it is
+ * schedulable, closing the window where a task scheduled before the startup
+ * callback runs could match a stale cache and skip the CSR write while the
+ * hardware CSR was reset across hart stop/start.
+ */
+static int riscv_srmcfg_reset_cache(unsigned int cpu)
+{
+ per_cpu(cpu_srmcfg, cpu) = U32_MAX;
+ return 0;
+}
+
+/*
+ * CPU PM notifier: invalidate the cached srmcfg on resume from a deep
+ * idle / suspend. Ssqosid v1.0 leaves CSR_SRMCFG state across low-power
+ * transitions implementation-defined, and the boot CPU never goes
+ * through the cpuhp online callback during system suspend, so without
+ * this hook __switch_to_srmcfg() would skip the CSR write when the
+ * outgoing task happens to share its srmcfg with the pre-suspend cache.
+ */
+static int riscv_srmcfg_pm_notify(struct notifier_block *nb,
+ unsigned long action, void *unused)
+{
+ switch (action) {
+ case CPU_PM_EXIT:
+ case CPU_PM_ENTER_FAILED:
+ /*
+ * The CSR is implementation-defined across the low-power
+ * transition. Invalidate the cache and eagerly rewrite the
+ * CSR for the current task so it does not run mis-tagged
+ * until the next context switch.
+ */
+ __this_cpu_write(cpu_srmcfg, U32_MAX);
+ __switch_to_srmcfg(current);
+ break;
+ }
+ return NOTIFY_OK;
+}
+
+static struct notifier_block riscv_srmcfg_pm_nb = {
+ .notifier_call = riscv_srmcfg_pm_notify,
+};
+
+static int __init riscv_srmcfg_init(void)
+{
+ int err;
+
+ if (!riscv_has_extension_unlikely(RISCV_ISA_EXT_SSQOSID))
+ return 0;
+
+ /*
+ * cpuhp_setup_state() invokes the startup callback locally on every
+ * already-online CPU, so no separate seed loop is needed here.
+ */
+ err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "riscv/srmcfg:online",
+ riscv_srmcfg_reset_cache, riscv_srmcfg_reset_cache);
+ if (err < 0) {
+ pr_warn("srmcfg cpuhp registration failed (%d), cpus brought online after boot will not invalidate the CSR_SRMCFG cache\n",
+ err);
+ return err;
+ }
+
+ cpu_pm_register_notifier(&riscv_srmcfg_pm_nb);
+ return 0;
+}
+arch_initcall(riscv_srmcfg_init);
--
2.34.1
^ permalink raw reply related
* [PATCH v2 2/8] riscv: Detect the Ssqosid extension
From: Drew Fustini @ 2026-06-25 1:38 UTC (permalink / raw)
To: Adrien Ricciardi, Alexandre Ghiti, Atish Kumar Patra, Atish Patra,
Babu Moger, Ben Horgan, Borislav Petkov, Chen Pei, Conor Dooley,
Conor Dooley, Dave Hansen, Dave Martin, Fenghua Yu, Gong Shuai,
Gong Shuai, guo.wenjia23, James Morse, Kornel Dulęba,
Krzysztof Kozlowski, liu.qingtao2, Liu Zhiwei, Palmer Dabbelt,
Paul Walmsley, Peter Newman, Radim Krčmář,
Reinette Chatre, Rob Herring, Samuel Holland,
Sebastian Andrzej Siewior, Tony Luck, Vasudevan Srinivasan,
Ved Shanbhogue, Weiwei Li, yunhui cui, Drew Fustini
Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>
Ssqosid is the RISC-V Quality-of-Service (QoS) Identifiers specification
which defines the Supervisor Resource Management Configuration (srmcfg)
register.
Link: https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0
Co-developed-by: Kornel Dulęba <mindal@semihalf.com>
Signed-off-by: Kornel Dulęba <mindal@semihalf.com>
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
arch/riscv/include/asm/hwcap.h | 1 +
arch/riscv/kernel/cpufeature.c | 1 +
2 files changed, 2 insertions(+)
diff --git a/arch/riscv/include/asm/hwcap.h b/arch/riscv/include/asm/hwcap.h
index 7ef8e5f55c8dcf26e3dcc676db32c2f16b7e7f5a..b83dae5cebb99269902279faa3c03fd23f7d4342 100644
--- a/arch/riscv/include/asm/hwcap.h
+++ b/arch/riscv/include/asm/hwcap.h
@@ -112,6 +112,7 @@
#define RISCV_ISA_EXT_ZCLSD 103
#define RISCV_ISA_EXT_ZICFILP 104
#define RISCV_ISA_EXT_ZICFISS 105
+#define RISCV_ISA_EXT_SSQOSID 106
#define RISCV_ISA_EXT_XLINUXENVCFG 127
diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c
index f46aa5602d74d3322960559fb73bb50152909344..668a7e71ff1c64e8bd36ba6ea94f8a5dd4600016 100644
--- a/arch/riscv/kernel/cpufeature.c
+++ b/arch/riscv/kernel/cpufeature.c
@@ -582,6 +582,7 @@ const struct riscv_isa_ext_data riscv_isa_ext[] = {
__RISCV_ISA_EXT_DATA(ssaia, RISCV_ISA_EXT_SSAIA),
__RISCV_ISA_EXT_DATA(sscofpmf, RISCV_ISA_EXT_SSCOFPMF),
__RISCV_ISA_EXT_SUPERSET(ssnpm, RISCV_ISA_EXT_SSNPM, riscv_xlinuxenvcfg_exts),
+ __RISCV_ISA_EXT_DATA(ssqosid, RISCV_ISA_EXT_SSQOSID),
__RISCV_ISA_EXT_DATA(sstc, RISCV_ISA_EXT_SSTC),
__RISCV_ISA_EXT_DATA(svade, RISCV_ISA_EXT_SVADE),
__RISCV_ISA_EXT_DATA_VALIDATE(svadu, RISCV_ISA_EXT_SVADU, riscv_ext_svadu_validate),
--
2.34.1
^ permalink raw reply related
* [PATCH v2 1/8] dt-bindings: riscv: Add Ssqosid extension description
From: Drew Fustini @ 2026-06-25 1:38 UTC (permalink / raw)
To: Adrien Ricciardi, Alexandre Ghiti, Atish Kumar Patra, Atish Patra,
Babu Moger, Ben Horgan, Borislav Petkov, Chen Pei, Conor Dooley,
Conor Dooley, Dave Hansen, Dave Martin, Fenghua Yu, Gong Shuai,
Gong Shuai, guo.wenjia23, James Morse, Kornel Dulęba,
Krzysztof Kozlowski, liu.qingtao2, Liu Zhiwei, Palmer Dabbelt,
Paul Walmsley, Peter Newman, Radim Krčmář,
Reinette Chatre, Rob Herring, Samuel Holland,
Sebastian Andrzej Siewior, Tony Luck, Vasudevan Srinivasan,
Ved Shanbhogue, Weiwei Li, yunhui cui, Drew Fustini
Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>
Document the ratified Supervisor-mode Quality of Service ID (Ssqosid)
extension v1.0.
Link: https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
Documentation/devicetree/bindings/riscv/extensions.yaml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/riscv/extensions.yaml b/Documentation/devicetree/bindings/riscv/extensions.yaml
index 2b0a8a93bb21445e40ec106bdff71f4daf9563f6..1c6f091518d49aca20ecd4838c124b4983f0ef88 100644
--- a/Documentation/devicetree/bindings/riscv/extensions.yaml
+++ b/Documentation/devicetree/bindings/riscv/extensions.yaml
@@ -232,6 +232,12 @@ properties:
ratified at commit d70011dde6c2 ("Update to ratified state")
of riscv-j-extension.
+ - const: ssqosid
+ description: |
+ The standard Ssqosid extension for Quality of Service ID is
+ ratified as v1.0 in commit d9c616497fde ("Merge pull
+ request #7 from ved-rivos/Ratified") of riscv-ssqosid.
+
- const: ssstateen
description: |
The standard Ssstateen extension for supervisor-mode view of the
--
2.34.1
^ permalink raw reply related
* [PATCH v2 0/8] riscv: Add Ssqosid and initial CBQRI resctrl support
From: Drew Fustini @ 2026-06-25 1:38 UTC (permalink / raw)
To: Adrien Ricciardi, Alexandre Ghiti, Atish Kumar Patra, Atish Patra,
Babu Moger, Ben Horgan, Borislav Petkov, Chen Pei, Conor Dooley,
Conor Dooley, Dave Hansen, Dave Martin, Fenghua Yu, Gong Shuai,
Gong Shuai, guo.wenjia23, James Morse, Kornel Dulęba,
Krzysztof Kozlowski, liu.qingtao2, Liu Zhiwei, Palmer Dabbelt,
Paul Walmsley, Peter Newman, Radim Krčmář,
Reinette Chatre, Rob Herring, Samuel Holland,
Sebastian Andrzej Siewior, Tony Luck, Vasudevan Srinivasan,
Ved Shanbhogue, Weiwei Li, yunhui cui, Drew Fustini
Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
linux-doc
This series adds initial RISC-V QoS support: the Ssqosid extension [1]
(srmcfg CSR), the CBQRI controller interface [2] integrated with resctrl
[3], and a DT-based platform driver for cache controllers. It has been
tested on both the Tenstorrent Ascalon Shared Cache controller and a QEMU
implementation [4].
qemu-system-riscv64 -M virt,aia=aplic-imsic -nographic -m 1G -smp 8 \
-kernel arch/riscv/boot/Image \
-append "root=/dev/vda ro console=ttyS0 rootwait" \
-drive if=none,file=rootfs.ext2,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0 \
-device riscv.cbqri.capacity,max_mcids=256,max_rcids=64,ncblks=16,mmio_base=0x04820000
Cache allocation can be exercised on the booted system. Mount resctrl
and read the default schemata. The L2 controller has 16 capacity
blocks, so the default capacity bitmask (CBM) is 0xffff:
# mount -t resctrl resctrl /sys/fs/resctrl
# cat /sys/fs/resctrl/schemata
L2:0=ffff
Write a narrower CBM to a new control group and read it back to confirm
the L2 controller applied it:
# mkdir /sys/fs/resctrl/group0
# echo "L2:0=ff" > /sys/fs/resctrl/group0/schemata
# cat /sys/fs/resctrl/group0/schemata
L2:0=ff
Note that this series only implements support for resctrl L2 and L3
cache resources using CBQRI capacity allocation control. cc_block_mask
maps onto resctrl's existing cbm schema. However, cc_cunits is not
supported as there is no existing equivalent for capacity units in the
resctrl schemata.
I had previously been iterating on an RFC series [5] that did a full
implementation of CBQRI including capacity monitoring, bandwidth
allocation and monitoring. The bandwidth controls for CBQRI do not fit
well into resctrl's existing throttle-based MB schemata. I believe that
the path forward is Reinette's generic schema description proof of
concept [6]. My plan is to rebase the full support of CBQRI onto the
generic schema once it is ready.
This series is based on the linux-next tag next-20260623.
[1] https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0
[2] https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
[3] https://docs.kernel.org/filesystems/resctrl.html
[4] https://github.com/tt-fustini/qemu/tree/riscv-cbqri-cache
[5] https://lore.kernel.org/linux-riscv/20260601-ssqosid-cbqri-rqsc-v7-0-v6-16-baf00f50028a@kernel.org/
[6] https://lore.kernel.org/all/aab804b9-e8b5-40ad-a85b-af7033391243@intel.com/
Changes in v2:
--------------
The changes in this revision address the Sashiko review of v1.
- Restore the srmcfg CSR for the current task on CPU_PM_EXIT and
CPU_PM_ENTER_FAILED, so it is not left configured incorrectly until
the next context switch.
- Serialize the cbqri_controllers list insert and the boot time walk
with a mutex, so an asynchronous driver probe cannot corrupt the list.
- Skip a controller at an unsupported cache level instead of aborting
resctrl setup, so valid L2 and L3 controllers still register.
- RISCV_ISA_SSQOSID selects ARCH_HAS_CPU_RESCTRL and RISCV_CBQRI
together, so no intermediate commit enables RESCTRL_FS without the
CBQRI resctrl glue.
- Rename the RISCV_CBQRI_DRIVER to RISCV_CBQRI, since it builds the
CBQRI core ops and resctrl integration rather than a driver.
- Drop the RISCV_CBQRI_DRIVER_DEBUG Kconfig option and rely on dynamic
debug to control the pr_debug() output.
- Note: Sashiko flagged the lack of suspend/resume state restore. I will
not fix that as register state is only lost when the power domain is
gated, which offlines the harts sharing the cache. resctrl reprograms
the default capacity mask through the normal control domain online
path on resume.
Link to v1:
https://lore.kernel.org/all/20260619-dfustini-atl-sc-cbqri-dt-v1-0-e79a7723fab0@kernel.org/
Sashiko review:
https://sashiko.dev/#/patchset/20260619-dfustini-atl-sc-cbqri-dt-v1-0-e79a7723fab0%40kernel.org
---
Drew Fustini (8):
dt-bindings: riscv: Add Ssqosid extension description
riscv: Detect the Ssqosid extension
riscv: Add support for srmcfg CSR from Ssqosid extension
riscv_cbqri: Add capacity controller probe and allocation device ops
riscv_cbqri: resctrl: Add cache allocation via capacity block mask
riscv: Enable resctrl filesystem for Ssqosid
dt-bindings: riscv: Add generic CBQRI controller binding
riscv_cbqri: Add CBQRI cache capacity-allocation platform driver
.../devicetree/bindings/riscv/extensions.yaml | 6 +
.../devicetree/bindings/riscv/riscv,cbqri.yaml | 97 +++
MAINTAINERS | 15 +
arch/riscv/Kconfig | 20 +
arch/riscv/include/asm/csr.h | 5 +
arch/riscv/include/asm/hwcap.h | 1 +
arch/riscv/include/asm/processor.h | 3 +
arch/riscv/include/asm/qos.h | 83 +++
arch/riscv/include/asm/resctrl.h | 147 ++++
arch/riscv/include/asm/switch_to.h | 3 +
arch/riscv/kernel/Makefile | 2 +
arch/riscv/kernel/cpufeature.c | 1 +
arch/riscv/kernel/qos.c | 98 +++
drivers/resctrl/Kconfig | 29 +
drivers/resctrl/Makefile | 5 +
drivers/resctrl/cbqri_capacity.c | 132 ++++
drivers/resctrl/cbqri_devices.c | 520 ++++++++++++++
drivers/resctrl/cbqri_internal.h | 107 +++
drivers/resctrl/cbqri_resctrl.c | 779 +++++++++++++++++++++
include/linux/riscv_cbqri.h | 47 ++
20 files changed, 2100 insertions(+)
---
base-commit: 4e5dfb7c84012007c3c7061126491bbc92d71bf1
change-id: 20260610-dfustini-atl-sc-cbqri-dt-410c8e2711dd
Best regards,
--
Drew Fustini <fustini@kernel.org>
^ permalink raw reply
* Re: [PATCH v8 2/5] thermal: samsung: Add Exynos ACPM TMU driver GS101
From: Alexey Klimov @ 2026-06-25 1:32 UTC (permalink / raw)
To: Tudor Ambarus, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bartlomiej Zolnierkiewicz, Krzysztof Kozlowski, Kees Cook,
Gustavo A. R. Silva, Peter Griffin, André Draszik,
Alim Akhtar
Cc: jyescas, linux-kernel, linux-samsung-soc, linux-pm, devicetree,
linux-hardening, linux-arm-kernel, Krzysztof Kozlowski
In-Reply-To: <20260603-acpm-tmu-v8-2-0f1810a356e6@linaro.org>
On Wed Jun 3, 2026 at 2:00 PM BST, Tudor Ambarus wrote:
> Add driver for the Thermal Management Unit (TMU) managed via the Alive
> Clock and Power Manager (ACPM), found on Samsung Exynos SoCs such as
> the Google GS101.
>
> The TMU on the GS101 utilizes a hybrid management model shared between
> the Application Processor (AP) and the ACPM firmware. The driver
> maintains direct memory-mapped access to the TMU interrupt pending
> registers to identify thermal events, while delegating functional
> tasks - such as sensor initialization, threshold configuration, and
> temperature acquisition, to the ACPM firmware via the ACPM IPC
> protocol.
[..]
> +++ b/drivers/thermal/samsung/acpm-tmu.c
[...]
> +static struct platform_driver acpm_tmu_driver = {
> + .driver = {
> + .name = "gs-tmu",
Should it be "gs-tmu" still?
https://lore.kernel.org/linux-samsung-soc/56c1bb6d-54e4-4977-bd88-9ce7a6086b1d@linaro.org/
> + .pm = pm_ptr(&acpm_tmu_pm_ops),
> + .of_match_table = acpm_tmu_match,
> + },
> + .probe = acpm_tmu_probe,
> + .remove = acpm_tmu_remove,
> +};
> +module_platform_driver(acpm_tmu_driver);
Thanks,
Alexey
^ permalink raw reply
* Re: [PATCH v8] arm64: dts: qcom: kodiak: Add EL2 overlay
From: Miaoqing Pan @ 2026-06-25 1:14 UTC (permalink / raw)
To: Mukesh Ojha, Bjorn Andersson, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, devicetree, linux-kernel, Sumit Garg
In-Reply-To: <20260624063952.2242702-1-mukesh.ojha@oss.qualcomm.com>
On 6/24/2026 2:39 PM, Mukesh Ojha wrote:
> All the existing variants Kodiak boards are using Gunyah hypervisor
> which means that, so far, Linux-based OS could only boot in EL1 on those
> devices. However, it is possible for us to boot Linux at EL2 on these
> devices [1].
>
> When running under Gunyah, the remote processor firmware IOMMU
> streams are controlled by Gunyah. However, without Gunyah, the IOMMU is
> managed by the consumer of this DeviceTree. Therefore, describe the
> firmware streams for each remote processor.
>
> Add a EL2-specific DT overlay and apply it to Kodiak IOT variant
> devices to create -el2.dtb for each of them alongside "normal" dtb.
>
> Note that modem and media subsystems haven't been supported yet due
> to missing dependencies. For GPU to work, zap shader is disabled and
> in EL2 mode the kernel owns hardware watchdog which is enabled here.
> And for wifi to work wpss copy engine memory need to be mapped for
> WPSS firmware to work which is aligning with sc7280 chrome.
>
> [1]
> https://docs.qualcomm.com/bundle/publicresource/topics/80-70020-4/boot-developer-touchpoints.html#uefi
>
> Co-developed-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> Signed-off-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
> ---
> Changes in v8: https://lore.kernel.org/lkml/20260522115936.201208-2-sumit.garg@kernel.org/
> - Added a wpss copy engine memory similar to chrome for Wifi to work.
> - WPSS does not have firmware Stream, so that was removed.
> - Added wifi streams similar to chrome for wifi to work.
> - Removed this patch from Generic Pas patch series, can be followed
> separately.
> - Moved Sumit as co-author as part of modification done to the patch
> in the past.
> - Added some more kodiak's board variants in the makefile.
>
> Changes in v1-v7:
> - mpss was disabled and will be enabled once the dependencies patches
> get merged.
>
> arch/arm64/boot/dts/qcom/Makefile | 12 ++++++
> arch/arm64/boot/dts/qcom/kodiak-el2.dtso | 52 ++++++++++++++++++++++++
> arch/arm64/boot/dts/qcom/kodiak.dtsi | 2 +-
> 3 files changed, 65 insertions(+), 1 deletion(-)
> create mode 100644 arch/arm64/boot/dts/qcom/kodiak-el2.dtso
>
> diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile
> index 6f33c4e2f09c..d2cee1190954 100644
> --- a/arch/arm64/boot/dts/qcom/Makefile
> +++ b/arch/arm64/boot/dts/qcom/Makefile
> @@ -164,7 +164,11 @@ purwa-iot-evk-el2-dtbs := purwa-iot-evk.dtb x1-el2.dtbo
>
> dtb-$(CONFIG_ARCH_QCOM) += purwa-iot-evk-el2.dtb
> dtb-$(CONFIG_ARCH_QCOM) += qcm6490-fairphone-fp5.dtb
> +
> dtb-$(CONFIG_ARCH_QCOM) += qcm6490-idp.dtb
> +qcm6490-idp-el2-dtbs := qcm6490-idp.dtb kodiak-el2.dtbo
> +dtb-$(CONFIG_ARCH_QCOM) += qcm6490-idp-el2.dtb
> +
> dtb-$(CONFIG_ARCH_QCOM) += qcm6490-particle-tachyon.dtb
> dtb-$(CONFIG_ARCH_QCOM) += qcm6490-shift-otter.dtb
> dtb-$(CONFIG_ARCH_QCOM) += qcs404-evb-1000.dtb
> @@ -176,12 +180,20 @@ qcs615-ride-el2-dtbs := qcs615-ride.dtb talos-el2.dtbo
> dtb-$(CONFIG_ARCH_QCOM) += qcs615-ride-el2.dtb
> dtb-$(CONFIG_ARCH_QCOM) += qcs6490-radxa-dragon-q6a.dtb
> dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2.dtb
> +qcs6490-rb3gen2-el2-dtbs := qcs6490-rb3gen2.dtb kodiak-el2.dtbo
> +dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-el2.dtb
>
> qcs6490-rb3gen2-vision-mezzanine-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2-vision-mezzanine.dtbo
> qcs6490-rb3gen2-industrial-mezzanine-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2-industrial-mezzanine.dtbo
>
> dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-industrial-mezzanine.dtb
> +qcs6490-rb3gen2-industrial-mezzanine-el2-dtbs := qcs6490-rb3gen2-industrial-mezzanine.dtb kodiak-el2.dtbo
> +dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-industrial-mezzanine-el2.dtb
> +
> dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-vision-mezzanine.dtb
> +qcs6490-rb3gen2-vision-mezzanine-el2-dtbs := qcs6490-rb3gen2-vision-mezzanine.dtb kodiak-el2.dtbo
> +dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-vision-mezzanine-el2.dtb
> +
> dtb-$(CONFIG_ARCH_QCOM) += qcs6490-thundercomm-minipc-g1iot.dtb
> dtb-$(CONFIG_ARCH_QCOM) += qcs6490-thundercomm-rubikpi3.dtb
> dtb-$(CONFIG_ARCH_QCOM) += qcs8300-ride.dtb
> diff --git a/arch/arm64/boot/dts/qcom/kodiak-el2.dtso b/arch/arm64/boot/dts/qcom/kodiak-el2.dtso
> new file mode 100644
> index 000000000000..91e4cda45b49
> --- /dev/null
> +++ b/arch/arm64/boot/dts/qcom/kodiak-el2.dtso
> @@ -0,0 +1,52 @@
> +// SPDX-License-Identifier: BSD-3-Clause
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + *
> + * Kodiak specific modifications required to boot in EL2.
> + */
> +
> +/dts-v1/;
> +/plugin/;
> +
> +&gpu_zap_shader {
> + status = "disabled";
> +};
> +
> +&remoteproc_adsp {
> + iommus = <&apps_smmu 0x1800 0x0>;
> +};
> +
> +&remoteproc_cdsp {
> + iommus = <&apps_smmu 0x11a0 0x0400>;
> +};
> +
> +&remoteproc_mpss {
> + status = "disabled";
> +};
> +
> +&reserved_memory {
> + #address-cells = <2>;
> + #size-cells = <2>;
> +
> + wlan_ce_mem: wlan-ce@4cd000 {
> + no-map;
> + reg = <0x0 0x004cd000 0x0 0x1000>;
> + };
> +};
> +
Is it necessary to redefine |wlan_ce_mem|? Can we consider updating
|qcs6490-rb3gen2.dts|?
I have verified that with the following changes, *NON-KVM works fine*,
and |wlan_ce_mem| is only used by the WCN6750 firmware.
--- a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts
+++ b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts
@@ -26,7 +26,6 @@
/delete-node/ &adsp_mem;
/delete-node/ &cdsp_mem;
/delete-node/ &video_mem;
-/delete-node/ &wlan_ce_mem;
/delete-node/ &wpss_mem;
/delete-node/ &xbl_mem;
@@ -1686,7 +1685,6 @@ &venus {
};
&wifi {
- memory-region = <&wlan_fw_mem>;
qcom,calibration-variant = "Qualcomm_rb3gen2";
> +&venus {
> + status = "disabled";
> +};
> +
> +&watchdog {
> + status = "okay";
> +};
> +
> +&wifi {
> + memory-region = <&wlan_fw_mem>, <&wlan_ce_mem>;
> + status = "okay";
> +
> + wifi-firmware {
> + iommus = <&apps_smmu 0x1c02 0x1>;
> + };
> +};
> diff --git a/arch/arm64/boot/dts/qcom/kodiak.dtsi b/arch/arm64/boot/dts/qcom/kodiak.dtsi
> index fa540d8c2615..2486d15fa2ba 100644
> --- a/arch/arm64/boot/dts/qcom/kodiak.dtsi
> +++ b/arch/arm64/boot/dts/qcom/kodiak.dtsi
> @@ -91,7 +91,7 @@ sleep_clk: sleep-clk {
> };
> };
>
> - reserved-memory {
> + reserved_memory: reserved-memory {
> #address-cells = <2>;
> #size-cells = <2>;
> ranges;
^ permalink raw reply
* Re: [PATCH v2 2/2] clk: ultrarisc: add DP1000 clock driver
From: Jia Wang @ 2026-06-25 1:04 UTC (permalink / raw)
To: Jia Wang
Cc: Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, linux-clk, devicetree,
linux-kernel
In-Reply-To: <20260617-ultrarisc-clock-v2-2-9cb16083e15e@ultrarisc.com>
On 2026-06-17 14:02 +0800, Jia Wang wrote:
> Add a clock driver for the UltraRISC DP1000 SoC.
>
> The clock tree is driven by a SYSPLL and provides fixed-factor clocks for
> the subsystem and PCIe, divider-based root clocks for GMAC and the UART,
> I2C, and SPI blocks, and per-instance gate clocks for UART0-3, I2C0-3,
> and SPI0-1.
>
> Signed-off-by: Jia Wang <wangjia@ultrarisc.com>
> ---
> MAINTAINERS | 1 +
> drivers/clk/Kconfig | 1 +
> drivers/clk/Makefile | 1 +
> drivers/clk/ultrarisc/Kconfig | 18 ++
> drivers/clk/ultrarisc/Makefile | 4 +
> drivers/clk/ultrarisc/clk-dp1000.c | 153 ++++++++++++
> drivers/clk/ultrarisc/clk-ultrarisc.c | 459 ++++++++++++++++++++++++++++++++++
> drivers/clk/ultrarisc/clk-ultrarisc.h | 73 ++++++
> 8 files changed, 710 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b7e43313c65f..aa5021f30cb5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -27444,6 +27444,7 @@ M: Jia Wang <wangjia@ultrarisc.com>
> L: linux-clk@vger.kernel.org
> S: Maintained
> F: Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml
> +F: drivers/clk/ultrarisc/*
> F: include/dt-bindings/clock/ultrarisc,dp1000-clk.h
>
> ULTRATRONIK BOARD SUPPORT
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index b2efbe9f6acb..75e336858420 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -535,6 +535,7 @@ source "drivers/clk/tenstorrent/Kconfig"
> source "drivers/clk/thead/Kconfig"
> source "drivers/clk/stm32/Kconfig"
> source "drivers/clk/ti/Kconfig"
> +source "drivers/clk/ultrarisc/Kconfig"
> source "drivers/clk/uniphier/Kconfig"
> source "drivers/clk/visconti/Kconfig"
> source "drivers/clk/x86/Kconfig"
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index a3e2862ebd7e..891272e8e1da 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -158,6 +158,7 @@ obj-$(CONFIG_ARCH_TEGRA) += tegra/
> obj-y += tenstorrent/
> obj-$(CONFIG_ARCH_THEAD) += thead/
> obj-y += ti/
> +obj-y += ultrarisc/
> obj-$(CONFIG_CLK_UNIPHIER) += uniphier/
> obj-$(CONFIG_ARCH_U8500) += ux500/
> obj-y += versatile/
> diff --git a/drivers/clk/ultrarisc/Kconfig b/drivers/clk/ultrarisc/Kconfig
> new file mode 100644
> index 000000000000..2eecc6ac3119
> --- /dev/null
> +++ b/drivers/clk/ultrarisc/Kconfig
> @@ -0,0 +1,18 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config CLK_ULTRARISC
> + tristate
> + depends on OF
> + depends on ARCH_ULTRARISC || COMPILE_TEST
> +
> +config CLK_ULTRARISC_DP1000
> + tristate "UltraRISC DP1000 clock controller"
> + select CLK_ULTRARISC
> + depends on OF && HAS_IOMEM
> + depends on ARCH_ULTRARISC || COMPILE_TEST
> + default ARCH_ULTRARISC
> + help
> + This driver provides the clock controller for the UltraRISC
> + DP1000 SoC. It exposes the PLL output, derived fixed-factor
> + clocks, programmable divider clocks, and peripheral gate
> + clocks to Linux consumers.
> diff --git a/drivers/clk/ultrarisc/Makefile b/drivers/clk/ultrarisc/Makefile
> new file mode 100644
> index 000000000000..b013708c9444
> --- /dev/null
> +++ b/drivers/clk/ultrarisc/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +obj-$(CONFIG_CLK_ULTRARISC) += clk-ultrarisc.o
> +obj-$(CONFIG_CLK_ULTRARISC_DP1000) += clk-dp1000.o
> diff --git a/drivers/clk/ultrarisc/clk-dp1000.c b/drivers/clk/ultrarisc/clk-dp1000.c
> new file mode 100644
> index 000000000000..33a16df8f189
> --- /dev/null
> +++ b/drivers/clk/ultrarisc/clk-dp1000.c
> @@ -0,0 +1,153 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd.
> + */
> +
> +#include <linux/module.h>
> +
> +#include <dt-bindings/clock/ultrarisc,dp1000-clk.h>
> +
> +#include "clk-ultrarisc.h"
> +
> +#define DP1000_PLL_CFG1_OFFSET 0x400
> +#define DP1000_PLL_CFG2_OFFSET 0x404
> +
> +#define DP1000_CCR_UART_OFFSET 0x220
> +#define DP1000_CCR_I2C_OFFSET 0x224
> +#define DP1000_CCR_GMAC_OFFSET 0x228
> +#define DP1000_CCR_SPI_OFFSET 0x22c
> +#define DP1000_PERI_CLKENA_OFFSET 0x270
> +
> +#define DP1000_CCR_LOAD BIT(16)
> +
> +#define DP1000_PERI_MAX_RATE 62500000UL
> +#define DP1000_CLK_NUM 21
> +
> +static const struct ultrarisc_pll_layout dp1000_pll_layout = {
> + .cfg1_offset = DP1000_PLL_CFG1_OFFSET,
> + .cfg2_offset = DP1000_PLL_CFG2_OFFSET,
> + .frac_mask = GENMASK(23, 0),
> + .m_mask = GENMASK(23, 16),
> + .n_mask = GENMASK(11, 6),
> + .oddiv1_mask = GENMASK(4, 3),
> + .oddiv2_mask = GENMASK(1, 0),
> +};
> +
> +static const struct ultrarisc_pll_desc dp1000_plls[] = {
> + {
> + .id = DP1000_CLK_SYSPLL,
> + .name = "syspll_clk",
> + },
> +};
> +
> +#define DP1000_FIXED_FACTOR(_id, _name, _parent, _mult, _div) \
> + { \
> + .id = (_id), \
> + .name = (_name), \
> + .parent_id = (_parent), \
> + .mult = (_mult), \
> + .div = (_div), \
> + }
> +
> +#define DP1000_DIV(_id, _name, _offset, _parent, _max_rate) \
> + { \
> + .id = (_id), \
> + .name = (_name), \
> + .offset = (_offset), \
> + .parent_id = (_parent), \
> + .max_rate = (_max_rate), \
> + .load_mask = DP1000_CCR_LOAD, \
> + .div_shift = 8, \
> + .div_width = 4, \
> + .gate_bit = 0, \
> + .divider_flags = CLK_DIVIDER_ONE_BASED, \
> + .gate_flags = 0, \
> + }
> +
> +#define DP1000_GATE(_id, _name, _parent, _bit) \
> + { \
> + .id = (_id), \
> + .name = (_name), \
> + .offset = DP1000_PERI_CLKENA_OFFSET, \
> + .parent_id = (_parent), \
> + .gate_bit = (_bit), \
> + .gate_flags = 0, \
> + }
> +
> +static const struct ultrarisc_fixed_factor_desc dp1000_fixed_factor_clks[] = {
> + DP1000_FIXED_FACTOR(DP1000_CLK_SYSPLL_DIV2, "syspll_div2_clk",
> + DP1000_CLK_SYSPLL, 1, 2),
> + DP1000_FIXED_FACTOR(DP1000_CLK_SUBSYS, "subsys_clk",
> + DP1000_CLK_SYSPLL_DIV2, 1, 2),
> + DP1000_FIXED_FACTOR(DP1000_CLK_PCIE_DBI, "pcie_dbi_clk",
> + DP1000_CLK_SYSPLL, 1, 10),
> + DP1000_FIXED_FACTOR(DP1000_CLK_PCIEX4_CORE, "pciex4_core_clk",
> + DP1000_CLK_SYSPLL, 1, 2),
> + DP1000_FIXED_FACTOR(DP1000_CLK_PCIEX16_CORE, "pciex16_core_clk",
> + DP1000_CLK_SYSPLL, 1, 1),
> + DP1000_FIXED_FACTOR(DP1000_CLK_PCIE_AUX, "pcie_aux_clk",
> + DP1000_CLK_SYSPLL, 1, 40),
> +};
> +
> +static const struct ultrarisc_divider_desc dp1000_divider_clks[] = {
> + DP1000_DIV(DP1000_CLK_GMAC, "gmac_clk", DP1000_CCR_GMAC_OFFSET,
> + DP1000_CLK_SYSPLL_DIV2, 0),
> + DP1000_DIV(DP1000_CLK_UART_ROOT, "uart_root_clk",
> + DP1000_CCR_UART_OFFSET, DP1000_CLK_SUBSYS,
> + DP1000_PERI_MAX_RATE),
> + DP1000_DIV(DP1000_CLK_I2C_ROOT, "i2c_root_clk",
> + DP1000_CCR_I2C_OFFSET, DP1000_CLK_SUBSYS,
> + DP1000_PERI_MAX_RATE),
> + DP1000_DIV(DP1000_CLK_SPI_ROOT, "spi_root_clk",
> + DP1000_CCR_SPI_OFFSET, DP1000_CLK_SUBSYS,
> + DP1000_PERI_MAX_RATE),
> +};
> +
> +static const struct ultrarisc_gate_desc dp1000_gate_clks[] = {
> + DP1000_GATE(DP1000_CLK_UART0, "uart0_clk", DP1000_CLK_UART_ROOT, 0),
> + DP1000_GATE(DP1000_CLK_UART1, "uart1_clk", DP1000_CLK_UART_ROOT, 1),
> + DP1000_GATE(DP1000_CLK_UART2, "uart2_clk", DP1000_CLK_UART_ROOT, 2),
> + DP1000_GATE(DP1000_CLK_UART3, "uart3_clk", DP1000_CLK_UART_ROOT, 3),
> + DP1000_GATE(DP1000_CLK_I2C0, "i2c0_clk", DP1000_CLK_I2C_ROOT, 4),
> + DP1000_GATE(DP1000_CLK_I2C1, "i2c1_clk", DP1000_CLK_I2C_ROOT, 5),
> + DP1000_GATE(DP1000_CLK_I2C2, "i2c2_clk", DP1000_CLK_I2C_ROOT, 6),
> + DP1000_GATE(DP1000_CLK_I2C3, "i2c3_clk", DP1000_CLK_I2C_ROOT, 7),
> + DP1000_GATE(DP1000_CLK_SPI0, "spi0_clk", DP1000_CLK_SPI_ROOT, 8),
> + DP1000_GATE(DP1000_CLK_SPI1, "spi1_clk", DP1000_CLK_SPI_ROOT, 9),
> +};
> +
> +static const struct ultrarisc_clk_soc_data dp1000_clk_soc_data = {
> + .num_clks = DP1000_CLK_NUM,
> + .pll_layout = &dp1000_pll_layout,
> + .plls = dp1000_plls,
> + .num_plls = ARRAY_SIZE(dp1000_plls),
> + .fixed_factors = dp1000_fixed_factor_clks,
> + .num_fixed_factors = ARRAY_SIZE(dp1000_fixed_factor_clks),
> + .dividers = dp1000_divider_clks,
> + .num_dividers = ARRAY_SIZE(dp1000_divider_clks),
> + .gates = dp1000_gate_clks,
> + .num_gates = ARRAY_SIZE(dp1000_gate_clks),
> +};
> +
> +static int dp1000_clk_probe(struct platform_device *pdev)
> +{
> + return ultrarisc_clk_probe(pdev, &dp1000_clk_soc_data);
> +}
> +
> +static const struct of_device_id dp1000_clk_of_match[] = {
> + { .compatible = "ultrarisc,dp1000-clk" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, dp1000_clk_of_match);
> +
> +static struct platform_driver dp1000_clk_driver = {
> + .probe = dp1000_clk_probe,
> + .driver = {
> + .name = "ultrarisc-dp1000-clk",
> + .of_match_table = dp1000_clk_of_match,
> + },
> +};
> +module_platform_driver(dp1000_clk_driver);
> +
> +MODULE_DESCRIPTION("UltraRISC DP1000 clock controller");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/ultrarisc/clk-ultrarisc.c b/drivers/clk/ultrarisc/clk-ultrarisc.c
> new file mode 100644
> index 000000000000..4ef222348dd7
> --- /dev/null
> +++ b/drivers/clk/ultrarisc/clk-ultrarisc.c
> @@ -0,0 +1,459 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk-provider.h>
> +#include <linux/io.h>
> +#include <linux/math64.h>
> +#include <linux/module.h>
> +#include <linux/of_clk.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#include "clk-ultrarisc.h"
> +
> +struct ultrarisc_pll_clk {
> + struct clk_hw hw;
> + void __iomem *base;
> + const struct ultrarisc_pll_layout *layout;
> +};
> +
> +struct ultrarisc_divider_clk {
> + struct clk_divider divider;
> + struct clk_gate gate;
> + u32 load_mask;
> +};
> +
> +#define to_ultrarisc_pll_clk(_hw) \
> + container_of(_hw, struct ultrarisc_pll_clk, hw)
> +
> +static inline struct ultrarisc_divider_clk *
> +to_ultrarisc_divider_clk(struct clk_hw *hw)
> +{
> + struct clk_divider *divider = to_clk_divider(hw);
> +
> + return container_of(divider, struct ultrarisc_divider_clk, divider);
> +}
> +
> +static unsigned long ultrarisc_pll_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct ultrarisc_pll_clk *pll = to_ultrarisc_pll_clk(hw);
> + const struct ultrarisc_pll_layout *layout = pll->layout;
> + u32 oddiv1_div, oddiv2_div;
> + u64 mult, rate, den;
> + u32 frac, m, n;
> + u32 cfg1, cfg2;
> +
> + cfg1 = readl_relaxed(pll->base + layout->cfg1_offset);
> + cfg2 = readl_relaxed(pll->base + layout->cfg2_offset);
> +
> + frac = field_get(layout->frac_mask, cfg1);
> + m = field_get(layout->m_mask, cfg2);
> + n = field_get(layout->n_mask, cfg2);
> + oddiv1_div = 1U << field_get(layout->oddiv1_mask, cfg2);
> + oddiv2_div = 1U << field_get(layout->oddiv2_mask, cfg2);
> +
> + if (!n)
> + return 0;
> +
> + /*
> + * The output frequency is calculated as:
> + * fvco = parent * (m + frac / 2^24) / n
> + * fout = fvco / (2^oddiv1_raw * 2^oddiv2_raw)
> + *
> + * The output divider values are derived from the raw register field
> + * values as:
> + * oddivX_div = 1 << oddivX_raw
> + */
> + mult = ((u64)m << 24) + frac;
> + rate = (u64)parent_rate * mult;
> + den = ((u64)n << 24) * oddiv1_div * oddiv2_div;
> +
> + return div64_u64(rate + (den >> 1), den);
> +}
> +
> +static const struct clk_ops ultrarisc_pll_ro_ops = {
> + .recalc_rate = ultrarisc_pll_recalc_rate,
> +};
> +
> +static unsigned long
> +ultrarisc_divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
> +{
> + struct clk_divider *divider = to_clk_divider(hw);
> + u32 val;
> +
> + val = readl_relaxed(divider->reg) >> divider->shift;
> + val &= clk_div_mask(divider->width);
> +
> + return divider_recalc_rate(hw, parent_rate, val, divider->table,
> + divider->flags, divider->width);
> +}
> +
> +static int ultrarisc_divider_determine_rate(struct clk_hw *hw,
> + struct clk_rate_request *req)
> +{
> + struct clk_divider *divider = to_clk_divider(hw);
> +
> + return divider_determine_rate(hw, req, divider->table, divider->width,
> + divider->flags);
> +}
> +
> +static int ultrarisc_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct ultrarisc_divider_clk *divider_clk = to_ultrarisc_divider_clk(hw);
> + struct clk_divider *divider = ÷r_clk->divider;
> + int value;
> + u32 val;
> +
> + value = divider_get_val(rate, parent_rate, divider->table,
> + divider->width, divider->flags);
> + if (value < 0)
> + return value;
> +
> + scoped_guard(spinlock_irqsave, divider->lock) {
> + val = readl_relaxed(divider->reg);
> + val &= ~(clk_div_mask(divider->width) << divider->shift);
> + val |= value << divider->shift;
> + writel_relaxed(val, divider->reg);
> +
> + if (divider_clk->load_mask) {
> + /*
> + * Program the new divider field, then write 1 to the
> + * load bit to trigger the update. The load bit is
> + * write-triggered and reads back as 0 on this hardware.
> + */
> + writel_relaxed(val | divider_clk->load_mask,
> + divider->reg);
> + }
> + }
> +
> + return 0;
> +}
> +
> +static const struct clk_ops ultrarisc_divider_ops = {
> + .recalc_rate = ultrarisc_divider_recalc_rate,
> + .determine_rate = ultrarisc_divider_determine_rate,
> + .set_rate = ultrarisc_divider_set_rate,
> +};
> +
> +static void ultrarisc_clk_unregister_composite(void *data)
> +{
> + clk_hw_unregister_composite(data);
> +}
> +
> +static struct clk_hw *
> +ultrarisc_clk_hw_register_composite(struct device *dev, const char *name,
> + const char * const *parent_names,
> + int num_parents, struct clk_hw *mux_hw,
> + const struct clk_ops *mux_ops,
> + struct clk_hw *rate_hw,
> + const struct clk_ops *rate_ops,
> + struct clk_hw *gate_hw,
> + const struct clk_ops *gate_ops,
> + unsigned long flags)
> +{
> + struct clk_hw *hw;
> + int ret;
> +
> + hw = clk_hw_register_composite(dev, name, parent_names, num_parents,
> + mux_hw, mux_ops, rate_hw, rate_ops,
> + gate_hw, gate_ops, flags);
> + if (IS_ERR(hw))
> + return hw;
> +
> + ret = devm_add_action_or_reset(dev, ultrarisc_clk_unregister_composite,
> + hw);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + return hw;
> +}
> +
> +static struct clk_hw *ultrarisc_clk_register_pll(struct device *dev,
> + const struct ultrarisc_pll_desc *desc,
> + const struct ultrarisc_pll_layout *layout,
> + void __iomem *base)
> +{
> + struct clk_parent_data pdata = { .index = 0 };
> + struct ultrarisc_pll_clk *pll;
> + struct clk_init_data init = {
> + .name = desc->name,
> + .ops = &ultrarisc_pll_ro_ops,
> + .parent_data = &pdata,
> + .num_parents = 1,
> + .flags = CLK_GET_RATE_NOCACHE,
> + };
> + int ret;
> +
> + pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
> + if (!pll)
> + return ERR_PTR(-ENOMEM);
> +
> + pll->base = base;
> + pll->layout = layout;
> + pll->hw.init = &init;
> +
> + ret = devm_clk_hw_register(dev, &pll->hw);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + return &pll->hw;
> +}
> +
> +static struct clk_hw *
> +ultrarisc_clk_register_divider(struct device *dev,
> + const struct ultrarisc_divider_desc *desc,
> + struct clk_hw *parent_hw, void __iomem *base,
> + spinlock_t *lock)
> +{
> + const char * const parent_names[] = { clk_hw_get_name(parent_hw) };
> + void __iomem *reg = base + desc->offset;
> + struct ultrarisc_divider_clk *divider;
> + const struct clk_ops *gate_ops = NULL;
> + struct clk_hw *gate_hw = NULL;
> +
> + if (!desc->div_width)
> + return ERR_PTR(-EINVAL);
> +
> + if (!lock)
> + return ERR_PTR(-EINVAL);
> +
> + divider = devm_kzalloc(dev, sizeof(*divider), GFP_KERNEL);
> + if (!divider)
> + return ERR_PTR(-ENOMEM);
> +
> + divider->divider.reg = reg;
> + divider->divider.shift = desc->div_shift;
> + divider->divider.width = desc->div_width;
> + divider->divider.flags = desc->divider_flags;
> + divider->divider.lock = lock;
> + divider->load_mask = desc->load_mask;
> +
> + if (desc->gate_bit != ULTRARISC_CLK_NO_GATE) {
> + divider->gate.reg = reg;
> + divider->gate.bit_idx = desc->gate_bit;
> + divider->gate.flags = desc->gate_flags;
> + divider->gate.lock = lock;
> + gate_hw = ÷r->gate.hw;
> + gate_ops = &clk_gate_ops;
> + }
> +
> + return ultrarisc_clk_hw_register_composite(dev, desc->name,
> + parent_names, 1,
> + NULL, NULL,
> + ÷r->divider.hw,
> + &ultrarisc_divider_ops,
> + gate_hw, gate_ops,
> + CLK_GET_RATE_NOCACHE);
> +}
> +
> +static int ultrarisc_clk_register_fixed_factors(struct device *dev,
> + struct clk_hw_onecell_data *clk_data,
> + const struct ultrarisc_clk_soc_data *soc_data)
> +{
> + u32 i;
> +
> + for (i = 0; i < soc_data->num_fixed_factors; i++) {
> + const struct ultrarisc_fixed_factor_desc *desc;
> + struct clk_hw *parent_hw;
> + struct clk_hw *hw;
> +
> + desc = &soc_data->fixed_factors[i];
> + if (desc->id >= clk_data->num ||
> + desc->parent_id >= clk_data->num)
> + return -EINVAL;
> +
> + parent_hw = clk_data->hws[desc->parent_id];
> + if (!parent_hw)
> + return -EINVAL;
> +
> + hw = devm_clk_hw_register_fixed_factor_parent_hw(dev, desc->name,
> + parent_hw,
> + CLK_GET_RATE_NOCACHE,
> + desc->mult,
> + desc->div);
> + if (IS_ERR(hw))
> + return PTR_ERR(hw);
> +
> + clk_data->hws[desc->id] = hw;
> + }
> +
> + return 0;
> +}
> +
> +static int ultrarisc_clk_register_plls(struct platform_device *pdev,
> + struct clk_hw_onecell_data *clk_data,
> + const struct ultrarisc_clk_soc_data *soc_data,
> + void __iomem *base)
> +{
> + struct device *dev = &pdev->dev;
> + u32 i;
> +
> + for (i = 0; i < soc_data->num_plls; i++) {
> + const struct ultrarisc_pll_desc *desc = &soc_data->plls[i];
> + struct clk_hw *hw;
> +
> + if (desc->id >= clk_data->num)
> + return -EINVAL;
> +
> + hw = ultrarisc_clk_register_pll(dev, desc, soc_data->pll_layout,
> + base);
> + if (IS_ERR(hw))
> + return PTR_ERR(hw);
> +
> + clk_data->hws[desc->id] = hw;
> + }
> +
> + return 0;
> +}
> +
> +static int ultrarisc_clk_register_dividers(struct platform_device *pdev,
> + struct clk_hw_onecell_data *clk_data,
> + const struct ultrarisc_clk_soc_data *soc_data,
> + void __iomem *base,
> + spinlock_t *lock)
> +{
> + struct device *dev = &pdev->dev;
> + u32 i;
> +
> + for (i = 0; i < soc_data->num_dividers; i++) {
> + const struct ultrarisc_divider_desc *desc;
> + struct clk_hw *parent_hw;
> + struct clk_hw *hw;
> +
> + desc = &soc_data->dividers[i];
> + if (desc->id >= clk_data->num ||
> + desc->parent_id >= clk_data->num)
> + return -EINVAL;
> +
> + parent_hw = clk_data->hws[desc->parent_id];
> + if (!parent_hw)
> + return -EINVAL;
> +
> + hw = ultrarisc_clk_register_divider(dev, desc, parent_hw, base,
> + lock);
> + if (IS_ERR(hw))
> + return PTR_ERR(hw);
> +
> + if (desc->max_rate) {
> + unsigned long rate;
> +
> + clk_hw_set_rate_range(hw, 0, desc->max_rate);
> +
> + rate = clk_hw_get_rate(hw);
> + if (rate > desc->max_rate)
> + dev_warn(dev, "%s rate %lu exceeds max %lu\n",
> + desc->name, rate, desc->max_rate);
> + }
> +
> + clk_data->hws[desc->id] = hw;
> + }
> +
> + return 0;
> +}
> +
> +static int ultrarisc_clk_register_gates(struct platform_device *pdev,
> + struct clk_hw_onecell_data *clk_data,
> + const struct ultrarisc_clk_soc_data *soc_data,
> + void __iomem *base,
> + spinlock_t *lock)
> +{
> + struct device *dev = &pdev->dev;
> + u32 i;
> +
> + for (i = 0; i < soc_data->num_gates; i++) {
> + const struct ultrarisc_gate_desc *desc;
> + struct clk_hw *parent_hw;
> + struct clk_hw *hw;
> +
> + desc = &soc_data->gates[i];
> + if (desc->id >= clk_data->num ||
> + desc->parent_id >= clk_data->num)
> + return -EINVAL;
> +
> + parent_hw = clk_data->hws[desc->parent_id];
> + if (!parent_hw)
> + return -EINVAL;
> +
> + hw = devm_clk_hw_register_gate_parent_hw(dev, desc->name,
> + parent_hw,
> + CLK_GET_RATE_NOCACHE,
> + base + desc->offset,
> + desc->gate_bit,
> + desc->gate_flags,
> + lock);
> + if (IS_ERR(hw))
> + return PTR_ERR(hw);
> +
> + clk_data->hws[desc->id] = hw;
> + }
> +
> + return 0;
> +}
> +
> +int ultrarisc_clk_probe(struct platform_device *pdev,
> + const struct ultrarisc_clk_soc_data *soc_data)
> +{
> + struct clk_hw_onecell_data *clk_data;
> + struct device *dev = &pdev->dev;
> + void __iomem *base;
> + spinlock_t *lock;
> + int ret;
> +
> + if (!soc_data)
> + return -EINVAL;
> +
> + lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
> + if (!lock)
> + return -ENOMEM;
> +
> + spin_lock_init(lock);
> +
> + clk_data = devm_kzalloc(dev, struct_size(clk_data, hws,
> + soc_data->num_clks),
> + GFP_KERNEL);
> + if (!clk_data)
> + return -ENOMEM;
> +
> + clk_data->num = soc_data->num_clks;
> +
> + base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(base))
> + return PTR_ERR(base);
> +
> + ret = ultrarisc_clk_register_plls(pdev, clk_data, soc_data, base);
> + if (ret)
> + return ret;
> +
> + ret = ultrarisc_clk_register_fixed_factors(dev, clk_data, soc_data);
> + if (ret)
> + return ret;
> +
> + ret = ultrarisc_clk_register_dividers(pdev, clk_data, soc_data, base,
> + lock);
> + if (ret)
> + return ret;
> +
> + ret = ultrarisc_clk_register_gates(pdev, clk_data, soc_data, base,
> + lock);
> + if (ret)
> + return ret;
> +
> + for (int i = 0; i < clk_data->num; i++) {
> + if (!clk_data->hws[i]) {
> + dev_err(dev, "missing clock ID %u\n", i);
> + return -EINVAL;
> + }
> + }
> +
> + return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
> + clk_data);
> +}
> +EXPORT_SYMBOL_GPL(ultrarisc_clk_probe);
> +
> +MODULE_DESCRIPTION("UltraRISC clock core driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/ultrarisc/clk-ultrarisc.h b/drivers/clk/ultrarisc/clk-ultrarisc.h
> new file mode 100644
> index 000000000000..1281196bb414
> --- /dev/null
> +++ b/drivers/clk/ultrarisc/clk-ultrarisc.h
> @@ -0,0 +1,73 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef __ULTRARISC_CLK_ULTRARISC_H
> +#define __ULTRARISC_CLK_ULTRARISC_H
> +
> +#include <linux/clk-provider.h>
> +#include <linux/platform_device.h>
> +#include <linux/types.h>
> +
> +#define ULTRARISC_CLK_NO_GATE (-1)
> +
> +struct ultrarisc_pll_layout {
> + u32 cfg1_offset;
> + u32 cfg2_offset;
> + u32 frac_mask;
> + u32 m_mask;
> + u32 n_mask;
> + u32 oddiv1_mask;
> + u32 oddiv2_mask;
> +};
> +
> +struct ultrarisc_pll_desc {
> + u32 id;
> + const char *name;
> +};
> +
> +struct ultrarisc_fixed_factor_desc {
> + u32 id;
> + const char *name;
> + u32 parent_id;
> + u32 mult;
> + u32 div;
> +};
> +
> +struct ultrarisc_divider_desc {
> + u32 id;
> + const char *name;
> + u32 offset;
> + u32 parent_id;
> + unsigned long max_rate;
> + u32 load_mask;
> + u8 div_shift;
> + u8 div_width;
> + s8 gate_bit;
> + u16 divider_flags;
> + u8 gate_flags;
> +};
> +
> +struct ultrarisc_gate_desc {
> + u32 id;
> + const char *name;
> + u32 offset;
> + u32 parent_id;
> + u8 gate_bit;
> + u8 gate_flags;
> +};
> +
> +struct ultrarisc_clk_soc_data {
> + const struct ultrarisc_pll_layout *pll_layout;
> + const struct ultrarisc_pll_desc *plls;
> + u32 num_plls;
> + const struct ultrarisc_fixed_factor_desc *fixed_factors;
> + u32 num_fixed_factors;
> + const struct ultrarisc_divider_desc *dividers;
> + u32 num_dividers;
> + const struct ultrarisc_gate_desc *gates;
> + u32 num_gates;
> + u32 num_clks;
> +};
> +
> +int ultrarisc_clk_probe(struct platform_device *pdev,
> + const struct ultrarisc_clk_soc_data *soc_data);
> +
> +#endif /* __ULTRARISC_CLK_ULTRARISC_H */
>
> --
> 2.34.1
>
>
Gentle ping.
Best Regards,
Jia Wang
^ permalink raw reply
* Re: [PATCH v2 2/2] arm64: dts: qcom: kaanapali: fix traceNoC probe issue
From: Jie Gan @ 2026-06-25 1:01 UTC (permalink / raw)
To: Leo Yan, Suzuki K Poulose
Cc: Konrad Dybcio, Bjorn Andersson, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Tingwei Zhang, Jingyi Wang,
Abel Vesa, Mike Leach, James Clark, Yuanfang Zhang, linux-arm-msm,
devicetree, linux-kernel, coresight, linux-arm-kernel
In-Reply-To: <20260624151610.GC575984@e132581.arm.com>
On 6/24/2026 11:16 PM, Leo Yan wrote:
> On Wed, Jun 24, 2026 at 11:08:32PM +0800, Jie Gan wrote:
>
> [...]
>
>>> Why does it fail ? power management ? hw broken ? Is it really AMBA or
>>> do you pretend that to be an AMBA device by faking the CID/PID?
>>
>> The CID reads as 0 from the register, which I suspect is a hardware design
>> issue. I have not yet confirmed this with the hardware team. As a
>> workaround, I provided a fake periphid via a DT property to bypass
>> amba_read_periphid.
>>
>>
>> Leo commented in other thread:
>>>> tnoc.c registers both an AMBA driver and a platform driver. Shouldn't >>it
>>>> be registered as a platform device instead?
>>
>> The platform driver is intended for the interconnect TraceNoC device and is
>> not designed to allocate an ATID. The issue is that the TPDM device borrows
>> the ATID from the TraceNoC device, resulting in the ATID always being 0 when
>> associated with an interconnect NoC device.
>>
>> However, I believe it is acceptable to allocate an ATID for the itNoC device
>> and the issue can be fixed with this way.
>
> I think so.
Hi Suzuki/Leo
Which solution do you prefer to address the issue?
The interconnect traceNoC platform driver is intended for the itnoc
device, implying that no TPDM devices are connected to it. So, if I
modify it to allocate an ATID, I think it would be better to rename the
“itnoc” node accordingly? Or it's ok to leave it as-is?
BTW, the traceNoC device definitely is an AMBA device with CID/PID
registers.
Thanks,
Jie
^ permalink raw reply
* Re: [PATCH v6 1/2] dt-bindings: bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
From: Sunyun Yang @ 2026-06-25 0:52 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
dmitry.baryshkov, maarten.lankhorst, rfoss, mripard,
Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng
In-Reply-To: <495f0dc3-b5ee-429e-bc60-78b13bcb42c3@kernel.org>
Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月24日周三 22:05写道:
>
> On 11/05/2026 05:28, Sunyun Yang wrote:
> > <syyang@lontium.com> 于2026年5月8日周五 22:25写道:
> >>
> >> From: Sunyun Yang <syyang@lontium.com>
> >>
> >> LT9611C(EX/UXD) is an I2C-controlled chip that Receiver signal/dual port
> >> mipi dsi and output hdmi, differences in hardware features:
> >> - LT9611C: supports 1-port mipi dsi to hdmi 1.4
> >> - LT9611EX: supports 2-port mipi dsi to hdmi 1.4
> >> - LT9611UXD: supports 2-port mipi dsi to hdmi 1.4/2.0
> >>
> >> Signed-off-by: Sunyun Yang <syyang@lontium.com>
> >> ---
> >> .../bindings/display/bridge/lontium,lt9611.yaml | 8 ++++++--
> >> 1 file changed, 6 insertions(+), 2 deletions(-)
> >>
> >> diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> >> index 429a06057ae8..e0821a63d9d7 100644
> >> --- a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> >> +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> >> @@ -4,19 +4,23 @@
> >> $id: http://devicetree.org/schemas/display/bridge/lontium,lt9611.yaml#
> >> $schema: http://devicetree.org/meta-schemas/core.yaml#
> >>
> >> -title: Lontium LT9611(UXC) 2 Port MIPI to HDMI Bridge
> >> +title: Lontium LT9611(UXC/C/EX/UXD) 2 Port MIPI DSI to HDMI Bridge
> >>
> >> maintainers:
> >> - Vinod Koul <vkoul@kernel.org>
> >>
> >> description: |
> >> - The LT9611 and LT9611UXC are bridge devices which convert DSI to HDMI
> >> + The LT9611、LT9611UXC、LT9611C、LT9611EX and LT9611UXD
> >> + are bridge devices which convert DSI to HDMI
> >>
> >> properties:
> >> compatible:
> >> enum:
> >> - lontium,lt9611
> >> + - lontium,lt9611c
> >> + - lontium,lt9611ex
> >> - lontium,lt9611uxc
> >> + - lontium,lt9611uxd
> >>
> >> reg:
> >> maxItems: 1
> >> --
> >
> > Gentle ping.
> > Thanks.
>
> Except mess with threading, your patchset does not build, when applied
> on next-20260618.
>
> What is the base of this?
>
Thanks for testing my patchset on next-20260618.
The base of this series is v7.0.
> Best regards,
> Krzysztof
^ permalink raw reply
* RE: [PATCH 2/3] dt-bindings: hwmon: pmbus: Support for onsemi's FD5121
From: Selvamani Rajagopal @ 2026-06-25 0:35 UTC (permalink / raw)
To: Guenter Roeck, Conor Dooley
Cc: Jonathan Corbet, Shuah Khan, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, linux-hwmon@vger.kernel.org,
linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
devicetree@vger.kernel.org
In-Reply-To: <eed3e19b-8cc7-4aef-b058-b2242c94c940@roeck-us.net>
> -----Original Message-----
> From: Guenter Roeck <groeck7@gmail.com> On Behalf Of Guenter Roeck
> Subject: Re: [PATCH 2/3] dt-bindings: hwmon: pmbus: Support for onsemi's FD5121
>
>
> One of the problems here is that the chip datasheet is not public,
Agree on both points. No datasheets on our website. And chips not available through distributors. Little
early in the process. I am working with our product team to see how to move forward. So, there will be some
delay in giving next patch. Thanks for your feedback. I have all the information to send a cleaner, hopefully
acceptable patch,
> so we can not verify what this actually is. The only available
> public document appears to be the "onsemi FD512x Ax Digital Controller
> User Manual" which describes the chip as follows.
>
> "The FD512x Digital Controller is a programmable device designed
> for machine vendors to configure their equipment at the factory."
>
> That really does not explain anything at all, and actually looks like
> an AI generated summary with the AI not understanding what it is talking
> about. According to the onsemi web page, the chip does not exist,
> and it appears that it is not available to buy from any distributors
> either.
>
> Guenter
>
^ permalink raw reply
* Re: [PATCH 2/3] dt-bindings: hwmon: pmbus: Support for onsemi's FD5121
From: Guenter Roeck @ 2026-06-24 23:05 UTC (permalink / raw)
To: Selvamani Rajagopal, Conor Dooley
Cc: Jonathan Corbet, Shuah Khan, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, linux-hwmon@vger.kernel.org,
linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
devicetree@vger.kernel.org
In-Reply-To: <CYYPR02MB9828EECB3F6AFDD2A7BD3E9B83ED2@CYYPR02MB9828.namprd02.prod.outlook.com>
On 6/24/26 15:36, Selvamani Rajagopal wrote:
>> -----Original Message-----
>> From: Conor Dooley <conor@kernel.org>
>> Subject: Re: [PATCH 2/3] dt-bindings: hwmon: pmbus: Support for onsemi's FD5121
>>
>>
>> My point is that what's actually being controlled is missing. Maybe it
>> is obvious to you, but it is not to me. Your nodename in your example is
>
>
> You are right. This chip may not be a "controller" in the traditional sense as it doesn't control anything.
> We can change node naming to sensor or regulator so that it aligns with the convention.
>
One of the problems here is that the chip datasheet is not public,
so we can not verify what this actually is. The only available
public document appears to be the "onsemi FD512x Ax Digital Controller
User Manual" which describes the chip as follows.
"The FD512x Digital Controller is a programmable device designed
for machine vendors to configure their equipment at the factory."
That really does not explain anything at all, and actually looks like
an AI generated summary with the AI not understanding what it is talking
about. According to the onsemi web page, the chip does not exist,
and it appears that it is not available to buy from any distributors
either.
Guenter
>
>>> + fd5121@50 {
>> which doesn't comply with node naming requirements and I wanted to come
>> up with a suggestion for what it should be.
>> I am assuming that its power or voltage that you're controlling so
>> either it should be hwmon@ or regulator@.
>>
>
^ permalink raw reply
* Re: [PATCH v4 03/11] HID: spi-hid: add transport driver skeleton for HID over SPI bus
From: Jingyuan Liang @ 2026-06-24 22:41 UTC (permalink / raw)
To: sashiko-reviews; +Cc: robh, conor+dt, devicetree, dmitry.torokhov, linux-input
In-Reply-To: <20260609045604.44FE71F00893@smtp.kernel.org>
On Mon, Jun 8, 2026 at 9:56 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] Embedded arrays `read_approval_header` and `read_approval_body` are used for SPI DMA transfers without cacheline alignment, risking memory corruption.
> - [Medium] Unchecked return values of reset operations in `spi_hid_dev_init` and `spi_hid_resume`
> --
>
> commit ea35a3a23739acaca367a8d250ef25c96d22df36
> Author: Angela Czubak <acz@semihalf.com>
>
> HID: spi-hid: add transport driver skeleton for HID over SPI bus
>
> Create spi-hid folder and add Kconfig and Makefile for spi-hid driver.
> Add basic device structure, definitions, and probe/remove functions.
>
> > diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-hid-core.c
> > new file mode 100644
> > index 0000000000000..02a7608c4b882
> > --- /dev/null
> > +++ b/drivers/hid/spi-hid/spi-hid-core.c
> > @@ -0,0 +1,213 @@
>
> [ ... ]
>
> > +/* Driver context */
> > +struct spi_hid {
> > + struct spi_device *spi; /* spi device. */
> > + struct hid_device *hid; /* pointer to corresponding HID dev. */
> > +
> > + struct spihid_ops *ops;
> > + struct spi_hid_conf *conf;
> > +
> > + enum hidspi_power_state power_state;
> > +
> > + u32 regulator_error_count;
> > + int regulator_last_error;
> > + u32 bus_error_count;
> > + int bus_last_error;
> > + u32 dir_count; /* device initiated reset count. */
> > +};
>
> [Severity: High]
> Does embedding the read_approval_header and read_approval_body arrays directly
> in this struct for SPI DMA transfers risk memory corruption?
>
> When these buffers are passed as tx_buf to spi_sync() (e.g. in
> spi_hid_input_sync()), they lack the ____cacheline_aligned directive. On
> architectures with non-coherent DMA, cache maintenance operations could
> encompass adjacent struct members.
>
> Concurrent CPU writes to those adjacent members during the DMA mapping window
> will be destroyed, leading to memory corruption.
>
> Should these buffers be cacheline aligned, or dynamically allocated?
read_approval_header and read_approval_body arrays are not in this patch.
>
> [ ... ]
>
> > +int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
> > + struct spi_hid_conf *conf)
> > +{
>
> [ ... ]
>
> > + /*
> > + * At the end of probe we initialize the device:
> > + * 0) assert reset, bias the interrupt line
> > + * 1) sleep minimal reset delay
> > + * 2) request IRQ
> > + * 3) power up the device
> > + * 4) deassert reset (high)
> > + * After this we expect an IRQ with a reset response.
> > + */
> > +
> > + shid->ops->assert_reset(shid->ops);
> > +
> > + shid->ops->sleep_minimal_reset_delay(shid->ops);
>
> [ ... ]
>
> > + error = shid->ops->power_up(shid->ops);
> > + if (error) {
> > + dev_err(dev, "%s: could not power up\n", __func__);
> > + return error;
> > + }
> > +
> > + shid->ops->deassert_reset(shid->ops);
>
> [Severity: Medium]
> Can hardware reset failures be masked here?
>
> The assert_reset and deassert_reset callbacks return integer error codes.
> However, their return values are ignored in spi_hid_core_probe() (as well as
> resume paths).
>
> If deassert_reset() fails, the device may remain in reset, causing the driver
> to hang waiting for a reset response IRQ that will never arrive.
>
> Should these return values be checked and handled?
>
> > +
> > + dev_dbg(dev, "%s: d3 -> %s\n", __func__,
> > + spi_hid_power_mode_string(shid->power_state));
> > +
> > + return 0;
> > +}
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260609-send-upstream-v4-0-b843d5e6ced3@chromium.org?part=3
^ permalink raw reply
* RE: [PATCH 2/3] dt-bindings: hwmon: pmbus: Support for onsemi's FD5121
From: Selvamani Rajagopal @ 2026-06-24 22:36 UTC (permalink / raw)
To: Conor Dooley
Cc: Guenter Roeck, Jonathan Corbet, Shuah Khan, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, linux-hwmon@vger.kernel.org,
linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
devicetree@vger.kernel.org
In-Reply-To: <20260623-scared-judgingly-7efc1c188670@spud>
> -----Original Message-----
> From: Conor Dooley <conor@kernel.org>
> Subject: Re: [PATCH 2/3] dt-bindings: hwmon: pmbus: Support for onsemi's FD5121
>
>
> My point is that what's actually being controlled is missing. Maybe it
> is obvious to you, but it is not to me. Your nodename in your example is
You are right. This chip may not be a "controller" in the traditional sense as it doesn't control anything.
We can change node naming to sensor or regulator so that it aligns with the convention.
> > + fd5121@50 {
> which doesn't comply with node naming requirements and I wanted to come
> up with a suggestion for what it should be.
> I am assuming that its power or voltage that you're controlling so
> either it should be hwmon@ or regulator@.
>
^ permalink raw reply
* Re: [PATCH v3 0/2] regulator: qcom-rpmh: Add off-on-delay support
From: Kamal Wadhwa @ 2026-06-24 22:16 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Saikiran, broonie, monish.chunara, jishnu.prakash, nitin.rawat,
Bjorn Andersson, lgirdwood, andersson, konrad.dybcio,
linux-arm-msm, linux-kernel, robh, krzk+dt, devicetree
In-Reply-To: <06a264d0-6a14-4b71-b783-82b771f04411@oss.qualcomm.com>
On Tue, Jun 16, 2026 at 01:48:50PM +0200, Konrad Dybcio wrote:
> On 5/15/26 1:16 PM, Kamal Wadhwa wrote:
> > On Wed, Jan 28, 2026 at 12:32:09AM +0530, Saikiran wrote:
> >> This series adds support for the standard `regulator-off-on-delay-us`
> >> property to the Qualcomm RPMh regulator driver and updates the
> >> corresponding Device Tree bindings.
> >>
> >> Motivation:
> >> On the Lenovo Yoga Slim 7x (Snapdragon X Elite), the camera regulators
> >> (LDO1, LDO3, LDO7) have large bulk capacitors and rely on passive discharge.
> >> When these regulators are disabled, the voltage decays very slowly. If
> >> re-enabled too quickly, the sensor experiences a brownout and fails to
> >> initialize.
> >>
> >> Verification:
> >> I verified that the core `drivers/regulator/of_regulator.c` does not
> >> currently parse `regulator-off-on-delay-us` in `of_get_regulation_constraints()`.
> >> Therefore, the driver must parse this property explicitly and populate
> >> `rdesc->off_on_delay` so the regulator core can enforce the constraint.
> >>
> >> Changes in v3:
> >> - Added Patch 1/2: Update DT bindings to allow `regulator-off-on-delay-us`
> >> for `qcom,rpmh-regulator` (Requested by Mark Brown).
> >> - Updated Patch 2/2: Refined commit message to explicitly mention the
> >> passive discharge and bulk capacitor mechanism on the Yoga Slim 7x
> >> (Requested by Mark Brown).
> >>
> >> Changes in v2:
> >> - Moved the motivation/context from the cover letter into the commit
> >> message of the driver patch.
> >>
> >> Saikiran (2):
> >> dt-bindings: regulator: qcom,rpmh: Allow regulator-off-on-delay-us
> >> regulator: qcom-rpmh: Add support for regulator-off-on-delay-us
> >
> > Hi Mark, Bjorn, Konrad and all,
> >
> > We have another UFS issue on QCS8300 RB4 EVK, where it seems this patch is
> > helping.
> >
> > Issue is seen 2/10 reboots and it happens in the UFS probe defer path:
> >
> > 1. UFS probe takes regulator handle for VCC(vreg_l8a) of UFS host controller.
> > 2. UFS probe enables the regulator
> > 3. UFS probe defers (due to some other dependency un-related to regulator)
> > 4. UFS regulator disabled on probe exit
> > 5. UFS re-attempts probe and re-enables the regulator
> > 6. UFS init sequence runs -> UFS NOP OUT command failed (no shell)
> >
> > Issue Log:
> >
> > [ 6.583836] ufshcd-qcom 1d84000.ufs: ufshcd_verify_dev_init: NOP OUT failed -11
> > [ 6.592780] ufshcd-qcom 1d84000.ufs: ufshcd_async_scan failed: -11
> >
> > NOTE
> > - Issue is not seen in first probe attempt, because UFS regulators are left ON
> > from bootloader, which gives enough time between rail turn ON and UFS init
> > sequence start. However in issue case, it seems re-probe is happening too
> > fast, which causes init sequence to fail and UFS brownouts (similar to camera
> > sensor case)
> >
> > - Also, we compared this board with other RBxx EVK boards for UFS rail, it
> > seems that this board has more caps on the VCC regulator, as the board is
> > designed to have both EMMC and UFS, and we have DT option to pick one of them.
> >
> > So for EMMC those extra caps were added and they are impacting rampup on VCC.
> >
> > Since this is not entirely a UFS part issue, but a board design constraint, it
> > seems better if we handle this in the regulator side itself, as adding it in the
> > UFS driver may not be acceptable from UFS reviewers.
> >
> > Please share your opinion, if this seems to be good reason to accept this patch?
>
> Is that board in production already, or is that something that can be fixed?
yes, it cannot be fixed in HW, as the board is already in production.
>
> Konrad
^ permalink raw reply
* Re: [PATCH v3 2/2] remoteproc: add AMD BRAM-based remote processor driver
From: Ben Levinsky @ 2026-06-24 22:01 UTC (permalink / raw)
To: Mathieu Poirier
Cc: Ben Levinsky, linux-remoteproc, devicetree, robh, krzk+dt,
conor+dt, andersson, linux-kernel, michal.simek, tanmay.shah
In-Reply-To: <CANLsYkwxErFjaOJ0q1wT_59C1WGp_24d0nOksFpk-LaGq6eDGg@mail.gmail.com>
Hi Mathieu
Picking this back up now. Please see my replies inline below.
On 5/11/26 10:41 AM, Mathieu Poirier wrote:
> On Fri, 8 May 2026 at 10:59, Ben Levinsky <blevinsk@amd.com> wrote:
>>
>> Hi Mathieu,
>>
>>
>> On 5/8/26 8:47 AM, Mathieu Poirier wrote:
>>> Good morning,
>>>
>>> On Tue, Apr 28, 2026 at 07:26:33AM -0700, Ben Levinsky wrote:
>>>> Add a remoteproc driver for AMD soft-core processor subsystems
>>>> instantiated in programmable logic and using dual-port BRAM for
>>>> firmware storage and execution.
>>>>
>>>> The driver parses the firmware memory window from the remoteproc device
>>>> node's reg property, interprets that address and size in the
>>>> processor-local address space, and then uses standard devicetree
>>>> address translation through the parent bus ranges property to obtain
>>>> the corresponding Linux-visible system physical address.
>>>>
>>>> The resulting translated region is registered as the executable
>>>> remoteproc carveout and coredump segment.
>>>>
>>>> The processor is controlled through an active-low reset GPIO and a
>>>> subsystem clock. The clock is enabled before reset is released, and the
>>>> processor is kept in reset until firmware loading completes.
>>>>
>>>> The firmware-name property is optional, allowing firmware to be
>>>> assigned later through the remoteproc framework. Firmware images
>>>> without a resource table are also accepted.
>>>>
>>>> Signed-off-by: Ben Levinsky <ben.levinsky@amd.com>
>>>> ---
>>>> MAINTAINERS | 7 +
>>>> drivers/remoteproc/Kconfig | 14 ++
>>>> drivers/remoteproc/Makefile | 1 +
>>>> drivers/remoteproc/amd_bram_rproc.c | 243 ++++++++++++++++++++++++++++
>>>> 4 files changed, 265 insertions(+)
>>>> create mode 100644 drivers/remoteproc/amd_bram_rproc.c
>>>>
>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>> index c871acf2179c..172539971950 100644
>>>> --- a/MAINTAINERS
>>>> +++ b/MAINTAINERS
>>>> @@ -1037,6 +1037,13 @@ S: Maintained
>>>> F: Documentation/devicetree/bindings/w1/amd,axi-1wire-host.yaml
>>>> F: drivers/w1/masters/amd_axi_w1.c
>>>>
>>>> +AMD BRAM REMOTEPROC DRIVER
>>>> +M: Ben Levinsky <ben.levinsky@amd.com>
>>>> +L: linux-remoteproc@vger.kernel.org
>>>> +S: Maintained
>>>> +F: Documentation/devicetree/bindings/remoteproc/amd,bram-rproc.yaml
>>>> +F: drivers/remoteproc/amd_bram_rproc.c
>>>> +
>>>
>>> There is no real advantage in adding this entry, checkpatch.pl should be
>>> sufficient.
>>>
>>>> AMD CDX BUS DRIVER
>>>> M: Nipun Gupta <nipun.gupta@amd.com>
>>>> M: Nikhil Agarwal <nikhil.agarwal@amd.com>
>>>> diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
>>>> index ee54436fea5a..9a2a887ede8a 100644
>>>> --- a/drivers/remoteproc/Kconfig
>>>> +++ b/drivers/remoteproc/Kconfig
>>>> @@ -23,6 +23,20 @@ config REMOTEPROC_CDEV
>>>>
>>>> It's safe to say N if you don't want to use this interface.
>>>>
>>>> +config AMD_BRAM_REMOTEPROC
>>>> + tristate "AMD BRAM-based remoteproc support"
>>>> + depends on OF && COMMON_CLK && (GPIOLIB || COMPILE_TEST)
>>>> + help
>>>> + Say y or m here to support a BRAM-based remote processor managed
>>>> + through the remoteproc framework.
>>>> +
>>>> + This driver matches designs where executable firmware memory is
>>>> + described in the BRAM-local address space and translated to
>>>> + the system physical address space with standard devicetree address
>>>> + translation.
>>>
>>> Not sure how this paragraph helps decide whether the driver should be enabled or
>>> not. Please remove.
>>>
>>>> +
>>>> + If unsure, say N.
>>>> +
>>>> config IMX_REMOTEPROC
>>>> tristate "i.MX remoteproc support"
>>>> depends on ARCH_MXC
>>>> diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
>>>> index 1c7598b8475d..5c39664b50c3 100644
>>>> --- a/drivers/remoteproc/Makefile
>>>> +++ b/drivers/remoteproc/Makefile
>>>> @@ -11,6 +11,7 @@ remoteproc-y += remoteproc_sysfs.o
>>>> remoteproc-y += remoteproc_virtio.o
>>>> remoteproc-y += remoteproc_elf_loader.o
>>>> obj-$(CONFIG_REMOTEPROC_CDEV) += remoteproc_cdev.o
>>>> +obj-$(CONFIG_AMD_BRAM_REMOTEPROC) += amd_bram_rproc.o
>>>> obj-$(CONFIG_IMX_REMOTEPROC) += imx_rproc.o
>>>> obj-$(CONFIG_IMX_DSP_REMOTEPROC) += imx_dsp_rproc.o
>>>> obj-$(CONFIG_INGENIC_VPU_RPROC) += ingenic_rproc.o
>>>> diff --git a/drivers/remoteproc/amd_bram_rproc.c b/drivers/remoteproc/amd_bram_rproc.c
>>>> new file mode 100644
>>>> index 000000000000..9383964b6046
>>>> --- /dev/null
>>>> +++ b/drivers/remoteproc/amd_bram_rproc.c
>>>> @@ -0,0 +1,243 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>>> +/*
>>>> + * AMD BRAM-based Remote Processor driver
>>>> + *
>>>> + * Copyright (C) 2026 Advanced Micro Devices, Inc.
>>>> + *
>>>> + * This driver supports soft-core processors (MicroBlaze, MicroBlaze-V, or
>>>> + * similar) instantiated in AMD programmable logic, using dual-port BRAM
>>>> + * for firmware storage and execution.
>>>> + *
>>>> + * The firmware memory (BRAM) is described in the processor-local address
>>>> + * space and translated to the Linux-visible system physical address with
>>>> + * standard devicetree address translation.
>>>> + *
>>>> + * Reset is controlled via GPIO connected to Processor System Reset IP.
>>>> + */
>>>> +
>>>> +#include <linux/clk.h>
>>>> +#include <linux/dma-mapping.h>
>>>> +#include <linux/gpio/consumer.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/of_address.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/remoteproc.h>
>>>> +
>>>> +#include "remoteproc_internal.h"
>>>> +
>>>> +/**
>>>> + * struct amd_bram_rproc - AMD BRAM-based remoteproc private data
>>>> + * @dev: device pointer
>>>> + * @reset: GPIO descriptor for reset control (active-low)
>>>> + * @clk: processor clock
>>>> + */
>>>> +struct amd_bram_rproc {
>>>> + struct device *dev;
>>>> + struct gpio_desc *reset;
>>>> + struct clk *clk;
>>>> +};
>>>> +
>>>> +static int amd_bram_rproc_mem_map(struct rproc *rproc,
>>>> + struct rproc_mem_entry *mem)
>>>> +{
>>>> + void __iomem *va;
>>>> +
>>>> + va = ioremap_wc(mem->dma, mem->len);
>>>> + if (!va)
>>>> + return -ENOMEM;
>>>> +
>>>> + mem->va = (__force void *)va;
>>>> + mem->is_iomem = true;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int amd_bram_rproc_mem_unmap(struct rproc *rproc,
>>>> + struct rproc_mem_entry *mem)
>>>> +{
>>>> + iounmap((void __iomem *)mem->va);
>>>> +
>>>> + return 0;
>>>> +}
>>>
>>> The above 2 are identical to what is found in xlnx_r5_remoteproc.c. Please
>>> coordinate with Tanmay to split that into common code that can be reused by both
>>> drivers.
>>>
>>>> +
>>>> +static int amd_bram_rproc_prepare(struct rproc *rproc)
>>>> +{
>>>> + struct amd_bram_rproc *priv = rproc->priv;
>>>> + struct rproc_mem_entry *mem;
>>>> + struct resource res;
>>>> + u64 da, size;
>>>> + int ret;
>>>> +
>>>> + ret = of_property_read_reg(priv->dev->of_node, 0, &da, &size);
>>>> + if (ret) {
>>>> + dev_err(priv->dev, "failed to parse executable memory reg\n");
>>>> + return ret;
>>>> + }
>>>> +
>>>> + if (!size || size > U32_MAX) {
>>>> + dev_err(priv->dev, "invalid executable memory size\n");
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + if (da > U32_MAX) {
>>>> + dev_err(priv->dev, "invalid executable memory address\n");
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + ret = of_address_to_resource(priv->dev->of_node, 0, &res);
>>>> + if (ret) {
>>>> + dev_err(priv->dev, "failed to translate executable memory reg\n");
>>>> + return ret;
>>>> + }
>>>> +
>>>> + mem = rproc_mem_entry_init(priv->dev, NULL, (dma_addr_t)res.start,
>>>> + (size_t)size, da,
>>>> + amd_bram_rproc_mem_map,
>>>> + amd_bram_rproc_mem_unmap,
>>>> + dev_name(priv->dev));
>>>> + if (!mem)
>>>> + return -ENOMEM;
>>>> +
>>>> + rproc_add_carveout(rproc, mem);
>>>> + rproc_coredump_add_segment(rproc, da, (size_t)size);
>>>
>>> I'm pretty sure you want @res.start instead of @da, and resource_size(&res)
>>> instead of @size.
>>>
For the coredump segment, I agree with using resource_size(&res) for
the size, but I think the address should remain @da rather than
@res.start.
The binding describes the reg property in the processor-local address
space and uses the parent bus ranges property only to translate that
window to the Linux-visible system physical address. That means @da and
@res.start are not necessarily in the same address space. For example,
the BRAM can appear at 0x0 to the soft-core processor while Linux sees
the same memory at a translated system physical address such as
0xa0000000.
rproc_coredump_add_segment() stores the address as a device address, and
the coredump path later resolves it through rproc_da_to_va() against the
registered carveout's device address. Since this driver registers the
carveout with @da as the device address and @res.start as the host-side
physical address used for ioremap_wc(), passing @res.start to
rproc_coredump_add_segment() could fail to match the carveout when those
addresses differ.
So in the respin I plan to use:
rproc_coredump_add_segment(rproc, da, resource_size(&res));
Does that match your expectation for this address model?
Thank you
Ben
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int amd_bram_rproc_start(struct rproc *rproc)
>>>> +{
>>>> + struct amd_bram_rproc *priv = rproc->priv;
>>>> + int ret;
>>>> +
>>>> + /* Enable clock before releasing reset */
>>>> + ret = clk_prepare_enable(priv->clk);
>>>> + if (ret) {
>>>> + dev_err(priv->dev, "failed to enable clock: %d\n", ret);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + /* Deassert reset and let the processor run. */
>>>> + ret = gpiod_set_value_cansleep(priv->reset, 0);
>>>> + if (ret) {
>>>> + dev_err(priv->dev, "failed to deassert reset: %d\n", ret);
>>>> + clk_disable_unprepare(priv->clk);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int amd_bram_rproc_stop(struct rproc *rproc)
>>>> +{
>>>> + struct amd_bram_rproc *priv = rproc->priv;
>>>> + int ret;
>>>> +
>>>> + /* Assert reset before disabling the processor clock. */
>>>> + ret = gpiod_set_value_cansleep(priv->reset, 1);
>>>> + if (ret) {
>>>> + dev_err(priv->dev, "failed to assert reset: %d\n", ret);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + /* Disable clock after asserting reset */
>>>> + clk_disable_unprepare(priv->clk);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int amd_bram_rproc_parse_fw(struct rproc *rproc,
>>>> + const struct firmware *fw)
>>>> +{
>>>> + int ret;
>>>> +
>>>> + ret = rproc_elf_load_rsc_table(rproc, fw);
>>>> + if (ret == -EINVAL) {
>>>> + dev_dbg(&rproc->dev, "no resource table found\n");
>>>> + return 0;
>>>> + }
>>>> +
>>>> + return ret;
>>>> +}
>>>
>>> This too should go in common code or simply replaced by
>>> rproc_elf_load_rsc_table() in @amd_bram_rproc_ops - the choice is yours.
>>>
>>> Thanks,
>>> Mathieu
>>
>> Thanks for the review.
>>
>> I went through the remoteproc drivers to scope the cleanup points you
>> called out.
>>
>> For the plain carveout map/unmap callbacks, the same ioremap_wc()/iounmap()
>> pattern exists not only in amd_bram_rproc and xlnx_r5_remoteproc, but also
>> in rcar_rproc, st_remoteproc, stm32_rproc, imx_rproc, and imx_dsp_rproc.
>>
>> The xlnx_r5 TCM path is close as well, but that one still needs a wrapper
>> since it clears the memory after ioremap_wc().
>>
>> For the optional resource-table parsing, amd_bram_rproc and xlnx_r5_remoteproc
>> share the same pattern of treating only -EINVAL from rproc_elf_load_rsc_table()
>> as non-fatal. PRU is similar, but has additional firmware parsing after that.
>> Other drivers such as rcar/imx/imx_dsp/stm32 also tolerate missing resource
>> tables, but their current behavior is not identical since they flatten all
>> errors to success and only log.
>>
>> For the next revision, would you prefer the following approach?
>>
>> 1. Add a small common helper for the plain carveout ioremap_wc()/iounmap()
>> case and use it in amd_bram_rproc and xlnx_r5_remoteproc.
>>
>> 2. For the optional resource-table handling, either:
>> - add a small common helper for the "missing table is OK" case
>> (i.e. return 0 on -EINVAL and propagate other errors), and use that
>> in amd_bram_rproc and xlnx_r5_remoteproc, or
>
> I would prefer to go with the common helper that returns 0 on -EINVAL
> and propagates other errors, and apply it to other architectures such
> as stm32, rcar, imx and imx_dsp.
>
>> - drop the custom AMD parse_fw() path and use rproc_elf_load_rsc_table()
>> directly, which would make the resource table mandatory there.
>>
>> Also, for the plain map/unmap helper, should I keep the cleanup scoped to
>> the drivers directly involved here, or would you prefer that I fold the
>> other exact-match users (rcar, st, stm32, imx, imx_dsp) into the same
>> cleanup patch as well?
>>
>
> Proceed with the other exact-match as well.
>
>> I want to make sure I take the direction you prefer before respinning.
>
> I think the best approach is to send out a cleanup patchset with the
> above changes, followed by another respin of this set once the cleanup
> is merged.
>
> Thanks for being proactive.
>
>>
>> Thanks,
>> Ben
>>>
>>>> +
>>>> +static const struct rproc_ops amd_bram_rproc_ops = {
>>>> + .prepare = amd_bram_rproc_prepare,
>>>> + .start = amd_bram_rproc_start,
>>>> + .stop = amd_bram_rproc_stop,
>>>> + .load = rproc_elf_load_segments,
>>>> + .sanity_check = rproc_elf_sanity_check,
>>>> + .get_boot_addr = rproc_elf_get_boot_addr,
>>>> + .parse_fw = amd_bram_rproc_parse_fw,
>>>> +};
>>>> +
>>>> +static int amd_bram_rproc_probe(struct platform_device *pdev)
>>>> +{
>>>> + struct device *dev = &pdev->dev;
>>>> + struct amd_bram_rproc *priv;
>>>> + const char *fw_name = NULL;
>>>> + struct rproc *rproc;
>>>> + int ret;
>>>> +
>>>> + ret = rproc_of_parse_firmware(dev, 0, &fw_name);
>>>> + if (ret < 0 && ret != -EINVAL)
>>>> + return dev_err_probe(dev, ret,
>>>> + "failed to parse firmware-name property\n");
>>>> +
>>>> + rproc = devm_rproc_alloc(dev, dev_name(dev), &amd_bram_rproc_ops,
>>>> + fw_name, sizeof(*priv));
>>>> + if (!rproc)
>>>> + return -ENOMEM;
>>>> +
>>>> + priv = rproc->priv;
>>>> + priv->dev = dev;
>>>> +
>>>> + /* Get the processor clock */
>>>> + priv->clk = devm_clk_get(dev, NULL);
>>>> + if (IS_ERR(priv->clk))
>>>> + return dev_err_probe(dev, PTR_ERR(priv->clk),
>>>> + "failed to get clock\n");
>>>> +
>>>> + /*
>>>> + * Keep the processor in reset until remoteproc has finished loading
>>>> + * firmware into the executable memory window described by reg and
>>>> + * translated through the parent bus ranges property.
>>>> + */
>>>> + priv->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
>>>> + if (IS_ERR(priv->reset))
>>>> + return dev_err_probe(dev, PTR_ERR(priv->reset),
>>>> + "failed to get reset gpio\n");
>>>> +
>>>> + rproc->auto_boot = false;
>>>> +
>>>> + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
>>>> + if (ret)
>>>> + return dev_err_probe(dev, ret, "failed to set DMA mask\n");
>>>> +
>>>> + platform_set_drvdata(pdev, rproc);
>>>> +
>>>> + ret = devm_rproc_add(dev, rproc);
>>>> + if (ret)
>>>> + return dev_err_probe(dev, ret, "failed to register rproc\n");
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct of_device_id amd_bram_rproc_of_match[] = {
>>>> + { .compatible = "xlnx,zynqmp-bram-rproc" },
>>>> + { /* sentinel */ },
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, amd_bram_rproc_of_match);
>>>> +
>>>> +static struct platform_driver amd_bram_rproc_driver = {
>>>> + .probe = amd_bram_rproc_probe,
>>>> + .driver = {
>>>> + .name = "amd-bram-rproc",
>>>> + .of_match_table = amd_bram_rproc_of_match,
>>>> + },
>>>> +};
>>>> +module_platform_driver(amd_bram_rproc_driver);
>>>> +
>>>> +MODULE_DESCRIPTION("AMD BRAM-based Remote Processor driver");
>>>> +MODULE_AUTHOR("Ben Levinsky <ben.levinsky@amd.com>");
>>>> +MODULE_LICENSE("GPL");
>>>> --
>>>> 2.34.1
>>>>
>>>
>>
^ permalink raw reply
* Re: [PATCH v2] ASoC: dt-bindings: Convert cirrus,cs35l36 to DT schema
From: Bjorn Helgaas @ 2026-06-24 21:43 UTC (permalink / raw)
To: Rob Herring
Cc: david, David Rhodes, Richard Fitzgerald, Liam Girdwood,
Mark Brown, Krzysztof Kozlowski, Conor Dooley, patches,
Bjorn Helgaas, linux-sound, devicetree, linux-kernel, phone-devel
In-Reply-To: <CAL_JsqLE8Z-LbeF9r=sqRqAoGUcs7R-T4cN+hF3QzjGydHctgQ@mail.gmail.com>
On Wed, Jun 24, 2026 at 01:17:58PM -0500, Rob Herring wrote:
> On Wed, Jun 24, 2026 at 11:02 AM David Heidelberg via B4 Relay
> ...
> <devnull+david.ixit.cz@kernel.org> wrote:
> > +title: Cirrus Logic CS35L36 Speaker Amplifier
> > +
> > +maintainers:
> > + - patches@opensource.cirrus.com
> > + - Bjorn Helgaas <bhelgaas@google.com>
>
> Bjorn is not correct. Generally we want a person, not a company list.
Haha, thanks for noticing that. I definitely do not want to be listed
here; I have nothing to do with this.
^ permalink raw reply
* Re: (subset) [PATCH v2 0/3] rtc: ds1307: Add support for Epson RX8901CE
From: Alexandre Belloni @ 2026-06-24 21:42 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Nobuhiro Iwamatsu,
Fredrik M Olsson
Cc: linux-rtc, devicetree, linux-kernel, kernel, Krzysztof Kozlowski,
Nobuhiro Iwamatsu
In-Reply-To: <20260520-ds1307-rx8901-add-v2-0-e069ea32e1db@axis.com>
On Wed, 20 May 2026 16:48:52 +0200, Fredrik M Olsson wrote:
> Add basic support for the Epson RX8901CE RTC.
> Datasheet: https://download.epsondevice.com/td/pdf/app/RX8901CE_en.pdf
>
> Also includes a bug fix for an issue with reading the weekday from the
> RTC which affects both the existing rx8130 and this rx8901 driver.
>
>
> [...]
Applied, thanks!
[1/3] dt-bindings: rtc: ds1307: Add epson,rx8901
https://git.kernel.org/abelloni/c/cc387941c0a1
[2/3] rtc: ds1307: Fix off-by-one issue with wday for rx8130
https://git.kernel.org/abelloni/c/6882aab3c661
Best regards,
--
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* Re: [PATCH v3] rtc: bq32000: add configurable delay between RTC reads
From: Alexandre Belloni @ 2026-06-24 21:42 UTC (permalink / raw)
To: Adriana Stancu
Cc: linux-rtc, devicetree, linux-kernel, robh, krzk+dt, conor+dt
In-Reply-To: <20260416142151.3385827-1-adriana@arista.com>
On Thu, 16 Apr 2026 07:21:51 -0700, Adriana Stancu wrote:
> When the RTC is used on systems without a interrupt line, userspace
> tools like `hwclock` fall back to a frequent polling loop to synchronize
> with the edge of the next second.
>
> On the BQ32000, this aggressive polling can temporarly lock the register
> refresh cycle, because the continuous transfers prevent the hardware from
> updating the buffer. This results in stale data reads or select() timeouts
> in userspace.
>
> [...]
Applied, thanks!
[1/1] rtc: bq32000: add configurable delay between RTC reads
https://git.kernel.org/abelloni/c/d4992b7050a1
Best regards,
--
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* Re: [PATCH v3 3/4] firmware: xilinx: Add support for Xilsem scan operations
From: sashiko-bot @ 2026-06-24 21:39 UTC (permalink / raw)
To: Rama devi Veggalam; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260624212545.2850787-4-rama.devi.veggalam@amd.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Uninitialized memory use and potential information leak via uninitialized stack buffer.
- [Medium] Potential address truncation and misleading parameter type for DMA addresses.
- [Low] API contract mismatch and typo in Kernel-Doc.
Pre-existing issues:
- [High] Lockless concurrent modification of global hashtable and arrays causing data races and UAF.
--
commit e939c99be4b47a8ba5e7ed4d9aaf024781ae5543
Author: Rama devi Veggalam <rama.devi.veggalam@amd.com>
firmware: xilinx: Add support for Xilsem scan operations
Add the ATF EEMI call support for Xilsem scan operations.
Initialize, start, stop scan, error inject, read configuration,
status and register for software error events.
Add macros for XilSem correctable and uncorrectable error events.
> diff --git a/drivers/firmware/xilinx/zynqmp-sem.c b/drivers/firmware/xilinx/zynqmp-sem.c
> new file mode 100644
> index 0000000000000..5cd399b165f55
> --- /dev/null
> +++ b/drivers/firmware/xilinx/zynqmp-sem.c
> @@ -0,0 +1,176 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Firmware layer for XilSEM APIs.
> + *
> + * Copyright (C), 2026 Advanced Micro Devices, Inc.
> + */
[ ... ]
> +/**
> + * zynqmp_pm_xilsem_cntrl_ops - PM call to perform XilSEM operations
> + * @cmd: Command for XilSEM scan control operations
> + * @slrid: SLR id on which scan operation to be done
> + * @response: Output response (command header, error code or status, slr id)
[Severity: Low]
Does the documentation accurately reflect the response size? The documentation
specifies 3 elements, but the function below writes 6 elements to the array.
Could this lead future callers to allocate undersized stack arrays?
> + *
> + * Return: Returns 0 on success or error value on failure.
> + */
> +int zynqmp_pm_xilsem_cntrl_ops(u32 cmd, u32 slrid, u32 *const response)
> +{
> + u32 ret_buf[PAYLOAD_ARG_CNT];
> + int ret;
> +
> + ret = zynqmp_pm_invoke_fn(PM_XSEM_HEADER | cmd, ret_buf, 1, slrid);
[Severity: High]
This is a pre-existing issue, but can multiple concurrent drivers calling into
zynqmp_pm_invoke_fn() cause race conditions or use-after-free?
Looking at zynqmp_pm_invoke_fn(), it calls zynqmp_pm_feature() which leads
to lockless hash table insertions into pm_api_features_map:
zynqmp_pm_invoke_fn()
zynqmp_pm_feature()
do_feature_check_call()
hash_add(pm_api_features_map, &feature_data->hentry, api_id);
Since hlist_add_head() is not thread-safe, could concurrent insertions
corrupt the hash bucket's linked list?
Additionally, zynqmp_firmware_remove() frees these hashtable entries
locklessly, which could expose concurrent callers to use-after-free.
> + response[0] = ret_buf[1];
[Severity: High]
Does this code copy uninitialized memory to response if zynqmp_pm_invoke_fn()
returns an error?
If zynqmp_pm_invoke_fn() fails early (for example, if zynqmp_pm_feature()
returns -EOPNOTSUPP), it returns without writing to ret_buf. The values
from ret_buf are then unconditionally copied into the response array.
Could this lead to reading uninitialized kernel stack memory?
> + response[1] = ret_buf[2];
> + response[2] = ret_buf[3];
> + response[3] = ret_buf[4];
> + response[4] = ret_buf[5];
> + response[5] = ret_buf[6];
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(zynqmp_pm_xilsem_cntrl_ops);
[ ... ]
> +/**
> + * zynqmp_pm_xilsem_read_ssit_status - PM call to perform Xilsem SSIT status
> + * @slrid: SLR id for which ECC read to be done
> + * @bufaddr: Buffer address to get the status information
> + * @response: Output response (status read header, slr id)
> + *
> + * Return: Returns 0 on success or error value on failure.
> + */
> +int zynqmp_pm_xilsem_read_ssit_status(u32 slrid, u32 bufaddr, u32 *const response)
^^^^^^^
[Severity: Medium]
Can this parameter cause address truncation? The EDAC driver may allocate a
coherent DMA buffer typed as dma_addr_t and pass it to bufaddr. Since
bufaddr is typed as u32, this could truncate a 64-bit DMA address to 32
bits, causing the firmware to write to an incorrect physical address.
> +{
> + u32 ret_buf[PAYLOAD_ARG_CNT];
> + int ret;
> +
> + ret = zynqmp_pm_invoke_fn(PM_XSEM_SSIT_RD_STS, ret_buf, 2, slrid, bufaddr);
> + response[0] = ret_buf[1];
> + response[1] = ret_buf[2];
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(zynqmp_pm_xilsem_read_ssit_status);
[ ... ]
> +/**
> + * zynqmp_pm_xilsem_cram_getcrc - PM call to perform CRAM Row CRC read
> + * @slrid: SLR id for which CRC read to be done
> + * @rowindex: CFRAME row number to be used for reading CRC
> + * @response: Output response (Get CRC header, CRC values, status)
> + *
> + * Return: Returns 0 on success or error value on failure.
> + */
> +int zynqmp_pm_xilsem_cram_getcrc(u32 slrid, u32 rowindex, u32 *const response)
> +{
> + u32 ret_buf[PAYLOAD_ARG_CNT];
> + int ret;
> +
> + ret = zynqmp_pm_invoke_fn(PM_XSEM_SSIT_GET_CRC, ret_buf, 2, slrid, rowindex);
> + response[0] = ret_buf[1];
[Severity: High]
Does this code also copy uninitialized memory to response if
zynqmp_pm_invoke_fn() returns an error? If it fails early, ret_buf is
not written to, resulting in uninitialized kernel stack memory being
read and copied unconditionally.
> + response[1] = ret_buf[2];
> + response[2] = ret_buf[3];
> + response[3] = ret_buf[4];
> + response[4] = ret_buf[5];
> + response[5] = ret_buf[6];
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(zynqmp_pm_xilsem_cram_getcrc);
> +
> +/**
> + * zynqmp_pm_xilsem_cram_ssit_totframes - PM call to perform total frames read
> + * @slrid: SLR id for which total frames read to be done
> + * @row: CFRAME row number to be used for reading ECC
> + * @framecnt: Buffer address to get toral frames data
[Severity: Low]
Is there a typo in the documentation here? Should toral be total?
> + * @response: Output response (Total frames header, slr id, row, status)
> + *
> + * Return: Returns 0 on success or error value on failure.
> + */
> +int zynqmp_pm_xilsem_cram_ssit_totframes(u32 slrid, u32 row, u32 framecnt, u32 *const response)
^^^^^^^^^^^^
[Severity: Medium]
Does framecnt suffer from address truncation similar to bufaddr? The
parameter name framecnt is somewhat misleading since it receives a DMA
buffer address rather than a count. Since it is typed as u32, passing
a 64-bit dma_addr_t could cause truncation.
> +{
> + u32 ret_buf[PAYLOAD_ARG_CNT];
> + int ret;
> +
> + ret = zynqmp_pm_invoke_fn(PM_XSEM_SSIT_GET_FRAMES, ret_buf, 3, slrid, row, framecnt);
> + response[0] = ret_buf[1];
> + response[1] = ret_buf[2];
> + response[2] = ret_buf[3];
> + response[3] = ret_buf[4];
> +
> + return ret;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260624212545.2850787-1-rama.devi.veggalam@amd.com?part=3
^ permalink raw reply
* Re: [PATCH v2 3/3] rtc: ds1307: Add driver for Epson RX8901CE
From: Alexandre Belloni @ 2026-06-24 21:38 UTC (permalink / raw)
To: Fredrik M Olsson
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Nobuhiro Iwamatsu,
linux-rtc, devicetree, linux-kernel, kernel
In-Reply-To: <20260520-ds1307-rx8901-add-v2-3-e069ea32e1db@axis.com>
On 20/05/2026 16:48:55+0200, Fredrik M Olsson wrote:
> +static int do_trickle_setup_rx8901(struct ds1307 *ds1307, u32 ohms __always_unused, bool diode)
> +{
> + int ret;
> + unsigned int setup;
> +
> + ret = regmap_read(ds1307->regmap, RX8901_REG_PWSW_CFG, &setup);
> + if (ret) {
> + dev_err(ds1307->dev, "Failed to read PWSW_CFG register\n");
> + return ret;
> + }
> +
> + /* Enable low battery voltage detection */
> + setup |= RX8901_REG_PWSW_CFG_VBATLDETEN;
> +
> + if (diode)
> + setup |= RX8901_REG_PWSW_CFG_CHGEN;
When diode is false, you need to explicitly clear
RX8901_REG_PWSW_CFG_CHGEN so it is possible to actually disable trickle
charging once it has been enabled has the register is battery backed.
--
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply
* Re: [PATCH v3 4/4] edac: xilinx: Add EDAC support for Versal XilSem
From: sashiko-bot @ 2026-06-24 21:37 UTC (permalink / raw)
To: Rama devi Veggalam; +Cc: devicetree, conor+dt, robh
In-Reply-To: <20260624212545.2850787-5-rama.devi.veggalam@amd.com>
Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [Critical] Passing a 64-bit `dma_addr_t` to a 32-bit firmware API parameter truncates the physical address, causing memory corruption.
- [High] Missing 'semrtca' resource breaks driver probe for older Device Trees.
- [High] The sysfs `store` functions use the virtual EDAC device for DMA allocations instead of the platform device.
- [High] Error paths introduced in `mc_probe` fail to unregister the `VERSAL_EVENT_ERROR_PMC_ERR1` PM notifier, causing a Use-After-Free.
- [High] The error event handler uses an `if - else if` chain for bitmask evaluation, silently dropping uncorrectable errors.
- [Medium] Hexadecimal prefixes mistakenly used for decimal bit-shift macros cause undefined behavior and break CRAM frame decoding.
- [Medium] The `xsem_total_cframes_ssit_show` function mutates the driver's cached CRAM frames state in-place, corrupting data on subsequent reads.
- [Medium] Sysfs `store` functions unconditionally truncate the last character of user input if it is not newline-terminated.
- [Medium] XilSEM fabric and interconnect errors are incorrectly attributed to DDR memory DIMM 0.
- [Medium] Failure to handle an unrecognized `family_code` causes the driver to register an event with uninitialized parameters.
- [Medium] Lack of synchronization in sysfs callbacks causes data races on shared buffers.
- [Medium] The `xsem_err_callback` event handler performs unprotected read-modify-write operations on shared error counters.
- [Low] Copy-paste error in `xsem_scan_control_show` format string causes incorrect output.
--
commit 3ba94d35140e37f0feb1403bd80442cef2ad8ed1
Author: Rama devi Veggalam <rama.devi.veggalam@amd.com>
Subject: edac: xilinx: Add EDAC support for Versal XilSem
This patch adds support for Xilinx Versal Soft Error Mitigation (XilSEM)
to handle and optionally correct soft errors in Configuration Memory.
It also introduces sysfs interfaces for XilSEM scan operations, error
injection, and configuration reads.
> diff --git a/drivers/edac/versal_edac.c b/drivers/edac/versal_edac.c
> index 5a43b5d43ca28..024ddd90f6996 100644
> --- a/drivers/edac/versal_edac.c
> +++ b/drivers/edac/versal_edac.c
[ ... ]
> @@ -11,12 +11,88 @@
> #include <linux/of_address.h>
> #include <linux/of_device.h>
> #include <linux/platform_device.h>
> +#include <linux/dma-mapping.h>
> #include <linux/sizes.h>
> +#include <linux/io.h>
> #include <linux/firmware/xlnx-zynqmp.h>
> +#include <linux/firmware/xlnx-versal-error-events.h>
> #include <linux/firmware/xlnx-event-manager.h>
>
> #include "edac_module.h"
>
> +/* XilSem CE Error log count */
> +#define XILSEM_MAX_CE_LOG_CNT 7
> +
> +/** Maximum CRAM error register count */
> +#define MAX_CRAMERR_REG_CNT 14
> +/** Maximum NPI slave skip count */
> +#define MAX_NPI_SLV_SKIP_CNT 8
> +/** Maximum NPI Error info count */
> +#define MAX_NPI_ERR_INFO_CNT 2
> +
> +/* Maximum SLR count */
> +#define MAX_SLR_ID 3
> +
> +/** Maximum number of cframe types */
> +#define CFRAME_MAX_TYPE 7
> +
> +/** Mask for getting Type_0, Type_4 frames */
> +#define CFRAME_TYPE_0_4_MASK GENMASK(19, 0)
> +
> +/** Low mask, High mask for getting Type_1, Type_5 frames */
> +#define CFRAME_TYPE_1_5_MASK_L GENMASK(39, 20)
> +#define CFRAME_TYPE_1_5_MASK_H GENMASK(7, 0)
> +
> +/** Shift for getting Type_1, Type_5 frames */
> +#define CFRAME_TYPE_1_5_SHIFT_R 0x20
> +#define CFRAME_TYPE_1_5_SHIFT_L 0x12
> +
> +/** Mask for getting Type_2, Type_6 frames */
> +#define CFRAME_TYPE_2_6_MASK GENMASK(27, 8)
> +
> +/** Shift for getting Type_2, Type_6 frames */
> +#define CFRAME_TYPE_2_6_SHIFT_R 0x8
> +
> +/** Low mask, high mask for getting Type_3 frames */
> +#define CFRAME_TYPE_3_MASK_L GENMASK(31, 28)
> +#define CFRAME_TYPE_3_MASK_H GENMASK(15, 0)
> +
> +/** Shift for getting Type_3 */
> +#define CFRAME_TYPE_3_SHIFT_R 0x28
[Severity: Medium]
Are these macro definitions incorrectly using a hex prefix for decimal shift
values? Shifting a 32-bit value by 0x20 (32) or 0x28 (40) bits will invoke
undefined behavior.
> +#define CFRAME_TYPE_3_SHIFT_L 0x4
[ ... ]
> +static ssize_t xsem_scan_control_show(struct device *dev,
> + struct device_attribute *mattr,
> + char *data)
> +{
> + struct mem_ctl_info *mci = to_mci(dev);
> + struct edac_priv *priv = mci->pvt_info;
> +
> + if (!priv->xsem_rtca)
> + return -EINVAL;
> +
> + return sprintf(data, "[0x%x][0x%x][0x%x][0x%x][0x%x][0x%x][0x%x]\n\r",
> + priv->xsem_rtca->scan_ctrl_status[0],
> + priv->xsem_rtca->scan_ctrl_status[0],
[Severity: Low]
Is there a typo here where scan_ctrl_status[0] is printed twice, causing the
output to miss the last element of the array?
> + priv->xsem_rtca->scan_ctrl_status[1],
> + priv->xsem_rtca->scan_ctrl_status[2],
> + priv->xsem_rtca->scan_ctrl_status[3],
> + priv->xsem_rtca->scan_ctrl_status[4],
> + priv->xsem_rtca->scan_ctrl_status[5]);
> +}
> +
> +/**
> + * xsem_scan_control_store - Set scan control operation
[ ... ]
> +static ssize_t xsem_scan_control_store(struct device *dev,
> + struct device_attribute *mattr,
> + const char *data, size_t count)
> +{
> + struct mem_ctl_info *mci = to_mci(dev);
> + struct edac_priv *priv = mci->pvt_info;
> + char *kern_buff, *inbuf, *tok;
> + u32 cmd;
> + u32 slrid;
> + int ret;
> +
> + if (!priv->xsem_rtca)
> + return -EINVAL;
> +
> + kern_buff = kzalloc(count, GFP_KERNEL);
> + if (!kern_buff)
> + return -ENOMEM;
> + strscpy(kern_buff, data, count);
[Severity: Medium]
Does this silently truncate the last character of the user input if it is not
newline-terminated? When count matches the string length exactly, strscpy()
copies at most count - 1 bytes and inserts a null terminator.
> +
> + inbuf = kern_buff;
[ ... ]
> +static ssize_t xsem_total_cframes_ssit_show(struct device *dev,
> + struct device_attribute *mattr,
> + char *data)
> +{
> + struct mem_ctl_info *mci = to_mci(dev);
> + struct edac_priv *priv = mci->pvt_info;
> + u32 temp_buf[CFRAME_MAX_TYPE] = {0};
> + u32 id;
> + int offset = 0;
> +
> + if (!priv->xsem_rtca)
> + return -EINVAL;
> +
> + for (id = 0; id < CFRAME_MAX_TYPE; id++)
> + temp_buf[id] = priv->xsem_rtca->cram_total_frames[id];
> +
> + priv->xsem_rtca->cram_total_frames[0] = (temp_buf[0] & CFRAME_TYPE_0_4_MASK);
> + priv->xsem_rtca->cram_total_frames[1] = (temp_buf[0] &
> + CFRAME_TYPE_1_5_MASK_L) >> CFRAME_TYPE_1_5_SHIFT_R;
[Severity: Medium]
Does this modify the shared cram_total_frames array in-place during a read
operation? Subsequent reads might apply the bitwise shifts to already-shifted
data, returning garbled output.
> + priv->xsem_rtca->cram_total_frames[1] |= (temp_buf[1] &
[ ... ]
> +static ssize_t xsem_total_cframes_ssit_store(struct device *dev,
> + struct device_attribute *mattr,
> + const char *data, size_t count)
> +{
> + struct mem_ctl_info *mci = to_mci(dev);
> + struct edac_priv *priv = mci->pvt_info;
> + char *kern_buff, *inbuf, *tok, *kbuf1;
> + dma_addr_t dma_addr = 0;
> + u32 row, slrid, dma_size;
> + int ret;
[ ... ]
> + dma_size = sizeof(priv->xsem_rtca->cram_total_frames);
> + kbuf1 = dma_alloc_coherent(dev, dma_size, &dma_addr, GFP_KERNEL);
[Severity: High]
Are we passing the virtual EDAC device pointer instead of the platform device
here? Sysfs store callbacks for this attribute receive &mci->dev, which lacks
DMA configuration and could cause allocation failures.
> + if (!kbuf1) {
> + ret = -ENOMEM;
> + goto err;
> + }
> +
> + ret = zynqmp_pm_xilsem_cram_ssit_totframes(slrid, row, dma_addr,
> + priv->xsem_rtca->cram_get_frames_status);
[Severity: Critical]
Can this truncate the physical address and cause memory corruption? On ARM64,
dma_addr_t is 64-bit, but the firmware API parameters are 32-bit.
> + if (ret) {
> + edac_printk(KERN_ERR, EDAC_MC, "ERROR: XilSEM Status PM API failed\n");
> + dma_free_coherent(dev, dma_size, kbuf1, dma_addr);
> + goto err;
> + }
> +
> + memcpy(priv->xsem_rtca->cram_total_frames, kbuf1, dma_size);
[Severity: Medium]
Can concurrent sysfs store operations corrupt this shared buffer due to a lack
of locking?
> + dma_free_coherent(dev, dma_size, kbuf1, dma_addr);
[ ... ]
> +static ssize_t xsem_read_ssit_status_store(struct device *dev,
> + struct device_attribute *mattr,
> + const char *data, size_t count)
> +{
> + struct mem_ctl_info *mci = to_mci(dev);
> + struct edac_priv *priv = mci->pvt_info;
> + char *kern_buff, *inbuf, *tok, *kbuf1;
> + size_t dma_size;
> + dma_addr_t dma_addr = 0;
> + u32 slrid;
> + int ret;
[ ... ]
> + dma_size = sizeof(struct xsem_ssit_status);
> +
> + kbuf1 = dma_alloc_coherent(dev, dma_size, &dma_addr, GFP_KERNEL);
[Severity: High]
Does passing the virtual EDAC device pointer (which lacks DMA configuration)
instead of the platform device cause DMA allocation failures?
> + if (!kbuf1) {
> + ret = -ENOMEM;
> + goto err;
> + }
> +
> + ret = zynqmp_pm_xilsem_read_ssit_status(slrid, dma_addr,
> + priv->xsem_rtca->xilsem_ssit_status);
[Severity: Critical]
Could passing the 64-bit dma_addr to this 32-bit firmware API parameter result
in physical address truncation and subsequent memory corruption?
> + if (ret) {
> + edac_printk(KERN_ERR, EDAC_MC, "ERROR: XilSEM Status PM API failed\n");
> + dma_free_coherent(dev, dma_size, kbuf1, dma_addr);
> + goto err;
> + }
> +
> + memcpy(priv->xsem_rtca->slr_info, kbuf1, dma_size);
[Severity: Medium]
Can concurrent executions of this sysfs store operation result in data races on
this shared slr_info buffer?
> +
> + dma_free_coherent(dev, dma_size, kbuf1, dma_addr);
[ ... ]
> +static void xsem_geterror_info(struct mem_ctl_info *mci, struct xsem_error_status *p,
> + int mask)
> +{
> + struct edac_priv *priv = mci->pvt_info;
> + u32 error_word_0, error_word_1, ce_count;
> + u8 index;
> +
> + if (!priv->xsem_rtca || !priv->sem_baseaddr)
> + return;
> +
> + if (mask & priv->xsem_rtca->cram_ce_mask) {
> + p->ce_cnt++;
[ ... ]
> + /* Read CRAM status */
> + p->ceinfo.status = readl(priv->sem_baseaddr + CRAM_STS_INFO_OFFSET);
> + } else if (mask & priv->xsem_rtca->cram_ue_mask) {
[Severity: High]
Does this if-else chain cause uncorrectable errors to be silently ignored if
the payload mask contains both a correctable and uncorrectable error?
> + p->ue_cnt++;
> + p->ueinfo.data0 = 0;
> + p->ueinfo.data1 = 0;
> + p->ueinfo.status = readl(priv->sem_baseaddr + CRAM_STS_INFO_OFFSET);
> + } else if (mask & priv->xsem_rtca->npi_ue_mask) {
> + p->ue_cnt++;
> + p->ueinfo.data0 = readl(priv->sem_baseaddr + NPI_ERR0_INFO_OFFSET);
> + p->ueinfo.data1 = readl(priv->sem_baseaddr + NPI_ERR1_INFO_OFFSET);
> + p->ueinfo.status = readl(priv->sem_baseaddr);
> + } else {
> + edac_printk(KERN_ERR, EDAC_MC, "Invalid Event received %d\n", mask);
> + }
> +}
> +
> +/**
> + * xsem_handle_error - Handle XilSem error types CE and UE
> + * @mci: Pointer to the memory controller instance
> + * @p: Pointer to the xilsem error status structure
> + *
> + * Handles the correctable and uncorrectable error.
> + */
> +static void xsem_handle_error(struct mem_ctl_info *mci, struct xsem_error_status *p)
> +{
> + struct xsem_ecc_error_info *pinf;
> + char message[XDDR_EDAC_MSG_SIZE];
> +
> + if (p->ce_cnt) {
> + pinf = &p->ceinfo;
> + snprintf(message, XDDR_EDAC_MSG_SIZE,
> + "\n\rXILSEM CRAM error type :%s\n\r"
> + "\nFrame_Addr: [0x%X]\t Row_num: [0x%X]\t Bit_loc: [0x%X]\t Qword: [0x%X]\n\r",
> + "CE", pinf->frame_addr, pinf->row_id,
> + pinf->bit_loc, pinf->qword);
> +
> + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
> + p->ce_cnt, 0, 0, 0, 0, 0, -1,
> + message, "");
[Severity: Medium]
Are these fabric and interconnect errors being incorrectly attributed to the
first DDR memory module? Passing top_layer=0 and mid_layer=0 explicitly maps
them to csrow 0, channel 0.
> + }
> +
> + if (p->ue_cnt) {
> + pinf = &p->ueinfo;
> + snprintf(message, XDDR_EDAC_MSG_SIZE,
> + "\n\rXILSEM error type :%s\n\r"
> + "status: [0x%X]\n\rError_Info0: [0x%X]\n\r"
> + "Error_Info1: [0x%X]",
> + "UE", pinf->status, pinf->data0, pinf->data1);
> +
> + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
> + p->ue_cnt, 0, 0, 0, 0, 0, -1,
> + message, "");
[Severity: Medium]
Does mapping uncorrectable XilSEM errors to top_layer=0 incorrectly attribute
them to the first DDR memory DIMM?
> + }
> +}
> +
> +/**
> + * xsem_err_callback - Handle Correctable and Uncorrectable errors.
> + * @payload: payload data.
> + * @data: controller data.
> + *
> + * Handles ECC correctable and uncorrectable errors.
> + */
> +static void xsem_err_callback(const u32 *payload, void *data)
> +{
> + struct xsem_error_status stat;
> + struct edac_priv *priv;
> + struct mem_ctl_info *mci = (struct mem_ctl_info *)data;
> + int event;
> +
> + priv = mci->pvt_info;
> + if (!priv->xsem_rtca)
> + return;
> +
> + memset(&stat, 0, sizeof(stat));
> + /* Read payload to get the event type */
> + event = payload[2];
> + edac_printk(KERN_INFO, EDAC_MC, "Event received %x\n", event);
> + xsem_geterror_info(mci, &stat, event);
> +
> + priv->xsem_rtca->cram_ce_cnt += stat.ce_cnt;
> + priv->xsem_rtca->cram_ue_cnt += stat.ue_cnt;
[Severity: Medium]
Could these read-modify-write operations on shared counters lose increments if
the hardware event callback is executed concurrently on multiple CPUs?
> + xsem_handle_error(mci, &stat);
> +}
[ ... ]
> @@ -1094,6 +2365,10 @@ static int mc_probe(struct platform_device *pdev)
> if (!get_ecc_state(ddrmc_baseaddr))
> return -ENXIO;
>
> + sem_baseaddr = devm_platform_ioremap_resource_byname(pdev, "semrtca");
> + if (IS_ERR(sem_baseaddr))
> + return PTR_ERR(sem_baseaddr);
[Severity: High]
Does this make the "semrtca" resource mandatory, breaking the driver probe for
older device trees that do not define it?
> +
> /* Allocate ID number for the EMIF controller */
> edac_mc_id = emif_get_id(pdev->dev.of_node);
>
[ ... ]
> + rc = zynqmp_pm_get_family_info(&family_code);
> + if (rc) {
> + if (rc == -ENODEV)
> + rc = -EPROBE_DEFER;
> +
> + goto del_mc;
> + }
> + if (family_code == PM_VERSAL_FAMILY_CODE) {
> + priv->xsem_rtca->sw_event_node_id = VERSAL_EVENT_ERROR_SW_ERR;
> + priv->xsem_rtca->cram_ce_mask = XPM_VERSAL_EVENT_ERROR_MASK_XSEM_CRAM_CE_5;
> + priv->xsem_rtca->cram_ue_mask = XPM_VERSAL_EVENT_ERROR_MASK_XSEM_CRAM_UE_6;
> + priv->xsem_rtca->npi_ue_mask = XPM_VERSAL_EVENT_ERROR_MASK_XSEM_NPI_UE_7;
> + } else {
> + edac_printk(KERN_ERR, EDAC_MC, "Invalid Device family code %d\n", family_code);
> + }
> +
> + rc = xlnx_register_event(PM_NOTIFY_CB, priv->xsem_rtca->sw_event_node_id,
[Severity: Medium]
Does this execution path fall through when the family code is unrecognized,
causing the driver to register an event using uninitialized parameters (zeros)?
> + priv->xsem_rtca->cram_ce_mask | priv->xsem_rtca->cram_ue_mask |
> + priv->xsem_rtca->npi_ue_mask,
> + false, xsem_err_callback, mci);
> + if (rc)
> + goto del_mc;
> +
> #ifdef CONFIG_EDAC_DEBUG
> create_debugfs_attributes(mci);
> setup_address_map(priv);
> @@ -1154,6 +2484,8 @@ static int mc_probe(struct platform_device *pdev)
> enable_intr(priv);
> return rc;
>
> +remove_sysfs:
> + xsem_edac_remove_sysfs_attributes(mci);
> del_mc:
> edac_mc_del_mc(&pdev->dev);
> free_edac_mc:
> @@ -1173,9 +2505,21 @@ static void mc_remove(struct platform_device *pdev)
[Severity: High]
Does this error path fail to unregister the VERSAL_EVENT_ERROR_PMC_ERR1 event
that was registered earlier in the function? If the event triggers after mci is
freed, could it cause a use-after-free in the callback?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260624212545.2850787-1-rama.devi.veggalam@amd.com?part=4
^ permalink raw reply
* Re: [PATCH v3 1/4] dt-bindings: edac: Add bindings for Xilinx Versal XilSEM
From: sashiko-bot @ 2026-06-24 21:33 UTC (permalink / raw)
To: Rama devi Veggalam; +Cc: conor+dt, robh, devicetree
In-Reply-To: <20260624212545.2850787-2-rama.devi.veggalam@amd.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Adding a third mandatory item to `reg` and `reg-names` breaks backward compatibility for existing device trees, causing schema validation and driver probe failures.
- [Medium] Unrelated XilSEM hardware registers are incorrectly merged into the DDR Memory Controller (`xlnx,versal-ddrmc`) device tree node, violating DT design principles.
--
commit 4759b2e3e1d7757d4a16075824d7a25fd86d0c73
Author: Rama devi Veggalam <rama.devi.veggalam@amd.com>
dt-bindings: edac: Add bindings for Xilinx Versal XilSEM
Update versal edac device tree bindings for Versal Soft Error Mitigation
(XilSEM).
> diff --git a/Documentation/devicetree/bindings/memory-controllers/xlnx,versal-ddrmc-edac.yaml b/Documentation/devicetree/bindings/memory-controllers/xlnx,versal-ddrmc-edac.yaml
> index 12f8e9f350bcf..568d2af7de81a 100644
> --- a/Documentation/devicetree/bindings/memory-controllers/xlnx,versal-ddrmc-edac.yaml
> +++ b/Documentation/devicetree/bindings/memory-controllers/xlnx,versal-ddrmc-edac.yaml
[ ... ]
> @@ -23,11 +37,13 @@ properties:
> items:
> - description: DDR Memory Controller registers
> - description: NOC registers corresponding to DDR Memory Controller
> + - description: SEM RTCA Controller registers
[Severity: Medium]
Is it correct to merge unrelated XilSEM hardware registers into the DDR Memory
Controller device tree node?
The device tree describes an inaccurate hardware topology by grouping physically
separate register bases under a single compatible string simply because a single
EDAC driver handles both. Should XilSEM be represented by its own distinct
device tree node?
[Severity: Critical]
Does adding this new required register break backward compatibility with older
device trees?
Because this adds a third mandatory item to the list without specifying
minItems: 2, older device trees lacking the "semrtca" register will fail
schema validation.
Additionally, does the driver unconditionally require this new register in
mc_probe(), causing probes to fail on existing systems? If the driver code
looks for the new register:
drivers/edac/versal_edac.c:mc_probe() {
...
sem_baseaddr = devm_platform_ioremap_resource_byname(pdev, "semrtca");
if (IS_ERR(sem_baseaddr))
return PTR_ERR(sem_baseaddr);
...
}
This will return an error when the DT node lacks the semrtca register, leading
to immediate probe failure on existing setups.
>
> reg-names:
> items:
> - const: base
> - const: noc
> + - const: semrtca
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260624212545.2850787-1-rama.devi.veggalam@amd.com?part=1
^ permalink raw reply
* Re: [PATCH v3 2/4] Documentation: ABI: Add ABI doc for versal edac sysfs
From: sashiko-bot @ 2026-06-24 21:32 UTC (permalink / raw)
To: Rama devi Veggalam; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260624212545.2850787-3-rama.devi.veggalam@amd.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] The sysfs entries implement a stateful RPC command-response interface, causing data races.
- [Medium] Multiple sysfs attributes output complex, multiline blocks instead of single values.
- [Low] Malformed ABI Documentation Syntax.
- [Low] Duplicate and conflicting entries in ABI Documentation.
--
commit 9760cee2db24f4eb5850706de75cd24db0ae078c
Author: Rama devi Veggalam <rama.devi.veggalam@amd.com>
Documentation: ABI: Add ABI doc for versal edac sysfs
Add documentation for the sysfs entries created for
versal edac (XilSEM).
> diff --git a/Documentation/ABI/testing/sysfs-driver-versal-edac b/Documentation/ABI/testing/sysfs-driver-versal-edac
> new file mode 100644
> index 0000000000000..c35d864fcbe2c
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-driver-versal-edac
> @@ -0,0 +1,303 @@
> +What: /sys/devices/system/edac/versal_xilsem/xsem_scan_control
> +Date: What: /sys/devices/system/edac/mc/mc0/xsem_scan_control
> +Date: June 2026
[Severity: Low]
Is there malformed syntax in this documentation block? The second line has a
duplicate "What:" definition inside the "Date:" field.
> +Contact: rama.devi.veggalam@amd.com
> +Description:
> + It is a read/write file.
> + Writing to this file causes the software to initiate a
> + request to the firmware for doing requested scan operation in
> + Configuration RAM (CRAM) or NPI of Versal mono/SSIT devices.
> + User needs to provide scan operation id (init, start, stop) and
> + SLR id (Versal SSIT device) details. For Versal mono devices,
> + the SLR id should be 0.
> + The scan operation id values are as given below:
> + 1 - Initialize the scan
> + 2 - Start CRAM scan
> + 3 - Stop CRAM scan
> + 5 - Start NPI scan
> + 6 - Stop NPI scan
> + 7 - Inject NPI error in first descriptor
> +
> + When read, it shows the current scan status with error code.
[Severity: Medium]
Could this stateful command-response interface introduce data races?
In drivers/edac/versal_edac.c, xsem_scan_control_store() writes the command
result directly to the shared, lockless array:
ret = zynqmp_pm_xilsem_cntrl_ops(cmd, slrid, priv->xsem_rtca->scan_ctrl_status);
Then xsem_scan_control_show() reads from it:
return sprintf(data, "[0x%x][0x%x][0x%x][0x%x][0x%x][0x%x][0x%x]\n\r",
priv->xsem_rtca->scan_ctrl_status[0],
Will concurrent writes to xsem_scan_control clobber the shared buffers
in priv->xsem_rtca->scan_ctrl_status before either process reads the
attribute, leading to incorrect results?
[ ... ]
> +What: /sys/devices/system/edac/mc/mc0/xsem_read_ssit_status
> +Date: June 2026
> +Contact: rama.devi.veggalam@amd.com
> +Description:
> + It is a read/write file.
> + Writing to this file causes the software to initiate a
> + request to read CRAM and NPI scan status information in a given SLR.
> + User needs to provide SLR id in Versal SSIT device for which
> + XilSEM status to be read. When read, it shows
> + the XilSEM status of the requested SLR.
> + The format is: <Header> <SLR id> <NPI status info> <CRAM status info>
> + Example: Read SLR Status Cmd:[0x1030d]
> + SLR ID:[0x3]
> + NPI status:[0xa01]
[Severity: Medium]
Will this sysfs attribute violate the single-value-per-file guideline?
Looking at xsem_read_ssit_status_show() in drivers/edac/versal_edac.c, it
generates a multiline block instead of a single value:
offset += sprintf(data + offset, "Read SLR Status Cmd:[0x%x]\n\r",
priv->xsem_rtca->xilsem_ssit_status[0]);
offset += sprintf(data + offset, "SLR ID:[0x%x]\n\r",
priv->xsem_rtca->xilsem_ssit_status[1]);
offset += sprintf(data + offset, "NPI status:[0x%x]\n\r",
Does this complex multiline output break standard userspace tools that
parse sysfs?
[ ... ]
> +What: /sys/devices/system/edac/versal_xilsem/xsem_cram_injecterr
> +Date: July 2025
> +Contact: rama.devi.veggalam@amd.com
[Severity: Low]
Are there duplicate and conflicting entries in the documentation?
The path /sys/devices/system/edac/versal_xilsem/xsem_cram_injecterr
listed here appears to conflict with the /sys/devices/system/edac/mc/mc0/
path documented earlier for the same attribute.
> +Description:
> + It is a read/write file.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260624212545.2850787-1-rama.devi.veggalam@amd.com?part=2
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox