How to add a sysfs entry

From RidgeRun Developer Connection
Jump to: navigation, search

When I was first learning about Linux, the proc file system (procfs) was the coolest concept I had come across. The heart and soul of *ix is the big five system calls, open(), read(), write(), ioctl(), and close(). I was amazed to see key kernel data structures exported to user space via procfs and the big 5 - go Linux!

When kobject came into existence, that was a good time to clean up the mess that the ad-hoc procfs morphed into. sysfs is like procfs, just not quite so messy.

What is going on with my driver

Let's say you are trying create a driver that takes advantage of some complex hardware. For example, a Linux driver to support streaming audio/video, where you are using the hardware DMA sub-system, like the TI DM368 Enhanced DMA, that supports channels, slots, queues, and transfer controllers (and something that shows up in the driver as CC which I never figured out what it meant). Being completely lost and confused as to why the EDMA wasn't being utilized smoothly when streaming audio/video, we wanted to find out if the wrong transfer controllers were being used. Since we wanted to let the system run real-time, we choose not to use a kernel debugger. Instead, we spent 30 minutes adding a few sysfs entries to allow us to dump the information of interest.

Adding a sysfs entry

Typically when I add a sysfs entry, it is often to assist in driver testing and debugging. A typical example is simply counting how many times some event of interest occurs. For example, if we are interested in the number of times a transfer controller is used, we could add a counter for each TC, which are called TC0, TC1, TC2, and TC3 (yep, there are four of them).

You can add code similar to the follow to any Linux driver. In the probe() and remove() type functions, just add calls to the fuctions that make the calls to add/remove sysfs as shown below.

#include <config/auto.conf>
#include <linux/kernel.h>
#include <linux/sysfs.h>

static unsigned long tc_usage_count[4];

#ifdef CONFIG_SYSFS
static ssize_t edma_show_tc_usage(struct device *dev,
				   struct device_attribute *attr, char *buf)
{
	return snprintf(buf, PAGE_SIZE,
			"TC0: %ld\n"
			"TC1: %ld\n"
			"TC2: %ld\n"
			"TC3: %ld\n",
			tc_usage_count[0],
			tc_usage_count[1],
			tc_usage_count[2],
			tc_usage_count[3]);
		);
}

static DEVICE_ATTR(edma_tc_usage, 0444, edma_show_tc_usage, NULL);

static int edma_add_edma_show_tc_usage(struct device *dev)
{
	int err;

	err = device_create_file(dev, &edma_tc_usage);
	if (err < 0)
		goto fail;

fail:
	return err;
}

static void edma_remove_edma_show_tc_usage(struct device *dev)
{
	device_remove_file(dev, &(edma_tc_usage);
}

#else
#define edma_add_edma_show_tc_usage(dev) 0
#define edma_remove_edma_show_tc_usage(dev) do {} while (0)
#endif /* CONFIG_SYSFS */

Then in the driver function that is programming the transfer controller, increment the tc_usage_count[] assoicated with the controller being programmed.