# Called once after boot or resume. No network access is required. set -euo pipefail nic="/sys/bus/pci/devices/$NIC_PCI" bridge="/sys/bus/pci/devices/$NIC_BRIDGE" log() { echo "rtl8125-recovery: $*"; } # Let normal PCI and network resume finish before diagnosing anything. sleep 10 if [[ ! -d "$bridge" ]]; then log "Parent bridge $NIC_BRIDGE is absent; refusing recovery." exit 1 fi if [[ $(cat "$bridge/vendor") != 0x1022 || $(cat "$bridge/device") != 0x43f5 ]]; then log "Parent bridge identity changed; refusing recovery." exit 1 fi if [[ -d "$nic" ]]; then if [[ $(cat "$nic/vendor") != 0x10ec || $(cat "$nic/device") != 0x8125 ]]; then log "Device at $NIC_PCI is not an RTL8125; refusing recovery." exit 1 fi if [[ $(dirname "$(readlink -f "$nic")") != "$(readlink -f "$bridge")" ]]; then log "NIC is no longer directly below the configured bridge; refusing recovery." exit 1 fi power=$(cat "$nic/power_state") command=$(timeout 5 setpci -s "$NIC_PCI" COMMAND 2>/dev/null || true) # Both memory decoding and bus mastering should be enabled on this host. # Disconnected cables do not disable these PCI command bits. if [[ "$power" != D3cold && "$command" =~ ^[[:xdigit:]]{4}$ ]] && (( (16#$command & 6) == 6 )); then log "PCI state healthy ($power, COMMAND=$command); no recovery needed." exit 0 fi # An administratively disabled NIC should not be automatically reset. netdirs=("$nic"/net/*) if [[ -e "${netdirs[0]}/flags" ]]; then flags=$(cat "${netdirs[0]}/flags") if (( (flags & 1) == 0 )); then log "Interface is administratively down; leaving it alone." exit 0 fi fi log "Failed PCI state: power=$power COMMAND=$command." else log "NIC missing; attempting one rescan of its known bridge." fi # Retain evidence from the latest failure without accumulating unlimited logs. evidence="${STATE_DIRECTORY:-/var/lib/rtl8125-recovery}/last-failure.log" mkdir -p "$(dirname "$evidence")" { date -Is timeout 5 lspci -s "$NIC_PCI" -vv || true timeout 5 lspci -s "$NIC_BRIDGE" -vv || true journalctl -b -k --since '10 minutes ago' -n 250 --no-pager || true } > "$evidence" 2>&1 # This is the remove/rescan recovery that worked on this machine. Never reset # or remove the parent bridge, which could affect other hardware. if [[ -d "$nic" ]]; then echo 1 > "$nic/remove" fi echo 1 > "$bridge/rescan" for ((attempt=0; attempt<20; attempt++)); do netdirs=("$nic"/net/*) if [[ -e "${netdirs[0]}/ifindex" ]]; then iface=$(basename "${netdirs[0]}") power=$(cat "$nic/power_state") command=$(timeout 5 setpci -s "$NIC_PCI" COMMAND 2>/dev/null || true) if [[ "$power" == D0 && "$command" =~ ^[[:xdigit:]]{4}$ ]] && (( (16#$command & 6) == 6 )); then # NetworkManager also reapplies the saved profile. Explicitly # restore WoL here so recovery does not depend on its timing. ethtool -s "$iface" wol g if ! ethtool "$iface" | grep -Eq '^[[:space:]]*Wake-on: g$'; then log "PCI recovered, but magic-packet WoL could not be confirmed." exit 1 fi log "Recovered $iface in D0; magic-packet WoL enabled. Evidence: $evidence" exit 0 fi fi sleep 1 done log "Recovery did not restore operational PCI state; no further attempt this invocation." exit 1