Which specific L4T release does your Jetson run? See “head -n 1 /etc/nv_tegra_release
”.
Just as a general concept, compilers put out code for some architecture. Native compile implies the compiler runs on the same architecture that the code is output for. Simplified, this means the native compiler works with the environment it is in and does not substitute with special rules. Cross compilers run on one architecture, but provide output to another architecture. So long as this is bare metal there is no need to link without outside libraries…a kernel is bare metal, so are bootloaders. They don’t use libraries.
As soon as you go to user space, and not bare metal (kernel space), there is an environment you must “fit into”. You need a linker to help with that. A regular native linker just looks at the default system. A cross linker is a cross tool (like the cross compiler) for loading outside software into a program. A cross linker runs in one architecture, but links between a library and executable code that differs from the native environment.
The part which is so difficult about the latter cross linking in user space (not bare metal) is that not only do you need to provide the cross tool, you also need to provide the alternative environment’s libraries. To some extent this also includes things like file paths, environment variables, and everything an end program might inherit.
Basic kernel compile has the option to output compiled code into an alternate location to keep the source code pristine. This is nice because for example several end users could compile code simultaneously using the same source code in read-only mode. There isn’t much a cross compile of a bare metal kernel or kernel driver needs other than the correct source code and the correct source configuration. The cross compiler is enough for tools, and everything else the compile might require usually can be from the native system (e.g., maybe there is a string parsing tool…this is not architecture dependent since strings are strings are strings…unless you start getting into character sets under special circumstances).
First you have to understand native compile setup, which isn’t actually too extensive, but it is difficult to just give commands. Then, if you use the correct cross compiler setting, cross compile is otherwise no different. The temporary output location must be used for install rather than installing it to the host PC, but this is essentially just a file to copy in the case of a UART driver.
Do you have the full kernel source from your specific L4T release? Use “head -n 1 /etc/nv_tegra_release
” to find that. Then go to that URL:
https://developer.nvidia.com/linux-tegra
In JetPack 6.x/L4T R36.x this might just be a mainline kernel, but you must still use the same source code release as that which runs on your system. The L4T release URL should be able to get to the kernel source.
This is quite old, and this will have variations on how to do this (there is more than one way to do this), but this is the gist of steps (you can unpack kernel source as user root/sudo, but do not compile as root/sudo…keep your source code pristine):
# The locations mentioned are random and just examples. This is NATIVE compile
mkdir -p "${HOME}/build/kernel"
mkdir -p "${HOME}/build/modules"
# This will be quite a bit different for Orin...version 4.9 is outdated, so expect
# some other location name for an Orin's kernel (this is an example path):
export TOP="/usr/src/sources/kernel/kernel-4.9"
# These are all temporary output locations. You could delete them and recreate
# to start over. The environment variable names are more or less random, they
# could be any name for *these*. Cross compile is not considered here, but will
# be noted later.
export TEGRA_KERNEL_OUT="${HOME}/build/kernel"
export TEGRA_MODULES_OUT="${HOME}/build/modules"
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
# This sets up part of the default:
make O=$TEGRA_KERNEL_OUT tegra_defconfig
# Within the nconfig editor make sure CONFIG_LOCALVERSION is "-tegra".
# There are alternative methods to set this. You would also use this to find
# and set up the new UART driver as a module. nconfig has a symbol search
# function.
make O=$TEGRA_KERNEL_OUT nconfig
# You might be building only modules, but it is very strongly recommended to build
# the Image once as an acid test to see if configuration is valid.
# 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 put the resulting modules in an alternate location which mimics the
# structure of where the final module will actually be copied:
make -j 6 O=$TEGRA_KERNEL_OUT INSTALL_MOD_PATH=$TEGRA_MODULES_OUT modules_install
All of that is nearly the same in a cross compile. However, in a cross compile you need to set up a slight change to environment to use the cross tools and name the architecture. If you have cross tools, then those can be named in an environment variable.
Cross tools generally get prefixed in their names based on the target output. A kernel compile would require gcc
, and so it is likely that the cross-gcc is named aarch64-linux-gnu-gcc
, although there are variations on that too. Kernel build is rather nicely set up, and so there are simple ways to add this information.
Here is an alternative copy of the native build listed above, but for 64-bit ARM (a.k.a., aarch64
, or sometimes arm64
, used in ARMv8-a):
# The locations mentioned are random and just examples. This is aarch64 compile.
# Remember that you can delete these temp locations and start over, the kernel
# source itself is kept pristine since no output is being used there in this example.
# The output in home must be writable by the user, but the source code can be
# read-only and enforced by being owned by root. Then compile only as a regular user.
mkdir -p "${HOME}/build/kernel"
mkdir -p "${HOME}/build/modules"
# Specific to cross compile:
export CROSS_COMPILE=/usr/bin/aarch64-linux-gnu-
export ARCH=arm64
# Note: arm64 is the terminology in the kernel source, aarch64 is the cross tool
# terminology. The build steps will know what to do with the above. More explanation
# follows after this example.
# This will be quite a bit different for Orin...version 4.9 is outdated, so expect
# some other location name for an Orin's kernel (this is an example path):
export TOP="/usr/src/sources/kernel/kernel-4.9"
# These are all temporary output locations. You could delete them and recreate
# to start over. The environment variable names are more or less random, they
# could be any name for *these*. Cross compile is not considered here, but will
# be noted later.
export TEGRA_KERNEL_OUT="${HOME}/build/kernel"
export TEGRA_MODULES_OUT="${HOME}/build/modules"
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
# This sets up part of the default:
make O=$TEGRA_KERNEL_OUT tegra_defconfig
# Within the nconfig editor make sure CONFIG_LOCALVERSION is "-tegra".
# There are alternative methods to set this. You would also use this to find
# and set up the new UART driver as a module. nconfig has a symbol search
# function.
make O=$TEGRA_KERNEL_OUT nconfig
# You might be building only modules, but it is very strongly recommended to build
# the Image once as an acid test to see if configuration is valid.
# 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 put the resulting modules in an alternate location which mimics the
# structure of where the final module will actually be copied:
make -j 6 O=$TEGRA_KERNEL_OUT INSTALL_MOD_PATH=$TEGRA_MODULES_OUT modules_install
In any compile any use of the tool gcc
is prefixed by what you see in the CROSS_COMPILE
variable. If you’ve installed a cross tool at /usr/bin/aarch64-linux-gnu-gcc
, and if you have CROSS_COMPILE
set to /usr/bin/aarch64-linux-gnu-
, then all use of gcc
finds the cross tool by combining with CROSS_COMPILE
. It is similar for cross linkers, that same prefix is prepended. If the prefix is empty, then you get exactly that tool and not the cross tool
The “ARCH=arm64
” sets up for that bare metal. Do not use this if native compiling.
Note that if you don’t trust that the original source code is pristine and unmodified and unconfigured, that you can cd
to the top of the source and run this command to make it pristine:
sudo make mrproper
(I only used sudo
because I think kernel source should be owned by root and read-only to all others; it is the other users which compile the source, although there is no enforcement of that policy)
You will find the output in the temporary module output location is what you set up $TEGRA_MODULES_OUT
as. For example, if you created a driver in “net” called “sample.ko”, then you would find the arm64
version at (the “uname -r
” is that of the kernel source, not that of the host o/s):
${TEGRA_MODULES_OUT}/lib/modules/$(uname -r)/kernel/drivers/net/sample.ko
For this contrived example you would copy “${TEGRA_MODULES_OUT}/lib/modules/$(uname -r)/kernel/drivers/net/sample.ko
” to the Jetson’s “/lib/modules/$(uname -r)/kernel/drivers/net/sample.ko
”.
The big key is to make sure the kernel configuration takes place before making config edits. Then use something like nconfig
to add your driver as a module.