For reference, if you want to just build the module(s), then they must be compatible with that currently running kernel. For that reason, write down this:
CONFIG_LOCALVERSION="-tegra"
This is what determines the suffix of “-tegra
” when you run the command “uname -r
”. If you copy the “/proc/config.gz
” somewhere safe, then this is one of the few parameters you can safely hand edit (you could use something like menuconfig
or nconfig
). If you gunzip
that file in some other location (you can’t edit in “/proc
”), then you could directly search for CONFIG_LOCALVERSION
, and then set it as I list above. The kernel source code of course has to also be an exact match.
Regarding source code version, there are a number of ways to get the correct version. However, if you know your L4T release and get the source code from NVIDIA at their download location for that L4T release, then this will always be valid. L4T is just what you call Ubuntu after it gets the NVIDIA content added to it. To find your L4T release see the output of “head -n 1 /etc/nv_tegra_release
”. For Orin it would be either an L4T R35.x or R36.x (always state your L4T release version when asking questions on the forums). Go to your L4T release here:
https://developer.nvidia.com/linux-tegra
The public sources is a package which contains other packages. One of those other packages is the kernel (and some out of tree content; the out of tree content isn’t always important, but often is). If you need help with that, then just ask.
Before explaining all of the compile I will mention some things that make life easier. You will always want your kernel source to remain pristine and clean. This means all builds and all configuration should be in some temporary location which is separate from the source code. This is the “O=/some/where
” option during build. If you don’t use that, build is in the source code tree itself. To make the source code tree completely clean and unconfigured you use this command:
make mrproper
Once that is done, then always use the “O=/some/where
” to name an empty directory location (it won’t be empty once you’ve done anything with the build). You can set an environment variable to more easily work with this. For example:
# This is a new empty location. Name is arbitrary.
mkdir ~/kernel
# This terminal will see this value. The name "TEGRA_KERNEL_OUT" is arbitrary, but happens to be
# what NVIDIA uses in its examples.
export TEGRA_KERNEL_OUT=~/kernel
# This should verify the location.
echo $TEGRA_KERNEL_OUT
From that point forward (in that terminal) any build command (other than mrproper
) would have this in it:
O=$TEGRA_KERNEL_OUT
For convenience, wherever the current kernel source is at, I will call that “TOP
”. If you first cd
to the location where you would normally compile kernel source from:
export TOP=`pwd`
echo $TOP
Most compile commands are from $TOP
.
You also must start with a configuration which matches the existing kernel. The default config target for L4T R35.x and earlier would tegra_defconfig
, but starting in L4T R36.x that default is “defconfig
”. The L4T R35.x has a lot of out-of-tree content, whereas R36.x is now mainline. You could start with that (assuming you set up $TEGRA_KERNEL_OUT
which is currently empty):
cd $TOP
# L4T R35.x:
make tegra_defconfig
# If L4T R36.x:
make defconfig
Remember that file you copied somewhere and appended “-tegra
” to the “CONFIG_LOCALVERSION
”? If the kernel was default, then this would be very similar, although it might order config lines differently. Just using defconfig
or tegra_defconfig
would also require you to add “-tegra
” to the CONFIG_LOCALVERSION
. To emphasize, the reason for this is to match the running kernel if and only if all of the “integrated” (“=y
”, non-modular) features are an exact match. Once an integrated feature changes you do not want CONFIG_LOCALVERSION
to match. We’re assuming everything you are editing will change only modules. That /proc/config.gz
(after gunzip
and after editing CONFIG_LOCALVERSION
) will always match because it is the actual running kernel which produced this. defconfig
and tegra_defconfig
only match if your kernel has a default configuration.
When you copy the /proc/config.gz
(after gunzip
and with CONFIG_LOCALVERSION
edited) to the new name “.config
”, and place it at the “O=/some/where
” ($TEGRA_KERNEL_OUT
) this replaces using defconfig
(or tegra_defconfig
). In fact, this is what “make defconfig
” (or tegra_defconfig
) does: It creates a .config
file in the output location of the kernel build. Regardless of whether you used a default target (such as make defconfig
) you will have “$TEGRA_KERNEL_OUT/.config
”.
Any editing software modifies “$TEGRA_KERNEL_OUT/.config
”, and so you have to remember to use “O=$TEGRA_KERNEL_OUT
” for everything you do. If ten people are using the same kernel source, but each person has their own $TEGRA_KERNEL_OUT
, then nobody will collide with someone else’s configuration. Or a single person could use multiple different $TEGRA_KERNEL_OUT
and different configurations, all using the same pristine and unmodified source code.
So here is a list of what you will probably actually do, assuming $TOP
and $TEGRA_KERNEL_OUT
are set (this does not mention the cross compile flags; I will add a version for cross compile later):
cp /proc/config.gz /some/safe/location/for/archive
cd /some/safe/location/for/archive
gunzip config.gz
# Now edit and set CONFIG_LOCALVERSION:
CONFIG_LOCALVERSION=-tegra
# You could also use quotes because it is a string:
CONFIG_LOCALVERSION="-tegra"
# We'll use the config.gz instead of a defconfig, but you could in a lot of cases also skip config.gz
# and instead use defconfig (L4T R36.x+) or tegra_defconfig (L4T R35.x or earlier).
cd $TEGRA_KERNEL_OUT
cp /some/safe/location/for/archive/config .config
cd $TOP
# Now we will configure your changes. Note that since there is a .config that what you start
# with is an unmodified exact match to your running kernel. The only differences will be from
# what you change. I use nconfig because it has a symbol search; every kernel feature has
# a symbol. CONFIG_LOCALVERSION is a symbol, and so is any change you make. Don't forget
# that if you fail to use O=/some/where, then your config will be missing.
make O=$TEGRA_KERNEL_OUT nconfig
# Use symbol search and find something you are interested in. Probably each of these, one at
# a time:
CONFIG_NETFILTER_XT_MATCH_RPFILTER
CONFIG_IP_SET
CONFIG_NETFILTER_XT_SET
CONFIG_NETFILTER_XT_MATCH_U32
CONFIG_IP_NF_SET
# At each of those use the 'm' key to enable as a module. Because this editor is dependency-aware
# any dependency will also be triggered. I don't know if CONFIG_IP_SET has dependencies, but if it
# does, then using 'm' to enable it will also set the other. This is rather important.
# Note that not all features can be integrated, nor can all features be a module. Most can be either.
# If you run into something which triggers an integration ("=y") instead of a module ("=m"), then you
# must change CONFIG_LOCALVERSION and you must reinstall everything. This includes modules and
# the kernel itself. Most likely you can just change modules.
# Then save and exit. This updates $TEGRA_KERNEL_OUT/.config. You could save that .config with
# an obvious name change in case you wish to start editing from that. You'd just copy it to a new
# $TEGRA_KERNEL_OUT with the name .config.
The above is only for configuration. That configuration is not yet propagated. If you build the main target, Image
, then this automatically propagates configuration throughout the source code. You don’t need to build Image
, although building it once as a test is advised. To manually propagate this configuration before building modules and skipping Image
compile:
make O=$TEGRA_KERNEL_OUT modules_prepare
If you choose to build Image
, which I advise as an “acid test” to see if all is in order:
make O=$TEGRA_KERNEL_OUT Image
To build modules:
make O=$TEGRA_KERNEL_OUT modules
There are steps to make finding what you want (after build) easier. Keep in mind that any kernel will look for its modules at:
/lib/modules/$(uname -r)/kernel/
(and half of uname -r
comes from CONFIG_LOCALVERSION
)
If you create another empty directory for modules to be output to, then there will be a temp location subdirectory named after the final module location. Let’s say you created this:
mkdir ~/modules
cd ~/modules
export INSTALL_MOD_PATH=`pwd`
echo $INSTALL_MOD_PATH
# You built modules, so put them in this new location:
cd $TOP
make O=$TEGRA_KERNEL_OUT modules_install INSTALL_MOD_PATH=$TEGRA_MODULES_OUT
If you now cd $TEGRA_MODULES_OUT
you will find this subdirectory:
lib/modules/$(uname -r)/kernel/
Somewhere within that is your kernel module. You need a few modules, and each will be at a location which mirrors where it was in the source code. There might for example be one of your files near here:
$TEGRA_MODULES_OUT/lib/modules/5.15.148-tegra/kernel/net/some_feature_of_networks/abc.ko
If you are only adding modular features, then you simply copy those files in that same subdirectory to the Jetson’s “/lib/modules/$(uname -r)/kernel/...wherever...
”. Then run “sudo depmod -a
”. Or reboot, or both.
Note: For this example I’m assuming your host has 12 CPU cores. In cases where more than one core can be used (sometimes dependencies stop this) you can use the job server. This is the “-j #
” option to make
. To use 12 cores “-j 12
”.
Here is a variation which is from cross compile; you will have to substitute the actual location of your cross tools (this is just my example and locations likely differ):
# Where is your actual cross tool chain?
export CROSS_COMPILE=/usr/bin/aarch64-linux-gnu-
# Only use ARCH when cross compiling. Do not set ARCH for native.
export ARCH=arm64
export TOP=/where/ever/kernel/source/is/at
mkdir ~/kernel
export TEGRA_KERNEL_OUT=~/kernel
echo $TEGRA_KERNEL_OUT
mkdir ~/modules
export TEGRA_MODULES_OUT=~modules
echo $TEGRA_MODULES_OUT
cp /saved/config/file/with/localversion/config $TEGRA_KERNEL_OUT/.config
cd $TOP
# You don't actually need to do this all the time, it is only if unsure of the source being pristine:
make mrproper
# This would be good even if you don't use
make nconfig
# Edit, changing only modules. If for some reason a change cannot be in the form of a module,
# then you must start over with a new CONFIG_LOCALVERSION and build everything...modules
# and kernel. Assuming we only need modules (and building Image is done once to see if it
# finds any errors, but Image won't be installed).
# Building Image, needed only once as a test:
make -j 12 O=$TEGRA_KERNEL_OUT Image
# If you did not build Image, then propagate the config:
make O=$TEGRA_KERNEL_OUT modules_prepare
# Build modules:
make -j 12 O=$TEGRA_KERNEL_OUT modules
# Distribute modules in the empty location:
make O=$TEGRA_KERNEL_OUT modules_install INSTALL_MOD_PATH=$TEGRA_MODULES_OUT
# Copy only the modules which are new to the Jetson's "/lib/modules/$(uname -r)/kernel/...somewhere...
# On the Jetson:
sudo depmod -a
# Reboot of the Jetson is advised, but you could manually test with "modprobe <module name>"
# manually if you wish.
That’s it. As complicated as it sounds, there is no flash required for adding modules if they match the running kernel. All of those steps can be put in a script, or copy and pasted with a few edits.