Die automatische Hardware-Erkennung beherrschen die meisten Distributionen erst seit wenigen Jahren. Dem Benutzer erscheint sie oft mystisch und magisch. Dabei kann jeder mit wenigen Zeilen Bash-Code eine eigene Hardware-Erkennung programmieren.
Zur Jahrtausendwende beherrschte kaum eine Distribution die automatische Hardware-Erkennung, oft genug musste man die nötigen Module selbst heraussuchen und in Konfigurationsdateien eintragen. Dabei ist es keine Hexerei, den passenden Treiber für ein Gerät zu finden.
Den Schlüssel zur die Hardware-Erkennung stellt die Liste der PCI-Geräte in den Unterverzeichnissen von /proc/bus/pci/. Hier gibt es für jedes PCI-Gerät eine eigene Pseudo-Datei mit detaillierten Informationen über die Konfiguration.
Abbildung 1 zeigt den Aufbau der dort abgelegten Konfigurationsdateien. Für die Hardware-Erkennung benötigen wir Angaben über den Hersteller und das Gerät: Die Vendor-ID und die Device-ID.

/proc/bus/pci eine Pseudo-Konfigurationsdatei. Sie enthält die für die Hardware-Erkennung wichtigen Vendor-, Device- und Class-IDs.” width=”300″ height=”61″ />
Abbildung 1: Für jedes PCI-Gerät gibt es in/proc/bus/pci eine Pseudo-Konfigurationsdatei. Sie enthält die für die Hardware-Erkennung wichtigen Vendor-, Device- und Class-IDs.Genormte IDs
Beide IDs sind in der PCI-ID-Liste auf Sourceforge [1] hinterlegt und werden vom PCI-Konsortium für jedes Gerät einzeln zugewiesen. Auch die Linux-Treiber verlassen sich auf die Vendor- und Device-ID, um festzustellen, welche Geräte sie jeweils unterstützen und welche nicht.
Die PCI-ID-Liste von [1] lässt sich sehr einfach parsen, da die einzelnen Spalten jeweils mit Tabulator getrennt sind. Die Zeilen 20 bis 35 aus Listing 1 übernehmen diese Aufgabe.
Insgesamt gibt es fünf Spalten; die ersten drei sind für uns interessant. In der ersten steht entweder ein “v” für einen Hersteller-Namen oder ein “d” für eine Gerätebezeichnung. Die zweite Spalte enthält die Vendor-ID als vierstellige hexadezimale Zahl und bei Geräte-Einträgen zusätzlich die an die Vendor-ID angehängt Device-ID – also eine achtstellige hexadezimale Zahl. In der dritten Spalte findet sich der Herstellername oder die Gerätebezeichnung als Klartext. Diese Spalte verwenden wir später für die Ausgabe.
Skalare Variablen statt Arrays
Das Einlesen der PCI-ID-Liste erfordert einen Trick: Zweidimensionale Arrays nach dem Muster d[Vendor-ID][Device-ID] beherrscht die Bash nicht, und bei eindimensionalen Arrays ist der Index nicht groß genug, um 32-Bit-Werte nach dem Muster d[Vendor-ID/Device-ID] aufzunehmen.
Die Lösung bieten “Variablennamen mit Variablen”, also einfache skalare Variablen: Der Herstellername oder die Gerätebezeichnung landet in einer Variablen, die mit “v” respektive mit “d” beginnt und dann die vierstellige hexadezimale Vendor-ID oder die achtstellige hexadezimale Vendor- und Device-ID trägt. In den Zeilen 28 und 32 sehen Sie die diese Zuweisungen – das Schlüsselwort declare ist erforderlich, damit die Bash aus v${2} respektive d${2} den vollen Variablennamen vor der Zuweisung auflöst.
Doch zurück zur PCI-ID-Datenbank: Spalte 4 hat noch eine besondere Bedeutung: Eine “0” signalisiert, dass es sich um einen von den Moderatoren der Liste verifizierten Geräteeintrag handelt, eine “1” zeigt einen noch nicht überprüften Eintrag eines Benutzers per Web-Formular. Das Skript verwendet bei den Herstellernamen lediglich geprüfte Einträge, siehe Zeile 27.
Kernel-Module zuordnen
Einen gänzlich anderen Aufbau hat die Modulliste des Kernels. Vorausgesetzt, der Kernel besitzt überhaupt Module, steht diese Liste in /lib/modules/`uname -r`/modules.pcimap und enthält durch mehrere Leerzeichen getrennte Spalten. Anhand der modules.pcimap stellt der Kernel fest, welches Modul für welches PCI-Gerät zuständig ist – wie geschaffen für das Hardware-Erkennungs-Skript.
Entsprechend einfach gestaltet sich der betreffende Code-Block in Listing 1: Zeile 37 bis 49 lesen die Modul-Zuordnungstabelle ein und speichern sie analog zu den Device-Einträgen aus der PCI-ID-Datenbank. Zeile 43 verhindert, dass ein vorhandener Eintrag überschrieben wird und fügt weitere Module, die zum Betrieb eines bestimmten PCI-Geräts erforderlich sind, mit Kommas getrennt an.
Fest eincompilierte Treiber
Treiber lassen sich jedoch nicht nur als Module kompilieren, sondern auch fest in den Kernel einbauen. Der Kernel verrät jedoch nicht, welche Geräte die fest einkompilierten Treiber unterstützen. Hier hilft die System.map des Kernels weiter.
Der Aufbau der Datei ist einfach: Es gibt drei mit Leerzeichen getrennte Spalten. Die einzelnen Treiber verwenden das Symbol __devicestr_ gefolgt von Vendor- und Device-ID in hexadezimaler Schreibweise.
Für die Auftrennung des Device-Strings verwenden wir die Variable IFS. In Zeile 56 wird die IFS auf Unterstrich gesetzt, womit die Bash Unterstriche als Trennzeichen zwischen Parametern erkennt. Der nachfolgende set-Aufruf ergibt vier Parameter: $3 enthält “devicestr” und schließlich $4 die kombinierte Vendor- und Device-ID.
Geräteklassen
Die Hardware-Erkennung selbst läuft in den Zeilen 62 bis Ende des Skripts ab. Die Schleife in Zeile 62 arbeitet alle PCI-Geräte ab, die unterhalb von /proc/bus/pci/ aufgelistet sind.
Mit Hilfe des externen Programms hexdump fördern die Zeilen 63, 64 und 66 nacheinander Vendor-ID, Device-ID und Class-ID zutage. Der Parameter -s beim Aufruf von hexdump gibt das Offset in Bytes relativ zum Dateianfang an, -n die Zahl der auszuwertenden Bytes. Die Werte ergeben sich aus Abb. 1: Die Vendor-ID besteht aus den Bytes 1 und 2, die Device-ID ist in Byte 3 und 4 gespeichert und die Class-ID in den Bytes 10 und 11. Der Parameter -e '1/2 "%04x"' beschreibt das Ausgabeformat, das Ergebnis ist eine vierstellige hexadezimale Zahl mit Kleinbuchstaben.
Die Class-ID wertet Listing 1 nicht weiter aus. Sie ist jedoch für eigene Skripte ganz praktisch: Will man etwa lediglich Netzwerkkarten erkennen und einrichten, sucht man einfach nach PCI-Geräten mit der Class-ID 0201. Eine Vollständige Liste der Class-IDs gibt es unter [2] in der Struktur PciClassCodeTable am Ende der Datei. Die Class-ID besteht aus den ersten beiden dort aufgeführten Bytes.
Variable Variablennamen
Die Zeile 68 bis 71 des Skripts verwenden die ausgelesene Vendor- und Device-ID, um die Variablennamen für Herstellernamen, Gerätebezeichnung, Modulnamen und Kernel-Symbol aufzubauen – der Variablenname besteht aus einem Anfangsbuchstaben, der Vendor-ID und (mit Ausnahme des Herstellernamens) zusätzlich der Device-ID.
Die Zeilen ab 73 dienen der Ausgabe. Hier erfolgt der Zugriff auf die “variablen Variablennamen”. ${!a} ist ein besonderes Variablen-Konstrukt: Das Ausrufezeichen als erstes Zeichen des Variablennamens bedeutet, dass die Bash den Inhalt der dahinter genannten Variable als Variablennamen verwendet und dann den Wert zurück liefert.
Hat die Variable a etwa den Wert “v8086”, wird ${!a} aufgelöst zu ${v8086} und somit der Inhalt der Variablen v8036 ausgegeben. Die zugehörige Zuweisung einer solchen “Variablen mit variablem Namen” finden Sie in den Zeilen 28, 32, 44, 46 und 58.
Mit den Beispielen aus Listing 1 können Sie auf einfache Weise eigene Skripte etwa zur Hardware-Erkennung von Netzwerk- und Soundkarten schreiben. Der Autor verwendet die hier gezeigten Methoden zum Beispiel, um in der Mini-Distribution LinVDR das Netzwerkmodul automatisch zu bestimmen – dort allerdings nicht in einem Bash-, sondern in einem Perl-Skript.
Listing 1
hwdetect
#!/bin/bash
PCIIDS=pci.db
PCIMAP=/lib/modules/`uname -r`/modules.pcimap
SYSTEMMAP=/boot/System.map
Tab=$'\t'
Newline=$'\n'
if [ ! -d /proc/bus/pci ]; then
echo "/proc/bus/pci existiert nicht."
exit 1
fi
if [ ! -r "$PCIIDS" -o ! -r "$PCIMAP" ]; then
echo "PCI-ID-Liste, PCI-Modul-Liste oder System.map nicht gefunden."
exit 1
fi
IFS="${Newline}"
for z in `cat $PCIIDS`; do
IFS="${Tab}"
set – $z
case "$1" in
v)
if [ "$4" = "0" ]; then
declare v${2}=$3
fi
;;
d)
declare d${2}=$3
;;
esac
done
IFS="${Newline}"
for z in `cat $PCIMAP`; do
IFS=" "
set – $z
id="m${2:6}${3:6}"
if [ -z "${!id}" ]; then
declare ${id}="$1"
else
declare ${id}="${!id}, ${1}"
fi
done
IFS="${Newline}"
for z in `cat $SYSTEMMAP`; do
IFS="${Tab} "
set – $z
if [ "${3:0:12}" = "__devicestr_" ]; then
IFS="_"
set – $3
declare k${4}=1
fi
done
for device in /proc/bus/pci/??/*; do
Vendor=`hexdump -s 0 -n 2 -e '1/2 "%04x"' $device`
Device=`hexdump -s 2 -n 2 -e '1/2 "%04x"' $device`
Class=`hexdump -s 10 -n 2 -e '1/2 "%04x"' $device`
v="v${Vendor}"
d="d${Vendor}${Device}"
m="m${Vendor}${Device}"
k="k${Vendor}${Device}"
echo "Hersteller: ${!v}${Tab}[0x${Vendor}]"
echo "Gerät: ${!d}${Tab}[0x${Device}]"
if [ -n "${!m}" ]; then
echo "Kernel-Modul: ${!m}"
elif [ -n "${!k}" ]; then
echo "Vom Kernel unterstützt"
else
echo "Nicht unterstützt"
fi
echo
done
Infos
[1] PCI-ID-Liste: http://pciids.sourceforge.net/pci.db
[2] PCI-Class-IDs: http://www.pcidatabase.com/pci_c_header.php in der Struktur PciClassCodeTable




