xref: /openbmc/linux/kernel/gcov/fs.c (revision 6524d794)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
22521f2c2SPeter Oberparleiter /*
32521f2c2SPeter Oberparleiter  *  This code exports profiling data as debugfs files to userspace.
42521f2c2SPeter Oberparleiter  *
52521f2c2SPeter Oberparleiter  *    Copyright IBM Corp. 2009
62521f2c2SPeter Oberparleiter  *    Author(s): Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
72521f2c2SPeter Oberparleiter  *
82521f2c2SPeter Oberparleiter  *    Uses gcc-internal data definitions.
92521f2c2SPeter Oberparleiter  *    Based on the gcov-kernel patch by:
102521f2c2SPeter Oberparleiter  *		 Hubertus Franke <frankeh@us.ibm.com>
112521f2c2SPeter Oberparleiter  *		 Nigel Hinds <nhinds@us.ibm.com>
122521f2c2SPeter Oberparleiter  *		 Rajan Ravindran <rajancr@us.ibm.com>
132521f2c2SPeter Oberparleiter  *		 Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
142521f2c2SPeter Oberparleiter  *		 Paul Larson
152521f2c2SPeter Oberparleiter  *		 Yi CDL Yang
162521f2c2SPeter Oberparleiter  */
172521f2c2SPeter Oberparleiter 
182521f2c2SPeter Oberparleiter #define pr_fmt(fmt)	"gcov: " fmt
192521f2c2SPeter Oberparleiter 
202521f2c2SPeter Oberparleiter #include <linux/init.h>
212521f2c2SPeter Oberparleiter #include <linux/module.h>
222521f2c2SPeter Oberparleiter #include <linux/debugfs.h>
232521f2c2SPeter Oberparleiter #include <linux/fs.h>
242521f2c2SPeter Oberparleiter #include <linux/list.h>
252521f2c2SPeter Oberparleiter #include <linux/string.h>
262521f2c2SPeter Oberparleiter #include <linux/slab.h>
272521f2c2SPeter Oberparleiter #include <linux/mutex.h>
282521f2c2SPeter Oberparleiter #include <linux/seq_file.h>
292521f2c2SPeter Oberparleiter #include "gcov.h"
302521f2c2SPeter Oberparleiter 
312521f2c2SPeter Oberparleiter /**
322521f2c2SPeter Oberparleiter  * struct gcov_node - represents a debugfs entry
332521f2c2SPeter Oberparleiter  * @list: list head for child node list
342521f2c2SPeter Oberparleiter  * @children: child nodes
352521f2c2SPeter Oberparleiter  * @all: list head for list of all nodes
362521f2c2SPeter Oberparleiter  * @parent: parent node
3785a0fdfdSPeter Oberparleiter  * @loaded_info: array of pointers to profiling data sets for loaded object
3885a0fdfdSPeter Oberparleiter  *   files.
3985a0fdfdSPeter Oberparleiter  * @num_loaded: number of profiling data sets for loaded object files.
4085a0fdfdSPeter Oberparleiter  * @unloaded_info: accumulated copy of profiling data sets for unloaded
4185a0fdfdSPeter Oberparleiter  *   object files. Used only when gcov_persist=1.
422521f2c2SPeter Oberparleiter  * @dentry: main debugfs entry, either a directory or data file
432521f2c2SPeter Oberparleiter  * @links: associated symbolic links
442521f2c2SPeter Oberparleiter  * @name: data file basename
452521f2c2SPeter Oberparleiter  *
462521f2c2SPeter Oberparleiter  * struct gcov_node represents an entity within the gcov/ subdirectory
472521f2c2SPeter Oberparleiter  * of debugfs. There are directory and data file nodes. The latter represent
482521f2c2SPeter Oberparleiter  * the actual synthesized data file plus any associated symbolic links which
492521f2c2SPeter Oberparleiter  * are needed by the gcov tool to work correctly.
502521f2c2SPeter Oberparleiter  */
512521f2c2SPeter Oberparleiter struct gcov_node {
522521f2c2SPeter Oberparleiter 	struct list_head list;
532521f2c2SPeter Oberparleiter 	struct list_head children;
542521f2c2SPeter Oberparleiter 	struct list_head all;
552521f2c2SPeter Oberparleiter 	struct gcov_node *parent;
5685a0fdfdSPeter Oberparleiter 	struct gcov_info **loaded_info;
5785a0fdfdSPeter Oberparleiter 	struct gcov_info *unloaded_info;
582521f2c2SPeter Oberparleiter 	struct dentry *dentry;
592521f2c2SPeter Oberparleiter 	struct dentry **links;
6085a0fdfdSPeter Oberparleiter 	int num_loaded;
616524d794SGustavo A. R. Silva 	char name[];
622521f2c2SPeter Oberparleiter };
632521f2c2SPeter Oberparleiter 
642521f2c2SPeter Oberparleiter static const char objtree[] = OBJTREE;
652521f2c2SPeter Oberparleiter static const char srctree[] = SRCTREE;
662521f2c2SPeter Oberparleiter static struct gcov_node root_node;
672521f2c2SPeter Oberparleiter static LIST_HEAD(all_head);
682521f2c2SPeter Oberparleiter static DEFINE_MUTEX(node_lock);
692521f2c2SPeter Oberparleiter 
702521f2c2SPeter Oberparleiter /* If non-zero, keep copies of profiling data for unloaded modules. */
712521f2c2SPeter Oberparleiter static int gcov_persist = 1;
722521f2c2SPeter Oberparleiter 
732521f2c2SPeter Oberparleiter static int __init gcov_persist_setup(char *str)
742521f2c2SPeter Oberparleiter {
752521f2c2SPeter Oberparleiter 	unsigned long val;
762521f2c2SPeter Oberparleiter 
776072ddc8SJingoo Han 	if (kstrtoul(str, 0, &val)) {
78a5ebb875SAndrew Morton 		pr_warn("invalid gcov_persist parameter '%s'\n", str);
792521f2c2SPeter Oberparleiter 		return 0;
802521f2c2SPeter Oberparleiter 	}
812521f2c2SPeter Oberparleiter 	gcov_persist = val;
822521f2c2SPeter Oberparleiter 	pr_info("setting gcov_persist to %d\n", gcov_persist);
832521f2c2SPeter Oberparleiter 
842521f2c2SPeter Oberparleiter 	return 1;
852521f2c2SPeter Oberparleiter }
862521f2c2SPeter Oberparleiter __setup("gcov_persist=", gcov_persist_setup);
872521f2c2SPeter Oberparleiter 
882521f2c2SPeter Oberparleiter /*
892521f2c2SPeter Oberparleiter  * seq_file.start() implementation for gcov data files. Note that the
902521f2c2SPeter Oberparleiter  * gcov_iterator interface is designed to be more restrictive than seq_file
912521f2c2SPeter Oberparleiter  * (no start from arbitrary position, etc.), to simplify the iterator
922521f2c2SPeter Oberparleiter  * implementation.
932521f2c2SPeter Oberparleiter  */
942521f2c2SPeter Oberparleiter static void *gcov_seq_start(struct seq_file *seq, loff_t *pos)
952521f2c2SPeter Oberparleiter {
962521f2c2SPeter Oberparleiter 	loff_t i;
972521f2c2SPeter Oberparleiter 
982521f2c2SPeter Oberparleiter 	gcov_iter_start(seq->private);
992521f2c2SPeter Oberparleiter 	for (i = 0; i < *pos; i++) {
1002521f2c2SPeter Oberparleiter 		if (gcov_iter_next(seq->private))
1012521f2c2SPeter Oberparleiter 			return NULL;
1022521f2c2SPeter Oberparleiter 	}
1032521f2c2SPeter Oberparleiter 	return seq->private;
1042521f2c2SPeter Oberparleiter }
1052521f2c2SPeter Oberparleiter 
1062521f2c2SPeter Oberparleiter /* seq_file.next() implementation for gcov data files. */
1072521f2c2SPeter Oberparleiter static void *gcov_seq_next(struct seq_file *seq, void *data, loff_t *pos)
1082521f2c2SPeter Oberparleiter {
1092521f2c2SPeter Oberparleiter 	struct gcov_iterator *iter = data;
1102521f2c2SPeter Oberparleiter 
1112521f2c2SPeter Oberparleiter 	if (gcov_iter_next(iter))
1122521f2c2SPeter Oberparleiter 		return NULL;
1132521f2c2SPeter Oberparleiter 	(*pos)++;
1142521f2c2SPeter Oberparleiter 
1152521f2c2SPeter Oberparleiter 	return iter;
1162521f2c2SPeter Oberparleiter }
1172521f2c2SPeter Oberparleiter 
1182521f2c2SPeter Oberparleiter /* seq_file.show() implementation for gcov data files. */
1192521f2c2SPeter Oberparleiter static int gcov_seq_show(struct seq_file *seq, void *data)
1202521f2c2SPeter Oberparleiter {
1212521f2c2SPeter Oberparleiter 	struct gcov_iterator *iter = data;
1222521f2c2SPeter Oberparleiter 
1232521f2c2SPeter Oberparleiter 	if (gcov_iter_write(iter, seq))
1242521f2c2SPeter Oberparleiter 		return -EINVAL;
1252521f2c2SPeter Oberparleiter 	return 0;
1262521f2c2SPeter Oberparleiter }
1272521f2c2SPeter Oberparleiter 
1282521f2c2SPeter Oberparleiter static void gcov_seq_stop(struct seq_file *seq, void *data)
1292521f2c2SPeter Oberparleiter {
1302521f2c2SPeter Oberparleiter 	/* Unused. */
1312521f2c2SPeter Oberparleiter }
1322521f2c2SPeter Oberparleiter 
1332521f2c2SPeter Oberparleiter static const struct seq_operations gcov_seq_ops = {
1342521f2c2SPeter Oberparleiter 	.start	= gcov_seq_start,
1352521f2c2SPeter Oberparleiter 	.next	= gcov_seq_next,
1362521f2c2SPeter Oberparleiter 	.show	= gcov_seq_show,
1372521f2c2SPeter Oberparleiter 	.stop	= gcov_seq_stop,
1382521f2c2SPeter Oberparleiter };
1392521f2c2SPeter Oberparleiter 
1402521f2c2SPeter Oberparleiter /*
14185a0fdfdSPeter Oberparleiter  * Return a profiling data set associated with the given node. This is
14285a0fdfdSPeter Oberparleiter  * either a data set for a loaded object file or a data set copy in case
14385a0fdfdSPeter Oberparleiter  * all associated object files have been unloaded.
1442521f2c2SPeter Oberparleiter  */
1452521f2c2SPeter Oberparleiter static struct gcov_info *get_node_info(struct gcov_node *node)
1462521f2c2SPeter Oberparleiter {
14785a0fdfdSPeter Oberparleiter 	if (node->num_loaded > 0)
14885a0fdfdSPeter Oberparleiter 		return node->loaded_info[0];
1492521f2c2SPeter Oberparleiter 
15085a0fdfdSPeter Oberparleiter 	return node->unloaded_info;
15185a0fdfdSPeter Oberparleiter }
15285a0fdfdSPeter Oberparleiter 
15385a0fdfdSPeter Oberparleiter /*
15485a0fdfdSPeter Oberparleiter  * Return a newly allocated profiling data set which contains the sum of
15585a0fdfdSPeter Oberparleiter  * all profiling data associated with the given node.
15685a0fdfdSPeter Oberparleiter  */
15785a0fdfdSPeter Oberparleiter static struct gcov_info *get_accumulated_info(struct gcov_node *node)
15885a0fdfdSPeter Oberparleiter {
15985a0fdfdSPeter Oberparleiter 	struct gcov_info *info;
16085a0fdfdSPeter Oberparleiter 	int i = 0;
16185a0fdfdSPeter Oberparleiter 
16285a0fdfdSPeter Oberparleiter 	if (node->unloaded_info)
16385a0fdfdSPeter Oberparleiter 		info = gcov_info_dup(node->unloaded_info);
16485a0fdfdSPeter Oberparleiter 	else
16585a0fdfdSPeter Oberparleiter 		info = gcov_info_dup(node->loaded_info[i++]);
16685a0fdfdSPeter Oberparleiter 	if (!info)
16785a0fdfdSPeter Oberparleiter 		return NULL;
16885a0fdfdSPeter Oberparleiter 	for (; i < node->num_loaded; i++)
16985a0fdfdSPeter Oberparleiter 		gcov_info_add(info, node->loaded_info[i]);
17085a0fdfdSPeter Oberparleiter 
17185a0fdfdSPeter Oberparleiter 	return info;
1722521f2c2SPeter Oberparleiter }
1732521f2c2SPeter Oberparleiter 
1742521f2c2SPeter Oberparleiter /*
1752521f2c2SPeter Oberparleiter  * open() implementation for gcov data files. Create a copy of the profiling
1762521f2c2SPeter Oberparleiter  * data set and initialize the iterator and seq_file interface.
1772521f2c2SPeter Oberparleiter  */
1782521f2c2SPeter Oberparleiter static int gcov_seq_open(struct inode *inode, struct file *file)
1792521f2c2SPeter Oberparleiter {
1802521f2c2SPeter Oberparleiter 	struct gcov_node *node = inode->i_private;
1812521f2c2SPeter Oberparleiter 	struct gcov_iterator *iter;
1822521f2c2SPeter Oberparleiter 	struct seq_file *seq;
1832521f2c2SPeter Oberparleiter 	struct gcov_info *info;
1842521f2c2SPeter Oberparleiter 	int rc = -ENOMEM;
1852521f2c2SPeter Oberparleiter 
1862521f2c2SPeter Oberparleiter 	mutex_lock(&node_lock);
1872521f2c2SPeter Oberparleiter 	/*
1882521f2c2SPeter Oberparleiter 	 * Read from a profiling data copy to minimize reference tracking
18985a0fdfdSPeter Oberparleiter 	 * complexity and concurrent access and to keep accumulating multiple
19085a0fdfdSPeter Oberparleiter 	 * profiling data sets associated with one node simple.
1912521f2c2SPeter Oberparleiter 	 */
19285a0fdfdSPeter Oberparleiter 	info = get_accumulated_info(node);
1932521f2c2SPeter Oberparleiter 	if (!info)
1942521f2c2SPeter Oberparleiter 		goto out_unlock;
1952521f2c2SPeter Oberparleiter 	iter = gcov_iter_new(info);
1962521f2c2SPeter Oberparleiter 	if (!iter)
1972521f2c2SPeter Oberparleiter 		goto err_free_info;
1982521f2c2SPeter Oberparleiter 	rc = seq_open(file, &gcov_seq_ops);
1992521f2c2SPeter Oberparleiter 	if (rc)
2002521f2c2SPeter Oberparleiter 		goto err_free_iter_info;
2012521f2c2SPeter Oberparleiter 	seq = file->private_data;
2022521f2c2SPeter Oberparleiter 	seq->private = iter;
2032521f2c2SPeter Oberparleiter out_unlock:
2042521f2c2SPeter Oberparleiter 	mutex_unlock(&node_lock);
2052521f2c2SPeter Oberparleiter 	return rc;
2062521f2c2SPeter Oberparleiter 
2072521f2c2SPeter Oberparleiter err_free_iter_info:
2082521f2c2SPeter Oberparleiter 	gcov_iter_free(iter);
2092521f2c2SPeter Oberparleiter err_free_info:
2102521f2c2SPeter Oberparleiter 	gcov_info_free(info);
2112521f2c2SPeter Oberparleiter 	goto out_unlock;
2122521f2c2SPeter Oberparleiter }
2132521f2c2SPeter Oberparleiter 
2142521f2c2SPeter Oberparleiter /*
2152521f2c2SPeter Oberparleiter  * release() implementation for gcov data files. Release resources allocated
2162521f2c2SPeter Oberparleiter  * by open().
2172521f2c2SPeter Oberparleiter  */
2182521f2c2SPeter Oberparleiter static int gcov_seq_release(struct inode *inode, struct file *file)
2192521f2c2SPeter Oberparleiter {
2202521f2c2SPeter Oberparleiter 	struct gcov_iterator *iter;
2212521f2c2SPeter Oberparleiter 	struct gcov_info *info;
2222521f2c2SPeter Oberparleiter 	struct seq_file *seq;
2232521f2c2SPeter Oberparleiter 
2242521f2c2SPeter Oberparleiter 	seq = file->private_data;
2252521f2c2SPeter Oberparleiter 	iter = seq->private;
2262521f2c2SPeter Oberparleiter 	info = gcov_iter_get_info(iter);
2272521f2c2SPeter Oberparleiter 	gcov_iter_free(iter);
2282521f2c2SPeter Oberparleiter 	gcov_info_free(info);
2292521f2c2SPeter Oberparleiter 	seq_release(inode, file);
2302521f2c2SPeter Oberparleiter 
2312521f2c2SPeter Oberparleiter 	return 0;
2322521f2c2SPeter Oberparleiter }
2332521f2c2SPeter Oberparleiter 
2342521f2c2SPeter Oberparleiter /*
2352521f2c2SPeter Oberparleiter  * Find a node by the associated data file name. Needs to be called with
2362521f2c2SPeter Oberparleiter  * node_lock held.
2372521f2c2SPeter Oberparleiter  */
2382521f2c2SPeter Oberparleiter static struct gcov_node *get_node_by_name(const char *name)
2392521f2c2SPeter Oberparleiter {
2402521f2c2SPeter Oberparleiter 	struct gcov_node *node;
2412521f2c2SPeter Oberparleiter 	struct gcov_info *info;
2422521f2c2SPeter Oberparleiter 
2432521f2c2SPeter Oberparleiter 	list_for_each_entry(node, &all_head, all) {
2442521f2c2SPeter Oberparleiter 		info = get_node_info(node);
2458cbce376SFrantisek Hrbata 		if (info && (strcmp(gcov_info_filename(info), name) == 0))
2462521f2c2SPeter Oberparleiter 			return node;
2472521f2c2SPeter Oberparleiter 	}
2482521f2c2SPeter Oberparleiter 
2492521f2c2SPeter Oberparleiter 	return NULL;
2502521f2c2SPeter Oberparleiter }
2512521f2c2SPeter Oberparleiter 
25285a0fdfdSPeter Oberparleiter /*
25385a0fdfdSPeter Oberparleiter  * Reset all profiling data associated with the specified node.
25485a0fdfdSPeter Oberparleiter  */
25585a0fdfdSPeter Oberparleiter static void reset_node(struct gcov_node *node)
25685a0fdfdSPeter Oberparleiter {
25785a0fdfdSPeter Oberparleiter 	int i;
25885a0fdfdSPeter Oberparleiter 
25985a0fdfdSPeter Oberparleiter 	if (node->unloaded_info)
26085a0fdfdSPeter Oberparleiter 		gcov_info_reset(node->unloaded_info);
26185a0fdfdSPeter Oberparleiter 	for (i = 0; i < node->num_loaded; i++)
26285a0fdfdSPeter Oberparleiter 		gcov_info_reset(node->loaded_info[i]);
26385a0fdfdSPeter Oberparleiter }
26485a0fdfdSPeter Oberparleiter 
2652521f2c2SPeter Oberparleiter static void remove_node(struct gcov_node *node);
2662521f2c2SPeter Oberparleiter 
2672521f2c2SPeter Oberparleiter /*
2682521f2c2SPeter Oberparleiter  * write() implementation for gcov data files. Reset profiling data for the
26985a0fdfdSPeter Oberparleiter  * corresponding file. If all associated object files have been unloaded,
27085a0fdfdSPeter Oberparleiter  * remove the debug fs node as well.
2712521f2c2SPeter Oberparleiter  */
2722521f2c2SPeter Oberparleiter static ssize_t gcov_seq_write(struct file *file, const char __user *addr,
2732521f2c2SPeter Oberparleiter 			      size_t len, loff_t *pos)
2742521f2c2SPeter Oberparleiter {
2752521f2c2SPeter Oberparleiter 	struct seq_file *seq;
2762521f2c2SPeter Oberparleiter 	struct gcov_info *info;
2772521f2c2SPeter Oberparleiter 	struct gcov_node *node;
2782521f2c2SPeter Oberparleiter 
2792521f2c2SPeter Oberparleiter 	seq = file->private_data;
2802521f2c2SPeter Oberparleiter 	info = gcov_iter_get_info(seq->private);
2812521f2c2SPeter Oberparleiter 	mutex_lock(&node_lock);
2828cbce376SFrantisek Hrbata 	node = get_node_by_name(gcov_info_filename(info));
2832521f2c2SPeter Oberparleiter 	if (node) {
2842521f2c2SPeter Oberparleiter 		/* Reset counts or remove node for unloaded modules. */
28585a0fdfdSPeter Oberparleiter 		if (node->num_loaded == 0)
2862521f2c2SPeter Oberparleiter 			remove_node(node);
2872521f2c2SPeter Oberparleiter 		else
28885a0fdfdSPeter Oberparleiter 			reset_node(node);
2892521f2c2SPeter Oberparleiter 	}
2902521f2c2SPeter Oberparleiter 	/* Reset counts for open file. */
2912521f2c2SPeter Oberparleiter 	gcov_info_reset(info);
2922521f2c2SPeter Oberparleiter 	mutex_unlock(&node_lock);
2932521f2c2SPeter Oberparleiter 
2942521f2c2SPeter Oberparleiter 	return len;
2952521f2c2SPeter Oberparleiter }
2962521f2c2SPeter Oberparleiter 
2972521f2c2SPeter Oberparleiter /*
2982521f2c2SPeter Oberparleiter  * Given a string <path> representing a file path of format:
2992521f2c2SPeter Oberparleiter  *   path/to/file.gcda
3002521f2c2SPeter Oberparleiter  * construct and return a new string:
3012521f2c2SPeter Oberparleiter  *   <dir/>path/to/file.<ext>
3022521f2c2SPeter Oberparleiter  */
3032521f2c2SPeter Oberparleiter static char *link_target(const char *dir, const char *path, const char *ext)
3042521f2c2SPeter Oberparleiter {
3052521f2c2SPeter Oberparleiter 	char *target;
3062521f2c2SPeter Oberparleiter 	char *old_ext;
3072521f2c2SPeter Oberparleiter 	char *copy;
3082521f2c2SPeter Oberparleiter 
3092521f2c2SPeter Oberparleiter 	copy = kstrdup(path, GFP_KERNEL);
3102521f2c2SPeter Oberparleiter 	if (!copy)
3112521f2c2SPeter Oberparleiter 		return NULL;
3122521f2c2SPeter Oberparleiter 	old_ext = strrchr(copy, '.');
3132521f2c2SPeter Oberparleiter 	if (old_ext)
3142521f2c2SPeter Oberparleiter 		*old_ext = '\0';
3152521f2c2SPeter Oberparleiter 	if (dir)
3162521f2c2SPeter Oberparleiter 		target = kasprintf(GFP_KERNEL, "%s/%s.%s", dir, copy, ext);
3172521f2c2SPeter Oberparleiter 	else
3182521f2c2SPeter Oberparleiter 		target = kasprintf(GFP_KERNEL, "%s.%s", copy, ext);
3192521f2c2SPeter Oberparleiter 	kfree(copy);
3202521f2c2SPeter Oberparleiter 
3212521f2c2SPeter Oberparleiter 	return target;
3222521f2c2SPeter Oberparleiter }
3232521f2c2SPeter Oberparleiter 
3242521f2c2SPeter Oberparleiter /*
3252521f2c2SPeter Oberparleiter  * Construct a string representing the symbolic link target for the given
3262521f2c2SPeter Oberparleiter  * gcov data file name and link type. Depending on the link type and the
3272521f2c2SPeter Oberparleiter  * location of the data file, the link target can either point to a
3282521f2c2SPeter Oberparleiter  * subdirectory of srctree, objtree or in an external location.
3292521f2c2SPeter Oberparleiter  */
3302521f2c2SPeter Oberparleiter static char *get_link_target(const char *filename, const struct gcov_link *ext)
3312521f2c2SPeter Oberparleiter {
3322521f2c2SPeter Oberparleiter 	const char *rel;
3332521f2c2SPeter Oberparleiter 	char *result;
3342521f2c2SPeter Oberparleiter 
3352521f2c2SPeter Oberparleiter 	if (strncmp(filename, objtree, strlen(objtree)) == 0) {
3362521f2c2SPeter Oberparleiter 		rel = filename + strlen(objtree) + 1;
3372521f2c2SPeter Oberparleiter 		if (ext->dir == SRC_TREE)
3382521f2c2SPeter Oberparleiter 			result = link_target(srctree, rel, ext->ext);
3392521f2c2SPeter Oberparleiter 		else
3402521f2c2SPeter Oberparleiter 			result = link_target(objtree, rel, ext->ext);
3412521f2c2SPeter Oberparleiter 	} else {
3422521f2c2SPeter Oberparleiter 		/* External compilation. */
3432521f2c2SPeter Oberparleiter 		result = link_target(NULL, filename, ext->ext);
3442521f2c2SPeter Oberparleiter 	}
3452521f2c2SPeter Oberparleiter 
3462521f2c2SPeter Oberparleiter 	return result;
3472521f2c2SPeter Oberparleiter }
3482521f2c2SPeter Oberparleiter 
3492521f2c2SPeter Oberparleiter #define SKEW_PREFIX	".tmp_"
3502521f2c2SPeter Oberparleiter 
3512521f2c2SPeter Oberparleiter /*
3522521f2c2SPeter Oberparleiter  * For a filename .tmp_filename.ext return filename.ext. Needed to compensate
3532521f2c2SPeter Oberparleiter  * for filename skewing caused by the mod-versioning mechanism.
3542521f2c2SPeter Oberparleiter  */
3552521f2c2SPeter Oberparleiter static const char *deskew(const char *basename)
3562521f2c2SPeter Oberparleiter {
3572521f2c2SPeter Oberparleiter 	if (strncmp(basename, SKEW_PREFIX, sizeof(SKEW_PREFIX) - 1) == 0)
3582521f2c2SPeter Oberparleiter 		return basename + sizeof(SKEW_PREFIX) - 1;
3592521f2c2SPeter Oberparleiter 	return basename;
3602521f2c2SPeter Oberparleiter }
3612521f2c2SPeter Oberparleiter 
3622521f2c2SPeter Oberparleiter /*
3632521f2c2SPeter Oberparleiter  * Create links to additional files (usually .c and .gcno files) which the
3642521f2c2SPeter Oberparleiter  * gcov tool expects to find in the same directory as the gcov data file.
3652521f2c2SPeter Oberparleiter  */
3662521f2c2SPeter Oberparleiter static void add_links(struct gcov_node *node, struct dentry *parent)
3672521f2c2SPeter Oberparleiter {
3681931d433SAndy Shevchenko 	const char *basename;
3692521f2c2SPeter Oberparleiter 	char *target;
3702521f2c2SPeter Oberparleiter 	int num;
3712521f2c2SPeter Oberparleiter 	int i;
3722521f2c2SPeter Oberparleiter 
3732521f2c2SPeter Oberparleiter 	for (num = 0; gcov_link[num].ext; num++)
3742521f2c2SPeter Oberparleiter 		/* Nothing. */;
3752521f2c2SPeter Oberparleiter 	node->links = kcalloc(num, sizeof(struct dentry *), GFP_KERNEL);
3762521f2c2SPeter Oberparleiter 	if (!node->links)
3772521f2c2SPeter Oberparleiter 		return;
3782521f2c2SPeter Oberparleiter 	for (i = 0; i < num; i++) {
3798cbce376SFrantisek Hrbata 		target = get_link_target(
3808cbce376SFrantisek Hrbata 				gcov_info_filename(get_node_info(node)),
3812521f2c2SPeter Oberparleiter 				&gcov_link[i]);
3822521f2c2SPeter Oberparleiter 		if (!target)
3832521f2c2SPeter Oberparleiter 			goto out_err;
3841931d433SAndy Shevchenko 		basename = kbasename(target);
3851931d433SAndy Shevchenko 		if (basename == target)
3862521f2c2SPeter Oberparleiter 			goto out_err;
3872521f2c2SPeter Oberparleiter 		node->links[i] = debugfs_create_symlink(deskew(basename),
3882521f2c2SPeter Oberparleiter 							parent,	target);
3892521f2c2SPeter Oberparleiter 		kfree(target);
3902521f2c2SPeter Oberparleiter 	}
3912521f2c2SPeter Oberparleiter 
3922521f2c2SPeter Oberparleiter 	return;
3932521f2c2SPeter Oberparleiter out_err:
3942521f2c2SPeter Oberparleiter 	kfree(target);
3952521f2c2SPeter Oberparleiter 	while (i-- > 0)
3962521f2c2SPeter Oberparleiter 		debugfs_remove(node->links[i]);
3972521f2c2SPeter Oberparleiter 	kfree(node->links);
3982521f2c2SPeter Oberparleiter 	node->links = NULL;
3992521f2c2SPeter Oberparleiter }
4002521f2c2SPeter Oberparleiter 
4012521f2c2SPeter Oberparleiter static const struct file_operations gcov_data_fops = {
4022521f2c2SPeter Oberparleiter 	.open		= gcov_seq_open,
4032521f2c2SPeter Oberparleiter 	.release	= gcov_seq_release,
4042521f2c2SPeter Oberparleiter 	.read		= seq_read,
4052521f2c2SPeter Oberparleiter 	.llseek		= seq_lseek,
4062521f2c2SPeter Oberparleiter 	.write		= gcov_seq_write,
4072521f2c2SPeter Oberparleiter };
4082521f2c2SPeter Oberparleiter 
4092521f2c2SPeter Oberparleiter /* Basic initialization of a new node. */
4102521f2c2SPeter Oberparleiter static void init_node(struct gcov_node *node, struct gcov_info *info,
4112521f2c2SPeter Oberparleiter 		      const char *name, struct gcov_node *parent)
4122521f2c2SPeter Oberparleiter {
4132521f2c2SPeter Oberparleiter 	INIT_LIST_HEAD(&node->list);
4142521f2c2SPeter Oberparleiter 	INIT_LIST_HEAD(&node->children);
4152521f2c2SPeter Oberparleiter 	INIT_LIST_HEAD(&node->all);
41685a0fdfdSPeter Oberparleiter 	if (node->loaded_info) {
41785a0fdfdSPeter Oberparleiter 		node->loaded_info[0] = info;
41885a0fdfdSPeter Oberparleiter 		node->num_loaded = 1;
41985a0fdfdSPeter Oberparleiter 	}
4202521f2c2SPeter Oberparleiter 	node->parent = parent;
4212521f2c2SPeter Oberparleiter 	if (name)
4222521f2c2SPeter Oberparleiter 		strcpy(node->name, name);
4232521f2c2SPeter Oberparleiter }
4242521f2c2SPeter Oberparleiter 
4252521f2c2SPeter Oberparleiter /*
4262521f2c2SPeter Oberparleiter  * Create a new node and associated debugfs entry. Needs to be called with
4272521f2c2SPeter Oberparleiter  * node_lock held.
4282521f2c2SPeter Oberparleiter  */
4292521f2c2SPeter Oberparleiter static struct gcov_node *new_node(struct gcov_node *parent,
4302521f2c2SPeter Oberparleiter 				  struct gcov_info *info, const char *name)
4312521f2c2SPeter Oberparleiter {
4322521f2c2SPeter Oberparleiter 	struct gcov_node *node;
4332521f2c2SPeter Oberparleiter 
4342521f2c2SPeter Oberparleiter 	node = kzalloc(sizeof(struct gcov_node) + strlen(name) + 1, GFP_KERNEL);
43585a0fdfdSPeter Oberparleiter 	if (!node)
43685a0fdfdSPeter Oberparleiter 		goto err_nomem;
43785a0fdfdSPeter Oberparleiter 	if (info) {
43885a0fdfdSPeter Oberparleiter 		node->loaded_info = kcalloc(1, sizeof(struct gcov_info *),
43985a0fdfdSPeter Oberparleiter 					   GFP_KERNEL);
44085a0fdfdSPeter Oberparleiter 		if (!node->loaded_info)
44185a0fdfdSPeter Oberparleiter 			goto err_nomem;
4422521f2c2SPeter Oberparleiter 	}
4432521f2c2SPeter Oberparleiter 	init_node(node, info, name, parent);
4442521f2c2SPeter Oberparleiter 	/* Differentiate between gcov data file nodes and directory nodes. */
4452521f2c2SPeter Oberparleiter 	if (info) {
4462521f2c2SPeter Oberparleiter 		node->dentry = debugfs_create_file(deskew(node->name), 0600,
4472521f2c2SPeter Oberparleiter 					parent->dentry, node, &gcov_data_fops);
4482521f2c2SPeter Oberparleiter 	} else
4492521f2c2SPeter Oberparleiter 		node->dentry = debugfs_create_dir(node->name, parent->dentry);
4502521f2c2SPeter Oberparleiter 	if (info)
4512521f2c2SPeter Oberparleiter 		add_links(node, parent->dentry);
4522521f2c2SPeter Oberparleiter 	list_add(&node->list, &parent->children);
4532521f2c2SPeter Oberparleiter 	list_add(&node->all, &all_head);
4542521f2c2SPeter Oberparleiter 
4552521f2c2SPeter Oberparleiter 	return node;
45685a0fdfdSPeter Oberparleiter 
45785a0fdfdSPeter Oberparleiter err_nomem:
45885a0fdfdSPeter Oberparleiter 	kfree(node);
459a5ebb875SAndrew Morton 	pr_warn("out of memory\n");
46085a0fdfdSPeter Oberparleiter 	return NULL;
4612521f2c2SPeter Oberparleiter }
4622521f2c2SPeter Oberparleiter 
4632521f2c2SPeter Oberparleiter /* Remove symbolic links associated with node. */
4642521f2c2SPeter Oberparleiter static void remove_links(struct gcov_node *node)
4652521f2c2SPeter Oberparleiter {
4662521f2c2SPeter Oberparleiter 	int i;
4672521f2c2SPeter Oberparleiter 
4682521f2c2SPeter Oberparleiter 	if (!node->links)
4692521f2c2SPeter Oberparleiter 		return;
4702521f2c2SPeter Oberparleiter 	for (i = 0; gcov_link[i].ext; i++)
4712521f2c2SPeter Oberparleiter 		debugfs_remove(node->links[i]);
4722521f2c2SPeter Oberparleiter 	kfree(node->links);
4732521f2c2SPeter Oberparleiter 	node->links = NULL;
4742521f2c2SPeter Oberparleiter }
4752521f2c2SPeter Oberparleiter 
4762521f2c2SPeter Oberparleiter /*
4772521f2c2SPeter Oberparleiter  * Remove node from all lists and debugfs and release associated resources.
4782521f2c2SPeter Oberparleiter  * Needs to be called with node_lock held.
4792521f2c2SPeter Oberparleiter  */
4802521f2c2SPeter Oberparleiter static void release_node(struct gcov_node *node)
4812521f2c2SPeter Oberparleiter {
4822521f2c2SPeter Oberparleiter 	list_del(&node->list);
4832521f2c2SPeter Oberparleiter 	list_del(&node->all);
4842521f2c2SPeter Oberparleiter 	debugfs_remove(node->dentry);
4852521f2c2SPeter Oberparleiter 	remove_links(node);
48685a0fdfdSPeter Oberparleiter 	kfree(node->loaded_info);
48785a0fdfdSPeter Oberparleiter 	if (node->unloaded_info)
48885a0fdfdSPeter Oberparleiter 		gcov_info_free(node->unloaded_info);
4892521f2c2SPeter Oberparleiter 	kfree(node);
4902521f2c2SPeter Oberparleiter }
4912521f2c2SPeter Oberparleiter 
4922521f2c2SPeter Oberparleiter /* Release node and empty parents. Needs to be called with node_lock held. */
4932521f2c2SPeter Oberparleiter static void remove_node(struct gcov_node *node)
4942521f2c2SPeter Oberparleiter {
4952521f2c2SPeter Oberparleiter 	struct gcov_node *parent;
4962521f2c2SPeter Oberparleiter 
4972521f2c2SPeter Oberparleiter 	while ((node != &root_node) && list_empty(&node->children)) {
4982521f2c2SPeter Oberparleiter 		parent = node->parent;
4992521f2c2SPeter Oberparleiter 		release_node(node);
5002521f2c2SPeter Oberparleiter 		node = parent;
5012521f2c2SPeter Oberparleiter 	}
5022521f2c2SPeter Oberparleiter }
5032521f2c2SPeter Oberparleiter 
5042521f2c2SPeter Oberparleiter /*
5052521f2c2SPeter Oberparleiter  * Find child node with given basename. Needs to be called with node_lock
5062521f2c2SPeter Oberparleiter  * held.
5072521f2c2SPeter Oberparleiter  */
5082521f2c2SPeter Oberparleiter static struct gcov_node *get_child_by_name(struct gcov_node *parent,
5092521f2c2SPeter Oberparleiter 					   const char *name)
5102521f2c2SPeter Oberparleiter {
5112521f2c2SPeter Oberparleiter 	struct gcov_node *node;
5122521f2c2SPeter Oberparleiter 
5132521f2c2SPeter Oberparleiter 	list_for_each_entry(node, &parent->children, list) {
5142521f2c2SPeter Oberparleiter 		if (strcmp(node->name, name) == 0)
5152521f2c2SPeter Oberparleiter 			return node;
5162521f2c2SPeter Oberparleiter 	}
5172521f2c2SPeter Oberparleiter 
5182521f2c2SPeter Oberparleiter 	return NULL;
5192521f2c2SPeter Oberparleiter }
5202521f2c2SPeter Oberparleiter 
5212521f2c2SPeter Oberparleiter /*
5222521f2c2SPeter Oberparleiter  * write() implementation for reset file. Reset all profiling data to zero
52385a0fdfdSPeter Oberparleiter  * and remove nodes for which all associated object files are unloaded.
5242521f2c2SPeter Oberparleiter  */
5252521f2c2SPeter Oberparleiter static ssize_t reset_write(struct file *file, const char __user *addr,
5262521f2c2SPeter Oberparleiter 			   size_t len, loff_t *pos)
5272521f2c2SPeter Oberparleiter {
5282521f2c2SPeter Oberparleiter 	struct gcov_node *node;
5292521f2c2SPeter Oberparleiter 
5302521f2c2SPeter Oberparleiter 	mutex_lock(&node_lock);
5312521f2c2SPeter Oberparleiter restart:
5322521f2c2SPeter Oberparleiter 	list_for_each_entry(node, &all_head, all) {
53385a0fdfdSPeter Oberparleiter 		if (node->num_loaded > 0)
53485a0fdfdSPeter Oberparleiter 			reset_node(node);
5352521f2c2SPeter Oberparleiter 		else if (list_empty(&node->children)) {
5362521f2c2SPeter Oberparleiter 			remove_node(node);
5372521f2c2SPeter Oberparleiter 			/* Several nodes may have gone - restart loop. */
5382521f2c2SPeter Oberparleiter 			goto restart;
5392521f2c2SPeter Oberparleiter 		}
5402521f2c2SPeter Oberparleiter 	}
5412521f2c2SPeter Oberparleiter 	mutex_unlock(&node_lock);
5422521f2c2SPeter Oberparleiter 
5432521f2c2SPeter Oberparleiter 	return len;
5442521f2c2SPeter Oberparleiter }
5452521f2c2SPeter Oberparleiter 
5462521f2c2SPeter Oberparleiter /* read() implementation for reset file. Unused. */
5472521f2c2SPeter Oberparleiter static ssize_t reset_read(struct file *file, char __user *addr, size_t len,
5482521f2c2SPeter Oberparleiter 			  loff_t *pos)
5492521f2c2SPeter Oberparleiter {
5502521f2c2SPeter Oberparleiter 	/* Allow read operation so that a recursive copy won't fail. */
5512521f2c2SPeter Oberparleiter 	return 0;
5522521f2c2SPeter Oberparleiter }
5532521f2c2SPeter Oberparleiter 
5542521f2c2SPeter Oberparleiter static const struct file_operations gcov_reset_fops = {
5552521f2c2SPeter Oberparleiter 	.write	= reset_write,
5562521f2c2SPeter Oberparleiter 	.read	= reset_read,
5576038f373SArnd Bergmann 	.llseek = noop_llseek,
5582521f2c2SPeter Oberparleiter };
5592521f2c2SPeter Oberparleiter 
5602521f2c2SPeter Oberparleiter /*
5612521f2c2SPeter Oberparleiter  * Create a node for a given profiling data set and add it to all lists and
5622521f2c2SPeter Oberparleiter  * debugfs. Needs to be called with node_lock held.
5632521f2c2SPeter Oberparleiter  */
5642521f2c2SPeter Oberparleiter static void add_node(struct gcov_info *info)
5652521f2c2SPeter Oberparleiter {
5662521f2c2SPeter Oberparleiter 	char *filename;
5672521f2c2SPeter Oberparleiter 	char *curr;
5682521f2c2SPeter Oberparleiter 	char *next;
5692521f2c2SPeter Oberparleiter 	struct gcov_node *parent;
5702521f2c2SPeter Oberparleiter 	struct gcov_node *node;
5712521f2c2SPeter Oberparleiter 
5728cbce376SFrantisek Hrbata 	filename = kstrdup(gcov_info_filename(info), GFP_KERNEL);
5732521f2c2SPeter Oberparleiter 	if (!filename)
5742521f2c2SPeter Oberparleiter 		return;
5752521f2c2SPeter Oberparleiter 	parent = &root_node;
5762521f2c2SPeter Oberparleiter 	/* Create directory nodes along the path. */
5772521f2c2SPeter Oberparleiter 	for (curr = filename; (next = strchr(curr, '/')); curr = next + 1) {
5782521f2c2SPeter Oberparleiter 		if (curr == next)
5792521f2c2SPeter Oberparleiter 			continue;
5802521f2c2SPeter Oberparleiter 		*next = 0;
5812521f2c2SPeter Oberparleiter 		if (strcmp(curr, ".") == 0)
5822521f2c2SPeter Oberparleiter 			continue;
5832521f2c2SPeter Oberparleiter 		if (strcmp(curr, "..") == 0) {
5842521f2c2SPeter Oberparleiter 			if (!parent->parent)
5852521f2c2SPeter Oberparleiter 				goto err_remove;
5862521f2c2SPeter Oberparleiter 			parent = parent->parent;
5872521f2c2SPeter Oberparleiter 			continue;
5882521f2c2SPeter Oberparleiter 		}
5892521f2c2SPeter Oberparleiter 		node = get_child_by_name(parent, curr);
5902521f2c2SPeter Oberparleiter 		if (!node) {
5912521f2c2SPeter Oberparleiter 			node = new_node(parent, NULL, curr);
5922521f2c2SPeter Oberparleiter 			if (!node)
5932521f2c2SPeter Oberparleiter 				goto err_remove;
5942521f2c2SPeter Oberparleiter 		}
5952521f2c2SPeter Oberparleiter 		parent = node;
5962521f2c2SPeter Oberparleiter 	}
5972521f2c2SPeter Oberparleiter 	/* Create file node. */
5982521f2c2SPeter Oberparleiter 	node = new_node(parent, info, curr);
5992521f2c2SPeter Oberparleiter 	if (!node)
6002521f2c2SPeter Oberparleiter 		goto err_remove;
6012521f2c2SPeter Oberparleiter out:
6022521f2c2SPeter Oberparleiter 	kfree(filename);
6032521f2c2SPeter Oberparleiter 	return;
6042521f2c2SPeter Oberparleiter 
6052521f2c2SPeter Oberparleiter err_remove:
6062521f2c2SPeter Oberparleiter 	remove_node(parent);
6072521f2c2SPeter Oberparleiter 	goto out;
6082521f2c2SPeter Oberparleiter }
6092521f2c2SPeter Oberparleiter 
6102521f2c2SPeter Oberparleiter /*
61185a0fdfdSPeter Oberparleiter  * Associate a profiling data set with an existing node. Needs to be called
61285a0fdfdSPeter Oberparleiter  * with node_lock held.
6132521f2c2SPeter Oberparleiter  */
61485a0fdfdSPeter Oberparleiter static void add_info(struct gcov_node *node, struct gcov_info *info)
6152521f2c2SPeter Oberparleiter {
61685a0fdfdSPeter Oberparleiter 	struct gcov_info **loaded_info;
61785a0fdfdSPeter Oberparleiter 	int num = node->num_loaded;
6182521f2c2SPeter Oberparleiter 
61985a0fdfdSPeter Oberparleiter 	/*
62085a0fdfdSPeter Oberparleiter 	 * Prepare new array. This is done first to simplify cleanup in
62185a0fdfdSPeter Oberparleiter 	 * case the new data set is incompatible, the node only contains
62285a0fdfdSPeter Oberparleiter 	 * unloaded data sets and there's not enough memory for the array.
62385a0fdfdSPeter Oberparleiter 	 */
62485a0fdfdSPeter Oberparleiter 	loaded_info = kcalloc(num + 1, sizeof(struct gcov_info *), GFP_KERNEL);
62585a0fdfdSPeter Oberparleiter 	if (!loaded_info) {
626a5ebb875SAndrew Morton 		pr_warn("could not add '%s' (out of memory)\n",
6278cbce376SFrantisek Hrbata 			gcov_info_filename(info));
62885a0fdfdSPeter Oberparleiter 		return;
62985a0fdfdSPeter Oberparleiter 	}
63085a0fdfdSPeter Oberparleiter 	memcpy(loaded_info, node->loaded_info,
63185a0fdfdSPeter Oberparleiter 	       num * sizeof(struct gcov_info *));
63285a0fdfdSPeter Oberparleiter 	loaded_info[num] = info;
63385a0fdfdSPeter Oberparleiter 	/* Check if the new data set is compatible. */
63485a0fdfdSPeter Oberparleiter 	if (num == 0) {
63585a0fdfdSPeter Oberparleiter 		/*
63685a0fdfdSPeter Oberparleiter 		 * A module was unloaded, modified and reloaded. The new
63785a0fdfdSPeter Oberparleiter 		 * data set replaces the copy of the last one.
63885a0fdfdSPeter Oberparleiter 		 */
63985a0fdfdSPeter Oberparleiter 		if (!gcov_info_is_compatible(node->unloaded_info, info)) {
640a5ebb875SAndrew Morton 			pr_warn("discarding saved data for %s "
6418cbce376SFrantisek Hrbata 				"(incompatible version)\n",
6428cbce376SFrantisek Hrbata 				gcov_info_filename(info));
64385a0fdfdSPeter Oberparleiter 			gcov_info_free(node->unloaded_info);
64485a0fdfdSPeter Oberparleiter 			node->unloaded_info = NULL;
64585a0fdfdSPeter Oberparleiter 		}
64685a0fdfdSPeter Oberparleiter 	} else {
64785a0fdfdSPeter Oberparleiter 		/*
64885a0fdfdSPeter Oberparleiter 		 * Two different versions of the same object file are loaded.
64985a0fdfdSPeter Oberparleiter 		 * The initial one takes precedence.
65085a0fdfdSPeter Oberparleiter 		 */
65185a0fdfdSPeter Oberparleiter 		if (!gcov_info_is_compatible(node->loaded_info[0], info)) {
652a5ebb875SAndrew Morton 			pr_warn("could not add '%s' (incompatible "
6538cbce376SFrantisek Hrbata 				"version)\n", gcov_info_filename(info));
65485a0fdfdSPeter Oberparleiter 			kfree(loaded_info);
65585a0fdfdSPeter Oberparleiter 			return;
65685a0fdfdSPeter Oberparleiter 		}
65785a0fdfdSPeter Oberparleiter 	}
65885a0fdfdSPeter Oberparleiter 	/* Overwrite previous array. */
65985a0fdfdSPeter Oberparleiter 	kfree(node->loaded_info);
66085a0fdfdSPeter Oberparleiter 	node->loaded_info = loaded_info;
66185a0fdfdSPeter Oberparleiter 	node->num_loaded = num + 1;
6622521f2c2SPeter Oberparleiter }
6632521f2c2SPeter Oberparleiter 
6642521f2c2SPeter Oberparleiter /*
66585a0fdfdSPeter Oberparleiter  * Return the index of a profiling data set associated with a node.
6662521f2c2SPeter Oberparleiter  */
66785a0fdfdSPeter Oberparleiter static int get_info_index(struct gcov_node *node, struct gcov_info *info)
6682521f2c2SPeter Oberparleiter {
66985a0fdfdSPeter Oberparleiter 	int i;
67085a0fdfdSPeter Oberparleiter 
67185a0fdfdSPeter Oberparleiter 	for (i = 0; i < node->num_loaded; i++) {
67285a0fdfdSPeter Oberparleiter 		if (node->loaded_info[i] == info)
67385a0fdfdSPeter Oberparleiter 			return i;
6742521f2c2SPeter Oberparleiter 	}
67585a0fdfdSPeter Oberparleiter 	return -ENOENT;
67685a0fdfdSPeter Oberparleiter }
67785a0fdfdSPeter Oberparleiter 
67885a0fdfdSPeter Oberparleiter /*
67985a0fdfdSPeter Oberparleiter  * Save the data of a profiling data set which is being unloaded.
68085a0fdfdSPeter Oberparleiter  */
68185a0fdfdSPeter Oberparleiter static void save_info(struct gcov_node *node, struct gcov_info *info)
68285a0fdfdSPeter Oberparleiter {
68385a0fdfdSPeter Oberparleiter 	if (node->unloaded_info)
68485a0fdfdSPeter Oberparleiter 		gcov_info_add(node->unloaded_info, info);
68585a0fdfdSPeter Oberparleiter 	else {
68685a0fdfdSPeter Oberparleiter 		node->unloaded_info = gcov_info_dup(info);
68785a0fdfdSPeter Oberparleiter 		if (!node->unloaded_info) {
688a5ebb875SAndrew Morton 			pr_warn("could not save data for '%s' "
6898cbce376SFrantisek Hrbata 				"(out of memory)\n",
6908cbce376SFrantisek Hrbata 				gcov_info_filename(info));
69185a0fdfdSPeter Oberparleiter 		}
69285a0fdfdSPeter Oberparleiter 	}
69385a0fdfdSPeter Oberparleiter }
69485a0fdfdSPeter Oberparleiter 
69585a0fdfdSPeter Oberparleiter /*
69685a0fdfdSPeter Oberparleiter  * Disassociate a profiling data set from a node. Needs to be called with
69785a0fdfdSPeter Oberparleiter  * node_lock held.
69885a0fdfdSPeter Oberparleiter  */
69985a0fdfdSPeter Oberparleiter static void remove_info(struct gcov_node *node, struct gcov_info *info)
70085a0fdfdSPeter Oberparleiter {
70185a0fdfdSPeter Oberparleiter 	int i;
70285a0fdfdSPeter Oberparleiter 
70385a0fdfdSPeter Oberparleiter 	i = get_info_index(node, info);
70485a0fdfdSPeter Oberparleiter 	if (i < 0) {
705a5ebb875SAndrew Morton 		pr_warn("could not remove '%s' (not found)\n",
7068cbce376SFrantisek Hrbata 			gcov_info_filename(info));
70785a0fdfdSPeter Oberparleiter 		return;
70885a0fdfdSPeter Oberparleiter 	}
70985a0fdfdSPeter Oberparleiter 	if (gcov_persist)
71085a0fdfdSPeter Oberparleiter 		save_info(node, info);
71185a0fdfdSPeter Oberparleiter 	/* Shrink array. */
71285a0fdfdSPeter Oberparleiter 	node->loaded_info[i] = node->loaded_info[node->num_loaded - 1];
71385a0fdfdSPeter Oberparleiter 	node->num_loaded--;
71485a0fdfdSPeter Oberparleiter 	if (node->num_loaded > 0)
71585a0fdfdSPeter Oberparleiter 		return;
71685a0fdfdSPeter Oberparleiter 	/* Last loaded data set was removed. */
71785a0fdfdSPeter Oberparleiter 	kfree(node->loaded_info);
71885a0fdfdSPeter Oberparleiter 	node->loaded_info = NULL;
71985a0fdfdSPeter Oberparleiter 	node->num_loaded = 0;
72085a0fdfdSPeter Oberparleiter 	if (!node->unloaded_info)
72185a0fdfdSPeter Oberparleiter 		remove_node(node);
7222521f2c2SPeter Oberparleiter }
7232521f2c2SPeter Oberparleiter 
7242521f2c2SPeter Oberparleiter /*
7252521f2c2SPeter Oberparleiter  * Callback to create/remove profiling files when code compiled with
7262521f2c2SPeter Oberparleiter  * -fprofile-arcs is loaded/unloaded.
7272521f2c2SPeter Oberparleiter  */
7282521f2c2SPeter Oberparleiter void gcov_event(enum gcov_action action, struct gcov_info *info)
7292521f2c2SPeter Oberparleiter {
7302521f2c2SPeter Oberparleiter 	struct gcov_node *node;
7312521f2c2SPeter Oberparleiter 
7322521f2c2SPeter Oberparleiter 	mutex_lock(&node_lock);
7338cbce376SFrantisek Hrbata 	node = get_node_by_name(gcov_info_filename(info));
7342521f2c2SPeter Oberparleiter 	switch (action) {
7352521f2c2SPeter Oberparleiter 	case GCOV_ADD:
73685a0fdfdSPeter Oberparleiter 		if (node)
73785a0fdfdSPeter Oberparleiter 			add_info(node, info);
73885a0fdfdSPeter Oberparleiter 		else
7392521f2c2SPeter Oberparleiter 			add_node(info);
7402521f2c2SPeter Oberparleiter 		break;
7412521f2c2SPeter Oberparleiter 	case GCOV_REMOVE:
74285a0fdfdSPeter Oberparleiter 		if (node)
74385a0fdfdSPeter Oberparleiter 			remove_info(node, info);
74485a0fdfdSPeter Oberparleiter 		else {
745a5ebb875SAndrew Morton 			pr_warn("could not remove '%s' (not found)\n",
7468cbce376SFrantisek Hrbata 				gcov_info_filename(info));
7472521f2c2SPeter Oberparleiter 		}
7482521f2c2SPeter Oberparleiter 		break;
7492521f2c2SPeter Oberparleiter 	}
7502521f2c2SPeter Oberparleiter 	mutex_unlock(&node_lock);
7512521f2c2SPeter Oberparleiter }
7522521f2c2SPeter Oberparleiter 
7532521f2c2SPeter Oberparleiter /* Create debugfs entries. */
7542521f2c2SPeter Oberparleiter static __init int gcov_fs_init(void)
7552521f2c2SPeter Oberparleiter {
7562521f2c2SPeter Oberparleiter 	init_node(&root_node, NULL, NULL, NULL);
7572521f2c2SPeter Oberparleiter 	/*
7582521f2c2SPeter Oberparleiter 	 * /sys/kernel/debug/gcov will be parent for the reset control file
7592521f2c2SPeter Oberparleiter 	 * and all profiling files.
7602521f2c2SPeter Oberparleiter 	 */
7612521f2c2SPeter Oberparleiter 	root_node.dentry = debugfs_create_dir("gcov", NULL);
7622521f2c2SPeter Oberparleiter 	/*
7632521f2c2SPeter Oberparleiter 	 * Create reset file which resets all profiling counts when written
7642521f2c2SPeter Oberparleiter 	 * to.
7652521f2c2SPeter Oberparleiter 	 */
7661c769fc4SGreg Kroah-Hartman 	debugfs_create_file("reset", 0600, root_node.dentry, NULL,
7671c769fc4SGreg Kroah-Hartman 			    &gcov_reset_fops);
7682521f2c2SPeter Oberparleiter 	/* Replay previous events to get our fs hierarchy up-to-date. */
7692521f2c2SPeter Oberparleiter 	gcov_enable_events();
7702521f2c2SPeter Oberparleiter 	return 0;
7712521f2c2SPeter Oberparleiter }
7722521f2c2SPeter Oberparleiter device_initcall(gcov_fs_init);
773