Skip to content

Instantly share code, notes, and snippets.

@romen
Created September 8, 2025 11:45
Show Gist options
  • Save romen/8b8c532410dc6947aafdd7a68773c0b4 to your computer and use it in GitHub Desktop.
Save romen/8b8c532410dc6947aafdd7a68773c0b4 to your computer and use it in GitHub Desktop.
A script to reset USB controllers
#!/usr/bin/env bash
#
# USB xHCI controller reset helper
# - Unbinds/rebinds the controller from/to xhci_hcd
# - Colors + Nerd Font icons chosen automatically by log level
# - --interactive: pick a controller from a list (uses fzf if available)
#
# Usage:
# sudo reset-usb.sh [--interactive] [CONTROLLER_ID]
# sudo reset-usb.sh --help
#
set -euo pipefail
# --- Config (defaults) ---
DRIVER="xhci_hcd"
CONTROLLER_DEFAULT="0000:00:14.0"
SLEEP_SECONDS=2
DRIVER_DIR="/sys/bus/pci/drivers/${DRIVER}"
UNBIND_PATH="${DRIVER_DIR}/unbind"
BIND_PATH="${DRIVER_DIR}/bind"
# --- Colors (respect NO_COLOR and only if stderr is a TTY) ---
if [[ -t 2 && -z "${NO_COLOR:-}" ]]; then
COLOR_RED=$'\e[31m'
COLOR_GREEN=$'\e[32m'
COLOR_YELLOW=$'\e[33m'
COLOR_BLUE=$'\e[34m'
COLOR_DIM=$'\e[2m'
COLOR_RESET=$'\e[0m'
else
COLOR_RED=""; COLOR_GREEN=""; COLOR_YELLOW=""; COLOR_BLUE=""; COLOR_DIM=""; COLOR_RESET=""
fi
# --- Nerd Font Icons (fallback if not supported) ---
ICON_INFO=""
ICON_OK=""
ICON_WARN=""
ICON_ERR=""
ICON_USB=""
# crude fallback if glyphs won’t render
if ! printf "%b" "$ICON_OK" | LC_ALL=C grep -q . 2>/dev/null; then
ICON_INFO="i"; ICON_OK="OK"; ICON_WARN="!"; ICON_ERR="X"; ICON_USB="USB"
fi
# --- Helpers to map level -> color/icon ---
_color_for_level() {
case "${1^^}" in
SUCCESS) printf "%s" "$COLOR_GREEN" ;;
INFO) printf "%s" "$COLOR_BLUE" ;;
WARN) printf "%s" "$COLOR_YELLOW";;
ERROR) printf "%s" "$COLOR_RED" ;;
*) printf "%s" "$COLOR_DIM" ;;
esac
}
_icon_for_level() {
case "${1^^}" in
SUCCESS) printf "%s" "$ICON_OK" ;;
INFO) printf "%s" "$ICON_INFO" ;;
WARN) printf "%s" "$ICON_WARN" ;;
ERROR) printf "%s" "$ICON_ERR" ;;
*) printf "%s" "$ICON_INFO" ;;
esac
}
# --- Logging ---
log() { # usage: log LEVEL "message..."
local level="${1:-INFO}"; shift || true
local msg="${*:-}"
local color; color="$(_color_for_level "$level")"
local icon; icon="$(_icon_for_level "$level")"
# Timestamp dimmed, then restore the level color for the message
printf "%b%s %b[%s]%b %b%s%b\n" \
"$color" "$icon" \
"$COLOR_DIM" "$(date '+%H:%M:%S')" "$COLOR_RESET" \
"$color" "$msg" "$COLOR_RESET" >&2
}
die() {
log ERROR "$*"
exit 1
}
usage() {
cat >&2 <<EOF
Reset an xHCI USB controller by unbinding/rebinding to ${DRIVER}.
${COLOR_DIM}Usage:${COLOR_RESET}
sudo ${0##*/} [--interactive] [CONTROLLER_ID]
sudo ${0##*/} --help
${COLOR_DIM}Examples:${COLOR_RESET}
sudo ${0##*/} # uses default ${CONTROLLER_DEFAULT}
sudo ${0##*/} 0000:03:00.0 # explicit controller
sudo ${0##*/} --interactive # pick from detected controllers
${COLOR_DIM}Notes:${COLOR_RESET}
- Must be run as root (or via sudo).
- You can whitelist this script in sudoers with NOPASSWD for convenience.
EOF
}
require_root() {
[[ "${EUID}" -eq 0 ]] || die "Please run as root (sudo)."
}
check_paths() {
[[ -w "$UNBIND_PATH" && -w "$BIND_PATH" ]] || die "Cannot access ${UNBIND_PATH} or ${BIND_PATH}. Is ${DRIVER} loaded?"
}
list_bound_controllers() {
shopt -s nullglob
local d
for d in "${DRIVER_DIR}"/0000:*; do
[[ -e "$d" ]] && basename "$d"
done
shopt -u nullglob
}
pick_controller_interactive() {
mapfile -t items < <(list_bound_controllers)
((${#items[@]})) || die "No controllers bound to ${DRIVER} were found."
if command -v fzf >/dev/null 2>&1; then
printf "%s\n" "${items[@]}" | fzf --prompt="Select controller > " --height=40% --reverse --border || true
else
log INFO "fzf not found, using bash menu."
local PS3="Select controller (1-${#items[@]} or 'q' to quit): "
local choice
select choice in "${items[@]}"; do
[[ -n "${choice:-}" ]] && { echo "$choice"; break; }
[[ "$REPLY" == "q" ]] && return 1
echo "Invalid choice." >&2
done
fi
}
reset_controller() {
local ctrl="$1"
log WARN "Unbinding ${ICON_USB} controller ${ctrl} from driver ${DRIVER}..."
echo "$ctrl" >"$UNBIND_PATH" || die "Failed to unbind ${ctrl}."
sleep "$SLEEP_SECONDS"
log INFO "Rebinding ${ICON_USB} controller ${ctrl} to driver ${DRIVER}..."
echo "$ctrl" >"$BIND_PATH" || die "Failed to bind ${ctrl}."
log SUCCESS "Controller ${ctrl} reset complete."
}
# --- Argument parsing ---
INTERACTIVE=false
CONTROLLER_ARG=""
while (( "$#" )); do
case "$1" in
-i|--interactive) INTERACTIVE=true ;;
-h|--help) usage; exit 0 ;;
--) shift; break ;;
-*)
die "Unknown option: $1. Use --help."
;;
*)
if [[ -z "$CONTROLLER_ARG" ]]; then
CONTROLLER_ARG="$1"
else
die "Unexpected extra argument: $1"
fi
;;
esac
shift
done
# --- Main ---
require_root
check_paths
if "$INTERACTIVE"; then
SELECTED="$(pick_controller_interactive || true)"
[[ -n "${SELECTED:-}" ]] || die "No controller selected."
CONTROLLER="$SELECTED"
else
CONTROLLER="${CONTROLLER_ARG:-$CONTROLLER_DEFAULT}"
fi
# Basic format hint (don't block if it doesn't match)
if [[ ! "$CONTROLLER" =~ ^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$ ]]; then
log WARN "Controller ID '$CONTROLLER' doesn't look like a PCI address (domain:bus:slot.func). Proceeding anyway..."
fi
reset_controller "$CONTROLLER"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment