#!/bin/bash
set -e
dev=$1
if [[ -z $dev ]]; then
   echo "Usage: $0 <interface>"
   exit 1
fi

# Confirm if the driver is "igc" to handle the case where a user calls this 
# script outside of initnetdev
driver=$(basename "$(readlink "/sys/class/net/${dev}/device/driver")")
if [ "$driver" != "igc" ]; then
   echo "Interface is not driven by igc driver."
   exit 1
fi

restore_nic_operstate() {
   local ifstate
   local ifname
   ifstate=$1
   ifname=$2
   # Interface was down when this script was called. So keep it down at exit
   if [ "$ifstate" != "up" ]; then
       /sbin/ip link set dev "$ifname" down
   fi
}

nthreads=$(getconf _NPROCESSORS_CONF)
# IRQs assigned to each TxRx queue
irqs_a=()

# IRQ numbers show up in /proc/irq directory only when the interface is UP
operstate=$(cat "/sys/class/net/${dev}/operstate")
if [ "$operstate" != "up" ]; then
   /sbin/ip link set dev "${dev}" up
fi

# Sort numerically and reverse it. "link status" IRQ will be the last one
# in irqs_a() array
for irq in $(ls -1rv "/sys/class/net/${dev}/device/msi_irqs"); do
   irqs_a+=( "${irq}" )
done

if [[ ${#irqs_a[@]} != 5 ]]; then
   restore_nic_operstate "${operstate}" "${dev}"
   echo "<5>Unexpected IRQ count:${#irqs_a[@]}. Skip setting affinity" > /dev/kmsg
   exit 0
fi

# Goal is make the IRQ to be processed by only one thread. If we have more
# than 4 cores, we do not want one thread of a core to process IRQ-TxRx-0
# and another thread of same core process IRQ-TxRx-1. On platforms with more 
# than 4 cores, only threads in 4 cores will process these IRQs
cpu_list=()

add_to_cpu_list() {
   local thread
   thread="$1"
   for ((j = 0 ;j < ${#cpu_list[@]}; j++ )); do
      if [[ "${cpu_list[$j]}" = "$thread" ]]; then
         return
      fi
   done
   cpu_list+=( "$thread" )
}

if [[ $nthreads = 2 ]]; then
   cpu_list=( "0" "0" "1" "1" )
# We have 4 threads. They may be in 4 cores or 2 cores
elif [[ $nthreads = 4 ]]; then
   cpu_list=( "0" "1" "2" "3" )
else 
   # If the thread numbers in a core are not in sequence, they will be
   # seperated by a comma, otherwise by '-'
   IFS='-,'
   for (( i = 0; i < nthreads; i++ )); do
      read t1 t2 < "/sys/devices/system/cpu/cpu$i/topology/thread_siblings_list"
      add_to_cpu_list "${t1}"
      # We need 4 threads from different cores
      if [[ ${#cpu_list[@]} = 4 ]]; then
         break
      fi
   done
fi

# Set the IRQ affinity. affinity for "link status" IRQ is not set
for (( i = 0; i < 4; i++ )); do
   echo "${cpu_list[$i]}" > "/proc/irq/${irqs_a[$i]}/smp_affinity_list"
done

restore_nic_operstate "${operstate}" "${dev}"
