デバイスドライバロード時の動作
どういう訳か、見えない「何か」に背中を押されたので、Linuxがデバイスドライバをロードした時の挙動を追ってみようと思います。例として(?) # modprobe r8169 した場合の挙動を追うことにします。
# 少し長めなので結論を急ぐ方は こちら をどうぞ。
まずは drivers/net/r8169.c の rtl8169_init_module() が呼ばれるところからスタートします。
static int __init rtl8169_init_module(void) { return pci_register_driver(&rtl8169_pci_driver); } static void __exit rtl8169_cleanup_module(void) { pci_unregister_driver(&rtl8169_pci_driver); } module_init(rtl8169_init_module); module_exit(rtl8169_cleanup_module);
ここでやっていることは pci_register_driver() だけですね。
で、引数として渡している &rtl8169_pci_driver はこんな構造体です。
struct pci_driver { struct list_head node; char *name; const struct pci_device_id *id_table; int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); void (*remove) (struct pci_dev *dev); int (*suspend) (struct pci_dev *dev, pm_message_t state); int (*suspend_late) (struct pci_dev *dev, pm_message_t state); int (*resume_early) (struct pci_dev *dev); int (*resume) (struct pci_dev *dev); int (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable); void (*shutdown) (struct pci_dev *dev); struct pci_error_handlers *err_handler; struct device_driver driver; struct pci_dynids dynids; int multithread_probe; };
と、いろいろありますが、とりあえず今回注目するメンバは以下のふたつです。
id_table
このドライバがサポートするデバイスのVendorIDとDeviceIDの組を保持するバッファのポインタです。
r8139.cでは以下のデバイスをサポートしています。
10EC:8129 # Realtek: RTL-8129 10EC:8136 # Realtek: RTL8101E PCI Express Fast Ethernet controller 10EC:8167 # Realtek: RTL-8110SC/8169SC Gigabit Ethernet 10EC:8168 # Realtek: RTL8111/8168B PCI Express Gigabit Ethernet controller 10EC:8169 # Realtek: RTL-8169 Gigabit Ethernet 1186:4300 # D-Link System Inc: DGE-528T Gigabit Ethernet Adapter 1259:C107 # Allied Telesyn International製のなんか 16EC:0116 # U.S. Robotics: USR997902 10/100/1000 Mbps PCI Network Card 1737:1032 # Linksys: Gigabit Network Adapter
(*probe)(struct pci_dev *dev, const struct pci_device_id *id);
デバイスを初期化するために呼び出す関数のポインタ。
.probe = rtl8169_init_one
さて、それでは、pci_register_driver() の処理はどうなっているのでしょうか。
include/linux/pci.h では以下のようになっています。
static inline int __must_check pci_register_driver(struct pci_driver *driver) { return __pci_register_driver(driver, THIS_MODULE); }
本体の __pci_register_driver() は drivers/pci/pci-driver.c にあります。
int __pci_register_driver(struct pci_driver *drv, struct module *owner) { int error; /* initialize common driver fields */ drv->driver.name = drv->name; drv->driver.bus = &pci_bus_type; drv->driver.owner = owner; drv->driver.kobj.ktype = &pci_driver_kobj_type; if (pci_multithread_probe) drv->driver.multithread_probe = pci_multithread_probe; else drv->driver.multithread_probe = drv->multithread_probe; spin_lock_init(&drv->dynids.lock); INIT_LIST_HEAD(&drv->dynids.list); /* register with core */ error = driver_register(&drv->driver); if (!error) error = pci_create_newid_file(drv); return error; }
で、最後までまじめに追うと終わらないので少し飛ばします。
結局のところ、どこぞからか drivers/pci/pci-driver.c の pci_device_probe() が呼ばれ、pci_match_device() と pci_match_id() を経て、
static int __pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev) { const struct pci_device_id *id; int error = 0; if (!pci_dev->driver && drv->probe) { error = -ENODEV; id = pci_match_device(drv, pci_dev); if (id) error = pci_call_probe(drv, pci_dev, id); if (error >= 0) { pci_dev->driver = drv; error = 0; } } return error; } static int pci_device_probe(struct device * dev) { int error = 0; struct pci_driver *drv; struct pci_dev *pci_dev; drv = to_pci_driver(dev->driver); pci_dev = to_pci_dev(dev); pci_dev_get(pci_dev); error = __pci_device_probe(drv, pci_dev); if (error) pci_dev_put(pci_dev); return error; }
最終的には drivers/pci/pci.h の pci_match_one_device() で id_tableに登録されているIDとハードウエアのIDが一致するかどうかを確認できたら、初期関数である rtl8169_init_one() を pci_call_probe() で呼び出しているんだと思います・・・
static inline const struct pci_device_id * pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev) { if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) && (id->device == PCI_ANY_ID || id->device == dev->device) && (id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) && (id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) && !((id->class ^ dev->class) & id->class_mask)) return id; return NULL; }
まとめ?
というわけで、もし仮に lspci とかで「desc: "Unknown device 0001:8168"」と言われてデバイスを認識できないようなことがあったとしたら、それは未定義なベンダーID(0x0001)にマッチするドライバがみつからないためと思われます。
で、その原因として考えられることは、
- 工場出荷時のミス(ほんと?
- NICが壊れた(まじ?
- NICのファームがおかしい(おいおい!
- BIOSでNICがDisableになってる(あるかも?
- LinuxカーネルがPCIデバイスのスキャンに失敗してる(むぅ
- カルボナーラが食べたい(w
くらいかなあと思うので、カーネルが起動時に pci_read_config_xxx() で変な値を読み込んでしまっているのか、もしくはNICのコンフィグレーションレジスタに VenderID=0x0001 と本当に書き込まれてしまっているのかを切り分けてみるのも有効かと思われます。
今回の教訓
すごい人を下手にいぢると痛い目をみるお!