There are different ways to do this, with some being easier than others. Some terminology to start will help…
If you have a Linux desktop PC running Ubuntu 18.04, then this will cover official instructions. This is because the official instructions are for “cross compiling”. Meaning use of the PC to compile for an architecture different than its own (in this case the “different” architecture is arm64/aarch64, the Jetson’s architecture, while the host PC is x86_64/amd64). If you were to instead build software on the Jetson for the Jetson, then this is “native” compile, and is the simplest compile (however, a Jetson may lack disk space or other resources which the PC would consider “trivial” to provide).
Either type of compile will turn out to be “easier than it looks” if you’ve done this once (I am providing a lot more information than you need if you were just following a recipe, so don’t be intimidated…what follows is to actually understand rather than following a recipe). Both native and cross-compile of kernel and/or module build has much in common. Once you have content to put in place though you should be careful to install that content with the easiest and/or safest method. Modules do not need any flashing, though some instructions will use flash to do this. Avoid flashing when you are just installing modules.
You should note the output of this command on the Jetson before you start: “uname -r
”. This is related to both compile setup and installation of modules. As an example I’ll pretend the result of the command is “4.9.140-tegra
”. In this name the base kernel version is “4.9.140
”, and is determined by the kernel version itself. The “suffix” of “-tegra
” (including the hyphen) is related to source configuration at the moment the 4.9.140
source was compiled. This suffix is important, as explained below. This applies whether using cross-compile or native compile.
The file “/boot/Image
” is the actual kernel (this can be loaded from a partition as well, but normally it would be this file…no need to flash if it is the file, and it is safer to not flash if you can just put a file in place, but default official instructions use flash). Modules are part of the kernel, but they are dynamically loaded by the kernel at a later time, and can be loaded or unloaded at will. The location which the kernel finds its modules at is why the “uname -r
” matters. That kernel will search for modules (which are just files and never part of a partition) here:
/lib/modules/$(uname -r)/kernel
For reference, when you see comments on setting the “CONFIG_LOCALVERSION
” prior to kernel or module compile, this refers to the suffix of “uname -r
”, and the default is usually:
CONFIG_LOCALVERSION="-tegra"
If your kernel source version and the CONFIG_LOCALVERSION
during module build are the same as your running kernel, then you can simply copy the module file to the correct subdirectory of “/lib/modules/$(uname -r)/kernel
”. If you do build the entire kernel and place it as “/boot/Image
”, and if the new kernel had a matching configuration to the old kernel (but perhaps with an added feature), and if that Image
has the same “uname -r
”, then the new Image
will find and use all of the old modules at the same location as which the old kernel found them. If not, then replacing the Image
would in fact also require building and installing all of the modules into the new location with the changed “/lib/modules/$(uname -r)/kernel
”. Just getting the configuration as a correct match, including “CONFIG_LOCALVERSION
” (and thus indirectly “uname -r
”) will save you much effort.
There is actually more than one way to specify the CONFIG_LOCALVERSION
, but I included what would be an edit to the kernel configuration file at build time. Other documents will start a compile with an environment variable, which has the same result.
The official documents which come with a particular release provide this information in the “kernel customization” section. This cross compile information is easy to use from a host PC. Just skip the part where it uses flash to install and ask here if you need to know more about copying a file into the Jetson. You should basically compile the entire kernel once as an “acid test” to see if configuration is correct, but only copy the new module in place once done. Native compile on the Jetson is a bit simpler, but the Jetson would probably run out of disk space unless you build on external storage, e.g., a thumb drive, or perhaps your SD card is larger than minimum (see “df -H -t ext4
” to see your relevant disk usage). If you don’t have a Linux PC, then this is a convenient alternate should you have sufficient disk space.
The particular L4T release (Ubuntu plus NVIDIA hardware accelerated drivers) can be found via:
head -n 1 /etc/nv_tegra_release
The document URL for that particular release can be found here (and the documentation there has a document with the kernel customization official information):
https://developer.nvidia.com/linux-tegra
If you want more information kernel naming and module install without flashing, see:
https://forums.developer.nvidia.com/t/jetson-nano-board-setup/163668/6
A URL similar to the one just above, but with more detail on finding and setting features, is here:
https://forums.developer.nvidia.com/t/attempts-to-set-up-ecryptfs-and-fscrypt-failed-and-failed-and-failed/119083/2
The gist is that configuring by any method will create a file “.config
” at the top of the kernel source code build location. The build target “tegra_defconfig
” sets this up as a match to the default a Jetson would ship with. Alternatively, the file “/proc/config.gz
” can be extracted from the Jetson and renamed as “.config
” to get a nearly exact match of the running kernel’s config (not needed if you just want the default, but suppose you’ve configured something new before…then the “/proc/config.gz
” would be a superior config starting point compared to make target “tegra_defconfig
”). In every case you’d need to set the CONFIG_LOCALVERSION
. In each case there are various ways to propagate the new config before building a module (and if you’ve started by building “Image
”, then you don’t need to propagate anything…it just takes time to build an “Image
”, and I recommend building it once anyway just to see if things work with that config). An example of not building Image
first would be to run the make command “modules_depend
” before building modules.
The above is a lot of reference. If you want to, then just start with the official docs and come back after you’ve cross-compiled the module you want. The reality is that having done this just once you’ll find it to be fairly trivial to build and install a kernel module.
Incidentally, here is something of a short recipe “cheat sheet” for native compile if the source code is on the Jetson (this shows how to build everything, not just modules, so you wouldn’t care about things like firmware):
# --- Setting Up: -------------------------------------------------------
# DO NOT BUILD AS ROOT/SUDO!!! You might need to install source code as root/sudo.
mkdir -p "${HOME}/build/kernel"
mkdir -p "${HOME}/build/modules"
mkdir -p "${HOME}/build/firmware"
export TOP="/usr/src/sources/kernel/kernel-4.9"
export TEGRA_KERNEL_OUT="${HOME}/build/kernel"
export TEGRA_MODULES_OUT="${HOME}/build/modules"
export TEGRA_FIRMWARE_OUT="${HOME}/build/firmware"
export TEGRA_BUILD="${HOME}/build"
# --- Notes: ------------------------------------------------------------
# It is assumed kernel source is at "/usr/src/sources/kernel/kernel-4.9".
# Check if you have 6 CPU cores, e.g., via "htop".
# If you are missing cores, then experiment with "sudo nvpmodel -m 0, -m 1, and -m 2".
# Perhaps use "htop" to see core counts.
# Using "-j 6" in hints below because of assumption of 6 cores.
# -----------------------------------------------------------------------
# Compile commands start in $TOP, thus:
cd $TOP
# Do not forget to provide a starting configuration. Probably copy of "/proc/config.gz",
# to $TEGRA_KERNEL_OUT, but also perhaps via:
make O=$TEGRA_KERNEL_OUT nconfig
# If building the kernel Image:
make -j 6 O=$TEGRA_KERNEL_OUT Image
# If you did not build Image, but are building modules:
make -j 6 O=$TEGRA_KERNEL_OUT modules_prepare
# To build modules:
make -j 6 O=$TEGRA_KERNEL_OUT modules
# To build device tree content:
make -j 6 O=$TEGRA_KERNEL_OUT dtbs
# To put modules in "$TEGRA_MODULES_OUT":
make -j 6 O=$TEGRA_KERNEL_OUT INSTALL_MOD_PATH=$TEGRA_MODULES_OUT
# To put firmware and device trees in "$TEGRA_FIRMWARE_OUT":
make -j 6 O=$TEGRA_KERNEL_OUT INSTALL_FW_PATH=$TEGRA_FIRMWARE_OUT
(the above has temporary output to the “TEGRA_KERNEL_OUT
” location so as to not interfere with the pristine default source code; similar for temporary output locations to avoid mixing temporary content with source code, and to avoid installing directly to the Jetson without having an opportunity to install in the easiest manner)
Note: The feature CONFIG_CIFS_SMB2
is also known as a “symbol” if you are performing kernel config. Config editors can set this for you if you search for CIFS_SMB2
(to be added after base configuration matches the running kernel). There are multiple ways to do this, but when you have your base kernel config built you can then use an editor to add this “symbol” (or feature). The result, if built as a module, is that the .config
file has this added to it, and the actual build would produce the CIFS kernel module for copy to the correct location.