[RFC 05/17] drm: Connect priority updates to drm core

From: Tvrtko Ursulin
Date: Wed Oct 19 2022 - 13:34:30 EST


From: Tvrtko Ursulin <tvrtko.ursulin@xxxxxxxxx>

On priority updates, drm cgroup controller is made walk all the processes
belonging to the group being updated, and notifies the drm core of them
via a new helper.

DRM core itself stores the current effective drm cgroup priority in
struct drm_file, while individual drivers can also register an optional
hook to be called at the same time, via struct drm_cgroup_ops which can be
provided as part of struct drm_driver used at driver registration time.

DRM cgroup controller on the other hand exports a new helper which the drm
core uses at client registration time in order to query to current drm
cgroup effective priority.

This establishes a two way communication channel between the drm cgroup
controller and the drm core and hence drm core module now has to be built
into the kernel.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@xxxxxxxxx>
---
Documentation/admin-guide/cgroup-v2.rst | 2 +
drivers/gpu/drm/Kconfig | 1 +
drivers/gpu/drm/drm_cgroup.c | 56 +++++++++++++++++
drivers/gpu/drm/drm_file.c | 4 ++
include/drm/drm_clients.h | 3 +
include/drm/drm_drv.h | 47 ++++++++++++++
include/drm/drm_file.h | 10 +++
include/linux/cgroup_drm.h | 4 ++
init/Kconfig | 1 +
kernel/cgroup/drm.c | 82 ++++++++++++++++++++++++-
10 files changed, 209 insertions(+), 1 deletion(-)

diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
index 0a6d97c83ea4..1f3cca4e2572 100644
--- a/Documentation/admin-guide/cgroup-v2.rst
+++ b/Documentation/admin-guide/cgroup-v2.rst
@@ -2444,6 +2444,8 @@ DRM static priority interface files
One of:
1) And integer representing the minimum number of discrete priority
levels for the whole group.
+ Optionally followed by an asterisk ('*') indicating some DRM clients
+ in the group support more than the minimum number.
2) '0'- indicating one or more DRM clients in the group has no support
for static priority control.
3) 'n/a' - when there are no DRM clients in the configured group.
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 34f5a092c99e..8f3c169ced10 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -7,6 +7,7 @@
#
menuconfig DRM
tristate "Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)"
+ default y if CGROUP_DRM=y
depends on (AGP || AGP=n) && !EMULATED_CMPXCHG && HAS_DMA
select DRM_NOMODESET
select DRM_PANEL_ORIENTATION_QUIRKS
diff --git a/drivers/gpu/drm/drm_cgroup.c b/drivers/gpu/drm/drm_cgroup.c
index 9e9caeb0aa87..0fbb88f08cef 100644
--- a/drivers/gpu/drm/drm_cgroup.c
+++ b/drivers/gpu/drm/drm_cgroup.c
@@ -65,3 +65,59 @@ int drm_clients_open(struct drm_file *file_priv)

return 0;
}
+
+unsigned int drm_pid_priority_levels(struct pid *pid, bool *non_uniform)
+{
+ unsigned int min_levels = UINT_MAX;
+ struct drm_pid_clients *clients;
+
+ *non_uniform = false;
+
+ rcu_read_lock();
+ clients = xa_load(&drm_pid_clients, (unsigned long)pid);
+ if (clients) {
+ struct drm_file *fpriv;
+
+ list_for_each_entry_rcu(fpriv, &clients->file_list, clink) {
+ const struct drm_cgroup_ops *cg_ops =
+ fpriv->minor->dev->driver->cg_ops;
+ unsigned int l;
+
+ if (cg_ops && cg_ops->priority_levels)
+ l = cg_ops->priority_levels(fpriv);
+ else
+ l = 0;
+
+ if (min_levels != UINT_MAX && l != min_levels)
+ *non_uniform = true;
+ if (l < min_levels)
+ min_levels = l;
+ }
+ }
+ rcu_read_unlock();
+
+ return min_levels;
+}
+EXPORT_SYMBOL_GPL(drm_pid_priority_levels);
+
+void drm_pid_update_priority(struct pid *pid, int priority)
+{
+ struct drm_pid_clients *clients;
+
+ rcu_read_lock();
+ clients = xa_load(&drm_pid_clients, (unsigned long)pid);
+ if (clients) {
+ struct drm_file *fpriv;
+
+ list_for_each_entry_rcu(fpriv, &clients->file_list, clink) {
+ const struct drm_cgroup_ops *cg_ops =
+ fpriv->minor->dev->driver->cg_ops;
+
+ fpriv->drm_cgroup_priority = priority;
+ if (cg_ops && cg_ops->update_priority)
+ cg_ops->update_priority(fpriv, priority);
+ }
+ }
+ rcu_read_unlock();
+}
+EXPORT_SYMBOL_GPL(drm_pid_update_priority);
diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index ce58d5c513db..38eb6003e74d 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -32,6 +32,7 @@
*/

#include <linux/anon_inodes.h>
+#include <linux/cgroup_drm.h>
#include <linux/dma-fence.h>
#include <linux/file.h>
#include <linux/module.h>
@@ -359,6 +360,9 @@ static int drm_open_helper(struct file *filp, struct drm_minor *minor)
filp->f_mode |= FMODE_UNSIGNED_OFFSET;
priv->filp = filp;

+ priv->drm_cgroup_priority =
+ drmcgroup_lookup_effective_priority(current);
+
mutex_lock(&dev->filelist_mutex);
ret = drm_clients_open(priv);
if (ret)
diff --git a/include/drm/drm_clients.h b/include/drm/drm_clients.h
index 4ae553a03d1e..10d21138f7af 100644
--- a/include/drm/drm_clients.h
+++ b/include/drm/drm_clients.h
@@ -28,4 +28,7 @@ static inline int drm_clients_open(struct drm_file *file_priv)
}
#endif

+unsigned int drm_pid_priority_levels(struct pid *pid, bool *non_uniform);
+void drm_pid_update_priority(struct pid *pid, int priority);
+
#endif
diff --git a/include/drm/drm_drv.h b/include/drm/drm_drv.h
index f6159acb8856..2371d73e12cf 100644
--- a/include/drm/drm_drv.h
+++ b/include/drm/drm_drv.h
@@ -148,6 +148,43 @@ enum drm_driver_feature {
DRIVER_KMS_LEGACY_CONTEXT = BIT(31),
};

+/**
+ * struct drm_cgroup_ops
+ *
+ * This structure contains a number of callbacks that drivers can provide if
+ * they are able to support one or more of the functionalities implemented by
+ * the DRM cgroup controller.
+ */
+struct drm_cgroup_ops {
+ /**
+ * @priority_levels:
+ *
+ * Returns the discrete number of priority levels supported by the DRM
+ * driver owning this client.
+ *
+ * The value is used by the DRM core when informing the DRM cgroup
+ * controller on the scheduling priority capability of a group of
+ * clients.
+ *
+ * If the callback is not implemented no support for scheduling priority
+ * is assumed and reported as such.
+ */
+ unsigned int (*priority_levels) (struct drm_file *);
+
+ /**
+ * @update_priority:
+ *
+ * Optional callback used by the DRM core for informing individual
+ * drivers of DRM cgroup priority changes.
+ *
+ * If not implemented drivers are still able to access the most recent
+ * priority via the drm_file->drm_cgroup_priority field. Therefore the
+ * main purpose of the callback is for drivers which are able to adjust
+ * priorities of already running workloads.
+ */
+ void (*update_priority) (struct drm_file *, int priority);
+};
+
/**
* struct drm_driver - DRM driver structure
*
@@ -459,6 +496,16 @@ struct drm_driver {
*/
const struct file_operations *fops;

+#ifdef CONFIG_CGROUP_DRM
+ /**
+ * @cg_ops:
+ *
+ * Optional pointer to driver callbacks facilitating integration with
+ * the DRM cgroup controller.
+ */
+ const struct drm_cgroup_ops *cg_ops;
+#endif
+
#ifdef CONFIG_DRM_LEGACY
/* Everything below here is for legacy driver, never use! */
/* private: */
diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
index 0965eb111f24..a4360e28e2db 100644
--- a/include/drm/drm_file.h
+++ b/include/drm/drm_file.h
@@ -223,6 +223,16 @@ struct drm_file {
*/
bool is_master;

+#ifdef CONFIG_CGROUP_DRM
+ /**
+ * @drm_cgroup_priority:
+ *
+ * Last known DRM cgroup priority is stored here by the DRM code when
+ * informed of changes by the cgroup controller.
+ */
+ int drm_cgroup_priority;
+#endif
+
/**
* @master:
*
diff --git a/include/linux/cgroup_drm.h b/include/linux/cgroup_drm.h
index a59792ccb550..66063b4708e8 100644
--- a/include/linux/cgroup_drm.h
+++ b/include/linux/cgroup_drm.h
@@ -6,8 +6,12 @@
#ifndef _CGROUP_DRM_H
#define _CGROUP_DRM_H

+struct task_struct;
+
#define DRM_CGROUP_PRIORITY_MIN (-10000)
#define DRM_CGROUP_PRIORITY_DEF (0)
#define DRM_CGROUP_PRIORITY_MAX (10000)

+int drmcgroup_lookup_effective_priority(struct task_struct *task);
+
#endif /* _CGROUP_DRM_H */
diff --git a/init/Kconfig b/init/Kconfig
index 6dd7faca7749..cfc7a1f2634c 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1089,6 +1089,7 @@ config CGROUP_RDMA

config CGROUP_DRM
bool "DRM controller"
+ select DRM
help
Provides the DRM subsystem controller.

diff --git a/kernel/cgroup/drm.c b/kernel/cgroup/drm.c
index 2350e1f8a48a..01954c3a2087 100644
--- a/kernel/cgroup/drm.c
+++ b/kernel/cgroup/drm.c
@@ -10,6 +10,8 @@
#include <linux/mutex.h>
#include <linux/sched.h>

+#include <drm/drm_clients.h>
+
struct drm_cgroup_state {
struct cgroup_subsys_state css;

@@ -25,9 +27,52 @@ css_to_drmcs(struct cgroup_subsys_state *css)
return container_of(css, struct drm_cgroup_state, css);
}

+static inline struct drm_cgroup_state *get_task_drmcs(struct task_struct *task)
+{
+ return css_to_drmcs(task_get_css(task, drm_cgrp_id));
+}
+
+int drmcgroup_lookup_effective_priority(struct task_struct *task)
+{
+ struct drm_cgroup_state *drmcs = get_task_drmcs(task);
+ int prio = drmcs->effective_priority;
+
+ css_put(&drmcs->css);
+
+ return prio;
+}
+EXPORT_SYMBOL_GPL(drmcgroup_lookup_effective_priority);
+
static int drmcs_show_priority_levels(struct seq_file *sf, void *v)
{
- seq_printf(sf, "%u\n", 0);
+ struct cgroup *cgrp = seq_css(sf)->cgroup;
+ unsigned int min_levels = UINT_MAX;
+ bool non_uniform = false;
+ struct task_struct *task;
+ struct css_task_iter it;
+
+ css_task_iter_start(&cgrp->self,
+ CSS_TASK_ITER_PROCS | CSS_TASK_ITER_THREADED, &it);
+ while ((task = css_task_iter_next(&it))) {
+ unsigned int l;
+ bool nu;
+
+ /* Ignore kernel threads here. */
+ if (task->flags & PF_KTHREAD)
+ continue;
+
+ l = drm_pid_priority_levels(task_pid(task), &nu);
+ if (nu || (min_levels != UINT_MAX && l != min_levels))
+ non_uniform = true;
+ if (l < min_levels)
+ min_levels = l;
+ }
+ css_task_iter_end(&it);
+
+ if (min_levels != UINT_MAX)
+ seq_printf(sf, "%u%s\n", min_levels, non_uniform ? "*" : "");
+ else
+ seq_puts(sf, "n/a\n");

return 0;
}
@@ -49,6 +94,24 @@ drmcs_read_priority(struct cgroup_subsys_state *css, struct cftype *cft)
return drmcs->priority;
}

+static void update_drm_priority(struct drm_cgroup_state *drmcs)
+{
+ struct cgroup *cgrp = drmcs->css.cgroup;
+ struct task_struct *task;
+ struct css_task_iter it;
+
+ css_task_iter_start(&cgrp->self,
+ CSS_TASK_ITER_PROCS | CSS_TASK_ITER_THREADED, &it);
+ while ((task = css_task_iter_next(&it))) {
+ /* Ignore kernel threads here. */
+ if (task->flags & PF_KTHREAD)
+ continue;
+ drm_pid_update_priority(task_pid(task),
+ drmcs->effective_priority);
+ }
+ css_task_iter_end(&it);
+}
+
static void update_priority(struct drm_cgroup_state *drmcs, int priority)
{
struct cgroup_subsys_state *node;
@@ -74,6 +137,8 @@ static void update_priority(struct drm_cgroup_state *drmcs, int priority)
clamp(pprio + dnode->priority,
DRM_CGROUP_PRIORITY_MIN,
DRM_CGROUP_PRIORITY_MAX);
+
+ update_drm_priority(dnode);
}
rcu_read_unlock();
}
@@ -114,6 +179,20 @@ static void drmcs_free(struct cgroup_subsys_state *css)
kfree(css_to_drmcs(css));
}

+static void drmcs_attach(struct cgroup_taskset *tset)
+{
+ struct cgroup_subsys_state *css;
+ struct task_struct *task;
+
+ /*
+ * As processes are assigned to groups we need to notify them of the
+ * current priority.
+ */
+ cgroup_taskset_for_each(task, css, tset)
+ drm_pid_update_priority(task_pid(task),
+ css_to_drmcs(css)->effective_priority);
+}
+
static struct drm_cgroup_state root_drmcs = {
.priority = DRM_CGROUP_PRIORITY_DEF,
.effective_priority = DRM_CGROUP_PRIORITY_DEF,
@@ -158,6 +237,7 @@ struct cgroup_subsys drm_cgrp_subsys = {
.css_alloc = drmcs_alloc,
.css_free = drmcs_free,
.css_online = drmcs_online,
+ .attach = drmcs_attach,
.early_init = false,
.legacy_cftypes = files,
.dfl_cftypes = files,
--
2.34.1