Linux网络驱动源码分析(一)

    技术2022-05-20  35

      网络驱动是一种典型的PCI设备驱动,无论在嵌入式平台还是在PC领域,网络相关的项目开发有着比较广阔的前景,因此,分析当前Linux内核中网络设备的驱动,不但能了解网络相关的基本原理,而且可以借鉴Linux内核的先进的技术,将其应用到嵌入式或其他领域。本文以Linux内核中的rtl8139网络驱动为例,对网络驱动的源码进行了简单分析,并对其中涉及的相关概念和技术进行了简单的介绍。

    一、PCI设备驱动模型

           rtl8139是典型的PCI设备,Linux内核的PCI核心驱动为PCI驱动开发者提供了方便的系统接口,极大地方便了PCI设备驱动的开发。

    1 pci设备驱动相关的数据结构

    pci驱动描述结构体 struct pci_driver {    struct list_head node; /*用于连接入pci驱动列表*/    char *name; /*pci驱动的名称*/    const struct pci_device_id *id_table; /* must be non-NULL for probe to be called *//*该驱动支持的pci设备*/    int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */    void (*remove) (struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */    int (*suspend) (struct pci_dev *dev, pm_message_t state); /* Device suspended */    int (*suspend_late) (struct pci_dev *dev, pm_message_t state);    int (*resume_early) (struct pci_dev *dev);    int (*resume) (struct pci_dev *dev); /* Device woken up */    void (*shutdown) (struct pci_dev *dev);    struct pci_error_handlers *err_handler;    struct device_driver driver;    struct pci_dynids dynids;};

    pci设备描述结构体

    struct pci_dev{    struct list_head bus_list; /* node in per-bus list */    struct pci_bus *bus; /* bus this device is on */    struct pci_bus *subordinate; /* bus this device bridges to */    void *sysdata; /* hook for sys-specific extension */    struct proc_dir_entry *procent; /* device entry in /proc/bus/pci */    struct pci_slot *slot; /* Physical slot this device is in */    unsigned int devfn; /* encoded device & function index */    unsigned short vendor;    unsigned short device;    unsigned short subsystem_vendor;    unsigned short subsystem_device;    unsigned int class; /* 3 bytes: (base,sub,prog-if) */    u8 revision; /* PCI revision, low byte of class word */    u8 hdr_type; /* PCI header type (`multi' flag masked out) */    u8 pcie_cap; /* PCI-E capability offset */    u8 pcie_type; /* PCI-E device/port type */    u8 rom_base_reg; /* which config register controls the ROM */    u8 pin; /* which interrupt pin this device uses */    struct pci_driver *driver; /* which driver has allocated this device */    u64 dma_mask; /* Mask of the bits of bus address this                       device implements. Normally this is                       0xffffffff. You only need to change                       this if your device has broken DMA                       or supports 64-bit transfers. */    struct device_dma_parameters dma_parms;    pci_power_t current_state; /* Current operating state. In ACPI-speak,                       this is D0-D3, D0 being fully functional,                       and D3 being off. */    int pm_cap; /* PM capability offset in the                       configuration space */    unsigned int pme_support:5; /* Bitmask of states from which PME#                       can be generated */    unsigned int pme_interrupt:1;    unsigned int d1_support:1; /* Low power state D1 is supported */    unsigned int d2_support:1; /* Low power state D2 is supported */    unsigned int no_d1d2:1; /* Only allow D0 and D3 */    unsigned int wakeup_prepared:1;    unsigned int d3_delay; /* D3->D0 transition time in ms */#ifdef CONFIG_PCIEASPM    struct pcie_link_state *link_state; /* ASPM link state. */#endif    pci_channel_state_t error_state; /* current connectivity state */    struct device dev; /* Generic device interface */    int cfg_size; /* Size of configuration space */    /*     * Instead of touching interrupt line and base address registers     * directly, use the values stored here. They might be      */    unsigned int irq;    struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O and memory regions + expansion ROMs */    resource_size_t fw_addr[DEVICE_COUNT_RESOURCE]; /* FW-assigned addr */    /* These fields are used by common fixups */    unsigned int transparent:1; /* Transparent PCI bridge */    unsigned int multifunction:1;/* Part of multi-function device */    /* keep track of device state */    unsigned int is_added:1;    unsigned int is_busmaster:1; /* device is busmaster */    unsigned int no_msi:1; /* device may not use msi */    unsigned int block_ucfg_access:1; /* userspace config space access is blocked */    unsigned int broken_parity_status:1; /* Device generates false positive parity */    unsigned int irq_reroute_variant:2; /* device needs IRQ rerouting variant */    unsigned int msi_enabled:1;    unsigned int msix_enabled:1;    unsigned int ari_enabled:1; /* ARI forwarding */    unsigned int is_managed:1;    unsigned int is_pcie:1; /* Obsolete. Will be removed.                       Use pci_is_pcie() instead */    unsigned int needs_freset:1; /* Dev requires fundamental reset */    unsigned int state_saved:1;    unsigned int is_physfn:1;    unsigned int is_virtfn:1;    unsigned int reset_fn:1;    unsigned int is_hotplug_bridge:1;    unsigned int __aer_firmware_first_valid:1;    unsigned int __aer_firmware_first:1;    pci_dev_flags_t dev_flags;    atomic_t enable_cnt; /* pci_enable_device has been called */    u32 saved_config_space[16]; /* config space saved at suspend time */    struct hlist_head saved_cap_space;    struct bin_attribute *rom_attr; /* attribute descriptor for sysfs ROM entry */    int rom_attr_enabled; /* has display of the rom attribute been enabled? */    struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* sysfs file for resources */    struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE]; /* sysfs file for WC mapping of resources */#ifdef CONFIG_PCI_MSI    struct list_head msi_list;#endif    struct pci_vpd *vpd;#ifdef CONFIG_PCI_IOV    union {        struct pci_sriov *sriov; /* SR-IOV capability related */        struct pci_dev *physfn; /* the PF this VF is associated with */    };    struct pci_ats *ats; /* Address Translation Service */#endif}

        驱动开发者要想为某个PCI设备开发驱动就必须定义一个与当前PCI设备相对应的pci_driver数据结构,用来描述将要开发的pci驱动的相关信息,比如驱动的名称,当前驱动可以支持哪些设备,以及当前驱动支持的一些操作等,类似地,还需要有个结构体来表示PCI设备,描述PCI设备的硬件信息,如厂商ID,设备ID,以及各种资源等,详见注释。

    二、PCI核心驱动API

    Linux内核的PCI驱动为PCI设备驱动的开发提供了方便的结构,下面列举几个常用的接口:

    pci_register_driver(struct pci_driver *drv)

    功能:注册PCI驱动,参数为要注册的pci驱动的结构体。

    下面来详细的分析以下这个函数,如此,才能更清楚的了解驱动和设备的匹配过程。

    pci_register_driver->driver_register(&drv->driver);->bus_add_driver->driver_attach->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

    在这个过程中有涉及到一个更为抽象的结构体struct device_driver,它是pci_driver的更高级的抽象,即下层是pci_driver,其上是device_driver,这符合通常的程序设计逻辑,越往上层抽象级别越高,因为在操作系统看来,它并不需要知道具体是什么设备,所有的设备对操作系统来说都是相同的,即都用struct device_driver来表示。

    在driver_register中先调用driver_find(drv->name, drv->bus),首先在相应的总线上查找drv->name的驱动是否已经被注册过,如果被注册过则返回,否则进行注册过程,即调用bus_add_driver(drv)。

    int bus_add_driver(struct device_driver *drv)函数首先判断当前总线是否支持自动探测,如果执行则执行探测函数driver_attach(drv)。

    if (drv->bus->p->drivers_autoprobe) {        error = driver_attach(drv);        if (error)            goto out_unregister;    }int driver_attach(struct device_driver *drv){    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);}

    这个函数对PCI总线的上所有已经连接的PCI设备与当前的PCI驱动进程一次匹配的过程,即对每一个PCI设备都调用匹配函数__driver_attach。

    static int __driver_attach(struct device *dev, void *data){    struct device_driver *drv = data;    /*     * Lock device and try to bind to it. We drop the error     * here and always return 0, because we need to keep trying     * to bind to devices and some drivers will return an error     * simply if it didn't support the device.     *     * driver_probe_device() will spit a warning if there     * is an error.     */    if (!driver_match_device(drv, dev))        return 0;    if (dev->parent) /* Needed for USB */        device_lock(dev->parent);    device_lock(dev);    if (!dev->driver)        driver_probe_device(drv, dev);    device_unlock(dev);    if (dev->parent)        device_unlock(dev->parent);    return 0;}

    该函数首先判断总线提供的match函数是否为空,如果非空则执行总线提供的match函数,在rtl8139网络驱动中,match非空,参见代码:

    drv->driver.bus = &pci_bus_type;struct bus_type pci_bus_type = {    .name = "pci",    .match = pci_bus_match,    .uevent = pci_uevent,    .probe = pci_device_probe,    .remove = pci_device_remove,    .shutdown = pci_device_shutdown,    .dev_attrs = pci_dev_attrs,    .bus_attrs = pci_bus_attrs,    .pm = PCI_PM_OPS_PTR,};

    这里将match函数即pci_bus_match,最后该函数调用到

    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;}

    在这里进行了pci_driver和pci_dev的匹配,如果匹配成功,则返回pci_device_id。如果匹配不成功,而且当前设备还没有驱动,则调用driver_probe_device(drv,dev)。

    int driver_probe_device(struct device_driver *drv, struct device *dev){    int ret = 0;    if (!device_is_registered(dev))        return -ENODEV;    pr_debug("bus: '%s': %s: matched device %s with driver %s/n",         drv->bus->name, __func__, dev_name(dev), drv->name);    pm_runtime_get_noresume(dev);    pm_runtime_barrier(dev);    ret = really_probe(dev, drv);    pm_runtime_put_sync(dev);    return ret;}

    执行到这里说明,说明PCI总线没有提供match函数或者总线提供的match函数返回非空。还需要进行更深层次的探测,至少在总线提供的match函数中仅仅是进行了匹配,并没有将驱动和设备关联起来,这些操作就是在下面的函数中实现的。

    int driver_probe_device(struct device_driver *drv, struct device *dev){    int ret = 0;    if (!device_is_registered(dev))        return -ENODEV;    pr_debug("bus: '%s': %s: matched device %s with driver %s/n",         drv->bus->name, __func__, dev_name(dev), drv->name);    pm_runtime_get_noresume(dev);    pm_runtime_barrier(dev);    ret = really_probe(dev, drv);    pm_runtime_put_sync(dev);    return ret;}

    重点看really_probe函数:

     

    static int really_probe(struct device *dev, struct device_driver *drv){    int ret = 0;    atomic_inc(&probe_count);    pr_debug("bus: '%s': %s: probing driver %s with device %s/n",         drv->bus->name, __func__, drv->name, dev_name(dev));    WARN_ON(!list_empty(&dev->devres_head));    dev->driver = drv;    if (driver_sysfs_add(dev)) {        printk(KERN_ERR "%s: driver_sysfs_add(%s) failed/n",            __func__, dev_name(dev));        goto probe_failed;    }    if (dev->bus->probe) {        ret = dev->bus->probe(dev);        if (ret)            goto probe_failed;    } else if (drv->probe) {        ret = drv->probe(dev);        if (ret)            goto probe_failed;    }    driver_bound(dev);    ret = 1;    pr_debug("bus: '%s': %s: bound device %s to driver %s/n",         drv->bus->name, __func__, dev_name(dev), drv->name);    goto done;probe_failed:    devres_release_all(dev);    driver_sysfs_remove(dev);    dev->driver = NULL;    if (ret != -ENODEV && ret != -ENXIO) {        /* driver matched but the probe failed */        printk(KERN_WARNING               "%s: probe of %s failed with error %d/n",               drv->name, dev_name(dev), ret);    }    /*     * Ignore errors returned by ->probe so that the next driver can try     * its luck.     */    ret = 0;done:    atomic_dec(&probe_count);    wake_up(&probe_waitqueue);    return ret;}

     

    在此函数中,首先将驱动和设备关联起来,即红色代码dev->driver = drv; 指明了当前设备的驱动。按照常规的程序设计思想,驱动和设备关联后是否还需要做一些其他工作才能是设备在相应的驱动下正常工作呢,这就是probe函数实现的功能了,很明显设备和驱动获取都需要做一些工作,因此这里分别留出设备和驱动的probe函数。其中设备的probe即设备所在总线的probe,这里暂且不去分析,因为与网络驱动关系不大,都是PCI总线相关的东西,重点来看驱动的probe,在前面提到的pci_driver结构体中,对于rtl8139驱动来说,其pci_driver结构体被初始化为:

    static struct pci_driver rtl8139_pci_driver = {    .name = DRV_NAME,    .id_table = rtl8139_pci_tbl,    .probe = rtl8139_init_one,    .remove = __devexit_p(rtl8139_remove_one),#ifdef CONFIG_PM    .suspend = rtl8139_suspend,    .resume = rtl8139_resume,#endif /* CONFIG_PM */};

    这里即调用rtl8139_init_one,经过上面的逐层分析,我们从pci核心驱动一步一步的走到了rtl8139网络设备的驱动,豁然开朗了,以后看网络驱动的时候就不会感到开始的地方有点迷糊了。代码分析重在代码之间的过渡,如果衔接不好,很多地方都会产生疑问。在下一篇文章中,将会详细分析rtl8139网络驱动的代码。


    最新回复(0)