This commit is contained in:
Miguel Fuertes committed 2025-10-07 20:10:37 +02:00
1 parent 6199ca9108
commit 632c4ae4af
2 files changed
+173 -119

No files matched your search

+2 -2
View File
@@ -13,7 +13,7 @@ define Package/$(PKG_NAME)
SECTION:=utils
CATEGORY:=Utilities
TITLE:=UCI-based USB Gadget Manager for OpenWrt
DEPENDS:=@USB_GADGET_SUPPORT +bash +kmod-fs-configfs +kmod-usb-gadget +kmod-usb-lib-composite +kmod-usb-gadget-serial +kmod-usb-gadget-mass-storage +kmod-usb-gadget-ncm +coreutils-truncate +coreutils-shuf +coreutils-dd
DEPENDS:=@USB_GADGET_SUPPORT +kmod-fs-configfs +kmod-usb-gadget +kmod-usb-lib-composite +kmod-usb-gadget-serial +kmod-usb-gadget-mass-storage +kmod-usb-gadget-ncm +coreutils-truncate +coreutils-shuf +coreutils-dd
PKGARCH:=all
endef
@@ -57,7 +57,7 @@ define Package/$(PKG_NAME)/install
# Install init script
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/usb-gadget.init $(1)/etc/init.d/usb-gadget
# $(INSTALL_BIN) ./files/usb-gadget.init $(1)/etc/init.d/usb-gadget
# Install UCI config
$(INSTALL_DIR) $(1)/etc/config
+171 -117
View File
@@ -1,6 +1,6 @@
#!/bin/bash
#!/bin/sh
# Universal USB Gadget Manager for OpenWrt
# Requires: bash, UCI, USB gadget kernel support
# Compatible with ash/busybox sh
set -e
@@ -8,44 +8,70 @@ set -e
. /lib/functions.sh
# ============================================================================
# Configuration
# Configuration
# ============================================================================
declare -A CFG
CFG_ENABLED=""
CFG_GADGET_NAME=""
CFG_GADGET_PATH=""
CFG_CONFIG_PATH=""
CFG_FUNCTIONS_PATH=""
CFG_VENDOR_ID=""
CFG_PRODUCT_ID=""
CFG_DEVICE_VERSION=""
CFG_MANUFACTURER=""
CFG_PRODUCT=""
CFG_UDC_DEVICE=""
CFG_RNDIS=""
CFG_ECM=""
CFG_NCM=""
CFG_ACM=""
CFG_UMS=""
CFG_ACM_SHELL=""
CFG_UMS_IMAGE=""
CFG_UMS_SIZE=""
CFG_UMS_READONLY=""
load_config() {
config_load usbgadget
# Gadget name (configurable, works on any hardware)
config_get CFG[gadget_name] usb gadget_name "g1"
# Global enable/disable
config_get_bool CFG_ENABLED usb enabled 1
# Gadget name
config_get CFG_GADGET_NAME usb gadget_name "g1"
# Paths
CFG[gadget_path]="/sys/kernel/config/usb_gadget/${CFG[gadget_name]}"
CFG[config_path]="${CFG[gadget_path]}/configs/c.1"
CFG[functions_path]="${CFG[gadget_path]}/functions"
CFG_GADGET_PATH="/sys/kernel/config/usb_gadget/${CFG_GADGET_NAME}"
CFG_CONFIG_PATH="${CFG_GADGET_PATH}/configs/c.1"
CFG_FUNCTIONS_PATH="${CFG_GADGET_PATH}/functions"
# USB Device IDs
config_get CFG[vendor_id] usb vendor_id "0x1d6b"
config_get CFG[product_id] usb product_id "0x0104"
config_get CFG[device_version] usb device_version "0x0100"
config_get CFG[manufacturer] usb manufacturer "OpenWrt"
config_get CFG[product] usb product "USB Gadget"
config_get CFG[udc_device] usb udc_device ""
config_get CFG_VENDOR_ID usb vendor_id "0x1d6b"
config_get CFG_PRODUCT_ID usb product_id "0x0104"
config_get CFG_DEVICE_VERSION usb device_version "0x0100"
config_get CFG_MANUFACTURER usb manufacturer "OpenWrt"
config_get CFG_PRODUCT usb product "USB Gadget"
config_get CFG_UDC_DEVICE usb udc_device ""
# Functions (boolean)
config_get_bool CFG[rndis] rndis enabled 0
config_get_bool CFG[ecm] ecm enabled 0
config_get_bool CFG[ncm] ncm enabled 0
config_get_bool CFG[acm] acm enabled 0
config_get_bool CFG[ums] ums enabled 0
config_get_bool CFG_RNDIS rndis enabled 0
config_get_bool CFG_ECM ecm enabled 0
config_get_bool CFG_NCM ncm enabled 0
config_get_bool CFG_ACM acm enabled 0
config_get_bool CFG_UMS ums enabled 0
# ACM options
config_get_bool CFG[acm_shell] acm shell 1
config_get_bool CFG_ACM_SHELL acm shell 1
# UMS options
config_get CFG[ums_image] ums image_path "/var/lib/usb-gadget/storage.img"
config_get CFG[ums_size] ums image_size "512M"
config_get_bool CFG[ums_readonly] ums readonly 0
config_get CFG_UMS_IMAGE ums image_path "/var/lib/usb-gadget/storage.img"
config_get CFG_UMS_SIZE ums image_size "512M"
config_get_bool CFG_UMS_READONLY ums readonly 0
}
# ============================================================================
@@ -67,10 +93,10 @@ error() {
# ============================================================================
get_serial_number() {
[[ -f /etc/machine-id ]] && {
if [ -f /etc/machine-id ]; then
sha256sum < /etc/machine-id | cut -d' ' -f1 | cut -c1-16
return
}
fi
echo "$(date +%s)-$(shuf -i 1000-9999 -n 1)"
}
@@ -79,26 +105,33 @@ generate_mac() {
local serial="$(get_serial_number)"
local hash="$(echo "${serial}${prefix}" | md5sum | cut -c1-12)"
# Set locally administered, unicast
local b1="$(printf '%02x' $((0x${hash:0:2} & 0xfe | 0x02)))"
# Extract bytes
local b1="$(echo "$hash" | cut -c1-2)"
local b2="$(echo "$hash" | cut -c3-4)"
local b3="$(echo "$hash" | cut -c5-6)"
local b4="$(echo "$hash" | cut -c7-8)"
local b5="$(echo "$hash" | cut -c9-10)"
local b6="$(echo "$hash" | cut -c11-12)"
echo "${b1}:${hash:2:2}:${hash:4:2}:${hash:6:2}:${hash:8:2}:${hash:10:2}"
# Set locally administered, unicast
b1="$(printf '%02x' $((0x${b1} & 0xfe | 0x02)))"
echo "${b1}:${b2}:${b3}:${b4}:${b5}:${b6}"
}
find_udc() {
# Return configured UDC if set
[[ -n "${CFG[udc_device]}" ]] && {
echo "${CFG[udc_device]}"
if [ -n "$CFG_UDC_DEVICE" ]; then
echo "$CFG_UDC_DEVICE"
return
}
fi
# Auto-detect UDC - works on ALL hardware:
# - Raspberry Pi (dwc2): 20980000.usb, fe980000.usb, 1000480000.usb
# - MSM8916 (ci_hdrc): ci_hdrc.0
# - Orange Pi (musb): musb-hdrc.4.auto
# Auto-detect UDC - works on ALL hardware
local udc="$(ls /sys/class/udc/ 2>/dev/null | head -1)"
[[ -n "$udc" ]] || error "No UDC device found. Is USB gadget support enabled?"
if [ -z "$udc" ]; then
error "No UDC device found. Is USB gadget support enabled?"
fi
echo "$udc"
}
@@ -115,57 +148,57 @@ sysfs_write() {
setup_rndis() {
log "Enabling RNDIS"
local func="${CFG[functions_path]}/rndis.usb0"
local func="${CFG_FUNCTIONS_PATH}/rndis.usb0"
mkdir -p "$func"
sysfs_write "${func}/host_addr" "$(generate_mac rndis-host)"
sysfs_write "${func}/dev_addr" "$(generate_mac rndis-dev)"
ln -sf "$func" "${CFG[config_path]}/"
ln -sf "$func" "${CFG_CONFIG_PATH}/"
# Windows compatibility
sysfs_write "${CFG[gadget_path]}/os_desc/use" "1"
sysfs_write "${CFG[gadget_path]}/os_desc/b_vendor_code" "0xcd"
sysfs_write "${CFG[gadget_path]}/os_desc/qw_sign" "MSFT100"
sysfs_write "${CFG_GADGET_PATH}/os_desc/use" "1"
sysfs_write "${CFG_GADGET_PATH}/os_desc/b_vendor_code" "0xcd"
sysfs_write "${CFG_GADGET_PATH}/os_desc/qw_sign" "MSFT100"
sysfs_write "${func}/os_desc/interface.rndis/compatible_id" "RNDIS"
sysfs_write "${func}/os_desc/interface.rndis/sub_compatible_id" "5162001"
ln -sf "${CFG[config_path]}" "${CFG[gadget_path]}/os_desc/"
ln -sf "${CFG_CONFIG_PATH}" "${CFG_GADGET_PATH}/os_desc/"
echo "+RNDIS"
}
setup_ecm() {
log "Enabling ECM"
local func="${CFG[functions_path]}/ecm.usb0"
local func="${CFG_FUNCTIONS_PATH}/ecm.usb0"
mkdir -p "$func"
sysfs_write "${func}/host_addr" "$(generate_mac ecm-host)"
sysfs_write "${func}/dev_addr" "$(generate_mac ecm-dev)"
ln -sf "$func" "${CFG[config_path]}/"
ln -sf "$func" "${CFG_CONFIG_PATH}/"
echo "+ECM"
}
setup_ncm() {
log "Enabling NCM"
local func="${CFG[functions_path]}/ncm.usb0"
local func="${CFG_FUNCTIONS_PATH}/ncm.usb0"
mkdir -p "$func"
sysfs_write "${func}/host_addr" "$(generate_mac ncm-host)"
sysfs_write "${func}/dev_addr" "$(generate_mac ncm-dev)"
ln -sf "$func" "${CFG[config_path]}/"
ln -sf "$func" "${CFG_CONFIG_PATH}/"
echo "+NCM"
}
setup_acm() {
log "Enabling ACM"
local func="${CFG[functions_path]}/acm.GS0"
local func="${CFG_FUNCTIONS_PATH}/acm.GS0"
mkdir -p "$func"
ln -sf "$func" "${CFG[config_path]}/"
ln -sf "$func" "${CFG_CONFIG_PATH}/"
# Manage shell in inittab
if [[ "${CFG[acm_shell]}" == "1" ]]; then
if [ "$CFG_ACM_SHELL" = "1" ]; then
log "Enabling serial shell on ttyGS0"
sed -i '/ttyGS0/d' /etc/inittab
echo "ttyGS0::askfirst:/usr/libexec/login.sh" >> /etc/inittab
@@ -180,23 +213,23 @@ setup_acm() {
}
setup_ums() {
local image="${CFG[ums_image]}"
local image="$CFG_UMS_IMAGE"
# Create image if doesn't exist
[[ ! -f "$image" ]] && {
log "Creating storage image: $image (${CFG[ums_size]})"
if [ ! -f "$image" ]; then
log "Creating storage image: $image ($CFG_UMS_SIZE)"
mkdir -p "$(dirname "$image")"
truncate -s "${CFG[ums_size]}" "$image" || \
dd if=/dev/zero of="$image" bs=1M count="${CFG[ums_size]%M}"
}
truncate -s "$CFG_UMS_SIZE" "$image" || \
dd if=/dev/zero of="$image" bs=1M count="${CFG_UMS_SIZE%M}"
fi
log "Enabling UMS"
local func="${CFG[functions_path]}/mass_storage.0"
local func="${CFG_FUNCTIONS_PATH}/mass_storage.0"
mkdir -p "$func"
sysfs_write "${func}/lun.0/ro" "${CFG[ums_readonly]}"
sysfs_write "${func}/lun.0/ro" "$CFG_UMS_READONLY"
sysfs_write "${func}/lun.0/file" "$image"
ln -sf "$func" "${CFG[config_path]}/"
ln -sf "$func" "${CFG_CONFIG_PATH}/"
echo "+UMS"
}
@@ -207,7 +240,8 @@ setup_ums() {
add_to_bridge() {
local ifname_file="$1"
[[ ! -f "$ifname_file" ]] && return
[ -f "$ifname_file" ] || return
local iface="$(cat "$ifname_file")"
log "Adding $iface to bridge"
@@ -219,9 +253,9 @@ add_to_bridge() {
setup_network() {
sleep 1
[[ "${CFG[rndis]}" == "1" ]] && add_to_bridge "${CFG[functions_path]}/rndis.usb0/ifname"
[[ "${CFG[ecm]}" == "1" ]] && add_to_bridge "${CFG[functions_path]}/ecm.usb0/ifname"
[[ "${CFG[ncm]}" == "1" ]] && add_to_bridge "${CFG[functions_path]}/ncm.usb0/ifname"
[ "$CFG_RNDIS" = "1" ] && add_to_bridge "${CFG_FUNCTIONS_PATH}/rndis.usb0/ifname"
[ "$CFG_ECM" = "1" ] && add_to_bridge "${CFG_FUNCTIONS_PATH}/ecm.usb0/ifname"
[ "$CFG_NCM" = "1" ] && add_to_bridge "${CFG_FUNCTIONS_PATH}/ncm.usb0/ifname"
uci commit network
/etc/init.d/network reload
@@ -232,37 +266,36 @@ setup_network() {
# ============================================================================
setup_gadget() {
log "Setting up USB gadget"
# Load configuration
load_config
# Check if gadget is enabled
if [[ "${CFG[enabled]}" != "1" ]]; then
if [ "$CFG_ENABLED" != "1" ]; then
log "USB Gadget is disabled in config (usb.enabled=0)"
log "USB port available for host mode"
return 0
fi
log "Gadget name: ${CFG[gadget_name]}"
log "Gadget path: ${CFG[gadget_path]}"
log "Setting up USB gadget"
log "Gadget name: $CFG_GADGET_NAME"
log "Gadget path: $CFG_GADGET_PATH"
# Prepare system
modprobe libcomposite || error "Failed to load libcomposite module"
mountpoint -q /sys/kernel/config || {
if ! mountpoint -q /sys/kernel/config; then
log "Mounting configfs"
mount -t configfs none /sys/kernel/config
}
fi
# Create gadget directory
mkdir -p "${CFG[gadget_path]}" || error "Failed to create gadget directory"
cd "${CFG[gadget_path]}"
mkdir -p "$CFG_GADGET_PATH" || error "Failed to create gadget directory"
cd "$CFG_GADGET_PATH" || error "Cannot change directory"
# Device descriptors
sysfs_write idVendor "${CFG[vendor_id]}"
sysfs_write idProduct "${CFG[product_id]}"
sysfs_write bcdDevice "${CFG[device_version]}"
sysfs_write idVendor "$CFG_VENDOR_ID"
sysfs_write idProduct "$CFG_PRODUCT_ID"
sysfs_write bcdDevice "$CFG_DEVICE_VERSION"
# Composite device class
sysfs_write bDeviceClass "0xEF"
@@ -272,47 +305,61 @@ setup_gadget() {
# Strings (USB descriptors)
mkdir -p strings/0x409
sysfs_write strings/0x409/serialnumber "$(get_serial_number)"
sysfs_write strings/0x409/manufacturer "${CFG[manufacturer]}"
sysfs_write strings/0x409/product "${CFG[product]}"
sysfs_write strings/0x409/manufacturer "$CFG_MANUFACTURER"
sysfs_write strings/0x409/product "$CFG_PRODUCT"
# Configuration
mkdir -p "${CFG[config_path]}/strings/0x409"
mkdir -p "${CFG_CONFIG_PATH}/strings/0x409"
local config_string=""
local has_wakeup=0
# Setup enabled functions in order
# RNDIS must be first for Windows compatibility
if [[ "${CFG[rndis]}" == "1" ]]; then
config_string+="$(setup_rndis)"
if [ "$CFG_RNDIS" = "1" ]; then
config_string="${config_string}$(setup_rndis)"
has_wakeup=1
fi
[[ "${CFG[acm]}" == "1" ]] && config_string+="$(setup_acm)"
if [ "$CFG_ACM" = "1" ]; then
config_string="${config_string}$(setup_acm)"
fi
if [[ "${CFG[ecm]}" == "1" ]] && [[ "${CFG[ncm]}" != "1" ]]; then
config_string+="$(setup_ecm)"
if [ "$CFG_ECM" = "1" ] && [ "$CFG_NCM" != "1" ]; then
config_string="${config_string}$(setup_ecm)"
has_wakeup=1
fi
if [[ "${CFG[ncm]}" == "1" ]]; then
config_string+="$(setup_ncm)"
if [ "$CFG_NCM" = "1" ]; then
config_string="${config_string}$(setup_ncm)"
has_wakeup=1
fi
[[ "${CFG[ums]}" == "1" ]] && config_string+="$(setup_ums)"
if [ "$CFG_UMS" = "1" ]; then
config_string="${config_string}$(setup_ums)"
fi
# Check if any function was enabled
if [ -z "$config_string" ]; then
log "WARNING: No USB functions enabled"
log "Enable at least one function (rndis, ecm, ncm, acm, or ums)"
cd /sys/kernel/config/usb_gadget
rmdir "$CFG_GADGET_NAME" 2>/dev/null || true
return 1
fi
# Configuration attributes
if [[ $has_wakeup -eq 1 ]]; then
sysfs_write "${CFG[config_path]}/bmAttributes" "0xe0" # Self-powered + remote wakeup
if [ "$has_wakeup" = "1" ]; then
sysfs_write "${CFG_CONFIG_PATH}/bmAttributes" "0xe0"
else
sysfs_write "${CFG[config_path]}/bmAttributes" "0xc0" # Self-powered
sysfs_write "${CFG_CONFIG_PATH}/bmAttributes" "0xc0"
fi
sysfs_write "${CFG[config_path]}/MaxPower" "250" # 500mA
sysfs_write "${CFG_CONFIG_PATH}/MaxPower" "250"
# Set configuration string (remove leading +)
sysfs_write "${CFG[config_path]}/strings/0x409/configuration" "${config_string#+}"
config_string="$(echo "$config_string" | sed 's/^+//')"
sysfs_write "${CFG_CONFIG_PATH}/strings/0x409/configuration" "$config_string"
# Find and enable UDC
local udc="$(find_udc)"
@@ -320,14 +367,18 @@ setup_gadget() {
sysfs_write UDC "$udc"
# Wait for ACM device if enabled
if [[ "${CFG[acm]}" == "1" ]]; then
if [ "$CFG_ACM" = "1" ]; then
log "Waiting for ttyGS0 device..."
for i in {1..10}; do
[[ -c /dev/ttyGS0 ]] && break
local i=0
while [ $i -lt 10 ]; do
[ -c /dev/ttyGS0 ] && break
sleep 1
i=$((i + 1))
done
[[ ! -c /dev/ttyGS0 ]] && log "Warning: /dev/ttyGS0 not found after 10s"
if [ ! -c /dev/ttyGS0 ]; then
log "Warning: /dev/ttyGS0 not found after 10s"
fi
fi
# Configure network
@@ -342,22 +393,22 @@ teardown_gadget() {
# Load config to get paths
load_config
[[ ! -d "${CFG[gadget_path]}" ]] && {
if [ ! -d "$CFG_GADGET_PATH" ]; then
log "Gadget not found, nothing to tear down"
return
}
fi
cd "${CFG[gadget_path]}"
cd "$CFG_GADGET_PATH" || return
# Disable gadget
echo "" > UDC 2>/dev/null || true
# Remove from network bridge
for ifname in functions/*/ifname; do
[[ -f "$ifname" ]] && {
if [ -f "$ifname" ]; then
local iface="$(cat "$ifname")"
uci -q delete "network.@device[0].ports=$iface" 2>/dev/null || true
}
fi
done
uci commit network
/etc/init.d/network reload
@@ -370,7 +421,7 @@ teardown_gadget() {
# Remove function directories
for func in functions/*; do
[[ -d "$func" ]] && rmdir "$func" 2>/dev/null || true
[ -d "$func" ] && rmdir "$func" 2>/dev/null || true
done
# Remove strings
@@ -378,20 +429,21 @@ teardown_gadget() {
# Remove gadget
cd /sys/kernel/config/usb_gadget
rmdir "${CFG[gadget_name]}" 2>/dev/null || true
rmdir "$CFG_GADGET_NAME" 2>/dev/null || true
# Clean shell from inittab
sed -i '/ttyGS0/d' /etc/inittab
kill -HUP 1 2>/dev/null || true
log "Teardown complete"
log "USB port available for host mode"
}
status() {
load_config
# Check if globally disabled
if [[ "${CFG[enabled]}" != "1" ]]; then
if [ "$CFG_ENABLED" != "1" ]; then
echo "USB Gadget: Disabled in configuration"
echo "Status: Inactive (host mode available)"
echo ""
@@ -399,20 +451,20 @@ status() {
return 1
fi
if [[ -d "${CFG[gadget_path]}" ]] && [[ -s "${CFG[gadget_path]}/UDC" ]]; then
echo "USB Gadget: ${CFG[gadget_name]}"
echo "Status: Active"
echo "UDC: $(cat "${CFG[gadget_path]}/UDC")"
if [ -d "$CFG_GADGET_PATH" ] && [ -s "${CFG_GADGET_PATH}/UDC" ]; then
echo "USB Gadget: $CFG_GADGET_NAME"
echo "Status: Active (device mode)"
echo "UDC: $(cat "${CFG_GADGET_PATH}/UDC")"
echo ""
echo "Enabled functions:"
ls -1 "${CFG[config_path]}" 2>/dev/null | grep -v strings | sed 's/^/ - /'
ls -1 "$CFG_CONFIG_PATH" 2>/dev/null | grep -v strings | sed 's/^/ - /'
# Show ACM status
if [[ "${CFG[acm]}" == "1" ]]; then
if [ "$CFG_ACM" = "1" ]; then
echo ""
echo "Serial console:"
if [[ -c /dev/ttyGS0 ]]; then
if [[ "${CFG[acm_shell]}" == "1" ]]; then
if [ -c /dev/ttyGS0 ]; then
if [ "$CFG_ACM_SHELL" = "1" ]; then
echo " /dev/ttyGS0 - Available (with shell)"
else
echo " /dev/ttyGS0 - Available (raw TTY)"
@@ -425,18 +477,20 @@ status() {
# Show network interfaces
echo ""
echo "Network interfaces:"
for ifname in "${CFG[functions_path]}"/*/ifname; do
[[ -f "$ifname" ]] && {
for ifname in "${CFG_FUNCTIONS_PATH}"/*/ifname; do
if [ -f "$ifname" ]; then
local iface="$(cat "$ifname")"
local state="$(cat /sys/class/net/$iface/operstate 2>/dev/null || echo unknown)"
echo " - $iface ($state)"
}
fi
done
return 0
else
echo "USB Gadget: ${CFG[gadget_name]}"
echo "USB Gadget: $CFG_GADGET_NAME"
echo "Status: Inactive"
echo ""
echo "Start with: /etc/init.d/usb-gadget start"
return 1
fi
}