1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved. 4 */ 5 #include <linux/debugfs.h> 6 #include <linux/interconnect.h> 7 #include <linux/platform_device.h> 8 #include <linux/slab.h> 9 10 #include "internal.h" 11 12 /* 13 * This can be dangerous, therefore don't provide any real compile time 14 * configuration option for this feature. 15 * People who want to use this will need to modify the source code directly. 16 */ 17 #undef INTERCONNECT_ALLOW_WRITE_DEBUGFS 18 19 #if defined(INTERCONNECT_ALLOW_WRITE_DEBUGFS) && defined(CONFIG_DEBUG_FS) 20 21 static LIST_HEAD(debugfs_paths); 22 static DEFINE_MUTEX(debugfs_lock); 23 24 static struct platform_device *pdev; 25 static struct icc_path *cur_path; 26 27 static char *src_node; 28 static char *dst_node; 29 static u32 avg_bw; 30 static u32 peak_bw; 31 static u32 tag; 32 33 struct debugfs_path { 34 const char *src; 35 const char *dst; 36 struct icc_path *path; 37 struct list_head list; 38 }; 39 40 static ssize_t icc_node_read(struct file *file, char __user *user_buf, 41 size_t count, loff_t *ppos) 42 { 43 char **node = file->private_data; 44 char *copy; 45 size_t len; 46 ssize_t ret; 47 48 scoped_guard(mutex, &debugfs_lock) { 49 copy = kstrdup(*node ?: "", GFP_KERNEL); 50 } 51 if (!copy) 52 return -ENOMEM; 53 54 len = strlen(copy); 55 copy[len++] = '\n'; 56 ret = simple_read_from_buffer(user_buf, count, ppos, copy, len); 57 kfree(copy); 58 return ret; 59 } 60 61 static ssize_t icc_node_write(struct file *file, const char __user *user_buf, 62 size_t count, loff_t *ppos) 63 { 64 char **node = file->private_data; 65 char *old, *new; 66 67 if (*ppos) 68 return -EINVAL; 69 if (count + 1 > PAGE_SIZE) 70 return -E2BIG; 71 72 new = memdup_user_nul(user_buf, count); 73 if (IS_ERR(new)) 74 return PTR_ERR(new); 75 strim(new); 76 77 scoped_guard(mutex, &debugfs_lock) { 78 old = *node; 79 *node = new; 80 } 81 82 kfree(old); 83 return count; 84 } 85 86 static const struct file_operations icc_node_fops = { 87 .open = simple_open, 88 .read = icc_node_read, 89 .write = icc_node_write, 90 .llseek = default_llseek, 91 }; 92 93 static struct icc_path *get_path(const char *src, const char *dst) 94 { 95 struct debugfs_path *path; 96 97 list_for_each_entry(path, &debugfs_paths, list) { 98 if (!strcmp(path->src, src) && !strcmp(path->dst, dst)) 99 return path->path; 100 } 101 102 return NULL; 103 } 104 105 static int icc_get_set(void *data, u64 val) 106 { 107 struct debugfs_path *debugfs_path; 108 char *src, *dst; 109 int ret = 0; 110 111 guard(mutex)(&debugfs_lock); 112 113 /* 114 * If we've already looked up a path, then use the existing one instead 115 * of calling icc_get() again. This allows for updating previous BW 116 * votes when "get" is written to multiple times for multiple paths. 117 */ 118 cur_path = get_path(src_node, dst_node); 119 if (cur_path) 120 goto out; 121 122 src = kstrdup(src_node, GFP_KERNEL); 123 dst = kstrdup(dst_node, GFP_KERNEL); 124 125 if (!src || !dst) { 126 ret = -ENOMEM; 127 goto err_free; 128 } 129 130 cur_path = icc_get(&pdev->dev, src, dst); 131 if (IS_ERR(cur_path)) { 132 ret = PTR_ERR(cur_path); 133 goto err_free; 134 } 135 136 debugfs_path = kzalloc_obj(*debugfs_path); 137 if (!debugfs_path) { 138 ret = -ENOMEM; 139 goto err_put; 140 } 141 142 debugfs_path->path = cur_path; 143 debugfs_path->src = src; 144 debugfs_path->dst = dst; 145 list_add_tail(&debugfs_path->list, &debugfs_paths); 146 147 goto out; 148 149 err_put: 150 icc_put(cur_path); 151 err_free: 152 kfree(src); 153 kfree(dst); 154 out: 155 return ret; 156 } 157 158 DEFINE_DEBUGFS_ATTRIBUTE(icc_get_fops, NULL, icc_get_set, "%llu\n"); 159 160 static int icc_commit_set(void *data, u64 val) 161 { 162 int ret; 163 164 guard(mutex)(&debugfs_lock); 165 166 if (!cur_path) { 167 ret = -EINVAL; 168 goto out; 169 } 170 171 if (IS_ERR(cur_path)) { 172 ret = PTR_ERR(cur_path); 173 goto out; 174 } 175 176 icc_set_tag(cur_path, tag); 177 ret = icc_set_bw(cur_path, avg_bw, peak_bw); 178 out: 179 return ret; 180 } 181 182 DEFINE_DEBUGFS_ATTRIBUTE(icc_commit_fops, NULL, icc_commit_set, "%llu\n"); 183 184 int icc_debugfs_client_init(struct dentry *icc_dir) 185 { 186 struct dentry *client_dir; 187 int ret; 188 189 pdev = platform_device_alloc("icc-debugfs-client", PLATFORM_DEVID_NONE); 190 if (!pdev) 191 return -ENOMEM; 192 193 ret = platform_device_add(pdev); 194 if (ret) { 195 pr_err("%s: failed to add platform device: %d\n", __func__, ret); 196 platform_device_put(pdev); 197 return ret; 198 } 199 200 src_node = kstrdup("", GFP_KERNEL); 201 dst_node = kstrdup("", GFP_KERNEL); 202 if (!src_node || !dst_node) { 203 kfree(dst_node); 204 kfree(src_node); 205 return -ENOMEM; 206 } 207 208 client_dir = debugfs_create_dir("test_client", icc_dir); 209 210 debugfs_create_file("src_node", 0600, client_dir, &src_node, 211 &icc_node_fops); 212 debugfs_create_file("dst_node", 0600, client_dir, &dst_node, 213 &icc_node_fops); 214 debugfs_create_file("get", 0200, client_dir, NULL, &icc_get_fops); 215 debugfs_create_u32("avg_bw", 0600, client_dir, &avg_bw); 216 debugfs_create_u32("peak_bw", 0600, client_dir, &peak_bw); 217 debugfs_create_u32("tag", 0600, client_dir, &tag); 218 debugfs_create_file("commit", 0200, client_dir, NULL, &icc_commit_fops); 219 220 return 0; 221 } 222 223 #else 224 225 int icc_debugfs_client_init(struct dentry *icc_dir) 226 { 227 return 0; 228 } 229 230 #endif 231