Contributors: 6
Author Tokens Token Proportion Commits Commit Proportion
Riana Tauro 831 58.40% 2 25.00%
Lucas De Marchi 566 39.78% 2 25.00%
Matthew Brost 17 1.19% 1 12.50%
Michal Wajdeczko 6 0.42% 1 12.50%
Thomas Zimmermann 2 0.14% 1 12.50%
Gustavo Sousa 1 0.07% 1 12.50%
Total 1423 8


// SPDX-License-Identifier: MIT
/*
 * Copyright © 2025 Intel Corporation
 */

#include <linux/bitops.h>
#include <linux/configfs.h>
#include <linux/find.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/string.h>

#include "xe_configfs.h"
#include "xe_module.h"

#include "xe_hw_engine_types.h"

/**
 * DOC: Xe Configfs
 *
 * Overview
 * =========
 *
 * Configfs is a filesystem-based manager of kernel objects. XE KMD registers a
 * configfs subsystem called ``'xe'`` that creates a directory in the mounted configfs directory
 * The user can create devices under this directory and configure them as necessary
 * See Documentation/filesystems/configfs.rst for more information about how configfs works.
 *
 * Create devices
 * ===============
 *
 * In order to create a device, the user has to create a directory inside ``'xe'``::
 *
 *	mkdir /sys/kernel/config/xe/0000:03:00.0/
 *
 * Every device created is populated by the driver with entries that can be
 * used to configure it::
 *
 *	/sys/kernel/config/xe/
 *		.. 0000:03:00.0/
 *			... survivability_mode
 *
 * Configure Attributes
 * ====================
 *
 * Survivability mode:
 * -------------------
 *
 * Enable survivability mode on supported cards. This setting only takes
 * effect when probing the device. Example to enable it::
 *
 *	# echo 1 > /sys/kernel/config/xe/0000:03:00.0/survivability_mode
 *	# echo 0000:03:00.0 > /sys/bus/pci/drivers/xe/bind  (Enters survivability mode if supported)
 *
 * Allowed engines:
 * ----------------
 *
 * Allow only a set of engine(s) to be available, disabling the other engines
 * even if they are available in hardware. This is applied after HW fuses are
 * considered on each tile. Examples:
 *
 * Allow only one render and one copy engines, nothing else::
 *
 *	# echo 'rcs0,bcs0' > /sys/kernel/config/xe/0000:03:00.0/engines_allowed
 *
 * Allow only compute engines and first copy engine::
 *
 *	# echo 'ccs*,bcs0' > /sys/kernel/config/xe/0000:03:00.0/engines_allowed
 *
 * Note that the engine names are the per-GT hardware names. On multi-tile
 * platforms, writing ``rcs0,bcs0`` to this file would allow the first render
 * and copy engines on each tile.
 *
 * The requested configuration may not be supported by the platform and driver
 * may fail to probe. For example: if at least one copy engine is expected to be
 * available for migrations, but it's disabled. This is intended for debugging
 * purposes only.
 *
 * Remove devices
 * ==============
 *
 * The created device directories can be removed using ``rmdir``::
 *
 *	rmdir /sys/kernel/config/xe/0000:03:00.0/
 */

struct xe_config_device {
	struct config_group group;

	bool survivability_mode;
	u64 engines_allowed;

	/* protects attributes */
	struct mutex lock;
};

struct engine_info {
	const char *cls;
	u64 mask;
};

/* Some helpful macros to aid on the sizing of buffer allocation when parsing */
#define MAX_ENGINE_CLASS_CHARS 5
#define MAX_ENGINE_INSTANCE_CHARS 2

static const struct engine_info engine_info[] = {
	{ .cls = "rcs", .mask = XE_HW_ENGINE_RCS_MASK },
	{ .cls = "bcs", .mask = XE_HW_ENGINE_BCS_MASK },
	{ .cls = "vcs", .mask = XE_HW_ENGINE_VCS_MASK },
	{ .cls = "vecs", .mask = XE_HW_ENGINE_VECS_MASK },
	{ .cls = "ccs", .mask = XE_HW_ENGINE_CCS_MASK },
	{ .cls = "gsccs", .mask = XE_HW_ENGINE_GSCCS_MASK },
};

static struct xe_config_device *to_xe_config_device(struct config_item *item)
{
	return container_of(to_config_group(item), struct xe_config_device, group);
}

static ssize_t survivability_mode_show(struct config_item *item, char *page)
{
	struct xe_config_device *dev = to_xe_config_device(item);

	return sprintf(page, "%d\n", dev->survivability_mode);
}

static ssize_t survivability_mode_store(struct config_item *item, const char *page, size_t len)
{
	struct xe_config_device *dev = to_xe_config_device(item);
	bool survivability_mode;
	int ret;

	ret = kstrtobool(page, &survivability_mode);
	if (ret)
		return ret;

	mutex_lock(&dev->lock);
	dev->survivability_mode = survivability_mode;
	mutex_unlock(&dev->lock);

	return len;
}

static ssize_t engines_allowed_show(struct config_item *item, char *page)
{
	struct xe_config_device *dev = to_xe_config_device(item);
	char *p = page;

	for (size_t i = 0; i < ARRAY_SIZE(engine_info); i++) {
		u64 mask = engine_info[i].mask;

		if ((dev->engines_allowed & mask) == mask) {
			p += sprintf(p, "%s*\n", engine_info[i].cls);
		} else if (mask & dev->engines_allowed) {
			u16 bit0 = __ffs64(mask), bit;

			mask &= dev->engines_allowed;

			for_each_set_bit(bit, (const unsigned long *)&mask, 64)
				p += sprintf(p, "%s%u\n", engine_info[i].cls,
					     bit - bit0);
		}
	}

	return p - page;
}

static bool lookup_engine_mask(const char *pattern, u64 *mask)
{
	for (size_t i = 0; i < ARRAY_SIZE(engine_info); i++) {
		u8 instance;
		u16 bit;

		if (!str_has_prefix(pattern, engine_info[i].cls))
			continue;

		pattern += strlen(engine_info[i].cls);

		if (!strcmp(pattern, "*")) {
			*mask = engine_info[i].mask;
			return true;
		}

		if (kstrtou8(pattern, 10, &instance))
			return false;

		bit = __ffs64(engine_info[i].mask) + instance;
		if (bit >= fls64(engine_info[i].mask))
			return false;

		*mask = BIT_ULL(bit);
		return true;
	}

	return false;
}

static ssize_t engines_allowed_store(struct config_item *item, const char *page,
				     size_t len)
{
	struct xe_config_device *dev = to_xe_config_device(item);
	size_t patternlen, p;
	u64 mask, val = 0;

	for (p = 0; p < len; p += patternlen + 1) {
		char buf[MAX_ENGINE_CLASS_CHARS + MAX_ENGINE_INSTANCE_CHARS + 1];

		patternlen = strcspn(page + p, ",\n");
		if (patternlen >= sizeof(buf))
			return -EINVAL;

		memcpy(buf, page + p, patternlen);
		buf[patternlen] = '\0';

		if (!lookup_engine_mask(buf, &mask))
			return -EINVAL;

		val |= mask;
	}

	mutex_lock(&dev->lock);
	dev->engines_allowed = val;
	mutex_unlock(&dev->lock);

	return len;
}

CONFIGFS_ATTR(, survivability_mode);
CONFIGFS_ATTR(, engines_allowed);

static struct configfs_attribute *xe_config_device_attrs[] = {
	&attr_survivability_mode,
	&attr_engines_allowed,
	NULL,
};

static void xe_config_device_release(struct config_item *item)
{
	struct xe_config_device *dev = to_xe_config_device(item);

	mutex_destroy(&dev->lock);
	kfree(dev);
}

static struct configfs_item_operations xe_config_device_ops = {
	.release	= xe_config_device_release,
};

static const struct config_item_type xe_config_device_type = {
	.ct_item_ops	= &xe_config_device_ops,
	.ct_attrs	= xe_config_device_attrs,
	.ct_owner	= THIS_MODULE,
};

static struct config_group *xe_config_make_device_group(struct config_group *group,
							const char *name)
{
	unsigned int domain, bus, slot, function;
	struct xe_config_device *dev;
	struct pci_dev *pdev;
	int ret;

	ret = sscanf(name, "%04x:%02x:%02x.%x", &domain, &bus, &slot, &function);
	if (ret != 4)
		return ERR_PTR(-EINVAL);

	pdev = pci_get_domain_bus_and_slot(domain, bus, PCI_DEVFN(slot, function));
	if (!pdev)
		return ERR_PTR(-ENODEV);
	pci_dev_put(pdev);

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return ERR_PTR(-ENOMEM);

	/* Default values */
	dev->engines_allowed = U64_MAX;

	config_group_init_type_name(&dev->group, name, &xe_config_device_type);

	mutex_init(&dev->lock);

	return &dev->group;
}

static struct configfs_group_operations xe_config_device_group_ops = {
	.make_group	= xe_config_make_device_group,
};

static const struct config_item_type xe_configfs_type = {
	.ct_group_ops	= &xe_config_device_group_ops,
	.ct_owner	= THIS_MODULE,
};

static struct configfs_subsystem xe_configfs = {
	.su_group = {
		.cg_item = {
			.ci_namebuf = "xe",
			.ci_type = &xe_configfs_type,
		},
	},
};

static struct xe_config_device *configfs_find_group(struct pci_dev *pdev)
{
	struct config_item *item;
	char name[64];

	snprintf(name, sizeof(name), "%04x:%02x:%02x.%x", pci_domain_nr(pdev->bus),
		 pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn));

	mutex_lock(&xe_configfs.su_mutex);
	item = config_group_find_item(&xe_configfs.su_group, name);
	mutex_unlock(&xe_configfs.su_mutex);

	if (!item)
		return NULL;

	return to_xe_config_device(item);
}

/**
 * xe_configfs_get_survivability_mode - get configfs survivability mode attribute
 * @pdev: pci device
 *
 * find the configfs group that belongs to the pci device and return
 * the survivability mode attribute
 *
 * Return: survivability mode if config group is found, false otherwise
 */
bool xe_configfs_get_survivability_mode(struct pci_dev *pdev)
{
	struct xe_config_device *dev = configfs_find_group(pdev);
	bool mode;

	if (!dev)
		return false;

	mode = dev->survivability_mode;
	config_item_put(&dev->group.cg_item);

	return mode;
}

/**
 * xe_configfs_clear_survivability_mode - clear configfs survivability mode attribute
 * @pdev: pci device
 *
 * find the configfs group that belongs to the pci device and clear survivability
 * mode attribute
 */
void xe_configfs_clear_survivability_mode(struct pci_dev *pdev)
{
	struct xe_config_device *dev = configfs_find_group(pdev);

	if (!dev)
		return;

	mutex_lock(&dev->lock);
	dev->survivability_mode = 0;
	mutex_unlock(&dev->lock);

	config_item_put(&dev->group.cg_item);
}

/**
 * xe_configfs_get_engines_allowed - get engine allowed mask from configfs
 * @pdev: pci device
 *
 * Find the configfs group that belongs to the pci device and return
 * the mask of engines allowed to be used.
 *
 * Return: engine mask with allowed engines
 */
u64 xe_configfs_get_engines_allowed(struct pci_dev *pdev)
{
	struct xe_config_device *dev = configfs_find_group(pdev);
	u64 engines_allowed;

	if (!dev)
		return U64_MAX;

	engines_allowed = dev->engines_allowed;
	config_item_put(&dev->group.cg_item);

	return engines_allowed;
}

int __init xe_configfs_init(void)
{
	struct config_group *root = &xe_configfs.su_group;
	int ret;

	config_group_init(root);
	mutex_init(&xe_configfs.su_mutex);
	ret = configfs_register_subsystem(&xe_configfs);
	if (ret) {
		pr_err("Error %d while registering %s subsystem\n",
		       ret, root->cg_item.ci_namebuf);
		return ret;
	}

	return 0;
}

void xe_configfs_exit(void)
{
	configfs_unregister_subsystem(&xe_configfs);
}