For hardware your company owns and intends to ship for years, default to putting the driver in mainline and carrying only the part you have not landed yet. Keep it out of tree when the hardware is not public, the programming interface is still moving, or the code cannot be licensed GPL-compatible. You trade review effort now against a maintenance cost that returns on every kernel rebase for as long as the product exists.
You have built a peripheral of your own โ an FPGA block, a custom sensor bridge, a companion controller โ and someone has written a working Linux driver for it. Where should that driver live? The in-tree vs out-of-tree driver decision looks like packaging, because both paths produce a .ko that loads and works. It is not packaging. It sets who pays to keep the driver alive across the next five years of kernel releases.
The context
The decision arrives when the driver works on the kernel you are developing against and the schedule needs an answer on how it ships. Nothing forces your hand then, which is why the choice is usually made by default. Kbuild supports both paths, and the kernel’s build documentation is explicit: “The method for building either is similar, and all modules are initially developed and built out-of-tree.” Every driver starts outside the tree. The decision is about where it ends up.
Four forces act on it. The kernel you ship: if your SoC vendor supplies a heavily modified tree, upstreaming does not help this year’s product. Product lifetime: a device that ships once has different economics from one taking LTS updates for a decade. Disclosure: an in-tree driver publishes a description of your hardware, including a binding others will read. Licence: mainline requires GPL-compatible code, and many kernel symbols are exported with EXPORT_SYMBOL_GPL, which a non-GPL module cannot use.
The options
Option A: keep the driver out of tree
The driver lives in your own repository with its own Makefile, built against a prepared kernel tree:
raghu@techveda.org:~$ make -C /lib/modules/$(uname -r)/build M=$PWDIn a Yocto build you wrap the same thing in a recipe that inherits the module class, which sets KERNEL_SRC and KERNEL_PATH to ${STAGING_KERNEL_DIR} so the module Makefile finds the cross-built kernel. The hello-mod recipe in meta-skeleton is the template to copy.
What this buys you is control. You decide when the driver changes, you review it internally, you can hold back details of unreleased hardware, and you can update the module independently of the kernel image. It also works when you do not control the kernel โ a vendor BSP, a customer’s kernel, a certified build.
What it costs you, at build time, is that the kernel does not promise you an internal interface. Greg Kroah-Hartman’s statement of the position is direct: function names change, structures grow and shrink, parameters get reworked, and “all of the instances of where this interface is used within the kernel are fixed up at the same time.” Your driver is not one of those instances, so every rebase someone on your team fixes it.
Two further costs land at run time. Loading the module sets taint bit 12, the letter O, and the kernel documentation says plainly that “bug reports from tainted kernels will often be ignored by developers”. With CONFIG_MODVERSIONS, a symbol CRC mismatch means the kernel refuses to load the module, turning quiet API drift into a boot failure. External modules install under /lib/modules/$(KERNELRELEASE)/updates/, not kernel/.
Option B: put the driver in mainline
You submit the driver to the subsystem mailing list, supply a device tree binding as a YAML schema, add a MAINTAINERS entry, and iterate through review until it is merged. From then on it is selected by a CONFIG_ symbol like any other driver.
The main benefit is that maintenance moves off your team. When a subsystem is reworked, the person doing the rework updates your call sites, because the tree must keep building. The kernel documentation lists the rest: other developers add features, find bugs and tuning opportunities, update the driver when external interfaces change, and it ships in every distribution without you asking. There is no O taint. Review is easy to undervalue too โ bindings get read by maintainers who have seen several hundred, and the result is usually better than what you sent.
The costs are real. Review takes calendar time you cannot schedule precisely, commonly over several rounds. You must describe the hardware in public, which conflicts with an unannounced product. The code must be GPL-compatible. And landing in mainline does not deliver the driver to the kernel you ship this year โ you still maintain a backport branch until it catches up.
The decision: in-tree vs out-of-tree driver
For hardware your organisation owns and will ship over a multi-year life, resolve the in-tree vs out-of-tree driver question in favour of mainline. Write against current mainline interfaces from day one, submit as soon as the hardware is public, and treat the out-of-tree period as temporary, with a target kernel release attached.
The failure mode is almost never a team that consciously chose to stay out of tree. It is a team that never made the choice at all, and found three years later that the driver had accumulated enough local API workarounds that upstreaming was no longer small.
Staying out of tree is correct under conditions that should be stated rather than assumed:
- The hardware is unreleased, or under an NDA preventing publication of a register description. You cannot upstream what you cannot describe.
- The programming interface is still changing week to week. Submitting a binding you will contradict in two months wastes maintainer time and creates an interface you are expected to keep.
- The code cannot be licensed GPL-compatible. Mainline is then not available, and the out-of-tree cost is part of that constraint.
- The driver is single-product glue with no life beyond one board revision.
If none of those apply, the default holds. This is the per-driver version of the argument made at product level in Architecting Mainline-Friendly Products: the closer your kernel sits to mainline, the cheaper these decisions become.
Consequences
Choosing mainline means accepting review latency, and the device tree binding becomes an interface you cannot casually change, because other people’s device trees will use it. You still carry a backport branch, so the work does not go to zero โ it goes from open-ended to bounded.
Choosing out of tree means budgeting engineering time against every kernel version bump, and that budget is not a one-off. It is charged again on each LTS update, which is when you are least able to absorb schedule risk, because you are usually taking that update to close a CVE. The module must be rebuilt and retested first.
The update strategy is affected too. If kernel and module are delivered as separate packages they can drift out of step in the field, and a CRC mismatch at boot on a device you cannot reach is an expensive way to learn that. An A/B image update that ships kernel and modules together removes that failure mode.
Finally, an in-tree driver can be read by any engineer who knows the kernel tree. An out-of-tree driver is local knowledge, and it leaves with the person who has it. Reading drivers the way maintainers do is a skill built deliberately, through structured work on Linux kernel infrastructure.
Key takeaways
- Every driver starts out of tree. The in-tree vs out-of-tree driver decision is whether it stays there, and it should be recorded rather than assumed.
- The kernel offers no stable internal API. In-tree drivers are fixed by whoever changes the interface; out-of-tree drivers are fixed by you.
- Out of tree costs you upstream goodwill and load-time safety at once: taint bit 12 (
O) discourages upstream triage, andCONFIG_MODVERSIONSturns symbol drift into a refused load. - Staying out of tree is correct for unreleased hardware, unstable interfaces, GPL-incompatible code, or short-lived glue. State which applies, and price in the rebuild-and-retest work on every LTS update.
Frequently asked questions
Does an out-of-tree module always taint the kernel?
Yes. Loading an externally-built module sets taint bit 12, shown as the letter O in oops and panic output, regardless of licence. A proprietary module additionally sets bit 0, shown as P, and a staging driver sets bit 10, shown as C.
Can I upstream a driver and still ship it out of tree in the meantime?
Yes, and that is the usual path. Develop against mainline interfaces, submit for review, and package the same source as an external module for the kernel you ship today. Treat the out-of-tree build as temporary and attach a target kernel release to it.
What breaks first when an out-of-tree module falls behind?
Usually the build, because a function signature or structure changed. With CONFIG_MODVERSIONS, the next failure is at load time: the kernel compares symbol CRCs and refuses to load a module whose CRCs do not match.
How do I package an out-of-tree module in a Yocto build?
Inherit the module class in your recipe, then pull the resulting package into the image through a machine variable such as MACHINE_EXTRA_RDEPENDS. Without that second step the module builds but never reaches the rootfs.
Further reading
- The Linux Kernel Driver Interface โ why there is no stable in-kernel API.
- Building External Modules โ kbuild, install paths, Module.symvers, modpost.
- Tainted kernels โ the taint bit table.
- Submitting patches โ the upstream review process.
- Yocto Project: Incorporating Out-of-Tree Modules โ the module class and hello-mod.




