接前一篇文章:
前文书解析了virtio_pci_driver的 probe 回调函数virtio_pci_probe()的前三段代码,本回继续讲解余下代码。为了便于理解和回顾,再次贴出virtio_pci_probe函数源码,在 Linux 内核源码/drivers/ virtio /virtio_pci_common.c中,如下所示:
- static int virtio_pci_probe(struct pci_dev *pci_dev,
- const struct pci_device_id *id)
- {
- struct virtio_pci_device *vp_dev, *reg_dev = NULL;
- int rc;
-
- /* allocate our structure and fill it out */
- vp_dev = kzalloc(sizeof(struct virtio_pci_device), GFP_KERNEL);
- if (!vp_dev)
- return -ENOMEM;
-
- pci_set_drvdata(pci_dev, vp_dev);
- vp_dev->vdev.dev.parent = &pci_dev->dev;
- vp_dev->vdev.dev.release = virtio_pci_release_dev;
- vp_dev->pci_dev = pci_dev;
- INIT_LIST_HEAD(&vp_dev->virtqueues);
- spin_lock_init(&vp_dev->lock);
-
- /* enable the device */
- rc = pci_enable_device(pci_dev);
- if (rc)
- goto err_enable_device;
-
- if (force_legacy) {
- rc = virtio_pci_legacy_probe(vp_dev);
- /* Also try modern mode if we can't map BAR0 (no IO space). */
- if (rc == -ENODEV || rc == -ENOMEM)
- rc = virtio_pci_modern_probe(vp_dev);
- if (rc)
- goto err_probe;
- } else {
- rc = virtio_pci_modern_probe(vp_dev);
- if (rc == -ENODEV)
- rc = virtio_pci_legacy_probe(vp_dev);
- if (rc)
- goto err_probe;
- }
-
- pci_set_master(pci_dev);
-
- vp_dev->is_legacy = vp_dev->ldev.ioaddr ? true : false;
-
- rc = register_virtio_device(&vp_dev->vdev);
- reg_dev = vp_dev;
- if (rc)
- goto err_register;
-
- return 0;
-
- err_register:
- if (vp_dev->is_legacy)
- virtio_pci_legacy_remove(vp_dev);
- else
- virtio_pci_modern_remove(vp_dev);
- err_probe:
- pci_disable_device(pci_dev);
- err_enable_device:
- if (reg_dev)
- put_device(&vp_dev->vdev.dev);
- else
- kfree(vp_dev);
- return rc;
- }
(4)调用pci_enable_device函数使能该 PCI 设备。代码片段如下:
- /* enable the device */
- rc = pci_enable_device(pci_dev);
- if (rc)
- goto err_enable_device;
pci_dev是virtio_pci_probe函数的第1个参数,类型为struct pci_dev。
pci_enable_device函数在Linux内核源码/drivers/pci/pci.c中,代码如下:
- /**
- * pci_enable_device - Initialize device before it's used by a driver.
- * @dev: PCI device to be initialized
- *
- * Initialize device before it's used by a driver. Ask low-level code
- * to enable I/O and memory. Wake up the device if it was suspended.
- * Beware, this function can fail.
- *
- * Note we don't actually enable the device many times if we call
- * this function repeatedly (we just increment the count).
- */
- int pci_enable_device(struct pci_dev *dev)
- {
- return pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO);
- }
- EXPORT_SYMBOL(pci_enable_device);
pci_enable_device_flags函数在同文件中,代码如下:
- static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags)
- {
- struct pci_dev *bridge;
- int err;
- int i, bars = 0;
-
- /*
- * Power state could be unknown at this point, either due to a fresh
- * boot or a device removal call. So get the current power state
- * so that things like MSI message writing will behave as expected
- * (e.g. if the device really is in D0 at enable time).
- */
- pci_update_current_state(dev, dev->current_state);
-
- if (atomic_inc_return(&dev->enable_cnt) > 1)
- return 0; /* already enabled */
-
- bridge = pci_upstream_bridge(dev);
- if (bridge)
- pci_enable_bridge(bridge);
-
- /* only skip sriov related */
- for (i = 0; i <= PCI_ROM_RESOURCE; i++)
- if (dev->resource[i].flags & flags)
- bars |= (1 << i);
- for (i = PCI_BRIDGE_RESOURCES; i < DEVICE_COUNT_RESOURCE; i++)
- if (dev->resource[i].flags & flags)
- bars |= (1 << i);
-
- err = do_pci_enable_device(dev, bars);
- if (err < 0)
- atomic_dec(&dev->enable_cnt);
- return err;
- }
(5)接下来,调用virtio_pci_legacy_probe或者virtio_pci_modern_probe函数来初始化该PCI设备对应的virtio设备。代码片段如下:
- if (force_legacy) {
- rc = virtio_pci_legacy_probe(vp_dev);
- /* Also try modern mode if we can't map BAR0 (no IO space). */
- if (rc == -ENODEV || rc == -ENOMEM)
- rc = virtio_pci_modern_probe(vp_dev);
- if (rc)
- goto err_probe;
- } else {
- rc = virtio_pci_modern_probe(vp_dev);
- if (rc == -ENODEV)
- rc = virtio_pci_legacy_probe(vp_dev);
- if (rc)
- goto err_probe;
- }
如果force_legacy标志为true,则先调用virtio_pci_legacy_probe函数,出现错误(-ENODEV或-ENOMEM)时再调用virtio_pci_modern_probe函数;如果force_legacy标志为false,则正好反过来,先调用virtio_pci_modern_probe函数,出现错误(-ENODEV或-ENOMEM)时再调用virtio_pci_legacy_probe函数。
force_legacy是模块参数,也在Linux内核源码/drivers/virtio/virtio_pci_common.c中,相关代码如下:
- static bool force_legacy = false;
-
- #if IS_ENABLED(CONFIG_VIRTIO_PCI_LEGACY)
- module_param(force_legacy, bool, 0444);
- MODULE_PARM_DESC(force_legacy,
- "Force legacy mode for transitional virtio 1 devices");
- #endif
- virtio_pci_legacy_probe
virtio_pci_legacy_probe函数在Linux内核源码/drivers/virtio/virtio_pci_legacy.c中,代码如下:
- /* the PCI probing function */
- int virtio_pci_legacy_probe(struct virtio_pci_device *vp_dev)
- {
- struct virtio_pci_legacy_device *ldev = &vp_dev->ldev;
- struct pci_dev *pci_dev = vp_dev->pci_dev;
- int rc;
-
- ldev->pci_dev = pci_dev;
-
- rc = vp_legacy_probe(ldev);
- if (rc)
- return rc;
-
- vp_dev->isr = ldev->isr;
- vp_dev->vdev.id = ldev->id;
-
- vp_dev->vdev.config = &virtio_pci_config_ops;
-
- vp_dev->config_vector = vp_config_vector;
- vp_dev->setup_vq = setup_vq;
- vp_dev->del_vq = del_vq;
-
- return 0;
- }
- virtio_pci_modern_probe
virtio_pci_modern_probe函数在Linux内核源码/drivers/virtio/virtio_pci_modern.c中,代码如下:
- /* the PCI probing function */
- int virtio_pci_modern_probe(struct virtio_pci_device *vp_dev)
- {
- struct virtio_pci_modern_device *mdev = &vp_dev->mdev;
- struct pci_dev *pci_dev = vp_dev->pci_dev;
- int err;
-
- mdev->pci_dev = pci_dev;
-
- err = vp_modern_probe(mdev);
- if (err)
- return err;
-
- if (mdev->device)
- vp_dev->vdev.config = &virtio_pci_config_ops;
- else
- vp_dev->vdev.config = &virtio_pci_config_nodev_ops;
-
- vp_dev->config_vector = vp_config_vector;
- vp_dev->setup_vq = setup_vq;
- vp_dev->del_vq = del_vq;
- vp_dev->isr = mdev->isr;
- vp_dev->vdev.id = mdev->id;
-
- return 0;
- }
下一回将对这两个函数尤其是virtio_pci_modern_probe()进行详细解析。