Before you can control a device from a Linux kernel driver, you first have to find out what the kernel already knows about it. This lecture is part of our free Linux kernel development course and continues the device driver chapter of our free Linux device drivers course. We finish the quick command-line tools for spotting devices, then go deep into sysfs — the single most useful place to look when you’re bringing up a new embedded board on the latest stable Linux kernel.
What You Will Learn
Prerequisites
You should already be comfortable with major and minor numbers and character device nodes, covered in the previous lecture of this free embedded Linux course. A Linux VM or single-board computer running a recent kernel (6.x) and basic C are enough to follow the hands-on part.
Recap: Quick Ways to Spot a Device
Before reaching for sysfs, a few command-line tools give you a fast first look at what the kernel has registered:
cat /proc/devices— lists every registered driver by its major number and base name (for example a line like8 sdtells you major 8 belongs to the SCSI/SATA disk driver, without telling you how many actual disks are attached).ip link show— lists network interfaces the kernel currently sees, including loopback, Ethernet, and any USB network gadgets.lsusbandlspci— enumerate devices sitting on the USB and PCI buses respectively, pulling vendor and product IDs straight from the bus.
All of these are useful for a first pass, but none of them tell you the full picture: how a device connects to its parent, which driver bound to it, or what runtime attributes that driver exposes. For that, you need sysfs.
What sysfs Actually Is
Since the unified device driver model landed in the 2.6 kernel series, every device and every driver in the system is represented internally as a kernel object (kobject). sysfs is simply that internal object graph exported as a filesystem, mounted at /sys:
- A directory in sysfs corresponds to one kernel object — typically one device or one driver.
- A file inside that directory is an attribute — a single readable, and sometimes writable, property of that object.
- A symbolic link represents a relationship between two objects, most commonly a device pointing to the driver bound to it, or vice versa.
Because sysfs mirrors live kernel state, nothing you see there is stale — reading an attribute calls straight into the driver’s show() callback, and writing to a writable attribute calls its store() callback.
Top-Level Layout of /sys
Listing the root of sysfs on any modern Linux system shows a small, fixed set of directories:
# ls /sys
block bus class dev devices firmware fs kernel module power
Of these, three matter most when you’re hunting for a specific device or driver: devices, class, and block. Each one presents the same underlying kernel objects from a different angle.
/sys/devices — the Hardware View
/sys/devices is the kernel’s record of every device discovered since boot, arranged by the physical or logical bus it hangs off. What you see here depends heavily on the board, but three directories are present on essentially every system:
system/— devices at the core of the machine, such as CPUs and system clocks.virtual/— purely software, memory-backed devices. This is where/dev/null,/dev/zero, and the loopback network interfacelolive, undervirtual/memandvirtual/net.platform/— a catch-all for devices not attached through a discoverable hardware bus. On an embedded SoC, most on-chip peripherals (UARTs, I2C controllers, GPIO banks) show up here, because they’re described in the device tree rather than probed off a bus.
Anything else appears under a directory named after the real bus it sits on — a PCI root complex shows up as something like pci0000:00, a USB controller as usb1, and so on. The path from a bus root down to a specific device can get long, which is exactly why the other two views exist.
/sys/class — the Driver/Software View
/sys/class groups devices by the type of driver that manages them rather than by physical topology — a software-centric view. Serial ports appear under tty, network interfaces under net, input devices such as keyboards and touchscreens under input, and so on. Each entry inside a class directory is a symlink back to the device’s real location under /sys/devices.
Take a serial port as an example. On a typical x86 or ARM board with a hardware UART exposed as ttyS0:
# cd /sys/class/tty/ttyS0
# ls
close_delay flags line uartclk
closing_wait io_type port uevent
custom_divisor iomem_base power xmit_fifo_size
dev iomem_reg_shift subsystem
device irq type
The device symlink points back to the hardware node under /sys/devices, and subsystem points back up to /sys/class/tty — that pair of symlinks is the “relationship” part of the kobject model. Everything else is a plain attribute: some, like xmit_fifo_size, are UART-specific; others, like irq and dev, are common to many device classes.
The dev attribute deserves a closer look:
# cat /sys/class/tty/ttyS0/dev
4:64
That’s the major:minor pair for this device node. It’s the driver that creates this attribute when it registers the interface, and it’s exactly what udev or mdev read to decide what device node to create in /dev when you aren’t relying on devtmpfs to do it automatically.
/sys/block — the Block Device View
/sys/block is the equivalent view for block devices, with one subdirectory per block device the kernel knows about:
# ls /sys/block
loop0 loop1 loop2 loop3 nvme0n1 sda sr0
Drilling into a disk, such as sda, shows the device’s attributes alongside its partitions:
# cd /sys/block/sda
# ls
alignment_offset device queue sda1
bdi discard_alignment range sda2
capability ext_range removable size
dev holders slaves stat
device -> ../../devices/pci0000:00/.../ata1/host0/target0:0:0/0:0:0:0
Between /sys/devices, /sys/class, and /sys/block, you can build a complete picture of what a Linux system has found, which driver is bound to it, and what runtime attributes that driver is willing to let you read or tune — no source code required.
Hands-On: Exposing Your Own sysfs Attribute
Reading sysfs is useful, but a device driver becomes far more debuggable once you control what shows up there. The cleanest way to expose a custom, driver-defined value is a DEVICE_ATTR-based sysfs attribute attached to a misc device. Here’s a small original demo driver, ep_sysfs_demo, that registers one misc device and one read/write attribute called ep_value.
// ep_sysfs_demo.c
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/sysfs.h>
#include <linux/kstrtox.h>
static int ep_value;
static ssize_t ep_value_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sysfs_emit(buf, "%d\n", ep_value);
}
static ssize_t ep_value_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret = kstrtoint(buf, 10, &ep_value);
if (ret)
return ret;
return count;
}
static DEVICE_ATTR_RW(ep_value);
static struct attribute *ep_sysfs_attrs[] = {
&dev_attr_ep_value.attr,
NULL,
};
ATTRIBUTE_GROUPS(ep_sysfs);
static const struct file_operations ep_fops = {
.owner = THIS_MODULE,
};
static struct miscdevice ep_sysfs_demo_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ep_sysfs_demo",
.fops = &ep_fops,
.groups = ep_sysfs_groups,
};
static int __init ep_sysfs_init(void)
{
return misc_register(&ep_sysfs_demo_dev);
}
static void __exit ep_sysfs_exit(void)
{
misc_deregister(&ep_sysfs_demo_dev);
}
module_init(ep_sysfs_init);
module_exit(ep_sysfs_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala sysfs attribute demo");
A matching Makefile for out-of-tree builds:
obj-m += ep_sysfs_demo.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Build and load it, then watch the attribute appear under /sys/class/misc — the same class view we just explored:
$ make
$ sudo insmod ep_sysfs_demo.ko
$ ls /sys/class/misc/ep_sysfs_demo/
dev device ep_value power subsystem uevent
$ cat /sys/class/misc/ep_sysfs_demo/dev
10:126
$ cat /sys/class/misc/ep_sysfs_demo/ep_value
0
$ echo 42 | sudo tee /sys/class/misc/ep_sysfs_demo/ep_value
42
$ cat /sys/class/misc/ep_sysfs_demo/ep_value
42
$ sudo rmmod ep_sysfs_demo
Every misc device shares major number 10, which is exactly what the dev attribute confirms. The ep_value file you see is a direct product of DEVICE_ATTR_RW — reading it calls ep_value_show(), writing it calls ep_value_store(), and no other code had to change to make the attribute show up under /sys/class/misc/ep_sysfs_demo/.
Comparing the Three Views
| View | Organized by | Best for |
|---|---|---|
| /sys/devices | Physical/logical bus topology | Understanding how a device is wired into the system |
| /sys/class | Driver subsystem (tty, net, input, misc…) | Finding a device quickly when you know its type |
| /sys/block | Block device | Inspecting disks, partitions, and storage attributes |
Common Mistakes
- Assuming a symlink is a copy.
deviceandsubsystemunder a class entry are symlinks into/sys/devices— following them withcd -Porrealpathshows the true location. - Forgetting attributes are callbacks, not files on disk. A read or write to a sysfs attribute runs driver code synchronously; a buggy
show()/store()can block or crash the reading process. - Exposing more than one value per attribute file. The sysfs convention is one value per file — cramming multiple fields into one attribute breaks tooling that expects to parse a single value.
- Not checking the return value of
misc_register()or the store callback’s parsing function — both can fail, and silently ignoring that leads to confusing behavior later.
Best Practices
- Prefer
DEVICE_ATTR_RO/DEVICE_ATTR_RWand attribute groups over manually callingdevice_create_file()in every code path — groups are created and removed atomically with the device. - Always use
sysfs_emit()rather thansprintf()in ashow()callback; it enforces thePAGE_SIZEbuffer limit sysfs guarantees. - Validate every write in a
store()callback — sysfs input comes from user space and must be treated as untrusted. - Keep one logical value per attribute file so scripts and standard tools like
udevadmcan parse it predictably.
Summary
sysfs turns the kernel’s internal device-driver model into a plain filesystem: directories are kernel objects, files are attributes backed by driver callbacks, and symlinks capture the relationships between objects. /sys/devices shows you hardware topology, /sys/class shows you the software/driver view, and /sys/block gives you the same view specifically for storage. Once you can read this tree confidently, you can also add to it — as the ep_sysfs_demo driver shows, a single DEVICE_ATTR_RW declaration is enough to give your own driver a live, readable and writable knob in sysfs.
FAQ
What is the difference between /proc and /sys?
/proc is a much older, general-purpose interface originally meant for process information and later used for miscellaneous kernel settings. /sys was introduced specifically to represent the unified device driver model in a structured, one-value-per-file way, and is the preferred place for new device attributes.
Can I write to any file under /sys/class?
No — only files backed by a writable attribute (registered with something like DEVICE_ATTR_RW or DEVICE_ATTR_WO) accept writes. Read-only attributes reject write attempts with a permission error at the filesystem level.
Why does my custom device show up under /sys/class/misc instead of its own class?
misc_register() intentionally places every misc device under the shared misc class using major number 10. If you need your own class name, you would create a full character device with class_create() and cdev_add() instead.
Is sysfs available on every embedded Linux build?
Yes, as long as CONFIG_SYSFS is enabled, which it is by default in virtually every mainstream embedded Linux configuration, including Buildroot and Yocto default configs.
How is sysfs different from device tree?
Device tree describes hardware statically before boot so the kernel knows what to probe. sysfs is the live, dynamic result after probing — it reflects what the kernel actually found and bound drivers to, not what was merely declared.
What happens to a sysfs attribute if my module unloads?
Calling misc_deregister() (or class_destroy()/device_unregister() for a full class-based driver) removes the device and, with it, every attribute group registered against that device — nothing is left behind in /sys.
Continue the Free Linux Kernel Development Course
More device driver lectures, including probing, the driver/device match process, and platform drivers, are coming next in this free embedded Linux course.
Next Lecture Course Index
4 Comments