Creating debugfs files
debugfs
debugfs is a pseudo-filesystem used for kernel debugging. It is usually mounted at /sys/kernel/debug. debugfs contains files that allow us to read debugging information.
By default, only the root user can cd into the /sys/kernel/debug directory.
To change it to allow the current user to cd into debugfs, we can remount it with uid set to the current user’s uid.
sudo umount /sys/kernel/debug
sudo mount -t debugfs none /sys/kernel/debug -o uid=`echo $UID`
cd /sys/kernel/debug
Creating debugfs entries
Creating debugfs files is similar to creating character device files. It is done by defining functions and storing pointers to these functions in a file_operations structure which is then passed to the kernel.
- Create a debugfs directory using debugfs_create_dir function
This functions takes the directory name and the parent dentry.
If the parent dentry is set to NULL, the directory is created directly under /sys/kernel/debug,
otherwise it is created under the given parent directory.
The return value of the function is a pointer to a dentry or an error value.
We need to store this dentry pointer to create files in that directory and also to remove the directory in the exit function.
struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);
- In some functions like debugfs_create_dir, the return value is either a normal kernel space pointer or an error code.
The return value of these functions must be checked with IS_ERR macro to check if it is an error code or a valid pointer.
root_dentry = debugfs_create_dir("hello", NULL); if (IS_ERR(root_dentry)) return -ENODEV;
- Create debugfs files using the functions available in include/linux/debugfs.h.
Arguments of the above function:
struct dentry *debugfs_create_file(const char *name, umode_t mode, struct dentry *parent, void *data, const struct file_operations *fops);
- name : Name of the file to be created
- mode : Access permissions of the file
- parent : Dentry of the folder under which we want to create this file
- data : Pointer value that is stored for later use. inode.i_private will be set to this value on a open() syscall.
- fops : The file_operations struct initialized with pointers to the defined functions
- To expose the values of simple variables, we have functions for some basic types like u8, u16, etc.
void debugfs_create_u8(const char *name, umode_t mode, struct dentry *parent, u8 *value); void debugfs_create_u16(const char *name, umode_t mode, struct dentry *parent, u16 *value); void debugfs_create_u32(const char *name, umode_t mode, struct dentry *parent, u32 *value); void debugfs_create_u64(const char *name, umode_t mode, struct dentry *parent, u64 *value); void debugfs_create_ulong(const char *name, umode_t mode, struct dentry *parent, unsigned long *value);
- In the exit function of the kernel modules, we remove the debugfs entries using debugfs_remove function.
We pass the dentry pointer of debugfs directory we created. The function removes debugfs entries recursively, i.e. all files
under the directory will be removed.
void debugfs_remove(struct dentry *dentry);
An example: Echo file
The source code shown below is a kernel module that creates a simple debugfs file that
- When written to, will store upto a page of data
- When read from, will return the data that was last written
This is the same functionality that was implemented using char device in a previous post. I have highlighted the lines that are related to debugfs operations.
echo.c
|
|
Testing echo
- Build and load the module
make sudo insmod echo.ko ls /sys/kernel/debug/hello #.rw-rw-rw- 0 root 5 Nov 22:26 echo
- Write to the device
echo "Good Morning!" > /sys/kernel/debug/hello/echo
- Read from the device. It will return what was last written to it.
cat /sys/kernel/debug/hello/echo # Good Morning!
- Unload the module
sudo rmmod echo