#!/bin/sh # # Usage: # usbdevice [start|update|stop] # # Hookable stages: # usb___hook # ___hook # # Example hook: # root@RK3588:/# more /etc/usbdevice.d/usb_init.sh # #!/bin/sh # usb_pre_init_hook() # { # echo "usb pre-init hook" # } # # root@RK3588:/# more /etc/usbdevice.d/uvc.sh # #!/bin/sh # UVC_INSTANCES="uvc.0 uvc.1" # # root@rk3588:/# more /etc/usbdevice.d/rndis.sh # #!/bin/sh # rndis_post_start_hook() # { # while ! ifconfig usb0 >/dev/null 2>&1; do # sleep .1 # done # ifconfig usb0 192.168.1.1 # } [ -z "$DEBUG" ] || set -x # Load default env variables from profiles . /etc/profile # Enable error out after loading env set -e TAG_DIR=/var/run/ LOG_FILE=/var/log/usbdevice.log LOCK_FILE=/var/run/.usbdevice.lock USB_FUNCS_FILE=/var/run/.usbdevice usb_enable() { touch $USB_FUNCS_FILE } usb_disable() { rm -f $USB_FUNCS_FILE } usb_is_enabled() { [ -f $USB_FUNCS_FILE ] } usb_set_started() { echo $USB_FUNCS > $USB_FUNCS_FILE } usb_get_started() { usb_is_enabled || return 0 cat $USB_FUNCS_FILE } CONFIGFS_DIR=/sys/kernel/config USB_GROUP=rockchip USB_STRINGS_ATTR=strings/0x409 USB_GADGET_DIR=$CONFIGFS_DIR/usb_gadget/$USB_GROUP USB_GADGET_STRINGS_DIR=$USB_GADGET_DIR/$USB_STRINGS_ATTR USB_FUNCTIONS_DIR=$USB_GADGET_DIR/functions USB_CONFIGS_DIR=$USB_GADGET_DIR/configs/b.1 USB_CONFIGS_STRINGS_DIR=$USB_CONFIGS_DIR/$USB_STRINGS_ATTR # Make sure that we own this session (pid equals sid) if [ $(sed "s/(.*)//" /proc/$$/stat | cut -d' ' -f6) != $$ ]; then setsid $0 $@ exit $? fi # ---- helper functions usb_msg() { if which logger >/dev/null; then logger -t "$(basename "$0")" "[$$]: $@" fi echo "[$(date +"%F %T")] $(basename "$0"): $@" } # Based on <> V2.0.0 usb_pid() { case "$(echo $USB_FUNCS | xargs -n 1 | sort | xargs | tr ' ' '-')" in ums) echo 0x0000;; mtp) echo 0x0001;; # For Android ptp) echo 0x0002;; # For Android rndis) echo 0x0003;; midi) echo 0x0004;; # For Android uvc) echo 0x0005;; # For Android adb) echo 0x0006;; # Single ADB # mtp) echo 0x0007;; # For Android # ptp) echo 0x0008;; # For Android ntb) echo 0x0009;; # For next Computing-Stick # ums) echo 0x0010;; # For U-Boot # adb-ums) echo 0x0010;; # Out-dated adb-mtp) echo 0x0011;; # adb-ptp) echo 0x0012;; # For Android adb-rndis) echo 0x0013;; # For Android adb-midi) echo 0x0014;; # For Android adb-uvc) echo 0x0015;; # For Android adb-uac[12]-uvc|adb-uvc-rndis) echo 0x0016;; # adb-mtp) echo 0x0017;; # For Android adb-ums) echo 0x0018;; # adb-ptp) echo 0x0018;; # For Android # ntb) echo 0x0018;; # For Computing-Stick # hid-ntb|ntb-rndis) echo 0x0019;; # For next Computing-Stick adb-*|*-adb|*-adb-*) echo 0x0012;; # Undefined composite ADB *) echo 0x00ff;; # Undefined esac } usb_instances() { for func in $@; do VAR=$(echo $func | tr 'a-z' 'A-Z')_INSTANCES eval echo "\${$VAR:-$func.0}" done } usb_run_stage() { for f in $1_pre_$2_hook $1_$2 $1_post_$2_hook; do type $f >/dev/null 2>/dev/null || continue usb_msg "Run stage: $f" eval $f || return 1 done } usb_wait_access() { for i in `seq 200`;do if fuser -s $@ 2>/dev/null; then return 0 fi sleep .01 done return 1 } usb_release() { for i in `seq 200`;do fuser -s -k $@ 2>/dev/null || break sleep .01 done } # usage: usb_mount usb_mount() { mkdir -p $2 mountpoint -q $2 || mount $@ } usb_umount() { mountpoint -q $1 || return 0 usb_release -m $1 umount $1 } usb_symlink() { mkdir -p $1 mkdir -p $(dirname $2) [ -e $2 ] || ln -s $1 $2 } usb_start_daemon() { NAME=$(echo $1 | sed "s~^[^ ]*/\([^ ]*\).*~\1~") TAG_FILE=$TAG_DIR.usb_$NAME # Enable spawn touch $TAG_FILE # Already started [ -z "$(usb_get_started)" ] || return 0 # Start and spawn background daemon { exec 3<&- cd /; cd / while usb_is_enabled; do # Don't spawn after stopped [ ! -f $TAG_FILE ] || /sbin/start-stop-daemon -Sqx $@ || true sleep .5 done }& } usb_stop_daemon() { NAME=$(echo $1 | sed "s~^[^ ]*/\([^ ]*\).*~\1~") TAG_FILE=$TAG_DIR.usb_$NAME # Stop and disable spawn rm -f $TAG_FILE /sbin/start-stop-daemon -Kqox $@ } usb_load_config() { USB_CONFIG_FILE=$(find /tmp/ /etc/ -name .usb_config | head -n 1) [ -n "$USB_CONFIG_FILE" -a -r "$USB_CONFIG_FILE" ] || return 0 echo "Loading configs from $USB_CONFIG_FILE ..." ums_parse() { grep "\<$1=" "$USB_CONFIG_FILE" | cut -d'=' -f2 } UMS_FILE="$(ums_parse ums_block)" UMS_SIZE=$(ums_parse ums_block_size || echo 0)M UMS_FSTYPE=$(ums_parse ums_block_type) UMS_MOUNT=$([ "$(ums_parse ums_block_auto_mount)" != on ] || echo 1) UMS_RO=$([ "$(ums_parse ums_block_ro)" != on ] || echo 1) USB_FUNCS="$(sed -n "s/usb_\(.*\)_en/\1/p" "$USB_CONFIG_FILE" | xargs)" } # ---- adb ADB_INSTANCES=${ADB_INSTANCES:-ffs.adb} adb_prepare() { usb_mount adb /dev/usb-ffs/adb -o uid=2000,gid=2000 -t functionfs /sbin/ifup --force lo 2>/dev/null || /sbin/ifup -f lo 2>/dev/null usb_start_daemon /usr/bin/adbd usb_wait_access -m /dev/usb-ffs/adb } adb_stop() { usb_stop_daemon /usr/bin/adbd } # ---- ntb NTB_INSTANCES=${NTB_INSTANCES:-ffs.ntb} ntb_prepare() { usb_mount ntb /dev/usb-ffs/ntb -o uid=2000,gid=2000 -t functionfs usb_start_daemon /usr/bin/ntbd usb_wait_access -m /dev/usb-ffs/ntb } ntb_stop() { usb_stop_daemon /usr/bin/ntbd } # ---- uac1 uac1_prepare() { for f in $(find . -name "*_feature_unit"); do echo 1 >$f done } # ---- uac2 uac2_prepare() { uac1_prepare } # ---- mtp MTP_INSTANCES=${MTP_INSTANCES:-ffs.mtp} mtp_prepare() { usb_mount mtp /dev/usb-ffs/mtp -o uid=2000,gid=2000 -t functionfs usb_start_daemon /usr/bin/umtprd usb_wait_access -m /dev/usb-ffs/mtp } mtp_stop() { usb_stop_daemon /usr/bin/umtprd } # ---- uvc uvc_add_yuyv() { WIDTH=$(echo $1 | cut -d'x' -f1) HEIGHT=$(echo $1 | cut -d'x' -f2) DIR=streaming/uncompressed/u/${HEIGHT}p [ ! -d $DIR ] || return 0 mkdir -p $DIR echo $WIDTH > $DIR/wWidth echo $HEIGHT > $DIR/wHeight echo 333333 > $DIR/dwDefaultFrameInterval echo $((WIDTH * HEIGHT * 20)) > $DIR/dwMinBitRate echo $((WIDTH * HEIGHT * 20)) > $DIR/dwMaxBitRate echo $((WIDTH * HEIGHT * 2)) > $DIR/dwMaxVideoFrameBufferSize echo -e "333333\n666666\n1000000\n2000000" > $DIR/dwFrameInterval } uvc_add_mjpeg() { WIDTH=$(echo $1 | cut -d'x' -f1) HEIGHT=$(echo $1 | cut -d'x' -f2) DIR=streaming/mjpeg/m/${HEIGHT}p [ ! -d $DIR ] || return 0 mkdir -p $DIR echo $WIDTH > $DIR/wWidth echo $HEIGHT > $DIR/wHeight echo 333333 > $DIR/dwDefaultFrameInterval echo $((WIDTH * HEIGHT * 20)) > $DIR/dwMinBitRate echo $((WIDTH * HEIGHT * 20)) > $DIR/dwMaxBitRate echo $((WIDTH * HEIGHT * 2)) > $DIR/dwMaxVideoFrameBufferSize echo -e "333333\n666666\n1000000\n2000000" > $DIR/dwFrameInterval } uvc_add_h264() { WIDTH=$(echo $1 | cut -d'x' -f1) HEIGHT=$(echo $1 | cut -d'x' -f2) DIR=streaming/framebased/f/${HEIGHT}p [ ! -d $DIR ] || return 0 mkdir -p $DIR echo $WIDTH > $DIR/wWidth echo $HEIGHT > $DIR/wHeight echo 333333 > $DIR/dwDefaultFrameInterval echo $((WIDTH * HEIGHT * 10)) > $DIR/dwMinBitRate echo $((WIDTH * HEIGHT * 10)) > $DIR/dwMaxBitRate echo -e "333333\n666666\n1000000\n2000000" > $DIR/dwFrameInterval } uvc_support_resolutions() { case ${1:-yuyv} in yuyv) echo "640x480 1280x720";; mjpeg) echo "640x480 1280x720 1920x1080 2560x1440 2592x1944";; h264) echo "640x480 1280x720 1920x1080";; esac } uvc_prepare() { UVC_DIR=$(pwd) UVC_YUYV_RES=$(uvc_support_resolutions yuyv) if [ -n "$UVC_YUYV_RES" ]; then for res in $UVC_YUYV_RES; do uvc_add_yuyv $res done usb_symlink $UVC_DIR/streaming/uncompressed/u \ $UVC_DIR/streaming/header/h/u fi UVC_MJPEG_RES=$(uvc_support_resolutions mjpeg) if [ -n "$UVC_MJPEG_RES" ]; then for res in $UVC_MJPEG_RES; do uvc_add_mjpeg $res done usb_symlink $UVC_DIR/streaming/mjpeg/m \ $UVC_DIR/streaming/header/h/m fi UVC_H264_RES=$(uvc_support_resolutions h264) if [ -n "$UVC_H264_RES" ]; then for res in $UVC_H264_RES; do uvc_add_h264 $res done echo -ne H264 > $UVC_DIR/streaming/framebased/f/guidFormat \ 2>/dev/null usb_symlink $UVC_DIR/streaming/framebased/f \ $UVC_DIR/streaming/header/h/f fi echo 2048 > $UVC_DIR/streaming_maxpacket usb_symlink $UVC_DIR/control/header/h $UVC_DIR/control/class/fs/h usb_symlink $UVC_DIR/control/header/h $UVC_DIR/control/class/ss/h usb_symlink $UVC_DIR/streaming/header/h $UVC_DIR/streaming/class/fs/h usb_symlink $UVC_DIR/streaming/header/h $UVC_DIR/streaming/class/hs/h usb_symlink $UVC_DIR/streaming/header/h $UVC_DIR/streaming/class/ss/h # Start your UVC daemon usb_start_daemon /usr/bin/uvc-gadget -- $instance } uvc_stop() { # Stop your UVC daemon usb_stop_daemon /usr/bin/uvc-gadget } # ---- hid hid_prepare() { echo 1 > protocol echo 1 > subclass echo 8 > 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" \ > report_desc } # ---- ums UMS_INSTANCES=${UMS_INSTANCES:-mass_storage.0} ums_prepare() { if ! stat "$UMS_FILE" >/dev/null 2>/dev/null; then usb_msg "Formating $UMS_FILE($UMS_SIZE) to $UMS_FSTYPE" truncate -s $UMS_SIZE "$UMS_FILE" mkfs.$UMS_FSTYPE "$UMS_FILE" || \ usb_msg "Failed to format $UMS_FILE to $UMS_FSTYPE" fi } ums_to_pc() { if [ "$(cat lun.0/ro)" != "$UMS_RO" ]; then echo > lun.0/file echo $UMS_RO > lun.0/ro fi if ! grep -wq "$UMS_FILE" lun.0/file; then usb_umount "$UMS_MOUNTPOINT" fsck -y "$UMS_FILE" || true echo "$UMS_FILE" > lun.0/file fi } ums_to_device() { echo > lun.0/file fsck -y "$UMS_FILE" || true [ "$UMS_MOUNT" -eq 1 ] || return 0 usb_umount "$UMS_MOUNTPOINT" # Try auto fstype firstly usb_mount "$UMS_FILE" "$UMS_MOUNTPOINT" -o sync 2>/dev/null || \ usb_mount "$UMS_FILE" "$UMS_MOUNTPOINT" -o sync -t $UMS_FSTYPE || \ usb_mount "$UMS_FILE" "$UMS_MOUNTPOINT" -o sync,loop -t $UMS_FSTYPE } ums_start() { case "$USB_STATE" in CONFIGURED) ums_to_pc ;; DISCONNECTED) ums_to_device ;; esac } ums_stop() { echo > lun.0/file if [ "$UMS_MOUNT" ]; then usb_umount "$UMS_MOUNTPOINT" fi } # ---- global usb_init() { # Clean it up before initializing. usb_disable usb_msg "Initializing" cd $USB_GADGET_DIR printf "0x%x" ${USB_VENDOR_ID:-0x2207} > idVendor printf "0x%x" ${USB_FW_VERSION:-0x0310} > bcdDevice mkdir -p $USB_GADGET_STRINGS_DIR SERIAL=$(grep Serial /proc/cpuinfo | cut -d':' -f2) echo ${SERIAL:-0123456789ABCDEF} > $USB_GADGET_STRINGS_DIR/serialnumber echo ${USB_MANUFACTUER:-Rockchip} > $USB_GADGET_STRINGS_DIR/manufacturer echo ${USB_PRODUCT:-rk3xxx} > $USB_GADGET_STRINGS_DIR/product mkdir -p $USB_CONFIGS_DIR echo 500 > $USB_CONFIGS_DIR/MaxPower echo 0x1 > os_desc/b_vendor_code echo MSFT100 > os_desc/qw_sign ln -s $USB_CONFIGS_DIR os_desc/ mkdir -p $USB_CONFIGS_STRINGS_DIR } usb_funcs_grep() { echo $USB_FUNCS | xargs -n 1 | sort | uniq | grep $@ || true } usb_funcs_sort() { # Orders required by kernel FUNCS="rndis uac uvc ntb ums mtp acm" { for func in $FUNCS; do usb_funcs_grep $func done usb_funcs_grep -vE $(echo $FUNCS adb | tr ' ' '|') usb_funcs_grep adb } | uniq | xargs } usb_prepare() { usb_load_config # Allow function/variable overriding if [ -d /etc/usbdevice.d ]; then for hook in $(find /etc/usbdevice.d/ -type f); do source "$hook" done fi UMS_FILE=${UMS_FILE:-/userdata/ums_shared.img} UMS_SIZE=${UMS_SIZE:-256M} UMS_FSTYPE=${UMS_FSTYPE:-vfat} UMS_MOUNT=${UMS_MOUNT:-0} UMS_MOUNTPOINT="${UMS_MOUNTPOINT:-/mnt/ums}" UMS_RO=${UMS_RO:-0} USB_FUNCS=${USB_FUNCS:-adb} USB_FUNCS="$(echo "$USB_FUNCS" | tr ',' ' ')" USB_FUNCS="$(echo "$USB_FUNCS" | usb_funcs_sort)" USB_CONFIG="$(echo "$USB_FUNCS" | tr ' ' '_')" if [ ! -d $USB_GADGET_DIR ]; then mountpoint -q $CONFIGFS_DIR || \ mount -t configfs none $CONFIGFS_DIR mkdir -p $USB_GADGET_DIR cd $USB_GADGET_DIR # Global initialize usb_run_stage usb init || return 1 fi USB_STATE=$(cat /sys/class/android_usb/android*/state 2>/dev/null) usb_msg "USB state: ${USB_STATE:-unknown}" USB_UDC=$(ls /sys/class/udc/ | head -n 1) if [ -z "$USB_UDC" ]; then usb_msg "Failed to find a valid UDC device..." return 1 fi usb_msg "Using USB UDC device: $USB_UDC" # Parse started USB functions OLD_FUNCS=$(usb_get_started) # Stop old USB functions when USB functions changed if [ -n "$OLD_FUNCS" ] && [ "$OLD_FUNCS" != "$USB_FUNCS" ]; then usb_msg "Functions changed $OLD_FUNCS -> $USB_FUNCS" usb_stop fi # Update USB PID printf "0x%x" ${USB_PRODUCT_ID:-$(usb_pid)} > $USB_GADGET_DIR/idProduct } usb_start() { usb_msg "Starting functions: $USB_FUNCS" echo "$USB_CONFIG" > $USB_CONFIGS_STRINGS_DIR/configuration for func in $USB_FUNCS; do for instance in $(usb_instances $func); do usb_msg "Preparing instance: $instance" if ! mkdir -p $USB_FUNCTIONS_DIR/$instance \ 2>/dev/null; then usb_msg "Failed to create instance: $instance" continue fi cd $USB_FUNCTIONS_DIR/$instance \ >/dev/null 2>/dev/null || continue usb_run_stage $func prepare || return 1 # Make symlink after prepared (required by UVC) usb_symlink $USB_FUNCTIONS_DIR/$instance \ $USB_CONFIGS_DIR/f-$instance done done if ! grep -q "^$USB_UDC$" $USB_GADGET_DIR/UDC; then echo "$USB_UDC" > $USB_GADGET_DIR/UDC fi for func in $USB_FUNCS; do for instance in $(usb_instances $func); do cd $USB_FUNCTIONS_DIR/$instance \ >/dev/null 2>/dev/null || continue usb_msg "Starting instance: $instance" usb_run_stage $func start || return 1 done done # Store started functions usb_set_started } usb_stop() { if [ -n "$OLD_FUNCS" ]; then usb_msg "Stopping functions: $OLD_FUNCS" fi echo > $USB_GADGET_DIR/UDC for func in $USB_FUNCS; do for instance in $(usb_instances $func); do cd $USB_FUNCTIONS_DIR/$instance \ >/dev/null 2>/dev/null || continue usb_msg "Stopping instance: $instance" usb_run_stage $func stop done done rm -f $USB_CONFIGS_DIR/f-* # Keep ffs instances registered and mounted, to avoid random crashes find $USB_FUNCTIONS_DIR -mindepth 1 -maxdepth 1 ! -name 'ffs.*' | \ xargs rmdir 2>/dev/null || true # Clear functions to avoid stopping them again unset OLD_FUNCS } usb_restart() { usb_run_stage usb stop usb_run_stage usb start } ACTION=${1:-update} if [ "$ACTION" = update ]; then usb_is_enabled || exit 0 fi # Lock it touch $LOCK_FILE exec 3<$LOCK_FILE flock -x 3 if [ -z "$DEBUG" ]; then echo "Starting $0 ${ACTION}, log saved to $LOG_FILE" # Redirect outputs to log file exec >>$LOG_FILE 2>&1 fi usb_msg "Handling ${ACTION} request" if ! usb_run_stage usb prepare; then usb_run_stage usb stop exit 1 fi case "$ACTION" in start|update) for i in `seq 5`; do usb_enable if usb_run_stage usb start; then break fi sleep 1 usb_msg "Retrying $ACTION request" usb_disable usb_run_stage usb stop done ;; stop) usb_disable usb_run_stage usb stop ;; restart) usb_enable usb_run_stage usb restart ;; *) echo "Usage: usbdevice [start|stop|restart|update]" >&2 ;; esac usb_msg "Done $ACTION request" echo # Unlock it flock -u 3