#!/usr/bin/env bash
# Phase B (low risk): steer USB (XR/HAT) IRQs to EG core; net softirq preference to NET core.
# Best-effort — NEVER fails the boot / update path (always exit 0 after setup).
#
#   sudo bash apply_irq_affinity.sh
#   sudo OHD_IRQ_AFFINITY=0 bash apply_irq_affinity.sh   # no-op off switch
#
# Prefer the copy under /usr/local/lib/ohd/ (install_cpu_affinity.sh installs it)
# so systemd does not exec a Dropbox-synced script (CRLF / write races).

# CRLF heal without rewriting the source tree (Dropbox often rejects sed -i).
if grep -q $'\r' "${BASH_SOURCE[0]}" 2>/dev/null; then
  _tmp="$(mktemp)"
  tr -d '\r' <"${BASH_SOURCE[0]}" >"${_tmp}"
  chmod 0755 "${_tmp}"
  exec bash "${_tmp}" "$@"
fi

# Soft: any pin/RPS failure must not fail the systemd oneshot (red "Job failed").
set +e
set -u

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Prefer colocated cpu_layout (installed next to this script under /usr/local/lib/ohd).
if [[ -f "${SCRIPT_DIR}/cpu_layout.sh" ]]; then
  # shellcheck source=cpu_layout.sh
  source "${SCRIPT_DIR}/cpu_layout.sh"
elif [[ -f /usr/local/lib/ohd/cpu_layout.sh ]]; then
  # shellcheck source=/dev/null
  source /usr/local/lib/ohd/cpu_layout.sh
else
  echo "ohd-irq-affinity: WARN missing cpu_layout.sh — skip" >&2
  exit 0
fi

LOG_TAG="ohd-irq-affinity"

log() { echo "${LOG_TAG}: $*" >&2; }

if [[ "${EUID}" -ne 0 ]]; then
  log "FATAL: run as root (sudo) — skipping (non-fatal for callers)"
  exit 0
fi

if [[ "${OHD_IRQ_AFFINITY:-1}" != "1" ]]; then
  log "OHD_IRQ_AFFINITY!=1 — skip"
  exit 0
fi

if ! ohd_cpu_lanes_ok; then
  log "skip (OHD_CPU_ENABLE=${OHD_CPU_ENABLE} nproc=$(ohd_cpu_count) need>=4)"
  exit 0
fi

EG_CORE="${OHD_CPU_EG}"
NET_CORE="${OHD_CPU_NET}"
NET_MASK="$(ohd_cpu_bitmask_hex "${NET_CORE}")"
EG_MASK="$(ohd_cpu_bitmask_hex "${EG_CORE}")"

pin_irq_list() {
  local irq="$1" core="$2"
  local path="/proc/irq/${irq}/smp_affinity_list"
  if [[ ! -w "${path}" ]]; then
    return 0
  fi
  if echo "${core}" >"${path}" 2>/dev/null; then
    log "irq ${irq} → cpu${core}"
  fi
  return 0
}

# Match /proc/interrupts names for USB host (Pi5 xHCI etc.) → EG core.
# Avoid graphics (v3d/vc4) so DF keeps its GPU path free.
while read -r line; do
  irq="${line%%:*}"
  irq="${irq// /}"
  [[ "${irq}" =~ ^[0-9]+$ ]] || continue
  lower="$(printf '%s' "${line}" | tr '[:upper:]' '[:lower:]')"
  case "${lower}" in
    *xhci*|*dwc3*|*dwc_otg*|*usb*|*uas*)
      pin_irq_list "${irq}" "${EG_CORE}"
      ;;
    *eth0*|*eth1*|*lan78*|*macb*|*stmmac*|*gmac*|*brcmfmac*|*wlan*|*mmc*)
      pin_irq_list "${irq}" "${NET_CORE}"
      ;;
  esac
done </proc/interrupts

# Device MSI IRQs for eth under /sys (more reliable than name parse alone).
for iface in eth0 eth1; do
  base="/sys/class/net/${iface}/device"
  [[ -d "${base}" ]] || continue
  if [[ -d "${base}/msi_irqs" ]]; then
    for irqf in "${base}"/msi_irqs/*; do
      [[ -e "${irqf}" ]] || continue
      pin_irq_list "$(basename "${irqf}")" "${NET_CORE}"
    done
  fi
done

# RPS: process inbound packets on NET core when driver supports it.
for iface in /sys/class/net/*; do
  name="$(basename "${iface}")"
  case "${name}" in
    eth*|wlan*|gretap*|wg*|br*)
      for q in "${iface}"/queues/rx-*; do
        [[ -d "${q}" ]] || continue
        rps="${q}/rps_cpus"
        [[ -w "${rps}" ]] || continue
        if echo "${NET_MASK}" >"${rps}" 2>/dev/null; then
          log "rps ${name}/$(basename "${q}") mask=0x${NET_MASK} (cpu${NET_CORE})"
        fi
      done
      ;;
  esac
done

log "layout os=${OHD_CPU_OS} net=${NET_CORE}(rps=0x${NET_MASK}) df=${OHD_CPU_DF} eg=${EG_CORE}(usb prefer 0x${EG_MASK})"
exit 0
