Commit 39ac06f7 authored by Lucas De Marchi's avatar Lucas De Marchi
Browse files

drm/xe/configfs: Add post context restore bb



Allow the user to specify commands to execute during a context restore.
Currently it's possible to parse 2 types of actions:

	- cmd: the instructions are added as is to the bb
	- reg: just use the address and value, without worrying about
	  encoding the right LRI instruction. This is possibly the most
	  useful use case, so added a dedicated action for that.

This also prepares for future BBs: mid context restore and rc6 context
restore that can re-use the same parsing functions.

Reviewed-by: default avatarRaag Jadav <raag.jadav@intel.com>
Link: https://lore.kernel.org/r/20250916-wa-bb-cmds-v5-4-306bddbc15da@intel.com


Signed-off-by: default avatarLucas De Marchi <lucas.demarchi@intel.com>
parent 6c6988c5
Loading
Loading
Loading
Loading
+278 −2
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
 */

#include <linux/bitops.h>
#include <linux/ctype.h>
#include <linux/configfs.h>
#include <linux/cleanup.h>
#include <linux/find.h>
@@ -12,6 +13,7 @@
#include <linux/pci.h>
#include <linux/string.h>

#include "instructions/xe_mi_commands.h"
#include "xe_configfs.h"
#include "xe_hw_engine_types.h"
#include "xe_module.h"
@@ -115,6 +117,37 @@
 *
 * This attribute can only be set before binding to the device.
 *
 * Context restore BB
 * ------------------
 *
 * Allow to execute a batch buffer during any context switches. When the
 * GPU is restoring the context, it executes additional commands. It's useful
 * for testing additional workarounds and validating certain HW behaviors: it's
 * not intended for normal execution and will taint the kernel with TAINT_TEST
 * when used.
 *
 * Currently this is implemented only for post context restore. Examples:
 *
 * #. Execute a LRI command to write 0xDEADBEEF to register 0x4f10::
 *
 *	# echo 'rcs cmd 11000001 4F100 DEADBEEF' \
 *		> /sys/kernel/config/xe/0000:03:00.0/ctx_restore_post_bb
 *
 * #. Load certain values in a couple of registers (it can be used as a simpler
 *    alternative to the `cmd`) action::
 *
 *	# cat > /sys/kernel/config/xe/0000:03:00.0/ctx_restore_post_bb <<EOF
 *	rcs reg 4F100 DEADBEEF
 *	rcs reg 4F104 FFFFFFFF
 *	EOF
 *
 *    .. note::
 *
 *       When using multiple lines, make sure to use a command that is
 *       implemented with a single write syscall, like HEREDOC.
 *
 * This attribute can only be set before binding to the device.
 *
 * Remove devices
 * ==============
 *
@@ -123,11 +156,18 @@
 *	# rmdir /sys/kernel/config/xe/0000:03:00.0/
 */

/* Similar to struct xe_bb, but not tied to HW (yet) */
struct wa_bb {
	u32 *cs;
	u32 len; /* in dwords */
};

struct xe_config_group_device {
	struct config_group group;

	struct xe_config_device {
		u64 engines_allowed;
		struct wa_bb ctx_restore_post_bb[XE_ENGINE_CLASS_MAX];
		bool survivability_mode;
		bool enable_psmi;
	} config;
@@ -371,11 +411,233 @@ static ssize_t enable_psmi_store(struct config_item *item, const char *page, siz
	return len;
}

static bool wa_bb_read_advance(bool dereference, char **p,
			       const char *append, size_t len,
			       size_t *max_size)
{
	if (dereference) {
		if (len >= *max_size)
			return false;
		*max_size -= len;
		if (append)
			memcpy(*p, append, len);
	}

	*p += len;

	return true;
}

static ssize_t wa_bb_show(struct xe_config_group_device *dev,
			  struct wa_bb wa_bb[static XE_ENGINE_CLASS_MAX],
			  char *data, size_t sz)
{
	char *p = data;

	guard(mutex)(&dev->lock);

	for (size_t i = 0; i < ARRAY_SIZE(engine_info); i++) {
		enum xe_engine_class ec = engine_info[i].engine_class;
		size_t len;

		if (!wa_bb[ec].len)
			continue;

		len = snprintf(p, sz, "%s:", engine_info[i].cls);
		if (!wa_bb_read_advance(data, &p, NULL, len, &sz))
			return -ENOBUFS;

		for (size_t j = 0; j < wa_bb[ec].len; j++) {
			len = snprintf(p, sz, " %08x", wa_bb[ec].cs[j]);
			if (!wa_bb_read_advance(data, &p, NULL, len, &sz))
				return -ENOBUFS;
		}

		if (!wa_bb_read_advance(data, &p, "\n", 1, &sz))
			return -ENOBUFS;
	}

	if (!wa_bb_read_advance(data, &p, "", 1, &sz))
		return -ENOBUFS;

	/* Reserve one more to match check for '\0' */
	if (!data)
		p++;

	return p - data;
}

static ssize_t ctx_restore_post_bb_show(struct config_item *item, char *page)
{
	struct xe_config_group_device *dev = to_xe_config_group_device(item);

	return wa_bb_show(dev, dev->config.ctx_restore_post_bb, page, SZ_4K);
}

static void wa_bb_append(struct wa_bb *wa_bb, u32 val)
{
	if (wa_bb->cs)
		wa_bb->cs[wa_bb->len] = val;

	wa_bb->len++;
}

static ssize_t parse_hex(const char *line, u32 *pval)
{
	char numstr[12];
	const char *p;
	ssize_t numlen;

	p = line + strspn(line, " \t");
	if (!*p || *p == '\n')
		return 0;

	numlen = strcspn(p, " \t\n");
	if (!numlen || numlen >= sizeof(numstr) - 1)
		return -EINVAL;

	memcpy(numstr, p, numlen);
	numstr[numlen] = '\0';
	p += numlen;

	if (kstrtou32(numstr, 16, pval))
		return -EINVAL;

	return p - line;
}

/*
 * Parse lines with the format
 *
 *	<engine-class> cmd <u32> <u32...>
 *	<engine-class> reg <u32_addr> <u32_val>
 *
 * and optionally save them in @wa_bb[i].cs is non-NULL.
 *
 * Return the number of dwords parsed.
 */
static ssize_t parse_wa_bb_lines(const char *lines,
				 struct wa_bb wa_bb[static XE_ENGINE_CLASS_MAX])
{
	ssize_t dwords = 0, ret;
	const char *p;

	for (p = lines; *p; p++) {
		const struct engine_info *info = NULL;
		u32 val, val2;

		/* Also allow empty lines */
		p += strspn(p, " \t\n");
		if (!*p)
			break;

		ret = parse_engine(p, " \t\n", NULL, &info);
		if (ret < 0)
			return ret;

		p += ret;
		p += strspn(p, " \t");

		if (str_has_prefix(p, "cmd")) {
			for (p += strlen("cmd"); *p;) {
				ret = parse_hex(p, &val);
				if (ret < 0)
					return -EINVAL;
				if (!ret)
					break;

				p += ret;
				dwords++;
				wa_bb_append(&wa_bb[info->engine_class], val);
			}
		} else if (str_has_prefix(p, "reg")) {
			p += strlen("reg");
			ret = parse_hex(p, &val);
			if (ret <= 0)
				return -EINVAL;

			p += ret;
			ret = parse_hex(p, &val2);
			if (ret <= 0)
				return -EINVAL;

			p += ret;
			dwords += 3;
			wa_bb_append(&wa_bb[info->engine_class],
				     MI_LOAD_REGISTER_IMM | MI_LRI_NUM_REGS(1));
			wa_bb_append(&wa_bb[info->engine_class], val);
			wa_bb_append(&wa_bb[info->engine_class], val2);
		} else {
			return -EINVAL;
		}
	}

	return dwords;
}

static ssize_t wa_bb_store(struct wa_bb wa_bb[static XE_ENGINE_CLASS_MAX],
			   struct xe_config_group_device *dev,
			   const char *page, size_t len)
{
	/* tmp_wa_bb must match wa_bb's size */
	struct wa_bb tmp_wa_bb[XE_ENGINE_CLASS_MAX] = { };
	ssize_t count, class;
	u32 *tmp;

	/* 1. Count dwords - wa_bb[i].cs is NULL for all classes */
	count = parse_wa_bb_lines(page, tmp_wa_bb);
	if (count < 0)
		return count;

	guard(mutex)(&dev->lock);

	if (is_bound(dev))
		return -EBUSY;

	/*
	 * 2. Allocate a u32 array and set the pointers to the right positions
	 * according to the length of each class' wa_bb
	 */
	tmp = krealloc(wa_bb[0].cs, count * sizeof(u32), GFP_KERNEL);
	if (!tmp)
		return -ENOMEM;

	if (!count) {
		memset(wa_bb, 0, sizeof(tmp_wa_bb));
		return len;
	}

	for (class = 0, count = 0; class < XE_ENGINE_CLASS_MAX; ++class) {
		tmp_wa_bb[class].cs = tmp + count;
		count += tmp_wa_bb[class].len;
		tmp_wa_bb[class].len = 0;
	}

	/* 3. Parse wa_bb lines again, this time saving the values */
	count = parse_wa_bb_lines(page, tmp_wa_bb);
	if (count < 0)
		return count;

	memcpy(wa_bb, tmp_wa_bb, sizeof(tmp_wa_bb));

	return len;
}

static ssize_t ctx_restore_post_bb_store(struct config_item *item,
					 const char *data, size_t sz)
{
	struct xe_config_group_device *dev = to_xe_config_group_device(item);

	return wa_bb_store(dev->config.ctx_restore_post_bb, dev, data, sz);
}

CONFIGFS_ATTR(, ctx_restore_post_bb);
CONFIGFS_ATTR(, enable_psmi);
CONFIGFS_ATTR(, engines_allowed);
CONFIGFS_ATTR(, survivability_mode);

static struct configfs_attribute *xe_config_device_attrs[] = {
	&attr_ctx_restore_post_bb,
	&attr_enable_psmi,
	&attr_engines_allowed,
	&attr_survivability_mode,
@@ -387,6 +649,8 @@ static void xe_config_device_release(struct config_item *item)
	struct xe_config_group_device *dev = to_xe_config_group_device(item);

	mutex_destroy(&dev->lock);

	kfree(dev->config.ctx_restore_post_bb[0].cs);
	kfree(dev);
}

@@ -636,14 +900,26 @@ bool xe_configfs_get_psmi_enabled(struct pci_dev *pdev)
/**
 * xe_configfs_get_ctx_restore_post_bb - get configfs ctx_restore_post_bb setting
 * @pdev: pci device
 * @class: hw engine class
 * @cs: pointer to the bb to use - only valid during probe
 *
 * Return: post_ctx_restore setting in configfs
 * Return: Number of dwords used in the post_ctx_restore setting in configfs
 */
u32 xe_configfs_get_ctx_restore_post_bb(struct pci_dev *pdev,
					enum xe_engine_class class,
					const u32 **cs)
{
	struct xe_config_group_device *dev = find_xe_config_group_device(pdev);
	u32 len;

	if (!dev)
		return 0;

	*cs = dev->config.ctx_restore_post_bb[class].cs;
	len = dev->config.ctx_restore_post_bb[class].len;
	config_group_put(&dev->group);

	return len;
}

int __init xe_configfs_init(void)