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