Need guidance: Proper kernel rebuild on Jetson Orin Nano (JetPack 6.0) with gs_usb, Wi-Fi, and header compatibility

Hello ,

I’m currently working on a mobile robot project using the Jetson Orin Nano 8GB Developer Kit, and I’ve been struggling with kernel customization issues for several months.

Background

My robot base communicates over CAN bus, but I found that JetPack 6.0 does not include the gs_usb kernel module, which is required for USB-CAN adapters.
(Reference: Missing gs_usb kernel module for JetPack 6)

To solve this, I decided to rebuild the Jetson kernel to include the gs_usb driver.
However, since I’m not very experienced with Linux kernel customization, I encountered various problems during the process.
These three threads record the issues I went through:

  1. sudo ./tools/l4t_update_initrd.sh error

  2. Jetson Orin Nano 8GB Development Board Kernel Customization and Ubuntu 22.04 Flash

  3. Wi-Fi not connecting after rebuilding the kernel

After these steps, I managed to boot and see the new kernel, and the gs_usb module worked.
However, I faced multiple follow-up issues:

  • Wi-Fi not connecting

  • Missing kernel headers for additional module builds

  • l4t_update_initrd.sh or initramfs update errors

  • Unstable environment after flashing

🔹 My current situation

My lab just purchased another Jetson Orin Nano 8GB, and I would like to start fresh — this time building a stable, fully functional kernel that:

  • Includes the gs_usb driver for CAN communication

  • Keeps Wi-Fi and NVIDIA modules working

  • Provides complete kernel headers for future development


🔹 My request

Could you please provide official, step-by-step guidance on:

  1. How to properly rebuild custom and flash the kernel (L4T 36.x, Ubuntu 22.04) with custom modules like gs_usb?

  2. How to ensure that existing NVIDIA kernel modules (Wi-Fi, camera drivers, etc.) continue to work after rebuilding?

  3. How to correctly set up kernel headers and dependencies to avoid build errors later?

Thank you for your support.
I really appreciate any advice or recommended workflow to help make the rebuild process cleaner and more reliable this time.

*** Please note that this reply is generated by LLM automatically ***

I’d be happy to help you with your kernel customization issues on the Jetson Orin Nano Developer Kit.

Background and Context

It seems like you’ve been struggling with kernel customization issues for several months, specifically with including the gs_usb kernel module for USBCAN adapters. You’ve encountered various problems during the process, including errors with sudo tools/l4t/update-initrd.sh, kernel customization, and WiFi connectivity issues after rebuilding the kernel.

Current Situation and Goals

You’ve recently purchased another Jetson Orin Nano Developer Kit and would like to rebuild the kernel with the following requirements:

  1. Include the gs_usb driver for CAN communication.
  2. Keep WiFi and NVIDIA modules working.
  3. Provide complete kernel headers for future development.

Rebuilding Custom Kernel and Flashing L4T 36.x Ubuntu 22.04

To properly rebuild a custom kernel and flash the L4T 36.x Ubuntu 22.04 with custom modules like gs_usb, follow these steps:

  1. Prepare the build environment:
    • Install the necessary tools and dependencies, including build-essential, linux-headers, and linux-image.
    • Set up the kernel source tree by running sudo apt-get source linux-image-$(uname -r).
  2. Configure the kernel:
    • Run make menuconfig to configure the kernel. Enable the gs_usb module and any other required modules.
    • Save the configuration and exit.
  3. Build the kernel:
    • Run make -j$(nproc) to build the kernel. This may take several hours depending on your system’s processing power.
  4. Create a kernel package:
    • Run make deb-pkg to create a kernel package.
  5. Install the kernel package:
    • Run sudo dpkg -i linux-image-*.deb to install the kernel package.
  6. Update the initrd:
    • Run sudo tools/l4t/update-initrd.sh to update the initrd.
  7. Flash the L4T 36.x Ubuntu 22.04:
    • Follow the instructions in the NVIDIA documentation to flash the L4T 36.x Ubuntu 22.04 with the custom kernel.

Ensuring Existing NVIDIA Kernel Modules, WiFi, and Camera Drivers Continue to Work

To ensure that existing NVIDIA kernel modules, WiFi, and camera drivers continue to work after rebuilding the kernel:

  1. Use the NVIDIA-provided kernel configuration:
    • Use the nvidia-tegra.cfg file as a starting point for your kernel configuration. This file is located in the linux-headers package.
  2. Enable NVIDIA kernel modules:
    • Enable the NVIDIA kernel modules in the kernel configuration, including nvidia, nvidia_modeset, and nvidia_uvm.
  3. Use the NVIDIA-provided WiFi and camera drivers:
    • Use the NVIDIA-provided WiFi and camera drivers, which are included in the L4T 36.x Ubuntu 22.04 package.

Correctly Setting Up Kernel Headers and Dependencies

To correctly set up kernel headers and dependencies:

  1. Install the kernel headers package:
    • Run sudo apt-get install linux-headers-$(uname -r) to install the kernel headers package.
  2. Install the kernel image package:
    • Run sudo apt-get install linux-image-$(uname -r) to install the kernel image package.
  3. Update the kernel dependencies:
    • Run sudo apt-get update to update the kernel dependencies.

Recommended Workflow

To make the rebuild process cleaner and more reliable, follow these best practices:

  1. Use a version control system:
    • Use a version control system like Git to track changes to your kernel configuration and source code.
  2. Test the kernel thoroughly:
    • Test the kernel thoroughly before deploying it to your robot base.
  3. Keep a backup of your kernel configuration:
    • Keep a backup of your kernel configuration and source code in case you need to revert to a previous version.

I hope this helps you rebuild a custom kernel and flash the L4T 36.x Ubuntu 22.04 with custom modules like gs_usb. If you have any further questions or issues, feel free to ask!

*** Please give the thumbs up if you get answers you like. Or provide feedback to help us improve the answer. ***

Because the previous operation caused many follow-up issues, I would like to get a real (human) reply this time and a more detailed step-by-step guide.

Hi,
The steps of kernel customization are clear in developer guide and we would suggest take a look at:

Kernel Customization — NVIDIA Jetson Linux Developer Guide

And may refer to the real practices:
No data from Joystick Logitech-f710 - #10 by DaneLLL
Orin Nano Dev Kit: RT Kernel OTA on r36.4 w/ Jetpack 6.1and NVME SSD - #5 by DaneLLL

Thank you for your reply.
I actually followed those steps before, but in the end, I encountered several issues such as missing header files, Wi-Fi connection failure, and other related problems.
Could you please tell me how to avoid these issues and ensure that my customized kernel and system remain stable?

hello, missing header files might cause other errors in my future development as well. Therefore, I would like to get a complete and reliable guide on how to properly build and customize a stable kernel.

If you don’t have headers, then probably you can use the full source after configuring it for your current running kernel (including CONFIG_LOCALVERSION, the suffix from the output of “uname -r”).

Okay, could you please tell me exactly what I should do step by step?
By the way, this is another post I madehttps://forums.developer.nvidia.com/t/jetson-orin-nano-wi-fi-backports-build-fails-due-to-missing-kernel-headers-after-custom-kernel-rebuild/347872/5, which includes detailed information about my Wi-Fi connection issue and the missing header files.
Thank you very much.

Hello, what should I do?

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.

1 Like

Thank you very much for your detailed and patient guidance.I’ll follow your advice and try compiling natively on the Jetson.

I successfully solved the problem following your guidance. Thanks!

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.