デバイスドライバロード時の動作

どういう訳か、見えない「何か」に背中を押されたので、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)にマッチするドライバがみつからないためと思われます。

で、その原因として考えられることは、

くらいかなあと思うので、カーネルが起動時に pci_read_config_xxx() で変な値を読み込んでしまっているのか、もしくはNICのコンフィグレーションレジスタに VenderID=0x0001 と本当に書き込まれてしまっているのかを切り分けてみるのも有効かと思われます。

今回の教訓

すごい人を下手にいぢると痛い目をみるお!