2007年11月27日 星期二

實作 Linux 下的硬體自動偵測:PCI 裝置

Standard
x86 的硬體分為很多種類別,從早期的 ISA 一直到後來的 PCI、PCMCIA、USB,都有各自的硬體接角定義,要辨識這些裝置,得靠這些硬體通電與主機板連接後,所提供的自身資訊。

就最常用到的 PCI 裝置來說,目前有 99.999% 的硬體都是歸屬於 PCI 裝置﹝包括AGP、主機板上的各種控制晶片﹞。所以,只要能夠正確辨識 PCI 裝置並載入相對應的 Modules,就能夠對大部份硬體做自動識別。

識別 PCI 的方法,就是讀取 /proc/bus/pci/devices 或 /sys/bus/devices/* 取得裝置所提供的 vendor(製造商)、device(裝置或晶片型號)、subvendor(再製造商)、subdevice(再製造之裝置型號),然後將這些資訊與 Linux kernel 所提供的 modules.pcimap 做比對,以得到該裝置所對應的 module name。最後,只要使用 modprobe 去載入模組就大功告成了。

比較要注意的是 modules.pcimap,如果你詳細研究過這 map 檔,應該會發現同一個 vendor:device,卻偶爾有兩種可使用的 modules。例如使用 10ec:8139 去尋找,會同時找到 8139cp 和 8139too。諸如此類的裝置非常多,所以比對 modules.pcimap 時有其規則以免發生這樣的狀況:

比對的優先順序應該是:
  1. 尋找 vendor:device 和 subvendor:subdevice 相同的專用模組。
  2. 尋找 device 和 subvendor:subdevice 相同的通用模組。
  3. 尋找 device 建議模組。
  4. 尋找 vendor:device 標準模組。

使用 bash 完成的程式碼:
#!/bin/bash
MODULEMAP=/usr/share/frexhwd/modules.fhm

# findmodule [vendor] [device] [subvendor] [subdevice]
findmodule() {
while read VENDEV SUBVENDEV MODULE; do
if [ x"${3}${4}" = x"$SUBVENDEV" ]; then
echo $MODULE
return
fi
done <<-EOF
$(grep "^${1}${2}" $MODULEMAP)
EOF

while read VENDEV SUBVENDEV MODULE; do
if [ x"${3}${4}" = x"$SUBVENDEV" ]; then
echo $MODULE
return
fi
done lgt;lgt;-EOF
$(grep "^ffff${2}" $MODULEMAP)
EOF

while read VENDEV SUBVENDEV MODULE; do
if [ x"$MODULE" != x ]; then
echo $MODULE
return
fi
done <<-EOF
$(grep "^ffff${2}" $MODULEMAP)
EOF

while read VENDEV SUBVENDEV MODULE; do
if [ x"${1}${2}" = x"$VENDEV" ] && [ x"ffffffff" = x"$SUBVENDEV" ]; then
echo $MODULE
return
fi
done lgt;lgt;-EOF
$(grep "ffffffff" $MODULEMAP)
EOF

}

for DEVINFO in /sys/bus/pci/devices/*;do
VENDOR=`cat $DEVINFO/vendor`
VENDOR=${VENDOR:2}
DEVICE=`cat $DEVINFO/device`
DEVICE=${DEVICE:2}
SUBVENDOR=`cat $DEVINFO/vendor`
SUBVENDOR=${SUBVENDOR:2}
SUBDEVICE=`cat $DEVINFO/subsystem_device`
SUBDEVICE=${SUBDEVICE:2}
MODULE=`findmodule $VENDOR $DEVICE $SUBVENDOR $SUBDEVICE`
if [ x"$MODULE" != x ]; then
modprobe -q $MODULE &< /dev/null
fi
done


關於 /usr/share/frexhwd/modules.fhm,是以 modules.pcimap 為基礎,自行轉的檔案格式。我寫了一小段 Script 做轉換:
#!/bin/bash
while read MODULE VENDOR DEVICE SUBVENDOR SUBDEVICE OINFO; do
if [ x"$MODULE" = x"#" ] || [ x"$MODULE" = x ]; then
continue
fi
echo -e "${VENDOR:6}${DEVICE:6}\t${SUBVENDOR:6}${SUBDEVICE:6}\t$MODULE" >> $2
done < $1