[PATCH 1/1] Managed Devices devres_debugfs file system

From: Rob Jones
Date: Thu Jul 24 2014 - 11:18:56 EST


Reviewed-by: Ian Molton <ian.molton@xxxxxxxxxxxxxxx>
Suggested-by: Ben Dooks <ben.dooks@xxxxxxxxxxxxxxx>
Signed-off-by: Rob Jones <rob.jones@xxxxxxxxxxxxxxx>
---
Documentation/driver-model/devres-debugfs.txt | 140 +++++++++
drivers/base/Kconfig | 18 ++
drivers/base/devres.c | 387 +++++++++++++++++++++++++
3 files changed, 545 insertions(+)
create mode 100644 Documentation/driver-model/devres-debugfs.txt

diff --git a/Documentation/driver-model/devres-debugfs.txt b/Documentation/driver-model/devres-debugfs.txt
new file mode 100644
index 0000000..7004ebd
--- /dev/null
+++ b/Documentation/driver-model/devres-debugfs.txt
@@ -0,0 +1,140 @@
+
+Introduction
+============
+
+This document describes a file system that can be enabled to assist
+with the debugging of devices that use managed reources
+
+Outline of Operation
+====================
+
+Setting the flag DEVRES_DEBUGFS (depends on DEBUG_DEVRES) enables a
+debug facility for device managed resources. This takes the form of
+a directory in the debugfs file system which is a pseudo file system
+(typically mounted at /sys/kernel/debug) similar in concept to sysfs
+but with no restrictions on the format of the data read from a file.
+
+The directory (devres/) is created the first time a managed resource
+is allocated for any device.
+
+The first time a managed resource is allocated for a device, a file
+with the same name as the device is created in devres/.
+
+The file is read only and can be read by normal userspace utilities
+(such as cat).
+
+The data returned is in ASCII text format and has one header line for
+the device followed by one line per allocated resource.
+
+It is possible to seek to a given line within the file: position 0
+(zero) goes directly to the device line, position 1 goes to the first
+resource line and so on. Attempting to seek to a line that does not
+exist returns EOF.
+
+If opened and read sequentially, a file will initially give Line 0 (see
+below) and then each subsequent read will give a Resource Line until
+EOF. Note that the data may change on the fly (see Notes below).
+
+Data Format
+===========
+
+Device Line (Line 0)
+--------------------
+
+dev: <address> <name>
+
+There is a single space between each field.
+
+dev: = literal text identifying this as a device line.
+
+<address> = the address in kernel memory of the device data structure.
+The layout of "struct device" can be found in include/linux/device.h.
+This value is displayed in hexadecimal format using the "%p" format
+specifier and thus will vary in length depending on the word size of
+the kernel, e.g. 8 characters for a 32 bit kernel.
+
+<name> = the name of the device. This should be the same as the file
+name, it is included to facilitate identification when multiple
+devices are being listed. The length of this field can vary.
+
+Resource Line (Line 1 et seq.)
+------------------------------
+
+res: <address> <length> <data> <name>
+
+There is a single space between each field.
+
+res: = literal text identifying this as a resource line.
+
+<address> = the address in kernel memory of the resource data. The
+structure pointed to can vary depending on the type of resource.
+This field is encoded in hexadecimal format using the "%p" format
+specifier and thus will vary in length depending on the word size of
+the kernel, e.g. 8 characters for a 32 bit kernel.
+
+<length> = the length of the resource data. This field is encoded
+in decimal format, right justified, space filled and is always 9
+characters long.
+
+<data> = a hexadecimal display of the first 16 (or <length> if less)
+bytes of data starting for location <address>. If <length> is less
+than 16, this field is padded with spaces on the right to the full
+32 characters.
+
+<name> = a string indentifying the resource type. This is often a
+string containing the name of the function to be used to free the
+resource. Typical examples are "devm_kzalloc_release" which identifies
+a memory resource and "devm_gpio_release" which identifies a gpio
+resource. The length of this field can vary.
+
+Notes
+=====
+
+1. Once created, a file persists until the device is removed even
+ if all allocated resources are freed. This means that the existence
+ of the file is confirmation that at least one resource has been
+ allocated at some point, even if the device now has none allocated.
+
+2. Opening a file and holding it open will prevent the freeing up of
+ of the device - the final removal is held off until the file is
+ closed. Managed resources can still be freed if, say, a driver is
+ removed.
+
+3. Resources can, in principle, be allocated and freed on the fly so
+ it should not be assumed in general that seeking to a particular
+ resource line will always return the same resource. However, that is
+ a function of the device driver concerned and it may be a valid
+ assumption for some drivers, e.g. one that only allocates resources
+ during its .probe function.
+
+4. The device's list of resources is traversed from its base to the
+ requested item each time the file is read. During this scan resources
+ are spinlocked, which may have implications if resources can be
+ allocated during time critical code at the same time as a read is
+ taking place.
+
+5. A copy is taken of (up to) 16 bytes of resource data while the
+ spinlock (see note 4, above) is still in place and so can be relied
+ upon as an accurate snapshot at the time of the read but it must be
+ remembered that resources may change dynamically (see note 3, above)
+ so the memory may have changed subsequently.
+
+Sample Output
+=============
+
+root@arm:~# cat /sys/kernel/debug/devres/mmc0
+dev: ed980008 mmc0
+res: ed95a618 50 000000004884e8800001000039a695ed devm_kzalloc_release
+res: ed95cc58 4 02000000 devm_gpio_release
+res: ed95cc98 8 a2000000000098ed devm_irq_release
+root@arm:~#
+
+This shows three managed resources allocated to device "mmc0".
+
+1. A block of memory 50 bytes long (the first 16 byte are displayed).
+2. A GPIO.
+3. An IRQ.
+
+If 16 bytes of data is insufficient, you can try using other debugging
+tools to examine the data.
+
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index 8fa8dea..6a2f125 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -177,6 +177,24 @@ config DEBUG_DEVRES

If you are unsure about this, Say N here.

+config DEVRES_DEBUGFS
+ bool "Managed device resources debugfs file system"
+ depends on DEBUG_DEVRES
+ help
+ This option enables a debugfs file system related to Managed
+ Resources. When a device allocates a managed resource, with
+ this option enabled, a read-only file with the same name as
+ the device is created in the file system. Reading this file
+ provides some basic debug information about the managed
+ resources allocated to the device.
+
+ The overhead caused by enabling this option is quite small.
+ Specifically, a small memory overhead for each device and a
+ small time overhead at each resource allocation and
+ de-allocation.
+
+ If you are unsure about this, Say N here.
+
config SYS_HYPERVISOR
bool
default n
diff --git a/drivers/base/devres.c b/drivers/base/devres.c
index 5c88a27..41b6465 100644
--- a/drivers/base/devres.c
+++ b/drivers/base/devres.c
@@ -7,9 +7,13 @@
* This file is released under the GPLv2.
*/

+#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/module.h>
+#include <linux/seq_file.h>
#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>

#include "base.h"

@@ -58,6 +62,383 @@ static void devres_log(struct device *dev, struct devres_node *node,
#define devres_log(dev, node, op) do {} while (0)
#endif /* CONFIG_DEBUG_DEVRES */

+#ifdef CONFIG_DEVRES_DEBUGFS
+static const char * const devres_dbgfs_rootname = "devres";
+static struct dentry *devres_dbgfs_root;
+
+struct devres_dbgfs_link {
+ struct list_head list;
+ struct device *dev;
+ struct dentry *dp;
+};
+
+struct devres_dbgfs_private {
+ struct device *dev;
+ loff_t pos;
+ bool eof;
+};
+
+static struct devres_dbgfs_link *devres_dbgfs_lastused;
+static DEFINE_SPINLOCK(devres_dbgfs_lock);
+
+/**
+ * devres_dbgfs_seq_start - seq file iterator init for a devres debugfs file
+ * @s: pointer to seq file structure
+ * @pos: pointer to current file position.
+ *
+ * returns: pointer to private data fo use by devres_dbgfs_seq_next().
+ *
+ * Static debug function called when the user first reads from a device
+ * managed resources debugfs file. It initializes the file position in
+ * the private data structure and returns a pointer to a private structure
+ * for use by devres_dbgfs_seq_next().
+ */
+static void *devres_dbgfs_seq_start(struct seq_file *s, loff_t *pos)
+{
+ struct devres_dbgfs_private *priv = s->private;
+
+ priv->pos = *pos;
+ priv->eof = false;
+
+ return priv;
+}
+
+/**
+ * devres_dbgfs_seq_next - seq file iterator routine for a devres debugfs file
+ * @s: pointer to seq file structure
+ * @v: pointer to private data set up by devres_dbgfs_seq_next().
+ * @pos: pointer to current file position.
+ *
+ * Static debug function called when the user reads from a device managed
+ * resources debugfs file. It returns a pointer to a private structure used
+ * by devres_dbgfs_seq_next() and uppdates the file pointer.
+ */
+static void *devres_dbgfs_seq_next(struct seq_file *s, void *v, loff_t *pos)
+{
+ struct devres_dbgfs_private *priv = v;
+
+ if (priv->eof)
+ return NULL;
+
+ *pos = ++priv->pos;
+
+ return priv;
+}
+
+static void devres_dbgfs_seq_stop(struct seq_file *s, void *v)
+{
+}
+
+/**
+ * devres_dbgfs_seq_show - seq file output routine for a devres debugfs file
+ * @s: pointer to seq file structure
+ * @v: pointer to private data set up by devres_dbgfs_seq_next().
+ *
+ * Static debug function called when the user reads from a device managed
+ * resources debugfs file. It outputs to the user buffer using seq_file
+ * function seq_printf();
+ *
+ * This function locks devres_lock in the device structure.
+ */
+static int devres_dbgfs_seq_show(struct seq_file *s, void *v)
+{
+ struct devres_dbgfs_private *priv = v;
+ struct device *dev = priv->dev;
+ int size, i, pos = priv->pos;
+ struct devres *dr;
+ struct list_head *head;
+ struct list_head *item;
+ unsigned long flags;
+ char data[16];
+ char *dataptr;
+
+ if (pos == 0) {
+ seq_printf(s, "dev: %p %s\n", dev, dev_name(dev));
+ return 0;
+ }
+
+ spin_lock_irqsave(&dev->devres_lock, flags);
+
+ head = &dev->devres_head;
+ if (!head || list_empty(head))
+ goto out_eof;
+ item = head;
+
+ /* Walk the device resource list to item number *position */
+ while (pos--) {
+ item = item->next;
+ /* Check for end of list before required item */
+ if (item == head)
+ goto out_eof;
+ }
+
+ /* Node found, grab the details */
+ dr = container_of(item, struct devres, node.entry);
+ size = dr->node.size;
+ dataptr = (char *)dr->data;
+
+ /* Take a copy of the data before unlock */
+ memcpy(data, dataptr, (size < 16 ? size : 16));
+
+ spin_unlock_irqrestore(&dev->devres_lock, flags);
+
+ /* Print the node details */
+ seq_printf(s, "res: %p %9d ", dataptr, size);
+
+ for (i = 0; i < 16; i++) {
+ if (size-- > 0)
+ seq_printf(s, "%02x", data[i]);
+ else
+ seq_puts(s, " ");
+ }
+
+ if (dr->node.name)
+ seq_printf(s, " %s", dr->node.name);
+
+ seq_putc(s, '\n');
+
+ return 0;
+
+out_eof:
+ spin_unlock_irqrestore(&dev->devres_lock, flags);
+ priv->eof = true;
+ return 0; /* Seek past EOF */
+}
+
+static const struct seq_operations devres_dbgfs_seq_ops = {
+ .start = devres_dbgfs_seq_start,
+ .show = devres_dbgfs_seq_show,
+ .next = devres_dbgfs_seq_next,
+ .stop = devres_dbgfs_seq_stop,
+};
+
+/**
+ * devres_dbgfs_seq_open - open seq file for a devres debugfs file
+ * @ino: pointer to debugfs inode
+ * @fp: pointer to file structure
+ *
+ * Static debug function called when the user opens a device managed
+ * resources debugfs file. It registers with the device to ensure that
+ * the device usage count never drops to zero while the file is open.
+ * This prevents the device from being released if the file is held
+ * open even if its device driver deregisters. This can be useful for
+ * debugging a device even after its driver has been removed with rmmod.
+ *
+ * It then kmallocs a private structure used to keep track of the file
+ * position and the struct device, it exits via seq_open().
+ */
+static int devres_dbgfs_seq_open(struct inode *ino, struct file *fp)
+{
+ struct devres_dbgfs_private *priv;
+ struct device *dev = ino->i_private;
+ struct seq_file *seq;
+ int err = -ENOMEM;
+
+ /* Register as a user of the device to prevent it from */
+ /* going away while the file is still open */
+ if (!get_device(dev))
+ return -ENODEV;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ goto out;
+
+ priv->dev = dev; /* no need to set pos & eof */
+
+ err = seq_open(fp, &devres_dbgfs_seq_ops);
+ if (err)
+ goto out;
+
+ seq = fp->private_data;
+ seq->private = priv;
+
+ return 0;
+
+out:
+ kfree(priv);
+
+ return err;
+}
+
+/**
+ * devres_dbgfs_seq_release - release seq file for a devres debugfs file
+ * @ino: pointer to debugfs inode
+ * @fp: pointer to file structure
+ *
+ * Static debug function called when the user closes a device managed
+ * resources debugfs file. It de-registers from the device to allow
+ * the device to be released.
+ */
+static int devres_dbgfs_seq_release(struct inode *ino, struct file *fp)
+{
+ struct device *dev = fp->f_inode->i_private;
+ struct seq_file *seq = fp->private_data;
+
+ kfree(seq->private);
+
+ /* Deregister from the device to allow it to go away when ready */
+ put_device(dev);
+
+ return seq_release(ino, fp);
+}
+
+static const struct file_operations devres_dbgfs_seq_fops = {
+ .owner = THIS_MODULE,
+ .open = devres_dbgfs_seq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = devres_dbgfs_seq_release,
+};
+
+/**
+ * devres_dbgfs_get_dir - find dentry of the managed devices debugfs directory
+ *
+ * Static debug function that returns a pointer to the dentry of the debugfs
+ * directory used to contain the debugfs files for devices that use managed
+ * resources.
+ *
+ * This function must only be called if devres_dbgfs_lock is held.
+ *
+ * A return value of NULL means that the directory did not exist and cannot
+ * be created.
+ */
+static struct dentry *devres_dbgfs_get_dir(void)
+{
+ struct dentry *root;
+
+ if (devres_dbgfs_root)
+ return devres_dbgfs_root;
+
+ root = debugfs_create_dir(devres_dbgfs_rootname, NULL);
+ if (!IS_ERR(root))
+ devres_dbgfs_root = root;
+
+ return devres_dbgfs_root;
+}
+
+/**
+ * devres_dbgfs_find_filelink - find the filelink for a device's debugfs file
+ * @dev: device
+ *
+ * Static debug function that locates a link pointing to the dentry
+ * of a device's debugfs file.
+ *
+ * This function must only be called if devres_dbgfs_lock is held.
+ *
+ * Safe to call even if no such file or link exists, just returns
+ * NULL. For speed, the function always starts the scan of the list
+ * of links at the last one accessed, on the assumption that such
+ * searches will occur in clusters for the same device and there will
+ * only be an occasional change of device.
+ */
+static struct devres_dbgfs_link *devres_dbgfs_find_filelink(struct device *dev)
+{
+ struct devres_dbgfs_link *link;
+
+ link = devres_dbgfs_lastused;
+
+ if (!link)
+ return NULL;
+
+ while (1) {
+ if (link->dev == dev) {
+ devres_dbgfs_lastused = link;
+ break;
+ }
+ if (list_is_last(&link->list, &devres_dbgfs_lastused->list)) {
+ link = NULL; /* Not found */
+ break;
+ }
+ link = list_next_entry(link, list);
+ };
+
+ return link;
+}
+
+/**
+ * devres_dbgfs_remove_file - remove a device's debugfs file.
+ * @dev: device
+ *
+ * Static debug function called as part of the device release procedure
+ * IFF at least one managed reource has been allocated resulting in a
+ * file being created.
+ *
+ * It is safe to call without checking that the file has actually been
+ * created.
+ *
+ * This function locks devres_dbgfs_lock.
+ */
+static void devres_remove_debugfs_file(struct device *dev)
+{
+ struct devres_dbgfs_link *link;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devres_dbgfs_lock, flags);
+ link = devres_dbgfs_find_filelink(dev);
+ if (link) {
+ debugfs_remove(link->dp);
+ if (list_empty(&link->list))
+ devres_dbgfs_lastused = NULL;
+ else {
+ devres_dbgfs_lastused = list_next_entry(link, list);
+ list_del(&link->list);
+ }
+ kfree(link);
+ }
+ spin_unlock_irqrestore(&devres_dbgfs_lock, flags);
+}
+
+/**
+ * devres_dbgfs_create_file - create debugfs file for a device
+ * @dev: device
+ *
+ * Static debug function that will automatically create a debugfs file
+ * with the same name as the supplied device IFF the said file has not
+ * already been created.
+ *
+ * This function locks devres_dbgfs_lock.
+ */
+static void devres_dbgfs_create_file(struct device *dev)
+{
+ struct dentry *debugfsfile = NULL;
+ struct devres_dbgfs_link *link;
+ struct dentry *debugfsdir;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devres_dbgfs_lock, flags);
+
+ link = devres_dbgfs_find_filelink(dev);
+ if (link)
+ goto out_unlock;
+
+ debugfsdir = devres_dbgfs_get_dir();
+ if (debugfsdir)
+ /* Create file, n.b. dev goes into inode->i_private */
+ debugfsfile = debugfs_create_file(dev_name(dev), 0444,
+ debugfsdir, dev,
+ &devres_dbgfs_seq_fops);
+ if (!debugfsfile)
+ goto out_unlock;
+
+ /* Success: now create filelink entry in linked list */
+ link = kmalloc(sizeof(*link), GFP_KERNEL);
+ if (!link) {
+ debugfs_remove(debugfsfile);
+ goto out_unlock;
+ }
+
+ INIT_LIST_HEAD(&link->list);
+ link->dev = dev;
+ link->dp = debugfsfile;
+ if (devres_dbgfs_lastused)
+ list_add(&link->list, &devres_dbgfs_lastused->list);
+ devres_dbgfs_lastused = link;
+
+out_unlock:
+ spin_unlock_irqrestore(&devres_dbgfs_lock, flags);
+}
+#endif /* CONFIG_DEVRES_DEBUGFS */
+
/*
* Release functions for devres group. These callbacks are used only
* for identification.
@@ -220,6 +601,9 @@ void devres_add(struct device *dev, void *res)
spin_lock_irqsave(&dev->devres_lock, flags);
add_dr(dev, &dr->node);
spin_unlock_irqrestore(&dev->devres_lock, flags);
+#ifdef CONFIG_DEVRES_DEBUGFS
+ devres_dbgfs_create_file(dev);
+#endif /* CONFIG_DEVRES_DEBUGFS */
}
EXPORT_SYMBOL_GPL(devres_add);

@@ -511,6 +895,9 @@ int devres_release_all(struct device *dev)
/* Looks like an uninitialized device structure */
if (WARN_ON(dev->devres_head.next == NULL))
return -ENODEV;
+#ifdef CONFIG_DEVRES_DEBUGFS
+ devres_remove_debugfs_file(dev);
+#endif /* CONFIG_DEVRES_DEBUGFS */
spin_lock_irqsave(&dev->devres_lock, flags);
return release_nodes(dev, dev->devres_head.next, &dev->devres_head,
flags);
--
1.7.10.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/