Micro-B USB port as a keyboard HID gadget

I’d like to use the USB Gadget functionality to emulate a keyboard for a host PC. That is, the host PC should see the TX2 as a USB keyboard. I think I got the idea on how to do it based on other posts and manuals (here, here and here) however, I’m facing some difficulties.

  1. I’ve rebuilt the kernel with the HID USB Gadget driver enabled (image below).

    Based on my understanding, I should be able to confirm that the support is enabled by checking "zcat /proc/config.gz | grep ‘USB_GADGET’ “ outputs. However, the config didn’t change:

CONFIG_USB_GADGET=y
CONFIG_USB_GADGET_VBUS_DRAW=2
CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS=2
That is, CONFIG_USB_G_HID didn’t show up. Did I do smth wrong?

  1. Normally, the next step would be to create a device in /sys/kernel/config/usb_gadget/ by making a folder there that would reflect a device. However, I’m getting an error “cannot create directory ‘key_test’: Cannot allocate memory”. I saw others reporting a similar problem for other use cases, and I’m wondering if this is the right way at all, or there is a different sequence of steps to create a USB gadget on a TX2?

  2. There are sample gadget setups in /opt/nvidia/l4t-usb-device-mode, but they appear to be only for a storage device (and a camera?). Can you pls please point me to any examples or manuals that can help me?

I don’t know for certain on the missing “/proc/config.gz” item, but probably what you examined was correct, but one would have to know if the starting kernel config matched the running system, and then if the build was correct, and since you used “=y”, whether the kernel was installed correctly. If there is a change in “uname -r”, then you’d have also had to have installed all modules again in the new module path, but keeping “uname -r” would avoid that issue. So there could be issues related to install even if the kernel build was correct. Don’t know. Perhaps all that follows is irrelevant because the kernel/feature was not installed as expected. Verifying the kernel started with a matching config, accepted the modified config, was build correctly, that the “uname -r” is correct, and that modules are found would be the next step.

One does not normally create files in “/sys”, but gadget API does make some of this possible. Normally, these are “pseudo” files, and not real files which exist only in RAM and are created by kernel drivers as a way for the driver to provide or accept information. Normally (but not in this case), you would find a file there not by creating it directly, but instead by creating a valid configuration wherever your gadget configuration is, and should the configuration succeed, then the file would just appear in “/sys” to reflect that success (or in this case allow creation). For the case of gadget, the directory being created exists because the driver owning the parent directory allows this (other content is basically data to the driver because the feature in the kernel allows this).

The files in “/opt/nvidia/” are examples. Note that part of this is configuration settings, e.g., the name of a file for the mass storage device to read. The “stop” and “start” files make good examples. However, the kernel feature would need to be correctly installed, and the user making the changes in “/sys” would have to be root (with “sudo”).

Thank you for the reply. Let me focus on the kernel aspect first. I use jetsonhacks to build and copy in the following sequence:

  1. getKernelSource.sh
  2. editConfig.sh → enable USB Gadget Drivers; also append LOCALVERSION so it’s “-tegra-usb_gadget”
  3. makeKernek.sh: builds with no errors or warnings
  4. makeModules.sh: build and installs successfully with one warning: “could not open drivers/misc/mods/mods.dtb.S: no such file or directory” (I don’t know if it’s relevant to my problem or not).
  5. copyImage.sh
  6. reboot
  7. uname -r shows “4.9.253-tegra-usb_gadget” which is great, but
    /proc/config.gz still doesn’t have it.

Common sense tells me that either I’m doing smth incorrectly or the jertsonhacks scripts are doing smth wrong. I would appreciate if you share your thoughts.

I don’t use those scripts, I simply download the one provided with the particular L4T release. To see L4T release use “head -n 1 /etc/nv_tegra_release”.

JetPack/SDK Manager is only a front end, it is the L4T which is the actual Ubuntu operating system. A given JetPack/SDKM is paired with a given L4T release. These are the URLs for listing releases based on either L4T version or JetPack/SDKM version:
https://developer.nvidia.com/linux-tegra
https://developer.nvidia.com/embedded/jetpack-archive

It is likely the script downloads the exact same source, though I can’t confirm. I give the above URLs if people want to download the source manually for the exact release.

The part I don’t know about (and I have not dissected the scripts) is what the beginning configuration is. This might be where things went wrong. Also, I see “uname -r” has changed, so this means you are guaranteed your kernel was installed, but it doesn’t say if modules were installed. Do you see modules in the subdirectories of this?
/lib/modules/$(uname -r)/kernel
…if not, then build/install steps were incomplete. Are those files in place?

Should the above set of modules be in place, then we would want to concentrate on how you edited the configuration. For example, using an ordinary text editor, versus using menuconfig or some other configuration editor.

Thanks. Here we go:
head -n 1 /etc/nv_tegra_release results in:
R32 (release), REVISION: 6.1, GCID: 27863751, BOARD: t186ref, EABI: aarch64, DATE: Mon Jul 26 19:36:31 UTC 2021

The copy script downloads the following L4T:

NVIDIA Jetson TX2
Jetpack 4.6 [L4T 32.6.1]
Kernel Release: 4.9

And places the source to /usr/src/kernel/kernel-4.9
I checked the .config there and it appears to contain changes that I want:
cat .config | grep LOCALVERSION results in:
CONFIG_LOCALVERSION="-tegra-usb_gadget"

cat .config | grep USB_G_HID shows:
CONFIG_USB_G_HID=y

Also, as you suggested, I checked /lib/modules/$(uname -r)/kernel and the folder looks identical for the base 4.9.253-tegra and my version 4.9.253-tegra-usb_gadget:
Screenshot from 2021-12-02 21-20-23

Any suggestions on where I should go from here? Should I drop the jetsonhacks scripts and use the vanilla build instructions? If yes, where can I get a manual on how to build 32.6.1?

I hesitate to say to drop the jetsonhacks scripts, but I know more about manual configuration, so it is easier to discuss if not using those scripts.

About:

…if you were to compile your kernel right there in the source, then this would be correct. However, if you use the “O=/some/where/else/for/temp/output”, then this would not apply and would be incorrect. I rarely suggest compiling directly from a root owned directory (in this case “/usr/src”) since one must also run as root. This does work, but might not be the best practice.

It does seem modules were installed correctly, so that isn’t in question.

I’m looking at an older TX2 right now, so it isn’t exactly the same as yours (the kernel version is older, so I don’t expect the same options to be available in the config.gz). What I don’t see is a “CONFIG_USB_G_HID” entry. Perhaps this is new and your release has this, but not my older release (4.9.140). Do you know how the CONFIG_USB_G_HID became part of the configuration? For example, was this already there (even if it wasn’t “=y”), and then configured to enable? If you run this command, does this show up (to indicate the kernel has the feature in the running system):
zcat /proc/config.gz | grep 'CONFIG_USB_G_HID'

If someone just added that to the config, and it wasn’t part of the actual kernel source, then the feature would be missing in the final /proc/config.gz.

You are actually right! CONFIG_USB_G_HID is not in the original config and I believe the config GUI editor added it there. Now it makes sense why it has no effect.
What are the correct flags to enable to unlock the Gadget keyboard and mouse USB functionality?

The kernel code itself has a document directory, and the “gadget” system is documented there for that particular kernel release. For example, if the kernel source is at “/usr/src/sources/kernel/kernel-4.9”, then there is a subdirectory “Documentation/usb/” and “Documentation/ABI/testing/”. You might find something in “testing/” related to fields to fill in, and the “usb/” part has “usb/gadget_hid.txt”, which has more details. The API might change, and so this is where you’d find the definitive information.

Keyboard and mouse are “HID” devices, and “HID” implies standard drivers rather than custom drivers, which is why the gadget framework can be of use. I have not followed the changes in gadget over various releases, but the docs for your particular kernel release would be the place to start, specifically using the HID documents.

Thanks for bearing with me. Unfortunately, the documentation you mention doesn’t discuss the kernel config flags (unless I need to read between the lines). I think I found the flag I need from this post: CONFIG_USB_CONFIGFS_F_HID. I still tend to modify flags in GUI since I’m not 100% confident in what I’m doing. So I change two flags: device drivers → USB support → USB gadget support → USB functions configurable through configfs CONFIG_USB_CONFIGFS_F_HID (I turned it on) → HID function (I turned it on since it looks related)

After rebooting, I use the script you mention in another post to stop the device mode: “/opt/nvidia/l4t-usb-device-mode/nvidia nv-l4t-usb-device-mode-stop.sh”.

After that, I attempt to create a keyboard gadget device by using the command sequence that is based on this post, except for excluding rndis.usb0 related stuff (I assume I don’t need it), but with an addition of the keyboard protocol in report_desc:

   modprobe libcomposite
   mkdir g1
   cd g1

   echo 0x1d6b > idVendor
   echo 0x0104 > idProduct
   echo 0x0100 > bcdDevice
   echo 0x0200 > bcdUSB
   echo 0xEF > bDeviceClass
   echo 0x02 > bDeviceSubClass
   echo 0x01 > bDeviceProtocol

   mkdir -p strings/0x409
   echo "deadbeef00115599" > strings/0x409/serialnumber
   echo "Something"        > strings/0x409/manufacturer
   echo "Something"        > strings/0x409/product

   mkdir -p functions/hid.usb0
   echo 1 > functions/hid.usb0/protocol
   echo 1 > functions/hid.usb0/subclass
   echo 8 > functions/hid.usb0/report_length
   echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > functions/hid.usb0/report_desc

   mkdir -p functions/acm.usb0
   mkdir -p functions/acm.usb1
   mkdir -p functions/acm.usb2
   mkdir -p functions/acm.usb3

   mkdir -p configs/c.1
   echo 250 > configs/c.1/MaxPower

   ln -s functions/rndis.usb0 configs/c.1/
   ln -s functions/hid.usb0 configs/c.1/
   ln -s functions/acm.usb0 configs/c.1/
   ln -s functions/acm.usb1 configs/c.1/
   ln -s functions/acm.usb2 configs/c.1/
   ln -s functions/acm.usb3 configs/c.1/

   echo 1       > os_desc/use
   echo 0xcd    > os_desc/b_vendor_code
   echo MSFT100 > os_desc/qw_sign

   ln -s configs/c.1 os_desc
   udevadm settle -t 5 || :
   ls /sys/class/udc/ > UDC

When I connect the TX2 to my host Ubuntu PC, I notice that the following usb device showing up:
Bus 001 Device 005: ID 1d6b:0104 Linux Foundation Multifunction Composite Gadget
I assume I did at least smth right if I see the above.

Now I use the kernel documentation to test the device. I compile the code mentioned in the documentation (I compile it identical to this repo) and run hid_gadget_test /dev/hidg0 keyboard expecting to be able to control the host PC through the TX2 keyboard, but the only thing I get is a standard printout on TX2 of what keyboard keys I can use and the following two lines:

recv report: 00
recv report: 00

Pressing any keys on the TX2’s keyboard doesn’t have any effect on the host PC.

How can I figure out what I’m missing?

Yes, that looks like progress. A composite device is like multiple device on a single USB cable (one can have multiple hardware devices talk over USB, or even the same hardware using different software). You might want to see what the verbose listing is:
sudo lsusb -vvv -d 1d6b:0104

I couldn’t say what is needed to complete this, but RidgeRun commonly writes about or provides hardware for use with Jetsons, and I noticed they have this article:
https://developer.ridgerun.com/wiki/index.php?title=Linux_USB_gadget_(g_hid)

That article assumes you are using one of their custom boards, which is what the “RidgeRun SDK” is about, but mostly you could skip the customization parts related to their carrier boards and probably find some useful information in that article. Other than that, I’d say to see what the fully verbose lsusb says.

1 Like

It finally works. In the end I used this manual as a reference but I think my previous attempts were also correct.
In the end I enabled only two kernel flags:
Device Drivers -> USB support -> USB Gadget Support -> USB Gadget Drivers-> USB functions configurable through configfs -> HID function and Device Drivers -> USB support -> USB Gadget Support -> USB Gadget Drivers-> USB gadget drivers -> HID gadget

Here is the final version of my keyboard device script (someone may find it useful):

  /opt/nvidia/l4t-usb-device-mode/nv-l4t-usb-device-mode-stop.sh # stops nvidia gadgets
  modprobe configfs
  modprobe libcomposite

  cd /sys/kernel/config/usb_gadget
  mkdir g1
  cd g1
  echo "64" > bMaxPacketSize0
  echo "0x0200" > bcdUSB    # USB2
  echo "0x100" > bcdDevice  # v1.0.0
  echo "0x1d6b" > idVendor  # Linux Foundation
  echo "0x0104" > idProduct # Multifunction Composite Gadget

  mkdir strings/0x409
  mkdir configs/c1.1
  echo "Logitech" > strings/0x409/manufacturer
  echo "G915 keyboard" > strings/0x409/product
  echo 6g65796d616d6570390 > strings/0x409/serialnumber
  mkdir configs/c1.1/strings/0x409/ -p
  echo "Config 1: ECM network" > configs/c1.1/strings/0x409/configuration
  echo 120 > configs/c1.1/MaxPower

  # emulate hid keyboard
  mkdir functions/hid.0
  echo 1 > functions/hid.0/protocol                      #  set the HID protocol
  echo 1 > functions/hid.0/subclass                      #  set the device subclass
  echo 8 > functions/hid.0/report_length                 #  set the byte length of HID reports
  echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > functions/hid.0/report_desc        
  ln -s functions/hid.0 configs/c1.1 

  # enable the USB device contoller
  ls /sys/class/udc > UDC

  chmod 777 /dev/hidg0

The main problem I had was not in the implementation but in testing: this method didn’t work correctly for my case, so I resorted to issuing keystrokes directly through the terminal. For instance, echo -ne "\0\0\x17\xb\x4\x11\xe\x2c" > /dev/hidg0 && echo -ne "\0\0\0\0\x1c\x12\x18\0" > /dev/hidg0 && echo -ne "\0\0\0\0\0\0\0\0" > /dev/hidg0 results in typing “thank you” on the host.

1 Like

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