| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  *  This code exports profiling data as debugfs files to userspace. | 
 | 3 |  * | 
 | 4 |  *    Copyright IBM Corp. 2009 | 
 | 5 |  *    Author(s): Peter Oberparleiter <oberpar@linux.vnet.ibm.com> | 
 | 6 |  * | 
 | 7 |  *    Uses gcc-internal data definitions. | 
 | 8 |  *    Based on the gcov-kernel patch by: | 
 | 9 |  *		 Hubertus Franke <frankeh@us.ibm.com> | 
 | 10 |  *		 Nigel Hinds <nhinds@us.ibm.com> | 
 | 11 |  *		 Rajan Ravindran <rajancr@us.ibm.com> | 
 | 12 |  *		 Peter Oberparleiter <oberpar@linux.vnet.ibm.com> | 
 | 13 |  *		 Paul Larson | 
 | 14 |  *		 Yi CDL Yang | 
 | 15 |  */ | 
 | 16 |  | 
 | 17 | #define pr_fmt(fmt)	"gcov: " fmt | 
 | 18 |  | 
 | 19 | #include <linux/init.h> | 
 | 20 | #include <linux/module.h> | 
 | 21 | #include <linux/debugfs.h> | 
 | 22 | #include <linux/fs.h> | 
 | 23 | #include <linux/list.h> | 
 | 24 | #include <linux/string.h> | 
 | 25 | #include <linux/slab.h> | 
 | 26 | #include <linux/mutex.h> | 
 | 27 | #include <linux/seq_file.h> | 
 | 28 | #include "gcov.h" | 
 | 29 |  | 
 | 30 | /** | 
 | 31 |  * struct gcov_node - represents a debugfs entry | 
 | 32 |  * @list: list head for child node list | 
 | 33 |  * @children: child nodes | 
 | 34 |  * @all: list head for list of all nodes | 
 | 35 |  * @parent: parent node | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 36 |  * @loaded_info: array of pointers to profiling data sets for loaded object | 
 | 37 |  *   files. | 
 | 38 |  * @num_loaded: number of profiling data sets for loaded object files. | 
 | 39 |  * @unloaded_info: accumulated copy of profiling data sets for unloaded | 
 | 40 |  *   object files. Used only when gcov_persist=1. | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 41 |  * @dentry: main debugfs entry, either a directory or data file | 
 | 42 |  * @links: associated symbolic links | 
 | 43 |  * @name: data file basename | 
 | 44 |  * | 
 | 45 |  * struct gcov_node represents an entity within the gcov/ subdirectory | 
 | 46 |  * of debugfs. There are directory and data file nodes. The latter represent | 
 | 47 |  * the actual synthesized data file plus any associated symbolic links which | 
 | 48 |  * are needed by the gcov tool to work correctly. | 
 | 49 |  */ | 
 | 50 | struct gcov_node { | 
 | 51 | 	struct list_head list; | 
 | 52 | 	struct list_head children; | 
 | 53 | 	struct list_head all; | 
 | 54 | 	struct gcov_node *parent; | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 55 | 	struct gcov_info **loaded_info; | 
 | 56 | 	struct gcov_info *unloaded_info; | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 57 | 	struct dentry *dentry; | 
 | 58 | 	struct dentry **links; | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 59 | 	int num_loaded; | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 60 | 	char name[0]; | 
 | 61 | }; | 
 | 62 |  | 
 | 63 | static const char objtree[] = OBJTREE; | 
 | 64 | static const char srctree[] = SRCTREE; | 
 | 65 | static struct gcov_node root_node; | 
 | 66 | static struct dentry *reset_dentry; | 
 | 67 | static LIST_HEAD(all_head); | 
 | 68 | static DEFINE_MUTEX(node_lock); | 
 | 69 |  | 
 | 70 | /* If non-zero, keep copies of profiling data for unloaded modules. */ | 
 | 71 | static int gcov_persist = 1; | 
 | 72 |  | 
 | 73 | static int __init gcov_persist_setup(char *str) | 
 | 74 | { | 
 | 75 | 	unsigned long val; | 
 | 76 |  | 
 | 77 | 	if (strict_strtoul(str, 0, &val)) { | 
 | 78 | 		pr_warning("invalid gcov_persist parameter '%s'\n", str); | 
 | 79 | 		return 0; | 
 | 80 | 	} | 
 | 81 | 	gcov_persist = val; | 
 | 82 | 	pr_info("setting gcov_persist to %d\n", gcov_persist); | 
 | 83 |  | 
 | 84 | 	return 1; | 
 | 85 | } | 
 | 86 | __setup("gcov_persist=", gcov_persist_setup); | 
 | 87 |  | 
 | 88 | /* | 
 | 89 |  * seq_file.start() implementation for gcov data files. Note that the | 
 | 90 |  * gcov_iterator interface is designed to be more restrictive than seq_file | 
 | 91 |  * (no start from arbitrary position, etc.), to simplify the iterator | 
 | 92 |  * implementation. | 
 | 93 |  */ | 
 | 94 | static void *gcov_seq_start(struct seq_file *seq, loff_t *pos) | 
 | 95 | { | 
 | 96 | 	loff_t i; | 
 | 97 |  | 
 | 98 | 	gcov_iter_start(seq->private); | 
 | 99 | 	for (i = 0; i < *pos; i++) { | 
 | 100 | 		if (gcov_iter_next(seq->private)) | 
 | 101 | 			return NULL; | 
 | 102 | 	} | 
 | 103 | 	return seq->private; | 
 | 104 | } | 
 | 105 |  | 
 | 106 | /* seq_file.next() implementation for gcov data files. */ | 
 | 107 | static void *gcov_seq_next(struct seq_file *seq, void *data, loff_t *pos) | 
 | 108 | { | 
 | 109 | 	struct gcov_iterator *iter = data; | 
 | 110 |  | 
 | 111 | 	if (gcov_iter_next(iter)) | 
 | 112 | 		return NULL; | 
 | 113 | 	(*pos)++; | 
 | 114 |  | 
 | 115 | 	return iter; | 
 | 116 | } | 
 | 117 |  | 
 | 118 | /* seq_file.show() implementation for gcov data files. */ | 
 | 119 | static int gcov_seq_show(struct seq_file *seq, void *data) | 
 | 120 | { | 
 | 121 | 	struct gcov_iterator *iter = data; | 
 | 122 |  | 
 | 123 | 	if (gcov_iter_write(iter, seq)) | 
 | 124 | 		return -EINVAL; | 
 | 125 | 	return 0; | 
 | 126 | } | 
 | 127 |  | 
 | 128 | static void gcov_seq_stop(struct seq_file *seq, void *data) | 
 | 129 | { | 
 | 130 | 	/* Unused. */ | 
 | 131 | } | 
 | 132 |  | 
 | 133 | static const struct seq_operations gcov_seq_ops = { | 
 | 134 | 	.start	= gcov_seq_start, | 
 | 135 | 	.next	= gcov_seq_next, | 
 | 136 | 	.show	= gcov_seq_show, | 
 | 137 | 	.stop	= gcov_seq_stop, | 
 | 138 | }; | 
 | 139 |  | 
 | 140 | /* | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 141 |  * Return a profiling data set associated with the given node. This is | 
 | 142 |  * either a data set for a loaded object file or a data set copy in case | 
 | 143 |  * all associated object files have been unloaded. | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 144 |  */ | 
 | 145 | static struct gcov_info *get_node_info(struct gcov_node *node) | 
 | 146 | { | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 147 | 	if (node->num_loaded > 0) | 
 | 148 | 		return node->loaded_info[0]; | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 149 |  | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 150 | 	return node->unloaded_info; | 
 | 151 | } | 
 | 152 |  | 
 | 153 | /* | 
 | 154 |  * Return a newly allocated profiling data set which contains the sum of | 
 | 155 |  * all profiling data associated with the given node. | 
 | 156 |  */ | 
 | 157 | static struct gcov_info *get_accumulated_info(struct gcov_node *node) | 
 | 158 | { | 
 | 159 | 	struct gcov_info *info; | 
 | 160 | 	int i = 0; | 
 | 161 |  | 
 | 162 | 	if (node->unloaded_info) | 
 | 163 | 		info = gcov_info_dup(node->unloaded_info); | 
 | 164 | 	else | 
 | 165 | 		info = gcov_info_dup(node->loaded_info[i++]); | 
 | 166 | 	if (!info) | 
 | 167 | 		return NULL; | 
 | 168 | 	for (; i < node->num_loaded; i++) | 
 | 169 | 		gcov_info_add(info, node->loaded_info[i]); | 
 | 170 |  | 
 | 171 | 	return info; | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 172 | } | 
 | 173 |  | 
 | 174 | /* | 
 | 175 |  * open() implementation for gcov data files. Create a copy of the profiling | 
 | 176 |  * data set and initialize the iterator and seq_file interface. | 
 | 177 |  */ | 
 | 178 | static int gcov_seq_open(struct inode *inode, struct file *file) | 
 | 179 | { | 
 | 180 | 	struct gcov_node *node = inode->i_private; | 
 | 181 | 	struct gcov_iterator *iter; | 
 | 182 | 	struct seq_file *seq; | 
 | 183 | 	struct gcov_info *info; | 
 | 184 | 	int rc = -ENOMEM; | 
 | 185 |  | 
 | 186 | 	mutex_lock(&node_lock); | 
 | 187 | 	/* | 
 | 188 | 	 * Read from a profiling data copy to minimize reference tracking | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 189 | 	 * complexity and concurrent access and to keep accumulating multiple | 
 | 190 | 	 * profiling data sets associated with one node simple. | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 191 | 	 */ | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 192 | 	info = get_accumulated_info(node); | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 193 | 	if (!info) | 
 | 194 | 		goto out_unlock; | 
 | 195 | 	iter = gcov_iter_new(info); | 
 | 196 | 	if (!iter) | 
 | 197 | 		goto err_free_info; | 
 | 198 | 	rc = seq_open(file, &gcov_seq_ops); | 
 | 199 | 	if (rc) | 
 | 200 | 		goto err_free_iter_info; | 
 | 201 | 	seq = file->private_data; | 
 | 202 | 	seq->private = iter; | 
 | 203 | out_unlock: | 
 | 204 | 	mutex_unlock(&node_lock); | 
 | 205 | 	return rc; | 
 | 206 |  | 
 | 207 | err_free_iter_info: | 
 | 208 | 	gcov_iter_free(iter); | 
 | 209 | err_free_info: | 
 | 210 | 	gcov_info_free(info); | 
 | 211 | 	goto out_unlock; | 
 | 212 | } | 
 | 213 |  | 
 | 214 | /* | 
 | 215 |  * release() implementation for gcov data files. Release resources allocated | 
 | 216 |  * by open(). | 
 | 217 |  */ | 
 | 218 | static int gcov_seq_release(struct inode *inode, struct file *file) | 
 | 219 | { | 
 | 220 | 	struct gcov_iterator *iter; | 
 | 221 | 	struct gcov_info *info; | 
 | 222 | 	struct seq_file *seq; | 
 | 223 |  | 
 | 224 | 	seq = file->private_data; | 
 | 225 | 	iter = seq->private; | 
 | 226 | 	info = gcov_iter_get_info(iter); | 
 | 227 | 	gcov_iter_free(iter); | 
 | 228 | 	gcov_info_free(info); | 
 | 229 | 	seq_release(inode, file); | 
 | 230 |  | 
 | 231 | 	return 0; | 
 | 232 | } | 
 | 233 |  | 
 | 234 | /* | 
 | 235 |  * Find a node by the associated data file name. Needs to be called with | 
 | 236 |  * node_lock held. | 
 | 237 |  */ | 
 | 238 | static struct gcov_node *get_node_by_name(const char *name) | 
 | 239 | { | 
 | 240 | 	struct gcov_node *node; | 
 | 241 | 	struct gcov_info *info; | 
 | 242 |  | 
 | 243 | 	list_for_each_entry(node, &all_head, all) { | 
 | 244 | 		info = get_node_info(node); | 
 | 245 | 		if (info && (strcmp(info->filename, name) == 0)) | 
 | 246 | 			return node; | 
 | 247 | 	} | 
 | 248 |  | 
 | 249 | 	return NULL; | 
 | 250 | } | 
 | 251 |  | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 252 | /* | 
 | 253 |  * Reset all profiling data associated with the specified node. | 
 | 254 |  */ | 
 | 255 | static void reset_node(struct gcov_node *node) | 
 | 256 | { | 
 | 257 | 	int i; | 
 | 258 |  | 
 | 259 | 	if (node->unloaded_info) | 
 | 260 | 		gcov_info_reset(node->unloaded_info); | 
 | 261 | 	for (i = 0; i < node->num_loaded; i++) | 
 | 262 | 		gcov_info_reset(node->loaded_info[i]); | 
 | 263 | } | 
 | 264 |  | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 265 | static void remove_node(struct gcov_node *node); | 
 | 266 |  | 
 | 267 | /* | 
 | 268 |  * write() implementation for gcov data files. Reset profiling data for the | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 269 |  * corresponding file. If all associated object files have been unloaded, | 
 | 270 |  * remove the debug fs node as well. | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 271 |  */ | 
 | 272 | static ssize_t gcov_seq_write(struct file *file, const char __user *addr, | 
 | 273 | 			      size_t len, loff_t *pos) | 
 | 274 | { | 
 | 275 | 	struct seq_file *seq; | 
 | 276 | 	struct gcov_info *info; | 
 | 277 | 	struct gcov_node *node; | 
 | 278 |  | 
 | 279 | 	seq = file->private_data; | 
 | 280 | 	info = gcov_iter_get_info(seq->private); | 
 | 281 | 	mutex_lock(&node_lock); | 
 | 282 | 	node = get_node_by_name(info->filename); | 
 | 283 | 	if (node) { | 
 | 284 | 		/* Reset counts or remove node for unloaded modules. */ | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 285 | 		if (node->num_loaded == 0) | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 286 | 			remove_node(node); | 
 | 287 | 		else | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 288 | 			reset_node(node); | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 289 | 	} | 
 | 290 | 	/* Reset counts for open file. */ | 
 | 291 | 	gcov_info_reset(info); | 
 | 292 | 	mutex_unlock(&node_lock); | 
 | 293 |  | 
 | 294 | 	return len; | 
 | 295 | } | 
 | 296 |  | 
 | 297 | /* | 
 | 298 |  * Given a string <path> representing a file path of format: | 
 | 299 |  *   path/to/file.gcda | 
 | 300 |  * construct and return a new string: | 
 | 301 |  *   <dir/>path/to/file.<ext> | 
 | 302 |  */ | 
 | 303 | static char *link_target(const char *dir, const char *path, const char *ext) | 
 | 304 | { | 
 | 305 | 	char *target; | 
 | 306 | 	char *old_ext; | 
 | 307 | 	char *copy; | 
 | 308 |  | 
 | 309 | 	copy = kstrdup(path, GFP_KERNEL); | 
 | 310 | 	if (!copy) | 
 | 311 | 		return NULL; | 
 | 312 | 	old_ext = strrchr(copy, '.'); | 
 | 313 | 	if (old_ext) | 
 | 314 | 		*old_ext = '\0'; | 
 | 315 | 	if (dir) | 
 | 316 | 		target = kasprintf(GFP_KERNEL, "%s/%s.%s", dir, copy, ext); | 
 | 317 | 	else | 
 | 318 | 		target = kasprintf(GFP_KERNEL, "%s.%s", copy, ext); | 
 | 319 | 	kfree(copy); | 
 | 320 |  | 
 | 321 | 	return target; | 
 | 322 | } | 
 | 323 |  | 
 | 324 | /* | 
 | 325 |  * Construct a string representing the symbolic link target for the given | 
 | 326 |  * gcov data file name and link type. Depending on the link type and the | 
 | 327 |  * location of the data file, the link target can either point to a | 
 | 328 |  * subdirectory of srctree, objtree or in an external location. | 
 | 329 |  */ | 
 | 330 | static char *get_link_target(const char *filename, const struct gcov_link *ext) | 
 | 331 | { | 
 | 332 | 	const char *rel; | 
 | 333 | 	char *result; | 
 | 334 |  | 
 | 335 | 	if (strncmp(filename, objtree, strlen(objtree)) == 0) { | 
 | 336 | 		rel = filename + strlen(objtree) + 1; | 
 | 337 | 		if (ext->dir == SRC_TREE) | 
 | 338 | 			result = link_target(srctree, rel, ext->ext); | 
 | 339 | 		else | 
 | 340 | 			result = link_target(objtree, rel, ext->ext); | 
 | 341 | 	} else { | 
 | 342 | 		/* External compilation. */ | 
 | 343 | 		result = link_target(NULL, filename, ext->ext); | 
 | 344 | 	} | 
 | 345 |  | 
 | 346 | 	return result; | 
 | 347 | } | 
 | 348 |  | 
 | 349 | #define SKEW_PREFIX	".tmp_" | 
 | 350 |  | 
 | 351 | /* | 
 | 352 |  * For a filename .tmp_filename.ext return filename.ext. Needed to compensate | 
 | 353 |  * for filename skewing caused by the mod-versioning mechanism. | 
 | 354 |  */ | 
 | 355 | static const char *deskew(const char *basename) | 
 | 356 | { | 
 | 357 | 	if (strncmp(basename, SKEW_PREFIX, sizeof(SKEW_PREFIX) - 1) == 0) | 
 | 358 | 		return basename + sizeof(SKEW_PREFIX) - 1; | 
 | 359 | 	return basename; | 
 | 360 | } | 
 | 361 |  | 
 | 362 | /* | 
 | 363 |  * Create links to additional files (usually .c and .gcno files) which the | 
 | 364 |  * gcov tool expects to find in the same directory as the gcov data file. | 
 | 365 |  */ | 
 | 366 | static void add_links(struct gcov_node *node, struct dentry *parent) | 
 | 367 | { | 
 | 368 | 	char *basename; | 
 | 369 | 	char *target; | 
 | 370 | 	int num; | 
 | 371 | 	int i; | 
 | 372 |  | 
 | 373 | 	for (num = 0; gcov_link[num].ext; num++) | 
 | 374 | 		/* Nothing. */; | 
 | 375 | 	node->links = kcalloc(num, sizeof(struct dentry *), GFP_KERNEL); | 
 | 376 | 	if (!node->links) | 
 | 377 | 		return; | 
 | 378 | 	for (i = 0; i < num; i++) { | 
 | 379 | 		target = get_link_target(get_node_info(node)->filename, | 
 | 380 | 					 &gcov_link[i]); | 
 | 381 | 		if (!target) | 
 | 382 | 			goto out_err; | 
 | 383 | 		basename = strrchr(target, '/'); | 
 | 384 | 		if (!basename) | 
 | 385 | 			goto out_err; | 
 | 386 | 		basename++; | 
 | 387 | 		node->links[i] = debugfs_create_symlink(deskew(basename), | 
 | 388 | 							parent,	target); | 
 | 389 | 		if (!node->links[i]) | 
 | 390 | 			goto out_err; | 
 | 391 | 		kfree(target); | 
 | 392 | 	} | 
 | 393 |  | 
 | 394 | 	return; | 
 | 395 | out_err: | 
 | 396 | 	kfree(target); | 
 | 397 | 	while (i-- > 0) | 
 | 398 | 		debugfs_remove(node->links[i]); | 
 | 399 | 	kfree(node->links); | 
 | 400 | 	node->links = NULL; | 
 | 401 | } | 
 | 402 |  | 
 | 403 | static const struct file_operations gcov_data_fops = { | 
 | 404 | 	.open		= gcov_seq_open, | 
 | 405 | 	.release	= gcov_seq_release, | 
 | 406 | 	.read		= seq_read, | 
 | 407 | 	.llseek		= seq_lseek, | 
 | 408 | 	.write		= gcov_seq_write, | 
 | 409 | }; | 
 | 410 |  | 
 | 411 | /* Basic initialization of a new node. */ | 
 | 412 | static void init_node(struct gcov_node *node, struct gcov_info *info, | 
 | 413 | 		      const char *name, struct gcov_node *parent) | 
 | 414 | { | 
 | 415 | 	INIT_LIST_HEAD(&node->list); | 
 | 416 | 	INIT_LIST_HEAD(&node->children); | 
 | 417 | 	INIT_LIST_HEAD(&node->all); | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 418 | 	if (node->loaded_info) { | 
 | 419 | 		node->loaded_info[0] = info; | 
 | 420 | 		node->num_loaded = 1; | 
 | 421 | 	} | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 422 | 	node->parent = parent; | 
 | 423 | 	if (name) | 
 | 424 | 		strcpy(node->name, name); | 
 | 425 | } | 
 | 426 |  | 
 | 427 | /* | 
 | 428 |  * Create a new node and associated debugfs entry. Needs to be called with | 
 | 429 |  * node_lock held. | 
 | 430 |  */ | 
 | 431 | static struct gcov_node *new_node(struct gcov_node *parent, | 
 | 432 | 				  struct gcov_info *info, const char *name) | 
 | 433 | { | 
 | 434 | 	struct gcov_node *node; | 
 | 435 |  | 
 | 436 | 	node = kzalloc(sizeof(struct gcov_node) + strlen(name) + 1, GFP_KERNEL); | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 437 | 	if (!node) | 
 | 438 | 		goto err_nomem; | 
 | 439 | 	if (info) { | 
 | 440 | 		node->loaded_info = kcalloc(1, sizeof(struct gcov_info *), | 
 | 441 | 					   GFP_KERNEL); | 
 | 442 | 		if (!node->loaded_info) | 
 | 443 | 			goto err_nomem; | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 444 | 	} | 
 | 445 | 	init_node(node, info, name, parent); | 
 | 446 | 	/* Differentiate between gcov data file nodes and directory nodes. */ | 
 | 447 | 	if (info) { | 
 | 448 | 		node->dentry = debugfs_create_file(deskew(node->name), 0600, | 
 | 449 | 					parent->dentry, node, &gcov_data_fops); | 
 | 450 | 	} else | 
 | 451 | 		node->dentry = debugfs_create_dir(node->name, parent->dentry); | 
 | 452 | 	if (!node->dentry) { | 
 | 453 | 		pr_warning("could not create file\n"); | 
 | 454 | 		kfree(node); | 
 | 455 | 		return NULL; | 
 | 456 | 	} | 
 | 457 | 	if (info) | 
 | 458 | 		add_links(node, parent->dentry); | 
 | 459 | 	list_add(&node->list, &parent->children); | 
 | 460 | 	list_add(&node->all, &all_head); | 
 | 461 |  | 
 | 462 | 	return node; | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 463 |  | 
 | 464 | err_nomem: | 
 | 465 | 	kfree(node); | 
 | 466 | 	pr_warning("out of memory\n"); | 
 | 467 | 	return NULL; | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 468 | } | 
 | 469 |  | 
 | 470 | /* Remove symbolic links associated with node. */ | 
 | 471 | static void remove_links(struct gcov_node *node) | 
 | 472 | { | 
 | 473 | 	int i; | 
 | 474 |  | 
 | 475 | 	if (!node->links) | 
 | 476 | 		return; | 
 | 477 | 	for (i = 0; gcov_link[i].ext; i++) | 
 | 478 | 		debugfs_remove(node->links[i]); | 
 | 479 | 	kfree(node->links); | 
 | 480 | 	node->links = NULL; | 
 | 481 | } | 
 | 482 |  | 
 | 483 | /* | 
 | 484 |  * Remove node from all lists and debugfs and release associated resources. | 
 | 485 |  * Needs to be called with node_lock held. | 
 | 486 |  */ | 
 | 487 | static void release_node(struct gcov_node *node) | 
 | 488 | { | 
 | 489 | 	list_del(&node->list); | 
 | 490 | 	list_del(&node->all); | 
 | 491 | 	debugfs_remove(node->dentry); | 
 | 492 | 	remove_links(node); | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 493 | 	kfree(node->loaded_info); | 
 | 494 | 	if (node->unloaded_info) | 
 | 495 | 		gcov_info_free(node->unloaded_info); | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 496 | 	kfree(node); | 
 | 497 | } | 
 | 498 |  | 
 | 499 | /* Release node and empty parents. Needs to be called with node_lock held. */ | 
 | 500 | static void remove_node(struct gcov_node *node) | 
 | 501 | { | 
 | 502 | 	struct gcov_node *parent; | 
 | 503 |  | 
 | 504 | 	while ((node != &root_node) && list_empty(&node->children)) { | 
 | 505 | 		parent = node->parent; | 
 | 506 | 		release_node(node); | 
 | 507 | 		node = parent; | 
 | 508 | 	} | 
 | 509 | } | 
 | 510 |  | 
 | 511 | /* | 
 | 512 |  * Find child node with given basename. Needs to be called with node_lock | 
 | 513 |  * held. | 
 | 514 |  */ | 
 | 515 | static struct gcov_node *get_child_by_name(struct gcov_node *parent, | 
 | 516 | 					   const char *name) | 
 | 517 | { | 
 | 518 | 	struct gcov_node *node; | 
 | 519 |  | 
 | 520 | 	list_for_each_entry(node, &parent->children, list) { | 
 | 521 | 		if (strcmp(node->name, name) == 0) | 
 | 522 | 			return node; | 
 | 523 | 	} | 
 | 524 |  | 
 | 525 | 	return NULL; | 
 | 526 | } | 
 | 527 |  | 
 | 528 | /* | 
 | 529 |  * write() implementation for reset file. Reset all profiling data to zero | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 530 |  * and remove nodes for which all associated object files are unloaded. | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 531 |  */ | 
 | 532 | static ssize_t reset_write(struct file *file, const char __user *addr, | 
 | 533 | 			   size_t len, loff_t *pos) | 
 | 534 | { | 
 | 535 | 	struct gcov_node *node; | 
 | 536 |  | 
 | 537 | 	mutex_lock(&node_lock); | 
 | 538 | restart: | 
 | 539 | 	list_for_each_entry(node, &all_head, all) { | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 540 | 		if (node->num_loaded > 0) | 
 | 541 | 			reset_node(node); | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 542 | 		else if (list_empty(&node->children)) { | 
 | 543 | 			remove_node(node); | 
 | 544 | 			/* Several nodes may have gone - restart loop. */ | 
 | 545 | 			goto restart; | 
 | 546 | 		} | 
 | 547 | 	} | 
 | 548 | 	mutex_unlock(&node_lock); | 
 | 549 |  | 
 | 550 | 	return len; | 
 | 551 | } | 
 | 552 |  | 
 | 553 | /* read() implementation for reset file. Unused. */ | 
 | 554 | static ssize_t reset_read(struct file *file, char __user *addr, size_t len, | 
 | 555 | 			  loff_t *pos) | 
 | 556 | { | 
 | 557 | 	/* Allow read operation so that a recursive copy won't fail. */ | 
 | 558 | 	return 0; | 
 | 559 | } | 
 | 560 |  | 
 | 561 | static const struct file_operations gcov_reset_fops = { | 
 | 562 | 	.write	= reset_write, | 
 | 563 | 	.read	= reset_read, | 
| Arnd Bergmann | 6038f37 | 2010-08-15 18:52:59 +0200 | [diff] [blame] | 564 | 	.llseek = noop_llseek, | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 565 | }; | 
 | 566 |  | 
 | 567 | /* | 
 | 568 |  * Create a node for a given profiling data set and add it to all lists and | 
 | 569 |  * debugfs. Needs to be called with node_lock held. | 
 | 570 |  */ | 
 | 571 | static void add_node(struct gcov_info *info) | 
 | 572 | { | 
 | 573 | 	char *filename; | 
 | 574 | 	char *curr; | 
 | 575 | 	char *next; | 
 | 576 | 	struct gcov_node *parent; | 
 | 577 | 	struct gcov_node *node; | 
 | 578 |  | 
 | 579 | 	filename = kstrdup(info->filename, GFP_KERNEL); | 
 | 580 | 	if (!filename) | 
 | 581 | 		return; | 
 | 582 | 	parent = &root_node; | 
 | 583 | 	/* Create directory nodes along the path. */ | 
 | 584 | 	for (curr = filename; (next = strchr(curr, '/')); curr = next + 1) { | 
 | 585 | 		if (curr == next) | 
 | 586 | 			continue; | 
 | 587 | 		*next = 0; | 
 | 588 | 		if (strcmp(curr, ".") == 0) | 
 | 589 | 			continue; | 
 | 590 | 		if (strcmp(curr, "..") == 0) { | 
 | 591 | 			if (!parent->parent) | 
 | 592 | 				goto err_remove; | 
 | 593 | 			parent = parent->parent; | 
 | 594 | 			continue; | 
 | 595 | 		} | 
 | 596 | 		node = get_child_by_name(parent, curr); | 
 | 597 | 		if (!node) { | 
 | 598 | 			node = new_node(parent, NULL, curr); | 
 | 599 | 			if (!node) | 
 | 600 | 				goto err_remove; | 
 | 601 | 		} | 
 | 602 | 		parent = node; | 
 | 603 | 	} | 
 | 604 | 	/* Create file node. */ | 
 | 605 | 	node = new_node(parent, info, curr); | 
 | 606 | 	if (!node) | 
 | 607 | 		goto err_remove; | 
 | 608 | out: | 
 | 609 | 	kfree(filename); | 
 | 610 | 	return; | 
 | 611 |  | 
 | 612 | err_remove: | 
 | 613 | 	remove_node(parent); | 
 | 614 | 	goto out; | 
 | 615 | } | 
 | 616 |  | 
 | 617 | /* | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 618 |  * Associate a profiling data set with an existing node. Needs to be called | 
 | 619 |  * with node_lock held. | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 620 |  */ | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 621 | static void add_info(struct gcov_node *node, struct gcov_info *info) | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 622 | { | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 623 | 	struct gcov_info **loaded_info; | 
 | 624 | 	int num = node->num_loaded; | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 625 |  | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 626 | 	/* | 
 | 627 | 	 * Prepare new array. This is done first to simplify cleanup in | 
 | 628 | 	 * case the new data set is incompatible, the node only contains | 
 | 629 | 	 * unloaded data sets and there's not enough memory for the array. | 
 | 630 | 	 */ | 
 | 631 | 	loaded_info = kcalloc(num + 1, sizeof(struct gcov_info *), GFP_KERNEL); | 
 | 632 | 	if (!loaded_info) { | 
 | 633 | 		pr_warning("could not add '%s' (out of memory)\n", | 
 | 634 | 			   info->filename); | 
 | 635 | 		return; | 
 | 636 | 	} | 
 | 637 | 	memcpy(loaded_info, node->loaded_info, | 
 | 638 | 	       num * sizeof(struct gcov_info *)); | 
 | 639 | 	loaded_info[num] = info; | 
 | 640 | 	/* Check if the new data set is compatible. */ | 
 | 641 | 	if (num == 0) { | 
 | 642 | 		/* | 
 | 643 | 		 * A module was unloaded, modified and reloaded. The new | 
 | 644 | 		 * data set replaces the copy of the last one. | 
 | 645 | 		 */ | 
 | 646 | 		if (!gcov_info_is_compatible(node->unloaded_info, info)) { | 
 | 647 | 			pr_warning("discarding saved data for %s " | 
 | 648 | 				   "(incompatible version)\n", info->filename); | 
 | 649 | 			gcov_info_free(node->unloaded_info); | 
 | 650 | 			node->unloaded_info = NULL; | 
 | 651 | 		} | 
 | 652 | 	} else { | 
 | 653 | 		/* | 
 | 654 | 		 * Two different versions of the same object file are loaded. | 
 | 655 | 		 * The initial one takes precedence. | 
 | 656 | 		 */ | 
 | 657 | 		if (!gcov_info_is_compatible(node->loaded_info[0], info)) { | 
 | 658 | 			pr_warning("could not add '%s' (incompatible " | 
 | 659 | 				   "version)\n", info->filename); | 
 | 660 | 			kfree(loaded_info); | 
 | 661 | 			return; | 
 | 662 | 		} | 
 | 663 | 	} | 
 | 664 | 	/* Overwrite previous array. */ | 
 | 665 | 	kfree(node->loaded_info); | 
 | 666 | 	node->loaded_info = loaded_info; | 
 | 667 | 	node->num_loaded = num + 1; | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 668 | } | 
 | 669 |  | 
 | 670 | /* | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 671 |  * Return the index of a profiling data set associated with a node. | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 672 |  */ | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 673 | static int get_info_index(struct gcov_node *node, struct gcov_info *info) | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 674 | { | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 675 | 	int i; | 
 | 676 |  | 
 | 677 | 	for (i = 0; i < node->num_loaded; i++) { | 
 | 678 | 		if (node->loaded_info[i] == info) | 
 | 679 | 			return i; | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 680 | 	} | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 681 | 	return -ENOENT; | 
 | 682 | } | 
 | 683 |  | 
 | 684 | /* | 
 | 685 |  * Save the data of a profiling data set which is being unloaded. | 
 | 686 |  */ | 
 | 687 | static void save_info(struct gcov_node *node, struct gcov_info *info) | 
 | 688 | { | 
 | 689 | 	if (node->unloaded_info) | 
 | 690 | 		gcov_info_add(node->unloaded_info, info); | 
 | 691 | 	else { | 
 | 692 | 		node->unloaded_info = gcov_info_dup(info); | 
 | 693 | 		if (!node->unloaded_info) { | 
 | 694 | 			pr_warning("could not save data for '%s' " | 
 | 695 | 				   "(out of memory)\n", info->filename); | 
 | 696 | 		} | 
 | 697 | 	} | 
 | 698 | } | 
 | 699 |  | 
 | 700 | /* | 
 | 701 |  * Disassociate a profiling data set from a node. Needs to be called with | 
 | 702 |  * node_lock held. | 
 | 703 |  */ | 
 | 704 | static void remove_info(struct gcov_node *node, struct gcov_info *info) | 
 | 705 | { | 
 | 706 | 	int i; | 
 | 707 |  | 
 | 708 | 	i = get_info_index(node, info); | 
 | 709 | 	if (i < 0) { | 
 | 710 | 		pr_warning("could not remove '%s' (not found)\n", | 
 | 711 | 			   info->filename); | 
 | 712 | 		return; | 
 | 713 | 	} | 
 | 714 | 	if (gcov_persist) | 
 | 715 | 		save_info(node, info); | 
 | 716 | 	/* Shrink array. */ | 
 | 717 | 	node->loaded_info[i] = node->loaded_info[node->num_loaded - 1]; | 
 | 718 | 	node->num_loaded--; | 
 | 719 | 	if (node->num_loaded > 0) | 
 | 720 | 		return; | 
 | 721 | 	/* Last loaded data set was removed. */ | 
 | 722 | 	kfree(node->loaded_info); | 
 | 723 | 	node->loaded_info = NULL; | 
 | 724 | 	node->num_loaded = 0; | 
 | 725 | 	if (!node->unloaded_info) | 
 | 726 | 		remove_node(node); | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 727 | } | 
 | 728 |  | 
 | 729 | /* | 
 | 730 |  * Callback to create/remove profiling files when code compiled with | 
 | 731 |  * -fprofile-arcs is loaded/unloaded. | 
 | 732 |  */ | 
 | 733 | void gcov_event(enum gcov_action action, struct gcov_info *info) | 
 | 734 | { | 
 | 735 | 	struct gcov_node *node; | 
 | 736 |  | 
 | 737 | 	mutex_lock(&node_lock); | 
 | 738 | 	node = get_node_by_name(info->filename); | 
 | 739 | 	switch (action) { | 
 | 740 | 	case GCOV_ADD: | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 741 | 		if (node) | 
 | 742 | 			add_info(node, info); | 
 | 743 | 		else | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 744 | 			add_node(info); | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 745 | 		break; | 
 | 746 | 	case GCOV_REMOVE: | 
| Peter Oberparleiter | 85a0fdf | 2010-09-09 16:37:35 -0700 | [diff] [blame] | 747 | 		if (node) | 
 | 748 | 			remove_info(node, info); | 
 | 749 | 		else { | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 750 | 			pr_warning("could not remove '%s' (not found)\n", | 
 | 751 | 				   info->filename); | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 752 | 		} | 
| Peter Oberparleiter | 2521f2c | 2009-06-17 16:28:08 -0700 | [diff] [blame] | 753 | 		break; | 
 | 754 | 	} | 
 | 755 | 	mutex_unlock(&node_lock); | 
 | 756 | } | 
 | 757 |  | 
 | 758 | /* Create debugfs entries. */ | 
 | 759 | static __init int gcov_fs_init(void) | 
 | 760 | { | 
 | 761 | 	int rc = -EIO; | 
 | 762 |  | 
 | 763 | 	init_node(&root_node, NULL, NULL, NULL); | 
 | 764 | 	/* | 
 | 765 | 	 * /sys/kernel/debug/gcov will be parent for the reset control file | 
 | 766 | 	 * and all profiling files. | 
 | 767 | 	 */ | 
 | 768 | 	root_node.dentry = debugfs_create_dir("gcov", NULL); | 
 | 769 | 	if (!root_node.dentry) | 
 | 770 | 		goto err_remove; | 
 | 771 | 	/* | 
 | 772 | 	 * Create reset file which resets all profiling counts when written | 
 | 773 | 	 * to. | 
 | 774 | 	 */ | 
 | 775 | 	reset_dentry = debugfs_create_file("reset", 0600, root_node.dentry, | 
 | 776 | 					   NULL, &gcov_reset_fops); | 
 | 777 | 	if (!reset_dentry) | 
 | 778 | 		goto err_remove; | 
 | 779 | 	/* Replay previous events to get our fs hierarchy up-to-date. */ | 
 | 780 | 	gcov_enable_events(); | 
 | 781 | 	return 0; | 
 | 782 |  | 
 | 783 | err_remove: | 
 | 784 | 	pr_err("init failed\n"); | 
 | 785 | 	if (root_node.dentry) | 
 | 786 | 		debugfs_remove(root_node.dentry); | 
 | 787 |  | 
 | 788 | 	return rc; | 
 | 789 | } | 
 | 790 | device_initcall(gcov_fs_init); |