Cannot use USB devices

Hi everyone,

I’m using a (Aetina) Jetson AGX Orin AIE-KX23 running Ubuntu 22.04 with JetPack 6.

It comes with a OTG, USB-C and 2 x USB-A ports.

USB storage devices work correctly, but when I connect other peripherals such as USB-to-RS232 converters, Wi-Fi adapters, or Arduino boards, I cannot find any assigned device address.

When I run:

sudo dmesg -d

I only see messages like:

new full-speed USB device number 8 using tegra-xusb

but no device path such as /dev/ttyACM0 or /dev/ttyUSB0 appears.

I’m used to working on x86 systems where these devices automatically get assigned a device path. Am I missing a configuration step on the Jetson platform?

I’m totally stuck with my project since I purchased this system for robotics research and it is critical to me to be able to plug USB converters or, in general, USB devices. Also the RS232 is not working.

Any suggestions would be greatly appreciated.

Thanks!

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

I’d be happy to help you troubleshoot the issue with your Jetson AGX Orin AIEKX23.

Firstly, it’s great that you’ve mentioned that USB storage devices work correctly, which suggests that the USB ports are functioning properly. However, the issue with other peripherals not being assigned a device path is puzzling.

On x86 systems, the Linux kernel typically assigns device paths to USB devices automatically, as you’ve mentioned. However, on embedded systems like the Jetson AGX Orin, the device tree and kernel configuration play a more significant role in determining how devices are enumerated and assigned device paths.

Here are a few potential reasons why you might not be seeing device paths assigned to your peripherals:

  1. Device Tree Configuration: The device tree is a data structure that describes the hardware components of your system. On the Jetson AGX Orin, the device tree is used to configure the USB ports and other peripherals. It’s possible that the device tree configuration is not enabling the USB ports or peripherals correctly. You can check the device tree configuration by running the command dtc -I fs /sys/firmware/devicetree/base.
  2. Kernel Configuration: The Linux kernel configuration also plays a role in determining how devices are enumerated and assigned device paths. You can check the kernel configuration by running the command zcat /proc/config.gz | grep USB.
  3. USB Driver Configuration: The USB driver configuration might also be a factor. You can check the USB driver configuration by running the command lsusb -t.

To troubleshoot the issue, I recommend trying the following steps:

  1. Check the kernel logs: Run the command dmesg | grep usb to check the kernel logs for any USB-related messages. This might give you an idea of what’s happening when you connect your peripherals.
  2. Use the lsusb command: Run the command lsusb -v to get a detailed list of all USB devices connected to your system. This might help you identify if the peripherals are being detected by the system.
  3. Check the device tree configuration: Run the command dtc -I fs /sys/firmware/devicetree/base to check the device tree configuration. Look for any USB-related nodes and check if they are enabled correctly.
  4. Try a different USB port: Try connecting your peripherals to a different USB port to see if the issue is specific to one port or all ports.

If none of these steps help, it’s possible that there’s a configuration issue specific to your system or peripherals. You might need to consult the Jetson AGX Orin documentation or seek help from the NVIDIA developer community.

In comparison to x86 systems, the Jetson AGX Orin has a more complex device tree and kernel configuration, which can make it more challenging to troubleshoot USB issues. However, by following these steps, you should be able to identify the root cause of the issue and find a solution.

I hope this helps! If you have any further questions or need more specific guidance, feel free to ask.

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

Thank you a lot for your feedback!

Because the output text for each command is too long to post directly, I have attached different text files related to each command. I hope this helps to find out what’s happening on my device.

Thank you again!

kernel-config.txt (15.3 KB)

usb-related-msgs.txt (4.5 KB)

usb-config.txt (736 Bytes)

tree-config.txt (316.9 KB)

lsusb.txt (17.5 KB)

For background, the desktop x86 works the same way as the Jetsons do with their arm64 architecture. USB itself though is often misunderstood.

To start with, if you just run “lsusb”, and the device shows up, then USB itself has done its job and is probably working. Note that lsusb will have an “ID” associated with the device, and you can limit your lsusb query to just that device ID (quite useful when there is more than one device because a verbose listing is huge). Basically, run and show just “lsusb”. Then, if the device in question has ID of 067b:2303, you can limit to this via:
lsusb -d 067b:2303

Then, you can make this fully verbose (probably not needed in this case though since it is working):
sudo lsusb -d 067b:2303 -vvv
(you need to be root/sudo for full verbosity)

USB is a hot plug system. USB devices have information in them for self-describing. Once the device is detected there will be a broadcast to a hot plug layer to announce this device description to drivers. It is at this point the PL2303 needs a driver specific to it (USB driver is running, otherwise you’d not see the device in lsusb, but that driver is for the data pipe, not the device attached to the data pipe). Even so-called “USB devices” like mice require this extra driver; it just so happens that USB ships with some standardized drivers for certain devices following a generic standard (e.g., a mouse or joystick will use the Human Interface Device class…HID…although the driver for running a generic mouse comes with USB it is not itself a USB driver…it is just a mouse driver shipping with USB and always present).

Your PL2303 driver is missing. This is not a “standard” class device. On a desktop PC though there are a lot of drivers shipped (in the form of a loadable module) which are not present by default on an embedded system. It is a case of needing to copy the driver module file to the right place.

Note that any file in “/dev” is a result of the device’s driver. Those files are not real files, and are just a driver pretending to be a file in RAM. As soon as the driver loads with a valid argument (the device specs are the argument) you will see that file show up.

Before L4T R36.x (L4T is just Ubuntu plus NVIDIA drivers, and is what gets flashed) the kernel was custom configured by NVIDIA. This included the PL2303 driver. As of R36.x the mainline kernel is now used, and the mainline kernel’s default configuration does not include the PL2303 driver. You do not need to follow official documents for rebuilding an entire kernel and putting it in flash software and then flashing; all you need is to copy the file in place and then run “sudo depmod -a” or reboot.

About getting that file: You will need to build it. Someone else has recently asked about this, and the procedure is starting to show up here:
https://forums.developer.nvidia.com/t/is-there-an-uncomplicated-method-to-support-usb-to-serial-adapters-using-a-pl-2303-chipset/343430/10

The full instructions depend on knowing if you are cross compiling on a host PC versus natively compiling on the Jetson itself. Jetsons are very capable of compiling this kernel and kernel and kernel modules, but this does require a lot of disk space and some installations won’t have enough storage. The official docs are for cross compiling (you would cross compile there, but then you’d only need to perform a file copy instead of requiring a flash to complete this). You can see how much disk space you have via:
df -H -T /
(or name a specific directory, e.g., “df -H -T /home/someone/tmp”)

Btw, you can find your L4T release version via “head -n 1 /etc/nv_tegra_release”. Then you can go to the URL for software and documentation specifically for your release (including things like kernel source):
https://developer.nvidia.com/linux-tegra

If you give details on whether you are natively compiling versus cross compiling more information can be provided.

@linuxdev Thank you for your reply! This is not encouraging since it means that I will have to build the driver for each USB device that I will have to use for my system, i.e., video cameras (like the Orbbec or Intel), other USB-to-RS232 converters, and so on. However, if I fully understand how to build the driver for the PL2303, then building drivers for the other devices will likely be easier.

This is the version I have:
R36 (release), REVISION: 3.0, GCID: 36191598, BOARD: generic, EABI: aarch64, DATE: Mon May 6 17:34:21 UTC 2024

while this is the PL2303 device:

nvidia@localhost:~$ lsusb -d 067b:2303
Bus 001 Device 016: ID 067b:2303 Prolific Technology, Inc. PL2303 Serial Port / Mobile Action MA-8910P
nvidia@localhost:~$ lsusb -d 067b:2303 -vvv
Bus 001 Device 016: ID 067b:2303 Prolific Technology, Inc. PL2303 Serial Port / Mobile Action MA-8910P
Couldn’t open device, some information will be missing
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x067b Prolific Technology, Inc.
idProduct 0x2303 PL2303 Serial Port / Mobile Action MA-8910P
bcdDevice 4.00
iManufacturer 1 Prolific Technology Inc.
iProduct 2 USB-Serial Controller D
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0027
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 100mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 3
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x000a 1x 10 bytes
bInterval 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x83 EP 3 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0

This is what I get for the PL2303:

nvidia@localhost:~$ zcat /proc/config.gz | grep -i ‘pl2303’

#CONFIG_USB_SERIAL_PL2303 is not set

My kernel version: 5.15.136-tegra

I have 42GB left on the hard disk of my Jetson, so I think I can use it to compile the driver. Otherwise, I have my x86 daily pc with Ubuntu 22.04 and 1TB of available space.

Unfortunately, I have no internet connection on my Jetson since the USB Wi-Fi adapter is, obviously, not recognized, and I cannot use the RJ45 cable. Really, I cannot understand how someone can use this kind of Jetson if no USB devices are recognized! :-)

I cannot understand why the RS232 is not working since in this case no driver is needed, and I was expecting to have it work without problem. The permissions are fine, so it is not a problem about accessing the device.

Thank you

There is the trick: The USB devices are recognized, but there is no driver. Kind of reminds me an old saying which I find humorous, “the lights are on, but nobody is home”. The shorter answer though is that yes, you have to compile the missing drivers.

It is actually somewhat easier to do this than it sounds, but the first build setup can seem intimidating. Once the kernel source is in place there are ways to make this vastly simple compared to a first build.

Have you noticed that during a normal flash of the AGX Orin that there is a USB cable connected between the Jetson and the host PC? Something you may not be aware of is that during a flash (when using JetPack) the Jetson will actually reboot when the flash is done, and then any optional components are added over ssh after the admin account first boot setup is complete. That same USB cable should continue to be available as a virtual network device even if USB is failing as a “host”. Restated, normally the USB port is a host port for plugging in devices, e.g., mice; however, some ports are intended to be a device and this mode can work even if other USB function is not available.

If you monitor “dmesg --follow” on your host PC, and you take a fully booted and running Jetson, and then connect that cable (USB-C for newer systems, this is what I’m betting on), then the log message from the USB connect event on the host will probably tell you that you now have a network connection to the Jetson from the host PC. If so, then the host can ping or communicate with the Jetson at 192.168.55.1. The Jetson would be able to ping or communicate with the host PC at 192.168.55.100. scp, rsync, sftp, and just about any other system could then work to copy kernel modules, kernel source, so on.

Furthermore, if you’ve set up the kernel source and configuration on a USB thumb drive which is large enough, then this could easily be moved between different Jetsons or host PCs and serve for build regardless of which system is doing the build. Lacking the PL2303 driver won’t harm USB mass storage (this is one of those “generic” device classes which always have a driver available).

I don’t know exactly how much space would be required for a thumb drive, but probably you could be comfortable and not have too many surprises with a 16 GB thumb drive. Once you go to 64 GB you could save multiple system builds.

I do not know if you are aware of this, but a kernel build is best accomplished with the original source tree being read-only and pristine, followed by using the “O=/some/where” to direct any output to some temporary alternate location. This includes the configuration file. What this means is that you could put the source tree on the thumb drive as read-only (you’d first “sudo make mrproper” to make sure it is pristine before making it read-only), and have a different “O=/some/where1”, “O=/some/where2”, so on, for each system.

If you do come up with a proper configuration, then the file has the name .config, and you could copy this to some archive location with a descriptive name. After that your builds could simply start with a copy of the named archived file to the name .config at the “O=/some/where” location and build. I might save a .config with some name like “config-5.15.136-tegra-pl2303_agx_orin”. Do note though that it is important set up the CONFIG_LOCALVERSION string in the .config; if the integrated features do not change, and only modules change, then this is best left as “-tegra”; if this did change, then you would have to build and install all modules, and not just the feature you are interested in. The file “/proc/config.gz” is almost an exact copy of the running system’s configuration…what it lacks is the CONFIG_LOCALVERSION.

One could even create simple scripts to copy the current “/proc/config.gz” if natively compiling.

FYI, there is a similar way to name installing modules, device tree, so on, to a temp location. For example, the “INSTALL_MOD_PATH=/some/where/modules” would cause the make target “modules_install” go to “/some/where/modules/” (it is a full tree of “/lib/modules/$(uname -r)/kernel/...”).

External USB SATA drive enclosures are really good at this since they are faster than a thumb drive.

I will suggest running “dmesg --follow” on the host PC and seeing what shows up when you plug in the fully running Jetson using the same USB cable which flashing occurs from (USB-C; there might be changes when working with a micro-OTG connector). Perhaps then your host PC can “ping 192.168.55.1” (any kernel source or other content is easily transferred that way if this succeeds).

If you want to set up an external device for building and working with kernels on multiple Jetsons just let me know and I can provide some information. Also, if you find the USB network connection works (the 192.168.55.1 address), then your options increase, so let me know if this works for you.

Thanks again for your answer.
What are the instructions and the steps to follow in order to compile and add the module to the kernel?

Should I follow these instructions? https://forums.developer.nvidia.com/t/enable-config-usb-serial-pl2303-on-kernel/285574/11-

Honestly, they are not so clear to me.

Considering that this is my version:
R36 (release), REVISION: 3.0, GCID: 36191598, BOARD: generic, EABI: aarch64, DATE: Mon May 6 17:34:21 UTC 2024

what would be the correct link where I can download the kernel source?

How can I compile the module for the USB device? Do I need to download a generic driver from the official website?

The URL you gave does not seem to be available. I’d need another way to look at that information.

I’m providing a lot of detail for someone wanting a portable kernel build external device like a thumb drive or USB drive. I’m assuming this might be moved around among Jetsons or host PCs.


I will need to know if you want to (A) use that USB device on the Jetson for build, or just for copy of module; (B) if you will cross compile on that USB device from a host PC; or (C) both.

Give me some details about the thumb drive. If you plug it in to any Linux system, and run the command “df -H -T”, then what do you see for the drive? I don’t need the output for all of the other content, it is just for that one drive. Please note that it is far far better if the partition type is ext4 and not one of the Windows filesystems. Something like VFAT has no ability to understand Linux file permissions, and I’m going to assume you have a Linux filesystem with valid permission setting abilities. If your removable drive’s designation for the partition is “/dev/sdb1”, then you could “sudo mkfs.ext4 /dev/sdb1” to force it to become ext4. If you do this you must be very very careful to get the right drive partition because it is going to erase everything on it.

For some starting information, you would:

  • Go to https://developer.nvidia.com/linux-tegra.
  • Find the link for R36.3.0 since that is what you are working on. Go there: https://developer.nvidia.com/embedded/jetson-linux-r363
  • Download the driver package sources, https://developer.nvidia.com/downloads/embedded/l4t/r36_release_v3.0/sources/public_sources.tbz2.
  • This produces file public_sources.tbz2.
  • If you are using this among different computers and might use different configurations, and if this is not going into the host PC’s standard search location (I’m assuming this drive gets mounted somewhere else), then you will want to move this to a location which makes the L4T release it uses obvious:
    • mkdir R36.3.0
    • mv public_sources.tbz2 ./R36.3.0/
    • cd R36.3.0
    • Now you are free to make similar locations with different L4T releases if you choose, e.g., by creating “R36.4.4/” if you change to that release and find the source code differs from R36.3.0.
  • Find the names of any packages you are interested in. The base command without filtering for specific content is:
    tar -tvjf public_sources.tbz2
  • The same command filtered for names of sub-packages you will be interested in extracting (you could extract all, but that is a huge amount of content you don’t want or need):
    tar -tvjf public_sources.tbz2 | egrep -i 'kernel.*tbz2$'
  • Note that the path listed in the previous .tbz2 search commands will extract to the subdirectory of those listings. For example, if you are in “~/Downloads/tmp/”, and you run a command to extract “Linux_for_Tegra/source/kernel_src.tbz2”, then you will end up with a subdirectory “Linux_for_Tegra/source/” containing that file. When you want to add this for convenience to the flash directory of your host PC you can find that flash content’s “Linux_for_Tegra/”, and then “cd ..” to end up unpacking in a default location. However, you don’t need to do this since you are going to make this an independent Jetson kernel build tool. You could do that, but then you’d probably want to change some details. I am going to assume that this is being unpacked into a USB thumb drive or external drive which is mounted somewhere which is temporary. For example, the thumb drive might have been automatically mounted at some “/media/...somewhere.../” location, and you have used cd to go there before unpacking.
  • Starting note: Eventually the source code will be best if saved as root and all other users are denied changes to the file. Then any build steps will name an alternate location for temporary output. This means your source code itself will remain pristine. However, I am going to suggest not using sudo to get packages within the package; actual sudo is for later use.
  • To unpack relevant content from the public_sources.tbz2, from the location of your removable media:
    • Generic: tar -xjf filename.tbz2 <package with path>; next, actual files:
    • tar -xjf public_sources.tbz2 Linux_for_Tegra/source/kernel_src.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/nvidia_kernel_display_driver_source.tbz2
    • tar -xjf public_sources.tbz2 Linux_for_Tegra/source/kernel_oot_modules_src.tbz2
    • Note that the above might be overkill for some situations, but that this ensures having everything.
  • You can now cd to “Linux_for_Tegra/source/” and run “ls *.tbz2”. There will be more than you need here most of the time. Next we extract the kernel source related content (remember we started with a package containing packages; there are yet more packages inside those packages). This is when we switch to using “sudo” because we do want these packages (resulting in final files) to be owned by root and not able to be changed by accident (they will essentially be read-only):
    • sudo tar xvfj kernel_src.tbz2
    • sudo tar xvfj kernel_oot_modules_src.tbz2
    • Note that the above all unpacked into subdirectory “kernel/kernel-jammy-src/”. It is this set of files we work with most. Sometimes out of tree content is referenced, and if so, and if that content is not already unpacked, then you could just unpack from the “source/” subdirectory which has all of the .tbz2 content.
  • You might need these at times:
    • sudo tar xvfj nvidia_kernel_display_driver_source.tbz2
    • sudo tar xvfj nvidia_kernel_display_driver_source_without_root_dir.tbz2
  • If you are working on other packages, then you could also do the same extraction of the other .tbz2 files, e.g., there are gstreamer sources and other packages.
  • From the “Linux_for_Tegra/source/kernel/kernel-jammy-src/” everything should be owned by root and not writable by any other user (this is why we used sudo for this step). Make sure this code is pristine, so from “Linux_for_Tegra/source/kernel/kernel-jammy-src/”, run “sudo make mrproper”. From now on all changes will be to other directories and locations and this location will never be touched again except for reading. Note that VFAT or other Windows filesystems have no ability to work with these permissions, and so it should be ext4. You can get around non-ext4, but it won’t be a good experience.
  • As a regular user again, now we choose some locations for temporary output. Note that you can pick a different empty temporary location whenever you configure differently. An example is that if you want to build for several systems with the same L4T release and no customization, then you’d just use the one temporary location; if one of them was from a different release, then probably you’d want to add a new temporary location for that (then again, the source code itself could change if the L4T release changed; so could the configuration).
    • I would choose the temp locations from “R36.3.0/”. Make as many temp locations as you need for intermediate output, but you will like need only one. We’ll use a name to remind us of what it is for:
      • cd ...wherever R36.3.0 is...
      • mkdir build_pl2303
      • mkdir modules_pl2303
      • mkdir other_pl2303
      • mkdir image
      • Note: These are just suggestions. You may not need all of these. I’m assuming any kind of firmware build could go into “other_pl2303”, but you might not ever build this. The “image/” location could be fore the main kernel “Image” file, but if you only have an interest in modules, then this too might not be used.

This is now ready to be used over and over. Other than perhaps creating other temporary locations for different builds you won’t need to do this again (I suppose if the kernel source itself changes you could put that content there too, but you don’t need to erase old content unless you no longer use it).

About Manual Native Build

When this removable drive (thumb, USB SATA, doesn’t matter) is mounted it might be at an unpredictable location. There isn’t a lot of environment setup for native build, although you do need to specify temporary output locations. Any cross compile will need a bit more environment variable setup, and this can change depending on where the drive is mounted. An automount might put the mount point at “/media/1000/”, or you might manually mount it. I am going to refer to this as “<mount>”, and if it is at “/media/1000/”, then use that; if it is at “~/mount/”, then use that.

I will suggest creating a file with notes. Content:

cd <media>/R36.3.0/
export BASE=`pwd`
echo $BASE

mkdir configs
cd configs
export CONFIGS=`pwd`
echo $CONFIGS

cd Linux_for_Tegra/source/kernel/kernel-jammy-src/
export SRC=`pwd`
echo $SRC

cd $BASE/build_pl2303
export TEGRA_KERNEL_OUT=`pwd`
echo $TEGRA_KERNEL_OUT

cd $BASE/modules_pl2303
export TEGRA_MODULES_OUT=`pwd`
echo $TEGRA_MODULES_OUT

cd $SRC

The above will not change if the mount location and configuration and release version do not change. You could use a bash alias to set specific values. I put something like this in my “~/.bash_aliases” file on any computer I work with. Example at the end of “.bash_aliases”:

alias setjet="export SRC=<media>/R36.3.0/Linux_for_Tegra/source/kernel/kernel-jammy-src;\
 export BASE=<media>/R36.3.0;\
 export TEGRA_KERNEL_OUT=<media>/R36.3.0/build_pl2303;\
 export TEGRA_MODULES_OUT=<media>/R36.3.0/modules_pl2303;\
 export CONFIGS=<media>/R36.3.0/configs"

you can put that in your regular user’s file on any system, including the Jetson. Just be sure your “<media>” is correct. Aliases work better than a shell script for setup because shell scripts are a fork of the current process while aliases are the same process (a child process forgets environment changes when it goes away, and security makes it impossible for a child process to alter a parent process’s environment).


All of the above creates everything you need for a build using convenience aliases. This does not have to be repeated other than setting environment variables if you move the drive (you’d have to run the alias and then start working; the environment variables are just a convenience unless cross-compiling).


Consider that there is a default configuration which must be matched before building. You would configure that and then change whatever it is you want changed. In this case you’d use a dependency-aware editor. If the system has arrived with a default kernel it shipped with, then for R36.x and newer there is a built in build target for the default, “defconfig”. A running system also has a compressed copy of the running configuration as “/proc/config.gz”. For either of those you would need to add the CONFIG_LOCALVERSION string. The default is “-tegra”. Everyone building for the first time will start with this.

You also may want to keep track of configurations so you can have them ready when you move the thumb drive or SATA drive. This is a good idea:

cd $CONFIGS
gunzip < /proc/config.gz > config-$(uname -r)
# Set read-only because this is a reference; just remember it still needs CONFIG_LOCALVERSION:
chmod ugo-w config-$(uname -r)

# Now create a working version which is going to be edited:
cp config-$(uname -r) $TEGRA_KERNEL_OUT/.config
# (the above renames it as ".config" while preserving the original)

It is important to know that the “O=/somewhere” parameter reads and alters the config from that location. The original source code never needs to be touched other than for reading. Running as a regular user is good because many people and/or machines can share the one drive and the kernel itself is always clean. Other than for the mrproper target most all commands must use the “O=/some/where”. For our environment we’ve set this up to be “O=$TEGRA_KERNEL_OUT”.

Start by asking yourself if you are going to change the base kernel, or if you are just adding modules. If the base does not change, then you want CONFIG_LOCALVERSION to remain the suffix from “uname -r”, which is “-tegra”. This is a very simple way to go since when done all you do is copy the module file to the right place. If you build the base kernel with any new features which are not in the form of a module, then it means you have to build and install the new kernel, all modules, change the “CONFIG_LOCALVERSION”, and update the initrd. Previous modules will be invalidated. Anything you can change by using the “m” key in the config editor greatly simplifies life.

An native build example, assuming the default config has been copied in to the $TEGRA_KERNEL_OUT, or if you use the defconfig target (initially they are the same thing):

# Pretending I am using the alias, and natively compiling:
setjet
cd $SRC
make O=$TEGRA_KERNEL_OUT nconfig
# nconfig is like menuconfig but note it has a symbol search;
# search for CONFIG_LOCALVERSION (you can just search for
# "localversion", it is case-insensitive and assumes the "CONFIG_".
# Search for "localversion", set it to "-tegra" if only modifying modules.

# Now search for pl2303, or CONFIG_USB_SERIAL_PL2303.
# Use the "m" key to set it as a module.
# Save the .config.

# An acid test you should do once. Technically not needed at all if building just a module
# since there is a faster "modules_depend" you could run without taking the time to build
# a full kernel. Do this once anyway, this is where finding something wrong has happened.

# The "-j number" specifies the number of CPU cores to use. If your Jetson has 8 cores,
# then use 8; if it has 14 cores, then you can use 14. More cores does use more RAM.
# I'm arbitrarily picking 8 for this:
make O=$TEGRA_KERNEL_OUT -j 8 Image

# If you did not build "Image", then run this, but do not use this if you built Image:
make O=$TEGRA_KERNEL_OUT modules_prepare

# Assuming that works:
make O=$TEGRA_KERNEL_OUT -j 8 modules

# That's it for build. Now send the entire set of modules to a convenient location
# instead of sending them all somewhere:
make O=$TEGRA_KERNEL_OUT modules_install INSTALL_MOD_PATH=$TEGRA_MODULES_OUT

There are files I save as an archive. If the configuration works, and if you only changed modules, then I recommend:

cd $TEGRA_KERNEL_OUT
cp .config $CONFIGS/config-$(uname -r)-pl2303module

Any time you wish to start clean, just go into $TEGRA_KERNEL_OUT and delete it all completely. Then copy in the config you want to start with using name “.config”. If you have more than one config you like, then use more than one $TEGRA_KERNEL_OUT and TEGRA_MODULES_OUT. If you build the exact same source from a different L4T release, then create that L4t number as a symbolic link into the release you originally built it for. Example:

cd $BASE
ln -s R36.3.0 R36.4.4

(the above is because of an assumption that the kernel source has not changed; it is not unusual for there to be kernel patches between releases, and so the above is just an example…I don’t know for real if the same kernel is used in R36.3.0 and R36.4.4)


If you ever have a kernel build fail, consider that it is building a lot of symbols simultaneously if the “-j number” is at least 2. If you build with “-j 8”, and there is a failure, then you can repeat the same build line and more of the working code will complete. At some point you will only see the error because the working features are already successfully built. You can create a log of just the failing code:
make O=$TEGRA_KERNEL_OUT -j 8 modules 2>&1 | tee log_modules.txt
(you could then attach the .config and kernel build error as files to the forum)

Let me know how well this works. Also let me know if you need to cross-compile instead of native compile. It starts the same but there is more environment setup.

Thank you again!

So, on my host pc, I followed all the steps up to:

I would choose the temp locations from “R36.3.0/”. Make as many temp locations as you need for intermediate output, but you will like need only one. We’ll use a name to remind us of what it is for:

  • cd ...wherever R36.3.0 is...

  • mkdir build_pl2303

  • mkdir modules_pl2303

  • mkdir other_pl2303

  • mkdir image

  • Note: These are just suggestions. You may not need all of these. I’m assuming any kind of firmware build could go into “other_pl2303”, but you might not ever build this. The “image/” location could be fore the main kernel “Image” file, but if you only have an interest in modules, then this too might not be used.

I created the folders, but now I don’t know how to proceed further. Why should I use an external thumb drive?

I found out that here:
/home/robo/R36.3.0/Linux_for_Tegra/source/kernel/kernel-jammy-src/drivers/usb
there is the file I was looking at: pl2303.c

But I cannot understand what I have to do in order to create the module starting from this file.
The instructions starting from: About Manual Native Build are not clear to me.
Can you help me, please?
I just need to load the module to make my UBS converter work fine on my Jetson.

You can build without it being on an external drive. However, if you have more than one Jetson, or you want to share between two computers which are not connected by network and/or do not have Internet access, or if there is not enough disk space on the particular device, then you can use an external medium which you can connect or disconnect as needed. It is a convenience when working on any of those cases. Everything works perfectly well without that though, so it is just choice.

The file itself will not help. This is just the core of a kernel module, and you need the surrounding content to turn it into a module. Modules load into the kernel, and thus it isn’t that different to a lock and key…if the module is not compiled to fit the ABI (Application Binary Interface), then it will not load. If the module is not compiled to fit against your kernel Image, or if the Image has changed and the modules have not, then you have a problem. If you’ve set up your kernel environment correctly, and then change only modules, the ABI will work as expected. You need the full kernel source, not just a file, and that kernel source needs to be configured to match the running kernel.

In your case when not using removable media you can just ignore the parts designed for moving it around between computers. Everything will just be in one place, but the procedures for kernel configuration still apply. Do you have the full kernel source code here:
/home/robo/R36.3.0/Linux_for_Tegra/source/kernel/kernel-jammy-src/
?

If so, then you can:

cd /home/robo/R36.3.0/Linux_for_Tegra/source/kernel/kernel-jammy-src/
export SRC=`pwd`
echo $SRC

Based on your other directories, you can set up like this (not all of these are strictly required, but it helps with clarity; the echo statements are just to verify; see notes just below this):

cd /home/robo/R36.3.0/
export BASE=`pwd`
echo $BASE

cd /home/robo/R36.3.0/build_pl2303
export TEGRA_KERNEL_OUT=`pwd`
echo $TEGRA_KERNEL_OUT

cd /home/robo/R36.3.0/modules_pl2303
export TEGRA_MODULES_OUT=`pwd`
echo $TEGRA_MODULES_OUT

cd /home/robo/R36.3.0/other_pl2303
export CONFIGS=`pwd`
echo $CONFIGS

cd /home/robo/R36.3.0/image
export ARCHIVE=`pwd`
echo $ARCHIVE

Notes:

  • SRC is often called TOP instead. This is just where the software looks for build content.
  • TEGRA_KERNEL_OUT is the workhorse. This is where all of the build content and temp content goes and is why you can leave the original source untouched. The original source is best keep readable by all but writable only by root; actual build should not be performed by root.
  • TEGRA_MODULES_OUT is a location where one can output all of the module information once they are built. Not strictly needed, but the work to separate out what this does in a second is far too much. This will create a directory tree in TEGRA_MODULES_OUT which is shaped just like the destination computer’s directory structure, and will contain files to be copied in. As an example, after you’ve built modules and then modules_install with INSTALL_MOD_PATH=$TEGRA_MODULES_OUT you will see an entire “lib/...lots of stuff.../”, and that “stuff” will contain all of the modules in a mirror of the directory tree where they eventually get copied. You don’t need all of those if you only want the PL2303 and did not alter the integrated features. It is easier to find the PL2303 content in $TEGRA_MODULES_OUT than it is $SRC (a.k.a., , $TOP).
  • I would have created a configs_pl2303/ directory, and I am assuming this is what you named other_pl2303. This is where you keep configurations for various purposes. It is a reference library. The intent is that you name configs there with some name that has meaning, and then when you need it, copy it to the empty “$TEGRA_KERNEL_OUT” as file name “.config”.
  • I am assuming that your kernel Image (and perhaps other files) are being saved in $ARCHIVE, but I could be wrong about your intent of what you want there. Whatever purpose you have in mind just use it consistently like that and name an environment variable after it for convenience.

There is in fact your next step: Set up the environment variables. You could add those in an alias in your ~/.bash_aliases, source the file, and run the alias; this would set up the environment variables for that session on that terminal. Assuming those are set up, do this once:

cd $SRC
sudo make mrproper

Since you said you are on your host PC, this means you are cross-compiling. Previous steps were accurate, but not including cross-compile details. This means the instructions will differ some. To start with, where are your cross-compile tools? Perhaps they are at one of these locations:

  • /usr/aarch64-linux-gnu/bin/
  • /usr/bin/
  • ~/usr/aarch64-linux-gnu/bin/

The point of the above is that you will need a cross-compiler when building on the host PC and not building on the Jetson, and that the location can vary. You will need another environment variable for whatever that cross-compiler is (you don’t need this on a native build; in fact, someone using some of the cross-compile environment for a native build will end up failing). I’m going to pick “/usr/bin/” as the location. If that is the correct location, then you will find a series of files there, and all will have this prefix to their name:
aarch64-linux-gnu-

When you build natively, you use gcc; when you cross build, you use aarch64-linux-gnu-gcc.
When you build natively, you use ld; when you cross build, you use aarch64-linux-gnu-ld.
When you build natively, you use as; when you cross build, you use aarch64-linux-gnu-as.
So on, so forth, etc. The kernel build, when told to cross compile, understands to use a special environment variable to substitute for the correct cross compiler. On 64-bit ARM that environment variable is CROSS_COMPILE. All of the previous environment variables were for the developer and could be named many things and still work. This particular environment variable is specifically understood by the kernel build system itself, and so this should not be changed, and if you are not cross-compiling, it should not be set.

For my example, I will set it like this since you are on the host PC (in which case you must cross-compile and cannot natively compile):
export CROSS_COMPILE=/usr/bin/aarch64-linux-gnu-

Do change that if your cross tools are in another location. Note that this is the full path up to and including that special “aarch64-linux-gnu-” prefix. When kernel build wants gcc, it will prepend the $CROSS_COMPILE; if this is empty, then it just looks for gcc. If that CROSS_COMPILE is /usr/bin/aarch64-linux-gnu-, then it will instead find “aarch64-linux-gnu-gcc”, which is still gcc, but it is the cross compiler version for 64-bit ARM.

In addition to setting CROSS_COMPILE to the correct prefix you must also tell the compiler what the architecture is, arm64. This too is an environment variable which must be set for cross compile, but cannot be set for native compile (it would appear to build, but the result would be wrong):
export ARCH=arm64

In summary, cross-compile is going to add these:

  • export CROSS_COMPILE=/some/where/aarch64-linux-gnu-
  • export ARCH=arm64

If you are not cross compiling, then you must unset those two if they were previously set. In some cases a variable can be used both on command line or as an environment variable, so you might see some redundant statements.

An example of building the default configuration setup, but adding PL2303 follows. Your SRC/TOP content should be pristine and untouched. The temp output location for $TEGRA_KERNEL_OUT and $TEGRA_MODULES_OUT should be empty. A regular user should do this, not root.

cd $SRC

# This is default configuration.
make O=$TEGRA_KERNEL_OUT ARCH=arm64 defconfig

# Enter the context-sensitive editor.
make O=$TEGRA_KERNEL_OUT ARCH=arm64 nconfig

# Use the symbol search function and find the location of the PL2303 feature.
# Use the "m" key to set it as a module.
# Save and exit.
# Verify this exists:
ls $TEGRA_KERNEL_OUT/.config

# When done you could save that:
cp $TEGRA_KERNEL_OUT/.config $ARCHIVE/config-tegra-pl2303module

# If you ever wanted to use that again, and TEGRA_KERNEL_OUT is empty:
cp $ARCHIVE/config-tegra-pl2303module $TEGRA_KERNEL_OUT/.config

That is now configured to the default configuration, but adds a module. You don’t need the Image, but it is a really good idea to build it once as an acid test. This also means you don’t need to build “modules_prepare”; if you skipped Image, then you would need “modules_prepare” instead. Here is building the main Image:

cd $SRC
make -j 8 O=$TEGRA_KERNEL_OUT ARCH=arm64 Image

Note that in the last command I added “-j 8”. This is the job server, and it uses up to 8 CPU cores. Does your host PC where you are compiling have fewer or more CPU cores? You can adjust this to match for best speed. If you lack RAM and run out of RAM, then you would use fewer cores.

Now, if Image built, or if you instead built modules_prepare, you can build modules:

cd $SRC
make -j 8 O=$TEGRA_KERNEL_OUT ARCH=arm64 modules

# If they built, copy them into the temp holding location:
make O=$TEGRA_KERNEL_OUT ARCH=arm64 modules_install INSTALL_MOD_PATH=$TEGRA_MODULES_OUT

You should now see an entire tree of files in $TEGRA_MODULES_OUT. Modules themselves are normally located at “/lib/modules/$(uname -r)/kernel/”, and so you will now find these at:
$TEGRA_MODULES_OUT/lib/modules/...the "uname -r" of the new kernel.../kernel/

You might find this useful:
find $TEGRA_MODULES_OUT/lib/modules/ -iname '*pl2303*'

You could also find the Image file from here and copy that to $ARCHIVE:
find $SRC -type f -name 'Image'

Have you modified the original kernel though? If not, then just the PL2303 .ko file needs copy to the right location (also an “sudo depmod -a”, perhaps a reboot). If you changed the Image, then you need the new Image and you also need 100% of all of the files. There might be another complication if out of tree modules are needed; in that case you’ll end up with something more extensive to get that out-of-tree content.

Hello!

Thanks a lot for your help!

I followed all your instructions and now I can see the module:

robo@eva-01:~/R36.3.0/Linux_for_Tegra/source/kernel/kernel-jammy-src$ find $TEGRA_MODULES_OUT/lib/modules/ -iname ‘pl2303
/home/robo/R36.3.0/modules_pl2303/lib/modules/5.15.136/kernel/drivers/usb/serial/pl2303.ko

I will try to load this module on my Jetson this evening and then I will update you.
On my Jetson, I should copy the pl2303.ko file into this directory:

/lib/modules/$(uname -r)/kernel/

and then run: sudo depmod -a

is it correct?

However, even if you suggested to run the commands with normal user and not as root, I couldn’t do it since I got this error:

robo@eva-01:~/R36.3.0/Linux_for_Tegra/source/kernel/kernel-jammy-src$ make O=$TEGRA_KERNEL_OUT ARCH=arm64 defconfig
mkdir: cannot create directory ‘.tmp_11612’: Permission denied
mkdir: cannot create directory ‘.tmp_11614’: Permission denied
mkdir: cannot create directory ‘.tmp_11616’: Permission denied
mkdir: cannot create directory ‘.tmp_11618’: Permission denied
mkdir: cannot create directory ‘.tmp_11620’: Permission denied
mkdir: cannot create directory ‘.tmp_11622’: Permission denied
HOSTCC scripts/basic/fixdep
scripts/basic/fixdep.c:373:1: fatal error: opening dependency file scripts/basic/.fixdep.d: Permission denied

so I had to follow all your instructions as root. Is it a problem?
Thanks again!

It seems like unpacking the kernel source, or some other operation, might have created temporary content, and that content might still be owned by root. Either the existing .tmp_...number... is a problem, or the parent directory owning this is a problem. Temporary content should be removed or the build is not from pristine source. A similar corollary is that in the temporary output location (TEGRA_KERNEL_OUT) sometimes people remove “all” files, but they don’t remove the so-called “hidden” files and directories which start with “.” in their name. Make sure that at the $SRC ($TOP in some cases) to “sudo make mrproper”. Then completely delete the entire $TEGRA_KERNEL_OUT directory and recreate it as a completely empty directory. You can then copy a config file in there as name “.config” if you’ve already set that up. Unfortunately, I do not have a way to actually poke around in the kernel source directory to examine the permission issue. Whenever you are building to “O=$TEGRA_KERNEL_OUT” though there should be no need for any file or temp content change in the original $SRC/$TOP.

Hello,

I loaded the ko module on my Jetson and now everything works fine since I’m able to plug the USB converter and get the device address on /dev/ttyUSB0.

Thank you a lot!!!