|  | // SPDX-License-Identifier: MIT | 
|  | /* | 
|  | * Copyright © 2019 Intel Corporation | 
|  | */ | 
|  |  | 
|  | #include <linux/kernel.h> | 
|  |  | 
|  | #include "i915_debugfs_params.h" | 
|  | #include "gt/intel_gt.h" | 
|  | #include "gt/uc/intel_guc.h" | 
|  | #include "i915_drv.h" | 
|  | #include "i915_params.h" | 
|  |  | 
|  | #define MATCH_DEBUGFS_NODE_NAME(_file, _name) \ | 
|  | (strcmp((_file)->f_path.dentry->d_name.name, (_name)) == 0) | 
|  |  | 
|  | #define GET_I915(i915, name, ptr)	\ | 
|  | do {	\ | 
|  | struct i915_params *params;	\ | 
|  | params = container_of(((void *)(ptr)), typeof(*params), name);	\ | 
|  | (i915) = container_of(params, typeof(*(i915)), params);	\ | 
|  | } while (0) | 
|  |  | 
|  | /* int param */ | 
|  | static int i915_param_int_show(struct seq_file *m, void *data) | 
|  | { | 
|  | int *value = m->private; | 
|  |  | 
|  | seq_printf(m, "%d\n", *value); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int i915_param_int_open(struct inode *inode, struct file *file) | 
|  | { | 
|  | return single_open(file, i915_param_int_show, inode->i_private); | 
|  | } | 
|  |  | 
|  | static int notify_guc(struct drm_i915_private *i915) | 
|  | { | 
|  | int ret = 0; | 
|  |  | 
|  | if (intel_uc_uses_guc_submission(&i915->gt.uc)) | 
|  | ret = intel_guc_global_policies_update(&i915->gt.uc.guc); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static ssize_t i915_param_int_write(struct file *file, | 
|  | const char __user *ubuf, size_t len, | 
|  | loff_t *offp) | 
|  | { | 
|  | struct seq_file *m = file->private_data; | 
|  | int *value = m->private; | 
|  | int ret; | 
|  |  | 
|  | ret = kstrtoint_from_user(ubuf, len, 0, value); | 
|  | if (ret) { | 
|  | /* support boolean values too */ | 
|  | bool b; | 
|  |  | 
|  | ret = kstrtobool_from_user(ubuf, len, &b); | 
|  | if (!ret) | 
|  | *value = b; | 
|  | } | 
|  |  | 
|  | return ret ?: len; | 
|  | } | 
|  |  | 
|  | static const struct file_operations i915_param_int_fops = { | 
|  | .owner = THIS_MODULE, | 
|  | .open = i915_param_int_open, | 
|  | .read = seq_read, | 
|  | .write = i915_param_int_write, | 
|  | .llseek = default_llseek, | 
|  | .release = single_release, | 
|  | }; | 
|  |  | 
|  | static const struct file_operations i915_param_int_fops_ro = { | 
|  | .owner = THIS_MODULE, | 
|  | .open = i915_param_int_open, | 
|  | .read = seq_read, | 
|  | .llseek = default_llseek, | 
|  | .release = single_release, | 
|  | }; | 
|  |  | 
|  | /* unsigned int param */ | 
|  | static int i915_param_uint_show(struct seq_file *m, void *data) | 
|  | { | 
|  | unsigned int *value = m->private; | 
|  |  | 
|  | seq_printf(m, "%u\n", *value); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int i915_param_uint_open(struct inode *inode, struct file *file) | 
|  | { | 
|  | return single_open(file, i915_param_uint_show, inode->i_private); | 
|  | } | 
|  |  | 
|  | static ssize_t i915_param_uint_write(struct file *file, | 
|  | const char __user *ubuf, size_t len, | 
|  | loff_t *offp) | 
|  | { | 
|  | struct drm_i915_private *i915; | 
|  | struct seq_file *m = file->private_data; | 
|  | unsigned int *value = m->private; | 
|  | unsigned int old = *value; | 
|  | int ret; | 
|  |  | 
|  | ret = kstrtouint_from_user(ubuf, len, 0, value); | 
|  | if (ret) { | 
|  | /* support boolean values too */ | 
|  | bool b; | 
|  |  | 
|  | ret = kstrtobool_from_user(ubuf, len, &b); | 
|  | if (!ret) | 
|  | *value = b; | 
|  | } | 
|  |  | 
|  | if (!ret && MATCH_DEBUGFS_NODE_NAME(file, "reset")) { | 
|  | GET_I915(i915, reset, value); | 
|  |  | 
|  | ret = notify_guc(i915); | 
|  | if (ret) | 
|  | *value = old; | 
|  | } | 
|  |  | 
|  | return ret ?: len; | 
|  | } | 
|  |  | 
|  | static const struct file_operations i915_param_uint_fops = { | 
|  | .owner = THIS_MODULE, | 
|  | .open = i915_param_uint_open, | 
|  | .read = seq_read, | 
|  | .write = i915_param_uint_write, | 
|  | .llseek = default_llseek, | 
|  | .release = single_release, | 
|  | }; | 
|  |  | 
|  | static const struct file_operations i915_param_uint_fops_ro = { | 
|  | .owner = THIS_MODULE, | 
|  | .open = i915_param_uint_open, | 
|  | .read = seq_read, | 
|  | .llseek = default_llseek, | 
|  | .release = single_release, | 
|  | }; | 
|  |  | 
|  | /* char * param */ | 
|  | static int i915_param_charp_show(struct seq_file *m, void *data) | 
|  | { | 
|  | const char **s = m->private; | 
|  |  | 
|  | seq_printf(m, "%s\n", *s); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int i915_param_charp_open(struct inode *inode, struct file *file) | 
|  | { | 
|  | return single_open(file, i915_param_charp_show, inode->i_private); | 
|  | } | 
|  |  | 
|  | static ssize_t i915_param_charp_write(struct file *file, | 
|  | const char __user *ubuf, size_t len, | 
|  | loff_t *offp) | 
|  | { | 
|  | struct seq_file *m = file->private_data; | 
|  | char **s = m->private; | 
|  | char *new, *old; | 
|  |  | 
|  | old = *s; | 
|  | new = strndup_user(ubuf, PAGE_SIZE); | 
|  | if (IS_ERR(new)) { | 
|  | len = PTR_ERR(new); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | *s = new; | 
|  |  | 
|  | kfree(old); | 
|  | out: | 
|  | return len; | 
|  | } | 
|  |  | 
|  | static const struct file_operations i915_param_charp_fops = { | 
|  | .owner = THIS_MODULE, | 
|  | .open = i915_param_charp_open, | 
|  | .read = seq_read, | 
|  | .write = i915_param_charp_write, | 
|  | .llseek = default_llseek, | 
|  | .release = single_release, | 
|  | }; | 
|  |  | 
|  | static const struct file_operations i915_param_charp_fops_ro = { | 
|  | .owner = THIS_MODULE, | 
|  | .open = i915_param_charp_open, | 
|  | .read = seq_read, | 
|  | .llseek = default_llseek, | 
|  | .release = single_release, | 
|  | }; | 
|  |  | 
|  | #define RO(mode) (((mode) & 0222) == 0) | 
|  |  | 
|  | static struct dentry * | 
|  | i915_debugfs_create_int(const char *name, umode_t mode, | 
|  | struct dentry *parent, int *value) | 
|  | { | 
|  | return debugfs_create_file_unsafe(name, mode, parent, value, | 
|  | RO(mode) ? &i915_param_int_fops_ro : | 
|  | &i915_param_int_fops); | 
|  | } | 
|  |  | 
|  | static struct dentry * | 
|  | i915_debugfs_create_uint(const char *name, umode_t mode, | 
|  | struct dentry *parent, unsigned int *value) | 
|  | { | 
|  | return debugfs_create_file_unsafe(name, mode, parent, value, | 
|  | RO(mode) ? &i915_param_uint_fops_ro : | 
|  | &i915_param_uint_fops); | 
|  | } | 
|  |  | 
|  | static struct dentry * | 
|  | i915_debugfs_create_charp(const char *name, umode_t mode, | 
|  | struct dentry *parent, char **value) | 
|  | { | 
|  | return debugfs_create_file(name, mode, parent, value, | 
|  | RO(mode) ? &i915_param_charp_fops_ro : | 
|  | &i915_param_charp_fops); | 
|  | } | 
|  |  | 
|  | static __always_inline void | 
|  | _i915_param_create_file(struct dentry *parent, const char *name, | 
|  | const char *type, int mode, void *value) | 
|  | { | 
|  | if (!mode) | 
|  | return; | 
|  |  | 
|  | if (!__builtin_strcmp(type, "bool")) | 
|  | debugfs_create_bool(name, mode, parent, value); | 
|  | else if (!__builtin_strcmp(type, "int")) | 
|  | i915_debugfs_create_int(name, mode, parent, value); | 
|  | else if (!__builtin_strcmp(type, "unsigned int")) | 
|  | i915_debugfs_create_uint(name, mode, parent, value); | 
|  | else if (!__builtin_strcmp(type, "unsigned long")) | 
|  | debugfs_create_ulong(name, mode, parent, value); | 
|  | else if (!__builtin_strcmp(type, "char *")) | 
|  | i915_debugfs_create_charp(name, mode, parent, value); | 
|  | else | 
|  | WARN(1, "no debugfs fops defined for param type %s (i915.%s)\n", | 
|  | type, name); | 
|  | } | 
|  |  | 
|  | /* add a subdirectory with files for each i915 param */ | 
|  | struct dentry *i915_debugfs_params(struct drm_i915_private *i915) | 
|  | { | 
|  | struct drm_minor *minor = i915->drm.primary; | 
|  | struct i915_params *params = &i915->params; | 
|  | struct dentry *dir; | 
|  |  | 
|  | dir = debugfs_create_dir("i915_params", minor->debugfs_root); | 
|  | if (IS_ERR(dir)) | 
|  | return dir; | 
|  |  | 
|  | /* | 
|  | * Note: We could create files for params needing special handling | 
|  | * here. Set mode in params to 0 to skip the generic create file, or | 
|  | * just let the generic create file fail silently with -EEXIST. | 
|  | */ | 
|  |  | 
|  | #define REGISTER(T, x, unused, mode, ...) _i915_param_create_file(dir, #x, #T, mode, ¶ms->x); | 
|  | I915_PARAMS_FOR_EACH(REGISTER); | 
|  | #undef REGISTER | 
|  |  | 
|  | return dir; | 
|  | } |