xref: /openbmc/linux/kernel/gcov/fs.c (revision 1391efa9)
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>
29*1391efa9SJohannes Berg #include <linux/mm.h>
302521f2c2SPeter Oberparleiter #include "gcov.h"
312521f2c2SPeter Oberparleiter 
322521f2c2SPeter Oberparleiter /**
332521f2c2SPeter Oberparleiter  * struct gcov_node - represents a debugfs entry
342521f2c2SPeter Oberparleiter  * @list: list head for child node list
352521f2c2SPeter Oberparleiter  * @children: child nodes
362521f2c2SPeter Oberparleiter  * @all: list head for list of all nodes
372521f2c2SPeter Oberparleiter  * @parent: parent node
3885a0fdfdSPeter Oberparleiter  * @loaded_info: array of pointers to profiling data sets for loaded object
3985a0fdfdSPeter Oberparleiter  *   files.
4085a0fdfdSPeter Oberparleiter  * @num_loaded: number of profiling data sets for loaded object files.
4185a0fdfdSPeter Oberparleiter  * @unloaded_info: accumulated copy of profiling data sets for unloaded
4285a0fdfdSPeter Oberparleiter  *   object files. Used only when gcov_persist=1.
432521f2c2SPeter Oberparleiter  * @dentry: main debugfs entry, either a directory or data file
442521f2c2SPeter Oberparleiter  * @links: associated symbolic links
452521f2c2SPeter Oberparleiter  * @name: data file basename
462521f2c2SPeter Oberparleiter  *
472521f2c2SPeter Oberparleiter  * struct gcov_node represents an entity within the gcov/ subdirectory
482521f2c2SPeter Oberparleiter  * of debugfs. There are directory and data file nodes. The latter represent
492521f2c2SPeter Oberparleiter  * the actual synthesized data file plus any associated symbolic links which
502521f2c2SPeter Oberparleiter  * are needed by the gcov tool to work correctly.
512521f2c2SPeter Oberparleiter  */
522521f2c2SPeter Oberparleiter struct gcov_node {
532521f2c2SPeter Oberparleiter 	struct list_head list;
542521f2c2SPeter Oberparleiter 	struct list_head children;
552521f2c2SPeter Oberparleiter 	struct list_head all;
562521f2c2SPeter Oberparleiter 	struct gcov_node *parent;
5785a0fdfdSPeter Oberparleiter 	struct gcov_info **loaded_info;
5885a0fdfdSPeter Oberparleiter 	struct gcov_info *unloaded_info;
592521f2c2SPeter Oberparleiter 	struct dentry *dentry;
602521f2c2SPeter Oberparleiter 	struct dentry **links;
6185a0fdfdSPeter Oberparleiter 	int num_loaded;
626524d794SGustavo A. R. Silva 	char name[];
632521f2c2SPeter Oberparleiter };
642521f2c2SPeter Oberparleiter 
652521f2c2SPeter Oberparleiter static const char objtree[] = OBJTREE;
662521f2c2SPeter Oberparleiter static const char srctree[] = SRCTREE;
672521f2c2SPeter Oberparleiter static struct gcov_node root_node;
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 
gcov_persist_setup(char * str)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 
897a1d55b9SJohannes Berg #define ITER_STRIDE	PAGE_SIZE
907a1d55b9SJohannes Berg 
917a1d55b9SJohannes Berg /**
927a1d55b9SJohannes Berg  * struct gcov_iterator - specifies current file position in logical records
937a1d55b9SJohannes Berg  * @info: associated profiling data
947a1d55b9SJohannes Berg  * @buffer: buffer containing file data
957a1d55b9SJohannes Berg  * @size: size of buffer
967a1d55b9SJohannes Berg  * @pos: current position in file
977a1d55b9SJohannes Berg  */
987a1d55b9SJohannes Berg struct gcov_iterator {
997a1d55b9SJohannes Berg 	struct gcov_info *info;
1007a1d55b9SJohannes Berg 	size_t size;
1017a1d55b9SJohannes Berg 	loff_t pos;
1023180c44fSJohannes Berg 	char buffer[];
1037a1d55b9SJohannes Berg };
1047a1d55b9SJohannes Berg 
1057a1d55b9SJohannes Berg /**
1067a1d55b9SJohannes Berg  * gcov_iter_new - allocate and initialize profiling data iterator
1077a1d55b9SJohannes Berg  * @info: profiling data set to be iterated
1087a1d55b9SJohannes Berg  *
1097a1d55b9SJohannes Berg  * Return file iterator on success, %NULL otherwise.
1107a1d55b9SJohannes Berg  */
gcov_iter_new(struct gcov_info * info)1117a1d55b9SJohannes Berg static struct gcov_iterator *gcov_iter_new(struct gcov_info *info)
1127a1d55b9SJohannes Berg {
1137a1d55b9SJohannes Berg 	struct gcov_iterator *iter;
1143180c44fSJohannes Berg 	size_t size;
1157a1d55b9SJohannes Berg 
1163180c44fSJohannes Berg 	/* Dry-run to get the actual buffer size. */
1173180c44fSJohannes Berg 	size = convert_to_gcda(NULL, info);
1183180c44fSJohannes Berg 
119*1391efa9SJohannes Berg 	iter = kvmalloc(struct_size(iter, buffer, size), GFP_KERNEL);
1207a1d55b9SJohannes Berg 	if (!iter)
1213180c44fSJohannes Berg 		return NULL;
1227a1d55b9SJohannes Berg 
1237a1d55b9SJohannes Berg 	iter->info = info;
1243180c44fSJohannes Berg 	iter->size = size;
1257a1d55b9SJohannes Berg 	convert_to_gcda(iter->buffer, info);
1267a1d55b9SJohannes Berg 
1277a1d55b9SJohannes Berg 	return iter;
1287a1d55b9SJohannes Berg }
1297a1d55b9SJohannes Berg 
1307a1d55b9SJohannes Berg 
1317a1d55b9SJohannes Berg /**
1327a1d55b9SJohannes Berg  * gcov_iter_free - free iterator data
1337a1d55b9SJohannes Berg  * @iter: file iterator
1347a1d55b9SJohannes Berg  */
gcov_iter_free(struct gcov_iterator * iter)1357a1d55b9SJohannes Berg static void gcov_iter_free(struct gcov_iterator *iter)
1367a1d55b9SJohannes Berg {
137*1391efa9SJohannes Berg 	kvfree(iter);
1387a1d55b9SJohannes Berg }
1397a1d55b9SJohannes Berg 
1407a1d55b9SJohannes Berg /**
1417a1d55b9SJohannes Berg  * gcov_iter_get_info - return profiling data set for given file iterator
1427a1d55b9SJohannes Berg  * @iter: file iterator
1437a1d55b9SJohannes Berg  */
gcov_iter_get_info(struct gcov_iterator * iter)1447a1d55b9SJohannes Berg static struct gcov_info *gcov_iter_get_info(struct gcov_iterator *iter)
1457a1d55b9SJohannes Berg {
1467a1d55b9SJohannes Berg 	return iter->info;
1477a1d55b9SJohannes Berg }
1487a1d55b9SJohannes Berg 
1497a1d55b9SJohannes Berg /**
1507a1d55b9SJohannes Berg  * gcov_iter_start - reset file iterator to starting position
1517a1d55b9SJohannes Berg  * @iter: file iterator
1527a1d55b9SJohannes Berg  */
gcov_iter_start(struct gcov_iterator * iter)1537a1d55b9SJohannes Berg static void gcov_iter_start(struct gcov_iterator *iter)
1547a1d55b9SJohannes Berg {
1557a1d55b9SJohannes Berg 	iter->pos = 0;
1567a1d55b9SJohannes Berg }
1577a1d55b9SJohannes Berg 
1587a1d55b9SJohannes Berg /**
1597a1d55b9SJohannes Berg  * gcov_iter_next - advance file iterator to next logical record
1607a1d55b9SJohannes Berg  * @iter: file iterator
1617a1d55b9SJohannes Berg  *
1627a1d55b9SJohannes Berg  * Return zero if new position is valid, non-zero if iterator has reached end.
1637a1d55b9SJohannes Berg  */
gcov_iter_next(struct gcov_iterator * iter)1647a1d55b9SJohannes Berg static int gcov_iter_next(struct gcov_iterator *iter)
1657a1d55b9SJohannes Berg {
1667a1d55b9SJohannes Berg 	if (iter->pos < iter->size)
1677a1d55b9SJohannes Berg 		iter->pos += ITER_STRIDE;
1687a1d55b9SJohannes Berg 
1697a1d55b9SJohannes Berg 	if (iter->pos >= iter->size)
1707a1d55b9SJohannes Berg 		return -EINVAL;
1717a1d55b9SJohannes Berg 
1727a1d55b9SJohannes Berg 	return 0;
1737a1d55b9SJohannes Berg }
1747a1d55b9SJohannes Berg 
1757a1d55b9SJohannes Berg /**
1767a1d55b9SJohannes Berg  * gcov_iter_write - write data for current pos to seq_file
1777a1d55b9SJohannes Berg  * @iter: file iterator
1787a1d55b9SJohannes Berg  * @seq: seq_file handle
1797a1d55b9SJohannes Berg  *
1807a1d55b9SJohannes Berg  * Return zero on success, non-zero otherwise.
1817a1d55b9SJohannes Berg  */
gcov_iter_write(struct gcov_iterator * iter,struct seq_file * seq)1827a1d55b9SJohannes Berg static int gcov_iter_write(struct gcov_iterator *iter, struct seq_file *seq)
1837a1d55b9SJohannes Berg {
1847a1d55b9SJohannes Berg 	size_t len;
1857a1d55b9SJohannes Berg 
1867a1d55b9SJohannes Berg 	if (iter->pos >= iter->size)
1877a1d55b9SJohannes Berg 		return -EINVAL;
1887a1d55b9SJohannes Berg 
1897a1d55b9SJohannes Berg 	len = ITER_STRIDE;
1907a1d55b9SJohannes Berg 	if (iter->pos + len > iter->size)
1917a1d55b9SJohannes Berg 		len = iter->size - iter->pos;
1927a1d55b9SJohannes Berg 
1937a1d55b9SJohannes Berg 	seq_write(seq, iter->buffer + iter->pos, len);
1947a1d55b9SJohannes Berg 
1957a1d55b9SJohannes Berg 	return 0;
1967a1d55b9SJohannes Berg }
1977a1d55b9SJohannes Berg 
1982521f2c2SPeter Oberparleiter /*
1992521f2c2SPeter Oberparleiter  * seq_file.start() implementation for gcov data files. Note that the
2002521f2c2SPeter Oberparleiter  * gcov_iterator interface is designed to be more restrictive than seq_file
2012521f2c2SPeter Oberparleiter  * (no start from arbitrary position, etc.), to simplify the iterator
2022521f2c2SPeter Oberparleiter  * implementation.
2032521f2c2SPeter Oberparleiter  */
gcov_seq_start(struct seq_file * seq,loff_t * pos)2042521f2c2SPeter Oberparleiter static void *gcov_seq_start(struct seq_file *seq, loff_t *pos)
2052521f2c2SPeter Oberparleiter {
2062521f2c2SPeter Oberparleiter 	loff_t i;
2072521f2c2SPeter Oberparleiter 
2082521f2c2SPeter Oberparleiter 	gcov_iter_start(seq->private);
2092521f2c2SPeter Oberparleiter 	for (i = 0; i < *pos; i++) {
2102521f2c2SPeter Oberparleiter 		if (gcov_iter_next(seq->private))
2112521f2c2SPeter Oberparleiter 			return NULL;
2122521f2c2SPeter Oberparleiter 	}
2132521f2c2SPeter Oberparleiter 	return seq->private;
2142521f2c2SPeter Oberparleiter }
2152521f2c2SPeter Oberparleiter 
2162521f2c2SPeter Oberparleiter /* seq_file.next() implementation for gcov data files. */
gcov_seq_next(struct seq_file * seq,void * data,loff_t * pos)2172521f2c2SPeter Oberparleiter static void *gcov_seq_next(struct seq_file *seq, void *data, loff_t *pos)
2182521f2c2SPeter Oberparleiter {
2192521f2c2SPeter Oberparleiter 	struct gcov_iterator *iter = data;
2202521f2c2SPeter Oberparleiter 
221f4d74ef6SVasily Averin 	(*pos)++;
2222521f2c2SPeter Oberparleiter 	if (gcov_iter_next(iter))
2232521f2c2SPeter Oberparleiter 		return NULL;
2242521f2c2SPeter Oberparleiter 
2252521f2c2SPeter Oberparleiter 	return iter;
2262521f2c2SPeter Oberparleiter }
2272521f2c2SPeter Oberparleiter 
2282521f2c2SPeter Oberparleiter /* seq_file.show() implementation for gcov data files. */
gcov_seq_show(struct seq_file * seq,void * data)2292521f2c2SPeter Oberparleiter static int gcov_seq_show(struct seq_file *seq, void *data)
2302521f2c2SPeter Oberparleiter {
2312521f2c2SPeter Oberparleiter 	struct gcov_iterator *iter = data;
2322521f2c2SPeter Oberparleiter 
2332521f2c2SPeter Oberparleiter 	if (gcov_iter_write(iter, seq))
2342521f2c2SPeter Oberparleiter 		return -EINVAL;
2352521f2c2SPeter Oberparleiter 	return 0;
2362521f2c2SPeter Oberparleiter }
2372521f2c2SPeter Oberparleiter 
gcov_seq_stop(struct seq_file * seq,void * data)2382521f2c2SPeter Oberparleiter static void gcov_seq_stop(struct seq_file *seq, void *data)
2392521f2c2SPeter Oberparleiter {
2402521f2c2SPeter Oberparleiter 	/* Unused. */
2412521f2c2SPeter Oberparleiter }
2422521f2c2SPeter Oberparleiter 
2432521f2c2SPeter Oberparleiter static const struct seq_operations gcov_seq_ops = {
2442521f2c2SPeter Oberparleiter 	.start	= gcov_seq_start,
2452521f2c2SPeter Oberparleiter 	.next	= gcov_seq_next,
2462521f2c2SPeter Oberparleiter 	.show	= gcov_seq_show,
2472521f2c2SPeter Oberparleiter 	.stop	= gcov_seq_stop,
2482521f2c2SPeter Oberparleiter };
2492521f2c2SPeter Oberparleiter 
2502521f2c2SPeter Oberparleiter /*
25185a0fdfdSPeter Oberparleiter  * Return a profiling data set associated with the given node. This is
25285a0fdfdSPeter Oberparleiter  * either a data set for a loaded object file or a data set copy in case
25385a0fdfdSPeter Oberparleiter  * all associated object files have been unloaded.
2542521f2c2SPeter Oberparleiter  */
get_node_info(struct gcov_node * node)2552521f2c2SPeter Oberparleiter static struct gcov_info *get_node_info(struct gcov_node *node)
2562521f2c2SPeter Oberparleiter {
25785a0fdfdSPeter Oberparleiter 	if (node->num_loaded > 0)
25885a0fdfdSPeter Oberparleiter 		return node->loaded_info[0];
2592521f2c2SPeter Oberparleiter 
26085a0fdfdSPeter Oberparleiter 	return node->unloaded_info;
26185a0fdfdSPeter Oberparleiter }
26285a0fdfdSPeter Oberparleiter 
26385a0fdfdSPeter Oberparleiter /*
26485a0fdfdSPeter Oberparleiter  * Return a newly allocated profiling data set which contains the sum of
26585a0fdfdSPeter Oberparleiter  * all profiling data associated with the given node.
26685a0fdfdSPeter Oberparleiter  */
get_accumulated_info(struct gcov_node * node)26785a0fdfdSPeter Oberparleiter static struct gcov_info *get_accumulated_info(struct gcov_node *node)
26885a0fdfdSPeter Oberparleiter {
26985a0fdfdSPeter Oberparleiter 	struct gcov_info *info;
27085a0fdfdSPeter Oberparleiter 	int i = 0;
27185a0fdfdSPeter Oberparleiter 
27285a0fdfdSPeter Oberparleiter 	if (node->unloaded_info)
27385a0fdfdSPeter Oberparleiter 		info = gcov_info_dup(node->unloaded_info);
27485a0fdfdSPeter Oberparleiter 	else
27585a0fdfdSPeter Oberparleiter 		info = gcov_info_dup(node->loaded_info[i++]);
27685a0fdfdSPeter Oberparleiter 	if (!info)
27785a0fdfdSPeter Oberparleiter 		return NULL;
27885a0fdfdSPeter Oberparleiter 	for (; i < node->num_loaded; i++)
27985a0fdfdSPeter Oberparleiter 		gcov_info_add(info, node->loaded_info[i]);
28085a0fdfdSPeter Oberparleiter 
28185a0fdfdSPeter Oberparleiter 	return info;
2822521f2c2SPeter Oberparleiter }
2832521f2c2SPeter Oberparleiter 
2842521f2c2SPeter Oberparleiter /*
2852521f2c2SPeter Oberparleiter  * open() implementation for gcov data files. Create a copy of the profiling
2862521f2c2SPeter Oberparleiter  * data set and initialize the iterator and seq_file interface.
2872521f2c2SPeter Oberparleiter  */
gcov_seq_open(struct inode * inode,struct file * file)2882521f2c2SPeter Oberparleiter static int gcov_seq_open(struct inode *inode, struct file *file)
2892521f2c2SPeter Oberparleiter {
2902521f2c2SPeter Oberparleiter 	struct gcov_node *node = inode->i_private;
2912521f2c2SPeter Oberparleiter 	struct gcov_iterator *iter;
2922521f2c2SPeter Oberparleiter 	struct seq_file *seq;
2932521f2c2SPeter Oberparleiter 	struct gcov_info *info;
2942521f2c2SPeter Oberparleiter 	int rc = -ENOMEM;
2952521f2c2SPeter Oberparleiter 
2962521f2c2SPeter Oberparleiter 	mutex_lock(&node_lock);
2972521f2c2SPeter Oberparleiter 	/*
2982521f2c2SPeter Oberparleiter 	 * Read from a profiling data copy to minimize reference tracking
29985a0fdfdSPeter Oberparleiter 	 * complexity and concurrent access and to keep accumulating multiple
30085a0fdfdSPeter Oberparleiter 	 * profiling data sets associated with one node simple.
3012521f2c2SPeter Oberparleiter 	 */
30285a0fdfdSPeter Oberparleiter 	info = get_accumulated_info(node);
3032521f2c2SPeter Oberparleiter 	if (!info)
3042521f2c2SPeter Oberparleiter 		goto out_unlock;
3052521f2c2SPeter Oberparleiter 	iter = gcov_iter_new(info);
3062521f2c2SPeter Oberparleiter 	if (!iter)
3072521f2c2SPeter Oberparleiter 		goto err_free_info;
3082521f2c2SPeter Oberparleiter 	rc = seq_open(file, &gcov_seq_ops);
3092521f2c2SPeter Oberparleiter 	if (rc)
3102521f2c2SPeter Oberparleiter 		goto err_free_iter_info;
3112521f2c2SPeter Oberparleiter 	seq = file->private_data;
3122521f2c2SPeter Oberparleiter 	seq->private = iter;
3132521f2c2SPeter Oberparleiter out_unlock:
3142521f2c2SPeter Oberparleiter 	mutex_unlock(&node_lock);
3152521f2c2SPeter Oberparleiter 	return rc;
3162521f2c2SPeter Oberparleiter 
3172521f2c2SPeter Oberparleiter err_free_iter_info:
3182521f2c2SPeter Oberparleiter 	gcov_iter_free(iter);
3192521f2c2SPeter Oberparleiter err_free_info:
3202521f2c2SPeter Oberparleiter 	gcov_info_free(info);
3212521f2c2SPeter Oberparleiter 	goto out_unlock;
3222521f2c2SPeter Oberparleiter }
3232521f2c2SPeter Oberparleiter 
3242521f2c2SPeter Oberparleiter /*
3252521f2c2SPeter Oberparleiter  * release() implementation for gcov data files. Release resources allocated
3262521f2c2SPeter Oberparleiter  * by open().
3272521f2c2SPeter Oberparleiter  */
gcov_seq_release(struct inode * inode,struct file * file)3282521f2c2SPeter Oberparleiter static int gcov_seq_release(struct inode *inode, struct file *file)
3292521f2c2SPeter Oberparleiter {
3302521f2c2SPeter Oberparleiter 	struct gcov_iterator *iter;
3312521f2c2SPeter Oberparleiter 	struct gcov_info *info;
3322521f2c2SPeter Oberparleiter 	struct seq_file *seq;
3332521f2c2SPeter Oberparleiter 
3342521f2c2SPeter Oberparleiter 	seq = file->private_data;
3352521f2c2SPeter Oberparleiter 	iter = seq->private;
3362521f2c2SPeter Oberparleiter 	info = gcov_iter_get_info(iter);
3372521f2c2SPeter Oberparleiter 	gcov_iter_free(iter);
3382521f2c2SPeter Oberparleiter 	gcov_info_free(info);
3392521f2c2SPeter Oberparleiter 	seq_release(inode, file);
3402521f2c2SPeter Oberparleiter 
3412521f2c2SPeter Oberparleiter 	return 0;
3422521f2c2SPeter Oberparleiter }
3432521f2c2SPeter Oberparleiter 
3442521f2c2SPeter Oberparleiter /*
3452521f2c2SPeter Oberparleiter  * Find a node by the associated data file name. Needs to be called with
3462521f2c2SPeter Oberparleiter  * node_lock held.
3472521f2c2SPeter Oberparleiter  */
get_node_by_name(const char * name)3482521f2c2SPeter Oberparleiter static struct gcov_node *get_node_by_name(const char *name)
3492521f2c2SPeter Oberparleiter {
3502521f2c2SPeter Oberparleiter 	struct gcov_node *node;
3512521f2c2SPeter Oberparleiter 	struct gcov_info *info;
3522521f2c2SPeter Oberparleiter 
3532521f2c2SPeter Oberparleiter 	list_for_each_entry(node, &all_head, all) {
3542521f2c2SPeter Oberparleiter 		info = get_node_info(node);
3558cbce376SFrantisek Hrbata 		if (info && (strcmp(gcov_info_filename(info), name) == 0))
3562521f2c2SPeter Oberparleiter 			return node;
3572521f2c2SPeter Oberparleiter 	}
3582521f2c2SPeter Oberparleiter 
3592521f2c2SPeter Oberparleiter 	return NULL;
3602521f2c2SPeter Oberparleiter }
3612521f2c2SPeter Oberparleiter 
36285a0fdfdSPeter Oberparleiter /*
36385a0fdfdSPeter Oberparleiter  * Reset all profiling data associated with the specified node.
36485a0fdfdSPeter Oberparleiter  */
reset_node(struct gcov_node * node)36585a0fdfdSPeter Oberparleiter static void reset_node(struct gcov_node *node)
36685a0fdfdSPeter Oberparleiter {
36785a0fdfdSPeter Oberparleiter 	int i;
36885a0fdfdSPeter Oberparleiter 
36985a0fdfdSPeter Oberparleiter 	if (node->unloaded_info)
37085a0fdfdSPeter Oberparleiter 		gcov_info_reset(node->unloaded_info);
37185a0fdfdSPeter Oberparleiter 	for (i = 0; i < node->num_loaded; i++)
37285a0fdfdSPeter Oberparleiter 		gcov_info_reset(node->loaded_info[i]);
37385a0fdfdSPeter Oberparleiter }
37485a0fdfdSPeter Oberparleiter 
3752521f2c2SPeter Oberparleiter static void remove_node(struct gcov_node *node);
3762521f2c2SPeter Oberparleiter 
3772521f2c2SPeter Oberparleiter /*
3782521f2c2SPeter Oberparleiter  * write() implementation for gcov data files. Reset profiling data for the
37985a0fdfdSPeter Oberparleiter  * corresponding file. If all associated object files have been unloaded,
38085a0fdfdSPeter Oberparleiter  * remove the debug fs node as well.
3812521f2c2SPeter Oberparleiter  */
gcov_seq_write(struct file * file,const char __user * addr,size_t len,loff_t * pos)3822521f2c2SPeter Oberparleiter static ssize_t gcov_seq_write(struct file *file, const char __user *addr,
3832521f2c2SPeter Oberparleiter 			      size_t len, loff_t *pos)
3842521f2c2SPeter Oberparleiter {
3852521f2c2SPeter Oberparleiter 	struct seq_file *seq;
3862521f2c2SPeter Oberparleiter 	struct gcov_info *info;
3872521f2c2SPeter Oberparleiter 	struct gcov_node *node;
3882521f2c2SPeter Oberparleiter 
3892521f2c2SPeter Oberparleiter 	seq = file->private_data;
3902521f2c2SPeter Oberparleiter 	info = gcov_iter_get_info(seq->private);
3912521f2c2SPeter Oberparleiter 	mutex_lock(&node_lock);
3928cbce376SFrantisek Hrbata 	node = get_node_by_name(gcov_info_filename(info));
3932521f2c2SPeter Oberparleiter 	if (node) {
3942521f2c2SPeter Oberparleiter 		/* Reset counts or remove node for unloaded modules. */
39585a0fdfdSPeter Oberparleiter 		if (node->num_loaded == 0)
3962521f2c2SPeter Oberparleiter 			remove_node(node);
3972521f2c2SPeter Oberparleiter 		else
39885a0fdfdSPeter Oberparleiter 			reset_node(node);
3992521f2c2SPeter Oberparleiter 	}
4002521f2c2SPeter Oberparleiter 	/* Reset counts for open file. */
4012521f2c2SPeter Oberparleiter 	gcov_info_reset(info);
4022521f2c2SPeter Oberparleiter 	mutex_unlock(&node_lock);
4032521f2c2SPeter Oberparleiter 
4042521f2c2SPeter Oberparleiter 	return len;
4052521f2c2SPeter Oberparleiter }
4062521f2c2SPeter Oberparleiter 
4072521f2c2SPeter Oberparleiter /*
4082521f2c2SPeter Oberparleiter  * Given a string <path> representing a file path of format:
4092521f2c2SPeter Oberparleiter  *   path/to/file.gcda
4102521f2c2SPeter Oberparleiter  * construct and return a new string:
4112521f2c2SPeter Oberparleiter  *   <dir/>path/to/file.<ext>
4122521f2c2SPeter Oberparleiter  */
link_target(const char * dir,const char * path,const char * ext)4132521f2c2SPeter Oberparleiter static char *link_target(const char *dir, const char *path, const char *ext)
4142521f2c2SPeter Oberparleiter {
4152521f2c2SPeter Oberparleiter 	char *target;
4162521f2c2SPeter Oberparleiter 	char *old_ext;
4172521f2c2SPeter Oberparleiter 	char *copy;
4182521f2c2SPeter Oberparleiter 
4192521f2c2SPeter Oberparleiter 	copy = kstrdup(path, GFP_KERNEL);
4202521f2c2SPeter Oberparleiter 	if (!copy)
4212521f2c2SPeter Oberparleiter 		return NULL;
4222521f2c2SPeter Oberparleiter 	old_ext = strrchr(copy, '.');
4232521f2c2SPeter Oberparleiter 	if (old_ext)
4242521f2c2SPeter Oberparleiter 		*old_ext = '\0';
4252521f2c2SPeter Oberparleiter 	if (dir)
4262521f2c2SPeter Oberparleiter 		target = kasprintf(GFP_KERNEL, "%s/%s.%s", dir, copy, ext);
4272521f2c2SPeter Oberparleiter 	else
4282521f2c2SPeter Oberparleiter 		target = kasprintf(GFP_KERNEL, "%s.%s", copy, ext);
4292521f2c2SPeter Oberparleiter 	kfree(copy);
4302521f2c2SPeter Oberparleiter 
4312521f2c2SPeter Oberparleiter 	return target;
4322521f2c2SPeter Oberparleiter }
4332521f2c2SPeter Oberparleiter 
4342521f2c2SPeter Oberparleiter /*
4352521f2c2SPeter Oberparleiter  * Construct a string representing the symbolic link target for the given
4362521f2c2SPeter Oberparleiter  * gcov data file name and link type. Depending on the link type and the
4372521f2c2SPeter Oberparleiter  * location of the data file, the link target can either point to a
4382521f2c2SPeter Oberparleiter  * subdirectory of srctree, objtree or in an external location.
4392521f2c2SPeter Oberparleiter  */
get_link_target(const char * filename,const struct gcov_link * ext)4402521f2c2SPeter Oberparleiter static char *get_link_target(const char *filename, const struct gcov_link *ext)
4412521f2c2SPeter Oberparleiter {
4422521f2c2SPeter Oberparleiter 	const char *rel;
4432521f2c2SPeter Oberparleiter 	char *result;
4442521f2c2SPeter Oberparleiter 
4452521f2c2SPeter Oberparleiter 	if (strncmp(filename, objtree, strlen(objtree)) == 0) {
4462521f2c2SPeter Oberparleiter 		rel = filename + strlen(objtree) + 1;
4472521f2c2SPeter Oberparleiter 		if (ext->dir == SRC_TREE)
4482521f2c2SPeter Oberparleiter 			result = link_target(srctree, rel, ext->ext);
4492521f2c2SPeter Oberparleiter 		else
4502521f2c2SPeter Oberparleiter 			result = link_target(objtree, rel, ext->ext);
4512521f2c2SPeter Oberparleiter 	} else {
4522521f2c2SPeter Oberparleiter 		/* External compilation. */
4532521f2c2SPeter Oberparleiter 		result = link_target(NULL, filename, ext->ext);
4542521f2c2SPeter Oberparleiter 	}
4552521f2c2SPeter Oberparleiter 
4562521f2c2SPeter Oberparleiter 	return result;
4572521f2c2SPeter Oberparleiter }
4582521f2c2SPeter Oberparleiter 
4592521f2c2SPeter Oberparleiter #define SKEW_PREFIX	".tmp_"
4602521f2c2SPeter Oberparleiter 
4612521f2c2SPeter Oberparleiter /*
4622521f2c2SPeter Oberparleiter  * For a filename .tmp_filename.ext return filename.ext. Needed to compensate
4632521f2c2SPeter Oberparleiter  * for filename skewing caused by the mod-versioning mechanism.
4642521f2c2SPeter Oberparleiter  */
deskew(const char * basename)4652521f2c2SPeter Oberparleiter static const char *deskew(const char *basename)
4662521f2c2SPeter Oberparleiter {
4672521f2c2SPeter Oberparleiter 	if (strncmp(basename, SKEW_PREFIX, sizeof(SKEW_PREFIX) - 1) == 0)
4682521f2c2SPeter Oberparleiter 		return basename + sizeof(SKEW_PREFIX) - 1;
4692521f2c2SPeter Oberparleiter 	return basename;
4702521f2c2SPeter Oberparleiter }
4712521f2c2SPeter Oberparleiter 
4722521f2c2SPeter Oberparleiter /*
4732521f2c2SPeter Oberparleiter  * Create links to additional files (usually .c and .gcno files) which the
4742521f2c2SPeter Oberparleiter  * gcov tool expects to find in the same directory as the gcov data file.
4752521f2c2SPeter Oberparleiter  */
add_links(struct gcov_node * node,struct dentry * parent)4762521f2c2SPeter Oberparleiter static void add_links(struct gcov_node *node, struct dentry *parent)
4772521f2c2SPeter Oberparleiter {
4781931d433SAndy Shevchenko 	const char *basename;
4792521f2c2SPeter Oberparleiter 	char *target;
4802521f2c2SPeter Oberparleiter 	int num;
4812521f2c2SPeter Oberparleiter 	int i;
4822521f2c2SPeter Oberparleiter 
4832521f2c2SPeter Oberparleiter 	for (num = 0; gcov_link[num].ext; num++)
4842521f2c2SPeter Oberparleiter 		/* Nothing. */;
4852521f2c2SPeter Oberparleiter 	node->links = kcalloc(num, sizeof(struct dentry *), GFP_KERNEL);
4862521f2c2SPeter Oberparleiter 	if (!node->links)
4872521f2c2SPeter Oberparleiter 		return;
4882521f2c2SPeter Oberparleiter 	for (i = 0; i < num; i++) {
4898cbce376SFrantisek Hrbata 		target = get_link_target(
4908cbce376SFrantisek Hrbata 				gcov_info_filename(get_node_info(node)),
4912521f2c2SPeter Oberparleiter 				&gcov_link[i]);
4922521f2c2SPeter Oberparleiter 		if (!target)
4932521f2c2SPeter Oberparleiter 			goto out_err;
4941931d433SAndy Shevchenko 		basename = kbasename(target);
4951931d433SAndy Shevchenko 		if (basename == target)
4962521f2c2SPeter Oberparleiter 			goto out_err;
4972521f2c2SPeter Oberparleiter 		node->links[i] = debugfs_create_symlink(deskew(basename),
4982521f2c2SPeter Oberparleiter 							parent,	target);
4992521f2c2SPeter Oberparleiter 		kfree(target);
5002521f2c2SPeter Oberparleiter 	}
5012521f2c2SPeter Oberparleiter 
5022521f2c2SPeter Oberparleiter 	return;
5032521f2c2SPeter Oberparleiter out_err:
5042521f2c2SPeter Oberparleiter 	kfree(target);
5052521f2c2SPeter Oberparleiter 	while (i-- > 0)
5062521f2c2SPeter Oberparleiter 		debugfs_remove(node->links[i]);
5072521f2c2SPeter Oberparleiter 	kfree(node->links);
5082521f2c2SPeter Oberparleiter 	node->links = NULL;
5092521f2c2SPeter Oberparleiter }
5102521f2c2SPeter Oberparleiter 
5112521f2c2SPeter Oberparleiter static const struct file_operations gcov_data_fops = {
5122521f2c2SPeter Oberparleiter 	.open		= gcov_seq_open,
5132521f2c2SPeter Oberparleiter 	.release	= gcov_seq_release,
5142521f2c2SPeter Oberparleiter 	.read		= seq_read,
5152521f2c2SPeter Oberparleiter 	.llseek		= seq_lseek,
5162521f2c2SPeter Oberparleiter 	.write		= gcov_seq_write,
5172521f2c2SPeter Oberparleiter };
5182521f2c2SPeter Oberparleiter 
5192521f2c2SPeter Oberparleiter /* Basic initialization of a new node. */
init_node(struct gcov_node * node,struct gcov_info * info,const char * name,struct gcov_node * parent)5202521f2c2SPeter Oberparleiter static void init_node(struct gcov_node *node, struct gcov_info *info,
5212521f2c2SPeter Oberparleiter 		      const char *name, struct gcov_node *parent)
5222521f2c2SPeter Oberparleiter {
5232521f2c2SPeter Oberparleiter 	INIT_LIST_HEAD(&node->list);
5242521f2c2SPeter Oberparleiter 	INIT_LIST_HEAD(&node->children);
5252521f2c2SPeter Oberparleiter 	INIT_LIST_HEAD(&node->all);
52685a0fdfdSPeter Oberparleiter 	if (node->loaded_info) {
52785a0fdfdSPeter Oberparleiter 		node->loaded_info[0] = info;
52885a0fdfdSPeter Oberparleiter 		node->num_loaded = 1;
52985a0fdfdSPeter Oberparleiter 	}
5302521f2c2SPeter Oberparleiter 	node->parent = parent;
5312521f2c2SPeter Oberparleiter 	if (name)
5322521f2c2SPeter Oberparleiter 		strcpy(node->name, name);
5332521f2c2SPeter Oberparleiter }
5342521f2c2SPeter Oberparleiter 
5352521f2c2SPeter Oberparleiter /*
5362521f2c2SPeter Oberparleiter  * Create a new node and associated debugfs entry. Needs to be called with
5372521f2c2SPeter Oberparleiter  * node_lock held.
5382521f2c2SPeter Oberparleiter  */
new_node(struct gcov_node * parent,struct gcov_info * info,const char * name)5392521f2c2SPeter Oberparleiter static struct gcov_node *new_node(struct gcov_node *parent,
5402521f2c2SPeter Oberparleiter 				  struct gcov_info *info, const char *name)
5412521f2c2SPeter Oberparleiter {
5422521f2c2SPeter Oberparleiter 	struct gcov_node *node;
5432521f2c2SPeter Oberparleiter 
5442521f2c2SPeter Oberparleiter 	node = kzalloc(sizeof(struct gcov_node) + strlen(name) + 1, GFP_KERNEL);
54585a0fdfdSPeter Oberparleiter 	if (!node)
54685a0fdfdSPeter Oberparleiter 		goto err_nomem;
54785a0fdfdSPeter Oberparleiter 	if (info) {
54885a0fdfdSPeter Oberparleiter 		node->loaded_info = kcalloc(1, sizeof(struct gcov_info *),
54985a0fdfdSPeter Oberparleiter 					   GFP_KERNEL);
55085a0fdfdSPeter Oberparleiter 		if (!node->loaded_info)
55185a0fdfdSPeter Oberparleiter 			goto err_nomem;
5522521f2c2SPeter Oberparleiter 	}
5532521f2c2SPeter Oberparleiter 	init_node(node, info, name, parent);
5542521f2c2SPeter Oberparleiter 	/* Differentiate between gcov data file nodes and directory nodes. */
5552521f2c2SPeter Oberparleiter 	if (info) {
5562521f2c2SPeter Oberparleiter 		node->dentry = debugfs_create_file(deskew(node->name), 0600,
5572521f2c2SPeter Oberparleiter 					parent->dentry, node, &gcov_data_fops);
5582521f2c2SPeter Oberparleiter 	} else
5592521f2c2SPeter Oberparleiter 		node->dentry = debugfs_create_dir(node->name, parent->dentry);
5602521f2c2SPeter Oberparleiter 	if (info)
5612521f2c2SPeter Oberparleiter 		add_links(node, parent->dentry);
5622521f2c2SPeter Oberparleiter 	list_add(&node->list, &parent->children);
5632521f2c2SPeter Oberparleiter 	list_add(&node->all, &all_head);
5642521f2c2SPeter Oberparleiter 
5652521f2c2SPeter Oberparleiter 	return node;
56685a0fdfdSPeter Oberparleiter 
56785a0fdfdSPeter Oberparleiter err_nomem:
56885a0fdfdSPeter Oberparleiter 	kfree(node);
569a5ebb875SAndrew Morton 	pr_warn("out of memory\n");
57085a0fdfdSPeter Oberparleiter 	return NULL;
5712521f2c2SPeter Oberparleiter }
5722521f2c2SPeter Oberparleiter 
5732521f2c2SPeter Oberparleiter /* Remove symbolic links associated with node. */
remove_links(struct gcov_node * node)5742521f2c2SPeter Oberparleiter static void remove_links(struct gcov_node *node)
5752521f2c2SPeter Oberparleiter {
5762521f2c2SPeter Oberparleiter 	int i;
5772521f2c2SPeter Oberparleiter 
5782521f2c2SPeter Oberparleiter 	if (!node->links)
5792521f2c2SPeter Oberparleiter 		return;
5802521f2c2SPeter Oberparleiter 	for (i = 0; gcov_link[i].ext; i++)
5812521f2c2SPeter Oberparleiter 		debugfs_remove(node->links[i]);
5822521f2c2SPeter Oberparleiter 	kfree(node->links);
5832521f2c2SPeter Oberparleiter 	node->links = NULL;
5842521f2c2SPeter Oberparleiter }
5852521f2c2SPeter Oberparleiter 
5862521f2c2SPeter Oberparleiter /*
5872521f2c2SPeter Oberparleiter  * Remove node from all lists and debugfs and release associated resources.
5882521f2c2SPeter Oberparleiter  * Needs to be called with node_lock held.
5892521f2c2SPeter Oberparleiter  */
release_node(struct gcov_node * node)5902521f2c2SPeter Oberparleiter static void release_node(struct gcov_node *node)
5912521f2c2SPeter Oberparleiter {
5922521f2c2SPeter Oberparleiter 	list_del(&node->list);
5932521f2c2SPeter Oberparleiter 	list_del(&node->all);
5942521f2c2SPeter Oberparleiter 	debugfs_remove(node->dentry);
5952521f2c2SPeter Oberparleiter 	remove_links(node);
59685a0fdfdSPeter Oberparleiter 	kfree(node->loaded_info);
59785a0fdfdSPeter Oberparleiter 	if (node->unloaded_info)
59885a0fdfdSPeter Oberparleiter 		gcov_info_free(node->unloaded_info);
5992521f2c2SPeter Oberparleiter 	kfree(node);
6002521f2c2SPeter Oberparleiter }
6012521f2c2SPeter Oberparleiter 
6022521f2c2SPeter Oberparleiter /* Release node and empty parents. Needs to be called with node_lock held. */
remove_node(struct gcov_node * node)6032521f2c2SPeter Oberparleiter static void remove_node(struct gcov_node *node)
6042521f2c2SPeter Oberparleiter {
6052521f2c2SPeter Oberparleiter 	struct gcov_node *parent;
6062521f2c2SPeter Oberparleiter 
6072521f2c2SPeter Oberparleiter 	while ((node != &root_node) && list_empty(&node->children)) {
6082521f2c2SPeter Oberparleiter 		parent = node->parent;
6092521f2c2SPeter Oberparleiter 		release_node(node);
6102521f2c2SPeter Oberparleiter 		node = parent;
6112521f2c2SPeter Oberparleiter 	}
6122521f2c2SPeter Oberparleiter }
6132521f2c2SPeter Oberparleiter 
6142521f2c2SPeter Oberparleiter /*
6152521f2c2SPeter Oberparleiter  * Find child node with given basename. Needs to be called with node_lock
6162521f2c2SPeter Oberparleiter  * held.
6172521f2c2SPeter Oberparleiter  */
get_child_by_name(struct gcov_node * parent,const char * name)6182521f2c2SPeter Oberparleiter static struct gcov_node *get_child_by_name(struct gcov_node *parent,
6192521f2c2SPeter Oberparleiter 					   const char *name)
6202521f2c2SPeter Oberparleiter {
6212521f2c2SPeter Oberparleiter 	struct gcov_node *node;
6222521f2c2SPeter Oberparleiter 
6232521f2c2SPeter Oberparleiter 	list_for_each_entry(node, &parent->children, list) {
6242521f2c2SPeter Oberparleiter 		if (strcmp(node->name, name) == 0)
6252521f2c2SPeter Oberparleiter 			return node;
6262521f2c2SPeter Oberparleiter 	}
6272521f2c2SPeter Oberparleiter 
6282521f2c2SPeter Oberparleiter 	return NULL;
6292521f2c2SPeter Oberparleiter }
6302521f2c2SPeter Oberparleiter 
6312521f2c2SPeter Oberparleiter /*
6322521f2c2SPeter Oberparleiter  * write() implementation for reset file. Reset all profiling data to zero
63385a0fdfdSPeter Oberparleiter  * and remove nodes for which all associated object files are unloaded.
6342521f2c2SPeter Oberparleiter  */
reset_write(struct file * file,const char __user * addr,size_t len,loff_t * pos)6352521f2c2SPeter Oberparleiter static ssize_t reset_write(struct file *file, const char __user *addr,
6362521f2c2SPeter Oberparleiter 			   size_t len, loff_t *pos)
6372521f2c2SPeter Oberparleiter {
6382521f2c2SPeter Oberparleiter 	struct gcov_node *node;
6392521f2c2SPeter Oberparleiter 
6402521f2c2SPeter Oberparleiter 	mutex_lock(&node_lock);
6412521f2c2SPeter Oberparleiter restart:
6422521f2c2SPeter Oberparleiter 	list_for_each_entry(node, &all_head, all) {
64385a0fdfdSPeter Oberparleiter 		if (node->num_loaded > 0)
64485a0fdfdSPeter Oberparleiter 			reset_node(node);
6452521f2c2SPeter Oberparleiter 		else if (list_empty(&node->children)) {
6462521f2c2SPeter Oberparleiter 			remove_node(node);
6472521f2c2SPeter Oberparleiter 			/* Several nodes may have gone - restart loop. */
6482521f2c2SPeter Oberparleiter 			goto restart;
6492521f2c2SPeter Oberparleiter 		}
6502521f2c2SPeter Oberparleiter 	}
6512521f2c2SPeter Oberparleiter 	mutex_unlock(&node_lock);
6522521f2c2SPeter Oberparleiter 
6532521f2c2SPeter Oberparleiter 	return len;
6542521f2c2SPeter Oberparleiter }
6552521f2c2SPeter Oberparleiter 
6562521f2c2SPeter Oberparleiter /* read() implementation for reset file. Unused. */
reset_read(struct file * file,char __user * addr,size_t len,loff_t * pos)6572521f2c2SPeter Oberparleiter static ssize_t reset_read(struct file *file, char __user *addr, size_t len,
6582521f2c2SPeter Oberparleiter 			  loff_t *pos)
6592521f2c2SPeter Oberparleiter {
6602521f2c2SPeter Oberparleiter 	/* Allow read operation so that a recursive copy won't fail. */
6612521f2c2SPeter Oberparleiter 	return 0;
6622521f2c2SPeter Oberparleiter }
6632521f2c2SPeter Oberparleiter 
6642521f2c2SPeter Oberparleiter static const struct file_operations gcov_reset_fops = {
6652521f2c2SPeter Oberparleiter 	.write	= reset_write,
6662521f2c2SPeter Oberparleiter 	.read	= reset_read,
6676038f373SArnd Bergmann 	.llseek = noop_llseek,
6682521f2c2SPeter Oberparleiter };
6692521f2c2SPeter Oberparleiter 
6702521f2c2SPeter Oberparleiter /*
6712521f2c2SPeter Oberparleiter  * Create a node for a given profiling data set and add it to all lists and
6722521f2c2SPeter Oberparleiter  * debugfs. Needs to be called with node_lock held.
6732521f2c2SPeter Oberparleiter  */
add_node(struct gcov_info * info)6742521f2c2SPeter Oberparleiter static void add_node(struct gcov_info *info)
6752521f2c2SPeter Oberparleiter {
6762521f2c2SPeter Oberparleiter 	char *filename;
6772521f2c2SPeter Oberparleiter 	char *curr;
6782521f2c2SPeter Oberparleiter 	char *next;
6792521f2c2SPeter Oberparleiter 	struct gcov_node *parent;
6802521f2c2SPeter Oberparleiter 	struct gcov_node *node;
6812521f2c2SPeter Oberparleiter 
6828cbce376SFrantisek Hrbata 	filename = kstrdup(gcov_info_filename(info), GFP_KERNEL);
6832521f2c2SPeter Oberparleiter 	if (!filename)
6842521f2c2SPeter Oberparleiter 		return;
6852521f2c2SPeter Oberparleiter 	parent = &root_node;
6862521f2c2SPeter Oberparleiter 	/* Create directory nodes along the path. */
6872521f2c2SPeter Oberparleiter 	for (curr = filename; (next = strchr(curr, '/')); curr = next + 1) {
6882521f2c2SPeter Oberparleiter 		if (curr == next)
6892521f2c2SPeter Oberparleiter 			continue;
6902521f2c2SPeter Oberparleiter 		*next = 0;
6912521f2c2SPeter Oberparleiter 		if (strcmp(curr, ".") == 0)
6922521f2c2SPeter Oberparleiter 			continue;
6932521f2c2SPeter Oberparleiter 		if (strcmp(curr, "..") == 0) {
6942521f2c2SPeter Oberparleiter 			if (!parent->parent)
6952521f2c2SPeter Oberparleiter 				goto err_remove;
6962521f2c2SPeter Oberparleiter 			parent = parent->parent;
6972521f2c2SPeter Oberparleiter 			continue;
6982521f2c2SPeter Oberparleiter 		}
6992521f2c2SPeter Oberparleiter 		node = get_child_by_name(parent, curr);
7002521f2c2SPeter Oberparleiter 		if (!node) {
7012521f2c2SPeter Oberparleiter 			node = new_node(parent, NULL, curr);
7022521f2c2SPeter Oberparleiter 			if (!node)
7032521f2c2SPeter Oberparleiter 				goto err_remove;
7042521f2c2SPeter Oberparleiter 		}
7052521f2c2SPeter Oberparleiter 		parent = node;
7062521f2c2SPeter Oberparleiter 	}
7072521f2c2SPeter Oberparleiter 	/* Create file node. */
7082521f2c2SPeter Oberparleiter 	node = new_node(parent, info, curr);
7092521f2c2SPeter Oberparleiter 	if (!node)
7102521f2c2SPeter Oberparleiter 		goto err_remove;
7112521f2c2SPeter Oberparleiter out:
7122521f2c2SPeter Oberparleiter 	kfree(filename);
7132521f2c2SPeter Oberparleiter 	return;
7142521f2c2SPeter Oberparleiter 
7152521f2c2SPeter Oberparleiter err_remove:
7162521f2c2SPeter Oberparleiter 	remove_node(parent);
7172521f2c2SPeter Oberparleiter 	goto out;
7182521f2c2SPeter Oberparleiter }
7192521f2c2SPeter Oberparleiter 
7202521f2c2SPeter Oberparleiter /*
72185a0fdfdSPeter Oberparleiter  * Associate a profiling data set with an existing node. Needs to be called
72285a0fdfdSPeter Oberparleiter  * with node_lock held.
7232521f2c2SPeter Oberparleiter  */
add_info(struct gcov_node * node,struct gcov_info * info)72485a0fdfdSPeter Oberparleiter static void add_info(struct gcov_node *node, struct gcov_info *info)
7252521f2c2SPeter Oberparleiter {
72685a0fdfdSPeter Oberparleiter 	struct gcov_info **loaded_info;
72785a0fdfdSPeter Oberparleiter 	int num = node->num_loaded;
7282521f2c2SPeter Oberparleiter 
72985a0fdfdSPeter Oberparleiter 	/*
73085a0fdfdSPeter Oberparleiter 	 * Prepare new array. This is done first to simplify cleanup in
73185a0fdfdSPeter Oberparleiter 	 * case the new data set is incompatible, the node only contains
73285a0fdfdSPeter Oberparleiter 	 * unloaded data sets and there's not enough memory for the array.
73385a0fdfdSPeter Oberparleiter 	 */
73485a0fdfdSPeter Oberparleiter 	loaded_info = kcalloc(num + 1, sizeof(struct gcov_info *), GFP_KERNEL);
73585a0fdfdSPeter Oberparleiter 	if (!loaded_info) {
736a5ebb875SAndrew Morton 		pr_warn("could not add '%s' (out of memory)\n",
7378cbce376SFrantisek Hrbata 			gcov_info_filename(info));
73885a0fdfdSPeter Oberparleiter 		return;
73985a0fdfdSPeter Oberparleiter 	}
74085a0fdfdSPeter Oberparleiter 	memcpy(loaded_info, node->loaded_info,
74185a0fdfdSPeter Oberparleiter 	       num * sizeof(struct gcov_info *));
74285a0fdfdSPeter Oberparleiter 	loaded_info[num] = info;
74385a0fdfdSPeter Oberparleiter 	/* Check if the new data set is compatible. */
74485a0fdfdSPeter Oberparleiter 	if (num == 0) {
74585a0fdfdSPeter Oberparleiter 		/*
74685a0fdfdSPeter Oberparleiter 		 * A module was unloaded, modified and reloaded. The new
74785a0fdfdSPeter Oberparleiter 		 * data set replaces the copy of the last one.
74885a0fdfdSPeter Oberparleiter 		 */
74985a0fdfdSPeter Oberparleiter 		if (!gcov_info_is_compatible(node->unloaded_info, info)) {
750a5ebb875SAndrew Morton 			pr_warn("discarding saved data for %s "
7518cbce376SFrantisek Hrbata 				"(incompatible version)\n",
7528cbce376SFrantisek Hrbata 				gcov_info_filename(info));
75385a0fdfdSPeter Oberparleiter 			gcov_info_free(node->unloaded_info);
75485a0fdfdSPeter Oberparleiter 			node->unloaded_info = NULL;
75585a0fdfdSPeter Oberparleiter 		}
75685a0fdfdSPeter Oberparleiter 	} else {
75785a0fdfdSPeter Oberparleiter 		/*
75885a0fdfdSPeter Oberparleiter 		 * Two different versions of the same object file are loaded.
75985a0fdfdSPeter Oberparleiter 		 * The initial one takes precedence.
76085a0fdfdSPeter Oberparleiter 		 */
76185a0fdfdSPeter Oberparleiter 		if (!gcov_info_is_compatible(node->loaded_info[0], info)) {
762a5ebb875SAndrew Morton 			pr_warn("could not add '%s' (incompatible "
7638cbce376SFrantisek Hrbata 				"version)\n", gcov_info_filename(info));
76485a0fdfdSPeter Oberparleiter 			kfree(loaded_info);
76585a0fdfdSPeter Oberparleiter 			return;
76685a0fdfdSPeter Oberparleiter 		}
76785a0fdfdSPeter Oberparleiter 	}
76885a0fdfdSPeter Oberparleiter 	/* Overwrite previous array. */
76985a0fdfdSPeter Oberparleiter 	kfree(node->loaded_info);
77085a0fdfdSPeter Oberparleiter 	node->loaded_info = loaded_info;
77185a0fdfdSPeter Oberparleiter 	node->num_loaded = num + 1;
7722521f2c2SPeter Oberparleiter }
7732521f2c2SPeter Oberparleiter 
7742521f2c2SPeter Oberparleiter /*
77585a0fdfdSPeter Oberparleiter  * Return the index of a profiling data set associated with a node.
7762521f2c2SPeter Oberparleiter  */
get_info_index(struct gcov_node * node,struct gcov_info * info)77785a0fdfdSPeter Oberparleiter static int get_info_index(struct gcov_node *node, struct gcov_info *info)
7782521f2c2SPeter Oberparleiter {
77985a0fdfdSPeter Oberparleiter 	int i;
78085a0fdfdSPeter Oberparleiter 
78185a0fdfdSPeter Oberparleiter 	for (i = 0; i < node->num_loaded; i++) {
78285a0fdfdSPeter Oberparleiter 		if (node->loaded_info[i] == info)
78385a0fdfdSPeter Oberparleiter 			return i;
7842521f2c2SPeter Oberparleiter 	}
78585a0fdfdSPeter Oberparleiter 	return -ENOENT;
78685a0fdfdSPeter Oberparleiter }
78785a0fdfdSPeter Oberparleiter 
78885a0fdfdSPeter Oberparleiter /*
78985a0fdfdSPeter Oberparleiter  * Save the data of a profiling data set which is being unloaded.
79085a0fdfdSPeter Oberparleiter  */
save_info(struct gcov_node * node,struct gcov_info * info)79185a0fdfdSPeter Oberparleiter static void save_info(struct gcov_node *node, struct gcov_info *info)
79285a0fdfdSPeter Oberparleiter {
79385a0fdfdSPeter Oberparleiter 	if (node->unloaded_info)
79485a0fdfdSPeter Oberparleiter 		gcov_info_add(node->unloaded_info, info);
79585a0fdfdSPeter Oberparleiter 	else {
79685a0fdfdSPeter Oberparleiter 		node->unloaded_info = gcov_info_dup(info);
79785a0fdfdSPeter Oberparleiter 		if (!node->unloaded_info) {
798a5ebb875SAndrew Morton 			pr_warn("could not save data for '%s' "
7998cbce376SFrantisek Hrbata 				"(out of memory)\n",
8008cbce376SFrantisek Hrbata 				gcov_info_filename(info));
80185a0fdfdSPeter Oberparleiter 		}
80285a0fdfdSPeter Oberparleiter 	}
80385a0fdfdSPeter Oberparleiter }
80485a0fdfdSPeter Oberparleiter 
80585a0fdfdSPeter Oberparleiter /*
80685a0fdfdSPeter Oberparleiter  * Disassociate a profiling data set from a node. Needs to be called with
80785a0fdfdSPeter Oberparleiter  * node_lock held.
80885a0fdfdSPeter Oberparleiter  */
remove_info(struct gcov_node * node,struct gcov_info * info)80985a0fdfdSPeter Oberparleiter static void remove_info(struct gcov_node *node, struct gcov_info *info)
81085a0fdfdSPeter Oberparleiter {
81185a0fdfdSPeter Oberparleiter 	int i;
81285a0fdfdSPeter Oberparleiter 
81385a0fdfdSPeter Oberparleiter 	i = get_info_index(node, info);
81485a0fdfdSPeter Oberparleiter 	if (i < 0) {
815a5ebb875SAndrew Morton 		pr_warn("could not remove '%s' (not found)\n",
8168cbce376SFrantisek Hrbata 			gcov_info_filename(info));
81785a0fdfdSPeter Oberparleiter 		return;
81885a0fdfdSPeter Oberparleiter 	}
81985a0fdfdSPeter Oberparleiter 	if (gcov_persist)
82085a0fdfdSPeter Oberparleiter 		save_info(node, info);
82185a0fdfdSPeter Oberparleiter 	/* Shrink array. */
82285a0fdfdSPeter Oberparleiter 	node->loaded_info[i] = node->loaded_info[node->num_loaded - 1];
82385a0fdfdSPeter Oberparleiter 	node->num_loaded--;
82485a0fdfdSPeter Oberparleiter 	if (node->num_loaded > 0)
82585a0fdfdSPeter Oberparleiter 		return;
82685a0fdfdSPeter Oberparleiter 	/* Last loaded data set was removed. */
82785a0fdfdSPeter Oberparleiter 	kfree(node->loaded_info);
82885a0fdfdSPeter Oberparleiter 	node->loaded_info = NULL;
82985a0fdfdSPeter Oberparleiter 	node->num_loaded = 0;
83085a0fdfdSPeter Oberparleiter 	if (!node->unloaded_info)
83185a0fdfdSPeter Oberparleiter 		remove_node(node);
8322521f2c2SPeter Oberparleiter }
8332521f2c2SPeter Oberparleiter 
8342521f2c2SPeter Oberparleiter /*
8352521f2c2SPeter Oberparleiter  * Callback to create/remove profiling files when code compiled with
8362521f2c2SPeter Oberparleiter  * -fprofile-arcs is loaded/unloaded.
8372521f2c2SPeter Oberparleiter  */
gcov_event(enum gcov_action action,struct gcov_info * info)8382521f2c2SPeter Oberparleiter void gcov_event(enum gcov_action action, struct gcov_info *info)
8392521f2c2SPeter Oberparleiter {
8402521f2c2SPeter Oberparleiter 	struct gcov_node *node;
8412521f2c2SPeter Oberparleiter 
8422521f2c2SPeter Oberparleiter 	mutex_lock(&node_lock);
8438cbce376SFrantisek Hrbata 	node = get_node_by_name(gcov_info_filename(info));
8442521f2c2SPeter Oberparleiter 	switch (action) {
8452521f2c2SPeter Oberparleiter 	case GCOV_ADD:
84685a0fdfdSPeter Oberparleiter 		if (node)
84785a0fdfdSPeter Oberparleiter 			add_info(node, info);
84885a0fdfdSPeter Oberparleiter 		else
8492521f2c2SPeter Oberparleiter 			add_node(info);
8502521f2c2SPeter Oberparleiter 		break;
8512521f2c2SPeter Oberparleiter 	case GCOV_REMOVE:
85285a0fdfdSPeter Oberparleiter 		if (node)
85385a0fdfdSPeter Oberparleiter 			remove_info(node, info);
85485a0fdfdSPeter Oberparleiter 		else {
855a5ebb875SAndrew Morton 			pr_warn("could not remove '%s' (not found)\n",
8568cbce376SFrantisek Hrbata 				gcov_info_filename(info));
8572521f2c2SPeter Oberparleiter 		}
8582521f2c2SPeter Oberparleiter 		break;
8592521f2c2SPeter Oberparleiter 	}
8602521f2c2SPeter Oberparleiter 	mutex_unlock(&node_lock);
8612521f2c2SPeter Oberparleiter }
8622521f2c2SPeter Oberparleiter 
8632521f2c2SPeter Oberparleiter /* Create debugfs entries. */
gcov_fs_init(void)8642521f2c2SPeter Oberparleiter static __init int gcov_fs_init(void)
8652521f2c2SPeter Oberparleiter {
8662521f2c2SPeter Oberparleiter 	init_node(&root_node, NULL, NULL, NULL);
8672521f2c2SPeter Oberparleiter 	/*
8682521f2c2SPeter Oberparleiter 	 * /sys/kernel/debug/gcov will be parent for the reset control file
8692521f2c2SPeter Oberparleiter 	 * and all profiling files.
8702521f2c2SPeter Oberparleiter 	 */
8712521f2c2SPeter Oberparleiter 	root_node.dentry = debugfs_create_dir("gcov", NULL);
8722521f2c2SPeter Oberparleiter 	/*
8732521f2c2SPeter Oberparleiter 	 * Create reset file which resets all profiling counts when written
8742521f2c2SPeter Oberparleiter 	 * to.
8752521f2c2SPeter Oberparleiter 	 */
8761c769fc4SGreg Kroah-Hartman 	debugfs_create_file("reset", 0600, root_node.dentry, NULL,
8771c769fc4SGreg Kroah-Hartman 			    &gcov_reset_fops);
8782521f2c2SPeter Oberparleiter 	/* Replay previous events to get our fs hierarchy up-to-date. */
8792521f2c2SPeter Oberparleiter 	gcov_enable_events();
8802521f2c2SPeter Oberparleiter 	return 0;
8812521f2c2SPeter Oberparleiter }
8822521f2c2SPeter Oberparleiter device_initcall(gcov_fs_init);
883