]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
drm/xe/configfs: Add post context restore bb
authorLucas De Marchi <lucas.demarchi@intel.com>
Tue, 16 Sep 2025 21:15:41 +0000 (14:15 -0700)
committerLucas De Marchi <lucas.demarchi@intel.com>
Thu, 18 Sep 2025 21:20:39 +0000 (14:20 -0700)
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: Raag Jadav <raag.jadav@intel.com>
Link: https://lore.kernel.org/r/20250916-wa-bb-cmds-v5-4-306bddbc15da@intel.com
Signed-off-by: Lucas De Marchi <lucas.demarchi@intel.com>
drivers/gpu/drm/xe/xe_configfs.c

index d86c75af032781810eb67434b534380f2f8f5193..9a30dc958c35c0dbed950aff0d820e46f4e7ee24 100644 (file)
@@ -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"
  *
  * 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
  * ==============
  *
  *     # 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)
 {
-       return 0;
+       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)