diff --git a/Makefile b/Makefile index 591d64b45..fa56a2376 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ DOCKER_RUN_BASE := docker run -e UID=$(UID) -e GID=$(GID) -v $(PWD):/home/build/ GO_BUILD_CMD := cd /home/build/NanoKVM/server && go mod tidy && CGO_ENABLED=1 GOOS=linux GOARCH=riscv64 CC=riscv64-unknown-linux-musl-gcc CGO_CFLAGS="-mcpu=c906fdv -march=rv64imafdcv0p7xthead -mcmodel=medany -mabi=lp64d" go build SUPPORT_BUILD_CMD := . ./home/build/MaixCDK/bin/activate && cd /home/build/NanoKVM/support/sg2002 && ./build kvm_system && ./build kvm_system add_to_kvmapp -.PHONY: help check-root builder-image rebuild-image check-image shell app support all clean +.PHONY: help check-root builder-image rebuild-image check-image shell app support all test-usb-scripts clean # Default target all: app support @@ -31,6 +31,7 @@ help: @echo " app - Build Go application server" @echo " support - Build hardware support libraries" @echo " all - Build both app and support (default)" + @echo " test-usb-scripts - Run USB init script regression tests" @echo " clean - Clean build artifacts" @echo "" @echo "Prerequisites:" @@ -83,6 +84,10 @@ support: check-root builder-image @echo "Building support..." @$(DOCKER_RUN_BASE) -it $(IMAGE_NAME) /bin/bash -c '$(SUPPORT_BUILD_CMD)' +test-usb-scripts: + @echo "Running USB init script tests..." + @sh tests/usb-init-scripts-test.sh + # Clean build artifacts clean: @echo "Cleaning build artifacts..." @@ -94,4 +99,4 @@ clean: rm -rf support/sg2002/build; \ echo "Removed support/sg2002/build"; \ fi - @echo "Clean completed." \ No newline at end of file + @echo "Clean completed." diff --git a/kvmapp/system/init.d/S03usb-common b/kvmapp/system/init.d/S03usb-common new file mode 100644 index 000000000..69d16b208 --- /dev/null +++ b/kvmapp/system/init.d/S03usb-common @@ -0,0 +1,258 @@ +#!/bin/sh +# shellcheck disable=SC2034 + +USB_BOOT_DIR="${USB_BOOT_DIR:-/boot}" +USB_GADGET_ROOT="${USB_GADGET_ROOT:-/sys/kernel/config/usb_gadget}" +USB_GADGET_NAME="${USB_GADGET_NAME:-g0}" +USB_UDC_CLASS="${USB_UDC_CLASS:-/sys/class/udc}" +USB_OTG_ROLE="${USB_OTG_ROLE:-/proc/cviusb/otg_role}" +USB_PHY_DEVICE="${USB_PHY_DEVICE:-4340000.usb}" +USB_DWC2_BIND="${USB_DWC2_BIND:-/sys/bus/platform/drivers/dwc2/bind}" +USB_DWC2_UNBIND="${USB_DWC2_UNBIND:-/sys/bus/platform/drivers/dwc2/unbind}" +PATH="/sbin:/usr/sbin:/bin:/usr/bin${PATH:+:${PATH}}" +export PATH + +USB_DEFAULT_VENDOR_ID='0x3346' +USB_DEFAULT_PRODUCT_ID='0x1009' +USB_SERIAL_NUMBER='0123456789ABCDEF' +USB_MANUFACTURER='sipeed' +USB_PRODUCT='NanoKVM' +USB_CONFIGURATION='NanoKVM' + +USB_NORMAL_BCD_USB='0x0200' +USB_NORMAL_BCD_DEVICE='0x0510' +USB_NORMAL_CONFIG_ATTRIBUTES='0xE0' +USB_NORMAL_MAX_POWER='120' +USB_HID_ONLY_BCD_USB='0x0110' +USB_HID_ONLY_BCD_DEVICE='0x0623' +USB_HID_ONLY_CONFIG_ATTRIBUTES='0xA0' +USB_HID_ONLY_MAX_POWER='200' + +USB_HID_KEYBOARD_FUNC='hid.GS0' +USB_HID_RELATIVE_MOUSE_FUNC='hid.GS1' +USB_HID_ABSOLUTE_MOUSE_FUNC='hid.GS2' +USB_HID_BOOT_SUBCLASS='1' +USB_HID_NON_BOOT_SUBCLASS='' +USB_HID_KEYBOARD_PROTOCOL='1' +USB_HID_MOUSE_PROTOCOL='2' +USB_HID_KEYBOARD_REPORT_LENGTH='8' +USB_HID_RELATIVE_MOUSE_REPORT_LENGTH='4' +USB_HID_ABSOLUTE_MOUSE_REPORT_LENGTH='6' + +USB_HID_REPORT_NORMAL_KEYBOARD='normal-keyboard' +USB_HID_REPORT_HID_ONLY_KEYBOARD='hid-only-keyboard' +USB_HID_REPORT_RELATIVE_MOUSE='relative-mouse' +USB_HID_REPORT_NORMAL_ABSOLUTE_MOUSE='normal-absolute-mouse' +USB_HID_REPORT_HID_ONLY_ABSOLUTE_MOUSE='hid-only-absolute-mouse' + +remove_existing_gadget(){ + gadget="${USB_GADGET_ROOT}/${USB_GADGET_NAME}" + [ -d "${gadget}" ] || return 0 + + if [ -n "${USB_TEST_MODE}" ] + then + rm -rf "${gadget}" + return 0 + fi + + echo '' > "${gadget}/UDC" 2>/dev/null || true + if [ -d "${gadget}/configs/c.1" ] + then + find "${gadget}/configs/c.1" -mindepth 1 -maxdepth 1 -type l -exec rm {} \; + rmdir "${gadget}/configs/c.1/strings/0x409" 2>/dev/null || true + rmdir "${gadget}/configs/c.1/strings" 2>/dev/null || true + rmdir "${gadget}/configs/c.1" 2>/dev/null || true + fi + + if [ -d "${gadget}/functions" ] + then + for func in "${gadget}/functions"/* + do + [ -d "${func}" ] || continue + rmdir "${func}/lun.0" 2>/dev/null || true + rmdir "${func}" 2>/dev/null || true + done + fi + + rmdir "${gadget}/strings/0x409" 2>/dev/null || true + rmdir "${gadget}/strings" 2>/dev/null || true + rmdir "${gadget}/configs" 2>/dev/null || true + rmdir "${gadget}/functions" 2>/dev/null || true + rmdir "${gadget}" 2>/dev/null || true +} + +prepare_fake_gadget(){ + [ -n "${USB_TEST_MODE}" ] || return 0 + mkdir -p strings configs functions + touch UDC +} + +prepare_fake_hid_function(){ + func="$1" + [ -n "${USB_TEST_MODE}" ] || return 0 + printf '%s' '0' > "functions/${func}/subclass" + for attr in protocol report_length report_desc wakeup_on_write + do + touch "functions/${func}/${attr}" + done +} + +prepare_fake_lun(){ + func="$1" + [ -n "${USB_TEST_MODE}" ] || return 0 + mkdir -p "functions/${func}/lun.0" + for attr in removable ro cdrom inquiry_string file + do + touch "functions/${func}/lun.0/${attr}" + done +} + +write_usb_strings(){ + mkdir strings/0x409 + printf '%s' "${USB_SERIAL_NUMBER}" > strings/0x409/serialnumber + printf '%s' "${USB_MANUFACTURER}" > strings/0x409/manufacturer + printf '%s' "${USB_PRODUCT}" > strings/0x409/product +} + +write_usb_config_string(){ + mkdir -p configs/c.1/strings/0x409 + printf '%s' "${USB_CONFIGURATION}" > configs/c.1/strings/0x409/configuration +} + +write_boot_or_default(){ + if [ -e "${USB_BOOT_DIR}/$2" ] + then + cat "${USB_BOOT_DIR}/$2" > "$1" + else + echo "$3" > "$1" + fi +} + +prepare_usb_gadget(){ + echo "usb mode: device" + cd "${USB_GADGET_ROOT}" || exit 1 + remove_existing_gadget + mkdir "${USB_GADGET_NAME}" + cd "${USB_GADGET_NAME}" || exit 1 + prepare_fake_gadget + + echo "$1" > bcdUSB + echo "$2" > bcdDevice + write_boot_or_default idVendor usb.vid "${USB_DEFAULT_VENDOR_ID}" + write_boot_or_default idProduct usb.pid "${USB_DEFAULT_PRODUCT_ID}" + write_usb_strings + + mkdir configs/c.1 + echo "$3" > configs/c.1/bmAttributes + echo "$4" > configs/c.1/MaxPower + write_usb_config_string +} + +set_hid_wakeup(){ + if [ ! -e "${USB_BOOT_DIR}/usb.notwakeup" ] && [ -e "functions/$1/wakeup_on_write" ] + then + echo 1 > "functions/$1/wakeup_on_write" + fi +} + +add_hid_function(){ + func="$1" + subclass="$2" + protocol="$3" + report_length="$4" + report_desc="$5" + + mkdir "functions/${func}" + prepare_fake_hid_function "${func}" + if [ -n "${subclass}" ] + then + echo "${subclass}" > "functions/${func}/subclass" + fi + set_hid_wakeup "${func}" + echo "${protocol}" > "functions/${func}/protocol" + echo "${report_length}" > "functions/${func}/report_length" + write_hid_report_desc "${report_desc}" > "functions/${func}/report_desc" + ln -s "functions/${func}" configs/c.1 +} + +write_hid_report_desc(){ + case "$1" in + "${USB_HID_REPORT_NORMAL_KEYBOARD}") + printf '%b' '\005\001\011\006\241\001\005\007\031\340\051\347\025\000\045\001\165\001\225\010\201\002\225\001\165\010\201\003\225\005\165\001\005\010\031\001\051\005\221\002\225\001\165\003\221\003\225\006\165\010\025\000\045\347\005\007\031\000\051\347\201\000\300' + ;; + "${USB_HID_REPORT_HID_ONLY_KEYBOARD}") + printf '%b' '\005\001\011\006\241\001\005\007\031\340\051\347\025\000\045\001\165\001\225\010\201\002\225\001\165\010\201\003\225\005\165\001\005\010\031\001\051\005\221\002\225\001\165\003\221\003\225\006\165\010\025\000\045\145\005\007\031\000\051\145\201\000\300' + ;; + "${USB_HID_REPORT_RELATIVE_MOUSE}") + printf '%b' '\005\001\011\002\241\001\011\001\241\000\005\011\031\001\051\003\025\000\045\001\225\003\165\001\201\002\225\001\165\005\201\003\005\001\011\060\011\061\011\070\025\201\045\177\165\010\225\003\201\006\300\300' + ;; + "${USB_HID_REPORT_NORMAL_ABSOLUTE_MOUSE}") + printf '%b' '\005\001\011\002\241\001\011\001\241\000\005\011\031\001\051\005\025\000\045\001\225\005\165\001\201\002\225\001\165\003\201\001\005\001\011\060\011\061\025\000\046\377\177\065\000\106\377\177\165\020\225\002\201\002\005\001\011\070\025\201\045\177\065\000\105\000\165\010\225\001\201\006\300\300' + ;; + "${USB_HID_REPORT_HID_ONLY_ABSOLUTE_MOUSE}") + printf '%b' '\005\001\011\002\241\001\011\001\241\000\005\011\031\001\051\003\025\000\045\001\225\003\165\001\201\002\225\001\165\005\201\001\005\001\011\060\011\061\025\000\046\377\177\065\000\106\377\177\165\020\225\002\201\002\005\001\011\070\025\201\045\177\065\000\105\000\165\010\225\001\201\006\300\300' + ;; + *) echo "unknown HID report descriptor: $1" >&2; return 1 ;; + esac +} + +add_hid_functions(){ + keyboard_report="$1" + absolute_mouse_report="$2" + add_hid_function "${USB_HID_KEYBOARD_FUNC}" "${USB_HID_BOOT_SUBCLASS}" "${USB_HID_KEYBOARD_PROTOCOL}" "${USB_HID_KEYBOARD_REPORT_LENGTH}" "${keyboard_report}" + add_hid_function "${USB_HID_RELATIVE_MOUSE_FUNC}" "${USB_HID_BOOT_SUBCLASS}" "${USB_HID_MOUSE_PROTOCOL}" "${USB_HID_RELATIVE_MOUSE_REPORT_LENGTH}" "${USB_HID_REPORT_RELATIVE_MOUSE}" + add_hid_function "${USB_HID_ABSOLUTE_MOUSE_FUNC}" "${USB_HID_NON_BOOT_SUBCLASS}" "${USB_HID_MOUSE_PROTOCOL}" "${USB_HID_ABSOLUTE_MOUSE_REPORT_LENGTH}" "${absolute_mouse_report}" +} + +first_udc(){ find "${USB_UDC_CLASS}" -mindepth 1 -maxdepth 1 -exec basename {} \; | sort | sed -n '1p'; } + +bind_first_udc(){ + udc=$(first_udc) + [ -n "${udc}" ] || return 1 + printf '%s' "${udc}" > UDC + echo device > "${USB_OTG_ROLE}" +} + +unbind_udc(){ + echo '' > "${USB_GADGET_ROOT}/${USB_GADGET_NAME}/UDC" +} + +start_usb_host(){ + unbind_udc + echo host > "${USB_OTG_ROLE}" +} + +restart_udc(){ + echo > "${USB_GADGET_ROOT}/${USB_GADGET_NAME}/UDC" + sleep 1 + udc=$(first_udc) + [ -n "${udc}" ] || return 1 + printf '%s' "${udc}" > "${USB_GADGET_ROOT}/${USB_GADGET_NAME}/UDC" +} + +restart_usb_dev(){ + restart_udc + echo "USB Restart OK!" +} + +restart_usb_phy(){ + command -v start_usb_dev >/dev/null || { + echo "start_usb_dev is not defined" >&2 + return 1 + } + echo "${USB_PHY_DEVICE}" > "${USB_DWC2_UNBIND}" + sleep 1 + echo "${USB_PHY_DEVICE}" > "${USB_DWC2_BIND}" + start_usb_host + start_usb_dev +} + +run_usb_action(){ + case "$1" in + start) start_usb_dev ;; + restart) restart_usb_dev ;; + stop) start_usb_host ;; + stop_start) start_usb_host; start_usb_dev ;; + restart_phy) restart_usb_phy ;; + esac +} diff --git a/kvmapp/system/init.d/S03usbdev b/kvmapp/system/init.d/S03usbdev index df5e24862..2367d4f22 100755 --- a/kvmapp/system/init.d/S03usbdev +++ b/kvmapp/system/init.d/S03usbdev @@ -1,159 +1,60 @@ #!/bin/sh # kvmhwd Rev2.1 -start_usb_dev(){ - . /etc/profile - echo "usb mode: device" - cd /sys/kernel/config/usb_gadget +# shellcheck source=/kvmapp/system/init.d/S03usb-common +. "${USB_COMMON:-/kvmapp/system/init.d/S03usb-common}" + +USB_NCM_FUNC='ncm.usb0' +USB_RNDIS_FUNC='rndis.usb0' +USB_MASS_STORAGE_FUNC='mass_storage.disk0' +USB_MASS_STORAGE_INQUIRY='NanoKVM USB Mass Storage0520' +USB_LEGACY_EMPTY_DISK_BACKING='/dev/mmcblk0p3' - mkdir g0 - cd g0 +start_usb_dev(){ + prepare_usb_gadget "${USB_NORMAL_BCD_USB}" "${USB_NORMAL_BCD_DEVICE}" "${USB_NORMAL_CONFIG_ATTRIBUTES}" "${USB_NORMAL_MAX_POWER}" - # echo 0x3346 > idVendor - # echo 0x1009 > idProduct - if [ -e /boot/usb.vid ] + if [ -e "${USB_BOOT_DIR}/usb.ncm" ] then - cat /boot/usb.vid > idVendor - else - echo 0x3346 > idVendor - fi - if [ -e /boot/usb.pid ] + mkdir "functions/${USB_NCM_FUNC}" + ln -s "functions/${USB_NCM_FUNC}" configs/c.1/ + elif [ -e "${USB_BOOT_DIR}/usb.rndis0" ] then - cat /boot/usb.pid > idProduct - else - echo 0x1009 > idProduct + mkdir "functions/${USB_RNDIS_FUNC}" + ln -s "functions/${USB_RNDIS_FUNC}" configs/c.1/ fi - mkdir strings/0x409 - echo '0123456789ABCDEF' > strings/0x409/serialnumber - echo 'sipeed' > strings/0x409/manufacturer - echo 'NanoKVM' > strings/0x409/product - - mkdir configs/c.1 - echo 0xE0 > configs/c.1/bmAttributes - echo 120 > configs/c.1/MaxPower - mkdir configs/c.1/strings/0x409 - echo "NanoKVM" > configs/c.1/strings/0x409/configuration - if [ -e /boot/usb.ncm ] + if [ ! -e "${USB_BOOT_DIR}/disable_hid" ] then - mkdir functions/ncm.usb0 - ln -s functions/ncm.usb0 configs/c.1/ - else - if [ -e /boot/usb.rndis0 ] - then - mkdir functions/rndis.usb0 - ln -s functions/rndis.usb0 configs/c.1/ - fi + add_hid_functions "${USB_HID_REPORT_NORMAL_KEYBOARD}" "${USB_HID_REPORT_NORMAL_ABSOLUTE_MOUSE}" fi - if [ ! -e /boot/disable_hid ] + if [ -e "${USB_BOOT_DIR}/usb.disk0" ] then - # keyboard - mkdir functions/hid.GS0 - if [ -e /boot/BIOS ] - then - echo 1 > functions/hid.GS0/subclass - fi - if [ ! -e /boot/usb.notwakeup ] - then - echo 1 > functions/hid.GS0/wakeup_on_write - fi - echo 1 > functions/hid.GS0/protocol - echo 8 > functions/hid.GS0/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\\xE7\\x05\\x07\\x19\\x00\\x29\\xE7\\x81\\x00\\xc0 > functions/hid.GS0/report_desc - ln -s functions/hid.GS0 configs/c.1 - - # mouse - mkdir functions/hid.GS1 - if [ -e /boot/BIOS ] + mkdir "functions/${USB_MASS_STORAGE_FUNC}" + prepare_fake_lun "${USB_MASS_STORAGE_FUNC}" + ln -s "functions/${USB_MASS_STORAGE_FUNC}" configs/c.1/ + echo 1 > "functions/${USB_MASS_STORAGE_FUNC}/lun.0/removable" + if [ -e "${USB_BOOT_DIR}/usb.disk0.ro" ] then - echo 1 > functions/hid.GS1/subclass + echo 1 > "functions/${USB_MASS_STORAGE_FUNC}/lun.0/ro" + echo 0 > "functions/${USB_MASS_STORAGE_FUNC}/lun.0/cdrom" fi - if [ ! -e /boot/usb.notwakeup ] + echo "${USB_MASS_STORAGE_INQUIRY}" > "functions/${USB_MASS_STORAGE_FUNC}/lun.0/inquiry_string" + disk=$(cat "${USB_BOOT_DIR}/usb.disk0") + if [ -z "${disk}" ] then - echo 1 > functions/hid.GS1/wakeup_on_write + # if [ ! -e /mnt/usbdisk.img ] + # then + # fallocate -l 8G /mnt/usbdisk.img + # mkfs.vfat /mnt/usbdisk.img + # fi + echo "${USB_LEGACY_EMPTY_DISK_BACKING}" > "functions/${USB_MASS_STORAGE_FUNC}/lun.0/file" + else + printf '%s' "${disk}" > "functions/${USB_MASS_STORAGE_FUNC}/lun.0/file" fi - echo 2 > functions/hid.GS1/protocol - echo 4 > functions/hid.GS1/report_length - echo -ne \\x5\\x1\\x9\\x2\\xa1\\x1\\x9\\x1\\xa1\\x0\\x5\\x9\\x19\\x1\\x29\\x3\\x15\\x0\\x25\\x1\\x95\\x3\\x75\\x1\\x81\\x2\\x95\\x1\\x75\\x5\\x81\\x3\\x5\\x1\\x9\\x30\\x9\\x31\\x9\\x38\\x15\\x81\\x25\\x7f\\x75\\x8\\x95\\x3\\x81\\x6\\xc0\\xc0 > functions/hid.GS1/report_desc - ln -s functions/hid.GS1 configs/c.1 - - # touchpad - mkdir functions/hid.GS2 - if [ -e /boot/BIOS ] - then - echo 1 > functions/hid.GS2/subclass - fi - if [ ! -e /boot/usb.notwakeup ] - then - echo 1 > functions/hid.GS2/wakeup_on_write - fi - echo 2 > functions/hid.GS2/protocol - echo 6 > functions/hid.GS2/report_length - echo -ne \\x05\\x01\\x09\\x02\\xa1\\x01\\x09\\x01\\xa1\\x00\\x05\\x09\\x19\\x01\\x29\\x05\\x15\\x00\\x25\\x01\\x95\\x05\\x75\\x01\\x81\\x02\\x95\\x01\\x75\\x03\\x81\\x01\\x05\\x01\\x09\\x30\\x09\\x31\\x15\\x00\\x26\\xff\\x7f\\x35\\x00\\x46\\xff\\x7f\\x75\\x10\\x95\\x02\\x81\\x02\\x05\\x01\\x09\\x38\\x15\\x81\\x25\\x7f\\x35\\x00\\x45\\x00\\x75\\x08\\x95\\x01\\x81\\x06\\xc0\\xc0 > functions/hid.GS2/report_desc - ln -s functions/hid.GS2 configs/c.1 - fi - - if [ -e /boot/usb.disk0 ] - then - mkdir functions/mass_storage.disk0 - ln -s functions/mass_storage.disk0 configs/c.1/ - echo 1 > functions/mass_storage.disk0/lun.0/removable - if [ -e /boot/usb.disk0.ro ] - then - echo 1 > functions/mass_storage.disk0/lun.0/ro - echo 0 > functions/mass_storage.disk0/lun.0/cdrom - fi - echo "NanoKVM USB Mass Storage0520" > functions/mass_storage.disk0/lun.0/inquiry_string - disk=$(cat /boot/usb.disk0) - if [ -z "${disk}" ] - then - # if [ ! -e /mnt/usbdisk.img ] - # then - # fallocate -l 8G /mnt/usbdisk.img - # mkfs.vfat /mnt/usbdisk.img - # fi - echo /dev/mmcblk0p3 > functions/mass_storage.disk0/lun.0/file - else - cat /boot/usb.disk0 > functions/mass_storage.disk0/lun.0/file - fi fi - ls /sys/class/udc/ | cat > UDC - echo device > /proc/cviusb/otg_role -} - -start_usb_host(){ - echo '' > /sys/kernel/config/usb_gadget/g0/UDC - echo host > /proc/cviusb/otg_role -} - -restart_usb_dev(){ - echo > /sys/kernel/config/usb_gadget/g0/UDC - sleep 1 - ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC - echo "USB Restart OK!" + bind_first_udc } -case "$1" in - start) - start_usb_dev - ;; - restart) - restart_usb_dev - ;; - stop) - start_usb_host - ;; - stop_start) - start_usb_host - start_usb_dev - ;; - restart_phy) - echo "4340000.usb" > /sys/bus/platform/drivers/dwc2/unbind - sleep 1 - echo "4340000.usb" > /sys/bus/platform/drivers/dwc2/bind - start_usb_host - start_usb_dev - ;; -esac +run_usb_action "$1" diff --git a/kvmapp/system/init.d/S03usbhid b/kvmapp/system/init.d/S03usbhid index b5f160e8a..8f1179b18 100755 --- a/kvmapp/system/init.d/S03usbhid +++ b/kvmapp/system/init.d/S03usbhid @@ -1,112 +1,13 @@ #!/bin/sh # kvmhwd hid only -start_usb_dev(){ - . /etc/profile - echo "usb mode: device" - cd /sys/kernel/config/usb_gadget - - mkdir g0 - cd g0 - - echo 0x0101 > bcdUSB - echo 0x0623 > bcdDevice - # echo 0x3346 > idVendor - # echo 0x1009 > idProduct - if [ -e /boot/usb.vid ] - then - cat /boot/usb.vid > idVendor - else - echo 0x3346 > idVendor - fi - if [ -e /boot/usb.pid ] - then - cat /boot/usb.pid > idProduct - else - echo 0x1009 > idProduct - fi - mkdir strings/0x409 - # echo '0' > strings/0x409/serialnumber - echo 'sipeed' > strings/0x409/manufacturer - echo 'NanoKVM' > strings/0x409/product - - mkdir configs/c.1 - echo 0xA0 > configs/c.1/bmAttributes - echo 200 > configs/c.1/MaxPower - mkdir configs/c.1/strings/0x409 - echo "NanoKVM" > configs/c.1/strings/0x409/configuration - - # keyboard - mkdir functions/hid.GS0 - echo 1 > functions/hid.GS0/subclass - if [ ! -e /boot/usb.notwakeup ] - then - echo 1 > functions/hid.GS0/wakeup_on_write - fi - echo 1 > functions/hid.GS0/protocol - echo 8 > functions/hid.GS0/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.GS0/report_desc - ln -s functions/hid.GS0 configs/c.1 - - # mouse - mkdir functions/hid.GS1 - echo 1 > functions/hid.GS1/subclass - if [ ! -e /boot/usb.notwakeup ] - then - echo 1 > functions/hid.GS1/wakeup_on_write - fi - echo 2 > functions/hid.GS1/protocol - echo 4 > functions/hid.GS1/report_length - echo -ne \\x5\\x1\\x9\\x2\\xa1\\x1\\x9\\x1\\xa1\\x0\\x5\\x9\\x19\\x1\\x29\\x3\\x15\\x0\\x25\\x1\\x95\\x3\\x75\\x1\\x81\\x2\\x95\\x1\\x75\\x5\\x81\\x3\\x5\\x1\\x9\\x30\\x9\\x31\\x9\\x38\\x15\\x81\\x25\\x7f\\x75\\x8\\x95\\x3\\x81\\x6\\xc0\\xc0 > functions/hid.GS1/report_desc - ln -s functions/hid.GS1 configs/c.1 +# shellcheck source=/kvmapp/system/init.d/S03usb-common +. "${USB_COMMON:-/kvmapp/system/init.d/S03usb-common}" - # touchpad - mkdir functions/hid.GS2 - echo 1 > functions/hid.GS2/subclass - if [ ! -e /boot/usb.notwakeup ] - then - echo 1 > functions/hid.GS2/wakeup_on_write - fi - echo 2 > functions/hid.GS2/protocol - echo 6 > functions/hid.GS2/report_length - echo -ne \\x05\\x01\\x09\\x02\\xa1\\x01\\x09\\x01\\xa1\\x00\\x05\\x09\\x19\\x01\\x29\\x03\\x15\\x00\\x25\\x01\\x95\\x03\\x75\\x01\\x81\\x02\\x95\\x01\\x75\\x05\\x81\\x01\\x05\\x01\\x09\\x30\\x09\\x31\\x15\\x00\\x26\\xff\\x7f\\x35\\x00\\x46\\xff\\x7f\\x75\\x10\\x95\\x02\\x81\\x02\\x05\\x01\\x09\\x38\\x15\\x81\\x25\\x7f\\x35\\x00\\x45\\x00\\x75\\x08\\x95\\x01\\x81\\x06\\xc0\\xc0 > functions/hid.GS2/report_desc - ln -s functions/hid.GS2 configs/c.1 - - ls /sys/class/udc/ | cat > UDC - echo device > /proc/cviusb/otg_role -} - -start_usb_host(){ - echo '' > /sys/kernel/config/usb_gadget/g0/UDC - echo host > /proc/cviusb/otg_role -} - -restart_usb_dev(){ - echo > /sys/kernel/config/usb_gadget/g0/UDC - sleep 1 - ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC - echo "USB Restart OK!" +start_usb_dev(){ + prepare_usb_gadget "${USB_HID_ONLY_BCD_USB}" "${USB_HID_ONLY_BCD_DEVICE}" "${USB_HID_ONLY_CONFIG_ATTRIBUTES}" "${USB_HID_ONLY_MAX_POWER}" + add_hid_functions "${USB_HID_REPORT_HID_ONLY_KEYBOARD}" "${USB_HID_REPORT_HID_ONLY_ABSOLUTE_MOUSE}" + bind_first_udc } -case "$1" in - start) - start_usb_dev - ;; - restart) - restart_usb_dev - ;; - stop) - start_usb_host - ;; - stop_start) - start_usb_host - start_usb_dev - ;; - restart_phy) - echo "4340000.usb" > /sys/bus/platform/drivers/dwc2/unbind - sleep 1 - echo "4340000.usb" > /sys/bus/platform/drivers/dwc2/bind - start_usb_host - start_usb_dev - ;; -esac +run_usb_action "$1" diff --git a/server/service/hid/hid.go b/server/service/hid/hid.go index 60185ba8c..511ce66ca 100644 --- a/server/service/hid/hid.go +++ b/server/service/hid/hid.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "strings" "sync" "syscall" "time" @@ -193,6 +194,24 @@ func (h *Hid) closeDeviceNoLock(device hidDevice) { } } +func (h *Hid) closeDeletedDeviceNoLock(device hidDevice) { + file := device.get() + if file == nil || !hidFileWasDeleted(file) { + return + } + + log.Debugf("reopen %s because the cached HID handle was deleted", device.path) + h.closeDeviceNoLock(device) +} + +func hidFileWasDeleted(file *os.File) bool { + target, err := os.Readlink(fmt.Sprintf("/proc/self/fd/%d", file.Fd())) + if err != nil { + return false + } + return strings.HasSuffix(target, " (deleted)") +} + // writeWithTimeout bounds how long callers hold HID locks when writing to a // nonblocking descriptor. EAGAIN means the host is not accepting HID reports // yet, so retry until the caller's deadline expires. @@ -230,6 +249,15 @@ func isRetryableWriteError(err error) bool { return errors.Is(err, syscall.EAGAIN) || errors.Is(err, syscall.EWOULDBLOCK) } +func isStaleHIDWriteError(err error) bool { + return errors.Is(err, os.ErrClosed) || + errors.Is(err, syscall.EIO) || + errors.Is(err, syscall.ENODEV) || + errors.Is(err, syscall.ENXIO) || + errors.Is(err, syscall.EPIPE) || + errors.Is(err, syscall.ESHUTDOWN) +} + func (h *Hid) Open() { h.kbMutex.Lock() defer h.kbMutex.Unlock() @@ -272,10 +300,31 @@ func (h *Hid) writeHID(device hidDevice, data []byte) error { device.mu.Lock() defer device.mu.Unlock() + h.closeDeletedDeviceNoLock(device) if err := h.openDeviceNoLock(device); err != nil { return err } + if err := h.writeHIDNoLock(device, data); err != nil { + h.closeDeviceNoLock(device) + if !isStaleHIDWriteError(err) { + return formatHIDWriteError(err) + } + + if reopenErr := h.openDeviceNoLock(device); reopenErr != nil { + return fmt.Errorf("reopen %s after stale HID write: %w", device.path, reopenErr) + } + if retryErr := h.writeHIDNoLock(device, data); retryErr != nil { + h.closeDeviceNoLock(device) + return formatHIDWriteError(retryErr) + } + } + + log.Debugf("write to %s: %v", device.path, data) + return nil +} + +func (h *Hid) writeHIDNoLock(device hidDevice, data []byte) error { file := device.get() if file == nil { return fmt.Errorf("%s: hid handle is nil", device.path) @@ -287,17 +336,19 @@ func (h *Hid) writeHID(device hidDevice, data []byte) error { } if err := writeWithTimeout(file, data, hidWriteTimeout); err != nil { - h.closeDeviceNoLock(device) - switch { - case errors.Is(err, os.ErrClosed): - return fmt.Errorf("hid already closed: %w", err) - case errors.Is(err, os.ErrDeadlineExceeded): - return fmt.Errorf("timeout after %s: %w", hidWriteTimeout, err) - default: - return err - } + return err } - log.Debugf("write to %s: %v", device.path, data) return nil } + +func formatHIDWriteError(err error) error { + switch { + case errors.Is(err, os.ErrClosed): + return fmt.Errorf("hid already closed: %w", err) + case errors.Is(err, os.ErrDeadlineExceeded): + return fmt.Errorf("timeout after %s: %w", hidWriteTimeout, err) + default: + return err + } +} diff --git a/server/service/hid/hid_test.go b/server/service/hid/hid_test.go new file mode 100644 index 000000000..43f396034 --- /dev/null +++ b/server/service/hid/hid_test.go @@ -0,0 +1,56 @@ +package hid + +import ( + "errors" + "os" + "runtime" + "syscall" + "testing" +) + +func TestHidFileWasDeleted(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skip("deleted fd detection uses /proc/self/fd") + } + + file, err := os.CreateTemp(t.TempDir(), "hidg") + if err != nil { + t.Fatal(err) + } + if hidFileWasDeleted(file) { + t.Fatal("new file reported as deleted") + } + + if err := os.Remove(file.Name()); err != nil { + t.Fatal(err) + } + if !hidFileWasDeleted(file) { + t.Fatal("unlinked open file did not report as deleted") + } +} + +func TestIsStaleHIDWriteError(t *testing.T) { + tests := []struct { + name string + err error + want bool + }{ + {name: "closed", err: os.ErrClosed, want: true}, + {name: "io", err: syscall.EIO, want: true}, + {name: "no device", err: syscall.ENODEV, want: true}, + {name: "no such device address", err: syscall.ENXIO, want: true}, + {name: "pipe", err: syscall.EPIPE, want: true}, + {name: "shutdown", err: syscall.ESHUTDOWN, want: true}, + {name: "wrapped", err: errors.Join(syscall.EIO), want: true}, + {name: "again", err: syscall.EAGAIN, want: false}, + {name: "deadline", err: os.ErrDeadlineExceeded, want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isStaleHIDWriteError(tt.err); got != tt.want { + t.Fatalf("isStaleHIDWriteError(%v) = %v, want %v", tt.err, got, tt.want) + } + }) + } +} diff --git a/server/service/hid/status.go b/server/service/hid/status.go index 43cfce1bf..a4afedbd8 100644 --- a/server/service/hid/status.go +++ b/server/service/hid/status.go @@ -1,7 +1,7 @@ package hid import ( - "NanoKVM-Server/proto" + "context" "errors" "fmt" "io" @@ -12,12 +12,17 @@ import ( "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" + + "NanoKVM-Server/proto" ) const ( ModeNormal = "normal" ModeHidOnly = "hid-only" - ModeFlag = "/sys/kernel/config/usb_gadget/g0/bcdDevice" +) + +var ( + ModeFlag = "/sys/kernel/config/usb_gadget/g0/bcdDevice" ModeNormalScript = "/kvmapp/system/init.d/S03usbdev" ModeHidOnlyScript = "/kvmapp/system/init.d/S03usbhid" @@ -120,8 +125,10 @@ func ResetUSBPHY() error { h.CloseNoLock() defer h.Unlock() - command := fmt.Sprintf("%s restart_phy", USBDevScript) - if err := exec.Command("sh", "-c", command).Run(); err != nil { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + if err := exec.CommandContext(ctx, "sh", USBDevScript, "restart_phy").Run(); err != nil { return fmt.Errorf("restart usb phy: %w", err) } diff --git a/server/service/hid/status_test.go b/server/service/hid/status_test.go new file mode 100644 index 000000000..362c60420 --- /dev/null +++ b/server/service/hid/status_test.go @@ -0,0 +1,66 @@ +package hid + +import ( + "os" + "path/filepath" + "testing" +) + +func TestGetHidMode(t *testing.T) { + tests := []struct { + name string + flag string + want string + }{ + {name: "normal", flag: "0x0510\n", want: ModeNormal}, + {name: "hid only", flag: "0x0623\n", want: ModeHidOnly}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + withModeFlag(t, tt.flag) + + got, err := getHidMode() + if err != nil { + t.Fatal(err) + } + if got != tt.want { + t.Fatalf("getHidMode() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestGetHidModeRejectsUnknownBcdDevice(t *testing.T) { + withModeFlag(t, "0x9999\n") + + if _, err := getHidMode(); err == nil { + t.Fatal("getHidMode succeeded for an unknown bcdDevice") + } +} + +func TestGetHidModeRequiresModeFlag(t *testing.T) { + oldModeFlag := ModeFlag + t.Cleanup(func() { + ModeFlag = oldModeFlag + }) + ModeFlag = filepath.Join(t.TempDir(), "missing-bcdDevice") + + if _, err := getHidMode(); err == nil { + t.Fatal("getHidMode succeeded with a missing bcdDevice") + } +} + +func withModeFlag(t *testing.T, content string) { + t.Helper() + + oldModeFlag := ModeFlag + t.Cleanup(func() { + ModeFlag = oldModeFlag + }) + + ModeFlag = filepath.Join(t.TempDir(), "bcdDevice") + if err := os.WriteFile(ModeFlag, []byte(content), 0o666); err != nil { + t.Fatal(err) + } +} diff --git a/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp b/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp index d98d57671..8bcf46b3e 100644 --- a/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp +++ b/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp @@ -10,6 +10,15 @@ using namespace maix::sys; extern kvm_sys_state_t kvm_sys_state; extern kvm_oled_state_t kvm_oled_state; +static const char *USB_HID0_LINK = "/sys/kernel/config/usb_gadget/g0/configs/c.1/hid.GS0"; +static const char *USB_HID1_LINK = "/sys/kernel/config/usb_gadget/g0/configs/c.1/hid.GS1"; +static const char *USB_HID2_LINK = "/sys/kernel/config/usb_gadget/g0/configs/c.1/hid.GS2"; + +static int path_exists(const char *path) +{ + return access(path, F_OK) == 0; +} + int get_nic_state(const char* interface_name) { int sock; @@ -224,8 +233,10 @@ void kvm_update_usb_state() else kvm_sys_state.usb_state = -1; // hid_state & udisk_state (rndis_state单独处理) if(kvm_sys_state.usb_state == 1){ - if(access("/sys/kernel/config/usb_gadget/g0/configs/c.1/hid.GS*", F_OK) == 0) - kvm_sys_state.hid_state = 1; + kvm_sys_state.hid_state = + path_exists(USB_HID0_LINK) || + path_exists(USB_HID1_LINK) || + path_exists(USB_HID2_LINK); if(access("/sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk0", F_OK) == 0) kvm_sys_state.udisk_state = 1; } else { @@ -519,4 +530,4 @@ int check_watchdog() return -1; } } else return 0; -} \ No newline at end of file +} diff --git a/tests/usb-init-scripts-test.sh b/tests/usb-init-scripts-test.sh new file mode 100644 index 000000000..1fc840aa8 --- /dev/null +++ b/tests/usb-init-scripts-test.sh @@ -0,0 +1,306 @@ +#!/bin/sh +set -eu + +ROOT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +COMMON_SCRIPT="${ROOT_DIR}/kvmapp/system/init.d/S03usb-common" +NORMAL_SCRIPT="${ROOT_DIR}/kvmapp/system/init.d/S03usbdev" +HID_ONLY_SCRIPT="${ROOT_DIR}/kvmapp/system/init.d/S03usbhid" +TMP_DIRS="" + +cleanup(){ + for dir in ${TMP_DIRS} + do + rm -rf "${dir}" + done +} +trap cleanup EXIT + +fail(){ + echo "FAIL: $*" >&2 + exit 1 +} + +assert_eq(){ + [ "$1" = "$2" ] || fail "$3: got '$1', want '$2'" +} + +assert_no_file(){ + [ ! -e "$1" ] && [ ! -L "$1" ] || fail "unexpected file: $1" +} + +assert_link(){ + path="$1" + want="$2" + [ -e "${path}" ] || [ -L "${path}" ] || fail "missing file: ${path}" + got=$(readlink "${path}") + assert_eq "${got}" "${want}" "symlink ${path}" +} + +assert_hex(){ + assert_eq "$(od -An -tx1 -v "$1" | tr -d ' \n')" "$2" "hex $1" +} + +hex_of(){ + printf '%s' "$1" | od -An -tx1 -v | tr -d ' \n' +} + +assert_text_bytes(){ + assert_hex "$1" "$(hex_of "$2")" +} + +assert_not_contains(){ + path="$1" + pattern="$2" + name="$3" + if grep -F "${pattern}" "${path}" >/dev/null + then + fail "${name}: unexpected '${pattern}' in ${path}" + fi +} + +USB_DEFAULT_UDC="4340000.usb" +USB_PHY_DEVICE="4340000.usb" +USB_SECONDARY_UDC="4350000.usb" +USB_SERIAL_NUMBER="0123456789ABCDEF" +USB_MANUFACTURER="sipeed" +USB_PRODUCT="NanoKVM" +USB_CONFIGURATION="NanoKVM" +USB_NORMAL_BCD_USB="0x0200" +USB_NORMAL_BCD_DEVICE="0x0510" +USB_HID_ONLY_BCD_USB="0x0110" +USB_HID_ONLY_BCD_DEVICE="0x0623" +USB_HID_ONLY_CONFIG_ATTRIBUTES="0xA0" +USB_HID_KEYBOARD_FUNC="hid.GS0" +USB_HID_RELATIVE_MOUSE_FUNC="hid.GS1" +USB_HID_ABSOLUTE_MOUSE_FUNC="hid.GS2" +USB_MASS_STORAGE_FUNC="mass_storage.disk0" +USB_RNDIS_FUNC="rndis.usb0" +USB_LEGACY_EMPTY_DISK_BACKING="/dev/mmcblk0p3" +USB_TEST_IMAGE="/data/install.iso" + +KEYBOARD_REPORT_DESC="05010906a101050719e029e71500250175019508810295017508810395057501050819012905910295017503910395067508150025e70507190029e78100c0" +HID_ONLY_KEYBOARD_REPORT_DESC="05010906a101050719e029e71500250175019508810295017508810395057501050819012905910295017503910395067508150025650507190029658100c0" +RELATIVE_MOUSE_REPORT_DESC="05010902a1010901a1000509190129031500250195037501810295017505810305010930093109381581257f750895038106c0c0" +ABSOLUTE_MOUSE_REPORT_DESC="05010902a1010901a10005091901290515002501950575018102950175038101050109300931150026ff7f350046ff7f751095028102050109381581257f35004500750895018106c0c0" +HID_ONLY_ABSOLUTE_MOUSE_REPORT_DESC="05010902a1010901a10005091901290315002501950375018102950175058101050109300931150026ff7f350046ff7f751095028102050109381581257f35004500750895018106c0c0" + +assert_hid_attr(){ + assert_eq "$(cat "$1/functions/$2/$3")" "$4" "$5" +} + +assert_hid_boot_interfaces(){ + g="$1" + label="$2" + assert_hid_attr "${g}" "${USB_HID_KEYBOARD_FUNC}" subclass 1 "${label} keyboard boot subclass" + assert_hid_attr "${g}" "${USB_HID_KEYBOARD_FUNC}" protocol 1 "${label} keyboard protocol" + assert_hid_attr "${g}" "${USB_HID_RELATIVE_MOUSE_FUNC}" subclass 1 "${label} mouse boot subclass" + assert_hid_attr "${g}" "${USB_HID_RELATIVE_MOUSE_FUNC}" protocol 2 "${label} mouse protocol" + assert_hid_attr "${g}" "${USB_HID_ABSOLUTE_MOUSE_FUNC}" subclass 0 "${label} touch non-boot subclass" + assert_hid_attr "${g}" "${USB_HID_ABSOLUTE_MOUSE_FUNC}" protocol 2 "${label} touch protocol" +} + +assert_hid_function(){ + assert_link "$1/configs/c.1/$2" "functions/$2" + assert_hid_attr "$1" "$2" subclass "$4" "$3 subclass" + assert_hid_attr "$1" "$2" protocol "$5" "$3 protocol" + assert_hid_attr "$1" "$2" report_length "$6" "$3 report length" + assert_hex "$1/functions/$2/report_desc" "$7" + assert_hid_attr "$1" "$2" wakeup_on_write "$8" "$3 wake" +} + +assert_hid_functions(){ + assert_hid_function "$1" "${USB_HID_KEYBOARD_FUNC}" "$2 keyboard" 1 1 8 "$3" "$5" + assert_hid_function "$1" "${USB_HID_RELATIVE_MOUSE_FUNC}" "$2 mouse" 1 2 4 "${RELATIVE_MOUSE_REPORT_DESC}" "$5" + assert_hid_function "$1" "${USB_HID_ABSOLUTE_MOUSE_FUNC}" "$2 touch" 0 2 6 "$4" "$5" +} + +assert_no_hid_functions(){ + g="$1" + for func in "${USB_HID_KEYBOARD_FUNC}" "${USB_HID_RELATIVE_MOUSE_FUNC}" "${USB_HID_ABSOLUTE_MOUSE_FUNC}" + do + assert_no_file "${g}/configs/c.1/${func}" + assert_no_file "${g}/functions/${func}" + done +} + +new_env(){ + base=$(mktemp -d) + TMP_DIRS="${TMP_DIRS} ${base}" + mkdir -p "${base}/boot" "${base}/dwc2" "${base}/gadget" "${base}/udc" "${base}/proc/cviusb" + touch "${base}/dwc2/bind" "${base}/dwc2/unbind" + touch "${base}/udc/${USB_DEFAULT_UDC}" + touch "${base}/proc/cviusb/otg_role" + echo "${base}" +} + +run_script_action(){ + script="$1" + action="$2" + base="$3" + USB_TEST_MODE=1 \ + USB_COMMON="${COMMON_SCRIPT}" \ + USB_BOOT_DIR="${base}/boot" \ + USB_GADGET_ROOT="${base}/gadget" \ + USB_UDC_CLASS="${base}/udc" \ + USB_OTG_ROLE="${base}/proc/cviusb/otg_role" \ + USB_PHY_DEVICE="${USB_PHY_DEVICE}" \ + USB_DWC2_BIND="${base}/dwc2/bind" \ + USB_DWC2_UNBIND="${base}/dwc2/unbind" \ + sh "${script}" "${action}" >/dev/null +} + +run_start(){ + script="$1" + base="$2" + run_script_action "${script}" start "${base}" +} + +test_usb_scripts_do_not_source_profile(){ + assert_not_contains "${NORMAL_SCRIPT}" "/etc/profile" "normal init profile" + assert_not_contains "${HID_ONLY_SCRIPT}" "/etc/profile" "hid-only init profile" +} + +test_normal_hid_descriptors(){ + base=$(new_env) + run_start "${NORMAL_SCRIPT}" "${base}" + g="${base}/gadget/g0" + + assert_eq "$(cat "${g}/bcdUSB")" "${USB_NORMAL_BCD_USB}" "normal bcdUSB" + assert_eq "$(cat "${g}/bcdDevice")" "${USB_NORMAL_BCD_DEVICE}" "normal bcdDevice" + assert_text_bytes "${g}/strings/0x409/manufacturer" "${USB_MANUFACTURER}" + assert_text_bytes "${g}/strings/0x409/product" "${USB_PRODUCT}" + assert_text_bytes "${g}/strings/0x409/serialnumber" "${USB_SERIAL_NUMBER}" + assert_text_bytes "${g}/configs/c.1/strings/0x409/configuration" "${USB_CONFIGURATION}" + + assert_hid_functions "${g}" normal "${KEYBOARD_REPORT_DESC}" "${ABSOLUTE_MOUSE_REPORT_DESC}" 1 + assert_eq "$(cat "${g}/UDC")" "${USB_DEFAULT_UDC}" "normal UDC" + assert_eq "$(cat "${base}/proc/cviusb/otg_role")" "device" "normal OTG role" +} + +test_normal_bios_flag_keeps_only_boot_hid_interfaces(){ + base=$(new_env) + touch "${base}/boot/BIOS" + run_start "${NORMAL_SCRIPT}" "${base}" + g="${base}/gadget/g0" + + assert_hid_boot_interfaces "${g}" BIOS +} + +test_normal_disable_hid_removes_hid_functions(){ + base=$(new_env) + touch "${base}/boot/disable_hid" + run_start "${NORMAL_SCRIPT}" "${base}" + g="${base}/gadget/g0" + + assert_no_hid_functions "${g}" +} + +test_normal_legacy_mass_storage(){ + base=$(new_env) + : > "${base}/boot/usb.disk0" + run_start "${NORMAL_SCRIPT}" "${base}" + g="${base}/gadget/g0" + + assert_link "${g}/configs/c.1/${USB_MASS_STORAGE_FUNC}" "functions/${USB_MASS_STORAGE_FUNC}" + assert_eq "$(cat "${g}/functions/${USB_MASS_STORAGE_FUNC}/lun.0/removable")" "1" "mass storage removable" + assert_eq "$(cat "${g}/functions/${USB_MASS_STORAGE_FUNC}/lun.0/file")" "${USB_LEGACY_EMPTY_DISK_BACKING}" "legacy empty disk backing" +} + +test_normal_mounted_image_and_network(){ + base=$(new_env) + printf '%s' "${USB_TEST_IMAGE}" > "${base}/boot/usb.disk0" + touch "${base}/boot/usb.disk0.ro" + touch "${base}/boot/usb.rndis0" + run_start "${NORMAL_SCRIPT}" "${base}" + g="${base}/gadget/g0" + + assert_link "${g}/configs/c.1/${USB_RNDIS_FUNC}" "functions/${USB_RNDIS_FUNC}" + assert_eq "$(cat "${g}/functions/${USB_MASS_STORAGE_FUNC}/lun.0/file")" "${USB_TEST_IMAGE}" "mounted image" + assert_eq "$(cat "${g}/functions/${USB_MASS_STORAGE_FUNC}/lun.0/ro")" "1" "media ro flag" + assert_eq "$(cat "${g}/functions/${USB_MASS_STORAGE_FUNC}/lun.0/cdrom")" "0" "media cdrom flag" +} + +test_hid_only_descriptors_and_no_wake(){ + base=$(new_env) + touch "${base}/boot/usb.notwakeup" + touch "${base}/boot/usb.disk0" + touch "${base}/boot/usb.rndis0" + run_start "${HID_ONLY_SCRIPT}" "${base}" + g="${base}/gadget/g0" + + assert_eq "$(cat "${g}/bcdUSB")" "${USB_HID_ONLY_BCD_USB}" "hid-only bcdUSB" + assert_eq "$(cat "${g}/bcdDevice")" "${USB_HID_ONLY_BCD_DEVICE}" "hid-only bcdDevice" + assert_eq "$(cat "${g}/configs/c.1/bmAttributes")" "${USB_HID_ONLY_CONFIG_ATTRIBUTES}" "hid-only bmAttributes" + assert_text_bytes "${g}/strings/0x409/serialnumber" "${USB_SERIAL_NUMBER}" + assert_text_bytes "${g}/strings/0x409/manufacturer" "${USB_MANUFACTURER}" + assert_text_bytes "${g}/strings/0x409/product" "${USB_PRODUCT}" + assert_text_bytes "${g}/configs/c.1/strings/0x409/configuration" "${USB_CONFIGURATION}" + assert_hid_functions "${g}" hid-only "${HID_ONLY_KEYBOARD_REPORT_DESC}" "${HID_ONLY_ABSOLUTE_MOUSE_REPORT_DESC}" "" + assert_no_file "${g}/configs/c.1/${USB_MASS_STORAGE_FUNC}" + assert_no_file "${g}/configs/c.1/${USB_RNDIS_FUNC}" +} + +test_uses_one_udc(){ + base=$(new_env) + touch "${base}/udc/${USB_SECONDARY_UDC}" + run_start "${NORMAL_SCRIPT}" "${base}" + + assert_eq "$(cat "${base}/gadget/g0/UDC")" "${USB_DEFAULT_UDC}" "first UDC selected" +} + +test_stop_unbinds_and_sets_host_role(){ + base=$(new_env) + run_start "${NORMAL_SCRIPT}" "${base}" + run_script_action "${NORMAL_SCRIPT}" stop "${base}" + + assert_eq "$(cat "${base}/gadget/g0/UDC")" "" "stopped UDC" + assert_eq "$(cat "${base}/proc/cviusb/otg_role")" "host" "stopped OTG role" +} + +test_stop_then_start_rebuilds_gadget(){ + base=$(new_env) + run_start "${NORMAL_SCRIPT}" "${base}" + run_script_action "${NORMAL_SCRIPT}" stop "${base}" + run_script_action "${NORMAL_SCRIPT}" start "${base}" + g="${base}/gadget/g0" + + assert_eq "$(cat "${g}/UDC")" "${USB_DEFAULT_UDC}" "stop-start UDC" + assert_eq "$(cat "${base}/proc/cviusb/otg_role")" "device" "stop-start OTG role" + assert_hid_functions "${g}" stop-start "${KEYBOARD_REPORT_DESC}" "${ABSOLUTE_MOUSE_REPORT_DESC}" 1 +} + +test_restart_phy_rebuilds_gadget(){ + base=$(new_env) + run_start "${NORMAL_SCRIPT}" "${base}" + run_script_action "${NORMAL_SCRIPT}" restart_phy "${base}" + g="${base}/gadget/g0" + + assert_eq "$(cat "${base}/dwc2/unbind")" "${USB_PHY_DEVICE}" "phy unbind" + assert_eq "$(cat "${base}/dwc2/bind")" "${USB_PHY_DEVICE}" "phy bind" + assert_eq "$(cat "${g}/UDC")" "${USB_DEFAULT_UDC}" "restart-phy UDC" + assert_eq "$(cat "${base}/proc/cviusb/otg_role")" "device" "restart-phy OTG role" + assert_hid_functions "${g}" restart-phy "${KEYBOARD_REPORT_DESC}" "${ABSOLUTE_MOUSE_REPORT_DESC}" 1 +} + +test_restart_rebinds_udc(){ + base=$(new_env) + run_start "${NORMAL_SCRIPT}" "${base}" + run_script_action "${NORMAL_SCRIPT}" restart "${base}" + + assert_eq "$(cat "${base}/gadget/g0/UDC")" "${USB_DEFAULT_UDC}" "restarted UDC" +} + +test_usb_scripts_do_not_source_profile +test_normal_hid_descriptors +test_normal_bios_flag_keeps_only_boot_hid_interfaces +test_normal_disable_hid_removes_hid_functions +test_normal_legacy_mass_storage +test_normal_mounted_image_and_network +test_hid_only_descriptors_and_no_wake +test_uses_one_udc +test_stop_unbinds_and_sets_host_role +test_stop_then_start_rebuilds_gadget +test_restart_phy_rebuilds_gadget +test_restart_rebinds_udc + +echo "usb init script tests passed"