Some background might help first (if the headers are available you don’t need to do this, but it is useful to know this when you only have the full source code). A lot of people ask something similar, so this is more detailed than you probably want…
A kernel has a way of plugging in code while it runs, but this is limited to code which has been compiled to match that kernel. Those would be kernel modules, and you will tend to fail if the module was compiled against a kernel which differs in “integrated” features (there is a kernel itself, and this contains many many features, some of which could be instead turned into a module, but which are not; these features cannot be unloaded and you cannot load a module which also fulfills that feature). So the goal is to configure the source to match the kernel a module will be running against prior to building a module against that kernel.
If you have only headers this isn’t much of an issue, but there are ways which building kernel code using those headers still has to refer to the existing kernel configuration. Also, a “symbol” would be a term you could use interchangeably with “feature”.
If you use the full source instead of just modules, then you probably need to configure the source. A very important question: Are you building natively on the Orin Nano or are you cross compiling from a Linux desktop PC?
You would always want to start by writing down what the current response is on your Jetson from “uname -r”. The prefix/start of that response is just the kernel version; the suffix is what you set symbol CONFIG_LOCALVERSION to, and with NVIDIA, that is normally “-tegra”.
Your running system will also have this file which you should copy somewhere and name it after the “uname -r” response. Keep that file somewhere for future use as well:
/proc/config.gz
So I would copy that somewhere, and if the name is something like 5.15.0-tegra then the CONFIG_LOCALVERSION is “-tegra”, and you’d want the 5.15.0 source code, and probably rename your copy of config.gz something like config_5-15-0-tegra.gz (I’m just illustrating; file names of this are just for your use later on).
config.gz is an exact copy of your running Jetson kernel configuration at that moment, except that it does not copy CONFIG_LOCALVERSION. The two work together.
From the root of your kernel source, when natively compiling, you can get a pristine source code without contaminating leftover by this:
make mrproper
Normally I tell people to keep the full source pristine and only configure somewhere outside of the source tree. In kernel compile there is an option “O=/some/where” which instructs the compiler to make no changes in the source code itself and to place all configuration at this alternate location. If you want the kernel source itself to be configured and intentionally altered, then you would leave out the “O=...somewhere...” option; the result is that the kernel source is no longer “mrproper”. If you wish to set up the full source to match your running system, then basically you go through these steps:
mrproper
- Set the
CONFIG_LOCALVERSION (usually to “-tegra”) in the decompressed copy of the config.gz.
- Use that decompressed (and edited) copy to configure what would previously be the
mrproper source. There are different ways to do this.
You can find your specific L4T release via “head -n 1 /etc/nv_tegra_release”. After this you can then go to your specific L4T release URL and find the full kernel source (but it is a package within a package so it isn’t always obvious):
https://developer.nvidia.com/embedded/jetson-linux-archive
Note before next paragraph: There is kernel source itself, and there is also some out-of-tree content. It’s probably better to unpack both the main source and the out-of-tree content. Out of tree content is normally ignored unless something from out of the tree is configured. Most likely something out-of-tree is part of the configuration, so it is less likely to be an error to include this when you unpack. The sha1sum files can be ignored, although if you wanted to verify content you could look at those too (they are not part of configuration).
The “driver package sources” are for the flash software, and this ends up downloading as “public_sources.tbz2” (this package contains kernel packages and more). You can unpack one file within a tar archive and ignore the others. This leads to the kernel source package(s). You’d extract the kernel source package(s). Normally the location does not matter, but when you are using this to pretend to be headers the location matters. You can easily unpack this anywhere else and then later move it when you feel it is correct. The packages within the package will extract to subdirectory “Linux_for_Tegra/source/” (or create the directory if it doesn’t already exist). Most of the time you would find your host PC’s “Linux_for_Tegra/” directory and unpack from its parent directory, but in this case it doesn’t matter because you are not making it part of the flash software anyway (you could though if you want to). The fact is though that you’re going to have extra directories and so you might as well pick a temp location and then move or copy content later. An empty starting directory named “deleteme” is one of my favorite places to experiment so I know anything in it is not needed if I forget to remove unused content.
To see kernel packages from file “public_sources.tbz2”:
tar -tvf public_sources.tbz2 | grep -i kernel
You’ll get a list like this, among which I am ignoring the sha1sum files (I might be looking at a different source code version/release so results will vary):
-rw-r--r-- buildbrain/dip 2673631 2025-06-16 09:53 Linux_for_Tegra/source/nvidia_kernel_display_driver_source_without_root_dir.tbz2
-rw-r--r-- buildbrain/dip 2670038 2025-06-16 09:53 Linux_for_Tegra/source/nvidia_kernel_display_driver_source.tbz2
-rw-r--r-- buildbrain/dip 8451357 2025-06-16 09:53 Linux_for_Tegra/source/kernel_oot_modules_src.tbz2
-rw-r--r-- buildbrain/dip 156726253 2025-06-16 09:53 Linux_for_Tegra/source/kernel_src.tbz2
Notice that we can unpack these (which creates subdirectory “Linux_for_Tegra/source/”:
tar -xjf public_sources.tbz2 'Linux_for_Tegra/source/nvidia_kernel_display_driver_source.tbz2'
tar -xjf public_sources.tbz2 'Linux_for_Tegra/source/nvidia_kernel_display_driver_source_without_root_dir.tbz2'
tar -xjf public_sources.tbz2 'Linux_for_Tegra/source/kernel_oot_modules_src.tbz2'
Then:
cd Linux_for_Tegra/source/
You’ll see extracted files there, which will themselves need to be extracted. To extract those:
tar xvfj kernel_oot_modules_src.tbz2
tar xvfj nvidia_kernel_display_driver_source_without_root_dir.tbz2
So one would want to know whether to use the “without root dir” version or other. Whenever you enable a feature in the kernel you are setting a “symbol” to either integrated (with the “y” key, so it is “=y” in the config file) or as a module (with the “m” key so it “=m” in the config file). Enabling a feature for external content is normally as a module. Either way there is a Makefile which will reference a location of the feature within the kernel. Anything which ships with the kernel source will only refer to source code within that kernel source. External “out-of-tree” content means the relevant Makefile (there are many Makefiles, one can refer to another) points to a relative path in the filesystem which descends below the actual source code, and then back up into some directory tree not part of it. The out-of-tree source kind of does the reverse and descends out of its source and back up into the kernel itself. This out-of-tree source normally uses only the headers, but it also uses configuration which might be set up in the headers.
The location of the out-of-tree content needs to be able to find the kernel source. We don’t really care about the out of tree content in the main kernel source when we are just using it for a header replacement, but it is possible that configuring to match the existing kernel configuration might fail if that configuration points to out-of-tree content. Maybe it won’t, in which case you don’t need to worry about any of those downloaded kernel sources other than the “without_root_dir”.
So we start by looking where out-of-tree content might look for kernel headers. The current kernel always looks for modules at:
/lib/modules/$(uname -r)/kernel/
So now “cd /lib/modules/$(uname -r)/kernel/”. Then run “ls -l”. There will be one symbolic link named build. The “ls -l” tells you where the headers should go. In this case, the full source code works because the headers are a subset of the full source. On one computer (Ubuntu, but not a Jetson) this shows as:
build -> /usr/src/linux-headers-5.15.0-105-generic/
Note that in this case “5.15.0-105-generic” is the output of “uname -r”. *The kernel always looks for modules at “/lib/modules/$(uname -r)/kernel/”, and this is why the path for headers is “/usr/src/linux-headers-$(uname -r)/”. Whenever you compile third part out-of-tree content this is how that content finds headers.
So the topmost directory (with the first Makefile) of your full source will be placed at:
/usr/src/linux-headers-$(uname -r)/
Please note that once the kernel source is there you can always reset its configuration to pristine state with “sudo make mrproper”. I’m assuming this is owned by root and writable only by root. Out of tree content won’t ever change this source code, but you will be configuring it (as root). Other users only need to be able to read this content, not write it.
You will place your edited “config.gz” there at /usr/src/linux-headers-$(uname -r)/, decompressed to be config (gunzip config.gz, or gunzip of whatever name you gave it for clarity), then copied to . config to make it active. I purposely only copy and do not rename so that the config will be available as a reference at future dates. Within that .config you will find “CONFIG_LOCALVERSION” (once decompressed with gunzip it is a human-readable plain text file; for most entries never edit this directly, CONFIG_LOCALVERSION is an exception). If the output of your “uname -r” has a suffix of “-tegra”, then the CONFIG_LOCALVERSION line of .config will be:
CONFIG_LOCALVERSION='-tegra'
With that config in place all that is needed is to propagate the configuration throughout the source and your full source will now be configured as an exact match to the kernel now running:
make modules_prepare
If we were actually building a kernel from there we might need to mrproper again and make changes. If this kernel source were somewhere else and just used to build kernels and modules, then it would normally be kept pristine with “mproper” and the .config and modules_prepare would refer to another location with the “O=/some/where/else” option, but for this case, the kernel config matches the running kernel to be used as headers instead of building there.
It would be a lot easier if there were a .deb package to put that header in place, but then that package would only be good for that exact configuration. Building your own configuration on any source which matches a potentially modified kernel will work for all cases, not just for out-of-tree content expecting this.
Starting with L4T R36.x (and Thor uses L4T R38.x at this time) the kernel source is mainline. If you have mainline for the correct kernel, then you can use that with the NVIDIA out-of-tree drivers for display and such on top of that source code. A configuration can refer to that by relative path. If not, then you don’t even need the NVIDIA source code (it is possible you’re just using a configuration which does not explicitly name NVIDIA out-of-tree content). Put the config where it is supposed to be, run “sudo make modules_prepare”, and if it works, try building your module code. If not, report back what the error is.