#!/bin/bash
#
# Author: David E. Box <david.e.box@intel.com>
#
# Tool for reading current Link Status for Intel PCIe root ports
#
# Requires: xxd

set -e

PCI_SYSFS="/sys/devices/pci0000:00"
PCI_BRIDGE_CLASS="0x060400"
devices=$(ls ${PCI_SYSFS})
bridge_devices=()


# Get list of pci bridge deivces
for dev in $devices; do
	if [ -d ${PCI_SYSFS}/${dev} ] && [ -f ${PCI_SYSFS}/${dev}/config ]; then
		class=$(cat ${PCI_SYSFS}/${dev}/class)
		if [ "${class}" == ${PCI_BRIDGE_CLASS} ] ; then
			bridge_devices+=("${dev}")
		fi
	fi
done


link_state() {
	local pciests1=$1
	local byte1="$((16#${pciests1}>>8))"
	local ltsmstate="$((16#${pciests1}&255))"
	local l1msg="Link is in L1"

	# Some bit manipulation to get Link Status
	lnkstat=$((($byte1>>3)&15))

	case "${lnkstat}" in
	0) case "${ltsmstate}" in
	   [0-9]|1[0-6]) l1msg="Link is in Detect"
		  ;;
	   4[2-9]) l1msg="Link is in L2, LTSMSTATE=0"
		  ;;
	   59|60|61) l1msg="Link is Disabled"
		  ;;
	   *) l1msg="Link is not in Low Power State, LTSMSTATE=${ltsmstate}"
		  ;;
	   esac
	   echo ${l1msg}
	   ;;
	1) echo "Link is Retraining"
	   ;;
	3) case "${ltsmstate}" in
	   5[5-9]|60) l1msg="Link is in L1"
		  ;;
	   61|62) l1msg="Link is in L1.1"
		  ;;
	   63|64) l1msg="Link is in L1.2"
		  ;;
	   *) l1msg="Link is other L1 state, LTSMSTATE=${ltsmstate}"
		  ;;
	   esac
	   echo ${l1msg}
	   ;;
	4) echo "Link is in L2"
	   ;;
	5) echo "Link is in L3"
	   ;;
	[7-9]|10) echo "Link is in L0"
	   ;;
	*) echo "Unknown link state ${lnkstat}"
	   ;;
	esac
}

stop=$((SECONDS+10))
while [ $SECONDS -lt $stop ]; do
	for dev in ${bridge_devices[@]}; do
		# Get lower 16 bits of PCIESTS1 register
		pciests1=$(sudo xxd -ps -l2 -s 0x32a ${PCI_SYSFS}/${dev}/config)
		echo $dev $(link_state "${pciests1}")
	done
	sleep .1
done
