module for recovering shitty realtek controller when it gets stuck in bugged power state
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user