Commit 365e960d authored by Ahmed S. Darwish's avatar Ahmed S. Darwish Committed by Ingo Molnar
Browse files

x86/cacheinfo: Move AMD cache_disable_0/1 handling to separate file



Parent commit decoupled amd_northbridge out of _cpuid4_info_regs, where
it was merely "parked" there until ci_info_init() can store it in the
private pointer of the <linux/cacheinfo.h> API.

Given that decoupling, move the AMD-specific L3 cache_disable_0/1 sysfs
code from the generic (and already extremely convoluted) x86/cacheinfo
code into its own file.

Compile the file only if CONFIG_AMD_NB and CONFIG_SYSFS are both
enabled, which mirrors the existing logic.

Signed-off-by: default avatarAhmed S. Darwish <darwi@linutronix.de>
Signed-off-by: default avatarIngo Molnar <mingo@kernel.org>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Link: https://lore.kernel.org/r/20250324133324.23458-14-darwi@linutronix.de
parent c58ed2d4
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -38,6 +38,9 @@ obj-y += intel.o tsx.o
obj-$(CONFIG_PM)			+= intel_epb.o
endif
obj-$(CONFIG_CPU_SUP_AMD)		+= amd.o
ifeq ($(CONFIG_AMD_NB)$(CONFIG_SYSFS),yy)
obj-y					+= amd_cache_disable.o
endif
obj-$(CONFIG_CPU_SUP_HYGON)		+= hygon.o
obj-$(CONFIG_CPU_SUP_CYRIX_32)		+= cyrix.o
obj-$(CONFIG_CPU_SUP_CENTAUR)		+= centaur.o
+301 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * AMD L3 cache_disable_{0,1} sysfs handling
 * Documentation/ABI/testing/sysfs-devices-system-cpu
 */

#include <linux/cacheinfo.h>
#include <linux/capability.h>
#include <linux/pci.h>
#include <linux/sysfs.h>

#include <asm/amd_nb.h>

#include "cpu.h"

/*
 * L3 cache descriptors
 */
static void amd_calc_l3_indices(struct amd_northbridge *nb)
{
	struct amd_l3_cache *l3 = &nb->l3_cache;
	unsigned int sc0, sc1, sc2, sc3;
	u32 val = 0;

	pci_read_config_dword(nb->misc, 0x1C4, &val);

	/* calculate subcache sizes */
	l3->subcaches[0] = sc0 = !(val & BIT(0));
	l3->subcaches[1] = sc1 = !(val & BIT(4));

	if (boot_cpu_data.x86 == 0x15) {
		l3->subcaches[0] = sc0 += !(val & BIT(1));
		l3->subcaches[1] = sc1 += !(val & BIT(5));
	}

	l3->subcaches[2] = sc2 = !(val & BIT(8))  + !(val & BIT(9));
	l3->subcaches[3] = sc3 = !(val & BIT(12)) + !(val & BIT(13));

	l3->indices = (max(max3(sc0, sc1, sc2), sc3) << 10) - 1;
}

/*
 * check whether a slot used for disabling an L3 index is occupied.
 * @l3: L3 cache descriptor
 * @slot: slot number (0..1)
 *
 * @returns: the disabled index if used or negative value if slot free.
 */
static int amd_get_l3_disable_slot(struct amd_northbridge *nb, unsigned int slot)
{
	unsigned int reg = 0;

	pci_read_config_dword(nb->misc, 0x1BC + slot * 4, &reg);

	/* check whether this slot is activated already */
	if (reg & (3UL << 30))
		return reg & 0xfff;

	return -1;
}

static ssize_t show_cache_disable(struct cacheinfo *ci, char *buf, unsigned int slot)
{
	int index;
	struct amd_northbridge *nb = ci->priv;

	index = amd_get_l3_disable_slot(nb, slot);
	if (index >= 0)
		return sprintf(buf, "%d\n", index);

	return sprintf(buf, "FREE\n");
}

#define SHOW_CACHE_DISABLE(slot)					\
static ssize_t								\
cache_disable_##slot##_show(struct device *dev,				\
			    struct device_attribute *attr, char *buf)	\
{									\
	struct cacheinfo *ci = dev_get_drvdata(dev);			\
	return show_cache_disable(ci, buf, slot);			\
}

SHOW_CACHE_DISABLE(0)
SHOW_CACHE_DISABLE(1)

static void amd_l3_disable_index(struct amd_northbridge *nb, int cpu,
				 unsigned int slot, unsigned long idx)
{
	int i;

	idx |= BIT(30);

	/*
	 *  disable index in all 4 subcaches
	 */
	for (i = 0; i < 4; i++) {
		u32 reg = idx | (i << 20);

		if (!nb->l3_cache.subcaches[i])
			continue;

		pci_write_config_dword(nb->misc, 0x1BC + slot * 4, reg);

		/*
		 * We need to WBINVD on a core on the node containing the L3
		 * cache which indices we disable therefore a simple wbinvd()
		 * is not sufficient.
		 */
		wbinvd_on_cpu(cpu);

		reg |= BIT(31);
		pci_write_config_dword(nb->misc, 0x1BC + slot * 4, reg);
	}
}

/*
 * disable a L3 cache index by using a disable-slot
 *
 * @l3:    L3 cache descriptor
 * @cpu:   A CPU on the node containing the L3 cache
 * @slot:  slot number (0..1)
 * @index: index to disable
 *
 * @return: 0 on success, error status on failure
 */
static int amd_set_l3_disable_slot(struct amd_northbridge *nb, int cpu,
				   unsigned int slot, unsigned long index)
{
	int ret = 0;

	/*  check if @slot is already used or the index is already disabled */
	ret = amd_get_l3_disable_slot(nb, slot);
	if (ret >= 0)
		return -EEXIST;

	if (index > nb->l3_cache.indices)
		return -EINVAL;

	/* check whether the other slot has disabled the same index already */
	if (index == amd_get_l3_disable_slot(nb, !slot))
		return -EEXIST;

	amd_l3_disable_index(nb, cpu, slot, index);

	return 0;
}

static ssize_t store_cache_disable(struct cacheinfo *ci, const char *buf,
				   size_t count, unsigned int slot)
{
	struct amd_northbridge *nb = ci->priv;
	unsigned long val = 0;
	int cpu, err = 0;

	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	cpu = cpumask_first(&ci->shared_cpu_map);

	if (kstrtoul(buf, 10, &val) < 0)
		return -EINVAL;

	err = amd_set_l3_disable_slot(nb, cpu, slot, val);
	if (err) {
		if (err == -EEXIST)
			pr_warn("L3 slot %d in use/index already disabled!\n",
				   slot);
		return err;
	}
	return count;
}

#define STORE_CACHE_DISABLE(slot)					\
static ssize_t								\
cache_disable_##slot##_store(struct device *dev,			\
			     struct device_attribute *attr,		\
			     const char *buf, size_t count)		\
{									\
	struct cacheinfo *ci = dev_get_drvdata(dev);			\
	return store_cache_disable(ci, buf, count, slot);		\
}

STORE_CACHE_DISABLE(0)
STORE_CACHE_DISABLE(1)

static ssize_t subcaches_show(struct device *dev, struct device_attribute *attr,
			      char *buf)
{
	struct cacheinfo *ci = dev_get_drvdata(dev);
	int cpu = cpumask_first(&ci->shared_cpu_map);

	return sprintf(buf, "%x\n", amd_get_subcaches(cpu));
}

static ssize_t subcaches_store(struct device *dev,
			       struct device_attribute *attr,
			       const char *buf, size_t count)
{
	struct cacheinfo *ci = dev_get_drvdata(dev);
	int cpu = cpumask_first(&ci->shared_cpu_map);
	unsigned long val;

	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	if (kstrtoul(buf, 16, &val) < 0)
		return -EINVAL;

	if (amd_set_subcaches(cpu, val))
		return -EINVAL;

	return count;
}

static DEVICE_ATTR_RW(cache_disable_0);
static DEVICE_ATTR_RW(cache_disable_1);
static DEVICE_ATTR_RW(subcaches);

static umode_t cache_private_attrs_is_visible(struct kobject *kobj,
					      struct attribute *attr, int unused)
{
	struct device *dev = kobj_to_dev(kobj);
	struct cacheinfo *ci = dev_get_drvdata(dev);
	umode_t mode = attr->mode;

	if (!ci->priv)
		return 0;

	if ((attr == &dev_attr_subcaches.attr) &&
	    amd_nb_has_feature(AMD_NB_L3_PARTITIONING))
		return mode;

	if ((attr == &dev_attr_cache_disable_0.attr ||
	     attr == &dev_attr_cache_disable_1.attr) &&
	    amd_nb_has_feature(AMD_NB_L3_INDEX_DISABLE))
		return mode;

	return 0;
}

static struct attribute_group cache_private_group = {
	.is_visible = cache_private_attrs_is_visible,
};

static void init_amd_l3_attrs(void)
{
	static struct attribute **amd_l3_attrs;
	int n = 1;

	if (amd_l3_attrs) /* already initialized */
		return;

	if (amd_nb_has_feature(AMD_NB_L3_INDEX_DISABLE))
		n += 2;
	if (amd_nb_has_feature(AMD_NB_L3_PARTITIONING))
		n += 1;

	amd_l3_attrs = kcalloc(n, sizeof(*amd_l3_attrs), GFP_KERNEL);
	if (!amd_l3_attrs)
		return;

	n = 0;
	if (amd_nb_has_feature(AMD_NB_L3_INDEX_DISABLE)) {
		amd_l3_attrs[n++] = &dev_attr_cache_disable_0.attr;
		amd_l3_attrs[n++] = &dev_attr_cache_disable_1.attr;
	}
	if (amd_nb_has_feature(AMD_NB_L3_PARTITIONING))
		amd_l3_attrs[n++] = &dev_attr_subcaches.attr;

	cache_private_group.attrs = amd_l3_attrs;
}

const struct attribute_group *cache_get_priv_group(struct cacheinfo *ci)
{
	struct amd_northbridge *nb = ci->priv;

	if (ci->level < 3 || !nb)
		return NULL;

	if (nb && nb->l3_cache.indices)
		init_amd_l3_attrs();

	return &cache_private_group;
}

struct amd_northbridge *amd_init_l3_cache(int index)
{
	struct amd_northbridge *nb;
	int node;

	/* only for L3, and not in virtualized environments */
	if (index < 3)
		return NULL;

	node = topology_amd_node_id(smp_processor_id());
	nb = node_to_amd_nb(node);
	if (nb && !nb->l3_cache.indices)
		amd_calc_l3_indices(nb);

	return nb;
}
+0 −298
Original line number Diff line number Diff line
@@ -9,12 +9,9 @@
 */

#include <linux/cacheinfo.h>
#include <linux/capability.h>
#include <linux/cpu.h>
#include <linux/cpuhotplug.h>
#include <linux/pci.h>
#include <linux/stop_machine.h>
#include <linux/sysfs.h>

#include <asm/amd_nb.h>
#include <asm/cacheinfo.h>
@@ -300,301 +297,6 @@ amd_cpuid4(int index, union _cpuid4_leaf_eax *eax,
		(ebx->split.ways_of_associativity + 1) - 1;
}

#if defined(CONFIG_AMD_NB) && defined(CONFIG_SYSFS)

/*
 * L3 cache descriptors
 */
static void amd_calc_l3_indices(struct amd_northbridge *nb)
{
	struct amd_l3_cache *l3 = &nb->l3_cache;
	unsigned int sc0, sc1, sc2, sc3;
	u32 val = 0;

	pci_read_config_dword(nb->misc, 0x1C4, &val);

	/* calculate subcache sizes */
	l3->subcaches[0] = sc0 = !(val & BIT(0));
	l3->subcaches[1] = sc1 = !(val & BIT(4));

	if (boot_cpu_data.x86 == 0x15) {
		l3->subcaches[0] = sc0 += !(val & BIT(1));
		l3->subcaches[1] = sc1 += !(val & BIT(5));
	}

	l3->subcaches[2] = sc2 = !(val & BIT(8))  + !(val & BIT(9));
	l3->subcaches[3] = sc3 = !(val & BIT(12)) + !(val & BIT(13));

	l3->indices = (max(max3(sc0, sc1, sc2), sc3) << 10) - 1;
}

/*
 * check whether a slot used for disabling an L3 index is occupied.
 * @l3: L3 cache descriptor
 * @slot: slot number (0..1)
 *
 * @returns: the disabled index if used or negative value if slot free.
 */
static int amd_get_l3_disable_slot(struct amd_northbridge *nb, unsigned slot)
{
	unsigned int reg = 0;

	pci_read_config_dword(nb->misc, 0x1BC + slot * 4, &reg);

	/* check whether this slot is activated already */
	if (reg & (3UL << 30))
		return reg & 0xfff;

	return -1;
}

static ssize_t show_cache_disable(struct cacheinfo *ci, char *buf, unsigned int slot)
{
	int index;
	struct amd_northbridge *nb = ci->priv;

	index = amd_get_l3_disable_slot(nb, slot);
	if (index >= 0)
		return sprintf(buf, "%d\n", index);

	return sprintf(buf, "FREE\n");
}

#define SHOW_CACHE_DISABLE(slot)					\
static ssize_t								\
cache_disable_##slot##_show(struct device *dev,				\
			    struct device_attribute *attr, char *buf)	\
{									\
	struct cacheinfo *ci = dev_get_drvdata(dev);			\
	return show_cache_disable(ci, buf, slot);			\
}
SHOW_CACHE_DISABLE(0)
SHOW_CACHE_DISABLE(1)

static void amd_l3_disable_index(struct amd_northbridge *nb, int cpu,
				 unsigned slot, unsigned long idx)
{
	int i;

	idx |= BIT(30);

	/*
	 *  disable index in all 4 subcaches
	 */
	for (i = 0; i < 4; i++) {
		u32 reg = idx | (i << 20);

		if (!nb->l3_cache.subcaches[i])
			continue;

		pci_write_config_dword(nb->misc, 0x1BC + slot * 4, reg);

		/*
		 * We need to WBINVD on a core on the node containing the L3
		 * cache which indices we disable therefore a simple wbinvd()
		 * is not sufficient.
		 */
		wbinvd_on_cpu(cpu);

		reg |= BIT(31);
		pci_write_config_dword(nb->misc, 0x1BC + slot * 4, reg);
	}
}

/*
 * disable a L3 cache index by using a disable-slot
 *
 * @l3:    L3 cache descriptor
 * @cpu:   A CPU on the node containing the L3 cache
 * @slot:  slot number (0..1)
 * @index: index to disable
 *
 * @return: 0 on success, error status on failure
 */
static int amd_set_l3_disable_slot(struct amd_northbridge *nb, int cpu,
			    unsigned slot, unsigned long index)
{
	int ret = 0;

	/*  check if @slot is already used or the index is already disabled */
	ret = amd_get_l3_disable_slot(nb, slot);
	if (ret >= 0)
		return -EEXIST;

	if (index > nb->l3_cache.indices)
		return -EINVAL;

	/* check whether the other slot has disabled the same index already */
	if (index == amd_get_l3_disable_slot(nb, !slot))
		return -EEXIST;

	amd_l3_disable_index(nb, cpu, slot, index);

	return 0;
}

static ssize_t store_cache_disable(struct cacheinfo *ci, const char *buf,
				   size_t count, unsigned int slot)
{
	unsigned long val = 0;
	int cpu, err = 0;
	struct amd_northbridge *nb = ci->priv;

	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	cpu = cpumask_first(&ci->shared_cpu_map);

	if (kstrtoul(buf, 10, &val) < 0)
		return -EINVAL;

	err = amd_set_l3_disable_slot(nb, cpu, slot, val);
	if (err) {
		if (err == -EEXIST)
			pr_warn("L3 slot %d in use/index already disabled!\n",
				   slot);
		return err;
	}
	return count;
}

#define STORE_CACHE_DISABLE(slot)					\
static ssize_t								\
cache_disable_##slot##_store(struct device *dev,			\
			     struct device_attribute *attr,		\
			     const char *buf, size_t count)		\
{									\
	struct cacheinfo *ci = dev_get_drvdata(dev);			\
	return store_cache_disable(ci, buf, count, slot);		\
}
STORE_CACHE_DISABLE(0)
STORE_CACHE_DISABLE(1)

static ssize_t subcaches_show(struct device *dev,
			      struct device_attribute *attr, char *buf)
{
	struct cacheinfo *ci = dev_get_drvdata(dev);
	int cpu = cpumask_first(&ci->shared_cpu_map);

	return sprintf(buf, "%x\n", amd_get_subcaches(cpu));
}

static ssize_t subcaches_store(struct device *dev,
			       struct device_attribute *attr,
			       const char *buf, size_t count)
{
	struct cacheinfo *ci = dev_get_drvdata(dev);
	int cpu = cpumask_first(&ci->shared_cpu_map);
	unsigned long val;

	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	if (kstrtoul(buf, 16, &val) < 0)
		return -EINVAL;

	if (amd_set_subcaches(cpu, val))
		return -EINVAL;

	return count;
}

static DEVICE_ATTR_RW(cache_disable_0);
static DEVICE_ATTR_RW(cache_disable_1);
static DEVICE_ATTR_RW(subcaches);

static umode_t
cache_private_attrs_is_visible(struct kobject *kobj,
			       struct attribute *attr, int unused)
{
	struct device *dev = kobj_to_dev(kobj);
	struct cacheinfo *ci = dev_get_drvdata(dev);
	umode_t mode = attr->mode;

	if (!ci->priv)
		return 0;

	if ((attr == &dev_attr_subcaches.attr) &&
	    amd_nb_has_feature(AMD_NB_L3_PARTITIONING))
		return mode;

	if ((attr == &dev_attr_cache_disable_0.attr ||
	     attr == &dev_attr_cache_disable_1.attr) &&
	    amd_nb_has_feature(AMD_NB_L3_INDEX_DISABLE))
		return mode;

	return 0;
}

static struct attribute_group cache_private_group = {
	.is_visible = cache_private_attrs_is_visible,
};

static void init_amd_l3_attrs(void)
{
	int n = 1;
	static struct attribute **amd_l3_attrs;

	if (amd_l3_attrs) /* already initialized */
		return;

	if (amd_nb_has_feature(AMD_NB_L3_INDEX_DISABLE))
		n += 2;
	if (amd_nb_has_feature(AMD_NB_L3_PARTITIONING))
		n += 1;

	amd_l3_attrs = kcalloc(n, sizeof(*amd_l3_attrs), GFP_KERNEL);
	if (!amd_l3_attrs)
		return;

	n = 0;
	if (amd_nb_has_feature(AMD_NB_L3_INDEX_DISABLE)) {
		amd_l3_attrs[n++] = &dev_attr_cache_disable_0.attr;
		amd_l3_attrs[n++] = &dev_attr_cache_disable_1.attr;
	}
	if (amd_nb_has_feature(AMD_NB_L3_PARTITIONING))
		amd_l3_attrs[n++] = &dev_attr_subcaches.attr;

	cache_private_group.attrs = amd_l3_attrs;
}

const struct attribute_group *
cache_get_priv_group(struct cacheinfo *ci)
{
	struct amd_northbridge *nb = ci->priv;

	if (ci->level < 3 || !nb)
		return NULL;

	if (nb && nb->l3_cache.indices)
		init_amd_l3_attrs();

	return &cache_private_group;
}

static struct amd_northbridge *amd_init_l3_cache(int index)
{
	struct amd_northbridge *nb;
	int node;

	/* only for L3, and not in virtualized environments */
	if (index < 3)
		return NULL;

	node = topology_amd_node_id(smp_processor_id());
	nb = node_to_amd_nb(node);
	if (nb && !nb->l3_cache.indices)
		amd_calc_l3_indices(nb);

	return nb;
}
#else
static struct amd_northbridge *amd_init_l3_cache(int index)
{
	return NULL;
}
#endif  /* CONFIG_AMD_NB && CONFIG_SYSFS */

/*
 * Fill passed _cpuid4_info_regs structure.
 * Intel-only code paths should pass NULL for the amd_northbridge
+9 −0
Original line number Diff line number Diff line
@@ -75,6 +75,15 @@ extern void check_null_seg_clears_base(struct cpuinfo_x86 *c);
void cacheinfo_amd_init_llc_id(struct cpuinfo_x86 *c, u16 die_id);
void cacheinfo_hygon_init_llc_id(struct cpuinfo_x86 *c);

#if defined(CONFIG_AMD_NB) && defined(CONFIG_SYSFS)
struct amd_northbridge *amd_init_l3_cache(int index);
#else
static inline struct amd_northbridge *amd_init_l3_cache(int index)
{
	return NULL;
}
#endif

unsigned int aperfmperf_get_khz(int cpu);
void cpu_select_mitigations(void);