Skip to content

Instantly share code, notes, and snippets.

@startergo
Forked from joevt/pcitree.sh
Created January 31, 2023 02:28
Show Gist options
  • Save startergo/f98dafe9dff921c156413ade1724f2e6 to your computer and use it in GitHub Desktop.
Save startergo/f98dafe9dff921c156413ade1724f2e6 to your computer and use it in GitHub Desktop.
A bash script to produce more informative output than lspci -nntv
#!/bin/bash
# by joevt Jan 22, 2023
#===================
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root like this:"
echo "sudo $0"
exit 1
fi
#===================
style="wide"
style="narrow"
#encoding="ascii"
encoding="utf8"
#method="-H1" # -A intel-conf1
method="" # default method which for macOS is -A darwin
#===================
if [ $encoding == "ascii" ]; then
box0='\'
box1='-'
box2='\'
box3='+'
box4='|'
box5='.'
else
box0='┐'
box1='─'
box2='└'
box3='├'
box4='│'
box5='┬'
fi
if [ $style == "wide" ]; then
if [ $encoding == "ascii" ]; then
# since we're using \, we want to indent one extra space
rootindent=' '
subindent=' '
else
rootindent=' '
subindent=' '
fi
domainprefix="\\${box1}\\["
domainsuffix="\\]\\${box1}\\${box0}"
bridgeprefix="\\${box3}\\${box1}"
bridgesuffixlong="\\${box1}\\${box1}\\${box1}\\${box1}\\${box0}"
bridgesuffix="\\${box1}\\${box0}"
deviceprefix="\\${box3}\\${box1}"
devicesuffix=""
else
rootindent=''
subindent=' '
domainprefix="\\${box5}\\["
domainsuffix="\\]"
bridgeprefix="\\${box3}\\${box5}"
bridgesuffixlong=""
bridgesuffix=""
deviceprefix="\\${box3}\\${box1}"
devicesuffix=""
fulladdress=1
fi
#===================
# Get header type and bus information for all pci devices - reverse sort so last bus is first (required for device path substitution step below).
# Format is "domain:bus%device.function domain:primary domain:secondary domain:subordinate".
# Keep the domain number for device path substitution step below.
# -0777 allows perl to process all lines at once - here we process two lines at a time.
setpci $method -v -s '*:*:*' HEADER_TYPE.b 0x18.l 2> /dev/null | perl -0777 -pe '
s/((.{4}):(..):(..\..)) \@0e = ([08][12])\n\1 \@18 = ..(.{2})(.{2})(.{2})\n/$2:$3%$4 $2:$8 $2:$7 $2:$6\n/g; # bridge(01) or card bus(02) header type, multifunction(80) or not (00).
s/((.{4}):(..):(..\..)) \@0e = (..)\n\1 \@18 = ..(.{2})(.{2})(.{2})\n/$2:$3%$4\n/g # endpoint (or anything else) header type
' \
| sort -r > /tmp/pcilist.txt
# If primary = secondary = subordinate then that is a problem - change them to XX.
sed '/^\(....:..%..\..\) \(\(.\{4\}:\)..\) \2 \2$/s//\1 \3XX \3XX \3XX/' /tmp/pcilist.txt > /tmp/pcilist2.txt
echo '/ ....:/s// /g' > /tmp/pcipaths.txt # Add a command that will remove domain from primary, secondary, and subordinate bus numbers.
# Create device path substitution commands that will add parent bus to each secondary bus.
# When applied to the list of pci devices, the commands will create a full device path for
# each pci device. Multiple commands will be applied to each device to build the device
# path from right to left: end point (greatest bus number) to domain (lowest bus number).
# The commands are sorted from highest bus number to lowest bus number to make that possible.
sed -nE '/^(....:..%..\..) .{4}:.. (.{4}:..) .*/s//\/(\2)\/s\/\/\1%\\1\/g/p' /tmp/pcilist2.txt >> /tmp/pcipaths.txt
sed -E -f /tmp/pcipaths.txt /tmp/pcilist2.txt > /tmp/pcitree1.txt # Apply the device path substitution commands.
sed -E '/^(....:..).*/s//\1/g' /tmp/pcitree1.txt | sort -u > /tmp/pcitree2.txt # Get all root complexes (usually just 0000:00).
cat /tmp/pcitree1.txt /tmp/pcitree2.txt | LC_ALL=C sort > /tmp/pcitree3.txt # Concatenate the devices and root complexes and sort.
# Use perl to do some formatting.
perl -CSDA -Mutf8 -pe '
s/^(.*(\w\w\w\w:\w\w%\w\w\.\w).*)/\1 # \2/; # save domain/bus/device/function after a " # " - this will be used to append name and ids
s/^\w\w\w\w:\w\w%/'"$rootindent"'/; # remove domain/bus number from left side and indent
s/\w\w\.\w%\w\w\w\w:\w\w%/'"$subindent"'/g; # use the paths to do more indenting
s/(\w\w\.\w) \w\w (\w\w) \2/'"${bridgeprefix}"'\1-[\2]'"${bridgesuffixlong}"'/; # format the secondary and subordinate bus numbers for single bus bridge
s/(\w\w\.\w) \w\w (\w\w) (\w\w)/'"${bridgeprefix}"'\1-[\2-\3]'"${bridgesuffix}"'/; # format the secondary and subordinate bus numbers for multiple bus bridge
s/%/:/; # replace % bus/device delimiter with standard ":"
s/(\w\w\.\w )/'"${deviceprefix}"'\1'"${devicesuffix}"'/; # add the "+-" formating for devices
s/^(\w\w\w\w:\w\w)$/'"${domainprefix}"'\1'"${domainsuffix}"'/ # add the -[ ]\" for domain/bus
' \
/tmp/pcitree3.txt > /tmp/pcitree4.txt
# https://perldoc.perl.org/perlrun.html
# The -C flag controls some of the Perl Unicode features.
# S: STDIN, STDOUT and STDERR are assumed to be in UTF-8.
# D: UTF-8 is the default PerlIO layer for both input and output streams.
# A: @ARGV elements are expected to be strings encoded in UTF-8.
# -0[octal/hexadecimal] specifies the input record separator ($/ ) as an octal or hexadecimal number.
# Any value 0400 or above will cause Perl to slurp files whole, but by convention the value 0777 is the one normally used for this purpose.
# -Mmodule executes use module ; before executing your program. This loads the module and calls its import method, causing the module to have its default effect, typically importing subroutines or giving effect to a pragma.
# -p causes Perl to assume a loop around your program, which makes it iterate over filename arguments somewhat like sed.
# -e commandline May be used to enter one line of program.
perl -CSDA -Mutf8 -0777 -pe '
# add | between devices of the same bus
do {
$good=0;
while (
/ # begin regular expression
( # begin g1
\n # \n
([\ '"\\${box4}"']*) # [ |]* -> g2
['"\\${box3}\\${box4}"'] # [+|]
[^\n]* # rest of the line up to \n, (change + to *+ to give nothing back)
(?{$X=pos()}) # mark position
\n # \n
) # end g1
(?= # begin lookahead
( # begin group
\2\ [^\n]* # following line(s) start with g2 also (change + to *+ to give nothing back)
\n #
)+ # end group, match one or more, (change + to ++ to give nothing back)
\2 # the line after those start with g2
'"\\${box3}"' # and "+" meaning that another device follows g1 on the same bus
) # end lookahead
(
\2 #
(?{$C=pos()}) # $C is the position where we want to insert the |
\ # the character at position $C to be modified is a space character
) #
/gx # end regular expression (g=global; x=ignore white space in RE)
) {
$good = 1;
substr($_,$C,1) = "'"\\${box4}"'"; # replace the space character at position $C with a |
pos() = $X; # start next search at $X which is the end of the previous line
}
} until ($good == 0);
# replace + with \ for last device of a bus
s/(\n[\ '"\\${box4}"']*)'"\\${box3}"'(?![^\n]*\1['"\\${box3}\\${box4}"'])/\1'"\\${box2}"'/g;
' /tmp/pcitree4.txt > /tmp/pcitree5.txt
# Get the length of the longest line so we can align device information in a detail column.
spaces=$(sed -E 's/./ /g' /tmp/pcitree5.txt | sort -r | head -n 1)
lspci $method -nnv -D 2> /dev/null > /tmp/pcidevices0_lspci.txt
perl -pe '
s/^\n//g;
s/^[ \t].*\n//g;
s|/|\\/|g;
s|^(....:..:..\..) ([^[]*) (\[....\])(.*) (\[....:....\])( \(rev ..\))?(?: \(prog-if ..( \[.*?\])\))?|/# (\1)\$/s//'" "'# \\1 \5 \3:\6 : \2\7 \4/|;
s/\]:([^:]{9}.*?) *:([^:]{37}.*?) *:/]\1\2 :/ # rev column is 9 characters and type column is 36 characters
' \
/tmp/pcidevices0_lspci.txt > /tmp/pcidevices1_lspci_sed.txt
echo '/^(.{'"$((${#spaces} - 14))"'}) *(.*)/s//\1\2/' >> /tmp/pcidevices1_lspci_sed.txt # create the substitutions for the detail column
# Get link width and speed max/current.
pat="(....:..:..\..) @09 = (..)"
IFS=$'\n'
echo "" > /tmp/pcidevices2_link_sed.txt
echo "" > /tmp/pcidevices1_progintf_sed.txt
for thedevice in $(sudo setpci $method -v -s '*:*:*' CLASS_PROG.b 2> /dev/null); do
pcidevice="${thedevice% @*}"
proginterface="${thedevice#* = }"
# We have to do this in a loop because setpci stops if express capability doesn't exist
LinkRegisters=$(sudo setpci $method -s $pcidevice CAP_EXP+12.w CAP_EXP+c.l 2> /dev/null)
if [[ -n $LinkRegisters ]]; then
LinkStatusRegister=${LinkRegisters:0:4}
LinkWidth=$((0x$LinkStatusRegister >> 4 & 31))
if [[ $LinkWidth -ge 0 ]]; then
LinkSpeed=$((0x$LinkStatusRegister & 15))
LinkCapabilitiesRegister=${LinkRegisters:5:8}
MaxLinkWidth=$((0x$LinkCapabilitiesRegister >> 4 & 31))
MaxLinkSpeed=$((0x$LinkCapabilitiesRegister & 15))
echo "/# ($pcidevice .*$)/s//# g${MaxLinkSpeed}x${MaxLinkWidth} > g${LinkSpeed}x${LinkWidth} \1/" >> /tmp/pcidevices2_link_sed.txt
fi
fi
echo "/(# +$pcidevice \[....:....\] \[....)/s//\1${proginterface}/" >> /tmp/pcidevices1_progintf_sed.txt
done
perl -CSDA -Mutf8 -pe '
s/# (g.x..?) > \1 /# \1 /; # if the links are the same then output it only once
s|(/s//#.{16}) *|\1|; # make the link column 16 characters wide
' /tmp/pcidevices2_link_sed.txt > /tmp/pcidevices3_link_sed.txt
# If there's only one domain/segment then remove them from the output.
if [[ $(sed -E '/(....).*/s//\1/' /tmp/pcitree2.txt | sort -u | wc -l) -eq 1 ]]; then
echo "/(#.{16}).{4}:/s//\\1/" >> /tmp/pcidevices3_link_sed.txt
fi
if [[ $fulladdress -eq 1 ]]; then
echo "/..\..([^#]*#.{16})([0-9a-f.:]+) /s//\\2\\1/" >> /tmp/pcidevices3_link_sed.txt
fi
sed -E -f /tmp/pcidevices1_lspci_sed.txt -f /tmp/pcidevices1_progintf_sed.txt -f /tmp/pcidevices3_link_sed.txt /tmp/pcitree5.txt > /tmp/pcitree6.txt # apply the substitutions
echo '#========================================================================================='
cat /tmp/pcitree6.txt # final output
@startergo
Copy link
Author

startergo commented Jan 31, 2023

  • Download the script:
cd ~
git clone https://gist.github.com/e3cd4ff08aae06279134969c98ca3ab7.git pcitree
cd pcitree
chmod +x pcitree.sh
  • Install pciutils:
cd ~
git clone https://github.com/joevt/pciutils.git
cd pciutils
make
sudo su
sudo make install
exit
grep -q /usr/local/sbin /etc/paths || sudo sed -e $'1i\\\n/usr/local/sbin\\\n' -i "" /etc/paths
cd ~
  • Create a new Terminal window to start using the new commands.
  • Occasionally, update the pciids database:
sudo update-pciids
  • Test the pciutils commands:
sudo lspci -A detect
sudo lspci
  • Try the pcitree script:
cd ~/pcitree
sudo ./pcitree.sh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment