#!/usr/bin/env bash
# Puts every USB host controller's interrupt thread at SCHED_FIFO PRIORITY, and deals the controllers' interrupts across
# the cores after the first, short of any the kernel isolated for the control loop, so neither waits behind the load nor
# on CPU 0, nor takes the loop's core. The controllers carrying a CAN link are dealt first, each to a core of its own:
# threads at one priority take turns, so two buses' controllers on one core make each other's replies late. Every CAN
# frame and camera frame on a USB-attached board passes through that work; left to the kernel, it waits behind memory
# load, sent frames are confirmed late, the CAN transmit queue is still full when the next tick's commands arrive, and
# replies go missing. A PREEMPT_RT kernel, or any kernel booted with threadirqs, runs every interrupt as a thread at
# SCHED_FIFO 50, below the camera capture's 60, so raising these is what keeps them ahead of it. Without either there are
# no threads to raise: it says so and fails. Run at every boot by anvil-usb-irqs.service, since the kernel forgets both
# settings. A controller sharing its line with another, as two of a Pi 5's do, moves with it.
set -euo pipefail

# Below the control loop's SCHED_FIFO 80 and above the camera capture's 60.
PRIORITY=70

# The cores after the first that the kernel did not isolate, or CPU 0 where there are none. The list reads like 3 or 2-3.
isolated=" "
IFS=, read -r -a ranges </sys/devices/system/cpu/isolated || true
for range in "${ranges[@]}"; do
    for ((core = ${range%-*}; core <= ${range#*-}; core++)); do
        isolated+="${core} "
    done
done
targets=()
for ((core = 1; core < $(nproc --all); core++)); do
    [[ "${isolated}" == *" ${core} "* ]] || targets+=("${core}")
done
[ "${#targets[@]}" -gt 0 ] || targets=(0)

# The lines each CAN link's controller interrupts on. The controller is the parent of the link's root hub, usb5; on a
# plain line /proc/interrupts names that hub, xhci-hcd:usb5, while on MSI it says only xhci_hcd and the lines are the
# controller's msi_irqs. A link off a controller that is not USB, a Jetson's own, has no root hub and is left alone.
declare -A carrying
for link in /sys/class/net/can*; do
    [ -e "${link}/device" ] || continue
    hub="$(readlink -f "${link}/device" | grep --extended-regexp --only-matching '/usb[0-9]+/' | tr -d / || true)"
    [ -n "${hub}" ] || continue
    controller="$(readlink -f "/sys/bus/usb/devices/${hub}/..")"
    for irq in $(ls "${controller}/msi_irqs" 2>/dev/null || true) \
        $(grep --extended-regexp "\<xhci-hcd:${hub}\>" /proc/interrupts | cut -d: -f1 || true); do
        carrying["${irq}"]+="${link##*/} "
    done
done

lines=()
while read -r irq _; do lines+=("${irq%:}"); done < <(grep --extended-regexp 'xhci' /proc/interrupts)
if [ "${#lines[@]}" -eq 0 ]; then
    echo "no USB host controller named xhci in /proc/interrupts, so nothing was raised; it names them differently here"
    exit 1
fi
ordered=()
for irq in "${lines[@]}"; do [ -n "${carrying[${irq}]:-}" ] && ordered+=("${irq}"); done
for irq in "${lines[@]}"; do [ -n "${carrying[${irq}]:-}" ] || ordered+=("${irq}"); done
dealt=0
status=0
for irq in "${ordered[@]}"; do
    # A shared line has one thread per handler: irq/40-xhci-hcd and irq/40-s-xhci-hcd.
    threads="$(pgrep "^irq/${irq}-" || true)"
    if [ -z "${threads}" ]; then
        echo "irq ${irq}: no interrupt thread; was this kernel booted PREEMPT_RT or with threadirqs? uname -v says: $(uname -v)"
        status=1
        continue
    fi
    for thread in ${threads}; do
        chrt --fifo --pid "${PRIORITY}" "${thread}"
    done
    core="${targets[$((dealt % ${#targets[@]}))]}"
    links="${carrying[${irq}]:-}"
    if echo "${core}" >"/proc/irq/${irq}/smp_affinity_list" 2>/dev/null; then
        echo "irq ${irq}: SCHED_FIFO ${PRIORITY}, on core ${core}${links:+, carrying ${links% }}"
    else
        echo "irq ${irq}: SCHED_FIFO ${PRIORITY}; its controller does not take an affinity, left where it is${links:+, carrying ${links% }}"
    fi
    dealt=$((dealt + 1))
done
exit "${status}"
