2980 lines · 78.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0// Copyright (C) 2025 Arm Ltd. #define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ #include <linux/acpi.h>#include <linux/atomic.h>#include <linux/arm_mpam.h>#include <linux/bitfield.h>#include <linux/bitmap.h>#include <linux/cacheinfo.h>#include <linux/cpu.h>#include <linux/cpumask.h>#include <linux/device.h>#include <linux/errno.h>#include <linux/gfp.h>#include <linux/interrupt.h>#include <linux/irq.h>#include <linux/irqdesc.h>#include <linux/list.h>#include <linux/lockdep.h>#include <linux/mutex.h>#include <linux/platform_device.h>#include <linux/printk.h>#include <linux/srcu.h>#include <linux/spinlock.h>#include <linux/types.h>#include <linux/workqueue.h> #include "mpam_internal.h" /* Values for the T241 errata workaround */#define T241_CHIPS_MAX			4#define T241_CHIP_NSLICES		12#define T241_SPARE_REG0_OFF		0x1b0000#define T241_SPARE_REG1_OFF		0x1c0000#define T241_CHIP_ID(phys)		FIELD_GET(GENMASK_ULL(44, 43), phys)#define T241_SHADOW_REG_OFF(sidx, pid)	(0x360048 + (sidx) * 0x10000 + (pid) * 8)#define SMCCC_SOC_ID_T241		0x036b0241static void __iomem *t241_scratch_regs[T241_CHIPS_MAX]; /* * mpam_list_lock protects the SRCU lists when writing. Once the * mpam_enabled key is enabled these lists are read-only, * unless the error interrupt disables the driver. */static DEFINE_MUTEX(mpam_list_lock);static LIST_HEAD(mpam_all_msc); struct srcu_struct mpam_srcu; /* * Number of MSCs that have been probed. Once all MSCs have been probed MPAM * can be enabled. */static atomic_t mpam_num_msc; static int mpam_cpuhp_state;static DEFINE_MUTEX(mpam_cpuhp_state_lock); /* * The smallest common values for any CPU or MSC in the system. * Generating traffic outside this range will result in screaming interrupts. */u16 mpam_partid_max;u8 mpam_pmg_max;static bool partid_max_init, partid_max_published;static DEFINE_SPINLOCK(partid_max_lock); /* * mpam is enabled once all devices have been probed from CPU online callbacks, * scheduled via this work_struct. If access to an MSC depends on a CPU that * was not brought online at boot, this can happen surprisingly late. */static DECLARE_WORK(mpam_enable_work, &mpam_enable); /* * All mpam error interrupts indicate a software bug. On receipt, disable the * driver. */static DECLARE_WORK(mpam_broken_work, &mpam_disable); /* When mpam is disabled, the printed reason to aid debugging */static char *mpam_disable_reason; /* * Whether resctrl has been setup. Used by cpuhp in preference to * mpam_is_enabled(). The disable call after an error interrupt makes * mpam_is_enabled() false before the cpuhp callbacks are made. * Reads/writes should hold mpam_cpuhp_state_lock, (or be cpuhp callbacks). */static bool mpam_resctrl_enabled; /* * An MSC is a physical container for controls and monitors, each identified by * their RIS index. These share a base-address, interrupts and some MMIO * registers. A vMSC is a virtual container for RIS in an MSC that control or * monitor the same thing. Members of a vMSC are all RIS in the same MSC, but * not all RIS in an MSC share a vMSC. * * Components are a group of vMSC that control or monitor the same thing but * are from different MSC, so have different base-address, interrupts etc. * Classes are the set components of the same type. * * The features of a vMSC is the union of the RIS it contains. * The features of a Class and Component are the common subset of the vMSC * they contain. * * e.g. The system cache may have bandwidth controls on multiple interfaces, * for regulating traffic from devices independently of traffic from CPUs. * If these are two RIS in one MSC, they will be treated as controlling * different things, and will not share a vMSC/component/class. * * e.g. The L2 may have one MSC and two RIS, one for cache-controls another * for bandwidth. These two RIS are members of the same vMSC. * * e.g. The set of RIS that make up the L2 are grouped as a component. These * are sometimes termed slices. They should be configured the same, as if there * were only one. * * e.g. The SoC probably has more than one L2, each attached to a distinct set * of CPUs. All the L2 components are grouped as a class. * * When creating an MSC, struct mpam_msc is added to the all mpam_all_msc list, * then linked via struct mpam_ris to a vmsc, component and class. * The same MSC may exist under different class->component->vmsc paths, but the * RIS index will be unique. */LIST_HEAD(mpam_classes); /* List of all objects that can be free()d after synchronise_srcu() */static LLIST_HEAD(mpam_garbage); static inline void init_garbage(struct mpam_garbage *garbage){	init_llist_node(&garbage->llist);} #define add_to_garbage(x)				\do {							\	__typeof__(x) _x = (x);				\	_x->garbage.to_free = _x;			\	llist_add(&_x->garbage.llist, &mpam_garbage);	\} while (0) static void mpam_free_garbage(void){	struct mpam_garbage *iter, *tmp;	struct llist_node *to_free = llist_del_all(&mpam_garbage); 	if (!to_free)		return; 	synchronize_srcu(&mpam_srcu); 	llist_for_each_entry_safe(iter, tmp, to_free, llist) {		if (iter->pdev)			devm_kfree(&iter->pdev->dev, iter->to_free);		else			kfree(iter->to_free);	}} /* * Once mpam is enabled, new requestors cannot further reduce the available * partid. Assert that the size is fixed, and new requestors will be turned * away. This is needed when walking over structures sized by PARTID. * * During mpam_disable() these structures are not fixed, but the MSC state * is still reset using whatever sizes have been discovered so far. As only * PARTID 0 will be used after mpam_disable(), any race would be benign. * Skip the check if a mpam_disable_reason has been set. */static void mpam_assert_partid_sizes_fixed(void){	if (!mpam_disable_reason)		WARN_ON_ONCE(!partid_max_published);} static u32 __mpam_read_reg(struct mpam_msc *msc, u16 reg){	WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility)); 	return readl_relaxed(msc->mapped_hwpage + reg);} static inline u32 _mpam_read_partsel_reg(struct mpam_msc *msc, u16 reg){	lockdep_assert_held_once(&msc->part_sel_lock);	return __mpam_read_reg(msc, reg);} #define mpam_read_partsel_reg(msc, reg) _mpam_read_partsel_reg(msc, MPAMF_##reg) static void __mpam_write_reg(struct mpam_msc *msc, u16 reg, u32 val){	WARN_ON_ONCE(reg + sizeof(u32) > msc->mapped_hwpage_sz);	WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility)); 	writel_relaxed(val, msc->mapped_hwpage + reg);} static inline void _mpam_write_partsel_reg(struct mpam_msc *msc, u16 reg, u32 val){	lockdep_assert_held_once(&msc->part_sel_lock);	__mpam_write_reg(msc, reg, val);} #define mpam_write_partsel_reg(msc, reg, val)  _mpam_write_partsel_reg(msc, MPAMCFG_##reg, val) static inline u32 _mpam_read_monsel_reg(struct mpam_msc *msc, u16 reg){	mpam_mon_sel_lock_held(msc);	return __mpam_read_reg(msc, reg);} #define mpam_read_monsel_reg(msc, reg) _mpam_read_monsel_reg(msc, MSMON_##reg) static inline void _mpam_write_monsel_reg(struct mpam_msc *msc, u16 reg, u32 val){	mpam_mon_sel_lock_held(msc);	__mpam_write_reg(msc, reg, val);} #define mpam_write_monsel_reg(msc, reg, val)   _mpam_write_monsel_reg(msc, MSMON_##reg, val) static bool mpam_msc_check_aidr(struct mpam_msc *msc){	u32 aidr = __mpam_read_reg(msc, MPAMF_AIDR);	u32 major = FIELD_GET(MPAMF_AIDR_ARCH_MAJOR_REV, aidr);	u32 minor = FIELD_GET(MPAMF_AIDR_ARCH_MINOR_REV, aidr); 	/*	 * v0.0 and >v2.x aren't supported, but anything else should be backward	 * compatible to v0.1 or v1.0.	 */	if (!major && !minor)		return false;	if (major > 1)		return false; 	return true;} static u64 mpam_msc_read_idr(struct mpam_msc *msc){	u64 idr_high = 0, idr_low; 	lockdep_assert_held(&msc->part_sel_lock); 	idr_low = mpam_read_partsel_reg(msc, IDR);	if (FIELD_GET(MPAMF_IDR_EXT, idr_low))		idr_high = mpam_read_partsel_reg(msc, IDR + 4); 	return (idr_high << 32) | idr_low;} static void mpam_msc_clear_esr(struct mpam_msc *msc){	u64 esr_low = __mpam_read_reg(msc, MPAMF_ESR); 	if (!esr_low)		return; 	/*	 * Clearing the high/low bits of MPAMF_ESR can not be atomic.	 * Clear the top half first, so that the pending error bits in the	 * lower half prevent hardware from updating either half of the	 * register.	 */	if (msc->has_extd_esr)		__mpam_write_reg(msc, MPAMF_ESR + 4, 0);	__mpam_write_reg(msc, MPAMF_ESR, 0);} static u64 mpam_msc_read_esr(struct mpam_msc *msc){	u64 esr_high = 0, esr_low; 	esr_low = __mpam_read_reg(msc, MPAMF_ESR);	if (msc->has_extd_esr)		esr_high = __mpam_read_reg(msc, MPAMF_ESR + 4); 	return (esr_high << 32) | esr_low;} static void __mpam_part_sel_raw(u32 partsel, struct mpam_msc *msc){	lockdep_assert_held(&msc->part_sel_lock); 	mpam_write_partsel_reg(msc, PART_SEL, partsel);} static void __mpam_part_sel(u8 ris_idx, u16 partid, struct mpam_msc *msc){	u32 partsel = FIELD_PREP(MPAMCFG_PART_SEL_RIS, ris_idx) |		      FIELD_PREP(MPAMCFG_PART_SEL_PARTID_SEL, partid); 	__mpam_part_sel_raw(partsel, msc);} static void __mpam_intpart_sel(u8 ris_idx, u16 intpartid, struct mpam_msc *msc){	u32 partsel = FIELD_PREP(MPAMCFG_PART_SEL_RIS, ris_idx) |		      FIELD_PREP(MPAMCFG_PART_SEL_PARTID_SEL, intpartid) |		      MPAMCFG_PART_SEL_INTERNAL; 	__mpam_part_sel_raw(partsel, msc);} int mpam_register_requestor(u16 partid_max, u8 pmg_max){	guard(spinlock)(&partid_max_lock);	if (!partid_max_init) {		mpam_partid_max = partid_max;		mpam_pmg_max = pmg_max;		partid_max_init = true;	} else if (!partid_max_published) {		mpam_partid_max = min(mpam_partid_max, partid_max);		mpam_pmg_max = min(mpam_pmg_max, pmg_max);	} else {		/* New requestors can't lower the values */		if (partid_max < mpam_partid_max || pmg_max < mpam_pmg_max)			return -EBUSY;	} 	return 0;}EXPORT_SYMBOL(mpam_register_requestor); static struct mpam_class *mpam_class_alloc(u8 level_idx, enum mpam_class_types type){	struct mpam_class *class; 	lockdep_assert_held(&mpam_list_lock); 	class = kzalloc_obj(*class);	if (!class)		return ERR_PTR(-ENOMEM);	init_garbage(&class->garbage); 	INIT_LIST_HEAD_RCU(&class->components);	/* Affinity is updated when ris are added */	class->level = level_idx;	class->type = type;	INIT_LIST_HEAD_RCU(&class->classes_list);	ida_init(&class->ida_csu_mon);	ida_init(&class->ida_mbwu_mon); 	list_add_rcu(&class->classes_list, &mpam_classes); 	return class;} static void mpam_class_destroy(struct mpam_class *class){	lockdep_assert_held(&mpam_list_lock); 	list_del_rcu(&class->classes_list);	add_to_garbage(class);} static struct mpam_class *mpam_class_find(u8 level_idx, enum mpam_class_types type){	struct mpam_class *class; 	lockdep_assert_held(&mpam_list_lock); 	list_for_each_entry(class, &mpam_classes, classes_list) {		if (class->type == type && class->level == level_idx)			return class;	} 	return mpam_class_alloc(level_idx, type);} static struct mpam_component *mpam_component_alloc(struct mpam_class *class, int id){	struct mpam_component *comp; 	lockdep_assert_held(&mpam_list_lock); 	comp = kzalloc_obj(*comp);	if (!comp)		return ERR_PTR(-ENOMEM);	init_garbage(&comp->garbage); 	comp->comp_id = id;	INIT_LIST_HEAD_RCU(&comp->vmsc);	/* Affinity is updated when RIS are added */	INIT_LIST_HEAD_RCU(&comp->class_list);	comp->class = class; 	list_add_rcu(&comp->class_list, &class->components); 	return comp;} static void __destroy_component_cfg(struct mpam_component *comp); static void mpam_component_destroy(struct mpam_component *comp){	struct mpam_class *class = comp->class; 	lockdep_assert_held(&mpam_list_lock); 	__destroy_component_cfg(comp); 	list_del_rcu(&comp->class_list);	add_to_garbage(comp); 	if (list_empty(&class->components))		mpam_class_destroy(class);} static struct mpam_component *mpam_component_find(struct mpam_class *class, int id){	struct mpam_component *comp; 	lockdep_assert_held(&mpam_list_lock); 	list_for_each_entry(comp, &class->components, class_list) {		if (comp->comp_id == id)			return comp;	} 	return mpam_component_alloc(class, id);} static struct mpam_vmsc *mpam_vmsc_alloc(struct mpam_component *comp, struct mpam_msc *msc){	struct mpam_vmsc *vmsc; 	lockdep_assert_held(&mpam_list_lock); 	vmsc = kzalloc_obj(*vmsc);	if (!vmsc)		return ERR_PTR(-ENOMEM);	init_garbage(&vmsc->garbage); 	INIT_LIST_HEAD_RCU(&vmsc->ris);	INIT_LIST_HEAD_RCU(&vmsc->comp_list);	vmsc->comp = comp;	vmsc->msc = msc; 	list_add_rcu(&vmsc->comp_list, &comp->vmsc); 	return vmsc;} static void mpam_vmsc_destroy(struct mpam_vmsc *vmsc){	struct mpam_component *comp = vmsc->comp; 	lockdep_assert_held(&mpam_list_lock); 	list_del_rcu(&vmsc->comp_list);	add_to_garbage(vmsc); 	if (list_empty(&comp->vmsc))		mpam_component_destroy(comp);} static struct mpam_vmsc *mpam_vmsc_find(struct mpam_component *comp, struct mpam_msc *msc){	struct mpam_vmsc *vmsc; 	lockdep_assert_held(&mpam_list_lock); 	list_for_each_entry(vmsc, &comp->vmsc, comp_list) {		if (vmsc->msc->id == msc->id)			return vmsc;	} 	return mpam_vmsc_alloc(comp, msc);} /* * The cacheinfo structures are only populated when CPUs are online. * This helper walks the acpi tables to include offline CPUs too. */int mpam_get_cpumask_from_cache_id(unsigned long cache_id, u32 cache_level,				   cpumask_t *affinity){	return acpi_pptt_get_cpumask_from_cache_id(cache_id, affinity);} /* * cpumask_of_node() only knows about online CPUs. This can't tell us whether * a class is represented on all possible CPUs. */static void get_cpumask_from_node_id(u32 node_id, cpumask_t *affinity){	int cpu;

Lines 1–500 of 2980.