#!/bin/bash

#
# Path to the required files
PCIIDS=pci.db      # pci id list from von http://pciids.sourceforge.net/pci.db
PCIMAP=/lib/modules/`uname -r`/modules.pcimap
SYSTEMMAP=/boot/System.map

#
# Tab and newline as variables for easier understanding
Tab=$'\t'
Newline=$'\n'

#
# We need access to /[proc/sys]/bus/pci, so check if these directories exist
if [ ! /sys/bus/pci -a ! -d /proc/bus/pci ]; then
  echo "/sys/bus/pci and/or /proc/bus/pci don't exist"
  exit 1
fi

#
# Read the pci id list (http://pciids.sf.net/pci.db)
if [ -r "$PCIIDS" ]; then
  PCIIDCMD="cat $PCIIDS"
else
  PCIIDCMD="wget -q -O - http://pciids.sf.net/pci.db"
fi

# check pci module list (/lib/modules/[kernel version]/modules.pcimap) and
# kernel symbol table (/boot/System.map)
if [ ! -r "$PCIMAP" -o ! -r "$SYSTEMMAP" ]; then
  echo "PCI-Modul-Liste oder System.map nicht gefunden."
  exit 1
fi

#
# read pci id list line for line
IFS="${Newline}"
for z in `eval ${PCIIDCMD}`; do
  # split the columns along the tabs
  IFS="${Tab}"
  set -- $z

  # the first character indicates vendor and device descriptions
  case "$1" in
    v)
      # vendor description: allow only verified descriptions ($4=0)
      if [ "$4" = "0" ]; then
        declare v${2}=$3
      fi
      ;;
    d)
      # device descriptions
      declare d${2}=$3
      ;;
  esac
done

#
# read pci module list line for line
IFS="${Newline}"
for z in `cat $PCIMAP`; do
  # columns are seperated by multiple spaces
  IFS=" "
  set -- $z

  # column 2 and 3 contain the vendor and device id, column 6 the class id
  if [ "$2" = "0xffffffff" -a "$3" = "0xffffffff" ]; then
    # generic driver for a whole device class
    id="c${6:4:4}"
  elif [ "$3" = "0xffffffff" ]; then
    # generic driver for all devices of a specific vendor
    id="m${2:6}"
  else
    id="m${2:6}${3:6}"
  fi
  
  if [ -z "${!id}" ]; then
    # column 1 lists the module name
    declare ${id}="$1"
  else
    declare ${id}="${!id}${Tab}${1}"
  fi
done

#
# read the list of kernel supported devices line by line
IFS="${Newline}"
for z in `cat $SYSTEMMAP`; do
  # columns are seperated by multiple spaces
  IFS="${Tab} "
  set -- $z

  # filter all symbols starting with "__devicestr_" in the third column
  if [ "${3:0:12}" = "__devicestr_" ]; then
    # the device string is seperated with an underscore from the id
    IFS="_"
    set -- $3
    # parameter 4 contains the combined vendor and device id
    declare k${4}=1
  fi
done

IFS=",${Tab}${Newline}"
#
# scan /sys/bus/pci/devices/* or /proc/bus/pci/??/* for devices
if [ -e /sys/bus/pci/devices ]; then
  PCIDevices=/sys/bus/pci/devices/*
  Method="sysfs"
elif [ -e /proc/bus/pci ]; then
  PCIDevices=/proc/bus/pci/??/*
  Method="proc"
fi

# fetch the device data from /proc or /sys
for device in $PCIDevices; do
  if [ "$Method" = "sysfs" ]; then
    read Vendor < ${device}/vendor
    Vendor=${Vendor:2:4}
    read Device < ${device}/device
    Device=${Device:2:4}
    read Class < ${device}/class
    Class=${Class:2:4}
  elif [ "$Method" = "proc" ]; then
    # convert vendor and device id to hex
    Vendor=`hexdump -s 0 -n 2 -e '1/2 "%04x"' $device`
    Device=`hexdump -s 2 -n 2 -e '1/2 "%04x"' $device`

    # same for class id
    Class=`hexdump -s 10 -n 2 -e '1/2 "%04x"' $device`
  fi

  # create the variable names of the variables
  v="v${Vendor}"             # name of vendor variable
  d="d${Vendor}${Device}"    # name of device variable
  m="m${Vendor}${Device}"    # name of kernel module variable
  g="m${Vendor}"             # name of generic kernel module variables
  k="k${Vendor}${Device}"    # name of kernel symbol variable
  c="c${Class}"              # pci device class

  echo "vendor:   ${!v}${Tab}[0x${Vendor}]"
  echo "device:   ${!d}${Tab}[0x${Device}]"
  # echo "class:    [0x${Class}]"

  if [ -n "${!m}" -o -n "${!g}" -o -n "${!c}" ]; then
    set -- ${!m} ${!g} ${!c}
    if [ "$#" -gt "1" ]; then
      echo "kernel modules: $*"
    else
      echo "kernel module: $1"
    fi
  elif [ -n "${!k}" ]; then
    echo "supported by kernel"
  else
    echo "unsupported"
  fi
  echo
done
