The kernel cleanup helpers โ __free(), guard() and scoped_guard() โ let the compiler release a resource when a variable leaves scope, replacing the goto ladder in a driver’s error path. The core arrived in mainline 6.5 and is now in every supported longterm series, down to 5.10. Everything built on top of it arrived later and reached the stable series at point releases rather than at .0, so your major version number does not tell you what you have. On an old vendor tree the practical answer is guard() plus a locally-defined free helper.
Most embedded teams ship a longterm kernel and assume anything added to mainline in the last three years is out of reach until the next BSP jump. For the kernel cleanup helpers that assumption is wrong, but the correction is not simply “you can use them”. The core macros are everywhere. The pieces you actually reach for โ a free helper for the specific thing you allocated, a guard for a lock that can fail โ landed at scattered point releases across the stable series, and a couple of the oldest trees still use a compiler standard that fights the coding pattern these helpers require. This is what that looks like on real trees.
The problem kernel cleanup helpers remove
The kernel documentation calls the “goto error” pattern notorious for introducing subtle resource leaks, and tedious and error prone to extend once a function already carries several unwind conditions. Concretely:
static int mydev_count_channels(struct mydev *priv)
{
struct device_node *np;
int ret, count;
np = of_get_child_by_name(priv->dev->of_node, "channels");
if (!np)
return -ENODEV;
mutex_lock(&priv->lock);
ret = mydev_sync(priv);
if (ret)
goto out_unlock;
count = of_get_child_count(np);
if (!count) {
ret = -EINVAL;
goto out_unlock;
}
ret = count;
out_unlock:
mutex_unlock(&priv->lock);
of_node_put(np);
return ret;
}The release sequence sits at the bottom, separated from the acquisitions at the top. Adding a third resource means adding a label, placing a release call correctly within the existing sequence, and re-checking every existing goto. The usual way it breaks is smaller than that: someone adds an early-return check near the top, does not notice a resource is already held above it, and returns directly. The compiler accepts it and the refcount leaks quietly.
What the compiler does instead
The kernel cleanup helpers move that bookkeeping to the compiler, using the cleanup variable attribute that GCC and Clang both support. You attach a function to a variable; the compiler calls it when the variable leaves scope, on every exit path, including paths added later by someone who has not read the rest of the function.
DEFINE_FREE() creates the wrapper โ a name, a type, and an expression using _T for the variable:
DEFINE_FREE(kfree, void *, if (_T) kfree(_T))The test looks redundant, since kfree(NULL) is safe. It is there for the code generator. On an ownership-transfer path the pointer is handed to the caller and set to NULL first, so the compiler sees this:
tmp = p; p = NULL; if (p) kfree(p); return tmp;Value propagation and dead-code elimination reduce that to return p; โ the cleanup call disappears. Without the test the compiler cannot make the deduction and the call survives. That elimination applies to the success path specifically; on a real error path the release happens, as it must.
Three helpers exist for that transfer, because ownership is the one thing scope-based cleanup cannot infer: no_free_ptr(p) nulls the variable and returns the value (with must-check semantics); return_ptr(p) is the same as a return statement; and retain_and_null_ptr(p) covers the case where a function you called has already taken ownership.
A conversion that builds on old trees
Here is the same function converted. Note the helper definition โ this is the part that matters for portability, and the reason is in the next section:
/* Define once, under a name no kernel header uses. */
DEFINE_FREE(mydev_of_node, struct device_node *, if (_T) of_node_put(_T))
static int mydev_count_channels(struct mydev *priv)
{
struct device_node *np __free(mydev_of_node) =
of_get_child_by_name(priv->dev->of_node, "channels");
if (!np)
return -ENODEV;
guard(mutex)(&priv->lock);
int ret = mydev_sync(priv);
if (ret)
return ret;
int count = of_get_child_count(np);
if (!count)
return -EINVAL;
return count;
}The labels are gone, every error path is a plain return, and the release is stated where the resource is acquired. The early-return mistake stops being a mistake: a return added by someone unfamiliar with the function still drops the node reference and releases the lock.
The cleanup order is also correct. np is declared before the guard, so the compiler releases in reverse โ mutex first, then the node reference โ matching the original’s tail exactly. That is safe here because dropping a node reference does not need the lock. When it does, the ordering becomes a trap, covered at the end.
guard() and scoped_guard() are not the same thing
guard(name)(args) holds the lock to the end of the enclosing scope. Written inside an if block, it releases at the end of that block, not at the end of the function โ what you want if you were thinking about the block, a surprise if you were thinking about the function. The kernel documentation spells this out with an example and the explicit note that the lock is held for the remainder of the if block. scoped_guard(name, args) { โฆ } binds the lock to the braces that follow it instead. Use guard() when the lock covers the rest of the function, scoped_guard() when the critical section is narrower.
Which kernels have which core helpers
| Feature | Mainline | In the longterm series |
|---|---|---|
__free, guard, scoped_guard | 6.5 | All โ 5.10.y, 5.15.y, 6.1.y, 6.6.y, 6.12.y, 6.18.y |
| Conditional guards | 6.8 | 6.6.y from 6.6.70; 6.12.y; 6.18.y. Absent from 5.10.y, 5.15.y, 6.1.y |
retain_and_null_ptr | 6.16 | 6.12.y in a late point release; 6.18.y |
ACQUIRE, ACQUIRE_ERR | 6.17 | 6.18.y only |
The mainline column is from the release tags. The longterm column is a snapshot taken while writing, and stable branches keep moving โ which is exactly why the conditional-guard row has to name a point release rather than a series. The 5.10.y, 5.15.y and 6.1.y headers are byte-identical to each other and to the one that shipped in 6.5: those three got the core and nothing since.
The subsystem helpers move separately
The table above covers include/linux/cleanup.h. It says nothing about whether the helper you actually need exists, and those live in the subsystem headers, on their own schedules.
The node-reference helper is the case in point, because it is the one an of_node conversion reaches for. DEFINE_FREE(device_node, โฆ) arrived in mainline 6.9 โ four releases after the core. It reached 6.6.y at 6.6.49 and 5.15.y only in the 5.15.21x range. It is not in 5.10.y at all. A vendor tree pinned anywhere below those points has the core macros and no node helper, so __free(device_node) does not compile even though guard() does.
The same unevenness shows up elsewhere. On 6.6.70 the mutex header offers conditional variants for trylock and interruptible acquires, but not the killable one; a file using scoped_guard(mutex_kill, โฆ) fails there despite the conditional-guard check passing. On a 6.6.30 tree the slab header defines exactly one free helper, DEFINE_FREE(kfree, void *, if (_T) kfree(_T)) โ no kvfree, no kfree_sensitive, and written in the plain-NULL form rather than the error-pointer-tolerant one used in current mainline.
Why the version number is not the answer
Conditional guards are in 6.6.y โ from 6.6.70, not 6.6.0. A BSP pinned at 6.6.30 does not have them, and does not have the node helper either. Vendors pin at whatever point release they forked from, stable series pick features up continuously, and the gap between those two facts is where the time goes. There is no version arithmetic that resolves it. You have to look at the headers.
How to check your tree
raghu@techveda.org:~$ grep -oE 'define (DEFINE_GUARD_COND|scoped_cond_guard|ACQUIRE_ERR)' "$KDIR/include/linux/cleanup.h"
define DEFINE_GUARD_COND
define scoped_cond_guardTwo lines means conditional guards but no ACQUIRE family โ a 6.12.y, or a 6.6.y at 6.6.70 or later. No output, with the header present, means the core only: 6.1.y or older, or a 6.6.x below 6.6.70. Current mainline prints five lines, because DEFINE_GUARD_COND is defined three times there under an argument-count dispatch.
For the subsystem helper, grep the header you actually need โ and match both spellings, because lock guards are frequently declared through the DEFINE_LOCK_GUARD generators rather than DEFINE_GUARD directly:
raghu@techveda.org:~$ grep -oE 'DEFINE_(FREE|GUARD|LOCK_GUARD_[01])' "$KDIR/include/linux/of.h" "$KDIR/include/linux/mutex.h"Searching only for DEFINE_GUARD returns nothing for mutex.h on current mainline, which would wrongly suggest guard(mutex) is unavailable.
Writing one file that builds on three kernels
This is the case the helpers make awkward, and it splits in two.
Macro features can be tested directly, because that is what they are:
#ifdef scoped_cond_guard
scoped_cond_guard(mutex_try, return -EBUSY, &priv->lock) {
/* ... */
}
#else
if (!mutex_trylock(&priv->lock))
return -EBUSY;
/* ... hand-written unwind ... */
mutex_unlock(&priv->lock);
#endifFree helpers cannot. __free(device_node) expands to __cleanup(__free_device_node), and __free_device_node is a static function generated by the macro, not a macro itself โ so there is nothing for #ifdef to test. Defining your own DEFINE_FREE(device_node, โฆ) to fill the gap collides on trees that already have it.
The way out is the one used in the conversion above: define your own helper under a name no kernel header will ever use, and use it unconditionally on every tree. It costs one line and removes the version question entirely.
DEFINE_FREE(mydev_of_node, struct device_node *, if (_T) of_node_put(_T))The old-toolchain problem on 5.10 and 5.15
One more obstacle applies to the two oldest longterm series, and it is the first thing that will stop a conversion there. Both 5.10.y and 5.15.y still build with -std=gnu89 and -Wdeclaration-after-statement. Scope-based cleanup requires declaring a variable at the point of acquisition, part-way through a function โ which is exactly the pattern that warning exists to reject. Every converted function produces warnings on those trees, and a BSP built with -Werror will fail outright.
From 6.1.y onward the kernel builds -std=gnu11 and the warning is gone, so the problem is confined to the two oldest series. If those are in your matrix, the honest position is that the helpers are available but the coding style they require is not, unless you can change the build flags.
Which branch to actually convert
Put those constraints together and the advice narrows usefully.
Convert the branch you intend to upstream. Mainline driver code uses these helpers as a matter of course now, and a submission that hand-rolls unwind paths where a guard would do will attract review comments. That branch has every helper, the newest toolchain, and a reviewer population that expects the style.
Leave the product branches alone unless you have a specific reason. Rewriting every error path in a driver that takes vendor patches on one branch and upstream fixes on another turns clean cherry-picks into three-way merges for the next two years, and on 5.15 you are fighting the compiler standard as well. The leak risk being avoided is real but small; the merge cost is certain.
Where you do convert on an older tree, guard() is the safe half. It is present everywhere, needs no subsystem support, and replaces the most error-prone part of the unwind โ the lock. Pair it with a locally-defined free helper and the file stays portable.
The wider question of where a driver should live when it has to track several kernels at once is covered in In-Tree vs Out-of-Tree Driver: Where Your Code Should Live.
The ordering trap
Cleanup functions run in reverse order of definition โ last defined, first released. That is usually right, since it mirrors acquisition order. It goes wrong when a function holds a lock and also owns an allocation whose release needs that lock held: if the allocation is declared before the lock is taken, it is released after the lock drops, outside the protected region. The kernel documentation walks through exactly this bug and annotates the offending call as happening with no lock held.
The fix is a habit. Define and initialise each resource in one statement, in the order the resources are acquired, rather than grouping declarations at the top of the function. The __free(...) = NULL-at-the-top pattern is what creates the interdependency, and the documentation recommends against it for that reason. The related rule is not to mix goto and cleanup helpers in one function โ a goto can jump between scopes in ways the cleanup attribute does not account for, so convert every resource in a routine or leave the routine alone.
Key takeaways
- The core kernel cleanup helpers are in every supported longterm series, down to 5.10.
guard()in particular is safe to adopt anywhere. - Everything else โ conditional guards, and the per-subsystem free helpers โ arrived at scattered point releases. A major version number does not tell you what you have.
__free(device_node)is unavailable on most pinned vendor trees. Define your own helper under a private name and the problem disappears.#ifdefworks for macro features such asscoped_cond_guard, but not for__freehelpers, which expand to a generated static function.- 5.10.y and 5.15.y build
-std=gnu89with-Wdeclaration-after-statement, which fights the declare-at-acquisition pattern the helpers require. - Convert the branch you intend to upstream; leave product branches alone unless there is a reason beyond tidiness.
- Cleanup runs in reverse order of definition, so declare each resource in acquisition order, and never mix goto with cleanup helpers in one function.
Frequently asked questions
Do I need a kernel config option to use the cleanup helpers?
No. They are macros in include/linux/cleanup.h, available to any code that includes the header. There is no CONFIG symbol involved.
My kernel is 6.6 and the conditional guard will not compile. Why?
They reached the 6.6 series at 6.6.70, not at 6.6.0. A tree pinned below that has the core helpers only. Check the header rather than the version number.
Why does __free(device_node) fail on my vendor tree?
That helper lives in include/linux/of.h, not in the core cleanup header, and it arrived later โ mainline 6.9, 6.6.y at 6.6.49, and not at all in 5.10.y. Define your own free helper under a private name and use it unconditionally.
Can I use these on a 5.15 vendor kernel?
The macros are present, but 5.15.y builds with -std=gnu89 and -Wdeclaration-after-statement, and scope-based cleanup requires declaring variables part-way through a function. Under -Werror that fails unless you can change the build flags.
What is the difference between guard() and scoped_guard()?guard() releases at the end of the enclosing scope, which inside an if block means the end of that block rather than the end of the function. scoped_guard() binds the lock explicitly to the block that follows it.
References
- Scope-based Cleanup Helpers โ The Linux Kernel documentation
- include/linux/cleanup.h โ mainline
- Merge tag core_guards_for_6.5_rc1 โ the merge that introduced the infrastructure
- include/linux/of.h โ the device-tree node free helper
- include/linux/mutex.h โ the mutex guard and its conditional variants
- include/linux/cleanup.h at v6.6.69 โ before the conditional guards
- include/linux/cleanup.h at v6.6.70 โ after
- The Linux Kernel Archives โ current stable and longterm series



