package: new package for usb gadget setup
authorChuanhong Guo <gch981213@gmail.com>
Sun, 19 Nov 2023 12:56:52 +0000 (20:56 +0800)
committerChuanhong Guo <gch981213@gmail.com>
Thu, 28 Dec 2023 05:10:32 +0000 (13:10 +0800)
Setting up usb gadgets using g_* kernel modules are considered a
legacy approach, but the usb_gadget configfs is a bit annoying
to use directly.
The usb_gadget configfs works by creating magic directories
and writing to magic files under /sys/kernel/config/usbgadget.
This new package is an init script to setup usb_gadget configfs
using uci. In the config file, gadget/configuration/function
sections create corresponding directories. UCI options are magic
files available in the configfs and strings/0x409 directories,
grabbed with a 'find' command. UDC option in gadget writes
the UDC file under the 'gadget' directory to attach the
generated gadget config.

It's also possible to apply pre-made config templates under
/usr/share/usbgadget. The templates use the same UCI config
format, with the 'gadget' entry named 'g1'. Currently, there
are templates for CDC-ACM and CDC-NCM gadgets written based
on existing g_*.ko module code.

Certain SBCs come with only a USB device port (e.g. Raspberry Pi
Zero). With this script, it's now possible to perform initial
setup on them by adding a default NCM gadget.

Signed-off-by: Chuanhong Guo <gch981213@gmail.com>
package/utils/usbgadget/Makefile [new file with mode: 0644]
package/utils/usbgadget/files/presets/acm [new file with mode: 0644]
package/utils/usbgadget/files/presets/ncm [new file with mode: 0644]
package/utils/usbgadget/files/usbgadget.conf [new file with mode: 0644]
package/utils/usbgadget/files/usbgadget.init [new file with mode: 0644]

diff --git a/package/utils/usbgadget/Makefile b/package/utils/usbgadget/Makefile
new file mode 100644 (file)
index 0000000..d3a68ea
--- /dev/null
@@ -0,0 +1,54 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=usbgadget
+PKG_RELEASE:=1
+
+PKG_LICENSE:=BSD-2-Clause
+
+PKG_MAINTAINER:=Chuanhong Guo <gch981213@gmail.com>
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/$(PKG_NAME)
+  SECTION:=utils
+  CATEGORY:=Utilities
+  DEPENDS:=@USB_GADGET_SUPPORT +kmod-usb-gadget +kmod-usb-lib-composite
+  TITLE:=init script to create USB gadgets
+endef
+
+define Build/Compile
+endef
+
+define Package/$(PKG_NAME)/install
+       $(INSTALL_DIR) $(1)/etc/config $(1)/etc/init.d
+       $(INSTALL_CONF) ./files/usbgadget.conf $(1)/etc/config/usbgadget
+       $(INSTALL_BIN) ./files/usbgadget.init $(1)/etc/init.d/usbgadget
+endef
+
+$(eval $(call BuildPackage,$(PKG_NAME)))
+
+# 1: short name
+# 2: description
+# 3: dependencies on other packages
+define GadgetPreset
+  define Package/$(PKG_NAME)-$(1)
+    SECTION:=utils
+    CATEGORY:=Utilities
+    TITLE+= $(2) gadget preset
+    DEPENDS+= $(3)
+  endef
+
+  define Package/$(PKG_NAME)-$(1)/description
+   This package contains the USB gadget preset for $(3).
+  endef
+
+  define Package/$(PKG_NAME)-$(1)/install
+       $(INSTALL_DIR) $$(1)/usr/share/usbgadget
+       $(INSTALL_CONF) ./files/presets/$(1) $$(1)/usr/share/usbgadget
+  endef
+
+  $$(eval $$(call BuildPackage,$(PKG_NAME)-$(1)))
+endef
+
+$(eval $(call GadgetPreset,ncm,CDC-NCM,+kmod-usb-gadget-ncm))
+$(eval $(call GadgetPreset,acm,CDC-ACM,+kmod-usb-gadget-serial))
\ No newline at end of file
diff --git a/package/utils/usbgadget/files/presets/acm b/package/utils/usbgadget/files/presets/acm
new file mode 100644 (file)
index 0000000..f8ce9c4
--- /dev/null
@@ -0,0 +1,13 @@
+config gadget 'g1'
+       option idVendor '0x0525'
+       option idProduct '0xa4a7'
+       option bDeviceClass '2'
+       option product 'Gadget Serial v2.4'
+
+config configuration 'cfg1'
+       option configuration 'ACM'
+       option gadget 'g1'
+
+config function 'acm1'
+       option function 'acm'
+       option configuration 'cfg1'
diff --git a/package/utils/usbgadget/files/presets/ncm b/package/utils/usbgadget/files/presets/ncm
new file mode 100644 (file)
index 0000000..cb3a193
--- /dev/null
@@ -0,0 +1,13 @@
+config gadget 'g1'
+       option idVendor '0x0525'
+       option idProduct '0xa4a1'
+       option bDeviceClass '2'
+       option product 'NCM Gadget'
+
+config configuration 'cfg1'
+       option configuration 'NCM'
+       option gadget 'g1'
+
+config function 'ncm1'
+       option function 'ncm'
+       option configuration 'cfg1'
diff --git a/package/utils/usbgadget/files/usbgadget.conf b/package/utils/usbgadget/files/usbgadget.conf
new file mode 100644 (file)
index 0000000..0d80fc9
--- /dev/null
@@ -0,0 +1,12 @@
+# apply a preset under /usr/share/usbgadget
+config preset
+       option name 'ncm'
+       # specify a UDC to enable this gadget:
+       # option UDC 'musb-hdrc.2.auto'
+
+# or create a custom gadget here following the content of presets:
+#config gadget 'g1'
+#      option idVendor ...
+#      ...
+# and add an UDC under the gadget section to enable it:
+#      option UDC 'musb-hdrc.2.auto'
diff --git a/package/utils/usbgadget/files/usbgadget.init b/package/utils/usbgadget/files/usbgadget.init
new file mode 100644 (file)
index 0000000..f2e105c
--- /dev/null
@@ -0,0 +1,144 @@
+#!/bin/sh /etc/rc.common
+
+START=19
+
+GADGET_FS=/sys/kernel/config/usb_gadget
+GADGET_PRESETS_DIR=/usr/share/usbgadget
+GADGET_PREFIX=owrt_
+
+log() {
+       logger -t usbgadget "$@"
+}
+
+apply_configs() {
+       local fs_path="$1"
+       local cfg="$2"
+       for param in $(find "$fs_path" -maxdepth 1 -type f -exec basename '{}' ';'); do
+               [ "$param" = "UDC" ] && continue
+
+               config_get val "$cfg" "$param"
+               [ -n "$val" ] && echo "$val" > "${fs_path}/${param}"
+       done
+}
+
+setup_gadget() {
+       local cfg="$1"
+       local gadget_path="${GADGET_FS}/${GADGET_PREFIX}${cfg}"
+       local param udc
+
+       config_get udc "$cfg" UDC
+       [ -z "$udc" ] && return
+
+       mkdir "$gadget_path" || return
+       apply_configs "$gadget_path" "$cfg"
+
+       local strings_path="${gadget_path}/strings/0x409"
+       mkdir "$strings_path"
+       apply_configs "$strings_path" "$cfg"
+}
+
+setup_configuration() {
+       local cfg="$1"
+       local gadget
+       config_get gadget "$cfg" gadget
+       local cfgs_path="${GADGET_FS}/${GADGET_PREFIX}${gadget}/configs"
+       [ -d "${cfgs_path}" ] || return
+       local cfg_path="${cfgs_path}/${cfg}.1"
+       mkdir "$cfg_path" || {
+               log "failed to create configuration ${cfg}"
+               return
+       }
+
+       apply_configs "$cfg_path" "$cfg"
+
+       local strings_path="${cfg_path}/strings/0x409"
+       mkdir "$strings_path"
+       apply_configs "$strings_path" "$cfg"
+}
+
+setup_function() {
+       local cfg="$1"
+       local usbcfg gadget usbfun
+
+       config_get usbcfg "$cfg" configuration
+       [ -z "$usbcfg" ] && return
+       config_get usbfun "$cfg" function
+       [ -z "$usbfun" ] && return
+
+       config_get gadget "$usbcfg" gadget
+       local gadget_path="${GADGET_FS}/${GADGET_PREFIX}${gadget}"
+       local cfg_path="${gadget_path}/configs/${usbcfg}.1"
+       [ -d "${cfg_path}" ] || return
+
+       local fun_path="${gadget_path}/functions/${usbfun}.${cfg}"
+       mkdir "$fun_path" || {
+               log "failed to create function ${usbfun}.${cfg}"
+               return
+       }
+
+       apply_configs "$fun_path" "$cfg"
+
+       ln -s "$fun_path" "$cfg_path"
+}
+
+attach_gadget() {
+       local cfg="$1"
+       local gadget_path="${GADGET_FS}/${GADGET_PREFIX}${cfg}"
+       local param udc
+
+       config_get udc "$cfg" UDC
+       [ -z "$udc" ] && return
+
+       echo "$udc" > "$gadget_path/UDC"
+}
+
+load_gadget() {
+       config_foreach setup_gadget gadget
+       config_foreach setup_configuration configuration
+       config_foreach setup_function function
+       config_foreach attach_gadget gadget
+}
+
+# use subshell for isolated env
+apply_preset() (
+       local preset="$1"
+       local gadget="$2"
+       UCI_CONFIG_DIR=$GADGET_PRESETS_DIR config_load "$1"
+       config_set g1 UDC "$2"
+       load_gadget
+)
+
+load_preset() {
+       local cfg="$1"
+       config_get name "$cfg" name
+       config_get udc "$cfg" UDC
+       [ -z "$udc" ] && return
+       apply_preset $name $udc
+}
+
+start() {
+       grep -q /sys/kernel/config /proc/mounts || \
+               mount -t configfs configfs /sys/kernel/config
+
+       [ -d /sys/kernel/config/usb_gadget ] || {
+               log "usb_gadget support not found."
+               return 1
+       }
+
+       config_load usbgadget
+       config_foreach load_preset preset
+       load_gadget
+}
+
+stop() {
+       for gadget_path in ${GADGET_FS}/${GADGET_PREFIX}* ; do
+               [ -d "$gadget_path" ] || continue
+               echo "" > ${gadget_path}/UDC
+               find ${gadget_path}/configs -maxdepth 2 -type l -exec rm '{}' ';'
+               rmdir ${gadget_path}/configs/*/strings/*
+               rmdir ${gadget_path}/configs/*
+               rmdir ${gadget_path}/functions/*
+               rmdir ${gadget_path}/strings/*
+               rmdir $gadget_path
+       done
+}