xref: /openbmc/linux/kernel/gcov/fs.c (revision b2441318)
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;
612521f2c2SPeter Oberparleiter 	char name[0];
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 struct dentry *reset_dentry;
682521f2c2SPeter Oberparleiter static LIST_HEAD(all_head);
692521f2c2SPeter Oberparleiter static DEFINE_MUTEX(node_lock);
702521f2c2SPeter Oberparleiter 
712521f2c2SPeter Oberparleiter /* If non-zero, keep copies of profiling data for unloaded modules. */
722521f2c2SPeter Oberparleiter static int gcov_persist = 1;
732521f2c2SPeter Oberparleiter 
742521f2c2SPeter Oberparleiter static int __init gcov_persist_setup(char *str)
752521f2c2SPeter Oberparleiter {
762521f2c2SPeter Oberparleiter 	unsigned long val;
772521f2c2SPeter Oberparleiter 
786072ddc8SJingoo Han 	if (kstrtoul(str, 0, &val)) {
79a5ebb875SAndrew Morton 		pr_warn("invalid gcov_persist parameter '%s'\n", str);
802521f2c2SPeter Oberparleiter 		return 0;
812521f2c2SPeter Oberparleiter 	}
822521f2c2SPeter Oberparleiter 	gcov_persist = val;
832521f2c2SPeter Oberparleiter 	pr_info("setting gcov_persist to %d\n", gcov_persist);
842521f2c2SPeter Oberparleiter 
852521f2c2SPeter Oberparleiter 	return 1;
862521f2c2SPeter Oberparleiter }
872521f2c2SPeter Oberparleiter __setup("gcov_persist=", gcov_persist_setup);
882521f2c2SPeter Oberparleiter 
892521f2c2SPeter Oberparleiter /*
902521f2c2SPeter Oberparleiter  * seq_file.start() implementation for gcov data files. Note that the
912521f2c2SPeter Oberparleiter  * gcov_iterator interface is designed to be more restrictive than seq_file
922521f2c2SPeter Oberparleiter  * (no start from arbitrary position, etc.), to simplify the iterator
932521f2c2SPeter Oberparleiter  * implementation.
942521f2c2SPeter Oberparleiter  */
952521f2c2SPeter Oberparleiter static void *gcov_seq_start(struct seq_file *seq, loff_t *pos)
962521f2c2SPeter Oberparleiter {
972521f2c2SPeter Oberparleiter 	loff_t i;
982521f2c2SPeter Oberparleiter 
992521f2c2SPeter Oberparleiter 	gcov_iter_start(seq->private);
1002521f2c2SPeter Oberparleiter 	for (i = 0; i < *pos; i++) {
1012521f2c2SPeter Oberparleiter 		if (gcov_iter_next(seq->private))
1022521f2c2SPeter Oberparleiter 			return NULL;
1032521f2c2SPeter Oberparleiter 	}
1042521f2c2SPeter Oberparleiter 	return seq->private;
1052521f2c2SPeter Oberparleiter }
1062521f2c2SPeter Oberparleiter 
1072521f2c2SPeter Oberparleiter /* seq_file.next() implementation for gcov data files. */
1082521f2c2SPeter Oberparleiter static void *gcov_seq_next(struct seq_file *seq, void *data, loff_t *pos)
1092521f2c2SPeter Oberparleiter {
1102521f2c2SPeter Oberparleiter 	struct gcov_iterator *iter = data;
1112521f2c2SPeter Oberparleiter 
1122521f2c2SPeter Oberparleiter 	if (gcov_iter_next(iter))
1132521f2c2SPeter Oberparleiter 		return NULL;
1142521f2c2SPeter Oberparleiter 	(*pos)++;
1152521f2c2SPeter Oberparleiter 
1162521f2c2SPeter Oberparleiter 	return iter;
1172521f2c2SPeter Oberparleiter }
1182521f2c2SPeter Oberparleiter 
1192521f2c2SPeter Oberparleiter /* seq_file.show() implementation for gcov data files. */
1202521f2c2SPeter Oberparleiter static int gcov_seq_show(struct seq_file *seq, void *data)
1212521f2c2SPeter Oberparleiter {
1222521f2c2SPeter Oberparleiter 	struct gcov_iterator *iter = data;
1232521f2c2SPeter Oberparleiter 
1242521f2c2SPeter Oberparleiter 	if (gcov_iter_write(iter, seq))
1252521f2c2SPeter Oberparleiter 		return -EINVAL;
1262521f2c2SPeter Oberparleiter 	return 0;
1272521f2c2SPeter Oberparleiter }
1282521f2c2SPeter Oberparleiter 
1292521f2c2SPeter Oberparleiter static void gcov_seq_stop(struct seq_file *seq, void *data)
1302521f2c2SPeter Oberparleiter {
1312521f2c2SPeter Oberparleiter 	/* Unused. */
1322521f2c2SPeter Oberparleiter }
1332521f2c2SPeter Oberparleiter 
1342521f2c2SPeter Oberparleiter static const struct seq_operations gcov_seq_ops = {
1352521f2c2SPeter Oberparleiter 	.start	= gcov_seq_start,
1362521f2c2SPeter Oberparleiter 	.next	= gcov_seq_next,
1372521f2c2SPeter Oberparleiter 	.show	= gcov_seq_show,
1382521f2c2SPeter Oberparleiter 	.stop	= gcov_seq_stop,
1392521f2c2SPeter Oberparleiter };
1402521f2c2SPeter Oberparleiter 
1412521f2c2SPeter Oberparleiter /*
14285a0fdfdSPeter Oberparleiter  * Return a profiling data set associated with the given node. This is
14385a0fdfdSPeter Oberparleiter  * either a data set for a loaded object file or a data set copy in case
14485a0fdfdSPeter Oberparleiter  * all associated object files have been unloaded.
1452521f2c2SPeter Oberparleiter  */
1462521f2c2SPeter Oberparleiter static struct gcov_info *get_node_info(struct gcov_node *node)
1472521f2c2SPeter Oberparleiter {
14885a0fdfdSPeter Oberparleiter 	if (node->num_loaded > 0)
14985a0fdfdSPeter Oberparleiter 		return node->loaded_info[0];
1502521f2c2SPeter Oberparleiter 
15185a0fdfdSPeter Oberparleiter 	return node->unloaded_info;
15285a0fdfdSPeter Oberparleiter }
15385a0fdfdSPeter Oberparleiter 
15485a0fdfdSPeter Oberparleiter /*
15585a0fdfdSPeter Oberparleiter  * Return a newly allocated profiling data set which contains the sum of
15685a0fdfdSPeter Oberparleiter  * all profiling data associated with the given node.
15785a0fdfdSPeter Oberparleiter  */
15885a0fdfdSPeter Oberparleiter static struct gcov_info *get_accumulated_info(struct gcov_node *node)
15985a0fdfdSPeter Oberparleiter {
16085a0fdfdSPeter Oberparleiter 	struct gcov_info *info;
16185a0fdfdSPeter Oberparleiter 	int i = 0;
16285a0fdfdSPeter Oberparleiter 
16385a0fdfdSPeter Oberparleiter 	if (node->unloaded_info)
16485a0fdfdSPeter Oberparleiter 		info = gcov_info_dup(node->unloaded_info);
16585a0fdfdSPeter Oberparleiter 	else
16685a0fdfdSPeter Oberparleiter 		info = gcov_info_dup(node->loaded_info[i++]);
16785a0fdfdSPeter Oberparleiter 	if (!info)
16885a0fdfdSPeter Oberparleiter 		return NULL;
16985a0fdfdSPeter Oberparleiter 	for (; i < node->num_loaded; i++)
17085a0fdfdSPeter Oberparleiter 		gcov_info_add(info, node->loaded_info[i]);
17185a0fdfdSPeter Oberparleiter 
17285a0fdfdSPeter Oberparleiter 	return info;
1732521f2c2SPeter Oberparleiter }
1742521f2c2SPeter Oberparleiter 
1752521f2c2SPeter Oberparleiter /*
1762521f2c2SPeter Oberparleiter  * open() implementation for gcov data files. Create a copy of the profiling
1772521f2c2SPeter Oberparleiter  * data set and initialize the iterator and seq_file interface.
1782521f2c2SPeter Oberparleiter  */
1792521f2c2SPeter Oberparleiter static int gcov_seq_open(struct inode *inode, struct file *file)
1802521f2c2SPeter Oberparleiter {
1812521f2c2SPeter Oberparleiter 	struct gcov_node *node = inode->i_private;
1822521f2c2SPeter Oberparleiter 	struct gcov_iterator *iter;
1832521f2c2SPeter Oberparleiter 	struct seq_file *seq;
1842521f2c2SPeter Oberparleiter 	struct gcov_info *info;
1852521f2c2SPeter Oberparleiter 	int rc = -ENOMEM;
1862521f2c2SPeter Oberparleiter 
1872521f2c2SPeter Oberparleiter 	mutex_lock(&node_lock);
1882521f2c2SPeter Oberparleiter 	/*
1892521f2c2SPeter Oberparleiter 	 * Read from a profiling data copy to minimize reference tracking
19085a0fdfdSPeter Oberparleiter 	 * complexity and concurrent access and to keep accumulating multiple
19185a0fdfdSPeter Oberparleiter 	 * profiling data sets associated with one node simple.
1922521f2c2SPeter Oberparleiter 	 */
19385a0fdfdSPeter Oberparleiter 	info = get_accumulated_info(node);
1942521f2c2SPeter Oberparleiter 	if (!info)
1952521f2c2SPeter Oberparleiter 		goto out_unlock;
1962521f2c2SPeter Oberparleiter 	iter = gcov_iter_new(info);
1972521f2c2SPeter Oberparleiter 	if (!iter)
1982521f2c2SPeter Oberparleiter 		goto err_free_info;
1992521f2c2SPeter Oberparleiter 	rc = seq_open(file, &gcov_seq_ops);
2002521f2c2SPeter Oberparleiter 	if (rc)
2012521f2c2SPeter Oberparleiter 		goto err_free_iter_info;
2022521f2c2SPeter Oberparleiter 	seq = file->private_data;
2032521f2c2SPeter Oberparleiter 	seq->private = iter;
2042521f2c2SPeter Oberparleiter out_unlock:
2052521f2c2SPeter Oberparleiter 	mutex_unlock(&node_lock);
2062521f2c2SPeter Oberparleiter 	return rc;
2072521f2c2SPeter Oberparleiter 
2082521f2c2SPeter Oberparleiter err_free_iter_info:
2092521f2c2SPeter Oberparleiter 	gcov_iter_free(iter);
2102521f2c2SPeter Oberparleiter err_free_info:
2112521f2c2SPeter Oberparleiter 	gcov_info_free(info);
2122521f2c2SPeter Oberparleiter 	goto out_unlock;
2132521f2c2SPeter Oberparleiter }
2142521f2c2SPeter Oberparleiter 
2152521f2c2SPeter Oberparleiter /*
2162521f2c2SPeter Oberparleiter  * release() implementation for gcov data files. Release resources allocated
2172521f2c2SPeter Oberparleiter  * by open().
2182521f2c2SPeter Oberparleiter  */
2192521f2c2SPeter Oberparleiter static int gcov_seq_release(struct inode *inode, struct file *file)
2202521f2c2SPeter Oberparleiter {
2212521f2c2SPeter Oberparleiter 	struct gcov_iterator *iter;
2222521f2c2SPeter Oberparleiter 	struct gcov_info *info;
2232521f2c2SPeter Oberparleiter 	struct seq_file *seq;
2242521f2c2SPeter Oberparleiter 
2252521f2c2SPeter Oberparleiter 	seq = file->private_data;
2262521f2c2SPeter Oberparleiter 	iter = seq->private;
2272521f2c2SPeter Oberparleiter 	info = gcov_iter_get_info(iter);
2282521f2c2SPeter Oberparleiter 	gcov_iter_free(iter);
2292521f2c2SPeter Oberparleiter 	gcov_info_free(info);
2302521f2c2SPeter Oberparleiter 	seq_release(inode, file);
2312521f2c2SPeter Oberparleiter 
2322521f2c2SPeter Oberparleiter 	return 0;
2332521f2c2SPeter Oberparleiter }
2342521f2c2SPeter Oberparleiter 
2352521f2c2SPeter Oberparleiter /*
2362521f2c2SPeter Oberparleiter  * Find a node by the associated data file name. Needs to be called with
2372521f2c2SPeter Oberparleiter  * node_lock held.
2382521f2c2SPeter Oberparleiter  */
2392521f2c2SPeter Oberparleiter static struct gcov_node *get_node_by_name(const char *name)
2402521f2c2SPeter Oberparleiter {
2412521f2c2SPeter Oberparleiter 	struct gcov_node *node;
2422521f2c2SPeter Oberparleiter 	struct gcov_info *info;
2432521f2c2SPeter Oberparleiter 
2442521f2c2SPeter Oberparleiter 	list_for_each_entry(node, &all_head, all) {
2452521f2c2SPeter Oberparleiter 		info = get_node_info(node);
2468cbce376SFrantisek Hrbata 		if (info && (strcmp(gcov_info_filename(info), name) == 0))
2472521f2c2SPeter Oberparleiter 			return node;
2482521f2c2SPeter Oberparleiter 	}
2492521f2c2SPeter Oberparleiter 
2502521f2c2SPeter Oberparleiter 	return NULL;
2512521f2c2SPeter Oberparleiter }
2522521f2c2SPeter Oberparleiter 
25385a0fdfdSPeter Oberparleiter /*
25485a0fdfdSPeter Oberparleiter  * Reset all profiling data associated with the specified node.
25585a0fdfdSPeter Oberparleiter  */
25685a0fdfdSPeter Oberparleiter static void reset_node(struct gcov_node *node)
25785a0fdfdSPeter Oberparleiter {
25885a0fdfdSPeter Oberparleiter 	int i;
25985a0fdfdSPeter Oberparleiter 
26085a0fdfdSPeter Oberparleiter 	if (node->unloaded_info)
26185a0fdfdSPeter Oberparleiter 		gcov_info_reset(node->unloaded_info);
26285a0fdfdSPeter Oberparleiter 	for (i = 0; i < node->num_loaded; i++)
26385a0fdfdSPeter Oberparleiter 		gcov_info_reset(node->loaded_info[i]);
26485a0fdfdSPeter Oberparleiter }
26585a0fdfdSPeter Oberparleiter 
2662521f2c2SPeter Oberparleiter static void remove_node(struct gcov_node *node);
2672521f2c2SPeter Oberparleiter 
2682521f2c2SPeter Oberparleiter /*
2692521f2c2SPeter Oberparleiter  * write() implementation for gcov data files. Reset profiling data for the
27085a0fdfdSPeter Oberparleiter  * corresponding file. If all associated object files have been unloaded,
27185a0fdfdSPeter Oberparleiter  * remove the debug fs node as well.
2722521f2c2SPeter Oberparleiter  */
2732521f2c2SPeter Oberparleiter static ssize_t gcov_seq_write(struct file *file, const char __user *addr,
2742521f2c2SPeter Oberparleiter 			      size_t len, loff_t *pos)
2752521f2c2SPeter Oberparleiter {
2762521f2c2SPeter Oberparleiter 	struct seq_file *seq;
2772521f2c2SPeter Oberparleiter 	struct gcov_info *info;
2782521f2c2SPeter Oberparleiter 	struct gcov_node *node;
2792521f2c2SPeter Oberparleiter 
2802521f2c2SPeter Oberparleiter 	seq = file->private_data;
2812521f2c2SPeter Oberparleiter 	info = gcov_iter_get_info(seq->private);
2822521f2c2SPeter Oberparleiter 	mutex_lock(&node_lock);
2838cbce376SFrantisek Hrbata 	node = get_node_by_name(gcov_info_filename(info));
2842521f2c2SPeter Oberparleiter 	if (node) {
2852521f2c2SPeter Oberparleiter 		/* Reset counts or remove node for unloaded modules. */
28685a0fdfdSPeter Oberparleiter 		if (node->num_loaded == 0)
2872521f2c2SPeter Oberparleiter 			remove_node(node);
2882521f2c2SPeter Oberparleiter 		else
28985a0fdfdSPeter Oberparleiter 			reset_node(node);
2902521f2c2SPeter Oberparleiter 	}
2912521f2c2SPeter Oberparleiter 	/* Reset counts for open file. */
2922521f2c2SPeter Oberparleiter 	gcov_info_reset(info);
2932521f2c2SPeter Oberparleiter 	mutex_unlock(&node_lock);
2942521f2c2SPeter Oberparleiter 
2952521f2c2SPeter Oberparleiter 	return len;
2962521f2c2SPeter Oberparleiter }
2972521f2c2SPeter Oberparleiter 
2982521f2c2SPeter Oberparleiter /*
2992521f2c2SPeter Oberparleiter  * Given a string <path> representing a file path of format:
3002521f2c2SPeter Oberparleiter  *   path/to/file.gcda
3012521f2c2SPeter Oberparleiter  * construct and return a new string:
3022521f2c2SPeter Oberparleiter  *   <dir/>path/to/file.<ext>
3032521f2c2SPeter Oberparleiter  */
3042521f2c2SPeter Oberparleiter static char *link_target(const char *dir, const char *path, const char *ext)
3052521f2c2SPeter Oberparleiter {
3062521f2c2SPeter Oberparleiter 	char *target;
3072521f2c2SPeter Oberparleiter 	char *old_ext;
3082521f2c2SPeter Oberparleiter 	char *copy;
3092521f2c2SPeter Oberparleiter 
3102521f2c2SPeter Oberparleiter 	copy = kstrdup(path, GFP_KERNEL);
3112521f2c2SPeter Oberparleiter 	if (!copy)
3122521f2c2SPeter Oberparleiter 		return NULL;
3132521f2c2SPeter Oberparleiter 	old_ext = strrchr(copy, '.');
3142521f2c2SPeter Oberparleiter 	if (old_ext)
3152521f2c2SPeter Oberparleiter 		*old_ext = '\0';
3162521f2c2SPeter Oberparleiter 	if (dir)
3172521f2c2SPeter Oberparleiter 		target = kasprintf(GFP_KERNEL, "%s/%s.%s", dir, copy, ext);
3182521f2c2SPeter Oberparleiter 	else
3192521f2c2SPeter Oberparleiter 		target = kasprintf(GFP_KERNEL, "%s.%s", copy, ext);
3202521f2c2SPeter Oberparleiter 	kfree(copy);
3212521f2c2SPeter Oberparleiter 
3222521f2c2SPeter Oberparleiter 	return target;
3232521f2c2SPeter Oberparleiter }
3242521f2c2SPeter Oberparleiter 
3252521f2c2SPeter Oberparleiter /*
3262521f2c2SPeter Oberparleiter  * Construct a string representing the symbolic link target for the given
3272521f2c2SPeter Oberparleiter  * gcov data file name and link type. Depending on the link type and the
3282521f2c2SPeter Oberparleiter  * location of the data file, the link target can either point to a
3292521f2c2SPeter Oberparleiter  * subdirectory of srctree, objtree or in an external location.
3302521f2c2SPeter Oberparleiter  */
3312521f2c2SPeter Oberparleiter static char *get_link_target(const char *filename, const struct gcov_link *ext)
3322521f2c2SPeter Oberparleiter {
3332521f2c2SPeter Oberparleiter 	const char *rel;
3342521f2c2SPeter Oberparleiter 	char *result;
3352521f2c2SPeter Oberparleiter 
3362521f2c2SPeter Oberparleiter 	if (strncmp(filename, objtree, strlen(objtree)) == 0) {
3372521f2c2SPeter Oberparleiter 		rel = filename + strlen(objtree) + 1;
3382521f2c2SPeter Oberparleiter 		if (ext->dir == SRC_TREE)
3392521f2c2SPeter Oberparleiter 			result = link_target(srctree, rel, ext->ext);
3402521f2c2SPeter Oberparleiter 		else
3412521f2c2SPeter Oberparleiter 			result = link_target(objtree, rel, ext->ext);
3422521f2c2SPeter Oberparleiter 	} else {
3432521f2c2SPeter Oberparleiter 		/* External compilation. */
3442521f2c2SPeter Oberparleiter 		result = link_target(NULL, filename, ext->ext);
3452521f2c2SPeter Oberparleiter 	}
3462521f2c2SPeter Oberparleiter 
3472521f2c2SPeter Oberparleiter 	return result;
3482521f2c2SPeter Oberparleiter }
3492521f2c2SPeter Oberparleiter 
3502521f2c2SPeter Oberparleiter #define SKEW_PREFIX	".tmp_"
3512521f2c2SPeter Oberparleiter 
3522521f2c2SPeter Oberparleiter /*
3532521f2c2SPeter Oberparleiter  * For a filename .tmp_filename.ext return filename.ext. Needed to compensate
3542521f2c2SPeter Oberparleiter  * for filename skewing caused by the mod-versioning mechanism.
3552521f2c2SPeter Oberparleiter  */
3562521f2c2SPeter Oberparleiter static const char *deskew(const char *basename)
3572521f2c2SPeter Oberparleiter {
3582521f2c2SPeter Oberparleiter 	if (strncmp(basename, SKEW_PREFIX, sizeof(SKEW_PREFIX) - 1) == 0)
3592521f2c2SPeter Oberparleiter 		return basename + sizeof(SKEW_PREFIX) - 1;
3602521f2c2SPeter Oberparleiter 	return basename;
3612521f2c2SPeter Oberparleiter }
3622521f2c2SPeter Oberparleiter 
3632521f2c2SPeter Oberparleiter /*
3642521f2c2SPeter Oberparleiter  * Create links to additional files (usually .c and .gcno files) which the
3652521f2c2SPeter Oberparleiter  * gcov tool expects to find in the same directory as the gcov data file.
3662521f2c2SPeter Oberparleiter  */
3672521f2c2SPeter Oberparleiter static void add_links(struct gcov_node *node, struct dentry *parent)
3682521f2c2SPeter Oberparleiter {
3691931d433SAndy Shevchenko 	const char *basename;
3702521f2c2SPeter Oberparleiter 	char *target;
3712521f2c2SPeter Oberparleiter 	int num;
3722521f2c2SPeter Oberparleiter 	int i;
3732521f2c2SPeter Oberparleiter 
3742521f2c2SPeter Oberparleiter 	for (num = 0; gcov_link[num].ext; num++)
3752521f2c2SPeter Oberparleiter 		/* Nothing. */;
3762521f2c2SPeter Oberparleiter 	node->links = kcalloc(num, sizeof(struct dentry *), GFP_KERNEL);
3772521f2c2SPeter Oberparleiter 	if (!node->links)
3782521f2c2SPeter Oberparleiter 		return;
3792521f2c2SPeter Oberparleiter 	for (i = 0; i < num; i++) {
3808cbce376SFrantisek Hrbata 		target = get_link_target(
3818cbce376SFrantisek Hrbata 				gcov_info_filename(get_node_info(node)),
3822521f2c2SPeter Oberparleiter 				&gcov_link[i]);
3832521f2c2SPeter Oberparleiter 		if (!target)
3842521f2c2SPeter Oberparleiter 			goto out_err;
3851931d433SAndy Shevchenko 		basename = kbasename(target);
3861931d433SAndy Shevchenko 		if (basename == target)
3872521f2c2SPeter Oberparleiter 			goto out_err;
3882521f2c2SPeter Oberparleiter 		node->links[i] = debugfs_create_symlink(deskew(basename),
3892521f2c2SPeter Oberparleiter 							parent,	target);
3902521f2c2SPeter Oberparleiter 		if (!node->links[i])
3912521f2c2SPeter Oberparleiter 			goto out_err;
3922521f2c2SPeter Oberparleiter 		kfree(target);
3932521f2c2SPeter Oberparleiter 	}
3942521f2c2SPeter Oberparleiter 
3952521f2c2SPeter Oberparleiter 	return;
3962521f2c2SPeter Oberparleiter out_err:
3972521f2c2SPeter Oberparleiter 	kfree(target);
3982521f2c2SPeter Oberparleiter 	while (i-- > 0)
3992521f2c2SPeter Oberparleiter 		debugfs_remove(node->links[i]);
4002521f2c2SPeter Oberparleiter 	kfree(node->links);
4012521f2c2SPeter Oberparleiter 	node->links = NULL;
4022521f2c2SPeter Oberparleiter }
4032521f2c2SPeter Oberparleiter 
4042521f2c2SPeter Oberparleiter static const struct file_operations gcov_data_fops = {
4052521f2c2SPeter Oberparleiter 	.open		= gcov_seq_open,
4062521f2c2SPeter Oberparleiter 	.release	= gcov_seq_release,
4072521f2c2SPeter Oberparleiter 	.read		= seq_read,
4082521f2c2SPeter Oberparleiter 	.llseek		= seq_lseek,
4092521f2c2SPeter Oberparleiter 	.write		= gcov_seq_write,
4102521f2c2SPeter Oberparleiter };
4112521f2c2SPeter Oberparleiter 
4122521f2c2SPeter Oberparleiter /* Basic initialization of a new node. */
4132521f2c2SPeter Oberparleiter static void init_node(struct gcov_node *node, struct gcov_info *info,
4142521f2c2SPeter Oberparleiter 		      const char *name, struct gcov_node *parent)
4152521f2c2SPeter Oberparleiter {
4162521f2c2SPeter Oberparleiter 	INIT_LIST_HEAD(&node->list);
4172521f2c2SPeter Oberparleiter 	INIT_LIST_HEAD(&node->children);
4182521f2c2SPeter Oberparleiter 	INIT_LIST_HEAD(&node->all);
41985a0fdfdSPeter Oberparleiter 	if (node->loaded_info) {
42085a0fdfdSPeter Oberparleiter 		node->loaded_info[0] = info;
42185a0fdfdSPeter Oberparleiter 		node->num_loaded = 1;
42285a0fdfdSPeter Oberparleiter 	}
4232521f2c2SPeter Oberparleiter 	node->parent = parent;
4242521f2c2SPeter Oberparleiter 	if (name)
4252521f2c2SPeter Oberparleiter 		strcpy(node->name, name);
4262521f2c2SPeter Oberparleiter }
4272521f2c2SPeter Oberparleiter 
4282521f2c2SPeter Oberparleiter /*
4292521f2c2SPeter Oberparleiter  * Create a new node and associated debugfs entry. Needs to be called with
4302521f2c2SPeter Oberparleiter  * node_lock held.
4312521f2c2SPeter Oberparleiter  */
4322521f2c2SPeter Oberparleiter static struct gcov_node *new_node(struct gcov_node *parent,
4332521f2c2SPeter Oberparleiter 				  struct gcov_info *info, const char *name)
4342521f2c2SPeter Oberparleiter {
4352521f2c2SPeter Oberparleiter 	struct gcov_node *node;
4362521f2c2SPeter Oberparleiter 
4372521f2c2SPeter Oberparleiter 	node = kzalloc(sizeof(struct gcov_node) + strlen(name) + 1, GFP_KERNEL);
43885a0fdfdSPeter Oberparleiter 	if (!node)
43985a0fdfdSPeter Oberparleiter 		goto err_nomem;
44085a0fdfdSPeter Oberparleiter 	if (info) {
44185a0fdfdSPeter Oberparleiter 		node->loaded_info = kcalloc(1, sizeof(struct gcov_info *),
44285a0fdfdSPeter Oberparleiter 					   GFP_KERNEL);
44385a0fdfdSPeter Oberparleiter 		if (!node->loaded_info)
44485a0fdfdSPeter Oberparleiter 			goto err_nomem;
4452521f2c2SPeter Oberparleiter 	}
4462521f2c2SPeter Oberparleiter 	init_node(node, info, name, parent);
4472521f2c2SPeter Oberparleiter 	/* Differentiate between gcov data file nodes and directory nodes. */
4482521f2c2SPeter Oberparleiter 	if (info) {
4492521f2c2SPeter Oberparleiter 		node->dentry = debugfs_create_file(deskew(node->name), 0600,
4502521f2c2SPeter Oberparleiter 					parent->dentry, node, &gcov_data_fops);
4512521f2c2SPeter Oberparleiter 	} else
4522521f2c2SPeter Oberparleiter 		node->dentry = debugfs_create_dir(node->name, parent->dentry);
4532521f2c2SPeter Oberparleiter 	if (!node->dentry) {
454a5ebb875SAndrew Morton 		pr_warn("could not create file\n");
4552521f2c2SPeter Oberparleiter 		kfree(node);
4562521f2c2SPeter Oberparleiter 		return NULL;
4572521f2c2SPeter Oberparleiter 	}
4582521f2c2SPeter Oberparleiter 	if (info)
4592521f2c2SPeter Oberparleiter 		add_links(node, parent->dentry);
4602521f2c2SPeter Oberparleiter 	list_add(&node->list, &parent->children);
4612521f2c2SPeter Oberparleiter 	list_add(&node->all, &all_head);
4622521f2c2SPeter Oberparleiter 
4632521f2c2SPeter Oberparleiter 	return node;
46485a0fdfdSPeter Oberparleiter 
46585a0fdfdSPeter Oberparleiter err_nomem:
46685a0fdfdSPeter Oberparleiter 	kfree(node);
467a5ebb875SAndrew Morton 	pr_warn("out of memory\n");
46885a0fdfdSPeter Oberparleiter 	return NULL;
4692521f2c2SPeter Oberparleiter }
4702521f2c2SPeter Oberparleiter 
4712521f2c2SPeter Oberparleiter /* Remove symbolic links associated with node. */
4722521f2c2SPeter Oberparleiter static void remove_links(struct gcov_node *node)
4732521f2c2SPeter Oberparleiter {
4742521f2c2SPeter Oberparleiter 	int i;
4752521f2c2SPeter Oberparleiter 
4762521f2c2SPeter Oberparleiter 	if (!node->links)
4772521f2c2SPeter Oberparleiter 		return;
4782521f2c2SPeter Oberparleiter 	for (i = 0; gcov_link[i].ext; i++)
4792521f2c2SPeter Oberparleiter 		debugfs_remove(node->links[i]);
4802521f2c2SPeter Oberparleiter 	kfree(node->links);
4812521f2c2SPeter Oberparleiter 	node->links = NULL;
4822521f2c2SPeter Oberparleiter }
4832521f2c2SPeter Oberparleiter 
4842521f2c2SPeter Oberparleiter /*
4852521f2c2SPeter Oberparleiter  * Remove node from all lists and debugfs and release associated resources.
4862521f2c2SPeter Oberparleiter  * Needs to be called with node_lock held.
4872521f2c2SPeter Oberparleiter  */
4882521f2c2SPeter Oberparleiter static void release_node(struct gcov_node *node)
4892521f2c2SPeter Oberparleiter {
4902521f2c2SPeter Oberparleiter 	list_del(&node->list);
4912521f2c2SPeter Oberparleiter 	list_del(&node->all);
4922521f2c2SPeter Oberparleiter 	debugfs_remove(node->dentry);
4932521f2c2SPeter Oberparleiter 	remove_links(node);
49485a0fdfdSPeter Oberparleiter 	kfree(node->loaded_info);
49585a0fdfdSPeter Oberparleiter 	if (node->unloaded_info)
49685a0fdfdSPeter Oberparleiter 		gcov_info_free(node->unloaded_info);
4972521f2c2SPeter Oberparleiter 	kfree(node);
4982521f2c2SPeter Oberparleiter }
4992521f2c2SPeter Oberparleiter 
5002521f2c2SPeter Oberparleiter /* Release node and empty parents. Needs to be called with node_lock held. */
5012521f2c2SPeter Oberparleiter static void remove_node(struct gcov_node *node)
5022521f2c2SPeter Oberparleiter {
5032521f2c2SPeter Oberparleiter 	struct gcov_node *parent;
5042521f2c2SPeter Oberparleiter 
5052521f2c2SPeter Oberparleiter 	while ((node != &root_node) && list_empty(&node->children)) {
5062521f2c2SPeter Oberparleiter 		parent = node->parent;
5072521f2c2SPeter Oberparleiter 		release_node(node);
5082521f2c2SPeter Oberparleiter 		node = parent;
5092521f2c2SPeter Oberparleiter 	}
5102521f2c2SPeter Oberparleiter }
5112521f2c2SPeter Oberparleiter 
5122521f2c2SPeter Oberparleiter /*
5132521f2c2SPeter Oberparleiter  * Find child node with given basename. Needs to be called with node_lock
5142521f2c2SPeter Oberparleiter  * held.
5152521f2c2SPeter Oberparleiter  */
5162521f2c2SPeter Oberparleiter static struct gcov_node *get_child_by_name(struct gcov_node *parent,
5172521f2c2SPeter Oberparleiter 					   const char *name)
5182521f2c2SPeter Oberparleiter {
5192521f2c2SPeter Oberparleiter 	struct gcov_node *node;
5202521f2c2SPeter Oberparleiter 
5212521f2c2SPeter Oberparleiter 	list_for_each_entry(node, &parent->children, list) {
5222521f2c2SPeter Oberparleiter 		if (strcmp(node->name, name) == 0)
5232521f2c2SPeter Oberparleiter 			return node;
5242521f2c2SPeter Oberparleiter 	}
5252521f2c2SPeter Oberparleiter 
5262521f2c2SPeter Oberparleiter 	return NULL;
5272521f2c2SPeter Oberparleiter }
5282521f2c2SPeter Oberparleiter 
5292521f2c2SPeter Oberparleiter /*
5302521f2c2SPeter Oberparleiter  * write() implementation for reset file. Reset all profiling data to zero
53185a0fdfdSPeter Oberparleiter  * and remove nodes for which all associated object files are unloaded.
5322521f2c2SPeter Oberparleiter  */
5332521f2c2SPeter Oberparleiter static ssize_t reset_write(struct file *file, const char __user *addr,
5342521f2c2SPeter Oberparleiter 			   size_t len, loff_t *pos)
5352521f2c2SPeter Oberparleiter {
5362521f2c2SPeter Oberparleiter 	struct gcov_node *node;
5372521f2c2SPeter Oberparleiter 
5382521f2c2SPeter Oberparleiter 	mutex_lock(&node_lock);
5392521f2c2SPeter Oberparleiter restart:
5402521f2c2SPeter Oberparleiter 	list_for_each_entry(node, &all_head, all) {
54185a0fdfdSPeter Oberparleiter 		if (node->num_loaded > 0)
54285a0fdfdSPeter Oberparleiter 			reset_node(node);
5432521f2c2SPeter Oberparleiter 		else if (list_empty(&node->children)) {
5442521f2c2SPeter Oberparleiter 			remove_node(node);
5452521f2c2SPeter Oberparleiter 			/* Several nodes may have gone - restart loop. */
5462521f2c2SPeter Oberparleiter 			goto restart;
5472521f2c2SPeter Oberparleiter 		}
5482521f2c2SPeter Oberparleiter 	}
5492521f2c2SPeter Oberparleiter 	mutex_unlock(&node_lock);
5502521f2c2SPeter Oberparleiter 
5512521f2c2SPeter Oberparleiter 	return len;
5522521f2c2SPeter Oberparleiter }
5532521f2c2SPeter Oberparleiter 
5542521f2c2SPeter Oberparleiter /* read() implementation for reset file. Unused. */
5552521f2c2SPeter Oberparleiter static ssize_t reset_read(struct file *file, char __user *addr, size_t len,
5562521f2c2SPeter Oberparleiter 			  loff_t *pos)
5572521f2c2SPeter Oberparleiter {
5582521f2c2SPeter Oberparleiter 	/* Allow read operation so that a recursive copy won't fail. */
5592521f2c2SPeter Oberparleiter 	return 0;
5602521f2c2SPeter Oberparleiter }
5612521f2c2SPeter Oberparleiter 
5622521f2c2SPeter Oberparleiter static const struct file_operations gcov_reset_fops = {
5632521f2c2SPeter Oberparleiter 	.write	= reset_write,
5642521f2c2SPeter Oberparleiter 	.read	= reset_read,
5656038f373SArnd Bergmann 	.llseek = noop_llseek,
5662521f2c2SPeter Oberparleiter };
5672521f2c2SPeter Oberparleiter 
5682521f2c2SPeter Oberparleiter /*
5692521f2c2SPeter Oberparleiter  * Create a node for a given profiling data set and add it to all lists and
5702521f2c2SPeter Oberparleiter  * debugfs. Needs to be called with node_lock held.
5712521f2c2SPeter Oberparleiter  */
5722521f2c2SPeter Oberparleiter static void add_node(struct gcov_info *info)
5732521f2c2SPeter Oberparleiter {
5742521f2c2SPeter Oberparleiter 	char *filename;
5752521f2c2SPeter Oberparleiter 	char *curr;
5762521f2c2SPeter Oberparleiter 	char *next;
5772521f2c2SPeter Oberparleiter 	struct gcov_node *parent;
5782521f2c2SPeter Oberparleiter 	struct gcov_node *node;
5792521f2c2SPeter Oberparleiter 
5808cbce376SFrantisek Hrbata 	filename = kstrdup(gcov_info_filename(info), GFP_KERNEL);
5812521f2c2SPeter Oberparleiter 	if (!filename)
5822521f2c2SPeter Oberparleiter 		return;
5832521f2c2SPeter Oberparleiter 	parent = &root_node;
5842521f2c2SPeter Oberparleiter 	/* Create directory nodes along the path. */
5852521f2c2SPeter Oberparleiter 	for (curr = filename; (next = strchr(curr, '/')); curr = next + 1) {
5862521f2c2SPeter Oberparleiter 		if (curr == next)
5872521f2c2SPeter Oberparleiter 			continue;
5882521f2c2SPeter Oberparleiter 		*next = 0;
5892521f2c2SPeter Oberparleiter 		if (strcmp(curr, ".") == 0)
5902521f2c2SPeter Oberparleiter 			continue;
5912521f2c2SPeter Oberparleiter 		if (strcmp(curr, "..") == 0) {
5922521f2c2SPeter Oberparleiter 			if (!parent->parent)
5932521f2c2SPeter Oberparleiter 				goto err_remove;
5942521f2c2SPeter Oberparleiter 			parent = parent->parent;
5952521f2c2SPeter Oberparleiter 			continue;
5962521f2c2SPeter Oberparleiter 		}
5972521f2c2SPeter Oberparleiter 		node = get_child_by_name(parent, curr);
5982521f2c2SPeter Oberparleiter 		if (!node) {
5992521f2c2SPeter Oberparleiter 			node = new_node(parent, NULL, curr);
6002521f2c2SPeter Oberparleiter 			if (!node)
6012521f2c2SPeter Oberparleiter 				goto err_remove;
6022521f2c2SPeter Oberparleiter 		}
6032521f2c2SPeter Oberparleiter 		parent = node;
6042521f2c2SPeter Oberparleiter 	}
6052521f2c2SPeter Oberparleiter 	/* Create file node. */
6062521f2c2SPeter Oberparleiter 	node = new_node(parent, info, curr);
6072521f2c2SPeter Oberparleiter 	if (!node)
6082521f2c2SPeter Oberparleiter 		goto err_remove;
6092521f2c2SPeter Oberparleiter out:
6102521f2c2SPeter Oberparleiter 	kfree(filename);
6112521f2c2SPeter Oberparleiter 	return;
6122521f2c2SPeter Oberparleiter 
6132521f2c2SPeter Oberparleiter err_remove:
6142521f2c2SPeter Oberparleiter 	remove_node(parent);
6152521f2c2SPeter Oberparleiter 	goto out;
6162521f2c2SPeter Oberparleiter }
6172521f2c2SPeter Oberparleiter 
6182521f2c2SPeter Oberparleiter /*
61985a0fdfdSPeter Oberparleiter  * Associate a profiling data set with an existing node. Needs to be called
62085a0fdfdSPeter Oberparleiter  * with node_lock held.
6212521f2c2SPeter Oberparleiter  */
62285a0fdfdSPeter Oberparleiter static void add_info(struct gcov_node *node, struct gcov_info *info)
6232521f2c2SPeter Oberparleiter {
62485a0fdfdSPeter Oberparleiter 	struct gcov_info **loaded_info;
62585a0fdfdSPeter Oberparleiter 	int num = node->num_loaded;
6262521f2c2SPeter Oberparleiter 
62785a0fdfdSPeter Oberparleiter 	/*
62885a0fdfdSPeter Oberparleiter 	 * Prepare new array. This is done first to simplify cleanup in
62985a0fdfdSPeter Oberparleiter 	 * case the new data set is incompatible, the node only contains
63085a0fdfdSPeter Oberparleiter 	 * unloaded data sets and there's not enough memory for the array.
63185a0fdfdSPeter Oberparleiter 	 */
63285a0fdfdSPeter Oberparleiter 	loaded_info = kcalloc(num + 1, sizeof(struct gcov_info *), GFP_KERNEL);
63385a0fdfdSPeter Oberparleiter 	if (!loaded_info) {
634a5ebb875SAndrew Morton 		pr_warn("could not add '%s' (out of memory)\n",
6358cbce376SFrantisek Hrbata 			gcov_info_filename(info));
63685a0fdfdSPeter Oberparleiter 		return;
63785a0fdfdSPeter Oberparleiter 	}
63885a0fdfdSPeter Oberparleiter 	memcpy(loaded_info, node->loaded_info,
63985a0fdfdSPeter Oberparleiter 	       num * sizeof(struct gcov_info *));
64085a0fdfdSPeter Oberparleiter 	loaded_info[num] = info;
64185a0fdfdSPeter Oberparleiter 	/* Check if the new data set is compatible. */
64285a0fdfdSPeter Oberparleiter 	if (num == 0) {
64385a0fdfdSPeter Oberparleiter 		/*
64485a0fdfdSPeter Oberparleiter 		 * A module was unloaded, modified and reloaded. The new
64585a0fdfdSPeter Oberparleiter 		 * data set replaces the copy of the last one.
64685a0fdfdSPeter Oberparleiter 		 */
64785a0fdfdSPeter Oberparleiter 		if (!gcov_info_is_compatible(node->unloaded_info, info)) {
648a5ebb875SAndrew Morton 			pr_warn("discarding saved data for %s "
6498cbce376SFrantisek Hrbata 				"(incompatible version)\n",
6508cbce376SFrantisek Hrbata 				gcov_info_filename(info));
65185a0fdfdSPeter Oberparleiter 			gcov_info_free(node->unloaded_info);
65285a0fdfdSPeter Oberparleiter 			node->unloaded_info = NULL;
65385a0fdfdSPeter Oberparleiter 		}
65485a0fdfdSPeter Oberparleiter 	} else {
65585a0fdfdSPeter Oberparleiter 		/*
65685a0fdfdSPeter Oberparleiter 		 * Two different versions of the same object file are loaded.
65785a0fdfdSPeter Oberparleiter 		 * The initial one takes precedence.
65885a0fdfdSPeter Oberparleiter 		 */
65985a0fdfdSPeter Oberparleiter 		if (!gcov_info_is_compatible(node->loaded_info[0], info)) {
660a5ebb875SAndrew Morton 			pr_warn("could not add '%s' (incompatible "
6618cbce376SFrantisek Hrbata 				"version)\n", gcov_info_filename(info));
66285a0fdfdSPeter Oberparleiter 			kfree(loaded_info);
66385a0fdfdSPeter Oberparleiter 			return;
66485a0fdfdSPeter Oberparleiter 		}
66585a0fdfdSPeter Oberparleiter 	}
66685a0fdfdSPeter Oberparleiter 	/* Overwrite previous array. */
66785a0fdfdSPeter Oberparleiter 	kfree(node->loaded_info);
66885a0fdfdSPeter Oberparleiter 	node->loaded_info = loaded_info;
66985a0fdfdSPeter Oberparleiter 	node->num_loaded = num + 1;
6702521f2c2SPeter Oberparleiter }
6712521f2c2SPeter Oberparleiter 
6722521f2c2SPeter Oberparleiter /*
67385a0fdfdSPeter Oberparleiter  * Return the index of a profiling data set associated with a node.
6742521f2c2SPeter Oberparleiter  */
67585a0fdfdSPeter Oberparleiter static int get_info_index(struct gcov_node *node, struct gcov_info *info)
6762521f2c2SPeter Oberparleiter {
67785a0fdfdSPeter Oberparleiter 	int i;
67885a0fdfdSPeter Oberparleiter 
67985a0fdfdSPeter Oberparleiter 	for (i = 0; i < node->num_loaded; i++) {
68085a0fdfdSPeter Oberparleiter 		if (node->loaded_info[i] == info)
68185a0fdfdSPeter Oberparleiter 			return i;
6822521f2c2SPeter Oberparleiter 	}
68385a0fdfdSPeter Oberparleiter 	return -ENOENT;
68485a0fdfdSPeter Oberparleiter }
68585a0fdfdSPeter Oberparleiter 
68685a0fdfdSPeter Oberparleiter /*
68785a0fdfdSPeter Oberparleiter  * Save the data of a profiling data set which is being unloaded.
68885a0fdfdSPeter Oberparleiter  */
68985a0fdfdSPeter Oberparleiter static void save_info(struct gcov_node *node, struct gcov_info *info)
69085a0fdfdSPeter Oberparleiter {
69185a0fdfdSPeter Oberparleiter 	if (node->unloaded_info)
69285a0fdfdSPeter Oberparleiter 		gcov_info_add(node->unloaded_info, info);
69385a0fdfdSPeter Oberparleiter 	else {
69485a0fdfdSPeter Oberparleiter 		node->unloaded_info = gcov_info_dup(info);
69585a0fdfdSPeter Oberparleiter 		if (!node->unloaded_info) {
696a5ebb875SAndrew Morton 			pr_warn("could not save data for '%s' "
6978cbce376SFrantisek Hrbata 				"(out of memory)\n",
6988cbce376SFrantisek Hrbata 				gcov_info_filename(info));
69985a0fdfdSPeter Oberparleiter 		}
70085a0fdfdSPeter Oberparleiter 	}
70185a0fdfdSPeter Oberparleiter }
70285a0fdfdSPeter Oberparleiter 
70385a0fdfdSPeter Oberparleiter /*
70485a0fdfdSPeter Oberparleiter  * Disassociate a profiling data set from a node. Needs to be called with
70585a0fdfdSPeter Oberparleiter  * node_lock held.
70685a0fdfdSPeter Oberparleiter  */
70785a0fdfdSPeter Oberparleiter static void remove_info(struct gcov_node *node, struct gcov_info *info)
70885a0fdfdSPeter Oberparleiter {
70985a0fdfdSPeter Oberparleiter 	int i;
71085a0fdfdSPeter Oberparleiter 
71185a0fdfdSPeter Oberparleiter 	i = get_info_index(node, info);
71285a0fdfdSPeter Oberparleiter 	if (i < 0) {
713a5ebb875SAndrew Morton 		pr_warn("could not remove '%s' (not found)\n",
7148cbce376SFrantisek Hrbata 			gcov_info_filename(info));
71585a0fdfdSPeter Oberparleiter 		return;
71685a0fdfdSPeter Oberparleiter 	}
71785a0fdfdSPeter Oberparleiter 	if (gcov_persist)
71885a0fdfdSPeter Oberparleiter 		save_info(node, info);
71985a0fdfdSPeter Oberparleiter 	/* Shrink array. */
72085a0fdfdSPeter Oberparleiter 	node->loaded_info[i] = node->loaded_info[node->num_loaded - 1];
72185a0fdfdSPeter Oberparleiter 	node->num_loaded--;
72285a0fdfdSPeter Oberparleiter 	if (node->num_loaded > 0)
72385a0fdfdSPeter Oberparleiter 		return;
72485a0fdfdSPeter Oberparleiter 	/* Last loaded data set was removed. */
72585a0fdfdSPeter Oberparleiter 	kfree(node->loaded_info);
72685a0fdfdSPeter Oberparleiter 	node->loaded_info = NULL;
72785a0fdfdSPeter Oberparleiter 	node->num_loaded = 0;
72885a0fdfdSPeter Oberparleiter 	if (!node->unloaded_info)
72985a0fdfdSPeter Oberparleiter 		remove_node(node);
7302521f2c2SPeter Oberparleiter }
7312521f2c2SPeter Oberparleiter 
7322521f2c2SPeter Oberparleiter /*
7332521f2c2SPeter Oberparleiter  * Callback to create/remove profiling files when code compiled with
7342521f2c2SPeter Oberparleiter  * -fprofile-arcs is loaded/unloaded.
7352521f2c2SPeter Oberparleiter  */
7362521f2c2SPeter Oberparleiter void gcov_event(enum gcov_action action, struct gcov_info *info)
7372521f2c2SPeter Oberparleiter {
7382521f2c2SPeter Oberparleiter 	struct gcov_node *node;
7392521f2c2SPeter Oberparleiter 
7402521f2c2SPeter Oberparleiter 	mutex_lock(&node_lock);
7418cbce376SFrantisek Hrbata 	node = get_node_by_name(gcov_info_filename(info));
7422521f2c2SPeter Oberparleiter 	switch (action) {
7432521f2c2SPeter Oberparleiter 	case GCOV_ADD:
74485a0fdfdSPeter Oberparleiter 		if (node)
74585a0fdfdSPeter Oberparleiter 			add_info(node, info);
74685a0fdfdSPeter Oberparleiter 		else
7472521f2c2SPeter Oberparleiter 			add_node(info);
7482521f2c2SPeter Oberparleiter 		break;
7492521f2c2SPeter Oberparleiter 	case GCOV_REMOVE:
75085a0fdfdSPeter Oberparleiter 		if (node)
75185a0fdfdSPeter Oberparleiter 			remove_info(node, info);
75285a0fdfdSPeter Oberparleiter 		else {
753a5ebb875SAndrew Morton 			pr_warn("could not remove '%s' (not found)\n",
7548cbce376SFrantisek Hrbata 				gcov_info_filename(info));
7552521f2c2SPeter Oberparleiter 		}
7562521f2c2SPeter Oberparleiter 		break;
7572521f2c2SPeter Oberparleiter 	}
7582521f2c2SPeter Oberparleiter 	mutex_unlock(&node_lock);
7592521f2c2SPeter Oberparleiter }
7602521f2c2SPeter Oberparleiter 
7612521f2c2SPeter Oberparleiter /* Create debugfs entries. */
7622521f2c2SPeter Oberparleiter static __init int gcov_fs_init(void)
7632521f2c2SPeter Oberparleiter {
7642521f2c2SPeter Oberparleiter 	int rc = -EIO;
7652521f2c2SPeter Oberparleiter 
7662521f2c2SPeter Oberparleiter 	init_node(&root_node, NULL, NULL, NULL);
7672521f2c2SPeter Oberparleiter 	/*
7682521f2c2SPeter Oberparleiter 	 * /sys/kernel/debug/gcov will be parent for the reset control file
7692521f2c2SPeter Oberparleiter 	 * and all profiling files.
7702521f2c2SPeter Oberparleiter 	 */
7712521f2c2SPeter Oberparleiter 	root_node.dentry = debugfs_create_dir("gcov", NULL);
7722521f2c2SPeter Oberparleiter 	if (!root_node.dentry)
7732521f2c2SPeter Oberparleiter 		goto err_remove;
7742521f2c2SPeter Oberparleiter 	/*
7752521f2c2SPeter Oberparleiter 	 * Create reset file which resets all profiling counts when written
7762521f2c2SPeter Oberparleiter 	 * to.
7772521f2c2SPeter Oberparleiter 	 */
7782521f2c2SPeter Oberparleiter 	reset_dentry = debugfs_create_file("reset", 0600, root_node.dentry,
7792521f2c2SPeter Oberparleiter 					   NULL, &gcov_reset_fops);
7802521f2c2SPeter Oberparleiter 	if (!reset_dentry)
7812521f2c2SPeter Oberparleiter 		goto err_remove;
7822521f2c2SPeter Oberparleiter 	/* Replay previous events to get our fs hierarchy up-to-date. */
7832521f2c2SPeter Oberparleiter 	gcov_enable_events();
7842521f2c2SPeter Oberparleiter 	return 0;
7852521f2c2SPeter Oberparleiter 
7862521f2c2SPeter Oberparleiter err_remove:
7872521f2c2SPeter Oberparleiter 	pr_err("init failed\n");
7882521f2c2SPeter Oberparleiter 	debugfs_remove(root_node.dentry);
7892521f2c2SPeter Oberparleiter 
7902521f2c2SPeter Oberparleiter 	return rc;
7912521f2c2SPeter Oberparleiter }
7922521f2c2SPeter Oberparleiter device_initcall(gcov_fs_init);
793