If you have already created a debugfs file using explicit read/write callback functions, you already know it takes real effort — a file_operations structure, a read handler, a write handler, buffer copying with copy_to_user() / copy_from_user(), and careful bounds checking. That’s the right approach when you need control. But what if all you want to do is expose a single integer or boolean variable from your kernel module so you can peek at it, or nudge it, from a shell? Writing a full set of callbacks for that is overkill.
This is exactly the gap that debugfs helper APIs fill. In this lecture of our free Linux kernel programming course, you’ll learn how the debugfs layer lets you bind a numeric or boolean kernel global directly to a virtual file — no read/write callback required.
What You Will Learn
- Why debugfs ships “shortcut” helper functions separate from
debugfs_create_file() - How to expose an unsigned integer global (8/16/32/64-bit) as a debugfs file, in decimal and hexadecimal form
- How to expose a boolean flag through debugfs so user space can flip it with a single
echo - The one important trade-off every kernel developer must know before using these helpers
- What has changed in the debugfs API on modern 6.x kernels compared to older references
Prerequisites
This lecture assumes you’re comfortable with basic Linux kernel module (LKM) programming — writing a module_init()/module_exit() pair, building with a simple Makefile against kernel headers, and loading modules with insmod/rmmod. If you’re new to LKMs, work through the earlier lectures in this free Linux kernel development course before continuing.
Why “Helper” debugfs APIs Exist
The debugfs pseudo-filesystem is the standard, informal channel kernel developers use to expose internal driver and subsystem state for debugging. Normally, creating a debugfs entry means calling debugfs_create_file() and supplying a file_operations structure with your own .read and .write handlers. That’s flexible, but for a huge number of real-world cases — a counter, a log level, a feature flag — all you actually want is “let user space see this variable and let user space change this variable.” Writing a bespoke read/write handler for every such variable is repetitive and error-prone.
The debugfs layer solves this with a family of purpose-built helper functions that bind a debugfs file directly to the address of a variable in your module. Internally, the kernel already knows how to serialize and parse an integer or a boolean, so it does that work for you.
cat / echo on the debugfs file
built-in parse / format logic
u32, u64, bool, …
Numeric Helper APIs (Decimal)
For unsigned integers of different widths, debugfs provides one helper function per size. Each takes the same four arguments: the file name, the file permissions, the parent directory dentry, and — the important part — the address of the variable you want bound to that file.
struct dentry *debugfs_create_u8 (const char *name, umode_t mode,
struct dentry *parent, u8 *value);
struct dentry *debugfs_create_u16(const char *name, umode_t mode,
struct dentry *parent, u16 *value);
struct dentry *debugfs_create_u32(const char *name, umode_t mode,
struct dentry *parent, u32 *value);
struct dentry *debugfs_create_u64(const char *name, umode_t mode,
struct dentry *parent, u64 *value);
Once you call, say, debugfs_create_u32() on a module-level u32 log_level;, that variable is instantly readable and writable from a shell:
# cat /sys/kernel/debug/<your_module>/log_level
0
# echo 3 > /sys/kernel/debug/<your_module>/log_level
# cat /sys/kernel/debug/<your_module>/log_level
3
No callback function was written. The kernel handled formatting the value on read and parsing the text on write.
Hexadecimal Variants
Many kernel values — flags, register contents, bitmasks — are far easier to read and write in hexadecimal than decimal. debugfs provides a parallel set of helpers purely for that purpose:
struct dentry *debugfs_create_x8 (const char *name, umode_t mode,
struct dentry *parent, u8 *value);
struct dentry *debugfs_create_x16(const char *name, umode_t mode,
struct dentry *parent, u16 *value);
struct dentry *debugfs_create_x32(const char *name, umode_t mode,
struct dentry *parent, u32 *value);
struct dentry *debugfs_create_x64(const char *name, umode_t mode,
struct dentry *parent, u64 *value);
These behave identically to the decimal versions except that reads return a hex string and writes accept hex input. Everything else — the underlying variable, the permission bits, the parent directory — works exactly the same way.
| Helper Function | Variable Type | Display Format |
|---|---|---|
| debugfs_create_u8/u16/u32/u64 | u8, u16, u32, u64 | Decimal |
| debugfs_create_x8/x16/x32/x64 | u8, u16, u32, u64 | Hexadecimal |
| debugfs_create_size_t | size_t | Decimal (size-portable) |
| debugfs_create_bool | bool | Y / N text |
Tip: On systems where the exact bit-width of a variable can differ between architectures (32-bit vs 64-bit builds), use debugfs_create_size_t() instead of guessing a fixed width — it automatically matches the platform’s size_t.
The Boolean Helper
Toggling a feature on or off is such a common debugging need that debugfs gives it a dedicated helper:
struct dentry *debugfs_create_bool(const char *name, umode_t mode,
struct dentry *parent, bool *value);
A read from this file returns either Y or N. A write accepts Y, N, 1, or 0 — anything else is rejected. This makes it a natural fit for enabling or disabling a debug trace, a simulated fault path, or a feature flag in your driver straight from user space.
The Trade-Off: No Validation
Here’s the catch every kernel programmer must understand before reaching for these helpers: because the debugfs core is doing the parsing internally, your module never gets a chance to validate the incoming value. If your log_level is only meant to range from 0–3, nothing stops a user from writing 99 through the numeric helper — it will be accepted and stored as-is.
There are two common ways to deal with this in practice:
- Push validation to user space. Wrap the raw debugfs file in a small shell script or admin tool that checks the value before writing it. This matches the classic UNIX philosophy of “mechanism, not policy” — the kernel provides the mechanism, and policy decisions are left to whoever is operating the system.
- Fall back to
debugfs_create_file(). If in-kernel validation is genuinely required — for example, the value controls something safety-critical — skip the shortcut helpers and write an explicitfile_operationshandler where your.writecallback checks the input before accepting it.
What’s Changed on Modern (6.x) Kernels
If you’re following along from an older textbook or blog post, note a few things that are different — or worth re-emphasizing — on current mainline kernels:
- Ignore the return value. Kernel documentation now explicitly recommends that drivers not check the
struct dentry *returned by these helpers for errors. debugfs failures are considered non-fatal by design, and checking return values across dozens of debugfs calls just adds clutter. The only time you need the returned pointer is if you plan to remove that specific entry individually later — and even then, the recommended pattern (covered in the next lecture) is to remove the whole directory tree at once instead. - debugfs may not exist at all. On kernels built without
CONFIG_DEBUG_FS, every debugfs_create_* call becomes a safe no-op stub. Your module still loads and runs correctly; the files simply won’t appear anywhere. - More helpers exist today. Beyond what’s shown here, current kernel headers also expose helpers such as
debugfs_create_atomic_t()for atomic counters anddebugfs_create_str()for simple strings — worth exploring once you’re comfortable with the numeric and boolean cases in this lecture.
Real-World Use Cases
These helpers aren’t just teaching examples — they’re used throughout the mainline kernel:
- Storage and MMC/SD card drivers expose internal counters and capability flags through numeric and boolean debugfs helpers so developers can inspect controller state without instrumenting the driver.
- Networking drivers commonly expose a boolean debugfs file to toggle verbose packet tracing on a live system.
- Power-management code exposes numeric thresholds so engineers can experiment with tuning values during bring-up, without rebuilding the kernel each time.
Common Mistakes and Troubleshooting
| Mistake | Why It Happens | Fix |
|---|---|---|
| Passing the wrong-width helper for a variable | Using debugfs_create_u32() on a u64 global |
Match the helper suffix exactly to your variable’s declared type |
| Expecting write-time validation | Assuming the kernel rejects “invalid” numbers | Validate in user space, or switch to a custom file_operations handler |
| Boolean file “not working” | Writing true/false text instead of Y/N/1/0 |
Only Y, N, 1, or 0 are accepted |
| Files missing after boot | debugfs not mounted or CONFIG_DEBUG_FS disabled |
mount -t debugfs none /sys/kernel/debug if not auto-mounted |
Best Practices
- Use the numeric/boolean helpers only for genuinely simple, non-critical debug variables.
- Group related debugfs files under a single parent directory created once at module init.
- Document valid ranges for every debugfs numeric file in your module’s help text or a companion README, since the kernel won’t enforce them for you.
- Never expose security-sensitive kernel state through debugfs on production systems — debugfs is a debugging aid, not a stable or access-controlled ABI.
Security Considerations
debugfs files are typically root-only by convention, but permission bits are whatever you pass in the mode argument — it is entirely possible to misconfigure a debugfs file as world-writable. Since these helper functions perform no semantic validation, an overly permissive debugfs file can become a way for an unprivileged or compromised process to push unexpected values straight into kernel memory. Always set the tightest permission bits that still meet your debugging needs, and never ship debugfs interfaces as a production control-plane.
Summary / Key Takeaways
- debugfs helper APIs let you bind a kernel numeric or boolean variable to a virtual file without writing any read/write callback.
- Decimal helpers:
debugfs_create_u8/u16/u32/u64. Hex helpers:debugfs_create_x8/x16/x32/x64. Platform-portable:debugfs_create_size_t. Flags:debugfs_create_bool. - The trade-off is zero input validation — push validation to user space, or fall back to a custom
file_operationshandler when it truly matters. - On modern kernels, the return value of these helpers is safe to ignore, and debugfs itself may be compiled out entirely without breaking your module.
Conclusion
debugfs helper APIs are one of the fastest ways to get visibility into a running kernel module while you’re developing and debugging Linux device drivers. They trade fine-grained control for speed of development — a trade-off that’s usually well worth it for anything that isn’t safety-critical. In the next lecture of this free Linux kernel programming course, we’ll look at how to correctly remove these debugfs entries when your module unloads, and what actually happens inside the kernel when you get that cleanup wrong.
FAQ
Q1. Do I need to check the return value of debugfs_create_u32() and similar functions?
No. Modern kernel guidance treats debugfs as a best-effort debugging aid, so checking every return value is unnecessary in normal driver code.
Q2. What happens if CONFIG_DEBUG_FS is disabled in the kernel config?
All debugfs_create_* calls become harmless stub functions. Your module still loads and runs correctly; the debug files simply never appear.
Q3. Can I validate input written to a debugfs numeric helper file?
Not directly — the helper functions perform no validation. Either validate in the user-space tool that writes to the file, or use debugfs_create_file() with your own write callback.
Q4. What’s the difference between debugfs_create_u32 and debugfs_create_x32?
Both bind to the same u32 variable type. The “u” variant reads and writes decimal text; the “x” variant reads and writes hexadecimal text.
Q5. Why would I use debugfs_create_size_t instead of a fixed-width helper?
Because size_t itself changes width between 32-bit and 64-bit builds. This helper automatically matches whatever width size_t has on the target platform.
Q6. Can unprivileged users read or write debugfs files?
Only if you set permissive mode bits when creating the file. By convention, debugfs files are typically restricted to root; always set the tightest permissions your debugging workflow allows.
Q7. Are these helper APIs suitable for production drivers shipped to customers?
debugfs in general is intended for debugging and development, not as a stable production interface. Avoid relying on it for functionality that must be guaranteed to exist on every kernel build.
This lecture is part of EmbeddedPathashala’s free Linux kernel development course and free Linux device drivers course, covering everything from your first kernel module to advanced embedded systems programming.
Explore the Full Course
2 Comments