99 lines
3.7 KiB
Bash
99 lines
3.7 KiB
Bash
# 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: $*"; }
|
|
disable_eee() {
|
|
local netdir
|
|
for netdir in "$nic"/net/*; do
|
|
[[ -e "$netdir/ifindex" ]] || continue
|
|
ethtool --set-eee "$(basename "$netdir")" eee off
|
|
done
|
|
}
|
|
|
|
# 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" == D0 && "$command" =~ ^[[:xdigit:]]{4}$ && "${command,,}" != ffff ]] &&
|
|
(( (16#$command & 6) == 6 )); then
|
|
log "PCI state healthy ($power, COMMAND=$command); no recovery needed."
|
|
disable_eee
|
|
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}$ && "${command,,}" != ffff ]] &&
|
|
(( (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
|
|
disable_eee
|
|
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
|