diff --git a/devices/gk7205v200_otg_generic/br-ext-chip-goke/configs/gk7205v200_otg_generic_defconfig b/devices/gk7205v200_otg_generic/br-ext-chip-goke/configs/gk7205v200_otg_generic_defconfig index 4ef6426..3fa89fb 100644 --- a/devices/gk7205v200_otg_generic/br-ext-chip-goke/configs/gk7205v200_otg_generic_defconfig +++ b/devices/gk7205v200_otg_generic/br-ext-chip-goke/configs/gk7205v200_otg_generic_defconfig @@ -54,5 +54,6 @@ BR2_PACKAGE_MAJESTIC_FONTS=y BR2_PACKAGE_MAJESTIC_WEBUI=y BR2_PACKAGE_MAJESTIC=y BR2_PACKAGE_MBEDTLS_OPENIPC=y +BR2_PACKAGE_USB_DUAL_ROLE=y BR2_PACKAGE_VTUND_OPENIPC=y BR2_PACKAGE_YAML_CLI=y diff --git a/devices/gk7205v200_otg_generic/general/overlay/etc/init.d/S72usbmode b/devices/gk7205v200_otg_generic/general/overlay/etc/init.d/S72usbmode deleted file mode 100755 index d7d2923..0000000 --- a/devices/gk7205v200_otg_generic/general/overlay/etc/init.d/S72usbmode +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh -# -# Force a concrete USB role at boot. -# -# The device tree sets dr_mode = "otg" so that dwc3 registers both an xHCI host -# and a UDC, which is what makes runtime role switching possible. The role -# itself is settled in the kernel: all-patches 0018 programs -# GCTL.PRTCAPDIR = HOST before the xHCI host is registered, because a host that -# probes while the core is still in OTG(3) comes up unable to drive the port -- -# it runs, powers the port, and never reports a connect. So the board boots as -# a host, and this script exists to apply a persisted "device" choice on top. -# -# Runs after S70vendor so the vendor modules are up first. - -case "$1" in - start) - mode=host - [ -r /etc/usbmode ] && read -r mode < /etc/usbmode - case "$mode" in - host|device) ;; - *) mode=host ;; - esac - echo "Setting USB role to $mode..." - /usr/bin/usb-mode "$mode" || echo "usb-mode $mode failed" - ;; - - stop) - ;; - - *) - echo "Usage: $0 {start|stop}" - exit 1 - ;; -esac diff --git a/devices/gk7205v200_otg_generic/general/overlay/usr/bin/usb-mode b/devices/gk7205v200_otg_generic/general/overlay/usr/bin/usb-mode deleted file mode 100755 index a1933cf..0000000 --- a/devices/gk7205v200_otg_generic/general/overlay/usr/bin/usb-mode +++ /dev/null @@ -1,191 +0,0 @@ -#!/bin/sh -# -# Switch the single DWC3 controller between USB host and USB device roles. -# -# host - xHCI drives the port. This is what the usbcam second-camera path -# needs (majestic consuming a USB webcam). -# device - the UVC gadget drives the port, so a PC sees this camera as a -# webcam. -# -# The two are mutually exclusive: xHCI HCSPARAMS1 reports MaxPorts = 1, so -# there is one port and one role at a time. Switching does not need a reboot. -# -# Why this is more than one echo: dwc3_set_mode() (drivers/usb/dwc3/core.c) -# only flips GCTL.PRTCAPDIR. It does not stop the host stack or start the -# gadget stack. Writing the debugfs `mode` file on its own leaves xHCI still -# bound and its DMA engine still live, pointed at a controller that has been -# re-aimed underneath it. Unbind first, then flip, then bring the other side -# up. - -set -e - -STATE=/etc/usbmode -GADGET=/sys/kernel/config/usb_gadget/uvc0 - -die() { echo "usb-mode: $*" >&2; exit 1; } - -dwc3_mode_file() { - for f in /sys/kernel/debug/*.dwc3/mode; do - [ -e "$f" ] && { echo "$f"; return 0; } - done - return 1 -} - -xhci_device() { - for d in /sys/bus/platform/drivers/xhci-hcd/*.auto; do - [ -e "$d" ] && { basename "$d"; return 0; } - done - # Not currently bound; fall back to the device that exists on the bus. - for d in /sys/bus/platform/devices/xhci-hcd.*.auto; do - [ -e "$d" ] && { basename "$d"; return 0; } - done - return 1 -} - -udc_name() { - for u in /sys/class/udc/*; do - [ -e "$u" ] && { basename "$u"; return 0; } - done - return 1 -} - -need_debugfs() { - mountpoint -q /sys/kernel/debug 2>/dev/null || - mount -t debugfs none /sys/kernel/debug - MODE_FILE=$(dwc3_mode_file) || - die "no dwc3 mode file; is CONFIG_USB_DWC3_DUAL_ROLE enabled and debugfs mounted?" -} - -teardown_gadget() { - [ -d "$GADGET" ] || return 0 - - # Unbind from the UDC first: this drops the D+ pullup so the host sees a - # disconnect rather than a device that stops answering. - if [ -e "$GADGET/UDC" ] && [ -s "$GADGET/UDC" ]; then - echo "" > "$GADGET/UDC" 2>/dev/null || true - fi - - # configfs rmdir is strict about ordering: links before the things they - # point at, leaves before their parents. ENOTEMPTY here means something - # below was missed. - rm -f "$GADGET/configs/c.1/uvc.0" - - F="$GADGET/functions/uvc.0" - rm -f "$F/control/class/fs/h" "$F/control/class/ss/h" - rm -f "$F/streaming/class/fs/h" "$F/streaming/class/hs/h" "$F/streaming/class/ss/h" - # uvc-gadget-setup composes whichever single format majestic is set to, so - # tear down both possibilities: only one of them exists on any given boot. - rm -f "$F/streaming/header/h/f" - rmdir "$F/streaming/header/h" 2>/dev/null || true - rmdir "$F/streaming/mjpeg/m/f" 2>/dev/null || true - rmdir "$F/streaming/mjpeg/m" 2>/dev/null || true - rmdir "$F/streaming/framebased/h/f" 2>/dev/null || true - rmdir "$F/streaming/framebased/h" 2>/dev/null || true - rmdir "$F/control/header/h" 2>/dev/null || true - - # EBUSY here means a userspace process still holds the gadget's - # /dev/videoN open -- almost always majestic. Stop it first. - if ! rmdir "$F" 2>/dev/null; then - die "cannot remove uvc.0 (EBUSY?) -- is majestic still holding /dev/video*? stop it first" - fi - - rmdir "$GADGET/configs/c.1/strings/0x409" 2>/dev/null || true - rmdir "$GADGET/configs/c.1" 2>/dev/null || true - rmdir "$GADGET/strings/0x409" 2>/dev/null || true - rmdir "$GADGET" 2>/dev/null || true -} - -to_device() { - need_debugfs - - if X=$(xhci_device) && [ -e "/sys/bus/platform/drivers/xhci-hcd/$X" ]; then - # Unbind only from the host role. Asking xhci_halt() to halt a - # controller whose GCTL.PRTCAPDIR is not HOST means halting a host - # that was never really running, and it times out: - # xhci-hcd.0.auto: Host not halted after 16000 microseconds. - # xhci-hcd.0.auto: Host controller not halted, aborting reset. - # Leaving it un-halted is precisely the live-DMA-engine state this - # unbind exists to avoid. Since all-patches 0018 the core is already - # in the host role whenever xHCI is bound, so this flip is a no-op; it - # stays as the cheap guarantee that the unbind below halts cleanly. - [ "$(cat "$MODE_FILE")" = host ] || echo host > "$MODE_FILE" - - echo "usb-mode: unbinding host controller $X" - echo "$X" > /sys/bus/platform/drivers/xhci-hcd/unbind - fi - - echo device > "$MODE_FILE" - - modprobe configfs 2>/dev/null || true - modprobe libcomposite 2>/dev/null || true - modprobe usb_f_uvc 2>/dev/null || true - - mountpoint -q /sys/kernel/config 2>/dev/null || - mount -t configfs none /sys/kernel/config - - /usr/bin/uvc-gadget-setup - - UDC=$(udc_name) || - die "no UDC registered -- is dr_mode=\"otg\" in the device tree?" - echo "$UDC" > "$GADGET/UDC" - - echo device > "$STATE" - echo "usb-mode: device (UVC gadget bound to $UDC)" -} - -to_host() { - need_debugfs - - teardown_gadget - - # Remember which role we are leaving: it decides whether an already-bound - # host stack has to be re-probed. See below. - was=$(cat "$MODE_FILE") - echo host > "$MODE_FILE" - - if X=$(xhci_device); then - if [ ! -e "/sys/bus/platform/drivers/xhci-hcd/$X" ]; then - echo "usb-mode: rebinding host controller $X" - echo "$X" > /sys/bus/platform/drivers/xhci-hcd/bind - elif [ "$was" != host ]; then - # Bound, but bound while the core was in another role: it - # probed, reset the controller and brought up its root hubs - # against a port it was not driving, and it will never report a - # connect -- the port powers up and stays at CCS = 0, so lsusb - # shows nothing but the root hubs however many devices are - # plugged in. Writing MODE_FILE only flips GCTL.PRTCAPDIR; it - # does not re-initialise a live xHCI. Making it probe again is - # what actually brings the port up. - # - # The kernel now settles the role before registering the host - # (all-patches 0018), so this cannot trigger on a matching - # kernel. It is here for a rootfs running on an older one. - echo "usb-mode: re-probing host controller $X (was $was)" - echo "$X" > /sys/bus/platform/drivers/xhci-hcd/unbind - echo "$X" > /sys/bus/platform/drivers/xhci-hcd/bind - fi - fi - - echo host > "$STATE" - echo "usb-mode: host" -} - -status() { - need_debugfs - echo "dwc3 role : $(cat "$MODE_FILE")" - echo "persisted : $([ -r "$STATE" ] && cat "$STATE" || echo '(unset, defaults to host)')" - echo "udc : $(udc_name || echo '(none)')" - echo "xhci bound: $(xhci_device >/dev/null 2>&1 && \ - ([ -e "/sys/bus/platform/drivers/xhci-hcd/$(xhci_device)" ] && echo yes || echo no) || echo '(no device)')" - echo "gadget : $([ -d "$GADGET" ] && echo "composed, UDC='$(cat "$GADGET/UDC" 2>/dev/null)'" || echo 'not composed')" -} - -case "$1" in - device) to_device ;; - host) to_host ;; - status) status ;; - *) - echo "Usage: $0 {host|device|status}" - exit 1 - ;; -esac diff --git a/devices/gk7205v200_otg_generic/general/overlay/usr/bin/uvc-gadget-setup b/devices/gk7205v200_otg_generic/general/overlay/usr/bin/uvc-gadget-setup deleted file mode 100755 index a4b0960..0000000 --- a/devices/gk7205v200_otg_generic/general/overlay/usr/bin/uvc-gadget-setup +++ /dev/null @@ -1,165 +0,0 @@ -#!/bin/sh -# -# Compose the UVC gadget in configfs, advertising exactly the one format and -# frame size majestic is configured to serve. -# -# Advertising a menu is worse than useless here. majestic answers every -# PROBE/COMMIT with its own bFormatIndex/bFrameIndex, because it has exactly one -# stream to give -- it cannot switch encoder geometry or codec to follow the -# host. So anything extra in the descriptors is a resolution or a codec the host -# is invited to select and will then not receive: ask for MJPEG and H.264 -# arrives (an SPS where a JPEG SOI belongs, which is garbage to every consumer), -# ask for 1080p and 720p arrives (decoders recover from the SPS, anything -# trusting the negotiated V4L2 format does not). -# -# Re-run this after changing uvcgadget.format/size (usb-mode host && usb-mode -# device, or a reboot) or the descriptors will describe the previous config. -# -# Run after the gadget modules are loaded and configfs is mounted, and BEFORE -# anything writes the UDC name. `usb-mode device` does both for you. -# -# Notes that cost real debugging time, do not "fix" these: -# -# * The streaming group is "framebased", NOT "frame_based". The latter is the -# upstream >=5.15 spelling; this 4.9 vendor tree uses framebased. Using the -# wrong one fails silently -- mkdir just creates a junk directory that the -# gadget ignores, and the host sees no H.264 format. -# * control/class has only fs and ss. streaming/class has fs, hs and ss. -# Creating control/class/hs fails. -# * Attribute file names are camelCase (wWidth, dwFrameInterval, ...), even -# though the kernel's C identifiers are snake_case. -# * Do NOT try to set streaming_maxpacket / streaming_interval / -# streaming_maxburst. uvc_configfs.c's UVCG_OPTS_ATTR macro takes no `aname` -# parameter but still calls UVC_ATTR(..., aname), so all three land in -# configfs as files literally named "aname" and are unreachable. This is -# harmless: uvc_alloc_inst() already defaults them to 1 / 3072 / 15, and -# 3072 is the USB 2.0 high-speed maximum (3 transactions x 1024 B per -# microframe = ~24.5 MB/s isoc ceiling). There is nothing better to set. -# * The framebased format's GUID is hardcoded to H.264 by the kernel, so -# there is no guidFormat attribute to write for it. - -set -e - -G=/sys/kernel/config/usb_gadget/uvc0 -YAML=/etc/majestic.yaml - -# Read majestic's config, falling back to the same defaults uvcgadget_start() -# uses so a missing key or a missing `cli` still composes a working gadget. -cfg() { - _v=$(cli -g ".$1" 2>/dev/null) || _v="" - case "$_v" in ""|null|"~") echo "$2" ;; *) echo "$_v" ;; esac -} - -FORMAT=$(cfg uvcgadget.format h264) -SIZE=$(cfg uvcgadget.size 1280x720) -WIDTH=${SIZE%%x*} -HEIGHT=${SIZE##*x} -case "$WIDTH$HEIGHT" in - *[!0-9]*|"") WIDTH=1280; HEIGHT=720 ;; -esac - -# MJPEG frames come from the always-on JPEG channel, which runs at jpeg.fps and -# not at uvcgadget.fps. Advertise whichever actually feeds this format. -if [ "$FORMAT" = "mjpeg" ]; then - FPS=$(cfg jpeg.fps 5) -else - FPS=$(cfg uvcgadget.fps 30) -fi -[ "$FPS" -gt 0 ] 2>/dev/null || FPS=30 - -# Frame intervals are in 100 ns units: 10000000 / fps. -INTERVAL=$((10000000 / FPS)) - -if [ -d "$G" ]; then - echo "uvc-gadget-setup: $G already exists, nothing to do" - exit 0 -fi - -mkdir -p "$G" - -# 0x1d6b/0x0102 is the Linux Foundation vendor ID with the "UVC gadget" -# product ID used by the in-tree g_webcam example. Fine for a device that is -# never going to ship a USB-IF certified VID/PID. -echo 0x1d6b > "$G/idVendor" -echo 0x0102 > "$G/idProduct" - -mkdir -p "$G/strings/0x409" -echo "OpenIPC" > "$G/strings/0x409/manufacturer" -echo "gk7205v200 UVC" > "$G/strings/0x409/product" -[ -r /sys/class/net/eth0/address ] && - tr -d ':' < /sys/class/net/eth0/address > "$G/strings/0x409/serialnumber" || - echo "0000" > "$G/strings/0x409/serialnumber" - -mkdir -p "$G/configs/c.1/strings/0x409" -echo "UVC" > "$G/configs/c.1/strings/0x409/configuration" -# Self-powered: the camera runs off its own PSU, it draws nothing from the host. -echo 0xc0 > "$G/configs/c.1/bmAttributes" -echo 0 > "$G/configs/c.1/MaxPower" - -mkdir -p "$G/functions/uvc.0" -F="$G/functions/uvc.0" - -# One interval, the one we can sustain. Hosts pick the fastest entry on offer, -# so a rate we cannot feed is not a harmless extra: the host commits to it, -# sizes its timing from it, and then reports the shortfall as missing frames. -# -# This MUST reach the kernel as a single write(): configfs invokes the -# attribute's store() once per write() syscall, and each store() replaces the -# whole list rather than appending. busybox `printf '%s\n%s\n' a b` emits one -# write() per directive, so it stores only the last value and then fails with -# EINVAL on the second write. An `echo` of one string is a single write(). - -# $1 format dir, $2 min bitrate, $3 max bitrate -add_frame() { - _d="$1/f" - mkdir -p "$_d" - echo "$WIDTH" > "$_d/wWidth" - echo "$HEIGHT" > "$_d/wHeight" - echo "$INTERVAL" > "$_d/dwDefaultFrameInterval" - echo "$INTERVAL" > "$_d/dwFrameInterval" - echo "$2" > "$_d/dwMinBitRate" - echo "$3" > "$_d/dwMaxBitRate" -} - -# Bit rates are informational; scale them off the frame area so the numbers stay -# sane whatever size was configured. -PIXELS=$((WIDTH * HEIGHT)) - -if [ "$FORMAT" = "mjpeg" ]; then - mkdir -p "$F/streaming/mjpeg/m" - add_frame "$F/streaming/mjpeg/m" $((PIXELS * 13)) $((PIXELS * 33)) - # dwMaxVideoFrameBufferSize is an upper bound the host sizes its buffer - # from; w*h*2 is a generous ceiling for JPEG. Undersizing truncates frames. - echo $((PIXELS * 2)) > "$F/streaming/mjpeg/m/f/dwMaxVideoFrameBufferSize" - STREAM_FORMAT="$F/streaming/mjpeg/m" -else - mkdir -p "$F/streaming/framebased/h" - add_frame "$F/streaming/framebased/h" $((PIXELS * 2)) $((PIXELS * 9)) - # dwBytesPerLine is meaningless for a compressed format; 0 says "variable". - echo 0 > "$F/streaming/framebased/h/f/dwBytesPerLine" - STREAM_FORMAT="$F/streaming/framebased/h" -fi - -# --- headers and class links ------------------------------------------------- -# Formats link INTO the streaming header; the header links INTO each speed -# class. -# -# Targets must be ABSOLUTE. configfs_symlink() resolves the target with -# kern_path(), which resolves a relative path against the calling process's -# CWD -- not against the directory the link lives in. The upstream -# gadget-testing.txt recipe gets away with relative targets only because it -# cd's into each directory first. Absolute paths do not care where we are. -mkdir -p "$F/streaming/header/h" -ln -s "$STREAM_FORMAT" "$F/streaming/header/h/f" - -ln -s "$F/streaming/header/h" "$F/streaming/class/fs/h" -ln -s "$F/streaming/header/h" "$F/streaming/class/hs/h" -ln -s "$F/streaming/header/h" "$F/streaming/class/ss/h" - -mkdir -p "$F/control/header/h" -ln -s "$F/control/header/h" "$F/control/class/fs/h" -ln -s "$F/control/header/h" "$F/control/class/ss/h" - -ln -s "$F" "$G/configs/c.1/uvc.0" - -echo "uvc-gadget-setup: composed $G ($FORMAT ${WIDTH}x${HEIGHT}@${FPS})"